mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-19 14:56:36 +00:00
applemedia: Add HEVC support to vtenc and vtdec
Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/2754>
This commit is contained in:
parent
1dd29a2564
commit
6bf15124e7
2 changed files with 129 additions and 21 deletions
|
@ -105,6 +105,8 @@ static void gst_vtdec_session_output_callback (void
|
||||||
CMTime duration);
|
CMTime duration);
|
||||||
static gboolean compute_h264_decode_picture_buffer_length (GstVtdec * vtdec,
|
static gboolean compute_h264_decode_picture_buffer_length (GstVtdec * vtdec,
|
||||||
GstBuffer * codec_data, int *length);
|
GstBuffer * codec_data, int *length);
|
||||||
|
static gboolean compute_hevc_decode_picture_buffer_length (GstVtdec * vtdec,
|
||||||
|
GstBuffer * codec_data, int *length);
|
||||||
static gboolean gst_vtdec_compute_reorder_queue_length (GstVtdec * vtdec,
|
static gboolean gst_vtdec_compute_reorder_queue_length (GstVtdec * vtdec,
|
||||||
CMVideoCodecType cm_format, GstBuffer * codec_data);
|
CMVideoCodecType cm_format, GstBuffer * codec_data);
|
||||||
static void gst_vtdec_set_latency (GstVtdec * vtdec);
|
static void gst_vtdec_set_latency (GstVtdec * vtdec);
|
||||||
|
@ -115,6 +117,8 @@ static GstStaticPadTemplate gst_vtdec_sink_template =
|
||||||
GST_PAD_SINK,
|
GST_PAD_SINK,
|
||||||
GST_PAD_ALWAYS,
|
GST_PAD_ALWAYS,
|
||||||
GST_STATIC_CAPS ("video/x-h264, stream-format=avc, alignment=au,"
|
GST_STATIC_CAPS ("video/x-h264, stream-format=avc, alignment=au,"
|
||||||
|
" width=(int)[1, MAX], height=(int)[1, MAX];"
|
||||||
|
"video/x-h265, stream-format=(string){ hev1, hvc1 }, alignment=au,"
|
||||||
" width=(int)[1, MAX], height=(int)[1, MAX];"
|
" width=(int)[1, MAX], height=(int)[1, MAX];"
|
||||||
"video/mpeg, mpegversion=2, systemstream=false, parsed=true;"
|
"video/mpeg, mpegversion=2, systemstream=false, parsed=true;"
|
||||||
"image/jpeg;"
|
"image/jpeg;"
|
||||||
|
@ -535,6 +539,8 @@ gst_vtdec_set_format (GstVideoDecoder * decoder, GstVideoCodecState * state)
|
||||||
caps_name = gst_structure_get_name (structure);
|
caps_name = gst_structure_get_name (structure);
|
||||||
if (!strcmp (caps_name, "video/x-h264")) {
|
if (!strcmp (caps_name, "video/x-h264")) {
|
||||||
cm_format = kCMVideoCodecType_H264;
|
cm_format = kCMVideoCodecType_H264;
|
||||||
|
} else if (!strcmp (caps_name, "video/x-h265")) {
|
||||||
|
cm_format = kCMVideoCodecType_HEVC;
|
||||||
} else if (!strcmp (caps_name, "video/mpeg")) {
|
} else if (!strcmp (caps_name, "video/mpeg")) {
|
||||||
cm_format = kCMVideoCodecType_MPEG2Video;
|
cm_format = kCMVideoCodecType_MPEG2Video;
|
||||||
} else if (!strcmp (caps_name, "image/jpeg")) {
|
} else if (!strcmp (caps_name, "image/jpeg")) {
|
||||||
|
@ -551,7 +557,9 @@ gst_vtdec_set_format (GstVideoDecoder * decoder, GstVideoCodecState * state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cm_format == kCMVideoCodecType_H264 && state->codec_data == NULL) {
|
if ((cm_format == kCMVideoCodecType_H264
|
||||||
|
|| cm_format == kCMVideoCodecType_HEVC)
|
||||||
|
&& state->codec_data == NULL) {
|
||||||
GST_INFO_OBJECT (vtdec, "no codec data, wait for one");
|
GST_INFO_OBJECT (vtdec, "no codec data, wait for one");
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
@ -788,7 +796,12 @@ create_format_description_from_codec_data (GstVtdec * vtdec,
|
||||||
gst_buffer_map (codec_data, &map, GST_MAP_READ);
|
gst_buffer_map (codec_data, &map, GST_MAP_READ);
|
||||||
atoms = CFDictionaryCreateMutable (NULL, 0, &kCFTypeDictionaryKeyCallBacks,
|
atoms = CFDictionaryCreateMutable (NULL, 0, &kCFTypeDictionaryKeyCallBacks,
|
||||||
&kCFTypeDictionaryValueCallBacks);
|
&kCFTypeDictionaryValueCallBacks);
|
||||||
|
|
||||||
|
if (cm_format == kCMVideoCodecType_HEVC)
|
||||||
|
gst_vtutil_dict_set_data (atoms, CFSTR ("hvcC"), map.data, map.size);
|
||||||
|
else
|
||||||
gst_vtutil_dict_set_data (atoms, CFSTR ("avcC"), map.data, map.size);
|
gst_vtutil_dict_set_data (atoms, CFSTR ("avcC"), map.data, map.size);
|
||||||
|
|
||||||
gst_vtutil_dict_set_object (extensions,
|
gst_vtutil_dict_set_object (extensions,
|
||||||
CFSTR ("SampleDescriptionExtensionAtoms"), (CFTypeRef *) atoms);
|
CFSTR ("SampleDescriptionExtensionAtoms"), (CFTypeRef *) atoms);
|
||||||
gst_buffer_unmap (codec_data, &map);
|
gst_buffer_unmap (codec_data, &map);
|
||||||
|
@ -1148,6 +1161,11 @@ gst_vtdec_compute_reorder_queue_length (GstVtdec * vtdec,
|
||||||
&vtdec->reorder_queue_length)) {
|
&vtdec->reorder_queue_length)) {
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
} else if (cm_format == kCMVideoCodecType_HEVC) {
|
||||||
|
if (!compute_hevc_decode_picture_buffer_length (vtdec, codec_data,
|
||||||
|
&vtdec->reorder_queue_length)) {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
vtdec->reorder_queue_length = 0;
|
vtdec->reorder_queue_length = 0;
|
||||||
}
|
}
|
||||||
|
@ -1195,6 +1213,34 @@ compute_h264_decode_picture_buffer_length (GstVtdec * vtdec,
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
compute_hevc_decode_picture_buffer_length (GstVtdec * vtdec,
|
||||||
|
GstBuffer * codec_data, int *length)
|
||||||
|
{
|
||||||
|
/* This value should be level dependent (table A.8)
|
||||||
|
* but let's assume the maximum possible one for simplicity. */
|
||||||
|
const gint max_luma_ps = 35651584;
|
||||||
|
const gint max_dpb_pic_buf = 6;
|
||||||
|
gint max_dbp_size, pic_size_samples_y;
|
||||||
|
|
||||||
|
if (vtdec->video_info.width == 0 || vtdec->video_info.height == 0)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
/* A.4.2 */
|
||||||
|
pic_size_samples_y = vtdec->video_info.width * vtdec->video_info.height;
|
||||||
|
if (pic_size_samples_y <= (max_luma_ps >> 2))
|
||||||
|
max_dbp_size = max_dpb_pic_buf * 4;
|
||||||
|
else if (pic_size_samples_y <= (max_luma_ps >> 1))
|
||||||
|
max_dbp_size = max_dpb_pic_buf * 2;
|
||||||
|
else if (pic_size_samples_y <= ((3 * max_luma_ps) >> 2))
|
||||||
|
max_dbp_size = (max_dpb_pic_buf * 4) / 3;
|
||||||
|
else
|
||||||
|
max_dbp_size = max_dpb_pic_buf;
|
||||||
|
|
||||||
|
*length = MIN (max_dbp_size, 16);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
gst_vtdec_set_latency (GstVtdec * vtdec)
|
gst_vtdec_set_latency (GstVtdec * vtdec)
|
||||||
{
|
{
|
||||||
|
|
|
@ -71,7 +71,6 @@
|
||||||
#include "vtutil.h"
|
#include "vtutil.h"
|
||||||
#include <gst/pbutils/codec-utils.h>
|
#include <gst/pbutils/codec-utils.h>
|
||||||
|
|
||||||
#define VTENC_DEFAULT_USAGE 6 /* Profile: Baseline Level: 2.1 */
|
|
||||||
#define VTENC_DEFAULT_BITRATE 0
|
#define VTENC_DEFAULT_BITRATE 0
|
||||||
#define VTENC_DEFAULT_FRAME_REORDERING TRUE
|
#define VTENC_DEFAULT_FRAME_REORDERING TRUE
|
||||||
#define VTENC_DEFAULT_REALTIME FALSE
|
#define VTENC_DEFAULT_REALTIME FALSE
|
||||||
|
@ -239,7 +238,6 @@ gst_vtenc_base_init (GstVTEncClass * klass)
|
||||||
gst_pad_template_new ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, caps));
|
gst_pad_template_new ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, caps));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
src_caps = gst_caps_new_simple (codec_details->mimetype,
|
src_caps = gst_caps_new_simple (codec_details->mimetype,
|
||||||
"width", GST_TYPE_INT_RANGE, min_width, max_width,
|
"width", GST_TYPE_INT_RANGE, min_width, max_width,
|
||||||
"height", GST_TYPE_INT_RANGE, min_height, max_height,
|
"height", GST_TYPE_INT_RANGE, min_height, max_height,
|
||||||
|
@ -268,6 +266,11 @@ gst_vtenc_base_init (GstVTEncClass * klass)
|
||||||
"stream-format", G_TYPE_STRING, "avc",
|
"stream-format", G_TYPE_STRING, "avc",
|
||||||
"alignment", G_TYPE_STRING, "au", NULL);
|
"alignment", G_TYPE_STRING, "au", NULL);
|
||||||
break;
|
break;
|
||||||
|
case kCMVideoCodecType_HEVC:
|
||||||
|
gst_structure_set (gst_caps_get_structure (src_caps, 0),
|
||||||
|
"stream-format", G_TYPE_STRING, "hvc1",
|
||||||
|
"alignment", G_TYPE_STRING, "au", NULL);
|
||||||
|
break;
|
||||||
case GST_kCMVideoCodecType_Some_AppleProRes:
|
case GST_kCMVideoCodecType_Some_AppleProRes:
|
||||||
if (g_strcmp0 (codec_details->mimetype, "video/x-prores") == 0) {
|
if (g_strcmp0 (codec_details->mimetype, "video/x-prores") == 0) {
|
||||||
G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
|
G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
|
||||||
|
@ -720,7 +723,7 @@ gst_vtenc_stop (GstVideoEncoder * enc)
|
||||||
}
|
}
|
||||||
|
|
||||||
static CFStringRef
|
static CFStringRef
|
||||||
gst_vtenc_profile_level_key (GstVTEnc * self, const gchar * profile,
|
gst_vtenc_h264_profile_level_key (GstVTEnc * self, const gchar * profile,
|
||||||
const gchar * level_arg)
|
const gchar * level_arg)
|
||||||
{
|
{
|
||||||
char level[64];
|
char level[64];
|
||||||
|
@ -763,6 +766,37 @@ gst_vtenc_profile_level_key (GstVTEnc * self, const gchar * profile,
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static CFStringRef
|
||||||
|
gst_vtenc_hevc_profile_level_key (GstVTEnc * self, const gchar * profile,
|
||||||
|
const gchar * level_arg)
|
||||||
|
{
|
||||||
|
gchar *key = NULL;
|
||||||
|
CFStringRef ret = NULL;
|
||||||
|
|
||||||
|
if (profile == NULL || !strcmp (profile, "main"))
|
||||||
|
profile = "Main";
|
||||||
|
else if (!strcmp (profile, "main-10"))
|
||||||
|
profile = "Main10";
|
||||||
|
else if (!strcmp (profile, "main-422-10"))
|
||||||
|
/* TODO: this should probably be guarded with a version check (macOS 12.3+ / iOS 15.4+)
|
||||||
|
* https://developer.apple.com/documentation/videotoolbox/kvtprofilelevel_hevc_main10_autolevel */
|
||||||
|
profile = "Main42210";
|
||||||
|
else {
|
||||||
|
GST_ERROR_OBJECT (self, "invalid profile: %s", profile);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* VT does not support specific levels for HEVC */
|
||||||
|
key = g_strdup_printf ("HEVC_%s_AutoLevel", profile);
|
||||||
|
ret = CFStringCreateWithBytes (NULL, (const guint8 *) key, strlen (key),
|
||||||
|
kCFStringEncodingASCII, 0);
|
||||||
|
|
||||||
|
GST_INFO_OBJECT (self, "negotiated profile and level %s", key);
|
||||||
|
|
||||||
|
g_free (key);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
static gboolean
|
static gboolean
|
||||||
gst_vtenc_negotiate_profile_and_level (GstVTEnc * self, GstStructure * s)
|
gst_vtenc_negotiate_profile_and_level (GstVTEnc * self, GstStructure * s)
|
||||||
{
|
{
|
||||||
|
@ -771,9 +805,16 @@ gst_vtenc_negotiate_profile_and_level (GstVTEnc * self, GstStructure * s)
|
||||||
|
|
||||||
if (self->profile_level)
|
if (self->profile_level)
|
||||||
CFRelease (self->profile_level);
|
CFRelease (self->profile_level);
|
||||||
self->profile_level = gst_vtenc_profile_level_key (self, profile, level);
|
|
||||||
|
if (self->specific_format_id == kCMVideoCodecType_HEVC)
|
||||||
|
self->profile_level =
|
||||||
|
gst_vtenc_hevc_profile_level_key (self, profile, level);
|
||||||
|
else
|
||||||
|
self->profile_level =
|
||||||
|
gst_vtenc_h264_profile_level_key (self, profile, level);
|
||||||
|
|
||||||
if (self->profile_level == NULL) {
|
if (self->profile_level == NULL) {
|
||||||
GST_ERROR_OBJECT (self, "unsupported h264 profile '%s' or level '%s'",
|
GST_ERROR_OBJECT (self, "unsupported profile '%s' or level '%s'",
|
||||||
profile, level);
|
profile, level);
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
@ -822,6 +863,11 @@ gst_vtenc_negotiate_specific_format_details (GstVideoEncoder * enc)
|
||||||
if (!gst_vtenc_negotiate_profile_and_level (self, s))
|
if (!gst_vtenc_negotiate_profile_and_level (self, s))
|
||||||
goto fail;
|
goto fail;
|
||||||
break;
|
break;
|
||||||
|
case kCMVideoCodecType_HEVC:
|
||||||
|
self->specific_format_id = kCMVideoCodecType_HEVC;
|
||||||
|
if (!gst_vtenc_negotiate_profile_and_level (self, s))
|
||||||
|
goto fail;
|
||||||
|
break;
|
||||||
case GST_kCMVideoCodecType_Some_AppleProRes:
|
case GST_kCMVideoCodecType_Some_AppleProRes:
|
||||||
if (g_strcmp0 (self->details->mimetype, "video/x-prores") != 0) {
|
if (g_strcmp0 (self->details->mimetype, "video/x-prores") != 0) {
|
||||||
GST_ERROR_OBJECT (self, "format_id == %i mimetype must be Apple "
|
GST_ERROR_OBJECT (self, "format_id == %i mimetype must be Apple "
|
||||||
|
@ -931,35 +977,48 @@ gst_vtenc_negotiate_downstream (GstVTEnc * self, CMSampleBufferRef sbuf)
|
||||||
|
|
||||||
switch (self->details->format_id) {
|
switch (self->details->format_id) {
|
||||||
case kCMVideoCodecType_H264:
|
case kCMVideoCodecType_H264:
|
||||||
|
case kCMVideoCodecType_HEVC:
|
||||||
{
|
{
|
||||||
CMFormatDescriptionRef fmt;
|
CMFormatDescriptionRef fmt;
|
||||||
CFDictionaryRef atoms;
|
CFDictionaryRef atoms;
|
||||||
CFStringRef avccKey;
|
CFStringRef boxKey;
|
||||||
CFDataRef avcc;
|
CFDataRef box;
|
||||||
guint8 *codec_data;
|
guint8 *codec_data;
|
||||||
gsize codec_data_size;
|
gsize codec_data_size;
|
||||||
GstBuffer *codec_data_buf;
|
GstBuffer *codec_data_buf;
|
||||||
guint8 sps[3];
|
guint8 sps[12];
|
||||||
|
|
||||||
fmt = CMSampleBufferGetFormatDescription (sbuf);
|
fmt = CMSampleBufferGetFormatDescription (sbuf);
|
||||||
atoms = CMFormatDescriptionGetExtension (fmt,
|
atoms = CMFormatDescriptionGetExtension (fmt,
|
||||||
kCMFormatDescriptionExtension_SampleDescriptionExtensionAtoms);
|
kCMFormatDescriptionExtension_SampleDescriptionExtensionAtoms);
|
||||||
avccKey = CFStringCreateWithCString (NULL, "avcC", kCFStringEncodingUTF8);
|
|
||||||
avcc = CFDictionaryGetValue (atoms, avccKey);
|
if (self->details->format_id == kCMVideoCodecType_HEVC)
|
||||||
CFRelease (avccKey);
|
boxKey =
|
||||||
codec_data_size = CFDataGetLength (avcc);
|
CFStringCreateWithCString (NULL, "hvcC", kCFStringEncodingUTF8);
|
||||||
|
else
|
||||||
|
boxKey =
|
||||||
|
CFStringCreateWithCString (NULL, "avcC", kCFStringEncodingUTF8);
|
||||||
|
|
||||||
|
box = CFDictionaryGetValue (atoms, boxKey);
|
||||||
|
CFRelease (boxKey);
|
||||||
|
codec_data_size = CFDataGetLength (box);
|
||||||
codec_data = g_malloc (codec_data_size);
|
codec_data = g_malloc (codec_data_size);
|
||||||
CFDataGetBytes (avcc, CFRangeMake (0, codec_data_size), codec_data);
|
CFDataGetBytes (box, CFRangeMake (0, codec_data_size), codec_data);
|
||||||
codec_data_buf = gst_buffer_new_wrapped (codec_data, codec_data_size);
|
codec_data_buf = gst_buffer_new_wrapped (codec_data, codec_data_size);
|
||||||
|
|
||||||
gst_structure_set (s, "codec_data", GST_TYPE_BUFFER, codec_data_buf,
|
gst_structure_set (s, "codec_data", GST_TYPE_BUFFER, codec_data_buf,
|
||||||
NULL);
|
NULL);
|
||||||
|
|
||||||
|
if (self->details->format_id == kCMVideoCodecType_HEVC) {
|
||||||
|
sps[0] = codec_data[1];
|
||||||
|
sps[11] = codec_data[12];
|
||||||
|
gst_codec_utils_h265_caps_set_level_tier_and_profile (caps, sps, 12);
|
||||||
|
} else {
|
||||||
sps[0] = codec_data[1];
|
sps[0] = codec_data[1];
|
||||||
sps[1] = codec_data[2] & ~0xDF;
|
sps[1] = codec_data[2] & ~0xDF;
|
||||||
sps[2] = codec_data[3];
|
sps[2] = codec_data[3];
|
||||||
|
|
||||||
gst_codec_utils_h264_caps_set_level_and_profile (caps, sps, 3);
|
gst_codec_utils_h264_caps_set_level_and_profile (caps, sps, 3);
|
||||||
|
}
|
||||||
|
|
||||||
gst_buffer_unref (codec_data_buf);
|
gst_buffer_unref (codec_data_buf);
|
||||||
}
|
}
|
||||||
|
@ -1189,7 +1248,7 @@ gst_vtenc_create_session (GstVTEnc * self)
|
||||||
(gdouble) self->negotiated_fps_n / (gdouble) self->negotiated_fps_d);
|
(gdouble) self->negotiated_fps_n / (gdouble) self->negotiated_fps_d);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* https://developer.apple.com/documentation/videotoolbox/vtcompressionsession/compression_properties/profile_and_level_constants
|
* https://developer.apple.com/documentation/videotoolbox/kvtcompressionpropertykey_profilelevel
|
||||||
*/
|
*/
|
||||||
status = VTSessionSetProperty (session,
|
status = VTSessionSetProperty (session,
|
||||||
kVTCompressionPropertyKey_ProfileLevel, self->profile_level);
|
kVTCompressionPropertyKey_ProfileLevel, self->profile_level);
|
||||||
|
@ -1885,8 +1944,11 @@ gst_vtenc_register (GstPlugin * plugin,
|
||||||
|
|
||||||
static const GstVTEncoderDetails gst_vtenc_codecs[] = {
|
static const GstVTEncoderDetails gst_vtenc_codecs[] = {
|
||||||
{"H.264", "h264", "video/x-h264", kCMVideoCodecType_H264, FALSE},
|
{"H.264", "h264", "video/x-h264", kCMVideoCodecType_H264, FALSE},
|
||||||
|
{"H.265/HEVC", "h265", "video/x-h265", kCMVideoCodecType_HEVC, FALSE},
|
||||||
#ifndef HAVE_IOS
|
#ifndef HAVE_IOS
|
||||||
{"H.264 (HW only)", "h264_hw", "video/x-h264", kCMVideoCodecType_H264, TRUE},
|
{"H.264 (HW only)", "h264_hw", "video/x-h264", kCMVideoCodecType_H264, TRUE},
|
||||||
|
{"H.265/HEVC (HW only)", "h265_hw", "video/x-h265", kCMVideoCodecType_HEVC,
|
||||||
|
TRUE},
|
||||||
#endif
|
#endif
|
||||||
{"Apple ProRes", "prores", "video/x-prores",
|
{"Apple ProRes", "prores", "video/x-prores",
|
||||||
GST_kCMVideoCodecType_Some_AppleProRes, FALSE},
|
GST_kCMVideoCodecType_Some_AppleProRes, FALSE},
|
||||||
|
|
Loading…
Reference in a new issue