/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim:set ts=2 sw=2 sts=2 et cindent: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "GonkMediaDataDecoder.h" #include "VideoUtils.h" #include "nsTArray.h" #include "MediaCodecProxy.h" #include #include "mozilla/Logging.h" #include #define GMDD_LOG(...) __android_log_print(ANDROID_LOG_DEBUG, "GonkMediaDataDecoder", __VA_ARGS__) #define INPUT_TIMEOUT_US 0LL // Don't wait for buffer if none is available. #define MIN_QUEUED_SAMPLES 2 #ifdef DEBUG #include #endif #define LOG(...) MOZ_LOG(sPDMLog, mozilla::LogLevel::Debug, (__VA_ARGS__)) using namespace android; namespace mozilla { bool GonkDecoderManager::InitLoopers(MediaData::Type aType) { MOZ_ASSERT(mDecodeLooper.get() == nullptr && mTaskLooper.get() == nullptr); MOZ_ASSERT(aType == MediaData::VIDEO_DATA || aType == MediaData::AUDIO_DATA); const char* suffix = (aType == MediaData::VIDEO_DATA ? "video" : "audio"); mDecodeLooper = new ALooper; android::AString name("MediaCodecProxy/"); name.append(suffix); mDecodeLooper->setName(name.c_str()); mTaskLooper = new ALooper; name.setTo("GonkDecoderManager/"); name.append(suffix); mTaskLooper->setName(name.c_str()); mTaskLooper->registerHandler(this); #ifdef DEBUG sp findThreadId(new AMessage(kNotifyFindLooperId, id())); findThreadId->post(); #endif return mDecodeLooper->start() == OK && mTaskLooper->start() == OK; } nsresult GonkDecoderManager::Input(MediaRawData* aSample) { RefPtr sample; if (aSample) { sample = aSample; } else { // It means EOS with empty sample. sample = new MediaRawData(); } { MutexAutoLock lock(mMutex); mQueuedSamples.AppendElement(sample); } sp input = new AMessage(kNotifyProcessInput, id()); if (!aSample) { input->setInt32("input-eos", 1); } input->post(); return NS_OK; } int32_t GonkDecoderManager::ProcessQueuedSamples() { MOZ_ASSERT(OnTaskLooper()); MutexAutoLock lock(mMutex); status_t rv; while (mQueuedSamples.Length()) { RefPtr data = mQueuedSamples.ElementAt(0); rv = mDecoder->Input(reinterpret_cast(data->Data()), data->Size(), data->mTime, 0, INPUT_TIMEOUT_US); if (rv == OK) { mQueuedSamples.RemoveElementAt(0); mWaitOutput.AppendElement(WaitOutputInfo(data->mOffset, data->mTime, /* eos */ data->Data() == nullptr)); } else if (rv == -EAGAIN || rv == -ETIMEDOUT) { // In most cases, EAGAIN or ETIMEOUT are safe because OMX can't fill // buffer on time. break; } else { return rv; } } return mQueuedSamples.Length(); } nsresult GonkDecoderManager::Flush() { if (mDecoder == nullptr) { GMDD_LOG("Decoder is not initialized"); return NS_ERROR_UNEXPECTED; } if (!mInitPromise.IsEmpty()) { return NS_OK; } { MutexAutoLock lock(mMutex); mQueuedSamples.Clear(); } MonitorAutoLock lock(mFlushMonitor); mIsFlushing = true; sp flush = new AMessage(kNotifyProcessFlush, id()); flush->post(); while (mIsFlushing) { lock.Wait(); } return NS_OK; } nsresult GonkDecoderManager::Shutdown() { if (mDecoder.get()) { mDecoder->stop(); mDecoder->ReleaseMediaResources(); mDecoder = nullptr; } mInitPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__); return NS_OK; } size_t GonkDecoderManager::NumQueuedSamples() { MutexAutoLock lock(mMutex); return mQueuedSamples.Length(); } void GonkDecoderManager::ProcessInput(bool aEndOfStream) { MOZ_ASSERT(OnTaskLooper()); status_t rv = ProcessQueuedSamples(); if (rv >= 0) { if (!aEndOfStream && rv <= MIN_QUEUED_SAMPLES) { mDecodeCallback->InputExhausted(); } if (mToDo.get() == nullptr) { mToDo = new AMessage(kNotifyDecoderActivity, id()); if (aEndOfStream) { mToDo->setInt32("input-eos", 1); } mDecoder->requestActivityNotification(mToDo); } else if (aEndOfStream) { mToDo->setInt32("input-eos", 1); } } else { GMDD_LOG("input processed: error#%d", rv); mDecodeCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__)); } } void GonkDecoderManager::ProcessFlush() { MOZ_ASSERT(OnTaskLooper()); mLastTime = INT64_MIN; MonitorAutoLock lock(mFlushMonitor); mWaitOutput.Clear(); if (mDecoder->flush() != OK) { GMDD_LOG("flush error"); mDecodeCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__)); } mIsFlushing = false; lock.NotifyAll(); } // Use output timestamp to determine which output buffer is already returned // and remove corresponding info, except for EOS, from the waiting list. // This method handles the cases that audio decoder sends multiple output // buffers for one input. void GonkDecoderManager::UpdateWaitingList(int64_t aForgetUpTo) { MOZ_ASSERT(OnTaskLooper()); size_t i; for (i = 0; i < mWaitOutput.Length(); i++) { const auto& item = mWaitOutput.ElementAt(i); if (item.mEOS || item.mTimestamp > aForgetUpTo) { break; } } if (i > 0) { mWaitOutput.RemoveElementsAt(0, i); } } void GonkDecoderManager::ProcessToDo(bool aEndOfStream) { MOZ_ASSERT(OnTaskLooper()); MOZ_ASSERT(mToDo.get() != nullptr); mToDo.clear(); if (NumQueuedSamples() > 0 && ProcessQueuedSamples() < 0) { mDecodeCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__)); return; } while (mWaitOutput.Length() > 0) { RefPtr output; WaitOutputInfo wait = mWaitOutput.ElementAt(0); nsresult rv = Output(wait.mOffset, output); if (rv == NS_OK) { MOZ_ASSERT(output); mDecodeCallback->Output(output); UpdateWaitingList(output->mTime); } else if (rv == NS_ERROR_ABORT) { // EOS MOZ_ASSERT(mQueuedSamples.IsEmpty()); if (output) { mDecodeCallback->Output(output); UpdateWaitingList(output->mTime); } MOZ_ASSERT(mWaitOutput.Length() == 1); mWaitOutput.RemoveElementAt(0); mDecodeCallback->DrainComplete(); ResetEOS(); return; } else if (rv == NS_ERROR_NOT_AVAILABLE) { break; } else { mDecodeCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__)); return; } } if (!aEndOfStream && NumQueuedSamples() <= MIN_QUEUED_SAMPLES) { mDecodeCallback->InputExhausted(); // No need to shedule todo task this time because InputExhausted() will // cause Input() to be invoked and do it for us. return; } if (NumQueuedSamples() || mWaitOutput.Length() > 0) { mToDo = new AMessage(kNotifyDecoderActivity, id()); if (aEndOfStream) { mToDo->setInt32("input-eos", 1); } mDecoder->requestActivityNotification(mToDo); } } void GonkDecoderManager::ResetEOS() { // After eos, android::MediaCodec needs to be flushed to receive next input mWaitOutput.Clear(); if (mDecoder->flush() != OK) { GMDD_LOG("flush error"); mDecodeCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__)); } } void GonkDecoderManager::onMessageReceived(const sp &aMessage) { switch (aMessage->what()) { case kNotifyProcessInput: { int32_t eos = 0; ProcessInput(aMessage->findInt32("input-eos", &eos) && eos); break; } case kNotifyProcessFlush: { ProcessFlush(); break; } case kNotifyDecoderActivity: { int32_t eos = 0; ProcessToDo(aMessage->findInt32("input-eos", &eos) && eos); break; } #ifdef DEBUG case kNotifyFindLooperId: { mTaskLooperId = androidGetThreadId(); MOZ_ASSERT(mTaskLooperId); break; } #endif default: { TRESPASS(); break; } } } #ifdef DEBUG bool GonkDecoderManager::OnTaskLooper() { return androidGetThreadId() == mTaskLooperId; } #endif GonkMediaDataDecoder::GonkMediaDataDecoder(GonkDecoderManager* aManager, MediaDataDecoderCallback* aCallback) : mManager(aManager) { MOZ_COUNT_CTOR(GonkMediaDataDecoder); mManager->SetDecodeCallback(aCallback); } GonkMediaDataDecoder::~GonkMediaDataDecoder() { MOZ_COUNT_DTOR(GonkMediaDataDecoder); } RefPtr GonkMediaDataDecoder::Init() { return mManager->Init(); } void GonkMediaDataDecoder::Shutdown() { mManager->Shutdown(); // Because codec allocated runnable and init promise is at reader TaskQueue, // so manager needs to be destroyed at reader TaskQueue to prevent racing. mManager = nullptr; } // Inserts data into the decoder's pipeline. void GonkMediaDataDecoder::Input(MediaRawData* aSample) { mManager->Input(aSample); } void GonkMediaDataDecoder::Flush() { mManager->Flush(); } void GonkMediaDataDecoder::Drain() { mManager->Input(nullptr); } } // namespace mozilla