diff src/mpg123/mpg123.c @ 12:3da1b8942b8b trunk

[svn] - remove src/Input src/Output src/Effect src/General src/Visualization src/Container
author nenolod
date Mon, 18 Sep 2006 03:14:20 -0700
parents src/Input/mpg123/mpg123.c@088092a52fea
children 98de08786b0a
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/mpg123/mpg123.c	Mon Sep 18 03:14:20 2006 -0700
@@ -0,0 +1,997 @@
+#include "mpg123.h"
+#include "common.h"
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+
+#include "audacious/util.h"
+#include "audacious/configdb.h"
+#include "audacious/vfs.h"
+#include "audacious/titlestring.h"
+#include "audacious/util.h"
+#include <tag_c.h>
+
+#define BUFSIZE_X	2048
+
+static struct frame fr, temp_fr;
+
+PlayerInfo *mpgdec_info = NULL;
+static GThread *decode_thread;
+
+static gboolean audio_error = FALSE, output_opened = FALSE, dopause = FALSE;
+gint mpgdec_bitrate, mpgdec_frequency, mpgdec_length, mpgdec_layer,
+    mpgdec_lsf;
+gchar *mpgdec_title = NULL, *mpgdec_filename = NULL;
+static int disp_bitrate, skip_frames = 0;
+static int cpu_fflags, cpu_efflags;
+gboolean mpgdec_stereo, mpgdec_mpeg25;
+int mpgdec_mode;
+
+mpgdec_t *ins;
+
+gchar **mpgdec_id3_encoding_list = NULL;
+
+const char *mpgdec_id3_genres[GENRE_MAX] = {
+    N_("Blues"), N_("Classic Rock"), N_("Country"), N_("Dance"),
+    N_("Disco"), N_("Funk"), N_("Grunge"), N_("Hip-Hop"),
+    N_("Jazz"), N_("Metal"), N_("New Age"), N_("Oldies"),
+    N_("Other"), N_("Pop"), N_("R&B"), N_("Rap"), N_("Reggae"),
+    N_("Rock"), N_("Techno"), N_("Industrial"), N_("Alternative"),
+    N_("Ska"), N_("Death Metal"), N_("Pranks"), N_("Soundtrack"),
+    N_("Euro-Techno"), N_("Ambient"), N_("Trip-Hop"), N_("Vocal"),
+    N_("Jazz+Funk"), N_("Fusion"), N_("Trance"), N_("Classical"),
+    N_("Instrumental"), N_("Acid"), N_("House"), N_("Game"),
+    N_("Sound Clip"), N_("Gospel"), N_("Noise"), N_("AlternRock"),
+    N_("Bass"), N_("Soul"), N_("Punk"), N_("Space"),
+    N_("Meditative"), N_("Instrumental Pop"),
+    N_("Instrumental Rock"), N_("Ethnic"), N_("Gothic"),
+    N_("Darkwave"), N_("Techno-Industrial"), N_("Electronic"),
+    N_("Pop-Folk"), N_("Eurodance"), N_("Dream"),
+    N_("Southern Rock"), N_("Comedy"), N_("Cult"),
+    N_("Gangsta Rap"), N_("Top 40"), N_("Christian Rap"),
+    N_("Pop/Funk"), N_("Jungle"), N_("Native American"),
+    N_("Cabaret"), N_("New Wave"), N_("Psychedelic"), N_("Rave"),
+    N_("Showtunes"), N_("Trailer"), N_("Lo-Fi"), N_("Tribal"),
+    N_("Acid Punk"), N_("Acid Jazz"), N_("Polka"), N_("Retro"),
+    N_("Musical"), N_("Rock & Roll"), N_("Hard Rock"), N_("Folk"),
+    N_("Folk/Rock"), N_("National Folk"), N_("Swing"),
+    N_("Fast-Fusion"), N_("Bebob"), N_("Latin"), N_("Revival"),
+    N_("Celtic"), N_("Bluegrass"), N_("Avantgarde"),
+    N_("Gothic Rock"), N_("Progressive Rock"),
+    N_("Psychedelic Rock"), N_("Symphonic Rock"), N_("Slow Rock"),
+    N_("Big Band"), N_("Chorus"), N_("Easy Listening"),
+    N_("Acoustic"), N_("Humour"), N_("Speech"), N_("Chanson"),
+    N_("Opera"), N_("Chamber Music"), N_("Sonata"), N_("Symphony"),
+    N_("Booty Bass"), N_("Primus"), N_("Porn Groove"),
+    N_("Satire"), N_("Slow Jam"), N_("Club"), N_("Tango"),
+    N_("Samba"), N_("Folklore"), N_("Ballad"), N_("Power Ballad"),
+    N_("Rhythmic Soul"), N_("Freestyle"), N_("Duet"),
+    N_("Punk Rock"), N_("Drum Solo"), N_("A Cappella"),
+    N_("Euro-House"), N_("Dance Hall"), N_("Goa"),
+    N_("Drum & Bass"), N_("Club-House"), N_("Hardcore"),
+    N_("Terror"), N_("Indie"), N_("BritPop"), N_("Negerpunk"),
+    N_("Polsk Punk"), N_("Beat"), N_("Christian Gangsta Rap"),
+    N_("Heavy Metal"), N_("Black Metal"), N_("Crossover"),
+    N_("Contemporary Christian"), N_("Christian Rock"),
+    N_("Merengue"), N_("Salsa"), N_("Thrash Metal"),
+    N_("Anime"), N_("JPop"), N_("Synthpop")
+};
+
+double
+mpgdec_compute_tpf(struct frame *fr)
+{
+    const int bs[4] = { 0, 384, 1152, 1152 };
+    double tpf;
+
+    tpf = bs[fr->lay];
+    tpf /= mpgdec_freqs[fr->sampling_frequency] << (fr->lsf);
+    return tpf;
+}
+
+static void
+set_synth_functions(struct frame *fr)
+{
+    typedef int (*func) (mpgdec_real *, int, unsigned char *, int *);
+    typedef int (*func_mono) (mpgdec_real *, unsigned char *, int *);
+    typedef void (*func_dct36) (mpgdec_real *, mpgdec_real *, mpgdec_real *, mpgdec_real *, mpgdec_real *);
+
+    int ds = fr->down_sample;
+    int p8 = 0;
+
+    static func funcs[2][2] = {
+        {mpgdec_synth_1to1,
+         mpgdec_synth_ntom},
+        {mpgdec_synth_1to1_8bit,
+	 mpgdec_synth_ntom_8bit}
+    };
+
+    static func_mono funcs_mono[2][2] = {
+        {mpgdec_synth_1to1_mono,
+         mpgdec_synth_ntom_mono},
+        {mpgdec_synth_1to1_8bit_mono,
+         mpgdec_synth_ntom_8bit_mono}
+    };
+
+    /* Compatibility with older configs. */
+    if (ds > 1)
+	ds = 1;
+
+    if (mpgdec_cfg.resolution == 8)
+        p8 = 1;
+    fr->synth = funcs[p8][ds];
+    fr->synth_mono = funcs_mono[p8][ds];
+    fr->synth_type = SYNTH_FPU;
+
+    if (p8) {
+        mpgdec_make_conv16to8_table();
+    }
+}
+
+static void
+init(void)
+{
+    ConfigDb *db;
+    gchar *tmp = NULL;
+
+    ins = mpgdec_new();
+
+    memset(&mpgdec_cfg, 0, sizeof(mpgdec_cfg));
+
+    mpgdec_cfg.resolution = 16;
+    mpgdec_cfg.channels = 2;
+    mpgdec_cfg.downsample = 0;
+    mpgdec_cfg.http_buffer_size = 128;
+    mpgdec_cfg.http_prebuffer = 25;
+    mpgdec_cfg.proxy_port = 8080;
+    mpgdec_cfg.proxy_use_auth = FALSE;
+    mpgdec_cfg.proxy_user = NULL;
+    mpgdec_cfg.proxy_pass = NULL;
+    mpgdec_cfg.use_udp_channel = TRUE;
+    mpgdec_cfg.title_override = FALSE;
+    mpgdec_cfg.disable_id3v2 = FALSE;
+    mpgdec_cfg.default_synth = SYNTH_AUTO;
+
+    mpgdec_cfg.title_encoding_enabled = FALSE;
+    mpgdec_cfg.title_encoding = NULL;
+
+    db = bmp_cfg_db_open();
+
+    bmp_cfg_db_get_int(db, "MPG123", "resolution", &mpgdec_cfg.resolution);
+    bmp_cfg_db_get_int(db, "MPG123", "channels", &mpgdec_cfg.channels);
+    bmp_cfg_db_get_int(db, "MPG123", "downsample", &mpgdec_cfg.downsample);
+    bmp_cfg_db_get_int(db, "MPG123", "http_buffer_size",
+                       &mpgdec_cfg.http_buffer_size);
+    bmp_cfg_db_get_int(db, "MPG123", "http_prebuffer",
+                       &mpgdec_cfg.http_prebuffer);
+    bmp_cfg_db_get_bool(db, "MPG123", "save_http_stream",
+                        &mpgdec_cfg.save_http_stream);
+    if (!bmp_cfg_db_get_string
+        (db, "MPG123", "save_http_path", &mpgdec_cfg.save_http_path))
+        mpgdec_cfg.save_http_path = g_strdup(g_get_home_dir());
+
+    bmp_cfg_db_get_bool(db, "MPG123", "use_udp_channel",
+                        &mpgdec_cfg.use_udp_channel);
+
+    bmp_cfg_db_get_bool(db, "MPG123", "title_override",
+                        &mpgdec_cfg.title_override);
+    bmp_cfg_db_get_bool(db, "MPG123", "disable_id3v2",
+                        &mpgdec_cfg.disable_id3v2);
+    if (!bmp_cfg_db_get_string
+        (db, "MPG123", "id3_format", &mpgdec_cfg.id3_format))
+        mpgdec_cfg.id3_format = g_strdup("%p - %t");
+    bmp_cfg_db_get_int(db, "MPG123", "default_synth",
+                       &mpgdec_cfg.default_synth);
+
+    bmp_cfg_db_get_bool(db, "MPG123", "title_encoding_enabled", &mpgdec_cfg.title_encoding_enabled);
+    bmp_cfg_db_get_string(db, "MPG123", "title_encoding", &mpgdec_cfg.title_encoding);
+    if (mpgdec_cfg.title_encoding_enabled)
+        mpgdec_id3_encoding_list = g_strsplit_set(mpgdec_cfg.title_encoding, ENCODING_SEPARATOR, 0);
+
+    bmp_cfg_db_get_bool(db, NULL, "use_proxy", &mpgdec_cfg.use_proxy);
+    bmp_cfg_db_get_string(db, NULL, "proxy_host", &mpgdec_cfg.proxy_host);
+    bmp_cfg_db_get_string(db, NULL, "proxy_port", &tmp);
+
+    if (tmp != NULL)
+	mpgdec_cfg.proxy_port = atoi(tmp);
+
+    bmp_cfg_db_get_bool(db, NULL, "proxy_use_auth", &mpgdec_cfg.proxy_use_auth);
+    bmp_cfg_db_get_string(db, NULL, "proxy_user", &mpgdec_cfg.proxy_user);
+    bmp_cfg_db_get_string(db, NULL, "proxy_pass", &mpgdec_cfg.proxy_pass);
+
+    bmp_cfg_db_close(db);
+
+    if (mpgdec_cfg.resolution != 16 && mpgdec_cfg.resolution != 8)
+        mpgdec_cfg.resolution = 16;
+
+    mpgdec_cfg.channels = CLAMP(mpgdec_cfg.channels, 0, 2);
+    mpgdec_cfg.downsample = CLAMP(mpgdec_cfg.downsample, 0, 2);
+    mpgdec_getcpuflags(&cpu_fflags, &cpu_efflags);
+}
+
+static void
+cleanup(void)
+{
+    g_free(mpgdec_ip.description);
+    mpgdec_ip.description = NULL;
+
+    if (mpgdec_cfg.save_http_path) {
+        free(mpgdec_cfg.save_http_path);
+        mpgdec_cfg.save_http_path = NULL;
+    }
+
+    if (mpgdec_cfg.proxy_host) {
+        free(mpgdec_cfg.proxy_host);
+        mpgdec_cfg.proxy_host = NULL;
+    }
+
+    if (mpgdec_cfg.proxy_user) {
+        free(mpgdec_cfg.proxy_user);
+        mpgdec_cfg.proxy_user = NULL;
+    }
+
+    if (mpgdec_cfg.proxy_pass) {
+        free(mpgdec_cfg.proxy_pass);
+        mpgdec_cfg.proxy_pass = NULL;
+    }
+
+    if (mpgdec_cfg.id3_format) {
+        free(mpgdec_cfg.id3_format);
+        mpgdec_cfg.id3_format = NULL;
+    }
+
+    if (mpgdec_cfg.title_encoding) {
+        free(mpgdec_cfg.title_encoding);
+        mpgdec_cfg.title_encoding = NULL;
+    }
+
+    g_strfreev(mpgdec_id3_encoding_list);
+}
+
+static guint32
+convert_to_header(guint8 * buf)
+{
+    return (buf[0] << 24) + (buf[1] << 16) + (buf[2] << 8) + buf[3];
+}
+
+
+#define DET_BUF_SIZE 4096
+
+static gboolean
+mpgdec_detect_by_content(char *filename)
+{
+    VFSFile *file;
+    guchar tmp[4];
+    guint32 head;
+    struct frame fr;
+    guchar buf[DET_BUF_SIZE];
+    int in_buf, i;
+    gboolean ret = FALSE;
+    guint cyc = 0;
+
+    if ((file = vfs_fopen(filename, "rb")) == NULL)
+        return FALSE;
+    if (vfs_fread(tmp, 1, 4, file) != 4)
+        goto done;
+    if (!memcmp(tmp, "ID3", 3))
+    {
+	ret = TRUE;
+	goto done;
+    }
+    head = convert_to_header(tmp);
+    while (!mpgdec_head_check(head)) {
+        /*
+         * The mpeg-stream can start anywhere in the file,
+         * so we check the entire file
+	 *
+	 * Incorrect! We give up past twenty iterations of this
+	 * code for safety's sake. Buffer overflows suck. --nenolod
+         */
+        /* Optimize this */
+        in_buf = vfs_fread(buf, 1, DET_BUF_SIZE, file);
+        if (in_buf == 0)
+            goto done;
+
+        for (i = 0; i < in_buf; i++) {
+            head <<= 8;
+            head |= buf[i];
+            if (mpgdec_head_check(head)) {
+                vfs_fseek(file, i + 1 - in_buf, SEEK_CUR);
+                break;
+            }
+        }
+
+        if (++cyc > 1024)
+	    goto done;
+    }
+    if (mpgdec_decode_header(&fr, head)) {
+        /*
+         * We found something which looks like a MPEG-header.
+         * We check the next frame too, to be sure
+         */
+
+        if (vfs_fseek(file, fr.framesize, SEEK_CUR) != 0)
+            goto done;
+        if (vfs_fread(tmp, 1, 4, file) != 4)
+            goto done;
+        head = convert_to_header(tmp);
+        if (mpgdec_head_check(head) && mpgdec_decode_header(&fr, head))
+            ret = TRUE;
+    }
+
+  done:
+    vfs_fclose(file);
+    return ret;
+}
+
+static int
+is_our_file(char *filename)
+{
+    gchar *ext = strrchr(filename, '.');
+
+    if (CHECK_STREAM(filename) &&
+        (ext && strncasecmp(ext, ".ogg", 4)) &&
+        (ext && strncasecmp(ext, ".flac", 5)))
+	return TRUE;
+    else if (mpgdec_detect_by_content(filename))
+        return TRUE;
+#if 0
+    else if (ext && (!strncasecmp(ext, ".mp3", 4)
+	|| !strncasecmp(ext, ".mp2", 4)
+	|| !strncasecmp(ext, ".mpg", 4)))
+        return TRUE;
+#endif
+
+    return FALSE;
+}
+
+static void
+play_frame(struct frame *fr)
+{
+    if (fr->error_protection) {
+        bsi.wordpointer += 2;
+        /*  mpgdec_getbits(16); *//* skip crc */
+    }
+    if (!fr->do_layer(fr)) {
+        skip_frames = 2;
+        mpgdec_info->output_audio = FALSE;
+    }
+    else {
+        if (!skip_frames)
+            mpgdec_info->output_audio = TRUE;
+        else
+            skip_frames--;
+    }
+}
+
+static const char *
+get_id3_genre(unsigned char genre_code)
+{
+    if (genre_code < GENRE_MAX)
+        return gettext(mpgdec_id3_genres[genre_code]);
+
+    return "";
+}
+
+guint
+mpgdec_strip_spaces(char *src, size_t n)
+{
+    gchar *space = NULL,        /* last space in src */
+        *start = src;
+
+    while (n--)
+        switch (*src++) {
+        case '\0':
+            n = 0;              /* breaks out of while loop */
+
+            src--;
+            break;
+        case ' ':
+            if (space == NULL)
+                space = src - 1;
+            break;
+        default:
+            space = NULL;       /* don't terminate intermediate spaces */
+
+            break;
+        }
+    if (space != NULL) {
+        src = space;
+        *src = '\0';
+    }
+    return src - start;
+}
+
+/*
+ * Function extname (filename)
+ *
+ *    Return pointer within filename to its extenstion, or NULL if
+ *    filename has no extension.
+ *
+ */
+static gchar *
+extname(const char *filename)
+{
+    gchar *ext = strrchr(filename, '.');
+
+    if (ext != NULL)
+        ++ext;
+
+    return ext;
+}
+
+/*
+ * Function id3v1_to_id3v2 (v1, v2)
+ *
+ *    Convert ID3v1 tag `v1' to ID3v2 tag `v2'.
+ *
+ */
+void
+mpgdec_id3v1_to_id3v2(struct id3v1tag_t *v1, struct id3tag_t *v2)
+{
+    memset(v2, 0, sizeof(struct id3tag_t));
+    strncpy(v2->title, v1->title, 30);
+    strncpy(v2->artist, v1->artist, 30);
+    strncpy(v2->album, v1->album, 30);
+    strncpy(v2->comment, v1->u.v1_0.comment, 30);
+    strncpy(v2->genre, get_id3_genre(v1->genre), sizeof(v2->genre));
+    g_strstrip(v2->title);
+    g_strstrip(v2->artist);
+    g_strstrip(v2->album);
+    g_strstrip(v2->comment);
+    g_strstrip(v2->genre);
+    {
+      char y[5];
+      memcpy(y, v1->year, 4); y[4]=0;
+      v2->year = atoi(y);
+    }
+
+    /* Check for v1.1 tags. */
+    if (v1->u.v1_1.__zero == 0)
+        v2->track_number = v1->u.v1_1.track_number;
+    else
+        v2->track_number = 0;
+}
+
+#define REMOVE_NONEXISTANT_TAG(x)   if (!*x) { x = NULL; }
+
+static long
+get_song_length(VFSFile * file)
+{
+    int len;
+    char tmp[4];
+
+    vfs_fseek(file, 0, SEEK_END);
+    len = vfs_ftell(file);
+    vfs_fseek(file, -128, SEEK_END);
+    vfs_fread(tmp, 1, 3, file);
+    if (!strncmp(tmp, "TAG", 3))
+        len -= 128;
+    return len;
+}
+
+
+static guint
+get_song_time(VFSFile * file)
+{
+    guint32 head;
+    guchar tmp[4], *buf;
+    struct frame frm;
+    xing_header_t xing_header;
+    double tpf, bpf;
+    guint32 len;
+
+    if (!file)
+        return -1;
+
+    vfs_fseek(file, 0, SEEK_SET);
+    if (vfs_fread(tmp, 1, 4, file) != 4)
+        return 0;
+    head = convert_to_header(tmp);
+    while (!mpgdec_head_check(head)) {
+        head <<= 8;
+        if (vfs_fread(tmp, 1, 1, file) != 1)
+            return 0;
+        head |= tmp[0];
+    }
+    if (mpgdec_decode_header(&frm, head)) {
+        buf = g_malloc(frm.framesize + 4);
+        vfs_fseek(file, -4, SEEK_CUR);
+        vfs_fread(buf, 1, frm.framesize + 4, file);
+        tpf = mpgdec_compute_tpf(&frm);
+        if (mpgdec_get_xing_header(&xing_header, buf)) {
+            g_free(buf);
+            if (xing_header.bytes == 0)
+                xing_header.bytes = get_song_length(file);
+            return (tpf * xing_header.frames * 1000);
+        }
+        g_free(buf);
+        bpf = mpgdec_compute_bpf(&frm);
+        len = get_song_length(file);
+        return ((guint) (len / bpf) * tpf * 1000);
+    }
+    return 0;
+}
+
+static TitleInput *
+get_song_tuple(char *filename)
+{
+    VFSFile *file;
+    TitleInput *tuple = NULL;
+    TagLib_File *taglib_file;
+    TagLib_Tag *taglib_tag;
+
+#ifdef USE_CHARDET
+    taglib_set_strings_unicode(FALSE);
+#endif
+
+    if ((file = vfs_fopen(filename, "rb")) != NULL)
+    {
+        tuple = bmp_title_input_new();
+
+        taglib_file = taglib_file_new(filename);
+        taglib_tag = NULL;
+
+        if (taglib_file != NULL)
+        {
+            taglib_tag = taglib_file_tag(taglib_file);
+        }
+
+	if (taglib_tag != NULL)
+	{
+	    tuple->performer = g_strdup(taglib_tag_artist(taglib_tag));
+            tuple->album_name = g_strdup(taglib_tag_album(taglib_tag));
+            tuple->track_name = g_strdup(taglib_tag_title(taglib_tag));
+
+            mpgdec_strip_spaces(tuple->performer, strlen(tuple->performer));
+            mpgdec_strip_spaces(tuple->album_name, strlen(tuple->album_name));
+            mpgdec_strip_spaces(tuple->track_name, strlen(tuple->track_name));
+
+            tuple->year = taglib_tag_year(taglib_tag);
+            tuple->track_number = taglib_tag_track(taglib_tag);
+            tuple->genre = g_strdup(taglib_tag_genre(taglib_tag));
+            tuple->comment = g_strdup(taglib_tag_comment(taglib_tag));
+
+            /* remove any blank tags, fucking taglib */
+            REMOVE_NONEXISTANT_TAG(tuple->performer);
+            REMOVE_NONEXISTANT_TAG(tuple->album_name);
+            REMOVE_NONEXISTANT_TAG(tuple->track_name);
+            REMOVE_NONEXISTANT_TAG(tuple->genre);
+            REMOVE_NONEXISTANT_TAG(tuple->comment);
+        }
+
+	if (tuple->performer != NULL)
+	    tuple->performer = str_to_utf8(tuple->performer);
+
+	if (tuple->album_name != NULL)
+	    tuple->album_name = str_to_utf8(tuple->album_name);
+
+	if (tuple->track_name != NULL)
+	    tuple->track_name = str_to_utf8(tuple->track_name);
+
+	if (tuple->comment != NULL)
+	    tuple->comment = str_to_utf8(tuple->comment);
+
+	tuple->file_name = g_path_get_basename(filename);
+	tuple->file_path = g_path_get_dirname(filename);
+	tuple->file_ext = extname(filename);
+        tuple->length = get_song_time(file);
+
+	if (taglib_file != NULL)
+	        taglib_file_free(taglib_file);
+
+        taglib_tag_free_strings();
+        vfs_fclose(file);
+    }
+
+    return tuple;
+}
+
+static gchar *
+get_song_title(TitleInput *tuple)
+{
+    return xmms_get_titlestring(mpgdec_cfg.title_override ?
+                                mpgdec_cfg.id3_format :
+                                xmms_get_gentitle_format(), tuple);
+}
+
+static void
+get_song_info(char *filename, char **title_real, int *len_real)
+{
+    TitleInput *tuple;
+
+    (*len_real) = -1;
+    (*title_real) = NULL;
+
+    /*
+     * TODO: Getting song info from http streams.
+     */
+    if (CHECK_STREAM(filename))
+        return;
+
+    if ((tuple = get_song_tuple(filename)) != NULL) {
+        (*len_real) = tuple->length;
+        (*title_real) = get_song_title(tuple);
+    }
+
+    bmp_title_input_free(tuple);
+}
+
+static int
+open_output(void)
+{
+    int r;
+    AFormat fmt = mpgdec_cfg.resolution == 16 ? FMT_S16_NE : FMT_U8;
+/*    int freq = mpgdec_freqs[fr.sampling_frequency] >> mpgdec_cfg.downsample; */
+    int freq = mpgdec_frequency;
+    int channels = mpgdec_cfg.channels == 2 ? fr.stereo : 1;
+    r = mpgdec_ip.output->open_audio(fmt, freq, channels);
+
+    if (r && dopause) {
+        mpgdec_ip.output->pause(TRUE);
+        dopause = FALSE;
+    }
+
+    return r;
+}
+
+
+static int
+mpgdec_seek(struct frame *fr, xing_header_t * xh, gboolean vbr, int time)
+{
+    int jumped = -1;
+
+    if (xh) {
+        int percent = ((double) time * 100.0) /
+            (mpgdec_info->num_frames * mpgdec_info->tpf);
+        int byte = mpgdec_seek_point(xh, percent);
+        jumped = mpgdec_stream_jump_to_byte(fr, byte);
+    }
+    else if (vbr && mpgdec_length > 0) {
+        int byte = ((guint64) time * 1000 * mpgdec_info->filesize) /
+            mpgdec_length;
+        jumped = mpgdec_stream_jump_to_byte(fr, byte);
+    }
+    else {
+        int frame = time / mpgdec_info->tpf;
+        jumped = mpgdec_stream_jump_to_frame(fr, frame);
+    }
+
+    return jumped;
+}
+
+
+static void *
+decode_loop(void *arg)
+{
+    gboolean have_xing_header = FALSE, vbr = FALSE;
+    int disp_count = 0;
+    char *filename = arg;
+    xing_header_t xing_header;
+
+    /* This is used by fileinfo on http streams */
+    mpgdec_bitrate = 0;
+
+    mpgdec_pcm_sample = g_malloc0(32768);
+    mpgdec_pcm_point = 0;
+    mpgdec_filename = filename;
+
+    mpgdec_read_frame_init();
+
+    mpgdec_open_stream(filename, -1);
+
+    if (mpgdec_info->eof || !mpgdec_read_frame(&fr))
+        mpgdec_info->eof = TRUE;
+
+    if (!mpgdec_info->eof && mpgdec_info->going) {
+        if (mpgdec_cfg.channels == 2)
+            fr.single = -1;
+        else
+            fr.single = 3;
+
+        fr.down_sample = mpgdec_cfg.downsample;
+        fr.down_sample_sblimit = SBLIMIT >> mpgdec_cfg.downsample;
+        set_synth_functions(&fr);
+
+        mpgdec_info->tpf = mpgdec_compute_tpf(&fr);
+        if (!CHECK_STREAM(filename)) {
+            if (mpgdec_stream_check_for_xing_header(&fr, &xing_header)) {
+                mpgdec_info->num_frames = xing_header.frames;
+                have_xing_header = TRUE;
+                mpgdec_read_frame(&fr);
+            }
+        }
+
+        for (;;) {
+            memcpy(&temp_fr, &fr, sizeof(struct frame));
+            if (!mpgdec_read_frame(&temp_fr)) {
+                mpgdec_info->eof = TRUE;
+                break;
+            }
+            if (fr.lay != temp_fr.lay ||
+                fr.sampling_frequency != temp_fr.sampling_frequency ||
+                fr.stereo != temp_fr.stereo || fr.lsf != temp_fr.lsf)
+                memcpy(&fr, &temp_fr, sizeof(struct frame));
+            else
+                break;
+        }
+
+        if (!have_xing_header && !CHECK_STREAM(filename))
+            mpgdec_info->num_frames = mpgdec_calc_numframes(&fr);
+
+        memcpy(&fr, &temp_fr, sizeof(struct frame));
+        mpgdec_bitrate = tabsel_123[fr.lsf][fr.lay - 1][fr.bitrate_index];
+        disp_bitrate = mpgdec_bitrate;
+        mpgdec_frequency = mpgdec_freqs[fr.sampling_frequency];
+        mpgdec_stereo = fr.stereo;
+        mpgdec_layer = fr.lay;
+        mpgdec_lsf = fr.lsf;
+        mpgdec_mpeg25 = fr.mpeg25;
+        mpgdec_mode = fr.mode;
+
+	/* XXX: note that this is temporary, until custom resampling is implemented
+	 * in prefs.
+	 */
+	if (fr.down_sample)
+	{
+	    long n = mpgdec_freqs[fr.sampling_frequency];
+	    long m = n / (fr.down_sample * 2);
+
+	    mpgdec_synth_ntom_set_step(n, m);
+
+	    mpgdec_frequency = (gint) m;
+	}
+
+        if (strncasecmp(filename, "http://", 7)) {
+	    TitleInput *tuple = NULL;
+            mpgdec_length = mpgdec_info->num_frames * mpgdec_info->tpf * 1000;
+            if (!mpgdec_title)
+	    {
+	        tuple = get_song_tuple(filename);
+                mpgdec_title = get_song_title(tuple);
+		bmp_title_input_free(tuple);
+	    }
+        }
+        else {
+            if (!mpgdec_title)
+                mpgdec_title = mpgdec_http_get_title(filename);
+            mpgdec_length = -1;
+        }
+
+        set_synth_functions(&fr);
+        mpgdec_init_layer3(fr.down_sample_sblimit);
+
+        mpgdec_ip.set_info(mpgdec_title, mpgdec_length,
+                           mpgdec_bitrate * 1000,
+                           mpgdec_freqs[fr.sampling_frequency], fr.stereo);
+
+        output_opened = TRUE;
+
+        if (!open_output()) {
+            audio_error = TRUE;
+            mpgdec_info->eof = TRUE;
+        }
+        else
+            play_frame(&fr);
+    }
+
+    mpgdec_info->first_frame = FALSE;
+    while (mpgdec_info->going) {
+        if (mpgdec_info->jump_to_time != -1) {
+            void *xp = NULL;
+            if (have_xing_header)
+                xp = &xing_header;
+            if (mpgdec_seek(&fr, xp, vbr, mpgdec_info->jump_to_time) > -1) {
+                mpgdec_ip.output->flush(mpgdec_info->jump_to_time * 1000);
+                mpgdec_info->eof = FALSE;
+            }
+            mpgdec_info->jump_to_time = -1;
+        }
+        if (!mpgdec_info->eof) {
+            if (mpgdec_read_frame(&fr) != 0) {
+                if (fr.lay != mpgdec_layer || fr.lsf != mpgdec_lsf) {
+                    memcpy(&temp_fr, &fr, sizeof(struct frame));
+                    if (mpgdec_read_frame(&temp_fr) != 0) {
+                        if (fr.lay == temp_fr.lay && fr.lsf == temp_fr.lsf) {
+                            mpgdec_layer = fr.lay;
+                            mpgdec_lsf = fr.lsf;
+                            memcpy(&fr, &temp_fr, sizeof(struct frame));
+                        }
+                        else {
+                            memcpy(&fr, &temp_fr, sizeof(struct frame));
+                            skip_frames = 2;
+                            mpgdec_info->output_audio = FALSE;
+                            continue;
+                        }
+
+                    }
+                }
+
+                if (tabsel_123[fr.lsf][fr.lay - 1][fr.bitrate_index] !=
+                    mpgdec_bitrate)
+                    mpgdec_bitrate =
+                        tabsel_123[fr.lsf][fr.lay - 1][fr.bitrate_index];
+
+                if (!disp_count) {
+                    disp_count = 20;
+                    if (mpgdec_bitrate != disp_bitrate) {
+                        /* FIXME networks streams */
+                        disp_bitrate = mpgdec_bitrate;
+                        if (!have_xing_header
+                            && !CHECK_STREAM(filename)) {
+                            double rel = mpgdec_relative_pos();
+                            if (rel) {
+                                mpgdec_length =
+                                    mpgdec_ip.output->written_time() / rel;
+                                vbr = TRUE;
+                            }
+
+                            if (rel == 0 || !(mpgdec_length > 0)) {
+                                mpgdec_info->num_frames =
+                                    mpgdec_calc_numframes(&fr);
+                                mpgdec_info->tpf = mpgdec_compute_tpf(&fr);
+                                mpgdec_length =
+                                    mpgdec_info->num_frames *
+                                    mpgdec_info->tpf * 1000;
+                            }
+
+
+                        }
+                        mpgdec_ip.set_info(mpgdec_title, mpgdec_length,
+                                           mpgdec_bitrate * 1000,
+                                           mpgdec_frequency, mpgdec_stereo);
+                    }
+                }
+                else
+                    disp_count--;
+                play_frame(&fr);
+            }
+            else {
+                mpgdec_ip.output->buffer_free();
+                mpgdec_ip.output->buffer_free();
+                mpgdec_info->eof = TRUE;
+                g_usleep(10000);
+            }
+        }
+        else {
+            g_usleep(10000);
+        }
+    }
+    g_free(mpgdec_title);
+    mpgdec_title = NULL;
+    mpgdec_stream_close();
+    if (output_opened && !audio_error)
+        mpgdec_ip.output->close_audio();
+    g_free(mpgdec_pcm_sample);
+    mpgdec_filename = NULL;
+    g_free(filename);
+
+    return NULL;
+}
+
+static void
+play_file(char *filename)
+{
+    memset(&fr, 0, sizeof(struct frame));
+    memset(&temp_fr, 0, sizeof(struct frame));
+
+    mpgdec_info = g_malloc0(sizeof(PlayerInfo));
+    mpgdec_info->going = 1;
+    mpgdec_info->first_frame = TRUE;
+    mpgdec_info->output_audio = TRUE;
+    mpgdec_info->jump_to_time = -1;
+    skip_frames = 0;
+    audio_error = FALSE;
+    output_opened = FALSE;
+    dopause = FALSE;
+
+    decode_thread = g_thread_create(decode_loop, g_strdup(filename), TRUE,
+                                    NULL);
+}
+
+static void
+stop(void)
+{
+    if (mpgdec_info && mpgdec_info->going) {
+        mpgdec_info->going = FALSE;
+        g_thread_join(decode_thread);
+        g_free(mpgdec_info);
+        mpgdec_info = NULL;
+    }
+}
+
+static void
+seek(int time)
+{
+    mpgdec_info->jump_to_time = time;
+
+    while (mpgdec_info->jump_to_time != -1)
+        g_usleep(10000);
+}
+
+static void
+do_pause(short p)
+{
+    if (output_opened)
+        mpgdec_ip.output->pause(p);
+    else
+        dopause = p;
+}
+
+static int
+get_time(void)
+{
+    if (audio_error)
+        return -2;
+    if (!mpgdec_info)
+        return -1;
+    if (!mpgdec_info->going
+        || (mpgdec_info->eof && !mpgdec_ip.output->buffer_playing()))
+        return -1;
+    return mpgdec_ip.output->output_time();
+}
+
+static void
+aboutbox(void)
+{
+    static GtkWidget *aboutbox;
+
+    if (aboutbox != NULL)
+        return;
+
+    aboutbox = xmms_show_message(_("About MPEG Audio Plugin"),
+				 _("Audacious decoding engine by William Pitcock <nenolod@nenolod.net>, derived from:\n"
+                                   "mpg123 decoding engine by Michael Hipp <mh@mpg123.de>\n"
+				   "Derived partially from mpg123 0.59s.mc3 as well.\n"
+                                   "Based on the original XMMS plugin."),
+                                  _("Ok"),
+                                  FALSE, NULL, NULL);
+
+    g_signal_connect(G_OBJECT(aboutbox), "destroy",
+                     G_CALLBACK(gtk_widget_destroyed), &aboutbox);
+}
+
+InputPlugin mpgdec_ip = {
+    NULL,
+    NULL,
+    NULL,                       /* Description */
+    init,
+    aboutbox,
+    mpgdec_configure,
+    is_our_file,
+    NULL,
+    play_file,
+    stop,
+    do_pause,
+    seek,
+    NULL,
+    get_time,
+    NULL, NULL,
+    cleanup,
+    NULL,
+    NULL, NULL, NULL,
+    get_song_info,
+    mpgdec_file_info_box,       /* file_info_box */
+    NULL,
+    get_song_tuple
+};
+
+InputPlugin *
+get_iplugin_info(void)
+{
+    mpgdec_ip.description = g_strdup_printf(_("MPEG Audio Plugin"));
+    return &mpgdec_ip;
+}