Several encoding changes: for video, double check the pix_fmt after opening the AVCod...
authorRonald S. Bultje <rbultje@ronald.bitfreak.net>
Wed, 12 Nov 2003 10:42:36 +0000 (10:42 +0000)
committerRonald S. Bultje <rbultje@ronald.bitfreak.net>
Wed, 12 Nov 2003 10:42:36 +0000 (10:42 +0000)
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. :).

ext/ffmpeg/gstffmpegcodecmap.c
ext/ffmpeg/gstffmpegenc.c

index e3c88d0..c7ff98b 100644 (file)
@@ -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",
index 1d0631e..c63e461 100644 (file)
@@ -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,
-                    GstData *_data)
+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);
-      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;
-  }
+  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);
 
   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);
@@ -422,6 +408,93 @@ gst_ffmpegenc_chain (GstPad    *pad,
 }
 
 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,
                            const GValue *value,
@@ -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;
   }