| /* GStreamer |
| * |
| * Copyright (C) 2008 Sebastian Dröge <sebastian.droege@collabora.co.uk>. |
| * Copyright (C) 2009 Mark Nauwelaerts <mark.nauwelaerts@collabora.co.uk> |
| * Copyright (C) 2009 Nokia Corporation. All rights reserved. |
| * Contact: Stefan Kost <stefan.kost@nokia.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. |
| */ |
| |
| /** |
| * SECTION:element-flacparse |
| * @see_also: flacdec, oggdemux, vorbisparse |
| * |
| * The flacparse element will parse the header packets of the FLAC |
| * stream and put them as the streamheader in the caps. This is used in the |
| * multifdsink case where you want to stream live FLAC streams to multiple |
| * clients, each client has to receive the streamheaders first before they can |
| * consume the FLAC packets. |
| * |
| * This element also makes sure that the buffers that it pushes out are properly |
| * timestamped and that their offset and offset_end are set. The buffers that |
| * flacparse outputs have all of the metadata that oggmux expects to receive, |
| * which allows you to (for example) remux an ogg/flac or convert a native FLAC |
| * format file to an ogg bitstream. |
| * |
| * <refsect2> |
| * <title>Example pipelines</title> |
| * |[ |
| * gst-launch-1.0 -v filesrc location=sine.flac ! flacparse ! identity \ |
| * ! oggmux ! filesink location=sine-remuxed.ogg |
| * ]| This pipeline converts a native FLAC format file to an ogg bitstream. |
| * It also illustrates that the streamheader is set in the caps, and that each |
| * buffer has the timestamp, duration, offset, and offset_end set. |
| * </refsect2> |
| * |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include "config.h" |
| #endif |
| |
| #include "gstflacparse.h" |
| |
| #include <string.h> |
| #include <gst/tag/tag.h> |
| #include <gst/audio/audio.h> |
| #include <gst/base/base.h> |
| #include <gst/pbutils/pbutils.h> |
| |
| GST_DEBUG_CATEGORY_STATIC (flacparse_debug); |
| #define GST_CAT_DEFAULT flacparse_debug |
| |
| /* CRC-8, poly = x^8 + x^2 + x^1 + x^0, init = 0 */ |
| static const guint8 crc8_table[256] = { |
| 0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15, |
| 0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D, |
| 0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65, |
| 0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D, |
| 0xE0, 0xE7, 0xEE, 0xE9, 0xFC, 0xFB, 0xF2, 0xF5, |
| 0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD, |
| 0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85, |
| 0xA8, 0xAF, 0xA6, 0xA1, 0xB4, 0xB3, 0xBA, 0xBD, |
| 0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2, |
| 0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA, |
| 0xB7, 0xB0, 0xB9, 0xBE, 0xAB, 0xAC, 0xA5, 0xA2, |
| 0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A, |
| 0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32, |
| 0x1F, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0D, 0x0A, |
| 0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42, |
| 0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A, |
| 0x89, 0x8E, 0x87, 0x80, 0x95, 0x92, 0x9B, 0x9C, |
| 0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4, |
| 0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC, |
| 0xC1, 0xC6, 0xCF, 0xC8, 0xDD, 0xDA, 0xD3, 0xD4, |
| 0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C, |
| 0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44, |
| 0x19, 0x1E, 0x17, 0x10, 0x05, 0x02, 0x0B, 0x0C, |
| 0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34, |
| 0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B, |
| 0x76, 0x71, 0x78, 0x7F, 0x6A, 0x6D, 0x64, 0x63, |
| 0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B, |
| 0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13, |
| 0xAE, 0xA9, 0xA0, 0xA7, 0xB2, 0xB5, 0xBC, 0xBB, |
| 0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83, |
| 0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB, |
| 0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4, 0xF3 |
| }; |
| |
| static guint8 |
| gst_flac_calculate_crc8 (const guint8 * data, guint length) |
| { |
| guint8 crc = 0; |
| |
| while (length--) { |
| crc = crc8_table[crc ^ *data]; |
| ++data; |
| } |
| |
| return crc; |
| } |
| |
| /* CRC-16, poly = x^16 + x^15 + x^2 + x^0, init = 0 */ |
| static const guint16 crc16_table[256] = { |
| 0x0000, 0x8005, 0x800f, 0x000a, 0x801b, 0x001e, 0x0014, 0x8011, |
| 0x8033, 0x0036, 0x003c, 0x8039, 0x0028, 0x802d, 0x8027, 0x0022, |
| 0x8063, 0x0066, 0x006c, 0x8069, 0x0078, 0x807d, 0x8077, 0x0072, |
| 0x0050, 0x8055, 0x805f, 0x005a, 0x804b, 0x004e, 0x0044, 0x8041, |
| 0x80c3, 0x00c6, 0x00cc, 0x80c9, 0x00d8, 0x80dd, 0x80d7, 0x00d2, |
| 0x00f0, 0x80f5, 0x80ff, 0x00fa, 0x80eb, 0x00ee, 0x00e4, 0x80e1, |
| 0x00a0, 0x80a5, 0x80af, 0x00aa, 0x80bb, 0x00be, 0x00b4, 0x80b1, |
| 0x8093, 0x0096, 0x009c, 0x8099, 0x0088, 0x808d, 0x8087, 0x0082, |
| 0x8183, 0x0186, 0x018c, 0x8189, 0x0198, 0x819d, 0x8197, 0x0192, |
| 0x01b0, 0x81b5, 0x81bf, 0x01ba, 0x81ab, 0x01ae, 0x01a4, 0x81a1, |
| 0x01e0, 0x81e5, 0x81ef, 0x01ea, 0x81fb, 0x01fe, 0x01f4, 0x81f1, |
| 0x81d3, 0x01d6, 0x01dc, 0x81d9, 0x01c8, 0x81cd, 0x81c7, 0x01c2, |
| 0x0140, 0x8145, 0x814f, 0x014a, 0x815b, 0x015e, 0x0154, 0x8151, |
| 0x8173, 0x0176, 0x017c, 0x8179, 0x0168, 0x816d, 0x8167, 0x0162, |
| 0x8123, 0x0126, 0x012c, 0x8129, 0x0138, 0x813d, 0x8137, 0x0132, |
| 0x0110, 0x8115, 0x811f, 0x011a, 0x810b, 0x010e, 0x0104, 0x8101, |
| 0x8303, 0x0306, 0x030c, 0x8309, 0x0318, 0x831d, 0x8317, 0x0312, |
| 0x0330, 0x8335, 0x833f, 0x033a, 0x832b, 0x032e, 0x0324, 0x8321, |
| 0x0360, 0x8365, 0x836f, 0x036a, 0x837b, 0x037e, 0x0374, 0x8371, |
| 0x8353, 0x0356, 0x035c, 0x8359, 0x0348, 0x834d, 0x8347, 0x0342, |
| 0x03c0, 0x83c5, 0x83cf, 0x03ca, 0x83db, 0x03de, 0x03d4, 0x83d1, |
| 0x83f3, 0x03f6, 0x03fc, 0x83f9, 0x03e8, 0x83ed, 0x83e7, 0x03e2, |
| 0x83a3, 0x03a6, 0x03ac, 0x83a9, 0x03b8, 0x83bd, 0x83b7, 0x03b2, |
| 0x0390, 0x8395, 0x839f, 0x039a, 0x838b, 0x038e, 0x0384, 0x8381, |
| 0x0280, 0x8285, 0x828f, 0x028a, 0x829b, 0x029e, 0x0294, 0x8291, |
| 0x82b3, 0x02b6, 0x02bc, 0x82b9, 0x02a8, 0x82ad, 0x82a7, 0x02a2, |
| 0x82e3, 0x02e6, 0x02ec, 0x82e9, 0x02f8, 0x82fd, 0x82f7, 0x02f2, |
| 0x02d0, 0x82d5, 0x82df, 0x02da, 0x82cb, 0x02ce, 0x02c4, 0x82c1, |
| 0x8243, 0x0246, 0x024c, 0x8249, 0x0258, 0x825d, 0x8257, 0x0252, |
| 0x0270, 0x8275, 0x827f, 0x027a, 0x826b, 0x026e, 0x0264, 0x8261, |
| 0x0220, 0x8225, 0x822f, 0x022a, 0x823b, 0x023e, 0x0234, 0x8231, |
| 0x8213, 0x0216, 0x021c, 0x8219, 0x0208, 0x820d, 0x8207, 0x0202 |
| }; |
| |
| static guint16 |
| gst_flac_calculate_crc16 (const guint8 * data, guint length) |
| { |
| guint16 crc = 0; |
| |
| while (length--) { |
| crc = ((crc << 8) ^ crc16_table[(crc >> 8) ^ *data]) & 0xffff; |
| data++; |
| } |
| |
| return crc; |
| } |
| |
| enum |
| { |
| PROP_0, |
| PROP_CHECK_FRAME_CHECKSUMS |
| }; |
| |
| #define DEFAULT_CHECK_FRAME_CHECKSUMS FALSE |
| |
| static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src", |
| GST_PAD_SRC, |
| GST_PAD_ALWAYS, |
| GST_STATIC_CAPS ("audio/x-flac, framed = (boolean) true, " |
| "channels = (int) [ 1, 8 ], " "rate = (int) [ 1, 655350 ]") |
| ); |
| |
| static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink", |
| GST_PAD_SINK, |
| GST_PAD_ALWAYS, |
| GST_STATIC_CAPS ("audio/x-flac") |
| ); |
| |
| static GstBuffer *gst_flac_parse_generate_vorbiscomment (GstFlacParse * |
| flacparse); |
| |
| static inline void gst_flac_parse_reset_buffer_time_and_offset (GstBuffer * |
| buffer); |
| static void gst_flac_parse_reset (GstFlacParse * parser); |
| static gboolean gst_flac_parse_handle_block_type (GstFlacParse * flacparse, |
| guint type, GstBuffer * sbuffer); |
| static void gst_flac_parse_finalize (GObject * object); |
| static void gst_flac_parse_set_property (GObject * object, guint prop_id, |
| const GValue * value, GParamSpec * pspec); |
| static void gst_flac_parse_get_property (GObject * object, guint prop_id, |
| GValue * value, GParamSpec * pspec); |
| |
| static gboolean gst_flac_parse_start (GstBaseParse * parse); |
| static gboolean gst_flac_parse_stop (GstBaseParse * parse); |
| static GstFlowReturn gst_flac_parse_handle_frame (GstBaseParse * parse, |
| GstBaseParseFrame * frame, gint * skipsize); |
| static GstFlowReturn gst_flac_parse_parse_frame (GstBaseParse * parse, |
| GstBaseParseFrame * frame, gint size); |
| static GstFlowReturn gst_flac_parse_pre_push_frame (GstBaseParse * parse, |
| GstBaseParseFrame * frame); |
| static gboolean gst_flac_parse_convert (GstBaseParse * parse, |
| GstFormat src_format, gint64 src_value, GstFormat dest_format, |
| gint64 * dest_value); |
| static gboolean gst_flac_parse_src_event (GstBaseParse * parse, |
| GstEvent * event); |
| static GstCaps *gst_flac_parse_get_sink_caps (GstBaseParse * parse, |
| GstCaps * filter); |
| static gboolean gst_flac_parse_set_sink_caps (GstBaseParse * parse, |
| GstCaps * caps); |
| |
| #define gst_flac_parse_parent_class parent_class |
| G_DEFINE_TYPE (GstFlacParse, gst_flac_parse, GST_TYPE_BASE_PARSE); |
| |
| static void |
| gst_flac_parse_class_init (GstFlacParseClass * klass) |
| { |
| GObjectClass *gobject_class = G_OBJECT_CLASS (klass); |
| GstElementClass *element_class = GST_ELEMENT_CLASS (klass); |
| GstBaseParseClass *baseparse_class = GST_BASE_PARSE_CLASS (klass); |
| |
| GST_DEBUG_CATEGORY_INIT (flacparse_debug, "flacparse", 0, |
| "Flac parser element"); |
| |
| gobject_class->finalize = gst_flac_parse_finalize; |
| gobject_class->set_property = gst_flac_parse_set_property; |
| gobject_class->get_property = gst_flac_parse_get_property; |
| |
| g_object_class_install_property (gobject_class, PROP_CHECK_FRAME_CHECKSUMS, |
| g_param_spec_boolean ("check-frame-checksums", "Check Frame Checksums", |
| "Check the overall checksums of every frame", |
| DEFAULT_CHECK_FRAME_CHECKSUMS, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| baseparse_class->start = GST_DEBUG_FUNCPTR (gst_flac_parse_start); |
| baseparse_class->stop = GST_DEBUG_FUNCPTR (gst_flac_parse_stop); |
| baseparse_class->handle_frame = |
| GST_DEBUG_FUNCPTR (gst_flac_parse_handle_frame); |
| baseparse_class->pre_push_frame = |
| GST_DEBUG_FUNCPTR (gst_flac_parse_pre_push_frame); |
| baseparse_class->convert = GST_DEBUG_FUNCPTR (gst_flac_parse_convert); |
| baseparse_class->src_event = GST_DEBUG_FUNCPTR (gst_flac_parse_src_event); |
| baseparse_class->get_sink_caps = |
| GST_DEBUG_FUNCPTR (gst_flac_parse_get_sink_caps); |
| baseparse_class->set_sink_caps = |
| GST_DEBUG_FUNCPTR (gst_flac_parse_set_sink_caps); |
| |
| gst_element_class_add_static_pad_template (element_class, &src_factory); |
| gst_element_class_add_static_pad_template (element_class, &sink_factory); |
| |
| gst_element_class_set_static_metadata (element_class, "FLAC audio parser", |
| "Codec/Parser/Audio", |
| "Parses audio with the FLAC lossless audio codec", |
| "Sebastian Dröge <sebastian.droege@collabora.co.uk>"); |
| } |
| |
| static void |
| gst_flac_parse_init (GstFlacParse * flacparse) |
| { |
| flacparse->check_frame_checksums = DEFAULT_CHECK_FRAME_CHECKSUMS; |
| GST_PAD_SET_ACCEPT_INTERSECT (GST_BASE_PARSE_SINK_PAD (flacparse)); |
| GST_PAD_SET_ACCEPT_TEMPLATE (GST_BASE_PARSE_SINK_PAD (flacparse)); |
| } |
| |
| static void |
| gst_flac_parse_set_property (GObject * object, guint prop_id, |
| const GValue * value, GParamSpec * pspec) |
| { |
| GstFlacParse *flacparse = GST_FLAC_PARSE (object); |
| |
| switch (prop_id) { |
| case PROP_CHECK_FRAME_CHECKSUMS: |
| flacparse->check_frame_checksums = g_value_get_boolean (value); |
| break; |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
| break; |
| } |
| } |
| |
| static void |
| gst_flac_parse_get_property (GObject * object, guint prop_id, |
| GValue * value, GParamSpec * pspec) |
| { |
| GstFlacParse *flacparse = GST_FLAC_PARSE (object); |
| |
| switch (prop_id) { |
| case PROP_CHECK_FRAME_CHECKSUMS: |
| g_value_set_boolean (value, flacparse->check_frame_checksums); |
| break; |
| default: |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
| break; |
| } |
| } |
| |
| static void |
| gst_flac_parse_reset (GstFlacParse * parser) |
| { |
| if (parser->tags) { |
| gst_tag_list_unref (parser->tags); |
| parser->tags = NULL; |
| } |
| if (parser->toc) { |
| gst_toc_unref (parser->toc); |
| parser->toc = NULL; |
| } |
| if (parser->seektable) { |
| gst_buffer_unref (parser->seektable); |
| parser->seektable = NULL; |
| } |
| |
| g_list_foreach (parser->headers, (GFunc) gst_mini_object_unref, NULL); |
| g_list_free (parser->headers); |
| parser->headers = NULL; |
| } |
| |
| static void |
| gst_flac_parse_finalize (GObject * object) |
| { |
| GstFlacParse *flacparse = GST_FLAC_PARSE (object); |
| |
| gst_flac_parse_reset (flacparse); |
| G_OBJECT_CLASS (parent_class)->finalize (object); |
| } |
| |
| static gboolean |
| gst_flac_parse_start (GstBaseParse * parse) |
| { |
| GstFlacParse *flacparse = GST_FLAC_PARSE (parse); |
| |
| flacparse->state = GST_FLAC_PARSE_STATE_INIT; |
| flacparse->min_blocksize = 0; |
| flacparse->max_blocksize = 0; |
| flacparse->min_framesize = 0; |
| flacparse->max_framesize = 0; |
| |
| flacparse->upstream_length = -1; |
| |
| flacparse->samplerate = 0; |
| flacparse->channels = 0; |
| flacparse->bps = 0; |
| flacparse->total_samples = 0; |
| |
| flacparse->offset = GST_CLOCK_TIME_NONE; |
| flacparse->blocking_strategy = 0; |
| flacparse->block_size = 0; |
| flacparse->sample_number = 0; |
| flacparse->strategy_checked = FALSE; |
| |
| flacparse->sent_codec_tag = FALSE; |
| |
| /* "fLaC" marker */ |
| gst_base_parse_set_min_frame_size (GST_BASE_PARSE (flacparse), 4); |
| |
| /* inform baseclass we can come up with ts, based on counters in packets */ |
| gst_base_parse_set_has_timing_info (GST_BASE_PARSE_CAST (flacparse), TRUE); |
| gst_base_parse_set_syncable (GST_BASE_PARSE_CAST (flacparse), TRUE); |
| |
| return TRUE; |
| } |
| |
| static gboolean |
| gst_flac_parse_stop (GstBaseParse * parse) |
| { |
| GstFlacParse *flacparse = GST_FLAC_PARSE (parse); |
| |
| gst_flac_parse_reset (flacparse); |
| return TRUE; |
| } |
| |
| static const guint8 sample_size_table[] = { 0, 8, 12, 0, 16, 20, 24, 0 }; |
| |
| static const guint16 blocksize_table[16] = { |
| 0, 192, 576 << 0, 576 << 1, 576 << 2, 576 << 3, 0, 0, |
| 256 << 0, 256 << 1, 256 << 2, 256 << 3, 256 << 4, 256 << 5, 256 << 6, |
| 256 << 7, |
| }; |
| |
| static const guint32 sample_rate_table[16] = { |
| 0, |
| 88200, 176400, 192000, |
| 8000, 16000, 22050, 24000, 32000, 44100, 48000, 96000, |
| 0, 0, 0, 0, |
| }; |
| |
| typedef enum |
| { |
| FRAME_HEADER_VALID, |
| FRAME_HEADER_INVALID, |
| FRAME_HEADER_MORE_DATA |
| } FrameHeaderCheckReturn; |
| |
| static FrameHeaderCheckReturn |
| gst_flac_parse_frame_header_is_valid (GstFlacParse * flacparse, |
| const guint8 * data, guint size, gboolean set, guint16 * block_size_ret, |
| gboolean * suspect) |
| { |
| GstBitReader reader = GST_BIT_READER_INIT (data, size); |
| guint8 blocking_strategy; |
| guint16 block_size; |
| guint32 samplerate = 0; |
| guint64 sample_number; |
| guint8 channels, bps; |
| guint8 tmp = 0; |
| guint8 actual_crc, expected_crc = 0; |
| |
| /* Skip 14 bit sync code */ |
| gst_bit_reader_skip_unchecked (&reader, 14); |
| |
| /* Must be 0 */ |
| if (gst_bit_reader_get_bits_uint8_unchecked (&reader, 1) != 0) |
| goto error; |
| |
| /* 0 == fixed block size, 1 == variable block size */ |
| blocking_strategy = gst_bit_reader_get_bits_uint8_unchecked (&reader, 1); |
| if (flacparse->force_variable_block_size) |
| blocking_strategy = 1; |
| |
| /* block size index, calculation of the real blocksize below */ |
| block_size = gst_bit_reader_get_bits_uint16_unchecked (&reader, 4); |
| if (block_size == 0) |
| goto error; |
| |
| /* sample rate index, calculation of the real samplerate below */ |
| samplerate = gst_bit_reader_get_bits_uint16_unchecked (&reader, 4); |
| if (samplerate == 0x0f) |
| goto error; |
| |
| /* channel assignment */ |
| channels = gst_bit_reader_get_bits_uint8_unchecked (&reader, 4); |
| if (channels < 8) { |
| channels++; |
| } else if (channels <= 10) { |
| channels = 2; |
| } else if (channels > 10) { |
| goto error; |
| } |
| if (flacparse->channels && flacparse->channels != channels) |
| goto error; |
| |
| /* bits per sample */ |
| bps = gst_bit_reader_get_bits_uint8_unchecked (&reader, 3); |
| if (bps == 0x03 || bps == 0x07) { |
| goto error; |
| } else if (bps == 0 && flacparse->bps == 0) { |
| goto need_streaminfo; |
| } |
| bps = sample_size_table[bps]; |
| if (flacparse->bps && bps != flacparse->bps) |
| goto error; |
| |
| /* reserved, must be 0 */ |
| if (gst_bit_reader_get_bits_uint8_unchecked (&reader, 1) != 0) |
| goto error; |
| |
| /* read "utf8" encoded sample/frame number */ |
| { |
| gint len = 0; |
| |
| len = gst_bit_reader_get_bits_uint8_unchecked (&reader, 8); |
| |
| /* This is slightly faster than a loop */ |
| if (!(len & 0x80)) { |
| sample_number = len; |
| len = 0; |
| } else if ((len & 0xc0) && !(len & 0x20)) { |
| sample_number = len & 0x1f; |
| len = 1; |
| } else if ((len & 0xe0) && !(len & 0x10)) { |
| sample_number = len & 0x0f; |
| len = 2; |
| } else if ((len & 0xf0) && !(len & 0x08)) { |
| sample_number = len & 0x07; |
| len = 3; |
| } else if ((len & 0xf8) && !(len & 0x04)) { |
| sample_number = len & 0x03; |
| len = 4; |
| } else if ((len & 0xfc) && !(len & 0x02)) { |
| sample_number = len & 0x01; |
| len = 5; |
| } else if ((len & 0xfe) && !(len & 0x01)) { |
| sample_number = len & 0x0; |
| len = 6; |
| } else { |
| goto error; |
| } |
| |
| if ((blocking_strategy == 0 && len > 5) || |
| (blocking_strategy == 1 && len > 6)) |
| goto error; |
| |
| while (len > 0) { |
| if (!gst_bit_reader_get_bits_uint8 (&reader, &tmp, 8)) |
| goto need_more_data; |
| |
| if ((tmp & 0xc0) != 0x80) |
| goto error; |
| |
| sample_number <<= 6; |
| sample_number |= (tmp & 0x3f); |
| len--; |
| } |
| } |
| |
| /* calculate real blocksize from the blocksize index */ |
| if (block_size == 6) { |
| if (!gst_bit_reader_get_bits_uint16 (&reader, &block_size, 8)) |
| goto need_more_data; |
| block_size++; |
| } else if (block_size == 7) { |
| if (!gst_bit_reader_get_bits_uint16 (&reader, &block_size, 16)) |
| goto need_more_data; |
| block_size++; |
| } else { |
| block_size = blocksize_table[block_size]; |
| } |
| |
| /* calculate the real samplerate from the samplerate index */ |
| if (samplerate == 0 && flacparse->samplerate == 0) { |
| goto need_streaminfo; |
| } else if (samplerate < 12) { |
| samplerate = sample_rate_table[samplerate]; |
| } else if (samplerate == 12) { |
| if (!gst_bit_reader_get_bits_uint32 (&reader, &samplerate, 8)) |
| goto need_more_data; |
| samplerate *= 1000; |
| } else if (samplerate == 13) { |
| if (!gst_bit_reader_get_bits_uint32 (&reader, &samplerate, 16)) |
| goto need_more_data; |
| } else if (samplerate == 14) { |
| if (!gst_bit_reader_get_bits_uint32 (&reader, &samplerate, 16)) |
| goto need_more_data; |
| samplerate *= 10; |
| } |
| |
| if (flacparse->samplerate && flacparse->samplerate != samplerate) |
| goto error; |
| |
| /* check crc-8 for the header */ |
| if (!gst_bit_reader_get_bits_uint8 (&reader, &expected_crc, 8)) |
| goto need_more_data; |
| |
| actual_crc = |
| gst_flac_calculate_crc8 (data, |
| (gst_bit_reader_get_pos (&reader) / 8) - 1); |
| if (actual_crc != expected_crc) { |
| GST_DEBUG_OBJECT (flacparse, |
| "Checksum mismatch. Header CRC was '%d' but frame has '%d'", |
| expected_crc, actual_crc); |
| goto error; |
| } |
| |
| /* Sanity check sample number against blocking strategy, as it seems |
| some files claim fixed block size but supply sample numbers, |
| rather than block numbers. */ |
| if (blocking_strategy == 0 && flacparse->block_size != 0) { |
| if (!flacparse->strategy_checked) { |
| if (block_size == sample_number) { |
| GST_WARNING_OBJECT (flacparse, "This file claims fixed block size, " |
| "but seems to be lying: assuming variable block size"); |
| flacparse->force_variable_block_size = TRUE; |
| blocking_strategy = 1; |
| } |
| flacparse->strategy_checked = TRUE; |
| } |
| } |
| |
| /* documentation says: |
| * The "blocking strategy" bit must be the same throughout the entire stream. */ |
| if (flacparse->blocking_strategy != blocking_strategy) { |
| if (flacparse->block_size != 0) { |
| GST_WARNING_OBJECT (flacparse, "blocking strategy is not constant"); |
| if (suspect) |
| *suspect = TRUE; |
| } |
| } |
| |
| /* |
| The FLAC format documentation says: |
| The "blocking strategy" bit determines how to calculate the sample number |
| of the first sample in the frame. If the bit is 0 (fixed-blocksize), the |
| frame header encodes the frame number as above, and the frame's starting |
| sample number will be the frame number times the blocksize. If it is 1 |
| (variable-blocksize), the frame header encodes the frame's starting |
| sample number itself. (In the case of a fixed-blocksize stream, only the |
| last block may be shorter than the stream blocksize; its starting sample |
| number will be calculated as the frame number times the previous frame's |
| blocksize, or zero if it is the first frame). |
| |
| Therefore, when in fixed block size mode, we only update the block size |
| the first time, then reuse that block size for subsequent calls. |
| This will also fix a timestamp problem with the last block's timestamp |
| being miscalculated by scaling the block number by a "wrong" block size. |
| */ |
| if (blocking_strategy == 0) { |
| if (flacparse->block_size != 0) { |
| /* after first block */ |
| if (flacparse->block_size != block_size) { |
| /* TODO: can we know we're on the last frame, to avoid warning ? */ |
| GST_WARNING_OBJECT (flacparse, "Block size is not constant"); |
| block_size = flacparse->block_size; |
| if (suspect) |
| *suspect = TRUE; |
| } |
| } |
| } |
| |
| if (set) { |
| flacparse->block_size = block_size; |
| if (!flacparse->samplerate) |
| flacparse->samplerate = samplerate; |
| if (!flacparse->bps) |
| flacparse->bps = bps; |
| if (!flacparse->blocking_strategy) |
| flacparse->blocking_strategy = blocking_strategy; |
| if (!flacparse->channels) |
| flacparse->channels = channels; |
| if (!flacparse->sample_number) |
| flacparse->sample_number = sample_number; |
| |
| GST_DEBUG_OBJECT (flacparse, |
| "Parsed frame at offset %" G_GUINT64_FORMAT ":\n" "Block size: %u\n" |
| "Sample/Frame number: %" G_GUINT64_FORMAT, flacparse->offset, |
| flacparse->block_size, flacparse->sample_number); |
| } |
| |
| if (block_size_ret) |
| *block_size_ret = block_size; |
| |
| return FRAME_HEADER_VALID; |
| |
| need_streaminfo: |
| GST_ERROR_OBJECT (flacparse, "Need STREAMINFO metadata. Bits per sample " |
| "or sample rate not in frame header"); |
| error: |
| return FRAME_HEADER_INVALID; |
| |
| need_more_data: |
| return FRAME_HEADER_MORE_DATA; |
| } |
| |
| static gboolean |
| gst_flac_parse_frame_is_valid (GstFlacParse * flacparse, |
| const guint8 * data, gsize size, guint * ret) |
| { |
| guint max, remaining; |
| guint i, search_start, search_end; |
| FrameHeaderCheckReturn header_ret; |
| guint16 block_size; |
| gboolean suspect_start = FALSE, suspect_end = FALSE; |
| |
| if (size < flacparse->min_framesize) |
| goto need_more; |
| |
| header_ret = |
| gst_flac_parse_frame_header_is_valid (flacparse, data, size, TRUE, |
| &block_size, &suspect_start); |
| if (header_ret == FRAME_HEADER_INVALID) { |
| *ret = 0; |
| return FALSE; |
| } |
| if (header_ret == FRAME_HEADER_MORE_DATA) |
| goto need_more; |
| |
| /* mind unknown framesize */ |
| search_start = MAX (2, flacparse->min_framesize); |
| if (flacparse->max_framesize) |
| search_end = MIN (size, flacparse->max_framesize + 9 + 2); |
| else |
| search_end = size; |
| search_end -= 2; |
| |
| remaining = size; |
| |
| for (i = search_start; i < search_end; i++, remaining--) { |
| |
| if ((GST_READ_UINT16_BE (data + i) & 0xfffe) != 0xfff8) |
| continue; |
| |
| GST_LOG_OBJECT (flacparse, "possible frame end at offset %d", i); |
| suspect_end = FALSE; |
| header_ret = |
| gst_flac_parse_frame_header_is_valid (flacparse, data + i, |
| remaining, FALSE, NULL, &suspect_end); |
| if (header_ret == FRAME_HEADER_VALID) { |
| if (flacparse->check_frame_checksums || suspect_start || suspect_end) { |
| guint16 actual_crc = gst_flac_calculate_crc16 (data, i - 2); |
| guint16 expected_crc = GST_READ_UINT16_BE (data + i - 2); |
| |
| GST_LOG_OBJECT (flacparse, |
| "Found possible frame (%d, %d). Checking for CRC match", |
| suspect_start, suspect_end); |
| if (actual_crc != expected_crc) { |
| GST_DEBUG_OBJECT (flacparse, |
| "Checksum mismatch. Header CRC was '%d' but frame has '%d'", |
| expected_crc, actual_crc); |
| continue; |
| } |
| } |
| *ret = i; |
| flacparse->block_size = block_size; |
| return TRUE; |
| } else if (header_ret == FRAME_HEADER_MORE_DATA) { |
| goto need_more; |
| } |
| } |
| |
| /* For the last frame output everything to the end */ |
| if (G_UNLIKELY (GST_BASE_PARSE_DRAINING (flacparse))) { |
| if (flacparse->check_frame_checksums) { |
| guint16 actual_crc = gst_flac_calculate_crc16 (data, size - 2); |
| guint16 expected_crc = GST_READ_UINT16_BE (data + size - 2); |
| |
| if (actual_crc == expected_crc) { |
| *ret = size; |
| flacparse->block_size = block_size; |
| return TRUE; |
| } |
| } else { |
| *ret = size; |
| flacparse->block_size = block_size; |
| return TRUE; |
| } |
| } |
| |
| /* so we searched to expected end and found nothing, |
| * give up on this frame (start) */ |
| if (flacparse->max_framesize && i > 2 * flacparse->max_framesize) { |
| GST_LOG_OBJECT (flacparse, |
| "could not determine valid frame end, discarding frame (start)"); |
| *ret = 1; |
| return FALSE; |
| } |
| |
| need_more: |
| max = flacparse->max_framesize + 16; |
| if (max == 16) |
| max = 1 << 24; |
| *ret = MIN (size + 4096, max); |
| return TRUE; |
| } |
| |
| static GstFlowReturn |
| gst_flac_parse_handle_frame (GstBaseParse * parse, |
| GstBaseParseFrame * frame, gint * skipsize) |
| { |
| GstFlacParse *flacparse = GST_FLAC_PARSE (parse); |
| GstBuffer *buffer = frame->buffer; |
| GstMapInfo map; |
| gboolean result = TRUE; |
| GstFlowReturn ret = GST_FLOW_OK; |
| guint framesize = 0; |
| |
| gst_buffer_map (buffer, &map, GST_MAP_READ); |
| |
| *skipsize = 1; |
| |
| if (G_UNLIKELY (map.size < 4)) { |
| result = FALSE; |
| goto cleanup; |
| } |
| |
| if (flacparse->state == GST_FLAC_PARSE_STATE_INIT) { |
| if (memcmp (map.data, "fLaC", 4) == 0) { |
| GST_DEBUG_OBJECT (flacparse, "fLaC marker found"); |
| framesize = 4; |
| goto cleanup; |
| } |
| if (map.data[0] == 0xff && (map.data[1] >> 2) == 0x3e) { |
| GST_DEBUG_OBJECT (flacparse, "Found headerless FLAC"); |
| /* Minimal size of a frame header */ |
| gst_base_parse_set_min_frame_size (GST_BASE_PARSE (flacparse), 9); |
| flacparse->state = GST_FLAC_PARSE_STATE_GENERATE_HEADERS; |
| *skipsize = 0; |
| result = FALSE; |
| goto cleanup; |
| } |
| GST_DEBUG_OBJECT (flacparse, "fLaC marker not found"); |
| result = FALSE; |
| goto cleanup; |
| } |
| |
| if (flacparse->state == GST_FLAC_PARSE_STATE_HEADERS) { |
| guint size = 4 + ((map.data[1] << 16) | (map.data[2] << 8) | (map.data[3])); |
| |
| GST_DEBUG_OBJECT (flacparse, "Found metadata block of size %u", size); |
| framesize = size; |
| gst_base_parse_set_min_frame_size (GST_BASE_PARSE (flacparse), framesize); |
| goto cleanup; |
| } |
| |
| if ((GST_READ_UINT16_BE (map.data) & 0xfffe) == 0xfff8) { |
| gboolean ret, is_first = !flacparse->strategy_checked; |
| guint next; |
| |
| flacparse->offset = GST_BUFFER_OFFSET (buffer); |
| flacparse->blocking_strategy = 0; |
| flacparse->sample_number = 0; |
| |
| GST_DEBUG_OBJECT (flacparse, "Found sync code"); |
| ret = gst_flac_parse_frame_is_valid (flacparse, map.data, map.size, &next); |
| if (ret) { |
| if (is_first) { |
| GST_INFO_OBJECT (flacparse, "First sample number is %" G_GUINT64_FORMAT, |
| flacparse->sample_number); |
| flacparse->first_sample_number = flacparse->sample_number; |
| } |
| framesize = next; |
| goto cleanup; |
| } |
| |
| /* If we're at EOS and the frame was not valid, drop it! */ |
| if (G_UNLIKELY (GST_BASE_PARSE_DRAINING (flacparse))) { |
| GST_WARNING_OBJECT (flacparse, "EOS"); |
| result = FALSE; |
| goto cleanup; |
| } |
| |
| if (next == 0) { |
| } else if (next > map.size) { |
| GST_DEBUG_OBJECT (flacparse, "Requesting %u bytes", next); |
| *skipsize = 0; |
| gst_base_parse_set_min_frame_size (parse, next); |
| result = FALSE; |
| goto cleanup; |
| } else { |
| GST_ERROR_OBJECT (flacparse, |
| "Giving up on invalid frame (%" G_GSIZE_FORMAT " bytes)", map.size); |
| result = FALSE; |
| goto cleanup; |
| } |
| } else { |
| GstByteReader reader; |
| gint off; |
| |
| gst_byte_reader_init (&reader, map.data, map.size); |
| off = |
| gst_byte_reader_masked_scan_uint32 (&reader, 0xfffc0000, 0xfff80000, |
| 0, map.size); |
| |
| if (off > 0) { |
| GST_DEBUG_OBJECT (parse, "Possible sync at buffer offset %d", off); |
| *skipsize = off; |
| result = FALSE; |
| goto cleanup; |
| } |
| |
| GST_DEBUG_OBJECT (flacparse, "Sync code not found"); |
| *skipsize = map.size - 3; |
| result = FALSE; |
| goto cleanup; |
| } |
| |
| result = FALSE; |
| |
| cleanup: |
| gst_buffer_unmap (buffer, &map); |
| |
| if (result) |
| *skipsize = 0; |
| |
| if (result && framesize <= map.size) { |
| ret = gst_flac_parse_parse_frame (parse, frame, framesize); |
| if (ret == GST_BASE_PARSE_FLOW_DROPPED) { |
| frame->flags |= GST_BASE_PARSE_FRAME_FLAG_DROP; |
| ret = GST_FLOW_OK; |
| } |
| if (ret == GST_FLOW_OK) |
| ret = gst_base_parse_finish_frame (parse, frame, framesize); |
| } |
| |
| return ret; |
| } |
| |
| static gboolean |
| gst_flac_parse_handle_streaminfo (GstFlacParse * flacparse, GstBuffer * buffer) |
| { |
| GstBitReader reader; |
| GstMapInfo map; |
| |
| gst_buffer_map (buffer, &map, GST_MAP_READ); |
| gst_bit_reader_init (&reader, map.data, map.size); |
| |
| if (map.size != 4 + 34) { |
| GST_ERROR_OBJECT (flacparse, |
| "Invalid metablock size for STREAMINFO: %" G_GSIZE_FORMAT "", map.size); |
| goto failure; |
| } |
| |
| /* Skip metadata block header */ |
| if (!gst_bit_reader_skip (&reader, 32)) |
| goto error; |
| |
| if (!gst_bit_reader_get_bits_uint16 (&reader, &flacparse->min_blocksize, 16)) |
| goto error; |
| if (flacparse->min_blocksize < 16) { |
| GST_WARNING_OBJECT (flacparse, "Invalid minimum block size: %u", |
| flacparse->min_blocksize); |
| } |
| |
| if (!gst_bit_reader_get_bits_uint16 (&reader, &flacparse->max_blocksize, 16)) |
| goto error; |
| if (flacparse->max_blocksize < 16) { |
| GST_WARNING_OBJECT (flacparse, "Invalid maximum block size: %u", |
| flacparse->max_blocksize); |
| } |
| |
| if (!gst_bit_reader_get_bits_uint32 (&reader, &flacparse->min_framesize, 24)) |
| goto error; |
| if (!gst_bit_reader_get_bits_uint32 (&reader, &flacparse->max_framesize, 24)) |
| goto error; |
| |
| if (!gst_bit_reader_get_bits_uint32 (&reader, &flacparse->samplerate, 20)) |
| goto error; |
| if (flacparse->samplerate == 0) { |
| GST_ERROR_OBJECT (flacparse, "Invalid sample rate 0"); |
| goto failure; |
| } |
| |
| if (!gst_bit_reader_get_bits_uint8 (&reader, &flacparse->channels, 3)) |
| goto error; |
| flacparse->channels++; |
| if (flacparse->channels > 8) { |
| GST_ERROR_OBJECT (flacparse, "Invalid number of channels %u", |
| flacparse->channels); |
| goto failure; |
| } |
| |
| if (!gst_bit_reader_get_bits_uint8 (&reader, &flacparse->bps, 5)) |
| goto error; |
| flacparse->bps++; |
| |
| if (!gst_bit_reader_get_bits_uint64 (&reader, &flacparse->total_samples, 36)) |
| goto error; |
| if (flacparse->total_samples) { |
| gst_base_parse_set_duration (GST_BASE_PARSE (flacparse), |
| GST_FORMAT_DEFAULT, flacparse->total_samples, 0); |
| } |
| |
| gst_buffer_unmap (buffer, &map); |
| |
| GST_DEBUG_OBJECT (flacparse, "STREAMINFO:\n" |
| "\tmin/max blocksize: %u/%u,\n" |
| "\tmin/max framesize: %u/%u,\n" |
| "\tsamplerate: %u,\n" |
| "\tchannels: %u,\n" |
| "\tbits per sample: %u,\n" |
| "\ttotal samples: %" G_GUINT64_FORMAT, |
| flacparse->min_blocksize, flacparse->max_blocksize, |
| flacparse->min_framesize, flacparse->max_framesize, |
| flacparse->samplerate, |
| flacparse->channels, flacparse->bps, flacparse->total_samples); |
| |
| return TRUE; |
| |
| error: |
| GST_ERROR_OBJECT (flacparse, "Failed to read data"); |
| failure: |
| gst_buffer_unmap (buffer, &map); |
| return FALSE; |
| } |
| |
| static gboolean |
| gst_flac_parse_handle_vorbiscomment (GstFlacParse * flacparse, |
| GstBuffer * buffer) |
| { |
| GstTagList *tags; |
| GstMapInfo map; |
| |
| gst_buffer_map (buffer, &map, GST_MAP_READ); |
| |
| tags = |
| gst_tag_list_from_vorbiscomment (map.data, map.size, map.data, 4, NULL); |
| gst_buffer_unmap (buffer, &map); |
| |
| if (tags == NULL) { |
| GST_ERROR_OBJECT (flacparse, "Invalid vorbiscomment block"); |
| } else if (gst_tag_list_is_empty (tags)) { |
| gst_tag_list_unref (tags); |
| } else if (flacparse->tags == NULL) { |
| flacparse->tags = tags; |
| } else { |
| gst_tag_list_insert (flacparse->tags, tags, GST_TAG_MERGE_APPEND); |
| gst_tag_list_unref (tags); |
| } |
| |
| return TRUE; |
| } |
| |
| static gboolean |
| gst_flac_parse_handle_cuesheet (GstFlacParse * flacparse, GstBuffer * buffer) |
| { |
| GstByteReader reader; |
| GstMapInfo map; |
| guint i, j; |
| guint8 n_tracks, track_num, index; |
| guint64 offset; |
| gint64 start, stop; |
| gchar *id; |
| gchar isrc[13]; |
| GstTagList *tags; |
| GstToc *toc; |
| GstTocEntry *cur_entry = NULL, *prev_entry = NULL; |
| |
| gst_buffer_map (buffer, &map, GST_MAP_READ); |
| gst_byte_reader_init (&reader, map.data, map.size); |
| |
| toc = gst_toc_new (GST_TOC_SCOPE_GLOBAL); |
| |
| /* skip 4 bytes METADATA_BLOCK_HEADER */ |
| /* https://xiph.org/flac/format.html#metadata_block_header */ |
| if (!gst_byte_reader_skip (&reader, 4)) |
| goto error; |
| |
| /* skip 395 bytes from METADATA_BLOCK_CUESHEET */ |
| /* https://xiph.org/flac/format.html#metadata_block_cuesheet */ |
| if (!gst_byte_reader_skip (&reader, 395)) |
| goto error; |
| |
| if (!gst_byte_reader_get_uint8 (&reader, &n_tracks)) |
| goto error; |
| |
| /* CUESHEET_TRACK */ |
| /* https://xiph.org/flac/format.html#cuesheet_track */ |
| for (i = 0; i < n_tracks; i++) { |
| if (!gst_byte_reader_get_uint64_be (&reader, &offset)) |
| goto error; |
| if (!gst_byte_reader_get_uint8 (&reader, &track_num)) |
| goto error; |
| |
| if (gst_byte_reader_get_remaining (&reader) < 12) |
| goto error; |
| memcpy (isrc, map.data + gst_byte_reader_get_pos (&reader), 12); |
| /* \0-terminate the string */ |
| isrc[12] = '\0'; |
| if (!gst_byte_reader_skip (&reader, 12)) |
| goto error; |
| |
| /* skip 14 bytes from CUESHEET_TRACK */ |
| if (!gst_byte_reader_skip (&reader, 14)) |
| goto error; |
| if (!gst_byte_reader_get_uint8 (&reader, &index)) |
| goto error; |
| /* add tracks in TOC */ |
| /* lead-out tack has number 170 or 255 */ |
| if (track_num != 170 && track_num != 255) { |
| prev_entry = cur_entry; |
| /* previous track stop time = current track start time */ |
| if (prev_entry != NULL) { |
| gst_toc_entry_get_start_stop_times (prev_entry, &start, NULL); |
| stop = |
| gst_util_uint64_scale_round (offset, GST_SECOND, |
| flacparse->samplerate); |
| gst_toc_entry_set_start_stop_times (prev_entry, start, stop); |
| } |
| id = g_strdup_printf ("%08x", track_num); |
| cur_entry = gst_toc_entry_new (GST_TOC_ENTRY_TYPE_TRACK, id); |
| g_free (id); |
| start = |
| gst_util_uint64_scale_round (offset, GST_SECOND, |
| flacparse->samplerate); |
| gst_toc_entry_set_start_stop_times (cur_entry, start, -1); |
| /* add ISRC as tag in track */ |
| if (strlen (isrc) != 0) { |
| tags = gst_tag_list_new_empty (); |
| gst_tag_list_add (tags, GST_TAG_MERGE_APPEND, GST_TAG_ISRC, isrc, NULL); |
| gst_toc_entry_set_tags (cur_entry, tags); |
| } |
| gst_toc_append_entry (toc, cur_entry); |
| /* CUESHEET_TRACK_INDEX */ |
| /* https://xiph.org/flac/format.html#cuesheet_track_index */ |
| for (j = 0; j < index; j++) { |
| if (!gst_byte_reader_skip (&reader, 12)) |
| goto error; |
| } |
| } else { |
| /* set stop time in last track */ |
| stop = |
| gst_util_uint64_scale_round (offset, GST_SECOND, |
| flacparse->samplerate); |
| gst_toc_entry_set_start_stop_times (cur_entry, start, stop); |
| } |
| } |
| |
| /* send data as TOC */ |
| if (!flacparse->toc) |
| flacparse->toc = toc; |
| |
| gst_buffer_unmap (buffer, &map); |
| return TRUE; |
| |
| error: |
| GST_ERROR_OBJECT (flacparse, "Error reading data"); |
| gst_buffer_unmap (buffer, &map); |
| return FALSE; |
| } |
| |
| static gboolean |
| gst_flac_parse_handle_picture (GstFlacParse * flacparse, GstBuffer * buffer) |
| { |
| GstByteReader reader; |
| GstMapInfo map; |
| guint32 img_len = 0, img_type = 0; |
| guint32 img_mimetype_len = 0, img_description_len = 0; |
| |
| gst_buffer_map (buffer, &map, GST_MAP_READ); |
| gst_byte_reader_init (&reader, map.data, map.size); |
| |
| if (!gst_byte_reader_skip (&reader, 4)) |
| goto error; |
| |
| if (!gst_byte_reader_get_uint32_be (&reader, &img_type)) |
| goto error; |
| |
| if (!gst_byte_reader_get_uint32_be (&reader, &img_mimetype_len)) |
| goto error; |
| if (!gst_byte_reader_skip (&reader, img_mimetype_len)) |
| goto error; |
| |
| if (!gst_byte_reader_get_uint32_be (&reader, &img_description_len)) |
| goto error; |
| if (!gst_byte_reader_skip (&reader, img_description_len)) |
| goto error; |
| |
| if (!gst_byte_reader_skip (&reader, 4 * 4)) |
| goto error; |
| |
| if (!gst_byte_reader_get_uint32_be (&reader, &img_len)) |
| goto error; |
| |
| if (gst_byte_reader_get_pos (&reader) + img_len > map.size) |
| goto error; |
| |
| GST_INFO_OBJECT (flacparse, "Got image of %d bytes", img_len); |
| |
| if (img_len > 0) { |
| if (flacparse->tags == NULL) |
| flacparse->tags = gst_tag_list_new_empty (); |
| |
| gst_tag_list_add_id3_image (flacparse->tags, |
| map.data + gst_byte_reader_get_pos (&reader), img_len, img_type); |
| } |
| |
| gst_buffer_unmap (buffer, &map); |
| return TRUE; |
| |
| error: |
| GST_ERROR_OBJECT (flacparse, "Error reading data"); |
| gst_buffer_unmap (buffer, &map); |
| return FALSE; |
| } |
| |
| static gboolean |
| gst_flac_parse_handle_seektable (GstFlacParse * flacparse, GstBuffer * buffer) |
| { |
| |
| GST_DEBUG_OBJECT (flacparse, "storing seektable"); |
| /* only store for now; |
| * offset of the first frame is needed to get real info */ |
| if (flacparse->seektable) |
| gst_buffer_unref (flacparse->seektable); |
| flacparse->seektable = gst_buffer_ref (buffer); |
| |
| return TRUE; |
| } |
| |
| static void |
| gst_flac_parse_process_seektable (GstFlacParse * flacparse, gint64 boffset) |
| { |
| GstByteReader br; |
| gint64 offset = 0, samples = 0; |
| GstMapInfo map; |
| |
| GST_DEBUG_OBJECT (flacparse, |
| "parsing seektable; base offset %" G_GINT64_FORMAT, boffset); |
| |
| if (boffset <= 0) |
| goto exit; |
| |
| gst_buffer_map (flacparse->seektable, &map, GST_MAP_READ); |
| gst_byte_reader_init (&br, map.data, map.size); |
| |
| /* skip header */ |
| if (!gst_byte_reader_skip (&br, 4)) |
| goto done; |
| |
| /* seekpoints */ |
| while (gst_byte_reader_get_remaining (&br)) { |
| if (!gst_byte_reader_get_int64_be (&br, &samples)) |
| break; |
| if (!gst_byte_reader_get_int64_be (&br, &offset)) |
| break; |
| if (!gst_byte_reader_skip (&br, 2)) |
| break; |
| |
| GST_LOG_OBJECT (flacparse, "samples %" G_GINT64_FORMAT " -> offset %" |
| G_GINT64_FORMAT, samples, offset); |
| |
| /* sanity check */ |
| if (G_LIKELY (offset > 0 && samples > 0)) { |
| gst_base_parse_add_index_entry (GST_BASE_PARSE (flacparse), |
| boffset + offset, gst_util_uint64_scale (samples, GST_SECOND, |
| flacparse->samplerate), TRUE, FALSE); |
| } |
| } |
| |
| done: |
| gst_buffer_unmap (flacparse->seektable, &map); |
| exit: |
| gst_buffer_unref (flacparse->seektable); |
| flacparse->seektable = NULL; |
| } |
| |
| static void |
| _value_array_append_buffer (GValue * array_val, GstBuffer * buf) |
| { |
| GValue value = { 0, }; |
| |
| g_value_init (&value, GST_TYPE_BUFFER); |
| /* copy buffer to avoid problems with circular refcounts */ |
| buf = gst_buffer_copy (buf); |
| /* again, for good measure */ |
| GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_HEADER); |
| gst_value_set_buffer (&value, buf); |
| gst_buffer_unref (buf); |
| gst_value_array_append_value (array_val, &value); |
| g_value_unset (&value); |
| } |
| |
| static GstFlowReturn |
| gst_flac_parse_handle_headers (GstFlacParse * flacparse) |
| { |
| GstBuffer *vorbiscomment = NULL; |
| GstBuffer *streaminfo = NULL; |
| GstBuffer *marker = NULL; |
| GValue array = { 0, }; |
| GstCaps *caps; |
| GList *l; |
| GstFlowReturn res = GST_FLOW_OK; |
| |
| caps = gst_caps_new_simple ("audio/x-flac", |
| "channels", G_TYPE_INT, flacparse->channels, |
| "framed", G_TYPE_BOOLEAN, TRUE, |
| "rate", G_TYPE_INT, flacparse->samplerate, NULL); |
| |
| if (!flacparse->headers) |
| goto push_headers; |
| |
| for (l = flacparse->headers; l; l = l->next) { |
| GstBuffer *header = l->data; |
| GstMapInfo map; |
| |
| gst_buffer_map (header, &map, GST_MAP_READ); |
| |
| GST_BUFFER_FLAG_SET (header, GST_BUFFER_FLAG_HEADER); |
| |
| if (map.size == 4 && memcmp (map.data, "fLaC", 4) == 0) { |
| marker = header; |
| } else if (map.size > 1 && (map.data[0] & 0x7f) == 0) { |
| streaminfo = header; |
| } else if (map.size > 1 && (map.data[0] & 0x7f) == 4) { |
| vorbiscomment = header; |
| } |
| |
| gst_buffer_unmap (header, &map); |
| } |
| |
| /* at least this one we can generate easily |
| * to provide full headers downstream */ |
| if (vorbiscomment == NULL && streaminfo != NULL) { |
| GST_DEBUG_OBJECT (flacparse, |
| "missing vorbiscomment header; generating dummy"); |
| vorbiscomment = gst_flac_parse_generate_vorbiscomment (flacparse); |
| flacparse->headers = g_list_insert (flacparse->headers, vorbiscomment, |
| g_list_index (flacparse->headers, streaminfo) + 1); |
| } |
| |
| if (marker == NULL || streaminfo == NULL || vorbiscomment == NULL) { |
| GST_WARNING_OBJECT (flacparse, |
| "missing header %p %p %p, muxing into container " |
| "formats may be broken", marker, streaminfo, vorbiscomment); |
| goto push_headers; |
| } |
| |
| g_value_init (&array, GST_TYPE_ARRAY); |
| |
| /* add marker including STREAMINFO header */ |
| { |
| GstBuffer *buf; |
| guint16 num; |
| GstMapInfo sinfomap, writemap; |
| |
| gst_buffer_map (streaminfo, &sinfomap, GST_MAP_READ); |
| |
| /* minus one for the marker that is merged with streaminfo here */ |
| num = g_list_length (flacparse->headers) - 1; |
| |
| buf = gst_buffer_new_and_alloc (13 + sinfomap.size); |
| gst_buffer_map (buf, &writemap, GST_MAP_WRITE); |
| |
| writemap.data[0] = 0x7f; |
| memcpy (writemap.data + 1, "FLAC", 4); |
| writemap.data[5] = 0x01; /* mapping version major */ |
| writemap.data[6] = 0x00; /* mapping version minor */ |
| writemap.data[7] = (num & 0xFF00) >> 8; |
| writemap.data[8] = (num & 0x00FF) >> 0; |
| memcpy (writemap.data + 9, "fLaC", 4); |
| memcpy (writemap.data + 13, sinfomap.data, sinfomap.size); |
| _value_array_append_buffer (&array, buf); |
| |
| gst_buffer_unmap (streaminfo, &sinfomap); |
| gst_buffer_unmap (buf, &writemap); |
| gst_buffer_unref (buf); |
| } |
| |
| /* add VORBISCOMMENT header */ |
| _value_array_append_buffer (&array, vorbiscomment); |
| |
| /* add other headers, if there are any */ |
| for (l = flacparse->headers; l; l = l->next) { |
| if (GST_BUFFER_CAST (l->data) != marker && |
| GST_BUFFER_CAST (l->data) != streaminfo && |
| GST_BUFFER_CAST (l->data) != vorbiscomment) { |
| _value_array_append_buffer (&array, GST_BUFFER_CAST (l->data)); |
| } |
| } |
| |
| gst_structure_set_value (gst_caps_get_structure (caps, 0), |
| "streamheader", &array); |
| g_value_unset (&array); |
| |
| push_headers: |
| |
| gst_pad_set_caps (GST_BASE_PARSE_SRC_PAD (GST_BASE_PARSE (flacparse)), caps); |
| gst_caps_unref (caps); |
| |
| /* push header buffers; update caps, so when we push the first buffer the |
| * negotiated caps will change to caps that include the streamheader field */ |
| while (flacparse->headers) { |
| GstBuffer *buf = GST_BUFFER (flacparse->headers->data); |
| GstBaseParseFrame frame; |
| |
| flacparse->headers = |
| g_list_delete_link (flacparse->headers, flacparse->headers); |
| buf = gst_buffer_make_writable (buf); |
| |
| /* init, set and give away frame */ |
| gst_base_parse_frame_init (&frame); |
| frame.buffer = buf; |
| frame.overhead = -1; |
| res = gst_base_parse_push_frame (GST_BASE_PARSE (flacparse), &frame); |
| gst_base_parse_frame_free (&frame); |
| if (res != GST_FLOW_OK) |
| break; |
| } |
| g_list_foreach (flacparse->headers, (GFunc) gst_mini_object_unref, NULL); |
| g_list_free (flacparse->headers); |
| flacparse->headers = NULL; |
| |
| return res; |
| } |
| |
| /* empty vorbiscomment */ |
| static GstBuffer * |
| gst_flac_parse_generate_vorbiscomment (GstFlacParse * flacparse) |
| { |
| GstTagList *taglist = gst_tag_list_new_empty (); |
| guchar header[4]; |
| guint size; |
| GstBuffer *vorbiscomment; |
| GstMapInfo map; |
| |
| header[0] = 0x84; /* is_last = 1; type = 4; */ |
| |
| vorbiscomment = |
| gst_tag_list_to_vorbiscomment_buffer (taglist, header, |
| sizeof (header), NULL); |
| gst_tag_list_unref (taglist); |
| |
| gst_buffer_map (vorbiscomment, &map, GST_MAP_WRITE); |
| |
| /* Get rid of framing bit */ |
| if (map.data[map.size - 1] == 1) { |
| GstBuffer *sub; |
| |
| sub = |
| gst_buffer_copy_region (vorbiscomment, GST_BUFFER_COPY_ALL, 0, |
| map.size - 1); |
| gst_buffer_unmap (vorbiscomment, &map); |
| gst_buffer_unref (vorbiscomment); |
| vorbiscomment = sub; |
| gst_buffer_map (vorbiscomment, &map, GST_MAP_WRITE); |
| } |
| |
| size = map.size - 4; |
| map.data[1] = ((size & 0xFF0000) >> 16); |
| map.data[2] = ((size & 0x00FF00) >> 8); |
| map.data[3] = (size & 0x0000FF); |
| gst_buffer_unmap (vorbiscomment, &map); |
| gst_flac_parse_reset_buffer_time_and_offset (vorbiscomment); |
| |
| return vorbiscomment; |
| } |
| |
| static gboolean |
| gst_flac_parse_generate_headers (GstFlacParse * flacparse) |
| { |
| GstBuffer *marker, *streaminfo; |
| GstMapInfo map; |
| |
| marker = gst_buffer_new_and_alloc (4); |
| gst_buffer_map (marker, &map, GST_MAP_WRITE); |
| memcpy (map.data, "fLaC", 4); |
| gst_buffer_unmap (marker, &map); |
| gst_flac_parse_reset_buffer_time_and_offset (marker); |
| flacparse->headers = g_list_append (flacparse->headers, marker); |
| |
| streaminfo = gst_buffer_new_and_alloc (4 + 34); |
| gst_buffer_map (streaminfo, &map, GST_MAP_WRITE); |
| memset (map.data, 0, 4 + 34); |
| |
| /* metadata block header */ |
| map.data[0] = 0x00; /* is_last = 0; type = 0; */ |
| map.data[1] = 0x00; /* length = 34; */ |
| map.data[2] = 0x00; |
| map.data[3] = 0x22; |
| |
| /* streaminfo */ |
| |
| map.data[4] = (flacparse->block_size >> 8) & 0xff; /* min blocksize = blocksize; */ |
| map.data[5] = (flacparse->block_size) & 0xff; |
| map.data[6] = (flacparse->block_size >> 8) & 0xff; /* max blocksize = blocksize; */ |
| map.data[7] = (flacparse->block_size) & 0xff; |
| |
| map.data[8] = 0x00; /* min framesize = 0; */ |
| map.data[9] = 0x00; |
| map.data[10] = 0x00; |
| map.data[11] = 0x00; /* max framesize = 0; */ |
| map.data[12] = 0x00; |
| map.data[13] = 0x00; |
| |
| map.data[14] = (flacparse->samplerate >> 12) & 0xff; |
| map.data[15] = (flacparse->samplerate >> 4) & 0xff; |
| map.data[16] = (flacparse->samplerate >> 0) & 0xf0; |
| |
| map.data[16] |= (flacparse->channels - 1) << 1; |
| |
| map.data[16] |= ((flacparse->bps - 1) >> 4) & 0x01; |
| map.data[17] = (((flacparse->bps - 1)) & 0x0f) << 4; |
| |
| { |
| gint64 duration; |
| |
| if (gst_pad_peer_query_duration (GST_BASE_PARSE_SINK_PAD (flacparse), |
| GST_FORMAT_TIME, &duration) && duration != -1) { |
| duration = GST_CLOCK_TIME_TO_FRAMES (duration, flacparse->samplerate); |
| |
| map.data[17] |= (duration >> 32) & 0xff; |
| map.data[18] |= (duration >> 24) & 0xff; |
| map.data[19] |= (duration >> 16) & 0xff; |
| map.data[20] |= (duration >> 8) & 0xff; |
| map.data[21] |= (duration >> 0) & 0xff; |
| } |
| } |
| /* MD5 = 0; */ |
| |
| gst_buffer_unmap (streaminfo, &map); |
| gst_flac_parse_reset_buffer_time_and_offset (streaminfo); |
| flacparse->headers = g_list_append (flacparse->headers, streaminfo); |
| |
| flacparse->headers = g_list_append (flacparse->headers, |
| gst_flac_parse_generate_vorbiscomment (flacparse)); |
| |
| return TRUE; |
| } |
| |
| static inline void |
| gst_flac_parse_reset_buffer_time_and_offset (GstBuffer * buffer) |
| { |
| GST_BUFFER_TIMESTAMP (buffer) = GST_CLOCK_TIME_NONE; |
| GST_BUFFER_DURATION (buffer) = GST_CLOCK_TIME_NONE; |
| GST_BUFFER_OFFSET (buffer) = 0; |
| GST_BUFFER_OFFSET_END (buffer) = 0; |
| } |
| |
| /* Type 127 is invalid for a metadata block header & should |
| * be discarded _before_ calling this function */ |
| static gboolean |
| gst_flac_parse_handle_block_type (GstFlacParse * flacparse, guint type, |
| GstBuffer * sbuffer) |
| { |
| gboolean ret = TRUE; |
| |
| switch (type) { |
| case 0: /* STREAMINFO */ |
| GST_INFO_OBJECT (flacparse, "STREAMINFO header"); |
| ret = gst_flac_parse_handle_streaminfo (flacparse, sbuffer); |
| break; |
| case 3: /* SEEKTABLE */ |
| GST_INFO_OBJECT (flacparse, "SEEKTABLE header"); |
| ret = gst_flac_parse_handle_seektable (flacparse, sbuffer); |
| break; |
| case 4: /* VORBIS_COMMENT */ |
| GST_INFO_OBJECT (flacparse, "VORBISCOMMENT header"); |
| ret = gst_flac_parse_handle_vorbiscomment (flacparse, sbuffer); |
| break; |
| case 5: /* CUESHEET */ |
| GST_INFO_OBJECT (flacparse, "CUESHEET header"); |
| ret = gst_flac_parse_handle_cuesheet (flacparse, sbuffer); |
| break; |
| case 6: /* PICTURE */ |
| GST_INFO_OBJECT (flacparse, "PICTURE header"); |
| ret = gst_flac_parse_handle_picture (flacparse, sbuffer); |
| break; |
| case 1: /* PADDING */ |
| GST_INFO_OBJECT (flacparse, "PADDING header"); |
| break; |
| case 2: /* APPLICATION */ |
| GST_INFO_OBJECT (flacparse, "APPLICATION header"); |
| break; |
| default: /* RESERVED */ |
| GST_INFO_OBJECT (flacparse, "Unhandled metadata header type '%u'", type); |
| GST_FIXME_OBJECT (flacparse, "FLAC version might not be fully supported"); |
| break; |
| } |
| |
| return ret; |
| } |
| |
| static GstFlowReturn |
| gst_flac_parse_parse_frame (GstBaseParse * parse, GstBaseParseFrame * frame, |
| gint size) |
| { |
| GstFlacParse *flacparse = GST_FLAC_PARSE (parse); |
| GstBuffer *buffer = frame->buffer, *sbuffer; |
| GstMapInfo map; |
| GstFlowReturn res = GST_FLOW_ERROR; |
| guint64 relative_sample_number; |
| |
| gst_buffer_map (buffer, &map, GST_MAP_READ); |
| |
| if (flacparse->state == GST_FLAC_PARSE_STATE_INIT) { |
| sbuffer = gst_buffer_copy_region (buffer, GST_BUFFER_COPY_ALL, 0, size); |
| gst_flac_parse_reset_buffer_time_and_offset (sbuffer); |
| |
| /* 32 bits metadata block */ |
| gst_base_parse_set_min_frame_size (GST_BASE_PARSE (flacparse), 4); |
| flacparse->state = GST_FLAC_PARSE_STATE_HEADERS; |
| |
| flacparse->headers = g_list_append (flacparse->headers, sbuffer); |
| |
| res = GST_BASE_PARSE_FLOW_DROPPED; |
| } else if (flacparse->state == GST_FLAC_PARSE_STATE_HEADERS) { |
| gboolean is_last = map.data[0] >> 7; |
| guint type = (map.data[0] & 0x7F); |
| |
| if (type == 127) { |
| GST_WARNING_OBJECT (flacparse, "Invalid metadata block type 127"); |
| res = GST_BASE_PARSE_FLOW_DROPPED; |
| goto cleanup; |
| } |
| |
| GST_DEBUG_OBJECT (flacparse, "Handling metadata block of type %u", type); |
| |
| sbuffer = gst_buffer_copy_region (buffer, GST_BUFFER_COPY_ALL, 0, size); |
| |
| if (gst_flac_parse_handle_block_type (flacparse, type, sbuffer)) { |
| gst_flac_parse_reset_buffer_time_and_offset (sbuffer); |
| flacparse->headers = g_list_append (flacparse->headers, sbuffer); |
| } else { |
| GST_WARNING_OBJECT (parse, "failed to parse header of type %u", type); |
| GST_MEMDUMP_OBJECT (parse, "bad header data", map.data, size); |
| |
| gst_buffer_unref (sbuffer); |
| |
| /* error out unless we have a STREAMINFO header */ |
| if (flacparse->samplerate == 0 || flacparse->bps == 0) |
| goto header_parsing_error; |
| |
| /* .. in which case just stop header parsing and try to find audio */ |
| is_last = TRUE; |
| } |
| |
| if (is_last) { |
| res = gst_flac_parse_handle_headers (flacparse); |
| |
| /* Minimal size of a frame header */ |
| gst_base_parse_set_min_frame_size (GST_BASE_PARSE (flacparse), MAX (9, |
| flacparse->min_framesize)); |
| flacparse->state = GST_FLAC_PARSE_STATE_DATA; |
| |
| if (res != GST_FLOW_OK) |
| goto cleanup; |
| } else { |
| /* Header length */ |
| gst_base_parse_set_min_frame_size (GST_BASE_PARSE (flacparse), 4); |
| } |
| |
| /* DROPPED because we pushed already or will push all headers manually */ |
| res = GST_BASE_PARSE_FLOW_DROPPED; |
| } else { |
| if (flacparse->offset != GST_BUFFER_OFFSET (buffer)) { |
| FrameHeaderCheckReturn ret; |
| |
| flacparse->offset = GST_BUFFER_OFFSET (buffer); |
| ret = |
| gst_flac_parse_frame_header_is_valid (flacparse, |
| map.data, map.size, TRUE, NULL, NULL); |
| if (ret != FRAME_HEADER_VALID) { |
| GST_ERROR_OBJECT (flacparse, |
| "Baseclass didn't provide a complete frame"); |
| goto cleanup; |
| } |
| } |
| |
| if (flacparse->block_size == 0) { |
| GST_ERROR_OBJECT (flacparse, "Unparsed frame"); |
| goto cleanup; |
| } |
| |
| if (flacparse->seektable) |
| gst_flac_parse_process_seektable (flacparse, GST_BUFFER_OFFSET (buffer)); |
| |
| if (flacparse->state == GST_FLAC_PARSE_STATE_GENERATE_HEADERS) { |
| if (flacparse->blocking_strategy == 1) { |
| GST_WARNING_OBJECT (flacparse, |
| "Generating headers for variable blocksize streams not supported"); |
| |
| res = gst_flac_parse_handle_headers (flacparse); |
| } else { |
| GST_DEBUG_OBJECT (flacparse, "Generating headers"); |
| |
| if (!gst_flac_parse_generate_headers (flacparse)) |
| goto cleanup; |
| |
| res = gst_flac_parse_handle_headers (flacparse); |
| } |
| flacparse->state = GST_FLAC_PARSE_STATE_DATA; |
| if (res != GST_FLOW_OK) |
| goto cleanup; |
| } |
| |
| /* also cater for oggmux metadata */ |
| relative_sample_number = |
| flacparse->sample_number - flacparse->first_sample_number; |
| if (flacparse->blocking_strategy == 0) { |
| GST_BUFFER_PTS (buffer) = |
| gst_util_uint64_scale (relative_sample_number, |
| flacparse->block_size * GST_SECOND, flacparse->samplerate); |
| GST_BUFFER_OFFSET_END (buffer) = |
| relative_sample_number * flacparse->block_size + |
| flacparse->block_size; |
| } else { |
| GST_BUFFER_PTS (buffer) = |
| gst_util_uint64_scale (relative_sample_number, GST_SECOND, |
| flacparse->samplerate); |
| GST_BUFFER_OFFSET_END (buffer) = |
| relative_sample_number + flacparse->block_size; |
| } |
| |
| GST_BUFFER_DTS (buffer) = GST_BUFFER_PTS (buffer); |
| GST_BUFFER_OFFSET (buffer) = |
| gst_util_uint64_scale (GST_BUFFER_OFFSET_END (buffer), GST_SECOND, |
| flacparse->samplerate); |
| GST_BUFFER_DURATION (buffer) = |
| GST_BUFFER_OFFSET (buffer) - GST_BUFFER_PTS (buffer); |
| |
| /* To simplify, we just assume that it's a fixed size header and ignore |
| * subframe headers. The first could lead us to be off by 88 bits and |
| * the second even less, so the total inaccuracy is negligible. */ |
| frame->overhead = 7; |
| |
| /* Minimal size of a frame header */ |
| gst_base_parse_set_min_frame_size (GST_BASE_PARSE (flacparse), MAX (9, |
| flacparse->min_framesize)); |
| |
| flacparse->offset = -1; |
| flacparse->blocking_strategy = 0; |
| flacparse->sample_number = 0; |
| res = GST_FLOW_OK; |
| } |
| |
| cleanup: |
| gst_buffer_unmap (buffer, &map); |
| return res; |
| |
| header_parsing_error: |
| GST_ELEMENT_ERROR (flacparse, STREAM, DECODE, (NULL), |
| ("Failed to parse headers")); |
| goto cleanup; |
| } |
| |
| static GstFlowReturn |
| gst_flac_parse_pre_push_frame (GstBaseParse * parse, GstBaseParseFrame * frame) |
| { |
| GstFlacParse *flacparse = GST_FLAC_PARSE (parse); |
| |
| if (!flacparse->sent_codec_tag) { |
| GstCaps *caps; |
| |
| if (flacparse->tags == NULL) |
| flacparse->tags = gst_tag_list_new_empty (); |
| |
| /* codec tag */ |
| caps = gst_pad_get_current_caps (GST_BASE_PARSE_SRC_PAD (parse)); |
| if (G_UNLIKELY (caps == NULL)) { |
| if (GST_PAD_IS_FLUSHING (GST_BASE_PARSE_SRC_PAD (parse))) { |
| GST_INFO_OBJECT (parse, "Src pad is flushing"); |
| return GST_FLOW_FLUSHING; |
| } |
| GST_INFO_OBJECT (parse, "Src pad is not negotiated!"); |
| return GST_FLOW_NOT_NEGOTIATED; |
| } |
| gst_pb_utils_add_codec_description_to_tag_list (flacparse->tags, |
| GST_TAG_AUDIO_CODEC, caps); |
| gst_caps_unref (caps); |
| |
| /* Announce our pending tags */ |
| gst_base_parse_merge_tags (parse, flacparse->tags, GST_TAG_MERGE_REPLACE); |
| |
| /* also signals the end of first-frame processing */ |
| flacparse->sent_codec_tag = TRUE; |
| } |
| |
| /* Push toc */ |
| if (flacparse->toc) { |
| gst_pad_push_event (GST_BASE_PARSE_SRC_PAD (flacparse), |
| gst_event_new_toc (flacparse->toc, FALSE)); |
| } |
| |
| frame->flags |= GST_BASE_PARSE_FRAME_FLAG_CLIP; |
| |
| return GST_FLOW_OK; |
| } |
| |
| static gboolean |
| gst_flac_parse_convert (GstBaseParse * parse, |
| GstFormat src_format, gint64 src_value, GstFormat dest_format, |
| gint64 * dest_value) |
| { |
| GstFlacParse *flacparse = GST_FLAC_PARSE (parse); |
| |
| if (flacparse->samplerate > 0) { |
| if (src_format == GST_FORMAT_DEFAULT && dest_format == GST_FORMAT_TIME) { |
| if (src_value != -1) |
| *dest_value = |
| gst_util_uint64_scale (src_value, GST_SECOND, |
| flacparse->samplerate); |
| else |
| *dest_value = -1; |
| return TRUE; |
| } else if (src_format == GST_FORMAT_TIME && |
| dest_format == GST_FORMAT_DEFAULT) { |
| if (src_value != -1) |
| *dest_value = |
| gst_util_uint64_scale (src_value, flacparse->samplerate, |
| GST_SECOND); |
| else |
| *dest_value = -1; |
| return TRUE; |
| } |
| } |
| |
| return GST_BASE_PARSE_CLASS (parent_class)->convert (parse, src_format, |
| src_value, dest_format, dest_value); |
| } |
| |
| static gboolean |
| gst_flac_parse_src_event (GstBaseParse * parse, GstEvent * event) |
| { |
| GstFlacParse *flacparse = GST_FLAC_PARSE (parse); |
| gboolean res = FALSE; |
| |
| switch (GST_EVENT_TYPE (event)) { |
| case GST_EVENT_TOC_SELECT: |
| { |
| GstTocEntry *entry = NULL; |
| GstEvent *seek_event; |
| GstToc *toc = NULL; |
| gint64 start_pos; |
| gchar *uid = NULL; |
| |
| /* FIXME: some locking would be good */ |
| if (flacparse->toc) |
| toc = gst_toc_ref (flacparse->toc); |
| |
| if (toc != NULL) { |
| gst_event_parse_toc_select (event, &uid); |
| if (uid != NULL) { |
| entry = gst_toc_find_entry (toc, uid); |
| if (entry != NULL) { |
| gst_toc_entry_get_start_stop_times (entry, &start_pos, NULL); |
| |
| /* FIXME: use segment rate here instead? */ |
| seek_event = gst_event_new_seek (1.0, |
| GST_FORMAT_TIME, |
| GST_SEEK_FLAG_FLUSH, |
| GST_SEEK_TYPE_SET, start_pos, GST_SEEK_TYPE_NONE, -1); |
| |
| res = |
| GST_BASE_PARSE_CLASS (parent_class)->src_event (parse, |
| seek_event); |
| |
| } else { |
| GST_WARNING_OBJECT (parse, "no TOC entry with given UID: %s", uid); |
| } |
| g_free (uid); |
| } |
| gst_toc_unref (toc); |
| } else { |
| GST_DEBUG_OBJECT (flacparse, "no TOC to select"); |
| } |
| gst_event_unref (event); |
| break; |
| } |
| default: |
| res = GST_BASE_PARSE_CLASS (parent_class)->src_event (parse, event); |
| break; |
| } |
| return res; |
| } |
| |
| static void |
| remove_fields (GstCaps * caps) |
| { |
| guint i, n; |
| |
| n = gst_caps_get_size (caps); |
| for (i = 0; i < n; i++) { |
| GstStructure *s = gst_caps_get_structure (caps, i); |
| |
| gst_structure_remove_field (s, "framed"); |
| } |
| } |
| |
| static GstCaps * |
| gst_flac_parse_get_sink_caps (GstBaseParse * parse, GstCaps * filter) |
| { |
| GstCaps *peercaps, *templ; |
| GstCaps *res; |
| |
| templ = gst_pad_get_pad_template_caps (GST_BASE_PARSE_SINK_PAD (parse)); |
| if (filter) { |
| GstCaps *fcopy = gst_caps_copy (filter); |
| /* Remove the fields we convert */ |
| remove_fields (fcopy); |
| peercaps = gst_pad_peer_query_caps (GST_BASE_PARSE_SRC_PAD (parse), fcopy); |
| gst_caps_unref (fcopy); |
| } else |
| peercaps = gst_pad_peer_query_caps (GST_BASE_PARSE_SRC_PAD (parse), NULL); |
| |
| if (peercaps) { |
| /* Remove the framed field */ |
| peercaps = gst_caps_make_writable (peercaps); |
| remove_fields (peercaps); |
| |
| res = gst_caps_intersect_full (peercaps, templ, GST_CAPS_INTERSECT_FIRST); |
| gst_caps_unref (peercaps); |
| gst_caps_unref (templ); |
| } else { |
| res = templ; |
| } |
| |
| if (filter) { |
| GstCaps *intersection; |
| |
| intersection = |
| gst_caps_intersect_full (filter, res, GST_CAPS_INTERSECT_FIRST); |
| gst_caps_unref (res); |
| res = intersection; |
| } |
| |
| return res; |
| } |
| |
| static gboolean |
| gst_flac_parse_set_sink_caps (GstBaseParse * parse, GstCaps * caps) |
| { |
| /* If caps are changing, drain any pending frames we have so that afterwards |
| * we can potentially accept a new stream that is starting with the FLAC |
| * headers again. If headers appear in the middle of the stream we can't |
| * detect them |
| */ |
| gst_base_parse_drain (parse); |
| return TRUE; |
| } |