/* * gstvaapiencode.c - VA-API video encoder * * Copyright (C) 2013-2014 Intel Corporation * Author: Wind Yuan * Author: Gwenole Beauchesne * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 * 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA */ #include "gstcompat.h" #include #include #include #include "gstvaapiencode.h" #include "gstvaapipluginutil.h" #include "gstvaapivideometa.h" #include "gstvaapivideomemory.h" #include "gstvaapivideobufferpool.h" #define GST_PLUGIN_NAME "vaapiencode" #define GST_PLUGIN_DESC "A VA-API based video encoder" #define GST_VAAPI_ENCODE_FLOW_TIMEOUT GST_FLOW_CUSTOM_SUCCESS #define GST_VAAPI_ENCODE_FLOW_MEM_ERROR GST_FLOW_CUSTOM_ERROR #define GST_VAAPI_ENCODE_FLOW_CONVERT_ERROR GST_FLOW_CUSTOM_ERROR_1 GST_DEBUG_CATEGORY_STATIC (gst_vaapiencode_debug); #ifndef GST_DISABLE_GST_DEBUG #define GST_CAT_DEFAULT gst_vaapiencode_debug #else #define GST_CAT_DEFAULT NULL #endif G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GstVaapiEncode, gst_vaapiencode, GST_TYPE_VIDEO_ENCODER, GST_VAAPI_PLUGIN_BASE_INIT_INTERFACES); GST_VAAPI_PLUGIN_BASE_DEFINE_SET_CONTEXT (gst_vaapiencode_parent_class); enum { PROP_0, PROP_BASE, }; static inline gboolean ensure_display (GstVaapiEncode * encode) { return gst_vaapi_plugin_base_ensure_display (GST_VAAPI_PLUGIN_BASE (encode)); } static gboolean gst_vaapiencode_sink_query (GstVideoEncoder * encoder, GstQuery * query) { gboolean ret = TRUE; GstElement *const element = GST_ELEMENT (encoder); switch (GST_QUERY_TYPE (query)) { case GST_QUERY_CONTEXT: ret = gst_vaapi_handle_context_query (element, query); break; default: ret = GST_VIDEO_ENCODER_CLASS (gst_vaapiencode_parent_class)->sink_query (encoder, query); break; } return ret; } static gboolean gst_vaapiencode_src_query (GstVideoEncoder * encoder, GstQuery * query) { gboolean ret = TRUE; GstElement *const element = GST_ELEMENT (encoder); switch (GST_QUERY_TYPE (query)) { case GST_QUERY_CONTEXT: ret = gst_vaapi_handle_context_query (element, query); break; default: ret = GST_VIDEO_ENCODER_CLASS (gst_vaapiencode_parent_class)->src_query (encoder, query); break; } return ret; } typedef struct { guint id; GParamSpec *pspec; GValue value; } PropValue; static void prop_value_free (PropValue * prop_value) { if (!prop_value) return; if (G_VALUE_TYPE (&prop_value->value)) g_value_unset (&prop_value->value); if (prop_value->pspec) { g_param_spec_unref (prop_value->pspec); prop_value->pspec = NULL; } g_slice_free (PropValue, prop_value); } static PropValue * prop_value_new_entry (guint id, GParamSpec * pspec, const GValue * value) { PropValue *prop_value; if (!pspec) return NULL; prop_value = g_slice_new0 (PropValue); if (!prop_value) return NULL; prop_value->id = id; prop_value->pspec = g_param_spec_ref (pspec); g_value_init (&prop_value->value, pspec->value_type); g_assert (g_value_type_compatible (pspec->value_type, G_VALUE_TYPE (value))); g_value_copy (value, &prop_value->value); return prop_value; } static inline PropValue * prop_value_lookup_entry (GPtrArray * prop_values, guint prop_id) { guint i; PropValue *prop_value; if (prop_values == NULL) return NULL; for (i = 0; i < prop_values->len; i++) { prop_value = g_ptr_array_index (prop_values, i); if (prop_value->id == prop_id) return prop_value; } return NULL; } static GstFlowReturn gst_vaapiencode_default_alloc_buffer (GstVaapiEncode * encode, GstVaapiCodedBuffer * coded_buf, GstBuffer ** outbuf_ptr) { GstBuffer *buf; gint32 buf_size; g_return_val_if_fail (coded_buf != NULL, GST_FLOW_ERROR); g_return_val_if_fail (outbuf_ptr != NULL, GST_FLOW_ERROR); buf_size = gst_vaapi_coded_buffer_get_size (coded_buf); if (buf_size <= 0) goto error_invalid_buffer; buf = gst_video_encoder_allocate_output_buffer (GST_VIDEO_ENCODER_CAST (encode), buf_size); if (!buf) goto error_create_buffer; if (!gst_vaapi_coded_buffer_copy_into (buf, coded_buf)) goto error_copy_buffer; *outbuf_ptr = buf; return GST_FLOW_OK; /* ERRORS */ error_invalid_buffer: { GST_ERROR ("invalid GstVaapiCodedBuffer size (%d bytes)", buf_size); return GST_VAAPI_ENCODE_FLOW_MEM_ERROR; } error_create_buffer: { GST_ERROR ("failed to create output buffer of size %d", buf_size); return GST_VAAPI_ENCODE_FLOW_MEM_ERROR; } error_copy_buffer: { GST_ERROR ("failed to copy GstVaapiCodedBuffer data"); gst_buffer_unref (buf); return GST_VAAPI_ENCODE_FLOW_MEM_ERROR; } } static gboolean ensure_output_state (GstVaapiEncode * encode) { GstVideoEncoder *const venc = GST_VIDEO_ENCODER_CAST (encode); GstVaapiEncodeClass *const klass = GST_VAAPIENCODE_GET_CLASS (encode); GstVaapiEncoderStatus status; GstCaps *out_caps; if (!encode->input_state_changed) return TRUE; out_caps = klass->get_caps (encode); if (!out_caps) return FALSE; if (encode->output_state) gst_video_codec_state_unref (encode->output_state); encode->output_state = gst_video_encoder_set_output_state (venc, out_caps, encode->input_state); if (encode->need_codec_data) { status = gst_vaapi_encoder_get_codec_data (encode->encoder, &encode->output_state->codec_data); if (status != GST_VAAPI_ENCODER_STATUS_SUCCESS) return FALSE; } if (!gst_video_encoder_negotiate (venc)) return FALSE; encode->input_state_changed = FALSE; return TRUE; } static GstFlowReturn gst_vaapiencode_push_frame (GstVaapiEncode * encode, gint64 timeout) { GstVideoEncoder *const venc = GST_VIDEO_ENCODER_CAST (encode); GstVaapiEncodeClass *const klass = GST_VAAPIENCODE_GET_CLASS (encode); GstVideoCodecFrame *out_frame; GstVaapiCodedBufferProxy *codedbuf_proxy = NULL; GstVaapiEncoderStatus status; GstBuffer *out_buffer; GstFlowReturn ret; #if USE_H264_FEI_ENCODER GstVaapiFeiVideoMeta *feimeta = NULL; #endif status = gst_vaapi_encoder_get_buffer_with_timeout (encode->encoder, &codedbuf_proxy, timeout); if (status == GST_VAAPI_ENCODER_STATUS_NO_BUFFER) return GST_VAAPI_ENCODE_FLOW_TIMEOUT; if (status != GST_VAAPI_ENCODER_STATUS_SUCCESS) goto error_get_buffer; out_frame = gst_vaapi_coded_buffer_proxy_get_user_data (codedbuf_proxy); if (!out_frame) goto error_get_buffer; gst_video_codec_frame_ref (out_frame); gst_video_codec_frame_set_user_data (out_frame, NULL, NULL); /* Update output state */ GST_VIDEO_ENCODER_STREAM_LOCK (encode); if (!ensure_output_state (encode)) goto error_output_state; GST_VIDEO_ENCODER_STREAM_UNLOCK (encode); /* Allocate and copy buffer into system memory */ out_buffer = NULL; ret = klass->alloc_buffer (encode, GST_VAAPI_CODED_BUFFER_PROXY_BUFFER (codedbuf_proxy), &out_buffer); #if USE_H264_FEI_ENCODER if (klass->save_stats_to_meta) { feimeta = klass->save_stats_to_meta (encode, codedbuf_proxy); if (feimeta != NULL) gst_buffer_set_vaapi_fei_video_meta (out_buffer, feimeta); } #endif gst_vaapi_coded_buffer_proxy_replace (&codedbuf_proxy, NULL); if (ret != GST_FLOW_OK) goto error_allocate_buffer; gst_buffer_replace (&out_frame->output_buffer, out_buffer); gst_buffer_unref (out_buffer); GST_TRACE_OBJECT (encode, "output:%" GST_TIME_FORMAT ", size:%zu", GST_TIME_ARGS (out_frame->pts), gst_buffer_get_size (out_buffer)); return gst_video_encoder_finish_frame (venc, out_frame); /* ERRORS */ error_get_buffer: { GST_ERROR ("failed to get encoded buffer (status %d)", status); if (codedbuf_proxy) gst_vaapi_coded_buffer_proxy_unref (codedbuf_proxy); return GST_FLOW_ERROR; } error_allocate_buffer: { GST_ERROR ("failed to allocate encoded buffer in system memory"); if (out_buffer) gst_buffer_unref (out_buffer); gst_video_codec_frame_unref (out_frame); return ret; } error_output_state: { GST_ERROR ("failed to negotiate output state (status %d)", status); GST_VIDEO_ENCODER_STREAM_UNLOCK (encode); gst_video_codec_frame_unref (out_frame); return GST_FLOW_NOT_NEGOTIATED; } } static void gst_vaapiencode_buffer_loop (GstVaapiEncode * encode) { GstFlowReturn ret; const gint64 timeout = 50000; /* microseconds */ ret = gst_vaapiencode_push_frame (encode, timeout); if (ret == GST_FLOW_OK || ret == GST_VAAPI_ENCODE_FLOW_TIMEOUT) return; GST_LOG_OBJECT (encode, "pausing task, reason %s", gst_flow_get_name (ret)); gst_pad_pause_task (GST_VAAPI_PLUGIN_BASE_SRC_PAD (encode)); } static GstVaapiProfile get_profile (GstVaapiEncode * encode) { GstVaapiEncodeClass *klass = GST_VAAPIENCODE_GET_CLASS (encode); if (klass->get_profile) { GstVaapiProfile profile = GST_VAAPI_PROFILE_UNKNOWN; GstCaps *allowed = gst_pad_get_allowed_caps (GST_VAAPI_PLUGIN_BASE_SRC_PAD (encode)); if (allowed) { if (!gst_caps_is_empty (allowed) && !gst_caps_is_any (allowed)) profile = klass->get_profile (allowed); gst_caps_unref (allowed); } if (profile != GST_VAAPI_PROFILE_UNKNOWN) return profile; } if (encode->encoder) return gst_vaapi_encoder_get_profile (encode->encoder); return GST_VAAPI_PROFILE_UNKNOWN; } static GstVaapiEntrypoint get_entrypoint (GstVaapiEncode * encode, GstVaapiProfile profile) { GstVaapiEncoderTune tune = GST_VAAPI_ENCODER_TUNE_NONE; g_object_get (encode, "tune", &tune, NULL); if (tune == GST_VAAPI_ENCODER_TUNE_LOW_POWER) return GST_VAAPI_ENTRYPOINT_SLICE_ENCODE_LP; if (profile == GST_VAAPI_PROFILE_JPEG_BASELINE) return GST_VAAPI_ENTRYPOINT_PICTURE_ENCODE; return GST_VAAPI_ENTRYPOINT_SLICE_ENCODE; } static gboolean ensure_allowed_sinkpad_caps (GstVaapiEncode * encode) { GstCaps *out_caps, *raw_caps = NULL; GArray *formats = NULL; gboolean ret = FALSE; GstVaapiProfile profile; guint i, size; GstStructure *structure; if (encode->allowed_sinkpad_caps) return TRUE; if (!encode->encoder) return TRUE; profile = get_profile (encode); if (profile == GST_VAAPI_PROFILE_UNKNOWN) return TRUE; out_caps = gst_caps_from_string (GST_VAAPI_MAKE_SURFACE_CAPS ";" GST_VAAPI_MAKE_DMABUF_CAPS); if (!out_caps) goto failed_create_va_caps; formats = gst_vaapi_encoder_get_surface_formats (encode->encoder, profile); if (!formats) goto failed_get_formats; raw_caps = gst_vaapi_video_format_new_template_caps_from_list (formats); if (!raw_caps) goto failed_create_raw_caps; out_caps = gst_caps_make_writable (out_caps); gst_caps_append (out_caps, gst_caps_copy (raw_caps)); size = gst_caps_get_size (out_caps); for (i = 0; i < size; i++) { structure = gst_caps_get_structure (out_caps, i); if (!structure) continue; gst_vaapi_profile_caps_append_encoder (GST_VAAPI_PLUGIN_BASE_DISPLAY (encode), profile, get_entrypoint (encode, profile), structure); } gst_caps_replace (&encode->allowed_sinkpad_caps, out_caps); GST_INFO_OBJECT (encode, "Allowed sink caps %" GST_PTR_FORMAT, encode->allowed_sinkpad_caps); ret = TRUE; bail: if (out_caps) gst_caps_unref (out_caps); if (raw_caps) gst_caps_unref (raw_caps); if (formats) g_array_unref (formats); return ret; failed_create_va_caps: { GST_WARNING_OBJECT (encode, "failed to create VA/GL sink caps"); return FALSE; } failed_get_formats: { GST_WARNING_OBJECT (encode, "failed to get allowed surface formats"); goto bail; } failed_create_raw_caps: { GST_WARNING_OBJECT (encode, "failed to create raw sink caps"); goto bail; } } static GstCaps * gst_vaapiencode_get_caps (GstVideoEncoder * venc, GstCaps * filter) { GstVaapiEncode *const encode = GST_VAAPIENCODE_CAST (venc); GstCaps *result; ensure_allowed_sinkpad_caps (encode); result = gst_video_encoder_proxy_getcaps (venc, encode->allowed_sinkpad_caps, filter); GST_DEBUG_OBJECT (venc, "Negotiated sink caps %" GST_PTR_FORMAT, result); return result; } static gboolean gst_vaapiencode_destroy (GstVaapiEncode * encode) { if (encode->input_state) { gst_video_codec_state_unref (encode->input_state); encode->input_state = NULL; } if (encode->output_state) { gst_video_codec_state_unref (encode->output_state); encode->output_state = NULL; } gst_caps_replace (&encode->allowed_sinkpad_caps, NULL); gst_vaapi_encoder_replace (&encode->encoder, NULL); return TRUE; } static void gst_vaapiencode_purge (GstVaapiEncode * encode) { GstVaapiCodedBufferProxy *codedbuf_proxy = NULL; GstVaapiEncoderStatus status; GstVideoCodecFrame *out_frame; do { status = gst_vaapi_encoder_get_buffer_with_timeout (encode->encoder, &codedbuf_proxy, 0); if (status == GST_VAAPI_ENCODER_STATUS_SUCCESS) { out_frame = gst_vaapi_coded_buffer_proxy_get_user_data (codedbuf_proxy); if (out_frame) gst_video_codec_frame_set_user_data (out_frame, NULL, NULL); gst_vaapi_coded_buffer_proxy_unref (codedbuf_proxy); } } while (status == GST_VAAPI_ENCODER_STATUS_SUCCESS); } static gboolean ensure_encoder (GstVaapiEncode * encode) { GstVaapiEncodeClass *klass = GST_VAAPIENCODE_GET_CLASS (encode); guint i; g_return_val_if_fail (klass->alloc_encoder, FALSE); if (encode->encoder) return FALSE; encode->encoder = klass->alloc_encoder (encode, GST_VAAPI_PLUGIN_BASE_DISPLAY (encode)); if (!encode->encoder) return FALSE; if (encode->prop_values && encode->prop_values->len) { for (i = 0; i < encode->prop_values->len; i++) { PropValue *const prop_value = g_ptr_array_index (encode->prop_values, i); g_object_set_property ((GObject *) encode->encoder, g_param_spec_get_name (prop_value->pspec), &prop_value->value); } /* clear alll the cache */ g_ptr_array_unref (encode->prop_values); encode->prop_values = NULL; } return TRUE; } static gboolean gst_vaapiencode_open (GstVideoEncoder * venc) { GstVaapiEncode *const encode = GST_VAAPIENCODE_CAST (venc); GstVaapiDisplay *const old_display = GST_VAAPI_PLUGIN_BASE_DISPLAY (encode); gboolean success; if (!gst_vaapi_plugin_base_open (GST_VAAPI_PLUGIN_BASE (encode))) return FALSE; GST_VAAPI_PLUGIN_BASE_DISPLAY (encode) = NULL; success = ensure_display (encode); if (old_display) gst_object_unref (old_display); return success; } static gboolean gst_vaapiencode_start (GstVideoEncoder * venc) { return ensure_encoder (GST_VAAPIENCODE_CAST (venc)); } static gboolean gst_vaapiencode_stop (GstVideoEncoder * venc) { return gst_vaapiencode_destroy (GST_VAAPIENCODE_CAST (venc)); } static gboolean gst_vaapiencode_close (GstVideoEncoder * venc) { gst_vaapi_plugin_base_close (GST_VAAPI_PLUGIN_BASE (venc)); return TRUE; } static gboolean set_codec_state (GstVaapiEncode * encode, GstVideoCodecState * state) { GstVaapiEncodeClass *const klass = GST_VAAPIENCODE_GET_CLASS (encode); GstVaapiEncoderStatus status; g_return_val_if_fail (encode->encoder, FALSE); /* Initialize codec specific parameters */ if (klass->set_config && !klass->set_config (encode)) return FALSE; status = gst_vaapi_encoder_set_codec_state (encode->encoder, state); if (status != GST_VAAPI_ENCODER_STATUS_SUCCESS) return FALSE; return TRUE; } static gboolean gst_vaapiencode_drain (GstVaapiEncode * encode) { GstVaapiEncoderStatus status; if (!encode->encoder) return TRUE; status = gst_vaapi_encoder_flush (encode->encoder); if (status != GST_VAAPI_ENCODER_STATUS_SUCCESS) return FALSE; gst_vaapiencode_purge (encode); return TRUE; } static gboolean gst_vaapiencode_set_format (GstVideoEncoder * venc, GstVideoCodecState * state) { GstVaapiEncode *const encode = GST_VAAPIENCODE_CAST (venc); g_return_val_if_fail (state->caps != NULL, FALSE); if (!set_codec_state (encode, state)) return FALSE; if (!gst_vaapi_plugin_base_set_caps (GST_VAAPI_PLUGIN_BASE (encode), state->caps, NULL)) return FALSE; if (!gst_vaapiencode_drain (encode)) return FALSE; if (encode->input_state) gst_video_codec_state_unref (encode->input_state); encode->input_state = gst_video_codec_state_ref (state); encode->input_state_changed = TRUE; /* Store some tags */ { GstTagList *tags = gst_tag_list_new_empty (); const gchar *encoder, *codec; guint bitrate = 0; g_object_get (encode, "bitrate", &bitrate, NULL); gst_tag_list_add (tags, GST_TAG_MERGE_REPLACE, GST_TAG_NOMINAL_BITRATE, bitrate, NULL); if ((encoder = gst_element_class_get_metadata (GST_ELEMENT_GET_CLASS (encode), GST_ELEMENT_METADATA_LONGNAME))) gst_tag_list_add (tags, GST_TAG_MERGE_REPLACE, GST_TAG_ENCODER, encoder, NULL); if ((codec = gst_vaapi_codec_get_name (gst_vaapi_profile_get_codec (gst_vaapi_profile_from_caps (state->caps))))) gst_tag_list_add (tags, GST_TAG_MERGE_REPLACE, GST_TAG_CODEC, codec, NULL); gst_video_encoder_merge_tags (venc, tags, GST_TAG_MERGE_REPLACE); gst_tag_list_unref (tags); } return TRUE; } static GstFlowReturn gst_vaapiencode_handle_frame (GstVideoEncoder * venc, GstVideoCodecFrame * frame) { GstVaapiEncode *const encode = GST_VAAPIENCODE_CAST (venc); GstPad *const srcpad = GST_VAAPI_PLUGIN_BASE_SRC_PAD (encode); GstVaapiEncoderStatus status; GstVaapiVideoMeta *meta; GstVaapiSurfaceProxy *proxy; GstFlowReturn ret; GstBuffer *buf; GstTaskState task_state; #if USE_H264_FEI_ENCODER GstVaapiFeiVideoMeta *feimeta = NULL; GstVaapiEncodeClass *const klass = GST_VAAPIENCODE_GET_CLASS (venc); #endif task_state = gst_pad_get_task_state (srcpad); if (task_state == GST_TASK_STOPPED || task_state == GST_TASK_PAUSED) if (!gst_pad_start_task (srcpad, (GstTaskFunction) gst_vaapiencode_buffer_loop, encode, NULL)) goto error_task_failed; buf = NULL; ret = gst_vaapi_plugin_base_get_input_buffer (GST_VAAPI_PLUGIN_BASE (encode), frame->input_buffer, &buf); if (ret != GST_FLOW_OK) goto error_buffer_invalid; gst_buffer_replace (&frame->input_buffer, buf); gst_buffer_unref (buf); meta = gst_buffer_get_vaapi_video_meta (buf); if (!meta) goto error_buffer_no_meta; proxy = gst_vaapi_video_meta_get_surface_proxy (meta); if (!proxy) goto error_buffer_no_surface_proxy; #if USE_H264_FEI_ENCODER feimeta = gst_buffer_get_vaapi_fei_video_meta (buf); if (feimeta && klass->load_control_data) klass->load_control_data (encode, feimeta, proxy); #endif gst_video_codec_frame_set_user_data (frame, gst_vaapi_surface_proxy_ref (proxy), (GDestroyNotify) gst_vaapi_surface_proxy_unref); GST_VIDEO_ENCODER_STREAM_UNLOCK (encode); status = gst_vaapi_encoder_put_frame (encode->encoder, frame); GST_VIDEO_ENCODER_STREAM_LOCK (encode); if (status < GST_VAAPI_ENCODER_STATUS_SUCCESS) goto error_encode_frame; gst_video_codec_frame_unref (frame); return GST_FLOW_OK; /* ERRORS */ error_task_failed: { GST_ELEMENT_ERROR (venc, RESOURCE, FAILED, ("Failed to start encoding thread."), (NULL)); gst_video_codec_frame_unref (frame); return GST_FLOW_ERROR; } error_buffer_invalid: { if (buf) gst_buffer_unref (buf); gst_video_codec_frame_unref (frame); return ret; } error_buffer_no_meta: { GST_ERROR ("failed to get GstVaapiVideoMeta information"); gst_video_codec_frame_unref (frame); return GST_FLOW_ERROR; } error_buffer_no_surface_proxy: { GST_ERROR ("failed to get VA surface proxy"); gst_video_codec_frame_unref (frame); return GST_FLOW_ERROR; } error_encode_frame: { GST_ERROR ("failed to encode frame %d (status %d)", frame->system_frame_number, status); gst_video_codec_frame_unref (frame); return GST_FLOW_ERROR; } } static GstFlowReturn gst_vaapiencode_finish (GstVideoEncoder * venc) { GstVaapiEncode *const encode = GST_VAAPIENCODE_CAST (venc); GstVaapiEncoderStatus status; GstFlowReturn ret = GST_FLOW_OK; /* Don't try to destroy encoder if none was created in the first place. Return "not-negotiated" error since this means we did not even reach GstVideoEncoder::set_format() state, where the encoder could have been created */ if (!encode->encoder) return GST_FLOW_NOT_NEGOTIATED; status = gst_vaapi_encoder_flush (encode->encoder); GST_VIDEO_ENCODER_STREAM_UNLOCK (encode); gst_pad_stop_task (GST_VAAPI_PLUGIN_BASE_SRC_PAD (encode)); GST_VIDEO_ENCODER_STREAM_LOCK (encode); while (status == GST_VAAPI_ENCODER_STATUS_SUCCESS && ret == GST_FLOW_OK) ret = gst_vaapiencode_push_frame (encode, 0); if (ret == GST_VAAPI_ENCODE_FLOW_TIMEOUT) ret = GST_FLOW_OK; return ret; } static GstStateChangeReturn gst_vaapiencode_change_state (GstElement * element, GstStateChange transition) { GstVaapiEncode *const encode = GST_VAAPIENCODE_CAST (element); switch (transition) { case GST_STATE_CHANGE_PAUSED_TO_READY: gst_pad_stop_task (GST_VAAPI_PLUGIN_BASE_SRC_PAD (encode)); if (!gst_vaapiencode_drain (encode)) goto drain_error; break; default: break; } return GST_ELEMENT_CLASS (gst_vaapiencode_parent_class)->change_state (element, transition); drain_error: { GST_ERROR ("failed to drain pending encoded frames"); return GST_STATE_CHANGE_FAILURE; } } static gboolean gst_vaapiencode_propose_allocation (GstVideoEncoder * venc, GstQuery * query) { GstVaapiPluginBase *const plugin = GST_VAAPI_PLUGIN_BASE (venc); if (!gst_vaapi_plugin_base_propose_allocation (plugin, query)) return FALSE; return TRUE; } static gboolean gst_vaapiencode_sink_event (GstVideoEncoder * venc, GstEvent * event) { GstVaapiEncode *const encode = GST_VAAPIENCODE_CAST (venc); GstPad *const srcpad = GST_VAAPI_PLUGIN_BASE_SRC_PAD (encode); gboolean ret; ret = GST_VIDEO_ENCODER_CLASS (gst_vaapiencode_parent_class)->sink_event (venc, event); if (!ret) return FALSE; switch (GST_EVENT_TYPE (event)) { case GST_EVENT_FLUSH_START: gst_pad_pause_task (srcpad); break; case GST_EVENT_FLUSH_STOP: ret = gst_pad_start_task (srcpad, (GstTaskFunction) gst_vaapiencode_buffer_loop, encode, NULL); break; default: break; } return ret; } static gboolean gst_vaapiencode_flush (GstVideoEncoder * venc) { GstVaapiEncode *const encode = GST_VAAPIENCODE_CAST (venc); if (!encode->encoder) return FALSE; GST_LOG_OBJECT (encode, "flushing"); if (!gst_vaapiencode_drain (encode)) return FALSE; gst_vaapi_encoder_replace (&encode->encoder, NULL); if (!ensure_encoder (encode)) return FALSE; if (!set_codec_state (encode, encode->input_state)) return FALSE; return TRUE; } static void gst_vaapiencode_finalize (GObject * object) { GstVaapiEncode *const encode = GST_VAAPIENCODE_CAST (object); gst_vaapiencode_destroy (encode); if (encode->prop_values) { g_ptr_array_unref (encode->prop_values); encode->prop_values = NULL; } gst_vaapi_plugin_base_finalize (GST_VAAPI_PLUGIN_BASE (object)); G_OBJECT_CLASS (gst_vaapiencode_parent_class)->finalize (object); } static void gst_vaapiencode_init (GstVaapiEncode * encode) { GstVaapiPluginBase *const plugin = GST_VAAPI_PLUGIN_BASE (encode); gst_vaapi_plugin_base_init (GST_VAAPI_PLUGIN_BASE (encode), GST_CAT_DEFAULT); gst_pad_use_fixed_caps (plugin->srcpad); } static void gst_vaapiencode_class_init (GstVaapiEncodeClass * klass) { GObjectClass *const object_class = G_OBJECT_CLASS (klass); GstElementClass *const element_class = GST_ELEMENT_CLASS (klass); GstVideoEncoderClass *const venc_class = GST_VIDEO_ENCODER_CLASS (klass); GST_DEBUG_CATEGORY_INIT (gst_vaapiencode_debug, GST_PLUGIN_NAME, 0, GST_PLUGIN_DESC); gst_vaapi_plugin_base_class_init (GST_VAAPI_PLUGIN_BASE_CLASS (klass)); object_class->finalize = gst_vaapiencode_finalize; element_class->set_context = gst_vaapi_base_set_context; element_class->change_state = GST_DEBUG_FUNCPTR (gst_vaapiencode_change_state); venc_class->open = GST_DEBUG_FUNCPTR (gst_vaapiencode_open); venc_class->close = GST_DEBUG_FUNCPTR (gst_vaapiencode_close); venc_class->start = GST_DEBUG_FUNCPTR (gst_vaapiencode_start); venc_class->stop = GST_DEBUG_FUNCPTR (gst_vaapiencode_stop); venc_class->set_format = GST_DEBUG_FUNCPTR (gst_vaapiencode_set_format); venc_class->handle_frame = GST_DEBUG_FUNCPTR (gst_vaapiencode_handle_frame); venc_class->finish = GST_DEBUG_FUNCPTR (gst_vaapiencode_finish); venc_class->getcaps = GST_DEBUG_FUNCPTR (gst_vaapiencode_get_caps); venc_class->propose_allocation = GST_DEBUG_FUNCPTR (gst_vaapiencode_propose_allocation); venc_class->flush = GST_DEBUG_FUNCPTR (gst_vaapiencode_flush); venc_class->sink_event = GST_DEBUG_FUNCPTR (gst_vaapiencode_sink_event); klass->alloc_buffer = gst_vaapiencode_default_alloc_buffer; venc_class->src_query = GST_DEBUG_FUNCPTR (gst_vaapiencode_src_query); venc_class->sink_query = GST_DEBUG_FUNCPTR (gst_vaapiencode_sink_query); } /* Only used by the drived class */ void gst_vaapiencode_set_property_subclass (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { GstVaapiEncodeClass *const encode_class = GST_VAAPIENCODE_GET_CLASS (object); GstVaapiEncode *const encode = GST_VAAPIENCODE_CAST (object); PropValue *prop_value; if (prop_id <= PROP_BASE || prop_id >= encode_class->prop_num) { G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); return; } /* direct forward the property to encoder */ if (encode->encoder) { g_object_set_property ((GObject *) encode->encoder, g_param_spec_get_name (pspec), value); return; } if (encode->prop_values) { /* Delete the same prop already in cache */ prop_value = prop_value_lookup_entry (encode->prop_values, prop_id); if (prop_value) g_ptr_array_remove (encode->prop_values, prop_value); } else { encode->prop_values = g_ptr_array_new_with_free_func ((GDestroyNotify) prop_value_free); } /* The encoder is delay created, we need to cache the property setting */ prop_value = prop_value_new_entry (prop_id, pspec, value); g_ptr_array_add (encode->prop_values, prop_value); } /* Only used by the drived class */ void gst_vaapiencode_get_property_subclass (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) { GstVaapiEncodeClass *const encode_class = GST_VAAPIENCODE_GET_CLASS (object); GstVaapiEncode *const encode = GST_VAAPIENCODE_CAST (object); PropValue *prop_value = NULL; if (prop_id <= PROP_BASE || prop_id >= encode_class->prop_num) { G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); return; } /* direct forward the property to encoder */ if (encode->encoder) { g_object_get_property ((GObject *) encode->encoder, g_param_spec_get_name (pspec), value); return; } if (encode->prop_values) prop_value = prop_value_lookup_entry (encode->prop_values, prop_id); if (prop_value) { /* In the cache */ g_value_copy (&prop_value->value, value); } else { /* set the default value */ g_param_value_set_default (pspec, value); } } /* Called by drived class to install all properties. The encode base class does not have any property, all the properties of the according encoderXXX class are installed to encodeXXX class. */ gboolean gst_vaapiencode_class_install_properties (GstVaapiEncodeClass * klass, GObjectClass * encoder_class) { GObjectClass *const object_class = G_OBJECT_CLASS (klass); guint i, n_props, installed; GParamSpec **specs = NULL; GParamSpec *pspec; GParamSpec *new_spec; GParamFlags flags; if (encoder_class) specs = g_object_class_list_properties (encoder_class, &n_props); if (!specs) return FALSE; installed = 0; for (i = 0; i < n_props; i++) { pspec = specs[i]; /* Encoder do not want to expose */ if (!(pspec->flags & GST_VAAPI_PARAM_ENCODER_EXPOSURE)) continue; /* Can only set on encoder init time */ if (pspec->flags & G_PARAM_CONSTRUCT_ONLY) continue; /* filter out the G_PARAM_CONSTRUCT, the encoder created later, no need to set the init value in encode. Also no GST_VAAPI_PARAM_ENCODER_EXPOSURE */ flags = pspec->flags & (~(GST_VAAPI_PARAM_ENCODER_EXPOSURE | G_PARAM_CONSTRUCT)); if (G_PARAM_SPEC_TYPE (pspec) == G_TYPE_PARAM_UINT) { GParamSpecUInt *pspecuint = G_PARAM_SPEC_UINT (pspec); new_spec = g_param_spec_uint (g_param_spec_get_name (pspec), g_param_spec_get_nick (pspec), g_param_spec_get_blurb (pspec), pspecuint->minimum, pspecuint->maximum, pspecuint->default_value, flags); } else if (G_PARAM_SPEC_TYPE (pspec) == G_TYPE_PARAM_INT) { GParamSpecInt *pspecint = G_PARAM_SPEC_INT (pspec); new_spec = g_param_spec_int (g_param_spec_get_name (pspec), g_param_spec_get_nick (pspec), g_param_spec_get_blurb (pspec), pspecint->minimum, pspecint->maximum, pspecint->default_value, flags); } else if (G_IS_PARAM_SPEC_ENUM (pspec)) { GParamSpecEnum *pspecenum = G_PARAM_SPEC_ENUM (pspec); new_spec = g_param_spec_enum (g_param_spec_get_name (pspec), g_param_spec_get_nick (pspec), g_param_spec_get_blurb (pspec), pspec->value_type, pspecenum->default_value, flags); } else if (G_IS_PARAM_SPEC_BOOLEAN (pspec)) { GParamSpecBoolean *pspecbool = G_PARAM_SPEC_BOOLEAN (pspec); new_spec = g_param_spec_boolean (g_param_spec_get_name (pspec), g_param_spec_get_nick (pspec), g_param_spec_get_blurb (pspec), pspecbool->default_value, flags); } else if (G_IS_PARAM_SPEC_FLAGS (pspec)) { GParamSpecFlags *pspecflags = G_PARAM_SPEC_FLAGS (pspec); new_spec = g_param_spec_flags (g_param_spec_get_name (pspec), g_param_spec_get_nick (pspec), g_param_spec_get_blurb (pspec), pspec->value_type, pspecflags->default_value, flags); } else if (GST_IS_PARAM_SPEC_ARRAY_LIST (pspec)) { GstParamSpecArray *pspecarray = GST_PARAM_SPEC_ARRAY_LIST (pspec); new_spec = gst_param_spec_array (g_param_spec_get_name (pspec), g_param_spec_get_nick (pspec), g_param_spec_get_blurb (pspec), pspecarray->element_spec, flags); } else { GST_WARNING ("encoder's %s property has a unimplemented" " type to expose to encode, the encode may lose the %s property", g_param_spec_get_name (pspec), g_param_spec_get_name (pspec)); continue; } g_object_class_install_property (object_class, PROP_BASE + 1 + installed, new_spec); installed++; } g_free (specs); klass->prop_num = PROP_BASE + 1 + installed; return TRUE; }