diff options
author | Allan Sandfeld Jensen <allan.jensen@theqtcompany.com> | 2016-05-09 14:22:11 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2016-05-09 15:11:45 +0000 |
commit | 2ddb2d3e14eef3de7dbd0cef553d669b9ac2361c (patch) | |
tree | e75f511546c5fd1a173e87c1f9fb11d7ac8d1af3 /chromium/media/base | |
parent | a4f3d46271c57e8155ba912df46a05559d14726e (diff) | |
download | qtwebengine-chromium-2ddb2d3e14eef3de7dbd0cef553d669b9ac2361c.tar.gz |
BASELINE: Update Chromium to 51.0.2704.41
Also adds in all smaller components by reversing logic for exclusion.
Change-Id: Ibf90b506e7da088ea2f65dcf23f2b0992c504422
Reviewed-by: Joerg Bornemann <joerg.bornemann@theqtcompany.com>
Diffstat (limited to 'chromium/media/base')
179 files changed, 6344 insertions, 3514 deletions
diff --git a/chromium/media/base/BUILD.gn b/chromium/media/base/BUILD.gn index 531d6dd616e..c0213562122 100644 --- a/chromium/media/base/BUILD.gn +++ b/chromium/media/base/BUILD.gn @@ -8,10 +8,14 @@ import("//build/config/features.gni") import("//build/config/ui.gni") import("//build/config/linux/pkg_config.gni") import("//media/media_options.gni") +import("//testing/libfuzzer/fuzzer_test.gni") source_set("base") { # This is part of the media component. - visibility = [ "//media" ] + visibility = [ + "//media", + "//media/capture", + ] sources = [ "audio_block_fifo.cc", "audio_block_fifo.h", @@ -22,6 +26,8 @@ source_set("base") { "audio_buffer_queue.cc", "audio_buffer_queue.h", "audio_capturer_source.h", + "audio_codecs.cc", + "audio_codecs.h", "audio_converter.cc", "audio_converter.h", "audio_decoder.cc", @@ -38,6 +44,8 @@ source_set("base") { "audio_hash.h", "audio_pull_fifo.cc", "audio_pull_fifo.h", + "audio_push_fifo.cc", + "audio_push_fifo.h", "audio_renderer.cc", "audio_renderer.h", "audio_renderer_mixer.cc", @@ -80,14 +88,20 @@ source_set("base") { "channel_mixer.h", "channel_mixing_matrix.cc", "channel_mixing_matrix.h", + "container_names.cc", + "container_names.h", "data_buffer.cc", "data_buffer.h", "data_source.cc", "data_source.h", + "decode_status.cc", + "decode_status.h", "decoder_buffer.cc", "decoder_buffer.h", "decoder_buffer_queue.cc", "decoder_buffer_queue.h", + "decoder_factory.cc", + "decoder_factory.h", "decrypt_config.cc", "decrypt_config.h", "decryptor.cc", @@ -101,13 +115,13 @@ source_set("base") { "djb2.cc", "djb2.h", "eme_constants.h", + "encryption_scheme.cc", + "encryption_scheme.h", "key_system_info.cc", "key_system_info.h", "key_systems.cc", "key_systems.h", "key_systems.h", - "key_systems_support_uma.cc", - "key_systems_support_uma.h", "loopback_audio_converter.cc", "loopback_audio_converter.h", "media.cc", @@ -125,19 +139,27 @@ source_set("base") { "media_resources.h", "media_switches.cc", "media_switches.h", + "media_track.cc", + "media_track.h", + "media_tracks.cc", + "media_tracks.h", "media_util.cc", "media_util.h", "mime_util.cc", "mime_util.h", + "mime_util_internal.cc", + "mime_util_internal.h", "moving_average.cc", "moving_average.h", "multi_channel_resampler.cc", "multi_channel_resampler.h", "null_video_sink.cc", "null_video_sink.h", - "output_device.h", - "pipeline.cc", + "output_device_info.cc", + "output_device_info.h", "pipeline.h", + "pipeline_impl.cc", + "pipeline_impl.h", "pipeline_status.h", "player_tracker.cc", "player_tracker.h", @@ -165,6 +187,7 @@ source_set("base") { "stream_parser.h", "stream_parser_buffer.cc", "stream_parser_buffer.h", + "surface_manager.h", "text_cue.cc", "text_cue.h", "text_ranges.cc", @@ -226,10 +249,6 @@ source_set("base") { ] if (media_use_ffmpeg) { - sources += [ - "container_names.cc", - "container_names.h", - ] if (!is_android) { sources += [ "audio_video_metadata_extractor.cc", @@ -245,9 +264,7 @@ source_set("base") { if (is_android) { public_deps = [ "//media/base/android", - "//media/base/android:media_java", "//media/base/android:media_jni_headers", - "//media/base/android:video_capture_jni_headers", ] allow_circular_includes_from += [ "//media/base/android" ] } @@ -342,8 +359,8 @@ source_set("test_support") { "fake_demuxer_stream.h", "fake_media_resources.cc", "fake_media_resources.h", - "fake_output_device.cc", - "fake_output_device.h", + "fake_single_thread_task_runner.cc", + "fake_single_thread_task_runner.h", "fake_text_track_stream.cc", "fake_text_track_stream.h", "gmock_callback_support.h", @@ -359,6 +376,7 @@ source_set("test_support") { "test_data_util.h", "test_helpers.cc", "test_helpers.h", + "test_random.h", ] configs += [ "//media:media_config" ] deps = [ @@ -381,6 +399,7 @@ source_set("unittests") { "audio_hardware_config_unittest.cc", "audio_hash_unittest.cc", "audio_pull_fifo_unittest.cc", + "audio_push_fifo_unittest.cc", "audio_renderer_mixer_input_unittest.cc", "audio_renderer_mixer_unittest.cc", "audio_shifter_unittest.cc", @@ -404,7 +423,7 @@ source_set("unittests") { "moving_average_unittest.cc", "multi_channel_resampler_unittest.cc", "null_video_sink_unittest.cc", - "pipeline_unittest.cc", + "pipeline_impl_unittest.cc", "ranges_unittest.cc", "run_all_unittests.cc", "seekable_buffer_unittest.cc", @@ -544,3 +563,23 @@ if (current_cpu == "x86" || current_cpu == "x64") { } } } + +fuzzer_test("media_bit_reader_fuzzer") { + sources = [ + "bit_reader_fuzzertest.cc", + ] + deps = [ + "//base", + "//media", + ] +} + +fuzzer_test("media_container_names_fuzzer") { + sources = [ + "container_names_fuzzertest.cc", + ] + deps = [ + "//base", + "//media", + ] +} diff --git a/chromium/media/base/android/BUILD.gn b/chromium/media/base/android/BUILD.gn index 54d80743d09..c3fd33f6e11 100644 --- a/chromium/media/base/android/BUILD.gn +++ b/chromium/media/base/android/BUILD.gn @@ -18,13 +18,13 @@ source_set("android") { "android_cdm_factory.h", "audio_decoder_job.cc", "audio_decoder_job.h", + "audio_media_codec_decoder.cc", + "audio_media_codec_decoder.h", "demuxer_android.h", "demuxer_stream_player_params.cc", "demuxer_stream_player_params.h", "media_client_android.cc", "media_client_android.h", - "media_codec_audio_decoder.cc", - "media_codec_audio_decoder.h", "media_codec_bridge.cc", "media_codec_bridge.h", "media_codec_decoder.cc", @@ -33,12 +33,12 @@ source_set("android") { "media_codec_player.h", "media_codec_util.cc", "media_codec_util.h", - "media_codec_video_decoder.cc", - "media_codec_video_decoder.h", "media_decoder_job.cc", "media_decoder_job.h", "media_drm_bridge.cc", "media_drm_bridge.h", + "media_drm_bridge_cdm_context.cc", + "media_drm_bridge_cdm_context.h", "media_drm_bridge_delegate.cc", "media_drm_bridge_delegate.h", "media_jni_registrar.cc", @@ -64,6 +64,8 @@ source_set("android") { "sdk_media_codec_bridge.h", "video_decoder_job.cc", "video_decoder_job.h", + "video_media_codec_decoder.cc", + "video_media_codec_decoder.h", ] configs += [ "//media:media_config", @@ -83,7 +85,6 @@ source_set("unittests") { sources = [ "access_unit_queue_unittest.cc", "media_codec_decoder_unittest.cc", - "media_codec_player_unittest.cc", "media_drm_bridge_unittest.cc", "media_player_bridge_unittest.cc", "media_source_player_unittest.cc", @@ -92,6 +93,11 @@ source_set("unittests") { "test_data_factory.h", "test_statistics.h", ] + + if (proprietary_codecs) { + sources += [ "media_codec_player_unittest.cc" ] + } + deps = [ ":android", "//media/base:test_support", @@ -115,27 +121,18 @@ generate_jni("media_jni_headers") { jni_package = "media" } -generate_jni("video_capture_jni_headers") { - sources = [ - "java/src/org/chromium/media/VideoCapture.java", - "java/src/org/chromium/media/VideoCaptureFactory.java", - ] - jni_package = "media" -} - -java_cpp_enum("media_java_enums_srcjar") { - sources = [ - "//media/capture/video/android/video_capture_device_android.h", - "//media/capture/video/video_capture_device.h", - ] -} - android_library("media_java") { deps = [ "//base:base_java", ] - srcjar_deps = [ ":media_java_enums_srcjar" ] - - DEPRECATED_java_in_dir = "java/src" + java_files = [ + "java/src/org/chromium/media/AudioManagerAndroid.java", + "java/src/org/chromium/media/AudioRecordInput.java", + "java/src/org/chromium/media/MediaCodecBridge.java", + "java/src/org/chromium/media/MediaCodecUtil.java", + "java/src/org/chromium/media/MediaDrmBridge.java", + "java/src/org/chromium/media/MediaPlayerBridge.java", + "java/src/org/chromium/media/MediaPlayerListener.java", + ] } diff --git a/chromium/media/base/android/audio_decoder_job.cc b/chromium/media/base/android/audio_decoder_job.cc index dd1fa5346f1..f0baca4e949 100644 --- a/chromium/media/base/android/audio_decoder_job.cc +++ b/chromium/media/base/android/audio_decoder_job.cc @@ -105,9 +105,16 @@ void AudioDecoderJob::ReleaseOutputBuffer( render_output = render_output && (size != 0u); bool is_audio_underrun = false; if (render_output) { - int64_t head_position = + bool postpone = false; + int64_t head_position; + MediaCodecStatus status = (static_cast<AudioCodecBridge*>(media_codec_bridge_.get())) - ->PlayOutputBuffer(output_buffer_index, size, offset); + ->PlayOutputBuffer(output_buffer_index, size, offset, postpone, + &head_position); + // TODO(timav,watk): This CHECK maintains the behavior of this call before + // we started catching CodecException and returning it as MEDIA_CODEC_ERROR. + // It needs to be handled some other way. http://crbug.com/585978 + CHECK_EQ(status, MEDIA_CODEC_OK); base::TimeTicks current_time = base::TimeTicks::Now(); @@ -191,7 +198,12 @@ void AudioDecoderJob::OnOutputFormatChanged() { DCHECK(media_codec_bridge_); int old_sampling_rate = output_sampling_rate_; - output_sampling_rate_ = media_codec_bridge_->GetOutputSamplingRate(); + MediaCodecStatus status = + media_codec_bridge_->GetOutputSamplingRate(&output_sampling_rate_); + // TODO(timav,watk): This CHECK maintains the behavior of this call before + // we started catching CodecException and returning it as MEDIA_CODEC_ERROR. + // It needs to be handled some other way. http://crbug.com/585978 + CHECK_EQ(status, MEDIA_CODEC_OK); if (output_sampling_rate_ != old_sampling_rate) ResetTimestampHelper(); } diff --git a/chromium/media/base/android/media_codec_audio_decoder.cc b/chromium/media/base/android/audio_media_codec_decoder.cc index e1c1fdb45e6..5654e7ca4a0 100644 --- a/chromium/media/base/android/media_codec_audio_decoder.cc +++ b/chromium/media/base/android/audio_media_codec_decoder.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "media/base/android/media_codec_audio_decoder.h" +#include "media/base/android/audio_media_codec_decoder.h" #include "base/bind.h" #include "base/logging.h" @@ -20,7 +20,7 @@ const int kBytesPerAudioOutputSample = 2; namespace media { -MediaCodecAudioDecoder::MediaCodecAudioDecoder( +AudioMediaCodecDecoder::AudioMediaCodecDecoder( const scoped_refptr<base::SingleThreadTaskRunner>& media_task_runner, FrameStatistics* frame_statistics, const base::Closure& request_data_cb, @@ -43,26 +43,25 @@ MediaCodecAudioDecoder::MediaCodecAudioDecoder( bytes_per_frame_(0), output_sampling_rate_(0), frame_count_(0), - update_current_time_cb_(update_current_time_cb) { -} + update_current_time_cb_(update_current_time_cb) {} -MediaCodecAudioDecoder::~MediaCodecAudioDecoder() { +AudioMediaCodecDecoder::~AudioMediaCodecDecoder() { DCHECK(media_task_runner_->BelongsToCurrentThread()); DVLOG(1) << "AudioDecoder::~AudioDecoder()"; ReleaseDecoderResources(); } -const char* MediaCodecAudioDecoder::class_name() const { +const char* AudioMediaCodecDecoder::class_name() const { return "AudioDecoder"; } -bool MediaCodecAudioDecoder::HasStream() const { +bool AudioMediaCodecDecoder::HasStream() const { DCHECK(media_task_runner_->BelongsToCurrentThread()); return configs_.audio_codec != kUnknownAudioCodec; } -void MediaCodecAudioDecoder::SetDemuxerConfigs(const DemuxerConfigs& configs) { +void AudioMediaCodecDecoder::SetDemuxerConfigs(const DemuxerConfigs& configs) { DVLOG(1) << class_name() << "::" << __FUNCTION__ << " " << configs; configs_ = configs; @@ -70,13 +69,13 @@ void MediaCodecAudioDecoder::SetDemuxerConfigs(const DemuxerConfigs& configs) { output_sampling_rate_ = configs.audio_sampling_rate; } -bool MediaCodecAudioDecoder::IsContentEncrypted() const { +bool AudioMediaCodecDecoder::IsContentEncrypted() const { // Make sure SetDemuxerConfigs() as been called. DCHECK(configs_.audio_codec != kUnknownAudioCodec); return configs_.is_audio_encrypted; } -void MediaCodecAudioDecoder::ReleaseDecoderResources() { +void AudioMediaCodecDecoder::ReleaseDecoderResources() { DCHECK(media_task_runner_->BelongsToCurrentThread()); DVLOG(1) << class_name() << "::" << __FUNCTION__; @@ -85,14 +84,14 @@ void MediaCodecAudioDecoder::ReleaseDecoderResources() { ReleaseMediaCodec(); } -void MediaCodecAudioDecoder::Flush() { +void AudioMediaCodecDecoder::Flush() { DCHECK(media_task_runner_->BelongsToCurrentThread()); MediaCodecDecoder::Flush(); frame_count_ = 0; } -void MediaCodecAudioDecoder::SetVolume(double volume) { +void AudioMediaCodecDecoder::SetVolume(double volume) { DCHECK(media_task_runner_->BelongsToCurrentThread()); DVLOG(1) << class_name() << "::" << __FUNCTION__ << " " << volume; @@ -101,7 +100,7 @@ void MediaCodecAudioDecoder::SetVolume(double volume) { SetVolumeInternal(); } -void MediaCodecAudioDecoder::SetBaseTimestamp(base::TimeDelta base_timestamp) { +void AudioMediaCodecDecoder::SetBaseTimestamp(base::TimeDelta base_timestamp) { // Called from Media thread and Decoder thread. When called from Media thread // Decoder thread should not be running. @@ -112,7 +111,7 @@ void MediaCodecAudioDecoder::SetBaseTimestamp(base::TimeDelta base_timestamp) { audio_timestamp_helper_->SetBaseTimestamp(base_timestamp_); } -bool MediaCodecAudioDecoder::IsCodecReconfigureNeeded( +bool AudioMediaCodecDecoder::IsCodecReconfigureNeeded( const DemuxerConfigs& next) const { if (always_reconfigure_for_tests_) return true; @@ -127,7 +126,7 @@ bool MediaCodecAudioDecoder::IsCodecReconfigureNeeded( next.audio_extra_data.begin()); } -MediaCodecDecoder::ConfigStatus MediaCodecAudioDecoder::ConfigureInternal( +MediaCodecDecoder::ConfigStatus AudioMediaCodecDecoder::ConfigureInternal( jobject media_crypto) { DCHECK(media_task_runner_->BelongsToCurrentThread()); @@ -170,18 +169,19 @@ MediaCodecDecoder::ConfigStatus MediaCodecAudioDecoder::ConfigureInternal( return kConfigOk; } -void MediaCodecAudioDecoder::OnOutputFormatChanged() { +void AudioMediaCodecDecoder::OnOutputFormatChanged() { DCHECK(decoder_thread_.task_runner()->BelongsToCurrentThread()); DCHECK(media_codec_bridge_); int old_sampling_rate = output_sampling_rate_; - output_sampling_rate_ = media_codec_bridge_->GetOutputSamplingRate(); - if (output_sampling_rate_ != old_sampling_rate) + MediaCodecStatus status = + media_codec_bridge_->GetOutputSamplingRate(&output_sampling_rate_); + if (status != MEDIA_CODEC_OK || output_sampling_rate_ != old_sampling_rate) ResetTimestampHelper(); } -void MediaCodecAudioDecoder::Render(int buffer_index, +void AudioMediaCodecDecoder::Render(int buffer_index, size_t offset, size_t size, RenderMode render_mode, @@ -202,8 +202,13 @@ void MediaCodecAudioDecoder::Render(int buffer_index, const bool postpone = (render_mode == kRenderAfterPreroll); - int64_t head_position = - audio_codec->PlayOutputBuffer(buffer_index, size, offset, postpone); + int64_t head_position; + MediaCodecStatus status = audio_codec->PlayOutputBuffer( + buffer_index, size, offset, postpone, &head_position); + // TODO(timav,watk): This CHECK maintains the behavior of this call before + // we started catching CodecException and returning it as MEDIA_CODEC_ERROR. + // It needs to be handled some other way. http://crbug.com/585978 + CHECK_EQ(status, MEDIA_CODEC_OK); base::TimeTicks current_time = base::TimeTicks::Now(); @@ -263,7 +268,7 @@ void MediaCodecAudioDecoder::Render(int buffer_index, CheckLastFrame(eos_encountered, false); // no delayed tasks } -void MediaCodecAudioDecoder::SetVolumeInternal() { +void AudioMediaCodecDecoder::SetVolumeInternal() { DCHECK(media_task_runner_->BelongsToCurrentThread()); if (media_codec_bridge_) { @@ -272,7 +277,7 @@ void MediaCodecAudioDecoder::SetVolumeInternal() { } } -void MediaCodecAudioDecoder::ResetTimestampHelper() { +void AudioMediaCodecDecoder::ResetTimestampHelper() { // Media thread or Decoder thread // When this method is called on Media thread, decoder thread // should not be running. diff --git a/chromium/media/base/android/media_codec_audio_decoder.h b/chromium/media/base/android/audio_media_codec_decoder.h index 434edf062ee..ce264b60ee6 100644 --- a/chromium/media/base/android/media_codec_audio_decoder.h +++ b/chromium/media/base/android/audio_media_codec_decoder.h @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef MEDIA_BASE_ANDROID_MEDIA_CODEC_AUDIO_DECODER_H_ -#define MEDIA_BASE_ANDROID_MEDIA_CODEC_AUDIO_DECODER_H_ +#ifndef MEDIA_BASE_ANDROID_AUDIO_MEDIA_CODEC_DECODER_H_ +#define MEDIA_BASE_ANDROID_AUDIO_MEDIA_CODEC_DECODER_H_ #include <stddef.h> #include <stdint.h> @@ -16,12 +16,12 @@ namespace media { class AudioTimestampHelper; // Audio decoder for MediaCodecPlayer -class MediaCodecAudioDecoder : public MediaCodecDecoder { +class AudioMediaCodecDecoder : public MediaCodecDecoder { public: // For parameters see media_codec_decoder.h // update_current_time_cb: callback that reports current playback time. // Called for each rendered frame. - MediaCodecAudioDecoder( + AudioMediaCodecDecoder( const scoped_refptr<base::SingleThreadTaskRunner>& media_runner, FrameStatistics* frame_statistics, const base::Closure& request_data_cb, @@ -31,7 +31,7 @@ class MediaCodecAudioDecoder : public MediaCodecDecoder { const base::Closure& waiting_for_decryption_key_cb, const base::Closure& error_cb, const SetTimeCallback& update_current_time_cb); - ~MediaCodecAudioDecoder() override; + ~AudioMediaCodecDecoder() override; const char* class_name() const override; @@ -95,9 +95,9 @@ class MediaCodecAudioDecoder : public MediaCodecDecoder { // The time limit for the next frame to avoid underrun. base::TimeTicks next_frame_time_limit_; - DISALLOW_COPY_AND_ASSIGN(MediaCodecAudioDecoder); + DISALLOW_COPY_AND_ASSIGN(AudioMediaCodecDecoder); }; } // namespace media -#endif // MEDIA_BASE_ANDROID_MEDIA_CODEC_DECODER_H_ +#endif // MEDIA_BASE_ANDROID_AUDIO_MEDIA_CODEC_DECODER_H_ diff --git a/chromium/media/base/android/demuxer_stream_player_params.cc b/chromium/media/base/android/demuxer_stream_player_params.cc index bafcb6e01fa..739105eb59f 100644 --- a/chromium/media/base/android/demuxer_stream_player_params.cc +++ b/chromium/media/base/android/demuxer_stream_player_params.cc @@ -17,14 +17,20 @@ DemuxerConfigs::DemuxerConfigs() video_codec(kUnknownVideoCodec), is_video_encrypted(false) {} +DemuxerConfigs::DemuxerConfigs(const DemuxerConfigs& other) = default; + DemuxerConfigs::~DemuxerConfigs() {} AccessUnit::AccessUnit() : is_end_of_stream(false), is_key_frame(false) {} +AccessUnit::AccessUnit(const AccessUnit& other) = default; + AccessUnit::~AccessUnit() {} DemuxerData::DemuxerData() : type(DemuxerStream::UNKNOWN) {} +DemuxerData::DemuxerData(const DemuxerData& other) = default; + DemuxerData::~DemuxerData() {} namespace { diff --git a/chromium/media/base/android/demuxer_stream_player_params.h b/chromium/media/base/android/demuxer_stream_player_params.h index f7619c60480..e0f98b7e08b 100644 --- a/chromium/media/base/android/demuxer_stream_player_params.h +++ b/chromium/media/base/android/demuxer_stream_player_params.h @@ -20,6 +20,7 @@ namespace media { struct MEDIA_EXPORT DemuxerConfigs { DemuxerConfigs(); + DemuxerConfigs(const DemuxerConfigs& other); ~DemuxerConfigs(); AudioCodec audio_codec; @@ -40,6 +41,7 @@ struct MEDIA_EXPORT DemuxerConfigs { struct MEDIA_EXPORT AccessUnit { AccessUnit(); + AccessUnit(const AccessUnit& other); ~AccessUnit(); DemuxerStream::Status status; @@ -55,6 +57,7 @@ struct MEDIA_EXPORT AccessUnit { struct MEDIA_EXPORT DemuxerData { DemuxerData(); + DemuxerData(const DemuxerData& other); ~DemuxerData(); DemuxerStream::Type type; diff --git a/chromium/media/base/android/media_codec_bridge.cc b/chromium/media/base/android/media_codec_bridge.cc index ca5588f4e65..833d51ef890 100644 --- a/chromium/media/base/android/media_codec_bridge.cc +++ b/chromium/media/base/android/media_codec_bridge.cc @@ -42,20 +42,15 @@ MediaCodecStatus MediaCodecBridge::QueueSecureInputBuffer( subsamples.size(), presentation_time); } -int MediaCodecBridge::GetOutputBuffersCount() { - return 0; -} - -size_t MediaCodecBridge::GetOutputBuffersCapacity() { - return 0; -} - bool MediaCodecBridge::FillInputBuffer(int index, const uint8_t* data, size_t size) { uint8_t* dst = nullptr; size_t capacity = 0; - GetInputBuffer(index, &dst, &capacity); + if (GetInputBuffer(index, &dst, &capacity) != MEDIA_CODEC_OK) { + LOG(ERROR) << "GetInputBuffer failed"; + return false; + } CHECK(dst); if (size > capacity) { diff --git a/chromium/media/base/android/media_codec_bridge.h b/chromium/media/base/android/media_codec_bridge.h index 41a7351ff63..25d61f97cc3 100644 --- a/chromium/media/base/android/media_codec_bridge.h +++ b/chromium/media/base/android/media_codec_bridge.h @@ -17,6 +17,7 @@ #include "base/time/time.h" #include "media/base/android/media_codec_util.h" #include "media/base/media_export.h" +#include "ui/gfx/geometry/size.h" namespace media { @@ -49,7 +50,7 @@ class MEDIA_EXPORT MediaCodecBridge { // DequeueInputBuffer() and DequeueOutputBuffer() become invalid. // Please note that this clears all the inputs in the media codec. In other // words, there will be no outputs until new input is provided. - // Returns MEDIA_CODEC_ERROR if an unexpected error happens, or Media_CODEC_OK + // Returns MEDIA_CODEC_ERROR if an unexpected error happens, or MEDIA_CODEC_OK // otherwise. virtual MediaCodecStatus Reset() = 0; @@ -64,13 +65,15 @@ class MEDIA_EXPORT MediaCodecBridge { // instance -> StartAudio/Video() is recommended. virtual void Stop() = 0; - // Used for getting output format. This is valid after DequeueInputBuffer() - // returns a format change by returning INFO_OUTPUT_FORMAT_CHANGED - virtual void GetOutputFormat(int* width, int* height) = 0; + // Used for getting the output size. This is valid after DequeueInputBuffer() + // returns a format change by returning INFO_OUTPUT_FORMAT_CHANGED. + // Returns MEDIA_CODEC_ERROR if an error occurs, or MEDIA_CODEC_OK otherwise. + virtual MediaCodecStatus GetOutputSize(gfx::Size* size) = 0; // Used for checking for new sampling rate after DequeueInputBuffer() returns // INFO_OUTPUT_FORMAT_CHANGED - virtual int GetOutputSamplingRate() = 0; + // Returns MEDIA_CODEC_ERROR if an error occurs, or MEDIA_CODEC_OK otherwise. + virtual MediaCodecStatus GetOutputSamplingRate(int* sampling_rate) = 0; // Submits a byte array to the given input buffer. Call this after getting an // available buffer from DequeueInputBuffer(). If |data| is NULL, assume the @@ -143,25 +146,19 @@ class MEDIA_EXPORT MediaCodecBridge { // configuring this video decoder you can optionally render the buffer. virtual void ReleaseOutputBuffer(int index, bool render) = 0; - // Returns the number of output buffers used by the codec. - // TODO(qinmin): this call is deprecated in Lollipop. - virtual int GetOutputBuffersCount(); - - // Returns the capacity of each output buffer used by the codec. - // TODO(qinmin): this call is deprecated in Lollipop. - virtual size_t GetOutputBuffersCapacity(); - // Returns an input buffer's base pointer and capacity. - virtual void GetInputBuffer(int input_buffer_index, - uint8_t** data, - size_t* capacity) = 0; - - // Copy |dst_size| bytes from output buffer |index|'s |offset| onwards into - // |*dst|. - virtual bool CopyFromOutputBuffer(int index, - size_t offset, - void* dst, - int dst_size) = 0; + virtual MediaCodecStatus GetInputBuffer(int input_buffer_index, + uint8_t** data, + size_t* capacity) = 0; + + // Copy |num| bytes from output buffer |index|'s |offset| into the memory + // region pointed to by |dst|. To avoid overflows, the size of both source + // and destination must be at least |num| bytes, and should not overlap. + // Returns MEDIA_CODEC_ERROR if an error occurs, or MEDIA_CODEC_OK otherwise. + virtual MediaCodecStatus CopyFromOutputBuffer(int index, + size_t offset, + void* dst, + size_t num) = 0; protected: MediaCodecBridge(); diff --git a/chromium/media/base/android/media_codec_decoder_unittest.cc b/chromium/media/base/android/media_codec_decoder_unittest.cc index ea0ffb35f36..087aec5689e 100644 --- a/chromium/media/base/android/media_codec_decoder_unittest.cc +++ b/chromium/media/base/android/media_codec_decoder_unittest.cc @@ -9,13 +9,13 @@ #include "base/macros.h" #include "base/thread_task_runner_handle.h" #include "base/timer/timer.h" -#include "media/base/android/media_codec_audio_decoder.h" +#include "media/base/android/audio_media_codec_decoder.h" #include "media/base/android/media_codec_util.h" -#include "media/base/android/media_codec_video_decoder.h" #include "media/base/android/media_statistics.h" #include "media/base/android/sdk_media_codec_bridge.h" #include "media/base/android/test_data_factory.h" #include "media/base/android/test_statistics.h" +#include "media/base/android/video_media_codec_decoder.h" #include "media/base/timestamp_constants.h" #include "testing/gtest/include/gtest/gtest.h" #include "ui/gl/android/surface_texture.h" @@ -266,7 +266,7 @@ bool MediaCodecDecoderTest::WaitForCondition(const Predicate& condition, } void MediaCodecDecoderTest::CreateAudioDecoder() { - decoder_ = scoped_ptr<MediaCodecDecoder>(new MediaCodecAudioDecoder( + decoder_ = scoped_ptr<MediaCodecDecoder>(new AudioMediaCodecDecoder( task_runner_, &frame_statistics_, base::Bind(&MediaCodecDecoderTest::OnDataRequested, base::Unretained(this)), @@ -284,7 +284,7 @@ void MediaCodecDecoderTest::CreateAudioDecoder() { } void MediaCodecDecoderTest::CreateVideoDecoder() { - decoder_ = scoped_ptr<MediaCodecDecoder>(new MediaCodecVideoDecoder( + decoder_ = scoped_ptr<MediaCodecDecoder>(new VideoMediaCodecDecoder( task_runner_, &frame_statistics_, base::Bind(&MediaCodecDecoderTest::OnDataRequested, base::Unretained(this)), @@ -320,8 +320,8 @@ void MediaCodecDecoderTest::SetVideoSurface() { surface_texture_ = gfx::SurfaceTexture::Create(0); gfx::ScopedJavaSurface surface(surface_texture_.get()); ASSERT_NE(nullptr, decoder_.get()); - MediaCodecVideoDecoder* video_decoder = - static_cast<MediaCodecVideoDecoder*>(decoder_.get()); + VideoMediaCodecDecoder* video_decoder = + static_cast<VideoMediaCodecDecoder*>(decoder_.get()); video_decoder->SetVideoSurface(std::move(surface)); } @@ -446,8 +446,8 @@ TEST_F(MediaCodecDecoderTest, VideoConfigureInvalidSurface) { // Release the surface texture. surface_texture = NULL; - MediaCodecVideoDecoder* video_decoder = - static_cast<MediaCodecVideoDecoder*>(decoder_.get()); + VideoMediaCodecDecoder* video_decoder = + static_cast<VideoMediaCodecDecoder*>(decoder_.get()); video_decoder->SetVideoSurface(std::move(surface)); EXPECT_EQ(MediaCodecDecoder::kConfigFailure, decoder_->Configure(nullptr)); @@ -505,7 +505,7 @@ TEST_F(MediaCodecDecoderTest, AudioStartWithoutConfigure) { } // http://crbug.com/518900 -TEST_F(MediaCodecDecoderTest, AudioPlayTillCompletion) { +TEST_F(MediaCodecDecoderTest, DISABLED_AudioPlayTillCompletion) { SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE(); DVLOG(0) << "AudioPlayTillCompletion started"; diff --git a/chromium/media/base/android/media_codec_player.cc b/chromium/media/base/android/media_codec_player.cc index 565c7452c3a..b5c3b1b4d4c 100644 --- a/chromium/media/base/android/media_codec_player.cc +++ b/chromium/media/base/android/media_codec_player.cc @@ -13,11 +13,11 @@ #include "base/logging.h" #include "base/thread_task_runner_handle.h" #include "base/threading/thread.h" -#include "media/base/android/media_codec_audio_decoder.h" -#include "media/base/android/media_codec_video_decoder.h" +#include "media/base/android/audio_media_codec_decoder.h" #include "media/base/android/media_drm_bridge.h" #include "media/base/android/media_player_manager.h" #include "media/base/android/media_task_runner.h" +#include "media/base/android/video_media_codec_decoder.h" #include "media/base/bind_to_current_loop.h" #include "media/base/timestamp_constants.h" @@ -41,11 +41,13 @@ MediaCodecPlayer::MediaCodecPlayer( base::WeakPtr<MediaPlayerManager> manager, const OnDecoderResourcesReleasedCB& on_decoder_resources_released_cb, scoped_ptr<DemuxerAndroid> demuxer, - const GURL& frame_url) + const GURL& frame_url, + int media_session_id) : MediaPlayerAndroid(player_id, manager.get(), on_decoder_resources_released_cb, - frame_url), + frame_url, + media_session_id), ui_task_runner_(base::ThreadTaskRunnerHandle::Get()), demuxer_(std::move(demuxer)), state_(kStatePaused), @@ -102,6 +104,10 @@ MediaCodecPlayer::~MediaCodecPlayer() audio_decoder_->ReleaseDecoderResources(); if (cdm_) { + // Cancel previously registered callback (if any). + static_cast<MediaDrmBridge*>(cdm_.get()) + ->SetMediaCryptoReadyCB(MediaDrmBridge::MediaCryptoReadyCB()); + DCHECK(cdm_registration_id_); static_cast<MediaDrmBridge*>(cdm_.get()) ->UnregisterPlayer(cdm_registration_id_); @@ -359,11 +365,11 @@ void MediaCodecPlayer::Release() { } } -void MediaCodecPlayer::SetVolume(double volume) { - RUN_ON_MEDIA_THREAD(SetVolume, volume); +void MediaCodecPlayer::UpdateEffectiveVolumeInternal(double effective_volume) { + RUN_ON_MEDIA_THREAD(UpdateEffectiveVolumeInternal, effective_volume); - DVLOG(1) << __FUNCTION__ << " " << volume; - audio_decoder_->SetVolume(volume); + DVLOG(1) << __FUNCTION__ << " " << effective_volume; + audio_decoder_->SetVolume(effective_volume); } bool MediaCodecPlayer::HasAudio() const { @@ -1347,7 +1353,7 @@ void MediaCodecPlayer::CreateDecoders() { media_stat_.reset(new MediaStatistics()); - audio_decoder_.reset(new MediaCodecAudioDecoder( + audio_decoder_.reset(new AudioMediaCodecDecoder( GetMediaTaskRunner(), &media_stat_->audio_frame_stats(), base::Bind(&MediaCodecPlayer::RequestDemuxerData, media_weak_this_, DemuxerStream::AUDIO), @@ -1359,11 +1365,10 @@ void MediaCodecPlayer::CreateDecoders() { DemuxerStream::AUDIO), base::Bind(&MediaCodecPlayer::OnMissingKeyReported, media_weak_this_, DemuxerStream::AUDIO), - internal_error_cb_, - base::Bind(&MediaCodecPlayer::OnTimeIntervalUpdate, media_weak_this_, - DemuxerStream::AUDIO))); + internal_error_cb_, base::Bind(&MediaCodecPlayer::OnTimeIntervalUpdate, + media_weak_this_, DemuxerStream::AUDIO))); - video_decoder_.reset(new MediaCodecVideoDecoder( + video_decoder_.reset(new VideoMediaCodecDecoder( GetMediaTaskRunner(), &media_stat_->video_frame_stats(), base::Bind(&MediaCodecPlayer::RequestDemuxerData, media_weak_this_, DemuxerStream::VIDEO), @@ -1375,9 +1380,8 @@ void MediaCodecPlayer::CreateDecoders() { DemuxerStream::VIDEO), base::Bind(&MediaCodecPlayer::OnMissingKeyReported, media_weak_this_, DemuxerStream::VIDEO), - internal_error_cb_, - base::Bind(&MediaCodecPlayer::OnTimeIntervalUpdate, media_weak_this_, - DemuxerStream::VIDEO), + internal_error_cb_, base::Bind(&MediaCodecPlayer::OnTimeIntervalUpdate, + media_weak_this_, DemuxerStream::VIDEO), base::Bind(&MediaCodecPlayer::OnVideoResolutionChanged, media_weak_this_))); } diff --git a/chromium/media/base/android/media_codec_player.h b/chromium/media/base/android/media_codec_player.h index eca83e225e0..83b4164d5e9 100644 --- a/chromium/media/base/android/media_codec_player.h +++ b/chromium/media/base/android/media_codec_player.h @@ -158,8 +158,8 @@ namespace media { -class MediaCodecAudioDecoder; -class MediaCodecVideoDecoder; +class AudioMediaCodecDecoder; +class VideoMediaCodecDecoder; class MEDIA_EXPORT MediaCodecPlayer : public MediaPlayerAndroid, public DemuxerAndroidClient { @@ -191,7 +191,8 @@ class MEDIA_EXPORT MediaCodecPlayer : public MediaPlayerAndroid, base::WeakPtr<MediaPlayerManager> manager, const OnDecoderResourcesReleasedCB& on_decoder_resources_released_cb, scoped_ptr<DemuxerAndroid> demuxer, - const GURL& frame_url); + const GURL& frame_url, + int media_session_id); ~MediaCodecPlayer() override; // A helper method that performs the media thread part of initialization. @@ -204,7 +205,6 @@ class MEDIA_EXPORT MediaCodecPlayer : public MediaPlayerAndroid, void Pause(bool is_media_related_action) override; void SeekTo(base::TimeDelta timestamp) override; void Release() override; - void SetVolume(double volume) override; bool HasVideo() const override; bool HasAudio() const override; int GetVideoWidth() override; @@ -268,6 +268,7 @@ class MEDIA_EXPORT MediaCodecPlayer : public MediaPlayerAndroid, }; // MediaPlayerAndroid implementation. + void UpdateEffectiveVolumeInternal(double effective_volume) override; // This method requests playback permission from the manager on UI // thread, passing total duration and whether the media has audio @@ -344,8 +345,8 @@ class MEDIA_EXPORT MediaCodecPlayer : public MediaPlayerAndroid, // Major components: demuxer, audio and video decoders. scoped_ptr<DemuxerAndroid> demuxer_; - scoped_ptr<MediaCodecAudioDecoder> audio_decoder_; - scoped_ptr<MediaCodecVideoDecoder> video_decoder_; + scoped_ptr<AudioMediaCodecDecoder> audio_decoder_; + scoped_ptr<VideoMediaCodecDecoder> video_decoder_; // The state of the state machine. PlayerState state_; diff --git a/chromium/media/base/android/media_codec_player_unittest.cc b/chromium/media/base/android/media_codec_player_unittest.cc index 5281c4280f1..eb3f827bbba 100644 --- a/chromium/media/base/android/media_codec_player_unittest.cc +++ b/chromium/media/base/android/media_codec_player_unittest.cc @@ -382,7 +382,7 @@ class MockDemuxerAndroid : public DemuxerAndroid { // Conditions to wait for. bool IsInitialized() const { return client_; } - bool HasPendingConfigs() const { return pending_configs_; } + bool HasPendingConfigs() const { return !!pending_configs_; } bool ReceivedSeekRequest() const { return num_seeks_ > 0; } bool ReceivedBrowserSeekRequest() const { return num_browser_seeks_ > 0; } @@ -644,7 +644,7 @@ void MediaCodecPlayerTest::CreatePlayer() { manager_.GetWeakPtr(), base::Bind(&MockMediaPlayerManager::OnMediaResourcesRequested, base::Unretained(&manager_)), - scoped_ptr<MockDemuxerAndroid>(demuxer_), GURL()); + scoped_ptr<MockDemuxerAndroid>(demuxer_), GURL(), kDefaultMediaSessionId); DCHECK(player_); } @@ -938,7 +938,7 @@ TEST_F(MediaCodecPlayerTest, SetAudioVideoConfigsAfterPlayerCreation) { EXPECT_EQ(240, manager_.media_metadata_.height); } -TEST_F(MediaCodecPlayerTest, AudioPlayTillCompletion) { +TEST_F(MediaCodecPlayerTest, DISABLED_AudioPlayTillCompletion) { SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE(); base::TimeDelta duration = base::TimeDelta::FromMilliseconds(1000); @@ -1053,7 +1053,7 @@ TEST_F(MediaCodecPlayerTest, VideoNoPermission) { } // http://crbug.com/518900 -TEST_F(MediaCodecPlayerTest, AudioSeekAfterStop) { +TEST_F(MediaCodecPlayerTest, DISABLED_AudioSeekAfterStop) { SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE(); // Play for 300 ms, then Pause, then Seek to beginning. The playback should @@ -1112,7 +1112,7 @@ TEST_F(MediaCodecPlayerTest, AudioSeekAfterStop) { &MockMediaPlayerManager::IsSeekCompleted, base::Unretained(&manager_)))); } -TEST_F(MediaCodecPlayerTest, AudioSeekThenPlay) { +TEST_F(MediaCodecPlayerTest, DISABLED_AudioSeekThenPlay) { SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE(); // Issue Seek command immediately followed by Start. The playback should @@ -1146,7 +1146,7 @@ TEST_F(MediaCodecPlayerTest, AudioSeekThenPlay) { &MockMediaPlayerManager::IsSeekCompleted, base::Unretained(&manager_)))); } -TEST_F(MediaCodecPlayerTest, AudioSeekThenPlayThenConfig) { +TEST_F(MediaCodecPlayerTest, DISABLED_AudioSeekThenPlayThenConfig) { SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE(); // Issue Seek command immediately followed by Start but without prior demuxer @@ -1186,7 +1186,7 @@ TEST_F(MediaCodecPlayerTest, AudioSeekThenPlayThenConfig) { } // http://crbug.com/518900 -TEST_F(MediaCodecPlayerTest, AudioSeekWhilePlaying) { +TEST_F(MediaCodecPlayerTest, DISABLED_AudioSeekWhilePlaying) { SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE(); // Play for 300 ms, then issue several Seek commands in the row. @@ -1265,7 +1265,7 @@ TEST_F(MediaCodecPlayerTest, VideoReplaceSurface) { EXPECT_LE(duration, manager_.pts_stat_.max()); } -TEST_F(MediaCodecPlayerTest, VideoRemoveAndSetSurface) { +TEST_F(MediaCodecPlayerTest, DISABLED_VideoRemoveAndSetSurface) { SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE(); base::TimeDelta duration = base::TimeDelta::FromMilliseconds(1000); @@ -1315,7 +1315,7 @@ TEST_F(MediaCodecPlayerTest, VideoRemoveAndSetSurface) { } // http://crbug.com/518900 -TEST_F(MediaCodecPlayerTest, VideoReleaseAndStart) { +TEST_F(MediaCodecPlayerTest, DISABLED_VideoReleaseAndStart) { SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE(); base::TimeDelta duration = base::TimeDelta::FromMilliseconds(1000); @@ -1363,7 +1363,7 @@ TEST_F(MediaCodecPlayerTest, VideoReleaseAndStart) { EXPECT_LE(max_pts_before_backgrounding, manager_.pts_stat_.max()); } -TEST_F(MediaCodecPlayerTest, VideoSeekAndRelease) { +TEST_F(MediaCodecPlayerTest, DISABLED_VideoSeekAndRelease) { SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE(); base::TimeDelta duration = base::TimeDelta::FromMilliseconds(2000); @@ -1526,7 +1526,7 @@ TEST_F(MediaCodecPlayerTest, VideoPrerollAfterSeek) { EXPECT_EQ(6, manager_.pts_stat_.num_values()); } -TEST_F(MediaCodecPlayerTest, AVPrerollAudioWaitsForVideo) { +TEST_F(MediaCodecPlayerTest, DISABLED_AVPrerollAudioWaitsForVideo) { SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE(); // Test that during prerolling neither audio nor video plays and that both @@ -1577,7 +1577,7 @@ TEST_F(MediaCodecPlayerTest, AVPrerollAudioWaitsForVideo) { EXPECT_TRUE(AlmostEqual(seek_position, manager_.pts_stat_.min(), 25)); } -TEST_F(MediaCodecPlayerTest, AVPrerollReleaseAndRestart) { +TEST_F(MediaCodecPlayerTest, DISABLED_AVPrerollReleaseAndRestart) { SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE(); // Test that player will resume prerolling if prerolling is interrupted by @@ -1651,7 +1651,7 @@ TEST_F(MediaCodecPlayerTest, AVPrerollReleaseAndRestart) { EXPECT_TRUE(AlmostEqual(seek_position, manager_.pts_stat_.min(), 50)); } -TEST_F(MediaCodecPlayerTest, AVPrerollStopAndRestart) { +TEST_F(MediaCodecPlayerTest, DISABLED_AVPrerollStopAndRestart) { SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE(); // Test that if Pause() happens during the preroll phase, @@ -1743,7 +1743,7 @@ TEST_F(MediaCodecPlayerTest, AVPrerollStopAndRestart) { EXPECT_TRUE(AlmostEqual(seek_position, manager_.pts_stat_.min(), 25)); } -TEST_F(MediaCodecPlayerTest, AVPrerollVideoEndsWhilePrerolling) { +TEST_F(MediaCodecPlayerTest, DISABLED_AVPrerollVideoEndsWhilePrerolling) { SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE(); // Test that when one stream ends in the preroll phase and another is not @@ -1828,7 +1828,7 @@ TEST_F(MediaCodecPlayerTest, AVPrerollVideoEndsWhilePrerolling) { DVLOG(0) << "AVPrerollVideoEndsWhilePrerolling: end"; } -TEST_F(MediaCodecPlayerTest, VideoConfigChangeWhilePlaying) { +TEST_F(MediaCodecPlayerTest, DISABLED_VideoConfigChangeWhilePlaying) { SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE(); // Test that video only playback continues after video config change. @@ -1898,7 +1898,8 @@ TEST_F(MediaCodecPlayerTest, VideoConfigChangeWhilePlaying) { manager_.render_stat_[DemuxerStream::VIDEO].num_values()); } -TEST_F(MediaCodecPlayerTest, AVVideoConfigChangeWhilePlaying) { +// https://crbug.com/587195 +TEST_F(MediaCodecPlayerTest, DISABLED_AVVideoConfigChangeWhilePlaying) { SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE(); // Test that A/V playback continues after video config change. @@ -1945,7 +1946,7 @@ TEST_F(MediaCodecPlayerTest, AVVideoConfigChangeWhilePlaying) { manager_.render_stat_[DemuxerStream::AUDIO].num_values()); } -TEST_F(MediaCodecPlayerTest, AVAudioConfigChangeWhilePlaying) { +TEST_F(MediaCodecPlayerTest, DISABLED_AVAudioConfigChangeWhilePlaying) { SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE(); // Test that A/V playback continues after audio config change. @@ -1989,7 +1990,7 @@ TEST_F(MediaCodecPlayerTest, AVAudioConfigChangeWhilePlaying) { manager_.render_stat_[DemuxerStream::AUDIO].num_values()); } -TEST_F(MediaCodecPlayerTest, AVSimultaneousConfigChange_1) { +TEST_F(MediaCodecPlayerTest, DISABLED_AVSimultaneousConfigChange_1) { SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE(); // Test that the playback continues if audio and video config changes happen @@ -2036,7 +2037,7 @@ TEST_F(MediaCodecPlayerTest, AVSimultaneousConfigChange_1) { manager_.render_stat_[DemuxerStream::AUDIO].num_values()); } -TEST_F(MediaCodecPlayerTest, AVSimultaneousConfigChange_2) { +TEST_F(MediaCodecPlayerTest, DISABLED_AVSimultaneousConfigChange_2) { SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE(); // Test that the playback continues if audio and video config changes happen @@ -2084,7 +2085,7 @@ TEST_F(MediaCodecPlayerTest, AVSimultaneousConfigChange_2) { manager_.render_stat_[DemuxerStream::AUDIO].num_values()); } -TEST_F(MediaCodecPlayerTest, AVAudioEndsAcrossVideoConfigChange) { +TEST_F(MediaCodecPlayerTest, DISABLED_AVAudioEndsAcrossVideoConfigChange) { SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE(); // Test that audio can end while video config change processing. @@ -2134,7 +2135,8 @@ TEST_F(MediaCodecPlayerTest, AVAudioEndsAcrossVideoConfigChange) { EXPECT_EQ(video_duration, manager_.pts_stat_.max()); } -TEST_F(MediaCodecPlayerTest, AVVideoEndsAcrossAudioConfigChange) { +// https://crbug.com/587195 +TEST_F(MediaCodecPlayerTest, DISABLED_AVVideoEndsAcrossAudioConfigChange) { SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE(); // Test that video can end while audio config change processing. @@ -2173,7 +2175,7 @@ TEST_F(MediaCodecPlayerTest, AVVideoEndsAcrossAudioConfigChange) { manager_.render_stat_[DemuxerStream::AUDIO].num_values()); } -TEST_F(MediaCodecPlayerTest, AVPrerollAcrossVideoConfigChange) { +TEST_F(MediaCodecPlayerTest, DISABLED_AVPrerollAcrossVideoConfigChange) { SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE(); // Test that preroll continues if interrupted by video config change. @@ -2222,7 +2224,7 @@ TEST_F(MediaCodecPlayerTest, AVPrerollAcrossVideoConfigChange) { EXPECT_TRUE(AlmostEqual(seek_position, manager_.pts_stat_.min(), 25)); } -TEST_F(MediaCodecPlayerTest, AVPrerollAcrossAudioConfigChange) { +TEST_F(MediaCodecPlayerTest, DISABLED_AVPrerollAcrossAudioConfigChange) { SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE(); // Test that preroll continues if interrupted by video config change. diff --git a/chromium/media/base/android/media_codec_util.cc b/chromium/media/base/android/media_codec_util.cc index 7650ecd7f78..21877cc8fe3 100644 --- a/chromium/media/base/android/media_codec_util.cc +++ b/chromium/media/base/android/media_codec_util.cc @@ -25,8 +25,7 @@ using base::android::ScopedJavaLocalRef; namespace media { -// static -const std::string CodecTypeToAndroidMimeType(const std::string& codec) { +static std::string CodecTypeToAndroidMimeType(const std::string& codec) { // TODO(xhwang): Shall we handle more detailed strings like "mp4a.40.2"? if (codec == "avc1") return "video/avc"; @@ -45,34 +44,9 @@ const std::string CodecTypeToAndroidMimeType(const std::string& codec) { return std::string(); } -// TODO(qinmin): using a map to help all the conversions in this class. -const std::string AndroidMimeTypeToCodecType(const std::string& mime) { - if (mime == "video/mp4v-es") - return "mp4v"; - if (mime == "video/avc") - return "avc1"; - if (mime == "video/hevc") - return "hvc1"; - if (mime == "video/x-vnd.on2.vp8") - return "vp8"; - if (mime == "video/x-vnd.on2.vp9") - return "vp9"; - if (mime == "audio/mp4a-latm") - return "mp4a"; - if (mime == "audio/mpeg") - return "mp3"; - if (mime == "audio/vorbis") - return "vorbis"; - if (mime == "audio/opus") - return "opus"; - return std::string(); -} - -std::string GetDefaultCodecName(const std::string& mime_type, - MediaCodecDirection direction) { - if (!MediaCodecUtil::IsMediaCodecAvailable()) - return std::string(); - +static std::string GetDefaultCodecName(const std::string& mime_type, + MediaCodecDirection direction) { + DCHECK(MediaCodecUtil::IsMediaCodecAvailable()); JNIEnv* env = AttachCurrentThread(); ScopedJavaLocalRef<jstring> j_mime = ConvertUTF8ToJavaString(env, mime_type); ScopedJavaLocalRef<jstring> j_codec_name = @@ -80,47 +54,11 @@ std::string GetDefaultCodecName(const std::string& mime_type, return ConvertJavaStringToUTF8(env, j_codec_name.obj()); } -bool SupportsGetName() { - // MediaCodec.getName() is only available on JB MR2 and greater. - return base::android::BuildInfo::GetInstance()->sdk_int() >= 18; -} - -// Represents supported codecs on android. -// TODO(qinmin): Currently the codecs string only contains one codec. Do we -// need to support codecs separated by comma. (e.g. "vp8" -> "vp8, vp8.0")? -struct CodecsInfo { - std::string codecs; // E.g. "vp8" or "avc1". - std::string name; // E.g. "OMX.google.vp8.decoder". - MediaCodecDirection direction; -}; - -// Get a list of supported codecs. -std::vector<CodecsInfo> GetCodecsInfo() { - std::vector<CodecsInfo> codecs_info; - if (!MediaCodecUtil::IsMediaCodecAvailable()) - return codecs_info; - +static bool IsDecoderSupportedByDevice(const std::string& mime_type) { + DCHECK(MediaCodecUtil::IsMediaCodecAvailable()); JNIEnv* env = AttachCurrentThread(); - std::string mime_type; - ScopedJavaLocalRef<jobjectArray> j_codec_info_array = - Java_MediaCodecUtil_getCodecsInfo(env); - jsize len = env->GetArrayLength(j_codec_info_array.obj()); - for (jsize i = 0; i < len; ++i) { - ScopedJavaLocalRef<jobject> j_info( - env, env->GetObjectArrayElement(j_codec_info_array.obj(), i)); - ScopedJavaLocalRef<jstring> j_codec_type = - Java_CodecInfo_codecType(env, j_info.obj()); - ConvertJavaStringToUTF8(env, j_codec_type.obj(), &mime_type); - ScopedJavaLocalRef<jstring> j_codec_name = - Java_CodecInfo_codecName(env, j_info.obj()); - CodecsInfo info; - info.codecs = AndroidMimeTypeToCodecType(mime_type); - ConvertJavaStringToUTF8(env, j_codec_name.obj(), &info.name); - info.direction = static_cast<MediaCodecDirection>( - Java_CodecInfo_direction(env, j_info.obj())); - codecs_info.push_back(info); - } - return codecs_info; + ScopedJavaLocalRef<jstring> j_mime = ConvertUTF8ToJavaString(env, mime_type); + return Java_MediaCodecUtil_isDecoderSupportedForDevice(env, j_mime.obj()); } // static @@ -183,40 +121,33 @@ bool MediaCodecUtil::IsKnownUnaccelerated(const std::string& mime_type, if (!IsMediaCodecAvailable()) return true; - std::string codec_name; - if (SupportsGetName()) { - codec_name = GetDefaultCodecName(mime_type, direction); - } else { - std::string codec_type = AndroidMimeTypeToCodecType(mime_type); - std::vector<CodecsInfo> codecs_info = GetCodecsInfo(); - for (size_t i = 0; i < codecs_info.size(); ++i) { - if (codecs_info[i].codecs == codec_type && - codecs_info[i].direction == direction) { - codec_name = codecs_info[i].name; - break; - } - } + std::string codec_name = GetDefaultCodecName(mime_type, direction); + DVLOG(1) << __FUNCTION__ << "Default codec for " << mime_type << " : " + << codec_name << ", direction: " << direction; + if (!codec_name.size()) + return true; + + // MediaTek hardware vp8 is known slower than the software implementation. + // MediaTek hardware vp9 is known crashy, see http://crbug.com/446974 and + // http://crbug.com/597836. + if (base::StartsWith(codec_name, "OMX.MTK.", base::CompareCase::SENSITIVE)) { + if (mime_type == "video/x-vnd.on2.vp8") + return true; + + if (mime_type == "video/x-vnd.on2.vp9") + return base::android::BuildInfo::GetInstance()->sdk_int() < 21; + + return false; } - DVLOG(1) << __PRETTY_FUNCTION__ << "Default codec for " << mime_type << " : " - << codec_name; + // It would be nice if MediaCodecInfo externalized some notion of // HW-acceleration but it doesn't. Android Media guidance is that the // "OMX.google" prefix is always used for SW decoders, so that's what we // use. "OMX.SEC.*" codec is Samsung software implementation - report it - // as unaccelerated as well. Also temporary blacklist Exynos and MediaTek - // devices while HW decoder video freezes and distortions are - // investigated - http://crbug.com/446974. - if (codec_name.length() > 0) { - return (base::StartsWith(codec_name, "OMX.google.", - base::CompareCase::SENSITIVE) || - base::StartsWith(codec_name, "OMX.SEC.", - base::CompareCase::SENSITIVE) || - base::StartsWith(codec_name, "OMX.MTK.", - base::CompareCase::SENSITIVE) || - base::StartsWith(codec_name, "OMX.Exynos.", - base::CompareCase::SENSITIVE)); - } - return true; + // as unaccelerated as well. + return base::StartsWith(codec_name, "OMX.google.", + base::CompareCase::SENSITIVE) || + base::StartsWith(codec_name, "OMX.SEC.", base::CompareCase::SENSITIVE); } // static @@ -245,4 +176,35 @@ bool MediaCodecUtil::RegisterMediaCodecUtil(JNIEnv* env) { return RegisterNativesImpl(env); } +// static +bool MediaCodecUtil::IsVp8DecoderAvailable() { + return IsMediaCodecAvailable() && + IsDecoderSupportedByDevice(CodecTypeToAndroidMimeType("vp8")); +} + +// static +bool MediaCodecUtil::IsVp8EncoderAvailable() { + // Currently the vp8 encoder and decoder blacklists cover the same devices, + // but we have a second method for clarity in future issues. + return IsVp8DecoderAvailable(); +} + +// static +bool MediaCodecUtil::IsVp9DecoderAvailable() { + return IsMediaCodecAvailable() && + IsDecoderSupportedByDevice(CodecTypeToAndroidMimeType("vp9")); +} + +// static +bool MediaCodecUtil::IsSurfaceViewOutputSupported() { + // Disable SurfaceView output for the Samsung Galaxy S3; it does not work + // well enough for even 360p24 H264 playback. http://crbug.com/602870. + // + // Notably this is not codec agnostic at present, so any devices added to + // the blacklist will avoid trying to play any codecs on SurfaceView. If + // needed in the future this can be expanded to be codec specific. + return !base::StartsWith(base::android::BuildInfo::GetInstance()->model(), + "GT-I9300", base::CompareCase::INSENSITIVE_ASCII); +} + } // namespace media diff --git a/chromium/media/base/android/media_codec_util.h b/chromium/media/base/android/media_codec_util.h index 3c2d020eea4..211d9f038ba 100644 --- a/chromium/media/base/android/media_codec_util.h +++ b/chromium/media/base/android/media_codec_util.h @@ -65,6 +65,16 @@ class MEDIA_EXPORT MediaCodecUtil { static bool IsHLSPath(const GURL& url); static bool RegisterMediaCodecUtil(JNIEnv* env); + + // Indicates if the vp8 decoder or encoder is available on this device. + static bool IsVp8DecoderAvailable(); + static bool IsVp8EncoderAvailable(); + + // Indicates if the vp9 decoder is available on this device. + static bool IsVp9DecoderAvailable(); + + // Indicates if SurfaceView and MediaCodec work well together on this device. + static bool IsSurfaceViewOutputSupported(); }; } // namespace media diff --git a/chromium/media/base/android/media_drm_bridge.cc b/chromium/media/base/android/media_drm_bridge.cc index 0633c31b986..96821aec43c 100644 --- a/chromium/media/base/android/media_drm_bridge.cc +++ b/chromium/media/base/android/media_drm_bridge.cc @@ -26,6 +26,7 @@ #include "base/thread_task_runner_handle.h" #include "jni/MediaDrmBridge_jni.h" #include "media/base/android/media_client_android.h" +#include "media/base/android/media_codec_util.h" #include "media/base/android/media_drm_bridge_delegate.h" #include "media/base/android/provision_fetcher.h" #include "media/base/cdm_key_information.h" @@ -174,8 +175,12 @@ base::LazyInstance<KeySystemManager>::Leaky g_key_system_manager = // resolved. bool IsKeySystemSupportedWithTypeImpl(const std::string& key_system, const std::string& container_mime_type) { - if (!MediaDrmBridge::IsAvailable()) + DCHECK(MediaDrmBridge::IsAvailable()); + + if (key_system.empty()) { + NOTREACHED(); return false; + } UUID scheme_uuid = g_key_system_manager.Get().GetUUID(key_system); if (scheme_uuid.empty()) @@ -215,10 +220,7 @@ std::string GetSecurityLevelString( return ""; } -} // namespace - -// static -bool MediaDrmBridge::IsAvailable() { +bool AreMediaDrmApisAvailable() { if (base::android::BuildInfo::GetInstance()->sdk_int() < 19) return false; @@ -233,6 +235,15 @@ bool MediaDrmBridge::IsAvailable() { return true; } +} // namespace + +// MediaDrm is not generally usable without MediaCodec. Thus, both the MediaDrm +// APIs and MediaCodec APIs must be enabled and not blacklisted. +// static +bool MediaDrmBridge::IsAvailable() { + return AreMediaDrmApisAvailable() && MediaCodecUtil::IsMediaCodecAvailable(); +} + // static bool MediaDrmBridge::RegisterMediaDrmBridge(JNIEnv* env) { return RegisterNativesImpl(env); @@ -240,7 +251,9 @@ bool MediaDrmBridge::RegisterMediaDrmBridge(JNIEnv* env) { // static bool MediaDrmBridge::IsKeySystemSupported(const std::string& key_system) { - DCHECK(!key_system.empty()); + if (!MediaDrmBridge::IsAvailable()) + return false; + return IsKeySystemSupportedWithTypeImpl(key_system, ""); } @@ -248,17 +261,24 @@ bool MediaDrmBridge::IsKeySystemSupported(const std::string& key_system) { bool MediaDrmBridge::IsKeySystemSupportedWithType( const std::string& key_system, const std::string& container_mime_type) { - DCHECK(!key_system.empty() && !container_mime_type.empty()); + DCHECK(!container_mime_type.empty()) << "Call IsKeySystemSupported instead"; + + if (!MediaDrmBridge::IsAvailable()) + return false; + return IsKeySystemSupportedWithTypeImpl(key_system, container_mime_type); } // static std::vector<std::string> MediaDrmBridge::GetPlatformKeySystemNames() { + if (!MediaDrmBridge::IsAvailable()) + return std::vector<std::string>(); + return g_key_system_manager.Get().GetPlatformKeySystemNames(); } // static -scoped_refptr<MediaDrmBridge> MediaDrmBridge::Create( +scoped_refptr<MediaDrmBridge> MediaDrmBridge::CreateInternal( const std::string& key_system, SecurityLevel security_level, const CreateFetcherCB& create_fetcher_cb, @@ -267,10 +287,8 @@ scoped_refptr<MediaDrmBridge> MediaDrmBridge::Create( const LegacySessionErrorCB& legacy_session_error_cb, const SessionKeysChangeCB& session_keys_change_cb, const SessionExpirationUpdateCB& session_expiration_update_cb) { - DVLOG(1) << __FUNCTION__; - - if (!IsAvailable()) - return nullptr; + // All paths requires the MediaDrmApis. + DCHECK(AreMediaDrmApisAvailable()); UUID scheme_uuid = g_key_system_manager.Get().GetUUID(key_system); if (scheme_uuid.empty()) @@ -288,12 +306,37 @@ scoped_refptr<MediaDrmBridge> MediaDrmBridge::Create( } // static +scoped_refptr<MediaDrmBridge> MediaDrmBridge::Create( + const std::string& key_system, + SecurityLevel security_level, + const CreateFetcherCB& create_fetcher_cb, + const SessionMessageCB& session_message_cb, + const SessionClosedCB& session_closed_cb, + const LegacySessionErrorCB& legacy_session_error_cb, + const SessionKeysChangeCB& session_keys_change_cb, + const SessionExpirationUpdateCB& session_expiration_update_cb) { + DVLOG(1) << __FUNCTION__; + + if (!IsAvailable()) + return nullptr; + + return CreateInternal(key_system, security_level, create_fetcher_cb, + session_message_cb, session_closed_cb, + legacy_session_error_cb, session_keys_change_cb, + session_expiration_update_cb); +} + +// static scoped_refptr<MediaDrmBridge> MediaDrmBridge::CreateWithoutSessionSupport( const std::string& key_system, SecurityLevel security_level, const CreateFetcherCB& create_fetcher_cb) { DVLOG(1) << __FUNCTION__; + // Sessions won't be used so decoding capability is not required. + if (!AreMediaDrmApisAvailable()) + return nullptr; + return MediaDrmBridge::Create(key_system, security_level, create_fetcher_cb, SessionMessageCB(), SessionClosedCB(), LegacySessionErrorCB(), SessionKeysChangeCB(), @@ -303,6 +346,7 @@ scoped_refptr<MediaDrmBridge> MediaDrmBridge::CreateWithoutSessionSupport( void MediaDrmBridge::SetServerCertificate( const std::vector<uint8_t>& certificate, scoped_ptr<media::SimpleCdmPromise> promise) { + DCHECK(task_runner_->BelongsToCurrentThread()); DVLOG(2) << __FUNCTION__ << "(" << certificate.size() << " bytes)"; DCHECK(!certificate.empty()); @@ -323,6 +367,7 @@ void MediaDrmBridge::CreateSessionAndGenerateRequest( media::EmeInitDataType init_data_type, const std::vector<uint8_t>& init_data, scoped_ptr<media::NewSessionCdmPromise> promise) { + DCHECK(task_runner_->BelongsToCurrentThread()); DVLOG(2) << __FUNCTION__; if (session_type != media::MediaKeys::TEMPORARY_SESSION) { @@ -347,6 +392,7 @@ void MediaDrmBridge::CreateSessionAndGenerateRequest( &init_data_from_delegate, &optional_parameters_from_delegate)) { promise->reject(INVALID_ACCESS_ERROR, 0, "Invalid init data."); + return; } if (!init_data_from_delegate.empty()) { j_init_data = @@ -377,6 +423,7 @@ void MediaDrmBridge::LoadSession( SessionType session_type, const std::string& session_id, scoped_ptr<media::NewSessionCdmPromise> promise) { + DCHECK(task_runner_->BelongsToCurrentThread()); DVLOG(2) << __FUNCTION__; NOTIMPLEMENTED() << "EME persistent sessions not yet supported on Android."; @@ -387,6 +434,7 @@ void MediaDrmBridge::UpdateSession( const std::string& session_id, const std::vector<uint8_t>& response, scoped_ptr<media::SimpleCdmPromise> promise) { + DCHECK(task_runner_->BelongsToCurrentThread()); DVLOG(2) << __FUNCTION__; JNIEnv* env = AttachCurrentThread(); @@ -402,6 +450,7 @@ void MediaDrmBridge::UpdateSession( void MediaDrmBridge::CloseSession(const std::string& session_id, scoped_ptr<media::SimpleCdmPromise> promise) { + DCHECK(task_runner_->BelongsToCurrentThread()); DVLOG(2) << __FUNCTION__; JNIEnv* env = AttachCurrentThread(); @@ -416,12 +465,19 @@ void MediaDrmBridge::CloseSession(const std::string& session_id, void MediaDrmBridge::RemoveSession( const std::string& session_id, scoped_ptr<media::SimpleCdmPromise> promise) { + DCHECK(task_runner_->BelongsToCurrentThread()); DVLOG(2) << __FUNCTION__; NOTIMPLEMENTED() << "EME persistent sessions not yet supported on Android."; promise->reject(NOT_SUPPORTED_ERROR, 0, "RemoveSession() is not supported."); } +CdmContext* MediaDrmBridge::GetCdmContext() { + DVLOG(2) << __FUNCTION__; + + return &media_drm_bridge_cdm_context_; +} + void MediaDrmBridge::DeleteOnCorrectThread() const { DVLOG(1) << __FUNCTION__; @@ -614,9 +670,6 @@ void MediaDrmBridge::OnSessionKeysChange( bool has_additional_usable_key) { DVLOG(2) << __FUNCTION__; - if (has_additional_usable_key) - player_tracker_.NotifyNewKey(); - CdmKeysInfo cdm_keys_info; size_t size = env->GetArrayLength(j_keys_info); @@ -647,6 +700,12 @@ void MediaDrmBridge::OnSessionKeysChange( FROM_HERE, base::Bind(session_keys_change_cb_, AsString(env, j_session_id), has_additional_usable_key, base::Passed(&cdm_keys_info))); + + if (has_additional_usable_key) { + task_runner_->PostTask(FROM_HERE, + base::Bind(&MediaDrmBridge::OnHasAdditionalUsableKey, + weak_factory_.GetWeakPtr())); + } } // According to MeidaDrm documentation [1], zero |expiry_time_ms| means the keys @@ -718,6 +777,7 @@ MediaDrmBridge::MediaDrmBridge( session_keys_change_cb_(session_keys_change_cb), session_expiration_update_cb_(session_expiration_update_cb), task_runner_(base::ThreadTaskRunnerHandle::Get()), + media_drm_bridge_cdm_context_(this), weak_factory_(this) { DVLOG(1) << __FUNCTION__; @@ -836,4 +896,11 @@ void MediaDrmBridge::ProcessProvisionResponse(bool success, j_response.obj()); } +void MediaDrmBridge::OnHasAdditionalUsableKey() { + DCHECK(task_runner_->BelongsToCurrentThread()); + DVLOG(1) << __FUNCTION__; + + player_tracker_.NotifyNewKey(); +} + } // namespace media diff --git a/chromium/media/base/android/media_drm_bridge.h b/chromium/media/base/android/media_drm_bridge.h index d46abbe2856..8c31f89694a 100644 --- a/chromium/media/base/android/media_drm_bridge.h +++ b/chromium/media/base/android/media_drm_bridge.h @@ -15,6 +15,7 @@ #include "base/macros.h" #include "base/memory/scoped_ptr.h" #include "base/memory/weak_ptr.h" +#include "media/base/android/media_drm_bridge_cdm_context.h" #include "media/base/android/provision_fetcher.h" #include "media/base/cdm_promise_adapter.h" #include "media/base/media_export.h" @@ -55,9 +56,9 @@ class MEDIA_EXPORT MediaDrmBridge : public MediaKeys, public PlayerTracker { using MediaCryptoReadyCB = base::Callback<void(JavaObjectPtr media_crypto, bool needs_protected_surface)>; - // Checks whether MediaDRM is available. - // All other static methods check IsAvailable() internally. There's no need - // to check IsAvailable() explicitly before calling them. + // Checks whether MediaDRM is available and usable, including for decoding. + // All other static methods check IsAvailable() or equivalent internally. + // There is no need to check IsAvailable() explicitly before calling them. static bool IsAvailable(); static bool RegisterMediaDrmBridge(JNIEnv* env); @@ -114,6 +115,7 @@ class MEDIA_EXPORT MediaDrmBridge : public MediaKeys, public PlayerTracker { scoped_ptr<media::SimpleCdmPromise> promise) override; void RemoveSession(const std::string& session_id, scoped_ptr<media::SimpleCdmPromise> promise) override; + CdmContext* GetCdmContext() override; void DeleteOnCorrectThread() const override; // PlayerTracker implementation. Can be called on any thread. @@ -190,8 +192,8 @@ class MEDIA_EXPORT MediaDrmBridge : public MediaKeys, public PlayerTracker { // Session event callbacks. - // TODO(xhwang): Remove |j_legacy_destination_url| when prefixed EME support - // is removed. + // TODO(xhwang): Remove |j_legacy_destination_url| now that prefixed EME + // support is removed. http://crbug.com/249976 void OnSessionMessage( JNIEnv* env, const base::android::JavaParamRef<jobject>& j_media_drm, @@ -224,6 +226,7 @@ class MEDIA_EXPORT MediaDrmBridge : public MediaKeys, public PlayerTracker { // unrelated to one of the MediaKeys calls that accept a |promise|. // Note: // - This method is only for supporting prefixed EME API. + // TODO(ddorwin): Remove it now. https://crbug.com/249976 // - This method will be ignored by unprefixed EME. All errors reported // in this method should probably also be reported by one of other methods. void OnLegacySessionError( @@ -242,6 +245,16 @@ class MEDIA_EXPORT MediaDrmBridge : public MediaKeys, public PlayerTracker { // For DeleteSoon() in DeleteOnCorrectThread(). friend class base::DeleteHelper<MediaDrmBridge>; + static scoped_refptr<MediaDrmBridge> CreateInternal( + const std::string& key_system, + SecurityLevel security_level, + const CreateFetcherCB& create_fetcher_cb, + const SessionMessageCB& session_message_cb, + const SessionClosedCB& session_closed_cb, + const LegacySessionErrorCB& legacy_session_error_cb, + const SessionKeysChangeCB& session_keys_change_cb, + const SessionExpirationUpdateCB& session_expiration_update_cb); + // Constructs a MediaDrmBridge for |scheme_uuid| and |security_level|. The // default security level will be used if |security_level| is // SECURITY_LEVEL_DEFAULT. Sessions should not be created if session callbacks @@ -275,6 +288,9 @@ class MEDIA_EXPORT MediaDrmBridge : public MediaKeys, public PlayerTracker { // Process the data received by provisioning server. void ProcessProvisionResponse(bool success, const std::string& response); + // Called on the |task_runner_| when there is additional usable key. + void OnHasAdditionalUsableKey(); + // UUID of the key system. std::vector<uint8_t> scheme_uuid_; @@ -315,6 +331,8 @@ class MEDIA_EXPORT MediaDrmBridge : public MediaKeys, public PlayerTracker { // Default task runner. scoped_refptr<base::SingleThreadTaskRunner> task_runner_; + MediaDrmBridgeCdmContext media_drm_bridge_cdm_context_; + // NOTE: Weak pointers must be invalidated before all other member variables. base::WeakPtrFactory<MediaDrmBridge> weak_factory_; diff --git a/chromium/media/base/android/media_drm_bridge_cdm_context.cc b/chromium/media/base/android/media_drm_bridge_cdm_context.cc new file mode 100644 index 00000000000..f9c9fa4e4b6 --- /dev/null +++ b/chromium/media/base/android/media_drm_bridge_cdm_context.cc @@ -0,0 +1,42 @@ +// Copyright 2016 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/base/android/media_drm_bridge_cdm_context.h" + +#include "media/base/android/media_drm_bridge.h" + +namespace media { + +MediaDrmBridgeCdmContext::MediaDrmBridgeCdmContext( + MediaDrmBridge* media_drm_bridge) + : media_drm_bridge_(media_drm_bridge) { + DCHECK(media_drm_bridge_); +} + +MediaDrmBridgeCdmContext::~MediaDrmBridgeCdmContext() {} + +Decryptor* MediaDrmBridgeCdmContext::GetDecryptor() { + return nullptr; +} + +int MediaDrmBridgeCdmContext::GetCdmId() const { + return kInvalidCdmId; +} + +int MediaDrmBridgeCdmContext::RegisterPlayer( + const base::Closure& new_key_cb, + const base::Closure& cdm_unset_cb) { + return media_drm_bridge_->RegisterPlayer(new_key_cb, cdm_unset_cb); +} + +void MediaDrmBridgeCdmContext::UnregisterPlayer(int registration_id) { + media_drm_bridge_->UnregisterPlayer(registration_id); +} + +void MediaDrmBridgeCdmContext::SetMediaCryptoReadyCB( + const MediaCryptoReadyCB& media_crypto_ready_cb) { + media_drm_bridge_->SetMediaCryptoReadyCB(media_crypto_ready_cb); +} + +} // namespace media diff --git a/chromium/media/base/android/media_drm_bridge_cdm_context.h b/chromium/media/base/android/media_drm_bridge_cdm_context.h new file mode 100644 index 00000000000..de76c5ec3b6 --- /dev/null +++ b/chromium/media/base/android/media_drm_bridge_cdm_context.h @@ -0,0 +1,74 @@ +// Copyright 2016 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_BASE_ANDROID_MEDIA_DRM_BRIDGE_CDM_CONTEXT_H_ +#define MEDIA_BASE_ANDROID_MEDIA_DRM_BRIDGE_CDM_CONTEXT_H_ + +#include <jni.h> + +#include "base/android/scoped_java_ref.h" +#include "base/callback.h" +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "media/base/cdm_context.h" +#include "media/base/media_export.h" +#include "media/base/player_tracker.h" + +namespace media { + +class MediaDrmBridge; + +// The CdmContext implementation for MediaDrmBridge. MediaDrmBridge supports +// neither Decryptor nor CDM ID, but uses MediaCrypto to connect to MediaCodec. +// MediaCodec-based decoders should cast the given CdmContext to this class to +// access APIs defined in this class. +// +// Methods can be called on any thread. The registered callbacks will be fired +// on the thread |media_drm_bridge_| is running on. The caller should make sure +// that the callbacks are posted to the correct thread. +// +// TODO(xhwang): Remove PlayerTracker interface. +class MEDIA_EXPORT MediaDrmBridgeCdmContext : public CdmContext, + public PlayerTracker { + public: + using JavaObjectPtr = scoped_ptr<base::android::ScopedJavaGlobalRef<jobject>>; + + // Notification called when MediaCrypto object is ready. + // Parameters: + // |media_crypto| - reference to MediaCrypto object + // |needs_protected_surface| - true if protected surface is required + using MediaCryptoReadyCB = base::Callback<void(JavaObjectPtr media_crypto, + bool needs_protected_surface)>; + + // The |media_drm_bridge| owns |this| and is guaranteed to outlive |this|. + explicit MediaDrmBridgeCdmContext(MediaDrmBridge* media_drm_bridge); + + ~MediaDrmBridgeCdmContext() final; + + // CdmContext implementation. + Decryptor* GetDecryptor() final; + int GetCdmId() const final; + + // PlayerTracker implementation. + // Methods can be called on any thread. The registered callbacks will be fired + // on |task_runner_|. The caller should make sure that the callbacks are + // posted to the correct thread. + // + // Note: RegisterPlayer() must be called before SetMediaCryptoReadyCB() to + // avoid missing any new key notifications. + int RegisterPlayer(const base::Closure& new_key_cb, + const base::Closure& cdm_unset_cb) final; + void UnregisterPlayer(int registration_id) final; + + void SetMediaCryptoReadyCB(const MediaCryptoReadyCB& media_crypto_ready_cb); + + private: + MediaDrmBridge* const media_drm_bridge_; + + DISALLOW_COPY_AND_ASSIGN(MediaDrmBridgeCdmContext); +}; + +} // namespace media + +#endif // MEDIA_BASE_ANDROID_MEDIA_DRM_BRIDGE_CDM_CONTEXT_H_ diff --git a/chromium/media/base/android/media_player_android.cc b/chromium/media/base/android/media_player_android.cc index ec0fe6af495..b8f40800d6c 100644 --- a/chromium/media/base/android/media_player_android.cc +++ b/chromium/media/base/android/media_player_android.cc @@ -4,6 +4,8 @@ #include "media/base/android/media_player_android.h" +#include <algorithm> + #include "base/android/context_utils.h" #include "base/logging.h" #include "base/single_thread_task_runner.h" @@ -11,17 +13,29 @@ #include "media/base/android/media_drm_bridge.h" #include "media/base/android/media_player_manager.h" +namespace { + +const double kDefaultVolume = 1.0; + +} // namespace + namespace media { +const double MediaPlayerAndroid::kDefaultVolumeMultiplier = 1.0; + MediaPlayerAndroid::MediaPlayerAndroid( int player_id, MediaPlayerManager* manager, const OnDecoderResourcesReleasedCB& on_decoder_resources_released_cb, - const GURL& frame_url) + const GURL& frame_url, + int media_session_id) : on_decoder_resources_released_cb_(on_decoder_resources_released_cb), player_id_(player_id), + volume_(kDefaultVolume), + volume_multiplier_(kDefaultVolumeMultiplier), manager_(manager), frame_url_(frame_url), + media_session_id_(media_session_id), weak_factory_(this) { listener_.reset(new MediaPlayerListener(base::ThreadTaskRunnerHandle::Get(), weak_factory_.GetWeakPtr())); @@ -29,6 +43,24 @@ MediaPlayerAndroid::MediaPlayerAndroid( MediaPlayerAndroid::~MediaPlayerAndroid() {} +void MediaPlayerAndroid::SetVolume(double volume) { + volume_ = std::max(0.0, std::min(volume, 1.0)); + UpdateEffectiveVolume(); +} + +void MediaPlayerAndroid::SetVolumeMultiplier(double volume_multiplier) { + volume_multiplier_ = std::max(0.0, std::min(volume_multiplier, 1.0)); + UpdateEffectiveVolume(); +} + +double MediaPlayerAndroid::GetEffectiveVolume() const { + return volume_ * volume_multiplier_; +} + +void MediaPlayerAndroid::UpdateEffectiveVolume() { + UpdateEffectiveVolumeInternal(GetEffectiveVolume()); +} + // For most subclasses we can delete on the caller thread. void MediaPlayerAndroid::DeleteOnCorrectThread() { delete this; diff --git a/chromium/media/base/android/media_player_android.h b/chromium/media/base/android/media_player_android.h index 3f07a9319a8..68e3978cf67 100644 --- a/chromium/media/base/android/media_player_android.h +++ b/chromium/media/base/android/media_player_android.h @@ -10,6 +10,7 @@ #include "base/callback.h" #include "base/macros.h" +#include "base/memory/scoped_ptr.h" #include "base/memory/weak_ptr.h" #include "base/time/time.h" #include "media/base/android/media_player_listener.h" @@ -23,6 +24,19 @@ namespace media { class MediaKeys; class MediaPlayerManager; +enum { + // Id used for players not participating in any media sessions + // because of undefined behavior in the specification. When all + // media session interactions have been worked out, this id should + // no longer be used. + kInvalidMediaSessionId = -1, + + // The media session for media elements that don't have an explicit + // user created media session set. Must be in-sync with + // WebMediaSession::DefaultID in blink. + kDefaultMediaSessionId = 0 +}; + // This class serves as the base class for different media player // implementations on Android. Subclasses need to provide their own // MediaPlayerAndroid::Create() implementation. @@ -36,8 +50,11 @@ class MEDIA_EXPORT MediaPlayerAndroid { MEDIA_ERROR_DECODE, MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK, MEDIA_ERROR_INVALID_CODE, + MEDIA_ERROR_SERVER_DIED, }; + static const double kDefaultVolumeMultiplier; + // Callback when the player releases decoding resources. typedef base::Callback<void(int player_id)> OnDecoderResourcesReleasedCB; @@ -62,8 +79,13 @@ class MEDIA_EXPORT MediaPlayerAndroid { // Release the player resources. virtual void Release() = 0; - // Set the player volume. - virtual void SetVolume(double volume) = 0; + // Set the player volume, and take effect immediately. + // The volume should be between 0.0 and 1.0. + void SetVolume(double volume); + + // Set the player volume multiplier, and take effect immediately. + // The volume should be between 0.0 and 1.0. + void SetVolumeMultiplier(double volume_multiplier); // Get the media information from the player. virtual bool HasVideo() const = 0; @@ -100,6 +122,8 @@ class MEDIA_EXPORT MediaPlayerAndroid { GURL frame_url() { return frame_url_; } + int media_session_id() { return media_session_id_; } + // Attach/Detaches |listener_| for listening to all the media events. If // |j_media_player| is NULL, |listener_| only listens to the system media // events. Otherwise, it also listens to the events from |j_media_player|. @@ -111,7 +135,8 @@ class MEDIA_EXPORT MediaPlayerAndroid { int player_id, MediaPlayerManager* manager, const OnDecoderResourcesReleasedCB& on_decoder_resources_released_cb, - const GURL& frame_url); + const GURL& frame_url, + int media_session_id); // TODO(qinmin): Simplify the MediaPlayerListener class to only listen to // media interrupt events. And have a separate child class to listen to all @@ -125,6 +150,9 @@ class MEDIA_EXPORT MediaPlayerAndroid { virtual void OnSeekComplete(); virtual void OnMediaPrepared(); + double GetEffectiveVolume() const; + void UpdateEffectiveVolume(); + // When destroying a subclassed object on a non-UI thread // it is still required to destroy the |listener_| related stuff // on the UI thread. @@ -137,11 +165,22 @@ class MEDIA_EXPORT MediaPlayerAndroid { OnDecoderResourcesReleasedCB on_decoder_resources_released_cb_; private: + // Set the effective player volume, implemented by children classes. + virtual void UpdateEffectiveVolumeInternal(double effective_volume) = 0; + friend class MediaPlayerListener; // Player ID assigned to this player. int player_id_; + // The player volume. Should be between 0.0 and 1.0. + double volume_; + + // The player volume multiplier. Should be between 0.0 and 1.0. This + // should be a cached version of the MediaSession volume multiplier, + // and should keep updated. + double volume_multiplier_; + // Resource manager for all the media players. MediaPlayerManager* manager_; @@ -151,6 +190,9 @@ class MEDIA_EXPORT MediaPlayerAndroid { // Listener object that listens to all the media player events. scoped_ptr<MediaPlayerListener> listener_; + // Media session ID assigned to this player. + int media_session_id_; + // Weak pointer passed to |listener_| for callbacks. // NOTE: Weak pointers must be invalidated before all other member variables. base::WeakPtrFactory<MediaPlayerAndroid> weak_factory_; diff --git a/chromium/media/base/android/media_player_bridge.cc b/chromium/media/base/android/media_player_bridge.cc index 2f1fc0a3cd0..50a0af3ee50 100644 --- a/chromium/media/base/android/media_player_bridge.cc +++ b/chromium/media/base/android/media_player_bridge.cc @@ -10,6 +10,7 @@ #include "base/android/jni_android.h" #include "base/android/jni_string.h" #include "base/logging.h" +#include "base/metrics/histogram_macros.h" #include "base/strings/string_util.h" #include "jni/MediaPlayerBridge_jni.h" #include "media/base/android/media_common_android.h" @@ -23,6 +24,16 @@ using base::android::ScopedJavaLocalRef; namespace media { +namespace { + +enum UMAExitStatus { + UMA_EXIT_SUCCESS = 0, + UMA_EXIT_ERROR, + UMA_EXIT_STATUS_MAX = UMA_EXIT_ERROR, +}; + +} // namespace + MediaPlayerBridge::MediaPlayerBridge( int player_id, const GURL& url, @@ -32,11 +43,13 @@ MediaPlayerBridge::MediaPlayerBridge( MediaPlayerManager* manager, const OnDecoderResourcesReleasedCB& on_decoder_resources_released_cb, const GURL& frame_url, - bool allow_credentials) + bool allow_credentials, + int media_session_id) : MediaPlayerAndroid(player_id, manager, on_decoder_resources_released_cb, - frame_url), + frame_url, + media_session_id), prepared_(false), pending_play_(false), should_seek_on_prepare_(false), @@ -49,10 +62,11 @@ MediaPlayerBridge::MediaPlayerBridge( can_pause_(true), can_seek_forward_(true), can_seek_backward_(true), - volume_(-1.0), allow_credentials_(allow_credentials), - weak_factory_(this) { -} + is_active_(false), + has_error_(false), + has_ever_started_(false), + weak_factory_(this) {} MediaPlayerBridge::~MediaPlayerBridge() { if (!j_media_player_bridge_.is_null()) { @@ -61,6 +75,12 @@ MediaPlayerBridge::~MediaPlayerBridge() { Java_MediaPlayerBridge_destroy(env, j_media_player_bridge_.obj()); } Release(); + + if (has_ever_started_) { + UMA_HISTOGRAM_ENUMERATION("Media.Android.MediaPlayerSuccess", + has_error_ ? UMA_EXIT_ERROR : UMA_EXIT_SUCCESS, + UMA_EXIT_STATUS_MAX + 1); + } } void MediaPlayerBridge::Initialize() { @@ -100,8 +120,7 @@ void MediaPlayerBridge::CreateJavaMediaPlayerBridge() { j_media_player_bridge_.Reset(Java_MediaPlayerBridge_create( env, reinterpret_cast<intptr_t>(this))); - if (volume_ >= 0) - SetVolume(volume_); + UpdateEffectiveVolume(); AttachListener(j_media_player_bridge_.obj()); } @@ -277,6 +296,17 @@ void MediaPlayerBridge::OnMediaMetadataExtracted( } void MediaPlayerBridge::Start() { + // A second Start() call after an error is considered another attempt for UMA + // and causes UMA reporting. + if (has_ever_started_ && has_error_) { + UMA_HISTOGRAM_ENUMERATION("Media.Android.MediaPlayerSuccess", + UMA_EXIT_ERROR, UMA_EXIT_STATUS_MAX + 1); + } + + has_ever_started_ = true; + has_error_ = false; + is_active_ = true; + if (j_media_player_bridge_.is_null()) { pending_play_ = true; Prepare(); @@ -297,6 +327,8 @@ void MediaPlayerBridge::Pause(bool is_media_related_action) { else pending_play_ = false; } + + is_active_ = false; } bool MediaPlayerBridge::IsPlaying() { @@ -367,6 +399,8 @@ base::TimeDelta MediaPlayerBridge::GetDuration() { } void MediaPlayerBridge::Release() { + is_active_ = false; + on_decoder_resources_released_cb_.Run(player_id()); if (j_media_player_bridge_.is_null()) return; @@ -386,17 +420,16 @@ void MediaPlayerBridge::Release() { DetachListener(); } -void MediaPlayerBridge::SetVolume(double volume) { +void MediaPlayerBridge::UpdateEffectiveVolumeInternal(double effective_volume) { if (j_media_player_bridge_.is_null()) { - volume_ = volume; return; } JNIEnv* env = base::android::AttachCurrentThread(); CHECK(env); - Java_MediaPlayerBridge_setVolume( - env, j_media_player_bridge_.obj(), volume); + Java_MediaPlayerBridge_setVolume(env, j_media_player_bridge_.obj(), + effective_volume); } void MediaPlayerBridge::OnVideoSizeChanged(int width, int height) { @@ -405,6 +438,22 @@ void MediaPlayerBridge::OnVideoSizeChanged(int width, int height) { MediaPlayerAndroid::OnVideoSizeChanged(width, height); } +void MediaPlayerBridge::OnMediaError(int error_type) { + // Gather errors for UMA only in the active state. + // The MEDIA_ERROR_INVALID_CODE is reported by MediaPlayerListener.java in + // the situations that are considered normal, and is ignored by upper level. + if (is_active_ && error_type != MEDIA_ERROR_INVALID_CODE) + has_error_ = true; + + // Do not propagate MEDIA_ERROR_SERVER_DIED. If it happens in the active state + // we want the playback to stall. It can be recovered by pressing the Play + // button again. + if (error_type == MEDIA_ERROR_SERVER_DIED) + error_type = MEDIA_ERROR_INVALID_CODE; + + MediaPlayerAndroid::OnMediaError(error_type); +} + void MediaPlayerBridge::OnPlaybackComplete() { time_update_timer_.Stop(); MediaPlayerAndroid::OnPlaybackComplete(); diff --git a/chromium/media/base/android/media_player_bridge.h b/chromium/media/base/android/media_player_bridge.h index b347e5408ea..21dc900ecca 100644 --- a/chromium/media/base/android/media_player_bridge.h +++ b/chromium/media/base/android/media_player_bridge.h @@ -52,7 +52,8 @@ class MEDIA_EXPORT MediaPlayerBridge : public MediaPlayerAndroid { MediaPlayerManager* manager, const OnDecoderResourcesReleasedCB& on_decoder_resources_released_cb, const GURL& frame_url, - bool allow_credentials); + bool allow_credentials, + int media_session_id); ~MediaPlayerBridge() override; // Initialize this object and extract the metadata from the media. @@ -64,7 +65,6 @@ class MEDIA_EXPORT MediaPlayerBridge : public MediaPlayerAndroid { void Pause(bool is_media_related_action) override; void SeekTo(base::TimeDelta timestamp) override; void Release() override; - void SetVolume(double volume) override; bool HasVideo() const override; bool HasAudio() const override; int GetVideoWidth() override; @@ -96,6 +96,7 @@ class MEDIA_EXPORT MediaPlayerBridge : public MediaPlayerAndroid { // MediaPlayerAndroid implementation. void OnVideoSizeChanged(int width, int height) override; + void OnMediaError(int error_type) override; void OnPlaybackComplete() override; void OnMediaInterrupted() override; void OnMediaPrepared() override; @@ -109,6 +110,9 @@ class MEDIA_EXPORT MediaPlayerBridge : public MediaPlayerAndroid { private: friend class MediaPlayerBridgeTest; + // MediaPlayerAndroid implementation + void UpdateEffectiveVolumeInternal(double effective_volume) override; + // Set the data source for the media player. void SetDataSource(const std::string& url); @@ -194,12 +198,22 @@ class MEDIA_EXPORT MediaPlayerBridge : public MediaPlayerAndroid { base::TimeDelta last_time_update_timestamp_; - // Volume of playback. - double volume_; - // Whether user credentials are allowed to be passed. bool allow_credentials_; + // Helper variables for UMA reporting. + + // Whether the preparation for playback or the playback is currently going on. + // This flag is set in Start() and cleared in Pause() and Release(). Used for + // UMA reporting only. + bool is_active_; + + // Whether there has been any errors in the active state. + bool has_error_; + + // The flag is set if Start() has been called at least once. + bool has_ever_started_; + // NOTE: Weak pointers must be invalidated before all other member variables. base::WeakPtrFactory<MediaPlayerBridge> weak_factory_; diff --git a/chromium/media/base/android/media_player_bridge_unittest.cc b/chromium/media/base/android/media_player_bridge_unittest.cc index c2b2447b6d6..d4db5ba6c5c 100644 --- a/chromium/media/base/android/media_player_bridge_unittest.cc +++ b/chromium/media/base/android/media_player_bridge_unittest.cc @@ -58,7 +58,8 @@ class MediaPlayerBridgeTest : public testing::Test { base::Bind(&MockMediaPlayerManager::OnMediaResourcesRequested, base::Unretained(&manager_)), GURL(), - false) {} + false, + kDefaultMediaSessionId) {} void SetCanSeekForward(bool can_seek_forward) { bridge_.can_seek_forward_ = can_seek_forward; diff --git a/chromium/media/base/android/media_source_player.cc b/chromium/media/base/android/media_source_player.cc index 84c0b43bec0..30636f64b19 100644 --- a/chromium/media/base/android/media_source_player.cc +++ b/chromium/media/base/android/media_source_player.cc @@ -32,11 +32,13 @@ MediaSourcePlayer::MediaSourcePlayer( MediaPlayerManager* manager, const OnDecoderResourcesReleasedCB& on_decoder_resources_released_cb, scoped_ptr<DemuxerAndroid> demuxer, - const GURL& frame_url) + const GURL& frame_url, + int media_session_id) : MediaPlayerAndroid(player_id, manager, on_decoder_resources_released_cb, - frame_url), + frame_url, + media_session_id), demuxer_(std::move(demuxer)), pending_event_(NO_EVENT_PENDING), playing_(false), @@ -74,6 +76,10 @@ MediaSourcePlayer::~MediaSourcePlayer() { Release(); DCHECK_EQ(!cdm_, !cdm_registration_id_); if (cdm_) { + // Cancel previously registered callback (if any). + static_cast<MediaDrmBridge*>(cdm_.get()) + ->SetMediaCryptoReadyCB(MediaDrmBridge::MediaCryptoReadyCB()); + static_cast<MediaDrmBridge*>(cdm_.get()) ->UnregisterPlayer(cdm_registration_id_); cdm_registration_id_ = 0; @@ -207,8 +213,8 @@ void MediaSourcePlayer::Release() { on_decoder_resources_released_cb_.Run(player_id()); } -void MediaSourcePlayer::SetVolume(double volume) { - audio_decoder_job_->SetVolume(volume); +void MediaSourcePlayer::UpdateEffectiveVolumeInternal(double effective_volume) { + audio_decoder_job_->SetVolume(effective_volume); } bool MediaSourcePlayer::CanPause() { diff --git a/chromium/media/base/android/media_source_player.h b/chromium/media/base/android/media_source_player.h index f2ea9ba0aba..d6b26d72499 100644 --- a/chromium/media/base/android/media_source_player.h +++ b/chromium/media/base/android/media_source_player.h @@ -45,7 +45,8 @@ class MEDIA_EXPORT MediaSourcePlayer : public MediaPlayerAndroid, MediaPlayerManager* manager, const OnDecoderResourcesReleasedCB& on_decoder_resources_released_cb, scoped_ptr<DemuxerAndroid> demuxer, - const GURL& frame_url); + const GURL& frame_url, + int media_session_id); ~MediaSourcePlayer() override; // MediaPlayerAndroid implementation. @@ -54,7 +55,6 @@ class MEDIA_EXPORT MediaSourcePlayer : public MediaPlayerAndroid, void Pause(bool is_media_related_action) override; void SeekTo(base::TimeDelta timestamp) override; void Release() override; - void SetVolume(double volume) override; bool HasVideo() const override; bool HasAudio() const override; int GetVideoWidth() override; @@ -77,6 +77,9 @@ class MEDIA_EXPORT MediaSourcePlayer : public MediaPlayerAndroid, private: friend class MediaSourcePlayerTest; + // MediaPlayerAndroid implementation + void UpdateEffectiveVolumeInternal(double effective_volume) override; + // Update the current timestamp. void UpdateTimestamps(base::TimeDelta current_presentation_timestamp, base::TimeDelta max_presentation_timestamp); diff --git a/chromium/media/base/android/media_source_player_unittest.cc b/chromium/media/base/android/media_source_player_unittest.cc index 0ce01abf36b..36fc6791543 100644 --- a/chromium/media/base/android/media_source_player_unittest.cc +++ b/chromium/media/base/android/media_source_player_unittest.cc @@ -162,11 +162,13 @@ class MediaSourcePlayerTest : public testing::Test { MediaSourcePlayerTest() : manager_(&message_loop_), demuxer_(new MockDemuxerAndroid(&message_loop_)), - player_(0, &manager_, + player_(0, + &manager_, base::Bind(&MockMediaPlayerManager::OnDecorderResourcesReleased, base::Unretained(&manager_)), scoped_ptr<DemuxerAndroid>(demuxer_), - GURL()), + GURL(), + kDefaultMediaSessionId), decoder_callback_hook_executed_(false), surface_texture_a_is_next_(true) {} diff --git a/chromium/media/base/android/ndk_media_codec_bridge.cc b/chromium/media/base/android/ndk_media_codec_bridge.cc index 6ab751bc08e..3a66db675c9 100644 --- a/chromium/media/base/android/ndk_media_codec_bridge.cc +++ b/chromium/media/base/android/ndk_media_codec_bridge.cc @@ -67,7 +67,7 @@ void NdkMediaCodecBridge::Stop() { AMediaCodec_stop(media_codec_.get()); } -void NdkMediaCodecBridge::GetOutputFormat(int* width, int* height) { +MediaCodecStatus NdkMediaCodecBridge::GetOutputSize(gfx::Size* size) { AMediaFormat* format = AMediaCodec_getOutputFormat(media_codec_.get()); int left, right, bottom, top; bool has_left = AMediaFormat_getInt32(format, kMediaFormatKeyCropLeft, &left); @@ -76,22 +76,26 @@ void NdkMediaCodecBridge::GetOutputFormat(int* width, int* height) { bool has_bottom = AMediaFormat_getInt32(format, kMediaFormatKeyCropBottom, &bottom); bool has_top = AMediaFormat_getInt32(format, kMediaFormatKeyCropTop, &top); + int width, height; if (has_left && has_right && has_bottom && has_top) { // Use crop size as it is more accurate. right and bottom are inclusive. - *width = right - left + 1; - *height = top - bottom + 1; + width = right - left + 1; + height = top - bottom + 1; } else { - AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_WIDTH, width); - AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_HEIGHT, height); + AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_WIDTH, &width); + AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_HEIGHT, &height); } + size->SetSize(width, height); + return MEDIA_CODEC_OK; } -int NdkMediaCodecBridge::GetOutputSamplingRate() { +MediaCodecStatus NdkMediaCodecBridge::GetOutputSamplingRate( + int* sampling_rate) { AMediaFormat* format = AMediaCodec_getOutputFormat(media_codec_.get()); - int sample_rate = 0; - AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_SAMPLE_RATE, &sample_rate); - DCHECK(sample_rate != 0); - return sample_rate; + *sampling_rate = 0; + AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_SAMPLE_RATE, sampling_rate); + DCHECK_NE(*sampling_rate, 0); + return MEDIA_CODEC_OK; } MediaCodecStatus NdkMediaCodecBridge::QueueInputBuffer( @@ -213,26 +217,24 @@ void NdkMediaCodecBridge::ReleaseOutputBuffer(int index, bool render) { AMediaCodec_releaseOutputBuffer(media_codec_.get(), index, render); } -void NdkMediaCodecBridge::GetInputBuffer(int input_buffer_index, - uint8_t** data, - size_t* capacity) { +MediaCodecStatus NdkMediaCodecBridge::GetInputBuffer(int input_buffer_index, + uint8_t** data, + size_t* capacity) { *data = AMediaCodec_getInputBuffer(media_codec_.get(), input_buffer_index, capacity); + return MEDIA_CODEC_OK; } -bool NdkMediaCodecBridge::CopyFromOutputBuffer(int index, - size_t offset, - void* dst, - int dst_size) { +MediaCodecStatus NdkMediaCodecBridge::CopyFromOutputBuffer(int index, + size_t offset, + void* dst, + size_t num) { size_t capacity; - uint8_t* src_data = + const uint8_t* src_data = AMediaCodec_getOutputBuffer(media_codec_.get(), index, &capacity); - - if (capacity < offset || capacity - offset < static_cast<size_t>(dst_size)) - return false; - - memcpy(dst, src_data + offset, dst_size); - return true; + CHECK_GE(capacity, offset + num); + memcpy(dst, src_data + offset, num); + return MEDIA_CODEC_OK; } } // namespace media diff --git a/chromium/media/base/android/ndk_media_codec_bridge.h b/chromium/media/base/android/ndk_media_codec_bridge.h index d675ffb058b..d7f2b108a1a 100644 --- a/chromium/media/base/android/ndk_media_codec_bridge.h +++ b/chromium/media/base/android/ndk_media_codec_bridge.h @@ -14,6 +14,7 @@ #include "base/time/time.h" #include "media/base/android/media_codec_bridge.h" #include "media/base/media_export.h" +#include "ui/gfx/geometry/size.h" namespace media { @@ -25,8 +26,8 @@ class MEDIA_EXPORT NdkMediaCodecBridge : public MediaCodecBridge { MediaCodecStatus Reset() override; bool Start() override; void Stop() override; - void GetOutputFormat(int* width, int* height) override; - int GetOutputSamplingRate() override; + MediaCodecStatus GetOutputSize(gfx::Size* size) override; + MediaCodecStatus GetOutputSamplingRate(int* sampling_rate) override; MediaCodecStatus QueueInputBuffer( int index, const uint8_t* data, @@ -53,13 +54,13 @@ class MEDIA_EXPORT NdkMediaCodecBridge : public MediaCodecBridge { bool* end_of_stream, bool* key_frame) override; void ReleaseOutputBuffer(int index, bool render) override; - void GetInputBuffer(int input_buffer_index, - uint8_t** data, - size_t* capacity) override; - bool CopyFromOutputBuffer(int index, - size_t offset, - void* dst, - int dst_size) override; + MediaCodecStatus GetInputBuffer(int input_buffer_index, + uint8_t** data, + size_t* capacity) override; + MediaCodecStatus CopyFromOutputBuffer(int index, + size_t offset, + void* dst, + size_t num) override; protected: NdkMediaCodecBridge(const std::string& mime, diff --git a/chromium/media/base/android/provision_fetcher.h b/chromium/media/base/android/provision_fetcher.h index 43db3e37629..c9673356e91 100644 --- a/chromium/media/base/android/provision_fetcher.h +++ b/chromium/media/base/android/provision_fetcher.h @@ -8,6 +8,7 @@ #include <string> #include "base/callback.h" +#include "base/memory/scoped_ptr.h" #include "media/base/media_export.h" namespace media { diff --git a/chromium/media/base/android/sdk_media_codec_bridge.cc b/chromium/media/base/android/sdk_media_codec_bridge.cc index 87b11760f06..226a90a6cbf 100644 --- a/chromium/media/base/android/sdk_media_codec_bridge.cc +++ b/chromium/media/base/android/sdk_media_codec_bridge.cc @@ -112,17 +112,29 @@ void SdkMediaCodecBridge::Stop() { Java_MediaCodecBridge_stop(env, j_media_codec_.obj()); } -void SdkMediaCodecBridge::GetOutputFormat(int* width, int* height) { +MediaCodecStatus SdkMediaCodecBridge::GetOutputSize(gfx::Size* size) { JNIEnv* env = AttachCurrentThread(); - - *width = Java_MediaCodecBridge_getOutputWidth(env, j_media_codec_.obj()); - *height = Java_MediaCodecBridge_getOutputHeight(env, j_media_codec_.obj()); + ScopedJavaLocalRef<jobject> result = + Java_MediaCodecBridge_getOutputFormat(env, j_media_codec_.obj()); + MediaCodecStatus status = static_cast<MediaCodecStatus>( + Java_GetOutputFormatResult_status(env, result.obj())); + if (status == MEDIA_CODEC_OK) { + size->SetSize(Java_GetOutputFormatResult_width(env, result.obj()), + Java_GetOutputFormatResult_height(env, result.obj())); + } + return status; } -int SdkMediaCodecBridge::GetOutputSamplingRate() { +MediaCodecStatus SdkMediaCodecBridge::GetOutputSamplingRate( + int* sampling_rate) { JNIEnv* env = AttachCurrentThread(); - - return Java_MediaCodecBridge_getOutputSamplingRate(env, j_media_codec_.obj()); + ScopedJavaLocalRef<jobject> result = + Java_MediaCodecBridge_getOutputFormat(env, j_media_codec_.obj()); + MediaCodecStatus status = static_cast<MediaCodecStatus>( + Java_GetOutputFormatResult_status(env, result.obj())); + if (status == MEDIA_CODEC_OK) + *sampling_rate = Java_GetOutputFormatResult_sampleRate(env, result.obj()); + return status; } MediaCodecStatus SdkMediaCodecBridge::QueueInputBuffer( @@ -274,52 +286,52 @@ void SdkMediaCodecBridge::ReleaseOutputBuffer(int index, bool render) { render); } -int SdkMediaCodecBridge::GetOutputBuffersCount() { - JNIEnv* env = AttachCurrentThread(); - return Java_MediaCodecBridge_getOutputBuffersCount(env, j_media_codec_.obj()); -} - -size_t SdkMediaCodecBridge::GetOutputBuffersCapacity() { - JNIEnv* env = AttachCurrentThread(); - return Java_MediaCodecBridge_getOutputBuffersCapacity(env, - j_media_codec_.obj()); -} - -void SdkMediaCodecBridge::GetInputBuffer(int input_buffer_index, - uint8_t** data, - size_t* capacity) { +MediaCodecStatus SdkMediaCodecBridge::GetInputBuffer(int input_buffer_index, + uint8_t** data, + size_t* capacity) { JNIEnv* env = AttachCurrentThread(); ScopedJavaLocalRef<jobject> j_buffer(Java_MediaCodecBridge_getInputBuffer( env, j_media_codec_.obj(), input_buffer_index)); + if (j_buffer.is_null()) + return MEDIA_CODEC_ERROR; + *data = static_cast<uint8_t*>(env->GetDirectBufferAddress(j_buffer.obj())); *capacity = base::checked_cast<size_t>(env->GetDirectBufferCapacity(j_buffer.obj())); + return MEDIA_CODEC_OK; } -bool SdkMediaCodecBridge::CopyFromOutputBuffer(int index, - size_t offset, - void* dst, - int dst_size) { +MediaCodecStatus SdkMediaCodecBridge::CopyFromOutputBuffer(int index, + size_t offset, + void* dst, + size_t num) { void* src_data = nullptr; - size_t src_capacity = GetOutputBufferAddress(index, offset, &src_data); - if (src_capacity < offset || - src_capacity - offset < static_cast<size_t>(dst_size)) { - return false; + size_t src_capacity = 0; + MediaCodecStatus status = + GetOutputBufferAddress(index, offset, &src_data, &src_capacity); + if (status == MEDIA_CODEC_OK) { + CHECK_GE(src_capacity, num); + memcpy(dst, src_data, num); } - memcpy(dst, static_cast<uint8_t*>(src_data) + offset, dst_size); - return true; + return status; } -int SdkMediaCodecBridge::GetOutputBufferAddress(int index, - size_t offset, - void** addr) { +MediaCodecStatus SdkMediaCodecBridge::GetOutputBufferAddress(int index, + size_t offset, + void** addr, + size_t* capacity) { JNIEnv* env = AttachCurrentThread(); ScopedJavaLocalRef<jobject> j_buffer( Java_MediaCodecBridge_getOutputBuffer(env, j_media_codec_.obj(), index)); + if (j_buffer.is_null()) + return MEDIA_CODEC_ERROR; + const size_t total_capacity = env->GetDirectBufferCapacity(j_buffer.obj()); + CHECK_GE(total_capacity, offset); *addr = reinterpret_cast<uint8_t*>(env->GetDirectBufferAddress(j_buffer.obj())) + offset; - return env->GetDirectBufferCapacity(j_buffer.obj()) - offset; + *capacity = total_capacity - offset; + return MEDIA_CODEC_OK; } // static @@ -333,7 +345,14 @@ AudioCodecBridge* AudioCodecBridge::Create(const AudioCodec& codec) { return nullptr; const std::string mime = AudioCodecToAndroidMimeType(codec); - return mime.empty() ? nullptr : new AudioCodecBridge(mime); + if (mime.empty()) + return nullptr; + + scoped_ptr<AudioCodecBridge> bridge(new AudioCodecBridge(mime)); + if (!bridge->media_codec()) + return nullptr; + + return bridge.release(); } // static @@ -347,6 +366,23 @@ AudioCodecBridge::AudioCodecBridge(const std::string& mime) // audio encoding yet. : SdkMediaCodecBridge(mime, false, MEDIA_CODEC_DECODER) {} +bool AudioCodecBridge::ConfigureAndStart(const AudioDecoderConfig& config, + bool play_audio, + jobject media_crypto) { + const int channel_count = + ChannelLayoutToChannelCount(config.channel_layout()); + const int64_t codec_delay_ns = base::Time::kNanosecondsPerSecond * + config.codec_delay() / + config.samples_per_second(); + const int64_t seek_preroll_ns = + 1000LL * config.seek_preroll().InMicroseconds(); + + return ConfigureAndStart(config.codec(), config.samples_per_second(), + channel_count, config.extra_data().data(), + config.extra_data().size(), codec_delay_ns, + seek_preroll_ns, play_audio, media_crypto); +} + bool AudioCodecBridge::ConfigureAndStart(const AudioCodec& codec, int sample_rate, int channel_count, @@ -356,15 +392,22 @@ bool AudioCodecBridge::ConfigureAndStart(const AudioCodec& codec, int64_t seek_preroll_ns, bool play_audio, jobject media_crypto) { - JNIEnv* env = AttachCurrentThread(); - - if (!media_codec()) - return false; + DVLOG(2) << __FUNCTION__ << ": " + << " codec:" << GetCodecName(codec) + << " samples_per_second:" << sample_rate + << " channel_count:" << channel_count + << " codec_delay_ns:" << codec_delay_ns + << " seek_preroll_ns:" << seek_preroll_ns + << " extra data size:" << extra_data_size + << " play audio:" << play_audio << " media_crypto:" << media_crypto; + DCHECK(media_codec()); std::string codec_string = AudioCodecToAndroidMimeType(codec); if (codec_string.empty()) return false; + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jstring> j_mime = ConvertUTF8ToJavaString(env, codec_string); ScopedJavaLocalRef<jobject> j_format(Java_MediaCodecBridge_createAudioFormat( @@ -515,23 +558,30 @@ bool AudioCodecBridge::ConfigureMediaFormat(jobject j_format, return true; } -int64_t AudioCodecBridge::PlayOutputBuffer(int index, - size_t size, - size_t offset, - bool postpone) { +MediaCodecStatus AudioCodecBridge::PlayOutputBuffer(int index, + size_t size, + size_t offset, + bool postpone, + int64_t* playback_pos) { DCHECK_LE(0, index); int numBytes = base::checked_cast<int>(size); void* buffer = nullptr; - int capacity = GetOutputBufferAddress(index, offset, &buffer); - numBytes = std::min(capacity, numBytes); + size_t capacity = 0; + MediaCodecStatus status = + GetOutputBufferAddress(index, offset, &buffer, &capacity); + if (status == MEDIA_CODEC_ERROR) + return status; + + numBytes = std::min(base::checked_cast<int>(capacity), numBytes); CHECK_GE(numBytes, 0); JNIEnv* env = AttachCurrentThread(); ScopedJavaLocalRef<jbyteArray> byte_array = base::android::ToJavaByteArray( env, static_cast<uint8_t*>(buffer), numBytes); - return Java_MediaCodecBridge_playOutputBuffer(env, media_codec(), - byte_array.obj(), postpone); + *playback_pos = Java_MediaCodecBridge_playOutputBuffer( + env, media_codec(), byte_array.obj(), postpone); + return status; } void AudioCodecBridge::SetVolume(double volume) { @@ -547,11 +597,13 @@ bool VideoCodecBridge::IsKnownUnaccelerated(const VideoCodec& codec, } // static -VideoCodecBridge* VideoCodecBridge::CreateDecoder(const VideoCodec& codec, - bool is_secure, - const gfx::Size& size, - jobject surface, - jobject media_crypto) { +VideoCodecBridge* VideoCodecBridge::CreateDecoder( + const VideoCodec& codec, + bool is_secure, + const gfx::Size& size, + jobject surface, + jobject media_crypto, + bool allow_adaptive_playback) { if (!MediaCodecUtil::IsMediaCodecAvailable()) return nullptr; @@ -570,9 +622,9 @@ VideoCodecBridge* VideoCodecBridge::CreateDecoder(const VideoCodec& codec, Java_MediaCodecBridge_createVideoDecoderFormat( env, j_mime.obj(), size.width(), size.height())); DCHECK(!j_format.is_null()); - if (!Java_MediaCodecBridge_configureVideo(env, bridge->media_codec(), - j_format.obj(), surface, - media_crypto, 0)) { + if (!Java_MediaCodecBridge_configureVideo( + env, bridge->media_codec(), j_format.obj(), surface, media_crypto, 0, + allow_adaptive_playback)) { return nullptr; } @@ -607,7 +659,7 @@ VideoCodecBridge* VideoCodecBridge::CreateEncoder(const VideoCodec& codec, DCHECK(!j_format.is_null()); if (!Java_MediaCodecBridge_configureVideo(env, bridge->media_codec(), j_format.obj(), nullptr, nullptr, - kConfigureFlagEncode)) { + kConfigureFlagEncode, true)) { return nullptr; } diff --git a/chromium/media/base/android/sdk_media_codec_bridge.h b/chromium/media/base/android/sdk_media_codec_bridge.h index 9079cbadd85..e23400df319 100644 --- a/chromium/media/base/android/sdk_media_codec_bridge.h +++ b/chromium/media/base/android/sdk_media_codec_bridge.h @@ -32,8 +32,8 @@ class MEDIA_EXPORT SdkMediaCodecBridge : public MediaCodecBridge { MediaCodecStatus Reset() override; bool Start() override; void Stop() override; - void GetOutputFormat(int* width, int* height) override; - int GetOutputSamplingRate() override; + MediaCodecStatus GetOutputSize(gfx::Size* size) override; + MediaCodecStatus GetOutputSamplingRate(int* sampling_rate) override; MediaCodecStatus QueueInputBuffer( int index, const uint8_t* data, @@ -60,15 +60,13 @@ class MEDIA_EXPORT SdkMediaCodecBridge : public MediaCodecBridge { bool* end_of_stream, bool* key_frame) override; void ReleaseOutputBuffer(int index, bool render) override; - int GetOutputBuffersCount() override; - size_t GetOutputBuffersCapacity() override; - void GetInputBuffer(int input_buffer_index, - uint8_t** data, - size_t* capacity) override; - bool CopyFromOutputBuffer(int index, - size_t offset, - void* dst, - int dst_size) override; + MediaCodecStatus GetInputBuffer(int input_buffer_index, + uint8_t** data, + size_t* capacity) override; + MediaCodecStatus CopyFromOutputBuffer(int index, + size_t offset, + void* dst, + size_t num) override; static bool RegisterSdkMediaCodecBridge(JNIEnv* env); @@ -78,9 +76,13 @@ class MEDIA_EXPORT SdkMediaCodecBridge : public MediaCodecBridge { MediaCodecDirection direction); // Called to get the buffer address given the output buffer index and offset. - // This function returns the size of the output and |addr| is the pointer to - // the address to read. - int GetOutputBufferAddress(int index, size_t offset, void** addr); + // The size of available data to read is written to |*capacity| and the + // address to read from is written to |*addr|. + // Returns MEDIA_CODEC_ERROR if a error occurs, or MEDIA_CODEC_OK otherwise. + MediaCodecStatus GetOutputBufferAddress(int index, + size_t offset, + void** addr, + size_t* capacity); jobject media_codec() { return j_media_codec_.obj(); } MediaCodecDirection direction_; @@ -104,7 +106,16 @@ class MEDIA_EXPORT AudioCodecBridge : public SdkMediaCodecBridge { // See MediaCodecUtil::IsKnownUnaccelerated(). static bool IsKnownUnaccelerated(const AudioCodec& codec); - // Start the audio codec bridge. + // Start the audio codec bridge. If |play_audio| is true this method creates + // Android AudioTrack object for the actual audio playback + // (http://developer.android.com/reference/android/media/AudioTrack.html). + bool ConfigureAndStart(const AudioDecoderConfig& config, + bool play_audio, + jobject media_crypto); + + // An overloaded variant used by AudioDecoderJob and AudioMediaCodecDecoder. + // TODO(timav): Modify the above mentioned classes to pass parameters as + // AudioDecoderConfig and remove this method. bool ConfigureAndStart(const AudioCodec& codec, int sample_rate, int channel_count, @@ -118,15 +129,17 @@ class MEDIA_EXPORT AudioCodecBridge : public SdkMediaCodecBridge { // Plays the output buffer right away or save for later playback if |postpone| // is set to true. This call must be called after DequeueOutputBuffer() and // before ReleaseOutputBuffer. The data is extracted from the output buffers - // using |index|, |size| and |offset|. Returns the playback head position - // expressed in frames. + // using |index|, |size| and |offset|. The playback head position in frames is + // output in |*playback_pos|. // When |postpone| is set to true, the next PlayOutputBuffer() should have // postpone == false, and it will play two buffers: the postponed one and // the one identified by |index|. - int64_t PlayOutputBuffer(int index, - size_t size, - size_t offset, - bool postpone = false); + // Returns MEDIA_CODEC_ERROR if an error occurs, or MEDIA_CODEC_OK otherwise. + MediaCodecStatus PlayOutputBuffer(int index, + size_t size, + size_t offset, + bool postpone, + int64_t* playback_pos); // Set the volume of the audio output. void SetVolume(double volume); @@ -155,10 +168,12 @@ class MEDIA_EXPORT VideoCodecBridge : public SdkMediaCodecBridge { // Create, start, and return a VideoCodecBridge decoder or NULL on failure. static VideoCodecBridge* CreateDecoder( const VideoCodec& codec, // e.g. media::kCodecVP8 - bool is_secure, - const gfx::Size& size, // Output frame size. - jobject surface, // Output surface, optional. - jobject media_crypto); // MediaCrypto object, optional. + bool is_secure, // Will be used with encrypted content. + const gfx::Size& size, // Output frame size. + jobject surface, // Output surface, optional. + jobject media_crypto, // MediaCrypto object, optional. + bool allow_adaptive_playback = + true); // Should adaptive playback be allowed if supported. // Create, start, and return a VideoCodecBridge encoder or NULL on failure. static VideoCodecBridge* CreateEncoder( diff --git a/chromium/media/base/android/sdk_media_codec_bridge_unittest.cc b/chromium/media/base/android/sdk_media_codec_bridge_unittest.cc index ef32acf684f..5ef6da1d5e9 100644 --- a/chromium/media/base/android/sdk_media_codec_bridge_unittest.cc +++ b/chromium/media/base/android/sdk_media_codec_bridge_unittest.cc @@ -90,6 +90,7 @@ unsigned char test_mp3[] = { 0x8a, 0xb3, 0x52, 0xd1, 0x3d, 0x79, 0x81, 0x4d, 0x31, 0x24, 0xf9, 0x38, 0x96, 0xbc, 0xf4, 0x8c, 0x25, 0xe9, 0xf2, 0x73, 0x94, 0x85, 0xc2, 0x61, 0x6a, 0x34, 0x68, 0x65, 0x78, 0x87, 0xa6, 0x4f}; +static const size_t kDecodedAudioLengthInBytes = 9216u; } // namespace @@ -105,6 +106,7 @@ namespace media { } while (0) static const int kPresentationTimeBase = 100; +static const int kMaxInputPts = kPresentationTimeBase + 2; static inline const base::TimeDelta InfiniteTimeOut() { return base::TimeDelta::FromMicroseconds(-1); @@ -181,6 +183,7 @@ TEST(SdkMediaCodecBridgeTest, DoNormal) { input_pts = kPresentationTimeBase; bool eos = false; + size_t total_size = 0; while (!eos) { size_t unused_offset = 0; size_t size = 0; @@ -205,11 +208,10 @@ TEST(SdkMediaCodecBridgeTest, DoNormal) { } ASSERT_GE(output_buf_index, 0); EXPECT_LE(1u, size); - if (!eos) - EXPECT_EQ(++input_pts, timestamp.InMicroseconds()); - ASSERT_LE(input_pts, kPresentationTimeBase + 2); + total_size += size; } - ASSERT_EQ(input_pts, kPresentationTimeBase + 2); + EXPECT_EQ(kDecodedAudioLengthInBytes, total_size); + ASSERT_LE(input_pts, kMaxInputPts); } TEST(SdkMediaCodecBridgeTest, InvalidVorbisHeader) { @@ -248,6 +250,9 @@ TEST(SdkMediaCodecBridgeTest, InvalidOpusHeader) { scoped_ptr<media::AudioCodecBridge> media_codec; media_codec.reset(AudioCodecBridge::Create(kCodecOpus)); + if (!media_codec) + return; + uint8_t dummy_extra_data[] = {0, 0}; // Extra Data is NULL. diff --git a/chromium/media/base/android/video_decoder_job.cc b/chromium/media/base/android/video_decoder_job.cc index fa49ab3ba48..3e3123c98ce 100644 --- a/chromium/media/base/android/video_decoder_job.cc +++ b/chromium/media/base/android/video_decoder_job.cc @@ -151,7 +151,7 @@ bool VideoDecoderJob::UpdateOutputFormat() { return false; int prev_output_width = output_width_; int prev_output_height = output_height_; - // See b/18224769. The values reported from MediaCodecBridge::GetOutputFormat + // See b/18224769. The values reported from MediaCodecBridge::GetOutputSize // correspond to the actual video frame size, but this is not necessarily the // size that should be output. output_width_ = config_width_; diff --git a/chromium/media/base/android/media_codec_video_decoder.cc b/chromium/media/base/android/video_media_codec_decoder.cc index 2726d3aa5b0..e9a305830a4 100644 --- a/chromium/media/base/android/media_codec_video_decoder.cc +++ b/chromium/media/base/android/video_media_codec_decoder.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "media/base/android/media_codec_video_decoder.h" +#include "media/base/android/video_media_codec_decoder.h" #include <utility> @@ -19,7 +19,7 @@ namespace { const int kDelayForStandAloneEOS = 2; // milliseconds } -MediaCodecVideoDecoder::MediaCodecVideoDecoder( +VideoMediaCodecDecoder::VideoMediaCodecDecoder( const scoped_refptr<base::SingleThreadTaskRunner>& media_task_runner, FrameStatistics* frame_statistics, const base::Closure& request_data_cb, @@ -41,26 +41,25 @@ MediaCodecVideoDecoder::MediaCodecVideoDecoder( error_cb), is_protected_surface_required_(false), update_current_time_cb_(update_current_time_cb), - video_size_changed_cb_(video_size_changed_cb) { -} + video_size_changed_cb_(video_size_changed_cb) {} -MediaCodecVideoDecoder::~MediaCodecVideoDecoder() { +VideoMediaCodecDecoder::~VideoMediaCodecDecoder() { DCHECK(media_task_runner_->BelongsToCurrentThread()); DVLOG(1) << "VideoDecoder::~VideoDecoder()"; ReleaseDecoderResources(); } -const char* MediaCodecVideoDecoder::class_name() const { +const char* VideoMediaCodecDecoder::class_name() const { return "VideoDecoder"; } -bool MediaCodecVideoDecoder::HasStream() const { +bool VideoMediaCodecDecoder::HasStream() const { DCHECK(media_task_runner_->BelongsToCurrentThread()); return configs_.video_codec != kUnknownVideoCodec; } -void MediaCodecVideoDecoder::SetDemuxerConfigs(const DemuxerConfigs& configs) { +void VideoMediaCodecDecoder::SetDemuxerConfigs(const DemuxerConfigs& configs) { DVLOG(1) << class_name() << "::" << __FUNCTION__ << " " << configs; configs_ = configs; @@ -72,13 +71,13 @@ void MediaCodecVideoDecoder::SetDemuxerConfigs(const DemuxerConfigs& configs) { } } -bool MediaCodecVideoDecoder::IsContentEncrypted() const { +bool VideoMediaCodecDecoder::IsContentEncrypted() const { // Make sure SetDemuxerConfigs() as been called. DCHECK(configs_.video_codec != kUnknownVideoCodec); return configs_.is_video_encrypted; } -void MediaCodecVideoDecoder::ReleaseDecoderResources() { +void VideoMediaCodecDecoder::ReleaseDecoderResources() { DCHECK(media_task_runner_->BelongsToCurrentThread()); DVLOG(1) << class_name() << "::" << __FUNCTION__; @@ -89,14 +88,14 @@ void MediaCodecVideoDecoder::ReleaseDecoderResources() { surface_ = gfx::ScopedJavaSurface(); } -void MediaCodecVideoDecoder::ReleaseMediaCodec() { +void VideoMediaCodecDecoder::ReleaseMediaCodec() { DCHECK(media_task_runner_->BelongsToCurrentThread()); MediaCodecDecoder::ReleaseMediaCodec(); delayed_buffers_.clear(); } -void MediaCodecVideoDecoder::SetVideoSurface(gfx::ScopedJavaSurface surface) { +void VideoMediaCodecDecoder::SetVideoSurface(gfx::ScopedJavaSurface surface) { DCHECK(media_task_runner_->BelongsToCurrentThread()); DVLOG(1) << class_name() << "::" << __FUNCTION__ @@ -107,25 +106,25 @@ void MediaCodecVideoDecoder::SetVideoSurface(gfx::ScopedJavaSurface surface) { needs_reconfigure_ = true; } -bool MediaCodecVideoDecoder::HasVideoSurface() const { +bool VideoMediaCodecDecoder::HasVideoSurface() const { DCHECK(media_task_runner_->BelongsToCurrentThread()); return !surface_.IsEmpty(); } -void MediaCodecVideoDecoder::SetProtectedSurfaceRequired(bool value) { +void VideoMediaCodecDecoder::SetProtectedSurfaceRequired(bool value) { DCHECK(media_task_runner_->BelongsToCurrentThread()); is_protected_surface_required_ = value; } -bool MediaCodecVideoDecoder::IsProtectedSurfaceRequired() const { +bool VideoMediaCodecDecoder::IsProtectedSurfaceRequired() const { DCHECK(media_task_runner_->BelongsToCurrentThread()); return is_protected_surface_required_; } -bool MediaCodecVideoDecoder::IsCodecReconfigureNeeded( +bool VideoMediaCodecDecoder::IsCodecReconfigureNeeded( const DemuxerConfigs& next) const { if (always_reconfigure_for_tests_) return true; @@ -143,11 +142,11 @@ bool MediaCodecVideoDecoder::IsCodecReconfigureNeeded( } return !static_cast<VideoCodecBridge*>(media_codec_bridge_.get()) - ->IsAdaptivePlaybackSupported(next.video_size.width(), - next.video_size.height()); + ->IsAdaptivePlaybackSupported(next.video_size.width(), + next.video_size.height()); } -MediaCodecDecoder::ConfigStatus MediaCodecVideoDecoder::ConfigureInternal( +MediaCodecDecoder::ConfigStatus VideoMediaCodecDecoder::ConfigureInternal( jobject media_crypto) { DCHECK(media_task_runner_->BelongsToCurrentThread()); @@ -173,11 +172,8 @@ MediaCodecDecoder::ConfigStatus MediaCodecVideoDecoder::ConfigureInternal( bool is_secure = IsContentEncrypted() && is_protected_surface_required_; media_codec_bridge_.reset(VideoCodecBridge::CreateDecoder( - configs_.video_codec, - is_secure, - configs_.video_size, - surface_.j_surface().obj(), - media_crypto)); + configs_.video_codec, is_secure, configs_.video_size, + surface_.j_surface().obj(), media_crypto)); if (!media_codec_bridge_) { DVLOG(0) << class_name() << "::" << __FUNCTION__ @@ -193,7 +189,7 @@ MediaCodecDecoder::ConfigStatus MediaCodecVideoDecoder::ConfigureInternal( return kConfigOk; } -void MediaCodecVideoDecoder::AssociateCurrentTimeWithPTS(base::TimeDelta pts) { +void VideoMediaCodecDecoder::AssociateCurrentTimeWithPTS(base::TimeDelta pts) { DCHECK(media_task_runner_->BelongsToCurrentThread()); DVLOG(1) << class_name() << "::" << __FUNCTION__ << " pts:" << pts; @@ -203,18 +199,18 @@ void MediaCodecVideoDecoder::AssociateCurrentTimeWithPTS(base::TimeDelta pts) { last_seen_pts_ = pts; } -void MediaCodecVideoDecoder::DissociatePTSFromTime() { +void VideoMediaCodecDecoder::DissociatePTSFromTime() { DCHECK(media_task_runner_->BelongsToCurrentThread()); start_pts_ = last_seen_pts_ = kNoTimestamp(); } -void MediaCodecVideoDecoder::OnOutputFormatChanged() { +void VideoMediaCodecDecoder::OnOutputFormatChanged() { DCHECK(decoder_thread_.task_runner()->BelongsToCurrentThread()); gfx::Size prev_size = video_size_; - // See b/18224769. The values reported from MediaCodecBridge::GetOutputFormat + // See b/18224769. The values reported from MediaCodecBridge::GetOutputSize // correspond to the actual video frame size, but this is not necessarily the // size that should be output. video_size_ = configs_.video_size; @@ -224,7 +220,7 @@ void MediaCodecVideoDecoder::OnOutputFormatChanged() { } } -void MediaCodecVideoDecoder::Render(int buffer_index, +void VideoMediaCodecDecoder::Render(int buffer_index, size_t offset, size_t size, RenderMode render_mode, @@ -299,19 +295,19 @@ void MediaCodecVideoDecoder::Render(int buffer_index, delayed_buffers_.insert(buffer_index); decoder_thread_.task_runner()->PostDelayedTask( - FROM_HERE, base::Bind(&MediaCodecVideoDecoder::ReleaseOutputBuffer, + FROM_HERE, base::Bind(&VideoMediaCodecDecoder::ReleaseOutputBuffer, base::Unretained(this), buffer_index, pts, render, update_time, eos_encountered), time_to_render); } -int MediaCodecVideoDecoder::NumDelayedRenderTasks() const { +int VideoMediaCodecDecoder::NumDelayedRenderTasks() const { DCHECK(decoder_thread_.task_runner()->BelongsToCurrentThread()); return delayed_buffers_.size(); } -void MediaCodecVideoDecoder::ReleaseDelayedBuffers() { +void VideoMediaCodecDecoder::ReleaseDelayedBuffers() { // Media thread // Called when there is no decoder thread for (int index : delayed_buffers_) @@ -321,7 +317,7 @@ void MediaCodecVideoDecoder::ReleaseDelayedBuffers() { } #ifndef NDEBUG -void MediaCodecVideoDecoder::VerifyUnitIsKeyFrame( +void VideoMediaCodecDecoder::VerifyUnitIsKeyFrame( const AccessUnit* unit) const { // The first video frame in a sequence must be a key frame or stand-alone EOS. DCHECK(unit); @@ -330,7 +326,7 @@ void MediaCodecVideoDecoder::VerifyUnitIsKeyFrame( } #endif -void MediaCodecVideoDecoder::ReleaseOutputBuffer(int buffer_index, +void VideoMediaCodecDecoder::ReleaseOutputBuffer(int buffer_index, base::TimeDelta pts, bool render, bool update_time, diff --git a/chromium/media/base/android/media_codec_video_decoder.h b/chromium/media/base/android/video_media_codec_decoder.h index b7fc3950a24..03497269ba1 100644 --- a/chromium/media/base/android/media_codec_video_decoder.h +++ b/chromium/media/base/android/video_media_codec_decoder.h @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef MEDIA_BASE_ANDROID_MEDIA_CODEC_VIDEO_DECODER_H_ -#define MEDIA_BASE_ANDROID_MEDIA_CODEC_VIDEO_DECODER_H_ +#ifndef MEDIA_BASE_ANDROID_VIDEO_MEDIA_CODEC_DECODER_H_ +#define MEDIA_BASE_ANDROID_VIDEO_MEDIA_CODEC_DECODER_H_ #include <stddef.h> @@ -16,7 +16,7 @@ namespace media { // Video decoder for MediaCodecPlayer -class MediaCodecVideoDecoder : public MediaCodecDecoder { +class VideoMediaCodecDecoder : public MediaCodecDecoder { public: // Typedefs for the notification callbacks typedef base::Callback<void(const gfx::Size& video_size)> @@ -29,7 +29,7 @@ class MediaCodecVideoDecoder : public MediaCodecDecoder { // codec_created_cb: reports that video codec has been created. A controller // class might use it to release more resources so that this // decoder can use them. - MediaCodecVideoDecoder( + VideoMediaCodecDecoder( const scoped_refptr<base::SingleThreadTaskRunner>& media_runner, FrameStatistics* frame_statistics, const base::Closure& request_data_cb, @@ -40,7 +40,7 @@ class MediaCodecVideoDecoder : public MediaCodecDecoder { const base::Closure& error_cb, const SetTimeCallback& update_current_time_cb, const VideoSizeChangedCallback& video_size_changed_cb); - ~MediaCodecVideoDecoder() override; + ~VideoMediaCodecDecoder() override; const char* class_name() const override; @@ -122,9 +122,9 @@ class MediaCodecVideoDecoder : public MediaCodecDecoder { // Mantain the last seen PTS for stand-alone EOS. base::TimeDelta last_seen_pts_; - DISALLOW_COPY_AND_ASSIGN(MediaCodecVideoDecoder); + DISALLOW_COPY_AND_ASSIGN(VideoMediaCodecDecoder); }; } // namespace media -#endif // MEDIA_BASE_ANDROID_MEDIA_CODEC_VIDEO_DECODER_H_ +#endif // MEDIA_BASE_ANDROID_VIDEO_MEDIA_CODEC_DECODER_H_ diff --git a/chromium/media/base/audio_buffer.cc b/chromium/media/base/audio_buffer.cc index ac9c31a1b22..9370ee891b0 100644 --- a/chromium/media/base/audio_buffer.cc +++ b/chromium/media/base/audio_buffer.cc @@ -208,6 +208,12 @@ inline int16_t ConvertSample<float, int16_t>(float sample) { : sample * std::numeric_limits<int16_t>::max())); } +void AudioBuffer::AdjustSampleRate(int sample_rate) { + DCHECK(!end_of_stream_); + sample_rate_ = sample_rate; + duration_ = CalculateDuration(adjusted_frame_count_, sample_rate_); +} + void AudioBuffer::ReadFrames(int frames_to_copy, int source_frame_offset, int dest_frame_offset, @@ -275,9 +281,9 @@ void AudioBuffer::ReadFrames(int frames_to_copy, // Remaining formats are integer interleaved data. Use the deinterleaving code // in AudioBus to copy the data. - DCHECK(sample_format_ == kSampleFormatU8 || - sample_format_ == kSampleFormatS16 || - sample_format_ == kSampleFormatS32); + DCHECK( + sample_format_ == kSampleFormatU8 || sample_format_ == kSampleFormatS16 || + sample_format_ == kSampleFormatS24 || sample_format_ == kSampleFormatS32); int bytes_per_channel = SampleFormatToBytesPerChannel(sample_format_); int frame_size = channel_count_ * bytes_per_channel; const uint8_t* source_data = data_.get() + source_frame_offset * frame_size; diff --git a/chromium/media/base/audio_buffer.h b/chromium/media/base/audio_buffer.h index 8ae281eabda..6d74c7773cf 100644 --- a/chromium/media/base/audio_buffer.h +++ b/chromium/media/base/audio_buffer.h @@ -78,6 +78,12 @@ class MEDIA_EXPORT AudioBuffer // is disallowed. static scoped_refptr<AudioBuffer> CreateEOSBuffer(); + // Update sample rate and computed duration. + // TODO(chcunningham): Remove this upon patching FFmpeg's AAC decoder to + // provide the correct sample rate at the boundary of an implicit config + // change. + void AdjustSampleRate(int sample_rate); + // Copy frames into |dest|. |frames_to_copy| is the number of frames to copy. // |source_frame_offset| specifies how many frames in the buffer to skip // first. |dest_frame_offset| is the frame offset in |dest|. The frames are @@ -131,10 +137,16 @@ class MEDIA_EXPORT AudioBuffer // If there's no data in this buffer, it represents end of stream. bool end_of_stream() const { return end_of_stream_; } - // Access to the raw buffer for ffmpeg to write directly to. Data for planar - // data is grouped by channel. There is only 1 entry for interleaved formats. + // Access to the raw buffer for ffmpeg and Android MediaCodec decoders to + // write directly to. For planar formats the vector elements correspond to + // the channels. For interleaved formats the resulting vector has exactly + // one element which contains the buffer pointer. const std::vector<uint8_t*>& channel_data() const { return channel_data_; } + // The size of allocated data memory block. For planar formats channels go + // sequentially in this block. + size_t data_size() const { return data_size_; } + private: friend class base::RefCountedThreadSafe<AudioBuffer>; @@ -162,7 +174,7 @@ class MEDIA_EXPORT AudioBuffer const SampleFormat sample_format_; const ChannelLayout channel_layout_; const int channel_count_; - const int sample_rate_; + int sample_rate_; int adjusted_frame_count_; int trim_start_; const bool end_of_stream_; diff --git a/chromium/media/base/audio_capturer_source.h b/chromium/media/base/audio_capturer_source.h index 31b39bbf993..fd79db3dfcd 100644 --- a/chromium/media/base/audio_capturer_source.h +++ b/chromium/media/base/audio_capturer_source.h @@ -23,6 +23,8 @@ class AudioCapturerSource class CaptureCallback { public: // Callback to deliver the captured data from the OS. + // TODO(chcunningham): Update delay argument to use frames instead of + // milliseconds to prevent loss of precision. See http://crbug.com/587291. virtual void Capture(const AudioBus* audio_source, int audio_delay_milliseconds, double volume, diff --git a/chromium/media/base/audio_codecs.cc b/chromium/media/base/audio_codecs.cc new file mode 100644 index 00000000000..46a7b2510fa --- /dev/null +++ b/chromium/media/base/audio_codecs.cc @@ -0,0 +1,51 @@ +// Copyright 2016 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/base/audio_codecs.h" + +#include "base/logging.h" + +namespace media { + +// These names come from src/third_party/ffmpeg/libavcodec/codec_desc.c +std::string GetCodecName(AudioCodec codec) { + switch (codec) { + case kUnknownAudioCodec: + return "unknown"; + case kCodecAAC: + return "aac"; + case kCodecMP3: + return "mp3"; + case kCodecPCM: + case kCodecPCM_S16BE: + case kCodecPCM_S24BE: + return "pcm"; + case kCodecVorbis: + return "vorbis"; + case kCodecFLAC: + return "flac"; + case kCodecAMR_NB: + return "amr_nb"; + case kCodecAMR_WB: + return "amr_wb"; + case kCodecPCM_MULAW: + return "pcm_mulaw"; + case kCodecGSM_MS: + return "gsm_ms"; + case kCodecOpus: + return "opus"; + case kCodecPCM_ALAW: + return "pcm_alaw"; + case kCodecEAC3: + return "eac3"; + case kCodecALAC: + return "alac"; + case kCodecAC3: + return "ac3"; + } + NOTREACHED(); + return ""; +} + +} // namespace media diff --git a/chromium/media/base/audio_codecs.h b/chromium/media/base/audio_codecs.h new file mode 100644 index 00000000000..5cba02955bf --- /dev/null +++ b/chromium/media/base/audio_codecs.h @@ -0,0 +1,48 @@ +// Copyright 2016 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_BASE_AUDIO_CODECS_H_ +#define MEDIA_BASE_AUDIO_CODECS_H_ + +#include <string> +#include "media/base/media_export.h" + +namespace media { + +enum AudioCodec { + // These values are histogrammed over time; do not change their ordinal + // values. When deleting a codec replace it with a dummy value; when adding a + // codec, do so at the bottom before kAudioCodecMax, and update the value of + // kAudioCodecMax to equal the new codec. + kUnknownAudioCodec = 0, + kCodecAAC = 1, + kCodecMP3 = 2, + kCodecPCM = 3, + kCodecVorbis = 4, + kCodecFLAC = 5, + kCodecAMR_NB = 6, + kCodecAMR_WB = 7, + kCodecPCM_MULAW = 8, + kCodecGSM_MS = 9, + kCodecPCM_S16BE = 10, + kCodecPCM_S24BE = 11, + kCodecOpus = 12, + kCodecEAC3 = 13, + kCodecPCM_ALAW = 14, + kCodecALAC = 15, + kCodecAC3 = 16, + // DO NOT ADD RANDOM AUDIO CODECS! + // + // The only acceptable time to add a new codec is if there is production code + // that uses said codec in the same CL. + + // Must always be equal to the largest entry ever logged. + kAudioCodecMax = kCodecAC3, +}; + +std::string MEDIA_EXPORT GetCodecName(AudioCodec codec); + +} // namespace media + +#endif // MEDIA_BASE_AUDIO_CODECS_H_ diff --git a/chromium/media/base/audio_decoder.cc b/chromium/media/base/audio_decoder.cc index 5212794d983..6e9245880ea 100644 --- a/chromium/media/base/audio_decoder.cc +++ b/chromium/media/base/audio_decoder.cc @@ -12,4 +12,8 @@ AudioDecoder::AudioDecoder() {} AudioDecoder::~AudioDecoder() {} +bool AudioDecoder::NeedsBitstreamConversion() const { + return false; +} + } // namespace media diff --git a/chromium/media/base/audio_decoder.h b/chromium/media/base/audio_decoder.h index 681fac3523f..a68c824eaa7 100644 --- a/chromium/media/base/audio_decoder.h +++ b/chromium/media/base/audio_decoder.h @@ -11,8 +11,8 @@ #include "base/macros.h" #include "base/memory/ref_counted.h" #include "media/base/audio_decoder_config.h" -#include "media/base/cdm_context.h" #include "media/base/channel_layout.h" +#include "media/base/decode_status.h" #include "media/base/decoder_buffer.h" #include "media/base/media_export.h" #include "media/base/pipeline_status.h" @@ -20,19 +20,11 @@ namespace media { class AudioBuffer; +class CdmContext; class DemuxerStream; class MEDIA_EXPORT AudioDecoder { public: - // Status codes for decode operations. - // TODO(rileya): Now that both AudioDecoder and VideoDecoder Status enums - // match, break them into a decoder_status.h. - enum Status { - kOk, // We're all good. - kAborted, // We aborted as a result of Reset() or destruction. - kDecodeError // A decoding error occurred. - }; - // Callback for VideoDecoder initialization. typedef base::Callback<void(bool success)> InitCB; @@ -40,10 +32,9 @@ class MEDIA_EXPORT AudioDecoder { // available. Only non-EOS frames should be returned via this callback. typedef base::Callback<void(const scoped_refptr<AudioBuffer>&)> OutputCB; - // Callback for Decode(). Called after the decoder has completed decoding - // corresponding DecoderBuffer, indicating that it's ready to accept another - // buffer to decode. - typedef base::Callback<void(Status)> DecodeCB; + // Callback for Decode(). Called after the decoder has accepted corresponding + // DecoderBuffer, indicating that the pipeline can send next buffer to decode. + typedef base::Callback<void(DecodeStatus)> DecodeCB; AudioDecoder(); @@ -56,17 +47,15 @@ class MEDIA_EXPORT AudioDecoder { // Returns the name of the decoder for logging purpose. virtual std::string GetDisplayName() const = 0; - // Initializes an AudioDecoder with the given DemuxerStream, executing the - // callback upon completion. - // - // |set_cdm_ready_cb| can be used to set/cancel a CdmReadyCB with which the - // decoder can be notified when a CDM is ready. The decoder can use the CDM to - // handle encrypted video stream. + // Initializes an AudioDecoder with |config|, executing the |init_cb| upon + // completion. // - // |init_cb| is used to return initialization status. - // |output_cb| is called for decoded audio buffers (see Decode()). + // |cdm_context| can be used to handle encrypted buffers. May be null if the + // stream is not encrypted. + // |init_cb| is used to return initialization status. + // |output_cb| is called for decoded audio buffers (see Decode()). virtual void Initialize(const AudioDecoderConfig& config, - const SetCdmReadyCB& set_cdm_ready_cb, + CdmContext* cdm_context, const InitCB& init_cb, const OutputCB& output_cb) = 0; @@ -88,6 +77,9 @@ class MEDIA_EXPORT AudioDecoder { // aborted before |closure| is called. virtual void Reset(const base::Closure& closure) = 0; + // Returns true if the decoder needs bitstream conversion before decoding. + virtual bool NeedsBitstreamConversion() const; + private: DISALLOW_COPY_AND_ASSIGN(AudioDecoder); }; diff --git a/chromium/media/base/audio_decoder_config.cc b/chromium/media/base/audio_decoder_config.cc index 9d3f67209ba..253c82b147c 100644 --- a/chromium/media/base/audio_decoder_config.cc +++ b/chromium/media/base/audio_decoder_config.cc @@ -9,46 +9,6 @@ namespace media { -// These names come from src/third_party/ffmpeg/libavcodec/codec_desc.c -std::string GetCodecName(AudioCodec codec) { - switch (codec) { - case kUnknownAudioCodec: - return "unknown"; - case kCodecAAC: - return "aac"; - case kCodecMP3: - return "mp3"; - case kCodecPCM: - case kCodecPCM_S16BE: - case kCodecPCM_S24BE: - return "pcm"; - case kCodecVorbis: - return "vorbis"; - case kCodecFLAC: - return "flac"; - case kCodecAMR_NB: - return "amr_nb"; - case kCodecAMR_WB: - return "amr_wb"; - case kCodecPCM_MULAW: - return "pcm_mulaw"; - case kCodecGSM_MS: - return "gsm_ms"; - case kCodecOpus: - return "opus"; - case kCodecPCM_ALAW: - return "pcm_alaw"; - case kCodecEAC3: - return "eac3"; - case kCodecALAC: - return "alac"; - case kCodecAC3: - return "ac3"; - } - NOTREACHED(); - return ""; -} - AudioDecoderConfig::AudioDecoderConfig() : codec_(kUnknownAudioCodec), sample_format_(kUnknownSampleFormat), @@ -56,26 +16,28 @@ AudioDecoderConfig::AudioDecoderConfig() channel_layout_(CHANNEL_LAYOUT_UNSUPPORTED), samples_per_second_(0), bytes_per_frame_(0), - is_encrypted_(false), - codec_delay_(0) { -} + codec_delay_(0) {} -AudioDecoderConfig::AudioDecoderConfig(AudioCodec codec, - SampleFormat sample_format, - ChannelLayout channel_layout, - int samples_per_second, - const std::vector<uint8_t>& extra_data, - bool is_encrypted) { +AudioDecoderConfig::AudioDecoderConfig( + AudioCodec codec, + SampleFormat sample_format, + ChannelLayout channel_layout, + int samples_per_second, + const std::vector<uint8_t>& extra_data, + const EncryptionScheme& encryption_scheme) { Initialize(codec, sample_format, channel_layout, samples_per_second, - extra_data, is_encrypted, base::TimeDelta(), 0); + extra_data, encryption_scheme, base::TimeDelta(), 0); } +AudioDecoderConfig::AudioDecoderConfig(const AudioDecoderConfig& other) = + default; + void AudioDecoderConfig::Initialize(AudioCodec codec, SampleFormat sample_format, ChannelLayout channel_layout, int samples_per_second, const std::vector<uint8_t>& extra_data, - bool is_encrypted, + const EncryptionScheme& encryption_scheme, base::TimeDelta seek_preroll, int codec_delay) { codec_ = codec; @@ -84,7 +46,7 @@ void AudioDecoderConfig::Initialize(AudioCodec codec, sample_format_ = sample_format; bytes_per_channel_ = SampleFormatToBytesPerChannel(sample_format); extra_data_ = extra_data; - is_encrypted_ = is_encrypted; + encryption_scheme_ = encryption_scheme; seek_preroll_ = seek_preroll; codec_delay_ = codec_delay; @@ -112,7 +74,7 @@ bool AudioDecoderConfig::Matches(const AudioDecoderConfig& config) const { (channel_layout() == config.channel_layout()) && (samples_per_second() == config.samples_per_second()) && (extra_data() == config.extra_data()) && - (is_encrypted() == config.is_encrypted()) && + (encryption_scheme().Matches(config.encryption_scheme())) && (sample_format() == config.sample_format()) && (seek_preroll() == config.seek_preroll()) && (codec_delay() == config.codec_delay())); diff --git a/chromium/media/base/audio_decoder_config.h b/chromium/media/base/audio_decoder_config.h index a0e262f005d..266de82d605 100644 --- a/chromium/media/base/audio_decoder_config.h +++ b/chromium/media/base/audio_decoder_config.h @@ -12,45 +12,14 @@ #include "base/macros.h" #include "base/time/time.h" +#include "media/base/audio_codecs.h" #include "media/base/channel_layout.h" +#include "media/base/encryption_scheme.h" #include "media/base/media_export.h" #include "media/base/sample_format.h" namespace media { -enum AudioCodec { - // These values are histogrammed over time; do not change their ordinal - // values. When deleting a codec replace it with a dummy value; when adding a - // codec, do so at the bottom before kAudioCodecMax, and update the value of - // kAudioCodecMax to equal the new codec. - kUnknownAudioCodec = 0, - kCodecAAC = 1, - kCodecMP3 = 2, - kCodecPCM = 3, - kCodecVorbis = 4, - kCodecFLAC = 5, - kCodecAMR_NB = 6, - kCodecAMR_WB = 7, - kCodecPCM_MULAW = 8, - kCodecGSM_MS = 9, - kCodecPCM_S16BE = 10, - kCodecPCM_S24BE = 11, - kCodecOpus = 12, - kCodecEAC3 = 13, - kCodecPCM_ALAW = 14, - kCodecALAC = 15, - kCodecAC3 = 16, - // DO NOT ADD RANDOM AUDIO CODECS! - // - // The only acceptable time to add a new codec is if there is production code - // that uses said codec in the same CL. - - // Must always be equal to the largest entry ever logged. - kAudioCodecMax = kCodecAC3, -}; - -std::string MEDIA_EXPORT GetCodecName(AudioCodec codec); - // TODO(dalecurtis): FFmpeg API uses |bytes_per_channel| instead of // |bits_per_channel|, we should switch over since bits are generally confusing // to work with. @@ -66,7 +35,9 @@ class MEDIA_EXPORT AudioDecoderConfig { ChannelLayout channel_layout, int samples_per_second, const std::vector<uint8_t>& extra_data, - bool is_encrypted); + const EncryptionScheme& encryption_scheme); + + AudioDecoderConfig(const AudioDecoderConfig& other); ~AudioDecoderConfig(); @@ -76,7 +47,7 @@ class MEDIA_EXPORT AudioDecoderConfig { ChannelLayout channel_layout, int samples_per_second, const std::vector<uint8_t>& extra_data, - bool is_encrypted, + const EncryptionScheme& encryption_scheme, base::TimeDelta seek_preroll, int codec_delay); @@ -109,7 +80,12 @@ class MEDIA_EXPORT AudioDecoderConfig { // Whether the audio stream is potentially encrypted. // Note that in a potentially encrypted audio stream, individual buffers // can be encrypted or not encrypted. - bool is_encrypted() const { return is_encrypted_; } + bool is_encrypted() const { return encryption_scheme_.is_encrypted(); } + + // Encryption scheme used for encrypted buffers. + const EncryptionScheme& encryption_scheme() const { + return encryption_scheme_; + } private: AudioCodec codec_; @@ -119,7 +95,7 @@ class MEDIA_EXPORT AudioDecoderConfig { int samples_per_second_; int bytes_per_frame_; std::vector<uint8_t> extra_data_; - bool is_encrypted_; + EncryptionScheme encryption_scheme_; // |seek_preroll_| is the duration of the data that the decoder must decode // before the decoded data is valid. diff --git a/chromium/media/base/audio_hardware_config.cc b/chromium/media/base/audio_hardware_config.cc index c4b56157957..f3f151385cd 100644 --- a/chromium/media/base/audio_hardware_config.cc +++ b/chromium/media/base/audio_hardware_config.cc @@ -100,17 +100,18 @@ void AudioHardwareConfig::UpdateOutputConfig( } // static -int AudioHardwareConfig::GetHighLatencyBufferSize( - const media::AudioParameters& output_params) { +int AudioHardwareConfig::GetHighLatencyBufferSize(int sample_rate, + int buffer_size) { // Empirically, we consider 20ms of samples to be high latency. - const double twenty_ms_size = 2.0 * output_params.sample_rate() / 100; + const double twenty_ms_size = 2.0 * sample_rate / 100; #if defined(OS_WIN) + buffer_size = std::max(buffer_size, 1); + // Windows doesn't use power of two buffer sizes, so we should always round up // to the nearest multiple of the output buffer size. const int high_latency_buffer_size = - std::ceil(twenty_ms_size / output_params.frames_per_buffer()) * - output_params.frames_per_buffer(); + std::ceil(twenty_ms_size / buffer_size) * buffer_size; #else // On other platforms use the nearest higher power of two buffer size. For a // given sample rate, this works out to: @@ -128,12 +129,13 @@ int AudioHardwareConfig::GetHighLatencyBufferSize( const int high_latency_buffer_size = RoundUpToPowerOfTwo(twenty_ms_size); #endif // defined(OS_WIN) - return std::max(output_params.frames_per_buffer(), high_latency_buffer_size); + return std::max(buffer_size, high_latency_buffer_size); } int AudioHardwareConfig::GetHighLatencyBufferSize() const { AutoLock auto_lock(config_lock_); - return GetHighLatencyBufferSize(output_params_); + return GetHighLatencyBufferSize(output_params_.sample_rate(), + output_params_.frames_per_buffer()); } } // namespace media diff --git a/chromium/media/base/audio_hardware_config.h b/chromium/media/base/audio_hardware_config.h index f7d26789989..124695d09c1 100644 --- a/chromium/media/base/audio_hardware_config.h +++ b/chromium/media/base/audio_hardware_config.h @@ -45,8 +45,9 @@ class MEDIA_EXPORT AudioHardwareConfig { // For clients which don't need low latency, a larger buffer size should be // used to save power and CPU resources. int GetHighLatencyBufferSize() const; - static int GetHighLatencyBufferSize( - const media::AudioParameters& output_params); + + // |buffer_size| should be set to 0 if a client has no preference. + static int GetHighLatencyBufferSize(int sample_rate, int buffer_size); private: // Cached values; access is protected by |config_lock_|. diff --git a/chromium/media/base/audio_hash.cc b/chromium/media/base/audio_hash.cc index d879e545362..7f133dd78ba 100644 --- a/chromium/media/base/audio_hash.cc +++ b/chromium/media/base/audio_hash.cc @@ -5,6 +5,7 @@ // MSVC++ requires this to be set before any other includes to get M_PI. #define _USE_MATH_DEFINES #include <cmath> +#include <sstream> #include "media/base/audio_hash.h" @@ -52,4 +53,17 @@ std::string AudioHash::ToString() const { return result; } +bool AudioHash::IsEquivalent(const std::string& other, double tolerance) const { + float other_hash; + char comma; + + std::stringstream is(other); + for (size_t i = 0; i < arraysize(audio_hash_); ++i) { + is >> other_hash >> comma; + if (fabs(audio_hash_[i] - other_hash) > tolerance) + return false; + } + return true; +} + } // namespace media diff --git a/chromium/media/base/audio_hash.h b/chromium/media/base/audio_hash.h index 83ee901d3c9..5ac1aa267b7 100644 --- a/chromium/media/base/audio_hash.h +++ b/chromium/media/base/audio_hash.h @@ -43,6 +43,11 @@ class MEDIA_EXPORT AudioHash { // Return a string representation of the current hash. std::string ToString() const; + // Compare with another hash value given as string representation. + // Returns true if for every bucket the difference between this and + // other is less than tolerance. + bool IsEquivalent(const std::string& other, double tolerance) const; + private: // Storage for the audio hash. The number of buckets controls the importance // of position in the hash. A higher number reduces the chance of false diff --git a/chromium/media/base/audio_pull_fifo.cc b/chromium/media/base/audio_pull_fifo.cc index cf25142d904..03aa001a38a 100644 --- a/chromium/media/base/audio_pull_fifo.cc +++ b/chromium/media/base/audio_pull_fifo.cc @@ -46,6 +46,10 @@ void AudioPullFifo::Consume(AudioBus* destination, int frames_to_consume) { void AudioPullFifo::Clear() { fifo_index_ = fifo_->frames(); } +int AudioPullFifo::SizeInFrames() const { + return fifo_->frames(); +} + int AudioPullFifo::ReadFromFifo(AudioBus* destination, int frames_to_provide, int write_pos) { diff --git a/chromium/media/base/audio_pull_fifo.h b/chromium/media/base/audio_pull_fifo.h index a839c8d6c8e..61e8332a677 100644 --- a/chromium/media/base/audio_pull_fifo.h +++ b/chromium/media/base/audio_pull_fifo.h @@ -7,6 +7,7 @@ #include "base/callback.h" #include "base/macros.h" +#include "base/memory/scoped_ptr.h" #include "media/base/media_export.h" namespace media { @@ -41,6 +42,9 @@ class MEDIA_EXPORT AudioPullFifo { // Empties the FIFO without deallocating any memory. void Clear(); + // Returns the size of the fifo in number of frames. + int SizeInFrames() const; + private: // Attempt to fulfill the request using what is available in the FIFO. // Append new data to the |destination| starting at |write_pos|. diff --git a/chromium/media/base/audio_pull_fifo_unittest.cc b/chromium/media/base/audio_pull_fifo_unittest.cc index e6a7362ce2d..4f1d98ed5fa 100644 --- a/chromium/media/base/audio_pull_fifo_unittest.cc +++ b/chromium/media/base/audio_pull_fifo_unittest.cc @@ -28,11 +28,15 @@ class AudioPullFifoTest : public testing::TestWithParam<int> { public: AudioPullFifoTest() - : pull_fifo_(kChannels, kMaxFramesInFifo, base::Bind( - &AudioPullFifoTest::ProvideInput, base::Unretained(this))), - audio_bus_(AudioBus::Create(kChannels, kMaxFramesInFifo)), - fill_value_(0), - last_frame_delay_(-1) {} + : pull_fifo_(kChannels, + kMaxFramesInFifo, + base::Bind(&AudioPullFifoTest::ProvideInput, + base::Unretained(this))), + audio_bus_(AudioBus::Create(kChannels, kMaxFramesInFifo)), + fill_value_(0), + last_frame_delay_(-1) { + EXPECT_EQ(kMaxFramesInFifo, pull_fifo_.SizeInFrames()); + } virtual ~AudioPullFifoTest() {} void VerifyValue(const float data[], int size, float start_value) { diff --git a/chromium/media/base/audio_push_fifo.cc b/chromium/media/base/audio_push_fifo.cc new file mode 100644 index 00000000000..5e759670b6b --- /dev/null +++ b/chromium/media/base/audio_push_fifo.cc @@ -0,0 +1,86 @@ +// Copyright 2016 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/base/audio_push_fifo.h" + +#include <algorithm> + +#include "base/logging.h" + +namespace media { + +AudioPushFifo::AudioPushFifo(const OutputCallback& callback) + : callback_(callback), frames_per_buffer_(0) { + DCHECK(!callback_.is_null()); +} + +AudioPushFifo::~AudioPushFifo() {} + +void AudioPushFifo::Reset(int frames_per_buffer) { + DCHECK_GT(frames_per_buffer, 0); + + audio_queue_.reset(); + queued_frames_ = 0; + + frames_per_buffer_ = frames_per_buffer; +} + +void AudioPushFifo::Push(const AudioBus& input_bus) { + DCHECK_GT(frames_per_buffer_, 0); + + // Fast path: No buffering required. + if ((queued_frames_ == 0) && (input_bus.frames() == frames_per_buffer_)) { + callback_.Run(input_bus, 0); + return; + } + + // Lazy-create the |audio_queue_| if needed. + if (!audio_queue_ || audio_queue_->channels() != input_bus.channels()) + audio_queue_ = AudioBus::Create(input_bus.channels(), frames_per_buffer_); + + // Start with a frame offset that refers to the position of the first sample + // in |audio_queue_| relative to the first sample in |input_bus|. + int frame_delay = -queued_frames_; + + // Repeatedly fill up |audio_queue_| with more sample frames from |input_bus| + // and deliver batches until all sample frames in |input_bus| have been + // consumed. + int input_offset = 0; + do { + // Attempt to fill |audio_queue_| completely. + const int frames_to_enqueue = + std::min(static_cast<int>(input_bus.frames() - input_offset), + frames_per_buffer_ - queued_frames_); + if (frames_to_enqueue > 0) { + DVLOG(2) << "Enqueuing " << frames_to_enqueue << " frames."; + input_bus.CopyPartialFramesTo(input_offset, frames_to_enqueue, + queued_frames_, audio_queue_.get()); + queued_frames_ += frames_to_enqueue; + input_offset += frames_to_enqueue; + } + + // If |audio_queue_| has been filled completely, deliver the re-buffered + // audio to the consumer. + if (queued_frames_ == frames_per_buffer_) { + DVLOG(2) << "Delivering another " << queued_frames_ << " frames."; + callback_.Run(*audio_queue_, frame_delay); + frame_delay += frames_per_buffer_; + queued_frames_ = 0; + } else { + // Not enough frames queued-up yet to deliver more frames. + } + } while (input_offset < input_bus.frames()); +} + +void AudioPushFifo::Flush() { + if (queued_frames_ == 0) + return; + + audio_queue_->ZeroFramesPartial(queued_frames_, + audio_queue_->frames() - queued_frames_); + callback_.Run(*audio_queue_, -queued_frames_); + queued_frames_ = 0; +} + +} // namespace media diff --git a/chromium/media/base/audio_push_fifo.h b/chromium/media/base/audio_push_fifo.h new file mode 100644 index 00000000000..8512cd17932 --- /dev/null +++ b/chromium/media/base/audio_push_fifo.h @@ -0,0 +1,73 @@ +// Copyright 2016 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_BASE_AUDIO_PUSH_FIFO_H_ +#define MEDIA_BASE_AUDIO_PUSH_FIFO_H_ + +#include "base/callback.h" +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "media/base/audio_bus.h" +#include "media/base/media_export.h" + +namespace media { + +// Yet another FIFO for audio data that re-buffers audio to a desired buffer +// size. Unlike AudioFifo and AudioBlockFifo, this FIFO cannot overflow: The +// client is required to provide a callback that is called synchronously during +// a push whenever enough data becomes available. This implementation +// eliminates redundant memory copies when the input buffer size always matches +// the desired buffer size. +class MEDIA_EXPORT AudioPushFifo final { + public: + // Called synchronously zero, one, or multiple times during a call to Push() + // to deliver re-buffered audio. |frame_delay| refers to the position of the + // first frame in |output| relative to the first frame in the Push() call. If + // negative, this indicates the output contains some data from a prior call to + // Push(). If zero or positive, the output contains data from the current + // call to Push(). Clients can use this to adjust timestamps. + using OutputCallback = + base::Callback<void(const AudioBus& output_bus, int frame_delay)>; + + // Creates a new AudioPushFifo which delivers re-buffered audio by running + // |callback|. + explicit AudioPushFifo(const OutputCallback& callback); + + ~AudioPushFifo(); + + // Returns the number of frames in each AudioBus delivered to the + // OutputCallback. + int frames_per_buffer() const { return frames_per_buffer_; } + + // Must be called at least once before the first call to Push(). May be + // called later (e.g., to support an audio format change). + void Reset(int frames_per_buffer); + + // Pushes all audio channel data from |input_bus| through the FIFO. This will + // result in zero, one, or multiple synchronous calls to the OutputCallback + // provided in the constructor. If the |input_bus| has a different number of + // channels than the prior Push() call, any currently-queued frames will be + // dropped. + void Push(const AudioBus& input_bus); + + // Flushes any enqueued frames by invoking the OutputCallback with those + // frames plus padded zero samples. If there are no frames currently + // enqueued, OutputCallback is not run. + void Flush(); + + private: + const OutputCallback callback_; + + int frames_per_buffer_; + + // Queue of frames pending for delivery. + scoped_ptr<AudioBus> audio_queue_; + int queued_frames_; + + DISALLOW_COPY_AND_ASSIGN(AudioPushFifo); +}; + +} // namespace media + +#endif // MEDIA_BASE_AUDIO_PUSH_FIFO_H_ diff --git a/chromium/media/base/audio_push_fifo_unittest.cc b/chromium/media/base/audio_push_fifo_unittest.cc new file mode 100644 index 00000000000..8d815fd613f --- /dev/null +++ b/chromium/media/base/audio_push_fifo_unittest.cc @@ -0,0 +1,256 @@ +// Copyright 2016 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 <limits> +#include <vector> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/macros.h" +#include "media/base/audio_bus.h" +#include "media/base/audio_push_fifo.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace media { + +namespace { + +class AudioPushFifoTest : public testing::TestWithParam<int> { + public: + AudioPushFifoTest() {} + ~AudioPushFifoTest() override {} + + int output_chunk_size() const { return GetParam(); } + + void SetUp() final { + fifo_.reset(new AudioPushFifo(base::Bind( + &AudioPushFifoTest::ReceiveAndCheckNextChunk, base::Unretained(this)))); + fifo_->Reset(output_chunk_size()); + ASSERT_EQ(output_chunk_size(), fifo_->frames_per_buffer()); + } + + protected: + struct OutputChunkResult { + int num_frames; + float first_sample_value; + float last_sample_value; + int frame_delay; + }; + + // Returns the number of output chunks that should have been emitted given the + // number of input frames pushed so far. + size_t GetExpectedOutputChunks(int frames_pushed) const { + return static_cast<size_t>(frames_pushed / output_chunk_size()); + } + + // Returns the number of Push() calls to make in order to get at least 3 + // output chunks. + int GetNumPushTestIterations(int input_chunk_size) const { + return 3 * std::max(1, output_chunk_size() / input_chunk_size); + } + + // Repeatedly pushes constant-sized batches of input samples and checks that + // the input data is re-chunked correctly. + void RunSimpleRechunkTest(int input_chunk_size) { + const int num_iterations = GetNumPushTestIterations(input_chunk_size); + + int sample_value = 0; + const scoped_ptr<AudioBus> audio_bus = + AudioBus::Create(1, input_chunk_size); + + for (int i = 0; i < num_iterations; ++i) { + EXPECT_EQ(GetExpectedOutputChunks(i * input_chunk_size), results_.size()); + + // Fill audio data with predictable values. + for (int j = 0; j < audio_bus->frames(); ++j) + audio_bus->channel(0)[j] = static_cast<float>(sample_value++); + + fifo_->Push(*audio_bus); + // Note: AudioPushFifo has just called ReceiveAndCheckNextChunk() zero or + // more times. + } + EXPECT_EQ(GetExpectedOutputChunks(num_iterations * input_chunk_size), + results_.size()); + + // Confirm first and last sample values that have been output are the + // expected ones. + ASSERT_FALSE(results_.empty()); + EXPECT_EQ(0.0f, results_.front().first_sample_value); + const float last_value_in_last_chunk = static_cast<float>( + GetExpectedOutputChunks(num_iterations * input_chunk_size) * + output_chunk_size() - + 1); + EXPECT_EQ(last_value_in_last_chunk, results_.back().last_sample_value); + + // Confirm the expected frame delays for the first output chunk (or two). + if (input_chunk_size < output_chunk_size()) { + const int num_queued_before_first_output = + ((output_chunk_size() - 1) / input_chunk_size) * input_chunk_size; + EXPECT_EQ(-num_queued_before_first_output, results_.front().frame_delay); + } else if (input_chunk_size >= output_chunk_size()) { + EXPECT_EQ(0, results_[0].frame_delay); + if (input_chunk_size >= 2 * output_chunk_size()) { + EXPECT_EQ(output_chunk_size(), results_[1].frame_delay); + } else { + const int num_remaining_after_first_output = + input_chunk_size - output_chunk_size(); + EXPECT_EQ(-num_remaining_after_first_output, results_[1].frame_delay); + } + } + + const size_t num_results_before_flush = results_.size(); + fifo_->Flush(); + const size_t num_results_after_flush = results_.size(); + if (num_results_after_flush > num_results_before_flush) { + EXPECT_NE(0, results_.back().frame_delay); + EXPECT_LT(-output_chunk_size(), results_.back().frame_delay); + } + } + + // Returns a "random" integer in the range [begin,end). + int GetRandomInRange(int begin, int end) { + const int len = end - begin; + const int rand_offset = (len == 0) ? 0 : (NextRandomInt() % (end - begin)); + return begin + rand_offset; + } + + scoped_ptr<AudioPushFifo> fifo_; + std::vector<OutputChunkResult> results_; + + private: + // Called by |fifo_| to deliver another chunk of audio. Sanity checks + // the sample values are as expected, and without any dropped/duplicated, and + // adds a result to |results_|. + void ReceiveAndCheckNextChunk(const AudioBus& audio_bus, int frame_delay) { + OutputChunkResult result; + result.num_frames = audio_bus.frames(); + result.first_sample_value = audio_bus.channel(0)[0]; + result.last_sample_value = audio_bus.channel(0)[audio_bus.frames() - 1]; + result.frame_delay = frame_delay; + + // Check that each sample value is the previous sample value plus one. + for (int i = 1; i < audio_bus.frames(); ++i) { + const float expected_value = result.first_sample_value + i; + const float actual_value = audio_bus.channel(0)[i]; + if (actual_value != expected_value) { + if (actual_value == 0.0f) { + // This chunk is probably being emitted by a Flush(). If that's true + // then the frame_delay will be negative and the rest of the + // |audio_bus| should be all zeroes. + ASSERT_GT(0, frame_delay); + for (int j = i + 1; j < audio_bus.frames(); ++j) + ASSERT_EQ(0.0f, audio_bus.channel(0)[j]); + break; + } else { + ASSERT_EQ(expected_value, actual_value) << "Sample at offset " << i + << " is incorrect."; + } + } + } + + results_.push_back(result); + } + + // Note: Not using base::RandInt() because it is horribly slow on debug + // builds. The following is a very simple, deterministic LCG: + int NextRandomInt() { + rand_seed_ = (1103515245 * rand_seed_ + 12345) % (1 << 31); + return static_cast<int>(rand_seed_); + } + + uint32_t rand_seed_ = 0x7e110; + + DISALLOW_COPY_AND_ASSIGN(AudioPushFifoTest); +}; + +// Tests an atypical edge case: Push()ing one frame at a time. +TEST_P(AudioPushFifoTest, PushOneFrameAtATime) { + RunSimpleRechunkTest(1); +} + +// Tests that re-chunking the audio from common platform input chunk sizes +// works. +TEST_P(AudioPushFifoTest, Push128FramesAtATime) { + RunSimpleRechunkTest(128); +} +TEST_P(AudioPushFifoTest, Push512FramesAtATime) { + RunSimpleRechunkTest(512); +} + +// Tests that re-chunking the audio from common "10 ms" input chunk sizes +// works (44100 Hz * 10 ms = 441, and 48000 Hz * 10 ms = 480). +TEST_P(AudioPushFifoTest, Push441FramesAtATime) { + RunSimpleRechunkTest(441); +} +TEST_P(AudioPushFifoTest, Push480FramesAtATime) { + RunSimpleRechunkTest(480); +} + +// Tests that re-chunking when input audio is provided in varying chunk sizes +// works. +TEST_P(AudioPushFifoTest, PushArbitraryNumbersOfFramesAtATime) { + // The loop below will run until both: 1) kMinNumIterations loops have + // occurred; and 2) there are at least 3 entries in |results_|. + const int kMinNumIterations = 30; + + int sample_value = 0; + int frames_pushed_so_far = 0; + for (int i = 0; i < kMinNumIterations || results_.size() < 3; ++i) { + EXPECT_EQ(GetExpectedOutputChunks(frames_pushed_so_far), results_.size()); + + // Create an AudioBus of a random length, populated with sample values. + const int input_chunk_size = GetRandomInRange(1, 1920); + const scoped_ptr<AudioBus> audio_bus = + AudioBus::Create(1, input_chunk_size); + for (int j = 0; j < audio_bus->frames(); ++j) + audio_bus->channel(0)[j] = static_cast<float>(sample_value++); + + fifo_->Push(*audio_bus); + // Note: AudioPushFifo has just called ReceiveAndCheckNextChunk() zero or + // more times. + + frames_pushed_so_far += input_chunk_size; + } + EXPECT_EQ(GetExpectedOutputChunks(frames_pushed_so_far), results_.size()); + + ASSERT_FALSE(results_.empty()); + EXPECT_EQ(0.0f, results_.front().first_sample_value); + const float last_value_in_last_chunk = static_cast<float>( + GetExpectedOutputChunks(frames_pushed_so_far) * output_chunk_size() - 1); + EXPECT_EQ(last_value_in_last_chunk, results_.back().last_sample_value); + + const size_t num_results_before_flush = results_.size(); + fifo_->Flush(); + const size_t num_results_after_flush = results_.size(); + if (num_results_after_flush > num_results_before_flush) { + EXPECT_NE(0, results_.back().frame_delay); + EXPECT_LT(-output_chunk_size(), results_.back().frame_delay); + } +} + +INSTANTIATE_TEST_CASE_P(, + AudioPushFifoTest, + ::testing::Values( + // 1 ms output chunks at common sample rates. + 16, // 16000 Hz + 22, // 22050 Hz + 44, // 44100 Hz + 48, // 48000 Hz + + // 10 ms output chunks at common sample rates. + 160, // 16000 Hz + 220, // 22050 Hz + 441, // 44100 Hz + 480, // 48000 Hz + + // 60 ms output chunks at common sample rates. + 960, // 16000 Hz + 1323, // 22050 Hz + 2646, // 44100 Hz + 2880 // 48000 Hz + )); + +} // namespace + +} // namespace media diff --git a/chromium/media/base/audio_renderer.h b/chromium/media/base/audio_renderer.h index 79438c785bf..11e112f1a1e 100644 --- a/chromium/media/base/audio_renderer.h +++ b/chromium/media/base/audio_renderer.h @@ -9,12 +9,12 @@ #include "base/macros.h" #include "base/time/time.h" #include "media/base/buffering_state.h" -#include "media/base/cdm_context.h" #include "media/base/media_export.h" #include "media/base/pipeline_status.h" namespace media { +class CdmContext; class DemuxerStream; class TimeSource; @@ -29,8 +29,8 @@ class MEDIA_EXPORT AudioRenderer { // completion. If initialization fails, only |init_cb| (not |error_cb|) will // be called. // - // |set_cdm_ready_cb| is fired when a CDM is needed, i.e. when the |stream| is - // encrypted. + // |cdm_context| can be used to handle encrypted streams. May be null if the + // stream is not encrypted. // // |statistics_cb| is executed periodically with audio rendering stats. // @@ -46,7 +46,7 @@ class MEDIA_EXPORT AudioRenderer { virtual void Initialize( DemuxerStream* stream, const PipelineStatusCB& init_cb, - const SetCdmReadyCB& set_cdm_ready_cb, + CdmContext* cdm_context, const StatisticsCB& statistics_cb, const BufferingStateCB& buffering_state_cb, const base::Closure& ended_cb, diff --git a/chromium/media/base/audio_renderer_mixer.cc b/chromium/media/base/audio_renderer_mixer.cc index 218bb9e2c56..d6cca18c40f 100644 --- a/chromium/media/base/audio_renderer_mixer.cc +++ b/chromium/media/base/audio_renderer_mixer.cc @@ -4,6 +4,8 @@ #include "media/base/audio_renderer_mixer.h" +#include <cmath> + #include "base/bind.h" #include "base/bind_helpers.h" #include "base/logging.h" @@ -110,14 +112,14 @@ void AudioRendererMixer::RemoveErrorCallback(const base::Closure& error_cb) { NOTREACHED(); } -OutputDevice* AudioRendererMixer::GetOutputDevice() { +OutputDeviceInfo AudioRendererMixer::GetOutputDeviceInfo() { DVLOG(1) << __FUNCTION__; base::AutoLock auto_lock(lock_); - return audio_sink_->GetOutputDevice(); + return audio_sink_->GetOutputDeviceInfo(); } int AudioRendererMixer::Render(AudioBus* audio_bus, - uint32_t audio_delay_milliseconds, + uint32_t frames_delayed, uint32_t frames_skipped) { base::AutoLock auto_lock(lock_); @@ -132,8 +134,13 @@ int AudioRendererMixer::Render(AudioBus* audio_bus, playing_ = false; } - master_converter_.ConvertWithDelay( - base::TimeDelta::FromMilliseconds(audio_delay_milliseconds), audio_bus); + // TODO(chcunningham): Delete this conversion and change ConvertWith delay to + // expect a count of frames delayed instead of TimeDelta (less precise). + // See http://crbug.com/587522. + base::TimeDelta audio_delay = base::TimeDelta::FromMicroseconds( + std::round(frames_delayed * output_params_.GetMicrosecondsPerFrame())); + + master_converter_.ConvertWithDelay(audio_delay, audio_bus); return audio_bus->frames(); } diff --git a/chromium/media/base/audio_renderer_mixer.h b/chromium/media/base/audio_renderer_mixer.h index dae8b7cd5e5..9c712105d98 100644 --- a/chromium/media/base/audio_renderer_mixer.h +++ b/chromium/media/base/audio_renderer_mixer.h @@ -46,11 +46,7 @@ class MEDIA_EXPORT AudioRendererMixer pause_delay_ = delay; } - // TODO(guidou): remove this method. The output device of a mixer should - // never be switched, as it may result in a discrepancy between the output - // parameters of the new device and the output parameters with which the - // mixer was initialized. See crbug.com/506507 - OutputDevice* GetOutputDevice(); + OutputDeviceInfo GetOutputDeviceInfo(); private: // Maps input sample rate to the dedicated converter. @@ -58,7 +54,7 @@ class MEDIA_EXPORT AudioRendererMixer // AudioRendererSink::RenderCallback implementation. int Render(AudioBus* audio_bus, - uint32_t audio_delay_milliseconds, + uint32_t frames_delayed, uint32_t frames_skipped) override; void OnRenderError() override; diff --git a/chromium/media/base/audio_renderer_mixer_input.cc b/chromium/media/base/audio_renderer_mixer_input.cc index d0d38d3b8ee..55dbfc8404f 100644 --- a/chromium/media/base/audio_renderer_mixer_input.cc +++ b/chromium/media/base/audio_renderer_mixer_input.cc @@ -4,6 +4,8 @@ #include "media/base/audio_renderer_mixer_input.h" +#include <cmath> + #include "base/bind.h" #include "base/callback_helpers.h" #include "media/base/audio_renderer_mixer.h" @@ -15,36 +17,40 @@ AudioRendererMixerInput::AudioRendererMixerInput( const RemoveMixerCB& remove_mixer_cb, const std::string& device_id, const url::Origin& security_origin) - : initialized_(false), + : started_(false), playing_(false), volume_(1.0f), get_mixer_cb_(get_mixer_cb), remove_mixer_cb_(remove_mixer_cb), device_id_(device_id), security_origin_(security_origin), - mixer_(NULL), - callback_(NULL), + mixer_(nullptr), + callback_(nullptr), error_cb_(base::Bind(&AudioRendererMixerInput::OnRenderError, base::Unretained(this))) {} AudioRendererMixerInput::~AudioRendererMixerInput() { + DCHECK(!started_); DCHECK(!mixer_); } void AudioRendererMixerInput::Initialize( const AudioParameters& params, AudioRendererSink::RenderCallback* callback) { + DCHECK(!started_); DCHECK(!mixer_); DCHECK(callback); params_ = params; callback_ = callback; - initialized_ = true; } void AudioRendererMixerInput::Start() { - DCHECK(initialized_); + DCHECK(!started_); DCHECK(!mixer_); + DCHECK(callback_); // Initialized. + + started_ = true; mixer_ = get_mixer_cb_.Run(params_, device_id_, security_origin_, nullptr); if (!mixer_) { callback_->OnRenderError(); @@ -64,10 +70,7 @@ void AudioRendererMixerInput::Start() { void AudioRendererMixerInput::Stop() { // Stop() may be called at any time, if Pause() hasn't been called we need to // remove our mixer input before shutdown. - if (playing_) { - mixer_->RemoveMixerInput(params_, this); - playing_ = false; - } + Pause(); if (mixer_) { // TODO(dalecurtis): This is required so that |callback_| isn't called after @@ -75,9 +78,11 @@ void AudioRendererMixerInput::Stop() { // should instead have sane ownership semantics: http://crbug.com/151051 mixer_->RemoveErrorCallback(error_cb_); remove_mixer_cb_.Run(params_, device_id_, security_origin_); - mixer_ = NULL; + mixer_ = nullptr; } + started_ = false; + if (!pending_switch_callback_.is_null()) { base::ResetAndReturn(&pending_switch_callback_) .Run(OUTPUT_DEVICE_STATUS_ERROR_INTERNAL); @@ -101,18 +106,19 @@ void AudioRendererMixerInput::Pause() { } bool AudioRendererMixerInput::SetVolume(double volume) { + base::AutoLock auto_lock(volume_lock_); volume_ = volume; return true; } -OutputDevice* AudioRendererMixerInput::GetOutputDevice() { - return this; +OutputDeviceInfo AudioRendererMixerInput::GetOutputDeviceInfo() { + return mixer_ ? mixer_->GetOutputDeviceInfo() : OutputDeviceInfo(); } void AudioRendererMixerInput::SwitchOutputDevice( const std::string& device_id, const url::Origin& security_origin, - const SwitchOutputDeviceCB& callback) { + const OutputDeviceStatusCB& callback) { if (!mixer_) { if (pending_switch_callback_.is_null()) { pending_switch_callback_ = callback; @@ -145,6 +151,7 @@ void AudioRendererMixerInput::SwitchOutputDevice( security_origin_ = security_origin; mixer_ = new_mixer; mixer_->AddErrorCallback(error_cb_); + started_ = true; if (was_playing) Play(); @@ -152,21 +159,15 @@ void AudioRendererMixerInput::SwitchOutputDevice( callback.Run(OUTPUT_DEVICE_STATUS_OK); } -AudioParameters AudioRendererMixerInput::GetOutputParameters() { - return mixer_->GetOutputDevice()->GetOutputParameters(); -} - -OutputDeviceStatus AudioRendererMixerInput::GetDeviceStatus() { - if (!mixer_) - return OUTPUT_DEVICE_STATUS_ERROR_INTERNAL; - - return mixer_->GetOutputDevice()->GetDeviceStatus(); -} - double AudioRendererMixerInput::ProvideInput(AudioBus* audio_bus, base::TimeDelta buffer_delay) { - int frames_filled = callback_->Render( - audio_bus, static_cast<int>(buffer_delay.InMillisecondsF() + 0.5), 0); + // TODO(chcunningham): Delete this conversion and change ProvideInput to more + // precisely describe delay as a count of frames delayed instead of TimeDelta. + // See http://crbug.com/587522. + uint32_t frames_delayed = std::round(buffer_delay.InMicroseconds() / + params_.GetMicrosecondsPerFrame()); + + int frames_filled = callback_->Render(audio_bus, frames_delayed, 0); // AudioConverter expects unfilled frames to be zeroed. if (frames_filled < audio_bus->frames()) { @@ -174,7 +175,13 @@ double AudioRendererMixerInput::ProvideInput(AudioBus* audio_bus, frames_filled, audio_bus->frames() - frames_filled); } - return frames_filled > 0 ? volume_ : 0; + // We're reading |volume_| from the audio device thread and must avoid racing + // with the main/media thread calls to SetVolume(). See thread safety comment + // in the header file. + { + base::AutoLock auto_lock(volume_lock_); + return frames_filled > 0 ? volume_ : 0; + } } void AudioRendererMixerInput::OnRenderError() { diff --git a/chromium/media/base/audio_renderer_mixer_input.h b/chromium/media/base/audio_renderer_mixer_input.h index 50f54bdd436..a75320e78c6 100644 --- a/chromium/media/base/audio_renderer_mixer_input.h +++ b/chromium/media/base/audio_renderer_mixer_input.h @@ -1,6 +1,17 @@ // 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. +// +// THREAD SAFETY +// +// This class is generally not thread safe. Callers should ensure thread safety. +// For instance, the |sink_lock_| in WebAudioSourceProvider synchronizes access +// to this object across the main thread (for WebAudio APIs) and the +// media thread (for HTMLMediaElement APIs). +// +// The one exception is protection for |volume_| via |volume_lock_|. This lock +// prevents races between SetVolume() (called on any thread) and ProvideInput +// (called on audio device thread). See http://crbug.com/588992. #ifndef MEDIA_BASE_AUDIO_RENDERER_MIXER_INPUT_H_ #define MEDIA_BASE_AUDIO_RENDERER_MIXER_INPUT_H_ @@ -9,6 +20,7 @@ #include "base/callback.h" #include "base/macros.h" +#include "base/synchronization/lock.h" #include "media/base/audio_converter.h" #include "media/base/audio_renderer_sink.h" #include "url/origin.h" @@ -18,8 +30,7 @@ namespace media { class AudioRendererMixer; class MEDIA_EXPORT AudioRendererMixerInput - : NON_EXPORTED_BASE(public RestartableAudioRendererSink), - NON_EXPORTED_BASE(public OutputDevice), + : NON_EXPORTED_BASE(public SwitchableAudioRendererSink), public AudioConverter::InputCallback { public: typedef base::Callback<AudioRendererMixer*(const AudioParameters& params, @@ -37,22 +48,18 @@ class MEDIA_EXPORT AudioRendererMixerInput const std::string& device_id, const url::Origin& security_origin); - // RestartableAudioRendererSink implementation. + // SwitchableAudioRendererSink implementation. void Start() override; void Stop() override; void Play() override; void Pause() override; bool SetVolume(double volume) override; - OutputDevice* GetOutputDevice() override; + OutputDeviceInfo GetOutputDeviceInfo() override; void Initialize(const AudioParameters& params, AudioRendererSink::RenderCallback* renderer) override; - - // OutputDevice implementation. void SwitchOutputDevice(const std::string& device_id, const url::Origin& security_origin, - const SwitchOutputDeviceCB& callback) override; - AudioParameters GetOutputParameters() override; - OutputDeviceStatus GetDeviceStatus() override; + const OutputDeviceStatusCB& callback) override; // Called by AudioRendererMixer when an error occurs. void OnRenderError(); @@ -63,7 +70,11 @@ class MEDIA_EXPORT AudioRendererMixerInput private: friend class AudioRendererMixerInputTest; - bool initialized_; + // Protect |volume_|, accessed by separate threads in ProvideInput() and + // SetVolume(). + base::Lock volume_lock_; + + bool started_; bool playing_; double volume_; @@ -95,7 +106,7 @@ class MEDIA_EXPORT AudioRendererMixerInput // Pending switch-device callback, in case SwitchOutputDevice() is invoked // before Start() - SwitchOutputDeviceCB pending_switch_callback_; + OutputDeviceStatusCB pending_switch_callback_; std::string pending_switch_device_id_; url::Origin pending_switch_security_origin_; diff --git a/chromium/media/base/audio_renderer_mixer_unittest.cc b/chromium/media/base/audio_renderer_mixer_unittest.cc index fb81c0f145a..f338a696031 100644 --- a/chromium/media/base/audio_renderer_mixer_unittest.cc +++ b/chromium/media/base/audio_renderer_mixer_unittest.cc @@ -464,7 +464,7 @@ TEST_P(AudioRendererMixerBehavioralTest, OnRenderErrorPausedInput) { // not call RemoveMixer(). TEST_P(AudioRendererMixerBehavioralTest, NoInitialize) { EXPECT_CALL(*this, RemoveMixer(testing::_, testing::_, testing::_)).Times(0); - scoped_refptr<AudioRendererMixerInput> audio_renderer_mixer = + scoped_refptr<AudioRendererMixerInput> audio_renderer_mixer_input = new AudioRendererMixerInput( base::Bind(&AudioRendererMixerTest::GetMixer, base::Unretained(this)), base::Bind(&AudioRendererMixerTest::RemoveMixer, diff --git a/chromium/media/base/audio_renderer_sink.h b/chromium/media/base/audio_renderer_sink.h index 9da243b7c55..01be1017aeb 100644 --- a/chromium/media/base/audio_renderer_sink.h +++ b/chromium/media/base/audio_renderer_sink.h @@ -8,21 +8,13 @@ #include <stdint.h> #include <string> -#include <vector> #include "base/callback.h" -#include "base/logging.h" #include "base/memory/ref_counted.h" -#include "media/audio/audio_output_ipc.h" #include "media/audio/audio_parameters.h" #include "media/base/audio_bus.h" -#include "media/base/media_export.h" -#include "media/base/output_device.h" -#include "url/gurl.h" - -namespace base { -class SingleThreadTaskRunner; -} +#include "media/base/output_device_info.h" +#include "url/origin.h" namespace media { @@ -39,7 +31,7 @@ class AudioRendererSink // number of frames filled. |frames_skipped| contains the number of frames // the consumer has skipped, if any. virtual int Render(AudioBus* dest, - uint32_t audio_delay_milliseconds, + uint32_t frames_delayed, uint32_t frames_skipped) = 0; // Signals an error has occurred. @@ -71,12 +63,12 @@ class AudioRendererSink // Returns |true| on success. virtual bool SetVolume(double volume) = 0; - // Returns a pointer to the internal output device. - // This pointer is not to be owned by the caller and is valid only during - // the lifetime of the AudioRendererSink. - // It can be null, which means that access to the output device is not - // supported. - virtual OutputDevice* GetOutputDevice() = 0; + // Returns current output device information. If the information is not + // available yet, this method may block until it becomes available. + // If the sink is not associated with any output device, |device_status| of + // OutputDeviceInfo should be set to OUTPUT_DEVICE_STATUS_ERROR_INTERNAL. + // Must never be called on the IO thread. + virtual OutputDeviceInfo GetOutputDeviceInfo() = 0; protected: friend class base::RefCountedThreadSafe<AudioRendererSink>; @@ -93,6 +85,21 @@ class RestartableAudioRendererSink : public AudioRendererSink { ~RestartableAudioRendererSink() override {} }; +class SwitchableAudioRendererSink : public RestartableAudioRendererSink { + public: + // Attempts to switch the audio output device associated with a sink. + // Once the attempt is finished, |callback| is invoked with the + // result of the operation passed as a parameter. The result is a value from + // the media::OutputDeviceStatus enum. + // There is no guarantee about the thread where |callback| will be invoked. + virtual void SwitchOutputDevice(const std::string& device_id, + const url::Origin& security_origin, + const OutputDeviceStatusCB& callback) = 0; + + protected: + ~SwitchableAudioRendererSink() override {} +}; + } // namespace media #endif // MEDIA_BASE_AUDIO_RENDERER_SINK_H_ diff --git a/chromium/media/base/audio_shifter.cc b/chromium/media/base/audio_shifter.cc index f5aee2c0e6b..6a268d3f633 100644 --- a/chromium/media/base/audio_shifter.cc +++ b/chromium/media/base/audio_shifter.cc @@ -82,27 +82,32 @@ AudioShifter::AudioQueueEntry::AudioQueueEntry( audio(audio_.release()) { } +AudioShifter::AudioQueueEntry::AudioQueueEntry(const AudioQueueEntry& other) = + default; + AudioShifter::AudioQueueEntry::~AudioQueueEntry() {} AudioShifter::AudioShifter(base::TimeDelta max_buffer_size, base::TimeDelta clock_accuracy, base::TimeDelta adjustment_time, - size_t rate, - int channels) : - max_buffer_size_(max_buffer_size), - clock_accuracy_(clock_accuracy), - adjustment_time_(adjustment_time), - rate_(rate), - input_clock_smoother_(new ClockSmoother(clock_accuracy)), - output_clock_smoother_(new ClockSmoother(clock_accuracy)), - running_(false), - position_(0), - previous_requested_samples_(0), - resampler_(channels, 1.0, 96, - base::Bind(&AudioShifter::ResamplerCallback, - base::Unretained(this))), - current_ratio_(1.0) { -} + int rate, + int channels) + : max_buffer_size_(max_buffer_size), + clock_accuracy_(clock_accuracy), + adjustment_time_(adjustment_time), + rate_(rate), + channels_(channels), + input_clock_smoother_(new ClockSmoother(clock_accuracy)), + output_clock_smoother_(new ClockSmoother(clock_accuracy)), + running_(false), + position_(0), + previous_requested_samples_(0), + resampler_( + channels, + 1.0, + 96, + base::Bind(&AudioShifter::ResamplerCallback, base::Unretained(this))), + current_ratio_(1.0) {} AudioShifter::~AudioShifter() {} @@ -270,15 +275,6 @@ void AudioShifter::ResamplerCallback(int frame_delay, AudioBus* destination) { } } -void AudioShifter::Flush() { - resampler_.Flush(); - position_ = 0; - queue_.clear(); - running_ = false; - previous_playout_time_ = base::TimeTicks(); - bias_ = base::TimeDelta(); -} - void AudioShifter::Zero(AudioBus* output) { output->Zero(); running_ = false; diff --git a/chromium/media/base/audio_shifter.h b/chromium/media/base/audio_shifter.h index e7bc502af3e..3429a2f197d 100644 --- a/chromium/media/base/audio_shifter.h +++ b/chromium/media/base/audio_shifter.h @@ -56,10 +56,13 @@ class MEDIA_EXPORT AudioShifter { AudioShifter(base::TimeDelta max_buffer_size, base::TimeDelta clock_accuracy, base::TimeDelta adjustment_time, - size_t rate, + int rate, int channels); ~AudioShifter(); + int sample_rate() const { return rate_; } + int channels() const { return channels_; } + // Push Audio into the shifter. All inputs must have the same number of // channels, but bus size can vary. The playout time can be noisy and // does not have to line up perfectly with the number of samples pushed @@ -79,9 +82,6 @@ class MEDIA_EXPORT AudioShifter { // calculate playout_time would be now + audio pipeline delay. void Pull(AudioBus* output, base::TimeTicks playout_time); - // Flush audio (but leave timing info) - void Flush(); - private: void Zero(AudioBus* output); void ResamplerCallback(int frame_delay, AudioBus* destination); @@ -89,6 +89,7 @@ private: struct AudioQueueEntry { AudioQueueEntry(base::TimeTicks target_playout_time_, scoped_ptr<AudioBus> audio_); + AudioQueueEntry(const AudioQueueEntry& other); ~AudioQueueEntry(); base::TimeTicks target_playout_time; linked_ptr<AudioBus> audio; @@ -100,7 +101,8 @@ private: const base::TimeDelta max_buffer_size_; const base::TimeDelta clock_accuracy_; const base::TimeDelta adjustment_time_; - const size_t rate_; + const int rate_; + const int channels_; // The clock smoothers are used to smooth out timestamps // and adjust for drift and inaccurate clocks. diff --git a/chromium/media/base/audio_video_metadata_extractor.cc b/chromium/media/base/audio_video_metadata_extractor.cc index 0ba36e12062..5b92c1511c1 100644 --- a/chromium/media/base/audio_video_metadata_extractor.cc +++ b/chromium/media/base/audio_video_metadata_extractor.cc @@ -54,6 +54,9 @@ const int kAttachedImageSizeLimit = 4 * 1024 * 1024; AudioVideoMetadataExtractor::StreamInfo::StreamInfo() {} +AudioVideoMetadataExtractor::StreamInfo::StreamInfo(const StreamInfo& other) = + default; + AudioVideoMetadataExtractor::StreamInfo::~StreamInfo() {} AudioVideoMetadataExtractor::AudioVideoMetadataExtractor() diff --git a/chromium/media/base/audio_video_metadata_extractor.h b/chromium/media/base/audio_video_metadata_extractor.h index 97f7a5b7e89..4e5906dbd1a 100644 --- a/chromium/media/base/audio_video_metadata_extractor.h +++ b/chromium/media/base/audio_video_metadata_extractor.h @@ -26,6 +26,7 @@ class MEDIA_EXPORT AudioVideoMetadataExtractor { struct StreamInfo { StreamInfo(); + StreamInfo(const StreamInfo& other); ~StreamInfo(); std::string type; TagDictionary tags; diff --git a/chromium/media/base/audio_video_metadata_extractor_unittest.cc b/chromium/media/base/audio_video_metadata_extractor_unittest.cc index d67808f16eb..744d3ebd96a 100644 --- a/chromium/media/base/audio_video_metadata_extractor_unittest.cc +++ b/chromium/media/base/audio_video_metadata_extractor_unittest.cc @@ -39,6 +39,13 @@ scoped_ptr<AudioVideoMetadataExtractor> GetExtractor( return extractor; } +const std::string GetTagValue( + const media::AudioVideoMetadataExtractor::TagDictionary& tags, + const char* tag_name) { + auto tag_data = tags.find(tag_name); + return tag_data == tags.end() ? "" : tag_data->second; +} + TEST(AudioVideoMetadataExtractorTest, InvalidFile) { GetExtractor("ten_byte_file", true, false, 0, -1, -1); } @@ -56,7 +63,7 @@ TEST(AudioVideoMetadataExtractorTest, AudioOGG) { EXPECT_EQ(1u, extractor->stream_infos()[1].tags.size()); EXPECT_EQ("vorbis", extractor->stream_infos()[1].type); EXPECT_EQ("Processed by SoX", - extractor->stream_infos()[1].tags.find("COMMENT")->second); + GetTagValue(extractor->stream_infos()[1].tags, "COMMENT")); EXPECT_EQ(0u, extractor->attached_images_bytes().size()); } @@ -72,9 +79,9 @@ TEST(AudioVideoMetadataExtractorTest, AudioWAV) { EXPECT_EQ(2u, extractor->stream_infos()[0].tags.size()); EXPECT_EQ("Lavf54.37.100", - extractor->stream_infos()[0].tags.find("encoder")->second); + GetTagValue(extractor->stream_infos()[0].tags, "encoder")); EXPECT_EQ("Amadeus Pro", - extractor->stream_infos()[0].tags.find("encoded_by")->second); + GetTagValue(extractor->stream_infos()[0].tags, "encoded_by")); EXPECT_EQ("pcm_u8", extractor->stream_infos()[1].type); EXPECT_EQ(0u, extractor->stream_infos()[1].tags.size()); @@ -92,7 +99,7 @@ TEST(AudioVideoMetadataExtractorTest, VideoWebM) { EXPECT_EQ("matroska,webm", extractor->stream_infos()[0].type); EXPECT_EQ(1u, extractor->stream_infos()[0].tags.size()); EXPECT_EQ("Lavf53.9.0", - extractor->stream_infos()[0].tags.find("ENCODER")->second); + GetTagValue(extractor->stream_infos()[0].tags, "ENCODER")); EXPECT_EQ("vp8", extractor->stream_infos()[1].type); EXPECT_EQ(0u, extractor->stream_infos()[1].tags.size()); @@ -109,7 +116,7 @@ TEST(AudioVideoMetadataExtractorTest, VideoWebM) { EXPECT_EQ("pcm_s16le", extractor->stream_infos()[5].type); EXPECT_EQ(1u, extractor->stream_infos()[5].tags.size()); EXPECT_EQ("Lavc52.32.0", - extractor->stream_infos()[5].tags.find("ENCODER")->second); + GetTagValue(extractor->stream_infos()[5].tags, "ENCODER")); EXPECT_EQ(0u, extractor->attached_images_bytes().size()); } @@ -125,33 +132,31 @@ TEST(AudioVideoMetadataExtractorTest, AndroidRotatedMP4Video) { EXPECT_EQ("mov,mp4,m4a,3gp,3g2,mj2", extractor->stream_infos()[0].type); EXPECT_EQ(4u, extractor->stream_infos()[0].tags.size()); - EXPECT_EQ( - "isom3gp4", - extractor->stream_infos()[0].tags.find("compatible_brands")->second); - EXPECT_EQ( - "2014-02-11 00:39:25", - extractor->stream_infos()[0].tags.find("creation_time")->second); + EXPECT_EQ("isom3gp4", GetTagValue(extractor->stream_infos()[0].tags, + "compatible_brands")); + EXPECT_EQ("2014-02-11 00:39:25", + GetTagValue(extractor->stream_infos()[0].tags, "creation_time")); EXPECT_EQ("isom", - extractor->stream_infos()[0].tags.find("major_brand")->second); + GetTagValue(extractor->stream_infos()[0].tags, "major_brand")); EXPECT_EQ("0", - extractor->stream_infos()[0].tags.find("minor_version")->second); + GetTagValue(extractor->stream_infos()[0].tags, "minor_version")); EXPECT_EQ("h264", extractor->stream_infos()[1].type); EXPECT_EQ(5u, extractor->stream_infos()[1].tags.size()); EXPECT_EQ("2014-02-11 00:39:25", - extractor->stream_infos()[1].tags.find("creation_time")->second); + GetTagValue(extractor->stream_infos()[1].tags, "creation_time")); EXPECT_EQ("VideoHandle", - extractor->stream_infos()[1].tags.find("handler_name")->second); - EXPECT_EQ("eng", extractor->stream_infos()[1].tags.find("language")->second); - EXPECT_EQ("90", extractor->stream_infos()[1].tags.find("rotate")->second); + GetTagValue(extractor->stream_infos()[1].tags, "handler_name")); + EXPECT_EQ("eng", GetTagValue(extractor->stream_infos()[1].tags, "language")); + EXPECT_EQ("90", GetTagValue(extractor->stream_infos()[1].tags, "rotate")); EXPECT_EQ("aac", extractor->stream_infos()[2].type); EXPECT_EQ(3u, extractor->stream_infos()[2].tags.size()); EXPECT_EQ("2014-02-11 00:39:25", - extractor->stream_infos()[2].tags.find("creation_time")->second); + GetTagValue(extractor->stream_infos()[2].tags, "creation_time")); EXPECT_EQ("SoundHandle", - extractor->stream_infos()[2].tags.find("handler_name")->second); - EXPECT_EQ("eng", extractor->stream_infos()[2].tags.find("language")->second); + GetTagValue(extractor->stream_infos()[2].tags, "handler_name")); + EXPECT_EQ("eng", GetTagValue(extractor->stream_infos()[2].tags, "language")); EXPECT_EQ(0u, extractor->attached_images_bytes().size()); } @@ -173,23 +178,23 @@ TEST(AudioVideoMetadataExtractorTest, AudioMP3) { EXPECT_EQ("mp3", extractor->stream_infos()[0].type); EXPECT_EQ(7u, extractor->stream_infos()[0].tags.size()); EXPECT_EQ("OK Computer", - extractor->stream_infos()[0].tags.find("album")->second); + GetTagValue(extractor->stream_infos()[0].tags, "album")); EXPECT_EQ("Radiohead", - extractor->stream_infos()[0].tags.find("artist")->second); - EXPECT_EQ("1997", extractor->stream_infos()[0].tags.find("date")->second); + GetTagValue(extractor->stream_infos()[0].tags, "artist")); + EXPECT_EQ("1997", GetTagValue(extractor->stream_infos()[0].tags, "date")); EXPECT_EQ("Lavf54.4.100", - extractor->stream_infos()[0].tags.find("encoder")->second); + GetTagValue(extractor->stream_infos()[0].tags, "encoder")); EXPECT_EQ("Alternative", - extractor->stream_infos()[0].tags.find("genre")->second); - EXPECT_EQ("Airbag", extractor->stream_infos()[0].tags.find("title")->second); - EXPECT_EQ("1", extractor->stream_infos()[0].tags.find("track")->second); + GetTagValue(extractor->stream_infos()[0].tags, "genre")); + EXPECT_EQ("Airbag", GetTagValue(extractor->stream_infos()[0].tags, "title")); + EXPECT_EQ("1", GetTagValue(extractor->stream_infos()[0].tags, "track")); EXPECT_EQ("mp3", extractor->stream_infos()[1].type); EXPECT_EQ(0u, extractor->stream_infos()[1].tags.size()); EXPECT_EQ("png", extractor->stream_infos()[2].type); EXPECT_EQ(1u, extractor->stream_infos()[2].tags.size()); - EXPECT_EQ("Other", extractor->stream_infos()[2].tags.find("comment")->second); + EXPECT_EQ("Other", GetTagValue(extractor->stream_infos()[2].tags, "comment")); EXPECT_EQ(1u, extractor->attached_images_bytes().size()); EXPECT_EQ(155752u, extractor->attached_images_bytes()[0].size()); diff --git a/chromium/media/base/bind_to_current_loop.h b/chromium/media/base/bind_to_current_loop.h index da568ed6ac5..8c14e9e88cd 100644 --- a/chromium/media/base/bind_to_current_loop.h +++ b/chromium/media/base/bind_to_current_loop.h @@ -7,6 +7,7 @@ #include "base/bind.h" #include "base/location.h" +#include "base/memory/scoped_ptr.h" #include "base/memory/scoped_vector.h" #include "base/single_thread_task_runner.h" #include "base/thread_task_runner_handle.h" @@ -26,7 +27,7 @@ namespace media { -// Mimic base::internal::CallbackForward, replacing p.Pass() with +// Mimic base::internal::CallbackForward, replacing std::move(p) with // base::Passed(&p) to account for the extra layer of indirection. namespace internal { template <typename T> diff --git a/chromium/media/base/bind_to_current_loop_unittest.cc b/chromium/media/base/bind_to_current_loop_unittest.cc index 7fb56d48cdb..ba981d0e15b 100644 --- a/chromium/media/base/bind_to_current_loop_unittest.cc +++ b/chromium/media/base/bind_to_current_loop_unittest.cc @@ -6,6 +6,7 @@ #include <utility> +#include "base/memory/free_deleter.h" #include "base/message_loop/message_loop.h" #include "base/synchronization/waitable_event.h" #include "testing/gtest/include/gtest/gtest.h" @@ -23,7 +24,7 @@ void BoundBoolSetFromScopedPtr(bool* var, scoped_ptr<bool> val) { void BoundBoolSetFromScopedPtrFreeDeleter( bool* var, scoped_ptr<bool, base::FreeDeleter> val) { - *var = val; + *var = *val; } void BoundBoolSetFromScopedArray(bool* var, scoped_ptr<bool[]> val) { diff --git a/chromium/media/base/bit_reader_core.cc b/chromium/media/base/bit_reader_core.cc index 32d2d530121..237470c7669 100644 --- a/chromium/media/base/bit_reader_core.cc +++ b/chromium/media/base/bit_reader_core.cc @@ -86,8 +86,11 @@ bool BitReaderCore::SkipBits(int num_bits) { byte_stream_provider_->GetBytes(nbytes, &byte_stream_window); DCHECK_GE(window_size, 0); DCHECK_LE(window_size, nbytes); - if (window_size < nbytes) + if (window_size < nbytes) { + // Note that some bytes were consumed. + bits_read_ += 8 * window_size; return false; + } num_bits -= 8 * nbytes; bits_read_ += 8 * nbytes; } diff --git a/chromium/media/base/bit_reader_fuzzertest.cc b/chromium/media/base/bit_reader_fuzzertest.cc new file mode 100644 index 00000000000..762a9b63818 --- /dev/null +++ b/chromium/media/base/bit_reader_fuzzertest.cc @@ -0,0 +1,37 @@ +// Copyright 2016 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 <stddef.h> +#include <stdint.h> + +#include "base/hash.h" +#include "base/numerics/safe_conversions.h" +#include "media/base/bit_reader.h" +#include "media/base/test_random.h" + +// Entry point for LibFuzzer. +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + media::BitReader reader(data, base::checked_cast<int>(size)); + + // Need a simple random number generator to generate the number of bits to + // read/skip in a reproducible way (given the same |data|). Using Hash() to + // ensure the seed varies significantly over minor changes in |data|. + media::TestRandom rnd(base::Hash(reinterpret_cast<const char*>(data), size)); + + // Read and skip through the data in |reader|. + while (reader.bits_available() > 0) { + if (rnd.Rand() & 1) { + // Read up to 64 bits. This may fail if there is not enough bits + // remaining, but it doesn't matter (testing for failures is also good). + uint64_t value; + if (!reader.ReadBits(rnd.Rand() % 64 + 1, &value)) + break; + } else { + // Skip up to 128 bits. As above, this may fail. + if (!reader.SkipBits(rnd.Rand() % 128 + 1)) + break; + } + } + return 0; +} diff --git a/chromium/media/base/bitstream_buffer.cc b/chromium/media/base/bitstream_buffer.cc index 49caf5b8215..5a1907db25d 100644 --- a/chromium/media/base/bitstream_buffer.cc +++ b/chromium/media/base/bitstream_buffer.cc @@ -6,23 +6,22 @@ namespace media { -BitstreamBuffer::BitstreamBuffer(int32_t id, - base::SharedMemoryHandle handle, - size_t size) - : id_(id), - handle_(handle), - size_(size), - presentation_timestamp_(kNoTimestamp()) {} +BitstreamBuffer::BitstreamBuffer() + : BitstreamBuffer(-1, base::SharedMemoryHandle(), 0) {} BitstreamBuffer::BitstreamBuffer(int32_t id, base::SharedMemoryHandle handle, size_t size, + off_t offset, base::TimeDelta presentation_timestamp) : id_(id), handle_(handle), size_(size), + offset_(offset), presentation_timestamp_(presentation_timestamp) {} +BitstreamBuffer::BitstreamBuffer(const BitstreamBuffer& other) = default; + BitstreamBuffer::~BitstreamBuffer() {} void BitstreamBuffer::SetDecryptConfig(const DecryptConfig& decrypt_config) { diff --git a/chromium/media/base/bitstream_buffer.h b/chromium/media/base/bitstream_buffer.h index fe3c8da358a..000836eaa85 100644 --- a/chromium/media/base/bitstream_buffer.h +++ b/chromium/media/base/bitstream_buffer.h @@ -15,18 +15,32 @@ #include "media/base/media_export.h" #include "media/base/timestamp_constants.h" +namespace IPC { +template <class P> +struct ParamTraits; +} + namespace media { // Class for passing bitstream buffers around. Does not take ownership of the // data. This is the media-namespace equivalent of PP_VideoBitstreamBuffer_Dev. class MEDIA_EXPORT BitstreamBuffer { public: - BitstreamBuffer(int32_t id, base::SharedMemoryHandle handle, size_t size); - + BitstreamBuffer(); + + // Constructs a new BitstreamBuffer. The content of the bitstream is located + // at |offset| bytes away from the start of the shared memory and the payload + // is |size| bytes. When not provided, the default value for |offset| is 0. + // |presentation_timestamp| is when the decoded frame should be displayed. + // When not provided, |presentation_timestamp| will be + // |media::kNoTimestamp()|. BitstreamBuffer(int32_t id, base::SharedMemoryHandle handle, size_t size, - base::TimeDelta presentation_timestamp); + off_t offset = 0, + base::TimeDelta presentation_timestamp = kNoTimestamp()); + + BitstreamBuffer(const BitstreamBuffer& other); ~BitstreamBuffer(); @@ -34,13 +48,21 @@ class MEDIA_EXPORT BitstreamBuffer { int32_t id() const { return id_; } base::SharedMemoryHandle handle() const { return handle_; } + + // The number of bytes of the actual bitstream data. It is the size of the + // content instead of the whole shared memory. size_t size() const { return size_; } + // The offset to the start of actual bitstream data in the shared memory. + off_t offset() const { return offset_; } + // The timestamp is only valid if it's not equal to |media::kNoTimestamp()|. base::TimeDelta presentation_timestamp() const { return presentation_timestamp_; } + void set_handle(const base::SharedMemoryHandle& handle) { handle_ = handle; } + // The following methods come from DecryptConfig. const std::string& key_id() const { return key_id_; } @@ -51,6 +73,7 @@ class MEDIA_EXPORT BitstreamBuffer { int32_t id_; base::SharedMemoryHandle handle_; size_t size_; + off_t offset_; // This is only set when necessary. For example, AndroidVideoDecodeAccelerator // needs the timestamp because the underlying decoder may require it to @@ -65,6 +88,8 @@ class MEDIA_EXPORT BitstreamBuffer { std::string iv_; // initialization vector std::vector<SubsampleEntry> subsamples_; // clear/cypher sizes + friend struct IPC::ParamTraits<media::BitstreamBuffer>; + // Allow compiler-generated copy & assign constructors. }; diff --git a/chromium/media/base/cdm_callback_promise.cc b/chromium/media/base/cdm_callback_promise.cc index 496ad1aba76..21a3412f7eb 100644 --- a/chromium/media/base/cdm_callback_promise.cc +++ b/chromium/media/base/cdm_callback_promise.cc @@ -4,6 +4,7 @@ #include "media/base/cdm_callback_promise.h" +#include "base/callback_helpers.h" #include "base/logging.h" namespace media { @@ -19,12 +20,17 @@ CdmCallbackPromise<T...>::CdmCallbackPromise( template <typename... T> CdmCallbackPromise<T...>::~CdmCallbackPromise() { + if (IsPromiseSettled()) + return; + + DCHECK(!resolve_cb_.is_null() && !reject_cb_.is_null()); + RejectPromiseOnDestruction(); } template <typename... T> void CdmCallbackPromise<T...>::resolve(const T&... result) { MarkPromiseSettled(); - resolve_cb_.Run(result...); + base::ResetAndReturn(&resolve_cb_).Run(result...); } template <typename... T> @@ -32,7 +38,8 @@ void CdmCallbackPromise<T...>::reject(MediaKeys::Exception exception_code, uint32_t system_code, const std::string& error_message) { MarkPromiseSettled(); - reject_cb_.Run(exception_code, system_code, error_message); + base::ResetAndReturn(&reject_cb_) + .Run(exception_code, system_code, error_message); } // Explicit template instantiation for the Promises needed. diff --git a/chromium/media/base/cdm_callback_promise.h b/chromium/media/base/cdm_callback_promise.h index b271a79bb35..3ddc34043fc 100644 --- a/chromium/media/base/cdm_callback_promise.h +++ b/chromium/media/base/cdm_callback_promise.h @@ -36,7 +36,9 @@ class MEDIA_EXPORT CdmCallbackPromise : public CdmPromiseTemplate<T...> { const std::string& error_message) override; private: + using CdmPromiseTemplate<T...>::IsPromiseSettled; using CdmPromiseTemplate<T...>::MarkPromiseSettled; + using CdmPromiseTemplate<T...>::RejectPromiseOnDestruction; base::Callback<void(const T&...)> resolve_cb_; PromiseRejectedCB reject_cb_; diff --git a/chromium/media/base/cdm_context.cc b/chromium/media/base/cdm_context.cc index b53fc7d3a54..5bba924e7e0 100644 --- a/chromium/media/base/cdm_context.cc +++ b/chromium/media/base/cdm_context.cc @@ -6,19 +6,10 @@ namespace media { -CdmContext::CdmContext() { -} +CdmContext::CdmContext() {} -CdmContext::~CdmContext() { -} +CdmContext::~CdmContext() {} -CdmContextProvider::CdmContextProvider() { -} - -CdmContextProvider::~CdmContextProvider() { -} - -void IgnoreCdmAttached(bool success) { -} +void IgnoreCdmAttached(bool /* success */) {} } // namespace media diff --git a/chromium/media/base/cdm_context.h b/chromium/media/base/cdm_context.h index c64f276efd0..d41281ec53d 100644 --- a/chromium/media/base/cdm_context.h +++ b/chromium/media/base/cdm_context.h @@ -13,9 +13,9 @@ namespace media { class Decryptor; -// An interface representing the context that a media pipeline needs from a +// An interface representing the context that a media player needs from a // content decryption module (CDM) to decrypt (and decode) encrypted buffers. -// Only used for implementing SetCdm(). +// This is used to pass the CDM to the media player (e.g. SetCdm()). class MEDIA_EXPORT CdmContext { public: // Indicates an invalid CDM ID. See GetCdmId() for details. @@ -24,15 +24,14 @@ class MEDIA_EXPORT CdmContext { virtual ~CdmContext(); // Gets the Decryptor object associated with the CDM. Returns nullptr if the - // CDM does not support a Decryptor. Must not return nullptr if GetCdmId() - // returns kInvalidCdmId. The returned object is only guaranteed to be valid - // during the CDM's lifetime. + // CDM does not support a Decryptor (i.e. platform-based CDMs where decryption + // occurs implicitly along with decoding). The returned object is only + // guaranteed to be valid during the CDM's lifetime. virtual Decryptor* GetDecryptor() = 0; - // Returns an ID that identifies a CDM, or kInvalidCdmId. The interpretation - // is implementation-specific; current implementations use the ID to locate a - // remote CDM in a different process. The return value will not be - // kInvalidCdmId if GetDecryptor() returns nullptr. + // Returns an ID that can be used to find a remote CDM, in which case this CDM + // serves as a proxy to the remote one. Returns kInvalidCdmId when remote CDM + // is not supported (e.g. this CDM is a local CDM). virtual int GetCdmId() const = 0; protected: @@ -42,25 +41,6 @@ class MEDIA_EXPORT CdmContext { DISALLOW_COPY_AND_ASSIGN(CdmContext); }; -// An interface for looking up CdmContext objects by the CDM ID. -class MEDIA_EXPORT CdmContextProvider { - public: - virtual ~CdmContextProvider(); - - // Returns the CdmContext corresponding to |cdm_id|. Returns nullptr if no - // such CdmContext can be found. - // Note: Calling GetCdmId() on the returned CdmContext returns kInvalidCdmId - // (in all current cases) because the CDM will be local in the process where - // GetCdmContext() is called. - virtual CdmContext* GetCdmContext(int cdm_id) = 0; - - protected: - CdmContextProvider(); - - private: - DISALLOW_COPY_AND_ASSIGN(CdmContextProvider); -}; - // Callback to notify that the CdmContext has been completely attached to // the media pipeline. Parameter indicates whether the operation succeeded. typedef base::Callback<void(bool)> CdmAttachedCB; @@ -68,19 +48,6 @@ typedef base::Callback<void(bool)> CdmAttachedCB; // A dummy implementation of CdmAttachedCB. MEDIA_EXPORT void IgnoreCdmAttached(bool success); -// Callback to notify that a CDM is ready. CdmAttachedCB is called when the CDM -// has been completely attached to the media pipeline. -typedef base::Callback<void(CdmContext*, const CdmAttachedCB&)> CdmReadyCB; - -// Callback to set/cancel a CdmReadyCB. -// Calling this callback with a non-null callback registers CDM ready -// notification. When the CDM is ready, notification will be sent -// through the provided callback. -// Calling this callback with a null callback cancels previously registered CDM -// ready notification. Any previously provided callback will be fired -// immediately with NULL. -typedef base::Callback<void(const CdmReadyCB&)> SetCdmReadyCB; - } // namespace media #endif // MEDIA_BASE_CDM_CONTEXT_H_ diff --git a/chromium/media/base/cdm_key_information.cc b/chromium/media/base/cdm_key_information.cc index 70d4464b850..efaed61f971 100644 --- a/chromium/media/base/cdm_key_information.cc +++ b/chromium/media/base/cdm_key_information.cc @@ -32,6 +32,8 @@ CdmKeyInformation::CdmKeyInformation(const uint8_t* key_id_data, status(status), system_code(system_code) {} +CdmKeyInformation::CdmKeyInformation(const CdmKeyInformation& other) = default; + CdmKeyInformation::~CdmKeyInformation() { } diff --git a/chromium/media/base/cdm_key_information.h b/chromium/media/base/cdm_key_information.h index 89c8112a262..7ed8eff693e 100644 --- a/chromium/media/base/cdm_key_information.h +++ b/chromium/media/base/cdm_key_information.h @@ -40,6 +40,7 @@ struct MEDIA_EXPORT CdmKeyInformation { size_t key_id_length, KeyStatus status, uint32_t system_code); + CdmKeyInformation(const CdmKeyInformation& other); ~CdmKeyInformation(); std::vector<uint8_t> key_id; diff --git a/chromium/media/base/cdm_promise.h b/chromium/media/base/cdm_promise.h index 0c44f80c8fa..dc11e7a0144 100644 --- a/chromium/media/base/cdm_promise.h +++ b/chromium/media/base/cdm_promise.h @@ -99,6 +99,8 @@ class MEDIA_EXPORT CdmPromiseTemplate : public CdmPromise { } protected: + bool IsPromiseSettled() const { return is_settled_; } + // All implementations must call this method in resolve() and reject() methods // to indicate that the promise has been settled. void MarkPromiseSettled() { @@ -107,6 +109,17 @@ class MEDIA_EXPORT CdmPromiseTemplate : public CdmPromise { is_settled_ = true; } + // Must be called by the concrete destructor if !IsPromiseSettled(). + // Note: We can't call reject() in ~CdmPromise() because reject() is virtual. + void RejectPromiseOnDestruction() { + DCHECK(!is_settled_); + std::string message = + "Unfulfilled promise rejected automatically during destruction."; + DVLOG(1) << message; + reject(MediaKeys::INVALID_STATE_ERROR, 0, message); + DCHECK(is_settled_); + } + private: // Keep track of whether the promise has been resolved or rejected yet. bool is_settled_; diff --git a/chromium/media/base/container_names.cc b/chromium/media/base/container_names.cc index 8bcb3c04336..24d8ea953ff 100644 --- a/chromium/media/base/container_names.cc +++ b/chromium/media/base/container_names.cc @@ -11,6 +11,7 @@ #include "base/logging.h" #include "base/macros.h" +#include "base/numerics/safe_conversions.h" #include "media/base/bit_reader.h" namespace media { @@ -1257,13 +1258,15 @@ static bool CheckWebm(const uint8_t* buffer, int buffer_size) { RCHECK(GetElementId(&reader) == 0x1a45dfa3); // Get the header size, and ensure there are enough bits to check. - int header_size = GetVint(&reader); + // Using saturated_cast<> in case the size read is really large + // (in which case the bits_available() check will fail). + int header_size = base::saturated_cast<int>(GetVint(&reader)); RCHECK(reader.bits_available() / 8 >= header_size); // Loop through the header. while (reader.bits_available() > 0) { int tag = GetElementId(&reader); - int tagsize = GetVint(&reader); + int tagsize = base::saturated_cast<int>(GetVint(&reader)); switch (tag) { case 0x4286: // EBMLVersion case 0x42f7: // EBMLReadVersion @@ -1273,15 +1276,18 @@ static bool CheckWebm(const uint8_t* buffer, int buffer_size) { case 0x4285: // DocTypeReadVersion case 0xec: // void case 0xbf: // CRC32 + RCHECK(reader.bits_available() / 8 >= tagsize); RCHECK(reader.SkipBits(tagsize * 8)); break; case 0x4282: // EBMLDocType // Need to see "webm" or "matroska" next. + RCHECK(reader.bits_available() >= 32); switch (ReadBits(&reader, 32)) { case TAG('w', 'e', 'b', 'm') : return true; case TAG('m', 'a', 't', 'r') : + RCHECK(reader.bits_available() >= 32); return (ReadBits(&reader, 32) == TAG('o', 's', 'k', 'a')); } return false; diff --git a/chromium/media/base/container_names_fuzzertest.cc b/chromium/media/base/container_names_fuzzertest.cc new file mode 100644 index 00000000000..281fcc4b792 --- /dev/null +++ b/chromium/media/base/container_names_fuzzertest.cc @@ -0,0 +1,15 @@ +// Copyright 2016 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 <stddef.h> + +#include "base/numerics/safe_conversions.h" +#include "media/base/container_names.h" + +// Entry point for LibFuzzer. +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + media::container_names::DetermineContainer(data, + base::checked_cast<int>(size)); + return 0; +} diff --git a/chromium/media/base/container_names_unittest.cc b/chromium/media/base/container_names_unittest.cc index 4b71c329bb2..b05632acb63 100644 --- a/chromium/media/base/container_names_unittest.cc +++ b/chromium/media/base/container_names_unittest.cc @@ -82,6 +82,32 @@ uint8_t kBug263073Buffer[] = { 0x00, 0x00, 0x00, 0xaa, 0x2e, 0x22, 0xcf, 0x00, 0x00, 0x00, 0x37, 0x67, 0x64, 0x00, 0x28, 0xac, 0x2c, 0xa4, 0x01, 0xe0, 0x08, 0x9f, 0x97, 0x01, 0x52, 0x02, 0x02, 0x02, 0x80, 0x00, 0x01}; +uint8_t kBug584401Buffer[] = { + 0x1a, 0x45, 0xdf, 0xa3, 0x01, 0x00, 0x3b, 0x00, 0xb1, 0x00, 0x00, 0x1f, + 0x42, 0x86, 0x81, 0x01, 0x42, 0xf7, 0x81, 0x01, 0x42, 0xf2, 0x0b, 0x77, + 0x01, 0xb2, 0x74, 0x87, 0xc0, 0x00, 0x20, 0x84, 0x21, 0x08, 0x23, 0x00, + 0xae, 0x06, 0x06, 0x01, 0x81, 0x87, 0xbb, 0x8e, 0x0f, 0x9f, 0x3e, 0x7c, + 0xfa, 0x61, 0xeb, 0x8e, 0x97, 0x74, 0x37, 0xd0, 0x5f, 0x5c, 0x75, 0x1e, + 0x71, 0x30, 0xfe, 0x7c, 0xe0, 0xf9, 0xf3, 0xe7, 0xcf, 0x9f, 0x3e, 0x7c, + 0xf8, 0x00, 0xc7, 0x32, 0xe2, 0x31, 0xb9, 0x58, 0x1a, 0xe7, 0xf9, 0x98, + 0x0c, 0xdd, 0x6f, 0xd4, 0x20, 0xb7, 0xe4, 0xb4, 0x7e, 0xaa, 0xdf, 0x29, + 0xd6, 0x9d, 0x2b, 0x37, 0x2f, 0x7a, 0xc2, 0x5f, 0x52, 0x44, 0x01, 0xdc, + 0x32, 0x96, 0xbb, 0xcb, 0x25, 0x52, 0x52, 0xac, 0x6f, 0xb1, 0xbd, 0x85, + 0x02, 0x83, 0x7f, 0x5e, 0x43, 0x98, 0xa6, 0x81, 0x04, 0x42, 0xf3, 0x9b, + 0x81, 0x30, 0x26, 0xb2, 0x75, 0x8e, 0x66, 0xcf, 0x11, 0xa6, 0xd9, 0x00, + 0xaa, 0x00, 0x62, 0xce, 0x6c, 0x6d, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x06, 0x00, 0x00, 0x00, 0x01, 0x02, 0xa1, 0xdc, 0xab, 0x8c, 0x47, + 0xa9, 0xcf, 0x11, 0x8e, 0xe4, 0x00, 0xc0, 0x0c, 0x20, 0x53, 0x65, 0x68, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x42, 0x82, 0x84, 0x77, 0x2a, + 0x65, 0x62, 0x6d, 0x42, 0xbe, 0x4c, 0xda, 0xc9, 0x4c, 0xa5, 0x0f, 0x15, + 0xe8, 0xfd, 0x5b, 0x09, 0x8c, 0x38, 0xd7, 0x18, 0x0a, 0x68, 0x41, 0x46, + 0x63, 0x18, 0xf9, 0xf4, 0xcb, 0xc7, 0x57, 0x95, 0xd8, 0x0b, 0x2c, 0x91, + 0x70, 0x1b, 0x81, 0xd3, 0xda, 0xa0, 0x62, 0x87, 0x2d, 0x03, 0x50, 0x6d, + 0x26, 0xb1, 0xcc, 0xb8, 0x8c, 0x81, 0x6e, 0x56}; +uint8_t kBug585243Buffer[] = {0x1a, 0x45, 0xdf, 0xa3, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, + 0x42, 0x82, 0x42, 0x82, 0x00, 0x00}; // Test that containers that start with fixed strings are handled correctly. // This is to verify that the TAG matches the first 4 characters of the string. @@ -100,6 +126,8 @@ TEST(ContainerNamesTest, CheckFixedStrings) { VERIFY(kRm2Buffer, CONTAINER_RM); VERIFY(kWtvBuffer, CONTAINER_WTV); VERIFY(kBug263073Buffer, CONTAINER_MOV); + VERIFY(kBug584401Buffer, CONTAINER_EAC3); + VERIFY(kBug585243Buffer, CONTAINER_UNKNOWN); } // Determine the container type of a specified file. diff --git a/chromium/media/base/decode_status.cc b/chromium/media/base/decode_status.cc new file mode 100644 index 00000000000..b0156643062 --- /dev/null +++ b/chromium/media/base/decode_status.cc @@ -0,0 +1,26 @@ +// Copyright 2016 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/base/decode_status.h" + +#include <ostream> + +namespace media { + +std::ostream& operator<<(std::ostream& os, const DecodeStatus& status) { + switch (status) { + case DecodeStatus::OK: + os << "DecodeStatus::OK"; + break; + case DecodeStatus::ABORTED: + os << "DecodeStatus::ABORTED"; + break; + case DecodeStatus::DECODE_ERROR: + os << "DecodeStatus::DECODE_ERROR"; + break; + } + return os; +} + +} // namespace media diff --git a/chromium/media/base/decode_status.h b/chromium/media/base/decode_status.h new file mode 100644 index 00000000000..790eb5fff8c --- /dev/null +++ b/chromium/media/base/decode_status.h @@ -0,0 +1,27 @@ +// Copyright 2016 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_BASE_DECODE_STATUS_H_ +#define MEDIA_BASE_DECODE_STATUS_H_ + +#include <iosfwd> + +#include "media/base/media_export.h" + +namespace media { + +enum class DecodeStatus { + OK = 0, // Everything went as planned. + ABORTED, // Read aborted due to Reset() during pending read. + DECODE_ERROR, // Decoder returned decode error. Note: Prefixed by DECODE_ + // since ERROR is a reserved name (special macro) on Windows. +}; + +// Helper function so that DecodeStatus can be printed easily. +MEDIA_EXPORT std::ostream& operator<<(std::ostream& os, + const DecodeStatus& status); + +} // namespace media + +#endif // MEDIA_BASE_DECODE_STATUS_H_ diff --git a/chromium/media/base/decoder_factory.cc b/chromium/media/base/decoder_factory.cc new file mode 100644 index 00000000000..c9fe0576e49 --- /dev/null +++ b/chromium/media/base/decoder_factory.cc @@ -0,0 +1,23 @@ +// Copyright 2016 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/base/decoder_factory.h" + +#include "base/single_thread_task_runner.h" + +namespace media { + +DecoderFactory::DecoderFactory() {} + +DecoderFactory::~DecoderFactory() {} + +void DecoderFactory::CreateAudioDecoders( + scoped_refptr<base::SingleThreadTaskRunner> task_runner, + ScopedVector<AudioDecoder>* audio_decoders) {} + +void DecoderFactory::CreateVideoDecoders( + scoped_refptr<base::SingleThreadTaskRunner> task_runner, + ScopedVector<VideoDecoder>* video_decoders) {} + +} // namespace media diff --git a/chromium/media/base/decoder_factory.h b/chromium/media/base/decoder_factory.h new file mode 100644 index 00000000000..9ae3f1b2b52 --- /dev/null +++ b/chromium/media/base/decoder_factory.h @@ -0,0 +1,46 @@ +// Copyright 2016 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_BASE_DECODER_FACTORY_H_ +#define MEDIA_BASE_DECODER_FACTORY_H_ + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_vector.h" +#include "media/base/media_export.h" + +namespace base { +class SingleThreadTaskRunner; +} + +namespace media { + +class AudioDecoder; +class VideoDecoder; + +// A factory class for creating audio and video decoders. +class MEDIA_EXPORT DecoderFactory { + public: + DecoderFactory(); + virtual ~DecoderFactory(); + + // Creates audio decoders and append them to the end of |audio_decoders|. + // Decoders are single-threaded, each decoder should run on |task_runner|. + virtual void CreateAudioDecoders( + scoped_refptr<base::SingleThreadTaskRunner> task_runner, + ScopedVector<AudioDecoder>* audio_decoders); + + // Creates video decoders and append them to the end of |video_decoders|. + // Decoders are single-threaded, each decoder should run on |task_runner|. + virtual void CreateVideoDecoders( + scoped_refptr<base::SingleThreadTaskRunner> task_runner, + ScopedVector<VideoDecoder>* video_decoders); + + private: + DISALLOW_COPY_AND_ASSIGN(DecoderFactory); +}; + +} // namespace media + +#endif // MEDIA_BASE_DECODER_FACTORY_H_ diff --git a/chromium/media/base/decrypt_config.h b/chromium/media/base/decrypt_config.h index c90a3bc1a99..84b3874886d 100644 --- a/chromium/media/base/decrypt_config.h +++ b/chromium/media/base/decrypt_config.h @@ -56,6 +56,10 @@ class MEDIA_EXPORT DecryptConfig { const std::string& iv() const { return iv_; } const std::vector<SubsampleEntry>& subsamples() const { return subsamples_; } + // Returns true if the corresponding decoder buffer requires decryption and + // false if that buffer is clear despite the presense of DecryptConfig. + bool is_encrypted() const { return !key_id_.empty() && !iv_.empty(); } + // Returns true if all fields in |config| match this config. bool Matches(const DecryptConfig& config) const; diff --git a/chromium/media/base/demuxer.h b/chromium/media/base/demuxer.h index 5a2218a3266..027264ba79f 100644 --- a/chromium/media/base/demuxer.h +++ b/chromium/media/base/demuxer.h @@ -10,6 +10,7 @@ #include <vector> #include "base/macros.h" +#include "base/memory/scoped_ptr.h" #include "base/time/time.h" #include "media/base/data_source.h" #include "media/base/demuxer_stream.h" @@ -22,6 +23,7 @@ namespace media { class TextTrackConfig; +class MediaTracks; class MEDIA_EXPORT DemuxerHost { public: @@ -37,8 +39,11 @@ class MEDIA_EXPORT DemuxerHost { // Duration may be kInfiniteDuration() if the duration is not known. virtual void SetDuration(base::TimeDelta duration) = 0; - // Stops execution of the pipeline due to a fatal error. Do not call this - // method with PIPELINE_OK. + // Stops execution of the pipeline due to a fatal error. Do not call this + // method with PIPELINE_OK. Stopping is not immediate so demuxers must be + // prepared to soft fail on subsequent calls. E.g., if Demuxer::Seek() is + // called after an unrecoverable error the provided PipelineStatusCB must be + // called with an error. virtual void OnDemuxerError(PipelineStatus error) = 0; // Add |text_stream| to the collection managed by the text renderer. @@ -61,6 +66,11 @@ class MEDIA_EXPORT Demuxer : public DemuxerStreamProvider { const std::vector<uint8_t>& init_data)> EncryptedMediaInitDataCB; + // Notifies demuxer clients that media track configuration has been updated + // (e.g. the inital stream metadata has been parsed successfully, or a new + // init segment has been parsed successfully in MSE case). + typedef base::Callback<void(scoped_ptr<MediaTracks>)> MediaTracksUpdatedCB; + Demuxer(); ~Demuxer() override; @@ -76,6 +86,32 @@ class MEDIA_EXPORT Demuxer : public DemuxerStreamProvider { const PipelineStatusCB& status_cb, bool enable_text_tracks) = 0; + // Indicates that a new Seek() call is on its way. Implementations may abort + // pending reads and future Read() calls may return kAborted until Seek() is + // executed. |seek_time| is the presentation timestamp of the new Seek() call. + // + // In actual use, this call occurs on the main thread while Seek() is called + // on the media thread. StartWaitingForSeek() can be used to synchronize the + // two. + // + // StartWaitingForSeek() MUST be called before Seek(). + virtual void StartWaitingForSeek(base::TimeDelta seek_time) = 0; + + // Indicates that the current Seek() operation is obsoleted by a new one. + // Implementations can expect that StartWaitingForSeek() will be called + // when the current seek operation completes. + // + // Like StartWaitingForSeek(), CancelPendingSeek() is called on the main + // thread. Ordering with respect to the to-be-canceled Seek() is not + // guaranteed. Regardless of ordering, implementations may abort pending reads + // and may return kAborted from future Read() calls, until after + // StartWaitingForSeek() and the following Seek() call occurs. + // + // |seek_time| should match that passed to the next StartWaitingForSeek(), but + // may not if the seek target changes again before the current seek operation + // completes or is aborted. + virtual void CancelPendingSeek(base::TimeDelta seek_time) = 0; + // Carry out any actions required to seek to the given time, executing the // callback upon completion. virtual void Seek(base::TimeDelta time, diff --git a/chromium/media/base/demuxer_perftest.cc b/chromium/media/base/demuxer_perftest.cc index 7d8a10ba1de..6703222bc71 100644 --- a/chromium/media/base/demuxer_perftest.cc +++ b/chromium/media/base/demuxer_perftest.cc @@ -14,6 +14,7 @@ #include "build/build_config.h" #include "media/base/media.h" #include "media/base/media_log.h" +#include "media/base/media_tracks.h" #include "media/base/test_data_util.h" #include "media/base/timestamp_constants.h" #include "media/filters/ffmpeg_demuxer.h" @@ -54,6 +55,10 @@ static void OnEncryptedMediaInitData(EmeInitDataType init_data_type, VLOG(0) << "File is encrypted."; } +static void OnMediaTracksUpdated(scoped_ptr<MediaTracks> tracks) { + VLOG(0) << "Got media tracks info, tracks = " << tracks->tracks().size(); +} + typedef std::vector<media::DemuxerStream* > Streams; // Simulates playback reading requirements by reading from each stream @@ -183,8 +188,11 @@ static void RunDemuxerBenchmark(const std::string& filename) { Demuxer::EncryptedMediaInitDataCB encrypted_media_init_data_cb = base::Bind(&OnEncryptedMediaInitData); + Demuxer::MediaTracksUpdatedCB tracks_updated_cb = + base::Bind(&OnMediaTracksUpdated); FFmpegDemuxer demuxer(message_loop.task_runner(), &data_source, - encrypted_media_init_data_cb, new MediaLog()); + encrypted_media_init_data_cb, tracks_updated_cb, + new MediaLog()); demuxer.Initialize(&demuxer_host, base::Bind(&QuitLoopWithStatus, &message_loop), diff --git a/chromium/media/base/encryption_scheme.cc b/chromium/media/base/encryption_scheme.cc new file mode 100644 index 00000000000..70d133c65ab --- /dev/null +++ b/chromium/media/base/encryption_scheme.cc @@ -0,0 +1,37 @@ +// 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 "media/base/encryption_scheme.h" + +namespace media { + +EncryptionScheme::Pattern::Pattern() {} + +EncryptionScheme::Pattern::Pattern(uint32_t encrypt_blocks, + uint32_t skip_blocks) + : encrypt_blocks_(encrypt_blocks), skip_blocks_(skip_blocks) {} + +EncryptionScheme::Pattern::~Pattern() {} + +bool EncryptionScheme::Pattern::Matches(const Pattern& other) const { + return encrypt_blocks_ == other.encrypt_blocks() && + skip_blocks_ == other.skip_blocks(); +} + +bool EncryptionScheme::Pattern::IsInEffect() const { + return encrypt_blocks_ != 0 && skip_blocks_ != 0; +} + +EncryptionScheme::EncryptionScheme() {} + +EncryptionScheme::EncryptionScheme(CipherMode mode, const Pattern& pattern) + : mode_(mode), pattern_(pattern) {} + +EncryptionScheme::~EncryptionScheme() {} + +bool EncryptionScheme::Matches(const EncryptionScheme& other) const { + return mode_ == other.mode_ && pattern_.Matches(other.pattern_); +} + +} // namespace media diff --git a/chromium/media/base/encryption_scheme.h b/chromium/media/base/encryption_scheme.h new file mode 100644 index 00000000000..37bea6710d2 --- /dev/null +++ b/chromium/media/base/encryption_scheme.h @@ -0,0 +1,79 @@ +// 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_BASE_ENCRYPTION_SCHEME_H_ +#define MEDIA_BASE_ENCRYPTION_SCHEME_H_ + +#include <stdint.h> + +#include "media/base/media_export.h" + +namespace media { + +// Specification of whether and how the stream is encrypted (in whole or part). +class MEDIA_EXPORT EncryptionScheme { + public: + // Algorithm and mode used for encryption. CIPHER_MODE_UNENCRYPTED indicates + // no encryption. + enum CipherMode { + CIPHER_MODE_UNENCRYPTED, + CIPHER_MODE_AES_CTR, + CIPHER_MODE_AES_CBC, + CIPHER_MODE_MAX = CIPHER_MODE_AES_CBC + }; + + // CENC 3rd Edition adds pattern encryption, through two new protection + // schemes: 'cens' (with AES-CTR) and 'cbcs' (with AES-CBC). + // The pattern applies independently to each 'encrypted' part of the frame (as + // defined by the relevant subsample entries), and reduces further the + // actual encryption applied through a repeating pattern of (encrypt:skip) + // 16 byte blocks. For example, in a (1:9) pattern, the first block is + // encrypted, and the next nine are skipped. This pattern is applied + // repeatedly until the end of the last 16-byte block in the subsample. + // Any remaining bytes are left clear. + // If either of encrypt_blocks or skip_blocks is 0, pattern encryption is + // disabled. + class MEDIA_EXPORT Pattern { + public: + Pattern(); + Pattern(uint32_t encrypt_blocks, uint32_t skip_blocks); + ~Pattern(); + + bool Matches(const Pattern& other) const; + + uint32_t encrypt_blocks() const { return encrypt_blocks_; } + uint32_t skip_blocks() const { return skip_blocks_; } + + bool IsInEffect() const; + + private: + uint32_t encrypt_blocks_ = 0; + uint32_t skip_blocks_ = 0; + + // Allow copy and assignment. + }; + + // The default constructor makes an instance that indicates no encryption. + EncryptionScheme(); + + // This constructor allows specification of the cipher mode and the pattern. + EncryptionScheme(CipherMode mode, const Pattern& pattern); + ~EncryptionScheme(); + + bool Matches(const EncryptionScheme& other) const; + + bool is_encrypted() const { return mode_ != CIPHER_MODE_UNENCRYPTED; } + CipherMode mode() const { return mode_; } + const Pattern& pattern() const { return pattern_; } + + private: + CipherMode mode_ = CIPHER_MODE_UNENCRYPTED; + Pattern pattern_; + + // Allow copy and assignment. +}; + +} // namespace media + +#endif // MEDIA_BASE_ENCRYPTION_SCHEME_H_ diff --git a/chromium/media/base/fake_audio_renderer_sink.cc b/chromium/media/base/fake_audio_renderer_sink.cc index 5ce60740b05..40c3cdf9c27 100644 --- a/chromium/media/base/fake_audio_renderer_sink.cc +++ b/chromium/media/base/fake_audio_renderer_sink.cc @@ -7,15 +7,20 @@ #include "base/bind.h" #include "base/location.h" #include "base/logging.h" -#include "base/single_thread_task_runner.h" -#include "media/base/fake_output_device.h" namespace media { FakeAudioRendererSink::FakeAudioRendererSink() : state_(kUninitialized), callback_(NULL), - output_device_(new FakeOutputDevice) {} + output_device_info_( + std::string(), + OUTPUT_DEVICE_STATUS_OK, + media::AudioParameters(media::AudioParameters::AUDIO_FAKE, + media::CHANNEL_LAYOUT_STEREO, + media::AudioParameters::kTelephoneSampleRate, + 16, + 1)) {} FakeAudioRendererSink::~FakeAudioRendererSink() { DCHECK(!callback_); @@ -56,8 +61,8 @@ bool FakeAudioRendererSink::SetVolume(double volume) { return true; } -OutputDevice* FakeAudioRendererSink::GetOutputDevice() { - return output_device_.get(); +OutputDeviceInfo FakeAudioRendererSink::GetOutputDeviceInfo() { + return output_device_info_; } bool FakeAudioRendererSink::Render(AudioBus* dest, diff --git a/chromium/media/base/fake_audio_renderer_sink.h b/chromium/media/base/fake_audio_renderer_sink.h index afdd01870b2..b69c1b477c3 100644 --- a/chromium/media/base/fake_audio_renderer_sink.h +++ b/chromium/media/base/fake_audio_renderer_sink.h @@ -12,11 +12,10 @@ #include "base/macros.h" #include "media/audio/audio_parameters.h" #include "media/base/audio_renderer_sink.h" +#include "media/base/output_device_info.h" namespace media { -class FakeOutputDevice; - class FakeAudioRendererSink : public AudioRendererSink { public: enum State { @@ -37,7 +36,7 @@ class FakeAudioRendererSink : public AudioRendererSink { void Pause() override; void Play() override; bool SetVolume(double volume) override; - OutputDevice* GetOutputDevice() override; + OutputDeviceInfo GetOutputDeviceInfo() override; // Attempts to call Render() on the callback provided to // Initialize() with |dest| and |audio_delay_milliseconds|. @@ -61,7 +60,7 @@ class FakeAudioRendererSink : public AudioRendererSink { State state_; RenderCallback* callback_; - scoped_ptr<FakeOutputDevice> output_device_; + OutputDeviceInfo output_device_info_; DISALLOW_COPY_AND_ASSIGN(FakeAudioRendererSink); }; diff --git a/chromium/media/base/fake_demuxer_stream.cc b/chromium/media/base/fake_demuxer_stream.cc index 267b3e5d8d2..7ed5841c4ab 100644 --- a/chromium/media/base/fake_demuxer_stream.cc +++ b/chromium/media/base/fake_demuxer_stream.cc @@ -147,11 +147,11 @@ void FakeDemuxerStream::SeekToStart() { void FakeDemuxerStream::UpdateVideoDecoderConfig() { const gfx::Rect kVisibleRect(kStartWidth, kStartHeight); - video_decoder_config_.Initialize(kCodecVP8, VIDEO_CODEC_PROFILE_UNKNOWN, - PIXEL_FORMAT_YV12, COLOR_SPACE_UNSPECIFIED, - next_coded_size_, kVisibleRect, - next_coded_size_, EmptyExtraData(), - is_encrypted_); + video_decoder_config_.Initialize( + kCodecVP8, VIDEO_CODEC_PROFILE_UNKNOWN, PIXEL_FORMAT_YV12, + COLOR_SPACE_UNSPECIFIED, next_coded_size_, kVisibleRect, next_coded_size_, + EmptyExtraData(), + is_encrypted_ ? AesCtrEncryptionScheme() : Unencrypted()); next_coded_size_.Enlarge(kWidthDelta, kHeightDelta); } diff --git a/chromium/media/base/fake_output_device.cc b/chromium/media/base/fake_output_device.cc deleted file mode 100644 index 2f7c8e70062..00000000000 --- a/chromium/media/base/fake_output_device.cc +++ /dev/null @@ -1,36 +0,0 @@ -// 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/base/fake_output_device.h" - -#include "base/callback.h" - -namespace media { - -FakeOutputDevice::FakeOutputDevice() - : FakeOutputDevice(OUTPUT_DEVICE_STATUS_OK) {} - -FakeOutputDevice::FakeOutputDevice(OutputDeviceStatus device_status) - : device_status_(device_status) {} - -FakeOutputDevice::~FakeOutputDevice() {} - -void FakeOutputDevice::SwitchOutputDevice( - const std::string& device_id, - const url::Origin& security_origin, - const SwitchOutputDeviceCB& callback) { - callback.Run(device_status_); -} - -AudioParameters FakeOutputDevice::GetOutputParameters() { - return media::AudioParameters( - media::AudioParameters::AUDIO_FAKE, media::CHANNEL_LAYOUT_STEREO, - media::AudioParameters::kTelephoneSampleRate, 16, 1); -} - -OutputDeviceStatus FakeOutputDevice::GetDeviceStatus() { - return device_status_; -} - -} // namespace media diff --git a/chromium/media/base/fake_output_device.h b/chromium/media/base/fake_output_device.h deleted file mode 100644 index 45a6abb7cfd..00000000000 --- a/chromium/media/base/fake_output_device.h +++ /dev/null @@ -1,35 +0,0 @@ -// 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 MEDIA_BASE_FAKE_OUTPUT_DEVICE_H_ -#define MEDIA_BASE_FAKE_OUTPUT_DEVICE_H_ - -#include <string> - -#include "base/macros.h" -#include "media/base/output_device.h" - -namespace media { - -class FakeOutputDevice : public OutputDevice { - public: - FakeOutputDevice(); - explicit FakeOutputDevice(OutputDeviceStatus status); - ~FakeOutputDevice() override; - - // OutputDevice implementation. - void SwitchOutputDevice(const std::string& device_id, - const url::Origin& security_origin, - const SwitchOutputDeviceCB& callback) override; - AudioParameters GetOutputParameters() override; - OutputDeviceStatus GetDeviceStatus() override; - - private: - OutputDeviceStatus device_status_; - DISALLOW_COPY_AND_ASSIGN(FakeOutputDevice); -}; - -} // namespace media - -#endif // MEDIA_BASE_FAKE_OUTPUT_DEVICE_H_ diff --git a/chromium/media/base/fake_single_thread_task_runner.cc b/chromium/media/base/fake_single_thread_task_runner.cc new file mode 100644 index 00000000000..77f1f73d3a4 --- /dev/null +++ b/chromium/media/base/fake_single_thread_task_runner.cc @@ -0,0 +1,113 @@ +// 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/base/fake_single_thread_task_runner.h" + +#include "base/location.h" +#include "base/logging.h" +#include "base/time/tick_clock.h" + +namespace media { + +FakeSingleThreadTaskRunner::FakeSingleThreadTaskRunner( + base::SimpleTestTickClock* clock) + : clock_(clock), fail_on_next_task_(false) {} + +FakeSingleThreadTaskRunner::~FakeSingleThreadTaskRunner() {} + +bool FakeSingleThreadTaskRunner::PostDelayedTask( + const tracked_objects::Location& from_here, + const base::Closure& task, + base::TimeDelta delay) { + if (fail_on_next_task_) { + LOG(FATAL) << "Infinite task posting loop detected. Possibly caused by " + << from_here.ToString() << " posting a task with delay " + << delay.InMicroseconds() << " usec."; + } + + CHECK_LE(base::TimeDelta(), delay); + const base::TimeTicks run_time = clock_->NowTicks() + delay; + + // If there are one or more tasks with the exact same run time, schedule this + // task to occur after them. This mimics the FIFO ordering behavior when + // scheduling delayed tasks to be run via base::MessageLoop in a + // multi-threaded application. + if (!tasks_.empty()) { + const auto after_it = tasks_.lower_bound( + TaskKey(run_time + base::TimeDelta::FromMicroseconds(1), 0)); + if (after_it != tasks_.begin()) { + auto it = after_it; + --it; + if (it->first.first == run_time) { + tasks_.insert( + after_it /* hint */, + std::make_pair(TaskKey(run_time, it->first.second + 1), task)); + return true; + } + } + } + + // No tasks have the exact same run time, so just do a simple insert. + tasks_.insert(std::make_pair(TaskKey(run_time, 0), task)); + return true; +} + +bool FakeSingleThreadTaskRunner::RunsTasksOnCurrentThread() const { + return true; +} + +void FakeSingleThreadTaskRunner::RunTasks() { + while (true) { + // Run all tasks equal or older than current time. + const auto it = tasks_.begin(); + if (it == tasks_.end()) + return; // No more tasks. + + if (clock_->NowTicks() < it->first.first) + return; + + const base::Closure task = it->second; + tasks_.erase(it); + task.Run(); + } +} + +void FakeSingleThreadTaskRunner::Sleep(base::TimeDelta t) { + CHECK_LE(base::TimeDelta(), t); + const base::TimeTicks run_until = clock_->NowTicks() + t; + + while (1) { + // Run up to 100000 tasks that were scheduled to run during the sleep + // period. 100000 should be enough for everybody (see comments below). + for (int i = 0; i < 100000; i++) { + const auto it = tasks_.begin(); + if (it == tasks_.end() || run_until < it->first.first) { + clock_->Advance(run_until - clock_->NowTicks()); + return; + } + + clock_->Advance(it->first.first - clock_->NowTicks()); + const base::Closure task = it->second; + tasks_.erase(it); + task.Run(); + } + + // If this point is reached, there's likely some sort of case where a new + // non-delayed task is being posted every time a task is popped and invoked + // from the queue. If that happens, set fail_on_next_task_ to true and throw + // an error when the next task is posted, where we might be able to identify + // the caller causing the problem via logging. + fail_on_next_task_ = true; + } +} + +bool FakeSingleThreadTaskRunner::PostNonNestableDelayedTask( + const tracked_objects::Location& from_here, + const base::Closure& task, + base::TimeDelta delay) { + NOTIMPLEMENTED(); + return false; +} + +} // namespace media diff --git a/chromium/media/base/fake_single_thread_task_runner.h b/chromium/media/base/fake_single_thread_task_runner.h new file mode 100644 index 00000000000..a9be9736fd7 --- /dev/null +++ b/chromium/media/base/fake_single_thread_task_runner.h @@ -0,0 +1,61 @@ +// 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_CAST_TEST_FAKE_TASK_RUNNER_H_ +#define MEDIA_CAST_TEST_FAKE_TASK_RUNNER_H_ + +#include <map> + +#include "base/callback.h" +#include "base/macros.h" +#include "base/single_thread_task_runner.h" +#include "base/test/simple_test_tick_clock.h" + +namespace media { + +class FakeSingleThreadTaskRunner : public base::SingleThreadTaskRunner { + public: + explicit FakeSingleThreadTaskRunner(base::SimpleTestTickClock* clock); + + void RunTasks(); + + // Note: Advances |clock_|. + void Sleep(base::TimeDelta t); + + // base::SingleThreadTaskRunner implementation. + bool PostDelayedTask(const tracked_objects::Location& from_here, + const base::Closure& task, + base::TimeDelta delay) final; + + bool RunsTasksOnCurrentThread() const final; + + // This function is currently not used, and will return false. + bool PostNonNestableDelayedTask(const tracked_objects::Location& from_here, + const base::Closure& task, + base::TimeDelta delay) final; + + protected: + ~FakeSingleThreadTaskRunner() final; + + private: + base::SimpleTestTickClock* const clock_; + + // A compound key is used to ensure FIFO execution of delayed tasks scheduled + // for the same point-in-time. The second part of the key is simply a FIFO + // sequence number. + using TaskKey = std::pair<base::TimeTicks, unsigned int>; + + // Note: The std::map data structure was chosen because the entire + // cast_unittests suite performed 20% faster than when using + // std::priority_queue. http://crbug.com/530842 + std::map<TaskKey, base::Closure> tasks_; + + bool fail_on_next_task_; + + DISALLOW_COPY_AND_ASSIGN(FakeSingleThreadTaskRunner); +}; + +} // namespace media + +#endif // MEDIA_CAST_TEST_FAKE_TASK_RUNNER_H_ diff --git a/chromium/media/base/key_system_info.cc b/chromium/media/base/key_system_info.cc index f36104ab120..da3586d7f4a 100644 --- a/chromium/media/base/key_system_info.cc +++ b/chromium/media/base/key_system_info.cc @@ -9,6 +9,8 @@ namespace media { KeySystemInfo::KeySystemInfo() { } +KeySystemInfo::KeySystemInfo(const KeySystemInfo& other) = default; + KeySystemInfo::~KeySystemInfo() { } diff --git a/chromium/media/base/key_system_info.h b/chromium/media/base/key_system_info.h index d349452b885..fe4dae7a418 100644 --- a/chromium/media/base/key_system_info.h +++ b/chromium/media/base/key_system_info.h @@ -21,10 +21,6 @@ // * Abstract key system // A key system string that cannot be instantiated like a concrete key system // but is otherwise useful, such as in discovery using isTypeSupported(). -// * Parent key system -// A key system string that is one level up from the child key system. It may -// be an abstract key system. -// As an example, "com.example" is the parent of "com.example.foo". namespace media { @@ -32,6 +28,7 @@ namespace media { // the corresponding CDM. struct MEDIA_EXPORT KeySystemInfo { KeySystemInfo(); + KeySystemInfo(const KeySystemInfo& other); ~KeySystemInfo(); std::string key_system; @@ -50,11 +47,6 @@ struct MEDIA_EXPORT KeySystemInfo { EmeFeatureSupport persistent_state_support = EmeFeatureSupport::INVALID; EmeFeatureSupport distinctive_identifier_support = EmeFeatureSupport::INVALID; - // A hierarchical parent for |key_system|. This value can be used to check - // supported types but cannot be used to instantiate a MediaKeys object. - // Only one parent key system is currently supported per concrete key system. - std::string parent_key_system; - // The following indicate how the corresponding CDM should be instantiated. bool use_aes_decryptor = false; #if defined(ENABLE_PEPPER_CDMS) diff --git a/chromium/media/base/key_systems.cc b/chromium/media/base/key_systems.cc index 6c2c8c2db31..8a3ae69c853 100644 --- a/chromium/media/base/key_systems.cc +++ b/chromium/media/base/key_systems.cc @@ -15,7 +15,7 @@ #include "base/time/time.h" #include "build/build_config.h" #include "media/base/key_system_info.h" -#include "media/base/key_systems_support_uma.h" +#include "media/base/media.h" #include "media/base/media_client.h" #include "media/cdm/key_system_names.h" #include "third_party/widevine/cdm/widevine_cdm_common.h" @@ -23,8 +23,6 @@ namespace media { const char kClearKeyKeySystem[] = "org.w3.clearkey"; -const char kPrefixedClearKeyKeySystem[] = "webkit-org.w3.clearkey"; -const char kUnsupportedClearKeyKeySystem[] = "unsupported-org.w3.clearkey"; // These names are used by UMA. Do not change them! const char kClearKeyKeySystemNameForUMA[] = "ClearKey"; @@ -36,31 +34,29 @@ struct NamedCodec { }; // Mapping between containers and their codecs. -// Only audio codec can belong to a "audio/*" container. Both audio and video -// codecs can belong to a "video/*" container. -// TODO(sandersd): This definition only makes sense for prefixed EME. Change it -// when prefixed EME is removed. http://crbug.com/249976 -static NamedCodec kContainerToCodecMasks[] = { +// Only audio codecs can belong to a "audio/*" mime_type, and only video codecs +// can belong to a "video/*" mime_type. +static const NamedCodec kMimeTypeToCodecMasks[] = { {"audio/webm", EME_CODEC_WEBM_AUDIO_ALL}, - {"video/webm", EME_CODEC_WEBM_ALL}, + {"video/webm", EME_CODEC_WEBM_VIDEO_ALL}, #if defined(USE_PROPRIETARY_CODECS) {"audio/mp4", EME_CODEC_MP4_AUDIO_ALL}, - {"video/mp4", EME_CODEC_MP4_ALL} + {"video/mp4", EME_CODEC_MP4_VIDEO_ALL} #endif // defined(USE_PROPRIETARY_CODECS) }; // Mapping between codec names and enum values. -static NamedCodec kCodecStrings[] = { - {"opus", EME_CODEC_WEBM_OPUS}, - {"vorbis", EME_CODEC_WEBM_VORBIS}, - {"vp8", EME_CODEC_WEBM_VP8}, - {"vp8.0", EME_CODEC_WEBM_VP8}, - {"vp9", EME_CODEC_WEBM_VP9}, - {"vp9.0", EME_CODEC_WEBM_VP9}, +static const NamedCodec kCodecStrings[] = { + {"opus", EME_CODEC_WEBM_OPUS}, // Opus. + {"vorbis", EME_CODEC_WEBM_VORBIS}, // Vorbis. + {"vp8", EME_CODEC_WEBM_VP8}, // VP8. + {"vp8.0", EME_CODEC_WEBM_VP8}, // VP8. + {"vp9", EME_CODEC_WEBM_VP9}, // VP9. + {"vp9.0", EME_CODEC_WEBM_VP9}, // VP9. #if defined(USE_PROPRIETARY_CODECS) - {"mp4a", EME_CODEC_MP4_AAC}, - {"avc1", EME_CODEC_MP4_AVC1}, - {"avc3", EME_CODEC_MP4_AVC1} + {"mp4a", EME_CODEC_MP4_AAC}, // AAC. + {"avc1", EME_CODEC_MP4_AVC1}, // AVC1. + {"avc3", EME_CODEC_MP4_AVC1} // AVC3. #endif // defined(USE_PROPRIETARY_CODECS) }; @@ -80,7 +76,7 @@ static EmeRobustness ConvertRobustness(const std::string& robustness) { return EmeRobustness::INVALID; } -static void AddClearKey(std::vector<KeySystemInfo>* concrete_key_systems) { +static void AddClearKey(std::vector<KeySystemInfo>* key_systems) { KeySystemInfo info; info.key_system = kClearKeyKeySystem; @@ -117,7 +113,7 @@ static void AddClearKey(std::vector<KeySystemInfo>* concrete_key_systems) { info.use_aes_decryptor = true; - concrete_key_systems->push_back(info); + key_systems->push_back(info); } // Returns whether the |key_system| is known to Chromium and is thus likely to @@ -157,7 +153,7 @@ static bool IsPotentiallySupportedKeySystem(const std::string& key_system) { // Chromecast defines behaviors for Cast clients within its reverse domain. const char kChromecastRoot[] = "com.chromecast"; - if (IsParentKeySystemOf(kChromecastRoot, key_system)) + if (IsChildKeySystemOf(key_system, kChromecastRoot)) return true; // Implementations that do not have a specification or appropriate glue code @@ -176,25 +172,19 @@ class KeySystemsImpl : public KeySystems { void UpdateIfNeeded(); - bool IsConcreteSupportedKeySystem(const std::string& key_system) const; - - bool PrefixedIsSupportedKeySystemWithMediaMimeType( - const std::string& mime_type, - const std::vector<std::string>& codecs, - const std::string& key_system); - std::string GetKeySystemNameForUMA(const std::string& key_system) const; - bool UseAesDecryptor(const std::string& concrete_key_system) const; + bool UseAesDecryptor(const std::string& key_system) const; #if defined(ENABLE_PEPPER_CDMS) - std::string GetPepperType(const std::string& concrete_key_system) const; + std::string GetPepperType(const std::string& key_system) const; #endif - void AddContainerMask(const std::string& container, uint32_t mask); + // These two functions are for testing purpose only. void AddCodecMask(EmeMediaType media_type, const std::string& codec, uint32_t mask); + void AddMimeTypeCodecMask(const std::string& mime_type, uint32_t mask); // Implementation of KeySystems interface. bool IsSupportedKeySystem(const std::string& key_system) const override; @@ -233,50 +223,32 @@ class KeySystemsImpl : public KeySystems { void UpdateSupportedKeySystems(); - void AddConcreteSupportedKeySystems( - const std::vector<KeySystemInfo>& concrete_key_systems); + void AddSupportedKeySystems(const std::vector<KeySystemInfo>& key_systems); + + void RegisterMimeType(const std::string& mime_type, EmeCodec codecs_mask); + bool IsValidMimeTypeCodecsCombination(const std::string& mime_type, + SupportedCodecs codecs_mask) const; friend struct base::DefaultLazyInstanceTraits<KeySystemsImpl>; typedef base::hash_map<std::string, KeySystemInfo> KeySystemInfoMap; - typedef base::hash_map<std::string, std::string> ParentKeySystemMap; - typedef base::hash_map<std::string, SupportedCodecs> ContainerCodecsMap; + typedef base::hash_map<std::string, SupportedCodecs> MimeTypeCodecsMap; typedef base::hash_map<std::string, EmeCodec> CodecsMap; typedef base::hash_map<std::string, EmeInitDataType> InitDataTypesMap; typedef base::hash_map<std::string, std::string> KeySystemNameForUMAMap; // TODO(sandersd): Separate container enum from codec mask value. // http://crbug.com/417440 - SupportedCodecs GetCodecMaskForContainer( - const std::string& container) const; + // Potentially pass EmeMediaType and a container enum. + SupportedCodecs GetCodecMaskForMimeType( + const std::string& container_mime_type) const; EmeCodec GetCodecForString(const std::string& codec) const; - const std::string& PrefixedGetConcreteKeySystemNameFor( - const std::string& key_system) const; - - // Returns whether a |container| type is supported by checking - // |key_system_supported_codecs|. - // TODO(xhwang): Update this to actually check initDataType support. - bool IsSupportedContainer(const std::string& container, - SupportedCodecs key_system_supported_codecs) const; - - // Returns true if all |codecs| are supported in |container| by checking - // |key_system_supported_codecs|. - bool IsSupportedContainerAndCodecs( - const std::string& container, - const std::vector<std::string>& codecs, - SupportedCodecs key_system_supported_codecs) const; - // Map from key system string to capabilities. - KeySystemInfoMap concrete_key_system_map_; + KeySystemInfoMap key_system_map_; - // Map from parent key system to the concrete key system that should be used - // to represent its capabilities. - ParentKeySystemMap parent_key_system_map_; - - KeySystemsSupportUMA key_systems_support_uma_; - - ContainerCodecsMap container_to_codec_mask_map_; + // This member should only be modified by RegisterMimeType(). + MimeTypeCodecsMap mime_type_to_codec_mask_map_; CodecsMap codec_string_map_; KeySystemNameForUMAMap key_system_name_for_uma_map_; @@ -303,16 +275,15 @@ KeySystemsImpl* KeySystemsImpl::GetInstance() { KeySystemsImpl::KeySystemsImpl() : audio_codec_mask_(EME_CODEC_AUDIO_ALL), video_codec_mask_(EME_CODEC_VIDEO_ALL) { - for (size_t i = 0; i < arraysize(kContainerToCodecMasks); ++i) { - const std::string& name = kContainerToCodecMasks[i].name; - DCHECK(!container_to_codec_mask_map_.count(name)); - container_to_codec_mask_map_[name] = kContainerToCodecMasks[i].type; - } for (size_t i = 0; i < arraysize(kCodecStrings); ++i) { const std::string& name = kCodecStrings[i].name; DCHECK(!codec_string_map_.count(name)); codec_string_map_[name] = kCodecStrings[i].type; } + for (size_t i = 0; i < arraysize(kMimeTypeToCodecMasks); ++i) { + RegisterMimeType(kMimeTypeToCodecMasks[i].name, + kMimeTypeToCodecMasks[i].type); + } InitializeUMAInfo(); @@ -323,13 +294,15 @@ KeySystemsImpl::KeySystemsImpl() : KeySystemsImpl::~KeySystemsImpl() { } -SupportedCodecs KeySystemsImpl::GetCodecMaskForContainer( - const std::string& container) const { - ContainerCodecsMap::const_iterator iter = - container_to_codec_mask_map_.find(container); - if (iter != container_to_codec_mask_map_.end()) - return iter->second; - return EME_CODEC_NONE; +SupportedCodecs KeySystemsImpl::GetCodecMaskForMimeType( + const std::string& container_mime_type) const { + MimeTypeCodecsMap::const_iterator iter = + mime_type_to_codec_mask_map_.find(container_mime_type); + if (iter == mime_type_to_codec_mask_map_.end()) + return EME_CODEC_NONE; + + DCHECK(IsValidMimeTypeCodecsCombination(container_mime_type, iter->second)); + return iter->second; } EmeCodec KeySystemsImpl::GetCodecForString(const std::string& codec) const { @@ -339,15 +312,6 @@ EmeCodec KeySystemsImpl::GetCodecForString(const std::string& codec) const { return EME_CODEC_NONE; } -const std::string& KeySystemsImpl::PrefixedGetConcreteKeySystemNameFor( - const std::string& key_system) const { - ParentKeySystemMap::const_iterator iter = - parent_key_system_map_.find(key_system); - if (iter != parent_key_system_map_.end()) - return iter->second; - return key_system; -} - void KeySystemsImpl::InitializeUMAInfo() { DCHECK(thread_checker_.CalledOnValidThread()); DCHECK(key_system_name_for_uma_map_.empty()); @@ -359,8 +323,6 @@ void KeySystemsImpl::InitializeUMAInfo() { for (const KeySystemInfoForUMA& info : key_systems_info_for_uma) { key_system_name_for_uma_map_[info.key_system] = info.key_system_name_for_uma; - if (info.reports_key_system_support_to_uma) - key_systems_support_uma_.AddKeySystemToReport(info.key_system); } // Clear Key is always supported. @@ -375,8 +337,7 @@ void KeySystemsImpl::UpdateIfNeeded() { void KeySystemsImpl::UpdateSupportedKeySystems() { DCHECK(thread_checker_.CalledOnValidThread()); - concrete_key_system_map_.clear(); - parent_key_system_map_.clear(); + key_system_map_.clear(); // Build KeySystemInfo. std::vector<KeySystemInfo> key_systems_info; @@ -388,16 +349,15 @@ void KeySystemsImpl::UpdateSupportedKeySystems() { // Clear Key is always supported. AddClearKey(&key_systems_info); - AddConcreteSupportedKeySystems(key_systems_info); + AddSupportedKeySystems(key_systems_info); } -void KeySystemsImpl::AddConcreteSupportedKeySystems( - const std::vector<KeySystemInfo>& concrete_key_systems) { +void KeySystemsImpl::AddSupportedKeySystems( + const std::vector<KeySystemInfo>& key_systems) { DCHECK(thread_checker_.CalledOnValidThread()); - DCHECK(concrete_key_system_map_.empty()); - DCHECK(parent_key_system_map_.empty()); + DCHECK(key_system_map_.empty()); - for (const KeySystemInfo& info : concrete_key_systems) { + for (const KeySystemInfo& info : key_systems) { DCHECK(!info.key_system.empty()); DCHECK(info.max_audio_robustness != EmeRobustness::INVALID); DCHECK(info.max_video_robustness != EmeRobustness::INVALID); @@ -448,77 +408,48 @@ void KeySystemsImpl::AddConcreteSupportedKeySystems( EmeFeatureSupport::ALWAYS_ENABLED); } - DCHECK(!IsConcreteSupportedKeySystem(info.key_system)) + DCHECK_EQ(key_system_map_.count(info.key_system), 0u) << "Key system '" << info.key_system << "' already registered"; - DCHECK(!parent_key_system_map_.count(info.key_system)) - << "'" << info.key_system << "' is already registered as a parent"; - concrete_key_system_map_[info.key_system] = info; - if (!info.parent_key_system.empty()) { - DCHECK(!IsConcreteSupportedKeySystem(info.parent_key_system)) - << "Parent '" << info.parent_key_system << "' " - << "already registered concrete"; - DCHECK(!parent_key_system_map_.count(info.parent_key_system)) - << "Parent '" << info.parent_key_system << "' already registered"; - parent_key_system_map_[info.parent_key_system] = info.key_system; + +#if defined(OS_ANDROID) + // Ensure that the renderer can access the decoders necessary to use the + // key system. + if (!info.use_aes_decryptor && !ArePlatformDecodersAvailable()) { + DLOG(WARNING) << info.key_system << " not registered"; + continue; } +#endif // defined(OS_ANDROID) + + key_system_map_[info.key_system] = info; } } -bool KeySystemsImpl::IsConcreteSupportedKeySystem( - const std::string& key_system) const { +// Adds the MIME type with the codec mask after verifying the validity. +// Only this function should modify |mime_type_to_codec_mask_map_|. +void KeySystemsImpl::RegisterMimeType(const std::string& mime_type, + EmeCodec codecs_mask) { DCHECK(thread_checker_.CalledOnValidThread()); - return concrete_key_system_map_.count(key_system) != 0; + DCHECK(!mime_type_to_codec_mask_map_.count(mime_type)); + DCHECK(IsValidMimeTypeCodecsCombination(mime_type, codecs_mask)); + + mime_type_to_codec_mask_map_[mime_type] = static_cast<EmeCodec>(codecs_mask); } -bool KeySystemsImpl::IsSupportedContainer( - const std::string& container, - SupportedCodecs key_system_supported_codecs) const { - DCHECK(thread_checker_.CalledOnValidThread()); - DCHECK(!container.empty()); - - // When checking container support for EME, "audio/foo" should be treated the - // same as "video/foo". Convert the |container| to achieve this. - // TODO(xhwang): Replace this with real checks against supported initDataTypes - // combined with supported demuxers. - std::string canonical_container = container; - if (container.find("audio/") == 0) - canonical_container.replace(0, 6, "video/"); - - // A container is supported iif at least one codec in that container is - // supported. - SupportedCodecs supported_codecs = - GetCodecMaskForContainer(canonical_container); - return (supported_codecs & key_system_supported_codecs) != 0; -} - -bool KeySystemsImpl::IsSupportedContainerAndCodecs( - const std::string& container, - const std::vector<std::string>& codecs, - SupportedCodecs key_system_supported_codecs) const { +// Returns whether |mime_type| follows a valid format and the specified codecs +// are of the correct type based on |*_codec_mask_|. +// Only audio/ or video/ MIME types with their respective codecs are allowed. +bool KeySystemsImpl::IsValidMimeTypeCodecsCombination( + const std::string& mime_type, + SupportedCodecs codecs_mask) const { DCHECK(thread_checker_.CalledOnValidThread()); - DCHECK(!container.empty()); - DCHECK(!codecs.empty()); - DCHECK(IsSupportedContainer(container, key_system_supported_codecs)); - - SupportedCodecs container_supported_codecs = - GetCodecMaskForContainer(container); - - for (size_t i = 0; i < codecs.size(); ++i) { - if (codecs[i].empty()) - continue; - - EmeCodec codec = GetCodecForString(codecs[i]); - - // Unsupported codec. - if (!(codec & key_system_supported_codecs)) - return false; - - // Unsupported codec/container combination, e.g. "video/webm" and "avc1". - if (!(codec & container_supported_codecs)) - return false; - } + if (!codecs_mask) + return false; + if (base::StartsWith(mime_type, "audio/", base::CompareCase::SENSITIVE)) + return !(codecs_mask & ~audio_codec_mask_); + if (base::StartsWith(mime_type, "video/", base::CompareCase::SENSITIVE)) + return !(codecs_mask & ~video_codec_mask_); - return true; + return false; } bool KeySystemsImpl::IsSupportedInitDataType( @@ -526,10 +457,9 @@ bool KeySystemsImpl::IsSupportedInitDataType( EmeInitDataType init_data_type) const { DCHECK(thread_checker_.CalledOnValidThread()); - // Locate |key_system|. Only concrete key systems are supported in unprefixed. KeySystemInfoMap::const_iterator key_system_iter = - concrete_key_system_map_.find(key_system); - if (key_system_iter == concrete_key_system_map_.end()) { + key_system_map_.find(key_system); + if (key_system_iter == key_system_map_.end()) { NOTREACHED(); return false; } @@ -551,49 +481,6 @@ bool KeySystemsImpl::IsSupportedInitDataType( return false; } -bool KeySystemsImpl::PrefixedIsSupportedKeySystemWithMediaMimeType( - const std::string& mime_type, - const std::vector<std::string>& codecs, - const std::string& key_system) { - DCHECK(thread_checker_.CalledOnValidThread()); - - const std::string& concrete_key_system = - PrefixedGetConcreteKeySystemNameFor(key_system); - - bool has_type = !mime_type.empty(); - - key_systems_support_uma_.ReportKeySystemQuery(key_system, has_type); - - // Check key system support. - KeySystemInfoMap::const_iterator key_system_iter = - concrete_key_system_map_.find(concrete_key_system); - if (key_system_iter == concrete_key_system_map_.end()) - return false; - - key_systems_support_uma_.ReportKeySystemSupport(key_system, false); - - if (!has_type) { - DCHECK(codecs.empty()); - return true; - } - - SupportedCodecs key_system_supported_codecs = - key_system_iter->second.supported_codecs; - - if (!IsSupportedContainer(mime_type, key_system_supported_codecs)) - return false; - - if (!codecs.empty() && - !IsSupportedContainerAndCodecs( - mime_type, codecs, key_system_supported_codecs)) { - return false; - } - - key_systems_support_uma_.ReportKeySystemSupport(key_system, true); - - return true; -} - std::string KeySystemsImpl::GetKeySystemNameForUMA( const std::string& key_system) const { DCHECK(thread_checker_.CalledOnValidThread()); @@ -606,14 +493,13 @@ std::string KeySystemsImpl::GetKeySystemNameForUMA( return iter->second; } -bool KeySystemsImpl::UseAesDecryptor( - const std::string& concrete_key_system) const { +bool KeySystemsImpl::UseAesDecryptor(const std::string& key_system) const { DCHECK(thread_checker_.CalledOnValidThread()); KeySystemInfoMap::const_iterator key_system_iter = - concrete_key_system_map_.find(concrete_key_system); - if (key_system_iter == concrete_key_system_map_.end()) { - DLOG(ERROR) << concrete_key_system << " is not a known concrete system"; + key_system_map_.find(key_system); + if (key_system_iter == key_system_map_.end()) { + DLOG(ERROR) << key_system << " is not a known system"; return false; } @@ -621,30 +507,22 @@ bool KeySystemsImpl::UseAesDecryptor( } #if defined(ENABLE_PEPPER_CDMS) -std::string KeySystemsImpl::GetPepperType( - const std::string& concrete_key_system) const { +std::string KeySystemsImpl::GetPepperType(const std::string& key_system) const { DCHECK(thread_checker_.CalledOnValidThread()); KeySystemInfoMap::const_iterator key_system_iter = - concrete_key_system_map_.find(concrete_key_system); - if (key_system_iter == concrete_key_system_map_.end()) { - DLOG(FATAL) << concrete_key_system << " is not a known concrete system"; - return std::string(); + key_system_map_.find(key_system); + if (key_system_iter == key_system_map_.end()) { + DLOG(FATAL) << key_system << " is not a known system"; + return std::string(); } const std::string& type = key_system_iter->second.pepper_type; - DLOG_IF(FATAL, type.empty()) << concrete_key_system << " is not Pepper-based"; + DLOG_IF(FATAL, type.empty()) << key_system << " is not Pepper-based"; return type; } #endif -void KeySystemsImpl::AddContainerMask(const std::string& container, - uint32_t mask) { - DCHECK(thread_checker_.CalledOnValidThread()); - DCHECK(!container_to_codec_mask_map_.count(container)); - container_to_codec_mask_map_[container] = static_cast<EmeCodec>(mask); -} - void KeySystemsImpl::AddCodecMask(EmeMediaType media_type, const std::string& codec, uint32_t mask) { @@ -658,10 +536,15 @@ void KeySystemsImpl::AddCodecMask(EmeMediaType media_type, } } +void KeySystemsImpl::AddMimeTypeCodecMask(const std::string& mime_type, + uint32_t codecs_mask) { + RegisterMimeType(mime_type, static_cast<EmeCodec>(codecs_mask)); +} + bool KeySystemsImpl::IsSupportedKeySystem(const std::string& key_system) const { DCHECK(thread_checker_.CalledOnValidThread()); - if (!IsConcreteSupportedKeySystem(key_system)) + if (!key_system_map_.count(key_system)) return false; // TODO(ddorwin): Move this to where we add key systems when prefixed EME is @@ -683,27 +566,24 @@ EmeConfigRule KeySystemsImpl::GetContentTypeConfigRule( const std::vector<std::string>& codecs) const { DCHECK(thread_checker_.CalledOnValidThread()); - // Make sure the container matches |media_type|. - SupportedCodecs media_type_codec_mask = EME_CODEC_NONE; + // Make sure the container MIME type matches |media_type|. switch (media_type) { case EmeMediaType::AUDIO: if (!base::StartsWith(container_mime_type, "audio/", base::CompareCase::SENSITIVE)) return EmeConfigRule::NOT_SUPPORTED; - media_type_codec_mask = audio_codec_mask_; break; case EmeMediaType::VIDEO: if (!base::StartsWith(container_mime_type, "video/", base::CompareCase::SENSITIVE)) return EmeConfigRule::NOT_SUPPORTED; - media_type_codec_mask = video_codec_mask_; break; } // Look up the key system's supported codecs. KeySystemInfoMap::const_iterator key_system_iter = - concrete_key_system_map_.find(key_system); - if (key_system_iter == concrete_key_system_map_.end()) { + key_system_map_.find(key_system); + if (key_system_iter == key_system_map_.end()) { NOTREACHED(); return EmeConfigRule::NOT_SUPPORTED; } @@ -717,16 +597,16 @@ EmeConfigRule KeySystemsImpl::GetContentTypeConfigRule( // Check that the container is supported by the key system. (This check is // necessary because |codecs| may be empty.) - SupportedCodecs container_codec_mask = - GetCodecMaskForContainer(container_mime_type) & media_type_codec_mask; - if ((key_system_codec_mask & container_codec_mask) == 0) + SupportedCodecs mime_type_codec_mask = + GetCodecMaskForMimeType(container_mime_type); + if ((key_system_codec_mask & mime_type_codec_mask) == 0) return EmeConfigRule::NOT_SUPPORTED; // Check that the codecs are supported by the key system and container. EmeConfigRule support = EmeConfigRule::SUPPORTED; for (size_t i = 0; i < codecs.size(); i++) { SupportedCodecs codec = GetCodecForString(codecs[i]); - if ((codec & key_system_codec_mask & container_codec_mask) == 0) + if ((codec & key_system_codec_mask & mime_type_codec_mask) == 0) return EmeConfigRule::NOT_SUPPORTED; #if defined(OS_ANDROID) // Check whether the codec supports a hardware-secure mode. The goal is to @@ -756,8 +636,8 @@ EmeConfigRule KeySystemsImpl::GetRobustnessConfigRule( return EmeConfigRule::NOT_SUPPORTED; KeySystemInfoMap::const_iterator key_system_iter = - concrete_key_system_map_.find(key_system); - if (key_system_iter == concrete_key_system_map_.end()) { + key_system_map_.find(key_system); + if (key_system_iter == key_system_map_.end()) { NOTREACHED(); return EmeConfigRule::NOT_SUPPORTED; } @@ -821,8 +701,8 @@ EmeSessionTypeSupport KeySystemsImpl::GetPersistentLicenseSessionSupport( DCHECK(thread_checker_.CalledOnValidThread()); KeySystemInfoMap::const_iterator key_system_iter = - concrete_key_system_map_.find(key_system); - if (key_system_iter == concrete_key_system_map_.end()) { + key_system_map_.find(key_system); + if (key_system_iter == key_system_map_.end()) { NOTREACHED(); return EmeSessionTypeSupport::INVALID; } @@ -834,8 +714,8 @@ EmeSessionTypeSupport KeySystemsImpl::GetPersistentReleaseMessageSessionSupport( DCHECK(thread_checker_.CalledOnValidThread()); KeySystemInfoMap::const_iterator key_system_iter = - concrete_key_system_map_.find(key_system); - if (key_system_iter == concrete_key_system_map_.end()) { + key_system_map_.find(key_system); + if (key_system_iter == key_system_map_.end()) { NOTREACHED(); return EmeSessionTypeSupport::INVALID; } @@ -847,8 +727,8 @@ EmeFeatureSupport KeySystemsImpl::GetPersistentStateSupport( DCHECK(thread_checker_.CalledOnValidThread()); KeySystemInfoMap::const_iterator key_system_iter = - concrete_key_system_map_.find(key_system); - if (key_system_iter == concrete_key_system_map_.end()) { + key_system_map_.find(key_system); + if (key_system_iter == key_system_map_.end()) { NOTREACHED(); return EmeFeatureSupport::INVALID; } @@ -860,8 +740,8 @@ EmeFeatureSupport KeySystemsImpl::GetDistinctiveIdentifierSupport( DCHECK(thread_checker_.CalledOnValidThread()); KeySystemInfoMap::const_iterator key_system_iter = - concrete_key_system_map_.find(key_system); - if (key_system_iter == concrete_key_system_map_.end()) { + key_system_map_.find(key_system); + if (key_system_iter == key_system_map_.end()) { NOTREACHED(); return EmeFeatureSupport::INVALID; } @@ -874,56 +754,23 @@ KeySystems* KeySystems::GetInstance() { //------------------------------------------------------------------------------ -std::string GetUnprefixedKeySystemName(const std::string& key_system) { - if (key_system == kClearKeyKeySystem) - return kUnsupportedClearKeyKeySystem; - - if (key_system == kPrefixedClearKeyKeySystem) - return kClearKeyKeySystem; - - return key_system; -} - -std::string GetPrefixedKeySystemName(const std::string& key_system) { - DCHECK_NE(key_system, kPrefixedClearKeyKeySystem); - - if (key_system == kClearKeyKeySystem) - return kPrefixedClearKeyKeySystem; - - return key_system; -} - -bool PrefixedIsSupportedConcreteKeySystem(const std::string& key_system) { - return KeySystemsImpl::GetInstance()->IsConcreteSupportedKeySystem( - key_system); -} - bool IsSupportedKeySystemWithInitDataType(const std::string& key_system, EmeInitDataType init_data_type) { return KeySystemsImpl::GetInstance()->IsSupportedInitDataType(key_system, init_data_type); } -bool PrefixedIsSupportedKeySystemWithMediaMimeType( - const std::string& mime_type, - const std::vector<std::string>& codecs, - const std::string& key_system) { - return KeySystemsImpl::GetInstance() - ->PrefixedIsSupportedKeySystemWithMediaMimeType(mime_type, codecs, - key_system); -} - std::string GetKeySystemNameForUMA(const std::string& key_system) { return KeySystemsImpl::GetInstance()->GetKeySystemNameForUMA(key_system); } -bool CanUseAesDecryptor(const std::string& concrete_key_system) { - return KeySystemsImpl::GetInstance()->UseAesDecryptor(concrete_key_system); +bool CanUseAesDecryptor(const std::string& key_system) { + return KeySystemsImpl::GetInstance()->UseAesDecryptor(key_system); } #if defined(ENABLE_PEPPER_CDMS) -std::string GetPepperType(const std::string& concrete_key_system) { - return KeySystemsImpl::GetInstance()->GetPepperType(concrete_key_system); +std::string GetPepperType(const std::string& key_system) { + return KeySystemsImpl::GetInstance()->GetPepperType(key_system); } #endif @@ -933,15 +780,15 @@ std::string GetPepperType(const std::string& concrete_key_system) { // "media" where "UNIT_TEST" is not defined. So we need to specify // "MEDIA_EXPORT" here again so that they are visible to tests. -MEDIA_EXPORT void AddContainerMask(const std::string& container, - uint32_t mask) { - KeySystemsImpl::GetInstance()->AddContainerMask(container, mask); -} - MEDIA_EXPORT void AddCodecMask(EmeMediaType media_type, const std::string& codec, uint32_t mask) { KeySystemsImpl::GetInstance()->AddCodecMask(media_type, codec, mask); } +MEDIA_EXPORT void AddMimeTypeCodecMask(const std::string& mime_type, + uint32_t mask) { + KeySystemsImpl::GetInstance()->AddMimeTypeCodecMask(mime_type, mask); +} + } // namespace media diff --git a/chromium/media/base/key_systems.h b/chromium/media/base/key_systems.h index dcdd254842d..92c15d899ad 100644 --- a/chromium/media/base/key_systems.h +++ b/chromium/media/base/key_systems.h @@ -16,8 +16,7 @@ namespace media { -// Provides an interface for querying registered key systems. The exposed API is -// only intended to support unprefixed EME. +// Provides an interface for querying registered key systems. // // Many of the original static methods are still available, they should be // migrated into this interface over time (or removed). @@ -71,58 +70,34 @@ class MEDIA_EXPORT KeySystems { virtual ~KeySystems() {}; }; -// Prefixed EME API only supports prefixed (webkit-) key system name for -// certain key systems. But internally only unprefixed key systems are -// supported. The following two functions help convert between prefixed and -// unprefixed key system names. - -// Gets the unprefixed key system name for |key_system|. -MEDIA_EXPORT std::string GetUnprefixedKeySystemName( - const std::string& key_system); - -// Gets the prefixed key system name for |key_system|. -MEDIA_EXPORT std::string GetPrefixedKeySystemName( - const std::string& key_system); - +// TODO(ddorwin): WebContentDecryptionModuleSessionImpl::initializeNewSession() +// is violating this rule! https://crbug.com/249976. // Use for prefixed EME only! MEDIA_EXPORT bool IsSupportedKeySystemWithInitDataType( const std::string& key_system, EmeInitDataType init_data_type); -// Use for prefixed EME only! -// Returns whether |key_system| is a real supported key system that can be -// instantiated. -// Abstract parent |key_system| strings will return false. -MEDIA_EXPORT bool PrefixedIsSupportedConcreteKeySystem( - const std::string& key_system); - -// Use for prefixed EME only! -// Returns whether |key_system| supports the specified media type and codec(s). -// To be used with prefixed EME only as it generates UMAs based on the query. -MEDIA_EXPORT bool PrefixedIsSupportedKeySystemWithMediaMimeType( - const std::string& mime_type, - const std::vector<std::string>& codecs, - const std::string& key_system); - // Returns a name for |key_system| suitable to UMA logging. MEDIA_EXPORT std::string GetKeySystemNameForUMA(const std::string& key_system); -// Returns whether AesDecryptor can be used for the given |concrete_key_system|. -MEDIA_EXPORT bool CanUseAesDecryptor(const std::string& concrete_key_system); +// Returns whether AesDecryptor can be used for the given |key_system|. +MEDIA_EXPORT bool CanUseAesDecryptor(const std::string& key_system); #if defined(ENABLE_PEPPER_CDMS) -// Returns the Pepper MIME type for |concrete_key_system|. -// Returns empty string if |concrete_key_system| is unknown or not Pepper-based. -MEDIA_EXPORT std::string GetPepperType( - const std::string& concrete_key_system); +// Returns the Pepper MIME type for |key_system|. +// Returns empty string if |key_system| is unknown or not Pepper-based. +MEDIA_EXPORT std::string GetPepperType(const std::string& key_system); #endif #if defined(UNIT_TEST) // Helper functions to add container/codec types for testing purposes. -MEDIA_EXPORT void AddContainerMask(const std::string& container, uint32_t mask); +// Call AddCodecMask() first to ensure the mask values passed to +// AddMimeTypeCodecMask() already exist. MEDIA_EXPORT void AddCodecMask(EmeMediaType media_type, const std::string& codec, uint32_t mask); +MEDIA_EXPORT void AddMimeTypeCodecMask(const std::string& mime_type, + uint32_t mask); #endif // defined(UNIT_TEST) } // namespace media diff --git a/chromium/media/base/key_systems_support_uma.cc b/chromium/media/base/key_systems_support_uma.cc deleted file mode 100644 index 7ac7a14b43e..00000000000 --- a/chromium/media/base/key_systems_support_uma.cc +++ /dev/null @@ -1,133 +0,0 @@ -// 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 "media/base/key_systems_support_uma.h" - -#include "base/metrics/histogram.h" -#include "media/base/key_systems.h" - -namespace media { - -namespace { - -const char kKeySystemSupportUMAPrefix[] = "Media.EME.KeySystemSupport."; - -// These values are reported to UMA. Do not change the existing values! -enum KeySystemSupportStatus { - KEY_SYSTEM_QUERIED = 0, - KEY_SYSTEM_SUPPORTED = 1, - KEY_SYSTEM_WITH_TYPE_QUERIED = 2, - KEY_SYSTEM_WITH_TYPE_SUPPORTED = 3, - KEY_SYSTEM_SUPPORT_STATUS_COUNT -}; - -// Reports an event only once. -class OneTimeReporter { - public: - OneTimeReporter(const std::string& key_system, KeySystemSupportStatus status); - ~OneTimeReporter(); - - void Report(); - - private: - bool is_reported_; - const std::string key_system_; - const KeySystemSupportStatus status_; -}; - -OneTimeReporter::OneTimeReporter(const std::string& key_system, - KeySystemSupportStatus status) - : is_reported_(false), key_system_(key_system), status_(status) { -} - -OneTimeReporter::~OneTimeReporter() {} - -void OneTimeReporter::Report() { - if (is_reported_) - return; - - // Not using UMA_HISTOGRAM_ENUMERATION directly because UMA_* macros require - // the names to be constant throughout the process' lifetime. - base::LinearHistogram::FactoryGet( - kKeySystemSupportUMAPrefix + GetKeySystemNameForUMA(key_system_), 1, - KEY_SYSTEM_SUPPORT_STATUS_COUNT, KEY_SYSTEM_SUPPORT_STATUS_COUNT + 1, - base::Histogram::kUmaTargetedHistogramFlag)->Add(status_); - - is_reported_ = true; -} - -} // namespace - -class KeySystemsSupportUMA::Reporter { - public: - explicit Reporter(const std::string& key_system); - ~Reporter(); - - void Report(bool has_type, bool is_supported); - - private: - const std::string key_system_; - - OneTimeReporter call_reporter_; - OneTimeReporter call_with_type_reporter_; - OneTimeReporter support_reporter_; - OneTimeReporter support_with_type_reporter_; -}; - -KeySystemsSupportUMA::Reporter::Reporter(const std::string& key_system) - : key_system_(key_system), - call_reporter_(key_system, KEY_SYSTEM_QUERIED), - call_with_type_reporter_(key_system, KEY_SYSTEM_WITH_TYPE_QUERIED), - support_reporter_(key_system, KEY_SYSTEM_SUPPORTED), - support_with_type_reporter_(key_system, KEY_SYSTEM_WITH_TYPE_SUPPORTED) {} - -KeySystemsSupportUMA::Reporter::~Reporter() {} - -void KeySystemsSupportUMA::Reporter::Report(bool has_type, bool is_supported) { - call_reporter_.Report(); - if (has_type) - call_with_type_reporter_.Report(); - - if (!is_supported) - return; - - support_reporter_.Report(); - if (has_type) - support_with_type_reporter_.Report(); -} - -KeySystemsSupportUMA::KeySystemsSupportUMA() {} - -KeySystemsSupportUMA::~KeySystemsSupportUMA() {} - -void KeySystemsSupportUMA::AddKeySystemToReport(const std::string& key_system) { - DCHECK(!GetReporter(key_system)); - reporters_.set(key_system, scoped_ptr<Reporter>(new Reporter(key_system))); -} - -void KeySystemsSupportUMA::ReportKeySystemQuery(const std::string& key_system, - bool has_type) { - Reporter* reporter = GetReporter(key_system); - if (!reporter) - return; - reporter->Report(has_type, false); -} - -void KeySystemsSupportUMA::ReportKeySystemSupport(const std::string& key_system, - bool has_type) { - Reporter* reporter = GetReporter(key_system); - if (!reporter) - return; - reporter->Report(has_type, true); -} - -KeySystemsSupportUMA::Reporter* KeySystemsSupportUMA::GetReporter( - const std::string& key_system) { - Reporters::iterator reporter = reporters_.find(key_system); - if (reporter == reporters_.end()) - return NULL; - return reporter->second; -} - -} // namespace media diff --git a/chromium/media/base/key_systems_support_uma.h b/chromium/media/base/key_systems_support_uma.h deleted file mode 100644 index 28f334db995..00000000000 --- a/chromium/media/base/key_systems_support_uma.h +++ /dev/null @@ -1,57 +0,0 @@ -// 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 MEDIA_BASE_KEY_SYSTEMS_SUPPORT_UMA_H_ -#define MEDIA_BASE_KEY_SYSTEMS_SUPPORT_UMA_H_ - -#include <string> - -#include "base/containers/scoped_ptr_hash_map.h" - -namespace media { - -// Key system support UMA statistics for queried key systems. -// 1. The key system is queried (with or without a MIME type). -// 2. The key system is queried with a MIME type. -// 3. The queried key system is supported (with or without a MIME type). This is -// reported when the key system is supported when queried, regardless of -// whether a MIME type is specified. -// 4. The queried key system is supported with a MIME type. This is reported -// when the key system is supported when queried without a MIME type -// specified. -// Note: All 4 stats are only reported once per renderer process per key system. -class KeySystemsSupportUMA { - public: - KeySystemsSupportUMA(); - ~KeySystemsSupportUMA(); - - // Adds a |key_system| for which query/support statistics are reported. - // If you use this function to add key system to report, make sure to update - // AddKeySystemSupportActions() in tools/metrics/actions/extract_actions.py. - void AddKeySystemToReport(const std::string& key_system); - - // Reports that the |key_system| is queried. When |has_type|, also reports - // that the |key_system| with a MIME type is queried. - void ReportKeySystemQuery(const std::string& key_system, bool has_type); - - // Reports that the queried |key_system| is supported. When |has_type| (a - // a MIME type is specified in the query), also reports that the queried - // |key_system| is supported with that MIME type. - void ReportKeySystemSupport(const std::string& key_system, bool has_type); - - private: - class Reporter; - - // Returns the Reporter for |key_system|. Returns NULL if |key_system| was not - // added for UMA reporting. - Reporter* GetReporter(const std::string& key_system); - - // Key system <-> Reporter map. - typedef base::ScopedPtrHashMap<std::string, scoped_ptr<Reporter>> Reporters; - Reporters reporters_; -}; - -} // namespace media - -#endif // MEDIA_BASE_KEY_SYSTEMS_SUPPORT_UMA_H_ diff --git a/chromium/media/base/key_systems_unittest.cc b/chromium/media/base/key_systems_unittest.cc index 12543946b0f..44b881939c9 100644 --- a/chromium/media/base/key_systems_unittest.cc +++ b/chromium/media/base/key_systems_unittest.cc @@ -16,6 +16,7 @@ #include "media/base/eme_constants.h" #include "media/base/key_system_info.h" #include "media/base/key_systems.h" +#include "media/base/media.h" #include "media/base/media_client.h" #include "testing/gtest/include/gtest/gtest.h" @@ -25,14 +26,11 @@ namespace media { // kUsesAes uses the AesDecryptor like Clear Key. // kExternal uses an external CDM, such as Pepper-based or Android platform CDM. const char kUsesAes[] = "x-org.example.clear"; -const char kUsesAesParent[] = "x-org.example"; // Not registered. const char kUseAesNameForUMA[] = "UseAes"; const char kExternal[] = "x-com.example.test"; -const char kExternalParent[] = "x-com.example"; const char kExternalNameForUMA[] = "External"; const char kClearKey[] = "org.w3.clearkey"; -const char kPrefixedClearKey[] = "webkit-org.w3.clearkey"; const char kExternalClearKey[] = "org.chromium.externalclearkey"; const char kAudioWebM[] = "audio/webm"; @@ -93,14 +91,26 @@ static void AddContainerAndCodecMasksForTest() { if (is_test_masks_added) return; - AddContainerMask("audio/foo", TEST_CODEC_FOO_AUDIO_ALL); - AddContainerMask("video/foo", TEST_CODEC_FOO_ALL); AddCodecMask(EmeMediaType::AUDIO, "fooaudio", TEST_CODEC_FOO_AUDIO); AddCodecMask(EmeMediaType::VIDEO, "foovideo", TEST_CODEC_FOO_VIDEO); + AddMimeTypeCodecMask("audio/foo", TEST_CODEC_FOO_AUDIO_ALL); + AddMimeTypeCodecMask("video/foo", TEST_CODEC_FOO_VIDEO_ALL); is_test_masks_added = true; } +static bool CanRunExternalKeySystemTests() { +#if defined(OS_ANDROID) + if (HasPlatformDecoderSupport()) + return true; + + EXPECT_FALSE(IsSupportedKeySystem(kExternal)); + return false; +#else + return true; +#endif +} + class TestMediaClient : public MediaClient { public: TestMediaClient(); @@ -143,9 +153,9 @@ TestMediaClient::~TestMediaClient() { void TestMediaClient::AddKeySystemsInfoForUMA( std::vector<KeySystemInfoForUMA>* key_systems_info_for_uma) { key_systems_info_for_uma->push_back( - media::KeySystemInfoForUMA(kUsesAes, kUseAesNameForUMA, false)); + media::KeySystemInfoForUMA(kUsesAes, kUseAesNameForUMA)); key_systems_info_for_uma->push_back( - media::KeySystemInfoForUMA(kExternal, kExternalNameForUMA, true)); + media::KeySystemInfoForUMA(kExternal, kExternalNameForUMA)); } bool TestMediaClient::IsKeySystemsUpdateNeeded() { @@ -209,7 +219,6 @@ void TestMediaClient::AddExternalKeySystem( ext.persistent_release_message_support = EmeSessionTypeSupport::NOT_SUPPORTED; ext.persistent_state_support = EmeFeatureSupport::ALWAYS_ENABLED; ext.distinctive_identifier_support = EmeFeatureSupport::ALWAYS_ENABLED; - ext.parent_key_system = kExternalParent; #if defined(ENABLE_PEPPER_CDMS) ext.pepper_type = "application/x-ppapi-external-cdm"; #endif // defined(ENABLE_PEPPER_CDMS) @@ -367,10 +376,6 @@ TEST_F(KeySystemsTest, ClearKey) { kVideoWebM, no_codecs(), kClearKey)); EXPECT_EQ("ClearKey", GetKeySystemNameForUMA(kClearKey)); - - // Prefixed Clear Key is not supported internally. - EXPECT_FALSE(IsSupportedKeySystem(kPrefixedClearKey)); - EXPECT_EQ("Unknown", GetKeySystemNameForUMA(kPrefixedClearKey)); } TEST_F(KeySystemsTest, ClearKeyWithInitDataType) { @@ -396,9 +401,8 @@ TEST_F(KeySystemsTest, Basic_UnrecognizedKeySystem) { #if defined(ENABLE_PEPPER_CDMS) std::string type; - EXPECT_DEBUG_DEATH( - type = GetPepperType(kUnrecognized), - "x-org.example.unrecognized is not a known concrete system"); + EXPECT_DEBUG_DEATH(type = GetPepperType(kUnrecognized), + "x-org.example.unrecognized is not a known system"); EXPECT_TRUE(type.empty()); #endif } @@ -469,22 +473,6 @@ TEST_F(KeySystemsTest, kAudioWebM, fooaudio_codec(), kUsesAes)); } -// No parent is registered for UsesAes. -TEST_F(KeySystemsTest, Parent_NoParentRegistered) { - EXPECT_FALSE(IsSupportedKeySystem(kUsesAesParent)); - - // The parent is not supported for most things. - EXPECT_EQ("Unknown", GetKeySystemNameForUMA(kUsesAesParent)); - EXPECT_FALSE(CanUseAesDecryptor(kUsesAesParent)); - -#if defined(ENABLE_PEPPER_CDMS) - std::string type; - EXPECT_DEBUG_DEATH(type = GetPepperType(kUsesAesParent), - "x-org.example is not a known concrete system"); - EXPECT_TRUE(type.empty()); -#endif -} - TEST_F(KeySystemsTest, IsSupportedKeySystem_InvalidVariants) { // Case sensitive. EXPECT_FALSE(IsSupportedKeySystem("x-org.example.ClEaR")); @@ -495,7 +483,10 @@ TEST_F(KeySystemsTest, IsSupportedKeySystem_InvalidVariants) { // Extra period. EXPECT_FALSE(IsSupportedKeySystem("x-org.example.clear.")); + + // Prefix. EXPECT_FALSE(IsSupportedKeySystem("x-org.example.")); + EXPECT_FALSE(IsSupportedKeySystem("x-org.example")); // Incomplete. EXPECT_FALSE(IsSupportedKeySystem("x-org.example.clea")); @@ -510,8 +501,6 @@ TEST_F(KeySystemsTest, IsSupportedKeySystem_InvalidVariants) { TEST_F(KeySystemsTest, IsSupportedKeySystemWithMediaMimeType_NoType) { EXPECT_FALSE(IsSupportedKeySystemWithMediaMimeType( std::string(), no_codecs(), kUsesAes)); - EXPECT_FALSE(IsSupportedKeySystemWithMediaMimeType( - std::string(), no_codecs(), kUsesAesParent)); EXPECT_FALSE(IsSupportedKeySystemWithMediaMimeType(std::string(), no_codecs(), "x-org.example.foo")); @@ -574,6 +563,9 @@ TEST_F(KeySystemsTest, // TEST_F(KeySystemsTest, Basic_ExternalDecryptor) { + if (!CanRunExternalKeySystemTests()) + return; + EXPECT_TRUE(IsSupportedKeySystem(kExternal)); EXPECT_TRUE(IsSupportedKeySystemWithMediaMimeType( kVideoWebM, no_codecs(), kExternal)); @@ -584,27 +576,12 @@ TEST_F(KeySystemsTest, Basic_ExternalDecryptor) { #endif // defined(ENABLE_PEPPER_CDMS) } -TEST_F(KeySystemsTest, Parent_ParentRegistered) { - // Unprefixed has no parent key system support. - EXPECT_FALSE(IsSupportedKeySystem(kExternalParent)); - EXPECT_TRUE(PrefixedIsSupportedKeySystemWithMediaMimeType( - kVideoWebM, no_codecs(), kExternalParent)); - - // The parent is not supported for most things. - EXPECT_EQ("Unknown", GetKeySystemNameForUMA(kExternalParent)); - EXPECT_FALSE(CanUseAesDecryptor(kExternalParent)); - -#if defined(ENABLE_PEPPER_CDMS) - std::string type; - EXPECT_DEBUG_DEATH(type = GetPepperType(kExternalParent), - "x-com.example is not a known concrete system"); - EXPECT_TRUE(type.empty()); -#endif -} - TEST_F( KeySystemsTest, IsSupportedKeySystemWithMediaMimeType_ExternalDecryptor_TypesContainer1) { + if (!CanRunExternalKeySystemTests()) + return; + // Valid video types. EXPECT_TRUE(IsSupportedKeySystemWithMediaMimeType( kVideoWebM, no_codecs(), kExternal)); @@ -625,25 +602,6 @@ TEST_F( EXPECT_FALSE(IsSupportedKeySystemWithMediaMimeType( kVideoWebM, vorbis_codec(), kExternal)); - // Valid video types - parent key system. - // Prefixed has parent key system support. - EXPECT_TRUE(PrefixedIsSupportedKeySystemWithMediaMimeType( - kVideoWebM, no_codecs(), kExternalParent)); - EXPECT_TRUE(PrefixedIsSupportedKeySystemWithMediaMimeType( - kVideoWebM, vp8_codec(), kExternalParent)); - EXPECT_TRUE(PrefixedIsSupportedKeySystemWithMediaMimeType( - kVideoWebM, vp80_codec(), kExternalParent)); - EXPECT_TRUE(PrefixedIsSupportedKeySystemWithMediaMimeType( - kVideoWebM, vp8_and_vorbis_codecs(), kExternalParent)); - EXPECT_TRUE(PrefixedIsSupportedKeySystemWithMediaMimeType( - kVideoWebM, vp9_codec(), kExternalParent)); - EXPECT_TRUE(PrefixedIsSupportedKeySystemWithMediaMimeType( - kVideoWebM, vp90_codec(), kExternalParent)); - EXPECT_TRUE(PrefixedIsSupportedKeySystemWithMediaMimeType( - kVideoWebM, vp9_and_vorbis_codecs(), kExternalParent)); - EXPECT_TRUE(PrefixedIsSupportedKeySystemWithMediaMimeType( - kVideoWebM, vorbis_codec(), kExternalParent)); - // Non-Webm codecs. EXPECT_FALSE(IsSupportedKeySystemWithMediaMimeType( kVideoWebM, foovideo_codec(), kExternal)); @@ -658,13 +616,6 @@ TEST_F( EXPECT_TRUE(IsSupportedKeySystemWithAudioMimeType( kAudioWebM, vorbis_codec(), kExternal)); - // Valid audio types - parent key system. - // Prefixed has parent key system support. - EXPECT_TRUE(PrefixedIsSupportedKeySystemWithMediaMimeType( - kAudioWebM, no_codecs(), kExternalParent)); - EXPECT_TRUE(PrefixedIsSupportedKeySystemWithMediaMimeType( - kAudioWebM, vorbis_codec(), kExternalParent)); - // Non-audio codecs. EXPECT_FALSE(IsSupportedKeySystemWithAudioMimeType( kAudioWebM, vp8_codec(), kExternal)); @@ -683,6 +634,9 @@ TEST_F( TEST_F( KeySystemsTest, IsSupportedKeySystemWithMediaMimeType_ExternalDecryptor_TypesContainer2) { + if (!CanRunExternalKeySystemTests()) + return; + // Valid video types. EXPECT_TRUE(IsSupportedKeySystemWithMediaMimeType( kVideoFoo, no_codecs(), kExternal)); @@ -695,17 +649,6 @@ TEST_F( EXPECT_FALSE(IsSupportedKeySystemWithMediaMimeType( kVideoFoo, fooaudio_codec(), kExternal)); - // Valid video types - parent key system. - // Prefixed has parent key system support. - EXPECT_TRUE(PrefixedIsSupportedKeySystemWithMediaMimeType( - kVideoFoo, no_codecs(), kExternalParent)); - EXPECT_TRUE(PrefixedIsSupportedKeySystemWithMediaMimeType( - kVideoFoo, foovideo_codec(), kExternalParent)); - EXPECT_TRUE(PrefixedIsSupportedKeySystemWithMediaMimeType( - kVideoFoo, foovideo_and_fooaudio_codecs(), kExternalParent)); - EXPECT_TRUE(PrefixedIsSupportedKeySystemWithMediaMimeType( - kVideoFoo, fooaudio_codec(), kExternalParent)); - // Extended codecs fail because this is handled by SimpleWebMimeRegistryImpl. // They should really pass canPlayType(). EXPECT_FALSE(IsSupportedKeySystemWithMediaMimeType( @@ -729,13 +672,6 @@ TEST_F( EXPECT_TRUE(IsSupportedKeySystemWithAudioMimeType( kAudioFoo, fooaudio_codec(), kExternal)); - // Valid audio types - parent key system. - // Prefixed has parent key system support. - EXPECT_TRUE(PrefixedIsSupportedKeySystemWithMediaMimeType( - kAudioFoo, no_codecs(), kExternalParent)); - EXPECT_TRUE(PrefixedIsSupportedKeySystemWithMediaMimeType( - kAudioFoo, fooaudio_codec(), kExternalParent)); - // Non-audio codecs. EXPECT_FALSE(IsSupportedKeySystemWithAudioMimeType( kAudioFoo, foovideo_codec(), kExternal)); @@ -749,45 +685,30 @@ TEST_F( TEST_F(KeySystemsTest, KeySystemNameForUMA) { EXPECT_EQ("ClearKey", GetKeySystemNameForUMA(kClearKey)); - // Prefixed is not supported internally. - EXPECT_EQ("Unknown", GetKeySystemNameForUMA(kPrefixedClearKey)); // External Clear Key never has a UMA name. - EXPECT_EQ("Unknown", GetKeySystemNameForUMA(kExternalClearKey)); + if (CanRunExternalKeySystemTests()) + EXPECT_EQ("Unknown", GetKeySystemNameForUMA(kExternalClearKey)); } TEST_F(KeySystemsTest, KeySystemsUpdate) { EXPECT_TRUE(IsSupportedKeySystem(kUsesAes)); EXPECT_TRUE(IsSupportedKeySystemWithMediaMimeType( kVideoWebM, no_codecs(), kUsesAes)); - EXPECT_TRUE(IsSupportedKeySystem(kExternal)); - EXPECT_TRUE(IsSupportedKeySystemWithMediaMimeType( - kVideoWebM, no_codecs(), kExternal)); - - UpdateClientKeySystems(); - - EXPECT_TRUE(IsSupportedKeySystem(kUsesAes)); - EXPECT_TRUE(IsSupportedKeySystemWithMediaMimeType( - kVideoWebM, no_codecs(), kUsesAes)); - EXPECT_FALSE(IsSupportedKeySystem(kExternal)); -} -TEST_F(KeySystemsTest, PrefixedKeySystemsUpdate) { - EXPECT_TRUE(IsSupportedKeySystem(kUsesAes)); - EXPECT_TRUE(PrefixedIsSupportedKeySystemWithMediaMimeType( - kVideoWebM, no_codecs(), kUsesAes)); - EXPECT_TRUE(IsSupportedKeySystem(kExternal)); - EXPECT_TRUE(PrefixedIsSupportedKeySystemWithMediaMimeType( - kVideoWebM, no_codecs(), kExternal)); + if (CanRunExternalKeySystemTests()) { + EXPECT_TRUE(IsSupportedKeySystem(kExternal)); + EXPECT_TRUE(IsSupportedKeySystemWithMediaMimeType(kVideoWebM, no_codecs(), + kExternal)); + } UpdateClientKeySystems(); EXPECT_TRUE(IsSupportedKeySystem(kUsesAes)); - EXPECT_TRUE(PrefixedIsSupportedKeySystemWithMediaMimeType( + EXPECT_TRUE(IsSupportedKeySystemWithMediaMimeType( kVideoWebM, no_codecs(), kUsesAes)); - EXPECT_FALSE(IsSupportedKeySystem(kExternal)); - EXPECT_FALSE(PrefixedIsSupportedKeySystemWithMediaMimeType( - kVideoWebM, no_codecs(), kExternal)); + if (CanRunExternalKeySystemTests()) + EXPECT_FALSE(IsSupportedKeySystem(kExternal)); } TEST_F(KeySystemsPotentiallySupportedNamesTest, PotentiallySupportedNames) { diff --git a/chromium/media/base/mac/BUILD.gn b/chromium/media/base/mac/BUILD.gn index c1ebc9e64e7..95540d6890f 100644 --- a/chromium/media/base/mac/BUILD.gn +++ b/chromium/media/base/mac/BUILD.gn @@ -14,6 +14,8 @@ source_set("mac") { "video_frame_mac.h", "videotoolbox_glue.h", "videotoolbox_glue.mm", + "videotoolbox_helpers.cc", + "videotoolbox_helpers.h", ] if (is_mac) { sources += [ @@ -21,6 +23,8 @@ source_set("mac") { "avfoundation_glue.mm", ] libs = [ + "AVFoundation.framework", + # Required by video_frame_mac.cc. "CoreVideo.framework", ] diff --git a/chromium/media/base/mac/avfoundation_glue.h b/chromium/media/base/mac/avfoundation_glue.h index fd744a3eeb7..a5b430412fd 100644 --- a/chromium/media/base/mac/avfoundation_glue.h +++ b/chromium/media/base/mac/avfoundation_glue.h @@ -28,10 +28,6 @@ class MEDIA_EXPORT AVFoundationGlue { // AVFoundation methods. static void InitializeAVFoundation(); - // This method returns true if the OS version supports AVFoundation and the - // AVFoundation bundle could be loaded correctly, or false otherwise. - static bool IsAVFoundationSupported(); - #if defined(__OBJC__) static NSBundle const* AVFoundationBundle(); diff --git a/chromium/media/base/mac/avfoundation_glue.mm b/chromium/media/base/mac/avfoundation_glue.mm index 23f11775798..84b77347725 100644 --- a/chromium/media/base/mac/avfoundation_glue.mm +++ b/chromium/media/base/mac/avfoundation_glue.mm @@ -4,6 +4,7 @@ #import "media/base/mac/avfoundation_glue.h" +#import <AVFoundation/AVFoundation.h> #include <dlfcn.h> #include <stddef.h> @@ -15,26 +16,21 @@ #include "base/trace_event/trace_event.h" #include "media/base/media_switches.h" -namespace { - -// Used for logging capture API usage. Classes are a partition. Elements in this -// enum should not be deleted or rearranged; the only permitted operation is to -// add new elements before CAPTURE_API_MAX, that must be equal to the last item. -enum CaptureApi { - CAPTURE_API_QTKIT_DUE_TO_OS_PREVIOUS_TO_LION = 0, - CAPTURE_API_QTKIT_FORCED_BY_FLAG = 1, - CAPTURE_API_QTKIT_DUE_TO_NO_FLAG = 2, - CAPTURE_API_QTKIT_DUE_TO_AVFOUNDATION_LOAD_ERROR = 3, - CAPTURE_API_AVFOUNDATION_LOADED_OK = 4, - CAPTURE_API_MAX = CAPTURE_API_AVFOUNDATION_LOADED_OK -}; - -void LogCaptureApi(CaptureApi api) { - UMA_HISTOGRAM_ENUMERATION("Media.VideoCaptureApi.Mac", - api, - CAPTURE_API_MAX + 1); -} +// Forward declarations of AVFoundation.h strings. +// This is needed to avoid compile time warnings since currently +// |mac_deployment_target| is 10.6. +extern NSString* const AVCaptureDeviceWasConnectedNotification; +extern NSString* const AVCaptureDeviceWasDisconnectedNotification; +extern NSString* const AVMediaTypeVideo; +extern NSString* const AVMediaTypeAudio; +extern NSString* const AVMediaTypeMuxed; +extern NSString* const AVCaptureSessionRuntimeErrorNotification; +extern NSString* const AVCaptureSessionDidStopRunningNotification; +extern NSString* const AVCaptureSessionErrorKey; +extern NSString* const AVVideoScalingModeKey; +extern NSString* const AVVideoScalingModeResizeAspectFill; +namespace { // This class is used to retrieve AVFoundation NSBundle and library handle. It // must be used as a LazyInstance so that it is initialised once and in a // thread-safe way. Normally no work is done in constructors: LazyInstance is @@ -44,37 +40,6 @@ class AVFoundationInternal { AVFoundationInternal() { bundle_ = [NSBundle bundleWithPath:@"/System/Library/Frameworks/AVFoundation.framework"]; - - const char* path = [[bundle_ executablePath] fileSystemRepresentation]; - CHECK(path); - library_handle_ = dlopen(path, RTLD_LAZY | RTLD_LOCAL); - CHECK(library_handle_) << dlerror(); - - struct { - NSString** loaded_string; - const char* symbol; - } av_strings[] = { - {&AVCaptureDeviceWasConnectedNotification_, - "AVCaptureDeviceWasConnectedNotification"}, - {&AVCaptureDeviceWasDisconnectedNotification_, - "AVCaptureDeviceWasDisconnectedNotification"}, - {&AVMediaTypeVideo_, "AVMediaTypeVideo"}, - {&AVMediaTypeAudio_, "AVMediaTypeAudio"}, - {&AVMediaTypeMuxed_, "AVMediaTypeMuxed"}, - {&AVCaptureSessionRuntimeErrorNotification_, - "AVCaptureSessionRuntimeErrorNotification"}, - {&AVCaptureSessionDidStopRunningNotification_, - "AVCaptureSessionDidStopRunningNotification"}, - {&AVCaptureSessionErrorKey_, "AVCaptureSessionErrorKey"}, - {&AVVideoScalingModeKey_, "AVVideoScalingModeKey"}, - {&AVVideoScalingModeResizeAspectFill_, - "AVVideoScalingModeResizeAspectFill"}, - }; - for (size_t i = 0; i < arraysize(av_strings); ++i) { - *av_strings[i].loaded_string = *reinterpret_cast<NSString**>( - dlsym(library_handle_, av_strings[i].symbol)); - DCHECK(*av_strings[i].loaded_string) << dlerror(); - } } NSBundle* bundle() const { return bundle_; } @@ -106,58 +71,54 @@ class AVFoundationInternal { NSBundle* bundle_; void* library_handle_; // The following members are replicas of the respectives in AVFoundation. - NSString* AVCaptureDeviceWasConnectedNotification_; - NSString* AVCaptureDeviceWasDisconnectedNotification_; - NSString* AVMediaTypeVideo_; - NSString* AVMediaTypeAudio_; - NSString* AVMediaTypeMuxed_; - NSString* AVCaptureSessionRuntimeErrorNotification_; - NSString* AVCaptureSessionDidStopRunningNotification_; - NSString* AVCaptureSessionErrorKey_; - NSString* AVVideoScalingModeKey_; - NSString* AVVideoScalingModeResizeAspectFill_; + NSString* AVCaptureDeviceWasConnectedNotification_ = + ::AVCaptureDeviceWasConnectedNotification; + NSString* AVCaptureDeviceWasDisconnectedNotification_ = + ::AVCaptureDeviceWasDisconnectedNotification; + NSString* AVMediaTypeVideo_ = ::AVMediaTypeVideo; + NSString* AVMediaTypeAudio_ = ::AVMediaTypeAudio; + NSString* AVMediaTypeMuxed_ = ::AVMediaTypeMuxed; + NSString* AVCaptureSessionRuntimeErrorNotification_ = + ::AVCaptureSessionRuntimeErrorNotification; + NSString* AVCaptureSessionDidStopRunningNotification_ = + ::AVCaptureSessionDidStopRunningNotification; + NSString* AVCaptureSessionErrorKey_ = ::AVCaptureSessionErrorKey; + NSString* AVVideoScalingModeKey_ = ::AVVideoScalingModeKey; + NSString* AVVideoScalingModeResizeAspectFill_ = + ::AVVideoScalingModeResizeAspectFill; DISALLOW_COPY_AND_ASSIGN(AVFoundationInternal); }; +static base::ThreadLocalStorage::StaticSlot g_avfoundation_handle = + TLS_INITIALIZER; + +void TlsCleanup(void* value) { + delete static_cast<AVFoundationInternal*>(value); +} + +AVFoundationInternal* GetAVFoundationInternal() { + return static_cast<AVFoundationInternal*>(g_avfoundation_handle.Get()); +} + // This contains the logic of checking whether AVFoundation is supported. // It's called only once and the results are cached in a static bool. bool LoadAVFoundationInternal() { - // AVFoundation is only available on OS Lion and above. - if (!base::mac::IsOSLionOrLater()) { - LogCaptureApi(CAPTURE_API_QTKIT_DUE_TO_OS_PREVIOUS_TO_LION); - return false; - } - - const base::CommandLine* command_line = - base::CommandLine::ForCurrentProcess(); - // The force-qtkit flag takes precedence over enable-avfoundation. - if (command_line->HasSwitch(switches::kForceQTKit)) { - LogCaptureApi(CAPTURE_API_QTKIT_FORCED_BY_FLAG); - return false; - } - - if (!command_line->HasSwitch(switches::kEnableAVFoundation)) { - LogCaptureApi(CAPTURE_API_QTKIT_DUE_TO_NO_FLAG); - return false; - } + g_avfoundation_handle.Initialize(TlsCleanup); + g_avfoundation_handle.Set(new AVFoundationInternal()); const bool ret = [AVFoundationGlue::AVFoundationBundle() load]; - LogCaptureApi(ret ? CAPTURE_API_AVFOUNDATION_LOADED_OK - : CAPTURE_API_QTKIT_DUE_TO_AVFOUNDATION_LOAD_ERROR); + CHECK(ret); return ret; } -} // namespace - -static base::LazyInstance<AVFoundationInternal>::Leaky g_avfoundation_handle = - LAZY_INSTANCE_INITIALIZER; - enum { INITIALIZE_NOT_CALLED = 0, AVFOUNDATION_IS_SUPPORTED, AVFOUNDATION_NOT_SUPPORTED } static g_avfoundation_initialization = INITIALIZE_NOT_CALLED; +} // namespace + void AVFoundationGlue::InitializeAVFoundation() { TRACE_EVENT0("video", "AVFoundationGlue::InitializeAVFoundation"); CHECK([NSThread isMainThread]); @@ -167,55 +128,50 @@ void AVFoundationGlue::InitializeAVFoundation() { AVFOUNDATION_IS_SUPPORTED : AVFOUNDATION_NOT_SUPPORTED; } -bool AVFoundationGlue::IsAVFoundationSupported() { - CHECK_NE(g_avfoundation_initialization, INITIALIZE_NOT_CALLED); - return g_avfoundation_initialization == AVFOUNDATION_IS_SUPPORTED; -} - NSBundle const* AVFoundationGlue::AVFoundationBundle() { - return g_avfoundation_handle.Get().bundle(); + return GetAVFoundationInternal()->bundle(); } NSString* AVFoundationGlue::AVCaptureDeviceWasConnectedNotification() { - return g_avfoundation_handle.Get().AVCaptureDeviceWasConnectedNotification(); + return GetAVFoundationInternal()->AVCaptureDeviceWasConnectedNotification(); } NSString* AVFoundationGlue::AVCaptureDeviceWasDisconnectedNotification() { - return - g_avfoundation_handle.Get().AVCaptureDeviceWasDisconnectedNotification(); + return GetAVFoundationInternal() + ->AVCaptureDeviceWasDisconnectedNotification(); } NSString* AVFoundationGlue::AVMediaTypeVideo() { - return g_avfoundation_handle.Get().AVMediaTypeVideo(); + return GetAVFoundationInternal()->AVMediaTypeVideo(); } NSString* AVFoundationGlue::AVMediaTypeAudio() { - return g_avfoundation_handle.Get().AVMediaTypeAudio(); + return GetAVFoundationInternal()->AVMediaTypeAudio(); } NSString* AVFoundationGlue::AVMediaTypeMuxed() { - return g_avfoundation_handle.Get().AVMediaTypeMuxed(); + return GetAVFoundationInternal()->AVMediaTypeMuxed(); } NSString* AVFoundationGlue::AVCaptureSessionRuntimeErrorNotification() { - return g_avfoundation_handle.Get().AVCaptureSessionRuntimeErrorNotification(); + return GetAVFoundationInternal()->AVCaptureSessionRuntimeErrorNotification(); } NSString* AVFoundationGlue::AVCaptureSessionDidStopRunningNotification() { - return - g_avfoundation_handle.Get().AVCaptureSessionDidStopRunningNotification(); + return GetAVFoundationInternal() + ->AVCaptureSessionDidStopRunningNotification(); } NSString* AVFoundationGlue::AVCaptureSessionErrorKey() { - return g_avfoundation_handle.Get().AVCaptureSessionErrorKey(); + return GetAVFoundationInternal()->AVCaptureSessionErrorKey(); } NSString* AVFoundationGlue::AVVideoScalingModeKey() { - return g_avfoundation_handle.Get().AVVideoScalingModeKey(); + return GetAVFoundationInternal()->AVVideoScalingModeKey(); } NSString* AVFoundationGlue::AVVideoScalingModeResizeAspectFill() { - return g_avfoundation_handle.Get().AVVideoScalingModeResizeAspectFill(); + return GetAVFoundationInternal()->AVVideoScalingModeResizeAspectFill(); } Class AVFoundationGlue::AVCaptureSessionClass() { diff --git a/chromium/media/base/mac/video_frame_mac.cc b/chromium/media/base/mac/video_frame_mac.cc index 0d489148f91..e3af1f000de 100644 --- a/chromium/media/base/mac/video_frame_mac.cc +++ b/chromium/media/base/mac/video_frame_mac.cc @@ -59,15 +59,7 @@ WrapVideoFrameInCVPixelBuffer(const VideoFrame& frame) { int num_planes = VideoFrame::NumPlanes(video_frame_format); DCHECK_LE(num_planes, kMaxPlanes); - gfx::Size coded_size = frame.coded_size(); - - // TODO(jfroy): Support extended pixels (i.e. padding). - if (coded_size != frame.visible_rect().size()) { - DLOG(ERROR) << " frame with extended pixels not supported: " - << " coded_size: " << coded_size.ToString() - << ", visible_rect: " << frame.visible_rect().ToString(); - return pixel_buffer; - } + const gfx::Rect& visible_rect = frame.visible_rect(); // Build arrays for each plane's data pointer, dimensions and byte alignment. void* plane_ptrs[kMaxPlanes]; @@ -75,9 +67,9 @@ WrapVideoFrameInCVPixelBuffer(const VideoFrame& frame) { size_t plane_heights[kMaxPlanes]; size_t plane_bytes_per_row[kMaxPlanes]; for (int plane_i = 0; plane_i < num_planes; ++plane_i) { - plane_ptrs[plane_i] = const_cast<uint8_t*>(frame.data(plane_i)); + plane_ptrs[plane_i] = const_cast<uint8_t*>(frame.visible_data(plane_i)); gfx::Size plane_size = - VideoFrame::PlaneSize(video_frame_format, plane_i, coded_size); + VideoFrame::PlaneSize(video_frame_format, plane_i, visible_rect.size()); plane_widths[plane_i] = plane_size.width(); plane_heights[plane_i] = plane_size.height(); plane_bytes_per_row[plane_i] = frame.stride(plane_i); @@ -94,9 +86,9 @@ WrapVideoFrameInCVPixelBuffer(const VideoFrame& frame) { // give it a smart pointer to the frame, so instead pass a raw pointer and // increment the frame's reference count manually. CVReturn result = CVPixelBufferCreateWithPlanarBytes( - kCFAllocatorDefault, coded_size.width(), coded_size.height(), cv_format, - descriptor, 0, num_planes, plane_ptrs, plane_widths, plane_heights, - plane_bytes_per_row, &CvPixelBufferReleaseCallback, + kCFAllocatorDefault, visible_rect.width(), visible_rect.height(), + cv_format, descriptor, 0, num_planes, plane_ptrs, plane_widths, + plane_heights, plane_bytes_per_row, &CvPixelBufferReleaseCallback, const_cast<VideoFrame*>(&frame), nullptr, pixel_buffer.InitializeInto()); if (result != kCVReturnSuccess) { DLOG(ERROR) << " CVPixelBufferCreateWithPlanarBytes failed: " << result; diff --git a/chromium/media/base/mac/video_frame_mac_unittests.cc b/chromium/media/base/mac/video_frame_mac_unittests.cc index 475d9aa9307..34cd0415bf0 100644 --- a/chromium/media/base/mac/video_frame_mac_unittests.cc +++ b/chromium/media/base/mac/video_frame_mac_unittests.cc @@ -21,6 +21,7 @@ namespace { const int kWidth = 64; const int kHeight = 48; +const int kVisibleRectOffset = 8; const base::TimeDelta kTimestamp = base::TimeDelta::FromMicroseconds(1337); struct FormatPair { @@ -43,8 +44,8 @@ TEST(VideoFrameMac, CheckBasicAttributes) { auto pb = WrapVideoFrameInCVPixelBuffer(*frame); ASSERT_TRUE(pb.get()); - gfx::Size coded_size = frame->coded_size(); - VideoPixelFormat format = frame->format(); + const gfx::Size coded_size = frame->coded_size(); + const VideoPixelFormat format = frame->format(); EXPECT_EQ(coded_size.width(), static_cast<int>(CVPixelBufferGetWidth(pb))); EXPECT_EQ(coded_size.height(), static_cast<int>(CVPixelBufferGetHeight(pb))); @@ -52,7 +53,7 @@ TEST(VideoFrameMac, CheckBasicAttributes) { CVPixelBufferLockBaseAddress(pb, 0); for (size_t i = 0; i < VideoFrame::NumPlanes(format); ++i) { - gfx::Size plane_size = VideoFrame::PlaneSize(format, i, coded_size); + const gfx::Size plane_size = VideoFrame::PlaneSize(format, i, coded_size); EXPECT_EQ(plane_size.width(), static_cast<int>(CVPixelBufferGetWidthOfPlane(pb, i))); EXPECT_EQ(plane_size.height(), @@ -94,7 +95,7 @@ TEST(VideoFrameMac, CheckLifetime) { int instances_destroyed = 0; auto wrapper_frame = VideoFrame::WrapVideoFrame( - frame, frame->visible_rect(), frame->natural_size()); + frame, frame->format(), frame->visible_rect(), frame->natural_size()); wrapper_frame->AddDestructionObserver( base::Bind(&Increment, &instances_destroyed)); ASSERT_TRUE(wrapper_frame.get()); @@ -115,7 +116,7 @@ TEST(VideoFrameMac, CheckWrapperFrame) { CoreVideoGlue::kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange}, }; - gfx::Size size(kWidth, kHeight); + const gfx::Size size(kWidth, kHeight); for (const auto& format_pair : format_pairs) { base::ScopedCFTypeRef<CVPixelBufferRef> pb; CVPixelBufferCreate(nullptr, kWidth, kHeight, format_pair.corevideo, @@ -132,4 +133,66 @@ TEST(VideoFrameMac, CheckWrapperFrame) { } } +static void FillFrameWithPredictableValues(const VideoFrame& frame) { + for (size_t i = 0; i < VideoFrame::NumPlanes(frame.format()); ++i) { + const gfx::Size& size = + VideoFrame::PlaneSize(frame.format(), i, frame.coded_size()); + uint8_t* plane_ptr = const_cast<uint8_t*>(frame.data(i)); + for (int h = 0; h < size.height(); ++h) { + const int row_index = h * frame.stride(i); + for (int w = 0; w < size.width(); ++w) { + const int index = row_index + w; + plane_ptr[index] = static_cast<uint8_t>(w ^ h); + } + } + } +} + +TEST(VideoFrameMac, CorrectlyWrapsFramesWithPadding) { + const gfx::Size coded_size(kWidth, kHeight); + const gfx::Rect visible_rect(kVisibleRectOffset, kVisibleRectOffset, + kWidth - 2 * kVisibleRectOffset, + kHeight - 2 * kVisibleRectOffset); + auto frame = + VideoFrame::CreateFrame(PIXEL_FORMAT_I420, coded_size, visible_rect, + visible_rect.size(), kTimestamp); + ASSERT_TRUE(frame.get()); + FillFrameWithPredictableValues(*frame); + + auto pb = WrapVideoFrameInCVPixelBuffer(*frame); + ASSERT_TRUE(pb.get()); + EXPECT_EQ(kCVPixelFormatType_420YpCbCr8Planar, + CVPixelBufferGetPixelFormatType(pb)); + EXPECT_EQ(visible_rect.width(), static_cast<int>(CVPixelBufferGetWidth(pb))); + EXPECT_EQ(visible_rect.height(), + static_cast<int>(CVPixelBufferGetHeight(pb))); + + CVPixelBufferLockBaseAddress(pb, 0); + for (size_t i = 0; i < VideoFrame::NumPlanes(frame->format()); ++i) { + const gfx::Size plane_size = + VideoFrame::PlaneSize(frame->format(), i, visible_rect.size()); + EXPECT_EQ(plane_size.width(), + static_cast<int>(CVPixelBufferGetWidthOfPlane(pb, i))); + EXPECT_EQ(plane_size.height(), + static_cast<int>(CVPixelBufferGetHeightOfPlane(pb, i))); + + uint8_t* plane_ptr = + reinterpret_cast<uint8_t*>(CVPixelBufferGetBaseAddressOfPlane(pb, i)); + EXPECT_EQ(frame->visible_data(i), plane_ptr); + const int stride = + static_cast<int>(CVPixelBufferGetBytesPerRowOfPlane(pb, i)); + EXPECT_EQ(frame->stride(i), stride); + const int offset = kVisibleRectOffset / ((i == 0) ? 1 : 2); + for (int h = 0; h < plane_size.height(); ++h) { + const int row_index = h * stride; + for (int w = 0; w < plane_size.width(); ++w) { + const int index = row_index + w; + EXPECT_EQ(static_cast<uint8_t>((w + offset) ^ (h + offset)), + plane_ptr[index]); + } + } + } + CVPixelBufferUnlockBaseAddress(pb, 0); +} + } // namespace media diff --git a/chromium/media/base/mac/videotoolbox_glue.h b/chromium/media/base/mac/videotoolbox_glue.h index 212722a08d1..9a978bf09da 100644 --- a/chromium/media/base/mac/videotoolbox_glue.h +++ b/chromium/media/base/mac/videotoolbox_glue.h @@ -11,11 +11,13 @@ #include "media/base/mac/coremedia_glue.h" #include "media/base/media_export.h" -// VideoToolbox API is available in OS X 10.9 and iOS 8 (10.8 has support for -// software encoding, but this class exposes the 10.9 API level). Chromium -// requires OS X 10.6 or iOS 6. Linking with VideoToolbox therefore has to -// happen at runtime. This class is defined to try and load the VideoToolbox -// library. If it succeeds, clients can use VideoToolbox via this class. +// VideoToolbox API is available in and after OS X 10.9 and iOS 8 (10.8 has +// support for software encoding, but this class exposes the 10.9 API level). +// Chromium requires OS X 10.9 or iOS 9. This class is defined to try and load +// the VideoToolbox library at runtime. If it succeeds, clients can use +// VideoToolbox via this class. +// Note that this file is necessary because Chromium still targets OS X 10.6 for +// deployment. It should be deprecated soon, see crbug.com/579648. class MEDIA_EXPORT VideoToolboxGlue { public: class Loader; @@ -49,6 +51,7 @@ class MEDIA_EXPORT VideoToolboxGlue { CFStringRef kVTCompressionPropertyKey_AllowFrameReordering() const; CFStringRef kVTCompressionPropertyKey_AverageBitRate() const; CFStringRef kVTCompressionPropertyKey_ColorPrimaries() const; + CFStringRef kVTCompressionPropertyKey_DataRateLimits() const; CFStringRef kVTCompressionPropertyKey_ExpectedFrameRate() const; CFStringRef kVTCompressionPropertyKey_MaxFrameDelayCount() const; CFStringRef kVTCompressionPropertyKey_MaxKeyFrameInterval() const; @@ -68,6 +71,8 @@ class MEDIA_EXPORT VideoToolboxGlue { CFStringRef kVTVideoEncoderSpecification_EnableHardwareAcceleratedVideoEncoder() const; + CFStringRef + kVTVideoEncoderSpecification_RequireHardwareAcceleratedVideoEncoder() const; // Originally from VTCompressionSession.h OSStatus VTCompressionSessionCreate( diff --git a/chromium/media/base/mac/videotoolbox_glue.mm b/chromium/media/base/mac/videotoolbox_glue.mm index 010767c8ee1..234f0ee184d 100644 --- a/chromium/media/base/mac/videotoolbox_glue.mm +++ b/chromium/media/base/mac/videotoolbox_glue.mm @@ -53,6 +53,7 @@ struct VideoToolboxGlue::Library { CFStringRef* kVTCompressionPropertyKey_AllowFrameReordering; CFStringRef* kVTCompressionPropertyKey_AverageBitRate; CFStringRef* kVTCompressionPropertyKey_ColorPrimaries; + CFStringRef* kVTCompressionPropertyKey_DataRateLimits; CFStringRef* kVTCompressionPropertyKey_ExpectedFrameRate; CFStringRef* kVTCompressionPropertyKey_MaxFrameDelayCount; CFStringRef* kVTCompressionPropertyKey_MaxKeyFrameInterval; @@ -68,6 +69,8 @@ struct VideoToolboxGlue::Library { CFStringRef* kVTProfileLevel_H264_High_AutoLevel; CFStringRef* kVTVideoEncoderSpecification_EnableHardwareAcceleratedVideoEncoder; + CFStringRef* + kVTVideoEncoderSpecification_RequireHardwareAcceleratedVideoEncoder; }; // Lazy-instance responsible for loading VideoToolbox. @@ -98,6 +101,7 @@ class VideoToolboxGlue::Loader { LOAD_SYMBOL(kVTCompressionPropertyKey_AllowFrameReordering) LOAD_SYMBOL(kVTCompressionPropertyKey_AverageBitRate) LOAD_SYMBOL(kVTCompressionPropertyKey_ColorPrimaries) + LOAD_SYMBOL(kVTCompressionPropertyKey_DataRateLimits) LOAD_SYMBOL(kVTCompressionPropertyKey_ExpectedFrameRate) LOAD_SYMBOL(kVTCompressionPropertyKey_MaxFrameDelayCount) LOAD_SYMBOL(kVTCompressionPropertyKey_MaxKeyFrameInterval) @@ -113,6 +117,8 @@ class VideoToolboxGlue::Loader { LOAD_SYMBOL(kVTProfileLevel_H264_High_AutoLevel) LOAD_SYMBOL( kVTVideoEncoderSpecification_EnableHardwareAcceleratedVideoEncoder) + LOAD_SYMBOL( + kVTVideoEncoderSpecification_RequireHardwareAcceleratedVideoEncoder) #undef LOAD_SYMBOL @@ -216,6 +222,7 @@ OSStatus VideoToolboxGlue::VTSessionSetProperty(VTSessionRef session, KEY_ACCESSOR(kVTCompressionPropertyKey_AllowFrameReordering) KEY_ACCESSOR(kVTCompressionPropertyKey_AverageBitRate) KEY_ACCESSOR(kVTCompressionPropertyKey_ColorPrimaries) +KEY_ACCESSOR(kVTCompressionPropertyKey_DataRateLimits) KEY_ACCESSOR(kVTCompressionPropertyKey_ExpectedFrameRate) KEY_ACCESSOR(kVTCompressionPropertyKey_MaxFrameDelayCount) KEY_ACCESSOR(kVTCompressionPropertyKey_MaxKeyFrameInterval) @@ -230,5 +237,7 @@ KEY_ACCESSOR(kVTProfileLevel_H264_Main_AutoLevel) KEY_ACCESSOR(kVTProfileLevel_H264_Extended_AutoLevel) KEY_ACCESSOR(kVTProfileLevel_H264_High_AutoLevel) KEY_ACCESSOR(kVTVideoEncoderSpecification_EnableHardwareAcceleratedVideoEncoder) +KEY_ACCESSOR( + kVTVideoEncoderSpecification_RequireHardwareAcceleratedVideoEncoder) #undef KEY_ACCESSOR diff --git a/chromium/media/base/mac/videotoolbox_helpers.cc b/chromium/media/base/mac/videotoolbox_helpers.cc new file mode 100644 index 00000000000..47cfb2fe662 --- /dev/null +++ b/chromium/media/base/mac/videotoolbox_helpers.cc @@ -0,0 +1,304 @@ +// Copyright 2016 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/base/mac/videotoolbox_helpers.h" + +#include <array> +#include <vector> + +#include "base/big_endian.h" +#include "base/memory/scoped_ptr.h" + +namespace media { + +namespace video_toolbox { + +base::ScopedCFTypeRef<CFDictionaryRef> +DictionaryWithKeysAndValues(CFTypeRef* keys, CFTypeRef* values, size_t size) { + return base::ScopedCFTypeRef<CFDictionaryRef>(CFDictionaryCreate( + kCFAllocatorDefault, keys, values, size, &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks)); +} + +base::ScopedCFTypeRef<CFDictionaryRef> DictionaryWithKeyValue(CFTypeRef key, + CFTypeRef value) { + CFTypeRef keys[1] = {key}; + CFTypeRef values[1] = {value}; + return DictionaryWithKeysAndValues(keys, values, 1); +} + +base::ScopedCFTypeRef<CFArrayRef> ArrayWithIntegers(const int* v, size_t size) { + std::vector<CFNumberRef> numbers; + numbers.reserve(size); + for (const int* end = v + size; v < end; ++v) + numbers.push_back(CFNumberCreate(nullptr, kCFNumberSInt32Type, v)); + base::ScopedCFTypeRef<CFArrayRef> array(CFArrayCreate( + kCFAllocatorDefault, reinterpret_cast<const void**>(&numbers[0]), + numbers.size(), &kCFTypeArrayCallBacks)); + for (auto& number : numbers) { + CFRelease(number); + } + return array; +} + +base::ScopedCFTypeRef<CFArrayRef> ArrayWithIntegerAndFloat(int int_val, + float float_val) { + std::array<CFNumberRef, 2> numbers = { + {CFNumberCreate(nullptr, kCFNumberSInt32Type, &int_val), + CFNumberCreate(nullptr, kCFNumberFloat32Type, &float_val)}}; + base::ScopedCFTypeRef<CFArrayRef> array(CFArrayCreate( + kCFAllocatorDefault, reinterpret_cast<const void**>(numbers.data()), + numbers.size(), &kCFTypeArrayCallBacks)); + for (auto& number : numbers) + CFRelease(number); + return array; +} + +// Wrapper class for writing AnnexBBuffer output into. +class AnnexBBuffer { + public: + virtual bool Reserve(size_t size) = 0; + virtual void Append(const char* s, size_t n) = 0; + virtual size_t GetReservedSize() const = 0; +}; + +class RawAnnexBBuffer : public AnnexBBuffer { + public: + RawAnnexBBuffer(char* annexb_buffer, size_t annexb_buffer_size) + : annexb_buffer_(annexb_buffer), + annexb_buffer_size_(annexb_buffer_size), + annexb_buffer_offset_(0) {} + bool Reserve(size_t size) override { + reserved_size_ = size; + return size <= annexb_buffer_size_; + } + void Append(const char* s, size_t n) override { + memcpy(annexb_buffer_ + annexb_buffer_offset_, s, n); + annexb_buffer_offset_ += n; + DCHECK_GE(reserved_size_, annexb_buffer_offset_); + } + size_t GetReservedSize() const override { return reserved_size_; } + + private: + char* annexb_buffer_; + size_t annexb_buffer_size_; + size_t annexb_buffer_offset_; + size_t reserved_size_; + + DISALLOW_IMPLICIT_CONSTRUCTORS(RawAnnexBBuffer); +}; + +class StringAnnexBBuffer : public AnnexBBuffer { + public: + explicit StringAnnexBBuffer(std::string* str_annexb_buffer) + : str_annexb_buffer_(str_annexb_buffer) {} + bool Reserve(size_t size) override { + str_annexb_buffer_->reserve(size); + return true; + } + void Append(const char* s, size_t n) override { + str_annexb_buffer_->append(s, n); + } + size_t GetReservedSize() const override { return str_annexb_buffer_->size(); } + + private: + std::string* str_annexb_buffer_; + DISALLOW_IMPLICIT_CONSTRUCTORS(StringAnnexBBuffer); +}; + +template <typename NalSizeType> +void CopyNalsToAnnexB(char* avcc_buffer, + const size_t avcc_size, + AnnexBBuffer* annexb_buffer) { + static_assert(sizeof(NalSizeType) == 1 || sizeof(NalSizeType) == 2 || + sizeof(NalSizeType) == 4, + "NAL size type has unsupported size"); + static const char startcode_3[3] = {0, 0, 1}; + DCHECK(avcc_buffer); + DCHECK(annexb_buffer); + size_t bytes_left = avcc_size; + while (bytes_left > 0) { + DCHECK_GT(bytes_left, sizeof(NalSizeType)); + NalSizeType nal_size; + base::ReadBigEndian(avcc_buffer, &nal_size); + bytes_left -= sizeof(NalSizeType); + avcc_buffer += sizeof(NalSizeType); + + DCHECK_GE(bytes_left, nal_size); + annexb_buffer->Append(startcode_3, sizeof(startcode_3)); + annexb_buffer->Append(avcc_buffer, nal_size); + bytes_left -= nal_size; + avcc_buffer += nal_size; + } +} + +bool CopySampleBufferToAnnexBBuffer(CoreMediaGlue::CMSampleBufferRef sbuf, + AnnexBBuffer* annexb_buffer, + bool keyframe) { + // Perform two pass, one to figure out the total output size, and another to + // copy the data after having performed a single output allocation. Note that + // we'll allocate a bit more because we'll count 4 bytes instead of 3 for + // video NALs. + OSStatus status; + + // Get the sample buffer's block buffer and format description. + auto bb = CoreMediaGlue::CMSampleBufferGetDataBuffer(sbuf); + DCHECK(bb); + auto fdesc = CoreMediaGlue::CMSampleBufferGetFormatDescription(sbuf); + DCHECK(fdesc); + + size_t bb_size = CoreMediaGlue::CMBlockBufferGetDataLength(bb); + size_t total_bytes = bb_size; + + size_t pset_count; + int nal_size_field_bytes; + status = CoreMediaGlue::CMVideoFormatDescriptionGetH264ParameterSetAtIndex( + fdesc, 0, nullptr, nullptr, &pset_count, &nal_size_field_bytes); + if (status == + CoreMediaGlue::kCMFormatDescriptionBridgeError_InvalidParameter) { + DLOG(WARNING) << " assuming 2 parameter sets and 4 bytes NAL length header"; + pset_count = 2; + nal_size_field_bytes = 4; + } else if (status != noErr) { + DLOG(ERROR) + << " CMVideoFormatDescriptionGetH264ParameterSetAtIndex failed: " + << status; + return false; + } + + if (keyframe) { + const uint8_t* pset; + size_t pset_size; + for (size_t pset_i = 0; pset_i < pset_count; ++pset_i) { + status = + CoreMediaGlue::CMVideoFormatDescriptionGetH264ParameterSetAtIndex( + fdesc, pset_i, &pset, &pset_size, nullptr, nullptr); + if (status != noErr) { + DLOG(ERROR) + << " CMVideoFormatDescriptionGetH264ParameterSetAtIndex failed: " + << status; + return false; + } + total_bytes += pset_size + nal_size_field_bytes; + } + } + + if (!annexb_buffer->Reserve(total_bytes)) { + DLOG(ERROR) << "Cannot fit encode output into bitstream buffer. Requested:" + << total_bytes; + return false; + } + + // Copy all parameter sets before keyframes. + if (keyframe) { + const uint8_t* pset; + size_t pset_size; + for (size_t pset_i = 0; pset_i < pset_count; ++pset_i) { + status = + CoreMediaGlue::CMVideoFormatDescriptionGetH264ParameterSetAtIndex( + fdesc, pset_i, &pset, &pset_size, nullptr, nullptr); + if (status != noErr) { + DLOG(ERROR) + << " CMVideoFormatDescriptionGetH264ParameterSetAtIndex failed: " + << status; + return false; + } + static const char startcode_4[4] = {0, 0, 0, 1}; + annexb_buffer->Append(startcode_4, sizeof(startcode_4)); + annexb_buffer->Append(reinterpret_cast<const char*>(pset), pset_size); + } + } + + // Block buffers can be composed of non-contiguous chunks. For the sake of + // keeping this code simple, flatten non-contiguous block buffers. + base::ScopedCFTypeRef<CoreMediaGlue::CMBlockBufferRef> contiguous_bb( + bb, base::scoped_policy::RETAIN); + if (!CoreMediaGlue::CMBlockBufferIsRangeContiguous(bb, 0, 0)) { + contiguous_bb.reset(); + status = CoreMediaGlue::CMBlockBufferCreateContiguous( + kCFAllocatorDefault, bb, kCFAllocatorDefault, nullptr, 0, 0, 0, + contiguous_bb.InitializeInto()); + if (status != noErr) { + DLOG(ERROR) << " CMBlockBufferCreateContiguous failed: " << status; + return false; + } + } + + // Copy all the NAL units. In the process convert them from AVCC format + // (length header) to AnnexB format (start code). + char* bb_data; + status = CoreMediaGlue::CMBlockBufferGetDataPointer(contiguous_bb, 0, nullptr, + nullptr, &bb_data); + if (status != noErr) { + DLOG(ERROR) << " CMBlockBufferGetDataPointer failed: " << status; + return false; + } + + if (nal_size_field_bytes == 1) { + CopyNalsToAnnexB<uint8_t>(bb_data, bb_size, annexb_buffer); + } else if (nal_size_field_bytes == 2) { + CopyNalsToAnnexB<uint16_t>(bb_data, bb_size, annexb_buffer); + } else if (nal_size_field_bytes == 4) { + CopyNalsToAnnexB<uint32_t>(bb_data, bb_size, annexb_buffer); + } else { + NOTREACHED(); + } + return true; +} + +bool CopySampleBufferToAnnexBBuffer(CoreMediaGlue::CMSampleBufferRef sbuf, + bool keyframe, + std::string* annexb_buffer) { + StringAnnexBBuffer buffer(annexb_buffer); + return CopySampleBufferToAnnexBBuffer(sbuf, &buffer, keyframe); +} + +bool CopySampleBufferToAnnexBBuffer(CoreMediaGlue::CMSampleBufferRef sbuf, + bool keyframe, + size_t annexb_buffer_size, + char* annexb_buffer, + size_t* used_buffer_size) { + RawAnnexBBuffer buffer(annexb_buffer, annexb_buffer_size); + const bool copy_rv = CopySampleBufferToAnnexBBuffer(sbuf, &buffer, keyframe); + *used_buffer_size = buffer.GetReservedSize(); + return copy_rv; +} + +SessionPropertySetter::SessionPropertySetter( + base::ScopedCFTypeRef<VideoToolboxGlue::VTCompressionSessionRef> session, + const VideoToolboxGlue* const glue) + : session_(session), glue_(glue) {} + +SessionPropertySetter::~SessionPropertySetter() {} + +bool SessionPropertySetter::Set(CFStringRef key, int32_t value) { + DCHECK(session_); + DCHECK(glue_); + base::ScopedCFTypeRef<CFNumberRef> cfvalue( + CFNumberCreate(nullptr, kCFNumberSInt32Type, &value)); + return glue_->VTSessionSetProperty(session_, key, cfvalue) == noErr; +} + +bool SessionPropertySetter::Set(CFStringRef key, bool value) { + DCHECK(session_); + DCHECK(glue_); + CFBooleanRef cfvalue = (value) ? kCFBooleanTrue : kCFBooleanFalse; + return glue_->VTSessionSetProperty(session_, key, cfvalue) == noErr; +} + +bool SessionPropertySetter::Set(CFStringRef key, CFStringRef value) { + DCHECK(session_); + DCHECK(glue_); + return glue_->VTSessionSetProperty(session_, key, value) == noErr; +} + +bool SessionPropertySetter::Set(CFStringRef key, CFArrayRef value) { + DCHECK(session_); + DCHECK(glue_); + return glue_->VTSessionSetProperty(session_, key, value) == noErr; +} + +} // namespace video_toolbox + +} // namespace media diff --git a/chromium/media/base/mac/videotoolbox_helpers.h b/chromium/media/base/mac/videotoolbox_helpers.h new file mode 100644 index 00000000000..87d769eef81 --- /dev/null +++ b/chromium/media/base/mac/videotoolbox_helpers.h @@ -0,0 +1,69 @@ +// Copyright 2016 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_BASE_MAC_VIDEOTOOLBOX_HELPERS_H_ +#define MEDIA_BASE_MAC_VIDEOTOOLBOX_HELPERS_H_ + +#include "base/mac/scoped_cftyperef.h" +#include "media/base/mac/videotoolbox_glue.h" +#include "media/base/media_export.h" + +namespace media { + +namespace video_toolbox { + +// Create a CFDictionaryRef with the given keys and values. +MEDIA_EXPORT base::ScopedCFTypeRef<CFDictionaryRef> +DictionaryWithKeysAndValues(CFTypeRef* keys, CFTypeRef* values, size_t size); + +// Create a CFDictionaryRef with the given key and value. +MEDIA_EXPORT base::ScopedCFTypeRef<CFDictionaryRef> DictionaryWithKeyValue( + CFTypeRef key, + CFTypeRef value); + +// Create a CFArrayRef with the given array of integers. +MEDIA_EXPORT base::ScopedCFTypeRef<CFArrayRef> ArrayWithIntegers(const int* v, + size_t size); + +// Create a CFArrayRef with the given int and float values. +MEDIA_EXPORT base::ScopedCFTypeRef<CFArrayRef> ArrayWithIntegerAndFloat( + int int_val, + float float_val); + +// Copy a H.264 frame stored in a CM sample buffer to an Annex B buffer. Copies +// parameter sets for keyframes before the frame data as well. +MEDIA_EXPORT bool CopySampleBufferToAnnexBBuffer( + CoreMediaGlue::CMSampleBufferRef sbuf, + bool keyframe, + std::string* annexb_buffer); +MEDIA_EXPORT bool CopySampleBufferToAnnexBBuffer( + CoreMediaGlue::CMSampleBufferRef sbuf, + bool keyframe, + size_t annexb_buffer_size, + char* annexb_buffer, + size_t* used_buffer_size); + +// Helper class to add session properties to a VTCompressionSessionRef. +class MEDIA_EXPORT SessionPropertySetter { + public: + SessionPropertySetter( + base::ScopedCFTypeRef<VideoToolboxGlue::VTCompressionSessionRef> session, + const VideoToolboxGlue* const glue); + ~SessionPropertySetter(); + + bool Set(CFStringRef key, int32_t value); + bool Set(CFStringRef key, bool value); + bool Set(CFStringRef key, CFStringRef value); + bool Set(CFStringRef key, CFArrayRef value); + + private: + base::ScopedCFTypeRef<VideoToolboxGlue::VTCompressionSessionRef> session_; + const VideoToolboxGlue* glue_; +}; + +} // namespace video_toolbox + +} // namespace media + +#endif // MEDIA_BASE_MAC_VIDEOTOOLBOX_HELPERS_H_ diff --git a/chromium/media/base/media.cc b/chromium/media/base/media.cc index f55d1c437a5..9fd89ccc7f0 100644 --- a/chromium/media/base/media.cc +++ b/chromium/media/base/media.cc @@ -4,15 +4,19 @@ #include "media/base/media.h" -#include "base/files/file_path.h" +#include "base/command_line.h" #include "base/lazy_instance.h" #include "base/macros.h" -#include "base/path_service.h" -#include "base/synchronization/lock.h" +#include "base/metrics/field_trial.h" #include "base/trace_event/trace_event.h" -#include "build/build_config.h" +#include "media/base/media_switches.h" #include "media/base/yuv_convert.h" +#if defined(OS_ANDROID) +#include "base/android/build_info.h" +#include "media/base/android/media_codec_util.h" +#endif + #if !defined(MEDIA_DISABLE_FFMPEG) #include "media/ffmpeg/ffmpeg_common.h" #endif @@ -21,6 +25,13 @@ namespace media { // Media must only be initialized once, so use a LazyInstance to ensure this. class MediaInitializer { + public: + void enable_platform_decoder_support() { + has_platform_decoder_support_ = true; + } + + bool has_platform_decoder_support() { return has_platform_decoder_support_; } + private: friend struct base::DefaultLazyInstanceTraits<MediaInitializer>; @@ -51,6 +62,8 @@ class MediaInitializer { NOTREACHED() << "MediaInitializer should be leaky!"; } + bool has_platform_decoder_support_ = false; + DISALLOW_COPY_AND_ASSIGN(MediaInitializer); }; @@ -61,4 +74,46 @@ void InitializeMediaLibrary() { g_media_library.Get(); } +#if defined(OS_ANDROID) +void EnablePlatformDecoderSupport() { + g_media_library.Pointer()->enable_platform_decoder_support(); +} + +bool HasPlatformDecoderSupport() { + return g_media_library.Pointer()->has_platform_decoder_support(); +} + +bool PlatformHasOpusSupport() { + return base::android::BuildInfo::GetInstance()->sdk_int() >= 21; +} + +bool IsUnifiedMediaPipelineEnabled() { + // TODO(dalecurtis): This experiment is temporary and should be removed once + // we have enough data to support the primacy of the unified media pipeline; + // see http://crbug.com/533190 for details. + // + // Note: It's important to query the field trial state first, to ensure that + // UMA reports the correct group. + const std::string group_name = + base::FieldTrialList::FindFullName("UnifiedMediaPipelineTrial"); + const bool disabled_via_cli = + base::CommandLine::ForCurrentProcess()->HasSwitch( + switches::kDisableUnifiedMediaPipeline); + // TODO(watk, dalecurtis): AVDA has bugs on API level 16 and 17 so it's + // disabled for now. http://crbug.com/597467 + const bool api_level_supported = + base::android::BuildInfo::GetInstance()->sdk_int() >= 18; + + return !disabled_via_cli && api_level_supported && + !base::StartsWith(group_name, "Disabled", + base::CompareCase::SENSITIVE); +} + +bool ArePlatformDecodersAvailable() { + return IsUnifiedMediaPipelineEnabled() + ? HasPlatformDecoderSupport() + : MediaCodecUtil::IsMediaCodecAvailable(); +} +#endif + } // namespace media diff --git a/chromium/media/base/media.h b/chromium/media/base/media.h index 01a913bc45f..c0fd87ee3bb 100644 --- a/chromium/media/base/media.h +++ b/chromium/media/base/media.h @@ -8,6 +8,7 @@ #ifndef MEDIA_BASE_MEDIA_H_ #define MEDIA_BASE_MEDIA_H_ +#include "build/build_config.h" #include "media/base/media_export.h" namespace base { @@ -20,6 +21,32 @@ namespace media { // features. MEDIA_EXPORT void InitializeMediaLibrary(); +#if defined(OS_ANDROID) +// Tells the media library it has support for OS level decoders. Should only be +// used for actual decoders (e.g. MediaCodec) and not full-featured players +// (e.g. MediaPlayer). +MEDIA_EXPORT void EnablePlatformDecoderSupport(); +MEDIA_EXPORT bool HasPlatformDecoderSupport(); + +// Indicates if the platform supports Opus. Determined *ONLY* by the platform +// version, so does not guarantee that either can actually be played. +MEDIA_EXPORT bool PlatformHasOpusSupport(); + +// Returns true if the unified media pipeline is enabled; the pipeline may still +// not work for all codecs if HasPlatformDecoderSupport() is false. Please see +// MimeUtil for an exhaustive listing of supported codecs. +// +// TODO(dalecurtis): These methods are temporary and should be removed once the +// unified media pipeline is supported everywhere. http://crbug.com/580626. +MEDIA_EXPORT bool IsUnifiedMediaPipelineEnabled(); + +// Returns whether the platform decoders are available for use. +// This includes decoders being available on the platform and accessible, such +// as via the GPU process. Should only be used for actual decoders +// (e.g. MediaCodec) and not full-featured players (e.g. MediaPlayer). +MEDIA_EXPORT bool ArePlatformDecodersAvailable(); +#endif + } // namespace media #endif // MEDIA_BASE_MEDIA_H_ diff --git a/chromium/media/base/media_client.cc b/chromium/media/base/media_client.cc index bcc6de0bffe..e7026aadc13 100644 --- a/chromium/media/base/media_client.cc +++ b/chromium/media/base/media_client.cc @@ -20,12 +20,9 @@ MediaClient* GetMediaClient() { KeySystemInfoForUMA::KeySystemInfoForUMA( const std::string& key_system, - const std::string& key_system_name_for_uma, - bool reports_key_system_support_to_uma) + const std::string& key_system_name_for_uma) : key_system(key_system), - key_system_name_for_uma(key_system_name_for_uma), - reports_key_system_support_to_uma(reports_key_system_support_to_uma) { -} + key_system_name_for_uma(key_system_name_for_uma) {} KeySystemInfoForUMA::~KeySystemInfoForUMA() { } diff --git a/chromium/media/base/media_client.h b/chromium/media/base/media_client.h index dc8da49218f..30d86799559 100644 --- a/chromium/media/base/media_client.h +++ b/chromium/media/base/media_client.h @@ -28,8 +28,7 @@ MEDIA_EXPORT MediaClient* GetMediaClient(); struct MEDIA_EXPORT KeySystemInfoForUMA { KeySystemInfoForUMA(const std::string& key_system, - const std::string& key_system_name_for_uma, - bool reports_key_system_support_to_uma); + const std::string& key_system_name_for_uma); ~KeySystemInfoForUMA(); // Concrete key system name; @@ -39,12 +38,6 @@ struct MEDIA_EXPORT KeySystemInfoForUMA { // "org.w3.clearkey" is "ClearKey". When providing this value, make sure to // update tools/metrics/histograms/histograms.xml. std::string key_system_name_for_uma; - - // Whether query/support statistics for |key_system| should be reported. - // If set to true, make sure to add a new Media.EME.KeySystemSupport.* to - // tools/metrics/histograms/histograms.xml. See KeySystemsSupportUMA for - // details on how key system query/support UMA is reported. - bool reports_key_system_support_to_uma; }; // A client interface for embedders (e.g. content/renderer) to provide diff --git a/chromium/media/base/media_keys.h b/chromium/media/base/media_keys.h index bb34adfbf3a..13c210c786a 100644 --- a/chromium/media/base/media_keys.h +++ b/chromium/media/base/media_keys.h @@ -66,22 +66,8 @@ typedef ScopedVector<CdmKeyInformation> CdmKeysInfo; class MEDIA_EXPORT MediaKeys : public base::RefCountedThreadSafe<MediaKeys, MediaKeysTraits> { public: - // Reported to UMA, so never reuse a value! - // Must be kept in sync with blink::WebMediaPlayerClient::MediaKeyErrorCode - // (enforced in webmediaplayer_impl.cc). - // TODO(jrummell): Can this be moved to proxy_decryptor as it should only be - // used by the prefixed EME code? - enum KeyError { - kUnknownError = 1, - kClientError, - // The commented v0.1b values below have never been used. - // kServiceError, - kOutputError = 4, - // kHardwareChangeError, - // kDomainError, - kMaxKeyError // Must be last and greater than any legit value. - }; - + // TODO(xhwang): Remove after prefixed EME support is removed. See + // http://crbug.com/249976 // Must be a superset of cdm::MediaKeyException. enum Exception { NOT_SUPPORTED_ERROR, @@ -158,12 +144,14 @@ class MEDIA_EXPORT MediaKeys virtual void RemoveSession(const std::string& session_id, scoped_ptr<SimpleCdmPromise> promise) = 0; - // Returns the CdmContext associated with |this| if Decryptor or CDM ID is - // supported. The returned CdmContext is owned by |this|. Caller needs to make - // sure it is not used after |this| is destructed. - // Returns null if no Decryptor nor CDM ID is supported. Instead the media - // player may use the CDM via some platform specific method. + // Returns the CdmContext associated with |this|. The returned CdmContext is + // owned by |this| and the caller needs to make sure it is not used after + // |this| is destructed. + // Returns null if CdmContext is not supported. Instead the media player may + // use the CDM via some platform specific method. // By default this method returns null. + // TODO(xhwang): Convert all SetCdm() implementations to use CdmContext so + // that this function should never return nullptr. virtual CdmContext* GetCdmContext(); // Deletes |this| on the correct thread. By default |this| is deleted @@ -193,6 +181,7 @@ struct MEDIA_EXPORT MediaKeysTraits { typedef base::Callback<void(const std::string& session_id, MediaKeys::MessageType message_type, const std::vector<uint8_t>& message, + // TODO(ddorwin): Remove. https://crbug.com/249976 const GURL& legacy_destination_url)> SessionMessageCB; diff --git a/chromium/media/base/media_log.cc b/chromium/media/base/media_log.cc index 916021a3a63..128e5138ff5 100644 --- a/chromium/media/base/media_log.cc +++ b/chromium/media/base/media_log.cc @@ -91,8 +91,6 @@ std::string MediaLog::PipelineStatusToString(PipelineStatus status) { switch (status) { case PIPELINE_OK: return "pipeline: ok"; - case PIPELINE_ERROR_URL_NOT_FOUND: - return "pipeline: url not found"; case PIPELINE_ERROR_NETWORK: return "pipeline: network error"; case PIPELINE_ERROR_DECODE: @@ -105,8 +103,6 @@ std::string MediaLog::PipelineStatusToString(PipelineStatus status) { return "pipeline: could not render"; case PIPELINE_ERROR_READ: return "pipeline: read error"; - case PIPELINE_ERROR_OPERATION_PENDING: - return "pipeline: operation pending"; case PIPELINE_ERROR_INVALID_STATE: return "pipeline: invalid state"; case DEMUXER_ERROR_COULD_NOT_OPEN: @@ -117,6 +113,16 @@ std::string MediaLog::PipelineStatusToString(PipelineStatus status) { return "demuxer: no supported streams"; case DECODER_ERROR_NOT_SUPPORTED: return "decoder: not supported"; + case CHUNK_DEMUXER_ERROR_APPEND_FAILED: + return "chunk demuxer: append failed"; + case CHUNK_DEMUXER_ERROR_EOS_STATUS_DECODE_ERROR: + return "chunk demuxer: application requested decode error on eos"; + case CHUNK_DEMUXER_ERROR_EOS_STATUS_NETWORK_ERROR: + return "chunk demuxer: application requested network error on eos"; + case AUDIO_RENDERER_ERROR: + return "audio renderer: output device reported an error"; + case AUDIO_RENDERER_ERROR_SPLICE_FAILED: + return "audio renderer: post-decode audio splicing failed"; } NOTREACHED(); return NULL; @@ -144,6 +150,10 @@ MediaLog::~MediaLog() {} void MediaLog::AddEvent(scoped_ptr<MediaLogEvent> event) {} +std::string MediaLog::GetLastErrorMessage() { + return ""; +} + scoped_ptr<MediaLogEvent> MediaLog::CreateEvent(MediaLogEvent::Type type) { scoped_ptr<MediaLogEvent> event(new MediaLogEvent); event->id = id_; @@ -195,10 +205,11 @@ scoped_ptr<MediaLogEvent> MediaLog::CreateSeekEvent(float seconds) { } scoped_ptr<MediaLogEvent> MediaLog::CreatePipelineStateChangedEvent( - Pipeline::State state) { + PipelineImpl::State state) { scoped_ptr<MediaLogEvent> event( CreateEvent(MediaLogEvent::PIPELINE_STATE_CHANGED)); - event->params.SetString("pipeline_state", Pipeline::GetStateString(state)); + event->params.SetString("pipeline_state", + PipelineImpl::GetStateString(state)); return event; } @@ -244,13 +255,6 @@ void MediaLog::SetStringProperty( AddEvent(std::move(event)); } -void MediaLog::SetIntegerProperty( - const std::string& key, int value) { - scoped_ptr<MediaLogEvent> event(CreateEvent(MediaLogEvent::PROPERTY_CHANGE)); - event->params.SetInteger(key, value); - AddEvent(std::move(event)); -} - void MediaLog::SetDoubleProperty( const std::string& key, double value) { scoped_ptr<MediaLogEvent> event(CreateEvent(MediaLogEvent::PROPERTY_CHANGE)); @@ -265,16 +269,6 @@ void MediaLog::SetBooleanProperty( AddEvent(std::move(event)); } -void MediaLog::SetTimeProperty( - const std::string& key, base::TimeDelta value) { - scoped_ptr<MediaLogEvent> event(CreateEvent(MediaLogEvent::PROPERTY_CHANGE)); - if (value.is_max()) - event->params.SetString(key, "unknown"); - else - event->params.SetDouble(key, value.InSecondsF()); - AddEvent(std::move(event)); -} - LogHelper::LogHelper(MediaLog::MediaLogLevel level, const scoped_refptr<MediaLog>& media_log) : level_(level), media_log_(media_log) { diff --git a/chromium/media/base/media_log.h b/chromium/media/base/media_log.h index c547ef776db..6ab18597eac 100644 --- a/chromium/media/base/media_log.h +++ b/chromium/media/base/media_log.h @@ -17,7 +17,7 @@ #include "base/memory/scoped_ptr.h" #include "media/base/media_export.h" #include "media/base/media_log_event.h" -#include "media/base/pipeline.h" +#include "media/base/pipeline_impl.h" #include "media/base/pipeline_status.h" namespace media { @@ -44,6 +44,9 @@ class MEDIA_EXPORT MediaLog : public base::RefCountedThreadSafe<MediaLog> { // with it. virtual void AddEvent(scoped_ptr<MediaLogEvent> event); + // Retrieve an error message, if any. + virtual std::string GetLastErrorMessage(); + // Helper methods to create events and their parameters. scoped_ptr<MediaLogEvent> CreateEvent(MediaLogEvent::Type type); scoped_ptr<MediaLogEvent> CreateBooleanEvent( @@ -57,7 +60,7 @@ class MEDIA_EXPORT MediaLog : public base::RefCountedThreadSafe<MediaLog> { scoped_ptr<MediaLogEvent> CreateLoadEvent(const std::string& url); scoped_ptr<MediaLogEvent> CreateSeekEvent(float seconds); scoped_ptr<MediaLogEvent> CreatePipelineStateChangedEvent( - Pipeline::State state); + PipelineImpl::State state); scoped_ptr<MediaLogEvent> CreatePipelineErrorEvent(PipelineStatus error); scoped_ptr<MediaLogEvent> CreateVideoSizeSetEvent( size_t width, size_t height); @@ -70,10 +73,8 @@ class MEDIA_EXPORT MediaLog : public base::RefCountedThreadSafe<MediaLog> { // Report a property change without an accompanying event. void SetStringProperty(const std::string& key, const std::string& value); - void SetIntegerProperty(const std::string& key, int value); void SetDoubleProperty(const std::string& key, double value); void SetBooleanProperty(const std::string& key, bool value); - void SetTimeProperty(const std::string& key, base::TimeDelta value); protected: friend class base::RefCountedThreadSafe<MediaLog>; diff --git a/chromium/media/base/media_switches.cc b/chromium/media/base/media_switches.cc index bc5d67fb921..b3b34f4e7cb 100644 --- a/chromium/media/base/media_switches.cc +++ b/chromium/media/base/media_switches.cc @@ -22,13 +22,13 @@ const char kDisableMediaSuspend[] = "disable-media-suspend"; const char kDisableMediaThreadForMediaPlayback[] = "disable-media-thread-for-media-playback"; +// Use WebMediaPlayerAndroid instead of WebMediaPlayerImpl. This is a temporary +// switch for holding back the new unified media pipeline. +const char kDisableUnifiedMediaPipeline[] = "disable-unified-media-pipeline"; + // Sets the MediaSource player that uses the separate media thread const char kEnableMediaThreadForMediaPlayback[] = "enable-media-thread-for-media-playback"; - -// Use WebMediaPlayerImpl instead of WebMediaPlayerAndroid. This is a temporary -// switch for experimenting with unifying the Android playback pipeline. -const char kEnableUnifiedMediaPipeline[] = "enable-unified-media-pipeline"; #endif #if defined(OS_LINUX) || defined(OS_FREEBSD) || defined(OS_SOLARIS) @@ -43,22 +43,6 @@ const char kAlsaOutputDevice[] = "alsa-output-device"; const char kUseGpuMemoryBuffersForCapture[] = "use-gpu-memory-buffers-for-capture"; -#if defined(OS_MACOSX) -// AVFoundation is available in versions 10.7 and onwards, and is to be used -// http://crbug.com/288562 for both audio and video device monitoring and for -// video capture. Being a dynamically loaded NSBundle and library, it hits the -// Chrome startup time (http://crbug.com/311325 and http://crbug.com/311437); -// for experimentation purposes, in particular library load time issue, the -// usage of this library can be enabled by using this flag. -const char kEnableAVFoundation[] = "enable-avfoundation"; - -// QTKit is the media capture API predecessor to AVFoundation, available up and -// until Mac OS X 10.9 (despite being deprecated in this last one). This flag -// is used for troubleshooting and testing, and forces QTKit in builds and -// configurations where AVFoundation would be used otherwise. -const char kForceQTKit[] = "force-qtkit"; -#endif - #if defined(OS_WIN) // Use exclusive mode audio streaming for Windows Vista and higher. // Leads to lower latencies for audio streams which uses the @@ -67,12 +51,6 @@ const char kForceQTKit[] = "force-qtkit"; // for details. const char kEnableExclusiveAudio[] = "enable-exclusive-audio"; -// Used to troubleshoot problems with different video capture implementations -// on Windows. By default we use the Media Foundation API on Windows 7 and up, -// but specifying this switch will force use of DirectShow always. -// See bug: http://crbug.com/268412 -const char kForceDirectShowVideoCapture[] = "force-directshow"; - // Force the use of MediaFoundation for video capture. This is only supported in // Windows 7 and above. Used, like |kForceDirectShowVideoCapture|, to // troubleshoot problems in Windows platforms. @@ -97,6 +75,13 @@ const char kWaveOutBuffers[] = "waveout-buffers"; const char kUseCras[] = "use-cras"; #endif +#if !defined(OS_ANDROID) +// Use a media session for each tabs in a way that two tabs can't play on top of +// each other. This is different from the Media Session API as it is enabling a +// default behaviour for the browser. +const char kEnableDefaultMediaSession[] = "enable-default-media-session"; +#endif + // Use fake device for Media Stream to replace actual camera and microphone. const char kUseFakeDeviceForMediaStream[] = "use-fake-device-for-media-stream"; @@ -129,7 +114,12 @@ const char kVideoUnderflowThresholdMs[] = "video-underflow-threshold-ms"; const char kDisableRTCSmoothnessAlgorithm[] = "disable-rtc-smoothness-algorithm"; +} // namespace switches + +namespace media { + // Use shared block-based buffering for media. -const char kUseNewMediaCache[] = "use-new-media-cache"; +const base::Feature kUseNewMediaCache{"use-new-media-cache", + base::FEATURE_DISABLED_BY_DEFAULT}; -} // namespace switches +} // namespace media diff --git a/chromium/media/base/media_switches.h b/chromium/media/base/media_switches.h index e8c99b4b102..0ae1953f0f3 100644 --- a/chromium/media/base/media_switches.h +++ b/chromium/media/base/media_switches.h @@ -7,6 +7,7 @@ #ifndef MEDIA_BASE_MEDIA_SWITCHES_H_ #define MEDIA_BASE_MEDIA_SWITCHES_H_ +#include "base/feature_list.h" #include "build/build_config.h" #include "media/base/media_export.h" @@ -21,8 +22,8 @@ MEDIA_EXPORT extern const char kDisableMediaSuspend[]; #if defined(OS_ANDROID) MEDIA_EXPORT extern const char kDisableMediaThreadForMediaPlayback[]; +MEDIA_EXPORT extern const char kDisableUnifiedMediaPipeline[]; MEDIA_EXPORT extern const char kEnableMediaThreadForMediaPlayback[]; -MEDIA_EXPORT extern const char kEnableUnifiedMediaPipeline[]; #endif #if defined(OS_LINUX) || defined(OS_FREEBSD) || defined(OS_SOLARIS) @@ -32,14 +33,8 @@ MEDIA_EXPORT extern const char kAlsaOutputDevice[]; MEDIA_EXPORT extern const char kUseGpuMemoryBuffersForCapture[]; -#if defined(OS_MACOSX) -MEDIA_EXPORT extern const char kEnableAVFoundation[]; -MEDIA_EXPORT extern const char kForceQTKit[]; -#endif - #if defined(OS_WIN) MEDIA_EXPORT extern const char kEnableExclusiveAudio[]; -MEDIA_EXPORT extern const char kForceDirectShowVideoCapture[]; MEDIA_EXPORT extern const char kForceMediaFoundationVideoCapture[]; MEDIA_EXPORT extern const char kForceWaveAudio[]; MEDIA_EXPORT extern const char kTrySupportedChannelLayouts[]; @@ -50,6 +45,10 @@ MEDIA_EXPORT extern const char kWaveOutBuffers[]; MEDIA_EXPORT extern const char kUseCras[]; #endif +#if !defined(OS_ANDROID) +MEDIA_EXPORT extern const char kEnableDefaultMediaSession[]; +#endif + MEDIA_EXPORT extern const char kUseFakeDeviceForMediaStream[]; MEDIA_EXPORT extern const char kUseFileForFakeVideoCapture[]; MEDIA_EXPORT extern const char kUseFileForFakeAudioCapture[]; @@ -62,8 +61,14 @@ MEDIA_EXPORT extern const char kVideoUnderflowThresholdMs[]; MEDIA_EXPORT extern const char kDisableRTCSmoothnessAlgorithm[]; -MEDIA_EXPORT extern const char kUseNewMediaCache[]; - } // namespace switches +namespace media { + +// All features in alphabetical order. The features should be documented +// alongside the definition of their values in the .cc file. +MEDIA_EXPORT extern const base::Feature kUseNewMediaCache; + +} // namespace media + #endif // MEDIA_BASE_MEDIA_SWITCHES_H_ diff --git a/chromium/media/base/media_track.cc b/chromium/media/base/media_track.cc new file mode 100644 index 00000000000..ad5178f2b02 --- /dev/null +++ b/chromium/media/base/media_track.cc @@ -0,0 +1,18 @@ +// Copyright 2016 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/base/media_track.h" + +namespace media { + +MediaTrack::MediaTrack(Type type, + const std::string& id, + const std::string& kind, + const std::string& label, + const std::string& lang) + : type_(type), id_(id), kind_(kind), label_(label), language_(lang) {} + +MediaTrack::~MediaTrack() {} + +} // namespace media diff --git a/chromium/media/base/media_track.h b/chromium/media/base/media_track.h new file mode 100644 index 00000000000..a5cb6d3d818 --- /dev/null +++ b/chromium/media/base/media_track.h @@ -0,0 +1,41 @@ +// Copyright 2016 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_BASE_MEDIA_TRACK_H_ +#define MEDIA_BASE_MEDIA_TRACK_H_ + +#include <string> + +#include "media/base/media_export.h" + +namespace media { + +class MEDIA_EXPORT MediaTrack { + public: + enum Type { Text, Audio, Video }; + MediaTrack(Type type, + const std::string& id, + const std::string& kind, + const std::string& label, + const std::string& lang); + ~MediaTrack(); + + Type type() const { return type_; } + + const std::string& id() const { return id_; } + const std::string& kind() const { return kind_; } + const std::string& label() const { return label_; } + const std::string& language() const { return language_; } + + private: + Type type_; + std::string id_; + std::string kind_; + std::string label_; + std::string language_; +}; + +} // namespace media + +#endif // MEDIA_BASE_MEDIA_TRACK_H_ diff --git a/chromium/media/base/media_tracks.cc b/chromium/media/base/media_tracks.cc new file mode 100644 index 00000000000..832e5a822b1 --- /dev/null +++ b/chromium/media/base/media_tracks.cc @@ -0,0 +1,81 @@ +// Copyright 2016 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/base/media_tracks.h" + +#include "base/bind.h" +#include "media/base/audio_decoder_config.h" +#include "media/base/video_decoder_config.h" + +namespace media { + +MediaTracks::MediaTracks() {} + +MediaTracks::~MediaTracks() {} + +void MediaTracks::AddAudioTrack(const AudioDecoderConfig& config, + const std::string& id, + const std::string& kind, + const std::string& label, + const std::string& language) { + DCHECK(config.IsValidConfig()); + CHECK(audio_configs_.find(id) == audio_configs_.end()); + scoped_ptr<MediaTrack> track = make_scoped_ptr( + new MediaTrack(MediaTrack::Audio, id, kind, label, language)); + tracks_.push_back(std::move(track)); + audio_configs_[id] = config; +} + +void MediaTracks::AddVideoTrack(const VideoDecoderConfig& config, + const std::string& id, + const std::string& kind, + const std::string& label, + const std::string& language) { + DCHECK(config.IsValidConfig()); + CHECK(video_configs_.find(id) == video_configs_.end()); + scoped_ptr<MediaTrack> track = make_scoped_ptr( + new MediaTrack(MediaTrack::Video, id, kind, label, language)); + tracks_.push_back(std::move(track)); + video_configs_[id] = config; +} + +const AudioDecoderConfig& MediaTracks::getAudioConfig( + const std::string& id) const { + auto it = audio_configs_.find(id); + if (it != audio_configs_.end()) + return it->second; + static AudioDecoderConfig invalidConfig; + return invalidConfig; +} + +const VideoDecoderConfig& MediaTracks::getVideoConfig( + const std::string& id) const { + auto it = video_configs_.find(id); + if (it != video_configs_.end()) + return it->second; + static VideoDecoderConfig invalidConfig; + return invalidConfig; +} + +const AudioDecoderConfig& MediaTracks::getFirstAudioConfig() const { + for (const auto& track : tracks()) { + if (track->type() == MediaTrack::Audio) { + return getAudioConfig(track->id()); + } + } + static AudioDecoderConfig invalidConfig; + return invalidConfig; +} + +const VideoDecoderConfig& MediaTracks::getFirstVideoConfig() const { + for (const auto& track : tracks()) { + if (track->type() == MediaTrack::Video) { + return getVideoConfig(track->id()); + } + } + static VideoDecoderConfig invalidConfig; + return invalidConfig; +} + +} // namespace media diff --git a/chromium/media/base/media_tracks.h b/chromium/media/base/media_tracks.h new file mode 100644 index 00000000000..4e0fbb0fc1f --- /dev/null +++ b/chromium/media/base/media_tracks.h @@ -0,0 +1,62 @@ +// Copyright 2016 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_BASE_MEDIA_TRACKS_H_ +#define MEDIA_BASE_MEDIA_TRACKS_H_ + +#include <map> +#include <string> +#include <vector> + +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "media/base/media_export.h" +#include "media/base/media_track.h" + +namespace media { + +class AudioDecoderConfig; +class VideoDecoderConfig; + +class MEDIA_EXPORT MediaTracks { + public: + typedef std::vector<scoped_ptr<MediaTrack>> MediaTracksCollection; + + MediaTracks(); + ~MediaTracks(); + + // Callers need to ensure that track id is unique. + void AddAudioTrack(const AudioDecoderConfig& config, + const std::string& id, + const std::string& kind, + const std::string& label, + const std::string& language); + // Callers need to ensure that track id is unique. + void AddVideoTrack(const VideoDecoderConfig& config, + const std::string& id, + const std::string& kind, + const std::string& label, + const std::string& language); + + const MediaTracksCollection& tracks() const { return tracks_; } + + const AudioDecoderConfig& getAudioConfig(const std::string& id) const; + const VideoDecoderConfig& getVideoConfig(const std::string& id) const; + + // TODO(servolk): These are temporary helpers useful until all code paths are + // converted to properly handle multiple media tracks. + const AudioDecoderConfig& getFirstAudioConfig() const; + const VideoDecoderConfig& getFirstVideoConfig() const; + + private: + MediaTracksCollection tracks_; + std::map<std::string, AudioDecoderConfig> audio_configs_; + std::map<std::string, VideoDecoderConfig> video_configs_; + + DISALLOW_COPY_AND_ASSIGN(MediaTracks); +}; + +} // namespace media + +#endif // MEDIA_BASE_MEDIA_TRACKS_H_ diff --git a/chromium/media/base/media_util.cc b/chromium/media/base/media_util.cc index bd7929f5609..a6516cd2305 100644 --- a/chromium/media/base/media_util.cc +++ b/chromium/media/base/media_util.cc @@ -10,4 +10,13 @@ std::vector<uint8_t> EmptyExtraData() { return std::vector<uint8_t>(); } +EncryptionScheme Unencrypted() { + return EncryptionScheme(); +} + +EncryptionScheme AesCtrEncryptionScheme() { + return EncryptionScheme(EncryptionScheme::CIPHER_MODE_AES_CTR, + EncryptionScheme::Pattern()); +} + } // namespace media diff --git a/chromium/media/base/media_util.h b/chromium/media/base/media_util.h index 4e53c9a0c0e..c7ddc8bb1ea 100644 --- a/chromium/media/base/media_util.h +++ b/chromium/media/base/media_util.h @@ -8,6 +8,7 @@ #include <stdint.h> #include <vector> +#include "media/base/encryption_scheme.h" #include "media/base/media_export.h" namespace media { @@ -16,6 +17,11 @@ namespace media { // constructed with empty extra data. MEDIA_EXPORT std::vector<uint8_t> EmptyExtraData(); +// The following helper functions return new instances of EncryptionScheme that +// indicate widely used settings. +MEDIA_EXPORT EncryptionScheme Unencrypted(); +MEDIA_EXPORT EncryptionScheme AesCtrEncryptionScheme(); + } // namespace media #endif // MEDIA_BASE_UTIL_H_ diff --git a/chromium/media/base/mime_util.cc b/chromium/media/base/mime_util.cc index ff1d3369e1d..6eba4fc70b7 100644 --- a/chromium/media/base/mime_util.cc +++ b/chromium/media/base/mime_util.cc @@ -2,742 +2,42 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include <stddef.h> -#include <stdint.h> - -#include <map> - -#include "base/containers/hash_tables.h" -#include "base/lazy_instance.h" -#include "base/macros.h" -#include "base/strings/string_number_conversions.h" -#include "base/strings/string_split.h" -#include "base/strings/string_util.h" -#include "build/build_config.h" #include "media/base/mime_util.h" -#include "media/media_features.h" -#if defined(OS_ANDROID) -#include "base/android/build_info.h" -#endif +#include "base/lazy_instance.h" +#include "media/base/mime_util_internal.h" namespace media { -// Singleton utility class for mime types. -class MimeUtil { - public: - enum Codec { - INVALID_CODEC, - PCM, - MP3, - AC3, - EAC3, - MPEG2_AAC_LC, - MPEG2_AAC_MAIN, - MPEG2_AAC_SSR, - MPEG4_AAC_LC, - MPEG4_AAC_SBR_v1, - MPEG4_AAC_SBR_PS_v2, - VORBIS, - OPUS, - H264_BASELINE, - H264_MAIN, - H264_HIGH, - HEVC_MAIN, - VP8, - VP9, - THEORA - }; - - bool IsSupportedMediaMimeType(const std::string& mime_type) const; - - void ParseCodecString(const std::string& codecs, - std::vector<std::string>* codecs_out, - bool strip); - - SupportsType IsSupportedMediaFormat( - const std::string& mime_type, - const std::vector<std::string>& codecs) const; - - void RemoveProprietaryMediaTypesAndCodecsForTests(); - - private: - friend struct base::DefaultLazyInstanceTraits<MimeUtil>; - - typedef base::hash_set<int> CodecSet; - typedef std::map<std::string, CodecSet> MediaFormatMappings; - struct CodecEntry { - CodecEntry() : codec(INVALID_CODEC), is_ambiguous(true) {} - CodecEntry(Codec c, bool ambiguous) : codec(c), is_ambiguous(ambiguous) {} - Codec codec; - bool is_ambiguous; - }; - typedef std::map<std::string, CodecEntry> StringToCodecMappings; - - MimeUtil(); - - // For faster lookup, keep hash sets. - void InitializeMimeTypeMaps(); - - // Returns IsSupported if all codec IDs in |codecs| are unambiguous - // and are supported by the platform. MayBeSupported is returned if - // at least one codec ID in |codecs| is ambiguous but all the codecs - // are supported by the platform. IsNotSupported is returned if at - // least one codec ID is not supported by the platform. - SupportsType AreSupportedCodecs( - const CodecSet& supported_codecs, - const std::vector<std::string>& codecs) const; - - // Converts a codec ID into an Codec enum value and indicates - // whether the conversion was ambiguous. - // Returns true if this method was able to map |codec_id| to a specific - // Codec enum value. |codec| and |is_ambiguous| are only valid if true - // is returned. Otherwise their value is undefined after the call. - // |is_ambiguous| is true if |codec_id| did not have enough information to - // unambiguously determine the proper Codec enum value. If |is_ambiguous| - // is true |codec| contains the best guess for the intended Codec enum value. - bool StringToCodec(const std::string& codec_id, - Codec* codec, - bool* is_ambiguous) const; - - // Returns true if |codec| is supported by the platform. - // Note: This method will return false if the platform supports proprietary - // codecs but |allow_proprietary_codecs_| is set to false. - bool IsCodecSupported(Codec codec) const; - - // Returns true if |codec| refers to a proprietary codec. - bool IsCodecProprietary(Codec codec) const; - - // Returns true and sets |*default_codec| if |mime_type| has a default codec - // associated with it. Returns false otherwise and the value of - // |*default_codec| is undefined. - bool GetDefaultCodecLowerCase(const std::string& mime_type_lower_case, - Codec* default_codec) const; - - // Returns true if |mime_type_lower_case| has a default codec associated with - // it and IsCodecSupported() returns true for that particular codec. - bool IsDefaultCodecSupportedLowerCase( - const std::string& mime_type_lower_case) const; - - // A map of mime_types and hash map of the supported codecs for the mime_type. - MediaFormatMappings media_format_map_; - - // Keeps track of whether proprietary codec support should be - // advertised to callers. - bool allow_proprietary_codecs_; - - // Lookup table for string compare based string -> Codec mappings. - StringToCodecMappings string_to_codec_map_; - - DISALLOW_COPY_AND_ASSIGN(MimeUtil); -}; // class MimeUtil - // This variable is Leaky because it is accessed from WorkerPool threads. -static base::LazyInstance<MimeUtil>::Leaky g_media_mime_util = +static base::LazyInstance<internal::MimeUtil>::Leaky g_media_mime_util = LAZY_INSTANCE_INITIALIZER; -#if defined(OS_ANDROID) -static bool IsCodecSupportedOnAndroid(MimeUtil::Codec codec) { - switch (codec) { - case MimeUtil::INVALID_CODEC: - return false; - - case MimeUtil::PCM: - case MimeUtil::MP3: - case MimeUtil::MPEG4_AAC_LC: - case MimeUtil::MPEG4_AAC_SBR_v1: - case MimeUtil::MPEG4_AAC_SBR_PS_v2: - case MimeUtil::VORBIS: - case MimeUtil::H264_BASELINE: - case MimeUtil::H264_MAIN: - case MimeUtil::H264_HIGH: - case MimeUtil::VP8: - return true; - - case MimeUtil::AC3: - case MimeUtil::EAC3: - // TODO(servolk): Revisit this for AC3/EAC3 support on AndroidTV - return false; - - case MimeUtil::MPEG2_AAC_LC: - case MimeUtil::MPEG2_AAC_MAIN: - case MimeUtil::MPEG2_AAC_SSR: - // MPEG-2 variants of AAC are not supported on Android. - return false; - - case MimeUtil::OPUS: - // Opus is supported only in Lollipop+ (API Level 21). - return base::android::BuildInfo::GetInstance()->sdk_int() >= 21; - - case MimeUtil::HEVC_MAIN: -#if BUILDFLAG(ENABLE_HEVC_DEMUXING) - // HEVC/H.265 is supported in Lollipop+ (API Level 21), according to - // http://developer.android.com/reference/android/media/MediaFormat.html - return base::android::BuildInfo::GetInstance()->sdk_int() >= 21; -#else - return false; -#endif - - case MimeUtil::VP9: - // VP9 is supported only in KitKat+ (API Level 19). - return base::android::BuildInfo::GetInstance()->sdk_int() >= 19; - - case MimeUtil::THEORA: - return false; - } - - return false; -} -#endif - -enum MediaFormatType { COMMON, PROPRIETARY }; - -struct MediaFormat { - const char* const mime_type; - MediaFormatType format_type; - const char* const codecs_list; -}; - -#if defined(USE_PROPRIETARY_CODECS) -// Following is the list of RFC 6381 compliant codecs: -// mp4a.66 - MPEG-2 AAC MAIN -// mp4a.67 - MPEG-2 AAC LC -// mp4a.68 - MPEG-2 AAC SSR -// mp4a.69 - MPEG-2 extension to MPEG-1 -// mp4a.6B - MPEG-1 audio -// mp4a.40.2 - MPEG-4 AAC LC -// mp4a.40.02 - MPEG-4 AAC LC (leading 0 in aud-oti for compatibility) -// mp4a.40.5 - MPEG-4 HE-AAC v1 (AAC LC + SBR) -// mp4a.40.05 - MPEG-4 HE-AAC v1 (AAC LC + SBR) (leading 0 in aud-oti for -// compatibility) -// mp4a.40.29 - MPEG-4 HE-AAC v2 (AAC LC + SBR + PS) -// -// avc1.42E0xx - H.264 Baseline -// avc1.4D40xx - H.264 Main -// avc1.6400xx - H.264 High -static const char kMP4AudioCodecsExpression[] = - "mp4a.66,mp4a.67,mp4a.68,mp4a.69,mp4a.6B,mp4a.40.2,mp4a.40.02,mp4a.40.5," -#if BUILDFLAG(ENABLE_AC3_EAC3_AUDIO_DEMUXING) - // Only one variant each of ac3 and eac3 codec string is sufficient here, - // since these strings are parsed and mapped to MimeUtil::Codec enum values. - "ac-3,ec-3," -#endif - "mp4a.40.05,mp4a.40.29"; -static const char kMP4VideoCodecsExpression[] = - // This is not a complete list of supported avc1 codecs. It is simply used - // to register support for the corresponding Codec enum. Instead of using - // strings in these three arrays, we should use the Codec enum values. - // This will avoid confusion and unnecessary parsing at runtime. - // kUnambiguousCodecStringMap/kAmbiguousCodecStringMap should be the only - // mapping from strings to codecs. See crbug.com/461009. - "avc1.42E00A,avc1.4D400A,avc1.64000A," -#if BUILDFLAG(ENABLE_HEVC_DEMUXING) - // Any valid unambiguous HEVC codec id will work here, since these strings - // are parsed and mapped to MimeUtil::Codec enum values. - "hev1.1.6.L93.B0," -#endif - "mp4a.66,mp4a.67,mp4a.68,mp4a.69,mp4a.6B,mp4a.40.2,mp4a.40.02,mp4a.40.5," -#if BUILDFLAG(ENABLE_AC3_EAC3_AUDIO_DEMUXING) - // Only one variant each of ac3 and eac3 codec string is sufficient here, - // since these strings are parsed and mapped to MimeUtil::Codec enum values. - "ac-3,ec-3," -#endif - "mp4a.40.05,mp4a.40.29"; -#endif // USE_PROPRIETARY_CODECS - -// A list of media types (https://en.wikipedia.org/wiki/Media_type) and -// corresponding media codecs supported by these types/containers. -// Media formats marked as PROPRIETARY are not supported by Chromium, only -// Google Chrome browser supports them. -static const MediaFormat kFormatCodecMappings[] = { - {"video/webm", COMMON, "opus,vorbis,vp8,vp8.0,vp9,vp9.0"}, - {"audio/webm", COMMON, "opus,vorbis"}, - {"audio/wav", COMMON, "1"}, - {"audio/x-wav", COMMON, "1"}, -#if defined(OS_ANDROID) - // Android does not support Opus in Ogg container. - // Android does not support Theora and thus video/ogg. - {"audio/ogg", COMMON, "vorbis"}, - {"application/ogg", COMMON, "vorbis"}, -#else - {"video/ogg", COMMON, "opus,theora,vorbis"}, - {"audio/ogg", COMMON, "opus,vorbis"}, - {"application/ogg", COMMON, "opus,theora,vorbis"}, -#endif -#if defined(USE_PROPRIETARY_CODECS) - {"audio/mpeg", PROPRIETARY, "mp3"}, - {"audio/mp3", PROPRIETARY, ""}, - {"audio/x-mp3", PROPRIETARY, ""}, - {"audio/aac", PROPRIETARY, ""}, // AAC / ADTS - {"audio/mp4", PROPRIETARY, kMP4AudioCodecsExpression}, - {"audio/x-m4a", PROPRIETARY, kMP4AudioCodecsExpression}, - {"video/mp4", PROPRIETARY, kMP4VideoCodecsExpression}, - {"video/x-m4v", PROPRIETARY, kMP4VideoCodecsExpression}, -#if BUILDFLAG(ENABLE_MSE_MPEG2TS_STREAM_PARSER) - {"video/mp2t", PROPRIETARY, kMP4VideoCodecsExpression}, -#endif -#if defined(OS_ANDROID) - // HTTP Live Streaming (HLS) - {"application/x-mpegurl", PROPRIETARY, kMP4VideoCodecsExpression}, - {"application/vnd.apple.mpegurl", PROPRIETARY, kMP4VideoCodecsExpression} -#endif -#endif // USE_PROPRIETARY_CODECS -}; - -struct CodecIDMappings { - const char* const codec_id; - MimeUtil::Codec codec; -}; - -// List of codec IDs that provide enough information to determine the -// codec and profile being requested. -// -// The "mp4a" strings come from RFC 6381. -static const CodecIDMappings kUnambiguousCodecStringMap[] = { - {"1", MimeUtil::PCM}, // We only allow this for WAV so it isn't ambiguous. - // avc1/avc3.XXXXXX may be unambiguous; handled by ParseH264CodecID(). - // hev1/hvc1.XXXXXX may be unambiguous; handled by ParseHEVCCodecID(). - {"mp3", MimeUtil::MP3}, - {"mp4a.66", MimeUtil::MPEG2_AAC_MAIN}, - {"mp4a.67", MimeUtil::MPEG2_AAC_LC}, - {"mp4a.68", MimeUtil::MPEG2_AAC_SSR}, - {"mp4a.69", MimeUtil::MP3}, - {"mp4a.6B", MimeUtil::MP3}, - {"mp4a.40.2", MimeUtil::MPEG4_AAC_LC}, - {"mp4a.40.02", MimeUtil::MPEG4_AAC_LC}, - {"mp4a.40.5", MimeUtil::MPEG4_AAC_SBR_v1}, - {"mp4a.40.05", MimeUtil::MPEG4_AAC_SBR_v1}, - {"mp4a.40.29", MimeUtil::MPEG4_AAC_SBR_PS_v2}, -#if BUILDFLAG(ENABLE_AC3_EAC3_AUDIO_DEMUXING) - // TODO(servolk): Strictly speaking only mp4a.A5 and mp4a.A6 codec ids are - // valid according to RFC 6381 section 3.3, 3.4. Lower-case oti (mp4a.a5 and - // mp4a.a6) should be rejected. But we used to allow those in older versions - // of Chromecast firmware and some apps (notably MPL) depend on those codec - // types being supported, so they should be allowed for now - // (crbug.com/564960). - {"ac-3", MimeUtil::AC3}, - {"mp4a.a5", MimeUtil::AC3}, - {"mp4a.A5", MimeUtil::AC3}, - {"ec-3", MimeUtil::EAC3}, - {"mp4a.a6", MimeUtil::EAC3}, - {"mp4a.A6", MimeUtil::EAC3}, -#endif - {"vorbis", MimeUtil::VORBIS}, - {"opus", MimeUtil::OPUS}, - {"vp8", MimeUtil::VP8}, - {"vp8.0", MimeUtil::VP8}, - {"vp9", MimeUtil::VP9}, - {"vp9.0", MimeUtil::VP9}, - {"theora", MimeUtil::THEORA}}; - -// List of codec IDs that are ambiguous and don't provide -// enough information to determine the codec and profile. -// The codec in these entries indicate the codec and profile -// we assume the user is trying to indicate. -static const CodecIDMappings kAmbiguousCodecStringMap[] = { - {"mp4a.40", MimeUtil::MPEG4_AAC_LC}, - {"avc1", MimeUtil::H264_BASELINE}, - {"avc3", MimeUtil::H264_BASELINE}, - // avc1/avc3.XXXXXX may be ambiguous; handled by ParseH264CodecID(). -}; - -#if BUILDFLAG(ENABLE_MSE_MPEG2TS_STREAM_PARSER) -static const char kHexString[] = "0123456789ABCDEF"; -static char IntToHex(int i) { - DCHECK_GE(i, 0) << i << " not a hex value"; - DCHECK_LE(i, 15) << i << " not a hex value"; - return kHexString[i]; -} - -std::string TranslateLegacyAvc1CodecIds(const std::string& codec_id) { - // Special handling for old, pre-RFC 6381 format avc1 strings, which are still - // being used by some HLS apps to preserve backward compatibility with older - // iOS devices. The old format was avc1.<profile>.<level> - // Where <profile> is H.264 profile_idc encoded as a decimal number, i.e. - // 66 is baseline profile (0x42) - // 77 is main profile (0x4d) - // 100 is high profile (0x64) - // And <level> is H.264 level multiplied by 10, also encoded as decimal number - // E.g. <level> 31 corresponds to H.264 level 3.1 - // See, for example, http://qtdevseed.apple.com/qadrift/testcases/tc-0133.php - uint32_t level_start = 0; - std::string result; - if (base::StartsWith(codec_id, "avc1.66.", base::CompareCase::SENSITIVE)) { - level_start = 8; - result = "avc1.4200"; - } else if (base::StartsWith(codec_id, "avc1.77.", - base::CompareCase::SENSITIVE)) { - level_start = 8; - result = "avc1.4D00"; - } else if (base::StartsWith(codec_id, "avc1.100.", - base::CompareCase::SENSITIVE)) { - level_start = 9; - result = "avc1.6400"; - } - - uint32_t level = 0; - if (level_start > 0 && - base::StringToUint(codec_id.substr(level_start), &level) && level < 256) { - // This is a valid legacy avc1 codec id - return the codec id translated - // into RFC 6381 format. - result.push_back(IntToHex(level >> 4)); - result.push_back(IntToHex(level & 0xf)); - return result; - } - - // This is not a valid legacy avc1 codec id - return the original codec id. - return codec_id; -} -#endif - -MimeUtil::MimeUtil() : allow_proprietary_codecs_(false) { - InitializeMimeTypeMaps(); -} - -SupportsType MimeUtil::AreSupportedCodecs( - const CodecSet& supported_codecs, - const std::vector<std::string>& codecs) const { - DCHECK(!supported_codecs.empty()); - DCHECK(!codecs.empty()); - - SupportsType result = IsSupported; - for (size_t i = 0; i < codecs.size(); ++i) { - bool is_ambiguous = true; - Codec codec = INVALID_CODEC; - if (!StringToCodec(codecs[i], &codec, &is_ambiguous)) - return IsNotSupported; - - if (!IsCodecSupported(codec) || - supported_codecs.find(codec) == supported_codecs.end()) { - return IsNotSupported; - } - - if (is_ambiguous) - result = MayBeSupported; - } - - return result; -} - -void MimeUtil::InitializeMimeTypeMaps() { - // Initialize the supported media types. -#if defined(USE_PROPRIETARY_CODECS) - allow_proprietary_codecs_ = true; -#endif - - for (size_t i = 0; i < arraysize(kUnambiguousCodecStringMap); ++i) { - string_to_codec_map_[kUnambiguousCodecStringMap[i].codec_id] = - CodecEntry(kUnambiguousCodecStringMap[i].codec, false); - } - - for (size_t i = 0; i < arraysize(kAmbiguousCodecStringMap); ++i) { - string_to_codec_map_[kAmbiguousCodecStringMap[i].codec_id] = - CodecEntry(kAmbiguousCodecStringMap[i].codec, true); - } - - // Initialize the supported media formats. - for (size_t i = 0; i < arraysize(kFormatCodecMappings); ++i) { - std::vector<std::string> mime_type_codecs; - ParseCodecString(kFormatCodecMappings[i].codecs_list, &mime_type_codecs, - false); - - CodecSet codecs; - for (size_t j = 0; j < mime_type_codecs.size(); ++j) { - Codec codec = INVALID_CODEC; - bool is_ambiguous = true; - CHECK(StringToCodec(mime_type_codecs[j], &codec, &is_ambiguous)); - DCHECK(!is_ambiguous); - codecs.insert(codec); - } - - media_format_map_[kFormatCodecMappings[i].mime_type] = codecs; - } -} - -bool MimeUtil::IsSupportedMediaMimeType(const std::string& mime_type) const { - return media_format_map_.find(base::ToLowerASCII(mime_type)) != - media_format_map_.end(); -} - -void MimeUtil::ParseCodecString(const std::string& codecs, - std::vector<std::string>* codecs_out, - bool strip) { - *codecs_out = base::SplitString( - base::TrimString(codecs, "\"", base::TRIM_ALL), - ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); - - // Convert empty or all-whitespace input to 0 results. - if (codecs_out->size() == 1 && (*codecs_out)[0].empty()) - codecs_out->clear(); - - if (!strip) - return; - - // Strip everything past the first '.' - for (std::vector<std::string>::iterator it = codecs_out->begin(); - it != codecs_out->end(); - ++it) { - size_t found = it->find_first_of('.'); - if (found != std::string::npos) - it->resize(found); - } -} - -SupportsType MimeUtil::IsSupportedMediaFormat( - const std::string& mime_type, - const std::vector<std::string>& codecs) const { - const std::string mime_type_lower_case = base::ToLowerASCII(mime_type); - MediaFormatMappings::const_iterator it_media_format_map = - media_format_map_.find(mime_type_lower_case); - if (it_media_format_map == media_format_map_.end()) - return IsNotSupported; - - if (it_media_format_map->second.empty()) { - // We get here if the mimetype does not expect a codecs parameter. - return (codecs.empty() && - IsDefaultCodecSupportedLowerCase(mime_type_lower_case)) - ? IsSupported - : IsNotSupported; - } - - if (codecs.empty()) { - // We get here if the mimetype expects to get a codecs parameter, - // but didn't get one. If |mime_type_lower_case| does not have a default - // codec the best we can do is say "maybe" because we don't have enough - // information. - Codec default_codec = INVALID_CODEC; - if (!GetDefaultCodecLowerCase(mime_type_lower_case, &default_codec)) - return MayBeSupported; - - return IsCodecSupported(default_codec) ? IsSupported : IsNotSupported; - } - -#if BUILDFLAG(ENABLE_MSE_MPEG2TS_STREAM_PARSER) - if (mime_type_lower_case == "video/mp2t") { - std::vector<std::string> codecs_to_check; - for (const auto& codec_id : codecs) { - codecs_to_check.push_back(TranslateLegacyAvc1CodecIds(codec_id)); - } - return AreSupportedCodecs(it_media_format_map->second, codecs_to_check); - } -#endif - - return AreSupportedCodecs(it_media_format_map->second, codecs); -} - -void MimeUtil::RemoveProprietaryMediaTypesAndCodecsForTests() { - for (size_t i = 0; i < arraysize(kFormatCodecMappings); ++i) - if (kFormatCodecMappings[i].format_type == PROPRIETARY) - media_format_map_.erase(kFormatCodecMappings[i].mime_type); - allow_proprietary_codecs_ = false; -} - -static bool IsValidH264Level(const std::string& level_str) { - uint32_t level; - if (level_str.size() != 2 || !base::HexStringToUInt(level_str, &level)) - return false; - - // Valid levels taken from Table A-1 in ISO-14496-10. - // Essentially |level_str| is toHex(10 * level). - return ((level >= 10 && level <= 13) || - (level >= 20 && level <= 22) || - (level >= 30 && level <= 32) || - (level >= 40 && level <= 42) || - (level >= 50 && level <= 51)); -} - -// Handle parsing H.264 codec IDs as outlined in RFC 6381 and ISO-14496-10. -// avc1.42x0yy - H.264 Baseline -// avc1.4Dx0yy - H.264 Main -// avc1.64x0yy - H.264 High -// -// avc1.xxxxxx & avc3.xxxxxx are considered ambiguous forms that are trying to -// signal H.264 Baseline. For example, the idc_level, profile_idc and -// constraint_set3_flag pieces may explicitly require decoder to conform to -// baseline profile at the specified level (see Annex A and constraint_set0 in -// ISO-14496-10). -static bool ParseH264CodecID(const std::string& codec_id, - MimeUtil::Codec* codec, - bool* is_ambiguous) { - // Make sure we have avc1.xxxxxx or avc3.xxxxxx , where xxxxxx are hex digits - if (!base::StartsWith(codec_id, "avc1.", base::CompareCase::SENSITIVE) && - !base::StartsWith(codec_id, "avc3.", base::CompareCase::SENSITIVE)) { - return false; - } - if (codec_id.size() != 11 || - !base::IsHexDigit(codec_id[5]) || !base::IsHexDigit(codec_id[6]) || - !base::IsHexDigit(codec_id[7]) || !base::IsHexDigit(codec_id[8]) || - !base::IsHexDigit(codec_id[9]) || !base::IsHexDigit(codec_id[10])) { - return false; - } - - // Validate constraint flags and reserved bits. - if (!base::IsHexDigit(codec_id[7]) || codec_id[8] != '0') { - *codec = MimeUtil::H264_BASELINE; - *is_ambiguous = true; - return true; - } - - // Extract the profile. - std::string profile = base::ToUpperASCII(codec_id.substr(5, 2)); - if (profile == "42") { - *codec = MimeUtil::H264_BASELINE; - } else if (profile == "4D") { - *codec = MimeUtil::H264_MAIN; - } else if (profile == "64") { - *codec = MimeUtil::H264_HIGH; - } else { - *codec = MimeUtil::H264_BASELINE; - *is_ambiguous = true; - return true; - } - - // Validate level. - *is_ambiguous = !IsValidH264Level(codec_id.substr(9)); - return true; -} - -#if BUILDFLAG(ENABLE_HEVC_DEMUXING) -// ISO/IEC FDIS 14496-15 standard section E.3 describes the syntax of codec ids -// reserved for HEVC. According to that spec HEVC codec id must start with -// either "hev1." or "hvc1.". We don't yet support full parsing of HEVC codec -// ids, but since no other codec id starts with those string we'll just treat -// any string starting with "hev1." or "hvc1." as valid HEVC codec ids. -// crbug.com/482761 -static bool ParseHEVCCodecID(const std::string& codec_id, - MimeUtil::Codec* codec, - bool* is_ambiguous) { - if (base::StartsWith(codec_id, "hev1.", base::CompareCase::SENSITIVE) || - base::StartsWith(codec_id, "hvc1.", base::CompareCase::SENSITIVE)) { - *codec = MimeUtil::HEVC_MAIN; - - // TODO(servolk): Full HEVC codec id parsing is not implemented yet (see - // crbug.com/482761). So treat HEVC codec ids as ambiguous for now. - *is_ambiguous = true; - - // TODO(servolk): Most HEVC codec ids are treated as ambiguous (see above), - // but we need to recognize at least one valid unambiguous HEVC codec id, - // which is added into kMP4VideoCodecsExpression. We need it to be - // unambiguous to avoid DCHECK(!is_ambiguous) in InitializeMimeTypeMaps. We - // also use these in unit tests (see - // content/browser/media/media_canplaytype_browsertest.cc). - // Remove this workaround after crbug.com/482761 is fixed. - if (codec_id == "hev1.1.6.L93.B0" || codec_id == "hvc1.1.6.L93.B0") { - *is_ambiguous = false; - } - - return true; - } - - return false; -} -#endif - -bool MimeUtil::StringToCodec(const std::string& codec_id, - Codec* codec, - bool* is_ambiguous) const { - StringToCodecMappings::const_iterator itr = - string_to_codec_map_.find(codec_id); - if (itr != string_to_codec_map_.end()) { - *codec = itr->second.codec; - *is_ambiguous = itr->second.is_ambiguous; - return true; - } - - // If |codec_id| is not in |string_to_codec_map_|, then we assume that it is - // either H.264 or HEVC/H.265 codec ID because currently those are the only - // ones that are not added to the |string_to_codec_map_| and require parsing. -#if BUILDFLAG(ENABLE_HEVC_DEMUXING) - if (ParseHEVCCodecID(codec_id, codec, is_ambiguous)) { - return true; - } -#endif - return ParseH264CodecID(codec_id, codec, is_ambiguous); -} - -bool MimeUtil::IsCodecSupported(Codec codec) const { - DCHECK_NE(codec, INVALID_CODEC); - -#if defined(OS_ANDROID) - if (!IsCodecSupportedOnAndroid(codec)) - return false; -#endif - - return allow_proprietary_codecs_ || !IsCodecProprietary(codec); -} - -bool MimeUtil::IsCodecProprietary(Codec codec) const { - switch (codec) { - case INVALID_CODEC: - case AC3: - case EAC3: - case MP3: - case MPEG2_AAC_LC: - case MPEG2_AAC_MAIN: - case MPEG2_AAC_SSR: - case MPEG4_AAC_LC: - case MPEG4_AAC_SBR_v1: - case MPEG4_AAC_SBR_PS_v2: - case H264_BASELINE: - case H264_MAIN: - case H264_HIGH: - case HEVC_MAIN: - return true; - - case PCM: - case VORBIS: - case OPUS: - case VP8: - case VP9: - case THEORA: - return false; - } - - return true; -} - -bool MimeUtil::GetDefaultCodecLowerCase(const std::string& mime_type_lower_case, - Codec* default_codec) const { - if (mime_type_lower_case == "audio/mpeg" || - mime_type_lower_case == "audio/mp3" || - mime_type_lower_case == "audio/x-mp3") { - *default_codec = MimeUtil::MP3; - return true; - } - - if (mime_type_lower_case == "audio/aac") { - *default_codec = MimeUtil::MPEG4_AAC_LC; - return true; - } - - return false; -} - -bool MimeUtil::IsDefaultCodecSupportedLowerCase( - const std::string& mime_type_lower_case) const { - Codec default_codec = Codec::INVALID_CODEC; - if (!GetDefaultCodecLowerCase(mime_type_lower_case, &default_codec)) - return false; - return IsCodecSupported(default_codec); -} - bool IsSupportedMediaMimeType(const std::string& mime_type) { - return g_media_mime_util.Get().IsSupportedMediaMimeType(mime_type); + return g_media_mime_util.Pointer()->IsSupportedMediaMimeType(mime_type); } SupportsType IsSupportedMediaFormat(const std::string& mime_type, const std::vector<std::string>& codecs) { - return g_media_mime_util.Get().IsSupportedMediaFormat(mime_type, codecs); + return g_media_mime_util.Pointer()->IsSupportedMediaFormat(mime_type, codecs, + false); +} + +SupportsType IsSupportedEncryptedMediaFormat( + const std::string& mime_type, + const std::vector<std::string>& codecs) { + return g_media_mime_util.Pointer()->IsSupportedMediaFormat(mime_type, codecs, + true); } void ParseCodecString(const std::string& codecs, std::vector<std::string>* codecs_out, - const bool strip) { - g_media_mime_util.Get().ParseCodecString(codecs, codecs_out, strip); + bool strip) { + g_media_mime_util.Pointer()->ParseCodecString(codecs, codecs_out, strip); } void RemoveProprietaryMediaTypesAndCodecsForTests() { - g_media_mime_util.Get().RemoveProprietaryMediaTypesAndCodecsForTests(); + g_media_mime_util.Pointer()->RemoveProprietaryMediaTypesAndCodecs(); } } // namespace media diff --git a/chromium/media/base/mime_util.h b/chromium/media/base/mime_util.h index 058953e6a33..6a49eba2a6e 100644 --- a/chromium/media/base/mime_util.h +++ b/chromium/media/base/mime_util.h @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef MEDIA_BASE_MIME_UTIL_H__ -#define MEDIA_BASE_MIME_UTIL_H__ +#ifndef MEDIA_BASE_MIME_UTIL_H_ +#define MEDIA_BASE_MIME_UTIL_H_ #include <string> #include <vector> @@ -25,19 +25,16 @@ MEDIA_EXPORT void ParseCodecString(const std::string& codecs, std::vector<std::string>* codecs_out, bool strip); -// Indicates that the MIME type and (possible codec string) are supported by the -// underlying platform. +// Indicates that the MIME type and (possible codec string) are supported. enum SupportsType { - // The underlying platform is known not to support the given MIME type and - // codec combination. + // The given MIME type and codec combination is not supported. IsNotSupported, - // The underlying platform is known to support the given MIME type and codec - // combination. + // The given MIME type and codec combination is supported. IsSupported, - // The underlying platform is unsure whether the given MIME type and codec - // combination can be rendered or not before actually trying to play it. + // There's not enough information to determine if the given MIME type and + // codec combination can be rendered or not before actually trying to play it. MayBeSupported }; @@ -56,6 +53,11 @@ MEDIA_EXPORT SupportsType IsSupportedMediaFormat(const std::string& mime_type, const std::vector<std::string>& codecs); +// Similar to the above, but for encrypted formats. +MEDIA_EXPORT SupportsType +IsSupportedEncryptedMediaFormat(const std::string& mime_type, + const std::vector<std::string>& codecs); + // Test only method that removes proprietary media types and codecs from the // list of supported MIME types and codecs. These types and codecs must be // removed to ensure consistent layout test results across all Chromium @@ -64,5 +66,4 @@ MEDIA_EXPORT void RemoveProprietaryMediaTypesAndCodecsForTests(); } // namespace media -#endif // MEDIA_BASE_MIME_UTIL_H__ - +#endif // MEDIA_BASE_MIME_UTIL_H_ diff --git a/chromium/media/base/mime_util_internal.cc b/chromium/media/base/mime_util_internal.cc new file mode 100644 index 00000000000..7a26254f54d --- /dev/null +++ b/chromium/media/base/mime_util_internal.cc @@ -0,0 +1,685 @@ +// Copyright 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. + +#include "media/base/mime_util_internal.h" + +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "build/build_config.h" +#include "media/base/media.h" +#include "media/base/video_codecs.h" +#include "media/media_features.h" + +#if defined(OS_ANDROID) +#include "base/android/build_info.h" +#include "media/base/android/media_codec_util.h" +#endif + +namespace media { +namespace internal { + +struct CodecIDMappings { + const char* const codec_id; + MimeUtil::Codec codec; +}; + +// List of codec IDs that provide enough information to determine the +// codec and profile being requested. +// +// The "mp4a" strings come from RFC 6381. +static const CodecIDMappings kUnambiguousCodecStringMap[] = { + {"1", MimeUtil::PCM}, // We only allow this for WAV so it isn't ambiguous. + // avc1/avc3.XXXXXX may be unambiguous; handled by ParseAVCCodecId(). + // hev1/hvc1.XXXXXX may be unambiguous; handled by ParseHEVCCodecID(). + {"mp3", MimeUtil::MP3}, + // Following is the list of RFC 6381 compliant audio codec strings: + // mp4a.66 - MPEG-2 AAC MAIN + // mp4a.67 - MPEG-2 AAC LC + // mp4a.68 - MPEG-2 AAC SSR + // mp4a.69 - MPEG-2 extension to MPEG-1 (MP3) + // mp4a.6B - MPEG-1 audio (MP3) + // mp4a.40.2 - MPEG-4 AAC LC + // mp4a.40.02 - MPEG-4 AAC LC (leading 0 in aud-oti for compatibility) + // mp4a.40.5 - MPEG-4 HE-AAC v1 (AAC LC + SBR) + // mp4a.40.05 - MPEG-4 HE-AAC v1 (AAC LC + SBR) (leading 0 in aud-oti for + // compatibility) + // mp4a.40.29 - MPEG-4 HE-AAC v2 (AAC LC + SBR + PS) + {"mp4a.66", MimeUtil::MPEG2_AAC}, + {"mp4a.67", MimeUtil::MPEG2_AAC}, + {"mp4a.68", MimeUtil::MPEG2_AAC}, + {"mp4a.69", MimeUtil::MP3}, + {"mp4a.6B", MimeUtil::MP3}, + {"mp4a.40.2", MimeUtil::MPEG4_AAC}, + {"mp4a.40.02", MimeUtil::MPEG4_AAC}, + {"mp4a.40.5", MimeUtil::MPEG4_AAC}, + {"mp4a.40.05", MimeUtil::MPEG4_AAC}, + {"mp4a.40.29", MimeUtil::MPEG4_AAC}, +#if BUILDFLAG(ENABLE_AC3_EAC3_AUDIO_DEMUXING) + // TODO(servolk): Strictly speaking only mp4a.A5 and mp4a.A6 codec ids are + // valid according to RFC 6381 section 3.3, 3.4. Lower-case oti (mp4a.a5 and + // mp4a.a6) should be rejected. But we used to allow those in older versions + // of Chromecast firmware and some apps (notably MPL) depend on those codec + // types being supported, so they should be allowed for now + // (crbug.com/564960). + {"ac-3", MimeUtil::AC3}, + {"mp4a.a5", MimeUtil::AC3}, + {"mp4a.A5", MimeUtil::AC3}, + {"ec-3", MimeUtil::EAC3}, + {"mp4a.a6", MimeUtil::EAC3}, + {"mp4a.A6", MimeUtil::EAC3}, +#endif + {"vorbis", MimeUtil::VORBIS}, + {"opus", MimeUtil::OPUS}, + {"vp8", MimeUtil::VP8}, + {"vp8.0", MimeUtil::VP8}, + {"vp9", MimeUtil::VP9}, + {"vp9.0", MimeUtil::VP9}, + {"theora", MimeUtil::THEORA}}; + +// List of codec IDs that are ambiguous and don't provide +// enough information to determine the codec and profile. +// The codec in these entries indicate the codec and profile +// we assume the user is trying to indicate. +static const CodecIDMappings kAmbiguousCodecStringMap[] = { + {"mp4a.40", MimeUtil::MPEG4_AAC}, + {"avc1", MimeUtil::H264}, + {"avc3", MimeUtil::H264}, + // avc1/avc3.XXXXXX may be ambiguous; handled by ParseAVCCodecId(). +}; + +#if BUILDFLAG(ENABLE_MSE_MPEG2TS_STREAM_PARSER) +static const char kHexString[] = "0123456789ABCDEF"; +static char IntToHex(int i) { + DCHECK_GE(i, 0) << i << " not a hex value"; + DCHECK_LE(i, 15) << i << " not a hex value"; + return kHexString[i]; +} + +static std::string TranslateLegacyAvc1CodecIds(const std::string& codec_id) { + // Special handling for old, pre-RFC 6381 format avc1 strings, which are still + // being used by some HLS apps to preserve backward compatibility with older + // iOS devices. The old format was avc1.<profile>.<level> + // Where <profile> is H.264 profile_idc encoded as a decimal number, i.e. + // 66 is baseline profile (0x42) + // 77 is main profile (0x4d) + // 100 is high profile (0x64) + // And <level> is H.264 level multiplied by 10, also encoded as decimal number + // E.g. <level> 31 corresponds to H.264 level 3.1 + // See, for example, http://qtdevseed.apple.com/qadrift/testcases/tc-0133.php + uint32_t level_start = 0; + std::string result; + if (base::StartsWith(codec_id, "avc1.66.", base::CompareCase::SENSITIVE)) { + level_start = 8; + result = "avc1.4200"; + } else if (base::StartsWith(codec_id, "avc1.77.", + base::CompareCase::SENSITIVE)) { + level_start = 8; + result = "avc1.4D00"; + } else if (base::StartsWith(codec_id, "avc1.100.", + base::CompareCase::SENSITIVE)) { + level_start = 9; + result = "avc1.6400"; + } + + uint32_t level = 0; + if (level_start > 0 && + base::StringToUint(codec_id.substr(level_start), &level) && level < 256) { + // This is a valid legacy avc1 codec id - return the codec id translated + // into RFC 6381 format. + result.push_back(IntToHex(level >> 4)); + result.push_back(IntToHex(level & 0xf)); + return result; + } + + // This is not a valid legacy avc1 codec id - return the original codec id. + return codec_id; +} +#endif + +static bool IsValidH264Level(uint8_t level_idc) { + // Valid levels taken from Table A-1 in ISO/IEC 14496-10. + // Level_idc represents the standard level represented as decimal number + // multiplied by ten, e.g. level_idc==32 corresponds to level==3.2 + return ((level_idc >= 10 && level_idc <= 13) || + (level_idc >= 20 && level_idc <= 22) || + (level_idc >= 30 && level_idc <= 32) || + (level_idc >= 40 && level_idc <= 42) || + (level_idc >= 50 && level_idc <= 51)); +} + +#if BUILDFLAG(ENABLE_HEVC_DEMUXING) +// ISO/IEC FDIS 14496-15 standard section E.3 describes the syntax of codec ids +// reserved for HEVC. According to that spec HEVC codec id must start with +// either "hev1." or "hvc1.". We don't yet support full parsing of HEVC codec +// ids, but since no other codec id starts with those string we'll just treat +// any string starting with "hev1." or "hvc1." as valid HEVC codec ids. +// crbug.com/482761 +static bool ParseHEVCCodecID(const std::string& codec_id, + MimeUtil::Codec* codec, + bool* is_ambiguous) { + if (base::StartsWith(codec_id, "hev1.", base::CompareCase::SENSITIVE) || + base::StartsWith(codec_id, "hvc1.", base::CompareCase::SENSITIVE)) { + *codec = MimeUtil::HEVC_MAIN; + + // TODO(servolk): Full HEVC codec id parsing is not implemented yet (see + // crbug.com/482761). So treat HEVC codec ids as ambiguous for now. + *is_ambiguous = true; + + // TODO(servolk): Most HEVC codec ids are treated as ambiguous (see above), + // but we need to recognize at least one valid unambiguous HEVC codec id, + // which is added into kMP4VideoCodecsExpression. We need it to be + // unambiguous to avoid DCHECK(!is_ambiguous) in InitializeMimeTypeMaps. We + // also use these in unit tests (see + // content/browser/media/media_canplaytype_browsertest.cc). + // Remove this workaround after crbug.com/482761 is fixed. + if (codec_id == "hev1.1.6.L93.B0" || codec_id == "hvc1.1.6.L93.B0") { + *is_ambiguous = false; + } + + return true; + } + + return false; +} +#endif + +MimeUtil::MimeUtil() : allow_proprietary_codecs_(false) { +#if defined(OS_ANDROID) + platform_info_.is_unified_media_pipeline_enabled = + IsUnifiedMediaPipelineEnabled(); + // When the unified media pipeline is enabled, we need support for both GPU + // video decoders and MediaCodec; indicated by HasPlatformDecoderSupport(). + // When the Android pipeline is used, we only need access to MediaCodec. + platform_info_.has_platform_decoders = ArePlatformDecodersAvailable(); + platform_info_.has_platform_vp8_decoder = + MediaCodecUtil::IsVp8DecoderAvailable(); + platform_info_.has_platform_vp9_decoder = + MediaCodecUtil::IsVp9DecoderAvailable(); + platform_info_.supports_opus = PlatformHasOpusSupport(); +#endif + + InitializeMimeTypeMaps(); +} + +MimeUtil::~MimeUtil() {} + +SupportsType MimeUtil::AreSupportedCodecs( + const CodecSet& supported_codecs, + const std::vector<std::string>& codecs, + const std::string& mime_type_lower_case, + bool is_encrypted) const { + DCHECK(!supported_codecs.empty()); + DCHECK(!codecs.empty()); + + SupportsType result = IsSupported; + for (size_t i = 0; i < codecs.size(); ++i) { + bool is_ambiguous = true; + Codec codec = INVALID_CODEC; + if (!StringToCodec(codecs[i], &codec, &is_ambiguous, is_encrypted)) + return IsNotSupported; + + if (!IsCodecSupported(codec, mime_type_lower_case, is_encrypted) || + supported_codecs.find(codec) == supported_codecs.end()) { + return IsNotSupported; + } + + if (is_ambiguous) + result = MayBeSupported; + } + + return result; +} + +void MimeUtil::InitializeMimeTypeMaps() { +#if defined(USE_PROPRIETARY_CODECS) + allow_proprietary_codecs_ = true; +#endif + + for (size_t i = 0; i < arraysize(kUnambiguousCodecStringMap); ++i) { + string_to_codec_map_[kUnambiguousCodecStringMap[i].codec_id] = + CodecEntry(kUnambiguousCodecStringMap[i].codec, false); + } + + for (size_t i = 0; i < arraysize(kAmbiguousCodecStringMap); ++i) { + string_to_codec_map_[kAmbiguousCodecStringMap[i].codec_id] = + CodecEntry(kAmbiguousCodecStringMap[i].codec, true); + } + + AddSupportedMediaFormats(); +} + +// Each call to AddContainerWithCodecs() contains a media type +// (https://en.wikipedia.org/wiki/Media_type) and corresponding media codec(s) +// supported by these types/containers. +// TODO(ddorwin): Replace insert() calls with initializer_list when allowed. +void MimeUtil::AddSupportedMediaFormats() { + CodecSet implicit_codec; + CodecSet wav_codecs; + wav_codecs.insert(PCM); + + CodecSet ogg_audio_codecs; + ogg_audio_codecs.insert(OPUS); + ogg_audio_codecs.insert(VORBIS); + CodecSet ogg_video_codecs; +#if !defined(OS_ANDROID) + ogg_video_codecs.insert(THEORA); +#endif // !defined(OS_ANDROID) + CodecSet ogg_codecs(ogg_audio_codecs); + ogg_codecs.insert(ogg_video_codecs.begin(), ogg_video_codecs.end()); + + CodecSet webm_audio_codecs; + webm_audio_codecs.insert(OPUS); + webm_audio_codecs.insert(VORBIS); + CodecSet webm_video_codecs; + webm_video_codecs.insert(VP8); + webm_video_codecs.insert(VP9); + CodecSet webm_codecs(webm_audio_codecs); + webm_codecs.insert(webm_video_codecs.begin(), webm_video_codecs.end()); + +#if defined(USE_PROPRIETARY_CODECS) + CodecSet mp3_codecs; + mp3_codecs.insert(MP3); + + CodecSet aac; + aac.insert(MPEG2_AAC); + aac.insert(MPEG4_AAC); + + CodecSet avc_and_aac(aac); + avc_and_aac.insert(H264); + + CodecSet mp4_audio_codecs(aac); + mp4_audio_codecs.insert(MP3); +#if BUILDFLAG(ENABLE_AC3_EAC3_AUDIO_DEMUXING) + mp4_audio_codecs.insert(AC3); + mp4_audio_codecs.insert(EAC3); +#endif // BUILDFLAG(ENABLE_AC3_EAC3_AUDIO_DEMUXING) + + CodecSet mp4_video_codecs; + mp4_video_codecs.insert(H264); +#if BUILDFLAG(ENABLE_HEVC_DEMUXING) + mp4_video_codecs.insert(HEVC_MAIN); +#endif // BUILDFLAG(ENABLE_HEVC_DEMUXING) + CodecSet mp4_codecs(mp4_audio_codecs); + mp4_codecs.insert(mp4_video_codecs.begin(), mp4_video_codecs.end()); +#endif // defined(USE_PROPRIETARY_CODECS) + + AddContainerWithCodecs("audio/wav", wav_codecs, false); + AddContainerWithCodecs("audio/x-wav", wav_codecs, false); + AddContainerWithCodecs("audio/webm", webm_audio_codecs, false); + DCHECK(!webm_video_codecs.empty()); + AddContainerWithCodecs("video/webm", webm_codecs, false); + AddContainerWithCodecs("audio/ogg", ogg_audio_codecs, false); + // video/ogg is only supported if an appropriate video codec is supported. + // Note: This assumes such codecs cannot be later excluded. + if (!ogg_video_codecs.empty()) + AddContainerWithCodecs("video/ogg", ogg_codecs, false); + // TODO(ddorwin): Should the application type support Opus? + AddContainerWithCodecs("application/ogg", ogg_codecs, false); + +#if defined(USE_PROPRIETARY_CODECS) + AddContainerWithCodecs("audio/mpeg", mp3_codecs, true); // Allow "mp3". + AddContainerWithCodecs("audio/mp3", implicit_codec, true); + AddContainerWithCodecs("audio/x-mp3", implicit_codec, true); + AddContainerWithCodecs("audio/aac", implicit_codec, true); // AAC / ADTS. + AddContainerWithCodecs("audio/mp4", mp4_audio_codecs, true); + DCHECK(!mp4_video_codecs.empty()); + AddContainerWithCodecs("video/mp4", mp4_codecs, true); + // These strings are supported for backwards compatibility only and thus only + // support the codecs needed for compatibility. + AddContainerWithCodecs("audio/x-m4a", aac, true); + AddContainerWithCodecs("video/x-m4v", avc_and_aac, true); + +#if BUILDFLAG(ENABLE_MSE_MPEG2TS_STREAM_PARSER) + // TODO(ddorwin): Exactly which codecs should be supported? + DCHECK(!mp4_video_codecs.empty()); + AddContainerWithCodecs("video/mp2t", mp4_codecs, true); +#endif // BUILDFLAG(ENABLE_MSE_MPEG2TS_STREAM_PARSER) +#if defined(OS_ANDROID) + // HTTP Live Streaming (HLS). + // TODO(ddorwin): Is any MP3 codec string variant included in real queries? + CodecSet hls_codecs(avc_and_aac); + hls_codecs.insert(MP3); + AddContainerWithCodecs("application/x-mpegurl", hls_codecs, true); + AddContainerWithCodecs("application/vnd.apple.mpegurl", hls_codecs, true); +#endif // defined(OS_ANDROID) +#endif // defined(USE_PROPRIETARY_CODECS) +} + +void MimeUtil::AddContainerWithCodecs(const std::string& mime_type, + const CodecSet& codecs, + bool is_proprietary_mime_type) { +#if !defined(USE_PROPRIETARY_CODECS) + DCHECK(!is_proprietary_mime_type); +#endif + + media_format_map_[mime_type] = codecs; + + if (is_proprietary_mime_type) + proprietary_media_containers_.push_back(mime_type); +} + +bool MimeUtil::IsSupportedMediaMimeType(const std::string& mime_type) const { + return media_format_map_.find(base::ToLowerASCII(mime_type)) != + media_format_map_.end(); +} + +void MimeUtil::ParseCodecString(const std::string& codecs, + std::vector<std::string>* codecs_out, + bool strip) { + *codecs_out = + base::SplitString(base::TrimString(codecs, "\"", base::TRIM_ALL), ",", + base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); + + // Convert empty or all-whitespace input to 0 results. + if (codecs_out->size() == 1 && (*codecs_out)[0].empty()) + codecs_out->clear(); + + if (!strip) + return; + + // Strip everything past the first '.' + for (std::vector<std::string>::iterator it = codecs_out->begin(); + it != codecs_out->end(); ++it) { + size_t found = it->find_first_of('.'); + if (found != std::string::npos) + it->resize(found); + } +} + +SupportsType MimeUtil::IsSupportedMediaFormat( + const std::string& mime_type, + const std::vector<std::string>& codecs, + bool is_encrypted) const { + const std::string mime_type_lower_case = base::ToLowerASCII(mime_type); + MediaFormatMappings::const_iterator it_media_format_map = + media_format_map_.find(mime_type_lower_case); + if (it_media_format_map == media_format_map_.end()) + return IsNotSupported; + + if (it_media_format_map->second.empty()) { + // We get here if the mimetype does not expect a codecs parameter. + return (codecs.empty() && IsDefaultCodecSupportedLowerCase( + mime_type_lower_case, is_encrypted)) + ? IsSupported + : IsNotSupported; + } + + if (codecs.empty()) { + // We get here if the mimetype expects to get a codecs parameter, + // but didn't get one. If |mime_type_lower_case| does not have a default + // codec the best we can do is say "maybe" because we don't have enough + // information. + Codec default_codec = INVALID_CODEC; + if (!GetDefaultCodecLowerCase(mime_type_lower_case, &default_codec)) + return MayBeSupported; + + return IsCodecSupported(default_codec, mime_type_lower_case, is_encrypted) + ? IsSupported + : IsNotSupported; + } + +#if BUILDFLAG(ENABLE_MSE_MPEG2TS_STREAM_PARSER) + if (mime_type_lower_case == "video/mp2t") { + std::vector<std::string> codecs_to_check; + for (const auto& codec_id : codecs) { + codecs_to_check.push_back(TranslateLegacyAvc1CodecIds(codec_id)); + } + return AreSupportedCodecs(it_media_format_map->second, codecs_to_check, + mime_type_lower_case, is_encrypted); + } +#endif + + return AreSupportedCodecs(it_media_format_map->second, codecs, + mime_type_lower_case, is_encrypted); +} + +void MimeUtil::RemoveProprietaryMediaTypesAndCodecs() { + for (const auto& container : proprietary_media_containers_) + media_format_map_.erase(container); + allow_proprietary_codecs_ = false; +} + +// static +bool MimeUtil::IsCodecSupportedOnPlatform( + Codec codec, + const std::string& mime_type_lower_case, + bool is_encrypted, + const PlatformInfo& platform_info) { + DCHECK_NE(mime_type_lower_case, ""); + + // Encrypted block support is never available without platform decoders. + if (is_encrypted && !platform_info.has_platform_decoders) + return false; + + // NOTE: We do not account for Media Source Extensions (MSE) within these + // checks since it has its own isTypeSupported() which will handle platform + // specific codec rejections. See http://crbug.com/587303. + + switch (codec) { + // ---------------------------------------------------------------------- + // The following codecs are never supported. + // ---------------------------------------------------------------------- + case INVALID_CODEC: + case AC3: + case EAC3: + case THEORA: + return false; + + // ---------------------------------------------------------------------- + // The remaining codecs may be supported depending on platform abilities. + // ---------------------------------------------------------------------- + + case PCM: + case MP3: + case MPEG4_AAC: + case VORBIS: + // These codecs are always supported; via a platform decoder (when used + // with MSE/EME), a software decoder (the unified pipeline), or with + // MediaPlayer. + DCHECK(!is_encrypted || platform_info.has_platform_decoders); + return true; + + case MPEG2_AAC: + // MPEG-2 variants of AAC are not supported on Android unless the unified + // media pipeline can be used and the container is not HLS. These codecs + // will be decoded in software. See https:crbug.com/544268 for details. + if (mime_type_lower_case == "application/x-mpegurl" || + mime_type_lower_case == "application/vnd.apple.mpegurl") { + return false; + } + return !is_encrypted && platform_info.is_unified_media_pipeline_enabled; + + case OPUS: + // If clear, the unified pipeline can always decode Opus in software. + if (!is_encrypted && platform_info.is_unified_media_pipeline_enabled) + return true; + + // Otherwise, platform support is required. + if (!platform_info.supports_opus) + return false; + + // MediaPlayer does not support Opus in ogg containers. + if (base::EndsWith(mime_type_lower_case, "ogg", + base::CompareCase::SENSITIVE)) { + return false; + } + + DCHECK(!is_encrypted || platform_info.has_platform_decoders); + return true; + + case H264: + // The unified pipeline requires platform support for h264. + if (platform_info.is_unified_media_pipeline_enabled) + return platform_info.has_platform_decoders; + + // When MediaPlayer or MediaCodec is used, h264 is always supported. + DCHECK(!is_encrypted || platform_info.has_platform_decoders); + return true; + + case HEVC_MAIN: +#if BUILDFLAG(ENABLE_HEVC_DEMUXING) + if (platform_info.is_unified_media_pipeline_enabled && + !platform_info.has_platform_decoders) { + return false; + } + +#if defined(OS_ANDROID) + // HEVC/H.265 is supported in Lollipop+ (API Level 21), according to + // http://developer.android.com/reference/android/media/MediaFormat.html + return base::android::BuildInfo::GetInstance()->sdk_int() >= 21; +#else + return true; +#endif // defined(OS_ANDROID) +#else + return false; +#endif // BUILDFLAG(ENABLE_HEVC_DEMUXING) + + case VP8: + // If clear, the unified pipeline can always decode VP8 in software. + if (!is_encrypted && platform_info.is_unified_media_pipeline_enabled) + return true; + + if (is_encrypted) + return platform_info.has_platform_vp8_decoder; + + // MediaPlayer can always play VP8. Note: This is incorrect for MSE, but + // MSE does not use this code. http://crbug.com/587303. + return true; + + case VP9: { + // If clear, the unified pipeline can always decode VP9 in software. + if (!is_encrypted && platform_info.is_unified_media_pipeline_enabled) + return true; + + // Otherwise, platform support is required. + return platform_info.has_platform_vp9_decoder; + } + } + + return false; +} + +bool MimeUtil::StringToCodec(const std::string& codec_id, + Codec* codec, + bool* is_ambiguous, + bool is_encrypted) const { + StringToCodecMappings::const_iterator itr = + string_to_codec_map_.find(codec_id); + if (itr != string_to_codec_map_.end()) { + *codec = itr->second.codec; + *is_ambiguous = itr->second.is_ambiguous; + return true; + } + +// If |codec_id| is not in |string_to_codec_map_|, then we assume that it is +// either H.264 or HEVC/H.265 codec ID because currently those are the only +// ones that are not added to the |string_to_codec_map_| and require parsing. + +#if BUILDFLAG(ENABLE_HEVC_DEMUXING) + if (ParseHEVCCodecID(codec_id, codec, is_ambiguous)) { + return true; + } +#endif + + VideoCodecProfile profile = VIDEO_CODEC_PROFILE_UNKNOWN; + uint8_t level_idc = 0; + if (ParseAVCCodecId(codec_id, &profile, &level_idc)) { + *codec = MimeUtil::H264; + switch (profile) { +// HIGH10PROFILE is supported through fallback to the ffmpeg decoder +// which is not available on Android, or if FFMPEG is not used. +#if !defined(MEDIA_DISABLE_FFMPEG) && !defined(OS_ANDROID) + case H264PROFILE_HIGH10PROFILE: + if (is_encrypted) { + // FFmpeg is not generally used for encrypted videos, so we do not + // know whether 10-bit is supported. + *is_ambiguous = true; + break; + } +// Fall through. +#endif + + case H264PROFILE_BASELINE: + case H264PROFILE_MAIN: + case H264PROFILE_HIGH: + *is_ambiguous = !IsValidH264Level(level_idc); + break; + default: + *is_ambiguous = true; + } + return true; + } + + DVLOG(4) << __FUNCTION__ << ": Unrecognized codec id " << codec_id; + return false; +} + +bool MimeUtil::IsCodecSupported(Codec codec, + const std::string& mime_type_lower_case, + bool is_encrypted) const { + DCHECK_NE(codec, INVALID_CODEC); + +#if defined(OS_ANDROID) + if (!IsCodecSupportedOnPlatform(codec, mime_type_lower_case, is_encrypted, + platform_info_)) { + return false; + } +#endif + + return allow_proprietary_codecs_ || !IsCodecProprietary(codec); +} + +bool MimeUtil::IsCodecProprietary(Codec codec) const { + switch (codec) { + case INVALID_CODEC: + case AC3: + case EAC3: + case MP3: + case MPEG2_AAC: + case MPEG4_AAC: + case H264: + case HEVC_MAIN: + return true; + + case PCM: + case VORBIS: + case OPUS: + case VP8: + case VP9: + case THEORA: + return false; + } + + return true; +} + +bool MimeUtil::GetDefaultCodecLowerCase(const std::string& mime_type_lower_case, + Codec* default_codec) const { + if (mime_type_lower_case == "audio/mpeg" || + mime_type_lower_case == "audio/mp3" || + mime_type_lower_case == "audio/x-mp3") { + *default_codec = MimeUtil::MP3; + return true; + } + + if (mime_type_lower_case == "audio/aac") { + *default_codec = MimeUtil::MPEG4_AAC; + return true; + } + + return false; +} + +bool MimeUtil::IsDefaultCodecSupportedLowerCase( + const std::string& mime_type_lower_case, + bool is_encrypted) const { + Codec default_codec = Codec::INVALID_CODEC; + if (!GetDefaultCodecLowerCase(mime_type_lower_case, &default_codec)) + return false; + return IsCodecSupported(default_codec, mime_type_lower_case, is_encrypted); +} + +} // namespace internal +} // namespace media diff --git a/chromium/media/base/mime_util_internal.h b/chromium/media/base/mime_util_internal.h new file mode 100644 index 00000000000..67396cf654e --- /dev/null +++ b/chromium/media/base/mime_util_internal.h @@ -0,0 +1,171 @@ +// Copyright 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 MEDIA_BASE_MIME_UTIL_INTERNAL_H_ +#define MEDIA_BASE_MIME_UTIL_INTERNAL_H_ + +#include <map> +#include <string> +#include <vector> + +#include "base/containers/hash_tables.h" +#include "base/macros.h" +#include "media/base/media_export.h" +#include "media/base/mime_util.h" + +namespace media { +namespace internal { + +// Internal utility class for handling mime types. Should only be invoked by +// tests and the functions within mime_util.cc -- NOT for direct use by others. +class MEDIA_EXPORT MimeUtil { + public: + MimeUtil(); + ~MimeUtil(); + + enum Codec { + INVALID_CODEC, + PCM, + MP3, + AC3, + EAC3, + MPEG2_AAC, + MPEG4_AAC, + VORBIS, + OPUS, + H264, + HEVC_MAIN, + VP8, + VP9, + THEORA, + LAST_CODEC = THEORA + }; + + // Platform configuration structure. Controls which codecs are supported at + // runtime. Also used by tests to simulate platform differences. + struct PlatformInfo { + bool has_platform_decoders = false; + + bool has_platform_vp8_decoder = false; + bool has_platform_vp9_decoder = false; + bool supports_opus = false; + + bool is_unified_media_pipeline_enabled = false; + }; + + // See mime_util.h for more information on these methods. + bool IsSupportedMediaMimeType(const std::string& mime_type) const; + void ParseCodecString(const std::string& codecs, + std::vector<std::string>* codecs_out, + bool strip); + SupportsType IsSupportedMediaFormat(const std::string& mime_type, + const std::vector<std::string>& codecs, + bool is_encrypted) const; + + void RemoveProprietaryMediaTypesAndCodecs(); + + // Checks special platform specific codec restrictions. Returns true if + // |codec| is supported when contained in |mime_type_lower_case|. + // |is_encrypted| means the codec will be used with encrypted blocks. + // |platform_info| describes the availability of various platform features; + // see PlatformInfo for more details. + static bool IsCodecSupportedOnPlatform( + Codec codec, + const std::string& mime_type_lower_case, + bool is_encrypted, + const PlatformInfo& platform_info); + + private: + typedef base::hash_set<int> CodecSet; + typedef std::map<std::string, CodecSet> MediaFormatMappings; + struct CodecEntry { + CodecEntry() : codec(INVALID_CODEC), is_ambiguous(true) {} + CodecEntry(Codec c, bool ambiguous) : codec(c), is_ambiguous(ambiguous) {} + Codec codec; + bool is_ambiguous; + }; + typedef std::map<std::string, CodecEntry> StringToCodecMappings; + + // Initializes the supported media types into hash sets for faster lookup. + void InitializeMimeTypeMaps(); + + // Initializes the supported media formats (|media_format_map_|). + void AddSupportedMediaFormats(); + + // Adds |mime_type| with the specified codecs to |media_format_map_|. + void AddContainerWithCodecs(const std::string& mime_type, + const CodecSet& codecs_list, + bool is_proprietary_mime_type); + + // Returns IsSupported if all codec IDs in |codecs| are unambiguous and are + // supported in |mime_type_lower_case|. MayBeSupported is returned if at least + // one codec ID in |codecs| is ambiguous but all the codecs are supported. + // IsNotSupported is returned if |mime_type_lower_case| is not supported or at + // least one is not supported in |mime_type_lower_case|. |is_encrypted| means + // the codec will be used with encrypted blocks. + SupportsType AreSupportedCodecs(const CodecSet& supported_codecs, + const std::vector<std::string>& codecs, + const std::string& mime_type_lower_case, + bool is_encrypted) const; + + // Converts a codec ID into an Codec enum value and indicates + // whether the conversion was ambiguous. + // Returns true if this method was able to map |codec_id| to a specific + // Codec enum value. |codec| and |is_ambiguous| are only valid if true + // is returned. Otherwise their value is undefined after the call. + // |is_ambiguous| is true if |codec_id| did not have enough information to + // unambiguously determine the proper Codec enum value. If |is_ambiguous| + // is true |codec| contains the best guess for the intended Codec enum value. + // |is_encrypted| means the codec will be used with encrypted blocks. + bool StringToCodec(const std::string& codec_id, + Codec* codec, + bool* is_ambiguous, + bool is_encrypted) const; + + // Returns true if |codec| is supported when contained in + // |mime_type_lower_case|. Note: This method will always return false for + // proprietary codecs if |allow_proprietary_codecs_| is set to false. + // |is_encrypted| means the codec will be used with encrypted blocks. + bool IsCodecSupported(Codec codec, + const std::string& mime_type_lower_case, + bool is_encrypted) const; + + // Returns true if |codec| refers to a proprietary codec. + bool IsCodecProprietary(Codec codec) const; + + // Returns true and sets |*default_codec| if |mime_type| has a default codec + // associated with it. Returns false otherwise and the value of + // |*default_codec| is undefined. + bool GetDefaultCodecLowerCase(const std::string& mime_type_lower_case, + Codec* default_codec) const; + + // Returns true if |mime_type_lower_case| has a default codec associated with + // it and IsCodecSupported() returns true for that particular codec. + // |is_encrypted| means the codec will be used with encrypted blocks. + bool IsDefaultCodecSupportedLowerCase(const std::string& mime_type_lower_case, + bool is_encrypted) const; + +#if defined(OS_ANDROID) + // Indicates the support of various codecs within the platform. + PlatformInfo platform_info_; +#endif + + // A map of mime_types and hash map of the supported codecs for the mime_type. + MediaFormatMappings media_format_map_; + + // List of proprietary containers in |media_format_map_|. + std::vector<std::string> proprietary_media_containers_; + // Whether proprietary codec support should be advertised to callers. + bool allow_proprietary_codecs_; + + // Lookup table for string compare based string -> Codec mappings. + StringToCodecMappings string_to_codec_map_; + + DISALLOW_COPY_AND_ASSIGN(MimeUtil); +}; + +} // namespace internal +} // namespace media + +#endif // MEDIA_BASE_MIME_UTIL_INTERNAL_H_ diff --git a/chromium/media/base/mime_util_unittest.cc b/chromium/media/base/mime_util_unittest.cc index 34a2ea3a33a..4db5aa7c9f3 100644 --- a/chromium/media/base/mime_util_unittest.cc +++ b/chromium/media/base/mime_util_unittest.cc @@ -6,12 +6,116 @@ #include "base/macros.h" #include "base/strings/string_split.h" +#include "base/strings/stringprintf.h" #include "build/build_config.h" #include "media/base/mime_util.h" +#include "media/base/mime_util_internal.h" #include "media/media_features.h" #include "testing/gtest/include/gtest/gtest.h" +#if defined(OS_ANDROID) +#include "base/android/build_info.h" +#endif + namespace media { +namespace internal { + +// MIME type for use with IsCodecSupportedOnPlatform() test; type is ignored in +// all cases except for when paired with the Opus codec. +const char kTestMimeType[] = "foo/foo"; + +// Helper method for creating a multi-value vector of |kTestStates| if +// |test_all_values| is true or if false, a single value vector containing +// |single_value|. +static std::vector<bool> CreateTestVector(bool test_all_values, + bool single_value) { + const bool kTestStates[] = {true, false}; + if (test_all_values) + return std::vector<bool>(kTestStates, kTestStates + arraysize(kTestStates)); + return std::vector<bool>(1, single_value); +} + +// Helper method for running IsCodecSupportedOnPlatform() tests that will +// iterate over all possible field values for a MimeUtil::PlatformInfo struct. +// +// To request a field be varied, set its value to true in the |states_to_vary| +// struct. If false, the only value tested will be the field value from +// |test_states|. +// +// |test_func| should have the signature <void(const MimeUtil::PlatformInfo&, +// MimeUtil::Codec)>. +template <typename TestCallback> +static void RunCodecSupportTest(const MimeUtil::PlatformInfo& states_to_vary, + const MimeUtil::PlatformInfo& test_states, + TestCallback test_func) { +#define MAKE_TEST_VECTOR(name) \ + std::vector<bool> name##_states = \ + CreateTestVector(states_to_vary.name, test_states.name) + + // Stuff states to test into vectors for easy for_each() iteration. + MAKE_TEST_VECTOR(has_platform_decoders); + MAKE_TEST_VECTOR(has_platform_vp8_decoder); + MAKE_TEST_VECTOR(has_platform_vp9_decoder); + MAKE_TEST_VECTOR(supports_opus); + MAKE_TEST_VECTOR(is_unified_media_pipeline_enabled); +#undef MAKE_TEST_VECTOR + + MimeUtil::PlatformInfo info; + +#define RUN_TEST_VECTOR(name) \ + size_t name##_index = 0; \ + for (info.name = name##_states[name##_index]; \ + name##_index < name##_states.size(); ++name##_index) + + RUN_TEST_VECTOR(has_platform_decoders) { + RUN_TEST_VECTOR(has_platform_vp8_decoder) { + RUN_TEST_VECTOR(has_platform_vp9_decoder) { + RUN_TEST_VECTOR(supports_opus) { + RUN_TEST_VECTOR(is_unified_media_pipeline_enabled) { + for (int codec = MimeUtil::INVALID_CODEC; + codec <= MimeUtil::LAST_CODEC; ++codec) { + SCOPED_TRACE(base::StringPrintf( + "has_platform_decoders=%d, has_platform_vp8_decoder=%d, " + "supports_opus=%d, " + "has_platform_vp9_decoder=%d, " + "is_unified_media_pipeline_enabled=%d, " + "codec=%d", + info.has_platform_decoders, info.has_platform_vp8_decoder, + info.supports_opus, info.has_platform_vp9_decoder, + info.is_unified_media_pipeline_enabled, codec)); + test_func(info, static_cast<MimeUtil::Codec>(codec)); + } + } + } + } + } + } +#undef RUN_TEST_VECTOR +} + +// Helper method for generating the |states_to_vary| value used by +// RunPlatformCodecTest(). Marks all fields to be varied. +static MimeUtil::PlatformInfo VaryAllFields() { + MimeUtil::PlatformInfo states_to_vary; + states_to_vary.has_platform_vp8_decoder = true; + states_to_vary.has_platform_vp9_decoder = true; + states_to_vary.supports_opus = true; + states_to_vary.is_unified_media_pipeline_enabled = true; + states_to_vary.has_platform_decoders = true; + return states_to_vary; +} + +static bool HasHevcSupport() { +#if BUILDFLAG(ENABLE_HEVC_DEMUXING) +#if defined(OS_ANDROID) + return base::android::BuildInfo::GetInstance()->sdk_int() >= 21; +#else + return true; +#endif // defined(OS_ANDROID) +#else + return false; +#endif // BUILDFLAG(ENABLE_HEVC_DEMUXING) +} TEST(MimeUtilTest, CommonMediaMimeType) { EXPECT_TRUE(IsSupportedMediaMimeType("audio/webm")); @@ -28,7 +132,7 @@ TEST(MimeUtilTest, CommonMediaMimeType) { EXPECT_TRUE(IsSupportedMediaMimeType("video/ogg")); #endif // OS_ANDROID -#if defined(OS_ANDROID) +#if defined(OS_ANDROID) && defined(USE_PROPRIETARY_CODECS) // HLS is supported on Android API level 14 and higher and Chrome supports // API levels 15 and higher, so these are expected to be supported. bool kHlsSupported = true; @@ -111,4 +215,205 @@ TEST(MimeUtilTest, ParseCodecString) { EXPECT_EQ("mp4a.40.2", codecs_out[1]); } +TEST(IsCodecSupportedOnPlatformTest, + EncryptedCodecsFailWithoutPlatformSupport) { + // Vary all parameters except |has_platform_decoders|. + MimeUtil::PlatformInfo states_to_vary = VaryAllFields(); + states_to_vary.has_platform_decoders = false; + + // Disable platform decoders. + MimeUtil::PlatformInfo test_states; + test_states.has_platform_decoders = false; + + // Every codec should fail since platform support is missing and we've + // requested encrypted codecs. + RunCodecSupportTest( + states_to_vary, test_states, + [](const MimeUtil::PlatformInfo& info, MimeUtil::Codec codec) { + EXPECT_FALSE(MimeUtil::IsCodecSupportedOnPlatform(codec, kTestMimeType, + true, info)); + }); +} + +TEST(IsCodecSupportedOnPlatformTest, EncryptedCodecBehavior) { + // Vary all parameters except |has_platform_decoders|. + MimeUtil::PlatformInfo states_to_vary = VaryAllFields(); + states_to_vary.has_platform_decoders = false; + + // Enable platform decoders. + MimeUtil::PlatformInfo test_states; + test_states.has_platform_decoders = true; + + RunCodecSupportTest( + states_to_vary, test_states, + [](const MimeUtil::PlatformInfo& info, MimeUtil::Codec codec) { + const bool result = MimeUtil::IsCodecSupportedOnPlatform( + codec, kTestMimeType, true, info); + switch (codec) { + // These codecs are never supported by the Android platform. + case MimeUtil::INVALID_CODEC: + case MimeUtil::AC3: + case MimeUtil::EAC3: + case MimeUtil::MPEG2_AAC: + case MimeUtil::THEORA: + EXPECT_FALSE(result); + break; + + // These codecs are always available with platform decoder support. + case MimeUtil::PCM: + case MimeUtil::MP3: + case MimeUtil::MPEG4_AAC: + case MimeUtil::VORBIS: + case MimeUtil::H264: + EXPECT_TRUE(result); + break; + + // The remaining codecs are not available on all platforms even when + // a platform decoder is available. + case MimeUtil::OPUS: + EXPECT_EQ(info.supports_opus, result); + break; + + case MimeUtil::VP8: + EXPECT_EQ(info.has_platform_vp8_decoder, result); + break; + + case MimeUtil::VP9: + EXPECT_EQ(info.has_platform_vp9_decoder, result); + break; + + case MimeUtil::HEVC_MAIN: + EXPECT_EQ(HasHevcSupport(), result); + break; + } + }); +} + +TEST(IsCodecSupportedOnPlatformTest, ClearCodecBehaviorWithAndroidPipeline) { + // Vary all parameters except |is_unified_media_pipeline_enabled|. + MimeUtil::PlatformInfo states_to_vary = VaryAllFields(); + states_to_vary.is_unified_media_pipeline_enabled = false; + + // Disable the unified pipeline. + MimeUtil::PlatformInfo test_states; + test_states.is_unified_media_pipeline_enabled = false; + + RunCodecSupportTest( + states_to_vary, test_states, + [](const MimeUtil::PlatformInfo& info, MimeUtil::Codec codec) { + const bool result = MimeUtil::IsCodecSupportedOnPlatform( + codec, kTestMimeType, false, info); + switch (codec) { + // These codecs are never supported by the Android platform. + case MimeUtil::INVALID_CODEC: + case MimeUtil::AC3: + case MimeUtil::EAC3: + case MimeUtil::MPEG2_AAC: + case MimeUtil::THEORA: + EXPECT_FALSE(result); + break; + + // These codecs are always available via MediaPlayer. + case MimeUtil::PCM: + case MimeUtil::MP3: + case MimeUtil::MPEG4_AAC: + case MimeUtil::VORBIS: + case MimeUtil::H264: + case MimeUtil::VP8: + EXPECT_TRUE(result); + break; + + // The remaining codecs depend on the platform version. + case MimeUtil::OPUS: + EXPECT_EQ(info.supports_opus, result); + break; + + case MimeUtil::VP9: + EXPECT_EQ(info.has_platform_vp9_decoder, result); + break; + + case MimeUtil::HEVC_MAIN: + EXPECT_EQ(HasHevcSupport(), result); + break; + } + }); +} + +TEST(IsCodecSupportedOnPlatformTest, ClearCodecBehaviorWithUnifiedPipeline) { + // Vary all parameters except |is_unified_media_pipeline_enabled|. + MimeUtil::PlatformInfo states_to_vary = VaryAllFields(); + states_to_vary.is_unified_media_pipeline_enabled = false; + + // Enable the unified pipeline. + MimeUtil::PlatformInfo test_states; + test_states.is_unified_media_pipeline_enabled = true; + + RunCodecSupportTest( + states_to_vary, test_states, + [](const MimeUtil::PlatformInfo& info, MimeUtil::Codec codec) { + const bool result = MimeUtil::IsCodecSupportedOnPlatform( + codec, kTestMimeType, false, info); + switch (codec) { + // These codecs are never supported by the Android platform. + case MimeUtil::INVALID_CODEC: + case MimeUtil::AC3: + case MimeUtil::EAC3: + case MimeUtil::THEORA: + EXPECT_FALSE(result); + break; + + // These codecs are always supported with the unified pipeline. + case MimeUtil::PCM: + case MimeUtil::MPEG2_AAC: + case MimeUtil::MP3: + case MimeUtil::MPEG4_AAC: + case MimeUtil::OPUS: + case MimeUtil::VORBIS: + case MimeUtil::VP8: + case MimeUtil::VP9: + EXPECT_TRUE(result); + break; + + // These codecs are only supported if platform decoders are supported. + case MimeUtil::H264: + EXPECT_EQ(info.has_platform_decoders, result); + break; + + case MimeUtil::HEVC_MAIN: + EXPECT_EQ(HasHevcSupport() && info.has_platform_decoders, result); + break; + } + }); +} + +TEST(IsCodecSupportedOnPlatformTest, OpusOggSupport) { + // Vary all parameters; thus use default initial state. + MimeUtil::PlatformInfo states_to_vary = VaryAllFields(); + MimeUtil::PlatformInfo test_states; + + RunCodecSupportTest( + states_to_vary, test_states, + [](const MimeUtil::PlatformInfo& info, MimeUtil::Codec codec) { + EXPECT_EQ(info.is_unified_media_pipeline_enabled, + MimeUtil::IsCodecSupportedOnPlatform( + MimeUtil::OPUS, "audio/ogg", false, info)); + }); +} + +TEST(IsCodecSupportedOnPlatformTest, HLSDoesNotSupportMPEG2AAC) { + // Vary all parameters; thus use default initial state. + MimeUtil::PlatformInfo states_to_vary = VaryAllFields(); + MimeUtil::PlatformInfo test_states; + + RunCodecSupportTest( + states_to_vary, test_states, + [](const MimeUtil::PlatformInfo& info, MimeUtil::Codec codec) { + EXPECT_FALSE(MimeUtil::IsCodecSupportedOnPlatform( + MimeUtil::MPEG2_AAC, "application/x-mpegurl", false, info)); + EXPECT_FALSE(MimeUtil::IsCodecSupportedOnPlatform( + MimeUtil::MPEG2_AAC, "application/vnd.apple.mpegurl", false, info)); + }); +} + +} // namespace internal } // namespace media diff --git a/chromium/media/base/mock_audio_renderer_sink.cc b/chromium/media/base/mock_audio_renderer_sink.cc index 79653b2f823..5785ee42254 100644 --- a/chromium/media/base/mock_audio_renderer_sink.cc +++ b/chromium/media/base/mock_audio_renderer_sink.cc @@ -3,24 +3,49 @@ // found in the LICENSE file. #include "media/base/mock_audio_renderer_sink.h" -#include "media/base/fake_output_device.h" namespace media { MockAudioRendererSink::MockAudioRendererSink() : MockAudioRendererSink(OUTPUT_DEVICE_STATUS_OK) {} MockAudioRendererSink::MockAudioRendererSink(OutputDeviceStatus device_status) - : output_device_(new FakeOutputDevice(device_status)) {} + : MockAudioRendererSink(std::string(), device_status) {} + +MockAudioRendererSink::MockAudioRendererSink(const std::string& device_id, + OutputDeviceStatus device_status) + : MockAudioRendererSink( + device_id, + device_status, + media::AudioParameters(media::AudioParameters::AUDIO_FAKE, + media::CHANNEL_LAYOUT_STEREO, + media::AudioParameters::kTelephoneSampleRate, + 16, + 1)) {} + +MockAudioRendererSink::MockAudioRendererSink( + const std::string& device_id, + OutputDeviceStatus device_status, + const AudioParameters& device_output_params) + : output_device_info_(device_id, device_status, device_output_params) {} MockAudioRendererSink::~MockAudioRendererSink() {} +void MockAudioRendererSink::SwitchOutputDevice( + const std::string& device_id, + const url::Origin& security_origin, + const OutputDeviceStatusCB& callback) { + // NB: output device won't be changed, since it's not required by any tests + // now. + callback.Run(output_device_info_.device_status()); +} + void MockAudioRendererSink::Initialize(const AudioParameters& params, RenderCallback* renderer) { callback_ = renderer; } -OutputDevice* MockAudioRendererSink::GetOutputDevice() { - return output_device_.get(); +OutputDeviceInfo MockAudioRendererSink::GetOutputDeviceInfo() { + return output_device_info_; } } // namespace media diff --git a/chromium/media/base/mock_audio_renderer_sink.h b/chromium/media/base/mock_audio_renderer_sink.h index 2194bc99d92..fa42a0ba46c 100644 --- a/chromium/media/base/mock_audio_renderer_sink.h +++ b/chromium/media/base/mock_audio_renderer_sink.h @@ -16,18 +16,27 @@ namespace media { class FakeOutputDevice; -class MockAudioRendererSink : public RestartableAudioRendererSink { +class MockAudioRendererSink : public SwitchableAudioRendererSink { public: MockAudioRendererSink(); explicit MockAudioRendererSink(OutputDeviceStatus device_status); + MockAudioRendererSink(const std::string& device_id, + OutputDeviceStatus device_status); + MockAudioRendererSink(const std::string& device_id, + OutputDeviceStatus device_status, + const AudioParameters& device_output_params); MOCK_METHOD0(Start, void()); MOCK_METHOD0(Stop, void()); MOCK_METHOD0(Pause, void()); MOCK_METHOD0(Play, void()); MOCK_METHOD1(SetVolume, bool(double volume)); - OutputDevice* GetOutputDevice(); + OutputDeviceInfo GetOutputDeviceInfo(); + + void SwitchOutputDevice(const std::string& device_id, + const url::Origin& security_origin, + const OutputDeviceStatusCB& callback) override; void Initialize(const AudioParameters& params, RenderCallback* renderer) override; AudioRendererSink::RenderCallback* callback() { return callback_; } @@ -37,7 +46,7 @@ class MockAudioRendererSink : public RestartableAudioRendererSink { private: RenderCallback* callback_; - scoped_ptr<FakeOutputDevice> output_device_; + OutputDeviceInfo output_device_info_; DISALLOW_COPY_AND_ASSIGN(MockAudioRendererSink); }; diff --git a/chromium/media/base/mock_filters.cc b/chromium/media/base/mock_filters.cc index 98c23be9bf0..1c783bd55f0 100644 --- a/chromium/media/base/mock_filters.cc +++ b/chromium/media/base/mock_filters.cc @@ -14,6 +14,31 @@ using ::testing::Return; namespace media { +MockPipeline::MockPipeline() {} + +MockPipeline::~MockPipeline() {} + +void MockPipeline::Start(Demuxer* demuxer, + scoped_ptr<Renderer> renderer, + const base::Closure& ended_cb, + const PipelineStatusCB& error_cb, + const PipelineStatusCB& seek_cb, + const PipelineMetadataCB& metadata_cb, + const BufferingStateCB& buffering_state_cb, + const base::Closure& duration_change_cb, + const AddTextTrackCB& add_text_track_cb, + const base::Closure& waiting_for_decryption_key_cb) { + Start(demuxer, &renderer, ended_cb, error_cb, seek_cb, metadata_cb, + buffering_state_cb, duration_change_cb, add_text_track_cb, + waiting_for_decryption_key_cb); +} + +void MockPipeline::Resume(scoped_ptr<Renderer> renderer, + base::TimeDelta timestamp, + const PipelineStatusCB& seek_cb) { + Resume(&renderer, timestamp, seek_cb); +} + MockDemuxer::MockDemuxer() {} MockDemuxer::~MockDemuxer() {} @@ -71,7 +96,7 @@ std::string MockVideoDecoder::GetDisplayName() const { } MockVideoDecoder::MockVideoDecoder() { - EXPECT_CALL(*this, HasAlpha()).WillRepeatedly(Return(false)); + ON_CALL(*this, CanReadWithoutStalling()).WillByDefault(Return(true)); } std::string MockAudioDecoder::GetDisplayName() const { diff --git a/chromium/media/base/mock_filters.h b/chromium/media/base/mock_filters.h index 39c8f3b7e33..f60c9cdf8f4 100644 --- a/chromium/media/base/mock_filters.h +++ b/chromium/media/base/mock_filters.h @@ -18,6 +18,7 @@ #include "media/base/decoder_buffer.h" #include "media/base/decryptor.h" #include "media/base/demuxer.h" +#include "media/base/pipeline.h" #include "media/base/pipeline_status.h" #include "media/base/renderer.h" #include "media/base/text_track.h" @@ -30,6 +31,72 @@ namespace media { +class MockPipeline : public Pipeline { + public: + MockPipeline(); + virtual ~MockPipeline(); + + // Note: Start() and Resume() declarations are not actually overrides; they + // take scoped_ptr* instead of scoped_ptr so that they can be mock methods. + // Private stubs for Start() and Resume() implement the actual Pipeline + // interface by forwarding to these mock methods. + MOCK_METHOD10(Start, + void(Demuxer*, + scoped_ptr<Renderer>*, + const base::Closure&, + const PipelineStatusCB&, + const PipelineStatusCB&, + const PipelineMetadataCB&, + const BufferingStateCB&, + const base::Closure&, + const AddTextTrackCB&, + const base::Closure&)); + MOCK_METHOD1(Stop, void(const base::Closure&)); + MOCK_METHOD2(Seek, void(base::TimeDelta, const PipelineStatusCB&)); + MOCK_METHOD1(Suspend, void(const PipelineStatusCB&)); + MOCK_METHOD3(Resume, + void(scoped_ptr<Renderer>*, + base::TimeDelta, + const PipelineStatusCB&)); + + // TODO(sandersd): This should automatically return true between Start() and + // Stop(). (Or better, remove it from the interface entirely.) + MOCK_CONST_METHOD0(IsRunning, bool()); + + // TODO(sandersd): These should be regular getters/setters. + MOCK_CONST_METHOD0(GetPlaybackRate, double()); + MOCK_METHOD1(SetPlaybackRate, void(double)); + MOCK_CONST_METHOD0(GetVolume, float()); + MOCK_METHOD1(SetVolume, void(float)); + + // TODO(sandersd): These should probably have setters too. + MOCK_CONST_METHOD0(GetMediaTime, base::TimeDelta()); + MOCK_CONST_METHOD0(GetBufferedTimeRanges, Ranges<base::TimeDelta>()); + MOCK_CONST_METHOD0(GetMediaDuration, base::TimeDelta()); + MOCK_METHOD0(DidLoadingProgress, bool()); + MOCK_CONST_METHOD0(GetStatistics, PipelineStatistics()); + + MOCK_METHOD2(SetCdm, void(CdmContext*, const CdmAttachedCB&)); + + private: + // Forwarding stubs (see comment above). + void Start(Demuxer* demuxer, + scoped_ptr<Renderer> renderer, + const base::Closure& ended_cb, + const PipelineStatusCB& error_cb, + const PipelineStatusCB& seek_cb, + const PipelineMetadataCB& metadata_cb, + const BufferingStateCB& buffering_state_cb, + const base::Closure& duration_change_cb, + const AddTextTrackCB& add_text_track_cb, + const base::Closure& waiting_for_decryption_key_cb) override; + void Resume(scoped_ptr<Renderer> renderer, + base::TimeDelta timestamp, + const PipelineStatusCB& seek_cb) override; + + DISALLOW_COPY_AND_ASSIGN(MockPipeline); +}; + class MockDemuxer : public Demuxer { public: MockDemuxer(); @@ -39,10 +106,10 @@ class MockDemuxer : public Demuxer { virtual std::string GetDisplayName() const; MOCK_METHOD3(Initialize, void(DemuxerHost* host, const PipelineStatusCB& cb, bool)); - MOCK_METHOD1(SetPlaybackRate, void(double playback_rate)); + MOCK_METHOD1(StartWaitingForSeek, void(base::TimeDelta)); + MOCK_METHOD1(CancelPendingSeek, void(base::TimeDelta)); MOCK_METHOD2(Seek, void(base::TimeDelta time, const PipelineStatusCB& cb)); MOCK_METHOD0(Stop, void()); - MOCK_METHOD0(OnAudioRendererDisabled, void()); MOCK_METHOD1(GetStream, DemuxerStream*(DemuxerStream::Type)); MOCK_CONST_METHOD0(GetStartTime, base::TimeDelta()); MOCK_CONST_METHOD0(GetTimelineOffset, base::Time()); @@ -91,13 +158,14 @@ class MockVideoDecoder : public VideoDecoder { MOCK_METHOD5(Initialize, void(const VideoDecoderConfig& config, bool low_delay, - const SetCdmReadyCB& set_cdm_ready_cb, + CdmContext* cdm_context, const InitCB& init_cb, const OutputCB& output_cb)); MOCK_METHOD2(Decode, void(const scoped_refptr<DecoderBuffer>& buffer, const DecodeCB&)); MOCK_METHOD1(Reset, void(const base::Closure&)); MOCK_CONST_METHOD0(HasAlpha, bool()); + MOCK_CONST_METHOD0(CanReadWithoutStalling, bool()); private: DISALLOW_COPY_AND_ASSIGN(MockVideoDecoder); @@ -112,7 +180,7 @@ class MockAudioDecoder : public AudioDecoder { virtual std::string GetDisplayName() const; MOCK_METHOD4(Initialize, void(const AudioDecoderConfig& config, - const SetCdmReadyCB& set_cdm_ready_cb, + CdmContext* cdm_context, const InitCB& init_cb, const OutputCB& output_cb)); MOCK_METHOD2(Decode, @@ -133,7 +201,7 @@ class MockVideoRenderer : public VideoRenderer { MOCK_METHOD9(Initialize, void(DemuxerStream* stream, const PipelineStatusCB& init_cb, - const SetCdmReadyCB& set_cdm_ready_cb, + CdmContext* cdm_context, const StatisticsCB& statistics_cb, const BufferingStateCB& buffering_state_cb, const base::Closure& ended_cb, @@ -157,7 +225,7 @@ class MockAudioRenderer : public AudioRenderer { MOCK_METHOD8(Initialize, void(DemuxerStream* stream, const PipelineStatusCB& init_cb, - const SetCdmReadyCB& set_cdm_ready_cb, + CdmContext* cdm_context, const StatisticsCB& statistics_cb, const BufferingStateCB& buffering_state_cb, const base::Closure& ended_cb, diff --git a/chromium/media/base/output_device.h b/chromium/media/base/output_device.h deleted file mode 100644 index 76f88c78ed9..00000000000 --- a/chromium/media/base/output_device.h +++ /dev/null @@ -1,67 +0,0 @@ -// 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 MEDIA_BASE_OUTPUT_DEVICE_H_ -#define MEDIA_BASE_OUTPUT_DEVICE_H_ - -#include <string> - -#include "base/callback.h" -#include "media/audio/audio_parameters.h" -#include "media/base/media_export.h" -#include "url/origin.h" - -namespace media { - -// Result of an audio output device switch operation -enum OutputDeviceStatus { - OUTPUT_DEVICE_STATUS_OK = 0, - OUTPUT_DEVICE_STATUS_ERROR_NOT_FOUND, - OUTPUT_DEVICE_STATUS_ERROR_NOT_AUTHORIZED, - OUTPUT_DEVICE_STATUS_ERROR_INTERNAL, - OUTPUT_DEVICE_STATUS_LAST = OUTPUT_DEVICE_STATUS_ERROR_INTERNAL, -}; - -typedef base::Callback<void(OutputDeviceStatus)> SwitchOutputDeviceCB; - -// OutputDevice is an interface that allows performing operations related -// audio output devices. - -class OutputDevice { - public: - // Attempts to switch the audio output device. - // Once the attempt is finished, |callback| is invoked with the - // result of the operation passed as a parameter. The result is a value from - // the media::SwitchOutputDeviceResult enum. - // There is no guarantee about the thread where |callback| will - // be invoked, so users are advised to use media::BindToCurrentLoop() to - // ensure that |callback| runs on the correct thread. - // Note also that copy constructors and destructors for arguments bound to - // |callback| may run on arbitrary threads as |callback| is moved across - // threads. It is advisable to bind arguments such that they are released by - // |callback| when it runs in order to avoid surprises. - virtual void SwitchOutputDevice(const std::string& device_id, - const url::Origin& security_origin, - const SwitchOutputDeviceCB& callback) = 0; - - // Returns the device's audio output parameters. - // The return value is undefined if the device status (as returned by - // GetDeviceStatus()) is different from OUTPUT_DEVICE_STATUS_OK. - // If the parameters are not available, this method may block until they - // become available. - // This method must never be called on the IO thread. - virtual AudioParameters GetOutputParameters() = 0; - - // Returns the status of output device. - // If the status is not available, this method may block until it becomes - // available. Must never be called on the IO thread. - virtual OutputDeviceStatus GetDeviceStatus() = 0; - - protected: - virtual ~OutputDevice() {} -}; - -} // namespace media - -#endif // MEDIA_BASE_OUTPUT_DEVICE_H_ diff --git a/chromium/media/base/output_device_info.cc b/chromium/media/base/output_device_info.cc new file mode 100644 index 00000000000..d1ffa4346a7 --- /dev/null +++ b/chromium/media/base/output_device_info.cc @@ -0,0 +1,40 @@ +// Copyright 2016 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/base/output_device_info.h" + +namespace media { + +// Output device information returned by GetOutputDeviceInfo() methods of +// various interfaces. +OutputDeviceInfo::OutputDeviceInfo() + : OutputDeviceInfo(OUTPUT_DEVICE_STATUS_ERROR_INTERNAL) {} + +OutputDeviceInfo::OutputDeviceInfo(OutputDeviceStatus device_status) + : OutputDeviceInfo(std::string(), + device_status, + AudioParameters::UnavailableDeviceParams()) {} + +OutputDeviceInfo::OutputDeviceInfo(const std::string& device_id, + OutputDeviceStatus device_status, + const AudioParameters& output_params) + : device_id_(device_id), + device_status_(device_status), + output_params_(output_params) {} + +OutputDeviceInfo::OutputDeviceInfo(const OutputDeviceInfo&) = default; + +OutputDeviceInfo& OutputDeviceInfo::operator=(const OutputDeviceInfo&) = + default; + +OutputDeviceInfo::~OutputDeviceInfo() {} + +std::string OutputDeviceInfo::AsHumanReadableString() const { + std::ostringstream s; + s << "device_id: " << device_id() << " device_status: " << device_status() + << " output_params: [ " << output_params().AsHumanReadableString() << " ]"; + return s.str(); +} + +} // namespace media diff --git a/chromium/media/base/output_device_info.h b/chromium/media/base/output_device_info.h new file mode 100644 index 00000000000..987ae6728fb --- /dev/null +++ b/chromium/media/base/output_device_info.h @@ -0,0 +1,68 @@ +// Copyright 2016 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_BASE_OUTPUT_DEVICE_INFO_H_ +#define MEDIA_BASE_OUTPUT_DEVICE_INFO_H_ + +#include <string> + +#include "base/callback.h" +#include "media/audio/audio_parameters.h" +#include "media/base/media_export.h" + +namespace media { + +// Result of an audio output device switch operation +enum OutputDeviceStatus { + OUTPUT_DEVICE_STATUS_OK = 0, + OUTPUT_DEVICE_STATUS_ERROR_NOT_FOUND, + OUTPUT_DEVICE_STATUS_ERROR_NOT_AUTHORIZED, + OUTPUT_DEVICE_STATUS_ERROR_INTERNAL, + OUTPUT_DEVICE_STATUS_LAST = OUTPUT_DEVICE_STATUS_ERROR_INTERNAL, +}; + +using OutputDeviceStatusCB = base::Callback<void(OutputDeviceStatus)>; + +class MEDIA_EXPORT OutputDeviceInfo { + public: + // Use this constructor to initialize with "no info available" values. + OutputDeviceInfo(); + + // Use this constructor to indicate a specific error status of the device. + explicit OutputDeviceInfo(OutputDeviceStatus device_status); + + OutputDeviceInfo(const std::string& device_id, + OutputDeviceStatus device_status, + const AudioParameters& output_params); + + OutputDeviceInfo(const OutputDeviceInfo& other); + + OutputDeviceInfo& operator=(const OutputDeviceInfo&); + + ~OutputDeviceInfo(); + + // Returns the device ID. + const std::string& device_id() const { return device_id_; } + + // Returns the status of output device. + OutputDeviceStatus device_status() const { return device_status_; } + + // Returns the device's audio output parameters. + // The return value is undefined if the device status (as returned by + // device_status()) is different from OUTPUT_DEVICE_STATUS_OK. + const AudioParameters& output_params() const { return output_params_; }; + + // Returns a human-readable string describing |*this|. For debugging & test + // output only. + std::string AsHumanReadableString() const; + + private: + std::string device_id_; + OutputDeviceStatus device_status_; + AudioParameters output_params_; +}; + +} // namespace media + +#endif // MEDIA_BASE_OUTPUT_DEVICE_INFO_H_ diff --git a/chromium/media/base/pipeline.h b/chromium/media/base/pipeline.h index 5eae2e09e1f..db326c29283 100644 --- a/chromium/media/base/pipeline.h +++ b/chromium/media/base/pipeline.h @@ -5,36 +5,21 @@ #ifndef MEDIA_BASE_PIPELINE_H_ #define MEDIA_BASE_PIPELINE_H_ -#include "base/gtest_prod_util.h" -#include "base/macros.h" #include "base/memory/ref_counted.h" -#include "base/memory/weak_ptr.h" -#include "base/synchronization/lock.h" -#include "base/threading/thread_checker.h" -#include "base/time/default_tick_clock.h" +#include "base/time/time.h" #include "media/base/buffering_state.h" #include "media/base/cdm_context.h" -#include "media/base/demuxer.h" #include "media/base/media_export.h" #include "media/base/pipeline_status.h" #include "media/base/ranges.h" -#include "media/base/serial_runner.h" #include "media/base/text_track.h" #include "media/base/video_rotation.h" #include "ui/gfx/geometry/size.h" -namespace base { -class SingleThreadTaskRunner; -class TimeDelta; -} - namespace media { -class MediaLog; +class Demuxer; class Renderer; -class TextRenderer; -class TextTrackConfig; -class TimeDeltaInterpolator; class VideoFrame; // Metadata describing a pipeline once it has been initialized. @@ -51,56 +36,11 @@ struct PipelineMetadata { typedef base::Callback<void(PipelineMetadata)> PipelineMetadataCB; -// Pipeline runs the media pipeline. Filters are created and called on the -// task runner injected into this object. Pipeline works like a state -// machine to perform asynchronous initialization, pausing, seeking and playing. -// -// Here's a state diagram that describes the lifetime of this object. -// -// [ *Created ] [ Any State ] -// | Start() | Stop() / SetError() -// V V -// [ InitXXX (for each filter) ] [ Stopping ] -// | | -// V V -// [ Playing ] <---------. [ Stopped ] -// | | Seek() | -// | V | -// | [ Seeking ] ----' -// | ^ -// | Suspend() | -// V | -// [ Suspending ] | -// | | -// V | -// [ Suspended ] | -// | Resume() | -// V | -// [ Resuming ] ---------' -// -// Initialization is a series of state transitions from "Created" through each -// filter initialization state. When all filter initialization states have -// completed, we simulate a Seek() to the beginning of the media to give filters -// a chance to preroll. From then on the normal Seek() transitions are carried -// out and we start playing the media. -// -// If any error ever happens, this object will transition to the "Error" state -// from any state. If Stop() is ever called, this object will transition to -// "Stopped" state. -// -// TODO(sandersd): It should be possible to pass through Suspended when going -// from InitDemuxer to InitRenderer, thereby eliminating the Resuming state. -// Some annoying differences between the two paths need to be removed first. -class MEDIA_EXPORT Pipeline : public DemuxerHost { +class MEDIA_EXPORT Pipeline { public: // Used to paint VideoFrame. typedef base::Callback<void(const scoped_refptr<VideoFrame>&)> PaintCB; - // Constructs a media pipeline that will execute on |task_runner|. - Pipeline(const scoped_refptr<base::SingleThreadTaskRunner>& task_runner, - MediaLog* media_log); - ~Pipeline() override; - // Build a pipeline to using the given |demuxer| and |renderer| to construct // a filter chain, executing |seek_cb| when the initial seek has completed. // @@ -120,16 +60,16 @@ class MEDIA_EXPORT Pipeline : public DemuxerHost { // |waiting_for_decryption_key_cb| will be executed whenever the key needed // to decrypt the stream is not available. // It is an error to call this method after the pipeline has already started. - void Start(Demuxer* demuxer, - scoped_ptr<Renderer> renderer, - const base::Closure& ended_cb, - const PipelineStatusCB& error_cb, - const PipelineStatusCB& seek_cb, - const PipelineMetadataCB& metadata_cb, - const BufferingStateCB& buffering_state_cb, - const base::Closure& duration_change_cb, - const AddTextTrackCB& add_text_track_cb, - const base::Closure& waiting_for_decryption_key_cb); + virtual void Start(Demuxer* demuxer, + scoped_ptr<Renderer> renderer, + const base::Closure& ended_cb, + const PipelineStatusCB& error_cb, + const PipelineStatusCB& seek_cb, + const PipelineMetadataCB& metadata_cb, + const BufferingStateCB& buffering_state_cb, + const base::Closure& duration_change_cb, + const AddTextTrackCB& add_text_track_cb, + const base::Closure& waiting_for_decryption_key_cb) = 0; // Asynchronously stops the pipeline, executing |stop_cb| when the pipeline // teardown has completed. @@ -138,7 +78,7 @@ class MEDIA_EXPORT Pipeline : public DemuxerHost { // call Stop() at any point during the lifetime of the pipeline. // // It is safe to delete the pipeline during the execution of |stop_cb|. - void Stop(const base::Closure& stop_cb); + virtual void Stop(const base::Closure& stop_cb) = 0; // Attempt to seek to the position specified by time. |seek_cb| will be // executed when the all filters in the pipeline have processed the seek. @@ -147,291 +87,74 @@ class MEDIA_EXPORT Pipeline : public DemuxerHost { // succeeded. // // It is an error to call this method if the pipeline has not started or - // is suspended. - void Seek(base::TimeDelta time, const PipelineStatusCB& seek_cb); + // has been suspended. + virtual void Seek(base::TimeDelta time, const PipelineStatusCB& seek_cb) = 0; + + // Suspends the pipeline, discarding the current renderer. + // + // While suspended, GetMediaTime() returns the presentation timestamp of the + // last rendered frame. + // + // It is an error to call this method if the pipeline has not started or is + // seeking. + virtual void Suspend(const PipelineStatusCB& suspend_cb) = 0; + + // Resume the pipeline with a new renderer, and initialize it with a seek. + // + // It is an error to call this method if the pipeline has not finished + // suspending. + virtual void Resume(scoped_ptr<Renderer> renderer, + base::TimeDelta timestamp, + const PipelineStatusCB& seek_cb) = 0; // Returns true if the pipeline has been started via Start(). If IsRunning() // returns true, it is expected that Stop() will be called before destroying // the pipeline. - bool IsRunning() const; + virtual bool IsRunning() const = 0; // Gets the current playback rate of the pipeline. When the pipeline is // started, the playback rate will be 0.0. A rate of 1.0 indicates // that the pipeline is rendering the media at the standard rate. Valid // values for playback rate are >= 0.0. - double GetPlaybackRate() const; + virtual double GetPlaybackRate() const = 0; // Attempt to adjust the playback rate. Setting a playback rate of 0.0 pauses // all rendering of the media. A rate of 1.0 indicates a normal playback // rate. Values for the playback rate must be greater than or equal to 0.0. // // TODO(scherkus): What about maximum rate? Does HTML5 specify a max? - void SetPlaybackRate(double playback_rate); - - // Suspend the pipeline, discarding the current renderer. - // - // While suspended, GetMediaTime() returns the presentation timestamp of the - // last rendered frame. - // - // It is an error to call this method if the pipeline has not started or is - // seeking. - void Suspend(const PipelineStatusCB& suspend_cb); - - // Resume the pipeline with a new renderer, and initialize it with a seek. - void Resume(scoped_ptr<Renderer> renderer, - base::TimeDelta timestamp, - const PipelineStatusCB& seek_cb); + virtual void SetPlaybackRate(double playback_rate) = 0; // Gets the current volume setting being used by the audio renderer. When // the pipeline is started, this value will be 1.0f. Valid values range // from 0.0f to 1.0f. - float GetVolume() const; + virtual float GetVolume() const = 0; // Attempt to set the volume of the audio renderer. Valid values for volume // range from 0.0f (muted) to 1.0f (full volume). This value affects all // channels proportionately for multi-channel audio streams. - void SetVolume(float volume); + virtual void SetVolume(float volume) = 0; // Returns the current media playback time, which progresses from 0 until // GetMediaDuration(). - base::TimeDelta GetMediaTime() const; + virtual base::TimeDelta GetMediaTime() const = 0; // Get approximate time ranges of buffered media. - Ranges<base::TimeDelta> GetBufferedTimeRanges() const; + virtual Ranges<base::TimeDelta> GetBufferedTimeRanges() const = 0; // Get the duration of the media in microseconds. If the duration has not // been determined yet, then returns 0. - base::TimeDelta GetMediaDuration() const; + virtual base::TimeDelta GetMediaDuration() const = 0; // Return true if loading progress has been made since the last time this // method was called. - bool DidLoadingProgress(); + virtual bool DidLoadingProgress() = 0; // Gets the current pipeline statistics. - PipelineStatistics GetStatistics() const; - - void SetCdm(CdmContext* cdm_context, const CdmAttachedCB& cdm_attached_cb); - - void SetErrorForTesting(PipelineStatus status); - bool HasWeakPtrsForTesting() const; - - private: - FRIEND_TEST_ALL_PREFIXES(PipelineTest, GetBufferedTimeRanges); - FRIEND_TEST_ALL_PREFIXES(PipelineTest, EndedCallback); - FRIEND_TEST_ALL_PREFIXES(PipelineTest, AudioStreamShorterThanVideo); - friend class MediaLog; - - // Pipeline states, as described above. - enum State { - kCreated, - kInitDemuxer, - kInitRenderer, - kSeeking, - kPlaying, - kStopping, - kStopped, - kSuspending, - kSuspended, - kResuming, - }; - - // Updates |state_|. All state transitions should use this call. - void SetState(State next_state); - - static const char* GetStateString(State state); - State GetNextState() const; - - // Helper method that runs & resets |seek_cb_| and resets |seek_timestamp_| - // and |seek_pending_|. - void FinishSeek(); - - // DemuxerHost implementaion. - void OnBufferedTimeRangesChanged( - const Ranges<base::TimeDelta>& ranges) override; - void SetDuration(base::TimeDelta duration) override; - void OnDemuxerError(PipelineStatus error) override; - void AddTextStream(DemuxerStream* text_stream, - const TextTrackConfig& config) override; - void RemoveTextStream(DemuxerStream* text_stream) override; - - // Callback executed when a rendering error happened, initiating the teardown - // sequence. - void OnError(PipelineStatus error); - - // Callback executed by filters to update statistics. - void OnUpdateStatistics(const PipelineStatistics& stats_delta); - - // The following "task" methods correspond to the public methods, but these - // methods are run as the result of posting a task to the Pipeline's - // task runner. - void StartTask(); - - // Suspends the pipeline, discarding the current renderer. - void SuspendTask(const PipelineStatusCB& suspend_cb); - - // Resumes the pipeline with a new renderer, and initializes it with a seek. - void ResumeTask(scoped_ptr<Renderer> renderer, - base::TimeDelta timestamp, - const PipelineStatusCB& seek_sb); - - // Stops and destroys all filters, placing the pipeline in the kStopped state. - void StopTask(const base::Closure& stop_cb); - - // Carries out stopping and destroying all filters, placing the pipeline in - // the kStopped state. - void ErrorChangedTask(PipelineStatus error); - - // Carries out notifying filters that the playback rate has changed. - void PlaybackRateChangedTask(double playback_rate); - - // Carries out notifying filters that the volume has changed. - void VolumeChangedTask(float volume); - - // Carries out notifying filters that we are seeking to a new timestamp. - void SeekTask(base::TimeDelta time, const PipelineStatusCB& seek_cb); - - // Carries out setting the |cdm_context| in |renderer_|, and then fires - // |cdm_attached_cb| with the result. If |renderer_| is null, - // |cdm_attached_cb| will be fired immediately with true, and |cdm_context| - // will be set in |renderer_| later when |renderer_| is created. - void SetCdmTask(CdmContext* cdm_context, - const CdmAttachedCB& cdm_attached_cb); - - // Callbacks executed when a renderer has ended. - void OnRendererEnded(); - void OnTextRendererEnded(); - void RunEndedCallbackIfNeeded(); - - scoped_ptr<TextRenderer> CreateTextRenderer(); - - // Carries out adding a new text stream to the text renderer. - void AddTextStreamTask(DemuxerStream* text_stream, - const TextTrackConfig& config); - - // Carries out removing a text stream from the text renderer. - void RemoveTextStreamTask(DemuxerStream* text_stream); - - // Callbacks executed when a text track is added. - void OnAddTextTrack(const TextTrackConfig& config, - const AddTextTrackDoneCB& done_cb); - - // Kicks off initialization for each media object, executing |done_cb| with - // the result when completed. - void InitializeDemuxer(const PipelineStatusCB& done_cb); - void InitializeRenderer(const PipelineStatusCB& done_cb); - - void StateTransitionTask(PipelineStatus status); - - // Initiates an asynchronous pause-flush-seek-preroll call sequence - // executing |done_cb| with the final status when completed. - void DoSeek(base::TimeDelta seek_timestamp, const PipelineStatusCB& done_cb); - - // Initiates an asynchronous pause-flush-stop call sequence executing - // |done_cb| when completed. - void DoStop(const PipelineStatusCB& done_cb); - void OnStopCompleted(PipelineStatus status); - - void ReportMetadata(); - - void BufferingStateChanged(BufferingState new_buffering_state); - - // Task runner used to execute pipeline tasks. - scoped_refptr<base::SingleThreadTaskRunner> task_runner_; - - // MediaLog to which to log events. - scoped_refptr<MediaLog> media_log_; - - // Lock used to serialize access for the following data members. - mutable base::Lock lock_; - - // Whether or not the pipeline is running. - bool running_; - - // Amount of available buffered data as reported by |demuxer_|. - Ranges<base::TimeDelta> buffered_time_ranges_; - - // True when OnBufferedTimeRangesChanged() has been called more recently than - // DidLoadingProgress(). - bool did_loading_progress_; - - // Current volume level (from 0.0f to 1.0f). This value is set immediately - // via SetVolume() and a task is dispatched on the task runner to notify the - // filters. - float volume_; - - // Current playback rate (>= 0.0). This value is set immediately via - // SetPlaybackRate() and a task is dispatched on the task runner to notify - // the filters. - double playback_rate_; - - // Current duration as reported by |demuxer_|. - base::TimeDelta duration_; - - // Status of the pipeline. Initialized to PIPELINE_OK which indicates that - // the pipeline is operating correctly. Any other value indicates that the - // pipeline is stopped or is stopping. Clients can call the Stop() method to - // reset the pipeline state, and restore this to PIPELINE_OK. - PipelineStatus status_; - - // The following data members are only accessed by tasks posted to - // |task_runner_|. - - // Member that tracks the current state. - State state_; - - // The timestamp to start playback from after starting/seeking/resuming has - // completed. - base::TimeDelta start_timestamp_; - - // The media timestamp to return while the pipeline is suspended. Otherwise - // set to kNoTimestamp(). - base::TimeDelta suspend_timestamp_; - - // Whether we've received the audio/video/text ended events. - bool renderer_ended_; - bool text_renderer_ended_; - - // Temporary callback used for Start(), Seek(), and Resume(). - PipelineStatusCB seek_cb_; - - // Temporary callback used for Stop(). - base::Closure stop_cb_; - - // Temporary callback used for Suspend(). - PipelineStatusCB suspend_cb_; - - // Permanent callbacks passed in via Start(). - base::Closure ended_cb_; - PipelineStatusCB error_cb_; - PipelineMetadataCB metadata_cb_; - BufferingStateCB buffering_state_cb_; - base::Closure duration_change_cb_; - AddTextTrackCB add_text_track_cb_; - base::Closure waiting_for_decryption_key_cb_; - - // Holds the initialized demuxer. Used for seeking. Owned by client. - Demuxer* demuxer_; - - // Holds the initialized renderers. Used for setting the volume, - // playback rate, and determining when playback has finished. - scoped_ptr<Renderer> renderer_; - scoped_ptr<TextRenderer> text_renderer_; - - PipelineStatistics statistics_; - - scoped_ptr<SerialRunner> pending_callbacks_; - - // CdmContext to be used to decrypt (and decode) encrypted stream in this - // pipeline. Non-null only when SetCdm() is called and the pipeline has not - // been started. Then during Start(), this value will be set on |renderer_|. - CdmContext* pending_cdm_context_; - - base::ThreadChecker thread_checker_; - - // NOTE: Weak pointers must be invalidated before all other member variables. - base::WeakPtrFactory<Pipeline> weak_factory_; + virtual PipelineStatistics GetStatistics() const = 0; - DISALLOW_COPY_AND_ASSIGN(Pipeline); + virtual void SetCdm(CdmContext* cdm_context, + const CdmAttachedCB& cdm_attached_cb) = 0; }; } // namespace media diff --git a/chromium/media/base/pipeline.cc b/chromium/media/base/pipeline_impl.cc index 867d6308a8e..5f4705b5d26 100644 --- a/chromium/media/base/pipeline.cc +++ b/chromium/media/base/pipeline_impl.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "media/base/pipeline.h" +#include "media/base/pipeline_impl.h" #include <algorithm> #include <utility> @@ -32,7 +32,7 @@ using base::TimeDelta; namespace media { -Pipeline::Pipeline( +PipelineImpl::PipelineImpl( const scoped_refptr<base::SingleThreadTaskRunner>& task_runner, MediaLog* media_log) : task_runner_(task_runner), @@ -47,12 +47,13 @@ Pipeline::Pipeline( renderer_ended_(false), text_renderer_ended_(false), demuxer_(NULL), - pending_cdm_context_(nullptr), + cdm_context_(nullptr), weak_factory_(this) { + weak_this_ = weak_factory_.GetWeakPtr(); media_log_->AddEvent(media_log_->CreatePipelineStateChangedEvent(kCreated)); } -Pipeline::~Pipeline() { +PipelineImpl::~PipelineImpl() { DCHECK(thread_checker_.CalledOnValidThread()) << "Pipeline must be destroyed on same thread that created it"; DCHECK(!running_) << "Stop() must complete before destroying object"; @@ -60,16 +61,16 @@ Pipeline::~Pipeline() { DCHECK(seek_cb_.is_null()); } -void Pipeline::Start(Demuxer* demuxer, - scoped_ptr<Renderer> renderer, - const base::Closure& ended_cb, - const PipelineStatusCB& error_cb, - const PipelineStatusCB& seek_cb, - const PipelineMetadataCB& metadata_cb, - const BufferingStateCB& buffering_state_cb, - const base::Closure& duration_change_cb, - const AddTextTrackCB& add_text_track_cb, - const base::Closure& waiting_for_decryption_key_cb) { +void PipelineImpl::Start(Demuxer* demuxer, + scoped_ptr<Renderer> renderer, + const base::Closure& ended_cb, + const PipelineStatusCB& error_cb, + const PipelineStatusCB& seek_cb, + const PipelineMetadataCB& metadata_cb, + const BufferingStateCB& buffering_state_cb, + const base::Closure& duration_change_cb, + const AddTextTrackCB& add_text_track_cb, + const base::Closure& waiting_for_decryption_key_cb) { DCHECK(!ended_cb.is_null()); DCHECK(!error_cb.is_null()); DCHECK(!seek_cb.is_null()); @@ -91,41 +92,38 @@ void Pipeline::Start(Demuxer* demuxer, add_text_track_cb_ = add_text_track_cb; waiting_for_decryption_key_cb_ = waiting_for_decryption_key_cb; - task_runner_->PostTask( - FROM_HERE, base::Bind(&Pipeline::StartTask, weak_factory_.GetWeakPtr())); + task_runner_->PostTask(FROM_HERE, + base::Bind(&PipelineImpl::StartTask, weak_this_)); } -void Pipeline::Stop(const base::Closure& stop_cb) { +void PipelineImpl::Stop(const base::Closure& stop_cb) { DVLOG(2) << __FUNCTION__; task_runner_->PostTask( - FROM_HERE, - base::Bind(&Pipeline::StopTask, weak_factory_.GetWeakPtr(), stop_cb)); + FROM_HERE, base::Bind(&PipelineImpl::StopTask, weak_this_, stop_cb)); } -void Pipeline::Seek(TimeDelta time, const PipelineStatusCB& seek_cb) { +void PipelineImpl::Seek(TimeDelta time, const PipelineStatusCB& seek_cb) { base::AutoLock auto_lock(lock_); if (!running_) { DLOG(ERROR) << "Media pipeline isn't running. Ignoring Seek()."; return; } - task_runner_->PostTask( - FROM_HERE, - base::Bind( - &Pipeline::SeekTask, weak_factory_.GetWeakPtr(), time, seek_cb)); + task_runner_->PostTask(FROM_HERE, base::Bind(&PipelineImpl::SeekTask, + weak_this_, time, seek_cb)); } -bool Pipeline::IsRunning() const { +bool PipelineImpl::IsRunning() const { base::AutoLock auto_lock(lock_); return running_; } -double Pipeline::GetPlaybackRate() const { +double PipelineImpl::GetPlaybackRate() const { base::AutoLock auto_lock(lock_); return playback_rate_; } -void Pipeline::SetPlaybackRate(double playback_rate) { +void PipelineImpl::SetPlaybackRate(double playback_rate) { if (playback_rate < 0.0) return; @@ -133,33 +131,30 @@ void Pipeline::SetPlaybackRate(double playback_rate) { playback_rate_ = playback_rate; if (running_) { task_runner_->PostTask(FROM_HERE, - base::Bind(&Pipeline::PlaybackRateChangedTask, - weak_factory_.GetWeakPtr(), - playback_rate)); + base::Bind(&PipelineImpl::PlaybackRateChangedTask, + weak_this_, playback_rate)); } } -void Pipeline::Suspend(const PipelineStatusCB& suspend_cb) { - task_runner_->PostTask( - FROM_HERE, base::Bind(&Pipeline::SuspendTask, weak_factory_.GetWeakPtr(), - suspend_cb)); +void PipelineImpl::Suspend(const PipelineStatusCB& suspend_cb) { + task_runner_->PostTask(FROM_HERE, base::Bind(&PipelineImpl::SuspendTask, + weak_this_, suspend_cb)); } -void Pipeline::Resume(scoped_ptr<Renderer> renderer, - base::TimeDelta timestamp, - const PipelineStatusCB& seek_cb) { +void PipelineImpl::Resume(scoped_ptr<Renderer> renderer, + base::TimeDelta timestamp, + const PipelineStatusCB& seek_cb) { task_runner_->PostTask( - FROM_HERE, - base::Bind(&Pipeline::ResumeTask, weak_factory_.GetWeakPtr(), - base::Passed(std::move(renderer)), timestamp, seek_cb)); + FROM_HERE, base::Bind(&PipelineImpl::ResumeTask, weak_this_, + base::Passed(&renderer), timestamp, seek_cb)); } -float Pipeline::GetVolume() const { +float PipelineImpl::GetVolume() const { base::AutoLock auto_lock(lock_); return volume_; } -void Pipeline::SetVolume(float volume) { +void PipelineImpl::SetVolume(float volume) { if (volume < 0.0f || volume > 1.0f) return; @@ -168,12 +163,11 @@ void Pipeline::SetVolume(float volume) { if (running_) { task_runner_->PostTask( FROM_HERE, - base::Bind( - &Pipeline::VolumeChangedTask, weak_factory_.GetWeakPtr(), volume)); + base::Bind(&PipelineImpl::VolumeChangedTask, weak_this_, volume)); } } -TimeDelta Pipeline::GetMediaTime() const { +TimeDelta PipelineImpl::GetMediaTime() const { base::AutoLock auto_lock(lock_); if (suspend_timestamp_ != kNoTimestamp()) return suspend_timestamp_; @@ -181,54 +175,56 @@ TimeDelta Pipeline::GetMediaTime() const { : TimeDelta(); } -Ranges<TimeDelta> Pipeline::GetBufferedTimeRanges() const { +Ranges<TimeDelta> PipelineImpl::GetBufferedTimeRanges() const { base::AutoLock auto_lock(lock_); return buffered_time_ranges_; } -TimeDelta Pipeline::GetMediaDuration() const { +TimeDelta PipelineImpl::GetMediaDuration() const { base::AutoLock auto_lock(lock_); return duration_; } -bool Pipeline::DidLoadingProgress() { +bool PipelineImpl::DidLoadingProgress() { base::AutoLock auto_lock(lock_); bool ret = did_loading_progress_; did_loading_progress_ = false; return ret; } -PipelineStatistics Pipeline::GetStatistics() const { +PipelineStatistics PipelineImpl::GetStatistics() const { base::AutoLock auto_lock(lock_); return statistics_; } -void Pipeline::SetCdm(CdmContext* cdm_context, - const CdmAttachedCB& cdm_attached_cb) { +void PipelineImpl::SetCdm(CdmContext* cdm_context, + const CdmAttachedCB& cdm_attached_cb) { task_runner_->PostTask( - FROM_HERE, base::Bind(&Pipeline::SetCdmTask, weak_factory_.GetWeakPtr(), - cdm_context, cdm_attached_cb)); + FROM_HERE, base::Bind(&PipelineImpl::SetCdmTask, weak_this_, cdm_context, + cdm_attached_cb)); } -void Pipeline::SetErrorForTesting(PipelineStatus status) { +void PipelineImpl::SetErrorForTesting(PipelineStatus status) { OnError(status); } -bool Pipeline::HasWeakPtrsForTesting() const { +bool PipelineImpl::HasWeakPtrsForTesting() const { DCHECK(task_runner_->BelongsToCurrentThread()); return weak_factory_.HasWeakPtrs(); } -void Pipeline::SetState(State next_state) { +void PipelineImpl::SetState(State next_state) { DVLOG(1) << GetStateString(state_) << " -> " << GetStateString(next_state); state_ = next_state; media_log_->AddEvent(media_log_->CreatePipelineStateChangedEvent(next_state)); } -#define RETURN_STRING(state) case state: return #state; +#define RETURN_STRING(state) \ + case state: \ + return #state; -const char* Pipeline::GetStateString(State state) { +const char* PipelineImpl::GetStateString(State state) { switch (state) { RETURN_STRING(kCreated); RETURN_STRING(kInitDemuxer); @@ -247,10 +243,9 @@ const char* Pipeline::GetStateString(State state) { #undef RETURN_STRING -Pipeline::State Pipeline::GetNextState() const { +PipelineImpl::State PipelineImpl::GetNextState() const { DCHECK(task_runner_->BelongsToCurrentThread()); - DCHECK(stop_cb_.is_null()) - << "State transitions don't happen when stopping"; + DCHECK(stop_cb_.is_null()) << "State transitions don't happen when stopping"; DCHECK_EQ(status_, PIPELINE_OK) << "State transitions don't happen when there's an error: " << status_; @@ -283,44 +278,38 @@ Pipeline::State Pipeline::GetNextState() const { return state_; } -void Pipeline::OnDemuxerError(PipelineStatus error) { - task_runner_->PostTask(FROM_HERE, - base::Bind(&Pipeline::ErrorChangedTask, - weak_factory_.GetWeakPtr(), - error)); +void PipelineImpl::OnDemuxerError(PipelineStatus error) { + task_runner_->PostTask(FROM_HERE, base::Bind(&PipelineImpl::ErrorChangedTask, + weak_this_, error)); } -void Pipeline::AddTextStream(DemuxerStream* text_stream, - const TextTrackConfig& config) { - task_runner_->PostTask(FROM_HERE, - base::Bind(&Pipeline::AddTextStreamTask, - weak_factory_.GetWeakPtr(), - text_stream, - config)); +void PipelineImpl::AddTextStream(DemuxerStream* text_stream, + const TextTrackConfig& config) { + task_runner_->PostTask( + FROM_HERE, base::Bind(&PipelineImpl::AddTextStreamTask, weak_this_, + text_stream, config)); } -void Pipeline::RemoveTextStream(DemuxerStream* text_stream) { - task_runner_->PostTask(FROM_HERE, - base::Bind(&Pipeline::RemoveTextStreamTask, - weak_factory_.GetWeakPtr(), - text_stream)); +void PipelineImpl::RemoveTextStream(DemuxerStream* text_stream) { + task_runner_->PostTask( + FROM_HERE, + base::Bind(&PipelineImpl::RemoveTextStreamTask, weak_this_, text_stream)); } -void Pipeline::OnError(PipelineStatus error) { +void PipelineImpl::OnError(PipelineStatus error) { DCHECK(task_runner_->BelongsToCurrentThread()); DCHECK(IsRunning()); DCHECK_NE(PIPELINE_OK, error); VLOG(1) << "Media pipeline error: " << error; - task_runner_->PostTask(FROM_HERE, base::Bind( - &Pipeline::ErrorChangedTask, weak_factory_.GetWeakPtr(), error)); + task_runner_->PostTask(FROM_HERE, base::Bind(&PipelineImpl::ErrorChangedTask, + weak_this_, error)); } -void Pipeline::SetDuration(TimeDelta duration) { +void PipelineImpl::SetDuration(TimeDelta duration) { DCHECK(IsRunning()); - media_log_->AddEvent( - media_log_->CreateTimeEvent( - MediaLogEvent::DURATION_SET, "duration", duration)); + media_log_->AddEvent(media_log_->CreateTimeEvent(MediaLogEvent::DURATION_SET, + "duration", duration)); UMA_HISTOGRAM_LONG_TIMES("Media.Duration", duration); base::AutoLock auto_lock(lock_); @@ -329,7 +318,7 @@ void Pipeline::SetDuration(TimeDelta duration) { duration_change_cb_.Run(); } -void Pipeline::StateTransitionTask(PipelineStatus status) { +void PipelineImpl::StateTransitionTask(PipelineStatus status) { DCHECK(task_runner_->BelongsToCurrentThread()); // No-op any state transitions if we're stopping. @@ -353,7 +342,7 @@ void Pipeline::StateTransitionTask(PipelineStatus status) { pending_callbacks_.reset(); PipelineStatusCB done_cb = - base::Bind(&Pipeline::StateTransitionTask, weak_factory_.GetWeakPtr()); + base::Bind(&PipelineImpl::StateTransitionTask, weak_this_); // Switch states, performing any entrance actions for the new state as well. SetState(GetNextState()); @@ -391,6 +380,8 @@ void Pipeline::StateTransitionTask(PipelineStatus status) { case kSuspended: renderer_.reset(); + statistics_.audio_memory_usage = 0; + statistics_.video_memory_usage = 0; base::ResetAndReturn(&suspend_cb_).Run(PIPELINE_OK); return; @@ -410,8 +401,8 @@ void Pipeline::StateTransitionTask(PipelineStatus status) { // // That being said, deleting the renderers while keeping |pending_callbacks_| // running on the media thread would result in crashes. -void Pipeline::DoSeek(TimeDelta seek_timestamp, - const PipelineStatusCB& done_cb) { +void PipelineImpl::DoSeek(TimeDelta seek_timestamp, + const PipelineStatusCB& done_cb) { DCHECK(task_runner_->BelongsToCurrentThread()); DCHECK(!pending_callbacks_.get()); DCHECK_EQ(state_, kSeeking); @@ -419,8 +410,8 @@ void Pipeline::DoSeek(TimeDelta seek_timestamp, // Pause. if (text_renderer_) { - bound_fns.Push(base::Bind( - &TextRenderer::Pause, base::Unretained(text_renderer_.get()))); + bound_fns.Push(base::Bind(&TextRenderer::Pause, + base::Unretained(text_renderer_.get()))); } // Flush. @@ -429,18 +420,18 @@ void Pipeline::DoSeek(TimeDelta seek_timestamp, base::Bind(&Renderer::Flush, base::Unretained(renderer_.get()))); if (text_renderer_) { - bound_fns.Push(base::Bind( - &TextRenderer::Flush, base::Unretained(text_renderer_.get()))); + bound_fns.Push(base::Bind(&TextRenderer::Flush, + base::Unretained(text_renderer_.get()))); } // Seek demuxer. - bound_fns.Push(base::Bind( - &Demuxer::Seek, base::Unretained(demuxer_), seek_timestamp)); + bound_fns.Push( + base::Bind(&Demuxer::Seek, base::Unretained(demuxer_), seek_timestamp)); pending_callbacks_ = SerialRunner::Run(bound_fns, done_cb); } -void Pipeline::DoStop(const PipelineStatusCB& done_cb) { +void PipelineImpl::DoStop(const PipelineStatusCB& done_cb) { DVLOG(2) << __FUNCTION__; DCHECK(task_runner_->BelongsToCurrentThread()); DCHECK(!pending_callbacks_.get()); @@ -463,7 +454,7 @@ void Pipeline::DoStop(const PipelineStatusCB& done_cb) { task_runner_->PostTask(FROM_HERE, base::Bind(done_cb, PIPELINE_OK)); } -void Pipeline::OnStopCompleted(PipelineStatus status) { +void PipelineImpl::OnStopCompleted(PipelineStatus status) { DVLOG(2) << __FUNCTION__; DCHECK(task_runner_->BelongsToCurrentThread()); DCHECK_EQ(state_, kStopping); @@ -507,16 +498,15 @@ void Pipeline::OnStopCompleted(PipelineStatus status) { } } -void Pipeline::OnBufferedTimeRangesChanged( +void PipelineImpl::OnBufferedTimeRangesChanged( const Ranges<base::TimeDelta>& ranges) { - DCHECK(IsRunning()); base::AutoLock auto_lock(lock_); buffered_time_ranges_ = ranges; did_loading_progress_ = true; } // Called from any thread. -void Pipeline::OnUpdateStatistics(const PipelineStatistics& stats_delta) { +void PipelineImpl::OnUpdateStatistics(const PipelineStatistics& stats_delta) { base::AutoLock auto_lock(lock_); statistics_.audio_bytes_decoded += stats_delta.audio_bytes_decoded; statistics_.video_bytes_decoded += stats_delta.video_bytes_decoded; @@ -526,7 +516,7 @@ void Pipeline::OnUpdateStatistics(const PipelineStatistics& stats_delta) { statistics_.video_memory_usage += stats_delta.video_memory_usage; } -void Pipeline::StartTask() { +void PipelineImpl::StartTask() { DCHECK(task_runner_->BelongsToCurrentThread()); CHECK_EQ(kCreated, state_) @@ -535,19 +525,13 @@ void Pipeline::StartTask() { text_renderer_ = CreateTextRenderer(); if (text_renderer_) { text_renderer_->Initialize( - base::Bind(&Pipeline::OnTextRendererEnded, weak_factory_.GetWeakPtr())); - } - - // Set CDM early to avoid unnecessary delay in Renderer::Initialize(). - if (pending_cdm_context_) { - renderer_->SetCdm(pending_cdm_context_, base::Bind(&IgnoreCdmAttached)); - pending_cdm_context_ = nullptr; + base::Bind(&PipelineImpl::OnTextRendererEnded, weak_this_)); } StateTransitionTask(PIPELINE_OK); } -void Pipeline::StopTask(const base::Closure& stop_cb) { +void PipelineImpl::StopTask(const base::Closure& stop_cb) { DCHECK(task_runner_->BelongsToCurrentThread()); DCHECK(stop_cb_.is_null()); @@ -581,26 +565,32 @@ void Pipeline::StopTask(const base::Closure& stop_cb) { SetState(kStopping); pending_callbacks_.reset(); - DoStop(base::Bind(&Pipeline::OnStopCompleted, weak_factory_.GetWeakPtr())); + DoStop(base::Bind(&PipelineImpl::OnStopCompleted, weak_this_)); } -void Pipeline::ErrorChangedTask(PipelineStatus error) { +void PipelineImpl::ErrorChangedTask(PipelineStatus error) { DCHECK(task_runner_->BelongsToCurrentThread()); DCHECK_NE(PIPELINE_OK, error) << "PIPELINE_OK isn't an error!"; - media_log_->AddEvent(media_log_->CreatePipelineErrorEvent(error)); + // Don't report pipeline error events to the media log here. The embedder will + // log this when |error_cb_| is called. If the pipeline is already stopped or + // stopping we also don't want to log any event. In case we are suspending or + // suspended, the error may be recoverable, so don't propagate it now, instead + // let the subsequent seek during resume propagate it if it's unrecoverable. - if (state_ == kStopping || state_ == kStopped) + if (state_ == kStopping || state_ == kStopped || state_ == kSuspending || + state_ == kSuspended) { return; + } SetState(kStopping); pending_callbacks_.reset(); status_ = error; - DoStop(base::Bind(&Pipeline::OnStopCompleted, weak_factory_.GetWeakPtr())); + DoStop(base::Bind(&PipelineImpl::OnStopCompleted, weak_this_)); } -void Pipeline::PlaybackRateChangedTask(double playback_rate) { +void PipelineImpl::PlaybackRateChangedTask(double playback_rate) { DCHECK(task_runner_->BelongsToCurrentThread()); // Playback rate changes are only carried out while playing. @@ -610,7 +600,7 @@ void Pipeline::PlaybackRateChangedTask(double playback_rate) { renderer_->SetPlaybackRate(playback_rate); } -void Pipeline::VolumeChangedTask(float volume) { +void PipelineImpl::VolumeChangedTask(float volume) { DCHECK(task_runner_->BelongsToCurrentThread()); // Volume changes are only carried out while playing. @@ -620,7 +610,7 @@ void Pipeline::VolumeChangedTask(float volume) { renderer_->SetVolume(volume); } -void Pipeline::SeekTask(TimeDelta time, const PipelineStatusCB& seek_cb) { +void PipelineImpl::SeekTask(TimeDelta time, const PipelineStatusCB& seek_cb) { DCHECK(task_runner_->BelongsToCurrentThread()); DCHECK(stop_cb_.is_null()); @@ -643,11 +633,11 @@ void Pipeline::SeekTask(TimeDelta time, const PipelineStatusCB& seek_cb) { text_renderer_ended_ = false; start_timestamp_ = seek_timestamp; - DoSeek(seek_timestamp, base::Bind(&Pipeline::StateTransitionTask, - weak_factory_.GetWeakPtr())); + DoSeek(seek_timestamp, + base::Bind(&PipelineImpl::StateTransitionTask, weak_this_)); } -void Pipeline::SuspendTask(const PipelineStatusCB& suspend_cb) { +void PipelineImpl::SuspendTask(const PipelineStatusCB& suspend_cb) { DCHECK(task_runner_->BelongsToCurrentThread()); // Suppress suspending if we're not playing. @@ -690,13 +680,12 @@ void Pipeline::SuspendTask(const PipelineStatusCB& suspend_cb) { } pending_callbacks_ = SerialRunner::Run( - fns, - base::Bind(&Pipeline::StateTransitionTask, weak_factory_.GetWeakPtr())); + fns, base::Bind(&PipelineImpl::StateTransitionTask, weak_this_)); } -void Pipeline::ResumeTask(scoped_ptr<Renderer> renderer, - base::TimeDelta timestamp, - const PipelineStatusCB& seek_cb) { +void PipelineImpl::ResumeTask(scoped_ptr<Renderer> renderer, + base::TimeDelta timestamp, + const PipelineStatusCB& seek_cb) { DCHECK(task_runner_->BelongsToCurrentThread()); // Suppress resuming if we're not suspended. @@ -724,30 +713,40 @@ void Pipeline::ResumeTask(scoped_ptr<Renderer> renderer, // kInitDemuxer, and even if we did the current code would seek to the start // instead of |timestamp|). SerialRunner::Queue fns; - base::WeakPtr<Pipeline> weak_this = weak_factory_.GetWeakPtr(); fns.Push( base::Bind(&Demuxer::Seek, base::Unretained(demuxer_), start_timestamp_)); - fns.Push(base::Bind(&Pipeline::InitializeRenderer, weak_this)); + fns.Push(base::Bind(&PipelineImpl::InitializeRenderer, weak_this_)); pending_callbacks_ = SerialRunner::Run( - fns, base::Bind(&Pipeline::StateTransitionTask, weak_this)); + fns, base::Bind(&PipelineImpl::StateTransitionTask, weak_this_)); } -void Pipeline::SetCdmTask(CdmContext* cdm_context, - const CdmAttachedCB& cdm_attached_cb) { +void PipelineImpl::SetCdmTask(CdmContext* cdm_context, + const CdmAttachedCB& cdm_attached_cb) { base::AutoLock auto_lock(lock_); if (!renderer_) { - pending_cdm_context_ = cdm_context; + cdm_context_ = cdm_context; cdm_attached_cb.Run(true); return; } - renderer_->SetCdm(cdm_context, cdm_attached_cb); + renderer_->SetCdm(cdm_context, + base::Bind(&PipelineImpl::OnCdmAttached, weak_this_, + cdm_attached_cb, cdm_context)); } -void Pipeline::OnRendererEnded() { +void PipelineImpl::OnCdmAttached(const CdmAttachedCB& cdm_attached_cb, + CdmContext* cdm_context, + bool success) { + DCHECK(task_runner_->BelongsToCurrentThread()); + if (success) + cdm_context_ = cdm_context; + cdm_attached_cb.Run(success); +} + +void PipelineImpl::OnRendererEnded() { DCHECK(task_runner_->BelongsToCurrentThread()); media_log_->AddEvent(media_log_->CreateEvent(MediaLogEvent::ENDED)); @@ -760,7 +759,7 @@ void Pipeline::OnRendererEnded() { RunEndedCallbackIfNeeded(); } -void Pipeline::OnTextRendererEnded() { +void PipelineImpl::OnTextRendererEnded() { DCHECK(task_runner_->BelongsToCurrentThread()); media_log_->AddEvent(media_log_->CreateEvent(MediaLogEvent::TEXT_ENDED)); @@ -773,7 +772,7 @@ void Pipeline::OnTextRendererEnded() { RunEndedCallbackIfNeeded(); } -void Pipeline::RunEndedCallbackIfNeeded() { +void PipelineImpl::RunEndedCallbackIfNeeded() { DCHECK(task_runner_->BelongsToCurrentThread()); if (renderer_ && !renderer_ended_) @@ -786,7 +785,7 @@ void Pipeline::RunEndedCallbackIfNeeded() { ended_cb_.Run(); } -scoped_ptr<TextRenderer> Pipeline::CreateTextRenderer() { +scoped_ptr<TextRenderer> PipelineImpl::CreateTextRenderer() { DCHECK(task_runner_->BelongsToCurrentThread()); const base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess(); @@ -794,12 +793,11 @@ scoped_ptr<TextRenderer> Pipeline::CreateTextRenderer() { return scoped_ptr<media::TextRenderer>(); return scoped_ptr<media::TextRenderer>(new media::TextRenderer( - task_runner_, - base::Bind(&Pipeline::OnAddTextTrack, weak_factory_.GetWeakPtr()))); + task_runner_, base::Bind(&PipelineImpl::OnAddTextTrack, weak_this_))); } -void Pipeline::AddTextStreamTask(DemuxerStream* text_stream, - const TextTrackConfig& config) { +void PipelineImpl::AddTextStreamTask(DemuxerStream* text_stream, + const TextTrackConfig& config) { DCHECK(task_runner_->BelongsToCurrentThread()); // TODO(matthewjheaney): fix up text_ended_ when text stream // is added (http://crbug.com/321446). @@ -807,24 +805,24 @@ void Pipeline::AddTextStreamTask(DemuxerStream* text_stream, text_renderer_->AddTextStream(text_stream, config); } -void Pipeline::RemoveTextStreamTask(DemuxerStream* text_stream) { +void PipelineImpl::RemoveTextStreamTask(DemuxerStream* text_stream) { DCHECK(task_runner_->BelongsToCurrentThread()); if (text_renderer_) text_renderer_->RemoveTextStream(text_stream); } -void Pipeline::OnAddTextTrack(const TextTrackConfig& config, - const AddTextTrackDoneCB& done_cb) { +void PipelineImpl::OnAddTextTrack(const TextTrackConfig& config, + const AddTextTrackDoneCB& done_cb) { DCHECK(task_runner_->BelongsToCurrentThread()); add_text_track_cb_.Run(config, done_cb); } -void Pipeline::InitializeDemuxer(const PipelineStatusCB& done_cb) { +void PipelineImpl::InitializeDemuxer(const PipelineStatusCB& done_cb) { DCHECK(task_runner_->BelongsToCurrentThread()); - demuxer_->Initialize(this, done_cb, text_renderer_); + demuxer_->Initialize(this, done_cb, !!text_renderer_); } -void Pipeline::InitializeRenderer(const PipelineStatusCB& done_cb) { +void PipelineImpl::InitializeRenderer(const PipelineStatusCB& done_cb) { DCHECK(task_runner_->BelongsToCurrentThread()); if (!demuxer_->GetStream(DemuxerStream::AUDIO) && @@ -837,18 +835,19 @@ void Pipeline::InitializeRenderer(const PipelineStatusCB& done_cb) { return; } - base::WeakPtr<Pipeline> weak_this = weak_factory_.GetWeakPtr(); + if (cdm_context_) + renderer_->SetCdm(cdm_context_, base::Bind(&IgnoreCdmAttached)); + renderer_->Initialize( - demuxer_, - done_cb, - base::Bind(&Pipeline::OnUpdateStatistics, weak_this), - base::Bind(&Pipeline::BufferingStateChanged, weak_this), - base::Bind(&Pipeline::OnRendererEnded, weak_this), - base::Bind(&Pipeline::OnError, weak_this), + demuxer_, done_cb, + base::Bind(&PipelineImpl::OnUpdateStatistics, weak_this_), + base::Bind(&PipelineImpl::BufferingStateChanged, weak_this_), + base::Bind(&PipelineImpl::OnRendererEnded, weak_this_), + base::Bind(&PipelineImpl::OnError, weak_this_), waiting_for_decryption_key_cb_); } -void Pipeline::ReportMetadata() { +void PipelineImpl::ReportMetadata() { DCHECK(task_runner_->BelongsToCurrentThread()); PipelineMetadata metadata; metadata.timeline_offset = demuxer_->GetTimelineOffset(); @@ -864,7 +863,7 @@ void Pipeline::ReportMetadata() { metadata_cb_.Run(metadata); } -void Pipeline::BufferingStateChanged(BufferingState new_buffering_state) { +void PipelineImpl::BufferingStateChanged(BufferingState new_buffering_state) { DVLOG(1) << __FUNCTION__ << "(" << new_buffering_state << ") "; DCHECK(task_runner_->BelongsToCurrentThread()); buffering_state_cb_.Run(new_buffering_state); diff --git a/chromium/media/base/pipeline_impl.h b/chromium/media/base/pipeline_impl.h new file mode 100644 index 00000000000..7a6da3a7b51 --- /dev/null +++ b/chromium/media/base/pipeline_impl.h @@ -0,0 +1,346 @@ +// Copyright (c) 2016 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_BASE_PIPELINE_IMPL_H_ +#define MEDIA_BASE_PIPELINE_IMPL_H_ + +#include "base/gtest_prod_util.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "base/synchronization/lock.h" +#include "base/threading/thread_checker.h" +#include "base/time/time.h" +#include "media/base/buffering_state.h" +#include "media/base/cdm_context.h" +#include "media/base/demuxer.h" +#include "media/base/media_export.h" +#include "media/base/pipeline.h" +#include "media/base/pipeline_status.h" +#include "media/base/ranges.h" +#include "media/base/serial_runner.h" +#include "media/base/text_track.h" + +namespace base { +class SingleThreadTaskRunner; +} + +namespace media { + +class MediaLog; +class Renderer; +class TextRenderer; + +// Pipeline runs the media pipeline. Filters are created and called on the +// task runner injected into this object. Pipeline works like a state +// machine to perform asynchronous initialization, pausing, seeking and playing. +// +// Here's a state diagram that describes the lifetime of this object. +// +// [ *Created ] [ Any State ] +// | Start() | Stop() / SetError() +// V V +// [ InitXXX (for each filter) ] [ Stopping ] +// | | +// V V +// [ Playing ] <---------. [ Stopped ] +// | | Seek() | +// | V | +// | [ Seeking ] ----' +// | ^ +// | Suspend() | +// V | +// [ Suspending ] | +// | | +// V | +// [ Suspended ] | +// | Resume() | +// V | +// [ Resuming ] ---------' +// +// Initialization is a series of state transitions from "Created" through each +// filter initialization state. When all filter initialization states have +// completed, we simulate a Seek() to the beginning of the media to give filters +// a chance to preroll. From then on the normal Seek() transitions are carried +// out and we start playing the media. +// +// If any error ever happens, this object will transition to the "Error" state +// from any state. If Stop() is ever called, this object will transition to +// "Stopped" state. +// +// TODO(sandersd): It should be possible to pass through Suspended when going +// from InitDemuxer to InitRenderer, thereby eliminating the Resuming state. +// Some annoying differences between the two paths need to be removed first. +class MEDIA_EXPORT PipelineImpl : public Pipeline, public DemuxerHost { + public: + // Constructs a media pipeline that will execute on |task_runner|. + PipelineImpl(const scoped_refptr<base::SingleThreadTaskRunner>& task_runner, + MediaLog* media_log); + ~PipelineImpl() override; + + void SetErrorForTesting(PipelineStatus status); + bool HasWeakPtrsForTesting() const; + + // Pipeline implementation. + void Start(Demuxer* demuxer, + scoped_ptr<Renderer> renderer, + const base::Closure& ended_cb, + const PipelineStatusCB& error_cb, + const PipelineStatusCB& seek_cb, + const PipelineMetadataCB& metadata_cb, + const BufferingStateCB& buffering_state_cb, + const base::Closure& duration_change_cb, + const AddTextTrackCB& add_text_track_cb, + const base::Closure& waiting_for_decryption_key_cb) override; + void Stop(const base::Closure& stop_cb) override; + void Seek(base::TimeDelta time, const PipelineStatusCB& seek_cb) override; + bool IsRunning() const override; + double GetPlaybackRate() const override; + void SetPlaybackRate(double playback_rate) override; + void Suspend(const PipelineStatusCB& suspend_cb) override; + void Resume(scoped_ptr<Renderer> renderer, + base::TimeDelta timestamp, + const PipelineStatusCB& seek_cb) override; + float GetVolume() const override; + void SetVolume(float volume) override; + base::TimeDelta GetMediaTime() const override; + Ranges<base::TimeDelta> GetBufferedTimeRanges() const override; + base::TimeDelta GetMediaDuration() const override; + bool DidLoadingProgress() override; + PipelineStatistics GetStatistics() const override; + void SetCdm(CdmContext* cdm_context, + const CdmAttachedCB& cdm_attached_cb) override; + + private: + friend class MediaLog; + friend class PipelineImplTest; + + // Pipeline states, as described above. + enum State { + kCreated, + kInitDemuxer, + kInitRenderer, + kSeeking, + kPlaying, + kStopping, + kStopped, + kSuspending, + kSuspended, + kResuming, + }; + + // Updates |state_|. All state transitions should use this call. + void SetState(State next_state); + + static const char* GetStateString(State state); + State GetNextState() const; + + // Helper method that runs & resets |seek_cb_| and resets |seek_timestamp_| + // and |seek_pending_|. + void FinishSeek(); + + // DemuxerHost implementaion. + void OnBufferedTimeRangesChanged( + const Ranges<base::TimeDelta>& ranges) override; + void SetDuration(base::TimeDelta duration) override; + void OnDemuxerError(PipelineStatus error) override; + void AddTextStream(DemuxerStream* text_stream, + const TextTrackConfig& config) override; + void RemoveTextStream(DemuxerStream* text_stream) override; + + // Callback executed when a rendering error happened, initiating the teardown + // sequence. + void OnError(PipelineStatus error); + + // Callback executed by filters to update statistics. + void OnUpdateStatistics(const PipelineStatistics& stats_delta); + + // The following "task" methods correspond to the public methods, but these + // methods are run as the result of posting a task to the Pipeline's + // task runner. + void StartTask(); + + // Suspends the pipeline, discarding the current renderer. + void SuspendTask(const PipelineStatusCB& suspend_cb); + + // Resumes the pipeline with a new renderer, and initializes it with a seek. + void ResumeTask(scoped_ptr<Renderer> renderer, + base::TimeDelta timestamp, + const PipelineStatusCB& seek_sb); + + // Stops and destroys all filters, placing the pipeline in the kStopped state. + void StopTask(const base::Closure& stop_cb); + + // Carries out stopping and destroying all filters, placing the pipeline in + // the kStopped state. + void ErrorChangedTask(PipelineStatus error); + + // Carries out notifying filters that the playback rate has changed. + void PlaybackRateChangedTask(double playback_rate); + + // Carries out notifying filters that the volume has changed. + void VolumeChangedTask(float volume); + + // Carries out notifying filters that we are seeking to a new timestamp. + void SeekTask(base::TimeDelta time, const PipelineStatusCB& seek_cb); + + // Carries out setting the |cdm_context| in |renderer_|, and then fires + // |cdm_attached_cb| with the result. If |renderer_| is null, + // |cdm_attached_cb| will be fired immediately with true, and |cdm_context| + // will be set in |renderer_| later when |renderer_| is created. + void SetCdmTask(CdmContext* cdm_context, + const CdmAttachedCB& cdm_attached_cb); + void OnCdmAttached(const CdmAttachedCB& cdm_attached_cb, + CdmContext* cdm_context, + bool success); + + // Callbacks executed when a renderer has ended. + void OnRendererEnded(); + void OnTextRendererEnded(); + void RunEndedCallbackIfNeeded(); + + scoped_ptr<TextRenderer> CreateTextRenderer(); + + // Carries out adding a new text stream to the text renderer. + void AddTextStreamTask(DemuxerStream* text_stream, + const TextTrackConfig& config); + + // Carries out removing a text stream from the text renderer. + void RemoveTextStreamTask(DemuxerStream* text_stream); + + // Callbacks executed when a text track is added. + void OnAddTextTrack(const TextTrackConfig& config, + const AddTextTrackDoneCB& done_cb); + + // Kicks off initialization for each media object, executing |done_cb| with + // the result when completed. + void InitializeDemuxer(const PipelineStatusCB& done_cb); + void InitializeRenderer(const PipelineStatusCB& done_cb); + + void StateTransitionTask(PipelineStatus status); + + // Initiates an asynchronous pause-flush-seek-preroll call sequence + // executing |done_cb| with the final status when completed. + void DoSeek(base::TimeDelta seek_timestamp, const PipelineStatusCB& done_cb); + + // Initiates an asynchronous pause-flush-stop call sequence executing + // |done_cb| when completed. + void DoStop(const PipelineStatusCB& done_cb); + void OnStopCompleted(PipelineStatus status); + + void ReportMetadata(); + + void BufferingStateChanged(BufferingState new_buffering_state); + + // Task runner used to execute pipeline tasks. + scoped_refptr<base::SingleThreadTaskRunner> task_runner_; + + // MediaLog to which to log events. + scoped_refptr<MediaLog> media_log_; + + // Lock used to serialize access for the following data members. + mutable base::Lock lock_; + + // Whether or not the pipeline is running. + bool running_; + + // Amount of available buffered data as reported by |demuxer_|. + Ranges<base::TimeDelta> buffered_time_ranges_; + + // True when OnBufferedTimeRangesChanged() has been called more recently than + // DidLoadingProgress(). + bool did_loading_progress_; + + // Current volume level (from 0.0f to 1.0f). This value is set immediately + // via SetVolume() and a task is dispatched on the task runner to notify the + // filters. + float volume_; + + // Current playback rate (>= 0.0). This value is set immediately via + // SetPlaybackRate() and a task is dispatched on the task runner to notify + // the filters. + double playback_rate_; + + // Current duration as reported by |demuxer_|. + base::TimeDelta duration_; + + // Status of the pipeline. Initialized to PIPELINE_OK which indicates that + // the pipeline is operating correctly. Any other value indicates that the + // pipeline is stopped or is stopping. Clients can call the Stop() method to + // reset the pipeline state, and restore this to PIPELINE_OK. + PipelineStatus status_; + + // The following data members are only accessed by tasks posted to + // |task_runner_|. + + // Member that tracks the current state. + State state_; + + // The timestamp to start playback from after starting/seeking/resuming has + // completed. + base::TimeDelta start_timestamp_; + + // The media timestamp to return while the pipeline is suspended. Otherwise + // set to kNoTimestamp(). + base::TimeDelta suspend_timestamp_; + + // Whether we've received the audio/video/text ended events. + bool renderer_ended_; + bool text_renderer_ended_; + + // Temporary callback used for Start(), Seek(), and Resume(). + PipelineStatusCB seek_cb_; + + // Temporary callback used for Stop(). + base::Closure stop_cb_; + + // Temporary callback used for Suspend(). + PipelineStatusCB suspend_cb_; + + // Permanent callbacks passed in via Start(). + base::Closure ended_cb_; + PipelineStatusCB error_cb_; + PipelineMetadataCB metadata_cb_; + BufferingStateCB buffering_state_cb_; + base::Closure duration_change_cb_; + AddTextTrackCB add_text_track_cb_; + base::Closure waiting_for_decryption_key_cb_; + + // Holds the initialized demuxer. Used for seeking. Owned by client. + Demuxer* demuxer_; + + // Holds the initialized renderers. Used for setting the volume, + // playback rate, and determining when playback has finished. + scoped_ptr<Renderer> renderer_; + scoped_ptr<TextRenderer> text_renderer_; + + PipelineStatistics statistics_; + + scoped_ptr<SerialRunner> pending_callbacks_; + + // The CdmContext to be used to decrypt (and decode) encrypted stream in this + // pipeline. It is set when SetCdm() succeeds on the renderer (or when + // SetCdm() is called before Start()), after which it is guaranteed to outlive + // this pipeline. The saved value will be used to configure new renderers, + // when starting or resuming. + CdmContext* cdm_context_; + + base::ThreadChecker thread_checker_; + + // A weak pointer that can be safely copied on the media thread. + base::WeakPtr<PipelineImpl> weak_this_; + + // Weak pointers must be created on the main thread, and must be dereferenced + // on the media thread. + // + // Declared last so that weak pointers will be invalidated before all other + // member variables. + base::WeakPtrFactory<PipelineImpl> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(PipelineImpl); +}; + +} // namespace media + +#endif // MEDIA_BASE_PIPELINE_IMPL_H_ diff --git a/chromium/media/base/pipeline_unittest.cc b/chromium/media/base/pipeline_impl_unittest.cc index 8f43d608889..d08ecf1c173 100644 --- a/chromium/media/base/pipeline_unittest.cc +++ b/chromium/media/base/pipeline_impl_unittest.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "media/base/pipeline.h" +#include "media/base/pipeline_impl.h" #include <stddef.h> #include <utility> @@ -72,7 +72,7 @@ ACTION_TEMPLATE(PostCallback, // InitializationComplete(), which keeps the pipeline humming along. If // either filters don't call InitializationComplete() immediately or filter // initialization is moved to a separate thread this test will become flaky. -class PipelineTest : public ::testing::Test { +class PipelineImplTest : public ::testing::Test { public: // Used for setting expectations on pipeline callbacks. Using a StrictMock // also lets us test for missing callbacks. @@ -96,17 +96,16 @@ class PipelineTest : public ::testing::Test { DISALLOW_COPY_AND_ASSIGN(CallbackHelper); }; - PipelineTest() - : pipeline_(new Pipeline(message_loop_.task_runner(), - new MediaLog())), + PipelineImplTest() + : pipeline_( + new PipelineImpl(message_loop_.task_runner(), new MediaLog())), demuxer_(new StrictMock<MockDemuxer>()), scoped_renderer_(new StrictMock<MockRenderer>()), renderer_(scoped_renderer_.get()) { // SetDemuxerExpectations() adds overriding expectations for expected // non-NULL streams. DemuxerStream* null_pointer = NULL; - EXPECT_CALL(*demuxer_, GetStream(_)) - .WillRepeatedly(Return(null_pointer)); + EXPECT_CALL(*demuxer_, GetStream(_)).WillRepeatedly(Return(null_pointer)); EXPECT_CALL(*demuxer_, GetTimelineOffset()) .WillRepeatedly(Return(base::Time())); @@ -117,7 +116,7 @@ class PipelineTest : public ::testing::Test { EXPECT_CALL(*demuxer_, GetStartTime()).WillRepeatedly(Return(start_time_)); } - virtual ~PipelineTest() { + virtual ~PipelineImplTest() { if (!pipeline_ || !pipeline_->IsRunning()) return; @@ -132,8 +131,8 @@ class PipelineTest : public ::testing::Test { // Expect a stop callback if we were started. ExpectPipelineStopAndDestroyPipeline(); - pipeline_->Stop(base::Bind(&CallbackHelper::OnStop, - base::Unretained(&callbacks_))); + pipeline_->Stop( + base::Bind(&CallbackHelper::OnStop, base::Unretained(&callbacks_))); message_loop_.RunUntilIdle(); } @@ -166,9 +165,9 @@ class PipelineTest : public ::testing::Test { SetDemuxerExpectations(streams, base::TimeDelta::FromSeconds(10)); } - scoped_ptr<StrictMock<MockDemuxerStream> > CreateStream( + scoped_ptr<StrictMock<MockDemuxerStream>> CreateStream( DemuxerStream::Type type) { - scoped_ptr<StrictMock<MockDemuxerStream> > stream( + scoped_ptr<StrictMock<MockDemuxerStream>> stream( new StrictMock<MockDemuxerStream>(type)); return stream; } @@ -176,18 +175,19 @@ class PipelineTest : public ::testing::Test { // Sets up expectations to allow the video renderer to initialize. void SetRendererExpectations() { EXPECT_CALL(*renderer_, Initialize(_, _, _, _, _, _, _)) - .WillOnce(DoAll(SaveArg<3>(&buffering_state_cb_), - SaveArg<4>(&ended_cb_), - PostCallback<1>(PIPELINE_OK))); + .WillOnce(DoAll(SaveArg<2>(&statistics_cb_), + SaveArg<3>(&buffering_state_cb_), + SaveArg<4>(&ended_cb_), PostCallback<1>(PIPELINE_OK))); EXPECT_CALL(*renderer_, HasAudio()).WillRepeatedly(Return(audio_stream())); EXPECT_CALL(*renderer_, HasVideo()).WillRepeatedly(Return(video_stream())); } void AddTextStream() { - EXPECT_CALL(*this, OnAddTextTrack(_,_)) - .WillOnce(Invoke(this, &PipelineTest::DoOnAddTextTrack)); - static_cast<DemuxerHost*>(pipeline_.get())->AddTextStream(text_stream(), - TextTrackConfig(kTextSubtitles, "", "", "")); + EXPECT_CALL(*this, OnAddTextTrack(_, _)) + .WillOnce(Invoke(this, &PipelineImplTest::DoOnAddTextTrack)); + static_cast<DemuxerHost*>(pipeline_.get()) + ->AddTextStream(text_stream(), + TextTrackConfig(kTextSubtitles, "", "", "")); message_loop_.RunUntilIdle(); } @@ -203,8 +203,8 @@ class PipelineTest : public ::testing::Test { base::Unretained(&callbacks_)), base::Bind(&CallbackHelper::OnDurationChange, base::Unretained(&callbacks_)), - base::Bind(&PipelineTest::OnAddTextTrack, base::Unretained(this)), - base::Bind(&PipelineTest::OnWaitingForDecryptionKey, + base::Bind(&PipelineImplTest::OnAddTextTrack, base::Unretained(this)), + base::Bind(&PipelineImplTest::OnWaitingForDecryptionKey, base::Unretained(this))); } @@ -218,8 +218,8 @@ class PipelineTest : public ::testing::Test { EXPECT_CALL(*renderer_, SetPlaybackRate(0.0)); EXPECT_CALL(*renderer_, SetVolume(1.0f)); EXPECT_CALL(*renderer_, StartPlayingFrom(start_time_)) - .WillOnce(SetBufferingState(&buffering_state_cb_, - BUFFERING_HAVE_ENOUGH)); + .WillOnce( + SetBufferingState(&buffering_state_cb_, BUFFERING_HAVE_ENOUGH)); EXPECT_CALL(callbacks_, OnBufferingStateChange(BUFFERING_HAVE_ENOUGH)); } @@ -242,31 +242,25 @@ class PipelineTest : public ::testing::Test { text_stream_ = std::move(text_stream); } - MockDemuxerStream* audio_stream() { - return audio_stream_.get(); - } + MockDemuxerStream* audio_stream() { return audio_stream_.get(); } - MockDemuxerStream* video_stream() { - return video_stream_.get(); - } + MockDemuxerStream* video_stream() { return video_stream_.get(); } - FakeTextTrackStream* text_stream() { - return text_stream_.get(); - } + FakeTextTrackStream* text_stream() { return text_stream_.get(); } void ExpectSeek(const base::TimeDelta& seek_time, bool underflowed) { EXPECT_CALL(*demuxer_, Seek(seek_time, _)) .WillOnce(RunCallback<1>(PIPELINE_OK)); EXPECT_CALL(*renderer_, Flush(_)) - .WillOnce(DoAll(SetBufferingState(&buffering_state_cb_, - BUFFERING_HAVE_NOTHING), - RunClosure<0>())); + .WillOnce(DoAll( + SetBufferingState(&buffering_state_cb_, BUFFERING_HAVE_NOTHING), + RunClosure<0>())); EXPECT_CALL(*renderer_, SetPlaybackRate(_)); EXPECT_CALL(*renderer_, SetVolume(_)); EXPECT_CALL(*renderer_, StartPlayingFrom(seek_time)) - .WillOnce(SetBufferingState(&buffering_state_cb_, - BUFFERING_HAVE_ENOUGH)); + .WillOnce( + SetBufferingState(&buffering_state_cb_, BUFFERING_HAVE_ENOUGH)); EXPECT_CALL(callbacks_, OnBufferingStateChange(BUFFERING_HAVE_NOTHING)); // We expect a successful seek callback followed by a buffering update. @@ -275,9 +269,8 @@ class PipelineTest : public ::testing::Test { } void DoSeek(const base::TimeDelta& seek_time) { - pipeline_->Seek(seek_time, - base::Bind(&CallbackHelper::OnSeek, - base::Unretained(&callbacks_))); + pipeline_->Seek(seek_time, base::Bind(&CallbackHelper::OnSeek, + base::Unretained(&callbacks_))); message_loop_.RunUntilIdle(); } @@ -337,11 +330,11 @@ class PipelineTest : public ::testing::Test { // After the Pipeline is stopped, it could be destroyed any time. Always // destroy the pipeline immediately after OnStop() to test this. EXPECT_CALL(callbacks_, OnStop()) - .WillOnce(Invoke(this, &PipelineTest::DestroyPipeline)); + .WillOnce(Invoke(this, &PipelineImplTest::DestroyPipeline)); } - MOCK_METHOD2(OnAddTextTrack, void(const TextTrackConfig&, - const AddTextTrackDoneCB&)); + MOCK_METHOD2(OnAddTextTrack, + void(const TextTrackConfig&, const AddTextTrackDoneCB&)); MOCK_METHOD0(OnWaitingForDecryptionKey, void(void)); void DoOnAddTextTrack(const TextTrackConfig& config, @@ -350,33 +343,47 @@ class PipelineTest : public ::testing::Test { done_cb.Run(std::move(text_track)); } + void RunBufferedTimeRangesTest(const base::TimeDelta duration) { + EXPECT_EQ(0u, pipeline_->GetBufferedTimeRanges().size()); + EXPECT_FALSE(pipeline_->DidLoadingProgress()); + Ranges<base::TimeDelta> ranges; + ranges.Add(base::TimeDelta(), duration); + pipeline_->OnBufferedTimeRangesChanged(ranges); + EXPECT_TRUE(pipeline_->DidLoadingProgress()); + EXPECT_FALSE(pipeline_->DidLoadingProgress()); + EXPECT_EQ(1u, pipeline_->GetBufferedTimeRanges().size()); + EXPECT_EQ(base::TimeDelta(), pipeline_->GetBufferedTimeRanges().start(0)); + EXPECT_EQ(duration, pipeline_->GetBufferedTimeRanges().end(0)); + } + // Fixture members. StrictMock<CallbackHelper> callbacks_; base::SimpleTestTickClock test_tick_clock_; base::MessageLoop message_loop_; - scoped_ptr<Pipeline> pipeline_; + scoped_ptr<PipelineImpl> pipeline_; - scoped_ptr<StrictMock<MockDemuxer> > demuxer_; - scoped_ptr<StrictMock<MockRenderer> > scoped_renderer_; + scoped_ptr<StrictMock<MockDemuxer>> demuxer_; + scoped_ptr<StrictMock<MockRenderer>> scoped_renderer_; StrictMock<MockRenderer>* renderer_; StrictMock<CallbackHelper> text_renderer_callbacks_; TextRenderer* text_renderer_; - scoped_ptr<StrictMock<MockDemuxerStream> > audio_stream_; - scoped_ptr<StrictMock<MockDemuxerStream> > video_stream_; + scoped_ptr<StrictMock<MockDemuxerStream>> audio_stream_; + scoped_ptr<StrictMock<MockDemuxerStream>> video_stream_; scoped_ptr<FakeTextTrackStream> text_stream_; BufferingStateCB buffering_state_cb_; base::Closure ended_cb_; + StatisticsCB statistics_cb_; VideoDecoderConfig video_decoder_config_; PipelineMetadata metadata_; base::TimeDelta start_time_; private: - DISALLOW_COPY_AND_ASSIGN(PipelineTest); + DISALLOW_COPY_AND_ASSIGN(PipelineImplTest); }; // Test that playback controls methods no-op when the pipeline hasn't been // started. -TEST_F(PipelineTest, NotStarted) { +TEST_F(PipelineImplTest, NotStarted) { const base::TimeDelta kZero; EXPECT_FALSE(pipeline_->IsRunning()); @@ -400,7 +407,7 @@ TEST_F(PipelineTest, NotStarted) { EXPECT_TRUE(kZero == pipeline_->GetMediaDuration()); } -TEST_F(PipelineTest, NeverInitializes) { +TEST_F(PipelineImplTest, NeverInitializes) { // Don't execute the callback passed into Initialize(). EXPECT_CALL(*demuxer_, Initialize(_, _, _)); @@ -417,14 +424,14 @@ TEST_F(PipelineTest, NeverInitializes) { EXPECT_CALL(callbacks_, OnStart(PIPELINE_OK)); } -TEST_F(PipelineTest, StopWithoutStart) { +TEST_F(PipelineImplTest, StopWithoutStart) { ExpectPipelineStopAndDestroyPipeline(); pipeline_->Stop( base::Bind(&CallbackHelper::OnStop, base::Unretained(&callbacks_))); message_loop_.RunUntilIdle(); } -TEST_F(PipelineTest, StartThenStopImmediately) { +TEST_F(PipelineImplTest, StartThenStopImmediately) { EXPECT_CALL(*demuxer_, Initialize(_, _, _)) .WillOnce(PostCallback<1>(PIPELINE_OK)); EXPECT_CALL(*demuxer_, Stop()); @@ -439,7 +446,7 @@ TEST_F(PipelineTest, StartThenStopImmediately) { message_loop_.RunUntilIdle(); } -TEST_F(PipelineTest, DemuxerErrorDuringStop) { +TEST_F(PipelineImplTest, DemuxerErrorDuringStop) { CreateAudioStream(); MockDemuxerStreamVector streams; streams.push_back(audio_stream()); @@ -450,7 +457,7 @@ TEST_F(PipelineTest, DemuxerErrorDuringStop) { StartPipelineAndExpect(PIPELINE_OK); EXPECT_CALL(*demuxer_, Stop()) - .WillOnce(InvokeWithoutArgs(this, &PipelineTest::OnDemuxerError)); + .WillOnce(InvokeWithoutArgs(this, &PipelineImplTest::OnDemuxerError)); ExpectPipelineStopAndDestroyPipeline(); pipeline_->Stop( @@ -458,15 +465,7 @@ TEST_F(PipelineTest, DemuxerErrorDuringStop) { message_loop_.RunUntilIdle(); } -TEST_F(PipelineTest, URLNotFound) { - EXPECT_CALL(*demuxer_, Initialize(_, _, _)) - .WillOnce(PostCallback<1>(PIPELINE_ERROR_URL_NOT_FOUND)); - EXPECT_CALL(*demuxer_, Stop()); - - StartPipelineAndExpect(PIPELINE_ERROR_URL_NOT_FOUND); -} - -TEST_F(PipelineTest, NoStreams) { +TEST_F(PipelineImplTest, NoStreams) { EXPECT_CALL(*demuxer_, Initialize(_, _, _)) .WillOnce(PostCallback<1>(PIPELINE_OK)); EXPECT_CALL(*demuxer_, Stop()); @@ -475,7 +474,7 @@ TEST_F(PipelineTest, NoStreams) { StartPipelineAndExpect(PIPELINE_ERROR_COULD_NOT_RENDER); } -TEST_F(PipelineTest, AudioStream) { +TEST_F(PipelineImplTest, AudioStream) { CreateAudioStream(); MockDemuxerStreamVector streams; streams.push_back(audio_stream()); @@ -488,7 +487,7 @@ TEST_F(PipelineTest, AudioStream) { EXPECT_FALSE(metadata_.has_video); } -TEST_F(PipelineTest, VideoStream) { +TEST_F(PipelineImplTest, VideoStream) { CreateVideoStream(); MockDemuxerStreamVector streams; streams.push_back(video_stream()); @@ -501,7 +500,7 @@ TEST_F(PipelineTest, VideoStream) { EXPECT_TRUE(metadata_.has_video); } -TEST_F(PipelineTest, AudioVideoStream) { +TEST_F(PipelineImplTest, AudioVideoStream) { CreateAudioStream(); CreateVideoStream(); MockDemuxerStreamVector streams; @@ -516,7 +515,7 @@ TEST_F(PipelineTest, AudioVideoStream) { EXPECT_TRUE(metadata_.has_video); } -TEST_F(PipelineTest, VideoTextStream) { +TEST_F(PipelineImplTest, VideoTextStream) { CreateVideoStream(); CreateTextStream(); MockDemuxerStreamVector streams; @@ -532,7 +531,7 @@ TEST_F(PipelineTest, VideoTextStream) { AddTextStream(); } -TEST_F(PipelineTest, VideoAudioTextStream) { +TEST_F(PipelineImplTest, VideoAudioTextStream) { CreateVideoStream(); CreateAudioStream(); CreateTextStream(); @@ -550,7 +549,7 @@ TEST_F(PipelineTest, VideoAudioTextStream) { AddTextStream(); } -TEST_F(PipelineTest, Seek) { +TEST_F(PipelineImplTest, Seek) { CreateAudioStream(); CreateVideoStream(); CreateTextStream(); @@ -570,7 +569,7 @@ TEST_F(PipelineTest, Seek) { DoSeek(expected); } -TEST_F(PipelineTest, SeekAfterError) { +TEST_F(PipelineImplTest, SeekAfterError) { CreateAudioStream(); MockDemuxerStreamVector streams; streams.push_back(audio_stream()); @@ -594,7 +593,7 @@ TEST_F(PipelineTest, SeekAfterError) { message_loop_.RunUntilIdle(); } -TEST_F(PipelineTest, SuspendResume) { +TEST_F(PipelineImplTest, SuspendResume) { CreateAudioStream(); CreateVideoStream(); CreateTextStream(); @@ -607,15 +606,28 @@ TEST_F(PipelineTest, SuspendResume) { StartPipelineAndExpect(PIPELINE_OK); + // Inject some fake memory usage to verify its cleared after suspend. + PipelineStatistics stats; + stats.audio_memory_usage = 12345; + stats.video_memory_usage = 67890; + statistics_cb_.Run(stats); + EXPECT_EQ(stats.audio_memory_usage, + pipeline_->GetStatistics().audio_memory_usage); + EXPECT_EQ(stats.video_memory_usage, + pipeline_->GetStatistics().video_memory_usage); + ExpectSuspend(); DoSuspend(); + EXPECT_EQ(pipeline_->GetStatistics().audio_memory_usage, 0); + EXPECT_EQ(pipeline_->GetStatistics().video_memory_usage, 0); + base::TimeDelta expected = base::TimeDelta::FromSeconds(2000); ExpectResume(expected); DoResume(expected); } -TEST_F(PipelineTest, SetVolume) { +TEST_F(PipelineImplTest, SetVolume) { CreateAudioStream(); MockDemuxerStreamVector streams; streams.push_back(audio_stream()); @@ -632,7 +644,7 @@ TEST_F(PipelineTest, SetVolume) { pipeline_->SetVolume(expected); } -TEST_F(PipelineTest, Properties) { +TEST_F(PipelineImplTest, Properties) { CreateVideoStream(); MockDemuxerStreamVector streams; streams.push_back(video_stream()); @@ -647,7 +659,7 @@ TEST_F(PipelineTest, Properties) { EXPECT_FALSE(pipeline_->DidLoadingProgress()); } -TEST_F(PipelineTest, GetBufferedTimeRanges) { +TEST_F(PipelineImplTest, GetBufferedTimeRanges) { CreateVideoStream(); MockDemuxerStreamVector streams; streams.push_back(video_stream()); @@ -657,18 +669,7 @@ TEST_F(PipelineTest, GetBufferedTimeRanges) { SetRendererExpectations(); StartPipelineAndExpect(PIPELINE_OK); - - EXPECT_EQ(0u, pipeline_->GetBufferedTimeRanges().size()); - - EXPECT_FALSE(pipeline_->DidLoadingProgress()); - Ranges<base::TimeDelta> ranges; - ranges.Add(base::TimeDelta(), kDuration / 8); - pipeline_->OnBufferedTimeRangesChanged(ranges); - EXPECT_TRUE(pipeline_->DidLoadingProgress()); - EXPECT_FALSE(pipeline_->DidLoadingProgress()); - EXPECT_EQ(1u, pipeline_->GetBufferedTimeRanges().size()); - EXPECT_EQ(base::TimeDelta(), pipeline_->GetBufferedTimeRanges().start(0)); - EXPECT_EQ(kDuration / 8, pipeline_->GetBufferedTimeRanges().end(0)); + RunBufferedTimeRangesTest(kDuration / 8); base::TimeDelta kSeekTime = kDuration / 2; ExpectSeek(kSeekTime, false); @@ -677,7 +678,24 @@ TEST_F(PipelineTest, GetBufferedTimeRanges) { EXPECT_FALSE(pipeline_->DidLoadingProgress()); } -TEST_F(PipelineTest, EndedCallback) { +TEST_F(PipelineImplTest, BufferedTimeRangesCanChangeAfterStop) { + EXPECT_CALL(*demuxer_, Initialize(_, _, _)) + .WillOnce(PostCallback<1>(PIPELINE_OK)); + EXPECT_CALL(*demuxer_, Stop()); + + EXPECT_CALL(callbacks_, OnStart(_)); + StartPipeline(); + + EXPECT_CALL(callbacks_, OnStop()); + pipeline_->Stop( + base::Bind(&CallbackHelper::OnStop, base::Unretained(&callbacks_))); + message_loop_.RunUntilIdle(); + + RunBufferedTimeRangesTest(base::TimeDelta::FromSeconds(5)); + DestroyPipeline(); +} + +TEST_F(PipelineImplTest, EndedCallback) { CreateAudioStream(); CreateVideoStream(); CreateTextStream(); @@ -700,7 +718,7 @@ TEST_F(PipelineTest, EndedCallback) { message_loop_.RunUntilIdle(); } -TEST_F(PipelineTest, ErrorDuringSeek) { +TEST_F(PipelineImplTest, ErrorDuringSeek) { CreateAudioStream(); MockDemuxerStreamVector streams; streams.push_back(audio_stream()); @@ -718,9 +736,9 @@ TEST_F(PipelineTest, ErrorDuringSeek) { EXPECT_CALL(callbacks_, OnBufferingStateChange(BUFFERING_HAVE_NOTHING)); EXPECT_CALL(*renderer_, Flush(_)) - .WillOnce(DoAll(SetBufferingState(&buffering_state_cb_, - BUFFERING_HAVE_NOTHING), - RunClosure<0>())); + .WillOnce( + DoAll(SetBufferingState(&buffering_state_cb_, BUFFERING_HAVE_NOTHING), + RunClosure<0>())); EXPECT_CALL(*demuxer_, Seek(seek_time, _)) .WillOnce(RunCallback<1>(PIPELINE_ERROR_READ)); @@ -734,9 +752,9 @@ TEST_F(PipelineTest, ErrorDuringSeek) { // Invoked function OnError. This asserts that the pipeline does not enqueue // non-teardown related tasks while tearing down. -static void TestNoCallsAfterError( - Pipeline* pipeline, base::MessageLoop* message_loop, - PipelineStatus /* status */) { +static void TestNoCallsAfterError(PipelineImpl* pipeline, + base::MessageLoop* message_loop, + PipelineStatus /* status */) { CHECK(pipeline); CHECK(message_loop); @@ -751,7 +769,7 @@ static void TestNoCallsAfterError( EXPECT_TRUE(message_loop->IsIdleForTesting()); } -TEST_F(PipelineTest, NoMessageDuringTearDownFromError) { +TEST_F(PipelineImplTest, NoMessageDuringTearDownFromError) { CreateAudioStream(); MockDemuxerStreamVector streams; streams.push_back(audio_stream()); @@ -761,8 +779,8 @@ TEST_F(PipelineTest, NoMessageDuringTearDownFromError) { StartPipelineAndExpect(PIPELINE_OK); // Trigger additional requests on the pipeline during tear down from error. - base::Callback<void(PipelineStatus)> cb = base::Bind( - &TestNoCallsAfterError, pipeline_.get(), &message_loop_); + base::Callback<void(PipelineStatus)> cb = + base::Bind(&TestNoCallsAfterError, pipeline_.get(), &message_loop_); ON_CALL(callbacks_, OnError(_)) .WillByDefault(Invoke(&cb, &base::Callback<void(PipelineStatus)>::Run)); @@ -770,9 +788,9 @@ TEST_F(PipelineTest, NoMessageDuringTearDownFromError) { // Seek() isn't called as the demuxer errors out first. EXPECT_CALL(*renderer_, Flush(_)) - .WillOnce(DoAll(SetBufferingState(&buffering_state_cb_, - BUFFERING_HAVE_NOTHING), - RunClosure<0>())); + .WillOnce( + DoAll(SetBufferingState(&buffering_state_cb_, BUFFERING_HAVE_NOTHING), + RunClosure<0>())); EXPECT_CALL(callbacks_, OnBufferingStateChange(BUFFERING_HAVE_NOTHING)); EXPECT_CALL(*demuxer_, Seek(seek_time, _)) @@ -785,7 +803,7 @@ TEST_F(PipelineTest, NoMessageDuringTearDownFromError) { message_loop_.RunUntilIdle(); } -TEST_F(PipelineTest, DestroyAfterStop) { +TEST_F(PipelineImplTest, DestroyAfterStop) { CreateAudioStream(); MockDemuxerStreamVector streams; streams.push_back(audio_stream()); @@ -801,7 +819,7 @@ TEST_F(PipelineTest, DestroyAfterStop) { message_loop_.RunUntilIdle(); } -TEST_F(PipelineTest, Underflow) { +TEST_F(PipelineImplTest, Underflow) { CreateAudioStream(); CreateVideoStream(); MockDemuxerStreamVector streams; @@ -822,7 +840,7 @@ TEST_F(PipelineTest, Underflow) { DoSeek(expected); } -TEST_F(PipelineTest, PositiveStartTime) { +TEST_F(PipelineImplTest, PositiveStartTime) { start_time_ = base::TimeDelta::FromSeconds(1); EXPECT_CALL(*demuxer_, GetStartTime()).WillRepeatedly(Return(start_time_)); CreateAudioStream(); @@ -838,7 +856,7 @@ TEST_F(PipelineTest, PositiveStartTime) { message_loop_.RunUntilIdle(); } -class PipelineTeardownTest : public PipelineTest { +class PipelineTeardownTest : public PipelineImplTest { public: enum TeardownState { kInitDemuxer, @@ -875,7 +893,7 @@ class PipelineTeardownTest : public PipelineTest { case kPlaying: DoInitialize(state, stop_or_error); - DoStopOrError(stop_or_error); + DoStopOrError(stop_or_error, true); break; case kSuspending: @@ -903,8 +921,8 @@ class PipelineTeardownTest : public PipelineTest { PipelineStatus SetInitializeExpectations(TeardownState state, StopOrError stop_or_error) { PipelineStatus status = PIPELINE_OK; - base::Closure stop_cb = base::Bind( - &CallbackHelper::OnStop, base::Unretained(&callbacks_)); + base::Closure stop_cb = + base::Bind(&CallbackHelper::OnStop, base::Unretained(&callbacks_)); if (state == kInitDemuxer) { if (stop_or_error == kStop) { @@ -959,8 +977,8 @@ class PipelineTeardownTest : public PipelineTest { EXPECT_CALL(*renderer_, SetPlaybackRate(0.0)); EXPECT_CALL(*renderer_, SetVolume(1.0f)); EXPECT_CALL(*renderer_, StartPlayingFrom(base::TimeDelta())) - .WillOnce(SetBufferingState(&buffering_state_cb_, - BUFFERING_HAVE_ENOUGH)); + .WillOnce( + SetBufferingState(&buffering_state_cb_, BUFFERING_HAVE_ENOUGH)); if (status == PIPELINE_OK) EXPECT_CALL(callbacks_, OnBufferingStateChange(BUFFERING_HAVE_ENOUGH)); @@ -979,32 +997,33 @@ class PipelineTeardownTest : public PipelineTest { ExpectPipelineStopAndDestroyPipeline(); } - pipeline_->Seek(base::TimeDelta::FromSeconds(10), base::Bind( - &CallbackHelper::OnSeek, base::Unretained(&callbacks_))); + pipeline_->Seek( + base::TimeDelta::FromSeconds(10), + base::Bind(&CallbackHelper::OnSeek, base::Unretained(&callbacks_))); message_loop_.RunUntilIdle(); } PipelineStatus SetSeekExpectations(TeardownState state, StopOrError stop_or_error) { PipelineStatus status = PIPELINE_OK; - base::Closure stop_cb = base::Bind( - &CallbackHelper::OnStop, base::Unretained(&callbacks_)); + base::Closure stop_cb = + base::Bind(&CallbackHelper::OnStop, base::Unretained(&callbacks_)); if (state == kFlushing) { if (stop_or_error == kStop) { EXPECT_CALL(*renderer_, Flush(_)) - .WillOnce(DoAll(Stop(pipeline_.get(), stop_cb), - SetBufferingState(&buffering_state_cb_, - BUFFERING_HAVE_NOTHING), - RunClosure<0>())); + .WillOnce(DoAll( + Stop(pipeline_.get(), stop_cb), + SetBufferingState(&buffering_state_cb_, BUFFERING_HAVE_NOTHING), + RunClosure<0>())); EXPECT_CALL(callbacks_, OnBufferingStateChange(BUFFERING_HAVE_NOTHING)); } else { status = PIPELINE_ERROR_READ; EXPECT_CALL(*renderer_, Flush(_)) - .WillOnce(DoAll(SetError(pipeline_.get(), status), - SetBufferingState(&buffering_state_cb_, - BUFFERING_HAVE_NOTHING), - RunClosure<0>())); + .WillOnce(DoAll( + SetError(pipeline_.get(), status), + SetBufferingState(&buffering_state_cb_, BUFFERING_HAVE_NOTHING), + RunClosure<0>())); EXPECT_CALL(callbacks_, OnBufferingStateChange(BUFFERING_HAVE_NOTHING)); } @@ -1012,9 +1031,9 @@ class PipelineTeardownTest : public PipelineTest { } EXPECT_CALL(*renderer_, Flush(_)) - .WillOnce(DoAll(SetBufferingState(&buffering_state_cb_, - BUFFERING_HAVE_NOTHING), - RunClosure<0>())); + .WillOnce(DoAll( + SetBufferingState(&buffering_state_cb_, BUFFERING_HAVE_NOTHING), + RunClosure<0>())); EXPECT_CALL(callbacks_, OnBufferingStateChange(BUFFERING_HAVE_NOTHING)); if (state == kSeeking) { @@ -1024,8 +1043,7 @@ class PipelineTeardownTest : public PipelineTest { RunCallback<1>(PIPELINE_OK))); } else { status = PIPELINE_ERROR_READ; - EXPECT_CALL(*demuxer_, Seek(_, _)) - .WillOnce(RunCallback<1>(status)); + EXPECT_CALL(*demuxer_, Seek(_, _)).WillOnce(RunCallback<1>(status)); } return status; @@ -1038,21 +1056,22 @@ class PipelineTeardownTest : public PipelineTest { void DoSuspend(TeardownState state, StopOrError stop_or_error) { PipelineStatus status = SetSuspendExpectations(state, stop_or_error); - if (state != kSuspended) { - // DoStopOrError() handles these for kSuspended. + if (state == kResuming) { EXPECT_CALL(*demuxer_, Stop()); - if (status == PIPELINE_OK) { + if (status == PIPELINE_OK) ExpectPipelineStopAndDestroyPipeline(); - } } - PipelineTest::DoSuspend(); + PipelineImplTest::DoSuspend(); - if (state == kSuspended) { - DoStopOrError(stop_or_error); - } else if (state == kResuming) { - PipelineTest::DoResume(base::TimeDelta()); + if (state == kResuming) { + PipelineImplTest::DoResume(base::TimeDelta()); + return; } + + // kSuspended, kSuspending never throw errors, since Resume() is always able + // to restore the pipeline to a pristine state. + DoStopOrError(stop_or_error, false); } PipelineStatus SetSuspendExpectations(TeardownState state, @@ -1062,72 +1081,58 @@ class PipelineTeardownTest : public PipelineTest { base::Bind(&CallbackHelper::OnStop, base::Unretained(&callbacks_)); EXPECT_CALL(*renderer_, SetPlaybackRate(0)); - if (state == kSuspended || state == kResuming) { - EXPECT_CALL(*renderer_, Flush(_)) - .WillOnce(DoAll( - SetBufferingState(&buffering_state_cb_, BUFFERING_HAVE_NOTHING), - RunClosure<0>())); - EXPECT_CALL(callbacks_, OnBufferingStateChange(BUFFERING_HAVE_NOTHING)); - EXPECT_CALL(callbacks_, OnSuspend(PIPELINE_OK)); - if (state == kResuming) { - if (stop_or_error == kStop) { - EXPECT_CALL(*demuxer_, Seek(_, _)) - .WillOnce(DoAll(Stop(pipeline_.get(), stop_cb), - RunCallback<1>(PIPELINE_OK))); - EXPECT_CALL(callbacks_, OnResume(PIPELINE_OK)); - } else { - status = PIPELINE_ERROR_READ; - EXPECT_CALL(*demuxer_, Seek(_, _)).WillOnce(RunCallback<1>(status)); - EXPECT_CALL(callbacks_, OnResume(status)); - } - } - return status; - } else if (state == kSuspending) { + EXPECT_CALL(callbacks_, OnBufferingStateChange(BUFFERING_HAVE_NOTHING)); + EXPECT_CALL(callbacks_, OnSuspend(PIPELINE_OK)); + EXPECT_CALL(*renderer_, Flush(_)) + .WillOnce(DoAll( + SetBufferingState(&buffering_state_cb_, BUFFERING_HAVE_NOTHING), + RunClosure<0>())); + if (state == kResuming) { if (stop_or_error == kStop) { - EXPECT_CALL(*renderer_, Flush(_)) - .WillOnce(DoAll( - Stop(pipeline_.get(), stop_cb), - SetBufferingState(&buffering_state_cb_, BUFFERING_HAVE_NOTHING), - RunClosure<0>())); - EXPECT_CALL(callbacks_, OnBufferingStateChange(BUFFERING_HAVE_NOTHING)); - EXPECT_CALL(callbacks_, OnSuspend(PIPELINE_OK)); + EXPECT_CALL(*demuxer_, Seek(_, _)) + .WillOnce(DoAll(Stop(pipeline_.get(), stop_cb), + RunCallback<1>(PIPELINE_OK))); + EXPECT_CALL(callbacks_, OnResume(PIPELINE_OK)); } else { status = PIPELINE_ERROR_READ; - EXPECT_CALL(*renderer_, Flush(_)) - .WillOnce(SetError(pipeline_.get(), status)); - EXPECT_CALL(callbacks_, OnSuspend(status)); + EXPECT_CALL(*demuxer_, Seek(_, _)).WillOnce(RunCallback<1>(status)); + EXPECT_CALL(callbacks_, OnResume(status)); } - return status; + } else if (state != kSuspended && state != kSuspending) { + NOTREACHED() << "State not supported: " << state; } - NOTREACHED() << "State not supported: " << state; return status; } - void DoStopOrError(StopOrError stop_or_error) { + void DoStopOrError(StopOrError stop_or_error, bool expect_errors) { InSequence s; - EXPECT_CALL(*demuxer_, Stop()); - switch (stop_or_error) { case kStop: + EXPECT_CALL(*demuxer_, Stop()); ExpectPipelineStopAndDestroyPipeline(); - pipeline_->Stop(base::Bind( - &CallbackHelper::OnStop, base::Unretained(&callbacks_))); + pipeline_->Stop( + base::Bind(&CallbackHelper::OnStop, base::Unretained(&callbacks_))); break; case kError: - EXPECT_CALL(callbacks_, OnError(PIPELINE_ERROR_READ)); + if (expect_errors) { + EXPECT_CALL(*demuxer_, Stop()); + EXPECT_CALL(callbacks_, OnError(PIPELINE_ERROR_READ)); + } pipeline_->SetErrorForTesting(PIPELINE_ERROR_READ); break; case kErrorAndStop: - EXPECT_CALL(callbacks_, OnError(PIPELINE_ERROR_READ)); + EXPECT_CALL(*demuxer_, Stop()); + if (expect_errors) + EXPECT_CALL(callbacks_, OnError(PIPELINE_ERROR_READ)); ExpectPipelineStopAndDestroyPipeline(); pipeline_->SetErrorForTesting(PIPELINE_ERROR_READ); message_loop_.RunUntilIdle(); - pipeline_->Stop(base::Bind( - &CallbackHelper::OnStop, base::Unretained(&callbacks_))); + pipeline_->Stop( + base::Bind(&CallbackHelper::OnStop, base::Unretained(&callbacks_))); break; } @@ -1137,10 +1142,10 @@ class PipelineTeardownTest : public PipelineTest { DISALLOW_COPY_AND_ASSIGN(PipelineTeardownTest); }; -#define INSTANTIATE_TEARDOWN_TEST(stop_or_error, state) \ - TEST_F(PipelineTeardownTest, stop_or_error##_##state) { \ - RunTest(k##state, k##stop_or_error); \ - } +#define INSTANTIATE_TEARDOWN_TEST(stop_or_error, state) \ + TEST_F(PipelineTeardownTest, stop_or_error##_##state) { \ + RunTest(k##state, k##stop_or_error); \ + } INSTANTIATE_TEARDOWN_TEST(Stop, InitDemuxer); INSTANTIATE_TEARDOWN_TEST(Stop, InitRenderer); diff --git a/chromium/media/base/pipeline_status.h b/chromium/media/base/pipeline_status.h index 32e163c913b..2f4b35945f6 100644 --- a/chromium/media/base/pipeline_status.h +++ b/chromium/media/base/pipeline_status.h @@ -15,10 +15,9 @@ namespace media { // Status states for pipeline. All codes except PIPELINE_OK indicate errors. // Logged to UMA, so never reuse a value, always add new/greater ones! -// TODO(vrk/scherkus): Trim the unused status codes. (crbug.com/126070) enum PipelineStatus { PIPELINE_OK = 0, - PIPELINE_ERROR_URL_NOT_FOUND = 1, + // Deprecated: PIPELINE_ERROR_URL_NOT_FOUND = 1, PIPELINE_ERROR_NETWORK = 2, PIPELINE_ERROR_DECODE = 3, // Deprecated: PIPELINE_ERROR_DECRYPT = 4, @@ -26,23 +25,35 @@ enum PipelineStatus { PIPELINE_ERROR_INITIALIZATION_FAILED = 6, PIPELINE_ERROR_COULD_NOT_RENDER = 8, PIPELINE_ERROR_READ = 9, - PIPELINE_ERROR_OPERATION_PENDING = 10, + // Deprecated: PIPELINE_ERROR_OPERATION_PENDING = 10, PIPELINE_ERROR_INVALID_STATE = 11, + // Demuxer related errors. DEMUXER_ERROR_COULD_NOT_OPEN = 12, DEMUXER_ERROR_COULD_NOT_PARSE = 13, DEMUXER_ERROR_NO_SUPPORTED_STREAMS = 14, + // Decoder related errors. DECODER_ERROR_NOT_SUPPORTED = 15, + + // ChunkDemuxer related errors. + CHUNK_DEMUXER_ERROR_APPEND_FAILED = 16, + CHUNK_DEMUXER_ERROR_EOS_STATUS_DECODE_ERROR = 17, + CHUNK_DEMUXER_ERROR_EOS_STATUS_NETWORK_ERROR = 18, + + // Audio rendering errors. + AUDIO_RENDERER_ERROR = 19, + AUDIO_RENDERER_ERROR_SPLICE_FAILED = 20, + // Must be equal to the largest value ever logged. - PIPELINE_STATUS_MAX = DECODER_ERROR_NOT_SUPPORTED, + PIPELINE_STATUS_MAX = AUDIO_RENDERER_ERROR_SPLICE_FAILED, }; typedef base::Callback<void(PipelineStatus)> PipelineStatusCB; struct PipelineStatistics { - uint64_t audio_bytes_decoded = 0; // Should be uint64_t? - uint32_t video_bytes_decoded = 0; // Should be uint64_t? + uint64_t audio_bytes_decoded = 0; + uint64_t video_bytes_decoded = 0; uint32_t video_frames_decoded = 0; uint32_t video_frames_dropped = 0; int64_t audio_memory_usage = 0; diff --git a/chromium/media/base/renderer_factory.h b/chromium/media/base/renderer_factory.h index 4b35f01764b..65c5cbee693 100644 --- a/chromium/media/base/renderer_factory.h +++ b/chromium/media/base/renderer_factory.h @@ -10,6 +10,7 @@ #include "base/memory/scoped_ptr.h" #include "media/base/media_export.h" #include "media/base/renderer.h" +#include "media/base/surface_manager.h" namespace base { class SingleThreadTaskRunner; @@ -36,7 +37,8 @@ class MEDIA_EXPORT RendererFactory { const scoped_refptr<base::SingleThreadTaskRunner>& media_task_runner, const scoped_refptr<base::TaskRunner>& worker_task_runner, AudioRendererSink* audio_renderer_sink, - VideoRendererSink* video_renderer_sink) = 0; + VideoRendererSink* video_renderer_sink, + const RequestSurfaceCB& request_surface_cb) = 0; private: DISALLOW_COPY_AND_ASSIGN(RendererFactory); diff --git a/chromium/media/base/run_all_unittests.cc b/chromium/media/base/run_all_unittests.cc index 97e799b0317..c84e24d7c06 100644 --- a/chromium/media/base/run_all_unittests.cc +++ b/chromium/media/base/run_all_unittests.cc @@ -14,7 +14,9 @@ #if defined(OS_ANDROID) #include "base/android/jni_android.h" +#include "media/base/android/media_codec_util.h" #include "media/base/android/media_jni_registrar.h" +#include "media/capture/video/android/capture_jni_registrar.h" #include "ui/gl/android/gl_jni_registrar.h" #endif @@ -43,6 +45,10 @@ void TestSuiteNoAtExit::Initialize() { // Needed for surface texture support. ui::gl::android::RegisterJni(env); media::RegisterJni(env); + media::RegisterCaptureJni(env); + + if (media::MediaCodecUtil::IsMediaCodecAvailable()) + media::EnablePlatformDecoderSupport(); #endif // Run this here instead of main() to ensure an AtExitManager is already diff --git a/chromium/media/base/serial_runner.cc b/chromium/media/base/serial_runner.cc index 2e085e35820..6cb89cbeaba 100644 --- a/chromium/media/base/serial_runner.cc +++ b/chromium/media/base/serial_runner.cc @@ -40,6 +40,7 @@ static void RunOnTaskRunner( } SerialRunner::Queue::Queue() {} +SerialRunner::Queue::Queue(const Queue& other) = default; SerialRunner::Queue::~Queue() {} void SerialRunner::Queue::Push(const base::Closure& closure) { diff --git a/chromium/media/base/serial_runner.h b/chromium/media/base/serial_runner.h index 4847e4c4c30..3adfd64f730 100644 --- a/chromium/media/base/serial_runner.h +++ b/chromium/media/base/serial_runner.h @@ -34,6 +34,7 @@ class MEDIA_EXPORT SerialRunner { class MEDIA_EXPORT Queue { public: Queue(); + Queue(const Queue& other); ~Queue(); void Push(const base::Closure& closure); diff --git a/chromium/media/base/stream_parser.cc b/chromium/media/base/stream_parser.cc index f63644a7755..9f3119b33b0 100644 --- a/chromium/media/base/stream_parser.cc +++ b/chromium/media/base/stream_parser.cc @@ -11,8 +11,10 @@ namespace media { StreamParser::InitParameters::InitParameters(base::TimeDelta duration) : duration(duration), auto_update_timestamp_offset(false), - liveness(DemuxerStream::LIVENESS_UNKNOWN) { -} + liveness(DemuxerStream::LIVENESS_UNKNOWN), + detected_audio_track_count(0), + detected_video_track_count(0), + detected_text_track_count(0) {} StreamParser::StreamParser() {} diff --git a/chromium/media/base/stream_parser.h b/chromium/media/base/stream_parser.h index 27ebc3f0a59..fed228c50c2 100644 --- a/chromium/media/base/stream_parser.h +++ b/chromium/media/base/stream_parser.h @@ -25,10 +25,9 @@ namespace media { -class AudioDecoderConfig; +class MediaTracks; class StreamParserBuffer; class TextTrackConfig; -class VideoDecoderConfig; // Abstract interface for parsing media byte streams. class MEDIA_EXPORT StreamParser { @@ -53,7 +52,7 @@ class MEDIA_EXPORT StreamParser { typedef std::map<TrackId, const BufferQueue> TextBufferQueueMap; // Stream parameters passed in InitCB. - struct InitParameters { + struct MEDIA_EXPORT InitParameters { InitParameters(base::TimeDelta duration); // Stream duration. @@ -69,6 +68,12 @@ class MEDIA_EXPORT StreamParser { // Indicates live stream. DemuxerStream::Liveness liveness; + + // Counts of tracks detected by type within this stream. Not all of these + // tracks may be selected for use by the parser. + int detected_audio_track_count; + int detected_video_track_count; + int detected_text_track_count; }; // Indicates completion of parser initialization. @@ -76,18 +81,17 @@ class MEDIA_EXPORT StreamParser { typedef base::Callback<void(const InitParameters& params)> InitCB; // Indicates when new stream configurations have been parsed. - // First parameter - The new audio configuration. If the config is not valid - // then it means that there isn't an audio stream. - // Second parameter - The new video configuration. If the config is not valid - // then it means that there isn't an audio stream. - // Third parameter - The new text tracks configuration. If the map is empty, - // then no text tracks were parsed from the stream. + // First parameter - An object containing information about media tracks as + // well as audio/video decoder configs associated with each + // track the parser will use from the stream. + // Second parameter - The new text tracks configuration. If the map is empty, + // then no text tracks were parsed for use from the stream. // Return value - True if the new configurations are accepted. // False if the new configurations are not supported // and indicates that a parsing error should be signalled. - typedef base::Callback<bool(const AudioDecoderConfig&, - const VideoDecoderConfig&, - const TextTrackConfigMap&)> NewConfigCB; + typedef base::Callback<bool(scoped_ptr<MediaTracks>, + const TextTrackConfigMap&)> + NewConfigCB; // New stream buffers have been parsed. // First parameter - A queue of newly parsed audio buffers. @@ -106,6 +110,9 @@ class MEDIA_EXPORT StreamParser { // Signals the beginning of a new media segment. typedef base::Callback<void()> NewMediaSegmentCB; + // Signals the end of a media segment. + typedef base::Callback<void()> EndMediaSegmentCB; + // A new potentially encrypted stream has been parsed. // First parameter - The type of the initialization data associated with the // stream. @@ -128,12 +135,13 @@ class MEDIA_EXPORT StreamParser { bool ignore_text_track, const EncryptedMediaInitDataCB& encrypted_media_init_data_cb, const NewMediaSegmentCB& new_segment_cb, - const base::Closure& end_of_segment_cb, + const EndMediaSegmentCB& end_of_segment_cb, const scoped_refptr<MediaLog>& media_log) = 0; - // Called when a seek occurs. This flushes the current parser state - // and puts the parser in a state where it can receive data for the new seek - // point. + // Called during the reset parser state algorithm. This flushes the current + // parser and puts the parser in a state where it can receive data. This + // method does not need to invoke the EndMediaSegmentCB since the parser reset + // algorithm already resets the segment parsing state. virtual void Flush() = 0; // Called when there is new data to parse. diff --git a/chromium/media/base/surface_manager.h b/chromium/media/base/surface_manager.h new file mode 100644 index 00000000000..84df7c136d6 --- /dev/null +++ b/chromium/media/base/surface_manager.h @@ -0,0 +1,45 @@ +// Copyright 2016 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_BASE_SURFACE_MANAGER_H_ +#define MEDIA_BASE_SURFACE_MANAGER_H_ + +#include "base/callback.h" +#include "base/macros.h" +#include "media/base/media_export.h" +#include "ui/gfx/geometry/size.h" + +namespace media { + +using SurfaceCreatedCB = base::Callback<void(int)>; +using RequestSurfaceCB = base::Callback<void(const SurfaceCreatedCB&)>; + +class MEDIA_EXPORT SurfaceManager { + public: + enum { kNoSurfaceID = -1 }; + + SurfaceManager() {} + virtual ~SurfaceManager() {} + + // Create a fullscreen surface. The id will be returned with + // |surface_created_cb|. If this is called more than once before the first + // |surface_created_cb| is called, the surface will be delivered to the last + // caller. If this is called after the fullscreen surface is created, the + // existing surface will be returned. The client should ensure that the + // previous consumer is no longer using the surface. + virtual void CreateFullscreenSurface( + const gfx::Size& video_natural_size, + const SurfaceCreatedCB& surface_created_cb) = 0; + + // Call this when the natural size of the fullscreen video changes. The + // surface will be resized to match the aspect ratio. + virtual void NaturalSizeChanged(const gfx::Size& size) = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(SurfaceManager); +}; + +} // namespace media + +#endif // MEDIA_BASE_SURFACE_MANAGER_H_ diff --git a/chromium/media/base/test_helpers.cc b/chromium/media/base/test_helpers.cc index 4b2128296b1..0ab19032f52 100644 --- a/chromium/media/base/test_helpers.cc +++ b/chromium/media/base/test_helpers.cc @@ -129,43 +129,57 @@ static VideoDecoderConfig GetTestConfig(VideoCodec codec, gfx::Rect visible_rect(coded_size.width(), coded_size.height()); gfx::Size natural_size = coded_size; - return VideoDecoderConfig(codec, VIDEO_CODEC_PROFILE_UNKNOWN, - PIXEL_FORMAT_YV12, COLOR_SPACE_UNSPECIFIED, - coded_size, visible_rect, natural_size, - EmptyExtraData(), is_encrypted); + return VideoDecoderConfig( + codec, VIDEO_CODEC_PROFILE_UNKNOWN, PIXEL_FORMAT_YV12, + COLOR_SPACE_UNSPECIFIED, coded_size, visible_rect, natural_size, + EmptyExtraData(), + is_encrypted ? AesCtrEncryptionScheme() : Unencrypted()); } static const gfx::Size kNormalSize(320, 240); static const gfx::Size kLargeSize(640, 480); +// static VideoDecoderConfig TestVideoConfig::Invalid() { return GetTestConfig(kUnknownVideoCodec, kNormalSize, false); } +// static VideoDecoderConfig TestVideoConfig::Normal() { return GetTestConfig(kCodecVP8, kNormalSize, false); } +// static VideoDecoderConfig TestVideoConfig::NormalEncrypted() { return GetTestConfig(kCodecVP8, kNormalSize, true); } +// static VideoDecoderConfig TestVideoConfig::Large() { return GetTestConfig(kCodecVP8, kLargeSize, false); } +// static VideoDecoderConfig TestVideoConfig::LargeEncrypted() { return GetTestConfig(kCodecVP8, kLargeSize, true); } +// static gfx::Size TestVideoConfig::NormalCodedSize() { return kNormalSize; } +// static gfx::Size TestVideoConfig::LargeCodedSize() { return kLargeSize; } +// static +AudioParameters TestAudioParameters::Normal() { + return AudioParameters(AudioParameters::AUDIO_PCM_LOW_LATENCY, + CHANNEL_LAYOUT_STEREO, 48000, 16, 2048); +} + template <class T> scoped_refptr<AudioBuffer> MakeAudioBuffer(SampleFormat format, ChannelLayout channel_layout, @@ -262,21 +276,4 @@ bool VerifyFakeVideoBufferForTest( height == config.coded_size().height()); } -CallbackPairChecker::CallbackPairChecker() : expecting_b_(false) { -} - -CallbackPairChecker::~CallbackPairChecker() { - EXPECT_FALSE(expecting_b_); -} - -void CallbackPairChecker::RecordACalled() { - EXPECT_FALSE(expecting_b_); - expecting_b_ = true; -} - -void CallbackPairChecker::RecordBCalled() { - EXPECT_TRUE(expecting_b_); - expecting_b_ = false; -} - } // namespace media diff --git a/chromium/media/base/test_helpers.h b/chromium/media/base/test_helpers.h index 214526439f3..d14d83c1983 100644 --- a/chromium/media/base/test_helpers.h +++ b/chromium/media/base/test_helpers.h @@ -9,6 +9,7 @@ #include "base/callback.h" #include "base/macros.h" +#include "media/audio/audio_parameters.h" #include "media/base/channel_layout.h" #include "media/base/media_log.h" #include "media/base/pipeline_status.h" @@ -90,7 +91,16 @@ class TestVideoConfig { static gfx::Size LargeCodedSize(); private: - DISALLOW_IMPLICIT_CONSTRUCTORS(TestVideoConfig); + DISALLOW_COPY_AND_ASSIGN(TestVideoConfig); +}; + +// Provides pre-canned AudioParameters objects. +class TestAudioParameters { + public: + static AudioParameters Normal(); + + private: + DISALLOW_COPY_AND_ASSIGN(TestAudioParameters); }; // Create an AudioBuffer containing |frames| frames of data, where each sample @@ -133,19 +143,6 @@ scoped_refptr<DecoderBuffer> CreateFakeVideoBufferForTest( bool VerifyFakeVideoBufferForTest(const scoped_refptr<DecoderBuffer>& buffer, const VideoDecoderConfig& config); -// Used to verify that the each call to A() is followed by a call to B(), -// before the next call to A(). There may be any number of pairs (including 0). -class CallbackPairChecker { - public: - CallbackPairChecker(); - ~CallbackPairChecker(); - void RecordACalled(); - void RecordBCalled(); - - private: - bool expecting_b_; -}; - } // namespace media #endif // MEDIA_BASE_TEST_HELPERS_H_ diff --git a/chromium/media/base/test_random.h b/chromium/media/base/test_random.h new file mode 100644 index 00000000000..ba081cb57a0 --- /dev/null +++ b/chromium/media/base/test_random.h @@ -0,0 +1,45 @@ +// 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_BLINK_TEST_RANDOM_H_ +#define MEDIA_BLINK_TEST_RANDOM_H_ + +#include <stdint.h> + +#include "base/logging.h" + +// Vastly simplified ACM random class meant to only be used for testing. +// This class is meant to generate predictable sequences of pseudorandom +// numbers, unlike the classes in base/rand_util.h which are meant to generate +// unpredictable sequences. +// See +// https://code.google.com/p/szl/source/browse/trunk/src/utilities/acmrandom.h +// for more information. + +namespace media { + +class TestRandom { + public: + explicit TestRandom(uint32_t seed) { + seed_ = seed & 0x7fffffff; // make this a non-negative number + if (seed_ == 0 || seed_ == M) { + seed_ = 1; + } + } + + int32_t Rand() { + static const uint64_t A = 16807; // bits 14, 8, 7, 5, 2, 1, 0 + seed_ = static_cast<int32_t>((seed_ * A) % M); + CHECK_GT(seed_, 0); + return seed_; + } + + private: + static const uint64_t M = 2147483647L; // 2^32-1 + int32_t seed_; +}; + +} // namespace media + +#endif // MEDIA_BLINK_TEST_RANDOM_H_ diff --git a/chromium/media/base/text_track_config.cc b/chromium/media/base/text_track_config.cc index 0d4b11f6ddb..dbd2b2d5c13 100644 --- a/chromium/media/base/text_track_config.cc +++ b/chromium/media/base/text_track_config.cc @@ -20,6 +20,8 @@ TextTrackConfig::TextTrackConfig(TextKind kind, id_(id) { } +TextTrackConfig::TextTrackConfig(const TextTrackConfig& other) = default; + bool TextTrackConfig::Matches(const TextTrackConfig& config) const { return config.kind() == kind_ && config.label() == label_ && diff --git a/chromium/media/base/text_track_config.h b/chromium/media/base/text_track_config.h index 58efba4b035..33128df99a0 100644 --- a/chromium/media/base/text_track_config.h +++ b/chromium/media/base/text_track_config.h @@ -23,6 +23,7 @@ enum TextKind { class MEDIA_EXPORT TextTrackConfig { public: TextTrackConfig(); + TextTrackConfig(const TextTrackConfig& other); TextTrackConfig(TextKind kind, const std::string& label, const std::string& language, diff --git a/chromium/media/base/video_capturer_source.h b/chromium/media/base/video_capturer_source.h index a99faf8e394..ddec90c6599 100644 --- a/chromium/media/base/video_capturer_source.h +++ b/chromium/media/base/video_capturer_source.h @@ -76,6 +76,16 @@ class MEDIA_EXPORT VideoCapturerSource { const VideoCaptureDeliverFrameCB& new_frame_callback, const RunningCallback& running_callback) = 0; + // Asks source to send a refresh frame. In cases where source does not provide + // a continuous rate of new frames (e.g. canvas capture, screen capture where + // the screen's content has not changed in a while), consumers may request a + // "refresh frame" to be delivered. For instance, this would be needed when + // a new sink is added to a MediaStreamTrack. + // The default implementation is a no-op and implementations are not required + // to honor this request. If they decide to and capturing is started + // successfully, then |new_frame_callback| should be called with a frame. + virtual void RequestRefreshFrame() {} + // Stops capturing frames and clears all callbacks including the // SupportedFormatsCallback callback. Note that queued frame callbacks // may still occur after this call, so the caller must take care to diff --git a/chromium/media/base/video_codecs.cc b/chromium/media/base/video_codecs.cc index 7b3f77f04ec..a2221a35d3d 100644 --- a/chromium/media/base/video_codecs.cc +++ b/chromium/media/base/video_codecs.cc @@ -5,6 +5,8 @@ #include "media/base/video_codecs.h" #include "base/logging.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" namespace media { @@ -62,11 +64,109 @@ std::string GetProfileName(VideoCodecProfile profile) { return "h264 multiview high"; case VP8PROFILE_ANY: return "vp8"; - case VP9PROFILE_ANY: - return "vp9"; + case VP9PROFILE_PROFILE0: + return "vp9 profile0"; + case VP9PROFILE_PROFILE1: + return "vp9 profile1"; + case VP9PROFILE_PROFILE2: + return "vp9 profile2"; + case VP9PROFILE_PROFILE3: + return "vp9 profile3"; } NOTREACHED(); return ""; } +bool ParseAVCCodecId(const std::string& codec_id, + VideoCodecProfile* profile, + uint8_t* level_idc) { + // Make sure we have avc1.xxxxxx or avc3.xxxxxx , where xxxxxx are hex digits + if (!base::StartsWith(codec_id, "avc1.", base::CompareCase::SENSITIVE) && + !base::StartsWith(codec_id, "avc3.", base::CompareCase::SENSITIVE)) { + return false; + } + uint32_t elem = 0; + if (codec_id.size() != 11 || + !base::HexStringToUInt(base::StringPiece(codec_id).substr(5), &elem)) { + DVLOG(4) << __FUNCTION__ << ": invalid avc codec id (" << codec_id << ")"; + return false; + } + + uint8_t level_byte = elem & 0xFF; + uint8_t constraints_byte = (elem >> 8) & 0xFF; + uint8_t profile_idc = (elem >> 16) & 0xFF; + + // Check that the lower two bits of |constraints_byte| are zero (those are + // reserved and must be zero according to ISO IEC 14496-10). + if (constraints_byte & 3) { + DVLOG(4) << __FUNCTION__ << ": non-zero reserved bits in codec id " + << codec_id; + return false; + } + + VideoCodecProfile out_profile = VIDEO_CODEC_PROFILE_UNKNOWN; + // profile_idc values for each profile are taken from ISO IEC 14496-10 and + // https://en.wikipedia.org/wiki/H.264/MPEG-4_AVC#Profiles + switch (profile_idc) { + case 66: + out_profile = H264PROFILE_BASELINE; + break; + case 77: + out_profile = H264PROFILE_MAIN; + break; + case 83: + out_profile = H264PROFILE_SCALABLEBASELINE; + break; + case 86: + out_profile = H264PROFILE_SCALABLEHIGH; + break; + case 88: + out_profile = H264PROFILE_EXTENDED; + break; + case 100: + out_profile = H264PROFILE_HIGH; + break; + case 110: + out_profile = H264PROFILE_HIGH10PROFILE; + break; + case 118: + out_profile = H264PROFILE_MULTIVIEWHIGH; + break; + case 122: + out_profile = H264PROFILE_HIGH422PROFILE; + break; + case 128: + out_profile = H264PROFILE_STEREOHIGH; + break; + case 244: + out_profile = H264PROFILE_HIGH444PREDICTIVEPROFILE; + break; + default: + DVLOG(1) << "Warning: unrecognized AVC/H.264 profile " << profile_idc; + return false; + } + + // TODO(servolk): Take into account also constraint set flags 3 through 5. + uint8_t constraint_set0_flag = (constraints_byte >> 7) & 1; + uint8_t constraint_set1_flag = (constraints_byte >> 6) & 1; + uint8_t constraint_set2_flag = (constraints_byte >> 5) & 1; + if (constraint_set2_flag && out_profile > H264PROFILE_EXTENDED) { + out_profile = H264PROFILE_EXTENDED; + } + if (constraint_set1_flag && out_profile > H264PROFILE_MAIN) { + out_profile = H264PROFILE_MAIN; + } + if (constraint_set0_flag && out_profile > H264PROFILE_BASELINE) { + out_profile = H264PROFILE_BASELINE; + } + + if (level_idc) + *level_idc = level_byte; + + if (profile) + *profile = out_profile; + + return true; +} + } // namespace media diff --git a/chromium/media/base/video_codecs.h b/chromium/media/base/video_codecs.h index 9d9032ff750..28c3b3eebc8 100644 --- a/chromium/media/base/video_codecs.h +++ b/chromium/media/base/video_codecs.h @@ -5,6 +5,7 @@ #ifndef MEDIA_BASE_VIDEO_CODECS_H_ #define MEDIA_BASE_VIDEO_CODECS_H_ +#include <stdint.h> #include <string> #include "media/base/media_export.h" @@ -57,14 +58,22 @@ enum VideoCodecProfile { VP8PROFILE_ANY = VP8PROFILE_MIN, VP8PROFILE_MAX = VP8PROFILE_ANY, VP9PROFILE_MIN = 12, - VP9PROFILE_ANY = VP9PROFILE_MIN, - VP9PROFILE_MAX = VP9PROFILE_ANY, + VP9PROFILE_PROFILE0 = VP9PROFILE_MIN, + VP9PROFILE_PROFILE1 = 13, + VP9PROFILE_PROFILE2 = 14, + VP9PROFILE_PROFILE3 = 15, + VP9PROFILE_MAX = VP9PROFILE_PROFILE3, VIDEO_CODEC_PROFILE_MAX = VP9PROFILE_MAX, }; std::string MEDIA_EXPORT GetCodecName(VideoCodec codec); std::string MEDIA_EXPORT GetProfileName(VideoCodecProfile profile); +// Handle parsing AVC/H.264 codec ids as outlined in RFC 6381 and ISO-14496-10. +MEDIA_EXPORT bool ParseAVCCodecId(const std::string& codec_id, + VideoCodecProfile* profile, + uint8_t* level_idc); + } // namespace media #endif // MEDIA_BASE_VIDEO_CODECS_H_ diff --git a/chromium/media/base/video_decoder.h b/chromium/media/base/video_decoder.h index 68737c7328d..d6abf03e175 100644 --- a/chromium/media/base/video_decoder.h +++ b/chromium/media/base/video_decoder.h @@ -10,28 +10,20 @@ #include "base/callback.h" #include "base/macros.h" #include "base/memory/ref_counted.h" -#include "media/base/cdm_context.h" +#include "media/base/decode_status.h" #include "media/base/media_export.h" #include "media/base/pipeline_status.h" #include "ui/gfx/geometry/size.h" namespace media { +class CdmContext; class DecoderBuffer; class VideoDecoderConfig; class VideoFrame; class MEDIA_EXPORT VideoDecoder { public: - // Status codes for decode operations on VideoDecoder. - // TODO(rileya): Now that both AudioDecoder and VideoDecoder Status enums - // match, break them into a decoder_status.h. - enum Status { - kOk, // Everything went as planned. - kAborted, // Decode was aborted as a result of Reset() being called. - kDecodeError // Decoding error happened. - }; - // Callback for VideoDecoder initialization. typedef base::Callback<void(bool success)> InitCB; @@ -42,7 +34,7 @@ class MEDIA_EXPORT VideoDecoder { // Callback type for Decode(). Called after the decoder has completed decoding // corresponding DecoderBuffer, indicating that it's ready to accept another // buffer to decode. - typedef base::Callback<void(Status status)> DecodeCB; + typedef base::Callback<void(DecodeStatus)> DecodeCB; VideoDecoder(); @@ -65,9 +57,8 @@ class MEDIA_EXPORT VideoDecoder { // Initialization should fail if |low_delay| is true and the decoder cannot // satisfy the requirements above. // - // |set_cdm_ready_cb| can be used to set/cancel a CdmReadyCB with which the - // decoder can be notified when a CDM is ready. The decoder can use the CDM to - // handle encrypted video stream. + // |cdm_context| can be used to handle encrypted buffers. May be null if the + // stream is not encrypted. // // Note: // 1) The VideoDecoder will be reinitialized if it was initialized before. @@ -76,7 +67,7 @@ class MEDIA_EXPORT VideoDecoder { // 3) No VideoDecoder calls should be made before |init_cb| is executed. virtual void Initialize(const VideoDecoderConfig& config, bool low_delay, - const SetCdmReadyCB& set_cdm_ready_cb, + CdmContext* cdm_context, const InitCB& init_cb, const OutputCB& output_cb) = 0; diff --git a/chromium/media/base/video_decoder_config.cc b/chromium/media/base/video_decoder_config.cc index dfe7254e9af..03133608987 100644 --- a/chromium/media/base/video_decoder_config.cc +++ b/chromium/media/base/video_decoder_config.cc @@ -29,7 +29,10 @@ VideoCodec VideoCodecProfileToVideoCodec(VideoCodecProfile profile) { return kCodecH264; case VP8PROFILE_ANY: return kCodecVP8; - case VP9PROFILE_ANY: + case VP9PROFILE_PROFILE0: + case VP9PROFILE_PROFILE1: + case VP9PROFILE_PROFILE2: + case VP9PROFILE_PROFILE3: return kCodecVP9; } NOTREACHED(); @@ -39,22 +42,25 @@ VideoCodec VideoCodecProfileToVideoCodec(VideoCodecProfile profile) { VideoDecoderConfig::VideoDecoderConfig() : codec_(kUnknownVideoCodec), profile_(VIDEO_CODEC_PROFILE_UNKNOWN), - format_(PIXEL_FORMAT_UNKNOWN), - is_encrypted_(false) {} + format_(PIXEL_FORMAT_UNKNOWN) {} -VideoDecoderConfig::VideoDecoderConfig(VideoCodec codec, - VideoCodecProfile profile, - VideoPixelFormat format, - ColorSpace color_space, - const gfx::Size& coded_size, - const gfx::Rect& visible_rect, - const gfx::Size& natural_size, - const std::vector<uint8_t>& extra_data, - bool is_encrypted) { +VideoDecoderConfig::VideoDecoderConfig( + VideoCodec codec, + VideoCodecProfile profile, + VideoPixelFormat format, + ColorSpace color_space, + const gfx::Size& coded_size, + const gfx::Rect& visible_rect, + const gfx::Size& natural_size, + const std::vector<uint8_t>& extra_data, + const EncryptionScheme& encryption_scheme) { Initialize(codec, profile, format, color_space, coded_size, visible_rect, - natural_size, extra_data, is_encrypted); + natural_size, extra_data, encryption_scheme); } +VideoDecoderConfig::VideoDecoderConfig(const VideoDecoderConfig& other) = + default; + VideoDecoderConfig::~VideoDecoderConfig() {} void VideoDecoderConfig::Initialize(VideoCodec codec, @@ -65,7 +71,7 @@ void VideoDecoderConfig::Initialize(VideoCodec codec, const gfx::Rect& visible_rect, const gfx::Size& natural_size, const std::vector<uint8_t>& extra_data, - bool is_encrypted) { + const EncryptionScheme& encryption_scheme) { codec_ = codec; profile_ = profile; format_ = format; @@ -74,7 +80,7 @@ void VideoDecoderConfig::Initialize(VideoCodec codec, visible_rect_ = visible_rect; natural_size_ = natural_size; extra_data_ = extra_data; - is_encrypted_ = is_encrypted; + encryption_scheme_ = encryption_scheme; } bool VideoDecoderConfig::IsValidConfig() const { @@ -86,14 +92,13 @@ bool VideoDecoderConfig::IsValidConfig() const { } bool VideoDecoderConfig::Matches(const VideoDecoderConfig& config) const { - return ((codec() == config.codec()) && - (format() == config.format()) && + return ((codec() == config.codec()) && (format() == config.format()) && (profile() == config.profile()) && (coded_size() == config.coded_size()) && (visible_rect() == config.visible_rect()) && (natural_size() == config.natural_size()) && (extra_data() == config.extra_data()) && - (is_encrypted() == config.is_encrypted())); + (encryption_scheme().Matches(config.encryption_scheme()))); } std::string VideoDecoderConfig::AsHumanReadableString() const { diff --git a/chromium/media/base/video_decoder_config.h b/chromium/media/base/video_decoder_config.h index ae9340afea3..50dee06534c 100644 --- a/chromium/media/base/video_decoder_config.h +++ b/chromium/media/base/video_decoder_config.h @@ -11,6 +11,7 @@ #include <vector> #include "base/macros.h" +#include "media/base/encryption_scheme.h" #include "media/base/media_export.h" #include "media/base/video_codecs.h" #include "media/base/video_types.h" @@ -38,7 +39,9 @@ class MEDIA_EXPORT VideoDecoderConfig { const gfx::Rect& visible_rect, const gfx::Size& natural_size, const std::vector<uint8_t>& extra_data, - bool is_encrypted); + const EncryptionScheme& encryption_scheme); + + VideoDecoderConfig(const VideoDecoderConfig& other); ~VideoDecoderConfig(); @@ -51,7 +54,7 @@ class MEDIA_EXPORT VideoDecoderConfig { const gfx::Rect& visible_rect, const gfx::Size& natural_size, const std::vector<uint8_t>& extra_data, - bool is_encrypted); + const EncryptionScheme& encryption_scheme); // Returns true if this object has appropriate configuration values, false // otherwise. @@ -98,7 +101,12 @@ class MEDIA_EXPORT VideoDecoderConfig { // Whether the video stream is potentially encrypted. // Note that in a potentially encrypted video stream, individual buffers // can be encrypted or not encrypted. - bool is_encrypted() const { return is_encrypted_; } + bool is_encrypted() const { return encryption_scheme_.is_encrypted(); } + + // Encryption scheme used for encrypted buffers. + const EncryptionScheme& encryption_scheme() const { + return encryption_scheme_; + } private: VideoCodec codec_; @@ -113,7 +121,7 @@ class MEDIA_EXPORT VideoDecoderConfig { std::vector<uint8_t> extra_data_; - bool is_encrypted_; + EncryptionScheme encryption_scheme_; // Not using DISALLOW_COPY_AND_ASSIGN here intentionally to allow the compiler // generated copy constructor and assignment operator. Since the extra data is diff --git a/chromium/media/base/video_decoder_config_unittest.cc b/chromium/media/base/video_decoder_config_unittest.cc index 7885c8f7170..18528330e6c 100644 --- a/chromium/media/base/video_decoder_config_unittest.cc +++ b/chromium/media/base/video_decoder_config_unittest.cc @@ -19,7 +19,7 @@ TEST(VideoDecoderConfigTest, Invalid_UnsupportedPixelFormat) { VideoDecoderConfig config(kCodecVP8, VIDEO_CODEC_PROFILE_UNKNOWN, PIXEL_FORMAT_UNKNOWN, COLOR_SPACE_UNSPECIFIED, kCodedSize, kVisibleRect, kNaturalSize, - EmptyExtraData(), false); + EmptyExtraData(), Unencrypted()); EXPECT_FALSE(config.IsValidConfig()); } @@ -27,7 +27,7 @@ TEST(VideoDecoderConfigTest, Invalid_AspectRatioNumeratorZero) { gfx::Size natural_size = GetNaturalSize(kVisibleRect.size(), 0, 1); VideoDecoderConfig config(kCodecVP8, VP8PROFILE_ANY, kVideoFormat, COLOR_SPACE_UNSPECIFIED, kCodedSize, kVisibleRect, - natural_size, EmptyExtraData(), false); + natural_size, EmptyExtraData(), Unencrypted()); EXPECT_FALSE(config.IsValidConfig()); } @@ -35,7 +35,7 @@ TEST(VideoDecoderConfigTest, Invalid_AspectRatioDenominatorZero) { gfx::Size natural_size = GetNaturalSize(kVisibleRect.size(), 1, 0); VideoDecoderConfig config(kCodecVP8, VP8PROFILE_ANY, kVideoFormat, COLOR_SPACE_UNSPECIFIED, kCodedSize, kVisibleRect, - natural_size, EmptyExtraData(), false); + natural_size, EmptyExtraData(), Unencrypted()); EXPECT_FALSE(config.IsValidConfig()); } @@ -43,7 +43,7 @@ TEST(VideoDecoderConfigTest, Invalid_AspectRatioNumeratorNegative) { gfx::Size natural_size = GetNaturalSize(kVisibleRect.size(), -1, 1); VideoDecoderConfig config(kCodecVP8, VP8PROFILE_ANY, kVideoFormat, COLOR_SPACE_UNSPECIFIED, kCodedSize, kVisibleRect, - natural_size, EmptyExtraData(), false); + natural_size, EmptyExtraData(), Unencrypted()); EXPECT_FALSE(config.IsValidConfig()); } @@ -51,7 +51,7 @@ TEST(VideoDecoderConfigTest, Invalid_AspectRatioDenominatorNegative) { gfx::Size natural_size = GetNaturalSize(kVisibleRect.size(), 1, -1); VideoDecoderConfig config(kCodecVP8, VP8PROFILE_ANY, kVideoFormat, COLOR_SPACE_UNSPECIFIED, kCodedSize, kVisibleRect, - natural_size, EmptyExtraData(), false); + natural_size, EmptyExtraData(), Unencrypted()); EXPECT_FALSE(config.IsValidConfig()); } @@ -61,7 +61,7 @@ TEST(VideoDecoderConfigTest, Invalid_AspectRatioNumeratorTooLarge) { gfx::Size natural_size = GetNaturalSize(kVisibleRect.size(), num, 1); VideoDecoderConfig config(kCodecVP8, VP8PROFILE_ANY, kVideoFormat, COLOR_SPACE_UNSPECIFIED, kCodedSize, kVisibleRect, - natural_size, EmptyExtraData(), false); + natural_size, EmptyExtraData(), Unencrypted()); EXPECT_FALSE(config.IsValidConfig()); } @@ -72,7 +72,7 @@ TEST(VideoDecoderConfigTest, Invalid_AspectRatioDenominatorTooLarge) { EXPECT_EQ(0, natural_size.width()); VideoDecoderConfig config(kCodecVP8, VP8PROFILE_ANY, kVideoFormat, COLOR_SPACE_UNSPECIFIED, kCodedSize, kVisibleRect, - natural_size, EmptyExtraData(), false); + natural_size, EmptyExtraData(), Unencrypted()); EXPECT_FALSE(config.IsValidConfig()); } diff --git a/chromium/media/base/video_frame.cc b/chromium/media/base/video_frame.cc index 0ad4ac0f807..b4a8392ff8c 100644 --- a/chromium/media/base/video_frame.cc +++ b/chromium/media/base/video_frame.cc @@ -36,17 +36,6 @@ static inline size_t RoundDown(size_t value, size_t alignment) { return value & ~(alignment - 1); } -static std::string ConfigToString(const VideoPixelFormat format, - const VideoFrame::StorageType storage_type, - const gfx::Size& coded_size, - const gfx::Rect& visible_rect, - const gfx::Size& natural_size) { - return base::StringPrintf( - "format:%s coded_size:%s visible_rect:%s natural_size:%s", - VideoPixelFormatToString(format).c_str(), coded_size.ToString().c_str(), - visible_rect.ToString().c_str(), natural_size.ToString().c_str()); -} - static std::string StorageTypeToString( const VideoFrame::StorageType storage_type) { switch (storage_type) { @@ -70,19 +59,14 @@ static std::string StorageTypeToString( #endif case VideoFrame::STORAGE_GPU_MEMORY_BUFFERS: return "GPU_MEMORY_BUFFERS"; + case VideoFrame::STORAGE_MOJO_SHARED_BUFFER: + return "MOJO_SHARED_BUFFER"; } NOTREACHED() << "Invalid StorageType provided: " << storage_type; return "INVALID"; } -// Returns true if |plane| is a valid plane index for the given |format|. -static bool IsValidPlane(size_t plane, VideoPixelFormat format) { - DCHECK_LE(VideoFrame::NumPlanes(format), - static_cast<size_t>(VideoFrame::kMaxPlanes)); - return (plane < VideoFrame::NumPlanes(format)); -} - // Returns true if |frame| is accesible mapped in the VideoFrame memory space. // static static bool IsStorageTypeMappable(VideoFrame::StorageType storage_type) { @@ -95,97 +79,20 @@ static bool IsStorageTypeMappable(VideoFrame::StorageType storage_type) { (storage_type == VideoFrame::STORAGE_UNOWNED_MEMORY || storage_type == VideoFrame::STORAGE_OWNED_MEMORY || storage_type == VideoFrame::STORAGE_SHMEM || - storage_type == VideoFrame::STORAGE_GPU_MEMORY_BUFFERS); + storage_type == VideoFrame::STORAGE_GPU_MEMORY_BUFFERS || + storage_type == VideoFrame::STORAGE_MOJO_SHARED_BUFFER); } -// Returns the pixel size per element for given |plane| and |format|. E.g. 2x2 -// for the U-plane in PIXEL_FORMAT_I420. -static gfx::Size SampleSize(VideoPixelFormat format, size_t plane) { - DCHECK(IsValidPlane(plane, format)); - - switch (plane) { - case VideoFrame::kYPlane: - case VideoFrame::kAPlane: - return gfx::Size(1, 1); - - case VideoFrame::kUPlane: // and VideoFrame::kUVPlane: - case VideoFrame::kVPlane: - switch (format) { - case PIXEL_FORMAT_YV24: - return gfx::Size(1, 1); - - case PIXEL_FORMAT_YV16: - return gfx::Size(2, 1); - - case PIXEL_FORMAT_YV12: - case PIXEL_FORMAT_I420: - case PIXEL_FORMAT_YV12A: - case PIXEL_FORMAT_NV12: - case PIXEL_FORMAT_NV21: - case PIXEL_FORMAT_MT21: - return gfx::Size(2, 2); - - case PIXEL_FORMAT_UNKNOWN: - case PIXEL_FORMAT_UYVY: - case PIXEL_FORMAT_YUY2: - case PIXEL_FORMAT_ARGB: - case PIXEL_FORMAT_XRGB: - case PIXEL_FORMAT_RGB24: - case PIXEL_FORMAT_RGB32: - case PIXEL_FORMAT_MJPEG: - break; - } - } - NOTREACHED(); - return gfx::Size(); -} +// Checks if |source_format| can be wrapped into a |target_format| frame. +static bool AreValidPixelFormatsForWrap(VideoPixelFormat source_format, + VideoPixelFormat target_format) { + if (source_format == target_format) + return true; -// Return the alignment for the whole frame, calculated as the max of the -// alignment for each individual plane. -static gfx::Size CommonAlignment(VideoPixelFormat format) { - int max_sample_width = 0; - int max_sample_height = 0; - for (size_t plane = 0; plane < VideoFrame::NumPlanes(format); ++plane) { - const gfx::Size sample_size = SampleSize(format, plane); - max_sample_width = std::max(max_sample_width, sample_size.width()); - max_sample_height = std::max(max_sample_height, sample_size.height()); - } - return gfx::Size(max_sample_width, max_sample_height); -} - -// Returns the number of bytes per element for given |plane| and |format|. -static int BytesPerElement(VideoPixelFormat format, size_t plane) { - DCHECK(IsValidPlane(plane, format)); - switch (format) { - case PIXEL_FORMAT_ARGB: - case PIXEL_FORMAT_XRGB: - case PIXEL_FORMAT_RGB32: - return 4; - case PIXEL_FORMAT_RGB24: - return 3; - case PIXEL_FORMAT_UYVY: - case PIXEL_FORMAT_YUY2: - return 2; - case PIXEL_FORMAT_NV12: - case PIXEL_FORMAT_NV21: - case PIXEL_FORMAT_MT21: { - static const int bytes_per_element[] = {1, 2}; - DCHECK_LT(plane, arraysize(bytes_per_element)); - return bytes_per_element[plane]; - } - case PIXEL_FORMAT_YV12: - case PIXEL_FORMAT_I420: - case PIXEL_FORMAT_YV16: - case PIXEL_FORMAT_YV12A: - case PIXEL_FORMAT_YV24: - return 1; - case PIXEL_FORMAT_MJPEG: - return 0; - case PIXEL_FORMAT_UNKNOWN: - break; - } - NOTREACHED(); - return 0; + // It is possible to add other planar to planar format conversions here if the + // use case is there. + return source_format == PIXEL_FORMAT_YV12A && + target_format == PIXEL_FORMAT_I420; } // static @@ -214,7 +121,7 @@ bool VideoFrame::IsValidConfig(VideoPixelFormat format, return true; // Make sure new formats are properly accounted for in the method. - static_assert(PIXEL_FORMAT_MAX == 15, + static_assert(PIXEL_FORMAT_MAX == 21, "Added pixel format, please review IsValidConfig()"); if (format == PIXEL_FORMAT_UNKNOWN) { @@ -483,33 +390,39 @@ scoped_refptr<VideoFrame> VideoFrame::WrapCVPixelBuffer( // static scoped_refptr<VideoFrame> VideoFrame::WrapVideoFrame( - const scoped_refptr<VideoFrame>& frame, - const gfx::Rect& visible_rect, - const gfx::Size& natural_size) { + const scoped_refptr<VideoFrame>& frame, + VideoPixelFormat format, + const gfx::Rect& visible_rect, + const gfx::Size& natural_size) { // Frames with textures need mailbox info propagated, and there's no support // for that here yet, see http://crbug/362521. CHECK(!frame->HasTextures()); - DCHECK(frame->visible_rect().Contains(visible_rect)); - if (!IsValidConfig(frame->format(), frame->storage_type(), - frame->coded_size(), visible_rect, natural_size)) { + if (!AreValidPixelFormatsForWrap(frame->format(), format)) { + LOG(DFATAL) << __FUNCTION__ << " Invalid format conversion." + << VideoPixelFormatToString(frame->format()) << " to " + << VideoPixelFormatToString(format); + return nullptr; + } + + if (!IsValidConfig(format, frame->storage_type(), frame->coded_size(), + visible_rect, natural_size)) { LOG(DFATAL) << __FUNCTION__ << " Invalid config." - << ConfigToString(frame->format(), frame->storage_type(), + << ConfigToString(format, frame->storage_type(), frame->coded_size(), visible_rect, natural_size); return nullptr; } - scoped_refptr<VideoFrame> wrapping_frame(new VideoFrame( - frame->format(), frame->storage_type(), frame->coded_size(), visible_rect, - natural_size, frame->timestamp())); - if (frame->metadata()->IsTrue(VideoFrameMetadata::END_OF_STREAM)) { - wrapping_frame->metadata()->SetBoolean(VideoFrameMetadata::END_OF_STREAM, - true); - } + scoped_refptr<VideoFrame> wrapping_frame( + new VideoFrame(format, frame->storage_type(), frame->coded_size(), + visible_rect, natural_size, frame->timestamp())); + + // Copy all metadata to the wrapped frame. + wrapping_frame->metadata()->MergeMetadataFrom(frame->metadata()); - for (size_t i = 0; i < NumPlanes(frame->format()); ++i) { + for (size_t i = 0; i < NumPlanes(format); ++i) { wrapping_frame->strides_[i] = frame->stride(i); wrapping_frame->data_[i] = frame->data(i); } @@ -617,6 +530,12 @@ size_t VideoFrame::NumPlanes(VideoPixelFormat format) { case PIXEL_FORMAT_YV12: case PIXEL_FORMAT_YV16: case PIXEL_FORMAT_YV24: + case PIXEL_FORMAT_YUV420P9: + case PIXEL_FORMAT_YUV422P9: + case PIXEL_FORMAT_YUV444P9: + case PIXEL_FORMAT_YUV420P10: + case PIXEL_FORMAT_YUV422P10: + case PIXEL_FORMAT_YUV444P10: return 3; case PIXEL_FORMAT_YV12A: return 4; @@ -860,12 +779,9 @@ std::string VideoFrame::AsHumanReadableString() { return "end of stream"; std::ostringstream s; - s << "format: " << VideoPixelFormatToString(format_) - << " storage_type: " << StorageTypeToString(storage_type_) - << " coded_size: " << coded_size_.ToString() - << " visible_rect: " << visible_rect_.ToString() - << " natural_size: " << natural_size_.ToString() - << " timestamp: " << timestamp_.InMicroseconds(); + s << ConfigToString(format_, storage_type_, coded_size_, visible_rect_, + natural_size_) + << " timestamp:" << timestamp_.InMicroseconds(); return s.str(); } @@ -941,6 +857,65 @@ VideoFrame::VideoFrame(VideoPixelFormat format, memset(&data_, 0, sizeof(data_)); } +VideoFrame::~VideoFrame() { + if (!mailbox_holders_release_cb_.is_null()) { + gpu::SyncToken release_sync_token; + { + // To ensure that changes to |release_sync_token_| are visible on this + // thread (imply a memory barrier). + base::AutoLock locker(release_sync_token_lock_); + release_sync_token = release_sync_token_; + } + base::ResetAndReturn(&mailbox_holders_release_cb_).Run(release_sync_token); + } + + for (auto& callback : done_callbacks_) + base::ResetAndReturn(&callback).Run(); +} + +// static +std::string VideoFrame::ConfigToString(const VideoPixelFormat format, + const StorageType storage_type, + const gfx::Size& coded_size, + const gfx::Rect& visible_rect, + const gfx::Size& natural_size) { + return base::StringPrintf( + "format:%s storage_type:%s coded_size:%s visible_rect:%s natural_size:%s", + VideoPixelFormatToString(format).c_str(), + StorageTypeToString(storage_type).c_str(), coded_size.ToString().c_str(), + visible_rect.ToString().c_str(), natural_size.ToString().c_str()); +} + +// static +bool VideoFrame::IsValidPlane(size_t plane, VideoPixelFormat format) { + DCHECK_LE(NumPlanes(format), static_cast<size_t>(kMaxPlanes)); + return (plane < NumPlanes(format)); +} + +// static +gfx::Size VideoFrame::DetermineAlignedSize(VideoPixelFormat format, + const gfx::Size& dimensions) { + const gfx::Size alignment = CommonAlignment(format); + const gfx::Size adjusted = + gfx::Size(RoundUp(dimensions.width(), alignment.width()), + RoundUp(dimensions.height(), alignment.height())); + DCHECK((adjusted.width() % alignment.width() == 0) && + (adjusted.height() % alignment.height() == 0)); + return adjusted; +} + +void VideoFrame::set_data(size_t plane, uint8_t* ptr) { + DCHECK(IsValidPlane(plane, format_)); + DCHECK(ptr); + data_[plane] = ptr; +} + +void VideoFrame::set_stride(size_t plane, int stride) { + DCHECK(IsValidPlane(plane, format_)); + DCHECK_GT(stride, 0); + strides_[plane] = stride; +} + VideoFrame::VideoFrame(VideoPixelFormat format, StorageType storage_type, const gfx::Size& coded_size, @@ -978,22 +953,6 @@ VideoFrame::VideoFrame(VideoPixelFormat format, mailbox_holders_release_cb_ = mailbox_holder_release_cb; } -VideoFrame::~VideoFrame() { - if (!mailbox_holders_release_cb_.is_null()) { - gpu::SyncToken release_sync_token; - { - // To ensure that changes to |release_sync_token_| are visible on this - // thread (imply a memory barrier). - base::AutoLock locker(release_sync_token_lock_); - release_sync_token = release_sync_token_; - } - base::ResetAndReturn(&mailbox_holders_release_cb_).Run(release_sync_token); - } - - for (auto& callback : done_callbacks_) - base::ResetAndReturn(&callback).Run(); -} - // static scoped_refptr<VideoFrame> VideoFrame::CreateFrameInternal( VideoPixelFormat format, @@ -1011,13 +970,7 @@ scoped_refptr<VideoFrame> VideoFrame::CreateFrameInternal( // ourselves), we can pad the requested |coded_size| if necessary if the // request does not line up on sample boundaries. See discussion at // http://crrev.com/1240833003 - const gfx::Size alignment = CommonAlignment(format); - const gfx::Size new_coded_size = - gfx::Size(RoundUp(coded_size.width(), alignment.width()), - RoundUp(coded_size.height(), alignment.height())); - DCHECK((new_coded_size.width() % alignment.width() == 0) && - (new_coded_size.height() % alignment.height() == 0)); - + const gfx::Size new_coded_size = DetermineAlignedSize(format, coded_size); const StorageType storage = STORAGE_OWNED_MEMORY; if (!IsValidConfig(format, storage, new_coded_size, visible_rect, natural_size)) { @@ -1033,6 +986,106 @@ scoped_refptr<VideoFrame> VideoFrame::CreateFrameInternal( return frame; } +// static +gfx::Size VideoFrame::SampleSize(VideoPixelFormat format, size_t plane) { + DCHECK(IsValidPlane(plane, format)); + + switch (plane) { + case kYPlane: + case kAPlane: + return gfx::Size(1, 1); + + case kUPlane: // and kUVPlane: + case kVPlane: + switch (format) { + case PIXEL_FORMAT_YV24: + case PIXEL_FORMAT_YUV444P9: + case PIXEL_FORMAT_YUV444P10: + return gfx::Size(1, 1); + + case PIXEL_FORMAT_YV16: + case PIXEL_FORMAT_YUV422P9: + case PIXEL_FORMAT_YUV422P10: + return gfx::Size(2, 1); + + case PIXEL_FORMAT_YV12: + case PIXEL_FORMAT_I420: + case PIXEL_FORMAT_YV12A: + case PIXEL_FORMAT_NV12: + case PIXEL_FORMAT_NV21: + case PIXEL_FORMAT_MT21: + case PIXEL_FORMAT_YUV420P9: + case PIXEL_FORMAT_YUV420P10: + return gfx::Size(2, 2); + + case PIXEL_FORMAT_UNKNOWN: + case PIXEL_FORMAT_UYVY: + case PIXEL_FORMAT_YUY2: + case PIXEL_FORMAT_ARGB: + case PIXEL_FORMAT_XRGB: + case PIXEL_FORMAT_RGB24: + case PIXEL_FORMAT_RGB32: + case PIXEL_FORMAT_MJPEG: + break; + } + } + NOTREACHED(); + return gfx::Size(); +} + +// static +int VideoFrame::BytesPerElement(VideoPixelFormat format, size_t plane) { + DCHECK(IsValidPlane(plane, format)); + switch (format) { + case PIXEL_FORMAT_ARGB: + case PIXEL_FORMAT_XRGB: + case PIXEL_FORMAT_RGB32: + return 4; + case PIXEL_FORMAT_RGB24: + return 3; + case PIXEL_FORMAT_UYVY: + case PIXEL_FORMAT_YUY2: + case PIXEL_FORMAT_YUV420P9: + case PIXEL_FORMAT_YUV422P9: + case PIXEL_FORMAT_YUV444P9: + case PIXEL_FORMAT_YUV420P10: + case PIXEL_FORMAT_YUV422P10: + case PIXEL_FORMAT_YUV444P10: + return 2; + case PIXEL_FORMAT_NV12: + case PIXEL_FORMAT_NV21: + case PIXEL_FORMAT_MT21: { + static const int bytes_per_element[] = {1, 2}; + DCHECK_LT(plane, arraysize(bytes_per_element)); + return bytes_per_element[plane]; + } + case PIXEL_FORMAT_YV12: + case PIXEL_FORMAT_I420: + case PIXEL_FORMAT_YV16: + case PIXEL_FORMAT_YV12A: + case PIXEL_FORMAT_YV24: + return 1; + case PIXEL_FORMAT_MJPEG: + return 0; + case PIXEL_FORMAT_UNKNOWN: + break; + } + NOTREACHED(); + return 0; +} + +// static +gfx::Size VideoFrame::CommonAlignment(VideoPixelFormat format) { + int max_sample_width = 0; + int max_sample_height = 0; + for (size_t plane = 0; plane < NumPlanes(format); ++plane) { + const gfx::Size sample_size = SampleSize(format, plane); + max_sample_width = std::max(max_sample_width, sample_size.width()); + max_sample_height = std::max(max_sample_height, sample_size.height()); + } + return gfx::Size(max_sample_width, max_sample_height); +} + void VideoFrame::AllocateYUV(bool zero_initialize_memory) { DCHECK_EQ(storage_type_, STORAGE_OWNED_MEMORY); static_assert(0 == kYPlane, "y plane data must be index 0"); diff --git a/chromium/media/base/video_frame.h b/chromium/media/base/video_frame.h index ad7f4c01a42..944a04018ca 100644 --- a/chromium/media/base/video_frame.h +++ b/chromium/media/base/video_frame.h @@ -73,7 +73,8 @@ class MEDIA_EXPORT VideoFrame : public base::RefCountedThreadSafe<VideoFrame> { STORAGE_HOLE = 6, #endif STORAGE_GPU_MEMORY_BUFFERS = 7, - STORAGE_LAST = STORAGE_GPU_MEMORY_BUFFERS, + STORAGE_MOJO_SHARED_BUFFER = 8, + STORAGE_LAST = STORAGE_MOJO_SHARED_BUFFER, }; // CB to be called on the mailbox backing this frame when the frame is @@ -244,6 +245,7 @@ class MEDIA_EXPORT VideoFrame : public base::RefCountedThreadSafe<VideoFrame> { // frame->visible_rect(). static scoped_refptr<VideoFrame> WrapVideoFrame( const scoped_refptr<VideoFrame>& frame, + VideoPixelFormat format, const gfx::Rect& visible_rect, const gfx::Size& natural_size); @@ -413,28 +415,40 @@ class MEDIA_EXPORT VideoFrame : public base::RefCountedThreadSafe<VideoFrame> { // Returns a human-readable string describing |*this|. std::string AsHumanReadableString(); - private: + protected: friend class base::RefCountedThreadSafe<VideoFrame>; - static scoped_refptr<VideoFrame> WrapExternalStorage( - VideoPixelFormat format, - StorageType storage_type, - const gfx::Size& coded_size, - const gfx::Rect& visible_rect, - const gfx::Size& natural_size, - uint8_t* data, - size_t data_size, - base::TimeDelta timestamp, - base::SharedMemoryHandle handle, - size_t data_offset); - // Clients must use the static factory/wrapping methods to create a new frame. + // Derived classes should create their own factory/wrapping methods, and use + // this constructor to do basic initialization. VideoFrame(VideoPixelFormat format, StorageType storage_type, const gfx::Size& coded_size, const gfx::Rect& visible_rect, const gfx::Size& natural_size, base::TimeDelta timestamp); + + virtual ~VideoFrame(); + + // Creates a summary of the configuration settings provided as parameters. + static std::string ConfigToString(const VideoPixelFormat format, + const VideoFrame::StorageType storage_type, + const gfx::Size& coded_size, + const gfx::Rect& visible_rect, + const gfx::Size& natural_size); + + // Returns true if |plane| is a valid plane index for the given |format|. + static bool IsValidPlane(size_t plane, VideoPixelFormat format); + + // Returns |dimensions| adjusted to appropriate boundaries based on |format|. + static gfx::Size DetermineAlignedSize(VideoPixelFormat format, + const gfx::Size& dimensions); + + void set_data(size_t plane, uint8_t* ptr); + void set_stride(size_t plane, int stride); + + private: + // Clients must use the static factory/wrapping methods to create a new frame. VideoFrame(VideoPixelFormat format, StorageType storage_type, const gfx::Size& coded_size, @@ -451,7 +465,18 @@ class MEDIA_EXPORT VideoFrame : public base::RefCountedThreadSafe<VideoFrame> { const gpu::MailboxHolder(&mailbox_holders)[kMaxPlanes], const ReleaseMailboxCB& mailbox_holder_release_cb, base::TimeDelta timestamp); - virtual ~VideoFrame(); + + static scoped_refptr<VideoFrame> WrapExternalStorage( + VideoPixelFormat format, + StorageType storage_type, + const gfx::Size& coded_size, + const gfx::Rect& visible_rect, + const gfx::Size& natural_size, + uint8_t* data, + size_t data_size, + base::TimeDelta timestamp, + base::SharedMemoryHandle handle, + size_t data_offset); static scoped_refptr<VideoFrame> CreateFrameInternal( VideoPixelFormat format, @@ -461,6 +486,17 @@ class MEDIA_EXPORT VideoFrame : public base::RefCountedThreadSafe<VideoFrame> { base::TimeDelta timestamp, bool zero_initialize_memory); + // Returns the pixel size of each subsample for a given |plane| and |format|. + // E.g. 2x2 for the U-plane in PIXEL_FORMAT_I420. + static gfx::Size SampleSize(VideoPixelFormat format, size_t plane); + + // Returns the number of bytes per element for given |plane| and |format|. + static int BytesPerElement(VideoPixelFormat format, size_t plane); + + // Return the alignment for the whole frame, calculated as the max of the + // alignment for each individual plane. + static gfx::Size CommonAlignment(VideoPixelFormat format); + void AllocateYUV(bool zero_initialize_memory); // Frame format. diff --git a/chromium/media/base/video_frame_metadata.cc b/chromium/media/base/video_frame_metadata.cc index 2ecf002550c..6aa322669c8 100644 --- a/chromium/media/base/video_frame_metadata.cc +++ b/chromium/media/base/video_frame_metadata.cc @@ -145,6 +145,11 @@ void VideoFrameMetadata::MergeInternalValuesFrom( dictionary_.MergeDictionary(&in); } +void VideoFrameMetadata::MergeMetadataFrom( + const VideoFrameMetadata* metadata_source) { + dictionary_.MergeDictionary(&metadata_source->dictionary_); +} + const base::BinaryValue* VideoFrameMetadata::GetBinaryValue(Key key) const { const base::Value* internal_value = nullptr; if (dictionary_.GetWithoutPathExpansion(ToInternalKey(key), diff --git a/chromium/media/base/video_frame_metadata.h b/chromium/media/base/video_frame_metadata.h index 12112a43ad0..1f6cd3539f3 100644 --- a/chromium/media/base/video_frame_metadata.h +++ b/chromium/media/base/video_frame_metadata.h @@ -9,6 +9,7 @@ #include "base/compiler_specific.h" #include "base/macros.h" +#include "base/memory/scoped_ptr.h" #include "base/time/time.h" #include "base/values.h" #include "media/base/media_export.h" @@ -40,6 +41,12 @@ class MEDIA_EXPORT VideoFrameMetadata { // contexts. COPY_REQUIRED, + // Indicates that the frame is owned by the decoder and that destroying the + // decoder will make the frame unrenderable. TODO(sandersd): Remove once OSX + // and Windows hardware decoders support frames which outlive the decoder. + // http://crbug.com/595716 and http://crbug.com/602708. + DECODER_OWNS_FRAME, + // Indicates if the current frame is the End of its current Stream. Use // Get/SetBoolean() for this Key. END_OF_STREAM, @@ -94,6 +101,11 @@ class MEDIA_EXPORT VideoFrameMetadata { // measurements would be used as feedback. RESOURCE_UTILIZATION, + // Sources of VideoFrames use this marker to indicate that an instance of + // VideoFrameExternalResources produced from the associated video frame + // should use read lock fences. + READ_LOCK_FENCES_ENABLED, + NUM_KEYS }; @@ -131,6 +143,9 @@ class MEDIA_EXPORT VideoFrameMetadata { void MergeInternalValuesInto(base::DictionaryValue* out) const; void MergeInternalValuesFrom(const base::DictionaryValue& in); + // Merges internal values from |metadata_source|. + void MergeMetadataFrom(const VideoFrameMetadata* metadata_source); + private: const base::BinaryValue* GetBinaryValue(Key key) const; diff --git a/chromium/media/base/video_frame_pool.cc b/chromium/media/base/video_frame_pool.cc index 40611eedc8c..2bc7ff6a47e 100644 --- a/chromium/media/base/video_frame_pool.cc +++ b/chromium/media/base/video_frame_pool.cc @@ -86,7 +86,7 @@ scoped_refptr<VideoFrame> VideoFramePool::PoolImpl::CreateFrame( } scoped_refptr<VideoFrame> wrapped_frame = VideoFrame::WrapVideoFrame( - frame, frame->visible_rect(), frame->natural_size()); + frame, frame->format(), frame->visible_rect(), frame->natural_size()); wrapped_frame->AddDestructionObserver( base::Bind(&VideoFramePool::PoolImpl::FrameReleased, this, frame)); return wrapped_frame; diff --git a/chromium/media/base/video_frame_unittest.cc b/chromium/media/base/video_frame_unittest.cc index 18e18932599..79652bb43cd 100644 --- a/chromium/media/base/video_frame_unittest.cc +++ b/chromium/media/base/video_frame_unittest.cc @@ -229,6 +229,8 @@ static void FrameNoLongerNeededCallback( TEST(VideoFrame, WrapVideoFrame) { const int kWidth = 4; const int kHeight = 4; + const base::TimeDelta kFrameDuration = base::TimeDelta::FromMicroseconds(42); + scoped_refptr<media::VideoFrame> frame; bool done_callback_was_run = false; { @@ -238,11 +240,12 @@ TEST(VideoFrame, WrapVideoFrame) { gfx::Rect visible_rect(1, 1, 1, 1); gfx::Size natural_size = visible_rect.size(); + wrapped_frame->metadata()->SetTimeDelta( + media::VideoFrameMetadata::FRAME_DURATION, kFrameDuration); frame = media::VideoFrame::WrapVideoFrame( - wrapped_frame, visible_rect, natural_size); - frame->AddDestructionObserver( - base::Bind(&FrameNoLongerNeededCallback, wrapped_frame, - &done_callback_was_run)); + wrapped_frame, wrapped_frame->format(), visible_rect, natural_size); + frame->AddDestructionObserver(base::Bind( + &FrameNoLongerNeededCallback, wrapped_frame, &done_callback_was_run)); EXPECT_EQ(wrapped_frame->coded_size(), frame->coded_size()); EXPECT_EQ(wrapped_frame->data(media::VideoFrame::kYPlane), frame->data(media::VideoFrame::kYPlane)); @@ -250,6 +253,20 @@ TEST(VideoFrame, WrapVideoFrame) { EXPECT_EQ(visible_rect, frame->visible_rect()); EXPECT_NE(wrapped_frame->natural_size(), frame->natural_size()); EXPECT_EQ(natural_size, frame->natural_size()); + + // Verify metadata was copied to the wrapped frame. + base::TimeDelta frame_duration; + ASSERT_TRUE(frame->metadata()->GetTimeDelta( + media::VideoFrameMetadata::FRAME_DURATION, &frame_duration)); + + EXPECT_EQ(frame_duration, kFrameDuration); + + // Verify the metadata copy was a deep copy. + wrapped_frame->metadata()->Clear(); + EXPECT_NE( + wrapped_frame->metadata()->HasKey( + media::VideoFrameMetadata::FRAME_DURATION), + frame->metadata()->HasKey(media::VideoFrameMetadata::FRAME_DURATION)); } EXPECT_FALSE(done_callback_was_run); @@ -274,8 +291,8 @@ static void TextureCallback(gpu::SyncToken* called_sync_token, // Verify the gpu::MailboxHolder::ReleaseCallback is called when VideoFrame is // destroyed with the default release sync point. TEST(VideoFrame, TextureNoLongerNeededCallbackIsCalled) { - gpu::SyncToken called_sync_token(gpu::CommandBufferNamespace::GPU_IO, 0, 1, - 1); + gpu::SyncToken called_sync_token(gpu::CommandBufferNamespace::GPU_IO, 0, + gpu::CommandBufferId::FromUnsafeValue(1), 1); { scoped_refptr<VideoFrame> frame = VideoFrame::WrapNativeTexture( @@ -321,7 +338,8 @@ TEST(VideoFrame, const int kPlanesNum = 3; const gpu::CommandBufferNamespace kNamespace = gpu::CommandBufferNamespace::GPU_IO; - const uint64_t kCommandBufferId = 0x123; + const gpu::CommandBufferId kCommandBufferId = + gpu::CommandBufferId::FromUnsafeValue(0x123); gpu::Mailbox mailbox[kPlanesNum]; for (int i = 0; i < kPlanesNum; ++i) { mailbox[i].name[0] = 50 + 1; diff --git a/chromium/media/base/video_renderer.h b/chromium/media/base/video_renderer.h index 6acce776652..ddd47777daa 100644 --- a/chromium/media/base/video_renderer.h +++ b/chromium/media/base/video_renderer.h @@ -10,13 +10,13 @@ #include "base/memory/ref_counted.h" #include "base/time/time.h" #include "media/base/buffering_state.h" -#include "media/base/cdm_context.h" #include "media/base/media_export.h" #include "media/base/pipeline_status.h" #include "media/base/time_source.h" namespace media { +class CdmContext; class DemuxerStream; class VideoDecoder; class VideoFrame; @@ -35,8 +35,8 @@ class MEDIA_EXPORT VideoRenderer { // completion. If initialization fails, only |init_cb| (not |error_cb|) will // be called. // - // |set_cdm_ready_cb| is fired when a CDM is needed, i.e. when the |stream| is - // encrypted. + // |cdm_context| can be used to handle encrypted streams. May be null if the + // stream is not encrypted. // // |statistics_cb| is executed periodically with video rendering stats, such // as dropped frames. @@ -56,7 +56,7 @@ class MEDIA_EXPORT VideoRenderer { virtual void Initialize( DemuxerStream* stream, const PipelineStatusCB& init_cb, - const SetCdmReadyCB& set_cdm_ready_cb, + CdmContext* cdm_context, const StatisticsCB& statistics_cb, const BufferingStateCB& buffering_state_cb, const base::Closure& ended_cb, diff --git a/chromium/media/base/video_types.cc b/chromium/media/base/video_types.cc index e2f137d67dd..f8eeb3e1bb4 100644 --- a/chromium/media/base/video_types.cc +++ b/chromium/media/base/video_types.cc @@ -42,6 +42,18 @@ std::string VideoPixelFormatToString(VideoPixelFormat format) { return "PIXEL_FORMAT_MJPEG"; case PIXEL_FORMAT_MT21: return "PIXEL_FORMAT_MT21"; + case PIXEL_FORMAT_YUV420P9: + return "PIXEL_FORMAT_YUV420P9"; + case PIXEL_FORMAT_YUV420P10: + return "PIXEL_FORMAT_YUV420P10"; + case PIXEL_FORMAT_YUV422P9: + return "PIXEL_FORMAT_YUV422P9"; + case PIXEL_FORMAT_YUV422P10: + return "PIXEL_FORMAT_YUV422P10"; + case PIXEL_FORMAT_YUV444P9: + return "PIXEL_FORMAT_YUV444P9"; + case PIXEL_FORMAT_YUV444P10: + return "PIXEL_FORMAT_YUV444P10"; } NOTREACHED() << "Invalid VideoPixelFormat provided: " << format; return ""; @@ -57,6 +69,12 @@ bool IsYuvPlanar(VideoPixelFormat format) { case PIXEL_FORMAT_NV12: case PIXEL_FORMAT_NV21: case PIXEL_FORMAT_MT21: + case PIXEL_FORMAT_YUV420P9: + case PIXEL_FORMAT_YUV420P10: + case PIXEL_FORMAT_YUV422P9: + case PIXEL_FORMAT_YUV422P10: + case PIXEL_FORMAT_YUV444P9: + case PIXEL_FORMAT_YUV444P10: return true; case PIXEL_FORMAT_UNKNOWN: @@ -87,6 +105,12 @@ bool IsOpaque(VideoPixelFormat format) { case PIXEL_FORMAT_RGB24: case PIXEL_FORMAT_MJPEG: case PIXEL_FORMAT_MT21: + case PIXEL_FORMAT_YUV420P9: + case PIXEL_FORMAT_YUV420P10: + case PIXEL_FORMAT_YUV422P9: + case PIXEL_FORMAT_YUV422P10: + case PIXEL_FORMAT_YUV444P9: + case PIXEL_FORMAT_YUV444P10: return true; case PIXEL_FORMAT_YV12A: case PIXEL_FORMAT_ARGB: diff --git a/chromium/media/base/video_types.h b/chromium/media/base/video_types.h index 7cf196e677a..7590b1b3a25 100644 --- a/chromium/media/base/video_types.h +++ b/chromium/media/base/video_types.h @@ -46,9 +46,17 @@ enum VideoPixelFormat { // Row pitch = ((width+15)/16) * 16. // Plane size = Row pitch * (((height+31)/32)*32) PIXEL_FORMAT_MT21 = 15, + + PIXEL_FORMAT_YUV420P9 = 16, + PIXEL_FORMAT_YUV420P10 = 17, + PIXEL_FORMAT_YUV422P9 = 18, + PIXEL_FORMAT_YUV422P10 = 19, + PIXEL_FORMAT_YUV444P9 = 20, + PIXEL_FORMAT_YUV444P10 = 21, + // Please update UMA histogram enumeration when adding new formats here. PIXEL_FORMAT_MAX = - PIXEL_FORMAT_MT21, // Must always be equal to largest entry logged. + PIXEL_FORMAT_YUV444P10, // Must always be equal to largest entry logged. }; // Color space or color range used for the pixels. diff --git a/chromium/media/base/video_util.cc b/chromium/media/base/video_util.cc index c6286738244..d9954f8f8e7 100644 --- a/chromium/media/base/video_util.cc +++ b/chromium/media/base/video_util.cc @@ -14,6 +14,13 @@ namespace media { +namespace { + +// Empty method used for keeping a reference to the original media::VideoFrame. +void ReleaseOriginalFrame(const scoped_refptr<media::VideoFrame>& frame) {} + +} // namespace + gfx::Size GetNaturalSize(const gfx::Size& visible_size, int aspect_ratio_numerator, int aspect_ratio_denominator) { @@ -316,4 +323,20 @@ void CopyRGBToVideoFrame(const uint8_t* source, uv_stride); } +scoped_refptr<VideoFrame> WrapAsI420VideoFrame( + const scoped_refptr<VideoFrame>& frame) { + DCHECK_EQ(VideoFrame::STORAGE_OWNED_MEMORY, frame->storage_type()); + DCHECK_EQ(PIXEL_FORMAT_YV12A, frame->format()); + + scoped_refptr<media::VideoFrame> wrapped_frame = + media::VideoFrame::WrapVideoFrame(frame, PIXEL_FORMAT_I420, + frame->visible_rect(), + frame->natural_size()); + if (!wrapped_frame) + return nullptr; + wrapped_frame->AddDestructionObserver( + base::Bind(&ReleaseOriginalFrame, frame)); + return wrapped_frame; +} + } // namespace media diff --git a/chromium/media/base/video_util.h b/chromium/media/base/video_util.h index e51343344a0..2dec7e93710 100644 --- a/chromium/media/base/video_util.h +++ b/chromium/media/base/video_util.h @@ -7,6 +7,7 @@ #include <stdint.h> +#include "base/memory/ref_counted.h" #include "media/base/media_export.h" #include "ui/gfx/geometry/rect.h" #include "ui/gfx/geometry/size.h" @@ -92,6 +93,10 @@ MEDIA_EXPORT void CopyRGBToVideoFrame(const uint8_t* source, const gfx::Rect& region_in_frame, VideoFrame* frame); +// Converts a frame with YV12A format into I420 by dropping alpha channel. +MEDIA_EXPORT scoped_refptr<VideoFrame> WrapAsI420VideoFrame( + const scoped_refptr<VideoFrame>& frame); + } // namespace media #endif // MEDIA_BASE_VIDEO_UTIL_H_ diff --git a/chromium/media/base/yuv_convert_perftest.cc b/chromium/media/base/yuv_convert_perftest.cc index 3af1b7555ba..f92be0462e4 100644 --- a/chromium/media/base/yuv_convert_perftest.cc +++ b/chromium/media/base/yuv_convert_perftest.cc @@ -9,6 +9,7 @@ #include "base/files/file_util.h" #include "base/logging.h" #include "base/macros.h" +#include "base/memory/scoped_ptr.h" #include "base/path_service.h" #include "base/time/time.h" #include "build/build_config.h" diff --git a/chromium/media/base/yuv_convert_unittest.cc b/chromium/media/base/yuv_convert_unittest.cc index 77c48ce0b9c..a05e15a2e02 100644 --- a/chromium/media/base/yuv_convert_unittest.cc +++ b/chromium/media/base/yuv_convert_unittest.cc @@ -9,6 +9,7 @@ #include "base/cpu.h" #include "base/files/file_util.h" #include "base/logging.h" +#include "base/memory/scoped_ptr.h" #include "base/path_service.h" #include "build/build_config.h" #include "media/base/djb2.h" @@ -39,7 +40,8 @@ static const int kRGBSizeScaled = kScaledWidth * kScaledHeight * kBpp; static const int kRGB24Size = kSourceYSize * 3; static const int kRGBSizeConverted = kSourceYSize * kBpp; -#if !defined(ARCH_CPU_ARM_FAMILY) && !defined(ARCH_CPU_MIPS_FAMILY) +#if !defined(ARCH_CPU_ARM_FAMILY) && !defined(ARCH_CPU_MIPS_FAMILY) && \ + !defined(OS_ANDROID) static const int kSourceAOffset = kSourceYSize * 12 / 8; static const int kYUVA12Size = kSourceYSize * 20 / 8; #endif |