/*\ XMMS - Cross-platform multimedia player |*| Copyright (C) 1998-1999 Peter Alm, Mikael Alm, Olle Hallnas, |*| Thomas Nilsson and 4Front Technologies |*| |*| CD audio data input plugin by Willem Monsuwe (willem@stack.nl) |*| |*| 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. \*/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "cdread.h" /*\ Global vars \*/ static gint32 cur_lba, end_lba, seek_lba; struct cd_struct *cd_list = 0, *cd_cur = 0, *cd_next = 0; pthread_mutex_t cd_list_mutex = PTHREAD_MUTEX_INITIALIZER; static gint running_threads = 0; static gint next_trk = -1, cur_trk; struct cd_cfg cd_cfg; #ifdef HAVE_LINUX_CDROM_H #include #elif defined(HAVE_SYS_CDIO_H) #include #endif #ifndef CD_FRAMESIZE_RAW #define CD_FRAMESIZE_RAW 2352 #endif /*\ Neat.. Three OSes with three different ways of talking to CDROM drives.. \*/ #if defined(HAVE_SYS_CDIO_H) && (defined(__FreeBSD__) || defined(__OpenBSD__)) #include "cdrombsd.h" #elif defined(__SOLARIS__) || defined(__Solaris__) || defined(__solaris__) || defined(__sun__) || defined(sun) #include "cdromsolaris.h" #else /*\ Linux ?? \*/ #include "cdromlinux.h" #endif InputPlugin cd_ip; static void cd_init(void); static gint cd_our_file(gchar *); static GList *cd_scan_dir(gchar *); static void cd_play_file(gchar *); static void cd_stop(void); static void cd_pause(short); static void cd_seek(gint); static gint cd_get_time(void); static void cd_song_info(gchar *, gchar **, gint *); void cd_set_eq(int on, float total_gain, float *gain); #ifdef HAVE_PREPLAY_FILE static gint cd_preplay_file(gchar *); #endif static void get_volume(gint *l, gint *r); static void set_volume(gint l, gint r); InputPlugin * get_iplugin_info(void) { memset(&cd_ip, 0, sizeof(cd_ip)); cd_ip.description = "AudioCD Reader " VERSION; cd_ip.init = cd_init; cd_ip.configure = cd_configure; cd_ip.is_our_file = cd_our_file; cd_ip.scan_dir = cd_scan_dir; cd_ip.play_file = cd_play_file; cd_ip.stop = cd_stop; cd_ip.pause = cd_pause; cd_ip.seek = cd_seek; cd_ip.get_time = cd_get_time; cd_ip.get_song_info = cd_song_info; cd_ip.file_info_box = cd_file_info; #ifdef HAVE_PREPLAY_FILE cd_ip.preplay_file = cd_preplay_file; #endif cd_ip.set_eq = cd_set_eq; return &cd_ip; } /*\ Wait for cd->action to change, unless we're the CD thread itself \*/ static void action_wait(struct cd_struct *cd, unsigned int action) { cd->action = action; if (pthread_equal(cd->thread, pthread_self())) return; while (cd->action == action) xmms_usleep(10000); } static gint get_track_num(gchar *f, struct cd_struct **cd_ret) { gint n = -1; gchar *bf = strrchr(f, '/'); struct cd_struct *cd = 0; cd = cd_list; while (cd) { if (!memcmp(f, cd->device, strlen(cd->device))) { if (strlen(f) == strlen(cd->device)) { bf = 0; n = 100; } break; } cd = cd->next; } if (bf) if (!cd || (sscanf(bf, "/" TRACK_CDR, &n) < 1)) n = -1; *cd_ret = cd; return n; } static void close_thread(struct cd_struct *cd) { struct cd_struct **cdd; if (cd_cur == cd) cd_cur = 0; if (cd_next == cd) cd_next = 0; cdd = &cd_list; while (*cdd && (*cdd != cd)) { cdd = &((*cdd)->next); } if (*cdd) *cdd = cd->next; cd->action = CD_EXIT; } static void read_toc(struct cd_struct *cd) { gint i, cdfd; GList *list; if (!playlist_check(cd->device)) { CD_LOCK(); close_thread(cd); CD_UNLOCK(); return; } cdfd = cdrom_open(cd->device, &cd->flags); cdrom_read_toc(cd, cdfd); cdrom_close(cdfd); /*\ Detect all-data CDs \*/ cd->datacd = 1; for (i = cd->first_trk; i <= cd->last_trk; i++) { if (!cd->data[i]) { cd->datacd = 0; break; } } /*\ For drives that don't detect media change.. \*/ if (!cd_read_cddb(cd, cd_cfg.cddb_auto)) { return; } list = 0; if (cd->playorder && cd_cfg.playorder) { gchar *po = cd->playorder; while (*po) { gint i; gchar *buf; while (*po && !isdigit(*po)) po++; if (!*po) break; i = 0; /*\ i = strtol(po, &po, 10); \*/ do { i *= 10; i += (*po - '0'); } while (isdigit(*(++po))); list = g_list_append(list, g_strdup_printf( "%s/" TRACK_CDR, cd->device, i)); } } if (!list) for (i = cd->first_trk; i <= cd->last_trk; i++) { if (!cd->data[i]) { list = g_list_append(list, g_strdup_printf( "%s/" TRACK_CDR, cd->device, i)); } } if (!list || cd_cfg.dtitle) list = g_list_prepend(list, g_strdup(cd->device)); if (!playlist_replace(cd->device, list)) { CD_LOCK(); close_thread(cd); CD_UNLOCK(); } } #define NUM_FRAMES 16 /*\ This thread does the deleting of the cd_struct, so everything |*| called from here doesn't have to CD_LOCK it's own struct. \*/ static void * cd_read_loop(void *arg) { gchar buf[CD_FRAMESIZE_RAW * NUM_FRAMES]; gint btw, trk; gint cdfd = -1; time_t tm, last = time(0); struct cd_struct *cd = (struct cd_struct *)arg; while (!playlist_check(cd->device)) xmms_usleep(10000); read_toc(cd); while (1) switch (cd->action) { case CD_STOP_NOW: cd->action = CD_STOP; /*\ FALLTHROUGH \*/ case CD_STOP: if (cdfd >= 0) { cdrom_close(cdfd); cdfd = -1; } tm = time(0); if (tm != last) { last = tm; read_toc(cd); } xmms_usleep(10000); break; case CD_DIRECT: xmms_usleep(10000); break; case CD_TRACK: seek_lba = 0; /*\ FALLTHROUGH \*/ case CD_SEEK: trk = next_trk; if (trk < 0) trk = cur_trk; if ((trk < cd->first_trk) || (trk > cd->last_trk)) { cd->action = CD_STOP; break; } cur_lba = cd->lba[trk] + seek_lba;; end_lba = cd->lba[trk + 1]; /*\ The position given in the leadout track seems to be one off.. |*| |*| This is just a quick fix. \*/ if (cur_trk == cd->last_trk) --end_lba; if (cdfd < 0) { cdfd = cdrom_open(cd->device, &cd->flags); if (cdfd < 0) { cd->action = CD_STOP; break; } } if (cd->action == CD_SEEK) cd_ip.output->flush((seek_lba * 40) / 3); cd->action = CD_PLAY; /*\ FALLTHROUGH \*/ case CD_PLAY: if (cur_lba >= end_lba) { cd->action = CD_STOP; #ifdef HAVE_PREPLAY_FILE cd_ip.eof_reached(); #endif break; } btw = (cd_ip.output->buffer_free() / CD_FRAMESIZE_RAW); if (btw > 0) { gint i; if (btw > NUM_FRAMES) btw = NUM_FRAMES; if (btw > (end_lba - cur_lba)) btw = (end_lba - cur_lba); btw = cdrom_read_audio(cdfd, cur_lba, buf, btw); if (btw < 0) { if (errno != EIO) { fprintf(stderr, "xmms-cdread: Unable to read audio: %s\n", strerror(errno)); fflush(stderr); } cd->action = CD_STOP; #ifdef HAVE_PREPLAY_FILE cd_ip.eof_reached(); #endif break; } cur_lba += btw; cd_filter(buf, btw * (CD_FRAMESIZE_RAW / 4)); for (i = 0; i < btw; i++) { cd_ip.output->write_audio(buf + (i * CD_FRAMESIZE_RAW), CD_FRAMESIZE_RAW); cd_ip.add_vis_pcm(cd_ip.output->written_time(), FMT_S16_LE, 2, CD_FRAMESIZE_RAW, buf + (i * CD_FRAMESIZE_RAW)); } } else { xmms_usleep(10000); } break; case CD_EXIT: { gint i; cdrom_close(cdfd); TT_LOCK(); for (i = 100; --i >= 0; ) { if (cd->ttitle[i]) g_free(cd->ttitle[i]); if (cd->extt[i]) g_free(cd->extt[i]); } if (cd->dtitle) g_free(cd->dtitle); if (cd->playorder) g_free(cd->playorder); if (cd->discid) g_free(cd->discid); TT_UNLOCK(); g_free(cd->device); g_free(cd); --running_threads; return NULL; } } } static void init_thread(gchar *device) { struct cd_struct *cd; CD_LOCK(); cd = cd_list; while (cd && strcmp(cd->device, device)) cd = cd->next; if (cd) { CD_UNLOCK(); return; } CNEW(cd, 1); cd->device = g_strdup(device); pthread_mutex_init(&(cd->title_mutex), NULL); cd->action = CD_STOP_NOW; cd->cdfd = -1; cd->volume_left = cd->volume_right = 100; cd->id = 0xff; /*\ Impossible discid, but not zero \*/ if (pthread_create(&(cd->thread), NULL, cd_read_loop, cd) < 0) { show_dialog("Couldn't start playing thread:\n%s", g_strerror(errno)); if (cd->device) g_free(cd->device); g_free(cd); } else { pthread_detach(cd->thread); cd->next = cd_list; cd_list = cd; ++running_threads; } CD_UNLOCK(); } void _fini(void) { CD_LOCK(); while (cd_list) close_thread(cd_list); CD_UNLOCK(); cddb_server_cleanup(); while (running_threads > 0) xmms_usleep(10000); } static void cd_init(void) { get_configure(); cd_init_eq(); } static gint cd_our_file(gchar *f) { struct cd_struct *cd; gint n; CD_LOCK(); n = get_track_num(f, &cd); if ((n == 100) && cd->id && (playlist_check(cd->device) == 1) && !cd->datacd) { TT_LOCK(); cd->id = 0; /*\ Force reread \*/ TT_UNLOCK(); } CD_UNLOCK(); if (!cd) { struct stat st; gchar *tf, *p; /*\ Check if it starts with '/dev' so we don't do lots |*| of work for nothing. |*| '/vol' is for solaris, and the last one is so that |*| the config-ed device always works. \*/ if (memcmp(f, "/dev/", 5) && memcmp(f, "/vol/", 5) && strncmp(f, cd_cfg.cd_device, 5)) return 0; tf = g_strdup(f); while (*tf) { if ((stat(tf, &st) >= 0) && ((S_ISBLK(st.st_mode)) || (S_ISCHR(st.st_mode)))) { int cdfd, tmp; tmp = 0; cdfd = cdrom_open(tf, &tmp); if (cdfd >= 0) { cdrom_close(cdfd); init_thread(tf); g_free(tf); xmms_usleep(10000); return 1; } } p = strrchr(tf, '/'); if (!p) { g_free(tf); return 0; } *p = 0; } g_free(tf); return 0; } return (n >= 0); } /*\ Useless function. |*| Only here because _someone_ decided this was the way |*| to check for dead files in virtual directories.. |*| (And XMMS crashes if it's playing a 'dead' file that's removed) \*/ static GList * cd_scan_dir(gchar *dir) { struct cd_struct *cd; GList *list = 0; CD_LOCK(); get_track_num(dir, &cd); if (cd) { gint i; for (i = cd->first_trk; i <= cd->last_trk; i++) { list = g_list_append(list, g_strdup_printf( TRACK_CDR, i)); } } CD_UNLOCK(); return list; } static gint cd_track_len(struct cd_struct *cd, gint n) { if (n == 100) return -1; return ((cd->lba[n + 1] - cd->lba[n]) * 40) / 3; } static void cd_play_file(gchar *f) { struct cd_struct *cd; gint n; /*\ Hack: f isn't guaranteed to survive a context switch. |*| |*| This doesn't make it 100% safe, but almost. \*/ f = g_strdup(f); CD_LOCK(); n = get_track_num(f, &cd); if (!cd || (n < cd->first_trk) || (n > cd->last_trk)) { CD_UNLOCK(); g_free(f); return; } cd_ip.set_info(cd_strdup_title(cd, n), cd_track_len(cd, n), 32 * 44100, 44100, 2); if (cd_next) { cd_cur = cd_next; cur_trk = next_trk; cd_next = 0; next_trk = -1; CD_UNLOCK(); g_free(f); return; } cur_trk = n; cd_cur = cd; cd->error = FALSE; if (cd_cfg.out_cdrom) { cd_ip.get_volume = get_volume; cd_ip.set_volume = set_volume; if (cd->action != CD_STOP) { CD_UNLOCK(); g_free(f); return; } end_lba = cd->lba[cur_trk + 1]; cd->action = CD_DIRECT; if (cd->cdfd < 0) cd->cdfd = cdrom_open(cd->device, &cd->flags); cd->paused = 0; CD_UNLOCK(); cd_seek(0); g_free(f); return; } cd_ip.get_volume = NULL; cd_ip.set_volume = NULL; if (!cd_ip.output->open_audio(FMT_S16_LE, 44100, 2)) { cd->error = TRUE; } else { action_wait(cd, CD_TRACK); } CD_UNLOCK(); g_free(f); } #ifdef HAVE_PREPLAY_FILE static gint cd_preplay_file(gchar *f) { struct cd_struct *cd; gint n, r; if (cd_cfg.out_cdrom) return -1; if (cd_next) return -1; CD_LOCK(); n = get_track_num(f, &cd); if (!cd || (n < cd->first_trk) || (n > cd->last_trk)) { CD_UNLOCK(); return -1; } r = cd_ip.output->continue_audio(FMT_S16_LE, 44100, 2); if (r < 0) { CD_UNLOCK(); return -1; } next_trk = n; cd_next = cd; cd->error = FALSE; cd->action = CD_TRACK; CD_UNLOCK(); return r; } #endif static void cd_stop(void) { CD_LOCK(); next_trk = -1; if (cd_cur) { if (cd_cur->action == CD_DIRECT) { cdrom_stop(cd_cur->cdfd); cdrom_close(cd_cur->cdfd); cd_cur->cdfd = -1; action_wait(cd_cur, CD_STOP_NOW); } else if (cd_next) { if (cd_next->action != CD_STOP) { action_wait(cd_next, CD_STOP_NOW); } cd_next = 0; } else { if (cd_cur->action != CD_STOP) { action_wait(cd_cur, CD_STOP_NOW); } if (!cd_cur->error) cd_ip.output->close_audio(); cd_cur = 0; } } CD_UNLOCK(); } static void cd_pause(short p) { CD_LOCK(); if (cd_cur) { if (cd_cur->action == CD_DIRECT) { cd_cur->paused = p; cdrom_pause(cd_cur->cdfd, p); } else if (cd_cur->action != CD_STOP) cd_ip.output->pause(p); } CD_UNLOCK(); } static void cd_seek(gint to) { CD_LOCK(); next_trk = -1; if (cd_cur && !cd_cur->error) { if (cd_cur->action == CD_DIRECT) { cdrom_play_lba(cd_cur->cdfd, cd_cur->lba[cur_trk] + (to * 75), cd_cur->lba[cur_trk + 1] - 1); if (cd_cur->paused) { cdrom_pause(cd_cur->cdfd, TRUE); } } else { seek_lba = (to * 75); action_wait(cd_cur, CD_SEEK); } } CD_UNLOCK(); } static gint cd_get_time(void) { gint r = -1; CD_LOCK(); if (cd_cur) { if (cd_cur->error) { r = -2; } else if (cd_cur->action == CD_DIRECT) { r = cdrom_get_time(cd_cur); } else if ((cd_cur->action != CD_STOP) || cd_ip.output->buffer_playing()) { r = cd_ip.output->output_time(); } } CD_UNLOCK(); return r; } static void cd_song_info(gchar *f, gchar **t, gint *l) { struct cd_struct *cd; gint n; CD_LOCK(); n = get_track_num(f, &cd); if (!cd || (n < 0)) { CD_UNLOCK(); return; } *l = cd_track_len(cd, n); *t = cd_strdup_title(cd, n); CD_UNLOCK(); } /*\ Taken from cdaudio.c |*| Only for supporting direct CD audio, which I don't like anyhow.. \*/ #ifdef HAVE_SYS_SOUNDCARD_H #include #elif defined(HAVE_MACHINE_SOUNDCARD_H) #include #endif static void get_volume(gint *l, gint *r) { #if defined(HAVE_SYS_SOUNDCARD_H) || defined(HAVE_MACHINE_SOUNDCARD_H) if (cd_cfg.use_oss_mixer) { gint fd, devs, cmd, v; fd = open("/dev/mixer", O_RDONLY); if (fd != -1) { ioctl(fd, SOUND_MIXER_READ_DEVMASK, &devs); if (devs & SOUND_MASK_CD) cmd = SOUND_MIXER_READ_CD; else if (devs & SOUND_MASK_VOLUME) cmd = SOUND_MIXER_READ_VOLUME; else { close(fd); return; } ioctl(fd, cmd, &v); *r = (v & 0xFF00) >> 8; *l = (v & 0x00FF); close(fd); } } else #endif { #if defined(HAVE_SYS_CDIO_H) && defined(__FreeBSD__) struct ioc_vol vol; CD_LOCK(); if (cd_cur && (cd_cur->cdfd >= 0)) { ioctl(cd_cur->cdfd, CDIOCGETVOL, &vol); *l = (100 * vol.vol[0]) / 255; *r = (100 * vol.vol[1]) / 255; } CD_UNLOCK(); #elif defined(CDROMVOLREAD) struct cdrom_volctrl vol; CD_LOCK(); if (cd_cur && (cd_cur->cdfd >= 0)) { ioctl(cd_cur->cdfd, CDROMVOLREAD, &vol); *l = (100 * vol.channel0) / 255; *r = (100 * vol.channel1) / 255; } CD_UNLOCK(); #else CD_LOCK(); *l = cd_cur->volume_left; *r = cd_cur->volume_right; CD_UNLOCK(); #endif } } static void set_volume(gint l, gint r) { #if defined(HAVE_SYS_CDIO_H) && defined(__FreeBSD__) struct ioc_vol vol; #else struct cdrom_volctrl vol; #endif #if defined(HAVE_SYS_SOUNDCARD_H) || defined(HAVE_MACHINE_SOUNDCARD_H) if (cd_cfg.use_oss_mixer) { gint fd, devs, cmd, v; fd = open("/dev/mixer", O_RDONLY); if (fd != -1) { ioctl(fd, SOUND_MIXER_READ_DEVMASK, &devs); if (devs & SOUND_MASK_CD) cmd = SOUND_MIXER_WRITE_CD; else if (devs & SOUND_MASK_VOLUME) cmd = SOUND_MIXER_WRITE_VOLUME; else { close(fd); return; } v = (r << 8) | l; ioctl(fd, cmd, &v); close(fd); } } else #endif { CD_LOCK(); if (cd_cur && (cd_cur->cdfd >= 0)) { #if defined(HAVE_SYS_CDIO_H) && defined(__FreeBSD__) vol.vol[0] = vol.vol[2] = (l * 255) / 100; vol.vol[1] = vol.vol[3] = (r * 255) / 100; ioctl(cd_cur->cdfd, CDIOCSETVOL, &vol); #elif defined(CDROMVOLCTRL) vol.channel0 = vol.channel2 = (l * 255) / 100; vol.channel1 = vol.channel3 = (r * 255) / 100; ioctl(cd_cur->cdfd, CDROMVOLCTRL, &vol); #else /* Nothing */ #endif } cd_cur->volume_left = l; cd_cur->volume_right = r; CD_UNLOCK(); } }