From c6205bd4617b12f080cff86ecf0d964ebe39dd62 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Mon, 16 Feb 2009 19:31:06 +0100 Subject: renamed decoder plugin "oggvorbis" to "vorbis" This plugin uses libvorbis. --- src/Makefile.am | 4 +- src/decoder/oggvorbis_plugin.c | 415 ----------------------------------------- src/decoder/vorbis_plugin.c | 415 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 417 insertions(+), 417 deletions(-) delete mode 100644 src/decoder/oggvorbis_plugin.c create mode 100644 src/decoder/vorbis_plugin.c (limited to 'src') diff --git a/src/Makefile.am b/src/Makefile.am index f3d9e40cf..77781b028 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -279,7 +279,7 @@ mpd_SOURCES += decoder/wavpack_plugin.c endif if HAVE_FAAD -mpd_SOURCES += decoder/aac_plugin.c +mpd_SOURCES += decoder/faad_plugin.c endif if HAVE_MP4 @@ -295,7 +295,7 @@ mpd_SOURCES += decoder/_flac_common.c endif if HAVE_OGGVORBIS -mpd_SOURCES += decoder/oggvorbis_plugin.c +mpd_SOURCES += decoder/vorbis_plugin.c endif if HAVE_FLAC diff --git a/src/decoder/oggvorbis_plugin.c b/src/decoder/oggvorbis_plugin.c deleted file mode 100644 index e935870de..000000000 --- a/src/decoder/oggvorbis_plugin.c +++ /dev/null @@ -1,415 +0,0 @@ -/* the Music Player Daemon (MPD) - * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) - * This project's homepage is: http://www.musicpd.org - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program 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 General Public License for more details. - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -/* TODO 'ogg' should probably be replaced with 'oggvorbis' in all instances */ - -#include "_ogg_common.h" -#include "config.h" -#include "ls.h" - -#ifndef HAVE_TREMOR -#include -#else -#include -/* Macros to make Tremor's API look like libogg. Tremor always - returns host-byte-order 16-bit signed data, and uses integer - milliseconds where libogg uses double seconds. -*/ -#define ov_read(VF, BUFFER, LENGTH, BIGENDIANP, WORD, SGNED, BITSTREAM) \ - ov_read(VF, BUFFER, LENGTH, BITSTREAM) -#define ov_time_total(VF, I) ((double)ov_time_total(VF, I)/1000) -#define ov_time_tell(VF) ((double)ov_time_tell(VF)/1000) -#define ov_time_seek_page(VF, S) (ov_time_seek_page(VF, (S)*1000)) -#endif /* HAVE_TREMOR */ - -#include - -#include -#include -#include -#include - -#if G_BYTE_ORDER == G_BIG_ENDIAN -#define OGG_DECODE_USE_BIGENDIAN 1 -#else -#define OGG_DECODE_USE_BIGENDIAN 0 -#endif - -typedef struct _OggCallbackData { - struct decoder *decoder; - - struct input_stream *input_stream; - bool seekable; -} OggCallbackData; - -static size_t ogg_read_cb(void *ptr, size_t size, size_t nmemb, void *vdata) -{ - size_t ret; - OggCallbackData *data = (OggCallbackData *) vdata; - - ret = decoder_read(data->decoder, data->input_stream, ptr, size * nmemb); - - errno = 0; - - return ret / size; -} - -static int ogg_seek_cb(void *vdata, ogg_int64_t offset, int whence) -{ - const OggCallbackData *data = (const OggCallbackData *) vdata; - - return data->seekable && - decoder_get_command(data->decoder) != DECODE_COMMAND_STOP && - input_stream_seek(data->input_stream, offset, whence) - ? 0 : -1; -} - -/* TODO: check Ogg libraries API and see if we can just not have this func */ -static int ogg_close_cb(G_GNUC_UNUSED void *vdata) -{ - return 0; -} - -static long ogg_tell_cb(void *vdata) -{ - const OggCallbackData *data = (const OggCallbackData *) vdata; - - return (long)data->input_stream->offset; -} - -static const char * -vorbis_comment_value(const char *comment, const char *needle) -{ - size_t len = strlen(needle); - - if (g_ascii_strncasecmp(comment, needle, len) == 0 && - comment[len] == '=') - return comment + len + 1; - - return NULL; -} - -static struct replay_gain_info * -vorbis_comments_to_replay_gain(char **comments) -{ - struct replay_gain_info *rgi; - const char *temp; - bool found = false; - - rgi = replay_gain_info_new(); - - while (*comments) { - if ((temp = - vorbis_comment_value(*comments, "replaygain_track_gain"))) { - rgi->tuples[REPLAY_GAIN_TRACK].gain = atof(temp); - found = true; - } else if ((temp = vorbis_comment_value(*comments, - "replaygain_album_gain"))) { - rgi->tuples[REPLAY_GAIN_ALBUM].gain = atof(temp); - found = true; - } else if ((temp = vorbis_comment_value(*comments, - "replaygain_track_peak"))) { - rgi->tuples[REPLAY_GAIN_TRACK].peak = atof(temp); - found = true; - } else if ((temp = vorbis_comment_value(*comments, - "replaygain_album_peak"))) { - rgi->tuples[REPLAY_GAIN_ALBUM].peak = atof(temp); - found = true; - } - - comments++; - } - - if (!found) { - replay_gain_info_free(rgi); - rgi = NULL; - } - - return rgi; -} - -static const char *VORBIS_COMMENT_TRACK_KEY = "tracknumber"; -static const char *VORBIS_COMMENT_DISC_KEY = "discnumber"; - -/** - * Check if the comment's name equals the passed name, and if so, copy - * the comment value into the tag. - */ -static bool -vorbis_copy_comment(struct tag *tag, const char *comment, - const char *name, enum tag_type tag_type) -{ - const char *value; - - value = vorbis_comment_value(comment, name); - if (value != NULL) { - tag_add_item(tag, tag_type, value); - return true; - } - - return false; -} - -static void -vorbis_parse_comment(struct tag *tag, const char *comment) -{ - assert(tag != NULL); - - if (vorbis_copy_comment(tag, comment, VORBIS_COMMENT_TRACK_KEY, - TAG_ITEM_TRACK) || - vorbis_copy_comment(tag, comment, VORBIS_COMMENT_DISC_KEY, - TAG_ITEM_DISC) || - vorbis_copy_comment(tag, comment, "album artist", - TAG_ITEM_ALBUM_ARTIST)) - return; - - for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) - if (vorbis_copy_comment(tag, comment, - mpdTagItemKeys[i], i)) - return; -} - -static struct tag * -vorbis_comments_to_tag(char **comments) -{ - struct tag *tag = tag_new(); - - while (*comments) - vorbis_parse_comment(tag, *comments++); - - if (tag_is_empty(tag)) { - tag_free(tag); - tag = NULL; - } - - return tag; -} - -static void -vorbis_send_comments(struct decoder *decoder, struct input_stream *is, - char **comments) -{ - struct tag *tag; - - tag = vorbis_comments_to_tag(comments); - if (!tag) - return; - - decoder_tag(decoder, is, tag); - tag_free(tag); -} - -static bool -oggvorbis_seekable(struct decoder *decoder) -{ - char *uri; - bool seekable; - - uri = decoder_get_uri(decoder); - /* disable seeking on remote streams, because libvorbis seeks - around like crazy, and due to being very expensive, this - delays song playback my 10 or 20 seconds */ - seekable = !uri_has_scheme(uri); - g_free(uri); - - return seekable; -} - -/* public */ -static void -vorbis_stream_decode(struct decoder *decoder, - struct input_stream *input_stream) -{ - OggVorbis_File vf; - ov_callbacks callbacks; - OggCallbackData data; - struct audio_format audio_format; - int current_section; - int prev_section = -1; - long ret; -#define OGG_CHUNK_SIZE 4096 - char chunk[OGG_CHUNK_SIZE]; - long bitRate = 0; - long test; - struct replay_gain_info *replay_gain_info = NULL; - char **comments; - bool initialized = false; - enum decoder_command cmd = DECODE_COMMAND_NONE; - - if (ogg_stream_type_detect(input_stream) != VORBIS) - return; - - /* rewind the stream, because ogg_stream_type_detect() has - moved it */ - input_stream_seek(input_stream, 0, SEEK_SET); - - data.decoder = decoder; - data.input_stream = input_stream; - data.seekable = input_stream->seekable && oggvorbis_seekable(decoder); - - callbacks.read_func = ogg_read_cb; - callbacks.seek_func = ogg_seek_cb; - callbacks.close_func = ogg_close_cb; - callbacks.tell_func = ogg_tell_cb; - if ((ret = ov_open_callbacks(&data, &vf, NULL, 0, callbacks)) < 0) { - const char *error; - - if (decoder_get_command(decoder) != DECODE_COMMAND_NONE) - return; - - switch (ret) { - case OV_EREAD: - error = "read error"; - break; - case OV_ENOTVORBIS: - error = "not vorbis stream"; - break; - case OV_EVERSION: - error = "vorbis version mismatch"; - break; - case OV_EBADHEADER: - error = "invalid vorbis header"; - break; - case OV_EFAULT: - error = "internal logic error"; - break; - default: - error = "unknown error"; - break; - } - - g_warning("Error decoding Ogg Vorbis stream: %s", error); - return; - } - audio_format.bits = 16; - - do { - if (cmd == DECODE_COMMAND_SEEK) { - double seek_where = decoder_seek_where(decoder); - if (0 == ov_time_seek_page(&vf, seek_where)) { - decoder_command_finished(decoder); - } else - decoder_seek_error(decoder); - } - - ret = ov_read(&vf, chunk, sizeof(chunk), - OGG_DECODE_USE_BIGENDIAN, 2, 1, ¤t_section); - if (ret == OV_HOLE) /* bad packet */ - ret = 0; - else if (ret <= 0) - /* break on EOF or other error */ - break; - - if (current_section != prev_section) { - /*printf("new song!\n"); */ - vorbis_info *vi = ov_info(&vf, -1); - struct replay_gain_info *new_rgi; - - audio_format.channels = vi->channels; - audio_format.sample_rate = vi->rate; - - if (!audio_format_valid(&audio_format)) { - g_warning("Invalid audio format: %u:%u:%u\n", - audio_format.sample_rate, - audio_format.bits, - audio_format.channels); - break; - } - - if (!initialized) { - float total_time = ov_time_total(&vf, -1); - if (total_time < 0) - total_time = 0; - decoder_initialized(decoder, &audio_format, - data.seekable, - total_time); - initialized = true; - } - comments = ov_comment(&vf, -1)->user_comments; - vorbis_send_comments(decoder, input_stream, comments); - new_rgi = vorbis_comments_to_replay_gain(comments); - if (new_rgi != NULL) { - if (replay_gain_info != NULL) - replay_gain_info_free(replay_gain_info); - replay_gain_info = new_rgi; - } - } - - prev_section = current_section; - - if ((test = ov_bitrate_instant(&vf)) > 0) - bitRate = test / 1000; - - cmd = decoder_data(decoder, input_stream, - chunk, ret, - ov_pcm_tell(&vf) / audio_format.sample_rate, - bitRate, replay_gain_info); - } while (cmd != DECODE_COMMAND_STOP); - - if (replay_gain_info) - replay_gain_info_free(replay_gain_info); - - ov_clear(&vf); -} - -static struct tag * -vorbis_tag_dup(const char *file) -{ - struct tag *ret; - FILE *fp; - OggVorbis_File vf; - - fp = fopen(file, "r"); - if (!fp) { - return NULL; - } - - if (ov_open(fp, &vf, NULL, 0) < 0) { - fclose(fp); - return NULL; - } - - ret = vorbis_comments_to_tag(ov_comment(&vf, -1)->user_comments); - - if (!ret) - ret = tag_new(); - ret->time = (int)(ov_time_total(&vf, -1) + 0.5); - - ov_clear(&vf); - - return ret; -} - -static const char *const vorbis_suffixes[] = { - "ogg", "oga", NULL -}; - -static const char *const vorbis_mime_types[] = { - "application/ogg", - "audio/x-vorbis+ogg", - "application/x-ogg", - NULL -}; - -const struct decoder_plugin vorbis_decoder_plugin = { - .name = "oggvorbis", - .stream_decode = vorbis_stream_decode, - .tag_dup = vorbis_tag_dup, - .suffixes = vorbis_suffixes, - .mime_types = vorbis_mime_types -}; diff --git a/src/decoder/vorbis_plugin.c b/src/decoder/vorbis_plugin.c new file mode 100644 index 000000000..526824cfc --- /dev/null +++ b/src/decoder/vorbis_plugin.c @@ -0,0 +1,415 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* TODO 'ogg' should probably be replaced with 'oggvorbis' in all instances */ + +#include "_ogg_common.h" +#include "config.h" +#include "ls.h" + +#ifndef HAVE_TREMOR +#include +#else +#include +/* Macros to make Tremor's API look like libogg. Tremor always + returns host-byte-order 16-bit signed data, and uses integer + milliseconds where libogg uses double seconds. +*/ +#define ov_read(VF, BUFFER, LENGTH, BIGENDIANP, WORD, SGNED, BITSTREAM) \ + ov_read(VF, BUFFER, LENGTH, BITSTREAM) +#define ov_time_total(VF, I) ((double)ov_time_total(VF, I)/1000) +#define ov_time_tell(VF) ((double)ov_time_tell(VF)/1000) +#define ov_time_seek_page(VF, S) (ov_time_seek_page(VF, (S)*1000)) +#endif /* HAVE_TREMOR */ + +#include + +#include +#include +#include +#include + +#if G_BYTE_ORDER == G_BIG_ENDIAN +#define OGG_DECODE_USE_BIGENDIAN 1 +#else +#define OGG_DECODE_USE_BIGENDIAN 0 +#endif + +typedef struct _OggCallbackData { + struct decoder *decoder; + + struct input_stream *input_stream; + bool seekable; +} OggCallbackData; + +static size_t ogg_read_cb(void *ptr, size_t size, size_t nmemb, void *vdata) +{ + size_t ret; + OggCallbackData *data = (OggCallbackData *) vdata; + + ret = decoder_read(data->decoder, data->input_stream, ptr, size * nmemb); + + errno = 0; + + return ret / size; +} + +static int ogg_seek_cb(void *vdata, ogg_int64_t offset, int whence) +{ + const OggCallbackData *data = (const OggCallbackData *) vdata; + + return data->seekable && + decoder_get_command(data->decoder) != DECODE_COMMAND_STOP && + input_stream_seek(data->input_stream, offset, whence) + ? 0 : -1; +} + +/* TODO: check Ogg libraries API and see if we can just not have this func */ +static int ogg_close_cb(G_GNUC_UNUSED void *vdata) +{ + return 0; +} + +static long ogg_tell_cb(void *vdata) +{ + const OggCallbackData *data = (const OggCallbackData *) vdata; + + return (long)data->input_stream->offset; +} + +static const char * +vorbis_comment_value(const char *comment, const char *needle) +{ + size_t len = strlen(needle); + + if (g_ascii_strncasecmp(comment, needle, len) == 0 && + comment[len] == '=') + return comment + len + 1; + + return NULL; +} + +static struct replay_gain_info * +vorbis_comments_to_replay_gain(char **comments) +{ + struct replay_gain_info *rgi; + const char *temp; + bool found = false; + + rgi = replay_gain_info_new(); + + while (*comments) { + if ((temp = + vorbis_comment_value(*comments, "replaygain_track_gain"))) { + rgi->tuples[REPLAY_GAIN_TRACK].gain = atof(temp); + found = true; + } else if ((temp = vorbis_comment_value(*comments, + "replaygain_album_gain"))) { + rgi->tuples[REPLAY_GAIN_ALBUM].gain = atof(temp); + found = true; + } else if ((temp = vorbis_comment_value(*comments, + "replaygain_track_peak"))) { + rgi->tuples[REPLAY_GAIN_TRACK].peak = atof(temp); + found = true; + } else if ((temp = vorbis_comment_value(*comments, + "replaygain_album_peak"))) { + rgi->tuples[REPLAY_GAIN_ALBUM].peak = atof(temp); + found = true; + } + + comments++; + } + + if (!found) { + replay_gain_info_free(rgi); + rgi = NULL; + } + + return rgi; +} + +static const char *VORBIS_COMMENT_TRACK_KEY = "tracknumber"; +static const char *VORBIS_COMMENT_DISC_KEY = "discnumber"; + +/** + * Check if the comment's name equals the passed name, and if so, copy + * the comment value into the tag. + */ +static bool +vorbis_copy_comment(struct tag *tag, const char *comment, + const char *name, enum tag_type tag_type) +{ + const char *value; + + value = vorbis_comment_value(comment, name); + if (value != NULL) { + tag_add_item(tag, tag_type, value); + return true; + } + + return false; +} + +static void +vorbis_parse_comment(struct tag *tag, const char *comment) +{ + assert(tag != NULL); + + if (vorbis_copy_comment(tag, comment, VORBIS_COMMENT_TRACK_KEY, + TAG_ITEM_TRACK) || + vorbis_copy_comment(tag, comment, VORBIS_COMMENT_DISC_KEY, + TAG_ITEM_DISC) || + vorbis_copy_comment(tag, comment, "album artist", + TAG_ITEM_ALBUM_ARTIST)) + return; + + for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) + if (vorbis_copy_comment(tag, comment, + mpdTagItemKeys[i], i)) + return; +} + +static struct tag * +vorbis_comments_to_tag(char **comments) +{ + struct tag *tag = tag_new(); + + while (*comments) + vorbis_parse_comment(tag, *comments++); + + if (tag_is_empty(tag)) { + tag_free(tag); + tag = NULL; + } + + return tag; +} + +static void +vorbis_send_comments(struct decoder *decoder, struct input_stream *is, + char **comments) +{ + struct tag *tag; + + tag = vorbis_comments_to_tag(comments); + if (!tag) + return; + + decoder_tag(decoder, is, tag); + tag_free(tag); +} + +static bool +oggvorbis_seekable(struct decoder *decoder) +{ + char *uri; + bool seekable; + + uri = decoder_get_uri(decoder); + /* disable seeking on remote streams, because libvorbis seeks + around like crazy, and due to being very expensive, this + delays song playback my 10 or 20 seconds */ + seekable = !uri_has_scheme(uri); + g_free(uri); + + return seekable; +} + +/* public */ +static void +vorbis_stream_decode(struct decoder *decoder, + struct input_stream *input_stream) +{ + OggVorbis_File vf; + ov_callbacks callbacks; + OggCallbackData data; + struct audio_format audio_format; + int current_section; + int prev_section = -1; + long ret; +#define OGG_CHUNK_SIZE 4096 + char chunk[OGG_CHUNK_SIZE]; + long bitRate = 0; + long test; + struct replay_gain_info *replay_gain_info = NULL; + char **comments; + bool initialized = false; + enum decoder_command cmd = DECODE_COMMAND_NONE; + + if (ogg_stream_type_detect(input_stream) != VORBIS) + return; + + /* rewind the stream, because ogg_stream_type_detect() has + moved it */ + input_stream_seek(input_stream, 0, SEEK_SET); + + data.decoder = decoder; + data.input_stream = input_stream; + data.seekable = input_stream->seekable && oggvorbis_seekable(decoder); + + callbacks.read_func = ogg_read_cb; + callbacks.seek_func = ogg_seek_cb; + callbacks.close_func = ogg_close_cb; + callbacks.tell_func = ogg_tell_cb; + if ((ret = ov_open_callbacks(&data, &vf, NULL, 0, callbacks)) < 0) { + const char *error; + + if (decoder_get_command(decoder) != DECODE_COMMAND_NONE) + return; + + switch (ret) { + case OV_EREAD: + error = "read error"; + break; + case OV_ENOTVORBIS: + error = "not vorbis stream"; + break; + case OV_EVERSION: + error = "vorbis version mismatch"; + break; + case OV_EBADHEADER: + error = "invalid vorbis header"; + break; + case OV_EFAULT: + error = "internal logic error"; + break; + default: + error = "unknown error"; + break; + } + + g_warning("Error decoding Ogg Vorbis stream: %s", error); + return; + } + audio_format.bits = 16; + + do { + if (cmd == DECODE_COMMAND_SEEK) { + double seek_where = decoder_seek_where(decoder); + if (0 == ov_time_seek_page(&vf, seek_where)) { + decoder_command_finished(decoder); + } else + decoder_seek_error(decoder); + } + + ret = ov_read(&vf, chunk, sizeof(chunk), + OGG_DECODE_USE_BIGENDIAN, 2, 1, ¤t_section); + if (ret == OV_HOLE) /* bad packet */ + ret = 0; + else if (ret <= 0) + /* break on EOF or other error */ + break; + + if (current_section != prev_section) { + /*printf("new song!\n"); */ + vorbis_info *vi = ov_info(&vf, -1); + struct replay_gain_info *new_rgi; + + audio_format.channels = vi->channels; + audio_format.sample_rate = vi->rate; + + if (!audio_format_valid(&audio_format)) { + g_warning("Invalid audio format: %u:%u:%u\n", + audio_format.sample_rate, + audio_format.bits, + audio_format.channels); + break; + } + + if (!initialized) { + float total_time = ov_time_total(&vf, -1); + if (total_time < 0) + total_time = 0; + decoder_initialized(decoder, &audio_format, + data.seekable, + total_time); + initialized = true; + } + comments = ov_comment(&vf, -1)->user_comments; + vorbis_send_comments(decoder, input_stream, comments); + new_rgi = vorbis_comments_to_replay_gain(comments); + if (new_rgi != NULL) { + if (replay_gain_info != NULL) + replay_gain_info_free(replay_gain_info); + replay_gain_info = new_rgi; + } + } + + prev_section = current_section; + + if ((test = ov_bitrate_instant(&vf)) > 0) + bitRate = test / 1000; + + cmd = decoder_data(decoder, input_stream, + chunk, ret, + ov_pcm_tell(&vf) / audio_format.sample_rate, + bitRate, replay_gain_info); + } while (cmd != DECODE_COMMAND_STOP); + + if (replay_gain_info) + replay_gain_info_free(replay_gain_info); + + ov_clear(&vf); +} + +static struct tag * +vorbis_tag_dup(const char *file) +{ + struct tag *ret; + FILE *fp; + OggVorbis_File vf; + + fp = fopen(file, "r"); + if (!fp) { + return NULL; + } + + if (ov_open(fp, &vf, NULL, 0) < 0) { + fclose(fp); + return NULL; + } + + ret = vorbis_comments_to_tag(ov_comment(&vf, -1)->user_comments); + + if (!ret) + ret = tag_new(); + ret->time = (int)(ov_time_total(&vf, -1) + 0.5); + + ov_clear(&vf); + + return ret; +} + +static const char *const vorbis_suffixes[] = { + "ogg", "oga", NULL +}; + +static const char *const vorbis_mime_types[] = { + "application/ogg", + "audio/x-vorbis+ogg", + "application/x-ogg", + NULL +}; + +const struct decoder_plugin vorbis_decoder_plugin = { + .name = "vorbis", + .stream_decode = vorbis_stream_decode, + .tag_dup = vorbis_tag_dup, + .suffixes = vorbis_suffixes, + .mime_types = vorbis_mime_types +}; -- cgit v1.2.3