blob: 884f637fce1736bb1ed877401100ad4001b294a6 [file] [log] [blame]
/*
* Copyright (C) 2010 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.
*/
/* AndroidBufferQueue implementation */
//#define USE_LOG SLAndroidLogLevel_Verbose
#include "sles_allinclusive.h"
// for AAC ADTS verification on enqueue:
#include "android/include/AacBqToPcmCbRenderer.h"
/**
* Determine the state of the audio player or audio recorder associated with a buffer queue.
* Note that PLAYSTATE and RECORDSTATE values are equivalent (where PLAYING == RECORDING).
*/
static SLuint32 getAssociatedState(IAndroidBufferQueue *thiz)
{
SLuint32 state;
switch (InterfaceToObjectID(thiz)) {
case XA_OBJECTID_MEDIAPLAYER:
state = ((CMediaPlayer *) thiz->mThis)->mPlay.mState;
break;
case SL_OBJECTID_AUDIOPLAYER:
state = ((CAudioPlayer *) thiz->mThis)->mPlay.mState;
break;
default:
// unreachable, but just in case we will assume it is stopped
assert(SL_BOOLEAN_FALSE);
state = SL_PLAYSTATE_STOPPED;
break;
}
return state;
}
/**
* parse and set the items associated with the given buffer, based on the buffer type,
* which determines the set of authorized items and format
*/
static void setItems(const SLAndroidBufferItem *pItems, SLuint32 itemsLength,
SLuint16 bufferType, AdvancedBufferHeader *pBuff)
{
// reset item structure based on type
switch (bufferType) {
case kAndroidBufferTypeMpeg2Ts:
pBuff->mItems.mTsCmdData.mTsCmdCode = ANDROID_MP2TSEVENT_NONE;
pBuff->mItems.mTsCmdData.mPts = 0;
break;
case kAndroidBufferTypeAacadts:
pBuff->mItems.mAdtsCmdData.mAdtsCmdCode = ANDROID_ADTSEVENT_NONE;
break;
case kAndroidBufferTypeInvalid:
default:
// shouldn't happen, but just in case clear out the item structure
memset(&pBuff->mItems, 0, sizeof(AdvancedBufferItems));
return;
}
// process all items in the array; if no items then we break out of loop immediately
while (itemsLength > 0) {
// remaining length must be large enough for one full item without any associated data
if (itemsLength < sizeof(SLAndroidBufferItem)) {
SL_LOGE("Partial item at end of array ignored");
break;
}
itemsLength -= sizeof(SLAndroidBufferItem);
// remaining length must be large enough for data with current item and alignment padding
SLuint32 itemDataSizeWithAlignmentPadding = (pItems->itemSize + 3) & ~3;
if (itemsLength < itemDataSizeWithAlignmentPadding) {
SL_LOGE("Partial item data at end of array ignored");
break;
}
itemsLength -= itemDataSizeWithAlignmentPadding;
// parse item data based on type
switch (bufferType) {
case kAndroidBufferTypeMpeg2Ts: {
switch (pItems->itemKey) {
case SL_ANDROID_ITEMKEY_EOS:
pBuff->mItems.mTsCmdData.mTsCmdCode |= ANDROID_MP2TSEVENT_EOS;
//SL_LOGD("Found EOS event=%d", pBuff->mItems.mTsCmdData.mTsCmdCode);
if (pItems->itemSize != 0) {
SL_LOGE("Invalid item parameter size %u for EOS, ignoring value",
pItems->itemSize);
}
break;
case SL_ANDROID_ITEMKEY_DISCONTINUITY:
if (pItems->itemSize == 0) {
pBuff->mItems.mTsCmdData.mTsCmdCode |= ANDROID_MP2TSEVENT_DISCONTINUITY;
//SL_LOGD("Found DISCONTINUITYevent=%d", pBuff->mItems.mTsCmdData.mTsCmdCode);
} else if (pItems->itemSize == sizeof(SLAuint64)) {
pBuff->mItems.mTsCmdData.mTsCmdCode |= ANDROID_MP2TSEVENT_DISCON_NEWPTS;
pBuff->mItems.mTsCmdData.mPts = *((SLAuint64*)pItems->itemData);
//SL_LOGD("Found PTS=%lld", pBuff->mItems.mTsCmdData.mPts);
} else {
SL_LOGE("Invalid item parameter size %u for MPEG-2 PTS, ignoring value",
pItems->itemSize);
pBuff->mItems.mTsCmdData.mTsCmdCode |= ANDROID_MP2TSEVENT_DISCONTINUITY;
}
break;
case SL_ANDROID_ITEMKEY_FORMAT_CHANGE:
// distinguish between a "full" format change and one where it says what changed
if (pItems->itemSize == 0) {
SL_LOGV("Received format change with no data == full format change");
pBuff->mItems.mTsCmdData.mTsCmdCode |= ANDROID_MP2TSEVENT_FORMAT_CHANGE_FULL;
} else if (pItems->itemSize == sizeof(SLuint32)) {
XAuint32 formatData = *((XAuint32*)pItems->itemData);
// intentionally only supporting video change when reading which specific
// stream has changed, interpret other changes as full change
if (formatData == XA_ANDROID_FORMATCHANGE_ITEMDATA_VIDEO) {
pBuff->mItems.mTsCmdData.mTsCmdCode |=
ANDROID_MP2TSEVENT_FORMAT_CHANGE_VIDEO;
SL_LOGV("Received video format change");
} else {
// note that we don't support specifying
// ANDROID_MP2TSEVENT_FORMAT_CHANGE_FULL by having all bits of
// the data mask set, we default to it with unsupported masks
SL_LOGE("Received format change with unsupported data, ignoring data");
pBuff->mItems.mTsCmdData.mTsCmdCode |=
ANDROID_MP2TSEVENT_FORMAT_CHANGE_FULL;
}
} else {
SL_LOGE("Received format change with invalid data size, ignoring data");
pBuff->mItems.mTsCmdData.mTsCmdCode |= ANDROID_MP2TSEVENT_FORMAT_CHANGE_FULL;
}
break;
default:
// unknown item key
SL_LOGE("Unknown item key %u with size %u ignored", pItems->itemKey,
pItems->itemSize);
break;
}// switch (pItems->itemKey)
} break;
case kAndroidBufferTypeAacadts: {
switch (pItems->itemKey) {
case SL_ANDROID_ITEMKEY_EOS:
pBuff->mItems.mAdtsCmdData.mAdtsCmdCode |= ANDROID_ADTSEVENT_EOS;
if (pItems->itemSize != 0) {
SL_LOGE("Invalid item parameter size %u for EOS, ignoring value",
pItems->itemSize);
}
break;
default:
// unknown item key
SL_LOGE("Unknown item key %u with size %u ignored", pItems->itemKey,
pItems->itemSize);
break;
}// switch (pItems->itemKey)
} break;
case kAndroidBufferTypeInvalid:
default:
// not reachable as we checked this earlier
return;
}// switch (bufferType)
// skip past this item, including data with alignment padding
pItems = (SLAndroidBufferItem *) ((char *) pItems +
sizeof(SLAndroidBufferItem) + itemDataSizeWithAlignmentPadding);
}
// now check for invalid combinations of items
switch (bufferType) {
case kAndroidBufferTypeMpeg2Ts: {
// supported Mpeg2Ts commands are mutually exclusive
switch (pBuff->mItems.mTsCmdData.mTsCmdCode) {
// single items are allowed
case ANDROID_MP2TSEVENT_NONE:
case ANDROID_MP2TSEVENT_EOS:
case ANDROID_MP2TSEVENT_DISCONTINUITY:
case ANDROID_MP2TSEVENT_DISCON_NEWPTS:
case ANDROID_MP2TSEVENT_FORMAT_CHANGE_FULL:
case ANDROID_MP2TSEVENT_FORMAT_CHANGE_VIDEO:
break;
// no combinations are allowed
default:
SL_LOGE("Invalid combination of items; all ignored");
pBuff->mItems.mTsCmdData.mTsCmdCode = ANDROID_MP2TSEVENT_NONE;
break;
}
} break;
case kAndroidBufferTypeAacadts: {
// only one item supported, and thus no combination check needed
} break;
case kAndroidBufferTypeInvalid:
default:
// not reachable as we checked this earlier
return;
}
}
static SLresult IAndroidBufferQueue_RegisterCallback(SLAndroidBufferQueueItf self,
slAndroidBufferQueueCallback callback, void *pContext)
{
SL_ENTER_INTERFACE
IAndroidBufferQueue *thiz = (IAndroidBufferQueue *) self;
interface_lock_exclusive(thiz);
// verify pre-condition that media object is in the SL_PLAYSTATE_STOPPED state
if (SL_PLAYSTATE_STOPPED == getAssociatedState(thiz)) {
thiz->mCallback = callback;
thiz->mContext = pContext;
// FIXME investigate why these two cases are not handled symmetrically any more
switch (InterfaceToObjectID(thiz)) {
case SL_OBJECTID_AUDIOPLAYER:
result = android_audioPlayer_androidBufferQueue_registerCallback_l(
(CAudioPlayer*) thiz->mThis);
break;
case XA_OBJECTID_MEDIAPLAYER:
result = SL_RESULT_SUCCESS;
break;
default:
result = SL_RESULT_PARAMETER_INVALID;
}
} else {
result = SL_RESULT_PRECONDITIONS_VIOLATED;
}
interface_unlock_exclusive(thiz);
SL_LEAVE_INTERFACE
}
static SLresult IAndroidBufferQueue_Clear(SLAndroidBufferQueueItf self)
{
SL_ENTER_INTERFACE
result = SL_RESULT_SUCCESS;
IAndroidBufferQueue *thiz = (IAndroidBufferQueue *) self;
interface_lock_exclusive(thiz);
// reset the queue pointers
thiz->mFront = &thiz->mBufferArray[0];
thiz->mRear = &thiz->mBufferArray[0];
// reset the queue state
thiz->mState.count = 0;
thiz->mState.index = 0;
// reset the individual buffers
for (XAuint16 i=0 ; i<(thiz->mNumBuffers + 1) ; i++) {
thiz->mBufferArray[i].mDataBuffer = NULL;
thiz->mBufferArray[i].mDataSize = 0;
thiz->mBufferArray[i].mDataSizeConsumed = 0;
thiz->mBufferArray[i].mBufferContext = NULL;
thiz->mBufferArray[i].mBufferState = SL_ANDROIDBUFFERQUEUEEVENT_NONE;
switch (thiz->mBufferType) {
case kAndroidBufferTypeMpeg2Ts:
thiz->mBufferArray[i].mItems.mTsCmdData.mTsCmdCode = ANDROID_MP2TSEVENT_NONE;
thiz->mBufferArray[i].mItems.mTsCmdData.mPts = 0;
break;
case kAndroidBufferTypeAacadts:
thiz->mBufferArray[i].mItems.mAdtsCmdData.mAdtsCmdCode = ANDROID_ADTSEVENT_NONE;
break;
default:
result = SL_RESULT_CONTENT_UNSUPPORTED;
}
}
if (SL_RESULT_SUCCESS == result) {
// object-specific behavior for a clear
switch (InterfaceToObjectID(thiz)) {
case SL_OBJECTID_AUDIOPLAYER:
result = SL_RESULT_SUCCESS;
android_audioPlayer_androidBufferQueue_clear_l((CAudioPlayer*) thiz->mThis);
break;
case XA_OBJECTID_MEDIAPLAYER:
result = SL_RESULT_SUCCESS;
android_Player_androidBufferQueue_clear_l((CMediaPlayer*) thiz->mThis);
break;
default:
result = SL_RESULT_PARAMETER_INVALID;
}
}
interface_unlock_exclusive(thiz);
SL_LEAVE_INTERFACE
}
static SLresult IAndroidBufferQueue_Enqueue(SLAndroidBufferQueueItf self,
void *pBufferContext,
void *pData,
SLuint32 dataLength,
const SLAndroidBufferItem *pItems,
SLuint32 itemsLength)
{
SL_ENTER_INTERFACE
SL_LOGD("IAndroidBufferQueue_Enqueue pData=%p dataLength=%d", pData, dataLength);
if ((dataLength > 0) && (NULL == pData)) {
SL_LOGE("Enqueue failure: non-zero data length %u but NULL data pointer", dataLength);
result = SL_RESULT_PARAMETER_INVALID;
} else if ((itemsLength > 0) && (NULL == pItems)) {
SL_LOGE("Enqueue failure: non-zero items length %u but NULL items pointer", itemsLength);
result = SL_RESULT_PARAMETER_INVALID;
} else if ((0 == dataLength) && (0 == itemsLength)) {
// no data and no msg
SL_LOGE("Enqueue failure: trying to enqueue buffer with no data and no items.");
result = SL_RESULT_PARAMETER_INVALID;
// Note that a non-NULL data pointer with zero data length is allowed.
// We track that data pointer as it moves through the queue
// to assist the application in accounting for data buffers.
// A non-NULL items pointer with zero items length is also allowed, but has no value.
} else {
IAndroidBufferQueue *thiz = (IAndroidBufferQueue *) self;
// buffer size check, can be done outside of lock because buffer type can't change
switch (thiz->mBufferType) {
case kAndroidBufferTypeMpeg2Ts:
if (dataLength % MPEG2_TS_BLOCK_SIZE == 0) {
break;
}
SL_LOGE("Error enqueueing MPEG-2 TS data: size must be a multiple of %d (block size)",
MPEG2_TS_BLOCK_SIZE);
result = SL_RESULT_PARAMETER_INVALID;
SL_LEAVE_INTERFACE
break;
case kAndroidBufferTypeAacadts:
// non-zero dataLength is permitted in case of EOS command only
if (dataLength > 0) {
result = android::AacBqToPcmCbRenderer::validateBufferStartEndOnFrameBoundaries(
pData, dataLength);
if (SL_RESULT_SUCCESS != result) {
SL_LOGE("Error enqueueing ADTS data: data must start and end on frame "
"boundaries");
SL_LEAVE_INTERFACE
}
}
break;
case kAndroidBufferTypeInvalid:
default:
result = SL_RESULT_PARAMETER_INVALID;
SL_LEAVE_INTERFACE
}
interface_lock_exclusive(thiz);
AdvancedBufferHeader *oldRear = thiz->mRear, *newRear;
if ((newRear = oldRear + 1) == &thiz->mBufferArray[thiz->mNumBuffers + 1]) {
newRear = thiz->mBufferArray;
}
if (newRear == thiz->mFront) {
result = SL_RESULT_BUFFER_INSUFFICIENT;
} else {
oldRear->mDataBuffer = pData;
oldRear->mDataSize = dataLength;
oldRear->mDataSizeConsumed = 0;
oldRear->mBufferContext = pBufferContext;
oldRear->mBufferState = SL_ANDROIDBUFFERQUEUEEVENT_NONE;
thiz->mRear = newRear;
++thiz->mState.count;
// set oldRear->mItems based on items
setItems(pItems, itemsLength, thiz->mBufferType, oldRear);
result = SL_RESULT_SUCCESS;
}
// set enqueue attribute if state is PLAYING and the first buffer is enqueued
interface_unlock_exclusive_attributes(thiz, ((SL_RESULT_SUCCESS == result) &&
(1 == thiz->mState.count) && (SL_PLAYSTATE_PLAYING == getAssociatedState(thiz))) ?
ATTR_ABQ_ENQUEUE : ATTR_NONE);
}
SL_LEAVE_INTERFACE
}
static SLresult IAndroidBufferQueue_GetState(SLAndroidBufferQueueItf self,
SLAndroidBufferQueueState *pState)
{
SL_ENTER_INTERFACE
// Note that GetState while a Clear is pending is equivalent to GetState before the Clear
if (NULL == pState) {
result = SL_RESULT_PARAMETER_INVALID;
} else {
IAndroidBufferQueue *thiz = (IAndroidBufferQueue *) self;
interface_lock_shared(thiz);
pState->count = thiz->mState.count;
pState->index = thiz->mState.index;
interface_unlock_shared(thiz);
result = SL_RESULT_SUCCESS;
}
SL_LEAVE_INTERFACE
}
static SLresult IAndroidBufferQueue_SetCallbackEventsMask(SLAndroidBufferQueueItf self,
SLuint32 eventFlags)
{
SL_ENTER_INTERFACE
IAndroidBufferQueue *thiz = (IAndroidBufferQueue *) self;
interface_lock_exclusive(thiz);
// FIXME only supporting SL_ANDROIDBUFFERQUEUEEVENT_PROCESSED in this implementation
if ((SL_ANDROIDBUFFERQUEUEEVENT_PROCESSED == eventFlags) ||
(SL_ANDROIDBUFFERQUEUEEVENT_NONE == eventFlags)) {
thiz->mCallbackEventsMask = eventFlags;
result = SL_RESULT_SUCCESS;
} else {
result = SL_RESULT_FEATURE_UNSUPPORTED;
}
interface_unlock_exclusive(thiz);
SL_LEAVE_INTERFACE
}
static SLresult IAndroidBufferQueue_GetCallbackEventsMask(SLAndroidBufferQueueItf self,
SLuint32 *pEventFlags)
{
SL_ENTER_INTERFACE
if (NULL == pEventFlags) {
result = SL_RESULT_PARAMETER_INVALID;
} else {
IAndroidBufferQueue *thiz = (IAndroidBufferQueue *) self;
interface_lock_shared(thiz);
SLuint32 callbackEventsMask = thiz->mCallbackEventsMask;
interface_unlock_shared(thiz);
*pEventFlags = callbackEventsMask;
result = SL_RESULT_SUCCESS;
}
SL_LEAVE_INTERFACE
}
static const struct SLAndroidBufferQueueItf_ IAndroidBufferQueue_Itf = {
IAndroidBufferQueue_RegisterCallback,
IAndroidBufferQueue_Clear,
IAndroidBufferQueue_Enqueue,
IAndroidBufferQueue_GetState,
IAndroidBufferQueue_SetCallbackEventsMask,
IAndroidBufferQueue_GetCallbackEventsMask
};
void IAndroidBufferQueue_init(void *self)
{
IAndroidBufferQueue *thiz = (IAndroidBufferQueue *) self;
thiz->mItf = &IAndroidBufferQueue_Itf;
thiz->mState.count = 0;
thiz->mState.index = 0;
thiz->mCallback = NULL;
thiz->mContext = NULL;
thiz->mCallbackEventsMask = SL_ANDROIDBUFFERQUEUEEVENT_PROCESSED;
thiz->mBufferType = kAndroidBufferTypeInvalid;
thiz->mBufferArray = NULL;
thiz->mFront = NULL;
thiz->mRear = NULL;
}
void IAndroidBufferQueue_deinit(void *self)
{
IAndroidBufferQueue *thiz = (IAndroidBufferQueue *) self;
if (NULL != thiz->mBufferArray) {
free(thiz->mBufferArray);
thiz->mBufferArray = NULL;
}
}