/* * 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 media player 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 SLresult setItems(SLuint32 dataLength, const SLAndroidBufferItem *pItems, SLuint32 itemsLength, SLuint16 bufferType, AdvancedBufferHeader *pBuff, bool *pEOS) { // 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 SL_RESULT_INTERNAL_ERROR; } // 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"); return SL_RESULT_PARAMETER_INVALID; } 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"); return SL_RESULT_PARAMETER_INVALID; } 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", pItems->itemSize); return SL_RESULT_PARAMETER_INVALID; } 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", pItems->itemSize); return SL_RESULT_PARAMETER_INVALID; } 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", pItems->itemKey, pItems->itemSize); return SL_RESULT_PARAMETER_INVALID; }// 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", pItems->itemSize); return SL_RESULT_PARAMETER_INVALID; } break; default: // unknown item key SL_LOGE("Unknown item key %u with size %u", pItems->itemKey, pItems->itemSize); return SL_RESULT_PARAMETER_INVALID; }// switch (pItems->itemKey) } break; case kAndroidBufferTypeInvalid: default: // not reachable as we checked this earlier return SL_RESULT_INTERNAL_ERROR; }// 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_EOS: if (dataLength > 0) { SL_LOGE("Can't enqueue non-zero data with EOS"); return SL_RESULT_PRECONDITIONS_VIOLATED; } *pEOS = true; break; case ANDROID_MP2TSEVENT_NONE: 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"); return SL_RESULT_PARAMETER_INVALID; } } break; case kAndroidBufferTypeAacadts: { // only one item supported, and thus no combination check needed if (pBuff->mItems.mAdtsCmdData.mAdtsCmdCode == ANDROID_ADTSEVENT_EOS) { if (dataLength > 0) { SL_LOGE("Can't enqueue non-zero data with EOS"); return SL_RESULT_PRECONDITIONS_VIOLATED; } *pEOS = true; } } break; case kAndroidBufferTypeInvalid: default: // not reachable as we checked this earlier return SL_RESULT_INTERNAL_ERROR; } return SL_RESULT_SUCCESS; } 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; result = SL_RESULT_SUCCESS; } 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; // object-specific behavior for a clear switch (InterfaceToObjectID(thiz)) { case SL_OBJECTID_AUDIOPLAYER: android_audioPlayer_androidBufferQueue_clear_l((CAudioPlayer*) thiz->mThis); break; case XA_OBJECTID_MEDIAPLAYER: 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_PACKET_SIZE == 0) { // The downstream Stagefright MPEG-2 TS parser is sensitive to format errors, // so do a quick sanity check beforehand on the first packet of the buffer. // We don't check all the packets to avoid thrashing the data cache. if ((dataLength > 0) && (*(SLuint8 *)pData != MPEG2_TS_PACKET_SYNC)) { SL_LOGE("Error enqueueing MPEG-2 TS data: incorrect packet sync"); result = SL_RESULT_CONTENT_CORRUPTED; SL_LEAVE_INTERFACE } break; } SL_LOGE("Error enqueueing MPEG-2 TS data: size must be a multiple of %d (packet size)", MPEG2_TS_PACKET_SIZE); result = SL_RESULT_PARAMETER_INVALID; SL_LEAVE_INTERFACE break; case kAndroidBufferTypeAacadts: // 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 (thiz->mEOS) { SL_LOGE("Can't enqueue after EOS"); result = SL_RESULT_PRECONDITIONS_VIOLATED; } else if (newRear == thiz->mFront) { result = SL_RESULT_BUFFER_INSUFFICIENT; } else { // set oldRear->mItems based on items result = setItems(dataLength, pItems, itemsLength, thiz->mBufferType, oldRear, &thiz->mEOS); if (SL_RESULT_SUCCESS == result) { oldRear->mDataBuffer = pData; oldRear->mDataSize = dataLength; oldRear->mDataSizeConsumed = 0; oldRear->mBufferContext = pBufferContext; //oldRear->mBufferState = TBD; thiz->mRear = newRear; ++thiz->mState.count; } } // 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 /* | others TBD */ ) & 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; thiz->mEOS = false; } void IAndroidBufferQueue_deinit(void *self) { IAndroidBufferQueue *thiz = (IAndroidBufferQueue *) self; if (NULL != thiz->mBufferArray) { free(thiz->mBufferArray); thiz->mBufferArray = NULL; } } #if 0 // Dump the contents of an IAndroidBufferQueue to the log. This is for debugging only, // and is not a documented API. The associated object is locked throughout for atomicity, // but the log entries may be interspersed with unrelated logs. void IAndroidBufferQueue_log(IAndroidBufferQueue *thiz) { interface_lock_shared(thiz); SL_LOGI("IAndroidBufferQueue %p:", thiz); SL_LOGI(" mState.count=%u mState.index=%u mCallback=%p mContext=%p", thiz->mState.count, thiz->mState.index, thiz->mCallback, thiz->mContext); const char *bufferTypeString; switch (thiz->mBufferType) { case kAndroidBufferTypeInvalid: bufferTypeString = "kAndroidBufferTypeInvalid"; break; case kAndroidBufferTypeMpeg2Ts: bufferTypeString = "kAndroidBufferTypeMpeg2Ts"; break; case kAndroidBufferTypeAacadts: bufferTypeString = "kAndroidBufferTypeAacadts"; break; default: bufferTypeString = "unknown"; break; } SL_LOGI(" mCallbackEventsMask=0x%x, mBufferType=0x%x (%s), mEOS=%s", thiz->mCallbackEventsMask, thiz->mBufferType, bufferTypeString, thiz->mEOS ? "true" : "false"); SL_LOGI(" mBufferArray=%p, mFront=%p (%u), mRear=%p (%u)", thiz->mBufferArray, thiz->mFront, thiz->mFront - thiz->mBufferArray, thiz->mRear, thiz->mRear - thiz->mBufferArray); SL_LOGI(" index mDataBuffer mDataSize mDataSizeConsumed mBufferContext mItems"); const AdvancedBufferHeader *hdr; for (hdr = thiz->mFront; hdr != thiz->mRear; ) { SLuint32 i = hdr - thiz->mBufferArray; char itemString[32]; switch (thiz->mBufferType) { case kAndroidBufferTypeMpeg2Ts: switch (hdr->mItems.mTsCmdData.mTsCmdCode) { case ANDROID_MP2TSEVENT_NONE: strcpy(itemString, "NONE"); break; case ANDROID_MP2TSEVENT_EOS: strcpy(itemString, "EOS"); break; case ANDROID_MP2TSEVENT_DISCONTINUITY: strcpy(itemString, "DISCONTINUITY"); break; case ANDROID_MP2TSEVENT_DISCON_NEWPTS: snprintf(itemString, sizeof(itemString), "NEWPTS %llu", hdr->mItems.mTsCmdData.mPts); break; case ANDROID_MP2TSEVENT_FORMAT_CHANGE: strcpy(itemString, "FORMAT_CHANGE"); break; default: snprintf(itemString, sizeof(itemString), "0x%x", hdr->mItems.mTsCmdData.mTsCmdCode); break; } break; case kAndroidBufferTypeAacadts: switch (hdr->mItems.mAdtsCmdData.mAdtsCmdCode) { case ANDROID_ADTSEVENT_NONE: strcpy(itemString, "NONE"); break; case ANDROID_ADTSEVENT_EOS: strcpy(itemString, "EOS"); break; default: snprintf(itemString, sizeof(itemString), "0x%x", hdr->mItems.mAdtsCmdData.mAdtsCmdCode); break; } break; default: strcpy(itemString, ""); break; } SL_LOGI(" %5u %11p %9u %17u %14p %s", i, hdr->mDataBuffer, hdr->mDataSize, hdr->mDataSizeConsumed, hdr->mBufferContext, itemString); // mBufferState if (++hdr == &thiz->mBufferArray[thiz->mNumBuffers + 1]) { hdr = thiz->mBufferArray; } } interface_unlock_shared(thiz); } #endif