gst-ducati: reorder H.264 frames ourselves
authorVincent Penquerc'h <vincent.penquerch@collabora.co.uk>
Wed, 18 Jul 2012 12:19:01 +0000 (12:19 +0000)
committerVincent Penquerc'h <vincent.penquerch@collabora.co.uk>
Wed, 18 Jul 2012 17:23:16 +0000 (17:23 +0000)
src/gstducatih264dec.c
src/gstducatih264dec.h
src/gstducatividdec.c
src/gstducatividdec.h

index 912a9d858c74c3fc9e935284ce38d8d01e98b0ba..d1abc9872509b54fd7478775c15bd9ef5a8e13b7 100644 (file)
@@ -34,6 +34,7 @@
 #  include <config.h>
 #endif
 
+#include <math.h>
 #include "gstducatih264dec.h"
 
 
@@ -67,6 +68,31 @@ static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink",
         )
     );
 
+/* *INDENT-OFF* */
+static const struct
+{
+  const char *level;
+  gint kb;
+} max_dpb_by_level[] = {
+  { "1", 149 },
+  { "1b", 149 }, /* That one's not in the spec ?? */
+  { "1.1", 338 },
+  { "1.2", 891 },
+  { "1.3", 891 },
+  { "2", 891 },
+  { "2.1", 1782 },
+  { "2.2", 3038 },
+  { "3", 3038 },
+  { "3.1", 6750 },
+  { "3.2", 7680 },
+  { "4", 12288 },
+  { "4.1", 12288 },
+  { "4.2", 12288 },
+  { "5", 41400 },
+  { "5.1", 69120 },
+};
+/* *INDENT-ON* */
+
 /* GstDucatiVidDec vmethod implementations */
 
 static void
@@ -92,7 +118,7 @@ gst_ducati_h264dec_allocate_params (GstDucatiVidDec * self, gint params_sz,
 
   if (ret) {
     IH264VDEC_Params *params = (IH264VDEC_Params *) self->params;
-    self->params->displayDelay = IVIDDEC3_DISPLAY_DELAY_AUTO;
+    self->params->displayDelay = IVIDDEC3_DECODE_ORDER;
     params->maxNumRefFrames = IH264VDEC_NUM_REFFRAMES_AUTO;
     params->pConstantMemory = 0;
     params->presetLevelIdc = IH264VDEC_LEVEL41;
@@ -153,6 +179,9 @@ gst_ducati_h264dec_query (GstDucatiVidDec * vdec, GstPad * pad,
       else
         latency = 0;
 
+      /* Take into account the backlog frames for reordering */
+      latency *= (self->backlog_maxframes + 1);
+
       if (min == GST_CLOCK_TIME_NONE)
         min = latency;
       else
@@ -189,6 +218,187 @@ gst_ducati_h264dec_can_drop_frame (GstDucatiVidDec * self, GstBuffer * buf,
   return FALSE;
 }
 
+static gint
+gst_ducati_h264dec_find_max_dpb_from_level (GstDucatiVidDec * self,
+    const char *level)
+{
+  guint n;
+
+  for (n = 0; n < G_N_ELEMENTS (max_dpb_by_level); ++n)
+    if (!strcmp (level, max_dpb_by_level[n].level))
+      return max_dpb_by_level[n].kb;
+
+  GST_WARNING_OBJECT (self, "Max DBP not found for level %s", level);
+  return -1;
+}
+
+static gint
+gst_ducati_h264dec_get_max_dpb_size (GstDucatiVidDec * self, GstCaps * caps)
+{
+  gint wmb = (self->width + 15) / 16;
+  gint hmb = (self->height + 15) / 16;
+  gint max_dpb, max_dpb_size;
+  GstStructure *structure;
+  const char *level;
+  float chroma_factor = 1.5;    /* We only support NV12, which is 4:2:0 */
+
+  /* Min( 1024 * MaxDPB / ( PicWidthInMbs * FrameHeightInMbs * 256 * ChromaFormatFactor ), 16 ) */
+
+  structure = gst_caps_get_structure (caps, 0);
+  if (!structure)
+    return 16;
+
+  level = gst_structure_get_string (structure, "level");
+  if (!level)
+    return 16;
+
+  max_dpb = gst_ducati_h264dec_find_max_dpb_from_level (self, level);
+  if (max_dpb < 0)
+    return 16;
+
+  max_dpb_size =
+      lrint (ceil (1024 * max_dpb / (wmb * hmb * 256 * chroma_factor)));
+  if (max_dpb_size > 16)
+    max_dpb_size = 16;
+
+  return max_dpb_size;
+}
+
+static gboolean
+gst_ducati_h264dec_set_sink_caps (GstDucatiVidDec * self, GstCaps * caps)
+{
+  GstDucatiH264Dec *h264dec = GST_DUCATIH264DEC (self);
+  GstStructure *structure;
+
+  GST_DEBUG_OBJECT (self, "set_sink_caps: %" GST_PTR_FORMAT, caps);
+
+  if (!GST_CALL_PARENT_WITH_DEFAULT (GST_DUCATIVIDDEC_CLASS, set_sink_caps,
+          (self, caps), TRUE))
+    return FALSE;
+
+  /* HW decoder fails in GETSTATUS */
+#if 0
+  /* When we have the first decoded buffer, we ask the decoder for the
+     max number of frames needed to reorder */
+  int err = VIDDEC3_control (self->codec, XDM_GETSTATUS,
+      self->dynParams, self->status);
+  if (!err) {
+    IH264VDEC_Status *s = (IH264VDEC_Status *) self->status;
+    if (s->spsMaxRefFrames > MAX_BACKLOG_FRAMES) {
+      h264dec->backlog_maxframes = MAX_BACKLOG_FRAMES;
+      GST_WARNING_OBJECT (self,
+          "Stream needs %d frames for reordering, we can only accomodate %d",
+          s->spsMaxRefFrames, MAX_BACKLOG_FRAMES);
+    } else {
+      h264dec->backlog_maxframes = s->spsMaxRefFrames;
+      GST_INFO_OBJECT (self, "Num frames for reordering: %d",
+          h264dec->backlog_maxframes);
+    }
+  } else {
+    h264dec->backlog_maxframes = MAX_BACKLOG_FRAMES;
+    GST_WARNING_OBJECT (self,
+        "Failed to request num frames for reordering, defaulting to %d",
+        h264dec->backlog_maxframes);
+  }
+#endif
+
+  h264dec->backlog_maxframes = -1;
+
+  structure = gst_caps_get_structure (caps, 0);
+  if (structure) {
+    gint num_ref_frames = -1;
+    if (gst_structure_get_int (structure, "num-ref-frames", &num_ref_frames)
+        && num_ref_frames >= 0) {
+      if (num_ref_frames > MAX_BACKLOG_FRAMES) {
+        h264dec->backlog_maxframes = MAX_BACKLOG_FRAMES;
+        GST_WARNING_OBJECT (self,
+            "Stream needs %d frames for reordering, we can only accomodate %d",
+            num_ref_frames, MAX_BACKLOG_FRAMES);
+      } else {
+        h264dec->backlog_maxframes = num_ref_frames;
+        GST_INFO_OBJECT (self, "Num frames for reordering: %d",
+            h264dec->backlog_maxframes);
+      }
+    }
+  }
+
+  /* If not present, use the spec forumula for a bound */
+  if (h264dec->backlog_maxframes < 0) {
+    h264dec->backlog_maxframes =
+        gst_ducati_h264dec_get_max_dpb_size (self, caps);
+    GST_WARNING_OBJECT (self,
+        "num-ref-frames not found on caps, defaulting to %d",
+        h264dec->backlog_maxframes);
+  }
+
+  return TRUE;
+}
+
+/* The following few functions reorder buffers by timestamp, on the
+   assumption that buffers are properly timestamped by presentation
+   time, but pushed in any (but presumably decode) order.
+   They also assume all incoming buffers have a valid timestamp.
+ */
+
+static GstFlowReturn
+gst_ducati_h264dec_push_earliest (GstDucatiH264Dec * self)
+{
+  GstClockTime earliest_ts = GST_CLOCK_TIME_NONE;
+  guint earliest_index = 0, i;
+  GstBuffer *buf;
+
+  if (self->backlog_nframes == 0)
+    return GST_FLOW_OK;
+
+  /* work out which frame has the earliest ts */
+  for (i = 0; i < self->backlog_nframes; i++) {
+    GstClockTime ts = GST_BUFFER_TIMESTAMP (self->backlog_frames[i]);
+    if (earliest_ts == GST_CLOCK_TIME_NONE || ts < earliest_ts) {
+      earliest_ts = ts;
+      earliest_index = i;
+    }
+  }
+
+  /* send it, giving away the ref */
+  buf = self->backlog_frames[earliest_index];
+  self->backlog_frames[earliest_index] =
+      self->backlog_frames[--self->backlog_nframes];
+  GST_DEBUG_OBJECT (self, "Actually pushing backlog buffer %" GST_PTR_FORMAT,
+      buf);
+  return gst_pad_push (GST_DUCATIVIDDEC (self)->srcpad, buf);
+}
+
+static GstFlowReturn
+gst_ducati_h264dec_push_output (GstDucatiVidDec * self, GstBuffer * buf)
+{
+  GstDucatiH264Dec *h264dec = GST_DUCATIH264DEC (self);
+  GstFlowReturn ret = GST_FLOW_OK;
+
+  /* add the frame to the list, the array will own the ref */
+  GST_DEBUG_OBJECT (self, "Adding buffer %" GST_PTR_FORMAT " to backlog", buf);
+  h264dec->backlog_frames[h264dec->backlog_nframes++] = buf;
+
+  /* push till we have no more than the max needed, or error */
+  while (h264dec->backlog_nframes > h264dec->backlog_maxframes) {
+    ret = gst_ducati_h264dec_push_earliest (h264dec);
+    if (ret != GST_FLOW_OK)
+      break;
+  }
+
+  return ret;
+}
+
+static void
+gst_ducati_h264dec_on_flush (GstDucatiVidDec * self, gboolean eos)
+{
+  GstDucatiH264Dec *h264dec = GST_DUCATIH264DEC (self);
+
+  /* push everything on the backlog, ignoring errors */
+  while (h264dec->backlog_nframes > 0) {
+    gst_ducati_h264dec_push_earliest (h264dec);
+  }
+}
+
 /* GObject vmethod implementations */
 
 static void
@@ -216,12 +426,18 @@ gst_ducati_h264dec_class_init (GstDucatiH264DecClass * klass)
   bclass->allocate_params =
       GST_DEBUG_FUNCPTR (gst_ducati_h264dec_allocate_params);
   bclass->handle_error = GST_DEBUG_FUNCPTR (gst_ducati_h264dec_handle_error);
-  bclass->can_drop_frame = GST_DEBUG_FUNCPTR (gst_ducati_h264dec_can_drop_frame);
+  bclass->can_drop_frame =
+      GST_DEBUG_FUNCPTR (gst_ducati_h264dec_can_drop_frame);
   bclass->query = GST_DEBUG_FUNCPTR (gst_ducati_h264dec_query);
+  bclass->push_output = GST_DEBUG_FUNCPTR (gst_ducati_h264dec_push_output);
+  bclass->on_flush = GST_DEBUG_FUNCPTR (gst_ducati_h264dec_on_flush);
+  bclass->set_sink_caps = GST_DEBUG_FUNCPTR (gst_ducati_h264dec_set_sink_caps);
 }
 
 static void
 gst_ducati_h264dec_init (GstDucatiH264Dec * self,
     GstDucatiH264DecClass * gclass)
 {
+  self->backlog_maxframes = 0;
+  self->backlog_nframes = 0;
 }
index a32c65d67117c93dd6cb9746ebd93dd21e654bd7..42cc12b391071b7b20104095f19191bd86c6986b 100644 (file)
@@ -36,9 +36,17 @@ G_BEGIN_DECLS
 typedef struct _GstDucatiH264Dec      GstDucatiH264Dec;
 typedef struct _GstDucatiH264DecClass GstDucatiH264DecClass;
 
+/* The H.264 spec has a hard limit of 16 */
+#define MAX_BACKLOG_FRAMES 16
+
 struct _GstDucatiH264Dec
 {
   GstDucatiVidDec parent;
+
+  /* Frames waiting to be reordered */
+  GstBuffer *backlog_frames[MAX_BACKLOG_FRAMES + 1];
+  guint backlog_maxframes;
+  guint backlog_nframes;
 };
 
 struct _GstDucatiH264DecClass 
index 46b28bf34711a6b59576b4d1e1c26a2792cdfe69..5603da61dc2bc85c42646869320e8d94689d17fd 100644 (file)
@@ -1,3 +1,4 @@
+#define USE_DTS_PTS_CODE
 /*
  * GStreamer
  * Copyright (c) 2010, Texas Instruments Incorporated
@@ -289,6 +290,11 @@ codec_unlock_outbuf (GstDucatiVidDec * self, XDAS_Int32 id)
   }
 }
 
+static void
+gst_ducati_viddec_on_flush (GstDucatiVidDec * self, gboolean eos)
+{
+}
+
 static gint
 codec_process (GstDucatiVidDec * self, gboolean send, gboolean flush,
     GstFlowReturn * flow_ret)
@@ -430,6 +436,7 @@ codec_process (GstDucatiVidDec * self, gboolean send, gboolean flush,
       GST_DEBUG_OBJECT (self, "got buffer: %d %p (%" GST_TIME_FORMAT ")",
           i, outbuf, GST_TIME_ARGS (ts));
 
+#ifdef USE_DTS_PTS_CODE
       if (self->ts_may_be_pts) {
         if ((self->last_pts != GST_CLOCK_TIME_NONE) && (self->last_pts > ts)) {
           GST_DEBUG_OBJECT (self, "detected PTS going backwards, "
@@ -437,6 +444,7 @@ codec_process (GstDucatiVidDec * self, gboolean send, gboolean flush,
           self->ts_is_pts = TRUE;
         }
       }
+#endif
 
       self->last_pts = ts;
 
@@ -466,7 +474,7 @@ codec_process (GstDucatiVidDec * self, gboolean send, gboolean flush,
       if (self->crop)
         gst_buffer_set_video_crop (outbuf, self->crop);
 
-      ret = gst_pad_push (self->srcpad, outbuf);
+      ret = klass->push_output (self, outbuf);
       if (flow_ret)
         *flow_ret = ret;
       if (ret != GST_FLOW_OK) {
@@ -496,15 +504,19 @@ gst_ducati_viddec_codec_flush (GstDucatiVidDec * self, gboolean eos)
 
   GST_DEBUG_OBJECT (self, "flush: eos=%d", eos);
 
+  GST_DUCATIVIDDEC_GET_CLASS (self)->on_flush (self, eos);
+
   /* note: flush is synchronized against _chain() to avoid calling
    * the codec from multiple threads
    */
   GST_PAD_STREAM_LOCK (self->sinkpad);
 
+#ifdef USE_DTS_PTS_CODE
   self->dts_ridx = self->dts_widx = 0;
   self->last_dts = self->last_pts = GST_CLOCK_TIME_NONE;
   self->ts_may_be_pts = TRUE;
   self->ts_is_pts = FALSE;
+#endif
   self->wait_keyframe = TRUE;
   self->in_size = 0;
   self->needs_flushing = FALSE;
@@ -673,6 +685,12 @@ gst_ducati_viddec_push_input (GstDucatiVidDec * self, GstBuffer * buf)
   return NULL;
 }
 
+static GstFlowReturn
+gst_ducati_viddec_push_output (GstDucatiVidDec * self, GstBuffer * buf)
+{
+  return gst_pad_push (self->srcpad, buf);
+}
+
 static gint
 gst_ducati_viddec_handle_error (GstDucatiVidDec * self, gint ret,
     gint extended_error, gint status_extended_error)
@@ -688,10 +706,9 @@ gst_ducati_viddec_handle_error (GstDucatiVidDec * self, gint ret,
 /* GstElement vmethod implementations */
 
 static gboolean
-gst_ducati_viddec_sink_setcaps (GstPad * pad, GstCaps * caps)
+gst_ducati_viddec_set_sink_caps (GstDucatiVidDec * self, GstCaps * caps)
 {
   gboolean ret = TRUE;
-  GstDucatiVidDec *self = GST_DUCATIVIDDEC (gst_pad_get_parent (pad));
   GstDucatiVidDecClass *klass = GST_DUCATIVIDDEC_GET_CLASS (self);
   GstStructure *s;
   GstCaps *outcaps = NULL;
@@ -699,8 +716,6 @@ gst_ducati_viddec_sink_setcaps (GstPad * pad, GstCaps * caps)
   gint par_width, par_height;
   gboolean par_present;
 
-  GST_INFO_OBJECT (self, "setcaps (sink): %" GST_PTR_FORMAT, caps);
-
   s = gst_caps_get_structure (caps, 0);
   if (!klass->parse_caps (self, s)) {
     GST_WARNING_OBJECT (self, "missing required fields");
@@ -784,6 +799,21 @@ gst_ducati_viddec_sink_setcaps (GstPad * pad, GstCaps * caps)
 out:
   if (outcaps)
     gst_caps_unref (outcaps);
+
+  return ret;
+}
+
+static gboolean
+gst_ducati_viddec_sink_setcaps (GstPad * pad, GstCaps * caps)
+{
+  gboolean ret = TRUE;
+  GstDucatiVidDec *self = GST_DUCATIVIDDEC (gst_pad_get_parent (pad));
+  GstDucatiVidDecClass *klass = GST_DUCATIVIDDEC_GET_CLASS (self);
+
+  GST_INFO_OBJECT (self, "setcaps (sink): %" GST_PTR_FORMAT, caps);
+
+  ret = klass->set_sink_caps (self, caps);
+
   gst_object_unref (self);
 
   return ret;
@@ -1024,6 +1054,7 @@ allocate_buffer:
 have_out_buf:
   buf = GST_DUCATIVIDDEC_GET_CLASS (self)->push_input (self, buf);
 
+#ifdef USE_DTS_PTS_CODE
   if (ts != GST_CLOCK_TIME_NONE) {
     self->dts_queue[self->dts_widx++ % NDTS] = ts;
     /* if next buffer has earlier ts than previous, then the ts
@@ -1035,6 +1066,7 @@ have_out_buf:
     }
     self->last_dts = ts;
   }
+#endif
 
   if (self->in_size == 0 && outbuf) {
     GST_DEBUG_OBJECT (self, "no input, skipping process");
@@ -1342,6 +1374,9 @@ gst_ducati_viddec_class_init (GstDucatiVidDecClass * klass)
   klass->handle_error = GST_DEBUG_FUNCPTR (gst_ducati_viddec_handle_error);
   klass->can_drop_frame = GST_DEBUG_FUNCPTR (gst_ducati_viddec_can_drop_frame);
   klass->query = GST_DEBUG_FUNCPTR (gst_ducati_viddec_query);
+  klass->push_output = GST_DEBUG_FUNCPTR (gst_ducati_viddec_push_output);
+  klass->on_flush = GST_DEBUG_FUNCPTR (gst_ducati_viddec_on_flush);
+  klass->set_sink_caps = GST_DEBUG_FUNCPTR (gst_ducati_viddec_set_sink_caps);
 
   g_object_class_install_property (gobject_class, PROP_VERSION,
       g_param_spec_string ("version", "Version",
@@ -1390,10 +1425,12 @@ gst_ducati_viddec_init (GstDucatiVidDec * self, GstDucatiVidDecClass * klass)
   self->interlaced = FALSE;
   self->send_crop_event = TRUE;
 
+#ifdef USE_DTS_PTS_CODE
   self->dts_ridx = self->dts_widx = 0;
   self->last_dts = self->last_pts = GST_CLOCK_TIME_NONE;
   self->ts_may_be_pts = TRUE;
   self->ts_is_pts = FALSE;
+#endif
 
   self->pageMemType = XDM_MEMTYPE_TILEDPAGE;
 
index 1aac4aaf0323f0b1e9fffd5fc6b7a58dfa470e7d..ea69e59b53db9a7465989b40dcc82817c618b80b 100644 (file)
@@ -179,6 +179,21 @@ struct _GstDucatiVidDecClass
 
   gboolean (*query) (GstDucatiVidDec * self, GstPad * pad, GstQuery * query,
       gboolean * forward);
+
+  /**
+   * Called to push a decoder buffer. Consumes reference to 'buf'.
+   */
+  GstFlowReturn (*push_output) (GstDucatiVidDec * self, GstBuffer * buf);
+
+  /**
+   * Called before a flush happens.
+   */
+  void (*on_flush) (GstDucatiVidDec * self, gboolean eos);
+
+  /**
+   * Called to set new caps on the sink pad.
+   */
+  gboolean (*set_sink_caps) (GstDucatiVidDec * self, GstCaps *caps);
 };
 
 GType gst_ducati_viddec_get_type (void);