Adding the Dolby API to your Unity Project

While developing Notespace with Amorse Inc, we decided to implement the Dolby API into our tablet based game. The game is a interactive musical activity book for girls so it makes a whole bunch of sense. As of the date of this post, the Dolby API is integrated into the following:

Acer Iconia Tab 10 2014
Amazon Kindle Fire HDX 7 2012
Amazon Kindle Fire HDX 8.9 2012
Amazon Kindle Fire HD 7 2013
Amazon Kindle Fire HDX 7 2013
Amazon Kindle Fire HDX 8.9 2013
Amazon Fire HDX 7 2014
Amazon Fire HDX 8.9 2014
Amazon Fire HD 6 2014
Fujitsu Arrow F-06E
Lenovo Yoga 8 2013
Lenovo Yoga 10 2013
Lenovo Yoga Tablet 2 8” 2014
Lenovo Yoga Tablet 2 10” 2014
Lenovo Yoga Tablet 2 Pro 2014
Samsung Galaxy Tab 3 8.0 2013
Samsung Galaxy Tab 3 10.1 2013
Tesco Hudle2 Tablet
ZTE Grand S Flex
ZTE Grand S
ZTE Grand Memo LTE
ZTE Grand Memo N5S
ZTE Nubia Z5S
ZTE Nubia Z5S Mini
ZTE V975
ZTE N5S
ZTE Geek U988S
ZTE Boost Max
ZTE ZMax
ZTE BladeBox

As you can see, we know that its not on iOS devices (boo) but is on every Kindle Fire device past first gen. Thats a sizable market to optimize our experience for! We also know, for development down the road, that every Windows 8 device will have Dolby hardware. This is not necessarily true for Windows 8 mobile though, so don’t get them mixed up.

First, we are targeting the Kindle Fire tablet so we need to get our android environment set up and our Amazon environment set up. Their are many tutorials on this but the one on the Amazon developer site is perfect. Their are a few gotchas to be aware of:

  • OSX 10.10 / Java Versions: I am currently using the latest Android SDK and OSX 10.10 (Yosemite) and had my build fail. The error message was:
    warning: java/lang/Object.class(java/lang:Object.class): major version 51 is newer than 50, the highest major version supported by this compiler.

    The reason for this was that I needed to update my Java version to above 1.6 which is what is installed on OSX. Java 1.7 was notoriously bad for OSX, especially when creating Android key stores, but it seems like 1.8 is fine. I’ll do my best to keep you up to date with any issues I have had. To fix this error, just follow the solution found here. Take special note of the OSX 10.10 instructions further down.

  • Android Manifest: If you are using other Android plugins, double check you AndroidManifest.xml file in Plugins/Android to ensure that one plugin is not overwriting the other’s manifest entries.

Now that we have all of our Amazon stuff set up, our first Dolby step is to download the SDK from the Dolby Developer site (http://developer.dolby.com). For Unity, go to the Frameworks section under “Tools & Tech”… or just click here. You can download the package from the Unity Asset Store but its the exact same thing as the SDK. You don’t get any free implementation doing it that way.

The first issue you will find is that you get a bunch of Android libs an no documentation telling you best practice on which one to use. I did a little research on my target audience min Android level and discovered that the lowest android level of the lowest end Kindle device that supported Dolby was 4.0 (Kindle Fire HD (2nd gen kindle fire)). So based on this assumption, I used the files from the “Android 4.0” folder. I am gathering that 4.0 is the lowest Android version that Dolby will go, so I have considered setting my min Android version in my Unity build settings to 4.0. All my target devices are 4.0 and up, my prime target being the Samsung Galaxy Tab 2 due to its market penetration. If you are looking for Kindle Fire tech specs, they are here. Interesting thing to note is that the GPU’s are not consistent which means we should make multiple binaries; one per texture compression method. I am using mainly 32bit RGBA so it doesn’t really phase me for this project.

Now that we have the files, lets get started:

  1. Copy the contents of the “Android 4.0” folder into the “Plugins/Android” folder. Unfortunatelly, you can’t use subfolders for organization (I tried) which is a little bit of a bummer. So now you should have a “DSJavaPlugin.jar” and a “libDSPlugin.so” in your “Plugins/Android” folder. FYI, the .so is compiled C code and the .jar is the Java bridge file which basically tells Unity how to access the C library.
  2. Make a manager script to connect to the native Android Dolby plugin. Native Android basically means that the API was written in C for performance and then has a Java bridge that allows us to connect to it. This method allows you to use some useful C libraries (like an OGG Vorbis encoder!) in Unity. Now the Dolby API has some example files in it to do just this. One works and one does not due to faulty DLLImport code. Use this one, it works and has a little something extra to help cycle through the Dolby profiles
    //********************************************
    //* GLOBAL DOLBY AUDIO CONTROLLER
    //********************************************
    using UnityEngine;
    using System;
    using System.Collections;
    #if UNITY_ANDROID
    using System.Runtime.InteropServices; //Allows us to use DLLImport... Dont think this works on iOS
    #endif
    
    //********************************************
    //* CLASS
    //********************************************
    public class DolbyManager : MonoBehaviour {
    
    	//****************************************
    	//* DLL Imports
    	//****************************************
    #if UNITY_ANDROID && !UNITY_EDITOR
    	[DllImport("DSPlugin")]
    	public static extern bool isAvailable();
    	[DllImport("DSPlugin")]
    	public static extern int initialize();
    	[DllImport("DSPlugin")]
    	public static extern int setProfile(int profileid);
    	[DllImport("DSPlugin")]
    	public static extern int suspendSession();
    	[DllImport("DSPlugin")]
    	public static extern int restartSession();
    	[DllImport("DSPlugin")]
    	public static extern void release();
    #else
    
    	//*** If Not desired Platform, return dummy data
    	public static bool isAvailable(){return false;}
    	public static int initialize(){return -1;}
    	public static int setProfile(int profileid){return -1;}
    	public static int suspendSession(){return -1;}
    	public static int restartSession(){return -1;}
    	public static void release(){}
    #endif
    
    	//****************************************
    	//* ENUMS
    	//****************************************
    	public enum DolbyProfile : int {
    		Off = -1,
    		Movie = 0,
    		Music = 1,
    		Game = 2,
    		Voice = 3
    	}
    	
    	//****************************************
    	//* CONSTANTS
    	//****************************************
    	
    	//****************************************
    	//* PROPERTIES
    	//****************************************
    
    	//****************************************
    	//* VARIABLES
    	//****************************************
    	[HideInInspector] public bool ready = false;
    	[HideInInspector] public DolbyProfile profile = DolbyProfile.Game;
    
    	private int initRetrys = 10;
    
    	//****************************************
    	//* UNITY METHODS
    	//****************************************
    	private void Awake(){
    
    		//*** Initialise
    		Init();
    	}
    	private void Start() {
    	}
    	private void OnDestroy(){
    
    		//*** releas eAPI
    		release();
    	}
    
    	private void OnApplicationPause(bool pPauseStatus) {
    
    		//*** IF Pausing and dolby is active
    		if(
    			(pPauseStatus)
    			&&(profile != DolbyProfile.Off)
    		) {
    			suspendSession();
    		}
    	}
    	private void OnApplicationFocus(bool pFocusStatus) {
    
    		//*** If Gaining focus (being switched to) and not Off
    		if(
    			(pFocusStatus)
    			&&(profile != DolbyProfile.Off)
    		) {
    			restartSession();
    		}
    	}
    	private void OnApplicationQuit() {
    
    		//*** Release Everything Dolby
    		release();
    	}
    
    	//****************************************
    	//* MAIN METHODS
    	//****************************************
    	private void Init(){
    
    		//*** If Any Android
    #if UNITY_ANDROID
    
    		//*** If Dolby is availibel
    		if (isAvailable()) {
    
    			//*** If Initialised
    			if (initialize() > -1) {
    
    				//*** Set Ready
    				ready = true;
    
    				//*** Set teh Default profile (cant be off)
    				setProfile(Mathf.Min((int)profile, 0));
    			}
    
    			//*** Not Inited and not out of retrys
    			else if (initRetrys > 0) {
    
    				//*** Consume a retyr
    				initRetrys --;
    
    				//*** Wati and try again
    				StartCoroutine(Delay());
    			}
    		}
    #endif
    	}
    	private IEnumerator Delay(){
    
    		// Wait 100ms to make sure Dolby service is enabled
    		yield return new WaitForSeconds(0.1f);
    		Init();
    	}
    	public void SwitchProfiles(){
    
    		//*** Save Previous Profile
    		DolbyProfile oProfile = profile;
    
    		//*** Get Index of current profile
    		int xIndex = (int)profile;
    
    		//*** Inc and fold
    		xIndex++;
    		if(xIndex >= 4){
    			xIndex = -1;
    		}
    
    		//*** Convert back to profile
    		profile = (DolbyProfile)xIndex;
    
    		//*** If Turning Off
    		if(profile == DolbyProfile.Off) {
    
    			//*** Susprend Session (turn Off)
    			suspendSession();
    		}
    
    		//*** If Regular profile
    		else {
    
    			//*** If Just Tunred On
    			if(oProfile == DolbyProfile.Off){
    
    				//*** Turn On
    				restartSession();
    			}
    
    			//*** Set Profile
    			setProfile(xIndex);
    		}
    	}
    
    	//****************************************
    	//* STATIC METHODS
    	//****************************************
    	public static DolbyManager Create(){
    
    		//*** Make Self
    		GameObject oDolbyContainer = new GameObject("DolbyManager", new Type[]{ typeof(DolbyManager) });
    
    		//*** Return Instance
    		return oDolbyContainer.GetComponent<DolbyManager>();
    	}
    }
  3. Add your controller to the scene. You can do this either programmatically or just make a GameObject and slap it on. Either way, if this object is not nested under a global object that is not being being destroyed between scenes, you will need to add the following code to your “Awake” method:
    //*** Do not destroy
    DontDestroyOnLoad(gameObject);

And now we are done! If you want to switch through your Dolby profiles, use the “SwitchProfiles()” method. You will also need to reference this class somehow. I leave that up to you. You could either link it to your global / persistent object or make this class into a Singleton and access it through a static instance.

The results on the Kindle 8.9 HD was really astounding! With earphones, the audio sounded big. It felt like I wasn’t wearing headphones and was incredibly immersive. With out headphones, the audio was louder and more full. One of the problems I have had with the iPad is that the bass drops out on the speakers and even with the Kindle, with out the Dolby, the game was hard to hear even at full volume. Big thanks Dolby people, you have made my game better!