tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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