blob: edf038cfa731bfb0220b40b3d011dfd2310a0f34 [file] [log] [blame]
// Copyright 2015 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/webm_muxer.h"
#include <cstddef>
#include <cstdint>
#include <memory>
#include <optional>
#include <string_view>
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/memory/ptr_util.h"
#include "base/memory/ref_counted.h"
#include "base/ranges/algorithm.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "media/base/audio_codecs.h"
#include "media/base/audio_parameters.h"
#include "media/base/channel_layout.h"
#include "media/base/decoder_buffer.h"
#include "media/base/media_tracks.h"
#include "media/base/mock_media_log.h"
#include "media/base/stream_parser.h"
#include "media/base/stream_parser_buffer.h"
#include "media/base/video_codecs.h"
#include "media/base/video_frame.h"
#include "media/formats/webm/webm_stream_parser.h"
#include "media/muxers/live_webm_muxer_delegate.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::_;
using ::testing::AllOf;
using ::testing::AnyNumber;
using ::testing::AtLeast;
using ::testing::ElementsAre;
using ::testing::Eq;
using ::testing::InSequence;
using ::testing::Mock;
using ::testing::Not;
using ::testing::Pair;
using ::testing::Pointee;
using ::testing::Property;
using ::testing::Return;
using ::testing::Sequence;
using ::testing::TestWithParam;
using ::testing::UnorderedElementsAre;
using ::testing::ValuesIn;
using ::testing::WithArgs;
namespace media {
struct TestParams {
VideoCodec video_codec;
AudioCodec audio_codec;
size_t num_video_tracks;
size_t num_audio_tracks;
};
class WebmMuxerTest : public TestWithParam<TestParams> {
public:
WebmMuxerTest()
: webm_muxer_(std::make_unique<WebmMuxer>(
GetParam().audio_codec,
GetParam().num_video_tracks,
GetParam().num_audio_tracks,
std::make_unique<LiveWebmMuxerDelegate>(
base::BindRepeating(&WebmMuxerTest::WriteCallback,
base::Unretained(this))),
std::nullopt)) {
EXPECT_EQ(webm_muxer_->delegate_->Position(), 0);
const mkvmuxer::int64 kRandomNewPosition = 333;
EXPECT_EQ(webm_muxer_->delegate_->Position(kRandomNewPosition), -1);
EXPECT_FALSE(webm_muxer_->delegate_->Seekable());
}
WebmMuxerTest(const WebmMuxerTest&) = delete;
WebmMuxerTest& operator=(const WebmMuxerTest&) = delete;
MOCK_METHOD(void, WriteCallback, (std::string_view));
void SaveEncodedDataLen(const std::string_view encoded_data) {
last_encoded_length_ = encoded_data.size();
accumulated_position_ += encoded_data.size();
}
mkvmuxer::int64 GetWebmMuxerPosition() const {
return webm_muxer_->delegate_->Position();
}
mkvmuxer::Segment::Mode GetWebmSegmentMode() const {
return webm_muxer_->segment_.mode();
}
mkvmuxer::int32 WebmMuxerWrite(const void* buf, mkvmuxer::uint32 len) {
return webm_muxer_->delegate_->Write(buf, len);
}
Muxer::VideoParameters GetVideoParameters(scoped_refptr<VideoFrame> frame) {
Muxer::VideoParameters parameters(*frame);
parameters.codec = GetParam().video_codec;
return parameters;
}
mkvmuxer::Colour* GetVideoTrackColor() const {
mkvmuxer::VideoTrack* const video_track =
reinterpret_cast<mkvmuxer::VideoTrack*>(
webm_muxer_->segment_.GetTrackByNumber(
webm_muxer_->video_track_index_));
return video_track->colour();
}
void PutAudio(
const AudioParameters& params,
std::string encoded_data,
std::optional<media::AudioEncoder::CodecDescription> codec_description) {
webm_muxer_->PutFrame(
Muxer::EncodedFrame{std::move(params), std::move(codec_description),
std::move(encoded_data), std::string(),
/*is_keyframe=*/true},
now_);
now_ = now_ + base::Milliseconds(10);
}
void PutVideo(
const Muxer::VideoParameters& params,
std::string encoded_data,
std::string encoded_alpha,
std::optional<media::VideoEncoder::CodecDescription> codec_description,
bool is_keyframe) {
webm_muxer_->PutFrame(
Muxer::EncodedFrame{std::move(params), std::move(codec_description),
std::move(encoded_data), std::move(encoded_alpha),
is_keyframe},
now_);
now_ = now_ + base::Milliseconds(30);
}
std::unique_ptr<WebmMuxer> webm_muxer_;
size_t last_encoded_length_ = 0;
int64_t accumulated_position_ = 0;
base::TimeDelta now_ = base::Seconds(0);
};
// Checks that the WriteCallback is called with appropriate params when
// WebmMuxer::Write() method is called.
TEST_P(WebmMuxerTest, Write) {
const std::string_view encoded_data("abcdefghijklmnopqrstuvwxyz");
EXPECT_CALL(*this, WriteCallback(encoded_data));
WebmMuxerWrite(encoded_data.data(), encoded_data.size());
EXPECT_EQ(GetWebmMuxerPosition(), static_cast<int64_t>(encoded_data.size()));
}
// This test sends two frames and checks that the WriteCallback is called with
// appropriate params in both cases.
TEST_P(WebmMuxerTest, OnEncodedVideoTwoFrames) {
if (GetParam().num_audio_tracks > 0)
return;
const gfx::Size frame_size(160, 80);
const scoped_refptr<VideoFrame> video_frame =
VideoFrame::CreateBlackFrame(frame_size);
const std::string encoded_data("abcdefghijklmnopqrstuvwxyz");
EXPECT_CALL(*this, WriteCallback(_))
.Times(AtLeast(1))
.WillRepeatedly(
WithArgs<0>(Invoke(this, &WebmMuxerTest::SaveEncodedDataLen)));
PutVideo(GetVideoParameters(video_frame), encoded_data, std::string(),
std::nullopt, /*is_keyframe=*/true);
// First time around WriteCallback() is pinged a number of times to write the
// Matroska header, but at the end it dumps |encoded_data|.
EXPECT_EQ(last_encoded_length_, encoded_data.size());
EXPECT_EQ(GetWebmMuxerPosition(), accumulated_position_);
EXPECT_GE(GetWebmMuxerPosition(), static_cast<int64_t>(last_encoded_length_));
EXPECT_EQ(GetWebmSegmentMode(), mkvmuxer::Segment::kLive);
const int64_t begin_of_second_block = accumulated_position_;
EXPECT_CALL(*this, WriteCallback(_))
.Times(AtLeast(1))
.WillRepeatedly(
WithArgs<0>(Invoke(this, &WebmMuxerTest::SaveEncodedDataLen)));
PutVideo(GetVideoParameters(video_frame), encoded_data, std::string(),
std::nullopt, /*is_keyframe=*/false);
// The second time around the callbacks should include a SimpleBlock header,
// namely the track index, a timestamp and a flags byte, for a total of 6B.
EXPECT_EQ(last_encoded_length_, encoded_data.size());
EXPECT_EQ(GetWebmMuxerPosition(), accumulated_position_);
const uint32_t kSimpleBlockSize = 6u;
EXPECT_EQ(static_cast<int64_t>(begin_of_second_block + kSimpleBlockSize +
encoded_data.size()),
accumulated_position_);
}
// This test sends two transparent frames and checks that the WriteCallback is
// called with appropriate params in both cases.
TEST_P(WebmMuxerTest, OnEncodedVideoTwoAlphaFrames) {
if (GetParam().num_audio_tracks > 0)
return;
const gfx::Size frame_size(160, 80);
const scoped_refptr<VideoFrame> video_frame =
VideoFrame::CreateTransparentFrame(frame_size);
const std::string encoded_data("abcdefghijklmnopqrstuvwxyz");
const std::string alpha_encoded_data("ijklmnopqrstuvwxyz");
InSequence s;
EXPECT_CALL(*this, WriteCallback(_))
.Times(AtLeast(1))
.WillRepeatedly(
WithArgs<0>(Invoke(this, &WebmMuxerTest::SaveEncodedDataLen)));
PutVideo(GetVideoParameters(video_frame), encoded_data, alpha_encoded_data,
std::nullopt, /*is_keyframe=*/true);
EXPECT_EQ(GetWebmMuxerPosition(), accumulated_position_);
EXPECT_GE(GetWebmMuxerPosition(), static_cast<int64_t>(last_encoded_length_));
EXPECT_EQ(GetWebmSegmentMode(), mkvmuxer::Segment::kLive);
const int64_t begin_of_second_block = accumulated_position_;
EXPECT_CALL(*this, WriteCallback(_))
.Times(AtLeast(1))
.WillRepeatedly(
WithArgs<0>(Invoke(this, &WebmMuxerTest::SaveEncodedDataLen)));
PutVideo(GetVideoParameters(video_frame), encoded_data, alpha_encoded_data,
std::nullopt, /*is_keyframe=*/false);
EXPECT_EQ(GetWebmMuxerPosition(), accumulated_position_);
// Alpha introduces additional elements to be written, see
// mkvmuxer::WriteBlock().
const uint32_t kBlockGroupSize = 2u;
const uint32_t kSimpleBlockSize = 6u;
const uint32_t kAdditionsSize = 13u;
EXPECT_EQ(static_cast<int64_t>(begin_of_second_block + kBlockGroupSize +
kSimpleBlockSize + encoded_data.size() +
kAdditionsSize + alpha_encoded_data.size()),
accumulated_position_);
}
TEST_P(WebmMuxerTest, OnEncodedAudioTwoFrames) {
if (GetParam().num_video_tracks > 0)
return;
const int sample_rate = 48000;
const int frames_per_buffer = 480;
media::AudioParameters audio_params(
media::AudioParameters::Format::AUDIO_PCM_LOW_LATENCY,
ChannelLayoutConfig::Mono(), sample_rate, frames_per_buffer);
const std::string encoded_data("abcdefghijklmnopqrstuvwxyz");
EXPECT_CALL(*this, WriteCallback(_))
.Times(AtLeast(1))
.WillRepeatedly(
WithArgs<0>(Invoke(this, &WebmMuxerTest::SaveEncodedDataLen)));
PutAudio(audio_params, encoded_data, std::nullopt);
// First time around WriteCallback() is pinged a number of times to write the
// Matroska header, but at the end it dumps |encoded_data|.
EXPECT_EQ(last_encoded_length_, encoded_data.size());
EXPECT_EQ(GetWebmMuxerPosition(), accumulated_position_);
EXPECT_GE(GetWebmMuxerPosition(), static_cast<int64_t>(last_encoded_length_));
EXPECT_EQ(GetWebmSegmentMode(), mkvmuxer::Segment::kLive);
const int64_t begin_of_second_block = accumulated_position_;
EXPECT_CALL(*this, WriteCallback(_))
.Times(AtLeast(1))
.WillRepeatedly(
WithArgs<0>(Invoke(this, &WebmMuxerTest::SaveEncodedDataLen)));
PutAudio(audio_params, encoded_data, std::nullopt);
// The second time around the callbacks should include a SimpleBlock header,
// namely the track index, a timestamp and a flags byte, for a total of 6B.
EXPECT_EQ(last_encoded_length_, encoded_data.size());
EXPECT_EQ(GetWebmMuxerPosition(), accumulated_position_);
const uint32_t kSimpleBlockSize = 6u;
EXPECT_EQ(static_cast<int64_t>(begin_of_second_block + kSimpleBlockSize +
encoded_data.size()),
accumulated_position_);
}
TEST_P(WebmMuxerTest, ColorSpaceREC709IsPropagatedToTrack) {
Muxer::VideoParameters params(gfx::Size(1, 1), 0, media::VideoCodec::kVP9,
gfx::ColorSpace::CreateREC709());
PutVideo(params, "abab", {}, std::nullopt, true);
mkvmuxer::Colour* colour = GetVideoTrackColor();
EXPECT_EQ(colour->primaries(), mkvmuxer::Colour::kIturBt709P);
EXPECT_EQ(colour->transfer_characteristics(), mkvmuxer::Colour::kIturBt709Tc);
EXPECT_EQ(colour->matrix_coefficients(), mkvmuxer::Colour::kBt709);
EXPECT_EQ(colour->range(), mkvmuxer::Colour::kBroadcastRange);
}
TEST_P(WebmMuxerTest, ColorSpaceExtendedSRGBIsPropagatedToTrack) {
Muxer::VideoParameters params(
gfx::Size(1, 1), 0, media::VideoCodec::kVP9,
gfx::ColorSpace(
gfx::ColorSpace::PrimaryID::BT709, gfx::ColorSpace::TransferID::SRGB,
gfx::ColorSpace::MatrixID::BT709, gfx::ColorSpace::RangeID::LIMITED));
PutVideo(params, "banana", {}, std::nullopt, true);
mkvmuxer::Colour* colour = GetVideoTrackColor();
EXPECT_EQ(colour->primaries(), mkvmuxer::Colour::kIturBt709P);
EXPECT_EQ(colour->transfer_characteristics(), mkvmuxer::Colour::kIec6196621);
EXPECT_EQ(colour->matrix_coefficients(), mkvmuxer::Colour::kBt709);
EXPECT_EQ(colour->range(), mkvmuxer::Colour::kBroadcastRange);
}
TEST_P(WebmMuxerTest, ColorSpaceHDR10IsPropagatedToTrack) {
Muxer::VideoParameters params(
gfx::Size(1, 1), 0, media::VideoCodec::kVP9,
gfx::ColorSpace(gfx::ColorSpace::PrimaryID::BT2020,
gfx::ColorSpace::TransferID::PQ,
gfx::ColorSpace::MatrixID::BT2020_NCL,
gfx::ColorSpace::RangeID::LIMITED));
PutVideo(params, "cafebabe", {}, std::nullopt, true);
mkvmuxer::Colour* colour = GetVideoTrackColor();
EXPECT_EQ(colour->primaries(), mkvmuxer::Colour::kIturBt2020);
EXPECT_EQ(colour->transfer_characteristics(), mkvmuxer::Colour::kSmpteSt2084);
EXPECT_EQ(colour->matrix_coefficients(),
mkvmuxer::Colour::kBt2020NonConstantLuminance);
EXPECT_EQ(colour->range(), mkvmuxer::Colour::kBroadcastRange);
}
TEST_P(WebmMuxerTest, ColorSpaceFullRangeHDR10IsPropagatedToTrack) {
Muxer::VideoParameters params(
gfx::Size(1, 1), 0, media::VideoCodec::kVP9,
gfx::ColorSpace(gfx::ColorSpace::PrimaryID::BT2020,
gfx::ColorSpace::TransferID::PQ,
gfx::ColorSpace::MatrixID::BT2020_NCL,
gfx::ColorSpace::RangeID::FULL));
PutVideo(params, "beatles", {}, std::nullopt, true);
mkvmuxer::Colour* colour = GetVideoTrackColor();
EXPECT_EQ(colour->range(), mkvmuxer::Colour::kFullRange);
}
const TestParams kTestCases[] = {
{VideoCodec::kVP8, AudioCodec::kOpus, 1 /* num_video_tracks */,
0 /*num_audio_tracks*/},
{VideoCodec::kVP8, AudioCodec::kOpus, 0, 1},
{VideoCodec::kVP8, AudioCodec::kOpus, 1, 1},
{VideoCodec::kVP9, AudioCodec::kOpus, 1, 0},
{VideoCodec::kVP9, AudioCodec::kOpus, 0, 1},
{VideoCodec::kVP9, AudioCodec::kOpus, 1, 1},
{VideoCodec::kH264, AudioCodec::kOpus, 1, 0},
{VideoCodec::kH264, AudioCodec::kOpus, 0, 1},
{VideoCodec::kH264, AudioCodec::kOpus, 1, 1},
{VideoCodec::kVP8, AudioCodec::kPCM, 0, 1},
{VideoCodec::kVP8, AudioCodec::kPCM, 1, 1},
{VideoCodec::kAV1, AudioCodec::kOpus, 1, 0},
{VideoCodec::kAV1, AudioCodec::kOpus, 0, 1},
{VideoCodec::kAV1, AudioCodec::kOpus, 1, 1},
};
INSTANTIATE_TEST_SUITE_P(All, WebmMuxerTest, ValuesIn(kTestCases));
class WebmMuxerTestUnparametrized : public testing::Test {
public:
WebmMuxerTestUnparametrized()
: environment_(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
void CreateMuxer(
bool has_video,
bool has_audio,
std::optional<base::TimeDelta> max_data_output_interval = std::nullopt) {
webm_muxer_ = std::make_unique<WebmMuxer>(
AudioCodec::kOpus, has_video, has_audio,
std::make_unique<LiveWebmMuxerDelegate>(base::BindRepeating(
&WebmMuxerTestUnparametrized::SaveChunkAndInvokeWriteCallback,
base::Unretained(this))),
max_data_output_interval);
}
bool Parse() {
if (got_video_) {
// Add one more video buffer to force WebMStreamParser to not hold back
// the last added video buffer due to missing duration of it.
AddVideoAtOffset(kSentinelVideoBufferTimestampMs, /*is_key_frame=*/false);
}
// Force any final flushes.
webm_muxer_ = nullptr;
media::WebMStreamParser parser;
parser.Init(
base::BindOnce(&WebmMuxerTestUnparametrized::OnInit,
base::Unretained(this)),
base::BindRepeating(&WebmMuxerTestUnparametrized::OnNewConfig,
base::Unretained(this)),
base::BindRepeating(&WebmMuxerTestUnparametrized::OnNewBuffers,
base::Unretained(this)),
base::BindRepeating(
&WebmMuxerTestUnparametrized::OnEncryptedMediaInitData,
base::Unretained(this)),
base::BindRepeating(&WebmMuxerTestUnparametrized::OnNewMediaSegment,
base::Unretained(this)),
base::BindRepeating(&WebmMuxerTestUnparametrized::OnEndMediaSegment,
base::Unretained(this)),
&media_log_);
if (!parser.AppendToParseBuffer(base::make_span(muxed_data_))) {
return false;
}
// Run the segment parser loop one time with the full size of the appended
// data to ensure the parser has had a chance to parse all the appended
// bytes. As a result, kSuccessHasMoreData is considered a failure since
// there should be no uninspected data remaining after the parse.
return StreamParser::ParseStatus::kSuccess ==
parser.Parse(muxed_data_.size());
}
bool AddVideoAtOffset(int system_timestamp_offset_ms, bool is_key_frame) {
Muxer::VideoParameters params(gfx::Size(1, 1), 0, media::VideoCodec::kVP8,
gfx::ColorSpace());
bool result = webm_muxer_->PutFrame(
Muxer::EncodedFrame{std::move(params), std::nullopt, "video_at_offset",
"", is_key_frame},
base::Milliseconds(system_timestamp_offset_ms));
got_video_ = true;
return result;
}
bool AddAudioAtOffsetWithDuration(int system_timestamp_offset_ms,
int duration_ms) {
int frame_rate_hz = 48000;
int frames_per_buffer = frame_rate_hz * duration_ms / 1000;
media::AudioParameters audio_params(
media::AudioParameters::Format::AUDIO_PCM_LOW_LATENCY,
ChannelLayoutConfig::Mono(), frame_rate_hz, frames_per_buffer);
return webm_muxer_->PutFrame(
Muxer::EncodedFrame{std::move(audio_params), std::nullopt,
"audio_at_offset", "", true},
base::Milliseconds(system_timestamp_offset_ms));
}
MOCK_METHOD(void, OnWrite, ());
base::test::TaskEnvironment environment_;
std::unique_ptr<WebmMuxer> webm_muxer_;
std::map<int, std::vector<int>> buffer_timestamps_ms_;
protected:
// media::StreamParser callbacks.
void OnInit(const media::StreamParser::InitParameters&) {}
bool OnNewConfig(std::unique_ptr<media::MediaTracks> tracks) { return true; }
bool OnNewBuffers(const media::StreamParser::BufferQueueMap& map) {
for (const auto& [track_id, queue] : map) {
for (const auto& stream_parser_buffer : queue) {
buffer_timestamps_ms_[track_id].push_back(
stream_parser_buffer->timestamp().InMilliseconds());
}
}
return true;
}
void OnEncryptedMediaInitData(EmeInitDataType, const std::vector<uint8_t>&) {}
void OnNewMediaSegment() {}
void OnEndMediaSegment() {}
private:
static constexpr int kSentinelVideoBufferTimestampMs = 1000000;
void SaveChunkAndInvokeWriteCallback(std::string_view chunk) {
OnWrite();
base::ranges::copy(chunk, std::back_inserter(muxed_data_));
}
// Muxed data gets saved here. The content is guaranteed to be finalized first
// when webm_muxer_ has been destroyed.
std::vector<unsigned char> muxed_data_;
// Mock media log for WebM parser.
media::MockMediaLog media_log_;
// True after a call to AddVideoAtOffset.
bool got_video_ = false;
};
TEST_F(WebmMuxerTestUnparametrized, HoldsAudioFramesBeforeVideo) {
CreateMuxer(true, true);
EXPECT_CALL(*this, OnWrite).Times(0);
AddAudioAtOffsetWithDuration(0, 10);
AddAudioAtOffsetWithDuration(10, 10);
Mock::VerifyAndClearExpectations(this);
}
TEST_F(WebmMuxerTestUnparametrized, HoldsVideoFramesBeforeAudio) {
CreateMuxer(true, true);
EXPECT_CALL(*this, OnWrite).Times(0);
AddVideoAtOffset(0, /*is_key_frame=*/true);
AddVideoAtOffset(10, /*is_key_frame=*/false);
Mock::VerifyAndClearExpectations(this);
}
TEST_F(WebmMuxerTestUnparametrized, HoldsAudioFramesUntilVideo) {
CreateMuxer(true, true);
AddAudioAtOffsetWithDuration(0, 10);
AddAudioAtOffsetWithDuration(10, 10);
AddVideoAtOffset(20, /*is_key_frame=*/true);
ASSERT_TRUE(Parse());
EXPECT_THAT(buffer_timestamps_ms_, ElementsAre(Pair(1, ElementsAre(0, 10)),
Pair(2, ElementsAre(20))));
}
TEST_F(WebmMuxerTestUnparametrized, HoldsVideoFramesUntilAudio) {
CreateMuxer(true, true);
AddVideoAtOffset(0, /*is_key_frame=*/true);
AddVideoAtOffset(10, /*is_key_frame=*/false);
AddAudioAtOffsetWithDuration(20, 10);
ASSERT_TRUE(Parse());
EXPECT_THAT(buffer_timestamps_ms_, ElementsAre(Pair(1, ElementsAre(0, 10)),
Pair(2, ElementsAre(20))));
}
TEST_F(WebmMuxerTestUnparametrized, EmitsVideoRightAway) {
CreateMuxer(/*has_video=*/true, /*has_audio=*/false);
EXPECT_CALL(*this, OnWrite).Times(AtLeast(1));
AddVideoAtOffset(10, /*is_key_frame=*/true);
ASSERT_TRUE(Parse());
EXPECT_THAT(buffer_timestamps_ms_, ElementsAre(Pair(1, ElementsAre(10))));
}
TEST_F(WebmMuxerTestUnparametrized, EmitsAudioRightAway) {
CreateMuxer(/*has_video=*/false, /*has_audio=*/true);
EXPECT_CALL(*this, OnWrite).Times(AtLeast(1));
AddAudioAtOffsetWithDuration(20, 10);
ASSERT_TRUE(Parse());
EXPECT_THAT(buffer_timestamps_ms_, ElementsAre(Pair(1, ElementsAre(20))));
}
TEST_F(WebmMuxerTestUnparametrized, HoldsDataUntilDurationExpiry) {
CreateMuxer(true, true, base::Milliseconds(200));
AddVideoAtOffset(0, /*is_key_frame=*/true);
AddAudioAtOffsetWithDuration(0, 10);
EXPECT_CALL(*this, OnWrite).Times(0);
AddAudioAtOffsetWithDuration(10, 10);
AddAudioAtOffsetWithDuration(20, 10);
AddAudioAtOffsetWithDuration(30, 10);
AddAudioAtOffsetWithDuration(40, 10);
Mock::VerifyAndClearExpectations(this);
environment_.FastForwardBy(base::Milliseconds(200));
EXPECT_CALL(*this, OnWrite).Times(AtLeast(1));
AddAudioAtOffsetWithDuration(50, 10);
Mock::VerifyAndClearExpectations(this);
ASSERT_TRUE(Parse());
}
TEST_F(WebmMuxerTestUnparametrized, DurationExpiryLimitedByMaxFrequency) {
CreateMuxer(true, true,
base::Milliseconds(
50)); // This value is below the minimum limit of 100 ms.
AddVideoAtOffset(0, /*is_key_frame=*/true);
AddAudioAtOffsetWithDuration(0, 10);
EXPECT_CALL(*this, OnWrite).Times(0);
AddAudioAtOffsetWithDuration(10, 10);
AddAudioAtOffsetWithDuration(20, 10);
AddAudioAtOffsetWithDuration(30, 10);
AddAudioAtOffsetWithDuration(40, 10);
Mock::VerifyAndClearExpectations(this);
environment_.FastForwardBy(base::Milliseconds(100));
EXPECT_CALL(*this, OnWrite).Times(AtLeast(1));
AddAudioAtOffsetWithDuration(50, 10);
Mock::VerifyAndClearExpectations(this);
ASSERT_TRUE(Parse());
}
TEST_F(WebmMuxerTestUnparametrized, ForwardsAudioMuxingError) {
CreateMuxer(/*has_video=*/false, /*has_audio=*/true);
webm_muxer_->ForceOneLibWebmErrorForTesting();
ASSERT_FALSE(AddAudioAtOffsetWithDuration(0, 10));
}
TEST_F(WebmMuxerTestUnparametrized, ForwardsVideoMuxingError) {
CreateMuxer(/*has_video=*/true, /*has_audio=*/false);
webm_muxer_->ForceOneLibWebmErrorForTesting();
ASSERT_FALSE(AddVideoAtOffset(0, /*is_key_frame=*/true));
}
TEST_F(WebmMuxerTestUnparametrized, ForwardsAudioVideoMuxingError) {
CreateMuxer(/*has_video=*/true, /*has_audio=*/true);
webm_muxer_->ForceOneLibWebmErrorForTesting();
// This returns true since no attempt to write happens before the audio
// sample.
ASSERT_TRUE(AddVideoAtOffset(0, /*is_key_frame=*/true));
ASSERT_FALSE(AddAudioAtOffsetWithDuration(0, 10));
}
} // namespace media