blob: 36921c597c120de03987e91e5b4c9a26601791d1 [file] [log] [blame]
/*
* Copyright (c) 2014, Freescale Semiconductor, Inc. All rights reserved.
*
* 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., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
/**
* SECTION:element-vpuenc
*
* VPU based video encoder.
*
* <refsect2>
* <title>Example launch line</title>
* |[
* gst-launch -v videotestsrc num-buffers=1000 ! vpuenc ! qtmux ! filesink location=videotestsrc.mp4
* ]| This example pipeline will encode a test video source to AVC muxed in an
* MP4 container.
* </refsect2>
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <string.h>
#include <gst/video/video.h>
#include <gst/video/gstvideometa.h>
#include <gst/video/gstvideopool.h>
#include "gstimxcommon.h"
#include "gstvpuallocator.h"
#include "gstvpuenc.h"
#define DEFAULT_BITRATE 0
#define DEFAULT_GOP_SIZE 15
#define DEFAULT_QUANT -1
#define DEFAULT_H264_QUANT 35
#define DEFAULT_MPEG4_QUANT 15
#define GST_VPU_ENC_PARAMS_QDATA g_quark_from_static_string("vpuenc-params")
typedef struct _VpuEncInfo {
gchar *name;
VpuCodStd std;
gchar *description;
gchar *detail;
} VpuEncInfo;
static const VpuEncInfo VpuEncInfos[] = {
{ .name = "h264",
.std = VPU_V_AVC,
.description = "VPU-based AVC/H264 video encoder",
.detail = "Encode raw data to compressed video",
},
{ .name = "mpeg4",
.std = VPU_V_MPEG4,
.description = "VPU-based MPEG4 video encoder",
.detail = "Encode raw data to compressed video",
},
{ .name = "h263",
.std = VPU_V_H263,
.description = "VPU-based H263 video encoder",
.detail = "Encode raw data to compressed video",
},
{ .name = "jpeg",
.std = VPU_V_MJPG,
.description = "VPU-based JPEG video encoder",
.detail = "Encode raw data to compressed video",
},
{
NULL
}
};
enum
{
PROP_0,
PROP_BITRATE,
PROP_GOP_SIZE,
PROP_QUANT,
};
static GstStaticPadTemplate static_sink_template = GST_STATIC_PAD_TEMPLATE(
"sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS(
"video/x-raw,"
"format = (string) { NV12, I420, YV12 }, "
"width = (int) [ 64, 1920, 8 ], "
"height = (int) [ 64, 1088, 8 ], "
"framerate = (fraction) [ 0, MAX ]"
)
);
static GstStaticPadTemplate static_sink_template_jpeg = GST_STATIC_PAD_TEMPLATE(
"sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS(
"video/x-raw,"
"format = (string) { NV12, I420, YV12 }, "
"width = (int) [ 64, 8192, 8 ], "
"height = (int) [ 64, 8192, 8 ], "
"framerate = (fraction) [ 0, MAX ]"
)
);
static GstStaticPadTemplate static_src_template_h264 = GST_STATIC_PAD_TEMPLATE(
"src",
GST_PAD_SRC,
GST_PAD_ALWAYS,
GST_STATIC_CAPS(
"video/x-h264, "
"stream-format = (string) { avc, byte-stream }, "
"alignment = (string) { au, nal }; "
)
);
static GstStaticPadTemplate static_src_template_mpeg4 = GST_STATIC_PAD_TEMPLATE(
"src",
GST_PAD_SRC,
GST_PAD_ALWAYS,
GST_STATIC_CAPS(
"video/mpeg, "
"mpegversion = (int) 4,"
"systemstream = (boolean) false, "
"width = (int) [ 64, 1920, 8 ], "
"height = (int) [ 64, 1088, 8 ], "
"framerate = (fraction) [ 0, MAX ]; "
)
);
static GstStaticPadTemplate static_src_template_h263 = GST_STATIC_PAD_TEMPLATE(
"src",
GST_PAD_SRC,
GST_PAD_ALWAYS,
GST_STATIC_CAPS(
"video/x-h263, "
"variant = (string) itu, "
"width = (int) [ 64, 1920, 8 ], "
"height = (int) [ 64, 1088, 8 ], "
"framerate = (fraction) [ 0, MAX ]; "
)
);
static GstStaticPadTemplate static_src_template_jpeg = GST_STATIC_PAD_TEMPLATE(
"src",
GST_PAD_SRC,
GST_PAD_ALWAYS,
GST_STATIC_CAPS(
"image/jpeg; "
)
);
GST_DEBUG_CATEGORY_STATIC (vpu_enc_debug);
#define GST_CAT_DEFAULT vpu_enc_debug
GST_DEBUG_CATEGORY_STATIC (GST_CAT_PERFORMANCE);
static void gst_vpu_enc_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec);
static void gst_vpu_enc_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec);
static gboolean gst_vpu_enc_open (GstVideoEncoder * benc);
static gboolean gst_vpu_enc_close (GstVideoEncoder * benc);
static gboolean gst_vpu_enc_start (GstVideoEncoder * benc);
static gboolean gst_vpu_enc_stop (GstVideoEncoder * benc);
static gboolean gst_vpu_enc_set_format (GstVideoEncoder * benc,
GstVideoCodecState * state);
static GstFlowReturn gst_vpu_enc_handle_frame (GstVideoEncoder * benc,
GstVideoCodecFrame * frame);
static gboolean gst_vpu_enc_propose_allocation (GstVideoEncoder * benc,
GstQuery * query);
static void
gst_vpu_enc_class_init (GstVpuEncClass * klass)
{
GObjectClass *gobject_class;
GstElementClass *element_class;
GstVideoEncoderClass *venc_class;
VpuEncInfo *in_plugin = (VpuEncInfo *)
g_type_get_qdata (G_OBJECT_CLASS_TYPE (klass), GST_VPU_ENC_PARAMS_QDATA);
g_assert (in_plugin != NULL);
gobject_class = (GObjectClass *) klass;
element_class = (GstElementClass *) klass;
venc_class = (GstVideoEncoderClass *) klass;
gobject_class->set_property = GST_DEBUG_FUNCPTR (gst_vpu_enc_set_property);
gobject_class->get_property = GST_DEBUG_FUNCPTR (gst_vpu_enc_get_property);
g_object_class_install_property (gobject_class, PROP_BITRATE,
g_param_spec_uint ("bitrate", "bit rate",
"set bit rate in kbps (0 for automatic)",
0, G_MAXINT, DEFAULT_BITRATE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
if (in_plugin->std != VPU_V_MJPG) {
g_object_class_install_property (gobject_class, PROP_GOP_SIZE,
g_param_spec_uint ("gop-size", "Group-of-picture size",
"How many frames a group-of-picture shall contain",
0, 32767, DEFAULT_GOP_SIZE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
}
if (in_plugin->std == VPU_V_AVC) {
g_object_class_install_property (gobject_class, PROP_QUANT,
g_param_spec_int ("quant", "quant",
"set quant value: H.264(0-51) (-1 for automatic)",
-1, 51, DEFAULT_QUANT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
} else if (in_plugin->std == VPU_V_MPEG4) {
g_object_class_install_property (gobject_class, PROP_QUANT,
g_param_spec_int ("quant", "quant",
"set quant value: Mpeg4(1-31) (-1 for automatic)",
-1, 31, DEFAULT_QUANT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
} else if (in_plugin->std == VPU_V_H263) {
g_object_class_install_property (gobject_class, PROP_QUANT,
g_param_spec_int ("quant", "quant",
"set quant value: H.263(1-31) (-1 for automatic)",
-1, 31, DEFAULT_QUANT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
}
if (in_plugin->std == VPU_V_AVC) {
gst_element_class_add_pad_template (element_class,
gst_static_pad_template_get (&static_sink_template));
gst_element_class_add_pad_template (element_class,
gst_static_pad_template_get (&static_src_template_h264));
} else if (in_plugin->std == VPU_V_MPEG4) {
gst_element_class_add_pad_template (element_class,
gst_static_pad_template_get (&static_sink_template));
gst_element_class_add_pad_template (element_class,
gst_static_pad_template_get (&static_src_template_mpeg4));
} else if (in_plugin->std == VPU_V_H263) {
gst_element_class_add_pad_template (element_class,
gst_static_pad_template_get (&static_sink_template));
gst_element_class_add_pad_template (element_class,
gst_static_pad_template_get (&static_src_template_h263));
} else if (in_plugin->std == VPU_V_MJPG) {
gst_element_class_add_pad_template (element_class,
gst_static_pad_template_get (&static_sink_template_jpeg));
gst_element_class_add_pad_template (element_class,
gst_static_pad_template_get (&static_src_template_jpeg));
}
gst_element_class_set_static_metadata (element_class,
in_plugin->description, "Codec/Encoder/Video",
in_plugin->detail, IMX_GST_PLUGIN_AUTHOR);
venc_class->open = GST_DEBUG_FUNCPTR (gst_vpu_enc_open);
venc_class->close = GST_DEBUG_FUNCPTR (gst_vpu_enc_close);
venc_class->start = GST_DEBUG_FUNCPTR (gst_vpu_enc_start);
venc_class->stop = GST_DEBUG_FUNCPTR (gst_vpu_enc_stop);
venc_class->set_format = GST_DEBUG_FUNCPTR (gst_vpu_enc_set_format);
venc_class->handle_frame = GST_DEBUG_FUNCPTR (gst_vpu_enc_handle_frame);
venc_class->propose_allocation = GST_DEBUG_FUNCPTR (gst_vpu_enc_propose_allocation);
GST_DEBUG_CATEGORY_INIT (vpu_enc_debug, "vpuenc", 0, "VPU encoder");
GST_DEBUG_CATEGORY_GET (GST_CAT_PERFORMANCE, "GST_PERFORMANCE");
}
static void
gst_vpu_enc_init (GstVpuEnc * enc)
{
GST_DEBUG ("initializing");
enc->bitrate = DEFAULT_BITRATE;
enc->gop_size = DEFAULT_GOP_SIZE;
enc->quant = DEFAULT_QUANT;
enc->gstbuffer_in_vpuenc = NULL;
enc->gop_count = 0;
enc->handle = NULL;
enc->state = NULL;
enc->bitrate_updated = FALSE;
}
static void
gst_vpu_enc_get_property (GObject * object, guint prop_id, GValue * value,
GParamSpec * pspec)
{
GstVpuEnc *enc = (GstVpuEnc *) object;
switch (prop_id) {
case PROP_BITRATE:
g_value_set_uint (value, enc->bitrate);
break;
case PROP_GOP_SIZE:
g_value_set_uint (value, enc->gop_size);
break;
case PROP_QUANT:
g_value_set_int (value, enc->quant);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_vpu_enc_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec)
{
GstVpuEnc *enc = (GstVpuEnc *) object;
switch (prop_id) {
case PROP_BITRATE:
enc->bitrate = g_value_get_uint (value);
enc->bitrate_updated = TRUE;
break;
case PROP_GOP_SIZE:
enc->gop_size = g_value_get_uint (value);
break;
case PROP_QUANT:
enc->quant = g_value_get_int (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static gchar const *
gst_vpu_enc_strerror(VpuEncRetCode code)
{
switch (code) {
case VPU_ENC_RET_SUCCESS: return "success";
case VPU_ENC_RET_FAILURE: return "failure";
case VPU_ENC_RET_INVALID_PARAM: return "invalid param";
case VPU_ENC_RET_INVALID_HANDLE: return "invalid handle";
case VPU_ENC_RET_INVALID_FRAME_BUFFER: return "invalid frame buffer";
case VPU_ENC_RET_INSUFFICIENT_FRAME_BUFFERS: return "insufficient frame buffers";
case VPU_ENC_RET_INVALID_STRIDE: return "invalid stride";
case VPU_ENC_RET_WRONG_CALL_SEQUENCE: return "wrong call sequence";
case VPU_ENC_RET_FAILURE_TIMEOUT: return "failure timeout";
default: return NULL;
}
}
static gboolean
gst_vpu_enc_open (GstVideoEncoder * benc)
{
GstVpuEnc *enc = (GstVpuEnc *) benc;
VpuEncRetCode ret;
ret = VPU_EncLoad();
if (ret != VPU_ENC_RET_SUCCESS) {
GST_ERROR_OBJECT(enc, "VPU_EncLoad fail: %s", \
gst_vpu_enc_strerror(ret));
return FALSE;
}
return TRUE;
}
static gboolean
gst_vpu_enc_close (GstVideoEncoder * benc)
{
GstVpuEnc *enc = (GstVpuEnc *) benc;
VpuEncRetCode ret;
ret = VPU_EncUnLoad();
if (ret != VPU_ENC_RET_SUCCESS) {
GST_ERROR_OBJECT(enc, "VPU_EncUnLoad fail: %s", \
gst_vpu_enc_strerror(ret));
return FALSE;
}
return TRUE;
}
static gboolean
gst_vpu_enc_start (GstVideoEncoder * benc)
{
GstVpuEnc *enc = (GstVpuEnc *) benc;
VpuEncRetCode ret;
VpuVersionInfo version;
VpuWrapperVersionInfo wrapper_version;
ret = VPU_EncGetVersionInfo(&version);
if (ret != VPU_ENC_RET_SUCCESS) {
GST_WARNING_OBJECT(enc, "VPU_EncGetVersionInfo fail: %s", \
gst_vpu_enc_strerror(ret));
}
ret = VPU_EncGetWrapperVersionInfo(&wrapper_version);
if (ret != VPU_ENC_RET_SUCCESS) {
GST_WARNING_OBJECT(enc, "VPU_EncGetWrapperVersionInfo fail: %s", \
gst_vpu_enc_strerror(ret));
}
g_print("====== VPUENC: %s build on %s %s. ======\n", (VERSION),__DATE__,__TIME__);
g_print("\twrapper: %d.%d.%d (%s)\n", wrapper_version.nMajor, wrapper_version.nMinor,
wrapper_version.nRelease, (wrapper_version.pBinary? wrapper_version.pBinary:"unknow"));
g_print("\tvpulib: %d.%d.%d\n", version.nLibMajor, version.nLibMinor, version.nLibRelease);
g_print("\tfirmware: %d.%d.%d.%d\n", version.nFwMajor, version.nFwMinor, version.nFwRelease, version.nFwCode);
/* mem_info contains information about how to set up memory blocks
* the VPU uses as temporary storage (they are "work buffers") */
memset(&(enc->vpu_internal_mem.mem_info), 0, sizeof(VpuMemInfo));
ret = VPU_EncQueryMem(&(enc->vpu_internal_mem.mem_info));
if (ret != VPU_ENC_RET_SUCCESS) {
GST_ERROR_OBJECT(enc, "could not get VPU memory information: %s", \
gst_vpu_enc_strerror(ret));
return FALSE;
}
if (!gst_vpu_allocate_internal_mem (&(enc->vpu_internal_mem))) {
GST_ERROR_OBJECT(enc, "gst_vpu_allocate_internal_mem fail");
return FALSE;
}
return TRUE;
}
static gboolean
gst_vpu_enc_reset (GstVpuEnc * enc)
{
VpuEncRetCode ret;
if (enc->handle) {
ret = VPU_EncClose(enc->handle);
if (ret != VPU_ENC_RET_SUCCESS) {
GST_ERROR_OBJECT(enc, "closing encoder failed: %s", \
gst_vpu_enc_strerror(ret));
return FALSE;
}
enc->handle = NULL;
}
if (enc->gstbuffer_in_vpuenc) {
g_list_foreach (enc->gstbuffer_in_vpuenc, (GFunc) gst_buffer_unref, NULL);
g_list_free (enc->gstbuffer_in_vpuenc);
enc->gstbuffer_in_vpuenc = NULL;
}
if (enc->pool) {
gst_buffer_pool_set_active (enc->pool, FALSE);
gst_object_unref (enc->pool);
enc->pool = NULL;
}
if (enc->state) {
gst_video_codec_state_unref (enc->state);
enc->state = NULL;
}
return TRUE;
}
static gboolean
gst_vpu_enc_stop (GstVideoEncoder * benc)
{
GstVpuEnc *enc = (GstVpuEnc *) benc;
if (!gst_vpu_enc_reset (enc)) {
GST_ERROR_OBJECT(enc, "gst_enc_free_output_buffer fail");
return FALSE;
}
if (!gst_vpu_free_internal_mem (&(enc->vpu_internal_mem))) {
GST_ERROR_OBJECT(enc, "gst_vpu_free_internal_mem fail");
return FALSE;
}
return TRUE;
}
static GstCaps *
gst_vpu_enc_decide_output_caps (GstVideoEncoder * benc)
{
GstVpuEnc *enc = (GstVpuEnc *) benc;
GstCaps *thiscaps;
GstCaps *caps = NULL;
GstCaps *peercaps = NULL;
gboolean result = FALSE;
/* first see what is possible on our source pad */
thiscaps = gst_pad_query_caps (GST_VIDEO_ENCODER_SRC_PAD (enc), NULL);
GST_DEBUG_OBJECT (enc, "caps of src: %" GST_PTR_FORMAT, thiscaps);
/* nothing or anything is allowed, we're done */
if (thiscaps == NULL || gst_caps_is_any (thiscaps))
goto no_nego_needed;
if (G_UNLIKELY (gst_caps_is_empty (thiscaps)))
goto no_caps;
/* get the peer caps */
peercaps = gst_pad_peer_query_caps (GST_VIDEO_ENCODER_SRC_PAD (enc), thiscaps);
GST_DEBUG_OBJECT (enc, "caps of peer: %" GST_PTR_FORMAT, peercaps);
if (peercaps) {
/* The result is already a subset of our caps */
caps = peercaps;
gst_caps_unref (thiscaps);
} else {
/* no peer, work with our own caps then */
caps = thiscaps;
}
if (caps && !gst_caps_is_empty (caps)) {
/* now fixate */
GST_DEBUG_OBJECT (enc, "have caps: %" GST_PTR_FORMAT, caps);
if (gst_caps_is_any (caps)) {
GST_DEBUG_OBJECT (enc, "any caps, we stop");
/* hmm, still anything, so element can do anything and
* nego is not needed */
result = TRUE;
} else {
caps = gst_caps_fixate (caps);
GST_DEBUG_OBJECT (enc, "fixated to: %" GST_PTR_FORMAT, caps);
}
} else {
GST_DEBUG_OBJECT (enc, "no common caps");
}
return caps;
no_nego_needed:
{
GST_DEBUG_OBJECT (enc, "no negotiation needed");
if (thiscaps)
gst_caps_unref (thiscaps);
return caps;
}
no_caps:
{
GST_ELEMENT_ERROR (enc, STREAM, FORMAT,
("No supported formats found"),
("This element did not produce valid caps"));
if (thiscaps)
gst_caps_unref (thiscaps);
return caps;
}
}
static gboolean
gst_vpu_enc_decide_output_video_format (GstVideoEncoder * benc)
{
GstVpuEnc *enc = (GstVpuEnc *) benc;
GstCaps *caps = NULL;
GstStructure *s;
const gchar *video_format_str = NULL;
caps = gst_vpu_enc_decide_output_caps(benc);
if (!caps) {
GST_ERROR_OBJECT(enc, "can't decide output caps.");
return FALSE;
}
enc->open_param.eFormat = gst_vpu_find_std (caps);
if (enc->open_param.eFormat < 0) {
GST_ERROR_OBJECT(enc, "can't find VPU encoder output format");
gst_caps_unref(caps);
return FALSE;
}
if (enc->open_param.eFormat == VPU_V_AVC && enc->quant == -1) {
enc->quant = DEFAULT_H264_QUANT;
} else if (enc->quant == -1) {
enc->quant = DEFAULT_MPEG4_QUANT;
}
GST_DEBUG_OBJECT (enc, "output caps: %" GST_PTR_FORMAT, caps);
s = gst_caps_get_structure(caps, 0);
GST_DEBUG_OBJECT (enc, "output structure: %" GST_PTR_FORMAT, s);
video_format_str = gst_structure_get_string(s, "stream-format");
if (video_format_str == NULL) {
gst_caps_unref(caps);
return TRUE;
}
GST_DEBUG_OBJECT(enc, "stream-format: %s", video_format_str);
if (!g_strcmp0(video_format_str, "avc"))
enc->open_param.nIsAvcc = 1;
gst_caps_unref(caps);
return TRUE;
}
static gboolean
gst_vpu_enc_set_caps (GstVideoEncoder * benc, guint8 * codec_data, gint codec_data_len)
{
GstVpuEnc *enc = (GstVpuEnc *) benc;
GstVideoCodecState *output_state;
GstCaps *out_caps;
GstStructure *s;
GstBuffer *gstbuf = NULL;
if (codec_data) {
gstbuf = gst_buffer_new_and_alloc (codec_data_len);
gst_buffer_fill(gstbuf, 0, codec_data, codec_data_len);
GST_DEBUG_OBJECT (enc,"set codec data for caps len=%d", codec_data_len);
}
out_caps = gst_vpu_enc_decide_output_caps(benc);
s = gst_caps_get_structure (out_caps, 0);
gst_structure_set (s, "width", G_TYPE_INT, enc->open_param.nPicWidth, NULL);
gst_structure_set (s, "height", G_TYPE_INT, enc->open_param.nPicHeight, NULL);
gst_structure_set (s, "framerate", GST_TYPE_FRACTION, \
GST_VIDEO_INFO_FPS_N(&(enc->state->info)), \
GST_VIDEO_INFO_FPS_D(&(enc->state->info)), NULL);
if (enc->open_param.eFormat == VPU_V_AVC) {
if (enc->open_param.nIsAvcc == 1) {
if (gstbuf != NULL) {
gst_caps_set_simple (out_caps, "codec_data", GST_TYPE_BUFFER, gstbuf, NULL);
}
gst_structure_set (s, "stream-format", G_TYPE_STRING, "avc", NULL);
} else {
gst_structure_set (s, "stream-format", G_TYPE_STRING, "byte-stream",
NULL);
}
gst_structure_set (s, "alignment", G_TYPE_STRING, "au", NULL);
} else {
if (gstbuf != NULL) {
gst_caps_set_simple (out_caps, "codec_data", GST_TYPE_BUFFER, gstbuf, NULL);
}
}
if (gstbuf != NULL)
gst_buffer_unref (gstbuf);
GST_DEBUG_OBJECT (enc, "output caps: %" GST_PTR_FORMAT, out_caps);
output_state = gst_video_encoder_set_output_state(benc, \
out_caps, enc->state);
gst_video_codec_state_unref(output_state);
return TRUE;
}
static gboolean
gst_vpu_enc_set_format (GstVideoEncoder * benc, GstVideoCodecState * state)
{
GstVpuEnc *enc = (GstVpuEnc *) benc;
const gchar *video_format_str = NULL;
GstStructure *s;
if (!gst_vpu_enc_reset (enc)) {
GST_ERROR_OBJECT (enc, "gst_vpu_enc_reset fail.");
return FALSE;
}
memset(&(enc->open_param), 0, sizeof(VpuEncOpenParamSimp));
if (!gst_vpu_enc_decide_output_video_format (benc)) {
GST_ERROR_OBJECT (enc, "gst_vpu_enc_decide_output_video_format fail.");
return FALSE;
}
enc->open_param.nPicWidth = GST_VIDEO_INFO_WIDTH(&(state->info));
enc->open_param.nPicHeight = GST_VIDEO_INFO_HEIGHT(&(state->info));
enc->open_param.nFrameRate = (GST_VIDEO_INFO_FPS_N(&(state->info)) \
& 0xffffUL) | (((GST_VIDEO_INFO_FPS_D(&(state->info)) - 1) & 0xffffUL) << 16);
enc->open_param.sMirror = VPU_ENC_MIRDIR_NONE;
enc->open_param.nBitRate = enc->bitrate;
enc->open_param.nGOPSize = enc->gop_size;
enc->open_param.nChromaInterleave = 0;
enc->open_param.nMapType = 0;
enc->open_param.nLinear2TiledEnable = 0;
enc->gop_count = 0;
if (enc->open_param.nFrameRate == 0)
enc->open_param.nFrameRate = 30;
GST_DEBUG_OBJECT (enc, "input caps: %" GST_PTR_FORMAT, state->caps);
s = gst_caps_get_structure(state->caps, 0);
video_format_str = gst_structure_get_string(s, "format");
if (video_format_str && !g_strcmp0(video_format_str, "NV12"))
enc->open_param.nChromaInterleave = 1;
GST_INFO_OBJECT(enc, "setting bitrate to %u kbps and GOP size to %u", \
enc->open_param.nBitRate, enc->open_param.nGOPSize);
enc->state = gst_video_codec_state_ref(state);
return TRUE;
}
static gboolean
gst_vpu_enc_open_vpu (GstVideoEncoder * benc)
{
GstVpuEnc *enc = (GstVpuEnc *) benc;
VpuEncRetCode ret;
ret = VPU_EncOpenSimp(&(enc->handle), &(enc->vpu_internal_mem.mem_info), \
&(enc->open_param));
if (ret != VPU_ENC_RET_SUCCESS) {
GST_ERROR_OBJECT(enc, "opening new VPU handle failed: %s", \
gst_vpu_enc_strerror(ret));
return FALSE;
}
ret = VPU_EncConfig(enc->handle, VPU_ENC_CONF_NONE, NULL);
if (ret != VPU_ENC_RET_SUCCESS) {
GST_ERROR_OBJECT(enc, "could not apply default configuration: %s", \
gst_vpu_enc_strerror(ret));
return FALSE;
}
ret = VPU_EncGetInitialInfo(enc->handle, &(enc->init_info));
if (ret != VPU_ENC_RET_SUCCESS) {
GST_ERROR_OBJECT(enc, "retrieving init info failed: %s", \
gst_vpu_enc_strerror(ret));
return FALSE;
}
if (!gst_vpu_enc_set_caps(benc, NULL, 0)) {
GST_ERROR_OBJECT(enc, "gst_vpu_enc_set_caps fail.");
return FALSE;
}
return TRUE;
}
static gboolean
gst_vpu_enc_setup_internal_bufferpool (GstVpuEnc * enc)
{
GstAllocationParams params = { 0 };
GstAllocator *allocator = NULL;
GstCaps *caps;
GstStructure *config;
guint i;
enc->pool = gst_video_buffer_pool_new ();
if (!enc->pool) {
GST_ERROR_OBJECT (enc, "New video buffer pool failed.");
return FALSE;
}
allocator = gst_vpu_allocator_obtain();
if (!allocator) {
GST_ERROR_OBJECT (enc, "New VPU allocator failed.");
return FALSE;
}
params.align = enc->init_info.nAddressAlignment;
memset(&(enc->video_align), 0, sizeof(GstVideoAlignment));
if (enc->open_param.nPicWidth % DEFAULT_FRAME_BUFFER_ALIGNMENT_H)
enc->video_align.padding_right = DEFAULT_FRAME_BUFFER_ALIGNMENT_H \
- enc->open_param.nPicWidth % DEFAULT_FRAME_BUFFER_ALIGNMENT_H;
if (enc->open_param.nPicHeight % DEFAULT_FRAME_BUFFER_ALIGNMENT_V)
enc->video_align.padding_bottom = DEFAULT_FRAME_BUFFER_ALIGNMENT_V\
- enc->open_param.nPicHeight % DEFAULT_FRAME_BUFFER_ALIGNMENT_V;
for (i = 0; i < GST_VIDEO_MAX_PLANES; i++)
enc->video_align.stride_align[i] = DEFAULT_FRAME_BUFFER_ALIGNMENT_H - 1;
config = gst_buffer_pool_get_config(enc->pool);
gst_buffer_pool_config_add_option(config, GST_BUFFER_POOL_OPTION_VIDEO_ALIGNMENT);
gst_buffer_pool_config_add_option(config, GST_BUFFER_POOL_OPTION_VIDEO_META);
caps = gst_video_info_to_caps (&(enc->state->info));
gst_buffer_pool_config_set_params(config, caps, enc->state->info.size, 2, 0);
gst_buffer_pool_config_set_video_alignment (config, &enc->video_align);
gst_buffer_pool_config_set_allocator(config, allocator, &params);
gst_buffer_pool_set_config(enc->pool, config);
if (allocator)
gst_object_unref (allocator);
gst_caps_unref (caps);
if (gst_buffer_pool_set_active (enc->pool, TRUE) != TRUE) {
GST_ERROR_OBJECT (enc, "active pool(%p) failed.", enc->pool);
return FALSE;
}
return TRUE;
}
static gboolean
gst_vpu_enc_allocate_physical_mem (GstVpuEnc * enc, gint src_stride)
{
VpuFrameBuffer *vpuframebuffers = NULL;
VpuEncRetCode ret;
GstBuffer * buffer;
if (enc->gstbuffer_in_vpuenc == NULL) {
if (enc->pool == NULL) {
if (!gst_vpu_enc_setup_internal_bufferpool (enc)) {
GST_ERROR_OBJECT (enc, "gst_vpu_enc_setup_internal_bufferpool failed.");
return FALSE;
}
}
while (g_list_length (enc->gstbuffer_in_vpuenc) \
< enc->init_info.nMinFrameBufferCount) {
gst_buffer_pool_acquire_buffer (enc->pool, &buffer, NULL);
if (!buffer) {
GST_ERROR_OBJECT (enc, "acquire buffer from pool(%p) failed.", \
enc->pool);
return FALSE;
}
enc->gstbuffer_in_vpuenc = g_list_append ( \
enc->gstbuffer_in_vpuenc, buffer);
}
vpuframebuffers = (VpuFrameBuffer *)g_malloc ( \
sizeof (VpuFrameBuffer) * enc->init_info.nMinFrameBufferCount);
if (vpuframebuffers == NULL) {
GST_ERROR_OBJECT (enc, "Could not allocate memory");
return FALSE;
}
memset (vpuframebuffers, 0, sizeof (VpuFrameBuffer) \
* enc->init_info.nMinFrameBufferCount);
if (!gst_vpu_register_frame_buffer (enc->gstbuffer_in_vpuenc, \
&enc->state->info, vpuframebuffers)) {
GST_ERROR_OBJECT (enc, "gst_vpu_register_frame_buffer fail.");
g_free(vpuframebuffers);
return FALSE;
}
ret = VPU_EncRegisterFrameBuffer (enc->handle, \
vpuframebuffers, enc->init_info.nMinFrameBufferCount, src_stride);
if (ret != VPU_ENC_RET_SUCCESS) {
GST_ERROR_OBJECT(enc, "registering framebuffers failed: %s", \
gst_vpu_enc_strerror(ret));
g_free(vpuframebuffers);
return FALSE;
}
g_free(vpuframebuffers);
}
return TRUE;
}
static GstFlowReturn
gst_vpu_enc_handle_frame (GstVideoEncoder * benc, GstVideoCodecFrame * frame)
{
GstVpuEnc *enc = (GstVpuEnc *) benc;
GstFlowReturn ret = GST_FLOW_OK;
VpuEncRetCode enc_ret;
VpuEncEncParam enc_enc_param;
VpuFrameBuffer input_framebuf;
GstVideoCropMeta *cropmeta = NULL;
GstBuffer *input_buffer;
GstBuffer *output_buffer = NULL;
GstMapInfo minfo;
GstBuffer *pool_buffer = NULL;
gboolean is_sync_point = FALSE;
gint src_stride;
memset(&enc_enc_param, 0, sizeof(enc_enc_param));
memset(&input_framebuf, 0, sizeof(input_framebuf));
cropmeta = gst_buffer_get_video_crop_meta (frame->input_buffer);
if (cropmeta) {
enc->open_param.nPicWidth = cropmeta->width;
enc->open_param.nPicHeight = cropmeta->height;
}
if (!enc->handle) {
if (!gst_vpu_enc_open_vpu (benc)) {
GST_ERROR_OBJECT (enc, "gst_vpu_enc_open_vpu failed.");
return GST_FLOW_ERROR;
}
}
if (!gst_buffer_is_phymem (frame->input_buffer)) {
GstVideoInfo info = enc->state->info;
GstVideoFrame frame1, frame2;
GST_DEBUG_OBJECT(enc, "not physical continues memory. allocate internal memory pool.");
if (enc->pool == NULL) {
if (!gst_vpu_enc_setup_internal_bufferpool (enc)) {
GST_ERROR_OBJECT (enc, "acquire buffer from pool(%p) failed.", enc->pool);
return GST_FLOW_ERROR;
}
}
gst_buffer_pool_acquire_buffer (enc->pool, &pool_buffer, NULL);
if (!pool_buffer) {
GST_ERROR_OBJECT (enc, "acquire buffer from pool(%p) failed.", enc->pool);
return GST_FLOW_ERROR;
}
gst_video_frame_map (&frame1, &info, pool_buffer, GST_MAP_WRITE);
gst_video_frame_map (&frame2, &info, frame->input_buffer, GST_MAP_READ);
gst_video_frame_copy (&frame1, &frame2);
gst_video_frame_unmap (&frame1);
gst_video_frame_unmap (&frame2);
input_buffer = pool_buffer;
} else {
GST_DEBUG_OBJECT(enc, "is physical continues memory.");
input_buffer = frame->input_buffer;
}
/* Set up physical addresses for the input framebuffer */
{
gsize *plane_offsets;
gint *plane_strides;
GstVideoMeta *video_meta;
PhyMemBlock *input_phys_buffer;
unsigned char *phys_ptr;
/* Try to use plane offset and stride information from the video
* metadata if present, since these can be more accurate than
* the information from the video info */
video_meta = gst_buffer_get_video_meta(input_buffer);
if (video_meta != NULL) {
plane_offsets = video_meta->offset;
plane_strides = video_meta->stride;
} else {
plane_offsets = enc->state->info.offset;
plane_strides = enc->state->info.stride;
}
input_phys_buffer = gst_buffer_query_phymem_block (input_buffer);
if (input_phys_buffer == NULL) {
GST_ERROR_OBJECT(enc, "could not get physical address from input buffer.");
ret = GST_FLOW_ERROR;
goto bail;
}
phys_ptr = (unsigned char*)(input_phys_buffer->paddr);
input_framebuf.pbufY = phys_ptr;
input_framebuf.pbufCb = phys_ptr + plane_offsets[1];
input_framebuf.pbufCr = phys_ptr + plane_offsets[2];
input_framebuf.pbufMvCol = NULL; /* not used by the VPU encoder */
input_framebuf.nStrideY = plane_strides[0];
input_framebuf.nStrideC = plane_strides[1];
/* this is needed for framebuffers registration below */
src_stride = plane_strides[0];
GST_TRACE_OBJECT(enc, "width: %d height: %d stride 0: %d stride 1: %d offset 0: %d offset 1: %d offset 2: %d", GST_VIDEO_INFO_WIDTH(&(enc->state->info)), GST_VIDEO_INFO_HEIGHT(&(enc->state->info)), plane_strides[0], plane_strides[1], plane_offsets[0], plane_offsets[1], plane_offsets[2]);
}
// Allocate needed physical buffer.
if (!gst_vpu_enc_allocate_physical_mem (enc, src_stride)) {
GST_ERROR_OBJECT(enc, "gst_vpu_enc_allocate_physical_mem failed.");
ret = GST_FLOW_ERROR;
goto bail;
}
output_buffer = gst_video_encoder_allocate_output_buffer(benc,
enc->state->info.size);
if (output_buffer == NULL) {
GST_ERROR_OBJECT(enc, "can't get output buffer from video encoder.");
ret = GST_FLOW_ERROR;
goto bail;
}
frame->output_buffer = output_buffer;
gst_buffer_map (output_buffer, &minfo, GST_MAP_READ);
/* Set up encoding parameters */
enc_enc_param.nInVirtOutput = (unsigned int)(minfo.data);
enc_enc_param.nInOutputBufLen = enc->state->info.size;
enc_enc_param.nPicWidth = enc->open_param.nPicWidth;
enc_enc_param.nPicHeight = enc->open_param.nPicHeight;
enc_enc_param.nFrameRate = enc->open_param.nFrameRate;
enc_enc_param.pInFrame = &input_framebuf;
enc_enc_param.eFormat = enc->open_param.eFormat;
enc_enc_param.nQuantParam = enc->quant;
enc_enc_param.nForceIPicture = 0;
GST_DEBUG_OBJECT(enc, "VPU enc width: %d, height: %d, fps: %d", \
enc_enc_param.nPicWidth, enc_enc_param.nPicHeight, enc_enc_param.nFrameRate);
if (GST_VIDEO_CODEC_FRAME_IS_FORCE_KEYFRAME(frame) \
|| GST_VIDEO_CODEC_FRAME_IS_FORCE_KEYFRAME_HEADERS(frame) \
|| (enc->gop_size && !(enc->gop_count % enc->gop_size)) \
|| enc->gop_count == 0) {
enc_enc_param.nForceIPicture = 1;
is_sync_point = TRUE;
GST_LOG_OBJECT(enc, "got request to make this a keyframe - forcing I frame");
}
if (enc->bitrate_updated) {
GST_DEBUG_OBJECT(enc, "update bitrate.");
int param = enc->bitrate;
enc_ret = VPU_EncConfig(enc->handle, VPU_ENC_CONF_BIT_RATE, &param);
if (enc_ret != VPU_ENC_RET_SUCCESS) {
GST_ERROR_OBJECT(enc, "could not apply default configuration: %s", \
gst_vpu_enc_strerror(enc_ret));
gst_buffer_unmap (output_buffer, &minfo);
ret = GST_FLOW_ERROR;
goto bail;
}
enc->bitrate_updated = FALSE;
}
{
gsize output_buffer_offset = 0;
gboolean frame_finished = FALSE;
do
{
gint64 start_time;
start_time = g_get_monotonic_time ();
enc_ret = VPU_EncEncodeFrame(enc->handle, &enc_enc_param);
if (enc_ret != VPU_ENC_RET_SUCCESS) {
GST_ERROR_OBJECT(enc, "failed to encode frame: %s", \
gst_vpu_enc_strerror(enc_ret));
VPU_EncReset(enc->handle);
gst_buffer_unmap (output_buffer, &minfo);
ret = GST_FLOW_ERROR;
goto bail;
}
GST_DEBUG_OBJECT(enc, "encoder consume time: %lld\n", \
g_get_monotonic_time () - start_time);
if (enc_enc_param.eOutRetCode & VPU_ENC_OUTPUT_SEQHEADER) {
if (!gst_vpu_enc_set_caps(benc, minfo.data, enc_enc_param.nOutOutputSize)) {
GST_ERROR_OBJECT(enc, "gst_vpu_enc_set_caps fail.");
gst_buffer_unmap (output_buffer, &minfo);
ret = GST_FLOW_ERROR;
goto bail;
}
if (!(enc->open_param.eFormat == VPU_V_AVC && enc->open_param.nIsAvcc == 1)) {
output_buffer_offset += enc_enc_param.nOutOutputSize;
enc_enc_param.nInVirtOutput = (unsigned int)(minfo.data) + enc_enc_param.nOutOutputSize;
enc_enc_param.nInOutputBufLen = enc->state->info.size - enc_enc_param.nOutOutputSize;
}
continue;
}
if (enc_enc_param.eOutRetCode & VPU_ENC_OUTPUT_DIS) {
GST_LOG_OBJECT(enc, "processing output data: %u bytes, output buffer offset %u", \
enc_enc_param.nOutOutputSize, output_buffer_offset);
gst_buffer_unmap (output_buffer, &minfo);
if (is_sync_point) {
GST_LOG_OBJECT(enc, "setting sync point");
GST_VIDEO_CODEC_FRAME_SET_SYNC_POINT(frame);
}
enc->gop_count ++;
output_buffer_offset += enc_enc_param.nOutOutputSize;
gst_buffer_set_size(output_buffer, output_buffer_offset);
frame->dts = frame->pts;
gst_video_encoder_finish_frame(benc, frame);
output_buffer = NULL;
frame_finished = TRUE;
if (!(enc_enc_param.eOutRetCode & VPU_ENC_INPUT_USED))
GST_WARNING_OBJECT(enc, "frame finished, but VPU did not report the input as used");
break;
}
} while (!(enc_enc_param.eOutRetCode & VPU_ENC_INPUT_USED));
bail:
if (pool_buffer)
gst_buffer_unref (pool_buffer);
/* If output_buffer is NULL at this point, it means VPU_ENC_OUTPUT_DIS was never communicated
* by the VPU, and the buffer is unfinished. -> Drop it. */
if (output_buffer != NULL) {
GST_WARNING_OBJECT(enc, "frame unfinished ; dropping");
gst_buffer_unref(output_buffer);
frame->output_buffer = NULL; /* necessary to make finish_frame() drop the frame */
gst_video_encoder_finish_frame(benc, frame);
}
}
return ret;
}
static gboolean
gst_vpu_enc_propose_allocation (GstVideoEncoder * benc, GstQuery * query)
{
GstVpuEnc *enc = (GstVpuEnc *) benc;
GstCaps *caps;
GstVideoInfo info;
GstBufferPool *pool;
guint size;
gst_query_parse_allocation (query, &caps, NULL);
if (caps == NULL)
return FALSE;
if (!gst_video_info_from_caps (&info, caps))
return FALSE;
size = GST_VIDEO_INFO_SIZE (&info);
if (gst_query_get_n_allocation_pools (query) == 0) {
GstStructure *structure;
GstAllocator *allocator = NULL;
GstAllocationParams params = { 0 };
allocator = gst_vpu_allocator_obtain();
pool = gst_video_buffer_pool_new ();
params.align = enc->init_info.nAddressAlignment;
structure = gst_buffer_pool_get_config (pool);
gst_buffer_pool_config_set_params (structure, caps, size, 0, 0);
gst_buffer_pool_config_set_allocator (structure, allocator, &params);
if (!gst_buffer_pool_set_config (pool, structure)) {
GST_ERROR_OBJECT (enc, "failed to set config");
gst_object_unref (allocator);
gst_object_unref (pool);
return FALSE;
}
gst_query_add_allocation_pool (query, pool, size, 3, 0);
gst_query_add_allocation_param (query, allocator, NULL);
gst_object_unref (allocator);
gst_object_unref (pool);
gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, NULL);
}
return TRUE;
}
const VpuEncInfo * gst_vpu_enc_get_info(void)
{
return &VpuEncInfos[0];
}
gboolean gst_vpu_enc_register (GstPlugin * plugin)
{
GTypeInfo tinfo = {
sizeof (GstVpuEncClass),
NULL,
NULL,
(GClassInitFunc) gst_vpu_enc_class_init,
NULL,
NULL,
sizeof (GstVpuEnc),
0,
(GInstanceInitFunc) gst_vpu_enc_init,
};
GType type;
gchar *t_name;
const VpuEncInfo *in_plugin = gst_vpu_enc_get_info();
while (in_plugin->name) {
t_name = g_strdup_printf ("vpuenc_%s", in_plugin->name);
type = g_type_from_name (t_name);
if (!type) {
type = g_type_register_static (GST_TYPE_VIDEO_ENCODER, t_name, &tinfo, 0);
g_type_set_qdata (type, GST_VPU_ENC_PARAMS_QDATA, (gpointer) in_plugin);
}
if (!gst_element_register (plugin, t_name, IMX_GST_PLUGIN_RANK, type)) {
g_free (t_name);
return FALSE;
}
g_free (t_name);
in_plugin++;
}
return TRUE;
}