blob: 8f34d559e0216bc98b17d07078ce3897d49ca093 [file] [log] [blame]
// Copyright 2012 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/test/pipeline_integration_test_base.h"
#include <memory>
#include <utility>
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/memory/ref_counted.h"
#include "base/run_loop.h"
#include "base/task/single_thread_task_runner.h"
#include "base/threading/sequence_bound.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "media/base/media_log.h"
#include "media/base/media_switches.h"
#include "media/base/media_tracks.h"
#include "media/base/test_data_util.h"
#include "media/filters/file_data_source.h"
#include "media/filters/memory_data_source.h"
#include "media/media_buildflags.h"
#include "media/renderers/audio_renderer_impl.h"
#include "media/renderers/renderer_impl.h"
#include "media/test/fake_encrypted_media.h"
#include "media/test/test_media_source.h"
#if BUILDFLAG(ENABLE_DAV1D_DECODER)
#include "media/filters/dav1d_video_decoder.h"
#endif
#if BUILDFLAG(ENABLE_FFMPEG)
#include "media/filters/ffmpeg_audio_decoder.h"
#include "media/filters/ffmpeg_demuxer.h"
#endif
#if BUILDFLAG(ENABLE_FFMPEG_VIDEO_DECODERS)
#include "media/filters/ffmpeg_video_decoder.h"
#endif
#if BUILDFLAG(ENABLE_LIBVPX)
#include "media/filters/vpx_video_decoder.h"
#endif
#if BUILDFLAG(ENABLE_HLS_DEMUXER)
#include "media/filters/hls_data_source_provider_impl.h"
#include "media/filters/hls_manifest_demuxer_engine.h"
#include "media/filters/manifest_demuxer.h"
#endif // BUILDFLAG(ENABLE_HLS_DEMUXER)
using ::testing::_;
using ::testing::AnyNumber;
using ::testing::AtLeast;
using ::testing::AtMost;
using ::testing::Invoke;
using ::testing::InvokeWithoutArgs;
using ::testing::InSequence;
using ::testing::Return;
using ::testing::SaveArg;
namespace media {
#if BUILDFLAG(ENABLE_HLS_DEMUXER)
namespace {
class TestDataSourceFactory
: public HlsDataSourceProviderImpl::DataSourceFactory {
public:
~TestDataSourceFactory() override = default;
void CreateDataSource(GURL uri, DataSourceCb callback) override {
auto file_data_source = std::make_unique<FileDataSource>();
base::FilePath file_path(uri.GetContent());
CHECK(file_data_source->Initialize(file_path))
<< "Is " << file_path.value() << " missing?";
std::move(callback).Run(std::move(file_data_source));
}
};
} // namespace
#endif // BUILDFLAG(ENABLE_HLS_DEMUXER)
static std::vector<std::unique_ptr<VideoDecoder>> CreateVideoDecodersForTest(
MediaLog* media_log,
CreateVideoDecodersCB prepend_video_decoders_cb) {
std::vector<std::unique_ptr<VideoDecoder>> video_decoders;
if (prepend_video_decoders_cb) {
video_decoders = prepend_video_decoders_cb.Run();
DCHECK(!video_decoders.empty());
}
#if BUILDFLAG(ENABLE_LIBVPX)
video_decoders.push_back(std::make_unique<OffloadingVpxVideoDecoder>());
#endif
#if BUILDFLAG(ENABLE_DAV1D_DECODER)
video_decoders.push_back(
std::make_unique<OffloadingDav1dVideoDecoder>(media_log->Clone()));
#endif
#if BUILDFLAG(ENABLE_FFMPEG_VIDEO_DECODERS)
video_decoders.push_back(std::make_unique<FFmpegVideoDecoder>(media_log));
#endif
return video_decoders;
}
static std::vector<std::unique_ptr<AudioDecoder>> CreateAudioDecodersForTest(
MediaLog* media_log,
const scoped_refptr<base::SingleThreadTaskRunner>& media_task_runner,
CreateAudioDecodersCB prepend_audio_decoders_cb) {
std::vector<std::unique_ptr<AudioDecoder>> audio_decoders;
if (prepend_audio_decoders_cb) {
audio_decoders = prepend_audio_decoders_cb.Run();
DCHECK(!audio_decoders.empty());
}
#if BUILDFLAG(ENABLE_FFMPEG)
audio_decoders.push_back(
std::make_unique<FFmpegAudioDecoder>(media_task_runner, media_log));
#endif
return audio_decoders;
}
const char kNullVideoHash[] = "d41d8cd98f00b204e9800998ecf8427e";
const char kNullAudioHash[] = "0.00,0.00,0.00,0.00,0.00,0.00,";
PipelineIntegrationTestBase::PipelineIntegrationTestBase()
:
// Use a UI type message loop on macOS, because it doesn't seem to schedule
// callbacks with enough precision to drive our fake audio output. See
// https://crbug.com/1014646 for more details.
#if BUILDFLAG(IS_MAC)
task_environment_(base::test::TaskEnvironment::MainThreadType::UI),
#endif
hashing_enabled_(false),
clockless_playback_(false),
webaudio_attached_(false),
mono_output_(false),
fuzzing_(false),
ended_(false),
pipeline_status_(PIPELINE_OK),
last_video_frame_format_(PIXEL_FORMAT_UNKNOWN),
current_duration_(kInfiniteDuration) {
pipeline_ = std::make_unique<PipelineImpl>(
task_environment_.GetMainThreadTaskRunner(),
task_environment_.GetMainThreadTaskRunner(),
base::BindRepeating(&PipelineIntegrationTestBase::CreateRenderer,
base::Unretained(this)),
&media_log_);
ResetVideoHash();
EXPECT_CALL(*this, OnVideoAverageKeyframeDistanceUpdate()).Times(AnyNumber());
}
PipelineIntegrationTestBase::~PipelineIntegrationTestBase() {
if (pipeline_->IsRunning())
Stop();
demuxer_.reset();
pipeline_.reset();
base::RunLoop().RunUntilIdle();
}
void PipelineIntegrationTestBase::ParseTestTypeFlags(uint8_t flags) {
hashing_enabled_ = flags & kHashed;
clockless_playback_ = !(flags & kNoClockless);
webaudio_attached_ = flags & kWebAudio;
mono_output_ = flags & kMonoOutput;
fuzzing_ = flags & kFuzzing;
}
// TODO(xhwang): Method definitions in this file needs to be reordered.
void PipelineIntegrationTestBase::OnSeeked(base::TimeDelta seek_time,
PipelineStatus status) {
// When fuzzing, sometimes a seek to 0 results in an actual media time > 0.
if (fuzzing_)
EXPECT_LE(seek_time, pipeline_->GetMediaTime());
else
EXPECT_EQ(seek_time, pipeline_->GetMediaTime());
pipeline_status_ = status;
// If the seek failed, then stop immediately.
if (!pipeline_status_.is_ok() && on_error_closure_) {
std::move(on_error_closure_).Run();
}
}
void PipelineIntegrationTestBase::OnStatusCallback(
const base::RepeatingClosure& quit_run_loop_closure,
PipelineStatus status) {
pipeline_status_ = status;
if (pipeline_status_ != PIPELINE_OK && pipeline_->IsRunning())
pipeline_->Stop();
quit_run_loop_closure.Run();
}
void PipelineIntegrationTestBase::DemuxerEncryptedMediaInitDataCB(
EmeInitDataType type,
const std::vector<uint8_t>& init_data) {
DCHECK(!init_data.empty());
CHECK(encrypted_media_init_data_cb_);
encrypted_media_init_data_cb_.Run(type, init_data);
}
void PipelineIntegrationTestBase::DemuxerMediaTracksUpdatedCB(
std::unique_ptr<MediaTracks> tracks) {
CHECK(tracks);
CHECK_GT(tracks->tracks().size(), 0u);
// Verify that track ids are unique.
std::set<MediaTrack::Id> track_ids;
for (const auto& track : tracks->tracks()) {
EXPECT_EQ(track_ids.end(), track_ids.find(track->id()));
track_ids.insert(track->id());
}
}
void PipelineIntegrationTestBase::OnEnded() {
DCHECK(!ended_);
ended_ = true;
pipeline_status_ = PIPELINE_OK;
if (on_ended_closure_)
std::move(on_ended_closure_).Run();
}
bool PipelineIntegrationTestBase::WaitUntilOnEnded() {
EXPECT_EQ(pipeline_status_, PIPELINE_OK);
PipelineStatus status = WaitUntilEndedOrError();
EXPECT_TRUE(ended_);
EXPECT_EQ(pipeline_status_, PIPELINE_OK);
return ended_ && (status == PIPELINE_OK);
}
PipelineStatus PipelineIntegrationTestBase::WaitUntilEndedOrError() {
if (!ended_ && pipeline_status_ == PIPELINE_OK) {
base::RunLoop run_loop;
RunUntilQuitOrEndedOrError(&run_loop);
} else {
task_environment_.RunUntilIdle();
}
return pipeline_status_;
}
void PipelineIntegrationTestBase::OnError(PipelineStatus status) {
DCHECK(status != PIPELINE_OK);
pipeline_status_ = status;
pipeline_->Stop();
if (on_error_closure_)
std::move(on_error_closure_).Run();
}
void PipelineIntegrationTestBase::OnFallback(PipelineStatus status) {
DCHECK(status != PIPELINE_OK);
}
void PipelineIntegrationTestBase::SetCreateRendererCB(
CreateRendererCB create_renderer_cb) {
create_renderer_cb_ = std::move(create_renderer_cb);
}
#if BUILDFLAG(ENABLE_HLS_DEMUXER)
PipelineStatus PipelineIntegrationTestBase::StartPipelineWithHlsManifest(
const std::string& filename) {
hashing_enabled_ = true;
auto full_path = GetTestDataFilePath(filename);
std::string file_url = "file://" + full_path.MaybeAsASCII();
GURL manifest_root{file_url};
auto multibuffer_factory = std::make_unique<TestDataSourceFactory>();
// HlsManifestDemuxerEngine requires a SequenceBound data source provider,
// regardless of which sequence it's actually bound to.
auto hls_dsp = base::SequenceBound<HlsDataSourceProviderImpl>(
task_environment_.GetMainThreadTaskRunner(),
std::move(multibuffer_factory));
auto engine = std::make_unique<HlsManifestDemuxerEngine>(
std::move(hls_dsp), task_environment_.GetMainThreadTaskRunner(),
manifest_root, &media_log_);
demuxer_ = std::make_unique<ManifestDemuxer>(
task_environment_.GetMainThreadTaskRunner(), base::DoNothing(),
std::move(engine), &media_log_);
EXPECT_CALL(*this, OnMetadata(_))
.Times(AtMost(1))
.WillRepeatedly(SaveArg<0>(&metadata_));
base::RunLoop run_loop;
pipeline_->Start(
Pipeline::StartType::kNormal, demuxer_.get(), this,
base::BindOnce(&PipelineIntegrationTestBase::OnStatusCallback,
base::Unretained(this), run_loop.QuitClosure()));
RunUntilQuitOrEndedOrError(&run_loop);
return pipeline_status_;
}
#endif // BUILDFLAG(ENABLE_HLS_DEMUXER)
PipelineStatus PipelineIntegrationTestBase::StartInternal(
std::unique_ptr<DataSource> data_source,
CdmContext* cdm_context,
uint8_t test_type,
CreateVideoDecodersCB prepend_video_decoders_cb,
CreateAudioDecodersCB prepend_audio_decoders_cb) {
prepend_video_decoders_cb_ = std::move(prepend_video_decoders_cb);
prepend_audio_decoders_cb_ = std::move(prepend_audio_decoders_cb);
ParseTestTypeFlags(test_type);
EXPECT_CALL(*this, OnMetadata(_))
.Times(AtMost(1))
.WillRepeatedly(SaveArg<0>(&metadata_));
EXPECT_CALL(*this, OnBufferingStateChange(_, _)).Times(AnyNumber());
// If the test is expected to have reliable duration information, permit at
// most two calls to OnDurationChange. CheckDuration will make sure that no
// more than one of them is a finite duration. This allows the pipeline to
// call back at the end of the media with the known duration.
//
// In the event of unreliable duration information, just set the expectation
// that it's called at least once. Such streams may repeatedly update their
// duration as new packets are demuxed.
if (test_type & kUnreliableDuration) {
EXPECT_CALL(*this, OnDurationChange()).Times(AnyNumber());
} else {
EXPECT_CALL(*this, OnDurationChange())
.Times(AtMost(2))
.WillRepeatedly(
Invoke(this, &PipelineIntegrationTestBase::CheckDuration));
}
EXPECT_CALL(*this, OnVideoNaturalSizeChange(_)).Times(AnyNumber());
EXPECT_CALL(*this, OnVideoOpacityChange(_)).WillRepeatedly(Return());
EXPECT_CALL(*this, OnVideoFrameRateChange(_)).Times(AnyNumber());
EXPECT_CALL(*this, OnAudioPipelineInfoChange(_)).Times(AnyNumber());
EXPECT_CALL(*this, OnVideoPipelineInfoChange(_)).Times(AnyNumber());
CreateDemuxer(std::move(data_source));
if (cdm_context) {
EXPECT_CALL(*this, DecryptorAttached(true));
pipeline_->SetCdm(
cdm_context,
base::BindOnce(&PipelineIntegrationTestBase::DecryptorAttached,
base::Unretained(this)));
}
// Should never be called as the required decryption keys for the encrypted
// media files are provided in advance.
EXPECT_CALL(*this, OnWaiting(WaitingReason::kNoDecryptionKey)).Times(0);
// DemuxerStreams may signal config changes.
// In practice, this doesn't happen for FFmpegDemuxer, but it's allowed for
// SRC= demuxers in general.
EXPECT_CALL(*this, OnAudioConfigChange(_)).Times(AnyNumber());
EXPECT_CALL(*this, OnVideoConfigChange(_)).Times(AnyNumber());
base::RunLoop run_loop;
pipeline_->Start(
Pipeline::StartType::kNormal, demuxer_.get(), this,
base::BindOnce(&PipelineIntegrationTestBase::OnStatusCallback,
base::Unretained(this), run_loop.QuitClosure()));
RunUntilQuitOrEndedOrError(&run_loop);
return pipeline_status_;
}
PipelineStatus PipelineIntegrationTestBase::StartWithFile(
const std::string& filename,
CdmContext* cdm_context,
uint8_t test_type,
CreateVideoDecodersCB prepend_video_decoders_cb,
CreateAudioDecodersCB prepend_audio_decoders_cb) {
std::unique_ptr<FileDataSource> file_data_source(new FileDataSource());
base::FilePath file_path(GetTestDataFilePath(filename));
CHECK(file_data_source->Initialize(file_path)) << "Is " << file_path.value()
<< " missing?";
return StartInternal(std::move(file_data_source), cdm_context, test_type,
prepend_video_decoders_cb, prepend_audio_decoders_cb);
}
PipelineStatus PipelineIntegrationTestBase::Start(const std::string& filename) {
return StartWithFile(filename, nullptr, kNormal);
}
PipelineStatus PipelineIntegrationTestBase::Start(const std::string& filename,
CdmContext* cdm_context) {
return StartWithFile(filename, cdm_context, kNormal);
}
PipelineStatus PipelineIntegrationTestBase::Start(
const std::string& filename,
uint8_t test_type,
CreateVideoDecodersCB prepend_video_decoders_cb,
CreateAudioDecodersCB prepend_audio_decoders_cb) {
return StartWithFile(filename, nullptr, test_type, prepend_video_decoders_cb,
prepend_audio_decoders_cb);
}
PipelineStatus PipelineIntegrationTestBase::Start(const uint8_t* data,
size_t size,
uint8_t test_type) {
return StartInternal(std::make_unique<MemoryDataSource>(data, size), nullptr,
test_type);
}
void PipelineIntegrationTestBase::Play() {
pipeline_->SetPlaybackRate(1);
}
void PipelineIntegrationTestBase::Pause() {
pipeline_->SetPlaybackRate(0);
}
void PipelineIntegrationTestBase::OnBufferingStateChangeForSeek(
BufferingState state,
BufferingStateChangeReason reason) {
// Record the first buffering state we get.
if (!buffering_state_) {
buffering_state_ = state;
// The first call must be HAVE_ENOUGH.
EXPECT_EQ(state, BUFFERING_HAVE_ENOUGH);
}
// Once we have HAVE_ENOUGH, we've had enough.
if (on_ended_closure_) {
std::move(on_ended_closure_).Run();
}
}
bool PipelineIntegrationTestBase::Seek(base::TimeDelta seek_time) {
// Enforce that BUFFERING_HAVE_ENOUGH is the first call below.
::testing::InSequence dummy;
ended_ = false;
base::RunLoop run_loop;
pipeline_status_ = PIPELINE_OK;
buffering_state_.reset();
// Should always transition to HAVE_ENOUGH once the seek completes
// successfully. On error, it shouldn't be called at all.
// After initial HAVE_ENOUGH, any buffering state change is allowed as
// playback may cause any number of underflow/preroll events.
EXPECT_CALL(*this, OnBufferingStateChange(_, _))
.Times(AnyNumber())
.WillRepeatedly(Invoke(
this, &PipelineIntegrationTestBase::OnBufferingStateChangeForSeek));
bool did_call_on_seeked = false;
pipeline_->Seek(seek_time,
base::BindOnce(
[](PipelineIntegrationTestBase* thiz, bool* flag,
base::TimeDelta seek_time, PipelineStatus status) {
*flag = true;
thiz->OnSeeked(seek_time, status);
},
base::Unretained(this), &did_call_on_seeked, seek_time));
RunUntilQuitOrEndedOrError(&run_loop);
// We must get at least one `OnSeeked()` status call.
EXPECT_TRUE(did_call_on_seeked);
// If the seek succeeded, then we must get to HAVE_ENOUGH
if (pipeline_status_ == PIPELINE_OK) {
EXPECT_TRUE(buffering_state_);
EXPECT_EQ(*buffering_state_, BUFFERING_HAVE_ENOUGH);
} // else we don't care about buffering state.
return (pipeline_status_ == PIPELINE_OK);
}
bool PipelineIntegrationTestBase::Suspend() {
base::RunLoop run_loop;
pipeline_->Suspend(
base::BindOnce(&PipelineIntegrationTestBase::OnStatusCallback,
base::Unretained(this), run_loop.QuitClosure()));
RunUntilQuitOrError(&run_loop);
return (pipeline_status_ == PIPELINE_OK);
}
bool PipelineIntegrationTestBase::Resume(base::TimeDelta seek_time) {
ended_ = false;
base::RunLoop run_loop;
EXPECT_CALL(*this, OnBufferingStateChange(BUFFERING_HAVE_ENOUGH, _))
.WillOnce(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit));
pipeline_->Resume(seek_time,
base::BindOnce(&PipelineIntegrationTestBase::OnSeeked,
base::Unretained(this), seek_time));
RunUntilQuitOrError(&run_loop);
return (pipeline_status_ == PIPELINE_OK);
}
void PipelineIntegrationTestBase::Stop() {
DCHECK(pipeline_->IsRunning());
pipeline_->Stop();
base::RunLoop().RunUntilIdle();
}
void PipelineIntegrationTestBase::FailTest(PipelineStatus status) {
DCHECK(status != PIPELINE_OK);
OnError(status);
}
void PipelineIntegrationTestBase::QuitAfterCurrentTimeTask(
base::TimeDelta quit_time,
base::OnceClosure quit_closure) {
if (!pipeline_ || pipeline_->GetMediaTime() >= quit_time ||
pipeline_status_ != PIPELINE_OK) {
std::move(quit_closure).Run();
return;
}
task_environment_.GetMainThreadTaskRunner()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&PipelineIntegrationTestBase::QuitAfterCurrentTimeTask,
base::Unretained(this), quit_time,
std::move(quit_closure)),
base::Milliseconds(10));
}
bool PipelineIntegrationTestBase::WaitUntilCurrentTimeIsAfter(
const base::TimeDelta& wait_time) {
DCHECK(pipeline_->IsRunning());
DCHECK_GT(pipeline_->GetPlaybackRate(), 0);
DCHECK(wait_time <= pipeline_->GetMediaDuration());
base::RunLoop run_loop;
task_environment_.GetMainThreadTaskRunner()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&PipelineIntegrationTestBase::QuitAfterCurrentTimeTask,
base::Unretained(this), wait_time, run_loop.QuitClosure()),
base::Milliseconds(10));
RunUntilQuitOrEndedOrError(&run_loop);
return (pipeline_status_ == PIPELINE_OK);
}
void PipelineIntegrationTestBase::CreateDemuxer(
std::unique_ptr<DataSource> data_source) {
data_source_ = std::move(data_source);
#if BUILDFLAG(ENABLE_FFMPEG)
demuxer_ = std::unique_ptr<Demuxer>(new FFmpegDemuxer(
task_environment_.GetMainThreadTaskRunner(), data_source_.get(),
base::BindRepeating(
&PipelineIntegrationTestBase::DemuxerEncryptedMediaInitDataCB,
base::Unretained(this)),
base::BindRepeating(
&PipelineIntegrationTestBase::DemuxerMediaTracksUpdatedCB,
base::Unretained(this)),
&media_log_, true));
#endif
}
std::unique_ptr<Renderer> PipelineIntegrationTestBase::CreateRenderer(
std::optional<RendererType> renderer_type) {
if (create_renderer_cb_)
return create_renderer_cb_.Run(renderer_type);
return CreateRendererImpl(renderer_type);
}
std::unique_ptr<Renderer> PipelineIntegrationTestBase::CreateRendererImpl(
std::optional<RendererType> renderer_type) {
if (renderer_type && *renderer_type != RendererType::kRendererImpl) {
DVLOG(1) << __func__ << ": renderer_type not supported";
return nullptr;
}
// Simulate a 60Hz rendering sink.
video_sink_ = std::make_unique<NullVideoSink>(
clockless_playback_, base::Seconds(1.0 / 60),
base::BindRepeating(&PipelineIntegrationTestBase::OnVideoFramePaint,
base::Unretained(this)),
task_environment_.GetMainThreadTaskRunner());
// Disable frame dropping if hashing is enabled.
auto video_renderer = std::make_unique<VideoRendererImpl>(
task_environment_.GetMainThreadTaskRunner(), video_sink_.get(),
base::BindRepeating(&CreateVideoDecodersForTest, &media_log_,
prepend_video_decoders_cb_),
false, &media_log_, nullptr, 0);
if (!clockless_playback_) {
DCHECK(!mono_output_) << " NullAudioSink doesn't specify output parameters";
audio_sink_ =
new NullAudioSink(task_environment_.GetMainThreadTaskRunner());
} else {
ChannelLayoutConfig output_layout_config =
mono_output_ ? ChannelLayoutConfig::Mono()
: ChannelLayoutConfig::Stereo();
clockless_audio_sink_ = new ClocklessAudioSink(
OutputDeviceInfo("", OUTPUT_DEVICE_STATUS_OK,
AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY,
output_layout_config, 44100, 512)));
// Say "not optimized for hardware parameters" to disallow renderer
// resampling. Hashed tests need this avoid platform dependent floating
// point precision differences.
if (webaudio_attached_ || hashing_enabled_) {
clockless_audio_sink_->SetIsOptimizedForHardwareParametersForTesting(
false);
}
}
auto audio_renderer = std::make_unique<AudioRendererImpl>(
task_environment_.GetMainThreadTaskRunner(),
(clockless_playback_)
? static_cast<AudioRendererSink*>(clockless_audio_sink_.get())
: audio_sink_.get(),
base::BindRepeating(&CreateAudioDecodersForTest, &media_log_,
task_environment_.GetMainThreadTaskRunner(),
prepend_audio_decoders_cb_),
&media_log_, 0, nullptr);
if (hashing_enabled_) {
if (clockless_playback_)
clockless_audio_sink_->StartAudioHashForTesting();
else
audio_sink_->StartAudioHashForTesting();
}
static_cast<AudioRendererImpl*>(audio_renderer.get())
->SetPlayDelayCBForTesting(std::move(audio_play_delay_cb_));
std::unique_ptr<RendererImpl> renderer_impl = std::make_unique<RendererImpl>(
task_environment_.GetMainThreadTaskRunner(), std::move(audio_renderer),
std::move(video_renderer));
// Prevent non-deterministic buffering state callbacks from firing (e.g., slow
// machine, valgrind).
renderer_impl->DisableUnderflowForTesting();
if (clockless_playback_)
renderer_impl->EnableClocklessVideoPlaybackForTesting();
return renderer_impl;
}
void PipelineIntegrationTestBase::OnVideoFramePaint(
scoped_refptr<VideoFrame> frame) {
last_video_frame_format_ = frame->format();
last_video_frame_color_space_ = frame->ColorSpace();
if (!hashing_enabled_ || last_frame_ == frame)
return;
DVLOG(3) << __func__ << " pts=" << frame->timestamp().InSecondsF();
VideoFrame::HashFrameForTesting(&md5_context_, *frame);
last_frame_ = std::move(frame);
}
void PipelineIntegrationTestBase::CheckDuration() {
// Allow the pipeline to specify indefinite duration, then reduce it once
// it becomes known.
ASSERT_EQ(kInfiniteDuration, current_duration_);
base::TimeDelta new_duration = pipeline_->GetMediaDuration();
current_duration_ = new_duration;
}
base::TimeDelta PipelineIntegrationTestBase::GetStartTime() {
return demuxer_->GetStartTime();
}
void PipelineIntegrationTestBase::ResetVideoHash() {
DVLOG(1) << __func__;
base::MD5Init(&md5_context_);
}
std::string PipelineIntegrationTestBase::GetVideoHash() {
DCHECK(hashing_enabled_);
base::MD5Digest digest;
base::MD5Final(&digest, &md5_context_);
return base::MD5DigestToBase16(digest);
}
const AudioHash& PipelineIntegrationTestBase::GetAudioHash() const {
DCHECK(hashing_enabled_);
if (clockless_playback_)
return clockless_audio_sink_->GetAudioHashForTesting();
return audio_sink_->GetAudioHashForTesting();
}
base::TimeDelta PipelineIntegrationTestBase::GetAudioTime() {
DCHECK(clockless_playback_);
return clockless_audio_sink_->render_time();
}
PipelineStatus PipelineIntegrationTestBase::StartPipelineWithMediaSource(
TestMediaSource* source) {
return StartPipelineWithMediaSource(source, kNormal, nullptr);
}
PipelineStatus PipelineIntegrationTestBase::StartPipelineWithEncryptedMedia(
TestMediaSource* source,
FakeEncryptedMedia* encrypted_media) {
return StartPipelineWithMediaSource(source, kNormal, encrypted_media);
}
PipelineStatus PipelineIntegrationTestBase::StartPipelineWithMediaSource(
TestMediaSource* source,
uint8_t test_type,
CreateAudioDecodersCB prepend_audio_decoders_cb) {
prepend_audio_decoders_cb_ = prepend_audio_decoders_cb;
return StartPipelineWithMediaSource(source, test_type, nullptr);
}
PipelineStatus PipelineIntegrationTestBase::StartPipelineWithMediaSource(
TestMediaSource* source,
uint8_t test_type,
FakeEncryptedMedia* encrypted_media) {
ParseTestTypeFlags(test_type);
if (fuzzing_) {
EXPECT_CALL(*source, InitSegmentReceivedMock(_)).Times(AnyNumber());
EXPECT_CALL(*source, OnParseWarningMock(_)).Times(AnyNumber());
} else if (!(test_type & kExpectDemuxerFailure)) {
EXPECT_CALL(*source, InitSegmentReceivedMock(_)).Times(AtLeast(1));
}
EXPECT_CALL(*this, OnMetadata(_))
.Times(AtMost(1))
.WillRepeatedly(SaveArg<0>(&metadata_));
EXPECT_CALL(*this, OnBufferingStateChange(_, _)).Times(AnyNumber());
EXPECT_CALL(*this, OnDurationChange()).Times(AnyNumber());
EXPECT_CALL(*this, OnVideoNaturalSizeChange(_)).Times(AnyNumber());
EXPECT_CALL(*this, OnVideoOpacityChange(_)).Times(AtMost(1));
EXPECT_CALL(*this, OnVideoFrameRateChange(_)).Times(AnyNumber());
EXPECT_CALL(*this, OnAudioPipelineInfoChange(_)).Times(AnyNumber());
EXPECT_CALL(*this, OnVideoPipelineInfoChange(_)).Times(AnyNumber());
base::RunLoop run_loop;
source->set_demuxer_failure_cb(
base::BindRepeating(&PipelineIntegrationTestBase::OnStatusCallback,
base::Unretained(this), run_loop.QuitClosure()));
demuxer_ = source->GetDemuxer();
// DemuxerStreams may signal config changes.
// Config change tests should set more specific expectations about the number
// of calls.
EXPECT_CALL(*this, OnAudioConfigChange(_)).Times(AnyNumber());
EXPECT_CALL(*this, OnVideoConfigChange(_)).Times(AnyNumber());
if (encrypted_media) {
EXPECT_CALL(*this, DecryptorAttached(true));
// Encrypted content used but keys provided in advance, so this is
// never called.
EXPECT_CALL(*this, OnWaiting(WaitingReason::kNoDecryptionKey)).Times(0);
pipeline_->SetCdm(
encrypted_media->GetCdmContext(),
base::BindOnce(&PipelineIntegrationTestBase::DecryptorAttached,
base::Unretained(this)));
} else if (fuzzing_) {
// Encrypted content is not expected unless the fuzzer generates a stream
// that appears to be encrypted. The fuzzer handles any encrypted media
// init data callbacks, but could timeout if there is no such data but the
// media is determined by the parser to be encrypted (as can occur in MSE
// mp2t fuzzing of some kinds of encrypted media). To prevent such fuzzer
// timeout, post a task to the main thread to fail the test if the pipeline
// transitions to waiting for a CDM.
EXPECT_CALL(*this, OnWaiting(WaitingReason::kNoCdm))
.Times(AnyNumber())
.WillRepeatedly([this]() {
task_environment_.GetMainThreadTaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(&PipelineIntegrationTestBase::FailTest,
base::Unretained(this),
media::PIPELINE_ERROR_INITIALIZATION_FAILED));
});
} else {
// We are neither fuzzing, nor expecting encrypted media, so we must not
// receive notification of waiting for a decryption key.
EXPECT_CALL(*this, OnWaiting(WaitingReason::kNoDecryptionKey)).Times(0);
}
pipeline_->Start(
Pipeline::StartType::kNormal, demuxer_.get(), this,
base::BindOnce(&PipelineIntegrationTestBase::OnStatusCallback,
base::Unretained(this), run_loop.QuitClosure()));
if (encrypted_media) {
source->set_encrypted_media_init_data_cb(
base::BindRepeating(&FakeEncryptedMedia::OnEncryptedMediaInitData,
base::Unretained(encrypted_media)));
}
RunUntilQuitOrEndedOrError(&run_loop);
for (media::DemuxerStream* stream : demuxer_->GetAllStreams()) {
EXPECT_TRUE(stream->SupportsConfigChanges());
}
return pipeline_status_;
}
void PipelineIntegrationTestBase::RunUntilQuitOrError(base::RunLoop* run_loop) {
// We always install an error handler to avoid test hangs.
on_error_closure_ = run_loop->QuitClosure();
run_loop->Run();
on_ended_closure_ = base::OnceClosure();
on_error_closure_ = base::OnceClosure();
task_environment_.RunUntilIdle();
}
void PipelineIntegrationTestBase::RunUntilQuitOrEndedOrError(
base::RunLoop* run_loop) {
DCHECK(on_ended_closure_.is_null());
DCHECK(on_error_closure_.is_null());
on_ended_closure_ = run_loop->QuitClosure();
RunUntilQuitOrError(run_loop);
}
} // namespace media