mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-09-09 13:48:55 +00:00
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
401 lines
13 KiB
C
401 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;
|
|
}
|