| // 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 "cobalt/media/base/sbplayer_bridge.h" |
| |
| #include <algorithm> |
| #include <iomanip> |
| #include <string> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/compiler_specific.h" |
| #include "base/location.h" |
| #include "base/logging.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/trace_event/trace_event.h" |
| #include "cobalt/base/statistics.h" |
| #include "cobalt/media/base/format_support_query_metrics.h" |
| #include "media/base/starboard_utils.h" |
| #include "starboard/common/media.h" |
| #include "starboard/common/once.h" |
| #include "starboard/common/player.h" |
| #include "starboard/common/string.h" |
| #include "starboard/configuration.h" |
| #include "starboard/extension/player_set_max_video_input_size.h" |
| #include "starboard/memory.h" |
| |
| namespace cobalt { |
| namespace media { |
| |
| namespace { |
| |
| using base::Time; |
| using base::TimeDelta; |
| using starboard::FormatString; |
| using starboard::GetPlayerOutputModeName; |
| |
| class StatisticsWrapper { |
| public: |
| static StatisticsWrapper* GetInstance(); |
| base::Statistics<int64_t, int, 1024> startup_latency{ |
| "Media.PlaybackStartupLatency"}; |
| }; |
| |
| void SetStreamInfo( |
| const SbMediaAudioStreamInfo& stream_info, |
| CobaltExtensionEnhancedAudioMediaAudioSampleInfo* sample_info) { |
| DCHECK(sample_info); |
| |
| sample_info->stream_info.codec = stream_info.codec; |
| sample_info->stream_info.mime = stream_info.mime; |
| sample_info->stream_info.number_of_channels = stream_info.number_of_channels; |
| sample_info->stream_info.samples_per_second = stream_info.samples_per_second; |
| sample_info->stream_info.bits_per_sample = stream_info.bits_per_sample; |
| sample_info->stream_info.audio_specific_config_size = |
| stream_info.audio_specific_config_size; |
| sample_info->stream_info.audio_specific_config = |
| stream_info.audio_specific_config; |
| } |
| |
| void SetStreamInfo(const SbMediaAudioStreamInfo& stream_info, |
| SbMediaAudioSampleInfo* sample_info) { |
| DCHECK(sample_info); |
| |
| #if SB_API_VERSION >= 15 |
| sample_info->stream_info = stream_info; |
| #else // SB_API_VERSION >= 15 |
| *sample_info = stream_info; |
| #endif // SB_API_VERSION >= 15} |
| } |
| |
| void SetStreamInfo( |
| const SbMediaVideoStreamInfo& stream_info, |
| CobaltExtensionEnhancedAudioMediaVideoSampleInfo* sample_info) { |
| DCHECK(sample_info); |
| |
| sample_info->stream_info.codec = stream_info.codec; |
| sample_info->stream_info.mime = stream_info.mime; |
| sample_info->stream_info.max_video_capabilities = |
| stream_info.max_video_capabilities; |
| sample_info->stream_info.frame_width = stream_info.frame_width; |
| sample_info->stream_info.frame_height = stream_info.frame_height; |
| } |
| |
| void SetStreamInfo(const SbMediaVideoStreamInfo& stream_info, |
| SbMediaVideoSampleInfo* sample_info) { |
| DCHECK(sample_info); |
| |
| #if SB_API_VERSION >= 15 |
| sample_info->stream_info = stream_info; |
| #else // SB_API_VERSION >= 15 |
| *sample_info = stream_info; |
| #endif // SB_API_VERSION >= 15} |
| } |
| |
| void SetDiscardPadding( |
| const ::media::DecoderBuffer::DiscardPadding& discard_padding, |
| CobaltExtensionEnhancedAudioMediaAudioSampleInfo* sample_info) { |
| DCHECK(sample_info); |
| |
| sample_info->discarded_duration_from_front = |
| discard_padding.first.InMicroseconds(); |
| sample_info->discarded_duration_from_back = |
| discard_padding.second.InMicroseconds(); |
| } |
| |
| void SetDiscardPadding( |
| const ::media::DecoderBuffer::DiscardPadding& discard_padding, |
| SbMediaAudioSampleInfo* sample_info) { |
| DCHECK(sample_info); |
| |
| #if SB_API_VERSION >= 15 |
| sample_info->discarded_duration_from_front = |
| discard_padding.first.InMicroseconds(); |
| sample_info->discarded_duration_from_back = |
| discard_padding.second.InMicroseconds(); |
| #endif // SB_API_VERSION >= 15} |
| } |
| |
| } // namespace |
| |
| SB_ONCE_INITIALIZE_FUNCTION(StatisticsWrapper, StatisticsWrapper::GetInstance); |
| |
| SbPlayerBridge::CallbackHelper::CallbackHelper(SbPlayerBridge* player_bridge) |
| : player_bridge_(player_bridge) {} |
| |
| void SbPlayerBridge::CallbackHelper::ClearDecoderBufferCache() { |
| base::AutoLock auto_lock(lock_); |
| if (player_bridge_) { |
| player_bridge_->ClearDecoderBufferCache(); |
| } |
| } |
| |
| void SbPlayerBridge::CallbackHelper::OnDecoderStatus(SbPlayer player, |
| SbMediaType type, |
| SbPlayerDecoderState state, |
| int ticket) { |
| base::AutoLock auto_lock(lock_); |
| if (player_bridge_) { |
| player_bridge_->OnDecoderStatus(static_cast<SbPlayer>(player), type, state, |
| ticket); |
| } |
| } |
| |
| void SbPlayerBridge::CallbackHelper::OnPlayerStatus(SbPlayer player, |
| SbPlayerState state, |
| int ticket) { |
| base::AutoLock auto_lock(lock_); |
| if (player_bridge_) { |
| player_bridge_->OnPlayerStatus(player, state, ticket); |
| } |
| } |
| |
| void SbPlayerBridge::CallbackHelper::OnPlayerError(SbPlayer player, |
| SbPlayerError error, |
| const std::string& message) { |
| base::AutoLock auto_lock(lock_); |
| if (player_bridge_) { |
| player_bridge_->OnPlayerError(player, error, message); |
| } |
| } |
| |
| void SbPlayerBridge::CallbackHelper::OnDeallocateSample( |
| const void* sample_buffer) { |
| base::AutoLock auto_lock(lock_); |
| if (player_bridge_) { |
| player_bridge_->OnDeallocateSample(sample_buffer); |
| } |
| } |
| |
| void SbPlayerBridge::CallbackHelper::ResetPlayer() { |
| base::AutoLock auto_lock(lock_); |
| player_bridge_ = NULL; |
| } |
| |
| #if SB_HAS(PLAYER_WITH_URL) |
| SbPlayerBridge::SbPlayerBridge( |
| SbPlayerInterface* interface, |
| const scoped_refptr<base::SequencedTaskRunner>& task_runner, |
| const std::string& url, SbWindow window, Host* host, |
| SbPlayerSetBoundsHelper* set_bounds_helper, bool allow_resume_after_suspend, |
| SbPlayerOutputMode default_output_mode, |
| const OnEncryptedMediaInitDataEncounteredCB& |
| on_encrypted_media_init_data_encountered_cb, |
| DecodeTargetProvider* const decode_target_provider, |
| std::string pipeline_identifier) |
| : url_(url), |
| sbplayer_interface_(interface), |
| task_runner_(task_runner), |
| callback_helper_( |
| new CallbackHelper(ALLOW_THIS_IN_INITIALIZER_LIST(this))), |
| window_(window), |
| host_(host), |
| set_bounds_helper_(set_bounds_helper), |
| allow_resume_after_suspend_(allow_resume_after_suspend), |
| on_encrypted_media_init_data_encountered_cb_( |
| on_encrypted_media_init_data_encountered_cb), |
| decode_target_provider_(decode_target_provider), |
| cval_stats_(&interface->cval_stats_), |
| pipeline_identifier_(pipeline_identifier), |
| is_url_based_(true) { |
| DCHECK(host_); |
| DCHECK(set_bounds_helper_); |
| |
| output_mode_ = ComputeSbUrlPlayerOutputMode(default_output_mode); |
| |
| CreateUrlPlayer(url_); |
| |
| task_runner->PostTask( |
| FROM_HERE, |
| base::Bind(&SbPlayerBridge::CallbackHelper::ClearDecoderBufferCache, |
| callback_helper_)); |
| } |
| #endif // SB_HAS(PLAYER_WITH_URL) |
| |
| SbPlayerBridge::SbPlayerBridge( |
| SbPlayerInterface* interface, |
| const scoped_refptr<base::SequencedTaskRunner>& task_runner, |
| const GetDecodeTargetGraphicsContextProviderFunc& |
| get_decode_target_graphics_context_provider_func, |
| const AudioDecoderConfig& audio_config, const std::string& audio_mime_type, |
| const VideoDecoderConfig& video_config, const std::string& video_mime_type, |
| SbWindow window, SbDrmSystem drm_system, Host* host, |
| SbPlayerSetBoundsHelper* set_bounds_helper, bool allow_resume_after_suspend, |
| SbPlayerOutputMode default_output_mode, |
| DecodeTargetProvider* const decode_target_provider, |
| const std::string& max_video_capabilities, int max_video_input_size, |
| std::string pipeline_identifier) |
| : sbplayer_interface_(interface), |
| task_runner_(task_runner), |
| get_decode_target_graphics_context_provider_func_( |
| get_decode_target_graphics_context_provider_func), |
| callback_helper_( |
| new CallbackHelper(ALLOW_THIS_IN_INITIALIZER_LIST(this))), |
| window_(window), |
| drm_system_(drm_system), |
| host_(host), |
| set_bounds_helper_(set_bounds_helper), |
| allow_resume_after_suspend_(allow_resume_after_suspend), |
| audio_config_(audio_config), |
| video_config_(video_config), |
| decode_target_provider_(decode_target_provider), |
| max_video_capabilities_(max_video_capabilities), |
| max_video_input_size_(max_video_input_size), |
| cval_stats_(&interface->cval_stats_), |
| pipeline_identifier_(pipeline_identifier) |
| #if SB_HAS(PLAYER_WITH_URL) |
| , |
| is_url_based_(false) |
| #endif // SB_HAS(PLAYER_WITH_URL |
| { |
| DCHECK(!get_decode_target_graphics_context_provider_func_.is_null()); |
| DCHECK(audio_config.IsValidConfig() || video_config.IsValidConfig()); |
| DCHECK(host_); |
| DCHECK(set_bounds_helper_); |
| DCHECK(decode_target_provider_); |
| |
| audio_stream_info_.codec = kSbMediaAudioCodecNone; |
| video_stream_info_.codec = kSbMediaVideoCodecNone; |
| |
| if (audio_config.IsValidConfig()) { |
| UpdateAudioConfig(audio_config, audio_mime_type); |
| } |
| if (video_config.IsValidConfig()) { |
| UpdateVideoConfig(video_config, video_mime_type); |
| SendColorSpaceHistogram(); |
| } |
| |
| output_mode_ = ComputeSbPlayerOutputMode(default_output_mode); |
| |
| CreatePlayer(); |
| |
| if (SbPlayerIsValid(player_)) { |
| task_runner->PostTask( |
| FROM_HERE, |
| base::Bind(&SbPlayerBridge::CallbackHelper::ClearDecoderBufferCache, |
| callback_helper_)); |
| } |
| } |
| |
| SbPlayerBridge::~SbPlayerBridge() { |
| DCHECK(task_runner_->RunsTasksInCurrentSequence()); |
| |
| callback_helper_->ResetPlayer(); |
| set_bounds_helper_->SetPlayerBridge(NULL); |
| |
| decode_target_provider_->SetOutputMode( |
| DecodeTargetProvider::kOutputModeInvalid); |
| decode_target_provider_->ResetGetCurrentSbDecodeTargetFunction(); |
| |
| if (SbPlayerIsValid(player_)) { |
| cval_stats_->StartTimer(MediaTiming::SbPlayerDestroy, pipeline_identifier_); |
| sbplayer_interface_->Destroy(player_); |
| cval_stats_->StopTimer(MediaTiming::SbPlayerDestroy, pipeline_identifier_); |
| } |
| } |
| |
| void SbPlayerBridge::UpdateAudioConfig(const AudioDecoderConfig& audio_config, |
| const std::string& mime_type) { |
| DCHECK(task_runner_->RunsTasksInCurrentSequence()); |
| DCHECK(audio_config.IsValidConfig()); |
| |
| LOG(INFO) << "Updated AudioDecoderConfig -- " |
| << audio_config.AsHumanReadableString(); |
| |
| audio_config_ = audio_config; |
| audio_mime_type_ = mime_type; |
| audio_stream_info_ = MediaAudioConfigToSbMediaAudioStreamInfo( |
| audio_config_, audio_mime_type_.c_str()); |
| LOG(INFO) << "Converted to SbMediaAudioStreamInfo -- " << audio_stream_info_; |
| } |
| |
| void SbPlayerBridge::UpdateVideoConfig(const VideoDecoderConfig& video_config, |
| const std::string& mime_type) { |
| DCHECK(task_runner_->RunsTasksInCurrentSequence()); |
| DCHECK(video_config.IsValidConfig()); |
| |
| LOG(INFO) << "Updated VideoDecoderConfig -- " |
| << video_config.AsHumanReadableString(); |
| |
| video_config_ = video_config; |
| video_stream_info_.frame_width = |
| static_cast<int>(video_config_.natural_size().width()); |
| video_stream_info_.frame_height = |
| static_cast<int>(video_config_.natural_size().height()); |
| video_stream_info_.codec = |
| MediaVideoCodecToSbMediaVideoCodec(video_config_.codec()); |
| video_stream_info_.color_metadata = |
| MediaToSbMediaColorMetadata(video_config_.color_space_info(), |
| video_config_.hdr_metadata(), mime_type); |
| video_mime_type_ = mime_type; |
| video_stream_info_.mime = video_mime_type_.c_str(); |
| video_stream_info_.max_video_capabilities = max_video_capabilities_.c_str(); |
| LOG(INFO) << "Converted to SbMediaVideoStreamInfo -- " << video_stream_info_; |
| } |
| |
| void SbPlayerBridge::WriteBuffers( |
| DemuxerStream::Type type, |
| const std::vector<scoped_refptr<DecoderBuffer>>& buffers) { |
| DCHECK(task_runner_->RunsTasksInCurrentSequence()); |
| #if SB_HAS(PLAYER_WITH_URL) |
| DCHECK(!is_url_based_); |
| #endif // SB_HAS(PLAYER_WITH_URL) |
| |
| if (allow_resume_after_suspend_) { |
| if (type == DemuxerStream::Type::AUDIO) { |
| decoder_buffer_cache_.AddBuffers(buffers, audio_stream_info_); |
| } else { |
| DCHECK(type == DemuxerStream::Type::VIDEO); |
| decoder_buffer_cache_.AddBuffers(buffers, video_stream_info_); |
| } |
| if (state_ != kSuspended) { |
| WriteNextBuffersFromCache(type, buffers.size()); |
| } |
| return; |
| } |
| |
| if (sbplayer_interface_->IsEnhancedAudioExtensionEnabled()) { |
| WriteBuffersInternal<CobaltExtensionEnhancedAudioPlayerSampleInfo>( |
| type, buffers, &audio_stream_info_, &video_stream_info_); |
| } else { |
| WriteBuffersInternal<SbPlayerSampleInfo>(type, buffers, &audio_stream_info_, |
| &video_stream_info_); |
| } |
| } |
| |
| void SbPlayerBridge::SetBounds(int z_index, const gfx::Rect& rect) { |
| base::AutoLock auto_lock(lock_); |
| |
| set_bounds_z_index_ = z_index; |
| set_bounds_rect_ = rect; |
| |
| if (state_ == kSuspended) { |
| return; |
| } |
| |
| UpdateBounds_Locked(); |
| } |
| |
| void SbPlayerBridge::PrepareForSeek() { |
| DCHECK(task_runner_->RunsTasksInCurrentSequence()); |
| |
| seek_pending_ = true; |
| |
| if (state_ == kSuspended) { |
| return; |
| } |
| |
| ++ticket_; |
| sbplayer_interface_->SetPlaybackRate(player_, 0.f); |
| } |
| |
| void SbPlayerBridge::Seek(TimeDelta time) { |
| DCHECK(task_runner_->RunsTasksInCurrentSequence()); |
| |
| decoder_buffer_cache_.ClearAll(); |
| seek_pending_ = false; |
| |
| pending_audio_eos_buffer_ = false; |
| pending_video_eos_buffer_ = false; |
| |
| if (state_ == kSuspended) { |
| preroll_timestamp_ = time; |
| return; |
| } |
| |
| // If a seek happens during resuming, the pipeline will write samples from the |
| // seek target time again so resuming can be aborted. |
| if (state_ == kResuming) { |
| state_ = kPlaying; |
| } |
| |
| DCHECK(SbPlayerIsValid(player_)); |
| |
| ++ticket_; |
| sbplayer_interface_->Seek(player_, time, ticket_); |
| |
| sbplayer_interface_->SetPlaybackRate(player_, playback_rate_); |
| } |
| |
| void SbPlayerBridge::SetVolume(float volume) { |
| DCHECK(task_runner_->RunsTasksInCurrentSequence()); |
| |
| volume_ = volume; |
| |
| if (state_ == kSuspended) { |
| return; |
| } |
| |
| DCHECK(SbPlayerIsValid(player_)); |
| sbplayer_interface_->SetVolume(player_, volume); |
| } |
| |
| void SbPlayerBridge::SetPlaybackRate(double playback_rate) { |
| DCHECK(task_runner_->RunsTasksInCurrentSequence()); |
| |
| playback_rate_ = playback_rate; |
| |
| if (state_ == kSuspended) { |
| return; |
| } |
| |
| if (seek_pending_) { |
| return; |
| } |
| |
| sbplayer_interface_->SetPlaybackRate(player_, playback_rate); |
| } |
| |
| void SbPlayerBridge::GetInfo(uint32* video_frames_decoded, |
| uint32* video_frames_dropped, |
| TimeDelta* media_time) { |
| DCHECK(video_frames_decoded || video_frames_dropped || media_time); |
| |
| base::AutoLock auto_lock(lock_); |
| GetInfo_Locked(video_frames_decoded, video_frames_dropped, media_time); |
| } |
| |
| #if SB_API_VERSION >= 15 |
| std::vector<SbMediaAudioConfiguration> |
| SbPlayerBridge::GetAudioConfigurations() { |
| base::AutoLock auto_lock(lock_); |
| |
| if (!SbPlayerIsValid(player_)) { |
| return std::vector<SbMediaAudioConfiguration>(); |
| } |
| |
| std::vector<SbMediaAudioConfiguration> configurations; |
| |
| // Set a limit to avoid infinite loop. |
| constexpr int kMaxAudioConfigurations = 32; |
| |
| for (int i = 0; i < kMaxAudioConfigurations; ++i) { |
| SbMediaAudioConfiguration configuration; |
| if (!sbplayer_interface_->GetAudioConfiguration(player_, i, |
| &configuration)) { |
| break; |
| } |
| |
| configurations.push_back(configuration); |
| } |
| |
| LOG_IF(WARNING, configurations.empty()) |
| << "Failed to find any audio configurations."; |
| |
| return configurations; |
| } |
| #endif // SB_API_VERSION >= 15 |
| |
| #if SB_HAS(PLAYER_WITH_URL) |
| void SbPlayerBridge::GetUrlPlayerBufferedTimeRanges( |
| TimeDelta* buffer_start_time, TimeDelta* buffer_length_time) { |
| DCHECK(buffer_start_time || buffer_length_time); |
| DCHECK(is_url_based_); |
| |
| if (state_ == kSuspended) { |
| *buffer_start_time = TimeDelta(); |
| *buffer_length_time = TimeDelta(); |
| return; |
| } |
| |
| DCHECK(SbPlayerIsValid(player_)); |
| |
| SbUrlPlayerExtraInfo url_player_info; |
| sbplayer_interface_->GetUrlPlayerExtraInfo(player_, &url_player_info); |
| |
| if (buffer_start_time) { |
| *buffer_start_time = |
| TimeDelta::FromMicroseconds(url_player_info.buffer_start_timestamp); |
| } |
| if (buffer_length_time) { |
| *buffer_length_time = |
| TimeDelta::FromMicroseconds(url_player_info.buffer_duration); |
| } |
| } |
| |
| void SbPlayerBridge::GetVideoResolution(int* frame_width, int* frame_height) { |
| DCHECK(frame_width); |
| DCHECK(frame_height); |
| DCHECK(is_url_based_); |
| |
| if (state_ == kSuspended) { |
| *frame_width = video_stream_info_.frame_width; |
| *frame_height = video_stream_info_.frame_height; |
| return; |
| } |
| |
| DCHECK(SbPlayerIsValid(player_)); |
| |
| #if SB_API_VERSION >= 15 |
| SbPlayerInfo out_player_info; |
| #else // SB_API_VERSION >= 15 |
| SbPlayerInfo2 out_player_info; |
| #endif // SB_API_VERSION >= 15 |
| sbplayer_interface_->GetInfo(player_, &out_player_info); |
| |
| video_stream_info_.frame_width = out_player_info.frame_width; |
| video_stream_info_.frame_height = out_player_info.frame_height; |
| |
| *frame_width = video_stream_info_.frame_width; |
| *frame_height = video_stream_info_.frame_height; |
| } |
| |
| TimeDelta SbPlayerBridge::GetDuration() { |
| DCHECK(is_url_based_); |
| |
| if (state_ == kSuspended) { |
| return TimeDelta(); |
| } |
| |
| DCHECK(SbPlayerIsValid(player_)); |
| |
| #if SB_API_VERSION >= 15 |
| SbPlayerInfo info; |
| #else // SB_API_VERSION >= 15 |
| SbPlayerInfo2 info; |
| #endif // SB_API_VERSION >= 15 |
| sbplayer_interface_->GetInfo(player_, &info); |
| if (info.duration == SB_PLAYER_NO_DURATION) { |
| // URL-based player may not have loaded asset yet, so map no duration to 0. |
| return TimeDelta(); |
| } |
| return TimeDelta::FromMicroseconds(info.duration); |
| } |
| |
| TimeDelta SbPlayerBridge::GetStartDate() { |
| DCHECK(is_url_based_); |
| |
| if (state_ == kSuspended) { |
| return TimeDelta(); |
| } |
| |
| DCHECK(SbPlayerIsValid(player_)); |
| |
| #if SB_API_VERSION >= 15 |
| SbPlayerInfo info; |
| #else // SB_API_VERSION >= 15 |
| SbPlayerInfo2 info; |
| #endif // SB_API_VERSION >= 15 |
| sbplayer_interface_->GetInfo(player_, &info); |
| return TimeDelta::FromMicroseconds(info.start_date); |
| } |
| |
| void SbPlayerBridge::SetDrmSystem(SbDrmSystem drm_system) { |
| DCHECK(is_url_based_); |
| |
| drm_system_ = drm_system; |
| sbplayer_interface_->SetUrlPlayerDrmSystem(player_, drm_system); |
| } |
| #endif // SB_HAS(PLAYER_WITH_URL) |
| |
| void SbPlayerBridge::Suspend() { |
| DCHECK(task_runner_->RunsTasksInCurrentSequence()); |
| |
| // Check if the player is already suspended. |
| if (state_ == kSuspended) { |
| return; |
| } |
| |
| DCHECK(SbPlayerIsValid(player_)); |
| |
| sbplayer_interface_->SetPlaybackRate(player_, 0.0); |
| |
| set_bounds_helper_->SetPlayerBridge(NULL); |
| |
| base::AutoLock auto_lock(lock_); |
| GetInfo_Locked(&cached_video_frames_decoded_, &cached_video_frames_dropped_, |
| &preroll_timestamp_); |
| |
| state_ = kSuspended; |
| |
| decode_target_provider_->SetOutputMode( |
| DecodeTargetProvider::kOutputModeInvalid); |
| decode_target_provider_->ResetGetCurrentSbDecodeTargetFunction(); |
| |
| cval_stats_->StartTimer(MediaTiming::SbPlayerDestroy, pipeline_identifier_); |
| sbplayer_interface_->Destroy(player_); |
| cval_stats_->StopTimer(MediaTiming::SbPlayerDestroy, pipeline_identifier_); |
| |
| player_ = kSbPlayerInvalid; |
| } |
| |
| void SbPlayerBridge::Resume(SbWindow window) { |
| DCHECK(task_runner_->RunsTasksInCurrentSequence()); |
| |
| window_ = window; |
| |
| // Check if the player is already resumed. |
| if (state_ != kSuspended) { |
| DCHECK(SbPlayerIsValid(player_)); |
| return; |
| } |
| |
| decoder_buffer_cache_.StartResuming(); |
| |
| #if SB_HAS(PLAYER_WITH_URL) |
| if (is_url_based_) { |
| CreateUrlPlayer(url_); |
| if (SbDrmSystemIsValid(drm_system_)) { |
| sbplayer_interface_->SetUrlPlayerDrmSystem(player_, drm_system_); |
| } |
| } else { |
| CreatePlayer(); |
| } |
| #else // SB_HAS(PLAYER_WITH_URL) |
| CreatePlayer(); |
| #endif // SB_HAS(PLAYER_WITH_URL) |
| |
| if (SbPlayerIsValid(player_)) { |
| base::AutoLock auto_lock(lock_); |
| state_ = kResuming; |
| UpdateBounds_Locked(); |
| } |
| } |
| |
| namespace { |
| DecodeTargetProvider::OutputMode ToVideoFrameProviderOutputMode( |
| SbPlayerOutputMode output_mode) { |
| switch (output_mode) { |
| case kSbPlayerOutputModeDecodeToTexture: |
| return DecodeTargetProvider::kOutputModeDecodeToTexture; |
| case kSbPlayerOutputModePunchOut: |
| return DecodeTargetProvider::kOutputModePunchOut; |
| case kSbPlayerOutputModeInvalid: |
| return DecodeTargetProvider::kOutputModeInvalid; |
| } |
| |
| NOTREACHED(); |
| return DecodeTargetProvider::kOutputModeInvalid; |
| } |
| |
| } // namespace |
| |
| #if SB_HAS(PLAYER_WITH_URL) |
| // static |
| void SbPlayerBridge::EncryptedMediaInitDataEncounteredCB( |
| SbPlayer player, void* context, const char* init_data_type, |
| const unsigned char* init_data, unsigned int init_data_length) { |
| SbPlayerBridge* helper = static_cast<SbPlayerBridge*>(context); |
| DCHECK(!helper->on_encrypted_media_init_data_encountered_cb_.is_null()); |
| // TODO: Use callback_helper here. |
| helper->on_encrypted_media_init_data_encountered_cb_.Run( |
| init_data_type, init_data, init_data_length); |
| } |
| |
| void SbPlayerBridge::CreateUrlPlayer(const std::string& url) { |
| TRACE_EVENT0("cobalt::media", "SbPlayerBridge::CreateUrlPlayer"); |
| DCHECK(task_runner_->RunsTasksInCurrentSequence()); |
| |
| DCHECK(!on_encrypted_media_init_data_encountered_cb_.is_null()); |
| LOG(INFO) << "CreateUrlPlayer passed url " << url; |
| |
| if (max_video_capabilities_.empty()) { |
| FormatSupportQueryMetrics::PrintAndResetMetrics(); |
| } |
| |
| player_creation_time_ = Time::Now(); |
| |
| cval_stats_->StartTimer(MediaTiming::SbPlayerCreate, pipeline_identifier_); |
| player_ = sbplayer_interface_->CreateUrlPlayer( |
| url.c_str(), window_, &SbPlayerBridge::PlayerStatusCB, |
| &SbPlayerBridge::EncryptedMediaInitDataEncounteredCB, |
| &SbPlayerBridge::PlayerErrorCB, this); |
| cval_stats_->StopTimer(MediaTiming::SbPlayerCreate, pipeline_identifier_); |
| DCHECK(SbPlayerIsValid(player_)); |
| |
| if (output_mode_ == kSbPlayerOutputModeDecodeToTexture) { |
| // If the player is setup to decode to texture, then provide Cobalt with |
| // a method of querying that texture. |
| decode_target_provider_->SetGetCurrentSbDecodeTargetFunction(base::Bind( |
| &SbPlayerBridge::GetCurrentSbDecodeTarget, base::Unretained(this))); |
| LOG(INFO) << "Playing in decode-to-texture mode."; |
| } else { |
| LOG(INFO) << "Playing in punch-out mode."; |
| } |
| |
| decode_target_provider_->SetOutputMode( |
| ToVideoFrameProviderOutputMode(output_mode_)); |
| |
| set_bounds_helper_->SetPlayerBridge(this); |
| |
| base::AutoLock auto_lock(lock_); |
| UpdateBounds_Locked(); |
| } |
| #endif // SB_HAS(PLAYER_WITH_URL) |
| void SbPlayerBridge::CreatePlayer() { |
| TRACE_EVENT0("cobalt::media", "SbPlayerBridge::CreatePlayer"); |
| DCHECK(task_runner_->RunsTasksInCurrentSequence()); |
| |
| bool is_visible = SbWindowIsValid(window_); |
| bool has_audio = audio_stream_info_.codec != kSbMediaAudioCodecNone; |
| |
| is_creating_player_ = true; |
| |
| if (output_mode_ == kSbPlayerOutputModeInvalid) { |
| PlayerErrorCB(kSbPlayerInvalid, this, kSbPlayerErrorDecode, |
| "Invalid output mode returned by " |
| "SbPlayerBridge::ComputeSbPlayerOutputMode()"); |
| is_creating_player_ = false; |
| player_ = kSbPlayerInvalid; |
| return; |
| } |
| |
| if (max_video_capabilities_.empty()) { |
| FormatSupportQueryMetrics::PrintAndResetMetrics(); |
| } |
| |
| player_creation_time_ = Time::Now(); |
| |
| SbPlayerCreationParam creation_param = {}; |
| creation_param.drm_system = drm_system_; |
| #if SB_API_VERSION >= 15 |
| creation_param.audio_stream_info = audio_stream_info_; |
| creation_param.video_stream_info = video_stream_info_; |
| #else // SB_API_VERSION >= 15 |
| creation_param.audio_sample_info = audio_stream_info_; |
| creation_param.video_sample_info = video_stream_info_; |
| #endif // SB_API_VERSION >= 15 |
| |
| // TODO: This is temporary for supporting background media playback. |
| // Need to be removed with media refactor. |
| if (!is_visible) { |
| #if SB_API_VERSION >= 15 |
| creation_param.video_stream_info.codec = kSbMediaVideoCodecNone; |
| #else // SB_API_VERSION >= 15 |
| creation_param.video_sample_info.codec = kSbMediaVideoCodecNone; |
| #endif // SB_API_VERSION >= 15 |
| } |
| creation_param.output_mode = output_mode_; |
| DCHECK_EQ(sbplayer_interface_->GetPreferredOutputMode(&creation_param), |
| output_mode_); |
| cval_stats_->StartTimer(MediaTiming::SbPlayerCreate, pipeline_identifier_); |
| const StarboardExtensionPlayerSetMaxVideoInputSizeApi* |
| player_set_max_video_input_size_extension = |
| static_cast<const StarboardExtensionPlayerSetMaxVideoInputSizeApi*>( |
| SbSystemGetExtension( |
| kStarboardExtensionPlayerSetMaxVideoInputSizeName)); |
| if (player_set_max_video_input_size_extension && |
| strcmp(player_set_max_video_input_size_extension->name, |
| kStarboardExtensionPlayerSetMaxVideoInputSizeName) == 0 && |
| player_set_max_video_input_size_extension->version >= 1) { |
| player_set_max_video_input_size_extension |
| ->SetMaxVideoInputSizeForCurrentThread(max_video_input_size_); |
| } |
| player_ = sbplayer_interface_->Create( |
| window_, &creation_param, &SbPlayerBridge::DeallocateSampleCB, |
| &SbPlayerBridge::DecoderStatusCB, &SbPlayerBridge::PlayerStatusCB, |
| &SbPlayerBridge::PlayerErrorCB, this, |
| get_decode_target_graphics_context_provider_func_.Run()); |
| cval_stats_->StopTimer(MediaTiming::SbPlayerCreate, pipeline_identifier_); |
| |
| is_creating_player_ = false; |
| |
| if (!SbPlayerIsValid(player_)) { |
| return; |
| } |
| |
| if (output_mode_ == kSbPlayerOutputModeDecodeToTexture) { |
| // If the player is setup to decode to texture, then provide Cobalt with |
| // a method of querying that texture. |
| decode_target_provider_->SetGetCurrentSbDecodeTargetFunction(base::Bind( |
| &SbPlayerBridge::GetCurrentSbDecodeTarget, base::Unretained(this))); |
| LOG(INFO) << "Playing in decode-to-texture mode."; |
| } else { |
| LOG(INFO) << "Playing in punch-out mode."; |
| } |
| |
| decode_target_provider_->SetOutputMode( |
| ToVideoFrameProviderOutputMode(output_mode_)); |
| set_bounds_helper_->SetPlayerBridge(this); |
| |
| base::AutoLock auto_lock(lock_); |
| UpdateBounds_Locked(); |
| } |
| |
| void SbPlayerBridge::WriteNextBuffersFromCache(DemuxerStream::Type type, |
| int max_buffers_per_write) { |
| DCHECK(state_ != kSuspended); |
| #if SB_HAS(PLAYER_WITH_URL) |
| DCHECK(!is_url_based_); |
| #endif // SB_HAS(PLAYER_WITH_URL) |
| |
| DCHECK(SbPlayerIsValid(player_)); |
| |
| if (type == DemuxerStream::AUDIO) { |
| std::vector<scoped_refptr<DecoderBuffer>> buffers; |
| SbMediaAudioStreamInfo stream_info; |
| decoder_buffer_cache_.ReadBuffers(&buffers, max_buffers_per_write, |
| &stream_info); |
| if (buffers.size() > 0) { |
| if (sbplayer_interface_->IsEnhancedAudioExtensionEnabled()) { |
| WriteBuffersInternal<CobaltExtensionEnhancedAudioPlayerSampleInfo>( |
| type, buffers, &stream_info, nullptr); |
| } else { |
| WriteBuffersInternal<SbPlayerSampleInfo>(type, buffers, &stream_info, |
| nullptr); |
| } |
| } |
| } else { |
| DCHECK_EQ(type, DemuxerStream::VIDEO); |
| std::vector<scoped_refptr<DecoderBuffer>> buffers; |
| SbMediaVideoStreamInfo stream_info; |
| decoder_buffer_cache_.ReadBuffers(&buffers, max_buffers_per_write, |
| &stream_info); |
| if (buffers.size() > 0) { |
| if (sbplayer_interface_->IsEnhancedAudioExtensionEnabled()) { |
| WriteBuffersInternal<CobaltExtensionEnhancedAudioPlayerSampleInfo>( |
| type, buffers, nullptr, &stream_info); |
| } else { |
| WriteBuffersInternal<SbPlayerSampleInfo>(type, buffers, nullptr, |
| &stream_info); |
| } |
| } |
| } |
| } |
| |
| template <typename PlayerSampleInfo> |
| void SbPlayerBridge::WriteBuffersInternal( |
| DemuxerStream::Type type, |
| const std::vector<scoped_refptr<DecoderBuffer>>& buffers, |
| const SbMediaAudioStreamInfo* audio_stream_info, |
| const SbMediaVideoStreamInfo* video_stream_info) { |
| #if SB_HAS(PLAYER_WITH_URL) |
| DCHECK(!is_url_based_); |
| #endif // SB_HAS(PLAYER_WITH_URL) |
| |
| auto sample_type = DemuxerStreamTypeToSbMediaType(type); |
| if (buffers.size() == 1 && buffers[0]->end_of_stream()) { |
| sbplayer_interface_->WriteEndOfStream(player_, |
| DemuxerStreamTypeToSbMediaType(type)); |
| return; |
| } |
| |
| std::vector<PlayerSampleInfo> gathered_sbplayer_sample_infos; |
| std::vector<SbDrmSampleInfo> gathered_sbplayer_sample_infos_drm_info; |
| std::vector<SbDrmSubSampleMapping> |
| gathered_sbplayer_sample_infos_subsample_mapping; |
| std::vector<SbPlayerSampleSideData> gathered_sbplayer_sample_infos_side_data; |
| |
| gathered_sbplayer_sample_infos.reserve(buffers.size()); |
| gathered_sbplayer_sample_infos_drm_info.reserve(buffers.size()); |
| gathered_sbplayer_sample_infos_subsample_mapping.reserve(buffers.size()); |
| gathered_sbplayer_sample_infos_side_data.reserve(buffers.size()); |
| |
| for (int i = 0; i < buffers.size(); i++) { |
| const auto& buffer = buffers[i]; |
| if (buffer->end_of_stream()) { |
| DCHECK_EQ(i, buffers.size() - 1); |
| if (type == DemuxerStream::AUDIO) { |
| pending_audio_eos_buffer_ = true; |
| } else { |
| pending_video_eos_buffer_ = true; |
| } |
| break; |
| } |
| |
| DecodingBuffers::iterator iter = decoding_buffers_.find(buffer->data()); |
| if (iter == decoding_buffers_.end()) { |
| decoding_buffers_[buffer->data()] = std::make_pair(buffer, 1); |
| } else { |
| ++iter->second.second; |
| } |
| |
| if (sample_type == kSbMediaTypeAudio && |
| first_audio_sample_time_.is_null()) { |
| first_audio_sample_time_ = Time::Now(); |
| } else if (sample_type == kSbMediaTypeVideo && |
| first_video_sample_time_.is_null()) { |
| first_video_sample_time_ = Time::Now(); |
| } |
| |
| gathered_sbplayer_sample_infos_drm_info.push_back(SbDrmSampleInfo()); |
| SbDrmSampleInfo* drm_info = &gathered_sbplayer_sample_infos_drm_info[i]; |
| |
| gathered_sbplayer_sample_infos_subsample_mapping.push_back( |
| SbDrmSubSampleMapping()); |
| SbDrmSubSampleMapping* subsample_mapping = |
| &gathered_sbplayer_sample_infos_subsample_mapping[i]; |
| |
| drm_info->subsample_count = 0; |
| if (buffer->decrypt_config()) { |
| FillDrmSampleInfo(buffer, drm_info, subsample_mapping); |
| } |
| |
| gathered_sbplayer_sample_infos_side_data.push_back( |
| SbPlayerSampleSideData()); |
| SbPlayerSampleSideData* side_data = |
| &gathered_sbplayer_sample_infos_side_data[i]; |
| |
| PlayerSampleInfo sample_info = {}; |
| sample_info.type = sample_type; |
| sample_info.buffer = buffer->data(); |
| sample_info.buffer_size = buffer->data_size(); |
| sample_info.timestamp = buffer->timestamp().InMicroseconds(); |
| |
| if (buffer->side_data_size() > 0) { |
| // We only support at most one side data currently. |
| side_data->type = kMatroskaBlockAdditional; |
| side_data->data = buffer->side_data(); |
| side_data->size = buffer->side_data_size(); |
| sample_info.side_data = side_data; |
| sample_info.side_data_count = 1; |
| } |
| |
| if (sample_type == kSbMediaTypeAudio) { |
| DCHECK(audio_stream_info); |
| SetStreamInfo(*audio_stream_info, &sample_info.audio_sample_info); |
| SetDiscardPadding(buffer->discard_padding(), |
| &sample_info.audio_sample_info); |
| } else { |
| DCHECK(sample_type == kSbMediaTypeVideo); |
| DCHECK(video_stream_info); |
| SetStreamInfo(*video_stream_info, &sample_info.video_sample_info); |
| sample_info.video_sample_info.is_key_frame = buffer->is_key_frame(); |
| } |
| if (drm_info->subsample_count > 0) { |
| sample_info.drm_info = drm_info; |
| } else { |
| sample_info.drm_info = NULL; |
| } |
| gathered_sbplayer_sample_infos.push_back(sample_info); |
| } |
| |
| if (!gathered_sbplayer_sample_infos.empty()) { |
| cval_stats_->StartTimer(MediaTiming::SbPlayerWriteSamples, |
| pipeline_identifier_); |
| sbplayer_interface_->WriteSamples(player_, sample_type, |
| gathered_sbplayer_sample_infos.data(), |
| gathered_sbplayer_sample_infos.size()); |
| cval_stats_->StopTimer(MediaTiming::SbPlayerWriteSamples, |
| pipeline_identifier_); |
| } |
| } |
| |
| SbDecodeTarget SbPlayerBridge::GetCurrentSbDecodeTarget() { |
| return sbplayer_interface_->GetCurrentFrame(player_); |
| } |
| |
| SbPlayerOutputMode SbPlayerBridge::GetSbPlayerOutputMode() { |
| return output_mode_; |
| } |
| |
| void SbPlayerBridge::GetInfo_Locked(uint32* video_frames_decoded, |
| uint32* video_frames_dropped, |
| TimeDelta* media_time) { |
| lock_.AssertAcquired(); |
| if (state_ == kSuspended) { |
| if (video_frames_decoded) { |
| *video_frames_decoded = cached_video_frames_decoded_; |
| } |
| if (video_frames_dropped) { |
| *video_frames_dropped = cached_video_frames_dropped_; |
| } |
| if (media_time) { |
| *media_time = preroll_timestamp_; |
| } |
| return; |
| } |
| |
| DCHECK(SbPlayerIsValid(player_)); |
| |
| #if SB_API_VERSION >= 15 |
| SbPlayerInfo info; |
| #else // SB_API_VERSION >= 15 |
| SbPlayerInfo2 info; |
| #endif // SB_API_VERSION >= 15 |
| sbplayer_interface_->GetInfo(player_, &info); |
| |
| if (media_time) { |
| *media_time = TimeDelta::FromMicroseconds(info.current_media_timestamp); |
| } |
| if (video_frames_decoded) { |
| *video_frames_decoded = info.total_video_frames; |
| } |
| if (video_frames_dropped) { |
| *video_frames_dropped = info.dropped_video_frames; |
| } |
| } |
| |
| void SbPlayerBridge::UpdateBounds_Locked() { |
| lock_.AssertAcquired(); |
| DCHECK(SbPlayerIsValid(player_)); |
| |
| if (!set_bounds_z_index_ || !set_bounds_rect_) { |
| return; |
| } |
| |
| auto& rect = *set_bounds_rect_; |
| sbplayer_interface_->SetBounds(player_, *set_bounds_z_index_, rect.x(), |
| rect.y(), rect.width(), rect.height()); |
| } |
| |
| void SbPlayerBridge::ClearDecoderBufferCache() { |
| DCHECK(task_runner_->RunsTasksInCurrentSequence()); |
| |
| if (state_ != kResuming) { |
| TimeDelta media_time; |
| GetInfo(NULL, NULL, &media_time); |
| decoder_buffer_cache_.ClearSegmentsBeforeMediaTime(media_time); |
| } |
| |
| task_runner_->PostDelayedTask( |
| FROM_HERE, |
| base::Bind(&SbPlayerBridge::CallbackHelper::ClearDecoderBufferCache, |
| callback_helper_), |
| TimeDelta::FromMilliseconds(kClearDecoderCacheIntervalInMilliseconds)); |
| } |
| |
| void SbPlayerBridge::OnDecoderStatus(SbPlayer player, SbMediaType type, |
| SbPlayerDecoderState state, int ticket) { |
| #if SB_HAS(PLAYER_WITH_URL) |
| DCHECK(!is_url_based_); |
| #endif // SB_HAS(PLAYER_WITH_URL) |
| DCHECK(task_runner_->RunsTasksInCurrentSequence()); |
| |
| if (player_ != player || ticket != ticket_) { |
| return; |
| } |
| |
| DCHECK_NE(state_, kSuspended); |
| |
| switch (state) { |
| case kSbPlayerDecoderStateNeedsData: |
| break; |
| } |
| |
| DemuxerStream::Type stream_type = |
| ::media::SbMediaTypeToDemuxerStreamType(type); |
| |
| if (stream_type == DemuxerStream::AUDIO && pending_audio_eos_buffer_) { |
| SbPlayerWriteEndOfStream(player_, type); |
| pending_audio_eos_buffer_ = false; |
| return; |
| } else if (stream_type == DemuxerStream::VIDEO && pending_video_eos_buffer_) { |
| SbPlayerWriteEndOfStream(player_, type); |
| pending_video_eos_buffer_ = false; |
| return; |
| } |
| |
| auto max_number_of_samples_to_write = |
| SbPlayerGetMaximumNumberOfSamplesPerWrite(player_, type); |
| if (state_ == kResuming) { |
| if (decoder_buffer_cache_.HasMoreBuffers(stream_type)) { |
| WriteNextBuffersFromCache(stream_type, max_number_of_samples_to_write); |
| return; |
| } |
| if (!decoder_buffer_cache_.HasMoreBuffers(DemuxerStream::AUDIO) && |
| !decoder_buffer_cache_.HasMoreBuffers(DemuxerStream::VIDEO)) { |
| state_ = kPlaying; |
| } |
| } |
| host_->OnNeedData(stream_type, max_number_of_samples_to_write); |
| } |
| |
| void SbPlayerBridge::OnPlayerStatus(SbPlayer player, SbPlayerState state, |
| int ticket) { |
| TRACE_EVENT1("cobalt::media", "SbPlayerBridge::OnPlayerStatus", "state", |
| state); |
| DCHECK(task_runner_->RunsTasksInCurrentSequence()); |
| |
| if (player_ != player) { |
| return; |
| } |
| |
| DCHECK_NE(state_, kSuspended); |
| |
| if (ticket != SB_PLAYER_INITIAL_TICKET && ticket != ticket_) { |
| return; |
| } |
| |
| if (state == kSbPlayerStateInitialized) { |
| if (ticket_ == SB_PLAYER_INITIAL_TICKET) { |
| ++ticket_; |
| } |
| if (sb_player_state_initialized_time_.is_null()) { |
| sb_player_state_initialized_time_ = Time::Now(); |
| } |
| sbplayer_interface_->Seek(player_, preroll_timestamp_, ticket_); |
| SetVolume(volume_); |
| sbplayer_interface_->SetPlaybackRate(player_, playback_rate_); |
| return; |
| } |
| if (state == kSbPlayerStatePrerolling && |
| sb_player_state_prerolling_time_.is_null()) { |
| sb_player_state_prerolling_time_ = Time::Now(); |
| } else if (state == kSbPlayerStatePresenting && |
| sb_player_state_presenting_time_.is_null()) { |
| sb_player_state_presenting_time_ = Time::Now(); |
| #if !defined(COBALT_BUILD_TYPE_GOLD) |
| LogStartupLatency(); |
| #endif // !defined(COBALT_BUILD_TYPE_GOLD) |
| } |
| host_->OnPlayerStatus(state); |
| } |
| |
| void SbPlayerBridge::OnPlayerError(SbPlayer player, SbPlayerError error, |
| const std::string& message) { |
| DCHECK(task_runner_->RunsTasksInCurrentSequence()); |
| |
| if (player_ != player) { |
| return; |
| } |
| host_->OnPlayerError(error, message); |
| } |
| |
| void SbPlayerBridge::OnDeallocateSample(const void* sample_buffer) { |
| #if SB_HAS(PLAYER_WITH_URL) |
| DCHECK(!is_url_based_); |
| #endif // SB_HAS(PLAYER_WITH_URL) |
| DCHECK(task_runner_->RunsTasksInCurrentSequence()); |
| |
| DecodingBuffers::iterator iter = decoding_buffers_.find(sample_buffer); |
| DCHECK(iter != decoding_buffers_.end()); |
| if (iter == decoding_buffers_.end()) { |
| LOG(ERROR) << "SbPlayerBridge::OnDeallocateSample encounters unknown " |
| << "sample_buffer " << sample_buffer; |
| return; |
| } |
| --iter->second.second; |
| if (iter->second.second == 0) { |
| decoding_buffers_.erase(iter); |
| } |
| } |
| |
| bool SbPlayerBridge::TryToSetPlayerCreationErrorMessage( |
| const std::string& message) { |
| DCHECK(task_runner_->RunsTasksInCurrentSequence()); |
| if (is_creating_player_) { |
| player_creation_error_message_ = message; |
| return true; |
| } |
| LOG(INFO) << "TryToSetPlayerCreationErrorMessage() " |
| "is called when |is_creating_player_| " |
| "is false. Error message is ignored."; |
| return false; |
| } |
| |
| // static |
| void SbPlayerBridge::DecoderStatusCB(SbPlayer player, void* context, |
| SbMediaType type, |
| SbPlayerDecoderState state, int ticket) { |
| SbPlayerBridge* helper = static_cast<SbPlayerBridge*>(context); |
| helper->task_runner_->PostTask( |
| FROM_HERE, |
| base::Bind(&SbPlayerBridge::CallbackHelper::OnDecoderStatus, |
| helper->callback_helper_, player, type, state, ticket)); |
| } |
| |
| // static |
| void SbPlayerBridge::PlayerStatusCB(SbPlayer player, void* context, |
| SbPlayerState state, int ticket) { |
| SbPlayerBridge* helper = static_cast<SbPlayerBridge*>(context); |
| helper->task_runner_->PostTask( |
| FROM_HERE, base::Bind(&SbPlayerBridge::CallbackHelper::OnPlayerStatus, |
| helper->callback_helper_, player, state, ticket)); |
| } |
| |
| // static |
| void SbPlayerBridge::PlayerErrorCB(SbPlayer player, void* context, |
| SbPlayerError error, const char* message) { |
| SbPlayerBridge* helper = static_cast<SbPlayerBridge*>(context); |
| if (player == kSbPlayerInvalid) { |
| // TODO: Simplify by combining the functionality of |
| // TryToSetPlayerCreationErrorMessage() with OnPlayerError(). |
| if (helper->TryToSetPlayerCreationErrorMessage(message)) { |
| return; |
| } |
| } |
| helper->task_runner_->PostTask( |
| FROM_HERE, base::Bind(&SbPlayerBridge::CallbackHelper::OnPlayerError, |
| helper->callback_helper_, player, error, |
| message ? std::string(message) : "")); |
| } |
| |
| // static |
| void SbPlayerBridge::DeallocateSampleCB(SbPlayer player, void* context, |
| const void* sample_buffer) { |
| SbPlayerBridge* helper = static_cast<SbPlayerBridge*>(context); |
| helper->task_runner_->PostTask( |
| FROM_HERE, base::Bind(&SbPlayerBridge::CallbackHelper::OnDeallocateSample, |
| helper->callback_helper_, sample_buffer)); |
| } |
| |
| #if SB_HAS(PLAYER_WITH_URL) |
| SbPlayerOutputMode SbPlayerBridge::ComputeSbUrlPlayerOutputMode( |
| SbPlayerOutputMode default_output_mode) { |
| // Try to choose the output mode according to the passed in value of |
| // |prefer_decode_to_texture|. If the preferred output mode is unavailable |
| // though, fallback to an output mode that is available. |
| SbPlayerOutputMode output_mode = kSbPlayerOutputModeInvalid; |
| if (sbplayer_interface_->GetUrlPlayerOutputModeSupported( |
| kSbPlayerOutputModePunchOut)) { |
| output_mode = kSbPlayerOutputModePunchOut; |
| } |
| if ((default_output_mode == kSbPlayerOutputModeDecodeToTexture || |
| output_mode == kSbPlayerOutputModeInvalid) && |
| sbplayer_interface_->GetUrlPlayerOutputModeSupported( |
| kSbPlayerOutputModeDecodeToTexture)) { |
| output_mode = kSbPlayerOutputModeDecodeToTexture; |
| } |
| CHECK_NE(kSbPlayerOutputModeInvalid, output_mode); |
| |
| return output_mode; |
| } |
| #endif // SB_HAS(PLAYER_WITH_URL) |
| |
| SbPlayerOutputMode SbPlayerBridge::ComputeSbPlayerOutputMode( |
| SbPlayerOutputMode default_output_mode) const { |
| SbPlayerCreationParam creation_param = {}; |
| creation_param.drm_system = drm_system_; |
| |
| #if SB_API_VERSION >= 15 |
| creation_param.audio_stream_info = audio_stream_info_; |
| creation_param.video_stream_info = video_stream_info_; |
| #else // SB_API_VERSION >= 15 |
| creation_param.audio_sample_info = audio_stream_info_; |
| creation_param.video_sample_info = video_stream_info_; |
| #endif // SB_API_VERSION >= 15 |
| |
| if (default_output_mode != kSbPlayerOutputModeDecodeToTexture && |
| video_stream_info_.codec != kSbMediaVideoCodecNone) { |
| DCHECK(video_stream_info_.mime); |
| DCHECK(video_stream_info_.max_video_capabilities); |
| |
| // Set the `default_output_mode` to `kSbPlayerOutputModeDecodeToTexture` if |
| // any of the mime associated with it has `decode-to-texture=true` set. |
| // TODO(b/232559177): Make the check below more flexible, to work with |
| // other well formed inputs (e.g. with extra space). |
| bool is_decode_to_texture_preferred = |
| strstr(video_stream_info_.mime, "decode-to-texture=true") || |
| strstr(video_stream_info_.max_video_capabilities, |
| "decode-to-texture=true"); |
| |
| if (is_decode_to_texture_preferred) { |
| LOG(INFO) << "Setting `default_output_mode` from \"" |
| << GetPlayerOutputModeName(default_output_mode) << "\" to \"" |
| << GetPlayerOutputModeName(kSbPlayerOutputModeDecodeToTexture) |
| << "\" because mime is set to \"" << video_stream_info_.mime |
| << "\", and max_video_capabilities is set to \"" |
| << video_stream_info_.max_video_capabilities << "\""; |
| default_output_mode = kSbPlayerOutputModeDecodeToTexture; |
| } |
| } |
| |
| creation_param.output_mode = default_output_mode; |
| |
| auto output_mode = |
| sbplayer_interface_->GetPreferredOutputMode(&creation_param); |
| |
| LOG(INFO) << "Output mode is set to " << GetPlayerOutputModeName(output_mode); |
| |
| return output_mode; |
| } |
| |
| void SbPlayerBridge::LogStartupLatency() const { |
| std::string first_events_str; |
| if (set_drm_system_ready_cb_time_.is_null()) { |
| first_events_str = FormatString("%-40s0 us", "SbPlayerCreate() called"); |
| } else if (set_drm_system_ready_cb_time_ < player_creation_time_) { |
| first_events_str = FormatString( |
| "%-40s0 us\n%-40s%" PRId64 " us", "set_drm_system_ready_cb called", |
| "SbPlayerCreate() called", |
| (player_creation_time_ - set_drm_system_ready_cb_time_) |
| .InMicroseconds()); |
| } else { |
| first_events_str = FormatString( |
| "%-40s0 us\n%-40s%" PRId64 " us", "SbPlayerCreate() called", |
| "set_drm_system_ready_cb called", |
| (set_drm_system_ready_cb_time_ - player_creation_time_) |
| .InMicroseconds()); |
| } |
| |
| Time first_event_time = |
| std::max(player_creation_time_, set_drm_system_ready_cb_time_); |
| TimeDelta player_initialization_time_delta = |
| sb_player_state_initialized_time_ - first_event_time; |
| TimeDelta player_preroll_time_delta = |
| sb_player_state_prerolling_time_ - sb_player_state_initialized_time_; |
| TimeDelta first_audio_sample_time_delta = std::max( |
| first_audio_sample_time_ - sb_player_state_prerolling_time_, TimeDelta()); |
| TimeDelta first_video_sample_time_delta = std::max( |
| first_video_sample_time_ - sb_player_state_prerolling_time_, TimeDelta()); |
| TimeDelta player_presenting_time_delta = |
| sb_player_state_presenting_time_ - |
| std::max(first_audio_sample_time_, first_video_sample_time_); |
| TimeDelta startup_latency = |
| sb_player_state_presenting_time_ - first_event_time; |
| |
| StatisticsWrapper::GetInstance()->startup_latency.AddSample( |
| startup_latency.InMicroseconds(), 1); |
| |
| // clang-format off |
| LOG(INFO) << FormatString( |
| "\nSbPlayer startup latencies: %" PRId64 " us\n" |
| " Event name time since last event\n" |
| " %s\n" // |first_events_str| populated above |
| " kSbPlayerStateInitialized received %" PRId64 " us\n" |
| " kSbPlayerStatePrerolling received %" PRId64 " us\n" |
| " First media sample(s) written [a/v] %" PRId64 "/%" PRId64 " us\n" |
| " kSbPlayerStatePresenting received %" PRId64 " us\n" |
| " Startup latency statistics (us):" |
| " min: %" PRId64 ", median: %" PRId64 ", average: %" PRId64 |
| ", max: %" PRId64, |
| startup_latency.InMicroseconds(), |
| first_events_str.c_str(), player_initialization_time_delta.InMicroseconds(), |
| player_preroll_time_delta.InMicroseconds(), first_audio_sample_time_delta.InMicroseconds(), |
| first_video_sample_time_delta.InMicroseconds(), player_presenting_time_delta.InMicroseconds(), |
| StatisticsWrapper::GetInstance()->startup_latency.min(), |
| StatisticsWrapper::GetInstance()->startup_latency.GetMedian(), |
| StatisticsWrapper::GetInstance()->startup_latency.average(), |
| StatisticsWrapper::GetInstance()->startup_latency.max()); |
| // clang-format on |
| } |
| |
| void SbPlayerBridge::SendColorSpaceHistogram() const { |
| using base::UmaHistogramEnumeration; |
| |
| const auto& cs_info = video_config_.color_space_info(); |
| |
| if (video_stream_info_.color_metadata.bits_per_channel > 8) { |
| UmaHistogramEnumeration("Cobalt.Media.HDR.Primaries", cs_info.primaries); |
| UmaHistogramEnumeration("Cobalt.Media.HDR.Transfer", cs_info.transfer); |
| UmaHistogramEnumeration("Cobalt.Media.HDR.Matrix", cs_info.matrix); |
| UmaHistogramEnumeration("Cobalt.Media.HDR.Range", cs_info.range); |
| } else { |
| UmaHistogramEnumeration("Cobalt.Media.SDR.Primaries", cs_info.primaries); |
| UmaHistogramEnumeration("Cobalt.Media.SDR.Transfer", cs_info.transfer); |
| UmaHistogramEnumeration("Cobalt.Media.SDR.Matrix", cs_info.matrix); |
| UmaHistogramEnumeration("Cobalt.Media.SDR.Range", cs_info.range); |
| } |
| } |
| |
| } // namespace media |
| } // namespace cobalt |