#include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_SOUNDCARD_H # include #endif #ifdef HAVE_SYS_SOUNDCARD_H # include #endif /* -------------------------------------------------------------------- */ static void tty_raw(void) { initscr(); cbreak(); noecho(); keypad(stdscr,1); refresh(); } static void tty_restore(void) { endwin(); } /* -------------------------------------------------------------------- */ static int sound_fd; static int sound_rcount; static unsigned int sound_blksize; static int16_t *sound_buffer; static int maxl,maxr; static int secl,secr; static int *histl,*histr,histn,histi; static float peak_seconds = 1.5; static char *audio_dev = "/dev/dsp"; static int sound_open(int rate) { int frag,afmt,channels,trigger,srate; if (-1 == (sound_fd = open(audio_dev, O_RDONLY))) { fprintf(stderr,"open %s: %s\n",audio_dev,strerror(errno)); exit(1); } frag = 0x7fff000d; /* 8k */ if (-1 == ioctl(sound_fd, SNDCTL_DSP_SETFRAGMENT, &frag)) perror("ioctl SNDCTL_DSP_SETFRAGMENT"); /* format */ afmt = AFMT_S16_LE; if (-1 == ioctl(sound_fd, SNDCTL_DSP_SETFMT, &afmt)) { perror("ioctl SNDCTL_DSP_SETFMT"); exit(1); } if (afmt != AFMT_S16_LE) { fprintf(stderr,"can't set sound format to 16 bit (le)\n"); exit(1); } /* channels */ channels = 2; if (-1 == ioctl(sound_fd, SNDCTL_DSP_CHANNELS, &channels)) { perror("ioctl SNDCTL_DSP_CHANNELS"); exit(1); } if (channels != 2) { fprintf(stderr,"can't record in stereo\n"); exit(1); } /* rate */ srate = rate; if (-1 == ioctl(sound_fd, SNDCTL_DSP_SPEED, &srate)) { perror("ioctl SNDCTL_DSP_SPEED"); exit(1); } /* accept +/- 1% */ if (srate < rate * 99 / 100 || srate > rate * 101 / 100) { fprintf(stderr,"can't set sample rate to %d (got %d)\n", rate,srate); exit(1); } /* get block size */ if (-1 == ioctl(sound_fd, SNDCTL_DSP_GETBLKSIZE, &sound_blksize)) { perror("ioctl SNDCTL_DSP_GETBLKSIZE"); exit(1); } if (0 == sound_blksize) sound_blksize = 4096; sound_buffer = malloc(sound_blksize); /* peak level history */ histn = peak_seconds * rate * 4 / sound_blksize; histl = malloc(histn * sizeof(int)); histr = malloc(histn * sizeof(int)); memset(histl,0,histn * sizeof(int)); memset(histr,0,histn * sizeof(int)); /* trigger record */ trigger = ~PCM_ENABLE_INPUT; ioctl(sound_fd,SNDCTL_DSP_SETTRIGGER,&trigger); trigger = PCM_ENABLE_INPUT; ioctl(sound_fd,SNDCTL_DSP_SETTRIGGER,&trigger); return sound_fd; } static int sound_read(void) { unsigned int have; int i,rc; int16_t *v; /* read */ for (have = 0;have < sound_blksize;) { rc = read(sound_fd,sound_buffer+have,sound_blksize-have); switch (rc) { case -1: if (EINTR != errno) { perror("read sound"); exit(1); } break; case 0: fprintf(stderr,"Huh? got 0 bytes from sound device?\n"); exit(1); default: have += rc; } } /* look for peaks */ maxl = 0; maxr = 0; for (i = sound_blksize>>2, v=sound_buffer; i > 0; i--) { if (abs(*v) > maxl) maxl = abs(*v); v++; if (abs(*v) > maxr) maxr = abs(*v); v++; } /* max for the last second */ histl[histi] = maxl; histr[histi] = maxr; histi++; if (histn == histi) histi = 0; for (secl = 0, secr = 0, i = 0; i < histn; i++) { if (secl < histl[i]) secl = histl[i]; if (secr < histr[i]) secr = histr[i]; } sound_rcount++; return 0; } /* -------------------------------------------------------------------- */ char *names[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_NAMES; char *config_names[SOUND_MIXER_NRDEVICES][4]; static int mix; static int dev = -1; static int volume; static char *mixer_dev = "/dev/mixer"; static int mixer_open(char *filename, char *device) { int i, devmask; if (-1 == (mix = open(filename,O_RDONLY))) { fprintf(stderr,"open %s: %s\n",filename,strerror(errno)); exit(1); } if (-1 == ioctl(mix,MIXER_READ(SOUND_MIXER_DEVMASK),&devmask)) { perror("mixer read devmask"); exit(1); } for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { if ((1<\n" "// use gcdmaster to edit\n" "// use cdrdao to burn\n" "\n" "CD_DA\n" "\n"); for (fs = 0, i = 0; i < tracks; fs = ff, i++) { ff = tracksplits[i] / 2352; fl = ff - fs; fprintf(fp, "TRACK AUDIO\n" "AUDIOFILE \"%s\" %d:%02d:%02d %d:%02d:%02d\n" "\n", wavfile, fs/(75*60), (fs/75)%60, fs%75, fl/(75*60), (fl/75)%60, fl%75); } fclose(fp); return 0; } static int record_start(char *wavfile, char *tocfile, int *nr) { int wav; do { sprintf(wavfile,"%s%03d.wav",filename,(*nr)); sprintf(tocfile,"%s%03d.toc",filename,(*nr)); (*nr)++; wav = open(wavfile, O_WRONLY | O_EXCL | O_CREAT, 0666); } while ((-1 == wav) && (EEXIST == errno)); if (-1 == wav) { perror("open"); exit(1); } wav_start_write(wav,rate); memset(tracksplits,0,sizeof(tracksplits)); tracks = 0; return wav; } static void record_stop(int wav, char *wavfile, char *tocfile) { if (tracks) { tracksplits[tracks++] = wav_size; write_toc(wavfile,tocfile); } wav_stop_write(wav); close(wav); switch (mode) { case CONSOLE: if (verbose) printf("\n"); break; case NCURSES: mvprintw(3,0,"%*.*s",COLS-1,COLS-1,blank); break; } } static size_t parse_size(const char *arg) { int value; char mul[4]; off_t retval = -1; if (2 != sscanf(arg,"%d%3s",&value,mul)) return 0; if (0 == strcasecmp(mul,"g") || 0 == strcasecmp(mul,"gb")) retval = (off_t)value * 1024 * 1024 * 1024; if (0 == strcasecmp(mul,"m") || 0 == strcasecmp(mul,"mb")) retval = (off_t)value * 1024 * 1024; if (0 == strcasecmp(mul,"k") || 0 == strcasecmp(mul,"kb")) retval = (off_t)value * 1024; return retval; } static char* str_mb(off_t value) { static char buf[32]; if (value > (1 << 30)) { value = (value * 10) >> 30; sprintf(buf,"%d.%d GB",(int)(value/10),(int)(value%10)); return buf; } if (value > (1 << 20)) { value = (value * 10) >> 20; sprintf(buf,"%d.%d MB",(int)(value/10),(int)(value%10)); return buf; } value >>= 10; sprintf(buf,"%3d kB",(int)value); return buf; } /* -------------------------------------------------------------------- */ char *progname; char *input = "line"; char *str_maxsize = "2GB"; int level_trigger; static void usage(FILE *fp) { fprintf(fp, "\n" "%s records sound in CD-Quality (44100/16bit/stereo).\n" "It has a nice ascii-art input-level meter. It is a\n" "interactive curses application. You'll need a fast\n" "terminal, don't try this on a 9600 bps vt100...\n" "\n" "%s has several options:\n" " -h this text\n" " -o file output file basename [%s], a number and the .wav\n" " extension are added by %s.\n" " -i ctrl mixer control [%s]. This should be the one\n" " where you can adjust the record level for\n" " your audio source, \"line\", \"mic\" and \"igain\"\n" " are good candidates.\n" " -m dev set mixer device [%s]\n" " -d dev set dsp device [%s]\n" " -r rate set sample rate [%d]\n" " -p sec peak seconds [%.1f]\n" "\n" "for non-interactive usage only:\n" " -c enable console (non-interactive) mode\n" " -v be verbose (show progress)\n" " -t mm:ss limit the time to record. By default it records\n" " until stopped by a signal (^C)\n" " -s size set max file size [%s]. You have to give number\n" " and unit without space inbetween, i.e. \"100mb\".\n" " -n num limit amount of files recorded, quits when\n" " reached.\n" " -l signal level triggered recording.\n" " -L level same as above + specify trigger level [%d]\n" "\n", progname,progname,filename,progname, input,mixer_dev,audio_dev, rate,peak_seconds,str_maxsize, level_trigger ? level_trigger : 1000); } int main(int argc, char *argv[]) { int c,key,vol,delay,auto_adjust; int record,nr,wav=0; char *wavfile; char *tocfile; fd_set s; int sec,maxhour,maxmin,maxsec; int maxfiles = 0; size_t maxsize; /* init some vars */ progname = strrchr(argv[0],'/'); progname = progname ? progname+1 : argv[0]; maxsec = 0; delay = 0; auto_adjust = 1; record = 0; nr = 0; /* parse options */ for (;;) { if (-1 == (c = getopt(argc, argv, "vhlci:o:d:m:r:t:s:L:p:n:"))) break; switch (c) { case 'v': verbose = 1; break; case 'l': level_trigger = 1000; break; case 'L': level_trigger = atoi(optarg); break; case 'i': input = optarg; break; case 'o': filename = optarg; break; case 'd': audio_dev = optarg; break; case 'm': mixer_dev = optarg; break; case 'c': mode = CONSOLE; break; case 'r': rate = atoi(optarg); break; case 'p': peak_seconds = atof(optarg); break; case 't': if (3 != sscanf(optarg,"%d:%d:%d",&maxhour,&maxmin,&maxsec)) { maxhour = 0; if (2 != sscanf(optarg,"%d:%d",&maxmin,&maxsec)) { fprintf(stderr,"time parse error\n"); exit(1); } } maxsec += maxmin * 60; maxsec += maxhour * 60 * 60; break; case 's': str_maxsize = optarg; break; case 'n': maxfiles = atoi(optarg); break; case 'h': usage(stdout); exit(0); default: usage(stderr); exit(1); } } maxsize = parse_size(str_maxsize); if (0 == maxsize) { fprintf(stderr,"maxsize parse error [%s]\n",str_maxsize); exit(1); } mixer_open(mixer_dev,input); sound_open(rate); wavfile = malloc(strlen(filename)+16); tocfile = malloc(strlen(filename)+16); if (mode == NCURSES) { tty_raw(); atexit(tty_restore); } signal(SIGINT,ctrlc); signal(SIGQUIT,ctrlc); signal(SIGTERM,ctrlc); signal(SIGHUP,ctrlc); if (mode == NCURSES) { mvprintw( 5,0,"record to %s*.wav",filename); mvprintw( 7,0,"left/right adjust mixer level for \"%s\"",input); mvprintw( 8,0,"space starts/stops recording"); /* line 9 is printed later */ mvprintw(10,0," auto-adjust reduces the record level on overruns"); mvprintw(11,0,"'N' next file (same as space twice, but without break)"); mvprintw(12,0,"'T' tracksplit (writes toc file)"); mvprintw(13,0,"'Q' quit"); mvprintw(LINES-3,0,"--"); mvprintw(LINES-2,0,"(c) 1999-2003 Gerd Knorr "); for (;!stop;) { refresh(); FD_ZERO(&s); FD_SET(0,&s); FD_SET(sound_fd,&s); if (-1 == select(sound_fd+1,&s,NULL,NULL,NULL)) { if (EINTR == errno) continue; perror("select"); break; } if (FD_ISSET(sound_fd,&s)) { /* sound */ if (-1 == sound_read()) break; if (delay) delay--; if (auto_adjust && (0 == delay) && (maxl >= 32767 || maxr >= 32767)) { /* auto-adjust */ vol = mixer_get_volume(); vol--; if (vol < 0) vol = 0; mixer_set_volume(vol); delay = 3; } print_bar(0,input,mixer_get_volume(),-1,100); print_bar(1,"left",maxl,secl,32768); print_bar(2,"right",maxr,secr,32768); mvprintw(9,0,"'A' toggle auto-adjust [%s] ", auto_adjust ? "on" : "off"); if (record) { wav_write_audio(wav,sound_buffer,sound_blksize); sec = wav_size / (rate*4); mvprintw(3,0,"%s: %3d:%02d (%s) ",wavfile, sec/60,sec%60,str_mb(wav_size)); if (tracks) { int f = tracksplits[tracks-1] / 2352; printw(" | %d @ %d:%02d:%02d ", tracks, f/(75*60), (f/75)%60, f%75); } } else { mvprintw(3,0,"%c",ALIVE(sound_rcount)); } } if (FD_ISSET(0,&s)) { /* tty in */ switch (key = getch()) { case 'Q': case 'q': stop = 1; break; case 'A': case 'a': auto_adjust = !auto_adjust; break; case 'N': case 'n': if (record) { record_stop(wav,wavfile,tocfile); wav = record_start(wavfile,tocfile,&nr); } break; case 'T': case 't': if (record && tracks < 98) tracksplits[tracks++] = wav_size; break; case ' ': if (!filename) break; if (!record) { /* start */ wav = record_start(wavfile,tocfile,&nr); record=1; auto_adjust=0; } else { /* stop */ record_stop(wav,wavfile,tocfile); record=0; } break; case KEY_RIGHT: vol = mixer_get_volume(); vol++; if (vol > 100) vol = 100; mixer_set_volume(vol); break; case KEY_LEFT: vol = mixer_get_volume(); vol--; if (vol < 0) vol = 0; mixer_set_volume(vol); break; } } } } if (mode == CONSOLE) { if (!level_trigger) { wav = record_start(wavfile,tocfile,&nr); record=1; } for (;!stop;) { if (-1 == sound_read()) break; if (level_trigger) { if (!record && (maxl > level_trigger || maxr > level_trigger)) { wav = record_start(wavfile,tocfile,&nr); record=1; } if (record && secl < level_trigger && secr < level_trigger) { record_stop(wav,wavfile,tocfile); record=0; if (maxfiles && nr == maxfiles) break; } } if (!record) { printf("waiting for signal %c [%d/%d]... \r", ALIVE(sound_rcount), maxl,maxr); fflush(stdout); continue; } sec = (done_size + wav_size) / (rate*4); if (maxsec && sec >= maxsec) break; if (wav_size + sound_blksize + sizeof(WAVEHDR) > maxsize) { record_stop(wav,wavfile,tocfile); wav = record_start(wavfile,tocfile,&nr); } wav_write_audio(wav,sound_buffer,sound_blksize); if (verbose) { int total = 10; int len = (maxl+maxr)*total/32768/2; printf("|%*.*s%*.*s| %s %d:%02d", len,len,full, total-len,total-len,empty, wavfile,sec/60,sec%60); if (maxsec) printf("/%d:%02d",maxsec/60,maxsec%60); printf(" (%s",str_mb(wav_size)); if (done_size) printf(", %s total",str_mb(done_size + wav_size)); printf(") \r"); fflush(stdout); } } } if (record) record_stop(wav,wavfile,tocfile); mixer_close(); exit(0); }