18#include <freerdp/config.h>
23#include <CoreMedia/CoreMedia.h>
24#include <CoreVideo/CoreVideo.h>
25#include <VideoToolbox/VideoToolbox.h>
27#include <winpr/assert.h>
28#include <freerdp/log.h>
37} H264_VT_PARAMETER_SET;
41 VTDecompressionSessionRef session;
42 CMVideoFormatDescriptionRef format;
43 CVPixelBufferRef image;
45 BOOL parametersChanged;
49 H264_VT_PARAMETER_SET sps[32];
50 H264_VT_PARAMETER_SET pps[256];
51} H264_CONTEXT_VIDEOTOOLBOX;
65 CVPixelBufferRef image;
70WINPR_ATTR_MALLOC(free, 1)
71static
char *osstatus2str(OSStatus status)
75 (void)winpr_asprintf(&str, &len,
"OSStatus %ld", status);
83static BOOL videotoolbox_read_bit(H264_VT_BITS *bits, UINT32 *value)
87 WINPR_ASSERT(bits->data);
91 if (bits->offset >= bits->size)
93 UINT32
byte = bits->data[bits->offset++];
94 if ((bits->zeros == 2) && (
byte == 3))
96 if ((bits->offset >= bits->size) || (bits->data[bits->offset] > 3))
98 byte = bits->data[bits->offset++];
101 bits->zeros = (
byte == 0) ? MIN(bits->zeros + 1, 2) : 0;
105 *value = (bits->byte >> --bits->bits) & 1;
110static BOOL videotoolbox_read_ue(H264_VT_BITS *bits, UINT32 *value)
119 if (!videotoolbox_read_bit(bits, &bit))
127 for (UINT32 x = 0; x < zeros; x++)
129 if (!videotoolbox_read_bit(bits, &bit))
131 result = (result << 1) | bit;
138static size_t videotoolbox_start_code(
const BYTE *data,
size_t size,
size_t *offset)
141 WINPR_ASSERT(offset);
143 for (; (*offset < size) && ((size - *offset) >= 3); (*offset)++)
145 const BYTE *p = &data[*offset];
146 if ((p[0] != 0) || (p[1] != 0))
150 if (((size - *offset) >= 4) && (p[2] == 0) && (p[3] == 1))
158static BOOL videotoolbox_next_nal(
const BYTE *data,
size_t size,
size_t *offset,
const BYTE **nal,
162 WINPR_ASSERT(offset);
164 WINPR_ASSERT(length);
166 const size_t previous = *offset;
167 const size_t prefix = videotoolbox_start_code(data, size, offset);
171 for (
size_t x = previous; x < *offset; x++)
177 const size_t start = *offset;
178 (void)videotoolbox_start_code(data, size, offset);
179 size_t end = *offset;
180 while ((end > start) && (data[end - 1] == 0))
182 if ((end == start) || ((data[start] & 0x80) != 0))
185 *length = end - start;
190static BOOL videotoolbox_save_parameter(H264_CONTEXT_VIDEOTOOLBOX *sys,
const BYTE *nal,
196 H264_VT_BITS bits = { .data = nal + 1, .size = length - 1 };
199 H264_VT_PARAMETER_SET *parameter =
nullptr;
200 if ((nal[0] & 0x1F) == 7)
203 for (UINT32 x = 0; x < 24; x++)
205 if (!videotoolbox_read_bit(&bits, &
id))
208 if (!videotoolbox_read_ue(&bits, &
id) || (
id >= ARRAYSIZE(sys->sps)))
210 parameter = &sys->sps[id];
214 if (!videotoolbox_read_ue(&bits, &
id) || (
id >= ARRAYSIZE(sys->pps)) ||
215 !videotoolbox_read_ue(&bits, &spsId) || (spsId >= ARRAYSIZE(sys->sps)))
217 parameter = &sys->pps[id];
219 if ((parameter->size == length) && (memcmp(parameter->data, nal, length) == 0))
221 BYTE *copy = malloc(length);
224 memcpy(copy, nal, length);
225 free(parameter->data);
226 parameter->data = copy;
227 parameter->size = length;
228 parameter->spsId = spsId;
229 sys->parametersChanged = TRUE;
233static void videotoolbox_release_image(H264_CONTEXT *h264)
237 H264_CONTEXT_VIDEOTOOLBOX *sys = h264->pSystemData;
242 if (sys->imageLocked)
243 CVPixelBufferUnlockBaseAddress(sys->image, kCVPixelBufferLock_ReadOnly);
244 CVPixelBufferRelease(sys->image);
245 sys->image =
nullptr;
247 sys->imageLocked = FALSE;
248 for (
size_t x = 0; x < 3; x++)
250 h264->pYUVData[x] =
nullptr;
251 h264->iStride[x] = 0;
257static void videotoolbox_destroy_session(H264_CONTEXT *h264)
261 H264_CONTEXT_VIDEOTOOLBOX *sys = h264->pSystemData;
266 VTDecompressionSessionInvalidate(sys->session);
267 CFRelease(sys->session);
268 sys->session =
nullptr;
272 CFRelease(sys->format);
273 sys->format =
nullptr;
275 sys->needsIdr = TRUE;
276 h264->hwAccel = FALSE;
279static void videotoolbox_output(WINPR_ATTR_UNUSED
void *context,
void *sourceFrame, OSStatus status,
280 WINPR_ATTR_UNUSED VTDecodeInfoFlags flags, CVImageBufferRef image,
281 WINPR_ATTR_UNUSED CMTime pts, WINPR_ATTR_UNUSED CMTime duration)
283 WINPR_ASSERT(sourceFrame);
285 H264_VT_OUTPUT *output = sourceFrame;
287 output->status = status;
291 CVPixelBufferRelease(output->image);
292 output->image = CVPixelBufferRetain(image);
297static BOOL videotoolbox_ensure_session(H264_CONTEXT *h264, UINT32 ppsId)
301 H264_CONTEXT_VIDEOTOOLBOX *sys = h264->pSystemData;
304 if (sys->session && !sys->parametersChanged && (sys->ppsId == ppsId))
306 const H264_VT_PARAMETER_SET *pps = &sys->pps[ppsId];
307 const H264_VT_PARAMETER_SET *sps = &sys->sps[pps->spsId];
308 if (!pps->data || !sps->data)
310 const BYTE *parameters[] = { sps->data, pps->data };
311 const size_t lengths[] = { sps->size, pps->size };
312 CMVideoFormatDescriptionRef format =
nullptr;
313 OSStatus status = CMVideoFormatDescriptionCreateFromH264ParameterSets(
314 kCFAllocatorDefault, ARRAYSIZE(parameters), parameters, lengths, 4, &format);
317 char *str = osstatus2str(status);
318 WLog_Print(h264->log, WLOG_WARN,
"VideoToolbox H.264 format creation failed: %s", str);
324 const BOOL fullRange =
325 CMFormatDescriptionGetExtension(format, kCMFormatDescriptionExtension_FullRangeVideo) ==
327 const OSType pixelFormat = fullRange ? kCVPixelFormatType_420YpCbCr8PlanarFullRange
328 : kCVPixelFormatType_420YpCbCr8Planar;
329 if (sys->session && (sys->pixelFormat == pixelFormat) &&
330 VTDecompressionSessionCanAcceptFormatDescription(sys->session, format))
332 CFRelease(sys->format);
333 sys->format = format;
334 sys->parametersChanged = FALSE;
339 videotoolbox_destroy_session(h264);
340 CFNumberRef pixel = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &pixelFormat);
346 const void *keys[] = { kCVPixelBufferPixelFormatTypeKey };
347 const void *values[] = { pixel };
348 CFDictionaryRef attributes =
349 CFDictionaryCreate(kCFAllocatorDefault, keys, values, ARRAYSIZE(keys),
350 &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
357 const VTDecompressionOutputCallbackRecord callback = { videotoolbox_output,
nullptr };
360 status = VTDecompressionSessionCreate(kCFAllocatorDefault, format,
nullptr, attributes,
361 &callback, &sys->session);
362 CFRelease(attributes);
365 char *str = osstatus2str(status);
366 WLog_Print(h264->log, WLOG_WARN,
"VideoToolbox H.264 session creation failed: %s", str);
371 sys->format = format;
372 sys->pixelFormat = pixelFormat;
374 sys->parametersChanged = FALSE;
376 (void)VTSessionSetProperty(sys->session, kVTDecompressionPropertyKey_RealTime, kCFBooleanTrue);
377 if (__builtin_available(macOS 10.9, iOS 17.0, tvOS 17.0, *))
379 CFTypeRef hardware =
nullptr;
380 status = VTSessionCopyProperty(
381 sys->session, kVTDecompressionPropertyKey_UsingHardwareAcceleratedVideoDecoder,
382 kCFAllocatorDefault, &hardware);
385 h264->hwAccel = (hardware == kCFBooleanTrue);
386 WLog_Print(h264->log, WLOG_DEBUG,
"VideoToolbox H.264 decoder: %s",
387 h264->hwAccel ?
"hardware" :
"software");
396static BOOL videotoolbox_map_image(H264_CONTEXT *h264)
400 H264_CONTEXT_VIDEOTOOLBOX *sys = h264->pSystemData;
403 CVPixelBufferRef image = sys->image;
406 const size_t width = CVPixelBufferGetWidth(image);
407 const size_t height = CVPixelBufferGetHeight(image);
408 if ((width == 0) || (height == 0) || (width > UINT32_MAX) || (height > UINT32_MAX) ||
409 (CVPixelBufferGetPixelFormatType(image) != sys->pixelFormat) ||
410 (CVPixelBufferGetPlaneCount(image) != 3))
412 if (CVPixelBufferLockBaseAddress(image, kCVPixelBufferLock_ReadOnly) != kCVReturnSuccess)
414 sys->imageLocked = TRUE;
415 for (
size_t x = 0; x < 3; x++)
417 const size_t stride = CVPixelBufferGetBytesPerRowOfPlane(image, x);
418 const size_t planeWidth = (x == 0) ? width : (width + 1) / 2;
419 const size_t planeHeight = (x == 0) ? height : (height + 1) / 2;
420 BYTE *data = CVPixelBufferGetBaseAddressOfPlane(image, x);
421 if (!data || (stride < planeWidth) || (stride > UINT32_MAX) ||
422 (CVPixelBufferGetWidthOfPlane(image, x) < planeWidth) ||
423 (CVPixelBufferGetHeightOfPlane(image, x) < planeHeight))
425 h264->pYUVData[x] = data;
426 h264->iStride[x] = (UINT32)stride;
428 h264->YUVWidth = (UINT32)width;
429 h264->YUVHeight = (UINT32)height;
434static int videotoolbox_decompress(H264_CONTEXT *WINPR_RESTRICT h264,
435 const BYTE *WINPR_RESTRICT data, UINT32 size)
438 WINPR_ASSERT(data || (size == 0));
440 H264_CONTEXT_VIDEOTOOLBOX *sys = h264->pSystemData;
443 CMBlockBufferRef block =
nullptr;
444 CMSampleBufferRef sample =
nullptr;
445 H264_VT_OUTPUT output = { 0 };
447 size_t sampleSize = 0;
449 UINT32 ppsId = UINT32_MAX;
451 videotoolbox_release_image(h264);
454 while (offset < size)
456 const BYTE *nal =
nullptr;
458 if (!videotoolbox_next_nal(data, size, &offset, &nal, &length))
460 const BYTE type = nal[0] & 0x1F;
461 if ((type == 7) || (type == 8))
463 if (!videotoolbox_save_parameter(sys, nal, length))
467 if ((type == 1) || (type == 5))
469 H264_VT_BITS bits = { .data = nal + 1, .size = length - 1 };
472 if (!videotoolbox_read_ue(&bits, &value) || !videotoolbox_read_ue(&bits, &value) ||
473 !videotoolbox_read_ue(&bits, &slicePps) || (slicePps >= ARRAYSIZE(sys->pps)))
475 if ((ppsId != UINT32_MAX) && ((ppsId != slicePps) || (idr != (type == 5))))
480 else if ((type == 9) || (type == 10) || (type == 11) || (type == 12))
484 if ((length > UINT32_MAX - 4) || (sampleSize > UINT32_MAX - length - 4))
486 sampleSize += length + 4;
489 if (ppsId == UINT32_MAX)
491 if (!videotoolbox_ensure_session(h264, ppsId))
493 if (sys->needsIdr && !idr)
495 WLog_Print(h264->log, WLOG_WARN,
"VideoToolbox H.264 decoder requires an IDR picture");
498 OSStatus status = CMBlockBufferCreateWithMemoryBlock(
499 kCFAllocatorDefault,
nullptr, sampleSize, kCFAllocatorDefault,
nullptr, 0, sampleSize,
500 kCMBlockBufferAssureMemoryNowFlag, &block);
503 char *bytes =
nullptr;
504 status = CMBlockBufferGetDataPointer(block, 0,
nullptr,
nullptr, &bytes);
505 if ((status != noErr) || !bytes)
509 while (offset < size)
511 const BYTE *nal =
nullptr;
513 if (!videotoolbox_next_nal(data, size, &offset, &nal, &length))
515 const BYTE type = nal[0] & 0x1F;
516 if ((type != 1) && (type != 5) && (type != 6))
518 bytes[written++] = (char)((length >> 24) & 0xFF);
519 bytes[written++] = (char)((length >> 16) & 0xFF);
520 bytes[written++] = (char)((length >> 8) & 0xFF);
521 bytes[written++] = (char)(length & 0xFF);
522 memcpy(&bytes[written], nal, length);
525 WINPR_ASSERT(written == sampleSize);
526 status = CMSampleBufferCreateReady(kCFAllocatorDefault, block, sys->format, 1, 0,
nullptr, 1,
527 &sampleSize, &sample);
533 status = VTDecompressionSessionDecodeFrame(sys->session, sample, 0, &output,
nullptr);
534 if ((status != noErr) || (output.status != noErr) || (output.count != 1) || !output.image)
536 char *str1 = osstatus2str(status);
537 char *str2 = osstatus2str(output.status);
538 WLog_Print(h264->log, WLOG_WARN,
539 "VideoToolbox H.264 decode failed: %s / %s, outputs=%" PRIu32, str1, str2,
545 sys->image = output.image;
546 output.image =
nullptr;
547 if (!videotoolbox_map_image(h264))
549 sys->needsIdr = FALSE;
553 CVPixelBufferRelease(output.image);
562 videotoolbox_release_image(h264);
563 videotoolbox_destroy_session(h264);
573 VTCompressionSessionRef session;
579 H264_RATECONTROL_MODE rateControl;
588} H264_CONTEXT_VT_ENCODER;
593static BOOL videotoolbox_encoder_full_range(
const H264_CONTEXT *h264)
596 switch (h264->UsageType)
598 case H264_CAMERA_VIDEO_REAL_TIME:
599 case H264_CAMERA_VIDEO_NON_REAL_TIME:
607static BOOL videotoolbox_encoder_append(H264_CONTEXT_VT_ENCODER *enc,
const void *data,
size_t size)
611 if (size > SIZE_MAX - enc->length)
613 const size_t need = enc->length + size;
614 if (need > enc->size)
616 size_t newSize = MAX(need, 4096);
617 if (enc->size <= SIZE_MAX / 2)
618 newSize = MAX(newSize, enc->size * 2);
619 BYTE *tmp = realloc(enc->data, newSize);
626 memcpy(&enc->data[enc->length], data, size);
632static BOOL videotoolbox_encoder_append_nal(H264_CONTEXT_VT_ENCODER *enc,
const void *nal,
635 static const BYTE startCode[] = { 0x00, 0x00, 0x00, 0x01 };
636 return videotoolbox_encoder_append(enc, startCode,
sizeof(startCode)) &&
637 videotoolbox_encoder_append(enc, nal, size);
641static BOOL videotoolbox_encoder_is_keyframe(CMSampleBufferRef sample)
644 CFArrayRef attachments = CMSampleBufferGetSampleAttachmentsArray(sample,
false);
645 if (!attachments || (CFArrayGetCount(attachments) < 1))
647 CFDictionaryRef dict = CFArrayGetValueAtIndex(attachments, 0);
648 const void *notSync =
nullptr;
649 if (dict && CFDictionaryGetValueIfPresent(dict, kCMSampleAttachmentKey_NotSync, ¬Sync) &&
650 notSync && CFBooleanGetValue((CFBooleanRef)notSync))
655static void videotoolbox_encoder_fail(H264_CONTEXT_VT_ENCODER *enc, OSStatus status,
660 enc->status = status;
661 char *str = osstatus2str(status);
662 WLog_Print(enc->log, WLOG_ERROR,
"VideoToolbox H.264 encoder: %s failed: %s", what, str);
666static void videotoolbox_encoder_output(
void *refcon, WINPR_ATTR_UNUSED
void *frameRefcon,
667 OSStatus status, VTEncodeInfoFlags flags,
668 CMSampleBufferRef sample)
670 H264_CONTEXT_VT_ENCODER *enc = refcon;
675 videotoolbox_encoder_fail(enc, status,
"encoding a frame");
679 if (!sample || ((flags & kVTEncodeInfo_FrameDropped) != 0))
682 int nalHeaderLength = 4;
683 CMFormatDescriptionRef format = CMSampleBufferGetFormatDescription(sample);
687 OSStatus rc = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(
688 format, 0,
nullptr,
nullptr, &count, &nalHeaderLength);
691 videotoolbox_encoder_fail(enc, rc,
"reading the parameter sets");
694 if (videotoolbox_encoder_is_keyframe(sample))
696 for (
size_t x = 0; x < count; x++)
698 const uint8_t *
set =
nullptr;
700 rc = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(format, x, &
set, &setSize,
704 videotoolbox_encoder_fail(enc, rc,
"reading a parameter set");
707 if (!videotoolbox_encoder_append_nal(enc,
set, setSize))
709 videotoolbox_encoder_fail(enc, kVTAllocationFailedErr,
710 "copying a parameter set");
716 if ((nalHeaderLength < 1) || (nalHeaderLength > 4))
718 videotoolbox_encoder_fail(enc, kVTParameterErr,
"the NAL length size");
722 CMBlockBufferRef block = CMSampleBufferGetDataBuffer(sample);
725 videotoolbox_encoder_fail(enc, kVTParameterErr,
"getting the frame data");
730 size_t contiguous = 0;
732 BYTE *copy =
nullptr;
733 OSStatus rc = CMBlockBufferGetDataPointer(block, 0, &contiguous, &total, &ptr);
734 if ((rc == noErr) && (contiguous < total))
737 copy = malloc(total);
739 rc = kVTAllocationFailedErr;
741 rc = CMBlockBufferCopyDataBytes(block, 0, total, copy);
746 videotoolbox_encoder_fail(enc, rc,
"reading the frame data");
751 const BYTE *data = (
const BYTE *)ptr;
752 const size_t header = (size_t)nalHeaderLength;
754 while (total - offset > header)
757 for (
size_t x = 0; x < header; x++)
758 nalSize = (nalSize << 8) | data[offset + x];
760 if (nalSize > total - offset)
762 videotoolbox_encoder_fail(enc, kVTParameterErr,
"a NAL unit longer than the frame");
765 if (!videotoolbox_encoder_append_nal(enc, &data[offset], nalSize))
767 videotoolbox_encoder_fail(enc, kVTAllocationFailedErr,
"copying a NAL unit");
775static void videotoolbox_encoder_destroy(H264_CONTEXT_VT_ENCODER *enc)
781 (void)VTCompressionSessionCompleteFrames(enc->session, kCMTimeInvalid);
782 VTCompressionSessionInvalidate(enc->session);
783 CFRelease(enc->session);
784 enc->session =
nullptr;
788static BOOL videotoolbox_encoder_set(H264_CONTEXT_VT_ENCODER *enc, CFStringRef key, CFTypeRef value,
796 const OSStatus rc = VTSessionSetProperty(enc->session, key, value);
799 char *str = osstatus2str(rc);
800 WLog_Print(enc->log, WLOG_DEBUG,
"VideoToolbox H.264 encoder: %s not set: %s", name, str);
807static BOOL videotoolbox_encoder_set_int(H264_CONTEXT_VT_ENCODER *enc, CFStringRef key,
808 int32_t value,
const char *name)
810 CFNumberRef number = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &value);
811 const BOOL rc = videotoolbox_encoder_set(enc, key, number, name);
817static BOOL videotoolbox_encoder_set_float(H264_CONTEXT_VT_ENCODER *enc, CFStringRef key,
818 float value,
const char *name)
820 CFNumberRef number = CFNumberCreate(kCFAllocatorDefault, kCFNumberFloat32Type, &value);
821 const BOOL rc = videotoolbox_encoder_set(enc, key, number, name);
828static BOOL videotoolbox_encoder_set_qp(H264_CONTEXT_VT_ENCODER *enc, UINT32 qp)
830 const int32_t value = (int32_t)MIN(qp, 51);
831 if (__builtin_available(macOS 13.0, iOS 16.0, *))
834 if (videotoolbox_encoder_set_int(enc, kVTCompressionPropertyKey_MinAllowedFrameQP, value,
835 "MinAllowedFrameQP") &&
836 videotoolbox_encoder_set_int(enc, kVTCompressionPropertyKey_MaxAllowedFrameQP, value,
837 "MaxAllowedFrameQP"))
841 return videotoolbox_encoder_set_float(enc, kVTCompressionPropertyKey_Quality,
842 1.0f - (1.0f * (
float)value / 51.0f),
"Quality");
846static BOOL videotoolbox_encoder_create(H264_CONTEXT *h264, H264_CONTEXT_VT_ENCODER *enc,
847 UINT32 width, UINT32 height, UINT32 frameRate,
854 CFMutableDictionaryRef source =
nullptr;
855 CFMutableDictionaryRef surface =
nullptr;
856 CFNumberRef number =
nullptr;
858 videotoolbox_encoder_destroy(enc);
859 if ((width > INT32_MAX) || (height > INT32_MAX) || (frameRate > INT32_MAX))
862 source = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks,
863 &kCFTypeDictionaryValueCallBacks);
864 surface = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks,
865 &kCFTypeDictionaryValueCallBacks);
866 const BOOL fullRange = videotoolbox_encoder_full_range(h264);
867 const int32_t pixelFormat = fullRange ? kCVPixelFormatType_420YpCbCr8BiPlanarFullRange
868 : kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange;
869 number = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &pixelFormat);
870 if (!source || !surface || !number)
872 CFDictionarySetValue(source, kCVPixelBufferPixelFormatTypeKey, number);
874 CFDictionarySetValue(source, kCVPixelBufferIOSurfacePropertiesKey, surface);
876 OSStatus status = VTCompressionSessionCreate(
877 kCFAllocatorDefault, (int32_t)width, (int32_t)height, kCMVideoCodecType_H264,
nullptr,
878 source, kCFAllocatorDefault, videotoolbox_encoder_output, enc, &enc->session);
881 char *str = osstatus2str(status);
882 WLog_Print(h264->log, WLOG_WARN,
883 "VideoToolbox H.264 encoder: VTCompressionSessionCreate %" PRIu32
"x%" PRIu32
887 enc->session =
nullptr;
893 (void)videotoolbox_encoder_set(enc, kVTCompressionPropertyKey_RealTime, kCFBooleanTrue,
895 if (!videotoolbox_encoder_set(enc, kVTCompressionPropertyKey_AllowFrameReordering,
896 kCFBooleanFalse,
"AllowFrameReordering"))
899 (void)videotoolbox_encoder_set(enc, kVTCompressionPropertyKey_ProfileLevel,
900 kVTProfileLevel_H264_Baseline_AutoLevel,
"ProfileLevel");
901 (void)videotoolbox_encoder_set_int(enc, kVTCompressionPropertyKey_MaxKeyFrameInterval, 30,
902 "MaxKeyFrameInterval");
903 (void)videotoolbox_encoder_set_int(enc, kVTCompressionPropertyKey_ExpectedFrameRate,
904 (int32_t)frameRate,
"ExpectedFrameRate");
906 switch (h264->RateControlMode)
908 case H264_RATECONTROL_CQP:
909 if (!videotoolbox_encoder_set_qp(enc, h264->QP))
912 case H264_RATECONTROL_VBR:
914 if (!videotoolbox_encoder_set_int(enc, kVTCompressionPropertyKey_AverageBitRate,
915 (int32_t)MIN(bitRate, INT32_MAX),
"AverageBitRate"))
920 status = VTCompressionSessionPrepareToEncodeFrames(enc->session);
923 char *str = osstatus2str(status);
924 WLog_Print(h264->log, WLOG_WARN,
925 "VideoToolbox H.264 encoder: VTCompressionSessionPrepareToEncodeFrames failed: "
934 BOOL hardware = FALSE;
935 if (__builtin_available(macOS 10.9, iOS 17.4, tvOS 17.4, *))
937 CFTypeRef usingHardware =
nullptr;
938 if ((VTSessionCopyProperty(enc->session,
939 kVTCompressionPropertyKey_UsingHardwareAcceleratedVideoEncoder,
940 kCFAllocatorDefault, (
void *)&usingHardware) == noErr) &&
943 hardware = (usingHardware == kCFBooleanTrue) ? TRUE : FALSE;
944 CFRelease(usingHardware);
949 enc->height = height;
950 enc->frameRate = frameRate;
951 enc->bitRate = bitRate;
953 enc->rateControl = h264->RateControlMode;
954 enc->fullRange = fullRange;
957 h264->log, WLOG_DEBUG,
958 "VideoToolbox H.264 encoder: %" PRIu32
"x%" PRIu32
" at %" PRIu32
" fps, %s, hardware %s",
959 width, height, frameRate,
960 (h264->RateControlMode == H264_RATECONTROL_CQP) ?
"constant QP" :
"variable bit rate",
961 hardware ?
"yes" :
"no");
972 videotoolbox_encoder_destroy(enc);
977static BOOL videotoolbox_encoder_update(H264_CONTEXT *h264, H264_CONTEXT_VT_ENCODER *enc,
978 UINT32 width, UINT32 height, UINT32 frameRate,
984 if (!enc->session || (enc->width != width) || (enc->height != height) ||
985 (enc->frameRate != frameRate) || (enc->rateControl != h264->RateControlMode) ||
986 (enc->fullRange != videotoolbox_encoder_full_range(h264)))
987 return videotoolbox_encoder_create(h264, enc, width, height, frameRate, bitRate);
990 if ((h264->RateControlMode != H264_RATECONTROL_CQP) && (enc->bitRate != bitRate))
992 if (!videotoolbox_encoder_set_int(enc, kVTCompressionPropertyKey_AverageBitRate,
993 (int32_t)MIN(bitRate, INT32_MAX),
"AverageBitRate"))
994 return videotoolbox_encoder_create(h264, enc, width, height, frameRate, bitRate);
995 enc->bitRate = bitRate;
997 if ((h264->RateControlMode == H264_RATECONTROL_CQP) && (enc->qp != h264->QP))
999 if (!videotoolbox_encoder_set_qp(enc, h264->QP))
1000 return videotoolbox_encoder_create(h264, enc, width, height, frameRate, bitRate);
1008static BOOL videotoolbox_encoder_copy(CVPixelBufferRef image,
const BYTE **WINPR_RESTRICT pSrcYuv,
1009 const UINT32 *WINPR_RESTRICT pStride, UINT32 width,
1012 if (CVPixelBufferLockBaseAddress(image, 0) != kCVReturnSuccess)
1016 BYTE *luma = CVPixelBufferGetBaseAddressOfPlane(image, 0);
1017 BYTE *chroma = CVPixelBufferGetBaseAddressOfPlane(image, 1);
1018 const size_t lumaStride = CVPixelBufferGetBytesPerRowOfPlane(image, 0);
1019 const size_t chromaStride = CVPixelBufferGetBytesPerRowOfPlane(image, 1);
1020 if (!luma || !chroma || (lumaStride < width) || (chromaStride < width) ||
1021 (CVPixelBufferGetHeightOfPlane(image, 0) < height) ||
1022 (CVPixelBufferGetHeightOfPlane(image, 1) < height / 2))
1025 for (UINT32 y = 0; y < height; y++)
1026 memcpy(&luma[y * lumaStride], &pSrcYuv[0][1ull * y * pStride[0]], width);
1027 for (UINT32 y = 0; y < height / 2; y++)
1029 const BYTE *u = &pSrcYuv[1][1ull * y * pStride[1]];
1030 const BYTE *v = &pSrcYuv[2][1ull * y * pStride[2]];
1031 BYTE *uv = &chroma[y * chromaStride];
1032 for (UINT32 x = 0; x < width / 2; x++)
1035 uv[2 * x + 1] = v[x];
1040 (void)CVPixelBufferUnlockBaseAddress(image, 0);
1045static int videotoolbox_compress(H264_CONTEXT *WINPR_RESTRICT h264,
1046 const BYTE **WINPR_RESTRICT pSrcYuv,
1047 const UINT32 *WINPR_RESTRICT pStride,
1048 BYTE **WINPR_RESTRICT ppDstData, UINT32 *WINPR_RESTRICT pDstSize)
1051 WINPR_ASSERT(pSrcYuv);
1052 WINPR_ASSERT(pStride);
1053 WINPR_ASSERT(ppDstData);
1054 WINPR_ASSERT(pDstSize);
1056 H264_CONTEXT_VT_ENCODER *enc = h264->pSystemData;
1057 if (!enc || !pSrcYuv[0] || !pSrcYuv[1] || !pSrcYuv[2])
1059 if ((h264->width == 0) || (h264->height == 0) || (h264->width > UINT16_MAX) ||
1060 (h264->height > UINT16_MAX))
1065 const UINT32 width = (h264->width + 1) & ~1u;
1066 const UINT32 height = (h264->height + 1) & ~1u;
1067 const UINT32 frameRate = (h264->FrameRate > 0) ? MIN(h264->FrameRate, 1000) : 30;
1068 const UINT32 bitRate = (h264->BitRate > 0) ? h264->BitRate : 1000000;
1070 if (!videotoolbox_encoder_update(h264, enc, width, height, frameRate, bitRate))
1073 CVPixelBufferRef image =
nullptr;
1074 CVPixelBufferPoolRef pool = VTCompressionSessionGetPixelBufferPool(enc->session);
1075 const OSType pixelFormat = enc->fullRange ? kCVPixelFormatType_420YpCbCr8BiPlanarFullRange
1076 : kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange;
1077 const CVReturn cv = pool ? CVPixelBufferPoolCreatePixelBuffer(kCFAllocatorDefault, pool, &image)
1078 : CVPixelBufferCreate(kCFAllocatorDefault, width, height, pixelFormat,
1080 if ((cv != kCVReturnSuccess) || !image)
1082 WLog_Print(h264->log, WLOG_ERROR,
"VideoToolbox H.264 encoder: no picture buffer: %d",
1086 if (!videotoolbox_encoder_copy(image, pSrcYuv, pStride, width, height))
1088 CVPixelBufferRelease(image);
1093 enc->failed = FALSE;
1094 enc->status = noErr;
1095 const CMTime pts = CMTimeMake(enc->frameIndex++, (int32_t)frameRate);
1096 const CMTime duration = CMTimeMake(1, (int32_t)frameRate);
1097 OSStatus status = VTCompressionSessionEncodeFrame(enc->session, image, pts, duration,
nullptr,
1099 CVPixelBufferRelease(image);
1100 if (status == noErr)
1102 status = VTCompressionSessionCompleteFrames(enc->session, pts);
1103 if ((status != noErr) || enc->failed)
1105 char *str1 = osstatus2str(status);
1106 char *str2 = osstatus2str(enc->status);
1107 WLog_Print(h264->log, WLOG_ERROR,
"VideoToolbox H.264 encoder: frame failed: %s / %s", str1,
1112 videotoolbox_encoder_destroy(enc);
1116 if ((enc->length == 0) || (enc->length > UINT32_MAX))
1119 *ppDstData = enc->data;
1120 *pDstSize = (UINT32)enc->length;
1124static void videotoolbox_encoder_uninit(H264_CONTEXT *h264)
1128 H264_CONTEXT_VT_ENCODER *enc = h264->pSystemData;
1131 videotoolbox_encoder_destroy(enc);
1134 h264->pSystemData =
nullptr;
1138static BOOL videotoolbox_encoder_init(H264_CONTEXT *h264)
1142 H264_CONTEXT_VT_ENCODER *enc = calloc(1,
sizeof(*enc));
1145 enc->log = h264->log;
1146 h264->pSystemData = enc;
1151 if (!videotoolbox_encoder_create(h264, enc, 64, 64, 30, 1000000))
1153 videotoolbox_encoder_uninit(h264);
1156 videotoolbox_encoder_destroy(enc);
1160static void videotoolbox_uninit(H264_CONTEXT *h264)
1164 if (h264->Compressor)
1166 videotoolbox_encoder_uninit(h264);
1170 H264_CONTEXT_VIDEOTOOLBOX *sys = h264->pSystemData;
1173 videotoolbox_release_image(h264);
1174 videotoolbox_destroy_session(h264);
1175 for (
size_t x = 0; x < ARRAYSIZE(sys->sps); x++)
1176 free(sys->sps[x].data);
1177 for (
size_t x = 0; x < ARRAYSIZE(sys->pps); x++)
1178 free(sys->pps[x].data);
1180 h264->pSystemData =
nullptr;
1184static BOOL videotoolbox_init(H264_CONTEXT *h264)
1188 if (h264->Compressor)
1189 return videotoolbox_encoder_init(h264);
1191 H264_CONTEXT_VIDEOTOOLBOX *sys = calloc(1,
sizeof(*sys));
1194 sys->needsIdr = TRUE;
1195 h264->pSystemData = sys;
1196 h264->hwAccel = FALSE;
1201const H264_CONTEXT_SUBSYSTEM g_Subsystem_VideoToolbox = {
"VideoToolbox", videotoolbox_init,
1202 videotoolbox_uninit,
1203 videotoolbox_decompress,
1204 videotoolbox_compress };