diff --git a/content/browser/gpu/gpu_process_host.cc b/content/browser/gpu/gpu_process_host.cc index 58fa17f847864e..fdf434b6a17d23 100644 --- a/content/browser/gpu/gpu_process_host.cc +++ b/content/browser/gpu/gpu_process_host.cc @@ -120,6 +120,7 @@ static const char* const kSwitchNames[] = { #endif #if defined(OS_WIN) switches::kEnableAcceleratedVpxDecode, + switches::kEnableMFH264Encoding, #endif switches::kEnableHeapProfiling, switches::kEnableLogging, diff --git a/content/gpu/gpu_main.cc b/content/gpu/gpu_main.cc index cc326628adae54..c1077998dca9b3 100644 --- a/content/gpu/gpu_main.cc +++ b/content/gpu/gpu_main.cc @@ -59,6 +59,7 @@ #include "base/win/scoped_com_initializer.h" #include "base/win/windows_version.h" #include "media/gpu/dxva_video_decode_accelerator_win.h" +#include "media/gpu/media_foundation_video_encode_accelerator_win.h" #include "sandbox/win/src/sandbox.h" #endif @@ -489,6 +490,7 @@ bool WarmUpSandbox(const base::CommandLine& command_line) { #if defined(OS_WIN) media::DXVAVideoDecodeAccelerator::PreSandboxInitialization(); + media::MediaFoundationVideoEncodeAccelerator::PreSandboxInitialization(); #endif return true; } diff --git a/media/base/media_switches.cc b/media/base/media_switches.cc index c5fe017b32e15e..5f710396399b52 100644 --- a/media/base/media_switches.cc +++ b/media/base/media_switches.cc @@ -55,6 +55,9 @@ const char kUseGpuMemoryBuffersForCapture[] = // for details. const char kEnableExclusiveAudio[] = "enable-exclusive-audio"; +// Enables H264 HW encode acceleration using Media Foundation for Windows. +const char kEnableMFH264Encoding[] = "enable-mf-h264-encoding"; + // 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. diff --git a/media/base/media_switches.h b/media/base/media_switches.h index 322f5525ce7da8..81d562dd24945a 100644 --- a/media/base/media_switches.h +++ b/media/base/media_switches.h @@ -37,6 +37,7 @@ MEDIA_EXPORT extern const char kUseGpuMemoryBuffersForCapture[]; #if defined(OS_WIN) MEDIA_EXPORT extern const char kEnableExclusiveAudio[]; +MEDIA_EXPORT extern const char kEnableMFH264Encoding[]; MEDIA_EXPORT extern const char kForceMediaFoundationVideoCapture[]; MEDIA_EXPORT extern const char kForceWaveAudio[]; MEDIA_EXPORT extern const char kTrySupportedChannelLayouts[]; diff --git a/media/base/win/BUILD.gn b/media/base/win/BUILD.gn index a90f7c9499ef05..f006c4c132f478 100644 --- a/media/base/win/BUILD.gn +++ b/media/base/win/BUILD.gn @@ -8,6 +8,8 @@ component("win") { defines = [ "MF_INITIALIZER_IMPLEMENTATION" ] set_sources_assignment_filter([]) sources = [ + "mf_helpers.cc", + "mf_helpers.h", "mf_initializer.cc", "mf_initializer.h", "mf_initializer_export.h", diff --git a/media/base/win/mf_helpers.cc b/media/base/win/mf_helpers.cc new file mode 100644 index 00000000000000..e5df966ae2c029 --- /dev/null +++ b/media/base/win/mf_helpers.cc @@ -0,0 +1,57 @@ +// 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/win/mf_helpers.h" + +namespace media { + +namespace mf { + +void LogDXVAError(int line) { + LOG(ERROR) << "Error in dxva_video_decode_accelerator_win.cc on line " + << line; +} + +IMFSample* CreateEmptySampleWithBuffer(uint32_t buffer_length, int align) { + CHECK_GT(buffer_length, 0U); + + base::win::ScopedComPtr sample; + HRESULT hr = MFCreateSample(sample.Receive()); + RETURN_ON_HR_FAILURE(hr, "MFCreateSample failed", NULL); + + base::win::ScopedComPtr buffer; + if (align == 0) { + // Note that MFCreateMemoryBuffer is same as MFCreateAlignedMemoryBuffer + // with the align argument being 0. + hr = MFCreateMemoryBuffer(buffer_length, buffer.Receive()); + } else { + hr = + MFCreateAlignedMemoryBuffer(buffer_length, align - 1, buffer.Receive()); + } + RETURN_ON_HR_FAILURE(hr, "Failed to create memory buffer for sample", NULL); + + hr = sample->AddBuffer(buffer.get()); + RETURN_ON_HR_FAILURE(hr, "Failed to add buffer to sample", NULL); + + buffer->SetCurrentLength(0); + return sample.Detach(); +} + +MediaBufferScopedPointer::MediaBufferScopedPointer(IMFMediaBuffer* media_buffer) + : media_buffer_(media_buffer), + buffer_(nullptr), + max_length_(0), + current_length_(0) { + HRESULT hr = media_buffer_->Lock(&buffer_, &max_length_, ¤t_length_); + CHECK(SUCCEEDED(hr)); +} + +MediaBufferScopedPointer::~MediaBufferScopedPointer() { + HRESULT hr = media_buffer_->Unlock(); + CHECK(SUCCEEDED(hr)); +} + +} // namespace mf + +} // namespace media \ No newline at end of file diff --git a/media/base/win/mf_helpers.h b/media/base/win/mf_helpers.h new file mode 100644 index 00000000000000..a99ecedbb3fdbf --- /dev/null +++ b/media/base/win/mf_helpers.h @@ -0,0 +1,77 @@ +// 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_WIN_MF_HELPERS_H_ +#define MEDIA_BASE_WIN_MF_HELPERS_H_ + +#include +#include + +#include "base/win/scoped_comptr.h" +#include "media/base/win/mf_initializer_export.h" + +namespace media { + +namespace mf { + +#define RETURN_ON_FAILURE(result, log, ret) \ + do { \ + if (!(result)) { \ + DLOG(ERROR) << log; \ + mf::LogDXVAError(__LINE__); \ + return ret; \ + } \ + } while (0) + +#define RETURN_ON_HR_FAILURE(result, log, ret) \ + RETURN_ON_FAILURE(SUCCEEDED(result), \ + log << ", HRESULT: 0x" << std::hex << result, ret); + +#define RETURN_AND_NOTIFY_ON_FAILURE(result, log, error_code, ret) \ + do { \ + if (!(result)) { \ + DVLOG(1) << log; \ + mf::LogDXVAError(__LINE__); \ + StopOnError(error_code); \ + return ret; \ + } \ + } while (0) + +#define RETURN_AND_NOTIFY_ON_HR_FAILURE(result, log, error_code, ret) \ + RETURN_AND_NOTIFY_ON_FAILURE(SUCCEEDED(result), \ + log << ", HRESULT: 0x" << std::hex << result, \ + error_code, ret); + +MF_INITIALIZER_EXPORT void LogDXVAError(int line); + +// Creates a Media Foundation sample with one buffer of length |buffer_length| +// on a |align|-byte boundary. Alignment must be a perfect power of 2 or 0. +MF_INITIALIZER_EXPORT IMFSample* CreateEmptySampleWithBuffer( + uint32_t buffer_length, + int align); + +// Provides scoped access to the underlying buffer in an IMFMediaBuffer +// instance. +class MF_INITIALIZER_EXPORT MediaBufferScopedPointer { + public: + MediaBufferScopedPointer(IMFMediaBuffer* media_buffer); + ~MediaBufferScopedPointer(); + + uint8_t* get() { return buffer_; } + DWORD current_length() const { return current_length_; } + + private: + base::win::ScopedComPtr media_buffer_; + uint8_t* buffer_; + DWORD max_length_; + DWORD current_length_; + + DISALLOW_COPY_AND_ASSIGN(MediaBufferScopedPointer); +}; + +} // namespace mf + +} // namespace media + +#endif // MEDIA_BASE_WIN_MF_HELPERS_H_ \ No newline at end of file diff --git a/media/gpu/BUILD.gn b/media/gpu/BUILD.gn index 57ec14a6dcf6cf..e8c264454dae52 100644 --- a/media/gpu/BUILD.gn +++ b/media/gpu/BUILD.gn @@ -337,6 +337,8 @@ component("gpu") { "dxva_picture_buffer_win.h", "dxva_video_decode_accelerator_win.cc", "dxva_video_decode_accelerator_win.h", + "media_foundation_video_encode_accelerator_win.cc", + "media_foundation_video_encode_accelerator_win.h", ] configs += [ # TODO(jschuh): crbug.com/167187 fix size_t to int truncations. @@ -344,7 +346,10 @@ component("gpu") { "//third_party/khronos:khronos_headers", ] public_deps += [ "//media/base/win" ] - deps += [ "//third_party/angle:includes" ] + deps += [ + "//third_party/angle:includes", + "//third_party/libyuv", + ] libs += [ "d3d9.lib", "d3d11.lib", @@ -434,7 +439,7 @@ if (is_win || is_android || is_chromeos) { } } -if (is_chromeos || is_mac) { +if (is_chromeos || is_mac || is_win) { test("video_encode_accelerator_unittest") { deps = [ "//base", diff --git a/media/gpu/dxva_video_decode_accelerator_win.cc b/media/gpu/dxva_video_decode_accelerator_win.cc index ce43d8713c650b..8f3f2575c3e7fa 100644 --- a/media/gpu/dxva_video_decode_accelerator_win.cc +++ b/media/gpu/dxva_video_decode_accelerator_win.cc @@ -39,6 +39,7 @@ #include "build/build_config.h" #include "gpu/command_buffer/service/gpu_preferences.h" #include "gpu/config/gpu_driver_bug_workarounds.h" +#include "media/base/win/mf_helpers.h" #include "media/base/win/mf_initializer.h" #include "media/gpu/dxva_picture_buffer_win.h" #include "media/video/video_decode_accelerator.h" @@ -187,42 +188,6 @@ static const DWORD g_IntelLegacyGPUList[] = { 0x102, 0x106, 0x116, 0x126, }; -// Provides scoped access to the underlying buffer in an IMFMediaBuffer -// instance. -class MediaBufferScopedPointer { - public: - explicit MediaBufferScopedPointer(IMFMediaBuffer* media_buffer) - : media_buffer_(media_buffer), - buffer_(nullptr), - max_length_(0), - current_length_(0) { - HRESULT hr = media_buffer_->Lock(&buffer_, &max_length_, ¤t_length_); - CHECK(SUCCEEDED(hr)); - } - - ~MediaBufferScopedPointer() { - HRESULT hr = media_buffer_->Unlock(); - CHECK(SUCCEEDED(hr)); - } - - uint8_t* get() { return buffer_; } - - DWORD current_length() const { return current_length_; } - - private: - base::win::ScopedComPtr media_buffer_; - uint8_t* buffer_; - DWORD max_length_; - DWORD current_length_; - - DISALLOW_COPY_AND_ASSIGN(MediaBufferScopedPointer); -}; - -void LogDXVAError(int line) { - LOG(ERROR) << "Error in dxva_video_decode_accelerator_win.cc on line " - << line; -} - } // namespace namespace media { @@ -235,34 +200,6 @@ static const VideoCodecProfile kSupportedProfiles[] = { CreateDXGIDeviceManager DXVAVideoDecodeAccelerator::create_dxgi_device_manager_ = NULL; -#define RETURN_ON_FAILURE(result, log, ret) \ - do { \ - if (!(result)) { \ - DLOG(ERROR) << log; \ - LogDXVAError(__LINE__); \ - return ret; \ - } \ - } while (0) - -#define RETURN_ON_HR_FAILURE(result, log, ret) \ - RETURN_ON_FAILURE(SUCCEEDED(result), \ - log << ", HRESULT: 0x" << std::hex << result, ret); - -#define RETURN_AND_NOTIFY_ON_FAILURE(result, log, error_code, ret) \ - do { \ - if (!(result)) { \ - DVLOG(1) << log; \ - LogDXVAError(__LINE__); \ - StopOnError(error_code); \ - return ret; \ - } \ - } while (0) - -#define RETURN_AND_NOTIFY_ON_HR_FAILURE(result, log, error_code, ret) \ - RETURN_AND_NOTIFY_ON_FAILURE(SUCCEEDED(result), \ - log << ", HRESULT: 0x" << std::hex << result, \ - error_code, ret); - enum { // Maximum number of iterations we allow before aborting the attempt to flush // the batched queries to the driver and allow torn/corrupt frames to be @@ -282,41 +219,6 @@ enum { kAcquireSyncWaitMs = 0, }; -static IMFSample* CreateEmptySample() { - base::win::ScopedComPtr sample; - HRESULT hr = MFCreateSample(sample.Receive()); - RETURN_ON_HR_FAILURE(hr, "MFCreateSample failed", NULL); - return sample.Detach(); -} - -// Creates a Media Foundation sample with one buffer of length |buffer_length| -// on a |align|-byte boundary. Alignment must be a perfect power of 2 or 0. -static IMFSample* CreateEmptySampleWithBuffer(uint32_t buffer_length, - int align) { - CHECK_GT(buffer_length, 0U); - - base::win::ScopedComPtr sample; - sample.Attach(CreateEmptySample()); - - base::win::ScopedComPtr buffer; - HRESULT hr = E_FAIL; - if (align == 0) { - // Note that MFCreateMemoryBuffer is same as MFCreateAlignedMemoryBuffer - // with the align argument being 0. - hr = MFCreateMemoryBuffer(buffer_length, buffer.Receive()); - } else { - hr = - MFCreateAlignedMemoryBuffer(buffer_length, align - 1, buffer.Receive()); - } - RETURN_ON_HR_FAILURE(hr, "Failed to create memory buffer for sample", NULL); - - hr = sample->AddBuffer(buffer.get()); - RETURN_ON_HR_FAILURE(hr, "Failed to add buffer to sample", NULL); - - buffer->SetCurrentLength(0); - return sample.Detach(); -} - // Creates a Media Foundation sample with one buffer containing a copy of the // given Annex B stream data. // If duration and sample time are not known, provide 0. @@ -330,7 +232,7 @@ static IMFSample* CreateInputSample(const uint8_t* stream, CHECK_GT(size, 0U); base::win::ScopedComPtr sample; sample.Attach( - CreateEmptySampleWithBuffer(std::max(min_size, size), alignment)); + mf::CreateEmptySampleWithBuffer(std::max(min_size, size), alignment)); RETURN_ON_FAILURE(sample.get(), "Failed to create empty sample", NULL); base::win::ScopedComPtr buffer; @@ -2751,7 +2653,7 @@ HRESULT DXVAVideoDecodeAccelerator::CheckConfigChanged(IMFSample* sample, HRESULT hr = sample->GetBufferByIndex(0, buffer.Receive()); RETURN_ON_HR_FAILURE(hr, "Failed to get buffer from input sample", hr); - MediaBufferScopedPointer scoped_media_buffer(buffer.get()); + mf::MediaBufferScopedPointer scoped_media_buffer(buffer.get()); if (!config_change_detector_->DetectConfig( scoped_media_buffer.get(), scoped_media_buffer.current_length())) { diff --git a/media/gpu/ipc/service/gpu_video_encode_accelerator.cc b/media/gpu/ipc/service/gpu_video_encode_accelerator.cc index dc4e51591bd843..4c48a9fd0ef806 100644 --- a/media/gpu/ipc/service/gpu_video_encode_accelerator.cc +++ b/media/gpu/ipc/service/gpu_video_encode_accelerator.cc @@ -35,6 +35,9 @@ #include "media/gpu/android_video_encode_accelerator.h" #elif defined(OS_MACOSX) #include "media/gpu/vt_video_encode_accelerator_mac.h" +#elif defined(OS_WIN) +#include "media/base/media_switches.h" +#include "media/gpu/media_foundation_video_encode_accelerator_win.h" #endif namespace media { @@ -207,6 +210,13 @@ GpuVideoEncodeAccelerator::CreateVEAFps( #endif #if defined(OS_MACOSX) create_vea_fps.push_back(&GpuVideoEncodeAccelerator::CreateVTVEA); +#endif +#if defined(OS_WIN) + if (base::CommandLine::ForCurrentProcess()->HasSwitch( + switches::kEnableMFH264Encoding)) { + create_vea_fps.push_back( + &GpuVideoEncodeAccelerator::CreateMediaFoundationVEA); + } #endif return create_vea_fps; } @@ -250,6 +260,15 @@ GpuVideoEncodeAccelerator::CreateVTVEA() { } #endif +#if defined(OS_WIN) +// static +std::unique_ptr +GpuVideoEncodeAccelerator::CreateMediaFoundationVEA() { + return base::WrapUnique( + new MediaFoundationVideoEncodeAccelerator()); +} +#endif + void GpuVideoEncodeAccelerator::OnEncode( const AcceleratedVideoEncoderMsg_Encode_Params& params) { DVLOG(3) << "GpuVideoEncodeAccelerator::OnEncode: frame_id = " diff --git a/media/gpu/ipc/service/gpu_video_encode_accelerator.h b/media/gpu/ipc/service/gpu_video_encode_accelerator.h index 9b86c08d2a38d3..c928d6e9858624 100644 --- a/media/gpu/ipc/service/gpu_video_encode_accelerator.h +++ b/media/gpu/ipc/service/gpu_video_encode_accelerator.h @@ -92,6 +92,10 @@ class GpuVideoEncodeAccelerator #if defined(OS_MACOSX) static std::unique_ptr CreateVTVEA(); #endif +#if defined(OS_WIN) + static std::unique_ptr + CreateMediaFoundationVEA(); +#endif // IPC handlers, proxying VideoEncodeAccelerator for the renderer // process. diff --git a/media/gpu/media_foundation_video_encode_accelerator_win.cc b/media/gpu/media_foundation_video_encode_accelerator_win.cc new file mode 100644 index 00000000000000..bd15bb2089421f --- /dev/null +++ b/media/gpu/media_foundation_video_encode_accelerator_win.cc @@ -0,0 +1,562 @@ +// 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/gpu/media_foundation_video_encode_accelerator_win.h" + +#pragma warning(push) +#pragma warning(disable : 4800) // Disable warning for added padding. + +#include +#include +#include + +#include +#include + +#include "base/threading/sequenced_task_runner_handle.h" +#include "base/win/scoped_co_mem.h" +#include "base/win/scoped_variant.h" +#include "base/win/windows_version.h" +#include "media/base/win/mf_helpers.h" +#include "media/base/win/mf_initializer.h" +#include "third_party/libyuv/include/libyuv.h" + +using base::win::ScopedComPtr; +using media::mf::MediaBufferScopedPointer; + +namespace media { + +namespace { + +const size_t kMaxFrameRateNumerator = 30; +const size_t kMaxFrameRateDenominator = 1; +const size_t kMaxResolutionWidth = 4096; +const size_t kMaxResolutionHeight = 2160; +const size_t kNumInputBuffers = 3; +const size_t kOneSecondInMicroseconds = 1000000; +const size_t kOutputSampleBufferSizeRatio = 4; + +constexpr const wchar_t* const kMediaFoundationVideoEncoderDLLs[] = { + L"mf.dll", L"mfplat.dll", +}; + +} // namespace + +class MediaFoundationVideoEncodeAccelerator::EncodeOutput { + public: + EncodeOutput(uint32_t size, bool key_frame, base::TimeDelta timestamp) + : keyframe(key_frame), capture_timestamp(timestamp), data_(size) {} + + uint8_t* memory() { return data_.data(); } + + int size() const { return static_cast(data_.size()); } + + const bool keyframe; + const base::TimeDelta capture_timestamp; + + private: + std::vector data_; + + DISALLOW_COPY_AND_ASSIGN(EncodeOutput); +}; + +struct MediaFoundationVideoEncodeAccelerator::BitstreamBufferRef { + BitstreamBufferRef(int32_t id, + std::unique_ptr shm, + size_t size) + : id(id), shm(std::move(shm)), size(size) {} + const int32_t id; + const std::unique_ptr shm; + const size_t size; + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(BitstreamBufferRef); +}; + +MediaFoundationVideoEncodeAccelerator::MediaFoundationVideoEncodeAccelerator() + : client_task_runner_(base::SequencedTaskRunnerHandle::Get()), + encoder_thread_("MFEncoderThread"), + encoder_task_weak_factory_(this) {} + +MediaFoundationVideoEncodeAccelerator:: + ~MediaFoundationVideoEncodeAccelerator() { + DVLOG(3) << __FUNCTION__; + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + + DCHECK(!encoder_thread_.IsRunning()); + DCHECK(!encoder_task_weak_factory_.HasWeakPtrs()); +} + +VideoEncodeAccelerator::SupportedProfiles +MediaFoundationVideoEncodeAccelerator::GetSupportedProfiles() { + DVLOG(3) << __FUNCTION__; + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + + SupportedProfiles profiles; + if (base::win::GetVersion() < base::win::VERSION_WIN8) { + DLOG(ERROR) << "Windows versions earlier than 8 are not supported."; + return profiles; + } + + SupportedProfile profile; + // More profiles can be supported here, but they should be available in SW + // fallback as well. + profile.profile = H264PROFILE_BASELINE; + profile.max_framerate_numerator = kMaxFrameRateNumerator; + profile.max_framerate_denominator = kMaxFrameRateDenominator; + profile.max_resolution = gfx::Size(kMaxResolutionWidth, kMaxResolutionHeight); + profiles.push_back(profile); + return profiles; +} + +bool MediaFoundationVideoEncodeAccelerator::Initialize( + VideoPixelFormat format, + const gfx::Size& input_visible_size, + VideoCodecProfile output_profile, + uint32_t initial_bitrate, + Client* client) { + DVLOG(3) << __FUNCTION__ + << ": input_format=" << VideoPixelFormatToString(format) + << ", input_visible_size=" << input_visible_size.ToString() + << ", output_profile=" << output_profile + << ", initial_bitrate=" << initial_bitrate; + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + + if (PIXEL_FORMAT_I420 != format) { + DLOG(ERROR) << "Input format not supported= " + << VideoPixelFormatToString(format); + return false; + } + + if (H264PROFILE_BASELINE != output_profile) { + DLOG(ERROR) << "Output profile not supported= " << output_profile; + return false; + } + + for (const wchar_t* mfdll : kMediaFoundationVideoEncoderDLLs) { + if (!::GetModuleHandle(mfdll)) { + DLOG(ERROR) << mfdll << " is required for encoding"; + return false; + } + } + + encoder_thread_.init_com_with_mta(false); + if (!encoder_thread_.Start()) { + DLOG(ERROR) << "Failed spawning encoder thread."; + return false; + } + encoder_thread_task_runner_ = encoder_thread_.task_runner(); + + InitializeMediaFoundation(); + + uint32_t flags = MFT_ENUM_FLAG_HARDWARE | MFT_ENUM_FLAG_SORTANDFILTER; + MFT_REGISTER_TYPE_INFO input_info; + input_info.guidMajorType = MFMediaType_Video; + input_info.guidSubtype = MFVideoFormat_NV12; + MFT_REGISTER_TYPE_INFO output_info; + output_info.guidMajorType = MFMediaType_Video; + output_info.guidSubtype = MFVideoFormat_H264; + + base::win::ScopedCoMem CLSIDs; + uint32_t count = 0; + HRESULT hr = MFTEnum(MFT_CATEGORY_VIDEO_ENCODER, flags, NULL, &output_info, + NULL, &CLSIDs, &count); + RETURN_ON_HR_FAILURE(hr, "Couldn't enumerate hardware encoder", false); + RETURN_ON_FAILURE((count > 0), "No HW encoder found", false); + DVLOG(3) << "HW encoder(s) found: " << count; + hr = encoder_.CreateInstance(CLSIDs[0]); + RETURN_ON_HR_FAILURE(hr, "Couldn't activate hardware encoder", false); + + client_ptr_factory_.reset(new base::WeakPtrFactory(client)); + client_ = client_ptr_factory_->GetWeakPtr(); + input_visible_size_ = input_visible_size; + frame_rate_ = kMaxFrameRateNumerator / kMaxFrameRateDenominator; + target_bitrate_ = initial_bitrate; + bitstream_buffer_size_ = input_visible_size.GetArea(); + + u_plane_offset_ = + VideoFrame::PlaneSize(PIXEL_FORMAT_I420, VideoFrame::kYPlane, + input_visible_size_) + .GetArea(); + v_plane_offset_ = + u_plane_offset_ + + VideoFrame::PlaneSize(PIXEL_FORMAT_I420, VideoFrame::kUPlane, + input_visible_size_) + .GetArea(); + + if (!InitializeInputOutputSamples()) { + DLOG(ERROR) << "Failed initializing input-output samples."; + return false; + } + + if (!SetEncoderModes()) { + DLOG(ERROR) << "Failed setting encoder parameters."; + return false; + } + + hr = encoder_->ProcessMessage(MFT_MESSAGE_NOTIFY_BEGIN_STREAMING, NULL); + RETURN_ON_HR_FAILURE(hr, "Couldn't set ProcessMessage", false); + + client_task_runner_->PostTask( + FROM_HERE, + base::Bind(&Client::RequireBitstreamBuffers, client_, kNumInputBuffers, + input_visible_size_, bitstream_buffer_size_)); + return SUCCEEDED(hr); +} + +void MediaFoundationVideoEncodeAccelerator::Encode( + const scoped_refptr& frame, + bool force_keyframe) { + DVLOG(3) << __FUNCTION__; + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + + encoder_thread_task_runner_->PostTask( + FROM_HERE, base::Bind(&MediaFoundationVideoEncodeAccelerator::EncodeTask, + encoder_task_weak_factory_.GetWeakPtr(), frame, + force_keyframe)); +} + +void MediaFoundationVideoEncodeAccelerator::UseOutputBitstreamBuffer( + const BitstreamBuffer& buffer) { + DVLOG(3) << __FUNCTION__ << ": buffer size=" << buffer.size(); + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + + if (buffer.size() < bitstream_buffer_size_) { + DLOG(ERROR) << "Output BitstreamBuffer isn't big enough: " << buffer.size() + << " vs. " << bitstream_buffer_size_; + client_->NotifyError(kInvalidArgumentError); + return; + } + + std::unique_ptr shm( + new base::SharedMemory(buffer.handle(), false)); + if (!shm->Map(buffer.size())) { + DLOG(ERROR) << "Failed mapping shared memory."; + client_->NotifyError(kPlatformFailureError); + return; + } + + std::unique_ptr buffer_ref( + new BitstreamBufferRef(buffer.id(), std::move(shm), buffer.size())); + encoder_thread_task_runner_->PostTask( + FROM_HERE, + base::Bind( + &MediaFoundationVideoEncodeAccelerator::UseOutputBitstreamBufferTask, + encoder_task_weak_factory_.GetWeakPtr(), base::Passed(&buffer_ref))); +} + +void MediaFoundationVideoEncodeAccelerator::RequestEncodingParametersChange( + uint32_t bitrate, + uint32_t framerate) { + DVLOG(3) << __FUNCTION__ << ": bitrate=" << bitrate + << ": framerate=" << framerate; + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + + encoder_thread_task_runner_->PostTask( + FROM_HERE, + base::Bind(&MediaFoundationVideoEncodeAccelerator:: + RequestEncodingParametersChangeTask, + encoder_task_weak_factory_.GetWeakPtr(), bitrate, framerate)); +} + +void MediaFoundationVideoEncodeAccelerator::Destroy() { + DVLOG(3) << __FUNCTION__; + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + + // Cancel all callbacks. + client_ptr_factory_.reset(); + + if (encoder_thread_.IsRunning()) { + encoder_thread_task_runner_->PostTask( + FROM_HERE, + base::Bind(&MediaFoundationVideoEncodeAccelerator::DestroyTask, + encoder_task_weak_factory_.GetWeakPtr())); + encoder_thread_.Stop(); + } + + delete this; +} + +// static +void MediaFoundationVideoEncodeAccelerator::PreSandboxInitialization() { + for (const wchar_t* mfdll : kMediaFoundationVideoEncoderDLLs) + ::LoadLibrary(mfdll); +} + +bool MediaFoundationVideoEncodeAccelerator::InitializeInputOutputSamples() { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + + HRESULT hr = encoder_->GetStreamLimits( + &input_stream_count_min_, &input_stream_count_max_, + &output_stream_count_min_, &output_stream_count_max_); + RETURN_ON_HR_FAILURE(hr, "Couldn't query stream limits", false); + DVLOG(3) << "Stream limits: " << input_stream_count_min_ << "," + << input_stream_count_max_ << "," << output_stream_count_min_ << "," + << output_stream_count_max_; + + // Initialize output parameters. + base::win::ScopedComPtr imf_output_media_type; + hr = MFCreateMediaType(imf_output_media_type.Receive()); + RETURN_ON_HR_FAILURE(hr, "Couldn't create media type", false); + hr = imf_output_media_type->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video); + RETURN_ON_HR_FAILURE(hr, "Couldn't set media type", false); + hr = imf_output_media_type->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_H264); + RETURN_ON_HR_FAILURE(hr, "Couldn't set video format", false); + hr = imf_output_media_type->SetUINT32(MF_MT_AVG_BITRATE, target_bitrate_); + RETURN_ON_HR_FAILURE(hr, "Couldn't set bitrate", false); + hr = MFSetAttributeRatio(imf_output_media_type.get(), MF_MT_FRAME_RATE, + frame_rate_, kMaxFrameRateDenominator); + RETURN_ON_HR_FAILURE(hr, "Couldn't set frame rate", false); + hr = MFSetAttributeSize(imf_output_media_type.get(), MF_MT_FRAME_SIZE, + input_visible_size_.width(), + input_visible_size_.height()); + RETURN_ON_HR_FAILURE(hr, "Couldn't set frame size", false); + hr = imf_output_media_type->SetUINT32(MF_MT_INTERLACE_MODE, + MFVideoInterlace_Progressive); + RETURN_ON_HR_FAILURE(hr, "Couldn't set interlace mode", false); + hr = imf_output_media_type->SetUINT32(MF_MT_MPEG2_PROFILE, + eAVEncH264VProfile_Base); + RETURN_ON_HR_FAILURE(hr, "Couldn't set codec profile", false); + hr = encoder_->SetOutputType(0, imf_output_media_type.get(), 0); + RETURN_ON_HR_FAILURE(hr, "Couldn't set output media type", false); + + // Initialize input parameters. + base::win::ScopedComPtr imf_input_media_type; + hr = MFCreateMediaType(imf_input_media_type.Receive()); + RETURN_ON_HR_FAILURE(hr, "Couldn't create media type", false); + hr = imf_input_media_type->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video); + RETURN_ON_HR_FAILURE(hr, "Couldn't set media type", false); + hr = imf_input_media_type->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_YV12); + RETURN_ON_HR_FAILURE(hr, "Couldn't set video format", false); + hr = MFSetAttributeRatio(imf_input_media_type.get(), MF_MT_FRAME_RATE, + frame_rate_, kMaxFrameRateDenominator); + RETURN_ON_HR_FAILURE(hr, "Couldn't set frame rate", false); + hr = MFSetAttributeSize(imf_input_media_type.get(), MF_MT_FRAME_SIZE, + input_visible_size_.width(), + input_visible_size_.height()); + RETURN_ON_HR_FAILURE(hr, "Couldn't set frame size", false); + hr = imf_input_media_type->SetUINT32(MF_MT_INTERLACE_MODE, + MFVideoInterlace_Progressive); + RETURN_ON_HR_FAILURE(hr, "Couldn't set interlace mode", false); + hr = encoder_->SetInputType(0, imf_input_media_type.get(), 0); + RETURN_ON_HR_FAILURE(hr, "Couldn't set input media type", false); + + input_sample_.Attach(mf::CreateEmptySampleWithBuffer( + VideoFrame::AllocationSize(PIXEL_FORMAT_I420, input_visible_size_), 2)); + output_sample_.Attach(mf::CreateEmptySampleWithBuffer( + bitstream_buffer_size_ * kOutputSampleBufferSizeRatio, 2)); + + return SUCCEEDED(hr); +} + +bool MediaFoundationVideoEncodeAccelerator::SetEncoderModes() { + DCHECK(sequence_checker_.CalledOnValidSequencedThread()); + + HRESULT hr = encoder_.QueryInterface(IID_ICodecAPI, codec_api_.ReceiveVoid()); + RETURN_ON_HR_FAILURE(hr, "Couldn't get ICodecAPI", false); + VARIANT var; + var.vt = VT_UI4; + var.ulVal = eAVEncCommonRateControlMode_CBR; + hr = codec_api_->SetValue(&CODECAPI_AVEncCommonRateControlMode, &var); + RETURN_ON_HR_FAILURE(hr, "Couldn't set CommonRateControlMode", false); + var.ulVal = target_bitrate_; + hr = codec_api_->SetValue(&CODECAPI_AVEncCommonMeanBitRate, &var); + RETURN_ON_HR_FAILURE(hr, "Couldn't set bitrate", false); + var.ulVal = eAVEncAdaptiveMode_FrameRate; + hr = codec_api_->SetValue(&CODECAPI_AVEncAdaptiveMode, &var); + RETURN_ON_HR_FAILURE(hr, "Couldn't set FrameRate", false); + var.vt = VT_BOOL; + var.boolVal = VARIANT_TRUE; + hr = codec_api_->SetValue(&CODECAPI_AVLowLatencyMode, &var); + RETURN_ON_HR_FAILURE(hr, "Couldn't set LowLatencyMode", false); + return SUCCEEDED(hr); +} + +void MediaFoundationVideoEncodeAccelerator::NotifyError( + VideoEncodeAccelerator::Error error) { + DCHECK(encoder_thread_task_runner_->BelongsToCurrentThread()); + client_task_runner_->PostTask( + FROM_HERE, base::Bind(&Client::NotifyError, client_, error)); +} + +void MediaFoundationVideoEncodeAccelerator::EncodeTask( + const scoped_refptr& frame, + bool force_keyframe) { + DVLOG(3) << __FUNCTION__; + DCHECK(encoder_thread_task_runner_->BelongsToCurrentThread()); + + base::win::ScopedComPtr input_buffer; + input_sample_->GetBufferByIndex(0, input_buffer.Receive()); + + { + MediaBufferScopedPointer scoped_buffer(input_buffer.get()); + DCHECK(scoped_buffer.get()); + libyuv::I420Copy(frame->visible_data(VideoFrame::kYPlane), + frame->stride(VideoFrame::kYPlane), + frame->visible_data(VideoFrame::kVPlane), + frame->stride(VideoFrame::kVPlane), + frame->visible_data(VideoFrame::kUPlane), + frame->stride(VideoFrame::kUPlane), scoped_buffer.get(), + frame->stride(VideoFrame::kYPlane), + scoped_buffer.get() + u_plane_offset_, + frame->stride(VideoFrame::kUPlane), + scoped_buffer.get() + v_plane_offset_, + frame->stride(VideoFrame::kVPlane), + input_visible_size_.width(), input_visible_size_.height()); + } + + input_sample_->SetSampleTime(frame->timestamp().InMicroseconds() * 10); + input_sample_->SetSampleDuration(kOneSecondInMicroseconds / frame_rate_); + HRESULT hr = encoder_->ProcessInput(0, input_sample_.get(), 0); + // According to MSDN, if encoder returns MF_E_NOTACCEPTING, we need to try + // processing the output. This error indicates that encoder does not accept + // any more input data. + if (hr == MF_E_NOTACCEPTING) { + DVLOG(3) << "MF_E_NOTACCEPTING"; + ProcessOutput(); + hr = encoder_->ProcessInput(0, input_sample_.get(), 0); + if (!SUCCEEDED(hr)) { + NotifyError(kPlatformFailureError); + RETURN_ON_HR_FAILURE(hr, "Couldn't encode", ); + } + } else if (!SUCCEEDED(hr)) { + NotifyError(kPlatformFailureError); + RETURN_ON_HR_FAILURE(hr, "Couldn't encode", ); + } + DVLOG(3) << "Sent for encode " << hr; + + ProcessOutput(); +} + +void MediaFoundationVideoEncodeAccelerator::ProcessOutput() { + DVLOG(3) << __FUNCTION__; + DCHECK(encoder_thread_task_runner_->BelongsToCurrentThread()); + + MFT_OUTPUT_DATA_BUFFER output_data_buffer = {0}; + output_data_buffer.dwStreamID = 0; + output_data_buffer.dwStatus = 0; + output_data_buffer.pEvents = NULL; + output_data_buffer.pSample = output_sample_.get(); + DWORD status = 0; + HRESULT hr = encoder_->ProcessOutput(0, 1, &output_data_buffer, &status); + if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) { + DVLOG(3) << "MF_E_TRANSFORM_NEED_MORE_INPUT"; + return; + } + RETURN_ON_HR_FAILURE(hr, "Couldn't get encoded data", ); + DVLOG(3) << "Got encoded data " << hr; + + base::win::ScopedComPtr output_buffer; + hr = output_sample_->GetBufferByIndex(0, output_buffer.Receive()); + RETURN_ON_HR_FAILURE(hr, "Couldn't get buffer by index", ); + DWORD size = 0; + hr = output_buffer->GetCurrentLength(&size); + RETURN_ON_HR_FAILURE(hr, "Couldn't get buffer length", ); + + const bool keyframe = MFGetAttributeUINT32( + output_sample_.get(), MFSampleExtension_CleanPoint, false); + DVLOG(3) << "We HAVE encoded data with size:" << size << " keyframe " + << keyframe; + + if (bitstream_buffer_queue_.empty()) { + DVLOG(3) << "No bitstream buffers."; + // We need to copy the output so that encoding can continue. + std::unique_ptr encode_output( + new EncodeOutput(size, keyframe, base::Time::Now() - base::Time())); + { + MediaBufferScopedPointer scoped_buffer(output_buffer.get()); + memcpy(encode_output->memory(), scoped_buffer.get(), size); + } + encoder_output_queue_.push_back(std::move(encode_output)); + return; + } + + std::unique_ptr + buffer_ref = std::move(bitstream_buffer_queue_.front()); + bitstream_buffer_queue_.pop_front(); + + { + MediaBufferScopedPointer scoped_buffer(output_buffer.get()); + memcpy(buffer_ref->shm->memory(), scoped_buffer.get(), size); + } + + client_task_runner_->PostTask( + FROM_HERE, + base::Bind(&Client::BitstreamBufferReady, client_, buffer_ref->id, size, + keyframe, base::Time::Now() - base::Time())); + + // Keep calling ProcessOutput recursively until MF_E_TRANSFORM_NEED_MORE_INPUT + // is returned to flush out all the output. + ProcessOutput(); +} + +void MediaFoundationVideoEncodeAccelerator::UseOutputBitstreamBufferTask( + std::unique_ptr buffer_ref) { + DVLOG(3) << __FUNCTION__; + DCHECK(encoder_thread_task_runner_->BelongsToCurrentThread()); + + // If there is already EncodeOutput waiting, copy its output first. + if (!encoder_output_queue_.empty()) { + std::unique_ptr + encode_output = std::move(encoder_output_queue_.front()); + encoder_output_queue_.pop_front(); + ReturnBitstreamBuffer(std::move(encode_output), std::move(buffer_ref)); + return; + } + + bitstream_buffer_queue_.push_back(std::move(buffer_ref)); +} + +void MediaFoundationVideoEncodeAccelerator::ReturnBitstreamBuffer( + std::unique_ptr encode_output, + std::unique_ptr + buffer_ref) { + DVLOG(3) << __FUNCTION__; + DCHECK(encoder_thread_task_runner_->BelongsToCurrentThread()); + + memcpy(buffer_ref->shm->memory(), encode_output->memory(), + encode_output->size()); + client_task_runner_->PostTask( + FROM_HERE, + base::Bind(&Client::BitstreamBufferReady, client_, buffer_ref->id, + encode_output->size(), encode_output->keyframe, + encode_output->capture_timestamp)); +} + +void MediaFoundationVideoEncodeAccelerator::RequestEncodingParametersChangeTask( + uint32_t bitrate, + uint32_t framerate) { + DVLOG(3) << __FUNCTION__; + DCHECK(encoder_thread_task_runner_->BelongsToCurrentThread()); + + frame_rate_ = framerate ? framerate : 1; + target_bitrate_ = bitrate ? bitrate : 1; + + VARIANT var; + var.vt = VT_UI4; + var.ulVal = target_bitrate_; + HRESULT hr = codec_api_->SetValue(&CODECAPI_AVEncCommonMeanBitRate, &var); + RETURN_ON_HR_FAILURE(hr, "Couldn't set bitrate", ); + + base::win::ScopedComPtr imf_output_media_type; + hr = MFCreateMediaType(imf_output_media_type.Receive()); + RETURN_ON_HR_FAILURE(hr, "Couldn't create output media type", ); + hr = imf_output_media_type->SetUINT32(MF_MT_AVG_BITRATE, target_bitrate_); + RETURN_ON_HR_FAILURE(hr, "Couldn't set bitrate", ); + hr = MFSetAttributeRatio(imf_output_media_type.get(), MF_MT_FRAME_RATE, + frame_rate_, kMaxFrameRateDenominator); + RETURN_ON_HR_FAILURE(hr, "Couldn't set output type params", ); +} + +void MediaFoundationVideoEncodeAccelerator::DestroyTask() { + DVLOG(3) << __FUNCTION__; + DCHECK(encoder_thread_task_runner_->BelongsToCurrentThread()); + + // Cancel all encoder thread callbacks. + encoder_task_weak_factory_.InvalidateWeakPtrs(); + + encoder_.Release(); +} + +} // namespace content diff --git a/media/gpu/media_foundation_video_encode_accelerator_win.h b/media/gpu/media_foundation_video_encode_accelerator_win.h new file mode 100644 index 00000000000000..f6b9a0d837ba5d --- /dev/null +++ b/media/gpu/media_foundation_video_encode_accelerator_win.h @@ -0,0 +1,147 @@ +// 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_GPU_MEDIA_FOUNDATION_VIDEO_ENCODE_ACCELERATOR_WIN_H_ +#define MEDIA_GPU_MEDIA_FOUNDATION_VIDEO_ENCODE_ACCELERATOR_WIN_H_ + +#include +#include +#include +#include + +#include +#include + +#include "base/memory/weak_ptr.h" +#include "base/sequenced_task_runner.h" +#include "base/single_thread_task_runner.h" +#include "base/threading/thread_checker.h" +#include "base/win/scoped_comptr.h" +#include "media/gpu/media_gpu_export.h" +#include "media/video/video_encode_accelerator.h" + +namespace media { + +// Media Foundation implementation of the VideoEncodeAccelerator interface for +// Windows. +// This class saves the task runner on which it is constructed and returns +// encoded data to the client using that same task runner. This class has +// DCHECKs to makes sure that methods are called in sequence. It starts an +// internal encoder thread on which VideoEncodeAccelerator implementation tasks +// are posted. +class MEDIA_GPU_EXPORT MediaFoundationVideoEncodeAccelerator + : public VideoEncodeAccelerator { + public: + MediaFoundationVideoEncodeAccelerator(); + + // VideoEncodeAccelerator implementation. + VideoEncodeAccelerator::SupportedProfiles GetSupportedProfiles() override; + bool Initialize(VideoPixelFormat input_format, + const gfx::Size& input_visible_size, + VideoCodecProfile output_profile, + uint32_t initial_bitrate, + Client* client) override; + void Encode(const scoped_refptr& frame, + bool force_keyframe) override; + void UseOutputBitstreamBuffer(const BitstreamBuffer& buffer) override; + void RequestEncodingParametersChange(uint32_t bitrate, + uint32_t framerate) override; + void Destroy() override; + + // Preload dlls required for encoding. + static void PreSandboxInitialization(); + + protected: + ~MediaFoundationVideoEncodeAccelerator() override; + + private: + // Holds output buffers coming from the client ready to be filled. + struct BitstreamBufferRef; + + // Holds output buffers coming from the encoder. + class EncodeOutput; + + // Initializes and allocates memory for input and output samples. + bool InitializeInputOutputSamples(); + + // Initializes encoder parameters for real-time use. + bool SetEncoderModes(); + + // Helper function to notify the client of an error on |client_task_runner_|. + void NotifyError(VideoEncodeAccelerator::Error error); + + // Encoding tasks to be run on |encoder_thread_|. + void EncodeTask(const scoped_refptr& frame, bool force_keyframe); + + // Checks for and copies encoded output on |encoder_thread_|. + void ProcessOutput(); + + // Inserts the output buffers for reuse on |encoder_thread_|. + void UseOutputBitstreamBufferTask( + std::unique_ptr buffer_ref); + + // Copies EncodeOutput into a BitstreamBuffer and returns it to the |client_|. + void ReturnBitstreamBuffer( + std::unique_ptr encode_output, + std::unique_ptr + buffer_ref); + + // Changes encode parameters on |encoder_thread_|. + void RequestEncodingParametersChangeTask(uint32_t bitrate, + uint32_t framerate); + + // Destroys encode session on |encoder_thread_|. + void DestroyTask(); + + // Bitstream buffers ready to be used to return encoded output as a FIFO. + std::deque> bitstream_buffer_queue_; + + // EncodeOutput needs to be copied into a BitstreamBufferRef as a FIFO. + std::deque> encoder_output_queue_; + + gfx::Size input_visible_size_; + size_t bitstream_buffer_size_; + int32_t frame_rate_; + int32_t target_bitrate_; + size_t u_plane_offset_; + size_t v_plane_offset_; + + base::win::ScopedComPtr encoder_; + base::win::ScopedComPtr codec_api_; + + DWORD input_stream_count_min_; + DWORD input_stream_count_max_; + DWORD output_stream_count_min_; + DWORD output_stream_count_max_; + + base::win::ScopedComPtr input_sample_; + base::win::ScopedComPtr output_sample_; + + // To expose client callbacks from VideoEncodeAccelerator. + // NOTE: all calls to this object *MUST* be executed on |client_task_runner_|. + base::WeakPtr client_; + std::unique_ptr> client_ptr_factory_; + + // Our original calling task runner for the child thread. + const scoped_refptr client_task_runner_; + // Sequence checker to enforce that the methods of this object are called in + // sequence. + base::SequenceChecker sequence_checker_; + + // This thread services tasks posted from the VEA API entry points by the + // GPU child thread and CompressionCallback() posted from device thread. + base::Thread encoder_thread_; + scoped_refptr encoder_thread_task_runner_; + + // Declared last to ensure that all weak pointers are invalidated before + // other destructors run. + base::WeakPtrFactory + encoder_task_weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(MediaFoundationVideoEncodeAccelerator); +}; + +} // namespace media + +#endif // MEDIA_GPU_MEDIA_FOUNDATION_VIDEO_ENCODE_ACCELERATOR_WIN_H_ \ No newline at end of file diff --git a/media/gpu/video_encode_accelerator_unittest.cc b/media/gpu/video_encode_accelerator_unittest.cc index eb13008756e2ae..6f47b1742c5279 100644 --- a/media/gpu/video_encode_accelerator_unittest.cc +++ b/media/gpu/video_encode_accelerator_unittest.cc @@ -27,6 +27,7 @@ #include "base/strings/string_number_conversions.h" #include "base/strings/string_split.h" #include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" #include "base/threading/thread.h" #include "base/threading/thread_checker.h" #include "base/time/time.h" @@ -61,6 +62,8 @@ #endif // defined(ARCH_CPU_X86_FAMILY) #elif defined(OS_MACOSX) #include "media/gpu/vt_video_encode_accelerator_mac.h" +#elif defined(OS_WIN) +#include "media/gpu/media_foundation_video_encode_accelerator_win.h" #else #error The VideoEncodeAcceleratorUnittest is not supported on this platform. #endif @@ -110,6 +113,8 @@ const unsigned int kLoggedLatencyPercentiles[] = {50, 75, 95}; // "in_filename:width:height:profile:out_filename:requested_bitrate // :requested_framerate:requested_subsequent_bitrate // :requested_subsequent_framerate" +// Instead of ":", "," can be used as a seperator as well. Note that ":" does +// not work on Windows as it interferes with file paths. // - |in_filename| must be an I420 (YUV planar) raw stream // (see http://www.fourcc.org/yuv.php#IYUV). // - |width| and |height| are in pixels. @@ -130,11 +135,14 @@ const unsigned int kLoggedLatencyPercentiles[] = {50, 75, 95}; // of the stream. // Bitrate is only forced for tests that test bitrate. const char* g_default_in_filename = "bear_320x192_40frames.yuv"; -#if !defined(OS_MACOSX) -const char* g_default_in_parameters = ":320:192:1:out.h264:200000"; -#else -const char* g_default_in_parameters = ":320:192:0:out.h264:200000"; -#endif + +#if defined(OS_CHROMEOS) +const base::FilePath::CharType* g_default_in_parameters = + FILE_PATH_LITERAL(":320:192:1:out.h264:200000"); +#elif defined(OS_MACOSX) || defined(OS_WIN) +const base::FilePath::CharType* g_default_in_parameters = + FILE_PATH_LITERAL(",320,192,0,out.h264,200000"); +#endif // defined(OS_CHROMEOS) // Enabled by including a --fake_encoder flag to the command line invoking the // test. @@ -240,6 +248,25 @@ static bool IsVP8(VideoCodecProfile profile) { return profile >= VP8PROFILE_MIN && profile <= VP8PROFILE_MAX; } +// Helper functions to do string conversions. +static base::FilePath::StringType StringToFilePathStringType( + const std::string& str) { +#if defined(OS_WIN) + return base::UTF8ToWide(str); +#else + return str; +#endif // defined(OS_WIN) +} + +static std::string FilePathStringTypeToString( + const base::FilePath::StringType& str) { +#if defined(OS_WIN) + return base::WideToUTF8(str); +#else + return str; +#endif // defined(OS_WIN) +} + // ARM performs CPU cache management with CPU cache line granularity. We thus // need to ensure our buffers are CPU cache line-aligned (64 byte-aligned). // Otherwise newer kernels will refuse to accept them, and on older kernels @@ -290,7 +317,7 @@ static void CreateAlignedInputStreamFile(const gfx::Size& coded_size, padding_sizes[i] = padding_rows * coded_bpl[i] + Align64Bytes(size) - size; } - base::FilePath src_file(test_stream->in_filename); + base::FilePath src_file(StringToFilePathStringType(test_stream->in_filename)); int64_t src_file_size = 0; LOG_ASSERT(base::GetFileSize(src_file, &src_file_size)); @@ -328,11 +355,13 @@ static void CreateAlignedInputStreamFile(const gfx::Size& coded_size, } src.Close(); +#if defined(OS_POSIX) // Assert that memory mapped of file starts at 64 byte boundary. So each // plane of frames also start at 64 byte boundary. ASSERT_EQ(reinterpret_cast(&test_stream->aligned_in_file_data[0]) & 63, 0) << "File should be mapped at a 64 byte boundary"; +#endif // defined(OS_POSIX) LOG_ASSERT(test_stream->num_frames > 0UL); } @@ -350,13 +379,19 @@ static void ParseAndReadTestStreamData(const base::FilePath::StringType& data, // Parse each test stream data and read the input file. for (size_t index = 0; index < test_streams_data.size(); ++index) { std::vector fields = base::SplitString( - test_streams_data[index], base::FilePath::StringType(1, ':'), + test_streams_data[index], base::FilePath::StringType(1, ','), base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); + // Try using ":" as the seperator if "," isn't used. + if (fields.size() == 1U) { + fields = base::SplitString(test_streams_data[index], + base::FilePath::StringType(1, ':'), + base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); + } LOG_ASSERT(fields.size() >= 4U) << data; LOG_ASSERT(fields.size() <= 9U) << data; TestStream* test_stream = new TestStream(); - test_stream->in_filename = fields[0]; + test_stream->in_filename = FilePathStringTypeToString(fields[0]); int width, height; bool result = base::StringToInt(fields[1], &width); LOG_ASSERT(result); @@ -372,7 +407,7 @@ static void ParseAndReadTestStreamData(const base::FilePath::StringType& data, test_stream->requested_profile = static_cast(profile); if (fields.size() >= 5 && !fields[4].empty()) - test_stream->out_filename = fields[4]; + test_stream->out_filename = FilePathStringTypeToString(fields[4]); if (fields.size() >= 6 && !fields[5].empty()) LOG_ASSERT( @@ -635,7 +670,7 @@ class VideoFrameQualityValidator { void VerifyOutputFrame(const scoped_refptr& output_frame); void Decode(); - enum State { UNINITIALIZED, INITIALIZED, DECODING, ERROR }; + enum State { UNINITIALIZED, INITIALIZED, DECODING, DECODER_ERROR }; const VideoCodecProfile profile_; std::unique_ptr decoder_; @@ -698,7 +733,7 @@ void VideoFrameQualityValidator::InitializeCB(bool success) { decoder_state_ = INITIALIZED; Decode(); } else { - decoder_state_ = ERROR; + decoder_state_ = DECODER_ERROR; if (IsH264(profile_)) LOG(ERROR) << "Chromium does not support H264 decode. Try Chrome."; FAIL() << "Decoder initialization error"; @@ -716,7 +751,7 @@ void VideoFrameQualityValidator::DecodeDone(DecodeStatus status) { decoder_state_ = INITIALIZED; Decode(); } else { - decoder_state_ = ERROR; + decoder_state_ = DECODER_ERROR; FAIL() << "Unexpected decode status = " << status << ". Stop decoding."; decode_error_cb_.Run(); } @@ -727,7 +762,7 @@ void VideoFrameQualityValidator::FlushDone(DecodeStatus status) { } void VideoFrameQualityValidator::Flush() { - if (decoder_state_ != ERROR) { + if (decoder_state_ != DECODER_ERROR) { decode_buffers_.push(DecoderBuffer::CreateEOSBuffer()); Decode(); } @@ -735,7 +770,7 @@ void VideoFrameQualityValidator::Flush() { void VideoFrameQualityValidator::AddDecodeBuffer( const scoped_refptr& buffer) { - if (decoder_state_ != ERROR) { + if (decoder_state_ != DECODER_ERROR) { decode_buffers_.push(buffer); Decode(); } @@ -771,15 +806,18 @@ void VideoFrameQualityValidator::VerifyOutputFrame( VideoFrame::Columns(plane, kInputFormat, visible_size.width()); size_t stride = original_frame->stride(plane); - for (size_t i = 0; i < rows; i++) - for (size_t j = 0; j < columns; j++) + for (size_t i = 0; i < rows; i++) { + for (size_t j = 0; j < columns; j++) { difference += std::abs(original_plane[stride * i + j] - output_plane[stride * i + j]); + } + } } + // Divide the difference by the size of frame. difference /= VideoFrame::AllocationSize(kInputFormat, visible_size); EXPECT_TRUE(difference <= kDecodeSimilarityThreshold) - << "differrence = " << difference << " > decode similarity threshold"; + << "difference = " << difference << " > decode similarity threshold"; } class VEAClient : public VideoEncodeAccelerator::Client { @@ -818,6 +856,7 @@ class VEAClient : public VideoEncodeAccelerator::Client { std::unique_ptr CreateV4L2VEA(); std::unique_ptr CreateVaapiVEA(); std::unique_ptr CreateVTVEA(); + std::unique_ptr CreateMFVEA(); void SetState(ClientState new_state); @@ -1050,7 +1089,11 @@ VEAClient::VEAClient(TestStream* test_stream, if (save_to_file_) { LOG_ASSERT(!test_stream_->out_filename.empty()); +#if defined(OS_POSIX) base::FilePath out_filename(test_stream_->out_filename); +#elif defined(OS_WIN) + base::FilePath out_filename(base::UTF8ToWide(test_stream_->out_filename)); +#endif // This creates or truncates out_filename. // Without it, AppendToFile() will not work. EXPECT_EQ(0, base::WriteFile(out_filename, NULL, 0)); @@ -1102,12 +1145,22 @@ std::unique_ptr VEAClient::CreateVTVEA() { return encoder; } +std::unique_ptr VEAClient::CreateMFVEA() { + std::unique_ptr encoder; +#if defined(OS_WIN) + MediaFoundationVideoEncodeAccelerator::PreSandboxInitialization(); + encoder.reset(new MediaFoundationVideoEncodeAccelerator()); +#endif + return encoder; +} + void VEAClient::CreateEncoder() { DCHECK(thread_checker_.CalledOnValidThread()); LOG_ASSERT(!has_encoder()); std::unique_ptr encoders[] = { - CreateFakeVEA(), CreateV4L2VEA(), CreateVaapiVEA(), CreateVTVEA()}; + CreateFakeVEA(), CreateV4L2VEA(), CreateVaapiVEA(), CreateVTVEA(), + CreateMFVEA()}; DVLOG(1) << "Profile: " << test_stream_->requested_profile << ", initial bitrate: " << requested_bitrate_; @@ -1697,7 +1750,7 @@ TEST_P(VideoEncodeAcceleratorTest, TestSimpleEncode) { encoder_thread.Stop(); } -#if !defined(OS_MACOSX) +#if defined(OS_CHROMEOS) INSTANTIATE_TEST_CASE_P( SimpleEncode, VideoEncodeAcceleratorTest, @@ -1755,7 +1808,7 @@ INSTANTIATE_TEST_CASE_P( ::testing::Values( std::make_tuple(1, false, 0, false, false, false, false, false, true))); -#else +#elif defined(OS_MACOSX) || defined(OS_WIN) INSTANTIATE_TEST_CASE_P( SimpleEncode, VideoEncodeAcceleratorTest, @@ -1780,7 +1833,15 @@ INSTANTIATE_TEST_CASE_P(MultipleEncoders, false, false, false))); -#endif +#if defined(OS_WIN) +INSTANTIATE_TEST_CASE_P( + ForceBitrate, + VideoEncodeAcceleratorTest, + ::testing::Values( + std::make_tuple(1, false, 0, true, false, false, false, false, false))); +#endif // defined(OS_WIN) + +#endif // defined(OS_CHROMEOS) // TODO(posciak): more tests: // - async FeedEncoderWithOutput @@ -1800,8 +1861,8 @@ int main(int argc, char** argv) { std::unique_ptr test_stream_data( new base::FilePath::StringType( - media::GetTestDataFilePath(media::g_default_in_filename).value() + - media::g_default_in_parameters)); + media::GetTestDataFilePath(media::g_default_in_filename).value())); + test_stream_data->append(media::g_default_in_parameters); // Needed to enable DVLOG through --vmodule. logging::LoggingSettings settings; diff --git a/media/media.gyp b/media/media.gyp index 5ea6e73f630275..463647f85fff98 100644 --- a/media/media.gyp +++ b/media/media.gyp @@ -1739,6 +1739,8 @@ 'include_dirs': [ '..', ], 'defines': [ 'MF_INITIALIZER_IMPLEMENTATION', ], 'sources': [ + 'base/win/mf_helpers.cc', + 'base/win/mf_helpers.h', 'base/win/mf_initializer_export.h', 'base/win/mf_initializer.cc', 'base/win/mf_initializer.h', @@ -2132,7 +2134,7 @@ } ] }], - ['chromeos==1 or OS=="mac"', { + ['chromeos==1 or OS=="mac" or OS=="win"', { 'targets': [ { 'target_name': 'video_encode_accelerator_unittest', @@ -2164,6 +2166,11 @@ '../third_party/webrtc/common_video/common_video.gyp:common_video', ], }], + ['OS=="win"', { + 'dependencies': [ + '../media/media.gyp:mf_initializer', + ], + }], ['use_x11==1', { 'dependencies': [ '../ui/gfx/x/gfx_x11.gyp:gfx_x11', diff --git a/media/media_gpu.gypi b/media/media_gpu.gypi index d59a94bf43adf0..e507201f1c8cbf 100644 --- a/media/media_gpu.gypi +++ b/media/media_gpu.gypi @@ -328,6 +328,7 @@ 'dependencies': [ '../media/media.gyp:media', '../media/media.gyp:mf_initializer', + '../third_party/libyuv/libyuv.gyp:libyuv', '../ui/gl/gl.gyp:gl', '../ui/gl/init/gl_init.gyp:gl_init', ], @@ -358,6 +359,8 @@ 'gpu/dxva_picture_buffer_win.h', 'gpu/dxva_video_decode_accelerator_win.cc', 'gpu/dxva_video_decode_accelerator_win.h', + 'gpu/media_foundation_video_encode_accelerator_win.cc', + 'gpu/media_foundation_video_encode_accelerator_win.h', ], 'include_dirs': [ '<(DEPTH)/third_party/khronos',