/*
Copyright (C) 2005, 2008-2009, 2012, 2014, 2017 Rocky Bernstein
<rocky@gnu.org>
Adapted from Gerd Knorr's player.c program <kraxel@bytesex.org>
Copyright (C) 1997, 1998
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 3 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, see <http://www.gnu.org/licenses/>.
*/
/* A program to show use of audio controls. For a more expanded
CDDA player program using curses display see cdda-player in this
distribution.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef HAVE_STDIO_H
#include <stdio.h>
#endif
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_STRING_H
#include <string.h>
#endif
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#ifdef HAVE_GETOPT_H
#include <getopt.h>
#endif
#ifdef HAVE_ERRNO_H
#include <errno.h>
#endif
#include <signal.h>
#include <cdio/cdio.h>
#include <cdio/mmc.h>
#include <cdio/util.h>
#include <cdio/cd_types.h>
static bool play_track(track_t t1, track_t t2);
static CdIo_t *p_cdio_global = NULL; /* libcdio handle */
static driver_id_t driver_id = DRIVER_DEVICE;
/* cdrom data */
static track_t i_first_track;
static track_t i_last_track;
static track_t i_first_audio_track;
static track_t i_last_audio_track;
static track_t i_tracks;
static msf_t toc[CDIO_CDROM_LEADOUT_TRACK+1];
static cdio_subchannel_t sub; /* subchannel last time read */
static int i_data; /* # of data tracks present ? */
static int start_track = 0;
static int stop_track = 0;
static int one_track = 0;
static bool b_cd = false;
static bool auto_mode = false;
static bool b_verbose = false;
static bool debug = false;
static bool b_record = false; /* we have a record for
the inserted CD */
static char *psz_device_global=NULL;
static char *psz_program;
inline static void
xperror(const char *psz_msg)
{
if (b_verbose) {
fprintf(stderr, "error: ");
perror(psz_msg);
}
return;
}
static void
oops(const char *psz_msg, int rc)
{
cdio_destroy (p_cdio_global);
free (psz_device_global);
exit (rc);
}
/* ---------------------------------------------------------------------- */
/*! Stop playing audio CD */
static bool
cd_stop(CdIo_t *p_cdio)
{
bool b_ok = true;
if (b_cd && p_cdio) {
i_last_audio_track = CDIO_INVALID_TRACK;
b_ok = DRIVER_OP_SUCCESS == cdio_audio_stop(p_cdio);
if ( !b_ok )
xperror("stop");
}
return b_ok;
}
/*! Eject CD */
static bool
cd_eject(void)
{
bool b_ok = true;
if (p_cdio_global) {
cd_stop(p_cdio_global);
b_ok = DRIVER_OP_SUCCESS == cdio_eject_media(&p_cdio_global);
if (!b_ok)
xperror("eject");
b_cd = false;
p_cdio_global = NULL;
}
return b_ok;
}
/*! Close CD tray */
static bool
cd_close(const char *psz_device)
{
bool b_ok = true;
if (!b_cd) {
b_ok = DRIVER_OP_SUCCESS == cdio_close_tray(psz_device, &driver_id);
if (!b_ok)
xperror("close");
}
return b_ok;
}
/*! Pause playing audio CD */
static bool
cd_pause(CdIo_t *p_cdio)
{
bool b_ok = true;
if (sub.audio_status == CDIO_MMC_READ_SUB_ST_PLAY) {
b_ok = DRIVER_OP_SUCCESS == cdio_audio_pause(p_cdio);
if (!b_ok)
xperror("pause");
}
return b_ok;
}
/*! Get status/track/position info of an audio CD */
static bool
read_subchannel(CdIo_t *p_cdio)
{
bool b_ok = true;
if (!b_cd) return false;
b_ok = DRIVER_OP_SUCCESS == cdio_audio_read_subchannel(p_cdio, &sub);
if (!b_ok) {
xperror("read subchannel");
b_cd = 0;
}
if (auto_mode && sub.audio_status == CDIO_MMC_READ_SUB_ST_COMPLETED)
cd_eject();
return b_ok;
}
/*! Read CD TOC and set CD information. */
static void
read_toc(CdIo_t *p_cdio)
{
track_t i;
i_first_track = cdio_get_first_track_num(p_cdio);
i_last_track = cdio_get_last_track_num(p_cdio);
i_tracks = cdio_get_num_tracks(p_cdio);
i_first_audio_track = i_first_track;
i_last_audio_track = i_last_track;
if ( CDIO_INVALID_TRACK == i_first_track ||
CDIO_INVALID_TRACK == i_last_track ) {
xperror("read toc header");
b_cd = false;
b_record = false;
} else {
b_cd = true;
i_data = 0;
for (i = i_first_track; i <= i_last_track+1; i++) {
if ( !cdio_get_track_msf(p_cdio, i, &(toc[i])) )
{
xperror("read toc entry");
b_cd = false;
return;
}
if ( TRACK_FORMAT_AUDIO != cdio_get_track_format(p_cdio, i) ) {
if ((i != i_last_track+1) ) {
i_data++;
if (i == i_first_track) {
if (i == i_last_track)
i_first_audio_track = CDIO_CDROM_LEADOUT_TRACK;
else
i_first_audio_track++;
}
}
}
}
b_record = true;
read_subchannel(p_cdio);
if (auto_mode && sub.audio_status != CDIO_MMC_READ_SUB_ST_PLAY)
play_track(1, CDIO_CDROM_LEADOUT_TRACK);
}
}
/*! Play an audio track. */
static bool
play_track(track_t i_start_track, track_t i_end_track)
{
bool b_ok = true;
if (!b_cd) {
cd_close(psz_device_global);
read_toc(p_cdio_global);
}
read_subchannel(p_cdio_global);
if (!b_cd || i_first_track == CDIO_CDROM_LEADOUT_TRACK)
return false;
if (debug)
fprintf(stderr,"play tracks: %d-%d => ", i_start_track, i_end_track);
if (i_start_track < i_first_track) i_start_track = i_first_track;
if (i_start_track > i_last_audio_track) i_start_track = i_last_audio_track;
if (i_end_track < i_first_track) i_end_track = i_first_track;
if (i_end_track > i_last_audio_track) i_end_track = i_last_audio_track;
if (debug)
fprintf(stderr,"%d-%d\n",i_start_track, i_end_track);
cd_pause(p_cdio_global);
b_ok = (DRIVER_OP_SUCCESS == cdio_audio_play_msf(p_cdio_global,
&(toc[i_start_track]),
&(toc[i_end_track])) );
if (!b_ok) xperror("play");
return b_ok;
}
static void
usage(char *prog)
{
fprintf(stderr,
"%s is a simple interface to issuing CD audio comamnds\n"
"\n"
"usage: %s [options] [device]\n"
"\n"
"default for to search for a CD-ROM device with a CD-DA loaded\n"
"\n"
"These command line options available:\n"
" -h print this help\n"
" -a start up in auto-mode\n"
" -v verbose\n"
"\n"
" Use only one of these:\n"
" -C close CD-ROM tray. If you use this option,\n"
" a CD-ROM device name must be specified.\n"
" -p play the whole CD\n"
" -t n play track >n<\n"
" -t a-b play all tracks between a and b (inclusive)\n"
" -L set volume level\n"
" -s stop playing\n"
" -S list audio subchannel information\n"
" -e eject cdrom\n"
"\n"
"That's all. Oh, maybe a few words more about the auto-mode. This\n"
"is the 'dont-touch-any-key' feature. You load a CD, player starts\n"
"to play it, and when it is done it ejects the CD. Start it that\n"
"way on a spare console and forget about it...\n"
"\n"
"(c) 1997,98 Gerd Knorr <kraxel@goldbach.in-berlin.de>\n"
"(c) 2005 Rocky Bernstein <rocky@gnu.org>\n"
, prog, prog);
}
typedef enum {
NO_OP=0,
PLAY_CD=1,
PLAY_TRACK=2,
STOP_PLAYING=3,
EJECT_CD=4,
CLOSE_CD=5,
SET_VOLUME=6,
LIST_SUBCHANNEL=7,
} cd_operation_t;
int
main(int argc, char *argv[])
{
int c;
char *h;
int i_rc = 0;
int i_volume_level = -1;
cd_operation_t todo = NO_OP; /* operation to do in non-interactive mode */
psz_program = strrchr(argv[0],'/');
psz_program = psz_program ? psz_program+1 : argv[0];
/* parse options */
while ( 1 ) {
if (-1 == (c = getopt(argc, argv, "aCdehkpL:sSt:vx")))
break;
switch (c) {
case 'v':
b_verbose = true;
break;
case 'd':
debug = 1;
break;
case 'a':
auto_mode = 1;
break;
case 'L':
if (NULL != strchr(optarg,'-')) {
i_volume_level = atoi(optarg);
todo = SET_VOLUME;
}
break;
case 't':
if (NULL != (h = strchr(optarg,'-'))) {
*h = 0;
start_track = atoi(optarg);
stop_track = atoi(h+1)+1;
if (0 == start_track) start_track = 1;
if (1 == stop_track) stop_track = CDIO_CDROM_LEADOUT_TRACK;
} else {
start_track = atoi(optarg);
stop_track = start_track+1;
one_track = 1;
}
todo = PLAY_TRACK;
break;
case 'p':
todo = PLAY_CD;
break;
case 'C':
todo = CLOSE_CD;
break;
break;
case 's':
todo = STOP_PLAYING;
break;
case 'S':
todo = LIST_SUBCHANNEL;
break;
case 'e':
todo = EJECT_CD;
break;
case 'h':
usage(psz_program);
exit(1);
default:
usage(psz_program);
exit(1);
}
}
if (argc > optind) {
psz_device_global = strdup(argv[optind]);
} else {
char **ppsz_cdda_drives=NULL;
char **ppsz_all_cd_drives = cdio_get_devices_ret(&driver_id);
if (!ppsz_all_cd_drives) {
fprintf(stderr, "Can't find a CD-ROM drive\n");
exit(2);
}
ppsz_cdda_drives = cdio_get_devices_with_cap(ppsz_all_cd_drives,
CDIO_FS_AUDIO, false);
if (!ppsz_cdda_drives || !ppsz_cdda_drives[0]) {
fprintf(stderr, "Can't find a CD-ROM drive with a CD-DA in it\n");
exit(3);
}
psz_device_global = strdup(ppsz_cdda_drives[0]);
cdio_free_device_list(ppsz_all_cd_drives);
cdio_free_device_list(ppsz_cdda_drives);
}
if (!b_cd && todo != EJECT_CD) {
cd_close(psz_device_global);
}
/* open device */
if (b_verbose)
fprintf(stderr,"open %s... ", psz_device_global);
p_cdio_global = cdio_open (psz_device_global, driver_id);
if (!p_cdio_global) {
if (b_verbose)
fprintf(stderr, "error: %s\n", strerror(errno));
else
fprintf(stderr, "open %s: %s\n", psz_device_global, strerror(errno));
exit(1);
} else
if (b_verbose) fprintf(stderr,"ok\n");
if (EJECT_CD == todo) {
i_rc = cd_eject() ? 0 : 1;
} else {
read_toc(p_cdio_global);
if (!b_cd) {
cd_close(psz_device_global);
read_toc(p_cdio_global);
}
if (b_cd)
switch (todo) {
case NO_OP:
break;
case STOP_PLAYING:
i_rc = cd_stop(p_cdio_global) ? 0 : 1;
break;
case EJECT_CD:
/* Should have been handled above before case statement. gcc
warns if we don't include this. And with this Coverty
complains when we do - we can't win, so go with gcc. */
cd_eject();
break;
case PLAY_TRACK:
/* play just this one track */
play_track(start_track, stop_track);
break;
case PLAY_CD:
play_track(1,CDIO_CDROM_LEADOUT_TRACK);
break;
case CLOSE_CD:
i_rc = cdio_close_tray(psz_device_global, NULL) ? 0 : 1;
break;
case SET_VOLUME:
{
cdio_audio_volume_t volume;
volume.level[0] = i_volume_level;
i_rc = (DRIVER_OP_SUCCESS == cdio_audio_set_volume(p_cdio_global,
&volume))
? 0 : 1;
break;
}
case LIST_SUBCHANNEL:
if (read_subchannel(p_cdio_global)) {
if (sub.audio_status == CDIO_MMC_READ_SUB_ST_PAUSED ||
sub.audio_status == CDIO_MMC_READ_SUB_ST_PLAY) {
{
printf("track %2d - %02x:%02x (%02x:%02x abs) ",
sub.track, sub.rel_addr.m, sub.rel_addr.s,
sub.abs_addr.m, sub.abs_addr.s);
}
}
printf("drive state: %s\n",
mmc_audio_state2str(sub.audio_status));
} else {
i_rc = 1;
}
break;
}
else {
fprintf(stderr,"no CD in drive (%s)\n", psz_device_global);
}
}
oops("bye", i_rc);
return 0; /* keep compiler happy */
}