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.
- Download the SDK and sample code on Github
Current version:1.0.1
- Play with the sample AudiorouteHost and the SimpleSynth and DelayEffect apps included in the Audioroute SDK Android Studio project
- Watch the Video Tutorial
Hosted apps (Synths, Effects etc.)
An Hosted App must:
- Add directives in the app manifest and build.gradle
- Pass Activity events to Audioroute in your Activity class
- Configure native callbacks to handle the audio and MIDI data
- Handle switching from audio device to/from Audioroute when the connection is started/closed
- Handle MIDI events
- Register the app in the Audioroute developer control panel
- Test your app with an Audioroute compatible host
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.1'
-
Add this line in the
<manifest><application>
section of your manifest file:<service android:name="com.ntrack.audioroute.AudioModuleForegroundService"></service>
Optional: If you don't want Audioroute to automatically show the notification icon because you are already showing one callAudiorouteActivityController.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" >
2. Pass Activity events to Audioroute in your Activity class
-
In your activity
onCreate
create an instance ofAudiorouteActivityController
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
. Handleconfigure_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:
Notice the inclusion of#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); }
audio_module.h
, you can find the file in the Audioroute SDK. Hosted apps don't need to link to the Audioroute native librarylibaudioroute.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 theAudiorouteActivityController.Listener
listener you've passed toAudiorouteActivityController
receives theonRouteConnected
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(); } }); }
@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 withswitchToHostApp()
. -
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
-
Currently available hosts include AudioRoute Sample Host and n-Track Studio
Host apps
-
Add this line in the
<manifest><application>
section of your manifest file:<service android:name="com.ntrack.audioroute.AudioModuleForegroundService"></service>
<manifest>
section (required only if you settargetSdkVersion
=28):
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.<uses-permission android:name="android.permission.FOREGROUND_SERVICE">
Optional: If you don't want Audioroute to automatically show the notification icon because you are already showing one callAudioRouteHostController.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" >
-
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.1'
-
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 withOnModuleCreatedListener
listener -
Call
AudioRouteHostController ononNewIntent()
-
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
andinstanceIndex
are the values passed byOnModuleCreatedListener.onModuleCreated
.framesPerBuffer
,numChannels
andsamplingFrequency
represet your current audio configuration.connectedInputBuses
andconnectedOutputBuses
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 callingaudioroute_process()
. If you need to re-initialize (after changing sample rate, frame sizes etc.) callaudioroute_initialize()
again.
Thread synchronization betweenaudioroute_initialize()
andaudioroute_process()
must be handled by the host app.
Theaudioroute_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 withshowUserInterface()
. -
When you're done with the hosted app call
AudioRouteHostController.releaseModuleInstance()
to shut down the connection.
-
Scan the available modules using
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
Questions?
Visit the Audioroute forum, or contact us at audioroute@ntrack.com.