| /* | |
| * 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. | |
| */ | |
| /* Play implementation */ | |
| #include "sles_allinclusive.h" | |
| static SLresult IPlay_SetPlayState(SLPlayItf self, SLuint32 state) | |
| { | |
| SL_ENTER_INTERFACE | |
| switch (state) { | |
| case SL_PLAYSTATE_STOPPED: | |
| case SL_PLAYSTATE_PAUSED: | |
| case SL_PLAYSTATE_PLAYING: | |
| { | |
| IPlay *thiz = (IPlay *) self; | |
| unsigned attr = ATTR_NONE; | |
| result = SL_RESULT_SUCCESS; | |
| #ifdef USE_OUTPUTMIXEXT | |
| CAudioPlayer *audioPlayer = (SL_OBJECTID_AUDIOPLAYER == InterfaceToObjectID(thiz)) ? | |
| (CAudioPlayer *) thiz->mThis : NULL; | |
| #endif | |
| interface_lock_exclusive(thiz); | |
| SLuint32 oldState = thiz->mState; | |
| if (state != oldState) { | |
| #ifdef USE_OUTPUTMIXEXT | |
| for (;; interface_cond_wait(thiz)) { | |
| // We are comparing the old state (left) vs. new state (right). | |
| // Note that the old state is 3 bits wide, but new state is only 2 bits wide. | |
| // That is why the old state is on the left and new state is on the right. | |
| switch ((oldState << 2) | state) { | |
| case (SL_PLAYSTATE_STOPPED << 2) | SL_PLAYSTATE_STOPPED: | |
| case (SL_PLAYSTATE_PAUSED << 2) | SL_PLAYSTATE_PAUSED: | |
| case (SL_PLAYSTATE_PLAYING << 2) | SL_PLAYSTATE_PLAYING: | |
| // no-op, and unreachable due to earlier "if (state != oldState)" | |
| break; | |
| case (SL_PLAYSTATE_STOPPED << 2) | SL_PLAYSTATE_PLAYING: | |
| case (SL_PLAYSTATE_PAUSED << 2) | SL_PLAYSTATE_PLAYING: | |
| attr = ATTR_PLAY_STATE; | |
| // set enqueue attribute if queue is non-empty and state becomes PLAYING | |
| if ((NULL != audioPlayer) && (audioPlayer->mBufferQueue.mFront != | |
| audioPlayer->mBufferQueue.mRear)) { | |
| // note that USE_OUTPUTMIXEXT does not support ATTR_ABQ_ENQUEUE | |
| attr |= ATTR_BQ_ENQUEUE; | |
| } | |
| FALLTHROUGH_INTENDED; | |
| case (SL_PLAYSTATE_STOPPED << 2) | SL_PLAYSTATE_PAUSED: | |
| case (SL_PLAYSTATE_PLAYING << 2) | SL_PLAYSTATE_PAUSED: | |
| // easy | |
| thiz->mState = state; | |
| break; | |
| case (SL_PLAYSTATE_STOPPING << 2) | SL_PLAYSTATE_STOPPED: | |
| // either spurious wakeup, or someone else requested same transition | |
| continue; | |
| case (SL_PLAYSTATE_STOPPING << 2) | SL_PLAYSTATE_PAUSED: | |
| case (SL_PLAYSTATE_STOPPING << 2) | SL_PLAYSTATE_PLAYING: | |
| // wait for someone else to finish their transition, then retry ours | |
| continue; | |
| case (SL_PLAYSTATE_PAUSED << 2) | SL_PLAYSTATE_STOPPED: | |
| case (SL_PLAYSTATE_PLAYING << 2) | SL_PLAYSTATE_STOPPED: | |
| // tell mixer to stop, then wait for mixer to acknowledge the request to stop | |
| thiz->mState = SL_PLAYSTATE_STOPPING; | |
| continue; | |
| default: | |
| // unexpected state | |
| assert(SL_BOOLEAN_FALSE); | |
| result = SL_RESULT_INTERNAL_ERROR; | |
| break; | |
| } | |
| break; | |
| } | |
| #else | |
| // Here life looks easy for an Android, but there are other troubles in play land | |
| thiz->mState = state; | |
| attr = ATTR_PLAY_STATE; | |
| // no need to set ATTR_BQ_ENQUEUE or ATTR_ABQ_ENQUEUE | |
| #endif | |
| } | |
| // SL_LOGD("set play state %d", state); | |
| interface_unlock_exclusive_attributes(thiz, attr); | |
| } | |
| break; | |
| default: | |
| result = SL_RESULT_PARAMETER_INVALID; | |
| break; | |
| } | |
| SL_LEAVE_INTERFACE | |
| } | |
| static SLresult IPlay_GetPlayState(SLPlayItf self, SLuint32 *pState) | |
| { | |
| SL_ENTER_INTERFACE | |
| if (NULL == pState) { | |
| result = SL_RESULT_PARAMETER_INVALID; | |
| } else { | |
| IPlay *thiz = (IPlay *) self; | |
| interface_lock_shared(thiz); | |
| SLuint32 state = thiz->mState; | |
| interface_unlock_shared(thiz); | |
| result = SL_RESULT_SUCCESS; | |
| #ifdef USE_OUTPUTMIXEXT | |
| switch (state) { | |
| case SL_PLAYSTATE_STOPPED: // as is | |
| case SL_PLAYSTATE_PAUSED: | |
| case SL_PLAYSTATE_PLAYING: | |
| break; | |
| case SL_PLAYSTATE_STOPPING: // these states require re-mapping | |
| state = SL_PLAYSTATE_STOPPED; | |
| break; | |
| default: // impossible | |
| assert(SL_BOOLEAN_FALSE); | |
| state = SL_PLAYSTATE_STOPPED; | |
| result = SL_RESULT_INTERNAL_ERROR; | |
| break; | |
| } | |
| #endif | |
| *pState = state; | |
| } | |
| SL_LEAVE_INTERFACE | |
| } | |
| static SLresult IPlay_GetDuration(SLPlayItf self, SLmillisecond *pMsec) | |
| { | |
| SL_ENTER_INTERFACE | |
| if (NULL == pMsec) { | |
| result = SL_RESULT_PARAMETER_INVALID; | |
| } else { | |
| result = SL_RESULT_SUCCESS; | |
| IPlay *thiz = (IPlay *) self; | |
| // even though this is a getter, it can modify state due to caching | |
| interface_lock_exclusive(thiz); | |
| SLmillisecond duration = thiz->mDuration; | |
| #ifdef ANDROID | |
| if (SL_TIME_UNKNOWN == duration) { | |
| SLmillisecond temp; | |
| switch (InterfaceToObjectID(thiz)) { | |
| case SL_OBJECTID_AUDIOPLAYER: | |
| result = android_audioPlayer_getDuration(thiz, &temp); | |
| break; | |
| case XA_OBJECTID_MEDIAPLAYER: | |
| result = android_Player_getDuration(thiz, &temp); | |
| break; | |
| default: | |
| result = SL_RESULT_FEATURE_UNSUPPORTED; | |
| break; | |
| } | |
| if (SL_RESULT_SUCCESS == result) { | |
| duration = temp; | |
| thiz->mDuration = duration; | |
| } | |
| } | |
| #else | |
| // will be set by containing AudioPlayer or MidiPlayer object at Realize, if known, | |
| // otherwise the duration will be updated each time a new maximum position is detected | |
| #endif | |
| interface_unlock_exclusive(thiz); | |
| *pMsec = duration; | |
| } | |
| SL_LEAVE_INTERFACE | |
| } | |
| static SLresult IPlay_GetPosition(SLPlayItf self, SLmillisecond *pMsec) | |
| { | |
| SL_ENTER_INTERFACE | |
| if (NULL == pMsec) { | |
| result = SL_RESULT_PARAMETER_INVALID; | |
| } else { | |
| IPlay *thiz = (IPlay *) self; | |
| SLmillisecond position; | |
| interface_lock_shared(thiz); | |
| #ifdef ANDROID | |
| // Android does not use the mPosition field for audio and media players | |
| // and doesn't cache the position | |
| switch (IObjectToObjectID((thiz)->mThis)) { | |
| case SL_OBJECTID_AUDIOPLAYER: | |
| android_audioPlayer_getPosition(thiz, &position); | |
| break; | |
| case XA_OBJECTID_MEDIAPLAYER: | |
| android_Player_getPosition(thiz, &position); | |
| break; | |
| default: | |
| // we shouldn'be here | |
| assert(SL_BOOLEAN_FALSE); | |
| } | |
| #else | |
| // on other platforms we depend on periodic updates to the current position | |
| position = thiz->mPosition; | |
| // if a seek is pending, then lie about current position so the seek appears synchronous | |
| if (SL_OBJECTID_AUDIOPLAYER == InterfaceToObjectID(thiz)) { | |
| CAudioPlayer *audioPlayer = (CAudioPlayer *) thiz->mThis; | |
| SLmillisecond pos = audioPlayer->mSeek.mPos; | |
| if (SL_TIME_UNKNOWN != pos) { | |
| position = pos; | |
| } | |
| } | |
| #endif | |
| interface_unlock_shared(thiz); | |
| *pMsec = position; | |
| result = SL_RESULT_SUCCESS; | |
| } | |
| SL_LEAVE_INTERFACE | |
| } | |
| static SLresult IPlay_RegisterCallback(SLPlayItf self, slPlayCallback callback, void *pContext) | |
| { | |
| SL_ENTER_INTERFACE | |
| IPlay *thiz = (IPlay *) self; | |
| interface_lock_exclusive(thiz); | |
| thiz->mCallback = callback; | |
| thiz->mContext = pContext; | |
| // omits _attributes b/c noone cares deeply enough about these fields to need quick notification | |
| interface_unlock_exclusive(thiz); | |
| result = SL_RESULT_SUCCESS; | |
| SL_LEAVE_INTERFACE | |
| } | |
| static SLresult IPlay_SetCallbackEventsMask(SLPlayItf self, SLuint32 eventFlags) | |
| { | |
| SL_ENTER_INTERFACE | |
| if (eventFlags & ~(SL_PLAYEVENT_HEADATEND | SL_PLAYEVENT_HEADATMARKER | | |
| SL_PLAYEVENT_HEADATNEWPOS | SL_PLAYEVENT_HEADMOVING | SL_PLAYEVENT_HEADSTALLED)) { | |
| result = SL_RESULT_PARAMETER_INVALID; | |
| } else { | |
| IPlay *thiz = (IPlay *) self; | |
| interface_lock_exclusive(thiz); | |
| if (thiz->mEventFlags != eventFlags) { | |
| #ifdef USE_OUTPUTMIXEXT | |
| // enabling the "head at new position" play event will postpone the next update event | |
| if (!(thiz->mEventFlags & SL_PLAYEVENT_HEADATNEWPOS) && | |
| (eventFlags & SL_PLAYEVENT_HEADATNEWPOS)) { | |
| thiz->mFramesSincePositionUpdate = 0; | |
| } | |
| #endif | |
| thiz->mEventFlags = eventFlags; | |
| interface_unlock_exclusive_attributes(thiz, ATTR_TRANSPORT); | |
| } else { | |
| interface_unlock_exclusive(thiz); | |
| } | |
| result = SL_RESULT_SUCCESS; | |
| } | |
| SL_LEAVE_INTERFACE | |
| } | |
| static SLresult IPlay_GetCallbackEventsMask(SLPlayItf self, SLuint32 *pEventFlags) | |
| { | |
| SL_ENTER_INTERFACE | |
| if (NULL == pEventFlags) { | |
| result = SL_RESULT_PARAMETER_INVALID; | |
| } else { | |
| IPlay *thiz = (IPlay *) self; | |
| interface_lock_shared(thiz); | |
| SLuint32 eventFlags = thiz->mEventFlags; | |
| interface_unlock_shared(thiz); | |
| *pEventFlags = eventFlags; | |
| result = SL_RESULT_SUCCESS; | |
| } | |
| SL_LEAVE_INTERFACE | |
| } | |
| static SLresult IPlay_SetMarkerPosition(SLPlayItf self, SLmillisecond mSec) | |
| { | |
| SL_ENTER_INTERFACE | |
| if (SL_TIME_UNKNOWN == mSec) { | |
| result = SL_RESULT_PARAMETER_INVALID; | |
| } else { | |
| IPlay *thiz = (IPlay *) self; | |
| bool significant = false; | |
| interface_lock_exclusive(thiz); | |
| if (thiz->mMarkerPosition != mSec) { | |
| thiz->mMarkerPosition = mSec; | |
| if (thiz->mEventFlags & SL_PLAYEVENT_HEADATMARKER) { | |
| significant = true; | |
| } | |
| } | |
| if (significant) { | |
| interface_unlock_exclusive_attributes(thiz, ATTR_TRANSPORT); | |
| } else { | |
| interface_unlock_exclusive(thiz); | |
| } | |
| result = SL_RESULT_SUCCESS; | |
| } | |
| SL_LEAVE_INTERFACE | |
| } | |
| static SLresult IPlay_ClearMarkerPosition(SLPlayItf self) | |
| { | |
| SL_ENTER_INTERFACE | |
| IPlay *thiz = (IPlay *) self; | |
| bool significant = false; | |
| interface_lock_exclusive(thiz); | |
| // clearing the marker position is equivalent to setting the marker to SL_TIME_UNKNOWN | |
| if (thiz->mMarkerPosition != SL_TIME_UNKNOWN) { | |
| thiz->mMarkerPosition = SL_TIME_UNKNOWN; | |
| if (thiz->mEventFlags & SL_PLAYEVENT_HEADATMARKER) { | |
| significant = true; | |
| } | |
| } | |
| if (significant) { | |
| interface_unlock_exclusive_attributes(thiz, ATTR_TRANSPORT); | |
| } else { | |
| interface_unlock_exclusive(thiz); | |
| } | |
| result = SL_RESULT_SUCCESS; | |
| SL_LEAVE_INTERFACE | |
| } | |
| static SLresult IPlay_GetMarkerPosition(SLPlayItf self, SLmillisecond *pMsec) | |
| { | |
| SL_ENTER_INTERFACE | |
| if (NULL == pMsec) { | |
| result = SL_RESULT_PARAMETER_INVALID; | |
| } else { | |
| IPlay *thiz = (IPlay *) self; | |
| interface_lock_shared(thiz); | |
| SLmillisecond markerPosition = thiz->mMarkerPosition; | |
| interface_unlock_shared(thiz); | |
| *pMsec = markerPosition; | |
| if (SL_TIME_UNKNOWN == markerPosition) { | |
| result = SL_RESULT_PRECONDITIONS_VIOLATED; | |
| } else { | |
| result = SL_RESULT_SUCCESS; | |
| } | |
| } | |
| SL_LEAVE_INTERFACE | |
| } | |
| static SLresult IPlay_SetPositionUpdatePeriod(SLPlayItf self, SLmillisecond mSec) | |
| { | |
| SL_ENTER_INTERFACE | |
| if (0 == mSec) { | |
| result = SL_RESULT_PARAMETER_INVALID; | |
| } else { | |
| IPlay *thiz = (IPlay *) self; | |
| bool significant = false; | |
| interface_lock_exclusive(thiz); | |
| if (thiz->mPositionUpdatePeriod != mSec) { | |
| thiz->mPositionUpdatePeriod = mSec; | |
| #ifdef USE_OUTPUTMIXEXT | |
| if (SL_OBJECTID_AUDIOPLAYER == InterfaceToObjectID(thiz)) { | |
| CAudioPlayer *audioPlayer = (CAudioPlayer *) thiz->mThis; | |
| SLuint32 frameUpdatePeriod = ((long long) mSec * | |
| (long long) audioPlayer->mSampleRateMilliHz) / 1000000LL; | |
| if (0 == frameUpdatePeriod) { | |
| frameUpdatePeriod = ~0; | |
| } | |
| thiz->mFrameUpdatePeriod = frameUpdatePeriod; | |
| // setting a new update period postpones the next callback | |
| thiz->mFramesSincePositionUpdate = 0; | |
| } | |
| #endif | |
| if (thiz->mEventFlags & SL_PLAYEVENT_HEADATNEWPOS) { | |
| significant = true; | |
| } | |
| } | |
| if (significant) { | |
| interface_unlock_exclusive_attributes(thiz, ATTR_TRANSPORT); | |
| } else { | |
| interface_unlock_exclusive(thiz); | |
| } | |
| result = SL_RESULT_SUCCESS; | |
| } | |
| SL_LEAVE_INTERFACE | |
| } | |
| static SLresult IPlay_GetPositionUpdatePeriod(SLPlayItf self, SLmillisecond *pMsec) | |
| { | |
| SL_ENTER_INTERFACE | |
| if (NULL == pMsec) { | |
| result = SL_RESULT_PARAMETER_INVALID; | |
| } else { | |
| IPlay *thiz = (IPlay *) self; | |
| interface_lock_shared(thiz); | |
| SLmillisecond positionUpdatePeriod = thiz->mPositionUpdatePeriod; | |
| interface_unlock_shared(thiz); | |
| *pMsec = positionUpdatePeriod; | |
| result = SL_RESULT_SUCCESS; | |
| } | |
| SL_LEAVE_INTERFACE | |
| } | |
| static const struct SLPlayItf_ IPlay_Itf = { | |
| IPlay_SetPlayState, | |
| IPlay_GetPlayState, | |
| IPlay_GetDuration, | |
| IPlay_GetPosition, | |
| IPlay_RegisterCallback, | |
| IPlay_SetCallbackEventsMask, | |
| IPlay_GetCallbackEventsMask, | |
| IPlay_SetMarkerPosition, | |
| IPlay_ClearMarkerPosition, | |
| IPlay_GetMarkerPosition, | |
| IPlay_SetPositionUpdatePeriod, | |
| IPlay_GetPositionUpdatePeriod | |
| }; | |
| void IPlay_init(void *self) | |
| { | |
| IPlay *thiz = (IPlay *) self; | |
| thiz->mItf = &IPlay_Itf; | |
| thiz->mState = SL_PLAYSTATE_STOPPED; | |
| thiz->mDuration = SL_TIME_UNKNOWN; // will be set by containing player object | |
| thiz->mPosition = (SLmillisecond) 0; | |
| thiz->mCallback = NULL; | |
| thiz->mContext = NULL; | |
| thiz->mEventFlags = 0; | |
| thiz->mMarkerPosition = SL_TIME_UNKNOWN; | |
| thiz->mPositionUpdatePeriod = 1000; // per spec | |
| #ifdef USE_OUTPUTMIXEXT | |
| thiz->mFrameUpdatePeriod = 0; // because we don't know the sample rate yet | |
| thiz->mLastSeekPosition = 0; | |
| thiz->mFramesSinceLastSeek = 0; | |
| thiz->mFramesSincePositionUpdate = 0; | |
| #endif | |
| } |