v4l2videoenc: Move the profile/level negotation in the base class

This removes duplicated code across different codec.
This commit is contained in:
Nicolas Dufresne 2017-08-02 10:39:46 -04:00
parent b0fb95b956
commit 783ac9a9f9
6 changed files with 258 additions and 749 deletions

View file

@ -267,222 +267,9 @@ v4l2_level_to_string (gint v4l2_level)
return NULL; return NULL;
} }
struct ProfileLevelCtx
{
GstV4l2H264Enc *self;
const gchar *profile;
const gchar *level;
};
static gboolean
get_string_list (GstStructure * s, const gchar * field, GQueue * queue)
{
const GValue *value;
value = gst_structure_get_value (s, field);
if (!value)
return FALSE;
if (GST_VALUE_HOLDS_LIST (value)) {
guint i;
if (gst_value_list_get_size (value) == 0)
return FALSE;
for (i = 0; i < gst_value_list_get_size (value); i++) {
const GValue *item = gst_value_list_get_value (value, i);
if (G_VALUE_HOLDS_STRING (item))
g_queue_push_tail (queue, g_value_dup_string (item));
}
} else if (G_VALUE_HOLDS_STRING (value)) {
g_queue_push_tail (queue, g_value_dup_string (value));
}
return TRUE;
}
static gboolean
negotiate_profile_and_level (GstCapsFeatures * features, GstStructure * s,
gpointer user_data)
{
struct ProfileLevelCtx *ctx = user_data;
GstV4l2Object *v4l2object = GST_V4L2_VIDEO_ENC (ctx->self)->v4l2output;
GQueue profiles = G_QUEUE_INIT;
GQueue levels = G_QUEUE_INIT;
gboolean failed = FALSE;
if (get_string_list (s, "profile", &profiles)) {
GList *l;
for (l = profiles.head; l; l = l->next) {
struct v4l2_control control = { 0, };
gint v4l2_profile;
const gchar *profile = l->data;
GST_TRACE_OBJECT (ctx->self, "Trying profile %s", profile);
control.id = V4L2_CID_MPEG_VIDEO_H264_PROFILE;
control.value = v4l2_profile = v4l2_profile_from_string (profile);
if (control.value < 0)
continue;
if (v4l2object->ioctl (v4l2object->video_fd, VIDIOC_S_CTRL, &control) < 0) {
GST_WARNING_OBJECT (ctx->self, "Failed to set H264 profile: '%s'",
g_strerror (errno));
break;
}
profile = v4l2_profile_to_string (control.value);
if (control.value == v4l2_profile) {
ctx->profile = profile;
break;
}
if (g_list_find_custom (l, profile, g_str_equal)) {
ctx->profile = profile;
break;
}
}
if (profiles.length && !ctx->profile)
failed = TRUE;
g_queue_foreach (&profiles, (GFunc) g_free, NULL);
g_queue_clear (&profiles);
}
if (!failed && get_string_list (s, "level", &levels)) {
GList *l;
for (l = levels.head; l; l = l->next) {
struct v4l2_control control = { 0, };
gint v4l2_level;
const gchar *level = l->data;
GST_TRACE_OBJECT (ctx->self, "Trying level %s", level);
control.id = V4L2_CID_MPEG_VIDEO_H264_LEVEL;
control.value = v4l2_level = v4l2_level_from_string (level);
if (control.value < 0)
continue;
if (v4l2object->ioctl (v4l2object->video_fd, VIDIOC_S_CTRL, &control) < 0) {
GST_WARNING_OBJECT (ctx->self, "Failed to set H264 level: '%s'",
g_strerror (errno));
break;
}
level = v4l2_level_to_string (control.value);
if (control.value == v4l2_level) {
ctx->level = level;
break;
}
if (g_list_find_custom (l, level, g_str_equal)) {
ctx->level = level;
break;
}
}
if (levels.length && !ctx->level)
failed = TRUE;
g_queue_foreach (&levels, (GFunc) g_free, NULL);
g_queue_clear (&levels);
}
/* If it failed, we continue */
return failed;
}
static gboolean
gst_v4l2_h264_enc_negotiate (GstVideoEncoder * encoder)
{
GstV4l2H264Enc *self = GST_V4L2_H264_ENC (encoder);
GstV4l2VideoEnc *venc = GST_V4L2_VIDEO_ENC (encoder);
GstV4l2Object *v4l2object = venc->v4l2output;
GstCaps *allowed_caps;
struct ProfileLevelCtx ctx = { self, NULL, NULL };
GstVideoCodecState *state;
GstStructure *s;
GST_DEBUG_OBJECT (self, "Negotiating H264 profile and level.");
allowed_caps = gst_pad_get_allowed_caps (GST_VIDEO_ENCODER_SRC_PAD (encoder));
if (allowed_caps) {
if (gst_caps_is_empty (allowed_caps))
goto not_negotiated;
allowed_caps = gst_caps_make_writable (allowed_caps);
/* negotiate_profile_and_level() will return TRUE on failure to keep
* iterating, if gst_caps_foreach() returns TRUE it means there was no
* compatible profile and level in any of the structure */
if (gst_caps_foreach (allowed_caps, negotiate_profile_and_level, &ctx)) {
goto no_profile_level;
}
}
if (!ctx.profile) {
struct v4l2_control control = { 0, };
control.id = V4L2_CID_MPEG_VIDEO_H264_PROFILE;
if (v4l2object->ioctl (v4l2object->video_fd, VIDIOC_G_CTRL, &control) < 0)
goto g_ctrl_failed;
ctx.profile = v4l2_profile_to_string (control.value);
}
if (!ctx.level) {
struct v4l2_control control = { 0, };
control.id = V4L2_CID_MPEG_VIDEO_H264_LEVEL;
if (v4l2object->ioctl (v4l2object->video_fd, VIDIOC_G_CTRL, &control) < 0)
goto g_ctrl_failed;
ctx.level = v4l2_level_to_string (control.value);
}
GST_DEBUG_OBJECT (self, "Selected H264 profile %s at level %s",
ctx.profile, ctx.level);
state = gst_video_encoder_get_output_state (encoder);
s = gst_caps_get_structure (state->caps, 0);
gst_structure_set (s, "profile", G_TYPE_STRING, ctx.profile,
"level", G_TYPE_STRING, ctx.level, NULL);
return GST_VIDEO_ENCODER_CLASS (parent_class)->negotiate (encoder);
g_ctrl_failed:
GST_WARNING_OBJECT (self, "Failed to get H264 profile and level: '%s'",
g_strerror (errno));
goto not_negotiated;
no_profile_level:
GST_WARNING_OBJECT (self, "No compatible level and profiled in caps: %"
GST_PTR_FORMAT, allowed_caps);
goto not_negotiated;
not_negotiated:
if (allowed_caps)
gst_caps_unref (allowed_caps);
return FALSE;
}
static void static void
gst_v4l2_h264_enc_init (GstV4l2H264Enc * self) gst_v4l2_h264_enc_init (GstV4l2H264Enc * self)
{ {
} }
static void static void
@ -490,13 +277,13 @@ gst_v4l2_h264_enc_class_init (GstV4l2H264EncClass * klass)
{ {
GstElementClass *element_class; GstElementClass *element_class;
GObjectClass *gobject_class; GObjectClass *gobject_class;
GstVideoEncoderClass *baseclass; GstV4l2VideoEncClass *baseclass;
parent_class = g_type_class_peek_parent (klass); parent_class = g_type_class_peek_parent (klass);
element_class = (GstElementClass *) klass; element_class = (GstElementClass *) klass;
gobject_class = (GObjectClass *) klass; gobject_class = (GObjectClass *) klass;
baseclass = GST_VIDEO_ENCODER_CLASS (klass); baseclass = (GstV4l2VideoEncClass *) (klass);
GST_DEBUG_CATEGORY_INIT (gst_v4l2_h264_enc_debug, "v4l2h264enc", 0, GST_DEBUG_CATEGORY_INIT (gst_v4l2_h264_enc_debug, "v4l2h264enc", 0,
"V4L2 H.264 Encoder"); "V4L2 H.264 Encoder");
@ -510,7 +297,14 @@ gst_v4l2_h264_enc_class_init (GstV4l2H264EncClass * klass)
GST_DEBUG_FUNCPTR (gst_v4l2_h264_enc_set_property); GST_DEBUG_FUNCPTR (gst_v4l2_h264_enc_set_property);
gobject_class->get_property = gobject_class->get_property =
GST_DEBUG_FUNCPTR (gst_v4l2_h264_enc_get_property); GST_DEBUG_FUNCPTR (gst_v4l2_h264_enc_get_property);
baseclass->negotiate = GST_DEBUG_FUNCPTR (gst_v4l2_h264_enc_negotiate);
baseclass->codec_name = "H264";
baseclass->profile_cid = V4L2_CID_MPEG_VIDEO_H264_PROFILE;
baseclass->profile_to_string = v4l2_profile_to_string;
baseclass->profile_from_string = v4l2_profile_from_string;
baseclass->level_cid = V4L2_CID_MPEG_VIDEO_H264_LEVEL;
baseclass->level_to_string = v4l2_level_to_string;
baseclass->level_from_string = v4l2_level_from_string;
} }
/* Probing functions */ /* Probing functions */

View file

@ -163,218 +163,6 @@ v4l2_level_to_string (gint v4l2_level)
return NULL; return NULL;
} }
struct ProfileLevelCtx
{
GstV4l2Mpeg4Enc *self;
const gchar *profile;
const gchar *level;
};
static gboolean
get_string_list (GstStructure * s, const gchar * field, GQueue * queue)
{
const GValue *value;
value = gst_structure_get_value (s, field);
if (!value)
return FALSE;
if (GST_VALUE_HOLDS_LIST (value)) {
guint i;
if (gst_value_list_get_size (value) == 0)
return FALSE;
for (i = 0; i < gst_value_list_get_size (value); i++) {
const GValue *item = gst_value_list_get_value (value, i);
if (G_VALUE_HOLDS_STRING (item))
g_queue_push_tail (queue, g_value_dup_string (item));
}
} else if (G_VALUE_HOLDS_STRING (value)) {
g_queue_push_tail (queue, g_value_dup_string (value));
}
return TRUE;
}
static gboolean
negotiate_profile_and_level (GstCapsFeatures * features, GstStructure * s,
gpointer user_data)
{
struct ProfileLevelCtx *ctx = user_data;
GstV4l2Object *v4l2object = GST_V4L2_VIDEO_ENC (ctx->self)->v4l2output;
GQueue profiles = G_QUEUE_INIT;
GQueue levels = G_QUEUE_INIT;
gboolean failed = FALSE;
if (get_string_list (s, "profile", &profiles)) {
GList *l;
for (l = profiles.head; l; l = l->next) {
struct v4l2_control control = { 0, };
gint v4l2_profile;
const gchar *profile = l->data;
GST_TRACE_OBJECT (ctx->self, "Trying profile %s", profile);
control.id = V4L2_CID_MPEG_VIDEO_MPEG4_PROFILE;
control.value = v4l2_profile = v4l2_profile_from_string (profile);
if (control.value < 0)
continue;
if (v4l2object->ioctl (v4l2object->video_fd, VIDIOC_S_CTRL, &control) < 0) {
GST_WARNING_OBJECT (ctx->self, "Failed to set MPEG4 profile: '%s'",
g_strerror (errno));
break;
}
profile = v4l2_profile_to_string (control.value);
if (control.value == v4l2_profile) {
ctx->profile = profile;
break;
}
if (g_list_find_custom (l, profile, g_str_equal)) {
ctx->profile = profile;
break;
}
}
if (profiles.length && !ctx->profile)
failed = TRUE;
g_queue_foreach (&profiles, (GFunc) g_free, NULL);
g_queue_clear (&profiles);
}
if (!failed && get_string_list (s, "level", &levels)) {
GList *l;
for (l = levels.head; l; l = l->next) {
struct v4l2_control control = { 0, };
gint v4l2_level;
const gchar *level = l->data;
GST_TRACE_OBJECT (ctx->self, "Trying level %s", level);
control.id = V4L2_CID_MPEG_VIDEO_MPEG4_LEVEL;
control.value = v4l2_level = v4l2_level_from_string (level);
if (control.value < 0)
continue;
if (v4l2object->ioctl (v4l2object->video_fd, VIDIOC_S_CTRL, &control) < 0) {
GST_WARNING_OBJECT (ctx->self, "Failed to set MPEG4 level: '%s'",
g_strerror (errno));
break;
}
level = v4l2_level_to_string (control.value);
if (control.value == v4l2_level) {
ctx->level = level;
break;
}
if (g_list_find_custom (l, level, g_str_equal)) {
ctx->level = level;
break;
}
}
if (levels.length && !ctx->level)
failed = TRUE;
g_queue_foreach (&levels, (GFunc) g_free, NULL);
g_queue_clear (&levels);
}
/* If it failed, we continue */
return failed;
}
static gboolean
gst_v4l2_mpeg4_enc_negotiate (GstVideoEncoder * encoder)
{
GstV4l2Mpeg4Enc *self = GST_V4L2_MPEG4_ENC (encoder);
GstV4l2VideoEnc *venc = GST_V4L2_VIDEO_ENC (encoder);
GstV4l2Object *v4l2object = venc->v4l2output;
GstCaps *allowed_caps;
struct ProfileLevelCtx ctx = { self, NULL, NULL };
GstVideoCodecState *state;
GstStructure *s;
GST_DEBUG_OBJECT (self, "Negotiating MPEG4 profile and level.");
allowed_caps = gst_pad_get_allowed_caps (GST_VIDEO_ENCODER_SRC_PAD (encoder));
if (allowed_caps) {
if (gst_caps_is_empty (allowed_caps))
goto not_negotiated;
allowed_caps = gst_caps_make_writable (allowed_caps);
/* negotiate_profile_and_level() will return TRUE on failure to keep
* iterating, if gst_caps_foreach() returns TRUE it means there was no
* compatible profile and level in any of the structure */
if (gst_caps_foreach (allowed_caps, negotiate_profile_and_level, &ctx)) {
goto no_profile_level;
}
}
if (!ctx.profile) {
struct v4l2_control control = { 0, };
control.id = V4L2_CID_MPEG_VIDEO_MPEG4_PROFILE;
if (v4l2object->ioctl (v4l2object->video_fd, VIDIOC_G_CTRL, &control) < 0)
goto g_ctrl_failed;
ctx.profile = v4l2_profile_to_string (control.value);
}
if (!ctx.level) {
struct v4l2_control control = { 0, };
control.id = V4L2_CID_MPEG_VIDEO_MPEG4_LEVEL;
if (v4l2object->ioctl (v4l2object->video_fd, VIDIOC_G_CTRL, &control) < 0)
goto g_ctrl_failed;
ctx.level = v4l2_level_to_string (control.value);
}
GST_DEBUG_OBJECT (self, "Selected MPEG4 profile %s at level %s",
ctx.profile, ctx.level);
state = gst_video_encoder_get_output_state (encoder);
s = gst_caps_get_structure (state->caps, 0);
gst_structure_set (s, "profile", G_TYPE_STRING, ctx.profile,
"level", G_TYPE_STRING, ctx.level, NULL);
return GST_VIDEO_ENCODER_CLASS (parent_class)->negotiate (encoder);
g_ctrl_failed:
GST_WARNING_OBJECT (self, "Failed to get MPEG4 profile and level: '%s'",
g_strerror (errno));
goto not_negotiated;
no_profile_level:
GST_WARNING_OBJECT (self, "No compatible level and profiled in caps: %"
GST_PTR_FORMAT, allowed_caps);
goto not_negotiated;
not_negotiated:
if (allowed_caps)
gst_caps_unref (allowed_caps);
return FALSE;
}
static void static void
gst_v4l2_mpeg4_enc_init (GstV4l2Mpeg4Enc * self) gst_v4l2_mpeg4_enc_init (GstV4l2Mpeg4Enc * self)
{ {
@ -385,13 +173,13 @@ gst_v4l2_mpeg4_enc_class_init (GstV4l2Mpeg4EncClass * klass)
{ {
GstElementClass *element_class; GstElementClass *element_class;
GObjectClass *gobject_class; GObjectClass *gobject_class;
GstVideoEncoderClass *baseclass; GstV4l2VideoEncClass *baseclass;
parent_class = g_type_class_peek_parent (klass); parent_class = g_type_class_peek_parent (klass);
element_class = (GstElementClass *) klass; element_class = (GstElementClass *) klass;
gobject_class = (GObjectClass *) klass; gobject_class = (GObjectClass *) klass;
baseclass = GST_VIDEO_ENCODER_CLASS (klass); baseclass = (GstV4l2VideoEncClass *) (klass);
GST_DEBUG_CATEGORY_INIT (gst_v4l2_mpeg4_enc_debug, "v4l2mpeg4enc", 0, GST_DEBUG_CATEGORY_INIT (gst_v4l2_mpeg4_enc_debug, "v4l2mpeg4enc", 0,
"V4L2 MPEG4 Encoder"); "V4L2 MPEG4 Encoder");
@ -406,7 +194,14 @@ gst_v4l2_mpeg4_enc_class_init (GstV4l2Mpeg4EncClass * klass)
GST_DEBUG_FUNCPTR (gst_v4l2_mpeg4_enc_set_property); GST_DEBUG_FUNCPTR (gst_v4l2_mpeg4_enc_set_property);
gobject_class->get_property = gobject_class->get_property =
GST_DEBUG_FUNCPTR (gst_v4l2_mpeg4_enc_get_property); GST_DEBUG_FUNCPTR (gst_v4l2_mpeg4_enc_get_property);
baseclass->negotiate = GST_DEBUG_FUNCPTR (gst_v4l2_mpeg4_enc_negotiate);
baseclass->codec_name = "MPEG4";
baseclass->profile_cid = V4L2_CID_MPEG_VIDEO_MPEG4_PROFILE;
baseclass->profile_to_string = v4l2_profile_to_string;
baseclass->profile_from_string = v4l2_profile_from_string;
baseclass->level_cid = V4L2_CID_MPEG_VIDEO_MPEG4_LEVEL;
baseclass->level_to_string = v4l2_level_to_string;
baseclass->level_from_string = v4l2_level_from_string;
} }
/* Probing functions */ /* Probing functions */

View file

@ -373,10 +373,205 @@ gst_v4l2_video_enc_flush (GstVideoEncoder * encoder)
return TRUE; return TRUE;
} }
struct ProfileLevelCtx
{
GstV4l2VideoEnc *self;
const gchar *profile;
const gchar *level;
};
static gboolean
get_string_list (GstStructure * s, const gchar * field, GQueue * queue)
{
const GValue *value;
value = gst_structure_get_value (s, field);
if (!value)
return FALSE;
if (GST_VALUE_HOLDS_LIST (value)) {
guint i;
if (gst_value_list_get_size (value) == 0)
return FALSE;
for (i = 0; i < gst_value_list_get_size (value); i++) {
const GValue *item = gst_value_list_get_value (value, i);
if (G_VALUE_HOLDS_STRING (item))
g_queue_push_tail (queue, g_value_dup_string (item));
}
} else if (G_VALUE_HOLDS_STRING (value)) {
g_queue_push_tail (queue, g_value_dup_string (value));
}
return TRUE;
}
static gboolean
negotiate_profile_and_level (GstCapsFeatures * features, GstStructure * s,
gpointer user_data)
{
struct ProfileLevelCtx *ctx = user_data;
GstV4l2VideoEncClass *klass = GST_V4L2_VIDEO_ENC_GET_CLASS (ctx->self);
GstV4l2Object *v4l2object = GST_V4L2_VIDEO_ENC (ctx->self)->v4l2output;
GQueue profiles = G_QUEUE_INIT;
GQueue levels = G_QUEUE_INIT;
gboolean failed = FALSE;
if (klass->profile_cid && get_string_list (s, "profile", &profiles)) {
GList *l;
for (l = profiles.head; l; l = l->next) {
struct v4l2_control control = { 0, };
gint v4l2_profile;
const gchar *profile = l->data;
GST_TRACE_OBJECT (ctx->self, "Trying profile %s", profile);
control.id = klass->profile_cid;
control.value = v4l2_profile = klass->profile_from_string (profile);
if (control.value < 0)
continue;
if (v4l2object->ioctl (v4l2object->video_fd, VIDIOC_S_CTRL, &control) < 0) {
GST_WARNING_OBJECT (ctx->self, "Failed to set %s profile: '%s'",
klass->codec_name, g_strerror (errno));
break;
}
profile = klass->profile_to_string (control.value);
if (control.value == v4l2_profile) {
ctx->profile = profile;
break;
}
if (g_list_find_custom (l, profile, g_str_equal)) {
ctx->profile = profile;
break;
}
}
if (profiles.length && !ctx->profile)
failed = TRUE;
g_queue_foreach (&profiles, (GFunc) g_free, NULL);
g_queue_clear (&profiles);
}
if (!failed && klass->level_cid && get_string_list (s, "level", &levels)) {
GList *l;
for (l = levels.head; l; l = l->next) {
struct v4l2_control control = { 0, };
gint v4l2_level;
const gchar *level = l->data;
GST_TRACE_OBJECT (ctx->self, "Trying level %s", level);
control.id = klass->level_cid;
control.value = v4l2_level = klass->level_from_string (level);
if (control.value < 0)
continue;
if (v4l2object->ioctl (v4l2object->video_fd, VIDIOC_S_CTRL, &control) < 0) {
GST_WARNING_OBJECT (ctx->self, "Failed to set %s level: '%s'",
klass->codec_name, g_strerror (errno));
break;
}
level = klass->level_to_string (control.value);
if (control.value == v4l2_level) {
ctx->level = level;
break;
}
if (g_list_find_custom (l, level, g_str_equal)) {
ctx->level = level;
break;
}
}
if (levels.length && !ctx->level)
failed = TRUE;
g_queue_foreach (&levels, (GFunc) g_free, NULL);
g_queue_clear (&levels);
}
/* If it failed, we continue */
return failed;
}
static gboolean static gboolean
gst_v4l2_video_enc_negotiate (GstVideoEncoder * encoder) gst_v4l2_video_enc_negotiate (GstVideoEncoder * encoder)
{ {
GstV4l2VideoEncClass *klass = GST_V4L2_VIDEO_ENC_GET_CLASS (encoder);
GstV4l2VideoEnc *self = GST_V4L2_VIDEO_ENC (encoder); GstV4l2VideoEnc *self = GST_V4L2_VIDEO_ENC (encoder);
GstV4l2Object *v4l2object = self->v4l2output;
GstCaps *allowed_caps;
struct ProfileLevelCtx ctx = { self, NULL, NULL };
GstVideoCodecState *state;
GstStructure *s;
GST_DEBUG_OBJECT (self, "Negotiating %s profile and level.",
klass->codec_name);
allowed_caps = gst_pad_get_allowed_caps (GST_VIDEO_ENCODER_SRC_PAD (encoder));
if (allowed_caps) {
if (gst_caps_is_empty (allowed_caps))
goto not_negotiated;
allowed_caps = gst_caps_make_writable (allowed_caps);
/* negotiate_profile_and_level() will return TRUE on failure to keep
* iterating, if gst_caps_foreach() returns TRUE it means there was no
* compatible profile and level in any of the structure */
if (gst_caps_foreach (allowed_caps, negotiate_profile_and_level, &ctx)) {
goto no_profile_level;
}
}
if (klass->profile_cid && !ctx.profile) {
struct v4l2_control control = { 0, };
control.id = klass->profile_cid;
if (v4l2object->ioctl (v4l2object->video_fd, VIDIOC_G_CTRL, &control) < 0)
goto g_ctrl_failed;
ctx.profile = klass->profile_to_string (control.value);
}
if (klass->level_cid && !ctx.level) {
struct v4l2_control control = { 0, };
control.id = klass->level_cid;
if (v4l2object->ioctl (v4l2object->video_fd, VIDIOC_G_CTRL, &control) < 0)
goto g_ctrl_failed;
ctx.level = klass->level_to_string (control.value);
}
GST_DEBUG_OBJECT (self, "Selected %s profile %s at level %s",
klass->codec_name, ctx.profile, ctx.level);
state = gst_video_encoder_get_output_state (encoder);
s = gst_caps_get_structure (state->caps, 0);
if (klass->profile_cid)
gst_structure_set (s, "profile", G_TYPE_STRING, ctx.profile, NULL);
if (klass->level_cid)
gst_structure_set (s, "level", G_TYPE_STRING, ctx.level, NULL);
if (!GST_VIDEO_ENCODER_CLASS (parent_class)->negotiate (encoder)) if (!GST_VIDEO_ENCODER_CLASS (parent_class)->negotiate (encoder))
return FALSE; return FALSE;
@ -388,6 +583,21 @@ gst_v4l2_video_enc_negotiate (GstVideoEncoder * encoder)
} }
return TRUE; return TRUE;
g_ctrl_failed:
GST_WARNING_OBJECT (self, "Failed to get %s profile and level: '%s'",
klass->codec_name, g_strerror (errno));
goto not_negotiated;
no_profile_level:
GST_WARNING_OBJECT (self, "No compatible level and profile in caps: %"
GST_PTR_FORMAT, allowed_caps);
goto not_negotiated;
not_negotiated:
if (allowed_caps)
gst_caps_unref (allowed_caps);
return FALSE;
} }
static GstVideoCodecFrame * static GstVideoCodecFrame *
@ -783,6 +993,7 @@ gst_v4l2_video_enc_change_state (GstElement * element,
return GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); return GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
} }
static void static void
gst_v4l2_video_enc_dispose (GObject * object) gst_v4l2_video_enc_dispose (GObject * object)
{ {
@ -805,6 +1016,7 @@ gst_v4l2_video_enc_finalize (GObject * object)
G_OBJECT_CLASS (parent_class)->finalize (object); G_OBJECT_CLASS (parent_class)->finalize (object);
} }
static void static void
gst_v4l2_video_enc_init (GstV4l2VideoEnc * self) gst_v4l2_video_enc_init (GstV4l2VideoEnc * self)
{ {

View file

@ -43,6 +43,9 @@ G_BEGIN_DECLS
(G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_V4L2_VIDEO_ENC)) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_V4L2_VIDEO_ENC))
#define GST_IS_V4L2_VIDEO_ENC_CLASS(obj) \ #define GST_IS_V4L2_VIDEO_ENC_CLASS(obj) \
(G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_V4L2_VIDEO_ENC)) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_V4L2_VIDEO_ENC))
#define GST_V4L2_VIDEO_ENC_GET_CLASS(obj) \
(G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_V4L2_VIDEO_ENC, GstV4l2VideoEncClass))
typedef struct _GstV4l2VideoEnc GstV4l2VideoEnc; typedef struct _GstV4l2VideoEnc GstV4l2VideoEnc;
typedef struct _GstV4l2VideoEncClass GstV4l2VideoEncClass; typedef struct _GstV4l2VideoEncClass GstV4l2VideoEncClass;
@ -71,9 +74,15 @@ struct _GstV4l2VideoEncClass
GstVideoEncoderClass parent_class; GstVideoEncoderClass parent_class;
gchar *default_device; gchar *default_device;
const char *codec_name;
GstFlowReturn (*get_output_caps) (GstVideoEncoder * encoder, guint32 profile_cid;
GstCaps ** outcaps); const gchar * (*profile_to_string) (gint v4l2_profile);
gint (*profile_from_string) (const gchar * profile);
guint32 level_cid;
const gchar * (*level_to_string) (gint v4l2_level);
gint (*level_from_string) (const gchar * level);
}; };
GType gst_v4l2_video_enc_get_type (void); GType gst_v4l2_video_enc_get_type (void);

View file

@ -104,161 +104,6 @@ v4l2_profile_to_string (gint v4l2_profile)
return NULL; return NULL;
} }
struct ProfileCtx
{
GstV4l2Vp8Enc *self;
const gchar *profile;
};
static gboolean
get_string_list (GstStructure * s, const gchar * field, GQueue * queue)
{
const GValue *value;
value = gst_structure_get_value (s, field);
if (!value)
return FALSE;
if (GST_VALUE_HOLDS_LIST (value)) {
guint i;
if (gst_value_list_get_size (value) == 0)
return FALSE;
for (i = 0; i < gst_value_list_get_size (value); i++) {
const GValue *item = gst_value_list_get_value (value, i);
if (G_VALUE_HOLDS_STRING (item))
g_queue_push_tail (queue, g_value_dup_string (item));
}
} else if (G_VALUE_HOLDS_STRING (value)) {
g_queue_push_tail (queue, g_value_dup_string (value));
}
return TRUE;
}
static gboolean
negotiate_profile (GstCapsFeatures * features, GstStructure * s,
gpointer user_data)
{
struct ProfileCtx *ctx = user_data;
GstV4l2Object *v4l2object = GST_V4L2_VIDEO_ENC (ctx->self)->v4l2output;
GQueue profiles = G_QUEUE_INIT;
gboolean failed = FALSE;
if (get_string_list (s, "profile", &profiles)) {
GList *l;
for (l = profiles.head; l; l = l->next) {
struct v4l2_control control = { 0, };
gint v4l2_profile;
const gchar *profile = l->data;
GST_TRACE_OBJECT (ctx->self, "Trying profile %s", profile);
control.id = V4L2_CID_MPEG_VIDEO_VPX_PROFILE;
control.value = v4l2_profile = v4l2_profile_from_string (profile);
if (control.value < 0)
continue;
if (v4l2object->ioctl (v4l2object->video_fd, VIDIOC_S_CTRL, &control) < 0) {
GST_WARNING_OBJECT (ctx->self, "Failed to set VP8 profile: '%s'",
g_strerror (errno));
break;
}
profile = v4l2_profile_to_string (control.value);
if (control.value == v4l2_profile) {
ctx->profile = profile;
break;
}
if (g_list_find_custom (l, profile, g_str_equal)) {
ctx->profile = profile;
break;
}
}
if (profiles.length && !ctx->profile)
failed = TRUE;
g_queue_foreach (&profiles, (GFunc) g_free, NULL);
g_queue_clear (&profiles);
}
/* If it failed, we continue */
return failed;
}
static gboolean
gst_v4l2_vp8_enc_negotiate (GstVideoEncoder * encoder)
{
GstV4l2Vp8Enc *self = GST_V4L2_VP8_ENC (encoder);
GstV4l2VideoEnc *venc = GST_V4L2_VIDEO_ENC (encoder);
GstV4l2Object *v4l2object = venc->v4l2output;
GstCaps *allowed_caps;
struct ProfileCtx ctx = { self, NULL };
GstVideoCodecState *state;
GstStructure *s;
GST_DEBUG_OBJECT (self, "Negotiating VP8 profile.");
allowed_caps = gst_pad_get_allowed_caps (GST_VIDEO_ENCODER_SRC_PAD (encoder));
if (allowed_caps) {
if (gst_caps_is_empty (allowed_caps))
goto not_negotiated;
allowed_caps = gst_caps_make_writable (allowed_caps);
/* negotiate_profile() will return TRUE on failure to keep
* iterating, if gst_caps_foreach() returns TRUE it means there was no
* compatible profile in any of the structure */
if (gst_caps_foreach (allowed_caps, negotiate_profile, &ctx)) {
goto no_profile;
}
}
if (!ctx.profile) {
struct v4l2_control control = { 0, };
control.id = V4L2_CID_MPEG_VIDEO_VPX_PROFILE;
if (v4l2object->ioctl (v4l2object->video_fd, VIDIOC_G_CTRL, &control) < 0)
goto g_ctrl_failed;
ctx.profile = v4l2_profile_to_string (control.value);
}
GST_DEBUG_OBJECT (self, "Selected VP8 profile %s", ctx.profile);
state = gst_video_encoder_get_output_state (encoder);
s = gst_caps_get_structure (state->caps, 0);
gst_structure_set (s, "profile", G_TYPE_STRING, ctx.profile, NULL);
return GST_VIDEO_ENCODER_CLASS (parent_class)->negotiate (encoder);
g_ctrl_failed:
GST_WARNING_OBJECT (self, "Failed to get VP8 profile: '%s'",
g_strerror (errno));
goto not_negotiated;
no_profile:
GST_WARNING_OBJECT (self, "No compatible profile in caps: %" GST_PTR_FORMAT,
allowed_caps);
goto not_negotiated;
not_negotiated:
if (allowed_caps)
gst_caps_unref (allowed_caps);
return FALSE;
}
static void static void
gst_v4l2_vp8_enc_init (GstV4l2Vp8Enc * self) gst_v4l2_vp8_enc_init (GstV4l2Vp8Enc * self)
{ {
@ -269,13 +114,14 @@ gst_v4l2_vp8_enc_class_init (GstV4l2Vp8EncClass * klass)
{ {
GstElementClass *element_class; GstElementClass *element_class;
GObjectClass *gobject_class; GObjectClass *gobject_class;
GstVideoEncoderClass *baseclass; GstV4l2VideoEncClass *baseclass;
parent_class = g_type_class_peek_parent (klass); parent_class = g_type_class_peek_parent (klass);
element_class = (GstElementClass *) klass; element_class = (GstElementClass *) klass;
gobject_class = (GObjectClass *) klass; gobject_class = (GObjectClass *) klass;
baseclass = GST_VIDEO_ENCODER_CLASS (klass); baseclass = (GstV4l2VideoEncClass *) (klass);
GST_DEBUG_CATEGORY_INIT (gst_v4l2_vp8_enc_debug, "v4l2vp8enc", 0, GST_DEBUG_CATEGORY_INIT (gst_v4l2_vp8_enc_debug, "v4l2vp8enc", 0,
"V4L2 VP8 Encoder"); "V4L2 VP8 Encoder");
@ -290,7 +136,11 @@ gst_v4l2_vp8_enc_class_init (GstV4l2Vp8EncClass * klass)
GST_DEBUG_FUNCPTR (gst_v4l2_vp8_enc_set_property); GST_DEBUG_FUNCPTR (gst_v4l2_vp8_enc_set_property);
gobject_class->get_property = gobject_class->get_property =
GST_DEBUG_FUNCPTR (gst_v4l2_vp8_enc_get_property); GST_DEBUG_FUNCPTR (gst_v4l2_vp8_enc_get_property);
baseclass->negotiate = GST_DEBUG_FUNCPTR (gst_v4l2_vp8_enc_negotiate);
baseclass->codec_name = "VP8";
baseclass->profile_cid = V4L2_CID_MPEG_VIDEO_VPX_PROFILE;
baseclass->profile_to_string = v4l2_profile_to_string;
baseclass->profile_from_string = v4l2_profile_from_string;
} }
/* Probing functions */ /* Probing functions */

View file

@ -104,161 +104,6 @@ v4l2_profile_to_string (gint v4l2_profile)
return NULL; return NULL;
} }
struct ProfileCtx
{
GstV4l2Vp9Enc *self;
const gchar *profile;
};
static gboolean
get_string_list (GstStructure * s, const gchar * field, GQueue * queue)
{
const GValue *value;
value = gst_structure_get_value (s, field);
if (!value)
return FALSE;
if (GST_VALUE_HOLDS_LIST (value)) {
guint i;
if (gst_value_list_get_size (value) == 0)
return FALSE;
for (i = 0; i < gst_value_list_get_size (value); i++) {
const GValue *item = gst_value_list_get_value (value, i);
if (G_VALUE_HOLDS_STRING (item))
g_queue_push_tail (queue, g_value_dup_string (item));
}
} else if (G_VALUE_HOLDS_STRING (value)) {
g_queue_push_tail (queue, g_value_dup_string (value));
}
return TRUE;
}
static gboolean
negotiate_profile (GstCapsFeatures * features, GstStructure * s,
gpointer user_data)
{
struct ProfileCtx *ctx = user_data;
GstV4l2Object *v4l2object = GST_V4L2_VIDEO_ENC (ctx->self)->v4l2output;
GQueue profiles = G_QUEUE_INIT;
gboolean failed = FALSE;
if (get_string_list (s, "profile", &profiles)) {
GList *l;
for (l = profiles.head; l; l = l->next) {
struct v4l2_control control = { 0, };
gint v4l2_profile;
const gchar *profile = l->data;
GST_TRACE_OBJECT (ctx->self, "Trying profile %s", profile);
control.id = V4L2_CID_MPEG_VIDEO_VPX_PROFILE;
control.value = v4l2_profile = v4l2_profile_from_string (profile);
if (control.value < 0)
continue;
if (v4l2object->ioctl (v4l2object->video_fd, VIDIOC_S_CTRL, &control) < 0) {
GST_WARNING_OBJECT (ctx->self, "Failed to set VP9 profile: '%s'",
g_strerror (errno));
break;
}
profile = v4l2_profile_to_string (control.value);
if (control.value == v4l2_profile) {
ctx->profile = profile;
break;
}
if (g_list_find_custom (l, profile, g_str_equal)) {
ctx->profile = profile;
break;
}
}
if (profiles.length && !ctx->profile)
failed = TRUE;
g_queue_foreach (&profiles, (GFunc) g_free, NULL);
g_queue_clear (&profiles);
}
/* If it failed, we continue */
return failed;
}
static gboolean
gst_v4l2_vp9_enc_negotiate (GstVideoEncoder * encoder)
{
GstV4l2Vp9Enc *self = GST_V4L2_VP9_ENC (encoder);
GstV4l2VideoEnc *venc = GST_V4L2_VIDEO_ENC (encoder);
GstV4l2Object *v4l2object = venc->v4l2output;
GstCaps *allowed_caps;
struct ProfileCtx ctx = { self, NULL };
GstVideoCodecState *state;
GstStructure *s;
GST_DEBUG_OBJECT (self, "Negotiating VP8 profile.");
allowed_caps = gst_pad_get_allowed_caps (GST_VIDEO_ENCODER_SRC_PAD (encoder));
if (allowed_caps) {
if (gst_caps_is_empty (allowed_caps))
goto not_negotiated;
allowed_caps = gst_caps_make_writable (allowed_caps);
/* negotiate_profile() will return TRUE on failure to keep
* iterating, if gst_caps_foreach() returns TRUE it means there was no
* compatible profile in any of the structure */
if (gst_caps_foreach (allowed_caps, negotiate_profile, &ctx)) {
goto no_profile;
}
}
if (!ctx.profile) {
struct v4l2_control control = { 0, };
control.id = V4L2_CID_MPEG_VIDEO_VPX_PROFILE;
if (v4l2object->ioctl (v4l2object->video_fd, VIDIOC_G_CTRL, &control) < 0)
goto g_ctrl_failed;
ctx.profile = v4l2_profile_to_string (control.value);
}
GST_DEBUG_OBJECT (self, "Selected VP9 profile %s", ctx.profile);
state = gst_video_encoder_get_output_state (encoder);
s = gst_caps_get_structure (state->caps, 0);
gst_structure_set (s, "profile", G_TYPE_STRING, ctx.profile, NULL);
return GST_VIDEO_ENCODER_CLASS (parent_class)->negotiate (encoder);
g_ctrl_failed:
GST_WARNING_OBJECT (self, "Failed to get VP9 profile: '%s'",
g_strerror (errno));
goto not_negotiated;
no_profile:
GST_WARNING_OBJECT (self, "No compatible profile in caps: %" GST_PTR_FORMAT,
allowed_caps);
goto not_negotiated;
not_negotiated:
if (allowed_caps)
gst_caps_unref (allowed_caps);
return FALSE;
}
static void static void
gst_v4l2_vp9_enc_init (GstV4l2Vp9Enc * self) gst_v4l2_vp9_enc_init (GstV4l2Vp9Enc * self)
{ {
@ -269,13 +114,13 @@ gst_v4l2_vp9_enc_class_init (GstV4l2Vp9EncClass * klass)
{ {
GstElementClass *element_class; GstElementClass *element_class;
GObjectClass *gobject_class; GObjectClass *gobject_class;
GstVideoEncoderClass *baseclass; GstV4l2VideoEncClass *baseclass;
parent_class = g_type_class_peek_parent (klass); parent_class = g_type_class_peek_parent (klass);
element_class = (GstElementClass *) klass; element_class = (GstElementClass *) klass;
gobject_class = (GObjectClass *) klass; gobject_class = (GObjectClass *) klass;
baseclass = GST_VIDEO_ENCODER_CLASS (klass); baseclass = (GstV4l2VideoEncClass *) (klass);
GST_DEBUG_CATEGORY_INIT (gst_v4l2_vp9_enc_debug, "v4l2vp9enc", 0, GST_DEBUG_CATEGORY_INIT (gst_v4l2_vp9_enc_debug, "v4l2vp9enc", 0,
"V4L2 VP9 Encoder"); "V4L2 VP9 Encoder");
@ -290,7 +135,11 @@ gst_v4l2_vp9_enc_class_init (GstV4l2Vp9EncClass * klass)
GST_DEBUG_FUNCPTR (gst_v4l2_vp9_enc_set_property); GST_DEBUG_FUNCPTR (gst_v4l2_vp9_enc_set_property);
gobject_class->get_property = gobject_class->get_property =
GST_DEBUG_FUNCPTR (gst_v4l2_vp9_enc_get_property); GST_DEBUG_FUNCPTR (gst_v4l2_vp9_enc_get_property);
baseclass->negotiate = GST_DEBUG_FUNCPTR (gst_v4l2_vp9_enc_negotiate);
baseclass->codec_name = "VP9";
baseclass->profile_cid = V4L2_CID_MPEG_VIDEO_VPX_PROFILE;
baseclass->profile_to_string = v4l2_profile_to_string;
baseclass->profile_from_string = v4l2_profile_from_string;
} }
/* Probing functions */ /* Probing functions */