mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-28 04:31:06 +00:00
5ccadd6e9c
This new virtual method, get_profile(), if implemented by specific encoders, will return the VA profile potentially determined by the source caps. Also it is implemented by h264 and h265 encoders, which are the main users of this vmethod. https://bugzilla.gnome.org/show_bug.cgi?id=771291
900 lines
25 KiB
C
900 lines
25 KiB
C
/*
|
|
* gstvaapiencode.c - VA-API video encoder
|
|
*
|
|
* Copyright (C) 2013-2014 Intel Corporation
|
|
* Author: Wind Yuan <feng.yuan@intel.com>
|
|
* Author: Gwenole Beauchesne <gwenole.beauchesne@intel.com>
|
|
*
|
|
* 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 <gst/vaapi/gstvaapivalue.h>
|
|
#include <gst/vaapi/gstvaapidisplay.h>
|
|
#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
|
|
{
|
|
GstVaapiEncoderProp id;
|
|
GParamSpec *pspec;
|
|
GValue value;
|
|
} PropValue;
|
|
|
|
static PropValue *
|
|
prop_value_new (GstVaapiEncoderPropInfo * prop)
|
|
{
|
|
static const GValue default_value = G_VALUE_INIT;
|
|
PropValue *prop_value;
|
|
|
|
if (!prop || !prop->pspec)
|
|
return NULL;
|
|
|
|
prop_value = g_slice_new (PropValue);
|
|
if (!prop_value)
|
|
return NULL;
|
|
|
|
prop_value->id = prop->prop;
|
|
prop_value->pspec = g_param_spec_ref (prop->pspec);
|
|
|
|
memcpy (&prop_value->value, &default_value, sizeof (prop_value->value));
|
|
g_value_init (&prop_value->value, prop->pspec->value_type);
|
|
g_param_value_set_default (prop->pspec, &prop_value->value);
|
|
return prop_value;
|
|
}
|
|
|
|
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 inline PropValue *
|
|
prop_value_lookup (GstVaapiEncode * encode, guint prop_id)
|
|
{
|
|
GPtrArray *const prop_values = encode->prop_values;
|
|
|
|
if (prop_values &&
|
|
(prop_id >= PROP_BASE && prop_id < PROP_BASE + prop_values->len))
|
|
return g_ptr_array_index (prop_values, prop_id - PROP_BASE);
|
|
return NULL;
|
|
}
|
|
|
|
static gboolean
|
|
gst_vaapiencode_default_get_property (GstVaapiEncode * encode, guint prop_id,
|
|
GValue * value)
|
|
{
|
|
PropValue *const prop_value = prop_value_lookup (encode, prop_id);
|
|
|
|
if (prop_value) {
|
|
g_value_copy (&prop_value->value, value);
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_vaapiencode_default_set_property (GstVaapiEncode * encode, guint prop_id,
|
|
const GValue * value)
|
|
{
|
|
PropValue *const prop_value = prop_value_lookup (encode, prop_id);
|
|
|
|
if (prop_value) {
|
|
g_value_copy (value, &prop_value->value);
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
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;
|
|
|
|
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);
|
|
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 gboolean
|
|
ensure_allowed_sinkpad_caps (GstVaapiEncode * encode)
|
|
{
|
|
GstVaapiEncodeClass *klass = GST_VAAPIENCODE_GET_CLASS (encode);
|
|
GstCaps *out_caps, *raw_caps = NULL;
|
|
GArray *formats = NULL;
|
|
gboolean ret = FALSE;
|
|
GstVaapiProfile profile = GST_VAAPI_PROFILE_UNKNOWN;
|
|
|
|
if (encode->allowed_sinkpad_caps)
|
|
return TRUE;
|
|
if (!encode->encoder)
|
|
return TRUE;
|
|
|
|
out_caps = gst_caps_from_string (GST_VAAPI_MAKE_SURFACE_CAPS);
|
|
if (!out_caps)
|
|
goto failed_create_va_caps;
|
|
|
|
if (klass->get_profile) {
|
|
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);
|
|
}
|
|
}
|
|
|
|
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));
|
|
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);
|
|
GstVaapiEncoderStatus status;
|
|
GPtrArray *const prop_values = encode->prop_values;
|
|
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 (prop_values) {
|
|
for (i = 0; i < prop_values->len; i++) {
|
|
PropValue *const prop_value = g_ptr_array_index (prop_values, i);
|
|
status = gst_vaapi_encoder_set_property (encode->encoder, prop_value->id,
|
|
&prop_value->value);
|
|
if (status != GST_VAAPI_ENCODER_STATUS_SUCCESS)
|
|
return FALSE;
|
|
}
|
|
}
|
|
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_vaapi_display_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_set_format (GstVideoEncoder * venc, GstVideoCodecState * state)
|
|
{
|
|
GstVaapiEncode *const encode = GST_VAAPIENCODE_CAST (venc);
|
|
gboolean ret;
|
|
|
|
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 (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;
|
|
|
|
ret = gst_pad_start_task (GST_VAAPI_PLUGIN_BASE_SRC_PAD (encode),
|
|
(GstTaskFunction) gst_vaapiencode_buffer_loop, encode, NULL);
|
|
|
|
if (!ret)
|
|
return FALSE;
|
|
|
|
/* 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);
|
|
GstVaapiEncoderStatus status;
|
|
GstVaapiVideoMeta *meta;
|
|
GstVaapiSurfaceProxy *proxy;
|
|
GstFlowReturn ret;
|
|
GstBuffer *buf;
|
|
|
|
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;
|
|
|
|
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_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));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return
|
|
GST_ELEMENT_CLASS (gst_vaapiencode_parent_class)->change_state (element,
|
|
transition);
|
|
}
|
|
|
|
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);
|
|
GstVaapiEncoderStatus status;
|
|
|
|
if (!encode->encoder)
|
|
return FALSE;
|
|
|
|
GST_LOG_OBJECT (encode, "flushing");
|
|
|
|
status = gst_vaapi_encoder_flush (encode->encoder);
|
|
if (status != GST_VAAPI_ENCODER_STATUS_SUCCESS)
|
|
return FALSE;
|
|
gst_vaapiencode_purge (encode);
|
|
|
|
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->get_property = gst_vaapiencode_default_get_property;
|
|
klass->set_property = gst_vaapiencode_default_set_property;
|
|
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);
|
|
}
|
|
|
|
static inline GPtrArray *
|
|
get_properties (GstVaapiEncodeClass * klass)
|
|
{
|
|
return klass->get_properties ? klass->get_properties () : NULL;
|
|
}
|
|
|
|
gboolean
|
|
gst_vaapiencode_init_properties (GstVaapiEncode * encode)
|
|
{
|
|
GPtrArray *const props = get_properties (GST_VAAPIENCODE_GET_CLASS (encode));
|
|
guint i;
|
|
|
|
/* XXX: use base_init()/base_finalize() to avoid multiple initializations */
|
|
if (!props)
|
|
return FALSE;
|
|
|
|
encode->prop_values =
|
|
g_ptr_array_new_full (props->len, (GDestroyNotify) prop_value_free);
|
|
if (!encode->prop_values) {
|
|
g_ptr_array_unref (props);
|
|
return FALSE;
|
|
}
|
|
|
|
for (i = 0; i < props->len; i++) {
|
|
PropValue *const prop_value = prop_value_new ((GstVaapiEncoderPropInfo *)
|
|
g_ptr_array_index (props, i));
|
|
if (!prop_value)
|
|
return FALSE;
|
|
g_ptr_array_add (encode->prop_values, prop_value);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
gst_vaapiencode_class_init_properties (GstVaapiEncodeClass * klass)
|
|
{
|
|
GObjectClass *const object_class = G_OBJECT_CLASS (klass);
|
|
GPtrArray *const props = get_properties (klass);
|
|
guint i;
|
|
|
|
if (!props)
|
|
return FALSE;
|
|
|
|
for (i = 0; i < props->len; i++) {
|
|
GstVaapiEncoderPropInfo *const prop = g_ptr_array_index (props, i);
|
|
g_object_class_install_property (object_class, PROP_BASE + i, prop->pspec);
|
|
}
|
|
g_ptr_array_unref (props);
|
|
return TRUE;
|
|
}
|