view src/demac/plugin.c @ 2284:d19b53359b24

cleaned up the sndfile wav plugin, currently limiting it ONLY TO WAV PLAYBACK. if somebody is more experienced with it and wants to restore the other formats, go ahead (maybe change the name of the plugin too?).
author mf0102 <0102@gmx.at>
date Wed, 09 Jan 2008 15:41:22 +0100
parents 05c9bec29aaa
children 699b5e756bc4
line wrap: on
line source

/* 
 * Audacious Monkey's Audio plugin
 *
 * Copyright (C) Eugene Zagidullin 2007
 *
 * Used some code from libdemac and example application:
 * Copyright (C) Dave Chapman 2007
 *
 * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
 *
 */

#ifndef _XOPEN_SOURCE
#define _XOPEN_SOURCE
#endif

#include "config.h" 

#include <ctype.h> 
#include <stdio.h> 

#include <stdlib.h> 
#include <errno.h> 
#include <string.h> 
#include <assert.h> 

#include <glib.h> 
#include <gtk/gtk.h>
#include <glib/gprintf.h>
#include <mowgli.h>

#include <audacious/i18n.h> 

#include <audacious/plugin.h> 
#include <audacious/main.h> 
#include <audacious/output.h> 
#include <audacious/vfs.h> 
#include <audacious/util.h> 

#include "ape.h"
#include "apev2.h"

#ifndef MIN
#define MIN(a,b) ((a) < (b) ? (a) : (b))
#endif

#define MAX_BYTESPERSAMPLE 2 // currently only 16bps supported

static GThread *pb_thread;
static gpointer demac_decode_loop(InputPlayback *pb);
static Tuple *demac_get_tuple(char *filename);
static Tuple *demac_probe_for_tuple (gchar *uri, VFSFile *vfd);

static GMutex *demac_mutex;
static unsigned long seek_to_msec=(unsigned long)-1; /* -1 == not needed */

static InputPlugin demac_ip;
static GtkWidget *about_window = NULL;

#ifdef DEBUG
# include "crc.c"
#endif

static gboolean demac_probe_vfs(char *filename, VFSFile* input_file) {
    APEContext *ctx;

    ctx = calloc(sizeof(APEContext), 1);
    if(ape_read_header(ctx, input_file, 1) < 0) {
      free(ctx);
      aud_vfs_rewind(input_file); /* Do we really need it? */
      return FALSE;
    }

    free(ctx);

    aud_vfs_rewind(input_file); /* Do we really need it? */

    return TRUE;
}

static void demac_play(InputPlayback *pb) {
    pb->playing = 1;
    pb->eof = 0;
    pb->error = FALSE;
    pb_thread = g_thread_self();
    pb->set_pb_ready(pb);
    
    demac_decode_loop(pb);
}

static void demac_do_mseek(APEContext *ctx, unsigned long msec) {
  if(ctx->seektable) {
    unsigned int framecount = msec * ((unsigned long long)ctx->totalframes - 1L) / ctx->duration;
    ctx->currentframe = framecount;
  }
}

gpointer demac_decode_loop(InputPlayback *pb) {
    VFSFile *vfd;
    uint8_t *frame_buf = NULL;
    guint8 *wav = NULL;
    int wav_buffer_size;
    gchar *title = NULL;
    int audio_opened = 0;
    int playing;
    unsigned long local_seek_to;
    APEContext *ctx = NULL;
    APEDecoderContext *dec = NULL;
    int decoded_bytes;
    int pkt_size, bytes_used;
#ifdef DEBUG
    uint32_t frame_crc;
#endif
    
    if ((vfd = aud_vfs_fopen(pb->filename, "r")) == NULL) {
#ifdef DEBUG
        fprintf(stderr, "** demac: plugin.c: Error opening URI: %s\n", pb->filename);
#endif
        pb->error = TRUE;
        goto cleanup;
    }

    ctx = calloc(sizeof(APEContext), 1);
    if(ape_read_header(ctx, vfd, 0) < 0) {
        pb->error = TRUE;
#ifdef DEBUG
        fprintf(stderr, "** demac: plugin.c: Cannot parse APE header or unsupported format: %s\n", pb->filename);
#endif
        goto cleanup;
    }

    dec = calloc(sizeof(APEDecoderContext), 1);
    if(ape_decode_init(dec, ctx) < 0) {
        pb->error = TRUE;
#ifdef DEBUG
        fprintf(stderr, "** demac: plugin.c: Error initializing decoder\n");
#endif
        goto cleanup;
    }
    
    frame_buf = malloc(ctx->max_packet_size);
   
#ifdef DEBUG
    fprintf(stderr, "** demac: plugin.c: Duration: %u msec\n", ctx->duration);
#endif

    if(!pb->output->open_audio(FMT_S16_LE, ctx->samplerate, ctx->channels)) {
        pb->error = TRUE;
#ifdef DEBUG
        fprintf(stderr, "** demac: plugin.c: Cannot open audio.\n");
#endif
        goto cleanup;
    }

    audio_opened = 1;

    Tuple *tpl = demac_probe_for_tuple (pb->filename, vfd);
    title = aud_tuple_formatter_make_title_string(tpl, aud_get_gentitle_format());
    pb->set_params(pb, title, ctx->duration, -1, ctx->samplerate, ctx->channels);
    aud_tuple_free(tpl);

    /* begin decoding */
    wav_buffer_size = ctx->blocksperframe * MAX_BYTESPERSAMPLE * ctx->channels;
    wav = malloc(wav_buffer_size);

    g_mutex_lock(demac_mutex);
    playing = pb->playing;
    g_mutex_unlock(demac_mutex);

    while (playing && (ctx->currentframe < ctx->totalframes))
    {
        g_mutex_lock(demac_mutex);
        playing = pb->playing;
        local_seek_to = seek_to_msec;
        g_mutex_unlock(demac_mutex);
        
        /* do seeking */
        if (local_seek_to != -1) {
            demac_do_mseek(ctx, local_seek_to);
            pb->output->flush(local_seek_to);
	    
	    local_seek_to = -1;
            g_mutex_lock(demac_mutex);
	    seek_to_msec = -1;
            g_mutex_unlock(demac_mutex);

	    /* reset decoder */
	    dec->samples = 0;
	}

        ape_read_packet(ctx, vfd, frame_buf, &pkt_size);
#ifdef DEBUG
        assert(pkt_size <= ctx->max_packet_size);
	frame_crc = ape_initcrc();
#endif
	bytes_used = 0;

/*#ifdef DEBUG
        fprintf(stderr, "frame %d, %d samples, offset %d, size %d\n", ctx->currentframe-1,
	        *((uint32_t*)frame_buf), *((uint32_t*)(frame_buf+4)), pkt_size);
#endif*/

        /* Decode the frame a chunk at a time */
        while (playing && (bytes_used != pkt_size) && (local_seek_to == -1))
        {
            g_mutex_lock(demac_mutex);
            playing = pb->playing;
            local_seek_to = seek_to_msec;
            g_mutex_unlock(demac_mutex);
	    
	    decoded_bytes = wav_buffer_size;
            bytes_used = ape_decode_frame(dec, wav, &decoded_bytes, frame_buf, pkt_size);
	    if(bytes_used < 0) {
	        /* skip frame */
		dec->samples = 0;
		break;
	    }
	    
	    if(local_seek_to != -1) break;

            /* Write audio data */
            pb->pass_audio(pb, FMT_S16_LE, ctx->channels, decoded_bytes, wav, &playing);
#if DEBUG
            frame_crc = ape_updatecrc(wav, decoded_bytes, frame_crc);
#endif

        }

#if DEBUG
        frame_crc = ape_finishcrc(frame_crc);

        if (dec->CRC != frame_crc) {
            fprintf(stderr, "** demac: plugin.c: CRC error in frame %d\n", ctx->currentframe-1);
        }
#endif
    }

cleanup:
    
    pb->eof = TRUE;
    pb->playing = 0;

    if(title) g_free(title);
    if(audio_opened) pb->output->close_audio();
   
    if(dec) {ape_decode_close(dec); free(dec);}
    if(wav) free(wav);
    if(frame_buf) free(frame_buf);
    if(ctx) {ape_read_close(ctx); free(ctx);}
    if(vfd) aud_vfs_fclose(vfd);

#ifdef DEBUG
    fprintf(stderr, "** demac: plugin.c: decoding loop finished\n");
#endif

    return NULL;
}

static void demac_stop(InputPlayback *pb)
{
    g_mutex_lock(demac_mutex);
    int playing = pb->playing;
    g_mutex_unlock(demac_mutex);

    if (playing) {
        g_mutex_lock(demac_mutex);
        pb->playing = 0;
        g_mutex_unlock(demac_mutex);
#ifdef DEBUG
        fprintf(stderr, "** demac: plugin.c: waiting for thread finished\n");
#endif
        //g_thread_join(pb->thread);
	/* pb->thread is useless if input plugin initialized from **terrible** cue-sheet plugin */
        g_thread_join(pb_thread);
#ifdef DEBUG
        fprintf(stderr, "** demac: plugin.c: thread finished\n");
#endif
    }
}

static void demac_pause(InputPlayback *pb, short paused) {
    pb->output->pause(paused);
}

static void destroy_cb(mowgli_dictionary_elem_t *delem, void *privdata) {
    g_free(delem->data);
}

Tuple *demac_probe_for_tuple (gchar *uri, VFSFile *vfd) {
#ifdef DEBUG
    fprintf(stderr, "** demac: plugin.c: demac_probe_for_tuple()\n");
#endif
    Tuple *tpl = aud_tuple_new_from_filename(uri);
    gchar codec_string[32];

    if (aud_vfs_is_streaming(vfd)) {
        /* This plugin does not support streams yet */
        return NULL;
    }

    mowgli_dictionary_t *tag = NULL;
    gchar *item;
    if ((tag = parse_apev2_tag(vfd)) != NULL) {
        if((item = mowgli_dictionary_retrieve(tag, "Artist")) != NULL) aud_tuple_associate_string(tpl, FIELD_ARTIST, NULL, item);
        if((item = mowgli_dictionary_retrieve(tag, "Title")) != NULL) aud_tuple_associate_string(tpl, FIELD_TITLE, NULL, item);
        if((item = mowgli_dictionary_retrieve(tag, "Album")) != NULL) aud_tuple_associate_string(tpl, FIELD_ALBUM, NULL, item);
        if((item = mowgli_dictionary_retrieve(tag, "Comment")) != NULL) aud_tuple_associate_string(tpl, FIELD_COMMENT, NULL, item);
        if((item = mowgli_dictionary_retrieve(tag, "Genre")) != NULL) aud_tuple_associate_string(tpl, FIELD_GENRE, NULL, item);
        if((item = mowgli_dictionary_retrieve(tag, "Track")) != NULL) aud_tuple_associate_int(tpl, FIELD_TRACK_NUMBER, NULL, atoi(item));
        if((item = mowgli_dictionary_retrieve(tag, "Year")) != NULL) aud_tuple_associate_int(tpl, FIELD_YEAR, NULL, atoi(item));
    }

    APEContext *ctx = calloc(sizeof(APEContext), 1);
    aud_vfs_rewind(vfd);
    ape_read_header(ctx, vfd, 1);
    aud_tuple_associate_int(tpl, FIELD_LENGTH, NULL, ctx->duration);
    ape_read_close(ctx);
    free(ctx);

    if (tag) mowgli_dictionary_destroy(tag, destroy_cb, NULL);
    
    g_sprintf(codec_string, "Monkey's Audio v%4.2f", (float)ctx->fileversion/1000.0);
#ifdef DEBUG
    fprintf(stderr, "** demac: plugin.c: Codec: %s\n", codec_string);
#endif
    aud_tuple_associate_string(tpl, FIELD_CODEC, NULL, codec_string);
    aud_tuple_associate_string(tpl, FIELD_QUALITY, NULL, "lossless");
    aud_tuple_associate_string(tpl, FIELD_MIMETYPE, NULL, "audio/x-ape");
    return tpl;
}

Tuple *demac_get_tuple(char *filename) {
#ifdef DEBUG
  fprintf(stderr, "** demac: plugin.c: demac_get_tuple()\n");
#endif
  VFSFile *vfd;

  if ((vfd = aud_vfs_fopen(filename, "r")) == NULL) {
    return NULL;
  }

  Tuple *tpl = demac_probe_for_tuple(filename, vfd);
  aud_vfs_fclose(vfd);
  return tpl;
}

static void demac_mseek (InputPlayback *pb, gulong millisecond) {
  g_mutex_lock(demac_mutex);
  seek_to_msec = millisecond;
  g_mutex_unlock(demac_mutex);
#ifdef DEBUG
  fprintf(stderr, "** demac: plugin.c: seeking to %u msec\n", millisecond);
#endif
}

static void demac_seek (InputPlayback *pb, gint time) {
  demac_mseek(pb, (unsigned long)time*1000);
}

static void demac_init() {
  /* Found in Freedesktop shared-mime-info */
  aud_mime_set_plugin("audio/x-ape", &demac_ip);
  demac_mutex = g_mutex_new();
}

static void demac_cleanup() {
  g_mutex_free(demac_mutex);
}

static void demac_about(void) {

    if (about_window) {
      gtk_window_present(GTK_WINDOW(about_window));
    } else {
      about_window = audacious_info_dialog(_("About Monkey's Audio Plugin"),
                                       _("Copyright (C) 2007 Eugene Zagidullin <e.asphyx@gmail.com>\n"
                                        "Based on ffape decoder, Copyright (C) 2007 Benjamin Zores\n"
					"ffape itself based on libdemac by Dave Chapman\n\n"
					"ffape is a part of FFmpeg project, http://ffmpeg.mplayerhq.hu/"),
                                       _("Ok"), FALSE, NULL, NULL);
      g_signal_connect(G_OBJECT(about_window), "destroy", G_CALLBACK(gtk_widget_destroyed), &about_window);
    }
}

static void insert_str_tuple_field_to_dictionary(Tuple *tuple, int fieldn, mowgli_dictionary_t *dict, char *key) {
    
    if(mowgli_dictionary_find(dict, key) != NULL) g_free(mowgli_dictionary_delete(dict, key));
    
    gchar *tmp = (gchar*)aud_tuple_get_string(tuple, fieldn, NULL);
    if(tmp != NULL && strlen(tmp) != 0) mowgli_dictionary_add(dict, key, g_strdup(tmp));
}

static void insert_int_tuple_field_to_dictionary(Tuple *tuple, int fieldn, mowgli_dictionary_t *dict, char *key) {
    int val;

    if(mowgli_dictionary_find(dict, key) != NULL) g_free(mowgli_dictionary_delete(dict, key));
    
    if(aud_tuple_get_value_type(tuple, fieldn, NULL) == TUPLE_INT && (val = aud_tuple_get_int(tuple, fieldn, NULL)) >= 0) {
        gchar *tmp = g_strdup_printf("%d", val);
        mowgli_dictionary_add(dict, key, tmp);
    }
}

static gboolean demac_update_song_tuple(Tuple *tuple, VFSFile *vfd) {

    mowgli_dictionary_t *tag = parse_apev2_tag(vfd);
    if (tag == NULL) tag = mowgli_dictionary_create(g_ascii_strcasecmp);

    insert_str_tuple_field_to_dictionary(tuple, FIELD_TITLE, tag, "Title");
    insert_str_tuple_field_to_dictionary(tuple, FIELD_ARTIST, tag, "Artist");
    insert_str_tuple_field_to_dictionary(tuple, FIELD_ALBUM, tag, "Album");
    insert_str_tuple_field_to_dictionary(tuple, FIELD_COMMENT, tag, "Comment");
    insert_str_tuple_field_to_dictionary(tuple, FIELD_GENRE, tag, "Genre");
    
    insert_int_tuple_field_to_dictionary(tuple, FIELD_YEAR, tag, "Year");
    insert_int_tuple_field_to_dictionary(tuple, FIELD_TRACK_NUMBER, tag, "Track");

    gboolean ret = write_apev2_tag(vfd, tag);
    mowgli_dictionary_destroy(tag, destroy_cb, NULL);

    return ret;
}

static gchar *fmts[] = { "ape", NULL };

static InputPlugin demac_ip = {
    .description = "Monkey's Audio Plugin",
    .init = demac_init,
    .about = demac_about,
    .play_file = demac_play,
    .stop = demac_stop,
    .pause = demac_pause,
    .seek = demac_seek,
    .cleanup = demac_cleanup,
    .get_song_tuple = demac_get_tuple,
    .is_our_file_from_vfs = demac_probe_vfs,
    .vfs_extensions = fmts,
    .mseek = demac_mseek,
    .probe_for_tuple = demac_probe_for_tuple,
    .update_song_tuple = demac_update_song_tuple,
};

InputPlugin *demac_iplist[] = { &demac_ip, NULL };
DECLARE_PLUGIN(demac, NULL, NULL, demac_iplist, NULL, NULL, NULL, NULL, NULL);

 /* vim:foldmethod=syntax: */