wavenc: add tags & toc support

Write tags as LIST INFO chunk. Format the toc as cue + LIST adtl chunk. Remove
old #ifdef'ed code.
This commit is contained in:
Anton Belka 2013-04-12 16:02:44 +03:00 committed by Stefan Sauer
parent 1f0600ee6f
commit db29522a43
2 changed files with 154 additions and 308 deletions

View file

@ -44,6 +44,7 @@
#include <gst/audio/audio.h>
#include <gst/riff/riff-media.h>
#include <gst/base/gstbytewriter.h>
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,286 +359,104 @@ fail:
return FALSE;
}
#if 0
static struct _maps
static void
gst_wavparse_tags_foreach (const GstTagList * tags, const gchar * tag,
gpointer data)
{
const guint32 id;
const gchar *name;
} maps[] = {
const struct
{
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)
{
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);
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)
static GstFlowReturn
gst_wavenc_write_tags (GstWavEnc * wavenc)
{
GString *cue_string, *point_string;
const GstTagList *user_tags;
GstTagList *tags;
guint size;
GstBuffer *buf;
GList *cue_list, *c;
int num_cues, total = 4;
GstByteWriter bw;
if (gst_props_get (wavenc->metadata->properties,
"cues", &cue_list, NULL) == FALSE) {
/* No cues, move along please, nothing to see here */
return;
g_return_val_if_fail (wavenc != NULL, GST_FLOW_OK);
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)));
/* 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);
GST_DEBUG_OBJECT (wavenc, "writing tags");
for (c = cue_list, num_cues = 0; c; c = c->next, num_cues++) {
GstCaps *cue_caps = c->data;
guint32 pos;
gst_byte_writer_init_with_size (&bw, 1024, FALSE);
gst_props_get (cue_caps->properties, "position", &pos, NULL);
/* 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);
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);
/* add tags */
gst_tag_list_foreach (tags, gst_wavparse_tags_foreach, &bw);
total += CUEPOINT_SIZE;
}
/* 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);
/* 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);
gst_tag_list_unref (tags);
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);
buf = gst_byte_writer_reset_and_get_buffer (&bw);
wavenc->meta_length += gst_buffer_get_size (buf);
return gst_pad_push (wavenc->srcpad, buf);
}
static void
write_labels (GstWavEnc * wavenc)
{
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);
}
total += req;
g_string_append (info_str, ltxt->str);
g_string_free (ltxt, TRUE);
}
}
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_props_get (labl_caps->properties, "name", &label, NULL);
len = strlen (label);
#define LABL_SIZE 4
labl = g_string_new ("labl ");
labl->str[8] = GUINT32_TO_LE (i);
g_string_append (labl, label);
g_free (label);
len += LABL_SIZE;
labl->str[4] = GUINT32_TO_LE (len);
/* Check our size */
req = ((len + 8) + 1) & ~1;
for (j = 0; j < req - len; j++) {
g_string_append_printf (labl, "%c", 0);
}
total += req;
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);
}
#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));
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 (wavenc->toc) {
GST_DEBUG_OBJECT (wavenc, "have toc");
gst_wavenc_write_toc (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));
}
#if 0
/* Write our metadata if we have any */
if (wavenc->metadata) {
write_metadata (wavenc);
write_cues (wavenc);
write_labels (wavenc);
}
#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:

View file

@ -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;