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 "mozilla/Assertions.h"
6 #include "mozilla/CheckedInt.h"
7 #include "mozilla/EndianUtils.h"
8 #include "mozilla/Logging.h"
9 #include "mozilla/RefPtr.h"
10 #include "mozilla/Telemetry.h"
11 #include "mozilla/UniquePtr.h"
12 #include "VideoUtils.h"
13 #include "MoofParser.h"
14 #include "MP4Metadata.h"
15 #include "ByteStream.h"
16 #include "mp4parse.h"
17 
18 #include <limits>
19 #include <stdint.h>
20 #include <vector>
21 
22 using mozilla::media::TimeUnit;
23 
24 namespace mozilla {
25 LazyLogModule gMP4MetadataLog("MP4Metadata");
26 
IndiceWrapper(Mp4parseByteData & aIndice)27 IndiceWrapper::IndiceWrapper(Mp4parseByteData& aIndice) {
28   mIndice.data = nullptr;
29   mIndice.length = aIndice.length;
30   mIndice.indices = aIndice.indices;
31 }
32 
Length() const33 size_t IndiceWrapper::Length() const { return mIndice.length; }
34 
GetIndice(size_t aIndex,Index::Indice & aIndice) const35 bool IndiceWrapper::GetIndice(size_t aIndex, Index::Indice& aIndice) const {
36   if (aIndex >= mIndice.length) {
37     MOZ_LOG(gMP4MetadataLog, LogLevel::Error, ("Index overflow in indice"));
38     return false;
39   }
40 
41   const Mp4parseIndice* indice = &mIndice.indices[aIndex];
42   aIndice.start_offset = indice->start_offset;
43   aIndice.end_offset = indice->end_offset;
44   aIndice.start_composition = indice->start_composition;
45   aIndice.end_composition = indice->end_composition;
46   aIndice.start_decode = indice->start_decode;
47   aIndice.sync = indice->sync;
48   return true;
49 }
50 
TrackTypeToString(mozilla::TrackInfo::TrackType aType)51 static const char* TrackTypeToString(mozilla::TrackInfo::TrackType aType) {
52   switch (aType) {
53     case mozilla::TrackInfo::kAudioTrack:
54       return "audio";
55     case mozilla::TrackInfo::kVideoTrack:
56       return "video";
57     default:
58       return "unknown";
59   }
60 }
61 
Read(uint8_t * buffer,uintptr_t size,size_t * bytes_read)62 bool StreamAdaptor::Read(uint8_t* buffer, uintptr_t size, size_t* bytes_read) {
63   if (!mOffset.isValid()) {
64     MOZ_LOG(gMP4MetadataLog, LogLevel::Error,
65             ("Overflow in source stream offset"));
66     return false;
67   }
68   bool rv = mSource->ReadAt(mOffset.value(), buffer, size, bytes_read);
69   if (rv) {
70     mOffset += *bytes_read;
71   }
72   return rv;
73 }
74 
75 // Wrapper to allow rust to call our read adaptor.
read_source(uint8_t * buffer,uintptr_t size,void * userdata)76 static intptr_t read_source(uint8_t* buffer, uintptr_t size, void* userdata) {
77   MOZ_ASSERT(buffer);
78   MOZ_ASSERT(userdata);
79 
80   auto source = reinterpret_cast<StreamAdaptor*>(userdata);
81   size_t bytes_read = 0;
82   bool rv = source->Read(buffer, size, &bytes_read);
83   if (!rv) {
84     MOZ_LOG(gMP4MetadataLog, LogLevel::Warning, ("Error reading source data"));
85     return -1;
86   }
87   return bytes_read;
88 }
89 
MP4Metadata(ByteStream * aSource)90 MP4Metadata::MP4Metadata(ByteStream* aSource)
91     : mSource(aSource), mSourceAdaptor(aSource) {
92   DDLINKCHILD("source", aSource);
93 }
94 
95 MP4Metadata::~MP4Metadata() = default;
96 
Parse()97 nsresult MP4Metadata::Parse() {
98   Mp4parseIo io = {read_source, &mSourceAdaptor};
99   Mp4parseParser* parser = nullptr;
100   Mp4parseStatus status = mp4parse_new(&io, &parser);
101   if (status == MP4PARSE_STATUS_OK && parser) {
102     mParser.reset(parser);
103     MOZ_ASSERT(mParser);
104   } else {
105     MOZ_ASSERT(!mParser);
106     MOZ_LOG(gMP4MetadataLog, LogLevel::Debug,
107             ("Parse failed, return code %d\n", status));
108     return status == MP4PARSE_STATUS_OOM ? NS_ERROR_OUT_OF_MEMORY
109                                          : NS_ERROR_DOM_MEDIA_METADATA_ERR;
110   }
111 
112   UpdateCrypto();
113 
114   return NS_OK;
115 }
116 
UpdateCrypto()117 void MP4Metadata::UpdateCrypto() {
118   Mp4parsePsshInfo info = {};
119   if (mp4parse_get_pssh_info(mParser.get(), &info) != MP4PARSE_STATUS_OK) {
120     return;
121   }
122 
123   if (info.data.length == 0) {
124     return;
125   }
126 
127   mCrypto.Update(info.data.data, info.data.length);
128 }
129 
TrackTypeEqual(TrackInfo::TrackType aLHS,Mp4parseTrackType aRHS)130 bool TrackTypeEqual(TrackInfo::TrackType aLHS, Mp4parseTrackType aRHS) {
131   switch (aLHS) {
132     case TrackInfo::kAudioTrack:
133       return aRHS == MP4PARSE_TRACK_TYPE_AUDIO;
134     case TrackInfo::kVideoTrack:
135       return aRHS == MP4PARSE_TRACK_TYPE_VIDEO;
136     default:
137       return false;
138   }
139 }
140 
GetNumberTracks(mozilla::TrackInfo::TrackType aType) const141 MP4Metadata::ResultAndTrackCount MP4Metadata::GetNumberTracks(
142     mozilla::TrackInfo::TrackType aType) const {
143   uint32_t tracks;
144   auto rv = mp4parse_get_track_count(mParser.get(), &tracks);
145   if (rv != MP4PARSE_STATUS_OK) {
146     MOZ_LOG(gMP4MetadataLog, LogLevel::Warning,
147             ("rust parser error %d counting tracks", rv));
148     return {MediaResult(NS_ERROR_DOM_MEDIA_METADATA_ERR,
149                         RESULT_DETAIL("Rust parser error %d", rv)),
150             MP4Metadata::NumberTracksError()};
151   }
152 
153   uint32_t total = 0;
154   for (uint32_t i = 0; i < tracks; ++i) {
155     Mp4parseTrackInfo track_info;
156     rv = mp4parse_get_track_info(mParser.get(), i, &track_info);
157     if (rv != MP4PARSE_STATUS_OK) {
158       continue;
159     }
160 
161     if (track_info.track_type == MP4PARSE_TRACK_TYPE_AUDIO) {
162       Mp4parseTrackAudioInfo audio;
163       auto rv = mp4parse_get_track_audio_info(mParser.get(), i, &audio);
164       if (rv != MP4PARSE_STATUS_OK) {
165         MOZ_LOG(gMP4MetadataLog, LogLevel::Warning,
166                 ("mp4parse_get_track_audio_info returned error %d", rv));
167         continue;
168       }
169       MOZ_DIAGNOSTIC_ASSERT(audio.sample_info_count > 0,
170                             "Must have at least one audio sample info");
171       if (audio.sample_info_count == 0) {
172         return {
173             MediaResult(
174                 NS_ERROR_DOM_MEDIA_METADATA_ERR,
175                 RESULT_DETAIL(
176                     "Got 0 audio sample info while checking number tracks")),
177             MP4Metadata::NumberTracksError()};
178       }
179       // We assume the codec of the first sample info is representative of the
180       // whole track and skip it if we don't recognize the codec.
181       if (audio.sample_info[0].codec_type == MP4PARSE_CODEC_UNKNOWN) {
182         continue;
183       }
184     } else if (track_info.track_type == MP4PARSE_TRACK_TYPE_VIDEO) {
185       Mp4parseTrackVideoInfo video;
186       auto rv = mp4parse_get_track_video_info(mParser.get(), i, &video);
187       if (rv != MP4PARSE_STATUS_OK) {
188         MOZ_LOG(gMP4MetadataLog, LogLevel::Warning,
189                 ("mp4parse_get_track_video_info returned error %d", rv));
190         continue;
191       }
192       MOZ_DIAGNOSTIC_ASSERT(video.sample_info_count > 0,
193                             "Must have at least one video sample info");
194       if (video.sample_info_count == 0) {
195         return {
196             MediaResult(
197                 NS_ERROR_DOM_MEDIA_METADATA_ERR,
198                 RESULT_DETAIL(
199                     "Got 0 video sample info while checking number tracks")),
200             MP4Metadata::NumberTracksError()};
201       }
202       // We assume the codec of the first sample info is representative of the
203       // whole track and skip it if we don't recognize the codec.
204       if (video.sample_info[0].codec_type == MP4PARSE_CODEC_UNKNOWN) {
205         continue;
206       }
207     } else {
208       // Only audio and video are supported
209       continue;
210     }
211     if (TrackTypeEqual(aType, track_info.track_type)) {
212       total += 1;
213     }
214   }
215 
216   MOZ_LOG(gMP4MetadataLog, LogLevel::Info,
217           ("%s tracks found: %u", TrackTypeToString(aType), total));
218 
219   return {NS_OK, total};
220 }
221 
TrackTypeToGlobalTrackIndex(mozilla::TrackInfo::TrackType aType,size_t aTrackNumber) const222 Maybe<uint32_t> MP4Metadata::TrackTypeToGlobalTrackIndex(
223     mozilla::TrackInfo::TrackType aType, size_t aTrackNumber) const {
224   uint32_t tracks;
225   auto rv = mp4parse_get_track_count(mParser.get(), &tracks);
226   if (rv != MP4PARSE_STATUS_OK) {
227     return Nothing();
228   }
229 
230   /* The MP4Metadata API uses a per-TrackType index of tracks, but mp4parse
231      (and libstagefright) use a global track index.  Convert the index by
232      counting the tracks of the requested type and returning the global
233      track index when a match is found. */
234   uint32_t perType = 0;
235   for (uint32_t i = 0; i < tracks; ++i) {
236     Mp4parseTrackInfo track_info;
237     rv = mp4parse_get_track_info(mParser.get(), i, &track_info);
238     if (rv != MP4PARSE_STATUS_OK) {
239       continue;
240     }
241     if (TrackTypeEqual(aType, track_info.track_type)) {
242       if (perType == aTrackNumber) {
243         return Some(i);
244       }
245       perType += 1;
246     }
247   }
248 
249   return Nothing();
250 }
251 
GetTrackInfo(mozilla::TrackInfo::TrackType aType,size_t aTrackNumber) const252 MP4Metadata::ResultAndTrackInfo MP4Metadata::GetTrackInfo(
253     mozilla::TrackInfo::TrackType aType, size_t aTrackNumber) const {
254   Maybe<uint32_t> trackIndex = TrackTypeToGlobalTrackIndex(aType, aTrackNumber);
255   if (trackIndex.isNothing()) {
256     return {MediaResult(NS_ERROR_DOM_MEDIA_METADATA_ERR,
257                         RESULT_DETAIL("No %s tracks", TrackTypeToStr(aType))),
258             nullptr};
259   }
260 
261   Mp4parseTrackInfo info;
262   auto rv = mp4parse_get_track_info(mParser.get(), trackIndex.value(), &info);
263   if (rv != MP4PARSE_STATUS_OK) {
264     MOZ_LOG(gMP4MetadataLog, LogLevel::Warning,
265             ("mp4parse_get_track_info returned %d", rv));
266     return {MediaResult(NS_ERROR_DOM_MEDIA_METADATA_ERR,
267                         RESULT_DETAIL("Cannot find %s track #%zu",
268                                       TrackTypeToStr(aType), aTrackNumber)),
269             nullptr};
270   }
271 #ifdef DEBUG
272   bool haveSampleInfo = false;
273   const char* codecString = "unrecognized";
274   Mp4parseCodec codecType = MP4PARSE_CODEC_UNKNOWN;
275   if (info.track_type == MP4PARSE_TRACK_TYPE_AUDIO) {
276     Mp4parseTrackAudioInfo audio;
277     auto rv = mp4parse_get_track_audio_info(mParser.get(), trackIndex.value(),
278                                             &audio);
279     if (rv == MP4PARSE_STATUS_OK && audio.sample_info_count > 0) {
280       codecType = audio.sample_info[0].codec_type;
281       haveSampleInfo = true;
282     }
283   } else if (info.track_type == MP4PARSE_TRACK_TYPE_VIDEO) {
284     Mp4parseTrackVideoInfo video;
285     auto rv = mp4parse_get_track_video_info(mParser.get(), trackIndex.value(),
286                                             &video);
287     if (rv == MP4PARSE_STATUS_OK && video.sample_info_count > 0) {
288       codecType = video.sample_info[0].codec_type;
289       haveSampleInfo = true;
290     }
291   }
292   if (haveSampleInfo) {
293     switch (codecType) {
294       case MP4PARSE_CODEC_UNKNOWN:
295         codecString = "unknown";
296         break;
297       case MP4PARSE_CODEC_AAC:
298         codecString = "aac";
299         break;
300       case MP4PARSE_CODEC_OPUS:
301         codecString = "opus";
302         break;
303       case MP4PARSE_CODEC_FLAC:
304         codecString = "flac";
305         break;
306       case MP4PARSE_CODEC_ALAC:
307         codecString = "alac";
308         break;
309       case MP4PARSE_CODEC_AVC:
310         codecString = "h.264";
311         break;
312       case MP4PARSE_CODEC_VP9:
313         codecString = "vp9";
314         break;
315       case MP4PARSE_CODEC_AV1:
316         codecString = "av1";
317         break;
318       case MP4PARSE_CODEC_MP3:
319         codecString = "mp3";
320         break;
321       case MP4PARSE_CODEC_MP4V:
322         codecString = "mp4v";
323         break;
324       case MP4PARSE_CODEC_JPEG:
325         codecString = "jpeg";
326         break;
327       case MP4PARSE_CODEC_AC3:
328         codecString = "ac-3";
329         break;
330       case MP4PARSE_CODEC_EC3:
331         codecString = "ec-3";
332         break;
333     }
334   }
335   MOZ_LOG(gMP4MetadataLog, LogLevel::Debug,
336           ("track codec %s (%u)\n", codecString, codecType));
337 #endif
338 
339   // This specialization interface is crazy.
340   UniquePtr<mozilla::TrackInfo> e;
341   switch (aType) {
342     case TrackInfo::TrackType::kAudioTrack: {
343       Mp4parseTrackAudioInfo audio;
344       auto rv = mp4parse_get_track_audio_info(mParser.get(), trackIndex.value(),
345                                               &audio);
346       if (rv != MP4PARSE_STATUS_OK) {
347         MOZ_LOG(gMP4MetadataLog, LogLevel::Warning,
348                 ("mp4parse_get_track_audio_info returned error %d", rv));
349         return {MediaResult(NS_ERROR_DOM_MEDIA_METADATA_ERR,
350                             RESULT_DETAIL("Cannot parse %s track #%zu",
351                                           TrackTypeToStr(aType), aTrackNumber)),
352                 nullptr};
353       }
354       auto track = mozilla::MakeUnique<MP4AudioInfo>();
355       MediaResult updateStatus = track->Update(&info, &audio);
356       if (NS_FAILED(updateStatus)) {
357         MOZ_LOG(gMP4MetadataLog, LogLevel::Warning,
358                 ("Updating audio track failed with %s",
359                  updateStatus.Message().get()));
360         return {MediaResult(NS_ERROR_DOM_MEDIA_METADATA_ERR,
361                             RESULT_DETAIL(
362                                 "Failed to update %s track #%zu with error: %s",
363                                 TrackTypeToStr(aType), aTrackNumber,
364                                 updateStatus.Message().get())),
365                 nullptr};
366       }
367       e = std::move(track);
368     } break;
369     case TrackInfo::TrackType::kVideoTrack: {
370       Mp4parseTrackVideoInfo video;
371       auto rv = mp4parse_get_track_video_info(mParser.get(), trackIndex.value(),
372                                               &video);
373       if (rv != MP4PARSE_STATUS_OK) {
374         MOZ_LOG(gMP4MetadataLog, LogLevel::Warning,
375                 ("mp4parse_get_track_video_info returned error %d", rv));
376         return {MediaResult(NS_ERROR_DOM_MEDIA_METADATA_ERR,
377                             RESULT_DETAIL("Cannot parse %s track #%zu",
378                                           TrackTypeToStr(aType), aTrackNumber)),
379                 nullptr};
380       }
381       auto track = mozilla::MakeUnique<MP4VideoInfo>();
382       MediaResult updateStatus = track->Update(&info, &video);
383       if (NS_FAILED(updateStatus)) {
384         MOZ_LOG(gMP4MetadataLog, LogLevel::Warning,
385                 ("Updating video track failed with %s",
386                  updateStatus.Message().get()));
387         return {MediaResult(NS_ERROR_DOM_MEDIA_METADATA_ERR,
388                             RESULT_DETAIL(
389                                 "Failed to update %s track #%zu with error: %s",
390                                 TrackTypeToStr(aType), aTrackNumber,
391                                 updateStatus.Message().get())),
392                 nullptr};
393       }
394       e = std::move(track);
395     } break;
396     default:
397       MOZ_LOG(gMP4MetadataLog, LogLevel::Warning,
398               ("unhandled track type %d", aType));
399       return {MediaResult(NS_ERROR_DOM_MEDIA_METADATA_ERR,
400                           RESULT_DETAIL("Cannot handle %s track #%zu",
401                                         TrackTypeToStr(aType), aTrackNumber)),
402               nullptr};
403   }
404 
405   // No duration in track, use fragment_duration.
406   if (e && !e->mDuration.IsPositive()) {
407     Mp4parseFragmentInfo info;
408     auto rv = mp4parse_get_fragment_info(mParser.get(), &info);
409     if (rv == MP4PARSE_STATUS_OK) {
410       e->mDuration = TimeUnit::FromMicroseconds(info.fragment_duration);
411     }
412   }
413 
414   if (e && e->IsValid()) {
415     return {NS_OK, std::move(e)};
416   }
417   MOZ_LOG(gMP4MetadataLog, LogLevel::Debug, ("TrackInfo didn't validate"));
418 
419   return {MediaResult(NS_ERROR_DOM_MEDIA_METADATA_ERR,
420                       RESULT_DETAIL("Invalid %s track #%zu",
421                                     TrackTypeToStr(aType), aTrackNumber)),
422           nullptr};
423 }
424 
CanSeek() const425 bool MP4Metadata::CanSeek() const { return true; }
426 
Crypto() const427 MP4Metadata::ResultAndCryptoFile MP4Metadata::Crypto() const {
428   return {NS_OK, &mCrypto};
429 }
430 
GetTrackIndice(uint32_t aTrackId)431 MP4Metadata::ResultAndIndice MP4Metadata::GetTrackIndice(uint32_t aTrackId) {
432   Mp4parseByteData indiceRawData = {};
433 
434   uint8_t fragmented = false;
435   auto rv = mp4parse_is_fragmented(mParser.get(), aTrackId, &fragmented);
436   if (rv != MP4PARSE_STATUS_OK) {
437     return {MediaResult(NS_ERROR_DOM_MEDIA_METADATA_ERR,
438                         RESULT_DETAIL("Cannot parse whether track id %u is "
439                                       "fragmented, mp4parse_error=%d",
440                                       aTrackId, int(rv))),
441             nullptr};
442   }
443 
444   if (!fragmented) {
445     rv = mp4parse_get_indice_table(mParser.get(), aTrackId, &indiceRawData);
446     if (rv != MP4PARSE_STATUS_OK) {
447       return {
448           MediaResult(NS_ERROR_DOM_MEDIA_METADATA_ERR,
449                       RESULT_DETAIL("Cannot parse index table in track id %u, "
450                                     "mp4parse_error=%d",
451                                     aTrackId, int(rv))),
452           nullptr};
453     }
454   }
455 
456   UniquePtr<IndiceWrapper> indice;
457   indice = mozilla::MakeUnique<IndiceWrapper>(indiceRawData);
458 
459   return {NS_OK, std::move(indice)};
460 }
461 
Metadata(ByteStream * aSource)462 /*static*/ MP4Metadata::ResultAndByteBuffer MP4Metadata::Metadata(
463     ByteStream* aSource) {
464   auto parser = mozilla::MakeUnique<MoofParser>(
465       aSource, AsVariant(ParseAllTracks{}), false);
466   RefPtr<mozilla::MediaByteBuffer> buffer = parser->Metadata();
467   if (!buffer) {
468     return {MediaResult(NS_ERROR_DOM_MEDIA_METADATA_ERR,
469                         RESULT_DETAIL("Cannot parse metadata")),
470             nullptr};
471   }
472   return {NS_OK, std::move(buffer)};
473 }
474 
475 }  // namespace mozilla
476