gstreamer/ext/dv/gstdvdemux.c
Sebastian Dröge 83d5797a58 dv: Use correct pixel-aspect-ratio values
The previous ones resulted in odd display aspect ratios and were different
from the ones used by e.g. ffmpeg. The new ones now result in display aspect
ratios of 4:3 and 16:9.

https://bugzilla.gnome.org/show_bug.cgi?id=765946
2016-05-05 13:37:27 +03:00

1908 lines
56 KiB
C

/* GStreamer
* Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
* <2005> Wim Taymans <wim@fluendo.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 <string.h>
#include <math.h>
#include <gst/audio/audio.h>
#include "gstdvdemux.h"
#include "gstsmptetimecode.h"
/**
* SECTION:element-dvdemux
*
* dvdemux splits raw DV into its audio and video components. The audio will be
* decoded raw samples and the video will be encoded DV video.
*
* This element can operate in both push and pull mode depending on the
* capabilities of the upstream peer.
*
* <refsect2>
* <title>Example launch line</title>
* |[
* gst-launch-1.0 filesrc location=test.dv ! dvdemux name=demux ! queue ! audioconvert ! alsasink demux. ! queue ! dvdec ! xvimagesink
* ]| This pipeline decodes and renders the raw DV stream to an audio and a videosink.
* </refsect2>
*/
/* DV output has two modes, normal and wide. The resolution is the same in both
* cases: 720 pixels wide by 576 pixels tall in PAL format, and 720x480 for
* NTSC.
*
* Each of the modes has its own pixel aspect ratio, which is fixed in practice
* by ITU-R BT.601 (also known as "CCIR-601" or "Rec.601"). Or so claims a
* reference that I culled from the reliable "internet",
* http://www.mir.com/DMG/aspect.html. Normal PAL is 59/54 and normal NTSC is
* 10/11. Because the pixel resolution is the same for both cases, we can get
* the pixel aspect ratio for wide recordings by multiplying by the ratio of
* display aspect ratios, 16/9 (for wide) divided by 4/3 (for normal):
*
* Wide NTSC: 10/11 * (16/9)/(4/3) = 40/33
* Wide PAL: 59/54 * (16/9)/(4/3) = 118/81
*
* However, the pixel resolution coming out of a DV source does not combine with
* the standard pixel aspect ratios to give a proper display aspect ratio. An
* image 480 pixels tall, with a 4:3 display aspect ratio, will be 768 pixels
* wide. But, if we take the normal PAL aspect ratio of 59/54, and multiply it
* with the width of the DV image (720 pixels), we get 786.666..., which is
* nonintegral and too wide. The camera is not outputting a 4:3 image.
*
* If the video sink for this stream has fixed dimensions (such as for
* fullscreen playback, or for a java applet in a web page), you then have two
* choices. Either you show the whole image, but pad the image with black
* borders on the top and bottom (like watching a widescreen video on a 4:3
* device), or you crop the video to the proper ratio. Apparently the latter is
* the standard practice.
*
* For its part, GStreamer is concerned with accuracy and preservation of
* information. This element outputs the 720x576 or 720x480 video that it
* recieves, noting the proper aspect ratio. This should not be a problem for
* windowed applications, which can change size to fit the video. Applications
* with fixed size requirements should decide whether to crop or pad which
* an element such as videobox can do.
*/
#define NTSC_HEIGHT 480
#define NTSC_BUFFER 120000
#define NTSC_FRAMERATE_NUMERATOR 30000
#define NTSC_FRAMERATE_DENOMINATOR 1001
#define PAL_HEIGHT 576
#define PAL_BUFFER 144000
#define PAL_FRAMERATE_NUMERATOR 25
#define PAL_FRAMERATE_DENOMINATOR 1
#define PAL_NORMAL_PAR_X 16
#define PAL_NORMAL_PAR_Y 15
#define PAL_WIDE_PAR_X 64
#define PAL_WIDE_PAR_Y 45
#define NTSC_NORMAL_PAR_X 8
#define NTSC_NORMAL_PAR_Y 9
#define NTSC_WIDE_PAR_X 32
#define NTSC_WIDE_PAR_Y 27
GST_DEBUG_CATEGORY_STATIC (dvdemux_debug);
#define GST_CAT_DEFAULT dvdemux_debug
static GstStaticPadTemplate sink_temp = GST_STATIC_PAD_TEMPLATE ("sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS ("video/x-dv, systemstream = (boolean) true")
);
static GstStaticPadTemplate video_src_temp = GST_STATIC_PAD_TEMPLATE ("video",
GST_PAD_SRC,
GST_PAD_SOMETIMES,
GST_STATIC_CAPS ("video/x-dv, systemstream = (boolean) false")
);
static GstStaticPadTemplate audio_src_temp = GST_STATIC_PAD_TEMPLATE ("audio",
GST_PAD_SRC,
GST_PAD_SOMETIMES,
GST_STATIC_CAPS ("audio/x-raw, "
"format = (string) " GST_AUDIO_NE (S16) ", "
"layout = (string) interleaved, "
"rate = (int) { 32000, 44100, 48000 }, " "channels = (int) {2, 4}")
);
#define gst_dvdemux_parent_class parent_class
G_DEFINE_TYPE (GstDVDemux, gst_dvdemux, GST_TYPE_ELEMENT);
static void gst_dvdemux_finalize (GObject * object);
/* query functions */
static gboolean gst_dvdemux_src_query (GstPad * pad, GstObject * parent,
GstQuery * query);
static gboolean gst_dvdemux_sink_query (GstPad * pad, GstObject * parent,
GstQuery * query);
/* convert functions */
static gboolean gst_dvdemux_sink_convert (GstDVDemux * demux,
GstFormat src_format, gint64 src_value, GstFormat dest_format,
gint64 * dest_value);
static gboolean gst_dvdemux_src_convert (GstDVDemux * demux, GstPad * pad,
GstFormat src_format, gint64 src_value, GstFormat dest_format,
gint64 * dest_value);
/* event functions */
static gboolean gst_dvdemux_send_event (GstElement * element, GstEvent * event);
static gboolean gst_dvdemux_handle_src_event (GstPad * pad, GstObject * parent,
GstEvent * event);
static gboolean gst_dvdemux_handle_sink_event (GstPad * pad, GstObject * parent,
GstEvent * event);
/* scheduling functions */
static void gst_dvdemux_loop (GstPad * pad);
static GstFlowReturn gst_dvdemux_flush (GstDVDemux * dvdemux);
static GstFlowReturn gst_dvdemux_chain (GstPad * pad, GstObject * parent,
GstBuffer * buffer);
/* state change functions */
static gboolean gst_dvdemux_sink_activate (GstPad * sinkpad,
GstObject * parent);
static gboolean gst_dvdemux_sink_activate_mode (GstPad * sinkpad,
GstObject * parent, GstPadMode mode, gboolean active);
static GstStateChangeReturn gst_dvdemux_change_state (GstElement * element,
GstStateChange transition);
static void
gst_dvdemux_class_init (GstDVDemuxClass * klass)
{
GObjectClass *gobject_class;
GstElementClass *gstelement_class;
gobject_class = (GObjectClass *) klass;
gstelement_class = (GstElementClass *) klass;
gobject_class->finalize = gst_dvdemux_finalize;
gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_dvdemux_change_state);
gstelement_class->send_event = GST_DEBUG_FUNCPTR (gst_dvdemux_send_event);
gst_element_class_add_static_pad_template (gstelement_class, &sink_temp);
gst_element_class_add_static_pad_template (gstelement_class, &video_src_temp);
gst_element_class_add_static_pad_template (gstelement_class, &audio_src_temp);
gst_element_class_set_static_metadata (gstelement_class,
"DV system stream demuxer", "Codec/Demuxer",
"Uses libdv to separate DV audio from DV video (libdv.sourceforge.net)",
"Erik Walthinsen <omega@cse.ogi.edu>, Wim Taymans <wim@fluendo.com>");
GST_DEBUG_CATEGORY_INIT (dvdemux_debug, "dvdemux", 0, "DV demuxer element");
}
static void
gst_dvdemux_init (GstDVDemux * dvdemux)
{
gint i;
dvdemux->sinkpad = gst_pad_new_from_static_template (&sink_temp, "sink");
/* we can operate in pull and push mode so we install
* a custom activate function */
gst_pad_set_activate_function (dvdemux->sinkpad,
GST_DEBUG_FUNCPTR (gst_dvdemux_sink_activate));
/* the function to activate in push mode */
gst_pad_set_activatemode_function (dvdemux->sinkpad,
GST_DEBUG_FUNCPTR (gst_dvdemux_sink_activate_mode));
/* for push mode, this is the chain function */
gst_pad_set_chain_function (dvdemux->sinkpad,
GST_DEBUG_FUNCPTR (gst_dvdemux_chain));
/* handling events (in push mode only) */
gst_pad_set_event_function (dvdemux->sinkpad,
GST_DEBUG_FUNCPTR (gst_dvdemux_handle_sink_event));
/* query functions */
gst_pad_set_query_function (dvdemux->sinkpad,
GST_DEBUG_FUNCPTR (gst_dvdemux_sink_query));
/* now add the pad */
gst_element_add_pad (GST_ELEMENT (dvdemux), dvdemux->sinkpad);
dvdemux->adapter = gst_adapter_new ();
/* we need 4 temp buffers for audio decoding which are of a static
* size and which we can allocate here */
for (i = 0; i < 4; i++) {
dvdemux->audio_buffers[i] =
(gint16 *) g_malloc (DV_AUDIO_MAX_SAMPLES * sizeof (gint16));
}
}
static void
gst_dvdemux_finalize (GObject * object)
{
GstDVDemux *dvdemux;
gint i;
dvdemux = GST_DVDEMUX (object);
g_object_unref (dvdemux->adapter);
/* clean up temp audio buffers */
for (i = 0; i < 4; i++) {
g_free (dvdemux->audio_buffers[i]);
}
G_OBJECT_CLASS (parent_class)->finalize (object);
}
/* reset to default values before starting streaming */
static void
gst_dvdemux_reset (GstDVDemux * dvdemux)
{
dvdemux->frame_offset = 0;
dvdemux->audio_offset = 0;
dvdemux->video_offset = 0;
g_atomic_int_set (&dvdemux->found_header, 0);
dvdemux->frame_len = -1;
dvdemux->need_segment = FALSE;
dvdemux->new_media = FALSE;
dvdemux->framerate_numerator = 0;
dvdemux->framerate_denominator = 0;
dvdemux->height = 0;
dvdemux->frequency = 0;
dvdemux->channels = 0;
dvdemux->wide = FALSE;
gst_segment_init (&dvdemux->byte_segment, GST_FORMAT_BYTES);
gst_segment_init (&dvdemux->time_segment, GST_FORMAT_TIME);
dvdemux->have_group_id = FALSE;
dvdemux->group_id = G_MAXUINT;
}
static gboolean
have_group_id (GstDVDemux * demux)
{
GstEvent *event;
event = gst_pad_get_sticky_event (demux->sinkpad, GST_EVENT_STREAM_START, 0);
if (event) {
if (gst_event_parse_group_id (event, &demux->group_id))
demux->have_group_id = TRUE;
else
demux->have_group_id = FALSE;
gst_event_unref (event);
} else if (!demux->have_group_id) {
demux->have_group_id = TRUE;
demux->group_id = gst_util_group_id_next ();
}
return demux->have_group_id;
}
static GstPad *
gst_dvdemux_add_pad (GstDVDemux * dvdemux, GstStaticPadTemplate * template,
GstCaps * caps)
{
gboolean no_more_pads;
GstPad *pad;
GstEvent *event;
gchar *stream_id;
gchar rec_datetime[40];
GstDateTime *rec_dt;
pad = gst_pad_new_from_static_template (template, template->name_template);
gst_pad_set_query_function (pad, GST_DEBUG_FUNCPTR (gst_dvdemux_src_query));
gst_pad_set_event_function (pad,
GST_DEBUG_FUNCPTR (gst_dvdemux_handle_src_event));
gst_pad_use_fixed_caps (pad);
gst_pad_set_active (pad, TRUE);
stream_id =
gst_pad_create_stream_id (pad,
GST_ELEMENT_CAST (dvdemux),
template == &video_src_temp ? "video" : "audio");
event = gst_event_new_stream_start (stream_id);
if (have_group_id (dvdemux))
gst_event_set_group_id (event, dvdemux->group_id);
gst_pad_push_event (pad, event);
g_free (stream_id);
gst_pad_set_caps (pad, caps);
gst_pad_push_event (pad, gst_event_new_segment (&dvdemux->time_segment));
gst_element_add_pad (GST_ELEMENT (dvdemux), pad);
no_more_pads =
(dvdemux->videosrcpad != NULL && template == &audio_src_temp) ||
(dvdemux->audiosrcpad != NULL && template == &video_src_temp);
if (no_more_pads)
gst_element_no_more_pads (GST_ELEMENT (dvdemux));
if (no_more_pads) {
GstTagList *tags;
tags = gst_tag_list_new (GST_TAG_CONTAINER_FORMAT, "DV", NULL);
gst_tag_list_set_scope (tags, GST_TAG_SCOPE_GLOBAL);
if (dv_get_recording_datetime (dvdemux->decoder, rec_datetime)) {
rec_dt = gst_date_time_new_from_iso8601_string (rec_datetime);
if (rec_dt) {
gst_tag_list_add (tags, GST_TAG_MERGE_REPLACE, GST_TAG_DATE_TIME,
rec_dt, NULL);
gst_date_time_unref (rec_dt);
}
}
if (dvdemux->videosrcpad)
gst_pad_push_event (dvdemux->videosrcpad,
gst_event_new_tag (gst_tag_list_ref (tags)));
if (dvdemux->audiosrcpad)
gst_pad_push_event (dvdemux->audiosrcpad,
gst_event_new_tag (gst_tag_list_ref (tags)));
gst_tag_list_unref (tags);
}
return pad;
}
static void
gst_dvdemux_remove_pads (GstDVDemux * dvdemux)
{
if (dvdemux->videosrcpad) {
gst_element_remove_pad (GST_ELEMENT (dvdemux), dvdemux->videosrcpad);
dvdemux->videosrcpad = NULL;
}
if (dvdemux->audiosrcpad) {
gst_element_remove_pad (GST_ELEMENT (dvdemux), dvdemux->audiosrcpad);
dvdemux->audiosrcpad = NULL;
}
}
static gboolean
gst_dvdemux_src_convert (GstDVDemux * dvdemux, GstPad * pad,
GstFormat src_format, gint64 src_value, GstFormat dest_format,
gint64 * dest_value)
{
gboolean res = TRUE;
if (dest_format == src_format || src_value == -1) {
*dest_value = src_value;
goto done;
}
if (dvdemux->frame_len <= 0)
goto error;
if (dvdemux->decoder == NULL)
goto error;
GST_INFO_OBJECT (pad,
"src_value:%" G_GINT64_FORMAT ", src_format:%d, dest_format:%d",
src_value, src_format, dest_format);
switch (src_format) {
case GST_FORMAT_BYTES:
switch (dest_format) {
case GST_FORMAT_DEFAULT:
if (pad == dvdemux->videosrcpad)
*dest_value = src_value / dvdemux->frame_len;
else if (pad == dvdemux->audiosrcpad)
*dest_value = src_value / (2 * dvdemux->channels);
break;
case GST_FORMAT_TIME:
if (pad == dvdemux->videosrcpad)
*dest_value = gst_util_uint64_scale (src_value,
GST_SECOND * dvdemux->framerate_denominator,
dvdemux->frame_len * dvdemux->framerate_numerator);
else if (pad == dvdemux->audiosrcpad)
*dest_value = gst_util_uint64_scale_int (src_value, GST_SECOND,
2 * dvdemux->frequency * dvdemux->channels);
break;
default:
res = FALSE;
}
break;
case GST_FORMAT_TIME:
switch (dest_format) {
case GST_FORMAT_BYTES:
if (pad == dvdemux->videosrcpad)
*dest_value = gst_util_uint64_scale (src_value,
dvdemux->frame_len * dvdemux->framerate_numerator,
dvdemux->framerate_denominator * GST_SECOND);
else if (pad == dvdemux->audiosrcpad)
*dest_value = gst_util_uint64_scale_int (src_value,
2 * dvdemux->frequency * dvdemux->channels, GST_SECOND);
break;
case GST_FORMAT_DEFAULT:
if (pad == dvdemux->videosrcpad) {
if (src_value)
*dest_value = gst_util_uint64_scale (src_value,
dvdemux->framerate_numerator,
dvdemux->framerate_denominator * GST_SECOND);
else
*dest_value = 0;
} else if (pad == dvdemux->audiosrcpad) {
*dest_value = gst_util_uint64_scale (src_value,
dvdemux->frequency, GST_SECOND);
}
break;
default:
res = FALSE;
}
break;
case GST_FORMAT_DEFAULT:
switch (dest_format) {
case GST_FORMAT_TIME:
if (pad == dvdemux->videosrcpad) {
*dest_value = gst_util_uint64_scale (src_value,
GST_SECOND * dvdemux->framerate_denominator,
dvdemux->framerate_numerator);
} else if (pad == dvdemux->audiosrcpad) {
if (src_value)
*dest_value = gst_util_uint64_scale (src_value,
GST_SECOND, dvdemux->frequency);
else
*dest_value = 0;
}
break;
case GST_FORMAT_BYTES:
if (pad == dvdemux->videosrcpad) {
*dest_value = src_value * dvdemux->frame_len;
} else if (pad == dvdemux->audiosrcpad) {
*dest_value = src_value * 2 * dvdemux->channels;
}
break;
default:
res = FALSE;
}
break;
default:
res = FALSE;
}
done:
GST_INFO_OBJECT (pad,
"Result : dest_format:%d, dest_value:%" G_GINT64_FORMAT ", res:%d",
dest_format, *dest_value, res);
return res;
/* ERRORS */
error:
{
GST_INFO ("source conversion failed");
return FALSE;
}
}
static gboolean
gst_dvdemux_sink_convert (GstDVDemux * dvdemux, GstFormat src_format,
gint64 src_value, GstFormat dest_format, gint64 * dest_value)
{
gboolean res = TRUE;
GST_DEBUG_OBJECT (dvdemux, "%d -> %d", src_format, dest_format);
GST_INFO_OBJECT (dvdemux,
"src_value:%" G_GINT64_FORMAT ", src_format:%d, dest_format:%d",
src_value, src_format, dest_format);
if (dest_format == src_format || src_value == -1) {
*dest_value = src_value;
goto done;
}
if (dvdemux->frame_len <= 0)
goto error;
switch (src_format) {
case GST_FORMAT_BYTES:
switch (dest_format) {
case GST_FORMAT_TIME:
{
guint64 frame;
/* get frame number, rounds down so don't combine this
* line and the next line. */
frame = src_value / dvdemux->frame_len;
*dest_value = gst_util_uint64_scale (frame,
GST_SECOND * dvdemux->framerate_denominator,
dvdemux->framerate_numerator);
break;
}
default:
res = FALSE;
}
break;
case GST_FORMAT_TIME:
switch (dest_format) {
case GST_FORMAT_BYTES:
{
guint64 frame;
/* calculate the frame */
frame =
gst_util_uint64_scale (src_value, dvdemux->framerate_numerator,
dvdemux->framerate_denominator * GST_SECOND);
/* calculate the offset from the rounded frame */
*dest_value = frame * dvdemux->frame_len;
break;
}
default:
res = FALSE;
}
break;
default:
res = FALSE;
}
GST_INFO_OBJECT (dvdemux,
"Result : dest_format:%d, dest_value:%" G_GINT64_FORMAT ", res:%d",
dest_format, *dest_value, res);
done:
return res;
error:
{
GST_INFO_OBJECT (dvdemux, "sink conversion failed");
return FALSE;
}
}
static gboolean
gst_dvdemux_src_query (GstPad * pad, GstObject * parent, GstQuery * query)
{
gboolean res = TRUE;
GstDVDemux *dvdemux;
dvdemux = GST_DVDEMUX (parent);
switch (GST_QUERY_TYPE (query)) {
case GST_QUERY_POSITION:
{
GstFormat format;
gint64 cur;
/* get target format */
gst_query_parse_position (query, &format, NULL);
/* bring the position to the requested format. */
if (!(res = gst_dvdemux_src_convert (dvdemux, pad,
GST_FORMAT_TIME, dvdemux->time_segment.position,
format, &cur)))
goto error;
gst_query_set_position (query, format, cur);
break;
}
case GST_QUERY_DURATION:
{
GstFormat format;
gint64 end;
GstQuery *pquery;
/* First ask the peer in the original format */
if (!gst_pad_peer_query (dvdemux->sinkpad, query)) {
/* get target format */
gst_query_parse_duration (query, &format, NULL);
/* Now ask the peer in BYTES format and try to convert */
pquery = gst_query_new_duration (GST_FORMAT_BYTES);
if (!gst_pad_peer_query (dvdemux->sinkpad, pquery)) {
gst_query_unref (pquery);
goto error;
}
/* get peer total length */
gst_query_parse_duration (pquery, NULL, &end);
gst_query_unref (pquery);
/* convert end to requested format */
if (end != -1) {
if (!(res = gst_dvdemux_sink_convert (dvdemux,
GST_FORMAT_BYTES, end, format, &end))) {
goto error;
}
gst_query_set_duration (query, format, end);
}
}
break;
}
case GST_QUERY_CONVERT:
{
GstFormat src_fmt, dest_fmt;
gint64 src_val, dest_val;
gst_query_parse_convert (query, &src_fmt, &src_val, &dest_fmt, &dest_val);
if (!(res =
gst_dvdemux_src_convert (dvdemux, pad, src_fmt, src_val,
dest_fmt, &dest_val)))
goto error;
gst_query_set_convert (query, src_fmt, src_val, dest_fmt, dest_val);
break;
}
default:
res = gst_pad_query_default (pad, parent, query);
break;
}
return res;
/* ERRORS */
error:
{
GST_DEBUG ("error source query");
return FALSE;
}
}
static gboolean
gst_dvdemux_sink_query (GstPad * pad, GstObject * parent, GstQuery * query)
{
gboolean res = TRUE;
GstDVDemux *dvdemux;
dvdemux = GST_DVDEMUX (parent);
switch (GST_QUERY_TYPE (query)) {
case GST_QUERY_CONVERT:
{
GstFormat src_fmt, dest_fmt;
gint64 src_val, dest_val;
gst_query_parse_convert (query, &src_fmt, &src_val, &dest_fmt, &dest_val);
if (!(res =
gst_dvdemux_sink_convert (dvdemux, src_fmt, src_val, dest_fmt,
&dest_val)))
goto error;
gst_query_set_convert (query, src_fmt, src_val, dest_fmt, dest_val);
break;
}
default:
res = gst_pad_query_default (pad, parent, query);
break;
}
return res;
/* ERRORS */
error:
{
GST_DEBUG ("error handling sink query");
return FALSE;
}
}
/* takes ownership of the event */
static gboolean
gst_dvdemux_push_event (GstDVDemux * dvdemux, GstEvent * event)
{
gboolean res = FALSE;
if (dvdemux->videosrcpad) {
gst_event_ref (event);
res |= gst_pad_push_event (dvdemux->videosrcpad, event);
}
if (dvdemux->audiosrcpad)
res |= gst_pad_push_event (dvdemux->audiosrcpad, event);
else {
gst_event_unref (event);
res = TRUE;
}
return res;
}
static gboolean
gst_dvdemux_handle_sink_event (GstPad * pad, GstObject * parent,
GstEvent * event)
{
GstDVDemux *dvdemux = GST_DVDEMUX (parent);
gboolean res = TRUE;
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_FLUSH_START:
/* we are not blocking on anything exect the push() calls
* to the peer which will be unblocked by forwarding the
* event.*/
res = gst_dvdemux_push_event (dvdemux, event);
break;
case GST_EVENT_FLUSH_STOP:
gst_adapter_clear (dvdemux->adapter);
GST_DEBUG ("cleared adapter");
gst_segment_init (&dvdemux->byte_segment, GST_FORMAT_BYTES);
gst_segment_init (&dvdemux->time_segment, GST_FORMAT_TIME);
res = gst_dvdemux_push_event (dvdemux, event);
break;
case GST_EVENT_SEGMENT:
{
const GstSegment *segment;
gst_event_parse_segment (event, &segment);
switch (segment->format) {
case GST_FORMAT_BYTES:
gst_segment_copy_into (segment, &dvdemux->byte_segment);
dvdemux->need_segment = TRUE;
gst_event_unref (event);
break;
case GST_FORMAT_TIME:
gst_segment_copy_into (segment, &dvdemux->time_segment);
/* and we can just forward this time event */
res = gst_dvdemux_push_event (dvdemux, event);
break;
default:
gst_event_unref (event);
/* cannot accept this format */
res = FALSE;
break;
}
break;
}
case GST_EVENT_EOS:
/* flush any pending data, should be nothing left. */
gst_dvdemux_flush (dvdemux);
/* forward event */
res = gst_dvdemux_push_event (dvdemux, event);
/* and clear the adapter */
gst_adapter_clear (dvdemux->adapter);
break;
case GST_EVENT_CAPS:
gst_event_unref (event);
break;
default:
res = gst_dvdemux_push_event (dvdemux, event);
break;
}
return res;
}
/* convert a pair of values on the given srcpad */
static gboolean
gst_dvdemux_convert_src_pair (GstDVDemux * dvdemux, GstPad * pad,
GstFormat src_format, gint64 src_start, gint64 src_stop,
GstFormat dst_format, gint64 * dst_start, gint64 * dst_stop)
{
gboolean res;
GST_INFO ("starting conversion of start");
/* bring the format to time on srcpad. */
if (!(res = gst_dvdemux_src_convert (dvdemux, pad,
src_format, src_start, dst_format, dst_start))) {
goto done;
}
GST_INFO ("Finished conversion of start: %" G_GINT64_FORMAT, *dst_start);
GST_INFO ("starting conversion of stop");
/* bring the format to time on srcpad. */
if (!(res = gst_dvdemux_src_convert (dvdemux, pad,
src_format, src_stop, dst_format, dst_stop))) {
/* could not convert seek format to time offset */
goto done;
}
GST_INFO ("Finished conversion of stop: %" G_GINT64_FORMAT, *dst_stop);
done:
return res;
}
/* convert a pair of values on the sinkpad */
static gboolean
gst_dvdemux_convert_sink_pair (GstDVDemux * dvdemux,
GstFormat src_format, gint64 src_start, gint64 src_stop,
GstFormat dst_format, gint64 * dst_start, gint64 * dst_stop)
{
gboolean res;
GST_INFO ("starting conversion of start");
/* bring the format to time on srcpad. */
if (!(res = gst_dvdemux_sink_convert (dvdemux,
src_format, src_start, dst_format, dst_start))) {
goto done;
}
GST_INFO ("Finished conversion of start: %" G_GINT64_FORMAT, *dst_start);
GST_INFO ("starting conversion of stop");
/* bring the format to time on srcpad. */
if (!(res = gst_dvdemux_sink_convert (dvdemux,
src_format, src_stop, dst_format, dst_stop))) {
/* could not convert seek format to time offset */
goto done;
}
GST_INFO ("Finished conversion of stop: %" G_GINT64_FORMAT, *dst_stop);
done:
return res;
}
/* convert a pair of values on the srcpad to a pair of
* values on the sinkpad
*/
static gboolean
gst_dvdemux_convert_src_to_sink (GstDVDemux * dvdemux, GstPad * pad,
GstFormat src_format, gint64 src_start, gint64 src_stop,
GstFormat dst_format, gint64 * dst_start, gint64 * dst_stop)
{
GstFormat conv;
gboolean res;
conv = GST_FORMAT_TIME;
/* convert to TIME intermediate format */
if (!(res = gst_dvdemux_convert_src_pair (dvdemux, pad,
src_format, src_start, src_stop, conv, dst_start, dst_stop))) {
/* could not convert format to time offset */
goto done;
}
/* convert to dst format on sinkpad */
if (!(res = gst_dvdemux_convert_sink_pair (dvdemux,
conv, *dst_start, *dst_stop, dst_format, dst_start, dst_stop))) {
/* could not convert format to time offset */
goto done;
}
done:
return res;
}
#if 0
static gboolean
gst_dvdemux_convert_segment (GstDVDemux * dvdemux, GstSegment * src,
GstSegment * dest)
{
dest->rate = src->rate;
dest->abs_rate = src->abs_rate;
dest->flags = src->flags;
return TRUE;
}
#endif
/* handle seek in push base mode.
*
* Convert the time seek to a bytes seek and send it
* upstream
* Does not take ownership of the event.
*/
static gboolean
gst_dvdemux_handle_push_seek (GstDVDemux * dvdemux, GstPad * pad,
GstEvent * event)
{
gboolean res = FALSE;
gdouble rate;
GstSeekFlags flags;
GstFormat format;
GstSeekType cur_type, stop_type;
gint64 cur, stop;
gint64 start_position, end_position;
GstEvent *newevent;
gst_event_parse_seek (event, &rate, &format, &flags,
&cur_type, &cur, &stop_type, &stop);
/* First try if upstream can handle time based seeks */
if (format == GST_FORMAT_TIME)
res = gst_pad_push_event (dvdemux->sinkpad, gst_event_ref (event));
if (!res) {
/* we convert the start/stop on the srcpad to the byte format
* on the sinkpad and forward the event */
res = gst_dvdemux_convert_src_to_sink (dvdemux, pad,
format, cur, stop, GST_FORMAT_BYTES, &start_position, &end_position);
if (!res)
goto done;
/* now this is the updated seek event on bytes */
newevent = gst_event_new_seek (rate, GST_FORMAT_BYTES, flags,
cur_type, start_position, stop_type, end_position);
res = gst_pad_push_event (dvdemux->sinkpad, newevent);
}
done:
return res;
}
/* position ourselves to the configured segment, used in pull mode.
* The input segment is in TIME format. We convert the time values
* to bytes values into our byte_segment which we use to pull data from
* the sinkpad peer.
*/
static gboolean
gst_dvdemux_do_seek (GstDVDemux * demux, GstSegment * segment)
{
gboolean res;
GstFormat format;
/* position to value configured is last_stop, this will round down
* to the byte position where the frame containing the given
* timestamp can be found. */
format = GST_FORMAT_BYTES;
res = gst_dvdemux_sink_convert (demux,
segment->format, segment->position,
format, (gint64 *) & demux->byte_segment.position);
if (!res)
goto done;
/* update byte segment start */
gst_dvdemux_sink_convert (demux,
segment->format, segment->start, format,
(gint64 *) & demux->byte_segment.start);
/* update byte segment stop */
gst_dvdemux_sink_convert (demux,
segment->format, segment->stop, format,
(gint64 *) & demux->byte_segment.stop);
/* update byte segment time */
gst_dvdemux_sink_convert (demux,
segment->format, segment->time, format,
(gint64 *) & demux->byte_segment.time);
/* calculate current frame number */
format = GST_FORMAT_DEFAULT;
gst_dvdemux_src_convert (demux, demux->videosrcpad,
segment->format, segment->start, format, &demux->video_offset);
/* calculate current audio number */
format = GST_FORMAT_DEFAULT;
gst_dvdemux_src_convert (demux, demux->audiosrcpad,
segment->format, segment->start, format, &demux->audio_offset);
/* every DV frame corresponts with one video frame */
demux->frame_offset = demux->video_offset;
done:
return res;
}
/* handle seek in pull base mode.
*
* Does not take ownership of the event.
*/
static gboolean
gst_dvdemux_handle_pull_seek (GstDVDemux * demux, GstPad * pad,
GstEvent * event)
{
gboolean res;
gdouble rate;
GstFormat format;
GstSeekFlags flags;
GstSeekType cur_type, stop_type;
gint64 cur, stop;
gboolean flush;
gboolean update;
GstSegment seeksegment;
GST_DEBUG_OBJECT (demux, "doing seek");
/* first bring the event format to TIME, our native format
* to perform the seek on */
if (event) {
GstFormat conv;
gst_event_parse_seek (event, &rate, &format, &flags,
&cur_type, &cur, &stop_type, &stop);
/* can't seek backwards yet */
if (rate <= 0.0)
goto wrong_rate;
/* convert input format to TIME */
conv = GST_FORMAT_TIME;
if (!(gst_dvdemux_convert_src_pair (demux, pad,
format, cur, stop, conv, &cur, &stop)))
goto no_format;
format = GST_FORMAT_TIME;
} else {
flags = 0;
}
flush = flags & GST_SEEK_FLAG_FLUSH;
/* send flush start */
if (flush)
gst_dvdemux_push_event (demux, gst_event_new_flush_start ());
else
gst_pad_pause_task (demux->sinkpad);
/* grab streaming lock, this should eventually be possible, either
* because the task is paused or our streaming thread stopped
* because our peer is flushing. */
GST_PAD_STREAM_LOCK (demux->sinkpad);
/* make copy into temp structure, we can only update the main one
* when the subclass actually could to the seek. */
memcpy (&seeksegment, &demux->time_segment, sizeof (GstSegment));
/* now configure the seek segment */
if (event) {
gst_segment_do_seek (&seeksegment, rate, format, flags,
cur_type, cur, stop_type, stop, &update);
}
GST_DEBUG_OBJECT (demux, "segment configured from %" G_GINT64_FORMAT
" to %" G_GINT64_FORMAT ", position %" G_GINT64_FORMAT,
seeksegment.start, seeksegment.stop, seeksegment.position);
/* do the seek, segment.position contains new position. */
res = gst_dvdemux_do_seek (demux, &seeksegment);
/* and prepare to continue streaming */
if (flush) {
/* send flush stop, peer will accept data and events again. We
* are not yet providing data as we still have the STREAM_LOCK. */
gst_dvdemux_push_event (demux, gst_event_new_flush_stop (TRUE));
}
/* if successfull seek, we update our real segment and push
* out the new segment. */
if (res) {
memcpy (&demux->time_segment, &seeksegment, sizeof (GstSegment));
if (demux->time_segment.flags & GST_SEEK_FLAG_SEGMENT) {
gst_element_post_message (GST_ELEMENT_CAST (demux),
gst_message_new_segment_start (GST_OBJECT_CAST (demux),
demux->time_segment.format, demux->time_segment.position));
}
if ((stop = demux->time_segment.stop) == -1)
stop = demux->time_segment.duration;
GST_INFO_OBJECT (demux,
"Saving newsegment event to be sent in streaming thread");
if (demux->pending_segment)
gst_event_unref (demux->pending_segment);
demux->pending_segment = gst_event_new_segment (&demux->time_segment);
demux->need_segment = FALSE;
}
/* and restart the task in case it got paused explicitely or by
* the FLUSH_START event we pushed out. */
gst_pad_start_task (demux->sinkpad, (GstTaskFunction) gst_dvdemux_loop,
demux->sinkpad, NULL);
/* and release the lock again so we can continue streaming */
GST_PAD_STREAM_UNLOCK (demux->sinkpad);
return TRUE;
/* ERRORS */
wrong_rate:
{
GST_DEBUG_OBJECT (demux, "negative playback rate %lf not supported.", rate);
return FALSE;
}
no_format:
{
GST_DEBUG_OBJECT (demux, "cannot convert to TIME format, seek aborted.");
return FALSE;
}
}
static gboolean
gst_dvdemux_send_event (GstElement * element, GstEvent * event)
{
GstDVDemux *dvdemux = GST_DVDEMUX (element);
gboolean res = FALSE;
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_SEEK:
{
/* checking header and configuring the seek must be atomic */
GST_OBJECT_LOCK (dvdemux);
if (g_atomic_int_get (&dvdemux->found_header) == 0) {
GstEvent **event_p;
event_p = &dvdemux->seek_event;
/* We don't have pads yet. Keep the event. */
GST_INFO_OBJECT (dvdemux, "Keeping the seek event for later");
gst_event_replace (event_p, event);
GST_OBJECT_UNLOCK (dvdemux);
res = TRUE;
} else {
GST_OBJECT_UNLOCK (dvdemux);
if (dvdemux->seek_handler) {
res = dvdemux->seek_handler (dvdemux, dvdemux->videosrcpad, event);
gst_event_unref (event);
}
}
break;
}
default:
res = GST_ELEMENT_CLASS (parent_class)->send_event (element, event);
break;
}
return res;
}
/* handle an event on the source pad, it's most likely a seek */
static gboolean
gst_dvdemux_handle_src_event (GstPad * pad, GstObject * parent,
GstEvent * event)
{
gboolean res = TRUE;
GstDVDemux *dvdemux;
dvdemux = GST_DVDEMUX (parent);
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_SEEK:
/* seek handler is installed based on scheduling mode */
if (dvdemux->seek_handler)
res = dvdemux->seek_handler (dvdemux, pad, event);
else
res = FALSE;
break;
case GST_EVENT_QOS:
/* we can't really (yet) do QoS */
res = FALSE;
break;
case GST_EVENT_NAVIGATION:
case GST_EVENT_CAPS:
/* no navigation or caps either... */
res = FALSE;
break;
default:
res = gst_pad_push_event (dvdemux->sinkpad, event);
event = NULL;
break;
}
if (event)
gst_event_unref (event);
return res;
}
/* does not take ownership of buffer */
static GstFlowReturn
gst_dvdemux_demux_audio (GstDVDemux * dvdemux, GstBuffer * buffer,
guint64 duration)
{
gint num_samples;
GstFlowReturn ret;
GstMapInfo map;
gst_buffer_map (buffer, &map, GST_MAP_READ);
dv_decode_full_audio (dvdemux->decoder, map.data, dvdemux->audio_buffers);
gst_buffer_unmap (buffer, &map);
if (G_LIKELY ((num_samples = dv_get_num_samples (dvdemux->decoder)) > 0)) {
gint16 *a_ptr;
gint i, j;
GstBuffer *outbuf;
gint frequency, channels;
/* get initial format or check if format changed */
frequency = dv_get_frequency (dvdemux->decoder);
channels = dv_get_num_channels (dvdemux->decoder);
if (G_UNLIKELY ((dvdemux->audiosrcpad == NULL)
|| (frequency != dvdemux->frequency)
|| (channels != dvdemux->channels))) {
GstCaps *caps;
GstAudioInfo info;
dvdemux->frequency = frequency;
dvdemux->channels = channels;
gst_audio_info_init (&info);
gst_audio_info_set_format (&info, GST_AUDIO_FORMAT_S16LE,
frequency, channels, NULL);
caps = gst_audio_info_to_caps (&info);
if (G_UNLIKELY (dvdemux->audiosrcpad == NULL)) {
dvdemux->audiosrcpad =
gst_dvdemux_add_pad (dvdemux, &audio_src_temp, caps);
} else {
gst_pad_set_caps (dvdemux->audiosrcpad, caps);
}
gst_caps_unref (caps);
}
outbuf = gst_buffer_new_and_alloc (num_samples *
sizeof (gint16) * dvdemux->channels);
gst_buffer_map (outbuf, &map, GST_MAP_WRITE);
a_ptr = (gint16 *) map.data;
for (i = 0; i < num_samples; i++) {
for (j = 0; j < dvdemux->channels; j++) {
*(a_ptr++) = dvdemux->audio_buffers[j][i];
}
}
gst_buffer_unmap (outbuf, &map);
GST_DEBUG ("pushing audio %" GST_TIME_FORMAT,
GST_TIME_ARGS (dvdemux->time_segment.position));
GST_BUFFER_TIMESTAMP (outbuf) = dvdemux->time_segment.position;
GST_BUFFER_DURATION (outbuf) = duration;
GST_BUFFER_OFFSET (outbuf) = dvdemux->audio_offset;
dvdemux->audio_offset += num_samples;
GST_BUFFER_OFFSET_END (outbuf) = dvdemux->audio_offset;
if (dvdemux->new_media)
GST_BUFFER_FLAG_SET (outbuf, GST_BUFFER_FLAG_DISCONT);
ret = gst_pad_push (dvdemux->audiosrcpad, outbuf);
} else {
/* no samples */
ret = GST_FLOW_OK;
}
return ret;
}
/* takes ownership of buffer */
static GstFlowReturn
gst_dvdemux_demux_video (GstDVDemux * dvdemux, GstBuffer * buffer,
guint64 duration)
{
GstBuffer *outbuf;
gint height;
gboolean wide;
GstFlowReturn ret = GST_FLOW_OK;
/* get params */
/* framerate is already up-to-date */
height = dvdemux->decoder->height;
wide = dv_format_wide (dvdemux->decoder);
/* see if anything changed */
if (G_UNLIKELY ((dvdemux->videosrcpad == NULL) || (dvdemux->height != height)
|| dvdemux->wide != wide)) {
gint par_x, par_y;
GstCaps *caps;
dvdemux->height = height;
dvdemux->wide = wide;
if (dvdemux->decoder->system == e_dv_system_625_50) {
if (wide) {
par_x = PAL_WIDE_PAR_X;
par_y = PAL_WIDE_PAR_Y;
} else {
par_x = PAL_NORMAL_PAR_X;
par_y = PAL_NORMAL_PAR_Y;
}
} else {
if (wide) {
par_x = NTSC_WIDE_PAR_X;
par_y = NTSC_WIDE_PAR_Y;
} else {
par_x = NTSC_NORMAL_PAR_X;
par_y = NTSC_NORMAL_PAR_Y;
}
}
caps = gst_caps_new_simple ("video/x-dv",
"systemstream", G_TYPE_BOOLEAN, FALSE,
"width", G_TYPE_INT, 720,
"height", G_TYPE_INT, height,
"framerate", GST_TYPE_FRACTION, dvdemux->framerate_numerator,
dvdemux->framerate_denominator,
"pixel-aspect-ratio", GST_TYPE_FRACTION, par_x, par_y, NULL);
if (G_UNLIKELY (dvdemux->videosrcpad == NULL)) {
dvdemux->videosrcpad =
gst_dvdemux_add_pad (dvdemux, &video_src_temp, caps);
} else {
gst_pad_set_caps (dvdemux->videosrcpad, caps);
}
gst_caps_unref (caps);
}
/* takes ownership of buffer here, we just need to modify
* the metadata. */
outbuf = gst_buffer_make_writable (buffer);
GST_BUFFER_TIMESTAMP (outbuf) = dvdemux->time_segment.position;
GST_BUFFER_OFFSET (outbuf) = dvdemux->video_offset;
GST_BUFFER_OFFSET_END (outbuf) = dvdemux->video_offset + 1;
GST_BUFFER_DURATION (outbuf) = duration;
if (dvdemux->new_media)
GST_BUFFER_FLAG_SET (outbuf, GST_BUFFER_FLAG_DISCONT);
GST_DEBUG ("pushing video %" GST_TIME_FORMAT,
GST_TIME_ARGS (dvdemux->time_segment.position));
ret = gst_pad_push (dvdemux->videosrcpad, outbuf);
dvdemux->video_offset++;
return ret;
}
static int
get_ssyb_offset (int dif, int ssyb)
{
int offset;
offset = dif * 12000; /* to dif */
offset += 80 * (1 + (ssyb / 6)); /* to subcode pack */
offset += 3; /* past header */
offset += 8 * (ssyb % 6); /* to ssyb */
return offset;
}
static gboolean
gst_dvdemux_get_timecode (GstDVDemux * dvdemux, GstBuffer * buffer,
GstSMPTETimeCode * timecode)
{
guint8 *data;
GstMapInfo map;
int offset;
int dif;
int n_difs = dvdemux->decoder->num_dif_seqs;
gst_buffer_map (buffer, &map, GST_MAP_READ);
data = map.data;
for (dif = 0; dif < n_difs; dif++) {
offset = get_ssyb_offset (dif, 3);
if (data[offset + 3] == 0x13) {
timecode->frames = ((data[offset + 4] >> 4) & 0x3) * 10 +
(data[offset + 4] & 0xf);
timecode->seconds = ((data[offset + 5] >> 4) & 0x3) * 10 +
(data[offset + 5] & 0xf);
timecode->minutes = ((data[offset + 6] >> 4) & 0x3) * 10 +
(data[offset + 6] & 0xf);
timecode->hours = ((data[offset + 7] >> 4) & 0x3) * 10 +
(data[offset + 7] & 0xf);
GST_DEBUG ("got timecode %" GST_SMPTE_TIME_CODE_FORMAT,
GST_SMPTE_TIME_CODE_ARGS (timecode));
gst_buffer_unmap (buffer, &map);
return TRUE;
}
}
gst_buffer_unmap (buffer, &map);
return FALSE;
}
static gboolean
gst_dvdemux_is_new_media (GstDVDemux * dvdemux, GstBuffer * buffer)
{
guint8 *data;
GstMapInfo map;
int aaux_offset;
int dif;
int n_difs;
n_difs = dvdemux->decoder->num_dif_seqs;
gst_buffer_map (buffer, &map, GST_MAP_READ);
data = map.data;
for (dif = 0; dif < n_difs; dif++) {
if (dif & 1) {
aaux_offset = (dif * 12000) + (6 + 16 * 1) * 80 + 3;
} else {
aaux_offset = (dif * 12000) + (6 + 16 * 4) * 80 + 3;
}
if (data[aaux_offset + 0] == 0x51) {
if ((data[aaux_offset + 2] & 0x80) == 0) {
gst_buffer_unmap (buffer, &map);
return TRUE;
}
}
}
gst_buffer_unmap (buffer, &map);
return FALSE;
}
/* takes ownership of buffer */
static GstFlowReturn
gst_dvdemux_demux_frame (GstDVDemux * dvdemux, GstBuffer * buffer)
{
GstClockTime next_ts;
GstFlowReturn aret, vret, ret;
GstMapInfo map;
guint64 duration;
GstSMPTETimeCode timecode;
int frame_number;
if (G_UNLIKELY (dvdemux->need_segment)) {
GstFormat format;
/* convert to time and store as start/end_timestamp */
format = GST_FORMAT_TIME;
if (!(gst_dvdemux_convert_sink_pair (dvdemux,
GST_FORMAT_BYTES, dvdemux->byte_segment.start,
dvdemux->byte_segment.stop, format,
(gint64 *) & dvdemux->time_segment.start,
(gint64 *) & dvdemux->time_segment.stop)))
goto segment_error;
dvdemux->time_segment.rate = dvdemux->byte_segment.rate;
dvdemux->time_segment.position = dvdemux->time_segment.start;
/* calculate current frame number */
format = GST_FORMAT_DEFAULT;
if (!(gst_dvdemux_src_convert (dvdemux, dvdemux->videosrcpad,
GST_FORMAT_TIME, dvdemux->time_segment.start,
format, &dvdemux->frame_offset)))
goto segment_error;
GST_DEBUG_OBJECT (dvdemux, "sending segment start: %" GST_TIME_FORMAT
", stop: %" GST_TIME_FORMAT ", time: %" GST_TIME_FORMAT,
GST_TIME_ARGS (dvdemux->time_segment.start),
GST_TIME_ARGS (dvdemux->time_segment.stop),
GST_TIME_ARGS (dvdemux->time_segment.start));
gst_dvdemux_push_event (dvdemux,
gst_event_new_segment (&dvdemux->time_segment));
dvdemux->need_segment = FALSE;
}
gst_dvdemux_get_timecode (dvdemux, buffer, &timecode);
gst_smpte_time_code_get_frame_number (
(dvdemux->decoder->system == e_dv_system_625_50) ?
GST_SMPTE_TIME_CODE_SYSTEM_25 : GST_SMPTE_TIME_CODE_SYSTEM_30,
&frame_number, &timecode);
next_ts = gst_util_uint64_scale_int (
(dvdemux->frame_offset + 1) * GST_SECOND,
dvdemux->framerate_denominator, dvdemux->framerate_numerator);
duration = next_ts - dvdemux->time_segment.position;
gst_buffer_map (buffer, &map, GST_MAP_READ);
dv_parse_packs (dvdemux->decoder, map.data);
gst_buffer_unmap (buffer, &map);
dvdemux->new_media = FALSE;
if (gst_dvdemux_is_new_media (dvdemux, buffer) &&
dvdemux->frames_since_new_media > 2) {
dvdemux->new_media = TRUE;
dvdemux->frames_since_new_media = 0;
}
dvdemux->frames_since_new_media++;
/* does not take ownership of buffer */
aret = ret = gst_dvdemux_demux_audio (dvdemux, buffer, duration);
if (G_UNLIKELY (ret != GST_FLOW_OK && ret != GST_FLOW_NOT_LINKED)) {
gst_buffer_unref (buffer);
goto done;
}
/* takes ownership of buffer */
vret = ret = gst_dvdemux_demux_video (dvdemux, buffer, duration);
if (G_UNLIKELY (ret != GST_FLOW_OK && ret != GST_FLOW_NOT_LINKED))
goto done;
/* if both are not linked, we stop */
if (G_UNLIKELY (aret == GST_FLOW_NOT_LINKED && vret == GST_FLOW_NOT_LINKED)) {
ret = GST_FLOW_NOT_LINKED;
goto done;
}
dvdemux->time_segment.position = next_ts;
dvdemux->frame_offset++;
/* check for the end of the segment */
if (dvdemux->time_segment.stop != -1 && next_ts > dvdemux->time_segment.stop)
ret = GST_FLOW_EOS;
else
ret = GST_FLOW_OK;
done:
return ret;
/* ERRORS */
segment_error:
{
GST_DEBUG ("error generating new_segment event");
gst_buffer_unref (buffer);
return GST_FLOW_ERROR;
}
}
/* flush any remaining data in the adapter, used in chain based scheduling mode */
static GstFlowReturn
gst_dvdemux_flush (GstDVDemux * dvdemux)
{
GstFlowReturn ret = GST_FLOW_OK;
while (gst_adapter_available (dvdemux->adapter) >= dvdemux->frame_len) {
const guint8 *data;
gint length;
/* get the accumulated bytes */
data = gst_adapter_map (dvdemux->adapter, dvdemux->frame_len);
/* parse header to know the length and other params */
if (G_UNLIKELY (dv_parse_header (dvdemux->decoder, data) < 0)) {
gst_adapter_unmap (dvdemux->adapter);
goto parse_header_error;
}
gst_adapter_unmap (dvdemux->adapter);
/* after parsing the header we know the length of the data */
length = dvdemux->frame_len = dvdemux->decoder->frame_size;
if (dvdemux->decoder->system == e_dv_system_625_50) {
dvdemux->framerate_numerator = PAL_FRAMERATE_NUMERATOR;
dvdemux->framerate_denominator = PAL_FRAMERATE_DENOMINATOR;
} else {
dvdemux->framerate_numerator = NTSC_FRAMERATE_NUMERATOR;
dvdemux->framerate_denominator = NTSC_FRAMERATE_DENOMINATOR;
}
g_atomic_int_set (&dvdemux->found_header, 1);
/* let demux_video set the height, it needs to detect when things change so
* it can reset caps */
/* if we still have enough for a frame, start decoding */
if (G_LIKELY (gst_adapter_available (dvdemux->adapter) >= length)) {
GstBuffer *buffer;
buffer = gst_adapter_take_buffer (dvdemux->adapter, length);
/* and decode the buffer, takes ownership */
ret = gst_dvdemux_demux_frame (dvdemux, buffer);
if (G_UNLIKELY (ret != GST_FLOW_OK))
goto done;
}
}
done:
return ret;
/* ERRORS */
parse_header_error:
{
GST_ELEMENT_ERROR (dvdemux, STREAM, DECODE,
(NULL), ("Error parsing DV header"));
return GST_FLOW_ERROR;
}
}
/* streaming operation:
*
* accumulate data until we have a frame, then decode.
*/
static GstFlowReturn
gst_dvdemux_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer)
{
GstDVDemux *dvdemux;
GstFlowReturn ret;
GstClockTime timestamp;
dvdemux = GST_DVDEMUX (parent);
/* a discontinuity in the stream, we need to get rid of
* accumulated data in the adapter and assume a new frame
* starts after the discontinuity */
if (G_UNLIKELY (GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_DISCONT)))
gst_adapter_clear (dvdemux->adapter);
/* a timestamp always should be respected */
timestamp = GST_BUFFER_TIMESTAMP (buffer);
if (GST_CLOCK_TIME_IS_VALID (timestamp)) {
dvdemux->time_segment.position = timestamp;
/* FIXME, adjust frame_offset and other counters */
}
gst_adapter_push (dvdemux->adapter, buffer);
/* Apparently dv_parse_header can read from the body of the frame
* too, so it needs more than header_size bytes. Wacky!
*/
if (G_UNLIKELY (dvdemux->frame_len == -1)) {
/* if we don't know the length of a frame, we assume it is
* the NTSC_BUFFER length, as this is enough to figure out
* if this is PAL or NTSC */
dvdemux->frame_len = NTSC_BUFFER;
}
/* and try to flush pending frames */
ret = gst_dvdemux_flush (dvdemux);
return ret;
}
/* pull based operation.
*
* Read header first to figure out the frame size. Then read
* and decode full frames.
*/
static void
gst_dvdemux_loop (GstPad * pad)
{
GstFlowReturn ret;
GstDVDemux *dvdemux;
GstBuffer *buffer = NULL;
GstMapInfo map;
dvdemux = GST_DVDEMUX (gst_pad_get_parent (pad));
if (G_UNLIKELY (g_atomic_int_get (&dvdemux->found_header) == 0)) {
GST_DEBUG_OBJECT (dvdemux, "pulling first buffer");
/* pull in NTSC sized buffer to figure out the frame
* length */
ret = gst_pad_pull_range (dvdemux->sinkpad,
dvdemux->byte_segment.position, NTSC_BUFFER, &buffer);
if (G_UNLIKELY (ret != GST_FLOW_OK))
goto pause;
/* check buffer size, don't want to read small buffers */
if (G_UNLIKELY (gst_buffer_get_size (buffer) < NTSC_BUFFER))
goto small_buffer;
gst_buffer_map (buffer, &map, GST_MAP_READ);
/* parse header to know the length and other params */
if (G_UNLIKELY (dv_parse_header (dvdemux->decoder, map.data) < 0)) {
gst_buffer_unmap (buffer, &map);
goto parse_header_error;
}
gst_buffer_unmap (buffer, &map);
/* after parsing the header we know the length of the data */
dvdemux->frame_len = dvdemux->decoder->frame_size;
if (dvdemux->decoder->system == e_dv_system_625_50) {
dvdemux->framerate_numerator = PAL_FRAMERATE_NUMERATOR;
dvdemux->framerate_denominator = PAL_FRAMERATE_DENOMINATOR;
} else {
dvdemux->framerate_numerator = NTSC_FRAMERATE_NUMERATOR;
dvdemux->framerate_denominator = NTSC_FRAMERATE_DENOMINATOR;
}
dvdemux->need_segment = TRUE;
/* see if we need to read a larger part */
if (dvdemux->frame_len != NTSC_BUFFER) {
gst_buffer_unref (buffer);
buffer = NULL;
}
{
GstEvent *event;
/* setting header and prrforming the seek must be atomic */
GST_OBJECT_LOCK (dvdemux);
/* got header now */
g_atomic_int_set (&dvdemux->found_header, 1);
/* now perform pending seek if any. */
event = dvdemux->seek_event;
if (event)
gst_event_ref (event);
GST_OBJECT_UNLOCK (dvdemux);
if (event) {
if (!gst_dvdemux_handle_pull_seek (dvdemux, dvdemux->videosrcpad,
event)) {
GST_ELEMENT_WARNING (dvdemux, STREAM, DECODE, (NULL),
("Error perfoming initial seek"));
}
gst_event_unref (event);
/* and we need to pull a new buffer in all cases. */
if (buffer) {
gst_buffer_unref (buffer);
buffer = NULL;
}
}
}
}
if (G_UNLIKELY (dvdemux->pending_segment)) {
/* now send the newsegment */
GST_DEBUG_OBJECT (dvdemux, "Sending newsegment from");
gst_dvdemux_push_event (dvdemux, dvdemux->pending_segment);
dvdemux->pending_segment = NULL;
}
if (G_LIKELY (buffer == NULL)) {
GST_DEBUG_OBJECT (dvdemux, "pulling buffer at offset %" G_GINT64_FORMAT,
dvdemux->byte_segment.position);
ret = gst_pad_pull_range (dvdemux->sinkpad,
dvdemux->byte_segment.position, dvdemux->frame_len, &buffer);
if (ret != GST_FLOW_OK)
goto pause;
/* check buffer size, don't want to read small buffers */
if (gst_buffer_get_size (buffer) < dvdemux->frame_len)
goto small_buffer;
}
/* and decode the buffer */
ret = gst_dvdemux_demux_frame (dvdemux, buffer);
if (G_UNLIKELY (ret != GST_FLOW_OK))
goto pause;
/* and position ourselves for the next buffer */
dvdemux->byte_segment.position += dvdemux->frame_len;
done:
gst_object_unref (dvdemux);
return;
/* ERRORS */
parse_header_error:
{
GST_ELEMENT_ERROR (dvdemux, STREAM, DECODE,
(NULL), ("Error parsing DV header"));
gst_buffer_unref (buffer);
gst_pad_pause_task (dvdemux->sinkpad);
gst_dvdemux_push_event (dvdemux, gst_event_new_eos ());
goto done;
}
small_buffer:
{
GST_ELEMENT_ERROR (dvdemux, STREAM, DECODE,
(NULL), ("Error reading buffer"));
gst_buffer_unref (buffer);
gst_pad_pause_task (dvdemux->sinkpad);
gst_dvdemux_push_event (dvdemux, gst_event_new_eos ());
goto done;
}
pause:
{
GST_INFO_OBJECT (dvdemux, "pausing task, %s", gst_flow_get_name (ret));
gst_pad_pause_task (dvdemux->sinkpad);
if (ret == GST_FLOW_EOS) {
GST_LOG_OBJECT (dvdemux, "got eos");
/* so align our position with the end of it, if there is one
* this ensures a subsequent will arrive at correct base/acc time */
if (dvdemux->time_segment.rate > 0.0 &&
GST_CLOCK_TIME_IS_VALID (dvdemux->time_segment.stop))
dvdemux->time_segment.position = dvdemux->time_segment.stop;
else if (dvdemux->time_segment.rate < 0.0)
dvdemux->time_segment.position = dvdemux->time_segment.start;
/* perform EOS logic */
if (dvdemux->time_segment.flags & GST_SEEK_FLAG_SEGMENT) {
gst_element_post_message (GST_ELEMENT (dvdemux),
gst_message_new_segment_done (GST_OBJECT_CAST (dvdemux),
dvdemux->time_segment.format, dvdemux->time_segment.position));
gst_dvdemux_push_event (dvdemux,
gst_event_new_segment_done (dvdemux->time_segment.format,
dvdemux->time_segment.position));
} else {
gst_dvdemux_push_event (dvdemux, gst_event_new_eos ());
}
} else if (ret == GST_FLOW_NOT_LINKED || ret < GST_FLOW_EOS) {
/* for fatal errors or not-linked we post an error message */
GST_ELEMENT_ERROR (dvdemux, STREAM, FAILED,
(NULL), ("streaming stopped, reason %s", gst_flow_get_name (ret)));
gst_dvdemux_push_event (dvdemux, gst_event_new_eos ());
}
goto done;
}
}
static gboolean
gst_dvdemux_sink_activate_mode (GstPad * sinkpad, GstObject * parent,
GstPadMode mode, gboolean active)
{
gboolean res;
GstDVDemux *demux = GST_DVDEMUX (parent);
switch (mode) {
case GST_PAD_MODE_PULL:
if (active) {
demux->seek_handler = gst_dvdemux_handle_pull_seek;
res = gst_pad_start_task (sinkpad,
(GstTaskFunction) gst_dvdemux_loop, sinkpad, NULL);
} else {
demux->seek_handler = NULL;
res = gst_pad_stop_task (sinkpad);
}
break;
case GST_PAD_MODE_PUSH:
if (active) {
GST_DEBUG_OBJECT (demux, "activating push/chain function");
demux->seek_handler = gst_dvdemux_handle_push_seek;
} else {
GST_DEBUG_OBJECT (demux, "deactivating push/chain function");
demux->seek_handler = NULL;
}
res = TRUE;
break;
default:
res = FALSE;
break;
}
return res;
}
/* decide on push or pull based scheduling */
static gboolean
gst_dvdemux_sink_activate (GstPad * sinkpad, GstObject * parent)
{
GstQuery *query;
gboolean pull_mode;
query = gst_query_new_scheduling ();
if (!gst_pad_peer_query (sinkpad, query)) {
gst_query_unref (query);
goto activate_push;
}
pull_mode = gst_query_has_scheduling_mode_with_flags (query,
GST_PAD_MODE_PULL, GST_SCHEDULING_FLAG_SEEKABLE);
gst_query_unref (query);
if (!pull_mode)
goto activate_push;
GST_DEBUG_OBJECT (sinkpad, "activating pull");
return gst_pad_activate_mode (sinkpad, GST_PAD_MODE_PULL, TRUE);
activate_push:
{
GST_DEBUG_OBJECT (sinkpad, "activating push");
return gst_pad_activate_mode (sinkpad, GST_PAD_MODE_PUSH, TRUE);
}
}
static GstStateChangeReturn
gst_dvdemux_change_state (GstElement * element, GstStateChange transition)
{
GstDVDemux *dvdemux = GST_DVDEMUX (element);
GstStateChangeReturn ret;
switch (transition) {
case GST_STATE_CHANGE_NULL_TO_READY:
break;
case GST_STATE_CHANGE_READY_TO_PAUSED:
dvdemux->decoder = dv_decoder_new (0, FALSE, FALSE);
dv_set_error_log (dvdemux->decoder, NULL);
gst_dvdemux_reset (dvdemux);
break;
case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
break;
default:
break;
}
ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
switch (transition) {
case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
break;
case GST_STATE_CHANGE_PAUSED_TO_READY:
gst_adapter_clear (dvdemux->adapter);
dv_decoder_free (dvdemux->decoder);
dvdemux->decoder = NULL;
gst_dvdemux_remove_pads (dvdemux);
break;
case GST_STATE_CHANGE_READY_TO_NULL:
{
GstEvent **event_p;
event_p = &dvdemux->seek_event;
gst_event_replace (event_p, NULL);
if (dvdemux->pending_segment)
gst_event_unref (dvdemux->pending_segment);
dvdemux->pending_segment = NULL;
break;
}
default:
break;
}
return ret;
}