From 5666d3d06b9955ee4263fb31a5ef2d725644cbc7 Mon Sep 17 00:00:00 2001 From: Anton Obzhirov & Julien Isorce Date: Wed, 24 Jun 2015 19:10:05 +0100 Subject: [PATCH] Add GStreamer backend for media playback in Chromium. - Media Process - GstGL - GstPlayer Doc: https://github.com/Samsung/ChromiumGStreamerBackend/ Signed-off-by: Anton Obzhirov Signed-off-by: Julien Isorce Updated version (104 followup commits) backported to Chromium 45 and squashed into one commit by Kevin Kofler . This version has the binary documentation files from images/ ripped out. --- README.md | 397 +++++++ base/message_loop/message_pump_glib.cc | 11 + base/threading/thread_restrictions.h | 6 + build/common.gypi | 9 + build/gn_migration.gypi | 2 +- build/install-build-deps.sh | 17 + build/linux/system.gyp | 27 + chrome/app/chrome_crash_reporter_client.cc | 5 + chrome/app/chrome_main_delegate.cc | 6 + chrome/app/generated_resources.grd | 10 +- chrome/browser/about_flags.cc | 7 + chrome/browser/chrome_content_browser_client.cc | 9 + .../providers/child_process_task.cc | 5 + .../child_process_resource_provider.cc | 5 + chrome/utility/extensions/extensions_handler.cc | 4 + .../media_galleries/media_metadata_parser.cc | 5 + content/app/content_main_runner.cc | 22 + content/browser/browser_main_loop.cc | 26 + content/browser/browser_main_loop.h | 3 + .../browser/gpu/gpu_data_manager_impl_private.cc | 9 + .../loader/resource_dispatcher_host_impl.cc | 1 + content/browser/media/media_data_manager_impl.cc | 78 ++ content/browser/media/media_data_manager_impl.h | 96 ++ .../media/media_data_manager_impl_private.cc | 118 ++ .../media/media_data_manager_impl_private.h | 83 ++ content/browser/media/media_process_host.cc | 583 +++++++++ content/browser/media/media_process_host.h | 179 +++ .../browser/media/media_process_host_ui_shim.cc | 166 +++ content/browser/media/media_process_host_ui_shim.h | 84 ++ .../browser/renderer_host/media_message_filter.cc | 76 ++ .../browser/renderer_host/media_message_filter.h | 58 + .../renderer_host/render_process_host_impl.cc | 17 + .../renderer_host/render_process_host_impl.h | 8 + content/common/child_process_host_impl.cc | 17 + content/common/content_constants_internal.cc | 3 + content/common/content_constants_internal.h | 3 + content/common/content_message_generator.h | 5 + .../common/gpu/client/command_buffer_metrics.cc | 10 + content/common/gpu/client/command_buffer_metrics.h | 3 + .../common/gpu/client/command_buffer_proxy_impl.cc | 39 + .../common/gpu/client/command_buffer_proxy_impl.h | 8 + content/common/gpu/gpu_channel.cc | 32 + content/common/gpu/gpu_channel.h | 7 + content/common/gpu/gpu_command_buffer_stub.cc | 63 + content/common/gpu/gpu_command_buffer_stub.h | 10 + content/common/gpu/gpu_messages.h | 8 + content/common/gpu/gpu_process_launch_causes.h | 3 + content/common/media/media_channel.cc | 369 ++++++ content/common/media/media_channel.h | 154 +++ content/common/media/media_channel_filter.cc | 97 ++ content/common/media/media_channel_filter.h | 99 ++ content/common/media/media_channel_host.cc | 151 +++ content/common/media/media_channel_host.h | 129 ++ content/common/media/media_config.h | 12 + content/common/media/media_messages.h | 83 ++ .../media/media_player_messages_enums_gstreamer.h | 15 + .../common/media/media_player_messages_gstreamer.h | 176 +++ content/common/media/media_process_launch_causes.h | 23 + content/common/sandbox_init_mac.cc | 6 + .../common/sandbox_linux/bpf_media_policy_linux.cc | 535 +++++++++ .../common/sandbox_linux/bpf_media_policy_linux.h | 64 + content/common/sandbox_linux/sandbox_linux.cc | 1 + .../sandbox_linux/sandbox_seccomp_bpf_linux.cc | 15 + content/content.gyp | 21 + content/content_browser.gypi | 16 + content/content_common.gypi | 33 + content/content_media.gypi | 44 + content/content_renderer.gypi | 43 + content/media/gstreamer/gpuprocess/client_egl.cc | 162 +++ content/media/gstreamer/gpuprocess/client_egl.h | 30 + .../gpuprocess/gstglcontext_gpu_process.c | 147 +++ .../gpuprocess/gstglcontext_gpu_process.h | 74 ++ .../gpuprocess/gstgldisplay_gpu_process.c | 44 + .../gpuprocess/gstgldisplay_gpu_process.h | 52 + .../gstreamer/gpuprocess/gstglwindow_gpu_process.c | 48 + .../gstreamer/gpuprocess/gstglwindow_gpu_process.h | 75 ++ content/media/gstreamer/gst_chromium_aes_ctr.cc | 77 ++ content/media/gstreamer/gst_chromium_aes_ctr.h | 24 + .../gst_chromium_common_encryption_decryptor.cc | 469 ++++++++ .../gst_chromium_common_encryption_decryptor.h | 39 + .../media/gstreamer/gst_chromium_http_source.cc | 903 ++++++++++++++ content/media/gstreamer/gst_chromium_http_source.h | 85 ++ content/media/gstreamer/gst_chromium_media_src.cc | 621 ++++++++++ content/media/gstreamer/gst_chromium_media_src.h | 82 ++ content/media/gstreamer/media_player_gstreamer.cc | 802 +++++++++++++ content/media/gstreamer/media_player_gstreamer.h | 190 +++ content/media/in_process_media_thread.cc | 39 + content/media/in_process_media_thread.h | 42 + content/media/media_child_thread.cc | 330 ++++++ content/media/media_child_thread.h | 137 +++ content/media/media_main.cc | 167 +++ content/media/media_process.cc | 13 + content/media/media_process.h | 22 + content/public/browser/media_data_manager.h | 43 + .../public/browser/media_data_manager_observer.h | 26 + content/public/common/content_switches.cc | 21 + content/public/common/content_switches.h | 9 + content/public/common/process_type.h | 3 + content/public/common/sandbox_type.h | 5 + content/renderer/media/audio_decoder.cc | 4 + .../media/gstreamer/webmediaplayer_gstreamer.cc | 1245 ++++++++++++++++++++ .../media/gstreamer/webmediaplayer_gstreamer.h | 390 ++++++ .../media/gstreamer/webmediasource_gstreamer.cc | 128 ++ .../media/gstreamer/webmediasource_gstreamer.h | 68 ++ .../media/gstreamer/websourcebuffer_gstreamer.cc | 164 +++ .../media/gstreamer/websourcebuffer_gstreamer.h | 67 ++ content/renderer/pepper/video_decoder_shim.cc | 6 + content/renderer/render_frame_impl.cc | 33 + content/renderer/render_thread_impl.cc | 52 +- content/renderer/render_thread_impl.h | 19 +- content/shell/app/shell_crash_reporter_client.cc | 5 + .../shell/browser/shell_content_browser_client.cc | 9 + gpu/command_buffer/service/feature_info.cc | 14 + gpu/config/gpu_blacklist.cc | 3 + ipc/ipc_message_start.h | 3 + media/base/cdm_key_information.h | 3 + media/base/media_switches.cc | 14 + media/base/media_switches.h | 5 + media/base/mime_util.cc | 2 +- media/blink/buffered_data_source.cc | 27 +- media/blink/buffered_data_source.h | 20 + media/blink/buffered_resource_loader.cc | 47 +- media/blink/buffered_resource_loader.h | 14 +- media/blink/encrypted_media_player_support.cc | 16 + media/blink/encrypted_media_player_support.h | 21 +- media/blink/webmediaplayer_impl.cc | 4 + media/cdm/aes_decryptor.cc | 6 +- media/cdm/cenc_utils.cc | 20 + media/cdm/proxy_decryptor.cc | 28 +- media/cdm/proxy_decryptor.h | 26 + .../linux/syscall_broker/broker_file_permission.cc | 2 +- .../linux/syscall_broker/broker_file_permission.h | 13 +- sandbox/linux/syscall_broker/broker_host.cc | 10 + ui/gl/gl_image_egl.cc | 13 + ui/gl/gl_image_egl.h | 15 + ui/gl/gl_surface_egl.cc | 5 + ui/gl/gl_surface_x11.cc | 3 + 137 files changed, 11711 insertions(+), 23 deletions(-) create mode 100644 README.md create mode 100644 content/browser/media/media_data_manager_impl.cc create mode 100644 content/browser/media/media_data_manager_impl.h create mode 100644 content/browser/media/media_data_manager_impl_private.cc create mode 100644 content/browser/media/media_data_manager_impl_private.h create mode 100644 content/browser/media/media_process_host.cc create mode 100644 content/browser/media/media_process_host.h create mode 100644 content/browser/media/media_process_host_ui_shim.cc create mode 100644 content/browser/media/media_process_host_ui_shim.h create mode 100644 content/browser/renderer_host/media_message_filter.cc create mode 100644 content/browser/renderer_host/media_message_filter.h create mode 100644 content/common/media/media_channel.cc create mode 100644 content/common/media/media_channel.h create mode 100644 content/common/media/media_channel_filter.cc create mode 100644 content/common/media/media_channel_filter.h create mode 100644 content/common/media/media_channel_host.cc create mode 100644 content/common/media/media_channel_host.h create mode 100644 content/common/media/media_config.h create mode 100644 content/common/media/media_messages.h create mode 100644 content/common/media/media_player_messages_enums_gstreamer.h create mode 100644 content/common/media/media_player_messages_gstreamer.h create mode 100644 content/common/media/media_process_launch_causes.h create mode 100644 content/common/sandbox_linux/bpf_media_policy_linux.cc create mode 100644 content/common/sandbox_linux/bpf_media_policy_linux.h create mode 100644 content/content_media.gypi create mode 100644 content/media/gstreamer/gpuprocess/client_egl.cc create mode 100644 content/media/gstreamer/gpuprocess/client_egl.h create mode 100644 content/media/gstreamer/gpuprocess/gstglcontext_gpu_process.c create mode 100644 content/media/gstreamer/gpuprocess/gstglcontext_gpu_process.h create mode 100644 content/media/gstreamer/gpuprocess/gstgldisplay_gpu_process.c create mode 100644 content/media/gstreamer/gpuprocess/gstgldisplay_gpu_process.h create mode 100644 content/media/gstreamer/gpuprocess/gstglwindow_gpu_process.c create mode 100644 content/media/gstreamer/gpuprocess/gstglwindow_gpu_process.h create mode 100644 content/media/gstreamer/gst_chromium_aes_ctr.cc create mode 100644 content/media/gstreamer/gst_chromium_aes_ctr.h create mode 100644 content/media/gstreamer/gst_chromium_common_encryption_decryptor.cc create mode 100644 content/media/gstreamer/gst_chromium_common_encryption_decryptor.h create mode 100644 content/media/gstreamer/gst_chromium_http_source.cc create mode 100644 content/media/gstreamer/gst_chromium_http_source.h create mode 100644 content/media/gstreamer/gst_chromium_media_src.cc create mode 100644 content/media/gstreamer/gst_chromium_media_src.h create mode 100644 content/media/gstreamer/media_player_gstreamer.cc create mode 100644 content/media/gstreamer/media_player_gstreamer.h create mode 100644 content/media/in_process_media_thread.cc create mode 100644 content/media/in_process_media_thread.h create mode 100644 content/media/media_child_thread.cc create mode 100644 content/media/media_child_thread.h create mode 100644 content/media/media_main.cc create mode 100644 content/media/media_process.cc create mode 100644 content/media/media_process.h create mode 100644 content/public/browser/media_data_manager.h create mode 100644 content/public/browser/media_data_manager_observer.h create mode 100644 content/renderer/media/gstreamer/webmediaplayer_gstreamer.cc create mode 100644 content/renderer/media/gstreamer/webmediaplayer_gstreamer.h create mode 100644 content/renderer/media/gstreamer/webmediasource_gstreamer.cc create mode 100644 content/renderer/media/gstreamer/webmediasource_gstreamer.h create mode 100644 content/renderer/media/gstreamer/websourcebuffer_gstreamer.cc create mode 100644 content/renderer/media/gstreamer/websourcebuffer_gstreamer.h diff --git a/README.md b/README.md new file mode 100644 index 0000000..bc7770d --- /dev/null +++ b/README.md @@ -0,0 +1,397 @@ +Chromium GStreamer Backend +========================== + +[Chromium](https://www.chromium.org/Home), [GStreamer](http://gstreamer.freedesktop.org/features/), [MediaProcess](#media-process-overview), [Sandbox](#media-process-sandbox), [MSE](#mse), [EME](#eme), [Zero-Copy](#zero-copy), [GstPlayer](#media-process-overview), [GstGL](#media-process-stack), [GstChromiumHttpSrc](#media-process-stack), [Build](#build), [Tips](#tips), [Maintenance](#maintenance), [UnitTests](#build-and-run-unit-tests), [Upstream](#contributing-to-upstream-chromium), [Issues](#issues-and-roadmap), [GstConf2015](#talk-at-gstreamer-conference-2015) + +### Current branching point from official chromium/src ### +d903850ee3b13ee59aadea96cea7e52f0d4bd8c4 (Sun Jul 5) +It will be rebased every week. + +### Project description ### +This is an experimental project that aims to have GStreamer as media player in Chromium browser. +We introduced a dedicated [Media Process](#media-process-overview) that maintains GStreamer pipelines. +The Media Process is [sandboxed](#media-process-sandbox) with same level as [GPU Process](https://code.google.com/p/chromium/wiki/LinuxSandboxing). +[GstPlayer](http://gstreamer.freedesktop.org/data/doc/gstreamer/head/gst-plugins-bad-libs/html/gst-plugins-bad-libs-gstplayer.html) is used to construct and to maintain GStreamer pipelines. +Each [HTML5 video tag](http://www.w3schools.com/html/html5_video.asp) is backed by a GStreamer pipeline that lives in the Media Process. + +### Licence ### +Same as official chromium/src source code: [here](https://chromium.googlesource.com/chromium/src.git/+/master/LICENSE). + +### Current supported features ### +* Progressive streaming (http) +* Adaptive streaming (hls, dash) +* Media Source Extension (Youtube) +* Encrypted Media Extension (Protected content) +* Zero-copy (dmabuf export / EGLImage import / Cross process) + +### Talk at GStreamer Conference 2015 ### +* Live: [Link1](https://gstconf.ubicast.tv/videos/chromium-a-new-media-backend-based-on-gstreamer_/) +[Link2](https://gstconf.ubicast.tv/permalink/v1253c6ef409dqo1vb5r/) +* Slides: [Link1](https://github.com/Samsung/ChromiumGStreamerBackend/blob/master/images/chromium_gstreamer_backend.pdf) +[Link2](https://github.com/Samsung/ChromiumGStreamerBackend/blob/master/images/chromium_gstreamer_backend.odp) + +### List of modified and added files ### +05/10/2015: (just to give an idea of the delta from official chromium/src) +79 files modified, 996 insertions(+), 23 deletions(-) +64 files added, 10474 insertions(+) +git diff --diff-filter=AM --stat sha-1 HEAD + +### Build ### +Start from a working build of official [Chromium Browser](https://chromium.googlesource.com/chromium/src/+/master/docs/linux_build_instructions.md). +Then refer to this [section](#build-steps) to build the Chromium GStreamer Backend. + +### Media Process overview ### +There is only one Media Process no matter the number of video tags in the same pages or the number of pages (~ tabulations). +GStreamer is almost only used through the a high level API [GstPlayer](http://gstreamer.freedesktop.org/data/doc/gstreamer/head/gst-plugins-bad-libs/html/gst-plugins-bad-libs-gstplayer.html) (ex: gst_player_new. gst_player_play, gst_player_pause, gst_player_seek) +It reduces GStreamer code lines a lot and it avoids doing mistakes when using more low level GStreamer API. +Exception for the video rendering part because GstGL needs to be setup to use GPU Process. In short we pass an external get_process_addr to GstGL. +Indeed the Media Process does not load OpenGL libraries; it uses chromium API to forward OpenGL calls to GPU Process which is the only sandboxed +process that is allowed to load GL libraries. +Exception also for the [GstChromiumHttpSrc](#media-process-stack). It is a GStreamer element that wraps chromium API to load an url. +![](https://github.com/Samsung/ChromiumGStreamerBackend/blob/master/images/chromium_media_process_overview.png?raw=true) + +__ + +### Media Process stack ### +**GstGLContextGPUProcess:** +A new backend that allows to build the GstGL’s vtable from a given get_proc_addr, i.e. chromium::gles2::GetGLFunctionPointer. See [gpu-accelerated-compositing-in-chrome](https://www.chromium.org/developers/design-documents/gpu-accelerated-compositing-in-chrome) “From the client's perspective, an application has the option to either write commands directly into the command buffer or use the GL ES 2.0 API via a client side library that we provide which handles the serialization behind the scenes. Both the compositor and WebGL currently use the GL ES client side library for convenience. On the server side, commands received via the command buffer are converted to calls into either desktop OpenGL or Direct3D via ANGLE.” The Media Process has its own connection to the GPU Process and it uses this second mechanism to forward gl calls it. In the Media Process all gl calls happen in the GLThread. + +**MediaPlayerGStreamer:** Created in the Media Process for each video tag. It receives player commands (load, play, pause, seek) from the WebMediaPlayerGStreamer that lives a Renderer Process. It uses the gst-player library to play a stream. In the handler of glimagesink “client-draw” signal, the current gltexture is wrapped using glGenMailboxCHROMIUM/glProduceTextureDirectCHROMIUM (see mailbox in [gpu-accelerated-compositing-in-chrome](https://www.chromium.org/developers/design-documents/gpu-accelerated-compositing-in-chrome)). The mailbox’s name for each gltexture is forwarded to WebMediaPlayerGStreamer through IPC (MediaChannel). But this can be avoided by making MediaPlayerGStreamer inherits from cc::VideoFrameProvider in order to avoid sending the gltexture id to Renderer Process (to be sync with the web compositor). Indeed according to [oop-iframes-rendering](https://www.chromium.org/developers/design-documents/oop-iframes) and [oop-iframes-rendering](https://www.chromium.org/developers/design-documents/oop-iframes/oop-iframes-rendering) it is theoretically possible to make our Media Process be a kind of SubFrame renderer. By registering a cc::VideoLayer in the MediaProcess, the ubercompositor will do the synchronisation with the Frame in the RendererProcess. We suspect it is all about having a cc::VideoFrameProvider::Client in MediaProcess. + +**WebMediaPlayerGStreamer:** Created in a Renderer Process for each video tag. It inherits from blink::WebMediaPlayer and cc::VideoFrameProvider. The gltexture is retrieved from a mailbox name and wrapped into a media::VideoFrame using media::VideoFrame::WrapNativeTexture. At any time the compositing engine is free to call GetCurrentFrame(). This late part can be avoided, see MediaPlayerGStreamer description. + +**GstChromiumHttpSrc:** GStreamer element that wraps the chromium media::BufferedDataSource and instantiate a content::WebURLLoader . It was not possible to re-use WebKitWebSrc because some interfaces are missing or has been removed like PlatformMediaResourceLoader. It uses some parts of WebKitWebSrc though but not sure if it is necessary. Also It does not use appsrc comparing to WebKitWebSrc. It is more similar to filesrc due to media::BufferedDataSource design. That’s clearly an area to improve, maybe we can get rid of media::BufferedDataSource and implement missing interface in order to re-use WebKitWebSrc. Indeed internally it uses ResourceDispatcher, see [multi-process-resource-loading](https://www.chromium.org/developers/design-documents/multi-process-resource-loading). Or maybe we can design a complete new element and interfaces that could be shared between blink and WebKit. + +![](https://github.com/Samsung/ChromiumGStreamerBackend/blob/master/images/chromium_media_process_stack.png?raw=true) + +__ + +### Media Process sandbox ### +It has limited access to resources. For example it cannot create network sockets. It has to use +chromium API in order to ask the Browser Process to create a socket. This is similar to Renderer Process. +Also all system calls are filters using linux kernel feature Seccomp-BPF (link). + +We opted for similar design as GPU Process sandbox, using Seccomp-BPF, see [chromium sandbox doc](https://code.google.com/p/chromium/wiki/LinuxSandboxing). All the following sequence is existing sequence for GPU Process. In some sort the Media Process just defines its own policy for the system calls, see [bpf_media_policy_linux.cc](https://github.com/Samsung/ChromiumGStreamerBackend/blob/45.0.2440.3_gst/content/common/sandbox_linux/bpf_media_policy_linux.cc). A main difference is that the Media Process allow loading GStreamer plugins dynamically. But the list can be restricted and open is done in +the brocker process. + +In Media Process main the sandbox is setup before any thread creation. Also there is a preSansbox step where it is possible to dlopen some particular resources. A routine is executed in one go for each system call. In this loop it is decided to allow the system call number, to refuse it or to add a handler for emulation. All of this is setup through Seccomp-BPF. A trap handler (SECCOMP_RET_TRAP), is installed for open and access. Then when starting the sandbox the process forks itself (see chromium/src/sandbox/linux/syscall_broker/broker_process ::BrokerProcess::Init). The child becomes the so called Media Broker Process which instantiates a BrokerHost. The Media Process instantiates a BrokerProcessClient. When a open or access happens it sends a cachable SIGSYS. In this trap handler the BrokerClient emulates the open/access call. It actually asks the BrokerProcessHost, through IPC, to do the real call. Depending on the policy that has been setup the path is allowed or rejected. The return value is passed back to the BrokerClient, i.e. the Media Process, for usage if the call has been accepted. + +![](https://github.com/Samsung/ChromiumGStreamerBackend/blob/master/images/chromium_media_process_sandbox.png?raw=true) + +__ + +### Media Process IPC ### + +IPC between media process and render process is maintained via browser process. Browser process MediaProcessHost is initialized with a connection to media process during the browser start up. Later, WebMediaPlayerDispatcher is used when the render process decides to create a media player for html media element. A first message is sent to media process. It establishes a direct IPC channel from render process to media process is created. + +Establish media channel request comes from WebMediaPlayerGStreamer to RenderThreadImpl (main render thread), which creates MediaChannelHost for render process and sends establish channel message to MediaMessageFilter in browser process, which redirects the message through MediaProcessHost to media process. + +Media process, upon receiving the request, establishes the channel (MediaChannel) with the help of MediaChannelFilter and sends back the channel handler which is passed then by browser process to the render process media player and used to further IPC communication between render process and media process. Any messages from media process to render process get filtered by MediaChannelHost and get dispatched by WebMediaPlayerMessageDispatcher to the right WebMediaPlayergStreamer instance. + +For any messages from WebMediaPlayerGStreamer WebMediaPlayerMessageDispatcher dispatches messages via MediaChannelHost to MediaChannel in media process. MediaChannel maps the message to the associated media player and call the callback. + +![](https://github.com/Samsung/ChromiumGStreamerBackend/blob/master/images/chromium_media_process_ipc.png?raw=true) + +__ + +### MSE ### + +[Media Source Extensions specification](https://w3c.github.io/media-source/). + +When load_type param of blink::WebMediaPlayer::load is equal to LoadTypeMediaSource the url is formated to start with mediasourceblob://. + +It allows GstPlayer's pipeline to select GstChromiumMediaSrc. This GstBin adds and removes appsrc elements when receiving AddSourceBuffer and RemoveSourceBuffer. + +Encoded data is currently sent through IPC message. We can later consider using shared memory but it seems efficient enough like. +And it is also what Android does. + +Currently seeking is not implemented but WebKitGTK does not support it too. +Also it exists on-going work somewhere. And it should be easier to handle it with future multiappsrc element. + +![](https://github.com/Samsung/ChromiumGStreamerBackend/blob/master/images/chromium_media_process_mse.png?raw=true) + +__ + +### EME ### + +[Encrypted Media Extensions specification](http://www.w3.org/TR/encrypted-media/). + +ChromiumCommonEncryptionDecrypt GStreamer element factory is registered in MediaPlayerGStreamer +to decrypt encrypted streams inside mp4 and webm containers. + +When MediaPlayerGStreamer receives a need key asynchronous event from ChromiumCommonEncryptionDecrypt +(it is triggered by GStreamer protection event) it passes an initial data from encrypted stream pssh box +to to the render process. + +On key needed event WebMediaPlayerGStreamer passes the initial data from the media process to the default CDM. +It registers OnCdmKeysReady callback in order to receive a parsed key data from the web application. +Then new keys are sent back to the media process. + +On getting add key IPC message from the render process MediaPlayerGStreamer passes the key down +to ChromiumCommonEncryptionDecrypt element, which unblocks and starts decrypting of the encrypted frame in place. + +![](https://github.com/Samsung/ChromiumGStreamerBackend/blob/master/images/chromium_media_process_eme.png?raw=true) + +__ + +### Zero-Copy ### + +[Khronos specification](https://www.khronos.org/registry/egl/extensions/EXT/EGL_EXT_image_dma_buf_import.txt). + +The main idea is to keep decoded frames in GPU memory all the time. In other words, any round trip to CPU memory has to be avoided. +As the rendering is done through OpenGL, the decoded surface has to be converted to a GL texture. + +2 solutions depending on the HW: +* Export the HW decoded surface as a DMA buffer in Media Process. Then import the DMA buffer into an EGLImage in GPU Process. +When using [VA-API](http://cgit.freedesktop.org/libva/) and [gstreamer-vaapi](https://github.com/01org/gstreamer-vaapi/commits/master) +it means exporting the surface using VaAcquireBufferHandle and importing the DMA buffer using EGL_EXT_image_dma_buf_import in the GPU Process. +* Export from GPU Process using [EGL_MESA_image_dma_buf_export](https://www.khronos.org/registry/egl/extensions/MESA/EGL_MESA_image_dma_buf_export.txt) +and import in Media Process. This second way is required when using [gst-omx](http://cgit.freedesktop.org/gstreamer/gst-omx/) +that wraps [OpenMAX](https://www.khronos.org/openmax/il/) + +In our experimentation we have selected the first solution because EGL_EXT_image_dma_buf_import is in EXT. +Whereas EGL_MESA_image_dma_buf_export is still under MESA specific extension. Though it should move to EXT as some point. +To experiment that on desktop openmax backend provided by Gallium3D needs to support [Tizonia](https://github.com/tizonia/tizonia-openmax-il/issues/116) first instead of Bellagio. +It will be similar improvements we made for [vaapi backend provided by Gallium3D](http://cgit.freedesktop.org/mesa/mesa/log/?qt=author&q=Julien+Isorce). + +For additional information see slides 17, 18, 19, 20 and the Demo2 from [live](https://gstconf.ubicast.tv/permalink/v1253c6ef409dqo1vb5r/) or +[slides](https://github.com/Samsung/ChromiumGStreamerBackend/blob/master/images/chromium_gstreamer_backend.pdf) + +The following diagram exposes the call stack using Gallium drivers ("nouveau" in this case) but we also support intel vaapi driver. +In theory it should work on AMD gpu because our solution is generic in a sense that we support all Gallium drivers. +![](https://github.com/Samsung/ChromiumGStreamerBackend/blob/master/images/chromium_media_process_zero_copy.png?raw=true) + +__ + +### HTML5 video element ### +``` bash +# Progressive streaming: +http://www.w3schools.com/html/mov_bbb.ogg +http://www.w3schools.com/html/html5_video.asp + + + +# Adaptive streaming + + + +``` + +### Build steps ### +``` bash +# GStreamer +gstreamer >= 1.8 is required. + +# clone official chromium repositories +git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git (then add it in front of your PATH) +git clone https://chromium.googlesource.com/chromium/chromium + +# fetch and sync all sub repos. (run all of this from chromium directory, not chromium/src) +fetch --nohooks chromium # only the very first time +git rebase-update +gclient sync # see proxy section +gclient runhooks # see proxy section +build/install-build-deps.sh + +# go to chromium/src and build everything. +cd src +build/install-build-deps.sh +ninja -C out/Release chrome + +# checkout Chromium GStreamer Backend +git remote add github_gstbackend https://github.com/Samsung/ChromiumGStreamerBackend +git fetch github_gstbackend +git checkout -b gstbackend --track github_gstbackend/master +git replace 2b332f507cc9739715b523778183ad20e64a8fcd 1c62327db90915de73cecf256f06cffc567a0be1 + +# build Chromium GStreamer Backend +cd .. # to go to root chromium directory +git rebase-update +gclient sync # see proxy section +gclient runhooks # see proxy section +cd src + +# 2 ways to generate ninja build files: +GYP (old but stable so we recommend it for now) and GN (new from a few months) + +# Using GYP to generate ninja build files +GYP_DEFINES="proprietary_codecs=1" build/gyp_chromium -D component=shared_library # if icecc then add linux_use_debug_fission=0 linux_use_bundled_binutils=0 clang=0 +ninja -C out/Release chrome chrome_sandbox -jN # if icecc -j60 + +# Using GN to generate ninja build files +gn clean out/mybuild/ +gn args out/mybuild --list +gn args out/mybuild +It should open a file then put the following flags inside: + is_debug = false + use_debug_fission = false + linux_use_bundled_binutils = false + is_clang = false + proprietary_codecs = true + is_component_build = true + enable_nacl = false + media_use_ffmpeg = false + media_use_libvpx = false + media_use_libwebm = false +ninja -C out/mybuild chrome chrome_sandbox -jN # if icecc -j60 + +# Run without any sandbox +./out/Release/chrome --no-sandbox http://www.w3schools.com/html/mov_bbb.ogg + +# Run while disabling setuid and media sandbox +./out/Release/chrome --disable-media-sandbox --disable-setuid-sandbox http://www.w3schools.com/html/mov_bbb.ogg + +# Run with all sandbox +CHROME_DEVEL_SANDBOX=out/Release/chrome_sandbox ./out/Release/chrome http://www.w3schools.com/html/mov_bbb.ogg + +# Run with EME enabled: +./out/Release/chrome --no-sandbox --enable-prefixed-encrypted-media http://yt-dash-mse-test.commondatastorage.googleapis.com/unit-tests/2015.html + +# Run with zero-copy decoding/rendering: +gst-inspect-1.0 vaapi +LIBVA_DRIVER_NAME=gallium vainfo # or LIBVA_DRIVER_NAME=i965 if you have an intel gpu, uses gallium otherwise +./out/Release/chrome --no-sandbox --use-gl=egl http://www.w3schools.com/html/html5_video.asp +``` + +### Proxy ### +``` bash +# touch ~/.boto and copy the 3 following lines inside ~/.boto +[Boto] +proxy = _proxy_ip +proxy_port = _proxy_port + +# run +NO_AUTH_BOTO_CONFIG=~/.boto gclient sync +NO_AUTH_BOTO_CONFIG=~/.boto gclient runhooks +``` + +### Maintenance ### +``` bash +# We have not managed to push original history of chromium because: +# "remote: error: File chrome_frame/tools/test/reference_build/chrome_frame/chrome_dll.pdb is +# 124.32 MB; this exceeds GitHub's file size limit of 100.00 MB" +# This file is not there anymore but it was there in the past. +# Even using tools like "bfg --strip-blobs-bigger-than 50M" it will change all the next SHA +# from the point it strips a file. +# Also there is no official chromium/src repo on github that we could fork. + +# solution: truncate history to a point where there is not file bigger than 100.00 MB in order +# to push to github. +# We use "git replace" to allow rebasing between our truncated branch and original branch. + +# fake our branch point because we truncated the history +git replace 64334b71ec2d2e4a8bf0cf5ca7e2dd18d90db0cb 2553ff05b802a94ef281e647874d37941eefd154 + +# replay chromium original upstream commits on top of our branch with truncated history +git checkout NEW_ORIGIN_SHA +git checkout -b master_new +git rebase 64334b71ec2d2e4a8bf0cf5ca7e2dd18d90db0cb +git replace NEW_ORIGIN_SHA $(git rev-parse HEAD) + +# replay gst backend +git checkout -b gstbackend --track github_gstbackend/master +git rebase master_new +git push github_gstbackend master_new:master --force + +# replace are located in .git/refs/replace/ +git replace -l +git replace -d SHA +``` + +### Tips ### +``` bash + +# disable ffmpeg and other decoders +Insert media_use_ffmpeg=0 media_use_libvpx=0 media_use_libwebm=0 into GYP_DEFINES, see build steps. + +# disable nacl to reduce build time +Insert disable_nacl=1 to GYP_DEFINES, see build steps. + +# command lines options for ./out/Release/chrome +A list of all command line switches is available here: http://peter.sh/experiments/chromium-command-line-switches/ + +# disable gpu process and particular sandbox at runtime +CHROME_SANDBOX_DEBUGGING=1 CHROME_DEVEL_SANDBOX=out/Release/chrome_sandbox ./out/Release/chrome --allow-sandbox-debugging --disable-render-sandbox --disable-hang-monitor http://localhost/test/mov_bbb.ogg +CHROME_DEVEL_SANDBOX=out/Release/chrome_sandbox ./out/Release/chrome --disable-render-sandbox --disable-gpu --disable-hang-monitor about:blank + +# run chromium in debug mode +CHROME_DEVEL_SANDBOX=out/Debug/chrome_sandbox ./out/Debug/chrome http://www.w3schools.com/html/mov_bbb.ogg +CHROME_DEVEL_SANDBOX=out/Debug/chrome_sandbox ./out/Debug/chrome --media-startup-dialog --allow-sandbox-debugging about:blank + +# gdb +./out/Release/chrome --disable-media-sandbox --disable-setuid-sandbox --allow-sandbox-debugging --media-launcher='xterm -title renderer -e gdb --args' +CHROME_DEVEL_SANDBOX=out/Release/chrome_sandbox ./out/Release/chrome --disable-render-sandbox --disable-gpu --disable-hang-monitor --media-startup-dialog --allow-sandbox-debugging about:blank + +# Enable / disable gpu workarounds +./out/Release/chrome --no-sandbox --use-gl=egl --use_virtualized_gl_contexts=0 +All workarounds are listed in chromium/src/gpu/config/gpu_driver_bug_workaround_type.h + +# disable all processes, i.e. make all processes running as in-process mode in browser process. +--single-process + +# Run media process as in-process mode, so it will be thread in Browser Process +--in-media-process + +# logs in debug mode +DVLOG(level) in debug mode, DVLOG(1) +VLOG(level) in release mode, VLOG(1) +--enable-logging=stderr --v=N (level) +--enable-logging=stderr --v=1 + +# attach debugger +pass --media-startup-dialog to pause the media process at startup and print the pid +Then attach a debugger: gdb -p the_pid +In gdb type: signal SIGUSR1 to resume pause and type c to continue + +# indent code +Just run: git cl format, to indent latest commit. +``` + +### Build and run unit tests ### +```bash +# build and run "content" unit tests +ninja -C out/Release content_unittest +./out/Release/content_unittests + +# build more group of unit tests at a time +ninja -C out/Release media_blink_unittests content_unittests media_unittests base_unittests gl_unittests gpu_unittests + +# find other group of unit tests and run one +find chromium/src -name "*unittests.isolate" : ./media/blink/media_blink_unittests.isolate +ninja -C out/Release media_blink_unittests + +# list all tests within "gpu" unit tests group +./out/Release/gpu_unittests --gtest_list_tests + +# run all tests in "gpu" unit tests group that contains "TexSubImage2DFloatDoesClearOnGLES3" +./out/Release/gpu_unittests --gtest_filter=*TexSubImage2DFloatDoesClearOnGLES3* --single-process-tests + +# list py tests +./content/test/gpu/run_gpu_test.py list + +# run py tests +CHROME_DEVEL_SANDBOX=out/Release/chrome_sandbox ./content/test/gpu/run_unittests.py +CHROME_DEVEL_SANDBOX=out/Release/chrome_sandbox ./content/test/gpu/run_gpu_test.py gpu_process +``` + +### Contributing to upstream Chromium ### + +##### Contributor License Agreements (CLA) ##### +If you signed the CLA with a non gmail account then create a google account from that external email: [SignUpWithoutGmail](https://accounts.google.com/SignUpWithoutGmail) + +##### Submitting a patch ##### +Sign in to codereview.chromium.org using the email account which one you used to sign the CLA. +Before uploading the patch run "depot-tools-auth login https://codereview.chromium.org" to authentificate using "OAuth2". +It should open a new tab in your browser starting by "localhost:8090" and after logged in it should be written: "The authentication flow has completed." +Then you are ready to upload your patch by running "git cl upload". +To submit to a patch to an existing CL just type "git cl issue 1415793003" before uploading. + + +### Issues and roadmap ### +* Pulseaudio crashes when running in sandbox mode: [resolved](http://cgit.freedesktop.org/pulseaudio/pulseaudio/commit/?id=9817f396d5451070ba5c7ae7d11f7cc376911105) +* See [issues tracker](https://github.com/Samsung/ChromiumGStreamerBackend/issues) + diff --git a/base/message_loop/message_pump_glib.cc b/base/message_loop/message_pump_glib.cc index f06f60d..37a0101 100644 --- a/base/message_loop/message_pump_glib.cc +++ b/base/message_loop/message_pump_glib.cc @@ -142,6 +142,11 @@ ThreadInfo* thread_info = NULL; void CheckThread(MessagePumpGlib* pump) { AutoLock auto_lock(thread_info_lock.Get()); +#if defined(USE_GSTREAMER) + if (thread_info && thread_info->pump != pump) { + return; + } +#endif if (!thread_info) { thread_info = new ThreadInfo; thread_info->pump = pump; @@ -181,7 +186,13 @@ struct MessagePumpGlib::RunState { MessagePumpGlib::MessagePumpGlib() : state_(NULL), +#if defined(USE_GSTREAMER) + context_(g_main_context_get_thread_default() + ? g_main_context_get_thread_default() + : g_main_context_default()), +#else context_(g_main_context_default()), +#endif wakeup_gpollfd_(new GPollFD) { // Create our wakeup pipe, which is used to flag when work was scheduled. int fds[2]; diff --git a/base/threading/thread_restrictions.h b/base/threading/thread_restrictions.h index b6cfa72..c5125d1 100644 --- a/base/threading/thread_restrictions.h +++ b/base/threading/thread_restrictions.h @@ -39,6 +39,9 @@ class BrowserGpuMemoryBufferManager; class BrowserShutdownProfileDumper; class BrowserTestBase; class GpuChannelHost; +#if defined(USE_GSTREAMER) +class MediaChannelHost; +#endif class NestedMessagePumpAndroid; class RenderWidgetResizeHelper; class ScopedAllowWaitForAndroidLayoutTests; @@ -202,6 +205,9 @@ class BASE_EXPORT ThreadRestrictions { friend class content::BrowserGpuMemoryBufferManager; // http://crbug.com/420368 friend class content::GpuChannelHost; // http://crbug.com/125264 +#if defined(USE_GSTREAMER) + friend class content::MediaChannelHost; +#endif friend class content::TextInputClientMac; // http://crbug.com/121917 friend class dbus::Bus; // http://crbug.com/125222 friend class disk_cache::BackendImpl; // http://crbug.com/74623 diff --git a/build/common.gypi b/build/common.gypi index 7456795..8a0ee32 100644 --- a/build/common.gypi +++ b/build/common.gypi @@ -123,8 +123,11 @@ # Whether we're a traditional desktop unix. ['(OS=="linux" or OS=="freebsd" or OS=="openbsd" or OS=="solaris") and chromeos==0', { 'desktop_linux%': 1, + 'use_gstreamer%': 1, + 'use_sysroot%': 0, }, { 'desktop_linux%': 0, + 'use_gstreamer%': 0, }], # Embedded implies ozone. @@ -148,6 +151,7 @@ 'use_aura%': '<(use_aura)', 'use_ash%': '<(use_ash)', 'use_cras%': '<(use_cras)', + 'use_gstreamer%': '<(use_gstreamer)', 'use_ozone%': '<(use_ozone)', 'embedded%': '<(embedded)', 'use_libpci%': '<(use_libpci)', @@ -323,6 +327,7 @@ 'use_aura%': '<(use_aura)', 'use_ash%': '<(use_ash)', 'use_cras%': '<(use_cras)', + 'use_gstreamer%': '<(use_gstreamer)', 'use_libpci%': '<(use_libpci)', 'use_ozone%': '<(use_ozone)', 'use_ozone_evdev%': '<(use_ozone_evdev)', @@ -1107,6 +1112,7 @@ 'use_glib%': '<(use_glib)', 'use_pango%': '<(use_pango)', 'use_cairo%': '<(use_cairo)', + 'use_gstreamer%': '<(use_gstreamer)', 'use_ozone%': '<(use_ozone)', 'use_ozone_evdev%': '<(use_ozone_evdev)', 'use_xkbcommon%': '<(use_xkbcommon)', @@ -2677,6 +2683,9 @@ ['use_cairo==1', { 'defines': ['USE_CAIRO=1'], }], + ['use_gstreamer==1', { + 'defines': ['USE_GSTREAMER=1'], + }], ['use_cras==1', { 'defines': ['USE_CRAS=1'], }], diff --git a/build/gn_migration.gypi b/build/gn_migration.gypi index 0323765..c8996d9 100644 --- a/build/gn_migration.gypi +++ b/build/gn_migration.gypi @@ -98,7 +98,7 @@ '../ipc/ipc.gyp:ipc_tests', '../ipc/mojo/ipc_mojo.gyp:ipc_mojo_unittests', '../jingle/jingle.gyp:jingle_unittests', - '../media/media.gyp:ffmpeg_regression_tests', # TODO(GYP) this should be conditional on media_use_ffmpeg + #'../media/media.gyp:ffmpeg_regression_tests', # TODO(GYP) this should be conditional on media_use_ffmpeg '../media/media.gyp:media_perftests', '../media/media.gyp:media_unittests', '../media/midi/midi.gyp:midi_unittests', diff --git a/build/install-build-deps.sh b/build/install-build-deps.sh index 0c39d87..4ce3b83 100755 --- a/build/install-build-deps.sh +++ b/build/install-build-deps.sh @@ -40,6 +40,8 @@ package_exists() { do_inst_arm=1 do_inst_nacl=1 +do_inst_gstreamer=1 + while test "$1" != "" do case "$1" in @@ -52,6 +54,7 @@ do --no-chromeos-fonts) do_inst_chromeos_fonts=0;; --nacl) do_inst_nacl=1;; --no-nacl) do_inst_nacl=0;; + --gstreamer) do_inst_gstreamer=1;; --no-prompt) do_default=1 do_quietly="-qq --assume-yes" ;; @@ -319,6 +322,20 @@ else nacl_list= fi +if test "$do_inst_gstreamer" = "1"; then + echo "Including GStreamer." + lib_list="${lib_list} libgstreamer1.0-0 libgstreamer-plugins-base1.0-0 + libgstreamer-plugins-good1.0-0 libgstreamer-plugins-bad1.0-0 + gstreamer1.0-plugins-ugly gstreamer1.0-libav" + + dev_list="${dev_list} libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev + libgstreamer-plugins-bad1.0-dev" + + dbg_list="${dbg_list} libgstreamer1.0-0-dbg gstreamer1.0-plugins-base-dbg + gstreamer1.0-plugins-good-dbg gstreamer1.0-plugins-bad-dbg + gstreamer1.0-libav-dbg" +fi + # The `sort -r -s -t: -k2` sorts all the :i386 packages to the front, to avoid # confusing dpkg-query (crbug.com/446172). packages="$( diff --git a/build/linux/system.gyp b/build/linux/system.gyp index a1d6bf0..c33850c 100644 --- a/build/linux/system.gyp +++ b/build/linux/system.gyp @@ -1218,5 +1218,32 @@ }], ], }, + { + 'target_name': 'gstreamer', + 'type': 'none', + 'variables': { + 'gstreamer_packages': 'gstreamer-1.0 gstreamer-base-1.0 gstreamer-audio-1.0 gstreamer-video-1.0 gstreamer-app-1.0 gstreamer-gl-1.0 gstreamer-player-1.0', + }, + 'conditions': [ + ['use_gstreamer==1', { + 'all_dependent_settings': { + 'cflags': [ + ' - • $1Read and change all your data on the websites you visit + • $1Read and change all your data on the websites you visit ($146) @@ -5778,6 +5778,14 @@ Keep your key file in a safe place. You will need it to create new versions of y Debugging keyboard shortcuts + + + Enable GStreamer media backend + + + Enable the use of GStreamer as media backend for video playback. + + Override software rendering list diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc index a25e2ba..23151db 100644 --- a/chrome/browser/about_flags.cc +++ b/chrome/browser/about_flags.cc @@ -566,6 +566,13 @@ const Experiment::Choice kV8CacheOptionsChoices[] = { // // When adding a new choice, add it to the end of the list. const Experiment kExperiments[] = { +#if defined(USE_GSTREAMER) + {"enable-gstreamer-media-backend", + IDS_FLAGS_ENABLE_GSTREAMER_MEDIA_BACKEND_NAME, + IDS_FLAGS_ENABLE_GSTREAMER_MEDIA_BACKEND_DESCRIPTION, + kOsLinux, + SINGLE_VALUE_TYPE(switches::kEnableGStreamerMediaBackend)}, +#endif {"ignore-gpu-blacklist", IDS_FLAGS_IGNORE_GPU_BLACKLIST_NAME, IDS_FLAGS_IGNORE_GPU_BLACKLIST_DESCRIPTION, diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc index 44869b2..9eb5b8e 100644 --- a/chrome/browser/chrome_content_browser_client.cc +++ b/chrome/browser/chrome_content_browser_client.cc @@ -497,6 +497,15 @@ int GetCrashSignalFD(const base::CommandLine& command_line) { return crash_handler->GetDeathSignalSocket(); } +#if defined(USE_GSTREAMER) + if (process_type == switches::kMediaProcess) { + static breakpad::CrashHandlerHostLinux* crash_handler = NULL; + if (!crash_handler) + crash_handler = CreateCrashHandlerHost(process_type); + return crash_handler->GetDeathSignalSocket(); + } +#endif + return -1; } #endif // defined(OS_POSIX) && !defined(OS_ANDROID) && !defined(OS_MACOSX) diff --git a/chrome/browser/task_management/providers/child_process_task.cc b/chrome/browser/task_management/providers/child_process_task.cc index 9770adc..d628449 100644 --- a/chrome/browser/task_management/providers/child_process_task.cc +++ b/chrome/browser/task_management/providers/child_process_task.cc @@ -56,6 +56,11 @@ base::string16 GetLocalizedTitle(const base::string16& title, return l10n_util::GetStringUTF16(IDS_TASK_MANAGER_UTILITY_PREFIX); case content::PROCESS_TYPE_GPU: return l10n_util::GetStringUTF16(IDS_TASK_MANAGER_GPU_PREFIX); +#if defined(USE_GSTREAMER) + // TODO: add localized resource + case content::PROCESS_TYPE_MEDIA: + return base::string16(reinterpret_cast(u"Media Process")); +#endif case content::PROCESS_TYPE_PLUGIN: case content::PROCESS_TYPE_PPAPI_PLUGIN: return l10n_util::GetStringFUTF16(IDS_TASK_MANAGER_PLUGIN_PREFIX, diff --git a/chrome/browser/task_manager/child_process_resource_provider.cc b/chrome/browser/task_manager/child_process_resource_provider.cc index 0054b4b..c5012a8 100644 --- a/chrome/browser/task_manager/child_process_resource_provider.cc +++ b/chrome/browser/task_manager/child_process_resource_provider.cc @@ -213,6 +213,11 @@ base::string16 ChildProcessResource::GetLocalizedTitle() const { return l10n_util::GetStringFUTF16(IDS_TASK_MANAGER_UTILITY_PREFIX, title); case content::PROCESS_TYPE_GPU: return l10n_util::GetStringUTF16(IDS_TASK_MANAGER_GPU_PREFIX); +#if defined(USE_GSTREAMER) + // TODO: add localized resource + case content::PROCESS_TYPE_MEDIA: + return base::string16(reinterpret_cast(u"Media Process")); +#endif case content::PROCESS_TYPE_PLUGIN: case content::PROCESS_TYPE_PPAPI_PLUGIN: return l10n_util::GetStringFUTF16(IDS_TASK_MANAGER_PLUGIN_PREFIX, title); diff --git a/chrome/utility/extensions/extensions_handler.cc b/chrome/utility/extensions/extensions_handler.cc index 942545e..ef3bbc6 100644 --- a/chrome/utility/extensions/extensions_handler.cc +++ b/chrome/utility/extensions/extensions_handler.cc @@ -105,11 +105,15 @@ bool ExtensionsHandler::OnMessageReceived(const IPC::Message& message) { void ExtensionsHandler::OnCheckMediaFile( int64 milliseconds_of_decoding, const IPC::PlatformFileForTransit& media_file) { +#if !defined(MEDIA_DISABLE_FFMPEG) media::MediaFileChecker checker( IPC::PlatformFileForTransitToFile(media_file)); const bool check_success = checker.Start( base::TimeDelta::FromMilliseconds(milliseconds_of_decoding)); Send(new ChromeUtilityHostMsg_CheckMediaFile_Finished(check_success)); +#else + Send(new ChromeUtilityHostMsg_CheckMediaFile_Finished(false)); +#endif ReleaseProcessIfNeeded(); } diff --git a/chrome/utility/media_galleries/media_metadata_parser.cc b/chrome/utility/media_galleries/media_metadata_parser.cc index a39324e..bc0ee41 100644 --- a/chrome/utility/media_galleries/media_metadata_parser.cc +++ b/chrome/utility/media_galleries/media_metadata_parser.cc @@ -56,6 +56,8 @@ void ParseAudioVideoMetadata( std::vector* attached_images) { DCHECK(source); DCHECK(metadata); + +#if !defined(MEDIA_DISABLE_FFMPEG) media::AudioVideoMetadataExtractor extractor; if (!extractor.Extract(source, get_attached_images)) @@ -108,6 +110,9 @@ void ParseAudioVideoMetadata( &attached_images->back().type); } } +#else + return; +#endif } void FinishParseAudioVideoMetadata( diff --git a/content/app/content_main_runner.cc b/content/app/content_main_runner.cc index b59c95a..032aa3e 100644 --- a/content/app/content_main_runner.cc +++ b/content/app/content_main_runner.cc @@ -32,6 +32,9 @@ #include "content/common/set_process_title.h" #include "content/common/url_schemes.h" #include "content/gpu/in_process_gpu_thread.h" +#if defined(USE_GSTREAMER) +#include "content/media/in_process_media_thread.h" +#endif #include "content/public/app/content_main.h" #include "content/public/app/content_main_delegate.h" #include "content/public/app/startup_helper_win.h" @@ -67,6 +70,9 @@ #if !defined(OS_IOS) #include "content/app/mojo/mojo_init.h" #include "content/browser/gpu/gpu_process_host.h" +#if defined(USE_GSTREAMER) +#include "content/browser/media/media_process_host.h" +#endif #include "content/browser/renderer_host/render_process_host_impl.h" #include "content/browser/utility_process_host_impl.h" #include "content/public/plugin/content_plugin_client.h" @@ -118,6 +124,9 @@ int tc_set_new_mode(int mode); namespace content { extern int GpuMain(const content::MainFunctionParams&); +#if defined(USE_GSTREAMER) +extern int MediaMain(const content::MainFunctionParams&); +#endif #if defined(ENABLE_PLUGINS) #if !defined(OS_LINUX) extern int PluginMain(const content::MainFunctionParams&); @@ -325,6 +334,10 @@ static void RegisterMainThreadFactories() { CreateInProcessRendererThread); GpuProcessHost::RegisterGpuMainThreadFactory( CreateInProcessGpuThread); +#if defined(USE_GSTREAMER) + MediaProcessHost::RegisterMediaMainThreadFactory( + CreateInProcessMediaThread); +#endif #else base::CommandLine& command_line = *base::CommandLine::ForCurrentProcess(); if (command_line.HasSwitch(switches::kSingleProcess)) { @@ -335,6 +348,12 @@ static void RegisterMainThreadFactories() { LOG(FATAL) << "--in-process-gpu is not supported in chrome multiple dll browser."; } +#if defined(USE_GSTREAMER) + if (command_line.HasSwitch(switches::kInProcessMedia)) { + LOG(FATAL) << + "--in-process-media is not supported in chrome multiple dll browser."; + } +#endif #endif // !CHROME_MULTIPLE_DLL_BROWSER && !CHROME_MULTIPLE_DLL_CHILD } @@ -360,6 +379,9 @@ int RunNamedProcessTypeMain( { switches::kUtilityProcess, UtilityMain }, { switches::kRendererProcess, RendererMain }, { switches::kGpuProcess, GpuMain }, +#if defined(USE_GSTREAMER) + { switches::kMediaProcess, MediaMain }, +#endif #endif // !CHROME_MULTIPLE_DLL_BROWSER }; diff --git a/content/browser/browser_main_loop.cc b/content/browser/browser_main_loop.cc index b0fa2c0..11aadf2 100644 --- a/content/browser/browser_main_loop.cc +++ b/content/browser/browser_main_loop.cc @@ -128,6 +128,11 @@ #include #endif +#if defined(USE_GSTREAMER) +#include "content/browser/media/media_data_manager_impl.h" +#include "content/browser/media/media_process_host.h" +#endif + #if defined(OS_LINUX) && defined(USE_UDEV) #include "content/browser/device_monitor_udev.h" #elif defined(OS_MACOSX) && !defined(OS_IOS) @@ -1223,6 +1228,20 @@ int BrowserMainLoop::BrowserThreadsStarted() { CAUSE_FOR_GPU_LAUNCH_BROWSER_STARTUP)); } +#if defined(USE_GSTREAMER) + MediaDataManagerImpl::GetInstance()->Initialize(); + + if (!UsingInProcessMedia()) { + TRACE_EVENT_INSTANT0("media", "Post task to launch media process", + TRACE_EVENT_SCOPE_THREAD); + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + base::Bind(base::IgnoreResult(&MediaProcessHost::Get), + MediaProcessHost::MEDIA_PROCESS_KIND_SANDBOXED, + CAUSE_FOR_MEDIA_LAUNCH_BROWSER_STARTUP)); + } +#endif + #if defined(OS_MACOSX) ThemeHelperMac::GetInstance(); SystemHotkeyHelperMac::GetInstance()->DeferredLoadSystemHotkeys(); @@ -1245,6 +1264,13 @@ bool BrowserMainLoop::UsingInProcessGpu() const { parsed_command_line_.HasSwitch(switches::kInProcessGPU); } +#if defined(USE_GSTREAMER) +bool BrowserMainLoop::UsingInProcessMedia() const { + return parsed_command_line_.HasSwitch(switches::kSingleProcess) || + parsed_command_line_.HasSwitch(switches::kInProcessMedia); +} +#endif + bool BrowserMainLoop::InitializeToolkit() { TRACE_EVENT0("startup", "BrowserMainLoop::InitializeToolkit"); TRACK_SCOPED_REGION("Startup", "BrowserMainLoop::InitializeToolkit"); diff --git a/content/browser/browser_main_loop.h b/content/browser/browser_main_loop.h index 14478ec..8d80fd7 100644 --- a/content/browser/browser_main_loop.h +++ b/content/browser/browser_main_loop.h @@ -150,6 +150,9 @@ class CONTENT_EXPORT BrowserMainLoop { void EndStartupTracing(); bool UsingInProcessGpu() const; +#if defined(USE_GSTREAMER) + bool UsingInProcessMedia() const; +#endif // Quick reference for initialization order: // Constructor diff --git a/content/browser/gpu/gpu_data_manager_impl_private.cc b/content/browser/gpu/gpu_data_manager_impl_private.cc index 87bad09..2188d1c 100644 --- a/content/browser/gpu/gpu_data_manager_impl_private.cc +++ b/content/browser/gpu/gpu_data_manager_impl_private.cc @@ -644,6 +644,15 @@ void GpuDataManagerImplPrivate::AppendGpuCommandLine( } else if (!use_gl.empty()) { command_line->AppendSwitchASCII(switches::kUseGL, use_gl); } +#if defined(USE_GSTREAMER) + else { + // TODO: use kGLImplementationEGLName (egl/gles2, --use-gl=egl) + // Set to default for now (i.e. glx, --use-gl=desktop). + command_line->AppendSwitchASCII(switches::kUseGL, + gfx::kGLImplementationDesktopName); + } +#endif + if (ui::GpuSwitchingManager::GetInstance()->SupportsDualGpus()) command_line->AppendSwitchASCII(switches::kSupportsDualGpus, "true"); else diff --git a/content/browser/loader/resource_dispatcher_host_impl.cc b/content/browser/loader/resource_dispatcher_host_impl.cc index 2b53281..defce36 100644 --- a/content/browser/loader/resource_dispatcher_host_impl.cc +++ b/content/browser/loader/resource_dispatcher_host_impl.cc @@ -1014,6 +1014,7 @@ void ResourceDispatcherHostImpl::OnRequestResource( "477117 ResourceDispatcherHostImpl::OnRequestResource")); // When logging time-to-network only care about main frame and non-transfer // navigations. + if (request_data.resource_type == RESOURCE_TYPE_MAIN_FRAME && request_data.transferred_request_request_id == -1 && !base::CommandLine::ForCurrentProcess()->HasSwitch( diff --git a/content/browser/media/media_data_manager_impl.cc b/content/browser/media/media_data_manager_impl.cc new file mode 100644 index 0000000..446fb07 --- /dev/null +++ b/content/browser/media/media_data_manager_impl.cc @@ -0,0 +1,78 @@ +// Copyright (c) 2015 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 "content/browser/media/media_data_manager_impl.h" + +#include "content/browser/media/media_data_manager_impl_private.h" + +namespace content { + +// static +MediaDataManager* MediaDataManager::GetInstance() { + return MediaDataManagerImpl::GetInstance(); +} + +// static +MediaDataManagerImpl* MediaDataManagerImpl::GetInstance() { + return Singleton::get(); +} + +void MediaDataManagerImpl::GetMediaProcessHandles( + const GetMediaProcessHandlesCallback& callback) const { + base::AutoLock auto_lock(lock_); + private_->GetMediaProcessHandles(callback); +} + +void MediaDataManagerImpl::AddObserver(MediaDataManagerObserver* observer) { + base::AutoLock auto_lock(lock_); + private_->AddObserver(observer); +} + +void MediaDataManagerImpl::RemoveObserver(MediaDataManagerObserver* observer) { + base::AutoLock auto_lock(lock_); + private_->RemoveObserver(observer); +} + +void MediaDataManagerImpl::Initialize() { + base::AutoLock auto_lock(lock_); + private_->Initialize(); +} + +// TODO: check unused code +void MediaDataManagerImpl::AppendRendererCommandLine( + base::CommandLine* command_line) const { + base::AutoLock auto_lock(lock_); + private_->AppendRendererCommandLine(command_line); +} + +void MediaDataManagerImpl::AppendMediaCommandLine( + base::CommandLine* command_line) const { + base::AutoLock auto_lock(lock_); + private_->AppendMediaCommandLine(command_line); +} + +void MediaDataManagerImpl::AddLogMessage(int level, + const std::string& header, + const std::string& message) { + base::AutoLock auto_lock(lock_); + private_->AddLogMessage(level, header, message); +} + +void MediaDataManagerImpl::ProcessCrashed(base::TerminationStatus exit_code) { + base::AutoLock auto_lock(lock_); + private_->ProcessCrashed(exit_code); +} + +// TODO: check unused code +base::ListValue* MediaDataManagerImpl::GetLogMessages() const { + base::AutoLock auto_lock(lock_); + return private_->GetLogMessages(); +} + +MediaDataManagerImpl::MediaDataManagerImpl() + : private_(MediaDataManagerImplPrivate::Create(this)) {} + +MediaDataManagerImpl::~MediaDataManagerImpl() {} + +} // namespace content diff --git a/content/browser/media/media_data_manager_impl.h b/content/browser/media/media_data_manager_impl.h new file mode 100644 index 0000000..1ff1456 --- /dev/null +++ b/content/browser/media/media_data_manager_impl.h @@ -0,0 +1,96 @@ +// Copyright (c) 2015 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. + +#ifndef CONTENT_BROWSER_MEDIA_MEDIA_DATA_MANAGER_IMPL_H_ +#define CONTENT_BROWSER_MEDIA_MEDIA_DATA_MANAGER_IMPL_H_ + +#include + +#include "base/compiler_specific.h" +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/singleton.h" +#include "base/process/kill.h" +#include "base/synchronization/lock.h" +#include "base/time/time.h" +#include "base/values.h" +#include "content/public/browser/media_data_manager.h" + +namespace base { +class CommandLine; +} + +namespace content { + +class MediaDataManagerImplPrivate; +struct WebPreferences; + +class CONTENT_EXPORT MediaDataManagerImpl + : public NON_EXPORTED_BASE(MediaDataManager) { + public: + // Getter for the singleton. This will return NULL on failure. + static MediaDataManagerImpl* GetInstance(); + + // MediaDataManager implementation. + void GetMediaProcessHandles( + const GetMediaProcessHandlesCallback& callback) const override; + + // TODO: useful for stats but unused for now. + void AddObserver(MediaDataManagerObserver* observer) override; + void RemoveObserver(MediaDataManagerObserver* observer) override; + + void Initialize(); + + // Insert disable-feature switches corresponding to preliminary media feature + // flags into the renderer process command line. + void AppendRendererCommandLine(base::CommandLine* command_line) const; + + // Insert switches into media process command line: XXXX, etc. + void AppendMediaCommandLine(base::CommandLine* command_line) const; + + void AddLogMessage(int level, + const std::string& header, + const std::string& message); + + void ProcessCrashed(base::TerminationStatus exit_code); + + // Returns a new copy of the ListValue. Caller is responsible to release + // the returned value. + base::ListValue* GetLogMessages() const; + + private: + friend class MediaDataManagerImplPrivate; + friend struct DefaultSingletonTraits; + + class UnlockedSession { + public: + explicit UnlockedSession(MediaDataManagerImpl* owner) : owner_(owner) { + DCHECK(owner_); + owner_->lock_.AssertAcquired(); + owner_->lock_.Release(); + } + + ~UnlockedSession() { + DCHECK(owner_); + owner_->lock_.Acquire(); + } + + private: + MediaDataManagerImpl* owner_; + DISALLOW_COPY_AND_ASSIGN(UnlockedSession); + }; + + MediaDataManagerImpl(); + ~MediaDataManagerImpl() override; + + mutable base::Lock lock_; + scoped_ptr private_; + + DISALLOW_COPY_AND_ASSIGN(MediaDataManagerImpl); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_MEDIA_MEDIA_DATA_MANAGER_IMPL_H_ diff --git a/content/browser/media/media_data_manager_impl_private.cc b/content/browser/media/media_data_manager_impl_private.cc new file mode 100644 index 0000000..bb4dc7a --- /dev/null +++ b/content/browser/media/media_data_manager_impl_private.cc @@ -0,0 +1,118 @@ +// Copyright (c) 2013 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 "content/browser/media/media_data_manager_impl_private.h" + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/command_line.h" +#include "base/metrics/field_trial.h" +#include "base/metrics/histogram.h" +#include "base/metrics/sparse_histogram.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/stringprintf.h" +#include "base/sys_info.h" +#include "base/trace_event/trace_event.h" +#include "base/version.h" +#include "cc/base/switches.h" +#include "content/browser/media/media_process_host.h" +#include "content/common/media/media_messages.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/media_data_manager_observer.h" +#include "content/public/common/content_client.h" +#include "content/public/common/content_constants.h" +#include "content/public/common/content_switches.h" +#include "content/public/common/web_preferences.h" + +namespace content { + +void MediaDataManagerImplPrivate::GetMediaProcessHandles( + const MediaDataManager::GetMediaProcessHandlesCallback& callback) const { + MediaProcessHost::GetProcessHandles(callback); +} + +void MediaDataManagerImplPrivate::AddObserver( + MediaDataManagerObserver* observer) { + MediaDataManagerImpl::UnlockedSession session(owner_); + observer_list_->AddObserver(observer); +} + +void MediaDataManagerImplPrivate::RemoveObserver( + MediaDataManagerObserver* observer) { + MediaDataManagerImpl::UnlockedSession session(owner_); + observer_list_->RemoveObserver(observer); +} + +void MediaDataManagerImplPrivate::Initialize() {} + +void MediaDataManagerImplPrivate::AppendRendererCommandLine( + base::CommandLine* command_line) const { + DCHECK(command_line); + // command_line->AppendSwitch(switches::kXXXX); +} + +void MediaDataManagerImplPrivate::AppendMediaCommandLine( + base::CommandLine* command_line) const { + DCHECK(command_line); + // command_line->AppendSwitch(switches::kXXXX); +} + +void MediaDataManagerImplPrivate::AddLogMessage(int level, + const std::string& header, + const std::string& message) { + log_messages_.push_back(LogMessage(level, header, message)); +} + +void MediaDataManagerImplPrivate::ProcessCrashed( + base::TerminationStatus exit_code) { + if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) { + // Unretained is ok, because it's posted to UI thread, the thread + // where the singleton GpuDataManagerImpl lives until the end. + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, + base::Bind(&MediaDataManagerImpl::ProcessCrashed, + base::Unretained(owner_), exit_code)); + return; + } +} + +base::ListValue* MediaDataManagerImplPrivate::GetLogMessages() const { + base::ListValue* value = new base::ListValue; + for (size_t ii = 0; ii < log_messages_.size(); ++ii) { + base::DictionaryValue* dict = new base::DictionaryValue(); + dict->SetInteger("level", log_messages_[ii].level); + dict->SetString("header", log_messages_[ii].header); + dict->SetString("message", log_messages_[ii].message); + value->Append(dict); + } + return value; +} + +// static +MediaDataManagerImplPrivate* MediaDataManagerImplPrivate::Create( + MediaDataManagerImpl* owner) { + return new MediaDataManagerImplPrivate(owner); +} + +MediaDataManagerImplPrivate::MediaDataManagerImplPrivate( + MediaDataManagerImpl* owner) + : observer_list_(new MediaDataManagerObserverList), + owner_(owner), + finalized_(false) { + DCHECK(owner_); + /* + const base::CommandLine* command_line = + base::CommandLine::ForCurrentProcess(); + + if (command_line->HasSwitch(switches::kXXXX)) + DoSomething(); + */ +} + +MediaDataManagerImplPrivate::~MediaDataManagerImplPrivate() {} + +void MediaDataManagerImplPrivate::OnMediaProcessInitFailure() { + // TODO: implement +} + +} // namespace content diff --git a/content/browser/media/media_data_manager_impl_private.h b/content/browser/media/media_data_manager_impl_private.h new file mode 100644 index 0000000..4b0ba00 --- /dev/null +++ b/content/browser/media/media_data_manager_impl_private.h @@ -0,0 +1,83 @@ +// Copyright (c) 2013 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. + +#ifndef CONTENT_BROWSER_MEDIA_MEDIA_DATA_MANAGER_IMPL_PRIVATE_H_ +#define CONTENT_BROWSER_MEDIA_MEDIA_DATA_MANAGER_IMPL_PRIVATE_H_ + +#include +#include +#include +#include +#include + +#include "base/memory/ref_counted.h" +#include "base/memory/singleton.h" +#include "base/observer_list_threadsafe.h" +#include "content/common/content_export.h" +#include "content/public/browser/media_data_manager.h" +#include "content/public/browser/media_data_manager_observer.h" +#include "content/browser/media/media_data_manager_impl.h" +#include "content/browser/media/media_data_manager_impl_private.h" + +namespace base { +class CommandLine; +} + +namespace content { + +class CONTENT_EXPORT MediaDataManagerImplPrivate { + public: + static MediaDataManagerImplPrivate* Create(MediaDataManagerImpl* owner); + + void GetMediaProcessHandles( + const MediaDataManager::GetMediaProcessHandlesCallback& callback) const; + void AddObserver(MediaDataManagerObserver* observer); + void RemoveObserver(MediaDataManagerObserver* observer); + + void Initialize(); + void AppendRendererCommandLine(base::CommandLine* command_line) const; + void AppendMediaCommandLine(base::CommandLine* command_line) const; + + void AddLogMessage(int level, + const std::string& header, + const std::string& message); + + void ProcessCrashed(base::TerminationStatus exit_code); + + base::ListValue* GetLogMessages() const; + void OnMediaProcessInitFailure(); + + virtual ~MediaDataManagerImplPrivate(); + + private: + typedef base::ObserverListThreadSafe + MediaDataManagerObserverList; + + struct LogMessage { + int level; + std::string header; + std::string message; + + LogMessage(int _level, + const std::string& _header, + const std::string& _message) + : level(_level), header(_header), message(_message) {} + }; + + explicit MediaDataManagerImplPrivate(MediaDataManagerImpl* owner); + const scoped_refptr observer_list_; + + std::vector log_messages_; + + MediaDataManagerImpl* owner_; + + // True if all future Initialize calls should be ignored. + bool finalized_; + + DISALLOW_COPY_AND_ASSIGN(MediaDataManagerImplPrivate); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_GPU_GPU_DATA_MANAGER_IMPL_PRIVATE_H_ diff --git a/content/browser/media/media_process_host.cc b/content/browser/media/media_process_host.cc new file mode 100644 index 0000000..008c09a --- /dev/null +++ b/content/browser/media/media_process_host.cc @@ -0,0 +1,583 @@ +// Copyright (c) 2015 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 "content/browser/media/media_process_host.h" + +#include "base/base64.h" +#include "base/base_switches.h" +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/callback_helpers.h" +#include "base/command_line.h" +#include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "base/metrics/histogram.h" +#include "base/sha1.h" +#include "base/threading/thread.h" +#include "content/browser/appcache/chrome_appcache_service.h" +#include "content/browser/browser_child_process_host_impl.h" +#include "content/browser/fileapi/chrome_blob_storage_context.h" +#include "storage/browser/fileapi/file_system_context.h" +#include "content/browser/host_zoom_level_context.h" +#include "content/browser/loader/resource_message_filter.h" +#include "content/browser/media/media_data_manager_impl.h" +#include "content/browser/media/media_process_host_ui_shim.h" +#include "content/browser/renderer_host/gpu_message_filter.h" +#include "content/browser/renderer_host/render_widget_helper.h" +#include "content/browser/renderer_host/render_widget_host_impl.h" +#include "content/browser/service_worker/service_worker_context_wrapper.h" +#include "content/common/child_process_host_impl.h" +#include "content/common/in_process_child_thread_params.h" +#include "content/common/resource_messages.h" +#include "content/common/media/media_messages.h" +#include "content/common/view_messages.h" +#include "content/public/browser/appcache_service.h" +#include "content/public/browser/browser_context.h" +#include "content/public/browser/browser_message_filter.h" +#include "content/public/browser/render_process_host.h" +#include "content/public/browser/storage_partition.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/content_browser_client.h" +#include "content/public/browser/render_process_host.h" +#include "content/public/browser/render_widget_host_view.h" +#include "content/public/browser/resource_context.h" +#include "content/public/common/content_client.h" +#include "content/public/common/content_switches.h" +#include "content/public/common/result_codes.h" +#include "content/public/common/sandboxed_process_launcher_delegate.h" +#include "ipc/ipc_channel_handle.h" +#include "ipc/ipc_switches.h" +#include "ipc/message_filter.h" +#include "media/base/media_switches.h" +#include "net/url_request/url_request_context_getter.h" + +namespace content { + +namespace { + +// Indexed by MediaProcessKind. There is one of each kind maximum. +// This array may only be accessed from the IO thread. +MediaProcessHost* + g_media_process_hosts[MediaProcessHost::MEDIA_PROCESS_KIND_COUNT]; + +void SendMediaProcessMessage(MediaProcessHost::MediaProcessKind kind, + CauseForMediaLaunch cause, + IPC::Message* message) { + MediaProcessHost* host = MediaProcessHost::Get(kind, cause); + if (host) { + host->Send(message); + } else { + delete message; + } +} + +class MediaSandboxedProcessLauncherDelegate + : public SandboxedProcessLauncherDelegate { + public: + MediaSandboxedProcessLauncherDelegate(base::CommandLine* cmd_line, + ChildProcessHost* host) + : ipc_fd_(host->TakeClientFileDescriptor()) {} + + ~MediaSandboxedProcessLauncherDelegate() override {} + + base::ScopedFD TakeIpcFd() override { return ipc_fd_.Pass(); } + + private: + base::ScopedFD ipc_fd_; +}; + +} // anonymous namespace + +// static +bool MediaProcessHost::ValidateHost(MediaProcessHost* host) { + if (base::CommandLine::ForCurrentProcess()->HasSwitch( + switches::kSingleProcess) || + base::CommandLine::ForCurrentProcess()->HasSwitch( + switches::kInProcessMedia) || + host->valid_) { + return true; + } + + host->ForceShutdown(); + return false; +} + +// static +MediaProcessHost* MediaProcessHost::Get(MediaProcessKind kind, + CauseForMediaLaunch cause) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + // Don't grant further access to media if it is not allowed. + MediaDataManagerImpl* media_data_manager = + MediaDataManagerImpl::GetInstance(); + DCHECK(media_data_manager); + + if (g_media_process_hosts[kind] && ValidateHost(g_media_process_hosts[kind])) + return g_media_process_hosts[kind]; + + if (cause == CAUSE_FOR_MEDIA_LAUNCH_NO_LAUNCH) + return NULL; + + static int last_host_id = 0; + int host_id; + host_id = ++last_host_id; + + MediaProcessHost* host = new MediaProcessHost(host_id, kind); + if (host->Init()) + return host; + + delete host; + return NULL; +} + +// static +void MediaProcessHost::GetProcessHandles( + const MediaDataManager::GetMediaProcessHandlesCallback& callback) { + if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) { + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + base::Bind(&MediaProcessHost::GetProcessHandles, callback)); + return; + } + + std::list handles; + for (size_t i = 0; i < arraysize(g_media_process_hosts); ++i) { + MediaProcessHost* host = g_media_process_hosts[i]; + if (host && ValidateHost(host)) + handles.push_back(host->process_->GetProcess().Handle()); + } + + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, + base::Bind(callback, handles)); +} + +// static +void MediaProcessHost::SendOnIO(MediaProcessKind kind, + CauseForMediaLaunch cause, + IPC::Message* message) { + if (!BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + base::Bind(&SendMediaProcessMessage, kind, cause, message))) { + delete message; + } +} + +MediaMainThreadFactoryFunction g_media_main_thread_factory = NULL; + +void MediaProcessHost::RegisterMediaMainThreadFactory( + MediaMainThreadFactoryFunction create) { + g_media_main_thread_factory = create; +} + +// static +MediaProcessHost* MediaProcessHost::FromID(int host_id) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + for (int i = 0; i < MEDIA_PROCESS_KIND_COUNT; ++i) { + MediaProcessHost* host = g_media_process_hosts[i]; + if (host && host->host_id_ == host_id && ValidateHost(host)) + return host; + } + + return NULL; +} + +MediaProcessHost::MediaProcessHost(int host_id, MediaProcessKind kind) + : host_id_(host_id), + valid_(true), + in_process_(false), + kind_(kind), + process_launched_(false), + initialized_(false), + gpu_client_id_(ChildProcessHostImpl::GenerateChildProcessUniqueId()), + gpu_message_filter_(nullptr), + weak_factory_ui_(this), + weak_factory_io_(this) { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + + widget_helper_ = new RenderWidgetHelper(); + + if (base::CommandLine::ForCurrentProcess()->HasSwitch( + switches::kSingleProcess) || + base::CommandLine::ForCurrentProcess()->HasSwitch( + switches::kInProcessMedia)) { + in_process_ = true; + } + + // If the 'single Media process' policy ever changes, we still want to + // maintain + // it for 'gpu thread' mode and only create one instance of host and thread. + DCHECK(!in_process_ || g_media_process_hosts[kind] == NULL); + + g_media_process_hosts[kind] = this; + + // Post a task to create the corresponding MediaProcessHostUIShim. The + // MediaProcessHostUIShim will be destroyed if either the browser exits, + // in which case it calls MediaProcessHostUIShim::DestroyAll, or the + // MediaProcessHost is destroyed, which happens when the corresponding GPU + // process terminates or fails to launch. + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(base::IgnoreResult(&MediaProcessHostUIShim::Create), host_id)); + + process_.reset(new BrowserChildProcessHostImpl(PROCESS_TYPE_MEDIA, this)); +} + +MediaProcessHost::~MediaProcessHost() { + DCHECK(CalledOnValidThread()); + + SendOutstandingReplies(); + + // In case we never started, clean up. + while (!queued_messages_.empty()) { + delete queued_messages_.front(); + queued_messages_.pop(); + } + + // This is only called on the IO thread so no race against the constructor + // for another MediaProcessHost. + if (g_media_process_hosts[kind_] == this) + g_media_process_hosts[kind_] = NULL; + + std::string message; + if (!in_process_) { + int exit_code; + base::TerminationStatus status = + process_->GetTerminationStatus(false /* known_dead */, &exit_code); + + if (status == base::TERMINATION_STATUS_NORMAL_TERMINATION || + status == base::TERMINATION_STATUS_ABNORMAL_TERMINATION) { + UMA_HISTOGRAM_ENUMERATION("Media.MediaProcessExitCode", exit_code, + RESULT_CODE_LAST_CODE); + } + + switch (status) { + case base::TERMINATION_STATUS_NORMAL_TERMINATION: + message = "The media process exited normally."; + break; + case base::TERMINATION_STATUS_ABNORMAL_TERMINATION: + message = base::StringPrintf("The media process exited with code %d.", + exit_code); + break; + case base::TERMINATION_STATUS_PROCESS_WAS_KILLED: + message = "The media process is killed."; + break; + case base::TERMINATION_STATUS_PROCESS_CRASHED: + message = "The media process crashed."; + break; + default: + break; + } + } + + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(&MediaProcessHostUIShim::Destroy, host_id_, message)); +} + +net::URLRequestContext* GetRequestContext( + scoped_refptr request_context, + scoped_refptr media_request_context, + ResourceType resource_type) { + // If the request has resource type of RESOURCE_TYPE_MEDIA, we use a request + // context specific to media for handling it because these resources have + // specific needs for caching. + if (resource_type == RESOURCE_TYPE_MEDIA) + return media_request_context->GetURLRequestContext(); + return request_context->GetURLRequestContext(); +} + +void GetContexts( + ResourceContext* resource_context, + scoped_refptr request_context, + scoped_refptr media_request_context, + const ResourceHostMsg_Request& request, + ResourceContext** resource_context_out, + net::URLRequestContext** request_context_out) { + *resource_context_out = resource_context; + *request_context_out = GetRequestContext( + request_context, media_request_context, request.resource_type); +} + +void MediaProcessHost::CreateGpuMessageFilter() { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + + if (!gpu_message_filter_) { + gpu_message_filter_ = + new GpuMessageFilter(gpu_client_id_, widget_helper_.get()); + process_->AddFilter(gpu_message_filter_); + } +} + +void MediaProcessHost::CreateResourceMessageFilter( + int renderer_id, + const EstablishChannelCallback& callback) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + + // Resource and Gpu message filters add is in sync with EstablishMediaChannel. + // Add GpuMessageFilter if not done already. + CreateGpuMessageFilter(); + + // For now the storage of corresponding render process is used. + // Maybe it is correct to create our own storage associated to the media + // process. + // But we suspect that the storage contains some required information to + // create url connection properly like credentials. + // If credentials are contained in the global browser context then we could + // even setup only one ResourceMessageFilter as done for GpuMessageFilter. + content::RenderProcessHost* rph = + content::RenderProcessHost::FromID(renderer_id); + content::StoragePartition* storage_partition = rph->GetStoragePartition(); + scoped_refptr request_context( + storage_partition->GetURLRequestContext()); + scoped_refptr media_request_context( + storage_partition->GetMediaURLRequestContext()); + BrowserContext* browser_context = rph->GetBrowserContext(); + + ResourceMessageFilter::GetContextsCallback get_contexts_callback( + base::Bind(&GetContexts, browser_context->GetResourceContext(), + request_context, media_request_context)); + + ResourceMessageFilter* resource_message_filter = new ResourceMessageFilter( + ChildProcessHostImpl::GenerateChildProcessUniqueId(), PROCESS_TYPE_MEDIA, + static_cast( + storage_partition->GetAppCacheService()), + ChromeBlobStorageContext::GetFor(browser_context), + storage_partition->GetFileSystemContext(), + static_cast( + storage_partition->GetServiceWorkerContext()), + storage_partition->GetHostZoomLevelContext(), get_contexts_callback); + process_->AddFilter(resource_message_filter); + + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + base::Bind(&MediaProcessHost::SendEstablishMediaChannel, + weak_factory_io_.GetWeakPtr(), renderer_id, callback)); +} + +bool MediaProcessHost::Init() { + TRACE_EVENT_INSTANT0("media", "Init", TRACE_EVENT_SCOPE_THREAD); + + std::string channel_id = process_->GetHost()->CreateChannel(); + if (channel_id.empty()) + return false; + + if (in_process_) { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + DCHECK(g_media_main_thread_factory); + base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); + + MediaDataManagerImpl* media_data_manager = + MediaDataManagerImpl::GetInstance(); + DCHECK(media_data_manager); + media_data_manager->AppendMediaCommandLine(command_line); + in_process_media_thread_.reset( + g_media_main_thread_factory(InProcessChildThreadParams( + channel_id, base::MessageLoop::current()->task_runner()))); + in_process_media_thread_->Start(); + + OnProcessLaunched(); // Fake a callback that the process is ready. + } else if (!LaunchMediaProcess(channel_id)) { + return false; + } + + if (!Send(new MediaMsg_Initialize())) + return false; + + return true; +} + +void MediaProcessHost::RouteOnUIThread(const IPC::Message& message) { + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(&RouteToMediaProcessHostUIShimTask, host_id_, message)); +} + +bool MediaProcessHost::Send(IPC::Message* msg) { + DCHECK(CalledOnValidThread()); + if (process_->GetHost()->IsChannelOpening()) { + queued_messages_.push(msg); + return true; + } + + bool result = process_->Send(msg); + if (!result) { + // Channel is hosed, but we may not get destroyed for a while. Send + // outstanding channel creation failures now so that the caller can restart + // with a new process/channel without waiting. + SendOutstandingReplies(); + } + return result; +} + +void MediaProcessHost::AddFilter(IPC::MessageFilter* filter) { + DCHECK(CalledOnValidThread()); + process_->GetHost()->AddFilter(filter); +} + +bool MediaProcessHost::OnMessageReceived(const IPC::Message& message) { + DCHECK(CalledOnValidThread()); + IPC_BEGIN_MESSAGE_MAP(MediaProcessHost, message) + IPC_MESSAGE_HANDLER(MediaHostMsg_Initialized, OnInitialized) + IPC_MESSAGE_HANDLER(MediaHostMsg_ChannelEstablished, OnChannelEstablished) + IPC_MESSAGE_UNHANDLED(RouteOnUIThread(message)) + IPC_END_MESSAGE_MAP() + + return true; +} + +void MediaProcessHost::OnChannelConnected(int32 peer_pid) { + TRACE_EVENT0("media", "MediaProcessHost::OnChannelConnected"); + + while (!queued_messages_.empty()) { + Send(queued_messages_.front()); + queued_messages_.pop(); + } +} + +void MediaProcessHost::SendEstablishMediaChannel( + int render_id, + const EstablishChannelCallback& callback) { + if (Send(new MediaMsg_EstablishChannel(render_id))) { + channel_requests_.push(callback); + } else { + VLOG(1) << "Failed to send MediaMsg_EstablishChannel."; + callback.Run(IPC::ChannelHandle()); + } +} + +void MediaProcessHost::EstablishMediaChannel( + int render_id, + const EstablishChannelCallback& callback) { + DCHECK(CalledOnValidThread()); + TRACE_EVENT0("media", "MediaProcessHost::EstablishMediaChannel"); + + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(&MediaProcessHost::CreateResourceMessageFilter, + weak_factory_ui_.GetWeakPtr(), render_id, callback)); +} + +void MediaProcessHost::OnInitialized(bool result) { + initialized_ = result; + TRACE_EVENT0("media", "MediaProcessHost::OnInitialized"); + + /* TODO: implement + if (!initialized_) + MediaDataManagerImpl::GetInstance()->OnMediaProcessInitFailure(); + */ +} + +void MediaProcessHost::OnChannelEstablished( + const IPC::ChannelHandle& channel_handle) { + TRACE_EVENT0("media", "MediaProcessHost::OnChannelEstablished"); + + if (channel_requests_.empty()) { + // This happens when media process is compromised. + RouteOnUIThread(MediaHostMsg_OnLogMessage( + logging::LOG_WARNING, "WARNING", + "Received a ChannelEstablished message but no requests in queue.")); + return; + } + EstablishChannelCallback callback = channel_requests_.front(); + channel_requests_.pop(); + + callback.Run(channel_handle); +} + +void MediaProcessHost::OnProcessLaunched() { + TRACE_EVENT0("media", "MediaProcessHost::OnProcessLaunched"); +} + +void MediaProcessHost::OnProcessCrashed(int exit_code) { + TRACE_EVENT0("media", "MediaProcessHost::OnProcessCrashed"); + SendOutstandingReplies(); + MediaDataManagerImpl::GetInstance()->ProcessCrashed( + process_->GetTerminationStatus(true /* known_dead */, NULL)); +} + +MediaProcessHost::MediaProcessKind MediaProcessHost::kind() { + return kind_; +} + +void MediaProcessHost::ForceShutdown() { + // This is only called on the IO thread so no race against the constructor + // for another MediaProcessHost. + if (g_media_process_hosts[kind_] == this) + g_media_process_hosts[kind_] = NULL; + + process_->ForceShutdown(); +} + +bool MediaProcessHost::LaunchMediaProcess(const std::string& channel_id) { + const base::CommandLine& browser_command_line = + *base::CommandLine::ForCurrentProcess(); + + base::CommandLine::StringType media_launcher = + browser_command_line.GetSwitchValueNative(switches::kMediaLauncher); + +#if defined(OS_LINUX) + int child_flags = media_launcher.empty() ? ChildProcessHost::CHILD_ALLOW_SELF + : ChildProcessHost::CHILD_NORMAL; +#else + int child_flags = ChildProcessHost::CHILD_NORMAL; +#endif + + base::FilePath exe_path = ChildProcessHost::GetChildPath(child_flags); + if (exe_path.empty()) + return false; + + base::CommandLine* cmd_line = new base::CommandLine(exe_path); + cmd_line->AppendSwitchASCII(switches::kProcessType, switches::kMediaProcess); + cmd_line->AppendSwitchASCII(switches::kProcessChannelID, channel_id); + + if (kind_ == MEDIA_PROCESS_KIND_UNSANDBOXED) + cmd_line->AppendSwitch(switches::kDisableMediaSandbox); + + // Propagate relevant command line switches. + static const char* const kSwitchNames[] = { + switches::kAllowSandboxDebugging, + switches::kDisableMediaSandbox, + switches::kDisableLogging, + switches::kDisableSeccompFilterSandbox, + switches::kEnableLogging, + switches::kMediaStartupDialog, + switches::kNoSandbox, + switches::kV, + switches::kVModule, + }; + + cmd_line->CopySwitchesFrom(browser_command_line, kSwitchNames, + arraysize(kSwitchNames)); + cmd_line->CopySwitchesFrom(browser_command_line, switches::kMediaSwitches, + switches::kNumMediaSwitches); + + GetContentClient()->browser()->AppendExtraCommandLineSwitches( + cmd_line, process_->GetData().id); + + MediaDataManagerImpl::GetInstance()->AppendMediaCommandLine(cmd_line); + + // If specified, prepend a launcher program to the command line. + if (!media_launcher.empty()) + cmd_line->PrependWrapper(media_launcher); + + process_->Launch( + new MediaSandboxedProcessLauncherDelegate(cmd_line, process_->GetHost()), + cmd_line, true); + + process_launched_ = true; + return true; +} + +void MediaProcessHost::SendOutstandingReplies() { + valid_ = false; + // First send empty channel handles for all EstablishChannel requests. + while (!channel_requests_.empty()) { + EstablishChannelCallback callback = channel_requests_.front(); + channel_requests_.pop(); + callback.Run(IPC::ChannelHandle()); + } +} + +} // namespace content diff --git a/content/browser/media/media_process_host.h b/content/browser/media/media_process_host.h new file mode 100644 index 0000000..c151702 --- /dev/null +++ b/content/browser/media/media_process_host.h @@ -0,0 +1,179 @@ +// Copyright (c) 2015 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. + +#ifndef CONTENT_BROWSER_MEDIA_MEDIA_PROCESS_HOST_H_ +#define CONTENT_BROWSER_MEDIA_MEDIA_PROCESS_HOST_H_ + +#include +#include +#include +#include + +#include "base/callback.h" +#include "base/containers/hash_tables.h" +#include "base/memory/weak_ptr.h" +#include "base/threading/non_thread_safe.h" +#include "base/threading/thread.h" +#include "base/time/time.h" +#include "content/common/content_export.h" +#include "content/public/browser/browser_child_process_host_delegate.h" +#include "content/public/browser/media_data_manager.h" +#include "content/common/media/media_process_launch_causes.h" +#include "ipc/ipc_sender.h" +#include "ipc/message_filter.h" + +namespace IPC { +struct ChannelHandle; +} + +namespace content { +class BrowserChildProcessHostImpl; +class GpuMessageFilter; +class MediaMainThread; +class RenderWidgetHelper; +class InProcessChildThreadParams; + +typedef base::Thread* (*MediaMainThreadFactoryFunction)( + const InProcessChildThreadParams&); + +class MediaProcessHost : public BrowserChildProcessHostDelegate, + public IPC::Sender, + public base::NonThreadSafe { + public: + enum MediaProcessKind { + MEDIA_PROCESS_KIND_UNSANDBOXED, + MEDIA_PROCESS_KIND_SANDBOXED, + MEDIA_PROCESS_KIND_COUNT + }; + + typedef base::Callback + EstablishChannelCallback; + + // Creates a new MediaProcessHost or gets an existing one, resulting in the + // launching of a media process if required. Returns null on failure. It + // is not safe to store the pointer once control has returned to the message + // loop as it can be destroyed. Instead store the associated GPU host ID. + // This could return NULL if media access is not allowed (blacklisted). + CONTENT_EXPORT static MediaProcessHost* Get(MediaProcessKind kind, + CauseForMediaLaunch cause); + + // Retrieves a list of process handles for all media processes. + static void GetProcessHandles( + const MediaDataManager::GetMediaProcessHandlesCallback& callback); + + static void SendOnIO(MediaProcessKind kind, + CauseForMediaLaunch cause, + IPC::Message* message); + + CONTENT_EXPORT static void RegisterMediaMainThreadFactory( + MediaMainThreadFactoryFunction create); + + // Get the media process host for the media process with the given ID. Returns + // null if the process no longer exists. + static MediaProcessHost* FromID(int host_id); + int host_id() const { return host_id_; } + + // IPC::Sender implementation. + bool Send(IPC::Message* msg) override; + + // Adds a message filter to the MediaProcessHost's channel. + void AddFilter(IPC::MessageFilter* filter); + + // Tells the media process to create a new channel for communication with a + // client. Once the Media process responds asynchronously with the IPC handle + // and MediaInfo, we call the callback. + void EstablishMediaChannel(int render_id, + const EstablishChannelCallback& callback); + + // What kind of media process, e.g. sandboxed or unsandboxed. + MediaProcessKind kind(); + + void ForceShutdown(); + + private: + static bool ValidateHost(MediaProcessHost* host); + + MediaProcessHost(int host_id, MediaProcessKind kind); + ~MediaProcessHost() override; + + bool Init(); + + void CreateGpuMessageFilter(); + void CreateResourceMessageFilter(int renderer_id, + const EstablishChannelCallback& callback); + void SendEstablishMediaChannel(int render_id, + const EstablishChannelCallback& callback); + + // Post an IPC message to the UI shim's message handler on the UI thread. + void RouteOnUIThread(const IPC::Message& message); + + // BrowserChildProcessHostDelegate implementation. + bool OnMessageReceived(const IPC::Message& message) override; + void OnChannelConnected(int32 peer_pid) override; + void OnProcessLaunched() override; + void OnProcessCrashed(int exit_code) override; + + // Message handlers. + void OnInitialized(bool result); + void OnChannelEstablished(const IPC::ChannelHandle& channel_handle); + + bool LaunchMediaProcess(const std::string& channel_id); + + void SendOutstandingReplies(); + + // The serial number of the MediaProcessHost / MediaProcessHostUIShim pair. + int host_id_; + + // These are the channel requests that we have already sent to + // the Media process, but haven't heard back about yet. + std::queue channel_requests_; + + // Queued messages to send when the process launches. + std::queue queued_messages_; + + // Whether the Media process is valid, set to false after Send() failed. + bool valid_; + + // Whether we are running a Media thread inside the browser process instead + // of a separate Media process. + bool in_process_; + + MediaProcessKind kind_; + + scoped_ptr in_process_media_thread_; + + // Whether we actually launched a Media process. + bool process_launched_; + + // Whether the Media process successfully initialized. + bool initialized_; + + int gpu_client_id_; + + scoped_ptr process_; + + // Used to allow a RenderWidgetHost to intercept various messages on the + // IO thread. + scoped_refptr widget_helper_; + + // The filter for GPU-related messages coming from the renderer. + // Thread safety note: this field is to be accessed from the UI thread. + // We don't keep a reference to it, to avoid it being destroyed on the UI + // thread, but we clear this field when we clear channel_. When channel_ goes + // away, it posts a task to the IO thread to destroy it there, so we know that + // it's valid if non-NULL. + GpuMessageFilter* gpu_message_filter_; + + // TODO: because of assert in debug mode we had to split weak factory for UI + // and IO threads. + // We should find a better solution. + base::WeakPtrFactory weak_factory_ui_; + base::WeakPtrFactory weak_factory_io_; + + DISALLOW_COPY_AND_ASSIGN(MediaProcessHost); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_MEDIA_MEDIA_PROCESS_HOST_H_ diff --git a/content/browser/media/media_process_host_ui_shim.cc b/content/browser/media/media_process_host_ui_shim.cc new file mode 100644 index 0000000..d5e6fe4 --- /dev/null +++ b/content/browser/media/media_process_host_ui_shim.cc @@ -0,0 +1,166 @@ +// Copyright (c) 2015 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 "content/browser/media/media_process_host_ui_shim.h" + +#include + +#include "base/bind.h" +#include "base/callback_helpers.h" +#include "base/id_map.h" +#include "base/lazy_instance.h" +#include "base/strings/string_number_conversions.h" +#include "base/trace_event/trace_event.h" +#include "content/browser/media/media_data_manager_impl.h" +#include "content/browser/media/media_process_host.h" +#include "content/common/media/media_messages.h" +#include "content/public/browser/browser_thread.h" + +namespace content { + +namespace { + +// One of the linux specific headers defines this as a macro. +#ifdef DestroyAll +#undef DestroyAll +#endif + +base::LazyInstance> g_hosts_by_id = + LAZY_INSTANCE_INITIALIZER; + +void SendOnIOThreadTask(int host_id, IPC::Message* msg) { + MediaProcessHost* host = MediaProcessHost::FromID(host_id); + if (host) + host->Send(msg); + else + delete msg; +} + +class ScopedSendOnIOThread { + public: + ScopedSendOnIOThread(int host_id, IPC::Message* msg) + : host_id_(host_id), msg_(msg), cancelled_(false) {} + + ~ScopedSendOnIOThread() { + if (!cancelled_) { + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + base::Bind(&SendOnIOThreadTask, host_id_, msg_.release())); + } + } + + void Cancel() { cancelled_ = true; } + + private: + int host_id_; + scoped_ptr msg_; + bool cancelled_; +}; + +} // namespace + +void RouteToMediaProcessHostUIShimTask(int host_id, const IPC::Message& msg) { + MediaProcessHostUIShim* ui_shim = MediaProcessHostUIShim::FromID(host_id); + if (ui_shim) + ui_shim->OnMessageReceived(msg); +} + +MediaProcessHostUIShim::MediaProcessHostUIShim(int host_id) + : host_id_(host_id) { + g_hosts_by_id.Pointer()->AddWithID(this, host_id_); +} + +// static +MediaProcessHostUIShim* MediaProcessHostUIShim::Create(int host_id) { + DCHECK(!FromID(host_id)); + return new MediaProcessHostUIShim(host_id); +} + +// static +void MediaProcessHostUIShim::Destroy(int host_id, const std::string& message) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + + MediaDataManagerImpl::GetInstance()->AddLogMessage( + logging::LOG_ERROR, "MediaProcessHostUIShim", message); + + delete FromID(host_id); +} + +// static +void MediaProcessHostUIShim::DestroyAll() { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + while (!g_hosts_by_id.Pointer()->IsEmpty()) { + IDMap::iterator it(g_hosts_by_id.Pointer()); + delete it.GetCurrentValue(); + } +} + +// static +MediaProcessHostUIShim* MediaProcessHostUIShim::FromID(int host_id) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + return g_hosts_by_id.Pointer()->Lookup(host_id); +} + +// static +MediaProcessHostUIShim* MediaProcessHostUIShim::GetOneInstance() { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + if (g_hosts_by_id.Pointer()->IsEmpty()) + return NULL; + IDMap::iterator it(g_hosts_by_id.Pointer()); + return it.GetCurrentValue(); +} + +bool MediaProcessHostUIShim::Send(IPC::Message* msg) { + DCHECK(CalledOnValidThread()); + return BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + base::Bind(&SendOnIOThreadTask, host_id_, msg)); +} + +bool MediaProcessHostUIShim::OnMessageReceived(const IPC::Message& message) { + DCHECK(CalledOnValidThread()); + + if (message.routing_id() != MSG_ROUTING_CONTROL) + return false; + + return OnControlMessageReceived(message); +} + +void MediaProcessHostUIShim::SimulateClean() { + Send(new MediaMsg_Clean()); +} + +void MediaProcessHostUIShim::SimulateCrash() { + Send(new MediaMsg_Crash()); +} + +void MediaProcessHostUIShim::SimulateHang() { + Send(new MediaMsg_Hang()); +} + +MediaProcessHostUIShim::~MediaProcessHostUIShim() { + DCHECK(CalledOnValidThread()); + g_hosts_by_id.Pointer()->Remove(host_id_); +} + +bool MediaProcessHostUIShim::OnControlMessageReceived( + const IPC::Message& message) { + DCHECK(CalledOnValidThread()); + + IPC_BEGIN_MESSAGE_MAP(MediaProcessHostUIShim, message) + IPC_MESSAGE_HANDLER(MediaHostMsg_OnLogMessage, OnLogMessage) + + IPC_MESSAGE_UNHANDLED_ERROR() + IPC_END_MESSAGE_MAP() + + return true; +} + +void MediaProcessHostUIShim::OnLogMessage(int level, + const std::string& header, + const std::string& message) { + MediaDataManagerImpl::GetInstance()->AddLogMessage(level, header, message); +} + +} // namespace content diff --git a/content/browser/media/media_process_host_ui_shim.h b/content/browser/media/media_process_host_ui_shim.h new file mode 100644 index 0000000..3fc5914 --- /dev/null +++ b/content/browser/media/media_process_host_ui_shim.h @@ -0,0 +1,84 @@ +// Copyright (c) 2015 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. + +#ifndef CONTENT_BROWSER_MEDIA_MEDIA_PROCESS_HOST_UI_SHIM_H_ +#define CONTENT_BROWSER_MEDIA_MEDIA_PROCESS_HOST_UI_SHIM_H_ + +// This class lives on the UI thread and supports classes +// which must live on the UI thread. The IO thread +// portion of this class, the MediaProcessHost, is responsible for +// shuttling messages between the browser and media processes. + +#include + +#include "base/callback.h" +#include "base/compiler_specific.h" +#include "base/memory/linked_ptr.h" +#include "base/memory/ref_counted.h" +#include "base/threading/non_thread_safe.h" +#include "content/common/content_export.h" +#include "content/common/message_router.h" +#include "ipc/ipc_listener.h" +#include "ipc/ipc_sender.h" + +namespace IPC { +class Message; +} + +namespace content { +void RouteToMediaProcessHostUIShimTask(int host_id, const IPC::Message& msg); + +class MediaProcessHostUIShim : public IPC::Listener, + public IPC::Sender, + public base::NonThreadSafe { + public: + // Create a MediaProcessHostUIShim with the given ID. The object can be found + // using FromID with the same id. + static MediaProcessHostUIShim* Create(int host_id); + + // Destroy the MediaProcessHostUIShim with the given host ID. This can only + // be called on the UI thread. Only the GpuProcessHost should destroy the + // UI shim. + static void Destroy(int host_id, const std::string& message); + + // Destroy all remaining MediaProcessHostUIShims. + CONTENT_EXPORT static void DestroyAll(); + + CONTENT_EXPORT static MediaProcessHostUIShim* FromID(int host_id); + + // Get a MediaProcessHostUIShim instance; it doesn't matter which one. + // Return NULL if none has been created. + CONTENT_EXPORT static MediaProcessHostUIShim* GetOneInstance(); + + // IPC::Sender implementation. + bool Send(IPC::Message* msg) override; + + // IPC::Listener implementation. + // The GpuProcessHost causes this to be called on the UI thread to + // dispatch the incoming messages from the GPU process, which are + // actually received on the IO thread. + bool OnMessageReceived(const IPC::Message& message) override; + + CONTENT_EXPORT void SimulateClean(); + CONTENT_EXPORT void SimulateCrash(); + CONTENT_EXPORT void SimulateHang(); + + private: + explicit MediaProcessHostUIShim(int host_id); + ~MediaProcessHostUIShim() override; + + // Message handlers. + bool OnControlMessageReceived(const IPC::Message& message); + + void OnLogMessage(int level, + const std::string& header, + const std::string& message); + + // The serial number of the MediaProcessHost / MediaProcessHostUIShim pair. + int host_id_; +}; + +} // namespace content + +#endif // CONTENT_BROWSER_MEDIA_MEDIA_PROCESS_HOST_UI_SHIM_H_ diff --git a/content/browser/renderer_host/media_message_filter.cc b/content/browser/renderer_host/media_message_filter.cc new file mode 100644 index 0000000..76f8702 --- /dev/null +++ b/content/browser/renderer_host/media_message_filter.cc @@ -0,0 +1,76 @@ +// Copyright (c) 2015 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 "content/browser/renderer_host/media_message_filter.h" + +#include "base/bind.h" +#include "base/command_line.h" +#include "content/browser/media/media_process_host.h" +#include "content/common/media/media_messages.h" +#include "content/public/common/content_switches.h" + +namespace content { + +MediaMessageFilter::MediaMessageFilter(int render_process_id) + : BrowserMessageFilter(MediaMsgStart), + media_process_id_(0), // There is only one media process. + render_process_id_(render_process_id), + weak_ptr_factory_(this) { + DCHECK_CURRENTLY_ON(BrowserThread::UI); + DVLOG(1) << __FUNCTION__ << "(Create MediaMessageFilter)"; +} + +MediaMessageFilter::~MediaMessageFilter() { + DCHECK_CURRENTLY_ON(BrowserThread::IO); +} + +bool MediaMessageFilter::OnMessageReceived(const IPC::Message& message) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(MediaMessageFilter, message) + IPC_MESSAGE_HANDLER_DELAY_REPLY(MediaHostMsg_EstablishMediaChannel, + OnEstablishMediaChannel) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + return handled; +} + +void MediaMessageFilter::OnEstablishMediaChannel( + CauseForMediaLaunch cause_for_media_launch, + IPC::Message* reply_ptr) { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + scoped_ptr reply(reply_ptr); + + MediaProcessHost* host = MediaProcessHost::FromID(media_process_id_); + if (!host) { + DVLOG(1) << __FUNCTION__ << "(Launching Media Process)"; + host = MediaProcessHost::Get(MediaProcessHost::MEDIA_PROCESS_KIND_SANDBOXED, + cause_for_media_launch); + if (!host) { + reply->set_reply_error(); + Send(reply.release()); + return; + } + + media_process_id_ = host->host_id(); + } + + DVLOG(1) << __FUNCTION__ << "(Establishing channel)"; + + host->EstablishMediaChannel( + render_process_id_, + base::Bind(&MediaMessageFilter::EstablishChannelCallback, + weak_ptr_factory_.GetWeakPtr(), base::Passed(&reply))); +} + +void MediaMessageFilter::EstablishChannelCallback( + scoped_ptr reply, + const IPC::ChannelHandle& channel) { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + DVLOG(1) << __FUNCTION__ << "(Media channel established)"; + MediaHostMsg_EstablishMediaChannel::WriteReplyParams( + reply.get(), render_process_id_, channel); + Send(reply.release()); +} + +} // namespace content diff --git a/content/browser/renderer_host/media_message_filter.h b/content/browser/renderer_host/media_message_filter.h new file mode 100644 index 0000000..10570e2 --- /dev/null +++ b/content/browser/renderer_host/media_message_filter.h @@ -0,0 +1,58 @@ +// Copyright (c) 2015 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. + +#ifndef CONTENT_BROWSER_RENDERER_HOST_MEDIA_MESSAGE_FILTER_H_ +#define CONTENT_BROWSER_RENDERER_HOST_MEDIA_MESSAGE_FILTER_H_ + +#include + +#include "base/memory/linked_ptr.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/sequenced_task_runner_helpers.h" +#include "content/common/media/media_process_launch_causes.h" +#include "content/public/browser/browser_message_filter.h" + +class MediaProcessHost; + +namespace content { +class RenderWidgetHelper; +class RenderWidgetHostViewFrameSubscriber; + +// A message filter for messages from the renderer to the +// MediaProcessHost(UIShim) +// in the browser. Such messages are typically destined for the media process, +// but need to be mediated by the browser. +class MediaMessageFilter : public BrowserMessageFilter { + public: + MediaMessageFilter(int render_process_id); + + // BrowserMessageFilter methods: + bool OnMessageReceived(const IPC::Message& message) override; + + private: + friend class BrowserThread; + friend class base::DeleteHelper; + + ~MediaMessageFilter() override; + + // Message handlers called on the browser IO thread: + void OnEstablishMediaChannel(CauseForMediaLaunch, IPC::Message* reply); + + // Helper callbacks for the message handlers. + void EstablishChannelCallback(scoped_ptr reply, + const IPC::ChannelHandle& channel); + + int media_process_id_; + int render_process_id_; + + base::WeakPtrFactory weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(MediaMessageFilter); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_RENDERER_HOST_MEDIA_MESSAGE_FILTER_H_ diff --git a/content/browser/renderer_host/render_process_host_impl.cc b/content/browser/renderer_host/render_process_host_impl.cc index 248ed1d..06c6509 100644 --- a/content/browser/renderer_host/render_process_host_impl.cc +++ b/content/browser/renderer_host/render_process_host_impl.cc @@ -169,6 +169,11 @@ #include "ui/gl/gpu_switching_manager.h" #include "ui/native_theme/native_theme_switches.h" +#if defined(USE_GSTREAMER) +#include "content/browser/media/media_process_host.h" +#include "content/browser/renderer_host/media_message_filter.h" +#endif + #if defined(OS_ANDROID) #include "content/browser/android/child_process_launcher_android.h" #include "content/browser/media/android/browser_demuxer_android.h" @@ -809,6 +814,12 @@ void RenderProcessHostImpl::CreateMessageFilters() { gpu_message_filter_ = new GpuMessageFilter(GetID(), widget_helper_.get()); AddFilter(gpu_message_filter_); + +#if defined(USE_GSTREAMER) + media_message_filter_ = new MediaMessageFilter(GetID()); + AddFilter(media_message_filter_); +#endif + #if defined(ENABLE_WEBRTC) AddFilter(new WebRTCIdentityServiceHost( GetID(), storage_partition_impl_->GetWebRTCIdentityStore())); @@ -1381,6 +1392,9 @@ void RenderProcessHostImpl::PropagateBrowserCommandLineToRenderer( switches::kDisableWebAudio, switches::kRendererWaitForJavaDebugger, #endif +#if defined(USE_GSTREAMER) + switches::kEnableGStreamerMediaBackend, +#endif #if defined(OS_MACOSX) // Allow this to be set when invoking the browser and relayed along. switches::kEnableSandboxLogging, @@ -1685,6 +1699,9 @@ void RenderProcessHostImpl::Cleanup() { // The following members should be cleared in ProcessDied() as well! gpu_message_filter_ = NULL; +#if defined(USE_GSTREAMER) + media_message_filter_ = NULL; +#endif message_port_message_filter_ = NULL; #if defined(ENABLE_BROWSER_CDMS) browser_cdm_manager_ = NULL; diff --git a/content/browser/renderer_host/render_process_host_impl.h b/content/browser/renderer_host/render_process_host_impl.h index 8937fd1..f92e5f8 100644 --- a/content/browser/renderer_host/render_process_host_impl.h +++ b/content/browser/renderer_host/render_process_host_impl.h @@ -52,6 +52,9 @@ class BrowserCdmManager; class BrowserDemuxerAndroid; class GpuMessageFilter; class InProcessChildThreadParams; +#if defined(USE_GSTREAMER) +class MediaMessageFilter; +#endif class MessagePortMessageFilter; class MojoApplicationHost; class NotificationMessageFilter; @@ -376,6 +379,11 @@ class CONTENT_EXPORT RenderProcessHostImpl // it's valid if non-NULL. GpuMessageFilter* gpu_message_filter_; +#if defined(USE_GSTREAMER) + // The filter for media related messages coming from the renderer. + MediaMessageFilter* media_message_filter_; +#endif + // The filter for MessagePort messages coming from the renderer. scoped_refptr message_port_message_filter_; diff --git a/content/common/child_process_host_impl.cc b/content/common/child_process_host_impl.cc index 45ede03..b651c3a 100644 --- a/content/common/child_process_host_impl.cc +++ b/content/common/child_process_host_impl.cc @@ -171,8 +171,25 @@ ChildProcessHostImpl::~ChildProcessHostImpl() { void ChildProcessHostImpl::AddFilter(IPC::MessageFilter* filter) { filters_.push_back(filter); +// TODO: Remove. +// This is a temporary solution to allow adding ResourceMessageFilter +// dynamically after the channel is created. +// It will be removed when we find the way to use the resource storage in media +// process. +// See MediaProcessHost::CreateResourceMessageFilter for more details. +#if defined(USE_GSTREAMER) + if (channel_) { + filter->OnFilterAdded(channel_.get()); + + // Adding filter after being connected. + if (peer_process_.IsValid()) { + filter->OnChannelConnected(peer_process_.Pid()); + } + } +#else if (channel_) filter->OnFilterAdded(channel_.get()); +#endif } void ChildProcessHostImpl::ForceShutdown() { diff --git a/content/common/content_constants_internal.cc b/content/common/content_constants_internal.cc index afd5a17..5a39534 100644 --- a/content/common/content_constants_internal.cc +++ b/content/common/content_constants_internal.cc @@ -15,6 +15,9 @@ const uint32 kMaxPluginSize = 8 << 20; // 10MiB const size_t kMaxLengthOfDataURLString = 1024 * 1024 * 10; +#if defined(USE_GSTREAMER) +const int kTraceEventMediaProcessSortIndex = -7; +#endif const int kTraceEventBrowserProcessSortIndex = -6; const int kTraceEventRendererProcessSortIndex = -5; const int kTraceEventPluginProcessSortIndex = -4; diff --git a/content/common/content_constants_internal.h b/content/common/content_constants_internal.h index a5ce925..b93d7be 100644 --- a/content/common/content_constants_internal.h +++ b/content/common/content_constants_internal.h @@ -23,6 +23,9 @@ extern const uint32 kMaxPluginSize; extern const size_t kMaxLengthOfDataURLString; // Constants used to organize content processes in about:tracing. +#if defined(USE_GSTREAMER) +CONTENT_EXPORT extern const int kTraceEventMediaProcessSortIndex; +#endif CONTENT_EXPORT extern const int kTraceEventBrowserProcessSortIndex; CONTENT_EXPORT extern const int kTraceEventRendererProcessSortIndex; CONTENT_EXPORT extern const int kTraceEventPluginProcessSortIndex; diff --git a/content/common/content_message_generator.h b/content/common/content_message_generator.h index 803e5f6..3c0a465 100644 --- a/content/common/content_message_generator.h +++ b/content/common/content_message_generator.h @@ -63,6 +63,11 @@ #include "content/common/websocket_messages.h" #include "content/common/worker_messages.h" +#if defined(USE_GSTREAMER) +#include "content/common/media/media_messages.h" +#include "content/common/media/media_player_messages_gstreamer.h" +#endif + #if defined(ENABLE_WEBRTC) #include "content/common/p2p_messages.h" #endif diff --git a/content/common/gpu/client/command_buffer_metrics.cc b/content/common/gpu/client/command_buffer_metrics.cc index d16f276..4cd714b 100644 --- a/content/common/gpu/client/command_buffer_metrics.cc +++ b/content/common/gpu/client/command_buffer_metrics.cc @@ -95,6 +95,12 @@ void RecordContextLost(CommandBufferContextType type, UMA_HISTOGRAM_ENUMERATION("GPU.ContextLost.VideoAccelerator", reason, CONTEXT_LOST_REASON_MAX_ENUM); break; +#if defined(USE_GSTREAMER) + case MEDIA_GSTREAMER_CONTEXT: + UMA_HISTOGRAM_ENUMERATION("GPU.ContextLost.MediaGStreamer", reason, + CONTEXT_LOST_REASON_MAX_ENUM); + break; +#endif case OFFSCREEN_VIDEO_CAPTURE_CONTEXT: UMA_HISTOGRAM_ENUMERATION("GPU.ContextLost.VideoCapture", reason, CONTEXT_LOST_REASON_MAX_ENUM); @@ -132,6 +138,10 @@ std::string CommandBufferContextTypeToString(CommandBufferContextType type) { return "Offscreen-CaptureThread"; case OFFSCREEN_CONTEXT_FOR_WEBGL: return "Offscreen-For-WebGL"; +#if defined(USE_GSTREAMER) + case MEDIA_GSTREAMER_CONTEXT: + return "Offscreen-For-Media-GStreamer"; +#endif default: NOTREACHED(); return "unknown"; diff --git a/content/common/gpu/client/command_buffer_metrics.h b/content/common/gpu/client/command_buffer_metrics.h index e198d85..e29872f 100644 --- a/content/common/gpu/client/command_buffer_metrics.h +++ b/content/common/gpu/client/command_buffer_metrics.h @@ -18,6 +18,9 @@ enum CommandBufferContextType { RENDER_WORKER_CONTEXT, RENDERER_MAINTHREAD_CONTEXT, GPU_VIDEO_ACCELERATOR_CONTEXT, +#if defined(USE_GSTREAMER) + MEDIA_GSTREAMER_CONTEXT, +#endif OFFSCREEN_VIDEO_CAPTURE_CONTEXT, OFFSCREEN_CONTEXT_FOR_WEBGL, CONTEXT_TYPE_UNKNOWN, diff --git a/content/common/gpu/client/command_buffer_proxy_impl.cc b/content/common/gpu/client/command_buffer_proxy_impl.cc index 60cc94a..46853f4 100644 --- a/content/common/gpu/client/command_buffer_proxy_impl.cc +++ b/content/common/gpu/client/command_buffer_proxy_impl.cc @@ -25,6 +25,10 @@ #include "ui/gfx/geometry/size.h" #include "ui/gl/gl_bindings.h" +#if defined(USE_GSTREAMER) && defined(OS_POSIX) +#include "ipc/ipc_platform_file_attachment_posix.h" +#endif + namespace content { CommandBufferProxyImpl::CommandBufferProxyImpl(GpuChannelHost* channel, @@ -436,6 +440,41 @@ int CommandBufferProxyImpl::GetRouteID() const { return route_id_; } +#if defined(USE_GSTREAMER) +int32_t CommandBufferProxyImpl::CreateEGLImage( + size_t width, + size_t height, + const std::vector& attributes, + const std::vector& dmabuf_fds) { + CheckLock(); + if (last_state_.error != gpu::error::kNoError) + return -1; + + if (!channel_ || channel_->IsLost()) + return -1; + + int32 new_id = channel_->ReserveImageId(); + + IPC::Message* msg = new GpuCommandBufferMsg_CreateEGLImage( + route_id_, new_id, gfx::Size(width, height), attributes); + + for (size_t i = 0; i < dmabuf_fds.size(); ++i) { + bool write_ret = msg->WriteAttachment( + new IPC::internal::PlatformFileAttachment(dmabuf_fds[i])); + if (!write_ret) + return -1; + } + + // GpuChannelHost has a thread safe sender sync_filter_ so it is fine to call + // it from any thread. It matchs usage of eglCreateImage with EGL_NO_CONTEXT. + if (!Send(msg)) { + return -1; + } + + return new_id; +} +#endif + uint32 CommandBufferProxyImpl::CreateStreamTexture(uint32 texture_id) { CheckLock(); if (last_state_.error != gpu::error::kNoError) diff --git a/content/common/gpu/client/command_buffer_proxy_impl.h b/content/common/gpu/client/command_buffer_proxy_impl.h index e248891..4c3d071 100644 --- a/content/common/gpu/client/command_buffer_proxy_impl.h +++ b/content/common/gpu/client/command_buffer_proxy_impl.h @@ -109,6 +109,14 @@ class CommandBufferProxyImpl size_t height, unsigned internalformat, unsigned usage) override; + +#if defined(USE_GSTREAMER) + int32_t CreateEGLImage(size_t width, + size_t height, + const std::vector& attributes, + const std::vector& dmabuf_fds); +#endif + uint32 InsertSyncPoint() override; uint32_t InsertFutureSyncPoint() override; void RetireSyncPoint(uint32_t sync_point) override; diff --git a/content/common/gpu/gpu_channel.cc b/content/common/gpu/gpu_channel.cc index 6078d74..04299d2 100644 --- a/content/common/gpu/gpu_channel.cc +++ b/content/common/gpu/gpu_channel.cc @@ -45,6 +45,10 @@ #include "ipc/ipc_channel_posix.h" #endif +#if defined(USE_GSTREAMER) +#include "ui/gl/gl_image_egl.h" +#endif + namespace content { namespace { @@ -869,6 +873,34 @@ scoped_refptr GpuChannel::CreateImageForGpuMemoryBuffer( } } +#if defined(USE_GSTREAMER) +scoped_refptr GpuChannel::CreateEGLImage( + const gfx::Size& size, + const std::vector& attributes, + const std::vector& dmabuf_fds) { + scoped_refptr image(new gfx::GLImageEGL(size)); + + EGLint* attrs = const_cast(attributes.data()); + for (size_t i = 0; i < 20; ++i) { + if (attrs[i] == EGL_DMA_BUF_PLANE0_FD_EXT) { + attrs[i + 1] = dmabuf_fds[0]; + } else if (attrs[i] == EGL_DMA_BUF_PLANE1_FD_EXT) { + attrs[i + 1] = dmabuf_fds[1]; + } else if (attrs[i] == EGL_DMA_BUF_PLANE2_FD_EXT) { + attrs[i + 1] = dmabuf_fds[2]; + } else if (attrs[i] == EGL_NONE) { + break; + } + } + + if (!image->Initialize(EGL_LINUX_DMA_BUF_EXT, + static_cast(nullptr), attrs)) + return scoped_refptr(); + + return image; +} +#endif + void GpuChannel::HandleUpdateValueState( unsigned int target, const gpu::ValueState& state) { pending_valuebuffer_state_->UpdateState(target, state); diff --git a/content/common/gpu/gpu_channel.h b/content/common/gpu/gpu_channel.h index 6920df5..36b81c7 100644 --- a/content/common/gpu/gpu_channel.h +++ b/content/common/gpu/gpu_channel.h @@ -164,6 +164,13 @@ class GpuChannel : public IPC::Listener, gfx::GpuMemoryBuffer::Format format, uint32 internalformat); +#if defined(USE_GSTREAMER) + scoped_refptr CreateEGLImage( + const gfx::Size& size, + const std::vector& attributes, + const std::vector& dmabuf_fds); +#endif + bool allow_future_sync_points() const { return allow_future_sync_points_; } void HandleUpdateValueState(unsigned int target, diff --git a/content/common/gpu/gpu_command_buffer_stub.cc b/content/common/gpu/gpu_command_buffer_stub.cc index 81a6e2c..450d13b 100644 --- a/content/common/gpu/gpu_command_buffer_stub.cc +++ b/content/common/gpu/gpu_command_buffer_stub.cc @@ -179,7 +179,12 @@ GpuCommandBufferStub::GpuCommandBufferStub( delayed_work_scheduled_(false), previous_messages_processed_(0), active_url_(active_url), +#if defined(USE_GSTREAMER) + total_gpu_memory_(0), + dmabuf_fds_(3, 0) { +#else total_gpu_memory_(0) { +#endif active_url_hash_ = base::Hash(active_url.possibly_invalid_spec()); FastSetActiveURL(active_url_, active_url_hash_); @@ -251,6 +256,21 @@ bool GpuCommandBufferStub::OnMessageReceived(const IPC::Message& message) { have_context = true; } +#if defined(USE_GSTREAMER) + if (message.type() == GpuCommandBufferMsg_CreateEGLImage::ID) { + scoped_refptr attachment; + base::PickleIterator iter(message); + if (iter.SkipBytes(68)) { + for (int i = 0; i < 3; ++i) { + if (message.ReadAttachment(&iter, &attachment)) + dmabuf_fds_[i] = attachment->TakePlatformFile(); + else + break; + } + } + } +#endif + // Always use IPC_MESSAGE_HANDLER_DELAY_REPLY for synchronous message handlers // here. This is so the reply can be delayed if the scheduler is unscheduled. bool handled = true; @@ -288,6 +308,9 @@ bool GpuCommandBufferStub::OnMessageReceived(const IPC::Message& message) { OnSetClientHasMemoryAllocationChangedCallback) IPC_MESSAGE_HANDLER(GpuCommandBufferMsg_CreateImage, OnCreateImage); IPC_MESSAGE_HANDLER(GpuCommandBufferMsg_DestroyImage, OnDestroyImage); +#if defined(USE_GSTREAMER) + IPC_MESSAGE_HANDLER(GpuCommandBufferMsg_CreateEGLImage, OnCreateEGLImage); +#endif IPC_MESSAGE_HANDLER(GpuCommandBufferMsg_CreateStreamTexture, OnCreateStreamTexture) IPC_MESSAGE_UNHANDLED(handled = false) @@ -520,6 +543,17 @@ void GpuCommandBufferStub::OnInitialize( } channel_->share_group()->SetSharedContext(context.get()); } + +#if defined(USE_GSTREAMER) + if (!handle_.is_null() && + surface_->GetConfig() != + channel_->gpu_channel_manager() + ->GetDefaultOffscreenSurface() + ->GetConfig()) + LOG(ERROR) << "OFF and ON screenSurface do not have the same EGLConfig, " + "eglMakeCurrent might fail."; +#endif + // This should be a non-virtual GL context. DCHECK(context->GetHandle()); context = new gpu::GLContextVirtual( @@ -1009,6 +1043,35 @@ void GpuCommandBufferStub::OnDestroyImage(int32 id) { image_manager->RemoveImage(id); } +#if defined(USE_GSTREAMER) +void GpuCommandBufferStub::OnCreateEGLImage( + int32 id, + gfx::Size size, + const std::vector& attributes) { + TRACE_EVENT0("gpu", "GpuCommandBufferStub::OnCreateEGLImage"); + + if (!decoder_) + return; + + gpu::gles2::ImageManager* image_manager = decoder_->GetImageManager(); + DCHECK(image_manager); + if (image_manager->LookupImage(id)) { + LOG(ERROR) << "Image already exists with same ID."; + return; + } + + scoped_refptr image = + channel()->CreateEGLImage(size, attributes, dmabuf_fds_); + dmabuf_fds_[0] = dmabuf_fds_[1] = dmabuf_fds_[2] = 0; + if (!image.get()) { + LOG(ERROR) << "channel()->CreateEGLImage failed."; + return; + } + + image_manager->AddImage(image.get(), id); +} +#endif + void GpuCommandBufferStub::SendConsoleMessage( int32 id, const std::string& message) { diff --git a/content/common/gpu/gpu_command_buffer_stub.h b/content/common/gpu/gpu_command_buffer_stub.h index f9e6207..f8caa3f 100644 --- a/content/common/gpu/gpu_command_buffer_stub.h +++ b/content/common/gpu/gpu_command_buffer_stub.h @@ -215,6 +215,12 @@ class GpuCommandBufferStub uint32 internalformat); void OnDestroyImage(int32 id); +#if defined(USE_GSTREAMER) + void OnCreateEGLImage(int32 id, + gfx::Size size, + const std::vector& attributes); +#endif + void OnCommandProcessed(); void OnParseError(); void OnCreateStreamTexture( @@ -289,6 +295,10 @@ class GpuCommandBufferStub scoped_ptr wait_for_token_; scoped_ptr wait_for_get_offset_; +#if defined(USE_GSTREAMER) + std::vector dmabuf_fds_; +#endif + DISALLOW_COPY_AND_ASSIGN(GpuCommandBufferStub); }; diff --git a/content/common/gpu/gpu_messages.h b/content/common/gpu/gpu_messages.h index c60cfad..42823eb 100644 --- a/content/common/gpu/gpu_messages.h +++ b/content/common/gpu/gpu_messages.h @@ -646,6 +646,14 @@ IPC_MESSAGE_ROUTED5(GpuCommandBufferMsg_CreateImage, IPC_MESSAGE_ROUTED1(GpuCommandBufferMsg_DestroyImage, int32 /* id */) +#if defined(USE_GSTREAMER) +// Create an EGLImage. +IPC_MESSAGE_ROUTED3(GpuCommandBufferMsg_CreateEGLImage, + int32 /* id */, + gfx::Size /* size */, + std::vector /* attributes */) +#endif + // Attaches an external image stream to the client texture. IPC_SYNC_MESSAGE_ROUTED2_1(GpuCommandBufferMsg_CreateStreamTexture, uint32, /* client_texture_id */ diff --git a/content/common/gpu/gpu_process_launch_causes.h b/content/common/gpu/gpu_process_launch_causes.h index 7524827..e7148e2 100644 --- a/content/common/gpu/gpu_process_launch_causes.h +++ b/content/common/gpu/gpu_process_launch_causes.h @@ -22,6 +22,9 @@ enum CauseForGpuLaunch { CAUSE_FOR_GPU_LAUNCH_PEPPERVIDEOENCODERACCELERATOR_INITIALIZE, CAUSE_FOR_GPU_LAUNCH_GPU_MEMORY_BUFFER_ALLOCATE, CAUSE_FOR_GPU_LAUNCH_JPEGDECODEACCELERATOR_INITIALIZE, +#if defined(USE_GSTREAMER) + CAUSE_FOR_GPU_LAUNCH_MEDIA_GSTREAMER_CONTEXT_INITIALIZE, +#endif // All new values should be inserted above this point so that // existing values continue to match up with those in histograms.xml. diff --git a/content/common/media/media_channel.cc b/content/common/media/media_channel.cc new file mode 100644 index 0000000..414c1b4 --- /dev/null +++ b/content/common/media/media_channel.cc @@ -0,0 +1,369 @@ +// Copyright (c) 2015 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 "content/common/media/media_channel.h" + +#include +#include + +#include "base/bind.h" +#include "base/command_line.h" +#include "base/stl_util.h" +#include "base/strings/string_util.h" +#include "base/timer/timer.h" +#include "base/trace_event/trace_event.h" +#include "content/public/common/content_switches.h" +#include "content/child/child_process.h" +#include "ipc/ipc_channel.h" +#include "ipc/ipc_message_macros.h" +#include "ipc/message_filter.h" +#include "content/common/media/media_channel_filter.h" +#include "content/common/media/media_player_messages_gstreamer.h" +#include "content/media/gstreamer/media_player_gstreamer.h" + +#if defined(OS_POSIX) +#include "ipc/ipc_channel_posix.h" +#endif + +namespace content { + +MediaChannel::MediaChannel(int client_id, MediaChannelFilter* channel_filter) + : client_id_(client_id), channel_filter_(channel_filter) { + DCHECK(client_id); + + channel_id_ = IPC::Channel::GenerateVerifiedChannelID("media"); +} + +MediaChannel::~MediaChannel() {} + +void MediaChannel::Init(base::SingleThreadTaskRunner* io_task_runner, + base::WaitableEvent* shutdown_event) { + DCHECK(!channel_.get()); + + // Map renderer ID to a (single) channel to that process. + channel_ = + IPC::SyncChannel::Create(channel_id_, IPC::Channel::MODE_SERVER, this, + io_task_runner, false, shutdown_event); +} + +std::string MediaChannel::GetChannelName() { + return channel_id_; +} + +#if defined(OS_POSIX) +base::ScopedFD MediaChannel::TakeRendererFileDescriptor() { + if (!channel_) { + NOTREACHED(); + return base::ScopedFD(); + } + return channel_->TakeClientFileDescriptor(); +} +#endif // defined(OS_POSIX) + +bool MediaChannel::OnMessageReceived(const IPC::Message& message) { + bool handled = true; + + IPC_BEGIN_MESSAGE_MAP(MediaChannel, message) + IPC_MESSAGE_HANDLER(MediaPlayerMsg_Create, OnMediaPlayerCreate) + IPC_MESSAGE_HANDLER(MediaPlayerMsg_Load, OnMediaPlayerLoad) + IPC_MESSAGE_HANDLER(MediaPlayerMsg_Start, OnMediaPlayerStart) + IPC_MESSAGE_HANDLER(MediaPlayerMsg_Pause, OnMediaPlayerPause) + IPC_MESSAGE_HANDLER(MediaPlayerMsg_Seek, OnMediaPlayerSeek) + IPC_MESSAGE_HANDLER(MediaPlayerMsg_Release, OnMediaPlayerRelease) + IPC_MESSAGE_HANDLER(MediaPlayerMsg_ReleaseTexture, + OnMediaPlayerReleaseTexture) + IPC_MESSAGE_HANDLER(MediaPlayerMsg_AddSourceId, OnMediaPlayerAddSourceId) + IPC_MESSAGE_HANDLER(MediaPlayerMsg_RemoveSourceId, + OnMediaPlayerRemoveSourceId) + IPC_MESSAGE_HANDLER(MediaPlayerMsg_SetDuration, OnMediaPlayerSetDuration) + IPC_MESSAGE_HANDLER(MediaPlayerMsg_MarkEndOfStream, + OnMediaPlayerMarkEndOfStream) + IPC_MESSAGE_HANDLER(MediaPlayerMsg_UnmarkEndOfStream, + OnMediaPlayerUnmarkEndOfStream) + IPC_MESSAGE_HANDLER(MediaPlayerMsg_SetSequenceMode, + OnMediaPlayerSetSequenceMode) + IPC_MESSAGE_HANDLER(MediaPlayerMsg_AppendData, OnMediaPlayerAppendData) + IPC_MESSAGE_HANDLER(MediaPlayerMsg_Abort, OnMediaPlayerAbort) + IPC_MESSAGE_HANDLER(MediaPlayerMsg_SetGroupStartTimestampIfInSequenceMode, + OnMediaPlayerSetGroupStartTimestampIfInSequenceMode) + IPC_MESSAGE_HANDLER(MediaPlayerMsg_RemoveSegment, + OnMediaPlayerRemoveSegment) + IPC_MESSAGE_HANDLER(MediaPlayerMsg_AddKey, OnMediaPlayerAddKey) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + return handled; +} + +void MediaChannel::OnChannelError() { + channel_filter_->RemoveChannel(client_id_); +} + +MediaPlayerGStreamer* MediaChannel::GetMediaPlayer(int player_id) { + MediaPlayerMap::const_iterator iter = media_players_.find(player_id); + if (iter != media_players_.end()) + return iter->second; + + scoped_ptr player( + channel_filter_->GetMediaPlayerFactory()->create(player_id, this)); + media_players_.set(player_id, player.Pass()); + return media_players_.find(player_id)->second; +} + +void MediaChannel::OnMediaPlayerCreate(int player_id) { + MediaPlayerGStreamer* player = GetMediaPlayer(player_id); + if (!player) { + SendMediaError(player_id, 0); + } +} + +void MediaChannel::OnMediaPlayerLoad(int player_id, GURL url, unsigned position_update_interval_ms) { + MediaPlayerGStreamer* player = GetMediaPlayer(player_id); + if (player) { + player->Load(url, position_update_interval_ms); + } +} + +void MediaChannel::OnMediaPlayerStart(int player_id) { + MediaPlayerGStreamer* player = GetMediaPlayer(player_id); + if (player) { + player->Play(); + } +} + +void MediaChannel::OnMediaPlayerPause(int player_id) { + MediaPlayerGStreamer* player = GetMediaPlayer(player_id); + if (player) { + player->Pause(); + } +} + +void MediaChannel::OnMediaPlayerSeek(int player_id, base::TimeDelta delta) { + MediaPlayerGStreamer* player = GetMediaPlayer(player_id); + if (player) { + player->Seek(delta); + } +} + +void MediaChannel::OnMediaPlayerRelease(int player_id) { + media_players_.erase(player_id); +} + +void MediaChannel::OnMediaPlayerReleaseTexture(int player_id, + unsigned texture_id) { + MediaPlayerGStreamer* player = GetMediaPlayer(player_id); + if (player) { + player->ReleaseTexture(texture_id); + } +} + +void MediaChannel::OnMediaPlayerAddSourceId( + int player_id, + const std::string& source_id, + const std::string& type, + const std::vector& codecs) { + MediaPlayerGStreamer* player = GetMediaPlayer(player_id); + if (player) { + player->AddSourceId(source_id, type, codecs); + } +} + +void MediaChannel::OnMediaPlayerRemoveSourceId(int player_id, + const std::string& source_id) { + MediaPlayerGStreamer* player = GetMediaPlayer(player_id); + if (player) { + player->RemoveSourceId(source_id); + } +} + +void MediaChannel::OnMediaPlayerSetDuration(int player_id, + const base::TimeDelta& duration) { + MediaPlayerGStreamer* player = GetMediaPlayer(player_id); + if (player) { + player->SetDuration(duration); + } +} + +void MediaChannel::OnMediaPlayerMarkEndOfStream(int player_id) { + MediaPlayerGStreamer* player = GetMediaPlayer(player_id); + if (player) { + player->MarkEndOfStream(); + } +} + +void MediaChannel::OnMediaPlayerUnmarkEndOfStream(int player_id) { + MediaPlayerGStreamer* player = GetMediaPlayer(player_id); + if (player) { + player->UnmarkEndOfStream(); + } +} + +void MediaChannel::OnMediaPlayerSetSequenceMode(int player_id, + const std::string& source_id, + bool sequence_mode) { + MediaPlayerGStreamer* player = GetMediaPlayer(player_id); + if (player) { + player->SetSequenceMode(source_id, sequence_mode); + } +} + +void MediaChannel::OnMediaPlayerAppendData( + int player_id, + const std::string& source_id, + const std::vector& data, + const std::vector& times) { + MediaPlayerGStreamer* player = GetMediaPlayer(player_id); + if (player) { + player->AppendData(source_id, data, times); + } +} + +void MediaChannel::OnMediaPlayerAbort(int player_id, + const std::string& source_id) { + MediaPlayerGStreamer* player = GetMediaPlayer(player_id); + if (player) { + player->Abort(source_id); + } +} + +void MediaChannel::OnMediaPlayerSetGroupStartTimestampIfInSequenceMode( + int player_id, + const std::string& source_id, + const base::TimeDelta& timestamp_offset) { + MediaPlayerGStreamer* player = GetMediaPlayer(player_id); + if (player) { + player->SetGroupStartTimestampIfInSequenceMode(source_id, timestamp_offset); + } +} + +void MediaChannel::OnMediaPlayerRemoveSegment(int player_id, + const std::string& source_id, + const base::TimeDelta& start, + const base::TimeDelta& end) { + MediaPlayerGStreamer* player = GetMediaPlayer(player_id); + if (player) { + player->RemoveSegment(source_id, start, end); + } +} + +void MediaChannel::OnMediaPlayerAddKey(int player_id, + const std::string& session_id, + const std::string& key_id, + const std::string& key) { + MediaPlayerGStreamer* player = GetMediaPlayer(player_id); + if (player) { + player->AddKey(session_id, key_id, key); + } +} + +void MediaChannel::SendMediaError(int player_id, int error) { + Send(new MediaPlayerMsg_MediaError(player_id, error)); +} + +void MediaChannel::SendMediaPlaybackCompleted(int player_id) { + Send(new MediaPlayerMsg_MediaPlaybackCompleted(player_id)); +} + +void MediaChannel::SendMediaDurationChanged(int player_id, + const base::TimeDelta& duration) { + Send(new MediaPlayerMsg_MediaDurationChanged(player_id, duration)); +} + +void MediaChannel::SendSeekCompleted(int player_id, + const base::TimeDelta& current_time) { + Send(new MediaPlayerMsg_SeekCompleted(player_id, current_time)); +} + +void MediaChannel::SendMediaVideoSizeChanged(int player_id, + int width, + int height) { + Send(new MediaPlayerMsg_MediaVideoSizeChanged(player_id, width, height)); +} + +void MediaChannel::SendMediaTimeUpdate(int player_id, + base::TimeDelta current_timestamp, + base::TimeTicks current_time_ticks) { + Send(new MediaPlayerMsg_MediaTimeUpdate(player_id, current_timestamp, + current_time_ticks)); +} + +void MediaChannel::SendBufferingUpdate(int player_id, int percent) { + Send(new MediaPlayerMsg_MediaBufferingUpdate(player_id, percent)); +} + +void MediaChannel::SendMediaPlayerReleased(int player_id) { + Send(new MediaPlayerMsg_MediaPlayerReleased(player_id)); +} + +void MediaChannel::SendDidMediaPlayerPlay(int player_id) { + Send(new MediaPlayerMsg_DidMediaPlayerPlay(player_id)); +} + +void MediaChannel::SendDidMediaPlayerPause(int player_id) { + Send(new MediaPlayerMsg_DidMediaPlayerPause(player_id)); +} + +void MediaChannel::SendSetCurrentFrame(int player_id, + int width, + int height, + unsigned texture_id, + unsigned target, + const std::vector& name) { + Send(new MediaPlayerMsg_SetCurrentFrame(player_id, width, height, texture_id, + target, name)); +} + +void MediaChannel::SendSourceSelected(int player_id) { + Send(new MediaPlayerMsg_SourceSelected(player_id)); +} + +void MediaChannel::SendDidAddSourceId(int player_id, + const std::string& source_id) { + Send(new MediaPlayerMsg_DidAddSourceId(player_id, source_id)); +} + +void MediaChannel::SendDidRemoveSourceId(int player_id, + const std::string& source_id) { + Send(new MediaPlayerMsg_DidRemoveSourceId(player_id, source_id)); +} + +void MediaChannel::SendInitSegmentReceived(int player_id, + const std::string& source_id) { + Send(new MediaPlayerMsg_InitSegmentReceived(player_id, source_id)); +} + +void MediaChannel::SendBufferedRangeUpdate( + int player_id, + const std::string& source_id, + const std::vector& ranges) { + Send(new MediaPlayerMsg_BufferedRangeUpdate(player_id, source_id, ranges)); +} + +void MediaChannel::SendTimestampOffsetUpdate( + int player_id, + const std::string& source_id, + const base::TimeDelta& timestamp_offset) { + Send(new MediaPlayerMsg_TimestampOffsetUpdate(player_id, source_id, + timestamp_offset)); +} + +void MediaChannel::SendNeedKey(int player_id, + const std::string& system_id, + const std::vector& init_data) { + Send(new MediaPlayerMsg_NeedKey(player_id, system_id, init_data)); +} + +bool MediaChannel::Send(IPC::Message* message) { + // The media process must never send a synchronous IPC message to the renderer + // process. This could result in deadlock. + DCHECK(!message->is_sync()); + + if (!channel_) { + delete message; + return false; + } + + return channel_->Send(message); +} + +} // namespace content diff --git a/content/common/media/media_channel.h b/content/common/media/media_channel.h new file mode 100644 index 0000000..d6a8721 --- /dev/null +++ b/content/common/media/media_channel.h @@ -0,0 +1,154 @@ +// Copyright (c) 2015 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. + +#ifndef CONTENT_COMMON_MEDIA_MEDIA_CHANNEL_H_ +#define CONTENT_COMMON_MEDIA_MEDIA_CHANNEL_H_ + +#include +#include + +#include "base/id_map.h" +#include "base/containers/scoped_ptr_hash_map.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/scoped_vector.h" +#include "base/memory/weak_ptr.h" +#include "base/process/process.h" +#include "build/build_config.h" +#include "content/common/message_router.h" +#include "ipc/ipc_sync_channel.h" +#include "url/gurl.h" + +namespace base { +class WaitableEvent; +} + +namespace IPC { +class MessageFilter; +} + +namespace content { +class MediaChannelFilter; +class MediaPlayerGStreamer; + +// Encapsulates an IPC channel between the media process and one renderer +// process. On the renderer side there's a corresponding web media player. +class MediaChannel : public IPC::Listener, public IPC::Sender { + public: + // Takes ownership of the renderer process handle. + MediaChannel(int, MediaChannelFilter*); + ~MediaChannel() override; + + void Init(base::SingleThreadTaskRunner* io_task_runner, + base::WaitableEvent* shutdown_event); + + // Returns the name of the associated IPC channel. + std::string GetChannelName(); + +#if defined(OS_POSIX) + base::ScopedFD TakeRendererFileDescriptor(); +#endif // defined(OS_POSIX) + + base::ProcessId renderer_pid() const { return channel_->GetPeerPID(); } + + int client_id() const { return client_id_; } + + MediaPlayerGStreamer* GetMediaPlayer(int player_id); + + // IPC::Listener implementation: + bool OnMessageReceived(const IPC::Message& msg) override; + void OnChannelError() override; + + void OnMediaPlayerCreate(int player_id); + void OnMediaPlayerLoad(int player_id, GURL url, unsigned position_update_interval_ms); + void OnMediaPlayerStart(int player_id); + void OnMediaPlayerPause(int player_id); + void OnMediaPlayerSeek(int player_id, base::TimeDelta delta); + void OnMediaPlayerRelease(int player_id); + void OnMediaPlayerReleaseTexture(int player_id, unsigned texture_id); + void OnMediaPlayerAddSourceId(int player_id, + const std::string& source_id, + const std::string& type, + const std::vector& codecs); + void OnMediaPlayerRemoveSourceId(int player_id, const std::string& source_id); + void OnMediaPlayerSetDuration(int player_id, const base::TimeDelta& duration); + void OnMediaPlayerMarkEndOfStream(int player_id); + void OnMediaPlayerUnmarkEndOfStream(int player_id); + void OnMediaPlayerSetSequenceMode(int player_id, + const std::string& source_id, + bool sequence_mode); + void OnMediaPlayerAppendData(int player_id, + const std::string& source_id, + const std::vector& data, + const std::vector& times); + void OnMediaPlayerAbort(int player_id, const std::string& source_id); + void OnMediaPlayerSetGroupStartTimestampIfInSequenceMode( + int player_id, + const std::string& source_id, + const base::TimeDelta& timestamp_offset); + void OnMediaPlayerRemoveSegment(int player_id, + const std::string& source_id, + const base::TimeDelta& start, + const base::TimeDelta& end); + void OnMediaPlayerAddKey(int player_id, + const std::string& session_id, + const std::string& key_id, + const std::string& key); + + // IPC::Sender implementation: + bool Send(IPC::Message* msg) override; + + void SendMediaError(int player_id, int error); + void SendMediaPlaybackCompleted(int player_id); + void SendMediaDurationChanged(int player_id, const base::TimeDelta& duration); + void SendSeekCompleted(int player_id, const base::TimeDelta& current_time); + void SendMediaVideoSizeChanged(int player_id, int width, int height); + void SendMediaTimeUpdate(int player_id, + base::TimeDelta current_timestamp, + base::TimeTicks current_time_ticks); + void SendBufferingUpdate(int player_id, int percent); + void SendMediaPlayerReleased(int player_id); + void SendDidMediaPlayerPlay(int player_id); + void SendDidMediaPlayerPause(int player_id); + void SendSetCurrentFrame(int player_id, + int width, + int height, + unsigned texture_id, + unsigned target, + const std::vector& name); + void SendSourceSelected(int player_id); + void SendDidAddSourceId(int player_id, const std::string& source_id); + void SendDidRemoveSourceId(int player_id, const std::string& source_id); + void SendInitSegmentReceived(int player_id, const std::string& source_id); + void SendBufferedRangeUpdate(int player_id, + const std::string& source_id, + const std::vector& ranges); + void SendTimestampOffsetUpdate(int player_id, + const std::string& source_id, + const base::TimeDelta& timestamp_offset); + void SendNeedKey(int player_id, + const std::string& system_id, + const std::vector& init_data); + + private: + scoped_ptr channel_; + + // The id of the client who is on the other side of the channel. + int client_id_; + + // Uniquely identifies the channel within this GPU process. + std::string channel_id_; + + scoped_refptr channel_filter_; + + typedef base::ScopedPtrHashMap> + MediaPlayerMap; + MediaPlayerMap media_players_; + + DISALLOW_COPY_AND_ASSIGN(MediaChannel); +}; + +} // namespace content + +#endif // CONTENT_COMMON_MEDIA_MEDIA_CHANNEL_H_ diff --git a/content/common/media/media_channel_filter.cc b/content/common/media/media_channel_filter.cc new file mode 100644 index 0000000..5870b1d --- /dev/null +++ b/content/common/media/media_channel_filter.cc @@ -0,0 +1,97 @@ +// Copyright (c) 2015 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_channel_filter.h" + +#include "base/bind.h" +#include "base/command_line.h" +#include "content/child/child_thread_impl.h" +#include "content/common/media/media_channel.h" +#include "content/common/media/media_messages.h" +#include "content/common/message_router.h" +#include "ipc/message_filter.h" + +namespace content { + +MediaChannelFilter::MediaChannelFilter( + MessageRouter* router, + base::SingleThreadTaskRunner* io_task_runner, + base::WaitableEvent* shutdown_event, + IPC::SyncChannel* channel, + MediaPlayerGStreamerFactory* media_player_factory) + : io_task_runner_(io_task_runner), + shutdown_event_(shutdown_event), + router_(router), + channel_(channel), + media_player_factory_(media_player_factory), + weak_factory_(this) { + DCHECK(router_); + DCHECK(io_task_runner); + DCHECK(shutdown_event); +} + +MediaChannelFilter::~MediaChannelFilter() { + media_channels_.clear(); +} + +void MediaChannelFilter::RemoveChannel(int client_id) { + media_channels_.erase(client_id); +} + +MediaChannel* MediaChannelFilter::LookupChannel(int32 client_id) { + MediaChannelMap::const_iterator iter = media_channels_.find(client_id); + if (iter == media_channels_.end()) + return NULL; + else + return iter->second; +} + +bool MediaChannelFilter::OnMessageReceived(const IPC::Message& msg) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(MediaChannelFilter, msg) + IPC_MESSAGE_HANDLER(MediaMsg_EstablishChannel, OnEstablishChannel) + IPC_MESSAGE_HANDLER(MediaMsg_CloseChannel, OnCloseChannel) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + return handled; +} + +bool MediaChannelFilter::Send(IPC::Message* msg) { + return router_->Send(msg); +} + +void MediaChannelFilter::OnEstablishChannel(int client_id) { + DCHECK_NE(io_task_runner_.get(), base::ThreadTaskRunnerHandle::Get().get()); + + IPC::ChannelHandle channel_handle; + + scoped_ptr channel(new MediaChannel(client_id, this)); + channel->Init(io_task_runner_.get(), shutdown_event_); + channel_handle.name = channel->GetChannelName(); + +#if defined(OS_POSIX) + // On POSIX, pass the renderer-side FD. Also mark it as auto-close so + // that it gets closed after it has been sent. + base::ScopedFD renderer_fd = channel->TakeRendererFileDescriptor(); + DCHECK(renderer_fd.is_valid()); + channel_handle.socket = base::FileDescriptor(renderer_fd.Pass()); +#endif + + media_channels_.set(client_id, channel.Pass()); + + Send(new MediaHostMsg_ChannelEstablished(channel_handle)); +} + +void MediaChannelFilter::OnCloseChannel( + const IPC::ChannelHandle& channel_handle) { + for (MediaChannelMap::iterator iter = media_channels_.begin(); + iter != media_channels_.end(); ++iter) { + if (iter->second->GetChannelName() == channel_handle.name) { + media_channels_.erase(iter); + return; + } + } +} + +} // namespace content diff --git a/content/common/media/media_channel_filter.h b/content/common/media/media_channel_filter.h new file mode 100644 index 0000000..0fcd540 --- /dev/null +++ b/content/common/media/media_channel_filter.h @@ -0,0 +1,99 @@ +// Copyright (c) 2015 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. + +#ifndef CONTENT_COMMON_MEDIA_MEDIA_CHANNEL_FILTER_H_ +#define CONTENT_COMMON_MEDIA_MEDIA_CHANNEL_FILTER_H_ + +#include +#include +#include + +#include "base/containers/scoped_ptr_hash_map.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "build/build_config.h" +#include "content/common/content_export.h" +#include "content/common/content_param_traits.h" +#include "content/media/gstreamer/media_player_gstreamer.h" +#include "ipc/ipc_listener.h" +#include "ipc/ipc_sender.h" +#include "ipc/message_filter.h" + +namespace base { +class WaitableEvent; +} + +namespace IPC { +struct ChannelHandle; +class SyncChannel; +// class MessageFilter; +} + +namespace content { +class MediaChannel; +class MediaPlayerGStreamerFactory; +class MessageRouter; + +// A MediaChannelManager is responsible for managing the lifetimes of media +// channels and forwarding IPC requests from the +// browser process to them based on the corresponding renderer ID. +class CONTENT_EXPORT MediaChannelFilter : public IPC::MessageFilter { + public: + MediaChannelFilter(MessageRouter* router, + base::SingleThreadTaskRunner* io_task_runner, + base::WaitableEvent* shutdown_event, + IPC::SyncChannel* channel, + MediaPlayerGStreamerFactory* media_player_factory); + ~MediaChannelFilter() override; + + // Remove the channel for a particular renderer. + void RemoveChannel(int client_id); + + // Listener overrides. + bool OnMessageReceived(const IPC::Message& msg) override; + + // Sender overrides. + bool Send(IPC::Message* msg); + + bool HandleMessagesScheduled(); + uint64 MessagesProcessed(); + + MediaChannel* LookupChannel(int32 client_id); + MediaPlayerGStreamerFactory* GetMediaPlayerFactory() { + return media_player_factory_.get(); + } + + private: + typedef base::ScopedPtrHashMap> MediaChannelMap; + + // Message handlers. + void OnEstablishChannel(int client_id); + void OnCloseChannel(const IPC::ChannelHandle& channel_handle); + + scoped_refptr io_task_runner_; + base::WaitableEvent* shutdown_event_; + + // Used to send and receive IPC messages from the browser process. + MessageRouter* const router_; + + // These objects manage channels to individual renderer processes there is + // one channel for each renderer process that has connected to this media + // process. + MediaChannelMap media_channels_; + IPC::SyncChannel* channel_; + scoped_refptr filter_; + scoped_ptr media_player_factory_; + + // Member variables should appear before the WeakPtrFactory, to ensure + // that any WeakPtrs to Controller are invalidated before its members + // variable's destructors are executed, rendering them invalid. + base::WeakPtrFactory weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(MediaChannelFilter); +}; + +} // namespace content + +#endif // CONTENT_COMMON_MEDIA_MEDIA_CHANNEL_FILTER_H_ diff --git a/content/common/media/media_channel_host.cc b/content/common/media/media_channel_host.cc new file mode 100644 index 0000000..ae7d19e --- /dev/null +++ b/content/common/media/media_channel_host.cc @@ -0,0 +1,151 @@ +// Copyright (c) 2015 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_channel_host.h" + +#include + +#include "base/bind.h" +#include "base/message_loop/message_loop.h" +#include "base/posix/eintr_wrapper.h" +#include "base/threading/thread_restrictions.h" +#include "base/trace_event/trace_event.h" +#include "content/child/child_process.h" +#include "content/common/media/media_messages.h" +#include "content/public/child/child_thread.h" +#include "content/renderer/media/gstreamer/webmediaplayer_gstreamer.h" +#include "ipc/ipc_sync_message_filter.h" +#include "url/gurl.h" + +using base::AutoLock; + +namespace content { + +// static +scoped_refptr MediaChannelHost::Create( + const IPC::ChannelHandle& channel_handle, + base::WaitableEvent* shutdown_event) { + scoped_refptr dispatcher = new MediaChannelHost; + dispatcher->Connect(channel_handle, shutdown_event); + return dispatcher; +} + +MediaChannelHost::MediaChannelHost() {} + +static bool IsMainThread() { + return !!ChildThread::Get(); +} + +void MediaChannelHost::Connect(const IPC::ChannelHandle& channel_handle, + base::WaitableEvent* shutdown_event) { + // Open a channel to the media process. We pass NULL as the main listener here + // since we need to filter everything to route it to the right thread. + channel_ = IPC::SyncChannel::Create( + channel_handle, IPC::Channel::MODE_CLIENT, NULL, + ChildProcess::current()->io_task_runner(), true, shutdown_event); + + sync_filter_ = new IPC::SyncMessageFilter(shutdown_event); + + channel_->AddFilter(sync_filter_.get()); + + channel_filter_ = new MessageFilter(); + + // Install the filter last, because we intercept all leftover + // messages. + channel_->AddFilter(channel_filter_.get()); +} + +bool MediaChannelHost::Send(IPC::Message* msg) { + scoped_ptr message(msg); + // The media process never sends synchronous IPCs so clear the unblock flag to + // preserve order. + message->set_unblock(false); + + if (IsMainThread()) { + base::ThreadRestrictions::ScopedAllowWait allow_wait; + bool result = channel_->Send(message.release()); + if (!result) + DVLOG(1) << "MediaChannelHost::Send failed: Channel::Send failed"; + return result; + } + + bool result = sync_filter_->Send(message.release()); + return result; +} + +void MediaChannelHost::DestroyChannel() { + DCHECK(IsMainThread()); + channel_.reset(); +} + +void MediaChannelHost::RegisterDispatcher( + int player_id, + media::WebMediaPlayerMessageDispatcher* dispatcher) { + channel_filter_->RegisterDispatcher(player_id, dispatcher); +} + +void MediaChannelHost::RemoveDispatcher(int player_id) { + channel_filter_->RemoveDispatcher(player_id); +} + +MediaChannelHost::~MediaChannelHost() { + DestroyChannel(); +} + +MediaChannelHost::MessageFilter::MessageFilter() : lost_(false) {} + +MediaChannelHost::MessageFilter::~MessageFilter() {} + +void MediaChannelHost::MessageFilter::RegisterDispatcher( + int player_id, + media::WebMediaPlayerMessageDispatcher* dispatcher) { + dispatchers_[player_id] = dispatcher; +} + +void MediaChannelHost::MessageFilter::RemoveDispatcher(int player_id) { + dispatchers_.erase(player_id); +} + +bool MediaChannelHost::MessageFilter::OnMessageReceived( + const IPC::Message& message) { + // Never handle sync message replies or we will deadlock here. + if (message.is_reply()) + return false; + + WebMediaPlayerDispatcherMap::iterator iter = + dispatchers_.find(message.routing_id()); + if (iter == dispatchers_.end()) + return false; + + media::WebMediaPlayerMessageDispatcher* dispatcher = iter->second; + scoped_refptr taskRunner = + dispatcher->GetTaskRunner(); + base::WeakPtr listener = dispatcher->AsWeakPtr(); + + taskRunner->PostTask( + FROM_HERE, + base::Bind(base::IgnoreResult(&IPC::Listener::OnMessageReceived), + listener, message)); + + return true; +} + +void MediaChannelHost::MessageFilter::OnChannelError() { + // Set the lost state before signalling the proxies. That way, if they + // themselves post a task to recreate the context, they will not try to re-use + // this channel host. + { + AutoLock lock(lock_); + lost_ = true; + } + + dispatchers_.clear(); +} + +bool MediaChannelHost::MessageFilter::IsLost() const { + AutoLock lock(lock_); + return lost_; +} + +} // namespace content diff --git a/content/common/media/media_channel_host.h b/content/common/media/media_channel_host.h new file mode 100644 index 0000000..752a3d6 --- /dev/null +++ b/content/common/media/media_channel_host.h @@ -0,0 +1,129 @@ +// Copyright (c) 2012 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. + +#ifndef CONTENT_COMMON_MEDIA_MESSAGE_DISPATCHER_HOST_H_ +#define CONTENT_COMMON_MEDIA_MESSAGE_DISPATCHER_HOST_H_ + +#include +#include + +#include "base/atomic_sequence_num.h" +#include "base/containers/hash_tables.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/process/process.h" +#include "base/synchronization/lock.h" +#include "content/common/content_export.h" +#include "content/common/message_router.h" +#include "ipc/ipc_channel_handle.h" +#include "ipc/ipc_sync_channel.h" +#include "ipc/message_filter.h" +#include "ui/events/latency_info.h" +#include "ui/gfx/geometry/size.h" +#include "ui/gfx/gpu_memory_buffer.h" +#include "ui/gfx/native_widget_types.h" +#include "ui/gl/gpu_preference.h" + +class GURL; + +namespace base { +class MessageLoop; +class WaitableEvent; +} + +namespace IPC { +class SyncMessageFilter; +} + +namespace media { +class WebMediaPlayerMessageDispatcher; +} + +namespace content { + +// Encapsulates an IPC channel between the client and one media process. +// On the media process side there's a corresponding channel. +// Every method can be called on any thread with a message loop, except for the +// IO thread. +class MediaChannelHost : public IPC::Sender, + public base::RefCountedThreadSafe { + public: + // Must be called on the main thread + static scoped_refptr Create( + const IPC::ChannelHandle& channel_handle, + base::WaitableEvent* shutdown_event); + + bool IsLost() const { + DCHECK(channel_filter_.get()); + return channel_filter_->IsLost(); + } + + // IPC::Sender implementation: + bool Send(IPC::Message* msg) override; + + void RegisterDispatcher(int, media::WebMediaPlayerMessageDispatcher*); + void RemoveDispatcher(int); + + // Destroy this channel. + void DestroyChannel(); + + private: + friend class base::RefCountedThreadSafe; + MediaChannelHost(); + ~MediaChannelHost() override; + void Connect(const IPC::ChannelHandle& channel_handle, + base::WaitableEvent* shutdown_event); + bool InternalSend(IPC::Message* msg); + void InternalFlush(); + + // A filter used internally to route incoming messages from the IO thread + // to the correct message loop. It also maintains some shared state between + // all the contexts. + class MessageFilter : public IPC::MessageFilter { + public: + MessageFilter(); + + void RegisterDispatcher(int, media::WebMediaPlayerMessageDispatcher*); + void RemoveDispatcher(int); + + // IPC::MessageFilter implementation + // (called on the IO thread): + bool OnMessageReceived(const IPC::Message& msg) override; + void OnChannelError() override; + + // The following methods can be called on any thread. + + // Whether the channel is lost. + bool IsLost() const; + + private: + ~MessageFilter() override; + + typedef base::hash_map + WebMediaPlayerDispatcherMap; + + // Threading notes: |dispatchers_| is only accessed on the IO thread. Every + // other field is protected by |lock_|. + WebMediaPlayerDispatcherMap dispatchers_; + + // Protects all fields below this one. + mutable base::Lock lock_; + + // Whether the channel has been lost. + bool lost_; + }; + + scoped_ptr channel_; + scoped_refptr channel_filter_; + + // A filter for sending messages from thread other than the main thread. + scoped_refptr sync_filter_; + + DISALLOW_COPY_AND_ASSIGN(MediaChannelHost); +}; + +} // namespace content + +#endif // CONTENT_COMMON_MEDIA_MESSAGE_DISPATCHER_HOST_H_ diff --git a/content/common/media/media_config.h b/content/common/media/media_config.h new file mode 100644 index 0000000..4439103 --- /dev/null +++ b/content/common/media/media_config.h @@ -0,0 +1,12 @@ +// Copyright (c) 2015 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. + +#ifndef CONTENT_COMMON_MEDIA_MEDIA_CONFIG_H_ +#define CONTENT_COMMON_MEDIA_MEDIA_CONFIG_H_ + +// This file declares common preprocessor configuration for the GPU process. + +#include "build/build_config.h" + +#endif // CONTENT_COMMON_MEDIA_MEDIA_CONFIG_H_ diff --git a/content/common/media/media_messages.h b/content/common/media/media_messages.h new file mode 100644 index 0000000..c185442 --- /dev/null +++ b/content/common/media/media_messages.h @@ -0,0 +1,83 @@ +// Copyright (c) 2015 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. + +// Multiply-included message file, hence no include guard here, but see below +// for a much smaller-than-usual include guard section. + +#include +#include + +#include "base/memory/shared_memory.h" +#include "content/common/content_export.h" +#include "content/common/content_param_traits.h" +#include "content/common/media/media_process_launch_causes.h" +#include "content/public/common/common_param_traits.h" +#include "ipc/ipc_channel_handle.h" +#include "ipc/ipc_message_macros.h" + +#undef IPC_MESSAGE_EXPORT +#define IPC_MESSAGE_EXPORT CONTENT_EXPORT + +#define IPC_MESSAGE_START MediaMsgStart + +IPC_ENUM_TRAITS_MAX_VALUE(content::CauseForMediaLaunch, + content::CAUSE_FOR_MEDIA_LAUNCH_MAX_ENUM - 1) + +//------------------------------------------------------------------------------ +// Media Messages +// These are messages from the browser to the media process. + +// Tells the media process to initialize itself. The browser explicitly +// requests this be done so that we are guaranteed that the channel is set +// up between the browser and media process before doing any work that might +// potentially crash the media process. Detection of the child process +// exiting abruptly is predicated on having the IPC channel set up. +IPC_MESSAGE_CONTROL0(MediaMsg_Initialize) + +// Tells the media process to create a new channel for communication with a +// given client. The channel name is returned in a +// MediaHostMsg_ChannelEstablished message. The client ID is passed so that +// the media process reuses an existing channel to that process if it exists. +// This ID is a unique opaque identifier generated by the browser process. +IPC_MESSAGE_CONTROL1(MediaMsg_EstablishChannel, int /* client_id */) + +// Tells the media process to close the channel identified by IPC channel +// handle. If no channel can be identified, do nothing. +IPC_MESSAGE_CONTROL1(MediaMsg_CloseChannel, + IPC::ChannelHandle /* channel_handle */) + +// Tells the media process to clean all resources. +IPC_MESSAGE_CONTROL0(MediaMsg_Clean) + +// Tells the media process to crash. +IPC_MESSAGE_CONTROL0(MediaMsg_Crash) + +// Tells the media process to hang. +IPC_MESSAGE_CONTROL0(MediaMsg_Hang) + +//------------------------------------------------------------------------------ +// Media Host Messages +// These are messages to the browser. + +// A renderer sends this when it wants to create a connection to the media +// process. The browser will create the media process if necessary, and will +// return a handle to the channel via a MediaChannelEstablished message. +IPC_SYNC_MESSAGE_CONTROL1_2(MediaHostMsg_EstablishMediaChannel, + content::CauseForMediaLaunch, + int /* client id */, + IPC::ChannelHandle /* handle to channel */) + +// Response from the media process to a MediaHostMsg_Initialize message. +IPC_MESSAGE_CONTROL1(MediaHostMsg_Initialized, bool /* result */) + +// Response from the media process to a MediaHostMsg_EstablishChannel message. +IPC_MESSAGE_CONTROL1(MediaHostMsg_ChannelEstablished, + IPC::ChannelHandle /* channel_handle */) + +// Message from the media process to add a media process log message to the +// about:media page. +IPC_MESSAGE_CONTROL3(MediaHostMsg_OnLogMessage, + int /*severity*/, + std::string /* header */, + std::string /* message */) diff --git a/content/common/media/media_player_messages_enums_gstreamer.h b/content/common/media/media_player_messages_enums_gstreamer.h new file mode 100644 index 0000000..512e16f --- /dev/null +++ b/content/common/media/media_player_messages_enums_gstreamer.h @@ -0,0 +1,15 @@ +// Copyright 2013 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. + +#ifndef CONTENT_COMMON_MEDIA_MEDIA_PLAYER_MESSAGES_ENUMS_GSTREAMER_H_ +#define CONTENT_COMMON_MEDIA_MEDIA_PLAYER_MESSAGES_ENUMS_GSTREAMER_H_ + +// Dictates which type of media playback is being initialized. +enum MediaPlayerHostMsg_Initialize_Type { + MEDIA_PLAYER_TYPE_URL, + MEDIA_PLAYER_TYPE_MEDIA_SOURCE, + MEDIA_PLAYER_TYPE_LAST = MEDIA_PLAYER_TYPE_MEDIA_SOURCE +}; + +#endif // CONTENT_COMMON_MEDIA_MEDIA_PLAYER_MESSAGES_ENUMS_GSTREAMER_H_ diff --git a/content/common/media/media_player_messages_gstreamer.h b/content/common/media/media_player_messages_gstreamer.h new file mode 100644 index 0000000..975b883 --- /dev/null +++ b/content/common/media/media_player_messages_gstreamer.h @@ -0,0 +1,176 @@ +// Copyright (c) 2015 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. + +// IPC messages for android media player. +// Multiply-included message file, hence no include guard. + +#include "base/basictypes.h" +#include "base/time/time.h" +#include "content/common/content_export.h" +#include "content/common/media/media_player_messages_enums_gstreamer.h" +#include "ipc/ipc_message_macros.h" +#include "url/gurl.h" + +#undef IPC_MESSAGE_EXPORT +#define IPC_MESSAGE_EXPORT CONTENT_EXPORT +#define IPC_MESSAGE_START MediaPlayerMsgStart + +// From render process to media process +IPC_SYNC_MESSAGE_CONTROL1_0(MediaPlayerMsg_Create, int /* player_id */) + +// Start the player for playback. +IPC_MESSAGE_CONTROL3(MediaPlayerMsg_Load, int /* player_id */, GURL /* url */, + unsigned /* position_update_interval */) + +IPC_MESSAGE_CONTROL1(MediaPlayerMsg_Start, int /* player_id */) + +IPC_MESSAGE_CONTROL1(MediaPlayerMsg_Pause, int /* player_id */) + +IPC_MESSAGE_CONTROL2(MediaPlayerMsg_Seek, + int /* player_id */, + base::TimeDelta /* time */) + +IPC_SYNC_MESSAGE_CONTROL1_0(MediaPlayerMsg_Release, int /* player_id */) + +// The player is done with the current texture +IPC_MESSAGE_CONTROL2(MediaPlayerMsg_ReleaseTexture, + int /* player_id */, + unsigned /* texture_id */) + +// Request the player to add a media source buffer. +IPC_MESSAGE_CONTROL4(MediaPlayerMsg_AddSourceId, + int /* player_id */, + std::string /* source id */, + std::string /* type */, + std::vector /* codecs */) + +// Request the player to remove a media source buffer. +IPC_MESSAGE_CONTROL2(MediaPlayerMsg_RemoveSourceId, + int /* player_id */, + std::string /* source id */) + +// Request the player to set the new duration. +IPC_MESSAGE_CONTROL2(MediaPlayerMsg_SetDuration, + int /* player_id */, + base::TimeDelta /* duration */) + +// Request the player to mark EOS the media source. +IPC_MESSAGE_CONTROL1(MediaPlayerMsg_MarkEndOfStream, int /* player_id */) + +// Request the player to unmark EOS the media source. +IPC_MESSAGE_CONTROL1(MediaPlayerMsg_UnmarkEndOfStream, int /* player_id */) + +// Request the player to set the MSE sequence mode. +IPC_MESSAGE_CONTROL3(MediaPlayerMsg_SetSequenceMode, + int /* player_id */, + std::string /* source id */, + bool /* sequence mode */) + +// Request the player to process source buffer data. +IPC_MESSAGE_CONTROL4(MediaPlayerMsg_AppendData, + int /* player_id */, + std::string /* source id */, + std::vector /* data */, + std::vector /* start, end, offset */) + +// Request the player to abort the current segment and reset the parser. +IPC_MESSAGE_CONTROL2(MediaPlayerMsg_Abort, + int /* player_id */, + std::string /* source id */) + +// Request the play to configure group timestamps for the sequence. +IPC_MESSAGE_CONTROL3(MediaPlayerMsg_SetGroupStartTimestampIfInSequenceMode, + int /* player_id */, + std::string /* source id */, + base::TimeDelta /* timestamp offset */) + +// Request the play to remove a segment for a source buffer. +IPC_MESSAGE_CONTROL4(MediaPlayerMsg_RemoveSegment, + int /* player_id */, + std::string /* source id */, + base::TimeDelta /* start */, + base::TimeDelta /* end */) + +// Add new DRM key. +IPC_MESSAGE_CONTROL4(MediaPlayerMsg_AddKey, + int /* player_id */, + std::string /* session id */, + std::string /* key id */, + std::string /* key */) + +// From media process to render process +IPC_MESSAGE_ROUTED1(MediaPlayerMsg_MediaBufferingUpdate, int /* percent */) + +// A media playback error has occurred. +IPC_MESSAGE_ROUTED1(MediaPlayerMsg_MediaError, int /* error */) + +// Playback is completed. +IPC_MESSAGE_ROUTED0(MediaPlayerMsg_MediaPlaybackCompleted) + +// Media metadata has changed. +IPC_MESSAGE_ROUTED1(MediaPlayerMsg_MediaDurationChanged, + base::TimeDelta /* duration */) + +// Media seek is completed. +IPC_MESSAGE_ROUTED1(MediaPlayerMsg_SeekCompleted, + base::TimeDelta /* current_time */) + +// Video size has changed. +IPC_MESSAGE_ROUTED2(MediaPlayerMsg_MediaVideoSizeChanged, + int /* width */, + int /* height */) + +// The current play time has updated. +IPC_MESSAGE_ROUTED2(MediaPlayerMsg_MediaTimeUpdate, + base::TimeDelta /* current_timestamp */, + base::TimeTicks /* current_time_ticks */) + +// The player has been released. +IPC_MESSAGE_ROUTED0(MediaPlayerMsg_MediaPlayerReleased) + +// The player exited fullscreen. +IPC_MESSAGE_ROUTED0(MediaPlayerMsg_DidExitFullscreen) + +// The player started playing. +IPC_MESSAGE_ROUTED0(MediaPlayerMsg_DidMediaPlayerPlay) + +// The player was paused. +IPC_MESSAGE_ROUTED0(MediaPlayerMsg_DidMediaPlayerPause) + +// The player is ready to use the current frame +IPC_MESSAGE_ROUTED5(MediaPlayerMsg_SetCurrentFrame, + int /* width */, + int /* height */, + unsigned /* texture_id */, + unsigned /* target */, + std::vector /* name */) + +// The player has setup its source element. +IPC_MESSAGE_ROUTED0(MediaPlayerMsg_SourceSelected) + +// The player has added a media source buffer. +IPC_MESSAGE_ROUTED1(MediaPlayerMsg_DidAddSourceId, std::string /* source id */) + +// The player has removed a media source buffer. +IPC_MESSAGE_ROUTED1(MediaPlayerMsg_DidRemoveSourceId, + std::string /* source id */) + +// The player validated a media source buffer. +IPC_MESSAGE_ROUTED1(MediaPlayerMsg_InitSegmentReceived, + std::string /* source id */) + +// The player updated the time range for last append buffer. +IPC_MESSAGE_ROUTED2(MediaPlayerMsg_BufferedRangeUpdate, + std::string /* source id */, + std::vector /* ranges */) + +// The player updated the timestamp offset for the next sequence. +IPC_MESSAGE_ROUTED2(MediaPlayerMsg_TimestampOffsetUpdate, + std::string /* source id */, + base::TimeDelta /* timestamp_offset */) + +// The player requested the DRM key. +IPC_MESSAGE_ROUTED2(MediaPlayerMsg_NeedKey, + std::string /* system_id */, + std::vector /* init_data */) diff --git a/content/common/media/media_process_launch_causes.h b/content/common/media/media_process_launch_causes.h new file mode 100644 index 0000000..c1b3f5a --- /dev/null +++ b/content/common/media/media_process_launch_causes.h @@ -0,0 +1,23 @@ +// Copyright (c) 2015 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. + +#ifndef CONTENT_COMMON_MEDIA_MEDIA_PROCESS_LAUNCH_CAUSES_H_ +#define CONTENT_COMMON_MEDIA_MEDIA_PROCESS_LAUNCH_CAUSES_H_ + +namespace content { + +enum CauseForMediaLaunch { + // Start enum from 2 to keep the same values for the histogram. + CAUSE_FOR_MEDIA_LAUNCH_NO_LAUNCH = 2, + CAUSE_FOR_MEDIA_LAUNCH_BROWSER_STARTUP, + CAUSE_FOR_MEDIA_LAUNCH_RENDERER, + + // All new values should be inserted above this point so that + // existing values continue to match up with those in histograms.xml. + CAUSE_FOR_MEDIA_LAUNCH_MAX_ENUM +}; + +} // namespace content + +#endif // CONTENT_COMMON_MEDIA_MEDIA_PROCESS_LAUNCH_CAUSES_H_ diff --git a/content/common/sandbox_init_mac.cc b/content/common/sandbox_init_mac.cc index 2597a75..4231805 100644 --- a/content/common/sandbox_init_mac.cc +++ b/content/common/sandbox_init_mac.cc @@ -57,6 +57,12 @@ bool GetSandboxTypeFromCommandLine(int* sandbox_type, return false; } else if (process_type == switches::kPpapiPluginProcess) { *sandbox_type = SANDBOX_TYPE_PPAPI; +#if defined(USE_GSTREAMER) + } else if (process_type == switches::kMediaProcess) { + if (command_line.HasSwitch(switches::kDisableMediaSandbox)) + return false; + *sandbox_type = SANDBOX_TYPE_MEDIA; +#endif } else { // This is a process which we don't know about, i.e. an embedder-defined // process. If the embedder wants it sandboxed, they have a chance to return diff --git a/content/common/sandbox_linux/bpf_media_policy_linux.cc b/content/common/sandbox_linux/bpf_media_policy_linux.cc new file mode 100644 index 0000000..67f7b25 --- /dev/null +++ b/content/common/sandbox_linux/bpf_media_policy_linux.cc @@ -0,0 +1,535 @@ +// Copyright (c) 2015 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 "content/common/sandbox_linux/bpf_media_policy_linux.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "base/bind.h" +#include "base/command_line.h" +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/string_split.h" +#include "build/build_config.h" +#include "content/common/sandbox_linux/sandbox_bpf_base_policy_linux.h" +#include "content/common/sandbox_linux/sandbox_seccomp_bpf_linux.h" +#include "content/common/set_process_title.h" +#include "content/public/common/content_switches.h" +#include "sandbox/linux/bpf_dsl/bpf_dsl.h" +#include "sandbox/linux/bpf_dsl/bpf_dsl_impl.h" +#include "sandbox/linux/bpf_dsl/policy_compiler.h" +#include "sandbox/linux/seccomp-bpf/die.h" +#include "sandbox/linux/seccomp-bpf/errorcode.h" +#include "sandbox/linux/seccomp-bpf/sandbox_bpf.h" +#include "sandbox/linux/seccomp-bpf/syscall.h" +#include "sandbox/linux/seccomp-bpf-helpers/sigsys_handlers.h" +#include "sandbox/linux/seccomp-bpf-helpers/syscall_parameters_restrictions.h" +#include "sandbox/linux/seccomp-bpf-helpers/syscall_sets.h" +#include "sandbox/linux/syscall_broker/broker_file_permission.h" +#include "sandbox/linux/syscall_broker/broker_process.h" +#include "sandbox/linux/system_headers/linux_syscalls.h" + +using sandbox::arch_seccomp_data; +using sandbox::bpf_dsl::Allow; +using sandbox::bpf_dsl::Error; +using sandbox::bpf_dsl::Arg; +using sandbox::bpf_dsl::If; +using sandbox::bpf_dsl::ResultExpr; +using sandbox::bpf_dsl::Trap; +using sandbox::bpf_dsl::UnsafeTrap; +using sandbox::syscall_broker::BrokerFilePermission; +using sandbox::syscall_broker::BrokerProcess; +using sandbox::SyscallSets; + +namespace content { + +namespace { + +intptr_t MediaSIGSYS_Handler(const struct arch_seccomp_data& args, + void* aux_broker_process) { + RAW_CHECK(aux_broker_process); + BrokerProcess* broker_process = + static_cast(aux_broker_process); + + // For debuging purposes: + // It is possible to do the system call in this handler (instead of doing + // it in the broker process) by just uncommenting the following line: + // return sandbox::SandboxBPF::ForwardSyscall(args); + + switch (args.nr) { +#if !defined(__aarch64__) + case __NR_access: { + intptr_t retv = + broker_process->Access(reinterpret_cast(args.args[0]), + static_cast(args.args[1])); + if (retv < 1) { + struct stat sb; + if (!stat(reinterpret_cast(args.args[0]), &sb)) { + VLOG(1) << "access failed: " + << reinterpret_cast(args.args[0]) + << static_cast(args.args[1]); + } + } + return retv; + } + case __NR_open: +#if defined(MEMORY_SANITIZER) + // http://crbug.com/372840 + __msan_unpoison_string(reinterpret_cast(args.args[0])); +#endif + { + intptr_t retv = + broker_process->Open(reinterpret_cast(args.args[0]), + static_cast(args.args[1])); + if (retv < 1) { + struct stat sb; + if (!stat(reinterpret_cast(args.args[0]), &sb)) { + VLOG(1) << "open failed: " + << reinterpret_cast(args.args[0]); + } + } + return retv; + } +#endif // !defined(__aarch64__) + case __NR_faccessat: + if (static_cast(args.args[0]) == AT_FDCWD) { + intptr_t retv = + broker_process->Access(reinterpret_cast(args.args[1]), + static_cast(args.args[2])); + if (retv < 1) { + struct stat sb; + if (!stat(reinterpret_cast(args.args[1]), &sb)) { + VLOG(1) << "faccessat failed: " + << reinterpret_cast(args.args[1]); + } + } + return retv; + } else { + return -EPERM; + } + case __NR_openat: + // Allow using openat() as open(). + if (static_cast(args.args[0]) == AT_FDCWD) { + intptr_t retv = + broker_process->Open(reinterpret_cast(args.args[1]), + static_cast(args.args[2])); + if (retv < 1) { + struct stat sb; + if (!stat(reinterpret_cast(args.args[1]), &sb)) { + VLOG(1) << "openat failed: " + << reinterpret_cast(args.args[1]); + } + } + return retv; + } else { + return -EPERM; + } + default: + RAW_CHECK(false); + return -ENOSYS; + } +} + +class MediaBrokerProcessPolicy : public MediaProcessPolicy { + public: + static sandbox::bpf_dsl::Policy* Create() { + return new MediaBrokerProcessPolicy(); + } + ~MediaBrokerProcessPolicy() override {} + + ResultExpr EvaluateSyscall(int system_call_number) const override; + + private: + MediaBrokerProcessPolicy() {} + DISALLOW_COPY_AND_ASSIGN(MediaBrokerProcessPolicy); +}; + +// x86_64/i386 or desktop ARM. +ResultExpr MediaBrokerProcessPolicy::EvaluateSyscall(int sysno) const { + switch (sysno) { +#if !defined(__aarch64__) + case __NR_access: + case __NR_open: +#endif // !defined(__aarch64__) + case __NR_faccessat: + case __NR_openat: + // The broker process needs to able to unlink the temporary + // files that it may create. + case __NR_unlink: + return Allow(); + default: + return MediaProcessPolicy::EvaluateSyscall(sysno); + } +} + +void UpdateProcessTypeToMediaBroker() { + base::CommandLine::StringVector exec = + base::CommandLine::ForCurrentProcess()->GetArgs(); + base::CommandLine::Reset(); + base::CommandLine::Init(0, NULL); + base::CommandLine::ForCurrentProcess()->InitFromArgv(exec); + base::CommandLine::ForCurrentProcess()->AppendSwitchASCII( + switches::kProcessType, "media-broker"); + + // Update the process title. The argv was already cached by the call to + // SetProcessTitleFromCommandLine in content_main_runner.cc, so we can pass + // NULL here (we don't have the original argv at this point). + SetProcessTitleFromCommandLine(NULL); +} + +bool UpdateProcessTypeAndEnableSandbox( + sandbox::bpf_dsl::Policy* (*broker_sandboxer_allocator)(void)) { + DCHECK(broker_sandboxer_allocator); + UpdateProcessTypeToMediaBroker(); + return SandboxSeccompBPF::StartSandboxWithExternalPolicy( + make_scoped_ptr(broker_sandboxer_allocator()), base::ScopedFD()); +} + +} // namespace + +MediaProcessPolicy::MediaProcessPolicy() : broker_process_(NULL) {} + +MediaProcessPolicy::~MediaProcessPolicy() {} + +// Main policy for x86_64/i386. +ResultExpr MediaProcessPolicy::EvaluateSyscall(int sysno) const { + // Uncomment next lines to enable UnsafeTrap below: + // if (sandbox::SandboxBPF::IsRequiredForUnsafeTrap(sysno)) + // return Allow(); + + switch (sysno) { +#if !defined(NDEBUG) + // TODO: check if this is really necessary for frame steping with gdb. + case __NR_pause: + return Allow(); +#endif + + case __NR_clock_getres: + // Allow the system calls below. + case __NR_fdatasync: +#if defined(__i386__) || defined(__x86_64__) || defined(__mips__) + case __NR_getrlimit: +#endif +#if defined(__i386__) || defined(__arm__) + case __NR_ugetrlimit: +#endif + case __NR_mremap: // See crbug.com/149834. + case __NR_pread64: + case __NR_pwrite64: + case __NR_sched_get_priority_max: + case __NR_sched_get_priority_min: + case __NR_sysinfo: + case __NR_times: + return Allow(); + +#if !defined(OS_CHROMEOS) + case __NR_ftruncate: +#endif + case __NR_fsync: +#if !defined(__aarch64__) + // TODO: investigate who is using it in order to remove it. + case __NR_getdents: // EPERM not a valid errno. +#endif + case __NR_getdents64: // EPERM not a valid errno. + case __NR_futex: + case __NR_ioctl: +#if defined(__i386__) || defined(__x86_64__) || defined(__mips__) + // The Nvidia driver uses flags not in the baseline policy + // (MAP_LOCKED | MAP_EXECUTABLE | MAP_32BIT) + case __NR_mmap: +#endif + // We also hit this on the linux_chromeos bot but don't yet know what + // weird flags were involved. + case __NR_mprotect: +#if defined(__i386__) || defined(__arm__) || defined(__mips__) + case __NR_stat64: + case __NR_statfs64: +#endif + case __NR_stat: + case __NR_statfs: // Required for shm_open. + case __NR_unlink: + case __NR_uname: + return Allow(); + + // Pulse audio, see pulsecore/core-utils.c::pa_make_secure_dir + case __NR_fchown: +#if defined(__i386__) || defined(__arm__) + case __NR_fchown32: +#endif + case __NR_lstat: + case __NR_mkdir: + return Allow(); + +// SECCOMP_RET_TRAP: +// Send a catchable SIGSYS, giving a chance to emulate the +// syscall. Emulation is done in the media broker process. +#if !defined(__aarch64__) + case __NR_access: + case __NR_open: +#endif // !defined(__aarch64__) + case __NR_faccessat: + case __NR_openat: { + DCHECK(broker_process_); + return Trap(MediaSIGSYS_Handler, broker_process_); + + // For debuging purposes in order to use SandboxBPF::ForwardSyscall + // in MediaSIGSYS_Handler you need to set CHROME_SANDBOX_DEBUGGING=1 + // and uncomment the following line: + // return UnsafeTrap(MediaSIGSYS_Handler, broker_process_); + } + + // Other cases: + default: + if (SyscallSets::IsEventFd(sysno)) + return Allow(); + + if (SyscallSets::IsUmask(sysno) || + SyscallSets::IsDeniedFileSystemAccessViaFd(sysno)) { + return Error(EPERM); + } + + // Debug + if (SyscallSets::IsDebug(sysno)) + return Error(EPERM); + + // Kernel + if (SyscallSets::IsKernelInternalApi(sysno) || + SyscallSets::IsKernelModule(sysno)) + return Error(EPERM); + + // System + if (SyscallSets::IsAdminOperation(sysno) || + SyscallSets::IsAnySystemV(sysno) || + SyscallSets::IsGlobalFSViewChange(sysno)) + return Error(EPERM); + + // Seccomp + if (SyscallSets::IsSeccomp(sysno)) + return Error(EPERM); + +// Network: (duplicate of SandboxBPFBasePolicy::EvaluateSyscall) +#if defined(__x86_64__) + if (sysno == __NR_socketpair) { + // Only allow AF_UNIX, PF_UNIX. Crash if anything else is seen. + static_assert(AF_UNIX == PF_UNIX, + "af_unix and pf_unix should not be different"); + const Arg domain(0); + return If(domain == AF_UNIX, Allow()).Else(sandbox::CrashSIGSYS()); + } + + // Pulseaudio, see socket-client.c::do_call + if (sysno == __NR_getsockopt) { + const Arg level(1); + return If(level == SOL_SOCKET, Allow()).Else(sandbox::CrashSIGSYS()); + } + + // Pulseaudio, see client-conf-x11::xcb_connect + switch (sysno) { +#if defined(__x86_64__) || defined(__arm__) || defined(__mips__) || \ + defined(__aarch64__) + case __NR_connect: + return Allow(); + case __NR_socket: + const Arg domain(0), type(1); + return If(domain == AF_UNIX && type == (SOCK_STREAM | SOCK_CLOEXEC), Allow()) + .Else(Error(EPERM)); + } +#endif + + if (SyscallSets::IsNetworkSocketInformation(sysno) || + SyscallSets::IsDeniedGetOrModifySocket(sysno)) { + return Error(EPERM); + } +#endif + + // Process + if (SyscallSets::IsCurrentDirectory(sysno) || + SyscallSets::IsFileSystem(sysno) || + SyscallSets::IsAllowedProcessStartOrDeath(sysno) || + SyscallSets::IsProcessGroupOrSession(sysno) || + SyscallSets::IsProcessPrivilegeChange(sysno)) + return Error(EPERM); + + return SandboxBPFBasePolicy::EvaluateSyscall(sysno); + } +} + +bool MediaProcessPolicy::PreSandboxHook() { + // Warm up resources needed by the policy we're about to enable and + // eventually start a broker process. + + DCHECK(!broker_process()); + // Create a new broker process. + InitMediaBrokerProcess( + MediaBrokerProcessPolicy::Create, + std::vector()); // No extra files in whitelist. + return true; +} + +static void splitAndAddReadOnlyPermission(const char* path, + const std::string& delimiter, + std::vector& permissions) +{ + if (path) { + std::vector results; + base::SplitStringUsingSubstr(path, delimiter, &results); + for (auto it = results.begin(); it != results.end(); ++it) { + if (!it->empty()) { + if (*(it->rbegin()) != '/') { + permissions.push_back( + BrokerFilePermission::ReadOnly(*it)); + it->append("/"); + } + permissions.push_back( + BrokerFilePermission::ReadOnlyRecursive(*it)); + } + } + } +} + +void MediaProcessPolicy::InitMediaBrokerProcess( + sandbox::bpf_dsl::Policy* (*broker_sandboxer_allocator)(void), + const std::vector& permissions_extra) { + CHECK(broker_process_ == NULL); + +// dev folders +#if !defined(NDEBUG) + // Core dumps. Can be chromium/src too. Use ulimit -c unlimited. + static const char kVarCrashPath[] = "/var/crash/"; +#endif + +#if !defined(OFFICIAL_BUILD) + // Gst plugins path + static const char* kGstPluginPath = getenv("GST_PLUGIN_PATH"); + static const char* kGstPluginScanner = getenv("GST_PLUGIN_SCANNER"); + static const char* kGstRegistry = getenv("GST_REGISTRY"); + static const char* kLdLibraryPath = getenv("LD_LIBRARY_PATH"); +#endif + + // Pulse audio. + static const char kDevShmPath[] = "/dev/shm/"; + static const char kUsrLibPulseModulesPath[] = "/usr/lib/pulse-4.0/modules/"; + static const char kEtcPulsePath[] = "/etc/pulse/"; + const std::string kDevRunUserPulse = + std::string(getenv("XDG_RUNTIME_DIR")) + "/pulse"; + const std::string kDevRunUserPulsePath = + std::string(getenv("XDG_RUNTIME_DIR")) + "/pulse/"; + const std::string kHomePulsePath = + std::string(getenv("HOME")) + "/.pulse/"; + const std::string kHomeConfigPulsePath = + std::string(getenv("HOME")) + "/.config/pulse/"; + const std::string kHomeXauth = + std::string(getenv("HOME")) + "/.Xauthority"; + + // GStreamer registry: + // TODO: Implement more fine grained approach + const std::string kHomeCacheGstreamerPath = + std::string(getenv("HOME")) + "/.cache/gstreamer-1.0/"; + const std::string kHomeCacheGstreamerRegistry = + std::string(getenv("HOME")) + "/.cache/gstreamer-1.0/registry.x86_64.bin"; + + // Shared libraries. + // TODO: Implement more fine grained approach to only allow + // required gstreamer plugins and its deps. + static const char kDevLibPath[] = "/lib/"; + static const char kDevLib64Path[] = "/lib64/"; + static const char kDevUSRLib32Path[] = "/usr/lib32/"; + static const char kDevUSRLib64Path[] = "/usr/lib64/"; + static const char kDevUSRLocalLib32Path[] = "/usr/local/lib32/"; + static const char kDevUSRLocalLib64Path[] = "/usr/local/lib64/"; + static const char kDevUSRLibPath[] = "/usr/lib/"; + static const char kDevUSRLocalLibPath[] = "/usr/local/lib/"; + + // TODO: check if these are required. + static const char kLDCachePath[] = "/etc/ld.so.cache"; + static const char kDevUSRSharePath[] = "/usr/share/"; + static const char kDevUSRLocalSharePath[] = "/usr/local/share/"; + static const char kVarLibDBUSMachineID[] = "/var/lib/dbus/machine-id"; + + std::vector permissions; + +#if !defined(NDEBUG) + permissions.push_back( + BrokerFilePermission::ReadWriteCreateRecursive(kVarCrashPath)); +#endif + +#if !defined(OFFICIAL_BUILD) + if (kGstRegistry) + permissions.push_back( + BrokerFilePermission::ReadOnly(kGstRegistry)); + + splitAndAddReadOnlyPermission(kGstPluginPath, ":", permissions); + splitAndAddReadOnlyPermission(kLdLibraryPath, ":", permissions); + splitAndAddReadOnlyPermission(kGstPluginScanner, ":", permissions); +#endif + + // Pulse audio + permissions.push_back( + BrokerFilePermission::ReadWriteCreateRecursive(kDevShmPath)); + permissions.push_back( + BrokerFilePermission::ReadOnlyRecursive(kUsrLibPulseModulesPath)); + permissions.push_back(BrokerFilePermission::ReadOnlyRecursive(kEtcPulsePath)); + permissions.push_back(BrokerFilePermission::ReadWriteCreateUnlinkRecursive( + kDevRunUserPulsePath)); + permissions.push_back( + BrokerFilePermission::ReadWriteCreate(kDevRunUserPulse)); + permissions.push_back( + BrokerFilePermission::ReadOnlyRecursive(kHomePulsePath)); + permissions.push_back( + BrokerFilePermission::ReadOnlyRecursive(kHomeConfigPulsePath)); + permissions.push_back(BrokerFilePermission::ReadOnly(kHomeXauth)); + + // GStreamer registry + permissions.push_back(BrokerFilePermission::ReadWriteCreateUnlinkRecursive( + kHomeCacheGstreamerPath)); + permissions.push_back( + BrokerFilePermission::ReadOnly(kHomeCacheGstreamerRegistry)); + + // Shared libraries. + permissions.push_back(BrokerFilePermission::ReadOnlyRecursive(kDevLibPath)); + permissions.push_back(BrokerFilePermission::ReadOnlyRecursive(kDevLib64Path)); + permissions.push_back( + BrokerFilePermission::ReadOnlyRecursive(kDevUSRLib32Path)); + permissions.push_back( + BrokerFilePermission::ReadOnlyRecursive(kDevUSRLib64Path)); + permissions.push_back( + BrokerFilePermission::ReadOnlyRecursive(kDevUSRLocalLib32Path)); + permissions.push_back( + BrokerFilePermission::ReadOnlyRecursive(kDevUSRLocalLib64Path)); + permissions.push_back( + BrokerFilePermission::ReadOnlyRecursive(kDevUSRLibPath)); + permissions.push_back( + BrokerFilePermission::ReadOnlyRecursive(kDevUSRLocalLibPath)); + + // TODO: check if this is required. + permissions.push_back(BrokerFilePermission::ReadOnly(kLDCachePath)); + permissions.push_back( + BrokerFilePermission::ReadOnlyRecursive(kDevUSRSharePath)); + permissions.push_back( + BrokerFilePermission::ReadOnlyRecursive(kDevUSRLocalSharePath)); + permissions.push_back(BrokerFilePermission::ReadOnly(kVarLibDBUSMachineID)); + + // Add eventual extra files from permissions_extra. + for (const auto& perm : permissions_extra) { + permissions.push_back(perm); + } + + broker_process_ = new BrokerProcess(GetFSDeniedErrno(), permissions); + // The initialization callback will perform generic initialization and then + // call broker_sandboxer_callback. + CHECK(broker_process_->Init(base::Bind(&UpdateProcessTypeAndEnableSandbox, + broker_sandboxer_allocator))); +} + +} // namespace content diff --git a/content/common/sandbox_linux/bpf_media_policy_linux.h b/content/common/sandbox_linux/bpf_media_policy_linux.h new file mode 100644 index 0000000..175addc --- /dev/null +++ b/content/common/sandbox_linux/bpf_media_policy_linux.h @@ -0,0 +1,64 @@ +// Copyright 2015 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. + +#ifndef CONTENT_COMMON_SANDBOX_LINUX_BPF_MEDIA_POLICY_LINUX_H_ +#define CONTENT_COMMON_SANDBOX_LINUX_BPF_MEDIA_POLICY_LINUX_H_ + +#include +#include + +#include "base/callback_forward.h" +#include "content/common/sandbox_linux/sandbox_bpf_base_policy_linux.h" + +namespace sandbox { +namespace syscall_broker { +class BrokerFilePermission; +class BrokerProcess; +} +} + +namespace content { + +class MediaProcessPolicy : public SandboxBPFBasePolicy { + public: + MediaProcessPolicy(); + ~MediaProcessPolicy() override; + + sandbox::bpf_dsl::ResultExpr EvaluateSyscall( + int system_call_number) const override; + + bool PreSandboxHook() override; + + protected: + // Start a broker process to handle open() inside the sandbox. + // |broker_sandboxer_allocator| is a function pointer which can allocate a + // suitable sandbox policy for the broker process itself. + // |permissions_extra| is a list of file permissions + // that should be whitelisted by the broker process, in addition to + // the basic ones. + void InitMediaBrokerProcess( + sandbox::bpf_dsl::Policy* (*broker_sandboxer_allocator)(void), + const std::vector& + permissions_extra); + + sandbox::syscall_broker::BrokerProcess* broker_process() { + return broker_process_; + } + + private: + // A BrokerProcess is a helper that is started before the sandbox is engaged + // and will serve requests to access files over an IPC channel. The client of + // this runs from a SIGSYS handler triggered by the seccomp-bpf sandbox. + // This should never be destroyed, as after the sandbox is started it is + // vital to the process. + // This is allocated by InitMediaBrokerProcess, called from PreSandboxHook(), + // which executes iff the sandbox is going to be enabled afterwards. + sandbox::syscall_broker::BrokerProcess* broker_process_; + + DISALLOW_COPY_AND_ASSIGN(MediaProcessPolicy); +}; + +} // namespace content + +#endif // CONTENT_COMMON_SANDBOX_LINUX_BPF_GPU_POLICY_LINUX_H_ diff --git a/content/common/sandbox_linux/sandbox_linux.cc b/content/common/sandbox_linux/sandbox_linux.cc index b2a7b3e..4819360 100644 --- a/content/common/sandbox_linux/sandbox_linux.cc +++ b/content/common/sandbox_linux/sandbox_linux.cc @@ -329,6 +329,7 @@ bool LinuxSandbox::InitializeSandboxImpl() { // The GPU process is allowed to call InitializeSandbox() with threads. bool sandbox_failure_fatal = process_type != switches::kGpuProcess; + // This can be disabled with the '--gpu-sandbox-failures-fatal' flag. // Setting the flag with no value or any value different than 'yes' or 'no' // is equal to setting '--gpu-sandbox-failures-fatal=yes'. diff --git a/content/common/sandbox_linux/sandbox_seccomp_bpf_linux.cc b/content/common/sandbox_linux/sandbox_seccomp_bpf_linux.cc index 79adbee..65075f8 100644 --- a/content/common/sandbox_linux/sandbox_seccomp_bpf_linux.cc +++ b/content/common/sandbox_linux/sandbox_seccomp_bpf_linux.cc @@ -24,6 +24,9 @@ #include "base/posix/eintr_wrapper.h" #include "content/common/sandbox_linux/bpf_cros_arm_gpu_policy_linux.h" #include "content/common/sandbox_linux/bpf_gpu_policy_linux.h" +#if defined(USE_GSTREAMER) +#include "content/common/sandbox_linux/bpf_media_policy_linux.h" +#endif #include "content/common/sandbox_linux/bpf_ppapi_policy_linux.h" #include "content/common/sandbox_linux/bpf_renderer_policy_linux.h" #include "content/common/sandbox_linux/bpf_utility_policy_linux.h" @@ -132,6 +135,9 @@ ResultExpr AllowAllPolicy::EvaluateSyscall(int sysno) const { void RunSandboxSanityChecks(const std::string& process_type) { if (process_type == switches::kRendererProcess || process_type == switches::kGpuProcess || +#if defined(USE_GSTREAMER) + process_type == switches::kMediaProcess || +#endif process_type == switches::kPpapiPluginProcess) { int syscall_ret; errno = 0; @@ -153,6 +159,7 @@ void RunSandboxSanityChecks(const std::string& process_type) { syscall_ret = socket(AF_NETLINK, SOCK_DGRAM, 0); CHECK_EQ(-1, syscall_ret); CHECK_EQ(EPERM, errno); + #endif // !defined(NDEBUG) } } @@ -195,6 +202,10 @@ bool StartBPFSandbox(const base::CommandLine& command_line, policy.reset(new PpapiProcessPolicy); } else if (process_type == switches::kUtilityProcess) { policy.reset(new UtilityProcessPolicy); +#if defined(USE_GSTREAMER) + } else if (process_type == switches::kMediaProcess) { + policy.reset(new MediaProcessPolicy); +#endif } else { NOTREACHED(); policy.reset(new AllowAllPolicy); @@ -243,6 +254,10 @@ bool SandboxSeccompBPF::ShouldEnableSeccompBPF( *base::CommandLine::ForCurrentProcess(); if (process_type == switches::kGpuProcess) return !command_line.HasSwitch(switches::kDisableGpuSandbox); +#if defined(USE_GSTREAMER) + else if (process_type == switches::kMediaProcess) + return !command_line.HasSwitch(switches::kDisableMediaSandbox); +#endif return true; #endif // USE_SECCOMP_BPF diff --git a/content/content.gyp b/content/content.gyp index 41beae7..43fba98 100644 --- a/content/content.gyp +++ b/content/content.gyp @@ -76,6 +76,7 @@ 'dependencies': [ 'content_child', 'content_gpu', + 'content_media', 'content_plugin', 'content_ppapi_plugin', 'content_renderer', @@ -229,6 +230,19 @@ ], }, { + # GN version: //content/media + 'target_name': 'content_media', + 'type': 'static_library', + 'variables': { 'enable_wexit_time_destructors': 1, }, + 'includes': [ + 'content_media.gypi', + ], + 'dependencies': [ + 'content_child', + 'content_common', + ], + }, + { # GN version: //content/plugin and //content/public/plugin 'target_name': 'content_plugin', 'type': 'static_library', @@ -317,6 +331,7 @@ 'content_child.gypi', 'content_common.gypi', 'content_gpu.gypi', + 'content_media.gypi', 'content_plugin.gypi', 'content_ppapi_plugin.gypi', 'content_renderer.gypi', @@ -380,6 +395,12 @@ 'dependencies': ['content'], }, { + # GN version: //content/media + 'target_name': 'content_media', + 'type': 'none', + 'dependencies': ['content'], + }, + { # GN version: //content/plugin 'target_name': 'content_plugin', 'type': 'none', diff --git a/content/content_browser.gypi b/content/content_browser.gypi index d2888f8..31ba808 100644 --- a/content/content_browser.gypi +++ b/content/content_browser.gypi @@ -1809,6 +1809,22 @@ '../third_party/mojo/mojo_public.gyp:mojo_cpp_bindings', ], }], + ['use_gstreamer==1', { + 'sources': [ + 'public/browser/media_data_manager.h', + 'public/browser/media_data_manager_observer.h', + 'browser/media/media_data_manager_impl.cc', + 'browser/media/media_data_manager_impl.h', + 'browser/media/media_data_manager_impl_private.cc', + 'browser/media/media_data_manager_impl_private.h', + 'browser/media/media_process_host.cc', + 'browser/media/media_process_host.h', + 'browser/media/media_process_host_ui_shim.cc', + 'browser/media/media_process_host_ui_shim.h', + 'browser/renderer_host/media_message_filter.cc', + 'browser/renderer_host/media_message_filter.h', + ] + }], ['debug_devtools==1', { 'defines': [ 'DEBUG_DEVTOOLS=1', diff --git a/content/content_common.gypi b/content/content_common.gypi index 57c6a5d..8532d5e 100644 --- a/content/content_common.gypi +++ b/content/content_common.gypi @@ -192,6 +192,7 @@ 'common/cache_storage/cache_storage_types.h', 'common/cc_messages.cc', 'common/cc_messages.h', + 'common/channel_host_thread.h', 'common/child_process_host_impl.cc', 'common/child_process_host_impl.h', 'common/child_process_messages.h', @@ -424,6 +425,17 @@ 'common/media/audio_messages.h', 'common/media/cdm_messages.h', 'common/media/cdm_messages_enums.h', + 'common/media/media_channel.cc', + 'common/media/media_channel.h', + 'common/media/media_channel_filter.cc', + 'common/media/media_channel_filter.h', + 'common/media/media_channel_host.cc', + 'common/media/media_channel_host.h', + 'common/media/media_config.h', + 'common/media/media_messages.h', + 'common/media/media_player_messages_gstreamer.h', + 'common/media/media_player_messages_enums_gstreamer.h', + 'common/media/media_process_launch_causes.h', 'common/media/media_param_traits.cc', 'common/media/media_param_traits.h', 'common/media/media_player_messages_android.h', @@ -492,6 +504,8 @@ 'common/sandbox_linux/bpf_cros_arm_gpu_policy_linux.h', 'common/sandbox_linux/bpf_gpu_policy_linux.cc', 'common/sandbox_linux/bpf_gpu_policy_linux.h', + 'common/sandbox_linux/bpf_media_policy_linux.cc', + 'common/sandbox_linux/bpf_media_policy_linux.h', 'common/sandbox_linux/bpf_ppapi_policy_linux.cc', 'common/sandbox_linux/bpf_ppapi_policy_linux.h', 'common/sandbox_linux/bpf_renderer_policy_linux.cc', @@ -797,6 +811,25 @@ '../third_party/libjingle/libjingle.gyp:libjingle', ], }], + ['use_gstreamer==0', { + 'sources!': [ + 'common/channel_host_thread.h', + 'common/gpu/media/android_video_decode_accelerator.h', + 'common/media/media_channel.cc', + 'common/media/media_channel.h', + 'common/media/media_channel_filter.cc', + 'common/media/media_channel_filter.h', + 'common/media/media_channel_host.cc', + 'common/media/media_channel_host.h', + 'common/media/media_config.h', + 'common/media/media_messages.h', + 'common/media/media_player_messages_gstreamer.h', + 'common/media/media_player_messages_enums_gstreamer.h', + 'common/media/media_process_launch_causes.h', + 'common/sandbox_linux/bpf_media_policy_linux.cc', + 'common/sandbox_linux/bpf_media_policy_linux.h', + ], + }], ['use_v4lplugin==1 and chromeos==1', { 'defines': [ 'USE_LIBV4L2' diff --git a/content/content_media.gypi b/content/content_media.gypi new file mode 100644 index 0000000..043ee7f --- /dev/null +++ b/content/content_media.gypi @@ -0,0 +1,44 @@ +# Copyright (c) 2015 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. + +{ + 'conditions': [ + ['use_gstreamer==1', { + 'dependencies': [ + '../base/base.gyp:base', + '../build/linux/system.gyp:gstreamer', + ], + 'sources': [ + 'media/in_process_media_thread.cc', + 'media/in_process_media_thread.h', + 'media/media_child_thread.cc', + 'media/media_child_thread.h', + 'media/media_main.cc', + 'media/media_process.cc', + 'media/media_process.h', + 'media/gstreamer/gpuprocess/client_egl.h', + 'media/gstreamer/gpuprocess/client_egl.cc', + 'media/gstreamer/gpuprocess/gstglcontext_gpu_process.c', + 'media/gstreamer/gpuprocess/gstglcontext_gpu_process.h', + 'media/gstreamer/gpuprocess/gstgldisplay_gpu_process.c', + 'media/gstreamer/gpuprocess/gstgldisplay_gpu_process.h', + 'media/gstreamer/gpuprocess/gstglwindow_gpu_process.c', + 'media/gstreamer/gpuprocess/gstglwindow_gpu_process.h', + 'media/gstreamer/gst_chromium_http_source.cc', + 'media/gstreamer/gst_chromium_http_source.h', + 'media/gstreamer/gst_chromium_media_src.cc', + 'media/gstreamer/gst_chromium_media_src.h', + 'media/gstreamer/gst_chromium_aes_ctr.cc', + 'media/gstreamer/gst_chromium_aes_ctr.h', + 'media/gstreamer/gst_chromium_common_encryption_decryptor.cc', + 'media/gstreamer/gst_chromium_common_encryption_decryptor.h', + 'media/gstreamer/media_player_gstreamer.cc', + 'media/gstreamer/media_player_gstreamer.h', + ], + 'include_dirs': [ + '..', + ], + }], + ], +} diff --git a/content/content_renderer.gypi b/content/content_renderer.gypi index 4cfd238..253885b 100644 --- a/content/content_renderer.gypi +++ b/content/content_renderer.gypi @@ -269,6 +269,12 @@ 'renderer/media/crypto/ppapi_decryptor.h', 'renderer/media/crypto/render_cdm_factory.cc', 'renderer/media/crypto/render_cdm_factory.h', + 'renderer/media/gstreamer/webmediaplayer_gstreamer.cc', + 'renderer/media/gstreamer/webmediaplayer_gstreamer.h', + 'renderer/media/gstreamer/webmediasource_gstreamer.cc', + 'renderer/media/gstreamer/webmediasource_gstreamer.h', + 'renderer/media/gstreamer/websourcebuffer_gstreamer.cc', + 'renderer/media/gstreamer/websourcebuffer_gstreamer.h', 'renderer/media/media_permission_dispatcher.cc', 'renderer/media/media_permission_dispatcher.h', 'renderer/media/media_stream_audio_level_calculator.cc', @@ -731,6 +737,43 @@ '../base/allocator/allocator.gyp:allocator', ], }], + ['use_gstreamer==1', { + 'sources': [ + '../media/blink/cdm_result_promise.h', + '../media/blink/cdm_result_promise_helper.cc', + '../media/blink/cdm_result_promise_helper.h', + '../media/blink/cdm_session_adapter.cc', + '../media/blink/cdm_session_adapter.h', + '../media/blink/webcontentdecryptionmodule_impl.cc', + '../media/blink/webcontentdecryptionmodule_impl.h', + '../media/blink/webcontentdecryptionmoduleaccess_impl.cc', + '../media/blink/webcontentdecryptionmoduleaccess_impl.h', + '../media/blink/webcontentdecryptionmodulesession_impl.cc', + '../media/blink/webcontentdecryptionmodulesession_impl.h', + '../media/blink/webencryptedmediaclient_impl.cc', + '../media/blink/webencryptedmediaclient_impl.h', + '../media/blink/encrypted_media_player_support.cc', + '../media/blink/encrypted_media_player_support.h', + '../media/blink/new_session_cdm_result_promise.cc', + '../media/blink/new_session_cdm_result_promise.h', + '../media/blink/texttrack_impl.cc', + '../media/blink/texttrack_impl.h', + '../media/blink/webinbandtexttrack_impl.cc', + '../media/blink/webinbandtexttrack_impl.h', + ], + 'defines': [ + 'MEDIA_IMPLEMENTATION', + ], + }, { + 'sources!': [ + 'renderer/media/gstreamer/webmediaplayer_gstreamer.cc', + 'renderer/media/gstreamer/webmediaplayer_gstreamer.h', + 'renderer/media/gstreamer/webmediasource_gstreamer.cc', + 'renderer/media/gstreamer/webmediasource_gstreamer.h', + 'renderer/media/gstreamer/websourcebuffer_gstreamer.cc', + 'renderer/media/gstreamer/websourcebuffer_gstreamer.h', + ], + }], ['OS=="android"', { 'sources!': [ 'renderer/media/audio_decoder.cc', diff --git a/content/media/gstreamer/gpuprocess/client_egl.cc b/content/media/gstreamer/gpuprocess/client_egl.cc new file mode 100644 index 0000000..39ef44b --- /dev/null +++ b/content/media/gstreamer/gpuprocess/client_egl.cc @@ -0,0 +1,162 @@ +// Copyright 2015 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 "content/media/gstreamer/gpuprocess/client_egl.h" + +#include + +#include "base/lazy_instance.h" +#include "content/common/gpu/client/command_buffer_proxy_impl.h" +#include "gpu/command_buffer/client/gpu_control.h" +#include "gpu/command_buffer/client/gles2_implementation.h" +#include "gpu/command_buffer/client/gles2_interface.h" +#include "gpu/command_buffer/client/gles2_lib.h" + +#include +#include + +namespace content { + +static base::LazyInstance + g_thread_safe_cmd_impl = LAZY_INSTANCE_INITIALIZER; + +bool ClientEGL_SetupCommandBufferProxy() +{ + gpu::gles2::GLES2Interface* gl_interface = ::gles2::GetGLContext(); + + if (!gl_interface) { + NOTREACHED() << "clientEglSetCommandBufferProxy, no gl interface"; + return false; + } + + gpu::gles2::GLES2Implementation* gl_impl = + reinterpret_cast(gl_interface); + + if (!gl_impl) { + NOTREACHED() << "clientEglSetCommandBufferProxy, no gles impl"; + return false; + } + + gpu::GpuControl* gpu_ctrl = gl_impl->gpu_control(); + + if (!gpu_ctrl) { + NOTREACHED() << "ClientEGL_SetupCommandBufferProxy, no gpu control"; + return false; + } + + if (gpu_ctrl->IsGpuChannelLost()) { + NOTREACHED() << "ClientEGL_SetupCommandBufferProxy, gpu channel is lost"; + return false; + } + + content::CommandBufferProxyImpl* cmd_impl = + static_cast(gpu_ctrl); + + if (!cmd_impl) { + NOTREACHED() << "ClientEGL_SetupCommandBufferProxy, no cmd buffer proxy"; + return false; + } + + g_thread_safe_cmd_impl.Get() = cmd_impl; + + return true; +} + +EGLImageKHR CreateEGLImageKHR(EGLDisplay dpy, + EGLContext ctx, + EGLenum target, + EGLClientBuffer buffer, + const EGLint* attrib_list) { + if (!g_thread_safe_cmd_impl.Get()) { + NOTREACHED() << "eglCreateImageKHR, no cmd buffer proxy"; + return EGL_NO_IMAGE_KHR; + } + + if (ctx != EGL_NO_CONTEXT) { + NOTREACHED() << "eglCreateImageKHR, ctx != EGL_NO_CONTEXT"; + return EGL_NO_IMAGE_KHR; + } + + if (target != EGL_LINUX_DMA_BUF_EXT) { + NOTREACHED() << "eglCreateImageKHR, target != EGL_LINUX_DMA_BUF_EXT"; + return EGL_NO_IMAGE_KHR; + } + + if (buffer != static_cast(nullptr)) { + NOTREACHED() << "eglCreateImageKHR, Client buffer must be null"; + return EGL_NO_IMAGE_KHR; + } + + if (!attrib_list) { + NOTREACHED() << "eglCreateImageKHR, attrib_list is null"; + return EGL_NO_IMAGE_KHR; + } + + // Retrieve width, height and number of attributes. + GLsizei width = 0; + GLsizei height = 0; + size_t nb_attribs = 0; + std::vector dmabuf_fds; + + for (size_t i = 0; i < 20; ++i) { + if (attrib_list[i] == EGL_WIDTH) + width = attrib_list[i + 1]; + else if (attrib_list[i] == EGL_HEIGHT) + height = attrib_list[i + 1]; + else if (attrib_list[i] == EGL_DMA_BUF_PLANE0_FD_EXT || + attrib_list[i] == EGL_DMA_BUF_PLANE1_FD_EXT || + attrib_list[i] == EGL_DMA_BUF_PLANE2_FD_EXT) + dmabuf_fds.push_back(attrib_list[i + 1]); + else if (attrib_list[i] == EGL_NONE) { + nb_attribs = i + 1; + break; + } + } + + if (!nb_attribs) { + NOTREACHED() << "eglCreateImageKHR, no attribs"; + return EGL_NO_IMAGE_KHR; + } + + if (width <= 0) { + NOTREACHED() << "eglCreateImageKHR, width <= 0"; + return EGL_NO_IMAGE_KHR; + } + + if (height <= 0) { + NOTREACHED() << "eglCreateImageKHR, height <= 0"; + return EGL_NO_IMAGE_KHR; + } + + int32_t image_id = g_thread_safe_cmd_impl.Get()->CreateEGLImage( + width, height, + std::vector(attrib_list, attrib_list + nb_attribs), dmabuf_fds); + + if (image_id < 0) { + NOTREACHED() << "eglCreateImageKHR, image_id < 0"; + return EGL_NO_IMAGE_KHR; + } + + return reinterpret_cast(image_id); +} + +EGLBoolean DestroyEGLImageKHR(EGLDisplay dpy, EGLImageKHR img) { + gpu::gles2::GLES2Interface* gl = ::gles2::GetGLContext(); + + if (!gl) { + NOTREACHED() << "eglDestroyEGLImageKHR, no gl interface"; + return EGL_FALSE; + } + + if (img == EGL_NO_IMAGE_KHR) { + NOTREACHED() << "eglDestroyEGLImageKHR, invalid egl image"; + return EGL_FALSE; + } + + gl->DestroyImageCHROMIUM(reinterpret_cast(img)); + + return EGL_TRUE; +} + +} // namespace content diff --git a/content/media/gstreamer/gpuprocess/client_egl.h b/content/media/gstreamer/gpuprocess/client_egl.h new file mode 100644 index 0000000..4190cf5 --- /dev/null +++ b/content/media/gstreamer/gpuprocess/client_egl.h @@ -0,0 +1,30 @@ +// Copyright 2015 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. + +#ifndef CONTENT_MEDIA_GSTREAMER_GPUPROCESS_CLIENT_EGL_H_ +#define CONTENT_MEDIA_GSTREAMER_GPUPROCESS_CLIENT_EGL_H_ + +namespace content { + +typedef unsigned int EGLBoolean; +typedef unsigned int EGLenum; +typedef void* EGLDisplay; +typedef void* EGLContext; +typedef void* EGLClientBuffer; +typedef int EGLint; +typedef void* EGLImageKHR; + +bool ClientEGL_SetupCommandBufferProxy(); + +EGLImageKHR CreateEGLImageKHR(EGLDisplay dpy, + EGLContext ctx, + EGLenum target, + EGLClientBuffer buffer, + const EGLint* attrib_list); + +EGLBoolean DestroyEGLImageKHR(EGLDisplay dpy, EGLImageKHR img); + +} // namespace content + +#endif // CONTENT_MEDIA_GSTREAMER_GPUPROCESS_CLIENT_EGL_H_ diff --git a/content/media/gstreamer/gpuprocess/gstglcontext_gpu_process.c b/content/media/gstreamer/gpuprocess/gstglcontext_gpu_process.c new file mode 100644 index 0000000..545dad3 --- /dev/null +++ b/content/media/gstreamer/gpuprocess/gstglcontext_gpu_process.c @@ -0,0 +1,147 @@ +// Copyright (c) 2015 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. + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "content/media/gstreamer/gpuprocess/gstglcontext_gpu_process.h" +#include "content/media/gstreamer/gpuprocess/gstglwindow_gpu_process.h" + +#define GST_GL_CONTEXT_GPU_PROCESS_GET_PRIVATE(o) \ + (G_TYPE_INSTANCE_GET_PRIVATE((o), GST_GL_TYPE_CONTEXT_GPU_PROCESS, GstGLContextGPUProcessPrivate)) + +#define GST_CAT_DEFAULT gst_gl_context_debug + +#define gst_gl_context_gpu_process_parent_class parent_class +G_DEFINE_TYPE (GstGLContextGPUProcess, gst_gl_context_gpu_process, + GST_GL_TYPE_CONTEXT_EGL); + +struct _GstGLContextGPUProcessPrivate +{ + // Nothing for now; + gint empty; +}; + +static guintptr +gst_gl_context_gpu_process_get_gl_context (GstGLContext * context) +{ + return 0; +} + +static GstGLAPI +gst_gl_context_gpu_process_get_gl_api (GstGLContext * context) +{ + return GST_GL_CONTEXT_EGL (context)->gl_api; +} + +static GstGLPlatform +gst_gl_context_gpu_process_get_gl_platform (GstGLContext * context) +{ + return 0; +} + +static gboolean +gst_gl_context_gpu_process_activate (GstGLContext * context, gboolean activate) +{ + return TRUE; +} + +static void +gst_gl_context_gpu_process_finalize (GObject * object) +{ + GstGLContext *context = GST_GL_CONTEXT (object); + + if (context->window) + GST_GL_WINDOW_GET_CLASS (context->window)->close (context->window); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gst_gl_context_gpu_process_class_init (GstGLContextGPUProcessClass * klass) +{ + GObjectClass *obj_class = G_OBJECT_CLASS (klass); + GstGLContextClass *context_class = (GstGLContextClass *) klass; + + g_type_class_add_private (klass, sizeof (GstGLContextGPUProcessPrivate)); + + obj_class->finalize = gst_gl_context_gpu_process_finalize; + + context_class->get_gl_context = + GST_DEBUG_FUNCPTR (gst_gl_context_gpu_process_get_gl_context); + context_class->get_gl_api = + GST_DEBUG_FUNCPTR (gst_gl_context_gpu_process_get_gl_api); + context_class->get_gl_platform = + GST_DEBUG_FUNCPTR (gst_gl_context_gpu_process_get_gl_platform); + context_class->activate = + GST_DEBUG_FUNCPTR (gst_gl_context_gpu_process_activate); +} + +static void +gst_gl_context_gpu_process_init (GstGLContextGPUProcess * context) +{ + context->priv = GST_GL_CONTEXT_GPU_PROCESS_GET_PRIVATE (context); +} + +GstGLContext * +gst_gl_context_gpu_process_new (GstGLDisplay * display, + GstGLAPI gl_api, GstGLProcAddrFunc proc_addr) +{ + GstGLContext *context = NULL; + GstGLContextEGL *egl_context = NULL; + GstGLContextGPUProcess *gpu_context = NULL; + GstGLContextClass *context_class = NULL; + GstGLWindow *window = NULL; + GError *error = NULL; + g_return_val_if_fail ((gst_gl_display_get_gl_api (display) & gl_api) != + GST_GL_API_NONE, NULL); + + gpu_context = g_object_new (GST_GL_TYPE_CONTEXT_GPU_PROCESS, NULL); + + egl_context = GST_GL_CONTEXT_EGL (gpu_context); + egl_context->gl_api = gl_api; + + context = GST_GL_CONTEXT (gpu_context); + + context->display = display; + gst_gl_display_add_context (display, context); + + context_class = GST_GL_CONTEXT_GET_CLASS (context); + + context_class->get_current_context = NULL; + context_class->get_proc_address = GST_DEBUG_FUNCPTR (proc_addr); + + gst_gl_context_activate (context, TRUE); + gst_gl_context_fill_info (context, &error); + + if (error) { + GST_ERROR_OBJECT (context, "Failed to create gpu process context: %s", + error->message); + g_error_free (error); + gst_object_unref (context); + return NULL; + } + + context->gl_vtable->EGLImageTargetTexture2D = + gst_gl_context_get_proc_address (context, "glEGLImageTargetTexture2D"); + + if (!context->gl_vtable->EGLImageTargetTexture2D) { + GST_ERROR_OBJECT (context, "Cannot find glEGLImageTargetTexture2D"); + gst_object_unref (context); + return NULL; + } + + egl_context->eglCreateImage = gst_gl_context_get_proc_address (context, + "eglCreateImage"); + egl_context->eglDestroyImage = gst_gl_context_get_proc_address (context, + "eglDestroyImage"); + + window = GST_GL_WINDOW (gst_gl_window_gpu_process_new (display)); + gst_gl_context_set_window (context, window); + GST_GL_WINDOW_GET_CLASS (window)->open (window, NULL); + gst_object_unref (window); + + return context; +} diff --git a/content/media/gstreamer/gpuprocess/gstglcontext_gpu_process.h b/content/media/gstreamer/gpuprocess/gstglcontext_gpu_process.h new file mode 100644 index 0000000..8c89322 --- /dev/null +++ b/content/media/gstreamer/gpuprocess/gstglcontext_gpu_process.h @@ -0,0 +1,74 @@ +// Copyright (c) 2015 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. + +#ifndef __GST_GL_CONTEXT_GPU_PROCESS_H__ +#define __GST_GL_CONTEXT_GPU_PROCESS_H__ + +#include +#include + +#if !GST_GL_HAVE_GLES2 +#error "GstGL with GLES2 support is required" +#endif + +#undef GST_GL_HAVE_OPENGL +#define GST_GL_HAVE_OPENGL 0 + +#include +#include + +G_BEGIN_DECLS + +#define GST_GL_TYPE_CONTEXT_GPU_PROCESS (gst_gl_context_gpu_process_get_type()) +#define GST_GL_CONTEXT_GPU_PROCESS(o) \ + (G_TYPE_CHECK_INSTANCE_CAST((o), GST_GL_TYPE_CONTEXT_GPU_PROCESS, \ + GstGLContextGPUProcess)) +#define GST_GL_CONTEXT_GPU_PROCESS_CLASS(k) \ + (G_TYPE_CHECK_CLASS_CAST((k), GST_GL_TYPE_CONTEXT_GPU_PROCESS, \ + GstGLContextGPUProcessClass)) +#define GST_GL_IS_CONTEXT_GPU_PROCESS(o) \ + (G_TYPE_CHECK_INSTANCE_TYPE((o), GST_GL_TYPE_CONTEXT_GPU_PROCESS)) +#define GST_GL_IS_CONTEXT_GPU_PROCESS_CLASS(k) \ + (G_TYPE_CHECK_CLASS_TYPE((k), GST_GL_TYPE_CONTEXT_GPU_PROCESS)) +#define GST_GL_CONTEXT_GPU_PROCESS_GET_CLASS(o) \ + (G_TYPE_INSTANCE_GET_CLASS((o), GST_GL_TYPE_CONTEXT_GPU_PROCESS, \ + GstGLContextGPUProcessClass)) + +typedef struct _GstGLContextGPUProcess GstGLContextGPUProcess; +typedef struct _GstGLContextGPUProcessPrivate GstGLContextGPUProcessPrivate; +typedef struct _GstGLContextGPUProcessClass GstGLContextGPUProcessClass; + +typedef gpointer (*GstGLProcAddrFunc)(GstGLAPI gl_api, const gchar* name); + +/** + * GstGLContextGPUProcess: + * + * Opaque #GstGLContextGPUProcess object + */ +struct _GstGLContextGPUProcess { + GstGLContextEGL parent; + + /*< private >*/ + GstGLContextGPUProcessPrivate* priv; + + /*< private >*/ + gpointer _reserved[GST_PADDING]; +}; + +struct _GstGLContextGPUProcessClass { + GstGLContextEGLClass parent; + + /*< private >*/ + gpointer _reserved[GST_PADDING]; +}; + +GType gst_gl_context_gpu_process_get_type(void); + +GstGLContext* gst_gl_context_gpu_process_new(GstGLDisplay* display, + GstGLAPI gl_api, + GstGLProcAddrFunc proc_addr); + +G_END_DECLS + +#endif /* __GST_GL_CONTEXT_GPU_PROCESS_H__ */ diff --git a/content/media/gstreamer/gpuprocess/gstgldisplay_gpu_process.c b/content/media/gstreamer/gpuprocess/gstgldisplay_gpu_process.c new file mode 100644 index 0000000..c1e180d --- /dev/null +++ b/content/media/gstreamer/gpuprocess/gstgldisplay_gpu_process.c @@ -0,0 +1,44 @@ +// Copyright (c) 2015 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. + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "content/media/gstreamer/gpuprocess/gstgldisplay_gpu_process.h" + +GST_DEBUG_CATEGORY_STATIC (gst_gl_display_debug); +#define GST_CAT_DEFAULT gst_gl_display_debug + +G_DEFINE_TYPE (GstGLDisplayGPUProcess, gst_gl_display_gpu_process, GST_TYPE_GL_DISPLAY); + +static void gst_gl_display_gpu_process_finalize (GObject * object); + +static void +gst_gl_display_gpu_process_class_init (GstGLDisplayGPUProcessClass * klass) +{ + G_OBJECT_CLASS (klass)->finalize = gst_gl_display_gpu_process_finalize; +} + +static void +gst_gl_display_gpu_process_init (GstGLDisplayGPUProcess * display_gpu_process) +{ + GstGLDisplay *display = (GstGLDisplay *) display_gpu_process; + + display->type = GST_GL_DISPLAY_TYPE_EGL; +} + +static void +gst_gl_display_gpu_process_finalize (GObject * object) +{ + G_OBJECT_CLASS (gst_gl_display_gpu_process_parent_class)->finalize (object); +} + +GstGLDisplayGPUProcess * +gst_gl_display_gpu_process_new (void) +{ + GST_DEBUG_CATEGORY_GET (gst_gl_display_debug, "gldisplay"); + + return g_object_new (GST_TYPE_GL_DISPLAY_GPU_PROCESS, NULL); +} diff --git a/content/media/gstreamer/gpuprocess/gstgldisplay_gpu_process.h b/content/media/gstreamer/gpuprocess/gstgldisplay_gpu_process.h new file mode 100644 index 0000000..8b0a239 --- /dev/null +++ b/content/media/gstreamer/gpuprocess/gstgldisplay_gpu_process.h @@ -0,0 +1,52 @@ +// Copyright (c) 2015 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. + +#ifndef __GST_GL_DISPLAY_GPU_PROCESS_H__ +#define __GST_GL_DISPLAY_GPU_PROCESS_H__ + +#include +#include + +#if !GST_GL_HAVE_GLES2 +#error "GstGL with GLES2 support is required" +#endif + +#undef GST_GL_HAVE_OPENGL +#define GST_GL_HAVE_OPENGL 0 + +#include + +G_BEGIN_DECLS + +GType gst_gl_display_gpu_process_get_type(void); + +#define GST_TYPE_GL_DISPLAY_GPU_PROCESS (gst_gl_display_gpu_process_get_type()) +#define GST_GL_DISPLAY_GPU_PROCESS(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_GL_DISPLAY_GPU_PROCESS, \ + GstGLDisplayGPUProcess)) +#define GST_GL_DISPLAY_GPU_PROCESS_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_GL_DISPLAY_GPU_PROCESS, \ + GstGLDisplayGPUProcessClass)) +#define GST_IS_GL_DISPLAY_GPU_PROCESS(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_GL_DISPLAY_GPU_PROCESS)) +#define GST_IS_GL_DISPLAY_GPU_PROCESS_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_GL_DISPLAY_GPU_PROCESS)) +#define GST_GL_DISPLAY_GPU_PROCESS_CAST(obj) ((GstGLDisplayGPUProcess*)(obj)) + +typedef struct _GstGLDisplayGPUProcess GstGLDisplayGPUProcess; +typedef struct _GstGLDisplayGPUProcessClass GstGLDisplayGPUProcessClass; + +struct _GstGLDisplayGPUProcess { + GstGLDisplay parent; +}; + +struct _GstGLDisplayGPUProcessClass { + GstGLDisplayClass object_class; +}; + +GstGLDisplayGPUProcess* gst_gl_display_gpu_process_new(void); + +G_END_DECLS + +#endif /* __GST_GL_DISPLAY_GPU_PROCESS_H__ */ diff --git a/content/media/gstreamer/gpuprocess/gstglwindow_gpu_process.c b/content/media/gstreamer/gpuprocess/gstglwindow_gpu_process.c new file mode 100644 index 0000000..39f273d --- /dev/null +++ b/content/media/gstreamer/gpuprocess/gstglwindow_gpu_process.c @@ -0,0 +1,48 @@ +// Copyright (c) 2015 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. + +#define GLIB_DISABLE_DEPRECATION_WARNINGS + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "content/media/gstreamer/gpuprocess/gstglwindow_gpu_process.h" + +#define GST_GL_WINDOW_GPU_PROCESS_GET_PRIVATE(o) \ + (G_TYPE_INSTANCE_GET_PRIVATE((o), GST_GL_TYPE_WINDOW_GPU_PROCESS, GstGLWindowGPUProcessPrivate)) + +#define GST_CAT_DEFAULT gst_gl_window_debug + +#define gst_gl_window_gpu_process_parent_class parent_class +G_DEFINE_TYPE (GstGLWindowGPUProcess, gst_gl_window_gpu_process, + GST_GL_TYPE_WINDOW); + +struct _GstGLWindowGPUProcessPrivate +{ + int empty; +}; + +static void +gst_gl_window_gpu_process_class_init (GstGLWindowGPUProcessClass * klass) +{ + g_type_class_add_private (klass, sizeof (GstGLWindowGPUProcessPrivate)); +} + +static void +gst_gl_window_gpu_process_init (GstGLWindowGPUProcess * window) +{ + window->priv = GST_GL_WINDOW_GPU_PROCESS_GET_PRIVATE (window); +} + +GstGLWindowGPUProcess * +gst_gl_window_gpu_process_new (GstGLDisplay * display) +{ + GstGLWindowGPUProcess *window = + g_object_new (GST_GL_TYPE_WINDOW_GPU_PROCESS, NULL); + + GST_GL_WINDOW (window)->display = gst_object_ref (display); + + return window; +} diff --git a/content/media/gstreamer/gpuprocess/gstglwindow_gpu_process.h b/content/media/gstreamer/gpuprocess/gstglwindow_gpu_process.h new file mode 100644 index 0000000..f571245 --- /dev/null +++ b/content/media/gstreamer/gpuprocess/gstglwindow_gpu_process.h @@ -0,0 +1,75 @@ +// Copyright (c) 2015 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. + +#ifndef __GST_GL_WINDOW_GPU_PROCESS_H__ +#define __GST_GL_WINDOW_GPU_PROCESS_H__ + +#include +#include + +#if !GST_GL_HAVE_GLES2 +#error "GstGL with GLES2 support is required" +#endif + +#undef GST_GL_HAVE_OPENGL +#define GST_GL_HAVE_OPENGL 0 + +#include +#include "gstglcontext_gpu_process.h" + +G_BEGIN_DECLS + +#define GST_GL_TYPE_WINDOW_GPU_PROCESS (gst_gl_window_gpu_process_get_type()) +#define GST_GL_WINDOW_GPU_PROCESS(o) \ + (G_TYPE_CHECK_INSTANCE_CAST((o), GST_GL_TYPE_WINDOW_GPU_PROCESS, \ + GstGLWindowGPUProcess)) +#define GST_GL_WINDOW_GPU_PROCESS_CLASS(k) \ + (G_TYPE_CHECK_CLASS_CAST((k), GST_GL_TYPE_WINDOW_GPU_PROCESS, \ + GstGLWindowGPUProcessClass)) +#define GST_GL_IS_WINDOW_GPU_PROCESS(o) \ + (G_TYPE_CHECK_INSTANCE_TYPE((o), GST_GL_TYPE_WINDOW_GPU_PROCESS)) +#define GST_GL_IS_WINDOW_GPU_PROCESS_CLASS(k) \ + (G_TYPE_CHECK_CLASS_TYPE((k), GST_GL_TYPE_WINDOW_GPU_PROCESS)) +#define GST_GL_WINDOW_GPU_PROCESS_GET_CLASS(o) \ + (G_TYPE_INSTANCE_GET_CLASS((o), GST_GL_TYPE_WINDOW_GPU_PROCESS, \ + GstGLWindowGPUProcessClass)) + +typedef struct _GstGLWindowGPUProcess GstGLWindowGPUProcess; +typedef struct _GstGLWindowGPUProcessPrivate GstGLWindowGPUProcessPrivate; +typedef struct _GstGLWindowGPUProcessClass GstGLWindowGPUProcessClass; + +/** + * GstGLWindowGPUProcess: + * + * Opaque #GstGLWindowGPUProcess object + */ +struct _GstGLWindowGPUProcess { + GstGLWindow parent; + + /*< private >*/ + GstGLWindowGPUProcessPrivate* priv; + + /*< private >*/ + gpointer _reserved[GST_PADDING]; +}; + +/** + * GstGLWindowGPUProcessClass: + * + * Opaque #GstGLWindowGPUProcessClass object + */ +struct _GstGLWindowGPUProcessClass { + GstGLWindowClass parent_class; + + /*< private >*/ + gpointer _reserved[GST_PADDING]; +}; + +GType gst_gl_window_gpu_process_get_type(void); + +GstGLWindowGPUProcess* gst_gl_window_gpu_process_new(GstGLDisplay* display); + +G_END_DECLS + +#endif /* __GST_GL_WINDOW_GPU_PROCESS_H__ */ diff --git a/content/media/gstreamer/gst_chromium_aes_ctr.cc b/content/media/gstreamer/gst_chromium_aes_ctr.cc new file mode 100644 index 0000000..18b2dc9 --- /dev/null +++ b/content/media/gstreamer/gst_chromium_aes_ctr.cc @@ -0,0 +1,77 @@ +// Copyright (c) 2015 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 "content/media/gstreamer/gst_chromium_aes_ctr.h" + +#include +#include +#include + +struct _AesCtrState { + volatile gint refcount; + AES_KEY key; + unsigned char ivec[16]; + unsigned int num; + unsigned char ecount[16]; +}; + +AesCtrState* chromium_aes_ctr_decrypt_new(GBytes* key, GBytes* iv) { + unsigned char* buf; + gsize iv_length; + AesCtrState* state; + + g_return_val_if_fail(key != NULL, NULL); + g_return_val_if_fail(iv != NULL, NULL); + + state = g_slice_new(AesCtrState); + if (!state) { + GST_ERROR("Failed to allocate AesCtrState"); + return NULL; + } + + g_assert(g_bytes_get_size(key) == 16); + AES_set_encrypt_key((const unsigned char*)g_bytes_get_data(key, NULL), + 8 * g_bytes_get_size(key), &state->key); + + buf = (unsigned char*)g_bytes_get_data(iv, &iv_length); + state->num = 0; + memset(state->ecount, 0, 16); + if (iv_length == 8) { + memset(state->ivec + 8, 0, 8); + memcpy(state->ivec, buf, 8); + } else { + memcpy(state->ivec, buf, 16); + } + + return state; +} + +AesCtrState* chromium_aes_ctr_decrypt_ref(AesCtrState* state) { + g_return_val_if_fail(state != NULL, NULL); + + g_atomic_int_inc(&state->refcount); + + return state; +} + +void chromium_aes_ctr_decrypt_unref(AesCtrState* state) { + g_return_if_fail(state != NULL); + + if (g_atomic_int_dec_and_test(&state->refcount)) { + g_slice_free(AesCtrState, state); + } +} + +gboolean chromium_aes_ctr_decrypt_ip(AesCtrState* state, + unsigned char* data, + int length) { + AES_ctr128_encrypt(data, data, length, &state->key, state->ivec, + state->ecount, &state->num); + return TRUE; +} + +G_DEFINE_BOXED_TYPE(AesCtrState, + chromium_aes_ctr, + (GBoxedCopyFunc)chromium_aes_ctr_decrypt_ref, + (GBoxedFreeFunc)chromium_aes_ctr_decrypt_unref); diff --git a/content/media/gstreamer/gst_chromium_aes_ctr.h b/content/media/gstreamer/gst_chromium_aes_ctr.h new file mode 100644 index 0000000..3dc78f4 --- /dev/null +++ b/content/media/gstreamer/gst_chromium_aes_ctr.h @@ -0,0 +1,24 @@ +// Copyright (c) 2015 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. + +#ifndef CONTENT_MEDIA_GSTREAMER_AES_H_ +#define CONTENT_MEDIA_GSTREAMER_AES_H_ + +#include + +G_BEGIN_DECLS + +typedef struct _AesCtrState AesCtrState; + +AesCtrState* chromium_aes_ctr_decrypt_new(GBytes* key, GBytes* iv); +AesCtrState* chromium_aes_ctr_decrypt_ref(AesCtrState* state); +void chromium_aes_ctr_decrypt_unref(AesCtrState* state); + +gboolean chromium_aes_ctr_decrypt_ip(AesCtrState* state, + unsigned char* data, + int length); + +G_END_DECLS + +#endif // CONTENT_MEDIA_GSTREAMER_AES_H_ diff --git a/content/media/gstreamer/gst_chromium_common_encryption_decryptor.cc b/content/media/gstreamer/gst_chromium_common_encryption_decryptor.cc new file mode 100644 index 0000000..52f89b2 --- /dev/null +++ b/content/media/gstreamer/gst_chromium_common_encryption_decryptor.cc @@ -0,0 +1,469 @@ +// Copyright (c) 2015 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 +#include "content/media/gstreamer/gst_chromium_aes_ctr.h" +#include "content/media/gstreamer/gst_chromium_common_encryption_decryptor.h" + +struct _ChromiumCommonEncryptionDecrypt { + GstBaseTransform parent; + GBytes* key; + GstBuffer* initDataBuffer; + + gboolean keyReceived; + GMutex mutex; + GCond condition; +}; + +struct _ChromiumCommonEncryptionDecryptClass { + GstBaseTransformClass parentClass; +}; + +static void chromium_common_encryption_decrypt_dispose(GObject* object); +static GstCaps* chromiumCommonEncryptionDecryptTransformCaps(GstBaseTransform*, + GstPadDirection, + GstCaps*, + GstCaps* filter); +static GstFlowReturn chromiumCommonEncryptionDecryptTransformInPlace( + GstBaseTransform*, + GstBuffer*); +static gboolean chromiumCommonEncryptionDecryptSinkEventHandler( + GstBaseTransform*, + GstEvent*); + +GST_DEBUG_CATEGORY_STATIC(chromium_common_encryption_decrypt_debug_category); +#define GST_CAT_DEFAULT chromium_common_encryption_decrypt_debug_category + +#define CLEAR_KEY_PROTECTION_SYSTEM_ID "58147ec8-0423-4659-92e6-f52c5ce8c3cc" + +static GstStaticPadTemplate sinkTemplate = GST_STATIC_PAD_TEMPLATE( + "sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS( + "application/x-cenc, original-media-type=(string)video/x-h264, " + "protection-system=(string)" CLEAR_KEY_PROTECTION_SYSTEM_ID "; " + "application/x-cenc, original-media-type=(string)audio/mpeg, " + "protection-system=(string)" CLEAR_KEY_PROTECTION_SYSTEM_ID)); + +static GstStaticPadTemplate srcTemplate = + GST_STATIC_PAD_TEMPLATE("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS("video/x-h264; audio/mpeg")); + +#define chromium_common_encryption_decrypt_parent_class parent_class +G_DEFINE_TYPE(ChromiumCommonEncryptionDecrypt, + chromium_common_encryption_decrypt, + GST_TYPE_BASE_TRANSFORM); + +static void chromium_common_encryption_decrypt_class_init( + ChromiumCommonEncryptionDecryptClass* klass) { + GstBaseTransformClass* baseTransformClass = GST_BASE_TRANSFORM_CLASS(klass); + GstElementClass* elementClass = GST_ELEMENT_CLASS(klass); + GObjectClass* gobjectClass = G_OBJECT_CLASS(klass); + + gobjectClass->dispose = chromium_common_encryption_decrypt_dispose; + + gst_element_class_add_pad_template( + elementClass, gst_static_pad_template_get(&sinkTemplate)); + gst_element_class_add_pad_template(elementClass, + gst_static_pad_template_get(&srcTemplate)); + + gst_element_class_set_static_metadata( + elementClass, + "Decrypt content encrypted using ISOBMFF ClearKey Common Encryption", + GST_ELEMENT_FACTORY_KLASS_DECRYPTOR, + "Decrypts media that has been encrypted using ISOBMFF ClearKey Common " + "Encryption.", + "Anton Obzhirov , " + "Alex Ashley " + "and Philippe Normand "); + + GST_DEBUG_CATEGORY_INIT(chromium_common_encryption_decrypt_debug_category, + "chromiumcencdec", 0, "ClearKey CENC decryptor"); + + baseTransformClass->transform_ip = + GST_DEBUG_FUNCPTR(chromiumCommonEncryptionDecryptTransformInPlace); + baseTransformClass->transform_caps = + GST_DEBUG_FUNCPTR(chromiumCommonEncryptionDecryptTransformCaps); + baseTransformClass->sink_event = + GST_DEBUG_FUNCPTR(chromiumCommonEncryptionDecryptSinkEventHandler); + baseTransformClass->transform_ip_on_passthrough = FALSE; +} + +static void chromium_common_encryption_decrypt_init( + ChromiumCommonEncryptionDecrypt* self) { + GstBaseTransform* base = GST_BASE_TRANSFORM(self); + + gst_base_transform_set_in_place(base, TRUE); + gst_base_transform_set_passthrough(base, FALSE); + gst_base_transform_set_gap_aware(base, FALSE); + + g_mutex_init(&self->mutex); + g_cond_init(&self->condition); +} + +static void chromium_common_encryption_decrypt_dispose(GObject* object) { + ChromiumCommonEncryptionDecrypt* self = CHROMIUM_MEDIA_CENC_DECRYPT(object); + + g_mutex_clear(&self->mutex); + g_cond_clear(&self->condition); + + G_OBJECT_CLASS(parent_class)->dispose(object); +} +/* + Given the pad in this direction and the given caps, what caps are allowed on + the other pad in this element ? + */ +static GstCaps* chromiumCommonEncryptionDecryptTransformCaps( + GstBaseTransform* base, + GstPadDirection direction, + GstCaps* caps, + GstCaps* filter) { + g_return_val_if_fail(direction != GST_PAD_UNKNOWN, nullptr); + GstCaps* transformedCaps = gst_caps_new_empty(); + + GST_DEBUG_OBJECT(base, "direction: %s, caps: %" GST_PTR_FORMAT + " filter:" + " %" GST_PTR_FORMAT, + (direction == GST_PAD_SRC) ? "src" : "sink", caps, filter); + + unsigned size = gst_caps_get_size(caps); + for (unsigned i = 0; i < size; ++i) { + GstStructure* in = gst_caps_get_structure(caps, i); + GstStructure* out = nullptr; + + if (direction == GST_PAD_SINK) { + if (!gst_structure_has_field(in, "original-media-type")) + continue; + + out = gst_structure_copy(in); + gst_structure_set_name( + out, gst_structure_get_string(out, "original-media-type")); + + /* filter out the DRM related fields from the down-stream caps */ + for (int j = 0; j < gst_structure_n_fields(in); ++j) { + const gchar* fieldName = gst_structure_nth_field_name(in, j); + + if (g_str_has_prefix(fieldName, "protection-system") || + g_str_has_prefix(fieldName, "original-media-type")) + gst_structure_remove_field(out, fieldName); + } + } else { + GstStructure* tmp = gst_structure_copy(in); + /* filter out the video related fields from the up-stream caps, + because they are not relevant to the input caps of this element and + can cause caps negotiation failures with adaptive bitrate streams */ + for (int index = gst_structure_n_fields(tmp) - 1; index >= 0; --index) { + const gchar* fieldName = gst_structure_nth_field_name(tmp, index); + GST_TRACE("Check field \"%s\" for removal", fieldName); + + if (!g_strcmp0(fieldName, "base-profile") || + !g_strcmp0(fieldName, "codec_data") || + !g_strcmp0(fieldName, "height") || + !g_strcmp0(fieldName, "framerate") || + !g_strcmp0(fieldName, "level") || + !g_strcmp0(fieldName, "pixel-aspect-ratio") || + !g_strcmp0(fieldName, "profile") || !g_strcmp0(fieldName, "rate") || + !g_strcmp0(fieldName, "width")) { + gst_structure_remove_field(tmp, fieldName); + GST_TRACE("Removing field %s", fieldName); + } + } + + out = gst_structure_copy(tmp); + gst_structure_set(out, "protection-system", G_TYPE_STRING, + CLEAR_KEY_PROTECTION_SYSTEM_ID, "original-media-type", + G_TYPE_STRING, gst_structure_get_name(in), nullptr); + + gst_structure_set_name(out, "application/x-cenc"); + gst_structure_free(tmp); + } + + bool duplicate = false; + unsigned size = gst_caps_get_size(transformedCaps); + + for (unsigned index = 0; !duplicate && index < size; ++index) { + GstStructure* s = gst_caps_get_structure(transformedCaps, index); + if (gst_structure_is_equal(s, out)) + duplicate = true; + } + + if (!duplicate) + gst_caps_append_structure(transformedCaps, out); + else + gst_structure_free(out); + } + + if (filter) { + GstCaps* intersection; + + GST_DEBUG_OBJECT(base, "Using filter caps %" GST_PTR_FORMAT, filter); + intersection = gst_caps_intersect_full(transformedCaps, filter, + GST_CAPS_INTERSECT_FIRST); + gst_caps_unref(transformedCaps); + transformedCaps = intersection; + } + + GST_DEBUG_OBJECT(base, "returning %" GST_PTR_FORMAT, transformedCaps); + return transformedCaps; +} + +static GstFlowReturn chromiumCommonEncryptionDecryptTransformInPlace( + GstBaseTransform* base, + GstBuffer* buffer) { + ChromiumCommonEncryptionDecrypt* self = CHROMIUM_MEDIA_CENC_DECRYPT(base); + GstFlowReturn result = GST_FLOW_OK; + GstMapInfo map, ivMap; + unsigned position = 0; + unsigned sampleIndex = 0; + guint subSampleCount; + AesCtrState* state = nullptr; + guint ivSize; + gboolean encrypted; + const GValue* value; + GstBuffer* ivBuffer = nullptr; + GBytes* ivBytes = nullptr; + GstBuffer* subsamplesBuffer = nullptr; + GstMapInfo subSamplesMap; + GstByteReader* reader = nullptr; + GstProtectionMeta* protectionMeta = nullptr; + + g_mutex_lock(&self->mutex); + + // The key might not have been received yet. Wait for it. + if (!self->keyReceived) + g_cond_wait(&self->condition, &self->mutex); + + if (!self->key) { + GST_ERROR_OBJECT(self, "Decryption key not provided"); + result = GST_FLOW_NOT_SUPPORTED; + goto release; + } + + protectionMeta = reinterpret_cast( + gst_buffer_get_protection_meta(buffer)); + if (!protectionMeta || !buffer) { + if (!protectionMeta) + GST_ERROR_OBJECT( + self, "Failed to get GstProtection metadata from buffer %p", buffer); + + if (!buffer) + GST_ERROR_OBJECT(self, "Failed to get writable buffer"); + + result = GST_FLOW_NOT_SUPPORTED; + goto release; + } + + if (!gst_buffer_map(buffer, &map, + static_cast(GST_MAP_READWRITE))) { + GST_ERROR_OBJECT(self, "Failed to map buffer"); + result = GST_FLOW_NOT_SUPPORTED; + goto release; + } + + GST_TRACE_OBJECT(self, "decrypt sample %lu", map.size); + if (!gst_structure_get_uint(protectionMeta->info, "iv_size", &ivSize)) { + GST_ERROR_OBJECT(self, "failed to get iv_size"); + result = GST_FLOW_NOT_SUPPORTED; + goto release; + } + + if (!gst_structure_get_boolean(protectionMeta->info, "encrypted", + &encrypted)) { + GST_ERROR_OBJECT(self, "failed to get encrypted flag"); + result = GST_FLOW_NOT_SUPPORTED; + goto release; + } + + // Unencrypted sample. + if (!ivSize || !encrypted) + goto beach; + + GST_DEBUG_OBJECT(base, "protection meta: %" GST_PTR_FORMAT, + protectionMeta->info); + if (!gst_structure_get_uint(protectionMeta->info, "subsample_count", + &subSampleCount)) { + GST_ERROR_OBJECT(self, "failed to get subsample_count"); + result = GST_FLOW_NOT_SUPPORTED; + goto release; + } + + value = gst_structure_get_value(protectionMeta->info, "iv"); + if (!value) { + GST_ERROR_OBJECT(self, "Failed to get IV for sample"); + result = GST_FLOW_NOT_SUPPORTED; + goto release; + } + + ivBuffer = gst_value_get_buffer(value); + if (!gst_buffer_map(ivBuffer, &ivMap, GST_MAP_READ)) { + GST_ERROR_OBJECT(self, "Failed to map IV"); + result = GST_FLOW_NOT_SUPPORTED; + goto release; + } + + ivBytes = g_bytes_new(ivMap.data, ivMap.size); + gst_buffer_unmap(ivBuffer, &ivMap); + if (subSampleCount) { + value = gst_structure_get_value(protectionMeta->info, "subsamples"); + if (!value) { + GST_ERROR_OBJECT(self, "Failed to get subsamples"); + result = GST_FLOW_NOT_SUPPORTED; + goto release; + } + subsamplesBuffer = gst_value_get_buffer(value); + if (!gst_buffer_map(subsamplesBuffer, &subSamplesMap, GST_MAP_READ)) { + GST_ERROR_OBJECT(self, "Failed to map subsample buffer"); + result = GST_FLOW_NOT_SUPPORTED; + goto release; + } + } + + state = chromium_aes_ctr_decrypt_new(self->key, ivBytes); + if (!state) { + GST_ERROR_OBJECT(self, "Failed to init AES cipher"); + result = GST_FLOW_NOT_SUPPORTED; + goto release; + } + + reader = gst_byte_reader_new(subSamplesMap.data, subSamplesMap.size); + if (!reader) { + GST_ERROR_OBJECT(self, "Failed to allocate subsample reader"); + result = GST_FLOW_NOT_SUPPORTED; + goto release; + } + + GST_DEBUG_OBJECT(self, "position: %u, size: %lu", position, map.size); + while (position < map.size) { + guint16 nBytesClear = 0; + guint32 nBytesEncrypted = 0; + + if (sampleIndex < subSampleCount) { + if (!gst_byte_reader_get_uint16_be(reader, &nBytesClear) || + !gst_byte_reader_get_uint32_be(reader, &nBytesEncrypted)) { + result = GST_FLOW_NOT_SUPPORTED; + GST_DEBUG_OBJECT(self, "unsupported"); + goto release; + } + + sampleIndex++; + } else { + nBytesClear = 0; + nBytesEncrypted = map.size - position; + } + + GST_TRACE_OBJECT(self, "%d bytes clear (todo=%lu)", nBytesClear, + map.size - position); + position += nBytesClear; + if (nBytesEncrypted) { + GST_TRACE_OBJECT(self, "%d bytes encrypted (todo=%lu)", nBytesEncrypted, + map.size - position); + if (!chromium_aes_ctr_decrypt_ip(state, map.data + position, + nBytesEncrypted)) { + result = GST_FLOW_NOT_SUPPORTED; + GST_ERROR_OBJECT(self, "decryption failed"); + goto beach; + } + position += nBytesEncrypted; + } + } + +beach: + gst_buffer_unmap(buffer, &map); + if (state) + chromium_aes_ctr_decrypt_unref(state); + +release: + if (reader) + gst_byte_reader_free(reader); + + if (subsamplesBuffer) + gst_buffer_unmap(subsamplesBuffer, &subSamplesMap); + + if (protectionMeta) + gst_buffer_remove_meta(buffer, reinterpret_cast(protectionMeta)); + + if (ivBytes) + g_bytes_unref(ivBytes); + + g_mutex_unlock(&self->mutex); + return result; +} + +static gboolean chromiumCommonEncryptionDecryptSinkEventHandler( + GstBaseTransform* trans, + GstEvent* event) { + gboolean result = FALSE; + ChromiumCommonEncryptionDecrypt* self = CHROMIUM_MEDIA_CENC_DECRYPT(trans); + + switch (GST_EVENT_TYPE(event)) { + case GST_EVENT_PROTECTION: { + const gchar* systemId; + const gchar* origin; + + GST_DEBUG_OBJECT(self, "received protection event"); + gst_event_parse_protection(event, &systemId, &self->initDataBuffer, + &origin); + GST_DEBUG_OBJECT(self, "systemId: %s", systemId); + if (!g_str_equal(systemId, CLEAR_KEY_PROTECTION_SYSTEM_ID)) { + gst_event_unref(event); + result = TRUE; + break; + } + + if (g_str_has_prefix(origin, "isobmff/")) { + gst_element_post_message( + GST_ELEMENT(self), + gst_message_new_element( + GST_OBJECT(self), + gst_structure_new("drm-key-needed", "data", GST_TYPE_BUFFER, + self->initDataBuffer, "key-system-id", + G_TYPE_STRING, "org.w3.clearkey", nullptr))); + } + + gst_event_unref(event); + result = TRUE; + break; + } + case GST_EVENT_CUSTOM_DOWNSTREAM_OOB: { + const GstStructure* structure = gst_event_get_structure(event); + if (gst_structure_has_name(structure, "drm-cipher")) { + GstBuffer* buffer; + GstMapInfo info; + const GValue* value = gst_structure_get_value(structure, "key"); + buffer = gst_value_get_buffer(value); + if (self->key) + g_bytes_unref(self->key); + gst_buffer_map(buffer, &info, GST_MAP_READ); + self->key = g_bytes_new(info.data, info.size); + gst_buffer_unmap(buffer, &info); + + self->keyReceived = TRUE; + g_cond_signal(&self->condition); + } + + gst_event_unref(event); + result = TRUE; + break; + } + default: + result = GST_BASE_TRANSFORM_CLASS(parent_class)->sink_event(trans, event); + break; + } + + return result; +} + +void chromiumCommonEncryptionDecryptAddKey(GstElement* pipeline, + const std::string& key) { + GstBuffer* buffer = + gst_buffer_new_wrapped(g_memdup(key.data(), key.size()), key.size()); + gst_element_send_event( + pipeline, gst_event_new_custom( + GST_EVENT_CUSTOM_DOWNSTREAM_OOB, + gst_structure_new("drm-cipher", "key", GST_TYPE_BUFFER, + buffer, nullptr))); + gst_buffer_unref(buffer); +} diff --git a/content/media/gstreamer/gst_chromium_common_encryption_decryptor.h b/content/media/gstreamer/gst_chromium_common_encryption_decryptor.h new file mode 100644 index 0000000..20b8d82 --- /dev/null +++ b/content/media/gstreamer/gst_chromium_common_encryption_decryptor.h @@ -0,0 +1,39 @@ +// Copyright (c) 2015 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. + +#ifndef CONTENT_MEDIA_GSTREAMER_COMMON_ENCRYPTION_DECRYPTOR_H_ +#define CONTENT_MEDIA_GSTREAMER_COMMON_ENCRYPTION_DECRYPTOR_H_ + +#include +#include +#include + +#include + +G_BEGIN_DECLS + +#define CHROMIUM_TYPE_MEDIA_CENC_DECRYPT \ + (chromium_common_encryption_decrypt_get_type()) +#define CHROMIUM_MEDIA_CENC_DECRYPT(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), CHROMIUM_TYPE_MEDIA_CENC_DECRYPT, \ + ChromiumCommonEncryptionDecrypt)) +#define CHROMIUM_MEDIA_CENC_DECRYPT_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), CHROMIUM_TYPE_MEDIA_CENC_DECRYPT, \ + ChromiumCommonEncryptionDecryptClass)) +#define CHROMIUM_IS_MEDIA_CENC_DECRYPT(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), CHROMIUM_TYPE_MEDIA_CENC_DECRYPT)) +#define CHROMIUM_IS_MEDIA_CENC_DECRYPT_CLASS(obj) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), CHROMIUM_TYPE_MEDIA_CENC_DECRYPT)) + +typedef struct _ChromiumCommonEncryptionDecrypt ChromiumCommonEncryptionDecrypt; +typedef struct _ChromiumCommonEncryptionDecryptClass + ChromiumCommonEncryptionDecryptClass; + +GType chromium_common_encryption_decrypt_get_type(void); +void chromiumCommonEncryptionDecryptAddKey(GstElement* pipeline, + const std::string& key); + +G_END_DECLS + +#endif // CONTENT_MEDIA_GSTREAMER_COMMON_ENCRYPTION_DECRYPTOR_H_ diff --git a/content/media/gstreamer/gst_chromium_http_source.cc b/content/media/gstreamer/gst_chromium_http_source.cc new file mode 100644 index 0000000..82bfa57 --- /dev/null +++ b/content/media/gstreamer/gst_chromium_http_source.cc @@ -0,0 +1,903 @@ +// Copyright (c) 2015 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 "content/media/gstreamer/gst_chromium_http_source.h" + +#include +#include + +#include "base/callback.h" +#include "base/bind.h" +#include "base/synchronization/waitable_event.h" +#include "content/child/child_process.h" +#include "content/child/child_thread_impl.h" +#include "content/child/web_url_loader_impl.h" +#include "url/gurl.h" + +#define DEFAULT_BLOCKSIZE 4 * 1024 + +#define CHROMIUM_HTTP_SRC_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE((obj), CHROMIUM_TYPE_HTTP_SRC, \ + ChromiumHttpSrcPrivate)) + +// Inspired from WTF/wtf/glib/GUniquePtr.h +// TODO: move this in a separate header and handle all types. +template +struct GPtrDeleter { + void operator()(T* ptr) const { g_free(ptr); } +}; + +template +using GUniquePtr = std::unique_ptr>; + +#define WTF_DEFINE_GPTR_DELETER(typeName, deleterFunc) \ + template <> \ + struct GPtrDeleter { \ + void operator()(typeName * ptr) const { \ + if (ptr) \ + deleterFunc(ptr); \ + } \ + }; + +WTF_DEFINE_GPTR_DELETER(GstStructure, gst_structure_free) + +struct _ChromiumHttpSrcPrivate { + gchar* uri_; + bool keepAlive_; + GUniquePtr extraHeaders_; + bool compress_; + + bool data_source_initialized_; + gint last_read_bytes_; + base::Closure error_cb_; + + base::WaitableEvent* aborted_; + base::WaitableEvent* read_complete_; + + // icecast stuff + gchar* iradioName; + gchar* iradioGenre; + gchar* iradioUrl; + gchar* iradioTitle; + + std::mutex mutex_data_source_; + std::condition_variable condition_data_source_; + scoped_ptr gst_data_source_ ; +}; + +enum { + PROP_IRADIO_NAME = 1, + PROP_IRADIO_GENRE, + PROP_IRADIO_URL, + PROP_IRADIO_TITLE, + PROP_LOCATION, + PROP_KEEP_ALIVE, + PROP_EXTRA_HEADERS, + PROP_COMPRESS +}; + +static GstStaticPadTemplate srcTemplate = + GST_STATIC_PAD_TEMPLATE("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + +GST_DEBUG_CATEGORY_STATIC(chromium_http_src_debug); +#define GST_CAT_DEFAULT chromium_http_src_debug + +static void chromiumHttpSrcUriHandlerInit(gpointer gIface, gpointer ifaceData); + +static void chromiumHttpSrcFinalize(GObject*); +static void chromiumHttpSrcSetProperty(GObject*, + guint propertyID, + const GValue*, + GParamSpec*); +static void chromiumHttpSrcGetProperty(GObject*, + guint propertyID, + GValue*, + GParamSpec*); +static gboolean chromiumHttpSrcStart(GstBaseSrc* basesrc); +static gboolean chromiumHttpSrcStop(GstBaseSrc* basesrc); +static gboolean chromiumHttpSrcUnlock(GstBaseSrc* basesrc); +static gboolean chromiumHttpSrcUnlockStop(GstBaseSrc* basesrc); +static gboolean chromiumHttpSrcIsSeekable(GstBaseSrc* src); +static gboolean chromiumHttpSrcGetSize(GstBaseSrc* src, guint64* size); +static GstFlowReturn chromiumHttpSrcFill(GstBaseSrc* src, + guint64 offset, + guint length, + GstBuffer* buf); + +static void onResetDataSource(GstBaseSrc* basesrc); + +#define chromium_http_src_parent_class parent_class +#define CHROMIUM_HTTP_SRC_CATEGORY_INIT \ + GST_DEBUG_CATEGORY_INIT(chromium_http_src_debug, "chromiumhttpsrc", 0, \ + "Chromium http source element"); +G_DEFINE_TYPE_WITH_CODE(ChromiumHttpSrc, + chromium_http_src, + GST_TYPE_BASE_SRC, + G_IMPLEMENT_INTERFACE(GST_TYPE_URI_HANDLER, + chromiumHttpSrcUriHandlerInit); + CHROMIUM_HTTP_SRC_CATEGORY_INIT); + +static void chromium_http_src_class_init(ChromiumHttpSrcClass* klass) { + GObjectClass* oklass = G_OBJECT_CLASS(klass); + GstElementClass* eklass = GST_ELEMENT_CLASS(klass); + GstBaseSrcClass* bklass = GST_BASE_SRC_CLASS(klass); + + oklass->finalize = GST_DEBUG_FUNCPTR(chromiumHttpSrcFinalize); + oklass->set_property = GST_DEBUG_FUNCPTR(chromiumHttpSrcSetProperty); + oklass->get_property = GST_DEBUG_FUNCPTR(chromiumHttpSrcGetProperty); + + bklass->start = GST_DEBUG_FUNCPTR(chromiumHttpSrcStart); + bklass->stop = GST_DEBUG_FUNCPTR(chromiumHttpSrcStop); + bklass->unlock = GST_DEBUG_FUNCPTR(chromiumHttpSrcUnlock); + bklass->unlock_stop = GST_DEBUG_FUNCPTR(chromiumHttpSrcUnlockStop); + bklass->is_seekable = GST_DEBUG_FUNCPTR(chromiumHttpSrcIsSeekable); + bklass->get_size = GST_DEBUG_FUNCPTR(chromiumHttpSrcGetSize); + bklass->fill = GST_DEBUG_FUNCPTR(chromiumHttpSrcFill); + + gst_element_class_set_static_metadata( + eklass, "Chromium http source element", "Source", + "Handles FILE/HTTP/HTTPS/blob uris", + "Julien Isorce "); + + gst_element_class_add_pad_template(eklass, + gst_static_pad_template_get(&srcTemplate)); + + // icecast stuff + g_object_class_install_property( + oklass, PROP_IRADIO_NAME, + g_param_spec_string( + "iradio-name", "iradio-name", "Name of the stream", 0, + (GParamFlags)(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS))); + + g_object_class_install_property( + oklass, PROP_IRADIO_GENRE, + g_param_spec_string( + "iradio-genre", "iradio-genre", "Genre of the stream", 0, + (GParamFlags)(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS))); + + g_object_class_install_property( + oklass, PROP_IRADIO_URL, + g_param_spec_string( + "iradio-url", "iradio-url", "Homepage URL for radio stream", 0, + (GParamFlags)(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS))); + + g_object_class_install_property( + oklass, PROP_IRADIO_TITLE, + g_param_spec_string( + "iradio-title", "iradio-title", "Name of currently playing song", 0, + (GParamFlags)(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS))); + + /* Allows setting the uri using the 'location' property, which is used + * for example by gst_element_make_from_uri() */ + g_object_class_install_property( + oklass, PROP_LOCATION, + g_param_spec_string( + "location", "location", "Location to read from", 0, + (GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + + g_object_class_install_property( + oklass, PROP_KEEP_ALIVE, + g_param_spec_boolean("keep-alive", "keep-alive", + "Use HTTP persistent connections", FALSE, + static_cast(G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS))); + + g_object_class_install_property( + oklass, PROP_EXTRA_HEADERS, + g_param_spec_boxed("extra-headers", "Extra Headers", + "Extra headers to append to the HTTP request", + GST_TYPE_STRUCTURE, + static_cast(G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS))); + + g_object_class_install_property( + oklass, PROP_COMPRESS, + g_param_spec_boolean("compress", "Compress", + "Allow compressed content encodings", FALSE, + static_cast(G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS))); + + g_type_class_add_private(klass, sizeof(ChromiumHttpSrcPrivate)); +} + +static void chromium_http_src_init(ChromiumHttpSrc* src) { + ChromiumHttpSrcPrivate* priv = CHROMIUM_HTTP_SRC_GET_PRIVATE(src); + src->priv = priv; + + priv->gst_data_source_ = nullptr; + priv->data_source_initialized_ = false; + + priv->aborted_ = new base::WaitableEvent{true, false}; + priv->read_complete_ = new base::WaitableEvent{false, false}; + + gst_base_src_set_blocksize(GST_BASE_SRC(src), DEFAULT_BLOCKSIZE); +} + +static void onDeleteDataSource(content::GStreamerBufferedDataSource* gst_data_source) { + delete gst_data_source; +} + +static void chromiumHttpSrcFinalize(GObject* object) { + ChromiumHttpSrc* src = CHROMIUM_HTTP_SRC(object); + ChromiumHttpSrcPrivate* priv = src->priv; + + g_free(priv->uri_); + + delete priv->read_complete_; + delete priv->aborted_; + + if (priv->gst_data_source_) { + content::GStreamerBufferedDataSourceFactory::Get()->data_source_task_runner()->PostTask(FROM_HERE, + base::Bind(&onDeleteDataSource, priv->gst_data_source_.release())); + + priv->gst_data_source_ = nullptr; + } + + GST_CALL_PARENT(G_OBJECT_CLASS, finalize, (object)); +} + +static void chromiumHttpSrcSetProperty(GObject* object, + guint propID, + const GValue* value, + GParamSpec* pspec) { + ChromiumHttpSrc* src = CHROMIUM_HTTP_SRC(object); + + switch (propID) { + case PROP_LOCATION: + gst_uri_handler_set_uri(reinterpret_cast(src), + g_value_get_string(value), 0); + break; + case PROP_KEEP_ALIVE: + src->priv->keepAlive_ = g_value_get_boolean(value); + break; + case PROP_EXTRA_HEADERS: { + const GstStructure* s = gst_value_get_structure(value); + src->priv->extraHeaders_.reset(s ? gst_structure_copy(s) : nullptr); + break; + } + case PROP_COMPRESS: + src->priv->compress_ = g_value_get_boolean(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propID, pspec); + break; + } +} + +static void chromiumHttpSrcGetProperty(GObject* object, + guint propID, + GValue* value, + GParamSpec* pspec) { + ChromiumHttpSrc* src = CHROMIUM_HTTP_SRC(object); + ChromiumHttpSrcPrivate* priv = src->priv; + + GST_OBJECT_LOCK(src); + switch (propID) { + case PROP_IRADIO_NAME: + g_value_set_string(value, priv->iradioName); + break; + case PROP_IRADIO_GENRE: + g_value_set_string(value, priv->iradioGenre); + break; + case PROP_IRADIO_URL: + g_value_set_string(value, priv->iradioUrl); + break; + case PROP_IRADIO_TITLE: + g_value_set_string(value, priv->iradioTitle); + break; + case PROP_LOCATION: + g_value_set_string(value, priv->uri_); + break; + case PROP_KEEP_ALIVE: + g_value_set_boolean(value, priv->keepAlive_); + break; + case PROP_EXTRA_HEADERS: + gst_value_set_structure(value, priv->extraHeaders_.get()); + break; + case PROP_COMPRESS: + g_value_set_boolean(value, priv->compress_); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propID, pspec); + break; + } + + GST_OBJECT_UNLOCK(src); +} + +static void SignalReadCompleted(GstBaseSrc* basesrc, int size) { + ChromiumHttpSrc* src = CHROMIUM_HTTP_SRC(basesrc); + ChromiumHttpSrcPrivate* priv = src->priv; + + priv->last_read_bytes_ = size; + priv->read_complete_->Signal(); +} + +static GstFlowReturn chromiumHttpSrcFill(GstBaseSrc* basesrc, + guint64 offset, + guint length, + GstBuffer* buf) { + ChromiumHttpSrc* src = CHROMIUM_HTTP_SRC(basesrc); + ChromiumHttpSrcPrivate* priv = src->priv; + guint to_read = 0; + guint bytes_read = 0; + GstMapInfo info; + int ret = 0; + guint8* data = 0; + int64 read_position = offset; + + if (priv->aborted_->IsSignaled()) { + GST_ELEMENT_ERROR(src, RESOURCE, READ, (NULL), GST_ERROR_SYSTEM); + return GST_FLOW_ERROR; + } + + int64 file_size; + if (priv->gst_data_source_->data_source()->GetSize(&file_size) && read_position >= file_size) { + GST_ELEMENT_ERROR(src, RESOURCE, READ, (NULL), GST_ERROR_SYSTEM); + return GST_FLOW_ERROR; + } + + gst_buffer_map(buf, &info, GST_MAP_WRITE); + data = info.data; + + bytes_read = 0; + to_read = length; + while (to_read > 0) { + GST_LOG_OBJECT(src, "Reading %d bytes at offset 0x%" G_GINT64_MODIFIER "x", + to_read, offset + bytes_read); + errno = 0; + // Blocking read from data source until either: + // 1) |last_read_bytes_| is set and |read_complete_| is signalled + // 2) |aborted_| is signalled + priv->last_read_bytes_ = 0; + + priv->gst_data_source_->data_source()->Read(read_position, to_read, data, + base::Bind(&SignalReadCompleted, basesrc)); + + base::WaitableEvent* events[] = {priv->aborted_, priv->read_complete_}; + size_t index = base::WaitableEvent::WaitMany(events, arraysize(events)); + + if (events[index] == priv->aborted_) { + GST_ELEMENT_ERROR(src, RESOURCE, READ, (NULL), GST_ERROR_SYSTEM); + gst_buffer_unmap(buf, &info); + gst_buffer_resize(buf, 0, 0); + return GST_FLOW_ERROR; + } + if (priv->last_read_bytes_ == media::DataSource::kReadError) { + priv->aborted_->Signal(); + priv->error_cb_.Run(); + gst_buffer_unmap(buf, &info); + gst_buffer_resize(buf, 0, 0); + GST_ELEMENT_ERROR(src, RESOURCE, READ, (NULL), GST_ERROR_SYSTEM); + return GST_FLOW_ERROR; + } + + ret = priv->last_read_bytes_; + if (G_UNLIKELY(ret == 0)) { + // .. but first we should return any remaining data + if (bytes_read > 0) + break; + goto eos; + } + + to_read -= ret; + bytes_read += ret; + + read_position += ret; + } + + priv->last_read_bytes_ = 0; + + gst_buffer_unmap(buf, &info); + if (bytes_read != length) + gst_buffer_resize(buf, 0, bytes_read); + + GST_BUFFER_OFFSET(buf) = offset; + GST_BUFFER_OFFSET_END(buf) = offset + bytes_read; + + return GST_FLOW_OK; + +eos : { + GST_DEBUG("EOS"); + gst_buffer_unmap(buf, &info); + gst_buffer_resize(buf, 0, 0); + return GST_FLOW_EOS; +} +} + +static gboolean chromiumHttpSrcIsSeekable(GstBaseSrc* basesrc) { + ChromiumHttpSrc* src = CHROMIUM_HTTP_SRC(basesrc); + ChromiumHttpSrcPrivate* priv = src->priv; + return !priv->gst_data_source_->data_source()->IsStreaming(); +} + +static gboolean chromiumHttpSrcGetSize(GstBaseSrc* basesrc, guint64* size) { + ChromiumHttpSrc* src = CHROMIUM_HTTP_SRC(basesrc); + ChromiumHttpSrcPrivate* priv = src->priv; + + int64 file_size; + if (!priv->gst_data_source_->data_source()->GetSize(&file_size)) + return FALSE; + + *size = file_size; + + return TRUE; +} + +static gboolean chromiumHttpSrcStop(GstBaseSrc* basesrc) { + ChromiumHttpSrc* src = CHROMIUM_HTTP_SRC(basesrc); + ChromiumHttpSrcPrivate* priv = src->priv; + + GST_OBJECT_LOCK(src); + if (priv->gst_data_source_) { + // Can be called in any thread. + priv->gst_data_source_->data_source()->Stop(); + priv->data_source_initialized_ = false; + + g_free(priv->iradioName); + priv->iradioName = 0; + + g_free(priv->iradioGenre); + priv->iradioGenre = 0; + + g_free(priv->iradioUrl); + priv->iradioUrl = 0; + + g_free(priv->iradioTitle); + priv->iradioTitle = 0; + } + + GST_OBJECT_UNLOCK(src); + return TRUE; +} + +static gboolean chromiumHttpSrcUnlock (GstBaseSrc * basesrc) { + ChromiumHttpSrc* src = CHROMIUM_HTTP_SRC(basesrc); + ChromiumHttpSrcPrivate* priv = src->priv; + GST_OBJECT_LOCK(src); + + if (priv->gst_data_source_ && !priv->read_complete_->IsSignaled()) { + GST_DEBUG("Data source is unlocking."); + + priv->last_read_bytes_ = 0; + priv->read_complete_->Signal(); + } + + GST_OBJECT_UNLOCK(src); + return TRUE; +} + +static gboolean chromiumHttpSrcUnlockStop (GstBaseSrc * basesrc) { + return TRUE; +} + +static bool chromiumHttpSrcSetExtraHeader(GQuark fieldId, + const GValue* value, + gpointer userData) { + GUniquePtr fieldContent; + + if (G_VALUE_HOLDS_STRING(value)) + fieldContent.reset(g_value_dup_string(value)); + else { + GValue dest = G_VALUE_INIT; + + g_value_init(&dest, G_TYPE_STRING); + if (g_value_transform(value, &dest)) + fieldContent.reset(g_value_dup_string(&dest)); + } + + const gchar* fieldName = g_quark_to_string(fieldId); + if (!fieldContent.get()) { + GST_ERROR( + "extra-headers field '%s' contains no value or can't be converted to a " + "string", + fieldName); + return false; + } + + GST_DEBUG("Appending extra header: \"%s: %s\"", fieldName, + fieldContent.get()); + // ResourceRequest* request = static_cast(userData); + // request->setHTTPHeaderField(fieldName, fieldContent.get()); + return true; +} + +static gboolean chromiumHttpSrcProcessExtraHeaders(GQuark fieldId, + const GValue* value, + gpointer userData) { + if (G_VALUE_TYPE(value) == GST_TYPE_ARRAY) { + unsigned size = gst_value_array_get_size(value); + + for (unsigned i = 0; i < size; i++) { + if (!chromiumHttpSrcSetExtraHeader( + fieldId, gst_value_array_get_value(value, i), userData)) + return FALSE; + } + return TRUE; + } + + if (G_VALUE_TYPE(value) == GST_TYPE_LIST) { + unsigned size = gst_value_list_get_size(value); + + for (unsigned i = 0; i < size; i++) { + if (!chromiumHttpSrcSetExtraHeader( + fieldId, gst_value_list_get_value(value, i), userData)) + return FALSE; + } + return TRUE; + } + + return chromiumHttpSrcSetExtraHeader(fieldId, value, userData); +} + +static void onSourceInitialized(GstBaseSrc* basesrc, bool success) { + ChromiumHttpSrc* src = CHROMIUM_HTTP_SRC(basesrc); + ChromiumHttpSrcPrivate* priv = src->priv; + + { + std::lock_guard lock(priv->mutex_data_source_); + priv->data_source_initialized_ = success; + } + + GST_DEBUG("Data source notified intialization"); + + priv->condition_data_source_.notify_one(); +} + +static void onNotifyDownloading(GstBaseSrc* basesrc, bool is_downloading) { + content::GStreamerBufferedDataSourceFactory::Get()->media_log()->AddEvent(content::GStreamerBufferedDataSourceFactory::Get()->media_log()->CreateBooleanEvent( + media::MediaLogEvent::NETWORK_ACTIVITY_SET, "is_downloading_data", + is_downloading)); + + GST_DEBUG("Data source downloading: %d", is_downloading); +} + +static void onResetDataSource(GstBaseSrc* basesrc) { + ChromiumHttpSrc* src = CHROMIUM_HTTP_SRC(basesrc); + ChromiumHttpSrcPrivate* priv = src->priv; + + GST_DEBUG("Preparing data source for uri: %s", priv->uri_); + + content::WebURLLoaderImpl* url_loader = new content::WebURLLoaderImpl( + content::GStreamerBufferedDataSourceFactory::Get()->resource_dispatcher(), + content::GStreamerBufferedDataSourceFactory::Get()->data_source_task_runner()); + + // TODO: allow to set extra headers on WebURLLoaderImpl + + /*URL url = URL(URL(), priv->uri_); + + ResourceRequest request(url); + request.setAllowCookies(true); + request.setFirstPartyForCookies(url); + + priv->size = 0; + + if (priv->player) + request.setHTTPReferrer(priv->player->referrer());*/ + + // By default, HTTP Accept-Encoding is disabled here as we don't + // want the received response to be encoded in any way as we need + // to rely on the proper size of the returned data on + // didReceiveResponse. + // If Accept-Encoding is used, the server may send the data in encoded format + // and + // request.expectedContentLength() will have the "wrong" size (the size of the + // compressed data), even though the data received in didReceiveData is + // uncompressed. + // This is however useful to enable for adaptive streaming + // scenarios, when the demuxer needs to download playlists. + /*if (!priv->compress) + request.setAcceptEncoding(false);*/ + + // Let Apple web servers know we want to access their nice movie trailers. + /*if (!g_ascii_strcasecmp("movies.apple.com", url.host().utf8().data()) + || !g_ascii_strcasecmp("trailers.apple.com", url.host().utf8().data())) + request.setHTTPUserAgent("Quicktime/7.6.6");*/ + + /*if (priv->requestedOffset) { + GUniquePtr val(g_strdup_printf("bytes=%" G_GUINT64_FORMAT "-", + priv->requestedOffset)); + request.setHTTPHeaderField(HTTPHeaderName::Range, val.get()); + } + priv->offset = priv->requestedOffset;*/ + + /*if (!priv->keepAlive_) { + GST_DEBUG_OBJECT(src, "Persistent connection support disabled"); + request.setHTTPHeaderField(HTTPHeaderName::Connection, "close"); + }*/ + + // We always request Icecast/Shoutcast metadata, just in case ... + // request.setHTTPHeaderField(HTTPHeaderName::IcyMetadata, "1");*/ + if (priv->extraHeaders_) + gst_structure_foreach(priv->extraHeaders_.get(), + chromiumHttpSrcProcessExtraHeaders, /*&request*/ 0); + + GST_DEBUG("Create data source for uri: %s", priv->uri_); + + content::GStreamerBufferedDataSourceFactory::Get()->create(priv->uri_, media::BufferedResourceLoader::CORSMode::kUnspecified, src); + priv->gst_data_source_->data_source()->SetPreload(media::BufferedDataSource::AUTO); + priv->gst_data_source_->data_source()->Initialize(base::Bind(&onSourceInitialized, basesrc), + url_loader, "", blink::WebReferrerPolicyDefault); + + priv->gst_data_source_->data_source()->MediaIsPlaying(); +} + +static gboolean chromiumHttpSrcStart(GstBaseSrc* basesrc) { + ChromiumHttpSrc* src = CHROMIUM_HTTP_SRC(basesrc); + ChromiumHttpSrcPrivate* priv = src->priv; + gboolean is_seekable = FALSE; + + GST_OBJECT_LOCK(src); + + if (!priv->uri_) { + GST_ERROR_OBJECT(src, "No URI provided"); + GST_OBJECT_UNLOCK(src); + return false; + } + + GST_OBJECT_UNLOCK(src); + + { + std::unique_lock lock(priv->mutex_data_source_); + + if (priv->data_source_initialized_) { + GST_ERROR("Data source already initialized"); + return false; + } + + GST_DEBUG("Creating data source"); + + content::GStreamerBufferedDataSourceFactory::Get() + ->data_source_task_runner() + ->PostTask(FROM_HERE, base::Bind(&onResetDataSource, basesrc)); + priv->condition_data_source_.wait(lock); + + if (!priv->data_source_initialized_) { + GST_ERROR("Failed to initialized data source"); + return false; + } + + GST_DEBUG("Data source is initialized"); + + // TODO set data_source_->SetBitrate(); + + is_seekable = chromiumHttpSrcIsSeekable(basesrc); + + // priv->data_source_->MediaIsPlaying(); + + // TODO call data_source_->MediaIsPaused(); when paused + } + + GST_OBJECT_LOCK(src); + gst_base_src_set_dynamic_size(basesrc, is_seekable); + GST_OBJECT_UNLOCK(src); + + return true; +} + +static bool urlHasSupportedProtocol(const GURL& url) { + return url.SchemeIsHTTPOrHTTPS() || url.SchemeIsBlob(); +} + +static GstURIType chromiumHttpSrcUriGetType(GType) { + return GST_URI_SRC; +} + +const gchar* const* chromiumHttpSrcGetProtocols(GType) { + static const char* protocols[] = {"file", "http", "https", "blob", 0}; + return protocols; +} + +static gchar* chromiumHttpSrcGetUri(GstURIHandler* handler) { + ChromiumHttpSrc* src = CHROMIUM_HTTP_SRC(handler); + gchar* ret; + + GST_OBJECT_LOCK(src); + ret = g_strdup(src->priv->uri_); + + GST_OBJECT_UNLOCK(src); + return ret; +} + +static gboolean chromiumHttpSrcSetUri(GstURIHandler* handler, + const gchar* uri, + GError** error) { + ChromiumHttpSrc* src = CHROMIUM_HTTP_SRC(handler); + ChromiumHttpSrcPrivate* priv = src->priv; + + if (GST_STATE(src) >= GST_STATE_PAUSED) { + GST_ERROR_OBJECT(src, "URI can only be set in states < PAUSED"); + return FALSE; + } + + GST_OBJECT_LOCK(src); + + g_free(priv->uri_); + priv->uri_ = 0; + + if (!uri) + return TRUE; + + GURL url(uri); + if (!urlHasSupportedProtocol(url)) { + g_set_error(error, GST_URI_ERROR, GST_URI_ERROR_BAD_URI, "Invalid URI '%s'", + uri); + return FALSE; + } + + priv->uri_ = g_strdup(url.spec().c_str()); + + GST_OBJECT_UNLOCK(src); + return TRUE; +} + +static void chromiumHttpSrcUriHandlerInit(gpointer gIface, gpointer) { + GstURIHandlerInterface* iface = (GstURIHandlerInterface*)gIface; + + iface->get_protocols = chromiumHttpSrcGetProtocols; + iface->get_type = chromiumHttpSrcUriGetType; + iface->get_uri = chromiumHttpSrcGetUri; + iface->set_uri = chromiumHttpSrcSetUri; +} + +// TODO Allow to register a handleResponse callback to data source in order to +// parse blink::WebURLResponse. +/* +void StreamingClient::handleResponseReceived(const ResourceResponse& response) +{ + ChromiumHttpSrc* src = CHROMIUM_HTTP_SRC(m_src); + ChromiumHttpSrcPrivate* priv = src->priv; + + GST_DEBUG_OBJECT(src, "Received response: %d", response.httpStatusCode()); + + if (response.httpStatusCode() >= 400) { + GST_ELEMENT_ERROR(src, RESOURCE, READ, ("Received %d HTTP error code", +response.httpStatusCode()), (nullptr)); + gst_app_src_end_of_stream(priv->appsrc); + chromiumHttpSrcStop(src); + return; + } + + WTF::GMutexLocker locker(*GST_OBJECT_GET_LOCK(src)); + + if (priv->seekSource.isActive()) { + GST_DEBUG_OBJECT(src, "Seek in progress, ignoring response"); + return; + } + + if (priv->requestedOffset) { + // Seeking ... we expect a 206 == PARTIAL_CONTENT + if (response.httpStatusCode() == 200) { + // Range request didn't have a ranged response; resetting offset. + priv->offset = 0; + } else if (response.httpStatusCode() != 206) { + // Range request completely failed. + locker.unlock(); + GST_ELEMENT_ERROR(src, RESOURCE, READ, ("Received unexpected %d HTTP +status code", response.httpStatusCode()), (nullptr)); + gst_app_src_end_of_stream(priv->appsrc); + chromiumHttpSrcStop(src); + return; + } + } + + long long length = response.expectedContentLength(); + if (length > 0 && priv->requestedOffset && response.httpStatusCode() == 206) + length += priv->requestedOffset; + + priv->size = length >= 0 ? length : 0; + priv->seekable = length > 0 && g_ascii_strcasecmp("none", +response.httpHeaderField(HTTPHeaderName::AcceptRanges).utf8().data()); + + // Wait until we unlock to send notifications + g_object_freeze_notify(G_OBJECT(src)); + + GstTagList* tags = gst_tag_list_new_empty(); + String value = response.httpHeaderField(HTTPHeaderName::IcyName); + if (!value.isEmpty()) { + g_free(priv->iradioName); + priv->iradioName = g_strdup(value.utf8().data()); + g_object_notify(G_OBJECT(src), "iradio-name"); + gst_tag_list_add(tags, GST_TAG_MERGE_REPLACE, GST_TAG_ORGANIZATION, +priv->iradioName, NULL); + } + value = response.httpHeaderField(HTTPHeaderName::IcyGenre); + if (!value.isEmpty()) { + g_free(priv->iradioGenre); + priv->iradioGenre = g_strdup(value.utf8().data()); + g_object_notify(G_OBJECT(src), "iradio-genre"); + gst_tag_list_add(tags, GST_TAG_MERGE_REPLACE, GST_TAG_GENRE, +priv->iradioGenre, NULL); + } + value = response.httpHeaderField(HTTPHeaderName::IcyURL); + if (!value.isEmpty()) { + g_free(priv->iradioUrl); + priv->iradioUrl = g_strdup(value.utf8().data()); + g_object_notify(G_OBJECT(src), "iradio-url"); + gst_tag_list_add(tags, GST_TAG_MERGE_REPLACE, GST_TAG_LOCATION, +priv->iradioUrl, NULL); + } + value = response.httpHeaderField(HTTPHeaderName::IcyTitle); + if (!value.isEmpty()) { + g_free(priv->iradioTitle); + priv->iradioTitle = g_strdup(value.utf8().data()); + g_object_notify(G_OBJECT(src), "iradio-title"); + gst_tag_list_add(tags, GST_TAG_MERGE_REPLACE, GST_TAG_TITLE, +priv->iradioTitle, NULL); + } + + locker.unlock(); + g_object_thaw_notify(G_OBJECT(src)); + + // notify size/duration + if (length > 0) { + gst_app_src_set_size(priv->appsrc, length); + } else + gst_app_src_set_size(priv->appsrc, -1); + + // icecast stuff + value = response.httpHeaderField(HTTPHeaderName::IcyMetaInt); + if (!value.isEmpty()) { + gchar* endptr = 0; + gint64 icyMetaInt = g_ascii_strtoll(value.utf8().data(), &endptr, 10); + + if (endptr && *endptr == '\0' && icyMetaInt > 0) { + GRefPtr caps = +adoptGRef(gst_caps_new_simple("application/x-icy", "metadata-interval", +G_TYPE_INT, (gint) icyMetaInt, NULL)); + + gst_app_src_set_caps(priv->appsrc, caps.get()); + } + } else + gst_app_src_set_caps(priv->appsrc, 0); + + // notify tags + if (gst_tag_list_is_empty(tags)) + gst_tag_list_unref(tags); + else + gst_pad_push_event(priv->srcpad, gst_event_new_tag(tags)); +} +*/ + +namespace content { + +GStreamerBufferedDataSource::GStreamerBufferedDataSource(GURL url, media::BufferedResourceLoader::CORSMode cors_mode, ChromiumHttpSrc* src) + : data_source_(new media::BufferedDataSource( + url, + cors_mode, + GStreamerBufferedDataSourceFactory::Get()->data_source_task_runner(), + nullptr, + GStreamerBufferedDataSourceFactory::Get()->media_log().get(), + &buffered_data_source_host_, + base::Bind(&onNotifyDownloading, GST_BASE_SRC(src)))) { +} + +GStreamerBufferedDataSourceFactory::GStreamerBufferedDataSourceFactory() { +} + +base::LazyInstance::Leaky g_data_source_factory_ = LAZY_INSTANCE_INITIALIZER; + +void GStreamerBufferedDataSourceFactory::create(gchar* uri, media::BufferedResourceLoader::CORSMode cors_mode, ChromiumHttpSrc* src) { + ChromiumHttpSrcPrivate* priv = src->priv; + priv->gst_data_source_.reset(new GStreamerBufferedDataSource(GURL(uri), cors_mode, src)); +} + +void GStreamerBufferedDataSourceFactory::Set(scoped_refptr media_log, content::ResourceDispatcher* resource_dispatcher, scoped_refptr data_source_task_runner) { + media_log_ = media_log; + resource_dispatcher_ = resource_dispatcher; + data_source_task_runner_ = data_source_task_runner; +} + +void GStreamerBufferedDataSourceFactory::Init(scoped_refptr media_log, content::ResourceDispatcher* resource_dispatcher, scoped_refptr data_source_task_runner) { + g_data_source_factory_.Pointer()->Set(media_log, resource_dispatcher, data_source_task_runner); +} + +GStreamerBufferedDataSourceFactory* GStreamerBufferedDataSourceFactory::Get() { + return g_data_source_factory_.Pointer(); +} + +} diff --git a/content/media/gstreamer/gst_chromium_http_source.h b/content/media/gstreamer/gst_chromium_http_source.h new file mode 100644 index 0000000..7c5a129 --- /dev/null +++ b/content/media/gstreamer/gst_chromium_http_source.h @@ -0,0 +1,85 @@ +// Copyright (c) 2015 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. + +#ifndef CONTENT_MEDIA_GSTREAMER_HTTP_SOURCE_H_ +#define CONTENT_MEDIA_GSTREAMER_HTTP_SOURCE_H_ + +#include +#include + +#include "base/basictypes.h" +#include "content/renderer/media/render_media_log.h" +#include "media/base/data_source.h" +#include "media/blink/buffered_data_source.h" +#include "media/blink/buffered_data_source_host_impl.h" + +namespace content { +class ResourceDispatcher; +} + +G_BEGIN_DECLS + +#define CHROMIUM_TYPE_HTTP_SRC (chromium_http_src_get_type()) +#define CHROMIUM_HTTP_SRC(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), CHROMIUM_TYPE_HTTP_SRC, ChromiumHttpSrc)) +#define CHROMIUM_HTTP_SRC_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), CHROMIUM_TYPE_HTTP_SRC, \ + ChromiumHttpSrcClass)) +#define CHROMIUM_IS_HTTP_SRC(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), CHROMIUM_TYPE_HTTP_SRC)) +#define CHROMIUM_IS_HTTP_SRC_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), CHROMIUM_TYPE_HTTP_SRC)) + +typedef struct _ChromiumHttpSrc ChromiumHttpSrc; +typedef struct _ChromiumHttpSrcClass ChromiumHttpSrcClass; +typedef struct _ChromiumHttpSrcPrivate ChromiumHttpSrcPrivate; + +struct _ChromiumHttpSrc { + GstBaseSrc parent; + + _ChromiumHttpSrcPrivate* priv; +}; + +struct _ChromiumHttpSrcClass { + GstBaseSrcClass parentClass; +}; + +GType chromium_http_src_get_type(void); + +G_END_DECLS + +namespace content { + +class GStreamerBufferedDataSource { +public: + GStreamerBufferedDataSource(GURL url, media::BufferedResourceLoader::CORSMode cors_mode, ChromiumHttpSrc* src); + media::BufferedDataSourceHostImpl* buffered_data_source_host() { return &buffered_data_source_host_; } + media::BufferedDataSource* data_source() { return data_source_.get(); } + +private: + media::BufferedDataSourceHostImpl buffered_data_source_host_; + scoped_ptr data_source_; +}; + +class GStreamerBufferedDataSourceFactory { +public: + GStreamerBufferedDataSourceFactory(); + void create(gchar* uri, media::BufferedResourceLoader::CORSMode cors_mode, ChromiumHttpSrc* src); + static GStreamerBufferedDataSourceFactory* Get(); + static void Init(scoped_refptr, content::ResourceDispatcher*, scoped_refptr); + void Set(scoped_refptr, content::ResourceDispatcher*, scoped_refptr); + scoped_refptr media_log() { return media_log_; } + content::ResourceDispatcher* resource_dispatcher() { return resource_dispatcher_; } + scoped_refptr data_source_task_runner() { return data_source_task_runner_; } + +private: + scoped_refptr media_log_; + content::ResourceDispatcher* resource_dispatcher_; + scoped_refptr data_source_task_runner_; + DISALLOW_COPY_AND_ASSIGN(GStreamerBufferedDataSourceFactory); +}; + +} + +#endif // CONTENT_MEDIA_GSTREAMER_HTTP_SOURCE_H_ diff --git a/content/media/gstreamer/gst_chromium_media_src.cc b/content/media/gstreamer/gst_chromium_media_src.cc new file mode 100644 index 0000000..93578fa --- /dev/null +++ b/content/media/gstreamer/gst_chromium_media_src.cc @@ -0,0 +1,621 @@ +// Copyright (c) 2015 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 "content/media/gstreamer/gst_chromium_media_src.h" + +#include +#include +#include + +#include + +// This element has been inspired from WebKitMediaSource +// TODO: use multiappsrc element which is not upstream +// at the moment, see https://bugzilla.gnome.org/show_bug.cgi?id=725187 + +typedef struct { + const gchar* gst; + const gchar* mse; +} CodecInfo; + +// TODO: make this generic, maybe improve gst_pb_utils. +static const CodecInfo codecs_map[] = { + {"MPEG-4 AAC", "mp4a"}, + {"On2 VP9", "vp9"}, +}; + +typedef struct _Source Source; +struct _Source { + GstElement* src; + GstPad* pad; + // Just for identification + std::string id; + std::string media_type; + std::string codec; + bool initialized; +}; + +struct _ChromiumMediaSrcPrivate { + GList* sources; + gchar* location; + GstClockTime duration; + ; + bool asyncStart; + bool noMorePads; +}; + +enum { Prop0, PropLocation }; + +static GstStaticPadTemplate srcTemplate = + GST_STATIC_PAD_TEMPLATE("src_%u", + GST_PAD_SRC, + GST_PAD_SOMETIMES, + GST_STATIC_CAPS_ANY); + +#define CHROMIUM_MEDIA_SRC_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE((obj), CHROMIUM_TYPE_MEDIA_SRC, \ + ChromiumMediaSrcPrivate)) + +GST_DEBUG_CATEGORY_STATIC(chromium_media_src_debug); +#define GST_CAT_DEFAULT chromium_media_src_debug + +static void chromiumMediaSrcUriHandlerInit(gpointer gIface, gpointer ifaceData); +static void chromiumMediaSrcFinalize(GObject*); +static void chromiumMediaSrcSetProperty(GObject*, + guint propertyId, + const GValue*, + GParamSpec*); +static void chromiumMediaSrcGetProperty(GObject*, + guint propertyId, + GValue*, + GParamSpec*); +static GstStateChangeReturn chromiumMediaSrcChangeState(GstElement*, + GstStateChange); +static gboolean chromiumMediaSrcQueryWithParent(GstPad*, GstObject*, GstQuery*); + +#define chromium_media_src_parent_class parent_class + +#define CHROMIUM_MEDIA_SRC_CATEGORY_INIT \ + GST_DEBUG_CATEGORY_INIT(chromium_media_src_debug, "chromiummediasrc", 0, \ + "media src element"); +G_DEFINE_TYPE_WITH_CODE(ChromiumMediaSrc, + chromium_media_src, + GST_TYPE_BIN, + G_IMPLEMENT_INTERFACE(GST_TYPE_URI_HANDLER, + chromiumMediaSrcUriHandlerInit); + CHROMIUM_MEDIA_SRC_CATEGORY_INIT); + +static void chromium_media_src_class_init(ChromiumMediaSrcClass* klass) { + GObjectClass* oklass = G_OBJECT_CLASS(klass); + GstElementClass* eklass = GST_ELEMENT_CLASS(klass); + + oklass->finalize = chromiumMediaSrcFinalize; + oklass->set_property = chromiumMediaSrcSetProperty; + oklass->get_property = chromiumMediaSrcGetProperty; + + gst_element_class_add_pad_template(eklass, + gst_static_pad_template_get(&srcTemplate)); + + gst_element_class_set_static_metadata( + eklass, "Chromium Media source element", "Source", "Handles Blob uris", + "Stephane Jadaud , Sebastian Dröge " + ""); + + /* Allows setting the uri using the 'location' property, which is used + * for example by gst_element_make_from_uri() */ + g_object_class_install_property( + oklass, PropLocation, + g_param_spec_string( + "location", "location", "Location to read from", 0, + (GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + + eklass->change_state = chromiumMediaSrcChangeState; + + g_type_class_add_private(klass, sizeof(ChromiumMediaSrcPrivate)); +} + +static void chromium_media_src_init(ChromiumMediaSrc* src) { + src->priv = CHROMIUM_MEDIA_SRC_GET_PRIVATE(src); + + src->priv->sources = NULL; + src->priv->location = NULL; +} + +static void destroy_source(gpointer data, gpointer user_data) { + // TODO: dynamically remove source + // ChromiumMediaSrc* media_source = CHROMIUM_MEDIA_SRC(user_data); + Source* source = (Source*)data; + if (GST_IS_APP_SRC(source->src)) { + /*gst_app_src_end_of_stream(GST_APP_SRC(source->src)); + gst_pad_set_active(source->pad, FALSE); + gst_remove_add_pad(GST_ELEMENT(media_source), source->pad); + gst_object_unref(source->pad); + gst_element_set_state(GST_ELEMENT(source->src), GST_STATE_NULL); + gst_bin_remove(GST_BIN(media_source), source->src);*/ + gst_object_unref(source->pad); + source->pad = NULL; + gst_object_unref(source->src); + source->src = NULL; + } + delete source; +} + +static void chromiumMediaSrcFinalize(GObject* object) { + ChromiumMediaSrc* src = CHROMIUM_MEDIA_SRC(object); + ChromiumMediaSrcPrivate* priv = src->priv; + + if (priv->sources) { + g_list_foreach(priv->sources, (GFunc)destroy_source, src); + g_list_free(priv->sources); + priv->sources = NULL; + } + + if (priv->location) { + g_free(priv->location); + priv->location = NULL; + } + + GST_CALL_PARENT(G_OBJECT_CLASS, finalize, (object)); +} + +static void chromiumMediaSrcSetProperty(GObject* object, + guint propId, + const GValue* value, + GParamSpec* pspec) { + ChromiumMediaSrc* src = CHROMIUM_MEDIA_SRC(object); + + switch (propId) { + case PropLocation: + gst_uri_handler_set_uri(reinterpret_cast(src), + g_value_get_string(value), 0); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propId, pspec); + break; + } +} + +static void chromiumMediaSrcGetProperty(GObject* object, + guint propId, + GValue* value, + GParamSpec* pspec) { + ChromiumMediaSrc* src = CHROMIUM_MEDIA_SRC(object); + ChromiumMediaSrcPrivate* priv = src->priv; + + GST_OBJECT_LOCK(src); + switch (propId) { + case PropLocation: + g_value_set_string(value, priv->location); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, propId, pspec); + break; + } + GST_OBJECT_UNLOCK(src); +} + +static void chromiumMediaSrcDoAsyncStart(ChromiumMediaSrc* src) { + ChromiumMediaSrcPrivate* priv = src->priv; + priv->asyncStart = true; + GST_BIN_CLASS(parent_class) + ->handle_message(GST_BIN(src), + gst_message_new_async_start(GST_OBJECT(src))); +} + +static void chromiumMediaSrcDoAsyncDone(ChromiumMediaSrc* src) { + ChromiumMediaSrcPrivate* priv = src->priv; + if (priv->asyncStart) { + GST_BIN_CLASS(parent_class) + ->handle_message( + GST_BIN(src), + gst_message_new_async_done(GST_OBJECT(src), GST_CLOCK_TIME_NONE)); + priv->asyncStart = false; + } +} + +static GstStateChangeReturn chromiumMediaSrcChangeState( + GstElement* element, + GstStateChange transition) { + GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; + ChromiumMediaSrc* src = CHROMIUM_MEDIA_SRC(element); + ChromiumMediaSrcPrivate* priv = src->priv; + + switch (transition) { + case GST_STATE_CHANGE_READY_TO_PAUSED: + priv->noMorePads = false; + chromiumMediaSrcDoAsyncStart(src); + break; + default: + break; + } + + ret = GST_ELEMENT_CLASS(parent_class)->change_state(element, transition); + if (G_UNLIKELY(ret == GST_STATE_CHANGE_FAILURE)) { + GST_DEBUG_OBJECT(src, "State change failed"); + chromiumMediaSrcDoAsyncDone(src); + return ret; + } + + switch (transition) { + case GST_STATE_CHANGE_READY_TO_PAUSED: + ret = GST_STATE_CHANGE_ASYNC; + break; + case GST_STATE_CHANGE_PAUSED_TO_READY: + chromiumMediaSrcDoAsyncDone(src); + priv->noMorePads = false; + break; + default: + break; + } + + return ret; +} + +static gboolean chromiumMediaSrcQueryWithParent(GstPad* pad, + GstObject* parent, + GstQuery* query) { + ChromiumMediaSrc* src = CHROMIUM_MEDIA_SRC(GST_ELEMENT(parent)); + gboolean result = FALSE; + + switch (GST_QUERY_TYPE(query)) { + case GST_QUERY_DURATION: { + GstFormat format; + gst_query_parse_duration(query, &format, NULL); + + GST_DEBUG_OBJECT(src, "duration query in format %s", + gst_format_get_name(format)); + GST_OBJECT_LOCK(src); + if ((format == GST_FORMAT_TIME) && (src->priv->duration > 0)) { + gst_query_set_duration(query, format, src->priv->duration); + result = TRUE; + } + GST_OBJECT_UNLOCK(src); + break; + } + case GST_QUERY_URI: { + GST_OBJECT_LOCK(src); + gst_query_set_uri(query, src->priv->location); + GST_OBJECT_UNLOCK(src); + result = TRUE; + break; + } + default: { + GstPad* target = gst_ghost_pad_get_target(GST_GHOST_PAD_CAST(pad)); + // Forward the query to the proxy target pad. + if (target) { + result = gst_pad_query(target, query); + gst_object_unref(target); + } + break; + } + } + + return result; +} + +// uri handler interface +static GstURIType chromiumMediaSrcUriGetType(GType) { + return GST_URI_SRC; +} + +const gchar* const* chromiumMediaSrcGetProtocols(GType) { + static const char* protocols[] = {"mediasourceblob", 0}; + return protocols; +} + +static gchar* chromiumMediaSrcGetUri(GstURIHandler* handler) { + ChromiumMediaSrc* src = CHROMIUM_MEDIA_SRC(handler); + gchar* ret; + + GST_OBJECT_LOCK(src); + ret = g_strdup(src->priv->location); + GST_OBJECT_UNLOCK(src); + return ret; +} + +static gboolean chromiumMediaSrcSetUri(GstURIHandler* handler, + const gchar* uri, + GError**) { + ChromiumMediaSrc* src = CHROMIUM_MEDIA_SRC(handler); + ChromiumMediaSrcPrivate* priv = src->priv; + GError* error = NULL; + + if (GST_STATE(src) >= GST_STATE_PAUSED) { + GST_ERROR_OBJECT(src, "URI can only be set in states < PAUSED"); + return FALSE; + } + + GST_OBJECT_LOCK(src); + g_free(priv->location); + priv->location = NULL; + + if (!uri) { + GST_OBJECT_UNLOCK(src); + return TRUE; + } + + GURL url(uri); + if (!url.SchemeIs("mediasourceblob")) { + g_set_error(&error, GST_URI_ERROR, GST_URI_ERROR_BAD_URI, + "Invalid URI '%s'", uri); + return FALSE; + } + + priv->location = g_strdup(url.spec().c_str()); + GST_OBJECT_UNLOCK(src); + return TRUE; +} +static void chromiumMediaSrcUriHandlerInit(gpointer gIface, gpointer) { + GstURIHandlerInterface* iface = (GstURIHandlerInterface*)gIface; + + iface->get_type = chromiumMediaSrcUriGetType; + iface->get_protocols = chromiumMediaSrcGetProtocols; + iface->get_uri = chromiumMediaSrcGetUri; + iface->set_uri = chromiumMediaSrcSetUri; +} + +blink::WebMediaSource::AddStatus chromiumMediaSrcAddSourceBufferId( + ChromiumMediaSrc* src, + const std::string& source_id, + const std::string& media_type, + const std::vector& codecs) { + ChromiumMediaSrcPrivate* priv = src->priv; + + if (priv->noMorePads) { + GST_ERROR_OBJECT( + src, "Adding new source buffers after first data not supported yet"); + return blink::WebMediaSource::AddStatusNotSupported; + } + + GST_DEBUG_OBJECT(src, "State %d", static_cast(GST_STATE(src))); + + GST_OBJECT_LOCK(src); + + Source* source = new Source; + guint numberOfSources = g_list_length(priv->sources); + gchar* src_name = g_strdup_printf("src%u", numberOfSources); + source->src = gst_element_factory_make("appsrc", src_name); + g_free(src_name); + + source->id = source_id; + source->media_type = media_type; + if (codecs.size() > 0) + source->codec = codecs[0]; + source->initialized = false; + + gchar* pad_name = g_strdup_printf("src_%u", numberOfSources); + priv->sources = g_list_prepend(priv->sources, source); + + GST_OBJECT_UNLOCK(src); + + gst_bin_add(GST_BIN(src), GST_ELEMENT(gst_object_ref(source->src))); + GstPad* pad = gst_element_get_static_pad(source->src, "src"); + GstPad* ghostPad = gst_ghost_pad_new_from_template( + pad_name, pad, gst_static_pad_template_get(&srcTemplate)); + g_free(pad_name); + gst_pad_set_query_function(ghostPad, chromiumMediaSrcQueryWithParent); + gst_pad_set_active(ghostPad, TRUE); + gst_element_add_pad(GST_ELEMENT(src), GST_PAD(gst_object_ref(ghostPad))); + + source->pad = ghostPad; + + gst_element_sync_state_with_parent(source->src); + + return blink::WebMediaSource::AddStatusOk; +} + +void chromiumMediaSrcSetDuration(ChromiumMediaSrc* src, + const base::TimeDelta& duration) { + ChromiumMediaSrcPrivate* priv = src->priv; + + GstClockTime gst_duration = duration.InMicroseconds() * 1000; + if (!GST_CLOCK_TIME_IS_VALID(gst_duration)) + return; + + GST_DEBUG_OBJECT(src, "Received duration: %" GST_TIME_FORMAT, + GST_TIME_ARGS(gst_duration)); + + GST_OBJECT_LOCK(src); + priv->duration = gst_duration; + GST_OBJECT_UNLOCK(src); + gst_element_post_message(GST_ELEMENT(src), + gst_message_new_duration_changed(GST_OBJECT(src))); + + GST_OBJECT_UNLOCK(src); +} + +bool chromiumMediaSrcAppendData(ChromiumMediaSrc* src, + const std::string& source_id, + const std::vector& data, + const std::vector& times, + base::TimeDelta& timestamp_offset) { + ChromiumMediaSrcPrivate* priv = src->priv; + GstFlowReturn ret = GST_FLOW_OK; + GstBuffer* buffer; + Source* source = 0; + + if (!priv->noMorePads) { + priv->noMorePads = true; + gst_element_no_more_pads(GST_ELEMENT(src)); + chromiumMediaSrcDoAsyncDone(src); + } + + GST_OBJECT_LOCK(src); + + for (GList* iter = priv->sources; iter; iter = iter->next) { + Source* tmp = static_cast(iter->data); + if (tmp->id == source_id) { + source = tmp; + break; + } + } + + if (!source || !source->src) { + GST_OBJECT_UNLOCK(src); + return false; + } + + buffer = gst_buffer_new_and_alloc(data.size()); + gst_buffer_fill(buffer, 0, data.data(), data.size()); + + ret = gst_app_src_push_buffer(GST_APP_SRC(source->src), buffer); + GST_DEBUG_OBJECT(src, "push buffer %d\n", static_cast(ret)); + + // TODO: use times which contains windowStart, windowEnd and timestamp offset. + // Also update timestamp_offset. + + if (ret == GST_FLOW_OK) { + GST_OBJECT_UNLOCK(src); + return true; + } + + GST_OBJECT_UNLOCK(src); + + return false; +} + +void chromiumMediaSrcAbort(ChromiumMediaSrc* src, + const std::string& source_id) { + ChromiumMediaSrcPrivate* priv = src->priv; + Source* source = 0; + + GST_OBJECT_LOCK(src); + + if (source_id.empty()) { + GST_WARNING_OBJECT(src, "source id is empty"); + GST_OBJECT_UNLOCK(src); + return; + } + + for (GList* iter = priv->sources; iter; iter = iter->next) { + Source* tmp = static_cast(iter->data); + if (tmp->id == source_id) { + source = tmp; + break; + } + } + + if (!source || !source->src) { + GST_WARNING_OBJECT(src, "cannot find source %s", source_id.c_str()); + GST_OBJECT_UNLOCK(src); + return; + } + + GST_OBJECT_UNLOCK(src); + + GST_DEBUG_OBJECT(src, "TODO: abort source: %s", source_id.c_str()); + + // TODO: after abort has been called the next append buffer will be + // the first playback position. + // Maybe a flush seek to 0 will do. + NOTIMPLEMENTED(); +} + +void chromiumMediaSrcMarkEndOfStream(ChromiumMediaSrc* src) { + ChromiumMediaSrcPrivate* priv = src->priv; + + GST_DEBUG_OBJECT(src, "Have EOS"); + + if (!priv->noMorePads) { + priv->noMorePads = true; + gst_element_no_more_pads(GST_ELEMENT(src)); + chromiumMediaSrcDoAsyncDone(src); + } + + for (GList* iter = priv->sources; iter; iter = iter->next) { + Source* source = static_cast(iter->data); + if (source->src) + gst_app_src_end_of_stream(GST_APP_SRC(source->src)); + } +} + +void chromiumMediaSrcRemoveSourceBufferId(ChromiumMediaSrc* src, + const std::string& source_id) { + GST_OBJECT_LOCK(src); + + ChromiumMediaSrcPrivate* priv = src->priv; + Source* source = 0; + + for (GList* iter = priv->sources; iter; iter = iter->next) { + Source* tmp = static_cast(iter->data); + if (tmp->id == source_id) { + source = tmp; + break; + } + } + + DCHECK(source && source->src); + + priv->sources = g_list_remove(priv->sources, (gconstpointer)source); + + destroy_source(source, src); + + GST_OBJECT_UNLOCK(src); +} + +static std::string gstCodecToMSECodec(const std::string& codec) { + if (codec.empty()) + return ""; + + for (size_t i = 0; i < G_N_ELEMENTS(codecs_map); ++i) { + if (strcmp(codec.c_str(), codecs_map[i].gst) == 0) { + return codecs_map[i].mse; + } + } + return ""; +} + +void chromiumMediaSrcIsMatchingSourceId(ChromiumMediaSrc* src, + const std::string& media_kind, + const std::string& gst_codec, + std::string& source_id) { + GST_OBJECT_LOCK(src); + + ChromiumMediaSrcPrivate* priv = src->priv; + + std::string mse_codec = gstCodecToMSECodec(gst_codec); + std::string lower_gst_codec; + std::transform(gst_codec.begin(), gst_codec.end(), lower_gst_codec.begin(), + ::tolower); + + for (GList* iter = priv->sources; iter; iter = iter->next) { + Source* source = static_cast(iter->data); + if (!source->initialized && + source->media_type.find(media_kind) != std::string::npos && + (source->codec.find(mse_codec) != std::string::npos || + source->codec.find(lower_gst_codec) != std::string::npos || + lower_gst_codec.find(source->codec) != std::string::npos)) { + source->initialized = true; + GST_OBJECT_UNLOCK(src); + source_id = source->id; + return; + } + } + + GST_OBJECT_UNLOCK(src); + + source_id = ""; +} + +void chromiumMediaSrcUnmarkEndOfStream(ChromiumMediaSrc* src) {} + +void chromiumMediaSrcSetSequenceMode(ChromiumMediaSrc* src, + const std::string& source_id, + bool sequence_mod) { + NOTIMPLEMENTED(); +} + +void chromiumMediaSrcSetGroupStartTimestampIfInSequenceMode( + ChromiumMediaSrc* src, + const std::string& source_id, + const base::TimeDelta& timestamp_offset) { + NOTIMPLEMENTED(); +} + +void chromiumMediaSrcRemoveSegment(ChromiumMediaSrc* src, + const std::string& source_id, + const base::TimeDelta& start, + const base::TimeDelta& end) { + NOTIMPLEMENTED(); +} diff --git a/content/media/gstreamer/gst_chromium_media_src.h b/content/media/gstreamer/gst_chromium_media_src.h new file mode 100644 index 0000000..bcde42d --- /dev/null +++ b/content/media/gstreamer/gst_chromium_media_src.h @@ -0,0 +1,82 @@ +// Copyright (c) 2015 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. + +#ifndef CONTENT_MEDIA_GSTREAMER_GST_CHROMIUM_MEDIA_SRC_H_ +#define CONTENT_MEDIA_GSTREAMER_GST_CHROMIUM_MEDIA_SRC_H_ + +#include + +#include +#include + +#include "base/time/time.h" +#include "third_party/WebKit/public/platform/WebMediaSource.h" + +G_BEGIN_DECLS + +#define CHROMIUM_TYPE_MEDIA_SRC (chromium_media_src_get_type()) +#define CHROMIUM_MEDIA_SRC(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), CHROMIUM_TYPE_MEDIA_SRC, ChromiumMediaSrc)) +#define CHROMIUM_MEDIA_SRC_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), CHROMIUM_TYPE_MEDIA_SRC, \ + ChromiumMediaSrcClass)) +#define CHROMIUM_IS_MEDIA_SRC(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj), CHROMIUM_TYPE_MEDIA_SRC)) +#define CHROMIUM_IS_MEDIA_SRC_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), CHROMIUM_TYPE_MEDIA_SRC)) + +typedef struct _ChromiumMediaSrc ChromiumMediaSrc; +typedef struct _ChromiumMediaSrcClass ChromiumMediaSrcClass; +typedef struct _ChromiumMediaSrcPrivate ChromiumMediaSrcPrivate; + +struct _ChromiumMediaSrc { + GstBin parent; + + ChromiumMediaSrcPrivate* priv; +}; + +struct _ChromiumMediaSrcClass { + GstBinClass parentClass; +}; + +GType chromium_media_src_get_type(void); + +blink::WebMediaSource::AddStatus chromiumMediaSrcAddSourceBufferId( + ChromiumMediaSrc* src, + const std::string& source_id, + const std::string& type, + const std::vector& codecs); +void chromiumMediaSrcRemoveSourceBufferId(ChromiumMediaSrc* src, + const std::string& source_id); +void chromiumMediaSrcSetDuration(ChromiumMediaSrc* src, + const base::TimeDelta& duration); +void chromiumMediaSrcMarkEndOfStream(ChromiumMediaSrc* src); +bool chromiumMediaSrcAppendData(ChromiumMediaSrc* src, + const std::string& source_id, + const std::vector& data, + const std::vector& times, + base::TimeDelta& timestamp_offset); +void chromiumMediaSrcAbort(ChromiumMediaSrc* src, const std::string& source_id); +void chromiumMediaSrcIsMatchingSourceId(ChromiumMediaSrc* src, + const std::string& media_kind, + const std::string& codec, + std::string& source_id); + +// Unimplemented. +void chromiumMediaSrcUnmarkEndOfStream(ChromiumMediaSrc* src); +void chromiumMediaSrcSetSequenceMode(ChromiumMediaSrc* src, + const std::string& source_id, + bool sequence_mod); +void chromiumMediaSrcSetGroupStartTimestampIfInSequenceMode( + ChromiumMediaSrc* src, + const std::string& source_id, + const base::TimeDelta& timestamp_offset); +void chromiumMediaSrcRemoveSegment(ChromiumMediaSrc* src, + const std::string& source_id, + const base::TimeDelta& start, + const base::TimeDelta& end); + +G_END_DECLS + +#endif // CONTENT_MEDIA_GSTREAMER_GST_CHROMIUM_MEDIA_SRC_H_ diff --git a/content/media/gstreamer/media_player_gstreamer.cc b/content/media/gstreamer/media_player_gstreamer.cc new file mode 100644 index 0000000..29131a2 --- /dev/null +++ b/content/media/gstreamer/media_player_gstreamer.cc @@ -0,0 +1,802 @@ +// Copyright 2015 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 "content/media/gstreamer/media_player_gstreamer.h" + +#include +#include +#include +#include + +#include "base/bind.h" +#include "base/callback.h" +#include "base/callback_helpers.h" +#include "base/debug/alias.h" +#include "base/debug/crash_logging.h" +#include "base/process/process_handle.h" +#include "base/synchronization/waitable_event.h" +#include "base/trace_event/trace_event.h" +#include "content/child/child_process.h" +#include "content/child/child_thread_impl.h" +#include "content/child/resource_dispatcher.h" +#include "content/common/gpu/client/context_provider_command_buffer.h" +#include "content/common/gpu/client/gl_helper.h" +#include "content/common/media/media_channel.h" +#include "content/media/gstreamer/gst_chromium_http_source.h" +#include "content/media/gstreamer/gst_chromium_media_src.h" +#include "content/media/gstreamer/gst_chromium_common_encryption_decryptor.h" +#include "content/media/gstreamer/gpuprocess/client_egl.h" +#include "content/media/gstreamer/gpuprocess/gstglcontext_gpu_process.h" +#include "content/media/gstreamer/gpuprocess/gstgldisplay_gpu_process.h" +#include "content/media/media_child_thread.h" +#include "gpu/command_buffer/client/gles2_interface.h" +#include "gpu/command_buffer/client/gles2_lib.h" +#include "gpu/command_buffer/common/mailbox.h" +#include "gpu/command_buffer/common/mailbox_holder.h" +#include "media/base/bind_to_current_loop.h" +#include "media/base/limits.h" +#include "media/base/media_log.h" +#include "third_party/WebKit/public/web/WebView.h" +#include "ui/gfx/x/x11_types.h" + +namespace content { + +static void end_of_stream_cb(GstPlayer* player, + MediaPlayerGStreamer* media_player) { + DVLOG(1) << __FUNCTION__ << "(end of stream reached)"; + media_player->DidEOS(); +} + +static void error_cb(GstPlayer* player, + GError* err, + MediaPlayerGStreamer* media_player) { + DVLOG(1) << __FUNCTION__ << "(GStreamer error: " << err->message << ")"; + media_player->OnError(err->code); +} + +static void seek_done_cb(GstPlayer* player, + GstClockTime position, + MediaPlayerGStreamer* media_player) { + media_player->OnSeekDone(position); +} + +static void position_updated_cb(GstPlayer* player, + GstClockTime pos, + MediaPlayerGStreamer* media_player) { + media_player->OnPositionUpdated( + base::TimeDelta::FromMilliseconds(GST_TIME_AS_MSECONDS(pos))); +} + +static void state_changed_cb(GstPlayer* player, + GstPlayerState state, + MediaPlayerGStreamer* media_player) { + DVLOG(1) << __FUNCTION__ + << "(GstPlayer state: " << gst_player_state_get_name(state) << ")"; + + switch (state) { + case GST_PLAYER_STATE_PLAYING: + media_player->DidPlay(); + break; + case GST_PLAYER_STATE_PAUSED: + media_player->DidPause(); + break; + case GST_PLAYER_STATE_STOPPED: + media_player->DidStop(); + break; + case GST_PLAYER_STATE_BUFFERING: + media_player->DidLoad(); + break; + } +} + +static void duration_changed_cb(GstPlayer* player, + GstClockTime duration, + MediaPlayerGStreamer* media_player) { + DVLOG(1) << __FUNCTION__ << "(Duration changed: " << duration + << ")"; // TODO: use GST_TIME_FORMAT / GST_TIME_ARGS + media_player->OnDurationChanged( + base::TimeDelta::FromMilliseconds(GST_TIME_AS_MSECONDS(duration))); +} + +static void video_dimensions_changed_cb(GstPlayer* player, + int width, + int height, + MediaPlayerGStreamer* media_player) { + if (width > 0 && height > 0) { + DVLOG(1) << __FUNCTION__ << "(Video dimension changed: " << width << "x" + << height << ")"; + media_player->OnVideoSizeChanged(width, height); + } +} + +static void media_info_updated_cb(GstPlayer* player, + GstPlayerMediaInfo* info, + MediaPlayerGStreamer* media_player) { + media_player->OnMediaInfoUpdated(info); +} + +static void buffering_cb(GstPlayer* player, + gint percent, + MediaPlayerGStreamer* media_player) { + media_player->OnBufferingUpdated(percent); +} + +static void source_setup_cb(GstElement* playbin, + GstElement* src, + MediaPlayerGStreamer* player) { + player->GstSourceSetup(playbin, src); +} + +static void sync_bus_call(GstBus* bus, + GstMessage* msg, + MediaPlayerGStreamer* player) { + player->SyncMessage(bus, msg); +} + +static void async_bus_call(GstBus* bus, + GstMessage* msg, + MediaPlayerGStreamer* player) { + player->AsyncMessage(bus, msg); +} + +static GstGLContext* gstgldisplay_create_context_cb( + GstGLDisplay* display, + GstGLContext* other_context, + MediaPlayerGStreamer* player) { + return player->GstgldisplayCreateContextCallback(display, other_context); +} + +static gpointer gpu_process_proc_addr(GstGLAPI gl_api, const gchar* name) { + if (std::string(name).find("eglCreateImage") != std::string::npos) + return (gpointer)content::CreateEGLImageKHR; + else if (std::string(name).find("eglDestroyImage") != std::string::npos) + return (gpointer)content::DestroyEGLImageKHR; + else if (std::string(name).find("glEGLImageTargetTexture2D") != + std::string::npos) + return (gpointer)gles2::GetGLFunctionPointer("glBindTexImage2DCHROMIUM"); + else + return (gpointer)gles2::GetGLFunctionPointer(name); +} + +static gboolean glimagesink_draw_cb(GstElement* gl_sink, + GstGLContext* context, + GstSample* sample, + MediaPlayerGStreamer* player) { + return player->GlimagesinkDrawCallback(gl_sink, context, sample); +} + +MediaPlayerGStreamerFactory::MediaPlayerGStreamerFactory( + media::MediaLog* media_log, + content::ResourceDispatcher* resource_dispatcher, + scoped_refptr main_task_runner, + scoped_refptr gl_task_runner) + : media_log_(media_log), + resource_dispatcher_(resource_dispatcher), + main_task_runner_(main_task_runner), + gl_task_runner_(gl_task_runner) {} + +MediaPlayerGStreamerFactory::~MediaPlayerGStreamerFactory() {} + +MediaPlayerGStreamer* MediaPlayerGStreamerFactory::create( + int player_id, + content::MediaChannel* media_channel) { + return new MediaPlayerGStreamer(player_id, GURL(), media_channel, + media_log_.get(), resource_dispatcher_, + main_task_runner_, gl_task_runner_); +} + +MediaPlayerGStreamer::MediaPlayerGStreamer( + int player_id, + const GURL& url, + content::MediaChannel* media_channel, + media::MediaLog* media_log, + content::ResourceDispatcher* resource_dispatcher, + scoped_refptr main_task_runner, + scoped_refptr gl_task_runner) + : player_id_(player_id), + url_(url), + provider_(nullptr), + media_channel_(media_channel), + media_log_(media_log), + resource_dispatcher_(resource_dispatcher), + main_task_runner_(main_task_runner), + gl_task_runner_(gl_task_runner), + player_(gst_player_new(NULL, NULL)), // It calls gst_init. + media_source_(nullptr), + gst_gl_display_(nullptr), + gst_gl_context_(nullptr), + seek_time_(GST_CLOCK_TIME_NONE), + was_preroll_(false), + weak_factory_(this) { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + DVLOG(1) << __FUNCTION__ << "(Creating player)"; + + GstElementFactory* httpSrcFactory = + gst_element_factory_find("chromiumhttpsrc"); + if (!httpSrcFactory) { + gst_element_register(0, "chromiumhttpsrc", GST_RANK_PRIMARY + 100, + CHROMIUM_TYPE_HTTP_SRC); + } + + GstElementFactory* mediaSrcFactory = + gst_element_factory_find("chromiummediasrc"); + if (!mediaSrcFactory) { + gst_element_register(0, "chromiummediasrc", GST_RANK_PRIMARY + 100, + CHROMIUM_TYPE_MEDIA_SRC); + } + + GstElementFactory* cencDecryptorFactory = + gst_element_factory_find("chromiumcencdec"); + + if (!cencDecryptorFactory) { + gst_element_register(0, "chromiumcencdec", GST_RANK_PRIMARY + 100, + CHROMIUM_TYPE_MEDIA_CENC_DECRYPT); + } + + g_signal_connect(player_, "duration-changed", G_CALLBACK(duration_changed_cb), + this); + g_signal_connect(player_, "position-updated", G_CALLBACK(position_updated_cb), + this); + g_signal_connect(player_, "state-changed", G_CALLBACK(state_changed_cb), + this); + g_signal_connect(player_, "video-dimensions-changed", + G_CALLBACK(video_dimensions_changed_cb), this); + g_signal_connect(player_, "media-info-updated", + G_CALLBACK(media_info_updated_cb), this); + g_signal_connect(player_, "buffering", G_CALLBACK(buffering_cb), this); + g_signal_connect(player_, "end-of-stream", G_CALLBACK(end_of_stream_cb), + this); + g_signal_connect(player_, "error", G_CALLBACK(error_cb), this); + g_signal_connect(player_, "seek-done", G_CALLBACK(seek_done_cb), this); + + GstElement* pipeline = gst_player_get_pipeline(player_); + g_signal_connect(pipeline, "source-setup", G_CALLBACK(source_setup_cb), this); + + GstElement* glimagesink = gst_element_factory_make("glimagesink", NULL); + + if (!glimagesink) { + DVLOG(1) << __FUNCTION__ << "(Failed: glimagesink is required)"; + OnError(0); + return; + } + + g_object_set(G_OBJECT(pipeline), "video-sink", glimagesink, NULL); + g_signal_connect(G_OBJECT(glimagesink), "client-draw", + G_CALLBACK(glimagesink_draw_cb), this); + + GstBus* bus = gst_element_get_bus(pipeline); + gst_bus_enable_sync_message_emission(bus); + g_signal_connect(bus, "sync-message", G_CALLBACK(sync_bus_call), this); + g_signal_connect(bus, "message::element", G_CALLBACK(async_bus_call), this); + + gst_object_unref(bus); + gst_object_unref(pipeline); + + provider_ = static_cast(MediaChildThread::current()) + ->CreateSharedContextProvider(); + + if (!provider_) { + LOG(ERROR) << __FUNCTION__ << "(Failed to create context provider)"; + OnError(0); + } +} + +MediaPlayerGStreamer::~MediaPlayerGStreamer() { + // TODO: release provider and call ::gles2::Terminate(); from gl thread. + DCHECK(main_task_runner_->BelongsToCurrentThread()); + DVLOG(1) << __FUNCTION__ << "(Releasing GstPlayer)"; + + // 1. Clean up samples first. + { + std::unique_lock gl_thread_lock(gl_thread_mutex_); + gl_task_runner_->PostTask(FROM_HERE, + base::Bind(&MediaPlayerGStreamer::CleanupSamples, + weak_factory_.GetWeakPtr())); + gl_thread_condition_.wait(gl_thread_lock); + } + + // 2. Destroy the pipeline. Note that some cleanup callbacks will be called later in this task runner. + gst_object_unref(player_); + player_ = nullptr; + + if (media_source_) { + gst_object_unref(media_source_); + media_source_ = nullptr; + } + + // 3. Clean up the context finally. + { + std::unique_lock gl_thread_lock(gl_thread_mutex_); + gl_task_runner_->PostTask(FROM_HERE, + base::Bind(&MediaPlayerGStreamer::CleanupGLContext, + weak_factory_.GetWeakPtr())); + gl_thread_condition_.wait(gl_thread_lock); + } + + DVLOG(1) << __FUNCTION__ << "(GstPlayer release)"; +} + +void MediaPlayerGStreamer::SetupContextProvider() { + DVLOG(1) << __FUNCTION__ << "(Set up GstGL context)"; + + { + std::unique_lock lock(gl_thread_mutex_); + + gl_task_runner_->PostTask(FROM_HERE, + base::Bind(&MediaPlayerGStreamer::SetupGLContext, + weak_factory_.GetWeakPtr())); + + gl_thread_condition_.wait(lock); + } + + if (!gst_gl_context_) { + LOG(ERROR) << __FUNCTION__ << "(Failed to create GstGL context)"; + OnError(0); + } +} + +void MediaPlayerGStreamer::SetupGLContext() { + bool ret = false; + + { + std::lock_guard lock(gl_thread_mutex_); + + DVLOG(1) << __FUNCTION__ << "(Setting up GstGL)"; + + ret = provider_->ContextSupport(); + + if (!ret) { + if (provider_->BindToCurrentThread()) { + gpu::gles2::GLES2Interface* gles2_ctx = provider_->ContextGL(); + + ::gles2::Initialize(); + ::gles2::SetGLContext(gles2_ctx); + ret = true; + } + } + + if (ret) { + // CLear old errors. + gpu::gles2::GLES2Interface* gl = ::gles2::GetGLContext(); + gl->GetError(); + + ret = content::ClientEGL_SetupCommandBufferProxy(); + } + + if (ret) { + gst_gl_display_ = + reinterpret_cast(gst_gl_display_gpu_process_new()); + + g_signal_connect(G_OBJECT(gst_gl_display_), "create-context", + G_CALLBACK(gstgldisplay_create_context_cb), this); + + gst_gl_context_ = gst_gl_context_gpu_process_new( + gst_gl_display_, GST_GL_API_GLES2, + (GstGLProcAddrFunc)gpu_process_proc_addr); + } + } + + if (!ret) + LOG(ERROR) << __FUNCTION__ << "(Failed to setup gl context)"; + + gl_thread_condition_.notify_one(); +} + +void MediaPlayerGStreamer::CleanupSamples() { + { + std::lock_guard lock(gl_thread_mutex_); + + DVLOG(1) << __FUNCTION__ << "(Cleaning samples)"; + + for (GstSampleMap::iterator iter = samples_.begin(); iter != samples_.end(); + ++iter) { + GstSample* sample = iter->second; + if (sample) { + iter->second = nullptr; + gst_sample_unref(sample); + } + } + } + + gl_thread_condition_.notify_one(); +} + +void MediaPlayerGStreamer::CleanupGLContext() { + { + std::lock_guard lock(gl_thread_mutex_); + + DVLOG(1) << __FUNCTION__ << "(Cleaning GstGL)"; + + if (gst_gl_context_) { + gst_object_unref(gst_gl_context_); + gst_gl_context_ = nullptr; + } + } + + gl_thread_condition_.notify_one(); +} + +void MediaPlayerGStreamer::GstSourceSetup(GstElement* playbin, + GstElement* src) { + if (url_.SchemeIs("mediasourceblob")) { + DCHECK(!media_source_); + media_source_ = GST_ELEMENT(gst_object_ref(src)); + media_channel_->SendSourceSelected(player_id_); + } else if (url_.SchemeIs(url::kDataScheme)) { + DVLOG(1) << __FUNCTION__ << "data url: " << url_.spec().c_str(); + // nothing todo + } else if (url_.SchemeIsHTTPOrHTTPS() || url_.SchemeIsBlob() || + url_.SchemeIsFile()) { + // nothing todo + } else { + DVLOG(1) << __FUNCTION__ << "(Restricted protocol: << " << url_.spec() + << ")"; + OnError(0); + } +} + +void MediaPlayerGStreamer::SyncMessage(GstBus* bus, GstMessage* msg) { + switch (GST_MESSAGE_TYPE(msg)) { + case GST_MESSAGE_ASYNC_DONE: { + was_preroll_ = true; + } break; + case GST_MESSAGE_NEED_CONTEXT: { + const gchar* context_type = NULL; + gst_message_parse_context_type(msg, &context_type); + + DVLOG(1) << __FUNCTION__ << "(Need context: " << context_type << ")"; + + if (gst_gl_display_ && + g_strcmp0(context_type, GST_GL_DISPLAY_CONTEXT_TYPE) == 0) { + GstContext* display_context = + gst_context_new(GST_GL_DISPLAY_CONTEXT_TYPE, TRUE); + gst_context_set_gl_display(display_context, gst_gl_display_); + gst_element_set_context(GST_ELEMENT(msg->src), display_context); + gst_object_unref(gst_gl_display_); + gst_gl_display_ = nullptr; + } + break; + } + default: + break; + } +} + +void MediaPlayerGStreamer::AsyncMessage(GstBus* bus, GstMessage* msg) { + switch (GST_MESSAGE_TYPE(msg)) { + case GST_MESSAGE_ELEMENT: { + const GstStructure* structure = gst_message_get_structure(msg); + if (gst_structure_has_name(structure, "drm-key-needed")) { + DVLOG(1) << __FUNCTION__ << "(drm-key-needed)"; + + GstBuffer* data; + const char* keySystemId; + gboolean valid = gst_structure_get( + structure, "data", GST_TYPE_BUFFER, &data, "key-system-id", + G_TYPE_STRING, &keySystemId, nullptr); + GstMapInfo mapInfo; + if (!valid || !gst_buffer_map(data, &mapInfo, GST_MAP_READ)) + break; + + DVLOG(1) << __FUNCTION__ << "(Need key: " << keySystemId << ")"; + media_channel_->SendNeedKey( + player_id_, keySystemId, + std::vector(mapInfo.data, + mapInfo.data + mapInfo.size)); + gst_buffer_unmap(data, &mapInfo); + } + break; + } + default: + break; + } +} + +void MediaPlayerGStreamer::DoReleaseTexture(unsigned texture_id) { + GstSampleMap::iterator iter = samples_.find(texture_id); + if (iter != samples_.end()) { + GstSample* sample = iter->second; + if (sample) { + DVLOG(1) << __FUNCTION__ << "(Releasing texture id: " << texture_id + << ")"; + iter->second = nullptr; + gst_sample_unref(sample); + } + } +} + +GstGLContext* MediaPlayerGStreamer::GstgldisplayCreateContextCallback( + GstGLDisplay* display, + GstGLContext* other_context) { + return gst_gl_context_; +} + +bool MediaPlayerGStreamer::GlimagesinkDrawCallback(GstElement* sink, + GstGLContext* context, + GstSample* sample) { + GstVideoFrame v_frame; + GstVideoInfo v_info; + guint texture_id = 0; + GstBuffer* buf = gst_sample_get_buffer(sample); + GstCaps* caps = gst_sample_get_caps(sample); + guint target = 0; + + gst_video_info_from_caps(&v_info, caps); + + if (!gst_video_frame_map(&v_frame, &v_info, buf, + (GstMapFlags)(GST_MAP_READ | GST_MAP_GL))) { + LOG(ERROR) << __FUNCTION__ << "(Failed to map GstGL buffer)"; + OnError(0); + // Here the return value means that the callback has been processed. + return true; + } + + texture_id = *(guint*)v_frame.data[0]; + + if (texture_id == 0) { + DVLOG(1) << __FUNCTION__ << "(Wrong texture id: 0)"; + OnError(0); + // Here the return value means that the callback has been processed. + return true; + } + + DVLOG(1) << __FUNCTION__ << "(Using texture id: " << texture_id << ")"; + + if (was_preroll_ && samples_[texture_id]) { + was_preroll_ = false; + gst_video_frame_unmap(&v_frame); + return true; + } + + DCHECK(samples_[texture_id] == 0); + + samples_[texture_id] = gst_sample_ref(sample); + + target = gst_gl_texture_target_to_gl(gst_gl_memory_get_texture_target(GST_GL_MEMORY_CAST(gst_buffer_peek_memory(buf, 0)))); + + gpu::gles2::GLES2Interface* gl = ::gles2::GetGLContext(); + + gpu::Mailbox mailbox; + gl->GenMailboxCHROMIUM(mailbox.name); + gl->ProduceTextureDirectCHROMIUM(texture_id, target, mailbox.name); + gl->Flush(); + + std::vector name(mailbox.name, + mailbox.name + GL_MAILBOX_SIZE_CHROMIUM); + + // TODO: use ubercompositor to avoid send the texture id to renderer process. + // MediaPlayerGStreamer needs to inherit from cc::VideoFrameProvider and + // register a cc::VideoLayer. + // In other words, media::VideoFrame::WrapNativeTexture has to be created + // here. + media_channel_->SendSetCurrentFrame(player_id_, v_info.width, v_info.height, + texture_id, target, name); + + gst_video_frame_unmap(&v_frame); + + return true; +} + +void MediaPlayerGStreamer::Load(GURL url, unsigned position_update_interval_ms) { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + if (!gst_gl_context_) + SetupContextProvider(); + + if (!gst_gl_context_) + return; + + url_ = url; + gst_player_set_uri(player_, url_.spec().c_str()); + gst_player_set_position_update_interval (player_, position_update_interval_ms); + gst_player_pause(player_); +} + +void MediaPlayerGStreamer::Play() { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + gst_player_play(player_); +} + +void MediaPlayerGStreamer::Pause() { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + gst_player_pause(player_); +} + +void MediaPlayerGStreamer::Seek(const base::TimeDelta& delta) { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + GstClockTime seek_time = delta.InMicroseconds() * 1000; + if (GST_CLOCK_TIME_IS_VALID(seek_time)) { + gst_player_seek(player_, seek_time); + } +} + +void MediaPlayerGStreamer::ReleaseTexture(unsigned texture_id) { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + gl_task_runner_->PostTask(FROM_HERE, + base::Bind(&MediaPlayerGStreamer::DoReleaseTexture, + weak_factory_.GetWeakPtr(), texture_id)); +} + +void MediaPlayerGStreamer::AddSourceId(const std::string& source_id, + const std::string& type, + const std::vector& codecs) { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + DCHECK(media_source_); + + blink::WebMediaSource::AddStatus ret = chromiumMediaSrcAddSourceBufferId( + CHROMIUM_MEDIA_SRC(media_source_), source_id, type, codecs); + + if (ret == blink::WebMediaSource::AddStatusOk) + media_channel_->SendDidAddSourceId(player_id_, source_id); + else + media_channel_->SendDidAddSourceId(player_id_, ""); +} + +void MediaPlayerGStreamer::RemoveSourceId(const std::string& source_id) { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + DCHECK(media_source_); + + chromiumMediaSrcRemoveSourceBufferId(CHROMIUM_MEDIA_SRC(media_source_), + source_id); + + media_channel_->SendDidRemoveSourceId(player_id_, source_id); +} + +void MediaPlayerGStreamer::SetDuration(const base::TimeDelta& duration) { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + DCHECK(media_source_); + chromiumMediaSrcSetDuration(CHROMIUM_MEDIA_SRC(media_source_), duration); +} + +void MediaPlayerGStreamer::MarkEndOfStream() { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + DCHECK(media_source_); + chromiumMediaSrcMarkEndOfStream(CHROMIUM_MEDIA_SRC(media_source_)); +} + +void MediaPlayerGStreamer::UnmarkEndOfStream() { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + DCHECK(media_source_); + chromiumMediaSrcUnmarkEndOfStream(CHROMIUM_MEDIA_SRC(media_source_)); +} + +void MediaPlayerGStreamer::SetSequenceMode(const std::string& source_id, + bool sequence_mode) { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + DCHECK(media_source_); + chromiumMediaSrcSetSequenceMode(CHROMIUM_MEDIA_SRC(media_source_), source_id, + sequence_mode); +} + +void MediaPlayerGStreamer::AppendData( + const std::string& source_id, + const std::vector& data, + const std::vector& times) { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + DCHECK(media_source_); + + base::TimeDelta timestamp_offset; + chromiumMediaSrcAppendData(CHROMIUM_MEDIA_SRC(media_source_), source_id, data, + times, timestamp_offset); + + media_channel_->SendTimestampOffsetUpdate(player_id_, source_id, + timestamp_offset); +} + +void MediaPlayerGStreamer::Abort(const std::string& source_id) { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + DCHECK(media_source_); + chromiumMediaSrcAbort(CHROMIUM_MEDIA_SRC(media_source_), source_id); +} + +void MediaPlayerGStreamer::SetGroupStartTimestampIfInSequenceMode( + const std::string& source_id, + const base::TimeDelta& timestamp_offset) { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + DCHECK(media_source_); + chromiumMediaSrcSetGroupStartTimestampIfInSequenceMode( + CHROMIUM_MEDIA_SRC(media_source_), source_id, timestamp_offset); +} + +void MediaPlayerGStreamer::RemoveSegment(const std::string& source_id, + const base::TimeDelta& start, + const base::TimeDelta& end) { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + DCHECK(media_source_); + chromiumMediaSrcRemoveSegment(CHROMIUM_MEDIA_SRC(media_source_), source_id, + start, end); +} + +void MediaPlayerGStreamer::AddKey(const std::string& session_id, + const std::string& key_id, + const std::string& key) { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + chromiumCommonEncryptionDecryptAddKey(gst_player_get_pipeline(player_), key); +} + +void MediaPlayerGStreamer::OnDurationChanged(const base::TimeDelta& duration) { + media_channel_->SendMediaDurationChanged(player_id_, duration); +} + +void MediaPlayerGStreamer::OnVideoSizeChanged(int width, int height) { + media_channel_->SendMediaVideoSizeChanged(player_id_, width, height); +} + +void MediaPlayerGStreamer::OnMediaInfoUpdated(GstPlayerMediaInfo* media_info) { + for (GList* list = gst_player_media_info_get_stream_list(media_info); + list != NULL; list = list->next) { + GstPlayerStreamInfo* stream = (GstPlayerStreamInfo*)list->data; + const gchar* codec = gst_player_stream_info_get_codec(stream); + if (media_source_) { + std::string source_id; + chromiumMediaSrcIsMatchingSourceId( + CHROMIUM_MEDIA_SRC(media_source_), + gst_player_stream_info_get_stream_type(stream), codec, source_id); + if (!source_id.empty()) + InitSegmentReceived(source_id); + } + } +} + +void MediaPlayerGStreamer::DidLoad() {} + +void MediaPlayerGStreamer::DidPlay() { + VLOG(1) << __FUNCTION__ << "Media player GStreamer did play"; + media_channel_->SendDidMediaPlayerPlay(player_id_); +} + +void MediaPlayerGStreamer::DidPause() { + media_channel_->SendDidMediaPlayerPause(player_id_); +} + +void MediaPlayerGStreamer::DidSeek(const base::TimeDelta& delta) { + media_channel_->SendSeekCompleted(player_id_, delta); +} + +void MediaPlayerGStreamer::DidEOS() { + media_channel_->SendMediaPlaybackCompleted(player_id_); + + DVLOG(1) << "Media player GStreamer EOS"; +} + +void MediaPlayerGStreamer::DidStop() { + DVLOG(1) << "Media player GStreamer stopped"; +} + +void MediaPlayerGStreamer::InitSegmentReceived(const std::string& source_id) { + media_channel_->SendInitSegmentReceived(player_id_, source_id); +} + +// TODO: ranges is the duration of the data that had been append. +// We should have a GstMessage each time a new chunck is parsed. +void MediaPlayerGStreamer::BufferedRangeUpdate( + const std::string& source_id, + const std::vector& ranges) { + media_channel_->SendBufferedRangeUpdate(player_id_, source_id, ranges); +} + +void MediaPlayerGStreamer::OnPositionUpdated(base::TimeDelta position) { + media_channel_->SendMediaTimeUpdate(player_id_, position, + base::TimeTicks::Now()); +} + +void MediaPlayerGStreamer::OnBufferingUpdated(int percent) { + media_channel_->SendBufferingUpdate(player_id_, percent); +} + +void MediaPlayerGStreamer::OnError(int error) { + media_channel_->SendMediaError(player_id_, error); +} + +void MediaPlayerGStreamer::OnSeekDone(GstClockTime position) { + if (GST_CLOCK_TIME_IS_VALID(position)) { + DidSeek(base::TimeDelta::FromMilliseconds(GST_TIME_AS_MSECONDS(position))); + } +} + +} // namespace media diff --git a/content/media/gstreamer/media_player_gstreamer.h b/content/media/gstreamer/media_player_gstreamer.h new file mode 100644 index 0000000..82e866b --- /dev/null +++ b/content/media/gstreamer/media_player_gstreamer.h @@ -0,0 +1,190 @@ +// Copyright 2015 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. + +#ifndef CONTENT_MEDIA_GSTREAMER_MEDIAPLAYER_GSTREAMER_H_ +#define CONTENT_MEDIA_GSTREAMER_MEDIAPLAYER_GSTREAMER_H_ + +#include +#include +#include +#include + +#include "base/basictypes.h" +#include "base/containers/hash_tables.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/threading/thread_checker.h" +#include "base/time/default_tick_clock.h" +#include "base/time/time.h" +#include "content/common/media/media_channel_filter.h" +#include "content/media/gstreamer/gst_chromium_http_source.h" +#include "content/renderer/media/render_media_log.h" +#include "media/base/time_delta_interpolator.h" +#include "media/blink/buffered_data_source.h" +#include "media/blink/buffered_data_source_host_impl.h" +#include "third_party/WebKit/public/web/WebLocalFrame.h" +#include "third_party/WebKit/public/web/WebFrameClient.h" +#include "ui/gfx/geometry/rect_f.h" + +#include + +struct _GstGLContext; +struct _GstGLDisplay; +typedef struct _GstGLContext GstGLContext; +typedef struct _GstGLDisplay GstGLDisplay; + +namespace cc { +class ContextProvider; +} + +namespace media { +class MediaLog; +} + +namespace content { + +struct RunHolder; +class MediaPlayerGStreamer; +class MediaChannel; +class ResourceDispatcher; +class GStreamerBufferedDataSourceFactory; + +class MediaPlayerGStreamerFactory { + public: + MediaPlayerGStreamerFactory( + media::MediaLog* media_log, + content::ResourceDispatcher* resource_dispatcher, + scoped_refptr main_task_runner, + scoped_refptr gl_task_runner); + + ~MediaPlayerGStreamerFactory(); + + MediaPlayerGStreamer* create(int player_id, + content::MediaChannel* media_channel); + + private: + scoped_refptr media_log_; + ResourceDispatcher* resource_dispatcher_; + scoped_refptr main_task_runner_; + scoped_refptr gl_task_runner_; +}; + +// TODO: use ubercompositor and inherits from cc::VideoFrameProvider. +class MediaPlayerGStreamer { + public: + MediaPlayerGStreamer( + int player_id, + const GURL& url, + content::MediaChannel* media_channel, + media::MediaLog* media_log, + content::ResourceDispatcher* resource_dispatcher, + scoped_refptr main_task_runner, + scoped_refptr gl_task_runner); + + virtual ~MediaPlayerGStreamer(); + + void Load(GURL url, unsigned position_update_interval_ms); + void Play(); + void Pause(); + void Seek(const base::TimeDelta& duration); + void ReleaseTexture(unsigned texture_id); + void AddSourceId(const std::string& source_id, + const std::string& type, + const std::vector& codecs); + void RemoveSourceId(const std::string& source_id); + void SetDuration(const base::TimeDelta& duration); + void MarkEndOfStream(); + void UnmarkEndOfStream(); + void SetSequenceMode(const std::string& source_id, bool sequence_mode); + void AppendData(const std::string& source_id, + const std::vector& data, + const std::vector& times); + void Abort(const std::string& source_id); + void SetGroupStartTimestampIfInSequenceMode( + const std::string& source_id, + const base::TimeDelta& timestamp_offset); + void RemoveSegment(const std::string& source_id, + const base::TimeDelta& start, + const base::TimeDelta& end); + void AddKey(const std::string& session_id, + const std::string& key_id, + const std::string& key); + + void DidLoad(); + void OnDurationChanged(const base::TimeDelta& duration); + void OnVideoSizeChanged(int width, int height); + void OnMediaInfoUpdated(GstPlayerMediaInfo* info); + void DidPlay(); + void DidPause(); + void DidSeek(const base::TimeDelta& delta); + void DidEOS(); + void DidStop(); + void InitSegmentReceived(const std::string& source_id); + void BufferedRangeUpdate(const std::string& source_id, + const std::vector& ranges); + void OnPositionUpdated(base::TimeDelta position); + void OnBufferingUpdated(int percent); + void OnError(int error); + void OnSeekDone(GstClockTime position); + + void DoReleaseTexture(unsigned texture_id); + + GstGLContext* GstgldisplayCreateContextCallback(GstGLDisplay* display, + GstGLContext* other_context); + bool GlimagesinkDrawCallback(GstElement* sink, + GstGLContext* context, + GstSample* sample); + void GstSourceSetup(GstElement* playbin, GstElement* src); + void SyncMessage(GstBus* bus, GstMessage* msg); + void AsyncMessage(GstBus* bus, GstMessage* msg); + + private: + void SetupContextProvider(); + void SetupGLContext(); + void CleanupSamples(); + void CleanupGLContext(); + void ExecuteInGLThread(RunHolder* holder); + + void DataSourceInitialized(bool success); + void NotifyDownloading(bool is_downloading); + + const int player_id_; + + GURL url_; + + scoped_refptr provider_; + + content::MediaChannel* media_channel_; + + scoped_refptr media_log_; + media::BufferedDataSourceHostImpl buffered_data_source_host_; + + blink::WebLocalFrame* web_frame_; + + ResourceDispatcher* resource_dispatcher_; + + scoped_refptr main_task_runner_; + scoped_refptr gl_task_runner_; + + GstPlayer* player_; + GstElement* media_source_; + GstGLDisplay* gst_gl_display_; + GstGLContext* gst_gl_context_; + + std::mutex gl_thread_mutex_; + std::condition_variable gl_thread_condition_; + + GstClockTime seek_time_; + + typedef base::hash_map GstSampleMap; + GstSampleMap samples_; + bool was_preroll_; + + base::WeakPtrFactory weak_factory_; +}; + +} // namespace content + +#endif // CONTENT_MEDIA_GSTREAMER_MEDIAPLAYER_GSTREAMER_H_ diff --git a/content/media/in_process_media_thread.cc b/content/media/in_process_media_thread.cc new file mode 100644 index 0000000..40af8fa --- /dev/null +++ b/content/media/in_process_media_thread.cc @@ -0,0 +1,39 @@ +// Copyright 2015 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 "content/media/in_process_media_thread.h" + +#include "content/media/media_child_thread.h" +#include "content/media/media_process.h" + +namespace content { + +InProcessMediaThread::InProcessMediaThread( + const InProcessChildThreadParams& params) + : base::Thread("Chrome_InProcMediaThread"), + params_(params), + media_process_(NULL) {} + +InProcessMediaThread::~InProcessMediaThread() { + Stop(); +} + +void InProcessMediaThread::Init() { + media_process_ = new MediaProcess(); + // The process object takes ownership of the thread object, so do not + // save and delete the pointer. + media_process_->set_main_thread(new MediaChildThread(params_)); +} + +void InProcessMediaThread::CleanUp() { + SetThreadWasQuitProperly(true); + delete media_process_; +} + +base::Thread* CreateInProcessMediaThread( + const InProcessChildThreadParams& params) { + return new InProcessMediaThread(params); +} + +} // namespace content diff --git a/content/media/in_process_media_thread.h b/content/media/in_process_media_thread.h new file mode 100644 index 0000000..14ed352 --- /dev/null +++ b/content/media/in_process_media_thread.h @@ -0,0 +1,42 @@ +// Copyright 2015 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. + +#ifndef CONTENT_MEDIA_IN_PROCESS_MEDIA_THREAD_H_ +#define CONTENT_MEDIA_IN_PROCESS_MEDIA_THREAD_H_ + +#include "base/threading/thread.h" +#include "content/common/content_export.h" +#include "content/common/content_export.h" +#include "content/common/in_process_child_thread_params.h" + +namespace content { + +class MediaProcess; + +// This class creates a media thread (instead of a media process), when running +// with --in-process-media or --single-process. +class InProcessMediaThread : public base::Thread { + public: + InProcessMediaThread(const InProcessChildThreadParams& params); + ~InProcessMediaThread() override; + + protected: + void Init() override; + void CleanUp() override; + + private: + InProcessChildThreadParams params_; + + // Deleted in CleanUp() on the media thread, so don't use smart pointers. + MediaProcess* media_process_; + + DISALLOW_COPY_AND_ASSIGN(InProcessMediaThread); +}; + +CONTENT_EXPORT base::Thread* CreateInProcessMediaThread( + const InProcessChildThreadParams& params); + +} // namespace content + +#endif // CONTENT_MEDIA_IN_PROCESS_MEDIA_THREAD_H_ diff --git a/content/media/media_child_thread.cc b/content/media/media_child_thread.cc new file mode 100644 index 0000000..6539e10 --- /dev/null +++ b/content/media/media_child_thread.cc @@ -0,0 +1,330 @@ +// Copyright (c) 2015 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 "content/media/media_child_thread.h" + +#include + +#include "base/bind.h" +#include "base/debug/stack_trace.h" +#include "base/lazy_instance.h" +#include "base/message_loop/message_pump_glib.h" +#include "base/threading/simple_thread.h" +#include "base/threading/thread_local.h" +#include "base/threading/thread_restrictions.h" +#include "base/threading/worker_pool.h" +#include "build/build_config.h" +#include "content/child/child_gpu_memory_buffer_manager.h" +#include "content/child/child_process.h" +#include "content/child/thread_safe_sender.h" +#include "content/common/gpu/client/context_provider_command_buffer.h" +#include "content/common/gpu/client/gpu_channel_host.h" +#include "content/common/gpu/gpu_messages.h" +#include "content/common/gpu/gpu_process_launch_causes.h" +#include "content/common/media/media_messages.h" +#include "content/media/gstreamer/media_player_gstreamer.h" +#include "content/public/common/content_client.h" +#include "content/public/common/content_switches.h" +#include "gin/public/debug.h" +#include "gpu/GLES2/gl2extchromium.h" +#include "gpu/command_buffer/common/gles2_cmd_utils.h" +#include "ipc/ipc_channel_handle.h" +#include "ipc/ipc_sync_message_filter.h" +#include "media/base/audio_hardware_config.h" +#include "media/base/media.h" +#include "public/web/WebKit.h" + +using base::ThreadRestrictions; + +namespace content { +namespace { + +blink::WebGraphicsContext3D::Attributes GetOffscreenAttribs() { + blink::WebGraphicsContext3D::Attributes attributes; + attributes.shareResources = true; + attributes.depth = false; + attributes.stencil = false; + attributes.antialias = false; + attributes.noAutomaticFlushes = true; + return attributes; +} + +static base::LazyInstance> + g_thread_safe_sender = LAZY_INSTANCE_INITIALIZER; + +bool MediaProcessLogMessageHandler(int severity, + const char* file, + int line, + size_t message_start, + const std::string& str) { + std::string header = str.substr(0, message_start); + std::string message = str.substr(message_start); + + g_thread_safe_sender.Get()->Send( + new MediaHostMsg_OnLogMessage(severity, header, message)); + + return false; +} + +ChildThreadImpl::Options GetOptions() { + ChildThreadImpl::Options::Builder builder; + + return builder.Build(); +} + +} // namespace + +MediaChildThread::MediaChildThread(bool dead_on_arrival, + const DeferredMessages& deferred_messages) + : ChildThreadImpl(GetOptions()), + dead_on_arrival_(dead_on_arrival), + deferred_messages_(deferred_messages), + in_browser_process_(false), + blink_platform_(new content::BlinkPlatformImpl) { + g_thread_safe_sender.Get() = thread_safe_sender(); + blink::initializeWithoutV8(blink_platform_.get()); +} + +MediaChildThread::MediaChildThread(const InProcessChildThreadParams& params) + : ChildThreadImpl(ChildThreadImpl::Options::Builder() + .InBrowserProcess(params) + .Build()), + dead_on_arrival_(false), + in_browser_process_(true), + blink_platform_(nullptr) { + DCHECK(base::CommandLine::ForCurrentProcess()->HasSwitch( + switches::kSingleProcess) || + base::CommandLine::ForCurrentProcess()->HasSwitch( + switches::kInProcessMedia)); + g_thread_safe_sender.Get() = thread_safe_sender(); +} + +MediaChildThread::~MediaChildThread() +{ + if (blink_platform_) + blink::shutdownWithoutV8(); +} + +void MediaChildThread::Shutdown() { + ChildThreadImpl::Shutdown(); + logging::SetLogMessageHandler(NULL); + + gl_thread_.reset(); + + if (gpu_channel_.get()) + gpu_channel_->DestroyChannel(); +} + +void MediaChildThread::Init(const base::Time& process_start_time) { + process_start_time_ = process_start_time; +} + +bool MediaChildThread::Send(IPC::Message* msg) { + // The media process must never send a synchronous IPC message to the browser + // process. This could result in deadlock. + DCHECK(!msg->is_sync() || msg->type() == GpuHostMsg_EstablishGpuChannel::ID); + return ChildThreadImpl::Send(msg); +} + +bool MediaChildThread::OnControlMessageReceived(const IPC::Message& msg) { + // Call from the main thread, not the io thread. + + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(MediaChildThread, msg) + IPC_MESSAGE_HANDLER(MediaMsg_Initialize, OnInitialize) + IPC_MESSAGE_HANDLER(MediaMsg_Clean, OnClean) + IPC_MESSAGE_HANDLER(MediaMsg_Crash, OnCrash) + IPC_MESSAGE_HANDLER(MediaMsg_Hang, OnHang) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + + if (handled) + return true; + + return media_channel_filter_.get() && + media_channel_filter_->OnMessageReceived(msg); +} + +scoped_refptr +MediaChildThread::CreateSharedContextProvider() { + // Created in the main thread but likely to be bind in another thread. + DCHECK(IsMainThread()); + + if (!provider_.get() || provider_->DestroyedOnMainThread()) { + provider_ = NULL; + + if (!provider_.get()) { + if (!gpu_channel_.get()) + EstablishGpuChannelSync( + CAUSE_FOR_GPU_LAUNCH_MEDIA_GSTREAMER_CONTEXT_INITIALIZE); + + provider_ = ContextProviderCommandBuffer::Create( + CreateOffscreenContext3d(), MEDIA_GSTREAMER_CONTEXT); + } + } + + return provider_; +} + +GpuChannelHost* MediaChildThread::EstablishGpuChannelSync( + CauseForGpuLaunch cause_for_gpu_launch) { + TRACE_EVENT0("media", "MediaChildThread::EstablishGpuChannelSync"); + + if (gpu_channel_.get()) { + // Do nothing if we already have a GPU channel or are already + // establishing one. + if (!gpu_channel_->IsLost()) + return gpu_channel_.get(); + + // Recreate the channel if it has been lost. + gpu_channel_->DestroyChannel(); + gpu_channel_ = NULL; + } + + // Ask the browser for the channel name. + int client_id = 0; + IPC::ChannelHandle channel_handle; + gpu::GPUInfo gpu_info; + if (!Send(new GpuHostMsg_EstablishGpuChannel(cause_for_gpu_launch, &client_id, + &channel_handle, &gpu_info)) || +#if defined(OS_POSIX) + channel_handle.socket.fd == -1 || +#endif + channel_handle.name.empty()) { + // Otherwise cancel the connection. + return NULL; + } + + GetContentClient()->SetGpuInfo(gpu_info); + + io_thread_task_runner_ = ChildProcess::current()->io_task_runner(); + + gpu_channel_ = GpuChannelHost::Create( + this, gpu_info, channel_handle, + ChildProcess::current()->GetShutDownEvent(), gpu_memory_buffer_manager()); + return gpu_channel_.get(); +} + +scoped_ptr +MediaChildThread::CreateOffscreenContext3d() { + blink::WebGraphicsContext3D::Attributes attributes(GetOffscreenAttribs()); + bool lose_context_when_out_of_memory = true; + return make_scoped_ptr( + WebGraphicsContext3DCommandBufferImpl::CreateOffscreenContext( + gpu_channel_.get(), attributes, lose_context_when_out_of_memory, + GURL("chrome://gpu/MediaChildThread::CreateOffscreenContext3d"), + WebGraphicsContext3DCommandBufferImpl::SharedMemoryLimits(), NULL)); +} + +bool MediaChildThread::IsMainThread() { + return !!current(); +} + +scoped_refptr +MediaChildThread::GetIOThreadTaskRunner() { + return io_thread_task_runner_; +} + +scoped_ptr MediaChildThread::AllocateSharedMemory( + size_t size) { + return scoped_ptr( + ChildThreadImpl::AllocateSharedMemory(size, thread_safe_sender())); +} + +CreateCommandBufferResult MediaChildThread::CreateViewCommandBuffer( + int32 surface_id, + const GPUCreateCommandBufferConfig& init_params, + int32 route_id) { + TRACE_EVENT1("media", "MediaChildThread::CreateViewCommandBuffer", + "surface_id", surface_id); + + CreateCommandBufferResult result = CREATE_COMMAND_BUFFER_FAILED; + IPC::Message* message = new GpuHostMsg_CreateViewCommandBuffer( + surface_id, init_params, route_id, &result); + + // Allow calling this from the compositor thread. + thread_safe_sender()->Send(message); + + return result; +} + +class MessagePumpGlibLocal : public base::MessagePumpGlib { + public: + explicit MessagePumpGlibLocal(GMainContext* context) : context_(context) {} + ~MessagePumpGlibLocal() override { + g_main_context_pop_thread_default(context_); + } + + private: + GMainContext* context_; +}; + +static scoped_ptr CreateMessagePumpGlibLocal() { + GMainContext* context = g_main_context_new(); + g_main_context_push_thread_default(context); + return scoped_ptr(new MessagePumpGlibLocal(context)); +} + +void MediaChildThread::OnInitialize() { + DCHECK_NE(ChildProcess::current()->io_task_runner(), + base::ThreadTaskRunnerHandle::Get().get()); + VLOG(1) << "Media: Initializing"; + + Send(new MediaHostMsg_Initialized(!dead_on_arrival_)); + while (!deferred_messages_.empty()) { + Send(deferred_messages_.front()); + deferred_messages_.pop(); + } + + if (dead_on_arrival_) { + LOG(ERROR) << "Exiting media process due to errors during initialization"; + base::MessageLoop::current()->Quit(); + return; + } + + // We don't need to pipe log messages if we are running the GPU thread in + // the browser process. + if (!in_browser_process_) + logging::SetLogMessageHandler(MediaProcessLogMessageHandler); + + gl_thread_.reset(new base::Thread("GLThread")); + base::Thread::Options options; + options.message_pump_factory = base::Bind(&CreateMessagePumpGlibLocal); + + gl_thread_->StartWithOptions(options); + gl_task_runner_ = gl_thread_->task_runner(); + + scoped_refptr media_log( + new media::MediaLog()); // TODO: new RenderMediaLog + + GStreamerBufferedDataSourceFactory::Init(media_log.get(), resource_dispatcher(), + base::ThreadTaskRunnerHandle::Get()); + + media_channel_filter_ = new MediaChannelFilter( + GetRouter(), ChildProcess::current()->io_task_runner(), + ChildProcess::current()->GetShutDownEvent(), channel(), + new MediaPlayerGStreamerFactory(media_log.get(), resource_dispatcher(), + base::ThreadTaskRunnerHandle::Get(), + gl_task_runner_)); +} + +void MediaChildThread::OnClean() { + VLOG(1) << "Media: Cleaning all resources"; +} + +void MediaChildThread::OnCrash() { + VLOG(1) << "Media: Simulating media process crash"; + // Good bye, cruel world. + volatile int* it_s_the_end_of_the_world_as_we_know_it = NULL; + *it_s_the_end_of_the_world_as_we_know_it = 0xdead; +} + +void MediaChildThread::OnHang() { + VLOG(1) << "Media: Simulating media process hang"; + for (;;) { + // TODO: Implement media process watchdog? + } +} + +} // namespace content diff --git a/content/media/media_child_thread.h b/content/media/media_child_thread.h new file mode 100644 index 0000000..9b5bb00 --- /dev/null +++ b/content/media/media_child_thread.h @@ -0,0 +1,137 @@ +// Copyright (c) 2015 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. + +#ifndef CONTENT_MEDIA_MEDIA_CHILD_THREAD_H_ +#define CONTENT_MEDIA_MEDIA_CHILD_THREAD_H_ + +#include +#include + +#include "base/basictypes.h" +#include "base/command_line.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/time/time.h" +#include "build/build_config.h" +#include "content/child/blink_platform_impl.h" +#include "content/child/child_thread_impl.h" +#include "content/common/gpu/client/gpu_channel_host.h" +#include "content/common/gpu/gpu_result_codes.h" +#include "content/common/media/media_channel.h" +#include "content/common/media/media_channel_filter.h" +#include "content/common/media/media_config.h" + +namespace blink { +class WebGraphicsContext3D; +} + +namespace base { +class MessageLoopProxy; +class Thread; +} + +namespace cc { +class ContextProvider; +} + +namespace cc_blink { +class ContextProviderWebContext; +} + +namespace sandbox { +class TargetServices; +} + +namespace content { + +class CompositorForwardingMessageFilter; +class ContextProviderCommandBuffer; +class GpuChannelHost; +class MediaPlayerGStreamer; +class WebGraphicsContext3DCommandBufferImpl; + +// The main thread of the media process. There will only ever be one of +// these per process. It does process initialization and shutdown, +// sends commands to the media. +class MediaChildThread : public ChildThreadImpl, public GpuChannelHostFactory { + public: + typedef std::queue DeferredMessages; + + explicit MediaChildThread(bool dead_on_arrival, + const DeferredMessages& deferred_messages); + + explicit MediaChildThread(const InProcessChildThreadParams& params); + + ~MediaChildThread() override; + + void Shutdown() override; + + void Init(const base::Time& process_start_time); + void StopWatchdog(); + + // ChildThread overrides. + bool Send(IPC::Message* msg) override; + bool OnControlMessageReceived(const IPC::Message& msg) override; + + scoped_refptr CreateSharedContextProvider(); + + private: + // GpuChannelHostFactory implementation: + bool IsMainThread() override; + scoped_refptr GetIOThreadTaskRunner() override; + scoped_ptr AllocateSharedMemory(size_t size) override; + CreateCommandBufferResult CreateViewCommandBuffer( + int32 surface_id, + const GPUCreateCommandBufferConfig& init_params, + int32 route_id) override; + + // Synchronously establish a channel to the GPU plugin if not previously + // established or if it has been lost (for example if the GPU plugin crashed). + // If there is a pending asynchronous request, it will be completed by the + // time this routine returns. + GpuChannelHost* EstablishGpuChannelSync(CauseForGpuLaunch); + + scoped_ptr CreateOffscreenContext3d(); + + // Message handlers. + void OnInitialize(); + + void OnClean(); + void OnCrash(); + void OnHang(); + + // Set this flag to true if a fatal error occurred before we receive the + // OnInitialize message, in which case we just declare ourselves DOA. + bool dead_on_arrival_; + + // Error messages collected in media_main() before the thread is created. + DeferredMessages deferred_messages_; + + // Whether the media thread is running in the browser process. + bool in_browser_process_; + + base::Time process_start_time_; + + scoped_refptr media_channel_filter_; + + // The channel from the media process to the GPU process. + scoped_refptr gpu_channel_; + + // Cache of variables that are needed on the compositor thread by + // GpuChannelHostFactory methods. + scoped_refptr io_thread_task_runner_; + + scoped_ptr gl_thread_; + scoped_refptr gl_task_runner_; + + scoped_refptr provider_; + + scoped_ptr blink_platform_; + + DISALLOW_COPY_AND_ASSIGN(MediaChildThread); +}; + +} // namespace content + +#endif // CONTENT_MEDIA_MEDIA_CHILD_THREAD_H_ diff --git a/content/media/media_main.cc b/content/media/media_main.cc new file mode 100644 index 0000000..4c9159d --- /dev/null +++ b/content/media/media_main.cc @@ -0,0 +1,167 @@ +// Copyright (c) 2015 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 + +#include "base/debug/stack_trace.h" +#include "base/lazy_instance.h" +#include "base/message_loop/message_loop.h" +#include "base/metrics/histogram.h" +#include "base/metrics/statistics_recorder.h" +#include "base/rand_util.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/stringprintf.h" +#include "base/third_party/dynamic_annotations/dynamic_annotations.h" +#include "base/threading/platform_thread.h" +#include "base/trace_event/trace_event.h" +#include "build/build_config.h" +#include "content/child/child_process.h" +#include "content/common/content_constants_internal.h" +#include "content/common/media/media_config.h" +#include "content/common/media/media_messages.h" +#include "content/common/sandbox_linux/sandbox_linux.h" +#include "content/media/media_child_thread.h" +#include "content/media/media_process.h" +#include "content/public/common/content_client.h" +#include "content/public/common/content_switches.h" +#include "content/public/common/main_function_params.h" + +#if defined(USE_X11) +#include "ui/base/x/x11_util.h" +#endif + +#if defined(OS_LINUX) +#include "content/public/common/sandbox_init.h" +#endif + +#if defined(SANITIZER_COVERAGE) +#include +#include +#endif + +namespace content { + +namespace { + +bool WarmUpSandbox(const base::CommandLine& command_line); + +#if defined(OS_LINUX) +bool StartSandboxLinux(); +#endif + +base::LazyInstance deferred_messages = + LAZY_INSTANCE_INITIALIZER; + +bool MediaProcessLogMessageHandler(int severity, + const char* file, + int line, + size_t message_start, + const std::string& str) { + std::string header = str.substr(0, message_start); + std::string message = str.substr(message_start); + deferred_messages.Get().push( + new MediaHostMsg_OnLogMessage(severity, header, message)); + return false; +} + +} // namespace anonymous + +// Main function for starting the media process. +int MediaMain(const MainFunctionParams& parameters) { + // base::debug::EnableInProcessStackDumping(); + base::debug::EnableInProcessStackDumpingForSandbox(); + + TRACE_EVENT0("media", "MediaMain"); + base::trace_event::TraceLog::GetInstance()->SetProcessName("Media Process"); + base::trace_event::TraceLog::GetInstance()->SetProcessSortIndex( + kTraceEventMediaProcessSortIndex); + + const base::CommandLine& command_line = parameters.command_line; + if (command_line.HasSwitch(switches::kMediaStartupDialog)) { + ChildProcess::WaitForDebugger("Media"); + } + + base::Time start_time = base::Time::Now(); + + logging::SetLogMessageHandler(MediaProcessLogMessageHandler); +#if defined(OS_LINUX) + base::MessageLoop main_message_loop(base::MessageLoop::TYPE_DEFAULT); +#else + base::MessageLoop main_message_loop(base::MessageLoop::TYPE_IO); +#endif + + base::PlatformThread::SetName("CrMediaMain"); + + bool initialized_sandbox = true; + + // Warm up resources + if (WarmUpSandbox(command_line)) { + initialized_sandbox = false; + } + + if (!initialized_sandbox) { + StartSandboxLinux(); + } + + logging::SetLogMessageHandler(NULL); + + MediaProcess media_process; + + MediaChildThread* child_thread = + new MediaChildThread(false, deferred_messages.Get()); + while (!deferred_messages.Get().empty()) + deferred_messages.Get().pop(); + + child_thread->Init(start_time); + + media_process.set_main_thread(child_thread); + + { + TRACE_EVENT0("media", "Run Message Loop"); + main_message_loop.Run(); + } + + return 0; +} + +namespace { + +bool WarmUpSandbox(const base::CommandLine& command_line) { + { + TRACE_EVENT0("media", "Warm up rand"); + // Warm up the random subsystem, which needs to be done pre-sandbox on all + // platforms. + (void)base::RandUint64(); + } + return true; +} + +#if defined(OS_LINUX) +bool StartSandboxLinux() { + TRACE_EVENT0("media", "Initialize sandbox"); + + bool res = false; + +// TODO: WarmUpSandbox -- access hardware resources? + +#if defined(SANITIZER_COVERAGE) + const std::string sancov_file_name = + "media." + base::Uint64ToString(base::RandUint64()); + LinuxSandbox* linux_sandbox = LinuxSandbox::GetInstance(); + linux_sandbox->sanitizer_args()->coverage_sandboxed = 1; + linux_sandbox->sanitizer_args()->coverage_fd = + __sanitizer_maybe_open_cov_file(sancov_file_name.c_str()); + linux_sandbox->sanitizer_args()->coverage_max_block_size = 0; +#endif + + // LinuxSandbox::InitializeSandbox() must always be called + // with only one thread. + res = LinuxSandbox::InitializeSandbox(); + return res; +} +#endif // defined(OS_LINUX) + +} // namespace. + +} // namespace content diff --git a/content/media/media_process.cc b/content/media/media_process.cc new file mode 100644 index 0000000..99e3891 --- /dev/null +++ b/content/media/media_process.cc @@ -0,0 +1,13 @@ +// Copyright (c) 2015 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 "content/media/media_process.h" + +namespace content { + +MediaProcess::MediaProcess() {} + +MediaProcess::~MediaProcess() {} + +} // namespace content diff --git a/content/media/media_process.h b/content/media/media_process.h new file mode 100644 index 0000000..98dcc37 --- /dev/null +++ b/content/media/media_process.h @@ -0,0 +1,22 @@ +// Copyright (c) 2015 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. + +#ifndef CONTENT_MEDIA_MEDIA_PROCESS_H_ +#define CONTENT_MEDIA_MEDIA_PROCESS_H_ + +#include "content/child/child_process.h" + +namespace content { + +class MediaProcess : public ChildProcess { + public: + MediaProcess(); + ~MediaProcess() override; + + private: + DISALLOW_COPY_AND_ASSIGN(MediaProcess); +}; +} + +#endif // CONTENT_MEDIA_MEDIA_PROCESS_H_ diff --git a/content/public/browser/media_data_manager.h b/content/public/browser/media_data_manager.h new file mode 100644 index 0000000..c259ca8 --- /dev/null +++ b/content/public/browser/media_data_manager.h @@ -0,0 +1,43 @@ +// Copyright (c) 2012 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. + +#ifndef CONTENT_PUBLIC_BROWSER_MEDIA_DATA_MANAGER_H_ +#define CONTENT_PUBLIC_BROWSER_MEDIA_DATA_MANAGER_H_ + +#include +#include + +#include "base/callback_forward.h" +#include "base/process/process.h" +#include "content/common/content_export.h" + +namespace content { + +class MediaDataManagerObserver; + +// This class is fully thread-safe. +class MediaDataManager { + public: + typedef base::Callback&)> + GetMediaProcessHandlesCallback; + + // Getter for the singleton. + CONTENT_EXPORT static MediaDataManager* GetInstance(); + + // Retrieves a list of process handles for all media processes. + virtual void GetMediaProcessHandles( + const GetMediaProcessHandlesCallback& callback) const = 0; + + // Registers/unregister |observer|. + // TODO: useful for stats but unused for now. + virtual void AddObserver(MediaDataManagerObserver* observer) = 0; + virtual void RemoveObserver(MediaDataManagerObserver* observer) = 0; + + protected: + virtual ~MediaDataManager() {} +}; + +}; // namespace content + +#endif // CONTENT_PUBLIC_BROWSER_MEDIA_DATA_MANAGER_H_ diff --git a/content/public/browser/media_data_manager_observer.h b/content/public/browser/media_data_manager_observer.h new file mode 100644 index 0000000..2234dee --- /dev/null +++ b/content/public/browser/media_data_manager_observer.h @@ -0,0 +1,26 @@ +// Copyright (c) 2015 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. + +#ifndef CONTENT_PUBLIC_BROWSER_MEDIA_DATA_MANAGER_OBSERVER_H_ +#define CONTENT_PUBLIC_BROWSER_MEDIA_DATA_MANAGER_OBSERVER_H_ + +#include "base/process/kill.h" +#include "content/common/content_export.h" + +namespace content { + +// Observers can register themselves via MediaDataManager::AddObserver, and +// can un-register with MediaDataManager::RemoveObserver. +class CONTENT_EXPORT MediaDataManagerObserver { + public: + // Called for any observer when the media process crashed. + virtual void OnMediaProcessCrashed(base::TerminationStatus exit_code) {} + + protected: + virtual ~MediaDataManagerObserver() {} +}; + +}; // namespace content + +#endif // CONTENT_PUBLIC_BROWSER_MEDIA_DATA_MANAGER_OBSERVER_H_ diff --git a/content/public/common/content_switches.cc b/content/public/common/content_switches.cc index af12206..9b635cc 100644 --- a/content/public/common/content_switches.cc +++ b/content/public/common/content_switches.cc @@ -879,6 +879,27 @@ const char kZygoteCmdPrefix[] = "zygote-cmd-prefix"; // Causes the process to run as a renderer zygote. const char kZygoteProcess[] = "zygote"; +#if defined(USE_GSTREAMER) +// Enable GStreamer media backend. +const char kEnableGStreamerMediaBackend[] = "enable-gstreamer-media-backend"; + +// Disable the media process sandbox. +const char kDisableMediaSandbox[] = "disable-media-sandbox"; + +// Extra command line options for launching the media process (normally used +// for debugging). Use like renderer-cmd-prefix. +const char kMediaLauncher[] = "media-launcher"; + +// Makes this process a GPU sub-process. +const char kMediaProcess[] = "media-process"; + +// Causes the media process to display a dialog on launch. +const char kMediaStartupDialog[] = "media-startup-dialog"; + +// Run the GPU process as a thread in the browser process. +const char kInProcessMedia[] = "in-process-media"; +#endif + #if defined(ENABLE_WEBRTC) // Disables HW decode acceleration for WebRTC. const char kDisableWebRtcHWDecoding[] = "disable-webrtc-hw-decoding"; diff --git a/content/public/common/content_switches.h b/content/public/common/content_switches.h index 41d43ae..e96268e 100644 --- a/content/public/common/content_switches.h +++ b/content/public/common/content_switches.h @@ -257,6 +257,15 @@ CONTENT_EXPORT extern const char kWebRtcStunProbeTrialParameter[]; extern const char kWebRtcMaxCaptureFramerate[]; #endif +#if defined(USE_GSTREAMER) +CONTENT_EXPORT extern const char kEnableGStreamerMediaBackend[]; +CONTENT_EXPORT extern const char kDisableMediaSandbox[]; +extern const char kMediaLauncher[]; +CONTENT_EXPORT extern const char kMediaProcess[]; +CONTENT_EXPORT extern const char kMediaStartupDialog[]; +extern const char kInProcessMedia[]; +#endif + #if defined(OS_ANDROID) CONTENT_EXPORT extern const char kDisableGestureRequirementForMediaPlayback[]; CONTENT_EXPORT extern const char kDisableClickDelay[]; diff --git a/content/public/common/process_type.h b/content/public/common/process_type.h index 1469669..8396a00 100644 --- a/content/public/common/process_type.h +++ b/content/public/common/process_type.h @@ -27,6 +27,9 @@ enum ProcessType { PROCESS_TYPE_GPU, PROCESS_TYPE_PPAPI_PLUGIN, PROCESS_TYPE_PPAPI_BROKER, +#if defined(USE_GSTREAMER) + PROCESS_TYPE_MEDIA, +#endif // Custom process types used by the embedder should start from here. PROCESS_TYPE_CONTENT_END, // If any embedder has more than 10 custom process types, update this. diff --git a/content/public/common/sandbox_type.h b/content/public/common/sandbox_type.h index ef610fb..e35e27c 100644 --- a/content/public/common/sandbox_type.h +++ b/content/public/common/sandbox_type.h @@ -28,6 +28,11 @@ enum SandboxType { // The PPAPI plugin process. SANDBOX_TYPE_PPAPI, +#if defined(USE_GSTREAMER) + // media process. + SANDBOX_TYPE_MEDIA, +#endif + SANDBOX_TYPE_AFTER_LAST_TYPE, // Placeholder to ease iteration. }; diff --git a/content/renderer/media/audio_decoder.cc b/content/renderer/media/audio_decoder.cc index c413bb0..805ff03 100644 --- a/content/renderer/media/audio_decoder.cc +++ b/content/renderer/media/audio_decoder.cc @@ -31,6 +31,7 @@ bool DecodeAudioFileData( if (!destination_bus) return false; +#if !defined(MEDIA_DISABLE_FFMPEG) // Uses the FFmpeg library for audio file reading. InMemoryUrlProtocol url_protocol(reinterpret_cast(data), data_size, false); @@ -89,6 +90,9 @@ bool DecodeAudioFileData( << " number of channels: " << number_of_channels; return actual_frames > 0; +#else + return false; +#endif } } // namespace content diff --git a/content/renderer/media/gstreamer/webmediaplayer_gstreamer.cc b/content/renderer/media/gstreamer/webmediaplayer_gstreamer.cc new file mode 100644 index 0000000..3d532a3 --- /dev/null +++ b/content/renderer/media/gstreamer/webmediaplayer_gstreamer.cc @@ -0,0 +1,1245 @@ +// Copyright 2015 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 "content/renderer/media/gstreamer/webmediaplayer_gstreamer.h" + +#include +#include +#include +#include + +#include "base/bind.h" +#include "base/callback.h" +#include "base/callback_helpers.h" +#include "base/debug/alias.h" +#include "base/debug/crash_logging.h" +#include "base/metrics/histogram.h" +#include "base/process/process_handle.h" +#include "base/single_thread_task_runner.h" +#include "base/strings/utf_string_conversions.h" +#include "base/synchronization/waitable_event.h" +#include "base/trace_event/trace_event.h" +#include "cc/blink/web_layer_impl.h" +#include "cc/layers/video_layer.h" +#include "content/renderer/render_thread_impl.h" +#include "content/common/gpu/client/context_provider_command_buffer.h" +#include "content/common/gpu/client/gl_helper.h" +#include "content/common/media/media_messages.h" +#include "content/child/child_process.h" +#include "content/renderer/media/gstreamer/webmediasource_gstreamer.h" +#include "content/renderer/render_thread_impl.h" +#include "gpu/blink/webgraphicscontext3d_impl.h" +#include "gpu/command_buffer/client/gles2_interface.h" +#include "gpu/command_buffer/client/gles2_lib.h" +#include "gpu/command_buffer/common/mailbox.h" +#include "gpu/command_buffer/common/mailbox_holder.h" +#include "ipc/ipc_message_macros.h" +#include "media/audio/null_audio_sink.h" +#include "media/base/bind_to_current_loop.h" +#include "media/base/cdm_context.h" +#include "media/base/limits.h" +#include "media/base/key_systems.h" +#include "media/base/media_log.h" +#include "media/base/pipeline.h" +#include "media/base/text_renderer.h" +#include "media/base/video_frame.h" +#include "media/blink/buffered_data_source.h" +#include "media/blink/encrypted_media_player_support.h" +#include "media/blink/texttrack_impl.h" +#include "media/blink/webaudiosourceprovider_impl.h" +#include "media/blink/webcontentdecryptionmodule_impl.h" +#include "media/blink/webinbandtexttrack_impl.h" +#include "media/blink/webmediaplayer_delegate.h" +#include "media/blink/webmediaplayer_util.h" +#include "media/blink/webmediasource_impl.h" +#include "media/filters/chunk_demuxer.h" +#include "media/filters/ffmpeg_demuxer.h" +#include "third_party/WebKit/public/platform/WebEncryptedMediaTypes.h" +#include "third_party/WebKit/public/platform/WebMediaSource.h" +#include "third_party/WebKit/public/platform/WebRect.h" +#include "third_party/WebKit/public/platform/WebSize.h" +#include "third_party/WebKit/public/platform/WebString.h" +#include "third_party/WebKit/public/platform/WebURL.h" +#include "third_party/WebKit/public/web/WebDocument.h" +#include "third_party/WebKit/public/web/WebLocalFrame.h" +#include "third_party/WebKit/public/web/WebRuntimeFeatures.h" +#include "third_party/WebKit/public/web/WebSecurityOrigin.h" +#include "third_party/WebKit/public/web/WebView.h" + +using blink::WebCanvas; +using blink::WebMediaPlayer; +using blink::WebRect; +using blink::WebSize; +using blink::WebString; + +namespace { + +// Limits the range of playback rate. +// +// TODO(kylep): Revisit these. +// +// Vista has substantially lower performance than XP or Windows7. If you speed +// up a video too much, it can't keep up, and rendering stops updating except on +// the time bar. For really high speeds, audio becomes a bottleneck and we just +// use up the data we have, which may not achieve the speed requested, but will +// not crash the tab. +// +// A very slow speed, ie 0.00000001x, causes the machine to lock up. (It seems +// like a busy loop). It gets unresponsive, although its not completely dead. +// +// Also our timers are not very accurate (especially for ogg), which becomes +// evident at low speeds and on Vista. Since other speeds are risky and outside +// the norms, we think 1/16x to 16x is a safe and useful range for now. +const double kMinRate = 0.0625; +const double kMaxRate = 16.0; + +} // namespace + +namespace media { + +// GstPlayer has 100 ms tick interval. +static const int kTimeUpdateInterval = 100; + +class BufferedDataSourceHostImpl; + +#define STATIC_ASSERT_MATCHING_ENUM(name) \ + static_assert(static_cast(WebMediaPlayer::CORSMode##name) == \ + static_cast(BufferedResourceLoader::k##name), \ + "mismatching enum values: " #name) +STATIC_ASSERT_MATCHING_ENUM(Unspecified); +STATIC_ASSERT_MATCHING_ENUM(Anonymous); +STATIC_ASSERT_MATCHING_ENUM(UseCredentials); +#undef STATIC_ASSERT_MATCHING_ENUM + +// Convert a WebString to ASCII, falling back on an empty string in the case +// of a non-ASCII string. +static std::string ToASCIIOrEmpty(const WebString& string) { + return base::IsStringASCII(string) + ? base::UTF16ToASCII(base::StringPiece16(string)) + : std::string(); +} + +WebMediaPlayerMessageDispatcher::WebMediaPlayerMessageDispatcher( + int player_id, + base::WeakPtr player) + : player_id_(player_id), player_(player) { + task_runner_ = base::ThreadTaskRunnerHandle::Get(); + content::MediaChannelHost* channel = + content::RenderThreadImpl::current()->GetMediaChannel(); + if (channel) + channel->RegisterDispatcher(player_id_, this); +} + +WebMediaPlayerMessageDispatcher::~WebMediaPlayerMessageDispatcher() { + content::MediaChannelHost* channel = + content::RenderThreadImpl::current()->GetMediaChannel(); + if (channel) + channel->RemoveDispatcher(player_id_); +} + +void WebMediaPlayerMessageDispatcher::SendCreate() { + content::MediaChannelHost* channel = + content::RenderThreadImpl::current()->GetMediaChannel(); + if (channel) + channel->Send(new MediaPlayerMsg_Create(player_id_)); +} + +void WebMediaPlayerMessageDispatcher::SendLoad(GURL url) { + content::MediaChannelHost* channel = + content::RenderThreadImpl::current()->GetMediaChannel(); + if (channel) + channel->Send(new MediaPlayerMsg_Load(player_id_, url, media::kTimeUpdateInterval)); +} + +void WebMediaPlayerMessageDispatcher::SendStart() { + content::MediaChannelHost* channel = + content::RenderThreadImpl::current()->GetMediaChannel(); + if (channel) + channel->Send(new MediaPlayerMsg_Start(player_id_)); +} + +void WebMediaPlayerMessageDispatcher::SendPause() { + content::MediaChannelHost* channel = + content::RenderThreadImpl::current()->GetMediaChannel(); + if (channel) + channel->Send(new MediaPlayerMsg_Pause(player_id_)); +} + +void WebMediaPlayerMessageDispatcher::SendSeek(base::TimeDelta delta) { + content::MediaChannelHost* channel = + content::RenderThreadImpl::current()->GetMediaChannel(); + if (channel) + channel->Send(new MediaPlayerMsg_Seek(player_id_, delta)); +} + +void WebMediaPlayerMessageDispatcher::SendRelease() { + content::MediaChannelHost* channel = + content::RenderThreadImpl::current()->GetMediaChannel(); + if (channel) + channel->Send(new MediaPlayerMsg_Release(player_id_)); +} + +void WebMediaPlayerMessageDispatcher::SendRealeaseTexture(unsigned texture_id) { + content::MediaChannelHost* channel = + content::RenderThreadImpl::current()->GetMediaChannel(); + if (channel) + channel->Send(new MediaPlayerMsg_ReleaseTexture(player_id_, texture_id)); +} + +bool WebMediaPlayerMessageDispatcher::SendAddSourceId( + const std::string& id, + const std::string& type, + const std::vector& codecs) { + bool ret = false; + content::MediaChannelHost* channel = + content::RenderThreadImpl::current()->GetMediaChannel(); + + if (channel) + ret = channel->Send( + new MediaPlayerMsg_AddSourceId(player_id_, id, type, codecs)); + + return ret; +} + +void WebMediaPlayerMessageDispatcher::SendRemoveSourceId( + const std::string& id) { + content::MediaChannelHost* channel = + content::RenderThreadImpl::current()->GetMediaChannel(); + if (channel) + channel->Send(new MediaPlayerMsg_RemoveSourceId(player_id_, id)); +} + +void WebMediaPlayerMessageDispatcher::SendSetDuration( + const base::TimeDelta& duration) { + content::MediaChannelHost* channel = + content::RenderThreadImpl::current()->GetMediaChannel(); + if (channel) + channel->Send(new MediaPlayerMsg_SetDuration(player_id_, duration)); +} + +void WebMediaPlayerMessageDispatcher::SendMarkEndOfStream() { + content::MediaChannelHost* channel = + content::RenderThreadImpl::current()->GetMediaChannel(); + if (channel) + channel->Send(new MediaPlayerMsg_MarkEndOfStream(player_id_)); +} + +void WebMediaPlayerMessageDispatcher::SendUnmarkEndOfStream() { + content::MediaChannelHost* channel = + content::RenderThreadImpl::current()->GetMediaChannel(); + if (channel) + channel->Send(new MediaPlayerMsg_UnmarkEndOfStream(player_id_)); +} + +void WebMediaPlayerMessageDispatcher::SendSetSequenceMode(const std::string& id, + bool sequence_mode) { + content::MediaChannelHost* channel = + content::RenderThreadImpl::current()->GetMediaChannel(); + if (channel) + channel->Send( + new MediaPlayerMsg_SetSequenceMode(player_id_, id, sequence_mode)); +} + +void WebMediaPlayerMessageDispatcher::SendAppendData( + const std::string& id, + const unsigned char* data, + unsigned length, + const base::TimeDelta& append_window_start, + const base::TimeDelta& append_window_end, + const base::TimeDelta& timestamp_offset) { + content::MediaChannelHost* channel = + content::RenderThreadImpl::current()->GetMediaChannel(); + if (channel) + channel->Send(new MediaPlayerMsg_AppendData( + player_id_, id, std::vector(data, data + length), + std::vector{append_window_start, append_window_end, + timestamp_offset})); +} + +void WebMediaPlayerMessageDispatcher::SendAbort(const std::string& id) { + content::MediaChannelHost* channel = + content::RenderThreadImpl::current()->GetMediaChannel(); + if (channel) + channel->Send(new MediaPlayerMsg_Abort(player_id_, id)); +} + +void WebMediaPlayerMessageDispatcher:: + SendSetGroupStartTimestampIfInSequenceMode( + const std::string& id, + const base::TimeDelta& timestamp_offset) { + content::MediaChannelHost* channel = + content::RenderThreadImpl::current()->GetMediaChannel(); + if (channel) + channel->Send(new MediaPlayerMsg_SetGroupStartTimestampIfInSequenceMode( + player_id_, id, timestamp_offset)); +} + +void WebMediaPlayerMessageDispatcher::SendRemoveSegment( + const std::string& id, + const base::TimeDelta& start, + const base::TimeDelta& end) { + content::MediaChannelHost* channel = + content::RenderThreadImpl::current()->GetMediaChannel(); + if (channel) + channel->Send(new MediaPlayerMsg_RemoveSegment(player_id_, id, start, end)); +} + +void WebMediaPlayerMessageDispatcher::SendAddKey(const std::string& session_id, + const std::string& key_id, + const std::string& key) { + content::MediaChannelHost* channel = + content::RenderThreadImpl::current()->GetMediaChannel(); + if (channel) + channel->Send( + new MediaPlayerMsg_AddKey(player_id_, session_id, key_id, key)); +} + +bool WebMediaPlayerMessageDispatcher::OnMessageReceived( + const IPC::Message& message) { + WebMediaPlayerGStreamer* player = player_.get(); + if (!player) + return false; + + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(WebMediaPlayerMessageDispatcher, message) + IPC_MESSAGE_FORWARD(MediaPlayerMsg_SetCurrentFrame, player, + WebMediaPlayerGStreamer::OnSetCurrentFrame) + IPC_MESSAGE_FORWARD(MediaPlayerMsg_MediaDurationChanged, player, + WebMediaPlayerGStreamer::OnMediaDurationChanged) + IPC_MESSAGE_FORWARD(MediaPlayerMsg_MediaPlaybackCompleted, player, + WebMediaPlayerGStreamer::OnMediaPlaybackCompleted) + IPC_MESSAGE_FORWARD(MediaPlayerMsg_MediaBufferingUpdate, player, + WebMediaPlayerGStreamer::OnMediaBufferingUpdate) + IPC_MESSAGE_FORWARD(MediaPlayerMsg_SeekCompleted, player, + WebMediaPlayerGStreamer::OnSeekCompleted) + IPC_MESSAGE_FORWARD(MediaPlayerMsg_MediaError, player, + WebMediaPlayerGStreamer::OnMediaError) + IPC_MESSAGE_FORWARD(MediaPlayerMsg_MediaVideoSizeChanged, player, + WebMediaPlayerGStreamer::OnVideoSizeChanged) + IPC_MESSAGE_FORWARD(MediaPlayerMsg_MediaTimeUpdate, player, + WebMediaPlayerGStreamer::OnTimeUpdate) + IPC_MESSAGE_FORWARD(MediaPlayerMsg_MediaPlayerReleased, player, + WebMediaPlayerGStreamer::OnMediaPlayerReleased) + IPC_MESSAGE_FORWARD(MediaPlayerMsg_DidMediaPlayerPlay, player, + WebMediaPlayerGStreamer::OnPlayerPlay) + IPC_MESSAGE_FORWARD(MediaPlayerMsg_DidMediaPlayerPause, player, + WebMediaPlayerGStreamer::OnPlayerPause) + IPC_MESSAGE_FORWARD(MediaPlayerMsg_SourceSelected, player, + WebMediaPlayerGStreamer::OnSourceSelected) + IPC_MESSAGE_FORWARD(MediaPlayerMsg_DidAddSourceId, player, + WebMediaPlayerGStreamer::OnAddSourceId) + IPC_MESSAGE_FORWARD(MediaPlayerMsg_DidRemoveSourceId, player, + WebMediaPlayerGStreamer::OnRemoveSourceId) + IPC_MESSAGE_FORWARD(MediaPlayerMsg_InitSegmentReceived, player, + WebMediaPlayerGStreamer::OnInitSegmentReceived) + IPC_MESSAGE_FORWARD(MediaPlayerMsg_BufferedRangeUpdate, player, + WebMediaPlayerGStreamer::OnBufferedRangeUpdate) + IPC_MESSAGE_FORWARD(MediaPlayerMsg_TimestampOffsetUpdate, player, + WebMediaPlayerGStreamer::OnTimestampOffsetUpdate) + IPC_MESSAGE_FORWARD(MediaPlayerMsg_NeedKey, player, + WebMediaPlayerGStreamer::OnNeedKey) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + return handled; +} + +base::AtomicSequenceNumber WebMediaPlayerGStreamer::next_player_id_; + +WebMediaPlayerGStreamer::WebMediaPlayerGStreamer( + blink::WebLocalFrame* frame, + blink::WebMediaPlayerClient* client, + base::WeakPtr delegate, + CdmFactory* cdm_factory, + media::MediaPermission* media_permission, + blink::WebContentDecryptionModule* initial_cdm, + MediaLog* media_log) + : video_frame_provider_client_(nullptr), + // Compositor thread does not exist in layout tests. + compositor_loop_( + content::RenderThreadImpl::current()->compositor_task_runner().get() + ? content::RenderThreadImpl::current()->compositor_task_runner() + : base::ThreadTaskRunnerHandle::Get()), + current_frame_(nullptr), + frame_(frame), + network_state_(WebMediaPlayer::NetworkStateEmpty), + ready_state_(WebMediaPlayer::ReadyStateHaveNothing), + preload_(BufferedDataSource::AUTO), + main_task_runner_(base::ThreadTaskRunnerHandle::Get()), + media_log_(media_log), + load_type_(LoadTypeURL), + paused_(true), + seeking_(false), + playback_rate_(0.0f), + natural_size_(0, 0), + ended_(false), + pending_seek_(false), + did_loading_progress_(false), + buffered_(static_cast(1)), + client_(client), + interpolator_(&default_tick_clock_), + delegate_(delegate), + media_source_(nullptr), + supports_save_(true), + message_dispatcher_(next_player_id_.GetNext(), AsWeakPtr()), + encrypted_media_support_( + cdm_factory, + client, + media_permission, + base::Bind(&WebMediaPlayerGStreamer::SetCdm, + AsWeakPtr(), + base::Bind(&IgnoreCdmAttached)), + base::Bind(&WebMediaPlayerGStreamer::OnCdmKeysReady, AsWeakPtr())) { + media_log_->AddEvent( + media_log_->CreateEvent(MediaLogEvent::WEBMEDIAPLAYER_CREATED)); + + interpolator_.SetUpperBound(base::TimeDelta()); + + if (initial_cdm) { + SetCdm(base::Bind(&IgnoreCdmAttached), + ToWebContentDecryptionModuleImpl(initial_cdm)->GetCdmContext()); + } + + // Ideally we could use the existing compositor thread + // but it does not seem easy to get the context so let's + // initialize one in the main thread for now. + main_task_runner_->PostTask( + FROM_HERE, + base::Bind(&WebMediaPlayerGStreamer::SetupGLContext, AsWeakPtr())); + + message_dispatcher_.SendCreate(); +} + +WebMediaPlayerGStreamer::~WebMediaPlayerGStreamer() { + SetVideoFrameProviderClient(NULL); + client_->setWebLayer(NULL); + + DCHECK(main_task_runner_->BelongsToCurrentThread()); + media_log_->AddEvent( + media_log_->CreateEvent(MediaLogEvent::WEBMEDIAPLAYER_DESTROYED)); + + if (delegate_) + delegate_->PlayerGone(this); + + message_dispatcher_.SendRelease(); +} + +void WebMediaPlayerGStreamer::SetupGLContext() { + gpu::gles2::GLES2Interface* gles2_ctx = + content::RenderThreadImpl::current() + ->SharedMainThreadContextProvider() + ->ContextGL(); + ::gles2::Initialize(); + ::gles2::SetGLContext(gles2_ctx); +} + +void WebMediaPlayerGStreamer::SetVideoFrameProviderClient( + cc::VideoFrameProvider::Client* client) { + // This is called from both the main renderer thread and the compositor + // thread (when the main thread is blocked). + if (video_frame_provider_client_ && video_frame_provider_client_ != client) + video_frame_provider_client_->StopUsingProvider(); + video_frame_provider_client_ = client; +} + +void WebMediaPlayerGStreamer::SetCurrentFrameInternal( + scoped_refptr& video_frame) { + base::AutoLock auto_lock(current_frame_lock_); + current_frame_ = video_frame; + if (video_frame_provider_client_) + compositor_loop_->PostTask( + FROM_HERE, base::Bind(&cc::VideoFrameProvider::Client::DidReceiveFrame, + base::Unretained(video_frame_provider_client_))); +} + +bool WebMediaPlayerGStreamer::UpdateCurrentFrame(base::TimeTicks deadline_min, + base::TimeTicks deadline_max) { + NOTIMPLEMENTED(); + return false; +} + +bool WebMediaPlayerGStreamer::HasCurrentFrame() { + base::AutoLock auto_lock(current_frame_lock_); + return current_frame_; +} + +scoped_refptr WebMediaPlayerGStreamer::GetCurrentFrame() { + scoped_refptr video_frame; + { + base::AutoLock auto_lock(current_frame_lock_); + video_frame = current_frame_; + } + + return video_frame; +} + +void WebMediaPlayerGStreamer::PutCurrentFrame() {} + +void WebMediaPlayerGStreamer::OnReleaseTexture(unsigned texture_id, + uint32 release_sync_point) { + gpu::gles2::GLES2Interface* gl = ::gles2::GetGLContext(); + + if (!gl) { + message_dispatcher_.SendRealeaseTexture(texture_id); + return; + } + + gl->WaitSyncPointCHROMIUM(release_sync_point); + + message_dispatcher_.SendRealeaseTexture(texture_id); +} + +void WebMediaPlayerGStreamer::OnSetCurrentFrame( + int width, + int height, + unsigned texture_id, + unsigned target, + const std::vector& v_name) { + gpu::gles2::GLES2Interface* gl = ::gles2::GetGLContext(); + + if (!gl) { + message_dispatcher_.SendRealeaseTexture(texture_id); + return; + } + + int8_t name[GL_MAILBOX_SIZE_CHROMIUM]; + for (int i = 0; i < GL_MAILBOX_SIZE_CHROMIUM; ++i) + name[i] = v_name[i]; + + gpu::Mailbox mailbox; + mailbox.SetName(name); + GLuint sync_point = gl->InsertSyncPointCHROMIUM(); + + // TODO: use ubercompositor to avoid inheriting from cc::VideoFrameProvider + // and to avoid creating media::VideoFrame::WrapNativeTexture here. + scoped_refptr frame = media::VideoFrame::WrapNativeTexture( + media::VideoFrame::ARGB, + gpu::MailboxHolder(mailbox, target, sync_point), + media::BindToCurrentLoop(base::Bind( + &WebMediaPlayerGStreamer::OnReleaseTexture, AsWeakPtr(), texture_id)), + gfx::Size(width, height), gfx::Rect(width, height), + gfx::Size(width, height), base::TimeDelta()); + + SetCurrentFrameInternal(frame); +} + +void WebMediaPlayerGStreamer::OnMediaDurationChanged( + const base::TimeDelta& duration) { + DVLOG(1) << __FUNCTION__ << "(" << duration << ")"; + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + SetNetworkState(WebMediaPlayer::NetworkStateLoaded); + duration_ = duration; + + if (ready_state_ != WebMediaPlayer::ReadyStateHaveEnoughData) { + SetReadyState(WebMediaPlayer::ReadyStateHaveMetadata); + SetReadyState(WebMediaPlayer::ReadyStateHaveEnoughData); + } + + client_->durationChanged(); +} + +void WebMediaPlayerGStreamer::OnMediaPlaybackCompleted() { + DVLOG(1) << __FUNCTION__; + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + // Ignore state changes until we've completed all outstanding seeks. + if (seeking_ || pending_seek_) + return; + + interpolator_.SetBounds(duration_, duration_); + + ended_ = true; + client_->timeChanged(); + + if (!paused_) { + OnPlayerPause(); + } +} + +void WebMediaPlayerGStreamer::OnMediaBufferingUpdate(int percent) { + DVLOG(1) << __FUNCTION__ << "(" << percent << ")"; + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + // Ignore buffering state changes until we've completed all outstanding seeks. + if (seeking_ || pending_seek_) + return; + + buffered_[0].end = duration() * percent / 100; + did_loading_progress_ = true; +} + +void WebMediaPlayerGStreamer::OnSeekCompleted( + const base::TimeDelta& current_time) { + DVLOG(1) << __FUNCTION__ << "(" << current_time << ")"; + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + seeking_ = false; + if (pending_seek_) { + pending_seek_ = false; + seek(pending_seek_time_.InSecondsF()); + return; + } + interpolator_.SetBounds(current_time, current_time); + + SetReadyState(WebMediaPlayer::ReadyStateHaveEnoughData); + + client_->timeChanged(); +} + +void WebMediaPlayerGStreamer::OnMediaError(int error) { + DVLOG(1) << __FUNCTION__ << "(" << error << ")"; + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + if (ready_state_ == WebMediaPlayer::ReadyStateHaveNothing) { + // Any error that occurs before reaching ReadyStateHaveMetadata should + // be considered a format error. + SetNetworkState(WebMediaPlayer::NetworkStateFormatError); + return; + } + + // TODO: Convert error SetNetworkState(error); + // but for now at least notify client_ there is an error. + SetNetworkState(WebMediaPlayer::NetworkStateFormatError); +} + +void WebMediaPlayerGStreamer::OnVideoSizeChanged(int width, int height) { + DVLOG(1) << __FUNCTION__ << "(" << width << "," << height << ")"; + if (natural_size_.width == width && natural_size_.height == height) + return; + + natural_size_.width = width; + natural_size_.height = height; + + if (!video_weblayer_) { + video_weblayer_.reset(new cc_blink::WebLayerImpl( + cc::VideoLayer::Create(cc_blink::WebLayerImpl::LayerSettings(), this, + media::VIDEO_ROTATION_0))); + client_->setWebLayer(video_weblayer_.get()); + } + + client_->timeChanged(); +} + +void WebMediaPlayerGStreamer::OnTimeUpdate(base::TimeDelta current_timestamp, + base::TimeTicks current_time_ticks) { + DVLOG(1) << __FUNCTION__ << "(" << current_timestamp << "," + << current_time_ticks << ")"; + + // Compensate the current_timestamp with the IPC latency. + base::TimeDelta lower_bound = + base::TimeTicks::Now() - current_time_ticks + current_timestamp; + base::TimeDelta upper_bound = lower_bound; + // We should get another time update in about |kTimeUpdateInterval| + // milliseconds. + if (!paused_) { + upper_bound += + base::TimeDelta::FromMilliseconds(media::kTimeUpdateInterval); + } + // if the lower_bound is smaller than the current time, just use the current + // time so that the timer is always progressing. + lower_bound = + std::min(lower_bound, base::TimeDelta::FromSecondsD(currentTime())); + interpolator_.SetBounds(lower_bound, upper_bound); +} + +void WebMediaPlayerGStreamer::OnMediaPlayerReleased() { + DVLOG(1) << __FUNCTION__; + + if (!paused_) { + OnPlayerPause(); + } +} + +void WebMediaPlayerGStreamer::OnPlayerPlay() { + DVLOG(1) << __FUNCTION__; + + UpdatePlayingState(true); + client_->playbackStateChanged(); +} + +void WebMediaPlayerGStreamer::OnPlayerPause() { + DVLOG(1) << __FUNCTION__; + + UpdatePlayingState(false); + client_->playbackStateChanged(); +} + +void WebMediaPlayerGStreamer::OnSourceSelected() { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + DVLOG(1) << __FUNCTION__; + + media_source_ = new WebMediaSourceGStreamer( + this, &message_dispatcher_, + base::Bind(&WebMediaPlayerGStreamer::SetNetworkState, AsWeakPtr())); + client_->mediaSourceOpened(media_source_); +} + +void WebMediaPlayerGStreamer::OnSourceDeleted(WebMediaSourceGStreamer* media_source) { + if (media_source_ == media_source) + media_source_ = nullptr; +} + +void WebMediaPlayerGStreamer::OnAddSourceId(const std::string& id) { + DVLOG(1) << __FUNCTION__; + + if (media_source_) + media_source_->OnAddSourceId(id); +} + +void WebMediaPlayerGStreamer::OnRemoveSourceId(const std::string& id) { + DVLOG(1) << __FUNCTION__; + + if (media_source_) + media_source_->OnRemoveSourceId(id); +} + +void WebMediaPlayerGStreamer::OnInitSegmentReceived(const std::string& id) { + DVLOG(1) << __FUNCTION__; + + if (media_source_) + media_source_->OnInitSegmentReceived(id); +} + +void WebMediaPlayerGStreamer::OnBufferedRangeUpdate( + const std::string& id, + const std::vector& raw_ranges) { + DVLOG(1) << __FUNCTION__; + + // TODO really receive ranges + Ranges ranges; + for (auto& iter : raw_ranges) + ranges.Add(base::TimeDelta(), iter); + + if (media_source_) + media_source_->OnBufferedRangeUpdate(id, ranges); +} + +void WebMediaPlayerGStreamer::OnTimestampOffsetUpdate( + const std::string& id, + const base::TimeDelta& timestamp_offset) { + DVLOG(1) << __FUNCTION__; + + if (media_source_) + media_source_->OnTimestampOffsetUpdate(id, timestamp_offset); +} + +void WebMediaPlayerGStreamer::OnNeedKey(const std::string& system_id, + const std::vector& init_data) { + DVLOG(1) << __FUNCTION__ << "(" << system_id << ")"; + EmeInitDataType type = EmeInitDataType::UNKNOWN; + + if (system_id == "org.w3.clearkey") + type = EmeInitDataType::CENC; + + OnEncryptedMediaInitData(type, init_data); +} + +void WebMediaPlayerGStreamer::load(LoadType load_type, + const blink::WebURL& url, + CORSMode cors_mode) { + DVLOG(1) << __FUNCTION__ << "(" << load_type << ", " << url << ", " + << cors_mode << ")"; + + DoLoad(load_type, url, cors_mode); +} + +void WebMediaPlayerGStreamer::DoLoad(LoadType load_type, + const blink::WebURL& url, + CORSMode cors_mode) { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + GURL gurl(url); + ReportMetrics(load_type, gurl, + GURL(frame_->document().securityOrigin().toString())); + + // Set subresource URL for crash reporting. + base::debug::SetCrashKeyValue("subresource_url", gurl.spec()); + + load_type_ = load_type; + + if (load_type == LoadTypeMediaSource) { + // TODO maybe just do blob:// + std::string media_url = gurl.spec(); + size_t pos = media_url.find("blob:"); + if (pos != std::string::npos) { + media_url = media_url.substr(pos + 5); + gurl = GURL("mediasourceblob://" + media_url); + } + } + + SetNetworkState(WebMediaPlayer::NetworkStateLoading); + SetReadyState(WebMediaPlayer::ReadyStateHaveNothing); + media_log_->AddEvent(media_log_->CreateLoadEvent(url.spec())); + + message_dispatcher_.SendLoad(gurl); +} + +void WebMediaPlayerGStreamer::play() { + DVLOG(1) << __FUNCTION__; + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + media_log_->AddEvent(media_log_->CreateEvent(MediaLogEvent::PLAY)); + + if (playback_rate_ > 0) { + message_dispatcher_.SendStart(); + UpdatePlayingState(true); + } +} + +void WebMediaPlayerGStreamer::pause() { + DVLOG(1) << __FUNCTION__; + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + UpdatePlayingState(false); + message_dispatcher_.SendPause(); +} + +bool WebMediaPlayerGStreamer::supportsSave() const { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + return supports_save_; +} + +void WebMediaPlayerGStreamer::seek(double seconds) { + DVLOG(1) << __FUNCTION__ << "(" << seconds << "s)"; + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + if (seeking_) { + pending_seek_ = true; + pending_seek_time_ = media::ConvertSecondsToTimestamp(seconds); + return; + } + + seek_time_ = media::ConvertSecondsToTimestamp(seconds); + seeking_ = true; + media_log_->AddEvent(media_log_->CreateSeekEvent(seconds)); + message_dispatcher_.SendSeek(base::TimeDelta::FromSecondsD(seconds)); +} + +void WebMediaPlayerGStreamer::setRate(double rate) { + DVLOG(1) << __FUNCTION__ << "(" << rate << ")"; + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + // TODO(kylep): Remove when support for negatives is added. Also, modify the + // following checks so rewind uses reasonable values also. + if (rate < 0.0) + return; + + // Limit rates to reasonable values by clamping. + if (rate != 0.0) { + if (rate < kMinRate) + rate = kMinRate; + else if (rate > kMaxRate) + rate = kMaxRate; + if (playback_rate_ == 0 && !paused_ && delegate_) + delegate_->DidPlay(this); + } else if (playback_rate_ != 0 && !paused_ && delegate_) { + delegate_->DidPause(this); + } + + playback_rate_ = rate; + if (!paused_) { + /* TODO: + * SetPlaybackRate(rate); + * if (data_source_) + * data_source_->MediaPlaybackRateChanged(rate); + */ + } +} + +void WebMediaPlayerGStreamer::setVolume(double volume) { + DVLOG(1) << __FUNCTION__ << "(" << volume << ")"; + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + NOTIMPLEMENTED(); +} + +void WebMediaPlayerGStreamer::setSinkId(const blink::WebString& device_id, + WebSetSinkIdCB* web_callbacks) { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + NOTIMPLEMENTED(); +} + +#define STATIC_ASSERT_MATCHING_ENUM(webkit_name, chromium_name) \ + static_assert(static_cast(WebMediaPlayer::webkit_name) == \ + static_cast(BufferedDataSource::chromium_name), \ + "mismatching enum values: " #webkit_name) +STATIC_ASSERT_MATCHING_ENUM(PreloadNone, NONE); +STATIC_ASSERT_MATCHING_ENUM(PreloadMetaData, METADATA); +STATIC_ASSERT_MATCHING_ENUM(PreloadAuto, AUTO); +#undef STATIC_ASSERT_MATCHING_ENUM + +void WebMediaPlayerGStreamer::setPreload(WebMediaPlayer::Preload preload) { + DVLOG(1) << __FUNCTION__ << "(" << preload << ")"; + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + // TODO: +} + +bool WebMediaPlayerGStreamer::hasVideo() const { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + return true; +} + +bool WebMediaPlayerGStreamer::hasAudio() const { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + return true; +} + +blink::WebSize WebMediaPlayerGStreamer::naturalSize() const { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + return natural_size_; +} + +bool WebMediaPlayerGStreamer::paused() const { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + return paused_; +} + +bool WebMediaPlayerGStreamer::seeking() const { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + if (ready_state_ == WebMediaPlayer::ReadyStateHaveNothing) + return false; + + return seeking_; +} + +double WebMediaPlayerGStreamer::duration() const { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + // HTML5 spec requires duration to be NaN if readyState is HAVE_NOTHING + + if (ready_state_ == WebMediaPlayer::ReadyStateHaveNothing) + return std::numeric_limits::quiet_NaN(); + + if (duration_ == media::kInfiniteDuration()) + return std::numeric_limits::infinity(); + + return duration_.InSecondsF(); +} + +double WebMediaPlayerGStreamer::timelineOffset() const { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + base::Time timeline_offset; + // TODO: + /* if (media_source_delegate_) + timeline_offset = media_source_delegate_->GetTimelineOffset(); + */ + + if (timeline_offset.is_null()) + return std::numeric_limits::quiet_NaN(); + + return timeline_offset.ToJsTime(); +} + +double WebMediaPlayerGStreamer::currentTime() const { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + // If the player is processing a seek, return the seek time. + // Blink may still query us if updatePlaybackState() occurs while seeking. + + if (seeking()) { + return pending_seek_ ? + pending_seek_time_.InSecondsF() : seek_time_.InSecondsF(); + } + + return std::min((const_cast(&interpolator_)) + ->GetInterpolatedTime(), + duration_) + .InSecondsF(); +} + +WebMediaPlayer::NetworkState WebMediaPlayerGStreamer::networkState() const { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + return network_state_; +} + +WebMediaPlayer::ReadyState WebMediaPlayerGStreamer::readyState() const { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + return ready_state_; +} + +blink::WebTimeRanges WebMediaPlayerGStreamer::buffered() const { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + return buffered_; +} + +blink::WebTimeRanges WebMediaPlayerGStreamer::seekable() const { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + if (ready_state_ < WebMediaPlayer::ReadyStateHaveMetadata) + return blink::WebTimeRanges(); + + const double seekable_end = duration(); + + // Allow a special exception for seeks to zero for streaming sources with a + // finite duration; this allows looping to work. + // TODO: const bool allow_seek_to_zero = data_source_ && + // data_source_->IsStreaming() && + // base::IsFinite(seekable_end); + + // TODO(dalecurtis): Technically this allows seeking on media which return an + // infinite duration so long as DataSource::IsStreaming() is false. While not + // expected, disabling this breaks semi-live players, http://crbug.com/427412. + const blink::WebTimeRange seekable_range(0.0, seekable_end); + return blink::WebTimeRanges(&seekable_range, 1); +} + +bool WebMediaPlayerGStreamer::didLoadingProgress() { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + bool ret = did_loading_progress_; + did_loading_progress_ = false; + return ret; +} + +void WebMediaPlayerGStreamer::paint(blink::WebCanvas* canvas, + const blink::WebRect& rect, + unsigned char alpha, + SkXfermode::Mode mode) { + DCHECK(main_task_runner_->BelongsToCurrentThread()); +} + +bool WebMediaPlayerGStreamer::hasSingleSecurityOrigin() const { + /* TODO + if (data_source_) + return data_source_->HasSingleOrigin(); + */ + + return true; +} + +bool WebMediaPlayerGStreamer::didPassCORSAccessCheck() const { + /* TODO + if (data_source_) + return data_source_->DidPassCORSAccessCheck(); + */ + + return false; +} + +double WebMediaPlayerGStreamer::mediaTimeForTimeValue(double timeValue) const { + return ConvertSecondsToTimestamp(timeValue).InSecondsF(); +} + +unsigned WebMediaPlayerGStreamer::decodedFrameCount() const { + NOTIMPLEMENTED(); + return 0; +} + +unsigned WebMediaPlayerGStreamer::droppedFrameCount() const { + NOTIMPLEMENTED(); + return 0; +} + +unsigned WebMediaPlayerGStreamer::audioDecodedByteCount() const { + NOTIMPLEMENTED(); + return 0; +} + +unsigned WebMediaPlayerGStreamer::videoDecodedByteCount() const { + NOTIMPLEMENTED(); + return 0; +} + +bool WebMediaPlayerGStreamer::copyVideoTextureToPlatformTexture( + blink::WebGraphicsContext3D* web_graphics_context, + unsigned int texture, + unsigned int level, + unsigned int internal_format, + unsigned int type, + bool premultiply_alpha, + bool flip_y) { + return copyVideoTextureToPlatformTexture(web_graphics_context, texture, + internal_format, type, + premultiply_alpha, flip_y); +} + +bool WebMediaPlayerGStreamer::copyVideoTextureToPlatformTexture( + blink::WebGraphicsContext3D* web_graphics_context, + unsigned int texture, + unsigned int internal_format, + unsigned int type, + bool premultiply_alpha, + bool flip_y) { + NOTIMPLEMENTED(); + return false; +} + +WebMediaPlayer::MediaKeyException WebMediaPlayerGStreamer::generateKeyRequest( + const WebString& key_system, + const unsigned char* init_data, + unsigned init_data_length) { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + std::string ascii_key_system = + media::GetUnprefixedKeySystemName(media::ToASCIIOrEmpty(key_system)); + + // Support common decryption only for now + if (!CanUseAesDecryptor(ascii_key_system)) { + return MediaKeyExceptionKeySystemNotSupported; + } + + return encrypted_media_support_.GenerateKeyRequest( + frame_, key_system, init_data, init_data_length); +} + +WebMediaPlayer::MediaKeyException WebMediaPlayerGStreamer::addKey( + const WebString& key_system, + const unsigned char* key, + unsigned key_length, + const unsigned char* init_data, + unsigned init_data_length, + const WebString& session_id) { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + return encrypted_media_support_.AddKey(key_system, key, key_length, init_data, + init_data_length, session_id); +} + +WebMediaPlayer::MediaKeyException WebMediaPlayerGStreamer::cancelKeyRequest( + const WebString& key_system, + const WebString& session_id) { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + return encrypted_media_support_.CancelKeyRequest(key_system, session_id); +} + +void WebMediaPlayerGStreamer::setContentDecryptionModule( + blink::WebContentDecryptionModule* cdm, + blink::WebContentDecryptionModuleResult result) { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + // TODO(xhwang): Support setMediaKeys(0) if necessary: http://crbug.com/330324 + if (!cdm) { + result.completeWithError( + blink::WebContentDecryptionModuleExceptionNotSupportedError, 0, + "Null MediaKeys object is not supported."); + return; + } + + SetCdm(BindToCurrentLoop(base::Bind(&WebMediaPlayerGStreamer::OnCdmAttached, + AsWeakPtr(), result)), + ToWebContentDecryptionModuleImpl(cdm)->GetCdmContext()); +} + +// TODO(jrummell): |init_data_type| should be an enum. http://crbug.com/417440 +void WebMediaPlayerGStreamer::OnEncryptedMediaInitData( + EmeInitDataType init_data_type, + const std::vector& init_data) { + DCHECK(init_data_type != EmeInitDataType::UNKNOWN); + + // Do not fire "encrypted" event if encrypted media is not enabled. + // TODO(xhwang): Handle this in |client_|. + if (!blink::WebRuntimeFeatures::isPrefixedEncryptedMediaEnabled() && + !blink::WebRuntimeFeatures::isEncryptedMediaEnabled()) { + return; + } + + // TODO(xhwang): Update this UMA name. + UMA_HISTOGRAM_COUNTS("Media.EME.NeedKey", 1); + + encrypted_media_support_.SetInitDataType(init_data_type); + + client_->encrypted(ConvertToWebInitDataType(init_data_type), + vector_as_array(&init_data), + base::saturated_cast(init_data.size())); +} + +void WebMediaPlayerGStreamer::OnWaitingForDecryptionKey() { + client_->didBlockPlaybackWaitingForKey(); + + // TODO(jrummell): didResumePlaybackBlockedForKey() should only be called + // when a key has been successfully added (e.g. OnSessionKeysChange() with + // |has_additional_usable_key| = true). http://crbug.com/461903 + client_->didResumePlaybackBlockedForKey(); +} + +void WebMediaPlayerGStreamer::SetCdm(const CdmAttachedCB& cdm_attached_cb, + CdmContext* cdm_context) { + NOTIMPLEMENTED(); +} + +void WebMediaPlayerGStreamer::OnCdmAttached( + blink::WebContentDecryptionModuleResult result, + bool success) { + if (success) { + result.complete(); + return; + } + + result.completeWithError( + blink::WebContentDecryptionModuleExceptionNotSupportedError, 0, + "Unable to set MediaKeys object"); +} + +void WebMediaPlayerGStreamer::OnCdmKeysReady(const std::string& session_id, + bool has_additional_usable_key, + CdmKeysInfo keys_info) { + for (const auto& item : keys_info) { + message_dispatcher_.SendAddKey( + session_id, std::string(item->key_id.begin(), item->key_id.end()), + item->key); + } +} + +void WebMediaPlayerGStreamer::OnAddTextTrack( + const TextTrackConfig& config, + const AddTextTrackDoneCB& done_cb) { + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + const WebInbandTextTrackImpl::Kind web_kind = + static_cast(config.kind()); + const blink::WebString web_label = blink::WebString::fromUTF8(config.label()); + const blink::WebString web_language = + blink::WebString::fromUTF8(config.language()); + const blink::WebString web_id = blink::WebString::fromUTF8(config.id()); + + scoped_ptr web_inband_text_track( + new WebInbandTextTrackImpl(web_kind, web_label, web_language, web_id)); + + scoped_ptr text_track(new TextTrackImpl( + main_task_runner_, client_, web_inband_text_track.Pass())); + + done_cb.Run(text_track.Pass()); +} + +void WebMediaPlayerGStreamer::SetNetworkState( + WebMediaPlayer::NetworkState state) { + DVLOG(1) << __FUNCTION__ << "(" << state << ")"; + DCHECK(main_task_runner_->BelongsToCurrentThread()); + network_state_ = state; + // Always notify to ensure client has the latest value. + client_->networkStateChanged(); +} + +void WebMediaPlayerGStreamer::SetReadyState(WebMediaPlayer::ReadyState state) { + DVLOG(1) << __FUNCTION__ << "(" << state << ")"; + DCHECK(main_task_runner_->BelongsToCurrentThread()); + + ready_state_ = state; + // Always notify to ensure client has the latest value. + client_->readyStateChanged(); +} + +void WebMediaPlayerGStreamer::UpdatePlayingState(bool is_playing) { + DVLOG(1) << __FUNCTION__ << "(" << is_playing << ")"; + + if (is_playing == !paused_) + return; + + paused_ = !is_playing; + + if (is_playing) + interpolator_.StartInterpolating(); + else + interpolator_.StopInterpolating(); + + if (delegate_) { + if (is_playing) + delegate_->DidPlay(this); + else + delegate_->DidPause(this); + } +} + +} // namespace media diff --git a/content/renderer/media/gstreamer/webmediaplayer_gstreamer.h b/content/renderer/media/gstreamer/webmediaplayer_gstreamer.h new file mode 100644 index 0000000..5c4d4bc --- /dev/null +++ b/content/renderer/media/gstreamer/webmediaplayer_gstreamer.h @@ -0,0 +1,390 @@ +// Copyright 2015 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. + +#ifndef MEDIA_GSTREAMER_WEBMEDIAPLAYER_GSTREAMER_H_ +#define MEDIA_GSTREAMER_WEBMEDIAPLAYER_GSTREAMER_H_ + +#include +#include + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/time/default_tick_clock.h" +#include "base/time/time.h" +#include "base/threading/thread.h" +#include "cc/layers/video_frame_provider.h" +#include "content/common/media/media_channel_host.h" +#include "content/common/media/media_player_messages_gstreamer.h" +#include "content/common/media/media_process_launch_causes.h" +#include "ipc/ipc_listener.h" +#include "ipc/ipc_message_macros.h" +#include "media/base/cdm_factory.h" +#include "media/base/media_export.h" +#include "media/base/pipeline.h" +#include "media/base/ranges.h" +#include "media/base/renderer_factory.h" +#include "media/base/text_track.h" +#include "media/base/time_delta_interpolator.h" +#include "media/blink/buffered_data_source.h" +#include "media/blink/buffered_data_source_host_impl.h" +#include "media/blink/encrypted_media_player_support.h" +#include "media/blink/encrypted_media_player_support.h" +#include "media/blink/webmediaplayer_util.h" +#include "third_party/WebKit/public/platform/WebContentDecryptionModuleResult.h" +#include "third_party/WebKit/public/platform/WebMediaPlayer.h" +#include "third_party/WebKit/public/platform/WebMediaPlayerClient.h" +#include "url/gurl.h" + +namespace base { +class SingleThreadTaskRunner; +} + +namespace blink { +class WebGraphicsContext3D; +class WebLocalFrame; +} + +namespace cc_blink { +class WebLayerImpl; +} + +namespace media { +class MediaLog; +class WebMediaPlayerDelegate; +class WebMediaPlayerGStreamer; +class WebMediaSourceGStreamer; + +class WebMediaPlayerMessageDispatcher + : public IPC::Listener, + public base::SupportsWeakPtr { + public: + WebMediaPlayerMessageDispatcher( + int player_id, + base::WeakPtr player); + ~WebMediaPlayerMessageDispatcher(); + + void SendCreate(); + void SendLoad(GURL); + void SendStart(); + void SendPause(); + void SendSeek(base::TimeDelta); + void SendRelease(); + void SendRealeaseTexture(unsigned texture_id); + + // MSE + bool SendAddSourceId(const std::string& id, + const std::string& type, + const std::vector& codecs); + void SendRemoveSourceId(const std::string& id); + void SendSetDuration(const base::TimeDelta& duration); + void SendMarkEndOfStream(); + void SendUnmarkEndOfStream(); + void SendSetSequenceMode(const std::string& id, bool sequence_mode); + void SendAppendData(const std::string& id, + const unsigned char* data, + unsigned length, + const base::TimeDelta& append_window_start, + const base::TimeDelta& append_window_end, + const base::TimeDelta& timestamp_offset); + void SendAbort(const std::string& id); + void SendSetGroupStartTimestampIfInSequenceMode( + const std::string& id, + const base::TimeDelta& timestamp_offset); + void SendRemoveSegment(const std::string& id, + const base::TimeDelta& start, + const base::TimeDelta& end); + void SendAddKey(const std::string& session_id, + const std::string& key_id, + const std::string& key); + + bool OnMessageReceived(const IPC::Message& message) override; + + scoped_refptr GetTaskRunner() { + return task_runner_.get(); + } + + private: + int player_id_; + base::WeakPtr player_; + scoped_refptr task_runner_; +}; + +// TODO: use ubercompositor to avoid inheriting from cc::VideoFrameProvider. +// Instead MediaPlayerGStreamer form Media Process will inherit this interface. +class MEDIA_EXPORT WebMediaPlayerGStreamer + : public NON_EXPORTED_BASE(blink::WebMediaPlayer), + public cc::VideoFrameProvider, + public base::SupportsWeakPtr { + public: + WebMediaPlayerGStreamer(blink::WebLocalFrame* frame, + blink::WebMediaPlayerClient* client, + base::WeakPtr delegate, + CdmFactory* cdm_factory, + media::MediaPermission* media_permission, + blink::WebContentDecryptionModule* initial_cdm, + MediaLog* media_log); + virtual ~WebMediaPlayerGStreamer(); + + void SetVideoFrameProviderClient( + cc::VideoFrameProvider::Client* client) override; + bool UpdateCurrentFrame(base::TimeTicks deadline_min, + base::TimeTicks deadline_max) override; + bool HasCurrentFrame() override; + scoped_refptr GetCurrentFrame() override; + void PutCurrentFrame() override; + + virtual void load(LoadType load_type, + const blink::WebURL& url, + CORSMode cors_mode); + + // Playback controls. + virtual void play(); + virtual void pause(); + virtual bool supportsSave() const; + virtual void seek(double seconds); + virtual void setRate(double rate); + virtual void setVolume(double volume); + virtual void setSinkId(const blink::WebString& device_id, + WebSetSinkIdCB* web_callbacks); + virtual void setPreload(blink::WebMediaPlayer::Preload preload); + virtual blink::WebTimeRanges buffered() const; + virtual blink::WebTimeRanges seekable() const; + + // Methods for painting. + virtual void paint(blink::WebCanvas* canvas, + const blink::WebRect& rect, + unsigned char alpha, + SkXfermode::Mode mode); + + // True if the loaded media has a playable video/audio track. + virtual bool hasVideo() const; + virtual bool hasAudio() const; + + // Dimensions of the video. + virtual blink::WebSize naturalSize() const; + + // Getters of playback state. + virtual bool paused() const; + virtual bool seeking() const; + virtual double duration() const; + virtual double timelineOffset() const; + virtual double currentTime() const; + + // Internal states of loading and network. + virtual blink::WebMediaPlayer::NetworkState networkState() const; + virtual blink::WebMediaPlayer::ReadyState readyState() const; + + virtual bool didLoadingProgress(); + + virtual bool hasSingleSecurityOrigin() const; + virtual bool didPassCORSAccessCheck() const; + + virtual double mediaTimeForTimeValue(double timeValue) const; + + virtual unsigned decodedFrameCount() const; + virtual unsigned droppedFrameCount() const; + virtual unsigned audioDecodedByteCount() const; + virtual unsigned videoDecodedByteCount() const; + + // TODO(dshwang): remove |level|. crbug.com/443151 + virtual bool copyVideoTextureToPlatformTexture( + blink::WebGraphicsContext3D* web_graphics_context, + unsigned int texture, + unsigned int level, + unsigned int internal_format, + unsigned int type, + bool premultiply_alpha, + bool flip_y); + virtual bool copyVideoTextureToPlatformTexture( + blink::WebGraphicsContext3D* web_graphics_context, + unsigned int texture, + unsigned int internal_format, + unsigned int type, + bool premultiply_alpha, + bool flip_y); + + virtual MediaKeyException generateKeyRequest( + const blink::WebString& key_system, + const unsigned char* init_data, + unsigned init_data_length); + + virtual MediaKeyException addKey(const blink::WebString& key_system, + const unsigned char* key, + unsigned key_length, + const unsigned char* init_data, + unsigned init_data_length, + const blink::WebString& session_id); + + virtual MediaKeyException cancelKeyRequest( + const blink::WebString& key_system, + const blink::WebString& session_id); + + virtual void setContentDecryptionModule( + blink::WebContentDecryptionModule* cdm, + blink::WebContentDecryptionModuleResult result); + + void OnAddTextTrack(const TextTrackConfig& config, + const AddTextTrackDoneCB& done_cb); + + void OnReleaseTexture(unsigned texture_id, uint32 release_sync_point); + + void OnSetCurrentFrame(int width, + int height, + unsigned texture_id, + unsigned target, + const std::vector& name); + void OnMediaDurationChanged(const base::TimeDelta& duration); + void OnMediaPlaybackCompleted(); + void OnMediaBufferingUpdate(int percent); + void OnSeekCompleted(const base::TimeDelta& current_time); + void OnMediaError(int error); + void OnVideoSizeChanged(int width, int height); + void OnTimeUpdate(base::TimeDelta current_timestamp, + base::TimeTicks current_time_ticks); + void OnMediaPlayerReleased(); + void OnPlayerPlay(); + void OnPlayerPause(); + + void OnSourceSelected(); + void OnSourceDeleted(WebMediaSourceGStreamer*); + void OnAddSourceId(const std::string& id); + void OnRemoveSourceId(const std::string& id); + void OnInitSegmentReceived(const std::string& id); + void OnBufferedRangeUpdate(const std::string& id, + const std::vector& raw_ranges); + void OnTimestampOffsetUpdate(const std::string& id, + const base::TimeDelta& timestamp_offset); + void OnNeedKey(const std::string& system_id, + const std::vector& init_data); + + private: + void SetupGLContext(); + void SetCurrentFrameInternal(scoped_refptr& frame); + + // Called after |defer_load_cb_| has decided to allow the load. If + // |defer_load_cb_| is null this is called immediately. + void DoLoad(LoadType load_type, const blink::WebURL& url, CORSMode cors_mode); + + // Helpers that set the network/ready state and notifies the client if + // they've changed. + void SetNetworkState(blink::WebMediaPlayer::NetworkState state); + void SetReadyState(blink::WebMediaPlayer::ReadyState state); + + // Called when the demuxer encounters encrypted streams. + void OnEncryptedMediaInitData(EmeInitDataType init_data_type, + const std::vector& init_data); + + // Called when a decoder detects that the key needed to decrypt the stream + // is not available. + void OnWaitingForDecryptionKey(); + + void SetCdm(const CdmAttachedCB& cdm_attached_cb, CdmContext* cdm_context); + + // Called when a CDM has been attached to the |pipeline_|. + void OnCdmAttached(blink::WebContentDecryptionModuleResult result, + bool success); + + void OnCdmKeysReady(const std::string& session_id, + bool has_additional_usable_key, + CdmKeysInfo keys_info); + + void UpdatePlayingState(bool is_playing); + + // A pointer back to the compositor to inform it about state changes. This is + // not NULL while the compositor is actively using this webmediaplayer. + // Accessed on main thread and on compositor thread when main thread is + // blocked. + cc::VideoFrameProvider::Client* video_frame_provider_client_; + const scoped_refptr compositor_loop_; + scoped_ptr video_weblayer_; + scoped_refptr current_frame_; + base::Lock current_frame_lock_; + + blink::WebLocalFrame* frame_; + + // TODO(hclam): get rid of these members and read from the pipeline directly. + blink::WebMediaPlayer::NetworkState network_state_; + blink::WebMediaPlayer::ReadyState ready_state_; + + // Preload state for when |data_source_| is created after setPreload(). + BufferedDataSource::Preload preload_; + + // Task runner for posting tasks on Chrome's main thread. Also used + // for DCHECKs so methods calls won't execute in the wrong thread. + const scoped_refptr main_task_runner_; + + scoped_refptr media_task_runner_; + scoped_refptr media_log_; + + // The LoadType passed in the |load_type| parameter of the load() call. + LoadType load_type_; + + // Cache of metadata for answering hasAudio(), hasVideo(), and naturalSize(). + PipelineMetadata pipeline_metadata_; + + // Playback state. + // + // TODO(scherkus): we have these because Pipeline favours the simplicity of a + // single "playback rate" over worrying about paused/stopped etc... It forces + // all clients to manage the pause+playback rate externally, but is that + // really a bad thing? + // + // TODO(scherkus): since SetPlaybackRate(0) is asynchronous and we don't want + // to hang the render thread during pause(), we record the time at the same + // time we pause and then return that value in currentTime(). Otherwise our + // clock can creep forward a little bit while the asynchronous + // SetPlaybackRate(0) is being executed. + bool paused_; + bool seeking_; + base::TimeDelta seek_time_; + double playback_rate_; + base::TimeDelta paused_time_; + base::TimeDelta duration_; + + // Size of the video. + blink::WebSize natural_size_; + + // TODO(scherkus): Replace with an explicit ended signal to HTMLMediaElement, + // see http://crbug.com/409280 + bool ended_; + + // Seek gets pending if another seek is in progress. Only last pending seek + // will have effect. + bool pending_seek_; + base::TimeDelta pending_seek_time_; + + bool did_loading_progress_; + + // Save the list of buffered time ranges. + blink::WebTimeRanges buffered_; + + blink::WebMediaPlayerClient* client_; + + // base::TickClock used by |interpolator_|. + base::DefaultTickClock default_tick_clock_; + + // Tracks the most recent media time update and provides interpolated values + // as playback progresses. + media::TimeDeltaInterpolator interpolator_; + + base::WeakPtr delegate_; + + WebMediaSourceGStreamer* media_source_; + + bool supports_save_; + + static base::AtomicSequenceNumber next_player_id_; + WebMediaPlayerMessageDispatcher message_dispatcher_; + + EncryptedMediaPlayerSupport encrypted_media_support_; + + DISALLOW_COPY_AND_ASSIGN(WebMediaPlayerGStreamer); +}; + +} // namespace media + +#endif // MEDIA_GSTREAMER_WEBMEDIAPLAYER_GSTREAMER_H_ diff --git a/content/renderer/media/gstreamer/webmediasource_gstreamer.cc b/content/renderer/media/gstreamer/webmediasource_gstreamer.cc new file mode 100644 index 0000000..5c66d70 --- /dev/null +++ b/content/renderer/media/gstreamer/webmediasource_gstreamer.cc @@ -0,0 +1,128 @@ +// Copyright 2015 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 "content/renderer/media/gstreamer/webmediasource_gstreamer.h" + +#include "base/guid.h" +#include "content/renderer/media/gstreamer/webmediaplayer_gstreamer.h" +#include "content/renderer/media/gstreamer/websourcebuffer_gstreamer.h" +#include "media/blink/websourcebuffer_impl.h" +#include "media/filters/chunk_demuxer.h" +#include "third_party/WebKit/public/platform/WebCString.h" +#include "third_party/WebKit/public/platform/WebString.h" + +using ::blink::WebString; +using ::blink::WebMediaSource; + +namespace media { + +#define STATIC_ASSERT_MATCHING_STATUS_ENUM(webkit_name, chromium_name) \ + static_assert(static_cast(WebMediaSource::webkit_name) == \ + static_cast(ChunkDemuxer::chromium_name), \ + "mismatching status enum values: " #webkit_name) +STATIC_ASSERT_MATCHING_STATUS_ENUM(AddStatusOk, kOk); +STATIC_ASSERT_MATCHING_STATUS_ENUM(AddStatusNotSupported, kNotSupported); +STATIC_ASSERT_MATCHING_STATUS_ENUM(AddStatusReachedIdLimit, kReachedIdLimit); +#undef STATIC_ASSERT_MATCHING_STATUS_ENUM + +WebMediaSourceGStreamer::WebMediaSourceGStreamer( + WebMediaPlayerGStreamer* player, + WebMediaPlayerMessageDispatcher* message_dispatcher, + const SetNetworkStateCB& set_network_state_cb) + : player_(player), + message_dispatcher_(message_dispatcher), + set_network_state_cb_(set_network_state_cb) { + DCHECK(player_); + DCHECK(message_dispatcher_); +} + +WebMediaSourceGStreamer::~WebMediaSourceGStreamer() { + player_->OnSourceDeleted(this); +} + +WebMediaSource::AddStatus WebMediaSourceGStreamer::addSourceBuffer( + const blink::WebString& type, + const blink::WebVector& codecs, + blink::WebSourceBuffer** source_buffer) { + std::string id = base::GenerateGUID(); + std::vector new_codecs(codecs.size()); + for (size_t i = 0; i < codecs.size(); ++i) + new_codecs[i] = codecs[i].utf8().data(); + + if (!message_dispatcher_->SendAddSourceId(id, type.utf8().data(), new_codecs)) + return WebMediaSource::AddStatusNotSupported; + + WebSourceBufferGStreamer* source_buffer_gstreamer = + new WebSourceBufferGStreamer(id, message_dispatcher_); + + *source_buffer = source_buffer_gstreamer; + source_buffers_[id] = source_buffer_gstreamer; + + return WebMediaSource::AddStatusOk; +} + +double WebMediaSourceGStreamer::duration() { + return player_->duration(); +} + +void WebMediaSourceGStreamer::setDuration(double new_duration) { + DCHECK_GE(new_duration, 0); + message_dispatcher_->SendSetDuration( + base::TimeDelta::FromSecondsD(new_duration)); +} + +void WebMediaSourceGStreamer::markEndOfStream( + WebMediaSource::EndOfStreamStatus) { + message_dispatcher_->SendMarkEndOfStream(); +} + +void WebMediaSourceGStreamer::unmarkEndOfStream() { + message_dispatcher_->SendUnmarkEndOfStream(); +} + +void WebMediaSourceGStreamer::OnAddSourceId(const std::string& id) { + if (id.empty() || source_buffers_.find(id) == source_buffers_.end()) + set_network_state_cb_.Run(blink::WebMediaPlayer::NetworkStateFormatError); +} + +void WebMediaSourceGStreamer::OnRemoveSourceId(const std::string& id) { + WebSourceBufferGStreamerMap::iterator iter = source_buffers_.find(id); + if (iter == source_buffers_.end()) + set_network_state_cb_.Run(blink::WebMediaPlayer::NetworkStateFormatError); + else + source_buffers_.erase(iter); +} + +void WebMediaSourceGStreamer::OnInitSegmentReceived(const std::string& id) { + WebSourceBufferGStreamerMap::iterator iter = source_buffers_.find(id); + + if (iter == source_buffers_.end()) + set_network_state_cb_.Run(blink::WebMediaPlayer::NetworkStateFormatError); + else + iter->second->OnInitSegmentReceived(); +} + +void WebMediaSourceGStreamer::OnBufferedRangeUpdate( + const std::string& id, + const Ranges& ranges) { + WebSourceBufferGStreamerMap::iterator iter = source_buffers_.find(id); + + if (iter == source_buffers_.end()) + set_network_state_cb_.Run(blink::WebMediaPlayer::NetworkStateFormatError); + else + iter->second->OnBufferedRangeUpdate(ranges); +} + +void WebMediaSourceGStreamer::OnTimestampOffsetUpdate( + const std::string& id, + const base::TimeDelta& timestamp_offset) { + WebSourceBufferGStreamerMap::iterator iter = source_buffers_.find(id); + + if (iter == source_buffers_.end()) + set_network_state_cb_.Run(blink::WebMediaPlayer::NetworkStateFormatError); + else + iter->second->OnTimestampOffsetUpdate(timestamp_offset); +} + +} // namespace media diff --git a/content/renderer/media/gstreamer/webmediasource_gstreamer.h b/content/renderer/media/gstreamer/webmediasource_gstreamer.h new file mode 100644 index 0000000..754dcf6 --- /dev/null +++ b/content/renderer/media/gstreamer/webmediasource_gstreamer.h @@ -0,0 +1,68 @@ +// Copyright 2015 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. + +#ifndef CONTENT_RENDERER_MEDIA_GSTREAMER_WEBMEDIASOURCE_GSTREAMER_H_ +#define CONTENT_RENDERER_MEDIA_GSTREAMER_WEBMEDIASOURCE_GSTREAMER_H_ + +#include +#include + +#include "media/base/media_export.h" +#include "media/base/media_log.h" +#include "media/base/ranges.h" +#include "third_party/WebKit/public/platform/WebMediaPlayer.h" +#include "third_party/WebKit/public/platform/WebMediaSource.h" + +namespace media { +class WebMediaPlayerGStreamer; +class WebMediaPlayerMessageDispatcher; +class WebSourceBufferGStreamer; + +class WebMediaSourceGStreamer : public blink::WebMediaSource { + public: + typedef base::Callback + SetNetworkStateCB; + + WebMediaSourceGStreamer(WebMediaPlayerGStreamer* player, + WebMediaPlayerMessageDispatcher* message_dispatcher, + const SetNetworkStateCB& set_network_state_cb); + virtual ~WebMediaSourceGStreamer(); + + // blink::WebMediaSource implementation. + virtual AddStatus addSourceBuffer( + const blink::WebString& type, + const blink::WebVector& codecs, + blink::WebSourceBuffer** source_buffer); + virtual double duration(); + virtual void setDuration(double duration); + virtual void markEndOfStream(EndOfStreamStatus status); + virtual void unmarkEndOfStream(); + + void OnAddSourceId(const std::string& id); + void OnRemoveSourceId(const std::string& id); + void OnInitSegmentReceived(const std::string& id); + void OnBufferedRangeUpdate(const std::string& id, + const Ranges& ranges); + void OnTimestampOffsetUpdate(const std::string& id, + const base::TimeDelta& timestamp_offset); + + private: + // WebMediaPlayerGStreamer will not be released before + // WebMediaSourceGStreamer. + WebMediaPlayerGStreamer* player_; + WebMediaPlayerMessageDispatcher* message_dispatcher_; + SetNetworkStateCB set_network_state_cb_; + + typedef base::hash_map + WebSourceBufferGStreamerMap; + // Threading notes: |dispatchers_| is only accessed on the IO thread. Every + // other field is protected by |lock_|. + WebSourceBufferGStreamerMap source_buffers_; + + DISALLOW_COPY_AND_ASSIGN(WebMediaSourceGStreamer); +}; + +} // namespace media + +#endif // CONTENT_RENDERER_MEDIA_GSTREAMER_WEBMEDIASOURCE_GSTREAMER_H_ diff --git a/content/renderer/media/gstreamer/websourcebuffer_gstreamer.cc b/content/renderer/media/gstreamer/websourcebuffer_gstreamer.cc new file mode 100644 index 0000000..5467cdc --- /dev/null +++ b/content/renderer/media/gstreamer/websourcebuffer_gstreamer.cc @@ -0,0 +1,164 @@ +// Copyright 2013 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 "content/renderer/media/gstreamer/websourcebuffer_gstreamer.h" + +#include +#include + +#include "base/bind.h" +#include "base/callback.h" +#include "base/callback_helpers.h" +#include "content/renderer/media/gstreamer/webmediaplayer_gstreamer.h" +#include "third_party/WebKit/public/platform/WebSourceBufferClient.h" + +namespace media { + +static base::TimeDelta DoubleToTimeDelta(double time) { + DCHECK(!std::isnan(time)); + DCHECK_NE(time, -std::numeric_limits::infinity()); + + if (time == std::numeric_limits::infinity()) + return kInfiniteDuration(); + + // Don't use base::TimeDelta::Max() here, as we want the largest finite time + // delta. + base::TimeDelta max_time = base::TimeDelta::FromInternalValue(kint64max - 1); + double max_time_in_seconds = max_time.InSecondsF(); + + if (time >= max_time_in_seconds) + return max_time; + + return base::TimeDelta::FromMicroseconds(time * + base::Time::kMicrosecondsPerSecond); +} + +WebSourceBufferGStreamer::WebSourceBufferGStreamer( + const std::string& id, + WebMediaPlayerMessageDispatcher* message_dispatcher) + : id_(id), + message_dispatcher_(message_dispatcher), + client_(NULL), + initialization_segment_received(false), + append_window_end_(kInfiniteDuration()) { + DCHECK(message_dispatcher); +} + +WebSourceBufferGStreamer::~WebSourceBufferGStreamer() { + DCHECK(!message_dispatcher_) + << "Object destroyed w/o removedFromMediaSource() call"; + DCHECK(!client_); +} + +void WebSourceBufferGStreamer::setClient(blink::WebSourceBufferClient* client) { + DCHECK(client); + DCHECK(!client_); + client_ = client; +} + +bool WebSourceBufferGStreamer::setMode(WebSourceBuffer::AppendMode mode) { + switch (mode) { + case WebSourceBuffer::AppendModeSegments: + message_dispatcher_->SendSetSequenceMode(id_, false); + return true; + case WebSourceBuffer::AppendModeSequence: + message_dispatcher_->SendSetSequenceMode(id_, true); + return true; + } + + NOTREACHED(); + return false; +} + +blink::WebTimeRanges WebSourceBufferGStreamer::buffered() { + blink::WebTimeRanges result(ranges_.size()); + for (size_t i = 0; i < ranges_.size(); i++) { + result[i].start = ranges_.start(i).InSecondsF(); + result[i].end = ranges_.end(i).InSecondsF(); + } + + return result; +} + +void WebSourceBufferGStreamer::append(const unsigned char* data, + unsigned length, + double* timestamp_offset) { + if (initialization_segment_received) { + initialization_segment_received = false; + client_->initializationSegmentReceived(); + } + + base::TimeDelta old_offset = timestamp_offset_; + + message_dispatcher_->SendAppendData(id_, data, length, append_window_start_, + append_window_end_, timestamp_offset_); + + // Coded frame processing may update the timestamp offset. If the caller + // provides a non-NULL |timestamp_offset| and frame processing changes the + // timestamp offset, report the new offset to the caller. Do not update the + // caller's offset otherwise, to preserve any pre-existing value that may have + // more than microsecond precision. + if (timestamp_offset && old_offset != timestamp_offset_) + *timestamp_offset = timestamp_offset_.InSecondsF(); +} + +void WebSourceBufferGStreamer::abort() { + message_dispatcher_->SendAbort(id_); +} + +void WebSourceBufferGStreamer::remove(double start, double end) { + DCHECK_GE(start, 0); + DCHECK_GE(end, 0); + message_dispatcher_->SendRemoveSegment(id_, DoubleToTimeDelta(start), + DoubleToTimeDelta(end)); +} + +bool WebSourceBufferGStreamer::setTimestampOffset(double offset) { + timestamp_offset_ = DoubleToTimeDelta(offset); + + // http://www.w3.org/TR/media-source/#widl-SourceBuffer-timestampOffset + // Step 6: If the mode attribute equals "sequence", then set the group start + // timestamp to new timestamp offset. + message_dispatcher_->SendSetGroupStartTimestampIfInSequenceMode( + id_, timestamp_offset_); + return true; +} + +void WebSourceBufferGStreamer::setAppendWindowStart(double start) { + DCHECK_GE(start, 0); + append_window_start_ = DoubleToTimeDelta(start); +} + +void WebSourceBufferGStreamer::setAppendWindowEnd(double end) { + DCHECK_GE(end, 0); + append_window_end_ = DoubleToTimeDelta(end); +} + +void WebSourceBufferGStreamer::removedFromMediaSource() { + message_dispatcher_->SendRemoveSourceId(id_); + message_dispatcher_ = NULL; + client_ = NULL; +} + +void WebSourceBufferGStreamer::OnInitSegmentReceived() { + DVLOG(1) << __FUNCTION__; + // Cannot notify the client here because SourceBuffer + // might be updating. Indeed there is ASSERT(m_updating) + // in SourceBuffer::initializationSegmentReceived(). + initialization_segment_received = true; +} + +void WebSourceBufferGStreamer::OnBufferedRangeUpdate( + const Ranges& ranges) { + DVLOG(1) << __FUNCTION__; + ranges_ = ranges; +} + +void WebSourceBufferGStreamer::OnTimestampOffsetUpdate( + const base::TimeDelta& timestamp_offset) { + DVLOG(1) << __FUNCTION__; + timestamp_offset_ = timestamp_offset; +} + +} // namespace media diff --git a/content/renderer/media/gstreamer/websourcebuffer_gstreamer.h b/content/renderer/media/gstreamer/websourcebuffer_gstreamer.h new file mode 100644 index 0000000..d13a85d --- /dev/null +++ b/content/renderer/media/gstreamer/websourcebuffer_gstreamer.h @@ -0,0 +1,67 @@ +// Copyright 2015 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. + +#ifndef CONTENT_RENDERER_MEDIA_GSTREAMER_WEBSOURCEBUFFER_GSTREAMER_H_ +#define CONTENT_RENDERER_MEDIA_GSTREAMER_WEBSOURCEBUFFER_GSTREAMER_H_ + +#include + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/time/time.h" +#include "media/base/ranges.h" +#include "third_party/WebKit/public/platform/WebSourceBuffer.h" + +namespace media { +class WebMediaPlayerMessageDispatcher; + +class WebSourceBufferGStreamer : public blink::WebSourceBuffer { + public: + WebSourceBufferGStreamer(const std::string& id, + WebMediaPlayerMessageDispatcher* message_dispatcher); + virtual ~WebSourceBufferGStreamer(); + + // blink::WebSourceBuffer implementation. + virtual void setClient(blink::WebSourceBufferClient* client); + virtual bool setMode(AppendMode mode); + virtual blink::WebTimeRanges buffered(); + virtual void append(const unsigned char* data, + unsigned length, + double* timestamp_offset); + virtual void abort(); + virtual void remove(double start, double end); + virtual bool setTimestampOffset(double offset); + virtual void setAppendWindowStart(double start); + virtual void setAppendWindowEnd(double end); + virtual void removedFromMediaSource(); + + void OnInitSegmentReceived(); + void OnBufferedRangeUpdate(const Ranges& ranges); + void OnTimestampOffsetUpdate(const base::TimeDelta& timestamp_offset); + + private: + std::string id_; + WebMediaPlayerMessageDispatcher* + message_dispatcher_; // Owned by WebMediaPlayerGStreamer. + + blink::WebSourceBufferClient* client_; + + bool initialization_segment_received; + + // Controls the offset applied to timestamps when processing appended media + // segments. It is initially 0, which indicates that no offset is being + // applied. Both setTimestampOffset() and append() may update this value. + base::TimeDelta timestamp_offset_; + + base::TimeDelta append_window_start_; + base::TimeDelta append_window_end_; + + Ranges ranges_; + + DISALLOW_COPY_AND_ASSIGN(WebSourceBufferGStreamer); +}; + +} // namespace media + +#endif // CONTENT_RENDERER_MEDIA_GSTREAMER_WEBSOURCEBUFFER_GSTREAMER_H_ diff --git a/content/renderer/pepper/video_decoder_shim.cc b/content/renderer/pepper/video_decoder_shim.cc index ec176bf..c3cac28 100644 --- a/content/renderer/pepper/video_decoder_shim.cc +++ b/content/renderer/pepper/video_decoder_shim.cc @@ -679,12 +679,18 @@ void VideoDecoderShim::DecoderImpl::Initialize( new media::VpxVideoDecoder(base::ThreadTaskRunnerHandle::Get())); } else #endif + +#if !defined(MEDIA_DISABLE_FFMPEG) && !defined(DISABLE_FFMPEG_VIDEO_DECODERS) { scoped_ptr ffmpeg_video_decoder( new media::FFmpegVideoDecoder(base::ThreadTaskRunnerHandle::Get())); ffmpeg_video_decoder->set_decode_nalus(true); decoder_ = ffmpeg_video_decoder.Pass(); } +#elif defined(MEDIA_DISABLE_LIBVPX) + OnInitDone(false); + return; +#endif // VpxVideoDecoder and FFmpegVideoDecoder support only one pending Decode() // request. diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc index 2e7d77a..d479ce9 100644 --- a/content/renderer/render_frame_impl.cc +++ b/content/renderer/render_frame_impl.cc @@ -188,6 +188,10 @@ #include "media/renderers/default_renderer_factory.h" #endif +#if defined(USE_GSTREAMER) +#include "media/gstreamer/webmediaplayer_gstreamer.h" +#endif + #if defined(ENABLE_WEBVR) #include "content/renderer/vr/vr_dispatcher.h" #endif @@ -1970,6 +1974,35 @@ blink::WebMediaPlayer* RenderFrameImpl::createMediaPlayer( } #endif // defined(VIDEO_HOLE) +#if defined(USE_GSTREAMER) + // TODO: Fix kEnableGStreamerMediaBackend. + if (1 || + base::CommandLine::ForCurrentProcess()->HasSwitch( + switches::kEnableGStreamerMediaBackend)) { + + if (!content::RenderThreadImpl::current()->GetMediaChannel()) { + content::RenderThreadImpl::current()->EstablishMediaChannelSync(); + } + + if (!content::RenderThreadImpl::current()->GetMediaChannel()) { + LOG(ERROR) << "Cannot create WebMediaPlayerGStreamer because there is no media channel"; + return NULL; + } + + DVLOG(1) << __FUNCTION__ << "(Create WebMediaPlayerGStreamer)"; + blink::WebMediaPlayer* player_gst = new media::WebMediaPlayerGStreamer( + frame, client, weak_factory_.GetWeakPtr(), GetCdmFactory(), + GetMediaPermission(), initial_cdm, new RenderMediaLog()); + + if (!player_gst) { + LOG(ERROR) << "Failed to create WebMediaPlayerGStreamer"; + return NULL; + } + + return player_gst; + } +#endif // defined(USE_GSTREAMER) + blink::WebMediaStream web_stream( blink::WebMediaStreamRegistry::lookupMediaStreamDescriptor(url)); if (!web_stream.isNull()) diff --git a/content/renderer/render_thread_impl.cc b/content/renderer/render_thread_impl.cc index 8a155a4..957a864 100644 --- a/content/renderer/render_thread_impl.cc +++ b/content/renderer/render_thread_impl.cc @@ -67,6 +67,10 @@ #include "content/common/gpu/gpu_process_launch_causes.h" #include "content/common/render_frame_setup.mojom.h" #include "content/common/resource_messages.h" +#if defined(USE_GSTREAMER) +#include "content/common/media/media_channel_host.h" +#include "content/common/media/media_messages.h" +#endif #include "content/common/view_messages.h" #include "content/common/worker_messages.h" #include "content/public/common/content_constants.h" @@ -880,7 +884,10 @@ void RenderThreadImpl::Shutdown() { if (gpu_channel_.get()) gpu_channel_->DestroyChannel(); - +#if defined(USE_GSTREAMER) + if (media_channel_.get()) + media_channel_->DestroyChannel(); +#endif // TODO(port) #if defined(OS_WIN) // Clean up plugin channels before this thread goes away. @@ -1713,7 +1720,50 @@ GpuChannelHost* RenderThreadImpl::EstablishGpuChannelSync( gpu_memory_buffer_manager()); return gpu_channel_.get(); } +#if defined(USE_GSTREAMER) +MediaChannelHost* RenderThreadImpl::EstablishMediaChannelSync( + CauseForMediaLaunch cause_for_media_launch) { + if (media_channel_.get()) { + // Do nothing if we already have a Media channel or are already + // establishing one. + if (!media_channel_->IsLost()) + return media_channel_.get(); + + // Recreate the channel if it has been lost. + media_channel_->DestroyChannel(); + media_channel_ = NULL; + } + + // Ask the browser for the channel name. + int client_id = 0; + IPC::ChannelHandle channel_handle; + + if (!Send(new MediaHostMsg_EstablishMediaChannel( + cause_for_media_launch, &client_id, &channel_handle)) || +#if defined(OS_POSIX) + channel_handle.socket.fd == -1 || +#endif + channel_handle.name.empty()) { + // Otherwise cancel the connection. + return NULL; + } + + media_channel_ = content::MediaChannelHost::Create( + channel_handle, content::ChildProcess::current()->GetShutDownEvent()); + + return media_channel_.get(); +} + +MediaChannelHost* RenderThreadImpl::GetMediaChannel() { + if (!media_channel_.get()) + return NULL; + if (media_channel_->IsLost()) + return NULL; + + return media_channel_.get(); +} +#endif blink::WebMediaStreamCenter* RenderThreadImpl::CreateMediaStreamCenter( blink::WebMediaStreamCenterClient* client) { #if defined(OS_ANDROID) diff --git a/content/renderer/render_thread_impl.h b/content/renderer/render_thread_impl.h index a6343d1..603a332 100644 --- a/content/renderer/render_thread_impl.h +++ b/content/renderer/render_thread_impl.h @@ -23,6 +23,10 @@ #include "content/common/frame_replication_state.h" #include "content/common/gpu/client/gpu_channel_host.h" #include "content/common/gpu/gpu_result_codes.h" +#if defined(USE_GSTREAMER) +#include "content/common/media/media_channel_host.h" +#include "content/common/media/media_process_launch_causes.h" +#endif #include "content/public/renderer/render_thread.h" #include "content/renderer/gpu/compositor_dependencies.h" #include "net/base/network_change_notifier.h" @@ -94,6 +98,7 @@ class DevToolsAgentFilter; class DomStorageDispatcher; class EmbeddedWorkerDispatcher; class GpuChannelHost; +class MediaChannelHost; class IndexedDBDispatcher; class InputHandlerManager; class MediaStreamCenter; @@ -212,6 +217,15 @@ class CONTENT_EXPORT RenderThreadImpl // time this routine returns. GpuChannelHost* EstablishGpuChannelSync(CauseForGpuLaunch); +#if defined(USE_GSTREAMER) + // Synchronously establish a channel to the media process if not previously + // established or if it has been lost or get the current channel. + MediaChannelHost* EstablishMediaChannelSync( + CauseForMediaLaunch = CAUSE_FOR_MEDIA_LAUNCH_RENDERER); + + // Get media channel if it is created and not lost. + MediaChannelHost* GetMediaChannel(); +#endif // This method modifies how the next message is sent. Normally, when sending // a synchronous message that runs a nested message loop, we need to suspend @@ -542,7 +556,10 @@ class CONTENT_EXPORT RenderThreadImpl // The channel from the renderer process to the GPU process. scoped_refptr gpu_channel_; - +#if defined(USE_GSTREAMER) + // The channel from the renderer process to the media process. + scoped_refptr media_channel_; +#endif // Cache of variables that are needed on the compositor thread by // GpuChannelHostFactory methods. scoped_refptr io_thread_task_runner_; diff --git a/content/shell/app/shell_crash_reporter_client.cc b/content/shell/app/shell_crash_reporter_client.cc index fab0e90..215dda3 100644 --- a/content/shell/app/shell_crash_reporter_client.cc +++ b/content/shell/app/shell_crash_reporter_client.cc @@ -68,7 +68,12 @@ bool ShellCrashReporterClient::EnableBreakpadForProcess( process_type == switches::kPluginProcess || process_type == switches::kPpapiPluginProcess || process_type == switches::kZygoteProcess || +#if defined(USE_GSTREAMER) + process_type == switches::kGpuProcess || + process_type == switches::kMediaProcess; +#else process_type == switches::kGpuProcess; +#endif } } // namespace content diff --git a/content/shell/browser/shell_content_browser_client.cc b/content/shell/browser/shell_content_browser_client.cc index fd8a49c..404aa60 100644 --- a/content/shell/browser/shell_content_browser_client.cc +++ b/content/shell/browser/shell_content_browser_client.cc @@ -114,6 +114,15 @@ int GetCrashSignalFD(const base::CommandLine& command_line) { return crash_handler->GetDeathSignalSocket(); } +#if defined(USE_GSTREAMER) + if (process_type == switches::kMediaProcess) { + static breakpad::CrashHandlerHostLinux* crash_handler = NULL; + if (!crash_handler) + crash_handler = CreateCrashHandlerHost(process_type); + return crash_handler->GetDeathSignalSocket(); + } +#endif + return -1; } #endif // defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_ANDROID) diff --git a/gpu/command_buffer/service/feature_info.cc b/gpu/command_buffer/service/feature_info.cc index c747383..41714d5 100644 --- a/gpu/command_buffer/service/feature_info.cc +++ b/gpu/command_buffer/service/feature_info.cc @@ -239,6 +239,20 @@ bool IsGL_REDSupportedOnFBOs() { GLubyte data[1] = {0}; glTexImage2D(GL_TEXTURE_2D, 0, GL_RED_EXT, 1, 1, 0, GL_RED_EXT, GL_UNSIGNED_BYTE, data); + +#if defined(USE_GSTREAMER) + // Workaround glCheckFramebufferStatusEXT that may + // returns true even if glTexImage2D failed. + GLenum gl_error = glGetError(); + if (gl_error != GL_NO_ERROR) { + LOG(INFO) << "RED not supported on FBO"; + glDeleteTextures(1, &textureId); + glBindFramebufferEXT(GL_FRAMEBUFFER, static_cast(fb_binding)); + glBindTexture(GL_TEXTURE_2D, static_cast(tex_binding)); + return false; + } +#endif + GLuint textureFBOID = 0; glGenFramebuffersEXT(1, &textureFBOID); glBindFramebufferEXT(GL_FRAMEBUFFER, textureFBOID); diff --git a/gpu/config/gpu_blacklist.cc b/gpu/config/gpu_blacklist.cc index a359123..f554f1b 100644 --- a/gpu/config/gpu_blacklist.cc +++ b/gpu/config/gpu_blacklist.cc @@ -20,8 +20,11 @@ GpuBlacklist* GpuBlacklist::Create() { GpuBlacklist* list = new GpuBlacklist(); list->AddSupportedFeature("accelerated_2d_canvas", GPU_FEATURE_TYPE_ACCELERATED_2D_CANVAS); +#if !defined(USE_GSTREAMER) + // Do not black list gpu compositing for GStreamer backend (GstGL). list->AddSupportedFeature("gpu_compositing", GPU_FEATURE_TYPE_GPU_COMPOSITING); +#endif list->AddSupportedFeature("webgl", GPU_FEATURE_TYPE_WEBGL); list->AddSupportedFeature("flash_3d", diff --git a/ipc/ipc_message_start.h b/ipc/ipc_message_start.h index bd47f20..a7ea118 100644 --- a/ipc/ipc_message_start.h +++ b/ipc/ipc_message_start.h @@ -70,6 +70,9 @@ enum IPCMessageStart { MetroViewerMsgStart, CCMsgStart, MediaPlayerMsgStart, +#if defined(USE_GSTREAMER) + MediaMsgStart, +#endif TracingMsgStart, PeerConnectionTrackerMsgStart, VisitedLinkMsgStart, diff --git a/media/base/cdm_key_information.h b/media/base/cdm_key_information.h index 4f9d8e0..ca4f044 100644 --- a/media/base/cdm_key_information.h +++ b/media/base/cdm_key_information.h @@ -28,6 +28,9 @@ struct MEDIA_EXPORT CdmKeyInformation { ~CdmKeyInformation(); std::vector key_id; +#if defined(USE_GSTREAMER) + std::string key; +#endif KeyStatus status; uint32 system_code; }; diff --git a/media/base/media_switches.cc b/media/base/media_switches.cc index 0b01546..916a700 100644 --- a/media/base/media_switches.cc +++ b/media/base/media_switches.cc @@ -4,6 +4,10 @@ #include "media/base/media_switches.h" +#if defined(USE_GSTREAMER) +#include "base/basictypes.h" +#endif + namespace switches { // Allow users to specify a custom buffer size for debugging purpose. @@ -114,6 +118,16 @@ const char kEnableInbandTextTracks[] = "enable-inband-text-tracks"; const char kRequireAudioHardwareForTesting[] = "require-audio-hardware-for-testing"; +#if defined(USE_GSTREAMER) +const char kEnableMediaDebugging[] = "enable-media-debugging"; + +const char* kMediaSwitches[] = { + kEnableMediaDebugging, +}; + +const int kNumMediaSwitches = arraysize(kMediaSwitches); +#endif + // Allows clients to override the threshold for when the media renderer will // declare the underflow state for the video stream when audio is present. // TODO(dalecurtis): Remove once experiments for http://crbug.com/470940 finish. diff --git a/media/base/media_switches.h b/media/base/media_switches.h index c8c8488..790babb 100644 --- a/media/base/media_switches.h +++ b/media/base/media_switches.h @@ -47,6 +47,11 @@ MEDIA_EXPORT extern const char kWaveOutBuffers[]; MEDIA_EXPORT extern const char kUseCras[]; #endif +#if defined(USE_GSTREAMER) +MEDIA_EXPORT extern const char* kMediaSwitches[]; +MEDIA_EXPORT extern const int kNumMediaSwitches; +#endif + MEDIA_EXPORT extern const char kEnableAudioHangMonitor[]; MEDIA_EXPORT extern const char kUseFakeDeviceForMediaStream[]; diff --git a/media/base/mime_util.cc b/media/base/mime_util.cc index 535837c..1e7f63e 100644 --- a/media/base/mime_util.cc +++ b/media/base/mime_util.cc @@ -154,7 +154,7 @@ static const char* const common_media_types[] = { "audio/wav", "audio/x-wav", -#if defined(OS_ANDROID) +#if defined(OS_ANDROID) || defined(USE_GSTREAMER) // HLS. "application/vnd.apple.mpegurl", "application/x-mpegurl", diff --git a/media/blink/buffered_data_source.cc b/media/blink/buffered_data_source.cc index d5f32b1..1bf808a 100644 --- a/media/blink/buffered_data_source.cc +++ b/media/blink/buffered_data_source.cc @@ -136,13 +136,28 @@ BufferedResourceLoader* BufferedDataSource::CreateResourceLoader( media_log_.get()); } +#if defined(USE_GSTREAMER) +void BufferedDataSource::Initialize(const InitializeCB& init_cb, + blink::WebURLLoader* url_loader, + const blink::WebString& referrer, + blink::WebReferrerPolicy referrer_policy) { +#else void BufferedDataSource::Initialize(const InitializeCB& init_cb) { +#endif + DCHECK(render_task_runner_->BelongsToCurrentThread()); DCHECK(!init_cb.is_null()); DCHECK(!loader_.get()); init_cb_ = init_cb; +#if defined(USE_GSTREAMER) + // TODO: improve this depending if we keep using BufferedDataSource. + url_loader_ = url_loader; + referrer_ = referrer; + referrer_policy_ = referrer_policy; +#endif + if (url_.SchemeIsHTTPOrHTTPS()) { // Do an unbounded range request starting at the beginning. If the server // responds with 200 instead of 206 we'll fall back into a streaming mode. @@ -159,8 +174,14 @@ void BufferedDataSource::Initialize(const InitializeCB& init_cb) { loader_->Start( base::Bind(&BufferedDataSource::StartCallback, weak_this), base::Bind(&BufferedDataSource::LoadingStateChangedCallback, weak_this), - base::Bind(&BufferedDataSource::ProgressCallback, weak_this), + base::Bind(&BufferedDataSource::ProgressCallback, weak_this) +#if defined(USE_GSTREAMER) + , + frame_, url_loader_, referrer_, referrer_policy_); +#else + , frame_); +#endif } void BufferedDataSource::SetPreload(Preload preload) { @@ -463,7 +484,11 @@ void BufferedDataSource::ReadCallback( base::Bind(&BufferedDataSource::LoadingStateChangedCallback, weak_this), base::Bind(&BufferedDataSource::ProgressCallback, weak_this), +#if defined(USE_GSTREAMER) + frame_, url_loader_, referrer_, referrer_policy_); +#else frame_); +#endif return; } diff --git a/media/blink/buffered_data_source.h b/media/blink/buffered_data_source.h index 32f1481..ad2f347 100644 --- a/media/blink/buffered_data_source.h +++ b/media/blink/buffered_data_source.h @@ -18,6 +18,11 @@ #include "media/blink/buffered_resource_loader.h" #include "url/gurl.h" +#if defined(USE_GSTREAMER) +#include "third_party/WebKit/public/platform/WebReferrerPolicy.h" +#include "third_party/WebKit/public/platform/WebURLLoader.h" +#endif + namespace base { class SingleThreadTaskRunner; } @@ -77,7 +82,16 @@ class MEDIA_EXPORT BufferedDataSource : public DataSource { // // Method called on the render thread. typedef base::Callback InitializeCB; + +#if defined(USE_GSTREAMER) + void Initialize(const InitializeCB& init_cb, + blink::WebURLLoader* url_loader = nullptr, + const blink::WebString& referrer = blink::WebString(), + blink::WebReferrerPolicy referrer_policy = + blink::WebReferrerPolicyDefault); +#else void Initialize(const InitializeCB& init_cb); +#endif // Adjusts the buffering algorithm based on the given preload value. void SetPreload(Preload preload); @@ -238,6 +252,12 @@ class MEDIA_EXPORT BufferedDataSource : public DataSource { DownloadingCB downloading_cb_; +#if defined(USE_GSTREAMER) + blink::WebURLLoader* url_loader_; + blink::WebString referrer_; + blink::WebReferrerPolicy referrer_policy_; +#endif + // Disallow rebinding WeakReference ownership to a different thread by keeping // a persistent reference. This avoids problems with the thread-safety of // reaching into this class from multiple threads to attain a WeakPtr. diff --git a/media/blink/buffered_resource_loader.cc b/media/blink/buffered_resource_loader.cc index 51ec8fe..3776f9e 100644 --- a/media/blink/buffered_resource_loader.cc +++ b/media/blink/buffered_resource_loader.cc @@ -19,6 +19,12 @@ #include "third_party/WebKit/public/web/WebKit.h" #include "third_party/WebKit/public/web/WebURLLoaderOptions.h" +#if defined(USE_GSTREAMER) +#include "content/child/web_url_loader_impl.h" +#include "content/public/child/child_thread.h" +#include "public/platform/Platform.h" +#endif + using blink::WebFrame; using blink::WebString; using blink::WebURLError; @@ -134,11 +140,18 @@ BufferedResourceLoader::BufferedResourceLoader( BufferedResourceLoader::~BufferedResourceLoader() {} -void BufferedResourceLoader::Start( - const StartCB& start_cb, - const LoadingStateChangedCB& loading_cb, - const ProgressCB& progress_cb, - WebFrame* frame) { +void BufferedResourceLoader::Start(const StartCB& start_cb, + const LoadingStateChangedCB& loading_cb, + const ProgressCB& progress_cb, + WebFrame* frame +#if defined(USE_GSTREAMER) + , + WebURLLoader* url_loader, + const WebString& referrer, + blink::WebReferrerPolicy referrer_policy) { +#else + ) { +#endif // Make sure we have not started. DCHECK(start_cb_.is_null()); DCHECK(loading_cb_.is_null()); @@ -146,7 +159,9 @@ void BufferedResourceLoader::Start( DCHECK(!start_cb.is_null()); DCHECK(!loading_cb.is_null()); DCHECK(!progress_cb.is_null()); +#if !defined(USE_GSTREAMER) CHECK(frame); +#endif start_cb_ = start_cb; loading_cb_ = loading_cb; @@ -170,7 +185,20 @@ void BufferedResourceLoader::Start( first_byte_position_, last_byte_position_).GetHeaderValue())); } +#if defined(USE_GSTREAMER) + if (url_loader) { + // TODO: Add API to BufferedDataSource to allow to set + // extra headers and referrer on the request. + // Depends if we keep using BufferedDataSource. + request.setHTTPReferrer(referrer, referrer_policy); + if (url_.host() == "movies.apple.com" || + url_.host() == "trailers.apple.com") + request.setHTTPHeaderField("User-Agent", "Quicktime/7.6.6"); + } else + frame->setReferrerForRequest(request, blink::WebURL()); +#else frame->setReferrerForRequest(request, blink::WebURL()); +#endif // Disable compression, compression for audio/video doesn't make sense... request.setHTTPHeaderField( @@ -196,7 +224,16 @@ void BufferedResourceLoader::Start( if (cors_mode_ == kUseCredentials) options.allowCredentials = true; } + +#if defined(USE_GSTREAMER) + // TODO: improve this depending if we keep using BufferedDataSource. + if (url_loader) + loader.reset(url_loader); + else + loader.reset(frame->createAssociatedURLLoader(options)); +#else loader.reset(frame->createAssociatedURLLoader(options)); +#endif } // Start the resource loading. diff --git a/media/blink/buffered_resource_loader.h b/media/blink/buffered_resource_loader.h index cb02720..032e534 100644 --- a/media/blink/buffered_resource_loader.h +++ b/media/blink/buffered_resource_loader.h @@ -18,6 +18,10 @@ #include "third_party/WebKit/public/web/WebFrame.h" #include "url/gurl.h" +#if defined(USE_GSTREAMER) +#include "third_party/WebKit/public/platform/WebReferrerPolicy.h" +#endif + namespace media { class MediaLog; class SeekableBuffer; @@ -99,7 +103,15 @@ class MEDIA_EXPORT BufferedResourceLoader void Start(const StartCB& start_cb, const LoadingStateChangedCB& loading_cb, const ProgressCB& progress_cb, - blink::WebFrame* frame); + blink::WebFrame* frame +#if defined(USE_GSTREAMER) + , + blink::WebURLLoader* url_loader, + const blink::WebString& referrer, + blink::WebReferrerPolicy referrer_policy); +#else + ); +#endif // Stops everything associated with this loader, including active URL loads // and pending callbacks. diff --git a/media/blink/encrypted_media_player_support.cc b/media/blink/encrypted_media_player_support.cc index 39ae48f..ffbf46c 100644 --- a/media/blink/encrypted_media_player_support.cc +++ b/media/blink/encrypted_media_player_support.cc @@ -117,12 +117,22 @@ EncryptedMediaPlayerSupport::EncryptedMediaPlayerSupport( CdmFactory* cdm_factory, blink::WebMediaPlayerClient* client, MediaPermission* media_permission, +#if defined(USE_GSTREAMER) + const CdmContextReadyCB& cdm_context_ready_cb, + const CdmContextKeysReadyCB& cdm_context_keys_ready_cb) +#else const CdmContextReadyCB& cdm_context_ready_cb) +#endif : cdm_factory_(cdm_factory), client_(client), media_permission_(media_permission), init_data_type_(EmeInitDataType::UNKNOWN), +#if defined(USE_GSTREAMER) + cdm_context_ready_cb_(cdm_context_ready_cb), + cdm_context_keys_ready_cb_(cdm_context_keys_ready_cb) { +#else cdm_context_ready_cb_(cdm_context_ready_cb) { +#endif } EncryptedMediaPlayerSupport::~EncryptedMediaPlayerSupport() { @@ -170,8 +180,14 @@ EncryptedMediaPlayerSupport::GenerateKeyRequestInternal( BIND_TO_RENDER_LOOP(&EncryptedMediaPlayerSupport::OnKeyMessage))); GURL security_origin(frame->document().securityOrigin().toString()); +#if defined(USE_GSTREAMER) + proxy_decryptor_->CreateCdm(cdm_factory_, key_system, security_origin, + cdm_context_ready_cb_, + cdm_context_keys_ready_cb_); +#else proxy_decryptor_->CreateCdm(cdm_factory_, key_system, security_origin, cdm_context_ready_cb_); +#endif current_key_system_ = key_system; } diff --git a/media/blink/encrypted_media_player_support.h b/media/blink/encrypted_media_player_support.h index 6ddec23..a8ecd27 100644 --- a/media/blink/encrypted_media_player_support.h +++ b/media/blink/encrypted_media_player_support.h @@ -38,12 +38,22 @@ class EncryptedMediaPlayerSupport : public base::SupportsWeakPtr { public: using CdmContextReadyCB = ProxyDecryptor::CdmContextReadyCB; +#if defined(USE_GSTREAMER) + using CdmContextKeysReadyCB = ProxyDecryptor::CdmContextKeysReadyCB; +#endif // |cdm_context_ready_cb| is called when the CDM instance creation completes. - EncryptedMediaPlayerSupport(CdmFactory* cdm_factory, - blink::WebMediaPlayerClient* client, - MediaPermission* media_permission, - const CdmContextReadyCB& cdm_context_ready_cb); + EncryptedMediaPlayerSupport( + CdmFactory* cdm_factory, + blink::WebMediaPlayerClient* client, + MediaPermission* media_permission, +#if defined(USE_GSTREAMER) + const CdmContextReadyCB& cdm_context_ready_cb, + const CdmContextKeysReadyCB& cdm_context_keys_ready_cb = + base::Bind(&media::IgnoreCdmContextKeysReady)); +#else + const CdmContextReadyCB& cdm_context_ready_cb); +#endif ~EncryptedMediaPlayerSupport(); blink::WebMediaPlayer::MediaKeyException GenerateKeyRequest( @@ -108,6 +118,9 @@ class EncryptedMediaPlayerSupport EmeInitDataType init_data_type_; CdmContextReadyCB cdm_context_ready_cb_; +#if defined(USE_GSTREAMER) + CdmContextKeysReadyCB cdm_context_keys_ready_cb_; +#endif // Manages decryption keys and decrypts encrypted frames. scoped_ptr proxy_decryptor_; diff --git a/media/blink/webmediaplayer_impl.cc b/media/blink/webmediaplayer_impl.cc index aa7b073..15105c8 100644 --- a/media/blink/webmediaplayer_impl.cc +++ b/media/blink/webmediaplayer_impl.cc @@ -903,8 +903,12 @@ void WebMediaPlayerImpl::StartPipeline() { DCHECK(!chunk_demuxer_); DCHECK(data_source_); +#if !defined(MEDIA_DISABLE_FFMPEG) demuxer_.reset(new FFmpegDemuxer(media_task_runner_, data_source_.get(), encrypted_media_init_data_cb, media_log_)); +#else + OnPipelineError(PipelineStatus::DEMUXER_ERROR_COULD_NOT_OPEN); +#endif } else { DCHECK(!chunk_demuxer_); DCHECK(!data_source_); diff --git a/media/cdm/aes_decryptor.cc b/media/cdm/aes_decryptor.cc index 6bc7121..5db71fb 100644 --- a/media/cdm/aes_decryptor.cc +++ b/media/cdm/aes_decryptor.cc @@ -274,7 +274,7 @@ void AesDecryptor::CreateSessionAndGenerateRequest( keys.push_back(init_data); break; case EmeInitDataType::CENC: -#if defined(USE_PROPRIETARY_CODECS) +#if defined(USE_PROPRIETARY_CODECS) || defined(USE_GSTREAMER) // |init_data| is a set of 0 or more concatenated 'pssh' boxes. if (!GetKeyIdsForCommonSystemId(init_data, &keys)) { promise->reject(NOT_SUPPORTED_ERROR, 0, @@ -389,6 +389,10 @@ void AesDecryptor::UpdateSession(const std::string& session_id, if (item.second->Contains(session_id)) { scoped_ptr key_info(new CdmKeyInformation); key_info->key_id.assign(item.first.begin(), item.first.end()); +#if defined(USE_GSTREAMER) + item.second->LatestDecryptionKey()->decryption_key()->GetRawKey( + &key_info->key); +#endif key_info->status = CdmKeyInformation::USABLE; key_info->system_code = 0; keys_info.push_back(key_info.release()); diff --git a/media/cdm/cenc_utils.cc b/media/cdm/cenc_utils.cc index ec7544e..7ed7dae 100644 --- a/media/cdm/cenc_utils.cc +++ b/media/cdm/cenc_utils.cc @@ -21,6 +21,12 @@ const uint8_t kCencCommonSystemId[] = {0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02, 0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b}; +#if defined(USE_GSTREAMER) +const uint8_t kLegacyCencCommonSystemId[] = {0x58, 0x14, 0x7e, 0xc8, 0x04, 0x23, + 0x46, 0x59, 0x92, 0xe6, 0xf5, 0x2c, + 0x5c, 0xe8, 0xc3, 0xcc}; +#endif + // Returns true if |input| contains only 1 or more valid 'pssh' boxes, false // otherwise. |pssh_boxes| is updated as the set of parsed 'pssh' boxes. // Note: All boxes in |input| must be 'pssh' boxes. However, if they can't be @@ -89,10 +95,24 @@ bool GetKeyIdsForCommonSystemId(const std::vector& pssh_boxes, std::vector common_system_id( kCencCommonSystemId, kCencCommonSystemId + arraysize(kCencCommonSystemId)); +#if defined(USE_GSTREAMER) // Some tests still use legacy cenc id. + std::vector legacy_common_system_id( + kLegacyCencCommonSystemId, + kLegacyCencCommonSystemId + arraysize(kLegacyCencCommonSystemId)); +#endif for (const auto& child : children) { +#if defined(USE_GSTREAMER) + if (child.system_id == common_system_id || + child.system_id == legacy_common_system_id) { +#else if (child.system_id == common_system_id) { +#endif key_ids->assign(child.key_ids.begin(), child.key_ids.end()); +#if defined(USE_GSTREAMER) + return true; // pssh for common decryption might not have key ids at all. +#else return key_ids->size() > 0; +#endif } } diff --git a/media/cdm/proxy_decryptor.cc b/media/cdm/proxy_decryptor.cc index a568211..afdbd5e 100644 --- a/media/cdm/proxy_decryptor.cc +++ b/media/cdm/proxy_decryptor.cc @@ -61,10 +61,22 @@ ProxyDecryptor::~ProxyDecryptor() { media_keys_.reset(); } -void ProxyDecryptor::CreateCdm(CdmFactory* cdm_factory, - const std::string& key_system, - const GURL& security_origin, - const CdmContextReadyCB& cdm_context_ready_cb) { +#if defined(USE_GSTREAMER) +void IgnoreCdmContextKeysReady(const std::string& session_id, + bool has_additional_usable_key, + CdmKeysInfo keys_info) {} +#endif + +void ProxyDecryptor::CreateCdm( + CdmFactory* cdm_factory, + const std::string& key_system, + const GURL& security_origin, +#if defined(USE_GSTREAMER) + const CdmContextReadyCB& cdm_context_ready_cb, + const CdmContextKeysReadyCB& cdm_context_keys_ready_cb) { +#else + const CdmContextReadyCB& cdm_context_ready_cb) { +#endif DVLOG(1) << __FUNCTION__ << ": key_system = " << key_system; DCHECK(!is_creating_cdm_); DCHECK(!media_keys_); @@ -79,6 +91,10 @@ void ProxyDecryptor::CreateCdm(CdmFactory* cdm_factory, is_creating_cdm_ = true; +#if defined(USE_GSTREAMER) + cdm_context_keys_ready_cb_ = cdm_context_keys_ready_cb; +#endif + base::WeakPtr weak_this = weak_ptr_factory_.GetWeakPtr(); cdm_factory->Create( key_system, security_origin, cdm_config, @@ -326,6 +342,10 @@ void ProxyDecryptor::OnSessionKeysChange(const std::string& session_id, bool has_additional_usable_key, CdmKeysInfo keys_info) { // EME v0.1b doesn't support this event. +#if defined(USE_GSTREAMER) + cdm_context_keys_ready_cb_.Run(session_id, has_additional_usable_key, + keys_info.Pass()); +#endif } void ProxyDecryptor::OnSessionExpirationUpdate( diff --git a/media/cdm/proxy_decryptor.h b/media/cdm/proxy_decryptor.h index 8103739..b340670 100644 --- a/media/cdm/proxy_decryptor.h +++ b/media/cdm/proxy_decryptor.h @@ -9,11 +9,17 @@ #include #include "base/basictypes.h" +#if defined(USE_GSTREAMER) +#include "base/bind.h" +#endif #include "base/containers/hash_tables.h" #include "base/memory/scoped_ptr.h" #include "base/memory/scoped_vector.h" #include "base/memory/weak_ptr.h" #include "media/base/cdm_context.h" +#if defined(USE_GSTREAMER) +#include "media/base/cdm_key_information.h" +#endif #include "media/base/decryptor.h" #include "media/base/eme_constants.h" #include "media/base/media_export.h" @@ -25,6 +31,12 @@ namespace media { class CdmFactory; class MediaPermission; +#if defined(USE_GSTREAMER) +MEDIA_EXPORT void IgnoreCdmContextKeysReady(const std::string& session_id, + bool has_additional_usable_key, + CdmKeysInfo keys_info); +#endif + // ProxyDecryptor is for EME v0.1b only. It should not be used for the WD API. // A decryptor proxy that creates a real decryptor object on demand and // forwards decryptor calls to it. @@ -37,6 +49,11 @@ class MEDIA_EXPORT ProxyDecryptor { // Callback to provide a CdmContext when the CDM creation is finished. // If CDM creation failed, |cdm_context| will be null. typedef base::Callback CdmContextReadyCB; +#if defined(USE_GSTREAMER) + typedef base::Callback CdmContextKeysReadyCB; +#endif // These are similar to the callbacks in media_keys.h, but pass back the // session ID rather than the internal session ID. @@ -61,7 +78,13 @@ class MEDIA_EXPORT ProxyDecryptor { void CreateCdm(CdmFactory* cdm_factory, const std::string& key_system, const GURL& security_origin, +#if defined(USE_GSTREAMER) + const CdmContextReadyCB& cdm_context_ready_cb, + const CdmContextKeysReadyCB& cdm_context_keys_ready_cb = + base::Bind(&IgnoreCdmContextKeysReady)); +#else const CdmContextReadyCB& cdm_context_ready_cb); +#endif // May only be called after CreateCDM(). void GenerateKeyRequest(EmeInitDataType init_data_type, @@ -138,6 +161,9 @@ class MEDIA_EXPORT ProxyDecryptor { KeyAddedCB key_added_cb_; KeyErrorCB key_error_cb_; KeyMessageCB key_message_cb_; +#if defined(USE_GSTREAMER) + CdmContextKeysReadyCB cdm_context_keys_ready_cb_; +#endif std::string key_system_; GURL security_origin_; diff --git a/sandbox/linux/syscall_broker/broker_file_permission.cc b/sandbox/linux/syscall_broker/broker_file_permission.cc index beceda9..3a9ccaf 100644 --- a/sandbox/linux/syscall_broker/broker_file_permission.cc +++ b/sandbox/linux/syscall_broker/broker_file_permission.cc @@ -240,4 +240,4 @@ BrokerFilePermission::BrokerFilePermission(const std::string& path, } // namespace syscall_broker -} // namespace sandbox \ No newline at end of file +} // namespace sandbox diff --git a/sandbox/linux/syscall_broker/broker_file_permission.h b/sandbox/linux/syscall_broker/broker_file_permission.h index 03300d1..c34b618 100644 --- a/sandbox/linux/syscall_broker/broker_file_permission.h +++ b/sandbox/linux/syscall_broker/broker_file_permission.h @@ -49,6 +49,17 @@ class SANDBOX_EXPORT BrokerFilePermission { return BrokerFilePermission(path, false, true, true, true, true); } +#if defined(USE_GSTREAMER) + static BrokerFilePermission ReadWriteRecursive(const std::string& path) { + return BrokerFilePermission(path, true, false, true, true, false); + } + + static BrokerFilePermission ReadWriteCreateRecursive( + const std::string& path) { + return BrokerFilePermission(path, true, false, true, true, true); + } +#endif + static BrokerFilePermission ReadWriteCreateUnlinkRecursive( const std::string& path) { return BrokerFilePermission(path, true, true, true, true, true); @@ -116,4 +127,4 @@ class SANDBOX_EXPORT BrokerFilePermission { } // namespace sandbox -#endif // SANDBOX_LINUX_SYSCALL_BROKER_BROKER_FILE_PERMISSION_H_ \ No newline at end of file +#endif // SANDBOX_LINUX_SYSCALL_BROKER_BROKER_FILE_PERMISSION_H_ diff --git a/sandbox/linux/syscall_broker/broker_host.cc b/sandbox/linux/syscall_broker/broker_host.cc index e5957ed..2584d68 100644 --- a/sandbox/linux/syscall_broker/broker_host.cc +++ b/sandbox/linux/syscall_broker/broker_host.cc @@ -45,6 +45,16 @@ int sys_open(const char* pathname, int flags) { } else { mode = 0; } + +#if defined(USE_GSTREAMER) + // FIXME: once media sandbox works with pulseaudio we will be able to check + // if the following lines are necessary. Pulseaudio calls shm_open with 0700. + // If required we will have to improve broker process 's IPC-COMMAND_OPEN. + if (std::string(pathname).find("/dev/shm/pulse-shm-") != std::string::npos) { + mode = 0700; + } +#endif + if (IsRunningOnValgrind()) { // Valgrind does not support AT_FDCWD, just use libc's open() in this case. return open(pathname, flags, mode); diff --git a/ui/gl/gl_image_egl.cc b/ui/gl/gl_image_egl.cc index 0ebd7cc..705935d 100644 --- a/ui/gl/gl_image_egl.cc +++ b/ui/gl/gl_image_egl.cc @@ -33,6 +33,19 @@ bool GLImageEGL::Initialize(EGLenum target, return false; } +#if defined(USE_GSTREAMER) + dmabuf_fds_.clear(); + for (size_t i = 0; i < 30; ++i) { + if (attrs[i] == EGL_DMA_BUF_PLANE0_FD_EXT || + attrs[i] == EGL_DMA_BUF_PLANE1_FD_EXT || + attrs[i] == EGL_DMA_BUF_PLANE2_FD_EXT) { + dmabuf_fds_.push_back(new base::ScopedFD(attrs[i + 1])); + } else if (attrs[i] == EGL_NONE) { + break; + } + } +#endif + return true; } diff --git a/ui/gl/gl_image_egl.h b/ui/gl/gl_image_egl.h index 14cc40b..ffecd97 100644 --- a/ui/gl/gl_image_egl.h +++ b/ui/gl/gl_image_egl.h @@ -9,6 +9,11 @@ #include "ui/gl/gl_bindings.h" #include "ui/gl/gl_image.h" +#if defined(USE_GSTREAMER) +#include "base/files/scoped_file.h" +#include "base/memory/scoped_vector.h" +#endif + namespace gfx { class GL_EXPORT GLImageEGL : public GLImage { @@ -36,6 +41,12 @@ class GL_EXPORT GLImageEGL : public GLImage { const Rect& bounds_rect, const RectF& crop_rect) override; +#if defined(USE_GSTREAMER) + void OnMemoryDump(base::trace_event::ProcessMemoryDump* pmd, + uint64_t process_tracing_id, + const std::string& dump_name) override {} +#endif + protected: ~GLImageEGL() override; @@ -43,6 +54,10 @@ class GL_EXPORT GLImageEGL : public GLImage { const gfx::Size size_; base::ThreadChecker thread_checker_; +#if defined(USE_GSTREAMER) + ScopedVector dmabuf_fds_; +#endif + private: DISALLOW_COPY_AND_ASSIGN(GLImageEGL); }; diff --git a/ui/gl/gl_surface_egl.cc b/ui/gl/gl_surface_egl.cc index 2e193a5..95bc939 100644 --- a/ui/gl/gl_surface_egl.cc +++ b/ui/gl/gl_surface_egl.cc @@ -632,7 +632,12 @@ EGLConfig NativeViewGLSurfaceEGL::GetConfig() { return NULL; } +#if defined(USE_GSTREAMER) + if ((g_config && g_config == config_) || + config_depth == win_attribs.depth) { +#else if (config_depth == win_attribs.depth) { +#endif return config_; } } diff --git a/ui/gl/gl_surface_x11.cc b/ui/gl/gl_surface_x11.cc index 4e14a43..d4a0b93 100644 --- a/ui/gl/gl_surface_x11.cc +++ b/ui/gl/gl_surface_x11.cc @@ -329,6 +329,9 @@ scoped_refptr GLSurface::CreateOffscreenGLSurface( return surface; } case kGLImplementationEGLGLES2: { +#if defined(USE_GSTREAMER) +// TODO: if EGL_KHR_surfaceless_context just use surface as EGL_NO_SURFACE. +#endif scoped_refptr surface(new PbufferGLSurfaceEGL(size)); if (!surface->Initialize()) return NULL; -- 2.1.0