From 6b4f87ff52197510e6d4ca475ecdb5725473e60a Mon Sep 17 00:00:00 2001 From: Benjamin Gaignard Date: Mon, 16 Jan 2023 17:38:58 +0100 Subject: [PATCH 4/5] v4l2codecs: Add V4L2 stateless VP8 encoder Add element for V4L2 stateless VP8 encoder. Using v4l2 request to set driver configuration by using VP8 stateless controls. --- sys/v4l2codecs/gstv4l2codecvp8enc.c | 716 ++++++++++++++++++++++++++++ sys/v4l2codecs/gstv4l2codecvp8enc.h | 54 +++ sys/v4l2codecs/meson.build | 1 + 3 files changed, 771 insertions(+) create mode 100644 sys/v4l2codecs/gstv4l2codecvp8enc.c create mode 100644 sys/v4l2codecs/gstv4l2codecvp8enc.h diff --git a/sys/v4l2codecs/gstv4l2codecvp8enc.c b/sys/v4l2codecs/gstv4l2codecvp8enc.c new file mode 100644 index 0000000..2f15caf --- /dev/null +++ b/sys/v4l2codecs/gstv4l2codecvp8enc.c @@ -0,0 +1,716 @@ +/* GStreamer + * Copyright (C) 2022 Benjamin Gaignard + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "gstv4l2codecallocator.h" +#include "gstv4l2codecpool.h" +#include "gstv4l2codecvp8enc.h" +#include "gstv4l2format.h" + +#define KERNEL_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c)) + +#define V4L2_MIN_KERNEL_VER_MAJOR 5 +#define V4L2_MIN_KERNEL_VER_MINOR 17 +#define V4L2_MIN_KERNEL_VERSION KERNEL_VERSION(V4L2_MIN_KERNEL_VER_MAJOR, V4L2_MIN_KERNEL_VER_MINOR, 0) + +GST_DEBUG_CATEGORY_STATIC (v4l2_vp8enc_debug); +#define GST_CAT_DEFAULT v4l2_vp8enc_debug + +enum +{ + PROP_0, + PROP_LAST = PROP_0 +}; + +static GstStaticPadTemplate sink_template = +GST_STATIC_PAD_TEMPLATE (GST_VIDEO_ENCODER_SINK_NAME, + GST_PAD_SINK, GST_PAD_ALWAYS, + GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE (GST_V4L2_DEFAULT_VIDEO_FORMATS))); + +static GstStaticPadTemplate src_template = +GST_STATIC_PAD_TEMPLATE (GST_VIDEO_ENCODER_SRC_NAME, + GST_PAD_SRC, GST_PAD_ALWAYS, + GST_STATIC_CAPS ("video/x-vp8")); + +struct _vp8_frame_hdr +{ + guint8 tag[3]; + guint8 start_code[3]; + guint16 width; + guint16 height; +}; + +#define VP8_FRAME_HDR_INTRA_FLAG 0x0 +#define VP8_FRAME_HDR_INTER_FLAG 0x1 +#define VP8_FRAME_HDR_VERSION1_FLAG (1 << 1) +#define VP8_FRAME_HDR_SHOW_FRAME (1 << 4) +#define VP8_FRAME_HDR_WIDTH_MASK 0x3fff +#define VP8_FRAME_HDR_HEIGHT_MASK 0x3fff + +struct _GstV4l2CodecVp8Enc +{ + GstVp8Encoder parent; + GstV4l2Encoder *encoder; + GstVideoCodecState *output_state; + GstVideoInfo vinfo; + gint width; + gint height; + guint qp_max, qp_min; + + GstV4l2CodecAllocator *sink_allocator; + GstV4l2CodecAllocator *src_allocator; + GstV4l2CodecPool *sink_pool; + GstV4l2CodecPool *src_pool; + + struct v4l2_ctrl_vp8_encode_params encode_params; +}; + +G_DEFINE_ABSTRACT_TYPE (GstV4l2CodecVp8Enc, gst_v4l2_codec_vp8_enc, + GST_TYPE_VP8_ENCODER); + +#define parent_class gst_v4l2_codec_vp8_enc_parent_class + +static gboolean +gst_v4l2_codec_vp8_enc_open (GstVideoEncoder * encoder) +{ + GstV4l2CodecVp8Enc *self = GST_V4L2_CODEC_VP8_ENC (encoder); + guint version; + + if (!gst_v4l2_encoder_open (self->encoder)) { + GST_ELEMENT_ERROR (self, RESOURCE, OPEN_READ_WRITE, + ("Failed to open VP8 encoder"), + ("gst_v4l2_encoder_open() failed: %s", g_strerror (errno))); + return FALSE; + } + + version = gst_v4l2_encoder_get_version (self->encoder); + if (version < V4L2_MIN_KERNEL_VERSION) + GST_WARNING_OBJECT (self, + "V4L2 API v%u.%u too old, at least v%u.%u required", + (version >> 16) & 0xff, (version >> 8) & 0xff, + V4L2_MIN_KERNEL_VER_MAJOR, V4L2_MIN_KERNEL_VER_MINOR); + + GST_DEBUG_OBJECT (self, "open vp8 encoder"); + + return TRUE; +} + +static gboolean +gst_v4l2_codec_vp8_enc_api_check (GstV4l2Encoder * encoder) +{ + guint i, ret_size; + /* *INDENT-OFF* */ + #define SET_ID(cid) .id = (cid), .name = #cid + struct + { + const gchar *name; + unsigned int id; + unsigned int size; + gboolean optional; + } controls[] = { + { + SET_ID (V4L2_CID_STATELESS_VP8_ENCODE_PARAMS), + .size = sizeof(struct v4l2_ctrl_vp8_encode_params), + }, { + SET_ID (V4L2_CID_STATELESS_VP8_ENCODE_QP), + .size = sizeof(__u32), + }, + }; + #undef SET_ID + /* *INDENT-ON* */ + + /* + * Compatibility check: make sure the pointer controls are + * the right size. + */ + for (i = 0; i < G_N_ELEMENTS (controls); i++) { + gboolean control_found; + + control_found = gst_v4l2_encoder_query_control_size (encoder, + controls[i].id, &ret_size); + + if (!controls[i].optional && !control_found) { + GST_WARNING ("Driver is missing %s support.", controls[i].name); + return FALSE; + } + + if (control_found && ret_size != controls[i].size) { + GST_WARNING ("%s control size mismatch: got %d bytes but %d expected.", + controls[i].name, ret_size, controls[i].size); + return FALSE; + } + } + + return TRUE; +} + +static gboolean +gst_v4l2_codec_vp8_enc_close (GstVideoEncoder * encoder) +{ + GstV4l2CodecVp8Enc *self = GST_V4L2_CODEC_VP8_ENC (encoder); + gst_v4l2_encoder_close (self->encoder); + return TRUE; +} + +static void +gst_v4l2_codec_vp8_enc_reset_allocation (GstV4l2CodecVp8Enc * self) +{ + if (self->sink_allocator) { + gst_v4l2_codec_allocator_detach (self->sink_allocator); + g_clear_object (&self->sink_allocator); + g_clear_object (&self->sink_pool); + } + + if (self->src_allocator) { + gst_v4l2_codec_allocator_detach (self->src_allocator); + g_clear_object (&self->src_allocator); + g_clear_object (&self->src_pool); + } +} + +static gboolean +gst_v4l2_codec_vp8_enc_start (GstVideoEncoder * encoder) +{ + GstV4l2CodecVp8Enc *self = GST_V4L2_CODEC_VP8_ENC (encoder); + + GST_DEBUG_OBJECT (self, "start"); + + return GST_VIDEO_ENCODER_CLASS (parent_class)->start (encoder); +} + +static gboolean +gst_v4l2_codec_vp8_enc_stop (GstVideoEncoder * encoder) +{ + GstV4l2CodecVp8Enc *self = GST_V4L2_CODEC_VP8_ENC (encoder); + + GST_DEBUG_OBJECT (self, "stop"); + + gst_v4l2_encoder_streamoff (self->encoder, GST_PAD_SINK); + gst_v4l2_encoder_streamoff (self->encoder, GST_PAD_SRC); + + gst_v4l2_codec_vp8_enc_reset_allocation (self); + + if (self->output_state) + gst_video_codec_state_unref (self->output_state); + self->output_state = NULL; + + return GST_VIDEO_ENCODER_CLASS (parent_class)->stop (encoder); +} + +static GstCaps * +gst_v4l2_codec_vp8_enc_getcaps (GstVideoEncoder * encoder, GstCaps * filter) +{ + GstV4l2CodecVp8Enc *self = GST_V4L2_CODEC_VP8_ENC (encoder); + GstCaps *caps, *result; + + caps = gst_v4l2_encoder_list_sink_formats (self->encoder); + GST_DEBUG_OBJECT (self, "Supported input formats: %" GST_PTR_FORMAT, caps); + + result = gst_video_encoder_proxy_getcaps (encoder, caps, filter); + + if (caps) + gst_caps_unref (caps); + + GST_DEBUG_OBJECT (self, "Returning sink caps: %" GST_PTR_FORMAT, result); + + return result; +} + +static gboolean +gst_v4l2_codec_vp8_enc_buffers_allocation (GstVideoEncoder * encoder) +{ + GstV4l2CodecVp8Enc *self = GST_V4L2_CODEC_VP8_ENC (encoder); + + GST_DEBUG_OBJECT (self, "buffers allocation"); + + g_clear_object (&self->sink_pool); + g_clear_object (&self->src_pool); + g_clear_object (&self->src_allocator); + + self->sink_allocator = gst_v4l2_codec_encoder_allocator_new (self->encoder, + GST_PAD_SINK, 4); + if (!self->sink_allocator) { + GST_ELEMENT_ERROR (self, RESOURCE, NO_SPACE_LEFT, + ("Not enough memory to allocate sink buffers."), (NULL)); + return FALSE; + } + + self->sink_pool = + gst_v4l2_codec_pool_new (self->sink_allocator, &self->vinfo); + + self->src_allocator = gst_v4l2_codec_encoder_allocator_new (self->encoder, + GST_PAD_SRC, 4); + if (!self->src_allocator) { + GST_ELEMENT_ERROR (self, RESOURCE, NO_SPACE_LEFT, + ("Not enough memory to allocate source buffers."), (NULL)); + g_clear_object (&self->sink_allocator); + return FALSE; + } + + self->src_pool = gst_v4l2_codec_pool_new (self->src_allocator, &self->vinfo); + + return TRUE; +} + +static gboolean +gst_v4l2_codec_vp8_enc_set_format (GstVideoEncoder * encoder, + GstVideoCodecState * state) +{ + GstV4l2CodecVp8Enc *self = GST_V4L2_CODEC_VP8_ENC (encoder); + GstCaps *caps; + + GST_DEBUG_OBJECT (self, "Set format"); + + gst_v4l2_encoder_streamoff (self->encoder, GST_PAD_SINK); + gst_v4l2_encoder_streamoff (self->encoder, GST_PAD_SRC); + + gst_v4l2_codec_vp8_enc_reset_allocation (self); + + if (!gst_v4l2_encoder_select_sink_format (self->encoder, &state->info, + &self->vinfo)) { + GST_ELEMENT_ERROR (self, CORE, NEGOTIATION, + ("Failed to configure VP8 encoder"), + ("gst_v4l2_encoder_select_sink_format() failed: %s", + g_strerror (errno))); + gst_v4l2_encoder_close (self->encoder); + return FALSE; + } + + if (!gst_v4l2_encoder_set_src_fmt (self->encoder, &self->vinfo, + V4L2_PIX_FMT_VP8_FRAME)) { + GST_ELEMENT_ERROR (self, CORE, NEGOTIATION, ("Unsupported pixel format"), + ("No support for %ux%u format VP8", state->info.width, + state->info.height)); + return FALSE; + } + + self->width = state->info.width; + self->height = state->info.height; + gst_v4l2_codec_vp8_enc_buffers_allocation (encoder); + + if (self->output_state) + gst_video_codec_state_unref (self->output_state); + + caps = gst_caps_new_empty_simple ("video/x-vp8"); + + self->output_state = + gst_video_encoder_set_output_state (GST_VIDEO_ENCODER (self), + caps, state); + + if (GST_VIDEO_ENCODER_CLASS (parent_class)->negotiate (encoder)) { + if (!gst_v4l2_encoder_streamon (self->encoder, GST_PAD_SINK)) { + GST_ELEMENT_ERROR (self, RESOURCE, FAILED, + ("Could not enable the encoder driver."), + ("VIDIOC_STREAMON(SINK) failed: %s", g_strerror (errno))); + return FALSE; + } + + if (!gst_v4l2_encoder_streamon (self->encoder, GST_PAD_SRC)) { + GST_ELEMENT_ERROR (self, RESOURCE, FAILED, + ("Could not enable the encoder driver."), + ("VIDIOC_STREAMON(SRC) failed: %s", g_strerror (errno))); + return FALSE; + } + + gst_v4l2_codec_vp8_enc_get_qp_range (self->encoder, &self->qp_min, + &self->qp_max); + + return TRUE; + } + + return FALSE; +} + +static void +gst_v4l2_codec_vp8_enc_set_flushing (GstV4l2CodecVp8Enc * self, + gboolean flushing) +{ + if (self->sink_allocator) + gst_v4l2_codec_allocator_set_flushing (self->sink_allocator, flushing); + if (self->src_allocator) + gst_v4l2_codec_allocator_set_flushing (self->src_allocator, flushing); +} + +static gboolean +gst_v4l2_codec_vp8_enc_flush (GstVideoEncoder * encoder) +{ + GstV4l2CodecVp8Enc *self = GST_V4L2_CODEC_VP8_ENC (encoder); + + GST_DEBUG_OBJECT (self, "Flushing encoder state."); + + gst_v4l2_encoder_flush (self->encoder); + gst_v4l2_codec_vp8_enc_set_flushing (self, FALSE); + + return GST_VIDEO_ENCODER_CLASS (parent_class)->flush (encoder); +} + +static gboolean +gst_v4l2_codec_vp8_enc_sink_event (GstVideoEncoder * encoder, GstEvent * event) +{ + GstV4l2CodecVp8Enc *self = GST_V4L2_CODEC_VP8_ENC (encoder); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_FLUSH_START: + GST_DEBUG_OBJECT (self, "flush start"); + gst_v4l2_codec_vp8_enc_set_flushing (self, TRUE); + break; + default: + break; + } + + return GST_VIDEO_ENCODER_CLASS (parent_class)->sink_event (encoder, event); +} + +static GstStateChangeReturn +gst_v4l2_codec_vp8_enc_change_state (GstElement * element, + GstStateChange transition) +{ + GstV4l2CodecVp8Enc *self = GST_V4L2_CODEC_VP8_ENC (element); + + if (transition == GST_STATE_CHANGE_PAUSED_TO_READY) + gst_v4l2_codec_vp8_enc_set_flushing (self, TRUE); + + return GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); +} + + +static void +gst_v4l2_codec_vp8_enc_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstV4l2CodecVp8Enc *self = GST_V4L2_CODEC_VP8_ENC (object); + GObject *dec = G_OBJECT (self->encoder); + + switch (prop_id) { + default: + gst_v4l2_encoder_set_property (dec, prop_id - PROP_LAST, value, pspec); + break; + } +} + +static void +gst_v4l2_codec_vp8_enc_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstV4l2CodecVp8Enc *self = GST_V4L2_CODEC_VP8_ENC (object); + GObject *dec = G_OBJECT (self->encoder); + + switch (prop_id) { + default: + gst_v4l2_encoder_get_property (dec, prop_id - PROP_LAST, value, pspec); + break; + } +} + +static gboolean +gst_v4l2_codec_vp8_enc_copy_input_buffer (GstV4l2CodecVp8Enc * self, + GstVideoCodecFrame * frame) +{ + GstVideoFrame src_frame; + GstVideoFrame dest_frame; + GstVideoInfo dest_vinfo; + GstBuffer *buffer; + GstFlowReturn flow_ret; + + gst_video_info_set_format (&dest_vinfo, GST_VIDEO_INFO_FORMAT (&self->vinfo), + self->width, self->height); + + flow_ret = gst_buffer_pool_acquire_buffer (GST_BUFFER_POOL (self->sink_pool), + &buffer, NULL); + if (flow_ret != GST_FLOW_OK) { + if (flow_ret == GST_FLOW_FLUSHING) + GST_DEBUG_OBJECT (self, "Frame encoding aborted, we are flushing."); + else + GST_ELEMENT_ERROR (self, RESOURCE, WRITE, + ("No more picture buffer available."), (NULL)); + return FALSE; + } + + if (!buffer) + goto fail; + + if (!gst_video_frame_map (&src_frame, &self->vinfo, + frame->input_buffer, GST_MAP_READ)) + goto fail; + + if (!gst_video_frame_map (&dest_frame, &dest_vinfo, buffer, GST_MAP_WRITE)) { + gst_video_frame_unmap (&dest_frame); + goto fail; + } + + if (!gst_video_frame_copy (&dest_frame, &src_frame)) { + gst_video_frame_unmap (&src_frame); + gst_video_frame_unmap (&dest_frame); + goto fail; + } + + gst_video_frame_unmap (&src_frame); + gst_video_frame_unmap (&dest_frame); + gst_buffer_replace (&frame->input_buffer, buffer); + gst_buffer_unref (buffer); + + return TRUE; + +fail: + GST_ERROR_OBJECT (self, "Failed copy input buffer."); + return FALSE; +} + +static gboolean +gst_v4l2_codec_vp8_enc_ensure_output_bitstream (GstV4l2CodecVp8Enc * self, + GstVideoCodecFrame * frame) +{ + GstFlowReturn flow_ret; + + flow_ret = gst_buffer_pool_acquire_buffer (GST_BUFFER_POOL (self->src_pool), + &frame->output_buffer, NULL); + if (flow_ret != GST_FLOW_OK) { + if (flow_ret == GST_FLOW_FLUSHING) + GST_DEBUG_OBJECT (self, "Frame encoding aborted, we are flushing."); + else + GST_ELEMENT_ERROR (self, RESOURCE, WRITE, + ("No more encoded buffer available."), (NULL)); + return FALSE; + } + + if (!frame->output_buffer) + return FALSE; + + return TRUE; +} + +static void +gst_v4l2_codec_vp8_enc_fill_encode_params (GstVp8Encoder * encoder, + GstVp8Frame * vp8_frame) +{ + GstV4l2CodecVp8Enc *self = GST_V4L2_CODEC_VP8_ENC (encoder); + + switch (vp8_frame->type) { + case GstVp8Keyframe: + self->encode_params.frame_type = V4L2_VP8_FRAME_TYPE_KEYFRAME; + self->encode_params.loop_filter_level = 26; + break; + case GstVp8Inter: + default: + self->encode_params.frame_type = V4L2_VP8_FRAME_TYPE_INTER; + self->encode_params.loop_filter_level = 12; + break; + } + + self->encode_params.flags = V4L2_VP8_FRAME_FLAG_SHOWFRAME; +} + +static guint +gst_v4l2_codec_vp8_enc_check_qp_range (GstV4l2CodecVp8Enc * self, + GstVp8Frame * vp8_frame) +{ + if (vp8_frame->quality > self->qp_max) + return self->qp_max; + if (vp8_frame->quality < self->qp_min) + return self->qp_min; + + return vp8_frame->quality; +} + +static GstFlowReturn +gst_v4l2_codec_vp8_enc_encode_frame (GstVp8Encoder * encoder, + GstVp8Frame * vp8_frame) +{ + GstV4l2CodecVp8Enc *self = GST_V4L2_CODEC_VP8_ENC (encoder); + GstVideoEncoder *venc = GST_VIDEO_ENCODER (encoder); + GstV4l2Request *request = NULL; + GstFlowReturn ret = GST_FLOW_ERROR; + GstVideoCodecFrame *frame = vp8_frame->frame; + GstBuffer *resized_buffer; + guint32 bytesused; + + /* *INDENT-OFF* */ + struct v4l2_ext_control control[] = { + { + .id = V4L2_CID_STATELESS_VP8_ENCODE_PARAMS, + .ptr = &self->encode_params, + .size = sizeof (self->encode_params), + }, { + .id = V4L2_CID_STATELESS_VP8_ENCODE_QP, + .value = gst_v4l2_codec_vp8_enc_check_qp_range (self, vp8_frame), + .size = sizeof (guint), + }, + }; + /* *INDENT-ON* */ + + GST_DEBUG_OBJECT (self, "encode vp8 frame with quality = %d", + vp8_frame->quality); + + if (!gst_v4l2_codec_vp8_enc_ensure_output_bitstream (self, frame)) { + GST_ELEMENT_ERROR (self, RESOURCE, NO_SPACE_LEFT, + ("Failed to allocate output buffer."), (NULL)); + goto done; + } + + if (!gst_v4l2_codec_vp8_enc_copy_input_buffer (self, frame)) { + GST_ELEMENT_ERROR (self, RESOURCE, NO_SPACE_LEFT, + ("Failed to allocate/copy input buffer."), (NULL)); + goto done; + } + + request = gst_v4l2_encoder_alloc_request (self->encoder, + frame->system_frame_number, frame->input_buffer, frame->output_buffer); + + if (!request) { + GST_ELEMENT_ERROR (self, RESOURCE, NO_SPACE_LEFT, + ("Failed to allocate a media request object."), (NULL)); + goto done; + } + + gst_v4l2_codec_vp8_enc_fill_encode_params (encoder, vp8_frame); + + if (!gst_v4l2_encoder_set_controls (self->encoder, request, control, + G_N_ELEMENTS (control))) { + GST_ELEMENT_ERROR (self, RESOURCE, WRITE, + ("Driver did not accept the control parameters."), (NULL)); + goto done; + } + + if (!gst_v4l2_encoder_request_queue (request, 0)) { + GST_ELEMENT_ERROR (self, RESOURCE, WRITE, + ("Driver did not accept the encode request."), (NULL)); + goto done; + } + + if (!gst_v4l2_encoder_request_set_done (request, &bytesused)) { + GST_ELEMENT_ERROR (self, RESOURCE, WRITE, + ("Driver did not ack the request."), (NULL)); + goto done; + } + + gst_v4l2_encoder_request_unref (request); + + resized_buffer = gst_buffer_copy_region (frame->output_buffer, + GST_BUFFER_COPY_MEMORY | GST_BUFFER_COPY_DEEP, 0, bytesused); + gst_buffer_replace (&frame->output_buffer, resized_buffer); + gst_buffer_unref (resized_buffer); + + return gst_video_encoder_finish_frame (venc, frame); + +done: + if (request) + gst_v4l2_encoder_request_unref (request); + + return ret; +} + +static void +gst_v4l2_codec_vp8_enc_init (GstV4l2CodecVp8Enc * self) +{ +} + +static void +gst_v4l2_codec_vp8_enc_subinit (GstV4l2CodecVp8Enc * self, + GstV4l2CodecVp8EncClass * klass) +{ + self->encoder = gst_v4l2_encoder_new (klass->device); +} + +static void +gst_v4l2_codec_vp8_enc_dispose (GObject * object) +{ + GstV4l2CodecVp8Enc *self = GST_V4L2_CODEC_VP8_ENC (object); + + g_clear_object (&self->encoder); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gst_v4l2_codec_vp8_enc_class_init (GstV4l2CodecVp8EncClass * klass) +{ +} + +static void +gst_v4l2_codec_vp8_enc_subclass_init (GstV4l2CodecVp8EncClass * klass, + GstV4l2CodecDevice * device) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GstElementClass *element_class = GST_ELEMENT_CLASS (klass); + GstVideoEncoderClass *encoder_class = GST_VIDEO_ENCODER_CLASS (klass); + GstVp8EncoderClass *vp8encoder_class = GST_VP8_ENCODER_CLASS (klass); + + gobject_class->set_property = gst_v4l2_codec_vp8_enc_set_property; + gobject_class->get_property = gst_v4l2_codec_vp8_enc_get_property; + gobject_class->dispose = gst_v4l2_codec_vp8_enc_dispose; + + gst_element_class_set_static_metadata (element_class, + "V4L2 Stateless VP8 Video Encoder", + "Codec/Encoder/Video/Hardware", + "A V4L2 based VP8 video encoder", + "Benjamin Gaignard "); + + gst_element_class_add_static_pad_template (element_class, &sink_template); + gst_element_class_add_static_pad_template (element_class, &src_template); + + element_class->change_state = + GST_DEBUG_FUNCPTR (gst_v4l2_codec_vp8_enc_change_state); + + encoder_class->open = GST_DEBUG_FUNCPTR (gst_v4l2_codec_vp8_enc_open); + encoder_class->close = GST_DEBUG_FUNCPTR (gst_v4l2_codec_vp8_enc_close); + encoder_class->start = GST_DEBUG_FUNCPTR (gst_v4l2_codec_vp8_enc_start); + encoder_class->stop = GST_DEBUG_FUNCPTR (gst_v4l2_codec_vp8_enc_stop); + encoder_class->set_format = + GST_DEBUG_FUNCPTR (gst_v4l2_codec_vp8_enc_set_format); + encoder_class->flush = GST_DEBUG_FUNCPTR (gst_v4l2_codec_vp8_enc_flush); + encoder_class->sink_event = + GST_DEBUG_FUNCPTR (gst_v4l2_codec_vp8_enc_sink_event); + encoder_class->getcaps = GST_DEBUG_FUNCPTR (gst_v4l2_codec_vp8_enc_getcaps); + vp8encoder_class->encode_frame = + GST_DEBUG_FUNCPTR (gst_v4l2_codec_vp8_enc_encode_frame); + + klass->device = device; + gst_v4l2_encoder_install_properties (gobject_class, PROP_LAST, device); +} + +void +gst_v4l2_codec_vp8_enc_register (GstPlugin * plugin, GstV4l2Encoder * encoder, + GstV4l2CodecDevice * device, guint rank) +{ + gchar *element_name; + guint version; + + GST_DEBUG_CATEGORY_INIT (v4l2_vp8enc_debug, "v4l2codecs-vp8enc", 0, + "V4L2 stateless VP8 encoder"); + + version = gst_v4l2_encoder_get_version (encoder); + if (version < V4L2_MIN_KERNEL_VERSION) + GST_WARNING ("V4L2 API v%u.%u too old, at least v%u.%u required", + (version >> 16) & 0xff, (version >> 8) & 0xff, + V4L2_MIN_KERNEL_VER_MAJOR, V4L2_MIN_KERNEL_VER_MINOR); + + if (!gst_v4l2_codec_vp8_enc_api_check (encoder)) { + GST_WARNING ("Not registering VP8 encoder as it failed ABI check."); + return; + } + + gst_v4l2_encoder_register (plugin, GST_TYPE_V4L2_CODEC_VP8_ENC, + (GClassInitFunc) gst_v4l2_codec_vp8_enc_subclass_init, + gst_mini_object_ref (GST_MINI_OBJECT (device)), + (GInstanceInitFunc) gst_v4l2_codec_vp8_enc_subinit, + "v4l2sl%svp8enc", device, rank, &element_name); +} diff --git a/sys/v4l2codecs/gstv4l2codecvp8enc.h b/sys/v4l2codecs/gstv4l2codecvp8enc.h new file mode 100644 index 0000000..115b147 --- /dev/null +++ b/sys/v4l2codecs/gstv4l2codecvp8enc.h @@ -0,0 +1,54 @@ +/* GStreamer + * Copyright (C) 2022 Benjamin Gaignard + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_V4L2_CODEC_VP8_ENC_H__ +#define __GST_V4L2_CODEC_VP8_ENC_H__ + +#define GST_USE_UNSTABLE_API +#include + +#include "gstv4l2encoder.h" + +G_BEGIN_DECLS + +#define GST_TYPE_V4L2_CODEC_VP8_ENC (gst_v4l2_codec_vp8_enc_get_type()) +#define GST_V4L2_CODEC_VP8_ENC(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_V4L2_CODEC_VP8_ENC,GstV4l2CodecVp8Enc)) +#define GST_V4L2_CODEC_VP8_ENC_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_V4L2_CODEC_VP8_ENC,GstV4l2CodecVp8EncClass)) +#define GST_V4L2_CODEC_VP8_ENC_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_V4L2_CODEC_VP8_ENC, GstV4l2CodecVp8EncClass)) +#define GST_IS_V4L2_CODEC_VP8_ENC(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_V4L2_CODEC_VP8_ENC)) +#define GST_IS_V4L2_CODEC_VP8_ENC_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_V4L2_CODEC_VP8_ENC)) + +typedef struct _GstV4l2CodecVp8Enc GstV4l2CodecVp8Enc; +typedef struct _GstV4l2CodecVp8EncClass GstV4l2CodecVp8EncClass; + +struct _GstV4l2CodecVp8EncClass +{ + GstVp8EncoderClass parent_class; + GstV4l2CodecDevice *device; +}; + +GType gst_v4l2_codec_vp8_enc_get_type (void); +void gst_v4l2_codec_vp8_enc_register (GstPlugin * plugin, + GstV4l2Encoder * encoder, + GstV4l2CodecDevice * device, + guint rank); + +G_END_DECLS + +#endif /* __GST_V4L2_CODEC_VP8_ENC_H__ */ diff --git a/sys/v4l2codecs/meson.build b/sys/v4l2codecs/meson.build index 53e8923..638c578 100644 --- a/sys/v4l2codecs/meson.build +++ b/sys/v4l2codecs/meson.build @@ -12,6 +12,7 @@ v4l2codecs_sources = [ 'gstv4l2format.c', 'gstv4l2codecalphadecodebin.c', 'gstv4l2encoder.c', + 'gstv4l2codecvp8enc.c', ] libgudev_dep = dependency('gudev-1.0', required: get_option('v4l2codecs')) -- 2.25.1