blob: 91e239b95ef8c629977237ca19163aa79e87ad3c [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 <string>
#include <string_view>
#include <type_traits>
#include <vector>
#include "base/big_endian.h"
#include "base/containers/span.h"
#include "base/functional/bind.h"
#include "base/memory/scoped_refptr.h"
#include "base/run_loop.h"
#include "base/test/task_environment.h"
#include "media/base/subsample_entry.h"
#include "media/base/video_codecs.h"
#include "media/base/video_color_space.h"
#include "media/formats/mp2t/es_parser_adts.h"
#include "media/formats/mp4/bitstream_converter.h"
#include "media/formats/mp4/box_definitions.h"
#include "media/formats/mp4/box_reader.h"
#include "media/formats/mp4/es_descriptor.h"
#include "media/formats/mp4/writable_box_definitions.h"
#include "media/formats/mpeg/adts_stream_parser.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_context.h"
#include "media/muxers/mp4_type_conversion.h"
#include "media/muxers/output_position_tracker.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace media {
namespace {
constexpr uint32_t kDuration1 = 12345u;
constexpr uint32_t kDuration2 = 12300u;
constexpr uint32_t kDefaultSampleSize = 1024u;
constexpr uint32_t kWidth = 1024u;
constexpr uint32_t kHeight = 780u;
constexpr uint32_t kVideoTimescale = 30000u;
constexpr uint32_t kAudioTimescale = 44100u;
constexpr uint32_t kVideoSampleFlags = 0x112u;
constexpr uint32_t kAudioSampleFlags = 0x113u;
constexpr uint16_t kAudioVolume = 0x0100;
constexpr char kVideoHandlerName[] = "VideoHandler";
constexpr char kAudioHandlerName[] = "SoundHandler";
constexpr uint32_t kTotalSizeLength = 4u;
constexpr uint32_t kFlagsAndVersionLength = 4u;
constexpr uint32_t kEntryCountLength = 4u;
constexpr uint32_t kSampleSizeAndCount = 8u;
#if BUILDFLAG(USE_PROPRIETARY_CODECS)
constexpr uint8_t kProfileIndicationNoChroma = 77;
constexpr uint8_t kProfileIndication = 122;
constexpr uint8_t kProfileCompatibility = 100;
constexpr uint8_t kLevelIndication = 64;
constexpr uint8_t kNALUUnitLength = 4;
constexpr uint8_t kSPS[] = {0x67, 0x64, 0x00, 0x0C, 0xAC, 0xD9, 0x41,
0x41, 0xFB, 0x01, 0x10, 0x00, 0x00, 0x03,
0x00, 0x10, 0x00, 0x00, 0x00, 0x03, 0x01,
0xE0, 0xF1, 0x42, 0x99, 0x60};
constexpr uint8_t kPPS[] = {0x68, 0xEE, 0xE3, 0xCB, 0x22, 0xC0};
constexpr uint8_t kChromaFormat = 0x3;
constexpr uint8_t kLumaMinus8 = 0x4;
constexpr uint8_t kChromaMinus8 = 0x4;
#endif
uint64_t ConvertTo1904TimeInSeconds(base::Time time) {
base::Time time1904;
CHECK(base::Time::FromUTCString("1904-01-01 00:00:00 UTC", &time1904));
uint64_t iso_time = (time - time1904).InSeconds();
return iso_time;
}
} // namespace
class Mp4MuxerBoxWriterTest : public testing::Test {
public:
Mp4MuxerBoxWriterTest() = default;
protected:
void CreateContext(std::unique_ptr<OutputPositionTracker> tracker) {
context_ = std::make_unique<Mp4MuxerContext>(std::move(tracker));
}
Mp4MuxerContext* context() const { return context_.get(); }
void Reset() { context_ = nullptr; }
void CreateContext(std::vector<uint8_t>& written_data) {
auto tracker = std::make_unique<OutputPositionTracker>(base::BindRepeating(
[&](base::OnceClosure run_loop_quit, std::vector<uint8_t>* written_data,
std::string_view mp4_data_string) {
// Callback is called per box output.
std::copy(mp4_data_string.begin(), mp4_data_string.end(),
std::back_inserter(*written_data));
std::move(run_loop_quit).Run();
},
run_loop_.QuitClosure(), &written_data));
// Initialize.
CreateContext(std::move(tracker));
}
size_t FlushAndWait(Mp4BoxWriter* box_writer) {
// Flush at requested.
size_t written_size = box_writer->WriteAndFlush();
// Wait for finishing flush of all boxes.
run_loop_.Run();
return written_size;
}
size_t FlushWithBoxWriterAndWait(Mp4BoxWriter* box_writer,
BoxByteStream& box_byte_stream) {
// Flush at requested.
size_t written_size = box_writer->WriteAndFlush(box_byte_stream);
// Wait for finishing flush of all boxes.
run_loop_.Run();
return written_size;
}
std::unique_ptr<Mp4MuxerContext> context_;
private:
base::test::TaskEnvironment task_environment;
mp4::writable_boxes::Movie mp4_moov_box_;
base::RunLoop run_loop_;
};
TEST_F(Mp4MuxerBoxWriterTest, Mp4MovieAndHeader) {
// Tests `moov/mvhd` box writer.
std::vector<uint8_t> written_data;
CreateContext(written_data);
// Populates the boxes during Mp4Muxer::OnEncodedVideo.
mp4::writable_boxes::Movie mp4_moov_box;
base::Time creation_time = base::Time::FromTimeT(0x1234567);
base::Time modification_time = base::Time::FromTimeT(0x2345678);
{
mp4_moov_box.header.creation_time = creation_time;
mp4_moov_box.header.modification_time = modification_time;
mp4_moov_box.header.timescale = kVideoTimescale;
mp4_moov_box.header.duration = base::Milliseconds(0);
mp4_moov_box.header.next_track_id = 1u;
}
// Flush at requested.
Mp4MovieBoxWriter box_writer(*context(), mp4_moov_box);
FlushAndWait(&box_writer);
// Validation of the written boxes.
// `written_data` test.
std::unique_ptr<mp4::BoxReader> reader;
mp4::ParseResult result = mp4::BoxReader::ReadTopLevelBox(
written_data.data(), written_data.size(), nullptr, &reader);
EXPECT_EQ(result, mp4::ParseResult::kOk);
EXPECT_TRUE(reader);
EXPECT_EQ(mp4::FOURCC_MOOV, reader->type());
EXPECT_TRUE(reader->ScanChildren());
// `mvhd` test.
mp4::MovieHeader mvhd_box;
EXPECT_TRUE(reader->ReadChild(&mvhd_box));
EXPECT_EQ(mvhd_box.version, 1);
EXPECT_EQ(mvhd_box.creation_time, ConvertTo1904TimeInSeconds(creation_time));
EXPECT_EQ(mvhd_box.modification_time,
ConvertTo1904TimeInSeconds(modification_time));
EXPECT_EQ(mvhd_box.timescale, kVideoTimescale);
EXPECT_EQ(mvhd_box.duration, 0u);
EXPECT_EQ(mvhd_box.next_track_id, 1u);
// Once Flush, it needs to reset the internal objects of context and buffer.
Reset();
}
TEST_F(Mp4MuxerBoxWriterTest, Mp4MovieExtends) {
// Tests `mvex/trex` box writer.
std::vector<uint8_t> written_data;
CreateContext(written_data);
context_->SetVideoTrack({0, kVideoTimescale});
context_->SetAudioTrack({1, kAudioTimescale});
// Populates the boxes during Mp4Muxer::OnEncodedVideo.
mp4::writable_boxes::Movie mp4_moov_box;
{
mp4::writable_boxes::TrackExtends video_extends;
video_extends.track_id = 1u;
video_extends.default_sample_description_index = 1u;
video_extends.default_sample_duration = base::Milliseconds(0);
video_extends.default_sample_size = kDefaultSampleSize;
video_extends.default_sample_flags = kVideoSampleFlags;
mp4_moov_box.extends.track_extends.push_back(std::move(video_extends));
mp4::writable_boxes::Track video_track = {};
mp4_moov_box.tracks.push_back(std::move(video_track));
}
{
mp4::writable_boxes::TrackExtends audio_extends;
audio_extends.track_id = 2u;
audio_extends.default_sample_description_index = 1u;
audio_extends.default_sample_duration = base::Milliseconds(0);
audio_extends.default_sample_size = kDefaultSampleSize;
audio_extends.default_sample_flags = kAudioSampleFlags;
mp4_moov_box.extends.track_extends.push_back(std::move(audio_extends));
mp4::writable_boxes::Track audio_track = {};
mp4_moov_box.tracks.push_back(std::move(audio_track));
}
// Flush at requested.
Mp4MovieBoxWriter box_writer(*context(), mp4_moov_box);
FlushAndWait(&box_writer);
// Validation of the written boxes.
// `written_data` test.
std::unique_ptr<mp4::BoxReader> reader;
mp4::ParseResult result = mp4::BoxReader::ReadTopLevelBox(
written_data.data(), written_data.size(), nullptr, &reader);
EXPECT_EQ(result, mp4::ParseResult::kOk);
EXPECT_TRUE(reader);
EXPECT_EQ(mp4::FOURCC_MOOV, reader->type());
EXPECT_TRUE(reader->ScanChildren());
// `mvex` test.
mp4::MovieExtends mvex_box;
EXPECT_TRUE(reader->ReadChild(&mvex_box));
// mp4::MovieExtends mvex_box = mvex_boxes[0];
EXPECT_EQ(mvex_box.tracks.size(), 2u);
EXPECT_EQ(mvex_box.tracks[0].track_id, 1u);
EXPECT_EQ(mvex_box.tracks[0].default_sample_description_index, 1u);
EXPECT_EQ(mvex_box.tracks[0].default_sample_duration, 0u);
EXPECT_EQ(mvex_box.tracks[0].default_sample_size, kDefaultSampleSize);
EXPECT_EQ(mvex_box.tracks[0].default_sample_flags, kVideoSampleFlags);
EXPECT_EQ(mvex_box.tracks[1].track_id, 2u);
EXPECT_EQ(mvex_box.tracks[1].default_sample_description_index, 1u);
EXPECT_EQ(mvex_box.tracks[1].default_sample_duration, 0u);
EXPECT_EQ(mvex_box.tracks[1].default_sample_size, kDefaultSampleSize);
EXPECT_EQ(mvex_box.tracks[1].default_sample_flags, kAudioSampleFlags);
// Once Flush, it needs to reset the internal objects of context and buffer.
Reset();
}
TEST_F(Mp4MuxerBoxWriterTest, Mp4MovieTrackAndMediaHeader) {
// Tests `tkhd/mdhd` box writer.
std::vector<uint8_t> written_data;
CreateContext(written_data);
// Populates the boxes during Mp4Muxer::OnEncodedVideo.
constexpr size_t kVideoIndex = 0;
constexpr size_t kAudioIndex = 1;
context_->SetVideoTrack({kVideoIndex, kVideoTimescale});
context_->SetAudioTrack({kAudioIndex, kAudioTimescale});
mp4::writable_boxes::Movie mp4_moov_box;
base::Time creation_time = base::Time::FromTimeT(0x1234567);
base::Time modification_time = base::Time::FromTimeT(0x2345678);
{
mp4::writable_boxes::TrackExtends video_extends;
mp4_moov_box.extends.track_extends.push_back(std::move(video_extends));
mp4::writable_boxes::Track video_track = {};
using T = std::underlying_type_t<mp4::writable_boxes::TrackHeaderFlags>;
video_track.header.flags =
(static_cast<T>(mp4::writable_boxes::TrackHeaderFlags::kTrackEnabled) |
static_cast<T>(mp4::writable_boxes::TrackHeaderFlags::kTrackInMovie));
video_track.header.track_id = 1u;
video_track.header.creation_time = creation_time;
video_track.header.modification_time = modification_time;
video_track.header.duration = base::Milliseconds(kDuration1);
video_track.header.is_audio = false;
video_track.header.natural_size = gfx::Size(kWidth, kHeight);
video_track.media.header.creation_time = creation_time;
video_track.media.header.modification_time = modification_time;
video_track.media.header.duration = base::Milliseconds(kDuration1);
video_track.media.header.timescale = kVideoTimescale;
video_track.media.header.language = "und";
video_track.media.handler.handler_type = mp4::FOURCC_VIDE;
video_track.media.handler.name = kVideoHandlerName;
mp4_moov_box.tracks.push_back(std::move(video_track));
}
{
mp4::writable_boxes::TrackExtends audio_extends;
mp4_moov_box.extends.track_extends.push_back(std::move(audio_extends));
mp4::writable_boxes::Track audio_track = {};
audio_track.header.track_id = 2u;
audio_track.header.creation_time = creation_time;
audio_track.header.modification_time = modification_time;
audio_track.header.duration = base::Milliseconds(kDuration2);
audio_track.header.is_audio = true;
audio_track.header.natural_size = gfx::Size(0, 0);
audio_track.media.header.creation_time = creation_time;
audio_track.media.header.modification_time = modification_time;
audio_track.media.header.duration = base::Milliseconds(kDuration2);
audio_track.media.header.timescale = kAudioTimescale;
audio_track.media.header.language = "";
audio_track.media.handler.handler_type = mp4::FOURCC_SOUN;
audio_track.media.handler.name = kAudioHandlerName;
mp4_moov_box.tracks.push_back(std::move(audio_track));
}
// Flush at requested.
Mp4MovieBoxWriter box_writer(*context(), mp4_moov_box);
FlushAndWait(&box_writer);
// Validation of the written boxes.
// `written_data` test.
std::unique_ptr<mp4::BoxReader> reader;
mp4::ParseResult result = mp4::BoxReader::ReadTopLevelBox(
written_data.data(), written_data.size(), nullptr, &reader);
EXPECT_EQ(result, mp4::ParseResult::kOk);
EXPECT_TRUE(reader);
EXPECT_EQ(mp4::FOURCC_MOOV, reader->type());
EXPECT_TRUE(reader->ScanChildren());
// Track test.
std::vector<mp4::Track> track_boxes;
EXPECT_TRUE(reader->ReadChildren(&track_boxes));
// mp4::MovieExtends mvex_box = mvex_boxes[0];
EXPECT_EQ(track_boxes.size(), 2u);
// Track header validation.
EXPECT_EQ(track_boxes[kVideoIndex].header.track_id, 1u);
EXPECT_EQ(track_boxes[kVideoIndex].header.creation_time,
ConvertTo1904TimeInSeconds(creation_time));
EXPECT_EQ(track_boxes[kVideoIndex].header.modification_time,
ConvertTo1904TimeInSeconds(modification_time));
EXPECT_EQ(track_boxes[kVideoIndex].header.duration, kDuration1);
EXPECT_EQ(track_boxes[kVideoIndex].header.volume, 0);
EXPECT_EQ(track_boxes[kVideoIndex].header.width, kWidth);
EXPECT_EQ(track_boxes[kVideoIndex].header.height, kHeight);
EXPECT_EQ(track_boxes[kAudioIndex].header.track_id, 2u);
EXPECT_EQ(track_boxes[kAudioIndex].header.creation_time,
ConvertTo1904TimeInSeconds(creation_time));
EXPECT_EQ(track_boxes[kAudioIndex].header.modification_time,
ConvertTo1904TimeInSeconds(modification_time));
EXPECT_EQ(track_boxes[kAudioIndex].header.duration, kDuration2);
EXPECT_EQ(track_boxes[kAudioIndex].header.volume, kAudioVolume);
EXPECT_EQ(track_boxes[kAudioIndex].header.width, 0u);
EXPECT_EQ(track_boxes[kAudioIndex].header.height, 0u);
// Media Header validation.
EXPECT_EQ(track_boxes[kAudioIndex].media.header.creation_time,
ConvertTo1904TimeInSeconds(creation_time));
EXPECT_EQ(track_boxes[kAudioIndex].media.header.modification_time,
ConvertTo1904TimeInSeconds(modification_time));
EXPECT_EQ(track_boxes[kAudioIndex].media.header.duration, kDuration2);
EXPECT_EQ(track_boxes[kAudioIndex].media.header.timescale, kAudioTimescale);
EXPECT_EQ(track_boxes[kAudioIndex].media.header.language_code,
kUndefinedLanguageCode);
EXPECT_EQ(track_boxes[kVideoIndex].media.header.creation_time,
ConvertTo1904TimeInSeconds(creation_time));
EXPECT_EQ(track_boxes[kVideoIndex].media.header.modification_time,
ConvertTo1904TimeInSeconds(modification_time));
EXPECT_EQ(track_boxes[kVideoIndex].media.header.duration, kDuration1);
EXPECT_EQ(track_boxes[kVideoIndex].media.header.timescale, kVideoTimescale);
EXPECT_EQ(track_boxes[kVideoIndex].media.header.language_code,
kUndefinedLanguageCode);
// Media Handler validation.
EXPECT_EQ(track_boxes[kVideoIndex].media.handler.type,
mp4::TrackType::kVideo);
EXPECT_EQ(track_boxes[kVideoIndex].media.handler.name, kVideoHandlerName);
EXPECT_EQ(track_boxes[kAudioIndex].media.handler.type,
mp4::TrackType::kAudio);
EXPECT_EQ(track_boxes[kAudioIndex].media.handler.name, kAudioHandlerName);
// Once Flush, it needs to reset the internal objects of context and buffer.
Reset();
}
TEST_F(Mp4MuxerBoxWriterTest, Mp4MovieMediaDataInformation) {
// Tests `tkhd/mdhd` box writer.
std::vector<uint8_t> written_data;
CreateContext(written_data);
// Populates the boxes during Mp4Muxer::OnEncodedVideo.
const std::string kUrl = "";
mp4::writable_boxes::DataUrlEntry entry;
mp4::writable_boxes::MediaInformation media_information;
media_information.video_header = mp4::writable_boxes::VideoMediaHeader();
media_information.data_information.data_reference.entries.push_back(
std::move(entry));
// Flush at requested.
Mp4MovieMediaInformationBoxWriter box_writer(*context(), media_information);
FlushAndWait(&box_writer);
// Validation of the written boxes.
// `written_data` test.
std::unique_ptr<mp4::BoxReader> box_reader(
mp4::BoxReader::ReadConcatentatedBoxes(written_data.data(),
written_data.size(), nullptr));
// `minf`.
uint32_t fourcc;
EXPECT_TRUE(box_reader->SkipBytes(kTotalSizeLength));
EXPECT_TRUE(box_reader->Read4(&fourcc));
EXPECT_EQ(mp4::FOURCC_MINF, static_cast<mp4::FourCC>(fourcc));
// `vmhd`
EXPECT_TRUE(box_reader->SkipBytes(kTotalSizeLength));
EXPECT_TRUE(box_reader->Read4(&fourcc));
EXPECT_EQ(mp4::FOURCC_VMHD, static_cast<mp4::FourCC>(fourcc));
EXPECT_TRUE(box_reader->SkipBytes(kFlagsAndVersionLength));
uint16_t value16;
// graphics_mode.
EXPECT_TRUE(box_reader->Read2(&value16));
EXPECT_EQ(0, value16);
// op_color
EXPECT_TRUE(box_reader->Read2(&value16));
EXPECT_EQ(0, value16);
EXPECT_TRUE(box_reader->Read2(&value16));
EXPECT_EQ(0, value16);
EXPECT_TRUE(box_reader->Read2(&value16));
EXPECT_EQ(0, value16);
// `dinf`.
EXPECT_TRUE(box_reader->SkipBytes(kTotalSizeLength));
EXPECT_TRUE(box_reader->Read4(&fourcc));
EXPECT_EQ(mp4::FOURCC_DINF, static_cast<mp4::FourCC>(fourcc));
// `dref`
uint32_t value;
EXPECT_TRUE(box_reader->SkipBytes(kTotalSizeLength));
EXPECT_TRUE(box_reader->Read4(&fourcc));
EXPECT_EQ(mp4::FOURCC_DREF, static_cast<mp4::FourCC>(fourcc));
EXPECT_TRUE(box_reader->SkipBytes(kFlagsAndVersionLength));
EXPECT_TRUE(box_reader->Read4(&value));
EXPECT_EQ(1u, value); // entry_count.
// `url`.
EXPECT_TRUE(box_reader->SkipBytes(kTotalSizeLength));
EXPECT_TRUE(box_reader->Read4(&fourcc));
EXPECT_EQ(mp4::FOURCC_URL, static_cast<mp4::FourCC>(fourcc));
EXPECT_TRUE(box_reader->SkipBytes(kFlagsAndVersionLength));
std::vector<uint8_t> value_bytes;
EXPECT_TRUE(box_reader->ReadVec(&value_bytes, kUrl.size()));
std::string location = std::string(value_bytes.begin(), value_bytes.end());
EXPECT_EQ(kUrl, location);
// Once Flush, it needs to reset the internal objects of context and buffer.
Reset();
}
TEST_F(Mp4MuxerBoxWriterTest, Mp4MovieMediaMultipleSampleBoxes) {
// Tests `dinf` and its children box writer.
std::vector<uint8_t> written_data;
CreateContext(written_data);
mp4::writable_boxes::SampleTable sample_table;
Mp4MovieSampleTableBoxWriter box_writer(*context(), sample_table);
FlushAndWait(&box_writer);
// MediaInformation will have multiple sample boxes even though they
// not added exclusively.
std::unique_ptr<mp4::BoxReader> box_reader(
mp4::BoxReader::ReadConcatentatedBoxes(written_data.data(),
written_data.size(), nullptr));
// `stbl`.
uint32_t fourcc;
EXPECT_TRUE(box_reader->SkipBytes(kTotalSizeLength));
EXPECT_TRUE(box_reader->Read4(&fourcc));
EXPECT_EQ(mp4::FOURCC_STBL, static_cast<mp4::FourCC>(fourcc));
// `stsc`.
EXPECT_TRUE(box_reader->SkipBytes(kTotalSizeLength));
EXPECT_TRUE(box_reader->Read4(&fourcc));
EXPECT_EQ(mp4::FOURCC_STSC, static_cast<mp4::FourCC>(fourcc));
EXPECT_TRUE(box_reader->SkipBytes(kFlagsAndVersionLength));
EXPECT_TRUE(box_reader->SkipBytes(kEntryCountLength));
// `stts`.
EXPECT_TRUE(box_reader->SkipBytes(kTotalSizeLength));
EXPECT_TRUE(box_reader->Read4(&fourcc));
EXPECT_EQ(mp4::FOURCC_STTS, static_cast<mp4::FourCC>(fourcc));
EXPECT_TRUE(box_reader->SkipBytes(kFlagsAndVersionLength));
EXPECT_TRUE(box_reader->SkipBytes(kEntryCountLength));
// `stsz`.
EXPECT_TRUE(box_reader->SkipBytes(kTotalSizeLength));
EXPECT_TRUE(box_reader->Read4(&fourcc));
EXPECT_EQ(mp4::FOURCC_STSZ, static_cast<mp4::FourCC>(fourcc));
EXPECT_TRUE(box_reader->SkipBytes(kFlagsAndVersionLength));
EXPECT_TRUE(box_reader->SkipBytes(kSampleSizeAndCount));
// `stco`.
EXPECT_TRUE(box_reader->SkipBytes(kTotalSizeLength));
EXPECT_TRUE(box_reader->Read4(&fourcc));
EXPECT_EQ(mp4::FOURCC_STCO, static_cast<mp4::FourCC>(fourcc));
EXPECT_TRUE(box_reader->SkipBytes(kFlagsAndVersionLength));
EXPECT_TRUE(box_reader->SkipBytes(kEntryCountLength));
// `stsd`.
EXPECT_TRUE(box_reader->SkipBytes(kTotalSizeLength));
EXPECT_TRUE(box_reader->Read4(&fourcc));
EXPECT_EQ(mp4::FOURCC_STSD, static_cast<mp4::FourCC>(fourcc));
}
#if BUILDFLAG(USE_PROPRIETARY_CODECS)
TEST_F(Mp4MuxerBoxWriterTest, Mp4MovieVisualSampleEntry) {
// Tests `avc1` and its children box writer.
std::vector<uint8_t> written_data;
CreateContext(written_data);
mp4::writable_boxes::SampleDescription sample_description;
mp4::writable_boxes::VisualSampleEntry visual_sample_entry;
visual_sample_entry.coded_size = gfx::Size(kWidth, kHeight);
visual_sample_entry.compressor_name = "Chromium AVC Coding";
visual_sample_entry.codec = VideoCodec::kH264;
mp4::writable_boxes::AVCDecoderConfiguration avc = {};
avc.avc_config_record.version = 1;
avc.avc_config_record.profile_indication = kProfileIndicationNoChroma;
avc.avc_config_record.profile_compatibility = kProfileCompatibility;
avc.avc_config_record.avc_level = kLevelIndication;
avc.avc_config_record.length_size = kNALUUnitLength;
std::vector<uint8_t> sps(std::begin(kSPS), std::end(kSPS));
avc.avc_config_record.sps_list.emplace_back(sps);
std::vector<uint8_t> pps(std::begin(kPPS), std::end(kPPS));
avc.avc_config_record.pps_list.emplace_back(pps);
visual_sample_entry.avc_decoder_configuration = std::move(avc);
sample_description.video_sample_entry = std::move(visual_sample_entry);
Mp4MovieSampleDescriptionBoxWriter box_writer(*context(), sample_description);
FlushAndWait(&box_writer);
// MediaInformation will have multiple sample boxes even though they
// not added exclusively.
std::unique_ptr<mp4::BoxReader> box_reader(
mp4::BoxReader::ReadConcatentatedBoxes(written_data.data(),
written_data.size(), nullptr));
EXPECT_TRUE(box_reader->ScanChildren());
mp4::SampleDescription reader_sample_description;
reader_sample_description.type = mp4::kVideo;
EXPECT_TRUE(box_reader->ReadChild(&reader_sample_description));
EXPECT_EQ(1u, reader_sample_description.video_entries.size());
const auto& video_sample_entry = reader_sample_description.video_entries[0];
EXPECT_TRUE(video_sample_entry.IsFormatValid());
EXPECT_EQ(1, video_sample_entry.data_reference_index);
EXPECT_EQ(static_cast<uint16_t>(kWidth), video_sample_entry.width);
EXPECT_EQ(static_cast<uint16_t>(kHeight), video_sample_entry.height);
EXPECT_EQ(VideoCodecProfile::H264PROFILE_MAIN,
video_sample_entry.video_info.profile);
}
TEST_F(Mp4MuxerBoxWriterTest, Mp4MovieAVCDecoderConfigurationRecord) {
// Tests `avc1` and its children box writer.
std::vector<uint8_t> written_data;
CreateContext(written_data);
mp4::writable_boxes::AVCDecoderConfiguration avc = {};
avc.avc_config_record.version = 1;
avc.avc_config_record.profile_indication = kProfileIndication;
avc.avc_config_record.profile_compatibility = kProfileCompatibility;
avc.avc_config_record.avc_level = kLevelIndication;
avc.avc_config_record.length_size = kNALUUnitLength;
std::vector<uint8_t> sps(std::begin(kSPS), std::end(kSPS));
avc.avc_config_record.sps_list.emplace_back(sps);
std::vector<uint8_t> pps(std::begin(kPPS), std::end(kPPS));
avc.avc_config_record.pps_list.emplace_back(pps);
avc.avc_config_record.chroma_format = kChromaFormat;
avc.avc_config_record.bit_depth_luma_minus8 = kLumaMinus8;
avc.avc_config_record.bit_depth_chroma_minus8 = kChromaMinus8;
Mp4MovieAVCDecoderConfigurationBoxWriter box_writer(*context(), avc);
FlushAndWait(&box_writer);
// MediaInformation will have multiple sample boxes even though they
// not added exclusively.
std::unique_ptr<mp4::BoxReader> box_reader(
mp4::BoxReader::ReadConcatentatedBoxes(written_data.data(),
written_data.size(), nullptr));
EXPECT_TRUE(box_reader->ScanChildren());
mp4::AVCDecoderConfigurationRecord avc_config_reader;
EXPECT_TRUE(box_reader->ReadChild(&avc_config_reader));
EXPECT_EQ(kProfileIndication, avc_config_reader.profile_indication);
EXPECT_EQ(kProfileCompatibility, avc_config_reader.profile_compatibility);
EXPECT_EQ(kLevelIndication, avc_config_reader.avc_level);
EXPECT_EQ(kNALUUnitLength, avc_config_reader.length_size);
EXPECT_EQ(1u, avc_config_reader.sps_list.size());
EXPECT_EQ(1u, avc_config_reader.pps_list.size());
std::vector<uint8_t> sps1 = avc_config_reader.sps_list[0];
std::vector<uint8_t> pps1 = avc_config_reader.pps_list[0];
EXPECT_EQ(std::vector<uint8_t>(std::begin(kSPS), std::end(kSPS)), sps1);
EXPECT_EQ(std::vector<uint8_t>(std::begin(kPPS), std::end(kPPS)), pps1);
EXPECT_EQ((kChromaFormat & 0x3), avc_config_reader.chroma_format);
EXPECT_EQ((kLumaMinus8 & 0x7), avc_config_reader.bit_depth_luma_minus8);
EXPECT_EQ((kChromaMinus8 & 0x7), avc_config_reader.bit_depth_chroma_minus8);
EXPECT_EQ(0u, avc_config_reader.sps_ext_list.size());
}
TEST_F(Mp4MuxerBoxWriterTest, Mp4AacAudioSampleEntry) {
// Tests `aac` and its children box writer.
std::vector<uint8_t> written_data;
CreateContext(written_data);
mp4::writable_boxes::SampleDescription sample_description;
mp4::writable_boxes::AudioSampleEntry audio_sample_entry;
constexpr uint32_t kSampleRate = 48000u;
audio_sample_entry.channel_count = 2u;
audio_sample_entry.sample_rate = kSampleRate;
audio_sample_entry.codec = AudioCodec::kAAC;
mp4::writable_boxes::ElementaryStreamDescriptor esds;
constexpr uint32_t kBitRate = 341000u;
constexpr int32_t kSampleFrequency = 48000;
esds.aac_codec_description.push_back(0x11);
esds.aac_codec_description.push_back(0x90);
audio_sample_entry.elementary_stream_descriptor = std::move(esds);
mp4::writable_boxes::BitRate bit_rate;
bit_rate.max_bit_rate = kBitRate;
bit_rate.avg_bit_rate = kBitRate;
audio_sample_entry.bit_rate = std::move(bit_rate);
sample_description.audio_sample_entry = std::move(audio_sample_entry);
Mp4MovieSampleDescriptionBoxWriter box_writer(*context(), sample_description);
FlushAndWait(&box_writer);
// MediaInformation will have multiple sample boxes even though they
// not added exclusively.
std::unique_ptr<mp4::BoxReader> box_reader(
mp4::BoxReader::ReadConcatentatedBoxes(written_data.data(),
written_data.size(), nullptr));
EXPECT_TRUE(box_reader->ScanChildren());
mp4::SampleDescription reader_sample_description;
reader_sample_description.type = mp4::kAudio;
EXPECT_TRUE(box_reader->ReadChild(&reader_sample_description));
EXPECT_EQ(1u, reader_sample_description.audio_entries.size());
const auto& audio_sample = reader_sample_description.audio_entries[0];
EXPECT_EQ(1, audio_sample.data_reference_index);
EXPECT_EQ(2, audio_sample.channelcount);
EXPECT_EQ(16, audio_sample.samplesize);
EXPECT_EQ(kSampleRate, audio_sample.samplerate);
const mp4::ElementaryStreamDescriptor& esds_reader = audio_sample.esds;
EXPECT_EQ(mp4::kISO_14496_3, esds_reader.object_type);
const mp4::AAC& aac = esds_reader.aac;
AudioCodecProfile profile = aac.GetProfile();
EXPECT_EQ(AudioCodecProfile::kUnknown, profile);
int aac_frequency = aac.GetOutputSamplesPerSecond(false);
EXPECT_EQ(kSampleFrequency, aac_frequency);
ChannelLayout channel_layout = aac.GetChannelLayout(false);
EXPECT_EQ(media::CHANNEL_LAYOUT_STEREO, channel_layout);
std::vector<uint8_t> buffer;
int adts_header_size;
EXPECT_TRUE(aac.ConvertEsdsToADTS(&buffer, &adts_header_size));
ADTSStreamParser adts_parser;
int frame_size = 0, sample_rate = 0, sample_count = 0;
ChannelLayout adts_channel_layout;
bool metadata_frame;
EXPECT_NE(adts_parser.ParseFrameHeader(
buffer.data(), adts_header_size, &frame_size, &sample_rate,
&adts_channel_layout, &sample_count, &metadata_frame, nullptr),
-1);
EXPECT_EQ(adts_header_size, frame_size);
EXPECT_EQ(kSampleFrequency, sample_rate);
EXPECT_EQ(media::CHANNEL_LAYOUT_STEREO, adts_channel_layout);
EXPECT_EQ(1024, sample_count);
EXPECT_FALSE(metadata_frame);
}
#endif
TEST_F(Mp4MuxerBoxWriterTest, Mp4MovieVPConfigurationRecord) {
// Tests `vpcC` and its children box writer.
std::vector<uint8_t> written_data;
CreateContext(written_data);
mp4::writable_boxes::VPCodecConfiguration vp_config = {};
vp_config.profile = VP9PROFILE_MIN;
vp_config.level = 0u;
vp_config.color_space = gfx::ColorSpace(
gfx::ColorSpace::PrimaryID::BT470M, gfx::ColorSpace::TransferID::GAMMA28,
gfx::ColorSpace::MatrixID::BT470BG, gfx::ColorSpace::RangeID::FULL);
Mp4MovieVPCodecConfigurationBoxWriter box_writer(*context(), vp_config);
FlushAndWait(&box_writer);
// MediaInformation will have multiple sample boxes even though they
// not added exclusively.
std::unique_ptr<mp4::BoxReader> box_reader(
mp4::BoxReader::ReadConcatentatedBoxes(written_data.data(),
written_data.size(), nullptr));
EXPECT_TRUE(box_reader->ScanChildren());
mp4::VPCodecConfigurationRecord vp_config_record;
EXPECT_TRUE(box_reader->ReadChild(&vp_config_record));
EXPECT_EQ(VP9PROFILE_MIN, vp_config_record.profile);
EXPECT_EQ(0u, vp_config_record.level);
EXPECT_EQ(gfx::ColorSpace::RangeID::FULL, vp_config_record.color_space.range);
EXPECT_EQ(VideoColorSpace::PrimaryID::BT470M,
vp_config_record.color_space.primaries);
EXPECT_EQ(VideoColorSpace::TransferID::GAMMA28,
vp_config_record.color_space.transfer);
EXPECT_EQ(VideoColorSpace::MatrixID::BT470BG,
vp_config_record.color_space.matrix);
}
TEST_F(Mp4MuxerBoxWriterTest, Mp4OpusAudioSampleEntry) {
// Tests `opus` and its children box writer.
std::vector<uint8_t> written_data;
CreateContext(written_data);
mp4::writable_boxes::SampleDescription sample_description;
mp4::writable_boxes::AudioSampleEntry audio_sample_entry;
constexpr uint32_t kSampleRate = 48000u;
audio_sample_entry.channel_count = 2u;
audio_sample_entry.sample_rate = kSampleRate;
audio_sample_entry.codec = AudioCodec::kOpus;
mp4::writable_boxes::OpusSpecificBox opus_specific_box;
opus_specific_box.channel_count = 2u;
opus_specific_box.sample_rate = 48000u;
audio_sample_entry.opus_specific_box = std::move(opus_specific_box);
sample_description.audio_sample_entry = std::move(audio_sample_entry);
Mp4MovieSampleDescriptionBoxWriter box_writer(*context(), sample_description);
FlushAndWait(&box_writer);
// MediaInformation will have multiple sample boxes even though they
// not added exclusively.
std::unique_ptr<mp4::BoxReader> box_reader(
mp4::BoxReader::ReadConcatentatedBoxes(written_data.data(),
written_data.size(), nullptr));
EXPECT_TRUE(box_reader->ScanChildren());
mp4::SampleDescription reader_sample_description;
reader_sample_description.type = mp4::kAudio;
EXPECT_TRUE(box_reader->ReadChild(&reader_sample_description));
EXPECT_EQ(1u, reader_sample_description.audio_entries.size());
const auto& audio_sample = reader_sample_description.audio_entries[0];
EXPECT_EQ(1, audio_sample.data_reference_index);
EXPECT_EQ(2, audio_sample.channelcount);
EXPECT_EQ(16, audio_sample.samplesize);
EXPECT_EQ(kSampleRate, audio_sample.samplerate);
const mp4::OpusSpecificBox& dops_box = audio_sample.dops;
EXPECT_EQ(2u, dops_box.channel_count);
EXPECT_EQ(48000u, dops_box.sample_rate);
}
TEST_F(Mp4MuxerBoxWriterTest, Mp4Fragments) {
// Tests `mvex/trex` box writer.
std::vector<uint8_t> written_data;
CreateContext(written_data);
context_->SetVideoTrack({0, kVideoTimescale});
context_->SetAudioTrack({1, kAudioTimescale});
constexpr uint32_t kSampleDurations[] = {960, 960, 960};
constexpr uint32_t kVideoSampleDurationsAfterTimescale[] = {28800, 28800,
28800};
constexpr uint32_t kAudioSampleDurationsAfterTimescale[] = {42336, 42336,
42336};
constexpr uint32_t kSampleSizes[] = {6400, 333, 333};
constexpr uint32_t kSampleCount = 3u;
constexpr uint32_t kVideoBaseDecodeTime = 123u;
constexpr uint32_t kAudioBaseDecodeTime = 345u;
constexpr uint32_t kVideoDataSize = 4000u;
constexpr uint32_t kAudioDataSize = 2000u;
constexpr uint32_t kBoxHeaderSize = 8u;
using H =
std::underlying_type_t<mp4::writable_boxes::TrackFragmentHeaderFlags>;
using R = std::underlying_type_t<mp4::writable_boxes::TrackFragmentRunFlags>;
using S = std::underlying_type_t<mp4::writable_boxes::FragmentSampleFlags>;
mp4::writable_boxes::MovieFragment moof;
moof.header.sequence_number = 2u;
{ // `video`.
mp4::writable_boxes::TrackFragment video_fragment;
video_fragment.header.track_id = 1u;
video_fragment.header.flags =
(static_cast<H>(mp4::writable_boxes::TrackFragmentHeaderFlags::
kDefaultBaseIsMoof) |
static_cast<H>(mp4::writable_boxes::TrackFragmentHeaderFlags::
kDefaultSampleDurationPresent) |
static_cast<H>(mp4::writable_boxes::TrackFragmentHeaderFlags::
kkDefaultSampleFlagsPresent));
video_fragment.header.default_sample_duration =
base::Milliseconds(kDuration1);
video_fragment.header.default_sample_flags = static_cast<S>(
mp4::writable_boxes::FragmentSampleFlags::kSampleFlagDependsNo);
video_fragment.decode_time.track_id = 1;
video_fragment.decode_time.base_media_decode_time =
base::Milliseconds(kVideoBaseDecodeTime);
{ // `video, trun`
mp4::writable_boxes::TrackFragmentRun video_trun;
video_trun.flags =
(static_cast<R>(
mp4::writable_boxes::TrackFragmentRunFlags::kDataOffsetPresent) |
static_cast<R>(mp4::writable_boxes::TrackFragmentRunFlags::
kFirstSampleFlagsPresent) |
static_cast<R>(mp4::writable_boxes::TrackFragmentRunFlags::
kSampleDurationPresent));
video_trun.sample_count = kSampleCount;
video_trun.first_sample_flags =
(static_cast<S>(
mp4::writable_boxes::FragmentSampleFlags::kSampleFlagIsNonSync) |
static_cast<S>(mp4::writable_boxes::FragmentSampleFlags::
kSampleFlagDependsYes));
std::vector<base::TimeTicks> time_ticks;
base::TimeTicks base_time_ticks = base::TimeTicks::Now();
time_ticks.push_back(base_time_ticks);
base::TimeDelta delta;
for (auto* iter = std::begin(kSampleDurations);
iter != std::end(kSampleDurations); ++iter) {
delta += base::Milliseconds(*iter);
time_ticks.push_back(base_time_ticks + delta);
}
video_trun.sample_timestamps = std::move(time_ticks);
video_fragment.run = std::move(video_trun);
}
moof.track_fragments.push_back(std::move(video_fragment));
}
{ // `audio`.
mp4::writable_boxes::TrackFragment audio_fragment;
audio_fragment.header.track_id = 2u;
audio_fragment.header.flags =
(static_cast<H>(mp4::writable_boxes::TrackFragmentHeaderFlags::
kDefaultBaseIsMoof) |
static_cast<H>(mp4::writable_boxes::TrackFragmentHeaderFlags::
kDefaultSampleSizePresent) |
static_cast<H>(mp4::writable_boxes::TrackFragmentHeaderFlags::
kkDefaultSampleFlagsPresent));
audio_fragment.header.default_sample_size = kDefaultSampleSize;
audio_fragment.header.default_sample_flags =
(static_cast<S>(
mp4::writable_boxes::FragmentSampleFlags::kSampleFlagIsNonSync) |
static_cast<S>(
mp4::writable_boxes::FragmentSampleFlags::kSampleFlagDependsYes));
audio_fragment.decode_time.track_id = 2u;
audio_fragment.decode_time.base_media_decode_time =
base::Milliseconds(kAudioBaseDecodeTime);
{ // `audio, trun.
mp4::writable_boxes::TrackFragmentRun audio_trun;
audio_trun.flags =
(static_cast<R>(
mp4::writable_boxes::TrackFragmentRunFlags::kDataOffsetPresent) |
static_cast<R>(mp4::writable_boxes::TrackFragmentRunFlags::
kSampleDurationPresent) |
static_cast<R>(
mp4::writable_boxes::TrackFragmentRunFlags::kSampleSizePresent));
audio_trun.sample_count = kSampleCount;
audio_trun.first_sample_flags =
(static_cast<S>(
mp4::writable_boxes::FragmentSampleFlags::kSampleFlagIsNonSync) |
static_cast<S>(mp4::writable_boxes::FragmentSampleFlags::
kSampleFlagDependsYes));
std::vector<base::TimeTicks> time_ticks;
base::TimeTicks base_time_ticks = base::TimeTicks::Now();
time_ticks.push_back(base_time_ticks);
base::TimeDelta delta = base::Milliseconds(0);
for (auto* iter = std::begin(kSampleDurations);
iter != std::end(kSampleDurations); ++iter) {
delta += base::Milliseconds(*iter);
time_ticks.push_back(base_time_ticks + delta);
}
audio_trun.sample_timestamps = std::move(time_ticks);
std::vector<uint32_t> sizes(std::begin(kSampleSizes),
std::end(kSampleSizes));
audio_trun.sample_sizes = std::move(sizes);
audio_fragment.run = std::move(audio_trun);
}
moof.track_fragments.push_back(std::move(audio_fragment));
}
// Write `mdat` data.
mp4::writable_boxes::MediaData media_data;
std::vector<uint8_t> video_data(kVideoDataSize, 0);
std::vector<uint8_t> audio_data(kAudioDataSize, 1);
media_data.track_data.push_back(std::move(video_data));
media_data.track_data.push_back(std::move(audio_data));
// Write `moof` boxes.
Mp4MovieFragmentBoxWriter box_writer(*context(), moof);
BoxByteStream box_byte_stream;
box_writer.Write(box_byte_stream);
// Write `mdat` box with `moof` boxes writer object.
Mp4MediaDataBoxWriter box_mdat_writer(*context(), media_data);
FlushWithBoxWriterAndWait(&box_mdat_writer, box_byte_stream);
// Validation of the written boxes.
// `written_data` test.
std::unique_ptr<mp4::BoxReader> reader;
mp4::ParseResult result = mp4::BoxReader::ReadTopLevelBox(
written_data.data(), written_data.size(), nullptr, &reader);
EXPECT_EQ(result, mp4::ParseResult::kOk);
EXPECT_TRUE(reader);
// `moof` test.
EXPECT_EQ(mp4::FOURCC_MOOF, reader->type());
EXPECT_TRUE(reader->ScanChildren());
// `mfhd` test.
mp4::MovieFragmentHeader mfhd_box;
EXPECT_TRUE(reader->ReadChild(&mfhd_box));
EXPECT_EQ(2u, mfhd_box.sequence_number);
// `traf` test.
std::vector<mp4::TrackFragment> traf_boxes;
EXPECT_TRUE(reader->ReadChildren(&traf_boxes));
ASSERT_EQ(traf_boxes.size(), 2u);
// `tfhd` test of video.
EXPECT_EQ(1u, traf_boxes[0].header.track_id);
EXPECT_EQ(kDuration1, traf_boxes[0].header.default_sample_duration);
EXPECT_EQ(0u, traf_boxes[0].header.default_sample_size);
EXPECT_EQ(true, traf_boxes[0].header.has_default_sample_flags);
EXPECT_EQ(static_cast<S>(
mp4::writable_boxes::FragmentSampleFlags::kSampleFlagDependsNo),
traf_boxes[0].header.default_sample_flags);
// `tfdt` test of video.
EXPECT_EQ(ConvertToTimescale(base::Milliseconds(kVideoBaseDecodeTime),
kVideoTimescale),
traf_boxes[0].decode_time.decode_time);
// `trun` test of video.
uint32_t mdat_video_data_offset;
ASSERT_EQ(1u, traf_boxes[0].runs.size());
EXPECT_EQ(kSampleCount, traf_boxes[0].runs[0].sample_count);
EXPECT_EQ(216u, traf_boxes[0].runs[0].data_offset);
mdat_video_data_offset = traf_boxes[0].runs[0].data_offset;
ASSERT_EQ(kSampleCount, traf_boxes[0].runs[0].sample_durations.size());
EXPECT_EQ(
std::vector<uint32_t>(std::begin(kVideoSampleDurationsAfterTimescale),
std::end(kVideoSampleDurationsAfterTimescale)),
traf_boxes[0].runs[0].sample_durations);
ASSERT_EQ(0u, traf_boxes[0].runs[0].sample_sizes.size());
// kFirstSampleFlagsPresent enabled and no sample_flags entry,
// then sample_flags will have a value of the first sample flags.
ASSERT_EQ(1u, traf_boxes[0].runs[0].sample_flags.size());
ASSERT_EQ(0u, traf_boxes[0].runs[0].sample_composition_time_offsets.size());
// `tfhd` test of audio.
EXPECT_EQ(2u, traf_boxes[1].header.track_id);
EXPECT_EQ(0u, traf_boxes[1].header.default_sample_duration);
EXPECT_EQ(kDefaultSampleSize, traf_boxes[1].header.default_sample_size);
EXPECT_EQ(true, traf_boxes[1].header.has_default_sample_flags);
EXPECT_EQ(
(static_cast<S>(
mp4::writable_boxes::FragmentSampleFlags::kSampleFlagIsNonSync) |
static_cast<S>(
mp4::writable_boxes::FragmentSampleFlags::kSampleFlagDependsYes)),
traf_boxes[1].header.default_sample_flags);
// `tfdt` test of audio.
EXPECT_EQ(ConvertToTimescale(base::Milliseconds(kAudioBaseDecodeTime),
kAudioTimescale),
traf_boxes[1].decode_time.decode_time);
// `trun` test of audio.
ASSERT_EQ(1u, traf_boxes[1].runs.size());
EXPECT_EQ(kSampleCount, traf_boxes[1].runs[0].sample_count);
uint32_t audio_data_offset = mdat_video_data_offset + kVideoDataSize;
EXPECT_EQ(audio_data_offset, traf_boxes[1].runs[0].data_offset);
ASSERT_EQ(kSampleCount, traf_boxes[1].runs[0].sample_durations.size());
EXPECT_EQ(
std::vector<uint32_t>(std::begin(kAudioSampleDurationsAfterTimescale),
std::end(kAudioSampleDurationsAfterTimescale)),
traf_boxes[1].runs[0].sample_durations);
ASSERT_EQ(kSampleCount, traf_boxes[1].runs[0].sample_sizes.size());
ASSERT_EQ(0u, traf_boxes[1].runs[0].sample_flags.size());
EXPECT_EQ(
std::vector<uint32_t>(std::begin(kSampleSizes), std::end(kSampleSizes)),
traf_boxes[1].runs[0].sample_sizes);
ASSERT_EQ(0u, traf_boxes[1].runs[0].sample_composition_time_offsets.size());
// `mdat` test.
std::unique_ptr<mp4::BoxReader> mdat_reader;
mp4::ParseResult result1 = mp4::BoxReader::ReadTopLevelBox(
written_data.data() + mdat_video_data_offset - kBoxHeaderSize,
written_data.size() - mdat_video_data_offset + kBoxHeaderSize, nullptr,
&mdat_reader);
EXPECT_EQ(result1, mp4::ParseResult::kOk);
EXPECT_TRUE(mdat_reader);
EXPECT_EQ(mp4::FOURCC_MDAT, mdat_reader->type());
EXPECT_EQ(kVideoDataSize + kAudioDataSize + kBoxHeaderSize,
mdat_reader->box_size());
// Once Flush, it needs to reset the internal objects of context and buffer.
Reset();
}
TEST_F(Mp4MuxerBoxWriterTest, Mp4FtypBox) {
// Tests `ftyp` box writer.
std::vector<uint8_t> written_data;
CreateContext(written_data);
mp4::writable_boxes::FileType mp4_file_type_box;
mp4_file_type_box.major_brand = mp4::FOURCC_MP41;
mp4_file_type_box.minor_version = 0;
mp4_file_type_box.compatible_brands.emplace_back(mp4::FOURCC_MP4A);
mp4_file_type_box.compatible_brands.emplace_back(mp4::FOURCC_AVC1);
// Flush at requested.
Mp4FileTypeBoxWriter box_writer(*context(), mp4_file_type_box);
FlushAndWait(&box_writer);
// Validation of the written boxes.
// `written_data` test.
std::unique_ptr<mp4::BoxReader> reader;
mp4::ParseResult result = mp4::BoxReader::ReadTopLevelBox(
written_data.data(), written_data.size(), nullptr, &reader);
EXPECT_EQ(result, mp4::ParseResult::kOk);
EXPECT_TRUE(reader);
EXPECT_EQ(mp4::FOURCC_FTYP, reader->type());
mp4::FileType file_type;
EXPECT_TRUE(file_type.Parse(reader.get()));
EXPECT_EQ(file_type.major_brand, mp4::FOURCC_MP41);
EXPECT_EQ(file_type.minor_version, 0u);
// Once Flush, it needs to reset the internal objects of context and buffer.
Reset();
}
TEST_F(Mp4MuxerBoxWriterTest, Mp4MfraBox) {
// Tests `mfra` box writer.
std::vector<uint8_t> written_data;
CreateContext(written_data);
context_->SetVideoTrack({1, kVideoTimescale});
context_->SetAudioTrack({0, kAudioTimescale});
mp4::writable_boxes::TrackFragmentRandomAccess video_randome_access;
video_randome_access.track_id = 2;
mp4::writable_boxes::TrackFragmentRandomAccessEntry entry1;
entry1.moof_offset = 200;
entry1.time = base::Seconds(0);
entry1.traf_number = 1;
entry1.trun_number = 1;
entry1.sample_number = 1;
video_randome_access.entries.emplace_back(std::move(entry1));
mp4::writable_boxes::TrackFragmentRandomAccessEntry entry2;
entry2.moof_offset = 1000;
entry2.time = base::Seconds(3);
entry2.trun_number = 1;
entry2.sample_number = 1;
video_randome_access.entries.emplace_back(std::move(entry2));
mp4::writable_boxes::TrackFragmentRandomAccessEntry entry3;
entry3.moof_offset = 4000;
entry3.time = base::Seconds(6);
entry3.traf_number = 1;
entry3.trun_number = 1;
entry3.sample_number = 1;
video_randome_access.entries.emplace_back(std::move(entry3));
mp4::writable_boxes::FragmentRandomAccess frag_random_access;
// Add empty audio random access by its index position.
mp4::writable_boxes::TrackFragmentRandomAccess audio_randome_access;
frag_random_access.tracks.emplace_back(std::move(audio_randome_access));
frag_random_access.tracks.emplace_back(std::move(video_randome_access));
// Flush at requested.
Mp4FragmentRandomAccessBoxWriter box_writer(*context(), frag_random_access);
FlushAndWait(&box_writer);
// Validation of the written boxes.
// `written_data` test.
// Read from the `mfro` size value that will lead lead to point at
// the `mfra` box start offset.
uint32_t mfra_box_size = 0;
for (int last_index = written_data.size() - 1, j = 0; j < 4; j++) {
mfra_box_size += (written_data[last_index - j] << (j * 8));
}
uint8_t* last_offset_of_mp4_file = written_data.data() + written_data.size();
uint8_t* mfra_start_offset = last_offset_of_mp4_file - mfra_box_size;
std::unique_ptr<mp4::BoxReader> reader;
mp4::ParseResult result = mp4::BoxReader::ReadTopLevelBox(
mfra_start_offset, mfra_box_size, nullptr, &reader);
EXPECT_EQ(result, mp4::ParseResult::kOk);
EXPECT_TRUE(reader);
EXPECT_EQ(mp4::FOURCC_MFRA, reader->type());
// Once Flush, it needs to reset the internal objects of context and buffer.
Reset();
}
} // namespace media