This source file includes following definitions.
- WAV_Setup
- WAV_Shutdown
- WAV_ConfigureOutput
- WAV_WriteAudio
- WAV_Play
- WAV_UpdateVolume
- WAV_SetVolume
- WAV_SetPan
- WAV_QueryOutputSampleRate
- WAV_GetAudioDelay
- WAV_GetTotalBufferTime
- NewWAVRender
- DeleteWAVRender
- QueryInterfaces
- LoadInterface
- ShutdownInterface
#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
#include <gpac/constants.h>
#ifdef GPAC_STATIC_MODULES
JavaVM* GetJavaVM();
JNIEnv* GetEnv();
#endif
static const char android_device[] = "Android Default";
static jclass cAudioTrack = NULL;
static jobject mtrack = NULL;
static jmethodID mAudioTrack;
static jmethodID setStereoVolume;
static jmethodID mGetMinBufferSize;
static jmethodID mPlay;
static jmethodID mStop;
static jmethodID mRelease;
static jmethodID mWriteB;
static jmethodID mWriteS;
static jmethodID mFlush;
#include <android/log.h>
#define TAG "GPAC Android Audio"
#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, TAG, __VA_ARGS__)
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)
typedef struct
{
JNIEnv* env;
jobject mtrack;
u32 num_buffers;
u32 delay, total_length_ms;
Bool force_config;
u32 cfg_num_buffers, cfg_duration;
u32 sampleRateInHz;
u32 channelConfig;
u32 audioFormat;
s32 mbufferSizeInBytes;
u32 volume;
u32 pan;
jarray buff;
} DroidContext;
static GF_Err WAV_Setup(GF_AudioOutput *dr, void *os_handle, u32 num_buffers, u32 total_duration)
{
DroidContext *ctx = (DroidContext *)dr->opaque;
JNIEnv* env = GetEnv();
int channels;
int bytes;
LOGV("[Android Audio] 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 (!cAudioTrack) {
cAudioTrack = (*env)->FindClass(env, "android/media/AudioTrack");
if (!cAudioTrack) {
return GF_NOT_SUPPORTED;
}
cAudioTrack = (*env)->NewGlobalRef(env, cAudioTrack);
mAudioTrack = (*env)->GetMethodID(env, cAudioTrack, "<init>", "(IIIIII)V");
mGetMinBufferSize = (*env)->GetStaticMethodID(env, cAudioTrack, "getMinBufferSize", "(III)I");
mPlay = (*env)->GetMethodID(env, cAudioTrack, "play", "()V");
mStop = (*env)->GetMethodID(env, cAudioTrack, "stop", "()V");
mRelease = (*env)->GetMethodID(env, cAudioTrack, "release", "()V");
mWriteB = (*env)->GetMethodID(env, cAudioTrack, "write", "([BII)I");
mWriteS = (*env)->GetMethodID(env, cAudioTrack, "write", "([SII)I");
mFlush = (*env)->GetMethodID(env, cAudioTrack, "flush", "()V");
setStereoVolume = (*env)->GetMethodID(env, cAudioTrack, "setStereoVolume", "(FF)I");
}
return GF_OK;
}
static void WAV_Shutdown(GF_AudioOutput *dr)
{
DroidContext *ctx = (DroidContext *)dr->opaque;
JNIEnv* env = NULL;
jint res = 0;
LOGV("[Android Audio] Shutdown START.");
res = (*GetJavaVM())->GetEnv(GetJavaVM(), (void**)&env, JNI_VERSION_1_2);
if ( res == JNI_EDETACHED ) {
(*GetJavaVM())->AttachCurrentThread(GetJavaVM(), &env, NULL);
}
(*env)->CallNonvirtualVoidMethod(env, mtrack, cAudioTrack, mStop);
(*env)->CallNonvirtualVoidMethod(env, mtrack, cAudioTrack, mRelease);
(*env)->PopLocalFrame(env, NULL);
(*env)->DeleteGlobalRef(env, ctx->buff);
(*env)->DeleteGlobalRef(env, mtrack);
(*env)->DeleteGlobalRef(env, cAudioTrack);
(*GetJavaVM())->DetachCurrentThread(GetJavaVM());
LOGV("[Android Audio] Shutdown DONE.");
}
static GF_Err WAV_ConfigureOutput(GF_AudioOutput *dr, u32 *SampleRate, u32 *NbChannels, u32 *nbBitsPerSample, u32 channel_cfg)
{
JNIEnv* env = NULL;
u32 i;
DroidContext *ctx = (DroidContext *)dr->opaque;
LOGI("[Android Audio] Configure Output for %u channels...", *NbChannels);
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;
(*GetJavaVM())->AttachCurrentThread(GetJavaVM(), &env, NULL);
ctx->env = env;
LOGV("[Android Audio] SampleRate : %d",ctx->sampleRateInHz);
LOGV("[Android Audio] BitPerSample : %d", *nbBitsPerSample);
(*env)->PushLocalFrame(env, 2);
ctx->num_buffers = 1;
ctx->mbufferSizeInBytes = (*env)->CallStaticIntMethod(env, cAudioTrack, mGetMinBufferSize,
ctx->sampleRateInHz, ctx->channelConfig, ctx->audioFormat);
LOGV("[Android Audio] Buffer Size : %d", ctx->mbufferSizeInBytes);
i = 1;
if ( ctx->channelConfig == CHANNEL_CONFIGURATION_STEREO )
i *= 2;
if ( ctx->audioFormat == ENCODING_PCM_16BIT )
i *= 2;
ctx->total_length_ms = 1000 * ctx->num_buffers * ctx->mbufferSizeInBytes / i / ctx->sampleRateInHz;
LOGV("[Android Audio] Buffer Length ms : %d", ctx->total_length_ms);
ctx->delay = ctx->total_length_ms;
mtrack = (*env)->NewObject(env, cAudioTrack, mAudioTrack, STREAM_MUSIC, ctx->sampleRateInHz,
ctx->channelConfig, ctx->audioFormat, ctx->mbufferSizeInBytes, MODE_STREAM);
if (mtrack) {
mtrack = (*env)->NewGlobalRef(env, mtrack);
ctx->mtrack = mtrack;
(*env)->CallNonvirtualVoidMethod(env, mtrack, cAudioTrack, mPlay);
} else {
LOGV("[Android Audio] mtrack = %p", mtrack);
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 {
LOGV("[Android Audio] ctx->buff = %p", ctx->buff );
return GF_NOT_SUPPORTED;
}
LOGV("[Android Audio] ConfigureOutput DONE.");
return GF_OK;
}
static void WAV_WriteAudio(GF_AudioOutput *dr)
{
DroidContext *ctx = (DroidContext *)dr->opaque;
if (!ctx)
return;
JNIEnv* env = ctx->env;
u32 written;
void* pBuffer;
if (!env)
return;
#ifdef DROID_EXTREME_LOGS
LOGV("[Android Audio] WAV_WriteAudio() : entering",ctx->sampleRateInHz);
#endif
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);
else
(*env)->ReleaseShortArrayElements(env, ctx->buff, pBuffer, 0);
if (written)
{
if ( ctx->audioFormat == ENCODING_PCM_8BIT )
(*env)->CallNonvirtualIntMethod(env, mtrack, cAudioTrack, mWriteB, ctx->buff, 0, ctx->mbufferSizeInBytes);
else
(*env)->CallNonvirtualIntMethod(env, mtrack, cAudioTrack, mWriteS, ctx->buff, 0, ctx->mbufferSizeInBytes/2);
}
}
else
{
LOGV("[Android Audio] Failed to get pointer to array bytes = %p", pBuffer);
}
#ifdef DROID_EXTREME_LOGS
LOGV("[Android Audio] WAV_WriteAudio() : done",ctx->sampleRateInHz);
#endif
}
static void WAV_Play(GF_AudioOutput *dr, u32 PlayType)
{
DroidContext *ctx = (DroidContext *)dr->opaque;
JNIEnv* env = GetEnv();
LOGV("[Android Audio] Play: %d\n", PlayType);
switch ( PlayType )
{
case 0:
(*env)->CallNonvirtualVoidMethod(env, mtrack, cAudioTrack, mStop);
(*env)->CallNonvirtualVoidMethod(env, mtrack, cAudioTrack, mFlush);
break;
case 2:
(*env)->CallNonvirtualVoidMethod(env, mtrack, cAudioTrack, mFlush);
case 1:
(*env)->CallNonvirtualVoidMethod(env, mtrack, cAudioTrack, mPlay);
break;
default:
LOGW("[Android Audio] Unknown Play method=%d.\n", PlayType);
}
LOGV("[Android Audio] Play DONE (%d).\n", PlayType);
}
static void WAV_UpdateVolume(DroidContext *ctx) {
float lV, rV;
JNIEnv* env = GetEnv();
if (!ctx)
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;
}
if (env && setStereoVolume && mtrack && cAudioTrack) {
int success;
if (0!= (success=((*env)->CallNonvirtualIntMethod(env, mtrack, cAudioTrack, setStereoVolume, lV, rV))))
LOGE("SetVolume(%f,%f) returned Error code %d", lV, rV, success );
} else {
LOGD("SetVolume(%f,%f)", lV, rV );
}
}
static void WAV_SetVolume(GF_AudioOutput *dr, u32 Volume) {
DroidContext *ctx = (DroidContext *)dr->opaque;
ctx->volume = Volume;
WAV_UpdateVolume(ctx);
}
static void WAV_SetPan(GF_AudioOutput *dr, u32 Pan)
{
DroidContext *ctx = (DroidContext *)dr->opaque;
WAV_UpdateVolume(ctx);
}
static GF_Err WAV_QueryOutputSampleRate(GF_AudioOutput *dr, u32 *desired_samplerate, u32 *NbChannels, u32 *nbBitsPerSample)
{
DroidContext *ctx = (DroidContext *)dr->opaque;
JNIEnv* env = ctx->env;
u32 sampleRateInHz, channelConfig, audioFormat;
LOGV("Query sample=%d", *desired_samplerate );
#ifdef TEST_QUERY_SAMPLE
sampleRateInHz = *desired_samplerate;
channelConfig = (*NbChannels == 1) ? CHANNEL_CONFIGURATION_MONO : CHANNEL_CONFIGURATION_STEREO;
audioFormat = (*nbBitsPerSample == 8)? ENCODING_PCM_8BIT : ENCODING_PCM_16BIT;
LOGV3("[Android Audio] Query: SampleRate ChannelConfig AudioFormat: %d %d %d \n",
sampleRateInHz,
(channelConfig == CHANNEL_CONFIGURATION_MONO)? 1 : 2,
(ctx->audioFormat == ENCODING_PCM_8BIT)? 8 : 16);
switch (*desired_samplerate) {
case 11025:
*desired_samplerate = 11025;
if ( (*env)->CallStaticIntMethod(env, cAudioTrack, mGetMinBufferSize,
*desired_samplerate, channelConfig, audioFormat) > 0 )
return GF_OK;
case 22050:
*desired_samplerate = 22050;
if ( (*env)->CallStaticIntMethod(env, cAudioTrack, mGetMinBufferSize,
*desired_samplerate, channelConfig, audioFormat) > 0 )
return GF_OK;
break;
case 8000:
*desired_samplerate = 8000;
if ( (*env)->CallStaticIntMethod(env, cAudioTrack, mGetMinBufferSize,
*desired_samplerate, channelConfig, audioFormat) > 0 )
return GF_OK;
case 16000:
*desired_samplerate = 16000;
if ( (*env)->CallStaticIntMethod(env, cAudioTrack, mGetMinBufferSize,
*desired_samplerate, channelConfig, audioFormat) > 0 )
return GF_OK;
case 32000:
*desired_samplerate = 32000;
if ( (*env)->CallStaticIntMethod(env, cAudioTrack, mGetMinBufferSize,
*desired_samplerate, channelConfig, audioFormat) > 0 )
return GF_OK;
break;
case 24000:
*desired_samplerate = 24000;
if ( (*env)->CallStaticIntMethod(env, cAudioTrack, mGetMinBufferSize,
*desired_samplerate, channelConfig, audioFormat) > 0 )
return GF_OK;
case 48000:
*desired_samplerate = 48000;
if ( (*env)->CallStaticIntMethod(env, cAudioTrack, mGetMinBufferSize,
*desired_samplerate, channelConfig, audioFormat) > 0 )
return GF_OK;
break;
case 44100:
*desired_samplerate = 44100;
if ( (*env)->CallStaticIntMethod(env, cAudioTrack, mGetMinBufferSize,
*desired_samplerate, channelConfig, audioFormat) > 0 )
return GF_OK;
break;
default:
break;
}
#endif
return GF_OK;
}
static u32 WAV_GetAudioDelay(GF_AudioOutput *dr)
{
DroidContext *ctx = (DroidContext *)dr->opaque;
return ctx->delay;
}
static u32 WAV_GetTotalBufferTime(GF_AudioOutput *dr)
{
DroidContext *ctx = (DroidContext *)dr->opaque;
return ctx->total_length_ms;
}
void *NewWAVRender()
{
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 Audio Output", "gpac distribution")
driv->opaque = ctx;
driv->SelfThreaded = 0;
driv->Setup = WAV_Setup;
driv->Shutdown = WAV_Shutdown;
driv->ConfigureOutput = WAV_ConfigureOutput;
driv->GetAudioDelay = WAV_GetAudioDelay;
driv->GetTotalBufferTime = WAV_GetTotalBufferTime;
driv->SetVolume = WAV_SetVolume;
driv->SetPan = WAV_SetPan;
driv->Play = WAV_Play;
driv->QueryOutputSampleRate = WAV_QueryOutputSampleRate;
driv->WriteAudio = WAV_WriteAudio;
return driv;
}
void DeleteWAVRender(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 NewWAVRender();
return NULL;
}
GPAC_MODULE_EXPORT
void ShutdownInterface(GF_BaseInterface *ifce)
{
switch (ifce->InterfaceType) {
case GF_AUDIO_OUTPUT_INTERFACE:
DeleteWAVRender((GF_AudioOutput *) ifce);
break;
}
}
GPAC_MODULE_STATIC_DECLARATION( droid_audio )