/src/mozilla-central/dom/media/ogg/OpusParser.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 <algorithm> |
8 | | #include "mozilla/EndianUtils.h" |
9 | | |
10 | | #include "OpusParser.h" |
11 | | #include "VideoUtils.h" |
12 | | |
13 | | #include "opus/opus.h" |
14 | | extern "C" { |
15 | | #include "opus/opus_multistream.h" |
16 | | } |
17 | | |
18 | | #include <cmath> |
19 | | |
20 | | namespace mozilla { |
21 | | |
22 | | extern LazyLogModule gMediaDecoderLog; |
23 | 0 | #define OPUS_LOG(type, msg) MOZ_LOG(gMediaDecoderLog, type, msg) |
24 | | |
25 | | OpusParser::OpusParser(): |
26 | | mRate(0), |
27 | | mNominalRate(0), |
28 | | mChannels(0), |
29 | | mPreSkip(0), |
30 | | #ifdef MOZ_SAMPLE_TYPE_FLOAT32 |
31 | | mGain(1.0f), |
32 | | #else |
33 | | mGain_Q16(65536), |
34 | | #endif |
35 | | mChannelMapping(0), |
36 | | mStreams(0), |
37 | | mCoupledStreams(0), |
38 | | mPrevPacketGranulepos(0) |
39 | 0 | { } |
40 | | |
41 | | bool OpusParser::DecodeHeader(unsigned char* aData, size_t aLength) |
42 | 0 | { |
43 | 0 | if (aLength < 19 || memcmp(aData, "OpusHead", 8)) { |
44 | 0 | OPUS_LOG(LogLevel::Debug, ("Invalid Opus file: unrecognized header")); |
45 | 0 | return false; |
46 | 0 | } |
47 | 0 |
|
48 | 0 | mRate = 48000; // The Opus decoder runs at 48 kHz regardless. |
49 | 0 |
|
50 | 0 | int version = aData[8]; |
51 | 0 | // Accept file format versions 0.x. |
52 | 0 | if ((version & 0xf0) != 0) { |
53 | 0 | OPUS_LOG(LogLevel::Debug, ("Rejecting unknown Opus file version %d", version)); |
54 | 0 | return false; |
55 | 0 | } |
56 | 0 |
|
57 | 0 | mChannels = aData[9]; |
58 | 0 | if (mChannels<1) { |
59 | 0 | OPUS_LOG(LogLevel::Debug, ("Invalid Opus file: Number of channels %d", mChannels)); |
60 | 0 | return false; |
61 | 0 | } |
62 | 0 |
|
63 | 0 | mPreSkip = LittleEndian::readUint16(aData + 10); |
64 | 0 | mNominalRate = LittleEndian::readUint32(aData + 12); |
65 | 0 | double gain_dB = LittleEndian::readInt16(aData + 16) / 256.0; |
66 | 0 | #ifdef MOZ_SAMPLE_TYPE_FLOAT32 |
67 | 0 | mGain = static_cast<float>(pow(10,0.05*gain_dB)); |
68 | | #else |
69 | | mGain_Q16 = static_cast<int32_t>(std::min(65536*pow(10,0.05*gain_dB)+0.5, |
70 | | static_cast<double>(INT32_MAX))); |
71 | | #endif |
72 | | mChannelMapping = aData[18]; |
73 | 0 |
|
74 | 0 | if (mChannelMapping == 0) { |
75 | 0 | // Mapping family 0 only allows two channels |
76 | 0 | if (mChannels>2) { |
77 | 0 | OPUS_LOG(LogLevel::Debug, ("Invalid Opus file: too many channels (%d) for" |
78 | 0 | " mapping family 0.", mChannels)); |
79 | 0 | return false; |
80 | 0 | } |
81 | 0 | mStreams = 1; |
82 | 0 | mCoupledStreams = mChannels - 1; |
83 | 0 | mMappingTable[0] = 0; |
84 | 0 | mMappingTable[1] = 1; |
85 | 0 | } else if (mChannelMapping == 1 || mChannelMapping == 2 || |
86 | 0 | mChannelMapping == 255) { |
87 | 0 | // Currently only up to 8 channels are defined for mapping family 1 |
88 | 0 | if (mChannelMapping == 1 && mChannels > 8) { |
89 | 0 | OPUS_LOG(LogLevel::Debug, |
90 | 0 | ("Invalid Opus file: too many channels (%d) for" |
91 | 0 | " mapping family 1.", |
92 | 0 | mChannels)); |
93 | 0 | return false; |
94 | 0 | } |
95 | 0 | if (mChannelMapping == 2) { |
96 | 0 | // https://tools.ietf.org/html/draft-ietf-codec-ambisonics-08#page-3 |
97 | 0 | // For both channel mapping family 2 and family 3, the allowed numbers |
98 | 0 | // of channels: (1 + n)^2 + 2j for n = 0, 1, ..., 14 and j = 0 or 1, |
99 | 0 | // where n denotes the (highest) ambisonic order and j denotes whether |
100 | 0 | // or not there is a separate non-diegetic stereo stream Explicitly the |
101 | 0 | // allowed number of channels are 1, 3, 4, 6, 9, 11, 16, 18, 25, 27, 36, |
102 | 0 | // 38, 49, 51, 64, 66, 81, 83, 100, 102, 121, 123, 144, 146, 169, 171, |
103 | 0 | // 196, 198, 225, and 227. |
104 | 0 |
|
105 | 0 | // We use the property that int(sqrt(n)) == int(sqrt(n+2)) for n != 3 |
106 | 0 | // which is handled by the test n^2 + 2 != channel |
107 | 0 | double val = sqrt(mChannels); |
108 | 0 | if (val == 0 || val > 15) { |
109 | 0 | return false; |
110 | 0 | } |
111 | 0 | if (val != int32_t(val)) { |
112 | 0 | if (val * val + 2 != mChannels) { |
113 | 0 | // Not a valid channel count. |
114 | 0 | return false; |
115 | 0 | } |
116 | 0 | } |
117 | 0 | } |
118 | 0 | if (aLength > static_cast<unsigned>(20 + mChannels)) { |
119 | 0 | mStreams = aData[19]; |
120 | 0 | mCoupledStreams = aData[20]; |
121 | 0 | int i; |
122 | 0 | for (i = 0; i < mChannels; i++) { |
123 | 0 | mMappingTable[i] = aData[21 + i]; |
124 | 0 | } |
125 | 0 | } else { |
126 | 0 | OPUS_LOG(LogLevel::Debug, |
127 | 0 | ("Invalid Opus file: channel mapping %d," |
128 | 0 | " but no channel mapping table", |
129 | 0 | mChannelMapping)); |
130 | 0 | return false; |
131 | 0 | } |
132 | 0 | } else { |
133 | 0 | OPUS_LOG(LogLevel::Debug, |
134 | 0 | ("Invalid Opus file: unsupported channel mapping " |
135 | 0 | "family %d", |
136 | 0 | mChannelMapping)); |
137 | 0 | return false; |
138 | 0 | } |
139 | 0 | if (mStreams < 1) { |
140 | 0 | OPUS_LOG(LogLevel::Debug, ("Invalid Opus file: no streams")); |
141 | 0 | return false; |
142 | 0 | } |
143 | 0 | if (mCoupledStreams > mStreams) { |
144 | 0 | OPUS_LOG(LogLevel::Debug, ("Invalid Opus file: more coupled streams (%d) than " |
145 | 0 | "total streams (%d)", mCoupledStreams, mStreams)); |
146 | 0 | return false; |
147 | 0 | } |
148 | 0 |
|
149 | | #ifdef DEBUG |
150 | | OPUS_LOG(LogLevel::Debug, ("Opus stream header:")); |
151 | | OPUS_LOG(LogLevel::Debug, (" channels: %d", mChannels)); |
152 | | OPUS_LOG(LogLevel::Debug, (" preskip: %d", mPreSkip)); |
153 | | OPUS_LOG(LogLevel::Debug, (" original: %d Hz", mNominalRate)); |
154 | | OPUS_LOG(LogLevel::Debug, (" gain: %.2f dB", gain_dB)); |
155 | | OPUS_LOG(LogLevel::Debug, ("Channel Mapping:")); |
156 | | OPUS_LOG(LogLevel::Debug, (" family: %d", mChannelMapping)); |
157 | | OPUS_LOG(LogLevel::Debug, (" streams: %d", mStreams)); |
158 | | #endif |
159 | 0 | return true; |
160 | 0 | } |
161 | | |
162 | | bool OpusParser::DecodeTags(unsigned char* aData, size_t aLength) |
163 | 0 | { |
164 | 0 | if (aLength < 16 || memcmp(aData, "OpusTags", 8)) |
165 | 0 | return false; |
166 | 0 | |
167 | 0 | // Copy out the raw comment lines, but only do basic validation |
168 | 0 | // checks against the string packing: too little data, too many |
169 | 0 | // comments, or comments that are too long. Rejecting these cases |
170 | 0 | // helps reduce the propagation of broken files. |
171 | 0 | // We do not ensure they are valid UTF-8 here, nor do we validate |
172 | 0 | // the required ASCII_TAG=value format of the user comments. |
173 | 0 | const unsigned char* buf = aData + 8; |
174 | 0 | uint32_t bytes = aLength - 8; |
175 | 0 | uint32_t len; |
176 | 0 | // Read the vendor string. |
177 | 0 | len = LittleEndian::readUint32(buf); |
178 | 0 | buf += 4; |
179 | 0 | bytes -= 4; |
180 | 0 | if (len > bytes) |
181 | 0 | return false; |
182 | 0 | mVendorString = nsCString(reinterpret_cast<const char*>(buf), len); |
183 | 0 | buf += len; |
184 | 0 | bytes -= len; |
185 | 0 | // Read the user comments. |
186 | 0 | if (bytes < 4) |
187 | 0 | return false; |
188 | 0 | uint32_t ncomments = LittleEndian::readUint32(buf); |
189 | 0 | buf += 4; |
190 | 0 | bytes -= 4; |
191 | 0 | // If there are so many comments even their length fields |
192 | 0 | // won't fit in the packet, stop reading now. |
193 | 0 | if (ncomments > (bytes>>2)) |
194 | 0 | return false; |
195 | 0 | for (uint32_t i = 0; i < ncomments; i++) { |
196 | 0 | if (bytes < 4) |
197 | 0 | return false; |
198 | 0 | len = LittleEndian::readUint32(buf); |
199 | 0 | buf += 4; |
200 | 0 | bytes -= 4; |
201 | 0 | if (len > bytes) |
202 | 0 | return false; |
203 | 0 | mTags.AppendElement(nsCString(reinterpret_cast<const char*>(buf), len)); |
204 | 0 | buf += len; |
205 | 0 | bytes -= len; |
206 | 0 | } |
207 | 0 |
|
208 | | #ifdef DEBUG |
209 | | OPUS_LOG(LogLevel::Debug, ("Opus metadata header:")); |
210 | | OPUS_LOG(LogLevel::Debug, (" vendor: %s", mVendorString.get())); |
211 | | for (uint32_t i = 0; i < mTags.Length(); i++) { |
212 | | OPUS_LOG(LogLevel::Debug, (" %s", mTags[i].get())); |
213 | | } |
214 | | #endif |
215 | 0 | return true; |
216 | 0 | } |
217 | | |
218 | | } // namespace mozilla |