diff src/madplug/decoder.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/decoder.c	Mon Feb 05 12:28:01 2007 -0800
@@ -0,0 +1,567 @@
+/*
+ * 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 <math.h>
+#include <assert.h>
+#include <pthread.h>
+#include <signal.h>
+
+#include <audacious/plugin.h>
+#include <audacious/output.h>
+#include <audacious/util.h>
+#include "plugin.h"
+#include "input.h"
+
+#define BUFFER_SIZE 16*1024
+#define N_AVERAGE_FRAMES 10
+
+extern long triangular_dither_noise(int nbits);
+
+/**
+ * Scale PCM data
+ */
+static inline signed int
+scale(mad_fixed_t sample, struct mad_info_t *file_info)
+{
+    /* replayGain by SamKR */
+    gdouble scale = -1;
+    if (audmad_config.replaygain.enable) {
+        if (file_info->has_replaygain) {
+            scale = file_info->replaygain_track_scale;
+            if (file_info->replaygain_album_scale != -1
+                && (scale == -1 || !audmad_config.replaygain.track_mode))
+            {
+                scale = file_info->replaygain_album_scale;
+            }
+        }
+        if (scale == -1)
+            scale = audmad_config.replaygain.default_scale;
+    }
+    if (scale == -1)
+        scale = 1.0;
+    if (audmad_config.pregain_scale != 1)
+        scale = scale * audmad_config.pregain_scale;
+
+    /* hard-limit (clipping-prevention) */
+    if (audmad_config.hard_limit) {
+        /* convert to double before computation, to avoid mad_fixed_t wrapping */
+        double x = mad_f_todouble(sample) * scale;
+        static const double k = 0.5;    // -6dBFS
+        if (x > k) {
+            x = tanh((x - k) / (1 - k)) * (1 - k) + k;
+        }
+        else if (x < -k) {
+            x = tanh((x + k) / (1 - k)) * (1 - k) - k;
+        }
+        sample = x * (MAD_F_ONE);
+    }
+    else
+        sample *= scale;
+
+    int n_bits_to_loose = MAD_F_FRACBITS + 1 - 16;
+
+    /* round */
+    /* add half of the bits_to_loose range to round */
+    sample += (1L << (n_bits_to_loose - 1));
+
+#ifdef DEBUG_DITHER
+    mad_fixed_t no_dither = sample;
+#endif
+
+    /* dither one bit of actual output */
+    if (audmad_config.dither) {
+        int dither = triangular_dither_noise(n_bits_to_loose + 1);
+        sample += dither;
+    }
+
+    /* clip */
+    /* make sure we are between -1 and 1 */
+    if (sample >= MAD_F_ONE) {
+        sample = MAD_F_ONE - 1;
+    }
+    else if (sample < -MAD_F_ONE) {
+        sample = -MAD_F_ONE;
+    }
+
+    /* quantize */
+    /*
+     * Turn our mad_fixed_t into an integer.
+     * Shift all but 16-bits of the fractional part
+     * off the right hand side and shift an extra place
+     * to get the sign bit.
+     */
+    sample >>= n_bits_to_loose;
+#ifdef DEBUG_DITHER
+    static int n_zeros = 0;
+    no_dither >>= n_bits_to_loose;
+    if (no_dither - sample == 0)
+        n_zeros++;
+    else {
+        g_message("dither: %d %d", n_zeros, no_dither - sample);
+        n_zeros = 0;
+    }
+#endif
+    return sample;
+}
+
+void
+write_output(struct mad_info_t *info, struct mad_pcm *pcm,
+             struct mad_header *header)
+{
+    unsigned int nsamples;
+    mad_fixed_t const *left_ch, *right_ch;
+    char *output;
+    int olen = 0;
+    int pos = 0;
+
+    nsamples = pcm->length;
+    left_ch = pcm->samples[0];
+    right_ch = pcm->samples[1];
+    olen = nsamples * MAD_NCHANNELS(header) * 2;
+    output = (char *) g_malloc(olen * sizeof(char));
+
+    while (nsamples--) {
+        signed int sample;
+        /* output sample(s) in 16-bit signed little-endian PCM */
+        sample = scale(*left_ch++, info);
+        output[pos++] = (sample >> 0) & 0xff;
+        output[pos++] = (sample >> 8) & 0xff;
+
+        if (MAD_NCHANNELS(header) == 2) {
+            sample = scale(*right_ch++, info);
+            output[pos++] = (sample >> 0) & 0xff;
+            output[pos++] = (sample >> 8) & 0xff;
+        }
+    }
+    assert(pos == olen);
+    if (info->playback->playing == 0)
+        return;
+    produce_audio(mad_plugin->output->written_time(),
+                  FMT_S16_LE, MAD_NCHANNELS(header), olen, output, NULL);
+    if (info->playback->playing == 0)
+        return;
+    g_free(output);
+}
+
+/**
+ * Decode all headers in the file and fill in stats
+ * @return FALSE if scan failed.
+ */
+gboolean scan_file(struct mad_info_t * info, gboolean fast)
+{
+    struct mad_stream stream;
+    struct mad_header header;
+    int remainder = 0;
+    int data_used = 0;
+    int len = 0;
+    int tagsize = 0;
+    unsigned char buffer[BUFFER_SIZE];
+    struct mad_frame frame;     /* to read xing data */
+    gboolean has_xing = FALSE;
+
+    mad_stream_init(&stream);
+    mad_header_init(&header);
+    mad_frame_init(&frame);
+    xing_init(&info->xing);
+
+    info->bitrate = 0;
+    info->pos = mad_timer_zero;
+
+#ifdef DEBUG
+    g_message("f: scan_file");
+#endif                          /* DEBUG */
+
+    while (1) {
+        remainder = stream.bufend - stream.next_frame;
+
+        /*
+           if (remainder >= BUFFER_SIZE)
+           {
+           printf("oh dear.. remainder = %d\n", remainder);
+           }
+         */
+
+        memcpy(buffer, stream.this_frame, remainder);
+        len = input_get_data(info, buffer + remainder,
+                             BUFFER_SIZE - remainder);
+
+        if (len <= 0)
+            break;
+
+        mad_stream_buffer(&stream, buffer, len + remainder);
+
+        while (1) {
+            if (mad_header_decode(&header, &stream) == -1) {
+                if (stream.error == MAD_ERROR_BUFLEN) {
+                    break;
+                }
+                if (!MAD_RECOVERABLE(stream.error)) {
+#ifdef DEBUG
+                    g_message("(fatal) error decoding header %d: %s",
+                              info->frames, mad_stream_errorstr(&stream));
+                    g_message("remainder = %d", remainder);
+                    g_message("len = %d", len);
+#endif                          /* DEBUG */
+                    break;
+                }
+                if (stream.error == MAD_ERROR_LOSTSYNC) {
+                    /* ignore LOSTSYNC due to ID3 tags */
+                    tagsize = id3_tag_query(stream.this_frame,
+                                            stream.bufend -
+                                            stream.this_frame);
+                    if (tagsize > 0) {
+#ifdef DEBUG
+                        g_message("skipping id3_tag: %d", tagsize);
+#endif                          /* DEBUG */
+                        mad_stream_skip(&stream, tagsize);
+                        continue;
+                    }
+                }
+#ifdef DEBUG
+                g_message("(recovered) error decoding header %d: %s",
+                          info->frames, mad_stream_errorstr(&stream));
+                g_message("remainder = %d", remainder);
+                g_message("len = %d", len);
+#endif                          /* DEBUG */
+                continue;
+            }
+            info->frames++;
+#ifdef DEBUG
+            g_message("duration = %lu",
+                      mad_timer_count(header.duration,
+                                      MAD_UNITS_MILLISECONDS));
+            g_message("size = %d", stream.next_frame - stream.this_frame);
+#endif
+            mad_timer_add(&info->duration, header.duration);
+            data_used += stream.next_frame - stream.this_frame;
+            if (info->frames == 1) {
+                /* most of these *should* remain constant */
+                info->bitrate = header.bitrate;
+                info->freq = header.samplerate;
+                info->channels = MAD_NCHANNELS(&header);
+                info->mpeg_layer = header.layer;
+                info->mode = header.mode;
+
+                if (audmad_config.use_xing) {
+                    frame.header = header;
+                    if (mad_frame_decode(&frame, &stream) == -1)
+                        break;
+                    if (xing_parse
+                        (&info->xing, stream.anc_ptr,
+                         stream.anc_bitlen) == 0) {
+#ifdef DEBUG
+                        g_message("found xing header");
+#endif                          /* DEBUG */
+                        has_xing = TRUE;
+                        info->vbr = TRUE;   /* otherwise xing header would have been 'Info' */
+                        info->frames = info->xing.frames;
+                        mad_timer_multiply(&info->duration, info->frames);
+                        info->bitrate =
+                            8.0 * info->xing.bytes /
+                            mad_timer_count(info->duration,
+                                            MAD_UNITS_SECONDS);
+                        break;
+                    }
+                }
+
+            }
+            else {
+                /* perhaps we have a VRB file */
+                if (info->bitrate != header.bitrate)
+                    info->vbr = TRUE;
+                if (info->vbr)
+                    info->bitrate += header.bitrate;
+                /* check for changin layer/samplerate/channels */
+                if (info->mpeg_layer != header.layer)
+                    g_warning("layer varies!!");
+                if (info->freq != header.samplerate)
+                    g_warning("samplerate varies!!");
+                if (info->channels != MAD_NCHANNELS(&header))
+                    g_warning("number of channels varies!!");
+            }
+
+            if (fast && info->frames >= N_AVERAGE_FRAMES) {
+                float frame_size = ((double) data_used) / N_AVERAGE_FRAMES;
+                info->frames = (info->size - tagsize) / frame_size;
+                //int frame_frac = (info->size - tagsize) % frame_size;
+                info->duration.seconds /= N_AVERAGE_FRAMES;
+                info->duration.fraction /= N_AVERAGE_FRAMES;
+                mad_timer_multiply(&info->duration, info->frames);
+#ifdef DEBUG
+                g_message("using fast playtime calculation");
+                g_message("data used = %d [tagsize=%d framesize=%f]",
+                          data_used, tagsize, frame_size);
+                //g_message ("frame_size = %f [frac=%d]", frame_size, frame_frac);
+                g_message("frames = %d, frequecy = %d, channels = %d",
+                          info->frames, info->freq, info->channels);
+                long millis = mad_timer_count(info->duration,
+                                              MAD_UNITS_MILLISECONDS);
+                g_message("duration = %lu:%lu", millis / 1000 / 60,
+                          (millis / 1000) % 60);
+#endif                          /* DEBUG */
+                break;
+            }
+        }
+        if (stream.error != MAD_ERROR_BUFLEN)
+            break;
+    }
+
+    if (info->vbr && !has_xing)
+        info->bitrate = info->bitrate / info->frames;
+
+    mad_frame_finish(&frame);
+    mad_header_finish(&header);
+    mad_stream_finish(&stream);
+    xing_finish(&info->xing);
+
+#ifdef DEBUG
+    g_message("e: scan_file");
+#endif                          /* DEBUG */
+    return (info->frames != 0 || info->remote == TRUE);
+}
+
+gpointer decode_loop(gpointer arg)
+{
+    unsigned char buffer[BUFFER_SIZE];
+    int len;
+    int seek_skip = 0;
+    int remainder = 0;
+    gint tlen;
+
+    /* mad structs */
+    struct mad_stream stream;
+    struct mad_frame frame;
+    struct mad_synth synth;
+
+    /* track info is passed in as thread argument */
+    struct mad_info_t *info = (struct mad_info_t *) arg;
+
+#ifdef DEBUG
+    g_message("f: decode");
+#endif                          /* DEBUG */
+
+    /* init mad stuff */
+    mad_frame_init(&frame);
+    mad_stream_init(&stream);
+    mad_synth_init(&synth);
+
+    if (!mad_plugin->output->
+        open_audio(info->fmt, info->freq, info->channels)) {
+        audmad_error("failed to open audio output: %s",
+                      mad_plugin->output->description);
+        g_message("failed to open audio output: %s",
+                  mad_plugin->output->description);
+        return NULL;
+    }
+
+    /* set mainwin title */
+    if (info->title)
+        g_free(info->title);
+    info->title =
+        xmms_get_titlestring(xmms_get_gentitle_format(), info->tuple);
+
+    tlen = (gint) mad_timer_count(info->duration, MAD_UNITS_MILLISECONDS),
+    
+    mad_plugin->set_info(info->title,
+                        tlen == 0 ? -1 : tlen,
+                        info->bitrate, info->freq, info->channels);
+
+    /* main loop */
+    do {
+        if (info->playback->playing == 0) {
+#ifdef DEBUG
+            g_message("decode: stop signaled");
+#endif                          /* DEBUG */
+            break;
+        }
+        if (seek_skip)
+            remainder = 0;
+        else {
+            remainder = stream.bufend - stream.next_frame;
+            memcpy(buffer, stream.this_frame, remainder);
+        }
+        len = input_get_data(info, buffer + remainder,
+                             BUFFER_SIZE - remainder);
+        if (len <= 0) {
+#ifdef DEBUG
+            g_message("finished decoding");
+#endif                          /* DEBUG */
+            break;
+        }
+        len += remainder;
+        if (len < MAD_BUFFER_GUARD) {
+            int i;
+            for (i = len; i < MAD_BUFFER_GUARD; i++)
+                buffer[i] = 0;
+            len = MAD_BUFFER_GUARD;
+        }
+
+        mad_stream_buffer(&stream, buffer, len);
+
+        if (seek_skip) {
+#ifdef DEBUG
+            g_message("skipping: %d", seek_skip);
+#endif
+            int skip = 2;
+            do {
+                if (mad_frame_decode(&frame, &stream) == 0) {
+                    mad_timer_add(&info->pos, frame.header.duration);
+                    if (--skip == 0)
+                        mad_synth_frame(&synth, &frame);
+                }
+                else if (!MAD_RECOVERABLE(stream.error))
+                    break;
+            }
+            while (skip);
+            seek_skip = 0;
+        }
+
+        while (!info->playback->playing == 0) {
+            if (info->seek != -1 && !info->remote) {
+#ifdef DEBUG
+                g_message("seeking: %d", info->seek);
+#endif
+                int new_position;
+                int seconds =
+                    mad_timer_count(info->duration, MAD_UNITS_SECONDS);
+                if (info->seek >= seconds)
+                    info->seek = seconds;
+
+                mad_timer_set(&info->pos, info->seek, 0, 0);
+                new_position =
+                    ((double) info->seek / (double) seconds) * info->size;
+#ifdef DEBUG
+                g_message("seeking to: %d bytes", new_position);
+#endif
+                if (vfs_fseek(info->infile, new_position, SEEK_SET) == -1)
+                    audmad_error("failed to seek to: %d", new_position);
+                mad_frame_mute(&frame);
+                mad_synth_mute(&synth);
+                stream.error = MAD_ERROR_BUFLEN;
+                mad_plugin->output->flush(mad_timer_count
+                                         (info->pos,
+                                          MAD_UNITS_MILLISECONDS));
+                stream.sync = 0;
+                info->seek = -1;
+                seek_skip = 1;
+                break;
+            }
+
+            if (mad_header_decode(&frame.header, &stream) == -1) {
+                if (!MAD_RECOVERABLE(stream.error))
+                    break;
+                if (stream.error == MAD_ERROR_LOSTSYNC) {
+                    /* ignore LOSTSYNC due to ID3 tags */
+                    int tagsize = id3_tag_query(stream.this_frame,
+                                                stream.bufend -
+                                                stream.this_frame);
+                    if (tagsize > 0) {
+                        mad_stream_skip(&stream, tagsize);
+                        continue;
+                    }
+                }
+#ifdef DEBUG
+                g_message("(recovered) error decoding header %d: %s",
+                          info->current_frame,
+                          mad_stream_errorstr(&stream));
+#endif                          /* DEBUG */
+                continue;
+            }
+
+            if (mad_frame_decode(&frame, &stream) == -1) {
+                if (!MAD_RECOVERABLE(stream.error))
+                    break;
+#ifdef DEBUG
+                g_message("(recovered) error decoding frame %d: %s",
+                          info->current_frame,
+                          mad_stream_errorstr(&stream));
+#endif                          /* DEBUG */
+            }
+
+            info->current_frame++;
+
+            if (info->freq != frame.header.samplerate
+                || info->channels !=
+                (guint) MAD_NCHANNELS(&frame.header))
+	    {
+		tlen = mad_timer_count(info->duration, MAD_UNITS_MILLISECONDS);
+#ifdef DEBUG
+                g_message("re-opening audio due to change in audio type");
+                g_message("old: frequency = %d, channels = %d", info->freq,
+                          info->channels);
+                g_message("new: frequency = %d, channels = %d",
+                          frame.header.samplerate,
+                          (guint) MAD_NCHANNELS(&frame.header));
+#endif                          /* DEBUG */
+
+                info->freq = frame.header.samplerate;
+                info->channels = MAD_NCHANNELS(&frame.header);
+                mad_plugin->output->close_audio();
+                if (!mad_plugin->output->open_audio(info->fmt, info->freq,
+                                                   info->channels)) {
+                    audmad_error("failed to re-open audio output: %s",
+                                  mad_plugin->output->description);
+                }
+
+		mad_plugin->set_info(info->title,
+                        tlen != 0 ? tlen : -1,
+                        info->bitrate, info->freq, info->channels);
+            }
+            if (info->playback->playing == 0)
+                break;
+            mad_synth_frame(&synth, &frame);
+            mad_stream_sync(&stream);
+            write_output(info, &synth.pcm, &frame.header);
+            mad_timer_add(&info->pos, frame.header.duration);
+        }
+    }
+    while (stream.error == MAD_ERROR_BUFLEN);
+
+    /* free mad stuff */
+    mad_frame_finish(&frame);
+    mad_stream_finish(&stream);
+    mad_synth_finish(&synth);
+
+    if (!info->playback->playing == 0) {
+        mad_plugin->output->buffer_free();
+        mad_plugin->output->buffer_free();
+        while (mad_plugin->output->buffer_playing()) {
+#ifdef DEBUG
+            g_message("f: buffer_playing=%d",
+                      mad_plugin->output->buffer_playing());
+#endif
+            xmms_usleep(10000);
+            if (info->playback->playing == 0)
+                break;
+        }
+    }
+#ifdef DEBUG
+    g_message("e: decode");
+#endif                          /* DEBUG */
+
+    bmp_title_input_free(info->tuple);
+    info->tuple = NULL;
+
+    mad_plugin->output->close_audio();
+    info->playback->playing = 0;
+    g_thread_exit(0);
+    return NULL; /* dummy */
+}