OggWriter.cpp (6310B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/ 2 /* This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this file, 4 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 #include "OggWriter.h" 6 7 #include "mozilla/ProfilerLabels.h" 8 #include "prtime.h" 9 10 #define LOG(args, ...) 11 12 namespace mozilla { 13 14 OggWriter::OggWriter() : mOggStreamState(), mOggPage(), mPacket() { 15 if (NS_FAILED(Init())) { 16 LOG("ERROR! Fail to initialize the OggWriter."); 17 } 18 } 19 20 OggWriter::~OggWriter() { 21 if (mInitialized) { 22 ogg_stream_clear(&mOggStreamState); 23 } 24 // mPacket's data was always owned by us, no need to ogg_packet_clear. 25 } 26 27 nsresult OggWriter::Init() { 28 MOZ_ASSERT(!mInitialized); 29 30 // The serial number (serialno) should be a random number, for the current 31 // implementation where the output file contains only a single stream, this 32 // serialno is used to differentiate between files. 33 srand(static_cast<unsigned>(PR_Now())); 34 int rc = ogg_stream_init(&mOggStreamState, rand()); 35 36 mPacket.b_o_s = 1; 37 mPacket.e_o_s = 0; 38 mPacket.granulepos = 0; 39 mPacket.packet = nullptr; 40 mPacket.packetno = 0; 41 mPacket.bytes = 0; 42 43 mInitialized = (rc == 0); 44 45 return (rc == 0) ? NS_OK : NS_ERROR_NOT_INITIALIZED; 46 } 47 48 nsresult OggWriter::WriteEncodedTrack( 49 const nsTArray<RefPtr<EncodedFrame>>& aData, uint32_t aFlags) { 50 AUTO_PROFILER_LABEL("OggWriter::WriteEncodedTrack", OTHER); 51 52 uint32_t len = aData.Length(); 53 for (uint32_t i = 0; i < len; i++) { 54 if (aData[i]->mFrameType != EncodedFrame::OPUS_AUDIO_FRAME) { 55 LOG("[OggWriter] wrong encoded data type!"); 56 return NS_ERROR_FAILURE; 57 } 58 59 // only pass END_OF_STREAM on the last frame! 60 nsresult rv = WriteEncodedData( 61 *aData[i]->mFrameData, aData[i]->mDuration, 62 i < len - 1 ? (aFlags & ~ContainerWriter::END_OF_STREAM) : aFlags); 63 if (NS_FAILED(rv)) { 64 LOG("%p Failed to WriteEncodedTrack!", this); 65 return rv; 66 } 67 } 68 return NS_OK; 69 } 70 71 nsresult OggWriter::WriteEncodedData(const nsTArray<uint8_t>& aBuffer, 72 int aDuration, uint32_t aFlags) { 73 if (!mInitialized) { 74 LOG("[OggWriter] OggWriter has not initialized!"); 75 return NS_ERROR_FAILURE; 76 } 77 78 MOZ_ASSERT(!ogg_stream_eos(&mOggStreamState), 79 "No data can be written after eos has marked."); 80 81 // Set eos flag to true, and once the eos is written to a packet, there must 82 // not be anymore pages after a page has marked as eos. 83 if (aFlags & ContainerWriter::END_OF_STREAM) { 84 LOG("[OggWriter] Set e_o_s flag to true."); 85 mPacket.e_o_s = 1; 86 } 87 88 mPacket.packet = const_cast<uint8_t*>(aBuffer.Elements()); 89 mPacket.bytes = aBuffer.Length(); 90 mPacket.granulepos += aDuration; 91 92 // 0 returned on success. -1 returned in the event of internal error. 93 // The data in the packet is copied into the internal storage managed by the 94 // mOggStreamState, so we are free to alter the contents of mPacket after 95 // this call has returned. 96 int rc = ogg_stream_packetin(&mOggStreamState, &mPacket); 97 if (rc < 0) { 98 LOG("[OggWriter] Failed in ogg_stream_packetin! (%d).", rc); 99 return NS_ERROR_FAILURE; 100 } 101 102 if (mPacket.b_o_s) { 103 mPacket.b_o_s = 0; 104 } 105 mPacket.packetno++; 106 mPacket.packet = nullptr; 107 108 return NS_OK; 109 } 110 111 void OggWriter::ProduceOggPage(nsTArray<nsTArray<uint8_t>>* aOutputBufs) { 112 aOutputBufs->AppendElement(); 113 aOutputBufs->LastElement().SetLength(mOggPage.header_len + mOggPage.body_len); 114 memcpy(aOutputBufs->LastElement().Elements(), mOggPage.header, 115 mOggPage.header_len); 116 memcpy(aOutputBufs->LastElement().Elements() + mOggPage.header_len, 117 mOggPage.body, mOggPage.body_len); 118 } 119 120 nsresult OggWriter::GetContainerData(nsTArray<nsTArray<uint8_t>>* aOutputBufs, 121 uint32_t aFlags) { 122 int rc = -1; 123 AUTO_PROFILER_LABEL("OggWriter::GetContainerData", OTHER); 124 // Generate the oggOpus Header 125 if (aFlags & ContainerWriter::GET_HEADER) { 126 OpusMetadata* meta = static_cast<OpusMetadata*>(mMetadata.get()); 127 NS_ASSERTION(meta, "should have meta data"); 128 NS_ASSERTION(meta->GetKind() == TrackMetadataBase::METADATA_OPUS, 129 "should have Opus meta data"); 130 131 nsresult rv = WriteEncodedData(meta->mIdHeader, 0); 132 NS_ENSURE_SUCCESS(rv, rv); 133 134 rc = ogg_stream_flush(&mOggStreamState, &mOggPage); 135 NS_ENSURE_TRUE(rc > 0, NS_ERROR_FAILURE); 136 ProduceOggPage(aOutputBufs); 137 138 rv = WriteEncodedData(meta->mCommentHeader, 0); 139 NS_ENSURE_SUCCESS(rv, rv); 140 141 rc = ogg_stream_flush(&mOggStreamState, &mOggPage); 142 NS_ENSURE_TRUE(rc > 0, NS_ERROR_FAILURE); 143 144 // Force generate a page even if the amount of packet data is not enough. 145 // Usually do so after a header packet. 146 147 ProduceOggPage(aOutputBufs); 148 } 149 150 // return value 0 means insufficient data has accumulated to fill a page, or 151 // an internal error has occurred. 152 while (ogg_stream_pageout(&mOggStreamState, &mOggPage) != 0) { 153 ProduceOggPage(aOutputBufs); 154 } 155 156 if (aFlags & ContainerWriter::FLUSH_NEEDED) { 157 // return value 0 means no packet to put into a page, or an internal error. 158 if (ogg_stream_flush(&mOggStreamState, &mOggPage) != 0) { 159 ProduceOggPage(aOutputBufs); 160 } 161 mIsWritingComplete = true; 162 } 163 164 // We always return NS_OK here since it's OK to call this without having 165 // enough data to fill a page. It's the more common case compared to internal 166 // errors, and we cannot distinguish the two. 167 return NS_OK; 168 } 169 170 nsresult OggWriter::SetMetadata( 171 const nsTArray<RefPtr<TrackMetadataBase>>& aMetadata) { 172 MOZ_ASSERT(aMetadata.Length() == 1); 173 MOZ_ASSERT(aMetadata[0]); 174 175 AUTO_PROFILER_LABEL("OggWriter::SetMetadata", OTHER); 176 177 if (aMetadata[0]->GetKind() != TrackMetadataBase::METADATA_OPUS) { 178 LOG("wrong meta data type!"); 179 return NS_ERROR_FAILURE; 180 } 181 // Validate each field of METADATA 182 mMetadata = static_cast<OpusMetadata*>(aMetadata[0].get()); 183 if (mMetadata->mIdHeader.Length() == 0) { 184 LOG("miss mIdHeader!"); 185 return NS_ERROR_FAILURE; 186 } 187 if (mMetadata->mCommentHeader.Length() == 0) { 188 LOG("miss mCommentHeader!"); 189 return NS_ERROR_FAILURE; 190 } 191 192 return NS_OK; 193 } 194 195 } // namespace mozilla 196 197 #undef LOG