mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-26 19:51:11 +00:00
Several encoding changes: for video, double check the pix_fmt after opening the AVCodec. The pix_fmt will be changed ...
Original commit message from CVS: Several encoding changes: * for video, double check the pix_fmt after opening the AVCodec. The pix_fmt will be changed to the preferred pix_fmt instead of that opening the codec fails. We fail when it has changed (then, ffmpeg doesn't like it). * for video, set the correct timestamp. * for audio, do *not* set the frame_size: most codecs don't like it. Instead, keep a cache of buffers and always give data of size "frame_size". Maybe I should have used bytestream here, I'm not sure. This works, though. I can now create mpeg1 video, mp2 and ac3 audio. I didn't test any others yet. I also didn't start working on integration with any of the muxers yet, that's all one big TODO. One thing at a time, please. :).
This commit is contained in:
parent
d26aa5ad0b
commit
199e7f14f2
2 changed files with 130 additions and 53 deletions
|
@ -831,8 +831,8 @@ gst_ffmpeg_caps_to_pixfmt (GstCaps *caps,
|
|||
GST_PROPS_FLOAT_TYPE)) {
|
||||
gfloat fps;
|
||||
gst_caps_get_float (caps, "framerate", &fps);
|
||||
context->frame_rate = fps * 1000;
|
||||
context->frame_rate_base = 1000;
|
||||
context->frame_rate = fps * DEFAULT_FRAME_RATE_BASE;
|
||||
context->frame_rate_base = DEFAULT_FRAME_RATE_BASE;
|
||||
}
|
||||
|
||||
if (gst_caps_has_property_typed (caps, "format",
|
||||
|
|
|
@ -44,6 +44,7 @@ struct _GstFFMpegEnc {
|
|||
AVCodecContext *context;
|
||||
AVFrame *picture;
|
||||
gboolean opened;
|
||||
GstBuffer *cache;
|
||||
|
||||
/* cache */
|
||||
gulong bitrate;
|
||||
|
@ -123,7 +124,8 @@ static void gst_ffmpegenc_dispose (GObject *object);
|
|||
|
||||
static GstPadLinkReturn
|
||||
gst_ffmpegenc_connect (GstPad *pad, GstCaps *caps);
|
||||
static void gst_ffmpegenc_chain (GstPad *pad, GstData *_data);
|
||||
static void gst_ffmpegenc_chain_video (GstPad *pad, GstData *_data);
|
||||
static void gst_ffmpegenc_chain_audio (GstPad *pad, GstData *_data);
|
||||
|
||||
static void gst_ffmpegenc_set_property (GObject *object,
|
||||
guint prop_id,
|
||||
|
@ -238,7 +240,6 @@ gst_ffmpegenc_init(GstFFMpegEnc *ffmpegenc)
|
|||
/* setup pads */
|
||||
ffmpegenc->sinkpad = gst_pad_new_from_template (oclass->sinktempl, "sink");
|
||||
gst_pad_set_link_function (ffmpegenc->sinkpad, gst_ffmpegenc_connect);
|
||||
gst_pad_set_chain_function (ffmpegenc->sinkpad, gst_ffmpegenc_chain);
|
||||
ffmpegenc->srcpad = gst_pad_new_from_template (oclass->srctempl, "src");
|
||||
|
||||
gst_element_add_pad (GST_ELEMENT (ffmpegenc), ffmpegenc->sinkpad);
|
||||
|
@ -248,12 +249,17 @@ gst_ffmpegenc_init(GstFFMpegEnc *ffmpegenc)
|
|||
ffmpegenc->context = avcodec_alloc_context();
|
||||
ffmpegenc->picture = avcodec_alloc_frame();
|
||||
ffmpegenc->opened = FALSE;
|
||||
ffmpegenc->cache = NULL;
|
||||
|
||||
if (oclass->in_plugin->type == CODEC_TYPE_VIDEO) {
|
||||
gst_pad_set_chain_function (ffmpegenc->sinkpad, gst_ffmpegenc_chain_video);
|
||||
|
||||
ffmpegenc->bitrate = 300000;
|
||||
ffmpegenc->buffer_size = 512 * 1024;
|
||||
ffmpegenc->gop_size = 15;
|
||||
} else if (oclass->in_plugin->type == CODEC_TYPE_AUDIO) {
|
||||
gst_pad_set_chain_function (ffmpegenc->sinkpad, gst_ffmpegenc_chain_audio);
|
||||
|
||||
ffmpegenc->bitrate = 128000;
|
||||
}
|
||||
}
|
||||
|
@ -309,25 +315,30 @@ gst_ffmpegenc_connect (GstPad *pad,
|
|||
/* no edges */
|
||||
ffmpegenc->context->flags |= CODEC_FLAG_EMU_EDGE;
|
||||
|
||||
/* FIXME: we actually need to request the framerate
|
||||
* from the previous element - we currently use the
|
||||
* default (25.0), which is just plain wrong */
|
||||
ffmpegenc->context->frame_rate = 25 * DEFAULT_FRAME_RATE_BASE;
|
||||
ffmpegenc->context->frame_rate_base = DEFAULT_FRAME_RATE_BASE;
|
||||
|
||||
for (ret_caps = caps; ret_caps != NULL; ret_caps = ret_caps->next) {
|
||||
enum PixelFormat pix_fmt;
|
||||
|
||||
/* fetch pix_fmt and so on */
|
||||
gst_ffmpeg_caps_to_codectype (oclass->in_plugin->type,
|
||||
caps, ffmpegenc->context);
|
||||
|
||||
pix_fmt = ffmpegenc->context->pix_fmt;
|
||||
|
||||
/* open codec */
|
||||
if (avcodec_open (ffmpegenc->context, oclass->in_plugin) < 0) {
|
||||
GST_DEBUG (
|
||||
"ffenc_%s: Failed to open FFMPEG codec",
|
||||
GST_DEBUG ("ffenc_%s: Failed to open FFMPEG codec",
|
||||
oclass->in_plugin->name);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* is the colourspace correct? */
|
||||
if (pix_fmt != ffmpegenc->context->pix_fmt) {
|
||||
avcodec_close (ffmpegenc->context);
|
||||
GST_DEBUG ("ffenc_%s: AV wants different colourspace (%d given, %d wanted)",
|
||||
oclass->in_plugin->name, pix_fmt, ffmpegenc->context->pix_fmt);
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -340,15 +351,13 @@ gst_ffmpegenc_connect (GstPad *pad,
|
|||
ffmpegenc->context);
|
||||
if (!ret_caps) {
|
||||
avcodec_close (ffmpegenc->context);
|
||||
GST_DEBUG (
|
||||
"Unsupported codec - no caps found");
|
||||
GST_DEBUG ("Unsupported codec - no caps found");
|
||||
return GST_PAD_LINK_REFUSED;
|
||||
}
|
||||
|
||||
if ((ret = gst_pad_try_set_caps (ffmpegenc->srcpad, ret_caps)) <= 0) {
|
||||
avcodec_close (ffmpegenc->context);
|
||||
GST_DEBUG (
|
||||
"Failed to set caps on next element for ffmpeg encoder (%s)",
|
||||
GST_DEBUG ("Failed to set caps on next element for ffmpeg encoder (%s)",
|
||||
oclass->in_plugin->name);
|
||||
return ret;
|
||||
}
|
||||
|
@ -360,50 +369,28 @@ gst_ffmpegenc_connect (GstPad *pad,
|
|||
}
|
||||
|
||||
static void
|
||||
gst_ffmpegenc_chain (GstPad *pad,
|
||||
gst_ffmpegenc_chain_video (GstPad *pad,
|
||||
GstData *_data)
|
||||
{
|
||||
GstBuffer *inbuf = GST_BUFFER (_data);
|
||||
GstBuffer *outbuf = NULL;
|
||||
GstFFMpegEnc *ffmpegenc = (GstFFMpegEnc *)(gst_pad_get_parent (pad));
|
||||
GstFFMpegEncClass *oclass = (GstFFMpegEncClass*)(G_OBJECT_GET_CLASS(ffmpegenc));
|
||||
gpointer data;
|
||||
gint size, ret_size = 0;
|
||||
|
||||
data = GST_BUFFER_DATA (inbuf);
|
||||
size = GST_BUFFER_SIZE (inbuf);
|
||||
gint ret_size = 0;
|
||||
|
||||
/* FIXME: events (discont (flush!) and eos (close down) etc.) */
|
||||
|
||||
switch (oclass->in_plugin->type) {
|
||||
case CODEC_TYPE_VIDEO:
|
||||
outbuf = gst_buffer_new_and_alloc (ffmpegenc->buffer_size);
|
||||
avpicture_fill ((AVPicture *) ffmpegenc->picture,
|
||||
GST_BUFFER_DATA (inbuf),
|
||||
ffmpegenc->context->pix_fmt,
|
||||
ffmpegenc->context->width,
|
||||
ffmpegenc->context->height);
|
||||
ffmpegenc->picture->pts = GST_BUFFER_TIMESTAMP (inbuf) / 1000;
|
||||
ret_size = avcodec_encode_video (ffmpegenc->context,
|
||||
GST_BUFFER_DATA (outbuf),
|
||||
GST_BUFFER_MAXSIZE (outbuf),
|
||||
ffmpegenc->picture);
|
||||
break;
|
||||
|
||||
case CODEC_TYPE_AUDIO:
|
||||
ffmpegenc->context->frame_size = GST_BUFFER_SIZE (inbuf) /
|
||||
(2 * ffmpegenc->context->channels);
|
||||
outbuf = gst_buffer_new_and_alloc (GST_BUFFER_SIZE (inbuf));
|
||||
ret_size = avcodec_encode_audio (ffmpegenc->context,
|
||||
GST_BUFFER_DATA (outbuf),
|
||||
GST_BUFFER_MAXSIZE (outbuf),
|
||||
(const short int *)
|
||||
GST_BUFFER_DATA (inbuf));
|
||||
break;
|
||||
|
||||
default:
|
||||
g_assert(0);
|
||||
break;
|
||||
}
|
||||
|
||||
if (ret_size < 0) {
|
||||
g_warning("ffenc_%s: failed to encode buffer",
|
||||
|
@ -412,7 +399,6 @@ gst_ffmpegenc_chain (GstPad *pad,
|
|||
return;
|
||||
}
|
||||
|
||||
/* bla */
|
||||
GST_BUFFER_SIZE (outbuf) = ret_size;
|
||||
GST_BUFFER_TIMESTAMP (outbuf) = GST_BUFFER_TIMESTAMP (inbuf);
|
||||
GST_BUFFER_DURATION (outbuf) = GST_BUFFER_DURATION (inbuf);
|
||||
|
@ -421,6 +407,93 @@ gst_ffmpegenc_chain (GstPad *pad,
|
|||
gst_buffer_unref (inbuf);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_ffmpegenc_chain_audio (GstPad *pad,
|
||||
GstData *_data)
|
||||
{
|
||||
GstBuffer *inbuf = GST_BUFFER (_data);
|
||||
GstBuffer *outbuf = NULL, *subbuf;
|
||||
GstFFMpegEnc *ffmpegenc = (GstFFMpegEnc *)(gst_pad_get_parent (pad));
|
||||
GstFFMpegEncClass *oclass = (GstFFMpegEncClass*)(G_OBJECT_GET_CLASS(ffmpegenc));
|
||||
gint size, ret_size = 0, in_size, frame_size;
|
||||
|
||||
size = GST_BUFFER_SIZE (inbuf);
|
||||
|
||||
/* FIXME: events (discont (flush!) and eos (close down) etc.) */
|
||||
|
||||
frame_size = ffmpegenc->context->frame_size * 2 *
|
||||
ffmpegenc->context->channels;
|
||||
in_size = size;
|
||||
if (ffmpegenc->cache)
|
||||
in_size += GST_BUFFER_SIZE (ffmpegenc->cache);
|
||||
|
||||
while (1) {
|
||||
/* do we have enough data for one frame? */
|
||||
if (in_size / (2 * ffmpegenc->context->channels) <
|
||||
ffmpegenc->context->frame_size) {
|
||||
if (in_size > size) {
|
||||
/* this is panic! we got a buffer, but still don't have enough
|
||||
* data. Merge them and retry in the next cycle... */
|
||||
ffmpegenc->cache = gst_buffer_merge (ffmpegenc->cache, inbuf);
|
||||
} else if (in_size == size) {
|
||||
/* exactly the same! how wonderful */
|
||||
ffmpegenc->cache = inbuf;
|
||||
} else if (in_size > 0) {
|
||||
ffmpegenc->cache = gst_buffer_create_sub (inbuf, size - in_size,
|
||||
in_size);
|
||||
GST_BUFFER_DURATION (ffmpegenc->cache) =
|
||||
GST_BUFFER_DURATION (inbuf) * GST_BUFFER_SIZE (ffmpegenc->cache) / size;
|
||||
GST_BUFFER_TIMESTAMP (ffmpegenc->cache) =
|
||||
GST_BUFFER_TIMESTAMP (inbuf) + (GST_BUFFER_DURATION (inbuf) *
|
||||
(size - in_size) / size);
|
||||
gst_buffer_unref (inbuf);
|
||||
} else {
|
||||
gst_buffer_unref (inbuf);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/* create the frame */
|
||||
if (in_size > size) {
|
||||
/* merge */
|
||||
subbuf = gst_buffer_create_sub (inbuf, 0, frame_size - (in_size - size));
|
||||
GST_BUFFER_DURATION (subbuf) =
|
||||
GST_BUFFER_DURATION (inbuf) * GST_BUFFER_SIZE (subbuf) / size;
|
||||
subbuf = gst_buffer_merge (ffmpegenc->cache, subbuf);
|
||||
ffmpegenc->cache = NULL;
|
||||
} else {
|
||||
subbuf = gst_buffer_create_sub (inbuf, size - in_size, frame_size);
|
||||
GST_BUFFER_DURATION (subbuf) =
|
||||
GST_BUFFER_DURATION (inbuf) * GST_BUFFER_SIZE (subbuf) / size;
|
||||
GST_BUFFER_TIMESTAMP (subbuf) =
|
||||
GST_BUFFER_TIMESTAMP (inbuf) + (GST_BUFFER_DURATION (inbuf) *
|
||||
(size - in_size) / size);
|
||||
}
|
||||
|
||||
outbuf = gst_buffer_new_and_alloc (GST_BUFFER_SIZE (inbuf));
|
||||
ret_size = avcodec_encode_audio (ffmpegenc->context,
|
||||
GST_BUFFER_DATA (outbuf),
|
||||
GST_BUFFER_MAXSIZE (outbuf),
|
||||
(const short int *)
|
||||
GST_BUFFER_DATA (subbuf));
|
||||
|
||||
if (ret_size < 0) {
|
||||
g_warning("ffenc_%s: failed to encode buffer",
|
||||
oclass->in_plugin->name);
|
||||
gst_buffer_unref (inbuf);
|
||||
return;
|
||||
}
|
||||
|
||||
GST_BUFFER_SIZE (outbuf) = ret_size;
|
||||
GST_BUFFER_TIMESTAMP (outbuf) = GST_BUFFER_TIMESTAMP (subbuf);
|
||||
GST_BUFFER_DURATION (outbuf) = GST_BUFFER_DURATION (subbuf);
|
||||
gst_pad_push (ffmpegenc->srcpad, GST_DATA (outbuf));
|
||||
|
||||
in_size -= frame_size;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gst_ffmpegenc_set_property (GObject *object,
|
||||
guint prop_id,
|
||||
|
@ -495,6 +568,10 @@ gst_ffmpegenc_change_state (GstElement *element)
|
|||
avcodec_close (ffmpegenc->context);
|
||||
ffmpegenc->opened = FALSE;
|
||||
}
|
||||
if (ffmpegenc->cache) {
|
||||
gst_buffer_unref (ffmpegenc->cache);
|
||||
ffmpegenc->cache = NULL;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue