gstreamer/ext/libav/gstavvidenc.c
Jan Schmidt 30a406591a avvid/audenc: Set some tags in the output for downstream's info
Add the codec name and bitrate into the output for informational
purposes. Bitrate in particular is now used by flvmux to set
videodatarate and audiodatarate in the resulting stream
2015-08-24 00:01:40 +10:00

1018 lines
31 KiB
C

/* GStreamer
* Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
*
* 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.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <assert.h>
#include <string.h>
/* for stats file handling */
#include <stdio.h>
#include <glib/gstdio.h>
#include <errno.h>
#include <libavcodec/avcodec.h>
#include <gst/gst.h>
#include <gst/video/gstvideometa.h>
#include "gstav.h"
#include "gstavcodecmap.h"
#include "gstavutils.h"
#include "gstavvidenc.h"
#include "gstavcfg.h"
#define DEFAULT_VIDEO_BITRATE 300000 /* in bps */
#define DEFAULT_VIDEO_GOP_SIZE 15
#define DEFAULT_WIDTH 352
#define DEFAULT_HEIGHT 288
#define VIDEO_BUFFER_SIZE (1024*1024)
enum
{
/* FILL ME */
LAST_SIGNAL
};
enum
{
PROP_0,
PROP_BIT_RATE,
PROP_GOP_SIZE,
PROP_ME_METHOD,
PROP_BUFSIZE,
PROP_RTP_PAYLOAD_SIZE,
PROP_MAX_THREADS,
PROP_COMPLIANCE,
PROP_CFG_BASE,
};
#define GST_TYPE_ME_METHOD (gst_ffmpegvidenc_me_method_get_type())
static GType
gst_ffmpegvidenc_me_method_get_type (void)
{
static GType ffmpegenc_me_method_type = 0;
static GEnumValue ffmpegenc_me_methods[] = {
{ME_ZERO, "None (Very low quality)", "zero"},
{ME_FULL, "Full (Slow, unmaintained)", "full"},
{ME_LOG, "Logarithmic (Low quality, unmaintained)", "logarithmic"},
{ME_PHODS, "phods (Low quality, unmaintained)", "phods"},
{ME_EPZS, "EPZS (Best quality, Fast)", "epzs"},
{ME_X1, "X1 (Experimental)", "x1"},
{0, NULL, NULL},
};
if (!ffmpegenc_me_method_type) {
ffmpegenc_me_method_type =
g_enum_register_static ("GstLibAVVidEncMeMethod", ffmpegenc_me_methods);
}
return ffmpegenc_me_method_type;
}
/* A number of function prototypes are given so we can refer to them later. */
static void gst_ffmpegvidenc_class_init (GstFFMpegVidEncClass * klass);
static void gst_ffmpegvidenc_base_init (GstFFMpegVidEncClass * klass);
static void gst_ffmpegvidenc_init (GstFFMpegVidEnc * ffmpegenc);
static void gst_ffmpegvidenc_finalize (GObject * object);
static gboolean gst_ffmpegvidenc_start (GstVideoEncoder * encoder);
static gboolean gst_ffmpegvidenc_stop (GstVideoEncoder * encoder);
static GstFlowReturn gst_ffmpegvidenc_finish (GstVideoEncoder * encoder);
static gboolean gst_ffmpegvidenc_set_format (GstVideoEncoder * encoder,
GstVideoCodecState * state);
static gboolean gst_ffmpegvidenc_propose_allocation (GstVideoEncoder * encoder,
GstQuery * query);
static gboolean gst_ffmpegvidenc_flush (GstVideoEncoder * encoder);
static GstFlowReturn gst_ffmpegvidenc_handle_frame (GstVideoEncoder * encoder,
GstVideoCodecFrame * frame);
static void gst_ffmpegvidenc_set_property (GObject * object,
guint prop_id, const GValue * value, GParamSpec * pspec);
static void gst_ffmpegvidenc_get_property (GObject * object,
guint prop_id, GValue * value, GParamSpec * pspec);
#define GST_FFENC_PARAMS_QDATA g_quark_from_static_string("avenc-params")
static GstElementClass *parent_class = NULL;
/*static guint gst_ffmpegvidenc_signals[LAST_SIGNAL] = { 0 }; */
static void
gst_ffmpegvidenc_base_init (GstFFMpegVidEncClass * klass)
{
GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
AVCodec *in_plugin;
GstPadTemplate *srctempl = NULL, *sinktempl = NULL;
GstCaps *srccaps = NULL, *sinkcaps = NULL;
gchar *longname, *description;
in_plugin =
(AVCodec *) g_type_get_qdata (G_OBJECT_CLASS_TYPE (klass),
GST_FFENC_PARAMS_QDATA);
g_assert (in_plugin != NULL);
/* construct the element details struct */
longname = g_strdup_printf ("libav %s encoder", in_plugin->long_name);
description = g_strdup_printf ("libav %s encoder", in_plugin->name);
gst_element_class_set_metadata (element_class, longname,
"Codec/Encoder/Video", description,
"Wim Taymans <wim.taymans@gmail.com>, "
"Ronald Bultje <rbultje@ronald.bitfreak.net>");
g_free (longname);
g_free (description);
if (!(srccaps = gst_ffmpeg_codecid_to_caps (in_plugin->id, NULL, TRUE))) {
GST_DEBUG ("Couldn't get source caps for encoder '%s'", in_plugin->name);
srccaps = gst_caps_new_empty_simple ("unknown/unknown");
}
sinkcaps = gst_ffmpeg_codectype_to_video_caps (NULL,
in_plugin->id, TRUE, in_plugin);
if (!sinkcaps) {
GST_DEBUG ("Couldn't get sink caps for encoder '%s'", in_plugin->name);
sinkcaps = gst_caps_new_empty_simple ("unknown/unknown");
}
/* pad templates */
sinktempl = gst_pad_template_new ("sink", GST_PAD_SINK,
GST_PAD_ALWAYS, sinkcaps);
srctempl = gst_pad_template_new ("src", GST_PAD_SRC, GST_PAD_ALWAYS, srccaps);
gst_element_class_add_pad_template (element_class, srctempl);
gst_element_class_add_pad_template (element_class, sinktempl);
klass->in_plugin = in_plugin;
klass->srctempl = srctempl;
klass->sinktempl = sinktempl;
return;
}
static void
gst_ffmpegvidenc_class_init (GstFFMpegVidEncClass * klass)
{
GObjectClass *gobject_class;
GstVideoEncoderClass *venc_class;
int caps;
gobject_class = (GObjectClass *) klass;
venc_class = (GstVideoEncoderClass *) klass;
parent_class = g_type_class_peek_parent (klass);
gobject_class->set_property = gst_ffmpegvidenc_set_property;
gobject_class->get_property = gst_ffmpegvidenc_get_property;
/* FIXME: could use -1 for a sensible per-codec default based on
* e.g. input resolution and framerate */
g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_BIT_RATE,
g_param_spec_int ("bitrate", "Bit Rate",
"Target Video Bitrate", 0, G_MAXINT, DEFAULT_VIDEO_BITRATE,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_GOP_SIZE,
g_param_spec_int ("gop-size", "GOP Size",
"Number of frames within one GOP", 0, G_MAXINT,
DEFAULT_VIDEO_GOP_SIZE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_ME_METHOD,
g_param_spec_enum ("me-method", "ME Method", "Motion Estimation Method",
GST_TYPE_ME_METHOD, ME_EPZS,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_BUFSIZE,
g_param_spec_int ("buffer-size", "Buffer Size",
"Size of the video buffers", 0, G_MAXINT, 0,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (G_OBJECT_CLASS (klass),
PROP_RTP_PAYLOAD_SIZE, g_param_spec_int ("rtp-payload-size",
"RTP Payload Size", "Target GOB length", 0, G_MAXINT, 0,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
caps = klass->in_plugin->capabilities;
if (caps & (CODEC_CAP_FRAME_THREADS | CODEC_CAP_SLICE_THREADS)) {
g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_MAX_THREADS,
g_param_spec_int ("max-threads", "Maximum encode threads",
"Maximum number of worker threads to spawn. (0 = auto)",
0, G_MAXINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
}
g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_COMPLIANCE,
g_param_spec_enum ("compliance", "Compliance",
"Adherence of the encoder to the specifications",
GST_TYPE_FFMPEG_COMPLIANCE, FFMPEG_DEFAULT_COMPLIANCE,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
/* register additional properties, possibly dependent on the exact CODEC */
gst_ffmpeg_cfg_install_property (klass, PROP_CFG_BASE);
venc_class->start = gst_ffmpegvidenc_start;
venc_class->stop = gst_ffmpegvidenc_stop;
venc_class->finish = gst_ffmpegvidenc_finish;
venc_class->handle_frame = gst_ffmpegvidenc_handle_frame;
venc_class->set_format = gst_ffmpegvidenc_set_format;
venc_class->propose_allocation = gst_ffmpegvidenc_propose_allocation;
venc_class->flush = gst_ffmpegvidenc_flush;
gobject_class->finalize = gst_ffmpegvidenc_finalize;
}
static void
gst_ffmpegvidenc_init (GstFFMpegVidEnc * ffmpegenc)
{
GstFFMpegVidEncClass *klass =
(GstFFMpegVidEncClass *) G_OBJECT_GET_CLASS (ffmpegenc);
GST_PAD_SET_ACCEPT_TEMPLATE (GST_VIDEO_ENCODER_SINK_PAD (ffmpegenc));
/* ffmpeg objects */
ffmpegenc->context = avcodec_alloc_context3 (klass->in_plugin);
ffmpegenc->picture = av_frame_alloc ();
ffmpegenc->opened = FALSE;
ffmpegenc->file = NULL;
ffmpegenc->bitrate = DEFAULT_VIDEO_BITRATE;
ffmpegenc->me_method = ME_EPZS;
ffmpegenc->buffer_size = 512 * 1024;
ffmpegenc->gop_size = DEFAULT_VIDEO_GOP_SIZE;
ffmpegenc->rtp_payload_size = 0;
ffmpegenc->compliance = FFMPEG_DEFAULT_COMPLIANCE;
ffmpegenc->max_threads = 0;
ffmpegenc->lmin = 2;
ffmpegenc->lmax = 31;
ffmpegenc->max_key_interval = 0;
gst_ffmpeg_cfg_set_defaults (ffmpegenc);
}
static void
gst_ffmpegvidenc_finalize (GObject * object)
{
GstFFMpegVidEnc *ffmpegenc = (GstFFMpegVidEnc *) object;
gst_ffmpeg_cfg_finalize (ffmpegenc);
/* clean up remaining allocated data */
av_frame_free (&ffmpegenc->picture);
gst_ffmpeg_avcodec_close (ffmpegenc->context);
av_free (ffmpegenc->context);
g_free (ffmpegenc->filename);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static gboolean
gst_ffmpegvidenc_set_format (GstVideoEncoder * encoder,
GstVideoCodecState * state)
{
GstCaps *other_caps;
GstCaps *allowed_caps;
GstCaps *icaps;
GstVideoCodecState *output_format;
enum PixelFormat pix_fmt;
GstFFMpegVidEnc *ffmpegenc = (GstFFMpegVidEnc *) encoder;
GstFFMpegVidEncClass *oclass =
(GstFFMpegVidEncClass *) G_OBJECT_GET_CLASS (ffmpegenc);
/* close old session */
if (ffmpegenc->opened) {
gst_ffmpeg_avcodec_close (ffmpegenc->context);
ffmpegenc->opened = FALSE;
if (avcodec_get_context_defaults3 (ffmpegenc->context,
oclass->in_plugin) < 0) {
GST_DEBUG_OBJECT (ffmpegenc, "Failed to set context defaults");
return FALSE;
}
}
/* if we set it in _getcaps we should set it also in _link */
ffmpegenc->context->strict_std_compliance = ffmpegenc->compliance;
/* user defined properties */
ffmpegenc->context->bit_rate = ffmpegenc->bitrate;
ffmpegenc->context->bit_rate_tolerance = ffmpegenc->bitrate;
ffmpegenc->context->gop_size = ffmpegenc->gop_size;
ffmpegenc->context->me_method = ffmpegenc->me_method;
GST_DEBUG_OBJECT (ffmpegenc, "Setting avcontext to bitrate %d, gop_size %d",
ffmpegenc->bitrate, ffmpegenc->gop_size);
if (ffmpegenc->max_threads == 0) {
if (!(oclass->in_plugin->capabilities & CODEC_CAP_AUTO_THREADS))
ffmpegenc->context->thread_count = gst_ffmpeg_auto_max_threads ();
else
ffmpegenc->context->thread_count = 0;
} else
ffmpegenc->context->thread_count = ffmpegenc->max_threads;
/* RTP payload used for GOB production (for Asterisk) */
if (ffmpegenc->rtp_payload_size) {
ffmpegenc->context->rtp_payload_size = ffmpegenc->rtp_payload_size;
}
/* additional avcodec settings */
/* first fill in the majority by copying over */
gst_ffmpeg_cfg_fill_context (ffmpegenc, ffmpegenc->context);
/* then handle some special cases */
ffmpegenc->context->lmin = (ffmpegenc->lmin * FF_QP2LAMBDA + 0.5);
ffmpegenc->context->lmax = (ffmpegenc->lmax * FF_QP2LAMBDA + 0.5);
if (ffmpegenc->interlaced) {
ffmpegenc->context->flags |=
CODEC_FLAG_INTERLACED_DCT | CODEC_FLAG_INTERLACED_ME;
}
/* some other defaults */
ffmpegenc->context->rc_strategy = 2;
ffmpegenc->context->b_frame_strategy = 0;
ffmpegenc->context->coder_type = 0;
ffmpegenc->context->context_model = 0;
ffmpegenc->context->scenechange_threshold = 0;
/* and last but not least the pass; CBR, 2-pass, etc */
ffmpegenc->context->flags |= ffmpegenc->pass;
switch (ffmpegenc->pass) {
/* some additional action depends on type of pass */
case CODEC_FLAG_QSCALE:
ffmpegenc->context->global_quality
= ffmpegenc->picture->quality = FF_QP2LAMBDA * ffmpegenc->quantizer;
break;
case CODEC_FLAG_PASS1: /* need to prepare a stats file */
/* we don't close when changing caps, fingers crossed */
if (!ffmpegenc->file)
ffmpegenc->file = g_fopen (ffmpegenc->filename, "w");
if (!ffmpegenc->file)
goto open_file_err;
break;
case CODEC_FLAG_PASS2:
{ /* need to read the whole stats file ! */
gsize size;
if (!g_file_get_contents (ffmpegenc->filename,
&ffmpegenc->context->stats_in, &size, NULL))
goto file_read_err;
break;
}
default:
break;
}
GST_DEBUG_OBJECT (ffmpegenc, "Extracting common video information");
/* fetch pix_fmt, fps, par, width, height... */
gst_ffmpeg_videoinfo_to_context (&state->info, ffmpegenc->context);
/* sanitize time base */
if (ffmpegenc->context->time_base.num <= 0
|| ffmpegenc->context->time_base.den <= 0)
goto insane_timebase;
if ((oclass->in_plugin->id == AV_CODEC_ID_MPEG4)
&& (ffmpegenc->context->time_base.den > 65535)) {
/* MPEG4 Standards do not support time_base denominator greater than
* (1<<16) - 1 . We therefore scale them down.
* Agreed, it will not be the exact framerate... but the difference
* shouldn't be that noticeable */
ffmpegenc->context->time_base.num =
(gint) gst_util_uint64_scale_int (ffmpegenc->context->time_base.num,
65535, ffmpegenc->context->time_base.den);
ffmpegenc->context->time_base.den = 65535;
GST_LOG_OBJECT (ffmpegenc, "MPEG4 : scaled down framerate to %d / %d",
ffmpegenc->context->time_base.den, ffmpegenc->context->time_base.num);
}
pix_fmt = ffmpegenc->context->pix_fmt;
/* max-key-interval may need the framerate set above */
if (ffmpegenc->max_key_interval) {
AVCodecContext *ctx;
/* override gop-size */
ctx = ffmpegenc->context;
ctx->gop_size = (ffmpegenc->max_key_interval < 0) ?
(-ffmpegenc->max_key_interval
* (ctx->time_base.den * ctx->ticks_per_frame / ctx->time_base.num))
: ffmpegenc->max_key_interval;
}
/* some codecs support more than one format, first auto-choose one */
GST_DEBUG_OBJECT (ffmpegenc, "picking an output format ...");
allowed_caps = gst_pad_get_allowed_caps (GST_VIDEO_ENCODER_SRC_PAD (encoder));
if (!allowed_caps) {
GST_DEBUG_OBJECT (ffmpegenc, "... but no peer, using template caps");
/* we need to copy because get_allowed_caps returns a ref, and
* get_pad_template_caps doesn't */
allowed_caps =
gst_pad_get_pad_template_caps (GST_VIDEO_ENCODER_SRC_PAD (encoder));
}
GST_DEBUG_OBJECT (ffmpegenc, "chose caps %" GST_PTR_FORMAT, allowed_caps);
gst_ffmpeg_caps_with_codecid (oclass->in_plugin->id,
oclass->in_plugin->type, allowed_caps, ffmpegenc->context);
/* try to set this caps on the other side */
other_caps = gst_ffmpeg_codecid_to_caps (oclass->in_plugin->id,
ffmpegenc->context, TRUE);
if (!other_caps) {
gst_caps_unref (allowed_caps);
goto unsupported_codec;
}
icaps = gst_caps_intersect (allowed_caps, other_caps);
gst_caps_unref (allowed_caps);
gst_caps_unref (other_caps);
if (gst_caps_is_empty (icaps)) {
gst_caps_unref (icaps);
goto unsupported_codec;
}
icaps = gst_caps_fixate (icaps);
GST_DEBUG_OBJECT (ffmpegenc, "codec flags 0x%08x", ffmpegenc->context->flags);
/* open codec */
if (gst_ffmpeg_avcodec_open (ffmpegenc->context, oclass->in_plugin) < 0)
goto open_codec_fail;
/* is the colourspace correct? */
if (pix_fmt != ffmpegenc->context->pix_fmt)
goto pix_fmt_err;
/* we may have failed mapping caps to a pixfmt,
* and quite some codecs do not make up their own mind about that
* in any case, _NONE can never work out later on */
if (pix_fmt == AV_PIX_FMT_NONE)
goto bad_input_fmt;
/* second pass stats buffer no longer needed */
if (ffmpegenc->context->stats_in)
g_free (ffmpegenc->context->stats_in);
/* Store input state and set output state */
if (ffmpegenc->input_state)
gst_video_codec_state_unref (ffmpegenc->input_state);
ffmpegenc->input_state = gst_video_codec_state_ref (state);
output_format = gst_video_encoder_set_output_state (encoder, icaps, state);
gst_video_codec_state_unref (output_format);
/* Store some tags */
{
GstTagList *tags = gst_tag_list_new_empty ();
const gchar *codec;
gst_tag_list_add (tags, GST_TAG_MERGE_REPLACE, GST_TAG_NOMINAL_BITRATE,
ffmpegenc->context->bit_rate, NULL);
if ((codec =
gst_ffmpeg_get_codecid_longname (ffmpegenc->context->codec_id)))
gst_tag_list_add (tags, GST_TAG_MERGE_REPLACE, GST_TAG_VIDEO_CODEC, codec,
NULL);
gst_video_encoder_merge_tags (encoder, tags, GST_TAG_MERGE_REPLACE);
gst_tag_list_unref (tags);
}
/* success! */
ffmpegenc->opened = TRUE;
return TRUE;
/* ERRORS */
open_file_err:
{
GST_ELEMENT_ERROR (ffmpegenc, RESOURCE, OPEN_WRITE,
(("Could not open file \"%s\" for writing."), ffmpegenc->filename),
GST_ERROR_SYSTEM);
return FALSE;
}
file_read_err:
{
GST_ELEMENT_ERROR (ffmpegenc, RESOURCE, READ,
(("Could not get contents of file \"%s\"."), ffmpegenc->filename),
GST_ERROR_SYSTEM);
return FALSE;
}
insane_timebase:
{
GST_ERROR_OBJECT (ffmpegenc, "Rejecting time base %d/%d",
ffmpegenc->context->time_base.den, ffmpegenc->context->time_base.num);
goto cleanup_stats_in;
}
unsupported_codec:
{
GST_DEBUG ("Unsupported codec - no caps found");
goto cleanup_stats_in;
}
open_codec_fail:
{
GST_DEBUG_OBJECT (ffmpegenc, "avenc_%s: Failed to open libav codec",
oclass->in_plugin->name);
goto close_codec;
}
pix_fmt_err:
{
GST_DEBUG_OBJECT (ffmpegenc,
"avenc_%s: AV wants different colourspace (%d given, %d wanted)",
oclass->in_plugin->name, pix_fmt, ffmpegenc->context->pix_fmt);
goto close_codec;
}
bad_input_fmt:
{
GST_DEBUG_OBJECT (ffmpegenc, "avenc_%s: Failed to determine input format",
oclass->in_plugin->name);
goto close_codec;
}
close_codec:
{
gst_ffmpeg_avcodec_close (ffmpegenc->context);
if (avcodec_get_context_defaults3 (ffmpegenc->context,
oclass->in_plugin) < 0)
GST_DEBUG_OBJECT (ffmpegenc, "Failed to set context defaults");
goto cleanup_stats_in;
}
cleanup_stats_in:
{
if (ffmpegenc->context->stats_in)
g_free (ffmpegenc->context->stats_in);
return FALSE;
}
}
static gboolean
gst_ffmpegvidenc_propose_allocation (GstVideoEncoder * encoder,
GstQuery * query)
{
gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, NULL);
return GST_VIDEO_ENCODER_CLASS (parent_class)->propose_allocation (encoder,
query);
}
static void
gst_ffmpegvidenc_free_avpacket (gpointer pkt)
{
av_packet_unref ((AVPacket *) pkt);
g_slice_free (AVPacket, pkt);
}
typedef struct
{
GstBuffer *buffer;
GstVideoFrame vframe;
} BufferInfo;
static void
buffer_info_free (void *opaque, guint8 * data)
{
BufferInfo *info = opaque;
gst_video_frame_unmap (&info->vframe);
gst_buffer_unref (info->buffer);
g_slice_free (BufferInfo, info);
}
static GstFlowReturn
gst_ffmpegvidenc_handle_frame (GstVideoEncoder * encoder,
GstVideoCodecFrame * frame)
{
GstFFMpegVidEnc *ffmpegenc = (GstFFMpegVidEnc *) encoder;
GstBuffer *outbuf;
gint ret = 0, c;
GstVideoInfo *info = &ffmpegenc->input_state->info;
AVPacket *pkt;
int have_data = 0;
BufferInfo *buffer_info;
if (ffmpegenc->interlaced) {
ffmpegenc->picture->interlaced_frame = TRUE;
/* if this is not the case, a filter element should be used to swap fields */
ffmpegenc->picture->top_field_first =
GST_BUFFER_FLAG_IS_SET (frame->input_buffer, GST_VIDEO_BUFFER_FLAG_TFF);
}
if (GST_VIDEO_CODEC_FRAME_IS_FORCE_KEYFRAME (frame))
ffmpegenc->picture->pict_type = AV_PICTURE_TYPE_I;
buffer_info = g_slice_new0 (BufferInfo);
buffer_info->buffer = gst_buffer_ref (frame->input_buffer);
if (!gst_video_frame_map (&buffer_info->vframe, info, frame->input_buffer,
GST_MAP_READ)) {
GST_ERROR_OBJECT (encoder, "Failed to map input buffer");
gst_buffer_unref (buffer_info->buffer);
g_slice_free (BufferInfo, buffer_info);
return GST_FLOW_ERROR;
}
/* Fill avpicture */
ffmpegenc->picture->buf[0] =
av_buffer_create (NULL, 0, buffer_info_free, buffer_info, 0);
for (c = 0; c < AV_NUM_DATA_POINTERS; c++) {
if (c < GST_VIDEO_INFO_N_COMPONENTS (info)) {
ffmpegenc->picture->data[c] =
GST_VIDEO_FRAME_PLANE_DATA (&buffer_info->vframe, c);
ffmpegenc->picture->linesize[c] =
GST_VIDEO_FRAME_COMP_STRIDE (&buffer_info->vframe, c);
} else {
ffmpegenc->picture->data[c] = NULL;
ffmpegenc->picture->linesize[c] = 0;
}
}
ffmpegenc->picture->pts =
gst_ffmpeg_time_gst_to_ff (frame->pts /
ffmpegenc->context->ticks_per_frame, ffmpegenc->context->time_base);
have_data = 0;
pkt = g_slice_new0 (AVPacket);
ret =
avcodec_encode_video2 (ffmpegenc->context, pkt, ffmpegenc->picture,
&have_data);
av_frame_unref (ffmpegenc->picture);
if (ret < 0 || !have_data)
g_slice_free (AVPacket, pkt);
if (ret < 0)
goto encode_fail;
/* Encoder needs more data */
if (!have_data)
return GST_FLOW_OK;
/* save stats info if there is some as well as a stats file */
if (ffmpegenc->file && ffmpegenc->context->stats_out)
if (fprintf (ffmpegenc->file, "%s", ffmpegenc->context->stats_out) < 0)
GST_ELEMENT_ERROR (ffmpegenc, RESOURCE, WRITE,
(("Could not write to file \"%s\"."), ffmpegenc->filename),
GST_ERROR_SYSTEM);
gst_video_codec_frame_unref (frame);
/* Get oldest frame */
frame = gst_video_encoder_get_oldest_frame (encoder);
outbuf =
gst_buffer_new_wrapped_full (GST_MEMORY_FLAG_READONLY, pkt->data,
pkt->size, 0, pkt->size, pkt, gst_ffmpegvidenc_free_avpacket);
frame->output_buffer = outbuf;
/* buggy codec may not set coded_frame */
if (ffmpegenc->context->coded_frame) {
if (ffmpegenc->context->coded_frame->key_frame)
GST_VIDEO_CODEC_FRAME_SET_SYNC_POINT (frame);
} else
GST_WARNING_OBJECT (ffmpegenc, "codec did not provide keyframe info");
return gst_video_encoder_finish_frame (encoder, frame);
/* ERRORS */
encode_fail:
{
#ifndef GST_DISABLE_GST_DEBUG
GstFFMpegVidEncClass *oclass =
(GstFFMpegVidEncClass *) (G_OBJECT_GET_CLASS (ffmpegenc));
GST_ERROR_OBJECT (ffmpegenc,
"avenc_%s: failed to encode buffer", oclass->in_plugin->name);
#endif /* GST_DISABLE_GST_DEBUG */
/* avoid frame (and ts etc) piling up */
return gst_video_encoder_finish_frame (encoder, frame);
}
}
static GstFlowReturn
gst_ffmpegvidenc_flush_buffers (GstFFMpegVidEnc * ffmpegenc, gboolean send)
{
GstVideoCodecFrame *frame;
GstFlowReturn flow_ret = GST_FLOW_OK;
GstBuffer *outbuf;
gint ret;
AVPacket *pkt;
int have_data = 0;
GST_DEBUG_OBJECT (ffmpegenc, "flushing buffers with sending %d", send);
/* no need to empty codec if there is none */
if (!ffmpegenc->opened)
goto done;
while ((frame =
gst_video_encoder_get_oldest_frame (GST_VIDEO_ENCODER (ffmpegenc)))) {
pkt = g_slice_new0 (AVPacket);
have_data = 0;
ret = avcodec_encode_video2 (ffmpegenc->context, pkt, NULL, &have_data);
if (ret < 0) { /* there should be something, notify and give up */
#ifndef GST_DISABLE_GST_DEBUG
GstFFMpegVidEncClass *oclass =
(GstFFMpegVidEncClass *) (G_OBJECT_GET_CLASS (ffmpegenc));
GST_WARNING_OBJECT (ffmpegenc,
"avenc_%s: failed to flush buffer", oclass->in_plugin->name);
#endif /* GST_DISABLE_GST_DEBUG */
g_slice_free (AVPacket, pkt);
gst_video_codec_frame_unref (frame);
break;
}
/* save stats info if there is some as well as a stats file */
if (ffmpegenc->file && ffmpegenc->context->stats_out)
if (fprintf (ffmpegenc->file, "%s", ffmpegenc->context->stats_out) < 0)
GST_ELEMENT_ERROR (ffmpegenc, RESOURCE, WRITE,
(("Could not write to file \"%s\"."), ffmpegenc->filename),
GST_ERROR_SYSTEM);
if (send && have_data) {
outbuf =
gst_buffer_new_wrapped_full (GST_MEMORY_FLAG_READONLY, pkt->data,
pkt->size, 0, pkt->size, pkt->data, gst_ffmpegvidenc_free_avpacket);
frame->output_buffer = outbuf;
if (ffmpegenc->context->coded_frame->key_frame)
GST_VIDEO_CODEC_FRAME_SET_SYNC_POINT (frame);
flow_ret =
gst_video_encoder_finish_frame (GST_VIDEO_ENCODER (ffmpegenc), frame);
} else {
/* no frame attached, so will be skipped and removed from frame list */
gst_video_encoder_finish_frame (GST_VIDEO_ENCODER (ffmpegenc), frame);
}
}
done:
return flow_ret;
}
static void
gst_ffmpegvidenc_set_property (GObject * object,
guint prop_id, const GValue * value, GParamSpec * pspec)
{
GstFFMpegVidEnc *ffmpegenc;
/* Get a pointer of the right type. */
ffmpegenc = (GstFFMpegVidEnc *) (object);
if (ffmpegenc->opened) {
GST_WARNING_OBJECT (ffmpegenc,
"Can't change properties once decoder is setup !");
return;
}
/* Check the argument id to see which argument we're setting. */
switch (prop_id) {
case PROP_BIT_RATE:
ffmpegenc->bitrate = g_value_get_int (value);
break;
case PROP_GOP_SIZE:
ffmpegenc->gop_size = g_value_get_int (value);
break;
case PROP_ME_METHOD:
ffmpegenc->me_method = g_value_get_enum (value);
break;
case PROP_BUFSIZE:
break;
case PROP_RTP_PAYLOAD_SIZE:
ffmpegenc->rtp_payload_size = g_value_get_int (value);
break;
case PROP_COMPLIANCE:
ffmpegenc->compliance = g_value_get_enum (value);
break;
case PROP_MAX_THREADS:
ffmpegenc->max_threads = g_value_get_int (value);
break;
default:
if (!gst_ffmpeg_cfg_set_property (object, value, pspec))
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
/* The set function is simply the inverse of the get fuction. */
static void
gst_ffmpegvidenc_get_property (GObject * object,
guint prop_id, GValue * value, GParamSpec * pspec)
{
GstFFMpegVidEnc *ffmpegenc;
/* It's not null if we got it, but it might not be ours */
ffmpegenc = (GstFFMpegVidEnc *) (object);
switch (prop_id) {
case PROP_BIT_RATE:
g_value_set_int (value, ffmpegenc->bitrate);
break;
case PROP_GOP_SIZE:
g_value_set_int (value, ffmpegenc->gop_size);
break;
case PROP_ME_METHOD:
g_value_set_enum (value, ffmpegenc->me_method);
break;
case PROP_BUFSIZE:
g_value_set_int (value, ffmpegenc->buffer_size);
break;
case PROP_RTP_PAYLOAD_SIZE:
g_value_set_int (value, ffmpegenc->rtp_payload_size);
break;
case PROP_COMPLIANCE:
g_value_set_enum (value, ffmpegenc->compliance);
break;
case PROP_MAX_THREADS:
g_value_set_int (value, ffmpegenc->max_threads);
break;
default:
if (!gst_ffmpeg_cfg_get_property (object, value, pspec))
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static gboolean
gst_ffmpegvidenc_flush (GstVideoEncoder * encoder)
{
GstFFMpegVidEnc *ffmpegenc = (GstFFMpegVidEnc *) encoder;
if (ffmpegenc->opened)
avcodec_flush_buffers (ffmpegenc->context);
return TRUE;
}
static gboolean
gst_ffmpegvidenc_start (GstVideoEncoder * encoder)
{
GstFFMpegVidEnc *ffmpegenc = (GstFFMpegVidEnc *) encoder;
GstFFMpegVidEncClass *oclass =
(GstFFMpegVidEncClass *) G_OBJECT_GET_CLASS (ffmpegenc);
/* close old session */
gst_ffmpeg_avcodec_close (ffmpegenc->context);
if (avcodec_get_context_defaults3 (ffmpegenc->context, oclass->in_plugin) < 0) {
GST_DEBUG_OBJECT (ffmpegenc, "Failed to set context defaults");
return FALSE;
}
return TRUE;
}
static gboolean
gst_ffmpegvidenc_stop (GstVideoEncoder * encoder)
{
GstFFMpegVidEnc *ffmpegenc = (GstFFMpegVidEnc *) encoder;
gst_ffmpegvidenc_flush_buffers (ffmpegenc, FALSE);
gst_ffmpeg_avcodec_close (ffmpegenc->context);
ffmpegenc->opened = FALSE;
if (ffmpegenc->file) {
fclose (ffmpegenc->file);
ffmpegenc->file = NULL;
}
if (ffmpegenc->input_state) {
gst_video_codec_state_unref (ffmpegenc->input_state);
ffmpegenc->input_state = NULL;
}
return TRUE;
}
static GstFlowReturn
gst_ffmpegvidenc_finish (GstVideoEncoder * encoder)
{
GstFFMpegVidEnc *ffmpegenc = (GstFFMpegVidEnc *) encoder;
return gst_ffmpegvidenc_flush_buffers (ffmpegenc, TRUE);
}
gboolean
gst_ffmpegvidenc_register (GstPlugin * plugin)
{
GTypeInfo typeinfo = {
sizeof (GstFFMpegVidEncClass),
(GBaseInitFunc) gst_ffmpegvidenc_base_init,
NULL,
(GClassInitFunc) gst_ffmpegvidenc_class_init,
NULL,
NULL,
sizeof (GstFFMpegVidEnc),
0,
(GInstanceInitFunc) gst_ffmpegvidenc_init,
};
GType type;
AVCodec *in_plugin;
GST_LOG ("Registering encoders");
/* build global ffmpeg param/property info */
gst_ffmpeg_cfg_init ();
in_plugin = av_codec_next (NULL);
while (in_plugin) {
gchar *type_name;
/* Skip non-AV codecs */
if (in_plugin->type != AVMEDIA_TYPE_VIDEO)
goto next;
/* no quasi codecs, please */
if (in_plugin->id == AV_CODEC_ID_RAWVIDEO ||
in_plugin->id == AV_CODEC_ID_V210 ||
in_plugin->id == AV_CODEC_ID_V210X ||
in_plugin->id == AV_CODEC_ID_R210
|| in_plugin->id == AV_CODEC_ID_ZLIB) {
goto next;
}
/* No encoders depending on external libraries (we don't build them, but
* people who build against an external ffmpeg might have them.
* We have native gstreamer plugins for all of those libraries anyway. */
if (!strncmp (in_plugin->name, "lib", 3)) {
GST_DEBUG
("Not using external library encoder %s. Use the gstreamer-native ones instead.",
in_plugin->name);
goto next;
}
/* only video encoders */
if (!av_codec_is_encoder (in_plugin)
|| in_plugin->type != AVMEDIA_TYPE_VIDEO)
goto next;
/* FIXME : We should have a method to know cheaply whether we have a mapping
* for the given plugin or not */
GST_DEBUG ("Trying plugin %s [%s]", in_plugin->name, in_plugin->long_name);
/* no codecs for which we're GUARANTEED to have better alternatives */
if (!strcmp (in_plugin->name, "gif")) {
GST_LOG ("Ignoring encoder %s", in_plugin->name);
goto next;
}
/* construct the type */
type_name = g_strdup_printf ("avenc_%s", in_plugin->name);
type = g_type_from_name (type_name);
if (!type) {
/* create the glib type now */
type =
g_type_register_static (GST_TYPE_VIDEO_ENCODER, type_name, &typeinfo,
0);
g_type_set_qdata (type, GST_FFENC_PARAMS_QDATA, (gpointer) in_plugin);
{
static const GInterfaceInfo preset_info = {
NULL,
NULL,
NULL
};
g_type_add_interface_static (type, GST_TYPE_PRESET, &preset_info);
}
}
if (!gst_element_register (plugin, type_name, GST_RANK_SECONDARY, type)) {
g_free (type_name);
return FALSE;
}
g_free (type_name);
next:
in_plugin = av_codec_next (in_plugin);
}
GST_LOG ("Finished registering encoders");
return TRUE;
}