view playogg.c @ 2:3617357a1b90

Added keyboard commands: p play/pause q quit n next file v previous file f fast forward b fast backward
author Eris Caffee <discordia@eldalin.com>
date Tue, 05 Aug 2014 12:15:46 -0500
parents 03892700ff0d
children 4d73408a0b71
line source
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
5 #include <getopt.h>
6 #include <signal.h>
7 #include <termios.h>
8 #include <unistd.h>
10 #include <ao/ao.h>
11 #include <vorbis/vorbisfile.h>
13 #include "playogg.h"
15 /******************************************************************************/
16 static struct opts {
17 int debug;
18 int verbose;
19 int list_devices;
20 char* device;
21 char **files;
22 char *outfile;
23 int ao_endian;
24 int ao_device_id;
25 ao_device *ao_device;
26 ao_info *ao_driver_info;
27 ao_sample_format ao_format;
28 } options;
32 struct termios termios_orig;
34 /******************************************************************************/
35 int
36 main (int argc, char **argv ) {
38 init();
39 parse_command_line( argc, argv );
41 term_init();
43 if ( ! sound_init() ) {
44 exit( EXIT_FAILURE );
45 }
47 if ( options.list_devices ) {
48 exit( sound_list_devices() );
49 }
51 char **file = options.files;
52 int next = 1; /* -1 play previous file, 0 stop playing, 1 play next file */
53 while (next && file && *file) {
54 if ( options.verbose ) {
55 printf( "Next file: %s\n", *file );
56 }
57 next = sound_play_file( *file );
58 if ( next < 0 ) {
59 if ( file > options.files ) {
60 file--;
61 }
62 }
63 else {
64 file++;
65 }
66 if ( options.outfile ) {
67 next = 0; // Only convert one file
68 }
69 }
71 exit( EXIT_SUCCESS );
72 }
74 /******************************************************************************/
75 void
76 err_exit ( char * msg ) {
77 fprintf(stderr, "FATAL ERROR: %s\n", msg ? msg : "Unknown error" );
78 exit( EXIT_FAILURE );
79 }
81 /******************************************************************************/
82 void
83 init ( void ) {
84 atexit( shutdown );
86 options.debug = 0;
87 options.verbose = 0;
88 options.device = NULL;
89 options.list_devices = 0;
90 options.files = NULL;
91 options.outfile = NULL;
92 options.ao_endian = 1;
93 options.ao_device_id = -1;
94 options.ao_device = NULL;
95 options.ao_driver_info = NULL;
96 memset( &options.ao_format, 0, sizeof(ao_sample_format) );
98 }
100 /******************************************************************************/
101 int
102 min ( int a, int b ) {
103 if (a <= b) {
104 return a;
105 }
106 else {
107 return b;
108 }
109 }
112 /******************************************************************************/
113 void
114 parse_command_line (int argc, char **argv ) {
116 static const char *short_options = "hd:lf:";
117 static const struct option long_options[] = {
118 { "help", 0, 0, 'h' },
119 { "verbose", 0, 0, 0 },
120 { "debug", 0, 0, 0 },
121 { "device", 1, 0, 'd' },
122 { "list-devices", 0, 0, 'l' },
123 { "outfile" , 1, 0, 'f' },
124 { NULL, 0, NULL, 0}
125 };
127 int c;
128 int option_index = 0;
129 while (1) {
130 c = getopt_long( argc, argv, short_options, long_options, &option_index );
132 if ( -1 == c ) {
133 break;
134 }
136 switch (c) {
138 case 0:
139 1;
140 int len = strlen( long_options[option_index].name );
141 if ( 0 == strncmp( long_options[option_index].name, "verbose", min(len, 7) ) ) {
142 options.verbose = 1;
143 }
144 else if ( 0 == strncmp( long_options[option_index].name, "debug", min(len, 5) ) ) {
145 options.debug = 1;
146 }
147 else {
148 fprintf( stderr, "Unrecognized option on command line: --%s\n", optarg );
149 show_help();
150 exit( EXIT_FAILURE );
151 }
153 break;
155 case 'f':
156 options.outfile = strdup( optarg );
157 printf( "outfile is %s\n", options.outfile );
158 break;
160 case 'h':
161 show_help();
162 exit( EXIT_SUCCESS );
163 break;
165 case 'd':
166 options.device = strdup( optarg );
167 printf( "device is %s\n", options.device );
168 break;
170 case 'l':
171 options.list_devices = 1;
172 break;
174 case 'v':
175 options.verbose = 1;
176 break;
178 default:
179 fprintf( stderr, "Unrecognized option on command line: -%c\n", c );
180 show_help();
181 exit( EXIT_FAILURE );
182 break;
183 }
184 }
186 if (optind < argc) {
187 options.files = calloc( argc - optind + 1, sizeof(char *) );
188 int i = 0;
189 while ( optind < argc) {
190 options.files[i++] = argv[optind++];
191 }
192 }
194 if (options.debug) {
195 fprintf( stderr, "verbose: %d\n", options.verbose );
196 fprintf( stderr, "debug : %d\n", options.debug );
197 fprintf( stderr, "device : %s\n", options.device );
198 fprintf( stderr, "list_devices : %d\n", options.list_devices );
199 fprintf( stderr, "files : ");
200 char **ptr = options.files;
201 while (ptr && *ptr) {
202 fprintf( stderr, "%s ", *ptr );
203 ptr++;
204 }
205 fprintf( stderr, "\n" );
206 }
208 }
210 /******************************************************************************/
211 void
212 shutdown ( void ) {
213 if ( options.device ) {
214 free( options.device );
215 }
216 if ( options.outfile ) {
217 free( options.outfile );
218 }
219 }
221 /******************************************************************************/
222 void
223 show_help ( void ) {
224 printf(
225 "Usage: myplayogg [-hlv] filename\n"
226 "\n"
227 "Play the Ogg Vorbis encoded file.\n"
228 "\n"
229 "Options:\n"
230 "\n"
231 "-h --help Show these instructions.\n"
232 " --verbose Print extra information.\n"
233 " --debug Print debugging information.\n"
234 "-d --device= Specify the output device to use.\n"
235 "-l --list-devices Print a list fo available output devices.\n"
236 "-f --outfile= Specify the name of the output file when using a file output device for format conversion.\n"
237 "\n"
238 );
239 }
241 /******************************************************************************/
242 int
243 sound_list_devices ( void ) {
244 ao_info ** driver_list = NULL;
245 int driver_count = -1;
247 driver_list = ao_driver_info_list( &driver_count );
248 if ( driver_list == NULL ) {
249 fprintf( stderr, "Unable to get list of drivers" );
250 return EXIT_FAILURE;
251 }
253 int default_driver_id = ao_default_driver_id();
255 for ( int i = 0; i < driver_count; ++i )
256 {
257 printf( "%s\n", driver_list[i]->name );
258 int driver_id = ao_driver_id( driver_list[i]->short_name );
259 if ( default_driver_id == driver_id ) {
260 printf( "\tDEFAULT DRIVER\n" );
261 }
262 printf( "\tshort name :\t%s\n", driver_list[i]->short_name );
263 printf( "\ttype :\t%s\n",
264 driver_list[i]->type == AO_TYPE_LIVE ? "live" :
265 (driver_list[i]->type == AO_TYPE_FILE ? "file" :
266 "unknown")
267 );
268 printf( "\tcomment :\t%s\n", driver_list[i]->comment);
269 }
271 return EXIT_SUCCESS;
272 }
274 /******************************************************************************/
275 int
276 sound_init ( void ) {
278 ao_initialize();
279 atexit( sound_shutdown );
281 options.ao_endian = ao_is_big_endian();
282 options.ao_device_id = ao_default_driver_id();
283 if ( options.device ) {
284 options.ao_device_id = ao_driver_id( options.device );
285 }
286 if ( -1 == options.ao_device_id ) {
287 fprintf( stderr, "Unable to open output device.\n" );
288 return 0;
289 }
291 options.ao_driver_info = ao_driver_info( options.ao_device_id );
293 options.ao_format.bits = 16;
294 options.ao_format.channels = 2;
295 options.ao_format.rate = 44100;
296 options.ao_format.byte_format = options.ao_driver_info->preferred_byte_format;
298 if ( AO_TYPE_LIVE == options.ao_driver_info->type ) {
299 options.ao_device = ao_open_live( options.ao_device_id, &options.ao_format, NULL );
300 }
301 else if ( AO_TYPE_FILE == options.ao_driver_info->type ) {
302 if ( options.outfile ) {
303 options.ao_device = ao_open_file( options.ao_device_id, options.outfile, 1, &options.ao_format, NULL );
304 }
305 else {
306 fprintf( stderr, "error: No output file name specified.\n" );
307 return 0;
308 }
309 }
310 else {
311 fprintf( stderr, "error: Unable to open output device.\n" );
312 return 0;
313 }
315 return 1;
316 }
318 /******************************************************************************/
319 int
320 sound_play_file ( char *file ) {
321 OggVorbis_File vf;
322 int next_file = 1;
324 int err = ov_fopen( file, &vf );
325 if ( err ) {
326 if ( OV_ENOTVORBIS == err ) {
327 fprintf( stderr, "Not a Vorbis file\n" );
328 }
329 else if ( OV_EVERSION == err ) {
330 fprintf( stderr, "Incompatible Vorbis version\n" );
331 }
332 else {
333 fprintf( stderr, "Unable to open file: %d\n", err );
334 }
335 return next_file;
336 }
338 sound_print_comments( &vf );
340 if ( options.debug ) {
341 vorbis_info *vi = ov_info( &vf, -1 );
342 printf( "Encoded by: %s\n\n", ov_comment( &vf, -1 )->vendor );
343 printf( "Bitstream is %d channel, %ldHz\n", vi->channels, vi->rate);
344 printf( "Decoded length: %ld samples\n", (long) ov_pcm_total( &vf, -1 ) );
345 }
347 int done = 0;
348 int paused = 0;
350 while ( ! done ) {
352 if ( ! paused ) {
353 if ( ! sound_play_section( &vf ) ) {
354 done = 1;
355 }
356 }
358 char c;
359 int num_read = read( fileno( stdin ), &c, sizeof( char ) );
360 if ( 1 == num_read ) {
361 switch (c) {
363 case 'p' :
364 case 'P' :
365 paused = ~paused;
366 break;
368 case 'q' :
369 case 'Q' :
370 done = 1;
371 next_file = 0;
372 break;
374 case 'n' :
375 case 'N' :
376 done = 1;
377 next_file = 1;
378 break;
380 case 'v' :
381 case 'V' :
382 done = 1;
383 next_file = -1;
384 break;
386 case 'f' :
387 case 'F' :
388 done = sound_skip_ahead( &vf );
389 break;
391 case 'b' :
392 case 'B' :
393 done = sound_skip_back( &vf );
394 break;
396 default:
397 break;
398 }
401 }
403 }
405 ov_clear( &vf );
407 return next_file;
408 }
410 /******************************************************************************/
411 int
412 sound_play_section ( OggVorbis_File *vf ) {
413 static char pcm[PCM_BUFFER_SIZE];
414 static int current_section;
416 int ok = 1;
418 long bytes_read = ov_read( vf, pcm, sizeof(pcm), options.ao_endian, 2, 1, &current_section );
420 if ( 0 == bytes_read ) {
421 ok = 0;
422 }
423 else if ( OV_HOLE == bytes_read ) {
424 if (options.verbose ) {
425 fprintf( stderr, "error: hole in data\n" );
426 }
427 }
428 else if ( OV_EBADLINK == bytes_read ) {
429 if (options.verbose ) {
430 fprintf( stderr, "error: invalid stream section in data\n" );
431 }
432 }
433 else if ( OV_EINVAL == bytes_read ) {
434 fprintf( stderr, "error: Unable to read file headers.\n" );
435 ok = 0;
436 }
437 else {
438 // Play it!
439 if ( ! ao_play( options.ao_device, pcm, bytes_read ) ) {
440 ok = 0;
441 }
442 }
444 return ok;
445 }
447 /******************************************************************************/
448 void
449 sound_print_comments ( OggVorbis_File *vf ) {
451 char **comments = ov_comment( vf, -1 )->user_comments;
453 char *track_title = NULL;
454 char **ptr = comments;
455 while ( ptr && *ptr ) {
456 char * pos = strstr( *ptr, "title" );
457 if ( NULL != pos ) {
458 pos = strchr( *ptr, '=' );
459 if ( pos ) {
460 track_title = pos + 1;
461 }
462 }
463 ++ptr;
464 }
466 printf( "Now playing: %s\n", track_title ? track_title : "[unknown]" );
468 if ( options.verbose ) {
469 ptr = comments;
470 while ( ptr && *ptr ) {
471 char * pos = strstr( *ptr, "title" );
472 if ( NULL == pos ) {
473 char *temp = strdup(*ptr);
474 pos = strchr( temp, '=' );
475 *pos = '\0';
476 printf( "\t%s: %s\n", temp, pos+1);
477 free(temp);
478 }
479 ++ptr;
480 }
481 }
482 }
484 /******************************************************************************/
485 void
486 sound_shutdown ( void ) {
487 ao_shutdown();
488 }
490 /******************************************************************************/
491 #define SKIP_TIME 1.0
492 int
493 sound_skip_ahead ( OggVorbis_File *vf ) {
494 double total_time = ov_time_total( vf, -1 );
495 double current_time = ov_time_tell( vf );
496 double new_position = current_time + SKIP_TIME;
497 if ( total_time < new_position ) {
498 return 1;
499 }
500 return ov_time_seek( vf, new_position );
501 }
503 /******************************************************************************/
504 int
505 sound_skip_back ( OggVorbis_File *vf ) {
506 double current_time = ov_time_tell( vf );
507 double new_position = current_time - SKIP_TIME;
508 if ( new_position > 0.0 ) {
509 return ov_time_seek( vf, new_position );
510 }
511 return ov_time_seek( vf, 0.0 );
512 }
514 /******************************************************************************/
515 void
516 term_init ( void ) {
517 tcgetattr( fileno( stdin ), &termios_orig );
519 struct termios termios_new;
520 tcgetattr( fileno( stdin ), &termios_new );
521 termios_new.c_lflag |= ICANON;
522 termios_new.c_lflag ^= ICANON;
523 termios_new.c_lflag |= ECHO;
524 termios_new.c_lflag ^= ECHO;
525 termios_new.c_cc[VMIN] = 0;
526 termios_new.c_cc[VTIME] = 0;
527 tcsetattr( fileno( stdin ), TCSANOW, &termios_new );
529 atexit( term_reset );
531 }
533 /******************************************************************************/
534 void
535 term_reset ( void ) {
536 tcsetattr( fileno( stdin ), TCSANOW, &termios_orig );
537 }