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