5 * Copyright (C) 2006 Red Hat Inc.
7 * rtrecord is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2, or (at your option)
12 * rtrecord is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with rtrecord; see the file COPYING. If not, write to the
19 * Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
26 #define _LARGEFILE_SOURCE
27 #define _LARGEFILE64_SOURCE
28 #define _FILE_OFFSET_BITS 64
46 #include <alsa/asoundlib.h>
47 #include <semaphore.h>
56 char *device
="hw:0,0";
57 snd_pcm_t
*devhandle
=NULL
;
59 int format
= SND_PCM_FORMAT_S16_LE
;
71 pthread_t main_thread_id
;
72 pthread_t record_thread_id
;
73 pthread_t disk_thread_id
;
74 pthread_t tty_thread_id
;
75 pthread_t ui_thread_id
;
80 unsigned char *diskbuffer
=0;
81 sig_atomic_t diskbuffer_head
=0;
82 sig_atomic_t diskbuffer_tail
=0;
85 int diskbuffer_frames
;
90 atomic_mark dma_mark
= {0,0};
91 atomic_mark disk_mark
= {0,0};
93 long dmabuffer_frames
= 0;
94 buffer_meta dma_data
[2] = {
98 channel_meta
*pcm_data
[2];
100 void exit_handler(int sig
){
102 write(ttypipe
[1],"q",1);
106 /* used to redirect the real tty input into the pipe that ncurses
108 void *tty_thread(void *dummy
){
112 int ret
=read(ttyfd
,&buf
,1);
114 write(ttypipe
[1],&buf
,1);
120 /* used to request a ui update (vi the ui sempahore) such that there
121 is absolutely no possibility of blocking if the other UI code is
122 otherwise unresponsive; just writing to the ttypipe could backfire
124 void *ui_thread(void *dummy
){
126 write(ttypipe
[1],"",1);
132 static void record_setup(void){
133 snd_pcm_hw_params_t
*hw
;
134 snd_pcm_uframes_t frames
;
137 if ((ret
= snd_pcm_hw_params_malloc (&hw
)) < 0) {
138 fprintf (stderr
, "capture cannot allocate hardware parameter structure (%s)\n",
143 if ((ret
= snd_pcm_hw_params_any (devhandle
, hw
)) < 0) {
144 fprintf (stderr
, "capture cannot initialize hardware parameter structure (%s)\n",
149 if ((ret
= snd_pcm_hw_params_set_access (devhandle
, hw
, SND_PCM_ACCESS_RW_INTERLEAVED
)) < 0) {
150 fprintf (stderr
, "capture cannot set access type (%s)\n",
155 if ((ret
= snd_pcm_hw_params_set_format (devhandle
, hw
, format
)) < 0) {
156 fprintf (stderr
, "capture cannot set sample format (%s)\n",
161 if ((ret
= snd_pcm_hw_params_set_rate (devhandle
, hw
, rate
, 0)) < 0) {
162 fprintf (stderr
, "capture cannot set sample rate (%s)\n",
167 if ((ret
= snd_pcm_hw_params_set_channels (devhandle
, hw
, channels
)) < 0) {
168 fprintf (stderr
, "capture cannot set channel count (%s)\n",
173 if ((ret
= snd_pcm_hw_params (devhandle
, hw
)) < 0) {
174 fprintf (stderr
, "capture cannot set parameters (%s)\n",
180 if((ret
= snd_pcm_hw_params_get_buffer_size(hw
,&frames
)) < 0){
181 fprintf (stderr
, "capture cannot query buffer size (%s)\n",
186 dmabuffer_frames
= frames
;
189 snd_pcm_hw_params_free (hw
);
191 if ((ret
= snd_pcm_prepare (devhandle
)) < 0) {
192 fprintf (stderr
, "capture cannot prepare audio interface for use (%s)\n",
199 /* realtime 'pounce' thread that grabs data from the audio device (and
200 the little bitty hardware buffer) and stuffs the data, for the time
201 being, into a relatively huge ringbuffer that the disk thread can
202 service at its leisure */
203 void *record_thread(void *dummy
){
204 /* sound device startup */
206 snd_pcm_sframes_t sframes
;
207 int fps16
= (rate
>>4);
210 struct sched_param param
;
211 param
.sched_priority
=89;
212 if(pthread_setschedparam(pthread_self(), SCHED_FIFO
, ¶m
)){
213 fprintf(stderr
,"\nERROR: Could not set realtime priority for capture thread.\n\n");
215 "To run without realtime scheduling, use the --no-realtime or -n option, or\n"
216 "consider running 'record', equivalent to 'rtrecord --no-lock --no-realtime'\n"
217 "Be warned that recording without realtime scheduling may be prone to skips\n"
218 "and pops depending on machine load and specific hardware; using realtime\n"
219 "is strongly suggested for any mission critical recording.\n\n"
220 "To run with realtime scheduling as a non-root user, make the rtrecord\n"
221 "executable setuid root.\n\n");
226 sem_post(&setup_sem
);
230 int head
= diskbuffer_head
;
233 int to_read
= diskbuffer_frames
- head
;
234 if(to_read
> fps16
-frames
) to_read
= fps16
- frames
;
236 /* update high water mark of dma buffer */
237 if(snd_pcm_delay (devhandle
, &sframes
)==0){
238 int watermark
= dmabuffer_frames
-sframes
;
239 if(dma_data
[dma_mark
.bank
].dmabuffer_min
>watermark
)
240 dma_data
[dma_mark
.bank
].dmabuffer_min
=watermark
;
243 /* read -- we always have space due to invariant */
244 ret
= snd_pcm_readi (devhandle
, diskbuffer
+ head
*framesize
,
248 // most likely an underrun
252 case -EPIPE
: case -ESTRPIPE
:
253 // underrun; set starve and reset soft device
254 dma_data
[dma_mark
.bank
].dmabuffer_overrun
=1;
255 snd_pcm_drop(devhandle
);
256 snd_pcm_prepare(devhandle
);
262 // Something went very wrong. Don't hose the machine here in a realtime thread
265 fprintf (stderr
, "unrecoverable capture read error: (%s)\n",
272 if(head
>=diskbuffer_frames
) head
= 0;
277 /* advance the head index, but only if there will be enough
278 space left to write the next frame. if there isn't enough
279 space left, leave the head as-is and set 'underrun'. This
280 wastes a small amount of buffer space but eliminates the
281 need for the sample thread to conditionally block on the disk
283 int tail
= diskbuffer_tail
;
284 int remaining
= diskbuffer_frames
- (tail
<= head
?
286 (head
+ diskbuffer_frames
- tail
));
288 if(remaining
>= fps16
){
290 /* explicit invariant: this is always full_frame aligned
291 (equal numbers of frames in each channel) */
293 if(dma_data
[dma_mark
.bank
].diskbuffer_min
>remaining
)
294 dma_data
[dma_mark
.bank
].diskbuffer_min
=remaining
;
295 if(dma_mark
.read
== dma_mark
.bank
){
296 dma_mark
.bank
= !dma_mark
.bank
;
298 /* initialize fresh bank */
299 dma_data
[dma_mark
.bank
].diskbuffer_min
= diskbuffer_frames
;
300 dma_data
[dma_mark
.bank
].dmabuffer_min
= dmabuffer_frames
;
301 dma_data
[dma_mark
.bank
].diskbuffer_overrun
=0;
302 dma_data
[dma_mark
.bank
].dmabuffer_overrun
=0;
306 diskbuffer_head
= head
;
307 sem_post(&buffer_sem
);
310 /* not enough space for next frame; discard what we just read
312 dma_data
[dma_mark
.bank
].diskbuffer_overrun
=1;
313 dma_data
[dma_mark
.bank
].diskbuffer_min
=0;
314 if(dma_mark
.read
== dma_mark
.bank
){
315 dma_mark
.bank
= !dma_mark
.bank
;
317 /* initialize fresh bank */
318 dma_data
[dma_mark
.bank
].diskbuffer_min
= diskbuffer_frames
;
319 dma_data
[dma_mark
.bank
].dmabuffer_min
= dmabuffer_frames
;
320 dma_data
[dma_mark
.bank
].diskbuffer_overrun
=0;
321 dma_data
[dma_mark
.bank
].dmabuffer_overrun
=0;
328 snd_pcm_close(devhandle
);
332 void PutNumLE(long num
,FILE *f
,int bytes
){
335 fputc((num
>>(i
<<3))&0xff,f
);
340 /* mutant WAV header with undeclared chunklength */
341 void wav_header(FILE *f
,long channels
,long rate
,long bits
){
344 PutNumLE(0xffffffff,f
,4);
345 fprintf(f
,"WAVEfmt ");
348 PutNumLE(channels
,f
,2);
350 PutNumLE(rate
*channels
*((bits
-1)/8+1),f
,4);
351 PutNumLE(((bits
-1)/8+1)*channels
,f
,2);
354 PutNumLE(0xffffffff,f
,4);
357 float aweight_w1
=0.f
;
358 float aweight_w2
=0.f
;
359 float aweight_w3
=0.f
;
360 float aweight_w4
=0.f
;
374 /* A-weighting code borrowed from LADSPA A-weighting plugin */
375 afilter
*aweight_filter
=0;
377 #define AW_F1 20.5990
378 #define AW_F2 107.652
379 #define AW_F3 737.862
380 #define AW_F4 12194.2
382 void aweight_init (int rate
, int channels
){
385 aweight_filter
= calloc(channels
, sizeof(*aweight_filter
));
401 fprintf(stderr
,"A-weighting only available with following sample-rates:\n"
412 aweight_w1
= 2 * M_PI
* f
;
413 aweight_g
*= 2 / (2 - aweight_w1
);
414 aweight_g
*= 2 / (2 - aweight_w1
); // twice !!
415 aweight_w1
*= 1 - 3 * f
;
418 aweight_w2
= 2 * M_PI
* f
;
419 aweight_g
*= 2 / (2 - aweight_w2
);
420 aweight_w2
*= 1 - 3 * f
;
423 aweight_w3
= 2 * M_PI
* f
;
424 aweight_g
*= 2 / (2 - aweight_w3
);
425 aweight_w3
*= 1 - 3 * f
;
429 float aweight_process (float x
, afilter
*f
){
432 f
->_z1a
+= aweight_w1
* (x
- f
->_z1a
+ 1e-40f
);
434 f
->_z1b
+= aweight_w1
* (x
- f
->_z1b
+ 1e-40f
);
436 f
->_z2
+= aweight_w2
* (x
- f
->_z2
+ 1e-40f
);
438 f
->_z3
+= aweight_w3
* (x
- f
->_z3
+ 1e-40f
);
442 f
->_z4a
+= aweight_w4
* (x
- f
->_z4a
);
444 f
->_z4b
+= aweight_w4
* (f
->_z4a
- f
->_z4b
);
447 return aweight_g
* x
;
450 void *disk_thread(void *dummy
){
452 /* write out a 'streaming' WAV header */
454 wav_header(out_FILE
, channels
, rate
, width
);
456 /* become consumer to the record thread's producer */
458 int head
= diskbuffer_head
;
459 int tail
= diskbuffer_tail
;
462 if(tail
> head
) head
= diskbuffer_frames
;
463 full_frames
= (head
- tail
);
465 /* scan audio, compiling metadata */
466 ptr
= diskbuffer
+ tail
* framesize
;
467 for(i
=0;i
<full_frames
;i
++){
468 for(j
=0;j
<channels
;j
++){
474 val
|= (int)((unsigned int)*ptr
);
475 val
|= (int)(((unsigned int)*(ptr
+1))<<8);
476 val
|= (int)(((unsigned int)*(ptr
+2))<<16);
477 val
|= (int)(((unsigned int)*(ptr
+3))<<24);
478 if(val
== 0x7fffffff)pcm_data
[disk_mark
.bank
][j
].pcm_clip
= 1;
481 val
|= (int)(((unsigned int)*ptr
)<<8);
482 val
|= (int)(((unsigned int)*(ptr
+1))<<16);
483 val
|= (int)(((unsigned int)*(ptr
+2))<<24);
484 if(val
== 0x7fffff00)pcm_data
[disk_mark
.bank
][j
].pcm_clip
= 1;
487 val
|= (int)(((unsigned int)*ptr
)<<16);
488 val
|= (int)(((unsigned int)*(ptr
+1))<<24);
489 if(val
== 0x7fff0000)pcm_data
[disk_mark
.bank
][j
].pcm_clip
= 1;
492 val
|= (int)(((unsigned int)*ptr
)<<24);
493 if(val
== 0x7f000000)pcm_data
[disk_mark
.bank
][j
].pcm_clip
= 1;
497 if(val
== (int32_t)0x80000000)pcm_data
[disk_mark
.bank
][j
].pcm_clip
= 1;
499 /* find peak val per channel */
500 if(pcm_data
[disk_mark
.bank
][j
].peak
< abs(val
))
501 pcm_data
[disk_mark
.bank
][j
].peak
= abs(val
);
503 /* find rms/A-weight per-channel */
504 wval
= val
*.00000000046566128730;
506 wval
= aweight_process(wval
,&aweight_filter
[j
]);
507 val
= wval
/ .00000000046566128730;
510 pcm_data
[disk_mark
.bank
][j
].weight_num
+= wval
*wval
;
511 pcm_data
[disk_mark
.bank
][j
].weight_denom
+= 1.;
513 /* write audio out */
514 if(out_FILE
&& !paused
)
515 fwrite(ptr
,1,samplesize
,out_FILE
);
522 if(disk_mark
.read
== disk_mark
.bank
){
523 disk_mark
.bank
= !disk_mark
.bank
;
525 /* initialize new bank */
526 for(j
=0;j
<channels
;j
++){
527 pcm_data
[disk_mark
.bank
][j
].peak
= 0;
528 pcm_data
[disk_mark
.bank
][j
].weight_num
= 0.;
529 pcm_data
[disk_mark
.bank
][j
].weight_denom
= 0.;
530 pcm_data
[disk_mark
.bank
][j
].pcm_clip
= 0;
535 /* atomic tail update */
537 if(tail
== diskbuffer_frames
) tail
= 0;
538 diskbuffer_tail
= tail
;
541 if(diskbuffer_head
== diskbuffer_tail
)
542 sem_wait(&buffer_sem
);
547 const char *optstring
= "ac:d:r:w:qb:ns:hu";
548 struct option options
[] = {
549 {"help",no_argument
,NULL
,'h'},
550 {"no-realtime",no_argument
,NULL
,'n'},
551 {"no-lock",no_argument
,NULL
,'u'},
552 {"a-weight",no_argument
,NULL
,'a'},
553 {"quiet",no_argument
,NULL
,'q'},
554 {"buffer",optional_argument
,NULL
,'b'},
555 {"channels",optional_argument
,NULL
,'c'},
556 {"width",optional_argument
,NULL
,'w'},
557 {"rate",optional_argument
,NULL
,'r'},
558 {"device",optional_argument
,NULL
,'d'},
559 {"smooth",optional_argument
,NULL
,'s'},
563 static void usage(void){
565 "Usage: rtrecord [options] [output_file] \n\n"
567 " -a --a-weight A-weight the RMS level display\n"
568 " default is unweighted\n\n"
569 " -b --buffer <seconds> Request <seconds> of disk buffer\n"
570 " default is 10 seconds\n\n"
571 " -c --channels <n> Request recording of <n> channel audio\n"
572 " default is 2 channels (stereo)\n\n"
573 " -d --device <devname> Record from specified audio device\n"
574 " default is hw:0,0\n\n"
575 " -h --help This help message\n\n"
576 " -n --no-realtime Do not use realtime scheduling\n\n"
577 " -r --rate <n> Request sampling rate of <n> samples per\n"
578 " second. Default is 44100\n\n"
579 " -s --smooth <n> Smooth the VU readouts by the specified\n"
580 " factor; 0 is unsmoothed, 10 is\n"
581 " very smooth; default is 1\n\n"
582 " -q --quiet Produce no terminal output; run silently\n\n"
583 " -u --no-lock Do not try to lock the recording buffer\n"
584 " into physical memory\n\n"
585 " -w --width <n> Request a sample width of <n> bits\n"
588 " p Pause recoding. Pause does not interrupt sampling; it \n"
589 " merely suspends saving/piping recoded data out.\n\n"
591 " space clear clipping and overrun flags\n\n"
592 "rtrecord outputs only uncompressed RIFF WAV format audio. When no\n"
593 "output file is specified and stdout is not redirected away from\n"
594 "the tty, no output is produced; rtrecord will only sample and update\n"
595 "the terminal VU meters.\n\n");
599 int main(int argc
,char *argv
[]){
600 int c
,long_option_index
,ret
,seconds
=10;
603 /* before anything else, drop filesystem privs! */
606 if(!strcmp(argv
[0],"record") ||
607 (strlen(argv
[0])>=7 && !strcmp(argv
[0]+strlen(argv
[0])-7,"/record"))){
608 /* an alias for a version that requires no resources normally
609 limited to root, eg, don't lock the buffer, don't ask for
610 realtime in the record thread */
615 while((c
=getopt_long(argc
,argv
,optstring
,options
,&long_option_index
))!=EOF
){
618 channels
= atoi(optarg
);
621 fprintf(stderr
,"Number of channels must be greater than zero\n");
626 seconds
= atoi(optarg
);
629 fprintf(stderr
,"Disk buffer must be at least 1 second.\n");
635 device
= strdup(optarg
);
657 fprintf(stderr
,"Sampling rate must be greater than zero\n");
663 int s
= atoi(optarg
);
665 fprintf(stderr
,"Minimum 'smooth' value is 0\n");
669 fprintf(stderr
,"Maximum 'smooth' value is 10\n");
677 width
= atoi(optarg
);
680 format
= SND_PCM_FORMAT_S8
;
684 format
= SND_PCM_FORMAT_S16_LE
;
688 format
= SND_PCM_FORMAT_S24_3LE
;
692 format
= SND_PCM_FORMAT_S32_LE
;
696 fprintf(stderr
,"Supported linear PCM sampling widths: 8, 16, 24 and 32 bits\n");
706 /* assume that anything following the options must be a filename */
707 out_name
= strdup(argv
[optind
]);
708 out_FILE
= fopen(out_name
,"wb");
710 fprintf(stderr
,"Unable to open file %s for writing.\n",out_name
);
714 if(!isatty(STDOUT_FILENO
)){
715 /* use stdout for writing, except that curses is fairly hardwired
716 to want stdout, so dup this first... */
717 int newfd
= dup(STDOUT_FILENO
);
719 out_FILE
= fdopen(newfd
,"wb");
726 /* if stdout isn't a terminal, and we're not running silent... */
727 if(!quiet
&& (!isatty(STDOUT_FILENO
))){
728 /* if stderr is the terminal, let's use that. */
729 if(isatty(STDERR_FILENO
)){
730 /* curses is fairly hardwired to want stdout, so remap stderr to stdout */
731 dup2(STDERR_FILENO
,STDOUT_FILENO
);
733 fprintf(stderr
,"No terminal available for panel output; assuming -q\n");
739 aweight_init(rate
,channels
);
743 /* Open hardware sample device */
744 if ((ret
= snd_pcm_open (&devhandle
, device
, SND_PCM_STREAM_CAPTURE
, 0)) < 0) {
745 fprintf(stderr
,"unable to open audio device %s for record: %s.\n",device
,snd_strerror(ret
));
750 /* set up tty input subversion so that ncurses can be fully
751 functional entirely withing one thread */
752 ttyfd
=open("/dev/tty",O_RDONLY
);
754 fprintf(stderr
,"Unable to open /dev/tty:\n"
755 " %s\n",strerror(errno
));
759 fprintf(stderr
,"Unable to open tty pipe:\n"
760 " %s\n",strerror(errno
));
765 framesize
= samplesize
* channels
;
766 diskbuffer_frames
= (rate
*seconds
/getpagesize()+1)*getpagesize();
767 diskbuffer_size
= diskbuffer_frames
*framesize
;
770 /* set up record buffer, lock it in-core */
771 diskbuffer
= mmap(0, diskbuffer_size
, PROT_READ
|PROT_WRITE
, MAP_PRIVATE
| MAP_ANONYMOUS
| MAP_LOCKED
, 0, 0);
772 if(diskbuffer
== MAP_FAILED
){
774 fprintf(stderr
,"\nERROR: Unable to mmap locked buffer for capture;\n",strerror(errno
));
775 fprintf(stderr
,"The configured per-user resource limits currently disallow\n"
776 "locking enough memory in-core for a recording ringbuffer.\n\n"
777 "To run with an unlocked ringbuffer, use the -u option; an unlocked\n"
778 "ringbuffer may be slightly less reliable on a loaded machine.\n\n"
779 "To allow running with a locked ringbuffer, consider unlimiting\n"
780 "\"max locked memory\" for selected users or installing rtrecord\n"
783 fprintf(stderr
,"Unable to mmap locked buffer for capture:\n %s\n",strerror(errno
));
789 diskbuffer
= malloc(diskbuffer_size
);
792 /* set up shared metadata feedback structs */
793 pcm_data
[0] = calloc(channels
,sizeof(**pcm_data
));
794 pcm_data
[1] = calloc(channels
,sizeof(**pcm_data
));
796 sem_init(&ui_sem
,0,0);
797 sem_init(&buffer_sem
,0,0);
798 sem_init(&setup_sem
,0,0);
800 /* spawn a small army of threads */
801 pthread_create(&disk_thread_id
,NULL
,disk_thread
,NULL
);
802 pthread_create(&record_thread_id
,NULL
,record_thread
,NULL
);
804 /* wait for realtime thread to declare it has started up */
805 sem_wait(&setup_sem
);
807 /* drop privs, then continue starting threads */
810 pthread_create(&tty_thread_id
,NULL
,tty_thread
,NULL
);
811 pthread_create(&ui_thread_id
,NULL
,ui_thread
,NULL
);
812 main_thread_id
=pthread_self();
814 signal(SIGINT
,exit_handler
);
815 signal(SIGSEGV
,exit_handler
);
816 signal(SIGPIPE
,exit_handler
);
820 terminal_init_panel();
822 /* setup complete; perform work */
823 terminal_main_loop(quiet
);
825 /* remove the panel if possible */
827 terminal_remove_panel();
831 /* wake the disk thread if its asleep */
832 sem_post(&buffer_sem
);
834 /* wake the ui thread if its asleep */
837 /* waking the tty thread cleanly would require another fd and a
838 select(); much easier to just uncleanly cancel it */
839 pthread_cancel(tty_thread_id
);
841 pthread_join(ui_thread_id
, NULL
);
842 pthread_join(tty_thread_id
, NULL
);
843 pthread_join(record_thread_id
, NULL
);
844 pthread_join(disk_thread_id
, NULL
);
845 sem_destroy(&ui_sem
);
846 sem_destroy(&buffer_sem
);