Index: chromecast/browser/media/cma_message_filter_host.cc |
diff --git a/chromecast/browser/media/cma_message_filter_host.cc b/chromecast/browser/media/cma_message_filter_host.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..2fc590c3472dc20cf211b85835e154fd3d2380a5 |
--- /dev/null |
+++ b/chromecast/browser/media/cma_message_filter_host.cc |
@@ -0,0 +1,443 @@ |
+// 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 "chromecast/browser/media/cma_message_filter_host.h" |
+ |
+#include <utility> |
+ |
+#include "base/memory/scoped_ptr.h" |
+#include "base/memory/shared_memory.h" |
+#include "base/sync_socket.h" |
+#include "chromecast/browser/media/cma_message_loop.h" |
+#include "chromecast/browser/media/media_pipeline_host.h" |
+#include "chromecast/common/media/cma_messages.h" |
+#include "chromecast/media/cma/backend/video_plane.h" |
+#include "chromecast/media/cma/pipeline/av_pipeline_client.h" |
+#include "chromecast/media/cma/pipeline/media_pipeline_client.h" |
+#include "chromecast/media/cma/pipeline/video_pipeline_client.h" |
+#include "content/public/browser/browser_thread.h" |
+#include "media/base/bind_to_current_loop.h" |
+#include "ui/gfx/geometry/point_f.h" |
+#include "ui/gfx/geometry/quad_f.h" |
+#include "ui/gfx/size.h" |
+ |
+namespace chromecast { |
+namespace media { |
+ |
+#define FORWARD_CALL(arg_pipeline, arg_fn, ...) \ |
+ task_runner_->PostTask( \ |
+ FROM_HERE, \ |
+ base::Bind(&MediaPipelineHost::arg_fn, \ |
+ base::Unretained(arg_pipeline), __VA_ARGS__)) |
+ |
+namespace { |
+ |
+const size_t kMaxSharedMem = 8 * 1024 * 1024; |
+ |
+void DestroyMediaPipeline(scoped_ptr<MediaPipelineHost> media_pipeline) { |
+} |
+ |
+void UpdateVideoSurfaceHost(int surface_id, const gfx::QuadF& quad) { |
+ // Currently supports only one video plane. |
+ CHECK_EQ(surface_id, 0); |
+ |
+ VideoPlane* video_plane = GetVideoPlane(); |
+ video_plane->SetGeometry( |
+ quad, |
+ VideoPlane::COORDINATE_TYPE_GRAPHICS_PLANE); |
+} |
+ |
+} // namespace |
+ |
+CmaMessageFilterHost::CmaMessageFilterHost(int render_process_id) |
+ : content::BrowserMessageFilter(CastMediaMsgStart), |
+ process_id_(render_process_id), |
+ task_runner_(CmaMessageLoop::GetMessageLoopProxy()), |
+ weak_factory_(this) { |
+ weak_this_ = weak_factory_.GetWeakPtr(); |
+} |
+ |
+CmaMessageFilterHost::~CmaMessageFilterHost() { |
+ DCHECK(media_pipelines_.empty()); |
+} |
+ |
+void CmaMessageFilterHost::OnChannelClosing() { |
+ content::BrowserMessageFilter::OnChannelClosing(); |
+ DeleteEntries(); |
+} |
+ |
+void CmaMessageFilterHost::OnDestruct() const { |
+ content::BrowserThread::DeleteOnIOThread::Destruct(this); |
+} |
+ |
+bool CmaMessageFilterHost::OnMessageReceived(const IPC::Message& message) { |
+ bool handled = true; |
+ IPC_BEGIN_MESSAGE_MAP(CmaMessageFilterHost, message) |
+ IPC_MESSAGE_HANDLER(CmaHostMsg_CreateMedia, CreateMedia) |
+ IPC_MESSAGE_HANDLER(CmaHostMsg_DestroyMedia, DestroyMedia) |
+ IPC_MESSAGE_HANDLER(CmaHostMsg_SetCdm, SetCdm) |
+ IPC_MESSAGE_HANDLER(CmaHostMsg_CreateAvPipe, CreateAvPipe) |
+ IPC_MESSAGE_HANDLER(CmaHostMsg_AudioInitialize, AudioInitialize) |
+ IPC_MESSAGE_HANDLER(CmaHostMsg_VideoInitialize, VideoInitialize) |
+ IPC_MESSAGE_HANDLER(CmaHostMsg_StartPlayingFrom, StartPlayingFrom) |
+ IPC_MESSAGE_HANDLER(CmaHostMsg_Flush, Flush) |
+ IPC_MESSAGE_HANDLER(CmaHostMsg_Stop, Stop) |
+ IPC_MESSAGE_HANDLER(CmaHostMsg_SetPlaybackRate, SetPlaybackRate) |
+ IPC_MESSAGE_HANDLER(CmaHostMsg_SetVolume, SetVolume) |
+ IPC_MESSAGE_HANDLER(CmaHostMsg_NotifyPipeWrite, NotifyPipeWrite) |
+ IPC_MESSAGE_HANDLER(CmaHostMsg_NotifyExternalSurface, |
+ NotifyExternalSurface) |
+ IPC_MESSAGE_UNHANDLED(handled = false) |
+ IPC_END_MESSAGE_MAP() |
+ |
+ return handled; |
+} |
+ |
+void CmaMessageFilterHost::DeleteEntries() { |
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); |
+ |
+ for (MediaPipelineMap::iterator it = media_pipelines_.begin(); |
+ it != media_pipelines_.end(); ) { |
+ scoped_ptr<MediaPipelineHost> media_pipeline(it->second); |
+ media_pipelines_.erase(it++); |
+ task_runner_->PostTask( |
+ FROM_HERE, |
+ base::Bind(&DestroyMediaPipeline, base::Passed(&media_pipeline))); |
+ } |
+} |
+ |
+MediaPipelineHost* CmaMessageFilterHost::LookupById(int media_id) { |
+ MediaPipelineMap::iterator it = media_pipelines_.find(media_id); |
+ if (it == media_pipelines_.end()) |
+ return NULL; |
+ return it->second; |
+} |
+ |
+ |
+// *** Handle incoming messages *** |
+ |
+void CmaMessageFilterHost::CreateMedia(int media_id, LoadType load_type) { |
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); |
+ |
+ scoped_ptr<MediaPipelineHost> media_pipeline_host(new MediaPipelineHost()); |
+ MediaPipelineClient client; |
+ client.time_update_cb = ::media::BindToCurrentLoop(base::Bind( |
+ &CmaMessageFilterHost::OnTimeUpdate, weak_this_, media_id)); |
+ client.buffering_state_cb = ::media::BindToCurrentLoop(base::Bind( |
+ &CmaMessageFilterHost::OnBufferingNotification, weak_this_, media_id)); |
+ client.error_cb = ::media::BindToCurrentLoop( |
+ base::Bind(&CmaMessageFilterHost::OnPlaybackError, |
+ weak_this_, media_id, media::kNoTrackId)); |
+ task_runner_->PostTask( |
+ FROM_HERE, |
+ base::Bind(&MediaPipelineHost::Initialize, |
+ base::Unretained(media_pipeline_host.get()), |
+ load_type, client)); |
+ std::pair<MediaPipelineMap::iterator, bool> ret = |
+ media_pipelines_.insert( |
+ std::make_pair(media_id, media_pipeline_host.release())); |
+ |
+ // Check there is no other entry with the same ID. |
+ DCHECK(ret.second != false); |
+} |
+ |
+void CmaMessageFilterHost::DestroyMedia(int media_id) { |
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); |
+ |
+ MediaPipelineMap::iterator it = media_pipelines_.find(media_id); |
+ if (it == media_pipelines_.end()) |
+ return; |
+ |
+ scoped_ptr<MediaPipelineHost> media_pipeline(it->second); |
+ media_pipelines_.erase(it); |
+ task_runner_->PostTask( |
+ FROM_HERE, |
+ base::Bind(&DestroyMediaPipeline, base::Passed(&media_pipeline))); |
+} |
+ |
+void CmaMessageFilterHost::SetCdm(int media_id, |
+ int render_frame_id, |
+ int cdm_id) { |
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); |
+ MediaPipelineHost* media_pipeline = LookupById(media_id); |
+ if (!media_pipeline) |
+ return; |
+ |
+ FORWARD_CALL(media_pipeline, SetCdm, process_id_, render_frame_id, cdm_id); |
+} |
+ |
+void CmaMessageFilterHost::CreateAvPipe( |
+ int media_id, TrackId track_id, size_t shared_mem_size) { |
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); |
+ |
+ base::FileDescriptor foreign_socket_handle; |
+ base::SharedMemoryHandle foreign_memory_handle; |
+ |
+ // A few sanity checks before allocating resources. |
+ MediaPipelineHost* media_pipeline = LookupById(media_id); |
+ if (!media_pipeline || !PeerHandle() || shared_mem_size > kMaxSharedMem) { |
+ Send(new CmaMsg_AvPipeCreated( |
+ media_id, track_id, false, |
+ foreign_memory_handle, foreign_socket_handle)); |
+ return; |
+ } |
+ |
+ // Create the local/foreign sockets to signal media message |
+ // consune/feed events. |
+ // Use CancelableSyncSocket so that write is always non-blocking. |
+ scoped_ptr<base::CancelableSyncSocket> local_socket( |
+ new base::CancelableSyncSocket()); |
+ scoped_ptr<base::CancelableSyncSocket> foreign_socket( |
+ new base::CancelableSyncSocket()); |
+ if (!base::CancelableSyncSocket::CreatePair(local_socket.get(), |
+ foreign_socket.get()) || |
+ foreign_socket->handle() == -1) { |
+ Send(new CmaMsg_AvPipeCreated( |
+ media_id, track_id, false, |
+ foreign_memory_handle, foreign_socket_handle)); |
+ return; |
+ } |
+ |
+ // Shared memory used to convey media messages. |
+ scoped_ptr<base::SharedMemory> shared_memory(new base::SharedMemory()); |
+ if (!shared_memory->CreateAndMapAnonymous(shared_mem_size) || |
+ !shared_memory->ShareToProcess(PeerHandle(), &foreign_memory_handle)) { |
+ Send(new CmaMsg_AvPipeCreated( |
+ media_id, track_id, false, |
+ foreign_memory_handle, foreign_socket_handle)); |
+ return; |
+ } |
+ |
+ // Note: the IPC message can be sent only once the pipe has been fully |
+ // configured. Part of this configuration is done in |
+ // |MediaPipelineHost::SetAvPipe|. |
+ // TODO(erickung): investigate possible memory leak here. |
+ // If the weak pointer in |av_pipe_set_cb| gets invalidated, |
+ // then |foreign_memory_handle| leaks. |
+ base::Closure pipe_read_activity_cb = ::media::BindToCurrentLoop( |
+ base::Bind(&CmaMessageFilterHost::OnPipeReadActivity, weak_this_, |
+ media_id, track_id)); |
+ base::Closure av_pipe_set_cb = ::media::BindToCurrentLoop( |
+ base::Bind(&CmaMessageFilterHost::OnAvPipeSet, weak_this_, |
+ media_id, track_id, |
+ foreign_memory_handle, base::Passed(&foreign_socket))); |
+ task_runner_->PostTask( |
+ FROM_HERE, |
+ base::Bind(&MediaPipelineHost::SetAvPipe, |
+ base::Unretained(media_pipeline), |
+ track_id, |
+ base::Passed(&shared_memory), |
+ pipe_read_activity_cb, |
+ av_pipe_set_cb)); |
+} |
+ |
+void CmaMessageFilterHost::OnAvPipeSet( |
+ int media_id, |
+ TrackId track_id, |
+ base::SharedMemoryHandle foreign_memory_handle, |
+ scoped_ptr<base::CancelableSyncSocket> foreign_socket) { |
+ base::FileDescriptor foreign_socket_handle; |
+ foreign_socket_handle.fd = foreign_socket->handle(); |
+ foreign_socket_handle.auto_close = false; |
+ |
+ // This message can only be set once the pipe has fully been configured |
+ // by |MediaPipelineHost|. |
+ Send(new CmaMsg_AvPipeCreated( |
+ media_id, track_id, true, foreign_memory_handle, foreign_socket_handle)); |
+} |
+ |
+void CmaMessageFilterHost::AudioInitialize( |
+ int media_id, TrackId track_id, const ::media::AudioDecoderConfig& config) { |
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); |
+ MediaPipelineHost* media_pipeline = LookupById(media_id); |
+ if (!media_pipeline) { |
+ Send(new CmaMsg_TrackStateChanged( |
+ media_id, track_id, ::media::PIPELINE_ERROR_ABORT)); |
+ return; |
+ } |
+ |
+ AvPipelineClient client; |
+ client.eos_cb = ::media::BindToCurrentLoop(base::Bind( |
+ &CmaMessageFilterHost::OnEos, weak_this_, media_id, track_id)); |
+ client.playback_error_cb = ::media::BindToCurrentLoop(base::Bind( |
+ &CmaMessageFilterHost::OnPlaybackError, weak_this_, media_id, track_id)); |
+ client.statistics_cb = ::media::BindToCurrentLoop( |
+ base::Bind(&CmaMessageFilterHost::OnStatisticsUpdated, weak_this_, |
+ media_id, track_id)); |
+ |
+ ::media::PipelineStatusCB pipeline_status_cb = ::media::BindToCurrentLoop( |
+ base::Bind(&CmaMessageFilterHost::OnTrackStateChanged, weak_this_, |
+ media_id, track_id)); |
+ task_runner_->PostTask( |
+ FROM_HERE, |
+ base::Bind(&MediaPipelineHost::AudioInitialize, |
+ base::Unretained(media_pipeline), |
+ track_id, client, config, pipeline_status_cb)); |
+} |
+ |
+void CmaMessageFilterHost::VideoInitialize( |
+ int media_id, TrackId track_id, const ::media::VideoDecoderConfig& config) { |
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); |
+ MediaPipelineHost* media_pipeline = LookupById(media_id); |
+ if (!media_pipeline) { |
+ Send(new CmaMsg_TrackStateChanged( |
+ media_id, track_id, ::media::PIPELINE_ERROR_ABORT)); |
+ return; |
+ } |
+ |
+ VideoPipelineClient client; |
+ client.av_pipeline_client.eos_cb = ::media::BindToCurrentLoop( |
+ base::Bind(&CmaMessageFilterHost::OnEos, weak_this_, |
+ media_id, track_id)); |
+ client.av_pipeline_client.playback_error_cb = ::media::BindToCurrentLoop( |
+ base::Bind(&CmaMessageFilterHost::OnPlaybackError, weak_this_, |
+ media_id, track_id)); |
+ client.av_pipeline_client.statistics_cb = ::media::BindToCurrentLoop( |
+ base::Bind(&CmaMessageFilterHost::OnStatisticsUpdated, weak_this_, |
+ media_id, track_id)); |
+ client.natural_size_changed_cb = ::media::BindToCurrentLoop( |
+ base::Bind(&CmaMessageFilterHost::OnNaturalSizeChanged, weak_this_, |
+ media_id, track_id)); |
+ |
+ ::media::PipelineStatusCB pipeline_status_cb = ::media::BindToCurrentLoop( |
+ base::Bind(&CmaMessageFilterHost::OnTrackStateChanged, weak_this_, |
+ media_id, track_id)); |
+ task_runner_->PostTask( |
+ FROM_HERE, |
+ base::Bind(&MediaPipelineHost::VideoInitialize, |
+ base::Unretained(media_pipeline), |
+ track_id, client, config, pipeline_status_cb)); |
+} |
+ |
+void CmaMessageFilterHost::StartPlayingFrom( |
+ int media_id, base::TimeDelta time) { |
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); |
+ MediaPipelineHost* media_pipeline = LookupById(media_id); |
+ if (!media_pipeline) |
+ return; |
+ FORWARD_CALL(media_pipeline, StartPlayingFrom, time); |
+} |
+ |
+void CmaMessageFilterHost::Flush(int media_id) { |
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); |
+ MediaPipelineHost* media_pipeline = LookupById(media_id); |
+ if (!media_pipeline) { |
+ Send(new CmaMsg_MediaStateChanged( |
+ media_id, ::media::PIPELINE_ERROR_ABORT)); |
+ return; |
+ } |
+ ::media::PipelineStatusCB pipeline_status_cb = ::media::BindToCurrentLoop( |
+ base::Bind(&CmaMessageFilterHost::OnMediaStateChanged, weak_this_, |
+ media_id)); |
+ FORWARD_CALL(media_pipeline, Flush, pipeline_status_cb); |
+} |
+ |
+void CmaMessageFilterHost::Stop(int media_id) { |
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); |
+ MediaPipelineHost* media_pipeline = LookupById(media_id); |
+ if (!media_pipeline) |
+ return; |
+ task_runner_->PostTask( |
+ FROM_HERE, |
+ base::Bind(&MediaPipelineHost::Stop, |
+ base::Unretained(media_pipeline))); |
+} |
+ |
+void CmaMessageFilterHost::SetPlaybackRate( |
+ int media_id, float playback_rate) { |
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); |
+ MediaPipelineHost* media_pipeline = LookupById(media_id); |
+ if (!media_pipeline) |
+ return; |
+ FORWARD_CALL(media_pipeline, SetPlaybackRate, playback_rate); |
+} |
+ |
+void CmaMessageFilterHost::SetVolume( |
+ int media_id, TrackId track_id, float volume) { |
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); |
+ MediaPipelineHost* media_pipeline = LookupById(media_id); |
+ if (!media_pipeline) |
+ return; |
+ FORWARD_CALL(media_pipeline, SetVolume, track_id, volume); |
+} |
+ |
+void CmaMessageFilterHost::NotifyPipeWrite(int media_id, TrackId track_id) { |
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); |
+ MediaPipelineHost* media_pipeline = LookupById(media_id); |
+ if (!media_pipeline) |
+ return; |
+ FORWARD_CALL(media_pipeline, NotifyPipeWrite, track_id); |
+} |
+ |
+void CmaMessageFilterHost::NotifyExternalSurface( |
+ int surface_id, |
+ const gfx::PointF& p0, const gfx::PointF& p1, |
+ const gfx::PointF& p2, const gfx::PointF& p3) { |
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); |
+ task_runner_->PostTask( |
+ FROM_HERE, |
+ base::Bind(&UpdateVideoSurfaceHost, surface_id, |
+ gfx::QuadF(p0, p1, p2, p3))); |
+} |
+ |
+// *** Browser to renderer messages *** |
+ |
+void CmaMessageFilterHost::OnMediaStateChanged( |
+ int media_id, ::media::PipelineStatus status) { |
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); |
+ Send(new CmaMsg_MediaStateChanged(media_id, status)); |
+} |
+ |
+void CmaMessageFilterHost::OnTrackStateChanged( |
+ int media_id, TrackId track_id, ::media::PipelineStatus status) { |
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); |
+ Send(new CmaMsg_TrackStateChanged(media_id, track_id, status)); |
+} |
+ |
+void CmaMessageFilterHost::OnPipeReadActivity(int media_id, TrackId track_id) { |
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); |
+ Send(new CmaMsg_NotifyPipeRead(media_id, track_id)); |
+} |
+ |
+void CmaMessageFilterHost::OnTimeUpdate( |
+ int media_id, |
+ base::TimeDelta media_time, |
+ base::TimeDelta max_media_time, |
+ base::TimeTicks stc) { |
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); |
+ Send(new CmaMsg_TimeUpdate(media_id, |
+ media_time, max_media_time, stc)); |
+} |
+ |
+void CmaMessageFilterHost::OnBufferingNotification( |
+ int media_id, ::media::BufferingState state) { |
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); |
+ Send(new CmaMsg_BufferingNotification(media_id, state)); |
+} |
+ |
+void CmaMessageFilterHost::OnEos(int media_id, TrackId track_id) { |
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); |
+ Send(new CmaMsg_Eos(media_id, track_id)); |
+} |
+ |
+void CmaMessageFilterHost::OnPlaybackError( |
+ int media_id, TrackId track_id, ::media::PipelineStatus status) { |
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); |
+ Send(new CmaMsg_PlaybackError(media_id, track_id, status)); |
+} |
+ |
+void CmaMessageFilterHost::OnStatisticsUpdated( |
+ int media_id, TrackId track_id, const ::media::PipelineStatistics& stats) { |
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); |
+ Send(new CmaMsg_PlaybackStatistics(media_id, track_id, stats)); |
+} |
+ |
+void CmaMessageFilterHost::OnNaturalSizeChanged( |
+ int media_id, TrackId track_id, const gfx::Size& size) { |
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); |
+ Send(new CmaMsg_NaturalSizeChanged(media_id, track_id, size)); |
+} |
+ |
+} // namespace media |
+} // namespace chromecast |