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;
74static BOOL videotoolbox_read_bit(H264_VT_BITS* bits, UINT32* value)
78 WINPR_ASSERT(bits->data);
82 if (bits->offset >= bits->size)
84 UINT32
byte = bits->data[bits->offset++];
85 if ((bits->zeros == 2) && (
byte == 3))
87 if ((bits->offset >= bits->size) || (bits->data[bits->offset] > 3))
89 byte = bits->data[bits->offset++];
92 bits->zeros = (
byte == 0) ? MIN(bits->zeros + 1, 2) : 0;
96 *value = (bits->byte >> --bits->bits) & 1;
101static BOOL videotoolbox_read_ue(H264_VT_BITS* bits, UINT32* value)
110 if (!videotoolbox_read_bit(bits, &bit))
118 for (UINT32 x = 0; x < zeros; x++)
120 if (!videotoolbox_read_bit(bits, &bit))
122 result = (result << 1) | bit;
129static size_t videotoolbox_start_code(
const BYTE* data,
size_t size,
size_t* offset)
132 WINPR_ASSERT(offset);
134 for (; (*offset < size) && ((size - *offset) >= 3); (*offset)++)
136 const BYTE* p = &data[*offset];
137 if ((p[0] != 0) || (p[1] != 0))
141 if (((size - *offset) >= 4) && (p[2] == 0) && (p[3] == 1))
149static BOOL videotoolbox_next_nal(
const BYTE* data,
size_t size,
size_t* offset,
const BYTE** nal,
153 WINPR_ASSERT(offset);
155 WINPR_ASSERT(length);
157 const size_t previous = *offset;
158 const size_t prefix = videotoolbox_start_code(data, size, offset);
162 for (
size_t x = previous; x < *offset; x++)
168 const size_t start = *offset;
169 (void)videotoolbox_start_code(data, size, offset);
170 size_t end = *offset;
171 while ((end > start) && (data[end - 1] == 0))
173 if ((end == start) || ((data[start] & 0x80) != 0))
176 *length = end - start;
181static BOOL videotoolbox_save_parameter(H264_CONTEXT_VIDEOTOOLBOX* sys,
const BYTE* nal,
187 H264_VT_BITS bits = { .data = nal + 1, .size = length - 1 };
190 H264_VT_PARAMETER_SET* parameter =
nullptr;
191 if ((nal[0] & 0x1F) == 7)
194 for (UINT32 x = 0; x < 24; x++)
196 if (!videotoolbox_read_bit(&bits, &
id))
199 if (!videotoolbox_read_ue(&bits, &
id) || (
id >= ARRAYSIZE(sys->sps)))
201 parameter = &sys->sps[id];
205 if (!videotoolbox_read_ue(&bits, &
id) || (
id >= ARRAYSIZE(sys->pps)) ||
206 !videotoolbox_read_ue(&bits, &spsId) || (spsId >= ARRAYSIZE(sys->sps)))
208 parameter = &sys->pps[id];
210 if ((parameter->size == length) && (memcmp(parameter->data, nal, length) == 0))
212 BYTE* copy = malloc(length);
215 memcpy(copy, nal, length);
216 free(parameter->data);
217 parameter->data = copy;
218 parameter->size = length;
219 parameter->spsId = spsId;
220 sys->parametersChanged = TRUE;
224static void videotoolbox_release_image(H264_CONTEXT* h264)
228 H264_CONTEXT_VIDEOTOOLBOX* sys = h264->pSystemData;
233 if (sys->imageLocked)
234 CVPixelBufferUnlockBaseAddress(sys->image, kCVPixelBufferLock_ReadOnly);
235 CVPixelBufferRelease(sys->image);
236 sys->image =
nullptr;
238 sys->imageLocked = FALSE;
239 for (
size_t x = 0; x < 3; x++)
241 h264->pYUVData[x] =
nullptr;
242 h264->iStride[x] = 0;
248static void videotoolbox_destroy_session(H264_CONTEXT* h264)
252 H264_CONTEXT_VIDEOTOOLBOX* sys = h264->pSystemData;
257 VTDecompressionSessionInvalidate(sys->session);
258 CFRelease(sys->session);
259 sys->session =
nullptr;
263 CFRelease(sys->format);
264 sys->format =
nullptr;
266 sys->needsIdr = TRUE;
267 h264->hwAccel = FALSE;
270static void videotoolbox_output(WINPR_ATTR_UNUSED
void* context,
void* sourceFrame, OSStatus status,
271 WINPR_ATTR_UNUSED VTDecodeInfoFlags flags, CVImageBufferRef image,
272 WINPR_ATTR_UNUSED CMTime pts, WINPR_ATTR_UNUSED CMTime duration)
274 WINPR_ASSERT(sourceFrame);
276 H264_VT_OUTPUT* output = sourceFrame;
278 output->status = status;
282 CVPixelBufferRelease(output->image);
283 output->image = CVPixelBufferRetain(image);
288static BOOL videotoolbox_ensure_session(H264_CONTEXT* h264, UINT32 ppsId)
292 H264_CONTEXT_VIDEOTOOLBOX* sys = h264->pSystemData;
295 if (sys->session && !sys->parametersChanged && (sys->ppsId == ppsId))
297 const H264_VT_PARAMETER_SET* pps = &sys->pps[ppsId];
298 const H264_VT_PARAMETER_SET* sps = &sys->sps[pps->spsId];
299 if (!pps->data || !sps->data)
301 const BYTE* parameters[] = { sps->data, pps->data };
302 const size_t lengths[] = { sps->size, pps->size };
303 CMVideoFormatDescriptionRef format =
nullptr;
304 OSStatus status = CMVideoFormatDescriptionCreateFromH264ParameterSets(
305 kCFAllocatorDefault, ARRAYSIZE(parameters), parameters, lengths, 4, &format);
308 WLog_Print(h264->log, WLOG_WARN,
"VideoToolbox H.264 format creation failed: %d", status);
313 const BOOL fullRange =
314 CMFormatDescriptionGetExtension(format, kCMFormatDescriptionExtension_FullRangeVideo) ==
316 const OSType pixelFormat = fullRange ? kCVPixelFormatType_420YpCbCr8PlanarFullRange
317 : kCVPixelFormatType_420YpCbCr8Planar;
318 if (sys->session && (sys->pixelFormat == pixelFormat) &&
319 VTDecompressionSessionCanAcceptFormatDescription(sys->session, format))
321 CFRelease(sys->format);
322 sys->format = format;
323 sys->parametersChanged = FALSE;
328 videotoolbox_destroy_session(h264);
329 CFNumberRef pixel = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &pixelFormat);
335 const void* keys[] = { kCVPixelBufferPixelFormatTypeKey };
336 const void* values[] = { pixel };
337 CFDictionaryRef attributes =
338 CFDictionaryCreate(kCFAllocatorDefault, keys, values, ARRAYSIZE(keys),
339 &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
346 const VTDecompressionOutputCallbackRecord callback = { videotoolbox_output,
nullptr };
349 status = VTDecompressionSessionCreate(kCFAllocatorDefault, format,
nullptr, attributes,
350 &callback, &sys->session);
351 CFRelease(attributes);
354 WLog_Print(h264->log, WLOG_WARN,
"VideoToolbox H.264 session creation failed: %d", status);
358 sys->format = format;
359 sys->pixelFormat = pixelFormat;
361 sys->parametersChanged = FALSE;
363 (void)VTSessionSetProperty(sys->session, kVTDecompressionPropertyKey_RealTime, kCFBooleanTrue);
364 if (__builtin_available(macOS 10.9, iOS 17.0, tvOS 17.0, *))
366 CFTypeRef hardware =
nullptr;
367 status = VTSessionCopyProperty(
368 sys->session, kVTDecompressionPropertyKey_UsingHardwareAcceleratedVideoDecoder,
369 kCFAllocatorDefault, &hardware);
372 h264->hwAccel = (hardware == kCFBooleanTrue);
373 WLog_Print(h264->log, WLOG_DEBUG,
"VideoToolbox H.264 decoder: %s",
374 h264->hwAccel ?
"hardware" :
"software");
383static BOOL videotoolbox_map_image(H264_CONTEXT* h264)
387 H264_CONTEXT_VIDEOTOOLBOX* sys = h264->pSystemData;
390 CVPixelBufferRef image = sys->image;
393 const size_t width = CVPixelBufferGetWidth(image);
394 const size_t height = CVPixelBufferGetHeight(image);
395 if ((width == 0) || (height == 0) || (width > UINT32_MAX) || (height > UINT32_MAX) ||
396 (CVPixelBufferGetPixelFormatType(image) != sys->pixelFormat) ||
397 (CVPixelBufferGetPlaneCount(image) != 3))
399 if (CVPixelBufferLockBaseAddress(image, kCVPixelBufferLock_ReadOnly) != kCVReturnSuccess)
401 sys->imageLocked = TRUE;
402 for (
size_t x = 0; x < 3; x++)
404 const size_t stride = CVPixelBufferGetBytesPerRowOfPlane(image, x);
405 const size_t planeWidth = (x == 0) ? width : (width + 1) / 2;
406 const size_t planeHeight = (x == 0) ? height : (height + 1) / 2;
407 BYTE* data = CVPixelBufferGetBaseAddressOfPlane(image, x);
408 if (!data || (stride < planeWidth) || (stride > UINT32_MAX) ||
409 (CVPixelBufferGetWidthOfPlane(image, x) < planeWidth) ||
410 (CVPixelBufferGetHeightOfPlane(image, x) < planeHeight))
412 h264->pYUVData[x] = data;
413 h264->iStride[x] = (UINT32)stride;
415 h264->YUVWidth = (UINT32)width;
416 h264->YUVHeight = (UINT32)height;
421static int videotoolbox_decompress(H264_CONTEXT* WINPR_RESTRICT h264,
422 const BYTE* WINPR_RESTRICT data, UINT32 size)
425 WINPR_ASSERT(data || (size == 0));
427 H264_CONTEXT_VIDEOTOOLBOX* sys = h264->pSystemData;
430 CMBlockBufferRef block =
nullptr;
431 CMSampleBufferRef sample =
nullptr;
432 H264_VT_OUTPUT output = { 0 };
434 size_t sampleSize = 0;
436 UINT32 ppsId = UINT32_MAX;
438 videotoolbox_release_image(h264);
441 while (offset < size)
443 const BYTE* nal =
nullptr;
445 if (!videotoolbox_next_nal(data, size, &offset, &nal, &length))
447 const BYTE type = nal[0] & 0x1F;
448 if ((type == 7) || (type == 8))
450 if (!videotoolbox_save_parameter(sys, nal, length))
454 if ((type == 1) || (type == 5))
456 H264_VT_BITS bits = { .data = nal + 1, .size = length - 1 };
459 if (!videotoolbox_read_ue(&bits, &value) || !videotoolbox_read_ue(&bits, &value) ||
460 !videotoolbox_read_ue(&bits, &slicePps) || (slicePps >= ARRAYSIZE(sys->pps)))
462 if ((ppsId != UINT32_MAX) && ((ppsId != slicePps) || (idr != (type == 5))))
467 else if ((type == 9) || (type == 10) || (type == 11) || (type == 12))
471 if ((length > UINT32_MAX - 4) || (sampleSize > UINT32_MAX - length - 4))
473 sampleSize += length + 4;
476 if (ppsId == UINT32_MAX)
478 if (!videotoolbox_ensure_session(h264, ppsId))
480 if (sys->needsIdr && !idr)
482 WLog_Print(h264->log, WLOG_WARN,
"VideoToolbox H.264 decoder requires an IDR picture");
485 OSStatus status = CMBlockBufferCreateWithMemoryBlock(
486 kCFAllocatorDefault,
nullptr, sampleSize, kCFAllocatorDefault,
nullptr, 0, sampleSize,
487 kCMBlockBufferAssureMemoryNowFlag, &block);
490 char* bytes =
nullptr;
491 status = CMBlockBufferGetDataPointer(block, 0,
nullptr,
nullptr, &bytes);
492 if ((status != noErr) || !bytes)
496 while (offset < size)
498 const BYTE* nal =
nullptr;
500 if (!videotoolbox_next_nal(data, size, &offset, &nal, &length))
502 const BYTE type = nal[0] & 0x1F;
503 if ((type != 1) && (type != 5) && (type != 6))
505 bytes[written++] = (char)((length >> 24) & 0xFF);
506 bytes[written++] = (char)((length >> 16) & 0xFF);
507 bytes[written++] = (char)((length >> 8) & 0xFF);
508 bytes[written++] = (char)(length & 0xFF);
509 memcpy(&bytes[written], nal, length);
512 WINPR_ASSERT(written == sampleSize);
513 status = CMSampleBufferCreateReady(kCFAllocatorDefault, block, sys->format, 1, 0,
nullptr, 1,
514 &sampleSize, &sample);
520 status = VTDecompressionSessionDecodeFrame(sys->session, sample, 0, &output,
nullptr);
521 if ((status != noErr) || (output.status != noErr) || (output.count != 1) || !output.image)
523 WLog_Print(h264->log, WLOG_WARN,
524 "VideoToolbox H.264 decode failed: %d / %d, outputs=%" PRIu32, status,
525 output.status, output.count);
528 sys->image = output.image;
529 output.image =
nullptr;
530 if (!videotoolbox_map_image(h264))
532 sys->needsIdr = FALSE;
536 CVPixelBufferRelease(output.image);
545 videotoolbox_release_image(h264);
546 videotoolbox_destroy_session(h264);
551static void videotoolbox_uninit(H264_CONTEXT* h264)
555 H264_CONTEXT_VIDEOTOOLBOX* sys = h264->pSystemData;
558 videotoolbox_release_image(h264);
559 videotoolbox_destroy_session(h264);
560 for (
size_t x = 0; x < ARRAYSIZE(sys->sps); x++)
561 free(sys->sps[x].data);
562 for (
size_t x = 0; x < ARRAYSIZE(sys->pps); x++)
563 free(sys->pps[x].data);
565 h264->pSystemData =
nullptr;
569static BOOL videotoolbox_init(H264_CONTEXT* h264)
574 if (h264->Compressor)
576 H264_CONTEXT_VIDEOTOOLBOX* sys = calloc(1,
sizeof(*sys));
579 sys->needsIdr = TRUE;
580 h264->pSystemData = sys;
581 h264->hwAccel = FALSE;
586const H264_CONTEXT_SUBSYSTEM g_Subsystem_VideoToolbox = {
"VideoToolbox", videotoolbox_init,
588 videotoolbox_decompress,
nullptr };