Using Audioroute in your project

With just minimal modifications to your app you can make it available to be hosted inside other apps.
The SDK makes the (likely) assumption that your app UI is based in Java or Kotlin while the audio engine runs in NDK C/C++ native code.

Hosted apps (Synths, Effects etc.)

An Hosted App must:

  1. Add directives in the app manifest and build.gradle
  2. Pass Activity events to Audioroute in your Activity class
  3. Configure native callbacks to handle the audio and MIDI data
  4. Handle switching from audio device to/from Audioroute when the connection is started/closed
  5. Handle MIDI events
  6. Register the app in the Audioroute developer control panel
  7. Test your app with an Audioroute compatible host
Please feel free to contact us at [email protected] or to post in the forum if you find issues with the SDK or with this guide.
Let's begin!

1. Add directives in the app manifest and build.gradle

  • Add this dependency to your build.gradle:
                    
                            implementation 'com.ntrack.audioroute:audioroute:1.0.0'
                    
            
  • Add this line in the <manifest><application> section of your manifest file:

    		
    			<service android:name="com.ntrack.audioroute.AudioModuleForegroundService"></service>
    		
    	
    This is used by Audioroute to launch a foreground service for the app that will show a notification icon to make the hosted app activity remain active in the background when the user switches back to the host app or to other hosted apps.
    Optional: If you don't want Audioroute to automatically show the notification icon because you are already showing one call AudiorouteActivityController.setShowForegroundServiceNotification(false). But make sure your activity can't be destructed by the system when you are hosting Audioroute modules, otherwise unexpected behavior will occur.

  • Add this line in the <manifest> section of your manifest file (required only if you set targetSdkVersion=28):
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
  • Make sure that your activity launch mode must be 'singleTop', 'singleTask' or 'singleInstance', in manifest:
    		
    			<activity android:name=".MainActivity"android:launchMode="singleTop" >
    		
    	
    'standard' launch mode is not currently supported.

2. Pass Activity events to Audioroute in your Activity class

  • In your activity onCreate create an instance of AudiorouteActivityController and invoke it like this:
    
    	@Override
      protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        controller= AudiorouteActivityController.getInstance();
        controller.setModuleLabel("My app name");
        controller.setModuleImage(myAppIcon);
        controller.setListener(new AudiorouteActivityController.Listener() {
          @Override
          public void onRouteConnected() {
            // You're connected, update your UI etc.
          }
    
          @Override
          public void onRouteDisconnected() {
            // You're now disconnected
          }
    
          @Override
          public AudioModule createAudioModule() {
            return new MyAudiorouteModule();
          }
        });
        controller.onActivityCreated(this, false);
      }
      @Override
      protected void onResume()
      {
        super.onResume();
        controller.onResume();
      }
      @Override protected void onNewIntent (Intent intent)
      {
        super.onNewIntent(intent);
        controller.onNewIntent(intent);
      }
    
  • Implement your subclass of AudioModule. Handle configure_module() and route it to your native code, which must register an audio callback:
    
    public class MyAppAudioModule extends AudioModule {
    private long my_native_resources = 0;
    
    @Override protected boolean configure_module(String name, long handle) {
        my_native_resources=configureNativeComponents(handle, channels);
    
        return my_native_resources!=0;
      }
    
      @Override
      protected void release() {
        if (my_native_resources != 0) {
          release(my_native_resources);
          my_native_resources = 0;
        }
      }
    
    private native long configureNativeComponents(long handle, int channels);
    
    private native void release(long native_resources_pointer);
    
    

3. Add native code process and initizialization callbacks

  • Implement configureNativeComponents and pass to Audioroute your process and initialize callbacks in your native code:
    
    
    #include "path_where_you_cloned_sdk/AudioRouteSamples/audioroute/include/audio_module.h"
    		
    // My audio callback		
    static void process_func(void *context, int sample_rate, int framesPerBuffer,
         int input_channels, const float *input_buffer,
         int output_channels, float *output_buffer, MusicEvent *events, int eventsNum, int instance_index, struct AudiorouteTimeInfo *timeInfo) {
        my_native_data *data = (my_native_data *) context;
        int currentEvent=0;
        for(int i=0; i<framesPerBuffer; ++i) {
        	// Your audio processing here
        	...
    	}
    }
    
    // Audio processing initialization (called before the actual processing starts)
    // NOTE: the init_func may be called by an arbitrary thread. If you need to update UI inside this function make sure 
    // you invoke the code responsible for updating the UI in the UI thread
    static void init_func(void *context, int sample_rate, int framesPerBuffer, int instance_index, int connectedInputBuses[MaxNumBuses], int connectedOutputBuses[MaxNumBuses])
    {
    	// do your initialization stuff here, allocate resources based on sample_rate and framesPerBuffers etc.
    }
    
    // Tell Audioroute what your initialization and process callbacks are
    extern "C" JNIEXPORT jlong JNICALL
    Java_com_mycompany_myapp_MyAppAudioModule_configureNativeComponents
    (JNIEnv *env, jobject obj, jlong handle, jint channels) {
      my_native_data *data = (my_native_data *)malloc(sizeof(my_native_data));
      if (data) {
          audioroute_configure_java(env, obj, process_func, init_func, data);
      }
      return (jlong) data;
    }
    
    
    // Release data allocated when connected to Audioroute
    extern "C" JNIEXPORT void JNICALL
    Java_com_mycompany_myapp_MyAppAudioModule_release__J
    (JNIEnv *env, jobject obj, jlong p) {
      my_native_data *data = (my_native_data *) p;
      free(data);
    }
    
    
    Notice the inclusion of audio_module.h, you can find the file in the Audioroute SDK. Hosted apps don't need to link to the Audioroute native library libaudioroute.so. The lib is required only for host apps.
  • After you've configured your module you'll receive a callback to your initialization callback and then callbacks to your process callback

4. Handle switch from audio device I/O to/from Audioroute

  • After the connection is established you should stop or mute your normal audio I/O and only resume when the Audioroute connection is shut down (and your Activity is Resumed).
    When Audioroute is connected the AudiorouteActivityController.Listener listener you've passed to AudiorouteActivityController receives the onRouteConnected call. Inside the call do something like this:
    
    @Override public void onRouteConnected() {
    	new Handler(Looper.getMainLooper()).post(new Runnable() {
              @Override
              public void run() {
                stopAudioDevice();
                updateUI();
              }
            });
    }
    
    
    And when the app is disconnected restart the audio (after checking that the app is visible to the user, i.e. if the activity is not paused):
    
    @Override public void onRouteDisconnected() {
            //isAudiorouteConnected = false;
            new Handler(Looper.getMainLooper()).post(new Runnable() {
              @Override
              public void run() {
                showHostIcon(false);
                if(IsAppVisible()) 
    		startAudioDevice();
              }
            });
    }
    
    
  • After you are connected to an Audioroute host, display somewhere in your UI an icon of the host app. You can get the icon drawable using the getHostIcon() method. When the icon is clicked switch to the host app with switchToHostApp().
  • When the host closes the hosted app instance it will call AudioModule.release(). Use this call to free resources in your Java and native code. Audioroute will automatically shut down the foreground service and notification icon.

5. Handling MIDI

  • If your app handles MIDI the host will send MusicEvent objects in your process callback, which should generate sounds corresponding to the input notes.
  • The MusicEvent class support structured MIDI data, with high resolution (i.e. float) velocities and controllers. If your app already handles MIDI in the standard 4 bytes raw format, you can easily convert from MusicEvents to standard 32 bit words using ConvertMusicEventToRawMIDI().

6. Register the app in the Audioroute Developer Console

  • For your app to appear in the list of available modules (even during development) inside an Audioroute host you must register the app in the Audioroute developer console
    You can use the app version number to tell Audioroute to show your app only when the device has the version of the app >= than the number entered in the dev console.

7. Test your app with an Audioroute compatible host

Host apps

  • Add this line in the <manifest><application> section of your manifest file:
    		
    			<service android:name="com.ntrack.audioroute.AudioModuleForegroundService"></service>
    		
    	
    and this line in the <manifest> section (required only if you set targetSdkVersion=28):
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE">
    The lines above are used by Audioroute to launch a foreground service for the app that will show a notification icon to make the host activity remain active in the background when the user switches to hosted apps.
    Optional: If you don't want Audioroute to automatically show the notification icon because you are already showing one call AudioRouteHostController.setShowForegroundServiceNotification(false). But make sure your activity can't be destructed by the system when you are hosting Audioroute modules, otherwise unexpected behavior will occur.
  • Add the internet permission to your manifest. Access to internet is required for hosts to be able to download the current list of apps that support Audioroute. Here's the code snippet:
                    
                            <uses-permission android:name="android.permission.INTERNET"/>
                    
            
  • Activity launch mode must be 'singleTop', 'singleTask' or 'singleInstance', in manifest:
    		
    			<activity android:name=".MainActivity"android:launchMode="singleTop" >
    		
    	
    'standard' launch mode is not currently supported.

  • Download the SDK from Github and add libaudiomodule.so library to your CMakeList.txt or Android.mk makefile.

    Add this dependency to your build.gradle:

    		
    			implementation 'com.ntrack.audioroute:audioroute:1.0.0'
    		
    	
  • Use the AudioRouteHostController class to scan installed apps and instantiate them:
    • Scan the available modules using scanInstalledModules
    • Set the host icon to show in the notification area with setHostNotificationIcon()
    • Call instantiateAudioRouteModuleAsPlugin to instantiate the module you've chosen with OnModuleCreatedListener listener
    • Call AudioRouteHostController on onNewIntent()
    • OnModuleCreatedListener.onModuleCreated will be called with info on the module instance. Pass that info to the native code.
    • Call
      				
      					audioroute_initialize(engine_ptr, samplingFrequency, framesPerBuffer, instanceIndex, connectedInputBuses, int connectedOutputBuses)
      				
      			
      engine_ptr, moduleIndex and instanceIndex are the values passed by OnModuleCreatedListener.onModuleCreated. framesPerBuffer, numChannels and samplingFrequency represet your current audio configuration. connectedInputBuses and connectedOutputBuses members are currently either 0 or 2, i.e. we only support stereo streams.
    • Inside your audio callback call audioroute_process().

      Note: Make sure you call audioroute_initialize() before calling audioroute_process(). If you need to re-initialize (after changing sample rate, frame sizes etc.) call audioroute_initialize() again.
      Thread synchronization between audioroute_initialize() and audioroute_process() must be handled by the host app.
      The audioroute_initialize() call should be performed before your audio engine starts or before adding the module to your engine

    • After you are connected the Audioroute module, display somewhere in your UI an icon of the hosted module. You can get the icon drawable using the getPackageIcon() method. When the icon is clicked switch to the hosted app with showUserInterface().
    • When you're done with the hosted app call AudioRouteHostController.releaseModuleInstance() to shut down the connection.

Advanced features

Supporting multiple instances [optional]

Audioroute supports having multiple instances of a module hostd inside the same app. This is implemented by using an instanceId. Each time you instantiate a module with instantiateAudiorouteModule you'll get back in onModuleCreated(ModuleRuntimeInfo m) a new instanceId that you can then pass along to the audioroute_initialize() and audioroute_process() calls.
The hosted app can use the instanceId as an index in a list/array that contains the data for each instance of the module. Look at how instanceId is used in the SimpleSynth sample, which allows multiple instances, recognizable by switching each between the sine and saw tooth sounds.
AudioRouteHostController.showUserInterface() takes an instanceId parameter, and the hosted app can use the AudioModule.getCurrentInstanceId() method to know for which instance to show the user interface.

Depending on how the hosted app is structured implementing multiple instances may make the implementation of Audioroute more complex so you can opt out of multiple instances and support a single instance of your app. We expect many existing apps to opt out (you can always opt back in later). If the hosts knows that you app supports a single instance will not allow the user to add a new instance when another is already instantiated. You can set whether the app support multiple instances in the online control panel.

Resiliency

An hosted app may become unresponsive, freeze or crash. Audioroute is designed to handle these unexpected situations gracefully. An host app can detect that the hosted app is no longer responding when the audioroute_initialize or audioroute_process calls return failure. When that happens the host should update the UI to show the app being in a 'zombie' state. When the user taps on the app icon the host can try to resuscitate the hosted app using AudioRouteHostController.resuscitateApp(). The hosted app will be re-instantiated. If the hosted app saves its state it may be able to restore it and continue with the same settings that were last used. The instanceId will remain the same as before the unexpected disconnection.

Saving state and parameters

Saving and restoring the state of hosted apps is not currently supported. We plan to add this in the next revision. We also plan to add the concept of parameters to allow the host to control and automate the settings of hosted apps like you would do with a desktop plugin.

Requirements

The Audioroute SDK has minimum SDK version = 16, but the SDK calls will fail (without crashing) if invoked on devices with API level 22 or lower. To use Audioroute the devices must have Android 6.0 (API 23) or later.

Questions?

Visit the Audioroute forum, or contact us at [email protected].