/* -*- 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 #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 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>* 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