|
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 "VorbisTrackEncoder.h" |
|
6 #include <ogg/ogg.h> |
|
7 #include <vorbis/vorbisenc.h> |
|
8 #include "WebMWriter.h" |
|
9 |
|
10 // One actually used: Encoding using a VBR quality mode. The usable range is -.1 |
|
11 // (lowest quality, smallest file) to 1. (highest quality, largest file). |
|
12 // Example quality mode .4: 44kHz stereo coupled, roughly 128kbps VBR |
|
13 // ret = vorbis_encode_init_vbr(&vi,2,44100,.4); |
|
14 static const float BASE_QUALITY = 0.4f; |
|
15 |
|
16 namespace mozilla { |
|
17 |
|
18 #undef LOG |
|
19 #ifdef PR_LOGGING |
|
20 PRLogModuleInfo* gVorbisTrackEncoderLog; |
|
21 #define VORBISLOG(msg, ...) PR_LOG(gVorbisTrackEncoderLog, PR_LOG_DEBUG, \ |
|
22 (msg, ##__VA_ARGS__)) |
|
23 #else |
|
24 #define VORBISLOG(msg, ...) |
|
25 #endif |
|
26 |
|
27 VorbisTrackEncoder::VorbisTrackEncoder() |
|
28 : AudioTrackEncoder() |
|
29 { |
|
30 MOZ_COUNT_CTOR(VorbisTrackEncoder); |
|
31 #ifdef PR_LOGGING |
|
32 if (!gVorbisTrackEncoderLog) { |
|
33 gVorbisTrackEncoderLog = PR_NewLogModule("VorbisTrackEncoder"); |
|
34 } |
|
35 #endif |
|
36 } |
|
37 |
|
38 VorbisTrackEncoder::~VorbisTrackEncoder() |
|
39 { |
|
40 MOZ_COUNT_DTOR(VorbisTrackEncoder); |
|
41 if (mInitialized) { |
|
42 vorbis_block_clear(&mVorbisBlock); |
|
43 vorbis_dsp_clear(&mVorbisDsp); |
|
44 vorbis_info_clear(&mVorbisInfo); |
|
45 } |
|
46 } |
|
47 |
|
48 nsresult |
|
49 VorbisTrackEncoder::Init(int aChannels, int aSamplingRate) |
|
50 { |
|
51 if (aChannels <= 0 || aChannels > 8) { |
|
52 VORBISLOG("aChannels <= 0 || aChannels > 8"); |
|
53 return NS_ERROR_INVALID_ARG; |
|
54 } |
|
55 |
|
56 // This monitor is used to wake up other methods that are waiting for encoder |
|
57 // to be completely initialized. |
|
58 ReentrantMonitorAutoEnter mon(mReentrantMonitor); |
|
59 mChannels = aChannels; |
|
60 mSamplingRate = aSamplingRate; |
|
61 |
|
62 int ret = 0; |
|
63 vorbis_info_init(&mVorbisInfo); |
|
64 |
|
65 ret = vorbis_encode_init_vbr(&mVorbisInfo, mChannels, mSamplingRate, |
|
66 BASE_QUALITY); |
|
67 |
|
68 mInitialized = (ret == 0); |
|
69 |
|
70 if (mInitialized) { |
|
71 // Set up the analysis state and auxiliary encoding storage |
|
72 vorbis_analysis_init(&mVorbisDsp, &mVorbisInfo); |
|
73 vorbis_block_init(&mVorbisDsp, &mVorbisBlock); |
|
74 } |
|
75 |
|
76 mon.NotifyAll(); |
|
77 |
|
78 return ret == 0 ? NS_OK : NS_ERROR_FAILURE; |
|
79 } |
|
80 |
|
81 void VorbisTrackEncoder::WriteLacing(nsTArray<uint8_t> *aOutput, int32_t aLacing) |
|
82 { |
|
83 while (aLacing >= 255) { |
|
84 aLacing -= 255; |
|
85 aOutput->AppendElement(255); |
|
86 } |
|
87 aOutput->AppendElement((uint8_t)aLacing); |
|
88 } |
|
89 |
|
90 already_AddRefed<TrackMetadataBase> |
|
91 VorbisTrackEncoder::GetMetadata() |
|
92 { |
|
93 { |
|
94 // Wait if encoder is not initialized. |
|
95 ReentrantMonitorAutoEnter mon(mReentrantMonitor); |
|
96 while (!mCanceled && !mInitialized) { |
|
97 mon.Wait(); |
|
98 } |
|
99 } |
|
100 |
|
101 if (mCanceled || mEncodingComplete) { |
|
102 return nullptr; |
|
103 } |
|
104 |
|
105 // Vorbis codec specific data |
|
106 // http://matroska.org/technical/specs/codecid/index.html |
|
107 nsRefPtr<VorbisMetadata> meta = new VorbisMetadata(); |
|
108 meta->mBitDepth = 32; // float for desktop |
|
109 meta->mChannels = mChannels; |
|
110 meta->mSamplingFrequency = mSamplingRate; |
|
111 ogg_packet header; |
|
112 ogg_packet header_comm; |
|
113 ogg_packet header_code; |
|
114 // Add comment |
|
115 vorbis_comment vorbisComment; |
|
116 vorbis_comment_init(&vorbisComment); |
|
117 vorbis_comment_add_tag(&vorbisComment, "ENCODER", |
|
118 NS_LITERAL_CSTRING("Mozilla VorbisTrackEncoder " MOZ_APP_UA_VERSION).get()); |
|
119 vorbis_analysis_headerout(&mVorbisDsp, &vorbisComment, |
|
120 &header,&header_comm, &header_code); |
|
121 vorbis_comment_clear(&vorbisComment); |
|
122 // number of distinct packets - 1 |
|
123 meta->mData.AppendElement(2); |
|
124 // Xiph-style lacing header.bytes, header_comm.bytes |
|
125 WriteLacing(&(meta->mData), header.bytes); |
|
126 WriteLacing(&(meta->mData), header_comm.bytes); |
|
127 |
|
128 // Append the three packets |
|
129 meta->mData.AppendElements(header.packet, header.bytes); |
|
130 meta->mData.AppendElements(header_comm.packet, header_comm.bytes); |
|
131 meta->mData.AppendElements(header_code.packet, header_code.bytes); |
|
132 |
|
133 return meta.forget(); |
|
134 } |
|
135 |
|
136 void |
|
137 VorbisTrackEncoder::GetEncodedFrames(EncodedFrameContainer& aData) |
|
138 { |
|
139 // vorbis does some data preanalysis, then divvies up blocks for |
|
140 // more involved (potentially parallel) processing. Get a single |
|
141 // block for encoding now. |
|
142 while (vorbis_analysis_blockout(&mVorbisDsp, &mVorbisBlock) == 1) { |
|
143 ogg_packet oggPacket; |
|
144 if (vorbis_analysis(&mVorbisBlock, &oggPacket) == 0) { |
|
145 VORBISLOG("vorbis_analysis_blockout block size %d", oggPacket.bytes); |
|
146 EncodedFrame* audiodata = new EncodedFrame(); |
|
147 audiodata->SetFrameType(EncodedFrame::VORBIS_AUDIO_FRAME); |
|
148 nsTArray<uint8_t> frameData; |
|
149 frameData.AppendElements(oggPacket.packet, oggPacket.bytes); |
|
150 audiodata->SwapInFrameData(frameData); |
|
151 aData.AppendEncodedFrame(audiodata); |
|
152 } |
|
153 } |
|
154 } |
|
155 |
|
156 nsresult |
|
157 VorbisTrackEncoder::GetEncodedTrack(EncodedFrameContainer& aData) |
|
158 { |
|
159 if (mEosSetInEncoder) { |
|
160 return NS_OK; |
|
161 } |
|
162 |
|
163 nsAutoPtr<AudioSegment> sourceSegment; |
|
164 sourceSegment = new AudioSegment(); |
|
165 { |
|
166 // Move all the samples from mRawSegment to sourceSegment. We only hold |
|
167 // the monitor in this block. |
|
168 ReentrantMonitorAutoEnter mon(mReentrantMonitor); |
|
169 |
|
170 // Wait if mEncoder is not initialized, or when not enough raw data, but is |
|
171 // not the end of stream nor is being canceled. |
|
172 while (!mCanceled && mRawSegment.GetDuration() < GetPacketDuration() && |
|
173 !mEndOfStream) { |
|
174 mon.Wait(); |
|
175 } |
|
176 VORBISLOG("GetEncodedTrack passes wait, duration is %lld\n", |
|
177 mRawSegment.GetDuration()); |
|
178 if (mCanceled || mEncodingComplete) { |
|
179 return NS_ERROR_FAILURE; |
|
180 } |
|
181 |
|
182 sourceSegment->AppendFrom(&mRawSegment); |
|
183 } |
|
184 |
|
185 if (mEndOfStream && (sourceSegment->GetDuration() == 0) |
|
186 && !mEosSetInEncoder) { |
|
187 mEncodingComplete = true; |
|
188 mEosSetInEncoder = true; |
|
189 VORBISLOG("[Vorbis] Done encoding."); |
|
190 vorbis_analysis_wrote(&mVorbisDsp, 0); |
|
191 GetEncodedFrames(aData); |
|
192 |
|
193 return NS_OK; |
|
194 } |
|
195 |
|
196 // Start encoding data. |
|
197 AudioSegment::ChunkIterator iter(*sourceSegment); |
|
198 |
|
199 AudioDataValue **vorbisBuffer = |
|
200 vorbis_analysis_buffer(&mVorbisDsp, (int)sourceSegment->GetDuration()); |
|
201 |
|
202 int framesCopied = 0; |
|
203 nsAutoTArray<AudioDataValue, 9600> interleavedPcm; |
|
204 nsAutoTArray<AudioDataValue, 9600> nonInterleavedPcm; |
|
205 interleavedPcm.SetLength(sourceSegment->GetDuration() * mChannels); |
|
206 nonInterleavedPcm.SetLength(sourceSegment->GetDuration() * mChannels); |
|
207 while (!iter.IsEnded()) { |
|
208 AudioChunk chunk = *iter; |
|
209 int frameToCopy = chunk.GetDuration(); |
|
210 if (!chunk.IsNull()) { |
|
211 InterleaveTrackData(chunk, frameToCopy, mChannels, |
|
212 interleavedPcm.Elements() + framesCopied * mChannels); |
|
213 } else { // empty data |
|
214 memset(interleavedPcm.Elements() + framesCopied * mChannels, 0, |
|
215 frameToCopy * mChannels * sizeof(AudioDataValue)); |
|
216 } |
|
217 framesCopied += frameToCopy; |
|
218 iter.Next(); |
|
219 } |
|
220 // De-interleave the interleavedPcm. |
|
221 DeInterleaveTrackData(interleavedPcm.Elements(), framesCopied, mChannels, |
|
222 nonInterleavedPcm.Elements()); |
|
223 // Copy the nonInterleavedPcm to vorbis buffer. |
|
224 for(uint8_t i = 0; i < mChannels; ++i) { |
|
225 memcpy(vorbisBuffer[i], nonInterleavedPcm.Elements() + framesCopied * i, |
|
226 framesCopied * sizeof(AudioDataValue)); |
|
227 } |
|
228 |
|
229 // Now the vorbisBuffer contain the all data in non-interleaved. |
|
230 // Tell the library how much we actually submitted. |
|
231 vorbis_analysis_wrote(&mVorbisDsp, framesCopied); |
|
232 VORBISLOG("vorbis_analysis_wrote framesCopied %d\n", framesCopied); |
|
233 GetEncodedFrames(aData); |
|
234 |
|
235 return NS_OK; |
|
236 } |
|
237 |
|
238 } // namespace mozilla |