gstreamer/gst/pnm/gstpnmenc.c
Dimitrios Katsaros 3cf4a70dbc pnm: Fixed segfault in pnmenc
The pnmenc was not mapping the input buffers as video buffers. Because
of this, the video frame stride was not being set based on frame but
based on the caps, which make the assumption that the strides are a
power of 4. For input that is not a power of 4, this would lead to a
SIGSEGV.

https://bugzilla.gnome.org/show_bug.cgi?id=793419
2018-02-14 10:19:22 +02:00

402 lines
13 KiB
C

/* GStreamer PNM encoder
* Copyright (C) 2009 Lutz Mueller <lutz@users.sourceforge.net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
/**
* SECTION:element-pnmenc
* @title: pnmenc
*
* Encodes pnm images. This plugin supports both raw and ASCII encoding.
* To enable ASCII encoding, set the parameter ascii to TRUE. If you omit
* the parameter or set it to FALSE, the output will be raw encoded.
*
* ## Example launch line
* |[
* gst-launch-1.0 videotestsrc num_buffers=1 ! videoconvert ! "video/x-raw,format=GRAY8" ! pnmenc ascii=true ! filesink location=test.pnm
* ]| The above pipeline writes a test pnm file (ASCII encoding).
*
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "gstpnmenc.h"
#include "gstpnmutils.h"
#include <gst/gstutils.h>
#include <gst/video/video.h>
#include <gst/video/gstvideometa.h>
#include <stdio.h>
#include <string.h>
enum
{
GST_PNMENC_PROP_0,
GST_PNMENC_PROP_ASCII
/* Add here. */
};
static GstStaticPadTemplate sink_pad_template =
GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS,
GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE
("{ RGB, GRAY8, GRAY16_BE, GRAY16_LE }")));
static GstStaticPadTemplate src_pad_template =
GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS,
GST_STATIC_CAPS (MIME_ALL));
G_DEFINE_TYPE (GstPnmenc, gst_pnmenc, GST_TYPE_VIDEO_ENCODER);
#define parent_class gst_pnmenc_parent_class
static GstFlowReturn
gst_pnmenc_handle_frame (GstVideoEncoder * encoder, GstVideoCodecFrame * frame);
static void gst_pnmenc_finalize (GObject * object);
static void
gst_pnmenc_set_property (GObject * object, guint prop_id, const GValue * value,
GParamSpec * pspec)
{
GstPnmenc *s = GST_PNMENC (object);
switch (prop_id) {
case GST_PNMENC_PROP_ASCII:
if (g_value_get_boolean (value)) {
s->info.encoding = GST_PNM_ENCODING_ASCII;
} else {
s->info.encoding = GST_PNM_ENCODING_RAW;
}
s->info.fields |= GST_PNM_INFO_FIELDS_ENCODING;
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_pnmenc_get_property (GObject * object, guint prop_id, GValue * value,
GParamSpec * pspec)
{
GstPnmenc *s = GST_PNMENC (object);
switch (prop_id) {
case GST_PNMENC_PROP_ASCII:
g_value_set_boolean (value, s->info.encoding == GST_PNM_ENCODING_ASCII);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_pnmenc_init (GstPnmenc * s)
{
GST_PAD_SET_ACCEPT_TEMPLATE (GST_VIDEO_ENCODER_SINK_PAD (s));
/* Set default encoding as RAW as ASCII takes up 4 time more bytes */
s->info.encoding = GST_PNM_ENCODING_RAW;
}
static void
gst_pnmenc_finalize (GObject * object)
{
GstPnmenc *pnmenc = GST_PNMENC (object);
if (pnmenc->input_state)
gst_video_codec_state_unref (pnmenc->input_state);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static gboolean
gst_pnmenc_set_format (GstVideoEncoder * encoder, GstVideoCodecState * state)
{
GstPnmenc *pnmenc;
gboolean ret = TRUE;
GstVideoInfo *info;
GstVideoCodecState *output_state;
const gchar *mime_type = NULL;
pnmenc = GST_PNMENC (encoder);
info = &state->info;
switch (GST_VIDEO_INFO_FORMAT (info)) {
case GST_VIDEO_FORMAT_RGB:
pnmenc->info.max = 255;
pnmenc->info.type = GST_PNM_TYPE_PIXMAP;
mime_type = MIME_PM;
break;
case GST_VIDEO_FORMAT_GRAY8:
pnmenc->info.max = 255;
pnmenc->info.type = GST_PNM_TYPE_GRAYMAP;
mime_type = MIME_GM;
break;
case GST_VIDEO_FORMAT_GRAY16_BE:
case GST_VIDEO_FORMAT_GRAY16_LE:
pnmenc->info.max = 65535;
pnmenc->info.type = GST_PNM_TYPE_GRAYMAP;
mime_type = MIME_GM;
break;
default:
ret = FALSE;
goto done;
}
pnmenc->info.width = GST_VIDEO_INFO_WIDTH (info);
pnmenc->info.height = GST_VIDEO_INFO_HEIGHT (info);
if (pnmenc->input_state)
gst_video_codec_state_unref (pnmenc->input_state);
pnmenc->input_state = gst_video_codec_state_ref (state);
output_state =
gst_video_encoder_set_output_state (encoder,
gst_caps_new_empty_simple (mime_type), state);
gst_video_codec_state_unref (output_state);
done:
return ret;
}
static GstFlowReturn
gst_pnmenc_handle_frame (GstVideoEncoder * encoder, GstVideoCodecFrame * frame)
{
GstPnmenc *pnmenc;
guint size, pixels, bytesize;
GstMapInfo omap;
gchar *header = NULL;
GstVideoInfo *info;
GstFlowReturn ret = GST_FLOW_OK;
guint i_rowstride, o_rowstride;
guint bytes = 0, index, head_size;
guint i, j;
guint maxbytes_per_pixel, str_len;
gchar format_str[4];
GstVideoFrame in_frame;
pnmenc = GST_PNMENC (encoder);
info = &pnmenc->input_state->info;
switch (GST_VIDEO_INFO_FORMAT (info)) {
case GST_VIDEO_FORMAT_RGB:
pixels = size = pnmenc->info.width * pnmenc->info.height * 3;
bytesize = 1;
maxbytes_per_pixel = 4;
str_len = 3;
g_strlcpy (format_str, "%3i", 4);
break;
case GST_VIDEO_FORMAT_GRAY8:
pixels = size = pnmenc->info.width * pnmenc->info.height * 1;
bytesize = 1;
maxbytes_per_pixel = 4;
str_len = 3;
g_strlcpy (format_str, "%3i", 4);
break;
case GST_VIDEO_FORMAT_GRAY16_LE:
case GST_VIDEO_FORMAT_GRAY16_BE:
pixels = pnmenc->info.width * pnmenc->info.height * 1;
bytesize = 2;
size = pixels * bytesize;
maxbytes_per_pixel = 6;
str_len = 5;
g_strlcpy (format_str, "%5i", 4);
break;
default:
ret = FALSE;
goto done;
}
header = g_strdup_printf ("P%i\n%i %i\n%i\n",
pnmenc->info.type + 3 * (1 - pnmenc->info.encoding), pnmenc->info.width,
pnmenc->info.height, pnmenc->info.max);
if (pnmenc->info.encoding == GST_PNM_ENCODING_ASCII) {
/* Per component 4 bytes are used in case of ASCII encoding */
size = size * 4 + size / 20;
size += strlen (header);
frame->output_buffer =
gst_video_encoder_allocate_output_buffer (encoder, (size));
} else {
size += strlen (header);
frame->output_buffer =
gst_video_encoder_allocate_output_buffer (encoder, size);
}
if (gst_buffer_map (frame->output_buffer, &omap, GST_MAP_WRITE) == FALSE) {
ret = GST_FLOW_ERROR;
goto done;
}
if (!gst_video_frame_map (&in_frame, &(pnmenc->input_state->info),
frame->input_buffer, GST_MAP_READ)) {
/* Unmap already mapped buffer */
gst_buffer_unmap (frame->output_buffer, &omap);
ret = GST_FLOW_ERROR;
goto done;
}
/* Copy out the header first */
head_size = strlen (header);
memcpy (omap.data, header, head_size);
if (pnmenc->info.encoding == GST_PNM_ENCODING_ASCII) {
/* We need to convert to ASCII */
/* Convert from gstreamer rowstride to PNM rowstride as we go */
if (pnmenc->info.type == GST_PNM_TYPE_PIXMAP) {
o_rowstride = 3 * pnmenc->info.width;
} else {
o_rowstride = pnmenc->info.width;
}
i_rowstride = GST_VIDEO_FRAME_PLANE_STRIDE (&in_frame, 0);
switch (GST_VIDEO_INFO_FORMAT (info)) {
case GST_VIDEO_FORMAT_RGB:
case GST_VIDEO_FORMAT_GRAY8:
for (i = 0; i < pnmenc->info.height; i++) {
index = i * i_rowstride;
for (j = 0; j < o_rowstride; j++, bytes++, index++) {
g_snprintf ((char *) omap.data + head_size, maxbytes_per_pixel,
format_str, in_frame.map[0].data[index]);
head_size += str_len;
omap.data[head_size++] = ' ';
/* Add new line so that file will not end up with single big line */
if (!((bytes + 1) % 20))
omap.data[head_size++] = '\n';
}
}
break;
case GST_VIDEO_FORMAT_GRAY16_BE:
for (i = 0; i < pnmenc->info.height; i++) {
index = i * i_rowstride;
for (j = 0; j < o_rowstride; j++, bytes++, index += 2) {
g_snprintf ((char *) omap.data + head_size, maxbytes_per_pixel,
format_str, GST_READ_UINT16_BE (in_frame.map[0].data + index));
head_size += str_len;
omap.data[head_size++] = ' ';
/* Add new line so that file will not end up with single big line */
if (!((bytes + 1) % 20))
omap.data[head_size++] = '\n';
}
}
break;
case GST_VIDEO_FORMAT_GRAY16_LE:
for (i = 0; i < pnmenc->info.height; i++) {
index = i * i_rowstride;
for (j = 0; j < o_rowstride; j++, bytes++, index += 2) {
g_snprintf ((char *) omap.data + head_size, maxbytes_per_pixel,
format_str, GST_READ_UINT16_LE (in_frame.map[0].data + index));
head_size += str_len;
omap.data[head_size++] = ' ';
/* Add new line so that file will not end up with single big line */
if (!((bytes + 1) % 20))
omap.data[head_size++] = '\n';
}
}
break;
default:
GST_ERROR_OBJECT (encoder, "Unhandled format %s",
gst_video_format_to_string (GST_VIDEO_INFO_FORMAT (info)));
gst_buffer_unmap (frame->output_buffer, &omap);
gst_video_frame_unmap (&in_frame);
g_free (header);
return GST_FLOW_ERROR;
}
gst_buffer_set_size (frame->output_buffer, head_size);
} else {
guint out_index = head_size;
/* Binary output. 8-bit, or 16-bit BE */
if (pnmenc->info.type == GST_PNM_TYPE_PIXMAP) {
o_rowstride = 3 * pnmenc->info.width * bytesize;
} else {
o_rowstride = pnmenc->info.width * bytesize;
}
i_rowstride = GST_VIDEO_FRAME_PLANE_STRIDE (&in_frame, 0);
switch (GST_VIDEO_INFO_FORMAT (info)) {
case GST_VIDEO_FORMAT_GRAY16_BE:
for (i = 0; i < pnmenc->info.height; i++) {
index = i * i_rowstride;
for (j = 0; j < o_rowstride; j += 2, index += 2) {
guint16 val = GST_READ_UINT16_LE (in_frame.map[0].data + index);
GST_WRITE_UINT16_BE (omap.data + out_index, val);
out_index += 2;
}
}
break;
case GST_VIDEO_FORMAT_GRAY16_LE:
for (i = 0; i < pnmenc->info.height; i++) {
index = i * i_rowstride;
for (j = 0; j < o_rowstride; j += 2, index += 2) {
guint16 val = GST_READ_UINT16_LE (in_frame.map[0].data + index);
GST_WRITE_UINT16_BE (omap.data + out_index, val);
out_index += 2;
}
}
break;
default:
for (i = 0; i < pnmenc->info.height; i++) {
memcpy (omap.data + head_size + o_rowstride * i,
in_frame.map[0].data + i_rowstride * i, o_rowstride);
}
}
}
gst_buffer_unmap (frame->output_buffer, &omap);
gst_video_frame_unmap (&in_frame);
if ((ret = gst_video_encoder_finish_frame (encoder, frame)) != GST_FLOW_OK)
goto done;
done:
g_free (header);
return ret;
}
static void
gst_pnmenc_class_init (GstPnmencClass * klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
GstVideoEncoderClass *venc_class = (GstVideoEncoderClass *) klass;
parent_class = g_type_class_peek_parent (klass);
gobject_class->set_property = gst_pnmenc_set_property;
gobject_class->get_property = gst_pnmenc_get_property;
g_object_class_install_property (gobject_class, GST_PNMENC_PROP_ASCII,
g_param_spec_boolean ("ascii", "ASCII Encoding", "The output will be "
"ASCII encoded", FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
gst_element_class_add_static_pad_template (element_class, &sink_pad_template);
gst_element_class_add_static_pad_template (element_class, &src_pad_template);
gst_element_class_set_static_metadata (element_class, "PNM image encoder",
"Codec/Encoder/Image",
"Encodes images into portable pixmap or graymap (PNM) format",
"Lutz Mueller <lutz@users.sourceforge.net>");
venc_class->set_format = gst_pnmenc_set_format;
venc_class->handle_frame = gst_pnmenc_handle_frame;
gobject_class->finalize = gst_pnmenc_finalize;
}