From: Tim-Philipp Müller Date: Fri, 25 May 2007 16:02:51 +0000 (+0000) Subject: ext/flac/gstflacenc.*: Collect headers, add "streamheader" field to output caps and set X-Git-Tag: RELEASE-0_10_6~51 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=adc1e646645ef6c6b726c600c57fefda66f4934c;p=platform%2Fupstream%2Fgst-plugins-good.git ext/flac/gstflacenc.*: Collect headers, add "streamheader" field to output caps and set Original commit message from CVS: * ext/flac/gstflacenc.c: (gst_flac_enc_init), (notgst_value_array_append_buffer), (gst_flac_enc_process_stream_headers), (gst_flac_enc_write_callback), (gst_flac_enc_chain), (gst_flac_enc_change_state): * ext/flac/gstflacenc.h: Collect headers, add "streamheader" field to output caps and set BUFFER_IN_CAPS flag on pushed header buffers. That way oggmux produces output according to the official FLAC-to-Ogg mapping instead of completely broken files. Fixes #426044. --- diff --git a/ChangeLog b/ChangeLog index 03e761f..9f8b849 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,16 @@ +2007-05-25 Tim-Philipp Müller + + * ext/flac/gstflacenc.c: (gst_flac_enc_init), + (notgst_value_array_append_buffer), + (gst_flac_enc_process_stream_headers), + (gst_flac_enc_write_callback), (gst_flac_enc_chain), + (gst_flac_enc_change_state): + * ext/flac/gstflacenc.h: + Collect headers, add "streamheader" field to output caps and set + BUFFER_IN_CAPS flag on pushed header buffers. That way oggmux + produces output according to the official FLAC-to-Ogg mapping + instead of completely broken files. Fixes #426044. + 2007-05-25 Jan Schmidt * gst/id3demux/gstid3demux.c: (gst_id3demux_reset), diff --git a/ext/flac/gstflacenc.c b/ext/flac/gstflacenc.c index 255d6c7..cf1f04c 100644 --- a/ext/flac/gstflacenc.c +++ b/ext/flac/gstflacenc.c @@ -17,6 +17,11 @@ * Boston, MA 02111-1307, USA. */ +/* FIXME: + * - we assume timestamps start from 0 and that we get a perfect stream; we + * don't handle non-zero starts and mid-stream discontinuities, esp. not if + * we're muxing into ogg + */ #ifdef HAVE_CONFIG_H #include "config.h" @@ -308,6 +313,8 @@ gst_flac_enc_init (GstFlacEnc * flacenc, GstFlacEncClass * klass) flacenc->samples_written = 0; gst_flac_enc_update_quality (flacenc, DEFAULT_QUALITY); flacenc->tags = gst_tag_list_new (); + flacenc->got_headers = FALSE; + flacenc->headers = NULL; } static void @@ -535,12 +542,134 @@ gst_flac_enc_seek_callback (const FLAC__SeekableStreamEncoder * encoder, return FLAC__SEEKABLE_STREAM_ENCODER_SEEK_STATUS_OK; } +static void +notgst_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_IN_CAPS); + gst_value_set_buffer (&value, buf); + gst_buffer_unref (buf); + gst_value_array_append_value (array_val, &value); + g_value_unset (&value); +} + +#define HDR_TYPE_STREAMINFO 0 +#define HDR_TYPE_VORBISCOMMENT 4 + +static void +gst_flac_enc_process_stream_headers (GstFlacEnc * enc) +{ + GstBuffer *vorbiscomment = NULL; + GstBuffer *streaminfo = NULL; + GstBuffer *marker = NULL; + GValue array = { 0, }; + GstCaps *caps; + GList *l; + + caps = gst_caps_new_simple ("audio/x-flac", + "channels", G_TYPE_INT, enc->channels, + "rate", G_TYPE_INT, enc->sample_rate, NULL); + + for (l = enc->headers; l != NULL; l = l->next) { + const guint8 *data; + guint size; + + /* mark buffers so oggmux will ignore them if it already muxed the + * header buffers from the streamheaders field in the caps */ + l->data = gst_buffer_make_metadata_writable (GST_BUFFER (l->data)); + GST_BUFFER_FLAG_SET (GST_BUFFER (l->data), GST_BUFFER_FLAG_IN_CAPS); + + data = GST_BUFFER_DATA (GST_BUFFER_CAST (l->data)); + size = GST_BUFFER_SIZE (GST_BUFFER_CAST (l->data)); + + /* find initial 4-byte marker which we need to skip later on */ + if (size == 4 && memcmp (data, "fLaC", 4) == 0) { + marker = GST_BUFFER_CAST (l->data); + } else if (size > 1 && (data[0] & 0x7f) == HDR_TYPE_STREAMINFO) { + streaminfo = GST_BUFFER_CAST (l->data); + } else if (size > 1 && (data[0] & 0x7f) == HDR_TYPE_VORBISCOMMENT) { + vorbiscomment = GST_BUFFER_CAST (l->data); + } + } + + if (marker == NULL || streaminfo == NULL || vorbiscomment == NULL) { + GST_WARNING_OBJECT (enc, "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; + + /* minus one for the marker that is merged with streaminfo here */ + num = g_list_length (enc->headers) - 1; + + buf = gst_buffer_new_and_alloc (13 + GST_BUFFER_SIZE (streaminfo)); + GST_BUFFER_DATA (buf)[0] = 0x7f; + memcpy (GST_BUFFER_DATA (buf) + 1, "FLAC", 4); + GST_BUFFER_DATA (buf)[5] = 0x01; /* mapping version major */ + GST_BUFFER_DATA (buf)[6] = 0x00; /* mapping version minor */ + GST_BUFFER_DATA (buf)[7] = (num & 0xFF00) >> 8; + GST_BUFFER_DATA (buf)[8] = (num & 0x00FF) >> 0; + memcpy (GST_BUFFER_DATA (buf) + 9, "fLaC", 4); + memcpy (GST_BUFFER_DATA (buf) + 13, GST_BUFFER_DATA (streaminfo), + GST_BUFFER_SIZE (streaminfo)); + notgst_value_array_append_buffer (&array, buf); + gst_buffer_unref (buf); + } + + /* add VORBISCOMMENT header */ + notgst_value_array_append_buffer (&array, vorbiscomment); + + /* add other headers, if there are any */ + for (l = enc->headers; l != NULL; l = l->next) { + if (GST_BUFFER_CAST (l->data) != marker && + GST_BUFFER_CAST (l->data) != streaminfo && + GST_BUFFER_CAST (l->data) != vorbiscomment) { + notgst_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 (enc->srcpad, 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 */ + for (l = enc->headers; l != NULL; l = l->next) { + GstBuffer *buf; + + buf = GST_BUFFER (l->data); + gst_buffer_set_caps (buf, caps); + GST_LOG ("Pushing header buffer, size %u bytes", GST_BUFFER_SIZE (buf)); + (void) gst_pad_push (enc->srcpad, buf); + l->data = NULL; + } + g_list_free (enc->headers); + enc->headers = NULL; + + gst_caps_unref (caps); +} + static FLAC__StreamEncoderWriteStatus gst_flac_enc_write_callback (const FLAC__SeekableStreamEncoder * encoder, const FLAC__byte buffer[], unsigned bytes, unsigned samples, unsigned current_frame, void *client_data) { - GstFlowReturn ret; + GstFlowReturn ret = GST_FLOW_OK; GstFlacEnc *flacenc; GstBuffer *outbuf; @@ -549,36 +678,56 @@ gst_flac_enc_write_callback (const FLAC__SeekableStreamEncoder * encoder, if (flacenc->stopped) return FLAC__STREAM_ENCODER_WRITE_STATUS_OK; - if (gst_pad_alloc_buffer_and_set_caps (flacenc->srcpad, flacenc->offset, - bytes, GST_PAD_CAPS (flacenc->srcpad), &outbuf) != GST_FLOW_OK) { - return FLAC__STREAM_ENCODER_WRITE_STATUS_FATAL_ERROR; - } - + outbuf = gst_buffer_new_and_alloc (bytes); memcpy (GST_BUFFER_DATA (outbuf), buffer, bytes); if (samples > 0 && flacenc->samples_written != (guint64) - 1) { + guint64 granulepos; + GST_BUFFER_TIMESTAMP (outbuf) = GST_FRAMES_TO_CLOCK_TIME (flacenc->samples_written, flacenc->sample_rate); GST_BUFFER_DURATION (outbuf) = GST_FRAMES_TO_CLOCK_TIME (samples, flacenc->sample_rate); /* offset_end = granulepos for ogg muxer */ - GST_BUFFER_OFFSET_END (outbuf) = flacenc->samples_written + samples; + granulepos = flacenc->samples_written + samples; + GST_BUFFER_OFFSET_END (outbuf) = granulepos; + /* offset = timestamp corresponding to granulepos for ogg muxer + * (see vorbisenc for a much more elaborate version of this) */ + GST_BUFFER_OFFSET (outbuf) = + GST_FRAMES_TO_CLOCK_TIME (granulepos, flacenc->sample_rate); } else { GST_BUFFER_TIMESTAMP (outbuf) = GST_CLOCK_TIME_NONE; GST_BUFFER_DURATION (outbuf) = GST_CLOCK_TIME_NONE; } - GST_DEBUG ("Pushing buffer: ts=%" GST_TIME_FORMAT ", samples=%u, size=%u, " + /* we assume libflac passes us stuff neatly framed */ + if (!flacenc->got_headers) { + if (samples == 0) { + GST_DEBUG_OBJECT (flacenc, "Got header, queueing (%u bytes)", bytes); + flacenc->headers = g_list_append (flacenc->headers, outbuf); + /* note: it's important that we increase our byte offset */ + goto out; + } else { + GST_INFO_OBJECT (flacenc, "Non-header packet, we have all headers now"); + gst_flac_enc_process_stream_headers (flacenc); + flacenc->got_headers = TRUE; + } + } + + GST_LOG ("Pushing buffer: ts=%" GST_TIME_FORMAT ", samples=%u, size=%u, " "pos=%" G_GUINT64_FORMAT, GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (outbuf)), samples, bytes, flacenc->offset); + gst_buffer_set_caps (outbuf, GST_PAD_CAPS (flacenc->srcpad)); ret = gst_pad_push (flacenc->srcpad, outbuf); +out: + flacenc->offset += bytes; flacenc->samples_written += samples; - if (ret != GST_FLOW_OK && GST_FLOW_IS_FATAL (ret)) + if (GST_FLOW_IS_FATAL (ret)) return FLAC__STREAM_ENCODER_WRITE_STATUS_FATAL_ERROR; return FLAC__STREAM_ENCODER_WRITE_STATUS_OK; @@ -670,7 +819,7 @@ gst_flac_enc_chain (GstPad * pad, GstBuffer * buffer) flacenc = GST_FLAC_ENC (GST_PAD_PARENT (pad)); - /* make sure setcaps has been called and the encoder is setup */ + /* make sure setcaps has been called and the encoder is set up */ if (G_UNLIKELY (flacenc->depth == 0)) return GST_FLOW_NOT_NEGOTIATED; @@ -880,11 +1029,18 @@ gst_flac_enc_change_state (GstElement * element, GstStateChange transition) } flacenc->offset = 0; flacenc->samples_written = 0; + flacenc->channels = 0; + flacenc->depth = 0; + flacenc->sample_rate = 0; if (flacenc->meta) { FLAC__metadata_object_delete (flacenc->meta[0]); g_free (flacenc->meta); flacenc->meta = NULL; } + g_list_foreach (flacenc->headers, (GFunc) gst_mini_object_unref, NULL); + g_list_free (flacenc->headers); + flacenc->headers = NULL; + flacenc->got_headers = FALSE; break; case GST_STATE_CHANGE_READY_TO_NULL: default: diff --git a/ext/flac/gstflacenc.h b/ext/flac/gstflacenc.h index 8629b20..500e26a 100644 --- a/ext/flac/gstflacenc.h +++ b/ext/flac/gstflacenc.h @@ -39,7 +39,8 @@ typedef struct _GstFlacEncClass GstFlacEncClass; struct _GstFlacEnc { GstElement element; - GstPad *sinkpad,*srcpad; + GstPad *sinkpad; + GstPad *srcpad; gboolean first; GstBuffer *first_buf; @@ -58,6 +59,10 @@ struct _GstFlacEnc { FLAC__StreamMetadata **meta; GstTagList * tags; + + /* queue headers until we have them all so we can add streamheaders to caps */ + gboolean got_headers; + GList *headers; }; struct _GstFlacEncClass {