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