Commit 04171dee authored by Matt Wolenetz's avatar Matt Wolenetz Committed by Commit Bot

MSE: New changeType PipelineIntegrationTests

Adds tests for changeType buffering and playback, for every combination
of up to 7 audio-only types, and for every combination of up to 6
video-only types.

Sequence mode is used for these because a buffered range gap and
playback stall occurs across the boundary of any audio-only combination
containing the test MP2TS AAC media in segments mode.

Other CLs include changeType chromium impl (pre-requisite for this CL);
and blink impl and new web-platform tests.

BUG=605134

Change-Id: I34e6afb9ce104497636958ed2892c1bc6134ff73
Reviewed-on: https://chromium-review.googlesource.com/1110521
Commit-Queue: Matthew Wolenetz <wolenetz@chromium.org>
Reviewed-by: default avatarDan Sanders <sandersd@chromium.org>
Cr-Commit-Position: refs/heads/master@{#569775}
parent 04b9ab64
...@@ -10,15 +10,47 @@ ...@@ -10,15 +10,47 @@
#include "media/base/test_data_util.h" #include "media/base/test_data_util.h"
#include "media/base/timestamp_constants.h" #include "media/base/timestamp_constants.h"
namespace {
// Copies parsed type and codecs from |mimetype| into |type| and |codecs|.
// This code assumes that |mimetype| is one of the following forms:
// 1. mimetype without codecs (e.g. audio/mpeg)
// 2. mimetype with codecs (e.g. video/webm; codecs="vorbis,vp8")
void SplitMime(const std::string& mimetype,
std::string* type,
std::string* codecs) {
DCHECK(type);
DCHECK(codecs);
size_t semicolon = mimetype.find(";");
if (semicolon == std::string::npos) {
*type = mimetype;
*codecs = "";
return;
}
*type = mimetype.substr(0, semicolon);
size_t codecs_param_start = mimetype.find("codecs=\"", semicolon);
CHECK_NE(codecs_param_start, std::string::npos);
codecs_param_start += 8; // Skip over the codecs=".
size_t codecs_param_end = mimetype.find("\"", codecs_param_start);
CHECK_NE(codecs_param_end, std::string::npos);
*codecs = mimetype.substr(codecs_param_start,
codecs_param_end - codecs_param_start);
}
} // namespace
namespace media { namespace media {
constexpr char kSourceId[] = "SourceId"; constexpr char kSourceId[] = "SourceId";
MockMediaSource::MockMediaSource(const std::string& filename, MockMediaSource::MockMediaSource(const std::string& filename,
const std::string& mimetype, const std::string& mimetype,
size_t initial_append_size) size_t initial_append_size,
bool initial_sequence_mode)
: current_position_(0), : current_position_(0),
initial_append_size_(initial_append_size), initial_append_size_(initial_append_size),
initial_sequence_mode_(initial_sequence_mode),
mimetype_(mimetype), mimetype_(mimetype),
chunk_demuxer_(new ChunkDemuxer( chunk_demuxer_(new ChunkDemuxer(
base::Bind(&MockMediaSource::DemuxerOpened, base::Unretained(this)), base::Bind(&MockMediaSource::DemuxerOpened, base::Unretained(this)),
...@@ -38,10 +70,12 @@ MockMediaSource::MockMediaSource(const std::string& filename, ...@@ -38,10 +70,12 @@ MockMediaSource::MockMediaSource(const std::string& filename,
MockMediaSource::MockMediaSource(scoped_refptr<DecoderBuffer> data, MockMediaSource::MockMediaSource(scoped_refptr<DecoderBuffer> data,
const std::string& mimetype, const std::string& mimetype,
size_t initial_append_size) size_t initial_append_size,
bool initial_sequence_mode)
: file_data_(data), : file_data_(data),
current_position_(0), current_position_(0),
initial_append_size_(initial_append_size), initial_append_size_(initial_append_size),
initial_sequence_mode_(initial_sequence_mode),
mimetype_(mimetype), mimetype_(mimetype),
chunk_demuxer_(new ChunkDemuxer( chunk_demuxer_(new ChunkDemuxer(
base::Bind(&MockMediaSource::DemuxerOpened, base::Unretained(this)), base::Bind(&MockMediaSource::DemuxerOpened, base::Unretained(this)),
...@@ -81,6 +115,11 @@ void MockMediaSource::Seek(base::TimeDelta seek_time) { ...@@ -81,6 +115,11 @@ void MockMediaSource::Seek(base::TimeDelta seek_time) {
chunk_demuxer_->StartWaitingForSeek(seek_time); chunk_demuxer_->StartWaitingForSeek(seek_time);
} }
void MockMediaSource::SetSequenceMode(bool sequence_mode) {
CHECK(!chunk_demuxer_->IsParsingMediaSegment(kSourceId));
chunk_demuxer_->SetSequenceMode(kSourceId, sequence_mode);
}
void MockMediaSource::AppendData(size_t size) { void MockMediaSource::AppendData(size_t size) {
CHECK(chunk_demuxer_); CHECK(chunk_demuxer_);
CHECK_LT(current_position_, file_data_->data_size()); CHECK_LT(current_position_, file_data_->data_size());
...@@ -144,6 +183,10 @@ void MockMediaSource::EndOfStream() { ...@@ -144,6 +183,10 @@ void MockMediaSource::EndOfStream() {
chunk_demuxer_->MarkEndOfStream(PIPELINE_OK); chunk_demuxer_->MarkEndOfStream(PIPELINE_OK);
} }
void MockMediaSource::UnmarkEndOfStream() {
chunk_demuxer_->UnmarkEndOfStream();
}
void MockMediaSource::Shutdown() { void MockMediaSource::Shutdown() {
if (!chunk_demuxer_) if (!chunk_demuxer_)
return; return;
...@@ -174,33 +217,25 @@ void MockMediaSource::DemuxerOpenedTask() { ...@@ -174,33 +217,25 @@ void MockMediaSource::DemuxerOpenedTask() {
kSourceId, kSourceId,
base::Bind(&MockMediaSource::OnParseWarningMock, base::Unretained(this))); base::Bind(&MockMediaSource::OnParseWarningMock, base::Unretained(this)));
SetSequenceMode(initial_sequence_mode_);
AppendData(initial_append_size_); AppendData(initial_append_size_);
} }
ChunkDemuxer::Status MockMediaSource::AddId() { ChunkDemuxer::Status MockMediaSource::AddId() {
// This code assumes that |mimetype_| is one of the following forms. std::string type;
// 1. audio/mpeg std::string codecs;
// 2. video/webm;codec="vorbis,vp8". SplitMime(mimetype_, &type, &codecs);
size_t semicolon = mimetype_.find(";"); return chunk_demuxer_->AddId(kSourceId, type, codecs);
std::string type = mimetype_; }
std::string codecs_param = "";
if (semicolon != std::string::npos) {
type = mimetype_.substr(0, semicolon);
size_t codecs_param_start = mimetype_.find("codecs=\"", semicolon);
CHECK_NE(codecs_param_start, std::string::npos);
codecs_param_start += 8; // Skip over the codecs=".
size_t codecs_param_end = mimetype_.find("\"", codecs_param_start);
CHECK_NE(codecs_param_end, std::string::npos);
codecs_param = mimetype_.substr(codecs_param_start,
codecs_param_end - codecs_param_start);
}
return chunk_demuxer_->AddId(kSourceId, type, codecs_param); void MockMediaSource::ChangeType(const std::string& mimetype) {
chunk_demuxer_->ResetParserState(kSourceId, base::TimeDelta(),
kInfiniteDuration, &last_timestamp_offset_);
std::string type;
std::string codecs;
SplitMime(mimetype, &type, &codecs);
mimetype_ = mimetype;
chunk_demuxer_->ChangeType(kSourceId, type, codecs);
} }
void MockMediaSource::OnEncryptedMediaInitData( void MockMediaSource::OnEncryptedMediaInitData(
......
...@@ -24,10 +24,12 @@ class MockMediaSource { ...@@ -24,10 +24,12 @@ class MockMediaSource {
public: public:
MockMediaSource(const std::string& filename, MockMediaSource(const std::string& filename,
const std::string& mimetype, const std::string& mimetype,
size_t initial_append_size); size_t initial_append_size,
bool initial_sequence_mode = false);
MockMediaSource(scoped_refptr<DecoderBuffer> data, MockMediaSource(scoped_refptr<DecoderBuffer> data,
const std::string& mimetype, const std::string& mimetype,
size_t initial_append_size); size_t initial_append_size,
bool initial_sequence_mode = false);
~MockMediaSource(); ~MockMediaSource();
std::unique_ptr<Demuxer> GetDemuxer(); std::unique_ptr<Demuxer> GetDemuxer();
...@@ -49,6 +51,7 @@ class MockMediaSource { ...@@ -49,6 +51,7 @@ class MockMediaSource {
size_t new_position, size_t new_position,
size_t seek_append_size); size_t seek_append_size);
void Seek(base::TimeDelta seek_time); void Seek(base::TimeDelta seek_time);
void SetSequenceMode(bool sequence_mode);
void AppendData(size_t size); void AppendData(size_t size);
bool AppendAtTime(base::TimeDelta timestamp_offset, bool AppendAtTime(base::TimeDelta timestamp_offset,
const uint8_t* pData, const uint8_t* pData,
...@@ -62,10 +65,12 @@ class MockMediaSource { ...@@ -62,10 +65,12 @@ class MockMediaSource {
bool EvictCodedFrames(base::TimeDelta currentMediaTime, size_t newDataSize); bool EvictCodedFrames(base::TimeDelta currentMediaTime, size_t newDataSize);
void RemoveRange(base::TimeDelta start, base::TimeDelta end); void RemoveRange(base::TimeDelta start, base::TimeDelta end);
void EndOfStream(); void EndOfStream();
void UnmarkEndOfStream();
void Shutdown(); void Shutdown();
void DemuxerOpened(); void DemuxerOpened();
void DemuxerOpenedTask(); void DemuxerOpenedTask();
ChunkDemuxer::Status AddId(); ChunkDemuxer::Status AddId();
void ChangeType(const std::string& type);
void OnEncryptedMediaInitData(EmeInitDataType init_data_type, void OnEncryptedMediaInitData(EmeInitDataType init_data_type,
const std::vector<uint8_t>& init_data); const std::vector<uint8_t>& init_data);
...@@ -87,6 +92,7 @@ class MockMediaSource { ...@@ -87,6 +92,7 @@ class MockMediaSource {
scoped_refptr<DecoderBuffer> file_data_; scoped_refptr<DecoderBuffer> file_data_;
size_t current_position_; size_t current_position_;
size_t initial_append_size_; size_t initial_append_size_;
bool initial_sequence_mode_;
std::string mimetype_; std::string mimetype_;
ChunkDemuxer* chunk_demuxer_; ChunkDemuxer* chunk_demuxer_;
std::unique_ptr<Demuxer> owned_chunk_demuxer_; std::unique_ptr<Demuxer> owned_chunk_demuxer_;
......
...@@ -626,6 +626,180 @@ INSTANTIATE_TEST_CASE_P(ProprietaryCodecs, ...@@ -626,6 +626,180 @@ INSTANTIATE_TEST_CASE_P(ProprietaryCodecs,
#endif // BUILDFLAG(USE_PROPRIETARY_CODECS) #endif // BUILDFLAG(USE_PROPRIETARY_CODECS)
struct MSEChangeTypeTestData {
const MSEPlaybackTestData file_one;
const MSEPlaybackTestData file_two;
};
class MSEChangeTypeTest
: public ::testing::WithParamInterface<
std::tuple<MSEPlaybackTestData, MSEPlaybackTestData>>,
public PipelineIntegrationTest {
public:
// Populate meaningful test suffixes instead of /0, /1, etc.
struct PrintToStringParamName {
template <class ParamType>
std::string operator()(
const testing::TestParamInfo<ParamType>& info) const {
std::stringstream ss;
ss << std::get<0>(info.param) << "_AND_" << std::get<1>(info.param);
std::string s = ss.str();
// Strip out invalid param name characters.
std::stringstream ss2;
for (size_t i = 0; i < s.size(); ++i) {
if (isalnum(s[i]) || s[i] == '_')
ss2 << s[i];
}
return ss2.str();
}
};
protected:
void PlayBackToBack() {
// TODO(wolenetz): Consider a modified, composable, hash that lets us
// combine known hashes for two files to generate an expected hash for when
// both are played. For now, only the duration (and successful append and
// play-to-end) are verified.
MSEPlaybackTestData file_one = std::get<0>(GetParam());
MSEPlaybackTestData file_two = std::get<1>(GetParam());
#if BUILDFLAG(ENABLE_AV1_DECODER)
// AV1 media is included in the some of these tests when the decoder is
// enabled.
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeature(kAv1Decoder);
#endif
// Start in 'sequence' appendMode, because some test media begin near enough
// to time 0, resulting in gaps across the changeType boundary in buffered
// media timeline.
// TODO(wolenetz): Switch back to 'segments' mode once we have some
// incubation of a way to flexibly allow playback through unbuffered
// regions. Known test media requiring sequence mode: MP3-in-MP2T
MockMediaSource source(file_one.filename, file_one.mimetype,
file_one.append_bytes, true);
ASSERT_EQ(PIPELINE_OK,
StartPipelineWithMediaSource(&source, kNormal, nullptr));
source.EndOfStream();
// Transitions between VP8A and other test media can trigger this again.
EXPECT_CALL(*this, OnVideoOpacityChange(_)).Times(AnyNumber());
Ranges<base::TimeDelta> ranges = pipeline_->GetBufferedTimeRanges();
EXPECT_EQ(1u, ranges.size());
EXPECT_EQ(0, ranges.start(0).InMilliseconds());
base::TimeDelta file_one_end_time = ranges.end(0);
EXPECT_EQ(file_one.duration_ms, file_one_end_time.InMilliseconds());
// Change type and append |file_two| with start time abutting end of
// the previous buffered range.
source.UnmarkEndOfStream();
source.ChangeType(file_two.mimetype);
scoped_refptr<DecoderBuffer> file_two_contents =
ReadTestDataFile(file_two.filename);
source.AppendAtTime(file_one_end_time, file_two_contents->data(),
file_two.append_bytes == kAppendWholeFile
? file_two_contents->data_size()
: file_two.append_bytes);
source.EndOfStream();
ranges = pipeline_->GetBufferedTimeRanges();
EXPECT_EQ(1u, ranges.size());
EXPECT_EQ(0, ranges.start(0).InMilliseconds());
base::TimeDelta file_two_actual_duration =
ranges.end(0) - file_one_end_time;
EXPECT_EQ(file_two_actual_duration.InMilliseconds(), file_two.duration_ms);
Play();
ASSERT_TRUE(WaitUntilOnEnded());
EXPECT_TRUE(demuxer_->GetTimelineOffset().is_null());
source.Shutdown();
Stop();
}
};
TEST_P(MSEChangeTypeTest, LegacyByDts_PlayBackToBack) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndDisableFeature(media::kMseBufferByPts);
PlayBackToBack();
}
TEST_P(MSEChangeTypeTest, NewByPts_PlayBackToBack) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeature(media::kMseBufferByPts);
PlayBackToBack();
}
const MSEPlaybackTestData kMediaSourceAudioFiles[] = {
// MP3
{"sfx.mp3", kMP3, kAppendWholeFile, 313},
// Opus in WebM
{"sfx-opus-441.webm", kOpusAudioOnlyWebM, kAppendWholeFile, 301},
// Vorbis in WebM
{"bear-320x240-audio-only.webm", kAudioOnlyWebM, kAppendWholeFile, 2768},
// FLAC in MP4
{"sfx-flac_frag.mp4", kMP4AudioFlac, kAppendWholeFile, 288},
#if BUILDFLAG(USE_PROPRIETARY_CODECS)
// AAC in ADTS
{"bear-audio-main-aac.aac", kADTS, kAppendWholeFile, 2773},
// AAC in MP4
{"bear-640x360-a_frag.mp4", kMP4Audio, kAppendWholeFile, 2803},
#if BUILDFLAG(ENABLE_MSE_MPEG2TS_STREAM_PARSER)
// MP3 in MP2T
{"bear-audio-mp4a.6B.ts", "video/mp2t; codecs=\"mp4a.6B\"",
kAppendWholeFile, 1097},
#endif // BUILDFLAG(ENABLE_MSE_MPEG2TS_STREAM_PARSER)
#endif // BUILDFLAG(USE_PROPRIETARY_CODECS)
};
const MSEPlaybackTestData kMediaSourceVideoFiles[] = {
// VP9 in WebM
{"bear-vp9.webm", kWebMVP9, kAppendWholeFile, kVP9WebMFileDurationMs},
// VP9 in MP4
{"bear-320x240-v_frag-vp9.mp4", kMP4VideoVP9, kAppendWholeFile, 2736},
// VP8 in WebM
{"bear-vp8a.webm", kVideoOnlyWebM, kAppendWholeFile,
kVP8AWebMFileDurationMs},
#if BUILDFLAG(ENABLE_AV1_DECODER)
// AV1 in MP4
// TODO(johannkoenig): re-enable when an av1 mp4 muxer is available.
// {"bear-av1.mp4", kMP4AV1, kAppendWholeFile, kVP9WebMFileDurationMs},
// AV1 in WebM
{"bear-av1.webm", kWebMAV1, kAppendWholeFile, kVP9WebMFileDurationMs},
#endif // BUILDFLAG(ENABLE_AV1_DECODER)
#if BUILDFLAG(USE_PROPRIETARY_CODECS)
// H264 AVC3 in MP4
{"bear-1280x720-v_frag-avc3.mp4", kMP4VideoAVC3, kAppendWholeFile,
k1280IsoAVC3FileDurationMs},
#endif // BUILDFLAG(USE_PROPRIETARY_CODECS)
};
INSTANTIATE_TEST_CASE_P(
AudioOnly,
MSEChangeTypeTest,
testing::Combine(testing::ValuesIn(kMediaSourceAudioFiles),
testing::ValuesIn(kMediaSourceAudioFiles)),
MSEChangeTypeTest::PrintToStringParamName());
INSTANTIATE_TEST_CASE_P(
VideoOnly,
MSEChangeTypeTest,
testing::Combine(testing::ValuesIn(kMediaSourceVideoFiles),
testing::ValuesIn(kMediaSourceVideoFiles)),
MSEChangeTypeTest::PrintToStringParamName());
// Test parameter determines if media::kMseBufferByPts feature should be forced // Test parameter determines if media::kMseBufferByPts feature should be forced
// on or off for the test. // on or off for the test.
// Note, the BasicMSEPlaybackTest test fixture defines its own parameter type, // Note, the BasicMSEPlaybackTest test fixture defines its own parameter type,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment