Chromium Code Reviews| Index: webrtc/modules/video_coding/codecs/h264/h264_video_toolbox_encoder.cc |
| diff --git a/webrtc/modules/video_coding/codecs/h264/h264_video_toolbox_encoder.cc b/webrtc/modules/video_coding/codecs/h264/h264_video_toolbox_encoder.cc |
| index 7df4ec74ba1971236fecc8b5a92f5b690b869f36..2a1a0c3c714b5a58e8aac4be9cdf3efb8b1f08dd 100644 |
| --- a/webrtc/modules/video_coding/codecs/h264/h264_video_toolbox_encoder.cc |
| +++ b/webrtc/modules/video_coding/codecs/h264/h264_video_toolbox_encoder.cc |
| @@ -21,6 +21,7 @@ |
| #include "webrtc/base/logging.h" |
| #include "webrtc/base/scoped_ptr.h" |
| #include "webrtc/modules/video_coding/codecs/h264/h264_video_toolbox_nalu.h" |
| +#include "webrtc/system_wrappers/include/clock.h" |
| namespace internal { |
| @@ -67,6 +68,22 @@ void SetVTSessionProperty(VTSessionRef session, |
| } |
| // Convenience function for setting a VT property. |
| +void SetVTSessionProperty(VTSessionRef session, |
| + CFStringRef key, |
| + uint32_t value) { |
| + int64_t value_64 = value; |
| + CFNumberRef cfNum = |
| + CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt64Type, &value_64); |
| + OSStatus status = VTSessionSetProperty(session, key, cfNum); |
| + CFRelease(cfNum); |
| + if (status != noErr) { |
| + std::string key_string = CFStringToString(key); |
| + LOG(LS_ERROR) << "VTSessionSetProperty failed to set: " << key_string |
| + << " to " << value << ": " << status; |
| + } |
| +} |
| + |
| +// Convenience function for setting a VT property. |
| void SetVTSessionProperty(VTSessionRef session, CFStringRef key, bool value) { |
| CFBooleanRef cf_bool = (value) ? kCFBooleanTrue : kCFBooleanFalse; |
| OSStatus status = VTSessionSetProperty(session, key, cf_bool); |
| @@ -93,20 +110,21 @@ void SetVTSessionProperty(VTSessionRef session, |
| // Struct that we pass to the encoder per frame to encode. We receive it again |
| // in the encoder callback. |
| struct FrameEncodeParams { |
| - FrameEncodeParams(webrtc::EncodedImageCallback* cb, |
| + FrameEncodeParams(webrtc::H264VideoToolboxEncoder* e, |
| const webrtc::CodecSpecificInfo* csi, |
| int32_t w, |
| int32_t h, |
| int64_t rtms, |
| uint32_t ts) |
| - : callback(cb), width(w), height(h), render_time_ms(rtms), timestamp(ts) { |
| + : encoder(e), width(w), height(h), render_time_ms(rtms), timestamp(ts) { |
| if (csi) { |
| codec_specific_info = *csi; |
| } else { |
| codec_specific_info.codecType = webrtc::kVideoCodecH264; |
| } |
| } |
| - webrtc::EncodedImageCallback* callback; |
| + |
| + webrtc::H264VideoToolboxEncoder* encoder; |
| webrtc::CodecSpecificInfo codec_specific_info; |
| int32_t width; |
| int32_t height; |
| @@ -153,7 +171,7 @@ bool CopyVideoFrameToPixelBuffer(const webrtc::VideoFrame& frame, |
| } |
| // This is the callback function that VideoToolbox calls when encode is |
| -// complete. |
| +// complete. From inspection this happens on its own queue. |
| void VTCompressionOutputCallback(void* encoder, |
| void* params, |
| OSStatus status, |
| @@ -161,46 +179,10 @@ void VTCompressionOutputCallback(void* encoder, |
| CMSampleBufferRef sample_buffer) { |
| rtc::scoped_ptr<FrameEncodeParams> encode_params( |
| reinterpret_cast<FrameEncodeParams*>(params)); |
| - if (status != noErr) { |
| - LOG(LS_ERROR) << "H264 encoding failed."; |
| - return; |
| - } |
| - if (info_flags & kVTEncodeInfo_FrameDropped) { |
| - LOG(LS_INFO) << "H264 encode dropped frame."; |
| - } |
| - |
| - bool is_keyframe = false; |
| - CFArrayRef attachments = |
| - CMSampleBufferGetSampleAttachmentsArray(sample_buffer, 0); |
| - if (attachments != nullptr && CFArrayGetCount(attachments)) { |
| - CFDictionaryRef attachment = |
| - static_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(attachments, 0)); |
| - is_keyframe = |
| - !CFDictionaryContainsKey(attachment, kCMSampleAttachmentKey_NotSync); |
| - } |
| - |
| - // Convert the sample buffer into a buffer suitable for RTP packetization. |
| - // TODO(tkchin): Allocate buffers through a pool. |
| - rtc::scoped_ptr<rtc::Buffer> buffer(new rtc::Buffer()); |
| - rtc::scoped_ptr<webrtc::RTPFragmentationHeader> header; |
| - if (!H264CMSampleBufferToAnnexBBuffer(sample_buffer, is_keyframe, |
| - buffer.get(), header.accept())) { |
| - return; |
| - } |
| - webrtc::EncodedImage frame(buffer->data(), buffer->size(), buffer->size()); |
| - frame._encodedWidth = encode_params->width; |
| - frame._encodedHeight = encode_params->height; |
| - frame._completeFrame = true; |
| - frame._frameType = |
| - is_keyframe ? webrtc::kVideoFrameKey : webrtc::kVideoFrameDelta; |
| - frame.capture_time_ms_ = encode_params->render_time_ms; |
| - frame._timeStamp = encode_params->timestamp; |
| - |
| - int result = encode_params->callback->Encoded( |
| - frame, &(encode_params->codec_specific_info), header.get()); |
| - if (result != 0) { |
| - LOG(LS_ERROR) << "Encoded callback failed: " << result; |
| - } |
| + encode_params->encoder->OnEncodedFrame( |
| + status, info_flags, sample_buffer, encode_params->codec_specific_info, |
| + encode_params->width, encode_params->height, |
| + encode_params->render_time_ms, encode_params->timestamp); |
| } |
| } // namespace internal |
| @@ -208,7 +190,9 @@ void VTCompressionOutputCallback(void* encoder, |
| namespace webrtc { |
| H264VideoToolboxEncoder::H264VideoToolboxEncoder() |
| - : callback_(nullptr), compression_session_(nullptr) {} |
| + : callback_(nullptr), |
| + compression_session_(nullptr), |
| + bitrate_adjuster_(Clock::GetRealTimeClock(), this) {} |
| H264VideoToolboxEncoder::~H264VideoToolboxEncoder() { |
| DestroyCompressionSession(); |
| @@ -224,7 +208,8 @@ int H264VideoToolboxEncoder::InitEncode(const VideoCodec* codec_settings, |
| width_ = codec_settings->width; |
| height_ = codec_settings->height; |
| // We can only set average bitrate on the HW encoder. |
| - bitrate_ = codec_settings->startBitrate * 1000; |
| + bitrate_kbit_ = codec_settings->startBitrate; |
| + bitrate_adjuster_.SetTargetBitrate(bitrate_kbit_); |
| // TODO(tkchin): Try setting payload size via |
| // kVTCompressionPropertyKey_MaxH264SliceBytes. |
| @@ -287,8 +272,8 @@ int H264VideoToolboxEncoder::Encode( |
| } |
| rtc::scoped_ptr<internal::FrameEncodeParams> encode_params; |
| encode_params.reset(new internal::FrameEncodeParams( |
| - callback_, codec_specific_info, width_, height_, |
| - input_image.render_time_ms(), input_image.timestamp())); |
| + this, codec_specific_info, width_, height_, input_image.render_time_ms(), |
| + input_image.timestamp())); |
| VTCompressionSessionEncodeFrame( |
| compression_session_, pixel_buffer, presentation_time_stamp, |
| kCMTimeInvalid, frame_properties, encode_params.release(), nullptr); |
| @@ -315,20 +300,20 @@ int H264VideoToolboxEncoder::SetChannelParameters(uint32_t packet_loss, |
| int H264VideoToolboxEncoder::SetRates(uint32_t new_bitrate_kbit, |
| uint32_t frame_rate) { |
| - bitrate_ = new_bitrate_kbit * 1000; |
| - if (compression_session_) { |
| - internal::SetVTSessionProperty(compression_session_, |
| - kVTCompressionPropertyKey_AverageBitRate, |
| - bitrate_); |
| - } |
| + bitrate_kbit_ = new_bitrate_kbit; |
| + bitrate_adjuster_.SetTargetBitrate(new_bitrate_kbit); |
| + SetBitrate(bitrate_kbit_); |
| + |
| return WEBRTC_VIDEO_CODEC_OK; |
| } |
| int H264VideoToolboxEncoder::Release() { |
| + // Need to reset so that the session is invalidated and won't use the |
| + // callback anymore. Do not remove callback until the session is invalidated |
| + // since async encoder callbacks can occur until invalidation. |
| + int ret = ResetCompressionSession(); |
| callback_ = nullptr; |
| - // Need to reset to that the session is invalidated and won't use the |
| - // callback anymore. |
| - return ResetCompressionSession(); |
| + return ret; |
| } |
| int H264VideoToolboxEncoder::ResetCompressionSession() { |
| @@ -389,11 +374,10 @@ void H264VideoToolboxEncoder::ConfigureCompressionSession() { |
| internal::SetVTSessionProperty(compression_session_, |
| kVTCompressionPropertyKey_ProfileLevel, |
| kVTProfileLevel_H264_Baseline_AutoLevel); |
| - internal::SetVTSessionProperty( |
| - compression_session_, kVTCompressionPropertyKey_AverageBitRate, bitrate_); |
| internal::SetVTSessionProperty(compression_session_, |
| kVTCompressionPropertyKey_AllowFrameReordering, |
| false); |
| + SetBitrate(bitrate_kbit_); |
| // TODO(tkchin): Look at entropy mode and colorspace matrices. |
| // TODO(tkchin): Investigate to see if there's any way to make this work. |
| // May need it to interop with Android. Currently this call just fails. |
| @@ -423,6 +407,71 @@ const char* H264VideoToolboxEncoder::ImplementationName() const { |
| return "VideoToolbox"; |
| } |
| +void H264VideoToolboxEncoder::OnBitrateAdjusted( |
| + uint32_t adjusted_bitrate_kbit) { |
| + SetBitrate(adjusted_bitrate_kbit); |
| +} |
| + |
| +void H264VideoToolboxEncoder::SetBitrate(uint32_t bitrate_kbit) { |
| + if (compression_session_) { |
| + internal::SetVTSessionProperty(compression_session_, |
| + kVTCompressionPropertyKey_AverageBitRate, |
| + bitrate_kbit * 1000); |
| + } |
| +} |
| + |
| +void H264VideoToolboxEncoder::OnEncodedFrame( |
| + OSStatus status, |
| + VTEncodeInfoFlags info_flags, |
| + CMSampleBufferRef sample_buffer, |
| + CodecSpecificInfo codec_specific_info, |
| + int32_t width, |
| + int32_t height, |
| + int64_t render_time_ms, |
| + uint32_t timestamp) { |
| + if (status != noErr) { |
| + LOG(LS_ERROR) << "H264 encode failed."; |
| + return; |
| + } |
| + if (info_flags & kVTEncodeInfo_FrameDropped) { |
| + LOG(LS_INFO) << "H264 encode dropped frame."; |
| + } |
| + |
| + bool is_keyframe = false; |
| + CFArrayRef attachments = |
| + CMSampleBufferGetSampleAttachmentsArray(sample_buffer, 0); |
| + if (attachments != nullptr && CFArrayGetCount(attachments)) { |
| + CFDictionaryRef attachment = |
| + static_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(attachments, 0)); |
| + is_keyframe = |
| + !CFDictionaryContainsKey(attachment, kCMSampleAttachmentKey_NotSync); |
| + } |
| + |
| + // Convert the sample buffer into a buffer suitable for RTP packetization. |
| + // TODO(tkchin): Allocate buffers through a pool. |
| + rtc::scoped_ptr<rtc::Buffer> buffer(new rtc::Buffer()); |
| + rtc::scoped_ptr<webrtc::RTPFragmentationHeader> header; |
| + if (!H264CMSampleBufferToAnnexBBuffer(sample_buffer, is_keyframe, |
| + buffer.get(), header.accept())) { |
| + return; |
| + } |
| + webrtc::EncodedImage frame(buffer->data(), buffer->size(), buffer->size()); |
| + frame._encodedWidth = width; |
| + frame._encodedHeight = height; |
| + frame._completeFrame = true; |
| + frame._frameType = |
| + is_keyframe ? webrtc::kVideoFrameKey : webrtc::kVideoFrameDelta; |
| + frame.capture_time_ms_ = render_time_ms; |
| + frame._timeStamp = timestamp; |
| + |
| + int result = callback_->Encoded(frame, &(codec_specific_info), header.get()); |
|
stefan-webrtc
2016/02/18 20:56:58
Remove parentheses around codec_specific_info
tkchin_webrtc
2016/02/18 23:50:39
Done.
|
| + if (result != 0) { |
| + LOG(LS_ERROR) << "Encode callback failed: " << result; |
| + return; |
| + } |
| + bitrate_adjuster_.Update(frame._size); |
| +} |
| + |
| } // namespace webrtc |
| #endif // defined(WEBRTC_VIDEO_TOOLBOX_SUPPORTED) |