Mercurial > audlegacy-plugins
diff src/sndfile/plugin.c @ 2303:5561e63e9133
threw out the WAV plugin in favour of the sndfile plugin
| author | mf0102 <0102@gmx.at> |
|---|---|
| date | Sat, 12 Jan 2008 18:41:04 +0100 |
| parents | |
| children | 41c619e96a17 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/sndfile/plugin.c Sat Jan 12 18:41:04 2008 +0100 @@ -0,0 +1,591 @@ +/* Audacious - Cross-platform multimedia player + * Copyright (C) 2005 Audacious development team. + * + * Based on the xmms_sndfile input plugin: + * Copyright (C) 2000, 2002 Erik de Castro Lopo + * + * 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 Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/* + * Rewritten 17-Feb-2007 (nenolod): + * - now uses conditional variables to ensure that sndfile mutex is + * entirely protected. + * - pausing works now + * - fixed some potential race conditions when dealing with NFS. + * - TITLE_LEN removed + */ + +#include "config.h" + +#include <glib.h> +#include <string.h> +#include <math.h> + +#include <audacious/plugin.h> +#include <audacious/util.h> +#include <audacious/i18n.h> +#include <audacious/main.h> +#include <audacious/output.h> +#include "plugin.h" + +#include <sndfile.h> + +static SNDFILE *sndfile = NULL; +static SF_INFO sfinfo; + +static int song_length; +static int bit_rate = 0; +static glong seek_time = -1; + +static GThread *decode_thread; +static GMutex *decode_mutex; +static GCond *decode_cond; + + + + +static sf_count_t sf_get_filelen (void *user_data) +{ + return aud_vfs_fsize (user_data); +} +static sf_count_t sf_vseek (sf_count_t offset, int whence, void *user_data) +{ + return aud_vfs_fseek(user_data, offset, whence); +} +static sf_count_t sf_vread (void *ptr, sf_count_t count, void *user_data) +{ + return aud_vfs_fread(ptr, 1, count, user_data); +} +static sf_count_t sf_vwrite (const void *ptr, sf_count_t count, void *user_data) +{ + return aud_vfs_fwrite(ptr, 1, count, user_data); +} +static sf_count_t sf_tell (void *user_data) +{ + return aud_vfs_ftell(user_data); +} +static SF_VIRTUAL_IO sf_virtual_io = +{ + sf_get_filelen, + sf_vseek, + sf_vread, + sf_vwrite, + sf_tell +}; + +static SNDFILE * +open_sndfile_from_uri(gchar *filename, VFSFile *vfsfile, SF_INFO *tmp_sfinfo) +{ + SNDFILE *snd_file = NULL; + vfsfile = aud_vfs_fopen(filename, "rb"); + + if (vfsfile == NULL) + return NULL; + + snd_file = sf_open_virtual (&sf_virtual_io, SFM_READ, tmp_sfinfo, vfsfile); + if (snd_file == NULL) + aud_vfs_fclose(vfsfile); + + return snd_file; +} + +static void +close_sndfile(SNDFILE *snd_file, VFSFile *vfsfile) +{ + sf_close(snd_file); + aud_vfs_fclose(vfsfile); +} + + + +static void +plugin_init (void) +{ + seek_time = -1; + + decode_mutex = g_mutex_new(); + decode_cond = g_cond_new(); +} + +static void +plugin_cleanup (void) +{ + g_cond_free(decode_cond); + g_mutex_free(decode_mutex); +} + +static void +fill_song_tuple (char *filename, Tuple *ti) +{ + VFSFile *vfsfile = NULL; + SNDFILE *tmp_sndfile; + SF_INFO tmp_sfinfo; + unsigned int lossy = 0; + gchar *codec = NULL, *format, *subformat = NULL; + GString *codec_gs = NULL; + + tmp_sndfile = open_sndfile_from_uri(filename, vfsfile, &tmp_sfinfo); + if ( sf_get_string(tmp_sndfile, SF_STR_TITLE) == NULL) + aud_tuple_associate_string(ti, FIELD_TITLE, NULL, g_path_get_basename(filename)); + else + aud_tuple_associate_string(ti, FIELD_TITLE, NULL, sf_get_string(tmp_sndfile, SF_STR_TITLE)); + + aud_tuple_associate_string(ti, FIELD_ARTIST, NULL, sf_get_string(tmp_sndfile, SF_STR_ARTIST)); + aud_tuple_associate_string(ti, FIELD_COMMENT, NULL, sf_get_string(tmp_sndfile, SF_STR_COMMENT)); + aud_tuple_associate_string(ti, FIELD_DATE, NULL, sf_get_string(tmp_sndfile, SF_STR_DATE)); + aud_tuple_associate_string(ti, -1, "software", sf_get_string(tmp_sndfile, SF_STR_SOFTWARE)); + + if (!tmp_sndfile) + return; + + close_sndfile (tmp_sndfile, vfsfile); + tmp_sndfile = NULL; + + if (tmp_sfinfo.samplerate > 0) + aud_tuple_associate_int(ti, FIELD_LENGTH, NULL, (int) ceil (1000.0 * tmp_sfinfo.frames / tmp_sfinfo.samplerate)); + + switch (tmp_sfinfo.format & SF_FORMAT_TYPEMASK) + { + case SF_FORMAT_WAV: + case SF_FORMAT_WAVEX: + format = "Microsoft WAV"; + break; + case SF_FORMAT_AIFF: + format = "Apple/SGI AIFF"; + break; + case SF_FORMAT_AU: + format = "Sun/NeXT AU"; + break; + case SF_FORMAT_RAW: + format = "Raw PCM data"; + break; + case SF_FORMAT_PAF: + format = "Ensoniq PARIS"; + break; + case SF_FORMAT_SVX: + format = "Amiga IFF / SVX8 / SV16"; + break; + case SF_FORMAT_NIST: + format = "Sphere NIST"; + break; + case SF_FORMAT_VOC: + format = "Creative VOC"; + break; + case SF_FORMAT_IRCAM: + format = "Berkeley/IRCAM/CARL"; + break; + case SF_FORMAT_W64: + format = "Sonic Foundry's 64 bit RIFF/WAV"; + break; + case SF_FORMAT_MAT4: + format = "Matlab (tm) V4.2 / GNU Octave 2.0"; + break; + case SF_FORMAT_MAT5: + format = "Matlab (tm) V5.0 / GNU Octave 2.1"; + break; + case SF_FORMAT_PVF: + format = "Portable Voice Format"; + break; + case SF_FORMAT_XI: + format = "Fasttracker 2 Extended Instrument"; + break; + case SF_FORMAT_HTK: + format = "HMM Tool Kit"; + break; + case SF_FORMAT_SDS: + format = "Midi Sample Dump Standard"; + break; + case SF_FORMAT_AVR: + format = "Audio Visual Research"; + break; + case SF_FORMAT_SD2: + format = "Sound Designer 2"; + break; + case SF_FORMAT_FLAC: + format = "Free Lossless Audio Codec"; + break; + case SF_FORMAT_CAF: + format = "Core Audio File"; + break; + default: + format = "unknown sndfile"; + } + switch (tmp_sfinfo.format & SF_FORMAT_SUBMASK) + { + case SF_FORMAT_PCM_S8: + subformat = "signed 8 bit"; + break; + case SF_FORMAT_PCM_16: + subformat = "signed 16 bit"; + break; + case SF_FORMAT_PCM_24: + subformat = "signed 24 bit"; + break; + case SF_FORMAT_PCM_32: + subformat = "signed 32 bit"; + break; + case SF_FORMAT_PCM_U8: + subformat = "unsigned 8 bit"; + break; + case SF_FORMAT_FLOAT: + subformat = "32 bit float"; + break; + case SF_FORMAT_DOUBLE: + subformat = "64 bit float"; + break; + case SF_FORMAT_ULAW: + subformat = "U-Law"; + lossy = 1; + break; + case SF_FORMAT_ALAW: + subformat = "A-Law"; + lossy = 1; + break; + case SF_FORMAT_IMA_ADPCM: + subformat = "IMA ADPCM"; + lossy = 1; + break; + case SF_FORMAT_MS_ADPCM: + subformat = "MS ADPCM"; + lossy = 1; + break; + case SF_FORMAT_GSM610: + subformat = "GSM 6.10"; + lossy = 1; + break; + case SF_FORMAT_VOX_ADPCM: + subformat = "Oki Dialogic ADPCM"; + lossy = 1; + break; + case SF_FORMAT_G721_32: + subformat = "32kbs G721 ADPCM"; + lossy = 1; + break; + case SF_FORMAT_G723_24: + subformat = "24kbs G723 ADPCM"; + lossy = 1; + break; + case SF_FORMAT_G723_40: + subformat = "40kbs G723 ADPCM"; + lossy = 1; + break; + case SF_FORMAT_DWVW_12: + subformat = "12 bit Delta Width Variable Word"; + lossy = 1; + break; + case SF_FORMAT_DWVW_16: + subformat = "16 bit Delta Width Variable Word"; + lossy = 1; + break; + case SF_FORMAT_DWVW_24: + subformat = "24 bit Delta Width Variable Word"; + lossy = 1; + break; + case SF_FORMAT_DWVW_N: + subformat = "N bit Delta Width Variable Word"; + lossy = 1; + break; + case SF_FORMAT_DPCM_8: + subformat = "8 bit differential PCM"; + break; + case SF_FORMAT_DPCM_16: + subformat = "16 bit differential PCM"; + } + + codec_gs = g_string_new(""); + if (subformat != NULL) + g_string_append_printf(codec_gs, "%s (%s)", format, subformat); + else + g_string_append_printf(codec_gs, "%s", format); + codec = g_strdup(codec_gs->str); + g_string_free(codec_gs, TRUE); + aud_tuple_associate_string(ti, FIELD_CODEC, NULL, codec); + + if (lossy != 0) + aud_tuple_associate_string(ti, FIELD_QUALITY, NULL, "lossy"); + else + aud_tuple_associate_string(ti, FIELD_QUALITY, NULL, "lossless"); +} + +static gchar *get_title(char *filename) +{ + Tuple *tuple; + gchar *title; + + tuple = aud_tuple_new_from_filename(filename); + fill_song_tuple(filename, tuple); + title = aud_tuple_formatter_make_title_string(tuple, aud_get_gentitle_format()); + if (*title == '\0') + { + g_free(title); + title = g_strdup(aud_tuple_get_string(tuple, FIELD_FILE_NAME, NULL)); + } + + aud_tuple_free(tuple); + return title; +} + +static int +is_our_file (char *filename) +{ + VFSFile *vfsfile = NULL; + SNDFILE *tmp_sndfile; + SF_INFO tmp_sfinfo; + + /* Have to open the file to see if libsndfile can handle it. */ + tmp_sndfile = open_sndfile_from_uri(filename, vfsfile, &tmp_sfinfo); + + if (!tmp_sndfile) { + return FALSE; + } + + /* It can so close file and return TRUE. */ + close_sndfile (tmp_sndfile, vfsfile); + tmp_sndfile = NULL; + + return TRUE; +} + +static gpointer +play_loop (gpointer arg) +{ + static short buffer [BUFFER_SIZE]; + int samples; + InputPlayback *playback = arg; + + for (;;) + { + GTimeVal sleeptime; + + /* sf_read_short will return 0 for all reads at EOF. */ + samples = sf_read_short (sndfile, buffer, BUFFER_SIZE); + + if (samples > 0 && playback->playing == TRUE) { + while ((playback->output->buffer_free () < samples) && + playback->playing == TRUE) { + g_get_current_time(&sleeptime); + g_time_val_add(&sleeptime, 500000); + g_mutex_lock(decode_mutex); + g_cond_timed_wait(decode_cond, decode_mutex, &sleeptime); + g_mutex_unlock(decode_mutex); + + if (playback->playing == FALSE) + break; + } + + playback->pass_audio(playback, FMT_S16_NE, sfinfo.channels, + samples * sizeof (short), buffer, &playback->playing); + } + else { + while(playback->output->buffer_playing()) { + g_get_current_time(&sleeptime); + g_time_val_add(&sleeptime, 500000); + g_mutex_lock(decode_mutex); + g_cond_timed_wait(decode_cond, decode_mutex, &sleeptime); + g_mutex_unlock(decode_mutex); + + if(playback->playing == FALSE) + break; + } + + playback->eof = TRUE; + playback->playing = FALSE; + + g_mutex_unlock(decode_mutex); + break; + } + + /* Do seek if seek_time is valid. */ + if (seek_time >= 0) { + sf_seek (sndfile, (sf_count_t)((gint64)seek_time * (gint64)sfinfo.samplerate / 1000L), + SEEK_SET); + playback->output->flush (seek_time); + seek_time = -1; + } + + if (playback->playing == FALSE) + break; + } + + sf_close (sndfile); + sndfile = NULL; + seek_time = -1; + + playback->output->close_audio(); + + return NULL; +} + +static void +play_start (InputPlayback *playback) +{ + VFSFile *vfsfile = NULL; + int pcmbitwidth; + gchar *song_title; + + if (sndfile) /* already opened */ + return; + + pcmbitwidth = 32; + song_title = get_title(playback->filename); + + sndfile = open_sndfile_from_uri(playback->filename, vfsfile, &sfinfo); + + if (!sndfile) + return; + + bit_rate = sfinfo.samplerate * pcmbitwidth; + + if (sfinfo.samplerate > 0) + song_length = (int) ceil (1000.0 * sfinfo.frames / sfinfo.samplerate); + else + song_length = 0; + + if (! playback->output->open_audio (FMT_S16_NE, sfinfo.samplerate, sfinfo.channels)) + { + close_sndfile (sndfile, vfsfile); + sndfile = NULL; + return; + } + + playback->set_params(playback, song_title, song_length, bit_rate, sfinfo.samplerate, sfinfo.channels); + g_free (song_title); + + playback->playing = TRUE; + + decode_thread = g_thread_self(); + playback->set_pb_ready(playback); + play_loop(playback); +} + +static void +play_pause (InputPlayback *playback, gshort p) +{ + playback->output->pause(p); +} + +static void +play_stop (InputPlayback *playback) +{ + if (decode_thread == NULL) + return; + + g_mutex_lock(decode_mutex); + playback->playing = FALSE; + g_mutex_unlock(decode_mutex); + g_cond_signal(decode_cond); + + g_thread_join (decode_thread); + + sndfile = NULL; + decode_thread = NULL; + seek_time = -1; +} + +static void +file_mseek (InputPlayback *playback, gulong millisecond) +{ + if (! sfinfo.seekable) + return; + + seek_time = (glong)millisecond; + + while (seek_time != -1) + g_usleep (80000); +} + +static void +file_seek (InputPlayback *playback, gint time) +{ + gulong millisecond = time * 1000; + file_mseek(playback, millisecond); +} + +static Tuple* +get_song_tuple (gchar *filename) +{ + Tuple *ti = aud_tuple_new_from_filename(filename); + fill_song_tuple(filename, ti); + return ti; +} + +static int is_our_file_from_vfs(char *filename, VFSFile *fin) +{ + SNDFILE *tmp_sndfile; + SF_INFO tmp_sfinfo; + + /* Have to open the file to see if libsndfile can handle it. */ + tmp_sndfile = sf_open_virtual (&sf_virtual_io, SFM_READ, &tmp_sfinfo, fin); + + if (!tmp_sndfile) + return FALSE; + + /* It can so close file and return TRUE. */ + sf_close (tmp_sndfile); + tmp_sndfile = NULL; + + return TRUE; +} + +static void plugin_about(void) +{ + static GtkWidget *box; + if (!box) + { + box = audacious_info_dialog(_("About sndfile plugin"), + _("Adapted for Audacious usage by Tony Vroon <chainsaw@gentoo.org>\n" + "from the xmms_sndfile plugin which is:\n" + "Copyright (C) 2000, 2002 Erik de Castro Lopo\n\n" + "This program is free software ; you can redistribute it and/or modify \n" + "it under the terms of the GNU General Public License as published by \n" + "the Free Software Foundation ; either version 2 of the License, or \n" + "(at your option) any later version. \n \n" + "This program is distributed in the hope that it will be useful, \n" + "but WITHOUT ANY WARRANTY ; without even the implied warranty of \n" + "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. \n" + "See the GNU General Public License for more details. \n\n" + "You should have received a copy of the GNU General Public \n" + "License along with this program ; if not, write to \n" + "the Free Software Foundation, Inc., \n" + "51 Franklin Street, Fifth Floor, \n" + "Boston, MA 02110-1301 USA"), + _("Ok"), FALSE, NULL, NULL); + g_signal_connect(G_OBJECT(box), "destroy", + (GCallback)gtk_widget_destroyed, &box); + } +} + +static gchar *fmts[] = { "aiff", "au", "raw", "wav", NULL }; + +InputPlugin sndfile_ip = { + .description = "sndfile plugin", + .init = plugin_init, + .about = plugin_about, + .is_our_file = is_our_file, + .play_file = play_start, + .stop = play_stop, + .pause = play_pause, + .seek = file_seek, + .cleanup = plugin_cleanup, + .get_song_tuple = get_song_tuple, + .is_our_file_from_vfs = is_our_file_from_vfs, + .vfs_extensions = fmts, + .mseek = file_mseek, +}; + +InputPlugin *sndfile_iplist[] = { &sndfile_ip, NULL }; + +SIMPLE_INPUT_PLUGIN(sndfile, sndfile_iplist)
