blob: 3dd3dfc9816ee54f1a88d449eb630988b194eab5 [file] [log] [blame]
// 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_movie_box_writer.h"
#include <memory>
#include <string>
#include <vector>
#include "base/big_endian.h"
#include "base/numerics/safe_conversions.h"
#include "media/base/audio_timestamp_helper.h"
#include "media/formats/mp4/es_descriptor.h"
#include "media/muxers/box_byte_stream.h"
#include "media/muxers/mp4_muxer_context.h"
#include "media/muxers/mp4_type_conversion.h"
#include "media/muxers/output_position_tracker.h"
namespace media {
namespace {
// ISO/IEC 14496-12.
// A transformation matrix for the video.
// Video frames are not scaled, rotated, or skewed, and are displayed at
// their original size with no zoom or depth applied.
// The value 0x00010000 in the top-left and middle element of the
// matrix specifies the horizontal and vertical scaling factor,
// respectively. This means that the video frames are not scaled and
// are displayed at their original size.
// The bottom-right element of the matrix, with a value of 0x40000000,
// specifies the fixed-point value of the zoom or depth of the video frames.
// This value is equal to 1.0 in decimal notation, meaning that there
// is no zoom or depth applied to the video frames.
constexpr int32_t kDisplayIdentityMatrix[9] = {
0x00010000, 0, 0, 0, 0x00010000, 0, 0, 0, 0x40000000};
void WriteIsoTime(BoxByteStream& writer, base::Time time) {
uint64_t iso_time =
base::saturated_cast<uint64_t>((time - kMP4Epoch).InSecondsF());
writer.WriteU64(iso_time);
}
void WriteLowHigh(BoxByteStream& writer, uint32_t value) {
writer.WriteU16(value & 0xFFFF);
writer.WriteU16(value >> 16);
}
} // namespace
// Mp4FileTypeBoxWriter class.
Mp4FileTypeBoxWriter::Mp4FileTypeBoxWriter(
const Mp4MuxerContext& context,
const mp4::writable_boxes::FileType& box)
: Mp4BoxWriter(context), box_(box) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
Mp4FileTypeBoxWriter::~Mp4FileTypeBoxWriter() = default;
void Mp4FileTypeBoxWriter::Write(BoxByteStream& writer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
writer.StartBox(mp4::FOURCC_FTYP);
writer.WriteU32(box_.major_brand); // normal rate.
writer.WriteU32(box_.minor_version); // normal rate.
// It should include at least of `avc1`.
CHECK_GE(box_.compatible_brands.size(), 1u);
CHECK(box_.compatible_brands.end() !=
std::find(box_.compatible_brands.begin(), box_.compatible_brands.end(),
mp4::FOURCC_AVC1));
for (const uint32_t& brand : box_.compatible_brands) {
writer.WriteU32(brand);
}
writer.EndBox();
}
// Mp4MovieBoxWriter class.
Mp4MovieBoxWriter::Mp4MovieBoxWriter(const Mp4MuxerContext& context,
const mp4::writable_boxes::Movie& box)
: Mp4BoxWriter(context), box_(box) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AddChildBox(std::make_unique<Mp4MovieHeaderBoxWriter>(context, box_.header));
bool video_is_first_track = false;
auto video_track = context.GetVideoTrack();
if (video_track) {
DCHECK_LE(video_track.value().index, box_.tracks.size());
video_is_first_track = video_track.value().index == 0;
if (video_is_first_track) {
AddChildBox(std::make_unique<Mp4MovieTrackBoxWriter>(
context, box_.tracks[video_track.value().index]));
}
}
if (auto audio_track = context.GetAudioTrack()) {
DCHECK_LE(audio_track.value().index, box_.tracks.size());
AddChildBox(std::make_unique<Mp4MovieTrackBoxWriter>(
context, box_.tracks[audio_track.value().index]));
}
if (!video_is_first_track && video_track) {
AddChildBox(std::make_unique<Mp4MovieTrackBoxWriter>(
context, box_.tracks[video_track.value().index]));
}
AddChildBox(
std::make_unique<Mp4MovieExtendsBoxWriter>(context, box_.extends));
}
Mp4MovieBoxWriter::~Mp4MovieBoxWriter() = default;
void Mp4MovieBoxWriter::Write(BoxByteStream& writer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
writer.StartBox(mp4::FOURCC_MOOV);
// Write the children.
WriteChildren(writer);
writer.EndBox();
}
// Mp4MovieHeaderBoxWriter class.
Mp4MovieHeaderBoxWriter::Mp4MovieHeaderBoxWriter(
const Mp4MuxerContext& context,
const mp4::writable_boxes::MovieHeader& box)
: Mp4BoxWriter(context), box_(box) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
Mp4MovieHeaderBoxWriter::~Mp4MovieHeaderBoxWriter() = default;
void Mp4MovieHeaderBoxWriter::Write(BoxByteStream& writer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
writer.StartFullBox(mp4::FOURCC_MVHD);
WriteIsoTime(writer, box_.creation_time);
WriteIsoTime(writer, box_.modification_time);
writer.WriteU32(box_.timescale);
// TODO(crbug.com://1465031): The conversion to timescale will be made in
// the box writer with its duration calculation.
writer.WriteU64(box_.duration.InMilliseconds());
writer.WriteU32(0x00010000); // normal rate.
writer.WriteU16(0x0100); // full volume.
writer.WriteU16(0); // reserved.
writer.WriteU32(0); // reserved.
writer.WriteU32(0); // reserved.
for (auto* it = std::begin(kDisplayIdentityMatrix);
it != std::end(kDisplayIdentityMatrix); ++it) {
writer.WriteU32(*it);
}
// uint32_t type of predefined[6].
for (uint32_t i = 0; i < 6; ++i) {
writer.WriteU32(0);
}
writer.WriteU32(box_.next_track_id);
writer.EndBox();
}
// Mp4MovieExtendsBoxWriter (`mvex`) class.
Mp4MovieExtendsBoxWriter::Mp4MovieExtendsBoxWriter(
const Mp4MuxerContext& context,
const mp4::writable_boxes::MovieExtends& box)
: Mp4BoxWriter(context), box_(box) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (auto video_track = context.GetVideoTrack()) {
DCHECK_LE(video_track.value().index, box_.track_extends.size());
AddChildBox(std::make_unique<Mp4MovieTrackExtendsBoxWriter>(
context, box_.track_extends[video_track.value().index]));
}
if (auto audio_track = context.GetAudioTrack()) {
DCHECK_LE(audio_track.value().index, box_.track_extends.size());
AddChildBox(std::make_unique<Mp4MovieTrackExtendsBoxWriter>(
context, box_.track_extends[audio_track.value().index]));
}
}
Mp4MovieExtendsBoxWriter::~Mp4MovieExtendsBoxWriter() = default;
void Mp4MovieExtendsBoxWriter::Write(BoxByteStream& writer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
writer.StartBox(mp4::FOURCC_MVEX);
// Write the children.
WriteChildren(writer);
writer.EndBox();
}
// Mp4MovieTrackExtendsBoxWriter (`trex`) class.
Mp4MovieTrackExtendsBoxWriter::Mp4MovieTrackExtendsBoxWriter(
const Mp4MuxerContext& context,
const mp4::writable_boxes::TrackExtends& box)
: Mp4BoxWriter(context), box_(box) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
Mp4MovieTrackExtendsBoxWriter::~Mp4MovieTrackExtendsBoxWriter() = default;
void Mp4MovieTrackExtendsBoxWriter::Write(BoxByteStream& writer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
writer.StartFullBox(mp4::FOURCC_TREX, /*flags=*/0, /*version=*/0);
writer.WriteU32(box_.track_id);
writer.WriteU32(box_.default_sample_description_index);
writer.WriteU32(
static_cast<uint32_t>(box_.default_sample_duration.InMilliseconds()));
writer.WriteU32(box_.default_sample_size);
writer.WriteU32(box_.default_sample_flags);
writer.EndBox();
}
// Mp4MovieTrackBoxWriter (`trak`) class.
Mp4MovieTrackBoxWriter::Mp4MovieTrackBoxWriter(
const Mp4MuxerContext& context,
const mp4::writable_boxes::Track& box)
: Mp4BoxWriter(context), box_(box) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AddChildBox(
std::make_unique<Mp4MovieTrackHeaderBoxWriter>(context, box_.header));
AddChildBox(std::make_unique<Mp4MovieMediaBoxWriter>(context, box_.media));
}
Mp4MovieTrackBoxWriter::~Mp4MovieTrackBoxWriter() = default;
void Mp4MovieTrackBoxWriter::Write(BoxByteStream& writer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
writer.StartBox(mp4::FOURCC_TRAK);
WriteChildren(writer);
writer.EndBox();
}
// Mp4MovieTrackHeaderBoxWriter (`tkhd`) class.
Mp4MovieTrackHeaderBoxWriter::Mp4MovieTrackHeaderBoxWriter(
const Mp4MuxerContext& context,
const mp4::writable_boxes::TrackHeader& box)
: Mp4BoxWriter(context), box_(box) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
Mp4MovieTrackHeaderBoxWriter::~Mp4MovieTrackHeaderBoxWriter() = default;
void Mp4MovieTrackHeaderBoxWriter::Write(BoxByteStream& writer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
writer.StartFullBox(mp4::FOURCC_TKHD, box_.flags);
WriteIsoTime(writer, box_.creation_time);
WriteIsoTime(writer, box_.modification_time);
writer.WriteU32(box_.track_id);
writer.WriteU32(0); // reserved
writer.WriteU64(box_.duration.InMilliseconds());
writer.WriteU32(0); // reserved;
writer.WriteU32(0); // reserved;
writer.WriteU16(0); // layer, 0 is the normal value.
writer.WriteU16(0); // alternate_group,
if (box_.is_audio) {
// 1.0 (0x0100) is a full volume for the audio.
writer.WriteU16(0x0100);
} else {
writer.WriteU16(0);
}
writer.WriteU16(0); // reserved.
for (auto* it = std::begin(kDisplayIdentityMatrix);
it != std::end(kDisplayIdentityMatrix); ++it) {
writer.WriteU32(*it);
}
WriteLowHigh(writer, box_.natural_size.width());
WriteLowHigh(writer, box_.natural_size.height());
writer.EndBox();
}
// Mp4MovieMediaBoxWriter (`mdia`) class.
Mp4MovieMediaBoxWriter::Mp4MovieMediaBoxWriter(
const Mp4MuxerContext& context,
const mp4::writable_boxes::Media& box)
: Mp4BoxWriter(context), box_(box) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AddChildBox(
std::make_unique<Mp4MovieMediaHeaderBoxWriter>(context, box_.header));
AddChildBox(
std::make_unique<Mp4MovieMediaHandlerBoxWriter>(context, box_.handler));
AddChildBox(std::make_unique<Mp4MovieMediaInformationBoxWriter>(
context, box_.information));
}
Mp4MovieMediaBoxWriter::~Mp4MovieMediaBoxWriter() = default;
void Mp4MovieMediaBoxWriter::Write(BoxByteStream& writer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
writer.StartBox(mp4::FOURCC_MDIA);
WriteChildren(writer);
writer.EndBox();
}
// Mp4MovieMediaHeaderBoxWriter (`mdhd`) class.
Mp4MovieMediaHeaderBoxWriter::Mp4MovieMediaHeaderBoxWriter(
const Mp4MuxerContext& context,
const mp4::writable_boxes::MediaHeader& box)
: Mp4BoxWriter(context), box_(box) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
Mp4MovieMediaHeaderBoxWriter::~Mp4MovieMediaHeaderBoxWriter() = default;
void Mp4MovieMediaHeaderBoxWriter::Write(BoxByteStream& writer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
writer.StartFullBox(mp4::FOURCC_MDHD);
WriteIsoTime(writer, box_.creation_time);
WriteIsoTime(writer, box_.modification_time);
writer.WriteU32(box_.timescale);
writer.WriteU64(box_.duration.InMilliseconds());
uint16_t language_code = ConvertIso639LanguageCodeToU16(box_.language);
writer.WriteU16(language_code);
writer.WriteU16(0); // pre_defined = 0;
writer.EndBox();
}
// Mp4MovieMediaHandlerBoxWriter (`hdlr`) class.
Mp4MovieMediaHandlerBoxWriter::Mp4MovieMediaHandlerBoxWriter(
const Mp4MuxerContext& context,
const mp4::writable_boxes::MediaHandler& box)
: Mp4BoxWriter(context), box_(box) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
Mp4MovieMediaHandlerBoxWriter::~Mp4MovieMediaHandlerBoxWriter() = default;
void Mp4MovieMediaHandlerBoxWriter::Write(BoxByteStream& writer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
writer.StartFullBox(mp4::FOURCC_HDLR, /*flags=*/0, /*version=*/0);
writer.WriteU32(0); // predefined = 0;
writer.WriteU32(box_.handler_type);
writer.WriteU32(0); // reserved;
writer.WriteU32(0); // reserved;
writer.WriteU32(0); // reserved;
writer.WriteString(box_.name);
WriteChildren(writer);
writer.EndBox();
}
// Mp4MovieMediaInformationBoxWriter (`minf`) class.
Mp4MovieMediaInformationBoxWriter::Mp4MovieMediaInformationBoxWriter(
const Mp4MuxerContext& context,
const mp4::writable_boxes::MediaInformation& box)
: Mp4BoxWriter(context), box_(box) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (box_.video_header.has_value()) {
AddChildBox(std::make_unique<Mp4MovieVideoHeaderBoxWriter>(context));
} else if (box_.sound_header.has_value()) {
AddChildBox(std::make_unique<Mp4MovieSoundHeaderBoxWriter>(context));
}
AddChildBox(std::make_unique<Mp4MovieDataInformationBoxWriter>(
context, box_.data_information));
AddChildBox(std::make_unique<Mp4MovieSampleTableBoxWriter>(
context, box_.sample_table));
}
Mp4MovieMediaInformationBoxWriter::~Mp4MovieMediaInformationBoxWriter() =
default;
void Mp4MovieMediaInformationBoxWriter::Write(BoxByteStream& writer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
writer.StartBox(mp4::FOURCC_MINF);
WriteChildren(writer);
writer.EndBox();
}
// Mp4MovieVideoHeaderBoxWriter (`vmhd`) class.
Mp4MovieVideoHeaderBoxWriter::Mp4MovieVideoHeaderBoxWriter(
const Mp4MuxerContext& context)
: Mp4BoxWriter(context) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
Mp4MovieVideoHeaderBoxWriter::~Mp4MovieVideoHeaderBoxWriter() = default;
void Mp4MovieVideoHeaderBoxWriter::Write(BoxByteStream& writer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
writer.StartFullBox(mp4::FOURCC_VMHD, /*flags=*/1, /*version=*/0);
writer.WriteU16(0); // graphics_mode.
writer.WriteU16(0); // op_color[0].
writer.WriteU16(0); // op_color[1]..
writer.WriteU16(0); // op_color[2]..
writer.EndBox();
}
// Mp4MovieSoundHeaderBoxWriter (`smhd`) class.
Mp4MovieSoundHeaderBoxWriter::Mp4MovieSoundHeaderBoxWriter(
const Mp4MuxerContext& context)
: Mp4BoxWriter(context) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
Mp4MovieSoundHeaderBoxWriter::~Mp4MovieSoundHeaderBoxWriter() = default;
void Mp4MovieSoundHeaderBoxWriter::Write(BoxByteStream& writer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
writer.StartFullBox(mp4::FOURCC_SMHD, /*flags=*/0, /*version=*/0);
writer.WriteU16(0); // balance.
writer.WriteU16(0); // reserved.
writer.EndBox();
}
// Mp4MovieDataInformationBoxWriter (`dinf`) class.
Mp4MovieDataInformationBoxWriter::Mp4MovieDataInformationBoxWriter(
const Mp4MuxerContext& context,
const mp4::writable_boxes::DataInformation& box)
: Mp4BoxWriter(context), box_(box) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AddChildBox(std::make_unique<Mp4MovieDataReferenceBoxWriter>(
context, box_.data_reference));
}
Mp4MovieDataInformationBoxWriter::~Mp4MovieDataInformationBoxWriter() = default;
void Mp4MovieDataInformationBoxWriter::Write(BoxByteStream& writer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
writer.StartBox(mp4::FOURCC_DINF);
WriteChildren(writer);
writer.EndBox();
}
// Mp4MovieDataReferenceBoxWriter (`dref`) class.
Mp4MovieDataReferenceBoxWriter::Mp4MovieDataReferenceBoxWriter(
const Mp4MuxerContext& context,
const mp4::writable_boxes::DataReference& box)
: Mp4BoxWriter(context), box_(box) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
for (size_t i = 0; i < box_.entries.size(); ++i) {
AddChildBox(std::make_unique<Mp4MovieDataUrlEntryBoxWriter>(context));
}
}
Mp4MovieDataReferenceBoxWriter::~Mp4MovieDataReferenceBoxWriter() = default;
void Mp4MovieDataReferenceBoxWriter::Write(BoxByteStream& writer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
writer.StartFullBox(mp4::FOURCC_DREF, /*flags=*/0, /*version=*/0);
writer.WriteU32(box_.entries.size());
WriteChildren(writer);
writer.EndBox();
}
// Mp4MovieDataUrlEntryBoxWriter (`url`) class.
Mp4MovieDataUrlEntryBoxWriter::Mp4MovieDataUrlEntryBoxWriter(
const Mp4MuxerContext& context)
: Mp4BoxWriter(context) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
Mp4MovieDataUrlEntryBoxWriter::~Mp4MovieDataUrlEntryBoxWriter() = default;
void Mp4MovieDataUrlEntryBoxWriter::Write(BoxByteStream& writer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
writer.StartFullBox(mp4::FOURCC_URL, /*flags=*/0, /*version=*/0);
// We use empty Url location to prevent accidental PII leak.
writer.WriteString("");
writer.EndBox();
}
// Mp4MovieSampleTableBoxWriter (`stbl`) class.
Mp4MovieSampleTableBoxWriter::Mp4MovieSampleTableBoxWriter(
const Mp4MuxerContext& context,
const mp4::writable_boxes::SampleTable& box)
: Mp4BoxWriter(context), box_(box) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AddChildBox(std::make_unique<Mp4MovieSampleToChunkBoxWriter>(context));
AddChildBox(std::make_unique<Mp4MovieDecodingTimeToSampleBoxWriter>(context));
AddChildBox(std::make_unique<Mp4MovieSampleSizeBoxWriter>(context));
AddChildBox(std::make_unique<Mp4MovieSampleChunkOffsetBoxWriter>(context));
AddChildBox(std::make_unique<Mp4MovieSampleDescriptionBoxWriter>(
context, box_.sample_description));
}
Mp4MovieSampleTableBoxWriter::~Mp4MovieSampleTableBoxWriter() = default;
void Mp4MovieSampleTableBoxWriter::Write(BoxByteStream& writer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
writer.StartBox(mp4::FOURCC_STBL);
WriteChildren(writer);
writer.EndBox();
}
// Mp4MovieSampleToChunkBoxWriter (`stsc`) class.
Mp4MovieSampleToChunkBoxWriter::Mp4MovieSampleToChunkBoxWriter(
const Mp4MuxerContext& context)
: Mp4BoxWriter(context) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
Mp4MovieSampleToChunkBoxWriter::~Mp4MovieSampleToChunkBoxWriter() = default;
void Mp4MovieSampleToChunkBoxWriter::Write(BoxByteStream& writer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
writer.StartFullBox(mp4::FOURCC_STSC, /*flags=*/0, /*version=*/0);
writer.WriteU32(0); // entry_count.
writer.EndBox();
}
// Mp4MovieDecodingTimeToSampleBoxWriter (`stts`) class.
Mp4MovieDecodingTimeToSampleBoxWriter::Mp4MovieDecodingTimeToSampleBoxWriter(
const Mp4MuxerContext& context)
: Mp4BoxWriter(context) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
Mp4MovieDecodingTimeToSampleBoxWriter::
~Mp4MovieDecodingTimeToSampleBoxWriter() = default;
void Mp4MovieDecodingTimeToSampleBoxWriter::Write(BoxByteStream& writer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
writer.StartFullBox(mp4::FOURCC_STTS, /*flags=*/0, /*version=*/0);
writer.WriteU32(0); // entry_count.
writer.EndBox();
}
// Mp4MovieSampleSizeBoxWriter (`stsz`) class.
Mp4MovieSampleSizeBoxWriter::Mp4MovieSampleSizeBoxWriter(
const Mp4MuxerContext& context)
: Mp4BoxWriter(context) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
Mp4MovieSampleSizeBoxWriter::~Mp4MovieSampleSizeBoxWriter() = default;
void Mp4MovieSampleSizeBoxWriter::Write(BoxByteStream& writer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
writer.StartFullBox(mp4::FOURCC_STSZ);
writer.WriteU32(0); // sample_size.
writer.WriteU32(0); // sample_count.
writer.EndBox();
}
// Mp4MovieSampleChunkOffsetBoxWriter (`stco`) class.
Mp4MovieSampleChunkOffsetBoxWriter::Mp4MovieSampleChunkOffsetBoxWriter(
const Mp4MuxerContext& context)
: Mp4BoxWriter(context) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
Mp4MovieSampleChunkOffsetBoxWriter::~Mp4MovieSampleChunkOffsetBoxWriter() =
default;
void Mp4MovieSampleChunkOffsetBoxWriter::Write(BoxByteStream& writer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
writer.StartFullBox(mp4::FOURCC_STCO, /*flags=*/0, /*version=*/0);
writer.WriteU32(0); // entry_count.
writer.EndBox();
}
// Mp4MovieSampleDescriptionBoxWriter (`stsd`) class.
Mp4MovieSampleDescriptionBoxWriter::Mp4MovieSampleDescriptionBoxWriter(
const Mp4MuxerContext& context,
const mp4::writable_boxes::SampleDescription& box)
: Mp4BoxWriter(context), box_(box) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (box_.video_sample_entry.has_value()) {
CHECK(!box_.audio_sample_entry.has_value());
AddChildBox(std::make_unique<Mp4MovieVisualSampleEntryBoxWriter>(
context, box_.video_sample_entry.value()));
return;
}
// TODO(crbug.com://331682616): Add validation of DCHECK for sample entry
// existence.
if (box_.audio_sample_entry.has_value()) {
AddChildBox(std::make_unique<Mp4MovieAudioSampleEntryBoxWriter>(
context, box_.audio_sample_entry.value()));
}
}
Mp4MovieSampleDescriptionBoxWriter::~Mp4MovieSampleDescriptionBoxWriter() =
default;
void Mp4MovieSampleDescriptionBoxWriter::Write(BoxByteStream& writer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
writer.StartFullBox(mp4::FOURCC_STSD, /*flags=*/0, /*version=*/0);
writer.WriteU32(box_.entry_count);
WriteChildren(writer);
writer.EndBox();
}
// Mp4MovieVisualSampleEntryBoxWriter (`avc1`, 'vp09') class.
Mp4MovieVisualSampleEntryBoxWriter::Mp4MovieVisualSampleEntryBoxWriter(
const Mp4MuxerContext& context,
const mp4::writable_boxes::VisualSampleEntry& box)
: Mp4BoxWriter(context), box_(box) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AddChildBox(std::make_unique<Mp4MoviePixelAspectRatioBoxBoxWriter>(context));
switch (box_.codec) {
case VideoCodec::kVP9:
CHECK(box_.vp_decoder_configuration.has_value());
AddChildBox(std::make_unique<Mp4MovieVPCodecConfigurationBoxWriter>(
context, box_.vp_decoder_configuration.value()));
break;
#if BUILDFLAG(USE_PROPRIETARY_CODECS)
case VideoCodec::kH264:
CHECK(box_.avc_decoder_configuration.has_value());
AddChildBox(std::make_unique<Mp4MovieAVCDecoderConfigurationBoxWriter>(
context, box_.avc_decoder_configuration.value()));
break;
#endif
default:
NOTREACHED();
}
}
Mp4MovieVisualSampleEntryBoxWriter::~Mp4MovieVisualSampleEntryBoxWriter() =
default;
void Mp4MovieVisualSampleEntryBoxWriter::Write(BoxByteStream& writer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
switch (box_.codec) {
case VideoCodec::kVP9:
writer.StartBox(mp4::FOURCC_VP09);
break;
#if BUILDFLAG(USE_PROPRIETARY_CODECS)
case VideoCodec::kH264:
writer.StartBox(mp4::FOURCC_AVC1);
break;
#endif
default:
NOTREACHED();
}
writer.WriteU32(0); // reserved.
writer.WriteU16(0); // reserved.
writer.WriteU16(1); // data_reference_index in `dref` box, 1 is start index.
writer.WriteU16(0); // predefined1.
writer.WriteU16(1); // reserved2.
writer.WriteU32(0); // pre_defined2[0].
writer.WriteU32(0); // pre_defined2[1].
writer.WriteU32(0); // pre_defined2[2].
writer.WriteU16(box_.coded_size.width());
writer.WriteU16(box_.coded_size.height());
writer.WriteU32(0x00480000); // horizontal resolution, 72 dpi.
writer.WriteU32(0x00480000); // vertical resolution, 72 dpi.
writer.WriteU32(0); // reserved3.
writer.WriteU16(1); // frame_count.
// compressor_name.
constexpr size_t kMaxCompressorNameSize = 30;
std::string compressor_name = box_.compressor_name;
uint8_t compressor_name_size =
std::min(compressor_name.size(), kMaxCompressorNameSize);
writer.WriteU8(compressor_name_size);
compressor_name.resize(kMaxCompressorNameSize);
compressor_name.push_back(0);
writer.WriteString(compressor_name); // It will write 31 chars.
writer.WriteU16(0x0018); // depth.
writer.WriteU16(0xFFFF); // pre_defined, -1.
WriteChildren(writer);
writer.EndBox();
}
#if BUILDFLAG(USE_PROPRIETARY_CODECS)
// Mp4MovieAVCDecoderConfigurationBoxWriter (`avcC`) class.
Mp4MovieAVCDecoderConfigurationBoxWriter::
Mp4MovieAVCDecoderConfigurationBoxWriter(
const Mp4MuxerContext& context,
const mp4::writable_boxes::AVCDecoderConfiguration& box)
: Mp4BoxWriter(context), box_(box) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
Mp4MovieAVCDecoderConfigurationBoxWriter::
~Mp4MovieAVCDecoderConfigurationBoxWriter() = default;
void Mp4MovieAVCDecoderConfigurationBoxWriter::Write(BoxByteStream& writer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
writer.StartBox(mp4::FOURCC_AVCC);
std::vector<uint8_t> write_data;
CHECK(box_.avc_config_record.Serialize(write_data));
writer.WriteBytes(write_data.data(), write_data.size());
writer.EndBox();
}
// Mp4MovieElementaryStreamDescriptorBoxWriter (`esds`) class.
Mp4MovieElementaryStreamDescriptorBoxWriter::
Mp4MovieElementaryStreamDescriptorBoxWriter(
const Mp4MuxerContext& context,
const mp4::writable_boxes::ElementaryStreamDescriptor& box)
: Mp4BoxWriter(context), box_(box) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
Mp4MovieElementaryStreamDescriptorBoxWriter::
~Mp4MovieElementaryStreamDescriptorBoxWriter() = default;
void Mp4MovieElementaryStreamDescriptorBoxWriter::Write(BoxByteStream& writer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
writer.StartFullBox(mp4::FOURCC_ESDS);
std::vector<uint8_t> esds =
mp4::ESDescriptor::CreateEsds(box_.aac_codec_description);
writer.WriteBytes(esds.data(), esds.size());
writer.EndBox();
}
#endif
// Mp4MovieAudioSampleEntryBoxWriter (`mp4a` or 'Opus') class.
Mp4MovieAudioSampleEntryBoxWriter::Mp4MovieAudioSampleEntryBoxWriter(
const Mp4MuxerContext& context,
const mp4::writable_boxes::AudioSampleEntry& box)
: Mp4BoxWriter(context), box_(box) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
AddChildBox(
std::make_unique<Mp4MovieBitRateBoxWriter>(context, box_.bit_rate));
switch (box_.codec) {
case AudioCodec::kOpus:
AddChildBox(std::make_unique<Mp4MovieOpusSpecificBoxWriter>(
context, box_.opus_specific_box.value()));
break;
#if BUILDFLAG(USE_PROPRIETARY_CODECS)
case AudioCodec::kAAC:
AddChildBox(std::make_unique<Mp4MovieElementaryStreamDescriptorBoxWriter>(
context, box_.elementary_stream_descriptor.value()));
break;
#endif
default:
NOTREACHED();
}
}
Mp4MovieAudioSampleEntryBoxWriter::~Mp4MovieAudioSampleEntryBoxWriter() =
default;
void Mp4MovieAudioSampleEntryBoxWriter::Write(BoxByteStream& writer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
switch (box_.codec) {
case AudioCodec::kOpus:
writer.StartBox(mp4::FOURCC_OPUS);
break;
#if BUILDFLAG(USE_PROPRIETARY_CODECS)
case AudioCodec::kAAC:
writer.StartBox(mp4::FOURCC_MP4A);
break;
#endif
default:
NOTREACHED();
}
constexpr size_t kAudioSampleEntryReservedSize = 6u;
for (size_t i = 0; i < kAudioSampleEntryReservedSize; ++i) {
writer.WriteU8(0);
}
writer.WriteU16(1); // data_reference_index in `dref` box, 1 is start index.
writer.WriteU32(0); // reserved1[0]
writer.WriteU32(0); // reserved1[1]
writer.WriteU16(box_.channel_count);
writer.WriteU16(16); // sample size.
writer.WriteU16(0); // predefined.
writer.WriteU16(0); // reserved.
WriteLowHigh(writer, box_.sample_rate);
WriteChildren(writer);
writer.EndBox();
}
// Mp4MovieOpusSpecificBoxWriter (`dOps`) class.
Mp4MovieOpusSpecificBoxWriter::Mp4MovieOpusSpecificBoxWriter(
const Mp4MuxerContext& context,
const mp4::writable_boxes::OpusSpecificBox& box)
: Mp4BoxWriter(context), box_(box) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
Mp4MovieOpusSpecificBoxWriter::~Mp4MovieOpusSpecificBoxWriter() = default;
void Mp4MovieOpusSpecificBoxWriter::Write(BoxByteStream& writer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
writer.StartBox(mp4::FOURCC_DOPS);
constexpr base::TimeDelta kOpusSkipDuration = base::Milliseconds(80);
writer.WriteU8(0u); // Version.
writer.WriteU8(box_.channel_count);
writer.WriteU16(static_cast<uint16_t>(AudioTimestampHelper::TimeToFrames(
kOpusSkipDuration, box_.sample_rate))); // Preskip.
writer.WriteU32(box_.sample_rate);
writer.WriteU16(0u); // OutputGain.
writer.WriteU8(0u); // ChannelMappingFamily
// TODO(crbug.com/330815378): Write channel mapping table.
writer.EndBox();
}
// Mp4MovieVPCodecConfigurationBoxWriter (`vpcC`) class.
Mp4MovieVPCodecConfigurationBoxWriter::Mp4MovieVPCodecConfigurationBoxWriter(
const Mp4MuxerContext& context,
const mp4::writable_boxes::VPCodecConfiguration& box)
: Mp4BoxWriter(context), box_(box) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
Mp4MovieVPCodecConfigurationBoxWriter::
~Mp4MovieVPCodecConfigurationBoxWriter() = default;
void Mp4MovieVPCodecConfigurationBoxWriter::Write(BoxByteStream& writer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
writer.StartFullBox(mp4::FOURCC_VPCC);
switch (box_.profile) {
case VP9PROFILE_PROFILE0:
writer.WriteU8(0);
break;
case VP9PROFILE_PROFILE1:
writer.WriteU8(1);
break;
case VP9PROFILE_PROFILE2:
writer.WriteU8(2);
break;
case VP9PROFILE_PROFILE3:
writer.WriteU8(3);
break;
default:
NOTREACHED();
}
writer.WriteU8(box_.level);
auto video_color_space =
box_.color_space.IsValid()
? VideoColorSpace::FromGfxColorSpace(box_.color_space)
: VideoColorSpace(VideoColorSpace::PrimaryID::UNSPECIFIED,
VideoColorSpace::TransferID::UNSPECIFIED,
VideoColorSpace::MatrixID::UNSPECIFIED,
gfx::ColorSpace::RangeID::DERIVED);
constexpr uint8_t bit_depth = 8u;
constexpr uint8_t chroma_sub_sampling = 0u;
uint8_t video_full_range_flag =
video_color_space.range == gfx::ColorSpace::RangeID::FULL ? 1u : 0u;
writer.WriteU8(bit_depth << 4 | chroma_sub_sampling << 1 |
video_full_range_flag);
writer.WriteU8(static_cast<uint8_t>(video_color_space.primaries));
writer.WriteU8(static_cast<uint8_t>(video_color_space.transfer));
writer.WriteU8(static_cast<uint8_t>(video_color_space.matrix));
writer.WriteU16(/*codecInitializationData Size*/ 0);
writer.EndBox();
}
// Mp4MoviePixelAspectRatioBoxBoxWriter (`pasp`) class.
Mp4MoviePixelAspectRatioBoxBoxWriter::Mp4MoviePixelAspectRatioBoxBoxWriter(
const Mp4MuxerContext& context)
: Mp4BoxWriter(context) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
Mp4MoviePixelAspectRatioBoxBoxWriter::~Mp4MoviePixelAspectRatioBoxBoxWriter() =
default;
void Mp4MoviePixelAspectRatioBoxBoxWriter::Write(BoxByteStream& writer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
writer.StartBox(mp4::FOURCC_PASP);
writer.WriteU32(1); // Horizontal spacing.
writer.WriteU32(1); // Vertical spacing.
writer.EndBox();
}
// Mp4MovieBitRateBoxWriter (`btrt`) class.
Mp4MovieBitRateBoxWriter::Mp4MovieBitRateBoxWriter(
const Mp4MuxerContext& context,
const mp4::writable_boxes::BitRate& box)
: Mp4BoxWriter(context), box_(box) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
Mp4MovieBitRateBoxWriter::~Mp4MovieBitRateBoxWriter() = default;
void Mp4MovieBitRateBoxWriter::Write(BoxByteStream& writer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
writer.StartBox(mp4::FOURCC_BTRT);
// bufferSizeDB, 0 for unknown so that decoder will decide with its own
// algorithm.
writer.WriteU32(0);
writer.WriteU32(box_.max_bit_rate);
writer.WriteU32(box_.avg_bit_rate);
writer.EndBox();
}
} // namespace media