OpenSolaris_b135/cmd/audio/audioconvert/main.cc

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright (c) 1993-2001 by Sun Microsystems, Inc.
 * All rights reserved.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <sys/param.h>

#include <convert.h>

#if !defined(TEXT_DOMAIN)	/* Should be defined by cc -D */
#define	TEXT_DOMAIN "SYS_TEST"	/* Use this only if it weren't */
#endif

const char	*opt_string = "pf:o:i:FTD?";

char		*Stdin;
char		*Stdout;
char		*Suffix = (char *)".AUDCVTMP";

char		*progname; // program name
char		*fake_argv[] = {(char *)"-", NULL}; // stdin with no args

extern char	*optarg;
extern int	optind;

int		Statistics = 0;
int		Debug = 0;

void		init_header(AudioHdr&);
void		usage();

main(
	int		argc,
	char		*argv[])
{
	AudioUnixfile*	ifp = NULL;	// input & output audio objects
	AudioUnixfile*	ofp = NULL;
	AudioHdr	ihdr;		// input/output headers
	AudioHdr	ohdr;
	char		*infile = NULL; // input/output file names
	char		*outfile = NULL;
	char		*realfile = NULL;
	char		*out_fmt = NULL;	// output fmt string
	AudioError	err;		// for error msgs
	int		c;		// for getopt
	int		pflag = 0; 	// in place flag
	int		fflag = 0;	// ignore header (force conversion)
	int		stdin_seen = 0;	// already read stdin
	int		israw = 0;	// once we've seen -i, it's raw data
	format_type	ofmt = F_SUN;	// output format type
	format_type	ifmt = F_SUN;	// expected input format type
	format_type	fmt = F_SUN;	// actual input format type
	off_t		o_offset = 0;	// output offset (ignored)
	off_t		i_offset = 0;	// input offset
	int		i;
	struct stat	st;

	setlocale(LC_ALL, "");
	(void) textdomain(TEXT_DOMAIN);

	// basename of program
	if (progname = strrchr(argv[0], '/')) {
		progname++;
	} else {
		progname = argv[0];
	}
	Stdin = MGET("(stdin)");
	Stdout = MGET("(stdout)");

	// init the input & output headers
	init_header(ihdr);
	init_header(ohdr);

	// some conversions depend on invocation name. we'll create
	// default input/output formats based on argv[0] that
	// can be overridden by -o or -i options.
	if (strcmp(progname, "ulaw2pcm") == 0) {
		(void) parse_format((char *)"ulaw", ihdr, ifmt, i_offset);
		(void) parse_format((char *)"pcm", ohdr, ofmt, o_offset);
	} else if (strcmp(progname, "pcm2ulaw") == 0) {
		(void) parse_format((char *)"pcm", ihdr, ifmt, i_offset);
		(void) parse_format((char *)"ulaw", ohdr, ofmt, o_offset);
	} else if (strcmp(progname, "adpcm_enc") == 0) {
		(void) parse_format((char *)"ulaw", ihdr, ifmt, i_offset);
		(void) parse_format((char *)"g721", ohdr, ofmt, o_offset);
	} else if (strcmp(progname, "adpcm_dec") == 0) {
		(void) parse_format((char *)"g721", ihdr, ifmt, i_offset);
		(void) parse_format((char *)"ulaw", ohdr, ofmt, o_offset);
	} else if (strcmp(progname, "raw2audio") == 0) {
		(void) parse_format((char *)"ulaw", ihdr, ifmt, i_offset);
		(void) parse_format((char *)"ulaw", ohdr, ofmt, o_offset);
		israw++;
		pflag++;
	} else if (argc <= 1) {
		// audioconvert with no arguments
		usage();
	}

	// now parse the rest of the arg's
	while ((c = getopt(argc, argv, opt_string)) != -1) {
		switch (c) {
#ifdef DEBUG
		case 'D':
			// enable debug messages
			Debug++;
			break;
#endif
		case 'p':
			// convert files in place
			if (outfile != NULL) {
				Err(MGET("can't use -p with -o\n"));
				exit(1);
			}
			pflag++;
			break;
		case 'F':
			// force treatment of audio files as raw files
			// (ignore filehdr).
			fflag++;
			break;
		case 'f':
			// save format string to parse later, but verify now
			out_fmt = optarg;
			if (parse_format(out_fmt, ohdr, ofmt, o_offset) == -1)
				exit(1);
			if (o_offset != 0) {
				Err(MGET("can't specify an offset with -f\n"));
				exit(1);
			}
			break;
		case 'o':
			if (pflag) {
				Err(MGET("can't use -o with -p\n"));
				exit(1);
			}
			outfile = optarg;
			break;
		case 'i':
			// if bogus input header, exit ...
			if (parse_format(optarg, ihdr, ifmt, i_offset) == -1) {
				exit(1);
			}
			israw++;
			break;
		default:
		case '?':
			usage();
		}
	}

	// XXX - should check argument consistency here....

	// If no args left, we're taking input from stdin.
	// In this case, make argv point to a fake argv with "-" as a file
	// name, and set optind and argc apropriately so we'll go through
	// the loop below once.
	if (optind >= argc) {
		argv = fake_argv;
		argc = 1;
		optind = 0;
		/*
		 * XXX - we turn off pflag if stdin is the only input file.
		 * this is kind of a hack. if invoked as raw2audio, pflag
		 * it turned on. if no files are given, we want to turn
		 * it off, otherwise we'll complain about using -p with
		 * stdin, which won't make sense if invoked as raw2audio.
		 * instead, just silently ignore. the message is still given
		 * and stdin is ignored if it's specified as one of several
		 * input files.
		 */
		pflag = 0;
	}

	// From this point on we're looking at file names or -i args
	// for input format specs.
	for (; optind < argc; optind++) {
		// new input format spec.
		if (strcmp(argv[optind], "-i") == 0) {
			init_header(ihdr);
			i_offset = 0;
			ifmt = F_SUN;
			// if bogus input header, exit ...
			if (parse_format(argv[++optind], ihdr, ifmt, i_offset)
			    == -1) {
				exit(1);
			}
			israw++;
		} else if (strcmp(argv[optind], "-") == 0) {
			// ignore stdin argument if in place
			if (pflag) {
				Err(MGET("can't use %s with -p flag\n"),
				    Stdin);
				continue;
			}

			if (stdin_seen) {
				Err(MGET("already used stdin for input\n"));
				continue;
			} else {
				stdin_seen++;
			}

			infile = Stdin;
		} else {
			infile = argv[optind];
		}

		// if no audio object returned, just continue to the next
		// file. if a fatal error occurs, open_input_file()
		// will exit the program.
		ifp =
		    open_input_file(infile, ihdr, israw, fflag, i_offset, fmt);
		if (!ifp) {
			continue;
		}

		if ((err = ifp->Open()) != AUDIO_SUCCESS) {
			Err(MGET("open error on input file %s - %s\n"),
			    infile, err.msg());
			exit(1);
		}
		ifp->Reference();

		// create the output file if not created yet, or if
		// converting in place. ofp will be NULL only the first
		// time through. use the header of the first input file
		// to base the output format on - then create the output
		// header w/the output format spec.
		if ((ofp == NULL) && !pflag) {

			ohdr = ifp->GetHeader();
			ohdr = ifp->GetHeader();
			ofmt = ifmt;
			// just use input hdr if no output hdr spec
			if (out_fmt) {
				if (parse_format(out_fmt, ohdr, ofmt, o_offset)
				    == -1) {
					exit(1);
				}
			}

			// need to check before output is opened ...
			if (verify_conversion(ifp->GetHeader(), ohdr) == -1) {
				// XXX - bomb out or skip?
				exit(3);
			}

			// Create the file and set the info string.
			char		*infoString;
			int  		infoStringLen;
			infoString = ifp->GetInfostring(infoStringLen);
			ofp = create_output_file(outfile, ohdr, ofmt,
						    infoString);

		} else if (pflag) {

			// create new output header based on each input file
			ohdr = ifp->GetHeader();
			ofmt = ifmt;
			// just use input hdr if no output hdr spec
			if (out_fmt) {
				if (parse_format(out_fmt, ohdr, ofmt, o_offset)
				    == -1) {
					exit(1);
				}
			}

			// get the *real* path of the infile (follow sym-links),
			// and the stat info.
			realfile = infile;
			get_realfile(realfile, &st);

			// if the file is read-only, give up
			if (access(realfile, W_OK)) {
				// XXX - do we really want to exit?
				perror(infile);
				Err(MGET("cannot rewrite in place\n"));
				exit(1);
			}

			// this is now the output file.
			i = strlen(realfile) + strlen(Suffix) + 1;
			outfile = (char *)malloc((unsigned)i);
			if (outfile == NULL) {
				Err(MGET("out of memory\n"));
				exit(1);
			}
			(void) sprintf(outfile, "%s%s", realfile, Suffix);

			// outfile will get re-assigned to a tmp file
			if (verify_conversion(ifp->GetHeader(), ohdr) == -1) {
				// XXX - bomb out or skip?
				exit(3);
			}

			// If no conversion, just skip the file
			if (noop_conversion(ifp->GetHeader(), ohdr,
			    fmt, ofmt, i_offset, o_offset)) {
				if (Debug)
				    Err(MGET(
					"%s: no-op conversion...skipping\n"),
					infile);
				continue;
			}

			// Get the input info string.
			char		*infoString;
			int  		infoStringLen;
			infoString = ifp->GetInfostring(infoStringLen);
			ofp = create_output_file(outfile, ohdr, ofmt,
						    infoString);
		}

		// verify that it's a valid conversion by looking at the
		// file headers. (this will be called twice for the first
		// file if *not* converting in place. that's ok....
		if (!pflag && (verify_conversion(ifp->GetHeader(), ohdr)
		    == -1)) {
			// XXX - bomb out or skip file if invalid conversion?
			exit(3);
		}

		// do the conversion, if error, bomb out
		if (do_convert(ifp, ofp) == -1) {
			exit(4);
		}

		ifp->Close();
		ifp->Dereference();

		// if in place, finish up by renaming the outfile to
		// back to the infile.
		if (pflag) {
			delete(ofp);	// will close and deref, etc.

			if (rename(outfile, realfile) < 0) {
				perror(outfile);
				Err(MGET("error renaming %s to %s"),
				    outfile, realfile);
				exit(1);
			}
			/* Set the permissions to match the original */
			if (chmod(realfile, (int)st.st_mode) < 0) {
				Err(MGET("WARNING: could not reset mode of"));
				perror(realfile);
			}
		}
	}

	if (!pflag) {
		delete(ofp);		// close output file
	}

	exit(0);		// outta here!
}


// initialize audio hdr to default val's
void
init_header(
	AudioHdr&	hdr)
{
	hdr.encoding = NONE;
	hdr.sample_rate = 0;
	hdr.samples_per_unit = 0;
	hdr.bytes_per_unit = 0;
	hdr.channels = 0;
}

extern "C" { void _doprnt(char *, ...); }

// report a fatal error and exit
void
Err(char *format, ...)
{
	va_list ap;

	va_start(ap, format);
	fprintf(stderr, "%s: ", progname);
	_doprnt(format, ap, stderr);
	fflush(stderr);
	va_end(ap);
}

void
usage()
{
	fprintf(stderr, MGET(
	    "Convert between audio file formats and data encodings -- usage:\n"
	    "\t%s [-pF] [-f outfmt] [-o outfile] [[-i infmt] [file ...]] ...\n"
	    "where:\n"
	    "\t-p\tConvert files in place\n"
	    "\t-F\tForce interpretation of -i (ignore existing file hdr)\n"
	    "\t-f\tOutput format description\n"
	    "\t-o\tOutput file (default: stdout)\n"
	    "\t-i\tInput format description\n"
	    "\tfile\tList of files to convert (default: stdin)\n\n"
	    "Format Description:\n"
	    "\tkeyword=value[,keyword=value...]\n"
	    "where:\n"
	    "\tKeywords:\tValues:\n"
	    "\trate\t\tSample Rate in samples/second\n"
	    "\tchannels\tNumber of interleaved channels\n"
	    "\tencoding\tAudio encoding. One of:\n"
	    "\t\t\t    ulaw, alaw, g721, g723,\n"
	    "\t\t\t    linear8, linear16, linear32\n"
	    "\t\t\t    pcm   (same as linear16)\n"
	    "\t\t\t    voice (ulaw,mono,rate=8k)\n"
	    "\t\t\t    cd    (linear16,stereo,rate=44.1k)\n"
	    "\t\t\t    dat   (linear16,stereo,rate=48k)\n"
	    "\tformat\t\tFile format. One of:\n"
	    "\t\t\t    sun, raw (no format)\n"
	    "\toffset\t\tByte offset (raw input only)\n"),
	    progname);
	exit(1);
}