mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-27 12:11:13 +00:00
427 lines
12 KiB
C
427 lines
12 KiB
C
|
/* GStreamer
|
||
|
* Copyright (C) 2021 Collabora Ltd.
|
||
|
* Author: Nicolas Dufresne <nicolas.dufresne@collabora.com>
|
||
|
*
|
||
|
* 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.
|
||
|
*/
|
||
|
|
||
|
#ifdef HAVE_CONFIG_H
|
||
|
#include "config.h"
|
||
|
#endif
|
||
|
|
||
|
#include <gst/gst.h>
|
||
|
#include <gst/video/video.h>
|
||
|
#include <gio/gio.h>
|
||
|
|
||
|
#include "gstdebugutilsbadelements.h"
|
||
|
#include "gstvideocodectestsink.h"
|
||
|
|
||
|
/**
|
||
|
* SECTION:videocodectestsink
|
||
|
*
|
||
|
* An element that computes the checksum of a video stream and/or writes back its
|
||
|
* raw I420 data ignoring the padding introduced by GStreamer. This element is
|
||
|
* meant to be used for CODEC conformance testing. It also supports producing an I420
|
||
|
* checksum and and can write out a file in I420 layout directly from NV12 input
|
||
|
* data.
|
||
|
*
|
||
|
* The checksum is communicated back to the application just before EOS
|
||
|
* message with an element message of type `conformance/checksum` with the
|
||
|
* following fields:
|
||
|
*
|
||
|
* * "checksum-type" G_TYPE_STRING The checksum type (only MD5 is supported)
|
||
|
* * "checksum" G_TYPE_STRING The checksum as a string
|
||
|
*
|
||
|
* ## Example launch lines
|
||
|
* |[
|
||
|
* gst-launch-1.0 videotestsrc num-buffers=2 ! videocodectestsink location=true-raw.yuv -m
|
||
|
* ]|
|
||
|
*
|
||
|
* Since: 1.20
|
||
|
*/
|
||
|
|
||
|
enum
|
||
|
{
|
||
|
PROP_0,
|
||
|
PROP_LOCATION,
|
||
|
};
|
||
|
|
||
|
struct _GstVideoCodecTestSink
|
||
|
{
|
||
|
GstBaseSink parent;
|
||
|
GChecksumType hash;
|
||
|
|
||
|
/* protect with stream lock */
|
||
|
GstVideoInfo vinfo;
|
||
|
GstFlowReturn (*process) (GstVideoCodecTestSink * self,
|
||
|
GstVideoFrame * frame);
|
||
|
GOutputStream *ostream;
|
||
|
GChecksum *checksum;
|
||
|
|
||
|
/* protect with object lock */
|
||
|
gchar *location;
|
||
|
};
|
||
|
|
||
|
static GstStaticPadTemplate gst_video_codec_test_sink_template =
|
||
|
GST_STATIC_PAD_TEMPLATE ("sink",
|
||
|
GST_PAD_SINK,
|
||
|
GST_PAD_ALWAYS,
|
||
|
GST_STATIC_CAPS ("video/x-raw, format = { I420, I420_10LE, NV12 }"));
|
||
|
|
||
|
#define gst_video_codec_test_sink_parent_class parent_class
|
||
|
G_DEFINE_TYPE (GstVideoCodecTestSink, gst_video_codec_test_sink,
|
||
|
GST_TYPE_BASE_SINK);
|
||
|
GST_ELEMENT_REGISTER_DEFINE (videocodectestsink, "videocodectestsink",
|
||
|
GST_RANK_NONE, gst_video_codec_test_sink_get_type ());
|
||
|
|
||
|
static void
|
||
|
gst_video_codec_test_sink_set_property (GObject * object, guint prop_id,
|
||
|
const GValue * value, GParamSpec * pspec)
|
||
|
{
|
||
|
GstVideoCodecTestSink *self = GST_VIDEO_CODEC_TEST_SINK (object);
|
||
|
|
||
|
GST_OBJECT_LOCK (self);
|
||
|
|
||
|
switch (prop_id) {
|
||
|
case PROP_LOCATION:
|
||
|
g_free (self->location);
|
||
|
self->location = g_value_dup_string (value);
|
||
|
break;
|
||
|
default:
|
||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
GST_OBJECT_UNLOCK (self);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
gst_video_codec_test_sink_get_property (GObject * object, guint prop_id,
|
||
|
GValue * value, GParamSpec * pspec)
|
||
|
{
|
||
|
GstVideoCodecTestSink *self = GST_VIDEO_CODEC_TEST_SINK (object);
|
||
|
|
||
|
GST_OBJECT_LOCK (self);
|
||
|
|
||
|
switch (prop_id) {
|
||
|
case PROP_LOCATION:
|
||
|
g_value_set_string (value, self->location);
|
||
|
break;
|
||
|
default:
|
||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
GST_OBJECT_UNLOCK (self);
|
||
|
}
|
||
|
|
||
|
static gboolean
|
||
|
gst_video_codec_test_sink_start (GstBaseSink * sink)
|
||
|
{
|
||
|
GstVideoCodecTestSink *self = GST_VIDEO_CODEC_TEST_SINK (sink);
|
||
|
GError *error = NULL;
|
||
|
GFile *file = NULL;
|
||
|
gboolean ret = TRUE;
|
||
|
|
||
|
GST_OBJECT_LOCK (self);
|
||
|
|
||
|
self->checksum = g_checksum_new (self->hash);
|
||
|
if (self->location)
|
||
|
file = g_file_new_for_path (self->location);
|
||
|
|
||
|
GST_OBJECT_UNLOCK (self);
|
||
|
|
||
|
if (file) {
|
||
|
self->ostream = G_OUTPUT_STREAM (g_file_replace (file, NULL, FALSE,
|
||
|
G_FILE_CREATE_REPLACE_DESTINATION, NULL, &error));
|
||
|
if (!self->ostream) {
|
||
|
GST_ELEMENT_ERROR (self, RESOURCE, WRITE,
|
||
|
("Failed to open '%s' for writing.", self->location),
|
||
|
("Open failed failed: %s", error->message));
|
||
|
g_error_free (error);
|
||
|
ret = FALSE;
|
||
|
}
|
||
|
|
||
|
g_object_unref (file);
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static gboolean
|
||
|
gst_video_codec_test_sink_stop (GstBaseSink * sink)
|
||
|
{
|
||
|
GstVideoCodecTestSink *self = GST_VIDEO_CODEC_TEST_SINK (sink);
|
||
|
|
||
|
g_checksum_free (self->checksum);
|
||
|
self->checksum = NULL;
|
||
|
|
||
|
if (self->ostream) {
|
||
|
GError *error = NULL;
|
||
|
|
||
|
if (!g_output_stream_close (self->ostream, NULL, &error)) {
|
||
|
GST_ELEMENT_WARNING (self, RESOURCE, CLOSE,
|
||
|
("Did not close '%s' properly", self->location),
|
||
|
("Failed to close stream: %s", error->message));
|
||
|
}
|
||
|
|
||
|
g_clear_object (&self->ostream);
|
||
|
}
|
||
|
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
static GstFlowReturn
|
||
|
gst_video_codec_test_sink_process_data (GstVideoCodecTestSink * self,
|
||
|
const guchar * data, gssize length)
|
||
|
{
|
||
|
GError *error = NULL;
|
||
|
|
||
|
g_checksum_update (self->checksum, data, length);
|
||
|
|
||
|
if (!self->ostream)
|
||
|
return GST_FLOW_OK;
|
||
|
|
||
|
if (!g_output_stream_write_all (self->ostream, data, length, NULL, NULL,
|
||
|
&error)) {
|
||
|
GST_ELEMENT_ERROR (self, RESOURCE, WRITE,
|
||
|
("Failed to write video data into '%s'", self->location),
|
||
|
("Writing %" G_GSIZE_FORMAT " bytes failed: %s", length,
|
||
|
error->message));
|
||
|
g_error_free (error);
|
||
|
return GST_FLOW_ERROR;
|
||
|
}
|
||
|
|
||
|
return GST_FLOW_OK;
|
||
|
}
|
||
|
|
||
|
static GstFlowReturn
|
||
|
gst_video_codec_test_sink_process_i420 (GstVideoCodecTestSink * self,
|
||
|
GstVideoFrame * frame)
|
||
|
{
|
||
|
guint plane;
|
||
|
|
||
|
for (plane = 0; plane < 3; plane++) {
|
||
|
gint y;
|
||
|
guint stride;
|
||
|
const guchar *data;
|
||
|
|
||
|
stride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, plane);
|
||
|
data = GST_VIDEO_FRAME_PLANE_DATA (frame, plane);
|
||
|
|
||
|
for (y = 0; y < GST_VIDEO_INFO_COMP_HEIGHT (&self->vinfo, plane); y++) {
|
||
|
gsize length = GST_VIDEO_INFO_COMP_WIDTH (&self->vinfo, plane) *
|
||
|
GST_VIDEO_INFO_COMP_PSTRIDE (&self->vinfo, plane);
|
||
|
GstFlowReturn ret;
|
||
|
|
||
|
ret = gst_video_codec_test_sink_process_data (self, data, length);
|
||
|
if (ret != GST_FLOW_OK)
|
||
|
return ret;
|
||
|
|
||
|
data += stride;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return GST_FLOW_OK;
|
||
|
}
|
||
|
|
||
|
static GstFlowReturn
|
||
|
gst_video_codec_test_sink_process_nv12 (GstVideoCodecTestSink * self,
|
||
|
GstVideoFrame * frame)
|
||
|
{
|
||
|
gint x, y, comp;
|
||
|
guint stride;
|
||
|
const guchar *data;
|
||
|
|
||
|
stride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0);
|
||
|
data = GST_VIDEO_FRAME_PLANE_DATA (frame, 0);
|
||
|
|
||
|
for (y = 0; y < GST_VIDEO_INFO_HEIGHT (&self->vinfo); y++) {
|
||
|
gsize length = GST_VIDEO_INFO_WIDTH (&self->vinfo);
|
||
|
GstFlowReturn ret;
|
||
|
|
||
|
ret = gst_video_codec_test_sink_process_data (self, data, length);
|
||
|
if (ret != GST_FLOW_OK)
|
||
|
return ret;
|
||
|
|
||
|
data += stride;
|
||
|
}
|
||
|
|
||
|
/* Deinterleave the UV plane */
|
||
|
stride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 1);
|
||
|
|
||
|
for (comp = 0; comp < 2; comp++) {
|
||
|
data = GST_VIDEO_FRAME_PLANE_DATA (frame, 1);
|
||
|
|
||
|
for (y = 0; y < GST_VIDEO_INFO_COMP_HEIGHT (&self->vinfo, 1); y++) {
|
||
|
guint width = GST_ROUND_UP_2 (GST_VIDEO_INFO_WIDTH (&self->vinfo)) / 2;
|
||
|
|
||
|
for (x = 0; x < width; x++) {
|
||
|
GstFlowReturn ret;
|
||
|
|
||
|
ret = gst_video_codec_test_sink_process_data (self,
|
||
|
data + 2 * x + comp, 1);
|
||
|
|
||
|
if (ret != GST_FLOW_OK)
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
data += stride;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return GST_FLOW_OK;
|
||
|
}
|
||
|
|
||
|
static GstFlowReturn
|
||
|
gst_video_codec_test_sink_render (GstBaseSink * sink, GstBuffer * buffer)
|
||
|
{
|
||
|
GstVideoCodecTestSink *self = GST_VIDEO_CODEC_TEST_SINK (sink);
|
||
|
GstVideoFrame frame;
|
||
|
|
||
|
if (!gst_video_frame_map (&frame, &self->vinfo, buffer, GST_MAP_READ))
|
||
|
return GST_FLOW_ERROR;
|
||
|
|
||
|
self->process (self, &frame);
|
||
|
|
||
|
gst_video_frame_unmap (&frame);
|
||
|
return GST_FLOW_OK;
|
||
|
}
|
||
|
|
||
|
static gboolean
|
||
|
gst_video_codec_test_sink_set_caps (GstBaseSink * sink, GstCaps * caps)
|
||
|
{
|
||
|
GstVideoCodecTestSink *self = GST_VIDEO_CODEC_TEST_SINK (sink);
|
||
|
|
||
|
if (!gst_video_info_from_caps (&self->vinfo, caps))
|
||
|
return FALSE;
|
||
|
|
||
|
switch (GST_VIDEO_INFO_FORMAT (&self->vinfo)) {
|
||
|
case GST_VIDEO_FORMAT_I420:
|
||
|
case GST_VIDEO_FORMAT_I420_10LE:
|
||
|
self->process = gst_video_codec_test_sink_process_i420;
|
||
|
break;
|
||
|
case GST_VIDEO_FORMAT_NV12:
|
||
|
self->process = gst_video_codec_test_sink_process_nv12;
|
||
|
break;
|
||
|
default:
|
||
|
g_assert_not_reached ();
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
static gboolean
|
||
|
gst_video_codec_test_sink_propose_allocation (GstBaseSink * sink,
|
||
|
GstQuery * query)
|
||
|
{
|
||
|
gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, NULL);
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
static gboolean
|
||
|
gst_video_codec_test_sink_event (GstBaseSink * sink, GstEvent * event)
|
||
|
{
|
||
|
GstVideoCodecTestSink *self = GST_VIDEO_CODEC_TEST_SINK (sink);
|
||
|
|
||
|
if (event->type == GST_EVENT_EOS) {
|
||
|
const gchar *checksum_type = "UNKNOWN";
|
||
|
|
||
|
switch (self->hash) {
|
||
|
case G_CHECKSUM_MD5:
|
||
|
checksum_type = "MD5";
|
||
|
break;
|
||
|
case G_CHECKSUM_SHA1:
|
||
|
checksum_type = "SHA1";
|
||
|
break;
|
||
|
case G_CHECKSUM_SHA256:
|
||
|
checksum_type = "SHA256";
|
||
|
break;
|
||
|
case G_CHECKSUM_SHA512:
|
||
|
checksum_type = "SHA512";
|
||
|
break;
|
||
|
case G_CHECKSUM_SHA384:
|
||
|
checksum_type = "SHA384";
|
||
|
break;
|
||
|
default:
|
||
|
g_assert_not_reached ();
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
gst_element_post_message (GST_ELEMENT (self),
|
||
|
gst_message_new_element (GST_OBJECT (self),
|
||
|
gst_structure_new ("conformance/checksum", "checksum-type",
|
||
|
G_TYPE_STRING, checksum_type, "checksum", G_TYPE_STRING,
|
||
|
g_checksum_get_string (self->checksum), NULL)));
|
||
|
g_checksum_reset (self->checksum);
|
||
|
}
|
||
|
|
||
|
return GST_BASE_SINK_CLASS (parent_class)->event (sink, event);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
gst_video_codec_test_sink_init (GstVideoCodecTestSink * sink)
|
||
|
{
|
||
|
gst_base_sink_set_sync (GST_BASE_SINK (sink), FALSE);
|
||
|
sink->hash = G_CHECKSUM_MD5;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
gst_video_codec_test_sink_finalize (GObject * object)
|
||
|
{
|
||
|
GstVideoCodecTestSink *self = GST_VIDEO_CODEC_TEST_SINK (object);
|
||
|
|
||
|
g_free (self->location);
|
||
|
|
||
|
G_OBJECT_CLASS (parent_class)->finalize (object);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
gst_video_codec_test_sink_class_init (GstVideoCodecTestSinkClass * klass)
|
||
|
{
|
||
|
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
||
|
GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
|
||
|
GstBaseSinkClass *base_sink_class = GST_BASE_SINK_CLASS (klass);
|
||
|
|
||
|
gobject_class->set_property = gst_video_codec_test_sink_set_property;
|
||
|
gobject_class->get_property = gst_video_codec_test_sink_get_property;
|
||
|
gobject_class->finalize = gst_video_codec_test_sink_finalize;
|
||
|
|
||
|
base_sink_class->start = GST_DEBUG_FUNCPTR (gst_video_codec_test_sink_start);
|
||
|
base_sink_class->stop = GST_DEBUG_FUNCPTR (gst_video_codec_test_sink_stop);
|
||
|
base_sink_class->render =
|
||
|
GST_DEBUG_FUNCPTR (gst_video_codec_test_sink_render);
|
||
|
base_sink_class->set_caps =
|
||
|
GST_DEBUG_FUNCPTR (gst_video_codec_test_sink_set_caps);
|
||
|
base_sink_class->propose_allocation =
|
||
|
GST_DEBUG_FUNCPTR (gst_video_codec_test_sink_propose_allocation);
|
||
|
base_sink_class->event = GST_DEBUG_FUNCPTR (gst_video_codec_test_sink_event);
|
||
|
|
||
|
gst_element_class_add_static_pad_template (element_class,
|
||
|
&gst_video_codec_test_sink_template);
|
||
|
|
||
|
g_object_class_install_property (gobject_class, PROP_LOCATION,
|
||
|
g_param_spec_string ("location", "Location",
|
||
|
"File path to store non-padded I420 stream (optional).", NULL,
|
||
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||
|
|
||
|
gst_element_class_set_static_metadata (element_class,
|
||
|
"Video CODEC Test Sink", "Debug/video/Sink",
|
||
|
"Sink to test video CODEC conformance",
|
||
|
"Nicolas Dufresne <nicolas.dufresne@collabora.com");
|
||
|
}
|