OLD | NEW |
(Empty) | |
| 1 /* |
| 2 * Copyright 2017 The WebRTC project authors. All Rights Reserved. |
| 3 * |
| 4 * Use of this source code is governed by a BSD-style license |
| 5 * that can be found in the LICENSE file in the root of the source |
| 6 * tree. An additional intellectual property rights grant can be found |
| 7 * in the file PATENTS. All contributing project authors may |
| 8 * be found in the AUTHORS file in the root of the source tree. |
| 9 */ |
| 10 |
| 11 #import "WebRTC/RTCVideoCodecH264.h" |
| 12 |
| 13 #import <VideoToolbox/VideoToolbox.h> |
| 14 #include <vector> |
| 15 |
| 16 #import "Video/RTCVideoCodec+Private.h" |
| 17 #import "WebRTC/RTCVideoCodec.h" |
| 18 #import "WebRTC/RTCVideoFrame.h" |
| 19 #import "WebRTC/RTCVideoFrameBuffer.h" |
| 20 #import "helpers.h" |
| 21 #if defined(WEBRTC_IOS) |
| 22 #import "Common/RTCUIApplicationStatusObserver.h" |
| 23 #import "WebRTC/UIDevice+RTCDevice.h" |
| 24 #endif |
| 25 |
| 26 #include "libyuv/convert.h" |
| 27 #include "webrtc/common_video/h264/h264_bitstream_parser.h" |
| 28 #include "webrtc/common_video/include/bitrate_adjuster.h" |
| 29 #include "webrtc/modules/include/module_common_types.h" |
| 30 #include "webrtc/modules/video_coding/include/video_error_codes.h" |
| 31 #include "webrtc/rtc_base/buffer.h" |
| 32 #include "webrtc/rtc_base/logging.h" |
| 33 #include "webrtc/rtc_base/timeutils.h" |
| 34 #include "webrtc/sdk/objc/Framework/Classes/VideoToolbox/nalu_rewriter.h" |
| 35 #include "webrtc/system_wrappers/include/clock.h" |
| 36 |
| 37 @interface RTCVideoEncoderH264 () |
| 38 |
| 39 - (void)frameWasEncoded:(OSStatus)status |
| 40 flags:(VTEncodeInfoFlags)infoFlags |
| 41 sampleBuffer:(CMSampleBufferRef)sampleBuffer |
| 42 codecSpecificInfo:(id<RTCCodecSpecificInfo>)codecSpecificInfo |
| 43 width:(int32_t)width |
| 44 height:(int32_t)height |
| 45 renderTimeMs:(int64_t)renderTimeMs |
| 46 timestamp:(uint32_t)timestamp |
| 47 rotation:(RTCVideoRotation)rotation; |
| 48 |
| 49 @end |
| 50 |
| 51 // The ratio between kVTCompressionPropertyKey_DataRateLimits and |
| 52 // kVTCompressionPropertyKey_AverageBitRate. The data rate limit is set higher |
| 53 // than the average bit rate to avoid undershooting the target. |
| 54 const float kLimitToAverageBitRateFactor = 1.5f; |
| 55 |
| 56 // Struct that we pass to the encoder per frame to encode. We receive it again |
| 57 // in the encoder callback. |
| 58 struct RTCFrameEncodeParams { |
| 59 RTCFrameEncodeParams(RTCVideoEncoderH264 *e, |
| 60 RTCCodecSpecificInfoH264 *csi, |
| 61 int32_t w, |
| 62 int32_t h, |
| 63 int64_t rtms, |
| 64 uint32_t ts, |
| 65 RTCVideoRotation r) |
| 66 : encoder(e), width(w), height(h), render_time_ms(rtms), timestamp(ts), ro
tation(r) { |
| 67 if (csi) { |
| 68 codecSpecificInfo = csi; |
| 69 } else { |
| 70 codecSpecificInfo = [[RTCCodecSpecificInfoH264 alloc] init]; |
| 71 } |
| 72 } |
| 73 |
| 74 RTCVideoEncoderH264 *encoder; |
| 75 RTCCodecSpecificInfoH264 *codecSpecificInfo; |
| 76 int32_t width; |
| 77 int32_t height; |
| 78 int64_t render_time_ms; |
| 79 uint32_t timestamp; |
| 80 RTCVideoRotation rotation; |
| 81 }; |
| 82 |
| 83 // This is the callback function that VideoToolbox calls when encode is |
| 84 // complete. From inspection this happens on its own queue. |
| 85 void compressionOutputCallback(void *encoder, |
| 86 void *params, |
| 87 OSStatus status, |
| 88 VTEncodeInfoFlags infoFlags, |
| 89 CMSampleBufferRef sampleBuffer) { |
| 90 std::unique_ptr<RTCFrameEncodeParams> encodeParams( |
| 91 reinterpret_cast<RTCFrameEncodeParams *>(params)); |
| 92 [encodeParams->encoder frameWasEncoded:status |
| 93 flags:infoFlags |
| 94 sampleBuffer:sampleBuffer |
| 95 codecSpecificInfo:encodeParams->codecSpecificInfo |
| 96 width:encodeParams->width |
| 97 height:encodeParams->height |
| 98 renderTimeMs:encodeParams->render_time_ms |
| 99 timestamp:encodeParams->timestamp |
| 100 rotation:encodeParams->rotation]; |
| 101 } |
| 102 |
| 103 @implementation RTCVideoEncoderH264 { |
| 104 RTCVideoCodecInfo *_codecInfo; |
| 105 webrtc::BitrateAdjuster *_bitrateAdjuster; |
| 106 uint32_t _targetBitrateBps; |
| 107 uint32_t _encoderBitrateBps; |
| 108 RTCH264PacketizationMode _packetizationMode; |
| 109 CFStringRef _profile; |
| 110 RTCVideoEncoderCallback _callback; |
| 111 int32_t _width; |
| 112 int32_t _height; |
| 113 VTCompressionSessionRef _compressionSession; |
| 114 RTCVideoCodecMode _mode; |
| 115 |
| 116 webrtc::H264BitstreamParser _h264BitstreamParser; |
| 117 std::vector<uint8_t> _nv12ScaleBuffer; |
| 118 } |
| 119 |
| 120 - (instancetype)initWithCodecInfo:(RTCVideoCodecInfo *)codecInfo { |
| 121 if (self = [super init]) { |
| 122 _codecInfo = codecInfo; |
| 123 _bitrateAdjuster = new webrtc::BitrateAdjuster(webrtc::Clock::GetRealTimeClo
ck(), .5, .95); |
| 124 _packetizationMode = NonInterleaved; |
| 125 _profile = (__bridge CFStringRef)codecInfo.parameters[@"videotoolbox-profile
"]; |
| 126 } |
| 127 return self; |
| 128 } |
| 129 |
| 130 - (void)dealloc { |
| 131 [self destroyCompressionSession]; |
| 132 } |
| 133 |
| 134 - (void)setCallback:(RTCVideoEncoderCallback)callback { |
| 135 _callback = callback; |
| 136 } |
| 137 |
| 138 - (int)initEncodeWithSettings:(RTCVideoEncoderSettings *)settings numberOfCores:
(int)numberOfCores { |
| 139 RTC_DCHECK(settings); |
| 140 RTC_DCHECK([settings.name isEqualToString:@"H264"]); |
| 141 |
| 142 _width = settings.width; |
| 143 _height = settings.height; |
| 144 _mode = settings.mode; |
| 145 |
| 146 // We can only set average bitrate on the HW encoder. |
| 147 _targetBitrateBps = settings.startBitrate; |
| 148 _bitrateAdjuster->SetTargetBitrateBps(_targetBitrateBps); |
| 149 |
| 150 // TODO(tkchin): Try setting payload size via |
| 151 // kVTCompressionPropertyKey_MaxH264SliceBytes. |
| 152 |
| 153 return [self resetCompressionSession]; |
| 154 } |
| 155 |
| 156 - (int)releaseEncode { |
| 157 // Need to reset so that the session is invalidated and won't use the |
| 158 // callback anymore. Do not remove callback until the session is invalidated |
| 159 // since async encoder callbacks can occur until invalidation. |
| 160 int ret = [self resetCompressionSession]; |
| 161 _callback = nullptr; |
| 162 return ret; |
| 163 } |
| 164 |
| 165 - (int)encode:(RTCVideoFrame *)frame |
| 166 codecSpecificInfo:(id<RTCCodecSpecificInfo>)codecSpecificInfo |
| 167 frameTypes:(NSArray<NSNumber *> *)frameTypes { |
| 168 RTC_DCHECK_EQ(frame.width, _width); |
| 169 RTC_DCHECK_EQ(frame.height, _height); |
| 170 if (!_callback || !_compressionSession) { |
| 171 return WEBRTC_VIDEO_CODEC_UNINITIALIZED; |
| 172 } |
| 173 #if defined(WEBRTC_IOS) |
| 174 if (![[RTCUIApplicationStatusObserver sharedInstance] isApplicationActive]) { |
| 175 // Ignore all encode requests when app isn't active. In this state, the |
| 176 // hardware encoder has been invalidated by the OS. |
| 177 return WEBRTC_VIDEO_CODEC_OK; |
| 178 } |
| 179 #endif |
| 180 BOOL isKeyframeRequired = NO; |
| 181 |
| 182 // Get a pixel buffer from the pool and copy frame data over. |
| 183 CVPixelBufferPoolRef pixelBufferPool = |
| 184 VTCompressionSessionGetPixelBufferPool(_compressionSession); |
| 185 |
| 186 #if defined(WEBRTC_IOS) |
| 187 if (!pixelBufferPool) { |
| 188 // Kind of a hack. On backgrounding, the compression session seems to get |
| 189 // invalidated, which causes this pool call to fail when the application |
| 190 // is foregrounded and frames are being sent for encoding again. |
| 191 // Resetting the session when this happens fixes the issue. |
| 192 // In addition we request a keyframe so video can recover quickly. |
| 193 [self resetCompressionSession]; |
| 194 pixelBufferPool = VTCompressionSessionGetPixelBufferPool(_compressionSession
); |
| 195 isKeyframeRequired = YES; |
| 196 LOG(LS_INFO) << "Resetting compression session due to invalid pool."; |
| 197 } |
| 198 #endif |
| 199 |
| 200 CVPixelBufferRef pixelBuffer = nullptr; |
| 201 if ([frame.buffer isKindOfClass:[RTCCVPixelBuffer class]]) { |
| 202 // Native frame buffer |
| 203 RTCCVPixelBuffer *rtcPixelBuffer = (RTCCVPixelBuffer *)frame.buffer; |
| 204 if (![rtcPixelBuffer requiresCropping]) { |
| 205 // This pixel buffer might have a higher resolution than what the |
| 206 // compression session is configured to. The compression session can |
| 207 // handle that and will output encoded frames in the configured |
| 208 // resolution regardless of the input pixel buffer resolution. |
| 209 pixelBuffer = rtcPixelBuffer.pixelBuffer; |
| 210 CVBufferRetain(pixelBuffer); |
| 211 } else { |
| 212 // Cropping required, we need to crop and scale to a new pixel buffer. |
| 213 pixelBuffer = [self createPixelBufferFromPool:pixelBufferPool]; |
| 214 if (!pixelBuffer) { |
| 215 return WEBRTC_VIDEO_CODEC_ERROR; |
| 216 } |
| 217 int dstWidth = CVPixelBufferGetWidth(pixelBuffer); |
| 218 int dstHeight = CVPixelBufferGetHeight(pixelBuffer); |
| 219 if ([rtcPixelBuffer requiresScalingToWidth:dstWidth height:dstHeight]) { |
| 220 int size = |
| 221 [rtcPixelBuffer bufferSizeForCroppingAndScalingToWidth:dstWidth heig
ht:dstHeight]; |
| 222 _nv12ScaleBuffer.resize(size); |
| 223 } else { |
| 224 _nv12ScaleBuffer.clear(); |
| 225 } |
| 226 _nv12ScaleBuffer.shrink_to_fit(); |
| 227 if (![rtcPixelBuffer cropAndScaleTo:pixelBuffer withTempBuffer:_nv12ScaleB
uffer.data()]) { |
| 228 return WEBRTC_VIDEO_CODEC_ERROR; |
| 229 } |
| 230 } |
| 231 } |
| 232 |
| 233 if (!pixelBuffer) { |
| 234 // We did not have a native frame buffer |
| 235 pixelBuffer = [self createPixelBufferFromPool:pixelBufferPool]; |
| 236 if (!pixelBuffer) { |
| 237 return WEBRTC_VIDEO_CODEC_ERROR; |
| 238 } |
| 239 RTC_DCHECK(pixelBuffer); |
| 240 if (![self copyVideoFrame:[frame.buffer toI420] toPixelBuffer:pixelBuffer])
{ |
| 241 LOG(LS_ERROR) << "Failed to copy frame data."; |
| 242 CVBufferRelease(pixelBuffer); |
| 243 return WEBRTC_VIDEO_CODEC_ERROR; |
| 244 } |
| 245 } |
| 246 |
| 247 // Check if we need a keyframe. |
| 248 if (!isKeyframeRequired && frameTypes) { |
| 249 for (NSNumber *frameType in frameTypes) { |
| 250 if ((RTCFrameType)frameType.intValue == RTCFrameType::VideoFrameKey) { |
| 251 isKeyframeRequired = YES; |
| 252 break; |
| 253 } |
| 254 } |
| 255 } |
| 256 |
| 257 CMTime presentationTimeStamp = CMTimeMake(frame.timeStampNs / rtc::kNumNanosec
sPerMillisec, 1000); |
| 258 CFDictionaryRef frameProperties = nullptr; |
| 259 if (isKeyframeRequired) { |
| 260 CFTypeRef keys[] = {kVTEncodeFrameOptionKey_ForceKeyFrame}; |
| 261 CFTypeRef values[] = {kCFBooleanTrue}; |
| 262 frameProperties = CreateCFTypeDictionary(keys, values, 1); |
| 263 } |
| 264 |
| 265 std::unique_ptr<RTCFrameEncodeParams> encodeParams; |
| 266 encodeParams.reset(new RTCFrameEncodeParams(self, |
| 267 codecSpecificInfo, |
| 268 _width, |
| 269 _height, |
| 270 frame.timeStampNs / rtc::kNumNanos
ecsPerMillisec, |
| 271 frame.timeStamp, |
| 272 frame.rotation)); |
| 273 encodeParams->codecSpecificInfo.packetizationMode = _packetizationMode; |
| 274 |
| 275 // Update the bitrate if needed. |
| 276 [self setBitrateBps:_bitrateAdjuster->GetAdjustedBitrateBps()]; |
| 277 |
| 278 OSStatus status = VTCompressionSessionEncodeFrame(_compressionSession, |
| 279 pixelBuffer, |
| 280 presentationTimeStamp, |
| 281 kCMTimeInvalid, |
| 282 frameProperties, |
| 283 encodeParams.release(), |
| 284 nullptr); |
| 285 if (frameProperties) { |
| 286 CFRelease(frameProperties); |
| 287 } |
| 288 if (pixelBuffer) { |
| 289 CVBufferRelease(pixelBuffer); |
| 290 } |
| 291 if (status != noErr) { |
| 292 LOG(LS_ERROR) << "Failed to encode frame with code: " << status; |
| 293 return WEBRTC_VIDEO_CODEC_ERROR; |
| 294 } |
| 295 return WEBRTC_VIDEO_CODEC_OK; |
| 296 } |
| 297 |
| 298 - (int)setBitrate:(uint32_t)bitrateKbit framerate:(uint32_t)framerate { |
| 299 _targetBitrateBps = 1000 * bitrateKbit; |
| 300 _bitrateAdjuster->SetTargetBitrateBps(_targetBitrateBps); |
| 301 [self setBitrateBps:_bitrateAdjuster->GetAdjustedBitrateBps()]; |
| 302 return WEBRTC_VIDEO_CODEC_OK; |
| 303 } |
| 304 |
| 305 #pragma mark - Private |
| 306 |
| 307 - (void)setBitrateBps:(uint32_t)bitrateBps { |
| 308 if (_encoderBitrateBps != bitrateBps) { |
| 309 [self setEncoderBitrateBps:bitrateBps]; |
| 310 } |
| 311 } |
| 312 |
| 313 - (void)setEncoderBitrateBps:(uint32_t)bitrateBps { |
| 314 if (_compressionSession) { |
| 315 SetVTSessionProperty(_compressionSession, kVTCompressionPropertyKey_AverageB
itRate, bitrateBps); |
| 316 |
| 317 // TODO(tkchin): Add a helper method to set array value. |
| 318 int64_t dataLimitBytesPerSecondValue = |
| 319 static_cast<int64_t>(bitrateBps * kLimitToAverageBitRateFactor / 8); |
| 320 CFNumberRef bytesPerSecond = |
| 321 CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt64Type, &dataLimitBytes
PerSecondValue); |
| 322 int64_t oneSecondValue = 1; |
| 323 CFNumberRef oneSecond = |
| 324 CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt64Type, &oneSecondValue
); |
| 325 const void *nums[2] = {bytesPerSecond, oneSecond}; |
| 326 CFArrayRef dataRateLimits = CFArrayCreate(nullptr, nums, 2, &kCFTypeArrayCal
lBacks); |
| 327 OSStatus status = VTSessionSetProperty( |
| 328 _compressionSession, kVTCompressionPropertyKey_DataRateLimits, dataRateL
imits); |
| 329 if (bytesPerSecond) { |
| 330 CFRelease(bytesPerSecond); |
| 331 } |
| 332 if (oneSecond) { |
| 333 CFRelease(oneSecond); |
| 334 } |
| 335 if (dataRateLimits) { |
| 336 CFRelease(dataRateLimits); |
| 337 } |
| 338 if (status != noErr) { |
| 339 LOG(LS_ERROR) << "Failed to set data rate limit"; |
| 340 } |
| 341 |
| 342 _encoderBitrateBps = bitrateBps; |
| 343 } |
| 344 } |
| 345 |
| 346 - (int)resetCompressionSession { |
| 347 [self destroyCompressionSession]; |
| 348 |
| 349 // Set source image buffer attributes. These attributes will be present on |
| 350 // buffers retrieved from the encoder's pixel buffer pool. |
| 351 const size_t attributesSize = 3; |
| 352 CFTypeRef keys[attributesSize] = { |
| 353 #if defined(WEBRTC_IOS) |
| 354 kCVPixelBufferOpenGLESCompatibilityKey, |
| 355 #elif defined(WEBRTC_MAC) |
| 356 kCVPixelBufferOpenGLCompatibilityKey, |
| 357 #endif |
| 358 kCVPixelBufferIOSurfacePropertiesKey, |
| 359 kCVPixelBufferPixelFormatTypeKey |
| 360 }; |
| 361 CFDictionaryRef ioSurfaceValue = CreateCFTypeDictionary(nullptr, nullptr, 0); |
| 362 int64_t nv12type = kCVPixelFormatType_420YpCbCr8BiPlanarFullRange; |
| 363 CFNumberRef pixelFormat = CFNumberCreate(nullptr, kCFNumberLongType, &nv12type
); |
| 364 CFTypeRef values[attributesSize] = {kCFBooleanTrue, ioSurfaceValue, pixelForma
t}; |
| 365 CFDictionaryRef sourceAttributes = CreateCFTypeDictionary(keys, values, attrib
utesSize); |
| 366 if (ioSurfaceValue) { |
| 367 CFRelease(ioSurfaceValue); |
| 368 ioSurfaceValue = nullptr; |
| 369 } |
| 370 if (pixelFormat) { |
| 371 CFRelease(pixelFormat); |
| 372 pixelFormat = nullptr; |
| 373 } |
| 374 OSStatus status = VTCompressionSessionCreate(nullptr, // use default allocato
r |
| 375 _width, |
| 376 _height, |
| 377 kCMVideoCodecType_H264, |
| 378 nullptr, // use default encoder |
| 379 sourceAttributes, |
| 380 nullptr, // use default compress
ed data allocator |
| 381 compressionOutputCallback, |
| 382 nullptr, |
| 383 &_compressionSession); |
| 384 if (sourceAttributes) { |
| 385 CFRelease(sourceAttributes); |
| 386 sourceAttributes = nullptr; |
| 387 } |
| 388 if (status != noErr) { |
| 389 LOG(LS_ERROR) << "Failed to create compression session: " << status; |
| 390 return WEBRTC_VIDEO_CODEC_ERROR; |
| 391 } |
| 392 [self configureCompressionSession]; |
| 393 return WEBRTC_VIDEO_CODEC_OK; |
| 394 } |
| 395 |
| 396 - (void)configureCompressionSession { |
| 397 RTC_DCHECK(_compressionSession); |
| 398 SetVTSessionProperty(_compressionSession, kVTCompressionPropertyKey_RealTime,
true); |
| 399 SetVTSessionProperty(_compressionSession, kVTCompressionPropertyKey_ProfileLev
el, _profile); |
| 400 SetVTSessionProperty(_compressionSession, kVTCompressionPropertyKey_AllowFrame
Reordering, false); |
| 401 [self setEncoderBitrateBps:_targetBitrateBps]; |
| 402 // TODO(tkchin): Look at entropy mode and colorspace matrices. |
| 403 // TODO(tkchin): Investigate to see if there's any way to make this work. |
| 404 // May need it to interop with Android. Currently this call just fails. |
| 405 // On inspecting encoder output on iOS8, this value is set to 6. |
| 406 // internal::SetVTSessionProperty(compression_session_, |
| 407 // kVTCompressionPropertyKey_MaxFrameDelayCount, |
| 408 // 1); |
| 409 |
| 410 // Set a relatively large value for keyframe emission (7200 frames or 4 minute
s). |
| 411 SetVTSessionProperty(_compressionSession, kVTCompressionPropertyKey_MaxKeyFram
eInterval, 7200); |
| 412 SetVTSessionProperty( |
| 413 _compressionSession, kVTCompressionPropertyKey_MaxKeyFrameIntervalDuration
, 240); |
| 414 } |
| 415 |
| 416 - (void)destroyCompressionSession { |
| 417 if (_compressionSession) { |
| 418 VTCompressionSessionInvalidate(_compressionSession); |
| 419 CFRelease(_compressionSession); |
| 420 _compressionSession = nullptr; |
| 421 } |
| 422 } |
| 423 |
| 424 // We receive I420Frames as input, but we need to feed CVPixelBuffers into the |
| 425 // encoder. This performs the copy and format conversion. |
| 426 // TODO(tkchin): See if encoder will accept i420 frames and compare performance. |
| 427 - (BOOL)copyVideoFrame:(id<RTCI420Buffer>)frameBuffer toPixelBuffer:(CVPixelBuff
erRef)pixelBuffer { |
| 428 RTC_DCHECK(pixelBuffer); |
| 429 RTC_DCHECK_EQ(CVPixelBufferGetPixelFormatType(pixelBuffer), |
| 430 kCVPixelFormatType_420YpCbCr8BiPlanarFullRange); |
| 431 RTC_DCHECK_EQ(CVPixelBufferGetHeightOfPlane(pixelBuffer, 0), frameBuffer.heigh
t); |
| 432 RTC_DCHECK_EQ(CVPixelBufferGetWidthOfPlane(pixelBuffer, 0), frameBuffer.width)
; |
| 433 |
| 434 CVReturn cvRet = CVPixelBufferLockBaseAddress(pixelBuffer, 0); |
| 435 if (cvRet != kCVReturnSuccess) { |
| 436 LOG(LS_ERROR) << "Failed to lock base address: " << cvRet; |
| 437 return NO; |
| 438 } |
| 439 uint8_t *dstY = reinterpret_cast<uint8_t *>(CVPixelBufferGetBaseAddressOfPlane
(pixelBuffer, 0)); |
| 440 int dstStrideY = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0); |
| 441 uint8_t *dstUV = reinterpret_cast<uint8_t *>(CVPixelBufferGetBaseAddressOfPlan
e(pixelBuffer, 1)); |
| 442 int dstStrideUV = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 1); |
| 443 // Convert I420 to NV12. |
| 444 int ret = libyuv::I420ToNV12(frameBuffer.dataY, frameBuffer.strideY, |
| 445 frameBuffer.dataU, frameBuffer.strideU, |
| 446 frameBuffer.dataV, frameBuffer.strideV, |
| 447 dstY, dstStrideY, dstUV, dstStrideUV, |
| 448 frameBuffer.width, frameBuffer.height); |
| 449 CVPixelBufferUnlockBaseAddress(pixelBuffer, 0); |
| 450 if (ret) { |
| 451 LOG(LS_ERROR) << "Error converting I420 VideoFrame to NV12 :" << ret; |
| 452 return NO; |
| 453 } |
| 454 return YES; |
| 455 } |
| 456 |
| 457 - (CVPixelBufferRef)createPixelBufferFromPool:(CVPixelBufferPoolRef)pixelBufferP
ool { |
| 458 if (!pixelBufferPool) { |
| 459 LOG(LS_ERROR) << "Failed to get pixel buffer pool."; |
| 460 return nullptr; |
| 461 } |
| 462 CVPixelBufferRef pixelBuffer; |
| 463 CVReturn ret = CVPixelBufferPoolCreatePixelBuffer(nullptr, pixelBufferPool, &p
ixelBuffer); |
| 464 if (ret != kCVReturnSuccess) { |
| 465 LOG(LS_ERROR) << "Failed to create pixel buffer: " << ret; |
| 466 // We probably want to drop frames here, since failure probably means |
| 467 // that the pool is empty. |
| 468 return nullptr; |
| 469 } |
| 470 return pixelBuffer; |
| 471 } |
| 472 |
| 473 - (void)frameWasEncoded:(OSStatus)status |
| 474 flags:(VTEncodeInfoFlags)infoFlags |
| 475 sampleBuffer:(CMSampleBufferRef)sampleBuffer |
| 476 codecSpecificInfo:(id<RTCCodecSpecificInfo>)codecSpecificInfo |
| 477 width:(int32_t)width |
| 478 height:(int32_t)height |
| 479 renderTimeMs:(int64_t)renderTimeMs |
| 480 timestamp:(uint32_t)timestamp |
| 481 rotation:(RTCVideoRotation)rotation { |
| 482 if (status != noErr) { |
| 483 LOG(LS_ERROR) << "H264 encode failed."; |
| 484 return; |
| 485 } |
| 486 if (infoFlags & kVTEncodeInfo_FrameDropped) { |
| 487 LOG(LS_INFO) << "H264 encode dropped frame."; |
| 488 return; |
| 489 } |
| 490 |
| 491 BOOL isKeyframe = NO; |
| 492 CFArrayRef attachments = CMSampleBufferGetSampleAttachmentsArray(sampleBuffer,
0); |
| 493 if (attachments != nullptr && CFArrayGetCount(attachments)) { |
| 494 CFDictionaryRef attachment = |
| 495 static_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(attachments, 0)); |
| 496 isKeyframe = !CFDictionaryContainsKey(attachment, kCMSampleAttachmentKey_Not
Sync); |
| 497 } |
| 498 |
| 499 if (isKeyframe) { |
| 500 LOG(LS_INFO) << "Generated keyframe"; |
| 501 } |
| 502 |
| 503 // Convert the sample buffer into a buffer suitable for RTP packetization. |
| 504 // TODO(tkchin): Allocate buffers through a pool. |
| 505 std::unique_ptr<rtc::Buffer> buffer(new rtc::Buffer()); |
| 506 RTCRtpFragmentationHeader *header; |
| 507 { |
| 508 webrtc::RTPFragmentationHeader *header_cpp; |
| 509 bool result = |
| 510 H264CMSampleBufferToAnnexBBuffer(sampleBuffer, isKeyframe, buffer.get(),
&header_cpp); |
| 511 header = [[RTCRtpFragmentationHeader alloc] initWithFragmentationHeader:head
er_cpp]; |
| 512 if (!result) { |
| 513 return; |
| 514 } |
| 515 } |
| 516 |
| 517 RTCEncodedImage *frame = [[RTCEncodedImage alloc] init]; |
| 518 frame.buffer = [NSData dataWithBytesNoCopy:buffer->data() length:buffer->size(
) freeWhenDone:NO]; |
| 519 frame.encodedWidth = width; |
| 520 frame.encodedHeight = height; |
| 521 frame.completeFrame = YES; |
| 522 frame.frameType = isKeyframe ? VideoFrameKey : VideoFrameDelta; |
| 523 frame.captureTimeMs = renderTimeMs; |
| 524 frame.timeStamp = timestamp; |
| 525 frame.rotation = rotation; |
| 526 frame.contentType = (_mode == Screensharing) ? Screenshare : Unspecified; |
| 527 frame.isTimingFrame = NO; |
| 528 |
| 529 int qp; |
| 530 _h264BitstreamParser.ParseBitstream(buffer->data(), buffer->size()); |
| 531 _h264BitstreamParser.GetLastSliceQp(&qp); |
| 532 frame.qp = @(qp); |
| 533 |
| 534 BOOL res = _callback(frame, codecSpecificInfo, header); |
| 535 if (!res) { |
| 536 LOG(LS_ERROR) << "Encode callback failed"; |
| 537 return; |
| 538 } |
| 539 _bitrateAdjuster->Update(frame.buffer.length); |
| 540 } |
| 541 |
| 542 @end |
OLD | NEW |