blob: da6e7b126b569c963beb4e6b977c77495850d9d6 [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_muxer.h"
#include <algorithm>
#include <cstddef>
#include <memory>
#include <vector>
#include "base/big_endian.h"
#include "base/files/file_path.h"
#include "base/files/memory_mapped_file.h"
#include "base/path_service.h"
#include "base/strings/string_piece.h"
#include "base/test/bind.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "media/base/audio_decoder_config.h"
#include "media/base/audio_parameters.h"
#include "media/base/media_tracks.h"
#include "media/base/mock_media_log.h"
#include "media/base/stream_parser.h"
#include "media/base/video_decoder_config.h"
#include "media/formats/mp4/es_descriptor.h"
#include "media/formats/mp4/mp4_stream_parser.h"
#include "media/muxers/mp4_muxer_delegate.h"
#include "media/muxers/mp4_type_conversion.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#if BUILDFLAG(USE_PROPRIETARY_CODECS)
namespace media {
namespace {
using ::testing::_;
using ::testing::AllOf;
using ::testing::Eq;
using ::testing::Field;
using ::testing::InSequence;
using ::testing::Mock;
using ::testing::Optional;
using ::testing::Property;
using ::testing::Return;
using ::testing::StrEq;
constexpr uint32_t kAudioSampleRate = 44100u;
class MockDelegate : public Mp4MuxerDelegateInterface {
public:
MOCK_METHOD(void,
AddVideoFrame,
(const Muxer::VideoParameters& params,
std::string encoded_data,
std::optional<VideoEncoder::CodecDescription> codec_description,
base::TimeTicks timestamp,
bool is_key_frame),
(override));
MOCK_METHOD(void,
AddAudioFrame,
(const AudioParameters& params,
std::string encoded_data,
std::optional<AudioEncoder::CodecDescription> codec_description,
base::TimeTicks timestamp),
(override));
MOCK_METHOD(bool, Flush, (), (override));
MOCK_METHOD(bool, FlushFragment, (), (override));
};
AudioEncoder::CodecDescription GetAudioCodecDescription(uint8_t data) {
return {data};
}
VideoEncoder::CodecDescription GetVideoCodecDescription(uint8_t data) {
return {data};
}
struct TestParam {
bool has_video;
bool has_audio;
};
class Mp4MuxerTest : public ::testing::TestWithParam<TestParam> {
public:
Mp4MuxerTest()
: task_environment_(base::test::TaskEnvironment::TimeSource::MOCK_TIME),
delegate_(std::make_unique<MockDelegate>()),
delegate_ptr_(delegate_.get()) {}
~Mp4MuxerTest() override { delegate_ptr_ = nullptr; }
void CreateMuxer(
std::optional<base::TimeDelta> max_data_output_interval = std::nullopt) {
muxer_ = std::make_unique<Mp4Muxer>(
AudioCodec::kAAC, GetParam().has_video, GetParam().has_audio,
std::move(delegate_), max_data_output_interval);
}
base::test::TaskEnvironment task_environment_;
std::unique_ptr<MockDelegate> delegate_;
raw_ptr<MockDelegate> delegate_ptr_;
std::unique_ptr<Mp4Muxer> muxer_;
};
TEST_P(Mp4MuxerTest, ForwardsFlush) {
CreateMuxer();
InSequence s;
EXPECT_CALL(*delegate_ptr_, Flush).WillOnce(Return(false));
EXPECT_CALL(*delegate_ptr_, Flush).WillOnce(Return(true));
EXPECT_FALSE(muxer_->Flush());
EXPECT_TRUE(muxer_->Flush());
}
TEST_P(Mp4MuxerTest, ForwardsFrames) {
CreateMuxer();
InSequence s;
if (GetParam().has_audio) {
AudioParameters audio_params(AudioParameters::AUDIO_PCM_LOW_LATENCY,
ChannelLayoutConfig::Stereo(),
kAudioSampleRate, 1000);
EXPECT_CALL(
*delegate_ptr_,
AddAudioFrame(
AllOf(Property(&AudioParameters::format,
AudioParameters::AUDIO_PCM_LOW_LATENCY),
Property(&AudioParameters::sample_rate, kAudioSampleRate)),
StrEq("a1"), Optional(GetAudioCodecDescription(99)),
base::TimeTicks() + base::Milliseconds(10)));
EXPECT_CALL(*delegate_ptr_,
AddAudioFrame(_, StrEq("a2"), Eq(std::nullopt),
base::TimeTicks() + base::Milliseconds(20)));
muxer_->PutFrame(
Muxer::EncodedFrame{audio_params, GetAudioCodecDescription(99), "a1",
std::string(), true},
base::Milliseconds(10));
muxer_->PutFrame(Muxer::EncodedFrame{audio_params, std::nullopt, "a2",
std::string(), true},
base::Milliseconds(20));
}
if (GetParam().has_video) {
Muxer::VideoParameters video_params(gfx::Size(40, 30), 30,
VideoCodec::kH264, gfx::ColorSpace());
EXPECT_CALL(
*delegate_ptr_,
AddVideoFrame(
AllOf(Field(&Muxer::VideoParameters::frame_rate, 30),
Field(&Muxer::VideoParameters::codec, VideoCodec::kH264)),
StrEq("v1"), Optional(GetVideoCodecDescription(66)),
base::TimeTicks() + base::Milliseconds(30), true));
EXPECT_CALL(
*delegate_ptr_,
AddVideoFrame(_, StrEq("v2"), Eq(std::nullopt),
base::TimeTicks() + base::Milliseconds(40), false));
muxer_->PutFrame(
Muxer::EncodedFrame{video_params, GetVideoCodecDescription(66), "v1",
std::string(), true},
base::Milliseconds(30));
muxer_->PutFrame(Muxer::EncodedFrame{video_params, std::nullopt, "v2",
std::string(), false},
base::Milliseconds(40));
}
}
TEST_P(Mp4MuxerTest, DoesntFlushOnInsufficientlySpacedFrames) {
// This test needs video.
if (!GetParam().has_video) {
return;
}
CreateMuxer(base::Seconds(2));
Muxer::VideoParameters video_params(gfx::Size(40, 30), 30, VideoCodec::kH264,
gfx::ColorSpace());
muxer_->PutFrame(
Muxer::EncodedFrame{video_params, GetVideoCodecDescription(1), "v1",
std::string(), true},
base::Milliseconds(0));
task_environment_.AdvanceClock(base::Seconds(1));
muxer_->PutFrame(Muxer::EncodedFrame{video_params, std::nullopt, "v2",
std::string(), false},
base::Milliseconds(0));
// Insert a frame just before the duration limit set initially in the test.
// Expect that Flush isn't invoked.
task_environment_.AdvanceClock(base::Seconds(1) - base::Milliseconds(1));
EXPECT_CALL(*delegate_ptr_, Flush).Times(0);
muxer_->PutFrame(
Muxer::EncodedFrame{video_params, GetVideoCodecDescription(1), "v2",
std::string(), true},
base::Milliseconds(0));
Mock::VerifyAndClearExpectations(delegate_ptr_);
}
TEST_P(Mp4MuxerTest, FlushesOnSufficientlySpacedFramesForVideo) {
// This test needs video.
if (!GetParam().has_video) {
return;
}
CreateMuxer(base::Seconds(2));
Muxer::VideoParameters video_params(gfx::Size(40, 30), 30, VideoCodec::kH264,
gfx::ColorSpace());
muxer_->PutFrame(
Muxer::EncodedFrame{video_params, GetVideoCodecDescription(1), "v1",
std::string(), true},
base::Milliseconds(0));
task_environment_.AdvanceClock(base::Seconds(1));
muxer_->PutFrame(Muxer::EncodedFrame{video_params, std::nullopt, "v2",
std::string(), false},
base::Milliseconds(0));
// Time will advance to the time for the next flush, so expect Flush called
// on the next keyframe.
task_environment_.AdvanceClock(base::Seconds(1));
EXPECT_CALL(*delegate_ptr_, FlushFragment);
muxer_->PutFrame(
Muxer::EncodedFrame{video_params, GetVideoCodecDescription(1), "v2",
std::string(), true},
base::Milliseconds(0));
Mock::VerifyAndClearExpectations(delegate_ptr_);
}
TEST_P(Mp4MuxerTest, FlushesOnSufficientlySpacedFramesForAudioOnly) {
if (GetParam().has_video) {
return;
}
CreateMuxer(base::Seconds(2));
AudioParameters audio_params(AudioParameters::AUDIO_PCM_LOW_LATENCY,
ChannelLayoutConfig::Stereo(), kAudioSampleRate,
1000);
muxer_->PutFrame(
Muxer::EncodedFrame{audio_params, GetVideoCodecDescription(1), "a1",
std::string(), true},
base::Milliseconds(0));
task_environment_.AdvanceClock(base::Seconds(1));
muxer_->PutFrame(Muxer::EncodedFrame{audio_params, std::nullopt, "a2",
std::string(), false},
base::Milliseconds(0));
// Time will advance to the time for the next flush, so expect Flush called
// on the next keyframe.
task_environment_.AdvanceClock(base::Seconds(1));
EXPECT_CALL(*delegate_ptr_, FlushFragment);
muxer_->PutFrame(
Muxer::EncodedFrame{audio_params, GetAudioCodecDescription(1), "a2",
std::string(), true},
base::Milliseconds(0));
Mock::VerifyAndClearExpectations(delegate_ptr_);
}
static const TestParam kTestCases[] = {
{true, false},
{false, true},
{true, true},
};
INSTANTIATE_TEST_SUITE_P(, Mp4MuxerTest, testing::ValuesIn(kTestCases));
} // namespace
} // namespace media
#endif // #if BUILDFLAG(USE_PROPRIETARY_CODECS)