michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "ISOMediaWriter.h" michael@0: #include "ISOControl.h" michael@0: #include "ISOMediaBoxes.h" michael@0: #include "ISOTrackMetadata.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "MediaEncoder.h" michael@0: #include "VideoUtils.h" michael@0: michael@0: #undef LOG michael@0: #ifdef MOZ_WIDGET_GONK michael@0: #include michael@0: #define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "MediaEncoder", ## args); michael@0: #else michael@0: #define LOG(args, ...) michael@0: #endif michael@0: michael@0: namespace mozilla { michael@0: michael@0: const static uint32_t FRAG_DURATION = 2 * USECS_PER_S; // microsecond per unit michael@0: michael@0: ISOMediaWriter::ISOMediaWriter(uint32_t aType, uint32_t aHint) michael@0: : ContainerWriter() michael@0: , mState(MUXING_HEAD) michael@0: , mBlobReady(false) michael@0: , mType(0) michael@0: { michael@0: if (aType & CREATE_AUDIO_TRACK) { michael@0: mType |= Audio_Track; michael@0: } michael@0: if (aType & CREATE_VIDEO_TRACK) { michael@0: mType |= Video_Track; michael@0: } michael@0: mControl = new ISOControl(aHint); michael@0: MOZ_COUNT_CTOR(ISOMediaWriter); michael@0: } michael@0: michael@0: ISOMediaWriter::~ISOMediaWriter() michael@0: { michael@0: MOZ_COUNT_DTOR(ISOMediaWriter); michael@0: } michael@0: michael@0: nsresult michael@0: ISOMediaWriter::RunState() michael@0: { michael@0: nsresult rv; michael@0: switch (mState) { michael@0: case MUXING_HEAD: michael@0: { michael@0: rv = mControl->GenerateFtyp(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = mControl->GenerateMoov(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: mState = MUXING_FRAG; michael@0: break; michael@0: } michael@0: case MUXING_FRAG: michael@0: { michael@0: rv = mControl->GenerateMoof(mType); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: bool EOS; michael@0: if (ReadyToRunState(EOS) && EOS) { michael@0: mState = MUXING_DONE; michael@0: } michael@0: break; michael@0: } michael@0: case MUXING_DONE: michael@0: { michael@0: break; michael@0: } michael@0: } michael@0: mBlobReady = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: ISOMediaWriter::WriteEncodedTrack(const EncodedFrameContainer& aData, michael@0: uint32_t aFlags) michael@0: { michael@0: // Muxing complete, it doesn't allowed to reentry again. michael@0: if (mState == MUXING_DONE) { michael@0: MOZ_ASSERT(false); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: FragmentBuffer* frag = nullptr; michael@0: uint32_t len = aData.GetEncodedFrames().Length(); michael@0: michael@0: if (!len) { michael@0: // no frame? why bother to WriteEncodedTrack michael@0: return NS_OK; michael@0: } michael@0: for (uint32_t i = 0; i < len; i++) { michael@0: nsRefPtr frame(aData.GetEncodedFrames()[i]); michael@0: EncodedFrame::FrameType type = frame->GetFrameType(); michael@0: if (type == EncodedFrame::AAC_AUDIO_FRAME || michael@0: type == EncodedFrame::AAC_CSD || michael@0: type == EncodedFrame::AMR_AUDIO_FRAME || michael@0: type == EncodedFrame::AMR_AUDIO_CSD) { michael@0: frag = mAudioFragmentBuffer; michael@0: } else if (type == EncodedFrame::AVC_I_FRAME || michael@0: type == EncodedFrame::AVC_P_FRAME || michael@0: type == EncodedFrame::AVC_B_FRAME || michael@0: type == EncodedFrame::AVC_CSD) { michael@0: frag = mVideoFragmentBuffer; michael@0: } else { michael@0: MOZ_ASSERT(0); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: frag->AddFrame(frame); michael@0: } michael@0: michael@0: // Encoder should send CSD (codec specific data) frame before sending the michael@0: // audio/video frames. When CSD data is ready, it is sufficient to generate a michael@0: // moov data. If encoder doesn't send CSD yet, muxer needs to wait before michael@0: // generating anything. michael@0: if (mType & Audio_Track && (!mAudioFragmentBuffer || michael@0: !mAudioFragmentBuffer->HasCSD())) { michael@0: return NS_OK; michael@0: } michael@0: if (mType & Video_Track && (!mVideoFragmentBuffer || michael@0: !mVideoFragmentBuffer->HasCSD())) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Only one FrameType in EncodedFrameContainer so it doesn't need to be michael@0: // inside the for-loop. michael@0: if (frag && (aFlags & END_OF_STREAM)) { michael@0: frag->SetEndOfStream(); michael@0: } michael@0: michael@0: nsresult rv; michael@0: bool EOS; michael@0: if (ReadyToRunState(EOS)) { michael@0: // TODO: michael@0: // The MediaEncoder doesn't use nsRunnable, so thread will be michael@0: // stocked on that part and the new added nsRunnable won't get to run michael@0: // before MediaEncoder completing. Before MediaEncoder change, it needs michael@0: // to call RunState directly. michael@0: // https://bugzilla.mozilla.org/show_bug.cgi?id=950429 michael@0: rv = RunState(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: ISOMediaWriter::ReadyToRunState(bool& aEOS) michael@0: { michael@0: aEOS = false; michael@0: bool bReadyToMux = true; michael@0: if ((mType & Audio_Track) && (mType & Video_Track)) { michael@0: if (!mAudioFragmentBuffer->HasEnoughData()) { michael@0: bReadyToMux = false; michael@0: } michael@0: if (!mVideoFragmentBuffer->HasEnoughData()) { michael@0: bReadyToMux = false; michael@0: } michael@0: michael@0: if (mAudioFragmentBuffer->EOS() && mVideoFragmentBuffer->EOS()) { michael@0: aEOS = true; michael@0: bReadyToMux = true; michael@0: } michael@0: } else if (mType == Audio_Track) { michael@0: if (!mAudioFragmentBuffer->HasEnoughData()) { michael@0: bReadyToMux = false; michael@0: } michael@0: if (mAudioFragmentBuffer->EOS()) { michael@0: aEOS = true; michael@0: bReadyToMux = true; michael@0: } michael@0: } else if (mType == Video_Track) { michael@0: if (!mVideoFragmentBuffer->HasEnoughData()) { michael@0: bReadyToMux = false; michael@0: } michael@0: if (mVideoFragmentBuffer->EOS()) { michael@0: aEOS = true; michael@0: bReadyToMux = true; michael@0: } michael@0: } michael@0: michael@0: return bReadyToMux; michael@0: } michael@0: michael@0: nsresult michael@0: ISOMediaWriter::GetContainerData(nsTArray>* aOutputBufs, michael@0: uint32_t aFlags) michael@0: { michael@0: if (mBlobReady) { michael@0: if (mState == MUXING_DONE) { michael@0: mIsWritingComplete = true; michael@0: } michael@0: mBlobReady = false; michael@0: return mControl->GetBufs(aOutputBufs); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: ISOMediaWriter::SetMetadata(TrackMetadataBase* aMetadata) michael@0: { michael@0: if (aMetadata->GetKind() == TrackMetadataBase::METADATA_AAC || michael@0: aMetadata->GetKind() == TrackMetadataBase::METADATA_AMR) { michael@0: mControl->SetMetadata(aMetadata); michael@0: mAudioFragmentBuffer = new FragmentBuffer(Audio_Track, FRAG_DURATION); michael@0: mControl->SetFragment(mAudioFragmentBuffer); michael@0: return NS_OK; michael@0: } michael@0: if (aMetadata->GetKind() == TrackMetadataBase::METADATA_AVC) { michael@0: mControl->SetMetadata(aMetadata); michael@0: mVideoFragmentBuffer = new FragmentBuffer(Video_Track, FRAG_DURATION); michael@0: mControl->SetFragment(mVideoFragmentBuffer); michael@0: return NS_OK; michael@0: } michael@0: michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: } // namespace mozilla