#include "h264_sei.h"
#include <dlfcn.h>
+#if !HAVE_KCMVIDEOCODECTYPE_HEVC
+enum { kCMVideoCodecType_HEVC = 'hvc1' };
+#endif
+
+typedef OSStatus (*getParameterSetAtIndex)(CMFormatDescriptionRef videoDesc,
+ size_t parameterSetIndex,
+ const uint8_t * _Nullable *parameterSetPointerOut,
+ size_t *parameterSetSizeOut,
+ size_t *parameterSetCountOut,
+ int *NALUnitHeaderLengthOut);
+
//These symbols may not be present
static struct{
CFStringRef kCVImageBufferColorPrimaries_ITU_R_2020;
CFStringRef kVTProfileLevel_H264_High_5_2;
CFStringRef kVTProfileLevel_H264_High_AutoLevel;
+ CFStringRef kVTProfileLevel_HEVC_Main_AutoLevel;
+ CFStringRef kVTProfileLevel_HEVC_Main10_AutoLevel;
+
CFStringRef kVTCompressionPropertyKey_RealTime;
CFStringRef kVTVideoEncoderSpecification_EnableHardwareAcceleratedVideoEncoder;
CFStringRef kVTVideoEncoderSpecification_RequireHardwareAcceleratedVideoEncoder;
+
+ getParameterSetAtIndex CMVideoFormatDescriptionGetHEVCParameterSetAtIndex;
} compat_keys;
#define GET_SYM(symbol, defaultVal) \
static pthread_once_t once_ctrl = PTHREAD_ONCE_INIT;
static void loadVTEncSymbols(){
+ compat_keys.CMVideoFormatDescriptionGetHEVCParameterSetAtIndex =
+ (getParameterSetAtIndex)dlsym(
+ RTLD_DEFAULT,
+ "CMVideoFormatDescriptionGetHEVCParameterSetAtIndex"
+ );
+
GET_SYM(kCVImageBufferColorPrimaries_ITU_R_2020, "ITU_R_2020");
GET_SYM(kCVImageBufferTransferFunction_ITU_R_2020, "ITU_R_2020");
GET_SYM(kCVImageBufferYCbCrMatrix_ITU_R_2020, "ITU_R_2020");
GET_SYM(kVTProfileLevel_H264_High_5_2, "H264_High_5_2");
GET_SYM(kVTProfileLevel_H264_High_AutoLevel, "H264_High_AutoLevel");
+ GET_SYM(kVTProfileLevel_HEVC_Main_AutoLevel, "HEVC_Main_AutoLevel");
+ GET_SYM(kVTProfileLevel_HEVC_Main10_AutoLevel, "HEVC_Main10_AutoLevel");
+
GET_SYM(kVTCompressionPropertyKey_RealTime, "RealTime");
GET_SYM(kVTVideoEncoderSpecification_EnableHardwareAcceleratedVideoEncoder,
VT_CABAC
} VTH264Entropy;
+typedef enum VT_HEVCProfile {
+ HEVC_PROF_AUTO,
+ HEVC_PROF_MAIN,
+ HEVC_PROF_MAIN10,
+ HEVC_PROF_COUNT
+} VT_HEVCProfile;
+
static const uint8_t start_code[] = { 0, 0, 0, 1 };
typedef struct ExtraSEI {
typedef struct VTEncContext {
AVClass *class;
+ enum AVCodecID codec_id;
VTCompressionSessionRef session;
CFStringRef ycbcr_matrix;
CFStringRef color_primaries;
CFStringRef transfer_function;
+ getParameterSetAtIndex get_param_set_func;
pthread_mutex_t lock;
pthread_cond_t cv_sample_sent;
{
switch (id) {
case AV_CODEC_ID_H264: return kCMVideoCodecType_H264;
+ case AV_CODEC_ID_HEVC: return kCMVideoCodecType_HEVC;
default: return 0;
}
}
CMVideoFormatDescriptionRef vid_fmt,
size_t *size)
{
+ VTEncContext *vtctx = avctx->priv_data;
size_t total_size = 0;
size_t ps_count;
int is_count_bad = 0;
size_t i;
int status;
- status = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(vid_fmt,
- 0,
- NULL,
- NULL,
- &ps_count,
- NULL);
+ status = vtctx->get_param_set_func(vid_fmt,
+ 0,
+ NULL,
+ NULL,
+ &ps_count,
+ NULL);
if (status) {
is_count_bad = 1;
ps_count = 0;
for (i = 0; i < ps_count || is_count_bad; i++) {
const uint8_t *ps;
size_t ps_size;
- status = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(vid_fmt,
- i,
- &ps,
- &ps_size,
- NULL,
- NULL);
+ status = vtctx->get_param_set_func(vid_fmt,
+ i,
+ &ps,
+ &ps_size,
+ NULL,
+ NULL);
if (status) {
/*
* When ps_count is invalid, status != 0 ends the loop normally
uint8_t *dst,
size_t dst_size)
{
+ VTEncContext *vtctx = avctx->priv_data;
size_t ps_count;
int is_count_bad = 0;
int status;
size_t offset = 0;
size_t i;
- status = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(vid_fmt,
- 0,
- NULL,
- NULL,
- &ps_count,
- NULL);
+ status = vtctx->get_param_set_func(vid_fmt,
+ 0,
+ NULL,
+ NULL,
+ &ps_count,
+ NULL);
if (status) {
is_count_bad = 1;
ps_count = 0;
size_t ps_size;
size_t next_offset;
- status = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(vid_fmt,
- i,
- &ps,
- &ps_size,
- NULL,
- NULL);
+ status = vtctx->get_param_set_func(vid_fmt,
+ i,
+ &ps,
+ &ps_size,
+ NULL,
+ NULL);
if (status) {
if (i > 0 && is_count_bad) status = 0;
CMSampleBufferRef sample_buffer,
size_t *size)
{
+ VTEncContext *vtctx = avctx->priv_data;
CMVideoFormatDescriptionRef vid_fmt;
int isize;
int status;
return AVERROR_EXTERNAL;
}
- status = CMVideoFormatDescriptionGetH264ParameterSetAtIndex(vid_fmt,
- 0,
- NULL,
- NULL,
- NULL,
- &isize);
+ status = vtctx->get_param_set_func(vid_fmt,
+ 0,
+ NULL,
+ NULL,
+ NULL,
+ &isize);
if (status) {
av_log(avctx, AV_LOG_ERROR, "Error getting length code size: %d\n", status);
return AVERROR_EXTERNAL;
* If profile_level_val is NULL and this method returns true, don't specify the
* profile/level to the encoder.
*/
-static bool get_vt_profile_level(AVCodecContext *avctx,
- CFStringRef *profile_level_val)
+static bool get_vt_h264_profile_level(AVCodecContext *avctx,
+ CFStringRef *profile_level_val)
{
VTEncContext *vtctx = avctx->priv_data;
int64_t profile = vtctx->profile;
return true;
}
+/*
+ * Returns true on success.
+ *
+ * If profile_level_val is NULL and this method returns true, don't specify the
+ * profile/level to the encoder.
+ */
+static bool get_vt_hevc_profile_level(AVCodecContext *avctx,
+ CFStringRef *profile_level_val)
+{
+ VTEncContext *vtctx = avctx->priv_data;
+ int64_t profile = vtctx->profile;
+
+ *profile_level_val = NULL;
+
+ switch (profile) {
+ case HEVC_PROF_AUTO:
+ return true;
+ case HEVC_PROF_MAIN:
+ *profile_level_val =
+ compat_keys.kVTProfileLevel_HEVC_Main_AutoLevel;
+ break;
+ case HEVC_PROF_MAIN10:
+ *profile_level_val =
+ compat_keys.kVTProfileLevel_HEVC_Main10_AutoLevel;
+ break;
+ }
+
+ if (!*profile_level_val) {
+ av_log(avctx, AV_LOG_ERROR, "Invalid Profile/Level.\n");
+ return false;
+ }
+
+ return true;
+}
+
static int get_cv_pixel_format(AVCodecContext* avctx,
enum AVPixelFormat fmt,
enum AVColorRange range,
return AVERROR_EXTERNAL;
}
- bytes_per_second_value = max_rate >> 3;
- bytes_per_second = CFNumberCreate(kCFAllocatorDefault,
- kCFNumberSInt64Type,
- &bytes_per_second_value);
- if (!bytes_per_second) {
- return AVERROR(ENOMEM);
- }
- one_second_value = 1;
- one_second = CFNumberCreate(kCFAllocatorDefault,
- kCFNumberSInt64Type,
- &one_second_value);
- if (!one_second) {
- CFRelease(bytes_per_second);
- return AVERROR(ENOMEM);
- }
- nums[0] = bytes_per_second;
- nums[1] = one_second;
- data_rate_limits = CFArrayCreate(kCFAllocatorDefault,
- nums,
- 2,
- &kCFTypeArrayCallBacks);
+ if (vtctx->codec_id == AV_CODEC_ID_H264) {
+ // kVTCompressionPropertyKey_DataRateLimits is not available for HEVC
+ bytes_per_second_value = max_rate >> 3;
+ bytes_per_second = CFNumberCreate(kCFAllocatorDefault,
+ kCFNumberSInt64Type,
+ &bytes_per_second_value);
+ if (!bytes_per_second) {
+ return AVERROR(ENOMEM);
+ }
+ one_second_value = 1;
+ one_second = CFNumberCreate(kCFAllocatorDefault,
+ kCFNumberSInt64Type,
+ &one_second_value);
+ if (!one_second) {
+ CFRelease(bytes_per_second);
+ return AVERROR(ENOMEM);
+ }
+ nums[0] = (void *)bytes_per_second;
+ nums[1] = (void *)one_second;
+ data_rate_limits = CFArrayCreate(kCFAllocatorDefault,
+ (const void **)nums,
+ 2,
+ &kCFTypeArrayCallBacks);
+
+ if (!data_rate_limits) {
+ CFRelease(bytes_per_second);
+ CFRelease(one_second);
+ return AVERROR(ENOMEM);
+ }
+ status = VTSessionSetProperty(vtctx->session,
+ kVTCompressionPropertyKey_DataRateLimits,
+ data_rate_limits);
- if (!data_rate_limits) {
CFRelease(bytes_per_second);
CFRelease(one_second);
- return AVERROR(ENOMEM);
- }
- status = VTSessionSetProperty(vtctx->session,
- kVTCompressionPropertyKey_DataRateLimits,
- data_rate_limits);
-
- CFRelease(bytes_per_second);
- CFRelease(one_second);
- CFRelease(data_rate_limits);
-
- if (status) {
- av_log(avctx, AV_LOG_ERROR, "Error setting max bitrate property: %d\n", status);
- return AVERROR_EXTERNAL;
- }
+ CFRelease(data_rate_limits);
- if (profile_level) {
- status = VTSessionSetProperty(vtctx->session,
- kVTCompressionPropertyKey_ProfileLevel,
- profile_level);
if (status) {
- av_log(avctx, AV_LOG_ERROR, "Error setting profile/level property: %d\n", status);
+ av_log(avctx, AV_LOG_ERROR, "Error setting max bitrate property: %d\n", status);
+ return AVERROR_EXTERNAL;
+ }
+
+ if (profile_level) {
+ status = VTSessionSetProperty(vtctx->session,
+ kVTCompressionPropertyKey_ProfileLevel,
+ profile_level);
+ if (status) {
+ av_log(avctx, AV_LOG_ERROR, "Error setting profile/level property: %d\n", status);
+ }
}
}
return AVERROR(EINVAL);
}
- vtctx->has_b_frames = avctx->max_b_frames > 0;
- if(vtctx->has_b_frames && vtctx->profile == H264_PROF_BASELINE){
- av_log(avctx, AV_LOG_WARNING, "Cannot use B-frames with baseline profile. Output will not contain B-frames.\n");
- vtctx->has_b_frames = false;
- }
+ vtctx->codec_id = avctx->codec_id;
- if (vtctx->entropy == VT_CABAC && vtctx->profile == H264_PROF_BASELINE) {
- av_log(avctx, AV_LOG_WARNING, "CABAC entropy requires 'main' or 'high' profile, but baseline was requested. Encode will not use CABAC entropy.\n");
- vtctx->entropy = VT_ENTROPY_NOT_SET;
- }
+ if (vtctx->codec_id == AV_CODEC_ID_H264) {
+ vtctx->get_param_set_func = CMVideoFormatDescriptionGetH264ParameterSetAtIndex;
- if (!get_vt_profile_level(avctx, &profile_level)) return AVERROR(EINVAL);
+ vtctx->has_b_frames = avctx->max_b_frames > 0;
+ if(vtctx->has_b_frames && vtctx->profile == H264_PROF_BASELINE){
+ av_log(avctx, AV_LOG_WARNING, "Cannot use B-frames with baseline profile. Output will not contain B-frames.\n");
+ vtctx->has_b_frames = false;
+ }
+
+ if (vtctx->entropy == VT_CABAC && vtctx->profile == H264_PROF_BASELINE) {
+ av_log(avctx, AV_LOG_WARNING, "CABAC entropy requires 'main' or 'high' profile, but baseline was requested. Encode will not use CABAC entropy.\n");
+ vtctx->entropy = VT_ENTROPY_NOT_SET;
+ }
+
+ if (!get_vt_h264_profile_level(avctx, &profile_level)) return AVERROR(EINVAL);
+ } else {
+ vtctx->get_param_set_func = compat_keys.CMVideoFormatDescriptionGetHEVCParameterSetAtIndex;
+ if (!vtctx->get_param_set_func) return AVERROR(EINVAL);
+ if (!get_vt_hevc_profile_level(avctx, &profile_level)) return AVERROR(EINVAL);
+ }
vtctx->session = NULL;
"Color range not set for %s. Using MPEG range.\n",
av_get_pix_fmt_name(av_format));
}
-
- av_log(avctx, AV_LOG_WARNING, "");
}
switch (av_format) {
AV_PIX_FMT_NONE
};
-#define OFFSET(x) offsetof(VTEncContext, x)
#define VE AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_ENCODING_PARAM
-static const AVOption options[] = {
+#define COMMON_OPTIONS \
+ { "allow_sw", "Allow software encoding", OFFSET(allow_sw), AV_OPT_TYPE_BOOL, \
+ { .i64 = 0 }, 0, 1, VE }, \
+ { "realtime", "Hint that encoding should happen in real-time if not faster (e.g. capturing from camera).", \
+ OFFSET(realtime), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, VE }, \
+ { "frames_before", "Other frames will come before the frames in this session. This helps smooth concatenation issues.", \
+ OFFSET(frames_before), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, VE }, \
+ { "frames_after", "Other frames will come after the frames in this session. This helps smooth concatenation issues.", \
+ OFFSET(frames_after), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, VE },
+
+#define OFFSET(x) offsetof(VTEncContext, x)
+static const AVOption h264_options[] = {
{ "profile", "Profile", OFFSET(profile), AV_OPT_TYPE_INT, { .i64 = H264_PROF_AUTO }, H264_PROF_AUTO, H264_PROF_COUNT, VE, "profile" },
{ "baseline", "Baseline Profile", 0, AV_OPT_TYPE_CONST, { .i64 = H264_PROF_BASELINE }, INT_MIN, INT_MAX, VE, "profile" },
{ "main", "Main Profile", 0, AV_OPT_TYPE_CONST, { .i64 = H264_PROF_MAIN }, INT_MIN, INT_MAX, VE, "profile" },
{ "5.1", "Level 5.1", 0, AV_OPT_TYPE_CONST, { .i64 = 51 }, INT_MIN, INT_MAX, VE, "level" },
{ "5.2", "Level 5.2", 0, AV_OPT_TYPE_CONST, { .i64 = 52 }, INT_MIN, INT_MAX, VE, "level" },
- { "allow_sw", "Allow software encoding", OFFSET(allow_sw), AV_OPT_TYPE_BOOL,
- { .i64 = 0 }, 0, 1, VE },
-
{ "coder", "Entropy coding", OFFSET(entropy), AV_OPT_TYPE_INT, { .i64 = VT_ENTROPY_NOT_SET }, VT_ENTROPY_NOT_SET, VT_CABAC, VE, "coder" },
{ "cavlc", "CAVLC entropy coding", 0, AV_OPT_TYPE_CONST, { .i64 = VT_CAVLC }, INT_MIN, INT_MAX, VE, "coder" },
{ "vlc", "CAVLC entropy coding", 0, AV_OPT_TYPE_CONST, { .i64 = VT_CAVLC }, INT_MIN, INT_MAX, VE, "coder" },
{ "cabac", "CABAC entropy coding", 0, AV_OPT_TYPE_CONST, { .i64 = VT_CABAC }, INT_MIN, INT_MAX, VE, "coder" },
{ "ac", "CABAC entropy coding", 0, AV_OPT_TYPE_CONST, { .i64 = VT_CABAC }, INT_MIN, INT_MAX, VE, "coder" },
- { "realtime", "Hint that encoding should happen in real-time if not faster (e.g. capturing from camera).",
- OFFSET(realtime), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, VE },
-
- { "frames_before", "Other frames will come before the frames in this session. This helps smooth concatenation issues.",
- OFFSET(frames_before), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, VE },
- { "frames_after", "Other frames will come after the frames in this session. This helps smooth concatenation issues.",
- OFFSET(frames_after), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, VE },
-
{ "a53cc", "Use A53 Closed Captions (if available)", OFFSET(a53_cc), AV_OPT_TYPE_BOOL, {.i64 = 1}, 0, 1, VE },
+ COMMON_OPTIONS
{ NULL },
};
static const AVClass h264_videotoolbox_class = {
.class_name = "h264_videotoolbox",
.item_name = av_default_item_name,
- .option = options,
+ .option = h264_options,
.version = LIBAVUTIL_VERSION_INT,
};
.caps_internal = FF_CODEC_CAP_INIT_THREADSAFE |
FF_CODEC_CAP_INIT_CLEANUP,
};
+
+static const AVOption hevc_options[] = {
+ { "profile", "Profile", OFFSET(profile), AV_OPT_TYPE_INT, { .i64 = HEVC_PROF_AUTO }, HEVC_PROF_AUTO, HEVC_PROF_COUNT, VE, "profile" },
+ { "main", "Main Profile", 0, AV_OPT_TYPE_CONST, { .i64 = HEVC_PROF_MAIN }, INT_MIN, INT_MAX, VE, "profile" },
+ { "main10", "Main10 Profile", 0, AV_OPT_TYPE_CONST, { .i64 = HEVC_PROF_MAIN10 }, INT_MIN, INT_MAX, VE, "profile" },
+
+ COMMON_OPTIONS
+ { NULL },
+};
+
+static const AVClass hevc_videotoolbox_class = {
+ .class_name = "hevc_videotoolbox",
+ .item_name = av_default_item_name,
+ .option = hevc_options,
+ .version = LIBAVUTIL_VERSION_INT,
+};
+
+AVCodec ff_hevc_videotoolbox_encoder = {
+ .name = "hevc_videotoolbox",
+ .long_name = NULL_IF_CONFIG_SMALL("VideoToolbox H.265 Encoder"),
+ .type = AVMEDIA_TYPE_VIDEO,
+ .id = AV_CODEC_ID_HEVC,
+ .priv_data_size = sizeof(VTEncContext),
+ .pix_fmts = pix_fmts,
+ .init = vtenc_init,
+ .encode2 = vtenc_frame,
+ .close = vtenc_close,
+ .capabilities = AV_CODEC_CAP_DELAY | AV_CODEC_CAP_HARDWARE,
+ .priv_class = &hevc_videotoolbox_class,
+ .caps_internal = FF_CODEC_CAP_INIT_THREADSAFE |
+ FF_CODEC_CAP_INIT_CLEANUP,
+ .wrapper_name = "videotoolbox",
+};