/src/mozilla-central/dom/media/platforms/agnostic/OpusDecoder.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
2 | | /* vim:set ts=2 sw=2 sts=2 et cindent: */ |
3 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
4 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
5 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
6 | | |
7 | | #include "OpusDecoder.h" |
8 | | #include "OpusParser.h" |
9 | | #include "TimeUnits.h" |
10 | | #include "VorbisUtils.h" |
11 | | #include "VorbisDecoder.h" // For VorbisLayout |
12 | | #include "mozilla/EndianUtils.h" |
13 | | #include "mozilla/PodOperations.h" |
14 | | #include "mozilla/SyncRunnable.h" |
15 | | #include "VideoUtils.h" |
16 | | |
17 | | #include <inttypes.h> // For PRId64 |
18 | | |
19 | | #include "opus/opus.h" |
20 | | extern "C" { |
21 | | #include "opus/opus_multistream.h" |
22 | | } |
23 | | |
24 | | #define OPUS_DEBUG(arg, ...) \ |
25 | 0 | DDMOZ_LOG( \ |
26 | 0 | sPDMLog, mozilla::LogLevel::Debug, "::%s: " arg, __func__, ##__VA_ARGS__) |
27 | | |
28 | | namespace mozilla { |
29 | | |
30 | | OpusDataDecoder::OpusDataDecoder(const CreateDecoderParams& aParams) |
31 | | : mInfo(aParams.AudioConfig()) |
32 | | , mTaskQueue(aParams.mTaskQueue) |
33 | | , mOpusDecoder(nullptr) |
34 | | , mSkip(0) |
35 | | , mDecodedHeader(false) |
36 | | , mPaddingDiscarded(false) |
37 | | , mFrames(0) |
38 | | , mChannelMap(AudioConfig::ChannelLayout::UNKNOWN_MAP) |
39 | 0 | { |
40 | 0 | } |
41 | | |
42 | | OpusDataDecoder::~OpusDataDecoder() |
43 | 0 | { |
44 | 0 | if (mOpusDecoder) { |
45 | 0 | opus_multistream_decoder_destroy(mOpusDecoder); |
46 | 0 | mOpusDecoder = nullptr; |
47 | 0 | } |
48 | 0 | } |
49 | | |
50 | | RefPtr<ShutdownPromise> |
51 | | OpusDataDecoder::Shutdown() |
52 | 0 | { |
53 | 0 | RefPtr<OpusDataDecoder> self = this; |
54 | 0 | return InvokeAsync(mTaskQueue, __func__, [self]() { |
55 | 0 | return ShutdownPromise::CreateAndResolve(true, __func__); |
56 | 0 | }); |
57 | 0 | } |
58 | | |
59 | | void |
60 | | OpusDataDecoder::AppendCodecDelay(MediaByteBuffer* config, uint64_t codecDelayUS) |
61 | 0 | { |
62 | 0 | uint8_t buffer[sizeof(uint64_t)]; |
63 | 0 | BigEndian::writeUint64(buffer, codecDelayUS); |
64 | 0 | config->AppendElements(buffer, sizeof(uint64_t)); |
65 | 0 | } |
66 | | |
67 | | RefPtr<MediaDataDecoder::InitPromise> |
68 | | OpusDataDecoder::Init() |
69 | 0 | { |
70 | 0 | size_t length = mInfo.mCodecSpecificConfig->Length(); |
71 | 0 | uint8_t *p = mInfo.mCodecSpecificConfig->Elements(); |
72 | 0 | if (length < sizeof(uint64_t)) { |
73 | 0 | OPUS_DEBUG("CodecSpecificConfig too short to read codecDelay!"); |
74 | 0 | return InitPromise::CreateAndReject( |
75 | 0 | MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, |
76 | 0 | RESULT_DETAIL("CodecSpecificConfig too short to read codecDelay!")), |
77 | 0 | __func__); |
78 | 0 | } |
79 | 0 | int64_t codecDelay = BigEndian::readUint64(p); |
80 | 0 | length -= sizeof(uint64_t); |
81 | 0 | p += sizeof(uint64_t); |
82 | 0 | if (NS_FAILED(DecodeHeader(p, length))) { |
83 | 0 | OPUS_DEBUG("Error decoding header!"); |
84 | 0 | return InitPromise::CreateAndReject( |
85 | 0 | MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, |
86 | 0 | RESULT_DETAIL("Error decoding header!")), |
87 | 0 | __func__); |
88 | 0 | } |
89 | 0 |
|
90 | 0 | MOZ_ASSERT(mMappingTable.Length() >= uint32_t(mOpusParser->mChannels)); |
91 | 0 | int r; |
92 | 0 | mOpusDecoder = opus_multistream_decoder_create(mOpusParser->mRate, |
93 | 0 | mOpusParser->mChannels, |
94 | 0 | mOpusParser->mStreams, |
95 | 0 | mOpusParser->mCoupledStreams, |
96 | 0 | mMappingTable.Elements(), |
97 | 0 | &r); |
98 | 0 |
|
99 | 0 | // Opus has a special feature for stereo coding where it represent wide |
100 | 0 | // stereo channels by 180-degree out of phase. This improves quality, but |
101 | 0 | // needs to be disabled when the output is downmixed to mono. Playback number |
102 | 0 | // of channels are set in AudioSink, using the same method |
103 | 0 | // `DecideAudioPlaybackChannels()`, and triggers downmix if needed. |
104 | 0 | if (IsDefaultPlaybackDeviceMono() || |
105 | 0 | DecideAudioPlaybackChannels(mInfo) == 1) { |
106 | 0 | opus_multistream_decoder_ctl(mOpusDecoder, OPUS_SET_PHASE_INVERSION_DISABLED(1)); |
107 | 0 | } |
108 | 0 |
|
109 | 0 | mSkip = mOpusParser->mPreSkip; |
110 | 0 | mPaddingDiscarded = false; |
111 | 0 |
|
112 | 0 | if (codecDelay != FramesToUsecs(mOpusParser->mPreSkip, |
113 | 0 | mOpusParser->mRate).value()) { |
114 | 0 | NS_WARNING("Invalid Opus header: CodecDelay and pre-skip do not match!"); |
115 | 0 | return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__); |
116 | 0 | } |
117 | 0 |
|
118 | 0 | if (mInfo.mRate != (uint32_t)mOpusParser->mRate) { |
119 | 0 | NS_WARNING("Invalid Opus header: container and codec rate do not match!"); |
120 | 0 | } |
121 | 0 | if (mInfo.mChannels != (uint32_t)mOpusParser->mChannels) { |
122 | 0 | NS_WARNING("Invalid Opus header: container and codec channels do not match!"); |
123 | 0 | } |
124 | 0 |
|
125 | 0 | return r == OPUS_OK ? InitPromise::CreateAndResolve(TrackInfo::kAudioTrack, __func__) |
126 | 0 | : InitPromise::CreateAndReject( |
127 | 0 | MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, |
128 | 0 | RESULT_DETAIL("could not create opus multistream decoder!")), |
129 | 0 | __func__); |
130 | 0 | } |
131 | | |
132 | | nsresult |
133 | | OpusDataDecoder::DecodeHeader(const unsigned char* aData, size_t aLength) |
134 | 0 | { |
135 | 0 | MOZ_ASSERT(!mOpusParser); |
136 | 0 | MOZ_ASSERT(!mOpusDecoder); |
137 | 0 | MOZ_ASSERT(!mDecodedHeader); |
138 | 0 | mDecodedHeader = true; |
139 | 0 |
|
140 | 0 | mOpusParser = new OpusParser; |
141 | 0 | if (!mOpusParser->DecodeHeader(const_cast<unsigned char*>(aData), aLength)) { |
142 | 0 | return NS_ERROR_FAILURE; |
143 | 0 | } |
144 | 0 | int channels = mOpusParser->mChannels; |
145 | 0 |
|
146 | 0 | mMappingTable.SetLength(channels); |
147 | 0 | AudioConfig::ChannelLayout vorbisLayout( |
148 | 0 | channels, VorbisDataDecoder::VorbisLayout(channels)); |
149 | 0 | if (vorbisLayout.IsValid()) { |
150 | 0 | mChannelMap = vorbisLayout.Map(); |
151 | 0 |
|
152 | 0 | AudioConfig::ChannelLayout smpteLayout( |
153 | 0 | AudioConfig::ChannelLayout::SMPTEDefault(vorbisLayout)); |
154 | 0 |
|
155 | 0 | AutoTArray<uint8_t, 8> map; |
156 | 0 | map.SetLength(channels); |
157 | 0 | if (vorbisLayout.MappingTable(smpteLayout, &map)) { |
158 | 0 | for (int i = 0; i < channels; i++) { |
159 | 0 | mMappingTable[i] = mOpusParser->mMappingTable[map[i]]; |
160 | 0 | } |
161 | 0 | } else { |
162 | 0 | // Should never get here as vorbis layout is always convertible to SMPTE |
163 | 0 | // default layout. |
164 | 0 | PodCopy(mMappingTable.Elements(), mOpusParser->mMappingTable, channels); |
165 | 0 | } |
166 | 0 | } else { |
167 | 0 | // Create a dummy mapping table so that channel ordering stay the same |
168 | 0 | // during decoding. |
169 | 0 | for (int i = 0; i < channels; i++) { |
170 | 0 | mMappingTable[i] = i; |
171 | 0 | } |
172 | 0 | } |
173 | 0 |
|
174 | 0 | return NS_OK; |
175 | 0 | } |
176 | | |
177 | | RefPtr<MediaDataDecoder::DecodePromise> |
178 | | OpusDataDecoder::Decode(MediaRawData* aSample) |
179 | 0 | { |
180 | 0 | return InvokeAsync<MediaRawData*>(mTaskQueue, this, __func__, |
181 | 0 | &OpusDataDecoder::ProcessDecode, aSample); |
182 | 0 | } |
183 | | |
184 | | RefPtr<MediaDataDecoder::DecodePromise> |
185 | | OpusDataDecoder::ProcessDecode(MediaRawData* aSample) |
186 | 0 | { |
187 | 0 | uint32_t channels = mOpusParser->mChannels; |
188 | 0 |
|
189 | 0 | if (mPaddingDiscarded) { |
190 | 0 | // Discard padding should be used only on the final packet, so |
191 | 0 | // decoding after a padding discard is invalid. |
192 | 0 | OPUS_DEBUG("Opus error, discard padding on interstitial packet"); |
193 | 0 | return DecodePromise::CreateAndReject( |
194 | 0 | MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, |
195 | 0 | RESULT_DETAIL("Discard padding on interstitial packet")), |
196 | 0 | __func__); |
197 | 0 | } |
198 | 0 |
|
199 | 0 | if (!mLastFrameTime || |
200 | 0 | mLastFrameTime.ref() != aSample->mTime.ToMicroseconds()) { |
201 | 0 | // We are starting a new block. |
202 | 0 | mFrames = 0; |
203 | 0 | mLastFrameTime = Some(aSample->mTime.ToMicroseconds()); |
204 | 0 | } |
205 | 0 |
|
206 | 0 | // Maximum value is 63*2880, so there's no chance of overflow. |
207 | 0 | int frames_number = |
208 | 0 | opus_packet_get_nb_frames(aSample->Data(), aSample->Size()); |
209 | 0 | if (frames_number <= 0) { |
210 | 0 | OPUS_DEBUG("Invalid packet header: r=%d length=%zu", frames_number, |
211 | 0 | aSample->Size()); |
212 | 0 | return DecodePromise::CreateAndReject( |
213 | 0 | MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR, |
214 | 0 | RESULT_DETAIL("Invalid packet header: r=%d length=%u", |
215 | 0 | frames_number, uint32_t(aSample->Size()))), |
216 | 0 | __func__); |
217 | 0 | } |
218 | 0 |
|
219 | 0 | int samples = opus_packet_get_samples_per_frame( |
220 | 0 | aSample->Data(), opus_int32(mOpusParser->mRate)); |
221 | 0 |
|
222 | 0 | // A valid Opus packet must be between 2.5 and 120 ms long (48kHz). |
223 | 0 | CheckedInt32 totalFrames = |
224 | 0 | CheckedInt32(frames_number) * CheckedInt32(samples); |
225 | 0 | if (!totalFrames.isValid()) { |
226 | 0 | return DecodePromise::CreateAndReject( |
227 | 0 | MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR, |
228 | 0 | RESULT_DETAIL("Frames count overflow")), |
229 | 0 | __func__); |
230 | 0 | } |
231 | 0 |
|
232 | 0 | int frames = totalFrames.value(); |
233 | 0 | if (frames < 120 || frames > 5760) { |
234 | 0 | OPUS_DEBUG("Invalid packet frames: %d", frames); |
235 | 0 | return DecodePromise::CreateAndReject( |
236 | 0 | MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR, |
237 | 0 | RESULT_DETAIL("Invalid packet frames:%d", frames)), |
238 | 0 | __func__); |
239 | 0 | } |
240 | 0 |
|
241 | 0 | AlignedAudioBuffer buffer(frames * channels); |
242 | 0 | if (!buffer) { |
243 | 0 | return DecodePromise::CreateAndReject( |
244 | 0 | MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__), __func__); |
245 | 0 | } |
246 | 0 | |
247 | 0 | // Decode to the appropriate sample type. |
248 | 0 | #ifdef MOZ_SAMPLE_TYPE_FLOAT32 |
249 | 0 | int ret = opus_multistream_decode_float(mOpusDecoder, |
250 | 0 | aSample->Data(), aSample->Size(), |
251 | 0 | buffer.get(), frames, false); |
252 | | #else |
253 | | int ret = opus_multistream_decode(mOpusDecoder, |
254 | | aSample->Data(), aSample->Size(), |
255 | | buffer.get(), frames, false); |
256 | | #endif |
257 | 0 | if (ret < 0) { |
258 | 0 | return DecodePromise::CreateAndReject( |
259 | 0 | MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR, |
260 | 0 | RESULT_DETAIL("Opus decoding error:%d", ret)), |
261 | 0 | __func__); |
262 | 0 | } |
263 | 0 | NS_ASSERTION(ret == frames, "Opus decoded too few audio samples"); |
264 | 0 | auto startTime = aSample->mTime; |
265 | 0 |
|
266 | 0 | // Trim the initial frames while the decoder is settling. |
267 | 0 | if (mSkip > 0) { |
268 | 0 | int32_t skipFrames = std::min<int32_t>(mSkip, frames); |
269 | 0 | int32_t keepFrames = frames - skipFrames; |
270 | 0 | OPUS_DEBUG( |
271 | 0 | "Opus decoder skipping %d of %d frames", skipFrames, frames); |
272 | 0 | PodMove(buffer.get(), |
273 | 0 | buffer.get() + skipFrames * channels, |
274 | 0 | keepFrames * channels); |
275 | 0 | startTime = startTime + FramesToTimeUnit(skipFrames, mOpusParser->mRate); |
276 | 0 | frames = keepFrames; |
277 | 0 | mSkip -= skipFrames; |
278 | 0 | } |
279 | 0 |
|
280 | 0 | if (aSample->mDiscardPadding > 0) { |
281 | 0 | OPUS_DEBUG("Opus decoder discarding %u of %d frames", |
282 | 0 | aSample->mDiscardPadding, frames); |
283 | 0 | // Padding discard is only supposed to happen on the final packet. |
284 | 0 | // Record the discard so we can return an error if another packet is |
285 | 0 | // decoded. |
286 | 0 | if (aSample->mDiscardPadding > uint32_t(frames)) { |
287 | 0 | // Discarding more than the entire packet is invalid. |
288 | 0 | OPUS_DEBUG("Opus error, discard padding larger than packet"); |
289 | 0 | return DecodePromise::CreateAndReject( |
290 | 0 | MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, |
291 | 0 | RESULT_DETAIL("Discard padding larger than packet")), |
292 | 0 | __func__); |
293 | 0 | } |
294 | 0 |
|
295 | 0 | mPaddingDiscarded = true; |
296 | 0 | frames = frames - aSample->mDiscardPadding; |
297 | 0 | } |
298 | 0 |
|
299 | 0 | // Apply the header gain if one was specified. |
300 | 0 | #ifdef MOZ_SAMPLE_TYPE_FLOAT32 |
301 | 0 | if (mOpusParser->mGain != 1.0f) { |
302 | 0 | float gain = mOpusParser->mGain; |
303 | 0 | uint32_t samples = frames * channels; |
304 | 0 | for (uint32_t i = 0; i < samples; i++) { |
305 | 0 | buffer[i] *= gain; |
306 | 0 | } |
307 | 0 | } |
308 | | #else |
309 | | if (mOpusParser->mGain_Q16 != 65536) { |
310 | | int64_t gain_Q16 = mOpusParser->mGain_Q16; |
311 | | uint32_t samples = frames * channels; |
312 | | for (uint32_t i = 0; i < samples; i++) { |
313 | | int32_t val = static_cast<int32_t>((gain_Q16*buffer[i] + 32768)>>16); |
314 | | buffer[i] = static_cast<AudioDataValue>(MOZ_CLIP_TO_15(val)); |
315 | | } |
316 | | } |
317 | | #endif |
318 | |
|
319 | 0 | auto duration = FramesToTimeUnit(frames, mOpusParser->mRate); |
320 | 0 | if (!duration.IsValid()) { |
321 | 0 | return DecodePromise::CreateAndReject( |
322 | 0 | MediaResult(NS_ERROR_DOM_MEDIA_OVERFLOW_ERR, |
323 | 0 | RESULT_DETAIL("Overflow converting WebM audio duration")), |
324 | 0 | __func__); |
325 | 0 | } |
326 | 0 | auto time = startTime - |
327 | 0 | FramesToTimeUnit(mOpusParser->mPreSkip, mOpusParser->mRate) + |
328 | 0 | FramesToTimeUnit(mFrames, mOpusParser->mRate); |
329 | 0 | if (!time.IsValid()) { |
330 | 0 | return DecodePromise::CreateAndReject( |
331 | 0 | MediaResult(NS_ERROR_DOM_MEDIA_OVERFLOW_ERR, |
332 | 0 | RESULT_DETAIL("Overflow shifting tstamp by codec delay")), |
333 | 0 | __func__); |
334 | 0 | }; |
335 | 0 |
|
336 | 0 | mFrames += frames; |
337 | 0 |
|
338 | 0 | if (!frames) { |
339 | 0 | return DecodePromise::CreateAndResolve(DecodedData(), __func__); |
340 | 0 | } |
341 | 0 | |
342 | 0 | return DecodePromise::CreateAndResolve( |
343 | 0 | DecodedData{ new AudioData(aSample->mOffset, |
344 | 0 | time, |
345 | 0 | duration, |
346 | 0 | frames, |
347 | 0 | std::move(buffer), |
348 | 0 | mOpusParser->mChannels, |
349 | 0 | mOpusParser->mRate, |
350 | 0 | mChannelMap) }, |
351 | 0 | __func__); |
352 | 0 | } |
353 | | |
354 | | RefPtr<MediaDataDecoder::DecodePromise> |
355 | | OpusDataDecoder::Drain() |
356 | 0 | { |
357 | 0 | RefPtr<OpusDataDecoder> self = this; |
358 | 0 | // InvokeAsync dispatches a task that will be run after any pending decode |
359 | 0 | // completes. As such, once the drain task run, there's nothing more to do. |
360 | 0 | return InvokeAsync(mTaskQueue, __func__, [] { |
361 | 0 | return DecodePromise::CreateAndResolve(DecodedData(), __func__); |
362 | 0 | }); |
363 | 0 | } |
364 | | |
365 | | RefPtr<MediaDataDecoder::FlushPromise> |
366 | | OpusDataDecoder::Flush() |
367 | 0 | { |
368 | 0 | if (!mOpusDecoder) { |
369 | 0 | return FlushPromise::CreateAndResolve(true, __func__); |
370 | 0 | } |
371 | 0 | |
372 | 0 | RefPtr<OpusDataDecoder> self = this; |
373 | 0 | return InvokeAsync(mTaskQueue, __func__, [self, this]() { |
374 | 0 | MOZ_ASSERT(mOpusDecoder); |
375 | 0 | // Reset the decoder. |
376 | 0 | opus_multistream_decoder_ctl(mOpusDecoder, OPUS_RESET_STATE); |
377 | 0 | mSkip = mOpusParser->mPreSkip; |
378 | 0 | mPaddingDiscarded = false; |
379 | 0 | mLastFrameTime.reset(); |
380 | 0 | return FlushPromise::CreateAndResolve(true, __func__); |
381 | 0 | }); |
382 | 0 | } |
383 | | |
384 | | /* static */ |
385 | | bool |
386 | | OpusDataDecoder::IsOpus(const nsACString& aMimeType) |
387 | 0 | { |
388 | 0 | return aMimeType.EqualsLiteral("audio/opus"); |
389 | 0 | } |
390 | | |
391 | | } // namespace mozilla |
392 | | #undef OPUS_DEBUG |