1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License, Version 1.0 only
6 * (the "License").  You may not use this file except in compliance
7 * with the License.
8 *
9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10 * or http://www.opensolaris.org/os/licensing.
11 * See the License for the specific language governing permissions
12 * and limitations under the License.
13 *
14 * When distributing Covered Code, include this CDDL HEADER in each
15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16 * If applicable, add the following below this CDDL HEADER, with the
17 * fields enclosed by brackets "[]" replaced with your own identifying
18 * information: Portions Copyright [yyyy] [name of copyright owner]
19 *
20 * CDDL HEADER END
21 */
22/*
23 * Copyright (c) 1993-2001 by Sun Microsystems, Inc.
24 * All rights reserved.
25 */
26
27#include <stdlib.h>
28#include <stdio.h>
29#include <stdarg.h>
30#include <string.h>
31#include <unistd.h>
32#include <sys/types.h>
33#include <sys/stat.h>
34#include <sys/file.h>
35#include <sys/param.h>
36
37#include <convert.h>
38
39#if !defined(TEXT_DOMAIN)	/* Should be defined by cc -D */
40#define	TEXT_DOMAIN "SYS_TEST"	/* Use this only if it weren't */
41#endif
42
43const char	*opt_string = "pf:o:i:FTD?";
44
45char		*Stdin;
46char		*Stdout;
47char		*Suffix = (char *)".AUDCVTMP";
48
49char		*progname; // program name
50char		*fake_argv[] = {(char *)"-", NULL}; // stdin with no args
51
52extern char	*optarg;
53extern int	optind;
54
55int		Statistics = 0;
56int		Debug = 0;
57
58void		init_header(AudioHdr&);
59void		usage();
60
61int
62main(int argc, char *argv[])
63{
64	AudioUnixfile*	ifp = NULL;	// input & output audio objects
65	AudioUnixfile*	ofp = NULL;
66	AudioHdr	ihdr;		// input/output headers
67	AudioHdr	ohdr;
68	char		*infile = NULL; // input/output file names
69	char		*outfile = NULL;
70	char		*realfile = NULL;
71	char		*out_fmt = NULL;	// output fmt string
72	AudioError	err;		// for error msgs
73	int		c;		// for getopt
74	int		pflag = 0;	// in place flag
75	int		fflag = 0;	// ignore header (force conversion)
76	int		stdin_seen = 0;	// already read stdin
77	int		israw = 0;	// once we've seen -i, it's raw data
78	format_type	ofmt = F_SUN;	// output format type
79	format_type	ifmt = F_SUN;	// expected input format type
80	format_type	fmt = F_SUN;	// actual input format type
81	off_t		o_offset = 0;	// output offset (ignored)
82	off_t		i_offset = 0;	// input offset
83	int		i;
84	struct stat	st;
85
86	setlocale(LC_ALL, "");
87	(void) textdomain(TEXT_DOMAIN);
88
89	// basename of program
90	if (progname = strrchr(argv[0], '/')) {
91		progname++;
92	} else {
93		progname = argv[0];
94	}
95	Stdin = MGET("(stdin)");
96	Stdout = MGET("(stdout)");
97
98	// init the input & output headers
99	init_header(ihdr);
100	init_header(ohdr);
101
102	// some conversions depend on invocation name. we'll create
103	// default input/output formats based on argv[0] that
104	// can be overridden by -o or -i options.
105	if (strcmp(progname, "ulaw2pcm") == 0) {
106		(void) parse_format((char *)"ulaw", ihdr, ifmt, i_offset);
107		(void) parse_format((char *)"pcm", ohdr, ofmt, o_offset);
108	} else if (strcmp(progname, "pcm2ulaw") == 0) {
109		(void) parse_format((char *)"pcm", ihdr, ifmt, i_offset);
110		(void) parse_format((char *)"ulaw", ohdr, ofmt, o_offset);
111	} else if (strcmp(progname, "adpcm_enc") == 0) {
112		(void) parse_format((char *)"ulaw", ihdr, ifmt, i_offset);
113		(void) parse_format((char *)"g721", ohdr, ofmt, o_offset);
114	} else if (strcmp(progname, "adpcm_dec") == 0) {
115		(void) parse_format((char *)"g721", ihdr, ifmt, i_offset);
116		(void) parse_format((char *)"ulaw", ohdr, ofmt, o_offset);
117	} else if (strcmp(progname, "raw2audio") == 0) {
118		(void) parse_format((char *)"ulaw", ihdr, ifmt, i_offset);
119		(void) parse_format((char *)"ulaw", ohdr, ofmt, o_offset);
120		israw++;
121		pflag++;
122	} else if (argc <= 1) {
123		// audioconvert with no arguments
124		usage();
125	}
126
127	// now parse the rest of the arg's
128	while ((c = getopt(argc, argv, opt_string)) != -1) {
129		switch (c) {
130#ifdef DEBUG
131		case 'D':
132			// enable debug messages
133			Debug++;
134			break;
135#endif
136		case 'p':
137			// convert files in place
138			if (outfile != NULL) {
139				Err(MGET("can't use -p with -o\n"));
140				exit(1);
141			}
142			pflag++;
143			break;
144		case 'F':
145			// force treatment of audio files as raw files
146			// (ignore filehdr).
147			fflag++;
148			break;
149		case 'f':
150			// save format string to parse later, but verify now
151			out_fmt = optarg;
152			if (parse_format(out_fmt, ohdr, ofmt, o_offset) == -1)
153				exit(1);
154			if (o_offset != 0) {
155				Err(MGET("can't specify an offset with -f\n"));
156				exit(1);
157			}
158			break;
159		case 'o':
160			if (pflag) {
161				Err(MGET("can't use -o with -p\n"));
162				exit(1);
163			}
164			outfile = optarg;
165			break;
166		case 'i':
167			// if bogus input header, exit ...
168			if (parse_format(optarg, ihdr, ifmt, i_offset) == -1) {
169				exit(1);
170			}
171			israw++;
172			break;
173		default:
174		case '?':
175			usage();
176		}
177	}
178
179	// XXX - should check argument consistency here....
180
181	// If no args left, we're taking input from stdin.
182	// In this case, make argv point to a fake argv with "-" as a file
183	// name, and set optind and argc apropriately so we'll go through
184	// the loop below once.
185	if (optind >= argc) {
186		argv = fake_argv;
187		argc = 1;
188		optind = 0;
189		/*
190		 * XXX - we turn off pflag if stdin is the only input file.
191		 * this is kind of a hack. if invoked as raw2audio, pflag
192		 * it turned on. if no files are given, we want to turn
193		 * it off, otherwise we'll complain about using -p with
194		 * stdin, which won't make sense if invoked as raw2audio.
195		 * instead, just silently ignore. the message is still given
196		 * and stdin is ignored if it's specified as one of several
197		 * input files.
198		 */
199		pflag = 0;
200	}
201
202	// From this point on we're looking at file names or -i args
203	// for input format specs.
204	for (; optind < argc; optind++) {
205		// new input format spec.
206		if (strcmp(argv[optind], "-i") == 0) {
207			init_header(ihdr);
208			i_offset = 0;
209			ifmt = F_SUN;
210			// if bogus input header, exit ...
211			if (parse_format(argv[++optind], ihdr, ifmt, i_offset)
212			    == -1) {
213				exit(1);
214			}
215			israw++;
216		} else if (strcmp(argv[optind], "-") == 0) {
217			// ignore stdin argument if in place
218			if (pflag) {
219				Err(MGET("can't use %s with -p flag\n"),
220				    Stdin);
221				continue;
222			}
223
224			if (stdin_seen) {
225				Err(MGET("already used stdin for input\n"));
226				continue;
227			} else {
228				stdin_seen++;
229			}
230
231			infile = Stdin;
232		} else {
233			infile = argv[optind];
234		}
235
236		// if no audio object returned, just continue to the next
237		// file. if a fatal error occurs, open_input_file()
238		// will exit the program.
239		ifp =
240		    open_input_file(infile, ihdr, israw, fflag, i_offset, fmt);
241		if (!ifp) {
242			continue;
243		}
244
245		if ((err = ifp->Open()) != AUDIO_SUCCESS) {
246			Err(MGET("open error on input file %s - %s\n"),
247			    infile, err.msg());
248			exit(1);
249		}
250		ifp->Reference();
251
252		// create the output file if not created yet, or if
253		// converting in place. ofp will be NULL only the first
254		// time through. use the header of the first input file
255		// to base the output format on - then create the output
256		// header w/the output format spec.
257		if ((ofp == NULL) && !pflag) {
258
259			ohdr = ifp->GetHeader();
260			ohdr = ifp->GetHeader();
261			ofmt = ifmt;
262			// just use input hdr if no output hdr spec
263			if (out_fmt) {
264				if (parse_format(out_fmt, ohdr, ofmt, o_offset)
265				    == -1) {
266					exit(1);
267				}
268			}
269
270			// need to check before output is opened ...
271			if (verify_conversion(ifp->GetHeader(), ohdr) == -1) {
272				// XXX - bomb out or skip?
273				exit(3);
274			}
275
276			// Create the file and set the info string.
277			char		*infoString;
278			int		infoStringLen;
279			infoString = ifp->GetInfostring(infoStringLen);
280			ofp = create_output_file(outfile, ohdr, ofmt,
281						    infoString);
282
283		} else if (pflag) {
284
285			// create new output header based on each input file
286			ohdr = ifp->GetHeader();
287			ofmt = ifmt;
288			// just use input hdr if no output hdr spec
289			if (out_fmt) {
290				if (parse_format(out_fmt, ohdr, ofmt, o_offset)
291				    == -1) {
292					exit(1);
293				}
294			}
295
296			// get the *real* path of the infile (follow sym-links),
297			// and the stat info.
298			realfile = infile;
299			get_realfile(realfile, &st);
300
301			// if the file is read-only, give up
302			if (access(realfile, W_OK)) {
303				// XXX - do we really want to exit?
304				perror(infile);
305				Err(MGET("cannot rewrite in place\n"));
306				exit(1);
307			}
308
309			// this is now the output file.
310			i = strlen(realfile) + strlen(Suffix) + 1;
311			outfile = (char *)malloc((unsigned)i);
312			if (outfile == NULL) {
313				Err(MGET("out of memory\n"));
314				exit(1);
315			}
316			(void) sprintf(outfile, "%s%s", realfile, Suffix);
317
318			// outfile will get re-assigned to a tmp file
319			if (verify_conversion(ifp->GetHeader(), ohdr) == -1) {
320				// XXX - bomb out or skip?
321				exit(3);
322			}
323
324			// If no conversion, just skip the file
325			if (noop_conversion(ifp->GetHeader(), ohdr,
326			    fmt, ofmt, i_offset, o_offset)) {
327				if (Debug)
328				    Err(MGET(
329					"%s: no-op conversion...skipping\n"),
330					infile);
331				continue;
332			}
333
334			// Get the input info string.
335			char		*infoString;
336			int		infoStringLen;
337			infoString = ifp->GetInfostring(infoStringLen);
338			ofp = create_output_file(outfile, ohdr, ofmt,
339						    infoString);
340		}
341
342		// verify that it's a valid conversion by looking at the
343		// file headers. (this will be called twice for the first
344		// file if *not* converting in place. that's ok....
345		if (!pflag && (verify_conversion(ifp->GetHeader(), ohdr)
346		    == -1)) {
347			// XXX - bomb out or skip file if invalid conversion?
348			exit(3);
349		}
350
351		// do the conversion, if error, bomb out
352		if (do_convert(ifp, ofp) == -1) {
353			exit(4);
354		}
355
356		ifp->Close();
357		ifp->Dereference();
358
359		// if in place, finish up by renaming the outfile to
360		// back to the infile.
361		if (pflag) {
362			delete(ofp);	// will close and deref, etc.
363
364			if (rename(outfile, realfile) < 0) {
365				perror(outfile);
366				Err(MGET("error renaming %s to %s"),
367				    outfile, realfile);
368				exit(1);
369			}
370			/* Set the permissions to match the original */
371			if (chmod(realfile, (int)st.st_mode) < 0) {
372				Err(MGET("WARNING: could not reset mode of"));
373				perror(realfile);
374			}
375		}
376	}
377
378	if (!pflag) {
379		delete(ofp);		// close output file
380	}
381
382	return (0);
383}
384
385
386// initialize audio hdr to default val's
387void
388init_header(
389	AudioHdr&	hdr)
390{
391	hdr.encoding = NONE;
392	hdr.sample_rate = 0;
393	hdr.samples_per_unit = 0;
394	hdr.bytes_per_unit = 0;
395	hdr.channels = 0;
396}
397
398extern "C" { void _doprnt(char *, ...); }
399
400// report a fatal error and exit
401void
402Err(char *format, ...)
403{
404	va_list ap;
405
406	va_start(ap, format);
407	fprintf(stderr, "%s: ", progname);
408	_doprnt(format, ap, stderr);
409	fflush(stderr);
410	va_end(ap);
411}
412
413void
414usage()
415{
416	fprintf(stderr, MGET(
417	    "Convert between audio file formats and data encodings -- usage:\n"
418	    "\t%s [-pF] [-f outfmt] [-o outfile] [[-i infmt] [file ...]] ...\n"
419	    "where:\n"
420	    "\t-p\tConvert files in place\n"
421	    "\t-F\tForce interpretation of -i (ignore existing file hdr)\n"
422	    "\t-f\tOutput format description\n"
423	    "\t-o\tOutput file (default: stdout)\n"
424	    "\t-i\tInput format description\n"
425	    "\tfile\tList of files to convert (default: stdin)\n\n"
426	    "Format Description:\n"
427	    "\tkeyword=value[,keyword=value...]\n"
428	    "where:\n"
429	    "\tKeywords:\tValues:\n"
430	    "\trate\t\tSample Rate in samples/second\n"
431	    "\tchannels\tNumber of interleaved channels\n"
432	    "\tencoding\tAudio encoding. One of:\n"
433	    "\t\t\t    ulaw, alaw, g721, g723,\n"
434	    "\t\t\t    linear8, linear16, linear32\n"
435	    "\t\t\t    pcm   (same as linear16)\n"
436	    "\t\t\t    voice (ulaw,mono,rate=8k)\n"
437	    "\t\t\t    cd    (linear16,stereo,rate=44.1k)\n"
438	    "\t\t\t    dat   (linear16,stereo,rate=48k)\n"
439	    "\tformat\t\tFile format. One of:\n"
440	    "\t\t\t    sun, raw (no format)\n"
441	    "\toffset\t\tByte offset (raw input only)\n"),
442	    progname);
443	exit(1);
444}
445