cdcontrol.c revision 13823
1/* 2 * Compact Disc Control Utility by Serge V. Vakulenko, <vak@cronyx.ru>. 3 * Based on the non-X based CD player by Jean-Marc Zucconi and 4 * Andrew A. Chernov. 5 */ 6#include <stdio.h> 7#include <stdlib.h> 8#include <string.h> 9#include <unistd.h> 10#include <errno.h> 11#include <sys/file.h> 12#include <sys/cdio.h> 13#include <sys/ioctl.h> 14 15#define VERSION "1.0" 16 17/* 18 * Audio Status Codes 19 */ 20#define ASTS_INVALID 0x00 /* Audio status byte not valid */ 21#define ASTS_PLAYING 0x11 /* Audio play operation in progress */ 22#define ASTS_PAUSED 0x12 /* Audio play operation paused */ 23#define ASTS_COMPLETED 0x13 /* Audio play operation successfully completed */ 24#define ASTS_ERROR 0x14 /* Audio play operation stopped due to error */ 25#define ASTS_VOID 0x15 /* No current audio status to return */ 26 27struct cmdtab { 28 int command; 29 char *name; 30 char *args; 31} cmdtab[] = { 32#define CMD_DEBUG 1 33 { CMD_DEBUG, "Debug", "[ on | off | reset ]", }, 34#define CMD_EJECT 2 35 { CMD_EJECT, "Eject", "", }, 36#define CMD_HELP 3 37 { CMD_HELP, "?", 0, }, 38 { CMD_HELP, "Help", "", }, 39#define CMD_INFO 4 40 { CMD_INFO, "Info", "", }, 41#define CMD_PAUSE 5 42 { CMD_PAUSE, "PAuse", "", }, 43#define CMD_PLAY 6 44 { CMD_PLAY, "P", 0, }, 45 { CMD_PLAY, "Play", "min1:sec1.fr1 [ min2:sec2.fr2 ]", }, 46 { CMD_PLAY, "Play", "track1.index1 [ track2.index2 ]", }, 47 { CMD_PLAY, "Play", "#block [ len ]", }, 48#define CMD_QUIT 7 49 { CMD_QUIT, "Quit", "", }, 50#define CMD_RESUME 8 51 { CMD_RESUME, "Resume", "", }, 52#define CMD_STOP 9 53 { CMD_STOP, "Stop", "", }, 54#define CMD_VOLUME 10 55 { CMD_VOLUME, "Volume", "<l> <r> | left | right | mute | mono | stereo", }, 56 { 0, 0, }, 57}; 58 59struct cd_toc_entry toc_buffer[100]; 60 61char *cdname; 62int fd = -1; 63int verbose = 1; 64 65extern char *optarg; 66extern int optind; 67 68int setvol (int, int); 69int read_toc_entrys (int); 70int play_msf (int, int, int, int, int, int); 71int play_track (int, int, int, int); 72int get_vol (int *, int *); 73int status (int *, int *, int *, int *); 74int open_cd (void); 75int play (char *arg); 76int info (char *arg); 77char *input (int*); 78void prtrack (struct cd_toc_entry *e, int lastflag); 79void lba2msf (int lba, u_char *m, u_char *s, u_char *f); 80int msf2lba (u_char m, u_char s, u_char f); 81int play_blocks (int blk, int len); 82int run (int cmd, char *arg); 83char *parse (char *buf, int *cmd); 84 85extern int errno; 86 87void help () 88{ 89 struct cmdtab *c; 90 91 for (c=cmdtab; c->name; ++c) { 92 if (! c->args) 93 continue; 94 printf ("\t%s", c->name); 95 if (*c->args) 96 printf (" %s", c->args); 97 printf ("\n"); 98 } 99} 100 101void usage () 102{ 103 printf ("Usage:\n\tcdcontrol [ -vs ] [ -f disc ] [ command args... ]\n"); 104 printf ("Options:\n"); 105 printf ("\t-v - verbose mode\n"); 106 printf ("\t-s - silent mode\n"); 107 printf ("\t-f disc - device name such as /dev/cd0c\n"); 108 printf ("\tDISC - shell variable with device name\n"); 109 printf ("Commands:\n"); 110 help (); 111 exit (1); 112} 113 114int main (int argc, char **argv) 115{ 116 int cmd; 117 char *arg; 118 119 cdname = getenv ("DISC"); 120 if (! cdname) 121 cdname = getenv ("CDPLAY"); 122 123 for (;;) { 124 switch (getopt (argc, argv, "svhf:")) { 125 case EOF: 126 break; 127 case 's': 128 verbose = 0; 129 continue; 130 case 'v': 131 verbose = 2; 132 continue; 133 case 'f': 134 cdname = optarg; 135 continue; 136 case 'h': 137 default: 138 usage (); 139 } 140 break; 141 } 142 argc -= optind; 143 argv += optind; 144 145 if (argc > 0 && strcasecmp (*argv, "help") == 0) 146 usage (); 147 148 if (! cdname) { 149 fprintf (stderr, "No CD device name specified.\n"); 150 usage (); 151 } 152 153 if (argc > 0) { 154 char buf[80], *p; 155 int len; 156 157 for (p=buf; argc-- > 0; ++argv) { 158 len = strlen (*argv); 159 if (p + len >= buf + sizeof (buf) - 1) 160 usage (); 161 if (p > buf) 162 *p++ = ' '; 163 strcpy (p, *argv); 164 p += len; 165 } 166 *p = 0; 167 arg = parse (buf, &cmd); 168 return run (cmd, arg); 169 } 170 171 if (verbose == 1) 172 verbose = isatty (0); 173 if (verbose) { 174 printf ("Compact Disc Control Utility, Version %s\n", VERSION); 175 printf ("Type `?' for command list\n\n"); 176 } 177 178 for (;;) { 179 arg = input (&cmd); 180 if (run (cmd, arg) < 0) { 181 if (verbose) 182 perror ("cdplay"); 183 close (fd); 184 fd = -1; 185 } 186 fflush (stdout); 187 } 188} 189 190int run (int cmd, char *arg) 191{ 192 int l, r, rc; 193 194 switch (cmd) { 195 case CMD_QUIT: 196 exit (0); 197 198 default: 199 case CMD_HELP: 200 help (); 201 return (0); 202 203 case CMD_INFO: 204 if (fd<0 && ! open_cd ()) return (0); 205 return info (arg); 206 207 case CMD_PAUSE: 208 if (fd<0 && ! open_cd ()) return (0); 209 return ioctl (fd, CDIOCPAUSE); 210 211 case CMD_RESUME: 212 if (fd<0 && ! open_cd ()) return (0); 213 return ioctl (fd, CDIOCRESUME); 214 215 case CMD_STOP: 216 if (fd<0 && ! open_cd ()) return (0); 217 return ioctl (fd, CDIOCSTOP); 218 219 case CMD_DEBUG: 220 if (fd<0 && ! open_cd ()) return (0); 221 if (strcasecmp (arg, "on") == 0) 222 return ioctl (fd, CDIOCSETDEBUG); 223 if (strcasecmp (arg, "off") == 0) 224 return ioctl (fd, CDIOCCLRDEBUG); 225 if (strcasecmp (arg, "reset") == 0) 226 return ioctl (fd, CDIOCRESET); 227 printf ("Invalid command arguments\n"); 228 return (0); 229 230 case CMD_EJECT: 231 if (fd<0 && ! open_cd ()) return (0); 232 (void) ioctl (fd, CDIOCALLOW); 233 rc = ioctl (fd, CDIOCEJECT); 234 if (rc < 0) 235 return (rc); 236 close (fd); 237 fd = -1; 238 return (0); 239 240 case CMD_PLAY: 241 if (fd<0 && ! open_cd ()) return (0); 242 return play (arg); 243 244 case CMD_VOLUME: 245 if (fd<0 && ! open_cd ()) return (0); 246 247 if (strcasecmp (arg, "left") == 0) 248 return ioctl (fd, CDIOCSETLEFT); 249 else if (strcasecmp (arg, "right") == 0) 250 return ioctl (fd, CDIOCSETRIGHT); 251 else if (strcasecmp (arg, "mute") == 0) 252 return ioctl (fd, CDIOCSETMUTE); 253 else if (strcasecmp (arg, "mono") == 0) 254 return ioctl (fd, CDIOCSETMONO); 255 else if (strcasecmp (arg, "stereo") == 0) 256 return ioctl (fd, CDIOCSETSTERIO); 257 258 if (2 != sscanf (arg, "%d %d", &l, &r)) { 259 printf ("Invalid command arguments\n"); 260 return (0); 261 } 262 return setvol (l, r); 263 } 264} 265 266int play (char *arg) 267{ 268 struct ioc_toc_header h; 269 int rc, n, start, end = 0, istart = 1, iend = 1; 270 271 rc = ioctl (fd, CDIOREADTOCHEADER, &h); 272 if (rc < 0) 273 return (rc); 274 275 n = h.ending_track - h.starting_track + 1; 276 rc = read_toc_entrys ((n + 1) * sizeof (struct cd_toc_entry)); 277 if (rc < 0) 278 return (rc); 279 280 if (! *arg) 281 /* 282 * Play the whole disc 283 */ 284 return play_blocks (0, msf2lba (toc_buffer[n].addr.msf.minute, 285 toc_buffer[n].addr.msf.second, 286 toc_buffer[n].addr.msf.frame)); 287 288 if (strchr (arg, '#')) { 289 /* 290 * Play block #blk [ len ] 291 */ 292 int blk, len = 0; 293 294 if (2 != sscanf (arg, "#%d%d", &blk, &len) && 295 1 != sscanf (arg, "#%d", &blk)) { 296err: printf ("Invalid command arguments\n"); 297 return (0); 298 } 299 if (len == 0) 300 len = msf2lba (toc_buffer[n].addr.msf.minute, 301 toc_buffer[n].addr.msf.second, 302 toc_buffer[n].addr.msf.frame) - blk; 303 return play_blocks (blk, len); 304 } 305 306 if (strchr (arg, ':')) { 307 /* 308 * Play MSF m1:s1 [ .f1 ] [ m2:s2 [ .f2 ] ] 309 */ 310 int m1, m2 = 0, s1, s2 = 0, f1 = 0, f2 = 0; 311 312 if (6 != sscanf (arg, "%d:%d.%d%d:%d.%d", &m1, &s1, &f1, &m2, &s2, &f2) && 313 5 != sscanf (arg, "%d:%d.%d%d:%d", &m1, &s1, &f1, &m2, &s2) && 314 5 != sscanf (arg, "%d:%d%d:%d.%d", &m1, &s1, &m2, &s2, &f2) && 315 3 != sscanf (arg, "%d:%d.%d", &m1, &s1, &f1) && 316 4 != sscanf (arg, "%d:%d%d:%d", &m1, &s1, &m2, &s2) && 317 2 != sscanf (arg, "%d:%d", &m1, &s1)) 318 goto err; 319 if (m2 == 0) { 320 m2 = toc_buffer[n].addr.msf.minute; 321 s2 = toc_buffer[n].addr.msf.second; 322 f2 = toc_buffer[n].addr.msf.frame; 323 } 324 return play_msf (m1, s1, f1, m2, s2, f2); 325 } 326 327 /* 328 * Play track trk1 [ .idx1 ] [ trk2 [ .idx2 ] ] 329 */ 330 if (4 != sscanf (arg, "%d.%d%d.%d", &start, &istart, &end, &iend) && 331 3 != sscanf (arg, "%d.%d%d", &start, &istart, &end) && 332 3 != sscanf (arg, "%d%d.%d", &start, &end, &iend) && 333 2 != sscanf (arg, "%d.%d", &start, &istart) && 334 2 != sscanf (arg, "%d%d", &start, &end) && 335 1 != sscanf (arg, "%d", &start)) 336 goto err; 337 if (end == 0) 338 end = n; 339 return play_track (start, istart, end, iend); 340} 341 342char *strstatus (int sts) 343{ 344 switch (sts) { 345 case ASTS_INVALID: return ("invalid"); 346 case ASTS_PLAYING: return ("playing"); 347 case ASTS_PAUSED: return ("paused"); 348 case ASTS_COMPLETED: return ("completed"); 349 case ASTS_ERROR: return ("error"); 350 case ASTS_VOID: return ("void"); 351 default: return ("??"); 352 } 353} 354 355int info (char *arg) 356{ 357 struct ioc_toc_header h; 358 struct ioc_vol v; 359 int rc, i, n, trk, m, s, f; 360 361 rc = status (&trk, &m, &s, &f); 362 if (rc >= 0) 363 if (verbose) 364 printf ("Audio status = %d<%s>, current track = %d, current position = %d:%02d.%02d\n", 365 rc, strstatus (rc), trk, m, s, f); 366 else 367 printf ("%d %d %d:%02d.%02d\n", rc, trk, m, s, f); 368 else 369 printf ("No current status info\n"); 370 371 rc = ioctl (fd, CDIOCGETVOL, &v); 372 if (rc >= 0) 373 if (verbose) 374 printf ("Left volume = %d, right volume = %d\n", 375 v.vol[0], v.vol[1]); 376 else 377 printf ("%d %d\n", v.vol[0], v.vol[1]); 378 else 379 printf ("No volume info\n"); 380 381 rc = ioctl (fd, CDIOREADTOCHEADER, &h); 382 if (rc >= 0) 383 if (verbose) 384 printf ("Starting track = %d, ending track = %d, TOC size = %d bytes\n", 385 h.starting_track, h.ending_track, h.len); 386 else 387 printf ("%d %d %d\n", h.starting_track, 388 h.ending_track, h.len); 389 else { 390 perror ("getting toc header"); 391 return (rc); 392 } 393 394 n = h.ending_track - h.starting_track + 1; 395 rc = read_toc_entrys ((n + 1) * sizeof (struct cd_toc_entry)); 396 if (rc < 0) 397 return (rc); 398 if (verbose) { 399 printf ("track start duration block length type\n"); 400 printf ("-------------------------------------------------\n"); 401 } 402 for (i = 0; i < n; i++) { 403 printf ("%5d ", toc_buffer[i].track); 404 prtrack (toc_buffer + i, 0); 405 } 406 printf (" end "); 407 prtrack (toc_buffer + n, 1); 408 return (0); 409} 410 411void lba2msf (int lba, u_char *m, u_char *s, u_char *f) 412{ 413 lba += 150; /* block start offset */ 414 lba &= 0xffffff; /* negative lbas use only 24 bits */ 415 *m = lba / (60 * 75); 416 lba %= (60 * 75); 417 *s = lba / 75; 418 *f = lba % 75; 419} 420 421int msf2lba (u_char m, u_char s, u_char f) 422{ 423 return (((m * 60) + s) * 75 + f) - 150; 424} 425 426void prtrack (struct cd_toc_entry *e, int lastflag) 427{ 428 int block, next, len; 429 u_char m, s, f; 430 431 /* Print track start */ 432 printf ("%2d:%02d.%02d ", e->addr.msf.minute, 433 e->addr.msf.second, e->addr.msf.frame); 434 435 block = msf2lba (e->addr.msf.minute, e->addr.msf.second, 436 e->addr.msf.frame); 437 if (lastflag) { 438 /* Last track -- print block */ 439 printf (" - %6d - -\n", block); 440 return; 441 } 442 443 next = msf2lba (e[1].addr.msf.minute, e[1].addr.msf.second, 444 e[1].addr.msf.frame); 445 len = next - block; 446 lba2msf (len, &m, &s, &f); 447 448 /* Print duration, block, length, type */ 449 printf ("%2d:%02d.%02d %6d %6d %5s\n", m, s, f, block, len, 450 e->addr_type & 4 ? "data" : "audio"); 451} 452 453int play_track (int tstart, int istart, int tend, int iend) 454{ 455 struct ioc_play_track t; 456 457 t.start_track = tstart; 458 t.start_index = istart; 459 t.end_track = tend; 460 t.end_index = iend; 461 return ioctl (fd, CDIOCPLAYTRACKS, &t); 462} 463 464int play_blocks (int blk, int len) 465{ 466 struct ioc_play_blocks t; 467 468 t.blk = blk; 469 t.len = len; 470 return ioctl (fd, CDIOCPLAYBLOCKS, &t); 471} 472 473int setvol (int l, int r) 474{ 475 struct ioc_vol v; 476 477 v.vol[0] = l; 478 v.vol[1] = r; 479 v.vol[2] = 0; 480 v.vol[3] = 0; 481 return ioctl (fd, CDIOCSETVOL, &v); 482} 483 484int read_toc_entrys (int len) 485{ 486 struct ioc_read_toc_entry t; 487 488 t.address_format = CD_MSF_FORMAT; 489 t.starting_track = 0; 490 t.data_len = len; 491 t.data = toc_buffer; 492 return ioctl (fd, CDIOREADTOCENTRYS, (char *) &t); 493} 494 495int play_msf (int start_m, int start_s, int start_f, 496 int end_m, int end_s, int end_f) 497{ 498 struct ioc_play_msf a; 499 500 a.start_m = start_m; 501 a.start_s = start_s; 502 a.start_f = start_f; 503 a.end_m = end_m; 504 a.end_s = end_s; 505 a.end_f = end_f; 506 return ioctl (fd, CDIOCPLAYMSF, (char *) &a); 507} 508 509int status (int *trk, int *min, int *sec, int *frame) 510{ 511 struct ioc_read_subchannel s; 512 struct cd_sub_channel_info data; 513 514 bzero (&s, sizeof (s)); 515 s.data = &data; 516 s.data_len = sizeof (data); 517 s.address_format = CD_MSF_FORMAT; 518 s.data_format = CD_CURRENT_POSITION; 519 if (ioctl (fd, CDIOCREADSUBCHANNEL, (char *) &s) < 0) 520 return -1; 521 *trk = s.data->what.position.track_number; 522 *min = s.data->what.position.reladdr.msf.minute; 523 *sec = s.data->what.position.reladdr.msf.second; 524 *frame = s.data->what.position.reladdr.msf.frame; 525 return s.data->header.audio_status; 526} 527 528char *input (int *cmd) 529{ 530 static char buf[80]; 531 char *p; 532 533 do { 534 if (verbose) 535 fprintf (stderr, "cd> "); 536 if (! fgets (buf, sizeof (buf), stdin)) { 537 *cmd = CMD_QUIT; 538 return 0; 539 } 540 p = parse (buf, cmd); 541 } while (! p); 542 return (p); 543} 544 545char *parse (char *buf, int *cmd) 546{ 547 struct cmdtab *c; 548 char *p; 549 int len; 550 551 for (p=buf; *p; ++p) 552 if (*p == '\t') 553 *p = ' '; 554 else if (*p == '\n') 555 *p = 0; 556 557 for (p=buf; *p; ++p) 558 if (*p == ' ') { 559 *p++ = 0; 560 break; 561 } 562 while (*p == ' ') 563 ++p; 564 565 len = strlen (buf); 566 if (! len) 567 return (0); 568 *cmd = -1; 569 for (c=cmdtab; c->name; ++c) { 570 /* Try short command form. */ 571 if (! c->args && len == strlen (c->name) && 572 strncasecmp (buf, c->name, len) == 0) { 573 *cmd = c->command; 574 break; 575 } 576 577 /* Try long form. */ 578 if (strncasecmp (buf, c->name, len) != 0) 579 continue; 580 581 /* Check inambiguity. */ 582 if (*cmd != -1) { 583 fprintf (stderr, "Ambiguous command\n"); 584 return (0); 585 } 586 *cmd = c->command; 587 } 588 if (*cmd == -1) { 589 fprintf (stderr, "Invalid command, enter ``help'' for command list\n"); 590 return (0); 591 } 592 return p; 593} 594 595int open_cd () 596{ 597 char devbuf[80]; 598 599 if (fd > -1) 600 return (1); 601 if (*cdname == '/') 602 strcpy (devbuf, cdname); 603 else if (*cdname == 'r') 604 sprintf (devbuf, "/dev/%s", cdname); 605 else 606 sprintf (devbuf, "/dev/r%s", cdname); 607 fd = open (devbuf, O_RDONLY); 608 if (fd < 0 && errno == ENOENT) { 609 strcat (devbuf, "c"); 610 fd = open (devbuf, O_RDONLY); 611 } 612 if (fd < 0) { 613 if (errno != ENXIO) { 614 perror (devbuf); 615 exit (1); 616 } 617 /* open says 'Device not configured' if no cd in */ 618 fprintf (stderr, "open: No CD in\n"); 619 return (0); 620 } 621 return (1); 622} 623