/* * 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 *this = (IPlay *) self; unsigned attr = ATTR_NONE; result = SL_RESULT_SUCCESS; CAudioPlayer *audioPlayer = (SL_OBJECTID_AUDIOPLAYER == InterfaceToObjectID(this)) ? (CAudioPlayer *) this->mThis : NULL; interface_lock_exclusive(this); #ifdef USE_OUTPUTMIXEXT for (;; interface_cond_wait(this)) { // 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 ((this->mState << 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 break; case (SL_PLAYSTATE_STOPPED << 2) | SL_PLAYSTATE_PLAYING: case (SL_PLAYSTATE_PAUSED << 2) | SL_PLAYSTATE_PLAYING: attr = ATTR_TRANSPORT; // set enqueue attribute if queue is non-empty and state becomes PLAYING if ((NULL != audioPlayer) && (audioPlayer->mBufferQueue.mFront != audioPlayer->mBufferQueue.mRear)) { attr |= ATTR_ENQUEUE; } // fall through case (SL_PLAYSTATE_STOPPED << 2) | SL_PLAYSTATE_PAUSED: case (SL_PLAYSTATE_PLAYING << 2) | SL_PLAYSTATE_PAUSED: // easy this->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 other guy to finish his 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 this->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 this->mState = state; attr = ATTR_TRANSPORT; #endif interface_unlock_exclusive_attributes(this, 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 *this = (IPlay *) self; interface_lock_peek(this); SLuint32 state = this->mState; interface_unlock_peek(this); 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 *this = (IPlay *) self; // even though this is a getter, it can modify state due to caching interface_lock_exclusive(this); SLmillisecond duration = this->mDuration; #ifdef ANDROID if ((SL_TIME_UNKNOWN == duration) && (SL_OBJECTID_AUDIOPLAYER == InterfaceToObjectID(this))) { SLmillisecond temp; result = android_audioPlayer_getDuration(this, &temp); if (SL_RESULT_SUCCESS == result) { duration = temp; this->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(this); *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 *this = (IPlay *) self; SLmillisecond position; interface_lock_shared(this); #ifdef ANDROID // Android does not use the mPosition field for audio players if (SL_OBJECTID_AUDIOPLAYER == InterfaceToObjectID(this)) { android_audioPlayer_getPosition(this, &position); // note that we do not cache the position } else { position = this->mPosition; } #else // on other platforms we depend on periodic updates to the current position position = this->mPosition; // if a seek is pending, then lie about current position so the seek appears synchronous if (SL_OBJECTID_AUDIOPLAYER == InterfaceToObjectID(this)) { CAudioPlayer *audioPlayer = (CAudioPlayer *) this->mThis; SLmillisecond pos = audioPlayer->mSeek.mPos; if (SL_TIME_UNKNOWN != pos) { position = pos; } } #endif interface_unlock_shared(this); *pMsec = position; result = SL_RESULT_SUCCESS; } SL_LEAVE_INTERFACE } static SLresult IPlay_RegisterCallback(SLPlayItf self, slPlayCallback callback, void *pContext) { SL_ENTER_INTERFACE IPlay *this = (IPlay *) self; interface_lock_exclusive(this); this->mCallback = callback; this->mContext = pContext; // omits _attributes b/c noone cares deeply enough about these fields to need quick notification interface_unlock_exclusive(this); 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 *this = (IPlay *) self; interface_lock_exclusive(this); if (this->mEventFlags != eventFlags) { #ifdef USE_OUTPUTMIXEXT // enabling the "head at new position" play event will postpone the next update event if (!(this->mEventFlags & SL_PLAYEVENT_HEADATNEWPOS) && (eventFlags & SL_PLAYEVENT_HEADATNEWPOS)) { this->mFramesSincePositionUpdate = 0; } #endif this->mEventFlags = eventFlags; interface_unlock_exclusive_attributes(this, ATTR_TRANSPORT); } else interface_unlock_exclusive(this); 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 *this = (IPlay *) self; interface_lock_peek(this); SLuint32 eventFlags = this->mEventFlags; interface_unlock_peek(this); *pEventFlags = eventFlags; result = SL_RESULT_SUCCESS; } SL_LEAVE_INTERFACE } static SLresult IPlay_SetMarkerPosition(SLPlayItf self, SLmillisecond mSec) { SL_ENTER_INTERFACE IPlay *this = (IPlay *) self; interface_lock_exclusive(this); if (this->mMarkerPosition != mSec) { this->mMarkerPosition = mSec; interface_unlock_exclusive_attributes(this, ATTR_TRANSPORT); } else interface_unlock_exclusive(this); result = SL_RESULT_SUCCESS; SL_LEAVE_INTERFACE } static SLresult IPlay_ClearMarkerPosition(SLPlayItf self) { SL_ENTER_INTERFACE IPlay *this = (IPlay *) self; interface_lock_exclusive(this); #ifdef ANDROID if (SL_OBJECTID_AUDIOPLAYER == InterfaceToObjectID(this)) { // clearing the marker position is equivalent to setting the marker at 0 this->mMarkerPosition = 0; } #endif interface_unlock_exclusive_attributes(this, ATTR_TRANSPORT); 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 *this = (IPlay *) self; interface_lock_peek(this); SLmillisecond markerPosition = this->mMarkerPosition; interface_unlock_peek(this); *pMsec = markerPosition; 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 *this = (IPlay *) self; interface_lock_exclusive(this); if (this->mPositionUpdatePeriod != mSec) { this->mPositionUpdatePeriod = mSec; #ifdef ANDROID if (SL_OBJECTID_AUDIOPLAYER == InterfaceToObjectID(this)) { // result = android_audioPlayer_useEventMask(this, this->mEventFlags); } #endif #ifdef USE_OUTPUTMIXEXT if (SL_OBJECTID_AUDIOPLAYER == InterfaceToObjectID(this)) { CAudioPlayer *audioPlayer = (CAudioPlayer *) this->mThis; SLuint32 frameUpdatePeriod = ((long long) mSec * (long long) audioPlayer->mSampleRateMilliHz) / 1000000LL; if (0 == frameUpdatePeriod) frameUpdatePeriod = ~0; this->mFrameUpdatePeriod = frameUpdatePeriod; // setting a new update period postpones the next callback this->mFramesSincePositionUpdate = 0; } #endif interface_unlock_exclusive_attributes(this, ATTR_TRANSPORT); } else interface_unlock_exclusive(this); 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 *this = (IPlay *) self; interface_lock_peek(this); SLmillisecond positionUpdatePeriod = this->mPositionUpdatePeriod; interface_unlock_peek(this); *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 *this = (IPlay *) self; this->mItf = &IPlay_Itf; this->mState = SL_PLAYSTATE_STOPPED; this->mDuration = SL_TIME_UNKNOWN; // will be set by containing player object this->mPosition = (SLmillisecond) 0; this->mCallback = NULL; this->mContext = NULL; this->mEventFlags = 0; this->mMarkerPosition = 0; this->mPositionUpdatePeriod = 1000; #ifdef USE_OUTPUTMIXEXT this->mFrameUpdatePeriod = 0; // because we don't know the sample rate yet this->mLastSeekPosition = 0; this->mFramesSinceLastSeek = 0; this->mFramesSincePositionUpdate = 0; #endif }