blob: 6c493934bdca74bc2f0a4fcb8dc445ad4a282008 [file] [edit]
/*
* GPAC - Multimedia Framework C SDK
*
* Authors: Ivica Arsov, Jean Le Feuvre
* Copyright (c) Mines-Telecom 2009-2022
* All rights reserved
*
* This file is part of GPAC / Wrapper
*
* GPAC is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* GPAC is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; see the file COPYING. If not, write to
* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
*
*/
#include "javaenv.h"
#include <gpac/modules/audio_out.h>
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#define STREAM_MUSIC 3
#define CHANNEL_CONFIGURATION_MONO 2
#define CHANNEL_CONFIGURATION_STEREO 3
#define ENCODING_PCM_8BIT 3
#define ENCODING_PCM_16BIT 2
#define MODE_STREAM 1
#define CHANNEL_OUT_MONO 4
#define CHANNEL_IN_STEREO 12
#define CHANNEL_IN_MONO 16
/*for channel codes*/
#include <gpac/constants.h>
#ifdef GPAC_STATIC_MODULES
JavaVM* GetJavaVM();
JNIEnv* GetEnv();
#endif
static const char android_device[] = "Android Default";
typedef struct
{
JNIEnv* env;
u32 num_buffers;
u32 delay, total_length_ms;
Bool force_config;
u32 cfg_num_buffers, cfg_duration;
u32 sampleRateInHz;
u32 channelConfig; //AudioFormat.CHANNEL_OUT_MONO
u32 audioFormat; //AudioFormat.ENCODING_PCM_16BIT
s32 mbufferSizeInBytes;
u32 volume;
u32 pan;
jarray buff;
//JNI vars - do NOT use static vars since we may load/unload the module at run time
jclass class_AudioTrack;
jobject obj_AudioTrack;
jmethodID fun_AudioTrack;
jmethodID fun_setStereoVolume;
jmethodID fun_GetMinBufferSize;
jmethodID fun_Play;
jmethodID fun_Stop;
jmethodID fun_Release;
jmethodID fun_WriteB;
jmethodID fun_WriteS;
jmethodID fun_Flush;
} DroidContext;
// Called by the main thread
static GF_Err AAOut_Setup(GF_AudioOutput *dr, void *os_handle, u32 num_buffers, u32 total_duration)
{
DroidContext *ctx = (DroidContext *)dr->opaque;
JNIEnv* env = GetEnv();
GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[AndroidAudio] Setup for %d buffers", num_buffers ));
ctx->force_config = (num_buffers && total_duration) ? 1 : 0;
ctx->cfg_num_buffers = num_buffers;
if (ctx->cfg_num_buffers <= 1) ctx->cfg_num_buffers = 2;
ctx->cfg_duration = total_duration;
if (!ctx->force_config) ctx->num_buffers = 1;
ctx->volume = 100;
ctx->pan = 50;
if (ctx->class_AudioTrack) return GF_OK;
ctx->class_AudioTrack = (*env)->FindClass(env, "android/media/AudioTrack");
if (!ctx->class_AudioTrack) {
return GF_NOT_SUPPORTED;
}
ctx->class_AudioTrack = (*env)->NewGlobalRef(env, ctx->class_AudioTrack);
ctx->fun_AudioTrack = (*env)->GetMethodID(env, ctx->class_AudioTrack, "<init>", "(IIIIII)V");
ctx->fun_GetMinBufferSize = (*env)->GetStaticMethodID(env, ctx->class_AudioTrack, "getMinBufferSize", "(III)I");
ctx->fun_Play = (*env)->GetMethodID(env, ctx->class_AudioTrack, "play", "()V");
ctx->fun_Stop = (*env)->GetMethodID(env, ctx->class_AudioTrack, "stop", "()V");
ctx->fun_Release = (*env)->GetMethodID(env, ctx->class_AudioTrack, "release", "()V");
ctx->fun_WriteB = (*env)->GetMethodID(env, ctx->class_AudioTrack, "write", "([BII)I");
ctx->fun_WriteS = (*env)->GetMethodID(env, ctx->class_AudioTrack, "write", "([SII)I");
ctx->fun_Flush = (*env)->GetMethodID(env, ctx->class_AudioTrack, "flush", "()V");
ctx->fun_setStereoVolume = (*env)->GetMethodID(env, ctx->class_AudioTrack, "setStereoVolume", "(FF)I");
return GF_OK;
}
// Called by the audio thread
static void AAOut_Shutdown(GF_AudioOutput *dr)
{
DroidContext *ctx = (DroidContext *)dr->opaque;
JNIEnv* env = NULL;
jint res = 0;
GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[AndroidAudio] Shutdown" ));
res = (*GetJavaVM())->GetEnv(GetJavaVM(), (void**)&env, JNI_VERSION_1_2);
if ( res == JNI_EDETACHED ) {
(*GetJavaVM())->AttachCurrentThread(GetJavaVM(), &env, NULL);
}
(*env)->CallNonvirtualVoidMethod(env, ctx->obj_AudioTrack, ctx->class_AudioTrack, ctx->fun_Stop);
(*env)->CallNonvirtualVoidMethod(env, ctx->obj_AudioTrack, ctx->class_AudioTrack, ctx->fun_Release);
(*env)->PopLocalFrame(env, NULL);
(*env)->DeleteGlobalRef(env, ctx->buff);
(*env)->DeleteGlobalRef(env, ctx->obj_AudioTrack);
(*env)->DeleteGlobalRef(env, ctx->class_AudioTrack);
//if ( res == JNI_EDETACHED ) {
(*GetJavaVM())->DetachCurrentThread(GetJavaVM());
//}
}
/* Called by the audio thread */
static GF_Err AAOut_ConfigureOutput(GF_AudioOutput *dr, u32 *SampleRate, u32 *NbChannels, u32 *nbBitsPerSample, u64 channel_cfg)
{
JNIEnv* env = NULL;
u32 bytes_per_samp;
DroidContext *ctx = (DroidContext *)dr->opaque;
if (!ctx) return GF_BAD_PARAM;
ctx->sampleRateInHz = *SampleRate;
ctx->channelConfig = (*NbChannels == 1) ? CHANNEL_CONFIGURATION_MONO : CHANNEL_CONFIGURATION_STEREO;
ctx->audioFormat = (*nbBitsPerSample == 8) ? ENCODING_PCM_8BIT : ENCODING_PCM_16BIT;
// Get the java environment in the new thread
(*GetJavaVM())->AttachCurrentThread(GetJavaVM(), &env, NULL);
ctx->env = env;
(*env)->PushLocalFrame(env, 2);
ctx->num_buffers = 1;
ctx->mbufferSizeInBytes = (*env)->CallStaticIntMethod(env, ctx->class_AudioTrack, ctx->fun_GetMinBufferSize,
ctx->sampleRateInHz, ctx->channelConfig, ctx->audioFormat);
bytes_per_samp = 1;
if (ctx->channelConfig == CHANNEL_CONFIGURATION_STEREO) bytes_per_samp *= 2;
if (ctx->audioFormat == ENCODING_PCM_16BIT) bytes_per_samp *= 2;
ctx->total_length_ms = 1000 * ctx->num_buffers * ctx->mbufferSizeInBytes / bytes_per_samp / ctx->sampleRateInHz;
GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[AndroidAudio] Configure Output for %u Hz %u channels - Buffer size %u ms", ctx->sampleRateInHz, *NbChannels, ctx->total_length_ms));
/*initial delay is full buffer size*/
ctx->delay = ctx->total_length_ms;
ctx->obj_AudioTrack = (*env)->NewObject(env, ctx->class_AudioTrack, ctx->fun_AudioTrack, STREAM_MUSIC, ctx->sampleRateInHz,
ctx->channelConfig, ctx->audioFormat, ctx->mbufferSizeInBytes, MODE_STREAM); //AudioTrack.MODE_STREAM
if (ctx->obj_AudioTrack) {
ctx->obj_AudioTrack = (*env)->NewGlobalRef(env, ctx->obj_AudioTrack);
(*env)->CallNonvirtualVoidMethod(env, ctx->obj_AudioTrack, ctx->class_AudioTrack, ctx->fun_Play);
} else {
return GF_NOT_SUPPORTED;
}
if ( ctx->audioFormat == ENCODING_PCM_8BIT )
ctx->buff = (*env)->NewByteArray(env, ctx->mbufferSizeInBytes);
else
ctx->buff = (*env)->NewShortArray(env, ctx->mbufferSizeInBytes/2);
if (ctx->buff) {
ctx->buff = (*env)->NewGlobalRef(env, ctx->buff);
} else {
return GF_NOT_SUPPORTED;
}
return GF_OK;
}
/* Called by the audio thread */
static void AAOut_WriteAudio(GF_AudioOutput *dr)
{
u32 written;
void* pBuffer;
DroidContext *ctx = (DroidContext *)dr->opaque;
if (!ctx) return;
JNIEnv* env = ctx->env;
if (!env) return;
if (ctx->audioFormat == ENCODING_PCM_8BIT)
pBuffer = (*env)->GetByteArrayElements(env, ctx->buff, NULL);
else
pBuffer = (*env)->GetShortArrayElements(env, ctx->buff, NULL);
if (pBuffer) {
written = dr->FillBuffer(dr->audio_renderer, pBuffer, ctx->mbufferSizeInBytes);
if (ctx->audioFormat == ENCODING_PCM_8BIT) {
(*env)->ReleaseByteArrayElements(env, ctx->buff, pBuffer, 0);
if (written)
(*env)->CallNonvirtualIntMethod(env, ctx->obj_AudioTrack, ctx->class_AudioTrack, ctx->fun_WriteB, ctx->buff, 0, written);
} else {
(*env)->ReleaseShortArrayElements(env, ctx->buff, pBuffer, 0);
if (written)
(*env)->CallNonvirtualIntMethod(env, ctx->obj_AudioTrack, ctx->class_AudioTrack, ctx->fun_WriteS, ctx->buff, 0, written/2);
}
} else {
GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[AndroidAudio] Failed to get pointer to array bytes"));
}
}
/* Called by the main thread */
static void AAOut_Play(GF_AudioOutput *dr, u32 PlayType)
{
DroidContext *ctx = (DroidContext *)dr->opaque;
JNIEnv* env = GetEnv();
if (!env) return;
switch (PlayType) {
case 0:
GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[AndroidAudio] Pause\n"));
(*env)->CallNonvirtualVoidMethod(env, ctx->obj_AudioTrack, ctx->class_AudioTrack, ctx->fun_Stop);
(*env)->CallNonvirtualVoidMethod(env, ctx->obj_AudioTrack, ctx->class_AudioTrack, ctx->fun_Flush);
break;
case 2:
GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[AndroidAudio] Resume\n"));
(*env)->CallNonvirtualVoidMethod(env, ctx->obj_AudioTrack, ctx->class_AudioTrack, ctx->fun_Flush);
(*env)->CallNonvirtualVoidMethod(env, ctx->obj_AudioTrack, ctx->class_AudioTrack, ctx->fun_Play);
break;
case 1:
GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[AndroidAudio] Play\n"));
(*env)->CallNonvirtualVoidMethod(env, ctx->obj_AudioTrack, ctx->class_AudioTrack, ctx->fun_Play);
break;
default:
GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[AndroidAudio] Unknown play method %d\n", PlayType));
break;
}
}
static void AAOut_UpdateVolume(DroidContext *ctx)
{
float lV, rV;
JNIEnv* env = GetEnv();
if (!env || !ctx->fun_setStereoVolume || !ctx->obj_AudioTrack || !ctx->class_AudioTrack) return;
if (ctx->pan > 100) ctx->pan = 100;
lV = rV = ctx->volume / 100.0;
if (ctx->pan > 50) {
float m = (100 - ctx->pan) / 50.0;
lV*=m;
} else if (ctx->pan < 50) {
float m = ctx->pan / 50.0;
rV*=m;
}
int success = (*env)->CallNonvirtualIntMethod(env, ctx->obj_AudioTrack, ctx->class_AudioTrack, ctx->fun_setStereoVolume, lV, rV);
if (success != 0) {
GF_LOG(GF_LOG_WARNING, GF_LOG_MMIO, ("[AndroidAudio] SetVolume(%f,%f) failed with error %d", lV, rV, success));
}
}
static void AAOut_SetVolume(GF_AudioOutput *dr, u32 Volume)
{
DroidContext *ctx = (DroidContext *)dr->opaque;
ctx->volume = Volume;
AAOut_UpdateVolume(ctx);
}
static void AAOut_SetPan(GF_AudioOutput *dr, u32 Pan)
{
DroidContext *ctx = (DroidContext *)dr->opaque;
AAOut_UpdateVolume(ctx);
}
/* Called by the audio thread */
static GF_Err AAOut_QueryOutputSampleRate(GF_AudioOutput *dr, u32 *desired_samplerate, u32 *NbChannels, u32 *nbBitsPerSample)
{
#ifdef TEST_QUERY_SAMPLE
DroidContext *ctx = (DroidContext *)dr->opaque;
JNIEnv* env = ctx->env;
u32 sampleRateInHz = *desired_samplerate;
u32 channelConfig = (*NbChannels == 1) ? CHANNEL_CONFIGURATION_MONO : CHANNEL_CONFIGURATION_STEREO;
u32 audioFormat = (*nbBitsPerSample == 8)? ENCODING_PCM_8BIT : ENCODING_PCM_16BIT;
GF_LOG(GF_LOG_INFO, GF_LOG_MMIO, ("[AndroidAudio] Query config: SampleRate ChannelConfig AudioFormat: %u %u %u\n",
sampleRateInHz,
(channelConfig == CHANNEL_CONFIGURATION_MONO)? 1 : 2,
(ctx->audioFormat == ENCODING_PCM_8BIT)? 8 : 16));
switch (*desired_samplerate) {
case 11025:
case 22050:
case 8000:
case 16000:
case 32000:
case 24000:
case 48000:
case 44100:
if ( (*env)->CallStaticIntMethod(env, ctx->class_AudioTrack, ctx->fun_GetMinBufferSize, *desired_samplerate, channelConfig, audioFormat) > 0 )
return GF_OK;
break;
default:
break;
}
#endif
return GF_OK;
}
static u32 AAOut_GetAudioDelay(GF_AudioOutput *dr)
{
DroidContext *ctx = (DroidContext *)dr->opaque;
return ctx->delay;
}
static u32 AAOut_GetTotalBufferTime(GF_AudioOutput *dr)
{
DroidContext *ctx = (DroidContext *)dr->opaque;
return ctx->total_length_ms;
}
void *NewAAOutRender()
{
DroidContext *ctx;
GF_AudioOutput *driv;
ctx = gf_malloc(sizeof(DroidContext));
memset(ctx, 0, sizeof(DroidContext));
ctx->num_buffers = 1;
ctx->pan = 50;
ctx->volume = 100;
driv = gf_malloc(sizeof(GF_AudioOutput));
memset(driv, 0, sizeof(GF_AudioOutput));
GF_REGISTER_MODULE_INTERFACE(driv, GF_AUDIO_OUTPUT_INTERFACE, "android", "gpac distribution")
driv->opaque = ctx;
driv->SelfThreaded = 0;
driv->Setup = AAOut_Setup;
driv->Shutdown = AAOut_Shutdown;
driv->Configure = AAOut_ConfigureOutput;
driv->GetAudioDelay = AAOut_GetAudioDelay;
driv->GetTotalBufferTime = AAOut_GetTotalBufferTime;
driv->SetVolume = AAOut_SetVolume;
driv->SetPan = AAOut_SetPan;
driv->Play = AAOut_Play;
driv->QueryOutputSampleRate = AAOut_QueryOutputSampleRate;
driv->WriteAudio = AAOut_WriteAudio;
return driv;
}
void DeleteAAOutRender(void *ifce)
{
GF_AudioOutput *dr = (GF_AudioOutput *) ifce;
if (!ifce)
return;
gf_free(dr);
}
GPAC_MODULE_EXPORT
const u32 *QueryInterfaces()
{
static u32 si [] = {
GF_AUDIO_OUTPUT_INTERFACE,
0
};
return si;
}
GPAC_MODULE_EXPORT
GF_BaseInterface *LoadInterface(u32 InterfaceType)
{
if (InterfaceType == GF_AUDIO_OUTPUT_INTERFACE) return NewAAOutRender();
return NULL;
}
GPAC_MODULE_EXPORT
void ShutdownInterface(GF_BaseInterface *ifce)
{
switch (ifce->InterfaceType) {
case GF_AUDIO_OUTPUT_INTERFACE:
DeleteAAOutRender((GF_AudioOutput *) ifce);
break;
}
}
GPAC_MODULE_STATIC_DECLARATION( droid_audio )