Commit abc4ec99 authored by Markus Handell's avatar Markus Handell Committed by Commit Bot

WebmMuxer: fix bug shadowed by broken unit test.

While cleaning up to act on actually observed buffers in the WebM stream parser stream, I discovered that this test expectation isn't working:

https://source.chromium.org/chromium/chromium/src/+/master:media/muxers/webm_muxer_unittest.cc;l=645-658?q=webm_muxer_unittest.cc&ss=chromium%2Fchromium%2Fsrc.

After acting on actually passed buffers, it was found that the test fails. The reason is that in https://source.chromium.org/chromium/chromium/src/+/master:media/muxers/webm_muxer.h;l=155-157;bpv=1;bpt=1, last_xxx_ attributes are unset on first incoming buffers, leading the code to believe it's receiving non-monotonically increasing buffer timestamps.

Bug: chromium:1124735
Change-Id: Icf093fb94eef637ab17b8d38edd066313b5f7b48
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2390634
Commit-Queue: Markus Handell <handellm@google.com>
Reviewed-by: default avatarDale Curtis <dalecurtis@chromium.org>
Cr-Commit-Position: refs/heads/master@{#804636}
parent 1ccf4c97
...@@ -222,6 +222,7 @@ bool WebmMuxer::OnEncodedVideo(const VideoParameters& params, ...@@ -222,6 +222,7 @@ bool WebmMuxer::OnEncodedVideo(const VideoParameters& params,
if (first_frame_timestamp_video_.is_null()) { if (first_frame_timestamp_video_.is_null()) {
// Compensate for time in pause spent before the first frame. // Compensate for time in pause spent before the first frame.
first_frame_timestamp_video_ = timestamp - total_time_in_pause_; first_frame_timestamp_video_ = timestamp - total_time_in_pause_;
last_frame_timestamp_video_ = first_frame_timestamp_video_;
} }
} }
...@@ -250,6 +251,7 @@ bool WebmMuxer::OnEncodedAudio(const media::AudioParameters& params, ...@@ -250,6 +251,7 @@ bool WebmMuxer::OnEncodedAudio(const media::AudioParameters& params,
if (first_frame_timestamp_audio_.is_null()) { if (first_frame_timestamp_audio_.is_null()) {
// Compensate for time in pause spent before the first frame. // Compensate for time in pause spent before the first frame.
first_frame_timestamp_audio_ = timestamp - total_time_in_pause_; first_frame_timestamp_audio_ = timestamp - total_time_in_pause_;
last_frame_timestamp_audio_ = first_frame_timestamp_audio_;
} }
} }
...@@ -437,7 +439,6 @@ bool WebmMuxer::FlushNextFrame() { ...@@ -437,7 +439,6 @@ bool WebmMuxer::FlushNextFrame() {
force_one_libwebm_error_ = false; force_one_libwebm_error_ = false;
return false; return false;
} }
DCHECK(frame.data.data()); DCHECK(frame.data.data());
bool result = bool result =
frame.alpha_data.empty() frame.alpha_data.empty()
......
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
#include "media/base/channel_layout.h" #include "media/base/channel_layout.h"
#include "media/base/decoder_buffer.h" #include "media/base/decoder_buffer.h"
#include "media/base/media_tracks.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.h"
#include "media/base/stream_parser_buffer.h" #include "media/base/stream_parser_buffer.h"
#include "media/base/text_track_config.h" #include "media/base/text_track_config.h"
...@@ -445,13 +446,18 @@ class WebmMuxerTestUnparametrized : public testing::Test { ...@@ -445,13 +446,18 @@ class WebmMuxerTestUnparametrized : public testing::Test {
: environment_(base::test::TaskEnvironment::TimeSource::MOCK_TIME), : environment_(base::test::TaskEnvironment::TimeSource::MOCK_TIME),
webm_muxer_(std::make_unique<WebmMuxer>( webm_muxer_(std::make_unique<WebmMuxer>(
kCodecOpus, kCodecOpus,
/*has_audio=*/1, /*has_audio=*/true,
/*has_video=*/1, /*has_video=*/true,
base::BindRepeating( base::BindRepeating(
&WebmMuxerTestUnparametrized::SaveChunkAndInvokeWriteCallback, &WebmMuxerTestUnparametrized::SaveChunkAndInvokeWriteCallback,
base::Unretained(this)))) {} base::Unretained(this)))) {}
bool Parse() { 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. // Force any final flushes.
webm_muxer_ = nullptr; webm_muxer_ = nullptr;
media::WebMStreamParser parser; media::WebMStreamParser parser;
...@@ -470,12 +476,37 @@ class WebmMuxerTestUnparametrized : public testing::Test { ...@@ -470,12 +476,37 @@ class WebmMuxerTestUnparametrized : public testing::Test {
base::Unretained(this)), base::Unretained(this)),
base::BindRepeating(&WebmMuxerTestUnparametrized::OnEndMediaSegment, base::BindRepeating(&WebmMuxerTestUnparametrized::OnEndMediaSegment,
base::Unretained(this)), base::Unretained(this)),
nullptr); &media_log_);
return parser.Parse(muxed_data_.data(), muxed_data_.size()); return parser.Parse(muxed_data_.data(), muxed_data_.size());
} }
void AddVideoAtOffset(int system_timestamp_offset_ms, bool is_key_frame) {
WebmMuxer::VideoParameters params(gfx::Size(1, 1), 0, media::kCodecVP8,
gfx::ColorSpace());
webm_muxer_->OnEncodedVideo(
params, "video_at_offset", "",
base::TimeTicks() +
base::TimeDelta::FromMilliseconds(system_timestamp_offset_ms),
is_key_frame);
got_video_ = true;
}
void 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,
media::CHANNEL_LAYOUT_MONO, frame_rate_hz, frames_per_buffer);
webm_muxer_->OnEncodedAudio(
audio_params, "audio_at_offset",
base::TimeTicks() +
base::TimeDelta::FromMilliseconds(system_timestamp_offset_ms));
}
base::test::TaskEnvironment environment_; base::test::TaskEnvironment environment_;
std::unique_ptr<WebmMuxer> webm_muxer_; std::unique_ptr<WebmMuxer> webm_muxer_;
std::map<int, std::vector<int>> buffer_timestamps_ms_;
protected: protected:
// media::StreamParser callbacks. // media::StreamParser callbacks.
...@@ -484,12 +515,24 @@ class WebmMuxerTestUnparametrized : public testing::Test { ...@@ -484,12 +515,24 @@ class WebmMuxerTestUnparametrized : public testing::Test {
const media::StreamParser::TextTrackConfigMap&) { const media::StreamParser::TextTrackConfigMap&) {
return true; return true;
} }
MOCK_METHOD1(OnNewBuffers, bool(const media::StreamParser::BufferQueueMap&)); bool OnNewBuffers(const media::StreamParser::BufferQueueMap& map) {
for (const auto& kv : map) {
int track_id = kv.first;
const media::StreamParser::BufferQueue& queue = kv.second;
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 OnEncryptedMediaInitData(EmeInitDataType, const std::vector<uint8_t>&) {}
void OnNewMediaSegment() {} void OnNewMediaSegment() {}
void OnEndMediaSegment() {} void OnEndMediaSegment() {}
private: private:
static constexpr int kSentinelVideoBufferTimestampMs = 1000000;
void SaveChunkAndInvokeWriteCallback(base::StringPiece chunk) { void SaveChunkAndInvokeWriteCallback(base::StringPiece chunk) {
std::copy(chunk.begin(), chunk.end(), std::back_inserter(muxed_data_)); std::copy(chunk.begin(), chunk.end(), std::back_inserter(muxed_data_));
} }
...@@ -497,166 +540,65 @@ class WebmMuxerTestUnparametrized : public testing::Test { ...@@ -497,166 +540,65 @@ class WebmMuxerTestUnparametrized : public testing::Test {
// Muxed data gets saved here. The content is guaranteed to be finalized first // Muxed data gets saved here. The content is guaranteed to be finalized first
// when webm_muxer_ has been destroyed. // when webm_muxer_ has been destroyed.
std::vector<unsigned char> muxed_data_; 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, MuxerCompensatesForPausedTimeWithVideo) { TEST_F(WebmMuxerTestUnparametrized, MuxerCompensatesForPausedTimeWithVideo) {
WebmMuxer::VideoParameters params(gfx::Size(1, 1), 0, media::kCodecVP8, AddVideoAtOffset(123, /*is_key_frame=*/true);
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(); webm_muxer_->Pause();
environment_.FastForwardBy(base::TimeDelta::FromMilliseconds(200)); environment_.FastForwardBy(base::TimeDelta::FromMilliseconds(200));
webm_muxer_->Resume(); webm_muxer_->Resume();
webm_muxer_->OnEncodedVideo( AddVideoAtOffset(123 + 266, /*is_key_frame=*/false);
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()); EXPECT_TRUE(Parse());
EXPECT_THAT(buffer_timestamps_ms_,
ElementsAre(Pair(1, ElementsAre(0, /*266 - 200=*/66))));
} }
TEST_F(WebmMuxerTestUnparametrized, MuxerCompensatesForPausedTimeWithAudio) { TEST_F(WebmMuxerTestUnparametrized, MuxerCompensatesForPausedTimeWithAudio) {
const int sample_rate = 48000; AddAudioAtOffsetWithDuration(234, 10);
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(); webm_muxer_->Pause();
environment_.FastForwardBy(base::TimeDelta::FromMilliseconds(666)); environment_.FastForwardBy(base::TimeDelta::FromMilliseconds(666));
webm_muxer_->Resume(); webm_muxer_->Resume();
webm_muxer_->OnEncodedAudio( AddAudioAtOffsetWithDuration(234 + 686, 10);
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()); EXPECT_TRUE(Parse());
EXPECT_THAT(buffer_timestamps_ms_,
ElementsAre(Pair(1, ElementsAre(0, /*686 - 666=*/20))));
} }
TEST_F(WebmMuxerTestUnparametrized, TEST_F(WebmMuxerTestUnparametrized,
MuxerCompensatesForPausedTimeWithAudioAndVideo) { MuxerCompensatesForPausedTimeWithAudioAndVideo) {
WebmMuxer::VideoParameters params(gfx::Size(1, 1), 0, media::kCodecVP8, AddAudioAtOffsetWithDuration(234, 10);
gfx::ColorSpace()); AddVideoAtOffset(234 + 1, /*is_key_frame=*/true);
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(); webm_muxer_->Pause();
environment_.FastForwardBy(base::TimeDelta::FromMilliseconds(300)); environment_.FastForwardBy(base::TimeDelta::FromMilliseconds(300));
webm_muxer_->Resume(); webm_muxer_->Resume();
webm_muxer_->OnEncodedAudio( AddAudioAtOffsetWithDuration(234 + 321, 10);
audio_params, encoded_audio, AddVideoAtOffset(234 + 315, /*is_key_frame=*/false);
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()); EXPECT_TRUE(Parse());
EXPECT_THAT(
buffer_timestamps_ms_,
UnorderedElementsAre(Pair(1, ElementsAre(0, /*321 - 300=*/21)),
Pair(2, ElementsAre(0, /*315 - 300 - 1=*/14))));
} }
TEST_F(WebmMuxerTestUnparametrized, TEST_F(WebmMuxerTestUnparametrized,
MuxerCompensatesForPausedTimeBeforeAudioVideo) { 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(); webm_muxer_->Pause();
environment_.FastForwardBy(base::TimeDelta::FromMilliseconds(100)); environment_.FastForwardBy(base::TimeDelta::FromMilliseconds(100));
webm_muxer_->Resume(); webm_muxer_->Resume();
webm_muxer_->OnEncodedAudio( AddAudioAtOffsetWithDuration(50, 10);
audio_params, encoded, AddVideoAtOffset(65, /*is_key_frame=*/true);
base::TimeTicks() + base::TimeDelta::FromMilliseconds(50)); AddAudioAtOffsetWithDuration(60, 10);
webm_muxer_->OnEncodedVideo( AddVideoAtOffset(70, /*is_key_frame=*/false);
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()); EXPECT_TRUE(Parse());
EXPECT_THAT(buffer_timestamps_ms_,
UnorderedElementsAre(Pair(1, ElementsAre(0, 10)),
Pair(2, ElementsAre(0, 5))));
} }
} // namespace media } // namespace media
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