| /* -*- mOde: C; tab-width: 2; indent-tabs-mode: t; c-basic-offset: 2 -*- */ |
| /* GStreamer .wav encoder |
| * Copyright (C) <2002> Iain Holmes <iain@prettypeople.org> |
| * Copyright (C) <2006> Tim-Philipp Müller <tim centricular net> |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public |
| * License along with this library; if not, write to the |
| * Free Software Foundation, Inc., 59 Temple Place - Suite 330, |
| * Boston, MA 02111-1307, USA. |
| * |
| */ |
| /** |
| * SECTION:element-wavenc |
| * |
| * Format a audio stream into the wav format. |
| * |
| */ |
| #ifdef HAVE_CONFIG_H |
| #include "config.h" |
| #endif |
| |
| #include <string.h> |
| #include "gstwavenc.h" |
| |
| #include <gst/audio/audio.h> |
| #include <gst/riff/riff-media.h> |
| |
| GST_DEBUG_CATEGORY_STATIC (wavenc_debug); |
| #define GST_CAT_DEFAULT wavenc_debug |
| |
| struct riff_struct |
| { |
| guint8 id[4]; /* RIFF */ |
| guint32 len; |
| guint8 wav_id[4]; /* WAVE */ |
| }; |
| |
| struct chunk_struct |
| { |
| guint8 id[4]; |
| guint32 len; |
| }; |
| |
| struct common_struct |
| { |
| guint16 wFormatTag; |
| guint16 wChannels; |
| guint32 dwSamplesPerSec; |
| guint32 dwAvgBytesPerSec; |
| guint16 wBlockAlign; |
| guint16 wBitsPerSample; /* Only for PCM */ |
| }; |
| |
| struct wave_header |
| { |
| struct riff_struct riff; |
| struct chunk_struct format; |
| struct common_struct common; |
| struct chunk_struct data; |
| }; |
| |
| /* FIXME: mono doesn't produce correct files it seems, at least mplayer xruns */ |
| /* Max. of two channels, more channels need WAVFORMATEX with |
| * channel layout, which we do not support yet */ |
| #define SINK_CAPS \ |
| "audio/x-raw, " \ |
| "rate = (int) [ 1, MAX ], " \ |
| "channels = (int) 1, " \ |
| "format = (string) { S32LE, S24LE, S16LE, U8, F32LE, F64LE }, " \ |
| "layout = (string) interleaved" \ |
| "; " \ |
| "audio/x-raw, " \ |
| "rate = (int) [ 1, MAX ], " \ |
| "channels = (int) 2, " \ |
| "channel-mask = (bitmask) 0x3, " \ |
| "format = (string) { S32LE, S24LE, S16LE, U8, F32LE, F64LE }, " \ |
| "layout = (string) interleaved" \ |
| "; " \ |
| "audio/x-alaw, " \ |
| "rate = (int) [ 8000, 192000 ], " \ |
| "channels = (int) [ 1, 2 ], " \ |
| "width = (int) 8, " \ |
| "depth = (int) 8, " \ |
| "signed = (boolean) false; " \ |
| "audio/x-mulaw, " \ |
| "rate = (int) [ 8000, 192000 ], " \ |
| "channels = (int) [ 1, 2 ], " \ |
| "width = (int) 8, " \ |
| "depth = (int) 8, " \ |
| "signed = (boolean) false" |
| |
| |
| static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink", |
| GST_PAD_SINK, |
| GST_PAD_ALWAYS, |
| GST_STATIC_CAPS (SINK_CAPS) |
| ); |
| |
| static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src", |
| GST_PAD_SRC, |
| GST_PAD_ALWAYS, |
| GST_STATIC_CAPS ("audio/x-wav") |
| ); |
| |
| #define gst_wavenc_parent_class parent_class |
| G_DEFINE_TYPE (GstWavEnc, gst_wavenc, GST_TYPE_ELEMENT); |
| |
| static GstFlowReturn gst_wavenc_chain (GstPad * pad, GstObject * parent, |
| GstBuffer * buf); |
| static gboolean gst_wavenc_event (GstPad * pad, GstObject * parent, |
| GstEvent * event); |
| static GstStateChangeReturn gst_wavenc_change_state (GstElement * element, |
| GstStateChange transition); |
| static gboolean gst_wavenc_sink_setcaps (GstPad * pad, GstCaps * caps); |
| |
| static void |
| gst_wavenc_class_init (GstWavEncClass * klass) |
| { |
| GstElementClass *element_class; |
| |
| element_class = (GstElementClass *) klass; |
| |
| element_class->change_state = GST_DEBUG_FUNCPTR (gst_wavenc_change_state); |
| |
| gst_element_class_set_static_metadata (element_class, "WAV audio muxer", |
| "Codec/Muxer/Audio", |
| "Encode raw audio into WAV", "Iain Holmes <iain@prettypeople.org>"); |
| |
| gst_element_class_add_pad_template (element_class, |
| gst_static_pad_template_get (&src_factory)); |
| gst_element_class_add_pad_template (element_class, |
| gst_static_pad_template_get (&sink_factory)); |
| |
| GST_DEBUG_CATEGORY_INIT (wavenc_debug, "wavenc", 0, "WAV encoder element"); |
| } |
| |
| static void |
| gst_wavenc_init (GstWavEnc * wavenc) |
| { |
| wavenc->sinkpad = gst_pad_new_from_static_template (&sink_factory, "sink"); |
| gst_pad_set_chain_function (wavenc->sinkpad, |
| GST_DEBUG_FUNCPTR (gst_wavenc_chain)); |
| gst_pad_set_event_function (wavenc->sinkpad, |
| GST_DEBUG_FUNCPTR (gst_wavenc_event)); |
| gst_pad_use_fixed_caps (wavenc->sinkpad); |
| gst_element_add_pad (GST_ELEMENT (wavenc), wavenc->sinkpad); |
| |
| wavenc->srcpad = gst_pad_new_from_static_template (&src_factory, "src"); |
| gst_pad_use_fixed_caps (wavenc->srcpad); |
| gst_pad_set_caps (wavenc->srcpad, |
| gst_static_pad_template_get_caps (&src_factory)); |
| gst_element_add_pad (GST_ELEMENT (wavenc), wavenc->srcpad); |
| } |
| |
| #define WAV_HEADER_LEN 44 |
| |
| static GstBuffer * |
| gst_wavenc_create_header_buf (GstWavEnc * wavenc, guint audio_data_size) |
| { |
| struct wave_header wave; |
| GstBuffer *buf; |
| GstMapInfo map; |
| guint8 *header; |
| |
| buf = gst_buffer_new_and_alloc (WAV_HEADER_LEN); |
| gst_buffer_map (buf, &map, GST_MAP_WRITE); |
| 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; |
| memcpy (wave.riff.wav_id, "WAVE", 4); |
| |
| memcpy (wave.format.id, "fmt ", 4); |
| wave.format.len = 16; |
| |
| 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; |
| |
| memcpy (header, (char *) wave.riff.id, 4); |
| GST_WRITE_UINT32_LE (header + 4, wave.riff.len); |
| memcpy (header + 8, (char *) wave.riff.wav_id, 4); |
| memcpy (header + 12, (char *) wave.format.id, 4); |
| GST_WRITE_UINT32_LE (header + 16, wave.format.len); |
| GST_WRITE_UINT16_LE (header + 20, wave.common.wFormatTag); |
| GST_WRITE_UINT16_LE (header + 22, wave.common.wChannels); |
| GST_WRITE_UINT32_LE (header + 24, wave.common.dwSamplesPerSec); |
| GST_WRITE_UINT32_LE (header + 28, wave.common.dwAvgBytesPerSec); |
| GST_WRITE_UINT16_LE (header + 32, wave.common.wBlockAlign); |
| GST_WRITE_UINT16_LE (header + 34, wave.common.wBitsPerSample); |
| memcpy (header + 36, (char *) wave.data.id, 4); |
| GST_WRITE_UINT32_LE (header + 40, wave.data.len); |
| |
| gst_buffer_unmap (buf, &map); |
| |
| return buf; |
| } |
| |
| static GstFlowReturn |
| gst_wavenc_push_header (GstWavEnc * wavenc, guint audio_data_size) |
| { |
| GstFlowReturn ret; |
| GstBuffer *outbuf; |
| GstSegment segment; |
| |
| /* seek to beginning of file */ |
| 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); |
| |
| outbuf = gst_wavenc_create_header_buf (wavenc, audio_data_size); |
| GST_BUFFER_OFFSET (outbuf) = 0; |
| |
| ret = gst_pad_push (wavenc->srcpad, outbuf); |
| |
| if (ret != GST_FLOW_OK) { |
| GST_WARNING_OBJECT (wavenc, "push header failed: flow = %s", |
| gst_flow_get_name (ret)); |
| } |
| |
| return ret; |
| } |
| |
| static gboolean |
| gst_wavenc_sink_setcaps (GstPad * pad, GstCaps * caps) |
| { |
| GstWavEnc *wavenc; |
| GstStructure *structure; |
| const gchar *name; |
| gint chans, rate; |
| GstCaps *ccaps; |
| |
| wavenc = GST_WAVENC (gst_pad_get_parent (pad)); |
| |
| ccaps = gst_pad_get_current_caps (pad); |
| if (wavenc->sent_header && ccaps && !gst_caps_can_intersect (caps, ccaps)) { |
| gst_caps_unref (ccaps); |
| GST_WARNING_OBJECT (wavenc, "cannot change format in middle of stream"); |
| goto fail; |
| } |
| if (ccaps) |
| gst_caps_unref (ccaps); |
| |
| GST_DEBUG_OBJECT (wavenc, "got caps: %" GST_PTR_FORMAT, caps); |
| |
| structure = gst_caps_get_structure (caps, 0); |
| name = gst_structure_get_name (structure); |
| |
| if (!gst_structure_get_int (structure, "channels", &chans) || |
| !gst_structure_get_int (structure, "rate", &rate)) { |
| GST_WARNING_OBJECT (wavenc, "caps incomplete"); |
| goto fail; |
| } |
| |
| if (strcmp (name, "audio/x-raw") == 0) { |
| GstAudioInfo info; |
| |
| if (!gst_audio_info_from_caps (&info, caps)) |
| goto fail; |
| |
| if (GST_AUDIO_INFO_IS_INTEGER (&info)) |
| wavenc->format = GST_RIFF_WAVE_FORMAT_PCM; |
| else if (GST_AUDIO_INFO_IS_FLOAT (&info)) |
| wavenc->format = GST_RIFF_WAVE_FORMAT_IEEE_FLOAT; |
| else |
| goto fail; |
| |
| wavenc->width = GST_AUDIO_INFO_WIDTH (&info); |
| } else if (strcmp (name, "audio/x-alaw") == 0) { |
| wavenc->format = GST_RIFF_WAVE_FORMAT_ALAW; |
| wavenc->width = 8; |
| } else if (strcmp (name, "audio/x-mulaw") == 0) { |
| wavenc->format = GST_RIFF_WAVE_FORMAT_MULAW; |
| wavenc->width = 8; |
| } else { |
| GST_WARNING_OBJECT (wavenc, "Unsupported format %s", name); |
| goto fail; |
| } |
| |
| wavenc->channels = chans; |
| wavenc->rate = rate; |
| |
| GST_LOG_OBJECT (wavenc, |
| "accepted caps: format=0x%04x chans=%u width=%u rate=%u", |
| wavenc->format, wavenc->channels, wavenc->width, wavenc->rate); |
| |
| gst_object_unref (wavenc); |
| return TRUE; |
| |
| fail: |
| gst_object_unref (wavenc); |
| 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) |
| { |
| 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); |
| } |
| |
| 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) |
| { |
| 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_event (GstPad * pad, GstObject * parent, GstEvent * event) |
| { |
| gboolean res = TRUE; |
| GstWavEnc *wavenc; |
| |
| wavenc = GST_WAVENC (parent); |
| |
| switch (GST_EVENT_TYPE (event)) { |
| case GST_EVENT_CAPS: |
| { |
| GstCaps *caps; |
| |
| gst_event_parse_caps (event, &caps); |
| gst_wavenc_sink_setcaps (pad, caps); |
| |
| /* have our own src caps */ |
| gst_event_unref (event); |
| break; |
| } |
| case GST_EVENT_EOS:{ |
| GST_DEBUG_OBJECT (wavenc, "got EOS"); |
| #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); |
| |
| /* we're done with this file */ |
| wavenc->finished_properly = TRUE; |
| |
| /* and forward the EOS event */ |
| res = gst_pad_event_default (pad, parent, event); |
| break; |
| } |
| case GST_EVENT_SEGMENT: |
| /* Just drop it, it's probably in TIME format |
| * anyway. We'll send our own newsegment event */ |
| gst_event_unref (event); |
| break; |
| default: |
| res = gst_pad_event_default (pad, parent, event); |
| break; |
| } |
| |
| return res; |
| } |
| |
| static GstFlowReturn |
| gst_wavenc_chain (GstPad * pad, GstObject * parent, GstBuffer * buf) |
| { |
| GstWavEnc *wavenc = GST_WAVENC (parent); |
| GstFlowReturn flow = GST_FLOW_OK; |
| |
| 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); |
| |
| /* starting a file, means we have to finish it properly */ |
| wavenc->finished_properly = FALSE; |
| |
| if (flow != GST_FLOW_OK) |
| return flow; |
| |
| GST_DEBUG_OBJECT (wavenc, "wrote dummy header"); |
| wavenc->sent_header = TRUE; |
| } |
| |
| GST_LOG_OBJECT (wavenc, |
| "pushing %" G_GSIZE_FORMAT " bytes raw audio, ts=%" GST_TIME_FORMAT, |
| gst_buffer_get_size (buf), GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf))); |
| |
| buf = gst_buffer_make_writable (buf); |
| |
| GST_BUFFER_OFFSET (buf) = WAV_HEADER_LEN + wavenc->length; |
| GST_BUFFER_OFFSET_END (buf) = GST_BUFFER_OFFSET_NONE; |
| |
| wavenc->length += gst_buffer_get_size (buf); |
| |
| flow = gst_pad_push (wavenc->srcpad, buf); |
| |
| return flow; |
| } |
| |
| static GstStateChangeReturn |
| gst_wavenc_change_state (GstElement * element, GstStateChange transition) |
| { |
| GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; |
| GstWavEnc *wavenc = GST_WAVENC (element); |
| |
| switch (transition) { |
| case GST_STATE_CHANGE_NULL_TO_READY: |
| wavenc->format = 0; |
| wavenc->channels = 0; |
| wavenc->width = 0; |
| wavenc->rate = 0; |
| wavenc->length = 0; |
| wavenc->sent_header = FALSE; |
| /* its true because we haven't writen anything */ |
| wavenc->finished_properly = TRUE; |
| break; |
| default: |
| break; |
| } |
| |
| ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); |
| if (ret != GST_STATE_CHANGE_SUCCESS) |
| return ret; |
| |
| switch (transition) { |
| case GST_STATE_CHANGE_PAUSED_TO_READY: |
| if (!wavenc->finished_properly) { |
| GST_ELEMENT_WARNING (wavenc, STREAM, MUX, |
| ("Wav stream not finished properly"), |
| ("Wav stream not finished properly, no EOS received " |
| "before shutdown")); |
| } |
| break; |
| default: |
| break; |
| } |
| |
| return ret; |
| } |
| |
| static gboolean |
| plugin_init (GstPlugin * plugin) |
| { |
| return gst_element_register (plugin, "wavenc", GST_RANK_PRIMARY, |
| GST_TYPE_WAVENC); |
| } |
| |
| GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, |
| GST_VERSION_MINOR, |
| wavenc, |
| "Encode raw audio into WAV", |
| plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN) |