📄 cdaudio.c
字号:
/* 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */#include "cdaudio.h"#include <pthread.h>#include <errno.h>#include "xmms/i18n.h"#include "libxmms/util.h"#include "libxmms/titlestring.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# ifdef __FreeBSD__# define CDDA_DEVICE "/dev/acd0"# elif defined __OpenBSD__# 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__# define CDDA_DIRECTORY "/cdrom"# else# define CDDA_DIRECTORY "/cdrom/cdrom0"# endif# else# define CDDA_DIRECTORY "/mnt/cdrom"# endif#endifstatic char * cdda_get_title(cdda_disc_toc_t *toc, int track);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 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 */};CDDAConfig cdda_cfg;static struct { struct driveinfo drive; cdda_disc_toc_t cd_toc; int track; int fd; gboolean playing;} cdda_playing;static struct { pthread_t 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 20InputPlugin *get_iplugin_info(void){ cdda_ip.description = g_strdup_printf(_("CD Audio Player %s"), VERSION); return &cdda_ip;}static void cdda_init(void){ ConfigFile *cfgfile; 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 cfgfile = xmms_cfg_open_default_file(); /* These names are used for backwards compatibility */ xmms_cfg_read_string(cfgfile, "CDDA", "device", &drive->device); xmms_cfg_read_string(cfgfile, "CDDA", "directory", &drive->directory); xmms_cfg_read_int(cfgfile, "CDDA", "mixer", &drive->mixer); xmms_cfg_read_int(cfgfile, "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); xmms_cfg_read_int(cfgfile, "CDDA", "num_drives", &ndrives); for (i = 1; i < ndrives; i++) { char label[20]; drive = g_malloc0(sizeof(struct driveinfo)); sprintf(label, "device%d", i); xmms_cfg_read_string(cfgfile, "CDDA", label, &drive->device); sprintf(label, "directory%d", i); xmms_cfg_read_string(cfgfile, "CDDA", label, &drive->directory); sprintf(label, "mixer%d", i); xmms_cfg_read_int(cfgfile, "CDDA", label, &drive->mixer); sprintf(label, "readmode%d", i); xmms_cfg_read_int(cfgfile, "CDDA", label, &drive->dae); cdda_cfg.drives = g_list_append(cdda_cfg.drives, drive); } xmms_cfg_read_boolean(cfgfile, "CDDA", "title_override", &cdda_cfg.title_override); xmms_cfg_read_string(cfgfile, "CDDA", "name_format", &cdda_cfg.name_format); xmms_cfg_read_boolean(cfgfile, "CDDA", "use_cddb", &cdda_cfg.use_cddb); xmms_cfg_read_string(cfgfile, "CDDA", "cddb_server", &cdda_cfg.cddb_server);#ifdef WITH_CDINDEX xmms_cfg_read_boolean(cfgfile, "CDDA", "use_cdin", &cdda_cfg.use_cdin);#else cdda_cfg.use_cdin = FALSE;#endif xmms_cfg_read_string(cfgfile, "CDDA", "cdin_server", &cdda_cfg.cdin_server); xmms_cfg_free(cfgfile); 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; 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){ while (timeout_list) { struct timeout *t = timeout_list->data; gtk_timeout_remove(t->id); stop_timeout(t); } cddb_quit();}static int is_our_file(char *filename){ char *ext; if (cdda_find_drive(filename) == NULL) return FALSE; ext = strrchr(filename, '.'); if (ext && !strcasecmp(ext, ".cda")) return TRUE; return FALSE;}static gboolean is_mounted(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) struct statfs *fsp; 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 NULL; while (entries-- > 0) { if (!strcmp(fsp->f_mntfromname, devname)) return TRUE; fsp++; }#endif #endif 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) cdda_ip.output->write_audio(data, cur); left -= cur; data += cur; } pos += frames; } cdda_ip.output->buffer_free(); cdda_ip.output->buffer_free(); g_free(buffer); 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; pthread_create(&dae_data.thread, NULL, dae_play_loop, NULL);}static void play_file(char *filename){ char *tmp; struct driveinfo *drive; int track; int track_len; if ((drive = cdda_find_drive(filename)) == NULL) return; if (is_mounted(drive->device)) return; tmp = strrchr(filename, '/'); if (tmp) tmp++; else tmp = filename; if (!sscanf(tmp, "Track %d.cda", &track)) 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) return; if ((cdda_playing.fd = open(drive->device, CDOPENFLAGS)) == -1) return; track_len = cdda_calculate_track_length(&cdda_playing.cd_toc, track); cdda_ip.set_info(cdda_get_title(&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 char * cdda_get_title(cdda_disc_toc_t *toc, int track){ static guint32 cached_id; static cdinfo_t cdinfo; static pthread_mutex_t title_mutex = PTHREAD_MUTEX_INITIALIZER; TitleInput *input; guint32 disc_id; char *title; 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. */ pthread_mutex_lock(&title_mutex); 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); } } XMMS_NEW_TITLEINPUT(input); cdda_cdinfo_get(&cdinfo, track, &input->performer, &input->album_name, &input->track_name); pthread_mutex_unlock(&title_mutex); input->track_number = track; input->file_name = input->file_path = g_strdup_printf(_("CD Audio Track %02u"), track); input->file_ext = "cda"; title = xmms_get_titlestring(cdda_cfg.title_override ? cdda_cfg.name_format : xmms_get_gentitle_format(), input); g_free(input->file_name); g_free(input); if (!title) title = g_strdup_printf(_("CD Audio Track %02u"), track); return title;}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)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -