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