blob: a3c0d505d4b99c4216a24cd983722c5a27e7738e [file] [log] [blame]
// Copyright 2016 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/shared/starboard/player/filter/filter_based_player_worker_handler.h"
#include "starboard/audio_sink.h"
#include "starboard/common/log.h"
#include "starboard/common/murmurhash2.h"
#include "starboard/common/string.h"
#include "starboard/memory.h"
#include "starboard/shared/starboard/application.h"
#include "starboard/shared/starboard/drm/drm_system_internal.h"
#include "starboard/shared/starboard/player/filter/audio_decoder_internal.h"
#include "starboard/shared/starboard/player/filter/video_decoder_internal.h"
#include "starboard/shared/starboard/player/input_buffer_internal.h"
#include "starboard/time.h"
namespace starboard {
namespace shared {
namespace starboard {
namespace player {
namespace filter {
namespace {
using std::placeholders::_1;
using std::placeholders::_2;
// TODO: Make this configurable inside SbPlayerCreate().
const SbTimeMonotonic kUpdateInterval = 200 * kSbTimeMillisecond;
#if defined(COBALT_BUILD_TYPE_GOLD)
void DumpInputHash(const InputBuffer* input_buffer) {}
#else // defined(COBALT_BUILD_TYPE_GOLD)
void DumpInputHash(const InputBuffer* input_buffer) {
static const bool s_dump_input_hash =
Application::Get()->GetCommandLine()->HasSwitch("dump_video_input_hash");
if (!s_dump_input_hash) {
return;
}
bool is_audio = input_buffer->sample_type() == kSbMediaTypeAudio;
SB_LOG(ERROR) << "Dump "
<< (input_buffer->drm_info() ? "encrypted " : "clear ")
<< (is_audio ? "audio input hash @ " : "video input hash @ ")
<< input_buffer->timestamp() << ": "
<< MurmurHash2_32(input_buffer->data(), input_buffer->size(),
0);
}
#endif // defined(COBALT_BUILD_TYPE_GOLD)
} // namespace
#if SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT)
FilterBasedPlayerWorkerHandler::FilterBasedPlayerWorkerHandler(
const SbPlayerCreationParam* creation_param,
SbDecodeTargetGraphicsContextProvider* provider)
: JobOwner(kDetached),
video_codec_(creation_param->video_sample_info.codec),
audio_codec_(creation_param->audio_sample_info.codec),
drm_system_(creation_param->drm_system),
audio_sample_info_(creation_param->audio_sample_info),
output_mode_(creation_param->output_mode),
decode_target_graphics_context_provider_(provider),
video_sample_info_(creation_param->video_sample_info) {
update_job_ = std::bind(&FilterBasedPlayerWorkerHandler::Update, this);
}
#else // SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT)
FilterBasedPlayerWorkerHandler::FilterBasedPlayerWorkerHandler(
SbMediaVideoCodec video_codec,
SbMediaAudioCodec audio_codec,
SbDrmSystem drm_system,
const SbMediaAudioSampleInfo* audio_sample_info,
SbPlayerOutputMode output_mode,
SbDecodeTargetGraphicsContextProvider* provider)
: JobOwner(kDetached),
video_codec_(video_codec),
audio_codec_(audio_codec),
drm_system_(drm_system),
output_mode_(output_mode),
decode_target_graphics_context_provider_(provider) {
if (audio_sample_info) {
audio_sample_info_ = *audio_sample_info;
}
update_job_ = std::bind(&FilterBasedPlayerWorkerHandler::Update, this);
}
#endif // SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT)
bool FilterBasedPlayerWorkerHandler::Init(
SbPlayer player,
UpdateMediaInfoCB update_media_info_cb,
GetPlayerStateCB get_player_state_cb,
UpdatePlayerStateCB update_player_state_cb,
UpdatePlayerErrorCB update_player_error_cb,
std::string* error_message) {
// This function should only be called once.
SB_DCHECK(update_media_info_cb_ == NULL);
// All parameters have to be valid.
SB_DCHECK(SbPlayerIsValid(player));
SB_DCHECK(update_media_info_cb);
SB_DCHECK(get_player_state_cb);
SB_DCHECK(update_player_state_cb);
SB_DCHECK(error_message);
AttachToCurrentThread();
player_ = player;
update_media_info_cb_ = update_media_info_cb;
get_player_state_cb_ = get_player_state_cb;
update_player_state_cb_ = update_player_state_cb;
update_player_error_cb_ = update_player_error_cb;
scoped_ptr<PlayerComponents::Factory> factory =
PlayerComponents::Factory::Create();
SB_DCHECK(factory);
if (audio_codec_ != kSbMediaAudioCodecNone) {
// TODO: This is not ideal as we should really handle the creation failure
// of audio sink inside the audio renderer to give the renderer a chance to
// resample the decoded audio.
const int required_audio_channels = audio_sample_info_.number_of_channels;
const int supported_audio_channels = SbAudioSinkGetMaxChannels();
if (required_audio_channels > supported_audio_channels) {
SB_LOG(ERROR) << "Audio channels requested " << required_audio_channels
<< ", but currently supported less than or equal to "
<< supported_audio_channels;
*error_message =
FormatString("Required channel %d is greater than maximum channel %d",
required_audio_channels, supported_audio_channels);
return false;
}
}
PlayerComponents::Factory::CreationParameters creation_parameters(
audio_codec_, audio_sample_info_, video_codec_,
#if SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT)
video_sample_info_,
#endif // SB_HAS(PLAYER_CREATION_AND_OUTPUT_MODE_QUERY_IMPROVEMENT)
player_, output_mode_, decode_target_graphics_context_provider_,
drm_system_);
{
::starboard::ScopedLock lock(player_components_existence_mutex_);
player_components_ =
factory->CreateComponents(creation_parameters, error_message);
if (!player_components_) {
SB_LOG(ERROR) << "Failed to create renderer with error: "
<< *error_message;
return false;
}
media_time_provider_ = player_components_->GetMediaTimeProvider();
audio_renderer_ = player_components_->GetAudioRenderer();
video_renderer_ = player_components_->GetVideoRenderer();
}
if (audio_codec_ != kSbMediaAudioCodecNone) {
SB_DCHECK(audio_renderer_);
}
if (video_codec_ != kSbMediaVideoCodecNone) {
SB_DCHECK(video_renderer_);
}
SB_DCHECK(media_time_provider_);
if (audio_renderer_) {
SB_LOG(INFO) << "Initialize audio renderer with volume " << volume_;
audio_renderer_->Initialize(
std::bind(&FilterBasedPlayerWorkerHandler::OnError, this, _1, _2),
std::bind(&FilterBasedPlayerWorkerHandler::OnPrerolled, this,
kSbMediaTypeAudio),
std::bind(&FilterBasedPlayerWorkerHandler::OnEnded, this,
kSbMediaTypeAudio));
audio_renderer_->SetVolume(volume_);
}
SB_LOG(INFO) << "Set playback rate to " << playback_rate_;
media_time_provider_->SetPlaybackRate(playback_rate_);
if (video_renderer_) {
SB_LOG(INFO) << "Initialize video renderer.";
video_renderer_->Initialize(
std::bind(&FilterBasedPlayerWorkerHandler::OnError, this, _1, _2),
std::bind(&FilterBasedPlayerWorkerHandler::OnPrerolled, this,
kSbMediaTypeVideo),
std::bind(&FilterBasedPlayerWorkerHandler::OnEnded, this,
kSbMediaTypeVideo));
}
update_job_token_ = Schedule(update_job_, kUpdateInterval);
return true;
}
bool FilterBasedPlayerWorkerHandler::Seek(SbTime seek_to_time, int ticket) {
SB_DCHECK(BelongsToCurrentThread());
SB_LOG(INFO) << "Seek to " << seek_to_time << ", and media time provider is "
<< media_time_provider_;
if (!media_time_provider_) {
return false;
}
if (seek_to_time < 0) {
SB_DLOG(ERROR) << "Try to seek to negative timestamp " << seek_to_time;
seek_to_time = 0;
}
media_time_provider_->Pause();
if (video_renderer_) {
video_renderer_->Seek(seek_to_time);
}
media_time_provider_->Seek(seek_to_time);
audio_prerolled_ = false;
video_prerolled_ = false;
return true;
}
bool FilterBasedPlayerWorkerHandler::WriteSample(
const scoped_refptr<InputBuffer>& input_buffer,
bool* written) {
SB_DCHECK(input_buffer);
SB_DCHECK(BelongsToCurrentThread());
SB_DCHECK(written != NULL);
if (input_buffer->sample_type() == kSbMediaTypeAudio) {
if (!audio_renderer_) {
return false;
}
*written = true;
if (audio_renderer_->IsEndOfStreamWritten()) {
SB_LOG(WARNING) << "Try to write audio sample after EOS is reached";
} else {
if (!audio_renderer_->CanAcceptMoreData()) {
*written = false;
return true;
}
if (input_buffer->drm_info()) {
if (!SbDrmSystemIsValid(drm_system_)) {
return false;
}
DumpInputHash(input_buffer);
SbDrmSystemPrivate::DecryptStatus decrypt_status =
drm_system_->Decrypt(input_buffer);
if (decrypt_status == SbDrmSystemPrivate::kRetry) {
*written = false;
return true;
}
if (decrypt_status == SbDrmSystemPrivate::kFailure) {
*written = false;
return false;
}
}
DumpInputHash(input_buffer);
audio_renderer_->WriteSample(input_buffer);
}
} else {
SB_DCHECK(input_buffer->sample_type() == kSbMediaTypeVideo);
if (!video_renderer_) {
return false;
}
*written = true;
if (video_renderer_->IsEndOfStreamWritten()) {
SB_LOG(WARNING) << "Try to write video sample after EOS is reached";
} else {
if (!video_renderer_->CanAcceptMoreData()) {
*written = false;
return true;
}
if (input_buffer->drm_info()) {
if (!SbDrmSystemIsValid(drm_system_)) {
return false;
}
DumpInputHash(input_buffer);
SbDrmSystemPrivate::DecryptStatus decrypt_status =
drm_system_->Decrypt(input_buffer);
if (decrypt_status == SbDrmSystemPrivate::kRetry) {
*written = false;
return true;
}
if (decrypt_status == SbDrmSystemPrivate::kFailure) {
*written = false;
return false;
}
}
DumpInputHash(input_buffer);
video_renderer_->WriteSample(input_buffer);
}
}
return true;
}
bool FilterBasedPlayerWorkerHandler::WriteEndOfStream(SbMediaType sample_type) {
SB_DCHECK(BelongsToCurrentThread());
if (sample_type == kSbMediaTypeAudio) {
if (!audio_renderer_) {
SB_LOG(INFO) << "Audio EOS enqueued when renderer is NULL.";
return false;
}
if (audio_renderer_->IsEndOfStreamWritten()) {
SB_LOG(WARNING) << "Try to write audio EOS after EOS is enqueued";
} else {
SB_LOG(INFO) << "Audio EOS enqueued";
audio_renderer_->WriteEndOfStream();
}
} else {
if (!video_renderer_) {
SB_LOG(INFO) << "Video EOS enqueued when renderer is NULL.";
return false;
}
if (video_renderer_->IsEndOfStreamWritten()) {
SB_LOG(WARNING) << "Try to write video EOS after EOS is enqueued";
} else {
SB_LOG(INFO) << "Video EOS enqueued";
video_renderer_->WriteEndOfStream();
}
}
return true;
}
bool FilterBasedPlayerWorkerHandler::SetPause(bool pause) {
SB_DCHECK(BelongsToCurrentThread());
SB_LOG(INFO) << "Set pause from " << paused_ << " to " << pause
<< ", and media time provider is " << media_time_provider_;
if (!media_time_provider_) {
return false;
}
paused_ = pause;
if (pause) {
media_time_provider_->Pause();
} else {
media_time_provider_->Play();
}
return true;
}
bool FilterBasedPlayerWorkerHandler::SetPlaybackRate(double playback_rate) {
SB_DCHECK(BelongsToCurrentThread());
SB_LOG(INFO) << "Set playback rate from " << playback_rate_ << " to "
<< playback_rate << ", and media time provider is "
<< media_time_provider_;
playback_rate_ = playback_rate;
if (!media_time_provider_) {
return false;
}
media_time_provider_->SetPlaybackRate(playback_rate_);
return true;
}
void FilterBasedPlayerWorkerHandler::SetVolume(double volume) {
SB_DCHECK(BelongsToCurrentThread());
SB_LOG(INFO) << "Set volume from " << volume_ << " to " << volume
<< ", and audio renderer is " << audio_renderer_;
volume_ = volume;
if (audio_renderer_) {
audio_renderer_->SetVolume(volume_);
}
}
bool FilterBasedPlayerWorkerHandler::SetBounds(
const PlayerWorker::Bounds& bounds) {
SB_DCHECK(BelongsToCurrentThread());
if (SbMemoryCompare(&bounds_, &bounds, sizeof(bounds_)) != 0) {
// |z_index| is changed quite frequently. Assign |z_index| first, so we
// only log when the other members of |bounds| have been changed to avoid
// spamming the log.
bounds_.z_index = bounds.z_index;
bool bounds_changed =
SbMemoryCompare(&bounds_, &bounds, sizeof(bounds_)) != 0;
SB_LOG_IF(INFO, bounds_changed)
<< "Set bounds to "
<< "x: " << bounds.x << ", y: " << bounds.y
<< ", width: " << bounds.width << ", height: " << bounds.height
<< ", z_index: " << bounds.z_index;
bounds_ = bounds;
if (video_renderer_) {
// TODO: Force a frame update
video_renderer_->SetBounds(bounds.z_index, bounds.x, bounds.y,
bounds.width, bounds.height);
}
}
return true;
}
void FilterBasedPlayerWorkerHandler::OnError(SbPlayerError error,
const std::string& error_message) {
if (!BelongsToCurrentThread()) {
Schedule(std::bind(&FilterBasedPlayerWorkerHandler::OnError, this, error,
error_message));
return;
}
if (update_player_error_cb_) {
update_player_error_cb_(error, error_message.empty()
? "FilterBasedPlayerWorkerHandler error."
: error_message);
}
}
void FilterBasedPlayerWorkerHandler::OnPrerolled(SbMediaType media_type) {
if (!BelongsToCurrentThread()) {
Schedule(std::bind(&FilterBasedPlayerWorkerHandler::OnPrerolled, this,
media_type));
return;
}
SB_DCHECK(get_player_state_cb_() == kSbPlayerStatePrerolling)
<< "Invalid player state " << get_player_state_cb_();
if (media_type == kSbMediaTypeAudio) {
SB_LOG(INFO) << "Audio prerolled.";
} else {
SB_LOG(INFO) << "Video prerolled.";
}
audio_prerolled_ |= media_type == kSbMediaTypeAudio;
video_prerolled_ |= media_type == kSbMediaTypeVideo;
if ((!audio_renderer_ || audio_prerolled_) &&
(!video_renderer_ || video_prerolled_)) {
update_player_state_cb_(kSbPlayerStatePresenting);
// The call is required to improve the calculation of media time in
// PlayerInternal, because it updates the system monotonic time used as the
// base of media time extrapolation.
Update();
if (!paused_) {
media_time_provider_->Play();
}
}
}
void FilterBasedPlayerWorkerHandler::OnEnded(SbMediaType media_type) {
if (!BelongsToCurrentThread()) {
Schedule(
std::bind(&FilterBasedPlayerWorkerHandler::OnEnded, this, media_type));
return;
}
if (media_type == kSbMediaTypeAudio) {
SB_LOG(INFO) << "Audio ended.";
} else {
SB_LOG(INFO) << "Video ended.";
}
audio_ended_ |= media_type == kSbMediaTypeAudio;
video_ended_ |= media_type == kSbMediaTypeVideo;
if ((!audio_renderer_ || audio_ended_) &&
(!video_renderer_ || video_ended_)) {
update_player_state_cb_(kSbPlayerStateEndOfStream);
}
}
void FilterBasedPlayerWorkerHandler::Update() {
SB_DCHECK(BelongsToCurrentThread());
if (!media_time_provider_) {
return;
}
if (get_player_state_cb_() == kSbPlayerStatePresenting) {
int dropped_frames = 0;
if (video_renderer_) {
dropped_frames = video_renderer_->GetDroppedFrames();
}
bool is_playing;
bool is_eos_played;
bool is_underflow;
double playback_rate;
auto media_time = media_time_provider_->GetCurrentMediaTime(
&is_playing, &is_eos_played, &is_underflow, &playback_rate);
update_media_info_cb_(media_time, dropped_frames, is_underflow);
}
update_job_token_ = Schedule(update_job_, kUpdateInterval);
}
void FilterBasedPlayerWorkerHandler::Stop() {
SB_DCHECK(BelongsToCurrentThread());
SB_LOG(INFO) << "FilterBasedPlayerWorkerHandler stopped.";
RemoveJobByToken(update_job_token_);
scoped_ptr<PlayerComponents> player_components;
{
// Set |player_components_| to null with the lock, but we actually destroy
// it outside of the lock. This is because the VideoRenderer destructor
// may post a task to destroy the SbDecodeTarget to the same thread that
// might call GetCurrentDecodeTarget(), which would try to take this lock.
::starboard::ScopedLock lock(player_components_existence_mutex_);
player_components = player_components_.Pass();
media_time_provider_ = nullptr;
audio_renderer_ = nullptr;
video_renderer_ = nullptr;
}
player_components.reset();
}
SbDecodeTarget FilterBasedPlayerWorkerHandler::GetCurrentDecodeTarget() {
if (output_mode_ != kSbPlayerOutputModeDecodeToTexture) {
return kSbDecodeTargetInvalid;
}
SbDecodeTarget decode_target = kSbDecodeTargetInvalid;
if (player_components_existence_mutex_.AcquireTry()) {
if (video_renderer_) {
decode_target = video_renderer_->GetCurrentDecodeTarget();
}
player_components_existence_mutex_.Release();
}
return decode_target;
}
} // namespace filter
} // namespace player
} // namespace starboard
} // namespace shared
} // namespace starboard