summaryrefslogtreecommitdiff
path: root/ext/ffmpeg
diff options
context:
space:
mode:
Diffstat (limited to 'ext/ffmpeg')
-rw-r--r--ext/ffmpeg/gstffmpegenc.c236
-rw-r--r--ext/ffmpeg/gstffmpegenc.h7
2 files changed, 163 insertions, 80 deletions
diff --git a/ext/ffmpeg/gstffmpegenc.c b/ext/ffmpeg/gstffmpegenc.c
index b46d806..a92ad87 100644
--- a/ext/ffmpeg/gstffmpegenc.c
+++ b/ext/ffmpeg/gstffmpegenc.c
@@ -255,6 +255,8 @@ gst_ffmpegenc_init (GstFFMpegEnc * ffmpegenc)
gst_element_add_pad (GST_ELEMENT (ffmpegenc), ffmpegenc->sinkpad);
gst_element_add_pad (GST_ELEMENT (ffmpegenc), ffmpegenc->srcpad);
+
+ ffmpegenc->adapter = gst_adapter_new ();
}
static void
@@ -277,6 +279,8 @@ gst_ffmpegenc_finalize (GObject * object)
g_queue_free (ffmpegenc->delay);
g_free (ffmpegenc->filename);
+ g_object_unref (ffmpegenc->adapter);
+
G_OBJECT_CLASS (parent_class)->finalize (object);
}
@@ -698,101 +702,177 @@ gst_ffmpegenc_chain_video (GstPad * pad, GstBuffer * inbuf)
}
static GstFlowReturn
+gst_ffmpegenc_encode_audio (GstFFMpegEnc * ffmpegenc, GstBuffer * inbuf,
+ gint max_size, gboolean discont)
+{
+ GstBuffer *outbuf;
+ AVCodecContext *ctx;
+ guint8 *audio_out, *audio_in;
+ GstClockTime timestamp, duration;
+ gint res;
+ GstFlowReturn ret;
+
+ ctx = ffmpegenc->context;
+
+ /* caller should set timestamps on inbuf */
+ timestamp = GST_BUFFER_TIMESTAMP (inbuf);
+ duration = GST_BUFFER_DURATION (inbuf);
+ audio_in = GST_BUFFER_DATA (inbuf);
+
+ outbuf = gst_buffer_new_and_alloc (max_size);
+ audio_out = GST_BUFFER_DATA (outbuf);
+
+ GST_LOG_OBJECT (ffmpegenc, "encoding buffer of max size %d", max_size);
+
+ res = avcodec_encode_audio (ctx, audio_out, max_size, (short *) audio_in);
+ gst_buffer_unref (inbuf);
+
+ if (res < 0) {
+ GST_ERROR_OBJECT (ffmpegenc, "Failed to encode buffer: %d", res);
+ gst_buffer_unref (outbuf);
+ return GST_FLOW_OK;
+ }
+ GST_LOG_OBJECT (ffmpegenc, "got output size %d", res);
+
+ GST_BUFFER_SIZE (outbuf) = res;
+ GST_BUFFER_TIMESTAMP (outbuf) = timestamp;
+ GST_BUFFER_DURATION (outbuf) = duration;
+ if (discont)
+ GST_BUFFER_FLAG_SET (outbuf, GST_BUFFER_FLAG_DISCONT);
+ gst_buffer_set_caps (outbuf, GST_PAD_CAPS (ffmpegenc->srcpad));
+
+ GST_LOG_OBJECT (ffmpegenc, "pushing size %d, timestamp %" GST_TIME_FORMAT,
+ res, GST_TIME_ARGS (timestamp));
+
+ ret = gst_pad_push (ffmpegenc->srcpad, outbuf);
+
+ return ret;
+}
+
+static GstFlowReturn
gst_ffmpegenc_chain_audio (GstPad * pad, GstBuffer * inbuf)
{
- GstBuffer *outbuf = NULL, *subbuf;
- GstFFMpegEnc *ffmpegenc = (GstFFMpegEnc *) (GST_OBJECT_PARENT (pad));
- gint size, ret_size = 0, in_size, frame_size;
+ GstFFMpegEnc *ffmpegenc;
+ GstFFMpegEncClass *oclass;
+ AVCodecContext *ctx;
+ GstClockTime timestamp, duration;
+ guint size, frame_size;
+ gint osize;
GstFlowReturn ret;
+ gint out_size;
+ gboolean discont;
- size = GST_BUFFER_SIZE (inbuf);
+ ffmpegenc = (GstFFMpegEnc *) (GST_OBJECT_PARENT (pad));
+ oclass = (GstFFMpegEncClass *) G_OBJECT_GET_CLASS (ffmpegenc);
- /* FIXME: events (discont (flush!) and eos (close down) etc.) */
+ ctx = ffmpegenc->context;
- frame_size = ffmpegenc->context->frame_size * 2 *
- ffmpegenc->context->channels;
- in_size = size;
- if (ffmpegenc->cache)
- in_size += GST_BUFFER_SIZE (ffmpegenc->cache);
+ size = GST_BUFFER_SIZE (inbuf);
+ timestamp = GST_BUFFER_TIMESTAMP (inbuf);
+ duration = GST_BUFFER_DURATION (inbuf);
+ discont = GST_BUFFER_IS_DISCONT (inbuf);
GST_DEBUG_OBJECT (ffmpegenc,
- "Received buffer of time %" GST_TIME_FORMAT " and size %d (cache: %d)",
- GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (inbuf)), size, in_size - size);
-
- 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_join (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 GST_FLOW_OK;
+ "Received time %" GST_TIME_FORMAT ", duration %" GST_TIME_FORMAT
+ ", size %d", GST_TIME_ARGS (timestamp), GST_TIME_ARGS (duration), size);
+
+ frame_size = ctx->frame_size;
+ osize = av_get_bits_per_sample_format (ctx->sample_fmt) / 8;
+
+ if (frame_size > 1) {
+ /* we have a frame_size, feed the encoder multiples of this frame size */
+ guint avail, frame_bytes;
+
+ if (discont) {
+ GST_LOG_OBJECT (ffmpegenc, "DISCONT, clear adapter");
+ gst_adapter_clear (ffmpegenc->adapter);
+ ffmpegenc->discont = TRUE;
}
- /* 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_join (ffmpegenc->cache, subbuf);
- ffmpegenc->cache = NULL;
+ if (gst_adapter_available (ffmpegenc->adapter) == 0) {
+ /* lock on to new timestamp */
+ GST_LOG_OBJECT (ffmpegenc, "taking buffer timestamp %" GST_TIME_FORMAT,
+ GST_TIME_ARGS (timestamp));
+ ffmpegenc->adapter_ts = timestamp;
+ ffmpegenc->adapter_consumed = 0;
} 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);
+ /* use timestamp at head of the adapter */
+ GST_LOG_OBJECT (ffmpegenc, "taking adapter timestamp %" GST_TIME_FORMAT,
+ GST_TIME_ARGS (ffmpegenc->adapter_ts));
+ timestamp = ffmpegenc->adapter_ts;
+ timestamp +=
+ gst_util_uint64_scale (ffmpegenc->adapter_consumed, GST_SECOND,
+ ctx->sample_rate);
}
- outbuf = gst_buffer_new_and_alloc (GST_BUFFER_SIZE (subbuf));
- ret_size = avcodec_encode_audio (ffmpegenc->context,
- GST_BUFFER_DATA (outbuf), GST_BUFFER_SIZE (outbuf), (const short int *)
- GST_BUFFER_DATA (subbuf));
+ GST_LOG_OBJECT (ffmpegenc, "pushing buffer in adapter");
+ gst_adapter_push (ffmpegenc->adapter, inbuf);
- if (ret_size < 0) {
- GST_ERROR_OBJECT (ffmpegenc, "Failed to encode buffer");
- gst_buffer_unref (inbuf);
- gst_buffer_unref (outbuf);
- gst_buffer_unref (subbuf);
- return GST_FLOW_OK;
- }
+ /* first see how many bytes we need to feed to the decoder. */
+ frame_bytes = frame_size * osize * ctx->channels;
+ avail = gst_adapter_available (ffmpegenc->adapter);
- GST_BUFFER_SIZE (outbuf) = ret_size;
- GST_BUFFER_TIMESTAMP (outbuf) = GST_BUFFER_TIMESTAMP (subbuf);
- GST_BUFFER_DURATION (outbuf) = GST_BUFFER_DURATION (subbuf);
- gst_buffer_set_caps (outbuf, GST_PAD_CAPS (ffmpegenc->srcpad));
- gst_buffer_unref (subbuf);
+ GST_LOG_OBJECT (ffmpegenc, "frame_bytes %u, avail %u", frame_bytes, avail);
+
+ /* while there is more than a frame size in the adapter, consume it */
+ while (avail >= frame_bytes) {
+ GST_LOG_OBJECT (ffmpegenc, "taking %u bytes from the adapter",
+ frame_bytes);
+
+ /* take an audio buffer out of the adapter */
+ inbuf = gst_adapter_take_buffer (ffmpegenc->adapter, frame_bytes);
+ ffmpegenc->adapter_consumed += frame_size;
+
+ /* calculate timestamp and duration relative to start of adapter and to
+ * the amount of samples we consumed */
+ duration =
+ gst_util_uint64_scale (ffmpegenc->adapter_consumed, GST_SECOND,
+ ctx->sample_rate);
+ duration -= (timestamp - ffmpegenc->adapter_ts);
- ret = gst_pad_push (ffmpegenc->srcpad, outbuf);
- if (ret != GST_FLOW_OK) {
- /* cleanup and return */
- gst_buffer_unref (inbuf);
- return ret;
+ GST_BUFFER_TIMESTAMP (inbuf) = timestamp;
+ GST_BUFFER_DURATION (inbuf) = duration;
+
+ /* advance the adapter timestamp with the duration */
+ timestamp += duration;
+
+ /* 4 times the input size should be big enough... */
+ out_size = frame_bytes * 4;
+
+ ret = gst_ffmpegenc_encode_audio (ffmpegenc, inbuf, out_size,
+ ffmpegenc->discont);
+ if (ret != GST_FLOW_OK)
+ goto push_failed;
+
+ ffmpegenc->discont = FALSE;
+ avail = gst_adapter_available (ffmpegenc->adapter);
}
+ GST_LOG_OBJECT (ffmpegenc, "%u bytes left in the adapter", avail);
+ } else {
+ /* we have no frame_size, feed the encoder all the data and expect a fixed
+ * output size */
+ int coded_bps = av_get_bits_per_sample (oclass->in_plugin->id) / 8;
+
+ GST_LOG_OBJECT (ffmpegenc, "coded bps %d, osize %d", coded_bps, osize);
+
+ out_size = size / osize;
+ if (coded_bps)
+ out_size *= coded_bps;
- in_size -= frame_size;
+ ret = gst_ffmpegenc_encode_audio (ffmpegenc, inbuf, out_size, discont);
+ if (ret != GST_FLOW_OK)
+ goto push_failed;
}
- return ret;
+ return GST_FLOW_OK;
+
+ /* ERRORS */
+push_failed:
+ {
+ GST_DEBUG_OBJECT (ffmpegenc, "Failed to push buffer %d (%s)", ret,
+ gst_flow_get_name (ret));
+ return ret;
+ }
}
static void
@@ -1000,10 +1080,8 @@ gst_ffmpegenc_change_state (GstElement * element, GstStateChange transition)
gst_ffmpeg_avcodec_close (ffmpegenc->context);
ffmpegenc->opened = FALSE;
}
- if (ffmpegenc->cache) {
- gst_buffer_unref (ffmpegenc->cache);
- ffmpegenc->cache = NULL;
- }
+ gst_adapter_clear (ffmpegenc->adapter);
+
if (ffmpegenc->file) {
fclose (ffmpegenc->file);
ffmpegenc->file = NULL;
diff --git a/ext/ffmpeg/gstffmpegenc.h b/ext/ffmpeg/gstffmpegenc.h
index ac9768a..ee8468c 100644
--- a/ext/ffmpeg/gstffmpegenc.h
+++ b/ext/ffmpeg/gstffmpegenc.h
@@ -26,6 +26,8 @@
G_BEGIN_DECLS
+#include <gst/base/gstadapter.h>
+
typedef struct _GstFFMpegEnc GstFFMpegEnc;
struct _GstFFMpegEnc
@@ -39,7 +41,10 @@ struct _GstFFMpegEnc
AVCodecContext *context;
AVFrame *picture;
gboolean opened;
- GstBuffer *cache;
+ GstClockTime adapter_ts;
+ guint64 adapter_consumed;
+ GstAdapter *adapter;
+ gboolean discont;
/* cache */
gulong bitrate;