Mercurial > audlegacy-plugins
diff src/Input/cdaudio/cdaudio.c @ 0:13389e613d67 trunk
[svn] - initial import of audacious-plugins tree (lots to do)
author | nenolod |
---|---|
date | Mon, 18 Sep 2006 01:11:49 -0700 |
parents | |
children | 0ad4849f6219 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/Input/cdaudio/cdaudio.c Mon Sep 18 01:11:49 2006 -0700 @@ -0,0 +1,1354 @@ +/* XMMS - Cross-platform multimedia player + * Copyright (C) 1998-2003 Peter Alm, Mikael Alm, Olle Hallnas, + * Thomas Nilsson and 4Front Technologies + * Copyright (C) 1999-2003 Haavard Kvaalen <havardk@xmms.org> + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "cdaudio.h" + +#include <glib.h> +#include <glib/gi18n.h> +#include <glib/gprintf.h> +#include <string.h> +#include <stdlib.h> + +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> +#include <sys/ioctl.h> +#include <sys/stat.h> + +#include <libaudacious/configdb.h> +#include <libaudacious/util.h> +#include <libaudacious/titlestring.h> +#include "audacious/output.h" + +#ifdef CDROMSTOP +# define XMMS_STOP CDROMSTOP +#elif defined CDIOCSTOP +# define XMMS_STOP CDIOCSTOP +#else +# error "No stop ioctl" +#endif + +#ifdef CDIOCPAUSE +# define XMMS_PAUSE CDIOCPAUSE +#elif defined CDROMPAUSE +# define XMMS_PAUSE CDROMPAUSE +#else +# error "No pause ioctl" +#endif + +#ifdef CDIOCRESUME +# define XMMS_RESUME CDIOCRESUME +#elif defined CDROMRESUME +# define XMMS_RESUME CDROMRESUME +#else +# error "No resume ioctl" +#endif + +/* + * Distributions should not patch this, but instead use the + * --with-cdda-device=path and --with-cdda-dir=path configure options. + */ + +#ifndef CDDA_DEVICE +# ifdef HAVE_SYS_CDIO_H +# if defined(__FreeBSD__) && !defined(CDIOCREADAUDIO) +# define CDDA_DEVICE "/dev/acd0c" +# elif defined __FreeBSD__ +# define CDDA_DEVICE "/dev/acd0" +# elif defined __OpenBSD__ || defined __NetBSD__ +# define CDDA_DEVICE "/dev/cd0c" +# else +# define CDDA_DEVICE "/vol/dev/aliases/cdrom0" +# endif +# else +# define CDDA_DEVICE "/dev/cdrom" +# endif +#endif + +#ifndef CDDA_DIRECTORY +# ifdef HAVE_SYS_CDIO_H +# ifdef __FreeBSD__ +# define CDDA_DIRECTORY "/cdrom" +# elif defined __OpenBSD__ || defined __NetBSD__ +# define CDDA_DIRECTORY "/cdrom" +# else +# define CDDA_DIRECTORY "/cdrom/cdrom0" +# endif +# else +# define CDDA_DIRECTORY "/mnt/cdrom" +# endif +#endif + + + + +static TitleInput *cdda_get_tuple(cdda_disc_toc_t * toc, int track); +static gchar *get_song_title(TitleInput *tuple); +static gboolean stop_timeout(gpointer data); + +static void cdda_init(void); +static int is_our_file(char *filename); +static GList *scan_dir(char *dir); +static void play_file(char *filename); +static void stop(void); +static void cdda_pause(short p); +static void seek(int time); +static int get_time(void); +static void get_song_info(char *filename, char **title, int *length); +static TitleInput *get_song_tuple(char *filename); +static void get_volume(int *l, int *r); +static void set_volume(int l, int r); +static void cleanup(void); +void cdda_fileinfo(char *filename); + +InputPlugin cdda_ip = { + NULL, + NULL, + NULL, /* Description */ + cdda_init, + NULL, /* about */ + cdda_configure, + is_our_file, + scan_dir, + play_file, + stop, + cdda_pause, + seek, + NULL, /* set_eq */ + get_time, + get_volume, + set_volume, + cleanup, + NULL, /* obsolete */ + NULL, /* add_vis_pcm */ + NULL, /* set_info, filled in by xmms */ + NULL, /* set_info_text, filled in by xmms */ + get_song_info, + NULL, /* cdda_fileinfo, *//* file_info_box */ + NULL, /* output plugin handle */ + get_song_tuple +}; + +CDDAConfig cdda_cfg; + +static struct { + struct driveinfo drive; + cdda_disc_toc_t cd_toc; + int track; + int fd; + gboolean playing; +} cdda_playing; + +static struct { + GThread *thread; + gboolean audio_error, eof; + int seek; + +} dae_data; + +static gboolean is_paused; +static int pause_time; + +struct timeout { + int id; + char *device; +}; + +static GList *timeout_list; + +/* Time to delay stop command in 1/10 second */ +#define STOP_DELAY 20 + +InputPlugin * +get_iplugin_info(void) +{ + cdda_ip.description = g_strdup_printf(_("CD Audio Plugin")); + return &cdda_ip; +} + + + +#ifdef BEEP_CDROM_SOLARIS +/* + * Lowlevel cdrom access, Solaris style (Solaris, Linux) + */ + +static void +play_ioctl(struct cdda_msf *start, struct cdda_msf *end) +{ + struct cdrom_msf msf; + + msf.cdmsf_min0 = start->minute; + msf.cdmsf_sec0 = start->second; + msf.cdmsf_frame0 = start->frame; + msf.cdmsf_min1 = end->minute; + msf.cdmsf_sec1 = end->second; + msf.cdmsf_frame1 = end->frame; + ioctl(cdda_playing.fd, CDROMPLAYMSF, &msf); +} + +static int +get_current_frame(void) +{ + struct cdrom_subchnl subchnl; + + subchnl.cdsc_format = CDROM_MSF; + if (ioctl(cdda_playing.fd, CDROMSUBCHNL, &subchnl) < 0) + return -1; + + switch (subchnl.cdsc_audiostatus) { + case CDROM_AUDIO_COMPLETED: + case CDROM_AUDIO_ERROR: + return -1; + } + + return (LBA(subchnl.cdsc_absaddr.msf)); +} + +#if !defined(CDROMVOLREAD) +static int volume_left = 100, volume_right = 100; +#endif + +static void +drive_get_volume(int *l, int *r) +{ +#if defined(CDROMVOLREAD) + struct cdrom_volctrl vol; + + if (cdda_playing.fd != -1 && !ioctl(cdda_playing.fd, CDROMVOLREAD, &vol)) { + *l = (100 * vol.channel0) / 255; + *r = (100 * vol.channel1) / 255; + } +#if 0 + else if (cdda_playing.fd != -1) + g_message("CDROMVOLREAD failed"); +#endif +#else + *l = volume_left; + *r = volume_right; +#endif +} + +static void +drive_set_volume(int l, int r) +{ + struct cdrom_volctrl vol; + + if (cdda_playing.fd != -1) { + vol.channel0 = vol.channel2 = (l * 255) / 100; + vol.channel1 = vol.channel3 = (r * 255) / 100; + ioctl(cdda_playing.fd, CDROMVOLCTRL, &vol); + } +#if !defined(CDROMVOLREAD) + volume_left = l; + volume_right = r; +#endif +} + +#ifdef CDROMREADAUDIO +int +read_audio_data(int fd, int pos, int num, void *buf) +{ + struct cdrom_read_audio cdra; + +#if 1 + cdra.addr.lba = pos - CDDA_MSF_OFFSET; + cdra.addr_format = CDROM_LBA; +#else + cdra.addr.msf.minute = pos / (60 * 75); + cdra.addr.msf.second = (pos / 75) % 60; + cdra.addr.msf.frame = pos % 75; + cdra.addr_format = CDROM_MSF; +#endif + + cdra.nframes = num; + cdra.buf = buf; + + if (ioctl(fd, CDROMREADAUDIO, &cdra) < 0) + return -errno; + + return cdra.nframes; +} +#endif /* CDROMREADAUDIO */ + +#if defined(CDROMCDDA) +int +read_audio_data(int fd, int pos, int num, void *buf) +{ + struct cdrom_cdda cdra; + + cdra.cdda_addr = pos - CDDA_MSF_OFFSET; + cdra.cdda_length = num; + cdra.cdda_data = buf; + cdra.cdda_subcode = CDROM_DA_NO_SUBCODE; + if (ioctl(fd, CDROMCDDA, &cdra) < 0) + return -errno; + + return cdra.cdda_length; +} +#endif + +static gboolean +cdda_get_toc_lowlevel(int fd, cdda_disc_toc_t * info) +{ + struct cdrom_tochdr tochdr; + struct cdrom_tocentry tocentry; + int i; + + + + if (ioctl(fd, CDROMREADTOCHDR, &tochdr)) + return FALSE; + + for (i = tochdr.cdth_trk0; i <= tochdr.cdth_trk1; i++) { + tocentry.cdte_format = CDROM_MSF; + tocentry.cdte_track = i; + if (ioctl(fd, CDROMREADTOCENTRY, &tocentry)) + return FALSE; + info->track[i].minute = tocentry.cdte_addr.msf.minute; + info->track[i].second = tocentry.cdte_addr.msf.second; + info->track[i].frame = tocentry.cdte_addr.msf.frame; + info->track[i].flags.data_track = + tocentry.cdte_ctrl == CDROM_DATA_TRACK; + + } + + /* Get the leadout track */ + tocentry.cdte_track = CDROM_LEADOUT; + tocentry.cdte_format = CDROM_MSF; + + if (ioctl(fd, CDROMREADTOCENTRY, &tocentry)) + return FALSE; + info->leadout.minute = tocentry.cdte_addr.msf.minute; + info->leadout.second = tocentry.cdte_addr.msf.second; + info->leadout.frame = tocentry.cdte_addr.msf.frame; + + info->first_track = tochdr.cdth_trk0; + info->last_track = tochdr.cdth_trk1; + + return TRUE; +} + +#endif + +#ifdef BEEP_CDROM_BSD +/* + * Lowlevel cdrom access, BSD style (FreeBSD, OpenBSD, NetBSD, Darwin) + */ + +static void +play_ioctl(struct cdda_msf *start, struct cdda_msf *end) +{ + struct ioc_play_msf msf; + + msf.start_m = start->minute; + msf.start_s = start->second; + msf.start_f = start->frame; + msf.end_m = end->minute; + msf.end_s = end->second; + msf.end_f = end->frame; + ioctl(cdda_playing.fd, CDIOCPLAYMSF, &msf); +} + +static int +get_current_frame(void) +{ + struct ioc_read_subchannel subchnl; + struct cd_sub_channel_info subinfo; + subchnl.address_format = CD_MSF_FORMAT; + subchnl.data_format = CD_CURRENT_POSITION; + subchnl.track = 0; + subchnl.data_len = sizeof(subinfo); + subchnl.data = &subinfo; + if (ioctl(cdda_playing.fd, CDIOCREADSUBCHANNEL, &subchnl) < 0) + return -1; + +#ifdef BEEP_CDROM_BSD_DARWIN + return ((subchnl.data->what.position.absaddr[1] * 60 + subchnl.data->what.position.absaddr[2]) * 75 + + subchnl.data->what.position.absaddr[3]); +#else + return (LBA(subchnl.data->what.position.absaddr.msf)); +#endif +} + +static void +drive_get_volume(int *l, int *r) +{ + struct ioc_vol vol; + + if (cdda_playing.fd != -1) { + ioctl(cdda_playing.fd, CDIOCGETVOL, &vol); + *l = (100 * vol.vol[0]) / 255; + *r = (100 * vol.vol[1]) / 255; + } +} + +static void +drive_set_volume(int l, int r) +{ + struct ioc_vol vol; + + if (cdda_playing.fd != -1) { + vol.vol[0] = vol.vol[2] = (l * 255) / 100; + vol.vol[1] = vol.vol[3] = (r * 255) / 100; + ioctl(cdda_playing.fd, CDIOCSETVOL, &vol); + } +} + +#if defined(__FreeBSD__) && !defined(CDIOCREADAUDIO) +int +read_audio_data(int fd, int pos, int num, void *buf) +{ + int bs = CD_FRAMESIZE_RAW; + + if (ioctl(fd, CDRIOCSETBLOCKSIZE, &bs) == -1) + return -1; + if (pread(fd, buf, num * bs, (pos - 150) * bs) != num * bs) + return -1; + + return num; +} +#endif + +#if defined(CDIOCREADAUDIO) +int +read_audio_data(int fd, int pos, int num, void *buf) +{ + struct ioc_read_audio cdra; + + cdra.address.lba = pos - CDDA_MSF_OFFSET; + cdra.address_format = CD_LBA_FORMAT; + cdra.nframes = num; + cdra.buffer = buf; + + if (ioctl(fd, CDIOCREADAUDIO, &cdra) < 0) + return -errno; + + return cdra.nframes; +} +#endif /* CDIOCREADAUDIO */ + +#ifdef BEEP_CDROM_BSD_NETBSD /* NetBSD, OpenBSD */ + +static gboolean +cdda_get_toc_lowlevel(int fd, cdda_disc_toc_t * info) +{ + struct ioc_toc_header tochdr; + struct ioc_read_toc_entry tocentry; + struct cd_toc_entry tocentrydata; + int i; + + if (ioctl(fd, CDIOREADTOCHEADER, &tochdr)) + return FALSE; + + for (i = tochdr.starting_track; i <= tochdr.ending_track; i++) { + tocentry.address_format = CD_MSF_FORMAT; + + tocentry.starting_track = i; + tocentry.data = &tocentrydata; + tocentry.data_len = sizeof(tocentrydata); + if (ioctl(fd, CDIOREADTOCENTRYS, &tocentry)) + return FALSE; + info->track[i].minute = tocentry.data->addr.msf.minute; + info->track[i].second = tocentry.data->addr.msf.second; + info->track[i].frame = tocentry.data->addr.msf.frame; + info->track[i].flags.data_track = (tocentry.data->control & 4) == 4; + } + + /* Get the leadout track */ + tocentry.address_format = CD_MSF_FORMAT; + + tocentry.starting_track = 0xAA; + tocentry.data = &tocentrydata; + tocentry.data_len = sizeof(tocentrydata); + if (ioctl(fd, CDIOREADTOCENTRYS, &tocentry)) + return FALSE; + info->leadout.minute = tocentry.data->addr.msf.minute; + info->leadout.second = tocentry.data->addr.msf.second; + info->leadout.frame = tocentry.data->addr.msf.frame; + + info->first_track = tochdr.starting_track; + info->last_track = tochdr.ending_track; + + return TRUE; +} + +#elif defined(BEEP_CDROM_BSD_DARWIN) + +static gboolean +cdda_get_toc_lowlevel(int fd, cdda_disc_toc_t * info) +{ + struct ioc_toc_header tochdr; + struct ioc_read_toc_entry tocentry; + int i; + + if (ioctl(fd, CDIOREADTOCHEADER, &tochdr)) + return FALSE; + + for (i = tochdr.starting_track; i <= tochdr.ending_track; i++) { + tocentry.address_format = CD_MSF_FORMAT; + + tocentry.starting_track = i; + if (ioctl(fd, CDIOREADTOCENTRYS, &tocentry)) + return FALSE; + info->track[i].minute = tocentry.data->addr[1]; + info->track[i].second = tocentry.data->addr[2]; + info->track[i].frame = tocentry.data->addr[3]; + info->track[i].flags.data_track = (tocentry.data->control & 4) == 4; + } + + /* Get the leadout track */ + tocentry.address_format = CD_MSF_FORMAT; + + tocentry.starting_track = 0xAA; + if (ioctl(fd, CDIOREADTOCENTRYS, &tocentry)) + return FALSE; + info->leadout.minute = tocentry.data->addr[1]; + info->leadout.second = tocentry.data->addr[2]; + info->leadout.frame = tocentry.data->addr[3]; + + return TRUE; +} + +#else /* FreeBSD */ + +static gboolean +cdda_get_toc_lowlevel(int fd, cdda_disc_toc_t * info) +{ + struct ioc_toc_header tochdr; + struct ioc_read_toc_single_entry tocentry; + int i; + + if (ioctl(fd, CDIOREADTOCHEADER, &tochdr)) + return FALSE; + + for (i = tochdr.starting_track; i <= tochdr.ending_track; i++) { + tocentry.address_format = CD_MSF_FORMAT; + + tocentry.track = i; + if (ioctl(fd, CDIOREADTOCENTRY, &tocentry)) + return FALSE; + info->track[i].minute = tocentry.entry.addr.msf.minute; + info->track[i].second = tocentry.entry.addr.msf.second; + info->track[i].frame = tocentry.entry.addr.msf.frame; + info->track[i].flags.data_track = (tocentry.entry.control & 4) == 4; + } + + /* Get the leadout track */ + tocentry.address_format = CD_MSF_FORMAT; + + tocentry.track = 0xAA; + if (ioctl(fd, CDIOREADTOCENTRY, &tocentry)) + return FALSE; + info->leadout.minute = tocentry.entry.addr.msf.minute; + info->leadout.second = tocentry.entry.addr.msf.second; + info->leadout.frame = tocentry.entry.addr.msf.frame; + + info->first_track = tochdr.starting_track; + info->last_track = tochdr.ending_track; + + return TRUE; +} +#endif + +#endif + + + + + + + + + + + +extern gboolean +is_mounted(const char *device_name) +{ +#if defined(HAVE_MNTENT_H) || defined(HAVE_GETMNTINFO) + char devname[256]; + struct stat st; +#if defined(HAVE_MNTENT_H) + FILE *mounts; + struct mntent *mnt; +#elif defined(HAVE_GETMNTINFO) +#if defined __NetBSD__ && HAVE_STATVFS + struct statvfs *fsp; +#else + struct statfs *fsp; +#endif + int entries; +#endif + + if (lstat(device_name, &st) < 0) + return -1; + + if (S_ISLNK(st.st_mode)) + readlink(device_name, devname, 256); + else + strncpy(devname, device_name, 256); + +#if defined(HAVE_MNTENT_H) + if ((mounts = setmntent(MOUNTED, "r")) == NULL) + return TRUE; + + while ((mnt = getmntent(mounts)) != NULL) { + if (strcmp(mnt->mnt_fsname, devname) == 0) { + endmntent(mounts); + return TRUE; + } + } + endmntent(mounts); +#elif defined(HAVE_GETMNTINFO) + entries = getmntinfo(&fsp, MNT_NOWAIT); + if (entries < 0) + return FALSE; + + while (entries-- > 0) { + if (!strcmp(fsp->f_mntfromname, devname)) + return TRUE; + fsp++; + } +#endif +#endif + return FALSE; +} + + +gboolean +cdda_get_toc(cdda_disc_toc_t * info, const char *device) +{ + gboolean retv = FALSE; + int fd; + + if (is_mounted(device)) + return FALSE; + + if ((fd = open(device, CDOPENFLAGS)) == -1) + return FALSE; + + memset(info, 0, sizeof(cdda_disc_toc_t)); + + retv = cdda_get_toc_lowlevel(fd, info); + close(fd); + + return retv; +} + +static void +cdda_init(void) +{ + ConfigDb *db; + struct driveinfo *drive = g_malloc0(sizeof(struct driveinfo)); + int ndrives = 1, i; + + cdda_playing.fd = -1; + memset(&cdda_cfg, 0, sizeof(CDDAConfig)); + +#ifdef HAVE_OSS + drive->mixer = CDDA_MIXER_OSS; + drive->oss_mixer = SOUND_MIXER_CD; +#endif + + db = bmp_cfg_db_open(); + + /* These names are used for backwards compatibility */ + bmp_cfg_db_get_string(db, "CDDA", "device", &drive->device); + bmp_cfg_db_get_string(db, "CDDA", "directory", &drive->directory); + bmp_cfg_db_get_int(db, "CDDA", "mixer", &drive->mixer); + bmp_cfg_db_get_int(db, "CDDA", "readmode", &drive->dae); + + if (!drive->device) + drive->device = g_strdup(CDDA_DEVICE); + if (!drive->directory) + drive->directory = g_strdup(CDDA_DIRECTORY); + + cdda_cfg.drives = g_list_append(cdda_cfg.drives, drive); + + bmp_cfg_db_get_int(db, "CDDA", "num_drives", &ndrives); + for (i = 1; i < ndrives; i++) { + char label[20]; + drive = g_malloc0(sizeof(struct driveinfo)); + + sprintf(label, "device%d", i); + bmp_cfg_db_get_string(db, "CDDA", label, &drive->device); + + sprintf(label, "directory%d", i); + bmp_cfg_db_get_string(db, "CDDA", label, &drive->directory); + + sprintf(label, "mixer%d", i); + bmp_cfg_db_get_int(db, "CDDA", label, &drive->mixer); + + sprintf(label, "readmode%d", i); + bmp_cfg_db_get_int(db, "CDDA", label, &drive->dae); + + cdda_cfg.drives = g_list_append(cdda_cfg.drives, drive); + } + bmp_cfg_db_get_bool(db, "CDDA", "title_override", + &cdda_cfg.title_override); + bmp_cfg_db_get_string(db, "CDDA", "name_format", &cdda_cfg.name_format); + bmp_cfg_db_get_bool(db, "CDDA", "use_cddb", &cdda_cfg.use_cddb); + bmp_cfg_db_get_string(db, "CDDA", "cddb_server", &cdda_cfg.cddb_server); +#ifdef WITH_CDINDEX + bmp_cfg_db_get_bool(db, "CDDA", "use_cdin", &cdda_cfg.use_cdin); +#else + cdda_cfg.use_cdin = FALSE; +#endif + bmp_cfg_db_get_string(db, "CDDA", "cdin_server", &cdda_cfg.cdin_server); + bmp_cfg_db_close(db); + + if (!cdda_cfg.cdin_server) + cdda_cfg.cdin_server = g_strdup("www.cdindex.org"); + if (!cdda_cfg.cddb_server) + cdda_cfg.cddb_server = g_strdup(CDDB_DEFAULT_SERVER); + if (!cdda_cfg.name_format) + cdda_cfg.name_format = g_strdup("%p - %t"); +} + +struct driveinfo * +cdda_find_drive(char *filename) +{ + GList *node; + + // FIXME: Will always return the first drive + + for (node = cdda_cfg.drives; node; node = node->next) { + struct driveinfo *d = node->data; + if (!strncmp(d->directory, filename, strlen(d->directory))) + return d; + } + + return NULL; + +} + +static void +timeout_destroy(struct timeout *entry) +{ + g_free(entry->device); + g_free(entry); + timeout_list = g_list_remove(timeout_list, entry); +} + +static void +timeout_remove_for_device(char *device) +{ + GList *node; + + for (node = timeout_list; node; node = node->next) { + struct timeout *t = node->data; + + if (!strcmp(t->device, device)) { + gtk_timeout_remove(t->id); + timeout_destroy(t); + return; + } + } + +} + +static void +cleanup(void) +{ + GList *node; + struct driveinfo *drive; + + g_free(cdda_ip.description); + cdda_ip.description = NULL; + + if (cdda_cfg.drives) { + for (node = g_list_first(cdda_cfg.drives); node; node = node->next) { + drive = (struct driveinfo *)node->data; + if (!drive) + continue; + + if (drive->device) + free(drive->device); + + if (drive->directory) + free(drive->directory); + + free(drive); + } + + g_list_free(cdda_cfg.drives); + cdda_cfg.drives = NULL; + } + + if (cdda_cfg.name_format) { + free(cdda_cfg.name_format); + cdda_cfg.name_format = NULL; + } + + if (cdda_cfg.cddb_server) { + free(cdda_cfg.cddb_server); + cdda_cfg.cddb_server = NULL; + } + + if (cdda_cfg.cdin_server) { + free(cdda_cfg.cdin_server); + cdda_cfg.cdin_server = NULL; + } + + while (timeout_list) { + struct timeout *t = timeout_list->data; + gtk_timeout_remove(t->id); + stop_timeout(t); + timeout_destroy(t); + } + cddb_quit(); +} + +static int +is_our_file(char *filename) +{ + char *ext = ".cda"; + + if (cdda_find_drive(filename) == NULL) { + return FALSE; + } + + if (g_str_has_suffix(filename, ext)) { + return TRUE; + } + return FALSE; +} + + +static GList * +scan_dir(char *dir) +{ + GList *list = NULL; + int i; + cdda_disc_toc_t toc; + struct driveinfo *drive; + + if ((drive = cdda_find_drive(dir)) == NULL) + return NULL; + + if (!cdda_get_toc(&toc, drive->device)) + return NULL; + + for (i = toc.last_track; i >= toc.first_track; i--) + if (!toc.track[i].flags.data_track) { + list = g_list_prepend(list, g_strdup_printf("Track %02d.cda", i)); + } + return list; +} + +guint +cdda_calculate_track_length(cdda_disc_toc_t * toc, int track) +{ + if (track == toc->last_track) + return (LBA(toc->leadout) - LBA(toc->track[track])); + else + return (LBA(toc->track[track + 1]) - LBA(toc->track[track])); +} + +static void * +dae_play_loop(void *arg) +{ + char *buffer = g_malloc(CD_FRAMESIZE_RAW * CDDA_DAE_FRAMES); + int pos = LBA(cdda_playing.cd_toc.track[cdda_playing.track]); + int end, frames; + + if (cdda_playing.track == cdda_playing.cd_toc.last_track) + end = LBA(cdda_playing.cd_toc.leadout); + else + end = LBA(cdda_playing.cd_toc.track[cdda_playing.track + 1]); + + while (cdda_playing.playing) { + int left; + char *data; + + if (dae_data.seek != -1) { + cdda_ip.output->flush(dae_data.seek * 1000); + pos = LBA(cdda_playing.cd_toc.track[cdda_playing.track]) + + dae_data.seek * 75; + dae_data.seek = -1; + dae_data.eof = FALSE; + } + frames = MIN(CDDA_DAE_FRAMES, end - pos); + if (frames == 0) + dae_data.eof = TRUE; + + if (dae_data.eof) { + xmms_usleep(30000); + continue; + } + + frames = read_audio_data(cdda_playing.fd, pos, frames, buffer); + if (frames <= 0) { + int err = -frames; + if (err == EOPNOTSUPP) + dae_data.eof = TRUE; + else { + /* + * If the read failed, skip ahead to + * avoid getting stuck on scratches + * and such. + */ + g_message("read_audio_data() failed: %s (%d)", + strerror(err), err); + pos += MIN(CDDA_DAE_FRAMES, end - pos); + } + continue; + } + left = frames * CD_FRAMESIZE_RAW; + data = buffer; + while (cdda_playing.playing && left > 0 && dae_data.seek == -1) { + int cur = MIN(512 * 2 * 2, left); + cdda_ip.add_vis_pcm(cdda_ip.output->written_time(), + FMT_S16_LE, 2, cur, data); + while (cdda_ip.output->buffer_free() < cur && + cdda_playing.playing && dae_data.seek == -1) + xmms_usleep(30000); + if (cdda_playing.playing && dae_data.seek == -1) + produce_audio(cdda_ip.output->written_time(), FMT_S16_LE, 2, cur, data, &cdda_playing.playing); + left -= cur; + data += cur; + } + pos += frames; + } + + cdda_ip.output->buffer_free(); + cdda_ip.output->buffer_free(); + g_free(buffer); + + g_thread_exit(NULL); + return NULL; +} + +static void +dae_play(void) +{ + if (cdda_ip.output->open_audio(FMT_S16_LE, 44100, 2) == 0) { + dae_data.audio_error = TRUE; + cdda_playing.playing = FALSE; + return; + } + dae_data.seek = -1; + dae_data.eof = FALSE; + dae_data.audio_error = FALSE; + dae_data.thread = g_thread_create(dae_play_loop, NULL, TRUE, NULL); +} + +static void +play_file(char *filename) +{ + char *tmp; + struct driveinfo *drive; + int track; + int track_len; + +// g_message(g_strdup_printf("** CD_AUDIO: trying to play file %s",filename)); + + if ((drive = cdda_find_drive(filename)) == NULL) { +// g_message("** CD_AUDIO: find drive check failed"); + return; + } + if (is_mounted(drive->device)) { +// g_message("** CD_AUDIO: drive is mounted"); + return; + } + tmp = strrchr(filename, '/'); + if (tmp) + tmp++; + else + tmp = filename; + + if (!sscanf(tmp, "Track %d.cda", &track)) { +// g_message("** CD_AUDIO: filename check failed"); + return; + } + + if (!cdda_get_toc(&cdda_playing.cd_toc, drive->device) || + cdda_playing.cd_toc.track[track].flags.data_track || + track < cdda_playing.cd_toc.first_track || + track > cdda_playing.cd_toc.last_track) { +// g_message("** CD_AUDIO: toc check failed"); + return; + } + + if ((cdda_playing.fd = open(drive->device, CDOPENFLAGS)) == -1) { +// g_message("** CD_AUDIO: device open failed"); + return; + } + track_len = cdda_calculate_track_length(&cdda_playing.cd_toc, track); + cdda_ip.set_info(get_song_title(cdda_get_tuple(&cdda_playing.cd_toc, track)), + (track_len * 1000) / 75, 44100 * 2 * 2 * 8, 44100, 2); + + memcpy(&cdda_playing.drive, drive, sizeof(struct driveinfo)); +#ifndef CDDA_HAS_READAUDIO + cdda_playing.drive.dae = FALSE; +#endif + + cdda_playing.track = track; + + is_paused = FALSE; + timeout_remove_for_device(drive->device); + + cdda_playing.playing = TRUE; + if (drive->dae) + dae_play(); + else + seek(0); +} + +static TitleInput * +cdda_get_tuple(cdda_disc_toc_t * toc, int track) +{ + G_LOCK_DEFINE_STATIC(tuple); + + static guint32 cached_id; + static cdinfo_t cdinfo; + static gchar *performer, *album_name, *track_name; + TitleInput *tuple = NULL; + guint32 disc_id; + + disc_id = cdda_cddb_compute_discid(toc); + + /* + * We want to avoid looking up a album from two threads simultaneously. + * This can happen since we are called both from the main-thread and + * from the playlist-thread. + */ + + G_LOCK(tuple); + if (!(disc_id == cached_id && cdinfo.is_valid)) { + /* + * We try to look up the disc again if the info is not + * valid. The user might have configured a new server + * in the meantime. + */ + cdda_cdinfo_flush(&cdinfo); + cached_id = disc_id; + + if (!cdda_cdinfo_read_file(disc_id, &cdinfo)) { + if (cdda_cfg.use_cddb) + cdda_cddb_get_info(toc, &cdinfo); + if (cdinfo.is_valid) + cdda_cdinfo_write_file(disc_id, &cdinfo); + } + } + + tuple = bmp_title_input_new(); + cdda_cdinfo_get(&cdinfo, track, &performer, &album_name, &track_name); + G_UNLOCK(tuple); + + tuple->performer = g_strdup(performer); + tuple->album_name = g_strdup(album_name); + tuple->track_name = g_strdup(track_name); + tuple->track_number = (track); + tuple->file_name = g_strdup(tuple->file_path); + tuple->file_path = g_strdup_printf(_("CD Audio Track %02u"), track); + tuple->file_ext = "cda"; + tuple->length = ((cdda_calculate_track_length(toc, track) * 1000) / 75); + + if (!tuple->track_name) + tuple->track_name = g_strdup_printf(_("CD Audio Track %02u"), track); + + return tuple; +} + +static gchar * +get_song_title(TitleInput *tuple) +{ + return xmms_get_titlestring(cdda_cfg.title_override ? + cdda_cfg.name_format : + xmms_get_gentitle_format(), tuple); +} + +static gboolean +stop_timeout(gpointer data) +{ + int fd; + struct timeout *to = data; + + fd = open(to->device, CDOPENFLAGS); + if (fd != -1) { + ioctl(fd, XMMS_STOP, 0); + close(fd); + } + timeout_destroy(to); + return FALSE; +} + +static void +stop(void) +{ + struct timeout *to_info; + if (cdda_playing.fd < 0) + return; + + cdda_playing.playing = FALSE; + + if (cdda_playing.drive.dae) { + g_thread_join(dae_data.thread); + cdda_ip.output->close_audio(); + } + else + ioctl(cdda_playing.fd, XMMS_PAUSE, 0); + + close(cdda_playing.fd); + cdda_playing.fd = -1; + + if (!cdda_playing.drive.dae) { + to_info = g_malloc(sizeof(*to_info)); + to_info->device = g_strdup(cdda_playing.drive.device); + to_info->id = gtk_timeout_add(STOP_DELAY * 100, stop_timeout, + to_info); + timeout_list = g_list_prepend(timeout_list, to_info); + } +} + +static void +cdda_pause(short p) +{ + if (cdda_playing.drive.dae) { + cdda_ip.output->pause(p); + return; + } + if (p) { + pause_time = get_time(); + ioctl(cdda_playing.fd, XMMS_PAUSE, 0); + } + else { + ioctl(cdda_playing.fd, XMMS_RESUME, 0); + pause_time = -1; + } + is_paused = p; +} + + + +static void +seek(int time) +{ + struct cdda_msf *end, start; + int track = cdda_playing.track; + +// g_message("** CD_AUDIO: seeking..."); + if (cdda_playing.drive.dae) { + dae_data.seek = time; + while (dae_data.seek != -1) + xmms_usleep(20000); + return; + } + + start.minute = (cdda_playing.cd_toc.track[track].minute * 60 + + cdda_playing.cd_toc.track[track].second + time) / 60; + start.second = (cdda_playing.cd_toc.track[track].second + time) % 60; + start.frame = cdda_playing.cd_toc.track[track].frame; + if (track == cdda_playing.cd_toc.last_track) + end = &cdda_playing.cd_toc.leadout; + else + end = &cdda_playing.cd_toc.track[track + 1]; + + play_ioctl(&start, end); + + if (is_paused) { + cdda_pause(TRUE); + pause_time = time * 1000; + } +} + +static int +get_time_analog(void) +{ + int frame, start_frame, length; + int track = cdda_playing.track; + + if (is_paused && pause_time != -1) + return pause_time; + + frame = get_current_frame(); + + if (frame == -1) + return -1; + + start_frame = LBA(cdda_playing.cd_toc.track[track]); + length = cdda_calculate_track_length(&cdda_playing.cd_toc, track); + + if (frame - start_frame >= length - 20) /* 20 seems to work better */ + return -1; + + return ((frame - start_frame) * 1000) / 75; +} + +static int +get_time_dae(void) +{ + if (dae_data.audio_error) + return -2; + if (!cdda_playing.playing || + (dae_data.eof && !cdda_ip.output->buffer_playing())) + return -1; + return cdda_ip.output->output_time(); +} + +static int +get_time(void) +{ + if (cdda_playing.fd == -1) + return -1; + + if (cdda_playing.drive.dae) + return get_time_dae(); + else + return get_time_analog(); +} + +static void +get_song_info(char *filename, char **title, int *len) +{ + cdda_disc_toc_t toc; + int t; + char *tmp; + struct driveinfo *drive; + TitleInput *tuple; + + *title = NULL; + *len = -1; + +// g_message("** CD_AUDIO: getting song info"); + + if ((drive = cdda_find_drive(filename)) == NULL) + return; + + tmp = strrchr(filename, '/'); + if (tmp) + tmp++; + else + tmp = filename; + + if (!sscanf(tmp, "Track %d.cda", &t)) + return; + if (!cdda_get_toc(&toc, drive->device)) + return; + if (t < toc.first_track || t > toc.last_track + || toc.track[t].flags.data_track) + return; + + if ((tuple = cdda_get_tuple(&toc, t)) != NULL) { + *len = tuple->length; + *title = get_song_title(tuple); + } + bmp_title_input_free(tuple); +} + +static TitleInput * +get_song_tuple(char *filename) +{ + cdda_disc_toc_t toc; + int t; + char *tmp; + struct driveinfo *drive; + TitleInput *tuple = NULL; + +// g_message("** CD_AUDIO: getting song info"); + + if ((drive = cdda_find_drive(filename)) == NULL) + return tuple; + + tmp = strrchr(filename, '/'); + if (tmp) + tmp++; + else + tmp = filename; + + if (!sscanf(tmp, "Track %d.cda", &t)) + return tuple; + if (!cdda_get_toc(&toc, drive->device)) + return tuple; + if (t < toc.first_track || t > toc.last_track + || toc.track[t].flags.data_track) + return tuple; + + tuple = cdda_get_tuple(&toc, t); + return tuple; +} + +#ifdef HAVE_OSS +static void +oss_get_volume(int *l, int *r, int mixer_line) +{ + int fd, v; + + fd = open(DEV_MIXER, O_RDONLY); + if (fd != -1) { + ioctl(fd, MIXER_READ(mixer_line), &v); + *r = (v & 0xFF00) >> 8; + *l = (v & 0x00FF); + close(fd); + } +} + +static void +oss_set_volume(int l, int r, int mixer_line) +{ + int fd, v; + + fd = open(DEV_MIXER, O_RDONLY); + if (fd != -1) { + v = (r << 8) | l; + ioctl(fd, MIXER_WRITE(mixer_line), &v); + close(fd); + } +} +#else +static void +oss_get_volume(int *l, int *r, int mixer_line) +{ +} +static void +oss_set_volume(int l, int r, int mixer_line) +{ +} +#endif + + +static void +get_volume(int *l, int *r) +{ + if (cdda_playing.drive.dae) + cdda_ip.output->get_volume(l, r); + else if (cdda_playing.drive.mixer == CDDA_MIXER_OSS) + oss_get_volume(l, r, cdda_playing.drive.oss_mixer); + else if (cdda_playing.drive.mixer == CDDA_MIXER_DRIVE) + drive_get_volume(l, r); +} + +static void +set_volume(int l, int r) +{ + if (cdda_playing.drive.dae) + cdda_ip.output->set_volume(l, r); + else if (cdda_playing.drive.mixer == CDDA_MIXER_OSS) + oss_set_volume(l, r, cdda_playing.drive.oss_mixer); + else if (cdda_playing.drive.mixer == CDDA_MIXER_DRIVE) + drive_set_volume(l, r); +}