From db29522a430e44450415ca3676abd1b77ee923d9 Mon Sep 17 00:00:00 2001 From: Anton Belka Date: Fri, 12 Apr 2013 16:02:44 +0300 Subject: [PATCH] wavenc: add tags & toc support Write tags as LIST INFO chunk. Format the toc as cue + LIST adtl chunk. Remove old #ifdef'ed code. --- gst/wavenc/gstwavenc.c | 454 ++++++++++++++++--------------------------------- gst/wavenc/gstwavenc.h | 6 +- 2 files changed, 153 insertions(+), 307 deletions(-) diff --git a/gst/wavenc/gstwavenc.c b/gst/wavenc/gstwavenc.c index 12aba40..5208fbb 100644 --- a/gst/wavenc/gstwavenc.c +++ b/gst/wavenc/gstwavenc.c @@ -44,6 +44,7 @@ #include #include +#include GST_DEBUG_CATEGORY_STATIC (wavenc_debug); #define GST_CAT_DEFAULT wavenc_debug @@ -156,6 +157,7 @@ static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src", #define gst_wavenc_parent_class parent_class G_DEFINE_TYPE_WITH_CODE (GstWavEnc, gst_wavenc, GST_TYPE_ELEMENT, + G_IMPLEMENT_INTERFACE (GST_TYPE_TAG_SETTER, NULL) G_IMPLEMENT_INTERFACE (GST_TYPE_TOC_SETTER, NULL) ); @@ -209,7 +211,7 @@ gst_wavenc_init (GstWavEnc * wavenc) #define WAV_HEADER_LEN 44 static GstBuffer * -gst_wavenc_create_header_buf (GstWavEnc * wavenc, guint audio_data_size) +gst_wavenc_create_header_buf (GstWavEnc * wavenc) { struct wave_header wave; GstBuffer *buf; @@ -221,25 +223,24 @@ gst_wavenc_create_header_buf (GstWavEnc * wavenc, guint audio_data_size) header = map.data; memset (header, 0, WAV_HEADER_LEN); - wave.common.wChannels = wavenc->channels; - wave.common.wBitsPerSample = wavenc->width; - wave.common.dwSamplesPerSec = wavenc->rate; - - /* Fill out our wav-header with some information */ memcpy (wave.riff.id, "RIFF", 4); - wave.riff.len = audio_data_size + WAV_HEADER_LEN - 8; + wave.riff.len = + wavenc->meta_length + wavenc->audio_length + WAV_HEADER_LEN - 8; memcpy (wave.riff.wav_id, "WAVE", 4); memcpy (wave.format.id, "fmt ", 4); wave.format.len = 16; + wave.common.wChannels = wavenc->channels; + wave.common.wBitsPerSample = wavenc->width; + wave.common.dwSamplesPerSec = wavenc->rate; wave.common.wFormatTag = wavenc->format; wave.common.wBlockAlign = (wavenc->width / 8) * wave.common.wChannels; wave.common.dwAvgBytesPerSec = wave.common.wBlockAlign * wave.common.dwSamplesPerSec; memcpy (wave.data.id, "data", 4); - wave.data.len = audio_data_size; + wave.data.len = wavenc->audio_length; memcpy (header, (char *) wave.riff.id, 4); GST_WRITE_UINT32_LE (header + 4, wave.riff.len); @@ -261,7 +262,7 @@ gst_wavenc_create_header_buf (GstWavEnc * wavenc, guint audio_data_size) } static GstFlowReturn -gst_wavenc_push_header (GstWavEnc * wavenc, guint audio_data_size) +gst_wavenc_push_header (GstWavEnc * wavenc) { GstFlowReturn ret; GstBuffer *outbuf; @@ -271,9 +272,10 @@ gst_wavenc_push_header (GstWavEnc * wavenc, guint audio_data_size) gst_segment_init (&segment, GST_FORMAT_BYTES); gst_pad_push_event (wavenc->srcpad, gst_event_new_segment (&segment)); - GST_DEBUG_OBJECT (wavenc, "writing header with datasize=%u", audio_data_size); + GST_DEBUG_OBJECT (wavenc, "writing header, meta_size=%u, audio_size=%u", + wavenc->meta_length, wavenc->audio_length); - outbuf = gst_wavenc_create_header_buf (wavenc, audio_data_size); + outbuf = gst_wavenc_create_header_buf (wavenc); GST_BUFFER_OFFSET (outbuf) = 0; ret = gst_pad_push (wavenc->srcpad, outbuf); @@ -357,285 +359,103 @@ fail: return FALSE; } -#if 0 -static struct _maps -{ - const guint32 id; - const gchar *name; -} maps[] = { - { - GST_RIFF_INFO_IARL, "Location"}, { - GST_RIFF_INFO_IART, "Artist"}, { - GST_RIFF_INFO_ICMS, "Commissioner"}, { - GST_RIFF_INFO_ICMT, "Comment"}, { - GST_RIFF_INFO_ICOP, "Copyright"}, { - GST_RIFF_INFO_ICRD, "Creation Date"}, { - GST_RIFF_INFO_IENG, "Engineer"}, { - GST_RIFF_INFO_IGNR, "Genre"}, { - GST_RIFF_INFO_IKEY, "Keywords"}, { - GST_RIFF_INFO_INAM, "Title"}, { - GST_RIFF_INFO_IPRD, "Product"}, { - GST_RIFF_INFO_ISBJ, "Subject"}, { - GST_RIFF_INFO_ISFT, "Software"}, { - GST_RIFF_INFO_ITCH, "Technician"} -}; - -static guint32 -get_id_from_name (const char *name) -{ - int i; - - for (i = 0; i < G_N_ELEMENTS (maps); i++) { - if (strcasecmp (maps[i].name, name) == 0) { - return maps[i].id; - } - } - - return 0; -} - static void -write_metadata (GstWavEnc * wavenc) +gst_wavparse_tags_foreach (const GstTagList * tags, const gchar * tag, + gpointer data) { - GString *info_str; - GList *props; - int total = 4; - gboolean need_to_write = FALSE; - - info_str = g_string_new ("LIST INFO"); - - for (props = wavenc->metadata->properties->properties; props; - props = props->next) { - GstPropsEntry *entry = props->data; - const char *name; - guint32 id; - - name = gst_props_entry_get_name (entry); - id = get_id_from_name (name); - if (id != 0) { - const char *text; - char *tmp; - int len, req, i; - - need_to_write = TRUE; /* We've got at least one entry */ - - gst_props_entry_get_string (entry, &text); - len = strlen (text) + 1; /* The length in the file includes the \0 */ - - tmp = g_strdup_printf ("%" GST_FOURCC_FORMAT "%d%s", GST_FOURCC_ARGS (id), - GUINT32_TO_LE (len), text); - g_string_append (info_str, tmp); - g_free (tmp); - - /* Check that we end on an even boundary */ - req = ((len + 8) + 1) & ~1; - for (i = 0; i < req - len; i++) { - g_string_append_printf (info_str, "%c", 0); + const struct + { + guint32 fcc; + const gchar *tag; + } rifftags[] = { + { + GST_RIFF_INFO_IARL, GST_TAG_LOCATION}, { + GST_RIFF_INFO_IART, GST_TAG_ARTIST}, { + GST_RIFF_INFO_ICMT, GST_TAG_COMMENT}, { + GST_RIFF_INFO_ICOP, GST_TAG_COPYRIGHT}, { + GST_RIFF_INFO_ICRD, GST_TAG_DATE}, { + GST_RIFF_INFO_IGNR, GST_TAG_GENRE}, { + GST_RIFF_INFO_IKEY, GST_TAG_KEYWORDS}, { + GST_RIFF_INFO_INAM, GST_TAG_TITLE}, { + GST_RIFF_INFO_IPRD, GST_TAG_ALBUM}, { + GST_RIFF_INFO_ISBJ, GST_TAG_ALBUM_ARTIST}, { + GST_RIFF_INFO_ISFT, GST_TAG_ENCODER}, { + GST_RIFF_INFO_ISRC, GST_TAG_ISRC}, { + 0, NULL} + }; + gint n; + gchar *str = NULL; + GstByteWriter *bw = data; + for (n = 0; rifftags[n].fcc != 0; n++) { + if (!strcmp (rifftags[n].tag, tag)) { + if (rifftags[n].fcc == GST_RIFF_INFO_ICRD) { + GDate *date; + /* special case for the date tag */ + if (gst_tag_list_get_date (tags, tag, &date)) { + str = + g_strdup_printf ("%04d:%02d:%02d", g_date_get_year (date), + g_date_get_month (date), g_date_get_day (date)); + g_date_free (date); + } + } else { + gst_tag_list_get_string (tags, tag, &str); + } + if (str) { + gst_byte_writer_put_uint32_le (bw, rifftags[n].fcc); + gst_byte_writer_put_uint32_le (bw, GST_ROUND_UP_2 (strlen (str))); + gst_byte_writer_put_string (bw, str); + g_free (str); + str = NULL; + break; } - - total += req; } } - if (need_to_write) { - GstBuffer *buf; - - /* Now we've got all the strings together, we can write our length in */ - info_str->str[4] = GUINT32_TO_LE (total); - - buf = gst_buffer_new (); - gst_buffer_set_data (buf, info_str->str, info_str->len); - - gst_pad_push (wavenc->srcpad, GST_DATA (buf)); - g_string_free (info_str, FALSE); - } -} - -static void -write_cues (GstWavEnc * wavenc) -{ - GString *cue_string, *point_string; - GstBuffer *buf; - GList *cue_list, *c; - int num_cues, total = 4; - - if (gst_props_get (wavenc->metadata->properties, - "cues", &cue_list, NULL) == FALSE) { - /* No cues, move along please, nothing to see here */ - return; - } - - /* Space for 'cue ', chunk size and number of cuepoints */ - cue_string = g_string_new ("cue "); -#define CUEPOINT_SIZE 24 - point_string = g_string_sized_new (CUEPOINT_SIZE); - - for (c = cue_list, num_cues = 0; c; c = c->next, num_cues++) { - GstCaps *cue_caps = c->data; - guint32 pos; - - gst_props_get (cue_caps->properties, "position", &pos, NULL); - - point_string->str[0] = GUINT32_TO_LE (num_cues + 1); - point_string->str[4] = GUINT32_TO_LE (0); - /* Fixme: There is probably a macro for this */ - point_string->str[8] = 'd'; - point_string->str[9] = 'a'; - point_string->str[10] = 't'; - point_string->str[11] = 'a'; - point_string->str[12] = GUINT32_TO_LE (0); - point_string->str[16] = GUINT32_TO_LE (0); - point_string->str[20] = GUINT32_TO_LE (pos); - - total += CUEPOINT_SIZE; - } - - /* Set the length and chunk size */ - cue_string->str[4] = GUINT32_TO_LE (total); - cue_string->str[8] = GUINT32_TO_LE (num_cues); - /* Stick the cue points on the end */ - g_string_append (cue_string, point_string->str); - g_string_free (point_string, TRUE); - - buf = gst_buffer_new (); - gst_buffer_set_data (buf, cue_string->str, cue_string->len); - - gst_pad_push (wavenc->srcpad, GST_DATA (buf)); - g_string_free (cue_string, FALSE); } -static void -write_labels (GstWavEnc * wavenc) +static GstFlowReturn +gst_wavenc_write_tags (GstWavEnc * wavenc) { + const GstTagList *user_tags; + GstTagList *tags; + guint size; GstBuffer *buf; - GString *info_str; - int total = 4; - GList *caps; - - info_str = g_string_new ("LIST adtl"); - if (gst_props_get (wavenc->metadata->properties, "ltxts", &caps, NULL)) { - GList *p; - int i; - - for (p = caps, i = 1; p; p = p->next, i++) { - GstCaps *ltxt_caps = p->data; - GString *ltxt; - char *label = NULL; - int len, req, j; - - gst_props_get (ltxt_caps->properties, "name", &label, NULL); - len = strlen (label); - -#define LTXT_SIZE 28 - ltxt = g_string_new ("ltxt "); - ltxt->str[8] = GUINT32_TO_LE (i); /* Identifier */ - ltxt->str[12] = GUINT32_TO_LE (0); /* Sample Length */ - ltxt->str[16] = GUINT32_TO_LE (0); /* FIXME: Don't save the purpose yet */ - ltxt->str[20] = GUINT16_TO_LE (0); /* Country */ - ltxt->str[22] = GUINT16_TO_LE (0); /* Language */ - ltxt->str[24] = GUINT16_TO_LE (0); /* Dialect */ - ltxt->str[26] = GUINT16_TO_LE (0); /* Code Page */ - g_string_append (ltxt, label); - g_free (label); - - len += LTXT_SIZE; - - ltxt->str[4] = GUINT32_TO_LE (len); - - /* Check that we end on an even boundary */ - req = ((len + 8) + 1) & ~1; - for (j = 0; j < req - len; j++) { - g_string_append_printf (ltxt, "%c", 0); - } + GstByteWriter bw; - total += req; + g_return_val_if_fail (wavenc != NULL, GST_FLOW_OK); - g_string_append (info_str, ltxt->str); - g_string_free (ltxt, TRUE); - } + user_tags = gst_tag_setter_get_tag_list (GST_TAG_SETTER (wavenc)); + if ((!wavenc->tags) && (!user_tags)) { + GST_DEBUG_OBJECT (wavenc, "have no tags"); + return GST_FLOW_OK; } + tags = + gst_tag_list_merge (user_tags, wavenc->tags, + gst_tag_setter_get_tag_merge_mode (GST_TAG_SETTER (wavenc))); - if (gst_props_get (wavenc->metadata->properties, "labels", &caps, NULL)) { - GList *p; - int i; - - for (p = caps, i = 1; p; p = p->next, i++) { - GstCaps *labl_caps = p->data; - GString *labl; - char *label = NULL; - int len, req, j; + GST_DEBUG_OBJECT (wavenc, "writing tags"); - gst_props_get (labl_caps->properties, "name", &label, NULL); - len = strlen (label); + gst_byte_writer_init_with_size (&bw, 1024, FALSE); -#define LABL_SIZE 4 - labl = g_string_new ("labl "); - labl->str[8] = GUINT32_TO_LE (i); - g_string_append (labl, label); - g_free (label); + /* add LIST INFO chunk */ + gst_byte_writer_put_data (&bw, (const guint8 *) "LIST", 4); + gst_byte_writer_put_uint32_le (&bw, 0); + gst_byte_writer_put_data (&bw, (const guint8 *) "INFO", 4); - len += LABL_SIZE; + /* add tags */ + gst_tag_list_foreach (tags, gst_wavparse_tags_foreach, &bw); - labl->str[4] = GUINT32_TO_LE (len); + /* sets real size of LIST INFO chunk */ + size = gst_byte_writer_get_pos (&bw); + gst_byte_writer_set_pos (&bw, 4); + gst_byte_writer_put_uint32_le (&bw, size - 8); - /* Check our size */ - req = ((len + 8) + 1) & ~1; - for (j = 0; j < req - len; j++) { - g_string_append_printf (labl, "%c", 0); - } - - total += req; + gst_tag_list_unref (tags); - g_string_append (info_str, labl->str); - g_string_free (labl, TRUE); - } - } - - if (gst_props_get (wavenc->metadata->properties, "notes", &caps, NULL)) { - GList *p; - int i; - - for (p = caps, i = 1; p; p = p->next, i++) { - GstCaps *note_caps = p->data; - GString *note; - char *label = NULL; - int len, req, j; - - gst_props_get (note_caps->properties, "name", &label, NULL); - len = strlen (label); - -#define NOTE_SIZE 4 - note = g_string_new ("note "); - note->str[8] = GUINT32_TO_LE (i); - g_string_append (note, label); - g_free (label); - - len += NOTE_SIZE; - - note->str[4] = GUINT32_TO_LE (len); - - /* Size check */ - req = ((len + 8) + 1) & ~1; - for (j = 0; j < req - len; j++) { - g_string_append_printf (note, "%c", 0); - } - - total += req; - - g_string_append (info_str, note->str); - g_string_free (note, TRUE); - } - } - - info_str->str[4] = GUINT32_TO_LE (total); - - buf = gst_buffer_new (); - gst_buffer_set_data (buf, info_str->str, info_str->len); - - gst_pad_push (wavenc->srcpad, GST_DATA (buf)); - g_string_free (info_str, FALSE); + buf = gst_byte_writer_reset_and_get_buffer (&bw); + wavenc->meta_length += gst_buffer_get_size (buf); + return gst_pad_push (wavenc->srcpad, buf); } -#endif static gboolean gst_wavenc_is_cue_id_unique (guint32 id, GList * list) @@ -793,7 +613,7 @@ gst_wavenc_write_notes (guint8 ** data, GList * list) return TRUE; } -static gboolean +static GstFlowReturn gst_wavenc_write_toc (GstWavEnc * wavenc) { GList *list; @@ -804,18 +624,21 @@ gst_wavenc_write_toc (GstWavEnc * wavenc) guint8 *data; guint32 ncues, size, cues_size, labls_size, notes_size; + if (!wavenc->toc) { + GST_DEBUG_OBJECT (wavenc, "have no toc, checking toc_setter"); + wavenc->toc = gst_toc_setter_get_toc (GST_TOC_SETTER (wavenc)); + } + if (!wavenc->toc) { + GST_WARNING_OBJECT (wavenc, "have no toc"); + return GST_FLOW_OK; + } + + toc = gst_toc_ref (wavenc->toc); size = 0; cues_size = 0; labls_size = 0; notes_size = 0; - if (wavenc->toc) { - toc = gst_toc_ref (wavenc->toc); - } else { - GST_WARNING_OBJECT (wavenc, "TOC not found"); - return FALSE; - } - /* check if the TOC entries is valid */ list = gst_toc_get_entries (toc); entry = list->data; @@ -935,10 +758,9 @@ gst_wavenc_write_toc (GstWavEnc * wavenc) g_list_free_full (wavenc->notes, g_free); gst_buffer_unmap (buf, &map); + wavenc->meta_length += gst_buffer_get_size (buf); - gst_pad_push (wavenc->srcpad, buf); - - return TRUE; + return gst_pad_push (wavenc->srcpad, buf); } static gboolean @@ -946,6 +768,7 @@ gst_wavenc_event (GstPad * pad, GstObject * parent, GstEvent * event) { gboolean res = TRUE; GstWavEnc *wavenc; + GstTagList *tags; GstToc *toc; wavenc = GST_WAVENC (parent); @@ -962,26 +785,24 @@ gst_wavenc_event (GstPad * pad, GstObject * parent, GstEvent * event) gst_event_unref (event); break; } - case GST_EVENT_EOS:{ + case GST_EVENT_EOS: + { + GstFlowReturn flow; GST_DEBUG_OBJECT (wavenc, "got EOS"); - if (!wavenc->toc) { - GST_DEBUG_OBJECT (wavenc, "have no toc, checking toc_setter"); - wavenc->toc = gst_toc_setter_get_toc (GST_TOC_SETTER (wavenc)); - } - if (wavenc->toc) { - GST_DEBUG_OBJECT (wavenc, "have toc"); - gst_wavenc_write_toc (wavenc); + + flow = gst_wavenc_write_toc (wavenc); + if (flow != GST_FLOW_OK) { + GST_WARNING_OBJECT (wavenc, "error pushing toc: %s", + gst_flow_get_name (flow)); } -#if 0 - /* Write our metadata if we have any */ - if (wavenc->metadata) { - write_metadata (wavenc); - write_cues (wavenc); - write_labels (wavenc); + flow = gst_wavenc_write_tags (wavenc); + if (flow != GST_FLOW_OK) { + GST_WARNING_OBJECT (wavenc, "error pushing tags: %s", + gst_flow_get_name (flow)); } -#endif + /* write header with correct length values */ - gst_wavenc_push_header (wavenc, wavenc->length); + gst_wavenc_push_header (wavenc); /* we're done with this file */ wavenc->finished_properly = TRUE; @@ -1008,6 +829,19 @@ gst_wavenc_event (GstPad * pad, GstObject * parent, GstEvent * event) } res = gst_pad_event_default (pad, parent, event); break; + case GST_EVENT_TAG: + gst_event_parse_tag (event, &tags); + if (tags) { + if (wavenc->tags != tags) { + if (wavenc->tags) + gst_tag_list_unref (wavenc->tags); + wavenc->tags = tags; + } else { + gst_toc_unref (tags); + } + } + res = gst_pad_event_default (pad, parent, event); + break; default: res = gst_pad_event_default (pad, parent, event); break; @@ -1024,17 +858,18 @@ gst_wavenc_chain (GstPad * pad, GstObject * parent, GstBuffer * buf) g_return_val_if_fail (wavenc->channels > 0, GST_FLOW_FLUSHING); - if (!wavenc->sent_header) { - /* use bogus size initially, we'll write the real - * header when we get EOS and know the exact length */ - flow = gst_wavenc_push_header (wavenc, 0x7FFF0000); - + if (G_UNLIKELY (!wavenc->sent_header)) { /* starting a file, means we have to finish it properly */ wavenc->finished_properly = FALSE; - if (flow != GST_FLOW_OK) + /* use bogus size initially, we'll write the real + * header when we get EOS and know the exact length */ + flow = gst_wavenc_push_header (wavenc); + if (flow != GST_FLOW_OK) { + GST_WARNING_OBJECT (wavenc, "error pushing header: %s", + gst_flow_get_name (flow)); return flow; - + } GST_DEBUG_OBJECT (wavenc, "wrote dummy header"); wavenc->sent_header = TRUE; } @@ -1045,10 +880,10 @@ gst_wavenc_chain (GstPad * pad, GstObject * parent, GstBuffer * buf) buf = gst_buffer_make_writable (buf); - GST_BUFFER_OFFSET (buf) = WAV_HEADER_LEN + wavenc->length; + GST_BUFFER_OFFSET (buf) = WAV_HEADER_LEN + wavenc->audio_length; GST_BUFFER_OFFSET_END (buf) = GST_BUFFER_OFFSET_NONE; - wavenc->length += gst_buffer_get_size (buf); + wavenc->audio_length += gst_buffer_get_size (buf); flow = gst_pad_push (wavenc->srcpad, buf); @@ -1067,7 +902,8 @@ gst_wavenc_change_state (GstElement * element, GstStateChange transition) wavenc->channels = 0; wavenc->width = 0; wavenc->rate = 0; - wavenc->length = 0; + wavenc->audio_length = 0; + wavenc->meta_length = 0; wavenc->sent_header = FALSE; /* its true because we haven't writen anything */ wavenc->finished_properly = TRUE; @@ -1090,11 +926,17 @@ gst_wavenc_change_state (GstElement * element, GstStateChange transition) } break; case GST_STATE_CHANGE_READY_TO_NULL: + GST_DEBUG_OBJECT (wavenc, "tags: %p", wavenc->tags); + if (wavenc->tags) { + gst_tag_list_unref (wavenc->tags); + wavenc->tags = NULL; + } GST_DEBUG_OBJECT (wavenc, "toc: %p", wavenc->toc); if (wavenc->toc) { gst_toc_unref (wavenc->toc); wavenc->toc = NULL; } + gst_tag_setter_reset_tags (GST_TAG_SETTER (wavenc)); gst_toc_setter_reset (GST_TOC_SETTER (wavenc)); break; default: diff --git a/gst/wavenc/gstwavenc.h b/gst/wavenc/gstwavenc.h index 76d6810..b6f8df4 100644 --- a/gst/wavenc/gstwavenc.h +++ b/gst/wavenc/gstwavenc.h @@ -46,6 +46,7 @@ struct _GstWavEnc { GstPad *sinkpad; GstPad *srcpad; + GstTagList *tags; GstToc *toc; GList *cues; GList *labls; @@ -56,7 +57,10 @@ struct _GstWavEnc { guint width; guint rate; guint channels; - guint32 length; + + /* data sizes */ + guint32 audio_length; + guint32 meta_length; gboolean sent_header; gboolean finished_properly; -- 2.7.4