1 /* Copyright (C) 2006 Britton Leo Kerin, see copyright. */
2
3 /* This function performs the actual playing run as specified by the
4 options. */
5
6 #include <errno.h>
7 #include <math.h>
8 #include <sched.h>
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <signal.h>
12 #include <string.h>
13 #include <unistd.h>
14 #include <sys/mman.h>
15 #include <sys/stat.h>
16 #include <sys/types.h>
17
18 #include "rawrec.h"
19 #include "thread_functions.h"
20
21 /* This stuff is here instead of in the headers because it is grottish
22 hackery which should go as soon as kernel pthread support gets
23 better and libc uses it. */
24 /* This flag gets set from the handler for signals we watch for to do
25 special shutdown processing (currently only SIGTERM), since we
26 can't POSIXly make the pthread calls directly from the handler and
27 need to work around some linux pthread signal handling deficiency. */
28 extern int got_watched_for_shutdown_signal;
29 void shutdown_signal_handler(int signum);
30 /* This allows the following thread (move_au for play, move_fd for
31 record) to tell this main thread when they finish. */
32 pthread_mutex_t tell_main_follower_done_mutex = PTHREAD_MUTEX_INITIALIZER;
33 int tell_main_follower_done = 0;
34
35
play(parameters_stt * clp)36 void play(parameters_stt *clp) /* pneumonic: app: actual params pointer */
37 {
38 /* Audio device file descriptor. The zero initializer is included
39 only to prevent gcc -Wall from complaining that `audio_fd' might
40 be used uninitialized in this function. */
41 int audio_fd = 0;
42 int arg_fd; /* Argument file fd. */
43 int bps; /* the bits per sample for the format in use */
44 struct stat arg_file_stats; /* argument file stats, in particular size */
45 /* Start playing at jump_bytes into the file. Initialization
46 prevents -Wall from generating "might be used unitialized"
47 warning. */
48 double jump_bytes = 0;
49 double play_bytes; /* number of bytes of data to play */
50 long audio_fragsz; /* Kernel audio fragment size in bytes. */
51 int ringbuf_segs; /* Number of senments in ringbuf. */
52 move_au_th_arg_stt au_th_arg; /* move_au_th argument structure. */
53 move_fd_th_arg_stt fd_th_arg; /* move_fd_th argument structure. */
54 int rtn; /* For return values of pthread fctns. */
55 /* Maximum priority of FIFO thread. Should always be initialized
56 elsewhere before use. */
57 void *au_th_ret; /* Audio thread return pointer. */
58 void *fd_th_ret; /* File thread return pointer. */
59 sigset_t all_sigs; /* Full set of all signals. */
60 sigset_t tmp_mask; /* Temporary signal set storage. */
61 /* sigaction structure for shutdown handler */
62 struct sigaction shutdown_handler_act;
63 double sleep_time_secs; /* Compute for nanosleep() */
64 struct timespec sleep_time; /* For nanosleep() call. */
65 /* The actual parameters used for the record/play run may differ slightly
66 from those given on the command line due to hardware limitations. */
67 parameters_stt actual_params;
68 /* pointer to the actual_params structure */
69 parameters_stt *app = NULL; /* bogus initializer reasures compiler */
70
71 /* If the user wants us to selfishly hold the audio device, grab it now. */
72 if ( clp->hold_audio_device == TRUE ) {
73 /* This function checks for incorrect semantics (incorrect syntax
74 is caught by the above switch and reported by usage()), issues
75 appropriate warnings for values the hardware can't support
76 (eg. 23477 Hz sampling rate is probably not supported, so the
77 value will need to be rounded), and on success returns the
78 actual parameters to be used for the recording or playing run
79 into actual_params. If the user has things really fouled up,
80 execution will be terminated. */
81 actual_params = process_command_line(clp);
82 app = &actual_params;
83 audio_fd = audio_init(app->audio_dev, FOR_WRITING, app->speed,
84 app->format, app->channels);
85 }
86
87 /* Pause execution as promised. If both -p seconds and -P samples
88 are specified, the one which is longest in real time is the one
89 used. */
90 sleep_on_option(clp->time_startpause, clp->samp_startpause, clp->speed);
91
92 /* If we arn't holding the audio device, we still have to
93 process_command_line. This needs to be done asap, so we do it
94 here. */
95 if ( clp->hold_audio_device == FALSE ) {
96 actual_params = process_command_line(clp);
97 app = &actual_params;
98 }
99
100 /* Determine the bits per sample from the format. The format
101 specified by the user (and stored in the structure pointed to by
102 clp) is used, since if this differs from the driver compatible
103 format selected in actual_params, then translation is presumably
104 in use. At present, no translation is performed, and I'm not at
105 all sure I ever want to support translating between samples of
106 different resolutions anyway. */
107 if ( (strcmp(clp->format, "s16_le") == 0)
108 || (strcmp(clp->format, "u16_le") == 0)
109 || (strcmp(clp->format, "s16_be") == 0)
110 || (strcmp(clp->format, "u16_be") == 0))
111 bps = 16;
112 else if ( (strcmp(clp->format, "s8") == 0) ||
113 (strcmp(clp->format, "u8") == 0) )
114 bps = 8;
115 else {
116 err_die("BUG: unrecognized sample format seen in function '%s'\n",
117 __func__);
118 }
119
120 /* Open the argument data file to be read from, or use standard input. */
121 if ( app->using_stdio )
122 arg_fd = STDIN_FILENO;
123 else
124 arg_fd = data_init(app->arg_file, FOR_READING);
125
126 /* If we're not using stdio... */
127 if (app->using_stdio == FALSE )
128 /* stat the argument file. we're interested in it's size in bytes. */
129 if ( fstat(arg_fd, &arg_file_stats) == -1 ) {
130 err_die("fstat of argument file %s failed: %s\n", app->arg_file,
131 strerror(errno));
132 }
133
134 /* Determine the actual amount of data to be played, not including
135 pause time or end jumping (time_endjump or samp_endjump). */
136 if ( app->timelim > (double) app->samplim / app->speed )
137 play_bytes = rint(app->timelim * app->speed) * bps
138 * (app->channels) / 8;
139 else
140 /* Note that precedence is critical here (play_bytes_may overflow
141 without parenthesies). */
142 play_bytes = (double) app->samplim * (bps * app->channels / 8);
143
144 /* Unless we're already selfishly holding the audio device, open it now. */
145 if ( app->hold_audio_device == FALSE )
146 audio_fd = audio_init(app->audio_dev, FOR_WRITING, app->speed,
147 app->format, app->channels);
148
149 /* Set or get the kernel audio buffer block size. */
150 if ( app->set_fragsz == USER_FRAGSZ ) {
151 set_au_blksz(audio_fd, app->fragsz);
152 audio_fragsz = app->fragsz;
153 } else /* app->set_fragsz == AUTO_FRAGSZ */
154 /* Get the block size used by the audio driver. */
155 audio_fragsz = get_au_blksz(audio_fd);
156
157 /* Compute the number of bytes to jump into the file. If both -j
158 seconds and -J samples are specified, the one which is longest in
159 real time (farthest into the file) in the one used. */
160 if ( app->time_startjump > 0 || app->samp_startjump > 0 ) {
161 if ( app->time_startjump > ( (double) app->samp_startjump)
162 / app->speed ) {
163 jump_bytes = floor( (double) app->time_startjump
164 * (double) app->speed * (double) bps
165 * (double) (app->channels) / 8.0);
166 /* We must be sure to jump a multiple of (app->channels * bps / 8). */
167 /* FIXME: The use of doubles in this loop conditional causes a
168 compiler warning, which is ok since doubles need to stop
169 being used for this sort of stuff. */
170 for ( ; jump_bytes / (app->channels * bps / 8)
171 != floor(jump_bytes / (app->channels * bps / 8))
172 ; jump_bytes-- )
173 ;
174 } else
175 jump_bytes = (double) app->samp_startjump * (double) bps
176 * (double) (app->channels) / 8.0;
177 if ( app->using_stdio == TRUE ) {
178 unsigned char *tmp_buf; /* Temporary buffer. */
179 double bytes_jumped; /* Bytes jumped so far. */
180 long bytes_read; /* Bytes read by last read. */
181 int empty_seg_seq_length = 0; /* Empty reads in a row. */
182
183 /* For consistency with the way we treat the data we read from
184 the pipe that we actually want to play, we read in
185 app->fragsz chunks and give up in some of the same
186 situations. */
187 if ( (tmp_buf = (unsigned char *) malloc( (size_t) audio_fragsz))
188 == NULL ) {
189 err_die("malloc failed: %s\n", strerror(errno));
190 }
191
192 /* Do the reads (and throw away the results). */
193 for ( bytes_jumped = 0 ; bytes_jumped < jump_bytes ;
194 bytes_jumped += bytes_read ) {
195 if ( (bytes_read = read(arg_fd, tmp_buf, (size_t) min(audio_fragsz,
196 jump_bytes - bytes_jumped))) == -1 ) {
197 err_die("read from standard input failed: %s\n", strerror(errno));
198 }
199
200 /* Count empty reads in a row. */
201 if ( bytes_read == 0 )
202 empty_seg_seq_length++;
203 else
204 empty_seg_seq_length = 0;
205
206 /* Deal with case where we are getting nothing from stdin. */
207 if ( empty_seg_seq_length >= MAGIC_EMPTY_SEG_SEQ_LENGTH ) {
208 /* Frailty here: there is also time_limit_set but we don't
209 use it. It needs to go away eventually or something. */
210 if ( (app->samp_limit_set == TRUE)
211 || (app->time_limit_set == TRUE) ) {
212 err_die("too many empty segments seen while skipping as per -j or -J options (starved for standard input), giving up\n");
213 } else { /* th_arg->limit_set == FALSE */
214 if ( app->verbose == TRUE ) {
215 err_die("warning: standard input ended without supplying enough data to satisfy the given skip option (-j or -J)\n");
216 }
217 }
218 }
219 }
220
221 free(tmp_buf);
222
223 } else { /* app->using_stdio == FALSE */
224 /* Use lseek to do the actual jumping. */
225 if ( lseek(arg_fd, (off_t) jump_bytes, SEEK_SET) == -1 ) {
226 err_die("lseek on %s failed: %s\n", app->arg_file, strerror(errno));
227 }
228 }
229 }
230
231 /* Set up the ring buffer, it's sub buffers, and their associated
232 mutexs. */
233 ringbuf_segs = ringbuf_init(app->ringbufsz, audio_fragsz);
234
235 /* Fill the move_au_th argument structure. */
236 au_th_arg.startup_order = 2;
237 au_th_arg.ringbuf_segs = ringbuf_segs;
238 au_th_arg.seg_sz = audio_fragsz;
239 au_th_arg.fd = audio_fd;
240 au_th_arg.recorder = 0;
241 au_th_arg.byte_cnt = play_bytes;
242 au_th_arg.sample_size = bps * app->channels / 8;
243 if ( (app->time_limit_set) || (app->samp_limit_set) )
244 au_th_arg.limit_set = TRUE;
245 else
246 au_th_arg.limit_set = FALSE;
247 /* Fill the move_fd_th argument structure. */
248 fd_th_arg.startup_order = 1;
249 fd_th_arg.ringbuf_segs = ringbuf_segs;
250 fd_th_arg.seg_sz = audio_fragsz;
251 fd_th_arg.fd = arg_fd;
252 fd_th_arg.recorder = 0;
253 fd_th_arg.byte_cnt = play_bytes;
254 fd_th_arg.using_stdio = app->using_stdio;
255
256 /* Set thread attributes. See thread_scheme.txt in docs/programmer
257 for rational. */
258 if ( (rtn = pthread_attr_init(&move_au_attr)) ) {
259 err_die("BUG: pthread_attr_init failed: %s\n", strerror(rtn));
260 }
261 if ( (rtn = pthread_attr_setdetachstate(&move_au_attr,
262 PTHREAD_CREATE_JOINABLE)) ) {
263 err_die("BUG: pthread_attr_setdetachstate failed: %s\n", strerror(rtn));
264 }
265
266 /* This is ugly, but FreeBSD defines _POSIX_THREAD_PRIORITY_SCHEDULING
267 but it does not support PTHREAD_SCOPE_SYSTEM
268
269 #if defined (_POSIX_THREAD_PRIORITY_SCHEDULING) \
270 && _POSIX_THREAD_PRIORITY_SCHEDULING != -1 \
271 && _POSIX_THREAD_PRIORITY_SCHEDULING != 0
272 if ( have_root_authority ) {
273 if ( (rtn = pthread_attr_setinheritsched(&move_au_attr,
274 PTHREAD_EXPLICIT_SCHED)) ) {
275 err_die("BUG: pthread_attr_setinheritsched failed: %s\n", strerror(rtn));
276 }
277 if ( (rtn = pthread_attr_setschedpolicy(&move_au_attr, SCHED_FIFO)) )
278 err_die("BUG: pthread_attr_setschedpolicy failed: %s\n", strerror(rtn));
279 if ( (fifo_max_prio = sched_get_priority_max(SCHED_FIFO)) == -1 )
280 err_die("BUG: sched_get_priority_max failed: %s", strerror(errno));
281 move_au_param.sched_priority = fifo_max_prio;
282 if ( (rtn = pthread_attr_setschedparam(&move_au_attr, &move_au_param)) )
283 err_die("BUG: pthread_attr_setschedparam failed: %s\n", strerror(rtn));
284 if ( (rtn = pthread_attr_setscope(&move_au_attr, PTHREAD_SCOPE_SYSTEM)) )
285 err_die("BUG: pthread_attr_setscope failed: %s\n", strerror(rtn));
286 }
287 #endif
288 */
289
290 if ( (rtn = pthread_attr_init(&move_fd_attr)) )
291 err_die("BUG: pthread_attr_init failed: %s\n", strerror(rtn));
292 if ( (rtn = pthread_attr_setdetachstate(&move_fd_attr,
293 PTHREAD_CREATE_JOINABLE)) ) {
294 err_die("BUG: pthread_attr_setdetachstate failed: %s\n", strerror(rtn));
295 }
296
297 /* This is ugly, but FreeBSD defines _POSIX_THREAD_PRIORITY_SCHEDULING
298 but it does not support PTHREAD_SCOPE_SYSTEM
299
300 #if defined (_POSIX_THREAD_PRIORITY_SCHEDULING) \
301 && _POSIX_THREAD_PRIORITY_SCHEDULING != -1 \
302 && _POSIX_THREAD_PRIORITY_SCHEDULING != 0
303 if ( have_root_authority ) {
304 if ( (rtn = pthread_attr_setinheritsched(&move_fd_attr,
305 PTHREAD_EXPLICIT_SCHED)) ) {
306 err_die("BUG: pthread_attr_setinheritsched failed: %s\n", strerror(rtn));
307 }
308 if ( (rtn = pthread_attr_setschedpolicy(&move_fd_attr, SCHED_FIFO)) )
309 err_die("BUG: pthread_attr_setschedpolicy failed: %s\n", strerror(rtn));
310 move_fd_param.sched_priority = fifo_max_prio;
311 if ( (rtn = pthread_attr_setschedparam(&move_fd_attr, &move_fd_param)) )
312 err_die("BUG: pthread_attr_setschedparam failed: %s\n", strerror(rtn));
313 if ( (rtn = pthread_attr_setscope(&move_fd_attr, PTHREAD_SCOPE_SYSTEM)) )
314 err_die("BUG: pthread_attr_setscope failed: %s\n", strerror(rtn));
315 }
316 #endif
317 */
318 /* Getting ugly. Here we install a handler (which sets a global
319 flag which the threads can poll in order to do graceful
320 death). */
321 shutdown_handler_act.sa_handler = shutdown_signal_handler;
322 sigfillset(&(shutdown_handler_act.sa_mask));
323 shutdown_handler_act.sa_flags = 0;
324 if ( sigaction(SIGTERM, &shutdown_handler_act, NULL) == -1 )
325 err_die("sigaction failed: %s\n", strerror(errno));
326 if ( sigaction(SIGINT, &shutdown_handler_act, NULL) == -1 )
327 err_die("sigaction failed: %s\n", strerror(errno));
328
329 /* Set up an empty signal set. */
330 if ( sigfillset(&all_sigs) == -1 )
331 err_die("sigfillset failed: %s\n", strerror(errno));
332 /* POSIX requires all signal sets to be initialized before use. */
333 if ( sigemptyset(&tmp_mask) == -1 )
334 err_die("sigemptyset failed: %s\n", strerror(errno));
335 /* Block all signals in preperation for starting threads. We are
336 moving into some linux specific hackery now, though it shouldn't
337 make problems elsewhere. The threads never unblock the signals,
338 so the processes they run in are effectively unsignalable to
339 everything but the unblockable signals. Once the threads are
340 started, the main thread handles important signals (at the
341 moment, SIGTERM) and nanosleeps. On sight of an important
342 signal, we set a locked flag indicating that the signal has
343 occured. The move_au and move_fd threads poll this flag as
344 readers, and exit at the end of the segment in progress if they
345 detect it. The "segment in progress" is the one which move_au is
346 acting on, i.e. when playing, we try to exit as soon as possible,
347 we don't try to finish playing all buffered data move_fd may have
348 loaded for use. Note that large segment sizes may still result
349 in significant signal response latency. */
350 if ( sigprocmask(SIG_BLOCK, &all_sigs, &tmp_mask) == -1 )
351 err_die("sigprocmask failed: %s\n", strerror(errno));
352
353 if ( have_root_authority ) {
354 /* Get root authority (the saved set-user id should be root). */
355 if ( seteuid( (uid_t) 0 ) == -1 )
356 err_die("seteuid( (uid_t) 0 ) failed: %s\n", strerror(errno));
357
358 /* Entering critical section. Lock down our memory, if possible. */
359 #if defined (_POSIX_MEMLOCK) && _POSIX_MEMLOCK != -1 && _POSIX_MEMLOCK != 0
360 if ( mlockall(MCL_CURRENT) == -1 )
361 err_die("mlockall(MCL_CURRENT) failed: %s\n", strerror(errno));
362 #endif
363 }
364
365 /* Start threads. If we have root authority, then these threads
366 will be using real time scheduling, and we must therefore be root
367 to start them, and they therefore inherit effective root ids, but
368 drop them as soon as all threads have been created.. */
369 if ( (rtn = pthread_create (&move_au_th, &move_au_attr,
370 (void *(*)(void *)) move_au, &au_th_arg)) ) {
371 err_die ("BUG: pthread_create failed to create audio thread: %s\n",
372 strerror (rtn));
373 }
374 if ( (rtn = pthread_create (&move_fd_th, &move_fd_attr,
375 (void *(*)(void *)) move_fd, &fd_th_arg)) ) {
376 err_die ("BUG: pthread_create failed to create file thread: %s\n",
377 strerror (rtn));
378 }
379
380 if ( have_root_authority ) {
381 /* Drop back to normal uid authority. */
382 if ( seteuid(getuid()) == -1 )
383 /* If for some crazy reason we fail to drop root permissions, exit
384 immediately without advertising the fact. */
385 exit (EXIT_FAILURE);
386 /* Let other threads know that we have dropped root permissions
387 (they wait for this before doing anything). */
388 pthread_mutex_lock(&root_permissions_dropped_mutex);
389 root_permissions_dropped = 1;
390 pthread_cond_broadcast(&root_permissions_dropped_cv);
391 pthread_mutex_unlock(&root_permissions_dropped_mutex);
392 }
393
394 /* Restore the default mask. Note that at no time has the default
395 action for the shutdown signals we handle (sloppy death in which
396 this initial thread expires but leaves its spawned threads to
397 thrash on for a brief period, hopefully this mess will be fixed
398 with kernel 2.5 and glibc 2.3) been allowed to take place after
399 threads have been spawned. */
400 if ( sigprocmask(SIG_SETMASK, &tmp_mask, NULL) == -1 )
401 err_die("sigprocmask failed: %s\n", strerror(errno));
402
403 /* Now here is some sad hackery. We can't sigwait() because these
404 are asynchronous signals we are worried about, and they might
405 never arrive, which would cause our main thread to hang forever.
406 We can't create a dedicated thread and deflect signals there,
407 because Linux pthreads aren't POSIX-conformant in that respect.
408 So, we nanosleep in a loop (note that if we don't have a time or
409 sample limit set, then the sample limit will have been set to its
410 maximum value in process_comand_line (needs cleanup) and wait for
411 a signal to come in and fire a handler for the only signal we
412 currently deal with, SIGTERM. The SIGTERM handler sets a global
413 flag indicating that we got a shutdown signal, and we pass the
414 word on to the thread functions via a mutex protected flag, then
415 break out of the loop. Also, the move_au thread packs it in when
416 it doesn't find any data to read or finishes normally. It has to
417 let this main thread know when this happens so we can stop
418 sleeping and waiting for signals and commence pthread_joining.
419 It tells us via mutex-protected tell_main_follower_done, which we
420 poll from here every half audio fragsz worth of time (hopefully
421 giving maximum latency < fragsz as advertized). */
422 sleep_time_secs = (8.0 * audio_fragsz) / (2 * bps
423 * app->speed * app->channels);
424 sleep_time.tv_sec = (time_t) floor(sleep_time_secs);
425 sleep_time.tv_nsec = (long) ((sleep_time_secs - floor(sleep_time_secs))
426 * 1000000000.0);
427 for ( ; ; ) {
428 nanosleep(&sleep_time, NULL);
429
430 /* Has the following thread (move_au in this case, since we are
431 playing) finished? */
432 pthread_mutex_lock(&tell_main_follower_done_mutex);
433 if ( tell_main_follower_done ) {
434 pthread_mutex_unlock(&tell_main_follower_done_mutex);
435 break;
436 }
437 pthread_mutex_unlock(&tell_main_follower_done_mutex);
438
439 /* We will be good boys and block watched for signals before
440 checking whether we have seen them or not, even though branch on
441 an int should be pretty darn atomic, and if we have made it this
442 far without getting the signal we no longer care much whether we
443 get it or not. */
444 if ( sigemptyset(&tmp_mask) == -1 )
445 err_die("sigemptyset failed: %s\n", strerror(errno));
446 if ( sigaddset(&tmp_mask, SIGTERM) == -1 )
447 err_die("sigaddset failed: %s\n", strerror(errno));
448 if ( sigprocmask(SIG_BLOCK, &tmp_mask, NULL) == -1 )
449 err_die("sigprocmask failed: %s\n", strerror(errno));
450 if ( got_watched_for_shutdown_signal ) {
451 /* Let the move_au and move_fd threads know that we have been
452 interrupted by a signal that we catch in order to do clean
453 program termination, then break out of nanosleep loop.
454 Threads check shutdown_signal_seen after reading or writing
455 each segment from the ring buffer. This could be done
456 slightly more efficiently with a reader-writer lock for the
457 play case, but it probably isn't worth the hassle and
458 complexity. */
459 pthread_mutex_lock(&shutdown_signal_seen_mutex);
460 shutdown_signal_seen = 1;
461 pthread_mutex_unlock(&shutdown_signal_seen_mutex);
462 if ( sigprocmask(SIG_UNBLOCK, &tmp_mask, NULL) == -1 )
463 err_die("sigprocmask failed: %s\n", strerror(errno));
464 break;
465 }
466 if ( sigprocmask(SIG_UNBLOCK, &tmp_mask, NULL) == -1 )
467 err_die("sigprocmask failed: %s\n", strerror(errno));
468 }
469
470 /* Wait for threads to finish. */
471 if ( (rtn = pthread_join(move_au_th, &au_th_ret)) )
472 err_die("BUG: pthread_join failed: %s\n", strerror(rtn));
473 if ( *( (int *) au_th_ret) == -1 )
474 err_die("abnormal termination of move_au_th, aborting\n");
475 if ( (rtn = pthread_join(move_fd_th, &fd_th_ret)) )
476 err_die("BUG: pthread_join failed: %s\n", strerror(rtn));
477 if ( *( (int *) fd_th_ret) == -1 )
478 err_die("abnormal termination of move_fd_th, aborting\n");
479
480 /* As per our promise not to do things like end record (-e or -E) or
481 end pause (-z or -Z) if we got interrupted by a signal. This
482 behavior may change eventually, but for the moment, since we only
483 do special processing on SIGTERM, and the other terminating
484 signals cause the clunky natural death which doesn't honor these
485 options, we do it this way for consistency. */
486 if ( got_watched_for_shutdown_signal ) {
487 ringbuf_close(); /* Free ring buffer and paraphenalia. */
488 data_close(arg_fd, app->arg_file); /* Close the argument data file. */
489 audio_close(audio_fd, app->audio_dev);
490 return;
491 }
492
493 /* Unlock our address space. Requires root permissions, perhaps best
494 not to bother. */
495 /* if ( munlockall() == -1 ) {
496 * fprintf(stderr, "%s: munlockall() failed: ", progname);
497 * perror("");
498 * exit(EXIT_FAILURE);
499 * }
500 */
501
502 ringbuf_close(); /* Free ring buffer and paraphenalia. */
503
504 data_close(arg_fd, app->arg_file); /* Close the argument data file. */
505
506 /* Unless we are selfishly holding onto the audio device... */
507 if ( app->hold_audio_device == FALSE )
508 audio_close(audio_fd, app->audio_dev);
509
510 /* Pause at end as promised. */
511 sleep_on_option(app->time_endpause, app->samp_endpause, app->speed);
512
513 /* If we have been selfishly holding onto the audio device, let it go now. */
514 if ( app->hold_audio_device == TRUE )
515 audio_close(audio_fd, app->audio_dev);
516
517 return;
518 }
519