| OLD | NEW |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "content/common/gpu/media/vt_video_encode_accelerator_mac.h" | 5 #include "media/gpu/vt_video_encode_accelerator_mac.h" |
| 6 | 6 |
| 7 #include "base/thread_task_runner_handle.h" | 7 #include "base/thread_task_runner_handle.h" |
| 8 #include "media/base/mac/coremedia_glue.h" | 8 #include "media/base/mac/coremedia_glue.h" |
| 9 #include "media/base/mac/corevideo_glue.h" | 9 #include "media/base/mac/corevideo_glue.h" |
| 10 #include "media/base/mac/video_frame_mac.h" | 10 #include "media/base/mac/video_frame_mac.h" |
| 11 | 11 |
| 12 namespace content { | 12 namespace media { |
| 13 | 13 |
| 14 namespace { | 14 namespace { |
| 15 | 15 |
| 16 // TODO(emircan): Check if we can find the actual system capabilities via | 16 // TODO(emircan): Check if we can find the actual system capabilities via |
| 17 // creating VTCompressionSessions with varying requirements. | 17 // creating VTCompressionSessions with varying requirements. |
| 18 // See crbug.com/584784. | 18 // See crbug.com/584784. |
| 19 const size_t kBitsPerByte = 8; | 19 const size_t kBitsPerByte = 8; |
| 20 const size_t kDefaultResolutionWidth = 640; | 20 const size_t kDefaultResolutionWidth = 640; |
| 21 const size_t kDefaultResolutionHeight = 480; | 21 const size_t kDefaultResolutionHeight = 480; |
| 22 const size_t kMaxFrameRateNumerator = 30; | 22 const size_t kMaxFrameRateNumerator = 30; |
| 23 const size_t kMaxFrameRateDenominator = 1; | 23 const size_t kMaxFrameRateDenominator = 1; |
| 24 const size_t kMaxResolutionWidth = 4096; | 24 const size_t kMaxResolutionWidth = 4096; |
| 25 const size_t kMaxResolutionHeight = 2160; | 25 const size_t kMaxResolutionHeight = 2160; |
| 26 const size_t kNumInputBuffers = 3; | 26 const size_t kNumInputBuffers = 3; |
| 27 | 27 |
| 28 } // namespace | 28 } // namespace |
| 29 | 29 |
| 30 struct VTVideoEncodeAccelerator::InProgressFrameEncode { | 30 struct VTVideoEncodeAccelerator::InProgressFrameEncode { |
| 31 InProgressFrameEncode(base::TimeDelta rtp_timestamp, | 31 InProgressFrameEncode(base::TimeDelta rtp_timestamp, base::TimeTicks ref_time) |
| 32 base::TimeTicks ref_time) | |
| 33 : timestamp(rtp_timestamp), reference_time(ref_time) {} | 32 : timestamp(rtp_timestamp), reference_time(ref_time) {} |
| 34 const base::TimeDelta timestamp; | 33 const base::TimeDelta timestamp; |
| 35 const base::TimeTicks reference_time; | 34 const base::TimeTicks reference_time; |
| 36 | 35 |
| 37 private: | 36 private: |
| 38 DISALLOW_IMPLICIT_CONSTRUCTORS(InProgressFrameEncode); | 37 DISALLOW_IMPLICIT_CONSTRUCTORS(InProgressFrameEncode); |
| 39 }; | 38 }; |
| 40 | 39 |
| 41 struct VTVideoEncodeAccelerator::EncodeOutput { | 40 struct VTVideoEncodeAccelerator::EncodeOutput { |
| 42 EncodeOutput(VTEncodeInfoFlags info_flags, CMSampleBufferRef sbuf) | 41 EncodeOutput(VTEncodeInfoFlags info_flags, CMSampleBufferRef sbuf) |
| (...skipping 78 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 121 << ", initial_bitrate=" << initial_bitrate; | 120 << ", initial_bitrate=" << initial_bitrate; |
| 122 DCHECK(thread_checker_.CalledOnValidThread()); | 121 DCHECK(thread_checker_.CalledOnValidThread()); |
| 123 DCHECK(client); | 122 DCHECK(client); |
| 124 | 123 |
| 125 if (media::PIXEL_FORMAT_I420 != format) { | 124 if (media::PIXEL_FORMAT_I420 != format) { |
| 126 DLOG(ERROR) << "Input format not supported= " | 125 DLOG(ERROR) << "Input format not supported= " |
| 127 << media::VideoPixelFormatToString(format); | 126 << media::VideoPixelFormatToString(format); |
| 128 return false; | 127 return false; |
| 129 } | 128 } |
| 130 if (media::H264PROFILE_BASELINE != output_profile) { | 129 if (media::H264PROFILE_BASELINE != output_profile) { |
| 131 DLOG(ERROR) << "Output profile not supported= " | 130 DLOG(ERROR) << "Output profile not supported= " << output_profile; |
| 132 << output_profile; | |
| 133 return false; | 131 return false; |
| 134 } | 132 } |
| 135 | 133 |
| 136 videotoolbox_glue_ = VideoToolboxGlue::Get(); | 134 videotoolbox_glue_ = VideoToolboxGlue::Get(); |
| 137 if (!videotoolbox_glue_) { | 135 if (!videotoolbox_glue_) { |
| 138 DLOG(ERROR) << "Failed creating VideoToolbox glue."; | 136 DLOG(ERROR) << "Failed creating VideoToolbox glue."; |
| 139 return false; | 137 return false; |
| 140 } | 138 } |
| 141 | 139 |
| 142 client_ptr_factory_.reset(new base::WeakPtrFactory<Client>(client)); | 140 client_ptr_factory_.reset(new base::WeakPtrFactory<Client>(client)); |
| (...skipping 76 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 219 | 217 |
| 220 void VTVideoEncodeAccelerator::Destroy() { | 218 void VTVideoEncodeAccelerator::Destroy() { |
| 221 DVLOG(3) << __FUNCTION__; | 219 DVLOG(3) << __FUNCTION__; |
| 222 DCHECK(thread_checker_.CalledOnValidThread()); | 220 DCHECK(thread_checker_.CalledOnValidThread()); |
| 223 | 221 |
| 224 // Cancel all callbacks. | 222 // Cancel all callbacks. |
| 225 client_ptr_factory_.reset(); | 223 client_ptr_factory_.reset(); |
| 226 | 224 |
| 227 if (encoder_thread_.IsRunning()) { | 225 if (encoder_thread_.IsRunning()) { |
| 228 encoder_thread_task_runner_->PostTask( | 226 encoder_thread_task_runner_->PostTask( |
| 229 FROM_HERE, | 227 FROM_HERE, base::Bind(&VTVideoEncodeAccelerator::DestroyTask, |
| 230 base::Bind(&VTVideoEncodeAccelerator::DestroyTask, | 228 base::Unretained(this))); |
| 231 base::Unretained(this))); | |
| 232 encoder_thread_.Stop(); | 229 encoder_thread_.Stop(); |
| 233 } else { | 230 } else { |
| 234 DestroyTask(); | 231 DestroyTask(); |
| 235 } | 232 } |
| 236 } | 233 } |
| 237 | 234 |
| 238 void VTVideoEncodeAccelerator::EncodeTask( | 235 void VTVideoEncodeAccelerator::EncodeTask( |
| 239 const scoped_refptr<media::VideoFrame>& frame, | 236 const scoped_refptr<media::VideoFrame>& frame, |
| 240 bool force_keyframe) { | 237 bool force_keyframe) { |
| 241 DCHECK(encoder_thread_task_runner_->BelongsToCurrentThread()); | 238 DCHECK(encoder_thread_task_runner_->BelongsToCurrentThread()); |
| (...skipping 165 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 407 client_task_runner_->PostTask( | 404 client_task_runner_->PostTask( |
| 408 FROM_HERE, base::Bind(&Client::BitstreamBufferReady, client_, | 405 FROM_HERE, base::Bind(&Client::BitstreamBufferReady, client_, |
| 409 buffer_ref->id, 0, false)); | 406 buffer_ref->id, 0, false)); |
| 410 return; | 407 return; |
| 411 } | 408 } |
| 412 | 409 |
| 413 auto sample_attachments = static_cast<CFDictionaryRef>(CFArrayGetValueAtIndex( | 410 auto sample_attachments = static_cast<CFDictionaryRef>(CFArrayGetValueAtIndex( |
| 414 CoreMediaGlue::CMSampleBufferGetSampleAttachmentsArray( | 411 CoreMediaGlue::CMSampleBufferGetSampleAttachmentsArray( |
| 415 encode_output->sample_buffer.get(), true), | 412 encode_output->sample_buffer.get(), true), |
| 416 0)); | 413 0)); |
| 417 const bool keyframe = | 414 const bool keyframe = !CFDictionaryContainsKey( |
| 418 !CFDictionaryContainsKey(sample_attachments, | 415 sample_attachments, CoreMediaGlue::kCMSampleAttachmentKey_NotSync()); |
| 419 CoreMediaGlue::kCMSampleAttachmentKey_NotSync()); | |
| 420 | 416 |
| 421 size_t used_buffer_size = 0; | 417 size_t used_buffer_size = 0; |
| 422 const bool copy_rv = media::video_toolbox::CopySampleBufferToAnnexBBuffer( | 418 const bool copy_rv = media::video_toolbox::CopySampleBufferToAnnexBBuffer( |
| 423 encode_output->sample_buffer.get(), keyframe, buffer_ref->size, | 419 encode_output->sample_buffer.get(), keyframe, buffer_ref->size, |
| 424 reinterpret_cast<char*>(buffer_ref->shm->memory()), &used_buffer_size); | 420 reinterpret_cast<char*>(buffer_ref->shm->memory()), &used_buffer_size); |
| 425 if (!copy_rv) { | 421 if (!copy_rv) { |
| 426 DLOG(ERROR) << "Cannot copy output from SampleBuffer to AnnexBBuffer."; | 422 DLOG(ERROR) << "Cannot copy output from SampleBuffer to AnnexBBuffer."; |
| 427 used_buffer_size = 0; | 423 used_buffer_size = 0; |
| 428 } | 424 } |
| 429 | 425 |
| 430 client_task_runner_->PostTask( | 426 client_task_runner_->PostTask( |
| 431 FROM_HERE, base::Bind(&Client::BitstreamBufferReady, client_, | 427 FROM_HERE, base::Bind(&Client::BitstreamBufferReady, client_, |
| 432 buffer_ref->id, used_buffer_size, keyframe)); | 428 buffer_ref->id, used_buffer_size, keyframe)); |
| 433 } | 429 } |
| 434 | 430 |
| 435 bool VTVideoEncodeAccelerator::ResetCompressionSession() { | 431 bool VTVideoEncodeAccelerator::ResetCompressionSession() { |
| 436 DCHECK(thread_checker_.CalledOnValidThread()); | 432 DCHECK(thread_checker_.CalledOnValidThread()); |
| 437 | 433 |
| 438 DestroyCompressionSession(); | 434 DestroyCompressionSession(); |
| 439 | 435 |
| 440 CFTypeRef attributes_keys[] = { | 436 CFTypeRef attributes_keys[] = {kCVPixelBufferOpenGLCompatibilityKey, |
| 441 kCVPixelBufferOpenGLCompatibilityKey, | 437 kCVPixelBufferIOSurfacePropertiesKey, |
| 442 kCVPixelBufferIOSurfacePropertiesKey, | 438 kCVPixelBufferPixelFormatTypeKey}; |
| 443 kCVPixelBufferPixelFormatTypeKey | |
| 444 }; | |
| 445 const int format[] = { | 439 const int format[] = { |
| 446 CoreVideoGlue::kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange}; | 440 CoreVideoGlue::kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange}; |
| 447 CFTypeRef attributes_values[] = { | 441 CFTypeRef attributes_values[] = { |
| 448 kCFBooleanTrue, | 442 kCFBooleanTrue, |
| 449 media::video_toolbox::DictionaryWithKeysAndValues(nullptr, nullptr, 0) | 443 media::video_toolbox::DictionaryWithKeysAndValues(nullptr, nullptr, 0) |
| 450 .release(), | 444 .release(), |
| 451 media::video_toolbox::ArrayWithIntegers(format, arraysize(format)) | 445 media::video_toolbox::ArrayWithIntegers(format, arraysize(format)) |
| 452 .release()}; | 446 .release()}; |
| 453 const base::ScopedCFTypeRef<CFDictionaryRef> attributes = | 447 const base::ScopedCFTypeRef<CFDictionaryRef> attributes = |
| 454 media::video_toolbox::DictionaryWithKeysAndValues( | 448 media::video_toolbox::DictionaryWithKeysAndValues( |
| (...skipping 16 matching lines...) Expand all Loading... |
| 471 | 465 |
| 472 bool VTVideoEncodeAccelerator::CreateCompressionSession( | 466 bool VTVideoEncodeAccelerator::CreateCompressionSession( |
| 473 base::ScopedCFTypeRef<CFDictionaryRef> attributes, | 467 base::ScopedCFTypeRef<CFDictionaryRef> attributes, |
| 474 const gfx::Size& input_size, | 468 const gfx::Size& input_size, |
| 475 bool require_hw_encoding) { | 469 bool require_hw_encoding) { |
| 476 DCHECK(thread_checker_.CalledOnValidThread()); | 470 DCHECK(thread_checker_.CalledOnValidThread()); |
| 477 | 471 |
| 478 std::vector<CFTypeRef> encoder_keys; | 472 std::vector<CFTypeRef> encoder_keys; |
| 479 std::vector<CFTypeRef> encoder_values; | 473 std::vector<CFTypeRef> encoder_values; |
| 480 if (require_hw_encoding) { | 474 if (require_hw_encoding) { |
| 481 encoder_keys.push_back(videotoolbox_glue_ | 475 encoder_keys.push_back( |
| 482 ->kVTVideoEncoderSpecification_RequireHardwareAcceleratedVideoEncoder()); | 476 videotoolbox_glue_ |
| 477 ->kVTVideoEncoderSpecification_RequireHardwareAcceleratedVideoEncode
r()); |
| 483 encoder_values.push_back(kCFBooleanTrue); | 478 encoder_values.push_back(kCFBooleanTrue); |
| 484 } else { | 479 } else { |
| 485 encoder_keys.push_back(videotoolbox_glue_ | 480 encoder_keys.push_back( |
| 486 ->kVTVideoEncoderSpecification_EnableHardwareAcceleratedVideoEncoder()); | 481 videotoolbox_glue_ |
| 482 ->kVTVideoEncoderSpecification_EnableHardwareAcceleratedVideoEncoder
()); |
| 487 encoder_values.push_back(kCFBooleanTrue); | 483 encoder_values.push_back(kCFBooleanTrue); |
| 488 } | 484 } |
| 489 base::ScopedCFTypeRef<CFDictionaryRef> encoder_spec = | 485 base::ScopedCFTypeRef<CFDictionaryRef> encoder_spec = |
| 490 media::video_toolbox::DictionaryWithKeysAndValues( | 486 media::video_toolbox::DictionaryWithKeysAndValues( |
| 491 encoder_keys.data(), encoder_values.data(), encoder_keys.size()); | 487 encoder_keys.data(), encoder_values.data(), encoder_keys.size()); |
| 492 | 488 |
| 493 // Create the compression session. | 489 // Create the compression session. |
| 494 // Note that the encoder object is given to the compression session as the | 490 // Note that the encoder object is given to the compression session as the |
| 495 // callback context using a raw pointer. The C API does not allow us to use a | 491 // callback context using a raw pointer. The C API does not allow us to use a |
| 496 // smart pointer, nor is this encoder ref counted. However, this is still | 492 // smart pointer, nor is this encoder ref counted. However, this is still |
| 497 // safe, because we 1) we own the compression session and 2) we tear it down | 493 // safe, because we 1) we own the compression session and 2) we tear it down |
| 498 // safely. When destructing the encoder, the compression session is flushed | 494 // safely. When destructing the encoder, the compression session is flushed |
| 499 // and invalidated. Internally, VideoToolbox will join all of its threads | 495 // and invalidated. Internally, VideoToolbox will join all of its threads |
| 500 // before returning to the client. Therefore, when control returns to us, we | 496 // before returning to the client. Therefore, when control returns to us, we |
| 501 // are guaranteed that the output callback will not execute again. | 497 // are guaranteed that the output callback will not execute again. |
| 502 OSStatus status = videotoolbox_glue_->VTCompressionSessionCreate( | 498 OSStatus status = videotoolbox_glue_->VTCompressionSessionCreate( |
| 503 kCFAllocatorDefault, | 499 kCFAllocatorDefault, input_size.width(), input_size.height(), |
| 504 input_size.width(), | 500 CoreMediaGlue::kCMVideoCodecType_H264, encoder_spec, attributes, |
| 505 input_size.height(), | |
| 506 CoreMediaGlue::kCMVideoCodecType_H264, | |
| 507 encoder_spec, | |
| 508 attributes, | |
| 509 nullptr /* compressedDataAllocator */, | 501 nullptr /* compressedDataAllocator */, |
| 510 &VTVideoEncodeAccelerator::CompressionCallback, | 502 &VTVideoEncodeAccelerator::CompressionCallback, |
| 511 reinterpret_cast<void*>(this), | 503 reinterpret_cast<void*>(this), compression_session_.InitializeInto()); |
| 512 compression_session_.InitializeInto()); | |
| 513 if (status != noErr) { | 504 if (status != noErr) { |
| 514 DLOG(ERROR) << " VTCompressionSessionCreate failed: " << status; | 505 DLOG(ERROR) << " VTCompressionSessionCreate failed: " << status; |
| 515 return false; | 506 return false; |
| 516 } | 507 } |
| 517 DVLOG(3) << " VTCompressionSession created with HW encode: " | 508 DVLOG(3) << " VTCompressionSession created with HW encode: " |
| 518 << require_hw_encoding << ", input size=" << input_size.ToString(); | 509 << require_hw_encoding << ", input size=" << input_size.ToString(); |
| 519 return true; | 510 return true; |
| 520 } | 511 } |
| 521 | 512 |
| 522 bool VTVideoEncodeAccelerator::ConfigureCompressionSession() { | 513 bool VTVideoEncodeAccelerator::ConfigureCompressionSession() { |
| (...skipping 19 matching lines...) Expand all Loading... |
| 542 DCHECK(thread_checker_.CalledOnValidThread() || | 533 DCHECK(thread_checker_.CalledOnValidThread() || |
| 543 (encoder_thread_.IsRunning() && | 534 (encoder_thread_.IsRunning() && |
| 544 encoder_thread_task_runner_->BelongsToCurrentThread())); | 535 encoder_thread_task_runner_->BelongsToCurrentThread())); |
| 545 | 536 |
| 546 if (compression_session_) { | 537 if (compression_session_) { |
| 547 videotoolbox_glue_->VTCompressionSessionInvalidate(compression_session_); | 538 videotoolbox_glue_->VTCompressionSessionInvalidate(compression_session_); |
| 548 compression_session_.reset(); | 539 compression_session_.reset(); |
| 549 } | 540 } |
| 550 } | 541 } |
| 551 | 542 |
| 552 } // namespace content | 543 } // namespace media |
| OLD | NEW |