Index: webrtc/sdk/objc/Framework/Classes/VideoToolbox/RTCVideoEncoderH264.mm |
diff --git a/webrtc/sdk/objc/Framework/Classes/VideoToolbox/RTCVideoEncoderH264.mm b/webrtc/sdk/objc/Framework/Classes/VideoToolbox/RTCVideoEncoderH264.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..01d96c48ead8afd0c918e0adaa530cce8f6646ac |
--- /dev/null |
+++ b/webrtc/sdk/objc/Framework/Classes/VideoToolbox/RTCVideoEncoderH264.mm |
@@ -0,0 +1,542 @@ |
+/* |
+ * Copyright 2017 The WebRTC project authors. All Rights Reserved. |
+ * |
+ * Use of this source code is governed by a BSD-style license |
+ * that can be found in the LICENSE file in the root of the source |
+ * tree. An additional intellectual property rights grant can be found |
+ * in the file PATENTS. All contributing project authors may |
+ * be found in the AUTHORS file in the root of the source tree. |
+ */ |
+ |
+#import "WebRTC/RTCVideoCodecH264.h" |
+ |
+#import <VideoToolbox/VideoToolbox.h> |
+#include <vector> |
+ |
+#import "Video/RTCVideoCodec+Private.h" |
+#import "WebRTC/RTCVideoCodec.h" |
+#import "WebRTC/RTCVideoFrame.h" |
+#import "WebRTC/RTCVideoFrameBuffer.h" |
+#import "helpers.h" |
+#if defined(WEBRTC_IOS) |
+#import "Common/RTCUIApplicationStatusObserver.h" |
+#import "WebRTC/UIDevice+RTCDevice.h" |
+#endif |
+ |
+#include "libyuv/convert.h" |
+#include "webrtc/common_video/h264/h264_bitstream_parser.h" |
+#include "webrtc/common_video/include/bitrate_adjuster.h" |
+#include "webrtc/modules/include/module_common_types.h" |
+#include "webrtc/modules/video_coding/include/video_error_codes.h" |
+#include "webrtc/rtc_base/buffer.h" |
+#include "webrtc/rtc_base/logging.h" |
+#include "webrtc/rtc_base/timeutils.h" |
+#include "webrtc/sdk/objc/Framework/Classes/VideoToolbox/nalu_rewriter.h" |
+#include "webrtc/system_wrappers/include/clock.h" |
+ |
+@interface RTCVideoEncoderH264 () |
+ |
+- (void)frameWasEncoded:(OSStatus)status |
+ flags:(VTEncodeInfoFlags)infoFlags |
+ sampleBuffer:(CMSampleBufferRef)sampleBuffer |
+ codecSpecificInfo:(id<RTCCodecSpecificInfo>)codecSpecificInfo |
+ width:(int32_t)width |
+ height:(int32_t)height |
+ renderTimeMs:(int64_t)renderTimeMs |
+ timestamp:(uint32_t)timestamp |
+ rotation:(RTCVideoRotation)rotation; |
+ |
+@end |
+ |
+// The ratio between kVTCompressionPropertyKey_DataRateLimits and |
+// kVTCompressionPropertyKey_AverageBitRate. The data rate limit is set higher |
+// than the average bit rate to avoid undershooting the target. |
+const float kLimitToAverageBitRateFactor = 1.5f; |
+ |
+// Struct that we pass to the encoder per frame to encode. We receive it again |
+// in the encoder callback. |
+struct RTCFrameEncodeParams { |
+ RTCFrameEncodeParams(RTCVideoEncoderH264 *e, |
+ RTCCodecSpecificInfoH264 *csi, |
+ int32_t w, |
+ int32_t h, |
+ int64_t rtms, |
+ uint32_t ts, |
+ RTCVideoRotation r) |
+ : encoder(e), width(w), height(h), render_time_ms(rtms), timestamp(ts), rotation(r) { |
+ if (csi) { |
+ codecSpecificInfo = csi; |
+ } else { |
+ codecSpecificInfo = [[RTCCodecSpecificInfoH264 alloc] init]; |
+ } |
+ } |
+ |
+ RTCVideoEncoderH264 *encoder; |
+ RTCCodecSpecificInfoH264 *codecSpecificInfo; |
+ int32_t width; |
+ int32_t height; |
+ int64_t render_time_ms; |
+ uint32_t timestamp; |
+ RTCVideoRotation rotation; |
+}; |
+ |
+// This is the callback function that VideoToolbox calls when encode is |
+// complete. From inspection this happens on its own queue. |
+void compressionOutputCallback(void *encoder, |
+ void *params, |
+ OSStatus status, |
+ VTEncodeInfoFlags infoFlags, |
+ CMSampleBufferRef sampleBuffer) { |
+ std::unique_ptr<RTCFrameEncodeParams> encodeParams( |
+ reinterpret_cast<RTCFrameEncodeParams *>(params)); |
+ [encodeParams->encoder frameWasEncoded:status |
+ flags:infoFlags |
+ sampleBuffer:sampleBuffer |
+ codecSpecificInfo:encodeParams->codecSpecificInfo |
+ width:encodeParams->width |
+ height:encodeParams->height |
+ renderTimeMs:encodeParams->render_time_ms |
+ timestamp:encodeParams->timestamp |
+ rotation:encodeParams->rotation]; |
+} |
+ |
+@implementation RTCVideoEncoderH264 { |
+ RTCVideoCodecInfo *_codecInfo; |
+ webrtc::BitrateAdjuster *_bitrateAdjuster; |
+ uint32_t _targetBitrateBps; |
+ uint32_t _encoderBitrateBps; |
+ RTCH264PacketizationMode _packetizationMode; |
+ CFStringRef _profile; |
+ RTCVideoEncoderCallback _callback; |
+ int32_t _width; |
+ int32_t _height; |
+ VTCompressionSessionRef _compressionSession; |
+ RTCVideoCodecMode _mode; |
+ |
+ webrtc::H264BitstreamParser _h264BitstreamParser; |
+ std::vector<uint8_t> _nv12ScaleBuffer; |
+} |
+ |
+- (instancetype)initWithCodecInfo:(RTCVideoCodecInfo *)codecInfo { |
+ if (self = [super init]) { |
+ _codecInfo = codecInfo; |
+ _bitrateAdjuster = new webrtc::BitrateAdjuster(webrtc::Clock::GetRealTimeClock(), .5, .95); |
+ _packetizationMode = NonInterleaved; |
+ _profile = (__bridge CFStringRef)codecInfo.parameters[@"videotoolbox-profile"]; |
+ } |
+ return self; |
+} |
+ |
+- (void)dealloc { |
+ [self destroyCompressionSession]; |
+} |
+ |
+- (void)setCallback:(RTCVideoEncoderCallback)callback { |
+ _callback = callback; |
+} |
+ |
+- (int)initEncodeWithSettings:(RTCVideoEncoderSettings *)settings numberOfCores:(int)numberOfCores { |
+ RTC_DCHECK(settings); |
+ RTC_DCHECK([settings.name isEqualToString:@"H264"]); |
+ |
+ _width = settings.width; |
+ _height = settings.height; |
+ _mode = settings.mode; |
+ |
+ // We can only set average bitrate on the HW encoder. |
+ _targetBitrateBps = settings.startBitrate; |
+ _bitrateAdjuster->SetTargetBitrateBps(_targetBitrateBps); |
+ |
+ // TODO(tkchin): Try setting payload size via |
+ // kVTCompressionPropertyKey_MaxH264SliceBytes. |
+ |
+ return [self resetCompressionSession]; |
+} |
+ |
+- (int)releaseEncode { |
+ // 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 = [self resetCompressionSession]; |
+ _callback = nullptr; |
+ return ret; |
+} |
+ |
+- (int)encode:(RTCVideoFrame *)frame |
+ codecSpecificInfo:(id<RTCCodecSpecificInfo>)codecSpecificInfo |
+ frameTypes:(NSArray<NSNumber *> *)frameTypes { |
+ RTC_DCHECK_EQ(frame.width, _width); |
+ RTC_DCHECK_EQ(frame.height, _height); |
+ if (!_callback || !_compressionSession) { |
+ return WEBRTC_VIDEO_CODEC_UNINITIALIZED; |
+ } |
+#if defined(WEBRTC_IOS) |
+ if (![[RTCUIApplicationStatusObserver sharedInstance] isApplicationActive]) { |
+ // Ignore all encode requests when app isn't active. In this state, the |
+ // hardware encoder has been invalidated by the OS. |
+ return WEBRTC_VIDEO_CODEC_OK; |
+ } |
+#endif |
+ BOOL isKeyframeRequired = NO; |
+ |
+ // Get a pixel buffer from the pool and copy frame data over. |
+ CVPixelBufferPoolRef pixelBufferPool = |
+ VTCompressionSessionGetPixelBufferPool(_compressionSession); |
+ |
+#if defined(WEBRTC_IOS) |
+ if (!pixelBufferPool) { |
+ // Kind of a hack. On backgrounding, the compression session seems to get |
+ // invalidated, which causes this pool call to fail when the application |
+ // is foregrounded and frames are being sent for encoding again. |
+ // Resetting the session when this happens fixes the issue. |
+ // In addition we request a keyframe so video can recover quickly. |
+ [self resetCompressionSession]; |
+ pixelBufferPool = VTCompressionSessionGetPixelBufferPool(_compressionSession); |
+ isKeyframeRequired = YES; |
+ LOG(LS_INFO) << "Resetting compression session due to invalid pool."; |
+ } |
+#endif |
+ |
+ CVPixelBufferRef pixelBuffer = nullptr; |
+ if ([frame.buffer isKindOfClass:[RTCCVPixelBuffer class]]) { |
+ // Native frame buffer |
+ RTCCVPixelBuffer *rtcPixelBuffer = (RTCCVPixelBuffer *)frame.buffer; |
+ if (![rtcPixelBuffer requiresCropping]) { |
+ // This pixel buffer might have a higher resolution than what the |
+ // compression session is configured to. The compression session can |
+ // handle that and will output encoded frames in the configured |
+ // resolution regardless of the input pixel buffer resolution. |
+ pixelBuffer = rtcPixelBuffer.pixelBuffer; |
+ CVBufferRetain(pixelBuffer); |
+ } else { |
+ // Cropping required, we need to crop and scale to a new pixel buffer. |
+ pixelBuffer = [self createPixelBufferFromPool:pixelBufferPool]; |
+ if (!pixelBuffer) { |
+ return WEBRTC_VIDEO_CODEC_ERROR; |
+ } |
+ int dstWidth = CVPixelBufferGetWidth(pixelBuffer); |
+ int dstHeight = CVPixelBufferGetHeight(pixelBuffer); |
+ if ([rtcPixelBuffer requiresScalingToWidth:dstWidth height:dstHeight]) { |
+ int size = |
+ [rtcPixelBuffer bufferSizeForCroppingAndScalingToWidth:dstWidth height:dstHeight]; |
+ _nv12ScaleBuffer.resize(size); |
+ } else { |
+ _nv12ScaleBuffer.clear(); |
+ } |
+ _nv12ScaleBuffer.shrink_to_fit(); |
+ if (![rtcPixelBuffer cropAndScaleTo:pixelBuffer withTempBuffer:_nv12ScaleBuffer.data()]) { |
+ return WEBRTC_VIDEO_CODEC_ERROR; |
+ } |
+ } |
+ } |
+ |
+ if (!pixelBuffer) { |
+ // We did not have a native frame buffer |
+ pixelBuffer = [self createPixelBufferFromPool:pixelBufferPool]; |
+ if (!pixelBuffer) { |
+ return WEBRTC_VIDEO_CODEC_ERROR; |
+ } |
+ RTC_DCHECK(pixelBuffer); |
+ if (![self copyVideoFrame:[frame.buffer toI420] toPixelBuffer:pixelBuffer]) { |
+ LOG(LS_ERROR) << "Failed to copy frame data."; |
+ CVBufferRelease(pixelBuffer); |
+ return WEBRTC_VIDEO_CODEC_ERROR; |
+ } |
+ } |
+ |
+ // Check if we need a keyframe. |
+ if (!isKeyframeRequired && frameTypes) { |
+ for (NSNumber *frameType in frameTypes) { |
+ if ((RTCFrameType)frameType.intValue == RTCFrameType::VideoFrameKey) { |
+ isKeyframeRequired = YES; |
+ break; |
+ } |
+ } |
+ } |
+ |
+ CMTime presentationTimeStamp = CMTimeMake(frame.timeStampNs / rtc::kNumNanosecsPerMillisec, 1000); |
+ CFDictionaryRef frameProperties = nullptr; |
+ if (isKeyframeRequired) { |
+ CFTypeRef keys[] = {kVTEncodeFrameOptionKey_ForceKeyFrame}; |
+ CFTypeRef values[] = {kCFBooleanTrue}; |
+ frameProperties = CreateCFTypeDictionary(keys, values, 1); |
+ } |
+ |
+ std::unique_ptr<RTCFrameEncodeParams> encodeParams; |
+ encodeParams.reset(new RTCFrameEncodeParams(self, |
+ codecSpecificInfo, |
+ _width, |
+ _height, |
+ frame.timeStampNs / rtc::kNumNanosecsPerMillisec, |
+ frame.timeStamp, |
+ frame.rotation)); |
+ encodeParams->codecSpecificInfo.packetizationMode = _packetizationMode; |
+ |
+ // Update the bitrate if needed. |
+ [self setBitrateBps:_bitrateAdjuster->GetAdjustedBitrateBps()]; |
+ |
+ OSStatus status = VTCompressionSessionEncodeFrame(_compressionSession, |
+ pixelBuffer, |
+ presentationTimeStamp, |
+ kCMTimeInvalid, |
+ frameProperties, |
+ encodeParams.release(), |
+ nullptr); |
+ if (frameProperties) { |
+ CFRelease(frameProperties); |
+ } |
+ if (pixelBuffer) { |
+ CVBufferRelease(pixelBuffer); |
+ } |
+ if (status != noErr) { |
+ LOG(LS_ERROR) << "Failed to encode frame with code: " << status; |
+ return WEBRTC_VIDEO_CODEC_ERROR; |
+ } |
+ return WEBRTC_VIDEO_CODEC_OK; |
+} |
+ |
+- (int)setBitrate:(uint32_t)bitrateKbit framerate:(uint32_t)framerate { |
+ _targetBitrateBps = 1000 * bitrateKbit; |
+ _bitrateAdjuster->SetTargetBitrateBps(_targetBitrateBps); |
+ [self setBitrateBps:_bitrateAdjuster->GetAdjustedBitrateBps()]; |
+ return WEBRTC_VIDEO_CODEC_OK; |
+} |
+ |
+#pragma mark - Private |
+ |
+- (void)setBitrateBps:(uint32_t)bitrateBps { |
+ if (_encoderBitrateBps != bitrateBps) { |
+ [self setEncoderBitrateBps:bitrateBps]; |
+ } |
+} |
+ |
+- (void)setEncoderBitrateBps:(uint32_t)bitrateBps { |
+ if (_compressionSession) { |
+ SetVTSessionProperty(_compressionSession, kVTCompressionPropertyKey_AverageBitRate, bitrateBps); |
+ |
+ // TODO(tkchin): Add a helper method to set array value. |
+ int64_t dataLimitBytesPerSecondValue = |
+ static_cast<int64_t>(bitrateBps * kLimitToAverageBitRateFactor / 8); |
+ CFNumberRef bytesPerSecond = |
+ CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt64Type, &dataLimitBytesPerSecondValue); |
+ int64_t oneSecondValue = 1; |
+ CFNumberRef oneSecond = |
+ CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt64Type, &oneSecondValue); |
+ const void *nums[2] = {bytesPerSecond, oneSecond}; |
+ CFArrayRef dataRateLimits = CFArrayCreate(nullptr, nums, 2, &kCFTypeArrayCallBacks); |
+ OSStatus status = VTSessionSetProperty( |
+ _compressionSession, kVTCompressionPropertyKey_DataRateLimits, dataRateLimits); |
+ if (bytesPerSecond) { |
+ CFRelease(bytesPerSecond); |
+ } |
+ if (oneSecond) { |
+ CFRelease(oneSecond); |
+ } |
+ if (dataRateLimits) { |
+ CFRelease(dataRateLimits); |
+ } |
+ if (status != noErr) { |
+ LOG(LS_ERROR) << "Failed to set data rate limit"; |
+ } |
+ |
+ _encoderBitrateBps = bitrateBps; |
+ } |
+} |
+ |
+- (int)resetCompressionSession { |
+ [self destroyCompressionSession]; |
+ |
+ // Set source image buffer attributes. These attributes will be present on |
+ // buffers retrieved from the encoder's pixel buffer pool. |
+ const size_t attributesSize = 3; |
+ CFTypeRef keys[attributesSize] = { |
+#if defined(WEBRTC_IOS) |
+ kCVPixelBufferOpenGLESCompatibilityKey, |
+#elif defined(WEBRTC_MAC) |
+ kCVPixelBufferOpenGLCompatibilityKey, |
+#endif |
+ kCVPixelBufferIOSurfacePropertiesKey, |
+ kCVPixelBufferPixelFormatTypeKey |
+ }; |
+ CFDictionaryRef ioSurfaceValue = CreateCFTypeDictionary(nullptr, nullptr, 0); |
+ int64_t nv12type = kCVPixelFormatType_420YpCbCr8BiPlanarFullRange; |
+ CFNumberRef pixelFormat = CFNumberCreate(nullptr, kCFNumberLongType, &nv12type); |
+ CFTypeRef values[attributesSize] = {kCFBooleanTrue, ioSurfaceValue, pixelFormat}; |
+ CFDictionaryRef sourceAttributes = CreateCFTypeDictionary(keys, values, attributesSize); |
+ if (ioSurfaceValue) { |
+ CFRelease(ioSurfaceValue); |
+ ioSurfaceValue = nullptr; |
+ } |
+ if (pixelFormat) { |
+ CFRelease(pixelFormat); |
+ pixelFormat = nullptr; |
+ } |
+ OSStatus status = VTCompressionSessionCreate(nullptr, // use default allocator |
+ _width, |
+ _height, |
+ kCMVideoCodecType_H264, |
+ nullptr, // use default encoder |
+ sourceAttributes, |
+ nullptr, // use default compressed data allocator |
+ compressionOutputCallback, |
+ nullptr, |
+ &_compressionSession); |
+ if (sourceAttributes) { |
+ CFRelease(sourceAttributes); |
+ sourceAttributes = nullptr; |
+ } |
+ if (status != noErr) { |
+ LOG(LS_ERROR) << "Failed to create compression session: " << status; |
+ return WEBRTC_VIDEO_CODEC_ERROR; |
+ } |
+ [self configureCompressionSession]; |
+ return WEBRTC_VIDEO_CODEC_OK; |
+} |
+ |
+- (void)configureCompressionSession { |
+ RTC_DCHECK(_compressionSession); |
+ SetVTSessionProperty(_compressionSession, kVTCompressionPropertyKey_RealTime, true); |
+ SetVTSessionProperty(_compressionSession, kVTCompressionPropertyKey_ProfileLevel, _profile); |
+ SetVTSessionProperty(_compressionSession, kVTCompressionPropertyKey_AllowFrameReordering, false); |
+ [self setEncoderBitrateBps:_targetBitrateBps]; |
+ // 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. |
+ // On inspecting encoder output on iOS8, this value is set to 6. |
+ // internal::SetVTSessionProperty(compression_session_, |
+ // kVTCompressionPropertyKey_MaxFrameDelayCount, |
+ // 1); |
+ |
+ // Set a relatively large value for keyframe emission (7200 frames or 4 minutes). |
+ SetVTSessionProperty(_compressionSession, kVTCompressionPropertyKey_MaxKeyFrameInterval, 7200); |
+ SetVTSessionProperty( |
+ _compressionSession, kVTCompressionPropertyKey_MaxKeyFrameIntervalDuration, 240); |
+} |
+ |
+- (void)destroyCompressionSession { |
+ if (_compressionSession) { |
+ VTCompressionSessionInvalidate(_compressionSession); |
+ CFRelease(_compressionSession); |
+ _compressionSession = nullptr; |
+ } |
+} |
+ |
+// We receive I420Frames as input, but we need to feed CVPixelBuffers into the |
+// encoder. This performs the copy and format conversion. |
+// TODO(tkchin): See if encoder will accept i420 frames and compare performance. |
+- (BOOL)copyVideoFrame:(id<RTCI420Buffer>)frameBuffer toPixelBuffer:(CVPixelBufferRef)pixelBuffer { |
+ RTC_DCHECK(pixelBuffer); |
+ RTC_DCHECK_EQ(CVPixelBufferGetPixelFormatType(pixelBuffer), |
+ kCVPixelFormatType_420YpCbCr8BiPlanarFullRange); |
+ RTC_DCHECK_EQ(CVPixelBufferGetHeightOfPlane(pixelBuffer, 0), frameBuffer.height); |
+ RTC_DCHECK_EQ(CVPixelBufferGetWidthOfPlane(pixelBuffer, 0), frameBuffer.width); |
+ |
+ CVReturn cvRet = CVPixelBufferLockBaseAddress(pixelBuffer, 0); |
+ if (cvRet != kCVReturnSuccess) { |
+ LOG(LS_ERROR) << "Failed to lock base address: " << cvRet; |
+ return NO; |
+ } |
+ uint8_t *dstY = reinterpret_cast<uint8_t *>(CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0)); |
+ int dstStrideY = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0); |
+ uint8_t *dstUV = reinterpret_cast<uint8_t *>(CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1)); |
+ int dstStrideUV = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 1); |
+ // Convert I420 to NV12. |
+ int ret = libyuv::I420ToNV12(frameBuffer.dataY, frameBuffer.strideY, |
+ frameBuffer.dataU, frameBuffer.strideU, |
+ frameBuffer.dataV, frameBuffer.strideV, |
+ dstY, dstStrideY, dstUV, dstStrideUV, |
+ frameBuffer.width, frameBuffer.height); |
+ CVPixelBufferUnlockBaseAddress(pixelBuffer, 0); |
+ if (ret) { |
+ LOG(LS_ERROR) << "Error converting I420 VideoFrame to NV12 :" << ret; |
+ return NO; |
+ } |
+ return YES; |
+} |
+ |
+- (CVPixelBufferRef)createPixelBufferFromPool:(CVPixelBufferPoolRef)pixelBufferPool { |
+ if (!pixelBufferPool) { |
+ LOG(LS_ERROR) << "Failed to get pixel buffer pool."; |
+ return nullptr; |
+ } |
+ CVPixelBufferRef pixelBuffer; |
+ CVReturn ret = CVPixelBufferPoolCreatePixelBuffer(nullptr, pixelBufferPool, &pixelBuffer); |
+ if (ret != kCVReturnSuccess) { |
+ LOG(LS_ERROR) << "Failed to create pixel buffer: " << ret; |
+ // We probably want to drop frames here, since failure probably means |
+ // that the pool is empty. |
+ return nullptr; |
+ } |
+ return pixelBuffer; |
+} |
+ |
+- (void)frameWasEncoded:(OSStatus)status |
+ flags:(VTEncodeInfoFlags)infoFlags |
+ sampleBuffer:(CMSampleBufferRef)sampleBuffer |
+ codecSpecificInfo:(id<RTCCodecSpecificInfo>)codecSpecificInfo |
+ width:(int32_t)width |
+ height:(int32_t)height |
+ renderTimeMs:(int64_t)renderTimeMs |
+ timestamp:(uint32_t)timestamp |
+ rotation:(RTCVideoRotation)rotation { |
+ if (status != noErr) { |
+ LOG(LS_ERROR) << "H264 encode failed."; |
+ return; |
+ } |
+ if (infoFlags & kVTEncodeInfo_FrameDropped) { |
+ LOG(LS_INFO) << "H264 encode dropped frame."; |
+ return; |
+ } |
+ |
+ BOOL isKeyframe = NO; |
+ CFArrayRef attachments = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, 0); |
+ if (attachments != nullptr && CFArrayGetCount(attachments)) { |
+ CFDictionaryRef attachment = |
+ static_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(attachments, 0)); |
+ isKeyframe = !CFDictionaryContainsKey(attachment, kCMSampleAttachmentKey_NotSync); |
+ } |
+ |
+ if (isKeyframe) { |
+ LOG(LS_INFO) << "Generated keyframe"; |
+ } |
+ |
+ // Convert the sample buffer into a buffer suitable for RTP packetization. |
+ // TODO(tkchin): Allocate buffers through a pool. |
+ std::unique_ptr<rtc::Buffer> buffer(new rtc::Buffer()); |
+ RTCRtpFragmentationHeader *header; |
+ { |
+ webrtc::RTPFragmentationHeader *header_cpp; |
+ bool result = |
+ H264CMSampleBufferToAnnexBBuffer(sampleBuffer, isKeyframe, buffer.get(), &header_cpp); |
+ header = [[RTCRtpFragmentationHeader alloc] initWithFragmentationHeader:header_cpp]; |
+ if (!result) { |
+ return; |
+ } |
+ } |
+ |
+ RTCEncodedImage *frame = [[RTCEncodedImage alloc] init]; |
+ frame.buffer = [NSData dataWithBytesNoCopy:buffer->data() length:buffer->size() freeWhenDone:NO]; |
+ frame.encodedWidth = width; |
+ frame.encodedHeight = height; |
+ frame.completeFrame = YES; |
+ frame.frameType = isKeyframe ? VideoFrameKey : VideoFrameDelta; |
+ frame.captureTimeMs = renderTimeMs; |
+ frame.timeStamp = timestamp; |
+ frame.rotation = rotation; |
+ frame.contentType = (_mode == Screensharing) ? Screenshare : Unspecified; |
+ frame.isTimingFrame = NO; |
+ |
+ int qp; |
+ _h264BitstreamParser.ParseBitstream(buffer->data(), buffer->size()); |
+ _h264BitstreamParser.GetLastSliceQp(&qp); |
+ frame.qp = @(qp); |
+ |
+ BOOL res = _callback(frame, codecSpecificInfo, header); |
+ if (!res) { |
+ LOG(LS_ERROR) << "Encode callback failed"; |
+ return; |
+ } |
+ _bitrateAdjuster->Update(frame.buffer.length); |
+} |
+ |
+@end |