/* * GStreamer * Copyright (c) 2010, Texas Instruments Incorporated * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifdef HAVE_CONFIG_H # include #endif #include "gstducatividdec.h" GST_BOILERPLATE (GstDucatiVidDec, gst_ducati_viddec, GstElement, GST_TYPE_ELEMENT); static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS, GST_STATIC_CAPS (GST_VIDEO_CAPS_YUV_STRIDED ("NV12", "[ 0, max ]")) ); enum { PROP_0, PROP_VERSION, }; /* helper functions */ static void engine_close (GstDucatiVidDec * self) { if (self->engine) { Engine_close (self->engine); self->engine = NULL; } if (self->params) { dce_free (self->params); self->params = NULL; } if (self->dynParams) { dce_free (self->dynParams); self->dynParams = NULL; } if (self->status) { dce_free (self->status); self->status = NULL; } if (self->inBufs) { dce_free (self->inBufs); self->inBufs = NULL; } if (self->outBufs) { dce_free (self->outBufs); self->outBufs = NULL; } if (self->inArgs) { dce_free (self->inArgs); self->inArgs = NULL; } if (self->outArgs) { dce_free (self->outArgs); self->outArgs = NULL; } } static gboolean engine_open (GstDucatiVidDec * self) { gboolean ret; if (G_UNLIKELY (self->engine)) { return TRUE; } GST_DEBUG_OBJECT (self, "opening engine"); self->engine = Engine_open ((String) "ivahd_vidsvr", NULL, NULL); if (G_UNLIKELY (!self->engine)) { GST_ERROR_OBJECT (self, "could not create engine"); return FALSE; } ret = GST_DUCATIVIDDEC_GET_CLASS (self)->allocate_params (self, sizeof (IVIDDEC3_Params), sizeof (IVIDDEC3_DynamicParams), sizeof (IVIDDEC3_Status), sizeof (IVIDDEC3_InArgs), sizeof (IVIDDEC3_OutArgs)); return ret; } static void codec_delete (GstDucatiVidDec * self) { if (self->pool) { gst_ducati_bufferpool_destroy (self->pool); self->pool = NULL; } if (self->codec) { VIDDEC3_delete (self->codec); self->codec = NULL; } if (self->input) { MemMgr_Free (self->input); self->input = NULL; } } static gboolean codec_create (GstDucatiVidDec * self) { gint err; const gchar *codec_name; codec_delete (self); if (G_UNLIKELY (!self->engine)) { GST_ERROR_OBJECT (self, "no engine"); return FALSE; } /* these need to be set before VIDDEC3_create */ self->params->maxWidth = self->width; self->params->maxHeight = self->height; codec_name = GST_DUCATIVIDDEC_GET_CLASS (self)->codec_name; /* create codec: */ GST_DEBUG_OBJECT (self, "creating codec: %s", codec_name); self->codec = VIDDEC3_create (self->engine, (String) codec_name, self->params); if (!self->codec) { return FALSE; } err = VIDDEC3_control (self->codec, XDM_SETPARAMS, self->dynParams, self->status); if (err) { GST_ERROR_OBJECT (self, "failed XDM_SETPARAMS"); return FALSE; } self->first_in_buffer = TRUE; self->first_out_buffer = TRUE; /* allocate input buffer and initialize inBufs: */ self->inBufs->numBufs = 1; self->input = gst_ducati_alloc_1d (self->width * self->height); self->inBufs->descs[0].buf = (XDAS_Int8 *) TilerMem_VirtToPhys (self->input); self->inBufs->descs[0].memType = XDM_MEMTYPE_RAW; return TRUE; } static inline GstBuffer * codec_bufferpool_get (GstDucatiVidDec * self, GstBuffer * buf) { if (G_UNLIKELY (!self->pool)) { guint size; size = gst_video_format_get_size (GST_VIDEO_FORMAT_NV12, self->padded_width, self->padded_height); GST_DEBUG_OBJECT (self, "creating bufferpool"); self->pool = gst_ducati_bufferpool_new (GST_ELEMENT (self), GST_PAD_CAPS (self->srcpad), size); } return GST_BUFFER (gst_ducati_bufferpool_get (self->pool, buf)); } static XDAS_Int32 codec_prepare_outbuf (GstDucatiVidDec * self, GstBuffer ** buf, gboolean force_internal) { XDAS_Int16 y_type, uv_type; guint8 *y_vaddr, *uv_vaddr; SSPtr y_paddr, uv_paddr; if (force_internal) { GstBuffer *orig = *buf; GST_DEBUG_OBJECT (self, "internal bufferpool forced"); *buf = codec_bufferpool_get (self, NULL); GST_BUFFER_TIMESTAMP (*buf) = GST_BUFFER_TIMESTAMP (orig); GST_BUFFER_DURATION (*buf) = GST_BUFFER_DURATION (orig); gst_buffer_unref (orig); return codec_prepare_outbuf (self, buf, FALSE); } y_vaddr = GST_BUFFER_DATA (*buf); uv_vaddr = y_vaddr + self->stride * self->padded_height; y_paddr = TilerMem_VirtToPhys (y_vaddr); uv_paddr = TilerMem_VirtToPhys (uv_vaddr); y_type = gst_ducati_get_mem_type (y_paddr); uv_type = gst_ducati_get_mem_type (uv_paddr); /* FIXME: workaround for the vc1 codec expecting _RAW when it's actually * _TILEDPAGE... should be removed once the codec is fixed */ if (y_type == XDM_MEMTYPE_TILEDPAGE && self->pageMemType != y_type) y_type = self->pageMemType; if (uv_type == XDM_MEMTYPE_TILEDPAGE && self->pageMemType != uv_type) uv_type = self->pageMemType; if (y_type < 0 || uv_type < 0) { GST_DEBUG_OBJECT (self, "non TILER buffer, fallback to bufferpool"); *buf = codec_bufferpool_get (self, *buf); return codec_prepare_outbuf (self, buf, FALSE); } if (!self->outBufs->numBufs) { /* initialize output buffer type */ self->outBufs->numBufs = 2; self->outBufs->descs[0].memType = y_type; self->outBufs->descs[1].memType = uv_type; if (y_type == XDM_MEMTYPE_RAW || y_type == XDM_MEMTYPE_TILEDPAGE) { self->outBufs->descs[0].bufSize.bytes = self->stride * self->padded_height; self->outBufs->descs[1].bufSize.bytes = self->stride * self->padded_height / 2; } else { self->outBufs->descs[0].bufSize.tileMem.width = self->padded_width; self->outBufs->descs[0].bufSize.tileMem.height = self->padded_height; /* note that UV interleaved width is same a Y: */ self->outBufs->descs[1].bufSize.tileMem.width = self->padded_width; self->outBufs->descs[1].bufSize.tileMem.height = self->padded_height / 2; } } else { /* verify output buffer type matches what we've already given * to the codec */ if ((self->outBufs->descs[0].memType != y_type) || (self->outBufs->descs[1].memType != uv_type)) { GST_DEBUG_OBJECT (self, "buffer mismatch, fallback to bufferpool"); *buf = codec_bufferpool_get (self, *buf); return codec_prepare_outbuf (self, buf, FALSE); } } self->outBufs->descs[0].buf = (XDAS_Int8 *) y_paddr; self->outBufs->descs[1].buf = (XDAS_Int8 *) uv_paddr; return (XDAS_Int32) * buf; // XXX use lookup table } static GstBuffer * codec_get_outbuf (GstDucatiVidDec * self, XDAS_Int32 id) { GstBuffer *buf = (GstBuffer *) id; // XXX use lookup table if (buf) { gst_buffer_ref (buf); } return buf; } static void codec_unlock_outbuf (GstDucatiVidDec * self, XDAS_Int32 id) { GstBuffer *buf = (GstBuffer *) id; // XXX use lookup table if (buf) { GST_DEBUG_OBJECT (self, "free buffer: %d %p", id, buf); gst_buffer_unref (buf); } } static gint codec_process (GstDucatiVidDec * self, gboolean send, gboolean flush) { gint err; GstClockTime t; GstBuffer *outbuf = NULL; gint i; GstDucatiVidDecClass *klass = GST_DUCATIVIDDEC_GET_CLASS (self); memset (&self->outArgs->outputID, 0, sizeof (self->outArgs->outputID)); memset (&self->outArgs->freeBufID, 0, sizeof (self->outArgs->freeBufID)); t = gst_util_get_timestamp (); err = VIDDEC3_process (self->codec, self->inBufs, self->outBufs, self->inArgs, self->outArgs); GST_DEBUG_OBJECT (self, "%10dns", (gint) (gst_util_get_timestamp () - t)); if (err) { GST_WARNING_OBJECT (self, "err=%d, extendedError=%08x", err, self->outArgs->extendedError); err = VIDDEC3_control (self->codec, XDM_GETSTATUS, self->dynParams, self->status); if (!err) { GST_WARNING_OBJECT (self, "XDM_GETSTATUS: err=%d, extendedError=%08x", err, self->status->extendedError); } if (flush) err = XDM_EFAIL; else err = klass->handle_error (self, err, self->outArgs->extendedError, self->status->extendedError); } for (i = 0; self->outArgs->outputID[i]; i++) { gboolean interlaced; outbuf = codec_get_outbuf (self, self->outArgs->outputID[i]); interlaced = self->outArgs->decodedBufs.contentType == IVIDEO_PROGRESSIVE ? FALSE : TRUE; /* if send is FALSE, don't try to renegotiate as we could be flushing during * a PAUSED->READY state change */ if (send && interlaced != self->interlaced) { GstCaps *caps; GST_WARNING_OBJECT (self, "upstream set interlaced=%d but codec " "thinks interlaced=%d... trusting codec", self->interlaced, interlaced); self->interlaced = interlaced; caps = gst_caps_make_writable (gst_pad_get_negotiated_caps (self->srcpad)); GST_INFO_OBJECT (self, "changing interlace field in caps"); gst_caps_set_simple (caps, "interlaced", G_TYPE_BOOLEAN, interlaced, NULL); gst_ducati_bufferpool_set_caps (self->pool, caps); if (!gst_pad_set_caps (self->srcpad, caps)) { GST_ERROR_OBJECT (self, "downstream didn't want to change interlace mode"); err = XDM_EFAIL; } gst_caps_unref (caps); /* this buffer still has the old caps so we skip it */ send = FALSE; } if (G_UNLIKELY (self->send_crop_event) && send) { gint crop_width, crop_height; GstDucatiVidDecClass *klass = GST_DUCATIVIDDEC_GET_CLASS (self); /* send region of interest to sink on first buffer: */ XDM_Rect *r = &(self->outArgs->displayBufs.bufDesc[0].activeFrameRegion); crop_width = r->bottomRight.x - r->topLeft.x; crop_height = r->bottomRight.y - r->topLeft.y; if (crop_width > self->input_width) crop_width = self->input_width; if (crop_height > self->input_height) crop_height = self->input_height; if (self->interlaced && !strcmp (klass->codec_name, "ivahd_mpeg2vdec")) crop_height = crop_height / 2; GST_INFO_OBJECT (self, "active frame region %d, %d, %d, %d, crop %dx%d", r->topLeft.x, r->topLeft.y, r->bottomRight.x, r->bottomRight.y, crop_width, crop_height); gst_pad_push_event (self->srcpad, gst_event_new_crop (r->topLeft.y, r->topLeft.x, crop_width, crop_height)); self->send_crop_event = FALSE; } if (G_UNLIKELY (self->first_out_buffer) && send) { GstDucatiBufferPool *pool; self->first_out_buffer = FALSE; /* Destroy the pool so the buffers we used so far are eventually released. * The pool will be recreated if needed. */ pool = self->pool; self->pool = NULL; gst_ducati_bufferpool_destroy (pool); } if (send) { GstClockTime ts; if (GST_IS_DUCATIBUFFER (outbuf)) { outbuf = gst_ducati_buffer_get (GST_DUCATIBUFFER (outbuf)); } ts = GST_BUFFER_TIMESTAMP (outbuf); GST_DEBUG_OBJECT (self, "got buffer: %d %p (%" GST_TIME_FORMAT ")", i, outbuf, GST_TIME_ARGS (ts)); 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, " "enabling ts_is_pts"); self->ts_is_pts = TRUE; } } self->last_pts = ts; if (self->dts_ridx != self->dts_widx) { ts = self->dts_queue[self->dts_ridx++ % NDTS]; } if (self->ts_is_pts) { /* if we have a queued DTS from demuxer, use that instead: */ GST_BUFFER_TIMESTAMP (outbuf) = ts; GST_DEBUG_OBJECT (self, "fixed ts: %d %p (%" GST_TIME_FORMAT ")", i, outbuf, GST_TIME_ARGS (ts)); } if (GST_BUFFER_CAPS (outbuf) && !gst_caps_is_equal (GST_BUFFER_CAPS (outbuf), GST_PAD_CAPS (self->srcpad))) { /* this looks a bit scary but it's really just to change the interlace= * field in caps when we start as !interlaced and the codec detects * otherwise */ GST_WARNING_OBJECT (self, "overriding buffer caps to fix " "interlace mismatch"); outbuf = gst_buffer_make_metadata_writable (outbuf); gst_buffer_set_caps (outbuf, GST_PAD_CAPS (self->srcpad)); } gst_pad_push (self->srcpad, outbuf); } else { GST_DEBUG_OBJECT (self, "free buffer: %d %p", i, outbuf); gst_buffer_unref (outbuf); } } for (i = 0; self->outArgs->freeBufID[i]; i++) { codec_unlock_outbuf (self, self->outArgs->freeBufID[i]); } return err; } /** call control(FLUSH), and then process() to pop out all buffers */ gboolean gst_ducati_viddec_codec_flush (GstDucatiVidDec * self, gboolean eos) { gint err; GST_DEBUG_OBJECT (self, "flush: eos=%d", eos); /* note: flush is synchronized against _chain() to avoid calling * the codec from multiple threads */ GST_PAD_STREAM_LOCK (self->sinkpad); 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; self->wait_keyframe = TRUE; if (G_UNLIKELY (self->first_in_buffer)) { return TRUE; } if (G_UNLIKELY (!self->codec)) { GST_WARNING_OBJECT (self, "no codec"); return TRUE; } err = VIDDEC3_control (self->codec, XDM_FLUSH, self->dynParams, self->status); if (err) { GST_ERROR_OBJECT (self, "failed XDM_FLUSH"); goto out; } self->inBufs->descs[0].bufSize.bytes = 0; self->inArgs->numBytes = 0; self->inArgs->inputID = 0; do { err = codec_process (self, eos, TRUE); } while (err != XDM_EFAIL); /* on a flush, it is normal (and not an error) for the last _process() call * to return an error.. */ err = XDM_EOK; out: GST_PAD_STREAM_UNLOCK (self->sinkpad); GST_DEBUG_OBJECT (self, "done"); return !err; } /* GstDucatiVidDec vmethod default implementations */ static gboolean gst_ducati_viddec_parse_caps (GstDucatiVidDec * self, GstStructure * s) { const GValue *codec_data; gint w, h; if (gst_structure_get_int (s, "width", &self->input_width) && gst_structure_get_int (s, "height", &self->input_height)) { h = ALIGN2 (self->input_height, 4); /* round up to MB */ w = ALIGN2 (self->input_width, 4); /* round up to MB */ /* if we've already created codec, but the resolution has changed, we * need to re-create the codec: */ if (G_UNLIKELY (self->codec)) { if ((h != self->height) || (w != self->width)) { codec_delete (self); } } self->width = w; self->height = h; codec_data = gst_structure_get_value (s, "codec_data"); if (codec_data) { GstBuffer *buffer = gst_value_get_buffer (codec_data); GST_DEBUG_OBJECT (self, "codec_data: %" GST_PTR_FORMAT, buffer); self->codec_data = gst_buffer_ref (buffer); } return TRUE; } return FALSE; } static gboolean gst_ducati_viddec_allocate_params (GstDucatiVidDec * self, gint params_sz, gint dynparams_sz, gint status_sz, gint inargs_sz, gint outargs_sz) { /* allocate params: */ self->params = dce_alloc (params_sz); if (G_UNLIKELY (!self->params)) { return FALSE; } self->params->size = params_sz; self->params->maxFrameRate = 30000; self->params->maxBitRate = 10000000; self->params->dataEndianness = XDM_BYTE; self->params->forceChromaFormat = XDM_YUV_420SP; self->params->operatingMode = IVIDEO_DECODE_ONLY; self->params->displayBufsMode = IVIDDEC3_DISPLAYBUFS_EMBEDDED; self->params->inputDataMode = IVIDEO_ENTIREFRAME; self->params->outputDataMode = IVIDEO_ENTIREFRAME; self->params->numInputDataUnits = 0; self->params->numOutputDataUnits = 0; self->params->metadataType[0] = IVIDEO_METADATAPLANE_NONE; self->params->metadataType[1] = IVIDEO_METADATAPLANE_NONE; self->params->metadataType[2] = IVIDEO_METADATAPLANE_NONE; self->params->errorInfoMode = IVIDEO_ERRORINFO_OFF; /* allocate dynParams: */ self->dynParams = dce_alloc (dynparams_sz); if (G_UNLIKELY (!self->dynParams)) { return FALSE; } self->dynParams->size = dynparams_sz; self->dynParams->decodeHeader = XDM_DECODE_AU; self->dynParams->displayWidth = 0; self->dynParams->frameSkipMode = IVIDEO_NO_SKIP; self->dynParams->newFrameFlag = XDAS_TRUE; /* allocate status: */ self->status = dce_alloc (status_sz); if (G_UNLIKELY (!self->status)) { return FALSE; } self->status->size = status_sz; /* allocate inBufs/outBufs: */ self->inBufs = dce_alloc (sizeof (XDM2_BufDesc)); self->outBufs = dce_alloc (sizeof (XDM2_BufDesc)); if (G_UNLIKELY (!self->inBufs) || G_UNLIKELY (!self->outBufs)) { return FALSE; } /* allocate inArgs/outArgs: */ self->inArgs = dce_alloc (inargs_sz); self->outArgs = dce_alloc (outargs_sz); if (G_UNLIKELY (!self->inArgs) || G_UNLIKELY (!self->outArgs)) { return FALSE; } self->inArgs->size = inargs_sz; self->outArgs->size = outargs_sz; return TRUE; } static GstBuffer * gst_ducati_viddec_push_input (GstDucatiVidDec * self, GstBuffer * buf) { if (G_UNLIKELY (self->first_in_buffer) && self->codec_data) { push_input (self, GST_BUFFER_DATA (self->codec_data), GST_BUFFER_SIZE (self->codec_data)); } /* just copy entire buffer */ push_input (self, GST_BUFFER_DATA (buf), GST_BUFFER_SIZE (buf)); gst_buffer_unref (buf); return NULL; } static gint gst_ducati_viddec_handle_error (GstDucatiVidDec * self, gint ret, gint extended_error, gint status_extended_error) { if (XDM_ISFATALERROR (extended_error)) ret = XDM_EFAIL; else ret = XDM_EOK; return ret; } /* GstElement vmethod implementations */ 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); GstStructure *s; GstCaps *outcaps = NULL; GstStructure *out_s; 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"); ret = FALSE; goto out; } /* update output/padded sizes */ klass->update_buffer_size (self); if (!gst_structure_get_fraction (s, "framerate", &self->fps_n, &self->fps_d)) { self->fps_n = 0; self->fps_d = 1; } gst_structure_get_boolean (s, "interlaced", &self->interlaced); par_present = gst_structure_get_fraction (s, "pixel-aspect-ratio", &par_width, &par_height); outcaps = gst_pad_get_allowed_caps (self->srcpad); if (outcaps) { outcaps = gst_caps_make_writable (outcaps); gst_caps_truncate (outcaps); if (gst_caps_is_empty (outcaps)) { gst_caps_unref (outcaps); outcaps = NULL; } } if (!outcaps) { /* note: default to non-strided for better compatibility with * other gst elements that don't understand stride: */ outcaps = gst_caps_new_simple ("video/x-raw-yuv", "format", GST_TYPE_FOURCC, GST_MAKE_FOURCC ('N', 'V', '1', '2'), NULL); } out_s = gst_caps_get_structure (outcaps, 0); gst_structure_set (out_s, "width", G_TYPE_INT, self->padded_width, "height", G_TYPE_INT, self->padded_height, "framerate", GST_TYPE_FRACTION, self->fps_n, self->fps_d, NULL); if (par_present) gst_structure_set (out_s, "pixel-aspect-ratio", GST_TYPE_FRACTION, par_width, par_height, NULL); if (self->interlaced) gst_structure_set (out_s, "interlaced", G_TYPE_BOOLEAN, TRUE, NULL); if (!strcmp (gst_structure_get_name (out_s), "video/x-raw-yuv-strided")) { if (!gst_structure_get_int (out_s, "rowstride", &self->stride)) { self->stride = 4096; gst_structure_set (out_s, "rowstride", G_TYPE_INT, self->stride, NULL); } } else { self->stride = gst_video_format_get_row_stride (GST_VIDEO_FORMAT_NV12, 0, self->padded_width); } self->outsize = gst_video_format_get_size (GST_VIDEO_FORMAT_NV12, self->stride, self->padded_height); GST_INFO_OBJECT (self, "outsize %d stride %d outcaps: %" GST_PTR_FORMAT, self->outsize, self->stride, outcaps); if (!self->first_in_buffer) { /* Caps changed mid stream. We flush the codec to unlock all the potentially * locked buffers. This is needed for downstream sinks that provide a * buffer pool and need to destroy all the outstanding buffers before they * can negotiate new caps (hello v4l2sink). */ gst_ducati_viddec_codec_flush (self, FALSE); } /* (re)send a crop event when caps change */ self->send_crop_event = TRUE; ret = gst_pad_set_caps (self->srcpad, outcaps); GST_INFO_OBJECT (self, "set caps done %d, %" GST_PTR_FORMAT, ret, outcaps); out: if (outcaps) gst_caps_unref (outcaps); gst_object_unref (self); return ret; } static GstCaps * gst_ducati_viddec_src_getcaps (GstPad * pad) { GstCaps *caps = NULL; GstCaps *outcaps = NULL; int i; caps = GST_PAD_CAPS (pad); if (caps == NULL) { outcaps = gst_caps_copy (gst_pad_get_pad_template_caps (pad)); return outcaps; } outcaps = gst_caps_new_empty (); /* allow -rowstrided and regular yuv */ for (i = 0; i < gst_caps_get_size (caps); i++) { GstStructure *structure; GstStructure *modified_structure; GValue value = { 0 }; structure = gst_caps_get_structure (caps, i); gst_caps_append_structure (outcaps, gst_structure_copy (structure)); modified_structure = gst_structure_copy (structure); if (gst_structure_has_name (structure, "video/x-raw-yuv")) { gst_structure_set_name (modified_structure, "video/x-raw-yuv-strided"); g_value_init (&value, GST_TYPE_INT_RANGE); gst_value_set_int_range (&value, 0, G_MAXINT); gst_structure_set_value (modified_structure, "rowstride", &value); gst_caps_append_structure (outcaps, modified_structure); g_value_unset (&value); } else { gst_structure_set_name (modified_structure, "video/x-raw-yuv"); gst_structure_remove_field (modified_structure, "rowstride"); gst_caps_append_structure (outcaps, modified_structure); } } return outcaps; } static gboolean gst_ducati_viddec_query (GstDucatiVidDec * self, GstPad * pad, GstQuery * query, gboolean * forward) { gboolean res = TRUE; switch (GST_QUERY_TYPE (query)) { case GST_QUERY_BUFFERS: GST_DEBUG_OBJECT (self, "min buffers: %d", self->min_buffers); gst_query_set_buffers_count (query, self->min_buffers); GST_DEBUG_OBJECT (self, "min dimensions: %dx%d", self->padded_width, self->padded_height); gst_query_set_buffers_dimensions (query, self->padded_width, self->padded_height); *forward = FALSE; break; default: break; } return res; } static gboolean gst_ducati_viddec_src_query (GstPad * pad, GstQuery * query) { gboolean res = TRUE, forward = TRUE; GstDucatiVidDec *self = GST_DUCATIVIDDEC (GST_OBJECT_PARENT (pad)); GstDucatiVidDecClass *klass = GST_DUCATIVIDDEC_GET_CLASS (self); GST_DEBUG_OBJECT (self, "query: %" GST_PTR_FORMAT, query); res = klass->query (self, pad, query, &forward); if (res && forward) res = gst_pad_query_default (pad, query); return res; } static gboolean gst_ducati_viddec_do_qos (GstDucatiVidDec * self, GstBuffer * buf) { GstClockTime timestamp, qostime; GstDucatiVidDecClass *klass = GST_DUCATIVIDDEC_GET_CLASS (self); gint64 diff; if (self->wait_keyframe) { if (GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT)) { GST_INFO_OBJECT (self, "skipping until the next keyframe"); return FALSE; } self->wait_keyframe = FALSE; } timestamp = GST_BUFFER_TIMESTAMP (buf); if (self->segment.format != GST_FORMAT_TIME || self->qos_earliest_time == GST_CLOCK_TIME_NONE) goto no_qos; if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (timestamp))) goto no_qos; qostime = gst_segment_to_running_time (&self->segment, GST_FORMAT_TIME, timestamp); if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (qostime))) /* out of segment */ goto no_qos; /* see how our next timestamp relates to the latest qos timestamp. negative * values mean we are early, positive values mean we are too late. */ diff = GST_CLOCK_DIFF (qostime, self->qos_earliest_time); GST_DEBUG_OBJECT (self, "QOS: qostime %" GST_TIME_FORMAT ", earliest %" GST_TIME_FORMAT " diff %" G_GINT64_FORMAT " proportion %f", GST_TIME_ARGS (qostime), GST_TIME_ARGS (self->qos_earliest_time), diff, self->qos_proportion); if (klass->drop_frame (self, buf, diff)) { GST_INFO_OBJECT (self, "dropping frame"); return FALSE; } no_qos: return TRUE; } static gboolean gst_ducati_viddec_drop_frame (GstDucatiVidDec * self, GstBuffer * buf, gint64 diff) { gboolean is_keyframe = !GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT); if (diff >= 0 && !is_keyframe) return TRUE; return FALSE; } static GstFlowReturn gst_ducati_viddec_chain (GstPad * pad, GstBuffer * buf) { GstDucatiVidDec *self = GST_DUCATIVIDDEC (GST_OBJECT_PARENT (pad)); GstClockTime ts = GST_BUFFER_TIMESTAMP (buf); GstFlowReturn ret; Int32 err; GstBuffer *outbuf = NULL; GstCaps *outcaps = NULL; gboolean decode; if (G_UNLIKELY (!self->engine)) { GST_ERROR_OBJECT (self, "no engine"); return GST_FLOW_ERROR; } GST_DEBUG_OBJECT (self, "chain: %" GST_TIME_FORMAT " (%d bytes, flags %d)", GST_TIME_ARGS (ts), GST_BUFFER_SIZE (buf), GST_BUFFER_FLAGS (buf)); decode = gst_ducati_viddec_do_qos (self, buf); if (!decode) { gst_buffer_unref (buf); return GST_FLOW_OK; } if (!self->need_out_buf) goto have_out_buf; /* do this before creating codec to ensure reverse caps negotiation * happens first: */ allocate_buffer: ret = gst_pad_alloc_buffer (self->srcpad, 0, self->outsize, GST_PAD_CAPS (self->srcpad), &outbuf); if (ret != GST_FLOW_OK) { GST_ERROR_OBJECT (self, "alloc_buffer failed %s", gst_flow_get_name (ret)); return ret; } outcaps = GST_BUFFER_CAPS (outbuf); if (outcaps && !gst_caps_is_equal (outcaps, GST_PAD_CAPS (self->srcpad))) { GstStructure *s; GST_INFO_OBJECT (self, "doing upstream negotiation bufsize %d", GST_BUFFER_SIZE (outbuf)); s = gst_caps_get_structure (outcaps, 0); gst_structure_get_int (s, "rowstride", &self->stride); self->outsize = gst_video_format_get_size (GST_VIDEO_FORMAT_NV12, self->stride, self->padded_height); GST_INFO_OBJECT (self, "outsize %d stride %d outcaps: %" GST_PTR_FORMAT, self->outsize, self->stride, outcaps); gst_pad_set_caps (self->srcpad, outcaps); if (GST_BUFFER_SIZE (outbuf) != self->outsize) { GST_INFO_OBJECT (self, "dropping buffer (bufsize %d != outsize %d)", GST_BUFFER_SIZE (outbuf), self->outsize); gst_buffer_unref (outbuf); goto allocate_buffer; } } if (G_UNLIKELY (!self->codec)) { if (!codec_create (self)) { GST_ERROR_OBJECT (self, "could not create codec"); return GST_FLOW_ERROR; } } GST_BUFFER_TIMESTAMP (outbuf) = GST_BUFFER_TIMESTAMP (buf); GST_BUFFER_DURATION (outbuf) = GST_BUFFER_DURATION (buf); /* Pass new output buffer to the decoder to decode into. Use buffers from the * internal pool while self->first_out_buffer == TRUE in order to simplify * things in case we need to renegotiate */ self->inArgs->inputID = codec_prepare_outbuf (self, &outbuf, self->first_out_buffer); if (!self->inArgs->inputID) { GST_ERROR_OBJECT (self, "could not prepare output buffer"); return GST_FLOW_ERROR; } have_out_buf: self->in_size = 0; buf = GST_DUCATIVIDDEC_GET_CLASS (self)->push_input (self, buf); 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 * we are getting are definitely decode order (DTS): */ if ((self->last_dts != GST_CLOCK_TIME_NONE) && (self->last_dts > ts)) { GST_DEBUG_OBJECT (self, "input timestamp definitely DTS"); self->ts_may_be_pts = FALSE; } self->last_dts = ts; } if (self->in_size == 0 && outbuf) { GST_DEBUG_OBJECT (self, "no input, skipping process"); gst_buffer_unref (outbuf); return GST_FLOW_OK; } self->inArgs->numBytes = self->in_size; self->inBufs->descs[0].bufSize.bytes = self->in_size; err = codec_process (self, TRUE, FALSE); if (err) { GST_ELEMENT_ERROR (self, STREAM, DECODE, (NULL), ("process returned error: %d %08x", err, self->outArgs->extendedError)); return GST_FLOW_ERROR; } self->first_in_buffer = FALSE; if (self->outArgs->outBufsInUseFlag) { GST_DEBUG_OBJECT (self, "outBufsInUseFlag set"); self->need_out_buf = FALSE; } else { self->need_out_buf = TRUE; } if (buf) { GST_DEBUG_OBJECT (self, "found remaining data: %d bytes", GST_BUFFER_SIZE (buf)); ts = GST_BUFFER_TIMESTAMP (buf); goto allocate_buffer; } return GST_FLOW_OK; } static gboolean gst_ducati_viddec_event (GstPad * pad, GstEvent * event) { GstDucatiVidDec *self = GST_DUCATIVIDDEC (GST_OBJECT_PARENT (pad)); gboolean ret = TRUE; GST_DEBUG_OBJECT (self, "begin: event=%s", GST_EVENT_TYPE_NAME (event)); switch (GST_EVENT_TYPE (event)) { case GST_EVENT_NEWSEGMENT: { gboolean update; GstFormat fmt; gint64 start, stop, time; gdouble rate, arate; gst_event_parse_new_segment_full (event, &update, &rate, &arate, &fmt, &start, &stop, &time); gst_segment_set_newsegment_full (&self->segment, update, rate, arate, fmt, start, stop, time); break; } case GST_EVENT_EOS: if (!gst_ducati_viddec_codec_flush (self, TRUE)) { GST_ERROR_OBJECT (self, "could not flush on eos"); ret = FALSE; } break; case GST_EVENT_FLUSH_STOP: if (!gst_ducati_viddec_codec_flush (self, FALSE)) { GST_ERROR_OBJECT (self, "could not flush"); ret = FALSE; } gst_segment_init (&self->segment, GST_FORMAT_UNDEFINED); self->qos_earliest_time = GST_CLOCK_TIME_NONE; self->qos_proportion = 1; self->need_out_buf = TRUE; break; default: break; } if (ret) ret = gst_pad_push_event (self->srcpad, event); GST_LOG_OBJECT (self, "end"); return ret; } static gboolean gst_ducati_viddec_src_event (GstPad * pad, GstEvent * event) { GstDucatiVidDec *self = GST_DUCATIVIDDEC (GST_OBJECT_PARENT (pad)); gboolean ret = TRUE; GST_LOG_OBJECT (self, "begin: event=%s", GST_EVENT_TYPE_NAME (event)); switch (GST_EVENT_TYPE (event)) { case GST_EVENT_QOS: { gdouble proportion; GstClockTimeDiff diff; GstClockTime timestamp; gst_event_parse_qos (event, &proportion, &diff, ×tamp); GST_OBJECT_LOCK (self); self->qos_proportion = proportion; self->qos_earliest_time = timestamp + 2 * diff; GST_OBJECT_UNLOCK (self); GST_DEBUG_OBJECT (self, "got QoS proportion %f %" GST_TIME_FORMAT ", %" G_GINT64_FORMAT, proportion, GST_TIME_ARGS (timestamp), diff); ret = gst_pad_push_event (self->sinkpad, event); break; } default: ret = gst_pad_push_event (self->sinkpad, event); break; } GST_LOG_OBJECT (self, "end"); return ret; } static GstStateChangeReturn gst_ducati_viddec_change_state (GstElement * element, GstStateChange transition) { GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; GstDucatiVidDec *self = GST_DUCATIVIDDEC (element); GST_DEBUG_OBJECT (self, "begin: changing state %s -> %s", gst_element_state_get_name (GST_STATE_TRANSITION_CURRENT (transition)), gst_element_state_get_name (GST_STATE_TRANSITION_NEXT (transition))); switch (transition) { case GST_STATE_CHANGE_NULL_TO_READY: if (!engine_open (self)) { GST_ERROR_OBJECT (self, "could not open"); return GST_STATE_CHANGE_FAILURE; } break; default: break; } ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); if (ret == GST_STATE_CHANGE_FAILURE) goto leave; switch (transition) { case GST_STATE_CHANGE_PAUSED_TO_READY: self->interlaced = FALSE; self->send_crop_event = TRUE; gst_ducati_viddec_codec_flush (self, FALSE); break; case GST_STATE_CHANGE_READY_TO_NULL: codec_delete (self); engine_close (self); break; default: break; } leave: GST_LOG_OBJECT (self, "end"); return ret; } /* GObject vmethod implementations */ #define VERSION_LENGTH 256 static void gst_ducati_viddec_get_property (GObject * obj, guint prop_id, GValue * value, GParamSpec * pspec) { GstDucatiVidDec *self = GST_DUCATIVIDDEC (obj); switch (prop_id) { case PROP_VERSION:{ int err; char *version = gst_ducati_alloc_1d (VERSION_LENGTH); /* in case something fails: */ snprintf (version, VERSION_LENGTH, "unsupported"); if (!self->engine) engine_open (self); if (!self->codec) codec_create (self); if (self->codec) { self->status->data.buf = (XDAS_Int8 *) TilerMem_VirtToPhys (version); self->status->data.bufSize = VERSION_LENGTH; err = VIDDEC3_control (self->codec, XDM_GETVERSION, self->dynParams, self->status); if (err) { GST_ERROR_OBJECT (self, "failed XDM_GETVERSION"); } self->status->data.buf = NULL; self->status->data.bufSize = 0; } g_value_set_string (value, version); MemMgr_Free (version); break; } default:{ G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop_id, pspec); break; } } } static void gst_ducati_viddec_finalize (GObject * obj) { GstDucatiVidDec *self = GST_DUCATIVIDDEC (obj); codec_delete (self); engine_close (self); if (self->codec_data) { gst_buffer_unref (self->codec_data); self->codec_data = NULL; } G_OBJECT_CLASS (parent_class)->finalize (obj); } static void gst_ducati_viddec_base_init (gpointer gclass) { GstElementClass *element_class = GST_ELEMENT_CLASS (gclass); gst_element_class_add_pad_template (element_class, gst_static_pad_template_get (&src_factory)); } static void gst_ducati_viddec_class_init (GstDucatiVidDecClass * klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass); gobject_class->get_property = GST_DEBUG_FUNCPTR (gst_ducati_viddec_get_property); gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_ducati_viddec_finalize); gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_ducati_viddec_change_state); klass->parse_caps = GST_DEBUG_FUNCPTR (gst_ducati_viddec_parse_caps); klass->allocate_params = GST_DEBUG_FUNCPTR (gst_ducati_viddec_allocate_params); klass->push_input = GST_DEBUG_FUNCPTR (gst_ducati_viddec_push_input); klass->handle_error = GST_DEBUG_FUNCPTR (gst_ducati_viddec_handle_error); klass->drop_frame = GST_DEBUG_FUNCPTR (gst_ducati_viddec_drop_frame); klass->query = GST_DEBUG_FUNCPTR (gst_ducati_viddec_query); g_object_class_install_property (gobject_class, PROP_VERSION, g_param_spec_string ("version", "Version", "The codec version string", "", G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); } static void gst_ducati_viddec_init (GstDucatiVidDec * self, GstDucatiVidDecClass * klass) { GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass); self->sinkpad = gst_pad_new_from_template (gst_element_class_get_pad_template (gstelement_class, "sink"), "sink"); gst_pad_set_setcaps_function (self->sinkpad, GST_DEBUG_FUNCPTR (gst_ducati_viddec_sink_setcaps)); gst_pad_set_chain_function (self->sinkpad, GST_DEBUG_FUNCPTR (gst_ducati_viddec_chain)); gst_pad_set_event_function (self->sinkpad, GST_DEBUG_FUNCPTR (gst_ducati_viddec_event)); self->srcpad = gst_pad_new_from_static_template (&src_factory, "src"); gst_pad_set_event_function (self->srcpad, GST_DEBUG_FUNCPTR (gst_ducati_viddec_src_event)); gst_pad_set_query_function (self->srcpad, GST_DEBUG_FUNCPTR (gst_ducati_viddec_src_query)); gst_pad_set_getcaps_function (self->srcpad, GST_DEBUG_FUNCPTR (gst_ducati_viddec_src_getcaps)); gst_element_add_pad (GST_ELEMENT (self), self->sinkpad); gst_element_add_pad (GST_ELEMENT (self), self->srcpad); self->input_width = 0; self->input_height = 0; /* sane defaults in case we need to create codec without caps negotiation * (for example, to get 'version' property) */ self->width = 128; self->height = 128; self->fps_n = -1; self->fps_d = -1; self->first_in_buffer = TRUE; self->first_out_buffer = TRUE; self->interlaced = FALSE; self->send_crop_event = TRUE; 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; self->pageMemType = XDM_MEMTYPE_TILEDPAGE; gst_segment_init (&self->segment, GST_FORMAT_UNDEFINED); self->qos_proportion = 1; self->qos_earliest_time = GST_CLOCK_TIME_NONE; self->wait_keyframe = TRUE; self->need_out_buf = TRUE; }