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:
Ronald S. Bultje 2003-11-12 10:42:36 +00:00
parent d26aa5ad0b
commit 199e7f14f2
2 changed files with 130 additions and 53 deletions

View file

@ -831,8 +831,8 @@ gst_ffmpeg_caps_to_pixfmt (GstCaps *caps,
GST_PROPS_FLOAT_TYPE)) { GST_PROPS_FLOAT_TYPE)) {
gfloat fps; gfloat fps;
gst_caps_get_float (caps, "framerate", &fps); gst_caps_get_float (caps, "framerate", &fps);
context->frame_rate = fps * 1000; context->frame_rate = fps * DEFAULT_FRAME_RATE_BASE;
context->frame_rate_base = 1000; context->frame_rate_base = DEFAULT_FRAME_RATE_BASE;
} }
if (gst_caps_has_property_typed (caps, "format", if (gst_caps_has_property_typed (caps, "format",

View file

@ -44,6 +44,7 @@ struct _GstFFMpegEnc {
AVCodecContext *context; AVCodecContext *context;
AVFrame *picture; AVFrame *picture;
gboolean opened; gboolean opened;
GstBuffer *cache;
/* cache */ /* cache */
gulong bitrate; gulong bitrate;
@ -123,7 +124,8 @@ static void gst_ffmpegenc_dispose (GObject *object);
static GstPadLinkReturn static GstPadLinkReturn
gst_ffmpegenc_connect (GstPad *pad, GstCaps *caps); 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, static void gst_ffmpegenc_set_property (GObject *object,
guint prop_id, guint prop_id,
@ -238,7 +240,6 @@ gst_ffmpegenc_init(GstFFMpegEnc *ffmpegenc)
/* setup pads */ /* setup pads */
ffmpegenc->sinkpad = gst_pad_new_from_template (oclass->sinktempl, "sink"); ffmpegenc->sinkpad = gst_pad_new_from_template (oclass->sinktempl, "sink");
gst_pad_set_link_function (ffmpegenc->sinkpad, gst_ffmpegenc_connect); 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"); ffmpegenc->srcpad = gst_pad_new_from_template (oclass->srctempl, "src");
gst_element_add_pad (GST_ELEMENT (ffmpegenc), ffmpegenc->sinkpad); gst_element_add_pad (GST_ELEMENT (ffmpegenc), ffmpegenc->sinkpad);
@ -248,12 +249,17 @@ gst_ffmpegenc_init(GstFFMpegEnc *ffmpegenc)
ffmpegenc->context = avcodec_alloc_context(); ffmpegenc->context = avcodec_alloc_context();
ffmpegenc->picture = avcodec_alloc_frame(); ffmpegenc->picture = avcodec_alloc_frame();
ffmpegenc->opened = FALSE; ffmpegenc->opened = FALSE;
ffmpegenc->cache = NULL;
if (oclass->in_plugin->type == CODEC_TYPE_VIDEO) { if (oclass->in_plugin->type == CODEC_TYPE_VIDEO) {
gst_pad_set_chain_function (ffmpegenc->sinkpad, gst_ffmpegenc_chain_video);
ffmpegenc->bitrate = 300000; ffmpegenc->bitrate = 300000;
ffmpegenc->buffer_size = 512 * 1024; ffmpegenc->buffer_size = 512 * 1024;
ffmpegenc->gop_size = 15; ffmpegenc->gop_size = 15;
} else if (oclass->in_plugin->type == CODEC_TYPE_AUDIO) { } else if (oclass->in_plugin->type == CODEC_TYPE_AUDIO) {
gst_pad_set_chain_function (ffmpegenc->sinkpad, gst_ffmpegenc_chain_audio);
ffmpegenc->bitrate = 128000; ffmpegenc->bitrate = 128000;
} }
} }
@ -309,25 +315,30 @@ gst_ffmpegenc_connect (GstPad *pad,
/* no edges */ /* no edges */
ffmpegenc->context->flags |= CODEC_FLAG_EMU_EDGE; 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) { for (ret_caps = caps; ret_caps != NULL; ret_caps = ret_caps->next) {
enum PixelFormat pix_fmt;
/* fetch pix_fmt and so on */ /* fetch pix_fmt and so on */
gst_ffmpeg_caps_to_codectype (oclass->in_plugin->type, gst_ffmpeg_caps_to_codectype (oclass->in_plugin->type,
caps, ffmpegenc->context); caps, ffmpegenc->context);
pix_fmt = ffmpegenc->context->pix_fmt;
/* open codec */ /* open codec */
if (avcodec_open (ffmpegenc->context, oclass->in_plugin) < 0) { if (avcodec_open (ffmpegenc->context, oclass->in_plugin) < 0) {
GST_DEBUG ( GST_DEBUG ("ffenc_%s: Failed to open FFMPEG codec",
"ffenc_%s: Failed to open FFMPEG codec",
oclass->in_plugin->name); oclass->in_plugin->name);
continue; 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; break;
} }
@ -340,15 +351,13 @@ gst_ffmpegenc_connect (GstPad *pad,
ffmpegenc->context); ffmpegenc->context);
if (!ret_caps) { if (!ret_caps) {
avcodec_close (ffmpegenc->context); avcodec_close (ffmpegenc->context);
GST_DEBUG ( GST_DEBUG ("Unsupported codec - no caps found");
"Unsupported codec - no caps found");
return GST_PAD_LINK_REFUSED; return GST_PAD_LINK_REFUSED;
} }
if ((ret = gst_pad_try_set_caps (ffmpegenc->srcpad, ret_caps)) <= 0) { if ((ret = gst_pad_try_set_caps (ffmpegenc->srcpad, ret_caps)) <= 0) {
avcodec_close (ffmpegenc->context); avcodec_close (ffmpegenc->context);
GST_DEBUG ( GST_DEBUG ("Failed to set caps on next element for ffmpeg encoder (%s)",
"Failed to set caps on next element for ffmpeg encoder (%s)",
oclass->in_plugin->name); oclass->in_plugin->name);
return ret; return ret;
} }
@ -360,50 +369,28 @@ gst_ffmpegenc_connect (GstPad *pad,
} }
static void static void
gst_ffmpegenc_chain (GstPad *pad, gst_ffmpegenc_chain_video (GstPad *pad,
GstData *_data) GstData *_data)
{ {
GstBuffer *inbuf = GST_BUFFER (_data); GstBuffer *inbuf = GST_BUFFER (_data);
GstBuffer *outbuf = NULL; GstBuffer *outbuf = NULL;
GstFFMpegEnc *ffmpegenc = (GstFFMpegEnc *)(gst_pad_get_parent (pad)); GstFFMpegEnc *ffmpegenc = (GstFFMpegEnc *)(gst_pad_get_parent (pad));
GstFFMpegEncClass *oclass = (GstFFMpegEncClass*)(G_OBJECT_GET_CLASS(ffmpegenc)); GstFFMpegEncClass *oclass = (GstFFMpegEncClass*)(G_OBJECT_GET_CLASS(ffmpegenc));
gpointer data; gint ret_size = 0;
gint size, ret_size = 0;
data = GST_BUFFER_DATA (inbuf);
size = GST_BUFFER_SIZE (inbuf);
/* FIXME: events (discont (flush!) and eos (close down) etc.) */ /* FIXME: events (discont (flush!) and eos (close down) etc.) */
switch (oclass->in_plugin->type) { outbuf = gst_buffer_new_and_alloc (ffmpegenc->buffer_size);
case CODEC_TYPE_VIDEO: avpicture_fill ((AVPicture *) ffmpegenc->picture,
outbuf = gst_buffer_new_and_alloc (ffmpegenc->buffer_size); GST_BUFFER_DATA (inbuf),
avpicture_fill ((AVPicture *) ffmpegenc->picture, ffmpegenc->context->pix_fmt,
GST_BUFFER_DATA (inbuf), ffmpegenc->context->width,
ffmpegenc->context->pix_fmt, ffmpegenc->context->height);
ffmpegenc->context->width, ffmpegenc->picture->pts = GST_BUFFER_TIMESTAMP (inbuf) / 1000;
ffmpegenc->context->height); ret_size = avcodec_encode_video (ffmpegenc->context,
ret_size = avcodec_encode_video (ffmpegenc->context, GST_BUFFER_DATA (outbuf),
GST_BUFFER_DATA (outbuf), GST_BUFFER_MAXSIZE (outbuf),
GST_BUFFER_MAXSIZE (outbuf), ffmpegenc->picture);
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) { if (ret_size < 0) {
g_warning("ffenc_%s: failed to encode buffer", g_warning("ffenc_%s: failed to encode buffer",
@ -412,7 +399,6 @@ gst_ffmpegenc_chain (GstPad *pad,
return; return;
} }
/* bla */
GST_BUFFER_SIZE (outbuf) = ret_size; GST_BUFFER_SIZE (outbuf) = ret_size;
GST_BUFFER_TIMESTAMP (outbuf) = GST_BUFFER_TIMESTAMP (inbuf); GST_BUFFER_TIMESTAMP (outbuf) = GST_BUFFER_TIMESTAMP (inbuf);
GST_BUFFER_DURATION (outbuf) = GST_BUFFER_DURATION (inbuf); GST_BUFFER_DURATION (outbuf) = GST_BUFFER_DURATION (inbuf);
@ -421,6 +407,93 @@ gst_ffmpegenc_chain (GstPad *pad,
gst_buffer_unref (inbuf); 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 static void
gst_ffmpegenc_set_property (GObject *object, gst_ffmpegenc_set_property (GObject *object,
guint prop_id, guint prop_id,
@ -495,6 +568,10 @@ gst_ffmpegenc_change_state (GstElement *element)
avcodec_close (ffmpegenc->context); avcodec_close (ffmpegenc->context);
ffmpegenc->opened = FALSE; ffmpegenc->opened = FALSE;
} }
if (ffmpegenc->cache) {
gst_buffer_unref (ffmpegenc->cache);
ffmpegenc->cache = NULL;
}
break; break;
} }