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