/* * Copyright (C) 2011 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. */ //#define LOG_NDEBUG 0 #define LOG_TAG "AudioPlayerBase" #include <utils/Log.h> #include <binder/IPCThreadState.h> #include <media/AudioTrack.h> #include <media/stagefright/MediaDebug.h> #include <media/stagefright/MediaDefs.h> #include <media/stagefright/MediaErrors.h> #include <media/stagefright/MediaSource.h> #include <media/stagefright/MetaData.h> #include "AudioPlayerBase.h" #include "PreviewPlayerBase.h" namespace android { AudioPlayerBase::AudioPlayerBase( const sp<MediaPlayerBase::AudioSink> &audioSink, PreviewPlayerBase *observer) : mAudioTrack(NULL), mInputBuffer(NULL), mSampleRate(0), mLatencyUs(0), mFrameSize(0), mNumFramesPlayed(0), mPositionTimeMediaUs(-1), mPositionTimeRealUs(-1), mSeeking(false), mReachedEOS(false), mFinalStatus(OK), mStarted(false), mIsFirstBuffer(false), mFirstBufferResult(OK), mFirstBuffer(NULL), mAudioSink(audioSink), mObserver(observer) { } AudioPlayerBase::~AudioPlayerBase() { if (mStarted) { reset(); } } void AudioPlayerBase::setSource(const sp<MediaSource> &source) { CHECK_EQ(mSource, NULL); mSource = source; } status_t AudioPlayerBase::start(bool sourceAlreadyStarted) { CHECK(!mStarted); CHECK(mSource != NULL); status_t err; if (!sourceAlreadyStarted) { err = mSource->start(); if (err != OK) { return err; } } // We allow an optional INFO_FORMAT_CHANGED at the very beginning // of playback, if there is one, getFormat below will retrieve the // updated format, if there isn't, we'll stash away the valid buffer // of data to be used on the first audio callback. CHECK(mFirstBuffer == NULL); mFirstBufferResult = mSource->read(&mFirstBuffer); if (mFirstBufferResult == INFO_FORMAT_CHANGED) { LOGV("INFO_FORMAT_CHANGED!!!"); CHECK(mFirstBuffer == NULL); mFirstBufferResult = OK; mIsFirstBuffer = false; } else { mIsFirstBuffer = true; } sp<MetaData> format = mSource->getFormat(); const char *mime; bool success = format->findCString(kKeyMIMEType, &mime); CHECK(success); CHECK(!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_RAW)); success = format->findInt32(kKeySampleRate, &mSampleRate); CHECK(success); int32_t numChannels; success = format->findInt32(kKeyChannelCount, &numChannels); CHECK(success); if (mAudioSink.get() != NULL) { status_t err = mAudioSink->open( mSampleRate, numChannels, AUDIO_FORMAT_PCM_16_BIT, DEFAULT_AUDIOSINK_BUFFERCOUNT, &AudioPlayerBase::AudioSinkCallback, this); if (err != OK) { if (mFirstBuffer != NULL) { mFirstBuffer->release(); mFirstBuffer = NULL; } if (!sourceAlreadyStarted) { mSource->stop(); } return err; } mLatencyUs = (int64_t)mAudioSink->latency() * 1000; mFrameSize = mAudioSink->frameSize(); mAudioSink->start(); } else { mAudioTrack = new AudioTrack( AUDIO_STREAM_MUSIC, mSampleRate, AUDIO_FORMAT_PCM_16_BIT, (numChannels == 2) ? AUDIO_CHANNEL_OUT_STEREO : AUDIO_CHANNEL_OUT_MONO, 0, 0, &AudioCallback, this, 0); if ((err = mAudioTrack->initCheck()) != OK) { delete mAudioTrack; mAudioTrack = NULL; if (mFirstBuffer != NULL) { mFirstBuffer->release(); mFirstBuffer = NULL; } if (!sourceAlreadyStarted) { mSource->stop(); } return err; } mLatencyUs = (int64_t)mAudioTrack->latency() * 1000; mFrameSize = mAudioTrack->frameSize(); mAudioTrack->start(); } mStarted = true; return OK; } void AudioPlayerBase::pause(bool playPendingSamples) { CHECK(mStarted); if (playPendingSamples) { if (mAudioSink.get() != NULL) { mAudioSink->stop(); } else { mAudioTrack->stop(); } } else { if (mAudioSink.get() != NULL) { mAudioSink->pause(); } else { mAudioTrack->pause(); } } } void AudioPlayerBase::resume() { CHECK(mStarted); if (mAudioSink.get() != NULL) { mAudioSink->start(); } else { mAudioTrack->start(); } } void AudioPlayerBase::reset() { CHECK(mStarted); if (mAudioSink.get() != NULL) { mAudioSink->stop(); mAudioSink->close(); } else { mAudioTrack->stop(); delete mAudioTrack; mAudioTrack = NULL; } // Make sure to release any buffer we hold onto so that the // source is able to stop(). if (mFirstBuffer != NULL) { mFirstBuffer->release(); mFirstBuffer = NULL; } if (mInputBuffer != NULL) { LOGV("AudioPlayerBase releasing input buffer."); mInputBuffer->release(); mInputBuffer = NULL; } mSource->stop(); // The following hack is necessary to ensure that the OMX // component is completely released by the time we may try // to instantiate it again. wp<MediaSource> tmp = mSource; mSource.clear(); while (tmp.promote() != NULL) { usleep(1000); } IPCThreadState::self()->flushCommands(); mNumFramesPlayed = 0; mPositionTimeMediaUs = -1; mPositionTimeRealUs = -1; mSeeking = false; mReachedEOS = false; mFinalStatus = OK; mStarted = false; } // static void AudioPlayerBase::AudioCallback(int event, void *user, void *info) { static_cast<AudioPlayerBase *>(user)->AudioCallback(event, info); } bool AudioPlayerBase::isSeeking() { Mutex::Autolock autoLock(mLock); return mSeeking; } bool AudioPlayerBase::reachedEOS(status_t *finalStatus) { *finalStatus = OK; Mutex::Autolock autoLock(mLock); *finalStatus = mFinalStatus; return mReachedEOS; } // static size_t AudioPlayerBase::AudioSinkCallback( MediaPlayerBase::AudioSink *audioSink, void *buffer, size_t size, void *cookie) { AudioPlayerBase *me = (AudioPlayerBase *)cookie; return me->fillBuffer(buffer, size); } void AudioPlayerBase::AudioCallback(int event, void *info) { if (event != AudioTrack::EVENT_MORE_DATA) { return; } AudioTrack::Buffer *buffer = (AudioTrack::Buffer *)info; size_t numBytesWritten = fillBuffer(buffer->raw, buffer->size); buffer->size = numBytesWritten; } uint32_t AudioPlayerBase::getNumFramesPendingPlayout() const { uint32_t numFramesPlayedOut; status_t err; if (mAudioSink != NULL) { err = mAudioSink->getPosition(&numFramesPlayedOut); } else { err = mAudioTrack->getPosition(&numFramesPlayedOut); } if (err != OK || mNumFramesPlayed < numFramesPlayedOut) { return 0; } // mNumFramesPlayed is the number of frames submitted // to the audio sink for playback, but not all of them // may have played out by now. return mNumFramesPlayed - numFramesPlayedOut; } size_t AudioPlayerBase::fillBuffer(void *data, size_t size) { if (mNumFramesPlayed == 0) { LOGV("AudioCallback"); } if (mReachedEOS) { return 0; } bool postSeekComplete = false; bool postEOS = false; int64_t postEOSDelayUs = 0; size_t size_done = 0; size_t size_remaining = size; while (size_remaining > 0) { MediaSource::ReadOptions options; { Mutex::Autolock autoLock(mLock); if (mSeeking) { if (mIsFirstBuffer) { if (mFirstBuffer != NULL) { mFirstBuffer->release(); mFirstBuffer = NULL; } mIsFirstBuffer = false; } options.setSeekTo(mSeekTimeUs); if (mInputBuffer != NULL) { mInputBuffer->release(); mInputBuffer = NULL; } mSeeking = false; if (mObserver) { postSeekComplete = true; } } } if (mInputBuffer == NULL) { status_t err; if (mIsFirstBuffer) { mInputBuffer = mFirstBuffer; mFirstBuffer = NULL; err = mFirstBufferResult; mIsFirstBuffer = false; } else { err = mSource->read(&mInputBuffer, &options); } CHECK((err == OK && mInputBuffer != NULL) || (err != OK && mInputBuffer == NULL)); Mutex::Autolock autoLock(mLock); if (err != OK) { if (mObserver && !mReachedEOS) { // We don't want to post EOS right away but only // after all frames have actually been played out. // These are the number of frames submitted to the // AudioTrack that you haven't heard yet. uint32_t numFramesPendingPlayout = getNumFramesPendingPlayout(); // These are the number of frames we're going to // submit to the AudioTrack by returning from this // callback. uint32_t numAdditionalFrames = size_done / mFrameSize; numFramesPendingPlayout += numAdditionalFrames; int64_t timeToCompletionUs = (1000000ll * numFramesPendingPlayout) / mSampleRate; LOGV("total number of frames played: %lld (%lld us)", (mNumFramesPlayed + numAdditionalFrames), 1000000ll * (mNumFramesPlayed + numAdditionalFrames) / mSampleRate); LOGV("%d frames left to play, %lld us (%.2f secs)", numFramesPendingPlayout, timeToCompletionUs, timeToCompletionUs / 1E6); postEOS = true; postEOSDelayUs = timeToCompletionUs + mLatencyUs; } mReachedEOS = true; mFinalStatus = err; break; } CHECK(mInputBuffer->meta_data()->findInt64( kKeyTime, &mPositionTimeMediaUs)); mPositionTimeRealUs = ((mNumFramesPlayed + size_done / mFrameSize) * 1000000) / mSampleRate; LOGV("buffer->size() = %d, " "mPositionTimeMediaUs=%.2f mPositionTimeRealUs=%.2f", mInputBuffer->range_length(), mPositionTimeMediaUs / 1E6, mPositionTimeRealUs / 1E6); } if (mInputBuffer->range_length() == 0) { mInputBuffer->release(); mInputBuffer = NULL; continue; } size_t copy = size_remaining; if (copy > mInputBuffer->range_length()) { copy = mInputBuffer->range_length(); } memcpy((char *)data + size_done, (const char *)mInputBuffer->data() + mInputBuffer->range_offset(), copy); mInputBuffer->set_range(mInputBuffer->range_offset() + copy, mInputBuffer->range_length() - copy); size_done += copy; size_remaining -= copy; } { Mutex::Autolock autoLock(mLock); mNumFramesPlayed += size_done / mFrameSize; } if (postEOS) { mObserver->postAudioEOS(postEOSDelayUs); } if (postSeekComplete) { mObserver->postAudioSeekComplete(); } return size_done; } int64_t AudioPlayerBase::getRealTimeUs() { Mutex::Autolock autoLock(mLock); return getRealTimeUsLocked(); } int64_t AudioPlayerBase::getRealTimeUsLocked() const { return -mLatencyUs + (mNumFramesPlayed * 1000000) / mSampleRate; } int64_t AudioPlayerBase::getMediaTimeUs() { Mutex::Autolock autoLock(mLock); if (mPositionTimeMediaUs < 0 || mPositionTimeRealUs < 0) { if (mSeeking) { return mSeekTimeUs; } return 0; } int64_t realTimeOffset = getRealTimeUsLocked() - mPositionTimeRealUs; if (realTimeOffset < 0) { realTimeOffset = 0; } return mPositionTimeMediaUs + realTimeOffset; } bool AudioPlayerBase::getMediaTimeMapping( int64_t *realtime_us, int64_t *mediatime_us) { Mutex::Autolock autoLock(mLock); *realtime_us = mPositionTimeRealUs; *mediatime_us = mPositionTimeMediaUs; return mPositionTimeRealUs != -1 && mPositionTimeMediaUs != -1; } status_t AudioPlayerBase::seekTo(int64_t time_us) { Mutex::Autolock autoLock(mLock); mSeeking = true; mPositionTimeRealUs = mPositionTimeMediaUs = -1; mReachedEOS = false; mSeekTimeUs = time_us; if (mAudioSink != NULL) { mAudioSink->flush(); } else { mAudioTrack->flush(); } return OK; } }