diff --git a/BUILD.gn b/BUILD.gn index 8aef7caaf55f8f..80a57000f36940 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -50,6 +50,7 @@ group("root") { "//media", "//media/blink", "//media/cast", + "//media/mojo", "//mojo", "//net", "//pdf", diff --git a/media/media.gyp b/media/media.gyp index e5fba8c99a9d70..5f4bca905661a9 100644 --- a/media/media.gyp +++ b/media/media.gyp @@ -1016,11 +1016,12 @@ }, { # GN version: //media/mojo/interfaces - 'target_name': 'mojo_media_bindings', + 'target_name': 'media_mojo_bindings', 'type': 'static_library', 'sources': [ 'mojo/interfaces/media_types.mojom', 'mojo/interfaces/media_renderer.mojom', + 'mojo/interfaces/demuxer_stream.mojom', ], 'includes': [ '../mojo/public/tools/bindings/mojom_bindings_generator.gypi' @@ -1033,33 +1034,57 @@ ], }, { - 'target_name': 'mojo_media_lib', + 'target_name': 'media_mojo_lib', 'type': 'static_library', 'includes': [ '../mojo/mojo_variables.gypi', ], 'dependencies': [ 'media', - 'mojo_media_bindings', + 'media_mojo_bindings', '../base/base.gyp:base', '../mojo/mojo_base.gyp:mojo_environment_chromium', '<(mojo_system_for_component)', ], 'export_dependent_settings': [ - 'mojo_media_bindings', + 'media_mojo_bindings', ], 'sources': [ 'mojo/services/media_type_converters.cc', 'mojo/services/media_type_converters.h', + 'mojo/services/mojo_demuxer_stream_impl.cc', + 'mojo/services/mojo_demuxer_stream_impl.h', + 'mojo/services/mojo_renderer_impl.cc', + 'mojo/services/mojo_renderer_impl.h', ], }, { - 'target_name': 'mojo_media_lib_unittests', + 'target_name': 'media_mojo_renderer_app', + 'type': 'loadable_module', + 'includes': [ + '../mojo/mojo_variables.gypi', + ], + 'dependencies': [ + '../base/base.gyp:base', + '../mojo/mojo_base.gyp:mojo_application_chromium', + '<(mojo_system_for_loadable_module)', + 'media_mojo_lib', + 'shared_memory_support', + ], + 'sources': [ + 'mojo/services/mojo_demuxer_stream_adapter.cc', + 'mojo/services/mojo_demuxer_stream_adapter.h', + 'mojo/services/mojo_renderer_service.cc', + 'mojo/services/mojo_renderer_service.h', + ], + }, + { + 'target_name': 'media_mojo_lib_unittests', 'type': '<(gtest_target_type)', 'dependencies': [ 'media', - 'mojo_media_bindings', - 'mojo_media_lib', + 'media_mojo_bindings', + 'media_mojo_lib', '../base/base.gyp:base', '../base/base.gyp:test_support_base', '../testing/gtest.gyp:gtest', @@ -1069,7 +1094,39 @@ 'sources': [ 'mojo/services/media_type_converters_unittest.cc', ], + }, + { + 'target_name': 'media_mojo_renderer_apptest', + 'type': 'loadable_module', + 'includes': [ + '../mojo/mojo_variables.gypi', + ], + 'dependencies': [ + 'media', + 'media_mojo_bindings', + 'media_mojo_lib', + 'media_mojo_renderer_app', + 'media_test_support', + '../base/base.gyp:base', + '../base/base.gyp:test_support_base', + '../testing/gtest.gyp:gtest', + '../mojo/mojo_base.gyp:mojo_application_chromium', + '<(mojo_system_for_loadable_module)', + ], + 'sources': [ + 'mojo/services/renderer_unittest.cc', + ], }, + { + 'target_name': 'media_mojo', + 'type': 'none', + 'dependencies': [ + 'media_mojo_lib', + 'media_mojo_lib_unittests', + 'media_mojo_renderer_app', + 'media_mojo_renderer_apptest', + ] + }, { # GN version: //media:media_unittests 'target_name': 'media_unittests', diff --git a/media/mojo/BUILD.gn b/media/mojo/BUILD.gn new file mode 100644 index 00000000000000..d30848b19213bb --- /dev/null +++ b/media/mojo/BUILD.gn @@ -0,0 +1,17 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +group("mojo") { + public_deps = [ + "//media/mojo/interfaces", + "//media/mojo/services", + ] +} + +group("tests") { + testonly = true + deps = [ + "//media/mojo/services:tests" + ] +} diff --git a/media/mojo/DEPS b/media/mojo/DEPS index ef8ad28d9d44b3..12fb4fe3dbdbb1 100644 --- a/media/mojo/DEPS +++ b/media/mojo/DEPS @@ -1,3 +1,4 @@ include_rules = [ + "+mojo/application", "+mojo/public", ] diff --git a/media/mojo/interfaces/BUILD.gn b/media/mojo/interfaces/BUILD.gn index c306cd5585952a..f21abbb91c8514 100644 --- a/media/mojo/interfaces/BUILD.gn +++ b/media/mojo/interfaces/BUILD.gn @@ -5,9 +5,10 @@ import("//mojo/public/tools/bindings/mojom.gni") # GYP version: media/media.gyp:mojo_media_bindings -mojom("media") { +mojom("interfaces") { sources = [ "media_types.mojom", "media_renderer.mojom", + "demuxer_stream.mojom", ] } diff --git a/media/mojo/interfaces/demuxer_stream.mojom b/media/mojo/interfaces/demuxer_stream.mojom new file mode 100644 index 00000000000000..73d6e6c25e56c0 --- /dev/null +++ b/media/mojo/interfaces/demuxer_stream.mojom @@ -0,0 +1,54 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import "media/mojo/interfaces/media_types.mojom" + +module mojo { + +// DemuxerStream is modeled after media::DemuxerStream using mojo in order to +// enable proxying between a media::Pipeline and media::Renderer living in two +// different applications. +[Client=DemuxerStreamClient] +interface DemuxerStream { + // See media::DemuxerStream for descriptions. + enum Type { + UNKNOWN, + AUDIO, + LAST_TYPE = AUDIO + }; + + // See media::DemuxerStream for descriptions. + enum Status { + OK = 0, + ABORTED, + CONFIG_CHANGED, + }; + + // Request a MediaDecoderBuffer from this stream for decoding and rendering. + // When available, the callback will be invoked with a Status and |response| + // buffer. See media::DemuxerStream::ReadCB for explanation of fields. + // + // TODO(tim): Remove this method in favor of initializing the + // DemuxerStreamClient with a DataPipeConsumerHandle once we have a framed + // DataPipe that we can serialize [|status| | response|]* over directly. + Read() => (Status status, MediaDecoderBuffer response); +}; + +interface DemuxerStreamClient { + // Informs the client that the stream is ready for reading. If |pipe| is + // present, it means the client should read + // + // [ |DemuxerStream::Status| |MediaDecoderBuffer| ] + // + // payloads from the DataPipe directly. If |pipe| is NULL, it means the + // client needs to use DemuxerStream::Read() directly to obtain buffers. + OnStreamReady(handle? pipe); + + // A new AudioDecoderConfig is available. Will be sent by the DemuxerStream + // whenever a DemuxerStream::STATUS_CONFIG_CHANGED is observed (either + // in a Read() callback or over the DataPipe). + OnAudioDecoderConfigChanged(AudioDecoderConfig config); +}; + +} // module mojo diff --git a/media/mojo/interfaces/media_renderer.mojom b/media/mojo/interfaces/media_renderer.mojom index 09b14820ef2f3b..555472ef2da71d 100644 --- a/media/mojo/interfaces/media_renderer.mojom +++ b/media/mojo/interfaces/media_renderer.mojom @@ -2,24 +2,17 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import "media/mojo/interfaces/demuxer_stream.mojom" import "media/mojo/interfaces/media_types.mojom" module mojo { [Client=MediaRendererClient] interface MediaRenderer { - // Initializes the Renderer, calling back upon completion. + // Initializes the Renderer with |stream|, calling back upon completion. // NOTE: If an error occurs, MediaRendererClient::OnError() will be called // before the callback is executed. - Initialize() => (); - - // Decodes and renders |buffer|, calling back when more data is required. - // NOTE: If an error occurs, MediaRendererClient::OnError() will be called - // before the callback is executed. - // TODO(tim): Switch decoding model to use framed data pipe when available. - // In that world, the signalling for more would be implicit by a writable - // pipe handle on the client side so this entire method + callback goes away. - DecodeAndRender(MediaDecoderBuffer buffer) => (); + Initialize(DemuxerStream stream) => (); // Discards any buffered data, executing callback when completed. // NOTE: If an error occurs, MediaRendererClient::OnError() can be called diff --git a/media/mojo/interfaces/media_types.mojom b/media/mojo/interfaces/media_types.mojom index 007ceb1baec223..709f8f0bb53425 100644 --- a/media/mojo/interfaces/media_types.mojom +++ b/media/mojo/interfaces/media_types.mojom @@ -4,18 +4,98 @@ module mojo { +// See media/base/buffering_state.h for descriptions. +// Kept in sync with media::BufferingState via COMPILE_ASSERTs. enum BufferingState { - // Indicates that there is no data buffered. - // - // Typical reason is data underflow and hence playback should be paused. HAVE_NOTHING, - - // Indicates that enough data has been buffered. - // - // Typical reason is enough data has been prerolled to start playback. HAVE_ENOUGH, }; +// See media/base/audio_decoder_config.h for descriptions. +// Kept in sync with media::AudioCodec via COMPILE_ASSERTs. +enum AudioCodec { + UNKNOWN = 0, + AAC = 1, + MP3 = 2, + PCM = 3, + Vorbis = 4, + FLAC = 5, + AMR_NB = 6, + AMR_WB = 7, + PCM_MULAW = 8, + GSM_MS = 9, + PCM_S16BE = 10, + PCM_S24BE = 11, + Opus = 12, + // EAC3 = 13, + PCM_ALAW = 14, + MAX = PCM_ALAW, +}; + +// See media/base/channel_layout.h for descriptions. +// Kept in sync with media::ChannelLayout via COMPILE_ASSERTs. +// TODO(tim): The bindings generators will always prepend the enum name, should +// mojom therefore allow enum values starting with numbers? +enum ChannelLayout { + k_NONE = 0, + k_UNSUPPORTED = 1, + k_MONO = 2, + k_STEREO = 3, + k_2_1 = 4, + k_SURROUND = 5, + k_4_0 = 6, + k_2_2 = 7, + k_QUAD = 8, + k_5_0 = 9, + k_5_1 = 10, + k_5_0_BACK = 11, + k_5_1_BACK = 12, + k_7_0 = 13, + k_7_1 = 14, + k_7_1_WIDE = 15, + k_STEREO_DOWNMIX = 16, + k_2POINT1 = 17, + k_3_1 = 18, + k_4_1 = 19, + k_6_0 = 20, + k_6_0_FRONT = 21, + k_HEXAGONAL = 22, + k_6_1 = 23, + k_6_1_BACK = 24, + k_6_1_FRONT = 25, + k_7_0_FRONT = 26, + k_7_1_WIDE_BACK = 27, + k_OCTAGONAL = 28, + k_DISCRETE = 29, + k_STEREO_AND_KEYBOARD_MIC = 30, + k_MAX = k_STEREO_AND_KEYBOARD_MIC +}; + +// See media/base/sample_format.h for descriptions. +// Kept in sync with media::SampleFormat via COMPILE_ASSERTs. +enum SampleFormat { + UNKNOWN = 0, + U8, + S16, + S32, + F32, + PlanarS16, + PlanarF32, + Max = PlanarF32, +}; + +// This defines a mojo transport format for media::AudioDecoderConfig. +// See media/base/audio_decoder_config.h for descriptions. +struct AudioDecoderConfig { + AudioCodec codec; + SampleFormat sample_format; + ChannelLayout channel_layout; + int32 samples_per_second; + uint8[]? extra_data; + int64 seek_preroll_usec; + int32 codec_delay; +}; + // This defines a mojo transport format for media::DecoderBuffer. struct MediaDecoderBuffer { // See media/base/buffers.h for details. diff --git a/media/mojo/services/BUILD.gn b/media/mojo/services/BUILD.gn index 1ad6846f07374f..790e246bf36df2 100644 --- a/media/mojo/services/BUILD.gn +++ b/media/mojo/services/BUILD.gn @@ -2,12 +2,13 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -# GYP version: media/media.gyp:mojo_media_lib +# Things needed by multiple targets, like renderer_impl and renderer_app. +# GYP version: media/media.gyp:media_mojo_lib source_set("lib") { deps = [ "//base", "//media", - "//media/mojo/interfaces" + "//media/mojo/interfaces", "//mojo/common", "//mojo/environment:chromium", "//mojo/public/c/system:for_component", @@ -19,15 +20,104 @@ source_set("lib") { ] } -test("mojo_media_lib_unittests") { +# mojo media::Renderer proxy (to a renderer_app) implementation. +source_set("renderer_impl_lib") { + deps = [ + "//base", + "//media", + "//mojo/common", + "//mojo/environment:chromium", + "//mojo/public/c/system:for_component", + ":lib", + ] + + sources = [ + "mojo_demuxer_stream_impl.cc", + "mojo_demuxer_stream_impl.h", + "mojo_renderer_impl.cc", + "mojo_renderer_impl.h", + ] +} + +# mojo media::Renderer application. +# GYP version: media/media.gyp:media_mojo_renderer_app +shared_library("renderer_app") { + output_name = "media_mojo_renderer_app" + + deps = [ + "//base", + "//media", + "//media:shared_memory_support", + "//media/mojo/interfaces", + "//mojo/common", + "//mojo/application", + "//mojo/public/c/system:for_shared_library", + ":lib", + ] + + sources = [ + "mojo_demuxer_stream_adapter.cc", + "mojo_demuxer_stream_adapter.h", + "mojo_renderer_service.cc", + "mojo_renderer_service.h", + ] +} + +test("media_mojo_lib_unittests") { sources = [ "media_type_converters_unittest.cc", ] deps = [ + "//base", "//base/test:test_support", + "//media", + "//media/mojo/interfaces", + "//mojo/common/test:run_all_unittests", "//mojo/environment:chromium", + "//mojo/system", "//testing/gtest", ":lib" ] } + +# GYP version: media/media.gyp:media_mojo_renderer_apptest +# Not a 'test' because this is loaded via mojo_shell as an app. +shared_library("renderer_apptest") { + testonly = true + output_name = "media_mojo_renderer_apptest" + + deps = [ + "//base", + "//base/test:test_support", + "//media", + "//media/mojo/interfaces", + "//mojo/common", + "//mojo/application", + "//testing/gtest", + ":renderer_impl_lib", + ":renderer_app", + ":lib", + "//mojo/public/c/system:for_shared_library", + ] + + sources = [ + "renderer_unittest.cc", + ] +} + +group("services") { + deps = [ + ":lib", + ":renderer_impl_lib", + ":renderer_app", + ] +} + +group("tests") { + testonly = true + deps = [ + ":media_mojo_lib_unittests", + ":renderer_apptest", + ] +} diff --git a/media/mojo/services/media_type_converters.cc b/media/mojo/services/media_type_converters.cc index a6b0fad9723b33..2a39b53a3264bc 100644 --- a/media/mojo/services/media_type_converters.cc +++ b/media/mojo/services/media_type_converters.cc @@ -5,19 +5,124 @@ #include "media/mojo/services/media_type_converters.h" #include "base/macros.h" +#include "media/base/audio_decoder_config.h" #include "media/base/buffering_state.h" #include "media/base/decoder_buffer.h" +#include "media/base/demuxer_stream.h" +#include "media/mojo/interfaces/demuxer_stream.mojom.h" #include "mojo/public/cpp/system/data_pipe.h" namespace mojo { -#define ASSERT_ENUM_VALUES_EQUAL(value) \ - COMPILE_ASSERT(media::BUFFERING_##value == \ - static_cast(BUFFERING_STATE_##value), \ - value##_enum_value_matches) +#define ASSERT_ENUM_EQ(media_enum, media_prefix, mojo_prefix, value) \ + COMPILE_ASSERT(media::media_prefix##value == \ + static_cast(mojo_prefix##value), \ + value##_enum_value_differs) -ASSERT_ENUM_VALUES_EQUAL(HAVE_NOTHING); -ASSERT_ENUM_VALUES_EQUAL(HAVE_ENOUGH); +// BufferingState. +ASSERT_ENUM_EQ(BufferingState, BUFFERING_, BUFFERING_STATE_, HAVE_NOTHING); +ASSERT_ENUM_EQ(BufferingState, BUFFERING_, BUFFERING_STATE_, HAVE_ENOUGH); + +// AudioCodec. +COMPILE_ASSERT(media::kUnknownAudioCodec == + static_cast(AUDIO_CODEC_UNKNOWN), + kUnknownAudioCodec_enum_value_differs); +ASSERT_ENUM_EQ(AudioCodec, kCodec, AUDIO_CODEC_, AAC); +ASSERT_ENUM_EQ(AudioCodec, kCodec, AUDIO_CODEC_, MP3); +ASSERT_ENUM_EQ(AudioCodec, kCodec, AUDIO_CODEC_, PCM); +ASSERT_ENUM_EQ(AudioCodec, kCodec, AUDIO_CODEC_, Vorbis); +ASSERT_ENUM_EQ(AudioCodec, kCodec, AUDIO_CODEC_, FLAC); +ASSERT_ENUM_EQ(AudioCodec, kCodec, AUDIO_CODEC_, AMR_NB); +ASSERT_ENUM_EQ(AudioCodec, kCodec, AUDIO_CODEC_, PCM_MULAW); +ASSERT_ENUM_EQ(AudioCodec, kCodec, AUDIO_CODEC_, GSM_MS); +ASSERT_ENUM_EQ(AudioCodec, kCodec, AUDIO_CODEC_, PCM_S16BE); +ASSERT_ENUM_EQ(AudioCodec, kCodec, AUDIO_CODEC_, PCM_S24BE); +ASSERT_ENUM_EQ(AudioCodec, kCodec, AUDIO_CODEC_, Opus); +ASSERT_ENUM_EQ(AudioCodec, kCodec, AUDIO_CODEC_, PCM_ALAW); +COMPILE_ASSERT(media::kAudioCodecMax == + static_cast(AUDIO_CODEC_MAX), + kAudioCodecMax_enum_value_differs); + +// ChannelLayout. +ASSERT_ENUM_EQ(ChannelLayout, CHANNEL_LAYOUT, CHANNEL_LAYOUT_k, _NONE); +ASSERT_ENUM_EQ(ChannelLayout, CHANNEL_LAYOUT, CHANNEL_LAYOUT_k, _UNSUPPORTED); +ASSERT_ENUM_EQ(ChannelLayout, CHANNEL_LAYOUT, CHANNEL_LAYOUT_k, _MONO); +ASSERT_ENUM_EQ(ChannelLayout, CHANNEL_LAYOUT, CHANNEL_LAYOUT_k, _STEREO); +ASSERT_ENUM_EQ(ChannelLayout, CHANNEL_LAYOUT, CHANNEL_LAYOUT_k, _2_1); +ASSERT_ENUM_EQ(ChannelLayout, CHANNEL_LAYOUT, CHANNEL_LAYOUT_k, _SURROUND); +ASSERT_ENUM_EQ(ChannelLayout, CHANNEL_LAYOUT, CHANNEL_LAYOUT_k, _4_0); +ASSERT_ENUM_EQ(ChannelLayout, CHANNEL_LAYOUT, CHANNEL_LAYOUT_k, _2_2); +ASSERT_ENUM_EQ(ChannelLayout, CHANNEL_LAYOUT, CHANNEL_LAYOUT_k, _QUAD); +ASSERT_ENUM_EQ(ChannelLayout, CHANNEL_LAYOUT, CHANNEL_LAYOUT_k, _5_0); +ASSERT_ENUM_EQ(ChannelLayout, CHANNEL_LAYOUT, CHANNEL_LAYOUT_k, _5_1); +ASSERT_ENUM_EQ(ChannelLayout, CHANNEL_LAYOUT, CHANNEL_LAYOUT_k, _5_0_BACK); +ASSERT_ENUM_EQ(ChannelLayout, CHANNEL_LAYOUT, CHANNEL_LAYOUT_k, _5_1_BACK); +ASSERT_ENUM_EQ(ChannelLayout, CHANNEL_LAYOUT, CHANNEL_LAYOUT_k, _7_0); +ASSERT_ENUM_EQ(ChannelLayout, CHANNEL_LAYOUT, CHANNEL_LAYOUT_k, _7_1); +ASSERT_ENUM_EQ(ChannelLayout, CHANNEL_LAYOUT, CHANNEL_LAYOUT_k, _7_1_WIDE); +ASSERT_ENUM_EQ(ChannelLayout, + CHANNEL_LAYOUT, + CHANNEL_LAYOUT_k, + _STEREO_DOWNMIX); +ASSERT_ENUM_EQ(ChannelLayout, CHANNEL_LAYOUT, CHANNEL_LAYOUT_k, _2POINT1); +ASSERT_ENUM_EQ(ChannelLayout, CHANNEL_LAYOUT, CHANNEL_LAYOUT_k, _3_1); +ASSERT_ENUM_EQ(ChannelLayout, CHANNEL_LAYOUT, CHANNEL_LAYOUT_k, _4_1); +ASSERT_ENUM_EQ(ChannelLayout, CHANNEL_LAYOUT, CHANNEL_LAYOUT_k, _6_0); +ASSERT_ENUM_EQ(ChannelLayout, CHANNEL_LAYOUT, CHANNEL_LAYOUT_k, _6_0_FRONT); +ASSERT_ENUM_EQ(ChannelLayout, CHANNEL_LAYOUT, CHANNEL_LAYOUT_k, _HEXAGONAL); +ASSERT_ENUM_EQ(ChannelLayout, CHANNEL_LAYOUT, CHANNEL_LAYOUT_k, _6_1); +ASSERT_ENUM_EQ(ChannelLayout, CHANNEL_LAYOUT, CHANNEL_LAYOUT_k, _6_1_BACK); +ASSERT_ENUM_EQ(ChannelLayout, CHANNEL_LAYOUT, CHANNEL_LAYOUT_k, _6_1_FRONT); +ASSERT_ENUM_EQ(ChannelLayout, CHANNEL_LAYOUT, CHANNEL_LAYOUT_k, _7_0_FRONT); +ASSERT_ENUM_EQ(ChannelLayout, CHANNEL_LAYOUT, CHANNEL_LAYOUT_k, _7_1_WIDE_BACK); +ASSERT_ENUM_EQ(ChannelLayout, CHANNEL_LAYOUT, CHANNEL_LAYOUT_k, _OCTAGONAL); +ASSERT_ENUM_EQ(ChannelLayout, CHANNEL_LAYOUT, CHANNEL_LAYOUT_k, _DISCRETE); +ASSERT_ENUM_EQ(ChannelLayout, + CHANNEL_LAYOUT, + CHANNEL_LAYOUT_k, + _STEREO_AND_KEYBOARD_MIC); +ASSERT_ENUM_EQ(ChannelLayout, CHANNEL_LAYOUT, CHANNEL_LAYOUT_k, _MAX); + +// SampleFormat. +COMPILE_ASSERT(media::kUnknownSampleFormat == + static_cast(SAMPLE_FORMAT_UNKNOWN), + kUnknownSampleFormat_enum_value_differs); +ASSERT_ENUM_EQ(SampleFormat, kSampleFormat, SAMPLE_FORMAT_, U8); +ASSERT_ENUM_EQ(SampleFormat, kSampleFormat, SAMPLE_FORMAT_, S16); +ASSERT_ENUM_EQ(SampleFormat, kSampleFormat, SAMPLE_FORMAT_, S32); +ASSERT_ENUM_EQ(SampleFormat, kSampleFormat, SAMPLE_FORMAT_, F32); +ASSERT_ENUM_EQ(SampleFormat, kSampleFormat, SAMPLE_FORMAT_, PlanarS16); +ASSERT_ENUM_EQ(SampleFormat, kSampleFormat, SAMPLE_FORMAT_, PlanarF32); +ASSERT_ENUM_EQ(SampleFormat, kSampleFormat, SAMPLE_FORMAT_, Max); + +// DemuxerStream Type. +COMPILE_ASSERT(media::DemuxerStream::UNKNOWN == + static_cast( + mojo::DemuxerStream::TYPE_UNKNOWN), + DemuxerStream_Type_enum_value_differs); +COMPILE_ASSERT(media::DemuxerStream::AUDIO == + static_cast( + mojo::DemuxerStream::TYPE_AUDIO), + DemuxerStream_Type_enum_value_differs); +// Update this if new media::DemuxerStream::Type values are introduced. +COMPILE_ASSERT(media::DemuxerStream::NUM_TYPES == + static_cast( + mojo::DemuxerStream::TYPE_LAST_TYPE + 3), + DemuxerStream_Type_enum_value_differs); + +// DemuxerStream Status. +COMPILE_ASSERT(media::DemuxerStream::kOk == + static_cast( + mojo::DemuxerStream::STATUS_OK), + DemuxerStream_Status_enum_value_differs); +COMPILE_ASSERT(media::DemuxerStream::kAborted == + static_cast( + mojo::DemuxerStream::STATUS_ABORTED), + DemuxerStream_Status_enum_value_differs); +COMPILE_ASSERT(media::DemuxerStream::kConfigChanged == + static_cast( + mojo::DemuxerStream::STATUS_CONFIG_CHANGED), + DemuxerStream_Status_enum_value_differs); // static MediaDecoderBufferPtr TypeConverter TypeConverter< if (input->side_data_size) { buffer = media::DecoderBuffer::CopyFrom(data.get(), num_bytes, - input->side_data.storage().data(), + &input->side_data.front(), input->side_data_size); } else { buffer = media::DecoderBuffer::CopyFrom(data.get(), num_bytes); @@ -102,4 +207,43 @@ scoped_refptr TypeConverter< return buffer; } +// static +AudioDecoderConfigPtr +TypeConverter::Convert( + const media::AudioDecoderConfig& input) { + mojo::AudioDecoderConfigPtr config(mojo::AudioDecoderConfig::New()); + config->codec = static_cast(input.codec()); + config->sample_format = + static_cast(input.sample_format()); + config->channel_layout = + static_cast(input.channel_layout()); + config->samples_per_second = input.samples_per_second(); + if (input.extra_data()) { + std::vector data(input.extra_data(), + input.extra_data() + input.extra_data_size()); + config->extra_data.Swap(&data); + } + config->seek_preroll_usec = input.seek_preroll().InMicroseconds(); + config->codec_delay = input.codec_delay(); + return config.Pass(); +} + +// static +media::AudioDecoderConfig +TypeConverter::Convert( + const AudioDecoderConfigPtr& input) { + media::AudioDecoderConfig config; + config.Initialize(static_cast(input->codec), + static_cast(input->sample_format), + static_cast(input->channel_layout), + input->samples_per_second, + &input->extra_data.front(), + input->extra_data.size(), + false, + false, + base::TimeDelta::FromMicroseconds(input->seek_preroll_usec), + input->codec_delay); + return config; +} + } // namespace mojo diff --git a/media/mojo/services/media_type_converters.h b/media/mojo/services/media_type_converters.h index f04e200bdfee33..fba78dcc8d1dbb 100644 --- a/media/mojo/services/media_type_converters.h +++ b/media/mojo/services/media_type_converters.h @@ -9,6 +9,7 @@ #include "media/mojo/interfaces/media_types.mojom.h" namespace media { +class AudioDecoderConfig; class DecoderBuffer; } @@ -27,6 +28,15 @@ struct TypeConverter, const MediaDecoderBufferPtr& input); }; +template <> +struct TypeConverter { + static AudioDecoderConfigPtr Convert(const media::AudioDecoderConfig& input); +}; +template <> +struct TypeConverter { + static media::AudioDecoderConfig Convert(const AudioDecoderConfigPtr& input); +}; + } // namespace mojo #endif // MEDIA_MOJO_SERVICES_MEDIA_TYPE_CONVERTERS_H_ diff --git a/media/mojo/services/media_type_converters_unittest.cc b/media/mojo/services/media_type_converters_unittest.cc index e5ad20e3f3b81c..29049b6e737e17 100644 --- a/media/mojo/services/media_type_converters_unittest.cc +++ b/media/mojo/services/media_type_converters_unittest.cc @@ -4,6 +4,7 @@ #include "media/mojo/services/media_type_converters.h" +#include "media/base/audio_decoder_config.h" #include "media/base/decoder_buffer.h" #include "testing/gtest/include/gtest/gtest.h" @@ -12,7 +13,7 @@ using media::DecoderBuffer; namespace mojo { namespace test { -TEST(MediaTypeConvertersTest, DecoderBuffer) { +TEST(MediaTypeConvertersTest, ConvertDecoderBuffer) { const uint8 kData[] = "hello, world"; const uint8 kSideData[] = "sideshow bob"; const int kDataSize = arraysize(kData); @@ -46,5 +47,24 @@ TEST(MediaTypeConvertersTest, DecoderBuffer) { // TODO(tim): Handle EOS, check other properties. +TEST(MediaTypeConvertersTest, ConvertAudioDecoderConfig) { + const uint8 kExtraData[] = "config extra data"; + const int kExtraDataSize = arraysize(kExtraData); + media::AudioDecoderConfig config; + config.Initialize(media::kCodecAAC, + media::kSampleFormatU8, + media::CHANNEL_LAYOUT_SURROUND, + 48000, + reinterpret_cast(&kExtraData), + kExtraDataSize, + false, + false, + base::TimeDelta(), + 0); + AudioDecoderConfigPtr ptr(AudioDecoderConfig::From(config)); + media::AudioDecoderConfig result(ptr.To()); + EXPECT_TRUE(result.Matches(config)); +} + } // namespace test } // namespace mojo diff --git a/media/mojo/services/mojo_demuxer_stream_adapter.cc b/media/mojo/services/mojo_demuxer_stream_adapter.cc new file mode 100644 index 00000000000000..fd70732d7a9c22 --- /dev/null +++ b/media/mojo/services/mojo_demuxer_stream_adapter.cc @@ -0,0 +1,103 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/mojo/services/mojo_demuxer_stream_adapter.h" + +#include "base/bind.h" +#include "base/callback_helpers.h" +#include "media/base/decoder_buffer.h" +#include "media/mojo/services/media_type_converters.h" + +namespace media { + +MojoDemuxerStreamAdapter::MojoDemuxerStreamAdapter( + mojo::DemuxerStreamPtr demuxer_stream, + const base::Closure& stream_ready_cb) + : demuxer_stream_(demuxer_stream.Pass()), + stream_ready_cb_(stream_ready_cb), + weak_factory_(this) { + demuxer_stream_.set_client(this); +} + +MojoDemuxerStreamAdapter::~MojoDemuxerStreamAdapter() { +} + +void MojoDemuxerStreamAdapter::Read(const DemuxerStream::ReadCB& read_cb) { + // We shouldn't be holding on to a previous callback if a new Read() came in. + DCHECK(read_cb_.is_null()); + read_cb_ = read_cb; + demuxer_stream_->Read(base::Bind(&MojoDemuxerStreamAdapter::OnBufferReady, + weak_factory_.GetWeakPtr())); +} + +AudioDecoderConfig MojoDemuxerStreamAdapter::audio_decoder_config() { + DCHECK(!config_queue_.empty()); + return config_queue_.front(); +} + +VideoDecoderConfig MojoDemuxerStreamAdapter::video_decoder_config() { + NOTREACHED(); + return VideoDecoderConfig(); +} + +media::DemuxerStream::Type MojoDemuxerStreamAdapter::type() { + return media::DemuxerStream::AUDIO; +} + +void MojoDemuxerStreamAdapter::EnableBitstreamConverter() { + NOTREACHED(); +} + +bool MojoDemuxerStreamAdapter::SupportsConfigChanges() { + return true; +} + +VideoRotation MojoDemuxerStreamAdapter::video_rotation() { + NOTIMPLEMENTED(); + return VIDEO_ROTATION_0; +} + +void MojoDemuxerStreamAdapter::OnStreamReady( + mojo::ScopedDataPipeConsumerHandle pipe) { + // TODO(tim): We don't support pipe streaming yet. + DCHECK(!pipe.is_valid()); + DCHECK(!config_queue_.empty()); + stream_ready_cb_.Run(); +} + +void MojoDemuxerStreamAdapter::OnAudioDecoderConfigChanged( + mojo::AudioDecoderConfigPtr config) { + config_queue_.push(config.To()); + + if (!read_cb_.is_null()) { + read_cb_.Run(media::DemuxerStream::Status::kConfigChanged, NULL); + read_cb_.Reset(); + } +} + +void MojoDemuxerStreamAdapter::OnBufferReady( + mojo::DemuxerStream::Status status, + mojo::MediaDecoderBufferPtr buffer) { + DCHECK(!read_cb_.is_null()); + DCHECK(!config_queue_.empty()); + + media::DemuxerStream::Status media_status( + static_cast(status)); + scoped_refptr media_buffer( + buffer.To >()); + + if (status == mojo::DemuxerStream::STATUS_CONFIG_CHANGED) { + DCHECK(!media_buffer.get()); + config_queue_.pop(); + + // If the |config_queue_| is empty we need to wait for + // OnAudioDecoderConfigChanged before invoking |read_cb|. + if (config_queue_.empty()) + return; + } + read_cb_.Run(media_status, media_buffer); + read_cb_.Reset(); +} + +} // namespace media diff --git a/media/mojo/services/mojo_demuxer_stream_adapter.h b/media/mojo/services/mojo_demuxer_stream_adapter.h new file mode 100644 index 00000000000000..1e792dae44cc6d --- /dev/null +++ b/media/mojo/services/mojo_demuxer_stream_adapter.h @@ -0,0 +1,75 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_MOJO_SERVICES_MOJO_DEMUXER_STREAM_ADAPTER_H_ +#define MEDIA_MOJO_SERVICES_MOJO_DEMUXER_STREAM_ADAPTER_H_ + +#include + +#include "base/memory/weak_ptr.h" +#include "media/base/audio_decoder_config.h" +#include "media/base/demuxer_stream.h" +#include "media/base/video_decoder_config.h" +#include "media/mojo/interfaces/demuxer_stream.mojom.h" + +namespace media { + +// This class acts as a MojoRendererService-side stub for a real +// media::DemuxerStream that is part of a media::Pipeline in a remote +// application. Roughly speaking, it takes a mojo::DemuxerStreamPtr and exposes +// it as a media::DemuxerStream for use by media components. +class MojoDemuxerStreamAdapter : public media::DemuxerStream, + public mojo::DemuxerStreamClient { + public: + // |demuxer_stream| is connected to the mojo::DemuxerStream that |this| will + // become the client of. + // |stream_ready_cb| will be invoked when |stream| has fully initialized + // and |this| is ready for use. + // NOTE: Illegal to call any methods until |stream_ready_cb| is invoked. + MojoDemuxerStreamAdapter(mojo::DemuxerStreamPtr demuxer_stream, + const base::Closure& stream_ready_cb); + virtual ~MojoDemuxerStreamAdapter(); + + // media::DemuxerStream implementation. + virtual void Read(const ReadCB& read_cb) OVERRIDE; + virtual AudioDecoderConfig audio_decoder_config() OVERRIDE; + virtual VideoDecoderConfig video_decoder_config() OVERRIDE; + virtual Type type() OVERRIDE; + virtual void EnableBitstreamConverter() OVERRIDE; + virtual bool SupportsConfigChanges() OVERRIDE; + virtual VideoRotation video_rotation() OVERRIDE; + + // mojo::DemuxerStreamClient implementation. + virtual void OnStreamReady(mojo::ScopedDataPipeConsumerHandle pipe) OVERRIDE; + virtual void OnAudioDecoderConfigChanged( + mojo::AudioDecoderConfigPtr config) OVERRIDE; + + private: + // The callback from |demuxer_stream_| that a read operation has completed. + // |read_cb| is a callback from the client who invoked Read() on |this|. + void OnBufferReady(mojo::DemuxerStream::Status status, + mojo::MediaDecoderBufferPtr buffer); + + // See constructor for descriptions. + mojo::DemuxerStreamPtr demuxer_stream_; + base::Closure stream_ready_cb_; + + // The last ReadCB received through a call to Read(). + // Used to store the results of OnBufferReady() in the event it is called + // with DemuxerStream::Status::kConfigChanged and we don't have an up to + // date AudioDecoderConfig yet. In that case we can't forward the results + // on to the caller of Read() until OnAudioDecoderConfigChanged is observed. + DemuxerStream::ReadCB read_cb_; + + // The front of the queue is the current config. We pop when we observe + // DemuxerStatus::CONFIG_CHANGED. + std::queue config_queue_; + + base::WeakPtrFactory weak_factory_; + DISALLOW_COPY_AND_ASSIGN(MojoDemuxerStreamAdapter); +}; + +} // namespace media + +#endif // MEDIA_MOJO_SERVICES_MOJO_DEMUXER_STREAM_ADAPTER_H_ diff --git a/media/mojo/services/mojo_demuxer_stream_impl.cc b/media/mojo/services/mojo_demuxer_stream_impl.cc new file mode 100644 index 00000000000000..0bce8a29fda84e --- /dev/null +++ b/media/mojo/services/mojo_demuxer_stream_impl.cc @@ -0,0 +1,60 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/mojo/services/mojo_demuxer_stream_impl.h" + +#include "base/bind.h" +#include "base/macros.h" +#include "media/base/audio_decoder_config.h" +#include "media/mojo/interfaces/demuxer_stream.mojom.h" +#include "media/mojo/services/media_type_converters.h" +#include "mojo/public/cpp/bindings/interface_impl.h" +#include "mojo/public/cpp/system/data_pipe.h" + +namespace media { + +MojoDemuxerStreamImpl::MojoDemuxerStreamImpl(media::DemuxerStream* stream) + : stream_(stream), weak_factory_(this) { +} + +MojoDemuxerStreamImpl::~MojoDemuxerStreamImpl() { +} + +void MojoDemuxerStreamImpl::Read(const mojo::Callback< + void(mojo::DemuxerStream::Status, mojo::MediaDecoderBufferPtr)>& callback) { + stream_->Read(base::Bind(&MojoDemuxerStreamImpl::OnBufferReady, + weak_factory_.GetWeakPtr(), + callback)); +} + +void MojoDemuxerStreamImpl::OnBufferReady( + const BufferReadyCB& callback, + media::DemuxerStream::Status status, + const scoped_refptr& buffer) { + if (status == media::DemuxerStream::kConfigChanged) { + // Send the config change so our client can read it once it parses the + // Status obtained via Run() below. + client()->OnAudioDecoderConfigChanged( + mojo::AudioDecoderConfig::From(stream_->audio_decoder_config())); + } + + // TODO(tim): Once using DataPipe, fill via the producer handle and then + // read more to keep the pipe full. + callback.Run(static_cast(status), + mojo::MediaDecoderBuffer::From(buffer)); +} + +void MojoDemuxerStreamImpl::OnConnectionEstablished() { + // This is called when our DemuxerStreamClient has connected itself and is + // ready to receive messages. Send an initial config and notify it that + // we are now ready for business. + client()->OnAudioDecoderConfigChanged( + mojo::AudioDecoderConfig::From(stream_->audio_decoder_config())); + + // TODO(tim): Create a DataPipe, hold the producer handle, and pass the + // consumer handle here. + client()->OnStreamReady(mojo::ScopedDataPipeConsumerHandle()); +} + +} // namespace media diff --git a/media/mojo/services/mojo_demuxer_stream_impl.h b/media/mojo/services/mojo_demuxer_stream_impl.h new file mode 100644 index 00000000000000..62dbfd2085bb57 --- /dev/null +++ b/media/mojo/services/mojo_demuxer_stream_impl.h @@ -0,0 +1,53 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_MOJO_SERVICES_MOJO_DEMUXER_STREAM_IMPL_H_ +#define MEDIA_MOJO_SERVICES_MOJO_DEMUXER_STREAM_IMPL_H_ + +#include "base/macros.h" +#include "base/memory/weak_ptr.h" +#include "media/base/demuxer_stream.h" +#include "media/mojo/interfaces/demuxer_stream.mojom.h" +#include "mojo/public/cpp/bindings/interface_impl.h" + +namespace media { +class DemuxerStream; + +// This class wraps a media::DemuxerStream and exposes it as a +// mojo::DemuxerStream for use as a proxy from remote applications. +class MojoDemuxerStreamImpl : public mojo::InterfaceImpl { + public: + // |stream| is the underlying DemuxerStream we are proxying for. + // Note: |this| does not take ownership of |stream|. + explicit MojoDemuxerStreamImpl(media::DemuxerStream* stream); + virtual ~MojoDemuxerStreamImpl(); + + // mojo::DemuxerStream implementation. + virtual void Read(const mojo::Callback< + void(mojo::DemuxerStream::Status, mojo::MediaDecoderBufferPtr)>& callback) + OVERRIDE; + + // mojo::InterfaceImpl overrides. + virtual void OnConnectionEstablished() OVERRIDE; + + private: + // |callback| is the callback that was passed to the initiating Read() + // call by our client. + // |status| and |buffer| are the standard media::ReadCB parameters. + typedef mojo::Callback BufferReadyCB; + void OnBufferReady(const BufferReadyCB& callback, + media::DemuxerStream::Status status, + const scoped_refptr& buffer); + + // See constructor. We do not own |stream_|. + media::DemuxerStream* stream_; + + base::WeakPtrFactory weak_factory_; + DISALLOW_COPY_AND_ASSIGN(MojoDemuxerStreamImpl); +}; + +} // namespace media + +#endif // MEDIA_MOJO_SERVICES_MOJO_DEMUXER_STREAM_IMPL_H_ diff --git a/media/mojo/services/mojo_renderer_impl.cc b/media/mojo/services/mojo_renderer_impl.cc new file mode 100644 index 00000000000000..d905b2c0eb7c1c --- /dev/null +++ b/media/mojo/services/mojo_renderer_impl.cc @@ -0,0 +1,127 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/mojo/services/mojo_renderer_impl.h" + +#include "base/bind.h" +#include "base/callback_helpers.h" +#include "base/single_thread_task_runner.h" +#include "media/base/demuxer_stream_provider.h" +#include "media/mojo/services/mojo_demuxer_stream_impl.h" +#include "mojo/public/cpp/application/connect.h" +#include "mojo/public/cpp/bindings/interface_impl.h" +#include "mojo/public/interfaces/application/service_provider.mojom.h" + +namespace media { + +MojoRendererImpl::MojoRendererImpl( + const scoped_refptr& task_runner, + DemuxerStreamProvider* demuxer_stream_provider, + mojo::ServiceProvider* audio_renderer_provider) + : task_runner_(task_runner), + demuxer_stream_provider_(demuxer_stream_provider), + weak_factory_(this) { + // For now we only support audio and there must be a provider. + DCHECK(audio_renderer_provider); + mojo::ConnectToService(audio_renderer_provider, &remote_audio_renderer_); + remote_audio_renderer_.set_client(this); +} + +MojoRendererImpl::~MojoRendererImpl() { + DCHECK(task_runner_->BelongsToCurrentThread()); + // Connection to |remote_audio_renderer_| will error-out here. +} + +void MojoRendererImpl::Initialize(const base::Closure& init_cb, + const StatisticsCB& statistics_cb, + const base::Closure& ended_cb, + const PipelineStatusCB& error_cb, + const BufferingStateCB& buffering_state_cb) { + DCHECK(task_runner_->BelongsToCurrentThread()); + init_cb_ = init_cb; + ended_cb_ = ended_cb; + error_cb_ = error_cb; + buffering_state_cb_ = buffering_state_cb; + + // Create a mojo::DemuxerStream and bind its lifetime to the pipe. + mojo::DemuxerStreamPtr demuxer_stream; + mojo::BindToProxy( + new MojoDemuxerStreamImpl( + demuxer_stream_provider_->GetStream(DemuxerStream::AUDIO)), + &demuxer_stream); + remote_audio_renderer_->Initialize(demuxer_stream.Pass(), init_cb); +} + +void MojoRendererImpl::Flush(const base::Closure& flush_cb) { + DCHECK(task_runner_->BelongsToCurrentThread()); + remote_audio_renderer_->Flush(flush_cb); +} + +void MojoRendererImpl::StartPlayingFrom(base::TimeDelta time) { + DCHECK(task_runner_->BelongsToCurrentThread()); + remote_audio_renderer_->StartPlayingFrom(time.InMicroseconds()); +} + +void MojoRendererImpl::SetPlaybackRate(float playback_rate) { + DCHECK(task_runner_->BelongsToCurrentThread()); + remote_audio_renderer_->SetPlaybackRate(playback_rate); +} + +void MojoRendererImpl::SetVolume(float volume) { + DCHECK(task_runner_->BelongsToCurrentThread()); + remote_audio_renderer_->SetVolume(volume); +} + +base::TimeDelta MojoRendererImpl::GetMediaTime() { + NOTIMPLEMENTED(); + return base::TimeDelta(); +} + +bool MojoRendererImpl::HasAudio() { + DCHECK(task_runner_->BelongsToCurrentThread()); + DCHECK(remote_audio_renderer_.get()); // We always bind the renderer. + return true; +} + +bool MojoRendererImpl::HasVideo() { + DCHECK(task_runner_->BelongsToCurrentThread()); + return false; +} + +void MojoRendererImpl::SetCdm(MediaKeys* cdm) { + DCHECK(task_runner_->BelongsToCurrentThread()); + NOTIMPLEMENTED(); +} + +void MojoRendererImpl::OnTimeUpdate(int64_t time_usec, int64_t max_time_usec) { + DCHECK(task_runner_->BelongsToCurrentThread()); + NOTIMPLEMENTED(); +} + +void MojoRendererImpl::OnBufferingStateChange(mojo::BufferingState state) { + DCHECK(task_runner_->BelongsToCurrentThread()); + buffering_state_cb_.Run(static_cast(state)); +} + +void MojoRendererImpl::OnEnded() { + DCHECK(task_runner_->BelongsToCurrentThread()); + ended_cb_.Run(); +} + +void MojoRendererImpl::OnError() { + DCHECK(task_runner_->BelongsToCurrentThread()); + // TODO(tim): Should we plumb error code from remote renderer? + // http://crbug.com/410451. + if (init_cb_.is_null()) // We have initialized already. + error_cb_.Run(PIPELINE_ERROR_DECODE); + else + error_cb_.Run(PIPELINE_ERROR_COULD_NOT_RENDER); +} + +void MojoRendererImpl::OnInitialized() { + DCHECK(!init_cb_.is_null()); + base::ResetAndReturn(&init_cb_).Run(); +} + +} // namespace media diff --git a/media/mojo/services/mojo_renderer_impl.h b/media/mojo/services/mojo_renderer_impl.h new file mode 100644 index 00000000000000..8b56ec3ad5e49c --- /dev/null +++ b/media/mojo/services/mojo_renderer_impl.h @@ -0,0 +1,90 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_MOJO_SERVICES_MOJO_RENDERER_IMPL_H_ +#define MEDIA_MOJO_SERVICES_MOJO_RENDERER_IMPL_H_ + +#include "base/macros.h" +#include "base/memory/weak_ptr.h" +#include "media/base/renderer.h" +#include "media/mojo/interfaces/media_renderer.mojom.h" + +namespace base { +class SingleThreadTaskRunner; +} + +namespace mojo { +class ServiceProvider; +} + +namespace media { +class DemuxerStreamProvider; + +// A media::Renderer that proxies to a mojo::MediaRenderer. That +// mojo::MediaRenderer proxies back to the MojoRendererImpl via the +// mojo::MediaRendererClient interface. +// +// MojoRendererImpl implements media::Renderer for use as either an audio +// or video renderer. +// +// TODO(tim): Only audio is currently supported. http://crbug.com/410451. +class MojoRendererImpl : public Renderer, public mojo::MediaRendererClient { + public: + // |task_runner| is the TaskRunner on which all methods are invoked. + // |demuxer_stream_provider| provides encoded streams for decoding and + // rendering. + // |audio_renderer_provider| is a ServiceProvider from a connected + // Application that is hosting a mojo::MediaRenderer. + MojoRendererImpl( + const scoped_refptr& task_runner, + DemuxerStreamProvider* demuxer_stream_provider, + mojo::ServiceProvider* audio_renderer_provider); + virtual ~MojoRendererImpl(); + + // Renderer implementation. + virtual void Initialize(const base::Closure& init_cb, + const StatisticsCB& statistics_cb, + const base::Closure& ended_cb, + const PipelineStatusCB& error_cb, + const BufferingStateCB& buffering_state_cb) OVERRIDE; + virtual void Flush(const base::Closure& flush_cb) OVERRIDE; + virtual void StartPlayingFrom(base::TimeDelta time) OVERRIDE; + virtual void SetPlaybackRate(float playback_rate) OVERRIDE; + virtual void SetVolume(float volume) OVERRIDE; + virtual base::TimeDelta GetMediaTime() OVERRIDE; + virtual bool HasAudio() OVERRIDE; + virtual bool HasVideo() OVERRIDE; + virtual void SetCdm(MediaKeys* cdm) OVERRIDE; + + // mojo::MediaRendererClient implementation. + virtual void OnTimeUpdate(int64_t time_usec, + int64_t max_time_usec) MOJO_OVERRIDE; + virtual void OnBufferingStateChange(mojo::BufferingState state) MOJO_OVERRIDE; + virtual void OnEnded() MOJO_OVERRIDE; + virtual void OnError() MOJO_OVERRIDE; + + private: + // Called when |remote_audio_renderer_| has finished initializing. + void OnInitialized(); + + // Task runner used to execute pipeline tasks. + scoped_refptr task_runner_; + + DemuxerStreamProvider* demuxer_stream_provider_; + mojo::MediaRendererPtr remote_audio_renderer_; + + // Callbacks passed to Initialize() that we forward messages from + // |remote_audio_renderer_| through. + base::Closure init_cb_; + base::Closure ended_cb_; + PipelineStatusCB error_cb_; + BufferingStateCB buffering_state_cb_; + + base::WeakPtrFactory weak_factory_; + DISALLOW_COPY_AND_ASSIGN(MojoRendererImpl); +}; + +} // namespace media + +#endif // MEDIA_MOJO_SERVICES_MOJO_RENDERER_IMPL_H_ diff --git a/media/mojo/services/mojo_renderer_service.cc b/media/mojo/services/mojo_renderer_service.cc new file mode 100644 index 00000000000000..5109a4253a94c6 --- /dev/null +++ b/media/mojo/services/mojo_renderer_service.cc @@ -0,0 +1,141 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/mojo/services/mojo_renderer_service.h" + +#include "base/bind.h" +#include "base/memory/scoped_vector.h" +#include "media/audio/null_audio_sink.h" +#include "media/base/audio_decoder.h" +#include "media/base/audio_renderer.h" +#include "media/base/audio_renderer_sink.h" +#include "media/base/decryptor.h" +#include "media/base/media_log.h" +#include "media/filters/audio_renderer_impl.h" +#include "media/mojo/services/mojo_demuxer_stream_adapter.h" +#include "mojo/application/application_runner_chromium.h" +#include "mojo/public/c/system/main.h" +#include "mojo/public/cpp/application/application_connection.h" +#include "mojo/public/cpp/application/application_delegate.h" +#include "mojo/public/cpp/application/interface_factory_impl.h" + +namespace media { + +class MojoRendererApplication + : public mojo::ApplicationDelegate, + public mojo::InterfaceFactory { + public: + // mojo::ApplicationDelegate implementation. + virtual bool ConfigureIncomingConnection( + mojo::ApplicationConnection* connection) OVERRIDE { + connection->AddService(this); + return true; + } + + // mojo::InterfaceFactory implementation. + virtual void Create( + mojo::ApplicationConnection* connection, + mojo::InterfaceRequest request) OVERRIDE { + mojo::BindToRequest(new MojoRendererService(connection), &request); + } +}; + +MojoRendererService::MojoRendererService( + mojo::ApplicationConnection* connection) + : hardware_config_(AudioParameters(), AudioParameters()), + weak_factory_(this), + weak_this_(weak_factory_.GetWeakPtr()) { + scoped_refptr runner( + base::MessageLoop::current()->task_runner()); + scoped_refptr media_log(new MediaLog()); + audio_renderer_.reset(new AudioRendererImpl( + runner, + // TODO(tim): We should use |connection| passed to MojoRendererService + // to connect to a MojoAudioRendererSink implementation that we would + // wrap in an AudioRendererSink and pass in here. + new NullAudioSink(runner), + // TODO(tim): Figure out how to select decoders. + ScopedVector(), + // TODO(tim): Not needed for now? + SetDecryptorReadyCB(), + hardware_config_, + media_log)); +} + +MojoRendererService::~MojoRendererService() { +} + +void MojoRendererService::Initialize(mojo::DemuxerStreamPtr stream, + const mojo::Callback& callback) { + DCHECK(client()); + stream_.reset(new MojoDemuxerStreamAdapter( + stream.Pass(), + base::Bind(&MojoRendererService::OnStreamReady, weak_this_))); + init_cb_ = callback; +} + +void MojoRendererService::Flush(const mojo::Callback& callback) { + NOTIMPLEMENTED(); +} + +void MojoRendererService::StartPlayingFrom(int64_t time_delta_usec) { + NOTIMPLEMENTED(); +} + +void MojoRendererService::SetPlaybackRate(float playback_rate) { + NOTIMPLEMENTED(); +} + +void MojoRendererService::SetVolume(float volume) { + NOTIMPLEMENTED(); +} + +void MojoRendererService::OnStreamReady() { + audio_renderer_->Initialize( + stream_.get(), + base::Bind(&MojoRendererService::OnAudioRendererInitializeDone, + weak_this_), + base::Bind(&MojoRendererService::OnUpdateStatistics, weak_this_), + base::Bind(&MojoRendererService::OnBufferingStateChanged, weak_this_), + base::Bind(&MojoRendererService::OnAudioRendererEnded, weak_this_), + base::Bind(&MojoRendererService::OnError, weak_this_)); +} + +void MojoRendererService::OnAudioRendererInitializeDone(PipelineStatus status) { + if (status != PIPELINE_OK) { + audio_renderer_.reset(); + client()->OnError(); + } + init_cb_.Run(); +} + +void MojoRendererService::OnUpdateStatistics(const PipelineStatistics& stats) { + NOTIMPLEMENTED(); +} + +void MojoRendererService::OnAudioTimeUpdate(base::TimeDelta time, + base::TimeDelta max_time) { + client()->OnTimeUpdate(time.InMicroseconds(), max_time.InMicroseconds()); +} + +void MojoRendererService::OnBufferingStateChanged( + media::BufferingState new_buffering_state) { + client()->OnBufferingStateChange( + static_cast(new_buffering_state)); +} + +void MojoRendererService::OnAudioRendererEnded() { + client()->OnEnded(); +} + +void MojoRendererService::OnError(PipelineStatus error) { + client()->OnError(); +} + +} // namespace media + +MojoResult MojoMain(MojoHandle shell_handle) { + mojo::ApplicationRunnerChromium runner(new media::MojoRendererApplication); + return runner.Run(shell_handle); +} diff --git a/media/mojo/services/mojo_renderer_service.h b/media/mojo/services/mojo_renderer_service.h new file mode 100644 index 00000000000000..0459c31da633c1 --- /dev/null +++ b/media/mojo/services/mojo_renderer_service.h @@ -0,0 +1,89 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_MOJO_SERVICES_MOJO_RENDERER_SERVICE_H_ +#define MEDIA_MOJO_SERVICES_MOJO_RENDERER_SERVICE_H_ + +#include "base/callback.h" +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "media/base/audio_decoder_config.h" +#include "media/base/audio_hardware_config.h" +#include "media/base/buffering_state.h" +#include "media/base/pipeline_status.h" +#include "media/mojo/interfaces/media_renderer.mojom.h" +#include "mojo/public/cpp/bindings/interface_impl.h" + +namespace mojo { +class ApplicationConnection; +} + +namespace media { + +class AudioRenderer; +class MojoDemuxerStreamAdapter; + +// A mojo::MediaRenderer implementation that uses media::AudioRenderer to +// decode and render audio to a sink obtained from the ApplicationConnection. +class MojoRendererService : public mojo::InterfaceImpl { + public: + // |connection| is a pointer to the connection back to our embedder. The + // embedder should have configured it (via ConfigureOutgoingConnection) to + // allow |this| to connect to a sink that will receive decoded data ready + // for playback. + explicit MojoRendererService(mojo::ApplicationConnection* connection); + virtual ~MojoRendererService(); + + // mojo::MediaRenderer implementation. + virtual void Initialize(mojo::DemuxerStreamPtr stream, + const mojo::Callback& callback) MOJO_OVERRIDE; + virtual void Flush(const mojo::Callback& callback) MOJO_OVERRIDE; + virtual void StartPlayingFrom(int64_t time_delta_usec) MOJO_OVERRIDE; + virtual void SetPlaybackRate(float playback_rate) MOJO_OVERRIDE; + virtual void SetVolume(float volume) MOJO_OVERRIDE; + + private: + // Called when the MojoDemuxerStreamAdapter is ready to go (has a config, + // pipe handle, etc) and can be handed off to a renderer for use. + void OnStreamReady(); + + // Called when |audio_renderer_| initialization has completed. + void OnAudioRendererInitializeDone(PipelineStatus status); + + // Callback executed by filters to update statistics. + void OnUpdateStatistics(const PipelineStatistics& stats); + + // Callback executed by audio renderer to update clock time. + void OnAudioTimeUpdate(base::TimeDelta time, base::TimeDelta max_time); + + // Callback executed by audio renderer when buffering state changes. + // TODO(tim): Need old and new. + void OnBufferingStateChanged(BufferingState new_buffering_state); + + // Callback executed when a renderer has ended. + void OnAudioRendererEnded(); + + // Callback executed when a runtime error happens. + void OnError(PipelineStatus error); + + scoped_ptr stream_; + scoped_ptr audio_renderer_; + + mojo::Callback init_cb_; + + // TODO(tim): Figure out how to set up hardware config. + // NOTE: AudioRendererImpl stores a const& to the config we pass in (hmm..). + // Hence stack-allocating one and passing it to Initialize results in + // undefined badness (e.g, hangs trying to acquire config_lock_); + media::AudioHardwareConfig hardware_config_; + + base::WeakPtrFactory weak_factory_; + base::WeakPtr weak_this_; + DISALLOW_COPY_AND_ASSIGN(MojoRendererService); +}; + +} // namespace media + +#endif // MEDIA_MOJO_SERVICES_MOJO_RENDERER_SERVICE_H_ diff --git a/media/mojo/services/renderer_unittest.cc b/media/mojo/services/renderer_unittest.cc new file mode 100644 index 00000000000000..e41557e082e75b --- /dev/null +++ b/media/mojo/services/renderer_unittest.cc @@ -0,0 +1,184 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/at_exit.h" +#include "base/bind.h" +#include "base/command_line.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "media/base/audio_decoder_config.h" +#include "media/base/channel_layout.h" +#include "media/base/demuxer_stream_provider.h" +#include "media/base/sample_format.h" +#include "media/base/video_decoder_config.h" +#include "media/mojo/services/mojo_renderer_impl.h" +#include "mojo/public/c/system/main.h" +#include "mojo/public/cpp/application/application_delegate.h" +#include "mojo/public/cpp/application/application_impl.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +// This class is here to give the gtest class access to the +// mojo::ApplicationImpl so that the tests can connect to other applications. +class MojoRendererTestHelper : public mojo::ApplicationDelegate { + public: + MojoRendererTestHelper() : application_impl_(NULL) {} + virtual ~MojoRendererTestHelper() {} + + // ApplicationDelegate implementation. + virtual void Initialize(mojo::ApplicationImpl* app) OVERRIDE { + application_impl_ = app; + } + + mojo::ApplicationImpl* application_impl() { return application_impl_; } + + private: + mojo::ApplicationImpl* application_impl_; + + DISALLOW_COPY_AND_ASSIGN(MojoRendererTestHelper); +}; + +// TODO(tim): Reconcile this with mojo apptest framework when ready. +MojoRendererTestHelper* g_test_delegate = NULL; + +// TODO(tim): Make media::FakeDemuxerStream support audio and use that for the +// DemuxerStream implementation instead. +class FakeDemuxerStream : public media::DemuxerStreamProvider, + public media::DemuxerStream { + public: + FakeDemuxerStream() {} + virtual ~FakeDemuxerStream() {} + + // media::Demuxer implementation. + virtual media::DemuxerStream* GetStream( + media::DemuxerStream::Type type) OVERRIDE { + DCHECK_EQ(media::DemuxerStream::AUDIO, type); + return this; + } + virtual media::DemuxerStreamProvider::Liveness GetLiveness() const OVERRIDE { + return media::DemuxerStreamProvider::LIVENESS_UNKNOWN; + } + + // media::DemuxerStream implementation. + virtual void Read(const ReadCB& read_cb) OVERRIDE {} + + virtual media::AudioDecoderConfig audio_decoder_config() OVERRIDE { + media::AudioDecoderConfig config; + config.Initialize(media::kCodecAAC, + media::kSampleFormatU8, + media::CHANNEL_LAYOUT_SURROUND, + 48000, + NULL, + 0, + false, + false, + base::TimeDelta(), + 0); + return config; + } + + virtual media::VideoDecoderConfig video_decoder_config() OVERRIDE { + NOTREACHED(); + return media::VideoDecoderConfig(); + } + + virtual media::DemuxerStream::Type type() OVERRIDE { + return media::DemuxerStream::AUDIO; + } + + virtual void EnableBitstreamConverter() OVERRIDE {} + + virtual bool SupportsConfigChanges() OVERRIDE { return true; } + + virtual media::VideoRotation video_rotation() OVERRIDE { + NOTREACHED(); + return media::VIDEO_ROTATION_0; + } + + private: + DISALLOW_COPY_AND_ASSIGN(FakeDemuxerStream); +}; + +} // namespace + +namespace media { + +class MojoRendererTest : public testing::Test { + public: + MojoRendererTest() : service_provider_(NULL) {} + + virtual void SetUp() OVERRIDE { + demuxer_stream_provider_.reset(new FakeDemuxerStream()); + service_provider_ = + g_test_delegate->application_impl() + ->ConnectToApplication("mojo:media_mojo_renderer_app") + ->GetServiceProvider(); + } + + mojo::ServiceProvider* service_provider() { return service_provider_; } + DemuxerStreamProvider* stream_provider() { + return demuxer_stream_provider_.get(); + } + scoped_refptr task_runner() { + return base::MessageLoop::current()->task_runner(); + } + + private: + scoped_ptr demuxer_stream_provider_; + mojo::ServiceProvider* service_provider_; + + DISALLOW_COPY_AND_ASSIGN(MojoRendererTest); +}; + +void ErrorCallback(PipelineStatus* output, PipelineStatus status) { + *output = status; +} + +// Tests that a MojoRendererImpl can successfully establish communication +// with a MojoRendererService and set up a MojoDemuxerStream +// connection. The test also initializes a media::AudioRendererImpl which +// will error-out expectedly due to lack of support for decoder selection. +TEST_F(MojoRendererTest, BasicInitialize) { + MojoRendererImpl rimpl(task_runner(), stream_provider(), service_provider()); + PipelineStatus expected_error(PIPELINE_OK); + rimpl.Initialize(base::MessageLoop::current()->QuitClosure(), + media::StatisticsCB(), + base::Closure(), + base::Bind(&ErrorCallback, &expected_error), + media::BufferingStateCB()); + base::MessageLoop::current()->Run(); + + // We expect an error during initialization because MojoRendererService + // doesn't initialize any decoders, which causes an error. + EXPECT_EQ(PIPELINE_ERROR_COULD_NOT_RENDER, expected_error); +} + +} // namespace media + +MojoResult MojoMain(MojoHandle shell_handle) { + base::CommandLine::Init(0, NULL); +#if !defined(COMPONENT_BUILD) + base::AtExitManager at_exit; +#endif + + // TODO(tim): Reconcile this with apptest framework when it is ready. + scoped_ptr delegate(new MojoRendererTestHelper()); + g_test_delegate = static_cast(delegate.get()); + { + base::MessageLoop loop; + mojo::ApplicationImpl impl( + delegate.get(), + mojo::MakeScopedHandle(mojo::MessagePipeHandle(shell_handle))); + + int argc = 0; + char** argv = NULL; + testing::InitGoogleTest(&argc, argv); + mojo_ignore_result(RUN_ALL_TESTS()); + } + + g_test_delegate = NULL; + delegate.reset(); + return MOJO_RESULT_OK; +}