Add basic testing app for Android.

This is only very basic right now. Will be expanded on later.
This commit is contained in:
David Reid
2025-02-22 18:54:45 +10:00
parent 37b95f0f42
commit a497466f75
39 changed files with 1200 additions and 0 deletions
@@ -0,0 +1,37 @@
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html.
# For more examples on how to use CMake, see https://github.com/android/ndk-samples.
# Sets the minimum CMake version required for this project.
cmake_minimum_required(VERSION 3.22.1)
# Declares the project name. The project name can be accessed via ${ PROJECT_NAME},
# Since this is the top level CMakeLists.txt, the project name is also accessible
# with ${CMAKE_PROJECT_NAME} (both CMake variables are in-sync within the top level
# build script scope).
project("miniaudiotester")
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
#
# In this top level CMakeLists.txt, ${CMAKE_PROJECT_NAME} is used to define
# the target library name; in the sub-module's CMakeLists.txt, ${PROJECT_NAME}
# is preferred for the same purpose.
#
# In order to load a library into your app from Java/Kotlin, you must call
# System.loadLibrary() and pass the name of the library defined here;
# for GameActivity/NativeActivity derived applications, the same library name must be
# used in the AndroidManifest.xml file.
add_library(${CMAKE_PROJECT_NAME} SHARED
# List C/C++ source files with relative paths to this CMakeLists.txt.
native-lib.cpp)
# Specifies libraries CMake should link to your target library. You
# can link libraries from various origins, such as libraries defined in this
# build script, prebuilt third-party libraries, or Android system libraries.
target_link_libraries(${CMAKE_PROJECT_NAME}
# List libraries link to the target library
android
log)
@@ -0,0 +1,173 @@
#include <jni.h>
#include <string>
#define MA_DEBUG_OUTPUT
#include "../../../../../../../miniaudio.c" /* Android projects have very deep folder structures... */
typedef enum
{
BACKEND_AUTO,
BACKEND_AAUDIO,
BACKEND_OPENSL
} backend_choice_t;
typedef struct
{
ma_device device;
ma_waveform waveform;
bool hasDevice;
bool hasError; /* Will be set to true if something went wrong. */
std::string errorMessage; /* Will be an empty string if there is no error message. */
} audio_state_t;
static void audio_state_set_error(audio_state_t* pAudioState, const char* pMessage)
{
assert(pAudioState != nullptr);
pAudioState->hasError = true;
pAudioState->errorMessage = pMessage;
}
static void data_callback(ma_device* pDevice, void* pFramesOut, const void* pFramesIn, ma_uint32 frameCount)
{
auto* pAudioState = (audio_state_t*)pDevice->pUserData;
assert(pAudioState != nullptr);
ma_waveform_read_pcm_frames(&pAudioState->waveform, pFramesOut, frameCount, nullptr);
}
extern "C"
JNIEXPORT jlong JNICALL
Java_io_miniaud_miniaudiotester_MainActivity_UninitializeAudio(JNIEnv *env, jobject, jlong audioState)
{
auto* pAudioState = (audio_state_t*)audioState;
if (pAudioState == nullptr) {
return 0;
}
if (pAudioState->hasDevice) {
ma_device_uninit(&pAudioState->device);
ma_waveform_uninit(&pAudioState->waveform);
pAudioState->hasDevice = false;
}
pAudioState->hasError = false;
pAudioState->errorMessage = "";
return (jlong)pAudioState;
}
extern "C"
JNIEXPORT jlong JNICALL
Java_io_miniaud_miniaudiotester_MainActivity_PlayAudio(JNIEnv *env, jobject, jlong audioState, int backend)
{
auto* pAudioState = (audio_state_t*)audioState;
ma_result result;
if (pAudioState == nullptr) {
pAudioState = new audio_state_t;
pAudioState->hasDevice = false;
pAudioState->hasError = false;
}
/* If we don't have a device, create one. */
if (!pAudioState->hasDevice) {
ma_context_config contextConfig = ma_context_config_init();
ma_backend pBackends[1];
size_t backendCount;
if (backend == BACKEND_AUTO) {
backendCount = 0;
} else {
backendCount = 1;
if (backend == BACKEND_AAUDIO) {
pBackends[0] = ma_backend_aaudio;
} else if (backend == BACKEND_OPENSL) {
pBackends[0] = ma_backend_opensl;
} else {
backendCount = 0;
}
}
ma_device_config deviceConfig = ma_device_config_init(ma_device_type_playback);
deviceConfig.dataCallback = data_callback;
deviceConfig.pUserData = pAudioState;
result = ma_device_init_ex((backendCount == 0) ? nullptr : pBackends, backendCount, &contextConfig, &deviceConfig, &pAudioState->device);
if (result != MA_SUCCESS) {
audio_state_set_error(pAudioState, (std::string("Failed to initialize device. ") + ma_result_description(result)).c_str());
pAudioState->hasDevice = false;
}
/* Before starting the device we will need a waveform object. This should never fail to initialize. */
ma_waveform_config waveformConfig = ma_waveform_config_init(pAudioState->device.playback.format, pAudioState->device.playback.channels, pAudioState->device.sampleRate, ma_waveform_type_sine, 0.2, 400);
ma_waveform_init(&waveformConfig, &pAudioState->waveform);
pAudioState->hasDevice = true;
}
/* At this point we should have a device. Start it. */
result = ma_device_start(&pAudioState->device);
if (result != MA_SUCCESS) {
audio_state_set_error(pAudioState, (std::string("Failed to start device. ") + ma_result_description(result)).c_str());
}
return (jlong)pAudioState;
}
extern "C"
JNIEXPORT jlong JNICALL
Java_io_miniaud_miniaudiotester_MainActivity_PauseAudio(JNIEnv *env, jobject, jlong audioState)
{
auto* pAudioState = (audio_state_t*)audioState;
if (pAudioState == nullptr) {
return true;
}
if (!pAudioState->hasError) {
if (pAudioState->hasDevice) {
ma_result result = ma_device_stop(&pAudioState->device);
if (result != MA_SUCCESS) {
audio_state_set_error(pAudioState, ma_result_description(result));
}
} else {
audio_state_set_error(pAudioState, "Trying to pause audio, but there is no device.");
}
}
return (jlong)pAudioState;
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_io_miniaud_miniaudiotester_MainActivity_HasAudioError(JNIEnv *env, jobject, jlong audioState)
{
auto* pAudioState = (audio_state_t*)audioState;
if (pAudioState == nullptr) {
return true;
}
return pAudioState->hasError;
}
extern "C"
JNIEXPORT jstring JNICALL
Java_io_miniaud_miniaudiotester_MainActivity_GetAudioError(JNIEnv *env, jobject, jlong audioState)
{
auto* pAudioState = (audio_state_t*)audioState;
if (pAudioState == nullptr) {
return env->NewStringUTF("Out of memory");
}
return env->NewStringUTF(pAudioState->errorMessage.c_str());
}
extern "C"
JNIEXPORT void JNICALL
Java_io_miniaud_miniaudiotester_MainActivity_DeleteAudioState(JNIEnv *env, jobject thiz, jlong audioState)
{
Java_io_miniaud_miniaudiotester_MainActivity_UninitializeAudio(env, thiz, audioState);
delete (audio_state_t*)audioState;
}