1 /* $NetBSD: record.c,v 1.16 2001/06/07 12:50:29 mrg Exp $ */ 2 3 /* 4 * Copyright (c) 1999 Matthew R. Green 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 3. The name of the author may not be used to endorse or promote products 16 * derived from this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 19 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 20 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 21 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 22 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 23 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 25 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28 * SUCH DAMAGE. 29 */ 30 31 /* 32 * SunOS compatible audiorecord(1) 33 */ 34 35 #include <sys/types.h> 36 #include <sys/audioio.h> 37 #include <sys/ioctl.h> 38 #include <sys/time.h> 39 #include <sys/uio.h> 40 41 #include <err.h> 42 #include <fcntl.h> 43 #include <paths.h> 44 #include <signal.h> 45 #include <stdio.h> 46 #include <stdlib.h> 47 #include <string.h> 48 #include <unistd.h> 49 50 #include "libaudio.h" 51 52 audio_info_t info, oinfo; 53 ssize_t total_size = -1; 54 char *device; 55 char *ctldev; 56 char *header_info; 57 char default_info[8] = { '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0' }; 58 int audiofd, ctlfd, outfd; 59 int qflag, aflag, fflag; 60 int verbose; 61 int monvol, omonvol; 62 int volume; 63 int balance; 64 int port; 65 int encoding; 66 char *encoding_str; 67 int precision; 68 int sample_rate; 69 int channels; 70 struct timeval record_time; 71 struct timeval start_time; /* XXX because that's what gettimeofday returns */ 72 73 void usage (void); 74 int main (int, char *[]); 75 int timeleft (struct timeval *, struct timeval *); 76 void cleanup (int) __attribute__((__noreturn__)); 77 void write_header (void); 78 void rewrite_header (void); 79 80 int 81 main(argc, argv) 82 int argc; 83 char *argv[]; 84 { 85 char *buffer; 86 size_t len, bufsize; 87 int ch, no_time_limit = 1; 88 89 while ((ch = getopt(argc, argv, "ab:C:c:d:e:fhi:m:P:p:qt:s:Vv:")) != -1) { 90 switch (ch) { 91 case 'a': 92 aflag++; 93 break; 94 case 'b': 95 decode_int(optarg, &balance); 96 if (balance < 0 || balance > 63) 97 errx(1, "balance must be between 0 and 63\n"); 98 break; 99 case 'C': 100 ctldev = optarg; 101 break; 102 case 'c': 103 decode_int(optarg, &channels); 104 if (channels < 0 || channels > 16) 105 errx(1, "channels must be between 0 and 16\n"); 106 break; 107 case 'd': 108 device = optarg; 109 break; 110 case 'e': 111 encoding_str = optarg; 112 break; 113 case 'f': 114 fflag++; 115 break; 116 case 'i': 117 header_info = optarg; 118 break; 119 case 'm': 120 decode_int(optarg, &monvol); 121 if (monvol < 0 || monvol > 255) 122 errx(1, "monitor volume must be between 0 and 255\n"); 123 break; 124 case 'P': 125 decode_int(optarg, &precision); 126 if (precision != 4 && precision != 8 && 127 precision != 16 && precision != 24 && 128 precision != 32) 129 errx(1, "precision must be between 4, 8, 16, 24 or 32"); 130 break; 131 case 'p': 132 len = strlen(optarg); 133 134 if (strncmp(optarg, "mic", len) == 0) 135 port |= AUDIO_MICROPHONE; 136 else if (strncmp(optarg, "cd", len) == 0 || 137 strncmp(optarg, "internal-cd", len) == 0) 138 port |= AUDIO_CD; 139 else if (strncmp(optarg, "line", len) == 0) 140 port |= AUDIO_LINE_IN; 141 else 142 errx(1, 143 "port must be `cd', `internal-cd', `mic', or `line'"); 144 break; 145 case 'q': 146 qflag++; 147 break; 148 case 's': 149 decode_int(optarg, &sample_rate); 150 if (sample_rate < 0 || sample_rate > 48000 * 2) /* XXX */ 151 errx(1, "sample rate must be between 0 and 96000\n"); 152 break; 153 case 't': 154 no_time_limit = 0; 155 decode_time(optarg, &record_time); 156 break; 157 case 'V': 158 verbose++; 159 break; 160 case 'v': 161 decode_int(optarg, &volume); 162 if (volume < 0 || volume > 255) 163 errx(1, "volume must be between 0 and 255\n"); 164 break; 165 /* case 'h': */ 166 default: 167 usage(); 168 /* NOTREACHED */ 169 } 170 } 171 argc -= optind; 172 argv += optind; 173 174 /* 175 * open the audio device, and control device 176 */ 177 if (device == NULL && (device = getenv("AUDIODEVICE")) == NULL && 178 (device = getenv("AUDIODEV")) == NULL) /* Sun compatibility */ 179 device = _PATH_AUDIO; 180 if (ctldev == NULL && (ctldev = getenv("AUDIOCTLDEVICE")) == NULL) 181 ctldev = _PATH_AUDIOCTL; 182 183 audiofd = open(device, O_RDONLY); 184 if (audiofd < 0) 185 err(1, "failed to open %s", device); 186 ctlfd = open(ctldev, O_RDWR); 187 if (ctlfd < 0) 188 err(1, "failed to open %s", ctldev); 189 190 /* 191 * work out the buffer size to use, and allocate it. also work out 192 * what the old monitor gain value is, so that we can reset it later. 193 */ 194 if (ioctl(ctlfd, AUDIO_GETINFO, &oinfo) < 0) 195 err(1, "failed to get audio info"); 196 bufsize = oinfo.record.buffer_size; 197 if (bufsize < 32 * 1024) 198 bufsize = 32 * 1024; 199 omonvol = oinfo.monitor_gain; 200 201 buffer = malloc(bufsize); 202 if (buffer == NULL) 203 err(1, "couldn't malloc buffer of %d size", (int)bufsize); 204 205 /* 206 * open the output file 207 */ 208 if (argc != 1) 209 usage(); 210 if (argv[0][0] != '-' && argv[0][1] != '\0') { 211 outfd = open(*argv, O_CREAT|(aflag ? O_APPEND : O_TRUNC)|O_WRONLY, 0666); 212 if (outfd < 0) 213 err(1, "could not open %s", *argv); 214 } else 215 outfd = STDOUT_FILENO; 216 217 /* 218 * set up audio device for recording with the speified parameters 219 */ 220 AUDIO_INITINFO(&info); 221 222 /* 223 * for these, get the current values for stuffing into the header 224 **/ 225 if (sample_rate) 226 info.record.sample_rate = sample_rate; 227 else 228 sample_rate = oinfo.record.sample_rate; 229 if (channels) 230 info.record.channels = channels; 231 else 232 channels = oinfo.record.channels; 233 234 if (encoding_str) { 235 encoding = audio_enc_to_val(encoding_str); 236 if (encoding == -1) 237 errx(1, "unknown encoding, bailing..."); 238 } 239 else 240 encoding = AUDIO_ENCODING_ULAW; 241 242 if (precision) 243 info.record.precision = precision; 244 if (encoding) 245 info.record.encoding = encoding; 246 if (volume) 247 info.record.gain = volume; 248 if (port) 249 info.record.port = port; 250 if (balance) 251 info.record.balance = (u_char)balance; 252 if (monvol) 253 info.monitor_gain = monvol; 254 255 info.mode = AUMODE_RECORD; 256 if (ioctl(ctlfd, AUDIO_SETINFO, &info) < 0) 257 err(1, "failed to reset audio info"); 258 259 signal(SIGINT, cleanup); 260 write_header(); 261 total_size = 0; 262 263 (void)gettimeofday(&start_time, NULL); 264 while (no_time_limit || timeleft(&start_time, &record_time)) { 265 if (read(audiofd, buffer, bufsize) != bufsize) 266 err(1, "read failed"); 267 if (write(outfd, buffer, bufsize) != bufsize) 268 err(1, "write failed"); 269 total_size += bufsize; 270 } 271 cleanup(0); 272 } 273 274 int 275 timeleft(start_tvp, record_tvp) 276 struct timeval *start_tvp; 277 struct timeval *record_tvp; 278 { 279 struct timeval now, diff; 280 281 (void)gettimeofday(&now, NULL); 282 timersub(&now, start_tvp, &diff); 283 timersub(record_tvp, &diff, &now); 284 285 return (now.tv_sec > 0 || (now.tv_sec == 0 && now.tv_usec > 0)); 286 } 287 288 void 289 cleanup(signo) 290 int signo; 291 { 292 293 close(audiofd); 294 rewrite_header(); 295 close(outfd); 296 if (omonvol) { 297 AUDIO_INITINFO(&info); 298 info.monitor_gain = omonvol; 299 if (ioctl(ctlfd, AUDIO_SETINFO, &info) < 0) 300 err(1, "failed to reset audio info"); 301 } 302 close(ctlfd); 303 exit(0); 304 } 305 306 void 307 write_header() 308 { 309 static int warned = 0; 310 sun_audioheader auh; 311 struct iovec iv[3]; 312 int veclen = 0, left, tlen = 0; 313 int sunenc; 314 315 /* if we can't express this as a Sun header, don't write any */ 316 if (audio_encoding_to_sun(encoding, precision, &sunenc) != 0) { 317 if (!qflag && !warned) 318 warnx("failed to convert to sun encoding; " 319 "Sun audio header not written"); 320 warned = 1; 321 return; 322 } 323 324 auh.magic = htonl(AUDIO_FILE_MAGIC); 325 if (outfd == STDOUT_FILENO) 326 auh.data_size = htonl(AUDIO_UNKNOWN_SIZE); 327 else 328 auh.data_size = htonl(total_size); 329 auh.encoding = htonl(sunenc); 330 auh.sample_rate = htonl(sample_rate); 331 auh.channels = htonl(channels); 332 if (header_info) { 333 int len, infolen; 334 335 infolen = ((len = strlen(header_info)) + 7) & 0xfffffff8; 336 left = infolen - len; 337 auh.hdr_size = htonl(sizeof(auh) + infolen); 338 } else { 339 left = sizeof(default_info); 340 auh.hdr_size = htonl(sizeof(auh) + left); 341 } 342 343 iv[veclen].iov_base = &auh; 344 iv[veclen].iov_len = sizeof(auh); 345 tlen = iv[veclen++].iov_len; 346 if (header_info) { 347 iv[veclen].iov_base = header_info; 348 iv[veclen].iov_len = (int)strlen(header_info); 349 tlen += iv[veclen++].iov_len; 350 } 351 if (left) { 352 iv[veclen].iov_base = default_info; 353 iv[veclen].iov_len = left; 354 tlen += iv[veclen++].iov_len; 355 } 356 357 if (writev(outfd, iv, veclen) != tlen) 358 err(1, "could not write audio header"); 359 } 360 361 void 362 rewrite_header() 363 { 364 365 /* can't do this here! */ 366 if (outfd == STDOUT_FILENO) 367 return; 368 369 if (lseek(outfd, SEEK_SET, 0) < 0) 370 err(1, "could not seek to start of file for header rewrite"); 371 write_header(); 372 } 373 374 void 375 usage() 376 { 377 378 fprintf(stderr, "Usage: %s [-afhqV] [options] {files ...|-}\n", 379 getprogname()); 380 fprintf(stderr, "Options:\n\t" 381 "-C audio control device\n\t" 382 "-b balance (0-63)\n\t" 383 "-c channels\n\t" 384 "-d audio device\n\t" 385 "-e encoding\n\t" 386 "-i header information\n\t" 387 "-m monitor volume\n\t" 388 "-P precision bits (4, 8, 16, 24 or 32)\n\t" 389 "-p input port\n\t" 390 "-s sample rate\n\t" 391 "-t recording time\n\t" 392 "-v volume\n"); 393 exit(EXIT_FAILURE); 394 } 395