pngenc: Allocate a single GstMemory per frame

Previously, we would create a new GstMemory per write operation
and then append them to the GstBuffer. This would cause a reallocation
every 16 Memories which is an issue since the png encoder will usually
do write in a pattern of 4, 8 and 8k bytes repeating until the frame
is done.

Instead allocate a single GstMemory and keep writting it into it
with a manual index. Much like the jpegenc does.

Doing some basic testing With a testsrc snow pattern at 4k and 8k
the same pipeline would take ~3.30s to encode a 4k frame and ~23s
for an 8k. At 4k 0.70s/33% is taken by memory allocations, while at
8k its ~10.5s/45%.

With this patch, at 4k the pipeline takes ~2.40s and at 8k only 9.60s
making this 28% and 58% faster accordingly on my laptop, and
allocation runtime is dropped to subsecond times.

Here's the test pipeline used, increase num-buffers in image freeze
to gather more samples.

```
gst-launch-1.0 videotestsrc num-buffers=1 pattern=snow ! imagefreeze num-buffers=1 ! \
  video/x-raw,width=7680,height=4320 ! pngenc ! fakesink
```

Close #2717

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/4944>
This commit is contained in:
Jordan Petridis 2023-06-28 15:51:15 +03:00 committed by GStreamer Marge Bot
parent 794cde703c
commit 1ef13dda12
2 changed files with 91 additions and 23 deletions

View file

@ -228,38 +228,86 @@ user_flush_data (png_structp png_ptr G_GNUC_UNUSED)
{
}
/* Copied from glib/gutilsprivate.h
*
* Returns the smallest power of 2 greater than or equal to n,
* or 0 if such power does not fit in a gsize
*/
static inline gsize
gst_pngenc_g_nearest_pow (gsize num)
{
gsize n = num - 1;
g_assert (num > 0 && num <= G_MAXSIZE / 2);
n |= n >> 1;
n |= n >> 2;
n |= n >> 4;
n |= n >> 8;
n |= n >> 16;
#if GLIB_SIZEOF_SIZE_T == 8
n |= n >> 32;
#endif
return n + 1;
}
static void
ensure_memory_is_enough (GstPngEnc * pngenc, unsigned int extra_length)
{
GstMemory *new_memory;
GstMapInfo map;
gsize old_size, desired_size;
guint8 *new_data;
old_size = pngenc->output_map.size;
desired_size = gst_pngenc_g_nearest_pow (old_size + extra_length);
g_assert (desired_size != 0);
/* Our output memory wasn't big enough.
* Make a new memory that's twice the size, */
new_memory = gst_allocator_alloc (NULL, desired_size, NULL);
gst_memory_map (new_memory, &map, GST_MAP_READWRITE);
new_data = map.data;
/* copy previous data */
memcpy (new_data, pngenc->output_map.data, old_size);
gst_memory_unmap (pngenc->output_mem, &pngenc->output_map);
gst_memory_unref (pngenc->output_mem);
/* drop it into place, */
pngenc->output_mem = new_memory;
pngenc->output_map = map;
}
static void
user_write_data (png_structp png_ptr, png_bytep data, png_uint_32 length)
{
GstPngEnc *pngenc;
GstMemory *mem;
GstMapInfo minfo;
pngenc = (GstPngEnc *) png_get_io_ptr (png_ptr);
mem = gst_allocator_alloc (NULL, length, NULL);
if (!mem) {
GST_ERROR_OBJECT (pngenc, "Failed to allocate memory");
png_error (png_ptr, "Failed to allocate memory");
GST_TRACE_OBJECT (pngenc,
"Memory size: %" G_GSIZE_FORMAT "\nLength to be written: %u",
pngenc->output_map.size, length);
if (pngenc->output_map.size > G_MAXSIZE - length) {
GST_ERROR_OBJECT (pngenc,
"Memory buffer would overflow after the png write, aborting.");
png_error (png_ptr, "Buffer would overflow, aborting the write.");
/* never reached */
/* libpng will longjmp and we will catch it later on */
return;
}
if (!gst_memory_map (mem, &minfo, GST_MAP_WRITE)) {
GST_ERROR_OBJECT (pngenc, "Failed to map memory");
gst_memory_unref (mem);
png_error (png_ptr, "Failed to map memory");
/* never reached */
return;
if ((pngenc->output_mem_pos + length) > pngenc->output_map.size) {
GST_INFO_OBJECT (pngenc, "Memory not enough, Allocating more.");
ensure_memory_is_enough (pngenc, length);
}
memcpy (minfo.data, data, length);
gst_memory_unmap (mem, &minfo);
gst_buffer_append_memory (pngenc->buffer_out, mem);
memcpy (&pngenc->output_map.data[pngenc->output_mem_pos], data, length);
pngenc->output_mem_pos += length;
}
static GstFlowReturn
@ -269,8 +317,10 @@ gst_pngenc_handle_frame (GstVideoEncoder * encoder, GstVideoCodecFrame * frame)
gint row_index;
png_byte **row_pointers;
GstFlowReturn ret = GST_FLOW_OK;
GstVideoInfo *info;
const GstVideoInfo *info;
GstVideoFrame vframe;
gsize memory_size;
GstBuffer *outbuf;
pngenc = GST_PNGENC (encoder);
info = &pngenc->input_state->info;
@ -323,7 +373,15 @@ gst_pngenc_handle_frame (GstVideoEncoder * encoder, GstVideoCodecFrame * frame)
}
/* allocate the output buffer */
pngenc->buffer_out = gst_buffer_new ();
pngenc->output_mem_pos = 0;
pngenc->output_mem =
gst_allocator_alloc (NULL, MAX (4096, GST_VIDEO_INFO_SIZE (info)), NULL);
if (!pngenc->output_mem) {
GST_ERROR_OBJECT (pngenc, "Failed to allocate memory");
return GST_FLOW_ERROR;
}
gst_memory_map (pngenc->output_mem, &pngenc->output_map, GST_MAP_READWRITE);
png_write_info (pngenc->png_struct_ptr, pngenc->png_info_ptr);
png_write_image (pngenc->png_struct_ptr, row_pointers);
@ -336,9 +394,16 @@ gst_pngenc_handle_frame (GstVideoEncoder * encoder, GstVideoCodecFrame * frame)
png_destroy_write_struct (&pngenc->png_struct_ptr, (png_infopp) NULL);
/* Set final size and store */
frame->output_buffer = pngenc->buffer_out;
gst_memory_unmap (pngenc->output_mem, &pngenc->output_map);
/* Trim the buffer size */
memory_size = pngenc->output_mem_pos;
gst_memory_resize (pngenc->output_mem, 0, memory_size);
pngenc->output_mem_pos = 0;
pngenc->buffer_out = NULL;
outbuf = gst_buffer_new ();
gst_buffer_append_memory (outbuf, pngenc->output_mem);
pngenc->output_mem = NULL;
frame->output_buffer = outbuf;
if ((ret = gst_video_encoder_finish_frame (encoder, frame)) != GST_FLOW_OK)
goto done;

View file

@ -37,7 +37,10 @@ struct _GstPngEnc
GstVideoEncoder parent;
GstVideoCodecState *input_state;
GstBuffer *buffer_out;
GstMemory *output_mem;
GstMapInfo output_map;
gsize output_mem_pos;
png_structp png_struct_ptr;
png_infop png_info_ptr;