blob: 2f3139eabab0723951bb841064dfc592d2c3e894 [file] [log] [blame]
// Copyright 2016 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/filters/source_buffer_state.h"
#include <vector>
#include "base/bind.h"
#include "base/memory/ptr_util.h"
#include "base/strings/string_number_conversions.h"
#include "media/base/gmock_callback_support.h"
#include "media/base/media_util.h"
#include "media/base/mock_filters.h"
#include "media/base/mock_media_log.h"
#include "media/base/test_helpers.h"
#include "media/filters/chunk_demuxer.h"
#include "media/filters/frame_processor.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace media {
using testing::_;
using testing::SaveArg;
using testing::Values;
namespace {
AudioDecoderConfig CreateAudioConfig(AudioCodec codec) {
return AudioDecoderConfig(codec, kSampleFormatPlanarF32,
CHANNEL_LAYOUT_STEREO, 1000, EmptyExtraData(),
Unencrypted());
}
VideoDecoderConfig CreateVideoConfig(VideoCodec codec, int w, int h) {
gfx::Size size(w, h);
gfx::Rect visible_rect(size);
return VideoDecoderConfig(codec, VIDEO_CODEC_PROFILE_UNKNOWN,
PIXEL_FORMAT_I420, VideoColorSpace::REC709(),
VIDEO_ROTATION_0, size, visible_rect, size,
EmptyExtraData(), Unencrypted());
}
void AddAudioTrack(std::unique_ptr<MediaTracks>& t, AudioCodec codec, int id) {
t->AddAudioTrack(CreateAudioConfig(codec), id, "", "", "");
}
void AddVideoTrack(std::unique_ptr<MediaTracks>& t, VideoCodec codec, int id) {
t->AddVideoTrack(CreateVideoConfig(codec, 16, 16), id, "", "", "");
}
void InvokeCbAndSaveResult(const base::Callback<bool()>& cb, bool* result) {
DCHECK(result);
*result = cb.Run();
}
}
class SourceBufferStateTest
: public ::testing::TestWithParam<ChunkDemuxerStream::RangeApi> {
public:
SourceBufferStateTest() : mock_stream_parser_(nullptr) {
range_api_ = GetParam();
}
std::unique_ptr<SourceBufferState> CreateSourceBufferState() {
std::unique_ptr<FrameProcessor> frame_processor = base::WrapUnique(
new FrameProcessor(base::Bind(&SourceBufferStateTest::OnUpdateDuration,
base::Unretained(this)),
&media_log_, range_api_));
mock_stream_parser_ = new testing::StrictMock<MockStreamParser>();
return base::WrapUnique(new SourceBufferState(
base::WrapUnique(mock_stream_parser_), std::move(frame_processor),
base::Bind(&SourceBufferStateTest::CreateDemuxerStream,
base::Unretained(this)),
&media_log_));
}
std::unique_ptr<SourceBufferState> CreateAndInitSourceBufferState(
const std::string& expected_codecs) {
std::unique_ptr<SourceBufferState> sbs = CreateSourceBufferState();
// Instead of using SaveArg<> to update |new_config_cb_| when mocked Init is
// called, we use a lambda because SaveArg<> doesn't work if any of the
// mocked method's arguments are move-only type.
EXPECT_CALL(*mock_stream_parser_, Init(_, _, _, _, _, _, _, _))
.WillOnce([&](auto init_cb, auto config_cb, auto new_buffers_cb,
auto ignore_text_track, auto encrypted_media_init_data_cb,
auto new_segment_cb, auto end_of_segment_cb,
auto media_log) { new_config_cb_ = config_cb; });
sbs->Init(base::BindOnce(&SourceBufferStateTest::SourceInitDone,
base::Unretained(this)),
expected_codecs,
base::BindRepeating(
&SourceBufferStateTest::StreamParserEncryptedInitData,
base::Unretained(this)),
base::Bind(&SourceBufferStateTest::StreamParserNewTextTrack,
base::Unretained(this)));
sbs->SetTracksWatcher(base::Bind(
&SourceBufferStateTest::OnMediaTracksUpdated, base::Unretained(this)));
// These tests are not expected to issue any parse warnings.
EXPECT_CALL(*this, OnParseWarningMock(_)).Times(0);
sbs->SetParseWarningCallback(base::Bind(
&SourceBufferStateTest::OnParseWarningMock, base::Unretained(this)));
return sbs;
}
// Emulates appending some data to the SourceBufferState, since OnNewConfigs
// can only be invoked when append is in progress.
bool AppendDataAndReportTracks(const std::unique_ptr<SourceBufferState>& sbs,
std::unique_ptr<MediaTracks> tracks) {
const uint8_t stream_data[] = "stream_data";
const int data_size = sizeof(stream_data);
base::TimeDelta t;
StreamParser::TextTrackConfigMap text_track_config_map;
bool new_configs_result = false;
base::Closure new_configs_closure =
base::Bind(InvokeCbAndSaveResult,
base::Bind(new_config_cb_, base::Passed(std::move(tracks)),
text_track_config_map),
&new_configs_result);
EXPECT_CALL(*mock_stream_parser_, Parse(stream_data, data_size))
.WillOnce(testing::DoAll(RunClosure(new_configs_closure),
testing::Return(true)));
sbs->Append(stream_data, data_size, t, t, &t);
return new_configs_result;
}
MOCK_METHOD1(OnParseWarningMock, void(const SourceBufferParseWarning));
MOCK_METHOD1(OnUpdateDuration, void(base::TimeDelta));
MOCK_METHOD1(SourceInitDone, void(const StreamParser::InitParameters&));
MOCK_METHOD2(StreamParserEncryptedInitData,
void(EmeInitDataType, const std::vector<uint8_t>&));
MOCK_METHOD2(StreamParserNewTextTrack,
void(ChunkDemuxerStream*, const TextTrackConfig&));
MOCK_METHOD1(MediaTracksUpdatedMock, void(std::unique_ptr<MediaTracks>&));
void OnMediaTracksUpdated(std::unique_ptr<MediaTracks> tracks) {
MediaTracksUpdatedMock(tracks);
}
ChunkDemuxerStream* CreateDemuxerStream(DemuxerStream::Type type) {
static unsigned track_id = 0;
demuxer_streams_.push_back(base::WrapUnique(new ChunkDemuxerStream(
type, base::NumberToString(++track_id), range_api_)));
return demuxer_streams_.back().get();
}
testing::StrictMock<MockMediaLog> media_log_;
std::vector<std::unique_ptr<ChunkDemuxerStream>> demuxer_streams_;
MockStreamParser* mock_stream_parser_;
StreamParser::NewConfigCB new_config_cb_;
ChunkDemuxerStream::RangeApi range_api_;
};
TEST_P(SourceBufferStateTest, InitSingleAudioTrack) {
std::unique_ptr<SourceBufferState> sbs =
CreateAndInitSourceBufferState("vorbis");
std::unique_ptr<MediaTracks> tracks(new MediaTracks());
AddAudioTrack(tracks, kCodecVorbis, 1);
EXPECT_MEDIA_LOG(FoundStream("audio"));
EXPECT_MEDIA_LOG(CodecName("audio", "vorbis"));
EXPECT_CALL(*this, MediaTracksUpdatedMock(_));
EXPECT_TRUE(AppendDataAndReportTracks(sbs, std::move(tracks)));
}
TEST_P(SourceBufferStateTest, InitSingleVideoTrack) {
std::unique_ptr<SourceBufferState> sbs =
CreateAndInitSourceBufferState("vp8");
std::unique_ptr<MediaTracks> tracks(new MediaTracks());
AddVideoTrack(tracks, kCodecVP8, 1);
EXPECT_MEDIA_LOG(FoundStream("video"));
EXPECT_MEDIA_LOG(CodecName("video", "vp8"));
EXPECT_CALL(*this, MediaTracksUpdatedMock(_));
EXPECT_TRUE(AppendDataAndReportTracks(sbs, std::move(tracks)));
}
TEST_P(SourceBufferStateTest, InitMultipleTracks) {
std::unique_ptr<SourceBufferState> sbs =
CreateAndInitSourceBufferState("vorbis,vp8,opus,vp9");
std::unique_ptr<MediaTracks> tracks(new MediaTracks());
AddAudioTrack(tracks, kCodecVorbis, 1);
AddAudioTrack(tracks, kCodecOpus, 2);
AddVideoTrack(tracks, kCodecVP8, 3);
AddVideoTrack(tracks, kCodecVP9, 4);
EXPECT_MEDIA_LOG(FoundStream("audio")).Times(2);
EXPECT_MEDIA_LOG(CodecName("audio", "vorbis"));
EXPECT_MEDIA_LOG(CodecName("audio", "opus"));
EXPECT_MEDIA_LOG(FoundStream("video")).Times(2);
EXPECT_MEDIA_LOG(CodecName("video", "vp8"));
EXPECT_MEDIA_LOG(CodecName("video", "vp9"));
EXPECT_CALL(*this, MediaTracksUpdatedMock(_));
EXPECT_TRUE(AppendDataAndReportTracks(sbs, std::move(tracks)));
}
TEST_P(SourceBufferStateTest, AudioStreamMismatchesExpectedCodecs) {
std::unique_ptr<SourceBufferState> sbs =
CreateAndInitSourceBufferState("opus");
std::unique_ptr<MediaTracks> tracks(new MediaTracks());
AddAudioTrack(tracks, kCodecVorbis, 1);
EXPECT_MEDIA_LOG(InitSegmentMismatchesMimeType("Audio", "vorbis"));
EXPECT_FALSE(AppendDataAndReportTracks(sbs, std::move(tracks)));
}
TEST_P(SourceBufferStateTest, VideoStreamMismatchesExpectedCodecs) {
std::unique_ptr<SourceBufferState> sbs =
CreateAndInitSourceBufferState("vp9");
std::unique_ptr<MediaTracks> tracks(new MediaTracks());
AddVideoTrack(tracks, kCodecVP8, 1);
EXPECT_MEDIA_LOG(InitSegmentMismatchesMimeType("Video", "vp8"));
EXPECT_FALSE(AppendDataAndReportTracks(sbs, std::move(tracks)));
}
TEST_P(SourceBufferStateTest, MissingExpectedAudioStream) {
std::unique_ptr<SourceBufferState> sbs =
CreateAndInitSourceBufferState("opus,vp9");
std::unique_ptr<MediaTracks> tracks(new MediaTracks());
AddVideoTrack(tracks, kCodecVP9, 1);
EXPECT_MEDIA_LOG(FoundStream("video"));
EXPECT_MEDIA_LOG(CodecName("video", "vp9"));
EXPECT_MEDIA_LOG(InitSegmentMissesExpectedTrack("opus"));
EXPECT_FALSE(AppendDataAndReportTracks(sbs, std::move(tracks)));
}
TEST_P(SourceBufferStateTest, MissingExpectedVideoStream) {
std::unique_ptr<SourceBufferState> sbs =
CreateAndInitSourceBufferState("opus,vp9");
std::unique_ptr<MediaTracks> tracks(new MediaTracks());
tracks->AddAudioTrack(CreateAudioConfig(kCodecOpus), 1, "", "", "");
EXPECT_MEDIA_LOG(FoundStream("audio"));
EXPECT_MEDIA_LOG(CodecName("audio", "opus"));
EXPECT_MEDIA_LOG(InitSegmentMissesExpectedTrack("vp9"));
EXPECT_FALSE(AppendDataAndReportTracks(sbs, std::move(tracks)));
}
TEST_P(SourceBufferStateTest, TrackIdsChangeInSecondInitSegment) {
std::unique_ptr<SourceBufferState> sbs =
CreateAndInitSourceBufferState("opus,vp9");
std::unique_ptr<MediaTracks> tracks(new MediaTracks());
AddAudioTrack(tracks, kCodecOpus, 1);
AddVideoTrack(tracks, kCodecVP9, 2);
EXPECT_MEDIA_LOG(FoundStream("audio"));
EXPECT_MEDIA_LOG(CodecName("audio", "opus"));
EXPECT_MEDIA_LOG(FoundStream("video"));
EXPECT_MEDIA_LOG(CodecName("video", "vp9"));
EXPECT_CALL(*this, MediaTracksUpdatedMock(_));
AppendDataAndReportTracks(sbs, std::move(tracks));
// This second set of tracks have bytestream track ids that differ from the
// first init segment above (audio track id 1 -> 3, video track id 2 -> 4).
// Bytestream track ids are allowed to change when there is only a single
// track of each type.
std::unique_ptr<MediaTracks> tracks2(new MediaTracks());
AddAudioTrack(tracks2, kCodecOpus, 3);
AddVideoTrack(tracks2, kCodecVP9, 4);
EXPECT_CALL(*this, MediaTracksUpdatedMock(_));
AppendDataAndReportTracks(sbs, std::move(tracks2));
}
TEST_P(SourceBufferStateTest, TrackIdChangeWithTwoAudioTracks) {
std::unique_ptr<SourceBufferState> sbs =
CreateAndInitSourceBufferState("vorbis,opus");
std::unique_ptr<MediaTracks> tracks(new MediaTracks());
AddAudioTrack(tracks, kCodecVorbis, 1);
AddAudioTrack(tracks, kCodecOpus, 2);
EXPECT_MEDIA_LOG(FoundStream("audio")).Times(2);
EXPECT_MEDIA_LOG(CodecName("audio", "vorbis"));
EXPECT_MEDIA_LOG(CodecName("audio", "opus"));
EXPECT_CALL(*this, MediaTracksUpdatedMock(_));
EXPECT_TRUE(AppendDataAndReportTracks(sbs, std::move(tracks)));
// Since we have two audio tracks, bytestream track ids must match the first
// init segment.
std::unique_ptr<MediaTracks> tracks2(new MediaTracks());
AddAudioTrack(tracks2, kCodecVorbis, 1);
AddAudioTrack(tracks2, kCodecOpus, 2);
EXPECT_CALL(*this, MediaTracksUpdatedMock(_));
EXPECT_TRUE(AppendDataAndReportTracks(sbs, std::move(tracks2)));
// Emulate the situation where bytestream track ids have changed in the third
// init segment. This must cause failure in the OnNewConfigs.
std::unique_ptr<MediaTracks> tracks3(new MediaTracks());
AddAudioTrack(tracks3, kCodecVorbis, 1);
AddAudioTrack(tracks3, kCodecOpus, 3);
EXPECT_MEDIA_LOG(UnexpectedTrack("audio", "3"));
EXPECT_FALSE(AppendDataAndReportTracks(sbs, std::move(tracks3)));
}
TEST_P(SourceBufferStateTest, TrackIdChangeWithTwoVideoTracks) {
std::unique_ptr<SourceBufferState> sbs =
CreateAndInitSourceBufferState("vp8,vp9");
std::unique_ptr<MediaTracks> tracks(new MediaTracks());
AddVideoTrack(tracks, kCodecVP8, 1);
AddVideoTrack(tracks, kCodecVP9, 2);
EXPECT_MEDIA_LOG(FoundStream("video")).Times(2);
EXPECT_MEDIA_LOG(CodecName("video", "vp8"));
EXPECT_MEDIA_LOG(CodecName("video", "vp9"));
EXPECT_CALL(*this, MediaTracksUpdatedMock(_));
EXPECT_TRUE(AppendDataAndReportTracks(sbs, std::move(tracks)));
// Since we have two video tracks, bytestream track ids must match the first
// init segment.
std::unique_ptr<MediaTracks> tracks2(new MediaTracks());
AddVideoTrack(tracks2, kCodecVP8, 1);
AddVideoTrack(tracks2, kCodecVP9, 2);
EXPECT_CALL(*this, MediaTracksUpdatedMock(_));
EXPECT_TRUE(AppendDataAndReportTracks(sbs, std::move(tracks2)));
// Emulate the situation where bytestream track ids have changed in the third
// init segment. This must cause failure in the OnNewConfigs.
std::unique_ptr<MediaTracks> tracks3(new MediaTracks());
AddVideoTrack(tracks3, kCodecVP8, 1);
AddVideoTrack(tracks3, kCodecVP9, 3);
EXPECT_MEDIA_LOG(UnexpectedTrack("video", "3"));
EXPECT_FALSE(AppendDataAndReportTracks(sbs, std::move(tracks3)));
}
TEST_P(SourceBufferStateTest, TrackIdsSwappedInSecondInitSegment) {
std::unique_ptr<SourceBufferState> sbs =
CreateAndInitSourceBufferState("opus,vp9");
std::unique_ptr<MediaTracks> tracks(new MediaTracks());
AddAudioTrack(tracks, kCodecOpus, 1);
AddVideoTrack(tracks, kCodecVP9, 2);
EXPECT_MEDIA_LOG(FoundStream("audio"));
EXPECT_MEDIA_LOG(CodecName("audio", "opus"));
EXPECT_MEDIA_LOG(FoundStream("video"));
EXPECT_MEDIA_LOG(CodecName("video", "vp9"));
EXPECT_CALL(*this, MediaTracksUpdatedMock(_));
AppendDataAndReportTracks(sbs, std::move(tracks));
// Track ids are swapped in the second init segment.
std::unique_ptr<MediaTracks> tracks2(new MediaTracks());
AddAudioTrack(tracks2, kCodecOpus, 2);
AddVideoTrack(tracks2, kCodecVP9, 1);
EXPECT_CALL(*this, MediaTracksUpdatedMock(_));
AppendDataAndReportTracks(sbs, std::move(tracks2));
}
INSTANTIATE_TEST_SUITE_P(LegacyByDts,
SourceBufferStateTest,
Values(ChunkDemuxerStream::RangeApi::kLegacyByDts));
INSTANTIATE_TEST_SUITE_P(NewByPts,
SourceBufferStateTest,
Values(ChunkDemuxerStream::RangeApi::kNewByPts));
} // namespace media