blob: 9103cdc6f9ffb7ee97d351982c3666e6b881a616 [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-record-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.AudioRecordNative.ReadFlags
enum {
READ_FLAG_BLOCKING = (1 << 0),
};
// buffer queue buffers on the OpenSL ES side.
// The choice can be >= 1. There is also internal buffering by AudioRecord.
static const size_t BUFFER_SIZE_MSEC = 20;
// TODO: Add a single buffer blocking read mode which does not require additional memory.
// TODO: Add internal buffer memory (e.g. use circular buffer, right now mallocs on heap).
class AudioRecordNative
#ifndef USE_SHARED_POINTER
: public RefBase // android strong pointers require RefBase
#endif
{
public:
AudioRecordNative() :
mEngineObj(NULL),
mEngine(NULL),
mRecordObj(NULL),
mRecord(NULL),
mBufferQueue(NULL),
mRecordState(SL_RECORDSTATE_STOPPED),
mBufferSize(0),
mNumBuffers(0)
{ }
~AudioRecordNative() {
close();
}
typedef std::lock_guard<std::recursive_mutex> auto_lock;
status_t open(uint32_t numChannels, uint32_t sampleRate, bool useFloat, uint32_t 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;
SLDataLocator_IODevice locator_mic;
/* Setup the data source structure */
locator_mic.locatorType = SL_DATALOCATOR_IODEVICE;
locator_mic.deviceType = SL_IODEVICE_AUDIOINPUT;
locator_mic.deviceID = SL_DEFAULTDEVICEID_AUDIOINPUT;
locator_mic.device= NULL;
SLDataSource audioSource;
audioSource.pLocator = (void *)&locator_mic;
audioSource.pFormat = NULL;
// FIXME: Android requires SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE
// because the recorder makes the distinction from SL_DATALOCATOR_BUFFERQUEUE
// which the player does not.
SLDataLocator_AndroidSimpleBufferQueue loc_bq = {
SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, numBuffers
};
#if 0
SLDataFormat_PCM pcm = {
SL_DATAFORMAT_PCM, 1, SL_SAMPLINGRATE_16,
SL_PCMSAMPLEFORMAT_FIXED_16, SL_PCMSAMPLEFORMAT_FIXED_16,
SL_SPEAKER_FRONT_LEFT, SL_BYTEORDER_LITTLEENDIAN
};
#else
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 = channelCountToMask(numChannels);
pcm.endianness = SL_BYTEORDER_LITTLEENDIAN;
// additional
pcm.representation = useFloat ? SL_ANDROID_PCM_REPRESENTATION_FLOAT
: SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT;
#endif
SLDataSink audioSink;
audioSink = { &loc_bq, &pcm };
SLboolean required[2];
SLInterfaceID iidArray[2];
/* Request the AndroidSimpleBufferQueue and AndroidConfiguration interfaces */
required[0] = SL_BOOLEAN_TRUE;
iidArray[0] = SL_IID_ANDROIDSIMPLEBUFFERQUEUE;
required[1] = SL_BOOLEAN_TRUE;
iidArray[1] = SL_IID_ANDROIDCONFIGURATION;
ALOGV("creating recorder");
/* Create audio recorder */
res = (*mEngine)->CreateAudioRecorder(mEngine, &mRecordObj,
&audioSource, &audioSink, 2, iidArray, required);
if (res != SL_RESULT_SUCCESS) break;
ALOGV("realizing recorder");
/* Realizing the recorder in synchronous mode. */
res = (*mRecordObj)->Realize(mRecordObj, SL_BOOLEAN_FALSE /* async */);
if (res != SL_RESULT_SUCCESS) break;
ALOGV("geting record interface");
/* Get the RECORD interface - it is an implicit interface */
res = (*mRecordObj)->GetInterface(mRecordObj, SL_IID_RECORD, (void *)&mRecord);
if (res != SL_RESULT_SUCCESS) break;
ALOGV("geting buffer queue interface");
/* Get the buffer queue interface which was explicitly requested */
res = (*mRecordObj)->GetInterface(mRecordObj, SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
(void *)&mBufferQueue);
if (res != SL_RESULT_SUCCESS) break;
ALOGV("registering buffer queue interface");
/* Setup to receive buffer queue event callbacks */
res = (*mBufferQueue)->RegisterCallback(mBufferQueue, BufferQueueCallback, this);
if (res != SL_RESULT_SUCCESS) break;
mBufferSize = (BUFFER_SIZE_MSEC * sampleRate / 1000)
* numChannels * (useFloat ? sizeof(float) : sizeof(int16_t));
mNumBuffers = numBuffers;
// 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 recordObj;
{
auto_lock l(mLock);
(void)stop();
// once stopped, we can unregister the callback
if (mBufferQueue != NULL) {
(void)(*mBufferQueue)->RegisterCallback(
mBufferQueue, NULL /* callback */, NULL /* *pContext */);
}
(void)flush();
engineObj = mEngineObj;
recordObj = mRecordObj;
// clear out interfaces and objects
mRecord = NULL;
mBufferQueue = NULL;
mEngine = NULL;
mRecordObj = NULL;
mEngineObj = NULL;
mRecordState = SL_RECORDSTATE_STOPPED;
mBufferSize = 0;
mNumBuffers = 0;
}
// destroy without lock
if (recordObj != NULL) {
(*recordObj)->Destroy(recordObj);
}
if (engineObj) {
CloseSLEngine(engineObj);
}
}
status_t setRecordState(SLuint32 recordState) {
auto_lock l(mLock);
if (mRecord == NULL) {
return INVALID_OPERATION;
}
if (recordState == SL_RECORDSTATE_RECORDING) {
queueBuffers();
}
SLresult res = (*mRecord)->SetRecordState(mRecord, recordState);
if (res != SL_RESULT_SUCCESS) {
ALOGW("setRecordState %d error %s", recordState, android::getSLErrStr(res));
return INVALID_OPERATION;
}
mRecordState = recordState;
return OK;
}
SLuint32 getRecordState() {
auto_lock l(mLock);
if (mRecord == NULL) {
return SL_RECORDSTATE_STOPPED;
}
SLuint32 recordState;
SLresult res = (*mRecord)->GetRecordState(mRecord, &recordState);
if (res != SL_RESULT_SUCCESS) {
ALOGW("getRecordState error %s", android::getSLErrStr(res));
return SL_RECORDSTATE_STOPPED;
}
return recordState;
}
status_t getPositionInMsec(int64_t *position) {
auto_lock l(mLock);
if (mRecord == NULL) {
return INVALID_OPERATION;
}
if (position == NULL) {
return BAD_VALUE;
}
SLuint32 pos;
SLresult res = (*mRecord)->GetPosition(mRecord, &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 setRecordState(SL_RECORDSTATE_RECORDING);
}
status_t pause() {
return setRecordState(SL_RECORDSTATE_PAUSED);
}
status_t stop() {
return setRecordState(SL_RECORDSTATE_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;
}
}
mReadyQueue.clear();
// possible race if the engine is in the callback
// safety is only achieved if the recorder is paused or stopped.
mDeliveredQueue.clear();
mReadBlob = NULL;
mReadReady.terminate();
return result;
}
ssize_t read(void *buffer, size_t size, bool blocking = false) {
std::lock_guard<std::mutex> rl(mReadLock);
// not needed if we assume that a single thread is doing the reading
// or we always operate in non-blocking mode.
ALOGV("reading:%p %zu", buffer, size);
size_t copied;
std::shared_ptr<Blob> blob;
{
auto_lock l(mLock);
if (mEngine == NULL) {
return INVALID_OPERATION;
}
size_t osize = size;
while (!mReadyQueue.empty() && size > 0) {
auto b = mReadyQueue.front();
size_t tocopy = min(size, b->mSize - b->mOffset);
// ALOGD("buffer:%p size:%zu b->mSize:%zu b->mOffset:%zu tocopy:%zu ",
// buffer, size, b->mSize, b->mOffset, tocopy);
memcpy(buffer, (char *)b->mData + b->mOffset, tocopy);
buffer = (char *)buffer + tocopy;
size -= tocopy;
b->mOffset += tocopy;
if (b->mOffset == b->mSize) {
mReadyQueue.pop_front();
}
}
copied = osize - size;
if (!blocking || size == 0 || mReadBlob.get() != NULL) {
return copied;
}
blob = std::make_shared<Blob>(buffer, size);
mReadBlob = blob;
mReadReady.closeGate(); // the callback will open gate when read is completed.
}
if (mReadReady.wait()) {
// success then the blob is ours with valid data otherwise a flush has occurred
// and we return a short count.
copied += blob->mOffset;
}
return copied;
}
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 mReadyQueue.size();
}
private:
status_t queueBuffers() {
if (mBufferQueue == NULL) {
return INVALID_OPERATION;
}
if (mReadyQueue.size() + mDeliveredQueue.size() < mNumBuffers) {
// add new empty buffer
auto b = std::make_shared<Blob>(mBufferSize);
mDeliveredQueue.emplace_back(b);
(*mBufferQueue)->Enqueue(mBufferQueue, b->mData, b->mSize);
}
return OK;
}
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()) {
auto b = mDeliveredQueue.front();
mDeliveredQueue.pop_front();
if (mReadBlob.get() != NULL) {
size_t tocopy = min(mReadBlob->mSize - mReadBlob->mOffset, b->mSize - b->mOffset);
memcpy((char *)mReadBlob->mData + mReadBlob->mOffset,
(char *)b->mData + b->mOffset, tocopy);
b->mOffset += tocopy;
mReadBlob->mOffset += tocopy;
if (mReadBlob->mOffset == mReadBlob->mSize) {
mReadBlob = NULL; // we're done, clear our reference.
mReadReady.openGate(); // allow read to continue.
}
if (b->mOffset == b->mSize) {
b = NULL;
}
}
if (b.get() != NULL) {
if (mReadyQueue.size() + mDeliveredQueue.size() < mNumBuffers) {
mReadyQueue.emplace_back(b); // save onto ready queue for future reads
} else {
ALOGW("dropping data");
}
}
} else {
ALOGW("no delivered data!");
}
queueBuffers();
}
static void BufferQueueCallback(SLBufferQueueItf queueItf, void *pContext) {
SLresult res;
// naked native record
AudioRecordNative *record = (AudioRecordNative *)pContext;
record->bufferQueueCallback(queueItf);
}
SLObjectItf mEngineObj;
SLEngineItf mEngine;
SLObjectItf mRecordObj;
SLRecordItf mRecord;
SLBufferQueueItf mBufferQueue;
SLuint32 mRecordState;
size_t mBufferSize;
size_t mNumBuffers;
std::recursive_mutex mLock; // monitor lock - locks public API methods and callback.
// recursive since it may call itself through API.
std::mutex mReadLock; // read lock - for blocking mode, prevents multiple
// reader threads from overlapping reads. this is
// generally unnecessary as reads occur from
// one thread only. acquire this before mLock.
std::shared_ptr<Blob> mReadBlob;
Gate mReadReady;
std::deque<std::shared_ptr<Blob>> mReadyQueue; // ready for read.
std::deque<std::shared_ptr<Blob>> mDeliveredQueue; // delivered to BufferQueue
};
/* Java static methods.
*
* These are not directly exposed to the user, so we can assume a valid "jrecord" handle
* to be passed in.
*/
extern "C" jint Java_android_media_cts_AudioRecordNative_nativeTest(
JNIEnv * /* env */, jclass /* clazz */,
jint numChannels, jint sampleRate, jboolean useFloat,
jint msecPerBuffer, jint numBuffers)
{
AudioRecordNative record;
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 = record.open(numChannels, sampleRate, useFloat, numBuffers);
if (res != OK) break;
record.logBufferState();
res = record.start();
if (res != OK) break;
size_t size = framesPerBuffer * numBuffers * frameSize;
for (size_t offset = 0; size - offset > 0; ) {
ssize_t amount = record.read((char *)buffer + offset, size -offset);
// ALOGD("read amount: %zd", amount);
if (amount < 0) break;
offset += amount;
usleep(5 * 1000 /* usec */);
}
res = record.stop();
break;
}
record.close();
free(buffer);
return res;
}
extern "C" jlong Java_android_media_cts_AudioRecordNative_nativeCreateRecord(
JNIEnv * /* env */, jclass /* clazz */)
{
return (jlong)(new shared_pointer<AudioRecordNative>(new AudioRecordNative()));
}
extern "C" void Java_android_media_cts_AudioRecordNative_nativeDestroyRecord(
JNIEnv * /* env */, jclass /* clazz */, jlong jrecord)
{
delete (shared_pointer<AudioRecordNative> *)jrecord;
}
extern "C" jint Java_android_media_cts_AudioRecordNative_nativeOpen(
JNIEnv * /* env */, jclass /* clazz */, jlong jrecord,
jint numChannels, jint sampleRate, jboolean useFloat, jint numBuffers)
{
auto record = *(shared_pointer<AudioRecordNative> *)jrecord;
if (record.get() == NULL) {
return (jint)INVALID_OPERATION;
}
return (jint)record->open(numChannels, sampleRate, useFloat == JNI_TRUE,
numBuffers);
}
extern "C" void Java_android_media_cts_AudioRecordNative_nativeClose(
JNIEnv * /* env */, jclass /* clazz */, jlong jrecord)
{
auto record = *(shared_pointer<AudioRecordNative> *)jrecord;
if (record.get() != NULL) {
record->close();
}
}
extern "C" jint Java_android_media_cts_AudioRecordNative_nativeStart(
JNIEnv * /* env */, jclass /* clazz */, jlong jrecord)
{
auto record = *(shared_pointer<AudioRecordNative> *)jrecord;
if (record.get() == NULL) {
return (jint)INVALID_OPERATION;
}
return (jint)record->start();
}
extern "C" jint Java_android_media_cts_AudioRecordNative_nativeStop(
JNIEnv * /* env */, jclass /* clazz */, jlong jrecord)
{
auto record = *(shared_pointer<AudioRecordNative> *)jrecord;
if (record.get() == NULL) {
return (jint)INVALID_OPERATION;
}
return (jint)record->stop();
}
extern "C" jint Java_android_media_cts_AudioRecordNative_nativePause(
JNIEnv * /* env */, jclass /* clazz */, jlong jrecord)
{
auto record = *(shared_pointer<AudioRecordNative> *)jrecord;
if (record.get() == NULL) {
return (jint)INVALID_OPERATION;
}
return (jint)record->pause();
}
extern "C" jint Java_android_media_cts_AudioRecordNative_nativeFlush(
JNIEnv * /* env */, jclass /* clazz */, jlong jrecord)
{
auto record = *(shared_pointer<AudioRecordNative> *)jrecord;
if (record.get() == NULL) {
return (jint)INVALID_OPERATION;
}
return (jint)record->flush();
}
extern "C" jint Java_android_media_cts_AudioRecordNative_nativeGetPositionInMsec(
JNIEnv *env, jclass /* clazz */, jlong jrecord, jlongArray jPosition)
{
auto record = *(shared_pointer<AudioRecordNative> *)jrecord;
if (record.get() == NULL) {
return (jint)INVALID_OPERATION;
}
int64_t pos;
status_t res = record->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_AudioRecordNative_nativeGetBuffersPending(
JNIEnv * /* env */, jclass /* clazz */, jlong jrecord)
{
auto record = *(shared_pointer<AudioRecordNative> *)jrecord;
if (record.get() == NULL) {
return (jint)0;
}
return (jint)record->getBuffersPending();
}
template <typename T>
static inline jint readFromRecord(jlong jrecord, T *data,
jint offsetInSamples, jint sizeInSamples, jint readFlags)
{
auto record = *(shared_pointer<AudioRecordNative> *)jrecord;
if (record.get() == NULL) {
return (jint)INVALID_OPERATION;
}
const bool isBlocking = readFlags & READ_FLAG_BLOCKING;
const size_t sizeInBytes = sizeInSamples * sizeof(T);
ssize_t ret = record->read(data + offsetInSamples, sizeInBytes, isBlocking == JNI_TRUE);
return (jint)(ret > 0 ? ret / sizeof(T) : ret);
}
template <typename T>
static inline jint readArray(JNIEnv *env, jclass /* clazz */, jlong jrecord,
T javaAudioData, jint offsetInSamples, jint sizeInSamples, jint readFlags)
{
if (javaAudioData == NULL) {
return (jint)BAD_VALUE;
}
auto cAudioData = envGetArrayElements(env, javaAudioData, NULL /* isCopy */);
if (cAudioData == NULL) {
ALOGE("Error retrieving destination of audio data to record");
return (jint)BAD_VALUE;
}
jint ret = readFromRecord(jrecord, cAudioData, offsetInSamples, sizeInSamples, readFlags);
envReleaseArrayElements(env, javaAudioData, cAudioData, 0 /* mode */);
return ret;
}
extern "C" jint Java_android_media_cts_AudioRecordNative_nativeReadByteArray(
JNIEnv *env, jclass clazz, jlong jrecord,
jbyteArray byteArray, jint offsetInSamples, jint sizeInSamples, jint readFlags)
{
return readArray(env, clazz, jrecord, byteArray, offsetInSamples, sizeInSamples, readFlags);
}
extern "C" jint Java_android_media_cts_AudioRecordNative_nativeReadShortArray(
JNIEnv *env, jclass clazz, jlong jrecord,
jshortArray shortArray, jint offsetInSamples, jint sizeInSamples, jint readFlags)
{
return readArray(env, clazz, jrecord, shortArray, offsetInSamples, sizeInSamples, readFlags);
}
extern "C" jint Java_android_media_cts_AudioRecordNative_nativeReadFloatArray(
JNIEnv *env, jclass clazz, jlong jrecord,
jfloatArray floatArray, jint offsetInSamples, jint sizeInSamples, jint readFlags)
{
return readArray(env, clazz, jrecord, floatArray, offsetInSamples, sizeInSamples, readFlags);
}