gstreamer/subprojects/gst-plugins-bad/sys/va/gstvah264enc.c
He Junyan 990fbb3b52 va: Move allocators and pool objects into gstva library.
In order to other plugins use gstva objects, such as allocators and buffer
pools, this merge request move them from the va plugin to the gstva library.

This objects are not exposed in <gst/va/gstva.h> since they are not expected
to be used by users, only by plugin implementators.

Because of the surface copy design, which is used to implement allocator's
mem_copy() virtual function, depends on the vafilter, which is kept inside
the plugin, memory copy through VAPosproc is disabled and removed temporarly.

Also added some missing parameter validation.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/2048>
2022-03-29 19:48:30 +00:00

4548 lines
137 KiB
C

/* GStreamer
* Copyright (C) 2021 Intel Corporation
* Author: He Junyan <junyan.he@intel.com>
* Author: Víctor Jáquez <vjaquez@igalia.com>
*
* 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-vah264enc
* @title: vah264enc
* @short_description: A VA-API based H264 video encoder
*
* vah264enc encodes raw video VA surfaces into H.264 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 ! vah264enc ! h264parse ! mp4mux ! filesink location=test.mp4
* ```
*
* Since: 1.22
*
*/
/* @TODO:
* 1. Look ahead, which can optimize the slice type and QP.
* 2. Field encoding.
* 3. The stereo encoding such as the frame-packing or MVC.
* 4. Weight prediction of B frame.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "gstvah264enc.h"
#include <gst/codecparsers/gsth264bitwriter.h>
#include <gst/va/gstvaallocator.h>
#include <gst/va/gstvapool.h>
#include <gst/va/gstvautils.h>
#include <gst/va/gstvavideoformat.h>
#include <gst/video/video.h>
#include <va/va_drmcommon.h>
#include "vacompat.h"
#include "gstvaencoder.h"
#include "gstvacaps.h"
#include "gstvaprofile.h"
#include "gstvadisplay_priv.h"
GST_DEBUG_CATEGORY_STATIC (gst_va_h264enc_debug);
#ifndef GST_DISABLE_GST_DEBUG
#define GST_CAT_DEFAULT gst_va_h264enc_debug
#else
#define GST_CAT_DEFAULT NULL
#endif
typedef struct _GstVaH264Enc GstVaH264Enc;
typedef struct _GstVaH264EncClass GstVaH264EncClass;
typedef struct _GstVaH264EncFrame GstVaH264EncFrame;
typedef struct _GstVaH264LevelLimits GstVaH264LevelLimits;
#define GST_VA_H264_ENC(obj) ((GstVaH264Enc *) obj)
#define GST_VA_H264_ENC_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), G_TYPE_FROM_INSTANCE (obj), GstVaH264EncClass))
#define GST_VA_H264_ENC_CLASS(klass) ((GstVaH264EncClass *) klass)
enum
{
PROP_KEY_INT_MAX = 1,
PROP_BFRAMES,
PROP_IFRAMES,
PROP_NUM_REF_FRAMES,
PROP_B_PYRAMID,
PROP_NUM_SLICES,
PROP_MIN_QP,
PROP_MAX_QP,
PROP_QP_I,
PROP_QP_P,
PROP_QP_B,
PROP_DCT8X8,
PROP_CABAC,
PROP_TRELLIS,
PROP_MBBRC,
PROP_BITRATE,
PROP_TARGET_PERCENTAGE,
PROP_TARGET_USAGE,
PROP_RATE_CONTROL,
PROP_CPB_SIZE,
PROP_AUD,
N_PROPERTIES
};
static GParamSpec *properties[N_PROPERTIES];
/* Scale factor for bitrate (HRD bit_rate_scale: min = 6) */
#define SX_BITRATE 6
/* Scale factor for CPB size (HRD cpb_size_scale: min = 4) */
#define SX_CPB_SIZE 4
/* Maximum sizes for common headers (in bits) */
#define MAX_SPS_HDR_SIZE 16473
#define MAX_VUI_PARAMS_SIZE 210
#define MAX_HRD_PARAMS_SIZE 4103
#define MAX_PPS_HDR_SIZE 101
#define MAX_SLICE_HDR_SIZE 397 + 2572 + 6670 + 2402
#define MAX_GOP_SIZE 1024
static GstObjectClass *parent_class = NULL;
/* *INDENT-OFF* */
struct _GstVaH264EncClass
{
GstVideoEncoderClass parent_class;
GstVaCodecs codec;
gchar *render_device_path;
gboolean (*reconfig) (GstVaH264Enc * encoder);
gboolean (*push_frame) (GstVaH264Enc * encoder,
GstVideoCodecFrame * frame,
gboolean last);
gboolean (*pop_frame) (GstVaH264Enc * encoder,
GstVideoCodecFrame ** out_frame);
gboolean (*encode_frame) (GstVaH264Enc * encoder,
GstVideoCodecFrame * frame);
};
/* *INDENT-ON* */
struct _GstVaH264Enc
{
/*< private > */
GstVideoEncoder parent_instance;
GstVaDisplay *display;
gint width;
gint height;
VAProfile profile;
VAEntrypoint entrypoint;
guint rt_format;
guint codedbuf_size;
/* properties */
struct
{
/* kbps */
guint bitrate;
/* VA_RC_XXX */
guint rc_ctrl;
guint key_int_max;
guint32 num_ref_frames;
gboolean b_pyramid;
guint32 num_bframes;
guint32 num_iframes;
guint32 min_qp;
guint32 max_qp;
guint32 qp_i;
guint32 qp_p;
guint32 qp_b;
gboolean use_cabac;
gboolean use_dct8x8;
gboolean use_trellis;
gboolean aud;
guint32 mbbrc;
guint32 num_slices;
guint32 cpb_size;
guint32 target_percentage;
guint32 target_usage;
} prop;
GstVideoCodecState *input_state;
GstVideoCodecState *output_state;
GstCaps *in_caps;
GstVideoInfo in_info;
GstVideoInfo sinkpad_info;
GstBufferPool *raw_pool;
GstClockTime start_pts;
GstClockTime frame_duration;
/* Total frames we handled since reconfig. */
guint input_frame_count;
guint output_frame_count;
GstVaEncoder *encoder;
GQueue reorder_list;
GQueue ref_list;
GQueue output_list;
/* H264 fields */
gint mb_width;
gint mb_height;
guint8 level_idc;
const gchar *level_str;
/* Minimum Compression Ratio (A.3.1) */
guint min_cr;
gboolean use_cabac;
gboolean use_dct8x8;
gboolean use_trellis;
guint32 num_slices;
guint32 packed_headers;
struct
{
/* frames between two IDR [idr, ...., idr) */
guint32 idr_period;
/* How may IDRs we have encoded */
guint32 total_idr_count;
/* frames between I/P and P frames [I, B, B, .., B, P) */
guint32 ip_period;
/* frames between I frames [I, B, B, .., B, P, ..., I), open GOP */
guint32 i_period;
/* B frames between I/P and P. */
guint32 num_bframes;
/* Use B pyramid structure in the GOP. */
gboolean b_pyramid;
/* Level 0 is the simple B not acting as ref. */
guint32 highest_pyramid_level;
/* If open GOP, I frames within a GOP. */
guint32 num_iframes;
/* A map of all frames types within a GOP. */
struct
{
guint8 slice_type;
gboolean is_ref;
guint8 pyramid_level;
/* Only for b pyramid */
gint left_ref_poc_diff;
gint right_ref_poc_diff;
} frame_types[MAX_GOP_SIZE];
/* current index in the frames types map. */
guint cur_frame_index;
/* Number of ref frames within current GOP. H264's frame num. */
gint cur_frame_num;
/* Max frame num within a GOP. */
guint32 max_frame_num;
guint32 log2_max_frame_num;
/* Max poc within a GOP. */
guint32 max_pic_order_cnt;
guint32 log2_max_pic_order_cnt;
/* Total ref frames of list0 and list1. */
guint32 num_ref_frames;
guint32 ref_num_list0;
guint32 ref_num_list1;
guint num_reorder_frames;
} gop;
struct
{
guint target_usage;
guint32 rc_ctrl_mode;
guint32 min_qp;
guint32 max_qp;
guint32 qp_i;
guint32 qp_p;
guint32 qp_b;
/* macroblock bitrate control */
guint32 mbbrc;
guint target_bitrate;
guint target_percentage;
guint max_bitrate;
/* bitrate (bits) */
guint max_bitrate_bits;
guint target_bitrate_bits;
/* length of CPB buffer */
guint cpb_size;
/* length of CPB buffer (bits) */
guint cpb_length_bits;
} rc;
GstH264SPS sequence_hdr;
};
struct _GstVaH264EncFrame
{
GstVaEncodePicture *picture;
GstH264SliceType type;
gboolean is_ref;
guint pyramid_level;
/* Only for b pyramid */
gint left_ref_poc_diff;
gint right_ref_poc_diff;
gint poc;
gint frame_num;
gboolean last_frame;
/* The pic_num will be marked as unused_for_reference, which is
* replaced by this frame. -1 if we do not need to care about it
* explicitly. */
gint unused_for_reference_pic_num;
/* The total frame count we handled. */
guint total_frame_count;
};
/**
* GstVaH264LevelLimits:
* @name: the level name
* @level_idc: the H.264 level_idc value
* @MaxMBPS: the maximum macroblock processing rate (MB/sec)
* @MaxFS: the maximum frame size (MBs)
* @MaxDpbMbs: the maxium decoded picture buffer size (MBs)
* @MaxBR: the maximum video bit rate (kbps)
* @MaxCPB: the maximum CPB size (kbits)
* @MinCR: the minimum Compression Ratio
*
* The data structure that describes the limits of an H.264 level.
*/
struct _GstVaH264LevelLimits
{
const gchar *name;
guint8 level_idc;
guint32 MaxMBPS;
guint32 MaxFS;
guint32 MaxDpbMbs;
guint32 MaxBR;
guint32 MaxCPB;
guint32 MinCR;
};
/* Table A-1 - Level limits */
/* *INDENT-OFF* */
static const GstVaH264LevelLimits _va_h264_level_limits[] = {
/* level idc MaxMBPS MaxFS MaxDpbMbs MaxBR MaxCPB MinCr */
{ "1", 10, 1485, 99, 396, 64, 175, 2 },
{ "1b", 11, 1485, 99, 396, 128, 350, 2 },
{ "1.1", 11, 3000, 396, 900, 192, 500, 2 },
{ "1.2", 12, 6000, 396, 2376, 384, 1000, 2 },
{ "1.3", 13, 11880, 396, 2376, 768, 2000, 2 },
{ "2", 20, 11880, 396, 2376, 2000, 2000, 2 },
{ "2.1", 21, 19800, 792, 4752, 4000, 4000, 2 },
{ "2.2", 22, 20250, 1620, 8100, 4000, 4000, 2 },
{ "3", 30, 40500, 1620, 8100, 10000, 10000, 2 },
{ "3.1", 31, 108000, 3600, 18000, 14000, 14000, 4 },
{ "3.2", 32, 216000, 5120, 20480, 20000, 20000, 4 },
{ "4", 40, 245760, 8192, 32768, 20000, 25000, 4 },
{ "4.1", 41, 245760, 8192, 32768, 50000, 62500, 2 },
{ "4.2", 42, 522240, 8704, 34816, 50000, 62500, 2 },
{ "5", 50, 589824, 22080, 110400, 135000, 135000, 2 },
{ "5.1", 51, 983040, 36864, 184320, 240000, 240000, 2 },
{ "5.2", 52, 2073600, 36864, 184320, 240000, 240000, 2 },
{ "6", 60, 4177920, 139264, 696320, 240000, 240000, 2 },
{ "6.1", 61, 8355840, 139264, 696320, 480000, 480000, 2 },
{ "6.2", 62, 16711680, 139264, 696320, 800000, 800000, 2 },
};
/* *INDENT-ON* */
static const gchar *
_slice_type_name (GstH264SliceType type)
{
switch (type) {
case GST_H264_P_SLICE:
return "P";
case GST_H264_B_SLICE:
return "B";
case GST_H264_I_SLICE:
return "I";
default:
g_assert_not_reached ();
}
return NULL;
}
static const gchar *
_rate_control_get_name (guint32 rc_mode)
{
switch (rc_mode) {
case VA_RC_CBR:
return "cbr";
case VA_RC_VBR:
return "vbr";
case VA_RC_VCM:
return "vcm";
case VA_RC_CQP:
return "cqp";
default:
return "unknown";
}
g_assert_not_reached ();
return NULL;
}
/**
* GstVaH264EncRateControl:
*
* Since: 1.22
*/
static GType
gst_va_h264_enc_rate_control_get_type (void)
{
static gsize type = 0;
static const GEnumValue values[] = {
{VA_RC_CBR, "Constant Bitrate", "cbr"},
{VA_RC_VBR, "Variable Bitrate", "vbr"},
{VA_RC_VCM, "Video Conferencing Mode (Non HRD compliant)", "vcm"},
{VA_RC_CQP, "Constant Quantizer", "cqp"},
{0, NULL, NULL}
};
if (g_once_init_enter (&type)) {
GType _type;
_type = g_enum_register_static ("GstVaH264EncRateControl", values);
g_once_init_leave (&type, _type);
}
return type;
}
/**
* GstVaH264Mbbrc:
*
* Since: 1.22
*/
static GType
gst_va_h264_enc_mbbrc_get_type (void)
{
static gsize type = 0;
static const GEnumValue values[] = {
{0, "Auto choose", "auto"},
{1, "Always enable", "enable"},
{2, "Always disable", "disable"},
{0, NULL, NULL}
};
if (g_once_init_enter (&type)) {
GType _type;
_type = g_enum_register_static ("GstVaH264Mbbrc", values);
g_once_init_leave (&type, _type);
}
return type;
}
static GstVaH264EncFrame *
gst_va_enc_frame_new (void)
{
GstVaH264EncFrame *frame;
frame = g_slice_new (GstVaH264EncFrame);
frame->last_frame = FALSE;
frame->frame_num = 0;
frame->unused_for_reference_pic_num = -1;
frame->picture = NULL;
frame->total_frame_count = 0;
return frame;
}
static void
gst_va_enc_frame_free (gpointer pframe)
{
GstVaH264EncFrame *frame = pframe;
g_clear_pointer (&frame->picture, gst_va_encode_picture_free);
g_slice_free (GstVaH264EncFrame, frame);
}
static inline GstVaH264EncFrame *
_enc_frame (GstVideoCodecFrame * frame)
{
GstVaH264EncFrame *enc_frame = gst_video_codec_frame_get_user_data (frame);
g_assert (enc_frame);
return enc_frame;
}
/* Normalizes bitrate (and CPB size) for HRD conformance */
static void
_calculate_bitrate_hrd (GstVaH264Enc * self)
{
guint bitrate_bits, cpb_bits_size;
/* Round down bitrate. This is a hard limit mandated by the user */
g_assert (SX_BITRATE >= 6);
bitrate_bits = (self->rc.max_bitrate * 1000) & ~((1U << SX_BITRATE) - 1);
GST_DEBUG_OBJECT (self, "Max bitrate: %u bits/sec", bitrate_bits);
self->rc.max_bitrate_bits = bitrate_bits;
bitrate_bits = (self->rc.target_bitrate * 1000) & ~((1U << SX_BITRATE) - 1);
GST_DEBUG_OBJECT (self, "Target bitrate: %u bits/sec", bitrate_bits);
self->rc.target_bitrate_bits = bitrate_bits;
if (self->rc.cpb_size > 0 && self->rc.cpb_size < (self->rc.max_bitrate / 2)) {
GST_INFO_OBJECT (self, "Too small cpb_size: %d", self->rc.cpb_size);
self->rc.cpb_size = 0;
}
if (self->rc.cpb_size == 0) {
/* We cache 2 second coded data by default. */
self->rc.cpb_size = self->rc.max_bitrate * 2;
GST_INFO_OBJECT (self, "Adjust cpb_size to: %d", self->rc.cpb_size);
}
/* Round up CPB size. This is an HRD compliance detail */
g_assert (SX_CPB_SIZE >= 4);
cpb_bits_size = (self->rc.cpb_size * 1000) & ~((1U << SX_CPB_SIZE) - 1);
GST_DEBUG_OBJECT (self, "HRD CPB size: %u bits", cpb_bits_size);
self->rc.cpb_length_bits = cpb_bits_size;
}
/* Estimates a good enough bitrate if none was supplied */
static void
_ensure_rate_control (GstVaH264Enc * self)
{
/* User can specify the properties of: "bitrate", "target-percentage",
* "max-qp", "min-qp", "qpi", "qpp", "qpb", "mbbrc", "cpb-size",
* "rate-control" and "target-usage" to control the RC behavior.
*
* "target-usage" is different from the others, it controls the encoding
* speed and quality, while the others control encoding bit rate and
* quality. The lower value has better quality(maybe bigger MV search
* range) but slower speed, the higher value has faster speed but lower
* quality.
*
* The possible composition to control the bit rate and quality:
*
* 1. CQP mode: "rate-control=cqp", then "qpi", "qpp" and "qpb"
* specify the QP of I/P/B frames respectively(within the
* "max-qp" and "min-qp" range). The QP will not change during
* the whole stream. Other properties are ignored.
*
* 2. CBR mode: "rate-control=CBR", then the "bitrate" specify the
* target bit rate and the "cpb-size" specifies the max coded
* picture buffer size to avoid overflow. If the "bitrate" is not
* set, it is calculated by the picture resolution and frame
* rate. If "cpb-size" is not set, it is set to the size of
* caching 2 second coded data. Encoder will try its best to make
* the QP with in the ["max-qp", "min-qp"] range. "mbbrc" can
* enable bit rate control in macro block level. Other paramters
* are ignored.
*
* 3. VBR mode: "rate-control=VBR", then the "bitrate" specify the
* target bit rate, "target-percentage" is used to calculate the
* max bit rate of VBR mode by ("bitrate" * 100) /
* "target-percentage". It is also used by driver to calculate
* the min bit rate. The "cpb-size" specifies the max coded
* picture buffer size to avoid overflow. If the "bitrate" is not
* set, the target bit rate will be calculated by the picture
* resolution and frame rate. Encoder will try its best to make
* the QP with in the ["max-qp", "min-qp"] range. "mbbrc" can
* enable bit rate control in macro block level. Other paramters
* are ignored.
*
* 4. VCM mode: "rate-control=VCM", then the "bitrate" specify the
* target bit rate, and encoder will try its best to make the QP
* with in the ["max-qp", "min-qp"] range. Other paramters are
* ignored.
*/
guint bitrate;
guint32 rc_mode;
guint32 quality_level;
quality_level = gst_va_encoder_get_quality_level (self->encoder,
self->profile, self->entrypoint);
if (self->rc.target_usage > quality_level) {
GST_INFO_OBJECT (self, "User setting target-usage: %d is not supported, "
"fallback to %d", self->rc.target_usage, quality_level);
self->rc.target_usage = quality_level;
self->prop.target_usage = self->rc.target_usage;
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TARGET_USAGE]);
}
/* TODO: find a better heuristics to infer a nearer control mode */
rc_mode = gst_va_encoder_get_rate_control_mode (self->encoder,
self->profile, self->entrypoint);
if (!(rc_mode & self->prop.rc_ctrl)) {
GST_INFO_OBJECT (self, "The race control mode %s is not supported, "
"fallback to %s mode",
_rate_control_get_name (self->prop.rc_ctrl),
_rate_control_get_name (VA_RC_CQP));
self->rc.rc_ctrl_mode = VA_RC_CQP;
self->prop.rc_ctrl = self->rc.rc_ctrl_mode;
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_RATE_CONTROL]);
}
if (self->rc.min_qp > self->rc.max_qp) {
GST_INFO_OBJECT (self, "The min_qp %d is bigger than the max_qp %d, "
"set it to the max_qp", self->rc.min_qp, self->rc.max_qp);
self->rc.min_qp = self->rc.max_qp;
self->prop.min_qp = self->rc.min_qp;
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MIN_QP]);
}
/* Make all the qp in the valid range */
if (self->rc.qp_i < self->rc.min_qp) {
if (self->rc.qp_i != 26)
GST_INFO_OBJECT (self, "The qp_i %d is smaller than the min_qp %d, "
"set it to the min_qp", self->rc.qp_i, self->rc.min_qp);
self->rc.qp_i = self->rc.min_qp;
}
if (self->rc.qp_i > self->rc.max_qp) {
if (self->rc.qp_i != 26)
GST_INFO_OBJECT (self, "The qp_i %d is bigger than the max_qp %d, "
"set it to the max_qp", self->rc.qp_i, self->rc.max_qp);
self->rc.qp_i = self->rc.max_qp;
}
if (self->rc.qp_p < self->rc.min_qp) {
if (self->rc.qp_p != 26)
GST_INFO_OBJECT (self, "The qp_p %d is smaller than the min_qp %d, "
"set it to the min_qp", self->rc.qp_p, self->rc.min_qp);
self->rc.qp_p = self->rc.min_qp;
}
if (self->rc.qp_p > self->rc.max_qp) {
if (self->rc.qp_p != 26)
GST_INFO_OBJECT (self, "The qp_p %d is bigger than the max_qp %d, "
"set it to the max_qp", self->rc.qp_p, self->rc.max_qp);
self->rc.qp_p = self->rc.max_qp;
}
if (self->rc.qp_b < self->rc.min_qp) {
if (self->rc.qp_b != 26)
GST_INFO_OBJECT (self, "The qp_b %d is smaller than the min_qp %d, "
"set it to the min_qp", self->rc.qp_b, self->rc.min_qp);
self->rc.qp_b = self->rc.min_qp;
}
if (self->rc.qp_b > self->rc.max_qp) {
if (self->rc.qp_b != 26)
GST_INFO_OBJECT (self, "The qp_b %d is bigger than the max_qp %d, "
"set it to the max_qp", self->rc.qp_b, self->rc.max_qp);
self->rc.qp_b = self->rc.max_qp;
}
bitrate = self->prop.bitrate;
/* Calculate a bitrate is not set. */
if ((self->rc.rc_ctrl_mode == VA_RC_CBR || self->rc.rc_ctrl_mode == VA_RC_VBR
|| self->rc.rc_ctrl_mode == VA_RC_VCM) && bitrate == 0) {
/* Default compression: 48 bits per macroblock in "high-compression" mode */
guint bits_per_mb = 48;
guint64 factor;
/* According to the literature and testing, CABAC entropy coding
* mode could provide for +10% to +18% improvement in general,
* thus estimating +15% here ; and using adaptive 8x8 transforms
* in I-frames could bring up to +10% improvement. */
if (!self->use_cabac)
bits_per_mb += (bits_per_mb * 15) / 100;
if (!self->use_dct8x8)
bits_per_mb += (bits_per_mb * 10) / 100;
factor = (guint64) self->mb_width * self->mb_height * bits_per_mb;
bitrate = gst_util_uint64_scale (factor,
GST_VIDEO_INFO_FPS_N (&self->in_info),
GST_VIDEO_INFO_FPS_D (&self->in_info)) / 1000;
GST_INFO_OBJECT (self, "target bitrate computed to %u kbps", bitrate);
}
/* Adjust the setting based on RC mode. */
switch (self->rc.rc_ctrl_mode) {
case VA_RC_CQP:
self->rc.max_bitrate = 0;
self->rc.target_bitrate = 0;
self->rc.target_percentage = 0;
self->rc.cpb_size = 0;
break;
case VA_RC_CBR:
self->rc.max_bitrate = bitrate;
self->rc.target_bitrate = bitrate;
self->rc.target_percentage = 100;
self->rc.qp_i = self->rc.qp_p = self->rc.qp_b = 26;
break;
case VA_RC_VBR:
g_assert (self->rc.target_percentage >= 10);
self->rc.max_bitrate = (guint) gst_util_uint64_scale_int (bitrate,
100, self->rc.target_percentage);
self->rc.target_bitrate = bitrate;
self->rc.qp_i = self->rc.qp_p = self->rc.qp_b = 26;
break;
case VA_RC_VCM:
self->rc.max_bitrate = bitrate;
self->rc.target_bitrate = bitrate;
self->rc.target_percentage = 0;
self->rc.qp_i = self->rc.qp_p = self->rc.qp_b = 26;
self->rc.cpb_size = 0;
if (self->gop.num_bframes > 0) {
GST_INFO_OBJECT (self, "VCM mode just support I/P mode, no B frame");
self->gop.num_bframes = 0;
self->gop.b_pyramid = FALSE;
}
break;
}
if (self->rc.rc_ctrl_mode != VA_RC_CQP)
_calculate_bitrate_hrd (self);
/* notifications */
if (self->rc.cpb_size != self->prop.cpb_size) {
self->prop.cpb_size = self->rc.cpb_size;
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_CPB_SIZE]);
}
if (self->prop.target_percentage != self->rc.target_percentage) {
self->prop.target_percentage = self->rc.target_percentage;
g_object_notify_by_pspec (G_OBJECT (self),
properties[PROP_TARGET_PERCENTAGE]);
}
if (self->prop.qp_i != self->rc.qp_i) {
self->prop.qp_i = self->rc.qp_i;
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_QP_I]);
}
if (self->prop.qp_p != self->rc.qp_p) {
self->prop.qp_p = self->rc.qp_p;
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_QP_P]);
}
if (self->prop.qp_b != self->rc.qp_b) {
self->prop.qp_b = self->rc.qp_b;
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_QP_B]);
}
}
static guint
_get_h264_cpb_nal_factor (VAProfile profile)
{
guint f;
/* Table A-2 */
switch (profile) {
case VAProfileH264High:
f = 1500;
break;
case VAProfileH264ConstrainedBaseline:
case VAProfileH264Main:
f = 1200;
break;
case VAProfileH264MultiviewHigh:
case VAProfileH264StereoHigh:
f = 1500; /* H.10.2.1 (r) */
break;
default:
g_assert_not_reached ();
f = 1200;
break;
}
return f;
}
/* Derives the level from the currently set limits */
static gboolean
_calculate_level (GstVaH264Enc * self)
{
const guint cpb_factor = _get_h264_cpb_nal_factor (self->profile);
guint i, PicSizeMbs, MaxDpbMbs, MaxMBPS;
PicSizeMbs = self->mb_width * self->mb_height;
MaxDpbMbs = PicSizeMbs * (self->gop.num_ref_frames + 1);
MaxMBPS = gst_util_uint64_scale_int_ceil (PicSizeMbs,
GST_VIDEO_INFO_FPS_N (&self->in_info),
GST_VIDEO_INFO_FPS_D (&self->in_info));
for (i = 0; i < G_N_ELEMENTS (_va_h264_level_limits); i++) {
const GstVaH264LevelLimits *const limits = &_va_h264_level_limits[i];
if (PicSizeMbs <= limits->MaxFS && MaxDpbMbs <= limits->MaxDpbMbs
&& MaxMBPS <= limits->MaxMBPS && (!self->rc.max_bitrate_bits
|| self->rc.max_bitrate_bits <= (limits->MaxBR * 1000 * cpb_factor))
&& (!self->rc.cpb_length_bits
|| self->rc.cpb_length_bits <=
(limits->MaxCPB * 1000 * cpb_factor))) {
self->level_idc = _va_h264_level_limits[i].level_idc;
self->level_str = _va_h264_level_limits[i].name;
self->min_cr = _va_h264_level_limits[i].MinCR;
return TRUE;
}
}
GST_ERROR_OBJECT (self,
"failed to find a suitable level matching codec config");
return FALSE;
}
static void
_validate_parameters (GstVaH264Enc * self)
{
gint32 max_slices;
/* Ensure the num_slices provided by the user not exceed the limit
* of the number of slices permitted by the stream and by the
* hardware. */
g_assert (self->num_slices >= 1);
max_slices = gst_va_encoder_get_max_slice_num (self->encoder,
self->profile, self->entrypoint);
if (self->num_slices > max_slices)
self->num_slices = max_slices;
/* The stream size limit. */
if (self->num_slices > ((self->mb_width * self->mb_height + 1) / 2))
self->num_slices = ((self->mb_width * self->mb_height + 1) / 2);
if (self->prop.num_slices != self->num_slices) {
self->prop.num_slices = self->num_slices;
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_NUM_SLICES]);
}
/* Ensure trellis. */
if (self->use_trellis &&
!gst_va_encoder_has_trellis (self->encoder, self->profile,
self->entrypoint)) {
GST_INFO_OBJECT (self, "The trellis is not supported");
self->use_trellis = FALSE;
}
if (self->prop.use_trellis != self->use_trellis) {
self->prop.use_trellis = self->use_trellis;
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TRELLIS]);
}
}
/* Get log2_max_frame_num_minus4, log2_max_pic_order_cnt_lsb_minus4
* value, shall be in the range of 0 to 12, inclusive. */
static guint
_get_log2_max_num (guint num)
{
guint ret = 0;
while (num) {
++ret;
num >>= 1;
}
/* shall be in the range of 0+4 to 12+4, inclusive. */
if (ret < 4) {
ret = 4;
} else if (ret > 16) {
ret = 16;
}
return ret;
}
static void
_print_gop_structure (GstVaH264Enc * self)
{
#ifndef GST_DISABLE_GST_DEBUG
GString *str;
gint i;
if (gst_debug_category_get_threshold (GST_CAT_DEFAULT) < GST_LEVEL_INFO)
return;
str = g_string_new (NULL);
g_string_append_printf (str, "[ ");
for (i = 0; i < self->gop.idr_period; i++) {
if (i == 0) {
g_string_append_printf (str, "IDR");
continue;
} else {
g_string_append_printf (str, ", ");
}
g_string_append_printf (str, "%s",
_slice_type_name (self->gop.frame_types[i].slice_type));
if (self->gop.b_pyramid
&& self->gop.frame_types[i].slice_type == GST_H264_B_SLICE) {
g_string_append_printf (str, "<L%d (%d, %d)>",
self->gop.frame_types[i].pyramid_level,
self->gop.frame_types[i].left_ref_poc_diff,
self->gop.frame_types[i].right_ref_poc_diff);
}
if (self->gop.frame_types[i].is_ref) {
g_string_append_printf (str, "(ref)");
}
}
g_string_append_printf (str, " ]");
GST_INFO_OBJECT (self, "GOP size: %d, forward reference %d, backward"
" reference %d, GOP structure: %s", self->gop.idr_period,
self->gop.ref_num_list0, self->gop.ref_num_list1, str->str);
g_string_free (str, TRUE);
#endif
}
struct PyramidInfo
{
guint level;
gint left_ref_poc_diff;
gint right_ref_poc_diff;
};
static void
_set_pyramid_info (struct PyramidInfo *info, guint len, guint level)
{
guint index;
g_assert (len >= 1);
if (level == 0 || len == 1) {
for (index = 0; index < len; index++) {
info[index].level = level;
info[index].left_ref_poc_diff = (index + 1) * -2;
info[index].right_ref_poc_diff = (len - index) * 2;
}
return;
}
index = len / 2;
info[index].level = level;
info[index].left_ref_poc_diff = (index + 1) * -2;
info[index].right_ref_poc_diff = (len - index) * 2;
level--;
if (index > 0)
_set_pyramid_info (info, index, level);
if (index + 1 < len)
_set_pyramid_info (&info[index + 1], len - (index + 1), level);
}
static void
_create_gop_frame_types (GstVaH264Enc * self)
{
guint i;
guint i_frames = self->gop.num_iframes;
struct PyramidInfo pyramid_info[31] = { 0, };
if (self->gop.highest_pyramid_level > 0) {
g_assert (self->gop.num_bframes > 0);
_set_pyramid_info (pyramid_info, self->gop.num_bframes,
self->gop.highest_pyramid_level);
}
g_assert (self->gop.idr_period <= MAX_GOP_SIZE);
for (i = 0; i < self->gop.idr_period; i++) {
if (i == 0) {
self->gop.frame_types[i].slice_type = GST_H264_I_SLICE;
self->gop.frame_types[i].is_ref = TRUE;
continue;
}
/* Intra only stream. */
if (self->gop.ip_period == 0) {
self->gop.frame_types[i].slice_type = GST_H264_I_SLICE;
self->gop.frame_types[i].is_ref = FALSE;
continue;
}
if (i % self->gop.ip_period) {
guint pyramid_index =
i % self->gop.ip_period - 1 /* The first P or IDR */ ;
self->gop.frame_types[i].slice_type = GST_H264_B_SLICE;
self->gop.frame_types[i].pyramid_level =
pyramid_info[pyramid_index].level;
self->gop.frame_types[i].is_ref =
(self->gop.frame_types[i].pyramid_level > 0);
self->gop.frame_types[i].left_ref_poc_diff =
pyramid_info[pyramid_index].left_ref_poc_diff;
self->gop.frame_types[i].right_ref_poc_diff =
pyramid_info[pyramid_index].right_ref_poc_diff;
continue;
}
if (self->gop.i_period && i % self->gop.i_period == 0 && i_frames > 0) {
/* Replace P with I. */
self->gop.frame_types[i].slice_type = GST_H264_I_SLICE;
self->gop.frame_types[i].is_ref = TRUE;
i_frames--;
continue;
}
self->gop.frame_types[i].slice_type = GST_H264_P_SLICE;
self->gop.frame_types[i].is_ref = TRUE;
}
/* Force the last one to be a P */
if (self->gop.idr_period > 1 && self->gop.ip_period > 0) {
self->gop.frame_types[self->gop.idr_period - 1].slice_type =
GST_H264_P_SLICE;
self->gop.frame_types[self->gop.idr_period - 1].is_ref = TRUE;
}
}
/* Consider the idr_period, num_bframes, L0/L1 reference number.
* TODO: Load some preset fixed GOP structure.
* TODO: Skip this if in lookahead mode. */
static void
_generate_gop_structure (GstVaH264Enc * self)
{
guint32 list0, list1, gop_ref_num;
gint32 p_frames;
/* If not set, generate a idr every second */
if (self->gop.idr_period == 0) {
self->gop.idr_period = (GST_VIDEO_INFO_FPS_N (&self->in_info)
+ GST_VIDEO_INFO_FPS_D (&self->in_info) - 1) /
GST_VIDEO_INFO_FPS_D (&self->in_info);
}
/* Do not use a too huge GOP size. */
if (self->gop.idr_period > 1024) {
self->gop.idr_period = 1024;
GST_INFO_OBJECT (self, "Lowering the GOP size to %d", self->gop.idr_period);
}
if (self->gop.idr_period != self->prop.key_int_max) {
self->prop.key_int_max = self->gop.idr_period;
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_KEY_INT_MAX]);
}
/* Prefer have more than 1 refs for the GOP which is not very small. */
if (self->gop.idr_period > 8) {
if (self->gop.num_bframes > (self->gop.idr_period - 1) / 2) {
self->gop.num_bframes = (self->gop.idr_period - 1) / 2;
GST_INFO_OBJECT (self, "Lowering the number of num_bframes to %d",
self->gop.num_bframes);
}
} else {
/* beign and end should be ref */
if (self->gop.num_bframes > self->gop.idr_period - 1 - 1) {
if (self->gop.idr_period > 1) {
self->gop.num_bframes = self->gop.idr_period - 1 - 1;
} else {
self->gop.num_bframes = 0;
}
GST_INFO_OBJECT (self, "Lowering the number of num_bframes to %d",
self->gop.num_bframes);
}
}
if (!gst_va_encoder_get_max_num_reference (self->encoder, self->profile,
self->entrypoint, &list0, &list1)) {
GST_INFO_OBJECT (self, "Failed to get the max num reference");
list0 = 1;
list1 = 0;
}
if (list0 > self->gop.num_ref_frames)
list0 = self->gop.num_ref_frames;
if (list1 > self->gop.num_ref_frames)
list1 = self->gop.num_ref_frames;
if (list0 == 0) {
GST_INFO_OBJECT (self,
"No reference support, fallback to intra only stream");
/* It does not make sense that if only the list1 exists. */
self->gop.num_ref_frames = 0;
self->gop.ip_period = 0;
self->gop.num_bframes = 0;
self->gop.b_pyramid = FALSE;
self->gop.highest_pyramid_level = 0;
self->gop.num_iframes = self->gop.idr_period - 1 /* The idr */ ;
self->gop.ref_num_list0 = 0;
self->gop.ref_num_list1 = 0;
goto create_poc;
}
if (self->gop.num_ref_frames <= 1) {
GST_INFO_OBJECT (self, "The number of reference frames is only %d,"
" no B frame allowed, fallback to I/P mode", self->gop.num_ref_frames);
self->gop.num_bframes = 0;
list1 = 0;
}
/* b_pyramid needs at least 1 ref for B, besides the I/P */
if (self->gop.b_pyramid && self->gop.num_ref_frames <= 2) {
GST_INFO_OBJECT (self, "The number of reference frames is only %d,"
" not enough for b_pyramid", self->gop.num_ref_frames);
self->gop.b_pyramid = FALSE;
}
if (list1 == 0 && self->gop.num_bframes > 0) {
GST_INFO_OBJECT (self,
"No hw reference support for list 1, fallback to I/P mode");
self->gop.num_bframes = 0;
self->gop.b_pyramid = FALSE;
}
/* I/P mode, no list1 needed. */
if (self->gop.num_bframes == 0)
list1 = 0;
/* Not enough B frame, no need for b_pyramid. */
if (self->gop.num_bframes <= 1)
self->gop.b_pyramid = FALSE;
/* b pyramid has only one backward ref. */
if (self->gop.b_pyramid)
list1 = 1;
if (self->gop.num_ref_frames > list0 + list1) {
self->gop.num_ref_frames = list0 + list1;
GST_INFO_OBJECT (self, "HW limits, lowering the number of reference"
" frames to %d", self->gop.num_ref_frames);
}
/* How many possible refs within a GOP. */
gop_ref_num = (self->gop.idr_period + self->gop.num_bframes) /
(self->gop.num_bframes + 1);
/* The end ref */
if (self->gop.num_bframes > 0
/* frame_num % (self->gop.num_bframes + 1) happens to be the end P */
&& (self->gop.idr_period % (self->gop.num_bframes + 1) != 1))
gop_ref_num++;
/* Adjust reference num based on B frames and B pyramid. */
if (self->gop.num_bframes == 0) {
self->gop.b_pyramid = FALSE;
self->gop.ref_num_list0 = self->gop.num_ref_frames;
self->gop.ref_num_list1 = 0;
} else if (self->gop.b_pyramid) {
guint b_frames = self->gop.num_bframes;
guint b_refs;
/* b pyramid has only one backward ref. */
g_assert (list1 == 1);
self->gop.ref_num_list1 = list1;
self->gop.ref_num_list0 =
self->gop.num_ref_frames - self->gop.ref_num_list1;
b_frames = b_frames / 2;
b_refs = 0;
while (b_frames) {
/* At least 1 B ref for each level, plus begin and end 2 P/I */
b_refs += 1;
if (b_refs + 2 > self->gop.num_ref_frames)
break;
self->gop.highest_pyramid_level++;
b_frames = b_frames / 2;
}
GST_INFO_OBJECT (self, "pyramid level is %d",
self->gop.highest_pyramid_level);
} else {
/* We prefer list0. Backward refs have more latency. */
self->gop.ref_num_list1 = 1;
self->gop.ref_num_list0 =
self->gop.num_ref_frames - self->gop.ref_num_list1;
/* Balance the forward and backward refs, but not cause a big latency. */
while ((self->gop.num_bframes * self->gop.ref_num_list1 <= 16)
&& (self->gop.ref_num_list1 <= gop_ref_num)
&& (self->gop.ref_num_list1 < list1)
&& (self->gop.ref_num_list0 / self->gop.ref_num_list1 > 4)) {
self->gop.ref_num_list0--;
self->gop.ref_num_list1++;
}
}
/* It's OK, keep slots for GST_VIDEO_CODEC_FRAME_IS_FORCE_KEYFRAME frame. */
if (self->gop.ref_num_list0 > gop_ref_num)
GST_DEBUG_OBJECT (self, "num_ref_frames %d is bigger than gop_ref_num %d",
self->gop.ref_num_list0, gop_ref_num);
/* Include the ref picture itself. */
self->gop.ip_period = 1 + self->gop.num_bframes;
p_frames = gop_ref_num - 1 /* IDR */ ;
if (p_frames < 0)
p_frames = 0;
if (self->gop.num_iframes > p_frames) {
self->gop.num_iframes = p_frames;
GST_INFO_OBJECT (self, "Too many I frames insertion, lowering it to %d",
self->gop.num_iframes);
}
if (self->gop.num_iframes > 0) {
guint total_i_frames = self->gop.num_iframes + 1 /* IDR */ ;
self->gop.i_period =
(gop_ref_num / total_i_frames) * (self->gop.num_bframes + 1);
}
create_poc:
/* init max_frame_num, max_poc */
self->gop.log2_max_frame_num = _get_log2_max_num (self->gop.idr_period);
self->gop.max_frame_num = (1 << self->gop.log2_max_frame_num);
self->gop.log2_max_pic_order_cnt = self->gop.log2_max_frame_num + 1;
self->gop.max_pic_order_cnt = (1 << self->gop.log2_max_pic_order_cnt);
self->gop.num_reorder_frames = self->gop.b_pyramid ?
self->gop.highest_pyramid_level * 2 + 1 /* the last P frame. */ :
self->gop.ref_num_list1;
/* Should not exceed the max ref num. */
self->gop.num_reorder_frames =
MIN (self->gop.num_reorder_frames, self->gop.num_ref_frames);
self->gop.num_reorder_frames = MIN (self->gop.num_reorder_frames, 16);
_create_gop_frame_types (self);
_print_gop_structure (self);
/* notifications */
if (self->prop.num_ref_frames != self->gop.num_ref_frames) {
self->prop.num_ref_frames = self->gop.num_ref_frames;
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_NUM_REF_FRAMES]);
}
if (self->prop.num_iframes != self->gop.num_iframes) {
self->prop.num_iframes = self->gop.num_iframes;
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_IFRAMES]);
}
}
static void
_calculate_coded_size (GstVaH264Enc * self)
{
guint codedbuf_size = 0;
if (self->profile == VAProfileH264High
|| self->profile == VAProfileH264MultiviewHigh
|| self->profile == VAProfileH264StereoHigh) {
/* The number of bits of macroblock_layer( ) data for any macroblock
is not greater than 128 + RawMbBits */
guint RawMbBits = 0;
guint BitDepthY = 8;
guint BitDepthC = 8;
guint MbWidthC = 8;
guint MbHeightC = 8;
switch (self->rt_format) {
case VA_RT_FORMAT_YUV420:
BitDepthY = 8;
BitDepthC = 8;
MbWidthC = 8;
MbHeightC = 8;
break;
case VA_RT_FORMAT_YUV422:
BitDepthY = 8;
BitDepthC = 8;
MbWidthC = 8;
MbHeightC = 16;
break;
case VA_RT_FORMAT_YUV444:
BitDepthY = 8;
BitDepthC = 8;
MbWidthC = 16;
MbHeightC = 16;
break;
case VA_RT_FORMAT_YUV400:
BitDepthY = 8;
BitDepthC = 0;
MbWidthC = 0;
MbHeightC = 0;
break;
case VA_RT_FORMAT_YUV420_10:
BitDepthY = 10;
BitDepthC = 10;
MbWidthC = 8;
MbHeightC = 8;
case VA_RT_FORMAT_YUV422_10:
BitDepthY = 10;
BitDepthC = 10;
MbWidthC = 8;
MbHeightC = 16;
case VA_RT_FORMAT_YUV444_10:
BitDepthY = 10;
BitDepthC = 10;
MbWidthC = 16;
MbHeightC = 16;
break;
default:
g_assert_not_reached ();
break;
}
/* The variable RawMbBits is derived as
* RawMbBits = 256 * BitDepthY + 2 * MbWidthC * MbHeightC * BitDepthC */
RawMbBits = 256 * BitDepthY + 2 * MbWidthC * MbHeightC * BitDepthC;
codedbuf_size = (self->mb_width * self->mb_height) * (128 + RawMbBits) / 8;
} else {
/* The number of bits of macroblock_layer( ) data for any macroblock
* is not greater than 3200 */
codedbuf_size = (self->mb_width * self->mb_height) * (3200 / 8);
}
/* Account for SPS header */
/* XXX: exclude scaling lists, MVC/SVC extensions */
codedbuf_size += 4 /* start code */ + GST_ROUND_UP_8 (MAX_SPS_HDR_SIZE +
MAX_VUI_PARAMS_SIZE + 2 * MAX_HRD_PARAMS_SIZE) / 8;
/* Account for PPS header */
/* XXX: exclude slice groups, scaling lists, MVC/SVC extensions */
codedbuf_size += 4 + GST_ROUND_UP_8 (MAX_PPS_HDR_SIZE) / 8;
/* Account for slice header */
codedbuf_size +=
self->num_slices * (4 + GST_ROUND_UP_8 (MAX_SLICE_HDR_SIZE) / 8);
/* Add 5% for safety */
self->codedbuf_size = (guint) ((gfloat) codedbuf_size * 1.05);
GST_DEBUG_OBJECT (self, "Calculate codedbuf size: %u", self->codedbuf_size);
}
static guint
_get_rtformat (GstVaH264Enc * self, GstVideoFormat format)
{
guint chroma;
chroma = gst_va_chroma_from_video_format (format);
/* Check whether the rtformat is supported. */
if (chroma != VA_RT_FORMAT_YUV420) {
GST_ERROR_OBJECT (self, "Unsupported chroma for video format: %s",
gst_video_format_to_string (format));
return 0;
}
return chroma;
}
static gboolean
_init_packed_headers (GstVaH264Enc * self)
{
guint32 packed_headers;
guint32 desired_packed_headers = VA_ENC_PACKED_HEADER_SEQUENCE /* SPS */
| VA_ENC_PACKED_HEADER_PICTURE /* PPS */
| VA_ENC_PACKED_HEADER_SLICE /* Slice headers */
| VA_ENC_PACKED_HEADER_RAW_DATA; /* SEI, AUD, etc. */
self->packed_headers = 0;
packed_headers = gst_va_encoder_get_packed_headers (self->encoder,
self->profile, self->entrypoint);
if (packed_headers == 0)
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
_decide_profile (GstVaH264Enc * self)
{
gboolean ret = FALSE;
GstVideoFormat in_format;
VAProfile profile;
guint rt_format;
GstCaps *allowed_caps = NULL;
guint num_structures, i;
GstStructure *structure;
const GValue *v_profile;
GPtrArray *candidates = NULL;
gchar *profile_name;
candidates = g_ptr_array_new_with_free_func (g_free);
/* First, check whether the downstream requires a specified profile. */
allowed_caps = gst_pad_get_allowed_caps (GST_VIDEO_ENCODER_SRC_PAD (self));
if (!allowed_caps)
allowed_caps = gst_pad_query_caps (GST_VIDEO_ENCODER_SRC_PAD (self), NULL);
if (allowed_caps && !gst_caps_is_empty (allowed_caps)) {
num_structures = gst_caps_get_size (allowed_caps);
for (i = 0; i < num_structures; i++) {
structure = gst_caps_get_structure (allowed_caps, i);
v_profile = gst_structure_get_value (structure, "profile");
if (!v_profile)
continue;
if (G_VALUE_HOLDS_STRING (v_profile)) {
profile_name = g_strdup (g_value_get_string (v_profile));
g_ptr_array_add (candidates, profile_name);
} else if (GST_VALUE_HOLDS_LIST (v_profile)) {
guint j;
for (j = 0; j < gst_value_list_get_size (v_profile); j++) {
const GValue *p = gst_value_list_get_value (v_profile, j);
if (!p)
continue;
profile_name = g_strdup (g_value_get_string (p));
g_ptr_array_add (candidates, profile_name);
}
}
}
}
if (candidates->len == 0) {
GST_ERROR_OBJECT (self, "No available profile in caps");
ret = FALSE;
goto out;
}
in_format = GST_VIDEO_INFO_FORMAT (&self->in_info);
rt_format = _get_rtformat (self, in_format);
if (!rt_format) {
GST_ERROR_OBJECT (self, "unsupported video format %s",
gst_video_format_to_string (in_format));
ret = FALSE;
goto out;
}
/* Find the suitable profile by features and check the HW
* support. */
ret = FALSE;
for (i = 0; i < candidates->len; i++) {
profile_name = g_ptr_array_index (candidates, i);
/* dct8x8 require at least high profile. */
if (self->use_dct8x8) {
if (!g_strstr_len (profile_name, -1, "high"))
continue;
}
/* cabac require at least main profile. */
if (self->use_cabac) {
if (!g_strstr_len (profile_name, -1, "main")
&& !g_strstr_len (profile_name, -1, "high"))
continue;
}
/* baseline only support I/P mode. */
if (self->gop.num_bframes > 0) {
if (g_strstr_len (profile_name, -1, "baseline"))
continue;
}
profile = gst_va_profile_from_name (H264, profile_name);
if (profile == VAProfileNone)
continue;
if (!gst_va_encoder_has_profile_and_entrypoint (self->encoder,
profile, VAEntrypointEncSlice))
continue;
if ((rt_format & gst_va_encoder_get_rtformat (self->encoder,
profile, VAEntrypointEncSlice)) == 0)
continue;
self->profile = profile;
self->entrypoint = VAEntrypointEncSlice;
self->rt_format = rt_format;
ret = TRUE;
goto out;
}
/* Just use the first HW available profile and disable features if
* needed. */
profile_name = NULL;
for (i = 0; i < candidates->len; i++) {
profile_name = g_ptr_array_index (candidates, i);
profile = gst_va_profile_from_name (H264, profile_name);
if (profile == VAProfileNone)
continue;
if (!gst_va_encoder_has_profile_and_entrypoint (self->encoder,
profile, VAEntrypointEncSlice))
continue;
if ((rt_format & gst_va_encoder_get_rtformat (self->encoder,
profile, VAEntrypointEncSlice)) == 0)
continue;
self->profile = profile;
self->entrypoint = VAEntrypointEncSlice;
self->rt_format = rt_format;
ret = TRUE;
}
if (ret == FALSE)
goto out;
if (self->use_dct8x8 && !g_strstr_len (profile_name, -1, "high")) {
GST_INFO_OBJECT (self, "Disable dct8x8, profile %s does not support it",
gst_va_profile_name (self->profile));
self->use_dct8x8 = FALSE;
self->prop.use_dct8x8 = FALSE;
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_DCT8X8]);
}
if (self->use_cabac && (!g_strstr_len (profile_name, -1, "main")
&& !g_strstr_len (profile_name, -1, "high"))) {
GST_INFO_OBJECT (self, "Disable cabac, profile %s does not support it",
gst_va_profile_name (self->profile));
self->use_cabac = FALSE;
self->prop.use_cabac = FALSE;
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_CABAC]);
}
if (self->gop.num_bframes > 0 && g_strstr_len (profile_name, -1, "baseline")) {
GST_INFO_OBJECT (self, "No B frames, profile %s does not support it",
gst_va_profile_name (self->profile));
self->gop.num_bframes = 0;
self->gop.b_pyramid = 0;
}
out:
g_clear_pointer (&candidates, g_ptr_array_unref);
g_clear_pointer (&allowed_caps, gst_caps_unref);
if (ret) {
GST_INFO_OBJECT (self, "Select the profile %s",
gst_va_profile_name (profile));
} else {
GST_ERROR_OBJECT (self, "Failed to find an available profile");
}
return ret;
}
/* Clear all the info of last reconfig and set the fields based on
* property. The reconfig may change these fields because of the
* profile/level and HW limitation. */
static void
gst_va_h264_enc_reset_state (GstVaH264Enc * self)
{
self->width = 0;
self->height = 0;
self->profile = VAProfileNone;
self->entrypoint = 0;
self->rt_format = 0;
self->codedbuf_size = 0;
self->frame_duration = GST_CLOCK_TIME_NONE;
self->input_frame_count = 0;
self->output_frame_count = 0;
self->level_idc = 0;
self->level_str = NULL;
self->mb_width = 0;
self->mb_height = 0;
self->use_cabac = self->prop.use_cabac;
self->use_dct8x8 = self->prop.use_dct8x8;
self->use_trellis = self->prop.use_trellis;
self->num_slices = self->prop.num_slices;
self->gop.idr_period = self->prop.key_int_max;
self->gop.i_period = 0;
self->gop.total_idr_count = 0;
self->gop.ip_period = 0;
self->gop.num_bframes = self->prop.num_bframes;
self->gop.b_pyramid = self->prop.b_pyramid;
self->gop.highest_pyramid_level = 0;
self->gop.num_iframes = self->prop.num_iframes;
memset (self->gop.frame_types, 0, sizeof (self->gop.frame_types));
self->gop.cur_frame_index = 0;
self->gop.cur_frame_num = 0;
self->gop.max_frame_num = 0;
self->gop.log2_max_frame_num = 0;
self->gop.max_pic_order_cnt = 0;
self->gop.log2_max_pic_order_cnt = 0;
self->gop.num_ref_frames = self->prop.num_ref_frames;
self->gop.ref_num_list0 = 0;
self->gop.ref_num_list1 = 0;
self->gop.num_reorder_frames = 0;
self->rc.rc_ctrl_mode = self->prop.rc_ctrl;
self->rc.min_qp = self->prop.min_qp;
self->rc.max_qp = self->prop.max_qp;
self->rc.qp_i = self->prop.qp_i;
self->rc.qp_p = self->prop.qp_p;
self->rc.qp_b = self->prop.qp_b;
self->rc.mbbrc = self->prop.mbbrc;
self->rc.max_bitrate = 0;
self->rc.target_bitrate = 0;
self->rc.target_percentage = self->prop.target_percentage;
self->rc.target_usage = self->prop.target_usage;
self->rc.max_bitrate_bits = 0;
self->rc.target_bitrate_bits = 0;
self->rc.cpb_size = self->prop.cpb_size;
self->rc.cpb_length_bits = 0;
memset (&self->sequence_hdr, 0, sizeof (GstH264SPS));
}
static gboolean
gst_va_h264_enc_reconfig (GstVaH264Enc * self)
{
gst_va_h264_enc_reset_state (self);
self->width = GST_VIDEO_INFO_WIDTH (&self->in_info);
self->height = GST_VIDEO_INFO_HEIGHT (&self->in_info);
self->mb_width = GST_ROUND_UP_16 (self->width) / 16;
self->mb_height = GST_ROUND_UP_16 (self->height) / 16;
/* Frame rate is needed for rate control and PTS setting. */
if (GST_VIDEO_INFO_FPS_N (&self->in_info) == 0
|| GST_VIDEO_INFO_FPS_D (&self->in_info) == 0) {
GST_INFO_OBJECT (self, "Unknown framerate, just set to 30 fps");
GST_VIDEO_INFO_FPS_N (&self->in_info) = 30;
GST_VIDEO_INFO_FPS_D (&self->in_info) = 1;
}
self->frame_duration = gst_util_uint64_scale (GST_SECOND,
GST_VIDEO_INFO_FPS_D (&self->in_info),
GST_VIDEO_INFO_FPS_N (&self->in_info));
GST_DEBUG_OBJECT (self, "resolution:%dx%d, MB size: %dx%d,"
" frame duration is %" GST_TIME_FORMAT,
self->width, self->height, self->mb_width, self->mb_height,
GST_TIME_ARGS (self->frame_duration));
if (!_decide_profile (self))
return FALSE;
_validate_parameters (self);
_ensure_rate_control (self);
if (!_calculate_level (self))
return FALSE;
_generate_gop_structure (self);
_calculate_coded_size (self);
/* notifications */
/* num_bframes are modified several times before */
if (self->prop.num_bframes != self->gop.num_bframes) {
self->prop.num_bframes = self->gop.num_bframes;
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_BFRAMES]);
}
if (self->prop.b_pyramid != self->gop.b_pyramid) {
self->prop.b_pyramid = self->gop.b_pyramid;
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_B_PYRAMID]);
}
if (!_init_packed_headers (self))
return FALSE;
return TRUE;
}
static gboolean
gst_va_h264_enc_push_frame (GstVaH264Enc * self, GstVideoCodecFrame * gst_frame,
gboolean last)
{
GstVaH264EncFrame *frame;
g_return_val_if_fail (self->gop.cur_frame_index <= self->gop.idr_period,
FALSE);
if (gst_frame) {
/* Begin a new GOP, should have a empty reorder_list. */
if (self->gop.cur_frame_index == self->gop.idr_period) {
g_assert (g_queue_is_empty (&self->reorder_list));
self->gop.cur_frame_index = 0;
self->gop.cur_frame_num = 0;
}
frame = _enc_frame (gst_frame);
frame->poc =
((self->gop.cur_frame_index * 2) % self->gop.max_pic_order_cnt);
if (self->gop.cur_frame_index == 0) {
g_assert (frame->poc == 0);
GST_LOG_OBJECT (self, "system_frame_number: %d, an IDR frame, starts"
" a new GOP", gst_frame->system_frame_number);
g_queue_clear_full (&self->ref_list,
(GDestroyNotify) gst_video_codec_frame_unref);
}
frame->type = self->gop.frame_types[self->gop.cur_frame_index].slice_type;
frame->is_ref = self->gop.frame_types[self->gop.cur_frame_index].is_ref;
frame->pyramid_level =
self->gop.frame_types[self->gop.cur_frame_index].pyramid_level;
frame->left_ref_poc_diff =
self->gop.frame_types[self->gop.cur_frame_index].left_ref_poc_diff;
frame->right_ref_poc_diff =
self->gop.frame_types[self->gop.cur_frame_index].right_ref_poc_diff;
if (GST_VIDEO_CODEC_FRAME_IS_FORCE_KEYFRAME (gst_frame)) {
GST_DEBUG_OBJECT (self, "system_frame_number: %d, a force key frame,"
" promote its type from %s to %s", gst_frame->system_frame_number,
_slice_type_name (frame->type), _slice_type_name (GST_H264_I_SLICE));
frame->type = GST_H264_I_SLICE;
frame->is_ref = TRUE;
}
GST_LOG_OBJECT (self, "Push frame, system_frame_number: %d, poc %d, "
"frame type %s", gst_frame->system_frame_number, frame->poc,
_slice_type_name (frame->type));
self->gop.cur_frame_index++;
g_queue_push_tail (&self->reorder_list,
gst_video_codec_frame_ref (gst_frame));
}
/* ensure the last one a non-B and end the GOP. */
if (last && self->gop.cur_frame_index < self->gop.idr_period) {
GstVideoCodecFrame *last_frame;
/* Ensure next push will start a new GOP. */
self->gop.cur_frame_index = self->gop.idr_period;
if (!g_queue_is_empty (&self->reorder_list)) {
last_frame = g_queue_peek_tail (&self->reorder_list);
frame = _enc_frame (last_frame);
if (frame->type == GST_H264_B_SLICE) {
frame->type = GST_H264_P_SLICE;
frame->is_ref = TRUE;
}
}
}
return TRUE;
}
struct RefFramesCount
{
gint poc;
guint num;
};
static void
_count_backward_ref_num (gpointer data, gpointer user_data)
{
GstVaH264EncFrame *frame = _enc_frame (data);
struct RefFramesCount *count = (struct RefFramesCount *) user_data;
g_assert (frame->poc != count->poc);
if (frame->poc > count->poc)
count->num++;
}
static GstVideoCodecFrame *
_pop_pyramid_b_frame (GstVaH264Enc * self)
{
guint i;
gint index = -1;
GstVaH264EncFrame *b_vaframe;
GstVideoCodecFrame *b_frame;
struct RefFramesCount count;
g_assert (self->gop.ref_num_list1 == 1);
b_frame = NULL;
b_vaframe = NULL;
/* Find the lowest level with smallest poc. */
for (i = 0; i < g_queue_get_length (&self->reorder_list); i++) {
GstVaH264EncFrame *vaf;
GstVideoCodecFrame *f;
f = g_queue_peek_nth (&self->reorder_list, i);
if (!b_frame) {
b_frame = f;
b_vaframe = _enc_frame (b_frame);
index = i;
continue;
}
vaf = _enc_frame (f);
if (b_vaframe->pyramid_level > vaf->pyramid_level) {
b_frame = f;
b_vaframe = vaf;
index = i;
continue;
}
if (b_vaframe->poc > vaf->poc) {
b_frame = f;
b_vaframe = vaf;
index = i;
}
}
again:
/* Check whether its refs are already poped. */
g_assert (b_vaframe->left_ref_poc_diff != 0);
g_assert (b_vaframe->right_ref_poc_diff != 0);
for (i = 0; i < g_queue_get_length (&self->reorder_list); i++) {
GstVaH264EncFrame *vaf;
GstVideoCodecFrame *f;
f = g_queue_peek_nth (&self->reorder_list, i);
if (f == b_frame)
continue;
vaf = _enc_frame (f);
if (vaf->poc == b_vaframe->poc + b_vaframe->left_ref_poc_diff
|| vaf->poc == b_vaframe->poc + b_vaframe->right_ref_poc_diff) {
b_frame = f;
b_vaframe = vaf;
index = i;
goto again;
}
}
/* Ensure we already have enough backward refs */
count.num = 0;
count.poc = b_vaframe->poc;
g_queue_foreach (&self->ref_list, (GFunc) _count_backward_ref_num, &count);
if (count.num >= self->gop.ref_num_list1) {
GstVideoCodecFrame *f;
/* it will unref at pop_frame */
f = g_queue_pop_nth (&self->reorder_list, index);
g_assert (f == b_frame);
} else {
b_frame = NULL;
}
return b_frame;
}
static gboolean
gst_va_h264_enc_pop_frame (GstVaH264Enc * self, GstVideoCodecFrame ** out_frame)
{
GstVaH264EncFrame *vaframe;
GstVideoCodecFrame *frame;
struct RefFramesCount count;
g_return_val_if_fail (self->gop.cur_frame_index <= self->gop.idr_period,
FALSE);
*out_frame = NULL;
if (g_queue_is_empty (&self->reorder_list))
return TRUE;
/* Return the last pushed non-B immediately. */
frame = g_queue_peek_tail (&self->reorder_list);
vaframe = _enc_frame (frame);
if (vaframe->type != GST_H264_B_SLICE) {
frame = g_queue_pop_tail (&self->reorder_list);
goto get_one;
}
if (self->gop.b_pyramid) {
frame = _pop_pyramid_b_frame (self);
if (frame == NULL)
return TRUE;
goto get_one;
}
g_assert (self->gop.ref_num_list1 > 0);
/* If GOP end, pop anyway. */
if (self->gop.cur_frame_index == self->gop.idr_period) {
frame = g_queue_pop_head (&self->reorder_list);
goto get_one;
}
/* Ensure we already have enough backward refs */
frame = g_queue_peek_head (&self->reorder_list);
vaframe = _enc_frame (frame);
count.num = 0;
count.poc = vaframe->poc;
g_queue_foreach (&self->ref_list, _count_backward_ref_num, &count);
if (count.num >= self->gop.ref_num_list1) {
frame = g_queue_pop_head (&self->reorder_list);
goto get_one;
}
return TRUE;
get_one:
g_assert (self->gop.cur_frame_num < self->gop.max_frame_num);
vaframe = _enc_frame (frame);
vaframe->frame_num = self->gop.cur_frame_num;
/* Add the frame number for ref frames. */
if (vaframe->is_ref)
self->gop.cur_frame_num++;
if (vaframe->frame_num == 0)
self->gop.total_idr_count++;
if (self->gop.b_pyramid && vaframe->type == GST_H264_B_SLICE) {
GST_LOG_OBJECT (self, "pop a pyramid B frame with system_frame_number:"
" %d, poc: %d, frame num: %d, is_ref: %s, level %d",
frame->system_frame_number, vaframe->poc, vaframe->frame_num,
vaframe->is_ref ? "true" : "false", vaframe->pyramid_level);
} else {
GST_LOG_OBJECT (self, "pop a frame with system_frame_number: %d,"
" frame type: %s, poc: %d, frame num: %d, is_ref: %s",
frame->system_frame_number, _slice_type_name (vaframe->type),
vaframe->poc, vaframe->frame_num, vaframe->is_ref ? "true" : "false");
}
/* unref frame popped from queue or pyramid b_frame */
gst_video_codec_frame_unref (frame);
*out_frame = frame;
return TRUE;
}
static inline gboolean
_fill_sps (GstVaH264Enc * self, VAEncSequenceParameterBufferH264 * seq_param)
{
GstH264Profile profile;
guint32 constraint_set0_flag, constraint_set1_flag;
guint32 constraint_set2_flag, constraint_set3_flag;
guint32 max_dec_frame_buffering;
/* let max_num_ref_frames <= MaxDpbFrames. */
max_dec_frame_buffering =
MIN (self->gop.num_ref_frames + 1 /* Last frame before bump */ ,
16 /* DPB_MAX_SIZE */ );
constraint_set0_flag = 0;
constraint_set1_flag = 0;
constraint_set2_flag = 0;
constraint_set3_flag = 0;
switch (self->profile) {
case VAProfileH264ConstrainedBaseline:
profile = GST_H264_PROFILE_BASELINE;
/* A.2.1 (baseline profile constraints) */
constraint_set0_flag = 1;
constraint_set1_flag = 1;
break;
case VAProfileH264Main:
profile = GST_H264_PROFILE_MAIN;
/* A.2.2 (main profile constraints) */
constraint_set1_flag = 1;
break;
case VAProfileH264High:
case VAProfileH264MultiviewHigh:
case VAProfileH264StereoHigh:
profile = GST_H264_PROFILE_HIGH;
break;
default:
return FALSE;
}
/* seq_scaling_matrix_present_flag not supported now */
g_assert (seq_param->seq_fields.bits.seq_scaling_matrix_present_flag == 0);
/* pic_order_cnt_type only support 0 now */
g_assert (seq_param->seq_fields.bits.pic_order_cnt_type == 0);
/* only progressive frames encoding is supported now */
g_assert (seq_param->seq_fields.bits.frame_mbs_only_flag);
/* *INDENT-OFF* */
GST_DEBUG_OBJECT (self, "filling SPS");
self->sequence_hdr = (GstH264SPS) {
.id = 0,
.profile_idc = profile,
.constraint_set0_flag = constraint_set0_flag,
.constraint_set1_flag = constraint_set1_flag,
.constraint_set2_flag = constraint_set2_flag,
.constraint_set3_flag = constraint_set3_flag,
.level_idc = self->level_idc,
.chroma_format_idc = seq_param->seq_fields.bits.chroma_format_idc,
.bit_depth_luma_minus8 = seq_param->bit_depth_luma_minus8,
.bit_depth_chroma_minus8 = seq_param->bit_depth_chroma_minus8,
.log2_max_frame_num_minus4 =
seq_param->seq_fields.bits.log2_max_frame_num_minus4,
.pic_order_cnt_type = seq_param->seq_fields.bits.pic_order_cnt_type,
.log2_max_pic_order_cnt_lsb_minus4 =
seq_param->seq_fields.bits.log2_max_pic_order_cnt_lsb_minus4,
.num_ref_frames = seq_param->max_num_ref_frames,
.gaps_in_frame_num_value_allowed_flag = 0,
.pic_width_in_mbs_minus1 = seq_param->picture_width_in_mbs - 1,
.pic_height_in_map_units_minus1 =
(seq_param->seq_fields.bits.frame_mbs_only_flag ?
seq_param->picture_height_in_mbs - 1 :
seq_param->picture_height_in_mbs / 2 - 1),
.frame_mbs_only_flag = seq_param->seq_fields.bits.frame_mbs_only_flag,
.mb_adaptive_frame_field_flag = 0,
.direct_8x8_inference_flag =
seq_param->seq_fields.bits.direct_8x8_inference_flag,
.frame_cropping_flag = seq_param->frame_cropping_flag,
.frame_crop_left_offset = seq_param->frame_crop_left_offset,
.frame_crop_right_offset = seq_param->frame_crop_right_offset,
.frame_crop_top_offset = seq_param->frame_crop_top_offset,
.frame_crop_bottom_offset = seq_param->frame_crop_bottom_offset,
.vui_parameters_present_flag = seq_param->vui_parameters_present_flag,
.vui_parameters = {
.aspect_ratio_info_present_flag =
seq_param->vui_fields.bits.aspect_ratio_info_present_flag,
.aspect_ratio_idc = seq_param->aspect_ratio_idc,
.sar_width = seq_param->sar_width,
.sar_height = seq_param->sar_height,
.overscan_info_present_flag = 0,
.overscan_appropriate_flag = 0,
.chroma_loc_info_present_flag = 0,
.timing_info_present_flag =
seq_param->vui_fields.bits.timing_info_present_flag,
.num_units_in_tick = seq_param->num_units_in_tick,
.time_scale = seq_param->time_scale,
.fixed_frame_rate_flag = seq_param->vui_fields.bits.fixed_frame_rate_flag,
/* We do not write hrd and no need for buffering period SEI. */
.nal_hrd_parameters_present_flag = 0,
.vcl_hrd_parameters_present_flag = 0,
.low_delay_hrd_flag = seq_param->vui_fields.bits.low_delay_hrd_flag,
.pic_struct_present_flag = 1,
.bitstream_restriction_flag =
seq_param->vui_fields.bits.bitstream_restriction_flag,
.motion_vectors_over_pic_boundaries_flag =
seq_param->vui_fields.bits.motion_vectors_over_pic_boundaries_flag,
.max_bytes_per_pic_denom = 2,
.max_bits_per_mb_denom = 1,
.log2_max_mv_length_horizontal =
seq_param->vui_fields.bits.log2_max_mv_length_horizontal,
.log2_max_mv_length_vertical =
seq_param->vui_fields.bits.log2_max_mv_length_vertical,
.num_reorder_frames = self->gop.num_reorder_frames,
.max_dec_frame_buffering = max_dec_frame_buffering,
},
};
/* *INDENT-ON* */
return TRUE;
}
static gboolean
_add_sequence_header (GstVaH264Enc * self, GstVaH264EncFrame * frame)
{
gsize size;
#define SPS_SIZE 4 + GST_ROUND_UP_8 (MAX_SPS_HDR_SIZE + MAX_VUI_PARAMS_SIZE + \
2 * MAX_HRD_PARAMS_SIZE) / 8
guint8 packed_sps[SPS_SIZE] = { 0, };
#undef SPS_SIZE
size = sizeof (packed_sps);
if (gst_h264_bit_writer_sps (&self->sequence_hdr, TRUE, packed_sps,
&size) != GST_H264_BIT_WRITER_OK) {
GST_ERROR_OBJECT (self, "Failed to generate the sequence header");
return FALSE;
}
if (!gst_va_encoder_add_packed_header (self->encoder, frame->picture,
VAEncPackedHeaderSequence, packed_sps, size, FALSE)) {
GST_ERROR_OBJECT (self, "Failed to add the packed sequence header");
return FALSE;
}
return TRUE;
}
static inline void
_fill_sequence_param (GstVaH264Enc * self,
VAEncSequenceParameterBufferH264 * sequence)
{
gboolean direct_8x8_inference_flag = TRUE;
g_assert (self->gop.log2_max_frame_num >= 4);
g_assert (self->gop.log2_max_pic_order_cnt >= 4);
/* A.2.3 Extended profile:
* Sequence parameter sets shall have direct_8x8_inference_flag
* equal to 1.
*
* A.3.3 Profile-specific level limits:
* direct_8x8_inference_flag is not relevant to the Baseline,
* Constrained Baseline, Constrained High, High 10 Intra, High 4:2:2
* Intra, High 4:4:4 Intra, and CAVLC 4:4:4 Intra profiles as these
* profiles do not allow B slice types, and
* direct_8x8_inference_flag is equal to 1 for all levels of the
* Extended profile. Table A-4. We only have constrained baseline
* here. */
if (self->profile == VAProfileH264ConstrainedBaseline)
direct_8x8_inference_flag = FALSE;
/* *INDENT-OFF* */
*sequence = (VAEncSequenceParameterBufferH264) {
.seq_parameter_set_id = 0,
.level_idc = self->level_idc,
.intra_period =
self->gop.i_period > 0 ? self->gop.i_period : self->gop.idr_period,
.intra_idr_period = self->gop.idr_period,
.ip_period = self->gop.ip_period,
.bits_per_second = self->rc.target_bitrate_bits,
.max_num_ref_frames = self->gop.num_ref_frames,
.picture_width_in_mbs = self->mb_width,
.picture_height_in_mbs = self->mb_height,
.seq_fields.bits = {
/* Only support 4:2:0 now. */
.chroma_format_idc = 1,
.frame_mbs_only_flag = 1,
.mb_adaptive_frame_field_flag = FALSE,
.seq_scaling_matrix_present_flag = FALSE,
.direct_8x8_inference_flag = direct_8x8_inference_flag,
.log2_max_frame_num_minus4 = self->gop.log2_max_frame_num - 4,
.pic_order_cnt_type = 0,
.log2_max_pic_order_cnt_lsb_minus4 = self->gop.log2_max_pic_order_cnt - 4,
},
.bit_depth_luma_minus8 = 0,
.bit_depth_chroma_minus8 = 0,
.vui_parameters_present_flag = TRUE,
.vui_fields.bits = {
.aspect_ratio_info_present_flag = TRUE,
.timing_info_present_flag = TRUE,
.bitstream_restriction_flag = TRUE,
.log2_max_mv_length_horizontal = 15,
.log2_max_mv_length_vertical = 15,
.fixed_frame_rate_flag = 1,
.low_delay_hrd_flag = 0,
.motion_vectors_over_pic_boundaries_flag = TRUE,
},
.aspect_ratio_idc = 0xff,
/* FIXME: what if no framerate info is provided */
.sar_width = GST_VIDEO_INFO_PAR_N (&self->in_info),
.sar_height = GST_VIDEO_INFO_PAR_D (&self->in_info),
.num_units_in_tick = GST_VIDEO_INFO_FPS_D (&self->in_info),
.time_scale = GST_VIDEO_INFO_FPS_N (&self->in_info) * 2,
};
/* *INDENT-ON* */
/* frame_cropping_flag */
if (self->width & 15 || self->height & 15) {
static const guint SubWidthC[] = { 1, 2, 2, 1 };
static const guint SubHeightC[] = { 1, 2, 1, 1 };
const guint CropUnitX =
SubWidthC[sequence->seq_fields.bits.chroma_format_idc];
const guint CropUnitY =
SubHeightC[sequence->seq_fields.bits.chroma_format_idc] *
(2 - sequence->seq_fields.bits.frame_mbs_only_flag);
sequence->frame_cropping_flag = 1;
sequence->frame_crop_left_offset = 0;
sequence->frame_crop_right_offset = (16 * self->mb_width -
self->width) / CropUnitX;
sequence->frame_crop_top_offset = 0;
sequence->frame_crop_bottom_offset = (16 * self->mb_height -
self->height) / CropUnitY;
}
}
static gboolean
_add_sequence_parameter (GstVaH264Enc * self, GstVaEncodePicture * picture,
VAEncSequenceParameterBufferH264 * sequence)
{
if (!gst_va_encoder_add_param (self->encoder, picture,
VAEncSequenceParameterBufferType, sequence, sizeof (*sequence))) {
GST_ERROR_OBJECT (self, "Failed to create the sequence parameter");
return FALSE;
}
return TRUE;
}
static gboolean
_add_rate_control_parameter (GstVaH264Enc * self, GstVaEncodePicture * picture)
{
uint32_t window_size;
struct VAEncMiscParameterRateControlWrap
{
VAEncMiscParameterType type;
VAEncMiscParameterRateControl rate_control;
} rate_control;
if (self->rc.rc_ctrl_mode == VA_RC_CQP)
return TRUE;
window_size = self->rc.rc_ctrl_mode == VA_RC_VBR ?
self->rc.max_bitrate_bits / 2 : self->rc.max_bitrate_bits;
/* *INDENT-OFF* */
rate_control = (struct VAEncMiscParameterRateControlWrap) {
.type = VAEncMiscParameterTypeRateControl,
.rate_control = {
.bits_per_second = self->rc.max_bitrate_bits,
.target_percentage = self->rc.target_percentage,
.window_size = window_size,
.initial_qp = self->rc.qp_i,
.min_qp = self->rc.min_qp,
.max_qp = self->rc.max_qp,
.rc_flags.bits.mb_rate_control = self->rc.mbbrc,
.quality_factor = 0,
},
};
/* *INDENT-ON* */
if (!gst_va_encoder_add_param (self->encoder, picture,
VAEncMiscParameterBufferType, &rate_control, sizeof (rate_control))) {
GST_ERROR_OBJECT (self, "Failed to create the race control parameter");
return FALSE;
}
return TRUE;
}
static gboolean
_add_hrd_parameter (GstVaH264Enc * self, GstVaEncodePicture * picture)
{
/* *INDENT-OFF* */
struct
{
VAEncMiscParameterType type;
VAEncMiscParameterHRD hrd;
} hrd = {
.type = VAEncMiscParameterTypeHRD,
.hrd = {
.buffer_size = self->rc.cpb_length_bits,
.initial_buffer_fullness = self->rc.cpb_length_bits / 2,
},
};
/* *INDENT-ON* */
if (self->rc.rc_ctrl_mode == VA_RC_CQP || self->rc.rc_ctrl_mode == VA_RC_VCM)
return TRUE;
g_assert (self->rc.max_bitrate_bits > 0);
if (!gst_va_encoder_add_param (self->encoder, picture,
VAEncMiscParameterBufferType, &hrd, sizeof (hrd))) {
GST_ERROR_OBJECT (self, "Failed to create the HRD parameter");
return FALSE;
}
return TRUE;
}
static gboolean
_add_quality_level_parameter (GstVaH264Enc * self, GstVaEncodePicture * picture)
{
/* *INDENT-OFF* */
struct
{
VAEncMiscParameterType type;
VAEncMiscParameterBufferQualityLevel ql;
} quality_level = {
.type = VAEncMiscParameterTypeQualityLevel,
.ql.quality_level = self->rc.target_usage,
};
/* *INDENT-ON* */
if (self->rc.target_usage == 0)
return TRUE;
if (!gst_va_encoder_add_param (self->encoder, picture,
VAEncMiscParameterBufferType, &quality_level,
sizeof (quality_level))) {
GST_ERROR_OBJECT (self, "Failed to create the quality level parameter");
return FALSE;
}
return TRUE;
}
static gboolean
_add_frame_rate_parameter (GstVaH264Enc * self, GstVaEncodePicture * picture)
{
/* *INDENT-OFF* */
struct
{
VAEncMiscParameterType type;
VAEncMiscParameterFrameRate fr;
} framerate = {
.type = VAEncMiscParameterTypeFrameRate,
/* denominator = framerate >> 16 & 0xffff;
* numerator = framerate & 0xffff; */
.fr.framerate = (GST_VIDEO_INFO_FPS_N (&self->in_info) & 0xffff) |
((GST_VIDEO_INFO_FPS_D (&self->in_info) & 0xffff) << 16)
};
/* *INDENT-ON* */
if (!gst_va_encoder_add_param (self->encoder, picture,
VAEncMiscParameterBufferType, &framerate, sizeof (framerate))) {
GST_ERROR_OBJECT (self, "Failed to create the frame rate parameter");
return FALSE;
}
return TRUE;
}
static gboolean
_add_trellis_parameter (GstVaH264Enc * self, GstVaEncodePicture * picture)
{
/* *INDENT-OFF* */
struct
{
VAEncMiscParameterType type;
VAEncMiscParameterQuantization tr;
} trellis = {
.type = VAEncMiscParameterTypeQuantization,
.tr.quantization_flags.bits = {
.disable_trellis = 0,
.enable_trellis_I = 1,
.enable_trellis_B = 1,
.enable_trellis_P = 1,
},
};
/* *INDENT-ON* */
if (!self->use_trellis)
return TRUE;
if (!gst_va_encoder_add_param (self->encoder, picture,
VAEncMiscParameterBufferType, &trellis, sizeof (trellis))) {
GST_ERROR_OBJECT (self, "Failed to create the trellis parameter");
return FALSE;
}
return TRUE;
}
static inline gboolean
_fill_picture_parameter (GstVaH264Enc * self, GstVaH264EncFrame * frame,
VAEncPictureParameterBufferH264 * pic_param)
{
guint i;
/* *INDENT-OFF* */
*pic_param = (VAEncPictureParameterBufferH264) {
.CurrPic.picture_id = gst_va_encode_picture_get_reconstruct_surface (frame->picture),
.CurrPic.TopFieldOrderCnt = frame->poc,
.coded_buf = frame->picture->coded_buffer,
/* Only support one sps and pps now. */
.pic_parameter_set_id = 0,
.seq_parameter_set_id = 0,
/* means last encoding picture, EOS nal added. */
.last_picture = frame->last_frame,
.frame_num = frame->frame_num,
.pic_init_qp = self->rc.qp_i,
/* Use slice's these fields to control ref num. */
.num_ref_idx_l0_active_minus1 = 0,
.num_ref_idx_l1_active_minus1 = 0,
.chroma_qp_index_offset = 0,
.second_chroma_qp_index_offset = 0,
/* picture fields */
.pic_fields.bits.idr_pic_flag = (frame->frame_num == 0),
.pic_fields.bits.reference_pic_flag = frame->is_ref,
.pic_fields.bits.entropy_coding_mode_flag = self->use_cabac,
.pic_fields.bits.weighted_pred_flag = 0,
.pic_fields.bits.weighted_bipred_idc = 0,
.pic_fields.bits.constrained_intra_pred_flag = 0,
.pic_fields.bits.transform_8x8_mode_flag = self->use_dct8x8,
/* enable debloking */
.pic_fields.bits.deblocking_filter_control_present_flag = 1,
.pic_fields.bits.redundant_pic_cnt_present_flag = 0,
/* bottom_field_pic_order_in_frame_present_flag */
.pic_fields.bits.pic_order_present_flag = 0,
.pic_fields.bits.pic_scaling_matrix_present_flag = 0,
};
/* *INDENT-ON* */
/* Non I frame, construct reference list. */
i = 0;
if (frame->type != GST_H264_I_SLICE) {
GstVaH264EncFrame *f;
if (g_queue_is_empty (&self->ref_list)) {
GST_ERROR_OBJECT (self, "No reference found for frame type %s",
_slice_type_name (frame->type));
return FALSE;
}
g_assert (g_queue_get_length (&self->ref_list) <= self->gop.num_ref_frames);
/* ref frames in queue are already sorted by frame_num. */
for (; i < g_queue_get_length (&self->ref_list); i++) {
f = _enc_frame (g_queue_peek_nth (&self->ref_list, i));
pic_param->ReferenceFrames[i].picture_id =
gst_va_encode_picture_get_reconstruct_surface (f->picture);
pic_param->ReferenceFrames[i].TopFieldOrderCnt = f->poc;
pic_param->ReferenceFrames[i].flags =
VA_PICTURE_H264_SHORT_TERM_REFERENCE;
pic_param->ReferenceFrames[i].frame_idx = f->frame_num;
}
}
for (; i < 16; ++i)
pic_param->ReferenceFrames[i].picture_id = VA_INVALID_ID;
return TRUE;
};
static gboolean
_add_picture_parameter (GstVaH264Enc * self, GstVaH264EncFrame * frame,
VAEncPictureParameterBufferH264 * pic_param)
{
if (!gst_va_encoder_add_param (self->encoder, frame->picture,
VAEncPictureParameterBufferType, pic_param,
sizeof (VAEncPictureParameterBufferH264))) {
GST_ERROR_OBJECT (self, "Failed to create the picture parameter");
return FALSE;
}
return TRUE;
}
static void
_fill_pps (VAEncPictureParameterBufferH264 * pic_param, GstH264SPS * sps,
GstH264PPS * pps)
{
/* *INDENT-OFF* */
*pps = (GstH264PPS) {
.id = 0,
.sequence = sps,
.entropy_coding_mode_flag =
pic_param->pic_fields.bits.entropy_coding_mode_flag,
.pic_order_present_flag =
pic_param->pic_fields.bits.pic_order_present_flag,
.num_slice_groups_minus1 = 0,
.num_ref_idx_l0_active_minus1 = pic_param->num_ref_idx_l0_active_minus1,
.num_ref_idx_l1_active_minus1 = pic_param->num_ref_idx_l1_active_minus1,
.weighted_pred_flag = pic_param->pic_fields.bits.weighted_pred_flag,
.weighted_bipred_idc = pic_param->pic_fields.bits.weighted_bipred_idc,
.pic_init_qp_minus26 = pic_param->pic_init_qp - 26,
.pic_init_qs_minus26 = 0,
.chroma_qp_index_offset = pic_param->chroma_qp_index_offset,
.deblocking_filter_control_present_flag =
pic_param->pic_fields.bits.deblocking_filter_control_present_flag,
.constrained_intra_pred_flag =
pic_param->pic_fields.bits.constrained_intra_pred_flag,
.redundant_pic_cnt_present_flag =
pic_param->pic_fields.bits.redundant_pic_cnt_present_flag,
.transform_8x8_mode_flag =
pic_param->pic_fields.bits.transform_8x8_mode_flag,
/* unsupport scaling lists */
.pic_scaling_matrix_present_flag = 0,
.second_chroma_qp_index_offset = pic_param->second_chroma_qp_index_offset,
};
/* *INDENT-ON* */
}
static gboolean
_add_picture_header (GstVaH264Enc * self, GstVaH264EncFrame * frame,
GstH264PPS * pps)
{
#define PPS_SIZE 4 + GST_ROUND_UP_8 (MAX_PPS_HDR_SIZE) / 8
guint8 packed_pps[PPS_SIZE] = { 0, };
#undef PPS_SIZE
gsize size;
size = sizeof (packed_pps);
if (gst_h264_bit_writer_pps (pps, TRUE, packed_pps,
&size) != GST_H264_BIT_WRITER_OK) {
GST_ERROR_OBJECT (self, "Failed to generate the picture header");
return FALSE;
}
if (!gst_va_encoder_add_packed_header (self->encoder, frame->picture,
VAEncPackedHeaderPicture, packed_pps, size, FALSE)) {
GST_ERROR_OBJECT (self, "Failed to add the packed picture header");
return FALSE;
}
return TRUE;
}
static gboolean
_add_one_slice (GstVaH264Enc * self, GstVaH264EncFrame * frame,
gint start_mb, gint mb_size,
VAEncSliceParameterBufferH264 * slice,
GstVaH264EncFrame * list0[16], guint list0_num,
GstVaH264EncFrame * list1[16], guint list1_num)
{
int8_t slice_qp_delta = 0;
gint i;
/* *INDENT-OFF* */
if (self->rc.rc_ctrl_mode == VA_RC_CQP) {
if (frame->type == GST_H264_P_SLICE) {
slice_qp_delta = self->rc.qp_p - self->rc.qp_i;
} else if (frame->type == GST_H264_B_SLICE) {
slice_qp_delta = (int8_t) (self->rc.qp_b - self->rc.qp_i);
}
g_assert (slice_qp_delta <= 51 && slice_qp_delta >= -51);
}
*slice = (VAEncSliceParameterBufferH264) {
.macroblock_address = start_mb,
.num_macroblocks = mb_size,
.macroblock_info = VA_INVALID_ID,
.slice_type = (uint8_t) frame->type,
/* Only one parameter set supported now. */
.pic_parameter_set_id = 0,
.idr_pic_id = self->gop.total_idr_count,
.pic_order_cnt_lsb = frame->poc,
/* Not support top/bottom. */
.delta_pic_order_cnt_bottom = 0,
.delta_pic_order_cnt[0] = 0,
.delta_pic_order_cnt[1] = 0,
.direct_spatial_mv_pred_flag = TRUE,
/* .num_ref_idx_active_override_flag = , */
/* .num_ref_idx_l0_active_minus1 = , */
/* .num_ref_idx_l1_active_minus1 = , */
/* Set the reference list later. */
.luma_log2_weight_denom = 0,
.chroma_log2_weight_denom = 0,
.luma_weight_l0_flag = 0,
.chroma_weight_l0_flag = 0,
.luma_weight_l1_flag = 0,
.chroma_weight_l1_flag = 0,
.cabac_init_idc = 0,
/* Just use picture default setting. */
.slice_qp_delta = slice_qp_delta,
.disable_deblocking_filter_idc = 0,
.slice_alpha_c0_offset_div2 = 2,
.slice_beta_offset_div2 = 2,
};
/* *INDENT-ON* */
if (frame->type == GST_H264_B_SLICE || frame->type == GST_H264_P_SLICE) {
slice->num_ref_idx_active_override_flag = (list0_num > 0 || list1_num > 0);
slice->num_ref_idx_l0_active_minus1 = list0_num > 0 ? list0_num - 1 : 0;
if (frame->type == GST_H264_B_SLICE)
slice->num_ref_idx_l1_active_minus1 = list1_num > 0 ? list1_num - 1 : 0;
}
i = 0;
if (frame->type != GST_H264_I_SLICE) {
for (; i < list0_num; i++) {
slice->RefPicList0[i].picture_id =
gst_va_encode_picture_get_reconstruct_surface (list0[i]->picture);
slice->RefPicList0[i].TopFieldOrderCnt = list0[i]->poc;
slice->RefPicList0[i].flags |= VA_PICTURE_H264_SHORT_TERM_REFERENCE;
slice->RefPicList0[i].frame_idx = list0[i]->frame_num;
}
}
for (; i < G_N_ELEMENTS (slice->RefPicList0); ++i) {
slice->RefPicList0[i].picture_id = VA_INVALID_SURFACE;
slice->RefPicList0[i].flags = VA_PICTURE_H264_INVALID;
}
i = 0;
if (frame->type == GST_H264_B_SLICE) {
for (; i < list1_num; i++) {
slice->RefPicList1[i].picture_id =
gst_va_encode_picture_get_reconstruct_surface (list1[i]->picture);
slice->RefPicList1[i].TopFieldOrderCnt = list1[i]->poc;
slice->RefPicList1[i].flags |= VA_PICTURE_H264_SHORT_TERM_REFERENCE;
slice->RefPicList1[i].frame_idx = list1[i]->frame_num;
}
}
for (; i < G_N_ELEMENTS (slice->RefPicList1); ++i) {
slice->RefPicList1[i].picture_id = VA_INVALID_SURFACE;
slice->RefPicList1[i].flags = VA_PICTURE_H264_INVALID;
}
if (!gst_va_encoder_add_param (self->encoder, frame->picture,
VAEncSliceParameterBufferType, slice,
sizeof (VAEncSliceParameterBufferH264))) {
GST_ERROR_OBJECT (self, "Failed to create the slice parameter");
return FALSE;
}
return TRUE;
}
static gint
_poc_asc_compare (const GstVaH264EncFrame ** a, const GstVaH264EncFrame ** b)
{
return (*a)->poc - (*b)->poc;
}
static gint
_poc_des_compare (const GstVaH264EncFrame ** a, const GstVaH264EncFrame ** b)
{
return (*b)->poc - (*a)->poc;
}
static gint
_frame_num_asc_compare (const GstVaH264EncFrame ** a,
const GstVaH264EncFrame ** b)
{
return (*a)->frame_num - (*b)->frame_num;
}
static gint
_frame_num_des_compare (const GstVaH264EncFrame ** a,
const GstVaH264EncFrame ** b)
{
return (*b)->frame_num - (*a)->frame_num;
}
/* If all the pic_num in the same order, OK. */
static gboolean
_ref_list_need_reorder (GstVaH264EncFrame * list[16], guint list_num,
gboolean is_asc)
{
guint i;
gint pic_num_diff;
if (list_num <= 1)
return FALSE;
for (i = 1; i < list_num; i++) {
pic_num_diff = list[i]->frame_num - list[i - 1]->frame_num;
g_assert (pic_num_diff != 0);
if (pic_num_diff > 0 && !is_asc)
return TRUE;
if (pic_num_diff < 0 && is_asc)
return TRUE;
}
return FALSE;
}
static void
_insert_ref_pic_list_modification (GstH264SliceHdr * slice_hdr,
GstVaH264EncFrame * list[16], guint list_num, gboolean is_asc)
{
GstVaH264EncFrame *list_by_pic_num[16] = { };
guint modification_num, i;
GstH264RefPicListModification *ref_pic_list_modification = NULL;
gint pic_num_diff, pic_num_lx_pred;
memcpy (list_by_pic_num, list, sizeof (GstVaH264EncFrame *) * list_num);
if (is_asc) {
g_qsort_with_data (list_by_pic_num, list_num, sizeof (gpointer),
(GCompareDataFunc) _frame_num_asc_compare, NULL);
} else {
g_qsort_with_data (list_by_pic_num, list_num, sizeof (gpointer),
(GCompareDataFunc) _frame_num_des_compare, NULL);
}
modification_num = 0;
for (i = 0; i < list_num; i++) {
if (list_by_pic_num[i]->poc != list[i]->poc)
modification_num = i + 1;
}
g_assert (modification_num > 0);
if (is_asc) {
slice_hdr->ref_pic_list_modification_flag_l1 = 1;
slice_hdr->n_ref_pic_list_modification_l1 =
modification_num + 1 /* The end operation. */ ;
ref_pic_list_modification = slice_hdr->ref_pic_list_modification_l1;
} else {
slice_hdr->ref_pic_list_modification_flag_l0 = 1;
slice_hdr->n_ref_pic_list_modification_l0 =
modification_num + 1 /* The end operation. */ ;
ref_pic_list_modification = slice_hdr->ref_pic_list_modification_l0;
}
pic_num_lx_pred = slice_hdr->frame_num;
for (i = 0; i < modification_num; i++) {
pic_num_diff = list[i]->frame_num - pic_num_lx_pred;
/* For the nex loop. */
pic_num_lx_pred = list[i]->frame_num;
g_assert (pic_num_diff != 0);
if (pic_num_diff > 0) {
ref_pic_list_modification->modification_of_pic_nums_idc = 1;
ref_pic_list_modification->value.abs_diff_pic_num_minus1 =
pic_num_diff - 1;
} else {
ref_pic_list_modification->modification_of_pic_nums_idc = 0;
ref_pic_list_modification->value.abs_diff_pic_num_minus1 =
(-pic_num_diff) - 1;
}
ref_pic_list_modification++;
}
ref_pic_list_modification->modification_of_pic_nums_idc = 3;
}
static void
_insert_ref_pic_marking_for_unused_frame (GstH264SliceHdr * slice_hdr,
gint cur_frame_num, gint unused_frame_num)
{
GstH264RefPicMarking *refpicmarking;
slice_hdr->dec_ref_pic_marking.adaptive_ref_pic_marking_mode_flag = 1;
slice_hdr->dec_ref_pic_marking.n_ref_pic_marking = 2;
refpicmarking = &slice_hdr->dec_ref_pic_marking.ref_pic_marking[0];
refpicmarking->memory_management_control_operation = 1;
refpicmarking->difference_of_pic_nums_minus1 =
cur_frame_num - unused_frame_num - 1;
refpicmarking = &slice_hdr->dec_ref_pic_marking.ref_pic_marking[1];
refpicmarking->memory_management_control_operation = 0;
}
static gboolean
_add_slice_header (GstVaH264Enc * self, GstVaH264EncFrame * frame,
GstH264PPS * pps, VAEncSliceParameterBufferH264 * slice,
GstVaH264EncFrame * list0[16], guint list0_num,
GstVaH264EncFrame * list1[16], guint list1_num)
{
GstH264SliceHdr slice_hdr;
gsize size;
GstH264NalUnitType nal_type = GST_H264_NAL_SLICE;
#define SLICE_HDR_SIZE 4 + GST_ROUND_UP_8 (MAX_SLICE_HDR_SIZE) / 8
guint8 packed_slice_hdr[SLICE_HDR_SIZE] = { 0, };
#undef SLICE_HDR_SIZE
if (frame->frame_num == 0)
nal_type = GST_H264_NAL_SLICE_IDR;
/* *INDENT-OFF* */
slice_hdr = (GstH264SliceHdr) {
.first_mb_in_slice = slice->macroblock_address,
.type = slice->slice_type,
.pps = pps,
.frame_num = frame->frame_num,
/* interlaced not supported now. */
.field_pic_flag = 0,
.bottom_field_flag = 0,
.idr_pic_id = (frame->frame_num == 0 ? slice->idr_pic_id : 0),
/* only pic_order_cnt_type 1 is supported now. */
.pic_order_cnt_lsb = slice->pic_order_cnt_lsb,
.delta_pic_order_cnt_bottom = slice->delta_pic_order_cnt_bottom,
/* Only for B frame. */
.direct_spatial_mv_pred_flag =
(frame->type == GST_H264_B_SLICE ?
slice->direct_spatial_mv_pred_flag : 0),
.num_ref_idx_active_override_flag = slice->num_ref_idx_active_override_flag,
.num_ref_idx_l0_active_minus1 = slice->num_ref_idx_l0_active_minus1,
.num_ref_idx_l1_active_minus1 = slice->num_ref_idx_l1_active_minus1,
/* Calculate it later. */
.ref_pic_list_modification_flag_l0 = 0,
.ref_pic_list_modification_flag_l1 = 0,
/* We have weighted_pred_flag and weighted_bipred_idc 0 here, no
* need weight_table. */
.dec_ref_pic_marking = {
.no_output_of_prior_pics_flag = 0,
.long_term_reference_flag = 0,
/* If not sliding_window, we set it later. */
.adaptive_ref_pic_marking_mode_flag = 0,
},
.cabac_init_idc = slice->cabac_init_idc,
.slice_qp_delta = slice->slice_qp_delta,
.disable_deblocking_filter_idc = slice->disable_deblocking_filter_idc,
.slice_alpha_c0_offset_div2 = slice->slice_alpha_c0_offset_div2,
.slice_beta_offset_div2 = slice->slice_beta_offset_div2,
};
/* *INDENT-ON* */
/* Reorder the ref lists if needed. */
if (list0_num > 1) {
/* list0 is in poc descend order now. */
if (_ref_list_need_reorder (list0, list0_num, FALSE))
_insert_ref_pic_list_modification (&slice_hdr, list0, list0_num, FALSE);
}
if (list0_num > 1) {
/* list0 is in poc ascend order now. */
if (_ref_list_need_reorder (list1, list1_num, TRUE)) {
_insert_ref_pic_list_modification (&slice_hdr, list1, list1_num, TRUE);
}
}
/* Mark the unused reference explicitly which this frame replaces. */
if (frame->unused_for_reference_pic_num >= 0) {
g_assert (frame->is_ref);
_insert_ref_pic_marking_for_unused_frame (&slice_hdr, frame->frame_num,
frame->unused_for_reference_pic_num);
}
size = sizeof (packed_slice_hdr);
if (gst_h264_bit_writer_slice_hdr (&slice_hdr, TRUE, nal_type, frame->is_ref,
packed_slice_hdr, &size) != GST_H264_BIT_WRITER_OK) {
GST_ERROR_OBJECT (self, "Failed to generate the slice header");
return FALSE;
}
if (!gst_va_encoder_add_packed_header (self->encoder, frame->picture,
VAEncPackedHeaderSlice, packed_slice_hdr, size, FALSE)) {
GST_ERROR_OBJECT (self, "Failed to add the packed slice header");
return FALSE;
}
return TRUE;
}
static gboolean
_add_aud (GstVaH264Enc * self, GstVaH264EncFrame * frame)
{
guint8 aud_data[8] = { };
gsize size;
guint8 primary_pic_type = 0;
switch (frame->type) {
case GST_H264_I_SLICE:
primary_pic_type = 0;
break;
case GST_H264_P_SLICE:
primary_pic_type = 1;
break;
case GST_H264_B_SLICE:
primary_pic_type = 2;
break;
default:
g_assert_not_reached ();
break;
}
size = sizeof (aud_data);
if (gst_h264_bit_writer_aud (primary_pic_type, TRUE, aud_data,
&size) != GST_H264_BIT_WRITER_OK) {
GST_ERROR_OBJECT (self, "Failed to generate the AUD");
return FALSE;
}
if (!gst_va_encoder_add_packed_header (self->encoder, frame->picture,
VAEncPackedHeaderRawData, aud_data, size, FALSE)) {
GST_ERROR_OBJECT (self, "Failed to add the AUD");
return FALSE;
}
return TRUE;
}
static gboolean
gst_va_h264_enc_encode_frame (GstVaH264Enc * self,
GstVideoCodecFrame * gst_frame)
{
VAEncPictureParameterBufferH264 pic_param;
GstH264PPS pps;
GstVaH264EncFrame *list0[16] = { NULL, };
guint list0_num = 0;
GstVaH264EncFrame *list1[16] = { NULL, };
guint list1_num = 0;
guint slice_of_mbs, slice_mod_mbs, slice_start_mb, slice_mbs;
gint i;
GstVaH264EncFrame *frame;
g_return_val_if_fail (gst_frame, FALSE);
frame = _enc_frame (gst_frame);
/* Repeat the SPS for IDR. */
if (frame->poc == 0) {
VAEncSequenceParameterBufferH264 sequence;
if (!_add_rate_control_parameter (self, frame->picture))
return FALSE;
if (!_add_quality_level_parameter (self, frame->picture))
return FALSE;
if (!_add_frame_rate_parameter (self, frame->picture))
return FALSE;
if (!_add_hrd_parameter (self, frame->picture))
return FALSE;
if (!_add_trellis_parameter (self, frame->picture))
return FALSE;
_fill_sequence_param (self, &sequence);
if (!_fill_sps (self, &sequence))
return FALSE;
if (!_add_sequence_parameter (self, frame->picture, &sequence))
return FALSE;
if ((self->packed_headers & VA_ENC_PACKED_HEADER_SEQUENCE)
&& !_add_sequence_header (self, frame))
return FALSE;
}
if (self->prop.aud) {
if ((self->packed_headers & VA_ENC_PACKED_HEADER_RAW_DATA)
&& !_add_aud (self, frame))
return FALSE;
}
/* Non I frame, construct reference list. */
if (frame->type != GST_H264_I_SLICE) {
GstVaH264EncFrame *vaf;
GstVideoCodecFrame *f;
for (i = g_queue_get_length (&self->ref_list) - 1; i >= 0; i--) {
f = g_queue_peek_nth (&self->ref_list, i);
vaf = _enc_frame (f);
if (vaf->poc > frame->poc)
continue;
list0[list0_num] = vaf;
list0_num++;
}
/* reorder to select the most nearest forward frames. */
g_qsort_with_data (list0, list0_num, sizeof (gpointer),
(GCompareDataFunc) _poc_des_compare, NULL);
if (list0_num > self->gop.ref_num_list0)
list0_num = self->gop.ref_num_list0;
}
if (frame->type == GST_H264_B_SLICE) {
GstVaH264EncFrame *vaf;
GstVideoCodecFrame *f;
for (i = 0; i < g_queue_get_length (&self->ref_list); i++) {
f = g_queue_peek_nth (&self->ref_list, i);
vaf = _enc_frame (f);
if (vaf->poc < frame->poc)
continue;
list1[list1_num] = vaf;
list1_num++;
}
/* reorder to select the most nearest backward frames. */
g_qsort_with_data (list1, list1_num, sizeof (gpointer),
(GCompareDataFunc) _poc_asc_compare, NULL);
if (list1_num > self->gop.ref_num_list1)
list1_num = self->gop.ref_num_list1;
}
g_assert (list0_num + list1_num <= self->gop.num_ref_frames);
if (!_fill_picture_parameter (self, frame, &pic_param))
return FALSE;
if (!_add_picture_parameter (self, frame, &pic_param))
return FALSE;
_fill_pps (&pic_param, &self->sequence_hdr, &pps);
if ((self->packed_headers & VA_ENC_PACKED_HEADER_PICTURE)
&& frame->type == GST_H264_I_SLICE
&& !_add_picture_header (self, frame, &pps))
return FALSE;
slice_of_mbs = self->mb_width * self->mb_height / self->num_slices;
slice_mod_mbs = self->mb_width * self->mb_height % self->num_slices;
slice_start_mb = 0;
slice_mbs = 0;
for (i = 0; i < self->num_slices; i++) {
VAEncSliceParameterBufferH264 slice;
slice_mbs = slice_of_mbs;
/* divide the remainder to each equally */
if (slice_mod_mbs) {
slice_mbs++;
slice_mod_mbs--;
}
if (!_add_one_slice (self, frame, slice_start_mb, slice_mbs, &slice,
list0, list0_num, list1, list1_num))
return FALSE;
if ((self->packed_headers & VA_ENC_PACKED_HEADER_SLICE) &&
(!_add_slice_header (self, frame, &pps, &slice, list0, list0_num, list1,
list1_num)))
return FALSE;
slice_start_mb += slice_mbs;
}
if (!gst_va_encoder_encode (self->encoder, frame->picture)) {
GST_ERROR_OBJECT (self, "Encode frame error");
return FALSE;
}
return TRUE;
}
static gboolean
gst_va_h264_enc_start (GstVideoEncoder * encoder)
{
GstVaH264Enc *self = GST_VA_H264_ENC (encoder);
/* Set the minimum pts to some huge value (1000 hours). This keeps
* the dts at the start of the stream from needing to be
* negative. */
self->start_pts = GST_SECOND * 60 * 60 * 1000;
gst_video_encoder_set_min_pts (encoder, self->start_pts);
return TRUE;
}
static gboolean
gst_va_h264_enc_open (GstVideoEncoder * venc)
{
GstVaH264Enc *encoder = GST_VA_H264_ENC (venc);
GstVaH264EncClass *klass = GST_VA_H264_ENC_GET_CLASS (venc);
gboolean ret = FALSE;
if (!gst_va_ensure_element_data (venc, klass->render_device_path,
&encoder->display))
return FALSE;
if (!g_atomic_pointer_get (&encoder->encoder)) {
GstVaEncoder *va_encoder;
va_encoder = gst_va_encoder_new (encoder->display, klass->codec);
if (va_encoder)
ret = TRUE;
gst_object_replace ((GstObject **) (&encoder->encoder),
(GstObject *) va_encoder);
gst_clear_object (&va_encoder);
} else {
ret = TRUE;
}
return ret;
}
static gboolean
gst_va_h264_enc_close (GstVideoEncoder * venc)
{
GstVaH264Enc *self = GST_VA_H264_ENC (venc);
gst_va_h264_enc_reset_state (self);
gst_clear_object (&self->encoder);
gst_clear_object (&self->display);
return TRUE;
}
static GstCaps *
gst_va_h264_enc_get_caps (GstVideoEncoder * venc, GstCaps * filter)
{
GstVaH264Enc *self = GST_VA_H264_ENC (venc);
GstCaps *caps = NULL, *tmp;
if (self->encoder)
caps = gst_va_encoder_get_sinkpad_caps (self->encoder);
if (caps) {
if (filter) {
tmp = gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
gst_caps_unref (caps);
caps = tmp;
}
} else {
caps = gst_video_encoder_proxy_getcaps (venc, NULL, filter);
}
GST_LOG_OBJECT (self, "Returning caps %" GST_PTR_FORMAT, caps);
return caps;
}
static void
_flush_all_frames (GstVideoEncoder * venc)
{
GstVaH264Enc *self = GST_VA_H264_ENC (venc);
g_queue_clear_full (&self->reorder_list,
(GDestroyNotify) gst_video_codec_frame_unref);
g_queue_clear_full (&self->output_list,
(GDestroyNotify) gst_video_codec_frame_unref);
g_queue_clear_full (&self->ref_list,
(GDestroyNotify) gst_video_codec_frame_unref);
}
static gboolean
gst_va_h264_enc_flush (GstVideoEncoder * venc)
{
GstVaH264Enc *self = GST_VA_H264_ENC (venc);
_flush_all_frames (venc);
/* begin from an IDR after flush. */
self->gop.cur_frame_index = 0;
self->gop.cur_frame_num = 0;
return TRUE;
}
static gboolean
gst_va_h264_enc_stop (GstVideoEncoder * venc)
{
GstVaH264Enc *const self = GST_VA_H264_ENC (venc);
_flush_all_frames (venc);
if (!gst_va_encoder_close (self->encoder)) {
GST_ERROR_OBJECT (self, "Failed to close the VA encoder");
return FALSE;
}
if (self->raw_pool)
gst_buffer_pool_set_active (self->raw_pool, FALSE);
gst_clear_object (&self->raw_pool);
if (self->input_state)
gst_video_codec_state_unref (self->input_state);
self->input_state = NULL;
if (self->output_state)
gst_video_codec_state_unref (self->output_state);
self->output_state = NULL;
gst_clear_caps (&self->in_caps);
return TRUE;
}
static gboolean
_try_import_buffer (GstVaH264Enc * self, GstBuffer * inbuf)
{
VASurfaceID surface;
/* The VA buffer. */
surface = gst_va_buffer_get_surface (inbuf);
if (surface != VA_INVALID_ID)
return TRUE;
/* TODO: DMA buffer. */
return FALSE;
}
static GstBufferPool *
_get_sinkpad_pool (GstVaH264Enc * self)
{
GstAllocator *allocator;
GstAllocationParams params = { 0, };
guint size, usage_hint = VA_SURFACE_ATTRIB_USAGE_HINT_ENCODER;
GArray *surface_formats = NULL;
GstCaps *caps;
if (self->raw_pool)
return self->raw_pool;
g_assert (self->in_caps);
caps = gst_caps_copy (self->in_caps);
gst_caps_set_features_simple (caps,
gst_caps_features_from_string (GST_CAPS_FEATURE_MEMORY_VA));
gst_allocation_params_init (&params);
size = GST_VIDEO_INFO_SIZE (&self->in_info);
surface_formats = gst_va_encoder_get_surface_formats (self->encoder);
allocator = gst_va_allocator_new (self->display, surface_formats);
self->raw_pool = gst_va_pool_new_with_config (caps, size, 1, 0,
usage_hint, allocator, &params);
if (!self->raw_pool) {
gst_object_unref (allocator);
return NULL;
}
gst_va_allocator_get_format (allocator, &self->sinkpad_info, NULL);
gst_object_unref (allocator);
gst_buffer_pool_set_active (self->raw_pool, TRUE);
return self->raw_pool;
}
static GstFlowReturn
_import_input_buffer (GstVaH264Enc * self, GstBuffer * inbuf, GstBuffer ** buf)
{
GstBuffer *buffer = NULL;
GstBufferPool *pool;
GstFlowReturn ret;
GstVideoFrame in_frame, out_frame;
gboolean imported, copied;
imported = _try_import_buffer (self, inbuf);
if (imported) {
*buf = gst_buffer_ref (inbuf);
return GST_FLOW_OK;
}
/* input buffer doesn't come from a vapool, thus it is required to
* have a pool, grab from it a new buffer and copy the input
* buffer to the new one */
if (!(pool = _get_sinkpad_pool (self)))
return GST_FLOW_ERROR;
ret = gst_buffer_pool_acquire_buffer (pool, &buffer, NULL);
if (ret != GST_FLOW_OK)
return ret;
GST_LOG_OBJECT (self, "copying input frame");
if (!gst_video_frame_map (&in_frame, &self->in_info, inbuf, GST_MAP_READ))
goto invalid_buffer;
if (!gst_video_frame_map (&out_frame, &self->sinkpad_info, buffer,
GST_MAP_WRITE)) {
gst_video_frame_unmap (&in_frame);
goto invalid_buffer;
}
copied = gst_video_frame_copy (&out_frame, &in_frame);
gst_video_frame_unmap (&out_frame);
gst_video_frame_unmap (&in_frame);
if (!copied)
goto invalid_buffer;
/* strictly speaking this is not needed but let's play safe */
if (!gst_buffer_copy_into (buffer, inbuf, GST_BUFFER_COPY_FLAGS |
GST_BUFFER_COPY_TIMESTAMPS, 0, -1))
return GST_FLOW_ERROR;
*buf = buffer;
return GST_FLOW_OK;
invalid_buffer:
{
GST_ELEMENT_WARNING (self, CORE, NOT_IMPLEMENTED, (NULL),
("invalid video buffer received"));
if (buffer)
gst_buffer_unref (buffer);
return GST_FLOW_ERROR;
}
}
static GstFlowReturn
_push_buffer_to_downstream (GstVaH264Enc * self, GstVideoCodecFrame * frame)
{
GstVaH264EncFrame *frame_enc;
GstFlowReturn ret;
guint coded_size;
goffset offset;
GstBuffer *buf;
VADisplay dpy;
VASurfaceID surface;
VAStatus status;
VACodedBufferSegment *seg, *seg_list;
dpy = gst_va_display_get_va_dpy (self->display);
frame_enc = _enc_frame (frame);
/* Wait for encoding to finish */
surface = gst_va_encode_picture_get_raw_surface (frame_enc->picture);
status = vaSyncSurface (dpy, surface);
if (status != VA_STATUS_SUCCESS) {
GST_WARNING_OBJECT (self, "vaSyncSurface: %s", vaErrorStr (status));
goto error;
}
seg_list = NULL;
status = vaMapBuffer (dpy, frame_enc->picture->coded_buffer,
(gpointer *) & seg_list);
if (status != VA_STATUS_SUCCESS) {
GST_WARNING_OBJECT (self, "vaMapBuffer: %s", vaErrorStr (status));
goto error;
}
if (!seg_list) {
GST_WARNING_OBJECT (self, "coded buffer has no segment list");
goto error;
}
coded_size = 0;
for (seg = seg_list; seg; seg = seg->next)
coded_size += seg->size;
buf = gst_video_encoder_allocate_output_buffer (GST_VIDEO_ENCODER_CAST (self),
coded_size);
if (!buf) {
GST_ERROR_OBJECT (self, "Failed to allocate output buffer, size %d",
coded_size);
goto error;
}
offset = 0;
for (seg = seg_list; seg; seg = seg->next) {
gsize write_size;
write_size = gst_buffer_fill (buf, offset, seg->buf, seg->size);
if (write_size != seg->size) {
GST_WARNING_OBJECT (self, "Segment size is %d, but copied %"
G_GSIZE_FORMAT, seg->size, write_size);
break;
}
offset += seg->size;
}
status = vaUnmapBuffer (dpy, frame_enc->picture->coded_buffer);
if (status != VA_STATUS_SUCCESS)
GST_WARNING ("vaUnmapBuffer: %s", vaErrorStr (status));
frame->pts =
self->start_pts + self->frame_duration * frame_enc->total_frame_count;
/* The PTS should always be later than the DTS. */
frame->dts = self->start_pts + self->frame_duration *
((gint64) self->output_frame_count -
(gint64) self->gop.num_reorder_frames);
self->output_frame_count++;
frame->duration = self->frame_duration;
gst_buffer_replace (&frame->output_buffer, buf);
gst_clear_buffer (&buf);
GST_LOG_OBJECT (self, "Push to downstream: frame system_frame_number: %d,"
" pts: %" GST_TIME_FORMAT ", dts: %" GST_TIME_FORMAT
" duration: %" GST_TIME_FORMAT ", buffer size: %" G_GSIZE_FORMAT,
frame->system_frame_number, GST_TIME_ARGS (frame->pts),
GST_TIME_ARGS (frame->dts), GST_TIME_ARGS (frame->duration),
gst_buffer_get_size (frame->output_buffer));
ret = gst_video_encoder_finish_frame (GST_VIDEO_ENCODER (self), frame);
return ret;
error:
gst_clear_buffer (&frame->output_buffer);
gst_clear_buffer (&buf);
gst_video_encoder_finish_frame (GST_VIDEO_ENCODER (self), frame);
return GST_FLOW_ERROR;
}
static gboolean
_reorder_frame (GstVideoEncoder * venc, GstVideoCodecFrame * frame,
gboolean bump_all, GstVideoCodecFrame ** out_frame)
{
GstVaH264Enc *self = GST_VA_H264_ENC (venc);
GstVaH264EncClass *klass = GST_VA_H264_ENC_GET_CLASS (self);
g_assert (klass->push_frame);
if (!klass->push_frame (self, frame, bump_all)) {
GST_ERROR_OBJECT (self, "Failed to push the input frame"
" system_frame_number: %d into the reorder list",
frame->system_frame_number);
*out_frame = NULL;
return FALSE;
}
g_assert (klass->pop_frame);
if (!klass->pop_frame (self, out_frame)) {
GST_ERROR_OBJECT (self, "Failed to pop the frame from the reorder list");
*out_frame = NULL;
return FALSE;
}
return TRUE;
}
static gint
_sort_by_frame_num (gconstpointer a, gconstpointer b, gpointer user_data)
{
GstVaH264EncFrame *frame1 = _enc_frame ((GstVideoCodecFrame *) a);
GstVaH264EncFrame *frame2 = _enc_frame ((GstVideoCodecFrame *) b);
g_assert (frame1->frame_num != frame2->frame_num);
return frame1->frame_num - frame2->frame_num;
}
static GstVideoCodecFrame *
_find_unused_reference_frame (GstVaH264Enc * self, GstVaH264EncFrame * frame)
{
GstVaH264EncFrame *b_vaframe;
GstVideoCodecFrame *b_frame;
guint i;
/* We still have more space. */
if (g_queue_get_length (&self->ref_list) < self->gop.num_ref_frames)
return NULL;
/* Not b_pyramid, sliding window is enough. */
if (!self->gop.b_pyramid)
return g_queue_peek_head (&self->ref_list);
/* I/P frame, just using sliding window. */
if (frame->type != GST_H264_B_SLICE)
return g_queue_peek_head (&self->ref_list);
/* Choose the B frame with lowest POC. */
b_frame = NULL;
b_vaframe = NULL;
for (i = 0; i < g_queue_get_length (&self->ref_list); i++) {
GstVaH264EncFrame *vaf;
GstVideoCodecFrame *f;
f = g_queue_peek_nth (&self->ref_list, i);
vaf = _enc_frame (f);
if (vaf->type != GST_H264_B_SLICE)
continue;
if (!b_frame) {
b_frame = f;
b_vaframe = _enc_frame (b_frame);
continue;
}
b_vaframe = _enc_frame (b_frame);
g_assert (vaf->poc != b_vaframe->poc);
if (vaf->poc < b_vaframe->poc) {
b_frame = f;
b_vaframe = _enc_frame (b_frame);
}
}
/* No B frame as ref. */
if (!b_frame)
return g_queue_peek_head (&self->ref_list);
if (b_frame != g_queue_peek_head (&self->ref_list)) {
b_vaframe = _enc_frame (b_frame);
frame->unused_for_reference_pic_num = b_vaframe->frame_num;
GST_LOG_OBJECT (self, "The frame with POC: %d, pic_num %d will be"
" replaced by the frame with POC: %d, pic_num %d explicitly by"
" using memory_management_control_operation=1",
b_vaframe->poc, b_vaframe->frame_num, frame->poc, frame->frame_num);
}
return b_frame;
}
static GstFlowReturn
_encode_frame (GstVideoEncoder * venc, GstVideoCodecFrame * gst_frame)
{
GstVaH264Enc *self = GST_VA_H264_ENC (venc);
GstVaH264EncClass *klass = GST_VA_H264_ENC_GET_CLASS (self);
GstVaH264EncFrame *frame;
GstVideoCodecFrame *unused_ref = NULL;
frame = _enc_frame (gst_frame);
g_assert (frame->picture == NULL);
frame->picture = gst_va_encode_picture_new (self->encoder,
gst_frame->input_buffer);
if (!frame->picture) {
GST_ERROR_OBJECT (venc, "Failed to create the encode picture");
return GST_FLOW_ERROR;
}
if (frame->is_ref)
unused_ref = _find_unused_reference_frame (self, frame);
if (!klass->encode_frame (self, gst_frame)) {
GST_ERROR_OBJECT (venc, "Failed to encode the frame");
return GST_FLOW_ERROR;
}
g_queue_push_tail (&self->output_list, gst_video_codec_frame_ref (gst_frame));
if (frame->is_ref) {
if (unused_ref) {
if (!g_queue_remove (&self->ref_list, unused_ref))
g_assert_not_reached ();
gst_video_codec_frame_unref (unused_ref);
}
/* Add it into the reference list. */
g_queue_push_tail (&self->ref_list, gst_video_codec_frame_ref (gst_frame));
g_queue_sort (&self->ref_list, _sort_by_frame_num, NULL);
g_assert (g_queue_get_length (&self->ref_list) <= self->gop.num_ref_frames);
}
return GST_FLOW_OK;
}
static GstFlowReturn
gst_va_h264_enc_handle_frame (GstVideoEncoder * venc,
GstVideoCodecFrame * frame)
{
GstVaH264Enc *self = GST_VA_H264_ENC (venc);
GstFlowReturn ret;
GstBuffer *in_buf = NULL;
GstVaH264EncFrame *frame_in = NULL;
GstVideoCodecFrame *frame_out, *frame_encode = NULL;
GST_LOG_OBJECT (venc,
"handle frame id %d, dts %" GST_TIME_FORMAT ", pts %" GST_TIME_FORMAT,
frame->system_frame_number,
GST_TIME_ARGS (GST_BUFFER_DTS (frame->input_buffer)),
GST_TIME_ARGS (GST_BUFFER_PTS (frame->input_buffer)));
ret = _import_input_buffer (self, frame->input_buffer, &in_buf);
if (ret != GST_FLOW_OK)
goto error_buffer_invalid;
gst_buffer_replace (&frame->input_buffer, in_buf);
gst_clear_buffer (&in_buf);
frame_in = gst_va_enc_frame_new ();
frame_in->total_frame_count = self->input_frame_count++;
gst_video_codec_frame_set_user_data (frame, frame_in, gst_va_enc_frame_free);
if (!_reorder_frame (venc, frame, FALSE, &frame_encode))
goto error_reorder;
/* pass it to reorder list and we should not use it again. */
frame = NULL;
while (frame_encode) {
ret = _encode_frame (venc, frame_encode);
if (ret != GST_FLOW_OK)
goto error_encode;
while (g_queue_get_length (&self->output_list) > 0) {
frame_out = g_queue_pop_head (&self->output_list);
gst_video_codec_frame_unref (frame_out);
ret = _push_buffer_to_downstream (self, frame_out);
if (ret != GST_FLOW_OK)
goto error_push_buffer;
}
frame_encode = NULL;
if (!_reorder_frame (venc, NULL, FALSE, &frame_encode))
goto error_reorder;
}
return ret;
error_buffer_invalid:
{
GST_ELEMENT_ERROR (venc, STREAM, ENCODE,
("Failed to import the input frame."), (NULL));
gst_clear_buffer (&in_buf);
gst_clear_buffer (&frame->output_buffer);
gst_video_encoder_finish_frame (venc, frame);
return ret;
}
error_reorder:
{
GST_ELEMENT_ERROR (venc, STREAM, ENCODE,
("Failed to reorder the input frame."), (NULL));
if (frame) {
gst_clear_buffer (&frame->output_buffer);
gst_video_encoder_finish_frame (venc, frame);
}
return GST_FLOW_ERROR;
}
error_encode:
{
GST_ELEMENT_ERROR (venc, STREAM, ENCODE,
("Failed to encode the frame."), (NULL));
gst_clear_buffer (&frame_encode->output_buffer);
gst_video_encoder_finish_frame (venc, frame_encode);
return ret;
}
error_push_buffer:
GST_ERROR_OBJECT (self, "Failed to push the buffer");
return ret;
}
static GstFlowReturn
gst_va_h264_enc_drain (GstVideoEncoder * venc)
{
GstVaH264Enc *self = GST_VA_H264_ENC (venc);
GstFlowReturn ret = GST_FLOW_OK;
GstVideoCodecFrame *frame_enc = NULL;
GST_DEBUG_OBJECT (self, "Encoder is draining");
/* Kickout all cached frames */
if (!_reorder_frame (venc, NULL, TRUE, &frame_enc)) {
ret = GST_FLOW_ERROR;
goto error_and_purge_all;
}
while (frame_enc) {
if (g_queue_is_empty (&self->reorder_list))
_enc_frame (frame_enc)->last_frame = TRUE;
ret = _encode_frame (venc, frame_enc);
if (ret != GST_FLOW_OK)
goto error_and_purge_all;
frame_enc = g_queue_pop_head (&self->output_list);
gst_video_codec_frame_unref (frame_enc);
ret = _push_buffer_to_downstream (self, frame_enc);
frame_enc = NULL;
if (ret != GST_FLOW_OK)
goto error_and_purge_all;
frame_enc = NULL;
if (!_reorder_frame (venc, NULL, TRUE, &frame_enc)) {
ret = GST_FLOW_ERROR;
goto error_and_purge_all;
}
}
g_assert (g_queue_is_empty (&self->reorder_list));
/* Output all frames. */
while (!g_queue_is_empty (&self->output_list)) {
frame_enc = g_queue_pop_head (&self->output_list);
gst_video_codec_frame_unref (frame_enc);
ret = _push_buffer_to_downstream (self, frame_enc);
frame_enc = NULL;
if (ret != GST_FLOW_OK)
goto error_and_purge_all;
}
/* Also clear the reference list. */
g_queue_clear_full (&self->ref_list,
(GDestroyNotify) gst_video_codec_frame_unref);
return GST_FLOW_OK;
error_and_purge_all:
if (frame_enc) {
gst_clear_buffer (&frame_enc->output_buffer);
gst_video_encoder_finish_frame (venc, frame_enc);
}
if (!g_queue_is_empty (&self->output_list)) {
GST_WARNING_OBJECT (self, "Still %d frame in the output list"
" after drain", g_queue_get_length (&self->output_list));
while (!g_queue_is_empty (&self->output_list)) {
frame_enc = g_queue_pop_head (&self->output_list);
gst_video_codec_frame_unref (frame_enc);
gst_clear_buffer (&frame_enc->output_buffer);
gst_video_encoder_finish_frame (venc, frame_enc);
}
}
if (!g_queue_is_empty (&self->reorder_list)) {
GST_WARNING_OBJECT (self, "Still %d frame in the reorder list"
" after drain", g_queue_get_length (&self->reorder_list));
while (!g_queue_is_empty (&self->reorder_list)) {
frame_enc = g_queue_pop_head (&self->reorder_list);
gst_video_codec_frame_unref (frame_enc);
gst_clear_buffer (&frame_enc->output_buffer);
gst_video_encoder_finish_frame (venc, frame_enc);
}
}
/* Also clear the reference list. */
g_queue_clear_full (&self->ref_list,
(GDestroyNotify) gst_video_codec_frame_unref);
return ret;
}
static GstFlowReturn
gst_va_h264_enc_finish (GstVideoEncoder * venc)
{
return gst_va_h264_enc_drain (venc);
}
static GstAllocator *
_allocator_from_caps (GstVaH264Enc * self, GstCaps * caps)
{
GstAllocator *allocator = NULL;
if (gst_caps_is_dmabuf (caps)) {
allocator = gst_va_dmabuf_allocator_new (self->display);
} else {
GArray *surface_formats =
gst_va_encoder_get_surface_formats (self->encoder);
allocator = gst_va_allocator_new (self->display, surface_formats);
}
return allocator;
}
static gboolean
gst_va_h264_enc_propose_allocation (GstVideoEncoder * venc, GstQuery * query)
{
GstVaH264Enc *self = GST_VA_H264_ENC (venc);
GstAllocator *allocator = NULL;
GstAllocationParams params = { 0, };
GstBufferPool *pool;
GstCaps *caps;
GstVideoInfo info;
gboolean need_pool = FALSE;
guint size, usage_hint = VA_SURFACE_ATTRIB_USAGE_HINT_ENCODER;
gst_query_parse_allocation (query, &caps, &need_pool);
if (!caps)
return FALSE;
if (!gst_video_info_from_caps (&info, caps)) {
GST_ERROR_OBJECT (self, "Cannot parse caps %" GST_PTR_FORMAT, caps);
return FALSE;
}
size = GST_VIDEO_INFO_SIZE (&info);
gst_allocation_params_init (&params);
if (!(allocator = _allocator_from_caps (self, caps)))
return FALSE;
pool = gst_va_pool_new_with_config (caps, size, 1, 0, usage_hint, allocator,
&params);
if (!pool) {
gst_object_unref (allocator);
goto config_failed;
}
gst_query_add_allocation_param (query, allocator, &params);
gst_query_add_allocation_pool (query, pool, size, 0, 0);
GST_DEBUG_OBJECT (self,
"proposing %" GST_PTR_FORMAT " with allocator %" GST_PTR_FORMAT,
pool, allocator);
gst_object_unref (allocator);
gst_object_unref (pool);
gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, NULL);
return TRUE;
/* ERRORS */
config_failed:
{
GST_ERROR_OBJECT (self, "failed to set config");
return FALSE;
}
}
static void
gst_va_h264_enc_set_context (GstElement * element, GstContext * context)
{
GstVaDisplay *old_display, *new_display;
GstVaH264Enc *self = GST_VA_H264_ENC (element);
GstVaH264EncClass *klass = GST_VA_H264_ENC_GET_CLASS (self);
gboolean ret;
old_display = self->display ? gst_object_ref (self->display) : NULL;
ret = gst_va_handle_set_context (element, context, klass->render_device_path,
&self->display);
new_display = self->display ? gst_object_ref (self->display) : NULL;
if (!ret || (old_display && new_display && old_display != new_display
&& self->encoder)) {
GST_ELEMENT_WARNING (element, RESOURCE, BUSY,
("Can't replace VA display while operating"), (NULL));
}
gst_clear_object (&old_display);
gst_clear_object (&new_display);
GST_ELEMENT_CLASS (parent_class)->set_context (element, context);
}
static gboolean
gst_va_h264_enc_set_format (GstVideoEncoder * venc, GstVideoCodecState * state)
{
GstVaH264Enc *self = GST_VA_H264_ENC (venc);
GstVaH264EncClass *klass = GST_VA_H264_ENC_GET_CLASS (self);
GstCaps *out_caps;
guint max_ref_frames;
g_return_val_if_fail (state->caps != NULL, FALSE);
if (self->input_state)
gst_video_codec_state_unref (self->input_state);
self->input_state = gst_video_codec_state_ref (state);
gst_caps_replace (&self->in_caps, state->caps);
if (!gst_video_info_from_caps (&self->in_info, self->in_caps))
return FALSE;
if (gst_va_h264_enc_drain (venc) != GST_FLOW_OK)
return FALSE;
if (!gst_va_encoder_close (self->encoder)) {
GST_ERROR_OBJECT (self, "Failed to close the VA encoder");
return FALSE;
}
g_assert (klass->reconfig);
if (!klass->reconfig (self)) {
GST_ERROR_OBJECT (self, "Reconfig the encoder error");
return FALSE;
}
max_ref_frames = self->gop.num_ref_frames + 3 /* scratch frames */ ;
if (!gst_va_encoder_open (self->encoder, self->profile, self->entrypoint,
GST_VIDEO_INFO_FORMAT (&self->in_info), self->rt_format,
self->mb_width * 16, self->mb_height * 16, self->codedbuf_size,
max_ref_frames, self->rc.rc_ctrl_mode, self->packed_headers)) {
GST_ERROR_OBJECT (self, "Failed to open the VA encoder.");
return FALSE;
}
/* Add some tags */
{
GstTagList *tags = gst_tag_list_new_empty ();
const gchar *encoder_name;
guint bitrate = 0;
g_object_get (venc, "bitrate", &bitrate, NULL);
if (bitrate > 0)
gst_tag_list_add (tags, GST_TAG_MERGE_REPLACE, GST_TAG_NOMINAL_BITRATE,
bitrate, NULL);
if ((encoder_name =
gst_element_class_get_metadata (GST_ELEMENT_GET_CLASS (venc),
GST_ELEMENT_METADATA_LONGNAME)))
gst_tag_list_add (tags, GST_TAG_MERGE_REPLACE, GST_TAG_ENCODER,
encoder_name, NULL);
gst_tag_list_add (tags, GST_TAG_MERGE_REPLACE, GST_TAG_CODEC, "H264", NULL);
gst_video_encoder_merge_tags (venc, tags, GST_TAG_MERGE_REPLACE);
gst_tag_list_unref (tags);
}
out_caps = gst_va_profile_caps (self->profile);
g_assert (out_caps);
out_caps = gst_caps_fixate (out_caps);
if (self->level_str)
gst_caps_set_simple (out_caps, "level", G_TYPE_STRING, self->level_str,
NULL);
gst_caps_set_simple (out_caps, "width", G_TYPE_INT, self->width,
"height", G_TYPE_INT, self->height, "alignment", G_TYPE_STRING, "au",
"stream-format", G_TYPE_STRING, "byte-stream", NULL);
GST_DEBUG_OBJECT (self, "output caps is %" GST_PTR_FORMAT, out_caps);
if (self->output_state)
gst_video_codec_state_unref (self->output_state);
self->output_state = gst_video_encoder_set_output_state (venc, out_caps,
self->input_state);
if (!gst_video_encoder_negotiate (venc)) {
GST_ERROR_OBJECT (self, "Failed to negotiate with the downstream");
return FALSE;
}
return TRUE;
}
static gboolean
_query_context (GstVaH264Enc * self, GstQuery * query)
{
GstVaDisplay *display = NULL;
gboolean ret;
gst_object_replace ((GstObject **) & display, (GstObject *) self->display);
ret = gst_va_handle_context_query (GST_ELEMENT_CAST (self), query, display);
gst_clear_object (&display);
return ret;
}
static gboolean
gst_va_h264_enc_src_query (GstVideoEncoder * venc, GstQuery * query)
{
GstVaH264Enc *self = GST_VA_H264_ENC (venc);
gboolean ret = FALSE;
switch (GST_QUERY_TYPE (query)) {
case GST_QUERY_CONTEXT:{
ret = _query_context (self, query);
break;
}
case GST_QUERY_CAPS:{
GstCaps *caps = NULL, *tmp, *filter = NULL;
GstVaEncoder *va_encoder = NULL;
gboolean fixed_caps;
gst_object_replace ((GstObject **) & va_encoder,
(GstObject *) self->encoder);
gst_query_parse_caps (query, &filter);
fixed_caps = GST_PAD_IS_FIXED_CAPS (GST_VIDEO_ENCODER_SRC_PAD (venc));
if (!fixed_caps && va_encoder)
caps = gst_va_encoder_get_srcpad_caps (va_encoder);
gst_clear_object (&va_encoder);
if (caps) {
if (filter) {
tmp = gst_caps_intersect_full (filter, caps,
GST_CAPS_INTERSECT_FIRST);
gst_caps_unref (caps);
caps = tmp;
}
GST_LOG_OBJECT (self, "Returning caps %" GST_PTR_FORMAT, caps);
gst_query_set_caps_result (query, caps);
gst_caps_unref (caps);
ret = TRUE;
break;
}
/* else jump to default */
}
default:
ret = GST_VIDEO_ENCODER_CLASS (parent_class)->src_query (venc, query);
break;
}
return ret;
}
static gboolean
gst_va_h264_enc_sink_query (GstVideoEncoder * venc, GstQuery * query)
{
GstVaH264Enc *self = GST_VA_H264_ENC (venc);
if (GST_QUERY_TYPE (query) == GST_QUERY_CONTEXT)
return _query_context (self, query);
return GST_VIDEO_ENCODER_CLASS (parent_class)->sink_query (venc, query);
}
/* *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 = "video/x-h264";
static gpointer
_register_debug_category (gpointer data)
{
GST_DEBUG_CATEGORY_INIT (gst_va_h264enc_debug, "vah264enc", 0,
"VA h264 encoder");
return NULL;
}
static void
gst_va_h264_enc_init (GTypeInstance * instance, gpointer g_class)
{
GstVaH264Enc *self = GST_VA_H264_ENC (instance);
g_queue_init (&self->reorder_list);
g_queue_init (&self->ref_list);
g_queue_init (&self->output_list);
/* default values */
self->prop.key_int_max = 0;
self->prop.num_bframes = 0;
self->prop.num_iframes = 0;
self->prop.num_ref_frames = 3;
self->prop.b_pyramid = FALSE;
self->prop.num_slices = 1;
self->prop.min_qp = 1;
self->prop.max_qp = 51;
self->prop.qp_i = 26;
self->prop.qp_p = 26;
self->prop.qp_b = 26;
self->prop.use_dct8x8 = TRUE;
self->prop.use_cabac = TRUE;
self->prop.use_trellis = FALSE;
self->prop.aud = FALSE;
self->prop.mbbrc = 0;
self->prop.bitrate = 0;
self->prop.target_percentage = 66;
self->prop.target_usage = 4;
self->prop.rc_ctrl = VA_RC_CBR;
self->prop.cpb_size = 0;
}
static void
gst_va_h264_enc_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec)
{
GstVaH264Enc *const self = GST_VA_H264_ENC (object);
if (self->encoder && gst_va_encoder_is_open (self->encoder)) {
GST_ERROR_OBJECT (object,
"failed to set any property after encoding started");
return;
}
GST_OBJECT_LOCK (self);
switch (prop_id) {
case PROP_KEY_INT_MAX:
self->prop.key_int_max = g_value_get_uint (value);
break;
case PROP_BFRAMES:
self->prop.num_bframes = g_value_get_uint (value);
break;
case PROP_IFRAMES:
self->prop.num_iframes = g_value_get_uint (value);
break;
case PROP_NUM_REF_FRAMES:
self->prop.num_ref_frames = g_value_get_uint (value);
break;
case PROP_B_PYRAMID:
self->prop.b_pyramid = g_value_get_boolean (value);
break;
case PROP_NUM_SLICES:
self->prop.num_slices = g_value_get_uint (value);
break;
case PROP_MIN_QP:
self->prop.min_qp = g_value_get_uint (value);
break;
case PROP_MAX_QP:
self->prop.max_qp = g_value_get_uint (value);
break;
case PROP_QP_I:
self->prop.qp_i = g_value_get_uint (value);
break;
case PROP_QP_P:
self->prop.qp_p = g_value_get_uint (value);
break;
case PROP_QP_B:
self->prop.qp_b = g_value_get_uint (value);
break;
case PROP_DCT8X8:
self->prop.use_dct8x8 = g_value_get_boolean (value);
break;
case PROP_CABAC:
self->prop.use_cabac = g_value_get_boolean (value);
break;
case PROP_TRELLIS:
self->prop.use_trellis = g_value_get_boolean (value);
break;
case PROP_AUD:
self->prop.aud = g_value_get_boolean (value);
break;
case PROP_MBBRC:
self->prop.mbbrc = g_value_get_enum (value);
break;
case PROP_BITRATE:
self->prop.bitrate = g_value_get_uint (value);
break;
case PROP_TARGET_PERCENTAGE:
self->prop.target_percentage = g_value_get_uint (value);
break;
case PROP_TARGET_USAGE:
self->prop.target_usage = g_value_get_uint (value);
break;
case PROP_RATE_CONTROL:
self->prop.rc_ctrl = g_value_get_enum (value);
break;
case PROP_CPB_SIZE:
self->prop.cpb_size = 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_h264_enc_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec)
{
GstVaH264Enc *const self = GST_VA_H264_ENC (object);
GST_OBJECT_LOCK (self);
switch (prop_id) {
case PROP_KEY_INT_MAX:
g_value_set_uint (value, self->prop.key_int_max);
break;
case PROP_BFRAMES:
g_value_set_uint (value, self->prop.num_bframes);
break;
case PROP_IFRAMES:
g_value_set_uint (value, self->prop.num_iframes);
break;
case PROP_NUM_REF_FRAMES:
g_value_set_uint (value, self->prop.num_ref_frames);
break;
case PROP_B_PYRAMID:
g_value_set_boolean (value, self->prop.b_pyramid);
break;
case PROP_NUM_SLICES:
g_value_set_uint (value, self->prop.num_slices);
break;
case PROP_MIN_QP:
g_value_set_uint (value, self->prop.min_qp);
break;
case PROP_MAX_QP:
g_value_set_uint (value, self->prop.max_qp);
break;
case PROP_QP_I:
g_value_set_uint (value, self->prop.qp_i);
break;
case PROP_QP_P:
g_value_set_uint (value, self->prop.qp_p);
break;
case PROP_QP_B:
g_value_set_uint (value, self->prop.qp_b);
break;
case PROP_DCT8X8:
g_value_set_boolean (value, self->prop.use_dct8x8);
break;
case PROP_CABAC:
g_value_set_boolean (value, self->prop.use_cabac);
break;
case PROP_TRELLIS:
g_value_set_boolean (value, self->prop.use_trellis);
break;
case PROP_AUD:
g_value_set_boolean (value, self->prop.aud);
break;
case PROP_MBBRC:
g_value_set_enum (value, self->prop.mbbrc);
break;
case PROP_BITRATE:
g_value_set_uint (value, self->prop.bitrate);
break;
case PROP_TARGET_PERCENTAGE:
g_value_set_uint (value, self->prop.target_percentage);
break;
case PROP_TARGET_USAGE:
g_value_set_uint (value, self->prop.target_usage);
break;
case PROP_RATE_CONTROL:
g_value_set_enum (value, self->prop.rc_ctrl);
break;
case PROP_CPB_SIZE:
g_value_set_uint (value, self->prop.cpb_size);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
GST_OBJECT_UNLOCK (self);
}
struct CData
{
gchar *render_device_path;
gchar *description;
GstCaps *sink_caps;
GstCaps *src_caps;
};
static void
gst_va_h264_enc_class_init (gpointer g_klass, gpointer class_data)
{
GstCaps *src_doc_caps, *sink_doc_caps;
GObjectClass *const object_class = G_OBJECT_CLASS (g_klass);
GstElementClass *const element_class = GST_ELEMENT_CLASS (g_klass);
GstVideoEncoderClass *const venc_class = GST_VIDEO_ENCODER_CLASS (g_klass);
GstVaH264EncClass *const klass = GST_VA_H264_ENC_CLASS (g_klass);
GstPadTemplate *sink_pad_templ, *src_pad_templ;
struct CData *cdata = class_data;
gchar *long_name;
parent_class = g_type_class_peek_parent (g_klass);
klass->render_device_path = g_strdup (cdata->render_device_path);
klass->codec = H264;
if (cdata->description) {
long_name = g_strdup_printf ("VA-API H.264 Encoder in %s",
cdata->description);
} else {
long_name = g_strdup ("VA-API H.264 Encoder");
}
gst_element_class_set_metadata (element_class, long_name,
"Codec/Encoder/Video/Hardware", "VA-API based H.264 video encoder",
"He Junyan <junyan.he@intel.com>");
sink_doc_caps = gst_caps_from_string (sink_caps_str);
src_doc_caps = gst_caps_from_string (src_caps_str);
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_h264_enc_set_property;
object_class->get_property = gst_va_h264_enc_get_property;
element_class->set_context = GST_DEBUG_FUNCPTR (gst_va_h264_enc_set_context);
venc_class->open = GST_DEBUG_FUNCPTR (gst_va_h264_enc_open);
venc_class->start = GST_DEBUG_FUNCPTR (gst_va_h264_enc_start);
venc_class->close = GST_DEBUG_FUNCPTR (gst_va_h264_enc_close);
venc_class->stop = GST_DEBUG_FUNCPTR (gst_va_h264_enc_stop);
venc_class->handle_frame = GST_DEBUG_FUNCPTR (gst_va_h264_enc_handle_frame);
venc_class->finish = GST_DEBUG_FUNCPTR (gst_va_h264_enc_finish);
venc_class->flush = GST_DEBUG_FUNCPTR (gst_va_h264_enc_flush);
venc_class->set_format = GST_DEBUG_FUNCPTR (gst_va_h264_enc_set_format);
venc_class->getcaps = GST_DEBUG_FUNCPTR (gst_va_h264_enc_get_caps);
venc_class->propose_allocation =
GST_DEBUG_FUNCPTR (gst_va_h264_enc_propose_allocation);
venc_class->src_query = GST_DEBUG_FUNCPTR (gst_va_h264_enc_src_query);
venc_class->sink_query = GST_DEBUG_FUNCPTR (gst_va_h264_enc_sink_query);
klass->reconfig = GST_DEBUG_FUNCPTR (gst_va_h264_enc_reconfig);
klass->push_frame = GST_DEBUG_FUNCPTR (gst_va_h264_enc_push_frame);
klass->pop_frame = GST_DEBUG_FUNCPTR (gst_va_h264_enc_pop_frame);
klass->encode_frame = GST_DEBUG_FUNCPTR (gst_va_h264_enc_encode_frame);
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);
/**
* GstVaEncoder:key-int-max:
*
* The maximal distance between two keyframes.
*/
properties[PROP_KEY_INT_MAX] = g_param_spec_uint ("key-int-max",
"Key frame maximal interval",
"The maximal distance between two keyframes. It decides the size of GOP"
" (0: auto-calculate)", 0, MAX_GOP_SIZE, 0,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT);
/**
* GstVaH264Enc:b-frames:
*
* Number of B-frames between two reference frames.
*/
properties[PROP_BFRAMES] = g_param_spec_uint ("b-frames", "B Frames",
"Number of B frames between I and P reference frames", 0, 31, 0,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT);
/**
* GstVaH264Enc:i-frames:
*
* Force the number of i-frames insertion within one GOP.
*/
properties[PROP_IFRAMES] = g_param_spec_uint ("i-frames", "I Frames",
"Force the number of I frames insertion within one GOP, not including the "
"first IDR frame", 0, 1023, 0,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT);
/**
* GstVaH264Enc:ref-frames:
*
* The number of reference frames.
*/
properties[PROP_NUM_REF_FRAMES] = g_param_spec_uint ("ref-frames",
"Number of Reference Frames",
"Number of reference frames, including both the forward and the backward",
0, 16, 3, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT);
/**
* GstVaH264Enc:b-pyramid:
*
* Enable the b-pyramid reference structure in GOP.
*/
properties[PROP_B_PYRAMID] = g_param_spec_boolean ("b-pyramid", "b pyramid",
"Enable the b-pyramid reference structure in the GOP", FALSE,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT);
/**
* GstVaH264Enc:num-slices:
*
* The number of slices per frame.
*/
properties[PROP_NUM_SLICES] = g_param_spec_uint ("num-slices",
"Number of Slices", "Number of slices per frame", 1, 200, 1,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT);
/**
* GstVaH264Enc:max-qp:
*
* The maximum quantizer value.
*/
properties[PROP_MAX_QP] = g_param_spec_uint ("max-qp", "Maximum QP",
"Maximum quantizer value for each frame", 0, 51, 51,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT);
/**
* GstVaH264Enc:min-qp:
*
* The minimum quantizer value.
*/
properties[PROP_MIN_QP] = g_param_spec_uint ("min-qp", "Minimum QP",
"Minimum quantizer value for each frame", 0, 51, 1,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT);
/**
* GstVaH264Enc:qpi:
*
* The quantizer value for I frame. In CQP mode, it specifies the QP of
* I frame, in other mode, it specifies the init QP of all frames.
*/
properties[PROP_QP_I] = g_param_spec_uint ("qpi", "I Frame QP",
"The quantizer value for I frame. In CQP mode, it specifies the QP of I "
"frame, in other mode, it specifies the init QP of all frames", 0, 51, 26,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT);
/**
* GstVaH264Enc:qpp:
*
* The quantizer value for P frame. This is available only in CQP mode.
*/
properties[PROP_QP_P] = g_param_spec_uint ("qpp",
"The quantizer value for P frame",
"The quantizer value for P frame. This is available only in CQP mode",
0, 51, 26,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT);
/**
* GstVaH264Enc:qpb:
*
* The quantizer value for B frame. This is available only in CQP mode.
*/
properties[PROP_QP_B] = g_param_spec_uint ("qpb",
"The quantizer value for B frame",
"The quantizer value for B frame. This is available only in CQP mode",
0, 51, 26,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT);
/**
* GstVaH264Enc:dct8x8:
*
* Enable adaptive use of 8x8 transforms in I-frames. This improves
* the compression ratio but requires high profile at least.
*/
properties[PROP_DCT8X8] = g_param_spec_boolean ("dct8x8",
"Enable 8x8 DCT",
"Enable adaptive use of 8x8 transforms in I-frames", TRUE,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT);
/**
* GstVaH264Enc:cabac:
*
* It enables CABAC entropy coding mode to improve compression ratio,
* but requires main profile at least.
*/
properties[PROP_CABAC] = g_param_spec_boolean ("cabac", "Enable CABAC",
"Enable CABAC entropy coding mode", TRUE,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT);
/**
* GstVaH264Enc:trellis:
*
* It enable the trellis quantization method.
* Trellis is an improved quantization algorithm.
*/
properties[PROP_TRELLIS] = g_param_spec_boolean ("trellis", "Enable trellis",
"Enable the trellis quantization method", FALSE,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT);
/**
* GstVaH264Enc:aud:
*
* Insert the AU (Access Unit) delimeter for each frame.
*/
properties[PROP_AUD] = g_param_spec_boolean ("aud", "Insert AUD",
"Insert AU (Access Unit) delimeter for each frame", FALSE,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT);
/**
* GstVaH264Enc:mbbrc:
*
* Macroblock level bitrate control.
* This is not compatible with Constant QP rate control.
*/
properties[PROP_MBBRC] = g_param_spec_enum ("mbbrc",
"Macroblock level Bitrate Control",
"Macroblock level Bitrate Control. It is not compatible with CQP",
gst_va_h264_enc_mbbrc_get_type (), 0,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT);
/**
* GstVaH264Enc:bitrate:
*
* The desired target bitrate, expressed in kbps.
* This is not available in CQP mode.
*
* CBR: This applies equally to the minimum, maximum and target bitrate.
* VBR: This applies to the target bitrate. The driver will use the
* "target-percentage" together to calculate the minimum and maximum bitrate.
* VCM: This applies to the target bitrate. The minimum and maximum bitrate
* are not needed.
*/
properties[PROP_BITRATE] = g_param_spec_uint ("bitrate", "Bitrate (kbps)",
"The desired bitrate expressed in kbps (0: auto-calculate)",
0, 2000 * 1024, 0,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT);
/**
* GstVaH264Enc:target-percentage:
*
* The target percentage of the max bitrate, and expressed in uint,
* equal to "target percentage"*100.
* "target percentage" = "target bitrate" * 100 / "max bitrate"
* This is available only when rate-control is VBR.
* The driver uses it to calculate the minimum and maximum bitrate.
*/
properties[PROP_TARGET_PERCENTAGE] = g_param_spec_uint ("target-percentage",
"target bitrate percentage",
"The percentage for 'target bitrate'/'maximum bitrate' (Only in VBR)",
50, 100, 66,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT);
/**
* GstVaH264Enc:target-usage:
*
* The target usage of the encoder. It controls and balances the encoding
* speed and the encoding quality. The lower value has better quality but
* slower speed, the higher value has faster speed but lower quality.
*/
properties[PROP_TARGET_USAGE] = g_param_spec_uint ("target-usage",
"target usage",
"The target usage to control and balance the encoding speed/quality",
1, 7, 4, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT);
/**
* GstVaH264Enc:cpb-size:
*
* The desired max CPB size in Kb (0: auto-calculate).
*/
properties[PROP_CPB_SIZE] = g_param_spec_uint ("cpb-size",
"max CPB size in Kb",
"The desired max CPB size in Kb (0: auto-calculate)", 0, 2000 * 1024, 0,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT);
/**
* GstVaH264Enc:rate-control:
*
* The desired rate control mode for the encoder.
*/
properties[PROP_RATE_CONTROL] = g_param_spec_enum ("rate-control",
"rate control mode", "The desired rate control mode for the encoder",
gst_va_h264_enc_rate_control_get_type (), VA_RC_CBR,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT);
g_object_class_install_properties (object_class, N_PROPERTIES, properties);
gst_type_mark_as_plugin_api (gst_va_h264_enc_rate_control_get_type (), 0);
gst_type_mark_as_plugin_api (gst_va_h264_enc_mbbrc_get_type (), 0);
}
static GstCaps *
_complete_src_caps (GstCaps * srccaps)
{
GstCaps *caps = gst_caps_copy (srccaps);
GValue val = G_VALUE_INIT;
g_value_init (&val, G_TYPE_STRING);
g_value_set_string (&val, "au");
gst_caps_set_value (caps, "alignment", &val);
g_value_unset (&val);
g_value_init (&val, G_TYPE_STRING);
g_value_set_string (&val, "byte-stream");
gst_caps_set_value (caps, "stream-format", &val);
g_value_unset (&val);
return caps;
}
gboolean
gst_va_h264_enc_register (GstPlugin * plugin, GstVaDevice * device,
GstCaps * sink_caps, GstCaps * src_caps, guint rank)
{
static GOnce debug_once = G_ONCE_INIT;
GType type;
GTypeInfo type_info = {
.class_size = sizeof (GstVaH264EncClass),
.class_init = gst_va_h264_enc_class_init,
.instance_size = sizeof (GstVaH264Enc),
.instance_init = gst_va_h264_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);
cdata = g_new (struct CData, 1);
cdata->description = NULL;
cdata->render_device_path = g_strdup (device->render_device_path);
cdata->sink_caps = gst_caps_ref (sink_caps);
cdata->src_caps = _complete_src_caps (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;
type_name = g_strdup ("GstVaH264Enc");
feature_name = g_strdup ("vah264enc");
/* The first encoder to be registered should use a constant name,
* like vah264enc, for any additional encoders, we create unique
* names, using inserting the render device name. */
if (g_type_from_name (type_name)) {
gchar *basename = g_path_get_basename (device->render_device_path);
g_free (type_name);
g_free (feature_name);
type_name = g_strdup_printf ("GstVa%sH264Enc", basename);
feature_name = g_strdup_printf ("va%sh264enc", basename);
cdata->description = basename;
/* lower rank for non-first device */
if (rank > 0)
rank--;
}
g_once (&debug_once, _register_debug_category, NULL);
type = g_type_register_static (GST_TYPE_VIDEO_ENCODER,
type_name, &type_info, 0);
ret = gst_element_register (plugin, feature_name, rank, type);
g_free (type_name);
g_free (feature_name);
return ret;
}