mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-30 12:10:37 +00:00
40ee3369a0
Appears to be utterly incapable of parsing and decoding TTA streams. Hasn't been updated to do TTA2. If you want this element to work, fix the bloody thing. The gst-ffmpeg decoder works fine. Also fixed an obvious endianness issue along the way. Fixes: #652924
506 lines
14 KiB
C
506 lines
14 KiB
C
/* GStreamer TTA plugin
|
|
* (c) 2004 Arwed v. Merkatz <v.merkatz@gmx.net>
|
|
*
|
|
* gstttaparse.c: TTA file parser
|
|
*
|
|
* 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., 59 Temple Place - Suite 330,
|
|
* Boston, MA 02111-1307, USA.
|
|
*/
|
|
|
|
#include <gst/gst.h>
|
|
|
|
#include <math.h>
|
|
|
|
#include "gstttaparse.h"
|
|
#include "ttadec.h"
|
|
#include "crc32.h"
|
|
|
|
GST_DEBUG_CATEGORY_STATIC (gst_tta_parse_debug);
|
|
#define GST_CAT_DEFAULT gst_tta_parse_debug
|
|
|
|
static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink",
|
|
GST_PAD_SINK,
|
|
GST_PAD_ALWAYS,
|
|
GST_STATIC_CAPS ("audio/x-ttafile")
|
|
);
|
|
|
|
static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src",
|
|
GST_PAD_SRC,
|
|
GST_PAD_ALWAYS,
|
|
GST_STATIC_CAPS ("audio/x-tta, "
|
|
"width = (int) { 8, 16, 24 }, "
|
|
"channels = (int) { 1, 2 }, " "rate = (int) [ 8000, 96000 ]")
|
|
);
|
|
|
|
static void gst_tta_parse_class_init (GstTtaParseClass * klass);
|
|
static void gst_tta_parse_base_init (GstTtaParseClass * klass);
|
|
static void gst_tta_parse_init (GstTtaParse * ttaparse);
|
|
|
|
static gboolean gst_tta_parse_src_event (GstPad * pad, GstEvent * event);
|
|
static const GstQueryType *gst_tta_parse_get_query_types (GstPad * pad);
|
|
static gboolean gst_tta_parse_query (GstPad * pad, GstQuery * query);
|
|
static gboolean gst_tta_parse_activate (GstPad * pad);
|
|
static gboolean gst_tta_parse_activate_pull (GstPad * pad, gboolean active);
|
|
static void gst_tta_parse_loop (GstTtaParse * ttaparse);
|
|
static GstStateChangeReturn gst_tta_parse_change_state (GstElement * element,
|
|
GstStateChange transition);
|
|
|
|
static GstElementClass *parent_class = NULL;
|
|
|
|
GType
|
|
gst_tta_parse_get_type (void)
|
|
{
|
|
static GType plugin_type = 0;
|
|
|
|
if (!plugin_type) {
|
|
static const GTypeInfo plugin_info = {
|
|
sizeof (GstTtaParseClass),
|
|
(GBaseInitFunc) gst_tta_parse_base_init,
|
|
NULL,
|
|
(GClassInitFunc) gst_tta_parse_class_init,
|
|
NULL,
|
|
NULL,
|
|
sizeof (GstTtaParse),
|
|
0,
|
|
(GInstanceInitFunc) gst_tta_parse_init,
|
|
};
|
|
plugin_type = g_type_register_static (GST_TYPE_ELEMENT,
|
|
"GstTtaParse", &plugin_info, 0);
|
|
}
|
|
return plugin_type;
|
|
}
|
|
|
|
static void
|
|
gst_tta_parse_base_init (GstTtaParseClass * klass)
|
|
{
|
|
|
|
GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
|
|
|
|
gst_element_class_add_pad_template (element_class,
|
|
gst_static_pad_template_get (&src_factory));
|
|
gst_element_class_add_pad_template (element_class,
|
|
gst_static_pad_template_get (&sink_factory));
|
|
gst_element_class_set_details_simple (element_class, "TTA file parser",
|
|
"Codec/Demuxer/Audio",
|
|
"Parses TTA files", "Arwed v. Merkatz <v.merkatz@gmx.net>");
|
|
}
|
|
|
|
static void
|
|
gst_tta_parse_dispose (GObject * object)
|
|
{
|
|
GstTtaParse *ttaparse = GST_TTA_PARSE (object);
|
|
|
|
g_free (ttaparse->index);
|
|
|
|
G_OBJECT_CLASS (parent_class)->dispose (object);
|
|
}
|
|
|
|
static void
|
|
gst_tta_parse_class_init (GstTtaParseClass * klass)
|
|
{
|
|
GObjectClass *gobject_class;
|
|
GstElementClass *gstelement_class;
|
|
|
|
gobject_class = (GObjectClass *) klass;
|
|
gstelement_class = (GstElementClass *) klass;
|
|
|
|
parent_class = g_type_class_peek_parent (klass);
|
|
|
|
gobject_class->dispose = gst_tta_parse_dispose;
|
|
gstelement_class->change_state = gst_tta_parse_change_state;
|
|
}
|
|
|
|
static void
|
|
gst_tta_parse_reset (GstTtaParse * ttaparse)
|
|
{
|
|
ttaparse->header_parsed = FALSE;
|
|
ttaparse->current_frame = 0;
|
|
ttaparse->data_length = 0;
|
|
ttaparse->samplerate = 0;
|
|
}
|
|
|
|
static void
|
|
gst_tta_parse_init (GstTtaParse * ttaparse)
|
|
{
|
|
GstElementClass *klass = GST_ELEMENT_GET_CLASS (ttaparse);
|
|
|
|
ttaparse->sinkpad =
|
|
gst_pad_new_from_template (gst_element_class_get_pad_template (klass,
|
|
"sink"), "sink");
|
|
|
|
ttaparse->srcpad =
|
|
gst_pad_new_from_template (gst_element_class_get_pad_template (klass,
|
|
"src"), "src");
|
|
gst_pad_use_fixed_caps (ttaparse->srcpad);
|
|
gst_pad_set_query_type_function (ttaparse->srcpad,
|
|
gst_tta_parse_get_query_types);
|
|
gst_pad_set_query_function (ttaparse->srcpad, gst_tta_parse_query);
|
|
gst_pad_set_event_function (ttaparse->srcpad, gst_tta_parse_src_event);
|
|
|
|
gst_element_add_pad (GST_ELEMENT (ttaparse), ttaparse->sinkpad);
|
|
gst_element_add_pad (GST_ELEMENT (ttaparse), ttaparse->srcpad);
|
|
gst_pad_set_activate_function (ttaparse->sinkpad, gst_tta_parse_activate);
|
|
gst_pad_set_activatepull_function (ttaparse->sinkpad,
|
|
gst_tta_parse_activate_pull);
|
|
|
|
gst_tta_parse_reset (ttaparse);
|
|
}
|
|
|
|
static gboolean
|
|
gst_tta_parse_src_event (GstPad * pad, GstEvent * event)
|
|
{
|
|
GstTtaParse *ttaparse = GST_TTA_PARSE (GST_PAD_PARENT (pad));
|
|
|
|
gboolean res = TRUE;
|
|
|
|
switch (GST_EVENT_TYPE (event)) {
|
|
case GST_EVENT_SEEK:
|
|
{
|
|
gdouble rate;
|
|
GstFormat format;
|
|
GstSeekFlags flags;
|
|
GstSeekType start_type, stop_type;
|
|
gint64 start, stop;
|
|
|
|
gst_event_parse_seek (event, &rate, &format, &flags,
|
|
&start_type, &start, &stop_type, &stop);
|
|
|
|
if (format == GST_FORMAT_TIME) {
|
|
if (flags & GST_SEEK_FLAG_FLUSH) {
|
|
gst_pad_push_event (ttaparse->srcpad, gst_event_new_flush_start ());
|
|
gst_pad_push_event (ttaparse->sinkpad, gst_event_new_flush_start ());
|
|
} else {
|
|
gst_pad_pause_task (ttaparse->sinkpad);
|
|
}
|
|
GST_PAD_STREAM_LOCK (ttaparse->sinkpad);
|
|
|
|
switch (start_type) {
|
|
case GST_SEEK_TYPE_CUR:
|
|
ttaparse->current_frame += (start / GST_SECOND) / FRAME_TIME;
|
|
break;
|
|
case GST_SEEK_TYPE_END:
|
|
ttaparse->current_frame += (start / GST_SECOND) / FRAME_TIME;
|
|
break;
|
|
case GST_SEEK_TYPE_SET:
|
|
ttaparse->current_frame = (start / GST_SECOND) / FRAME_TIME;
|
|
break;
|
|
case GST_SEEK_TYPE_NONE:
|
|
break;
|
|
}
|
|
res = TRUE;
|
|
|
|
if (flags & GST_SEEK_FLAG_FLUSH) {
|
|
gst_pad_push_event (ttaparse->srcpad, gst_event_new_flush_stop ());
|
|
gst_pad_push_event (ttaparse->sinkpad, gst_event_new_flush_stop ());
|
|
}
|
|
|
|
gst_pad_push_event (ttaparse->srcpad, gst_event_new_new_segment (FALSE,
|
|
1.0, GST_FORMAT_TIME, 0,
|
|
ttaparse->num_frames * FRAME_TIME * GST_SECOND, 0));
|
|
|
|
gst_pad_start_task (ttaparse->sinkpad,
|
|
(GstTaskFunction) gst_tta_parse_loop, ttaparse);
|
|
|
|
GST_PAD_STREAM_UNLOCK (ttaparse->sinkpad);
|
|
|
|
} else {
|
|
res = FALSE;
|
|
}
|
|
|
|
gst_event_unref (event);
|
|
break;
|
|
}
|
|
default:
|
|
res = gst_pad_event_default (pad, event);
|
|
break;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
static const GstQueryType *
|
|
gst_tta_parse_get_query_types (GstPad * pad)
|
|
{
|
|
static const GstQueryType types[] = {
|
|
GST_QUERY_POSITION,
|
|
GST_QUERY_DURATION,
|
|
0
|
|
};
|
|
|
|
return types;
|
|
}
|
|
|
|
static gboolean
|
|
gst_tta_parse_query (GstPad * pad, GstQuery * query)
|
|
{
|
|
GstTtaParse *ttaparse = GST_TTA_PARSE (gst_pad_get_parent (pad));
|
|
|
|
switch (GST_QUERY_TYPE (query)) {
|
|
case GST_QUERY_POSITION:
|
|
{
|
|
GstFormat format;
|
|
gint64 cur;
|
|
|
|
gst_query_parse_position (query, &format, NULL);
|
|
switch (format) {
|
|
case GST_FORMAT_TIME:
|
|
cur = ttaparse->index[ttaparse->current_frame].time;
|
|
break;
|
|
default:
|
|
format = GST_FORMAT_BYTES;
|
|
cur = ttaparse->index[ttaparse->current_frame].pos;
|
|
break;
|
|
}
|
|
gst_query_set_position (query, format, cur);
|
|
break;
|
|
}
|
|
case GST_QUERY_DURATION:
|
|
{
|
|
GstFormat format;
|
|
gint64 end;
|
|
|
|
gst_query_parse_duration (query, &format, NULL);
|
|
switch (format) {
|
|
case GST_FORMAT_TIME:
|
|
end = ((gdouble) ttaparse->data_length /
|
|
(gdouble) ttaparse->samplerate) * GST_SECOND;
|
|
break;
|
|
default:
|
|
format = GST_FORMAT_BYTES;
|
|
end = ttaparse->index[ttaparse->num_frames].pos +
|
|
ttaparse->index[ttaparse->num_frames].size;
|
|
break;
|
|
}
|
|
gst_query_set_duration (query, format, end);
|
|
break;
|
|
}
|
|
default:
|
|
return FALSE;
|
|
break;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_tta_parse_activate (GstPad * pad)
|
|
{
|
|
if (gst_pad_check_pull_range (pad)) {
|
|
return gst_pad_activate_pull (pad, TRUE);
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_tta_parse_activate_pull (GstPad * pad, gboolean active)
|
|
{
|
|
GstTtaParse *ttaparse = GST_TTA_PARSE (GST_OBJECT_PARENT (pad));
|
|
|
|
if (active) {
|
|
gst_pad_start_task (pad, (GstTaskFunction) gst_tta_parse_loop, ttaparse);
|
|
} else {
|
|
gst_pad_stop_task (pad);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_tta_parse_parse_header (GstTtaParse * ttaparse)
|
|
{
|
|
guchar *data;
|
|
GstBuffer *buf = NULL;
|
|
guint32 crc;
|
|
double frame_length;
|
|
int num_frames;
|
|
GstCaps *caps;
|
|
int i;
|
|
guint32 offset;
|
|
GstEvent *discont;
|
|
|
|
if (gst_pad_pull_range (ttaparse->sinkpad, 0, 22, &buf) != GST_FLOW_OK)
|
|
goto pull_fail;
|
|
data = GST_BUFFER_DATA (buf);
|
|
ttaparse->channels = GST_READ_UINT16_LE (data + 6);
|
|
ttaparse->bits = GST_READ_UINT16_LE (data + 8);
|
|
ttaparse->samplerate = GST_READ_UINT32_LE (data + 10);
|
|
ttaparse->data_length = GST_READ_UINT32_LE (data + 14);
|
|
crc = crc32 (data, 18);
|
|
if (crc != GST_READ_UINT32_LE (data + 18)) {
|
|
GST_DEBUG ("Header CRC wrong!");
|
|
}
|
|
frame_length = FRAME_TIME * ttaparse->samplerate;
|
|
num_frames = (ttaparse->data_length / frame_length) + 1;
|
|
ttaparse->num_frames = num_frames;
|
|
gst_buffer_unref (buf);
|
|
|
|
ttaparse->index =
|
|
(GstTtaIndex *) g_malloc (num_frames * sizeof (GstTtaIndex));
|
|
if (gst_pad_pull_range (ttaparse->sinkpad,
|
|
22, num_frames * 4 + 4, &buf) != GST_FLOW_OK)
|
|
goto pull_fail;
|
|
data = GST_BUFFER_DATA (buf);
|
|
|
|
offset = 22 + num_frames * 4 + 4; // header size + seektable size
|
|
for (i = 0; i < num_frames; i++) {
|
|
ttaparse->index[i].size = GST_READ_UINT32_LE (data + i * 4);
|
|
ttaparse->index[i].pos = offset;
|
|
offset += ttaparse->index[i].size;
|
|
ttaparse->index[i].time = i * FRAME_TIME * GST_SECOND;
|
|
}
|
|
crc = crc32 (data, num_frames * 4);
|
|
if (crc != GST_READ_UINT32_LE (data + num_frames * 4)) {
|
|
GST_DEBUG ("Seektable CRC wrong!");
|
|
}
|
|
|
|
GST_DEBUG
|
|
("channels: %u, bits: %u, samplerate: %u, data_length: %u, num_frames: %u",
|
|
ttaparse->channels, ttaparse->bits, ttaparse->samplerate,
|
|
ttaparse->data_length, num_frames);
|
|
|
|
ttaparse->header_parsed = TRUE;
|
|
caps = gst_caps_new_simple ("audio/x-tta",
|
|
"width", G_TYPE_INT, ttaparse->bits,
|
|
"channels", G_TYPE_INT, ttaparse->channels,
|
|
"rate", G_TYPE_INT, ttaparse->samplerate, NULL);
|
|
gst_pad_set_caps (ttaparse->srcpad, caps);
|
|
|
|
discont =
|
|
gst_event_new_new_segment (FALSE, 1.0, GST_FORMAT_TIME, 0,
|
|
num_frames * FRAME_TIME * GST_SECOND, 0);
|
|
|
|
gst_pad_push_event (ttaparse->srcpad, discont);
|
|
|
|
return GST_FLOW_OK;
|
|
|
|
pull_fail:
|
|
{
|
|
GST_ELEMENT_ERROR (ttaparse, STREAM, DEMUX, (NULL),
|
|
("Couldn't read header"));
|
|
return GST_FLOW_ERROR;
|
|
}
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_tta_parse_stream_data (GstTtaParse * ttaparse)
|
|
{
|
|
GstBuffer *buf = NULL;
|
|
GstFlowReturn res = GST_FLOW_OK;
|
|
|
|
if (ttaparse->current_frame >= ttaparse->num_frames)
|
|
goto found_eos;
|
|
|
|
GST_DEBUG ("playing frame %u of %u", ttaparse->current_frame + 1,
|
|
ttaparse->num_frames);
|
|
if ((res = gst_pad_pull_range (ttaparse->sinkpad,
|
|
ttaparse->index[ttaparse->current_frame].pos,
|
|
ttaparse->index[ttaparse->current_frame].size,
|
|
&buf)) != GST_FLOW_OK)
|
|
goto pull_error;
|
|
|
|
GST_BUFFER_OFFSET (buf) = ttaparse->index[ttaparse->current_frame].pos;
|
|
GST_BUFFER_TIMESTAMP (buf) = ttaparse->index[ttaparse->current_frame].time;
|
|
if (ttaparse->current_frame + 1 == ttaparse->num_frames) {
|
|
guint32 samples =
|
|
ttaparse->data_length % (gint64) (ttaparse->samplerate * FRAME_TIME);
|
|
gdouble frametime = (gdouble) samples / (gdouble) ttaparse->samplerate;
|
|
|
|
GST_BUFFER_DURATION (buf) = (guint64) (frametime * GST_SECOND);
|
|
} else {
|
|
GST_BUFFER_DURATION (buf) = FRAME_TIME * GST_SECOND;
|
|
}
|
|
gst_buffer_set_caps (buf, GST_PAD_CAPS (ttaparse->srcpad));
|
|
|
|
if ((res = gst_pad_push (ttaparse->srcpad, buf)) != GST_FLOW_OK)
|
|
goto push_error;
|
|
ttaparse->current_frame++;
|
|
|
|
return res;
|
|
|
|
found_eos:
|
|
{
|
|
GST_DEBUG ("found EOS");
|
|
gst_pad_push_event (ttaparse->srcpad, gst_event_new_eos ());
|
|
return GST_FLOW_WRONG_STATE;
|
|
}
|
|
pull_error:
|
|
{
|
|
GST_DEBUG ("Error getting frame from the sinkpad");
|
|
return res;
|
|
}
|
|
push_error:
|
|
{
|
|
GST_DEBUG ("Error pushing on srcpad");
|
|
return res;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_tta_parse_loop (GstTtaParse * ttaparse)
|
|
{
|
|
GstFlowReturn ret;
|
|
|
|
if (!ttaparse->header_parsed)
|
|
if ((ret = gst_tta_parse_parse_header (ttaparse)) != GST_FLOW_OK)
|
|
goto pause;
|
|
if ((ret = gst_tta_parse_stream_data (ttaparse)) != GST_FLOW_OK)
|
|
goto pause;
|
|
|
|
return;
|
|
|
|
pause:
|
|
GST_LOG_OBJECT (ttaparse, "pausing task, %s", gst_flow_get_name (ret));
|
|
gst_pad_pause_task (ttaparse->sinkpad);
|
|
if (ret == GST_FLOW_UNEXPECTED) {
|
|
gst_pad_push_event (ttaparse->srcpad, gst_event_new_eos ());
|
|
} else if (ret < GST_FLOW_UNEXPECTED || ret == GST_FLOW_NOT_LINKED) {
|
|
GST_ELEMENT_ERROR (ttaparse, STREAM, FAILED,
|
|
("Internal data stream error."),
|
|
("streaming stopped, reason %s", gst_flow_get_name (ret)));
|
|
gst_pad_push_event (ttaparse->srcpad, gst_event_new_eos ());
|
|
}
|
|
}
|
|
|
|
static GstStateChangeReturn
|
|
gst_tta_parse_change_state (GstElement * element, GstStateChange transition)
|
|
{
|
|
GstStateChangeReturn ret;
|
|
GstTtaParse *ttaparse = GST_TTA_PARSE (element);
|
|
|
|
ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
|
|
|
|
switch (transition) {
|
|
case GST_STATE_CHANGE_PAUSED_TO_READY:
|
|
gst_tta_parse_reset (ttaparse);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
gboolean
|
|
gst_tta_parse_plugin_init (GstPlugin * plugin)
|
|
{
|
|
if (!gst_element_register (plugin, "ttaparse",
|
|
GST_RANK_NONE, GST_TYPE_TTA_PARSE)) {
|
|
return FALSE;
|
|
}
|
|
|
|
GST_DEBUG_CATEGORY_INIT (gst_tta_parse_debug, "ttaparse", 0,
|
|
"tta file parser");
|
|
|
|
return TRUE;
|
|
}
|