]> git.ipfire.org Git - thirdparty/tvheadend.git/commitdiff
transcoder: implement a video filter chain for deint+scale
authorManuel Lauss <manuel.lauss@gmail.com>
Tue, 3 Nov 2015 13:43:27 +0000 (14:43 +0100)
committerJaroslav Kysela <perex@perex.cz>
Tue, 3 Nov 2015 14:07:29 +0000 (15:07 +0100)
This patch reimplements deinterlacing and scaling with a simple libav
filter chain, to make the transcoder compatible with newer ffmpeg/libav
codebase.

Upstream ffmpeg has removed the long deprecated deinterlacer module
used by this code, which made the reimplementation using a filter
chain necessary.

Signed-off-by: Manuel Lauss <manuel.lauss@gmail.com>
Makefile
Makefile.ffmpeg
configure
src/libav.c
src/libav.h
src/plumbing/transcoding.c

index 88cb9eb3009c959e2a90785a2767972ee4935799..bbfa0e53e124ddfe3b921562728e440b2f0df1cc 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -64,7 +64,7 @@ CFLAGS  += -I${ROOTDIR}/libav_static/build/ffmpeg/include
 LDFLAGS_FFDIR = ${ROOTDIR}/libav_static/build/ffmpeg/lib
 LDFLAGS += ${LDFLAGS_FFDIR}/libavresample.a
 LDFLAGS += ${LDFLAGS_FFDIR}/libswresample.a
-LDFLAGS += ${LDFLAGS_FFDIR}/libswscale.a
+LDFLAGS += ${LDFLAGS_FFDIR}/libavfilter.a
 LDFLAGS += ${LDFLAGS_FFDIR}/libavutil.a
 LDFLAGS += ${LDFLAGS_FFDIR}/libavformat.a
 LDFLAGS += ${LDFLAGS_FFDIR}/libavcodec.a
@@ -101,6 +101,7 @@ LDFLAGS += -lmfx
 endif
 LDFLAGS += ${CONFIG_LIBMFX_VA_LIBS}
 endif
+LDFLAGS += -lavfilter
 endif
 
 ifeq ($(CONFIG_HDHOMERUN_STATIC),yes)
index 8a571f5bb29f93915c953f1a2e9f63d2f83dd993..33158952d98a76edb84bdb9aa2d466f8448b9e36 100644 (file)
@@ -62,7 +62,7 @@ FFMPEG_URL      = http://ffmpeg.org/releases/$(FFMPEG_TB)
 FFMPEG_SHA1     = 95046cd9251b69c61b11ebcd1e163ac14d0fc2c6
 
 EXTLIBS         = libx264 libvorbis libvpx
-COMPONENTS      = avutil avformat avcodec swresample swscale avresample
+COMPONENTS      = avutil avformat avcodec swresample avfilter avresample
 PROTOCOLS       = file
 DECODERS        = mpeg2video mp2 ac3 eac3 h264 h264_vdpau hevc aac aac_latm vorbis libvorbis
 ENCODERS        = mpeg2video mp2 libx264 libvpx_vp8 libvpx_vp9 aac libaacplus vorbis libvorbis
index fbff583573eaaf2e36d09be094a165c656ef8324..4bea36ac32cdc6f3436d4c24e6039ae46fe7a6c3 100755 (executable)
--- a/configure
+++ b/configure
@@ -467,7 +467,7 @@ else
         has_libav=false
       fi
 
-      if $has_libav && ! check_pkg libswscale ">=2.3.100"; then
+      if $has_libav && ! check_pkg libavfilter ">=4.0.0"; then
         has_libav=false
       fi
 
@@ -493,7 +493,7 @@ else
         has_libav=false
       fi
 
-      if $has_libav && ! check_pkg libswscale ">=2.1.2"; then
+      if $has_libav && ! check_pkg libavfilter ">=4.0.0"; then
         has_libav=false
       fi
 
index 7ed4a5d8614c161dec4e1dc1af51b2b6c5b70577..077cae3619cfd0ba31503689ff162620c6e8c212 100644 (file)
@@ -214,5 +214,6 @@ libav_init(void)
   av_log_set_callback(libav_log_callback);
   libav_set_loglevel();
   av_register_all();
+  avfilter_register_all();
   transcoding_init();
 }
index 9e2511e3e76da3213494a3a48963b11e13f5d950..b46ee0baf7cbb4659e8701511c49d750ea8890ae 100644 (file)
@@ -24,6 +24,7 @@
 #if ENABLE_LIBAV
 
 #include <libavformat/avformat.h>
+#include <libavfilter/avfilter.h>
 #include "tvheadend.h"
 
 /*
index d6c78386718d5d0712ca8158f06360f72bb29346..6decde3eac2f1486dec9c5a3f07abe4f39bfbe8d 100644 (file)
 #include <unistd.h>
 #include <libavformat/avformat.h>
 #include <libavcodec/avcodec.h>
-#include <libswscale/swscale.h>
+#include <libavfilter/avfiltergraph.h>
+#include <libavfilter/buffersink.h>
+#include <libavfilter/buffersrc.h>
+#include <libavutil/opt.h>
 #include <libavresample/avresample.h>
 #include <libavutil/opt.h>
 #include <libavutil/audio_fifo.h>
@@ -92,9 +95,12 @@ typedef struct video_stream {
   AVCodec                   *vid_ocodec;
 
   AVFrame                   *vid_dec_frame;
-  struct SwsContext         *vid_scaler;
   AVFrame                   *vid_enc_frame;
 
+  AVFilterGraph             *flt_graph;
+  AVFilterContext           *flt_bufsrcctx;
+  AVFilterContext           *flt_bufsinkctx;
+
   int16_t                    vid_width;
   int16_t                    vid_height;
 
@@ -1022,6 +1028,114 @@ send_video_packet(transcoder_t *t, transcoder_stream_t *ts, th_pkt_t *pkt,
 
 }
 
+/* create a simple deinterlacer-scaler video filter chain */
+static int
+create_video_filter(video_stream_t *vs, transcoder_t *t,
+                    AVCodecContext *ictx, AVCodecContext *octx)
+{
+  AVFilterInOut *flt_inputs, *flt_outputs;
+  AVFilter *flt_bufsrc, *flt_bufsink;
+  char opt[128];
+  int err;
+
+  err = 1;
+  flt_inputs = flt_outputs = NULL;
+  flt_bufsrc = flt_bufsink = NULL;
+
+  if (vs->flt_graph)
+    avfilter_graph_free(&vs->flt_graph);
+
+  vs->flt_graph = avfilter_graph_alloc();
+  if (!vs->flt_graph)
+    return err;
+
+  flt_inputs = avfilter_inout_alloc();
+  if (!flt_inputs)
+    goto out_err;
+
+  flt_outputs = avfilter_inout_alloc();
+  if (!flt_outputs)
+    goto out_err;
+
+  flt_bufsrc = avfilter_get_by_name("buffer");
+  flt_bufsink = avfilter_get_by_name("buffersink");
+  if (!flt_bufsrc || !flt_bufsink) {
+    tvherror("transcode", "%04X: libav default buffers unknown", shortid(t));
+    goto out_err;
+  }
+
+  memset(opt, 0, sizeof(opt));
+  snprintf(opt, sizeof(opt), "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",
+           ictx->width,
+           ictx->height,
+           ictx->pix_fmt,
+           ictx->time_base.num,
+           ictx->time_base.den,
+           ictx->sample_aspect_ratio.num,
+           ictx->sample_aspect_ratio.den);
+
+  err = avfilter_graph_create_filter(&vs->flt_bufsrcctx, flt_bufsrc, "in",
+                                     opt, NULL, vs->flt_graph);
+  if (err < 0) {
+    tvherror("transcode", "%04X: fltchain IN init error", shortid(t));
+    goto out_err;
+  }
+
+  err = avfilter_graph_create_filter(&vs->flt_bufsinkctx, flt_bufsink,
+                                     "out", NULL, NULL, vs->flt_graph);
+  if (err < 0) {
+    tvherror("transcode", "%04X: fltchain OUT init error", shortid(t));
+    goto out_err;
+  }
+
+  flt_outputs->name = av_strdup("in");
+  flt_outputs->filter_ctx = vs->flt_bufsrcctx;
+  flt_outputs->pad_idx = 0;
+  flt_outputs->next = NULL;
+  flt_inputs->name = av_strdup("out");
+  flt_inputs->filter_ctx = vs->flt_bufsinkctx;
+  flt_inputs->pad_idx = 0;
+  flt_inputs->next = NULL;
+
+  /* add filters: yadif to deinterlace and a scaler */
+  memset(opt, 0, sizeof(opt));
+  snprintf(opt, sizeof(opt), "yadif,scale=%dx%d",
+           octx->width,
+           octx->height);
+  err = avfilter_graph_parse_ptr(vs->flt_graph,
+                                 opt,
+                                 &flt_inputs,
+                                 &flt_outputs,
+                                 NULL);
+  if (err < 0) {
+    tvherror("transcode", "%04X: failed to init filter chain", shortid(t));
+    goto out_err;
+  }
+
+  err = avfilter_graph_config(vs->flt_graph, NULL);
+  if (err < 0) {
+    tvherror("transcode", "%04X: failed to config filter chain", shortid(t));
+    goto out_err;
+  }
+
+  avfilter_inout_free(&flt_inputs);
+  avfilter_inout_free(&flt_outputs);
+
+  return 0;  /* all OK */
+
+out_err:
+  if (flt_inputs)
+    avfilter_inout_free(&flt_inputs);
+  if (flt_outputs)
+    avfilter_inout_free(&flt_outputs);
+  if (vs->flt_graph) {
+    avfilter_graph_free(&vs->flt_graph);
+    vs->flt_graph = NULL;
+  }
+
+  return err;
+}
+
 /**
  *
  */
@@ -1032,9 +1146,7 @@ transcoder_stream_video(transcoder_t *t, transcoder_stream_t *ts, th_pkt_t *pkt)
   AVCodecContext *ictx, *octx;
   AVDictionary *opts;
   AVPacket packet, packet2;
-  AVPicture deint_pic;
-  uint8_t *buf, *deint;
-  int length, len, ret, got_picture, got_output, got_ref;
+  int length, ret, got_picture, got_output, got_ref;
   video_stream_t *vs = (video_stream_t*)ts;
   streaming_message_t *sm;
   th_pkt_t *pkt2;
@@ -1051,7 +1163,6 @@ transcoder_stream_video(transcoder_t *t, transcoder_stream_t *ts, th_pkt_t *pkt)
   icodec = vs->vid_icodec;
   ocodec = vs->vid_ocodec;
 
-  buf = deint = NULL;
   opts = NULL;
 
   got_ref = 0;
@@ -1129,7 +1240,7 @@ transcoder_stream_video(transcoder_t *t, transcoder_stream_t *ts, th_pkt_t *pkt)
 
     switch (ts->ts_type) {
     case SCT_MPEG2VIDEO:
-      octx->pix_fmt        = PIX_FMT_YUV420P;
+      octx->pix_fmt        = AV_PIX_FMT_YUV420P;
       octx->flags         |= CODEC_FLAG_GLOBAL_HEADER;
 
       if (t->t_props.tp_vbitrate < 64) {
@@ -1152,7 +1263,7 @@ transcoder_stream_video(transcoder_t *t, transcoder_stream_t *ts, th_pkt_t *pkt)
       break;
 
     case SCT_VP8:
-      octx->pix_fmt        = PIX_FMT_YUV420P;
+      octx->pix_fmt        = AV_PIX_FMT_YUV420P;
 
       // setting quality to realtime will use as much CPU for transcoding as possible,
       // while still encoding in realtime
@@ -1176,7 +1287,7 @@ transcoder_stream_video(transcoder_t *t, transcoder_stream_t *ts, th_pkt_t *pkt)
       break;
 
     case SCT_H264:
-      octx->pix_fmt        = PIX_FMT_YUV420P;
+      octx->pix_fmt        = AV_PIX_FMT_YUV420P;
       octx->flags         |= CODEC_FLAG_GLOBAL_HEADER;
 
       // Default = "medium". We gain more encoding speed compared to the loss of quality when lowering it _slightly_.
@@ -1205,7 +1316,7 @@ transcoder_stream_video(transcoder_t *t, transcoder_stream_t *ts, th_pkt_t *pkt)
       break;
 
     case SCT_HEVC:
-      octx->pix_fmt        = PIX_FMT_YUV420P;
+      octx->pix_fmt        = AV_PIX_FMT_YUV420P;
       octx->flags         |= CODEC_FLAG_GLOBAL_HEADER;
 
       // on all hardware ultrafast (or maybe superfast) should be safe
@@ -1256,79 +1367,53 @@ transcoder_stream_video(transcoder_t *t, transcoder_stream_t *ts, th_pkt_t *pkt)
       transcoder_stream_invalidate(ts);
       goto cleanup;
     }
-  }
 
-  len = avpicture_get_size(ictx->pix_fmt, ictx->width, ictx->height);
-  deint = av_malloc(len);
-
-  avpicture_fill(&deint_pic,
-                deint,
-                ictx->pix_fmt,
-                ictx->width,
-                ictx->height);
-
-  if (avpicture_deinterlace(&deint_pic,
-                           (AVPicture *)vs->vid_dec_frame,
-                           ictx->pix_fmt,
-                           ictx->width,
-                           ictx->height) < 0) {
-    tvherror("transcode", "%04X: Cannot deinterlace frame", shortid(t));
-    transcoder_stream_invalidate(ts);
-    goto cleanup;
+    if (create_video_filter(vs, t, ictx, octx)) {
+      tvherror("transcode", "%04X: Video filter creation failed",
+               shortid(t));
+      transcoder_stream_invalidate(ts);
+      goto cleanup;
+    }
   }
 
-  len = avpicture_get_size(octx->pix_fmt, octx->width, octx->height);
-  buf = av_malloc(len + FF_INPUT_BUFFER_PADDING_SIZE);
-  memset(buf, 0, len);
-
-  avpicture_fill((AVPicture *)vs->vid_enc_frame,
-                 buf,
-                 octx->pix_fmt,
-                 octx->width,
-                 octx->height);
-
-  vs->vid_scaler = sws_getCachedContext(vs->vid_scaler,
-                                   ictx->width,
-                                   ictx->height,
-                                   ictx->pix_fmt,
-                                   octx->width,
-                                   octx->height,
-                                   octx->pix_fmt,
-                                   1,
-                                   NULL,
-                                   NULL,
-                                   NULL);
-
-  if (sws_scale(vs->vid_scaler,
-               (const uint8_t * const*)deint_pic.data,
-               deint_pic.linesize,
-               0,
-               ictx->height,
-               vs->vid_enc_frame->data,
-               vs->vid_enc_frame->linesize) < 0) {
-    tvherror("transcode", "%04X: Cannot scale frame", shortid(t));
+  /* push decoded frame into filter chain */
+  if (av_buffersrc_add_frame(vs->flt_bufsrcctx, vs->vid_dec_frame) < 0) {
+    tvherror("transcode", "%04X: filter input error", shortid(t));
     transcoder_stream_invalidate(ts);
     goto cleanup;
   }
 
-  vs->vid_enc_frame->format  = octx->pix_fmt;
-  vs->vid_enc_frame->width   = octx->width;
-  vs->vid_enc_frame->height  = octx->height;
-
-  vs->vid_enc_frame->pkt_pts = vs->vid_dec_frame->pkt_pts;
-  vs->vid_enc_frame->pkt_dts = vs->vid_dec_frame->pkt_dts;
-
-  if (vs->vid_dec_frame->reordered_opaque != AV_NOPTS_VALUE)
-    vs->vid_enc_frame->pts = vs->vid_dec_frame->reordered_opaque;
-
-  else if (ictx->coded_frame && ictx->coded_frame->pts != AV_NOPTS_VALUE)
-    vs->vid_enc_frame->pts = vs->vid_dec_frame->pts;
-
-  ret = avcodec_encode_video2(octx, &packet2, vs->vid_enc_frame, &got_output);
-  if (ret < 0) {
-    tvherror("transcode", "%04X: Error encoding frame", shortid(t));
-    transcoder_stream_invalidate(ts);
-    goto cleanup;
+  /* and pull out a filtered frame */
+  while (1) {
+       ret = av_buffersink_get_frame(vs->flt_bufsinkctx, vs->vid_enc_frame);
+       if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
+               break;
+       if (ret < 0) {
+               tvherror("transcode", "%04X: filter output error", shortid(t));
+               transcoder_stream_invalidate(ts);
+               goto cleanup;
+       }
+
+       vs->vid_enc_frame->format  = octx->pix_fmt;
+       vs->vid_enc_frame->width   = octx->width;
+       vs->vid_enc_frame->height  = octx->height;
+
+       vs->vid_enc_frame->pkt_pts = vs->vid_dec_frame->pkt_pts;
+       vs->vid_enc_frame->pkt_dts = vs->vid_dec_frame->pkt_dts;
+
+       if (vs->vid_dec_frame->reordered_opaque != AV_NOPTS_VALUE)
+               vs->vid_enc_frame->pts = vs->vid_dec_frame->reordered_opaque;
+
+       else if (ictx->coded_frame && ictx->coded_frame->pts != AV_NOPTS_VALUE)
+               vs->vid_enc_frame->pts = vs->vid_dec_frame->pts;
+
+       ret = avcodec_encode_video2(octx, &packet2, vs->vid_enc_frame, &got_output);
+       if (ret < 0) {
+               tvherror("transcode", "%04X: Error encoding frame", shortid(t));
+               transcoder_stream_invalidate(ts);
+               goto cleanup;
+       }
+       av_frame_unref(vs->vid_enc_frame);
   }
 
   if (got_output)
@@ -1342,12 +1427,6 @@ transcoder_stream_video(transcoder_t *t, transcoder_stream_t *ts, th_pkt_t *pkt)
 
   av_free_packet(&packet);
 
-  if(buf)
-    av_free(buf);
-
-  if(deint)
-    av_free(deint);
-
   if(opts)
     av_dict_free(&opts);
 
@@ -1624,15 +1703,17 @@ transcoder_destroy_video(transcoder_t *t, transcoder_stream_t *ts)
   if(vs->vid_dec_frame)
     av_free(vs->vid_dec_frame);
 
-  if(vs->vid_scaler)
-    sws_freeContext(vs->vid_scaler);
-
   if(vs->vid_enc_frame)
     av_free(vs->vid_enc_frame);
 
   if (vs->vid_first_pkt)
     pkt_ref_dec(vs->vid_first_pkt);
 
+  if (vs->flt_graph) {
+    avfilter_graph_free(&vs->flt_graph);
+    vs->flt_graph = NULL;
+  }
+
   free(ts);
 }
 
@@ -1679,11 +1760,13 @@ transcoder_init_video(transcoder_t *t, streaming_start_component_t *ssc)
   if (t->t_props.tp_nrprocessors)
     vs->vid_octx->thread_count = t->t_props.tp_nrprocessors;
 
-  vs->vid_dec_frame = avcodec_alloc_frame();
-  vs->vid_enc_frame = avcodec_alloc_frame();
+  vs->vid_dec_frame = av_frame_alloc();
+  vs->vid_enc_frame = av_frame_alloc();
+
+  av_frame_unref(vs->vid_dec_frame);
+  av_frame_unref(vs->vid_enc_frame);
 
-  avcodec_get_frame_defaults(vs->vid_dec_frame);
-  avcodec_get_frame_defaults(vs->vid_enc_frame);
+  vs->flt_graph = NULL;                /* allocated in packet processor */
 
   LIST_INSERT_HEAD(&t->t_stream_list, (transcoder_stream_t*)vs, ts_link);