From 7048134fa9818fc7814942698e87570f31932f26 Mon Sep 17 00:00:00 2001 From: Guillaume Desmottes Date: Wed, 9 Aug 2017 12:07:33 -0400 Subject: [PATCH] omxvideodec: use dynamic buffer mode on input if possible Prevent from copying the input buffers between GStreamer and OMX. Tested on zynqultrascaleplus and rpi (without dynamic buffers). https://bugzilla.gnome.org/show_bug.cgi?id=787093 --- omx/gstomxvideodec.c | 180 ++++++++++++++++++++++++++++++++++++------- omx/gstomxvideodec.h | 1 + 2 files changed, 155 insertions(+), 26 deletions(-) diff --git a/omx/gstomxvideodec.c b/omx/gstomxvideodec.c index 1a1c3d7052..9679c02964 100644 --- a/omx/gstomxvideodec.c +++ b/omx/gstomxvideodec.c @@ -2136,19 +2136,95 @@ gst_omx_video_dec_disable (GstOMXVideoDec * self) static gboolean gst_omx_video_dec_allocate_in_buffers (GstOMXVideoDec * self) { - if (gst_omx_port_allocate_buffers (self->dec_in_port) != OMX_ErrorNone) - return FALSE; + switch (self->input_allocation) { + case GST_OMX_BUFFER_ALLOCATION_ALLOCATE_BUFFER: + if (gst_omx_port_allocate_buffers (self->dec_in_port) != OMX_ErrorNone) + return FALSE; + break; + case GST_OMX_BUFFER_ALLOCATION_USE_BUFFER_DYNAMIC: + if (gst_omx_port_use_dynamic_buffers (self->dec_in_port) != OMX_ErrorNone) + return FALSE; + break; + case GST_OMX_BUFFER_ALLOCATION_USE_BUFFER: + default: + /* Not supported */ + g_return_val_if_reached (FALSE); + } return TRUE; } static gboolean -gst_omx_video_dec_enable (GstOMXVideoDec * self) +check_input_alignment (GstOMXVideoDec * self, GstMapInfo * map) +{ + OMX_PARAM_PORTDEFINITIONTYPE *port_def = &self->dec_in_port->port_def; + + if (port_def->nBufferAlignment && + (GPOINTER_TO_UINT (map->data) & (port_def->nBufferAlignment - 1)) != 0) { + GST_DEBUG_OBJECT (self, + "input buffer is not properly aligned (address: %p alignment: %u bytes), can't use dynamic allocation", + map->data, (guint32) port_def->nBufferAlignment); + return FALSE; + } + + return TRUE; +} + +/* Check if @inbuf's alignment matches the requirements to use the + * dynamic buffer mode. */ +static gboolean +can_use_dynamic_buffer_mode (GstOMXVideoDec * self, GstBuffer * inbuf) +{ + gboolean result = TRUE; + guint i; + + for (i = 0; i < gst_buffer_n_memory (inbuf) && result; i++) { + GstMemory *mem = gst_buffer_peek_memory (inbuf, i); + GstMapInfo map; + + if (!gst_memory_map (mem, &map, GST_MAP_READ)) { + GST_ELEMENT_ERROR (self, STREAM, FORMAT, (NULL), + ("failed to map input buffer")); + return FALSE; + } + + result = check_input_alignment (self, &map); + + gst_memory_unmap (mem, &map); + } + + return result; +} + +/* Choose the allocation mode for input buffers depending of what's supported by + * the component and the size/alignment of the input buffer. */ +static GstOMXBufferAllocation +gst_omx_video_dec_pick_input_allocation_mode (GstOMXVideoDec * self, + GstBuffer * inbuf) +{ + if (!gst_omx_is_dynamic_allocation_supported ()) + return GST_OMX_BUFFER_ALLOCATION_ALLOCATE_BUFFER; + + if (can_use_dynamic_buffer_mode (self, inbuf)) { + GST_DEBUG_OBJECT (self, + "input buffer is properly aligned, use dynamic allocation"); + return GST_OMX_BUFFER_ALLOCATION_USE_BUFFER_DYNAMIC; + } + + GST_DEBUG_OBJECT (self, "let input buffer allocate its buffers"); + return GST_OMX_BUFFER_ALLOCATION_ALLOCATE_BUFFER; +} + +static gboolean +gst_omx_video_dec_enable (GstOMXVideoDec * self, GstBuffer * input) { GstOMXVideoDecClass *klass = GST_OMX_VIDEO_DEC_GET_CLASS (self); GST_DEBUG_OBJECT (self, "Enabling component"); + self->input_allocation = gst_omx_video_dec_pick_input_allocation_mode (self, + input); + if (self->disabled) { if (gst_omx_port_set_enabled (self->dec_in_port, TRUE) != OMX_ErrorNone) return FALSE; @@ -2430,6 +2506,9 @@ gst_omx_video_dec_handle_frame (GstVideoDecoder * decoder, guint offset = 0, size; GstClockTime timestamp, duration; OMX_ERRORTYPE err; + gboolean done = FALSE; + gboolean first_ouput_buffer = TRUE; + guint memory_idx = 0; /* only used in dynamic buffer mode */ self = GST_OMX_VIDEO_DEC (decoder); klass = GST_OMX_VIDEO_DEC_GET_CLASS (self); @@ -2448,7 +2527,7 @@ gst_omx_video_dec_handle_frame (GstVideoDecoder * decoder, } if (gst_omx_port_is_flushing (self->dec_out_port)) { - if (!gst_omx_video_dec_enable (self)) + if (!gst_omx_video_dec_enable (self, frame->input_buffer)) return FALSE; } @@ -2475,7 +2554,7 @@ gst_omx_video_dec_handle_frame (GstVideoDecoder * decoder, port = self->dec_in_port; size = gst_buffer_get_size (frame->input_buffer); - while (offset < size) { + while (!done) { /* Make sure to release the base class stream lock, otherwise * _loop() can't call _finish_frame() and we might block forever * because no input buffers are released */ @@ -2560,18 +2639,28 @@ gst_omx_video_dec_handle_frame (GstVideoDecoder * decoder, codec_data = self->codec_data; - if (buf->omx_buf->nAllocLen - buf->omx_buf->nOffset < - gst_buffer_get_size (codec_data)) { - gst_omx_port_release_buffer (port, buf); - goto too_large_codec_data; + if (self->input_allocation == + GST_OMX_BUFFER_ALLOCATION_USE_BUFFER_DYNAMIC) { + /* Map the full buffer, this may lead to copying if for some reason its + * content is split on more than one memory but that seems unlikely and + * the codec data aren't supposed to be that big anyway. */ + if (!gst_omx_buffer_map_buffer (buf, codec_data)) + goto map_failed; + } else { + if (buf->omx_buf->nAllocLen - buf->omx_buf->nOffset < + gst_buffer_get_size (codec_data)) { + gst_omx_port_release_buffer (port, buf); + goto too_large_codec_data; + } + + buf->omx_buf->nFilledLen = gst_buffer_get_size (codec_data);; + gst_buffer_extract (codec_data, 0, + buf->omx_buf->pBuffer + buf->omx_buf->nOffset, + buf->omx_buf->nFilledLen); } buf->omx_buf->nFlags |= OMX_BUFFERFLAG_CODECCONFIG; buf->omx_buf->nFlags |= OMX_BUFFERFLAG_ENDOFFRAME; - buf->omx_buf->nFilledLen = gst_buffer_get_size (codec_data);; - gst_buffer_extract (codec_data, 0, - buf->omx_buf->pBuffer + buf->omx_buf->nOffset, - buf->omx_buf->nFilledLen); if (GST_CLOCK_TIME_IS_VALID (timestamp)) GST_OMX_SET_TICKS (buf->omx_buf->nTimeStamp, @@ -2591,15 +2680,46 @@ gst_omx_video_dec_handle_frame (GstVideoDecoder * decoder, } /* Now handle the frame */ - GST_DEBUG_OBJECT (self, "Passing frame offset %d to the component", offset); - /* Copy the buffer content in chunks of size as requested - * by the port */ - buf->omx_buf->nFilledLen = - MIN (size - offset, buf->omx_buf->nAllocLen - buf->omx_buf->nOffset); - gst_buffer_extract (frame->input_buffer, offset, - buf->omx_buf->pBuffer + buf->omx_buf->nOffset, - buf->omx_buf->nFilledLen); + if (self->input_allocation == GST_OMX_BUFFER_ALLOCATION_USE_BUFFER_DYNAMIC) { + /* Transfer the buffer content per memory rather than mapping the full + * buffer to prevent copies. */ + GstMemory *mem = gst_buffer_peek_memory (frame->input_buffer, memory_idx); + + GST_LOG_OBJECT (self, + "Transferring %" G_GSIZE_FORMAT " bytes to the component", + gst_memory_get_sizes (mem, NULL, NULL)); + + if (!gst_omx_buffer_map_memory (buf, mem)) + goto map_failed; + + if (!check_input_alignment (self, &buf->map)) { + GST_ELEMENT_ERROR (self, STREAM, FORMAT, (NULL), + ("input buffer now has wrong alignment/stride, can't use dynamic allocation any more")); + return FALSE; + } + + memory_idx++; + if (memory_idx == gst_buffer_n_memory (frame->input_buffer)) + done = TRUE; + } else { + /* Copy the buffer content in chunks of size as requested + * by the port */ + buf->omx_buf->nFilledLen = + MIN (size - offset, buf->omx_buf->nAllocLen - buf->omx_buf->nOffset); + + GST_LOG_OBJECT (self, + "Copying %d bytes (frame offset %d) to the component", + (guint) buf->omx_buf->nFilledLen, offset); + + gst_buffer_extract (frame->input_buffer, offset, + buf->omx_buf->pBuffer + buf->omx_buf->nOffset, + buf->omx_buf->nFilledLen); + + offset += buf->omx_buf->nFilledLen; + if (offset == size) + done = TRUE; + } if (timestamp != GST_CLOCK_TIME_NONE) { GST_OMX_SET_TICKS (buf->omx_buf->nTimeStamp, @@ -2609,7 +2729,7 @@ gst_omx_video_dec_handle_frame (GstVideoDecoder * decoder, GST_OMX_SET_TICKS (buf->omx_buf->nTimeStamp, G_GUINT64_CONSTANT (0)); } - if (duration != GST_CLOCK_TIME_NONE && offset == 0) { + if (duration != GST_CLOCK_TIME_NONE && first_ouput_buffer) { buf->omx_buf->nTickCount = gst_util_uint64_scale (duration, OMX_TICKS_PER_SECOND, GST_SECOND); self->last_upstream_ts += duration; @@ -2617,7 +2737,7 @@ gst_omx_video_dec_handle_frame (GstVideoDecoder * decoder, buf->omx_buf->nTickCount = 0; } - if (offset == 0 && GST_VIDEO_CODEC_FRAME_IS_SYNC_POINT (frame)) + if (first_ouput_buffer && GST_VIDEO_CODEC_FRAME_IS_SYNC_POINT (frame)) buf->omx_buf->nFlags |= OMX_BUFFERFLAG_SYNCFRAME; /* TODO: Set flags @@ -2625,15 +2745,15 @@ gst_omx_video_dec_handle_frame (GstVideoDecoder * decoder, * the segment */ - offset += buf->omx_buf->nFilledLen; - - if (offset == size) + if (done) buf->omx_buf->nFlags |= OMX_BUFFERFLAG_ENDOFFRAME; self->started = TRUE; err = gst_omx_port_release_buffer (port, buf); if (err != OMX_ErrorNone) goto release_error; + + first_ouput_buffer = FALSE; } gst_video_codec_frame_unref (frame); @@ -2668,6 +2788,14 @@ too_large_codec_data: return GST_FLOW_ERROR; } +map_failed: + { + gst_video_codec_frame_unref (frame); + GST_ELEMENT_ERROR (self, STREAM, FORMAT, (NULL), + ("failed to map input buffer")); + return GST_FLOW_ERROR; + } + component_error: { gst_video_codec_frame_unref (frame); diff --git a/omx/gstomxvideodec.h b/omx/gstomxvideodec.h index d6c46a991f..ee719e46a8 100644 --- a/omx/gstomxvideodec.h +++ b/omx/gstomxvideodec.h @@ -94,6 +94,7 @@ struct _GstOMXVideoDec /* TRUE if decoder is producing dmabuf */ gboolean dmabuf; + GstOMXBufferAllocation input_allocation; }; struct _GstOMXVideoDecClass