blob: 175d05782134076c85a7368fa093d934c865129c [file] [log] [blame]
// Copyright 2020 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 "starboard/nplb/player_test_util.h"
#include <functional>
#include "starboard/audio_sink.h"
#include "starboard/common/atomic.h"
#include "starboard/common/string.h"
#include "starboard/extension/enhanced_audio.h"
#include "starboard/nplb/drm_helpers.h"
#include "starboard/nplb/maximum_player_configuration_explorer.h"
#include "starboard/nplb/player_creation_param_helpers.h"
#include "starboard/shared/starboard/player/video_dmp_reader.h"
#include "starboard/testing/fake_graphics_context_provider.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace starboard {
namespace nplb {
namespace {
using shared::starboard::media::AudioSampleInfo;
using shared::starboard::media::VideoSampleInfo;
using shared::starboard::player::video_dmp::VideoDmpReader;
using std::placeholders::_1;
using std::placeholders::_2;
using std::placeholders::_3;
using std::placeholders::_4;
using testing::FakeGraphicsContextProvider;
const char* kAudioTestFiles[] = {
"beneath_the_canopy_aac_stereo.dmp",
"beneath_the_canopy_opus_stereo.dmp",
"sintel_329_ec3.dmp",
"sintel_381_ac3.dmp",
};
// For uncommon audio formats, we add audio only tests, without tests combined
// with a video stream, to shorten the overall test time.
const char* kAudioOnlyTestFiles[] = {
"beneath_the_canopy_aac_5_1.dmp",
"beneath_the_canopy_aac_mono.dmp",
"beneath_the_canopy_opus_5_1.dmp",
"beneath_the_canopy_opus_mono.dmp",
"heaac.dmp",
#if SB_API_VERSION >= 14
"sintel_5s_flac.dmp",
"sintel_5s_mp3.dmp",
"sintel_5s_pcm_s16le.dmp",
#endif // SB_API_VERSION >= 14
"sintel_5s_vorbis.dmp",
};
const char* kVideoTestFiles[] = {
"beneath_the_canopy_137_avc.dmp",
"beneath_the_canopy_248_vp9.dmp",
"sintel_399_av1.dmp",
"sintel_5s_vp8.dmp",
"sintel_5s_hevc.dmp",
};
const SbPlayerOutputMode kOutputModes[] = {kSbPlayerOutputModeDecodeToTexture,
kSbPlayerOutputModePunchOut};
void ErrorFunc(SbPlayer player,
void* context,
SbPlayerError error,
const char* message) {
atomic_bool* error_occurred = static_cast<atomic_bool*>(context);
error_occurred->exchange(true);
}
} // namespace
std::vector<const char*> GetAudioTestFiles() {
return std::vector<const char*>(std::begin(kAudioTestFiles),
std::end(kAudioTestFiles));
}
std::vector<const char*> GetVideoTestFiles() {
return std::vector<const char*>(std::begin(kVideoTestFiles),
std::end(kVideoTestFiles));
}
std::vector<SbPlayerOutputMode> GetPlayerOutputModes() {
return std::vector<SbPlayerOutputMode>(std::begin(kOutputModes),
std::end(kOutputModes));
}
std::vector<const char*> GetKeySystems() {
std::vector<const char*> key_systems;
key_systems.push_back("");
key_systems.insert(key_systems.end(), kKeySystems,
kKeySystems + SB_ARRAY_SIZE_INT(kKeySystems));
return key_systems;
}
std::vector<SbPlayerTestConfig> GetSupportedSbPlayerTestConfigs(
const char* key_system) {
SB_DCHECK(key_system);
const char* kEmptyName = NULL;
std::vector<const char*> supported_audio_files;
supported_audio_files.push_back(kEmptyName);
for (auto audio_filename : kAudioTestFiles) {
VideoDmpReader dmp_reader(audio_filename,
VideoDmpReader::kEnableReadOnDemand);
SB_DCHECK(dmp_reader.number_of_audio_buffers() > 0);
if (SbMediaCanPlayMimeAndKeySystem(dmp_reader.audio_mime_type().c_str(),
key_system)) {
supported_audio_files.push_back(audio_filename);
}
}
std::vector<const char*> supported_video_files;
supported_video_files.push_back(kEmptyName);
for (auto video_filename : kVideoTestFiles) {
VideoDmpReader dmp_reader(video_filename,
VideoDmpReader::kEnableReadOnDemand);
SB_DCHECK(dmp_reader.number_of_video_buffers() > 0);
if (SbMediaCanPlayMimeAndKeySystem(dmp_reader.video_mime_type().c_str(),
key_system)) {
supported_video_files.push_back(video_filename);
}
}
std::vector<SbPlayerTestConfig> test_configs;
for (auto audio_filename : supported_audio_files) {
SbMediaAudioCodec audio_codec = kSbMediaAudioCodecNone;
if (audio_filename) {
VideoDmpReader audio_dmp_reader(audio_filename,
VideoDmpReader::kEnableReadOnDemand);
audio_codec = audio_dmp_reader.audio_codec();
}
for (auto video_filename : supported_video_files) {
SbMediaVideoCodec video_codec = kSbMediaVideoCodecNone;
if (video_filename) {
VideoDmpReader video_dmp_reader(video_filename,
VideoDmpReader::kEnableReadOnDemand);
video_codec = video_dmp_reader.video_codec();
}
if (audio_codec == kSbMediaAudioCodecNone &&
video_codec == kSbMediaVideoCodecNone) {
continue;
}
for (auto output_mode : kOutputModes) {
if (IsOutputModeSupported(output_mode, audio_codec, video_codec,
key_system)) {
test_configs.emplace_back(audio_filename, video_filename, output_mode,
key_system);
}
}
}
}
for (auto audio_filename : kAudioOnlyTestFiles) {
VideoDmpReader dmp_reader(audio_filename,
VideoDmpReader::kEnableReadOnDemand);
SB_DCHECK(dmp_reader.number_of_audio_buffers() > 0);
if (SbMediaCanPlayMimeAndKeySystem(dmp_reader.audio_mime_type().c_str(),
key_system)) {
for (auto output_mode : kOutputModes) {
if (IsOutputModeSupported(output_mode, dmp_reader.audio_codec(),
kSbMediaVideoCodecNone, key_system)) {
test_configs.emplace_back(audio_filename, kEmptyName, output_mode,
key_system);
}
}
}
}
return test_configs;
}
std::string GetSbPlayerTestConfigName(
::testing::TestParamInfo<SbPlayerTestConfig> info) {
const SbPlayerTestConfig& config = info.param;
const char* audio_filename = config.audio_filename;
const char* video_filename = config.video_filename;
const SbPlayerOutputMode output_mode = config.output_mode;
const char* key_system = config.key_system;
std::string name(FormatString(
"audio_%s_video_%s_output_%s_key_system_%s",
audio_filename && strlen(audio_filename) > 0 ? audio_filename : "null",
video_filename && strlen(video_filename) > 0 ? video_filename : "null",
output_mode == kSbPlayerOutputModeDecodeToTexture ? "decode_to_texture"
: "punch_out",
strlen(key_system) > 0 ? key_system : "null"));
std::replace(name.begin(), name.end(), '.', '_');
std::replace(name.begin(), name.end(), '(', '_');
std::replace(name.begin(), name.end(), ')', '_');
return name;
}
void DummyDeallocateSampleFunc(SbPlayer player,
void* context,
const void* sample_buffer) {}
void DummyDecoderStatusFunc(SbPlayer player,
void* context,
SbMediaType type,
SbPlayerDecoderState state,
int ticket) {}
void DummyPlayerStatusFunc(SbPlayer player,
void* context,
SbPlayerState state,
int ticket) {}
void DummyErrorFunc(SbPlayer player,
void* context,
SbPlayerError error,
const char* message) {}
SbPlayer CallSbPlayerCreate(
SbWindow window,
SbMediaVideoCodec video_codec,
SbMediaAudioCodec audio_codec,
SbDrmSystem drm_system,
const shared::starboard::media::AudioStreamInfo* audio_stream_info,
const char* max_video_capabilities,
SbPlayerDeallocateSampleFunc sample_deallocate_func,
SbPlayerDecoderStatusFunc decoder_status_func,
SbPlayerStatusFunc player_status_func,
SbPlayerErrorFunc player_error_func,
void* context,
SbPlayerOutputMode output_mode,
SbDecodeTargetGraphicsContextProvider* context_provider) {
if (audio_stream_info) {
SB_CHECK(audio_stream_info->codec == audio_codec);
} else {
SB_CHECK(audio_codec == kSbMediaAudioCodecNone);
}
// TODO: pass real audio/video info to SbPlayerGetPreferredOutputMode.
PlayerCreationParam creation_param =
CreatePlayerCreationParam(audio_codec, video_codec, output_mode);
if (audio_stream_info) {
creation_param.audio_stream_info = *audio_stream_info;
}
creation_param.drm_system = drm_system;
creation_param.video_stream_info.max_video_capabilities =
max_video_capabilities;
SbPlayerCreationParam param = {};
creation_param.ConvertTo(&param);
return SbPlayerCreate(window, &param, sample_deallocate_func,
decoder_status_func, player_status_func,
player_error_func, context, context_provider);
}
void CallSbPlayerWriteSamples(
SbPlayer player,
SbMediaType sample_type,
shared::starboard::player::video_dmp::VideoDmpReader* dmp_reader,
int start_index,
int number_of_samples_to_write,
int64_t timestamp_offset,
const std::vector<int64_t>& discarded_durations_from_front,
const std::vector<int64_t>& discarded_durations_from_back) {
SB_DCHECK(start_index >= 0);
SB_DCHECK(number_of_samples_to_write > 0);
if (sample_type == kSbMediaTypeAudio) {
SB_DCHECK(discarded_durations_from_front.empty() ||
discarded_durations_from_front.size() ==
number_of_samples_to_write);
SB_DCHECK(discarded_durations_from_front.size() ==
discarded_durations_from_back.size());
} else {
SB_DCHECK(sample_type == kSbMediaTypeVideo);
SB_DCHECK(discarded_durations_from_front.empty());
SB_DCHECK(discarded_durations_from_back.empty());
}
static auto const* enhanced_audio_extension =
static_cast<const CobaltExtensionEnhancedAudioApi*>(
SbSystemGetExtension(kCobaltExtensionEnhancedAudioName));
#if SB_API_VERSION >= 15
ASSERT_FALSE(enhanced_audio_extension);
#endif // SB_API_VERSION >= 15
if (enhanced_audio_extension) {
ASSERT_STREQ(enhanced_audio_extension->name,
kCobaltExtensionEnhancedAudioName);
ASSERT_EQ(enhanced_audio_extension->version, 1u);
std::vector<CobaltExtensionEnhancedAudioPlayerSampleInfo> sample_infos;
// We have to hold all intermediate sample infos to ensure that their member
// variables with allocated memory (like `std::string mime`) won't go out of
// scope before the call to `enhanced_audio_extension->PlayerWriteSamples`.
std::vector<AudioSampleInfo> audio_sample_infos;
std::vector<VideoSampleInfo> video_sample_infos;
for (int i = 0; i < number_of_samples_to_write; ++i) {
SbPlayerSampleInfo source =
dmp_reader->GetPlayerSampleInfo(sample_type, start_index++);
sample_infos.resize(sample_infos.size() + 1);
sample_infos.back().type = source.type;
sample_infos.back().buffer = source.buffer;
sample_infos.back().buffer_size = source.buffer_size;
sample_infos.back().timestamp = source.timestamp + timestamp_offset;
sample_infos.back().side_data = source.side_data;
sample_infos.back().side_data_count = source.side_data_count;
sample_infos.back().drm_info = source.drm_info;
if (sample_type == kSbMediaTypeAudio) {
audio_sample_infos.emplace_back(source.audio_sample_info);
audio_sample_infos.back().ConvertTo(
&sample_infos.back().audio_sample_info);
if (!discarded_durations_from_front.empty()) {
sample_infos.back().audio_sample_info.discarded_duration_from_front =
discarded_durations_from_front[i];
}
if (!discarded_durations_from_back.empty()) {
sample_infos.back().audio_sample_info.discarded_duration_from_back =
discarded_durations_from_back[i];
}
} else {
video_sample_infos.emplace_back(source.video_sample_info);
video_sample_infos.back().ConvertTo(
&sample_infos.back().video_sample_info);
}
}
enhanced_audio_extension->PlayerWriteSamples(
player, sample_type, sample_infos.data(), number_of_samples_to_write);
return;
}
std::vector<SbPlayerSampleInfo> sample_infos;
for (int i = 0; i < number_of_samples_to_write; ++i) {
sample_infos.push_back(
dmp_reader->GetPlayerSampleInfo(sample_type, start_index++));
sample_infos.back().timestamp += timestamp_offset;
#if SB_API_VERSION >= 15
if (!discarded_durations_from_front.empty()) {
sample_infos.back().audio_sample_info.discarded_duration_from_front =
discarded_durations_from_front[i];
}
if (!discarded_durations_from_back.empty()) {
sample_infos.back().audio_sample_info.discarded_duration_from_back =
discarded_durations_from_back[i];
}
#endif // SB_API_VERSION >= 15
}
#if SB_API_VERSION >= 15
SbPlayerWriteSamples(player, sample_type, sample_infos.data(),
number_of_samples_to_write);
#else // SB_API_VERSION >= 15
SbPlayerWriteSample2(player, sample_type, sample_infos.data(),
number_of_samples_to_write);
#endif // SB_API_VERSION >= 15
}
bool IsOutputModeSupported(SbPlayerOutputMode output_mode,
SbMediaAudioCodec audio_codec,
SbMediaVideoCodec video_codec,
const char* key_system) {
SB_DCHECK(key_system);
// TODO: pass real audio/video info to SbPlayerGetPreferredOutputMode.
PlayerCreationParam creation_param =
CreatePlayerCreationParam(audio_codec, video_codec, output_mode);
SbPlayerCreationParam param = {};
creation_param.ConvertTo(&param);
if (strlen(key_system) > 0) {
param.drm_system = SbDrmCreateSystem(
key_system, NULL /* context */, DummySessionUpdateRequestFunc,
DummySessionUpdatedFunc, DummySessionKeyStatusesChangedFunc,
DummyServerCertificateUpdatedFunc, DummySessionClosedFunc);
if (!SbDrmSystemIsValid(param.drm_system)) {
return false;
}
}
bool supported = SbPlayerGetPreferredOutputMode(&param) == output_mode;
if (SbDrmSystemIsValid(param.drm_system)) {
SbDrmDestroySystem(param.drm_system);
}
return supported;
}
bool IsPartialAudioSupported() {
#if SB_API_VERSION >= 15
return true;
#else // SB_API_VERSION >= 15
return SbSystemGetExtension(kCobaltExtensionEnhancedAudioName) != nullptr;
#endif // SB_API_VERSION >= 15
}
} // namespace nplb
} // namespace starboard