1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5 #include "Adts.h"
6 #include "AnnexB.h"
7 #include "BufferReader.h"
8 #include "DecoderData.h"
9 #include "mozilla/ArrayUtils.h"
10 #include "mozilla/EndianUtils.h"
11 #include "mozilla/Telemetry.h"
12 #include "VideoUtils.h"
13
14 // OpusDecoder header is really needed only by MP4 in rust
15 #include "OpusDecoder.h"
16 #include "mp4parse.h"
17
18 using mozilla::media::TimeUnit;
19
20 namespace mozilla {
21
DoUpdate(const uint8_t * aData,size_t aLength)22 mozilla::Result<mozilla::Ok, nsresult> CryptoFile::DoUpdate(
23 const uint8_t* aData, size_t aLength) {
24 BufferReader reader(aData, aLength);
25 while (reader.Remaining()) {
26 PsshInfo psshInfo;
27 if (!reader.ReadArray(psshInfo.uuid, 16)) {
28 return mozilla::Err(NS_ERROR_FAILURE);
29 }
30
31 if (!reader.CanReadType<uint32_t>()) {
32 return mozilla::Err(NS_ERROR_FAILURE);
33 }
34 auto length = reader.ReadType<uint32_t>();
35
36 if (!reader.ReadArray(psshInfo.data, length)) {
37 return mozilla::Err(NS_ERROR_FAILURE);
38 }
39 pssh.AppendElement(std::move(psshInfo));
40 }
41 return mozilla::Ok();
42 }
43
UpdateTrackProtectedInfo(mozilla::TrackInfo & aConfig,const Mp4parseSinfInfo & aSinf)44 static MediaResult UpdateTrackProtectedInfo(mozilla::TrackInfo& aConfig,
45 const Mp4parseSinfInfo& aSinf) {
46 if (aSinf.is_encrypted != 0) {
47 if (aSinf.scheme_type == MP4_PARSE_ENCRYPTION_SCHEME_TYPE_CENC) {
48 aConfig.mCrypto.mCryptoScheme = CryptoScheme::Cenc;
49 } else if (aSinf.scheme_type == MP4_PARSE_ENCRYPTION_SCHEME_TYPE_CBCS) {
50 aConfig.mCrypto.mCryptoScheme = CryptoScheme::Cbcs;
51 } else {
52 // Unsupported encryption type;
53 return MediaResult(
54 NS_ERROR_DOM_MEDIA_METADATA_ERR,
55 RESULT_DETAIL(
56 "Unsupported encryption scheme encountered aSinf.scheme_type=%d",
57 static_cast<int>(aSinf.scheme_type)));
58 }
59 aConfig.mCrypto.mIVSize = aSinf.iv_size;
60 aConfig.mCrypto.mKeyId.AppendElements(aSinf.kid.data, aSinf.kid.length);
61 aConfig.mCrypto.mCryptByteBlock = aSinf.crypt_byte_block;
62 aConfig.mCrypto.mSkipByteBlock = aSinf.skip_byte_block;
63 aConfig.mCrypto.mConstantIV.AppendElements(aSinf.constant_iv.data,
64 aSinf.constant_iv.length);
65 }
66 return NS_OK;
67 }
68
69 // Verify various information shared by Mp4ParseTrackAudioInfo and
70 // Mp4ParseTrackVideoInfo and record telemetry on that info. Returns an
71 // appropriate MediaResult indicating if the info is valid or not.
72 // This verifies:
73 // - That we have a sample_info_count > 0 (valid tracks should have at least one
74 // sample description entry)
75 // - That only a single codec is used across all sample infos, as we don't
76 // handle multiple.
77 // - If more than one sample information structures contain crypto info. This
78 // case is not fatal (we don't return an error), but does record telemetry
79 // to help judge if we need more handling in gecko for multiple crypto.
80 //
81 // Telemetry is also recorded on the above. As of writing, the
82 // telemetry is recorded to give us early warning if MP4s exist that we're not
83 // handling. Note, if adding new checks and telemetry to this function,
84 // telemetry should be recorded before returning to ensure it is gathered.
85 template <typename Mp4ParseTrackAudioOrVideoInfo>
VerifyAudioOrVideoInfoAndRecordTelemetry(Mp4ParseTrackAudioOrVideoInfo * audioOrVideoInfo)86 static MediaResult VerifyAudioOrVideoInfoAndRecordTelemetry(
87 Mp4ParseTrackAudioOrVideoInfo* audioOrVideoInfo) {
88 Telemetry::Accumulate(
89 Telemetry::MEDIA_MP4_PARSE_NUM_SAMPLE_DESCRIPTION_ENTRIES,
90 audioOrVideoInfo->sample_info_count);
91
92 bool hasMultipleCodecs = false;
93 uint32_t cryptoCount = 0;
94 Mp4parseCodec codecType = audioOrVideoInfo->sample_info[0].codec_type;
95 for (uint32_t i = 0; i < audioOrVideoInfo->sample_info_count; i++) {
96 if (audioOrVideoInfo->sample_info[0].codec_type != codecType) {
97 hasMultipleCodecs = true;
98 }
99
100 // Update our encryption info if any is present on the sample info.
101 if (audioOrVideoInfo->sample_info[i].protected_data.is_encrypted) {
102 cryptoCount += 1;
103 }
104 }
105
106 Telemetry::Accumulate(
107 Telemetry::
108 MEDIA_MP4_PARSE_SAMPLE_DESCRIPTION_ENTRIES_HAVE_MULTIPLE_CODECS,
109 hasMultipleCodecs);
110
111 // Accumulate if we have multiple (2 or more) crypto entries.
112 // TODO(1715283): rework this to count number of crypto entries + gather
113 // richer data.
114 Telemetry::Accumulate(
115 Telemetry::
116 MEDIA_MP4_PARSE_SAMPLE_DESCRIPTION_ENTRIES_HAVE_MULTIPLE_CRYPTO,
117 cryptoCount >= 2);
118
119 if (audioOrVideoInfo->sample_info_count == 0) {
120 return MediaResult(
121 NS_ERROR_DOM_MEDIA_METADATA_ERR,
122 RESULT_DETAIL("Got 0 sample info while verifying track."));
123 }
124
125 if (hasMultipleCodecs) {
126 // Different codecs in a single track. We don't handle this.
127 return MediaResult(
128 NS_ERROR_DOM_MEDIA_METADATA_ERR,
129 RESULT_DETAIL("Multiple codecs encountered while verifying track."));
130 }
131
132 return NS_OK;
133 }
134
Update(const Mp4parseTrackInfo * track,const Mp4parseTrackAudioInfo * audio)135 MediaResult MP4AudioInfo::Update(const Mp4parseTrackInfo* track,
136 const Mp4parseTrackAudioInfo* audio) {
137 auto rv = VerifyAudioOrVideoInfoAndRecordTelemetry(audio);
138 NS_ENSURE_SUCCESS(rv, rv);
139
140 Mp4parseCodec codecType = audio->sample_info[0].codec_type;
141 for (uint32_t i = 0; i < audio->sample_info_count; i++) {
142 if (audio->sample_info[i].protected_data.is_encrypted) {
143 auto rv =
144 UpdateTrackProtectedInfo(*this, audio->sample_info[i].protected_data);
145 NS_ENSURE_SUCCESS(rv, rv);
146 break;
147 }
148 }
149
150 // We assume that the members of the first sample info are representative of
151 // the entire track. This code will need to be updated should this assumption
152 // ever not hold. E.g. if we need to handle different codecs in a single
153 // track, or if we have different numbers or channels in a single track.
154 Mp4parseByteData codecSpecificConfig =
155 audio->sample_info[0].codec_specific_config;
156 if (codecType == MP4PARSE_CODEC_OPUS) {
157 mMimeType = "audio/opus"_ns;
158 // The Opus decoder expects the container's codec delay or
159 // pre-skip value, in microseconds, as a 64-bit int at the
160 // start of the codec-specific config blob.
161 if (codecSpecificConfig.data && codecSpecificConfig.length >= 12) {
162 uint16_t preskip =
163 mozilla::LittleEndian::readUint16(codecSpecificConfig.data + 10);
164 mozilla::OpusDataDecoder::AppendCodecDelay(
165 mCodecSpecificConfig, mozilla::FramesToUsecs(preskip, 48000).value());
166 } else {
167 // This file will error later as it will be rejected by the opus decoder.
168 mozilla::OpusDataDecoder::AppendCodecDelay(mCodecSpecificConfig, 0);
169 }
170 } else if (codecType == MP4PARSE_CODEC_AAC) {
171 mMimeType = "audio/mp4a-latm"_ns;
172 } else if (codecType == MP4PARSE_CODEC_FLAC) {
173 mMimeType = "audio/flac"_ns;
174 } else if (codecType == MP4PARSE_CODEC_MP3) {
175 mMimeType = "audio/mpeg"_ns;
176 }
177
178 mRate = audio->sample_info[0].sample_rate;
179 mChannels = audio->sample_info[0].channels;
180 mBitDepth = audio->sample_info[0].bit_depth;
181 mExtendedProfile = audio->sample_info[0].extended_profile;
182 mDuration = TimeUnit::FromMicroseconds(track->duration);
183 mMediaTime = TimeUnit::FromMicroseconds(track->media_time);
184 mTrackId = track->track_id;
185
186 // In stagefright, mProfile is kKeyAACProfile, mExtendedProfile is kKeyAACAOT.
187 if (audio->sample_info[0].profile <= 4) {
188 mProfile = audio->sample_info[0].profile;
189 }
190
191 Mp4parseByteData extraData = audio->sample_info[0].extra_data;
192 // If length is 0 we append nothing
193 mExtraData->AppendElements(extraData.data, extraData.length);
194 mCodecSpecificConfig->AppendElements(codecSpecificConfig.data,
195 codecSpecificConfig.length);
196 return NS_OK;
197 }
198
IsValid() const199 bool MP4AudioInfo::IsValid() const {
200 return mChannels > 0 && mRate > 0 &&
201 // Accept any mime type here, but if it's aac, validate the profile.
202 (!mMimeType.EqualsLiteral("audio/mp4a-latm") || mProfile > 0 ||
203 mExtendedProfile > 0);
204 }
205
Update(const Mp4parseTrackInfo * track,const Mp4parseTrackVideoInfo * video)206 MediaResult MP4VideoInfo::Update(const Mp4parseTrackInfo* track,
207 const Mp4parseTrackVideoInfo* video) {
208 auto rv = VerifyAudioOrVideoInfoAndRecordTelemetry(video);
209 NS_ENSURE_SUCCESS(rv, rv);
210
211 Mp4parseCodec codecType = video->sample_info[0].codec_type;
212 for (uint32_t i = 0; i < video->sample_info_count; i++) {
213 if (video->sample_info[i].protected_data.is_encrypted) {
214 auto rv =
215 UpdateTrackProtectedInfo(*this, video->sample_info[i].protected_data);
216 NS_ENSURE_SUCCESS(rv, rv);
217 break;
218 }
219 }
220
221 // We assume that the members of the first sample info are representative of
222 // the entire track. This code will need to be updated should this assumption
223 // ever not hold. E.g. if we need to handle different codecs in a single
224 // track, or if we have different numbers or channels in a single track.
225 if (codecType == MP4PARSE_CODEC_AVC) {
226 mMimeType = "video/avc"_ns;
227 } else if (codecType == MP4PARSE_CODEC_VP9) {
228 mMimeType = "video/vp9"_ns;
229 } else if (codecType == MP4PARSE_CODEC_AV1) {
230 mMimeType = "video/av1"_ns;
231 } else if (codecType == MP4PARSE_CODEC_MP4V) {
232 mMimeType = "video/mp4v-es"_ns;
233 }
234 mTrackId = track->track_id;
235 mDuration = TimeUnit::FromMicroseconds(track->duration);
236 mMediaTime = TimeUnit::FromMicroseconds(track->media_time);
237 mDisplay.width = video->display_width;
238 mDisplay.height = video->display_height;
239 mImage.width = video->sample_info[0].image_width;
240 mImage.height = video->sample_info[0].image_height;
241 mRotation = ToSupportedRotation(video->rotation);
242 Mp4parseByteData extraData = video->sample_info[0].extra_data;
243 // If length is 0 we append nothing
244 mExtraData->AppendElements(extraData.data, extraData.length);
245 return NS_OK;
246 }
247
IsValid() const248 bool MP4VideoInfo::IsValid() const {
249 return (mDisplay.width > 0 && mDisplay.height > 0) ||
250 (mImage.width > 0 && mImage.height > 0);
251 }
252
253 } // namespace mozilla
254