Play any sound in an Android or iOS app with JUCE (2/3)

Matthieu Regnauld
4 min readMar 11, 2023

--

This article is a follow up of the previous article, that you can find here.

In the first part, we managed to create, code and export our C++ JUCE library for both Android and iOS.

Now it’s time to implement that library in an Android app and in an iOS app. Again, there is nothing difficult here, but follow each step very carefully, especially in this part of the tutorial, otherwise you may end up with some painful error messages!

And in order to keep that article as readable as possible, I’ll start from an (almost) empty app, where I already have implemented everything to run some basic C++ code, as described in this article. This app is named Juce Audio Demo, which package name (or bundle identifier) is: com.juceaudio.juceaudiodemo.

How to implement the library in an Android project

In this part of that article, I’ll use Android Studio to describe each step. But you should be able to follow with any other IDE.

STEP 1: Copy the 4 directories containing the 4 version of our library exported by Projucer and paste them in your app, in: app/src/main/cpp/<library>/lib/. In our example, <library> will be juceaudio.

STEP 2: In that same directory, create a new directory named include.

STEP 3: In the file explorer, go to <Project>/Source/, copy the header files and paste them in the include directory (<Project> is our library project directory).

STEP 4: Now go to <Project>/JuceLibraryCode/, copy the header files and paste them in the include directory.

STEP 5: In the CMakeLists.txt file, we’ll add quite a few things. Here is what it should look like now:

cmake_minimum_required(VERSION 3.18.1)
project(JUCEAudioDemo)

# optimization
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Ofast")

# JUCE directory
set(JUCE_DIR "$ENV{HOME}/JUCE")

# Oboe
set(OBOE_DIR "${JUCE_DIR}/modules/juce_audio_devices/native/oboe")
add_subdirectory(${OBOE_DIR} ./oboe)

# CPU Features
add_library("cpufeatures" STATIC "${ANDROID_NDK}/sources/android/cpufeatures/cpu-features.c")

# JUCE
set(JuceAudio_DIR ${CMAKE_SOURCE_DIR}/src/main/cpp/juceaudio)
add_library(JuceAudio STATIC IMPORTED)
set_property(TARGET JuceAudio PROPERTY IMPORTED_LOCATION
${JuceAudio_DIR}/lib/${ANDROID_ABI}/libjuceaudio.a)
set_target_properties(JuceAudio PROPERTIES INTERFACE_INCLUDE_DIRECTORIES
${JuceAudio_DIR}/lib/include)
include_directories(${JuceAudio_DIR}/lib/include)
include_directories(${JUCE_DIR}/modules)

add_library( # Sets the name of the library.
native-lib

# Sets the library as a shared library.
SHARED

# Provides a relative path to your source file(s).
src/main/cpp/native-lib.cpp
)

find_library(
log-lib

log)

target_link_libraries(
native-lib

JuceAudio

"cpufeatures"
"oboe"

${log-lib})

One important thing to note here: you can see twice in the code above ${USER_HOME_DIR}/JUCE to point to the directory where JUCE is installed. You may have to change it according to your needs.

STEP 6: In the app/src/main/kotlin/ directory, create the following package: com/rmsl/juce, and create a new Java class named Java (so yeah, Java.java) that looks like this:

package com.rmsl.juce;

import android.content.Context;

public class Java
{
static
{
System.loadLibrary("native-lib");
}

public native static void initialiseJUCE(Context context);
}

Just copy / paste the code above, basically. And don’t worry if the initialiseJUCE() function is always red, it’s normal.

STEP 7: In the com.juceaudio.juceaudiodemo package, create a new Application class, like the following:

package com.juceaudio.juceaudiodemo

import android.app.Application
import com.rmsl.juce.Java

class JuceAudioDemoApplication : Application()
{
override fun onCreate()
{
super.onCreate()
Java.initialiseJUCE(this)
}
}

And don’t forget to add it in the AndroidManifest.xml file:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.juceaudio.juceaudiodemo">
<application
android:label="juceaudiodemo"
android:name=".JuceAudioDemoApplication"
android:icon="@mipmap/ic_launcher">
...
</application>
</manifest>

STEP 8: Back in the native-lib.cpp file (in app/src/main/cpp/), add the following include on top of the file: #include <JuceHeader.h>

STEP 9: Finally, go to the app/build.gradle file. In that file, go in the android > defaultConfig block, and:

  • check that the minSdkVersion is 21 or higher (and update if necessary)
  • in the externalNativeBuild > cmake block, add the following line:
    cppFlags “-std=c++17” (since JUCE requires C++ 17 or higher)

Now, your app should compile and run.

How to implement the library in an iOS project

Unlike Android Studio (or any other decent IDE), we will have to do many manipulations through a lot of visual interfaces and menus in Xcode to achieve what we want.

To make my next explanations easier to understand, here is a screenshot of my iOS project (some of you may recognize a Flutter project), where the root of the project is Runner:

Xcode project screenshot

STEP 1: Go to Runner (Targets) > Build Settings > Apple Clang - Preprocessing, and for Preprocessor Macros:

  • for Release, double-click on the right column (a new popup should appear) and add the two entries:
    - _NDEBUG=1
    - NDEBUG=1
  • do exactly the same for Profile, if it exists for your project.

STEP 2: Go to Runner (Targets) > General > Frameworks, Libraries, and Embedded Content, and add the following frameworks:

  • Accelerate.framework
  • AudioToolbox.framework
  • AVFoundation.framework
  • CoreAudio.framework
  • CoreMIDI.framework

STEP 3: Go to Runner (Project) > Build Settings > Apple Clang - Language - C++, and for C++ Language Dialect, choose GNU++17 [-std=gnu++17]

STEP 4: Go to Runner (Project) > Build Settings > Search Paths and for Header Search Paths, add the following:

  • $SRCROOT/Runner/Natif/Cpp/Juce
  • $HOME/JUCE/modules

Note here: $HOME/JUCE is the directory where JUCE is installed on my computer. Don’t hesitate to change it to meets your needs.

STEP 5: In the Native/Cpp/ group, create the Juce group.

STEP 6: Copy the library exported by Projucer and paste it in the Juce group (always check Copy items if needed). Since we’re dealing with Xcode, you’ll have to drag & drop it into the group.

Note that if you copy the library generated in debug mode, you’ll only be able to launch the app in debug mode. Same for release mode.

STEP 7: Copy the headers from the following directories, again in the Juce group (<Project> is our library project directory):

  • <Projet>/Source/
  • <Projet>/JuceLibraryCode/

Now, your app should compile and run.

Conclusion

Even though you managed to make it to this point, please be patient since it won’t play any sound yet. We will see that in the last article!

And again, if you have any question or suggestion, feel free to share them in the comments.

--

--