blob: 2eb50510617074a94679005ef90b96d297f5ae4c [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// 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 <stddef.h>
#include <stdint.h>
#include <memory>
#include "base/bind.h"
#include "base/location.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/memory/ref_counted.h"
#include "base/test/task_environment.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/stream_parser.h"
#include "media/base/stream_parser_buffer.h"
#include "media/base/text_track_config.h"
#include "media/base/video_codecs.h"
#include "media/base/video_frame.h"
#include "media/formats/webm/webm_stream_parser.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,
base::BindRepeating(&WebmMuxerTest::WriteCallback,
base::Unretained(this)))),
last_encoded_length_(0),
accumulated_position_(0) {
EXPECT_EQ(webm_muxer_->Position(), 0);
const mkvmuxer::int64 kRandomNewPosition = 333;
EXPECT_EQ(webm_muxer_->Position(kRandomNewPosition), -1);
EXPECT_FALSE(webm_muxer_->Seekable());
}
MOCK_METHOD1(WriteCallback, void(base::StringPiece));
void SaveEncodedDataLen(const base::StringPiece& encoded_data) {
last_encoded_length_ = encoded_data.size();
accumulated_position_ += encoded_data.size();
}
mkvmuxer::int64 GetWebmMuxerPosition() const {
return webm_muxer_->Position();
}
mkvmuxer::Segment::Mode GetWebmSegmentMode() const {
return webm_muxer_->segment_.mode();
}
mkvmuxer::int32 WebmMuxerWrite(const void* buf, mkvmuxer::uint32 len) {
return webm_muxer_->Write(buf, len);
}
WebmMuxer::VideoParameters GetVideoParameters(
scoped_refptr<VideoFrame> frame) {
WebmMuxer::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();
}
std::unique_ptr<WebmMuxer> webm_muxer_;
size_t last_encoded_length_;
int64_t accumulated_position_;
private:
DISALLOW_COPY_AND_ASSIGN(WebmMuxerTest);
};
// Checks that the WriteCallback is called with appropriate params when
// WebmMuxer::Write() method is called.
TEST_P(WebmMuxerTest, Write) {
const base::StringPiece 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()));
}
TEST_P(WebmMuxerTest,
HandlesMuxerErrorInPassingEncodedFramesWithAudioThenVideo) {
auto video_params =
GetVideoParameters(VideoFrame::CreateBlackFrame(gfx::Size(160, 80)));
const std::string encoded_data("abcdefghijklmnopqrstuvwxyz");
media::AudioParameters audio_params(
media::AudioParameters::Format::AUDIO_PCM_LOW_LATENCY,
media::CHANNEL_LAYOUT_MONO, /*sample_rate=*/48000,
/*frames_per_buffer=*/480);
// Force an error in libwebm and expect OnEncodedVideo to fail.
webm_muxer_->ForceOneLibWebmErrorForTesting();
base::TimeTicks now = base::TimeTicks::Now();
bool audio_success =
!GetParam().num_audio_tracks ||
webm_muxer_->OnEncodedAudio(audio_params, encoded_data, now);
bool video_success =
!GetParam().num_video_tracks ||
webm_muxer_->OnEncodedVideo(video_params, encoded_data, std::string(),
now + base::TimeDelta::FromMilliseconds(1),
/*is_key_frame=*/true);
EXPECT_FALSE(audio_success && video_success);
}
TEST_P(WebmMuxerTest,
HandlesMuxerErrorInPassingEncodedFramesWithVideoThenAudio) {
auto video_params =
GetVideoParameters(VideoFrame::CreateBlackFrame(gfx::Size(160, 80)));
const std::string encoded_data("abcdefghijklmnopqrstuvwxyz");
media::AudioParameters audio_params(
media::AudioParameters::Format::AUDIO_PCM_LOW_LATENCY,
media::CHANNEL_LAYOUT_MONO, /*sample_rate=*/48000,
/*frames_per_buffer=*/480);
// Force an error in libwebm and expect OnEncodedVideo to fail.
webm_muxer_->ForceOneLibWebmErrorForTesting();
base::TimeTicks now = base::TimeTicks::Now();
bool video_success =
!GetParam().num_video_tracks ||
webm_muxer_->OnEncodedVideo(video_params, encoded_data, std::string(),
now + base::TimeDelta::FromMilliseconds(1),
/*is_key_frame=*/true);
bool audio_success =
!GetParam().num_audio_tracks ||
webm_muxer_->OnEncodedAudio(audio_params, encoded_data, now);
EXPECT_FALSE(audio_success && video_success);
}
// 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)));
EXPECT_TRUE(webm_muxer_->OnEncodedVideo(
GetVideoParameters(video_frame), encoded_data, std::string(),
base::TimeTicks::Now(), false /* keyframe */));
// 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)));
EXPECT_TRUE(webm_muxer_->OnEncodedVideo(
GetVideoParameters(video_frame), encoded_data, std::string(),
base::TimeTicks::Now(), false /* keyframe */));
// 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)));
EXPECT_TRUE(webm_muxer_->OnEncodedVideo(
GetVideoParameters(video_frame), encoded_data, alpha_encoded_data,
base::TimeTicks::Now(), true /* keyframe */));
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)));
EXPECT_TRUE(webm_muxer_->OnEncodedVideo(
GetVideoParameters(video_frame), encoded_data, alpha_encoded_data,
base::TimeTicks::Now(), false /* keyframe */));
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,
media::CHANNEL_LAYOUT_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)));
EXPECT_TRUE(webm_muxer_->OnEncodedAudio(audio_params, encoded_data,
base::TimeTicks::Now()));
// 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)));
EXPECT_TRUE(webm_muxer_->OnEncodedAudio(audio_params, encoded_data,
base::TimeTicks::Now()));
// 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) {
WebmMuxer::VideoParameters params(gfx::Size(1, 1), 0, media::kCodecVP9,
gfx::ColorSpace::CreateREC709());
webm_muxer_->OnEncodedVideo(params, "abab", {}, base::TimeTicks::Now(),
true /* keyframe */);
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) {
WebmMuxer::VideoParameters params(
gfx::Size(1, 1), 0, media::kCodecVP9,
gfx::ColorSpace(gfx::ColorSpace::PrimaryID::BT709,
gfx::ColorSpace::TransferID::IEC61966_2_1,
gfx::ColorSpace::MatrixID::BT709,
gfx::ColorSpace::RangeID::LIMITED));
webm_muxer_->OnEncodedVideo(params, "banana", {}, base::TimeTicks::Now(),
true /* keyframe */);
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) {
WebmMuxer::VideoParameters params(
gfx::Size(1, 1), 0, media::kCodecVP9,
gfx::ColorSpace(gfx::ColorSpace::PrimaryID::BT2020,
gfx::ColorSpace::TransferID::SMPTEST2084,
gfx::ColorSpace::MatrixID::BT2020_NCL,
gfx::ColorSpace::RangeID::LIMITED));
webm_muxer_->OnEncodedVideo(params, "cafebabe", {}, base::TimeTicks::Now(),
true /* keyframe */);
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) {
WebmMuxer::VideoParameters params(
gfx::Size(1, 1), 0, media::kCodecVP9,
gfx::ColorSpace(gfx::ColorSpace::PrimaryID::BT2020,
gfx::ColorSpace::TransferID::SMPTEST2084,
gfx::ColorSpace::MatrixID::BT2020_NCL,
gfx::ColorSpace::RangeID::FULL));
webm_muxer_->OnEncodedVideo(params, "beatles", {}, base::TimeTicks::Now(),
true /* keyframe */);
mkvmuxer::Colour* colour = GetVideoTrackColor();
EXPECT_EQ(colour->range(), mkvmuxer::Colour::kFullRange);
}
// This test verifies that when video data comes before audio data, we save the
// encoded video frames and add it to the video track when audio data arrives.
TEST_P(WebmMuxerTest, VideoIsStoredWhileWaitingForAudio) {
// This test is only relevant if we have both kinds of tracks.
if (GetParam().num_video_tracks == 0 || GetParam().num_audio_tracks == 0)
return;
// First send a video keyframe.
const gfx::Size frame_size(160, 80);
const scoped_refptr<VideoFrame> video_frame =
VideoFrame::CreateBlackFrame(frame_size);
const std::string encoded_video("thisisanencodedvideopacket");
// Timestamp: 0 (video origin)
webm_muxer_->OnEncodedVideo(GetVideoParameters(video_frame), encoded_video,
std::string(), base::TimeTicks(),
true /* keyframe */);
// Timestamp: video origin + X
webm_muxer_->OnEncodedVideo(
GetVideoParameters(video_frame), encoded_video, std::string(),
base::TimeTicks() + base::TimeDelta::FromMilliseconds(1),
false /* keyframe */);
// Timestamp: video origin + X + Y
webm_muxer_->OnEncodedVideo(
GetVideoParameters(video_frame), encoded_video, std::string(),
base::TimeTicks() + base::TimeDelta::FromMilliseconds(2),
false /* keyframe */);
const int sample_rate = 48000;
const int frames_per_buffer = 480;
media::AudioParameters audio_params(
media::AudioParameters::Format::AUDIO_PCM_LOW_LATENCY,
media::CHANNEL_LAYOUT_MONO, sample_rate, frames_per_buffer);
const std::string encoded_audio("thisisanencodedaudiopacket");
// Timestamped frames should come as:
// [video origin, audio origin, video origin + X, video origin + X + Y]
Sequence s;
EXPECT_CALL(*this, WriteCallback(Eq(encoded_video))).Times(1).InSequence(s);
EXPECT_CALL(*this, WriteCallback(Eq(encoded_audio))).Times(1).InSequence(s);
EXPECT_CALL(*this, WriteCallback(Eq(encoded_video))).Times(1).InSequence(s);
EXPECT_CALL(*this, WriteCallback(Eq(encoded_video))).Times(1).InSequence(s);
// We'll also get lots of other header-related stuff.
EXPECT_CALL(*this, WriteCallback(
AllOf(Not(Eq(encoded_video)), Not(Eq(encoded_audio)))))
.Times(AnyNumber());
// Timestamp: 0 (audio origin)
webm_muxer_->OnEncodedAudio(
audio_params, encoded_audio,
base::TimeTicks() + base::TimeDelta::FromMilliseconds(3));
webm_muxer_.reset();
}
const TestParams kTestCases[] = {
{kCodecVP8, kCodecOpus, 1 /* num_video_tracks */, 0 /*num_audio_tracks*/},
{kCodecVP8, kCodecOpus, 0, 1},
{kCodecVP8, kCodecOpus, 1, 1},
{kCodecVP9, kCodecOpus, 1, 0},
{kCodecVP9, kCodecOpus, 0, 1},
{kCodecVP9, kCodecOpus, 1, 1},
{kCodecH264, kCodecOpus, 1, 0},
{kCodecH264, kCodecOpus, 0, 1},
{kCodecH264, kCodecOpus, 1, 1},
{kCodecVP8, kCodecPCM, 0, 1},
{kCodecVP8, kCodecPCM, 1, 1},
};
INSTANTIATE_TEST_SUITE_P(All, WebmMuxerTest, ValuesIn(kTestCases));
class WebmMuxerTestUnparametrized : public testing::Test {
public:
WebmMuxerTestUnparametrized()
: environment_(base::test::TaskEnvironment::TimeSource::MOCK_TIME),
webm_muxer_(std::make_unique<WebmMuxer>(
kCodecOpus,
/*has_audio=*/1,
/*has_video=*/1,
base::BindRepeating(
&WebmMuxerTestUnparametrized::SaveChunkAndInvokeWriteCallback,
base::Unretained(this)))) {}
bool Parse() {
// 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)),
/*ignore_text_tracks=*/true,
base::BindRepeating(
&WebmMuxerTestUnparametrized::OnEncryptedMediaInitData,
base::Unretained(this)),
base::BindRepeating(&WebmMuxerTestUnparametrized::OnNewMediaSegment,
base::Unretained(this)),
base::BindRepeating(&WebmMuxerTestUnparametrized::OnEndMediaSegment,
base::Unretained(this)),
nullptr);
return parser.Parse(muxed_data_.data(), muxed_data_.size());
}
base::test::TaskEnvironment environment_;
std::unique_ptr<WebmMuxer> webm_muxer_;
protected:
// media::StreamParser callbacks.
void OnInit(const media::StreamParser::InitParameters&) {}
bool OnNewConfig(std::unique_ptr<media::MediaTracks> tracks,
const media::StreamParser::TextTrackConfigMap&) {
return true;
}
MOCK_METHOD1(OnNewBuffers, bool(const media::StreamParser::BufferQueueMap&));
void OnEncryptedMediaInitData(EmeInitDataType, const std::vector<uint8_t>&) {}
void OnNewMediaSegment() {}
void OnEndMediaSegment() {}
private:
void SaveChunkAndInvokeWriteCallback(base::StringPiece chunk) {
std::copy(chunk.begin(), chunk.end(), 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_;
};
TEST_F(WebmMuxerTestUnparametrized, MuxerCompensatesForPausedTimeWithVideo) {
WebmMuxer::VideoParameters params(gfx::Size(1, 1), 0, media::kCodecVP8,
gfx::ColorSpace());
const std::string encoded("oranges are delicious");
auto first_frame_timestamp =
base::TimeTicks() + base::TimeDelta::FromMilliseconds(123);
webm_muxer_->OnEncodedVideo(params, encoded, "", first_frame_timestamp, true);
webm_muxer_->Pause();
environment_.FastForwardBy(base::TimeDelta::FromMilliseconds(200));
webm_muxer_->Resume();
webm_muxer_->OnEncodedVideo(
params, encoded, "",
first_frame_timestamp + base::TimeDelta::FromMilliseconds(266), false);
// Add one more buffer to force WebMStreamParser to not hold back the above
// important one due to missing duration.
webm_muxer_->OnEncodedVideo(
params, encoded, "",
first_frame_timestamp + base::TimeDelta::FromMilliseconds(1000), false);
EXPECT_CALL(
*this,
OnNewBuffers(ElementsAre(Pair(
1, ElementsAre(Pointee(Property(&DecoderBuffer::timestamp,
base::TimeDelta())),
Pointee(Property(&DecoderBuffer::timestamp,
base::TimeDelta::FromMilliseconds(
66)))))))) // 266 - 200
.WillRepeatedly(Return(true));
EXPECT_TRUE(Parse());
}
TEST_F(WebmMuxerTestUnparametrized, MuxerCompensatesForPausedTimeWithAudio) {
const int sample_rate = 48000;
const int frames_per_buffer = 480;
media::AudioParameters audio_params(
media::AudioParameters::Format::AUDIO_PCM_LOW_LATENCY,
media::CHANNEL_LAYOUT_MONO, sample_rate, frames_per_buffer);
const std::string encoded("apples too");
auto first_frame_timestamp =
base::TimeTicks() + base::TimeDelta::FromMilliseconds(234);
webm_muxer_->OnEncodedAudio(audio_params, encoded, first_frame_timestamp);
webm_muxer_->Pause();
environment_.FastForwardBy(base::TimeDelta::FromMilliseconds(666));
webm_muxer_->Resume();
webm_muxer_->OnEncodedAudio(
audio_params, encoded,
first_frame_timestamp + base::TimeDelta::FromMilliseconds(686));
EXPECT_CALL(
*this,
OnNewBuffers(ElementsAre(Pair(
1, ElementsAre(Pointee(Property(&DecoderBuffer::timestamp,
base::TimeDelta())),
Pointee(Property(&DecoderBuffer::timestamp,
base::TimeDelta::FromMilliseconds(
20)))))))) // 686 - 666
.WillRepeatedly(Return(true));
EXPECT_TRUE(Parse());
}
TEST_F(WebmMuxerTestUnparametrized,
MuxerCompensatesForPausedTimeWithAudioAndVideo) {
WebmMuxer::VideoParameters params(gfx::Size(1, 1), 0, media::kCodecVP8,
gfx::ColorSpace());
const std::string encoded_video("even bananas");
const int sample_rate = 48000;
const int frames_per_buffer = 480;
media::AudioParameters audio_params(
media::AudioParameters::Format::AUDIO_PCM_LOW_LATENCY,
media::CHANNEL_LAYOUT_MONO, sample_rate, frames_per_buffer);
const std::string encoded_audio("and plums");
auto first_frame_timestamp =
base::TimeTicks() + base::TimeDelta::FromMilliseconds(234);
webm_muxer_->OnEncodedAudio(audio_params, encoded_audio,
first_frame_timestamp);
webm_muxer_->OnEncodedVideo(
params, encoded_video, "",
first_frame_timestamp + base::TimeDelta::FromMilliseconds(1), false);
webm_muxer_->Pause();
environment_.FastForwardBy(base::TimeDelta::FromMilliseconds(300));
webm_muxer_->Resume();
webm_muxer_->OnEncodedAudio(
audio_params, encoded_audio,
first_frame_timestamp + base::TimeDelta::FromMilliseconds(321));
webm_muxer_->OnEncodedVideo(
params, encoded_video, "",
first_frame_timestamp + base::TimeDelta::FromMilliseconds(315), false);
// Add one more buffer to force WebMStreamParser to not hold back the above
// important one due to missing duration.
webm_muxer_->OnEncodedVideo(
params, encoded_video, "",
first_frame_timestamp + base::TimeDelta::FromMilliseconds(1000), false);
EXPECT_CALL(
*this,
OnNewBuffers(UnorderedElementsAre(
Pair(1, ElementsAre(
Pointee(Property(&DecoderBuffer::timestamp,
base::TimeDelta())),
Pointee(Property(
&DecoderBuffer::timestamp,
base::TimeDelta::FromMilliseconds(21)))) // 321 - 300
),
Pair(
2, ElementsAre(Pointee(Property(&DecoderBuffer::timestamp,
base::TimeDelta())),
Pointee(Property(&DecoderBuffer::timestamp,
base::TimeDelta::FromMilliseconds(
14)))) // 315 - 300 - 1
))))
.WillRepeatedly(Return(true));
EXPECT_TRUE(Parse());
}
TEST_F(WebmMuxerTestUnparametrized,
MuxerCompensatesForPausedTimeBeforeAudioVideo) {
const int sample_rate = 48000;
const int frames_per_buffer = 480;
media::AudioParameters audio_params(
media::AudioParameters::Format::AUDIO_PCM_LOW_LATENCY,
media::CHANNEL_LAYOUT_MONO, sample_rate, frames_per_buffer);
const std::string encoded("audio");
WebmMuxer::VideoParameters params(gfx::Size(1, 1), 0, media::kCodecVP8,
gfx::ColorSpace());
const std::string encoded_video("video");
webm_muxer_->Pause();
environment_.FastForwardBy(base::TimeDelta::FromMilliseconds(100));
webm_muxer_->Resume();
webm_muxer_->OnEncodedAudio(
audio_params, encoded,
base::TimeTicks() + base::TimeDelta::FromMilliseconds(50));
webm_muxer_->OnEncodedVideo(
params, encoded_video, "",
base::TimeTicks() + base::TimeDelta::FromMilliseconds(65), true);
webm_muxer_->OnEncodedAudio(
audio_params, encoded,
base::TimeTicks() + base::TimeDelta::FromMilliseconds(60));
// Add one more buffer to force WebMStreamParser to not hold back the above
// important one due to missing duration.
webm_muxer_->OnEncodedVideo(
params, encoded_video, "",
base::TimeTicks() + base::TimeDelta::FromMilliseconds(70), false);
EXPECT_CALL(
*this,
OnNewBuffers(UnorderedElementsAre(
Pair(1, ElementsAre(Pointee(Property(&DecoderBuffer::timestamp,
base::TimeDelta())),
Pointee(Property(
&DecoderBuffer::timestamp,
base::TimeDelta::FromMilliseconds(15))))),
Pair(2, ElementsAre(Pointee(Property(&DecoderBuffer::timestamp,
base::TimeDelta())),
Pointee(Property(
&DecoderBuffer::timestamp,
base::TimeDelta::FromMilliseconds(5))))))))
.WillRepeatedly(Return(true));
EXPECT_TRUE(Parse());
}
} // namespace media