diff options
Diffstat (limited to 'dom/media/encoder/fmp4_muxer/ISOMediaWriter.cpp')
-rw-r--r-- | dom/media/encoder/fmp4_muxer/ISOMediaWriter.cpp | 234 |
1 files changed, 234 insertions, 0 deletions
diff --git a/dom/media/encoder/fmp4_muxer/ISOMediaWriter.cpp b/dom/media/encoder/fmp4_muxer/ISOMediaWriter.cpp new file mode 100644 index 000000000..fa23616e9 --- /dev/null +++ b/dom/media/encoder/fmp4_muxer/ISOMediaWriter.cpp @@ -0,0 +1,234 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/ +/* 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 "ISOMediaWriter.h" +#include "ISOControl.h" +#include "ISOMediaBoxes.h" +#include "ISOTrackMetadata.h" +#include "nsThreadUtils.h" +#include "MediaEncoder.h" +#include "VideoUtils.h" +#include "GeckoProfiler.h" + +#undef LOG +#ifdef MOZ_WIDGET_GONK +#include <android/log.h> +#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "MediaEncoder", ## args); +#else +#define LOG(args, ...) +#endif + +namespace mozilla { + +const static uint32_t FRAG_DURATION = 2 * USECS_PER_S; // microsecond per unit + +ISOMediaWriter::ISOMediaWriter(uint32_t aType, uint32_t aHint) + : ContainerWriter() + , mState(MUXING_HEAD) + , mBlobReady(false) + , mType(0) +{ + if (aType & CREATE_AUDIO_TRACK) { + mType |= Audio_Track; + } + if (aType & CREATE_VIDEO_TRACK) { + mType |= Video_Track; + } + mControl = new ISOControl(aHint); + MOZ_COUNT_CTOR(ISOMediaWriter); +} + +ISOMediaWriter::~ISOMediaWriter() +{ + MOZ_COUNT_DTOR(ISOMediaWriter); +} + +nsresult +ISOMediaWriter::RunState() +{ + nsresult rv; + switch (mState) { + case MUXING_HEAD: + { + rv = mControl->GenerateFtyp(); + NS_ENSURE_SUCCESS(rv, rv); + rv = mControl->GenerateMoov(); + NS_ENSURE_SUCCESS(rv, rv); + mState = MUXING_FRAG; + break; + } + case MUXING_FRAG: + { + rv = mControl->GenerateMoof(mType); + NS_ENSURE_SUCCESS(rv, rv); + + bool EOS; + if (ReadyToRunState(EOS) && EOS) { + mState = MUXING_DONE; + } + break; + } + case MUXING_DONE: + { + break; + } + } + mBlobReady = true; + return NS_OK; +} + +nsresult +ISOMediaWriter::WriteEncodedTrack(const EncodedFrameContainer& aData, + uint32_t aFlags) +{ + PROFILER_LABEL("ISOMediaWriter", "WriteEncodedTrack", + js::ProfileEntry::Category::OTHER); + // Muxing complete, it doesn't allowed to reentry again. + if (mState == MUXING_DONE) { + MOZ_ASSERT(false); + return NS_ERROR_FAILURE; + } + + FragmentBuffer* frag = nullptr; + uint32_t len = aData.GetEncodedFrames().Length(); + + if (!len) { + // no frame? why bother to WriteEncodedTrack + return NS_OK; + } + for (uint32_t i = 0; i < len; i++) { + RefPtr<EncodedFrame> frame(aData.GetEncodedFrames()[i]); + EncodedFrame::FrameType type = frame->GetFrameType(); + if (type == EncodedFrame::AAC_AUDIO_FRAME || + type == EncodedFrame::AAC_CSD || + type == EncodedFrame::AMR_AUDIO_FRAME || + type == EncodedFrame::AMR_AUDIO_CSD || + type == EncodedFrame::EVRC_AUDIO_FRAME || + type == EncodedFrame::EVRC_AUDIO_CSD) { + frag = mAudioFragmentBuffer; + } else if (type == EncodedFrame::AVC_I_FRAME || + type == EncodedFrame::AVC_P_FRAME || + type == EncodedFrame::AVC_B_FRAME || + type == EncodedFrame::AVC_CSD) { + frag = mVideoFragmentBuffer; + } else { + MOZ_ASSERT(0); + return NS_ERROR_FAILURE; + } + + frag->AddFrame(frame); + } + + // Encoder should send CSD (codec specific data) frame before sending the + // audio/video frames. When CSD data is ready, it is sufficient to generate a + // moov data. If encoder doesn't send CSD yet, muxer needs to wait before + // generating anything. + if (mType & Audio_Track && (!mAudioFragmentBuffer || + !mAudioFragmentBuffer->HasCSD())) { + return NS_OK; + } + if (mType & Video_Track && (!mVideoFragmentBuffer || + !mVideoFragmentBuffer->HasCSD())) { + return NS_OK; + } + + // Only one FrameType in EncodedFrameContainer so it doesn't need to be + // inside the for-loop. + if (frag && (aFlags & END_OF_STREAM)) { + frag->SetEndOfStream(); + } + + nsresult rv; + bool EOS; + if (ReadyToRunState(EOS)) { + // Because track encoder won't generate new data after EOS, it needs to make + // sure the state reaches MUXING_DONE when EOS is signaled. + do { + rv = RunState(); + } while (EOS && mState != MUXING_DONE); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +bool +ISOMediaWriter::ReadyToRunState(bool& aEOS) +{ + aEOS = false; + bool bReadyToMux = true; + if ((mType & Audio_Track) && (mType & Video_Track)) { + if (!mAudioFragmentBuffer->HasEnoughData()) { + bReadyToMux = false; + } + if (!mVideoFragmentBuffer->HasEnoughData()) { + bReadyToMux = false; + } + + if (mAudioFragmentBuffer->EOS() && mVideoFragmentBuffer->EOS()) { + aEOS = true; + bReadyToMux = true; + } + } else if (mType == Audio_Track) { + if (!mAudioFragmentBuffer->HasEnoughData()) { + bReadyToMux = false; + } + if (mAudioFragmentBuffer->EOS()) { + aEOS = true; + bReadyToMux = true; + } + } else if (mType == Video_Track) { + if (!mVideoFragmentBuffer->HasEnoughData()) { + bReadyToMux = false; + } + if (mVideoFragmentBuffer->EOS()) { + aEOS = true; + bReadyToMux = true; + } + } + + return bReadyToMux; +} + +nsresult +ISOMediaWriter::GetContainerData(nsTArray<nsTArray<uint8_t>>* aOutputBufs, + uint32_t aFlags) +{ + PROFILER_LABEL("ISOMediaWriter", "GetContainerData", + js::ProfileEntry::Category::OTHER); + if (mBlobReady) { + if (mState == MUXING_DONE) { + mIsWritingComplete = true; + } + mBlobReady = false; + return mControl->GetBufs(aOutputBufs); + } + return NS_OK; +} + +nsresult +ISOMediaWriter::SetMetadata(TrackMetadataBase* aMetadata) +{ + PROFILER_LABEL("ISOMediaWriter", "SetMetadata", + js::ProfileEntry::Category::OTHER); + if (aMetadata->GetKind() == TrackMetadataBase::METADATA_AAC || + aMetadata->GetKind() == TrackMetadataBase::METADATA_AMR || + aMetadata->GetKind() == TrackMetadataBase::METADATA_EVRC) { + mControl->SetMetadata(aMetadata); + mAudioFragmentBuffer = new FragmentBuffer(Audio_Track, FRAG_DURATION); + mControl->SetFragment(mAudioFragmentBuffer); + return NS_OK; + } + if (aMetadata->GetKind() == TrackMetadataBase::METADATA_AVC) { + mControl->SetMetadata(aMetadata); + mVideoFragmentBuffer = new FragmentBuffer(Video_Track, FRAG_DURATION); + mControl->SetFragment(mVideoFragmentBuffer); + return NS_OK; + } + + return NS_ERROR_FAILURE; +} + +} // namespace mozilla |