/* * 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. */ /** \brief libsndfile integration */ #include "sles_allinclusive.h" /** \brief Called by SndFile.c:audioPlayerTransportUpdate after a play state change or seek, * and by IOutputMixExt::FillBuffer after each buffer is consumed. */ void SndFile_Callback(SLBufferQueueItf caller, void *pContext) { CAudioPlayer *thisAP = (CAudioPlayer *) pContext; object_lock_peek(&thisAP->mObject); SLuint32 state = thisAP->mPlay.mState; object_unlock_peek(&thisAP->mObject); if (SL_PLAYSTATE_PLAYING != state) { return; } struct SndFile *this = &thisAP->mSndFile; SLresult result; pthread_mutex_lock(&this->mMutex); if (this->mEOF) { pthread_mutex_unlock(&this->mMutex); return; } short *pBuffer = &this->mBuffer[this->mWhich * SndFile_BUFSIZE]; if (++this->mWhich >= SndFile_NUMBUFS) { this->mWhich = 0; } sf_count_t count; count = sf_read_short(this->mSNDFILE, pBuffer, (sf_count_t) SndFile_BUFSIZE); pthread_mutex_unlock(&this->mMutex); bool headAtNewPos = false; object_lock_exclusive(&thisAP->mObject); slPlayCallback callback = thisAP->mPlay.mCallback; void *context = thisAP->mPlay.mContext; // make a copy of sample rate so we are absolutely sure we will not divide by zero SLuint32 sampleRateMilliHz = thisAP->mSampleRateMilliHz; if (0 != sampleRateMilliHz) { // this will overflow after 49 days, but no fix possible as it's part of the API thisAP->mPlay.mPosition = (SLuint32) (((long long) thisAP->mPlay.mFramesSinceLastSeek * 1000000LL) / sampleRateMilliHz) + thisAP->mPlay.mLastSeekPosition; // make a good faith effort for the mean time between "head at new position" callbacks to // occur at the requested update period, but there will be jitter SLuint32 frameUpdatePeriod = thisAP->mPlay.mFrameUpdatePeriod; if ((0 != frameUpdatePeriod) && (thisAP->mPlay.mFramesSincePositionUpdate >= frameUpdatePeriod) && (SL_PLAYEVENT_HEADATNEWPOS & thisAP->mPlay.mEventFlags)) { // if we overrun a requested update period, then reset the clock modulo the // update period so that it appears to the application as one or more lost callbacks, // but no additional jitter if ((thisAP->mPlay.mFramesSincePositionUpdate -= thisAP->mPlay.mFrameUpdatePeriod) >= frameUpdatePeriod) { thisAP->mPlay.mFramesSincePositionUpdate %= frameUpdatePeriod; } headAtNewPos = true; } } if (0 < count) { object_unlock_exclusive(&thisAP->mObject); SLuint32 size = (SLuint32) (count * sizeof(short)); result = IBufferQueue_Enqueue(caller, pBuffer, size); // not much we can do if the Enqueue fails, so we'll just drop the decoded data if (SL_RESULT_SUCCESS != result) { SL_LOGE("enqueue failed 0x%lx", result); } } else { thisAP->mPlay.mState = SL_PLAYSTATE_PAUSED; this->mEOF = SL_BOOLEAN_TRUE; // this would result in a non-monotonically increasing position, so don't do it // thisAP->mPlay.mPosition = thisAP->mPlay.mDuration; object_unlock_exclusive_attributes(&thisAP->mObject, ATTR_TRANSPORT); } // callbacks are called with mutex unlocked if (NULL != callback) { if (headAtNewPos) { (*callback)(&thisAP->mPlay.mItf, context, SL_PLAYEVENT_HEADATNEWPOS); } } } /** \brief Check whether the supplied libsndfile format is supported by us */ SLboolean SndFile_IsSupported(const SF_INFO *sfinfo) { switch (sfinfo->format & SF_FORMAT_TYPEMASK) { case SF_FORMAT_WAV: break; default: return SL_BOOLEAN_FALSE; } switch (sfinfo->format & SF_FORMAT_SUBMASK) { case SF_FORMAT_PCM_U8: case SF_FORMAT_PCM_16: break; default: return SL_BOOLEAN_FALSE; } switch (sfinfo->samplerate) { case 11025: case 22050: case 44100: break; default: return SL_BOOLEAN_FALSE; } switch (sfinfo->channels) { case 1: case 2: break; default: return SL_BOOLEAN_FALSE; } return SL_BOOLEAN_TRUE; } /** \brief Check whether the partially-constructed AudioPlayer is compatible with libsndfile */ SLresult SndFile_checkAudioPlayerSourceSink(CAudioPlayer *this) { const SLDataSource *pAudioSrc = &this->mDataSource.u.mSource; SLuint32 locatorType = *(SLuint32 *)pAudioSrc->pLocator; SLuint32 formatType = *(SLuint32 *)pAudioSrc->pFormat; switch (locatorType) { case SL_DATALOCATOR_BUFFERQUEUE: #ifdef ANDROID case SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE: #endif break; case SL_DATALOCATOR_URI: { SLDataLocator_URI *dl_uri = (SLDataLocator_URI *) pAudioSrc->pLocator; SLchar *uri = dl_uri->URI; if (NULL == uri) { return SL_RESULT_PARAMETER_INVALID; } if (!strncmp((const char *) uri, "file:///", 8)) { uri += 8; } switch (formatType) { case SL_DATAFORMAT_NULL: // OK to omit the data format case SL_DATAFORMAT_MIME: // we ignore a MIME type if specified break; default: return SL_RESULT_CONTENT_UNSUPPORTED; } this->mSndFile.mPathname = uri; this->mBufferQueue.mNumBuffers = SndFile_NUMBUFS; } break; default: return SL_RESULT_CONTENT_UNSUPPORTED; } this->mSndFile.mWhich = 0; this->mSndFile.mSNDFILE = NULL; // this->mSndFile.mMutex is initialized only when there is a valid mSNDFILE this->mSndFile.mEOF = SL_BOOLEAN_FALSE; return SL_RESULT_SUCCESS; } /** \brief Called with mutex unlocked for marker and position updates, and play state change */ void audioPlayerTransportUpdate(CAudioPlayer *audioPlayer) { if (NULL != audioPlayer->mSndFile.mSNDFILE) { object_lock_exclusive(&audioPlayer->mObject); SLboolean empty = 0 == audioPlayer->mBufferQueue.mState.count; // FIXME a made-up number that should depend on player state and prefetch status audioPlayer->mPrefetchStatus.mLevel = 1000; SLmillisecond pos = audioPlayer->mSeek.mPos; if (SL_TIME_UNKNOWN != pos) { audioPlayer->mSeek.mPos = SL_TIME_UNKNOWN; // trim seek position to the current known duration if (pos > audioPlayer->mPlay.mDuration) { pos = audioPlayer->mPlay.mDuration; } audioPlayer->mPlay.mLastSeekPosition = pos; audioPlayer->mPlay.mFramesSinceLastSeek = 0; // seek postpones the next head at new position callback audioPlayer->mPlay.mFramesSincePositionUpdate = 0; } object_unlock_exclusive(&audioPlayer->mObject); if (SL_TIME_UNKNOWN != pos) { // discard any enqueued buffers for the old position IBufferQueue_Clear(&audioPlayer->mBufferQueue.mItf); empty = SL_BOOLEAN_TRUE; pthread_mutex_lock(&audioPlayer->mSndFile.mMutex); // FIXME why void? (void) sf_seek(audioPlayer->mSndFile.mSNDFILE, (sf_count_t) (((long long) pos * audioPlayer->mSndFile.mSfInfo.samplerate) / 1000LL), SEEK_SET); audioPlayer->mSndFile.mEOF = SL_BOOLEAN_FALSE; audioPlayer->mSndFile.mWhich = 0; pthread_mutex_unlock(&audioPlayer->mSndFile.mMutex); } // FIXME only on seek or play state change (STOPPED, PAUSED) -> PLAYING if (empty) { SndFile_Callback(&audioPlayer->mBufferQueue.mItf, audioPlayer); } } } /** \brief Called by CAudioPlayer_Realize */ SLresult SndFile_Realize(CAudioPlayer *this) { SLresult result = SL_RESULT_SUCCESS; if (NULL != this->mSndFile.mPathname) { this->mSndFile.mSfInfo.format = 0; this->mSndFile.mSNDFILE = sf_open( (const char *) this->mSndFile.mPathname, SFM_READ, &this->mSndFile.mSfInfo); if (NULL == this->mSndFile.mSNDFILE) { result = SL_RESULT_CONTENT_NOT_FOUND; } else if (!SndFile_IsSupported(&this->mSndFile.mSfInfo)) { sf_close(this->mSndFile.mSNDFILE); this->mSndFile.mSNDFILE = NULL; result = SL_RESULT_CONTENT_UNSUPPORTED; } else { int ok; ok = pthread_mutex_init(&this->mSndFile.mMutex, (const pthread_mutexattr_t *) NULL); assert(0 == ok); SLBufferQueueItf bufferQueue = &this->mBufferQueue.mItf; IBufferQueue *thisBQ = (IBufferQueue *) bufferQueue; IBufferQueue_RegisterCallback(&thisBQ->mItf, SndFile_Callback, this); this->mPrefetchStatus.mStatus = SL_PREFETCHSTATUS_SUFFICIENTDATA; // this is the initial duration; will update when a new maximum position is detected this->mPlay.mDuration = (SLmillisecond) (((long long) this->mSndFile.mSfInfo.frames * 1000LL) / this->mSndFile.mSfInfo.samplerate); this->mNumChannels = this->mSndFile.mSfInfo.channels; this->mSampleRateMilliHz = this->mSndFile.mSfInfo.samplerate * 1000; #ifdef USE_OUTPUTMIXEXT this->mPlay.mFrameUpdatePeriod = ((long long) this->mPlay.mPositionUpdatePeriod * (long long) this->mSampleRateMilliHz) / 1000000LL; #endif } } return result; } /** \brief Called by CAudioPlayer_Destroy */ void SndFile_Destroy(CAudioPlayer *this) { if (NULL != this->mSndFile.mSNDFILE) { sf_close(this->mSndFile.mSNDFILE); this->mSndFile.mSNDFILE = NULL; int ok; ok = pthread_mutex_destroy(&this->mSndFile.mMutex); assert(0 == ok); } }