diff --git a/subprojects/gst-plugins-bad/sys/va/gstvajpegenc.c b/subprojects/gst-plugins-bad/sys/va/gstvajpegenc.c new file mode 100644 index 0000000000..22663823f5 --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/va/gstvajpegenc.c @@ -0,0 +1,1301 @@ +/* GStreamer + * Copyright (C) 2024 Intel Corporation + * Author: He Junyan + * + * 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. + */ + +/** + * SECTION:element-vajpegenc + * @title: vajpegenc + * @short_description: A VA-API based JPEG video encoder + * + * vajpegenc encodes raw video VA surfaces into JPEG bitstreams using + * the installed and chosen [VA-API](https://01.org/linuxmedia/vaapi) + * driver. + * + * The raw video frames in main memory can be imported into VA surfaces. + * + * ## Example launch line + * ``` + * gst-launch-1.0 videotestsrc num-buffers=60 ! timeoverlay ! vajpegenc ! jpegparse ! filesink location=test.mjpeg + * ``` + * + * Since: 1.26 + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstvajpegenc.h" + +#include +#include +#include +#include +#include + +#include "vacompat.h" +#include "gstvabaseenc.h" +#include "gstvaencoder.h" +#include "gstvacaps.h" +#include "gstvaprofile.h" +#include "gstvadisplay_priv.h" +#include "gstvapluginutils.h" + +GST_DEBUG_CATEGORY_STATIC (gst_va_jpegenc_debug); +#define GST_CAT_DEFAULT gst_va_jpegenc_debug + +#define GST_VA_JPEG_ENC(obj) ((GstVaJpegEnc *) obj) +#define GST_VA_JPEG_ENC_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), G_TYPE_FROM_INSTANCE (obj), GstVaJpegEncClass)) +#define GST_VA_JPEG_ENC_CLASS(klass) ((GstVaJpegEncClass *) klass) + +typedef struct _GstVaJpegEnc GstVaJpegEnc; +typedef struct _GstVaJpegEncClass GstVaJpegEncClass; + +enum +{ + PROP_QUALITY = 1, + N_PROPERTIES +}; + +static GParamSpec *properties[N_PROPERTIES]; + +static GstElementClass *parent_class = NULL; + +/* Maximum sizes for common segment (in bytes) */ +#define MAX_APP_HDR_SIZE 20 +#define MAX_FRAME_HDR_SIZE 19 +#define MAX_QUANT_TABLE_SIZE 138 +#define MAX_HUFFMAN_TABLE_SIZE 432 +#define MAX_SCAN_HDR_SIZE 14 + +struct _GstVaJpegEncClass +{ + GstVaBaseEncClass parent_class; +}; + +struct _GstVaJpegEnc +{ + /*< private > */ + GstVaBaseEnc parent; + + /* JPEG fields */ + guint32 quality; + + guint32 packed_headers; + + gint cwidth[GST_VIDEO_MAX_COMPONENTS]; + gint cheight[GST_VIDEO_MAX_COMPONENTS]; + gint h_samp[GST_VIDEO_MAX_COMPONENTS]; + gint v_samp[GST_VIDEO_MAX_COMPONENTS]; + gint h_max_samp; + gint v_max_samp; + guint n_components; + GstJpegQuantTables quant_tables; + GstJpegQuantTables scaled_quant_tables; + gboolean has_quant_tables; + GstJpegHuffmanTables huff_tables; + gboolean has_huff_tables; +}; + +static void +gst_va_jpeg_enc_frame_free (gpointer pframe) +{ + GstVaEncFrame *frame = pframe; + + g_clear_pointer (&frame->picture, gst_va_encode_picture_free); + g_free (frame); +} + +static gboolean +gst_va_jpeg_enc_new_frame (GstVaBaseEnc * base, GstVideoCodecFrame * frame) +{ + GstVaEncFrame *frame_in; + + frame_in = g_new0 (GstVaEncFrame, 1); + gst_va_set_enc_frame (frame, frame_in, gst_va_jpeg_enc_frame_free); + + return TRUE; +} + +static inline GstVaEncFrame * +_enc_frame (GstVideoCodecFrame * frame) +{ + GstVaEncFrame *enc_frame = gst_video_codec_frame_get_user_data (frame); + g_assert (enc_frame); + + return enc_frame; +} + +static gboolean +_ensure_profile (GstVaJpegEnc * self) +{ + GstVaBaseEnc *base = GST_VA_BASE_ENC (self); + + if (!gst_va_encoder_has_profile (base->encoder, VAProfileJPEGBaseline)) { + GST_ERROR_OBJECT (self, "No jpeg profile found"); + return FALSE; + } + + return TRUE; +} + +static void +_jpeg_generate_sampling_factors (GstVaJpegEnc * self) +{ + GstVaBaseEnc *base = GST_VA_BASE_ENC (self); + const GstVideoInfo *vinfo; + gint i; + + vinfo = &base->in_info; + + self->n_components = GST_VIDEO_INFO_N_COMPONENTS (vinfo); + + self->h_max_samp = 0; + self->v_max_samp = 0; + for (i = 0; i < self->n_components; ++i) { + self->cwidth[i] = GST_VIDEO_INFO_COMP_WIDTH (vinfo, i); + self->cheight[i] = GST_VIDEO_INFO_COMP_HEIGHT (vinfo, i); + self->h_samp[i] = + GST_ROUND_UP_4 (GST_VIDEO_INFO_WIDTH (vinfo)) / self->cwidth[i]; + self->h_max_samp = MAX (self->h_max_samp, self->h_samp[i]); + self->v_samp[i] = + GST_ROUND_UP_4 (GST_VIDEO_INFO_HEIGHT (vinfo)) / self->cheight[i]; + self->v_max_samp = MAX (self->v_max_samp, self->v_samp[i]); + } + /* samp should only be 1, 2 or 4 */ + g_assert (self->h_max_samp <= 4); + g_assert (self->v_max_samp <= 4); + + /* now invert */ + /* maximum is invariant, as one of the components should have samp 1 */ + for (i = 0; i < self->n_components; ++i) { + self->h_samp[i] = self->h_max_samp / self->h_samp[i]; + self->v_samp[i] = self->v_max_samp / self->v_samp[i]; + GST_DEBUG_OBJECT (self, "sampling factors: %d %d", self->h_samp[i], + self->v_samp[i]); + } +} + +static void +_jpeg_calculate_coded_size (GstVaJpegEnc * self) +{ + GstVaBaseEnc *base = GST_VA_BASE_ENC (self); + guint codedbuf_size = 0; + + /* Just set a conservative size */ + codedbuf_size = GST_ROUND_UP_16 (base->width) * + GST_ROUND_UP_16 (base->height) * 3; + + codedbuf_size += MAX_APP_HDR_SIZE + MAX_FRAME_HDR_SIZE + + MAX_QUANT_TABLE_SIZE + MAX_HUFFMAN_TABLE_SIZE + MAX_SCAN_HDR_SIZE; + + base->codedbuf_size = codedbuf_size; + GST_DEBUG_OBJECT (self, "Calculate codedbuf size: %u", base->codedbuf_size); +} + +static gboolean +_jpeg_init_packed_headers (GstVaJpegEnc * self) +{ + GstVaBaseEnc *base = GST_VA_BASE_ENC (self); + guint32 packed_headers; + /* JPEG segments info */ + guint32 desired_packed_headers = VA_ENC_PACKED_HEADER_RAW_DATA; + + self->packed_headers = 0; + + if (!gst_va_encoder_get_packed_headers (base->encoder, base->profile, + GST_VA_BASE_ENC_ENTRYPOINT (base), &packed_headers)) + return FALSE; + + if (desired_packed_headers & ~packed_headers) { + GST_INFO_OBJECT (self, "Driver does not support some wanted packed headers " + "(wanted %#x, found %#x)", desired_packed_headers, packed_headers); + } + + self->packed_headers = desired_packed_headers & packed_headers; + + return TRUE; +} + +static gboolean +_jpeg_get_capability_attribute (GstVaJpegEnc * self) +{ + GstVaBaseEnc *base = GST_VA_BASE_ENC (self); + + VAStatus status; + VAConfigAttrib attrib = {.type = VAConfigAttribEncJPEG }; + VAConfigAttribValEncJPEG jpeg_attrib_val; + + status = vaGetConfigAttributes (gst_va_display_get_va_dpy (base->display), + base->profile, GST_VA_BASE_ENC_ENTRYPOINT (base), &attrib, 1); + if (status != VA_STATUS_SUCCESS) { + GST_INFO_OBJECT (self, "Failed to query encoding features: %s", + vaErrorStr (status)); + /* If no such attribute, we just assume that everything is OK. */ + return TRUE; + } + + jpeg_attrib_val.value = attrib.value; + + GST_DEBUG_OBJECT (self, "Get jpeg attribute, arithmatic_coding_mode: %d, " + "progressive_dct_mode: %d, non_interleaved_mode: %d, " + "differential_mode %d, max_num_components %d, max_num_scans %d, " + "max_num_huffman_tables %d, max_num_quantization_tables %d", + jpeg_attrib_val.bits.arithmatic_coding_mode, + jpeg_attrib_val.bits.progressive_dct_mode, + jpeg_attrib_val.bits.non_interleaved_mode, + jpeg_attrib_val.bits.differential_mode, + jpeg_attrib_val.bits.max_num_components, + jpeg_attrib_val.bits.max_num_scans, + jpeg_attrib_val.bits.max_num_huffman_tables, + jpeg_attrib_val.bits.max_num_quantization_tables); + + if (jpeg_attrib_val.bits.arithmatic_coding_mode) { + GST_ERROR_OBJECT (self, "arithmatic_coding_mode is not supported"); + return FALSE; + } + + if (jpeg_attrib_val.bits.progressive_dct_mode) { + GST_ERROR_OBJECT (self, "progressive_dct_mode is not supported"); + return FALSE; + } + + /* It seems that we need to do nothing to switch the + non_interleaved_mode/interleaved_mode in our code, so both + modes are OK for us. */ + + if (jpeg_attrib_val.bits.differential_mode) { + GST_ERROR_OBJECT (self, "differential_mode is not supported"); + return FALSE; + } + + if (jpeg_attrib_val.bits.max_num_huffman_tables < 1) { + GST_ERROR_OBJECT (self, "need at least 1 huffman table."); + return FALSE; + } + + if (jpeg_attrib_val.bits.max_num_quantization_tables < 2) { + GST_ERROR_OBJECT (self, "need at least 2 quantization tables for " + "luma and chroma."); + return FALSE; + } + + return TRUE; +} + +static gboolean +gst_va_jpeg_enc_reconfig (GstVaBaseEnc * base) +{ + GstVaBaseEncClass *klass = GST_VA_BASE_ENC_GET_CLASS (base); + GstVideoEncoder *venc = GST_VIDEO_ENCODER (base); + GstVaJpegEnc *self = GST_VA_JPEG_ENC (base); + GstCaps *out_caps, *reconf_caps = NULL; + GstVideoCodecState *output_state = NULL; + gboolean do_renegotiation = TRUE, do_reopen, need_negotiation; + gint width, height; + GstVideoFormat format, reconf_format = GST_VIDEO_FORMAT_UNKNOWN; + guint rt_format = 0, codedbuf_size, latency_num, + max_surfaces = 0, max_cached_frames; + + width = GST_VIDEO_INFO_WIDTH (&base->in_info); + height = GST_VIDEO_INFO_HEIGHT (&base->in_info); + format = GST_VIDEO_INFO_FORMAT (&base->in_info); + codedbuf_size = base->codedbuf_size; + latency_num = base->preferred_output_delay; + + need_negotiation = + !gst_va_encoder_get_reconstruct_pool_config (base->encoder, &reconf_caps, + &max_surfaces); + if (!need_negotiation && reconf_caps) { + GstVideoInfo vi; + if (!gst_video_info_from_caps (&vi, reconf_caps)) + return FALSE; + reconf_format = GST_VIDEO_INFO_FORMAT (&vi); + } + + rt_format = gst_va_chroma_from_video_format (format); + if (!rt_format) { + GST_ERROR_OBJECT (self, "unrecognized input format."); + return FALSE; + } + + if (!_ensure_profile (self)) + return FALSE; + + /* first check */ + do_reopen = !(base->profile == VAProfileJPEGBaseline + && base->rt_format == rt_format + && format == reconf_format && width == base->width + && height == base->height); + + if (do_reopen && gst_va_encoder_is_open (base->encoder)) + gst_va_encoder_close (base->encoder); + + gst_va_base_enc_reset_state (base); + + if (base->is_live) { + base->preferred_output_delay = 0; + } else { + /* FIXME: An experience value for most of the platforms. */ + base->preferred_output_delay = 4; + } + + base->profile = VAProfileJPEGBaseline; + base->rt_format = rt_format; + base->width = width; + base->height = height; + GST_DEBUG_OBJECT (self, "resolution: %dx%d", base->width, base->height); + + _jpeg_generate_sampling_factors (self); + + _jpeg_calculate_coded_size (self); + + if (!_jpeg_init_packed_headers (self)) + return FALSE; + + /* Let the downstream know the new latency. */ + if (latency_num != base->preferred_output_delay) { + need_negotiation = TRUE; + latency_num = base->preferred_output_delay; + } + + /* Unknown frame rate is allowed for jpeg, such as a single still image. */ + if (GST_VIDEO_INFO_FPS_N (&base->in_info) == 0 + || GST_VIDEO_INFO_FPS_D (&base->in_info) == 0) { + GST_DEBUG_OBJECT (self, "Unknown framerate"); + GST_VIDEO_INFO_FPS_N (&base->in_info) = 0; + GST_VIDEO_INFO_FPS_D (&base->in_info) = 1; + base->frame_duration = GST_CLOCK_TIME_NONE; + } else { + GstClockTime latency; + + base->frame_duration = gst_util_uint64_scale (GST_SECOND, + GST_VIDEO_INFO_FPS_D (&base->in_info), + GST_VIDEO_INFO_FPS_N (&base->in_info)); + GST_DEBUG_OBJECT (self, "frame duration is %" GST_TIME_FORMAT, + GST_TIME_ARGS (base->frame_duration)); + + /* Set the latency */ + latency = gst_util_uint64_scale (latency_num, + GST_VIDEO_INFO_FPS_D (&base->input_state->info) * GST_SECOND, + GST_VIDEO_INFO_FPS_N (&base->input_state->info)); + gst_video_encoder_set_latency (venc, latency, latency); + } + + max_cached_frames = base->preferred_output_delay; + base->min_buffers = max_cached_frames; + max_cached_frames += 3 /* scratch frames */ ; + + /* second check after calculations */ + do_reopen |= !(max_cached_frames == max_surfaces && + codedbuf_size == base->codedbuf_size); + if (do_reopen && gst_va_encoder_is_open (base->encoder)) + gst_va_encoder_close (base->encoder); + + /* Just use driver's capability attribute, we do not change them. */ + if (!_jpeg_get_capability_attribute (self)) { + GST_ERROR_OBJECT (self, "Failed to satisfy the jpeg capability."); + return FALSE; + } + + if (!gst_va_encoder_is_open (base->encoder) + && !gst_va_encoder_open (base->encoder, base->profile, format, + base->rt_format, base->width, base->height, base->codedbuf_size, + 1, VA_RC_NONE, self->packed_headers)) { + GST_ERROR_OBJECT (self, "Failed to open the VA encoder."); + return FALSE; + } + + /* Add some tags */ + gst_va_base_enc_add_codec_tag (base, "JPEG"); + + out_caps = gst_va_profile_caps (base->profile, klass->entrypoint); + g_assert (out_caps); + out_caps = gst_caps_fixate (out_caps); + + gst_caps_set_simple (out_caps, "width", G_TYPE_INT, base->width, + "height", G_TYPE_INT, base->height, NULL); + + if (!need_negotiation) { + output_state = gst_video_encoder_get_output_state (venc); + do_renegotiation = TRUE; + if (output_state) { + do_renegotiation = !gst_caps_is_subset (output_state->caps, out_caps); + gst_video_codec_state_unref (output_state); + } + if (!do_renegotiation) { + gst_caps_unref (out_caps); + return TRUE; + } + } + + GST_DEBUG_OBJECT (self, "output caps is %" GST_PTR_FORMAT, out_caps); + + output_state = + gst_video_encoder_set_output_state (venc, out_caps, base->input_state); + gst_video_codec_state_unref (output_state); + + if (!gst_video_encoder_negotiate (venc)) { + GST_ERROR_OBJECT (self, "Failed to negotiate with the downstream"); + return FALSE; + } + + return TRUE; +} + +static gboolean +gst_va_jpeg_enc_reorder_frame (GstVaBaseEnc * base, GstVideoCodecFrame * frame, + gboolean bump_all, GstVideoCodecFrame ** out_frame) +{ + *out_frame = frame; + return TRUE; +} + +static void +gst_va_jpeg_enc_reset_state (GstVaBaseEnc * base) +{ + GstVaJpegEnc *self = GST_VA_JPEG_ENC (base); + + GST_VA_BASE_ENC_CLASS (parent_class)->reset_state (base); + + self->packed_headers = 0; + memset (self->cwidth, 0, sizeof (self->cwidth)); + memset (self->cheight, 0, sizeof (self->cheight)); + memset (self->h_samp, 0, sizeof (self->h_samp)); + memset (self->v_samp, 0, sizeof (self->v_samp)); + self->h_max_samp = 0; + self->v_max_samp = 0; + self->n_components = 0; + memset (&self->quant_tables, 0, sizeof (GstJpegQuantTables)); + memset (&self->scaled_quant_tables, 0, sizeof (GstJpegQuantTables)); + self->has_quant_tables = FALSE; + memset (&self->huff_tables, 0, sizeof (GstJpegHuffmanTables)); + self->has_huff_tables = FALSE; +} + +static void +_jpeg_fill_picture (GstVaJpegEnc * self, GstVaEncFrame * frame, + VAEncPictureParameterBufferJPEG * pic_param, guint32 quality) +{ + GstVaBaseEnc *base = GST_VA_BASE_ENC (self); + guint i; + + /* *INDENT-OFF* */ + *pic_param = (VAEncPictureParameterBufferJPEG) { + .reconstructed_picture = + gst_va_encode_picture_get_reconstruct_surface (frame->picture), + .picture_width = base->width, + .picture_height = base->height, + .coded_buf = frame->picture->coded_buffer, + /* Profile = Baseline */ + .pic_flags.bits.profile = 0, + /* Sequential encoding */ + .pic_flags.bits.progressive = 0, + /* Uses Huffman coding */ + .pic_flags.bits.huffman = 1, + /* Input format is non interleaved (YUV) */ + .pic_flags.bits.interleaved = 0, + /* non-Differential Encoding */ + .pic_flags.bits.differential = 0, + .sample_bit_depth = 8, + .num_scan = 1, + .num_components = self->n_components, + .quality = quality, + }; + /* *INDENT-ON* */ + + for (i = 0; i < pic_param->num_components; i++) { + pic_param->component_id[i] = i + 1; + if (i != 0) + pic_param->quantiser_table_selector[i] = 1; + } +} + +static gboolean +_jpeg_add_picture_parameter (GstVaJpegEnc * self, GstVaEncFrame * frame, + VAEncPictureParameterBufferJPEG * pic_param) +{ + GstVaBaseEnc *base = GST_VA_BASE_ENC (self); + + if (!gst_va_encoder_add_param (base->encoder, frame->picture, + VAEncPictureParameterBufferType, pic_param, + sizeof (VAEncPictureParameterBufferJPEG))) { + GST_ERROR_OBJECT (self, "Failed to create the picture parameter"); + return FALSE; + } + + return TRUE; +} + +/* Normalize the quality factor and scale QM values. */ +static void +_jpeg_generate_scaled_qm (GstJpegQuantTables * quant_tables, + GstJpegQuantTables * scaled_quant_tables, guint quality, guint shift) +{ + guint qt_val, nm_quality, i; + + nm_quality = quality == 0 ? 1 : quality; + nm_quality = + (nm_quality < 50) ? (5000 / nm_quality) : (200 - (nm_quality * 2)); + + g_assert (quant_tables != NULL); + g_assert (scaled_quant_tables != NULL); + + scaled_quant_tables->quant_tables[0].quant_precision = + quant_tables->quant_tables[0].quant_precision; + scaled_quant_tables->quant_tables[0].valid = + quant_tables->quant_tables[0].valid; + scaled_quant_tables->quant_tables[1].quant_precision = + quant_tables->quant_tables[1].quant_precision; + scaled_quant_tables->quant_tables[1].valid = + quant_tables->quant_tables[1].valid; + + for (i = 0; i < GST_JPEG_MAX_QUANT_ELEMENTS; i++) { + /* Luma QM */ + qt_val = + (quant_tables->quant_tables[0].quant_table[i] * nm_quality + + shift) / 100; + scaled_quant_tables->quant_tables[0].quant_table[i] = + CLAMP (qt_val, 1, 255); + /* Chroma QM */ + qt_val = + (quant_tables->quant_tables[1].quant_table[i] * nm_quality + + shift) / 100; + scaled_quant_tables->quant_tables[1].quant_table[i] = + CLAMP (qt_val, 1, 255); + } +} + +static void +_jpeg_fill_quantization_table (GstVaJpegEnc * self, + VAQMatrixBufferJPEG * q_matrix, guint32 quality) +{ + GstVaBaseEnc *base = GST_VA_BASE_ENC (self); + guint i; + + if (!self->has_quant_tables) { + guint shift = 0; + + if (gst_va_display_is_implementation (base->display, + GST_VA_IMPLEMENTATION_INTEL_IHD)) + shift = 50; + + gst_jpeg_get_default_quantization_tables (&self->quant_tables); + /* Just use table 0 and 1 */ + self->quant_tables.quant_tables[2].valid = FALSE; + self->quant_tables.quant_tables[3].valid = FALSE; + + _jpeg_generate_scaled_qm (&self->quant_tables, + &self->scaled_quant_tables, quality, shift); + + self->has_quant_tables = TRUE; + } + + q_matrix->load_lum_quantiser_matrix = 1; + for (i = 0; i < GST_JPEG_MAX_QUANT_ELEMENTS; i++) { + q_matrix->lum_quantiser_matrix[i] = + self->quant_tables.quant_tables[0].quant_table[i]; + } + + q_matrix->load_chroma_quantiser_matrix = 1; + for (i = 0; i < GST_JPEG_MAX_QUANT_ELEMENTS; i++) { + q_matrix->chroma_quantiser_matrix[i] = + self->quant_tables.quant_tables[1].quant_table[i]; + } +} + +static gboolean +_jpeg_add_quantization_table (GstVaJpegEnc * self, GstVaEncFrame * frame, + VAQMatrixBufferJPEG * q_matrix) +{ + GstVaBaseEnc *base = GST_VA_BASE_ENC (self); + + if (!gst_va_encoder_add_param (base->encoder, frame->picture, + VAQMatrixBufferType, q_matrix, sizeof (VAQMatrixBufferJPEG))) { + GST_ERROR_OBJECT (self, "Failed to create the quantization table"); + return FALSE; + } + + return TRUE; +} + +static void +_jpeg_fill_huffman_table (GstVaJpegEnc * self, + VAHuffmanTableBufferJPEGBaseline * huffman_table) +{ + guint i, num_tables; + + num_tables = MIN (G_N_ELEMENTS (huffman_table->huffman_table), + GST_JPEG_MAX_SCAN_COMPONENTS); + + if (!self->has_huff_tables) { + gst_jpeg_get_default_huffman_tables (&self->huff_tables); + self->has_huff_tables = TRUE; + } + + for (i = 0; i < num_tables; i++) { + huffman_table->load_huffman_table[i] = + self->huff_tables.dc_tables[i].valid + && self->huff_tables.ac_tables[i].valid; + if (!huffman_table->load_huffman_table[i]) + continue; + + memcpy (huffman_table->huffman_table[i].num_dc_codes, + self->huff_tables.dc_tables[i].huf_bits, + sizeof (huffman_table->huffman_table[i].num_dc_codes)); + memcpy (huffman_table->huffman_table[i].dc_values, + self->huff_tables.dc_tables[i].huf_values, + sizeof (huffman_table->huffman_table[i].dc_values)); + memcpy (huffman_table->huffman_table[i].num_ac_codes, + self->huff_tables.ac_tables[i].huf_bits, + sizeof (huffman_table->huffman_table[i].num_ac_codes)); + memcpy (huffman_table->huffman_table[i].ac_values, + self->huff_tables.ac_tables[i].huf_values, + sizeof (huffman_table->huffman_table[i].ac_values)); + memset (huffman_table->huffman_table[i].pad, + 0, sizeof (huffman_table->huffman_table[i].pad)); + } +} + +static gboolean +_jpeg_add_huffman_table (GstVaJpegEnc * self, GstVaEncFrame * frame, + VAHuffmanTableBufferJPEGBaseline * huffman_table) +{ + GstVaBaseEnc *base = GST_VA_BASE_ENC (self); + + if (!gst_va_encoder_add_param (base->encoder, frame->picture, + VAHuffmanTableBufferType, huffman_table, + sizeof (VAHuffmanTableBufferJPEGBaseline))) { + GST_ERROR_OBJECT (self, "Failed to create the huffman table"); + return FALSE; + } + + return TRUE; +} + +static void +_jpeg_fill_slice (GstVaJpegEnc * self, + VAEncPictureParameterBufferJPEG * pic_param, + VAEncSliceParameterBufferJPEG * slice_param) +{ + /* *INDENT-OFF* */ + *slice_param = (VAEncSliceParameterBufferJPEG) { + .restart_interval = 0, + .num_components = pic_param->num_components, + .components[0].component_selector = 1, + .components[0].dc_table_selector = 0, + .components[0].ac_table_selector = 0, + .components[1].component_selector = 2, + .components[1].dc_table_selector = 1, + .components[1].ac_table_selector = 1, + .components[2].component_selector = 3, + .components[2].dc_table_selector = 1, + .components[2].ac_table_selector = 1, + }; + /* *INDENT-ON* */ +} + +static gboolean +_jpeg_add_slice_parameter (GstVaJpegEnc * self, GstVaEncFrame * frame, + VAEncSliceParameterBufferJPEG * slice_param) +{ + GstVaBaseEnc *base = GST_VA_BASE_ENC (self); + + if (!gst_va_encoder_add_param (base->encoder, frame->picture, + VAEncSliceParameterBufferType, slice_param, + sizeof (VAEncSliceParameterBufferJPEG))) { + GST_ERROR_OBJECT (self, "Failed to create the slice parameter"); + return FALSE; + } + + return TRUE; +} + +static gboolean +_jpeg_create_and_add_packed_segments (GstVaJpegEnc * self, + GstVaEncFrame * frame, VAEncPictureParameterBufferJPEG * pic_param, + VAEncSliceParameterBufferJPEG * slice_param) +{ + GstVaBaseEnc *base = GST_VA_BASE_ENC (self); + GstJpegBitWriterResult writer_res; + guint8 data[2048] = { 0, }; + guint8 app_data[14] = { + 0x4A /* J */ , + 0x46 /* F */ , + 0x49 /* I */ , + 0x46 /* F */ , + 0x00 /* 0 */ , + 0x01 /* Major Version */ , + 0x02 /* Minor Version */ , + 0x00 /* Density units 0:no units, 1:pixels per inch, 2: pixels per cm */ , + 0x00, 0x01 /* X density (pixel-aspect-ratio) */ , + 0x00, 0x01 /* Y density (pixel-aspect-ratio) */ , + 0x00 /* Thumbnail width */ , + 0x00 /* Thumbnail height */ , + }; + GstJpegFrameHdr frame_hdr; + GstJpegScanHdr scan_hdr = { 0, }; + guint i; + guint size, offset; + + /* SOI */ + offset = 0; + size = sizeof (data); + writer_res = gst_jpeg_bit_writer_segment_with_data (GST_JPEG_MARKER_SOI, + NULL, 0, data, &size); + if (writer_res != GST_JPEG_BIT_WRITER_OK) + return FALSE; + + /* APP0 */ + offset += size; + size = sizeof (data) - offset; + writer_res = gst_jpeg_bit_writer_segment_with_data (GST_JPEG_MARKER_APP_MIN, + app_data, sizeof (app_data), data + offset, &size); + if (writer_res != GST_JPEG_BIT_WRITER_OK) + return FALSE; + + /* Quantization tables */ + g_assert (self->has_quant_tables); + offset += size; + size = sizeof (data) - offset; + writer_res = + gst_jpeg_bit_writer_quantization_table (&self->scaled_quant_tables, + data + offset, &size); + if (writer_res != GST_JPEG_BIT_WRITER_OK) + return FALSE; + + /* SOF */ + /* *INDENT-OFF* */ + frame_hdr = (GstJpegFrameHdr) { + .sample_precision = 8, + .width = pic_param->picture_width, + .height = pic_param->picture_height, + .num_components = pic_param->num_components, + }; + /* *INDENT-ON* */ + for (i = 0; i < frame_hdr.num_components; i++) { + frame_hdr.components[i].identifier = pic_param->component_id[i]; + frame_hdr.components[i].horizontal_factor = self->h_samp[i]; + frame_hdr.components[i].vertical_factor = self->v_samp[i]; + frame_hdr.components[i].quant_table_selector = + pic_param->quantiser_table_selector[i]; + }; + + offset += size; + size = sizeof (data) - offset; + writer_res = gst_jpeg_bit_writer_frame_header (&frame_hdr, + GST_JPEG_MARKER_SOF_MIN, data + offset, &size); + if (writer_res != GST_JPEG_BIT_WRITER_OK) + return FALSE; + + /* huffman tables */ + g_assert (self->has_huff_tables); + offset += size; + size = sizeof (data) - offset; + writer_res = gst_jpeg_bit_writer_huffman_table (&self->huff_tables, + data + offset, &size); + if (writer_res != GST_JPEG_BIT_WRITER_OK) + return FALSE; + + /* Scan header */ + scan_hdr.num_components = slice_param->num_components; + for (i = 0; i < frame_hdr.num_components; i++) { + scan_hdr.components[i].component_selector = + slice_param->components[i].component_selector; + scan_hdr.components[i].dc_selector = + slice_param->components[i].dc_table_selector; + scan_hdr.components[i].ac_selector = + slice_param->components[i].ac_table_selector; + } + + offset += size; + size = sizeof (data) - offset; + writer_res = gst_jpeg_bit_writer_scan_header (&scan_hdr, + data + offset, &size); + if (writer_res != GST_JPEG_BIT_WRITER_OK) + return FALSE; + + offset += size; + + if (!gst_va_encoder_add_packed_header (base->encoder, frame->picture, + VAEncPackedHeaderRawData, data, offset * 8, FALSE)) { + GST_ERROR_OBJECT (self, "Failed to add packed segment data"); + return FALSE; + } + + return TRUE; +} + +static gboolean +_jpeg_encode_one_frame (GstVaJpegEnc * self, GstVideoCodecFrame * gst_frame) +{ + GstVaBaseEnc *base = GST_VA_BASE_ENC (self); + GstVaEncFrame *frame; + VAEncPictureParameterBufferJPEG pic_param; + VAQMatrixBufferJPEG q_matrix; + VAHuffmanTableBufferJPEGBaseline huffman_table; + VAEncSliceParameterBufferJPEG slice_param; + guint32 quality; + + g_return_val_if_fail (gst_frame, FALSE); + + frame = _enc_frame (gst_frame); + + GST_OBJECT_LOCK (self); + quality = self->quality; + GST_OBJECT_UNLOCK (self); + + _jpeg_fill_quantization_table (self, &q_matrix, quality); + if (!_jpeg_add_quantization_table (self, frame, &q_matrix)) + return FALSE; + + _jpeg_fill_huffman_table (self, &huffman_table); + if (!_jpeg_add_huffman_table (self, frame, &huffman_table)) + return FALSE; + + _jpeg_fill_picture (self, frame, &pic_param, quality); + if (!_jpeg_add_picture_parameter (self, frame, &pic_param)) + return FALSE; + + _jpeg_fill_slice (self, &pic_param, &slice_param); + if (!_jpeg_add_slice_parameter (self, frame, &slice_param)) + return FALSE; + + if (!_jpeg_create_and_add_packed_segments (self, frame, &pic_param, + &slice_param)) { + GST_ERROR_OBJECT (self, "Failed to create packed segments"); + return FALSE; + } + + if (!gst_va_encoder_encode (base->encoder, frame->picture)) { + GST_ERROR_OBJECT (self, "Encode frame error"); + return FALSE; + } + + return TRUE; +} + +static GstFlowReturn +gst_va_jpeg_enc_encode_frame (GstVaBaseEnc * base, + GstVideoCodecFrame * gst_frame, gboolean is_last) +{ + GstVaJpegEnc *self = GST_VA_JPEG_ENC (base); + GstVaEncFrame *frame; + + frame = _enc_frame (gst_frame); + + g_assert (frame->picture == NULL); + frame->picture = gst_va_encode_picture_new (base->encoder, + gst_frame->input_buffer); + + if (!frame->picture) { + GST_ERROR_OBJECT (self, "Failed to create the encode picture"); + return GST_FLOW_ERROR; + } + + if (!_jpeg_encode_one_frame (self, gst_frame)) { + GST_ERROR_OBJECT (self, "Failed to encode the frame"); + return GST_FLOW_ERROR; + } + + g_queue_push_tail (&base->output_list, gst_video_codec_frame_ref (gst_frame)); + + return GST_FLOW_OK; +} + +static gboolean +gst_va_jpeg_enc_prepare_output (GstVaBaseEnc * base, + GstVideoCodecFrame * frame, gboolean * complete) +{ + GstVaEncFrame *frame_enc; + GstBuffer *buf; + + frame_enc = _enc_frame (frame); + + buf = gst_va_base_enc_create_output_buffer (base, + frame_enc->picture, NULL, 0); + if (!buf) { + GST_ERROR_OBJECT (base, "Failed to create output buffer"); + return FALSE; + } + + GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_MARKER); + GST_VIDEO_CODEC_FRAME_SET_SYNC_POINT (frame); + GST_BUFFER_FLAG_UNSET (buf, GST_BUFFER_FLAG_DELTA_UNIT); + + gst_buffer_replace (&frame->output_buffer, buf); + gst_clear_buffer (&buf); + + *complete = TRUE; + return TRUE; +} + +/* *INDENT-OFF* */ +static const gchar *sink_caps_str = + GST_VIDEO_CAPS_MAKE_WITH_FEATURES (GST_CAPS_FEATURE_MEMORY_VA, + "{ NV12 }") " ;" + GST_VIDEO_CAPS_MAKE ("{ NV12 }"); +/* *INDENT-ON* */ + +static const gchar *src_caps_str = "image/jpeg"; + +static gpointer +_register_debug_category (gpointer data) +{ + GST_DEBUG_CATEGORY_INIT (gst_va_jpegenc_debug, "vajpegenc", 0, + "VA jpeg encoder"); + + return NULL; +} + +static void +gst_va_jpeg_enc_init (GTypeInstance * instance, gpointer g_class) +{ + GstVaJpegEnc *self = GST_VA_JPEG_ENC (instance); + + self->quality = 50; +} + +static void +gst_va_jpeg_enc_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstVaJpegEnc *self = GST_VA_JPEG_ENC (object); + + GST_OBJECT_LOCK (self); + + switch (prop_id) { + case PROP_QUALITY: + self->quality = g_value_get_uint (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } + + GST_OBJECT_UNLOCK (self); +} + +static void +gst_va_jpeg_enc_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstVaJpegEnc *const self = GST_VA_JPEG_ENC (object); + + GST_OBJECT_LOCK (self); + + switch (prop_id) { + case PROP_QUALITY: + g_value_set_uint (value, self->quality); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } + + GST_OBJECT_UNLOCK (self); +} + +static void +gst_va_jpeg_enc_class_init (gpointer g_klass, gpointer class_data) +{ + GstCaps *src_doc_caps, *sink_doc_caps; + GstPadTemplate *sink_pad_templ, *src_pad_templ; + GObjectClass *object_class = G_OBJECT_CLASS (g_klass); + GstElementClass *element_class = GST_ELEMENT_CLASS (g_klass); + GstVaBaseEncClass *va_enc_class = GST_VA_BASE_ENC_CLASS (g_klass); + struct CData *cdata = class_data; + gchar *long_name; + const gchar *name, *desc; + gint n_props = N_PROPERTIES; + + desc = "VA-API based JPEG video encoder"; + name = "VA-API JPEG Encoder"; + + if (cdata->description) + long_name = g_strdup_printf ("%s in %s", name, cdata->description); + else + long_name = g_strdup (name); + + gst_element_class_set_metadata (element_class, long_name, + "Codec/Encoder/Video/Hardware", desc, "He Junyan "); + + sink_doc_caps = gst_caps_from_string (sink_caps_str); + src_doc_caps = gst_caps_from_string (src_caps_str); + + parent_class = g_type_class_peek_parent (g_klass); + + va_enc_class->codec = JPEG; + va_enc_class->entrypoint = cdata->entrypoint; + va_enc_class->render_device_path = g_strdup (cdata->render_device_path); + + sink_pad_templ = gst_pad_template_new ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, + cdata->sink_caps); + gst_element_class_add_pad_template (element_class, sink_pad_templ); + + gst_pad_template_set_documentation_caps (sink_pad_templ, sink_doc_caps); + gst_caps_unref (sink_doc_caps); + + src_pad_templ = gst_pad_template_new ("src", GST_PAD_SRC, GST_PAD_ALWAYS, + cdata->src_caps); + gst_element_class_add_pad_template (element_class, src_pad_templ); + + gst_pad_template_set_documentation_caps (src_pad_templ, src_doc_caps); + gst_caps_unref (src_doc_caps); + + object_class->set_property = gst_va_jpeg_enc_set_property; + object_class->get_property = gst_va_jpeg_enc_get_property; + + va_enc_class->reconfig = GST_DEBUG_FUNCPTR (gst_va_jpeg_enc_reconfig); + va_enc_class->reset_state = GST_DEBUG_FUNCPTR (gst_va_jpeg_enc_reset_state); + va_enc_class->reorder_frame = + GST_DEBUG_FUNCPTR (gst_va_jpeg_enc_reorder_frame); + va_enc_class->new_frame = GST_DEBUG_FUNCPTR (gst_va_jpeg_enc_new_frame); + va_enc_class->encode_frame = GST_DEBUG_FUNCPTR (gst_va_jpeg_enc_encode_frame); + va_enc_class->prepare_output = + GST_DEBUG_FUNCPTR (gst_va_jpeg_enc_prepare_output); + + g_free (long_name); + g_free (cdata->description); + g_free (cdata->render_device_path); + gst_caps_unref (cdata->src_caps); + gst_caps_unref (cdata->sink_caps); + g_free (cdata); + + /** + * GstVaJpegEnc:quality: + * + * Quality factor. + */ + properties[PROP_QUALITY] = g_param_spec_uint ("quality", + "Quality factor", "Quality factor for encoding", 0, 100, 50, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT); + + g_object_class_install_properties (object_class, n_props, properties); +} + +static gboolean +_is_supported_format (GstVideoFormat gst_format) +{ + guint chroma; + + /* Only support depth == 8 */ + chroma = gst_va_chroma_from_video_format (gst_format); + if (chroma >= VA_RT_FORMAT_YUV420 && chroma <= VA_RT_FORMAT_YUV400) + return TRUE; + + /* And the special RGB case */ + if (chroma == VA_RT_FORMAT_RGB32) + return TRUE; + + return FALSE; +} + +static void +_generate_supported_formats (GPtrArray * supported_formats, + GValue * supported_value) +{ + guint i; + + if (supported_formats->len == 1) { + g_value_init (supported_value, G_TYPE_STRING); + g_value_set_string (supported_value, + g_ptr_array_index (supported_formats, 0)); + } else { + GValue item = G_VALUE_INIT; + + gst_value_list_init (supported_value, supported_formats->len); + + for (i = 0; i < supported_formats->len; i++) { + g_value_init (&item, G_TYPE_STRING); + g_value_set_string (&item, g_ptr_array_index (supported_formats, i)); + gst_value_list_append_value (supported_value, &item); + g_value_unset (&item); + } + } +} + +static GstCaps * +_filter_sink_caps (GstCaps * sinkcaps) +{ + GPtrArray *supported_formats; + const gchar *format_str; + GstVideoFormat gst_format; + const GValue *val; + GValue supported_value = G_VALUE_INIT; + GstCaps *ret; + guint num_structures; + guint i, j; + + supported_formats = g_ptr_array_new (); + ret = gst_caps_new_empty (); + + num_structures = gst_caps_get_size (sinkcaps); + + for (i = 0; i < num_structures; i++) { + GstStructure *st; + GstCapsFeatures *features; + + g_ptr_array_set_size (supported_formats, 0); + + st = gst_caps_get_structure (sinkcaps, i); + st = gst_structure_copy (st); + + features = gst_caps_get_features (sinkcaps, i); + + if (gst_caps_features_contains (features, GST_CAPS_FEATURE_MEMORY_DMABUF)) { + guint32 fourcc; + + val = gst_structure_get_value (st, "drm-format"); + if (!val) + continue; + + if (G_VALUE_HOLDS_STRING (val)) { + format_str = g_value_get_string (val); + fourcc = gst_video_dma_drm_fourcc_from_string (format_str, NULL); + gst_format = gst_va_video_format_from_drm_fourcc (fourcc); + + if (_is_supported_format (gst_format)) + g_ptr_array_add (supported_formats, (gpointer) format_str); + } else if (GST_VALUE_HOLDS_LIST (val)) { + guint num_values = gst_value_list_get_size (val); + + for (j = 0; j < num_values; j++) { + const GValue *v = gst_value_list_get_value (val, j); + + format_str = g_value_get_string (v); + fourcc = gst_video_dma_drm_fourcc_from_string (format_str, NULL); + gst_format = gst_va_video_format_from_drm_fourcc (fourcc); + + if (_is_supported_format (gst_format)) + g_ptr_array_add (supported_formats, (gpointer) format_str); + } + } + + if (!supported_formats->len) { + gst_structure_free (st); + continue; + } + + _generate_supported_formats (supported_formats, &supported_value); + gst_structure_take_value (st, "drm-format", &supported_value); + + gst_caps_append_structure_full (ret, st, + gst_caps_features_copy (features)); + } else { + val = gst_structure_get_value (st, "format"); + if (!val) + continue; + + if (G_VALUE_HOLDS_STRING (val)) { + format_str = g_value_get_string (val); + gst_format = gst_video_format_from_string (format_str); + + if (_is_supported_format (gst_format)) + g_ptr_array_add (supported_formats, (gpointer) format_str); + } else if (GST_VALUE_HOLDS_LIST (val)) { + guint num_values = gst_value_list_get_size (val); + + for (j = 0; j < num_values; j++) { + const GValue *v = gst_value_list_get_value (val, j); + + format_str = g_value_get_string (v); + gst_format = gst_video_format_from_string (format_str); + + if (_is_supported_format (gst_format)) + g_ptr_array_add (supported_formats, (gpointer) format_str); + } + } + + if (!supported_formats->len) { + gst_structure_free (st); + continue; + } + + _generate_supported_formats (supported_formats, &supported_value); + gst_structure_take_value (st, "format", &supported_value); + + gst_caps_append_structure_full (ret, st, + gst_caps_features_copy (features)); + } + } + + g_ptr_array_unref (supported_formats); + + if (gst_caps_is_empty (ret)) { + gst_caps_unref (ret); + ret = NULL; + } + + return ret; +} + + +gboolean +gst_va_jpeg_enc_register (GstPlugin * plugin, GstVaDevice * device, + GstCaps * sink_caps, GstCaps * src_caps, guint rank, + VAEntrypoint entrypoint) +{ + static GOnce debug_once = G_ONCE_INIT; + GType type; + GTypeInfo type_info = { + .class_size = sizeof (GstVaJpegEncClass), + .class_init = gst_va_jpeg_enc_class_init, + .instance_size = sizeof (GstVaJpegEnc), + .instance_init = gst_va_jpeg_enc_init, + }; + struct CData *cdata; + gboolean ret; + gchar *type_name, *feature_name; + + g_return_val_if_fail (GST_IS_PLUGIN (plugin), FALSE); + g_return_val_if_fail (GST_IS_VA_DEVICE (device), FALSE); + g_return_val_if_fail (GST_IS_CAPS (sink_caps), FALSE); + g_return_val_if_fail (GST_IS_CAPS (src_caps), FALSE); + g_return_val_if_fail (entrypoint == VAEntrypointEncPicture, FALSE); + + sink_caps = _filter_sink_caps (sink_caps); + + cdata = g_new (struct CData, 1); + cdata->entrypoint = entrypoint; + cdata->description = NULL; + cdata->render_device_path = g_strdup (device->render_device_path); + cdata->sink_caps = sink_caps; + cdata->src_caps = gst_caps_ref (src_caps); + + /* class data will be leaked if the element never gets instantiated */ + GST_MINI_OBJECT_FLAG_SET (cdata->sink_caps, + GST_MINI_OBJECT_FLAG_MAY_BE_LEAKED); + GST_MINI_OBJECT_FLAG_SET (cdata->src_caps, + GST_MINI_OBJECT_FLAG_MAY_BE_LEAKED); + + type_info.class_data = cdata; + gst_va_create_feature_name (device, "GstVaJpegEnc", "GstVa%sJpegEnc", + &type_name, "vajpegenc", "va%sjpegenc", &feature_name, + &cdata->description, &rank); + + g_once (&debug_once, _register_debug_category, NULL); + type = g_type_register_static (GST_TYPE_VA_BASE_ENC, + type_name, &type_info, 0); + ret = gst_element_register (plugin, feature_name, rank, type); + + g_free (type_name); + g_free (feature_name); + + return ret; +} diff --git a/subprojects/gst-plugins-bad/sys/va/gstvajpegenc.h b/subprojects/gst-plugins-bad/sys/va/gstvajpegenc.h new file mode 100644 index 0000000000..952db86e10 --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/va/gstvajpegenc.h @@ -0,0 +1,34 @@ +/* GStreamer + * Copyright (C) 2024 Intel Corporation + * Author: He Junyan + * + * 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. + */ + +#pragma once + +#include "gstvadevice.h" + +G_BEGIN_DECLS + +gboolean gst_va_jpeg_enc_register (GstPlugin * plugin, + GstVaDevice * device, + GstCaps * sink_caps, + GstCaps * src_caps, + guint rank, + VAEntrypoint entrypoint); + +G_END_DECLS diff --git a/subprojects/gst-plugins-bad/sys/va/meson.build b/subprojects/gst-plugins-bad/sys/va/meson.build index ef3958d8be..0f2a262e92 100644 --- a/subprojects/gst-plugins-bad/sys/va/meson.build +++ b/subprojects/gst-plugins-bad/sys/va/meson.build @@ -18,6 +18,7 @@ va_sources = [ 'gstvah265dec.c', 'gstvah265enc.c', 'gstvajpegdec.c', + 'gstvajpegenc.c', 'gstvampeg2dec.c', 'gstvapluginutils.c', 'gstvaprofile.c', diff --git a/subprojects/gst-plugins-bad/sys/va/plugin.c b/subprojects/gst-plugins-bad/sys/va/plugin.c index 5088880c07..5eb65cbea5 100644 --- a/subprojects/gst-plugins-bad/sys/va/plugin.c +++ b/subprojects/gst-plugins-bad/sys/va/plugin.c @@ -39,6 +39,7 @@ #include "gstvah265dec.h" #include "gstvah265enc.h" #include "gstvajpegdec.h" +#include "gstvajpegenc.h" #include "gstvampeg2dec.h" #include "gstvaprofile.h" #include "gstvavp8dec.h" @@ -223,6 +224,13 @@ plugin_register_encoders (GstPlugin * plugin, GstVaDevice * device, device->render_device_path); } break; + case JPEG: + if (!gst_va_jpeg_enc_register (plugin, device, sinkcaps, srccaps, + GST_RANK_NONE, entrypoint)) { + GST_WARNING ("Failed to register JPEG encoder: %s", + device->render_device_path); + } + break; #if VA_CHECK_VERSION(1, 15, 0) case AV1: if (!gst_va_av1_enc_register (plugin, device, sinkcaps, srccaps,