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