| /* |
| * Copyright 2015 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 "MediaSync" |
| #include <inttypes.h> |
| |
| #include <gui/BufferQueue.h> |
| #include <gui/IGraphicBufferConsumer.h> |
| #include <gui/IGraphicBufferProducer.h> |
| |
| #include <media/AudioTrack.h> |
| #include <media/stagefright/MediaClock.h> |
| #include <media/stagefright/MediaSync.h> |
| #include <media/stagefright/VideoFrameScheduler.h> |
| #include <media/stagefright/foundation/ADebug.h> |
| #include <media/stagefright/foundation/ALooper.h> |
| #include <media/stagefright/foundation/AMessage.h> |
| |
| #include <ui/GraphicBuffer.h> |
| |
| #include <system/window.h> |
| |
| // Maximum late time allowed for a video frame to be rendered. When a video |
| // frame arrives later than this number, it will be discarded without rendering. |
| static const int64_t kMaxAllowedVideoLateTimeUs = 40000ll; |
| |
| namespace android { |
| |
| // static |
| sp<MediaSync> MediaSync::create() { |
| sp<MediaSync> sync = new MediaSync(); |
| sync->mLooper->registerHandler(sync); |
| return sync; |
| } |
| |
| MediaSync::MediaSync() |
| : mIsAbandoned(false), |
| mMutex(), |
| mReleaseCondition(), |
| mNumOutstandingBuffers(0), |
| mUsageFlagsFromOutput(0), |
| mMaxAcquiredBufferCount(1), |
| mReturnPendingInputFrame(false), |
| mNativeSampleRateInHz(0), |
| mNumFramesWritten(0), |
| mHasAudio(false), |
| mNextBufferItemMediaUs(-1), |
| mPlaybackRate(0.0) { |
| mMediaClock = new MediaClock; |
| |
| // initialize settings |
| mPlaybackSettings = AUDIO_PLAYBACK_RATE_DEFAULT; |
| mPlaybackSettings.mSpeed = mPlaybackRate; |
| |
| mLooper = new ALooper; |
| mLooper->setName("MediaSync"); |
| mLooper->start(false, false, ANDROID_PRIORITY_AUDIO); |
| } |
| |
| MediaSync::~MediaSync() { |
| if (mInput != NULL) { |
| mInput->consumerDisconnect(); |
| } |
| if (mOutput != NULL) { |
| mOutput->disconnect(NATIVE_WINDOW_API_MEDIA); |
| } |
| |
| if (mLooper != NULL) { |
| mLooper->unregisterHandler(id()); |
| mLooper->stop(); |
| } |
| } |
| |
| status_t MediaSync::setSurface(const sp<IGraphicBufferProducer> &output) { |
| Mutex::Autolock lock(mMutex); |
| |
| if (output == mOutput) { |
| return NO_ERROR; // same output surface. |
| } |
| |
| if (output == NULL && mSyncSettings.mSource == AVSYNC_SOURCE_VSYNC) { |
| ALOGE("setSurface: output surface is used as sync source and cannot be removed."); |
| return INVALID_OPERATION; |
| } |
| |
| if (output != NULL) { |
| int newUsage = 0; |
| output->query(NATIVE_WINDOW_CONSUMER_USAGE_BITS, &newUsage); |
| |
| // Check usage flags only when current output surface has been used to create input surface. |
| if (mOutput != NULL && mInput != NULL) { |
| int ignoredFlags = (GRALLOC_USAGE_HW_TEXTURE | GRALLOC_USAGE_HW_COMPOSER |
| | GRALLOC_USAGE_EXTERNAL_DISP); |
| // New output surface is not allowed to add new usage flag except ignored ones. |
| if ((newUsage & ~(mUsageFlagsFromOutput | ignoredFlags)) != 0) { |
| ALOGE("setSurface: new output surface has new usage flag not used by current one."); |
| return BAD_VALUE; |
| } |
| } |
| |
| // Try to connect to new output surface. If failed, current output surface will not |
| // be changed. |
| IGraphicBufferProducer::QueueBufferOutput queueBufferOutput; |
| sp<OutputListener> listener(new OutputListener(this, output)); |
| IInterface::asBinder(output)->linkToDeath(listener); |
| status_t status = |
| output->connect(listener, |
| NATIVE_WINDOW_API_MEDIA, |
| true /* producerControlledByApp */, |
| &queueBufferOutput); |
| if (status != NO_ERROR) { |
| ALOGE("setSurface: failed to connect (%d)", status); |
| return status; |
| } |
| |
| if (mFrameScheduler == NULL) { |
| mFrameScheduler = new VideoFrameScheduler(); |
| mFrameScheduler->init(); |
| } |
| } |
| |
| if (mOutput != NULL) { |
| mOutput->disconnect(NATIVE_WINDOW_API_MEDIA); |
| while (!mBuffersSentToOutput.isEmpty()) { |
| returnBufferToInput_l(mBuffersSentToOutput.valueAt(0), Fence::NO_FENCE); |
| mBuffersSentToOutput.removeItemsAt(0); |
| } |
| } |
| |
| mOutput = output; |
| |
| return NO_ERROR; |
| } |
| |
| // |audioTrack| is used only for querying information. |
| status_t MediaSync::setAudioTrack(const sp<AudioTrack> &audioTrack) { |
| Mutex::Autolock lock(mMutex); |
| |
| // TODO: support audio track change. |
| if (mAudioTrack != NULL) { |
| ALOGE("setAudioTrack: audioTrack has already been configured."); |
| return INVALID_OPERATION; |
| } |
| |
| if (audioTrack == NULL && mSyncSettings.mSource == AVSYNC_SOURCE_AUDIO) { |
| ALOGE("setAudioTrack: audioTrack is used as sync source and cannot be removed."); |
| return INVALID_OPERATION; |
| } |
| |
| if (audioTrack != NULL) { |
| // check if audio track supports the playback settings |
| if (mPlaybackSettings.mSpeed != 0.f |
| && audioTrack->setPlaybackRate(mPlaybackSettings) != OK) { |
| ALOGE("playback settings are not supported by the audio track"); |
| return INVALID_OPERATION; |
| } |
| uint32_t nativeSampleRateInHz = audioTrack->getOriginalSampleRate(); |
| if (nativeSampleRateInHz <= 0) { |
| ALOGE("setAudioTrack: native sample rate should be positive."); |
| return BAD_VALUE; |
| } |
| mAudioTrack = audioTrack; |
| mNativeSampleRateInHz = nativeSampleRateInHz; |
| (void)setPlaybackSettings_l(mPlaybackSettings); |
| } |
| else { |
| mAudioTrack = NULL; |
| mNativeSampleRateInHz = 0; |
| } |
| |
| // potentially resync to new source |
| resync_l(); |
| return OK; |
| } |
| |
| status_t MediaSync::createInputSurface( |
| sp<IGraphicBufferProducer> *outBufferProducer) { |
| if (outBufferProducer == NULL) { |
| return BAD_VALUE; |
| } |
| |
| Mutex::Autolock lock(mMutex); |
| |
| if (mOutput == NULL) { |
| return NO_INIT; |
| } |
| |
| if (mInput != NULL) { |
| return INVALID_OPERATION; |
| } |
| |
| sp<IGraphicBufferProducer> bufferProducer; |
| sp<IGraphicBufferConsumer> bufferConsumer; |
| BufferQueue::createBufferQueue(&bufferProducer, &bufferConsumer); |
| |
| sp<InputListener> listener(new InputListener(this)); |
| IInterface::asBinder(bufferConsumer)->linkToDeath(listener); |
| status_t status = |
| bufferConsumer->consumerConnect(listener, false /* controlledByApp */); |
| if (status == NO_ERROR) { |
| bufferConsumer->setConsumerName(String8("MediaSync")); |
| // propagate usage bits from output surface |
| mUsageFlagsFromOutput = 0; |
| mOutput->query(NATIVE_WINDOW_CONSUMER_USAGE_BITS, &mUsageFlagsFromOutput); |
| bufferConsumer->setConsumerUsageBits(mUsageFlagsFromOutput); |
| *outBufferProducer = bufferProducer; |
| mInput = bufferConsumer; |
| |
| // set undequeued buffer count |
| int minUndequeuedBuffers; |
| mOutput->query(NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS, &minUndequeuedBuffers); |
| mMaxAcquiredBufferCount = minUndequeuedBuffers; |
| bufferConsumer->setMaxAcquiredBufferCount(mMaxAcquiredBufferCount); |
| } |
| return status; |
| } |
| |
| void MediaSync::resync_l() { |
| AVSyncSource src = mSyncSettings.mSource; |
| if (src == AVSYNC_SOURCE_DEFAULT) { |
| if (mAudioTrack != NULL) { |
| src = AVSYNC_SOURCE_AUDIO; |
| } else { |
| src = AVSYNC_SOURCE_SYSTEM_CLOCK; |
| } |
| } |
| |
| // TODO: resync ourselves to the current clock (e.g. on sync source change) |
| updatePlaybackRate_l(mPlaybackRate); |
| } |
| |
| void MediaSync::updatePlaybackRate_l(float rate) { |
| if (rate > mPlaybackRate) { |
| mNextBufferItemMediaUs = -1; |
| } |
| mPlaybackRate = rate; |
| // TODO: update frame scheduler with this info |
| mMediaClock->setPlaybackRate(rate); |
| onDrainVideo_l(); |
| } |
| |
| sp<const MediaClock> MediaSync::getMediaClock() { |
| return mMediaClock; |
| } |
| |
| status_t MediaSync::getPlayTimeForPendingAudioFrames(int64_t *outTimeUs) { |
| Mutex::Autolock lock(mMutex); |
| // User should check the playback rate if it doesn't want to receive a |
| // huge number for play time. |
| if (mPlaybackRate == 0.0f) { |
| *outTimeUs = INT64_MAX; |
| return OK; |
| } |
| |
| uint32_t numFramesPlayed = 0; |
| if (mAudioTrack != NULL) { |
| status_t res = mAudioTrack->getPosition(&numFramesPlayed); |
| if (res != OK) { |
| return res; |
| } |
| } |
| |
| int64_t numPendingFrames = mNumFramesWritten - numFramesPlayed; |
| if (numPendingFrames < 0) { |
| numPendingFrames = 0; |
| ALOGW("getPlayTimeForPendingAudioFrames: pending frame count is negative."); |
| } |
| double timeUs = numPendingFrames * 1000000.0 |
| / (mNativeSampleRateInHz * (double)mPlaybackRate); |
| if (timeUs > (double)INT64_MAX) { |
| // Overflow. |
| *outTimeUs = INT64_MAX; |
| ALOGW("getPlayTimeForPendingAudioFrames: play time for pending audio frames " |
| "is too high, possibly due to super low playback rate(%f)", mPlaybackRate); |
| } else { |
| *outTimeUs = (int64_t)timeUs; |
| } |
| |
| return OK; |
| } |
| |
| status_t MediaSync::updateQueuedAudioData( |
| size_t sizeInBytes, int64_t presentationTimeUs) { |
| if (sizeInBytes == 0) { |
| return OK; |
| } |
| |
| Mutex::Autolock lock(mMutex); |
| |
| if (mAudioTrack == NULL) { |
| ALOGW("updateQueuedAudioData: audioTrack has NOT been configured."); |
| return INVALID_OPERATION; |
| } |
| |
| int64_t numFrames = sizeInBytes / mAudioTrack->frameSize(); |
| int64_t maxMediaTimeUs = presentationTimeUs |
| + getDurationIfPlayedAtNativeSampleRate_l(numFrames); |
| |
| int64_t nowUs = ALooper::GetNowUs(); |
| int64_t nowMediaUs = presentationTimeUs |
| - getDurationIfPlayedAtNativeSampleRate_l(mNumFramesWritten) |
| + getPlayedOutAudioDurationMedia_l(nowUs); |
| |
| mNumFramesWritten += numFrames; |
| |
| int64_t oldRealTime = -1; |
| if (mNextBufferItemMediaUs != -1) { |
| oldRealTime = getRealTime(mNextBufferItemMediaUs, nowUs); |
| } |
| |
| mMediaClock->updateAnchor(nowMediaUs, nowUs, maxMediaTimeUs); |
| mHasAudio = true; |
| |
| if (oldRealTime != -1) { |
| int64_t newRealTime = getRealTime(mNextBufferItemMediaUs, nowUs); |
| if (newRealTime >= oldRealTime) { |
| return OK; |
| } |
| } |
| |
| mNextBufferItemMediaUs = -1; |
| onDrainVideo_l(); |
| return OK; |
| } |
| |
| void MediaSync::setName(const AString &name) { |
| Mutex::Autolock lock(mMutex); |
| mInput->setConsumerName(String8(name.c_str())); |
| } |
| |
| void MediaSync::flush() { |
| Mutex::Autolock lock(mMutex); |
| if (mFrameScheduler != NULL) { |
| mFrameScheduler->restart(); |
| } |
| while (!mBufferItems.empty()) { |
| BufferItem *bufferItem = &*mBufferItems.begin(); |
| returnBufferToInput_l(bufferItem->mGraphicBuffer, bufferItem->mFence); |
| mBufferItems.erase(mBufferItems.begin()); |
| } |
| mNextBufferItemMediaUs = -1; |
| mNumFramesWritten = 0; |
| mReturnPendingInputFrame = true; |
| mReleaseCondition.signal(); |
| mMediaClock->clearAnchor(); |
| } |
| |
| status_t MediaSync::setVideoFrameRateHint(float rate) { |
| Mutex::Autolock lock(mMutex); |
| if (rate < 0.f) { |
| return BAD_VALUE; |
| } |
| if (mFrameScheduler != NULL) { |
| mFrameScheduler->init(rate); |
| } |
| return OK; |
| } |
| |
| float MediaSync::getVideoFrameRate() { |
| Mutex::Autolock lock(mMutex); |
| if (mFrameScheduler != NULL) { |
| float fps = mFrameScheduler->getFrameRate(); |
| if (fps > 0.f) { |
| return fps; |
| } |
| } |
| |
| // we don't have or know the frame rate |
| return -1.f; |
| } |
| |
| status_t MediaSync::setSyncSettings(const AVSyncSettings &syncSettings) { |
| // validate settings |
| if (syncSettings.mSource >= AVSYNC_SOURCE_MAX |
| || syncSettings.mAudioAdjustMode >= AVSYNC_AUDIO_ADJUST_MODE_MAX |
| || syncSettings.mTolerance < 0.f |
| || syncSettings.mTolerance >= AVSYNC_TOLERANCE_MAX) { |
| return BAD_VALUE; |
| } |
| |
| Mutex::Autolock lock(mMutex); |
| |
| // verify that we have the sync source |
| switch (syncSettings.mSource) { |
| case AVSYNC_SOURCE_AUDIO: |
| if (mAudioTrack == NULL) { |
| ALOGE("setSyncSettings: audio sync source requires an audio track"); |
| return BAD_VALUE; |
| } |
| break; |
| case AVSYNC_SOURCE_VSYNC: |
| if (mOutput == NULL) { |
| ALOGE("setSyncSettings: vsync sync source requires an output surface"); |
| return BAD_VALUE; |
| } |
| break; |
| default: |
| break; |
| } |
| |
| mSyncSettings = syncSettings; |
| resync_l(); |
| return OK; |
| } |
| |
| void MediaSync::getSyncSettings(AVSyncSettings *syncSettings) { |
| Mutex::Autolock lock(mMutex); |
| *syncSettings = mSyncSettings; |
| } |
| |
| status_t MediaSync::setPlaybackSettings(const AudioPlaybackRate &rate) { |
| Mutex::Autolock lock(mMutex); |
| |
| status_t err = setPlaybackSettings_l(rate); |
| if (err == OK) { |
| // TODO: adjust rate if using VSYNC as source |
| updatePlaybackRate_l(rate.mSpeed); |
| } |
| return err; |
| } |
| |
| status_t MediaSync::setPlaybackSettings_l(const AudioPlaybackRate &rate) { |
| if (rate.mSpeed < 0.f || rate.mPitch < 0.f) { |
| // We don't validate other audio settings. |
| // They will be validated when/if audiotrack is set. |
| return BAD_VALUE; |
| } |
| |
| if (mAudioTrack != NULL) { |
| if (rate.mSpeed == 0.f) { |
| mAudioTrack->pause(); |
| } else { |
| status_t err = mAudioTrack->setPlaybackRate(rate); |
| if (err != OK) { |
| return BAD_VALUE; |
| } |
| |
| // ignore errors |
| (void)mAudioTrack->start(); |
| } |
| } |
| mPlaybackSettings = rate; |
| return OK; |
| } |
| |
| void MediaSync::getPlaybackSettings(AudioPlaybackRate *rate) { |
| Mutex::Autolock lock(mMutex); |
| *rate = mPlaybackSettings; |
| } |
| |
| int64_t MediaSync::getRealTime(int64_t mediaTimeUs, int64_t nowUs) { |
| int64_t realUs; |
| if (mMediaClock->getRealTimeFor(mediaTimeUs, &realUs) != OK) { |
| // If failed to get current position, e.g. due to audio clock is |
| // not ready, then just play out video immediately without delay. |
| return nowUs; |
| } |
| return realUs; |
| } |
| |
| int64_t MediaSync::getDurationIfPlayedAtNativeSampleRate_l(int64_t numFrames) { |
| return (numFrames * 1000000LL / mNativeSampleRateInHz); |
| } |
| |
| int64_t MediaSync::getPlayedOutAudioDurationMedia_l(int64_t nowUs) { |
| CHECK(mAudioTrack != NULL); |
| |
| uint32_t numFramesPlayed; |
| int64_t numFramesPlayedAtUs; |
| AudioTimestamp ts; |
| |
| status_t res = mAudioTrack->getTimestamp(ts); |
| if (res == OK) { |
| // case 1: mixing audio tracks. |
| numFramesPlayed = ts.mPosition; |
| numFramesPlayedAtUs = ts.mTime.tv_sec * 1000000LL + ts.mTime.tv_nsec / 1000; |
| //ALOGD("getTimestamp: OK %d %lld", |
| // numFramesPlayed, (long long)numFramesPlayedAtUs); |
| } else if (res == WOULD_BLOCK) { |
| // case 2: transitory state on start of a new track |
| numFramesPlayed = 0; |
| numFramesPlayedAtUs = nowUs; |
| //ALOGD("getTimestamp: WOULD_BLOCK %d %lld", |
| // numFramesPlayed, (long long)numFramesPlayedAtUs); |
| } else { |
| // case 3: transitory at new track or audio fast tracks. |
| res = mAudioTrack->getPosition(&numFramesPlayed); |
| CHECK_EQ(res, (status_t)OK); |
| numFramesPlayedAtUs = nowUs; |
| numFramesPlayedAtUs += 1000LL * mAudioTrack->latency() / 2; /* XXX */ |
| //ALOGD("getPosition: %d %lld", numFramesPlayed, (long long)numFramesPlayedAtUs); |
| } |
| |
| //can't be negative until 12.4 hrs, test. |
| //CHECK_EQ(numFramesPlayed & (1 << 31), 0); |
| int64_t durationUs = |
| getDurationIfPlayedAtNativeSampleRate_l(numFramesPlayed) |
| + nowUs - numFramesPlayedAtUs; |
| if (durationUs < 0) { |
| // Occurs when numFramesPlayed position is very small and the following: |
| // (1) In case 1, the time nowUs is computed before getTimestamp() is |
| // called and numFramesPlayedAtUs is greater than nowUs by time more |
| // than numFramesPlayed. |
| // (2) In case 3, using getPosition and adding mAudioTrack->latency() |
| // to numFramesPlayedAtUs, by a time amount greater than |
| // numFramesPlayed. |
| // |
| // Both of these are transitory conditions. |
| ALOGV("getPlayedOutAudioDurationMedia_l: negative duration %lld " |
| "set to zero", (long long)durationUs); |
| durationUs = 0; |
| } |
| ALOGV("getPlayedOutAudioDurationMedia_l(%lld) nowUs(%lld) frames(%u) " |
| "framesAt(%lld)", |
| (long long)durationUs, (long long)nowUs, numFramesPlayed, |
| (long long)numFramesPlayedAtUs); |
| return durationUs; |
| } |
| |
| void MediaSync::onDrainVideo_l() { |
| if (!isPlaying()) { |
| return; |
| } |
| |
| while (!mBufferItems.empty()) { |
| int64_t nowUs = ALooper::GetNowUs(); |
| BufferItem *bufferItem = &*mBufferItems.begin(); |
| int64_t itemMediaUs = bufferItem->mTimestamp / 1000; |
| int64_t itemRealUs = getRealTime(itemMediaUs, nowUs); |
| |
| // adjust video frame PTS based on vsync |
| itemRealUs = mFrameScheduler->schedule(itemRealUs * 1000) / 1000; |
| int64_t twoVsyncsUs = 2 * (mFrameScheduler->getVsyncPeriod() / 1000); |
| |
| // post 2 display refreshes before rendering is due |
| if (itemRealUs <= nowUs + twoVsyncsUs) { |
| ALOGV("adjusting PTS from %lld to %lld", |
| (long long)bufferItem->mTimestamp / 1000, (long long)itemRealUs); |
| bufferItem->mTimestamp = itemRealUs * 1000; |
| bufferItem->mIsAutoTimestamp = false; |
| |
| if (mHasAudio) { |
| if (nowUs - itemRealUs <= kMaxAllowedVideoLateTimeUs) { |
| renderOneBufferItem_l(*bufferItem); |
| } else { |
| // too late. |
| returnBufferToInput_l( |
| bufferItem->mGraphicBuffer, bufferItem->mFence); |
| mFrameScheduler->restart(); |
| } |
| } else { |
| // always render video buffer in video-only mode. |
| renderOneBufferItem_l(*bufferItem); |
| |
| // smooth out videos >= 10fps |
| mMediaClock->updateAnchor( |
| itemMediaUs, nowUs, itemMediaUs + 100000); |
| } |
| |
| mBufferItems.erase(mBufferItems.begin()); |
| mNextBufferItemMediaUs = -1; |
| } else { |
| if (mNextBufferItemMediaUs == -1 |
| || mNextBufferItemMediaUs > itemMediaUs) { |
| sp<AMessage> msg = new AMessage(kWhatDrainVideo, this); |
| msg->post(itemRealUs - nowUs - twoVsyncsUs); |
| mNextBufferItemMediaUs = itemMediaUs; |
| } |
| break; |
| } |
| } |
| } |
| |
| void MediaSync::onFrameAvailableFromInput() { |
| Mutex::Autolock lock(mMutex); |
| |
| const static nsecs_t kAcquireWaitTimeout = 2000000000; // 2 seconds |
| |
| mReturnPendingInputFrame = false; |
| |
| // If there are too many outstanding buffers, wait until a buffer is |
| // released back to the input in onBufferReleased. |
| // NOTE: BufferQueue allows dequeuing maxAcquiredBufferCount + 1 buffers |
| while (mNumOutstandingBuffers > mMaxAcquiredBufferCount |
| && !mIsAbandoned && !mReturnPendingInputFrame) { |
| if (mReleaseCondition.waitRelative(mMutex, kAcquireWaitTimeout) != OK) { |
| ALOGI_IF(mPlaybackRate != 0.f, "still waiting to release a buffer before acquire"); |
| } |
| |
| // If the sync is abandoned while we are waiting, the release |
| // condition variable will be broadcast, and we should just return |
| // without attempting to do anything more (since the input queue will |
| // also be abandoned). |
| if (mIsAbandoned) { |
| return; |
| } |
| } |
| |
| // Acquire and detach the buffer from the input. |
| BufferItem bufferItem; |
| status_t status = mInput->acquireBuffer(&bufferItem, 0 /* presentWhen */); |
| if (status != NO_ERROR) { |
| ALOGE("acquiring buffer from input failed (%d)", status); |
| return; |
| } |
| ++mNumOutstandingBuffers; |
| |
| ALOGV("acquired buffer %#llx from input", (long long)bufferItem.mGraphicBuffer->getId()); |
| |
| status = mInput->detachBuffer(bufferItem.mSlot); |
| if (status != NO_ERROR) { |
| ALOGE("detaching buffer from input failed (%d)", status); |
| if (status == NO_INIT) { |
| // If the input has been abandoned, move on. |
| onAbandoned_l(true /* isInput */); |
| } |
| return; |
| } |
| |
| if (mBuffersFromInput.indexOfKey(bufferItem.mGraphicBuffer->getId()) >= 0) { |
| // Something is wrong since this buffer should be at our hands, bail. |
| ALOGE("received buffer multiple times from input"); |
| mInput->consumerDisconnect(); |
| onAbandoned_l(true /* isInput */); |
| return; |
| } |
| mBuffersFromInput.add(bufferItem.mGraphicBuffer->getId(), bufferItem.mGraphicBuffer); |
| |
| // If flush happened while waiting for a buffer to be released, simply return it |
| // TRICKY: do it here after it is detached so that we don't have to cache mGraphicBuffer. |
| if (mReturnPendingInputFrame) { |
| mReturnPendingInputFrame = false; |
| returnBufferToInput_l(bufferItem.mGraphicBuffer, bufferItem.mFence); |
| return; |
| } |
| |
| mBufferItems.push_back(bufferItem); |
| |
| if (mBufferItems.size() == 1) { |
| onDrainVideo_l(); |
| } |
| } |
| |
| void MediaSync::renderOneBufferItem_l(const BufferItem &bufferItem) { |
| IGraphicBufferProducer::QueueBufferInput queueInput( |
| bufferItem.mTimestamp, |
| bufferItem.mIsAutoTimestamp, |
| bufferItem.mDataSpace, |
| bufferItem.mCrop, |
| static_cast<int32_t>(bufferItem.mScalingMode), |
| bufferItem.mTransform, |
| bufferItem.mFence); |
| |
| // Attach and queue the buffer to the output. |
| int slot; |
| mOutput->setGenerationNumber(bufferItem.mGraphicBuffer->getGenerationNumber()); |
| status_t status = mOutput->attachBuffer(&slot, bufferItem.mGraphicBuffer); |
| ALOGE_IF(status != NO_ERROR, "attaching buffer to output failed (%d)", status); |
| if (status == NO_ERROR) { |
| IGraphicBufferProducer::QueueBufferOutput queueOutput; |
| status = mOutput->queueBuffer(slot, queueInput, &queueOutput); |
| ALOGE_IF(status != NO_ERROR, "queueing buffer to output failed (%d)", status); |
| } |
| |
| if (status != NO_ERROR) { |
| returnBufferToInput_l(bufferItem.mGraphicBuffer, bufferItem.mFence); |
| if (status == NO_INIT) { |
| // If the output has been abandoned, move on. |
| onAbandoned_l(false /* isInput */); |
| } |
| return; |
| } |
| |
| if (mBuffersSentToOutput.indexOfKey(bufferItem.mGraphicBuffer->getId()) >= 0) { |
| // Something is wrong since this buffer should be held by output now, bail. |
| mInput->consumerDisconnect(); |
| onAbandoned_l(true /* isInput */); |
| return; |
| } |
| mBuffersSentToOutput.add(bufferItem.mGraphicBuffer->getId(), bufferItem.mGraphicBuffer); |
| |
| ALOGV("queued buffer %#llx to output", (long long)bufferItem.mGraphicBuffer->getId()); |
| } |
| |
| void MediaSync::onBufferReleasedByOutput(sp<IGraphicBufferProducer> &output) { |
| Mutex::Autolock lock(mMutex); |
| |
| if (output != mOutput) { |
| return; // This is not the current output, ignore. |
| } |
| |
| sp<GraphicBuffer> buffer; |
| sp<Fence> fence; |
| status_t status = mOutput->detachNextBuffer(&buffer, &fence); |
| ALOGE_IF(status != NO_ERROR, "detaching buffer from output failed (%d)", status); |
| |
| if (status == NO_INIT) { |
| // If the output has been abandoned, we can't do anything else, |
| // since buffer is invalid. |
| onAbandoned_l(false /* isInput */); |
| return; |
| } |
| |
| ALOGV("detached buffer %#llx from output", (long long)buffer->getId()); |
| |
| // If we've been abandoned, we can't return the buffer to the input, so just |
| // move on. |
| if (mIsAbandoned) { |
| return; |
| } |
| |
| ssize_t ix = mBuffersSentToOutput.indexOfKey(buffer->getId()); |
| if (ix < 0) { |
| // The buffer is unknown, maybe leftover, ignore. |
| return; |
| } |
| mBuffersSentToOutput.removeItemsAt(ix); |
| |
| returnBufferToInput_l(buffer, fence); |
| } |
| |
| void MediaSync::returnBufferToInput_l( |
| const sp<GraphicBuffer> &buffer, const sp<Fence> &fence) { |
| ssize_t ix = mBuffersFromInput.indexOfKey(buffer->getId()); |
| if (ix < 0) { |
| // The buffer is unknown, something is wrong, bail. |
| ALOGE("output returned unknown buffer"); |
| mOutput->disconnect(NATIVE_WINDOW_API_MEDIA); |
| onAbandoned_l(false /* isInput */); |
| return; |
| } |
| sp<GraphicBuffer> oldBuffer = mBuffersFromInput.valueAt(ix); |
| mBuffersFromInput.removeItemsAt(ix); |
| |
| // Attach and release the buffer back to the input. |
| int consumerSlot; |
| status_t status = mInput->attachBuffer(&consumerSlot, oldBuffer); |
| ALOGE_IF(status != NO_ERROR, "attaching buffer to input failed (%d)", status); |
| if (status == NO_ERROR) { |
| status = mInput->releaseBuffer(consumerSlot, 0 /* frameNumber */, |
| EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, fence); |
| ALOGE_IF(status != NO_ERROR, "releasing buffer to input failed (%d)", status); |
| } |
| |
| // Notify any waiting onFrameAvailable calls. |
| --mNumOutstandingBuffers; |
| mReleaseCondition.signal(); |
| |
| if (status == NO_ERROR) { |
| ALOGV("released buffer %#llx to input", (long long)oldBuffer->getId()); |
| } |
| } |
| |
| void MediaSync::onAbandoned_l(bool isInput) { |
| ALOGE("the %s has abandoned me", (isInput ? "input" : "output")); |
| if (!mIsAbandoned) { |
| if (isInput) { |
| mOutput->disconnect(NATIVE_WINDOW_API_MEDIA); |
| } else { |
| mInput->consumerDisconnect(); |
| } |
| mIsAbandoned = true; |
| } |
| mReleaseCondition.broadcast(); |
| } |
| |
| void MediaSync::onMessageReceived(const sp<AMessage> &msg) { |
| switch (msg->what()) { |
| case kWhatDrainVideo: |
| { |
| Mutex::Autolock lock(mMutex); |
| if (mNextBufferItemMediaUs != -1) { |
| int64_t nowUs = ALooper::GetNowUs(); |
| int64_t itemRealUs = getRealTime(mNextBufferItemMediaUs, nowUs); |
| |
| // The message could arrive earlier than expected due to |
| // various reasons, e.g., media clock has been changed because |
| // of new anchor time or playback rate. In such cases, the |
| // message needs to be re-posted. |
| if (itemRealUs > nowUs) { |
| msg->post(itemRealUs - nowUs); |
| break; |
| } |
| } |
| |
| onDrainVideo_l(); |
| break; |
| } |
| |
| default: |
| TRESPASS(); |
| break; |
| } |
| } |
| |
| MediaSync::InputListener::InputListener(const sp<MediaSync> &sync) |
| : mSync(sync) {} |
| |
| MediaSync::InputListener::~InputListener() {} |
| |
| void MediaSync::InputListener::onFrameAvailable(const BufferItem &/* item */) { |
| mSync->onFrameAvailableFromInput(); |
| } |
| |
| // We don't care about sideband streams, since we won't relay them. |
| void MediaSync::InputListener::onSidebandStreamChanged() { |
| ALOGE("onSidebandStreamChanged: got sideband stream unexpectedly."); |
| } |
| |
| |
| void MediaSync::InputListener::binderDied(const wp<IBinder> &/* who */) { |
| Mutex::Autolock lock(mSync->mMutex); |
| mSync->onAbandoned_l(true /* isInput */); |
| } |
| |
| MediaSync::OutputListener::OutputListener(const sp<MediaSync> &sync, |
| const sp<IGraphicBufferProducer> &output) |
| : mSync(sync), |
| mOutput(output) {} |
| |
| MediaSync::OutputListener::~OutputListener() {} |
| |
| void MediaSync::OutputListener::onBufferReleased() { |
| mSync->onBufferReleasedByOutput(mOutput); |
| } |
| |
| void MediaSync::OutputListener::binderDied(const wp<IBinder> &/* who */) { |
| Mutex::Autolock lock(mSync->mMutex); |
| mSync->onAbandoned_l(false /* isInput */); |
| } |
| |
| } // namespace android |