diff src/madplug/plugin.c @ 610:862190d39e00 trunk

[svn] - add madplug. It is not yet hooked up, I'll do that later.
author nenolod
date Mon, 05 Feb 2007 12:28:01 -0800
parents
children 3f7a52adfe0e
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/madplug/plugin.c	Mon Feb 05 12:28:01 2007 -0800
@@ -0,0 +1,568 @@
+/*
+ * mad plugin for audacious
+ * Copyright (C) 2005-2007 William Pitcock, Yoshiki Yazawa
+ *
+ * Portions derived from xmms-mad:
+ * Copyright (C) 2001-2002 Sam Clegg - See COPYING
+ *
+ * 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; under version 2 of the License.
+ *
+ * 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
+ */
+
+#include "config.h"
+#include "plugin.h"
+#include "input.h"
+
+#include <math.h>
+
+#include <gtk/gtk.h>
+#include <audacious/util.h>
+#include <audacious/configdb.h>
+#include <stdarg.h>
+#include <fcntl.h>
+#include <audacious/vfs.h>
+#include <sys/stat.h>
+
+/*
+ * Global variables
+ */
+struct audmad_config_t audmad_config;   /**< global configuration */
+static GStaticMutex mutex;
+InputPlugin *mad_plugin = NULL;
+
+/*
+ * static variables
+ */
+static GThread *decode_thread; /**< the single decoder thread */
+static struct mad_info_t info;   /**< info for current track */
+
+#ifndef NOGUI
+static GtkWidget *error_dialog = 0;
+#endif
+
+extern gboolean scan_file(struct mad_info_t *info, gboolean fast);
+
+/*
+ * 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;
+}
+
+void audmad_config_compute(struct audmad_config_t *config)
+{
+    /* set some config parameters by parsing text fields 
+       (RG default gain, etc..)
+     */
+    const gchar *text;
+    gdouble x;
+
+    text = config->pregain_db;
+    x = g_strtod(text, NULL);
+    config->pregain_scale = (x != 0) ? pow(10.0, x / 20) : 1;
+#ifdef DEBUG
+    g_message("pregain=[%s] -> %g  -> %g", text, x, config->pregain_scale);
+#endif
+    text = config->replaygain.default_db;
+    x = g_strtod(text, NULL);
+    config->replaygain.default_scale = (x != 0) ? pow(10.0, x / 20) : 1;
+#ifdef DEBUG
+    g_message("RG.default=[%s] -> %g  -> %g", text, x,
+              config->replaygain.default_scale);
+#endif
+}
+
+static void audmad_init()
+{
+    ConfigDb *db = NULL;
+
+    audmad_config.fast_play_time_calc = TRUE;
+    audmad_config.use_xing = TRUE;
+    audmad_config.dither = TRUE;
+    audmad_config.pregain_db = "+0.00";
+    audmad_config.replaygain.enable = TRUE;
+    audmad_config.replaygain.track_mode = FALSE;
+    audmad_config.hard_limit = FALSE;
+    audmad_config.replaygain.default_db = "-9.00";
+
+    db = bmp_cfg_db_open();
+    if (db) {
+        bmp_cfg_db_get_bool(db, "MAD", "fast_play_time_calc",
+                            &audmad_config.fast_play_time_calc);
+        bmp_cfg_db_get_bool(db, "MAD", "use_xing",
+                            &audmad_config.use_xing);
+        bmp_cfg_db_get_bool(db, "MAD", "dither", &audmad_config.dither);
+        bmp_cfg_db_get_bool(db, "MAD", "hard_limit",
+                            &audmad_config.hard_limit);
+        bmp_cfg_db_get_string(db, "MAD", "pregain_db",
+                              &audmad_config.pregain_db);
+        bmp_cfg_db_get_bool(db, "MAD", "RG.enable",
+                            &audmad_config.replaygain.enable);
+        bmp_cfg_db_get_bool(db, "MAD", "RG.track_mode",
+                            &audmad_config.replaygain.track_mode);
+        bmp_cfg_db_get_string(db, "MAD", "RG.default_db",
+                              &audmad_config.replaygain.default_db);
+        bmp_cfg_db_get_bool(db, "MAD", "title_override",
+                            &audmad_config.title_override);
+        bmp_cfg_db_get_string(db, "MAD", "id3_format",
+                              &audmad_config.id3_format);
+
+        bmp_cfg_db_close(db);
+    }
+
+    g_static_mutex_init(&mutex);
+    audmad_config_compute(&audmad_config);
+
+}
+
+static void audmad_cleanup()
+{
+}
+
+static gboolean mp3_head_check(guint32 head)
+{
+    /*
+     * First two bytes must be a sync header (11 bits all 1)
+     * http://www.mp3-tech.org/programmer/frame_header.html
+     */
+    if ((head & 0xffe00000) != 0xffe00000)
+        return FALSE;
+
+    /* check if bits 18 and 19 are set */
+    if (!((head >> 17) & 3))
+        return FALSE;
+
+    /* check if bits 13 - 16 are all set */
+    if (((head >> 12) & 0xf) == 0xf)
+        return FALSE;
+
+    /* check if bits 13 - 16 are all not set */
+    if (!((head >> 12) & 0xf))
+        return FALSE;
+
+    /* check if bit 11 and 12 are both set */
+    if (((head >> 10) & 0x3) == 0x3)
+        return FALSE;
+
+    /* check if bits 17 - 20 are all set */
+    if (((head >> 19) & 1) == 1 &&
+        ((head >> 17) & 3) == 3 && ((head >> 16) & 1) == 1)
+        return FALSE;
+
+    /* not sure why we check this, but ok! */
+    if ((head & 0xffff0000) == 0xfffe0000)
+        return FALSE;
+
+    return TRUE;
+}
+
+static int mp3_head_convert(const guchar * hbuf)
+{
+    return ((unsigned long) hbuf[0] << 24) |
+        ((unsigned long) hbuf[1] << 16) |
+        ((unsigned long) hbuf[2] << 8) | (unsigned long) hbuf[3];
+}
+
+// audacious vfs fast version
+static int audmad_is_our_fd(char *filename, VFSFile *fin)
+{
+    guint32 check;
+    gchar *ext = extname(filename);
+    gint cyc = 0;
+    guchar buf[4];
+    guchar tmp[4096];
+    gint ret, i;
+
+    /* I've seen some flac files beginning with id3 frames..
+       so let's exclude known non-mp3 filename extensions */
+    if (!strcasecmp(".flac", ext) || !strcasecmp(".mpc", ext) ||
+        !strcasecmp(".tta", ext))
+        return 0;
+
+    if (fin == NULL)
+        return FALSE;
+
+    vfs_fread(buf, 1, 4, fin);
+
+    check = mp3_head_convert(buf);
+
+    if (memcmp(buf, "ID3", 3) == 0)
+        return 1;
+    else if (memcmp(buf, "RIFF", 4) == 0)
+    {
+        vfs_fseek(fin, 4, SEEK_CUR);
+        vfs_fread(buf, 1, 4, fin);
+
+        if (memcmp(buf, "RMP3", 4) == 0)
+            return 1;
+    }
+
+    while (!mp3_head_check(check))
+    {
+        ret = vfs_fread(tmp, 1, 4096, fin);
+	if (ret == 0)
+            return 0;
+
+        for (i = 0; i < ret; i++)
+        {
+            check <<= 8;
+            check |= tmp[i];
+
+            if (mp3_head_check(check))
+                return 1;
+        }
+
+        if (++cyc > 1024)
+            return 0;
+    }
+
+    return 1;
+}
+
+// audacious vfs version
+static int audmad_is_our_file(char *filename)
+{
+    VFSFile *fin = NULL;
+    gint rtn;
+
+    fin = vfs_fopen(filename, "rb");
+
+    if (fin == NULL)
+        return 0;
+
+    rtn = audmad_is_our_fd(filename, fin);
+    vfs_fclose(fin);
+
+    return rtn;
+}
+
+static void audmad_stop(InputPlayback *playback)
+{
+#ifdef DEBUG
+    g_message("f: audmad_stop");
+#endif                          /* DEBUG */
+    g_static_mutex_lock(&mutex);
+    if (decode_thread) {
+        info.playback->playing = 0;
+#ifdef DEBUG
+        g_message("waiting for thread");
+#endif                          /* DEBUG */
+        g_thread_join(decode_thread);
+#ifdef DEBUG
+        g_message("thread done");
+#endif                          /* DEBUG */
+        input_term(&info);
+        decode_thread = NULL;
+    }
+    g_static_mutex_unlock(&mutex);
+}
+
+
+static void audmad_play_file(InputPlayback *playback)
+{
+    gboolean rtn;
+    gchar *url = playback->filename;
+
+#ifdef DEBUG
+    g_message("playing %s", url);
+#endif                          /* DEBUG */
+
+    if (input_init(&info, url) == FALSE) {
+        g_message("error initialising input");
+        return;
+    }
+
+    rtn = input_get_info(&info, audmad_config.fast_play_time_calc);
+
+    if (rtn == FALSE) {
+        g_message("error reading input info");
+        return;
+    }
+
+    info.playback = playback;
+    info.playback->playing = 1;
+
+    decode_thread = g_thread_create(decode_loop, (void *) &info, TRUE, NULL);
+}
+
+static void audmad_pause(InputPlayback *playback, short paused)
+{
+    mad_plugin->output->pause(paused);
+}
+
+static void audmad_seek(InputPlayback *playback, int time)
+{
+    /* xmms gives us the desired seek time in seconds */
+    info.seek = time;
+}
+
+/**
+ * Scan the given file or URL.
+ * Fills in the title string and the track length in milliseconds.
+ */
+void audmad_get_song_info(char *url, char **title, int *length)
+{
+    struct mad_info_t myinfo;
+#ifdef DEBUG
+    g_message("f: audmad_get_song_info: %s", url);
+#endif                          /* DEBUG */
+
+    input_init(&myinfo, url);
+
+    if (input_get_info(&myinfo, audmad_config.fast_play_time_calc) == TRUE)
+    {
+        *title = strdup(myinfo.title);
+        *length = mad_timer_count(myinfo.duration, MAD_UNITS_MILLISECONDS);
+    }
+    else
+    {
+        *title = strdup(url);
+        *length = -1;
+    }
+
+    input_term(&myinfo);
+
+#ifdef DEBUG
+    g_message("e: audmad_get_song_info");
+#endif                          /* DEBUG */
+}
+
+static void audmad_about()
+{
+    static GtkWidget *aboutbox;
+    gchar *scratch;
+
+    if (aboutbox != NULL)
+        return;
+
+    scratch = g_strdup_printf(
+	"Audacious MPEG Audio Plugin\n"
+	"\n"
+	"Compiled against libMAD version: %d.%d.%d%s\n"
+	"\n"
+	"Written by:\n"
+	"    William Pitcock <nenolod@sacredspiral.co.uk>\n"
+	"    Yoshiki Yazawa <yaz@cc.rim.or.jp>\n"
+	"\n"
+	"Portions derived from XMMS-MAD by:\n"
+	"    Sam Clegg\n"
+	"\n"
+	"ReplayGain support by:\n"
+	"    Samuel Krempp",
+	MAD_VERSION_MAJOR, MAD_VERSION_MINOR, MAD_VERSION_PATCH,
+	MAD_VERSION_EXTRA);
+
+    aboutbox = xmms_show_message("About MPEG Audio Plugin",
+                                 scratch,
+                                 "Ok", FALSE, NULL, NULL);
+
+    g_free(scratch);
+
+    g_signal_connect(G_OBJECT(aboutbox), "destroy",
+                     G_CALLBACK(gtk_widget_destroyed), &aboutbox);
+}
+
+/**
+ * Display a GTK box containing the given error message.
+ * Taken from mpg123 plugin.
+ */
+void audmad_error(char *error, ...)
+{
+#ifndef NOGUI
+    if (!error_dialog) {
+        va_list args;
+        char string[256];
+        va_start(args, error);
+        vsnprintf(string, 256, error, args);
+        va_end(args);
+        GDK_THREADS_ENTER();
+        error_dialog =
+            xmms_show_message("Error", string, "Ok", FALSE, 0, 0);
+        gtk_signal_connect(GTK_OBJECT(error_dialog), "destroy",
+                           GTK_SIGNAL_FUNC(gtk_widget_destroyed),
+                           &error_dialog);
+        GDK_THREADS_LEAVE();
+    }
+#endif                          /* !NOGUI */
+}
+
+extern void audmad_get_file_info(char *filename);
+extern void audmad_configure();
+
+// tuple stuff
+static TitleInput *audmad_get_song_tuple(char *filename)
+{
+    TitleInput *tuple = NULL;
+    gchar *string = NULL;
+    VFSFile *file;
+
+    struct id3_file *id3file = NULL;
+    struct id3_tag *tag = NULL;
+
+#ifdef DEBUG
+    string = str_to_utf8(filename);
+    g_message("f: mad: audmad_get_song_tuple: %s", string);
+    if (string) {
+        g_free(string);
+        string = NULL;
+    }
+#endif
+
+    if ((file = vfs_fopen(filename, "rb")) != NULL) {
+        tuple = bmp_title_input_new();
+
+        id3file = id3_file_open(filename, ID3_FILE_MODE_READONLY);
+
+        if (id3file) {
+            tag = id3_file_tag(id3file);
+
+            if (tag) {
+                tuple->performer =
+                    input_id3_get_string(tag, ID3_FRAME_ARTIST);
+                tuple->album_name =
+                    input_id3_get_string(tag, ID3_FRAME_ALBUM);
+                tuple->track_name =
+                    input_id3_get_string(tag, ID3_FRAME_TITLE);
+
+                // year
+                string = NULL;
+                string = input_id3_get_string(tag, ID3_FRAME_YEAR); //TDRC
+                if (!string)
+                    string = input_id3_get_string(tag, "TYER");
+
+                if (string) {
+                    tuple->year = atoi(string);
+                    g_free(string);
+                    string = NULL;
+                }
+
+                tuple->file_name = g_path_get_basename(filename);
+                tuple->file_path = g_path_get_dirname(filename);
+                tuple->file_ext = extname(filename);
+
+                // length
+                {
+                    char *dummy = NULL;
+                    int length = 0;
+                    audmad_get_song_info(filename, &dummy, &length);
+                    tuple->length = length;
+		    g_free(dummy);
+                }
+
+                // track number
+                string = input_id3_get_string(tag, ID3_FRAME_TRACK);
+                if (string) {
+                    tuple->track_number = atoi(string);
+                    g_free(string);
+                    string = NULL;
+                }
+                // genre
+                tuple->genre = input_id3_get_string(tag, ID3_FRAME_GENRE);
+#ifdef DEBUG
+                g_message("genre = %s\n", tuple->genre);
+#endif
+                // comment
+                tuple->comment =
+                    input_id3_get_string(tag, ID3_FRAME_COMMENT);
+
+                // mtime
+//                        tuple->mtime = audmad_get_mtime(filename);
+
+            }
+            id3_file_close(id3file);
+        }
+        vfs_fclose(file);
+    }
+#ifdef DEBUG
+    g_message("e: mad: audmad_get_song_tuple");
+#endif
+    tuple->formatter = NULL;    //ensure
+    return tuple;
+
+}
+
+/**
+ * Retrieve meta-information about URL.
+ * For local files this means ID3 tag etc.
+ */
+gboolean mad_get_info(struct mad_info_t * info, gboolean fast_scan)
+{
+    TitleInput *tuple = NULL;
+
+    g_message("f: mad_get_info: %s", info->filename);
+
+    if (info->remote)
+        return TRUE;
+
+    tuple = audmad_get_song_tuple(info->filename);
+    info->title = xmms_get_titlestring(audmad_config.title_override == TRUE ?
+	audmad_config.id3_format : xmms_get_gentitle_format(), tuple);
+
+    /* scan mp3 file, decoding headers unless fast_scan is set */
+    if (scan_file(info, fast_scan) == FALSE)
+        return FALSE;
+
+    /* reset the input file to the start */
+    vfs_rewind(info->infile);
+    info->offset = 0;
+
+    /* use the filename for the title as a last resort */
+    if (!info->title)
+    {
+        char *pos = strrchr(info->filename, '/');
+        if (pos)
+            info->title = g_strdup(pos + 1);
+        else
+            info->title = g_strdup(info->filename);
+    }
+
+    g_message("e: mad_get_info");
+    return TRUE;
+}
+
+static gchar *fmts[] = { "mp3", "mp2", "mpg", NULL };
+
+InputPlugin *get_iplugin_info(void)
+{
+    if (mad_plugin != NULL)
+        return mad_plugin;
+
+    mad_plugin = g_new0(InputPlugin, 1);
+    mad_plugin->description = g_strdup(_("MPEG Audio Plugin"));
+    mad_plugin->init = audmad_init;
+    mad_plugin->about = audmad_about;
+    mad_plugin->configure = audmad_configure;
+    mad_plugin->is_our_file = audmad_is_our_file;
+    mad_plugin->play_file = audmad_play_file;
+    mad_plugin->stop = audmad_stop;
+    mad_plugin->pause = audmad_pause;
+    mad_plugin->seek = audmad_seek;
+    mad_plugin->cleanup = audmad_cleanup;
+    mad_plugin->get_song_info = audmad_get_song_info;
+    mad_plugin->file_info_box = audmad_get_file_info;
+    mad_plugin->get_song_tuple = audmad_get_song_tuple;
+    mad_plugin->is_our_file_from_vfs = audmad_is_our_fd;
+    mad_plugin->vfs_extensions = fmts;
+
+    return mad_plugin;
+}