summaryrefslogtreecommitdiffstats
path: root/dom/media/encoder/fmp4_muxer/ISOControl.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/encoder/fmp4_muxer/ISOControl.cpp')
-rw-r--r--dom/media/encoder/fmp4_muxer/ISOControl.cpp415
1 files changed, 415 insertions, 0 deletions
diff --git a/dom/media/encoder/fmp4_muxer/ISOControl.cpp b/dom/media/encoder/fmp4_muxer/ISOControl.cpp
new file mode 100644
index 000000000..6addaeb30
--- /dev/null
+++ b/dom/media/encoder/fmp4_muxer/ISOControl.cpp
@@ -0,0 +1,415 @@
+/* -*- 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 <time.h>
+#include "nsAutoPtr.h"
+#include "ISOControl.h"
+#include "ISOMediaBoxes.h"
+#include "EncodedFrameContainer.h"
+
+namespace mozilla {
+
+// For MP4 creation_time and modification_time offset from January 1, 1904 to
+// January 1, 1970.
+#define iso_time_offset 2082844800
+
+FragmentBuffer::FragmentBuffer(uint32_t aTrackType, uint32_t aFragDuration)
+ : mTrackType(aTrackType)
+ , mFragDuration(aFragDuration)
+ , mMediaStartTime(0)
+ , mFragmentNumber(0)
+ , mLastFrameTimeOfLastFragment(0)
+ , mEOS(false)
+{
+ mFragArray.AppendElement();
+ MOZ_COUNT_CTOR(FragmentBuffer);
+}
+
+FragmentBuffer::~FragmentBuffer()
+{
+ MOZ_COUNT_DTOR(FragmentBuffer);
+}
+
+bool
+FragmentBuffer::HasEnoughData()
+{
+ // Audio or video frame is enough to form a moof.
+ return (mFragArray.Length() > 1);
+}
+
+nsresult
+FragmentBuffer::GetCSD(nsTArray<uint8_t>& aCSD)
+{
+ if (!mCSDFrame) {
+ return NS_ERROR_FAILURE;
+ }
+ aCSD.AppendElements(mCSDFrame->GetFrameData().Elements(),
+ mCSDFrame->GetFrameData().Length());
+
+ return NS_OK;
+}
+
+nsresult
+FragmentBuffer::AddFrame(EncodedFrame* aFrame)
+{
+ // already EOS, it rejects all new data.
+ if (mEOS) {
+ MOZ_ASSERT(0);
+ return NS_OK;
+ }
+
+ EncodedFrame::FrameType type = aFrame->GetFrameType();
+ if (type == EncodedFrame::AAC_CSD || type == EncodedFrame::AVC_CSD ||
+ type == EncodedFrame::AMR_AUDIO_CSD || type == EncodedFrame::EVRC_AUDIO_CSD) {
+ mCSDFrame = aFrame;
+ // Use CSD's timestamp as the start time. Encoder should send CSD frame first
+ // and then data frames.
+ mMediaStartTime = aFrame->GetTimeStamp();
+ mFragmentNumber = 1;
+ return NS_OK;
+ }
+
+ // if the timestamp is incorrect, abort it.
+ if (aFrame->GetTimeStamp() < mMediaStartTime) {
+ MOZ_ASSERT(false);
+ return NS_ERROR_FAILURE;
+ }
+
+ mFragArray.LastElement().AppendElement(aFrame);
+
+ // check if current fragment is reach the fragment duration.
+ if ((aFrame->GetTimeStamp() - mMediaStartTime) >= (mFragDuration * mFragmentNumber)) {
+ mFragArray.AppendElement();
+ mFragmentNumber++;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+FragmentBuffer::GetFirstFragment(nsTArray<RefPtr<EncodedFrame>>& aFragment,
+ bool aFlush)
+{
+ // It should be called only if there is a complete fragment in mFragArray.
+ if (mFragArray.Length() <= 1 && !mEOS) {
+ MOZ_ASSERT(false);
+ return NS_ERROR_FAILURE;
+ }
+
+ if (aFlush) {
+ aFragment.SwapElements(mFragArray.ElementAt(0));
+ mFragArray.RemoveElementAt(0);
+ } else {
+ aFragment.AppendElements(mFragArray.ElementAt(0));
+ }
+ return NS_OK;
+}
+
+uint32_t
+FragmentBuffer::GetFirstFragmentSampleNumber()
+{
+ return mFragArray.ElementAt(0).Length();
+}
+
+uint32_t
+FragmentBuffer::GetFirstFragmentSampleSize()
+{
+ uint32_t size = 0;
+ uint32_t len = mFragArray.ElementAt(0).Length();
+ for (uint32_t i = 0; i < len; i++) {
+ size += mFragArray.ElementAt(0).ElementAt(i)->GetFrameData().Length();
+ }
+ return size;
+}
+
+ISOControl::ISOControl(uint32_t aMuxingType)
+ : mMuxingType(aMuxingType)
+ , mAudioFragmentBuffer(nullptr)
+ , mVideoFragmentBuffer(nullptr)
+ , mFragNum(0)
+ , mOutputSize(0)
+ , mBitCount(0)
+ , mBit(0)
+{
+ // Create a data array for first mp4 Box, ftyp.
+ mOutBuffers.SetLength(1);
+ MOZ_COUNT_CTOR(ISOControl);
+}
+
+ISOControl::~ISOControl()
+{
+ MOZ_COUNT_DTOR(ISOControl);
+}
+
+uint32_t
+ISOControl::GetNextTrackID()
+{
+ return (mMetaArray.Length() + 1);
+}
+
+uint32_t
+ISOControl::GetTrackID(TrackMetadataBase::MetadataKind aKind)
+{
+ for (uint32_t i = 0; i < mMetaArray.Length(); i++) {
+ if (mMetaArray[i]->GetKind() == aKind) {
+ return (i + 1);
+ }
+ }
+
+ // Track ID shouldn't be 0. It must be something wrong here.
+ MOZ_ASSERT(0);
+ return 0;
+}
+
+nsresult
+ISOControl::SetMetadata(TrackMetadataBase* aTrackMeta)
+{
+ if (aTrackMeta->GetKind() == TrackMetadataBase::METADATA_AAC ||
+ aTrackMeta->GetKind() == TrackMetadataBase::METADATA_AMR ||
+ aTrackMeta->GetKind() == TrackMetadataBase::METADATA_AVC ||
+ aTrackMeta->GetKind() == TrackMetadataBase::METADATA_EVRC) {
+ mMetaArray.AppendElement(aTrackMeta);
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+nsresult
+ISOControl::GetAudioMetadata(RefPtr<AudioTrackMetadata>& aAudMeta)
+{
+ for (uint32_t i = 0; i < mMetaArray.Length() ; i++) {
+ if (mMetaArray[i]->GetKind() == TrackMetadataBase::METADATA_AAC ||
+ mMetaArray[i]->GetKind() == TrackMetadataBase::METADATA_AMR ||
+ mMetaArray[i]->GetKind() == TrackMetadataBase::METADATA_EVRC) {
+ aAudMeta = static_cast<AudioTrackMetadata*>(mMetaArray[i].get());
+ return NS_OK;
+ }
+ }
+ return NS_ERROR_FAILURE;
+}
+
+nsresult
+ISOControl::GetVideoMetadata(RefPtr<VideoTrackMetadata>& aVidMeta)
+{
+ for (uint32_t i = 0; i < mMetaArray.Length() ; i++) {
+ if (mMetaArray[i]->GetKind() == TrackMetadataBase::METADATA_AVC) {
+ aVidMeta = static_cast<VideoTrackMetadata*>(mMetaArray[i].get());
+ return NS_OK;
+ }
+ }
+ return NS_ERROR_FAILURE;
+}
+
+bool
+ISOControl::HasAudioTrack()
+{
+ RefPtr<AudioTrackMetadata> audMeta;
+ GetAudioMetadata(audMeta);
+ return audMeta;
+}
+
+bool
+ISOControl::HasVideoTrack()
+{
+ RefPtr<VideoTrackMetadata> vidMeta;
+ GetVideoMetadata(vidMeta);
+ return vidMeta;
+}
+
+nsresult
+ISOControl::SetFragment(FragmentBuffer* aFragment)
+{
+ if (aFragment->GetType() == Audio_Track) {
+ mAudioFragmentBuffer = aFragment;
+ } else {
+ mVideoFragmentBuffer = aFragment;
+ }
+ return NS_OK;
+}
+
+FragmentBuffer*
+ISOControl::GetFragment(uint32_t aType)
+{
+ if (aType == Audio_Track) {
+ return mAudioFragmentBuffer;
+ } else if (aType == Video_Track){
+ return mVideoFragmentBuffer;
+ }
+ MOZ_ASSERT(0);
+ return nullptr;
+}
+
+nsresult
+ISOControl::GetBufs(nsTArray<nsTArray<uint8_t>>* aOutputBufs)
+{
+ uint32_t len = mOutBuffers.Length();
+ for (uint32_t i = 0; i < len; i++) {
+ mOutBuffers[i].SwapElements(*aOutputBufs->AppendElement());
+ }
+ return FlushBuf();
+}
+
+nsresult
+ISOControl::FlushBuf()
+{
+ mOutBuffers.SetLength(1);
+ return NS_OK;
+}
+
+uint32_t
+ISOControl::WriteAVData(nsTArray<uint8_t>& aArray)
+{
+ MOZ_ASSERT(!mBitCount);
+
+ uint32_t len = aArray.Length();
+ if (!len) {
+ return 0;
+ }
+
+ mOutputSize += len;
+
+ // The last element already has data, allocated a new element for pointer
+ // swapping.
+ if (mOutBuffers.LastElement().Length()) {
+ mOutBuffers.AppendElement();
+ }
+ // Swap the video/audio data pointer.
+ mOutBuffers.LastElement().SwapElements(aArray);
+ // Following data could be boxes, so appending a new uint8_t array here.
+ mOutBuffers.AppendElement();
+
+ return len;
+}
+
+uint32_t
+ISOControl::WriteBits(uint64_t aBits, size_t aNumBits)
+{
+ uint8_t output_byte = 0;
+
+ MOZ_ASSERT(aNumBits <= 64);
+ // TODO: rewritten following with bitset?
+ for (size_t i = aNumBits; i > 0; i--) {
+ mBit |= (((aBits >> (i - 1)) & 1) << (8 - ++mBitCount));
+ if (mBitCount == 8) {
+ Write(&mBit, sizeof(uint8_t));
+ mBit = 0;
+ mBitCount = 0;
+ output_byte++;
+ }
+ }
+ return output_byte;
+}
+
+uint32_t
+ISOControl::Write(uint8_t* aBuf, uint32_t aSize)
+{
+ mOutBuffers.LastElement().AppendElements(aBuf, aSize);
+ mOutputSize += aSize;
+ return aSize;
+}
+
+uint32_t
+ISOControl::Write(uint8_t aData)
+{
+ MOZ_ASSERT(!mBitCount);
+ Write((uint8_t*)&aData, sizeof(uint8_t));
+ return sizeof(uint8_t);
+}
+
+uint32_t
+ISOControl::GetBufPos()
+{
+ uint32_t len = mOutBuffers.Length();
+ uint32_t pos = 0;
+ for (uint32_t i = 0; i < len; i++) {
+ pos += mOutBuffers.ElementAt(i).Length();
+ }
+ return pos;
+}
+
+uint32_t
+ISOControl::WriteFourCC(const char* aType)
+{
+ // Bit operation should be aligned to byte before writing any byte data.
+ MOZ_ASSERT(!mBitCount);
+
+ uint32_t size = strlen(aType);
+ if (size == 4) {
+ return Write((uint8_t*)aType, size);
+ }
+
+ return 0;
+}
+
+nsresult
+ISOControl::GenerateFtyp()
+{
+ nsresult rv;
+ uint32_t size;
+ nsAutoPtr<FileTypeBox> type_box(new FileTypeBox(this));
+ rv = type_box->Generate(&size);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = type_box->Write();
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+}
+
+nsresult
+ISOControl::GenerateMoov()
+{
+ nsresult rv;
+ uint32_t size;
+ nsAutoPtr<MovieBox> moov_box(new MovieBox(this));
+ rv = moov_box->Generate(&size);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = moov_box->Write();
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+}
+
+nsresult
+ISOControl::GenerateMoof(uint32_t aTrackType)
+{
+ mFragNum++;
+
+ nsresult rv;
+ uint32_t size;
+ uint64_t first_sample_offset = mOutputSize;
+ nsAutoPtr<MovieFragmentBox> moof_box(new MovieFragmentBox(aTrackType, this));
+ nsAutoPtr<MediaDataBox> mdat_box(new MediaDataBox(aTrackType, this));
+
+ rv = moof_box->Generate(&size);
+ NS_ENSURE_SUCCESS(rv, rv);
+ first_sample_offset += size;
+ rv = mdat_box->Generate(&size);
+ NS_ENSURE_SUCCESS(rv, rv);
+ first_sample_offset += mdat_box->FirstSampleOffsetInMediaDataBox();
+
+ // correct offset info
+ nsTArray<RefPtr<MuxerOperation>> tfhds;
+ rv = moof_box->Find(NS_LITERAL_CSTRING("tfhd"), tfhds);
+ NS_ENSURE_SUCCESS(rv, rv);
+ uint32_t len = tfhds.Length();
+ for (uint32_t i = 0; i < len; i++) {
+ TrackFragmentHeaderBox* tfhd = (TrackFragmentHeaderBox*) tfhds.ElementAt(i).get();
+ rv = tfhd->UpdateBaseDataOffset(first_sample_offset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = moof_box->Write();
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mdat_box->Write();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+uint32_t
+ISOControl::GetTime()
+{
+ return (uint64_t)time(nullptr) + iso_time_offset;
+}
+
+}