| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "media/mojo/services/mojo_renderer_service.h" |
| |
| #include "base/bind.h" |
| #include "base/callback_helpers.h" |
| #include "base/memory/scoped_vector.h" |
| #include "media/audio/audio_manager.h" |
| #include "media/audio/audio_manager_base.h" |
| #include "media/audio/null_audio_sink.h" |
| #include "media/base/audio_decoder.h" |
| #include "media/base/audio_renderer.h" |
| #include "media/base/audio_renderer_sink.h" |
| #include "media/base/decryptor.h" |
| #include "media/base/media_log.h" |
| #include "media/filters/audio_renderer_impl.h" |
| #include "media/filters/ffmpeg_audio_decoder.h" |
| #include "media/filters/opus_audio_decoder.h" |
| #include "media/mojo/services/mojo_demuxer_stream_adapter.h" |
| #include "mojo/application/application_runner_chromium.h" |
| #include "mojo/public/c/system/main.h" |
| #include "mojo/public/cpp/application/application_connection.h" |
| #include "mojo/public/cpp/application/application_delegate.h" |
| #include "mojo/public/cpp/application/interface_factory_impl.h" |
| |
| namespace media { |
| |
| // Time interval to update media time. |
| const int kTimeUpdateIntervalMs = 50; |
| |
| #if !defined(OS_ANDROID) |
| static void LogMediaSourceError(const scoped_refptr<MediaLog>& media_log, |
| const std::string& error) { |
| media_log->AddEvent(media_log->CreateMediaSourceErrorEvent(error)); |
| } |
| #endif |
| |
| static base::TimeDelta TimeUpdateInterval() { |
| return base::TimeDelta::FromMilliseconds(kTimeUpdateIntervalMs); |
| } |
| |
| class MojoRendererApplication |
| : public mojo::ApplicationDelegate, |
| public mojo::InterfaceFactory<mojo::MediaRenderer> { |
| public: |
| // mojo::ApplicationDelegate implementation. |
| bool ConfigureIncomingConnection( |
| mojo::ApplicationConnection* connection) override { |
| connection->AddService(this); |
| return true; |
| } |
| |
| // mojo::InterfaceFactory<mojo::MediaRenderer> implementation. |
| void Create(mojo::ApplicationConnection* connection, |
| mojo::InterfaceRequest<mojo::MediaRenderer> request) override { |
| mojo::BindToRequest(new MojoRendererService(connection), &request); |
| } |
| }; |
| |
| // TODO(xhwang): This class looks insanely similar to RendererImpl. We should |
| // really host a Renderer in this class instead of a AudioRenderer. |
| |
| MojoRendererService::MojoRendererService( |
| mojo::ApplicationConnection* connection) |
| : state_(STATE_UNINITIALIZED), |
| time_source_(NULL), |
| time_ticking_(false), |
| ended_(false), |
| // AudioManager() has been created by WebMediaPlayerFactory. This will |
| // be problematic when MojoRendererService really runs in a separate |
| // process. |
| // TODO(xhwang/dalecurtis): Figure out what config we should use. |
| audio_manager_( |
| media::AudioManager::Get() |
| ? media::AudioManager::Get() |
| : media::AudioManager::Create(&fake_audio_log_factory_)), |
| audio_hardware_config_( |
| audio_manager_->GetInputStreamParameters( |
| media::AudioManagerBase::kDefaultDeviceId), |
| audio_manager_->GetDefaultOutputStreamParameters()), |
| weak_factory_(this), |
| weak_this_(weak_factory_.GetWeakPtr()) { |
| DVLOG(1) << __FUNCTION__; |
| |
| scoped_refptr<base::SingleThreadTaskRunner> runner( |
| base::MessageLoop::current()->task_runner()); |
| scoped_refptr<MediaLog> media_log(new MediaLog()); |
| |
| // TODO(xhwang): Provide a more general way to add new decoders. |
| ScopedVector<AudioDecoder> audio_decoders; |
| |
| #if !defined(OS_ANDROID) |
| audio_decoders.push_back(new media::FFmpegAudioDecoder( |
| runner, base::Bind(&LogMediaSourceError, media_log))); |
| audio_decoders.push_back(new media::OpusAudioDecoder(runner)); |
| #endif |
| |
| audio_renderer_.reset(new AudioRendererImpl( |
| runner, |
| // TODO(tim): We should use |connection| passed to MojoRendererService |
| // to connect to a MojoAudioRendererSink implementation that we would |
| // wrap in an AudioRendererSink and pass in here. |
| new NullAudioSink(runner), |
| audio_decoders.Pass(), |
| // TODO(tim): Not needed for now? |
| SetDecryptorReadyCB(), |
| audio_hardware_config_, |
| media_log)); |
| } |
| |
| MojoRendererService::~MojoRendererService() { |
| } |
| |
| void MojoRendererService::Initialize(mojo::DemuxerStreamPtr stream, |
| const mojo::Callback<void()>& callback) { |
| DVLOG(1) << __FUNCTION__; |
| DCHECK_EQ(state_, STATE_UNINITIALIZED) << state_; |
| DCHECK(client()); |
| |
| init_cb_ = callback; |
| state_ = STATE_INITIALIZING; |
| stream_.reset(new MojoDemuxerStreamAdapter( |
| stream.Pass(), |
| base::Bind(&MojoRendererService::OnStreamReady, weak_this_))); |
| } |
| |
| void MojoRendererService::Flush(const mojo::Callback<void()>& callback) { |
| DVLOG(2) << __FUNCTION__; |
| DCHECK_EQ(state_, STATE_PLAYING) << state_; |
| |
| state_ = STATE_FLUSHING; |
| if (time_ticking_) |
| PausePlayback(); |
| |
| // TODO(xhwang): This is not completed. Finish the flushing path. |
| NOTIMPLEMENTED(); |
| } |
| |
| void MojoRendererService::StartPlayingFrom(int64_t time_delta_usec) { |
| DVLOG(2) << __FUNCTION__ << ": " << time_delta_usec; |
| base::TimeDelta time = base::TimeDelta::FromMicroseconds(time_delta_usec); |
| time_source_->SetMediaTime(time); |
| audio_renderer_->StartPlaying(); |
| } |
| |
| void MojoRendererService::SetPlaybackRate(float playback_rate) { |
| DVLOG(2) << __FUNCTION__ << ": " << playback_rate; |
| |
| // Playback rate changes are only carried out while playing. |
| if (state_ != STATE_PLAYING) |
| return; |
| |
| time_source_->SetPlaybackRate(playback_rate); |
| } |
| |
| void MojoRendererService::SetVolume(float volume) { |
| if (audio_renderer_) |
| audio_renderer_->SetVolume(volume); |
| } |
| |
| void MojoRendererService::OnStreamReady() { |
| DCHECK_EQ(state_, STATE_INITIALIZING) << state_; |
| audio_renderer_->Initialize( |
| stream_.get(), |
| base::Bind(&MojoRendererService::OnAudioRendererInitializeDone, |
| weak_this_), |
| base::Bind(&MojoRendererService::OnUpdateStatistics, weak_this_), |
| base::Bind(&MojoRendererService::OnBufferingStateChanged, weak_this_), |
| base::Bind(&MojoRendererService::OnAudioRendererEnded, weak_this_), |
| base::Bind(&MojoRendererService::OnError, weak_this_)); |
| } |
| |
| void MojoRendererService::OnAudioRendererInitializeDone(PipelineStatus status) { |
| DVLOG(1) << __FUNCTION__ << ": " << status; |
| DCHECK_EQ(state_, STATE_INITIALIZING) << state_; |
| |
| if (status != PIPELINE_OK) { |
| state_ = STATE_ERROR; |
| audio_renderer_.reset(); |
| client()->OnError(); |
| init_cb_.Run(); |
| init_cb_.reset(); |
| return; |
| } |
| |
| time_source_ = audio_renderer_->GetTimeSource(); |
| |
| state_ = STATE_PLAYING; |
| init_cb_.Run(); |
| init_cb_.reset(); |
| } |
| |
| void MojoRendererService::OnUpdateStatistics(const PipelineStatistics& stats) { |
| NOTIMPLEMENTED(); |
| } |
| |
| void MojoRendererService::UpdateMediaTime() { |
| uint64_t media_time = time_source_->CurrentMediaTime().InMicroseconds(); |
| client()->OnTimeUpdate(media_time, media_time); |
| } |
| |
| void MojoRendererService::SchedulePeriodicMediaTimeUpdates() { |
| // Update media time immediately. |
| UpdateMediaTime(); |
| |
| // Then setup periodic time update. |
| time_update_timer_.Start( |
| FROM_HERE, |
| TimeUpdateInterval(), |
| base::Bind(&MojoRendererService::UpdateMediaTime, weak_this_)); |
| } |
| |
| void MojoRendererService::OnBufferingStateChanged( |
| media::BufferingState new_buffering_state) { |
| DVLOG(2) << __FUNCTION__ << "(" << buffering_state_ << ", " |
| << new_buffering_state << ") "; |
| bool was_waiting_for_enough_data = WaitingForEnoughData(); |
| |
| buffering_state_ = new_buffering_state; |
| |
| // Renderer underflowed. |
| if (!was_waiting_for_enough_data && WaitingForEnoughData()) { |
| PausePlayback(); |
| // TODO(xhwang): Notify client of underflow condition. |
| return; |
| } |
| |
| // Renderer prerolled. |
| if (was_waiting_for_enough_data && !WaitingForEnoughData()) { |
| StartPlayback(); |
| client()->OnBufferingStateChange( |
| static_cast<mojo::BufferingState>(new_buffering_state)); |
| return; |
| } |
| } |
| |
| void MojoRendererService::OnAudioRendererEnded() { |
| DVLOG(1) << __FUNCTION__; |
| |
| if (state_ != STATE_PLAYING) |
| return; |
| |
| DCHECK(!ended_); |
| ended_ = true; |
| |
| if (time_ticking_) |
| PausePlayback(); |
| |
| client()->OnEnded(); |
| } |
| |
| void MojoRendererService::OnError(PipelineStatus error) { |
| client()->OnError(); |
| } |
| |
| bool MojoRendererService::WaitingForEnoughData() const { |
| DCHECK(audio_renderer_); |
| |
| return state_ == STATE_PLAYING && buffering_state_ != BUFFERING_HAVE_ENOUGH; |
| } |
| |
| void MojoRendererService::StartPlayback() { |
| DVLOG(1) << __FUNCTION__; |
| DCHECK_EQ(state_, STATE_PLAYING); |
| DCHECK(!time_ticking_); |
| DCHECK(!WaitingForEnoughData()); |
| |
| time_ticking_ = true; |
| time_source_->StartTicking(); |
| |
| SchedulePeriodicMediaTimeUpdates(); |
| } |
| |
| void MojoRendererService::PausePlayback() { |
| DVLOG(1) << __FUNCTION__; |
| DCHECK(time_ticking_); |
| switch (state_) { |
| case STATE_PLAYING: |
| DCHECK(ended_ || WaitingForEnoughData()) |
| << "Playback should only pause due to ending or underflowing"; |
| break; |
| |
| case STATE_FLUSHING: |
| // It's OK to pause playback when flushing. |
| break; |
| |
| case STATE_UNINITIALIZED: |
| case STATE_INITIALIZING: |
| case STATE_ERROR: |
| NOTREACHED() << "Invalid state: " << state_; |
| break; |
| } |
| |
| time_ticking_ = false; |
| time_source_->StopTicking(); |
| |
| // Cancel repeating time update timer and update the current media time. |
| time_update_timer_.Stop(); |
| UpdateMediaTime(); |
| } |
| |
| } // namespace media |
| |
| MojoResult MojoMain(MojoHandle shell_handle) { |
| mojo::ApplicationRunnerChromium runner(new media::MojoRendererApplication); |
| return runner.Run(shell_handle); |
| } |