910e62b5创建于 1月15日历史提交
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "media/muxers/mp4_muxer_delegate.h"

#include "base/logging.h"
#include "base/notreached.h"
#include "components/version_info/version_info.h"
#include "media/base/audio_parameters.h"
#include "media/base/decoder_buffer.h"
#include "media/base/video_codecs.h"
#include "media/formats/mp4/avc.h"
#include "media/formats/mp4/box_constants.h"
#include "media/formats/mp4/box_definitions.h"
#include "media/formats/mp4/fourccs.h"
#include "media/formats/mp4/mp4_status.h"
#include "media/muxers/box_byte_stream.h"
#include "media/muxers/mp4_box_writer.h"
#include "media/muxers/mp4_fragment_box_writer.h"
#include "media/muxers/mp4_movie_box_writer.h"
#include "media/muxers/mp4_muxer_delegate_fragment.h"
#include "media/muxers/mp4_type_conversion.h"
#include "media/muxers/output_position_tracker.h"
#include "third_party/libgav1/src/src/obu_parser.h"

#if BUILDFLAG(USE_PROPRIETARY_CODECS) || \
    BUILDFLAG(ENABLE_HEVC_PARSER_AND_HW_DECODER)
#include "media/formats/mp4/h26x_annex_b_to_bitstream_converter.h"
#endif

#if BUILDFLAG(ENABLE_HEVC_PARSER_AND_HW_DECODER)
#include "media/formats/mp4/hevc.h"
#endif

namespace media {

namespace {

using mp4::writable_boxes::TrackHeaderFlags;

constexpr char kVideoHandlerName[] = "VideoHandler";
constexpr char kAudioHandlerName[] = "SoundHandler";
constexpr char kUndefinedLanguageName[] = "und";

// Milliseconds time scale is set in the Movie header and it will
// be the base for the duration.
constexpr uint32_t kMillisecondsTimeScale = 1000u;
constexpr uint32_t kAudioSamplesPerFrame = 1024u;

void BuildTrack(
    mp4::writable_boxes::Movie& moov,
    size_t track_index,
    bool is_audio,
    uint32_t timescale,
    const mp4::writable_boxes::SampleDescription& sample_description) {
  mp4::writable_boxes::Track& track = moov.tracks[track_index];
  // `tkhd`.
  mp4::writable_boxes::TrackHeader track_header(track_index + 1, is_audio);
  track_header.flags = BuildFlags<TrackHeaderFlags>(
      {TrackHeaderFlags::kTrackEnabled, TrackHeaderFlags::kTrackInMovie});
  track.header = std::move(track_header);

  // `mdhd`
  track.media.header.timescale = timescale;
  track.media.header.language =
      kUndefinedLanguageName;  // use 'und' as default at this time.

  // `dinf`, `dref`, `url`.
  mp4::writable_boxes::DataInformation data_info;
  mp4::writable_boxes::DataUrlEntry url;
  data_info.data_reference.entries.emplace_back(std::move(url));
  track.media.information.data_information = std::move(data_info);

  // `trex`.
  mp4::writable_boxes::TrackExtends& audio_extends =
      moov.extends.track_extends[track_index];
  audio_extends.track_id = track_index + 1;

  // TODO(crbug.com/40275472): Various MP4 samples doesn't need
  // default_sample_duration, default_sample_size, default_sample_flags. We need
  // to investigate it further though whether we need to set these fields.
  audio_extends.default_sample_description_index = 1;
  audio_extends.default_sample_size = 0;
  audio_extends.default_sample_duration = base::Milliseconds(0);
  audio_extends.default_sample_flags = 0;

  // `stbl`, `stco`, `stsz`, `stts`, `stsc'.
  mp4::writable_boxes::SampleTable sample_table;
  sample_table.sample_to_chunk = mp4::writable_boxes::SampleToChunk();
  sample_table.decoding_time_to_sample =
      mp4::writable_boxes::DecodingTimeToSample();
  sample_table.sample_size = mp4::writable_boxes::SampleSize();
  sample_table.sample_chunk_offset = mp4::writable_boxes::SampleChunkOffset();
  sample_table.sample_description = std::move(sample_description);

  track.media.information.sample_table = std::move(sample_table);
}

void CopyCreationTimeAndDuration(mp4::writable_boxes::Track& track,
                                 const mp4::writable_boxes::MovieHeader& header,
                                 base::TimeDelta track_duration) {
  track.header.creation_time = header.creation_time;
  track.header.modification_time = header.modification_time;

  track.media.header.creation_time = header.creation_time;
  track.media.header.modification_time = header.modification_time;

  // video track duration on the `tkhd` and `mdhd`.
  track.header.duration = track_duration;
  track.media.header.duration = track_duration;
}

}  // namespace

Mp4MuxerDelegate::Mp4MuxerDelegate(
    AudioCodec audio_codec,
    VideoCodec video_codec,
    std::optional<VideoCodecProfile> video_profile,
    std::optional<VideoCodecLevel> video_level,
    bool add_parameter_sets_in_bitstream,
    Muxer::WriteDataCB write_callback,
    size_t audio_sample_count_per_fragment)
    : write_callback_(std::move(write_callback)),
      audio_codec_(audio_codec),
      video_codec_(video_codec),
      video_profile_(std::move(video_profile)),
      video_level_(std::move(video_level)),
      add_parameter_sets_in_bitstream_(add_parameter_sets_in_bitstream),
      audio_sample_count_per_fragment_(audio_sample_count_per_fragment) {}

Mp4MuxerDelegate::~Mp4MuxerDelegate() = default;

void Mp4MuxerDelegate::AddVideoFrame(
    const Muxer::VideoParameters& params,
    scoped_refptr<DecoderBuffer> encoded_data,
    std::optional<VideoEncoder::CodecDescription> codec_description,
    base::TimeTicks timestamp) {
  DVLOG(1) << __func__ << ", " << params.AsHumanReadableString();

  if (!video_track_index_.has_value()) {
    CHECK(codec_description.has_value() || (params.codec != VideoCodec::kH264 &&
                                            params.codec != VideoCodec::kHEVC));
    CHECK(encoded_data->is_key_frame());
    CHECK(start_video_time_.is_null());
    CHECK_NE(params.codec, VideoCodec::kUnknown);
    CHECK_EQ(params.codec, video_codec_);

    EnsureInitialized();
    last_video_time_ = start_video_time_ = timestamp;

    CHECK_GT(params.frame_rate, 0);
    video_frame_rate_ = params.frame_rate;

    uint32_t timescale = video_frame_rate_ * kMillisecondsTimeScale;

    video_track_index_ = GetNextTrackIndex();
    context_->SetVideoTrack({video_track_index_.value(), timescale});
    DVLOG(1) << __func__ << ", video track timescale:" << timescale;

    BuildMovieVideoTrack(params, *encoded_data, std::move(codec_description));
  }
  last_video_time_ = timestamp;

  AddDataToVideoFragment(std::move(encoded_data));
}

void Mp4MuxerDelegate::BuildMovieVideoTrack(
    const Muxer::VideoParameters& params,
    const DecoderBuffer& encoded_data,
    std::optional<VideoEncoder::CodecDescription> codec_description) {
  DCHECK(video_track_index_.has_value());

  // `stsd`, `avc1`, `avcC`.
  mp4::writable_boxes::SampleDescription description;
  mp4::writable_boxes::VisualSampleEntry visual_sample_entry(video_codec_);

  visual_sample_entry.coded_size = params.visible_rect_size;
  visual_sample_entry.pixel_aspect_ratio =
      mp4::writable_boxes::PixelAspectRatioBox();

  if (video_codec_ == VideoCodec::kH264) {
#if BUILDFLAG(USE_PROPRIETARY_CODECS)
    visual_sample_entry.compressor_name =
        add_parameter_sets_in_bitstream_ ? "AVC3 Coding" : "AVC1 Coding";

    mp4::writable_boxes::AVCDecoderConfiguration avc_config;
    mp4::AVCDecoderConfigurationRecord avc_config_record;
    bool result = avc_config_record.Parse(codec_description.value());
    DCHECK(result);

    avc_config.avc_config_record = std::move(avc_config_record);
    avc_config.add_parameter_sets_in_bitstream =
        add_parameter_sets_in_bitstream_;
    visual_sample_entry.avc_decoder_configuration = std::move(avc_config);
#else
    NOTREACHED();
#endif
  } else if (video_codec_ == VideoCodec::kHEVC) {
#if BUILDFLAG(ENABLE_HEVC_PARSER_AND_HW_DECODER)
    visual_sample_entry.compressor_name =
        add_parameter_sets_in_bitstream_ ? "HEV1 Coding" : "HVC1 Coding";

    mp4::writable_boxes::HEVCDecoderConfiguration hevc_config;
    mp4::HEVCDecoderConfigurationRecord hevc_config_record;
    bool result = hevc_config_record.Parse(codec_description.value());
    DCHECK(result);

    hevc_config.hevc_config_record = std::move(hevc_config_record);
    hevc_config.add_parameter_sets_in_bitstream =
        add_parameter_sets_in_bitstream_;
    visual_sample_entry.hevc_decoder_configuration = std::move(hevc_config);
#else
    NOTREACHED();
#endif
  } else if (video_codec_ == VideoCodec::kVP9) {
    visual_sample_entry.compressor_name = "VPC Coding";

    gfx::ColorSpace color_space;
    if (params.color_space) {
      color_space = *params.color_space;
    }

    // DefaultCodecProfile() returns VP9PROFILE_PROFILE0(VP9PROFILE_MIN).
    mp4::writable_boxes::VPCodecConfiguration vp_config(
        video_profile_.value_or(VP9PROFILE_PROFILE0), video_level_.value_or(0),
        color_space);
    visual_sample_entry.vp_decoder_configuration = std::move(vp_config);
  } else if (video_codec_ == VideoCodec::kAV1) {
    CHECK(!codec_description.has_value());

    visual_sample_entry.compressor_name = "AV1 Coding";

    mp4::writable_boxes::AV1CodecConfiguration av1_config;
    size_t config_size = 0;
    auto encoded_data_span = base::span(encoded_data);
    auto codec_descriptions = libgav1::ObuParser::GetAV1CodecConfigurationBox(
        encoded_data_span.data(), encoded_data_span.size(), &config_size);
    CHECK(codec_descriptions);
    CHECK_GT(config_size, 0u);

    av1_config.av1_decoder_configuration_data.assign(
        &codec_descriptions[0], UNSAFE_TODO(&codec_descriptions[config_size]));
    visual_sample_entry.av1_decoder_configuration = std::move(av1_config);
  } else {
    NOTREACHED();
  }

  // `colr`
  if (params.color_space && params.color_space->IsValid()) {
    visual_sample_entry.color_information =
        mp4::writable_boxes::ColorInformation(
            VideoColorSpace::FromGfxColorSpace(*params.color_space));
  }

  description.video_sample_entry = std::move(visual_sample_entry);
  description.entry_count = 1;

  BuildTrack(*moov_, video_track_index_.value(), false,
             context_->GetVideoTrack().value().timescale, description);

  mp4::writable_boxes::Track& video_track =
      moov_->tracks[video_track_index_.value()];

  // `tkhd`.
  video_track.header.natural_size = params.visible_rect_size;
  if (params.transformation) {
    auto mat = params.transformation->GetMatrix();
    video_track.header.matrix[0] = mat[0];
    video_track.header.matrix[1] = mat[1];
    video_track.header.matrix[3] = mat[2];
    video_track.header.matrix[4] = mat[3];
  } else {
    // If VideoParameters do not have a VideoTransformation, we should provide a
    // default matrix that has zero rotations, zero mirroring, normal zoom.
    std::copy(std::begin(kDisplayIdentityMatrix),
              std::end(kDisplayIdentityMatrix), video_track.header.matrix);
  }

  // `hdlr`
  mp4::writable_boxes::MediaHandler media_handler(/*is_audio=*/false);
  media_handler.name = kVideoHandlerName;
  video_track.media.handler = std::move(media_handler);

  // `minf`

  // `vmhd`
  mp4::writable_boxes::VideoMediaHeader video_header;
  video_track.media.information.video_header = std::move(video_header);

  DVLOG(1) << __func__ << ", video track created";
}

void Mp4MuxerDelegate::AddDataToVideoFragment(
    scoped_refptr<DecoderBuffer> encoded_data) {
  DCHECK(video_track_index_.has_value());
  CreateFragmentIfNeeded(false, encoded_data->is_key_frame());

  Mp4MuxerDelegateFragment* fragment = fragments_.back().get();
  if (!fragment) {
    DVLOG(1) << __func__ << ", no valid video fragment exists";
    return;
  }

#if BUILDFLAG(USE_PROPRIETARY_CODECS)
  if (video_codec_ == VideoCodec::kH264
#if BUILDFLAG(ENABLE_HEVC_PARSER_AND_HW_DECODER)
      || video_codec_ == VideoCodec::kHEVC
#endif
  ) {
    // Convert Annex-B to AVC/HEVC bitstream.
    encoded_data = ConvertNALUData(std::move(encoded_data));
  }
#endif

  fragment->AddVideoData(std::move(encoded_data), last_video_time_);

  MaybeFlushFileTypeBoxForStartup();
}

void Mp4MuxerDelegate::AddAudioFrame(
    const AudioParameters& params,
    scoped_refptr<DecoderBuffer> encoded_data,
    std::optional<AudioEncoder::CodecDescription> codec_description,
    base::TimeTicks timestamp) {
  if (!audio_track_index_.has_value()) {
    DVLOG(1) << __func__ << ", " << params.AsHumanReadableString();

    CHECK(codec_description.has_value() || (audio_codec_ == AudioCodec::kOpus));
    CHECK(start_audio_time_.is_null());

    EnsureInitialized();
    last_audio_time_ = start_audio_time_ = timestamp;

    CHECK(params.IsValid());
    audio_sample_rate_ = params.sample_rate();
    audio_track_index_ = GetNextTrackIndex();

    context_->SetAudioTrack({audio_track_index_.value(),
                             static_cast<uint32_t>(audio_sample_rate_)});

    BuildMovieAudioTrack(params, *encoded_data, std::move(codec_description));
  }
  last_audio_time_ = timestamp;

  AddDataToAudioFragment(std::move(encoded_data));
}

void Mp4MuxerDelegate::BuildMovieAudioTrack(
    const AudioParameters& params,
    const DecoderBuffer& encoded_data,
    std::optional<AudioEncoder::CodecDescription> codec_description) {
  DCHECK(audio_track_index_.has_value());
  DCHECK(codec_description.has_value() || (audio_codec_ == AudioCodec::kOpus));

  // `stsd`, `mp4a`, `esds`, 'opus', 'dops'.
  mp4::writable_boxes::SampleDescription description;
  mp4::writable_boxes::AudioSampleEntry audio_sample_entry(
      audio_codec_, audio_sample_rate_, params.channels());

  if (audio_codec_ == AudioCodec::kAAC) {
#if BUILDFLAG(USE_PROPRIETARY_CODECS)
    mp4::writable_boxes::ElementaryStreamDescriptor
        elementary_stream_descriptor;
    elementary_stream_descriptor.aac_codec_description =
        std::move(codec_description.value());
    audio_sample_entry.elementary_stream_descriptor =
        std::move(elementary_stream_descriptor);
#else
    NOTREACHED();
#endif
  } else {
    // TODO(crbug.com/40281463): Ensure the below OpusSpecificBox is correct.
    CHECK_EQ(audio_codec_, AudioCodec::kOpus);
    mp4::writable_boxes::OpusSpecificBox opus_specific_box;
    opus_specific_box.channel_count = audio_sample_entry.channel_count;
    opus_specific_box.sample_rate = audio_sample_entry.sample_rate;
    audio_sample_entry.opus_specific_box = std::move(opus_specific_box);
  }

  description.audio_sample_entry = std::move(audio_sample_entry);
  description.entry_count = 1;

  BuildTrack(*moov_, audio_track_index_.value(), true,
             context_->GetAudioTrack().value().timescale, description);

  mp4::writable_boxes::Track& audio_track =
      moov_->tracks[audio_track_index_.value()];

  // `hdlr`
  mp4::writable_boxes::MediaHandler media_handler(/*is_audio=*/true);
  media_handler.name = kAudioHandlerName;
  audio_track.media.handler = std::move(media_handler);
  // `minf`

  // `smhd`
  mp4::writable_boxes::SoundMediaHeader sound_header;
  audio_track.media.information.sound_header = std::move(sound_header);
  DVLOG(1) << __func__ << ", audio track created";
}

void Mp4MuxerDelegate::AddDataToAudioFragment(
    scoped_refptr<DecoderBuffer> encoded_data) {
  DCHECK(audio_track_index_.has_value());
  CreateFragmentIfNeeded(true, false);

  Mp4MuxerDelegateFragment* fragment = fragments_.back().get();
  if (!fragment) {
    DVLOG(1) << __func__ << ", no valid audio fragment exists";
    return;
  }

  fragment->AddAudioData(std::move(encoded_data), last_audio_time_);
  MaybeFlushFileTypeBoxForStartup();
}

bool Mp4MuxerDelegate::FlushFragment() {
  // `live_mode_` is set to true regardless of `Flush()` failure as
  // `FlushFragment()` is called only when it is a live mode and it
  // will be referenced inside `Flush()` for the `mfra` box.
  live_mode_ = true;

  if (!Flush()) {
    DVLOG(1) << __func__
             << "flush fragment failed, it could be the first video frame";
    return false;
  }

  fragments_.clear();
  return true;
}

bool Mp4MuxerDelegate::Flush() {
  if (!video_track_index_.has_value() && !audio_track_index_.has_value()) {
    return false;
  }

  // File type box write will be called at the first frame arrival.
  size_t written_offset = MaybeFlushFileTypeBoxForStartup();

  // Moov box write could be called at the flush whether it is
  // fragment only by live mode, at the end for file mode.
  written_offset += MaybeFlushMoovBox();

  MaybeFlushMoofAndMfraBoxes(written_offset);

  return true;
}

size_t Mp4MuxerDelegate::MaybeFlushFileTypeBoxForStartup() {
  if (written_file_type_box_size_.has_value()) {
    return *written_file_type_box_size_;
  }

  // Build and write `FTYP` box.
  mp4::writable_boxes::FileType mp4_file_type_box(
      /*major_brand=*/mp4::FOURCC_ISOM, 512);
  BuildFileTypeBox(mp4_file_type_box);
  Mp4FileTypeBoxWriter file_type_box_writer(*context_, mp4_file_type_box);
  written_file_type_box_size_ = file_type_box_writer.WriteAndFlush();
  return *written_file_type_box_size_;
}

size_t Mp4MuxerDelegate::MaybeFlushMoovBox() {
  if (written_mov_box_size_.has_value()) {
    return *written_mov_box_size_;
  }

  BuildMovieBox();

  // Log blob info.
  LogBoxInfo();

  // Write `moov` box and its children.
  Mp4MovieBoxWriter movie_box_writer(*context_, *moov_);
  written_mov_box_size_ = movie_box_writer.WriteAndFlush();
  return *written_mov_box_size_;
}

void Mp4MuxerDelegate::MaybeFlushMoofAndMfraBoxes(size_t written_offset) {
  // Update the last sample timestamp for the fragments.
  for (auto& fragment : fragments_) {
    if (video_track_index_.has_value()) {
      CHECK_NE(video_frame_rate_, 0);
      fragment->AddVideoLastTimestamp(
          base::Milliseconds(kMillisecondsTimeScale / video_frame_rate_));
    }

    if (audio_track_index_.has_value()) {
      CHECK_NE(audio_sample_rate_, 0);
      int audio_frame_rate = audio_sample_rate_ / kAudioSamplesPerFrame;
      fragment->AddAudioLastTimestamp(
          base::Milliseconds(kMillisecondsTimeScale / audio_frame_rate));
    }
  }

  mp4::writable_boxes::TrackFragmentRandomAccess video_track_random_access;
  for (auto& fragment : fragments_) {
    if (video_track_index_.has_value() && !live_mode_) {
      base::TimeTicks start_timestamp = fragment->GetVideoStartTimestamp();
      if (start_timestamp.is_null()) {
        start_timestamp = start_video_time_;
      }
      BuildVideoTrackFragmentRandomAccess(
          start_timestamp, video_track_random_access, written_offset);
    }

    fragment->Finalize(start_audio_time_, start_video_time_);

    // `moof` and `mdat` should use same `BoxByteStream` as `moof`
    // has a dependency of `mdat` offset.
    Mp4MovieFragmentBoxWriter fragment_box_writer(*context_,
                                                  fragment->GetMovieFragment());
    BoxByteStream box_byte_stream;
    fragment_box_writer.Write(box_byte_stream);

    // Write `mdat` box with `moof` boxes writer object.
    Mp4MediaDataBoxWriter mdat_box_writer(*context_, fragment->GetMediaData());

    written_offset += mdat_box_writer.WriteAndFlush(box_byte_stream);
  }

  if (live_mode_) {
    // live mode can not have `mfra` box.
    return;
  }

  // Write `mfra` box as a last box for mp4 file.
  if (video_track_index_.has_value()) {
    video_track_random_access.track_id = video_track_index_.value() + 1;

    mp4::writable_boxes::FragmentRandomAccess fragment_random_access;
    mp4::writable_boxes::TrackFragmentRandomAccess audio_random_access;

    // Add audio random access first as it is 0 index by default.
    fragment_random_access.tracks.emplace_back(std::move(audio_random_access));
    fragment_random_access.tracks.emplace_back(
        std::move(video_track_random_access));
    if (video_track_index_.value() == 0) {
      std::swap(fragment_random_access.tracks[kDefaultAudioIndex],
                fragment_random_access.tracks[kDefaultVideoIndex]);
    }

    // Flush at requested.
    Mp4FragmentRandomAccessBoxWriter fragment_random_access_box_writer(
        *context_, fragment_random_access);
    fragment_random_access_box_writer.WriteAndFlush();
  }

  fragments_.clear();
}

void Mp4MuxerDelegate::BuildFileTypeBox(
    mp4::writable_boxes::FileType& mp4_file_type_box) {
  mp4_file_type_box.compatible_brands.emplace_back(mp4::FOURCC_ISOM);
  mp4_file_type_box.compatible_brands.emplace_back(mp4::FOURCC_ISO6);
  mp4_file_type_box.compatible_brands.emplace_back(mp4::FOURCC_ISO2);
  switch (video_codec_) {
    case VideoCodec::kVP9:
      mp4_file_type_box.compatible_brands.emplace_back(mp4::FOURCC_VP09);
      break;
    case VideoCodec::kAV1:
      mp4_file_type_box.compatible_brands.emplace_back(mp4::FOURCC_AV01);
      break;
#if BUILDFLAG(ENABLE_HEVC_PARSER_AND_HW_DECODER)
    case VideoCodec::kHEVC:
      mp4_file_type_box.compatible_brands.emplace_back(
          add_parameter_sets_in_bitstream_ ? mp4::FOURCC_HEV1
                                           : mp4::FOURCC_HVC1);
      break;
#endif
    case VideoCodec::kH264:
    default:
      mp4_file_type_box.compatible_brands.emplace_back(
          add_parameter_sets_in_bitstream_ ? mp4::FOURCC_AVC3
                                           : mp4::FOURCC_AVC1);
      break;
  }
  mp4_file_type_box.compatible_brands.emplace_back(mp4::FOURCC_MP41);
}

void Mp4MuxerDelegate::BuildMovieBox() {
  // It will be called during Flush time.
  moov_->header.creation_time = base::Time::Now();
  moov_->header.modification_time = moov_->header.creation_time;

  // Milliseconds timescale for movie header.
  moov_->header.timescale = kMillisecondsTimeScale;

  // Use inverse video frame rate just in case when it has a single frame.
  base::TimeDelta video_track_duration = base::Seconds(0);
  if (video_track_index_.has_value()) {
    CHECK_NE(video_frame_rate_, 0);
    base::TimeTicks last_video_time_include_last_sample =
        last_video_time_ +
        base::Milliseconds(kMillisecondsTimeScale / video_frame_rate_);
    video_track_duration =
        last_video_time_include_last_sample - start_video_time_;
    CopyCreationTimeAndDuration(moov_->tracks[video_track_index_.value()],
                                moov_->header, video_track_duration);
  }

  // Use inverse audio sample rate just in case when it has a single frame.
  base::TimeDelta audio_track_duration = base::Seconds(0);
  if (audio_track_index_.has_value()) {
    CHECK_NE(audio_sample_rate_, 0);

    int audio_frame_rate = audio_sample_rate_ / kAudioSamplesPerFrame;
    base::TimeTicks last_audio_time_include_last_sample =
        last_audio_time_ +
        base::Milliseconds(kMillisecondsTimeScale / audio_frame_rate);
    audio_track_duration =
        last_audio_time_include_last_sample - start_audio_time_;
    CopyCreationTimeAndDuration(moov_->tracks[audio_track_index_.value()],
                                moov_->header, audio_track_duration);
  }

  if (live_mode_) {
    // live should not have duration.
    return;
  }

  // Update the track's duration that the longest duration on the track
  // whether it is video or audio.
  moov_->header.duration = std::max(video_track_duration, audio_track_duration);

  moov_->header.next_track_id = next_track_index_ + 1;
}

void Mp4MuxerDelegate::BuildVideoTrackFragmentRandomAccess(
    base::TimeTicks start_video_time,
    mp4::writable_boxes::TrackFragmentRandomAccess&
        fragment_random_access_box_writer,
    size_t written_offset) {
  mp4::writable_boxes::TrackFragmentRandomAccessEntry entry;
  // `time` is a duration since its target track's start until the
  // previous fragment, which is presentation time of the track.
  entry.time = start_video_time - start_video_time_;
  entry.moof_offset = written_offset;
  entry.traf_number = 1;
  entry.trun_number = 1;
  entry.sample_number = 1;
  fragment_random_access_box_writer.entries.emplace_back(std::move(entry));
}

void Mp4MuxerDelegate::CreateFragmentIfNeeded(bool audio, bool is_key_frame) {
  CHECK(video_track_index_.has_value() || audio_track_index_.has_value());
  bool is_audio_only = !video_track_index_.has_value();

  // Create new fragment if the key frame is found or number of audio
  // samples exceeds the limit when the video is not found.
  bool create_fragment = fragments_.empty();

  if (!create_fragment && is_key_frame) {
    // It is video key frame that should create new fragment unless
    // it is the first fragment that already exists by the earlier audio
    // samples.
    Mp4MuxerDelegateFragment* fragment = fragments_.back().get();
    create_fragment = fragment->GetVideoSampleSize() >= 1;
  }

  if (!create_fragment && is_audio_only) {
    // The audio only fragment is created when the audio samples exceed the
    // limit.
    Mp4MuxerDelegateFragment* fragment = fragments_.back().get();
    create_fragment =
        fragment->GetAudioSampleSize() >= audio_sample_count_per_fragment_;
  }

  if (!create_fragment) {
    return;
  }

  int video_track_id =
      video_track_index_.has_value() ? *video_track_index_ + 1 : 2;
  int audio_track_id =
      audio_track_index_.has_value() ? *audio_track_index_ + 1 : 2;
  auto new_fragment = std::make_unique<Mp4MuxerDelegateFragment>(
      *context_, video_track_id, audio_track_id, sequence_number_++);

  fragments_.emplace_back(std::move(new_fragment));
}

void Mp4MuxerDelegate::EnsureInitialized() {
  if (context_) {
    return;
  }

  // `write_callback_` continue to be used even after `Reset`.
  auto output_position_tracker =
      std::make_unique<OutputPositionTracker>(write_callback_);

  context_ =
      std::make_unique<Mp4MuxerContext>(std::move(output_position_tracker));

  moov_ = std::make_unique<mp4::writable_boxes::Movie>();

  // We add two tracks to the moov box, one for video and one for audio, but
  // we don't know which is which yet. The correct fields will be filled in
  // when the first video or audio frame is added.
  moov_->tracks.emplace_back(0, false);
  moov_->tracks.emplace_back(0, false);

  moov_->extends.track_extends.emplace_back(
      mp4::writable_boxes::TrackExtends());
  moov_->extends.track_extends.emplace_back(
      mp4::writable_boxes::TrackExtends());
}

#if BUILDFLAG(USE_PROPRIETARY_CODECS) || \
    BUILDFLAG(ENABLE_HEVC_PARSER_AND_HW_DECODER)
scoped_refptr<DecoderBuffer> Mp4MuxerDelegate::ConvertNALUData(
    scoped_refptr<DecoderBuffer> encoded_data) {
  CHECK(video_codec_ == VideoCodec::kH264 || video_codec_ == VideoCodec::kHEVC);
  if (!h26x_converter_) {
    h26x_converter_ = std::make_unique<H26xAnnexBToBitstreamConverter>(
        video_codec_, add_parameter_sets_in_bitstream_);
  }

  return h26x_converter_->Convert(*encoded_data);
}
#endif  // BUILDFLAG(USE_PROPRIETARY_CODECS) ||
        // BUILDFLAG(ENABLE_HEVC_PARSER_AND_HW_DECODER)

int Mp4MuxerDelegate::GetNextTrackIndex() {
  return next_track_index_++;
}

void Mp4MuxerDelegate::LogBoxInfo() const {
  std::ostringstream s;

  s << "movie timescale:" << moov_->header.timescale
    << ", duration in seconds:" << moov_->header.duration.InSeconds();
  if (video_track_index_.has_value()) {
    mp4::writable_boxes::Track& track =
        moov_->tracks[video_track_index_.value()];
    s << ", video track index:" << video_track_index_.value()
      << ", video track timescale:"
      << context_->GetVideoTrack().value().timescale
      << ", video track duration:" << track.header.duration;
  }

  if (audio_track_index_.has_value()) {
    mp4::writable_boxes::Track& track =
        moov_->tracks[audio_track_index_.value()];
    s << ", audio track index:" << audio_track_index_.value()
      << ", audio track timescale:"
      << context_->GetAudioTrack().value().timescale
      << ", audio track duration:" << track.header.duration;
  }
  s << ", Fragment counts:" << fragments_.size();

  DVLOG(1) << __func__ << ", " << s.str();
}

}  // namespace media