OpenSolaris_b135/cmd/audio/utilities/AudioHdrParse.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 2005 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

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

#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <math.h>
#include <AudioHdr.h>

#define	irint(d)	((int)(d))

// Convert a string to lowercase and return an allocated copy of it.
// XXX - There really should be a string-insensitive 8-bit compare routine.
static char *
to_lowercase(
	char	*str)
{
	unsigned char	*oldstr;
	unsigned char	*newstr;
	int		i;

	oldstr = (unsigned char *) str;
	newstr = new unsigned char [strlen(str) + 1];
	for (i = 0; ; i++) {
		if (isupper(oldstr[i]))
			newstr[i] = tolower(oldstr[i]);
		else
			newstr[i] = oldstr[i];
		if (oldstr[i] == '\0')
			break;
	}
	return ((char *)newstr);
}



// class AudioHdr parsing methods


// Return a string containing the sample rate
char *AudioHdr::
RateString() const
{
	char	*str;
	int	ratek;
	int	rateh;
	int	prec;

	str = new char[32];
	ratek = sample_rate / 1000;
	rateh = sample_rate % 1000;
	if (rateh == 0) {
		(void) sprintf(str, "%dkHz", ratek);
	} else {
		// scale down to print minimum digits after the decimal point
		prec = 3;
		if ((rateh % 10) == 0) {
			prec--;
			rateh /= 10;
		}
		if ((rateh % 10) == 0) {
			prec--;
			rateh /= 10;
		}
		(void) sprintf(str, "%d.%0*dkHz", ratek, prec, rateh);
	}
	return (str);
}

// Return a string containing the number of channels
char *AudioHdr::
ChannelString() const
{
	char	*str;

	str = new char[32];
	switch (channels) {
	case 1:
		(void) sprintf(str, "mono");
		break;
	case 2:
		(void) sprintf(str, "stereo");
		break;
	case 4:
		(void) sprintf(str, "quad");
		break;
	default:
		(void) sprintf(str, "%d-channel", channels);
		break;
	}
	return (str);
}

// Return a string containing the encoding
char *AudioHdr::
EncodingString() const
{
	char	*str;
	Double	prec;
	int	iprec;

	str = new char[64];
	if ((samples_per_unit == 0) || (bytes_per_unit == 0) ||
	    (encoding == NONE)) {
		(void) sprintf(str, "???");
	} else {
		// First encode precision
		iprec = (bytes_per_unit * 8) / samples_per_unit;
		prec = ((Double)bytes_per_unit * 8.) / (Double)samples_per_unit;
		if (prec == (Double) iprec) {
			(void) sprintf(str, "%d-bit ", iprec);
		} else {
			(void) sprintf(str, "%.1f-bit ", double(prec));
		}

		// Then encode format
		switch (encoding) {
		case ULAW:
			// XXX - See bug 1121000
			// XXX - (void) strcat(str, "µ-law");
			(void) strcat(str, "u-law");
			break;
		case ALAW:
			(void) strcat(str, "A-law");
			break;
		case LINEAR:
			(void) strcat(str, "linear");
			break;
		case FLOAT:
			(void) strcat(str, "float");
			break;
		case G721:
			(void) strcat(str, "G.721 ADPCM");
			break;
		case G722:
			(void) strcat(str, "G.722 ADPCM");
			break;
		case G723:
			(void) strcat(str, "G.723 ADPCM");
			break;
		case DVI:
			(void) strcat(str, "DVI ADPCM");
			break;
		default:
			(void) strcat(str, "???");
			break;
		}
	}
	return (str);
}

// Return a string containing the entire audio encoding
char *AudioHdr::
FormatString() const
{
	char	*str;
	char	*rate;
	char	*chan;
	char	*enc;

	str = new char[4 * 32];

	enc = EncodingString();
	rate = RateString();
	chan = ChannelString();
	(void) sprintf(str, "%s, %s, %s", enc, rate, chan);
	delete rate;
	delete chan;
	delete enc;
	return (str);
}

// Parse a string containing the sample rate
AudioError AudioHdr::
RateParse(
	char		*str)
{
static char		*lib_khz = NULL;
static char		*lib_hz = NULL;

	double		r;
	int		rate;
	char		khzbuf[16];
	char		*khz;

	if (str == NULL)
		return (AUDIO_ERR_BADARG);

	// Init i18n string translations
	if (lib_khz == NULL) {
		lib_khz = to_lowercase(_MGET_("khz"));
		lib_hz = to_lowercase(_MGET_("hz"));
	}

	// Scan for a number followed by an optional khz designator
	switch (sscanf(str, " %lf %15s", &r, khzbuf)) {
	case 2:
		// Process 'khz', if present, and fall through
		khz = to_lowercase(khzbuf);
		if ((strcmp(khz, "khz") == 0) ||
		    (strcmp(khz, "khertz") == 0) ||
		    (strcmp(khz, "kilohertz") == 0) ||
		    (strcmp(khz, "k") == 0) ||
		    (strcoll(khz, lib_khz) == 0)) {
			r *= 1000.;
		} else if ((strcmp(khz, "hz") != 0) &&
		    (strcmp(khz, "hertz") != 0) &&
		    (strcoll(khz, lib_hz) != 0)) {
			delete khz;
			return (AUDIO_ERR_BADARG);
		}
		delete khz;
	case 1:
		rate = irint(r);
		break;
	default:
		return (AUDIO_ERR_BADARG);
	}
	// Check for reasonable bounds
	if ((rate <= 0) || (rate > 500000)) {
		return (AUDIO_ERR_BADARG);
	}
	sample_rate = (unsigned int) rate;
	return (AUDIO_SUCCESS);
}

// Parse a string containing the number of channels
AudioError AudioHdr::
ChannelParse(
	char		*str)
{
static char		*lib_chan = NULL;
static char		*lib_mono = NULL;
static char		*lib_stereo = NULL;
	char		cstrbuf[16];
	char		*cstr;
	char		xtra[4];
	int		chan;

	// Init i18n string translations
	if (lib_chan == NULL) {
		lib_chan = to_lowercase(_MGET_("channel"));
		lib_mono = to_lowercase(_MGET_("mono"));
		lib_stereo = to_lowercase(_MGET_("stereo"));
	}

	// Parse a number, followed by optional "-channel"
	switch (sscanf(str, " %d %15s", &chan, cstrbuf)) {
	case 2:
		cstr = to_lowercase(cstrbuf);
		if ((strcmp(cstr, "-channel") != 0) &&
		    (strcmp(cstr, "-chan") != 0) &&
		    (strcoll(cstr, lib_chan) != 0)) {
			delete cstr;
			return (AUDIO_ERR_BADARG);
		}
		delete cstr;
	case 1:
		break;
	default:
		// If no number, look for reasonable keywords
		if (sscanf(str, " %15s %1s", cstrbuf, xtra) != 1) {
			return (AUDIO_ERR_BADARG);
		}
		cstr = to_lowercase(cstrbuf);
		if ((strcmp(cstr, "mono") == 0) ||
		    (strcmp(cstr, "monaural") == 0) ||
		    (strcoll(cstr, lib_mono) == 0)) {
			chan = 1;
		} else if ((strcmp(cstr, "stereo") == 0) ||
		    (strcmp(cstr, "dual") == 0) ||
		    (strcoll(cstr, lib_stereo) == 0)) {
			chan = 2;
		} else if ((strcmp(cstr, "quad") == 0) ||
		    (strcmp(cstr, "quadrophonic") == 0)) {
			chan = 4;
		} else {
			delete cstr;
			return (AUDIO_ERR_BADARG);
		}
		delete cstr;
	}
	if ((chan <= 0) || (chan > 256)) {
		return (AUDIO_ERR_BADARG);
	}
	channels = (unsigned int) chan;
	return (AUDIO_SUCCESS);
}

// Parse a string containing the audio encoding
AudioError AudioHdr::
EncodingParse(
	char		*str)
{
static char		*lib_bit = NULL;
static char		*lib_ulaw = NULL;
static char		*lib_Alaw = NULL;
static char		*lib_linear = NULL;
	int		i;
	char		*p;
	char		estrbuf[64];
	char		*estr;
	char		xtrabuf[32];
	char		*xtra;
	char		*xp;
	char		buf[BUFSIZ];
	char		*cp;
	double		prec;

	// Init i18n string translations
	if (lib_bit == NULL) {
		lib_bit = to_lowercase(_MGET_("bit"));
		lib_ulaw = to_lowercase(_MGET_("u-law"));
		lib_Alaw = to_lowercase(_MGET_("A-law"));
		lib_linear = to_lowercase(_MGET_("linear8"));
		lib_linear = to_lowercase(_MGET_("linear"));
	}

	// first copy and remove leading spaces
	(void) strncpy(buf, str, BUFSIZ);
	for (cp = buf; *cp == ' '; cp++)
		continue;

	// Delimit the precision.  If there is one, parse it.
	prec = 0.;
	p = strchr(cp, ' ');
	if (p != NULL) {
		*p++ = '\0';
		i = sscanf(cp, " %lf %15s", &prec, xtrabuf);
		if (i == 0) {
			return (AUDIO_ERR_BADARG);
		}
		if (i == 2) {
			// convert to lowercase and skip leading "-", if any
			xtra = to_lowercase(xtrabuf);
			xp = (xtra[0] == '-') ? &xtra[1] : &xtra[0];

			if ((strcmp(xp, "bit") != 0) &&
			    (strcoll(xp, lib_bit) != 0)) {
				delete xtra;
				return (AUDIO_ERR_BADARG);
			}
			delete xtra;
		}
		if ((prec <= 0.) || (prec > 512.)) {
			return (AUDIO_ERR_BADARG);
		}

		// Don't be fooled by "8 bit"
		i = sscanf(p, " %15s", xtrabuf);
		if (i == 1) {
			// convert to lowercase and skip leading "-", if any
			xtra = to_lowercase(xtrabuf);
			xp = (xtra[0] == '-') ? &xtra[1] : &xtra[0];
			if ((strcmp(xp, "bit") == 0) ||
			    (strcoll(xp, lib_bit) == 0)) {
				    xp = strchr(p, ' ');
				    if (xp != NULL)
					    p = xp;
				    else
					    p += strlen(xtrabuf);
			}
			delete xtra;
		}
	} else {
		p = cp;
	}

	i = sscanf(p, " %31s %31s", estrbuf, xtrabuf);

	// If "adpcm" appended with a space, concatenate it
	if (i == 2) {
		xtra = to_lowercase(xtrabuf);
		if (strcmp(xtra, "adpcm") == 0) {
			(void) strcat(estrbuf, xtra);
			i = 1;
		}
		delete xtra;
	}
	if (i == 1) {
		estr = to_lowercase(estrbuf);
		if ((strcmp(estr, "ulaw") == 0) ||
		    (strcmp(estr, "u-law") == 0) ||
		    (strcmp(estr, "µlaw") == 0) ||
		    (strcmp(estr, "µ-law") == 0) ||
		    (strcmp(estr, "mulaw") == 0) ||
		    (strcmp(estr, "mu-law") == 0) ||
		    (strcoll(estr, lib_ulaw) == 0)) {
			if ((prec != 0.) && (prec != 8.))
				return (AUDIO_ERR_BADARG);
			encoding = ULAW;
			samples_per_unit = 1;
			bytes_per_unit = 1;
		} else if ((strcmp(estr, "alaw") == 0) ||
		    (strcmp(estr, "a-law") == 0) ||
		    (strcoll(estr, lib_Alaw) == 0)) {
			if ((prec != 0.) && (prec != 8.))
				return (AUDIO_ERR_BADARG);
			encoding = ALAW;
			samples_per_unit = 1;
			bytes_per_unit = 1;

		} else if ((strcmp(estr, "linear") == 0) ||
		    (strcmp(estr, "lin") == 0) ||
		    (strcmp(estr, "pcm") == 0) ||
		    (strcoll(estr, lib_linear) == 0)) {
			if ((prec != 0.) && (prec != 8.) && (prec != 16.) &&
			    (prec != 24.) && (prec != 32.))
				return (AUDIO_ERR_BADARG);
			if (prec == 0.)
				prec = 16.;
			encoding = LINEAR;
			samples_per_unit = 1;
			bytes_per_unit = irint(prec / 8.);

		} else if ((strcmp(estr, "linear8") == 0) ||
		    (strcmp(estr, "lin8") == 0) ||
		    (strcmp(estr, "pcm8") == 0)) {
			if ((prec != 0.) && (prec != 8.))
				return (AUDIO_ERR_BADARG);
			prec = 8.;
			encoding = LINEAR;
			samples_per_unit = 1;
			bytes_per_unit = irint(prec / 8.);

		} else if ((strcmp(estr, "linear16") == 0) ||
		    (strcmp(estr, "lin16") == 0) ||
		    (strcmp(estr, "pcm16") == 0)) {
			if ((prec != 0.) && (prec != 16.))
				return (AUDIO_ERR_BADARG);
			prec = 16.;
			encoding = LINEAR;
			samples_per_unit = 1;
			bytes_per_unit = irint(prec / 8.);

		} else if ((strcmp(estr, "linear24") == 0) ||
		    (strcmp(estr, "lin24") == 0) ||
		    (strcmp(estr, "pcm24") == 0)) {
			if ((prec != 0.) && (prec != 24.))
				return (AUDIO_ERR_BADARG);
			prec = 24.;
			encoding = LINEAR;
			samples_per_unit = 1;
			bytes_per_unit = irint(prec / 8.);

		} else if ((strcmp(estr, "linear32") == 0) ||
		    (strcmp(estr, "lin32") == 0) ||
		    (strcmp(estr, "pcm32") == 0)) {
			if ((prec != 0.) && (prec != 32.))
				return (AUDIO_ERR_BADARG);
			prec = 32.;
			encoding = LINEAR;
			samples_per_unit = 1;
			bytes_per_unit = irint(prec / 8.);

		} else if ((strcmp(estr, "float") == 0) ||
		    (strcmp(estr, "floatingpoint") == 0) ||
		    (strcmp(estr, "floating-point") == 0)) {
			if ((prec != 0.) && (prec != 32.) && (prec != 64.))
				return (AUDIO_ERR_BADARG);
			if (prec == 0.)
				prec = 64.;
			encoding = FLOAT;
			samples_per_unit = 1;
			bytes_per_unit = irint(prec / 8.);

		} else if ((strcmp(estr, "float32") == 0) ||
		    (strcmp(estr, "floatingpoint32") == 0) ||
		    (strcmp(estr, "floating-point32") == 0)) {
			if ((prec != 0.) && (prec != 32.))
				return (AUDIO_ERR_BADARG);
			prec = 32.;
			encoding = FLOAT;
			samples_per_unit = 1;
			bytes_per_unit = irint(prec / 8.);

		} else if ((strcmp(estr, "float64") == 0) ||
		    (strcmp(estr, "double") == 0) ||
		    (strcmp(estr, "floatingpoint64") == 0) ||
		    (strcmp(estr, "floating-point64") == 0)) {
			if ((prec != 0.) && (prec != 64.))
				return (AUDIO_ERR_BADARG);
			prec = 64.;
			encoding = FLOAT;
			samples_per_unit = 1;
			bytes_per_unit = irint(prec / 8.);

		} else if ((strcmp(estr, "g.721") == 0) ||
		    (strcmp(estr, "g721") == 0) ||
		    (strcmp(estr, "g.721adpcm") == 0) ||
		    (strcmp(estr, "g721adpcm") == 0)) {
			if ((prec != 0.) && (prec != 4.))
				return (AUDIO_ERR_BADARG);
			encoding = G721;
			samples_per_unit = 2;
			bytes_per_unit = 1;

		} else if ((strcmp(estr, "g.722") == 0) ||
		    (strcmp(estr, "g722") == 0) ||
		    (strcmp(estr, "g.722adpcm") == 0) ||
		    (strcmp(estr, "g722adpcm") == 0)) {
			if ((prec != 0.) && (prec != 8.))
				return (AUDIO_ERR_BADARG);
			encoding = G722;
			samples_per_unit = 1;
			bytes_per_unit = 1;

		} else if ((strcmp(estr, "g.723") == 0) ||
		    (strcmp(estr, "g723") == 0) ||
		    (strcmp(estr, "g.723adpcm") == 0) ||
		    (strcmp(estr, "g723adpcm") == 0)) {
			if ((prec != 0.) && (prec != 3.) && (prec != 5.))
				return (AUDIO_ERR_BADARG);
			if (prec == 0.)
				prec = 3.;
			encoding = G723;
			samples_per_unit = 8;
			bytes_per_unit = irint(prec);

		} else if ((strcmp(estr, "g.723-3") == 0) ||
		    (strcmp(estr, "g.723_3") == 0) ||
		    (strcmp(estr, "g.723.3") == 0) ||
		    (strcmp(estr, "g723-3") == 0) ||
		    (strcmp(estr, "g723_3") == 0) ||
		    (strcmp(estr, "g723.3") == 0)) {
			if ((prec != 0.) && (prec != 3.))
				return (AUDIO_ERR_BADARG);
			prec = 3.;
			encoding = G723;
			samples_per_unit = 8;
			bytes_per_unit = irint(prec);

		} else if ((strcmp(estr, "g.723-5") == 0) ||
		    (strcmp(estr, "g.723_5") == 0) ||
		    (strcmp(estr, "g.723.5") == 0) ||
		    (strcmp(estr, "g723-5") == 0) ||
		    (strcmp(estr, "g723_5") == 0) ||
		    (strcmp(estr, "g723.5") == 0)) {
			if ((prec != 0.) && (prec != 5.))
				return (AUDIO_ERR_BADARG);
			prec = 5.;
			encoding = G723;
			samples_per_unit = 8;
			bytes_per_unit = irint(prec);

		} else if ((strcmp(estr, "dvi") == 0) ||
		    (strcmp(estr, "dviadpcm") == 0)) {
			if ((prec != 0.) && (prec != 4.))
				return (AUDIO_ERR_BADARG);
			encoding = DVI;
			samples_per_unit = 2;
			bytes_per_unit = 1;

		} else {
			delete estr;
			return (AUDIO_ERR_BADARG);
		}
		delete estr;
	} else {
		return (AUDIO_ERR_BADARG);
	}
	return (AUDIO_SUCCESS);
}

// Parse a string containing the comma-separated audio encoding
// Format is: "enc, chan, rate"
//	XXX - some countries use comma instead of decimal point
//	so there may be a problem with "44,1 khz"
AudioError AudioHdr::
FormatParse(
	char		*str)
{
	char		*pstr;
	char		*ptr;
	char		*p;
	AudioHdr	newhdr;
	AudioError	err;

	pstr = new char[strlen(str) + 1];
	(void) strcpy(pstr, str);
	ptr = pstr;

	// Delimit and parse the precision string
	p = strchr(ptr, ',');
	if (p == NULL)
		p = strchr(ptr, ' ');
	if (p == NULL) {
		err = AUDIO_ERR_BADARG;
		goto errret;
	}
	*p++ = '\0';
	err = newhdr.EncodingParse(ptr);

	// Delimit and parse the sample rate string
	if (!err) {
		ptr = p;
		p = strchr(ptr, ',');
		if (p == NULL)
			p = strchr(ptr, ' ');
		if (p == NULL) {
			err = AUDIO_ERR_BADARG;
			goto errret;
		}
		*p++ = '\0';
		err = newhdr.RateParse(ptr);
	}

	// Finally, parse the channels string
	if (!err) {
		err = newhdr.ChannelParse(p);
	}

	// Validate the resulting header
	if (!err)
		err = newhdr.Validate();
	if (!err)
		*this = newhdr;
errret:
	delete pstr;
	return (err);
}