blob: d199aa4d1f507a26850a6160affea1f4a43942d4 [file] [log] [blame]
// Copyright 2022 The Cobalt Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "cobalt/media/progressive/demuxer_extension_wrapper.h"
#include <cstdint>
#include <memory>
#include <tuple>
#include <vector>
#include "base/synchronization/waitable_event.h"
#include "base/test/mock_callback.h"
#include "base/test/scoped_task_environment.h"
#include "base/threading/platform_thread.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "base/time/time.h"
#include "cobalt/media/decoder_buffer_allocator.h"
#include "starboard/extension/demuxer.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/chromium/media/base/demuxer.h"
namespace cobalt {
namespace media {
namespace {
using ::testing::_;
using ::testing::AtMost;
using ::testing::ElementsAre;
using ::testing::ElementsAreArray;
using ::testing::ExplainMatchResult;
using ::testing::Invoke;
using ::testing::InvokeWithoutArgs;
using ::testing::NiceMock;
using ::testing::NotNull;
using ::testing::Pointee;
using ::testing::Return;
using ::testing::UnorderedElementsAre;
// Matches a DemuxerStream and verifies that the type is |type|.
MATCHER_P(TypeIs, type, "") { return arg.type() == type; }
// Matches a DecoderBuffer and verifies that the data in the buffer is |data|.
MATCHER_P(BufferHasData, data, "") {
return ExplainMatchResult(
ElementsAreArray(data),
std::tuple<const uint8_t*, size_t>{arg.data(), arg.data_size()},
result_listener);
}
class MockDemuxerHost : public ::media::DemuxerHost {
public:
MockDemuxerHost() = default;
MockDemuxerHost(const MockDemuxerHost&) = delete;
MockDemuxerHost& operator=(const MockDemuxerHost&) = delete;
~MockDemuxerHost() override = default;
MOCK_METHOD1(OnBufferedTimeRangesChanged,
void(const ::media::Ranges<base::TimeDelta>&));
MOCK_METHOD1(SetDuration, void(base::TimeDelta duration));
MOCK_METHOD1(OnDemuxerError, void(::media::PipelineStatus error));
};
class MockDataSource : public DataSource {
public:
MockDataSource() {
// Set reasonable default behavior for functions that are expected to
// interact with callbacks and/or output parameters.
ON_CALL(*this, Read(_, _, _, _))
.WillByDefault(Invoke(+[](int64_t position, int size, uint8_t* data,
const DataSource::ReadCB& read_cb) {
memset(data, 0, size);
read_cb.Run(size);
}));
ON_CALL(*this, GetSize(_)).WillByDefault(Invoke(+[](int64_t* size_out) {
*size_out = 0;
return true;
}));
}
~MockDataSource() override = default;
MOCK_METHOD4(Read, void(int64_t position, int size, uint8_t* data,
const DataSource::ReadCB& read_cb));
MOCK_METHOD0(Stop, void());
MOCK_METHOD0(Abort, void());
MOCK_METHOD1(GetSize, bool(int64_t* size_out));
MOCK_METHOD1(SetDownloadingStatusCB,
void(const DownloadingStatusCB& downloading_status_cb));
};
// Mock class for receiving calls to the Cobalt Extension demuxer. Based on the
// CobaltExtensionDemuxer struct.
class MockCobaltExtensionDemuxer {
public:
MOCK_METHOD0(Initialize, CobaltExtensionDemuxerStatus());
MOCK_METHOD1(Seek, CobaltExtensionDemuxerStatus(int64_t seek_time_us));
MOCK_METHOD0(GetStartTime, SbTime());
MOCK_METHOD0(GetTimelineOffset, SbTime());
MOCK_METHOD3(Read, void(CobaltExtensionDemuxerStreamType type,
CobaltExtensionDemuxerReadCB read_cb,
void* read_cb_user_data));
MOCK_METHOD1(GetAudioConfig,
bool(CobaltExtensionDemuxerAudioDecoderConfig* config));
MOCK_METHOD1(GetVideoConfig,
bool(CobaltExtensionDemuxerVideoDecoderConfig* config));
MOCK_METHOD0(GetDuration, SbTime());
// Pure C functions to be used in CobaltExtensionDemuxer. These expect
// |user_data| to be a pointer to a MockCobaltExtensionDemuxer.
static CobaltExtensionDemuxerStatus InitializeImpl(void* user_data) {
return static_cast<MockCobaltExtensionDemuxer*>(user_data)->Initialize();
}
static CobaltExtensionDemuxerStatus SeekImpl(int64_t seek_time_us,
void* user_data) {
return static_cast<MockCobaltExtensionDemuxer*>(user_data)->Seek(
seek_time_us);
}
static SbTime GetStartTimeImpl(void* user_data) {
return static_cast<MockCobaltExtensionDemuxer*>(user_data)->GetStartTime();
}
static SbTime GetTimelineOffsetImpl(void* user_data) {
return static_cast<MockCobaltExtensionDemuxer*>(user_data)
->GetTimelineOffset();
}
static void ReadImpl(CobaltExtensionDemuxerStreamType type,
CobaltExtensionDemuxerReadCB read_cb,
void* read_cb_user_data, void* user_data) {
static_cast<MockCobaltExtensionDemuxer*>(user_data)->Read(
type, read_cb, read_cb_user_data);
}
static bool GetAudioConfigImpl(
CobaltExtensionDemuxerAudioDecoderConfig* config, void* user_data) {
return static_cast<MockCobaltExtensionDemuxer*>(user_data)->GetAudioConfig(
config);
}
static bool GetVideoConfigImpl(
CobaltExtensionDemuxerVideoDecoderConfig* config, void* user_data) {
return static_cast<MockCobaltExtensionDemuxer*>(user_data)->GetVideoConfig(
config);
}
static SbTime GetDurationImpl(void* user_data) {
return static_cast<MockCobaltExtensionDemuxer*>(user_data)->GetDuration();
}
};
// Forward declaration for the purpose of defining GetMockDemuxerApi. Defined
// below.
class MockDemuxerApi;
// Returns a pointer to a MockDemuxerApi with static storage duration. The
// returned pointer should not be deleted. Defined below.
MockDemuxerApi* GetMockDemuxerApi();
// Mock class for receiving calls to the Cobalt Extension demuxer API. Based on
// the CobaltExtensionDemuxerApi struct.
class MockDemuxerApi {
public:
MOCK_METHOD5(CreateDemuxer,
CobaltExtensionDemuxer*(
CobaltExtensionDemuxerDataSource* data_source,
CobaltExtensionDemuxerAudioCodec* supported_audio_codecs,
int64_t supported_audio_codecs_size,
CobaltExtensionDemuxerVideoCodec* supported_video_codecs,
int64_t supported_video_codecs_size));
MOCK_METHOD1(DestroyDemuxer, void(CobaltExtensionDemuxer* demuxer));
// Pure C functions to be used in CobaltExtensionDemuxer. These expect
// |user_data| to be a pointer to a MockDemuxerApi.
static CobaltExtensionDemuxer* CreateDemuxerImpl(
CobaltExtensionDemuxerDataSource* data_source,
CobaltExtensionDemuxerAudioCodec* supported_audio_codecs,
int64_t supported_audio_codecs_size,
CobaltExtensionDemuxerVideoCodec* supported_video_codecs,
int64_t supported_video_codecs_size) {
return GetMockDemuxerApi()->CreateDemuxer(
data_source, supported_audio_codecs, supported_audio_codecs_size,
supported_video_codecs, supported_video_codecs_size);
}
static void DestroyDemuxerImpl(CobaltExtensionDemuxer* demuxer) {
GetMockDemuxerApi()->DestroyDemuxer(demuxer);
}
};
MockDemuxerApi* GetMockDemuxerApi() {
static auto* const demuxer_api = []() {
auto* inner_demuxer_api = new MockDemuxerApi;
// This mock won't be destructed.
testing::Mock::AllowLeak(inner_demuxer_api);
return inner_demuxer_api;
}();
return demuxer_api;
}
CobaltExtensionDemuxer* CreateCDemuxer(
MockCobaltExtensionDemuxer* mock_demuxer) {
CHECK(mock_demuxer);
return new CobaltExtensionDemuxer{
&MockCobaltExtensionDemuxer::InitializeImpl,
&MockCobaltExtensionDemuxer::SeekImpl,
&MockCobaltExtensionDemuxer::GetStartTimeImpl,
&MockCobaltExtensionDemuxer::GetTimelineOffsetImpl,
&MockCobaltExtensionDemuxer::ReadImpl,
&MockCobaltExtensionDemuxer::GetAudioConfigImpl,
&MockCobaltExtensionDemuxer::GetVideoConfigImpl,
&MockCobaltExtensionDemuxer::GetDurationImpl,
mock_demuxer};
}
// A test fixture is used to verify and clear the global mock demuxer, and to
// manage the lifetime of the ScopedTaskEnvironment.
class DemuxerExtensionWrapperTest : public ::testing::Test {
protected:
DemuxerExtensionWrapperTest() = default;
~DemuxerExtensionWrapperTest() override {
testing::Mock::VerifyAndClearExpectations(GetMockDemuxerApi());
}
// Waits |time_limit| for |done| to occur. Returns true if the event occurred,
// false otherwise. While waiting, allows other threads to run and runs the
// task runner until idle.
bool WaitForEvent(base::WaitableEvent& done,
base::TimeDelta time_limit = base::Seconds(1)) {
const base::Time deadline = base::Time::Now() + base::Seconds(1);
while (base::Time::Now() < deadline) {
task_environment_.RunUntilIdle();
base::PlatformThread::YieldCurrentThread();
if (done.IsSignaled()) {
break;
}
}
return done.IsSignaled();
}
// This must be deleted last.
base::test::ScopedTaskEnvironment task_environment_{
base::test::ScopedTaskEnvironment::MainThreadType::MOCK_TIME};
// This is necessary in order to allocate DecoderBuffers. This is done
// internally in DemuxerExtensionWrapper.
DecoderBufferAllocator allocator_;
};
TEST_F(DemuxerExtensionWrapperTest, SuccessfullyInitializes) {
// This must outlive the DemuxerExtensionWrapper.
NiceMock<MockDemuxerHost> mock_host;
MockDataSource data_source;
MockDemuxerApi* mock_demuxer_api = GetMockDemuxerApi(); // Not owned.
NiceMock<MockCobaltExtensionDemuxer> mock_demuxer;
// In this test we don't care about what data is read.
ON_CALL(mock_demuxer, Read(_, _, _))
.WillByDefault(Invoke([](CobaltExtensionDemuxerStreamType type,
CobaltExtensionDemuxerReadCB read_cb,
void* read_cb_user_data) {
// Simulate EOS.
CobaltExtensionDemuxerBuffer eos_buffer = {};
eos_buffer.end_of_stream = true;
read_cb(&eos_buffer, read_cb_user_data);
}));
const CobaltExtensionDemuxerApi api = {
/*name=*/kCobaltExtensionDemuxerApi,
/*version=*/1,
/*CreateDemuxer=*/&MockDemuxerApi::CreateDemuxerImpl,
/*DestroyDemuxer=*/&MockDemuxerApi::DestroyDemuxerImpl,
};
auto c_demuxer =
std::unique_ptr<CobaltExtensionDemuxer>(CreateCDemuxer(&mock_demuxer));
EXPECT_CALL(*mock_demuxer_api, CreateDemuxer(_, _, _, _, _))
.WillOnce(Return(c_demuxer.get()));
EXPECT_CALL(*mock_demuxer_api, DestroyDemuxer(c_demuxer.get())).Times(1);
std::unique_ptr<DemuxerExtensionWrapper> demuxer_wrapper =
DemuxerExtensionWrapper::Create(
&data_source, base::SequencedTaskRunnerHandle::Get(), &api);
ASSERT_THAT(demuxer_wrapper, NotNull());
base::WaitableEvent init_done;
base::MockCallback<base::OnceCallback<void(::media::PipelineStatus)>>
initialize_cb;
EXPECT_CALL(mock_demuxer, Initialize())
.WillOnce(Return(kCobaltExtensionDemuxerOk));
// Simulate an audio file.
EXPECT_CALL(mock_demuxer, GetAudioConfig(NotNull()))
.WillOnce(Invoke([](CobaltExtensionDemuxerAudioDecoderConfig* config) {
config->codec = kCobaltExtensionDemuxerCodecAAC;
config->sample_format = kCobaltExtensionDemuxerSampleFormatF32;
config->channel_layout = kCobaltExtensionDemuxerChannelLayoutStereo;
config->encryption_scheme =
kCobaltExtensionDemuxerEncryptionSchemeUnencrypted;
config->samples_per_second = 44100;
config->extra_data = nullptr;
config->extra_data_size = 0;
return true;
}));
EXPECT_CALL(mock_demuxer, GetVideoConfig(NotNull())).WillOnce(Return(false));
EXPECT_CALL(initialize_cb, Run(::media::PIPELINE_OK))
.WillOnce(InvokeWithoutArgs([&init_done]() { init_done.Signal(); }));
demuxer_wrapper->Initialize(&mock_host, initialize_cb.Get());
EXPECT_TRUE(WaitForEvent(init_done));
}
TEST_F(DemuxerExtensionWrapperTest, ProvidesAudioAndVideoStreams) {
// This must outlive the DemuxerExtensionWrapper.
NiceMock<MockDemuxerHost> mock_host;
MockDataSource data_source;
MockDemuxerApi* mock_demuxer_api = GetMockDemuxerApi(); // Not owned.
NiceMock<MockCobaltExtensionDemuxer> mock_demuxer;
// In this test we don't care about what data is read.
ON_CALL(mock_demuxer, Read(_, _, _))
.WillByDefault(Invoke([](CobaltExtensionDemuxerStreamType type,
CobaltExtensionDemuxerReadCB read_cb,
void* read_cb_user_data) {
// Simulate EOS.
CobaltExtensionDemuxerBuffer eos_buffer = {};
eos_buffer.end_of_stream = true;
read_cb(&eos_buffer, read_cb_user_data);
}));
const CobaltExtensionDemuxerApi api = {
/*name=*/kCobaltExtensionDemuxerApi,
/*version=*/1,
/*CreateDemuxer=*/&MockDemuxerApi::CreateDemuxerImpl,
/*DestroyDemuxer=*/&MockDemuxerApi::DestroyDemuxerImpl,
};
auto c_demuxer =
std::unique_ptr<CobaltExtensionDemuxer>(CreateCDemuxer(&mock_demuxer));
EXPECT_CALL(*mock_demuxer_api, CreateDemuxer(_, _, _, _, _))
.WillOnce(Return(c_demuxer.get()));
EXPECT_CALL(*mock_demuxer_api, DestroyDemuxer(c_demuxer.get())).Times(1);
std::unique_ptr<DemuxerExtensionWrapper> demuxer_wrapper =
DemuxerExtensionWrapper::Create(
&data_source, base::SequencedTaskRunnerHandle::Get(), &api);
ASSERT_THAT(demuxer_wrapper, NotNull());
base::WaitableEvent init_done;
base::MockCallback<base::OnceCallback<void(::media::PipelineStatus)>>
initialize_cb;
EXPECT_CALL(mock_demuxer, Initialize())
.WillOnce(Return(kCobaltExtensionDemuxerOk));
// Simulate an audio+video file.
EXPECT_CALL(mock_demuxer, GetAudioConfig(NotNull()))
.WillOnce(Invoke([](CobaltExtensionDemuxerAudioDecoderConfig* config) {
config->codec = kCobaltExtensionDemuxerCodecAAC;
config->sample_format = kCobaltExtensionDemuxerSampleFormatF32;
config->channel_layout = kCobaltExtensionDemuxerChannelLayoutStereo;
config->encryption_scheme =
kCobaltExtensionDemuxerEncryptionSchemeUnencrypted;
config->samples_per_second = 44100;
config->extra_data = nullptr;
config->extra_data_size = 0;
return true;
}));
EXPECT_CALL(mock_demuxer, GetVideoConfig(NotNull()))
.WillOnce(Invoke([](CobaltExtensionDemuxerVideoDecoderConfig* config) {
config->codec = kCobaltExtensionDemuxerCodecH264;
config->profile = kCobaltExtensionDemuxerH264ProfileMain;
config->color_space_primaries = 1;
config->color_space_transfer = 1;
config->color_space_matrix = 1;
config->color_space_range_id =
kCobaltExtensionDemuxerColorSpaceRangeIdFull;
config->alpha_mode = kCobaltExtensionDemuxerHasAlpha;
config->coded_width = 1920;
config->coded_height = 1080;
config->visible_rect_x = 0;
config->visible_rect_y = 0;
config->visible_rect_width = 1920;
config->visible_rect_height = 1080;
config->natural_width = 1920;
config->natural_height = 1080;
config->encryption_scheme =
kCobaltExtensionDemuxerEncryptionSchemeUnencrypted;
config->extra_data = nullptr;
config->extra_data_size = 0;
return true;
}));
EXPECT_CALL(initialize_cb, Run(::media::PIPELINE_OK))
.WillOnce(InvokeWithoutArgs([&init_done]() { init_done.Signal(); }));
demuxer_wrapper->Initialize(&mock_host, initialize_cb.Get());
EXPECT_TRUE(WaitForEvent(init_done));
std::vector<::media::DemuxerStream*> streams =
demuxer_wrapper->GetAllStreams();
EXPECT_THAT(streams,
UnorderedElementsAre(
Pointee(TypeIs(::media::DemuxerStream::Type::AUDIO)),
Pointee(TypeIs(::media::DemuxerStream::Type::VIDEO))));
}
TEST_F(DemuxerExtensionWrapperTest, ReadsAudioData) {
// This must outlive the DemuxerExtensionWrapper.
NiceMock<MockDemuxerHost> mock_host;
MockDataSource data_source;
MockDemuxerApi* mock_demuxer_api = GetMockDemuxerApi(); // Not owned.
NiceMock<MockCobaltExtensionDemuxer> mock_demuxer;
const CobaltExtensionDemuxerApi api = {
/*name=*/kCobaltExtensionDemuxerApi,
/*version=*/1,
/*CreateDemuxer=*/&MockDemuxerApi::CreateDemuxerImpl,
/*DestroyDemuxer=*/&MockDemuxerApi::DestroyDemuxerImpl,
};
auto c_demuxer =
std::unique_ptr<CobaltExtensionDemuxer>(CreateCDemuxer(&mock_demuxer));
EXPECT_CALL(*mock_demuxer_api, CreateDemuxer(_, _, _, _, _))
.WillOnce(Return(c_demuxer.get()));
EXPECT_CALL(*mock_demuxer_api, DestroyDemuxer(c_demuxer.get())).Times(1);
std::unique_ptr<DemuxerExtensionWrapper> demuxer_wrapper =
DemuxerExtensionWrapper::Create(
&data_source, base::SequencedTaskRunnerHandle::Get(), &api);
ASSERT_THAT(demuxer_wrapper, NotNull());
base::WaitableEvent init_done;
base::MockCallback<base::OnceCallback<void(::media::PipelineStatus)>>
initialize_cb;
EXPECT_CALL(mock_demuxer, Initialize())
.WillOnce(Return(kCobaltExtensionDemuxerOk));
// Simulate an audio+video file.
EXPECT_CALL(mock_demuxer, GetAudioConfig(NotNull()))
.WillOnce(Invoke([](CobaltExtensionDemuxerAudioDecoderConfig* config) {
config->codec = kCobaltExtensionDemuxerCodecAAC;
config->sample_format = kCobaltExtensionDemuxerSampleFormatF32;
config->channel_layout = kCobaltExtensionDemuxerChannelLayoutStereo;
config->encryption_scheme =
kCobaltExtensionDemuxerEncryptionSchemeUnencrypted;
config->samples_per_second = 44100;
config->extra_data = nullptr;
config->extra_data_size = 0;
return true;
}));
EXPECT_CALL(mock_demuxer, GetVideoConfig(NotNull()))
.WillOnce(Invoke([](CobaltExtensionDemuxerVideoDecoderConfig* config) {
config->codec = kCobaltExtensionDemuxerCodecH264;
config->profile = kCobaltExtensionDemuxerH264ProfileMain;
config->color_space_primaries = 1;
config->color_space_transfer = 1;
config->color_space_matrix = 1;
config->color_space_range_id =
kCobaltExtensionDemuxerColorSpaceRangeIdFull;
config->alpha_mode = kCobaltExtensionDemuxerHasAlpha;
config->coded_width = 1920;
config->coded_height = 1080;
config->visible_rect_x = 0;
config->visible_rect_y = 0;
config->visible_rect_width = 1920;
config->visible_rect_height = 1080;
config->natural_width = 1920;
config->natural_height = 1080;
config->encryption_scheme =
kCobaltExtensionDemuxerEncryptionSchemeUnencrypted;
config->extra_data = nullptr;
config->extra_data_size = 0;
return true;
}));
EXPECT_CALL(initialize_cb, Run(::media::PIPELINE_OK))
.WillOnce(InvokeWithoutArgs([&init_done]() { init_done.Signal(); }));
std::vector<uint8_t> buffer_data = {1, 2, 3, 4, 5};
EXPECT_CALL(mock_demuxer, Read(kCobaltExtensionDemuxerStreamTypeAudio, _, _))
.WillOnce(Invoke([&buffer_data](CobaltExtensionDemuxerStreamType type,
CobaltExtensionDemuxerReadCB read_cb,
void* read_cb_user_data) {
// Send one "real" buffer.
CobaltExtensionDemuxerBuffer buffer = {};
buffer.data = buffer_data.data();
buffer.data_size = buffer_data.size();
buffer.side_data = nullptr;
buffer.side_data_elements = 0;
buffer.pts = 0;
buffer.duration = 1000;
buffer.is_keyframe = true;
buffer.end_of_stream = false;
read_cb(&buffer, read_cb_user_data);
}))
.WillOnce(Invoke([](CobaltExtensionDemuxerStreamType type,
CobaltExtensionDemuxerReadCB read_cb,
void* read_cb_user_data) {
// Simulate the audio stream being done.
CobaltExtensionDemuxerBuffer eos_buffer = {};
eos_buffer.end_of_stream = true;
read_cb(&eos_buffer, read_cb_user_data);
}));
// The impl may or may not try reading video data. If it does, return EOS.
EXPECT_CALL(mock_demuxer, Read(kCobaltExtensionDemuxerStreamTypeVideo, _, _))
.Times(AtMost(1))
.WillOnce(Invoke([](CobaltExtensionDemuxerStreamType type,
CobaltExtensionDemuxerReadCB read_cb,
void* read_cb_user_data) {
// Simulate the video stream being done.
CobaltExtensionDemuxerBuffer eos_buffer = {};
eos_buffer.end_of_stream = true;
read_cb(&eos_buffer, read_cb_user_data);
}));
demuxer_wrapper->Initialize(&mock_host, initialize_cb.Get());
EXPECT_TRUE(WaitForEvent(init_done));
std::vector<::media::DemuxerStream*> streams =
demuxer_wrapper->GetAllStreams();
ASSERT_THAT(streams,
UnorderedElementsAre(
Pointee(TypeIs(::media::DemuxerStream::Type::AUDIO)),
Pointee(TypeIs(::media::DemuxerStream::Type::VIDEO))));
::media::DemuxerStream* audio_stream =
streams[0]->type() == ::media::DemuxerStream::Type::AUDIO ? streams[0]
: streams[1];
base::MockCallback<base::OnceCallback<void(
::media::DemuxerStream::Status,
const std::vector<scoped_refptr<::media::DecoderBuffer>>&)>>
read_cb;
base::WaitableEvent read_done;
std::vector<std::vector<uint8_t>> buffers;
buffers.push_back(buffer_data);
EXPECT_CALL(read_cb, Run(::media::DemuxerStream::kOk,
ElementsAre(Pointee(BufferHasData(buffer_data)))))
.WillOnce(InvokeWithoutArgs([&read_done]() { read_done.Signal(); }));
audio_stream->Read(1, read_cb.Get());
EXPECT_TRUE(WaitForEvent(read_done));
}
TEST_F(DemuxerExtensionWrapperTest, ReadsVideoData) {
// This must outlive the DemuxerExtensionWrapper.
NiceMock<MockDemuxerHost> mock_host;
MockDataSource data_source;
MockDemuxerApi* mock_demuxer_api = GetMockDemuxerApi(); // Not owned.
NiceMock<MockCobaltExtensionDemuxer> mock_demuxer;
const CobaltExtensionDemuxerApi api = {
/*name=*/kCobaltExtensionDemuxerApi,
/*version=*/1,
/*CreateDemuxer=*/&MockDemuxerApi::CreateDemuxerImpl,
/*DestroyDemuxer=*/&MockDemuxerApi::DestroyDemuxerImpl,
};
auto c_demuxer =
std::unique_ptr<CobaltExtensionDemuxer>(CreateCDemuxer(&mock_demuxer));
EXPECT_CALL(*mock_demuxer_api, CreateDemuxer(_, _, _, _, _))
.WillOnce(Return(c_demuxer.get()));
EXPECT_CALL(*mock_demuxer_api, DestroyDemuxer(c_demuxer.get())).Times(1);
std::unique_ptr<DemuxerExtensionWrapper> demuxer_wrapper =
DemuxerExtensionWrapper::Create(
&data_source, base::SequencedTaskRunnerHandle::Get(), &api);
ASSERT_THAT(demuxer_wrapper, NotNull());
base::WaitableEvent init_done;
base::MockCallback<base::OnceCallback<void(::media::PipelineStatus)>>
initialize_cb;
EXPECT_CALL(mock_demuxer, Initialize())
.WillOnce(Return(kCobaltExtensionDemuxerOk));
// Simulate an audio+video file.
EXPECT_CALL(mock_demuxer, GetAudioConfig(NotNull()))
.WillOnce(Invoke([](CobaltExtensionDemuxerAudioDecoderConfig* config) {
config->codec = kCobaltExtensionDemuxerCodecAAC;
config->sample_format = kCobaltExtensionDemuxerSampleFormatF32;
config->channel_layout = kCobaltExtensionDemuxerChannelLayoutStereo;
config->encryption_scheme =
kCobaltExtensionDemuxerEncryptionSchemeUnencrypted;
config->samples_per_second = 44100;
config->extra_data = nullptr;
config->extra_data_size = 0;
return true;
}));
EXPECT_CALL(mock_demuxer, GetVideoConfig(NotNull()))
.WillOnce(Invoke([](CobaltExtensionDemuxerVideoDecoderConfig* config) {
config->codec = kCobaltExtensionDemuxerCodecH264;
config->profile = kCobaltExtensionDemuxerH264ProfileMain;
config->color_space_primaries = 1;
config->color_space_transfer = 1;
config->color_space_matrix = 1;
config->color_space_range_id =
kCobaltExtensionDemuxerColorSpaceRangeIdFull;
config->alpha_mode = kCobaltExtensionDemuxerHasAlpha;
config->coded_width = 1920;
config->coded_height = 1080;
config->visible_rect_x = 0;
config->visible_rect_y = 0;
config->visible_rect_width = 1920;
config->visible_rect_height = 1080;
config->natural_width = 1920;
config->natural_height = 1080;
config->encryption_scheme =
kCobaltExtensionDemuxerEncryptionSchemeUnencrypted;
config->extra_data = nullptr;
config->extra_data_size = 0;
return true;
}));
EXPECT_CALL(initialize_cb, Run(::media::PIPELINE_OK))
.WillOnce(InvokeWithoutArgs([&init_done]() { init_done.Signal(); }));
std::vector<uint8_t> buffer_data = {1, 2, 3, 4, 5};
EXPECT_CALL(mock_demuxer, Read(kCobaltExtensionDemuxerStreamTypeVideo, _, _))
.WillOnce(Invoke([&buffer_data](CobaltExtensionDemuxerStreamType type,
CobaltExtensionDemuxerReadCB read_cb,
void* read_cb_user_data) {
// Send one "real" buffer.
CobaltExtensionDemuxerBuffer buffer = {};
buffer.data = buffer_data.data();
buffer.data_size = buffer_data.size();
buffer.side_data = nullptr;
buffer.side_data_elements = 0;
buffer.pts = 0;
buffer.duration = 1000;
buffer.is_keyframe = true;
buffer.end_of_stream = false;
read_cb(&buffer, read_cb_user_data);
}))
.WillOnce(Invoke([](CobaltExtensionDemuxerStreamType type,
CobaltExtensionDemuxerReadCB read_cb,
void* read_cb_user_data) {
// Simulate the video stream being done.
CobaltExtensionDemuxerBuffer eos_buffer = {};
eos_buffer.end_of_stream = true;
read_cb(&eos_buffer, read_cb_user_data);
}));
// The impl may or may not try reading audio data. If it does, return EOS.
EXPECT_CALL(mock_demuxer, Read(kCobaltExtensionDemuxerStreamTypeAudio, _, _))
.Times(AtMost(1))
.WillOnce(Invoke([](CobaltExtensionDemuxerStreamType type,
CobaltExtensionDemuxerReadCB read_cb,
void* read_cb_user_data) {
// Simulate the audio stream being done.
CobaltExtensionDemuxerBuffer eos_buffer = {};
eos_buffer.end_of_stream = true;
read_cb(&eos_buffer, read_cb_user_data);
}));
demuxer_wrapper->Initialize(&mock_host, initialize_cb.Get());
EXPECT_TRUE(WaitForEvent(init_done));
std::vector<::media::DemuxerStream*> streams =
demuxer_wrapper->GetAllStreams();
ASSERT_THAT(streams,
UnorderedElementsAre(
Pointee(TypeIs(::media::DemuxerStream::Type::AUDIO)),
Pointee(TypeIs(::media::DemuxerStream::Type::VIDEO))));
::media::DemuxerStream* video_stream =
streams[0]->type() == ::media::DemuxerStream::Type::VIDEO ? streams[0]
: streams[1];
base::MockCallback<base::OnceCallback<void(
::media::DemuxerStream::Status,
const std::vector<scoped_refptr<::media::DecoderBuffer>>&)>>
read_cb;
base::WaitableEvent read_done;
std::vector<std::vector<uint8_t>> buffers;
buffers.push_back(buffer_data);
EXPECT_CALL(read_cb, Run(::media::DemuxerStream::kOk,
ElementsAre(Pointee(BufferHasData(buffer_data)))))
.WillOnce(InvokeWithoutArgs([&read_done]() { read_done.Signal(); }));
video_stream->Read(1, read_cb.Get());
EXPECT_TRUE(WaitForEvent(read_done));
}
} // namespace
} // namespace media
} // namespace cobalt