Mercurial > audlegacy-plugins
view src/cdaudio/cdaudio.c @ 972:cf7021ca4e7b trunk
[svn] Add lastfm:// transport, an abstract VFS class which derives from curl
to provide lastfm radio support. Written by majeru with some cleanups
by me. Most last.fm metadata support isn't yet implemented, however, and
will need to be done by majeru. ;)
| author | nenolod |
|---|---|
| date | Sun, 22 Apr 2007 04:16:08 -0700 |
| parents | 914c96de3244 |
| children | daa0323924d6 |
line wrap: on
line source
/* 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 <audacious/i18n.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 <audacious/configdb.h> #include <audacious/util.h> #include <audacious/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(InputPlayback *playback); static void stop(InputPlayback *playback); static void cdda_pause(InputPlayback *playback, short p); static void seek(InputPlayback *playback, int time); static int get_time(InputPlayback *playback); static void get_song_info(char *filename, char **title, int *length); static TitleInput *get_song_tuple(char *filename); static gint get_volume(int *l, int *r); static gint 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); if (bmp_cfg_db_get_int(db, "CDDA", "readmode", &drive->dae)==FALSE) drive->dae = 1; /* default to digital */ 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); bmp_cfg_db_close(db); 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; } 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 *data) { InputPlayback *playback = data; 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) { playback->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(playback->output->written_time(), FMT_S16_LE, 2, cur, data); while (playback->output->buffer_free() < cur && cdda_playing.playing && dae_data.seek == -1) xmms_usleep(30000); if (cdda_playing.playing && dae_data.seek == -1) produce_audio(playback->output->written_time(), FMT_S16_LE, 2, cur, data, &cdda_playing.playing); left -= cur; data += cur; } pos += frames; } playback->output->buffer_free(); playback->output->buffer_free(); g_free(buffer); g_thread_exit(NULL); return NULL; } static void dae_play(InputPlayback *playback) { if (playback->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, playback, TRUE, NULL); } static void play_file(InputPlayback *playback) { char *filename = playback->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(playback); else seek(playback, 0); return; } 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); tuple->genre = g_strdup(cdinfo.genre); tuple->year = cdinfo.year; 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(InputPlayback * playback) { 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); playback->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(InputPlayback *playback, short p) { if (cdda_playing.drive.dae) { playback->output->pause(p); return; } if (p) { pause_time = get_time(playback); ioctl(cdda_playing.fd, XMMS_PAUSE, 0); } else { ioctl(cdda_playing.fd, XMMS_RESUME, 0); pause_time = -1; } is_paused = p; } static void seek(InputPlayback *data, 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(data, 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(InputPlayback *playback) { if (dae_data.audio_error) return -2; if (!cdda_playing.playing || (dae_data.eof && !playback->output->buffer_playing())) return -1; return playback->output->output_time(); } static int get_time(InputPlayback *playback) { if (cdda_playing.fd == -1) return -1; if (cdda_playing.drive.dae) return get_time_dae(playback); 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 gint get_volume(int *l, int *r) { if (cdda_playing.drive.dae) return 0; 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); return 1; } static gint set_volume(int l, int r) { if (cdda_playing.drive.dae) return 0; 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); return 1; }
