blob: ce8bf91a200b71d5be24f1ce736d37f85aef5839 [file] [log] [blame]
/*
* Copyright (C) 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.
*/
//#define LOG_NDEBUG 0
#define LOG_TAG "audio-track-native"
#include "Blob.h"
#include "Gate.h"
#include "sl-utils.h"
#include <deque>
#include <utils/Errors.h>
// Select whether to use STL shared pointer or to use Android strong pointer.
// We really don't promote any sharing of this object for its lifetime, but nevertheless could
// change the shared pointer value on the fly if desired.
#define USE_SHARED_POINTER
#ifdef USE_SHARED_POINTER
#include <memory>
template <typename T> using shared_pointer = std::shared_ptr<T>;
#else
#include <utils/RefBase.h>
template <typename T> using shared_pointer = android::sp<T>;
#endif
using namespace android;
// Must be kept in sync with Java android.media.cts.AudioTrackNative.WriteFlags
enum {
WRITE_FLAG_BLOCKING = (1 << 0),
};
// TODO: Add a single buffer blocking write mode which does not require additional memory.
// TODO: Add internal buffer memory (e.g. use circular buffer, right now mallocs on heap).
class AudioTrackNative
#ifndef USE_SHARED_POINTER
: public RefBase // android strong pointers require RefBase
#endif
{
public:
AudioTrackNative() :
mEngineObj(NULL),
mEngine(NULL),
mOutputMixObj(NULL),
mPlayerObj(NULL),
mPlay(NULL),
mBufferQueue(NULL),
mPlayState(SL_PLAYSTATE_STOPPED),
mNumBuffers(0)
{ }
~AudioTrackNative() {
close();
}
typedef std::lock_guard<std::recursive_mutex> auto_lock;
status_t open(jint numChannels, jint channelMask,
jint sampleRate, jboolean useFloat, jint numBuffers) {
close();
auto_lock l(mLock);
mEngineObj = OpenSLEngine();
if (mEngineObj == NULL) {
ALOGW("cannot create OpenSL ES engine");
return INVALID_OPERATION;
}
SLresult res;
for (;;) {
/* Get the SL Engine Interface which is implicit */
res = (*mEngineObj)->GetInterface(mEngineObj, SL_IID_ENGINE, (void *)&mEngine);
if (res != SL_RESULT_SUCCESS) break;
// Create Output Mix object to be used by player
res = (*mEngine)->CreateOutputMix(
mEngine, &mOutputMixObj, 0 /* numInterfaces */,
NULL /* pInterfaceIds */, NULL /* pInterfaceRequired */);
if (res != SL_RESULT_SUCCESS) break;
// Realizing the Output Mix object in synchronous mode.
res = (*mOutputMixObj)->Realize(mOutputMixObj, SL_BOOLEAN_FALSE /* async */);
if (res != SL_RESULT_SUCCESS) break;
/* Setup the data source structure for the buffer queue */
SLDataLocator_BufferQueue bufferQueue;
bufferQueue.locatorType = SL_DATALOCATOR_BUFFERQUEUE;
bufferQueue.numBuffers = numBuffers;
mNumBuffers = numBuffers;
/* Setup the format of the content in the buffer queue */
SLAndroidDataFormat_PCM_EX pcm;
pcm.formatType = useFloat ? SL_ANDROID_DATAFORMAT_PCM_EX : SL_DATAFORMAT_PCM;
pcm.numChannels = numChannels;
pcm.sampleRate = sampleRate * 1000;
pcm.bitsPerSample = useFloat ?
SL_PCMSAMPLEFORMAT_FIXED_32 : SL_PCMSAMPLEFORMAT_FIXED_16;
pcm.containerSize = pcm.bitsPerSample;
pcm.channelMask = channelMask;
pcm.endianness = SL_BYTEORDER_LITTLEENDIAN;
// additional
pcm.representation = useFloat ? SL_ANDROID_PCM_REPRESENTATION_FLOAT
: SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT;
SLDataSource audioSource;
audioSource.pFormat = (void *)&pcm;
audioSource.pLocator = (void *)&bufferQueue;
/* Setup the data sink structure */
SLDataLocator_OutputMix locator_outputmix;
locator_outputmix.locatorType = SL_DATALOCATOR_OUTPUTMIX;
locator_outputmix.outputMix = mOutputMixObj;
SLDataSink audioSink;
audioSink.pLocator = (void *)&locator_outputmix;
audioSink.pFormat = NULL;
SLboolean required[1];
SLInterfaceID iidArray[1];
required[0] = SL_BOOLEAN_TRUE;
iidArray[0] = SL_IID_BUFFERQUEUE;
res = (*mEngine)->CreateAudioPlayer(mEngine, &mPlayerObj,
&audioSource, &audioSink, 1 /* numInterfaces */, iidArray, required);
if (res != SL_RESULT_SUCCESS) break;
res = (*mPlayerObj)->Realize(mPlayerObj, SL_BOOLEAN_FALSE /* async */);
if (res != SL_RESULT_SUCCESS) break;
res = (*mPlayerObj)->GetInterface(mPlayerObj, SL_IID_PLAY, (void*)&mPlay);
if (res != SL_RESULT_SUCCESS) break;
res = (*mPlayerObj)->GetInterface(
mPlayerObj, SL_IID_BUFFERQUEUE, (void*)&mBufferQueue);
if (res != SL_RESULT_SUCCESS) break;
/* Setup to receive buffer queue event callbacks */
res = (*mBufferQueue)->RegisterCallback(mBufferQueue, BufferQueueCallback, this);
if (res != SL_RESULT_SUCCESS) break;
// success
break;
}
if (res != SL_RESULT_SUCCESS) {
close(); // should be safe to close even with lock held
ALOGW("open error %s", android::getSLErrStr(res));
return INVALID_OPERATION;
}
return OK;
}
void close() {
SLObjectItf engineObj;
SLObjectItf outputMixObj;
SLObjectItf playerObj;
{
auto_lock l(mLock);
if (mPlay != NULL && mPlayState != SL_PLAYSTATE_STOPPED) {
(void)stop();
}
// once stopped, we can unregister the callback
if (mBufferQueue != NULL) {
(void)(*mBufferQueue)->RegisterCallback(
mBufferQueue, NULL /* callback */, NULL /* *pContext */);
}
(void)flush();
engineObj = mEngineObj;
outputMixObj = mOutputMixObj;
playerObj = mPlayerObj;
// clear out interfaces and objects
mPlay = NULL;
mBufferQueue = NULL;
mEngine = NULL;
mPlayerObj = NULL;
mOutputMixObj = NULL;
mEngineObj = NULL;
mPlayState = SL_PLAYSTATE_STOPPED;
}
// destroy without lock
if (playerObj != NULL) {
(*playerObj)->Destroy(playerObj);
}
if (outputMixObj != NULL) {
(*outputMixObj)->Destroy(outputMixObj);
}
if (engineObj != NULL) {
CloseSLEngine(engineObj);
}
}
status_t setPlayState(SLuint32 playState) {
auto_lock l(mLock);
if (mPlay == NULL) {
return INVALID_OPERATION;
}
SLresult res = (*mPlay)->SetPlayState(mPlay, playState);
if (res != SL_RESULT_SUCCESS) {
ALOGW("setPlayState %d error %s", playState, android::getSLErrStr(res));
return INVALID_OPERATION;
}
mPlayState = playState;
return OK;
}
SLuint32 getPlayState() {
auto_lock l(mLock);
if (mPlay == NULL) {
return SL_PLAYSTATE_STOPPED;
}
SLuint32 playState;
SLresult res = (*mPlay)->GetPlayState(mPlay, &playState);
if (res != SL_RESULT_SUCCESS) {
ALOGW("getPlayState error %s", android::getSLErrStr(res));
return SL_PLAYSTATE_STOPPED;
}
return playState;
}
status_t getPositionInMsec(int64_t *position) {
auto_lock l(mLock);
if (mPlay == NULL) {
return INVALID_OPERATION;
}
if (position == NULL) {
return BAD_VALUE;
}
SLuint32 pos;
SLresult res = (*mPlay)->GetPosition(mPlay, &pos);
if (res != SL_RESULT_SUCCESS) {
ALOGW("getPosition error %s", android::getSLErrStr(res));
return INVALID_OPERATION;
}
// only lower 32 bits valid
*position = pos;
return OK;
}
status_t start() {
return setPlayState(SL_PLAYSTATE_PLAYING);
}
status_t pause() {
return setPlayState(SL_PLAYSTATE_PAUSED);
}
status_t stop() {
return setPlayState(SL_PLAYSTATE_STOPPED);
}
status_t flush() {
auto_lock l(mLock);
status_t result = OK;
if (mBufferQueue != NULL) {
SLresult res = (*mBufferQueue)->Clear(mBufferQueue);
if (res != SL_RESULT_SUCCESS) {
return INVALID_OPERATION;
}
}
// possible race if the engine is in the callback
// safety is only achieved if the player is paused or stopped.
mDeliveredQueue.clear();
return result;
}
status_t write(const void *buffer, size_t size, bool isBlocking = false) {
std::lock_guard<std::mutex> rl(mWriteLock);
// not needed if we assume that a single thread is doing the reading
// or we always operate in non-blocking mode.
{
auto_lock l(mLock);
if (mBufferQueue == NULL) {
return INVALID_OPERATION;
}
if (mDeliveredQueue.size() < mNumBuffers) {
auto b = std::make_shared<BlobReadOnly>(buffer, size, false /* byReference */);
mDeliveredQueue.emplace_back(b);
(*mBufferQueue)->Enqueue(mBufferQueue, b->mData, b->mSize);
return size;
}
if (!isBlocking) {
return 0;
}
mWriteReady.closeGate(); // we're full.
}
if (mWriteReady.wait()) {
auto_lock l(mLock);
if (mDeliveredQueue.size() < mNumBuffers) {
auto b = std::make_shared<BlobReadOnly>(buffer, size, false /* byReference */);
mDeliveredQueue.emplace_back(b);
(*mBufferQueue)->Enqueue(mBufferQueue, b->mData, b->mSize);
return size;
}
}
ALOGW("unable to deliver write");
return 0;
}
void logBufferState() {
auto_lock l(mLock);
SLBufferQueueState state;
SLresult res = (*mBufferQueue)->GetState(mBufferQueue, &state);
CheckErr(res);
ALOGD("logBufferState state.count:%d state.playIndex:%d", state.count, state.playIndex);
}
size_t getBuffersPending() {
auto_lock l(mLock);
return mDeliveredQueue.size();
}
private:
void bufferQueueCallback(SLBufferQueueItf queueItf) {
auto_lock l(mLock);
if (queueItf != mBufferQueue) {
ALOGW("invalid buffer queue interface, ignoring");
return;
}
// logBufferState();
// remove from delivered queue
if (mDeliveredQueue.size()) {
mDeliveredQueue.pop_front();
} else {
ALOGW("no delivered data!");
}
if (!mWriteReady.isOpen()) {
mWriteReady.openGate();
}
}
static void BufferQueueCallback(SLBufferQueueItf queueItf, void *pContext) {
// naked native track
AudioTrackNative *track = (AudioTrackNative *)pContext;
track->bufferQueueCallback(queueItf);
}
SLObjectItf mEngineObj;
SLEngineItf mEngine;
SLObjectItf mOutputMixObj;
SLObjectItf mPlayerObj;
SLPlayItf mPlay;
SLBufferQueueItf mBufferQueue;
SLuint32 mPlayState;
SLuint32 mNumBuffers;
std::recursive_mutex mLock; // monitor lock - locks public API methods and callback.
// recursive since it may call itself through API.
std::mutex mWriteLock; // write lock - for blocking mode, prevents multiple
// writer threads from overlapping writes. this is
// generally unnecessary as writes occur from
// one thread only. acquire this before mLock.
Gate mWriteReady;
std::deque<std::shared_ptr<BlobReadOnly>> mDeliveredQueue; // delivered to mBufferQueue
};
/* Java static methods.
*
* These are not directly exposed to the user, so we can assume a valid "jtrack" handle
* to be passed in.
*/
extern "C" jint Java_android_media_cts_AudioTrackNative_nativeTest(
JNIEnv * /* env */, jclass /* clazz */,
jint numChannels, jint channelMask, jint sampleRate, jboolean useFloat,
jint msecPerBuffer, jint numBuffers)
{
AudioTrackNative track;
const size_t frameSize = numChannels * (useFloat ? sizeof(float) : sizeof(int16_t));
const size_t framesPerBuffer = msecPerBuffer * sampleRate / 1000;
status_t res;
void *buffer = calloc(framesPerBuffer * numBuffers, frameSize);
for (;;) {
res = track.open(numChannels, channelMask, sampleRate, useFloat, numBuffers);
if (res != OK) break;
for (int i = 0; i < numBuffers; ++i) {
track.write((char *)buffer + i * (framesPerBuffer * frameSize),
framesPerBuffer * frameSize);
}
track.logBufferState();
res = track.start();
if (res != OK) break;
size_t buffers;
while ((buffers = track.getBuffersPending()) > 0) {
// ALOGD("outstanding buffers: %zu", buffers);
usleep(5 * 1000 /* usec */);
}
res = track.stop();
break;
}
track.close();
free(buffer);
return res;
}
extern "C" jlong Java_android_media_cts_AudioTrackNative_nativeCreateTrack(
JNIEnv * /* env */, jclass /* clazz */)
{
return (jlong)(new shared_pointer<AudioTrackNative>(new AudioTrackNative()));
}
extern "C" void Java_android_media_cts_AudioTrackNative_nativeDestroyTrack(
JNIEnv * /* env */, jclass /* clazz */, jlong jtrack)
{
delete (shared_pointer<AudioTrackNative> *)jtrack;
}
extern "C" jint Java_android_media_cts_AudioTrackNative_nativeOpen(
JNIEnv * /* env */, jclass /* clazz */, jlong jtrack,
jint numChannels, jint channelMask, jint sampleRate,
jboolean useFloat, jint numBuffers)
{
auto track = *(shared_pointer<AudioTrackNative> *)jtrack;
if (track.get() == NULL) {
return (jint)INVALID_OPERATION;
}
return (jint) track->open(numChannels,
channelMask,
sampleRate,
useFloat == JNI_TRUE,
numBuffers);
}
extern "C" void Java_android_media_cts_AudioTrackNative_nativeClose(
JNIEnv * /* env */, jclass /* clazz */, jlong jtrack)
{
auto track = *(shared_pointer<AudioTrackNative> *)jtrack;
if (track.get() != NULL) {
track->close();
}
}
extern "C" jint Java_android_media_cts_AudioTrackNative_nativeStart(
JNIEnv * /* env */, jclass /* clazz */, jlong jtrack)
{
auto track = *(shared_pointer<AudioTrackNative> *)jtrack;
if (track.get() == NULL) {
return (jint)INVALID_OPERATION;
}
return (jint)track->start();
}
extern "C" jint Java_android_media_cts_AudioTrackNative_nativeStop(
JNIEnv * /* env */, jclass /* clazz */, jlong jtrack)
{
auto track = *(shared_pointer<AudioTrackNative> *)jtrack;
if (track.get() == NULL) {
return (jint)INVALID_OPERATION;
}
return (jint)track->stop();
}
extern "C" jint Java_android_media_cts_AudioTrackNative_nativePause(
JNIEnv * /* env */, jclass /* clazz */, jlong jtrack)
{
auto track = *(shared_pointer<AudioTrackNative> *)jtrack;
if (track.get() == NULL) {
return (jint)INVALID_OPERATION;
}
return (jint)track->pause();
}
extern "C" jint Java_android_media_cts_AudioTrackNative_nativeFlush(
JNIEnv * /* env */, jclass /* clazz */, jlong jtrack)
{
auto track = *(shared_pointer<AudioTrackNative> *)jtrack;
if (track.get() == NULL) {
return (jint)INVALID_OPERATION;
}
return (jint)track->flush();
}
extern "C" jint Java_android_media_cts_AudioTrackNative_nativeGetPositionInMsec(
JNIEnv *env, jclass /* clazz */, jlong jtrack, jlongArray jPosition)
{
auto track = *(shared_pointer<AudioTrackNative> *)jtrack;
if (track.get() == NULL) {
return (jint)INVALID_OPERATION;
}
int64_t pos;
status_t res = track->getPositionInMsec(&pos);
if (res != OK) {
return res;
}
jlong *nPostition = (jlong *) env->GetPrimitiveArrayCritical(jPosition, NULL /* isCopy */);
if (nPostition == NULL) {
ALOGE("Unable to get array for nativeGetPositionInMsec()");
return BAD_VALUE;
}
nPostition[0] = (jlong)pos;
env->ReleasePrimitiveArrayCritical(jPosition, nPostition, 0 /* mode */);
return OK;
}
extern "C" jint Java_android_media_cts_AudioTrackNative_nativeGetBuffersPending(
JNIEnv * /* env */, jclass /* clazz */, jlong jtrack)
{
auto track = *(shared_pointer<AudioTrackNative> *)jtrack;
if (track.get() == NULL) {
return (jint)0;
}
return (jint)track->getBuffersPending();
}
template <typename T>
static inline jint writeToTrack(jlong jtrack, const T *data,
jint offsetInSamples, jint sizeInSamples, jint writeFlags)
{
auto track = *(shared_pointer<AudioTrackNative> *)jtrack;
if (track.get() == NULL) {
return (jint)INVALID_OPERATION;
}
const bool isBlocking = writeFlags & WRITE_FLAG_BLOCKING;
const size_t sizeInBytes = sizeInSamples * sizeof(T);
ssize_t ret = track->write(data + offsetInSamples, sizeInBytes, isBlocking);
return (jint)(ret > 0 ? ret / sizeof(T) : ret);
}
template <typename T>
static inline jint writeArray(JNIEnv *env, jclass /* clazz */, jlong jtrack,
T javaAudioData, jint offsetInSamples, jint sizeInSamples, jint writeFlags)
{
if (javaAudioData == NULL) {
return (jint)INVALID_OPERATION;
}
auto cAudioData = envGetArrayElements(env, javaAudioData, NULL /* isCopy */);
if (cAudioData == NULL) {
ALOGE("Error retrieving source of audio data to play");
return (jint)BAD_VALUE;
}
jint ret = writeToTrack(jtrack, cAudioData, offsetInSamples, sizeInSamples, writeFlags);
envReleaseArrayElements(env, javaAudioData, cAudioData, 0 /* mode */);
return ret;
}
extern "C" jint Java_android_media_cts_AudioTrackNative_nativeWriteByteArray(
JNIEnv *env, jclass clazz, jlong jtrack,
jbyteArray byteArray, jint offsetInSamples, jint sizeInSamples, jint writeFlags)
{
ALOGV("nativeWriteByteArray(%p, %d, %d, %d)",
byteArray, offsetInSamples, sizeInSamples, writeFlags);
return writeArray(env, clazz, jtrack, byteArray, offsetInSamples, sizeInSamples, writeFlags);
}
extern "C" jint Java_android_media_cts_AudioTrackNative_nativeWriteShortArray(
JNIEnv *env, jclass clazz, jlong jtrack,
jshortArray shortArray, jint offsetInSamples, jint sizeInSamples, jint writeFlags)
{
ALOGV("nativeWriteShortArray(%p, %d, %d, %d)",
shortArray, offsetInSamples, sizeInSamples, writeFlags);
return writeArray(env, clazz, jtrack, shortArray, offsetInSamples, sizeInSamples, writeFlags);
}
extern "C" jint Java_android_media_cts_AudioTrackNative_nativeWriteFloatArray(
JNIEnv *env, jclass clazz, jlong jtrack,
jfloatArray floatArray, jint offsetInSamples, jint sizeInSamples, jint writeFlags)
{
ALOGV("nativeWriteFloatArray(%p, %d, %d, %d)",
floatArray, offsetInSamples, sizeInSamples, writeFlags);
return writeArray(env, clazz, jtrack, floatArray, offsetInSamples, sizeInSamples, writeFlags);
}