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 #include "prtime.h"
7 #include "mozilla/ProfilerLabels.h"
8 
9 #define LOG(args, ...)
10 
11 namespace mozilla {
12 
OggWriter()13 OggWriter::OggWriter()
14     : ContainerWriter(), mOggStreamState(), mOggPage(), mPacket() {
15   if (NS_FAILED(Init())) {
16     LOG("ERROR! Fail to initialize the OggWriter.");
17   }
18 }
19 
~OggWriter()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 
Init()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 
WriteEncodedTrack(const nsTArray<RefPtr<EncodedFrame>> & aData,uint32_t aFlags)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 
WriteEncodedData(const nsTArray<uint8_t> & aBuffer,int aDuration,uint32_t aFlags)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 
ProduceOggPage(nsTArray<nsTArray<uint8_t>> * aOutputBufs)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 
GetContainerData(nsTArray<nsTArray<uint8_t>> * aOutputBufs,uint32_t aFlags)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 
SetMetadata(const nsTArray<RefPtr<TrackMetadataBase>> & aMetadata)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
198