/* XMMS2 - X Music Multiplexer System * Copyright (C) 2003-2007 XMMS2 Team * * PLUGINS ARE NOT CONSIDERED TO BE DERIVED WORK !!! * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser General Public License for more details. */ #include "xmms/xmms_xformplugin.h" #include "xmms/xmms_sample.h" #include "xmms/xmms_log.h" #include "xmms/xmms_medialib.h" #include #include #include #include #if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 # define FLAC__STREAM_DECODER_SEEK_STATUS_OK FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_OK # define FLAC__STREAM_DECODER_SEEK_STATUS_ERROR FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_ERROR # define FLAC__STREAM_DECODER_TELL_STATUS_OK FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_OK # define FLAC__STREAM_DECODER_TELL_STATUS_ERROR FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_ERROR # define FLAC__STREAM_DECODER_LENGTH_STATUS_OK FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_OK # define FLAC__STREAM_DECODER_LENGTH_STATUS_ERROR FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_ERROR # define FLAC__STREAM_DECODER_END_OF_STREAM FLAC__SEEKABLE_STREAM_DECODER_END_OF_STREAM # define FLAC__StreamDecoder FLAC__SeekableStreamDecoder # define FLAC__StreamDecoderState FLAC__SeekableStreamDecoderState # define FLAC__StreamDecoderReadStatus FLAC__SeekableStreamDecoderReadStatus # define FLAC__StreamDecoderTellStatus FLAC__SeekableStreamDecoderTellStatus # define FLAC__StreamDecoderSeekStatus FLAC__SeekableStreamDecoderSeekStatus # define FLAC__StreamDecoderLengthStatus FLAC__SeekableStreamDecoderLengthStatus # define FLAC__stream_decoder_new FLAC__seekable_stream_decoder_new # define FLAC__stream_decoder_set_metadata_respond_all FLAC__seekable_stream_decoder_set_metadata_respond_all # define FLAC__stream_decoder_finish FLAC__seekable_stream_decoder_finish # define FLAC__stream_decoder_delete FLAC__seekable_stream_decoder_delete # define FLAC__stream_decoder_process_single FLAC__seekable_stream_decoder_process_single # define FLAC__stream_decoder_get_state FLAC__seekable_stream_decoder_get_state # define FLAC__stream_decoder_seek_absolute FLAC__seekable_stream_decoder_seek_absolute # define FLAC__stream_decoder_process_until_end_of_metadata FLAC__seekable_stream_decoder_process_until_end_of_metadata #endif typedef struct xmms_flac_data_St { FLAC__StreamDecoder *flacdecoder; FLAC__StreamMetadata *vorbiscomment; guint channels; guint sample_rate; guint bit_rate; guint bits_per_sample; guint64 total_samples; GString *buffer; } xmms_flac_data_t; /* * Function prototypes */ static gboolean xmms_flac_plugin_setup (xmms_xform_plugin_t *xform_plugin); static gint xmms_flac_read (xmms_xform_t *xform, xmms_sample_t *buf, gint len, xmms_error_t *err); static gboolean xmms_flac_init (xmms_xform_t *xform); static void xmms_flac_destroy (xmms_xform_t *xform); static gint64 xmms_flac_seek (xmms_xform_t *xform, gint64 samples, xmms_xform_seek_mode_t whence, xmms_error_t *err); /* * Plugin header */ XMMS_XFORM_PLUGIN ("flac", "FLAC Decoder", XMMS_VERSION, "Free Lossless Audio Codec decoder", xmms_flac_plugin_setup); static gboolean xmms_flac_plugin_setup (xmms_xform_plugin_t *xform_plugin) { xmms_xform_methods_t methods; XMMS_XFORM_METHODS_INIT (methods); methods.init = xmms_flac_init; methods.destroy = xmms_flac_destroy; methods.read = xmms_flac_read; methods.seek = xmms_flac_seek; xmms_xform_plugin_methods_set (xform_plugin, &methods); xmms_xform_plugin_indata_add (xform_plugin, XMMS_STREAM_TYPE_MIMETYPE, "audio/x-flac", NULL); xmms_magic_add ("flac header", "audio/x-flac", "0 string fLaC", NULL); return TRUE; } static FLAC__StreamDecoderReadStatus flac_callback_read (const FLAC__StreamDecoder *flacdecoder, FLAC__byte buffer[], guint *bytes, void *client_data) { xmms_xform_t *xform = (xmms_xform_t *) client_data; xmms_error_t error; gint ret; g_return_val_if_fail (xform, #if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_ERROR); #else FLAC__STREAM_DECODER_READ_STATUS_ABORT); #endif ret = xmms_xform_read (xform, (gchar *)buffer, *bytes, &error); *bytes = ret; #if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 return (ret <= 0) ? FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_ERROR : FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK; #else return (ret <= 0) ? FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM : FLAC__STREAM_DECODER_READ_STATUS_CONTINUE; #endif } static FLAC__StreamDecoderWriteStatus flac_callback_write (const FLAC__StreamDecoder *flacdecoder, const FLAC__Frame *frame, const FLAC__int32 * const buffer[], void *client_data) { xmms_xform_t *xform = (xmms_xform_t *)client_data; xmms_flac_data_t *data; guint length = frame->header.blocksize * frame->header.channels * frame->header.bits_per_sample / 8; guint sample, channel, pos = 0; guint8 packed[length]; guint16 *packed16 = (guint16 *) packed; data = xmms_xform_private_data_get (xform); for (sample = 0; sample < frame->header.blocksize; sample++) { for (channel = 0; channel < frame->header.channels; channel++) { switch (data->bits_per_sample) { case 8: packed[pos] = (guint8)buffer[channel][sample]; break; case 16: packed16[pos] = (guint16)buffer[channel][sample]; break; } pos++; } } g_string_append_len (data->buffer, (gchar *) packed, length); return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; } static FLAC__StreamDecoderTellStatus flac_callback_tell (const FLAC__StreamDecoder *flacdecoder, FLAC__uint64 *offset, void *client_data) { xmms_error_t err; xmms_xform_t *xform = (xmms_xform_t *) client_data; g_return_val_if_fail (xform, FLAC__STREAM_DECODER_TELL_STATUS_ERROR); xmms_error_reset (&err); *offset = xmms_xform_seek (xform, 0, XMMS_XFORM_SEEK_CUR, &err); return FLAC__STREAM_DECODER_TELL_STATUS_OK; } static FLAC__StreamDecoderSeekStatus flac_callback_seek (const FLAC__StreamDecoder *flacdecoder, FLAC__uint64 offset, void *client_data) { xmms_error_t err; xmms_xform_t *xform = (xmms_xform_t *) client_data; gint retval; xmms_error_reset (&err); retval = xmms_xform_seek (xform, (gint64) offset, XMMS_XFORM_SEEK_SET, &err); return (retval == -1) ? FLAC__STREAM_DECODER_SEEK_STATUS_ERROR : FLAC__STREAM_DECODER_SEEK_STATUS_OK; } static FLAC__StreamDecoderLengthStatus flac_callback_length (const FLAC__StreamDecoder *flacdecoder, FLAC__uint64 *stream_length, void *client_data) { xmms_xform_t *xform = (xmms_xform_t *) client_data; gint retval; retval = xmms_xform_metadata_get_int (xform, XMMS_MEDIALIB_ENTRY_PROPERTY_SIZE); *stream_length = retval; return (retval == -1) ? FLAC__STREAM_DECODER_LENGTH_STATUS_ERROR : FLAC__STREAM_DECODER_LENGTH_STATUS_OK; } static void flac_callback_metadata (const FLAC__StreamDecoder *flacdecoder, const FLAC__StreamMetadata *metadata, void *client_data) { xmms_flac_data_t *data; xmms_xform_t *xform = (xmms_xform_t *) client_data; guint64 filesize; g_return_if_fail (xform); filesize = xmms_xform_metadata_get_int (xform, XMMS_MEDIALIB_ENTRY_PROPERTY_SIZE); data = xmms_xform_private_data_get (xform); switch (metadata->type) { case FLAC__METADATA_TYPE_STREAMINFO: /* FLAC__metadata_object_clone ()? */ data->bits_per_sample = metadata->data.stream_info.bits_per_sample; data->sample_rate = metadata->data.stream_info.sample_rate; data->channels = metadata->data.stream_info.channels; data->total_samples = metadata->data.stream_info.total_samples; if (filesize > 0 && data->total_samples) { data->bit_rate = (guint) (filesize * 8 * (guint64) data->sample_rate / (guint64) data->total_samples); } XMMS_DBG ("STREAMINFO: BPS %d. Samplerate: %d. Channels: %d.", data->bits_per_sample, data->sample_rate, data->channels); break; case FLAC__METADATA_TYPE_VORBIS_COMMENT: data->vorbiscomment = FLAC__metadata_object_clone (metadata); break; /* if we want to support more metadata types here, * don't forget to add a call to * FLAC__stream_decoder_set_metadata_respond() below. */ default: break; } } static FLAC__bool flac_callback_eof (const FLAC__StreamDecoder *flacdecoder, void *client_data) { xmms_xform_t *xform = (xmms_xform_t *) client_data; g_return_val_if_fail (flacdecoder, TRUE); g_return_val_if_fail (xform, TRUE); return xmms_xform_iseos (xform); } static void flac_callback_error (const FLAC__StreamDecoder *flacdecoder, FLAC__StreamDecoderErrorStatus status, void *client_data) { xmms_xform_t *data = (xmms_xform_t *) client_data; g_return_if_fail (flacdecoder); g_return_if_fail (data); XMMS_DBG ("%s", FLAC__StreamDecoderErrorStatusString[status]); } typedef enum { STRING, INTEGER, RPGAIN } ptype; typedef struct { gchar *vname; gchar *xname; ptype type; } props; #define MUSICBRAINZ_VA_ID "89ad4ac3-39f7-470e-963a-56509c546377" /** These are the properties that we extract from the comments */ static props properties[] = { { "title", XMMS_MEDIALIB_ENTRY_PROPERTY_TITLE, STRING }, { "artist", XMMS_MEDIALIB_ENTRY_PROPERTY_ARTIST, STRING }, { "album", XMMS_MEDIALIB_ENTRY_PROPERTY_ALBUM, STRING }, { "tracknumber", XMMS_MEDIALIB_ENTRY_PROPERTY_TRACKNR, INTEGER }, { "date", XMMS_MEDIALIB_ENTRY_PROPERTY_YEAR, STRING }, { "genre", XMMS_MEDIALIB_ENTRY_PROPERTY_GENRE, STRING }, { "comment", XMMS_MEDIALIB_ENTRY_PROPERTY_COMMENT, STRING }, { "discnumber", XMMS_MEDIALIB_ENTRY_PROPERTY_PARTOFSET, INTEGER }, { "musicbrainz_albumid", XMMS_MEDIALIB_ENTRY_PROPERTY_ALBUM_ID, STRING }, { "musicbrainz_artistid", XMMS_MEDIALIB_ENTRY_PROPERTY_ARTIST_ID, STRING }, { "musicbrainz_trackid", XMMS_MEDIALIB_ENTRY_PROPERTY_TRACK_ID, STRING }, { "replaygain_track_gain",XMMS_MEDIALIB_ENTRY_PROPERTY_GAIN_TRACK,RPGAIN }, { "replaygain_album_gain",XMMS_MEDIALIB_ENTRY_PROPERTY_GAIN_ALBUM,RPGAIN }, { "replaygain_track_peak",XMMS_MEDIALIB_ENTRY_PROPERTY_PEAK_TRACK,STRING }, { "replaygain_album_peak",XMMS_MEDIALIB_ENTRY_PROPERTY_PEAK_ALBUM,STRING }, }; /* note that "key" is NOT NUL-terminated here, * but "value" is. */ static void handle_comment (xmms_xform_t *xform, const gchar *key, gint key_len, const gchar *value) { gchar buf[8]; gint i; for (i = 0; i < G_N_ELEMENTS (properties); i++) { if ((!g_ascii_strncasecmp (key, "MUSICBRAINZ_ALBUMARTISTID", key_len)) && (!g_ascii_strcasecmp (value, MUSICBRAINZ_VA_ID))) { xmms_xform_metadata_set_int (xform, XMMS_MEDIALIB_ENTRY_PROPERTY_COMPILATION, 1); } else if (!g_ascii_strncasecmp (key, properties[i].vname, key_len)) { if (properties[i].type == INTEGER) { gint tmp = strtol (value, NULL, 10); xmms_xform_metadata_set_int (xform, properties[i].xname, tmp); } else if (properties[i].type == RPGAIN) { g_snprintf (buf, sizeof (buf), "%f", pow (10.0, g_strtod (value, NULL) / 20)); /** @todo this should probably be a int instead? */ xmms_xform_metadata_set_str (xform, properties[i].xname, buf); } else { xmms_xform_metadata_set_str (xform, properties[i].xname, value); } } } } static void handle_comments (xmms_xform_t *xform, xmms_flac_data_t *data) { FLAC__StreamMetadata_VorbisComment *vc; gint i; g_return_if_fail (data->vorbiscomment); vc = &data->vorbiscomment->data.vorbis_comment; for (i = 0; i < vc->num_comments; i++) { FLAC__byte *ptr, *content = vc->comments[i].entry; gint j; /* check whether it's a valid comment */ if (!content || !*content || *content == '=') continue; for (ptr = content, j = 0; j < vc->comments[i].length; ptr++, j++) if (*ptr == '=') break; if (j == vc->comments[i].length) continue; handle_comment (xform, (gchar *) content, j, (gchar *) ptr + 1); } } static gboolean xmms_flac_init (xmms_xform_t *xform) { xmms_flac_data_t *data; xmms_sample_format_t sample_fmt; FLAC__bool retval; #if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 FLAC__StreamDecoderState init_status; #else FLAC__StreamDecoderInitStatus init_status; #endif gint filesize; g_return_val_if_fail (xform, FALSE); data = g_new0 (xmms_flac_data_t, 1); xmms_xform_private_data_set (xform, data); data->flacdecoder = FLAC__stream_decoder_new (); /* we don't need to explicitly tell the decoder to respond to * FLAC__METADATA_TYPE_STREAMINFO here, it always does. */ #if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 FLAC__seekable_stream_decoder_set_metadata_respond (data->flacdecoder, FLAC__METADATA_TYPE_VORBIS_COMMENT); FLAC__seekable_stream_decoder_set_eof_callback (data->flacdecoder, flac_callback_eof); FLAC__seekable_stream_decoder_set_read_callback (data->flacdecoder, flac_callback_read); FLAC__seekable_stream_decoder_set_seek_callback (data->flacdecoder, flac_callback_seek); FLAC__seekable_stream_decoder_set_tell_callback (data->flacdecoder, flac_callback_tell); FLAC__seekable_stream_decoder_set_write_callback (data->flacdecoder, flac_callback_write); FLAC__seekable_stream_decoder_set_error_callback (data->flacdecoder, flac_callback_error); FLAC__seekable_stream_decoder_set_length_callback (data->flacdecoder, flac_callback_length); FLAC__seekable_stream_decoder_set_metadata_callback (data->flacdecoder, flac_callback_metadata); FLAC__seekable_stream_decoder_set_client_data (data->flacdecoder, xform); init_status = FLAC__seekable_stream_decoder_init (data->flacdecoder); if (init_status != FLAC__SEEKABLE_STREAM_DECODER_OK) { const gchar *errmsg = FLAC__seekable_stream_decoder_get_resolved_state_string (data->flacdecoder); XMMS_DBG ("FLAC init failed: %s", errmsg); goto err; } #else FLAC__stream_decoder_set_metadata_respond (data->flacdecoder, FLAC__METADATA_TYPE_VORBIS_COMMENT); init_status = FLAC__stream_decoder_init_stream (data->flacdecoder, flac_callback_read, flac_callback_seek, flac_callback_tell, flac_callback_length, flac_callback_eof, flac_callback_write, flac_callback_metadata, flac_callback_error, xform); if (init_status != FLAC__STREAM_DECODER_INIT_STATUS_OK) { XMMS_DBG ("FLAC init failed: %s", FLAC__stream_decoder_get_resolved_state_string (data->flacdecoder)); goto err; } #endif retval = FLAC__stream_decoder_process_until_end_of_metadata (data->flacdecoder); if (!retval) goto err; if (data->vorbiscomment) { handle_comments (xform, data); } xmms_xform_metadata_set_int (xform, XMMS_MEDIALIB_ENTRY_PROPERTY_BITRATE, (gint) data->bit_rate); filesize = xmms_xform_metadata_get_int (xform, XMMS_MEDIALIB_ENTRY_PROPERTY_SIZE); if (filesize != -1) { xmms_xform_metadata_set_int (xform, XMMS_MEDIALIB_ENTRY_PROPERTY_DURATION, (gint) data->total_samples / data->sample_rate * 1000); } if (data->bits_per_sample != 8 && data->bits_per_sample != 16) { goto err; } if (data->bits_per_sample == 8) sample_fmt = XMMS_SAMPLE_FORMAT_S8; else sample_fmt = XMMS_SAMPLE_FORMAT_S16; xmms_xform_outdata_type_add (xform, XMMS_STREAM_TYPE_MIMETYPE, "audio/pcm", XMMS_STREAM_TYPE_FMT_FORMAT, sample_fmt, XMMS_STREAM_TYPE_FMT_CHANNELS, data->channels, XMMS_STREAM_TYPE_FMT_SAMPLERATE, data->sample_rate, XMMS_STREAM_TYPE_END); data->buffer = g_string_new (NULL); return TRUE; err: FLAC__stream_decoder_finish (data->flacdecoder); FLAC__stream_decoder_delete (data->flacdecoder); g_free (data); xmms_xform_private_data_set (xform, NULL); return FALSE; } static gint xmms_flac_read (xmms_xform_t *xform, xmms_sample_t *buf, gint len, xmms_error_t *err) { FLAC__StreamDecoderState state; xmms_flac_data_t *data; gboolean ret; guint32 size; g_return_val_if_fail (xform, FALSE); data = xmms_xform_private_data_get (xform); g_return_val_if_fail (data, FALSE); size = MIN (data->buffer->len, len); if (size <= 0) { ret = FLAC__stream_decoder_process_single (data->flacdecoder); } state = FLAC__stream_decoder_get_state (data->flacdecoder); if (state == FLAC__STREAM_DECODER_END_OF_STREAM) { return 0; } size = MIN (data->buffer->len, len); memcpy (buf, data->buffer->str, size); g_string_erase (data->buffer, 0, size); return size; } static gint64 xmms_flac_seek (xmms_xform_t *xform, gint64 samples, xmms_xform_seek_mode_t whence, xmms_error_t *err) { xmms_flac_data_t *data; FLAC__bool res; g_return_val_if_fail (xform, -1); g_return_val_if_fail (whence == XMMS_XFORM_SEEK_SET, -1); data = xmms_xform_private_data_get (xform); g_return_val_if_fail (data, -1); if (samples > data->total_samples) { xmms_log_error ("Trying to seek past end of stream"); return -1; } res = FLAC__stream_decoder_seek_absolute (data->flacdecoder, (FLAC__uint64) samples); return res ? samples : -1; } static void xmms_flac_destroy (xmms_xform_t *xform) { xmms_flac_data_t *data; g_return_if_fail (xform); data = xmms_xform_private_data_get (xform); g_return_if_fail (data); if (data->vorbiscomment) { FLAC__metadata_object_delete (data->vorbiscomment); } g_string_free (data->buffer, TRUE); FLAC__stream_decoder_finish (data->flacdecoder); FLAC__stream_decoder_delete (data->flacdecoder); g_free (data); }