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