1 /*
2 **
3 ** bplay/shmbuf.c (C) David Monro 1996
4 **
5 ** Copyright under the GPL - see the file COPYING in this directory
6 **
7 ** Adapted by J.A. Bezemer for use with GramoFile - July, August 1998
8 **
9 ** Patch to compile with egcs from Daniel Kobras, applied by J.A. Bezemer
10 ** - October, 1998
11 */
12
13 #include <stdio.h>
14 #include <stdlib.h>
15 #include <unistd.h>
16 #include <errno.h>
17 #include <signal.h>
18 #include <sys/types.h>
19 #include <sys/ipc.h>
20 #include <sys/sem.h>
21 #include <sys/shm.h>
22 #include <sys/wait.h>
23
24 #include "../errorwindow.h"
25 #include "../clrscr.h"
26 #include "../playwav.h"
27 #include "../secshms.h"
28 #include "../reclp_main.h"
29 #include "../boxes.h"
30 #include "../buttons.h"
31 #include <string.h>
32 #ifndef OLD_CURSES
33 #include <ncurses.h>
34 #else
35 #include <curses.h>
36 #endif
37
38 #ifndef SEMMSL
39 #ifdef __FreeBSD__
40 /*
41 * you may want to adjust this to whats configured into your kernel,
42 * 30 is just the current default. (see <sys/sem.h>) -nox
43 */
44 #define SEMMSL 30
45 #else
46 #define SEMMSL 32
47 #endif
48 #endif
49
50 #ifdef _SEM_SEMUN_UNDEFINED
51 union semun /* This has gone out of standard */
52 { /* libc headers as of glibc2.1, */
53 int val; /* we need to define it ourselves.*/
54 struct semid_ds *buf;
55 unsigned short int *array;
56 struct seminfo *__buf;
57 };
58 #endif
59
60 extern void finish_curses(int sig);
61
62 /* The size of the big array */
63 /* (currently 256K - nearly 1.5 sec at max rate) */
64 #define BIGBUFFSIZE 0x040000
65
66 /* Types */
67 typedef struct blockinf_t
68 {
69 int count; /* How many bytes in this buffer */
70 int last; /* Should we terminate after this buffer? */
71 int setit; /* Should we re-set the audio parameters to be the ones here? */
72 int speed;
73 int bits;
74 int stereo;
75 } blockinf_t;
76
77 /* Statics - mostly shared memory etc */
78 static int shmid, shmid2, *disksemid, *sndsemid;
79 static char *bigbuff;
80 static char **buffarr;
81 static int numbuffs, numsemblks;
82 static blockinf_t *buffinf;
83 static volatile int stoprecording;
84
85 /* prototypes */
86 void cleanupsems(void);
87 static void sighandler(int i);
88 static void childerrhandler(int i);
89
90 /* Extern globals */
91 extern int abuf_size;
92 extern int audio;
93 extern char *progname;
94
95
96 /* extern prototypes */
97 extern void ErrDie(char *err);
98 extern void snd_parm(int speed, int bits, int stereo);
99 extern void sync_audio(void);
100
init_shm(void)101 void init_shm(void)
102 {
103 int i;
104
105 /* Create, attach and mark for death the big buffer */
106 shmid = shmget(IPC_PRIVATE, BIGBUFFSIZE,
107 IPC_EXCL | IPC_CREAT | 0600);
108 if (shmid == -1)
109 ErrDie("shmget");
110 bigbuff = shmat(shmid, IPC_RMID, SHM_RND);
111 if (bigbuff == (char*)-1)
112 {
113 perror("shmat");
114 if(shmctl(shmid, IPC_RMID, NULL))
115 perror("shmctl");
116 exit(-1);
117 }
118 if(shmctl(shmid, IPC_RMID, NULL))
119 ErrDie("shmctl");
120
121 /* Create an array of pointers. Point them at equally spaced
122 ** chunks in the main buffer, to give lots of smaller buffers
123 */
124 numbuffs = BIGBUFFSIZE/abuf_size;
125 buffarr = (char**)malloc(numbuffs*sizeof(char*));
126 for (i=0; i<numbuffs; i++)
127 buffarr[i] = bigbuff + i * abuf_size;
128
129 /* Create a small amount of shared memory to hold the info
130 ** for each buffer.
131 */
132 shmid2 = shmget(IPC_PRIVATE, numbuffs*sizeof(blockinf_t),
133 IPC_EXCL | IPC_CREAT | 0600);
134 if (shmid2 == -1)
135 ErrDie("shmget");
136 buffinf = (blockinf_t*)shmat(shmid2, IPC_RMID, SHM_RND);
137 if (buffinf == (blockinf_t*)((char*)-1))
138 {
139 perror("shmat");
140 if(shmctl(shmid2, IPC_RMID, NULL))
141 perror("shmctl");
142 exit(-1);
143 }
144 if(shmctl(shmid2, IPC_RMID, NULL))
145 ErrDie("shmctl");
146
147 #ifndef __DragonFly__
148 #if USEBUFFLOCK
149 /* Ok, go root to lock the buffers down */
150 if(setreuid(geteuid(), getuid()) == -1)
151 {
152 #ifndef LP2CD
153 fprintf(stderr, "%s: setreuid: %s: continuing anyway\n",
154 progname, strerror(errno));
155 fprintf(stderr, "real uid = %d, effective uid = %d\n",
156 getuid(), geteuid());
157 #endif
158 }
159
160 if(shmctl(shmid, SHM_LOCK, NULL) || shmctl(shmid2, SHM_LOCK, NULL))
161 #ifndef LP2CD
162 fprintf(stderr,
163 "%s: shmctl: %s: continuing with unlocked buffers\n",
164 progname, strerror(errno))
165 #endif
166 ;
167
168 if(setreuid(geteuid(), getuid()) == -1)
169 {
170 #ifndef LP2CD
171 fprintf(stderr, "%s: setreuid: %s: continuing anyway\n",
172 progname, strerror(errno));
173 fprintf(stderr, "real uid = %d, effective uid = %d\n",
174 getuid(), geteuid());
175 #endif
176 }
177
178 #endif
179 #endif
180 /* Set up the appropriate number of semaphore blocks */
181 numsemblks = numbuffs/SEMMSL;
182 if((numsemblks * SEMMSL) < numbuffs)
183 numsemblks++;
184 /* Malloc arrays of semaphore ids (ints) for the semaphores */
185 if ((disksemid = (int*)malloc(sizeof(int)*numsemblks)) == NULL)
186 ErrDie("malloc");
187 if ((sndsemid = (int*)malloc(sizeof(int)*numsemblks)) == NULL)
188 ErrDie("malloc");
189 /* Create the semaphores */
190 for (i=0;i<numsemblks;i++)
191 {
192 if ((disksemid[i] = semget(IPC_PRIVATE, SEMMSL,
193 IPC_EXCL | IPC_CREAT | 0600)) == -1)
194 ErrDie("semget");
195 if ((sndsemid[i] = semget(IPC_PRIVATE, SEMMSL,
196 IPC_EXCL | IPC_CREAT | 0600)) == -1)
197 ErrDie("semget");
198 }
199 /* Catch some signals, so we clean up semaphores */
200 signal(SIGINT, sighandler);
201 /* Out of disk space errors are handled explicitly already. */
202 signal(SIGXFSZ, SIG_IGN);
203 }
204
205
206 /* Does an up on the appropriate semaphore */
up(int * semblk,int xsemnum)207 void up(int *semblk, int xsemnum)
208 {
209 struct sembuf sbuf;
210
211 sbuf.sem_num = xsemnum%SEMMSL;
212 sbuf.sem_op = 1;
213 sbuf.sem_flg = 0;
214
215 if (semop(semblk[xsemnum/SEMMSL], &sbuf, 1) == -1)
216 perror("semop");
217 }
218
219 /* Does a down on the appropriate semaphore */
down(int * semblk,int xsemnum)220 void down(int *semblk, int xsemnum)
221 {
222 struct sembuf sbuf;
223
224 sbuf.sem_num = xsemnum%SEMMSL;
225 sbuf.sem_op = -1;
226 sbuf.sem_flg = 0;
227
228 if (semop(semblk[xsemnum/SEMMSL], &sbuf, 1) == -1)
229 perror("semop");
230 }
231
232 /* The recording function */
shmrec(int outfd,long totalcount,int terminate)233 void shmrec(int outfd, long totalcount, int terminate)
234 {
235 pid_t pid;
236 int i;
237 button_t ok_button;
238 char timestring[100];
239
240 nodelay(stdscr, TRUE);
241
242 sync();
243
244 pid = fork();
245 if (pid == 0)
246 {
247 int cbuff = 0;
248 long totalrd = 0; /* we need it here too... */
249 int err = 0;
250
251 #ifdef VUMETER
252 signed short *ssptr;
253 signed short leftvalue, rightvalue, maxleft, maxright;
254 long samplesabove50pct = 0;
255 long samplesabove90pct = 0;
256 long samplesabove99pct = 0;
257 long samplestooloud = 0;
258 #endif
259
260 /* Uncatch the signals */
261 signal(SIGINT, SIG_DFL);
262
263 /* Child process writes the disk */
264 while(1)
265 {
266 long count, numwr, trgt;
267 char *tmpptr;
268
269 /* Grab the buffer. Blocks till it is OK to do so. */
270 down(disksemid, cbuff);
271 /* Spit it out */
272 tmpptr = buffarr[cbuff];
273 #ifdef VUMETER
274 ssptr = (signed short *) tmpptr;
275 #endif
276 numwr = 0;
277 trgt = buffinf[cbuff].count;
278 while ( (numwr < trgt) &&
279 ((count = write(outfd, tmpptr, trgt - numwr)) > 0) )
280 {
281 numwr += count;
282 tmpptr += count;
283 totalrd += count;
284 }
285
286 if (count == -1 && errno != EINTR) {
287 err = errno;
288 for (i = 0; i < numbuffs; i++)
289 up(sndsemid, i);
290 break;
291 }
292
293 #ifdef VUMETER
294 maxleft = 0;
295 maxright = 0;
296 trgt = buffinf[cbuff].count;
297
298 for (numwr = 0; numwr < trgt; numwr += 4 )
299 {
300 leftvalue=abs(*ssptr);
301 if (leftvalue > maxleft) maxleft = leftvalue;
302 ssptr ++;
303
304 rightvalue=abs(*ssptr);
305 if (rightvalue > maxright) maxright = rightvalue;
306 ssptr ++;
307
308 if (leftvalue > 16383 || rightvalue > 16383)
309 samplesabove50pct ++;
310 if (leftvalue > 29490 || rightvalue > 29490)
311 samplesabove90pct ++;
312 if (leftvalue > 32439 || rightvalue > 32439)
313 samplesabove99pct ++;
314 if (leftvalue > 32764 || rightvalue > 32764)
315 samplestooloud ++;
316 }
317 move(ERROR_WINDOW_Y + 2,ERROR_WINDOW_X +1);
318 addstr("L: =");
319 leftvalue = maxleft / (32768/(ERROR_WINDOW_W-6));
320 for (numwr = 0; numwr < leftvalue; numwr ++)
321 addch(numwr >= ERROR_WINDOW_W-8 ? '#' : '=');
322 for (; numwr < ERROR_WINDOW_W-6; numwr ++)
323 addch(' ');
324
325 move(ERROR_WINDOW_Y + 3,ERROR_WINDOW_X +1);
326 addstr("R: =");
327 rightvalue = maxright / (32768/(ERROR_WINDOW_W-6));
328 for (numwr = 0; numwr < rightvalue; numwr ++)
329 addch(numwr >= ERROR_WINDOW_W-8 ? '#' : '=');
330 for (; numwr < ERROR_WINDOW_W-6; numwr ++)
331 addch(' ');
332
333 move(0,79);
334 refresh();
335 #endif
336
337 /* Mark the buffer as clean */
338 up(sndsemid, cbuff);
339
340 /* If the block was marked as the last one, stop */
341 if (buffinf[cbuff].last)
342 break;
343 /* Advance the pointer */
344 cbuff++;
345 cbuff%=numbuffs;
346 }
347 /* Tidy up and exit, we are being waited for */
348 close(outfd);
349
350 #ifdef VUMETER
351 /* Display some informative data. This is really weird: we
352 display it here (in the child), then exit() and wait for a key
353 in the _parent_ process. But it's the only simple way to get it
354 working */
355 clearscreen(RECLP_HEADERTEXT);
356
357 printw("\n\n");
358 printw(" Recording information:\n\n\n");
359
360 if (!err) {
361
362 fsec2hmsf ( (double) totalrd / (4 * 44100) , timestring);
363 printw(" Recorded time : %s\n", timestring);
364 printw(" Recorded samples : %11ld\n", totalrd / 4);
365 printw(" Recorded bytes : %11ld (excl. header)\n", totalrd);
366 printw("\n");
367 printw(" Samples above 50%% of max. volume : %9ld (%5.1f%%)\n",
368 samplesabove50pct, samplesabove50pct * 100. / (totalrd/4));
369 printw(" Samples above 90%% of max. volume : %9ld (%5.1f%%)\n",
370 samplesabove90pct, samplesabove90pct * 100. / (totalrd/4));
371 printw(" Samples above 99%% of max. volume : %9ld (%5.1f%%)\n",
372 samplesabove99pct, samplesabove99pct * 100. / (totalrd/4));
373 printw(" Really too loud (clipped) samples : %9ld (%5.1f%%)\n",
374 samplestooloud, samplestooloud * 100. / (totalrd/4));
375
376 } else {
377 printw(" Recording has terminated due to an error.\n");
378 printw(" Operating system reports: %s\n", strerror(err));
379 }
380
381 #if 0
382 /* The computation of the avg volume is not simple. One approach
383 is totalvolume+=abs(sampleleft)+abs(sampleright) for each
384 sample, but if totalvolume gets too big, nothing is added any
385 more (lack of precision). If anyone has a better (working)
386 idea, please tell me! */
387 printw("\n");
388 printw(" Average volume : %7.1f (%5.1f%% of max.)\n",
389 totalvolume / (totalrd/2),
390 (totalvolume / (totalrd/2) * 100) / 32768);
391 /* (totalrd/2)=((totalrd/4)*2) */
392 #endif /* 0 */
393
394 ok_button.text = " OK ";
395 ok_button.y = 20;
396 ok_button.x = 71;
397 ok_button.selected = TRUE;
398
399 button_display (&ok_button);
400 mybox (ok_button.y - 1, ok_button.x - 1,
401 3, strlen (ok_button.text) + 2);
402 move (0, 79);
403 refresh();
404
405 /* Tell parent to stop recording */
406 if (err)
407 kill(getppid(), SIGUSR1);
408
409 #endif
410
411 exit(0);
412 }
413 else
414 {
415 /* Parent reads audio */
416 int cbuff = 0;
417 long totalrd = 0;
418
419 int in_ch;
420
421 signal(SIGUSR1, childerrhandler);
422
423 while (totalrd < totalcount && !stoprecording)
424 {
425 long trgt, count, numrd;
426 char *tmpptr;
427 trgt = totalcount - totalrd;
428 if (trgt > abuf_size)
429 trgt = abuf_size;
430 /* Get the buffer. Blocks until OK to do so */
431 down(sndsemid, cbuff);
432 if (stoprecording)
433 break;
434
435 /* Read a block of data */
436 numrd = 0;
437 tmpptr = buffarr[cbuff];
438 while( (numrd < trgt) &&
439 ((count = read(audio, tmpptr, trgt - numrd)) > 0) &&
440 !stoprecording)
441 {
442 numrd += count;
443 tmpptr += count;
444 }
445 /* Update the count for this block */
446 buffinf[cbuff].count = numrd;
447 /* Mark the buffer dirty */
448 up(disksemid, cbuff);
449 /* Update the amount done */
450 totalrd += numrd;
451 /* Tell the reader to stop if needed */
452
453 in_ch=getch();
454 #ifdef DEBUG
455 printw(" %d",cbuff);
456 #endif
457 if (in_ch==KEY_ENTER || in_ch==13 || in_ch==27)
458 stoprecording=1;
459 if ( ((totalrd >= totalcount) && terminate) || stoprecording)
460 buffinf[cbuff].last = 1;
461 /* Update the counter */
462 cbuff++;
463 cbuff%=numbuffs;
464 }
465 /* Tidy up and wait for the child */
466 close(audio);
467
468 /* XXX fix the occasional deadlock in the following wait() -nox */
469 for (i = 0; i < numbuffs; i++)
470 up(disksemid, i);
471
472 wait(NULL);
473
474 /* Free all the semaphores */
475 cleanupsems();
476
477 #ifdef VUMETER
478 nodelay(stdscr, FALSE);
479
480 /* child has displayed informative data */
481 do
482 i = getch ();
483 while (i != 13 && i != KEY_ENTER && i != 27);
484 #endif
485 }
486 }
487
diskread(int infd,long totalplay,long skipped,char hd_buf[20],int terminate,int speed,int bits,int stereo)488 void diskread(int infd, long totalplay, long skipped, char hd_buf[20],
489 int terminate, int speed, int bits, int stereo)
490 {
491
492 int count, i, limited = 0;
493 char *tmppt;
494 long numread, totalread = 0;
495 int first = 1;
496
497 static int triggered = 0; /* Have we let the writer go? */
498 static int cbuff = 0; /* Which buffer */
499
500 char tempstring[50];
501 int in_ch;
502
503 if (totalplay) limited = 1;
504 if (totalplay == -1)
505 {
506 totalplay = 0;
507 limited = 1;
508 }
509
510 clearscreen(PLAYWAV_HEADERTEXT);
511 error_window_display("Playing...", " Stop ");
512 nodelay(stdscr, TRUE);
513 refresh();
514
515 while (1)
516 {
517 int trgt;
518
519 /* Wait for a clean buffer */
520 down(disksemid, cbuff);
521 /* Read from the input */
522 numread = 0;
523 trgt = abuf_size;
524 if (limited && (totalread + trgt > totalplay))
525 trgt = totalplay - totalread;
526 tmppt = buffarr[cbuff];
527 if(first && trgt)
528 {
529 buffinf[cbuff].setit = 1;
530 buffinf[cbuff].speed = speed;
531 buffinf[cbuff].bits = bits;
532 buffinf[cbuff].stereo = stereo;
533 if(hd_buf)
534 {
535 memcpy(tmppt, hd_buf, 20);
536 tmppt += 20; numread = 20;
537 }
538 first = 0;
539 }
540 while ( (numread < trgt) &&
541 ((count = read(infd, tmppt, trgt - numread)) != 0) )
542 {
543 tmppt += count; numread += count;
544 }
545 #ifdef DEBUG
546 fprintf(stderr, "in:%d, %d\n", cbuff, numread);
547 #endif
548 /* Update the count for this block */
549 buffinf[cbuff].count = numread;
550 totalread += numread;
551
552 in_ch = getch();
553 /* Was it our last block? */
554 if (numread < abuf_size )
555 {
556 break;
557 }
558 if ( in_ch == 27 || in_ch == KEY_ENTER || in_ch == 13 )
559 {
560 mvprintw(ERROR_WINDOW_Y+2, ERROR_WINDOW_X+1,
561 "Time: ");
562 move(0,79);
563 refresh();
564 break;
565 }
566
567 if(triggered)
568 {
569 up(sndsemid, cbuff);
570
571 fsec2hmsf( (skipped + totalread - BIGBUFFSIZE - 65536.) /
572 (speed * (bits/8) * (stereo+1)), tempstring);
573
574 mvprintw(ERROR_WINDOW_Y+2, ERROR_WINDOW_X+1,
575 "Time: %s", tempstring);
576 move(0,79);
577 refresh();
578
579 /* fprintf(stderr,"\nbyte(1): %ld", skipped + totalread - BIGBUFFSIZE - 65536);
580 */ }
581 else
582 if(cbuff == numbuffs-1)
583 {
584 #ifdef DEBUG
585 fprintf(stderr, "Triggering (in loop)\n");
586 #endif
587 for(i = 0; i < numbuffs; i++)
588 up(sndsemid,i);
589 triggered = 1;
590 }
591 /* Update counter */
592 cbuff++;
593 cbuff %= numbuffs;
594 }
595 /* Finish off this set of buffers */
596 if(terminate)
597 {
598 buffinf[cbuff].last = 1;
599 if(!triggered)
600 #ifdef DEBUG
601 fprintf(stderr, "Triggering (after loop, partial)\n");
602 #endif
603 /* If it wasn't triggered, we haven't filled past cbuff */
604 for(i = 0; i < cbuff; i++)
605 up(sndsemid, i);
606 up(sndsemid, cbuff);
607 }
608 else if((!triggered) && (cbuff == numbuffs-1))
609 {
610 #ifdef DEBUG
611 fprintf(stderr, "Triggering (after loop, full)\n");
612 #endif
613 for(i = 0; i < numbuffs; i++)
614 up(sndsemid,i);
615 triggered = 1;
616 }
617 else if(triggered)
618 up(sndsemid,cbuff);
619 cbuff++;
620 cbuff %= numbuffs;
621 }
622
audiowrite(void)623 volatile void audiowrite(void)
624 {
625 int cbuff = 0, count, numwr, trgt;
626 char *tmpptr;
627
628 /* Uncatch the signals, so we don't clean up twice */
629 signal(SIGINT, SIG_DFL);
630
631 /* Child process writes the audio */
632 while(1)
633 {
634 /* Wait for dirty buffer */
635 down(sndsemid, cbuff);
636 /* Spit to the audio device */
637 if(buffinf[cbuff].setit)
638 {
639 snd_parm(buffinf[cbuff].speed, buffinf[cbuff].bits,
640 buffinf[cbuff].stereo);
641 buffinf[cbuff].setit = 0;
642 }
643 trgt = buffinf[cbuff].count;
644 numwr = 0;
645 tmpptr = buffarr[cbuff];
646 while ( (numwr < trgt) &&
647 ((count = write(audio, tmpptr, trgt - numwr)) > 0) )
648 {
649 if (count == -1)
650 ErrDie("write");
651 numwr += count;
652 tmpptr += count;
653 }
654 #ifdef DEBUG
655 fprintf(stderr, "out:%d, %d\n", cbuff, numwr);
656 #endif
657 /* Was it the last buffer? */
658 if (buffinf[cbuff].last)
659 {
660 up(disksemid, cbuff); /* Not really needed */
661 break;
662 }
663 /* Mark as clean */
664 up(disksemid, cbuff);
665 /* Update counter */
666 cbuff++;
667 cbuff %= numbuffs;
668 }
669 /* Tidy up and be reaped */
670 sync_audio();
671 close(audio);
672 exit(0);
673 }
674
initsems(int disks,int snds)675 void initsems(int disks, int snds)
676 {
677 int i,j;
678
679 for (i=0;i<numsemblks;i++)
680 for (j=0; j<SEMMSL;j++)
681 {
682 if(semctl(disksemid[i], j, SETVAL, (union semun) disks) == -1)
683 ErrDie("semctl");
684 if(semctl(sndsemid[i], j, SETVAL, (union semun) snds) == -1)
685 ErrDie("semctl");
686 }
687 }
688
cleanupsems(void)689 void cleanupsems(void)
690 {
691 int i;
692
693 for (i = 0; i < numsemblks; i++)
694 {
695 semctl(disksemid[i], 0, IPC_RMID, (union semun) 0);
696 semctl(sndsemid[i], 0, IPC_RMID, (union semun) 0);
697 }
698 }
699
sighandler(int i)700 static void sighandler(int i)
701 {
702 #ifndef LP2CD
703 fprintf(stderr, "signal %d received, cleaning up.\n", i);
704 #endif
705 cleanupsems();
706 finish_curses(1);
707 exit(1);
708 }
709
childerrhandler(int i)710 static void childerrhandler(int i)
711 {
712 stoprecording = 1;
713 }
714