chromium/media/muxers/mp4_muxer_box_writer_unittest.cc

// 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.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#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 =;
constexpr uint32_t kDuration2 =;
constexpr uint32_t kDefaultSampleSize =;
constexpr uint32_t kWidth =;
constexpr uint32_t kHeight =;
constexpr uint32_t kVideoTimescale =;
constexpr uint32_t kAudioTimescale =;
constexpr uint32_t kVideoSampleFlags =;
constexpr uint32_t kAudioSampleFlags =;
constexpr uint16_t kAudioVolume =;
constexpr char kVideoHandlerName[] =;
constexpr char kAudioHandlerName[] =;
constexpr uint32_t kTotalSizeLength =;
constexpr uint32_t kFlagsAndVersionLength =;
constexpr uint32_t kEntryCountLength =;
constexpr uint32_t kSampleSizeAndCount =;
constexpr size_t kVideoIndex =;
constexpr size_t kAudioIndex =;

#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) {}

}  // namespace
class Mp4MuxerBoxWriterTest : public testing::Test {};

TEST_F(Mp4MuxerBoxWriterTest, Mp4MovieAndHeader) {}

TEST_F(Mp4MuxerBoxWriterTest, Mp4MovieExtends) {}

TEST_F(Mp4MuxerBoxWriterTest, Mp4MovieTrackAndMediaHeader) {}

TEST_F(Mp4MuxerBoxWriterTest, Mp4MovieMediaDataInformation) {}

TEST_F(Mp4MuxerBoxWriterTest, Mp4MovieMediaMultipleSampleBoxes) {}

#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(VideoCodec::kH264);
  visual_sample_entry.coded_size = gfx::Size(kWidth, kHeight);
  visual_sample_entry.compressor_name = "Chromium AVC Coding";

  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;

  constexpr uint32_t kSampleRate = 48000u;
  mp4::writable_boxes::AudioSampleEntry audio_sample_entry(AudioCodec::kAAC,
                                                           kSampleRate, 2u);

  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);

  int adts_header_size;
  auto buffer = aac.CreateAdtsFromEsds({}, &adts_header_size);
  EXPECT_FALSE(buffer.empty());

  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) {}

TEST_F(Mp4MuxerBoxWriterTest, Mp4OpusAudioSampleEntry) {}

TEST_F(Mp4MuxerBoxWriterTest, Mp4Fragments) {}

TEST_F(Mp4MuxerBoxWriterTest, Mp4FtypBox) {}

TEST_F(Mp4MuxerBoxWriterTest, Mp4MfraBox) {}

}  // namespace media