Commit 491a2c2c authored by Matt Wolenetz's avatar Matt Wolenetz Committed by Commit Bot

MSE: Drop empty AV buffers emitted by Stream Parser

Since FFmpeg decode of 0-byte audio and video buffers is now disallowed,
and FFmpegDemuxer skips enqueuing such packets into its DemuxerStream,
this change does similar for MSE:

Any 0-byte |data| audio or video buffers are dropped during initial
processing in FrameProcessor::ProcessFrames(). No 0-byte |data| text
buffers are dropped, because:
* We don't use FFmpeg to decode in-band MSE text, and
* In-band MSE text buffers' |side_data| may contain meaningful
  information. (This assumption isn't asserted by this change).

Note, this change takes the simpler route of dropping 0-byte AV buffers
before they're buffered by the coded frame processing algorithm. A more
complex alternative to skip them when reading from SourceBufferStream
was rejected in hopes this simpler change is sufficient.

Includes ability in ChunkDemuxerTest to vary |block_size_| instead of
always using |kBlockSize|. While the product change is closer to
FrameProcessor, FrameProcessorTests always encode the coded frame's
timestamp as the coded frame contents to enable precise verification of
processing results, so the test of this change is done via
ChunkDemuxerTest instead.

BUG=823375,663438
TEST=ChunkDemuxerTest.ZeroLengthFramesDropped

Cq-Include-Trybots: luci.chromium.try:android_optional_gpu_tests_rel;luci.chromium.try:linux_optional_gpu_tests_rel;luci.chromium.try:mac_optional_gpu_tests_rel;master.tryserver.chromium.win:win_optional_gpu_tests_rel
Change-Id: Ie6a802ba64a576f0219ca7c400fc7dec3a45fbf6
Reviewed-on: https://chromium-review.googlesource.com/974427
Commit-Queue: Matthew Wolenetz <wolenetz@chromium.org>
Reviewed-by: default avatarDale Curtis <dalecurtis@chromium.org>
Cr-Commit-Position: refs/heads/master@{#546159}
parent 0ad845d3
...@@ -381,6 +381,13 @@ MATCHER_P3(NegativeDtsFailureWhenByDts, frame_type, pts_us, dts_us, "") { ...@@ -381,6 +381,13 @@ MATCHER_P3(NegativeDtsFailureWhenByDts, frame_type, pts_us, dts_us, "") {
"and filtering against append window"); "and filtering against append window");
} }
MATCHER_P2(DiscardingEmptyFrame, pts_us, dts_us, "") {
return CONTAINS_STRING(arg,
"Discarding empty audio or video coded frame, PTS=" +
base::IntToString(pts_us) +
"us, DTS=" + base::IntToString(dts_us) + "us");
}
} // namespace media } // namespace media
#endif // MEDIA_BASE_TEST_HELPERS_H_ #endif // MEDIA_BASE_TEST_HELPERS_H_
...@@ -534,7 +534,10 @@ class ChunkDemuxerTest : public ::testing::TestWithParam<BufferingApi> { ...@@ -534,7 +534,10 @@ class ChunkDemuxerTest : public ::testing::TestWithParam<BufferingApi> {
DCHECK_GT(blocks.size(), 0u); DCHECK_GT(blocks.size(), 0u);
ClusterBuilder cb; ClusterBuilder cb;
std::vector<uint8_t> data(10); // Ensure we can obtain a valid pointer to a region of data of |block_size_|
// length.
std::vector<uint8_t> data(block_size_ ? block_size_ : 1);
for (size_t i = 0; i < blocks.size(); ++i) { for (size_t i = 0; i < blocks.size(); ++i) {
if (i == 0) if (i == 0)
cb.SetClusterTimecode(blocks[i].timestamp_in_ms); cb.SetClusterTimecode(blocks[i].timestamp_in_ms);
...@@ -543,11 +546,10 @@ class ChunkDemuxerTest : public ::testing::TestWithParam<BufferingApi> { ...@@ -543,11 +546,10 @@ class ChunkDemuxerTest : public ::testing::TestWithParam<BufferingApi> {
cb.AddBlockGroup(blocks[i].track_number, blocks[i].timestamp_in_ms, cb.AddBlockGroup(blocks[i].track_number, blocks[i].timestamp_in_ms,
blocks[i].duration, blocks[i].flags, blocks[i].duration, blocks[i].flags,
blocks[i].flags & kWebMFlagKeyframe, &data[0], blocks[i].flags & kWebMFlagKeyframe, &data[0],
data.size()); block_size_);
} else { } else {
cb.AddSimpleBlock(blocks[i].track_number, blocks[i].timestamp_in_ms, cb.AddSimpleBlock(blocks[i].track_number, blocks[i].timestamp_in_ms,
blocks[i].flags, blocks[i].flags, &data[0], block_size_);
&data[0], data.size());
} }
} }
...@@ -989,21 +991,23 @@ class ChunkDemuxerTest : public ::testing::TestWithParam<BufferingApi> { ...@@ -989,21 +991,23 @@ class ChunkDemuxerTest : public ::testing::TestWithParam<BufferingApi> {
int block_duration) { int block_duration) {
CHECK_GT(end_timecode, timecode); CHECK_GT(end_timecode, timecode);
std::vector<uint8_t> data(kBlockSize); // Ensure we can obtain a valid pointer to a region of data of |block_size_|
// length.
std::vector<uint8_t> data(block_size_ ? block_size_ : 1);
ClusterBuilder cb; ClusterBuilder cb;
cb.SetClusterTimecode(timecode); cb.SetClusterTimecode(timecode);
// Create simple blocks for everything except the last block. // Create simple blocks for everything except the last block.
while (timecode < (end_timecode - block_duration)) { while (timecode < (end_timecode - block_duration)) {
cb.AddSimpleBlock(track_number, timecode, kWebMFlagKeyframe, cb.AddSimpleBlock(track_number, timecode, kWebMFlagKeyframe, &data[0],
&data[0], data.size()); block_size_);
timecode += block_duration; timecode += block_duration;
} }
cb.AddBlockGroup(track_number, timecode, block_duration, kWebMFlagKeyframe, cb.AddBlockGroup(track_number, timecode, block_duration, kWebMFlagKeyframe,
static_cast<bool>(kWebMFlagKeyframe), &data[0], static_cast<bool>(kWebMFlagKeyframe), &data[0],
data.size()); block_size_);
return cb.Finish(); return cb.Finish();
} }
...@@ -1322,6 +1326,11 @@ class ChunkDemuxerTest : public ::testing::TestWithParam<BufferingApi> { ...@@ -1322,6 +1326,11 @@ class ChunkDemuxerTest : public ::testing::TestWithParam<BufferingApi> {
BufferingApi buffering_api_; BufferingApi buffering_api_;
base::test::ScopedFeatureList scoped_feature_list_; base::test::ScopedFeatureList scoped_feature_list_;
// The size of coded frame data for a WebM SimpleBlock or BlockGroup muxed
// into a test cluster. This defaults to |kBlockSize|, but can be changed to
// test behavior.
size_t block_size_ = kBlockSize;
// Map of source id to timestamp offset to use for the next AppendData() // Map of source id to timestamp offset to use for the next AppendData()
// operation for that source id. // operation for that source id.
std::map<std::string, base::TimeDelta> timestamp_offset_map_; std::map<std::string, base::TimeDelta> timestamp_offset_map_;
...@@ -3820,8 +3829,8 @@ TEST_P(ChunkDemuxerTest, SetMemoryLimitType) { ...@@ -3820,8 +3829,8 @@ TEST_P(ChunkDemuxerTest, SetMemoryLimitType) {
ASSERT_TRUE(InitDemuxer(HAS_AUDIO | HAS_VIDEO)); ASSERT_TRUE(InitDemuxer(HAS_AUDIO | HAS_VIDEO));
// Set different memory limits for audio and video. // Set different memory limits for audio and video.
demuxer_->SetMemoryLimitsForTest(DemuxerStream::AUDIO, 10 * kBlockSize); demuxer_->SetMemoryLimitsForTest(DemuxerStream::AUDIO, 10 * block_size_);
demuxer_->SetMemoryLimitsForTest(DemuxerStream::VIDEO, 5 * kBlockSize + 1); demuxer_->SetMemoryLimitsForTest(DemuxerStream::VIDEO, 5 * block_size_ + 1);
base::TimeDelta seek_time = base::TimeDelta::FromMilliseconds(1000); base::TimeDelta seek_time = base::TimeDelta::FromMilliseconds(1000);
...@@ -3858,7 +3867,7 @@ TEST_P(ChunkDemuxerTest, SetMemoryLimitType) { ...@@ -3858,7 +3867,7 @@ TEST_P(ChunkDemuxerTest, SetMemoryLimitType) {
TEST_P(ChunkDemuxerTest, GCDuringSeek_SingleRange_SeekForward) { TEST_P(ChunkDemuxerTest, GCDuringSeek_SingleRange_SeekForward) {
ASSERT_TRUE(InitDemuxer(HAS_AUDIO)); ASSERT_TRUE(InitDemuxer(HAS_AUDIO));
demuxer_->SetMemoryLimitsForTest(DemuxerStream::AUDIO, 10 * kBlockSize); demuxer_->SetMemoryLimitsForTest(DemuxerStream::AUDIO, 10 * block_size_);
// Append some data at position 1000ms // Append some data at position 1000ms
AppendSingleStreamCluster(kSourceId, kAudioTrackNum, 1000, 10); AppendSingleStreamCluster(kSourceId, kAudioTrackNum, 1000, 10);
CheckExpectedRanges("{ [1000,1230) }"); CheckExpectedRanges("{ [1000,1230) }");
...@@ -3867,7 +3876,8 @@ TEST_P(ChunkDemuxerTest, GCDuringSeek_SingleRange_SeekForward) { ...@@ -3867,7 +3876,8 @@ TEST_P(ChunkDemuxerTest, GCDuringSeek_SingleRange_SeekForward) {
// those frames are earlier than the seek target position. // those frames are earlier than the seek target position.
base::TimeDelta seek_time = base::TimeDelta::FromMilliseconds(2000); base::TimeDelta seek_time = base::TimeDelta::FromMilliseconds(2000);
Seek(seek_time); Seek(seek_time);
EXPECT_TRUE(demuxer_->EvictCodedFrames(kSourceId, seek_time, 5 * kBlockSize)); EXPECT_TRUE(
demuxer_->EvictCodedFrames(kSourceId, seek_time, 5 * block_size_));
// Append data to complete seek operation // Append data to complete seek operation
AppendSingleStreamCluster(kSourceId, kAudioTrackNum, 2000, 5); AppendSingleStreamCluster(kSourceId, kAudioTrackNum, 2000, 5);
...@@ -3876,7 +3886,7 @@ TEST_P(ChunkDemuxerTest, GCDuringSeek_SingleRange_SeekForward) { ...@@ -3876,7 +3886,7 @@ TEST_P(ChunkDemuxerTest, GCDuringSeek_SingleRange_SeekForward) {
TEST_P(ChunkDemuxerTest, GCDuringSeek_SingleRange_SeekBack) { TEST_P(ChunkDemuxerTest, GCDuringSeek_SingleRange_SeekBack) {
ASSERT_TRUE(InitDemuxer(HAS_AUDIO)); ASSERT_TRUE(InitDemuxer(HAS_AUDIO));
demuxer_->SetMemoryLimitsForTest(DemuxerStream::AUDIO, 10 * kBlockSize); demuxer_->SetMemoryLimitsForTest(DemuxerStream::AUDIO, 10 * block_size_);
// Append some data at position 1000ms // Append some data at position 1000ms
AppendSingleStreamCluster(kSourceId, kAudioTrackNum, 1000, 10); AppendSingleStreamCluster(kSourceId, kAudioTrackNum, 1000, 10);
CheckExpectedRanges("{ [1000,1230) }"); CheckExpectedRanges("{ [1000,1230) }");
...@@ -3886,7 +3896,8 @@ TEST_P(ChunkDemuxerTest, GCDuringSeek_SingleRange_SeekBack) { ...@@ -3886,7 +3896,8 @@ TEST_P(ChunkDemuxerTest, GCDuringSeek_SingleRange_SeekBack) {
// evicted to make space for the upcoming append at seek target position. // evicted to make space for the upcoming append at seek target position.
base::TimeDelta seek_time = base::TimeDelta(); base::TimeDelta seek_time = base::TimeDelta();
Seek(seek_time); Seek(seek_time);
EXPECT_TRUE(demuxer_->EvictCodedFrames(kSourceId, seek_time, 5 * kBlockSize)); EXPECT_TRUE(
demuxer_->EvictCodedFrames(kSourceId, seek_time, 5 * block_size_));
// Append data to complete seek operation // Append data to complete seek operation
AppendSingleStreamCluster(kSourceId, kAudioTrackNum, 0, 5); AppendSingleStreamCluster(kSourceId, kAudioTrackNum, 0, 5);
...@@ -3895,7 +3906,7 @@ TEST_P(ChunkDemuxerTest, GCDuringSeek_SingleRange_SeekBack) { ...@@ -3895,7 +3906,7 @@ TEST_P(ChunkDemuxerTest, GCDuringSeek_SingleRange_SeekBack) {
TEST_P(ChunkDemuxerTest, GCDuringSeek_MultipleRanges_SeekForward) { TEST_P(ChunkDemuxerTest, GCDuringSeek_MultipleRanges_SeekForward) {
ASSERT_TRUE(InitDemuxer(HAS_AUDIO)); ASSERT_TRUE(InitDemuxer(HAS_AUDIO));
demuxer_->SetMemoryLimitsForTest(DemuxerStream::AUDIO, 10 * kBlockSize); demuxer_->SetMemoryLimitsForTest(DemuxerStream::AUDIO, 10 * block_size_);
// Append some data at position 1000ms then at 2000ms // Append some data at position 1000ms then at 2000ms
AppendSingleStreamCluster(kSourceId, kAudioTrackNum, 1000, 5); AppendSingleStreamCluster(kSourceId, kAudioTrackNum, 1000, 5);
AppendSingleStreamCluster(kSourceId, kAudioTrackNum, 2000, 5); AppendSingleStreamCluster(kSourceId, kAudioTrackNum, 2000, 5);
...@@ -3905,7 +3916,8 @@ TEST_P(ChunkDemuxerTest, GCDuringSeek_MultipleRanges_SeekForward) { ...@@ -3905,7 +3916,8 @@ TEST_P(ChunkDemuxerTest, GCDuringSeek_MultipleRanges_SeekForward) {
// those frames are earlier than the seek target position. // those frames are earlier than the seek target position.
base::TimeDelta seek_time = base::TimeDelta::FromMilliseconds(3000); base::TimeDelta seek_time = base::TimeDelta::FromMilliseconds(3000);
Seek(seek_time); Seek(seek_time);
EXPECT_TRUE(demuxer_->EvictCodedFrames(kSourceId, seek_time, 8 * kBlockSize)); EXPECT_TRUE(
demuxer_->EvictCodedFrames(kSourceId, seek_time, 8 * block_size_));
// Append data to complete seek operation // Append data to complete seek operation
AppendSingleStreamCluster(kSourceId, kAudioTrackNum, 3000, 5); AppendSingleStreamCluster(kSourceId, kAudioTrackNum, 3000, 5);
...@@ -3914,7 +3926,7 @@ TEST_P(ChunkDemuxerTest, GCDuringSeek_MultipleRanges_SeekForward) { ...@@ -3914,7 +3926,7 @@ TEST_P(ChunkDemuxerTest, GCDuringSeek_MultipleRanges_SeekForward) {
TEST_P(ChunkDemuxerTest, GCDuringSeek_MultipleRanges_SeekInbetween1) { TEST_P(ChunkDemuxerTest, GCDuringSeek_MultipleRanges_SeekInbetween1) {
ASSERT_TRUE(InitDemuxer(HAS_AUDIO)); ASSERT_TRUE(InitDemuxer(HAS_AUDIO));
demuxer_->SetMemoryLimitsForTest(DemuxerStream::AUDIO, 10 * kBlockSize); demuxer_->SetMemoryLimitsForTest(DemuxerStream::AUDIO, 10 * block_size_);
// Append some data at position 1000ms then at 2000ms // Append some data at position 1000ms then at 2000ms
AppendSingleStreamCluster(kSourceId, kAudioTrackNum, 1000, 5); AppendSingleStreamCluster(kSourceId, kAudioTrackNum, 1000, 5);
AppendSingleStreamCluster(kSourceId, kAudioTrackNum, 2000, 5); AppendSingleStreamCluster(kSourceId, kAudioTrackNum, 2000, 5);
...@@ -3930,7 +3942,8 @@ TEST_P(ChunkDemuxerTest, GCDuringSeek_MultipleRanges_SeekInbetween1) { ...@@ -3930,7 +3942,8 @@ TEST_P(ChunkDemuxerTest, GCDuringSeek_MultipleRanges_SeekInbetween1) {
// the upcoming append and allow seek to proceed. // the upcoming append and allow seek to proceed.
base::TimeDelta seek_time = base::TimeDelta::FromMilliseconds(1500); base::TimeDelta seek_time = base::TimeDelta::FromMilliseconds(1500);
Seek(seek_time); Seek(seek_time);
EXPECT_TRUE(demuxer_->EvictCodedFrames(kSourceId, seek_time, 8 * kBlockSize)); EXPECT_TRUE(
demuxer_->EvictCodedFrames(kSourceId, seek_time, 8 * block_size_));
// Append data to complete seek operation // Append data to complete seek operation
AppendSingleStreamCluster(kSourceId, kAudioTrackNum, 1500, 5); AppendSingleStreamCluster(kSourceId, kAudioTrackNum, 1500, 5);
...@@ -3939,7 +3952,7 @@ TEST_P(ChunkDemuxerTest, GCDuringSeek_MultipleRanges_SeekInbetween1) { ...@@ -3939,7 +3952,7 @@ TEST_P(ChunkDemuxerTest, GCDuringSeek_MultipleRanges_SeekInbetween1) {
TEST_P(ChunkDemuxerTest, GCDuringSeek_MultipleRanges_SeekInbetween2) { TEST_P(ChunkDemuxerTest, GCDuringSeek_MultipleRanges_SeekInbetween2) {
ASSERT_TRUE(InitDemuxer(HAS_AUDIO)); ASSERT_TRUE(InitDemuxer(HAS_AUDIO));
demuxer_->SetMemoryLimitsForTest(DemuxerStream::AUDIO, 10 * kBlockSize); demuxer_->SetMemoryLimitsForTest(DemuxerStream::AUDIO, 10 * block_size_);
// Append some data at position 2000ms first, then at 1000ms, so that the last // Append some data at position 2000ms first, then at 1000ms, so that the last
// appended data position is in the first buffered range (that matters to the // appended data position is in the first buffered range (that matters to the
...@@ -3952,7 +3965,8 @@ TEST_P(ChunkDemuxerTest, GCDuringSeek_MultipleRanges_SeekInbetween2) { ...@@ -3952,7 +3965,8 @@ TEST_P(ChunkDemuxerTest, GCDuringSeek_MultipleRanges_SeekInbetween2) {
// without calling Seek(), the GC algorithm should try to preserve data in the // without calling Seek(), the GC algorithm should try to preserve data in the
// first range, since that is most recently appended data. // first range, since that is most recently appended data.
base::TimeDelta seek_time = base::TimeDelta::FromMilliseconds(2030); base::TimeDelta seek_time = base::TimeDelta::FromMilliseconds(2030);
EXPECT_TRUE(demuxer_->EvictCodedFrames(kSourceId, seek_time, 5 * kBlockSize)); EXPECT_TRUE(
demuxer_->EvictCodedFrames(kSourceId, seek_time, 5 * block_size_));
AppendSingleStreamCluster(kSourceId, kAudioTrackNum, 1500, 5); AppendSingleStreamCluster(kSourceId, kAudioTrackNum, 1500, 5);
CheckExpectedRanges("{ [1000,1115) [1500,1615) }"); CheckExpectedRanges("{ [1000,1115) [1500,1615) }");
...@@ -3960,7 +3974,7 @@ TEST_P(ChunkDemuxerTest, GCDuringSeek_MultipleRanges_SeekInbetween2) { ...@@ -3960,7 +3974,7 @@ TEST_P(ChunkDemuxerTest, GCDuringSeek_MultipleRanges_SeekInbetween2) {
TEST_P(ChunkDemuxerTest, GCDuringSeek_MultipleRanges_SeekBack) { TEST_P(ChunkDemuxerTest, GCDuringSeek_MultipleRanges_SeekBack) {
ASSERT_TRUE(InitDemuxer(HAS_AUDIO)); ASSERT_TRUE(InitDemuxer(HAS_AUDIO));
demuxer_->SetMemoryLimitsForTest(DemuxerStream::AUDIO, 10 * kBlockSize); demuxer_->SetMemoryLimitsForTest(DemuxerStream::AUDIO, 10 * block_size_);
// Append some data at position 1000ms then at 2000ms // Append some data at position 1000ms then at 2000ms
AppendSingleStreamCluster(kSourceId, kAudioTrackNum, 1000, 5); AppendSingleStreamCluster(kSourceId, kAudioTrackNum, 1000, 5);
AppendSingleStreamCluster(kSourceId, kAudioTrackNum, 2000, 5); AppendSingleStreamCluster(kSourceId, kAudioTrackNum, 2000, 5);
...@@ -3970,7 +3984,8 @@ TEST_P(ChunkDemuxerTest, GCDuringSeek_MultipleRanges_SeekBack) { ...@@ -3970,7 +3984,8 @@ TEST_P(ChunkDemuxerTest, GCDuringSeek_MultipleRanges_SeekBack) {
// those frames are earlier than the seek target position. // those frames are earlier than the seek target position.
base::TimeDelta seek_time = base::TimeDelta(); base::TimeDelta seek_time = base::TimeDelta();
Seek(seek_time); Seek(seek_time);
EXPECT_TRUE(demuxer_->EvictCodedFrames(kSourceId, seek_time, 8 * kBlockSize)); EXPECT_TRUE(
demuxer_->EvictCodedFrames(kSourceId, seek_time, 8 * block_size_));
// Append data to complete seek operation // Append data to complete seek operation
AppendSingleStreamCluster(kSourceId, kAudioTrackNum, 0, 5); AppendSingleStreamCluster(kSourceId, kAudioTrackNum, 0, 5);
...@@ -3980,7 +3995,7 @@ TEST_P(ChunkDemuxerTest, GCDuringSeek_MultipleRanges_SeekBack) { ...@@ -3980,7 +3995,7 @@ TEST_P(ChunkDemuxerTest, GCDuringSeek_MultipleRanges_SeekBack) {
TEST_P(ChunkDemuxerTest, GCDuringSeek) { TEST_P(ChunkDemuxerTest, GCDuringSeek) {
ASSERT_TRUE(InitDemuxer(HAS_AUDIO)); ASSERT_TRUE(InitDemuxer(HAS_AUDIO));
demuxer_->SetMemoryLimitsForTest(DemuxerStream::AUDIO, 5 * kBlockSize); demuxer_->SetMemoryLimitsForTest(DemuxerStream::AUDIO, 5 * block_size_);
base::TimeDelta seek_time1 = base::TimeDelta::FromMilliseconds(1000); base::TimeDelta seek_time1 = base::TimeDelta::FromMilliseconds(1000);
base::TimeDelta seek_time2 = base::TimeDelta::FromMilliseconds(500); base::TimeDelta seek_time2 = base::TimeDelta::FromMilliseconds(500);
...@@ -4025,7 +4040,7 @@ TEST_P(ChunkDemuxerTest, GCDuringSeek) { ...@@ -4025,7 +4040,7 @@ TEST_P(ChunkDemuxerTest, GCDuringSeek) {
TEST_P(ChunkDemuxerTest, GCKeepPlayhead) { TEST_P(ChunkDemuxerTest, GCKeepPlayhead) {
ASSERT_TRUE(InitDemuxer(HAS_AUDIO)); ASSERT_TRUE(InitDemuxer(HAS_AUDIO));
demuxer_->SetMemoryLimitsForTest(DemuxerStream::AUDIO, 5 * kBlockSize); demuxer_->SetMemoryLimitsForTest(DemuxerStream::AUDIO, 5 * block_size_);
// Append data at the start that can be garbage collected: // Append data at the start that can be garbage collected:
AppendSingleStreamCluster(kSourceId, kAudioTrackNum, 0, 10); AppendSingleStreamCluster(kSourceId, kAudioTrackNum, 0, 10);
...@@ -4424,8 +4439,8 @@ TEST_P(ChunkDemuxerTest, CuesBetweenClusters) { ...@@ -4424,8 +4439,8 @@ TEST_P(ChunkDemuxerTest, CuesBetweenClusters) {
TEST_P(ChunkDemuxerTest, EvictCodedFramesTest) { TEST_P(ChunkDemuxerTest, EvictCodedFramesTest) {
ASSERT_TRUE(InitDemuxer(HAS_AUDIO | HAS_VIDEO)); ASSERT_TRUE(InitDemuxer(HAS_AUDIO | HAS_VIDEO));
demuxer_->SetMemoryLimitsForTest(DemuxerStream::AUDIO, 10 * kBlockSize); demuxer_->SetMemoryLimitsForTest(DemuxerStream::AUDIO, 10 * block_size_);
demuxer_->SetMemoryLimitsForTest(DemuxerStream::VIDEO, 15 * kBlockSize); demuxer_->SetMemoryLimitsForTest(DemuxerStream::VIDEO, 15 * block_size_);
DemuxerStream* audio_stream = GetStream(DemuxerStream::AUDIO); DemuxerStream* audio_stream = GetStream(DemuxerStream::AUDIO);
DemuxerStream* video_stream = GetStream(DemuxerStream::VIDEO); DemuxerStream* video_stream = GetStream(DemuxerStream::VIDEO);
...@@ -4924,6 +4939,60 @@ TEST_P(ChunkDemuxerTest, UnmarkEOSRetainsParseErrorState_AfterInit) { ...@@ -4924,6 +4939,60 @@ TEST_P(ChunkDemuxerTest, UnmarkEOSRetainsParseErrorState_AfterInit) {
ASSERT_FALSE(AppendInitSegment(HAS_AUDIO | HAS_VIDEO)); ASSERT_FALSE(AppendInitSegment(HAS_AUDIO | HAS_VIDEO));
} }
struct ZeroLengthFrameCase {
DemuxerStream::Type stream_type;
int flags;
int track_number;
};
// Test that 0-length audio and video coded frames are dropped gracefully.
TEST_P(ChunkDemuxerTest, ZeroLengthFramesDropped) {
struct ZeroLengthFrameCase cases[] = {
{DemuxerStream::AUDIO, HAS_AUDIO, kAudioTrackNum},
{DemuxerStream::VIDEO, HAS_VIDEO, kVideoTrackNum}};
for (const auto& c : cases) {
InSequence s;
CreateNewDemuxer();
ASSERT_TRUE(InitDemuxer(c.flags));
DemuxerStream* stream = GetStream(c.stream_type);
// Append a cluster containing nonzero-sized frames. Use end of stream to
// ensure we read back precisely the expected buffers.
ASSERT_GT(block_size_, 0U);
AppendSingleStreamCluster(kSourceId, c.track_number, "0K 10K 20K 30D10K");
EXPECT_CALL(host_, SetDuration(base::TimeDelta::FromMilliseconds(40)));
MarkEndOfStream(PIPELINE_OK);
CheckExpectedRanges("{ [0,40) }");
CheckExpectedBuffers(stream, "0K 10K 20K 30K");
ExpectEndOfStream(c.stream_type);
// Append a cluster containing a 0-sized frame. Verify there is nothing new
// buffered.
demuxer_->UnmarkEndOfStream();
EXPECT_MEDIA_LOG(DiscardingEmptyFrame(40000, 40000));
block_size_ = 0;
AppendSingleStreamCluster(kSourceId, c.track_number, "40D10K");
MarkEndOfStream(PIPELINE_OK);
Seek(base::TimeDelta::FromMilliseconds(0));
CheckExpectedRanges("{ [0,40) }");
CheckExpectedBuffers(stream, "0K 10K 20K 30K");
ExpectEndOfStream(c.stream_type);
// Append a cluster containing a nonzero-sized frame. Verify it is buffered.
demuxer_->UnmarkEndOfStream();
EXPECT_CALL(host_, SetDuration(base::TimeDelta::FromMilliseconds(50)));
block_size_ = kBlockSize;
AppendSingleStreamCluster(kSourceId, c.track_number, "40D10K");
MarkEndOfStream(PIPELINE_OK);
Seek(base::TimeDelta::FromMilliseconds(0));
CheckExpectedRanges("{ [0,50) }");
CheckExpectedBuffers(stream, "0K 10K 20K 30K 40K");
ExpectEndOfStream(c.stream_type);
}
}
// TODO(servolk): Add a unit test with multiple audio/video tracks using the // TODO(servolk): Add a unit test with multiple audio/video tracks using the
// same codec type in a single SourceBufferState, when WebM parser supports // same codec type in a single SourceBufferState, when WebM parser supports
// multiple tracks. crbug.com/646900 // multiple tracks. crbug.com/646900
......
...@@ -20,6 +20,7 @@ const int kMaxDtsBeyondPtsWarnings = 10; ...@@ -20,6 +20,7 @@ const int kMaxDtsBeyondPtsWarnings = 10;
const int kMaxAudioNonKeyframeWarnings = 10; const int kMaxAudioNonKeyframeWarnings = 10;
const int kMaxNumKeyframeTimeGreaterThanDependantWarnings = 1; const int kMaxNumKeyframeTimeGreaterThanDependantWarnings = 1;
const int kMaxMuxedSequenceModeWarnings = 1; const int kMaxMuxedSequenceModeWarnings = 1;
const int kMaxSkippedEmptyFrameWarnings = 5;
// Helper class to capture per-track details needed by a frame processor. Some // Helper class to capture per-track details needed by a frame processor. Some
// of this information may be duplicated in the short-term in the associated // of this information may be duplicated in the short-term in the associated
...@@ -370,9 +371,21 @@ bool FrameProcessor::ProcessFrames( ...@@ -370,9 +371,21 @@ bool FrameProcessor::ProcessFrames(
// https://rawgit.com/w3c/media-source/d8f901f22/ // https://rawgit.com/w3c/media-source/d8f901f22/
// index.html#sourcebuffer-coded-frame-processing // index.html#sourcebuffer-coded-frame-processing
// 1. For each coded frame in the media segment run the following steps: // 1. For each coded frame in the media segment run the following steps:
for (StreamParser::BufferQueue::const_iterator frames_itr = frames.begin(); for (const auto& frame : frames) {
frames_itr != frames.end(); ++frames_itr) { // Skip any 0-byte audio or video buffers, since they cannot produce any
if (!ProcessFrame(*frames_itr, append_window_start, append_window_end, // valid decode output (and are rejected by FFmpeg A/V decode.) Retain
// 0-byte text buffers because their |side_data| just might be useful, and
// we don't feed them to FFmpeg later.
if (!frame->data_size() && frame->type() != DemuxerStream::TEXT) {
LIMITED_MEDIA_LOG(DEBUG, media_log_, num_skipped_empty_frame_warnings_,
kMaxSkippedEmptyFrameWarnings)
<< "Discarding empty audio or video coded frame, PTS="
<< frame->timestamp().InMicroseconds()
<< "us, DTS=" << frame->GetDecodeTimestamp().InMicroseconds() << "us";
continue;
}
if (!ProcessFrame(frame, append_window_start, append_window_end,
timestamp_offset)) { timestamp_offset)) {
FlushProcessedFrames(); FlushProcessedFrames();
return false; return false;
......
...@@ -190,6 +190,7 @@ class MEDIA_EXPORT FrameProcessor { ...@@ -190,6 +190,7 @@ class MEDIA_EXPORT FrameProcessor {
int num_dts_beyond_pts_warnings_ = 0; int num_dts_beyond_pts_warnings_ = 0;
int num_audio_non_keyframe_warnings_ = 0; int num_audio_non_keyframe_warnings_ = 0;
int num_muxed_sequence_mode_warnings_ = 0; int num_muxed_sequence_mode_warnings_ = 0;
int num_skipped_empty_frame_warnings_ = 0;
DISALLOW_COPY_AND_ASSIGN(FrameProcessor); DISALLOW_COPY_AND_ASSIGN(FrameProcessor);
}; };
......
...@@ -191,7 +191,7 @@ void ClusterBuilder::WriteBlock(uint8_t* buf, ...@@ -191,7 +191,7 @@ void ClusterBuilder::WriteBlock(uint8_t* buf,
DCHECK_GE(flags, 0); DCHECK_GE(flags, 0);
DCHECK_LE(flags, 0xff); DCHECK_LE(flags, 0xff);
DCHECK(data); DCHECK(data);
DCHECK_GT(size, 0); DCHECK_GE(size, 0); // For testing, allow 0-byte coded frames.
DCHECK_NE(cluster_timecode_, -1); DCHECK_NE(cluster_timecode_, -1);
int64_t timecode_delta = timecode - cluster_timecode_; int64_t timecode_delta = timecode - cluster_timecode_;
......
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