diff options
Diffstat (limited to 'android/WALT/app/src/main/jni/player.c')
-rw-r--r-- | android/WALT/app/src/main/jni/player.c | 520 |
1 files changed, 520 insertions, 0 deletions
diff --git a/android/WALT/app/src/main/jni/player.c b/android/WALT/app/src/main/jni/player.c new file mode 100644 index 0000000..361d0a8 --- /dev/null +++ b/android/WALT/app/src/main/jni/player.c @@ -0,0 +1,520 @@ +/* + * Copyright 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <android/log.h> +#include <assert.h> +#include <jni.h> +#include <malloc.h> +#include <math.h> +#include <sys/types.h> + +// for native audio +#include <SLES/OpenSLES.h> +#include <SLES/OpenSLES_Android.h> +#include <SLES/OpenSLES_AndroidConfiguration.h> + +#include "sync_clock.h" + +// logging +#define APPNAME "WALT" + +// engine interfaces +static SLObjectItf engineObject = NULL; +static SLEngineItf engineEngine = NULL; + +// output mix interfaces +static SLObjectItf outputMixObject = NULL; + +// buffer queue player interfaces +static SLObjectItf bqPlayerObject = NULL; +static SLPlayItf bqPlayerPlay = NULL; +static SLAndroidSimpleBufferQueueItf bqPlayerBufferQueue = NULL; + +// recorder interfaces +static SLObjectItf recorderObject = NULL; +static SLRecordItf recorderRecord; +static SLAndroidSimpleBufferQueueItf recorderBufferQueue; +static volatile int bqPlayerRecorderBusy = 0; + +static unsigned int recorder_frames; +static short* recorderBuffer; +static unsigned recorderSize = 0; + +static unsigned int framesPerBuffer; + +#define CHANNELS 1 // 1 for mono, 2 for stereo + +// Each short represents a 16-bit audio sample +static short* beepBuffer = NULL; +static short* silenceBuffer = NULL; +static unsigned int bufferSizeInBytes = 0; + +#define MAXIMUM_AMPLITUDE_VALUE 32767 + +// how many times to play the wave table (so we can actually hear it) +#define BUFFERS_TO_PLAY 10 + +static unsigned buffersRemaining = 0; +static short warmedUp = 0; + + +// Timestamps +// te - enqueue time +// tc - callback time +int64_t te_play = 0, te_rec = 0, tc_rec = 0; + +/** + * Create wave tables for audio out. + */ +void createWaveTables(){ + bufferSizeInBytes = framesPerBuffer * sizeof(*beepBuffer); + silenceBuffer = malloc(bufferSizeInBytes); + beepBuffer = malloc(bufferSizeInBytes); + + + __android_log_print(ANDROID_LOG_VERBOSE, + APPNAME, + "Creating wave tables, 1 channel. Frames: %i Buffer size (bytes): %i", + framesPerBuffer, + bufferSizeInBytes); + + unsigned int i; + for (i = 0; i < framesPerBuffer; i++) { + silenceBuffer[i] = 0; + beepBuffer[i] = (i & 2 - 1) * MAXIMUM_AMPLITUDE_VALUE; + // This fills a buffer that looks like [min, min, max, max, min, min...] + // which is a square wave at 1/4 frequency of the sampling rate + // for 48kHz sampling this is 12kHz pitch, still well audible. + } +} + +// this callback handler is called every time a buffer finishes playing +void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void *context) +{ + if (bq == NULL) { + __android_log_print(ANDROID_LOG_ERROR, APPNAME, "buffer queue is null"); + } + assert(bq == bqPlayerBufferQueue); + assert(NULL == context); + + if (buffersRemaining > 0) { // continue playing tone + if(buffersRemaining == BUFFERS_TO_PLAY && warmedUp) { + // Enqueue the first non-silent buffer, save the timestamp + // For cold test Enqueue happens in playTone rather than here. + te_play = uptimeMicros(); + } + buffersRemaining--; + + SLresult result = (*bqPlayerBufferQueue)->Enqueue(bqPlayerBufferQueue, beepBuffer, + bufferSizeInBytes); + (void)result; + assert(SL_RESULT_SUCCESS == result); + } else if (warmedUp) { // stop tone but keep playing silence + SLresult result = (*bqPlayerBufferQueue)->Enqueue(bqPlayerBufferQueue, silenceBuffer, + bufferSizeInBytes); + assert(SL_RESULT_SUCCESS == result); + (void) result; + } else { // stop playing completely + SLresult result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_STOPPED); + assert(SL_RESULT_SUCCESS == result); + (void)result; + + __android_log_print(ANDROID_LOG_VERBOSE, APPNAME, "Done playing tone"); + } +} + +jlong Java_org_chromium_latency_walt_AudioTest_playTone(JNIEnv* env, jclass clazz){ + + int64_t t_start = uptimeMicros(); + te_play = 0; + + SLresult result; + + if (!warmedUp) { + result = (*bqPlayerBufferQueue)->Clear(bqPlayerBufferQueue); + assert(SL_RESULT_SUCCESS == result); + (void)result; + + // Enqueue first buffer + te_play = uptimeMicros(); + result = (*bqPlayerBufferQueue)->Enqueue(bqPlayerBufferQueue, beepBuffer, + bufferSizeInBytes); + assert(SL_RESULT_SUCCESS == result); + (void) result; + + result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PLAYING); + assert(SL_RESULT_SUCCESS == result); + (void) result; + + int dt_state = uptimeMicros() - t_start; + __android_log_print(ANDROID_LOG_VERBOSE, APPNAME, "playTone() changed state to playing dt=%d us", dt_state); + // TODO: this block takes lots of time (~13ms on Nexus 7) research this and decide how to measure. + } + + __android_log_print(ANDROID_LOG_VERBOSE, APPNAME, "Playing tone"); + buffersRemaining = BUFFERS_TO_PLAY; + + return (jlong) t_start; +} + + +// create the engine and output mix objects +void Java_org_chromium_latency_walt_AudioTest_createEngine(JNIEnv* env, jclass clazz) +{ + __android_log_print(ANDROID_LOG_VERBOSE, APPNAME, "Creating audio engine"); + + SLresult result; + + // create engine + result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL); + assert(SL_RESULT_SUCCESS == result); + (void)result; + + // realize the engine + result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE); + assert(SL_RESULT_SUCCESS == result); + (void)result; + + // get the engine interface, which is needed in order to create other objects + result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine); + assert(SL_RESULT_SUCCESS == result); + (void)result; + + // create output mix, + result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 0, NULL, NULL); + assert(SL_RESULT_SUCCESS == result); + (void)result; + + // realize the output mix + result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE); + assert(SL_RESULT_SUCCESS == result); + (void)result; +} + +void Java_org_chromium_latency_walt_AudioTest_destroyEngine(JNIEnv *env, jclass clazz) +{ + if (bqPlayerObject != NULL) { + (*bqPlayerObject)->Destroy(bqPlayerObject); + bqPlayerObject = NULL; + } + + if (outputMixObject != NULL) { + (*outputMixObject)->Destroy(outputMixObject); + outputMixObject = NULL; + } + + if (engineObject != NULL) { + (*engineObject)->Destroy(engineObject); + engineObject = NULL; + } +} + +// create buffer queue audio player +void Java_org_chromium_latency_walt_AudioTest_createBufferQueueAudioPlayer(JNIEnv* env, + jclass clazz, jint optimalFrameRate, jint optimalFramesPerBuffer) +{ + __android_log_print(ANDROID_LOG_VERBOSE, APPNAME, "Creating audio player with frame rate %d and frames per buffer %d", + optimalFrameRate, optimalFramesPerBuffer); + + framesPerBuffer = optimalFramesPerBuffer; + createWaveTables(); + + SLresult result; + + // configure the audio source (supply data through a buffer queue in PCM format) + SLDataLocator_AndroidSimpleBufferQueue locator_bufferqueue_source; + SLDataFormat_PCM format_pcm; + SLDataSource audio_source; + + // source location + locator_bufferqueue_source.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE; + locator_bufferqueue_source.numBuffers = 1; + + // source format + format_pcm.formatType = SL_DATAFORMAT_PCM; + format_pcm.numChannels = 1; + + // Note: this shouldn't be called samplesPerSec it should be called *framesPerSec* + // because when channels = 2 then there are 2 samples per frame. + format_pcm.samplesPerSec = (SLuint32) optimalFrameRate * 1000; + format_pcm.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16; + format_pcm.containerSize = 16; + format_pcm.channelMask = SL_SPEAKER_FRONT_CENTER; + format_pcm.endianness = SL_BYTEORDER_LITTLEENDIAN; + + audio_source.pLocator = &locator_bufferqueue_source; + audio_source.pFormat = &format_pcm; + + // configure the output: An output mix sink + SLDataLocator_OutputMix locator_output_mix; + SLDataSink audio_sink; + + locator_output_mix.locatorType = SL_DATALOCATOR_OUTPUTMIX; + locator_output_mix.outputMix = outputMixObject; + + audio_sink.pLocator = &locator_output_mix; + audio_sink.pFormat = NULL; + + // create audio player + // Note: Adding other output interfaces here will result in your audio being routed using the + // normal path NOT the fast path + const SLInterfaceID interface_ids[2] = { SL_IID_ANDROIDSIMPLEBUFFERQUEUE, SL_IID_VOLUME }; + const SLboolean interfaces_required[2] = { SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE }; + + result = (*engineEngine)->CreateAudioPlayer( + engineEngine, + &bqPlayerObject, + &audio_source, + &audio_sink, + 2, // Number of interfaces + interface_ids, + interfaces_required + ); + + assert(SL_RESULT_SUCCESS == result); + (void)result; + + // realize the player + result = (*bqPlayerObject)->Realize(bqPlayerObject, SL_BOOLEAN_FALSE); + assert(SL_RESULT_SUCCESS == result); + (void)result; + + // get the play interface + result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_PLAY, &bqPlayerPlay); + assert(SL_RESULT_SUCCESS == result); + (void)result; + + // get the buffer queue interface + result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_BUFFERQUEUE, + &bqPlayerBufferQueue); + assert(SL_RESULT_SUCCESS == result); + (void)result; + + // register callback on the buffer queue + result = (*bqPlayerBufferQueue)->RegisterCallback(bqPlayerBufferQueue, bqPlayerCallback, NULL); + assert(SL_RESULT_SUCCESS == result); + (void)result; +} + +void Java_org_chromium_latency_walt_AudioTest_startWarmTest(JNIEnv* env, jclass clazz) { + SLresult result; + + result = (*bqPlayerBufferQueue)->Clear(bqPlayerBufferQueue); + assert(SL_RESULT_SUCCESS == result); + (void)result; + + // enqueue some silence + result = (*bqPlayerBufferQueue)->Enqueue(bqPlayerBufferQueue, silenceBuffer, bufferSizeInBytes); + assert(SL_RESULT_SUCCESS == result); + (void)result; + + // set the player's state to playing + result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PLAYING); + assert(SL_RESULT_SUCCESS == result); + (void)result; + + warmedUp = 1; +} + +void Java_org_chromium_latency_walt_AudioTest_stopTests(JNIEnv *env, jclass clazz) { + SLresult result; + + result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_STOPPED); + assert(SL_RESULT_SUCCESS == result); + (void)result; + + warmedUp = 0; +} + +// this callback handler is called every time a buffer finishes recording +void bqRecorderCallback(SLAndroidSimpleBufferQueueItf bq, void *context) +{ + tc_rec = uptimeMicros(); + assert(bq == recorderBufferQueue); + assert(NULL == context); + + // for streaming recording, here we would call Enqueue to give recorder the next buffer to fill + // but instead, this is a one-time buffer so we stop recording + SLresult result; + result = (*recorderRecord)->SetRecordState(recorderRecord, SL_RECORDSTATE_STOPPED); + if (SL_RESULT_SUCCESS == result) { + recorderSize = recorder_frames * sizeof(short); + } + bqPlayerRecorderBusy = 0; + + //// TODO: Use small buffers and re-enqueue each time + // result = (*recorderBufferQueue)->Enqueue(recorderBufferQueue, recorderBuffer, + // recorder_frames * sizeof(short)); + // assert(SL_RESULT_SUCCESS == result); +} + +// create audio recorder +jboolean Java_org_chromium_latency_walt_AudioTest_createAudioRecorder(JNIEnv* env, + jclass clazz, jint optimalFrameRate, jint framesToRecord) +{ + SLresult result; + + __android_log_print(ANDROID_LOG_VERBOSE, APPNAME, "Creating audio recorder with frame rate %d and frames to record %d", + optimalFrameRate, framesToRecord); + // Allocate buffer + recorder_frames = framesToRecord; + recorderBuffer = malloc(sizeof(*recorderBuffer) * recorder_frames); + + // configure audio source + SLDataLocator_IODevice loc_dev = { + SL_DATALOCATOR_IODEVICE, + SL_IODEVICE_AUDIOINPUT, + SL_DEFAULTDEVICEID_AUDIOINPUT, + NULL + }; + SLDataSource audioSrc = {&loc_dev, NULL}; + + // configure audio sink + SLDataLocator_AndroidSimpleBufferQueue loc_bq; + loc_bq.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE; + loc_bq.numBuffers = 2; + + + // source format + SLDataFormat_PCM format_pcm; + format_pcm.formatType = SL_DATAFORMAT_PCM; + format_pcm.numChannels = CHANNELS; + // Note: this shouldn't be called samplesPerSec it should be called *framesPerSec* + // because when channels = 2 then there are 2 samples per frame. + format_pcm.samplesPerSec = (SLuint32) optimalFrameRate * 1000; + format_pcm.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16; + format_pcm.containerSize = 16; + format_pcm.channelMask = SL_SPEAKER_FRONT_CENTER; + format_pcm.endianness = SL_BYTEORDER_LITTLEENDIAN; + + + SLDataSink audioSnk = {&loc_bq, &format_pcm}; + + // create audio recorder + // (requires the RECORD_AUDIO permission) + const SLInterfaceID id[2] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE, + SL_IID_ANDROIDCONFIGURATION }; + const SLboolean req[2] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE}; + result = (*engineEngine)->CreateAudioRecorder(engineEngine, + &recorderObject, + &audioSrc, + &audioSnk, + sizeof(id)/sizeof(id[0]), + id, req); + + // Configure the voice recognition preset which has no + // signal processing for lower latency. + SLAndroidConfigurationItf inputConfig; + result = (*recorderObject)->GetInterface(recorderObject, + SL_IID_ANDROIDCONFIGURATION, + &inputConfig); + if (SL_RESULT_SUCCESS == result) { + SLuint32 presetValue = SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION; + (*inputConfig)->SetConfiguration(inputConfig, + SL_ANDROID_KEY_RECORDING_PRESET, + &presetValue, + sizeof(SLuint32)); + } + + // realize the audio recorder + result = (*recorderObject)->Realize(recorderObject, SL_BOOLEAN_FALSE); + if (SL_RESULT_SUCCESS != result) { + return JNI_FALSE; + } + + // get the record interface + result = (*recorderObject)->GetInterface(recorderObject, SL_IID_RECORD, &recorderRecord); + assert(SL_RESULT_SUCCESS == result); + (void)result; + + // get the buffer queue interface + result = (*recorderObject)->GetInterface(recorderObject, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, + &recorderBufferQueue); + assert(SL_RESULT_SUCCESS == result); + (void)result; + + // register callback on the buffer queue + result = (*recorderBufferQueue)->RegisterCallback(recorderBufferQueue, bqRecorderCallback, + NULL); + assert(SL_RESULT_SUCCESS == result); + (void)result; + + __android_log_print(ANDROID_LOG_VERBOSE, APPNAME, "Audio recorder created, buffer size: %d frames", + recorder_frames); + + return JNI_TRUE; +} + + +// set the recording state for the audio recorder +void Java_org_chromium_latency_walt_AudioTest_startRecording(JNIEnv* env, jclass clazz) +{ + SLresult result; + + if( bqPlayerRecorderBusy) { + return; + } + // in case already recording, stop recording and clear buffer queue + result = (*recorderRecord)->SetRecordState(recorderRecord, SL_RECORDSTATE_STOPPED); + assert(SL_RESULT_SUCCESS == result); + (void)result; + result = (*recorderBufferQueue)->Clear(recorderBufferQueue); + assert(SL_RESULT_SUCCESS == result); + (void)result; + + // the buffer is not valid for playback yet + recorderSize = 0; + + // enqueue an empty buffer to be filled by the recorder + // (for streaming recording, we would enqueue at least 2 empty buffers to start things off) + te_rec = uptimeMicros(); // TODO: investigate if it's better to time after SetRecordState + tc_rec = 0; + result = (*recorderBufferQueue)->Enqueue(recorderBufferQueue, recorderBuffer, + recorder_frames * sizeof(short)); + // the most likely other result is SL_RESULT_BUFFER_INSUFFICIENT, + // which for this code example would indicate a programming error + assert(SL_RESULT_SUCCESS == result); + (void)result; + + // start recording + result = (*recorderRecord)->SetRecordState(recorderRecord, SL_RECORDSTATE_RECORDING); + assert(SL_RESULT_SUCCESS == result); + (void)result; + bqPlayerRecorderBusy = 1; +} + +jshortArray Java_org_chromium_latency_walt_AudioTest_getRecordedWave(JNIEnv *env, jclass cls) +{ + jshortArray result; + result = (*env)->NewShortArray(env, recorder_frames); + if (result == NULL) { + return NULL; /* out of memory error thrown */ + } + (*env)->SetShortArrayRegion(env, result, 0, recorder_frames, recorderBuffer); + return result; +} + +jlong Java_org_chromium_latency_walt_AudioTest_getTcRec(JNIEnv *env, jclass cls) { + return (jlong) tc_rec; +} + +jlong Java_org_chromium_latency_walt_AudioTest_getTeRec(JNIEnv *env, jclass cls) { + return (jlong) te_rec; +} + +jlong Java_org_chromium_latency_walt_AudioTest_getTePlay(JNIEnv *env, jclass cls) { + return (jlong) te_play; +} |