├── theora2flv ├── Makefile ├── audio.pyx ├── arecord.py ├── amplify.py ├── README ├── record.py └── arecord.c /theora2flv: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ffmpeg -b 800k -i $1 $1.flv 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | cython audio.pyx 3 | gcc -fPIC -I/usr/include/python2.6 -c -o audio.o audio.c 4 | gcc -fPIC -c -o arecord.o arecord.c 5 | gcc -shared -o audio.so audio.o arecord.o -lasound 6 | -------------------------------------------------------------------------------- /audio.pyx: -------------------------------------------------------------------------------- 1 | cdef extern int run(char *filename) nogil 2 | cdef extern void stop() nogil 3 | 4 | def capture(filename): 5 | with nogil: 6 | run(filename) 7 | 8 | def capture_stop(): 9 | with nogil: 10 | stop() 11 | -------------------------------------------------------------------------------- /arecord.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | from time import sleep 4 | from threading import Thread 5 | 6 | from audio import capture, capture_stop 7 | 8 | class Audio(Thread): 9 | 10 | def __init__(self, filename): 11 | Thread.__init__(self) 12 | self._filename = filename 13 | 14 | def run(self): 15 | capture(self._filename) 16 | 17 | def stop(self): 18 | capture_stop() 19 | 20 | a = Audio("c.wav") 21 | a.start() 22 | try: 23 | while 1: 24 | sleep(0.1) 25 | finally: 26 | a.stop() 27 | -------------------------------------------------------------------------------- /amplify.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | """ 4 | Amplifies a wav file. 5 | """ 6 | 7 | from sys import argv 8 | import wave 9 | import audioop 10 | 11 | def normalize(filein, fileout): 12 | a = wave.open(filein) 13 | params = a.getparams() 14 | nchannels, sampwidth, framerate, nframes, comptype, compname = params 15 | data = a.readframes(nframes) 16 | 17 | if sampwidth == 1: 18 | max_val = 0x7f 19 | elif sampwidth == 2: 20 | max_val = 0x7fff 21 | elif sampwidth == 4: 22 | max_val == 0x7fffffff 23 | 24 | max = audioop.max(data, sampwidth) 25 | factor = float(max_val)/max 26 | #factor = 200 27 | print "amplifying by F=%f" % factor 28 | 29 | data = audioop.mul(data, sampwidth, factor) 30 | 31 | b = wave.open(fileout, "w") 32 | b.setparams(params) 33 | b.writeframes(data) 34 | 35 | if len(argv) == 3: 36 | normalize(argv[1], argv[2]) 37 | else: 38 | print "usage: amplify.py infile outfile" 39 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Record 2 | ------ 3 | 4 | The record.py script uses gtk's "get_from_drawable()" function to take 5 | screenshots and modified alsa's arecord to record the sound. 6 | 7 | More details how it works: 8 | 9 | The arecord and aplay alsa utilities are actually one and the same program, so 10 | it was stripped down, all signals removed, all playback removed, all header 11 | files merged into it, then a "capture_stop" global variable was added, which if 12 | set to 1, the main audio capture loop will end. Then it is called from cython 13 | using nogil in it's own thread and when the user wants to end it, the 14 | "capture_stop" is set to 1. The main python thread takes screenshots in 15 | periodic intervals (15 fps by default) and if it's late, it skips the frame, so 16 | that the next one is on time. All screenshots are saved to the "data" file in 17 | the temporary directory, which is later read and converted to a set of png 18 | images. The audio is saved to a wav file. 19 | 20 | It is then your job to create a video from it (and to convert sound to any 21 | format you like, like ogg vorbis), it tells you some suggestions (see below). 22 | Also the amplifier.py script is provided to amplify volume of the wav file in 23 | case it is too quiet. 24 | 25 | Usage 26 | ----- 27 | 28 | Install the following packages in Debian/Ubuntu: 29 | 30 | sudo apt-get install python-gtk2 libasound2-dev 31 | 32 | and run it: 33 | 34 | ./record.py 35 | 36 | It produces a wav file in the tmp directory (it tells you which) and a set of 37 | png images. It also suggests you exact commands how to create videos from it, 38 | using either mencoder or ffmpeg2theora (it's up to you). 39 | 40 | In my experience mencoder can't sync the sound and video correctly, while 41 | ffmpeg2theora just works. If the sound is too quiet, use the amplifier.py 42 | utility to amplify the wav file before converting it to ogg or the 43 | "normalize-audio" program (better results) from the Debian package of the same 44 | name (it operates *inplace* on the wav file). 45 | 46 | Convert to FLV 47 | -------------- 48 | 49 | In order to convert to FLV, so that you can play it with flowplayer (flash 50 | player in the browser), use the script theora2flv. 51 | 52 | License 53 | ------- 54 | 55 | The arecord.c file is GPL (see alsa-utils for more info), all the other code is 56 | BSD. The package as a whole is GPL due to arecord.c. 57 | 58 | Flowplayer 59 | ---------- 60 | 61 | Example of usage: 62 | 63 | 65 | 67 | 77 | -------------------------------------------------------------------------------- /record.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | """ 4 | Captures a video of a window plus audio, then encodes this into one media file. 5 | 6 | Other tips: 7 | 8 | 1) extracting audio from avi file: 9 | 10 | $ mplayer -ao pcm:fast:file=audio_out.wav -vo null -vc null some_video.ogv 11 | 12 | 2) taking video from one file and audio from another: 13 | 14 | $ mencoder -audiofile audio_out.wav -oac lavc -ovc lavc some_video.ogv \ 15 | -o out.avi 16 | 17 | """ 18 | 19 | import sys 20 | import os 21 | import re 22 | from time import sleep 23 | from timeit import default_timer as clock 24 | from subprocess import Popen, PIPE, check_call, STDOUT 25 | from tempfile import mkdtemp 26 | from select import select 27 | from cPickle import dump, load 28 | from optparse import OptionParser 29 | from threading import Thread 30 | 31 | import gtk 32 | from PIL import Image 33 | 34 | from audio import capture, capture_stop 35 | 36 | class Audio(Thread): 37 | 38 | def __init__(self, filename): 39 | Thread.__init__(self) 40 | self._filename = filename 41 | 42 | def run(self): 43 | capture(self._filename) 44 | 45 | def stop(self): 46 | capture_stop() 47 | 48 | class Video(object): 49 | 50 | def __init__(self, tmpdir, win_id=None, fps=15): 51 | """ 52 | Starts capturing the video and saves it to a file 'filename'. 53 | 54 | win_id ... the window id to capture, if None, it automatically runs 55 | xwininfo and parses the output to capture the windows id 56 | """ 57 | x, y, w, h = self.get_active_window_pos() 58 | self.x = x 59 | self.y = y 60 | self.width = w 61 | self.height = h 62 | self.fps = fps 63 | self.tmpdir = tmpdir 64 | self.screengrab = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, 8, w, h) 65 | self._datafile = open(tmpdir+"/data", "w") 66 | headers = (w, h, self.screengrab.get_rowstride(), fps) 67 | dump(headers, self._datafile) 68 | 69 | def wait(self, fps=2): 70 | """ 71 | Generates consecutive integers with the given "fps". 72 | 73 | It maintains the constant fps, and if necessary, it skips frames (in this 74 | case the "skip" variable returns the number of skipped frames). 75 | """ 76 | i = 1 77 | t = clock() 78 | while 1: 79 | free_count = 0 80 | skip = 0 81 | while free_count == 0: 82 | while clock()-t < float(i)/fps: 83 | free_count += 1 84 | if free_count == 0: 85 | i += 1 86 | skip += 1 87 | yield i-1, skip 88 | i += 1 89 | 90 | def start(self): 91 | t = clock() 92 | t_start = t 93 | for i, skip in self.wait(fps=self.fps): 94 | t_new = clock() 95 | self.screengrab.get_from_drawable(gtk.gdk.get_default_root_window(), 96 | gtk.gdk.colormap_get_system(), 97 | self.x, self.y, 0, 0, self.width, self.height) 98 | img = self.screengrab.get_pixels() 99 | frame_header = (len(img), skip) 100 | dump(frame_header, self._datafile) 101 | self._datafile.write(img) 102 | i += 1 103 | print "time: %.3f, frame: %04d, current fps: %6.3f, skip: %d, " \ 104 | "lag: %.6f" % ( t_new-t_start, i, 1/(t_new-t), skip, 105 | t_new-t_start - float(i)/self.fps) 106 | t = t_new 107 | 108 | def get_window_pos(self, win_id, dX=0, dY=-17, dw=2, dh=16): 109 | """ 110 | Determines the correct window position and dimensions. 111 | 112 | win_id ... windows ID to whose parameters are to be determined 113 | dX, dY, dw, dh ... those are corrections that have to be applied to 114 | what is read from xwininfo 115 | 116 | Note: they are not precised anyways, there seems to be some weird 117 | rounding to multiplies of 4... 118 | """ 119 | p = Popen(["xwininfo", "-id", win_id], stdout=PIPE) 120 | out = p.communicate()[0] 121 | if p.returncode != 0: 122 | raise Exception("xwininfo failed") 123 | X = int(re.search("Absolute upper-left X:.*?(\d+)", out).groups()[0]) 124 | Y = int(re.search("Absolute upper-left Y:.*?(\d+)", out).groups()[0]) 125 | width = int(re.search("Width:.*?(\d+)", out).groups()[0]) 126 | height = int(re.search("Height:.*?(\d+)", out).groups()[0]) 127 | X += dX 128 | Y += dY 129 | width += dw 130 | height += dh 131 | return X, Y, width, height 132 | 133 | def get_active_window_pos(self): 134 | root = gtk.gdk.screen_get_default() 135 | win = root.get_active_window() 136 | winw, winh = win.get_geometry()[2:4] 137 | _or, _ror = win.get_origin(), win.get_root_origin() 138 | border, titlebar = _or[0] - _ror[0], _or[1] - _ror[1] 139 | w, h = winw + (border*2), winh + (titlebar+border) 140 | x, y = win.get_root_origin() 141 | return x, y, w, h 142 | 143 | def get_window_id(self): 144 | p = Popen("xwininfo", stdout=PIPE) 145 | out = p.communicate()[0] 146 | if p.returncode != 0: 147 | raise Exception("xwininfo failed") 148 | s = "Window id: " 149 | i1 = out.find(s) + len(s) 150 | i2 = out.find(" ", i1) 151 | id = out[i1: i2] 152 | return id 153 | 154 | def convert(self): 155 | self._datafile.close() 156 | f = open(self.tmpdir+"/data") 157 | data = load(f) 158 | img_width, img_height, stride, fps = data 159 | print img_width, img_height, stride, fps 160 | i = 0 161 | while 1: 162 | try: 163 | frame_headers = load(f) 164 | except EOFError: 165 | break 166 | n, skip = frame_headers 167 | for j in range(skip): 168 | # ideally this should be interpolated with the next image 169 | img.save(self.tmpdir + "/screen%04d.png" % i) 170 | i += 1 171 | pixels = f.read(n) 172 | img = Image.frombuffer("RGB", (img_width, img_height), 173 | pixels, "raw", "RGB", stride, 1) 174 | img.save(self.tmpdir + "/screen%04d.png" % i) 175 | print i 176 | i += 1 177 | print "images saved to: %s" % self.tmpdir 178 | 179 | 180 | def encode(audio, video, output): 181 | """ 182 | Combines the audio and video to a resulting file. 183 | """ 184 | check_call(["mencoder", "-audiofile", audio, "-oac", "lavc", "-ovc", 185 | "lavc", video, "-o", output], stdin=PIPE, stdout=PIPE, stderr=STDOUT) 186 | 187 | if __name__ == "__main__": 188 | parser = OptionParser() 189 | parser.add_option("-f", "--file", dest="filename", default="out.avi", 190 | help="save to FILE [default: %default]", metavar="FILE") 191 | parser.add_option("-w", "--window", dest="window", 192 | help="window id to capture", default=None) 193 | options, args = parser.parse_args() 194 | 195 | tmp_dir = mkdtemp() 196 | video_file = os.path.join(tmp_dir, "video.ogv") 197 | audio_file = os.path.join(tmp_dir, "audio.wav") 198 | print "work dir:", tmp_dir 199 | print "select a window to capture (2s sleep)" 200 | sleep(2) 201 | print "active window selected" 202 | v = Video(tmp_dir, options.window) 203 | a = Audio(audio_file) 204 | print "Capturing audio and video. Press CTRL-C to stop." 205 | try: 206 | try: 207 | a.start() 208 | v.start() 209 | except KeyboardInterrupt: 210 | pass 211 | finally: 212 | a.stop() 213 | print "stopped." 214 | print "converting to png images" 215 | v.convert() 216 | print "To encode using mencoder:" 217 | print "-"*80 218 | print "mencoder mf://%s/*.png -mf fps=%d -audiofile %s -oac lavc " \ 219 | "-ovc lavc -lavcopts vcodec=mpeg4:vbitrate=800 -o v.avi" % \ 220 | (tmp_dir, v.fps, audio_file) 221 | print "-"*80 222 | print 223 | print "To encode using theora:" 224 | print "-"*80 225 | print "ffmpeg2theora -F %d -v 10 %s/screen%%04d.png -o tmp.ogv" % \ 226 | (v.fps, tmp_dir) 227 | print "normalize-audio %s # beware: normalizes *in place*" % audio_file 228 | print "oggenc %s" % audio_file 229 | print "oggz-merge -o v.ogv tmp.ogv %sogg" % audio_file[:-3] 230 | print "-"*80 231 | print 232 | print "To just archive audio and video in a lossless format for later processing:" 233 | print "-"*80 234 | print "advmng -v -a %d video.mng %s/screen*.png" % (v.fps, tmp_dir) 235 | print "flac -o audio.flac %s" % audio_file 236 | print "-"*80 237 | print "(Use 'advmng -x video.mng' to extract the png images)" 238 | -------------------------------------------------------------------------------- /arecord.c: -------------------------------------------------------------------------------- 1 | /* 2 | This file was taken from alsa-utils aplay.c and stripped down, see README 3 | for more details. This file is GPL, see the license information in 4 | alsa-utils for more info. 5 | */ 6 | #define _GNU_SOURCE 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include 28 | 29 | #include 30 | #include 31 | 32 | /* Definitions for Microsoft WAVE format */ 33 | 34 | #if __BYTE_ORDER == __LITTLE_ENDIAN 35 | #define COMPOSE_ID(a,b,c,d) ((a) | ((b)<<8) | ((c)<<16) | ((d)<<24)) 36 | #define LE_SHORT(v) (v) 37 | #define LE_INT(v) (v) 38 | #define BE_SHORT(v) bswap_16(v) 39 | #define BE_INT(v) bswap_32(v) 40 | #elif __BYTE_ORDER == __BIG_ENDIAN 41 | #define COMPOSE_ID(a,b,c,d) ((d) | ((c)<<8) | ((b)<<16) | ((a)<<24)) 42 | #define LE_SHORT(v) bswap_16(v) 43 | #define LE_INT(v) bswap_32(v) 44 | #define BE_SHORT(v) (v) 45 | #define BE_INT(v) (v) 46 | #else 47 | #error "Wrong endian" 48 | #endif 49 | 50 | #define WAV_RIFF COMPOSE_ID('R','I','F','F') 51 | #define WAV_WAVE COMPOSE_ID('W','A','V','E') 52 | #define WAV_FMT COMPOSE_ID('f','m','t',' ') 53 | #define WAV_DATA COMPOSE_ID('d','a','t','a') 54 | 55 | /* WAVE fmt block constants from Microsoft mmreg.h header */ 56 | #define WAV_FMT_PCM 0x0001 57 | #define WAV_FMT_IEEE_FLOAT 0x0003 58 | 59 | /* it's in chunks like .voc and AMIGA iff, but my source say there 60 | are in only in this combination, so I combined them in one header; 61 | it works on all WAVE-file I have 62 | */ 63 | typedef struct { 64 | u_int magic; /* 'RIFF' */ 65 | u_int length; /* filelen */ 66 | u_int type; /* 'WAVE' */ 67 | } WaveHeader; 68 | 69 | typedef struct { 70 | u_short format; /* see WAV_FMT_* */ 71 | u_short channels; 72 | u_int sample_fq; /* frequence of sample */ 73 | u_int byte_p_sec; 74 | u_short byte_p_spl; /* samplesize; 1 or 2 bytes */ 75 | u_short bit_p_spl; /* 8, 12 or 16 bit */ 76 | } WaveFmtBody; 77 | 78 | typedef struct { 79 | u_int type; /* 'data' */ 80 | u_int length; /* samplecount */ 81 | } WaveChunkHeader; 82 | 83 | #define _(msgid) gettext (msgid) 84 | #define gettext_noop(msgid) msgid 85 | #define N_(msgid) gettext_noop (msgid) 86 | 87 | #ifndef LLONG_MAX 88 | #define LLONG_MAX 9223372036854775807LL 89 | #endif 90 | 91 | #define DEFAULT_FORMAT SND_PCM_FORMAT_U8 92 | #define DEFAULT_SPEED 8000 93 | 94 | #define FORMAT_DEFAULT -1 95 | #define FORMAT_RAW 0 96 | #define FORMAT_VOC 1 97 | #define FORMAT_WAVE 2 98 | #define FORMAT_AU 3 99 | 100 | /* global data */ 101 | 102 | static snd_pcm_sframes_t (*readi_func)(snd_pcm_t *handle, void *buffer, snd_pcm_uframes_t size); 103 | static snd_pcm_sframes_t (*writei_func)(snd_pcm_t *handle, const void *buffer, snd_pcm_uframes_t size); 104 | static snd_pcm_sframes_t (*readn_func)(snd_pcm_t *handle, void **bufs, snd_pcm_uframes_t size); 105 | static snd_pcm_sframes_t (*writen_func)(snd_pcm_t *handle, void **bufs, snd_pcm_uframes_t size); 106 | 107 | enum { 108 | VUMETER_NONE, 109 | VUMETER_MONO, 110 | VUMETER_STEREO 111 | }; 112 | 113 | static char *command; 114 | static snd_pcm_t *handle; 115 | static struct { 116 | snd_pcm_format_t format; 117 | unsigned int channels; 118 | unsigned int rate; 119 | } hwparams, rhwparams; 120 | static int timelimit = 0; 121 | static int quiet_mode = 0; 122 | static int file_type = FORMAT_DEFAULT; 123 | static int open_mode = 0; 124 | static int capture_stop = 0; 125 | static snd_pcm_stream_t stream = SND_PCM_STREAM_PLAYBACK; 126 | static int interleaved = 1; 127 | static int nonblock = 0; 128 | static u_char *audiobuf = NULL; 129 | static snd_pcm_uframes_t chunk_size = 0; 130 | static unsigned period_time = 0; 131 | static unsigned buffer_time = 0; 132 | static snd_pcm_uframes_t period_frames = 0; 133 | static snd_pcm_uframes_t buffer_frames = 0; 134 | static int avail_min = -1; 135 | static int start_delay = 0; 136 | static int stop_delay = 0; 137 | static int verbose = 0; 138 | static int vumeter = VUMETER_NONE; 139 | static int buffer_pos = 0; 140 | static size_t bits_per_sample, bits_per_frame; 141 | static size_t chunk_bytes; 142 | static snd_output_t *log; 143 | 144 | static int fd = -1; 145 | static off64_t pbrec_count = LLONG_MAX, fdcount; 146 | static int vocmajor, vocminor; 147 | 148 | /* needed prototypes */ 149 | 150 | static void capture(char *filename); 151 | 152 | static void begin_wave(int fd, size_t count); 153 | static void end_wave(int fd); 154 | 155 | struct fmt_capture { 156 | void (*start) (int fd, size_t count); 157 | void (*end) (int fd); 158 | char *what; 159 | long long max_filesize; 160 | } fmt_rec_table[] = { 161 | { NULL, NULL, N_("raw data"), LLONG_MAX }, 162 | { NULL, NULL, N_("VOC"), 16000000LL }, 163 | { begin_wave, end_wave, N_("WAVE"), 2147483648LL }, 164 | { NULL, NULL, N_("Sparc Audio"), LLONG_MAX } 165 | }; 166 | 167 | #if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 95) 168 | #define error(...) do {\ 169 | fprintf(stderr, "%s: %s:%d: ", command, __FUNCTION__, __LINE__); \ 170 | fprintf(stderr, __VA_ARGS__); \ 171 | putc('\n', stderr); \ 172 | } while (0) 173 | #else 174 | #define error(args...) do {\ 175 | fprintf(stderr, "%s: %s:%d: ", command, __FUNCTION__, __LINE__); \ 176 | fprintf(stderr, ##args); \ 177 | putc('\n', stderr); \ 178 | } while (0) 179 | #endif 180 | 181 | static void version(void) 182 | { 183 | } 184 | 185 | static void signal_handler(int sig) 186 | { 187 | if (verbose==2) 188 | putchar('\n'); 189 | if (!quiet_mode) 190 | fprintf(stderr, _("Aborted by signal %s...\n"), strsignal(sig)); 191 | if (stream == SND_PCM_STREAM_CAPTURE) { 192 | if (fmt_rec_table[file_type].end) { 193 | fmt_rec_table[file_type].end(fd); 194 | fd = -1; 195 | } 196 | stream = -1; 197 | } 198 | if (fd > 1) { 199 | close(fd); 200 | fd = -1; 201 | } 202 | if (handle && sig != SIGABRT) { 203 | snd_pcm_close(handle); 204 | handle = NULL; 205 | } 206 | exit(EXIT_FAILURE); 207 | } 208 | 209 | enum { 210 | OPT_VERSION = 1, 211 | OPT_PERIOD_SIZE, 212 | OPT_BUFFER_SIZE, 213 | OPT_DISABLE_RESAMPLE, 214 | OPT_DISABLE_CHANNELS, 215 | OPT_DISABLE_FORMAT, 216 | OPT_DISABLE_SOFTVOL, 217 | OPT_TEST_POSITION 218 | }; 219 | 220 | int main() 221 | { 222 | run("b.wav"); 223 | return 0; 224 | } 225 | 226 | void stop() 227 | { 228 | capture_stop = 1; 229 | } 230 | 231 | int run(char *filename) 232 | { 233 | capture_stop = 0; 234 | char *pcm_name = "default"; 235 | int tmp, err; 236 | snd_pcm_info_t *info; 237 | 238 | snd_pcm_info_alloca(&info); 239 | 240 | err = snd_output_stdio_attach(&log, stderr, 0); 241 | assert(err >= 0); 242 | 243 | file_type = FORMAT_DEFAULT; 244 | stream = SND_PCM_STREAM_CAPTURE; 245 | file_type = FORMAT_WAVE; 246 | command = "arecord"; 247 | start_delay = 1; 248 | 249 | chunk_size = -1; 250 | rhwparams.format = DEFAULT_FORMAT; 251 | rhwparams.rate = DEFAULT_SPEED; 252 | rhwparams.channels = 1; 253 | 254 | file_type = FORMAT_WAVE; 255 | 256 | // cdr: 257 | // rhwparams.format = SND_PCM_FORMAT_S16_BE; 258 | rhwparams.format = file_type == FORMAT_AU ? SND_PCM_FORMAT_S16_BE : SND_PCM_FORMAT_S16_LE; 259 | rhwparams.rate = 44100; 260 | rhwparams.channels = 2; 261 | 262 | err = snd_pcm_open(&handle, pcm_name, stream, open_mode); 263 | if (err < 0) { 264 | error(_("audio open error: %s"), snd_strerror(err)); 265 | return 1; 266 | } 267 | 268 | if ((err = snd_pcm_info(handle, info)) < 0) { 269 | error(_("info error: %s"), snd_strerror(err)); 270 | return 1; 271 | } 272 | 273 | if (nonblock) { 274 | err = snd_pcm_nonblock(handle, 1); 275 | if (err < 0) { 276 | error(_("nonblock setting error: %s"), snd_strerror(err)); 277 | return 1; 278 | } 279 | } 280 | 281 | chunk_size = 1024; 282 | hwparams = rhwparams; 283 | 284 | audiobuf = (u_char *)malloc(1024); 285 | if (audiobuf == NULL) { 286 | error(_("not enough memory")); 287 | return 1; 288 | } 289 | 290 | writei_func = snd_pcm_writei; 291 | readi_func = snd_pcm_readi; 292 | writen_func = snd_pcm_writen; 293 | readn_func = snd_pcm_readn; 294 | 295 | 296 | //signal(SIGINT, signal_handler); 297 | //signal(SIGTERM, signal_handler); 298 | //signal(SIGABRT, signal_handler); 299 | capture(filename); 300 | 301 | if (fmt_rec_table[file_type].end) { 302 | fmt_rec_table[file_type].end(fd); 303 | fd = -1; 304 | } 305 | stream = -1; 306 | if (fd > 1) { 307 | close(fd); 308 | fd = -1; 309 | } 310 | if (handle) { 311 | snd_pcm_close(handle); 312 | handle = NULL; 313 | } 314 | //snd_pcm_close(handle); 315 | //free(audiobuf); 316 | //snd_output_close(log); 317 | //snd_config_update_free_global(); 318 | return EXIT_SUCCESS; 319 | } 320 | 321 | static void set_params(void) 322 | { 323 | snd_pcm_hw_params_t *params; 324 | snd_pcm_sw_params_t *swparams; 325 | snd_pcm_uframes_t buffer_size; 326 | int err; 327 | size_t n; 328 | unsigned int rate; 329 | snd_pcm_uframes_t start_threshold, stop_threshold; 330 | snd_pcm_hw_params_alloca(¶ms); 331 | snd_pcm_sw_params_alloca(&swparams); 332 | err = snd_pcm_hw_params_any(handle, params); 333 | if (err < 0) { 334 | error(_("Broken configuration for this PCM: no configurations available")); 335 | exit(EXIT_FAILURE); 336 | } 337 | else if (interleaved) 338 | err = snd_pcm_hw_params_set_access(handle, params, 339 | SND_PCM_ACCESS_RW_INTERLEAVED); 340 | else 341 | err = snd_pcm_hw_params_set_access(handle, params, 342 | SND_PCM_ACCESS_RW_NONINTERLEAVED); 343 | if (err < 0) { 344 | error(_("Access type not available")); 345 | exit(EXIT_FAILURE); 346 | } 347 | err = snd_pcm_hw_params_set_format(handle, params, hwparams.format); 348 | if (err < 0) { 349 | error(_("Sample format non available")); 350 | exit(EXIT_FAILURE); 351 | } 352 | err = snd_pcm_hw_params_set_channels(handle, params, hwparams.channels); 353 | if (err < 0) { 354 | error(_("Channels count non available")); 355 | exit(EXIT_FAILURE); 356 | } 357 | 358 | #if 0 359 | err = snd_pcm_hw_params_set_periods_min(handle, params, 2); 360 | assert(err >= 0); 361 | #endif 362 | rate = hwparams.rate; 363 | err = snd_pcm_hw_params_set_rate_near(handle, params, &hwparams.rate, 0); 364 | assert(err >= 0); 365 | if ((float)rate * 1.05 < hwparams.rate || (float)rate * 0.95 > hwparams.rate) { 366 | if (!quiet_mode) { 367 | char plugex[64]; 368 | const char *pcmname = snd_pcm_name(handle); 369 | fprintf(stderr, _("Warning: rate is not accurate (requested = %iHz, got = %iHz)\n"), rate, hwparams.rate); 370 | if (! pcmname || strchr(snd_pcm_name(handle), ':')) 371 | *plugex = 0; 372 | else 373 | snprintf(plugex, sizeof(plugex), "(-Dplug:%s)", 374 | snd_pcm_name(handle)); 375 | fprintf(stderr, _(" please, try the plug plugin %s\n"), 376 | plugex); 377 | } 378 | } 379 | rate = hwparams.rate; 380 | if (buffer_time == 0 && buffer_frames == 0) { 381 | err = snd_pcm_hw_params_get_buffer_time_max(params, 382 | &buffer_time, 0); 383 | assert(err >= 0); 384 | if (buffer_time > 500000) 385 | buffer_time = 500000; 386 | } 387 | if (period_time == 0 && period_frames == 0) { 388 | if (buffer_time > 0) 389 | period_time = buffer_time / 4; 390 | else 391 | period_frames = buffer_frames / 4; 392 | } 393 | if (period_time > 0) 394 | err = snd_pcm_hw_params_set_period_time_near(handle, params, 395 | &period_time, 0); 396 | else 397 | err = snd_pcm_hw_params_set_period_size_near(handle, params, 398 | &period_frames, 0); 399 | assert(err >= 0); 400 | if (buffer_time > 0) { 401 | err = snd_pcm_hw_params_set_buffer_time_near(handle, params, 402 | &buffer_time, 0); 403 | } else { 404 | err = snd_pcm_hw_params_set_buffer_size_near(handle, params, 405 | &buffer_frames); 406 | } 407 | assert(err >= 0); 408 | err = snd_pcm_hw_params(handle, params); 409 | if (err < 0) { 410 | error(_("Unable to install hw params:")); 411 | snd_pcm_hw_params_dump(params, log); 412 | exit(EXIT_FAILURE); 413 | } 414 | snd_pcm_hw_params_get_period_size(params, &chunk_size, 0); 415 | snd_pcm_hw_params_get_buffer_size(params, &buffer_size); 416 | if (chunk_size == buffer_size) { 417 | error(_("Can't use period equal to buffer size (%lu == %lu)"), 418 | chunk_size, buffer_size); 419 | exit(EXIT_FAILURE); 420 | } 421 | snd_pcm_sw_params_current(handle, swparams); 422 | if (avail_min < 0) 423 | n = chunk_size; 424 | else 425 | n = (double) rate * avail_min / 1000000; 426 | err = snd_pcm_sw_params_set_avail_min(handle, swparams, n); 427 | 428 | /* round up to closest transfer boundary */ 429 | n = buffer_size; 430 | if (start_delay <= 0) { 431 | start_threshold = n + (double) rate * start_delay / 1000000; 432 | } else 433 | start_threshold = (double) rate * start_delay / 1000000; 434 | if (start_threshold < 1) 435 | start_threshold = 1; 436 | if (start_threshold > n) 437 | start_threshold = n; 438 | err = snd_pcm_sw_params_set_start_threshold(handle, swparams, start_threshold); 439 | assert(err >= 0); 440 | if (stop_delay <= 0) 441 | stop_threshold = buffer_size + (double) rate * stop_delay / 1000000; 442 | else 443 | stop_threshold = (double) rate * stop_delay / 1000000; 444 | err = snd_pcm_sw_params_set_stop_threshold(handle, swparams, stop_threshold); 445 | assert(err >= 0); 446 | 447 | if (snd_pcm_sw_params(handle, swparams) < 0) { 448 | error(_("unable to install sw params:")); 449 | snd_pcm_sw_params_dump(swparams, log); 450 | exit(EXIT_FAILURE); 451 | } 452 | 453 | if (verbose) 454 | snd_pcm_dump(handle, log); 455 | 456 | bits_per_sample = snd_pcm_format_physical_width(hwparams.format); 457 | bits_per_frame = bits_per_sample * hwparams.channels; 458 | chunk_bytes = chunk_size * bits_per_frame / 8; 459 | audiobuf = realloc(audiobuf, chunk_bytes); 460 | if (audiobuf == NULL) { 461 | error(_("not enough memory")); 462 | exit(EXIT_FAILURE); 463 | } 464 | // fprintf(stderr, "real chunk_size = %i, frags = %i, total = %i\n", chunk_size, setup.buf.block.frags, setup.buf.block.frags * chunk_size); 465 | 466 | /* stereo VU-meter isn't always available... */ 467 | if (vumeter == VUMETER_STEREO) { 468 | if (hwparams.channels != 2 || !interleaved || verbose > 2) 469 | vumeter = VUMETER_MONO; 470 | } 471 | 472 | buffer_frames = buffer_size; /* for position test */ 473 | } 474 | 475 | #ifndef timersub 476 | #define timersub(a, b, result) \ 477 | do { \ 478 | (result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \ 479 | (result)->tv_usec = (a)->tv_usec - (b)->tv_usec; \ 480 | if ((result)->tv_usec < 0) { \ 481 | --(result)->tv_sec; \ 482 | (result)->tv_usec += 1000000; \ 483 | } \ 484 | } while (0) 485 | #endif 486 | 487 | /* I/O error handler */ 488 | static void xrun(void) 489 | { 490 | snd_pcm_status_t *status; 491 | int res; 492 | 493 | snd_pcm_status_alloca(&status); 494 | if ((res = snd_pcm_status(handle, status))<0) { 495 | error(_("status error: %s"), snd_strerror(res)); 496 | exit(EXIT_FAILURE); 497 | } 498 | if (snd_pcm_status_get_state(status) == SND_PCM_STATE_XRUN) { 499 | struct timeval now, diff, tstamp; 500 | gettimeofday(&now, 0); 501 | snd_pcm_status_get_trigger_tstamp(status, &tstamp); 502 | timersub(&now, &tstamp, &diff); 503 | fprintf(stderr, _("%s!!! (at least %.3f ms long)\n"), 504 | stream == SND_PCM_STREAM_PLAYBACK ? _("underrun") : _("overrun"), 505 | diff.tv_sec * 1000 + diff.tv_usec / 1000.0); 506 | if (verbose) { 507 | fprintf(stderr, _("Status:\n")); 508 | snd_pcm_status_dump(status, log); 509 | } 510 | if ((res = snd_pcm_prepare(handle))<0) { 511 | error(_("xrun: prepare error: %s"), snd_strerror(res)); 512 | exit(EXIT_FAILURE); 513 | } 514 | return; /* ok, data should be accepted again */ 515 | } if (snd_pcm_status_get_state(status) == SND_PCM_STATE_DRAINING) { 516 | if (verbose) { 517 | fprintf(stderr, _("Status(DRAINING):\n")); 518 | snd_pcm_status_dump(status, log); 519 | } 520 | if (stream == SND_PCM_STREAM_CAPTURE) { 521 | fprintf(stderr, _("capture stream format change? attempting recover...\n")); 522 | if ((res = snd_pcm_prepare(handle))<0) { 523 | error(_("xrun(DRAINING): prepare error: %s"), snd_strerror(res)); 524 | exit(EXIT_FAILURE); 525 | } 526 | return; 527 | } 528 | } 529 | if (verbose) { 530 | fprintf(stderr, _("Status(R/W):\n")); 531 | snd_pcm_status_dump(status, log); 532 | } 533 | error(_("read/write error, state = %s"), snd_pcm_state_name(snd_pcm_status_get_state(status))); 534 | exit(EXIT_FAILURE); 535 | } 536 | 537 | /* I/O suspend handler */ 538 | static void suspend(void) 539 | { 540 | int res; 541 | 542 | if (!quiet_mode) 543 | fprintf(stderr, _("Suspended. Trying resume. ")); fflush(stderr); 544 | while ((res = snd_pcm_resume(handle)) == -EAGAIN) 545 | sleep(1); /* wait until suspend flag is released */ 546 | if (res < 0) { 547 | if (!quiet_mode) 548 | fprintf(stderr, _("Failed. Restarting stream. ")); fflush(stderr); 549 | if ((res = snd_pcm_prepare(handle)) < 0) { 550 | error(_("suspend: prepare error: %s"), snd_strerror(res)); 551 | exit(EXIT_FAILURE); 552 | } 553 | } 554 | if (!quiet_mode) 555 | fprintf(stderr, _("Done.\n")); 556 | } 557 | 558 | static void print_vu_meter_mono(int perc, int maxperc) 559 | { 560 | const int bar_length = 50; 561 | char line[80]; 562 | int val; 563 | 564 | for (val = 0; val <= perc * bar_length / 100 && val < bar_length; val++) 565 | line[val] = '#'; 566 | for (; val <= maxperc * bar_length / 100 && val < bar_length; val++) 567 | line[val] = ' '; 568 | line[val] = '+'; 569 | for (++val; val <= bar_length; val++) 570 | line[val] = ' '; 571 | if (maxperc > 99) 572 | sprintf(line + val, "| MAX"); 573 | else 574 | sprintf(line + val, "| %02i%%", maxperc); 575 | fputs(line, stdout); 576 | if (perc > 100) 577 | printf(_(" !clip ")); 578 | } 579 | 580 | static void print_vu_meter_stereo(int *perc, int *maxperc) 581 | { 582 | const int bar_length = 35; 583 | char line[80]; 584 | int c; 585 | 586 | memset(line, ' ', sizeof(line) - 1); 587 | line[bar_length + 3] = '|'; 588 | 589 | for (c = 0; c < 2; c++) { 590 | int p = perc[c] * bar_length / 100; 591 | char tmp[4]; 592 | if (p > bar_length) 593 | p = bar_length; 594 | if (c) 595 | memset(line + bar_length + 6 + 1, '#', p); 596 | else 597 | memset(line + bar_length - p - 1, '#', p); 598 | p = maxperc[c] * bar_length / 100; 599 | if (p > bar_length) 600 | p = bar_length; 601 | if (c) 602 | line[bar_length + 6 + 1 + p] = '+'; 603 | else 604 | line[bar_length - p - 1] = '+'; 605 | if (maxperc[c] > 99) 606 | sprintf(tmp, "MAX"); 607 | else 608 | sprintf(tmp, "%02d%%", maxperc[c]); 609 | if (c) 610 | memcpy(line + bar_length + 3 + 1, tmp, 3); 611 | else 612 | memcpy(line + bar_length, tmp, 3); 613 | } 614 | line[bar_length * 2 + 6 + 2] = 0; 615 | fputs(line, stdout); 616 | } 617 | 618 | static void print_vu_meter(signed int *perc, signed int *maxperc) 619 | { 620 | if (vumeter == VUMETER_STEREO) 621 | print_vu_meter_stereo(perc, maxperc); 622 | else 623 | print_vu_meter_mono(*perc, *maxperc); 624 | } 625 | 626 | /* peak handler */ 627 | static void compute_max_peak(u_char *data, size_t count) 628 | { 629 | signed int val, max, perc[2], max_peak[2]; 630 | static int run = 0; 631 | size_t ocount = count; 632 | int format_little_endian = snd_pcm_format_little_endian(hwparams.format); 633 | int ichans, c; 634 | 635 | if (vumeter == VUMETER_STEREO) 636 | ichans = 2; 637 | else 638 | ichans = 1; 639 | 640 | memset(max_peak, 0, sizeof(max_peak)); 641 | switch (bits_per_sample) { 642 | case 8: { 643 | signed char *valp = (signed char *)data; 644 | signed char mask = snd_pcm_format_silence(hwparams.format); 645 | c = 0; 646 | while (count-- > 0) { 647 | val = *valp++ ^ mask; 648 | val = abs(val); 649 | if (max_peak[c] < val) 650 | max_peak[c] = val; 651 | if (vumeter == VUMETER_STEREO) 652 | c = !c; 653 | } 654 | break; 655 | } 656 | case 16: { 657 | signed short *valp = (signed short *)data; 658 | signed short mask = snd_pcm_format_silence_16(hwparams.format); 659 | signed short sval; 660 | 661 | count /= 2; 662 | c = 0; 663 | while (count-- > 0) { 664 | if (format_little_endian) 665 | sval = __le16_to_cpu(*valp); 666 | else 667 | sval = __be16_to_cpu(*valp); 668 | sval = abs(sval) ^ mask; 669 | if (max_peak[c] < sval) 670 | max_peak[c] = sval; 671 | valp++; 672 | if (vumeter == VUMETER_STEREO) 673 | c = !c; 674 | } 675 | break; 676 | } 677 | case 24: { 678 | unsigned char *valp = data; 679 | signed int mask = snd_pcm_format_silence_32(hwparams.format); 680 | 681 | count /= 3; 682 | c = 0; 683 | while (count-- > 0) { 684 | if (format_little_endian) { 685 | val = valp[0] | (valp[1]<<8) | (valp[2]<<16); 686 | } else { 687 | val = (valp[0]<<16) | (valp[1]<<8) | valp[2]; 688 | } 689 | /* Correct signed bit in 32-bit value */ 690 | if (val & (1<<(bits_per_sample-1))) { 691 | val |= 0xff<<24; /* Negate upper bits too */ 692 | } 693 | val = abs(val) ^ mask; 694 | if (max_peak[c] < val) 695 | max_peak[c] = val; 696 | valp += 3; 697 | if (vumeter == VUMETER_STEREO) 698 | c = !c; 699 | } 700 | break; 701 | } 702 | case 32: { 703 | signed int *valp = (signed int *)data; 704 | signed int mask = snd_pcm_format_silence_32(hwparams.format); 705 | 706 | count /= 4; 707 | c = 0; 708 | while (count-- > 0) { 709 | if (format_little_endian) 710 | val = __le32_to_cpu(*valp); 711 | else 712 | val = __be32_to_cpu(*valp); 713 | val = abs(val) ^ mask; 714 | if (max_peak[c] < val) 715 | max_peak[c] = val; 716 | valp++; 717 | if (vumeter == VUMETER_STEREO) 718 | c = !c; 719 | } 720 | break; 721 | } 722 | default: 723 | if (run == 0) { 724 | fprintf(stderr, _("Unsupported bit size %d.\n"), (int)bits_per_sample); 725 | run = 1; 726 | } 727 | return; 728 | } 729 | max = 1 << (bits_per_sample-1); 730 | if (max <= 0) 731 | max = 0x7fffffff; 732 | 733 | for (c = 0; c < ichans; c++) { 734 | if (bits_per_sample > 16) 735 | perc[c] = max_peak[c] / (max / 100); 736 | else 737 | perc[c] = max_peak[c] * 100 / max; 738 | } 739 | 740 | if (interleaved && verbose <= 2) { 741 | static int maxperc[2]; 742 | static time_t t=0; 743 | const time_t tt=time(NULL); 744 | if(tt>t) { 745 | t=tt; 746 | maxperc[0] = 0; 747 | maxperc[1] = 0; 748 | } 749 | for (c = 0; c < ichans; c++) 750 | if (perc[c] > maxperc[c]) 751 | maxperc[c] = perc[c]; 752 | 753 | putchar('\r'); 754 | print_vu_meter(perc, maxperc); 755 | fflush(stdout); 756 | } 757 | else if(verbose==3) { 758 | printf(_("Max peak (%li samples): 0x%08x "), (long)ocount, max_peak[0]); 759 | for (val = 0; val < 20; val++) 760 | if (val <= perc[0] / 5) 761 | putchar('#'); 762 | else 763 | putchar(' '); 764 | printf(" %i%%\n", perc[0]); 765 | fflush(stdout); 766 | } 767 | } 768 | 769 | /* 770 | * read function 771 | */ 772 | 773 | static ssize_t pcm_read(u_char *data, size_t rcount) 774 | { 775 | ssize_t r; 776 | size_t result = 0; 777 | size_t count = rcount; 778 | 779 | if (count != chunk_size) { 780 | count = chunk_size; 781 | } 782 | 783 | while (count > 0) { 784 | r = readi_func(handle, data, count); 785 | if (r == -EAGAIN || (r >= 0 && (size_t)r < count)) { 786 | snd_pcm_wait(handle, 1000); 787 | } else if (r == -EPIPE) { 788 | xrun(); 789 | } else if (r == -ESTRPIPE) { 790 | suspend(); 791 | } else if (r < 0) { 792 | error(_("read error: %s"), snd_strerror(r)); 793 | exit(EXIT_FAILURE); 794 | } 795 | if (r > 0) { 796 | if (vumeter) 797 | compute_max_peak(data, r * hwparams.channels); 798 | result += r; 799 | count -= r; 800 | data += r * bits_per_frame / 8; 801 | } 802 | } 803 | return rcount; 804 | } 805 | 806 | /* setting the globals for playing raw data */ 807 | static void init_raw_data(void) 808 | { 809 | hwparams = rhwparams; 810 | } 811 | 812 | /* calculate the data count to read from/to dsp */ 813 | static off64_t calc_count(void) 814 | { 815 | off64_t count; 816 | 817 | if (timelimit == 0) { 818 | count = pbrec_count; 819 | } else { 820 | count = snd_pcm_format_size(hwparams.format, hwparams.rate * hwparams.channels); 821 | count *= (off64_t)timelimit; 822 | } 823 | return count < pbrec_count ? count : pbrec_count; 824 | } 825 | 826 | /* write a WAVE-header */ 827 | static void begin_wave(int fd, size_t cnt) 828 | { 829 | WaveHeader h; 830 | WaveFmtBody f; 831 | WaveChunkHeader cf, cd; 832 | int bits; 833 | u_int tmp; 834 | u_short tmp2; 835 | 836 | /* WAVE cannot handle greater than 32bit (signed?) int */ 837 | if (cnt == (size_t)-2) 838 | cnt = 0x7fffff00; 839 | 840 | bits = 8; 841 | switch ((unsigned long) hwparams.format) { 842 | case SND_PCM_FORMAT_U8: 843 | bits = 8; 844 | break; 845 | case SND_PCM_FORMAT_S16_LE: 846 | bits = 16; 847 | break; 848 | case SND_PCM_FORMAT_S32_LE: 849 | case SND_PCM_FORMAT_FLOAT_LE: 850 | bits = 32; 851 | break; 852 | case SND_PCM_FORMAT_S24_LE: 853 | case SND_PCM_FORMAT_S24_3LE: 854 | bits = 24; 855 | break; 856 | default: 857 | error(_("Wave doesn't support %s format..."), snd_pcm_format_name(hwparams.format)); 858 | exit(EXIT_FAILURE); 859 | } 860 | h.magic = WAV_RIFF; 861 | tmp = cnt + sizeof(WaveHeader) + sizeof(WaveChunkHeader) + sizeof(WaveFmtBody) + sizeof(WaveChunkHeader) - 8; 862 | h.length = LE_INT(tmp); 863 | h.type = WAV_WAVE; 864 | 865 | cf.type = WAV_FMT; 866 | cf.length = LE_INT(16); 867 | 868 | if (hwparams.format == SND_PCM_FORMAT_FLOAT_LE) 869 | f.format = LE_SHORT(WAV_FMT_IEEE_FLOAT); 870 | else 871 | f.format = LE_SHORT(WAV_FMT_PCM); 872 | f.channels = LE_SHORT(hwparams.channels); 873 | f.sample_fq = LE_INT(hwparams.rate); 874 | #if 0 875 | tmp2 = (samplesize == 8) ? 1 : 2; 876 | f.byte_p_spl = LE_SHORT(tmp2); 877 | tmp = dsp_speed * hwparams.channels * (u_int) tmp2; 878 | #else 879 | tmp2 = hwparams.channels * snd_pcm_format_physical_width(hwparams.format) / 8; 880 | f.byte_p_spl = LE_SHORT(tmp2); 881 | tmp = (u_int) tmp2 * hwparams.rate; 882 | #endif 883 | f.byte_p_sec = LE_INT(tmp); 884 | f.bit_p_spl = LE_SHORT(bits); 885 | 886 | cd.type = WAV_DATA; 887 | cd.length = LE_INT(cnt); 888 | 889 | if (write(fd, &h, sizeof(WaveHeader)) != sizeof(WaveHeader) || 890 | write(fd, &cf, sizeof(WaveChunkHeader)) != sizeof(WaveChunkHeader) || 891 | write(fd, &f, sizeof(WaveFmtBody)) != sizeof(WaveFmtBody) || 892 | write(fd, &cd, sizeof(WaveChunkHeader)) != sizeof(WaveChunkHeader)) { 893 | error(_("write error")); 894 | exit(EXIT_FAILURE); 895 | } 896 | } 897 | 898 | static void end_wave(int fd) 899 | { /* only close output */ 900 | WaveChunkHeader cd; 901 | off64_t length_seek; 902 | off64_t filelen; 903 | u_int rifflen; 904 | 905 | length_seek = sizeof(WaveHeader) + 906 | sizeof(WaveChunkHeader) + 907 | sizeof(WaveFmtBody); 908 | cd.type = WAV_DATA; 909 | cd.length = fdcount > 0x7fffffff ? LE_INT(0x7fffffff) : LE_INT(fdcount); 910 | filelen = fdcount + 2*sizeof(WaveChunkHeader) + sizeof(WaveFmtBody) + 4; 911 | rifflen = filelen > 0x7fffffff ? LE_INT(0x7fffffff) : LE_INT(filelen); 912 | if (lseek64(fd, 4, SEEK_SET) == 4) 913 | write(fd, &rifflen, 4); 914 | if (lseek64(fd, length_seek, SEEK_SET) == length_seek) 915 | write(fd, &cd, sizeof(WaveChunkHeader)); 916 | if (fd != 1) 917 | close(fd); 918 | } 919 | 920 | static int new_capture_file(char *name, char *namebuf, size_t namelen, 921 | int filecount) 922 | { 923 | /* get a copy of the original filename */ 924 | char *s; 925 | char buf[PATH_MAX+1]; 926 | 927 | strncpy(buf, name, sizeof(buf)); 928 | 929 | /* separate extension from filename */ 930 | s = buf + strlen(buf); 931 | while (s > buf && *s != '.' && *s != '/') 932 | --s; 933 | if (*s == '.') 934 | *s++ = 0; 935 | else if (*s == '/') 936 | s = buf + strlen(buf); 937 | 938 | /* upon first jump to this if block rename the first file */ 939 | if (filecount == 1) { 940 | if (*s) 941 | snprintf(namebuf, namelen, "%s-01.%s", buf, s); 942 | else 943 | snprintf(namebuf, namelen, "%s-01", buf); 944 | remove(namebuf); 945 | rename(name, namebuf); 946 | filecount = 2; 947 | } 948 | 949 | /* name of the current file */ 950 | if (*s) 951 | snprintf(namebuf, namelen, "%s-%02i.%s", buf, filecount, s); 952 | else 953 | snprintf(namebuf, namelen, "%s-%02i", buf, filecount); 954 | 955 | return filecount; 956 | } 957 | 958 | static void capture(char *orig_name) 959 | { 960 | int tostdout=0; /* boolean which describes output stream */ 961 | int filecount=0; /* number of files written */ 962 | char *name = orig_name; /* current filename */ 963 | char namebuf[PATH_MAX+1]; 964 | off64_t count, rest; /* number of bytes to capture */ 965 | 966 | /* get number of bytes to capture */ 967 | count = calc_count(); 968 | if (count == 0) 969 | count = LLONG_MAX; 970 | /* WAVE-file should be even (I'm not sure), but wasting one byte 971 | isn't a problem (this can only be in 8 bit mono) */ 972 | if (count < LLONG_MAX) 973 | count += count % 2; 974 | else 975 | count -= count % 2; 976 | 977 | printf("arecord: Recording audio to: %s\n", name); 978 | /* setup sound hardware */ 979 | set_params(); 980 | 981 | /* write to stdout? */ 982 | if (!name || !strcmp(name, "-")) { 983 | fd = fileno(stdout); 984 | name = "stdout"; 985 | tostdout=1; 986 | if (count > fmt_rec_table[file_type].max_filesize) 987 | count = fmt_rec_table[file_type].max_filesize; 988 | } 989 | 990 | do { 991 | /* open a file to write */ 992 | if(!tostdout) { 993 | /* upon the second file we start the numbering scheme */ 994 | if (filecount) { 995 | filecount = new_capture_file(orig_name, namebuf, 996 | sizeof(namebuf), 997 | filecount); 998 | name = namebuf; 999 | } 1000 | 1001 | /* open a new file */ 1002 | remove(name); 1003 | if ((fd = open64(name, O_WRONLY | O_CREAT, 0644)) == -1) { 1004 | perror(name); 1005 | exit(EXIT_FAILURE); 1006 | } 1007 | filecount++; 1008 | } 1009 | 1010 | rest = count; 1011 | if (rest > fmt_rec_table[file_type].max_filesize) 1012 | rest = fmt_rec_table[file_type].max_filesize; 1013 | 1014 | /* setup sample header */ 1015 | if (fmt_rec_table[file_type].start) 1016 | fmt_rec_table[file_type].start(fd, rest); 1017 | 1018 | /* capture */ 1019 | fdcount = 0; 1020 | while (rest > 0 && capture_stop == 0) { 1021 | size_t c = (rest <= (off64_t)chunk_bytes) ? 1022 | (size_t)rest : chunk_bytes; 1023 | size_t f = c * 8 / bits_per_frame; 1024 | if (pcm_read(audiobuf, f) != f) 1025 | break; 1026 | if (write(fd, audiobuf, c) != c) { 1027 | perror(name); 1028 | exit(EXIT_FAILURE); 1029 | } 1030 | count -= c; 1031 | rest -= c; 1032 | fdcount += c; 1033 | } 1034 | 1035 | /* finish sample container */ 1036 | if (fmt_rec_table[file_type].end && !tostdout) { 1037 | fmt_rec_table[file_type].end(fd); 1038 | fd = -1; 1039 | } 1040 | 1041 | /* repeat the loop when format is raw without timelimit or 1042 | * requested counts of data are recorded 1043 | */ 1044 | } while ( ((file_type == FORMAT_RAW && !timelimit) || count > 0) && 1045 | capture_stop == 0); 1046 | printf("arecord: Stopping capturing audio.\n"); 1047 | } 1048 | --------------------------------------------------------------------------------