/* * 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 #include #include #include #include #include #include #include #include #include #include #include // 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::create() { sp 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 &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 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) { 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 *outBufferProducer) { if (outBufferProducer == NULL) { return BAD_VALUE; } Mutex::Autolock lock(mMutex); if (mOutput == NULL) { return NO_INIT; } if (mInput != NULL) { return INVALID_OPERATION; } sp bufferProducer; sp bufferConsumer; BufferQueue::createBufferQueue(&bufferProducer, &bufferConsumer); sp 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 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 numFramesPlayedAt; AudioTimestamp ts; static const int64_t kStaleTimestamp100ms = 100000; status_t res = mAudioTrack->getTimestamp(ts); if (res == OK) { // case 1: mixing audio tracks. numFramesPlayed = ts.mPosition; numFramesPlayedAt = ts.mTime.tv_sec * 1000000LL + ts.mTime.tv_nsec / 1000; const int64_t timestampAge = nowUs - numFramesPlayedAt; if (timestampAge > kStaleTimestamp100ms) { // This is an audio FIXME. // getTimestamp returns a timestamp which may come from audio // mixing threads. After pausing, the MixerThread may go idle, // thus the mTime estimate may become stale. Assuming that the // MixerThread runs 20ms, with FastMixer at 5ms, the max latency // should be about 25ms with an average around 12ms (to be // verified). For safety we use 100ms. ALOGV("getTimestamp: returned stale timestamp nowUs(%lld) " "numFramesPlayedAt(%lld)", (long long)nowUs, (long long)numFramesPlayedAt); numFramesPlayedAt = nowUs - kStaleTimestamp100ms; } //ALOGD("getTimestamp: OK %d %lld", // numFramesPlayed, (long long)numFramesPlayedAt); } else if (res == WOULD_BLOCK) { // case 2: transitory state on start of a new track numFramesPlayed = 0; numFramesPlayedAt = nowUs; //ALOGD("getTimestamp: WOULD_BLOCK %d %lld", // numFramesPlayed, (long long)numFramesPlayedAt); } else { // case 3: transitory at new track or audio fast tracks. res = mAudioTrack->getPosition(&numFramesPlayed); CHECK_EQ(res, (status_t)OK); numFramesPlayedAt = nowUs; numFramesPlayedAt += 1000LL * mAudioTrack->latency() / 2; /* XXX */ //ALOGD("getPosition: %d %lld", numFramesPlayed, (long long)numFramesPlayedAt); } //can't be negative until 12.4 hrs, test. //CHECK_EQ(numFramesPlayed & (1 << 31), 0); int64_t durationUs = getDurationIfPlayedAtNativeSampleRate_l(numFramesPlayed) + nowUs - numFramesPlayedAt; 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 numFramesPlayedAt is greater than nowUs by time more // than numFramesPlayed. // (2) In case 3, using getPosition and adding mAudioTrack->latency() // to numFramesPlayedAt, 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)numFramesPlayedAt); 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 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.mBuf); 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(bufferItem.mScalingMode), bufferItem.mTransform, bufferItem.mIsDroppable, 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 &output) { Mutex::Autolock lock(mMutex); if (output != mOutput) { return; // This is not the current output, ignore. } sp buffer; sp 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 &buffer, const sp &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 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 &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 &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 &/* who */) { Mutex::Autolock lock(mSync->mMutex); mSync->onAbandoned_l(true /* isInput */); } MediaSync::OutputListener::OutputListener(const sp &sync, const sp &output) : mSync(sync), mOutput(output) {} MediaSync::OutputListener::~OutputListener() {} void MediaSync::OutputListener::onBufferReleased() { mSync->onBufferReleasedByOutput(mOutput); } void MediaSync::OutputListener::binderDied(const wp &/* who */) { Mutex::Autolock lock(mSync->mMutex); mSync->onAbandoned_l(false /* isInput */); } } // namespace android