From: nu774 Date: Fri, 4 Jan 2013 16:10:05 +0000 (+0900) Subject: initial commit X-Git-Tag: v0.0.1 X-Git-Url: http://git.ieval.ro/?a=commitdiff_plain;h=48e2f01c5653c8f643b2a763e1cfa2efc1eccd60;p=fdkaac.git initial commit --- 48e2f01c5653c8f643b2a763e1cfa2efc1eccd60 diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..e69de29 diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..17484d2 --- /dev/null +++ b/COPYING @@ -0,0 +1,17 @@ +Copyright (C) 2013 nu774 + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..e69de29 diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..a1e89e1 --- /dev/null +++ b/INSTALL @@ -0,0 +1,370 @@ +Installation Instructions +************************* + +Copyright (C) 1994-1996, 1999-2002, 2004-2011 Free Software Foundation, +Inc. + + Copying and distribution of this file, with or without modification, +are permitted in any medium without royalty provided the copyright +notice and this notice are preserved. This file is offered as-is, +without warranty of any kind. + +Basic Installation +================== + + Briefly, the shell commands `./configure; make; make install' should +configure, build, and install this package. The following +more-detailed instructions are generic; see the `README' file for +instructions specific to this package. Some packages provide this +`INSTALL' file but do not implement all of the features documented +below. The lack of an optional feature in a given package is not +necessarily a bug. More recommendations for GNU packages can be found +in *note Makefile Conventions: (standards)Makefile Conventions. + + The `configure' shell script attempts to guess correct values for +various system-dependent variables used during compilation. It uses +those values to create a `Makefile' in each directory of the package. +It may also create one or more `.h' files containing system-dependent +definitions. Finally, it creates a shell script `config.status' that +you can run in the future to recreate the current configuration, and a +file `config.log' containing compiler output (useful mainly for +debugging `configure'). + + It can also use an optional file (typically called `config.cache' +and enabled with `--cache-file=config.cache' or simply `-C') that saves +the results of its tests to speed up reconfiguring. Caching is +disabled by default to prevent problems with accidental use of stale +cache files. + + If you need to do unusual things to compile the package, please try +to figure out how `configure' could check whether to do them, and mail +diffs or instructions to the address given in the `README' so they can +be considered for the next release. If you are using the cache, and at +some point `config.cache' contains results you don't want to keep, you +may remove or edit it. + + The file `configure.ac' (or `configure.in') is used to create +`configure' by a program called `autoconf'. You need `configure.ac' if +you want to change it or regenerate `configure' using a newer version +of `autoconf'. + + The simplest way to compile this package is: + + 1. `cd' to the directory containing the package's source code and type + `./configure' to configure the package for your system. + + Running `configure' might take a while. While running, it prints + some messages telling which features it is checking for. + + 2. Type `make' to compile the package. + + 3. Optionally, type `make check' to run any self-tests that come with + the package, generally using the just-built uninstalled binaries. + + 4. Type `make install' to install the programs and any data files and + documentation. When installing into a prefix owned by root, it is + recommended that the package be configured and built as a regular + user, and only the `make install' phase executed with root + privileges. + + 5. Optionally, type `make installcheck' to repeat any self-tests, but + this time using the binaries in their final installed location. + This target does not install anything. Running this target as a + regular user, particularly if the prior `make install' required + root privileges, verifies that the installation completed + correctly. + + 6. You can remove the program binaries and object files from the + source code directory by typing `make clean'. To also remove the + files that `configure' created (so you can compile the package for + a different kind of computer), type `make distclean'. There is + also a `make maintainer-clean' target, but that is intended mainly + for the package's developers. If you use it, you may have to get + all sorts of other programs in order to regenerate files that came + with the distribution. + + 7. Often, you can also type `make uninstall' to remove the installed + files again. In practice, not all packages have tested that + uninstallation works correctly, even though it is required by the + GNU Coding Standards. + + 8. Some packages, particularly those that use Automake, provide `make + distcheck', which can by used by developers to test that all other + targets like `make install' and `make uninstall' work correctly. + This target is generally not run by end users. + +Compilers and Options +===================== + + Some systems require unusual options for compilation or linking that +the `configure' script does not know about. Run `./configure --help' +for details on some of the pertinent environment variables. + + You can give `configure' initial values for configuration parameters +by setting variables in the command line or in the environment. Here +is an example: + + ./configure CC=c99 CFLAGS=-g LIBS=-lposix + + *Note Defining Variables::, for more details. + +Compiling For Multiple Architectures +==================================== + + You can compile the package for more than one kind of computer at the +same time, by placing the object files for each architecture in their +own directory. To do this, you can use GNU `make'. `cd' to the +directory where you want the object files and executables to go and run +the `configure' script. `configure' automatically checks for the +source code in the directory that `configure' is in and in `..'. This +is known as a "VPATH" build. + + With a non-GNU `make', it is safer to compile the package for one +architecture at a time in the source code directory. After you have +installed the package for one architecture, use `make distclean' before +reconfiguring for another architecture. + + On MacOS X 10.5 and later systems, you can create libraries and +executables that work on multiple system types--known as "fat" or +"universal" binaries--by specifying multiple `-arch' options to the +compiler but only a single `-arch' option to the preprocessor. Like +this: + + ./configure CC="gcc -arch i386 -arch x86_64 -arch ppc -arch ppc64" \ + CXX="g++ -arch i386 -arch x86_64 -arch ppc -arch ppc64" \ + CPP="gcc -E" CXXCPP="g++ -E" + + This is not guaranteed to produce working output in all cases, you +may have to build one architecture at a time and combine the results +using the `lipo' tool if you have problems. + +Installation Names +================== + + By default, `make install' installs the package's commands under +`/usr/local/bin', include files under `/usr/local/include', etc. You +can specify an installation prefix other than `/usr/local' by giving +`configure' the option `--prefix=PREFIX', where PREFIX must be an +absolute file name. + + You can specify separate installation prefixes for +architecture-specific files and architecture-independent files. If you +pass the option `--exec-prefix=PREFIX' to `configure', the package uses +PREFIX as the prefix for installing programs and libraries. +Documentation and other data files still use the regular prefix. + + In addition, if you use an unusual directory layout you can give +options like `--bindir=DIR' to specify different values for particular +kinds of files. Run `configure --help' for a list of the directories +you can set and what kinds of files go in them. In general, the +default for these options is expressed in terms of `${prefix}', so that +specifying just `--prefix' will affect all of the other directory +specifications that were not explicitly provided. + + The most portable way to affect installation locations is to pass the +correct locations to `configure'; however, many packages provide one or +both of the following shortcuts of passing variable assignments to the +`make install' command line to change installation locations without +having to reconfigure or recompile. + + The first method involves providing an override variable for each +affected directory. For example, `make install +prefix=/alternate/directory' will choose an alternate location for all +directory configuration variables that were expressed in terms of +`${prefix}'. Any directories that were specified during `configure', +but not in terms of `${prefix}', must each be overridden at install +time for the entire installation to be relocated. The approach of +makefile variable overrides for each directory variable is required by +the GNU Coding Standards, and ideally causes no recompilation. +However, some platforms have known limitations with the semantics of +shared libraries that end up requiring recompilation when using this +method, particularly noticeable in packages that use GNU Libtool. + + The second method involves providing the `DESTDIR' variable. For +example, `make install DESTDIR=/alternate/directory' will prepend +`/alternate/directory' before all installation names. The approach of +`DESTDIR' overrides is not required by the GNU Coding Standards, and +does not work on platforms that have drive letters. On the other hand, +it does better at avoiding recompilation issues, and works well even +when some directory options were not specified in terms of `${prefix}' +at `configure' time. + +Optional Features +================= + + If the package supports it, you can cause programs to be installed +with an extra prefix or suffix on their names by giving `configure' the +option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'. + + Some packages pay attention to `--enable-FEATURE' options to +`configure', where FEATURE indicates an optional part of the package. +They may also pay attention to `--with-PACKAGE' options, where PACKAGE +is something like `gnu-as' or `x' (for the X Window System). The +`README' should mention any `--enable-' and `--with-' options that the +package recognizes. + + For packages that use the X Window System, `configure' can usually +find the X include and library files automatically, but if it doesn't, +you can use the `configure' options `--x-includes=DIR' and +`--x-libraries=DIR' to specify their locations. + + Some packages offer the ability to configure how verbose the +execution of `make' will be. For these packages, running `./configure +--enable-silent-rules' sets the default to minimal output, which can be +overridden with `make V=1'; while running `./configure +--disable-silent-rules' sets the default to verbose, which can be +overridden with `make V=0'. + +Particular systems +================== + + On HP-UX, the default C compiler is not ANSI C compatible. If GNU +CC is not installed, it is recommended to use the following options in +order to use an ANSI C compiler: + + ./configure CC="cc -Ae -D_XOPEN_SOURCE=500" + +and if that doesn't work, install pre-built binaries of GCC for HP-UX. + + HP-UX `make' updates targets which have the same time stamps as +their prerequisites, which makes it generally unusable when shipped +generated files such as `configure' are involved. Use GNU `make' +instead. + + On OSF/1 a.k.a. Tru64, some versions of the default C compiler cannot +parse its `' header file. The option `-nodtk' can be used as +a workaround. If GNU CC is not installed, it is therefore recommended +to try + + ./configure CC="cc" + +and if that doesn't work, try + + ./configure CC="cc -nodtk" + + On Solaris, don't put `/usr/ucb' early in your `PATH'. This +directory contains several dysfunctional programs; working variants of +these programs are available in `/usr/bin'. So, if you need `/usr/ucb' +in your `PATH', put it _after_ `/usr/bin'. + + On Haiku, software installed for all users goes in `/boot/common', +not `/usr/local'. It is recommended to use the following options: + + ./configure --prefix=/boot/common + +Specifying the System Type +========================== + + There may be some features `configure' cannot figure out +automatically, but needs to determine by the type of machine the package +will run on. Usually, assuming the package is built to be run on the +_same_ architectures, `configure' can figure that out, but if it prints +a message saying it cannot guess the machine type, give it the +`--build=TYPE' option. TYPE can either be a short name for the system +type, such as `sun4', or a canonical name which has the form: + + CPU-COMPANY-SYSTEM + +where SYSTEM can have one of these forms: + + OS + KERNEL-OS + + See the file `config.sub' for the possible values of each field. If +`config.sub' isn't included in this package, then this package doesn't +need to know the machine type. + + If you are _building_ compiler tools for cross-compiling, you should +use the option `--target=TYPE' to select the type of system they will +produce code for. + + If you want to _use_ a cross compiler, that generates code for a +platform different from the build platform, you should specify the +"host" platform (i.e., that on which the generated programs will +eventually be run) with `--host=TYPE'. + +Sharing Defaults +================ + + If you want to set default values for `configure' scripts to share, +you can create a site shell script called `config.site' that gives +default values for variables like `CC', `cache_file', and `prefix'. +`configure' looks for `PREFIX/share/config.site' if it exists, then +`PREFIX/etc/config.site' if it exists. Or, you can set the +`CONFIG_SITE' environment variable to the location of the site script. +A warning: not all `configure' scripts look for a site script. + +Defining Variables +================== + + Variables not defined in a site shell script can be set in the +environment passed to `configure'. However, some packages may run +configure again during the build, and the customized values of these +variables may be lost. In order to avoid this problem, you should set +them in the `configure' command line, using `VAR=value'. For example: + + ./configure CC=/usr/local2/bin/gcc + +causes the specified `gcc' to be used as the C compiler (unless it is +overridden in the site shell script). + +Unfortunately, this technique does not work for `CONFIG_SHELL' due to +an Autoconf bug. Until the bug is fixed you can use this workaround: + + CONFIG_SHELL=/bin/bash /bin/bash ./configure CONFIG_SHELL=/bin/bash + +`configure' Invocation +====================== + + `configure' recognizes the following options to control how it +operates. + +`--help' +`-h' + Print a summary of all of the options to `configure', and exit. + +`--help=short' +`--help=recursive' + Print a summary of the options unique to this package's + `configure', and exit. The `short' variant lists options used + only in the top level, while the `recursive' variant lists options + also present in any nested packages. + +`--version' +`-V' + Print the version of Autoconf used to generate the `configure' + script, and exit. + +`--cache-file=FILE' + Enable the cache: use and save the results of the tests in FILE, + traditionally `config.cache'. FILE defaults to `/dev/null' to + disable caching. + +`--config-cache' +`-C' + Alias for `--cache-file=config.cache'. + +`--quiet' +`--silent' +`-q' + Do not print messages saying which checks are being made. To + suppress all normal output, redirect it to `/dev/null' (any error + messages will still be shown). + +`--srcdir=DIR' + Look for the package's source code in directory DIR. Usually + `configure' can determine that directory automatically. + +`--prefix=DIR' + Use DIR as the installation prefix. *note Installation Names:: + for more details, including other options available for fine-tuning + the installation locations. + +`--no-create' +`-n' + Run the configure checks, but stop before creating any output + files. + +`configure' also accepts some other, not widely useful, options. Run +`configure --help' for more details. + diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..438b7af --- /dev/null +++ b/Makefile.am @@ -0,0 +1,25 @@ +ACLOCAL_AMFLAGS = -I m4 +EXTRA_DIST = COPYING + +bin_PROGRAMS = fdkaac + +fdkaac_SOURCES = \ + src/aacenc.c \ + src/lpcm.c \ + src/m4af.c \ + src/main.c \ + src/progress.c \ + src/wav_reader.c + +fdkaac_LDADD = \ + @LIBICONV@ -lm + +if FDK_PLATFORM_POSIX + fdkaac_SOURCES += \ + src/compat_posix.c +endif + +if FDK_PLATFORM_WIN32 + fdkaac_SOURCES += \ + src/compat_win32.c +endif diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..e69de29 diff --git a/README b/README new file mode 100644 index 0000000..70a2301 --- /dev/null +++ b/README @@ -0,0 +1,14 @@ +========================================================================== +fdkaac - command line frontend encoder for libfdk-aac +========================================================================== + +Prerequisites +------------- +You need libfdk-aac, GNU autoconf and automake, and C compiler. +On Posix environment, you will also need GNU gettext (for iconv.m4). + +How to build +------------ +$ autoreconf -i +$ ./configure +$ make diff --git a/config.rpath b/config.rpath new file mode 100644 index 0000000..e69de29 diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..58bc422 --- /dev/null +++ b/configure.ac @@ -0,0 +1,52 @@ +m4_define([VERSION_H],m4_esyscmd([cat version.h])) +changequote({{,}})dnl +m4_define({{XX_VERSION}},m4_bregexp(VERSION_H,{{^const.*"\(.+\)";}},{{\1}})) +changequote([,])dnl + +AC_INIT([fdkaac], [XX_VERSION], [honeycomb77@gmail.com]) +AC_CONFIG_HEADERS([config.h]) +AC_CONFIG_MACRO_DIR([m4]) + +AM_INIT_AUTOMAKE + +AC_PROG_CC + +AC_CHECK_HEADERS([getopt.h sys/time.h]) +AC_CHECK_HEADERS([localcharset.h langinfo.h endian.h byteswap.h]) +AC_CHECK_HEADERS([fdk-aac/aacenc_lib.h], , + AC_MSG_ERROR([libfdk-aac is required])) + +AC_C_INLINE +AC_C_BIGENDIAN +AC_TYPE_INT16_T +AC_TYPE_INT32_T +AC_TYPE_INT64_T +AC_TYPE_INT8_T +AC_TYPE_SIZE_T +AC_TYPE_UINT16_T +AC_TYPE_UINT32_T +AC_TYPE_UINT64_T +AC_TYPE_UINT8_T +AC_CHECK_TYPES([ptrdiff_t]) + +AC_SYS_LARGEFILE +AC_FUNC_FSEEKO +AC_CHECK_FUNCS([gettimeofday nl_langinfo strdup]) +AC_SEARCH_LIBS([aacEncOpen],[fdk-aac]) + +AC_CANONICAL_HOST + +X_PLATFORM=posix +case ${host} in +*-*-mingw*) + X_PLATFORM=win32 + ;; +*) + AM_ICONV +esac +AM_CONDITIONAL([FDK_PLATFORM_POSIX],[test "$X_PLATFORM" = "posix"]) +AM_CONDITIONAL([FDK_PLATFORM_WIN32],[test "$X_PLATFORM" = "win32"]) + +AC_CONFIG_FILES([Makefile]) + +AC_OUTPUT diff --git a/m4/.gitkeep b/m4/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/missings/getopt.c b/missings/getopt.c new file mode 100644 index 0000000..ea6800e --- /dev/null +++ b/missings/getopt.c @@ -0,0 +1,630 @@ +/* $OpenBSD: getopt_long.c,v 1.21 2006/09/22 17:22:05 millert Exp $ */ +/* $NetBSD: getopt_long.c,v 1.15 2002/01/31 22:43:40 tv Exp $ */ + +/* + * Copyright (c) 2002 Todd C. Miller + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Sponsored in part by the Defense Advanced Research Projects + * Agency (DARPA) and Air Force Research Laboratory, Air Force + * Materiel Command, USAF, under agreement number F39502-99-1-0512. + */ +/*- + * Copyright (c) 2000 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Dieter Baron and Thomas Klausner. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#if 0 +#if defined(LIBC_SCCS) && !defined(lint) +static char *rcsid = "$OpenBSD: getopt_long.c,v 1.16 2004/02/04 18:17:25 millert Exp $"; +#endif /* LIBC_SCCS and not lint */ +#endif + +#include +#include +#include +#include +#include +#include "getopt.h" + +#define GNU_COMPATIBLE /* Be more compatible, configure's use us! */ + +#if 0 /* we prefer to keep our getopt(3) */ +#define REPLACE_GETOPT /* use this getopt as the system getopt(3) */ +#endif + +int opterr = 1; /* if error message should be printed */ +int optind = 1; /* index into parent argv vector */ +int optopt = '?'; /* character checked for validity */ +int optreset; /* reset getopt */ +char *optarg; /* argument associated with option */ + +#define PRINT_ERROR ((opterr) && (*options != ':')) + +#define FLAG_PERMUTE 0x01 /* permute non-options to the end of argv */ +#define FLAG_ALLARGS 0x02 /* treat non-options as args to option "-1" */ +#define FLAG_LONGONLY 0x04 /* operate as getopt_long_only */ + +/* return values */ +#define BADCH (int)'?' +#define BADARG ((*options == ':') ? (int)':' : (int)'?') +#define INORDER (int)1 + +#define EMSG "" + +#ifdef GNU_COMPATIBLE +#define NO_PREFIX (-1) +#define D_PREFIX 0 +#define DD_PREFIX 1 +#define W_PREFIX 2 +#endif + +static int getopt_internal(int, char * const *, const char *, + const struct option *, int *, int); +static int parse_long_options(char * const *, const char *, + const struct option *, int *, int, int); +static int gcd(int, int); +static void permute_args(int, int, int, char * const *); + +static char *place = EMSG; /* option letter processing */ + +/* XXX: set optreset to 1 rather than these two */ +static int nonopt_start = -1; /* first non option argument (for permute) */ +static int nonopt_end = -1; /* first option after non options (for permute) */ + +/* Error messages */ +static const char recargchar[] = "option requires an argument -- %c"; +static const char illoptchar[] = "illegal option -- %c"; /* From P1003.2 */ +#ifdef GNU_COMPATIBLE +static int dash_prefix = NO_PREFIX; +static const char gnuoptchar[] = "invalid option -- %c"; + +static const char recargstring[] = "option `%s%s' requires an argument"; +static const char ambig[] = "option `%s%.*s' is ambiguous"; +static const char noarg[] = "option `%s%.*s' doesn't allow an argument"; +static const char illoptstring[] = "unrecognized option `%s%s'"; +#else +static const char recargstring[] = "option requires an argument -- %s"; +static const char ambig[] = "ambiguous option -- %.*s"; +static const char noarg[] = "option doesn't take an argument -- %.*s"; +static const char illoptstring[] = "unknown option -- %s"; +#endif + +static void +warnx(const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + vfprintf(stderr, fmt, args); + putc('\n', stderr); + va_end(args); +} + +/* + * Compute the greatest common divisor of a and b. + */ +static int +gcd(int a, int b) +{ + int c; + + c = a % b; + while (c != 0) { + a = b; + b = c; + c = a % b; + } + + return (b); +} + +/* + * Exchange the block from nonopt_start to nonopt_end with the block + * from nonopt_end to opt_end (keeping the same order of arguments + * in each block). + */ +static void +permute_args(int panonopt_start, int panonopt_end, int opt_end, + char * const *nargv) +{ + int cstart, cyclelen, i, j, ncycle, nnonopts, nopts, pos; + char *swap; + + /* + * compute lengths of blocks and number and size of cycles + */ + nnonopts = panonopt_end - panonopt_start; + nopts = opt_end - panonopt_end; + ncycle = gcd(nnonopts, nopts); + cyclelen = (opt_end - panonopt_start) / ncycle; + + for (i = 0; i < ncycle; i++) { + cstart = panonopt_end+i; + pos = cstart; + for (j = 0; j < cyclelen; j++) { + if (pos >= panonopt_end) + pos -= nnonopts; + else + pos += nopts; + swap = nargv[pos]; + /* LINTED const cast */ + ((char **) nargv)[pos] = nargv[cstart]; + /* LINTED const cast */ + ((char **)nargv)[cstart] = swap; + } + } +} + +/* + * parse_long_options -- + * Parse long options in argc/argv argument vector. + * Returns -1 if short_too is set and the option does not match long_options. + */ +static int +parse_long_options(char * const *nargv, const char *options, + const struct option *long_options, int *idx, int short_too, int flags) +{ + char *current_argv, *has_equal; +#ifdef GNU_COMPATIBLE + char *current_dash; +#endif + size_t current_argv_len; + int i, match, exact_match, second_partial_match; + + current_argv = place; +#ifdef GNU_COMPATIBLE + switch (dash_prefix) { + case D_PREFIX: + current_dash = "-"; + break; + case DD_PREFIX: + current_dash = "--"; + break; + case W_PREFIX: + current_dash = "-W "; + break; + default: + current_dash = ""; + break; + } +#endif + match = -1; + exact_match = 0; + second_partial_match = 0; + + optind++; + + if ((has_equal = strchr(current_argv, '=')) != NULL) { + /* argument found (--option=arg) */ + current_argv_len = has_equal - current_argv; + has_equal++; + } else + current_argv_len = strlen(current_argv); + + for (i = 0; long_options[i].name; i++) { + /* find matching long option */ + if (strncmp(current_argv, long_options[i].name, + current_argv_len)) + continue; + + if (strlen(long_options[i].name) == current_argv_len) { + /* exact match */ + match = i; + exact_match = 1; + break; + } + /* + * If this is a known short option, don't allow + * a partial match of a single character. + */ + if (short_too && current_argv_len == 1) + continue; + + if (match == -1) /* first partial match */ + match = i; + else if ((flags & FLAG_LONGONLY) || + long_options[i].has_arg != + long_options[match].has_arg || + long_options[i].flag != long_options[match].flag || + long_options[i].val != long_options[match].val) + second_partial_match = 1; + } + if (!exact_match && second_partial_match) { + /* ambiguous abbreviation */ + if (PRINT_ERROR) + warnx(ambig, +#ifdef GNU_COMPATIBLE + current_dash, +#endif + (int)current_argv_len, + current_argv); + optopt = 0; + return (BADCH); + } + if (match != -1) { /* option found */ + if (long_options[match].has_arg == no_argument + && has_equal) { + if (PRINT_ERROR) + warnx(noarg, +#ifdef GNU_COMPATIBLE + current_dash, +#endif + (int)current_argv_len, + current_argv); + /* + * XXX: GNU sets optopt to val regardless of flag + */ + if (long_options[match].flag == NULL) + optopt = long_options[match].val; + else + optopt = 0; +#ifdef GNU_COMPATIBLE + return (BADCH); +#else + return (BADARG); +#endif + } + if (long_options[match].has_arg == required_argument || + long_options[match].has_arg == optional_argument) { + if (has_equal) + optarg = has_equal; + else if (long_options[match].has_arg == + required_argument) { + /* + * optional argument doesn't use next nargv + */ + optarg = nargv[optind++]; + } + } + if ((long_options[match].has_arg == required_argument) + && (optarg == NULL)) { + /* + * Missing argument; leading ':' indicates no error + * should be generated. + */ + if (PRINT_ERROR) + warnx(recargstring, +#ifdef GNU_COMPATIBLE + current_dash, +#endif + current_argv); + /* + * XXX: GNU sets optopt to val regardless of flag + */ + if (long_options[match].flag == NULL) + optopt = long_options[match].val; + else + optopt = 0; + --optind; + return (BADARG); + } + } else { /* unknown option */ + if (short_too) { + --optind; + return (-1); + } + if (PRINT_ERROR) + warnx(illoptstring, +#ifdef GNU_COMPATIBLE + current_dash, +#endif + current_argv); + optopt = 0; + return (BADCH); + } + if (idx) + *idx = match; + if (long_options[match].flag) { + *long_options[match].flag = long_options[match].val; + return (0); + } else + return (long_options[match].val); +} + +/* + * getopt_internal -- + * Parse argc/argv argument vector. Called by user level routines. + */ +static int +getopt_internal(int nargc, char * const *nargv, const char *options, + const struct option *long_options, int *idx, int flags) +{ + char *oli; /* option letter list index */ + int optchar, short_too; + int posixly_correct; /* no static, can be changed on the fly */ + + if (options == NULL) + return (-1); + + /* + * Disable GNU extensions if POSIXLY_CORRECT is set or options + * string begins with a '+'. + */ + posixly_correct = (getenv("POSIXLY_CORRECT") != NULL); +#ifdef GNU_COMPATIBLE + if (*options == '-') + flags |= FLAG_ALLARGS; + else if (posixly_correct || *options == '+') + flags &= ~FLAG_PERMUTE; +#else + if (posixly_correct || *options == '+') + flags &= ~FLAG_PERMUTE; + else if (*options == '-') + flags |= FLAG_ALLARGS; +#endif + if (*options == '+' || *options == '-') + options++; + + /* + * XXX Some GNU programs (like cvs) set optind to 0 instead of + * XXX using optreset. Work around this braindamage. + */ + if (optind == 0) + optind = optreset = 1; + + optarg = NULL; + if (optreset) + nonopt_start = nonopt_end = -1; +start: + if (optreset || !*place) { /* update scanning pointer */ + optreset = 0; + if (optind >= nargc) { /* end of argument vector */ + place = EMSG; + if (nonopt_end != -1) { + /* do permutation, if we have to */ + permute_args(nonopt_start, nonopt_end, + optind, nargv); + optind -= nonopt_end - nonopt_start; + } + else if (nonopt_start != -1) { + /* + * If we skipped non-options, set optind + * to the first of them. + */ + optind = nonopt_start; + } + nonopt_start = nonopt_end = -1; + return (-1); + } + if (*(place = nargv[optind]) != '-' || +#ifdef GNU_COMPATIBLE + place[1] == '\0') { +#else + (place[1] == '\0' && strchr(options, '-') == NULL)) { +#endif + place = EMSG; /* found non-option */ + if (flags & FLAG_ALLARGS) { + /* + * GNU extension: + * return non-option as argument to option 1 + */ + optarg = nargv[optind++]; + return (INORDER); + } + if (!(flags & FLAG_PERMUTE)) { + /* + * If no permutation wanted, stop parsing + * at first non-option. + */ + return (-1); + } + /* do permutation */ + if (nonopt_start == -1) + nonopt_start = optind; + else if (nonopt_end != -1) { + permute_args(nonopt_start, nonopt_end, + optind, nargv); + nonopt_start = optind - + (nonopt_end - nonopt_start); + nonopt_end = -1; + } + optind++; + /* process next argument */ + goto start; + } + if (nonopt_start != -1 && nonopt_end == -1) + nonopt_end = optind; + + /* + * If we have "-" do nothing, if "--" we are done. + */ + if (place[1] != '\0' && *++place == '-' && place[1] == '\0') { + optind++; + place = EMSG; + /* + * We found an option (--), so if we skipped + * non-options, we have to permute. + */ + if (nonopt_end != -1) { + permute_args(nonopt_start, nonopt_end, + optind, nargv); + optind -= nonopt_end - nonopt_start; + } + nonopt_start = nonopt_end = -1; + return (-1); + } + } + + /* + * Check long options if: + * 1) we were passed some + * 2) the arg is not just "-" + * 3) either the arg starts with -- we are getopt_long_only() + */ + if (long_options != NULL && place != nargv[optind] && + (*place == '-' || (flags & FLAG_LONGONLY))) { + short_too = 0; +#ifdef GNU_COMPATIBLE + dash_prefix = D_PREFIX; +#endif + if (*place == '-') { + place++; /* --foo long option */ +#ifdef GNU_COMPATIBLE + dash_prefix = DD_PREFIX; +#endif + } else if (*place != ':' && strchr(options, *place) != NULL) + short_too = 1; /* could be short option too */ + + optchar = parse_long_options(nargv, options, long_options, + idx, short_too, flags); + if (optchar != -1) { + place = EMSG; + return (optchar); + } + } + + if ((optchar = (int)*place++) == (int)':' || + (optchar == (int)'-' && *place != '\0') || + (oli = strchr(options, optchar)) == NULL) { + /* + * If the user specified "-" and '-' isn't listed in + * options, return -1 (non-option) as per POSIX. + * Otherwise, it is an unknown option character (or ':'). + */ + if (optchar == (int)'-' && *place == '\0') + return (-1); + if (!*place) + ++optind; +#ifdef GNU_COMPATIBLE + if (PRINT_ERROR) + warnx(posixly_correct ? illoptchar : gnuoptchar, + optchar); +#else + if (PRINT_ERROR) + warnx(illoptchar, optchar); +#endif + optopt = optchar; + return (BADCH); + } + if (long_options != NULL && optchar == 'W' && oli[1] == ';') { + /* -W long-option */ + if (*place) /* no space */ + /* NOTHING */; + else if (++optind >= nargc) { /* no arg */ + place = EMSG; + if (PRINT_ERROR) + warnx(recargchar, optchar); + optopt = optchar; + return (BADARG); + } else /* white space */ + place = nargv[optind]; +#ifdef GNU_COMPATIBLE + dash_prefix = W_PREFIX; +#endif + optchar = parse_long_options(nargv, options, long_options, + idx, 0, flags); + place = EMSG; + return (optchar); + } + if (*++oli != ':') { /* doesn't take argument */ + if (!*place) + ++optind; + } else { /* takes (optional) argument */ + optarg = NULL; + if (*place) /* no white space */ + optarg = place; + else if (oli[1] != ':') { /* arg not optional */ + if (++optind >= nargc) { /* no arg */ + place = EMSG; + if (PRINT_ERROR) + warnx(recargchar, optchar); + optopt = optchar; + return (BADARG); + } else + optarg = nargv[optind]; + } + place = EMSG; + ++optind; + } + /* dump back option letter */ + return (optchar); +} + +/* + * getopt -- + * Parse argc/argv argument vector. + * + * [eventually this will replace the BSD getopt] + */ +int +getopt(int nargc, char * const *nargv, const char *options) +{ + + /* + * We don't pass FLAG_PERMUTE to getopt_internal() since + * the BSD getopt(3) (unlike GNU) has never done this. + * + * Furthermore, since many privileged programs call getopt() + * before dropping privileges it makes sense to keep things + * as simple (and bug-free) as possible. + */ + return (getopt_internal(nargc, nargv, options, NULL, NULL, FLAG_PERMUTE)); +} + +/* + * getopt_long -- + * Parse argc/argv argument vector. + */ +int +getopt_long(int nargc, char * const *nargv, const char *options, + const struct option *long_options, int *idx) +{ + + return (getopt_internal(nargc, nargv, options, long_options, idx, + FLAG_PERMUTE)); +} + +/* + * getopt_long_only -- + * Parse argc/argv argument vector. + */ +int +getopt_long_only(int nargc, char * const *nargv, const char *options, + const struct option *long_options, int *idx) +{ + + return (getopt_internal(nargc, nargv, options, long_options, idx, + FLAG_PERMUTE|FLAG_LONGONLY)); +} diff --git a/missings/getopt.h b/missings/getopt.h new file mode 100644 index 0000000..252257c --- /dev/null +++ b/missings/getopt.h @@ -0,0 +1,90 @@ +/* $NetBSD: getopt.h,v 1.4 2000/07/07 10:43:54 ad Exp $ */ +/* $FreeBSD: src/include/getopt.h,v 1.6.34.1 2010/12/21 17:10:29 kensmith Exp $ */ + +/*- + * Copyright (c) 2000 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Dieter Baron and Thomas Klausner. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _GETOPT_H_ +#define _GETOPT_H_ + + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * GNU-like getopt_long()/getopt_long_only() with 4.4BSD optreset extension. + * getopt() is declared here too for GNU programs. + */ +#define no_argument 0 +#define required_argument 1 +#define optional_argument 2 + +struct option { + /* name of long option */ + const char *name; + /* + * one of no_argument, required_argument, and optional_argument: + * whether option takes an argument + */ + int has_arg; + /* if not NULL, set *flag to val when option found */ + int *flag; + /* if flag not NULL, value to set *flag to; else return value */ + int val; +}; + +int getopt_long(int, char * const *, const char *, + const struct option *, int *); +int getopt_long_only(int, char * const *, const char *, + const struct option *, int *); +#ifndef _GETOPT_DECLARED +#define _GETOPT_DECLARED +int getopt(int, char * const [], const char *); + +extern char *optarg; /* getopt(3) external variables */ +extern int optind, opterr, optopt; +#endif +#ifndef _OPTRESET_DECLARED +#define _OPTRESET_DECLARED +extern int optreset; /* getopt(3) external variable */ +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* !_GETOPT_H_ */ diff --git a/src/aacenc.c b/src/aacenc.c new file mode 100644 index 0000000..e622c62 --- /dev/null +++ b/src/aacenc.c @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2013 nu774 + * For conditions of distribution and use, see copyright notice in COPYING + */ +#if HAVE_CONFIG_H +# include "config.h" +#endif +#if HAVE_STDINT_H +# include +#endif +#include +#include +#include "aacenc.h" + +int aacenc_is_sbr_active(const aacenc_param_t *params) +{ + switch (params->profile) { + case AOT_SBR: case AOT_PS: case AOT_MP2_SBR: case AOT_MP2_PS: + case AOT_DABPLUS_SBR: case AOT_DABPLUS_PS: + case AOT_DRM_SBR: case AOT_DRM_MPEG_PS: + return 1; + } + if (params->profile == AOT_ER_AAC_ELD && params->lowdelay_sbr) + return 1; + return 0; +} + +static +int aacenc_channel_mode(const pcm_sample_description_t *format) +{ + uint32_t chanmask = format->channel_mask; + + if (format->channels_per_frame > 6) + return 0; + if (!chanmask) { + static uint32_t defaults[] = { 0x4, 0x3, 0x7, 0, 0x37, 0x3f }; + chanmask = defaults[format->channels_per_frame - 1]; + } + switch (chanmask) { + case 0x3: return MODE_2; + case 0x4: return MODE_1; + case 0x7: return MODE_1_2; + case 0x37: return MODE_1_2_2; + case 0x3f: return MODE_1_2_2_1; + case 0x107: return MODE_1_2_1; + case 0x607: return MODE_1_2_2; + case 0x60f: return MODE_1_2_2_1; + } + return 0; +} + +int aacenc_init(HANDLE_AACENCODER *encoder, const aacenc_param_t *params, + const pcm_sample_description_t *format, + AACENC_InfoStruct *info) +{ + int channel_mode; + int aot; + + *encoder = 0; + if ((channel_mode = aacenc_channel_mode(format)) == 0) { + fprintf(stderr, "ERROR: unsupported channel layout\n"); + goto FAIL; + } + if (aacEncOpen(encoder, 0, 0) != AACENC_OK) { + fprintf(stderr, "ERROR: aacEncOpen() failed\n"); + goto FAIL; + } + aot = (params->profile ? params->profile : AOT_AAC_LC); + if (aacEncoder_SetParam(*encoder, AACENC_AOT, aot) != AACENC_OK) { + fprintf(stderr, "ERROR: unsupported profile\n"); + goto FAIL; + } + if (params->bitrate_mode == 0) + aacEncoder_SetParam(*encoder, AACENC_BITRATE, params->bitrate); + else if (aacEncoder_SetParam(*encoder, AACENC_BITRATEMODE, + params->bitrate_mode) != AACENC_OK) { + fprintf(stderr, "ERROR: unsupported bitrate mode\n"); + goto FAIL; + } + if (aacEncoder_SetParam(*encoder, AACENC_SAMPLERATE, + format->sample_rate) != AACENC_OK) { + fprintf(stderr, "ERROR: unsupported sample rate\n"); + goto FAIL; + } + aacEncoder_SetParam(*encoder, AACENC_CHANNELMODE, channel_mode); + aacEncoder_SetParam(*encoder, AACENC_BANDWIDTH, params->bandwidth); + aacEncoder_SetParam(*encoder, AACENC_CHANNELORDER, 1); + aacEncoder_SetParam(*encoder, AACENC_AFTERBURNER, !!params->afterburner); + + if (aot == AOT_ER_AAC_ELD && params->lowdelay_sbr) + aacEncoder_SetParam(*encoder, AACENC_SBR_MODE, 1); + + if (aacEncoder_SetParam(*encoder, AACENC_TRANSMUX, + params->transport_format) != AACENC_OK) { + fprintf(stderr, "ERROR: unsupported transport format\n"); + goto FAIL; + } + if (aacEncoder_SetParam(*encoder, AACENC_SIGNALING_MODE, + params->sbr_signaling) != AACENC_OK) { + fprintf(stderr, "ERROR: unsupported transport format\n"); + goto FAIL; + } + if (params->adts_crc_check) + aacEncoder_SetParam(*encoder, AACENC_PROTECTION, 1); + if (params->header_period) + aacEncoder_SetParam(*encoder, AACENC_HEADER_PERIOD, + params->header_period); + + if (aacEncEncode(*encoder, 0, 0, 0, 0) != AACENC_OK) { + fprintf(stderr, "ERROR: encoder initialization failed\n"); + goto FAIL; + } + if (aacEncInfo(*encoder, info) != AACENC_OK) { + fprintf(stderr, "ERROR: cannot retrieve encoder info\n"); + goto FAIL; + } + return 0; +FAIL: + if (encoder) + aacEncClose(encoder); + return -1; +} + +int aac_encode_frame(HANDLE_AACENCODER encoder, + const pcm_sample_description_t *format, + const int16_t *input, unsigned iframes, + uint8_t **output, uint32_t *olen, uint32_t *osize) +{ + uint32_t ilen = iframes * format->channels_per_frame; + AACENC_BufDesc ibdesc = { 0 }, obdesc = { 0 }; + AACENC_InArgs iargs = { 0 }; + AACENC_OutArgs oargs = { 0 }; + void *ibufs[] = { (void*)input }; + void *obufs[1]; + INT ibuf_ids[] = { IN_AUDIO_DATA }; + INT obuf_ids[] = { OUT_BITSTREAM_DATA }; + INT ibuf_sizes[] = { ilen * sizeof(int16_t) }; + INT obuf_sizes[1]; + INT ibuf_el_sizes[] = { sizeof(int16_t) }; + INT obuf_el_sizes[] = { 1 }; + AACENC_ERROR err; + unsigned channel_mode, obytes; + + channel_mode = aacEncoder_GetParam(encoder, AACENC_CHANNELMODE); + obytes = 6144 / 8 * channel_mode; + if (!*output || *osize < obytes) { + *osize = obytes; + *output = realloc(*output, obytes); + } + obufs[0] = *output; + obuf_sizes[0] = obytes; + + iargs.numInSamples = ilen ? ilen : -1; /* -1 for signaling EOF */ + ibdesc.numBufs = 1; + ibdesc.bufs = ibufs; + ibdesc.bufferIdentifiers = ibuf_ids; + ibdesc.bufSizes = ibuf_sizes; + ibdesc.bufElSizes = ibuf_el_sizes; + obdesc.numBufs = 1; + obdesc.bufs = obufs; + obdesc.bufferIdentifiers = obuf_ids; + obdesc.bufSizes = obuf_sizes; + obdesc.bufElSizes = obuf_el_sizes; + + err = aacEncEncode(encoder, &ibdesc, &obdesc, &iargs, &oargs); + if (err != AACENC_ENCODE_EOF && err != AACENC_OK) { + fprintf(stderr, "ERROR: aacEncEncode() failed\n"); + return -1; + } + *olen = oargs.numOutBytes; + return oargs.numInSamples; +} diff --git a/src/aacenc.h b/src/aacenc.h new file mode 100644 index 0000000..ebb0509 --- /dev/null +++ b/src/aacenc.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2013 nu774 + * For conditions of distribution and use, see copyright notice in COPYING + */ +#ifndef AACENC_H +#define AACENC_H + +#include +#include "lpcm.h" + +#define AACENC_PARAMS \ + unsigned profile; \ + unsigned bitrate; \ + unsigned bitrate_mode; \ + unsigned bandwidth; \ + unsigned afterburner; \ + unsigned lowdelay_sbr; \ + unsigned sbr_signaling; \ + unsigned transport_format; \ + unsigned adts_crc_check; \ + unsigned header_period; + +typedef struct aacenc_param_t { + AACENC_PARAMS +} aacenc_param_t; + +int aacenc_is_sbr_active(const aacenc_param_t *params); + +int aacenc_init(HANDLE_AACENCODER *encoder, const aacenc_param_t *params, + const pcm_sample_description_t *format, + AACENC_InfoStruct *info); + +int aac_encode_frame(HANDLE_AACENCODER encoder, + const pcm_sample_description_t *format, + const int16_t *input, unsigned iframes, + uint8_t **output, uint32_t *olen, uint32_t *osize); + +#endif diff --git a/src/compat.h b/src/compat.h new file mode 100644 index 0000000..f20f03f --- /dev/null +++ b/src/compat.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2013 nu774 + * For conditions of distribution and use, see copyright notice in COPYING + */ +#ifndef COMPAT_H +#define COMPAT_H + +#ifndef HAVE_FSEEKO +# if _MSC_VER >= 1400 +# define fseeko _fseeki64 +# define ftello _ftelli64 +# else +# define fseeko fseek +# define ftello ftell +# endif +#endif + +int64_t aacenc_timer(void); +FILE *aacenc_fopen(const char *name, const char *mode); +void aacenc_getmainargs(int *argc, char ***argv); +char *aacenc_to_utf8(const char *s); +int aacenc_fprintf(FILE *fp, const char *fmt, ...); + +#endif diff --git a/src/compat_posix.c b/src/compat_posix.c new file mode 100644 index 0000000..1b8f14a --- /dev/null +++ b/src/compat_posix.c @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2013 nu774 + * For conditions of distribution and use, see copyright notice in COPYING + */ +#if HAVE_CONFIG_H +# include "config.h" +#endif +#if HAVE_STDINT_H +# include +#endif +#include +#include +#include +#include +#include +#include "compat.h" + +int64_t aacenc_timer(void) +{ + struct timeval tv = { 0 }; + gettimeofday(&tv, 0); + return (int64_t)tv.tv_sec * 1000 + tv.tv_usec / 1000; +} + +FILE *aacenc_fopen(const char *name, const char *mode) +{ + FILE *fp; + if (strcmp(name, "-") == 0) + fp = (mode[0] == 'r') ? stdin : stdout; + else + fp = fopen(name, mode); + return fp; +} + +void aacenc_getmainargs(int *argc, char ***argv) +{ + return; +} + +int aacenc_fprintf(FILE *fp, const char *fmt, ...) +{ + va_list ap; + int cnt; + + va_start(ap, fmt); + cnt = vfprintf(fp, fmt, ap); + va_end(ap); + return cnt; +} + +#ifndef HAVE_ICONV +char *aacenc_to_utf8(const char *s) +{ + return strdup(s); +} +#else /* HAVE_ICONV */ + +#include +#include +#include +#include + +#if HAVE_LOCALCHARSET_H +#include +#elif HAVE_LANGINFO_H +#include +static const char *locale_charset(void) +{ + return nl_langinfo(CODESET); +} +#else +static const char *locale_charset(void) +{ + return 0; +} +#endif + +static +int utf8_from_charset(const char *charset, const char *from, char **to) +{ + iconv_t cd; + size_t fromlen, obsize, ibleft, obleft; + char *ip, *op; + + cd = iconv_open("UTF-8", charset); + if (cd == (iconv_t)-1) + return -1; + + fromlen = strlen(from); + ibleft = fromlen; + obsize = 2; + obleft = obsize - 1; + *to = malloc(obsize); + ip = (char *)from; + op = *to; + + while (ibleft > 0) { + if (iconv(cd, &ip, &ibleft, &op, &obleft) != (size_t)-1) + break; + else { + if (errno == E2BIG || obleft == 0) { + ptrdiff_t offset = op - *to; + obsize *= 2; + *to = realloc(*to, obsize); + op = *to + offset; + obleft = obsize - offset - 1; + } + if (errno == EILSEQ) { + ++ip; + --ibleft; + *op++ = '?'; + --obleft; + } + if (errno != E2BIG && errno != EILSEQ) + break; + } + } + iconv_close(cd); + *op = 0; + if (fromlen > 0 && op == *to) { + free(op); + return -1; + } + return 0; +} + +char *aacenc_to_utf8(const char *s) +{ + char *result; + const char *charset; + + if ((charset = locale_charset()) == 0) + charset = "US-ASCII"; + if (utf8_from_charset(charset, s, &result) < 0) + result = strdup(s); + return result; +} +#endif /* HAVE_ICONV */ diff --git a/src/compat_win32.c b/src/compat_win32.c new file mode 100644 index 0000000..a68fa3f --- /dev/null +++ b/src/compat_win32.c @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2013 nu774 + * For conditions of distribution and use, see copyright notice in COPYING + */ +#if HAVE_CONFIG_H +# include "config.h" +#endif +#if HAVE_STDINT_H +# include +#endif +#include +#include +#include +#include +#include +#include +#include +#include "compat.h" +#define WIN32_LEAN_AND_MEAN +#include + +typedef struct +{ + int newmode; +} _startupinfo; + +extern +int __wgetmainargs(int *, wchar_t ***, wchar_t ***, int, _startupinfo *); + +int64_t aacenc_timer(void) +{ + struct __timeb64 tv; + _ftime64(&tv); + return (int64_t)tv.time * 1000 + tv.millitm; +} + +static +int codepage_decode_wchar(int codepage, const char *from, wchar_t **to) +{ + int nc = MultiByteToWideChar(codepage, 0, from, -1, 0, 0); + if (nc == 0) + return -1; + *to = malloc(nc * sizeof(wchar_t)); + MultiByteToWideChar(codepage, 0, from, -1, *to, nc); + return 0; +} + +static +int codepage_encode_wchar(int codepage, const wchar_t *from, char **to) +{ + int nc = WideCharToMultiByte(codepage, 0, from, -1, 0, 0, 0, 0); + if (nc == 0) + return -1; + *to = malloc(nc); + WideCharToMultiByte(codepage, 0, from, -1, *to, nc, 0, 0); + return 0; +} + +FILE *aacenc_fopen(const char *name, const char *mode) +{ + wchar_t *wname, *wmode; + FILE *fp; + + if (strcmp(name, "-") == 0) { + fp = (mode[0] == 'r') ? stdin : stdout; + _setmode(_fileno(fp), _O_BINARY); + } else { + codepage_decode_wchar(CP_UTF8, name, &wname); + codepage_decode_wchar(CP_UTF8, mode, &wmode); + fp = _wfopen(wname, wmode); + free(wname); + free(wmode); + } + return fp; +} + +void aacenc_getmainargs(int *argc, char ***argv) +{ + int i; + wchar_t **wargv, **envp; + _startupinfo si = { 0 }; + + __wgetmainargs(argc, &wargv, &envp, 1, &si); + *argv = malloc((*argc + 1) * sizeof(char*)); + for (i = 0; i < *argc; ++i) + codepage_encode_wchar(CP_UTF8, wargv[i], &(*argv)[i]); + (*argv)[*argc] = 0; +} + +char *aacenc_to_utf8(const char *s) +{ + return _strdup(s); +} + +int aacenc_fprintf(FILE *fp, const char *fmt, ...) +{ + va_list ap; + int cnt; + HANDLE fh = (HANDLE)_get_osfhandle(_fileno(fp)); + + if (GetFileType(fh) != FILE_TYPE_CHAR) { + va_start(ap, fmt); + cnt = vfprintf(fp, fmt, ap); + va_end(ap); + } else { + char *s; + wchar_t *ws; + DWORD nw; + + va_start(ap, fmt); + cnt = _vscprintf(fmt, ap); + va_end(ap); + + s = malloc(cnt + 1); + + va_start(ap, fmt); + cnt = _vsnprintf(s, cnt + 1, fmt, ap); + va_end(ap); + + codepage_decode_wchar(CP_UTF8, s, &ws); + free(s); + fflush(fp); + WriteConsoleW(fh, ws, cnt, &nw, 0); + free(ws); + } + return cnt; +} + diff --git a/src/lpcm.c b/src/lpcm.c new file mode 100644 index 0000000..5d338c7 --- /dev/null +++ b/src/lpcm.c @@ -0,0 +1,234 @@ +/* + * Copyright (C) 2013 nu774 + * For conditions of distribution and use, see copyright notice in COPYING + */ +#if HAVE_CONFIG_H +# include "config.h" +#endif +#if HAVE_STDINT_H +# include +#endif +#include +#include +#include "lpcm.h" +#include "m4af_endian.h" + +#ifdef _MSC_VER +# define inline __inline +# ifdef _M_IX86 +inline int lrint(double x) +{ + int n; + _asm { + fld x + fistp n + } + return n; +} +# else +# include +inline int lrint(double x) +{ + return _mm_cvtsd_si32(_mm_load_sd(&x)); +} +# endif +#endif + +inline int pcm_clip(int n, int min_value, int max_value) +{ + if (n < min_value) + return min_value; + else if (n > max_value) + return max_value; + return n; +} +inline float pcm_i2f(int32_t n) +{ + union { + int32_t ivalue; + float fvalue; + } u; + u.ivalue = n; + return u.fvalue; +} +inline double pcm_i2d(int64_t n) +{ + union { + int64_t ivalue; + double fvalue; + } u; + u.ivalue = n; + return u.fvalue; +} +inline int16_t pcm_quantize_s32(int32_t n) +{ + n = ((n >> 15) + 1) >> 1; + return (n == 0x8000) ? 0x7fff : n; +} +inline int16_t pcm_quantize_f64(double v) +{ + return pcm_clip(lrint(v * 32768.0), -32768, 32767); +} +inline int16_t pcm_s8_to_s16(int8_t n) +{ + return n << 8; +} +inline int16_t pcm_u8_to_s16(uint8_t n) +{ + return (n << 8) ^ 0x8000; +} +inline int16_t pcm_s16le_to_s16(int16_t n) +{ + return m4af_ltoh16(n); +} +inline int16_t pcm_s16be_to_s16(int16_t n) +{ + return m4af_btoh16(n); +} +inline int16_t pcm_u16le_to_s16(uint16_t n) +{ + return m4af_ltoh16(n) ^ 0x8000; +} +inline int16_t pcm_u16be_to_s16(uint16_t n) +{ + return m4af_btoh16(n) ^ 0x8000; +} +inline int32_t pcm_s24le_to_s32(uint8_t *p) +{ + return p[0]<<8 | p[1]<<16 | p[2]<<24; +} +inline int32_t pcm_s24be_to_s32(uint8_t *p) +{ + return p[0]<<24 | p[1]<<16 | p[2]<<8; +} +inline int32_t pcm_u24le_to_s32(uint8_t *p) +{ + return pcm_s24le_to_s32(p) ^ 0x80000000; +} +inline int32_t pcm_u24be_to_s32(uint8_t *p) +{ + return pcm_s24be_to_s32(p) ^ 0x80000000; +} +inline int16_t pcm_s24le_to_s16(uint8_t *p) +{ + return pcm_quantize_s32(pcm_s24le_to_s32(p)); +} +inline int16_t pcm_s24be_to_s16(uint8_t *p) +{ + return pcm_quantize_s32(pcm_s24be_to_s32(p)); +} +inline int16_t pcm_u24le_to_s16(uint8_t *p) +{ + return pcm_quantize_s32(pcm_u24le_to_s32(p)); +} +inline int16_t pcm_u24be_to_s16(uint8_t *p) +{ + return pcm_quantize_s32(pcm_u24be_to_s32(p)); +} +inline int16_t pcm_s32le_to_s16(int32_t n) +{ + return pcm_quantize_s32(m4af_ltoh32(n)); +} +inline int16_t pcm_s32be_to_s16(int32_t n) +{ + return pcm_quantize_s32(m4af_btoh32(n)); +} +inline int16_t pcm_u32le_to_s16(int32_t n) +{ + return pcm_quantize_s32(m4af_ltoh32(n) ^ 0x80000000); +} +inline int16_t pcm_u32be_to_s16(int32_t n) +{ + return pcm_quantize_s32(m4af_btoh32(n) ^ 0x80000000); +} +inline int16_t pcm_f32le_to_s16(int32_t n) +{ + return pcm_quantize_f64(pcm_i2f(m4af_ltoh32(n))); +} +inline int16_t pcm_f32be_to_s16(int32_t n) +{ + return pcm_quantize_f64(pcm_i2f(m4af_btoh32(n))); +} +inline int16_t pcm_f64le_to_s16(int64_t n) +{ + return pcm_quantize_f64(pcm_i2d(m4af_ltoh64(n))); +} +inline int16_t pcm_f64be_to_s16(int64_t n) +{ + return pcm_quantize_f64(pcm_i2d(m4af_btoh64(n))); +} + +int pcm_convert_to_native_sint16(const pcm_sample_description_t *format, + const void *input, uint32_t nframes, + int16_t **result, uint32_t *osize) +{ +#define CONVERT(type, conv) \ + do { \ + unsigned i; \ + type *ip = (type *)input; \ + for (i = 0; i < count; ++i) { \ + (*result)[i] = conv(ip[i]); \ + } \ + } while(0) + +#define CONVERT_BYTES(conv) \ + do { \ + unsigned i, bytes_per_channel; \ + uint8_t *ip = (uint8_t *)input; \ + bytes_per_channel = PCM_BYTES_PER_CHANNEL(format); \ + for (i = 0; i < count; ++i) { \ + (*result)[i] = conv(ip); \ + ip += bytes_per_channel; \ + } \ + } while(0) + + uint32_t count = nframes * format->channels_per_frame; + if (!count) + return 0; + if (!*result || *osize < count) { + *osize = count; + *result = realloc(*result, count * sizeof(int16_t)); + } + + switch (PCM_BYTES_PER_CHANNEL(format) | format->sample_type<<4) { + case 1 | PCM_TYPE_SINT<<4: + CONVERT(int8_t, pcm_s8_to_s16); break; + case 1 | PCM_TYPE_UINT<<4: + CONVERT(uint8_t, pcm_u8_to_s16); break; + case 2 | PCM_TYPE_SINT<<4: + CONVERT(int16_t, pcm_s16le_to_s16); break; + case 2 | PCM_TYPE_UINT<<4: + CONVERT(uint16_t, pcm_u16le_to_s16); break; + case 2 | PCM_TYPE_SINT_BE<<4: + CONVERT(int16_t, pcm_s16be_to_s16); break; + case 2 | PCM_TYPE_UINT_BE<<4: + CONVERT(int16_t, pcm_u16be_to_s16); break; + case 3 | PCM_TYPE_SINT<<4: + CONVERT_BYTES(pcm_s24le_to_s16); break; + case 3 | PCM_TYPE_UINT<<4: + CONVERT_BYTES(pcm_u24le_to_s16); break; + case 3 | PCM_TYPE_SINT_BE<<4: + CONVERT_BYTES(pcm_s24be_to_s16); break; + case 3 | PCM_TYPE_UINT_BE<<4: + CONVERT_BYTES(pcm_u24be_to_s16); break; + case 4 | PCM_TYPE_SINT<<4: + CONVERT(int32_t, pcm_s32le_to_s16); break; + case 4 | PCM_TYPE_UINT<<4: + CONVERT(uint32_t, pcm_u32le_to_s16); break; + case 4 | PCM_TYPE_FLOAT<<4: + CONVERT(int32_t, pcm_f32le_to_s16); break; + case 4 | PCM_TYPE_SINT_BE<<4: + CONVERT(int32_t, pcm_s32be_to_s16); break; + case 4 | PCM_TYPE_UINT_BE<<4: + CONVERT(uint32_t, pcm_u32be_to_s16); break; + case 4 | PCM_TYPE_FLOAT_BE<<4: + CONVERT(int32_t, pcm_f32be_to_s16); break; + case 8 | PCM_TYPE_FLOAT<<4: + CONVERT(int64_t, pcm_f64le_to_s16); break; + case 8 | PCM_TYPE_FLOAT_BE<<4: + CONVERT(int64_t, pcm_f64be_to_s16); break; + default: + return -1; + } + return 0; +} diff --git a/src/lpcm.h b/src/lpcm.h new file mode 100644 index 0000000..ae67a43 --- /dev/null +++ b/src/lpcm.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2013 nu774 + * For conditions of distribution and use, see copyright notice in COPYING + */ +#ifndef LPCM_H +#define LPCM_H + +enum pcm_type { + PCM_TYPE_UNKNOWN = 0, + PCM_TYPE_SINT = 1, + PCM_TYPE_UINT = 2, + PCM_TYPE_FLOAT = 4, + PCM_TYPE_SINT_BE = (8|1), + PCM_TYPE_UINT_BE = (8|2), + PCM_TYPE_FLOAT_BE = (8|4), +}; + +typedef struct pcm_sample_description_t { + enum pcm_type sample_type; + uint32_t sample_rate; + uint8_t bits_per_channel; + uint8_t bytes_per_frame; + uint8_t channels_per_frame; + uint32_t channel_mask; +} pcm_sample_description_t; + +#define PCM_IS_SINT(desc) ((desc)->sample_type & 1) +#define PCM_IS_UINT(desc) ((desc)->sample_type & 2) +#define PCM_IS_FLOAT(desc) ((desc)->sample_type & 4) +#define PCM_IS_BIG_ENDIAN(desc) ((desc)->sample_type & 8) +#define PCM_BYTES_PER_CHANNEL(desc) \ + ((desc)->bytes_per_frame / (desc)->channels_per_frame) + +int pcm_convert_to_native_sint16(const pcm_sample_description_t *format, + const void *input, uint32_t nframes, + int16_t **result, uint32_t *osize); +#endif diff --git a/src/m4af.c b/src/m4af.c new file mode 100644 index 0000000..0c62727 --- /dev/null +++ b/src/m4af.c @@ -0,0 +1,1188 @@ +/* + * Copyright (C) 2013 nu774 + * For conditions of distribution and use, see copyright notice in COPYING + */ +#if HAVE_CONFIG_H +# include "config.h" +#endif +#include +#include +#include +#include +#if HAVE_STDINT_H +# include +#endif +#include "m4af.h" +#include "m4af_endian.h" + +#define m4af_realloc(memory,size) realloc(memory, size) +#define m4af_free(memory) free(memory) + +typedef struct m4af_sample_entry_t { + uint32_t size; + uint32_t delta; +} m4af_sample_entry_t; + +typedef struct m4af_chunk_entry_t { + int64_t offset; + uint32_t size; + uint32_t samples_per_chunk; + uint32_t duration; +} m4af_chunk_entry_t; + +typedef struct m4af_itmf_entry_t { + uint32_t type; /* fcc */ + union { + uint32_t type_code; + char *name; + } u; + char *data; + uint32_t data_size; +} m4af_itmf_entry_t; + +typedef struct m4af_track_t { + uint32_t codec; + uint32_t timescale; + int64_t creation_time; + int64_t modification_time; + int64_t duration; + uint32_t frame_duration; + uint32_t encoder_delay; + uint32_t padding; + uint8_t *decSpecificInfo; + uint32_t decSpecificInfoSize; + uint32_t bufferSizeDB; + uint32_t maxBitrate; + uint32_t avgBitrate; + + m4af_sample_entry_t *sample_table; + uint32_t num_samples; + uint32_t sample_table_capacity; + + m4af_chunk_entry_t *chunk_table; + uint32_t num_chunks; + uint32_t chunk_table_capacity; + + uint8_t *chunk_buffer; + uint32_t chunk_size; + uint32_t chunk_capacity; +} m4af_track_t; + +struct m4af_writer_t { + uint32_t timescale; + int64_t creation_time; + int64_t modification_time; + int64_t mdat_pos; + int64_t mdat_size; + int last_error; + + m4af_itmf_entry_t *itmf_table; + uint32_t num_tags; + uint32_t itmf_table_capacity; + + m4af_io_callbacks_t io; + void *io_cookie; + + uint16_t num_tracks; + m4af_track_t track[1]; +}; + +static +int64_t m4af_timestamp(void) +{ + return (int64_t)(time(0)) + (((1970 - 1904) * 365) + 17) * 24 * 60 * 60; +} + +/* + * http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 + */ +static +uint32_t m4af_roundup(uint32_t n) +{ + n--; + n |= n >> 1; + n |= n >> 2; + n |= n >> 4; + n |= n >> 8; + n |= n >> 16; + n++; + return n; +} + +static +int64_t m4af_tell(m4af_writer_t *ctx) +{ + int64_t pos = -1; + if (!ctx->last_error && (pos = ctx->io.tell(ctx->io_cookie)) < 0) + ctx->last_error = M4AF_IO_ERROR; + return pos; +} + +static +int m4af_set_pos(m4af_writer_t *ctx, int64_t pos) +{ + int rc = -1; + if (!ctx->last_error && + (rc = ctx->io.seek(ctx->io_cookie, pos, SEEK_SET)) < 0) + ctx->last_error = M4AF_IO_ERROR; + return rc; +} + +static +int m4af_write(m4af_writer_t *ctx, const void *data, uint32_t size) +{ + int rc = -1; + if (!ctx->last_error && + (rc = ctx->io.write(ctx->io_cookie, data, size)) < 0) + ctx->last_error = M4AF_IO_ERROR; + return rc; +} + +static +int m4af_write32(m4af_writer_t *ctx, uint32_t data) +{ + data = m4af_htob32(data); + return m4af_write(ctx, &data, 4); +} + +static +int m4af_write64(m4af_writer_t *ctx, uint64_t data) +{ + data = m4af_htob64(data); + return m4af_write(ctx, &data, 8); +} + +static +int m4af_write24(m4af_writer_t *ctx, uint32_t data) +{ + data = m4af_htob32(data << 8); + return m4af_write(ctx, &data, 3); +} + +static +void m4af_write32_at(m4af_writer_t *ctx, int64_t pos, uint32_t value) +{ + int64_t current_pos = m4af_tell(ctx); + m4af_set_pos(ctx, pos); + m4af_write32(ctx, value); + m4af_set_pos(ctx, current_pos); +} + +m4af_writer_t *m4af_create(uint32_t codec, uint32_t timescale, + m4af_io_callbacks_t *io, void *io_cookie) +{ + m4af_writer_t *ctx; + int64_t timestamp; + + if (codec != M4AF_FOURCC('m','p','4','a') && + codec != M4AF_FOURCC('a','l','a','c')) + return 0; + if ((ctx = m4af_realloc(0, sizeof(m4af_writer_t))) == 0) + return 0; + memset(ctx, 0, sizeof(m4af_writer_t)); + memcpy(&ctx->io, io, sizeof(m4af_io_callbacks_t)); + ctx->io_cookie = io_cookie; + ctx->timescale = timescale; + timestamp = m4af_timestamp(); + ctx->creation_time = timestamp; + ctx->modification_time = timestamp; + ctx->num_tracks = 1; + ctx->track[0].codec = codec; + ctx->track[0].timescale = timescale; + ctx->track[0].creation_time = timestamp; + ctx->track[0].modification_time = timestamp; + return ctx; +} + +void m4af_set_fixed_frame_duration(m4af_writer_t *ctx, int track_idx, + uint32_t length) +{ + ctx->track[track_idx].frame_duration = length; +} + +int m4af_decoder_specific_info(m4af_writer_t *ctx, int track_idx, + uint8_t *data, uint32_t size) +{ + m4af_track_t *track = &ctx->track[track_idx]; + if (size > track->decSpecificInfoSize) { + uint8_t *memory = m4af_realloc(track->decSpecificInfo, size); + if (memory == 0) { + ctx->last_error = M4AF_NO_MEMORY; + return -1; + } + track->decSpecificInfo = memory; + } + if (size > 0) + memcpy(track->decSpecificInfo, data, size); + track->decSpecificInfoSize = size; + return 0; +} + +void m4af_set_priming(m4af_writer_t *ctx, int track_idx, + uint32_t encoder_delay, uint32_t padding) +{ + m4af_track_t *track = &ctx->track[track_idx]; + track->encoder_delay = encoder_delay; + track->padding = padding; +} + +static +int m4af_add_sample_entry(m4af_writer_t *ctx, int track_idx, + uint32_t size, uint32_t delta) +{ + m4af_track_t *track = &ctx->track[track_idx]; + m4af_sample_entry_t *entry; + + if (ctx->last_error) + return -1; + if (track->num_samples == track->sample_table_capacity) { + uint32_t new_size = track->sample_table_capacity; + new_size = new_size ? new_size * 2 : 1; + entry = m4af_realloc(track->sample_table, new_size * sizeof(*entry)); + if (entry == 0) { + ctx->last_error = M4AF_NO_MEMORY; + return -1; + } + track->sample_table = entry; + track->sample_table_capacity = new_size; + } + entry = track->sample_table + track->num_samples; + entry->size = size; + entry->delta = delta; + ++track->num_samples; + return 0; +} + +static +int m4af_flush_chunk(m4af_writer_t *ctx, int track_idx) +{ + m4af_track_t *track = &ctx->track[track_idx]; + m4af_chunk_entry_t *entry; + if (!track->num_chunks || !track->chunk_size) + return 0; + entry = &track->chunk_table[track->num_chunks - 1]; + entry->offset = m4af_tell(ctx); + m4af_write(ctx, track->chunk_buffer, track->chunk_size); + ctx->mdat_size += track->chunk_size; + track->chunk_size = 0; + return ctx->last_error ? -1 : 0; +} + +static +int m4af_update_chunk_table(m4af_writer_t *ctx, int track_idx, + uint32_t size, uint32_t delta) +{ + m4af_track_t *track = &ctx->track[track_idx]; + m4af_chunk_entry_t *entry; + int add_new_chunk = 0; + + if (ctx->last_error) + return -1; + if (track->num_chunks == 0) + add_new_chunk = 1; + else { + entry = &track->chunk_table[track->num_chunks - 1]; + if (entry->duration + delta > track->timescale / 2) + add_new_chunk = 1; + } + if (add_new_chunk) { + m4af_flush_chunk(ctx, track_idx); + if (track->num_chunks == track->chunk_table_capacity) { + uint32_t new_size = track->chunk_table_capacity; + new_size = new_size ? new_size * 2 : 1; + entry = m4af_realloc(track->chunk_table, new_size * sizeof(*entry)); + if (entry == 0) { + ctx->last_error = M4AF_NO_MEMORY; + return -1; + } + track->chunk_table = entry; + track->chunk_table_capacity = new_size; + } + memset(&track->chunk_table[track->num_chunks++], 0, + sizeof(m4af_chunk_entry_t)); + } + entry = &track->chunk_table[track->num_chunks - 1]; + entry->size += size; + ++entry->samples_per_chunk; + entry->duration += delta; + return 0; +} + +static +void m4af_update_max_bitrate(m4af_writer_t *ctx, int track_idx) +{ + m4af_track_t *track = &ctx->track[track_idx]; + uint32_t duration = 0, size = 0, bitrate; + m4af_sample_entry_t *ent = track->sample_table + track->num_samples - 1; + + for (; ent >= track->sample_table && duration < track->timescale; --ent) { + duration += ent->delta; + size += ent->size; + } + bitrate = (uint32_t)(size * 8.0 * track->timescale / duration + .5); + if (bitrate > track->maxBitrate) + track->maxBitrate = bitrate; +} + +static +int m4af_append_sample_to_chunk(m4af_writer_t *ctx, int track_idx, + const void *data, uint32_t size) +{ + m4af_track_t *track = &ctx->track[track_idx]; + uint32_t newsize = track->chunk_size + size; + + if (ctx->last_error) + return -1; + if (track->chunk_capacity < newsize) { + uint32_t capacity = m4af_roundup(newsize); + uint8_t *memory = realloc(track->chunk_buffer, capacity); + if (!memory) { + ctx->last_error = M4AF_NO_MEMORY; + return -1; + } + track->chunk_buffer = memory; + track->chunk_capacity = capacity; + } + memcpy(track->chunk_buffer + track->chunk_size, data, size); + track->chunk_size = newsize; + return 0; +} + +int m4af_write_sample(m4af_writer_t *ctx, int track_idx, const void *data, + uint32_t size, uint32_t duration) +{ + m4af_track_t *track = &ctx->track[track_idx]; + if (track->frame_duration) + duration = track->frame_duration; + if (size > track->bufferSizeDB) + track->bufferSizeDB = size; + track->duration += duration; + m4af_add_sample_entry(ctx, track_idx, size, duration); + m4af_update_chunk_table(ctx, track_idx, size, duration); + m4af_update_max_bitrate(ctx, track_idx); + m4af_append_sample_to_chunk(ctx, track_idx, data, size); + return ctx->last_error ? -1 : 0; +} + +static +int m4af_add_itmf_entry(m4af_writer_t *ctx) +{ + m4af_itmf_entry_t *entry; + if (ctx->num_tags == ctx->itmf_table_capacity) { + uint32_t new_size = ctx->itmf_table_capacity; + new_size = new_size ? new_size * 2 : 1; + entry = m4af_realloc(ctx->itmf_table, new_size * sizeof(*entry)); + if (entry == 0) { + ctx->last_error = M4AF_NO_MEMORY; + return -1; + } + ctx->itmf_table = entry; + ctx->itmf_table_capacity = new_size; + } + ++ctx->num_tags; + return 0; +} + +int m4af_add_itmf_long_tag(m4af_writer_t *ctx, const char *name, + const char *data) +{ + m4af_itmf_entry_t *entry; + size_t name_len = strlen(name); + size_t data_len = strlen(data); + char *name_copy = m4af_realloc(0, name_len + 1); + char *data_copy = m4af_realloc(0, data_len); + if (!name_copy || !data_copy) { + ctx->last_error = M4AF_NO_MEMORY; + goto FAIL; + } + if (m4af_add_itmf_entry(ctx) < 0) + goto FAIL; + memcpy(name_copy, name, name_len + 1); + memcpy(data_copy, data, data_len); + entry = ctx->itmf_table + ctx->num_tags - 1; + entry->type = M4AF_FOURCC('-','-','-','-'); + entry->u.name = name_copy; + entry->data = data_copy; + entry->data_size = data_len; + return 0; +FAIL: + if (name_copy) + m4af_free(name_copy); + if (data_copy) + m4af_free(data_copy); + return -1; +} + +int m4af_add_itmf_short_tag(m4af_writer_t *ctx, uint32_t type, + uint32_t type_code, const void *data, + uint32_t data_size) +{ + m4af_itmf_entry_t *entry; + char *data_copy = m4af_realloc(0, data_size); + if (!data_copy) { + ctx->last_error = M4AF_NO_MEMORY; + goto FAIL; + } + if (m4af_add_itmf_entry(ctx) < 0) + goto FAIL; + entry = ctx->itmf_table + ctx->num_tags - 1; + entry->type = type; + entry->u.type_code = type_code; + memcpy(data_copy, data, data_size); + entry->data = data_copy; + entry->data_size = data_size; + return 0; +FAIL: + if (data_copy) + m4af_free(data_copy); + return -1; +} + +int m4af_add_itmf_string_tag(m4af_writer_t *ctx, uint32_t type, + const char *data) +{ + return m4af_add_itmf_short_tag(ctx, type, M4AF_UTF8, data, strlen(data)); +} + +int m4af_add_itmf_int8_tag(m4af_writer_t *ctx, uint32_t type, int value) +{ + uint8_t data = value; + return m4af_add_itmf_short_tag(ctx, type, M4AF_INTEGER, &data, 1); +} + +int m4af_add_itmf_int16_tag(m4af_writer_t *ctx, uint32_t type, int value) +{ + uint16_t data = m4af_htob16(value); + return m4af_add_itmf_short_tag(ctx, type, M4AF_INTEGER, &data, 2); +} + +int m4af_add_itmf_int32_tag(m4af_writer_t *ctx, uint32_t type, int value) +{ + uint32_t data = m4af_htob32(value); + return m4af_add_itmf_short_tag(ctx, type, M4AF_INTEGER, &data, 4); +} + +int m4af_add_itmf_track_tag(m4af_writer_t *ctx, int track, int total) +{ + uint16_t data[4] = { 0 }; + data[1] = m4af_htob16(track); + data[2] = m4af_htob16(total); + return m4af_add_itmf_short_tag(ctx, M4AF_FOURCC('t','r','k','n'), + M4AF_IMPLICIT, &data, 8); +} + +int m4af_add_itmf_disk_tag(m4af_writer_t *ctx, int disk, int total) +{ + uint16_t data[3] = { 0 }; + data[1] = m4af_htob16(disk); + data[2] = m4af_htob16(total); + return m4af_add_itmf_short_tag(ctx, M4AF_FOURCC('d','i','s','k'), + M4AF_IMPLICIT, &data, 6); +} + +int m4af_add_itmf_genre_tag(m4af_writer_t *ctx, int genre) +{ + uint16_t data = m4af_htob16(genre); + return m4af_add_itmf_short_tag(ctx, M4AF_FOURCC('g','n','r','e'), + M4AF_IMPLICIT, &data, 2); +} + +static +int m4af_set_iTunSMPB(m4af_writer_t *ctx) +{ + const char *template = " 00000000 %08X %08X %08X%08X 00000000 00000000 " + "00000000 00000000 00000000 00000000 00000000 00000000"; + m4af_track_t *track = &ctx->track[0]; + char buf[256]; + uint64_t length = track->duration - track->encoder_delay - track->padding; + sprintf(buf, template, track->encoder_delay, track->padding, + (uint32_t)(length >> 32), (uint32_t)length); + return m4af_add_itmf_long_tag(ctx, "iTunSMPB", buf); +} + +static +void m4af_update_size(m4af_writer_t *ctx, int64_t pos) +{ + int64_t current_pos = m4af_tell(ctx); + m4af_set_pos(ctx, pos); + m4af_write32(ctx, current_pos - pos); + m4af_set_pos(ctx, current_pos); +} + +static +int m4af_head_version(m4af_writer_t *ctx, int track_idx) +{ + m4af_track_t *track = &ctx->track[track_idx]; + return track->duration > UINT32_MAX + || track->creation_time > UINT32_MAX + || track->modification_time > UINT32_MAX; +} + +static +void m4af_descriptor(m4af_writer_t *ctx, uint32_t tag, uint32_t size) +{ + uint8_t buf[5]; + buf[0] = tag; + buf[1] = ((size >> 21) | 0x80); + buf[2] = ((size >> 14) | 0x80); + buf[3] = ((size >> 7) | 0x80); + buf[4] = (size & 0x7f); + m4af_write(ctx, buf, 5); +} + +static +void m4af_ftyp_box(m4af_writer_t *ctx) +{ + m4af_write(ctx, "\0\0\0\040""ftypM4A \0\0\0\0M4A mp42isom\0\0\0\0", 32); +} + +static +void m4af_free_box(m4af_writer_t *ctx, uint32_t size) +{ + int64_t pos = m4af_tell(ctx); + m4af_write32(ctx, size + 8); + m4af_write(ctx, "free", 4); + if (size > 0) + m4af_set_pos(ctx, pos + size + 8); +} + +int m4af_begin_write(m4af_writer_t *ctx) +{ + m4af_ftyp_box(ctx); + m4af_free_box(ctx, 0); + m4af_write(ctx, "\0\0\0\0mdat", 8); + ctx->mdat_pos = m4af_tell(ctx); + return ctx->last_error ? -1 : 0; +} + +static +void m4af_stco_box(m4af_writer_t *ctx, int track_idx) +{ + m4af_track_t *track = &ctx->track[track_idx]; + uint32_t i; + m4af_chunk_entry_t *index = track->chunk_table; + int is_co64 = (ctx->mdat_pos + ctx->mdat_size > UINT32_MAX); + int64_t pos = m4af_tell(ctx); + + m4af_write32(ctx, 0); /* size */ + m4af_write(ctx, is_co64 ? "co64" : "stco", 4); + m4af_write32(ctx, 0); /* version and flags */ + m4af_write32(ctx, track->num_chunks); + for (i = 0; i < track->num_chunks; ++i, ++index) { + if (is_co64) + m4af_write64(ctx, index->offset); + else + m4af_write32(ctx, index->offset); + } + m4af_update_size(ctx, pos); +} + +static +void m4af_stsz_box(m4af_writer_t *ctx, int track_idx) +{ + m4af_track_t *track = &ctx->track[track_idx]; + m4af_sample_entry_t *index = track->sample_table; + uint32_t i; + int64_t pos = m4af_tell(ctx); + m4af_write(ctx, + "\0\0\0\0" /* size */ + "stsz" /* type */ + "\0" /* version */ + "\0\0\0" /* flags */ + "\0\0\0\0" /* sample_size: 0(variable) */ + , 16); + m4af_write32(ctx, track->num_samples); + for (i = 0; i < track->num_samples; ++i, ++index) + m4af_write32(ctx, index->size); + m4af_update_size(ctx, pos); +} + +static +void m4af_stsc_box(m4af_writer_t *ctx, int track_idx) +{ + m4af_track_t *track = &ctx->track[track_idx]; + m4af_chunk_entry_t *index = track->chunk_table; + uint32_t i, prev_samples_per_chunk = 0, entry_count = 0; + int64_t pos = m4af_tell(ctx); + m4af_write(ctx, + "\0\0\0\0" /* size */ + "stsc" /* type */ + "\0" /* version */ + "\0\0\0" /* flags */ + "\0\0\0\0" /* entry_count */ + , 16); + + for (i = 0; i < track->num_chunks; ++i, ++index) { + if (index->samples_per_chunk != prev_samples_per_chunk) { + ++entry_count; + m4af_write32(ctx, i + 1); + m4af_write32(ctx, index->samples_per_chunk); + m4af_write32(ctx, 1); /* sample_description_index */ + prev_samples_per_chunk = index->samples_per_chunk; + } + } + m4af_write32_at(ctx, pos + 12, entry_count); + m4af_update_size(ctx, pos); +} + +static +void m4af_stts_box(m4af_writer_t *ctx, int track_idx) +{ + m4af_track_t *track = &ctx->track[track_idx]; + m4af_sample_entry_t *index = track->sample_table; + uint32_t i, prev_delta = 0, entry_count = 0, sample_count = 0; + int64_t pos = m4af_tell(ctx); + m4af_write(ctx, + "\0\0\0\0" /* size */ + "stts" /* type */ + "\0" /* version */ + "\0\0\0" /* flags */ + "\0\0\0\0" /* entry_count */ + , 16); + + for (i = 0; i < track->num_samples; ++i, ++index) { + if (index->delta == prev_delta) + ++sample_count; + else { + ++entry_count; + if (sample_count) { + m4af_write32(ctx, sample_count); + m4af_write32(ctx, prev_delta); + } + prev_delta = index->delta; + sample_count = 1; + } + } + if (sample_count) { + m4af_write32(ctx, sample_count); + m4af_write32(ctx, prev_delta); + } + m4af_write32_at(ctx, pos + 12, entry_count); + m4af_update_size(ctx, pos); +} + +static +void m4af_esds_box(m4af_writer_t *ctx, int track_idx) +{ + m4af_track_t *track = &ctx->track[track_idx]; + int64_t pos = m4af_tell(ctx); + m4af_write(ctx, "\0\0\0\0esds", 8); + m4af_write32(ctx, 0); /* version + flags */ + + /* ES_Descriptor */ + m4af_descriptor(ctx, 3, 32 + track->decSpecificInfoSize); + m4af_write(ctx, "\0\0\0", 3); + /* DecoderConfigDescriptor */ + m4af_descriptor(ctx, 4, 18 + track->decSpecificInfoSize); + m4af_write(ctx, + "\x40" /* objectTypeIndication: 0x40(Audio ISO/IEC 14496-3)*/ + "\x15" /* streamType(6): 0x05(AudioStream) + * upStream(1) : 0 + * reserved(1) : 1 + */ + , 2); + m4af_write24(ctx, track->bufferSizeDB); + m4af_write32(ctx, track->maxBitrate); + m4af_write32(ctx, track->avgBitrate); + /* DecoderSpecificInfo */ + m4af_descriptor(ctx, 5, track->decSpecificInfoSize); + m4af_write(ctx, track->decSpecificInfo, track->decSpecificInfoSize); + /* SLConfigDescriptor */ + m4af_descriptor(ctx, 6, 1); + m4af_write(ctx, "\002", 1); /* predefined */ + + m4af_update_size(ctx, pos); +} + +static +void m4af_alac_box(m4af_writer_t *ctx, int track_idx) +{ + m4af_track_t *track = &ctx->track[track_idx]; + int64_t pos = m4af_tell(ctx); + m4af_write(ctx, + "\0\0\0\0" /* size */ + "alac" /* type */ + "\0" /* version */ + "\0\0\0" /* flags */ + , 12); + m4af_write(ctx, track->decSpecificInfo, track->decSpecificInfoSize); + m4af_update_size(ctx, pos); +} + +static +void m4af_mp4a_box(m4af_writer_t *ctx, int track_idx) +{ + m4af_track_t *track = &ctx->track[track_idx]; + int64_t pos = m4af_tell(ctx); + m4af_write32(ctx, 0); /* size */ + m4af_write32(ctx, track->codec); /* mp4a or alac */ + m4af_write(ctx, + "\0\0\0\0\0\0" /* reserved */ + "\0\001" /* data_reference_index: 1 */ + "\0\0\0\0" /* reserved[0] */ + "\0\0\0\0" /* reserved[1] */ + "\0\002" /* channelcount: 2 */ + "\0\020" /* samplesize: 16 */ + "\0\0" /* pre_defined */ + "\0\0" /* reserved */ + ,24); + if (track->codec == M4AF_FOURCC('m','p','4','a')) { + m4af_write32(ctx, track->timescale << 16); + m4af_esds_box(ctx, track_idx); + } else { + m4af_write32(ctx, 44100); + m4af_alac_box(ctx, track_idx); + } + m4af_update_size(ctx, pos); +} + +static +void m4af_stsd_box(m4af_writer_t *ctx, int track_idx) +{ + int64_t pos = m4af_tell(ctx); + m4af_write(ctx, "\0\0\0\0stsd", 8); + m4af_write(ctx, + "\0" /* version */ + "\0\0\0" /* flags */ + "\0\0\0\001" /* entry_count: 1 */ + , 8); + m4af_mp4a_box(ctx, track_idx); + m4af_update_size(ctx, pos); +} + +static +void m4af_stbl_box(m4af_writer_t *ctx, int track_idx) +{ + int64_t pos = m4af_tell(ctx); + m4af_write(ctx, "\0\0\0\0stbl", 8); + m4af_stsd_box(ctx, track_idx); + m4af_stts_box(ctx, track_idx); + m4af_stsc_box(ctx, track_idx); + m4af_stsz_box(ctx, track_idx); + m4af_stco_box(ctx, track_idx); + m4af_update_size(ctx, pos); +} + +static +void m4af_url_box(m4af_writer_t *ctx, int track_idx) +{ + m4af_write(ctx, + "\0\0\0\014" /* size */ + "url " /* type */ + "\0" /* version */ + "\0\0\001" /* flags: 1(in the same file) */ + , 12); +} + +static +void m4af_dref_box(m4af_writer_t *ctx, int track_idx) +{ + int64_t pos = m4af_tell(ctx); + m4af_write(ctx, "\0\0\0\0dref", 8); + m4af_write(ctx, + "\0" /* version */ + "\0\0\0" /* flags */ + "\0\0\0\001" /* entry_count: 1 */ + ,8); + m4af_url_box(ctx, track_idx); + m4af_update_size(ctx, pos); +} + +static +void m4af_dinf_box(m4af_writer_t *ctx, int track_idx) +{ + int64_t pos = m4af_tell(ctx); + m4af_write(ctx, "\0\0\0\0dinf", 8); + m4af_dref_box(ctx, track_idx); + m4af_update_size(ctx, pos); +} + +static +void m4af_smhd_box(m4af_writer_t *ctx, int track_idx) +{ + m4af_write(ctx, + "\0\0\0\020" /* size */ + "smhd" /* type */ + "\0" /* version */ + "\0\0\0" /* flags */ + "\0\0" /* balance */ + "\0\0" /* reserved */ + , 16); +} + +static +void m4af_minf_box(m4af_writer_t *ctx, int track_idx) +{ + m4af_track_t *track = &ctx->track[track_idx]; + int64_t pos = m4af_tell(ctx); + m4af_write(ctx, "\0\0\0\0minf", 8); + /* TODO: add TEXT support */ + if (track->codec != M4AF_CODEC_TEXT) + m4af_smhd_box(ctx, track_idx); + m4af_dinf_box(ctx, track_idx); + m4af_stbl_box(ctx, track_idx); + m4af_update_size(ctx, pos); +} + +static +void m4af_mdhd_box(m4af_writer_t *ctx, int track_idx) +{ + m4af_track_t *track = &ctx->track[track_idx]; + int64_t pos = m4af_tell(ctx); + uint8_t version = m4af_head_version(ctx, track_idx); + + m4af_write(ctx, "\0\0\0\0mdhd", 8); + m4af_write(ctx, &version, 1); + m4af_write(ctx, "\0\0\0", 3); /* flags */ + if (version) { + m4af_write64(ctx, track->creation_time); + m4af_write64(ctx, track->modification_time); + m4af_write32(ctx, track->timescale); + m4af_write64(ctx, track->duration); + } else { + m4af_write32(ctx, track->creation_time); + m4af_write32(ctx, track->modification_time); + m4af_write32(ctx, track->timescale); + m4af_write32(ctx, track->duration); + } + m4af_write(ctx, + "\x55\xc4" /* language: und */ + "\0\0" /* pre_defined */ + , 4); + m4af_update_size(ctx, pos); +} + +static +void m4af_hdlr_box(m4af_writer_t *ctx, int track_idx, const char *type) +{ + int64_t pos = m4af_tell(ctx); + static const char reserved_and_name[10] = { 0 }; + + m4af_write(ctx, + "\0\0\0\0" /* size */ + "hdlr" /* type */ + "\0" /* version */ + "\0\0\0" /* flags */ + "\0\0\0\0" /* pre_defined */ + , 16); + m4af_write(ctx, type, 4); /* handler_type */ + /* reserved[0] */ + m4af_write(ctx, !strcmp(type, "mdir") ? "appl" : "\0\0\0\0", 4); + /* reserved[1], reserved[2], name */ + m4af_write(ctx, reserved_and_name, (pos & 1) ? 9 : 10); + m4af_update_size(ctx, pos); +} + +static +void m4af_mdia_box(m4af_writer_t *ctx, int track_idx) +{ + m4af_track_t *track = &ctx->track[track_idx]; + const char *hdlr = + (track->codec == M4AF_CODEC_TEXT) ? "text" : "soun"; + int64_t pos = m4af_tell(ctx); + m4af_write(ctx, "\0\0\0\0mdia", 8); + m4af_mdhd_box(ctx, track_idx); + m4af_hdlr_box(ctx, track_idx, hdlr); + m4af_minf_box(ctx, track_idx); + m4af_update_size(ctx, pos); +} + +static +void m4af_tkhd_box(m4af_writer_t *ctx, int track_idx) +{ + m4af_track_t *track = &ctx->track[track_idx]; + int64_t pos = m4af_tell(ctx); + uint8_t version = m4af_head_version(ctx, track_idx); + m4af_write(ctx, "\0\0\0\0tkhd", 8); + m4af_write(ctx, &version, 1); + m4af_write(ctx, "\0\0\007", 3); /* flags */ + if (version) { + m4af_write64(ctx, track->creation_time); + m4af_write64(ctx, track->modification_time); + m4af_write32(ctx, track_idx + 1); + m4af_write(ctx, "\0\0\0\0" /* reserved */ + , 4); + m4af_write64(ctx, track->duration); + } else { + m4af_write32(ctx, track->creation_time); + m4af_write32(ctx, track->modification_time); + m4af_write32(ctx, track_idx + 1); + m4af_write(ctx, "\0\0\0\0" /* reserved */ + , 4); + m4af_write32(ctx, track->duration); + } + m4af_write(ctx, + "\0\0\0\0" /* reserved[0] */ + "\0\0\0\0" /* reserved[1] */ + "\0\0" /* layer */ + "\0\0" /* alternate_group */ + "\001\0" /* volume: 1.0 */ + "\0\0" /* reserved */ + "\0\001\0\0" /* matrix[0] */ + "\0\0\0\0" /* matrix[1] */ + "\0\0\0\0" /* matrix[2] */ + "\0\0\0\0" /* matrix[3] */ + "\0\001\0\0" /* matrix[4] */ + "\0\0\0\0" /* matrix[5] */ + "\0\0\0\0" /* matrix[6] */ + "\0\0\0\0" /* matrix[7] */ + "\100\0\0\0" /* matrix[8] */ + "\0\0\0\0" /* width */ + "\0\0\0\0" /* height */ + , 60); + m4af_update_size(ctx, pos); +} + +static +void m4af_trak_box(m4af_writer_t *ctx, int track_idx) +{ + int64_t pos = m4af_tell(ctx); + m4af_write(ctx, "\0\0\0\0trak", 8); + m4af_tkhd_box(ctx, track_idx); + m4af_mdia_box(ctx, track_idx); + m4af_update_size(ctx, pos); +} + +static +int64_t m4af_movie_duration(m4af_writer_t *ctx) +{ + int64_t movie_duration = 0; + unsigned i; + for (i = 0; i < ctx->num_tracks; ++i) { + double x = ctx->track[i].duration; + int64_t duration = x * ctx->track[i].timescale / ctx->timescale + .5; + if (duration > movie_duration) + movie_duration = duration; + } + return movie_duration; +} + +static +void m4af_mvhd_box(m4af_writer_t *ctx) +{ + int64_t pos = m4af_tell(ctx); + uint8_t version = m4af_head_version(ctx, 0); + int64_t movie_duration = m4af_movie_duration(ctx); + + m4af_write(ctx, "\0\0\0\0mvhd", 8); + m4af_write(ctx, &version, 1); + m4af_write(ctx, "\0\0\0", 3); /* flags */ + if (version) { + m4af_write64(ctx, ctx->creation_time); + m4af_write64(ctx, ctx->modification_time); + m4af_write32(ctx, ctx->timescale); + m4af_write64(ctx, movie_duration); + } else { + m4af_write32(ctx, ctx->creation_time); + m4af_write32(ctx, ctx->modification_time); + m4af_write32(ctx, ctx->timescale); + m4af_write32(ctx, movie_duration); + } + m4af_write(ctx, + "\0\001\0\0" /* rate: 1.0 */ + "\001\0" /* volume: 1.0 */ + "\0\0" /* reserved */ + "\0\0\0\0" /* reserved[0] */ + "\0\0\0\0" /* reserved[1] */ + "\0\001\0\0" /* matrix[0] */ + "\0\0\0\0" /* matrix[1] */ + "\0\0\0\0" /* matrix[2] */ + "\0\0\0\0" /* matrix[3] */ + "\0\001\0\0" /* matrix[4] */ + "\0\0\0\0" /* matrix[5] */ + "\0\0\0\0" /* matrix[6] */ + "\0\0\0\0" /* matrix[7] */ + "\100\0\0\0" /* matrix[8] */ + "\0\0\0\0" /* pre_defined[0] */ + "\0\0\0\0" /* pre_defined[1] */ + "\0\0\0\0" /* pre_defined[2] */ + "\0\0\0\0" /* pre_defined[3] */ + "\0\0\0\0" /* pre_defined[4] */ + "\0\0\0\0" /* pre_defined[5] */ + , 76); + m4af_write32(ctx, ctx->num_tracks + 1); + m4af_update_size(ctx, pos); +} + +static +void m4af_mean_box(m4af_writer_t *ctx) +{ + m4af_write(ctx, + "\0\0\0\034" /* size */ + "mean" + "\0" /* version */ + "\0\0\0" /* flags */ + "com.apple.iTunes" /* meaning-string */ + , 28); +} + +static +void m4af_name_box(m4af_writer_t *ctx, const char *name) +{ + int64_t pos = m4af_tell(ctx); + m4af_write(ctx, + "\0\0\0\0" /* size */ + "name" /* type */ + "\0" /* version */ + "\0\0\0" /* flags */ + , 12); + m4af_write(ctx, name, strlen(name)); + m4af_update_size(ctx, pos); +} + +static +void m4af_data_box(m4af_writer_t *ctx, uint32_t type_code, + const char *data, uint32_t data_size) +{ + int64_t pos = m4af_tell(ctx); + uint8_t code = type_code; + m4af_write(ctx, + "\0\0\0\0" /* size */ + "data" /* type */ + "\0\0" /* reserved */ + "\0" /* type_set_indifier */ + ,11); + m4af_write(ctx, &code, 1); + m4af_write(ctx, "\0\0\0\0", 4); /* locale */ + m4af_write(ctx, data, data_size); + m4af_update_size(ctx, pos); +} + +static +void m4af_write_metadata(m4af_writer_t *ctx, m4af_itmf_entry_t *entry) +{ + int64_t pos = m4af_tell(ctx); + m4af_write(ctx, "\0\0\0\0", 4); + m4af_write32(ctx, entry->type); + if (entry->type != M4AF_FOURCC('-','-','-','-')) + m4af_data_box(ctx, entry->u.type_code, entry->data, entry->data_size); + else { + m4af_mean_box(ctx); + m4af_name_box(ctx, entry->u.name); + m4af_data_box(ctx, 1, entry->data, entry->data_size); + } + m4af_update_size(ctx, pos); +} + +static +void m4af_ilst_box(m4af_writer_t *ctx) +{ + uint32_t i; + int64_t pos = m4af_tell(ctx); + m4af_write(ctx, "\0\0\0\0ilst", 8); + for (i = 0; i < ctx->num_tags; ++i) + m4af_write_metadata(ctx, &ctx->itmf_table[i]); + m4af_update_size(ctx, pos); +} + +static +void m4af_meta_box(m4af_writer_t *ctx) +{ + int64_t pos = m4af_tell(ctx); + m4af_write(ctx, + "\0\0\0\0" /* size */ + "meta" /* type */ + "\0" /* version */ + "\0\0\0" /* flags */ + , 12); + m4af_hdlr_box(ctx, 0, "mdir"); + m4af_ilst_box(ctx); + m4af_update_size(ctx, pos); +} + +static +void m4af_udta_box(m4af_writer_t *ctx) +{ + int64_t pos = m4af_tell(ctx); + m4af_write(ctx, "\0\0\0\0udta", 8); + m4af_meta_box(ctx); + m4af_update_size(ctx, pos); +} + +static +void m4af_moov_box(m4af_writer_t *ctx) +{ + unsigned i; + int64_t pos = m4af_tell(ctx); + m4af_write(ctx, "\0\0\0\0moov", 8); + m4af_mvhd_box(ctx); + for (i = 0; i < ctx->num_tracks; ++i) + m4af_trak_box(ctx, i); + if (ctx->num_tags) + m4af_udta_box(ctx); + m4af_update_size(ctx, pos); +} + +static +void m4af_finalize_mdat(m4af_writer_t *ctx) +{ + if (ctx->mdat_size + 8 > UINT32_MAX) { + m4af_set_pos(ctx, ctx->mdat_pos - 16); + m4af_write32(ctx, 1); + m4af_write(ctx, "mdat", 4); + m4af_write64(ctx, ctx->mdat_size + 16); + } else { + m4af_set_pos(ctx, ctx->mdat_pos - 8); + m4af_write32(ctx, ctx->mdat_size + 8); + } + m4af_set_pos(ctx, ctx->mdat_pos + ctx->mdat_size); +} + +int m4af_finalize(m4af_writer_t *ctx) +{ + unsigned i; + m4af_track_t *track; + + for (i = 0; i < ctx->num_tracks; ++i) { + track = ctx->track + i; + if (track->duration) { + int64_t track_size = 0; + unsigned j; + for (j = 0; j < track->num_chunks; ++j) + track_size += track->chunk_table[j].size; + track->avgBitrate = + 8.0 * track_size * track->timescale / track->duration + .5; + } + m4af_flush_chunk(ctx, i); + } + if (ctx->track[0].encoder_delay || ctx->track[0].padding) + m4af_set_iTunSMPB(ctx); + m4af_finalize_mdat(ctx); + m4af_moov_box(ctx); + return ctx->last_error ? -1 : 0; +} + +static +void m4af_free_itmf_table(m4af_writer_t *ctx) +{ + uint32_t i; + m4af_itmf_entry_t *entry = ctx->itmf_table; + for (i = 0; i < ctx->num_tags; ++i, ++entry) { + if (entry->type == M4AF_FOURCC('-','-','-','-')) + m4af_free(entry->u.name); + m4af_free(entry->data); + } + m4af_free(ctx->itmf_table); +} + +void m4af_teardown(m4af_writer_t **ctxp) +{ + unsigned i; + m4af_writer_t *ctx = *ctxp; + m4af_track_t *track; + for (i = 0; i < ctx->num_tracks; ++i) { + track = ctx->track + i; + if (track->decSpecificInfo) + m4af_free(track->decSpecificInfo); + if (track->sample_table) + m4af_free(track->sample_table); + if (track->chunk_table) + m4af_free(track->chunk_table); + if (track->chunk_buffer) + m4af_free(track->chunk_buffer); + } + if (ctx->itmf_table) + m4af_free_itmf_table(ctx); + m4af_free(ctx); + *ctxp = 0; +} diff --git a/src/m4af.h b/src/m4af.h new file mode 100644 index 0000000..fd6bc78 --- /dev/null +++ b/src/m4af.h @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2013 nu774 + * For conditions of distribution and use, see copyright notice in COPYING + */ +#ifndef M4AF_H +#define M4AF_H + +#define M4AF_FOURCC(a,b,c,d) (((a)<<24)|((b)<<16)|((c)<<8)|(d)) + +enum m4af_error_code { + M4AF_IO_ERROR = 1, + M4AF_NO_MEMORY, +}; + +enum m4af_itmf_tag { + M4AF_TAG_TITLE = M4AF_FOURCC('\xa9','n','a','m'), + M4AF_TAG_ARTIST = M4AF_FOURCC('\xa9','A','R','T'), + M4AF_TAG_ALBUM = M4AF_FOURCC('\xa9','a','l','b'), + M4AF_TAG_GENRE = M4AF_FOURCC('\xa9','g','e','n'), + M4AF_TAG_DATE = M4AF_FOURCC('\xa9','d','a','y'), + M4AF_TAG_COMPOSER = M4AF_FOURCC('\xa9','w','r','t'), + M4AF_TAG_GROUPING = M4AF_FOURCC('\xa9','g','r','p'), + M4AF_TAG_COMMENT = M4AF_FOURCC('\xa9','c','m','t'), + M4AF_TAG_LYRICS = M4AF_FOURCC('\xa9','l','y','r'), + M4AF_TAG_TOOL = M4AF_FOURCC('\xa9','t','o','o'), + M4AF_TAG_ALBUM_ARTIST = M4AF_FOURCC('a','A','R','T'), + M4AF_TAG_TRACK = M4AF_FOURCC('t','r','k','n'), + M4AF_TAG_DISK = M4AF_FOURCC('d','i','s','k'), + M4AF_TAG_GENRE_ID3 = M4AF_FOURCC('g','n','r','e'), + M4AF_TAG_TEMPO = M4AF_FOURCC('t','m','p','o'), + M4AF_TAG_DESCRIPTION = M4AF_FOURCC('d','e','s','c'), + M4AF_TAG_LONG_DESCRIPTION = M4AF_FOURCC('l','d','e','s'), + M4AF_TAG_COPYRIGHT = M4AF_FOURCC('c','p','r','t'), + M4AF_TAG_COMPILATION = M4AF_FOURCC('c','p','i','l'), + M4AF_TAG_ARTWORK = M4AF_FOURCC('c','o','v','r'), +}; + +enum m4af_itmf_type_code { + M4AF_IMPLICIT = 0, + M4AF_UTF8 = 1, + M4AF_GIF = 12, + M4AF_JPEG = 13, + M4AF_PNG = 14, + M4AF_INTEGER = 21, +}; + +enum m4af_codec_type { + M4AF_CODEC_MP4A = M4AF_FOURCC('m','p','4','a'), + M4AF_CODEC_ALAC = M4AF_FOURCC('a','l','a','c'), + M4AF_CODEC_TEXT = M4AF_FOURCC('t','e','x','t'), +}; + +typedef int (*m4af_write_callback)(void *cookie, const void *data, + uint32_t size); +typedef int (*m4af_seek_callback)(void *cookie, int64_t off, int whence); +typedef int64_t (*m4af_tell_callback)(void *cookie); + +typedef struct m4af_io_callbacks_t { + m4af_write_callback write; + m4af_seek_callback seek; + m4af_tell_callback tell; +} m4af_io_callbacks_t; + +typedef struct m4af_writer_t m4af_writer_t; + +m4af_writer_t *m4af_create(uint32_t codec, uint32_t timescale, + m4af_io_callbacks_t *io, void *io_cookie); + +void m4af_teardown(m4af_writer_t **ctx); + +int m4af_begin_write(m4af_writer_t *ctx); + +int m4af_finalize(m4af_writer_t *ctx); + +/* can be called before m4af_write_sample() */ +void m4af_set_fixed_frame_duration(m4af_writer_t *ctx, int track_idx, + uint32_t length); + +/* can be called between mfa4_begin_write() and m4af_finalize() */ +int m4af_write_sample(m4af_writer_t *ctx, int track_idx, const void *data, + uint32_t size, uint32_t duration); + + +/* the following can be called at anytime before m4af_finalize() */ + +int m4af_decoder_specific_info(m4af_writer_t *ctx, int track_idx, + uint8_t *data, uint32_t size); + +void m4af_set_priming(m4af_writer_t *ctx, int track_idx, + uint32_t encoder_delay, uint32_t padding); + +int m4af_add_itmf_long_tag(m4af_writer_t *ctx, const char *name, + const char *data); + +int m4af_add_itmf_short_tag(m4af_writer_t *ctx, uint32_t type, + uint32_t type_code, const void *data, + uint32_t data_size); + +int m4af_add_itmf_string_tag(m4af_writer_t *ctx, uint32_t type, + const char *data); + +int m4af_add_itmf_int8_tag(m4af_writer_t *ctx, uint32_t type, int value); + +int m4af_add_itmf_int16_tag(m4af_writer_t *ctx, uint32_t type, int value); + +int m4af_add_itmf_int32_tag(m4af_writer_t *ctx, uint32_t type, int value); + +int m4af_add_itmf_track_tag(m4af_writer_t *ctx, int track, int total); + +int m4af_add_itmf_disk_tag(m4af_writer_t *ctx, int disk, int total); + +int m4af_add_itmf_genre_tag(m4af_writer_t *ctx, int genre); + +#endif diff --git a/src/m4af_endian.h b/src/m4af_endian.h new file mode 100644 index 0000000..bed407d --- /dev/null +++ b/src/m4af_endian.h @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2013 nu774 + * For conditions of distribution and use, see copyright notice in COPYING + */ +#ifndef M4AF_ENDIAN_H +#define M4AF_ENDIAN_H + +#if HAVE_CONFIG_H +# include "config.h" +#endif +#if HAVE_STDINT_H +# include +#endif + +#if HAVE_ENDIAN_H +# include +# define m4af_htob16(x) htobe16(x) +# define m4af_htob32(x) htobe32(x) +# define m4af_htob64(x) htobe64(x) +# define m4af_btoh16(x) be16toh(x) +# define m4af_btoh32(x) be32toh(x) +# define m4af_btoh64(x) be64toh(x) +# define m4af_htol16(x) htole16(x) +# define m4af_htol32(x) htole32(x) +# define m4af_htol64(x) htole64(x) +# define m4af_ltoh16(x) le16toh(x) +# define m4af_ltoh32(x) le32toh(x) +# define m4af_ltoh64(x) le64toh(x) +#elif WORDS_BIGENDIAN +# define m4af_htob16(x) (x) +# define m4af_htob32(x) (x) +# define m4af_htob64(x) (x) +# define m4af_btoh16(x) (x) +# define m4af_btoh32(x) (x) +# define m4af_btoh64(x) (x) +# define m4af_ltoh16(x) m4af_swap16(x) +# define m4af_ltoh32(x) m4af_swap32(x) +# define m4af_ltoh64(x) m4af_swap64(x) +# define m4af_htol16(x) m4af_swap16(x) +# define m4af_htol32(x) m4af_swap32(x) +# define m4af_htol64(x) m4af_swap64(x) +#else +# define m4af_htob16(x) m4af_swap16(x) +# define m4af_htob32(x) m4af_swap32(x) +# define m4af_htob64(x) m4af_swap64(x) +# define m4af_btoh16(x) m4af_swap16(x) +# define m4af_btoh32(x) m4af_swap32(x) +# define m4af_btoh64(x) m4af_swap64(x) +# define m4af_ltoh16(x) (x) +# define m4af_ltoh32(x) (x) +# define m4af_ltoh64(x) (x) +# define m4af_htol16(x) (x) +# define m4af_htol32(x) (x) +# define m4af_htol64(x) (x) +#endif + +#if _MSC_VER >= 1400 +# include +# define m4af_swap16(x) _byteswap_ushort(x) +# define m4af_swap32(x) _byteswap_ulong(x) +# define m4af_swap64(x) _byteswap_uint64(x) +#elif HAVE_BYTESWAP_H +# include +# define m4af_swap16(x) bswap_16(x) +# define m4af_swap32(x) bswap_32(x) +# define m4af_swap64(x) bswap_64(x) +#else +static inline uint16_t m4af_swap16(uint16_t x) +{ + return (x >> 8) | (x << 8); +} + +static inline uint32_t m4af_swap32(uint32_t x) +{ + return (m4af_htob16(x) << 16) | m4af_htob16(x >> 16); +} + +static inline uint64_t m4af_swap64(uint64_t x) +{ + return ((uint64_t)m4af_htob32(x) << 32) | m4af_htob32(x >> 32); +} +#endif + +#endif /* M4AF_ENDIAN_H */ + diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..fe4653e --- /dev/null +++ b/src/main.c @@ -0,0 +1,522 @@ +/* + * Copyright (C) 2013 nu774 + * For conditions of distribution and use, see copyright notice in COPYING + */ +#if HAVE_CONFIG_H +# include "config.h" +#endif +#if HAVE_STDINT_H +# include +#endif +#include +#include +#include +#include +#include +#include +#include "compat.h" +#include "wav_reader.h" +#include "aacenc.h" +#include "m4af.h" +#include "progress.h" +#include "version.h" + +#define PROGNAME "fdkaac" + +static +int read_callback(void *cookie, void *data, uint32_t size) +{ + return fread(data, 1, size, (FILE*)cookie); +} + +static +int write_callback(void *cookie, const void *data, uint32_t size) +{ + return fwrite(data, 1, size, (FILE*)cookie); +} + +static +int seek_callback(void *cookie, int64_t off, int whence) +{ + return fseeko((FILE*)cookie, off, whence); +} + +static +int64_t tell_callback(void *cookie) +{ + return ftello((FILE*)cookie); +} + +static +void usage(void) +{ + printf( +PROGNAME " %s\n" +"Usage: " PROGNAME " [options] input_file\n" +"Options:\n" +" -h, --help Print this help message\n" +" -p, --profile Profile (audio object type)\n" +" 2: MPEG-4 AAC LC (default)\n" +" 5: MPEG-4 HE-AAC (SBR)\n" +" 29: MPEG-4 HE-AAC v2 (SBR+PS)\n" +" 23: MPEG-4 AAC LD\n" +" 39: MPEG-4 AAC ELD\n" +" 129: MPEG-2 AAC LC\n" +" 132: MPEG-2 HE-AAC (SBR)\n" +" 156: MPEG-2 HE-AAC v2 (SBR+PS)\n" +" -b, --bitrate Bitrate in bits per seconds (for CBR)\n" +" -m, --bitrate-mode Bitrate configuration\n" +" 0: CBR (default)\n" +" 1-5: VBR\n" +" (VBR mode is not officially supported, and\n" +" works only on a certain combination of\n" +" parameter settings, sample rate, and\n" +" channel configuration)\n" +" -w, --bandwidth Frequency bandwidth in Hz (AAC LC only)\n" +" -a, --afterurner Afterburner\n" +" 0: Off\n" +" 1: On(default)\n" +" -L, --lowdelay-sbr Enable ELD-SBR (AAC ELD only)\n" +" -s, --sbr-signaling SBR signaling mode\n" +" 0: Implicit, backward compatible(default)\n" +" 1: Explicit SBR and implicit PS\n" +" 2: Explicit hierarchical signaling\n" +" -f, --transport-format Transport format\n" +" 0: RAW (default, muxed into M4A)\n" +" 1: ADIF\n" +" 2: ADTS\n" +" 6: LATM MCP=1\n" +" 7: LATM MCP=0\n" +" 10: LOAS/LATM (LATM within LOAS)\n" +" -c, --adts-crc-check Add CRC protection on ADTS header\n" +" -h, --header-period StreamMuxConfig/PCE repetition period in\n" +" transport layer\n" +"\n" +" -o Output filename\n" +" --ignore-length Ignore length of WAV header\n" +"\n" +"Tagging options:\n" +" --title \n" +" --artist \n" +" --album \n" +" --genre \n" +" --date \n" +" --composer \n" +" --grouping \n" +" --comment \n" +" --album-artist \n" +" --track \n" +" --disk \n" +" --tempo \n" + , fdkaac_version); +} + +typedef struct aacenc_tag_entry_t { + uint32_t tag; + const char *data; +} aacenc_tag_entry_t; + +typedef struct aacenc_param_ex_t { + AACENC_PARAMS + + char *input_filename; + char *output_filename; + unsigned ignore_length; + + aacenc_tag_entry_t *tag_table; + unsigned tag_count; + unsigned tag_table_capacity; +} aacenc_param_ex_t; + +static +int parse_options(int argc, char **argv, aacenc_param_ex_t *params) +{ + int ch; + unsigned n; + aacenc_tag_entry_t *tag; + + static struct option long_options[] = { + { "help", no_argument, 0, 'h' }, + { "profile", required_argument, 0, 'p' }, + { "bitrate", required_argument, 0, 'b' }, + { "biterate-mode", required_argument, 0, 'm' }, + { "bandwidth", required_argument, 0, 'w' }, + { "afterburner", required_argument, 0, 'a' }, + { "lowdelay-sbr", no_argument, 0, 'L' }, + { "sbr-signaling", required_argument, 0, 's' }, + { "transport-format", required_argument, 0, 'f' }, + { "adts-crc-check", no_argument, 0, 'c' }, + { "header-period", required_argument, 0, 'P' }, + + { "ignore-length", no_argument, 0, 'I' }, + + { "title", required_argument, 0, M4AF_TAG_TITLE }, + { "artist", required_argument, 0, M4AF_TAG_ARTIST }, + { "album", required_argument, 0, M4AF_TAG_ALBUM }, + { "genre", required_argument, 0, M4AF_TAG_GENRE }, + { "date", required_argument, 0, M4AF_TAG_DATE }, + { "composer", required_argument, 0, M4AF_TAG_COMPOSER }, + { "grouping", required_argument, 0, M4AF_TAG_GROUPING }, + { "comment", required_argument, 0, M4AF_TAG_COMMENT }, + { "album-artist", required_argument, 0, M4AF_TAG_ALBUM_ARTIST }, + { "track", required_argument, 0, M4AF_TAG_TRACK }, + { "disk", required_argument, 0, M4AF_TAG_DISK }, + { "tempo", required_argument, 0, M4AF_TAG_TEMPO }, + }; + params->afterburner = 1; + + aacenc_getmainargs(&argc, &argv); + while ((ch = getopt_long(argc, argv, "hp:b:m:w:a:Ls:f:cP:Io:", + long_options, 0)) != EOF) { + switch (ch) { + case 'h': + return usage(), -1; + case 'p': + if (sscanf(optarg, "%u", &n) != 1) { + fprintf(stderr, "invalid arg for profile\n"); + return -1; + } + params->profile = n; + break; + case 'b': + if (sscanf(optarg, "%u", &n) != 1) { + fprintf(stderr, "invalid arg for bitrate\n"); + return -1; + } + params->bitrate = n; + break; + case 'm': + if (sscanf(optarg, "%u", &n) != 1 || n > 5) { + fprintf(stderr, "invalid arg for bitrate-mode\n"); + return -1; + } + params->bitrate_mode = n; + break; + case 'w': + if (sscanf(optarg, "%u", &n) != 1) { + fprintf(stderr, "invalid arg for bandwidth\n"); + return -1; + } + params->bandwidth = n; + break; + case 'a': + if (sscanf(optarg, "%u", &n) != 1 || n > 1) { + fprintf(stderr, "invalid arg for afterburner\n"); + return -1; + } + params->afterburner = n; + break; + case 'L': + params->lowdelay_sbr = 1; + break; + case 's': + if (sscanf(optarg, "%u", &n) != 1 || n > 2) { + fprintf(stderr, "invalid arg for sbr-signaling\n"); + return -1; + } + params->sbr_signaling = n; + break; + case 'f': + if (sscanf(optarg, "%u", &n) != 1) { + fprintf(stderr, "invalid arg for transport-format\n"); + return -1; + } + params->transport_format = n; + break; + case 'c': + params->adts_crc_check = 1; + break; + case 'P': + if (sscanf(optarg, "%u", &n) != 1) { + fprintf(stderr, "invalid arg for header-period\n"); + return -1; + } + params->header_period = n; + break; + case 'o': + params->output_filename = optarg; + break; + case 'I': + params->ignore_length = 1; + break; + case M4AF_TAG_TITLE: + case M4AF_TAG_ARTIST: + case M4AF_TAG_ALBUM: + case M4AF_TAG_GENRE: + case M4AF_TAG_DATE: + case M4AF_TAG_COMPOSER: + case M4AF_TAG_GROUPING: + case M4AF_TAG_COMMENT: + case M4AF_TAG_ALBUM_ARTIST: + case M4AF_TAG_TRACK: + case M4AF_TAG_DISK: + case M4AF_TAG_TEMPO: + if (params->tag_count == params->tag_table_capacity) { + unsigned newsize = params->tag_table_capacity; + newsize = newsize ? newsize * 2 : 1; + params->tag_table = + realloc(params->tag_table, + newsize * sizeof(aacenc_tag_entry_t)); + params->tag_table_capacity = newsize; + } + tag = params->tag_table + params->tag_count; + tag->tag = ch; + tag->data = optarg; + params->tag_count++; + break; + default: + return usage(), -1; + } + } + if (argc == optind) + return usage(), -1; + if (!params->bitrate && !params->bitrate_mode) { + fprintf(stderr, "bitrate or bitrate-mode is mandatory\n"); + return -1; + } + if (params->output_filename && !strcmp(params->output_filename, "-") && + !params->transport_format) { + fprintf(stderr, "stdout streaming is not available on M4A output\n"); + return -1; + } + if (params->bitrate && params->bitrate < 10000) + params->bitrate *= 1000; + params->input_filename = argv[optind]; + return 0; +}; + +static +int write_sample(FILE *ofp, m4af_writer_t *m4af, + const void *data, uint32_t size, uint32_t duration) +{ + if (!m4af) { + if (fwrite(data, 1, size, ofp) < 0) { + fprintf(stderr, "ERROR: fwrite(): %s\n", strerror(errno)); + return -1; + } + } else if (m4af_write_sample(m4af, 0, data, size, duration) < 0) { + fprintf(stderr, "ERROR: failed to write m4a sample\n"); + return -1; + } + return 0; +} + +static +int encode(wav_reader_t *wavf, HANDLE_AACENCODER encoder, + uint32_t frame_length, FILE *ofp, m4af_writer_t *m4af) +{ + uint8_t *ibuf = 0; + int16_t *pcmbuf = 0; + uint32_t pcmsize = 0; + uint8_t *obuf = 0; + uint32_t olen; + uint32_t osize = 0; + int nread = 1; + int consumed; + int rc = -1; + int frames_written = 0; + aacenc_progress_t progress = { 0 }; + const pcm_sample_description_t *format = wav_get_format(wavf); + + ibuf = malloc(frame_length * format->bytes_per_frame); + aacenc_progress_init(&progress, wav_get_length(wavf), format->sample_rate); + do { + if (nread) { + if ((nread = wav_read_frames(wavf, ibuf, frame_length)) < 0) { + fprintf(stderr, "ERROR: read failed\n"); + goto END; + } else if (nread > 0) { + if (pcm_convert_to_native_sint16(format, ibuf, nread, + &pcmbuf, &pcmsize) < 0) { + fprintf(stderr, "ERROR: unsupported sample format\n"); + goto END; + } + } + aacenc_progress_update(&progress, wav_get_position(wavf), + format->sample_rate * 2); + } + if ((consumed = aac_encode_frame(encoder, format, pcmbuf, nread, + &obuf, &olen, &osize)) < 0) + goto END; + if (olen > 0) { + if (write_sample(ofp, m4af, obuf, olen, frame_length) < 0) + goto END; + ++frames_written; + } + } while (nread > 0 || olen > 0); + aacenc_progress_finish(&progress, wav_get_position(wavf)); + rc = frames_written; +END: + if (ibuf) free(ibuf); + if (pcmbuf) free(pcmbuf); + if (obuf) free(obuf); + return rc; +} + +static +int finalize_m4a(m4af_writer_t *m4af, const aacenc_param_ex_t *params, + HANDLE_AACENCODER encoder) +{ + unsigned i; + aacenc_tag_entry_t *tag = params->tag_table; + + for (i = 0; i < params->tag_count; ++i, ++tag) { + switch (tag->tag) { + case M4AF_TAG_TRACK: + { + unsigned m, n = 0; + if (sscanf(tag->data, "%u/%u", &m, &n) >= 1) + m4af_add_itmf_track_tag(m4af, m, n); + break; + } + case M4AF_TAG_DISK: + { + unsigned m, n = 0; + if (sscanf(tag->data, "%u/%u", &m, &n) >= 1) + m4af_add_itmf_disk_tag(m4af, m, n); + break; + } + case M4AF_TAG_TEMPO: + { + unsigned n; + if (sscanf(tag->data, "%u", &n) == 1) + m4af_add_itmf_int16_tag(m4af, tag->tag, n); + break; + } + default: + { + char *u8 = aacenc_to_utf8(tag->data); + m4af_add_itmf_string_tag(m4af, tag->tag, u8); + free(u8); + } + } + } + { + char tool_info[256]; + char *p = tool_info; + LIB_INFO *lib_info = 0; + + p += sprintf(p, PROGNAME " %s, ", fdkaac_version); + + lib_info = malloc(FDK_MODULE_LAST * sizeof(LIB_INFO)); + /* XXX: aacEncGetLibInfo() seems buggy and sometimes fails */ + if (aacEncGetLibInfo(lib_info) == AACENC_OK) { + for (i = 0; i < FDK_MODULE_LAST; ++i) + if (lib_info[i].module_id == FDK_AACENC) + break; + p += sprintf(p, "libfdk-aac %s, ", lib_info[i].versionStr); + } + free(lib_info); + if (params->bitrate_mode) + sprintf(p, "VBR mode %d", params->bitrate_mode); + else + sprintf(p, "CBR %dkbps", params->bitrate / 1000); + + m4af_add_itmf_string_tag(m4af, M4AF_TAG_TOOL, tool_info); + } + if (m4af_finalize(m4af) < 0) { + fprintf(stderr, "ERROR: failed to finalize m4a\n"); + return -1; + } + return 0; +} + +int main(int argc, char **argv) +{ + wav_io_context_t wav_io = { read_callback, seek_callback }; + m4af_io_callbacks_t m4af_io = { + write_callback, seek_callback, tell_callback }; + aacenc_param_ex_t params = { 0 }; + + int result = 2; + FILE *ifp = 0; + FILE *ofp = 0; + char *output_filename = 0; + wav_reader_t *wavf = 0; + HANDLE_AACENCODER encoder = 0; + AACENC_InfoStruct aacinfo = { 0 }; + m4af_writer_t *m4af = 0; + const pcm_sample_description_t *sample_format; + int downsampled_timescale = 0; + int frame_count = 0; + + setlocale(LC_CTYPE, ""); + setbuf(stderr, 0); + + if (parse_options(argc, argv, ¶ms) < 0) + return 1; + + if ((ifp = aacenc_fopen(params.input_filename, "rb")) == 0) { + aacenc_fprintf(stderr, "ERROR: %s: %s\n", params.input_filename, + strerror(errno)); + goto END; + } + + if (ifp == stdin) + wav_io.seek = 0; + + if ((wavf = wav_open(&wav_io, ifp, params.ignore_length)) == 0) { + fprintf(stderr, "ERROR: broken / unsupported input file\n"); + goto END; + } + sample_format = wav_get_format(wavf); + + if (aacenc_init(&encoder, (aacenc_param_t*)¶ms, sample_format, + &aacinfo) < 0) + goto END; + + if (!params.output_filename) { + size_t ilen = strlen(params.input_filename); + const char *ext = strrchr(params.input_filename, '.'); + if (ext) ilen = ext - params.input_filename; + output_filename = malloc(ilen + 5); + sprintf(output_filename, "%.*s%s", ilen, params.input_filename, + params.transport_format ? ".aac" : ".m4a"); + params.output_filename = output_filename; + } + + if ((ofp = aacenc_fopen(params.output_filename, "wb")) == 0) { + aacenc_fprintf(stderr, "ERROR: %s: %s\n", params.output_filename, + strerror(errno)); + goto END; + } + if (!params.transport_format) { + uint32_t scale; + unsigned framelen = aacinfo.frameLength; + int sbr_mode = aacenc_is_sbr_active((aacenc_param_t*)¶ms); + int sig_mode = aacEncoder_GetParam(encoder, AACENC_SIGNALING_MODE); + if (sbr_mode && !sig_mode) + downsampled_timescale = 1; + scale = sample_format->sample_rate >> downsampled_timescale; + if ((m4af = m4af_create(M4AF_CODEC_MP4A, scale, &m4af_io, ofp)) < 0) + goto END; + m4af_decoder_specific_info(m4af, 0, aacinfo.confBuf, aacinfo.confSize); + m4af_set_fixed_frame_duration(m4af, 0, + framelen >> downsampled_timescale); + m4af_begin_write(m4af); + } + frame_count = encode(wavf, encoder, aacinfo.frameLength, ofp, m4af); + if (frame_count < 0) + goto END; + if (m4af) { + uint32_t delay = aacinfo.encoderDelay; + int64_t frames_read = wav_get_position(wavf); + uint32_t padding = frame_count * aacinfo.frameLength + - frames_read - aacinfo.encoderDelay; + m4af_set_priming(m4af, 0, delay >> downsampled_timescale, + padding >> downsampled_timescale); + if (finalize_m4a(m4af, ¶ms, encoder) < 0) + goto END; + } + result = 0; +END: + if (wavf) wav_teardown(&wavf); + if (ifp) fclose(ifp); + if (m4af) m4af_teardown(&m4af); + if (ofp) fclose(ofp); + if (encoder) aacEncClose(&encoder); + if (output_filename) free(output_filename); + if (params.tag_table) free(params.tag_table); + + return result; +} diff --git a/src/progress.c b/src/progress.c new file mode 100644 index 0000000..c1c80b6 --- /dev/null +++ b/src/progress.c @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2013 nu774 + * For conditions of distribution and use, see copyright notice in COPYING + */ +#if HAVE_CONFIG_H +# include "config.h" +#endif +#include +#include +#if HAVE_STDINT_H +# include +#endif +#if HAVE_INTTYPES_H +# include +#elif defined _MSC_VER +# define PRId64 "I64d" +#endif +#include "compat.h" +#include "progress.h" + +static +void seconds_to_hms(double seconds, int *h, int *m, int *s, int *millis) +{ + *h = (int)(seconds / 3600.0); + seconds -= *h * 3600; + *m = (int)(seconds / 60.0); + seconds -= *m * 60; + *s = (int)seconds; + *millis = (int)((seconds - *s) * 1000.0 + 0.5); +} + +static +void print_seconds(FILE *fp, double seconds) +{ + int h, m, s, millis; + seconds_to_hms(seconds, &h, &m, &s, &millis); + if (h) + fprintf(stderr, "%d:%02d:%02d.%03d", h, m, s, millis); + else + fprintf(stderr, "%02d:%02d.%03d", m, s, millis); +} + +void aacenc_progress_init(aacenc_progress_t *progress, int64_t total, + int32_t timescale) +{ + progress->start = aacenc_timer(); + progress->timescale = timescale; + progress->total = total; +} + +void aacenc_progress_update(aacenc_progress_t *progress, int64_t current, + int period) +{ + int percent = 100.0 * current / progress->total + .5; + double seconds = current / progress->timescale; + double ellapsed = (aacenc_timer() - progress->start) / 1000.0; + double eta = ellapsed * (progress->total / (double)current - 1.0); + double speed = ellapsed ? seconds / ellapsed : 0.0; + if (current < progress->processed + period) + return; + + if (progress->total == INT64_MAX) { + putc('\r', stderr); + print_seconds(stderr, seconds); + fprintf(stderr, " (%.0fx) ", speed); + } else { + fprintf(stderr, "\r[%d%%] ", percent); + print_seconds(stderr, seconds); + putc('/', stderr); + print_seconds(stderr, progress->total / progress->timescale); + fprintf(stderr, " (%.0fx), ETA ", speed); + print_seconds(stderr, eta); + fputs(" ", stderr); + } + progress->processed = current; +} + +void aacenc_progress_finish(aacenc_progress_t *progress, int64_t current) +{ + double ellapsed = (aacenc_timer() - progress->start) / 1000.0; + aacenc_progress_update(progress, current, 0); + if (progress->total == INT64_MAX) + fprintf(stderr, "\n%" PRId64 "samples processed in ", current); + else + fprintf(stderr, "\n%" PRId64 "/%" PRId64 " samples processed in ", + current, progress->total); + print_seconds(stderr, ellapsed); + putc('\n', stderr); +} diff --git a/src/progress.h b/src/progress.h new file mode 100644 index 0000000..527da91 --- /dev/null +++ b/src/progress.h @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2013 nu774 + * For conditions of distribution and use, see copyright notice in COPYING + */ +#ifndef PROGRESS_H +#define PROGRESS_H + +typedef struct aacenc_progress_t { + double start; + double timescale; + int64_t total; + int64_t processed; +} aacenc_progress_t; + +void aacenc_progress_init(aacenc_progress_t *progress, int64_t total, + int32_t timescale); +void aacenc_progress_update(aacenc_progress_t *progress, int64_t current, + int period); +void aacenc_progress_finish(aacenc_progress_t *progress, int64_t current); + +#endif diff --git a/src/wav_reader.c b/src/wav_reader.c new file mode 100644 index 0000000..460225e --- /dev/null +++ b/src/wav_reader.c @@ -0,0 +1,333 @@ +/* + * Copyright (C) 2013 nu774 + * For conditions of distribution and use, see copyright notice in COPYING + */ +#if HAVE_CONFIG_H +# include "config.h" +#endif + +#if HAVE_STDINT_H +# include +#endif + +#include +#include +#include +#include +#include "wav_reader.h" +#include "m4af_endian.h" + +#define RIFF_FOURCC(a,b,c,d) ((a)|((b)<<8)|((c)<<16)|((d)<<24)) + +#define TRY_IO(expr) \ + do { \ + if (expr) \ + goto FAIL; \ + } while (0) + +#define ASSERT_FORMAT(ctx, expr) \ + do { \ + if (!expr) { \ + if (!ctx->last_error) \ + ctx->last_error = WAV_INVALID_FORMAT; \ + goto FAIL;\ + } \ + } while (0) + +struct wav_reader_t { + pcm_sample_description_t sample_format; + int64_t length; + int64_t position; + int ignore_length; + int last_error; + wav_io_context_t io; + void *io_cookie; +}; + +static const uint8_t WAV_GUID_PCM[] = { + 1, 0, 0, 0, 0, 0, 0x10, 0, 0x80, 0, 0, 0xaa, 0, 0x38, 0x9b, 0x71 +}; +static const uint8_t WAV_GUID_FLOAT[] = { + 3, 0, 0, 0, 0, 0, 0x10, 0, 0x80, 0, 0, 0xaa, 0, 0x38, 0x9b, 0x71 +}; + +const pcm_sample_description_t *wav_get_format(wav_reader_t *reader) +{ + return &reader->sample_format; +} + +int64_t wav_get_length(wav_reader_t *reader) +{ + return reader->length; +} + +int64_t wav_get_position(wav_reader_t *reader) +{ + return reader->position; +} + +void wav_teardown(wav_reader_t **reader) +{ + free(*reader); + *reader = 0; +} + +static +int riff_read(wav_reader_t *reader, void *buffer, uint32_t size) +{ + int rc; + uint32_t count = 0; + + if (reader->last_error) + return -1; + do { + rc = reader->io.read(reader->io_cookie, buffer, size - count); + if (rc > 0) + count += rc; + else if (rc < 0) + reader->last_error = WAV_IO_ERROR; + } while (rc > 0 && count < size); + return count > 0 ? count : rc; +} + +static +int riff_skip(wav_reader_t *reader, uint32_t count) +{ + char buff[8192]; + int rc; + + if (reader->last_error) + return -1; + if (count == 0) + return 0; + if (reader->io.seek && + reader->io.seek(reader->io_cookie, count, SEEK_CUR) >= 0) + return 0; + + do { + if ((rc = riff_read(reader, buff, count > 8192 ? 8192 : count)) > 0) + count -= rc; + } while (rc > 0 && count > 0); + + if (count > 0) + reader->last_error = WAV_IO_ERROR; + return reader->last_error ? -1 : 0; +} + +static +int riff_read16(wav_reader_t *reader, uint16_t *value) +{ + TRY_IO(riff_read(reader, value, 2) != 2); + *value = m4af_ltoh16(*value); + return 0; +FAIL: + return -1; +} + +static +int riff_read32(wav_reader_t *reader, uint32_t *value) +{ + TRY_IO(riff_read(reader, value, 4) != 4); + *value = m4af_ltoh32(*value); + return 0; +FAIL: + return -1; +} + +static +int riff_read64(wav_reader_t *reader, uint64_t *value) +{ + TRY_IO(riff_read(reader, value, 8) != 8); + *value = m4af_ltoh64(*value); + return 0; +FAIL: + return -1; +} + +static +int riff_scan(wav_reader_t *reader, const char *fmt, ...) +{ + int c, count = 0; + va_list ap; + + va_start(ap, fmt); + while ((c = *fmt++)) { + switch (c) { + case 'S': + TRY_IO(riff_read16(reader, va_arg(ap, uint16_t*))); + ++count; + break; + case 'L': + TRY_IO(riff_read32(reader, va_arg(ap, uint32_t*))); + ++count; + break; + case 'Q': + TRY_IO(riff_read64(reader, va_arg(ap, uint64_t*))); + ++count; + break; + } + } +FAIL: + va_end(ap); + return count; +} + +static +uint32_t riff_next_chunk(wav_reader_t *reader, uint32_t *chunk_size) +{ + uint32_t fcc; + if (riff_scan(reader, "LL", &fcc, chunk_size) == 2) + return fcc; + return 0; +} + +int wav_read_frames(wav_reader_t *reader, void *buffer, unsigned nframes) +{ + int rc; + unsigned nbytes; + + if (!reader->ignore_length && nframes > reader->length - reader->position) + nframes = reader->length - reader->position; + nbytes = nframes * reader->sample_format.bytes_per_frame; + if (nbytes) { + if ((rc = riff_read(reader, buffer, nbytes)) < 0) + return -1; + nframes = rc / reader->sample_format.bytes_per_frame; + reader->position += nframes; + } + return nframes; +} + +static +int riff_ds64(wav_reader_t *reader, int64_t *length) +{ + uint32_t fcc, chunk_size, table_size; + uint64_t riff_size, sample_count; + + fcc = riff_next_chunk(reader, &chunk_size); + ASSERT_FORMAT(reader, + fcc == RIFF_FOURCC('d','s','6','4') && chunk_size >= 28); + TRY_IO(riff_scan(reader, "QQQL", + &riff_size, length, &sample_count, &table_size) != 4); + if (table_size == 0) + return 0; + reader->last_error = WAV_UNSUPPORTED_FORMAT; +FAIL: + return -1; +} + +static +int wav_fmt(wav_reader_t *reader, uint32_t size) +{ + uint16_t wFormatTag, nChannels, nBlockAlign, wBitsPerSample, cbSize; + uint32_t nSamplesPerSec, nAvgBytesPerSec, dwChannelMask = 0; + uint16_t wValidBitsPerSample; + uint8_t guid[16]; + int is_float = 0; + + ASSERT_FORMAT(reader, size >= 16); + TRY_IO(riff_scan(reader, "SSLLSS", &wFormatTag, &nChannels, + &nSamplesPerSec, &nAvgBytesPerSec, &nBlockAlign, + &wBitsPerSample) != 6); + wValidBitsPerSample = wBitsPerSample; + + if (wFormatTag != 1 && wFormatTag != 3 && wFormatTag != 0xfffe) { + reader->last_error = WAV_UNSUPPORTED_FORMAT; + goto FAIL; + } + ASSERT_FORMAT(reader, + nChannels && nSamplesPerSec && nAvgBytesPerSec && + nBlockAlign && wBitsPerSample && !(wBitsPerSample & 7) && + nBlockAlign == nChannels * wBitsPerSample / 8); + if (wFormatTag == 3) + is_float = 1; + + if (wFormatTag != 0xfffe) + TRY_IO(riff_skip(reader, (size - 15) & ~1)); + else { + ASSERT_FORMAT(reader, size >= 40); + TRY_IO(riff_scan(reader, "SSL", + &cbSize, &wValidBitsPerSample, &dwChannelMask) != 3); + TRY_IO(riff_read(reader, guid, 16) != 16); + + if (memcmp(guid, WAV_GUID_FLOAT, 16) == 0) + is_float = 1; + else if (memcmp(guid, WAV_GUID_PCM, 16) != 0) { + reader->last_error = WAV_UNSUPPORTED_FORMAT; + goto FAIL; + } + ASSERT_FORMAT(reader, + wValidBitsPerSample && + wValidBitsPerSample <= wBitsPerSample); + TRY_IO(riff_skip(reader, (size - 39) & ~1)); + } + reader->sample_format.sample_rate = nSamplesPerSec; + reader->sample_format.bits_per_channel = wValidBitsPerSample; + reader->sample_format.bytes_per_frame = nBlockAlign; + reader->sample_format.channels_per_frame = nChannels; + reader->sample_format.channel_mask = dwChannelMask; + if (is_float) + reader->sample_format.sample_type = PCM_TYPE_FLOAT; + else if (wBitsPerSample == 8) + reader->sample_format.sample_type = PCM_TYPE_UINT; + else + reader->sample_format.sample_type = PCM_TYPE_SINT; + return 0; +FAIL: + return -1; +} + +static +int wav_parse(wav_reader_t *reader, int64_t *data_length) +{ + uint32_t container, fcc, chunk_size; + + *data_length = 0; + container = riff_next_chunk(reader, &chunk_size); + if (container != RIFF_FOURCC('R','I','F','F') && + container != RIFF_FOURCC('R','F','6','4')) + goto FAIL; + TRY_IO(riff_read32(reader, &fcc)); + if (fcc != RIFF_FOURCC('W','A','V','E')) + goto FAIL; + if (container == RIFF_FOURCC('R','F','6','4')) + riff_ds64(reader, data_length); + while ((fcc = riff_next_chunk(reader, &chunk_size)) != 0) { + if (fcc == RIFF_FOURCC('f','m','t',' ')) { + if (wav_fmt(reader, chunk_size) < 0) + goto FAIL; + } else if (fcc == RIFF_FOURCC('d','a','t','a')) { + if (container == RIFF_FOURCC('R','I','F','F')) + *data_length = chunk_size; + break; + } else + TRY_IO(riff_skip(reader, (chunk_size + 1) & ~1)); + } + if (fcc == RIFF_FOURCC('d','a','t','a')) + return 0; +FAIL: + return -1; +} + +wav_reader_t *wav_open(wav_io_context_t *io_ctx, void *io_cookie, + int ignore_length) +{ + wav_reader_t *reader; + int64_t data_length; + + if ((reader = calloc(1, sizeof(wav_reader_t))) == 0) + return 0; + memcpy(&reader->io, io_ctx, sizeof(wav_io_context_t)); + reader->io_cookie = io_cookie; + reader->ignore_length = ignore_length; + if (wav_parse(reader, &data_length) < 0) { + free(reader); + return 0; + } + if (ignore_length || !data_length || + data_length % reader->sample_format.bytes_per_frame != 0) + reader->length = INT64_MAX; + else + reader->length = data_length / reader->sample_format.bytes_per_frame; + return reader; +} diff --git a/src/wav_reader.h b/src/wav_reader.h new file mode 100644 index 0000000..9335e1d --- /dev/null +++ b/src/wav_reader.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2013 nu774 + * For conditions of distribution and use, see copyright notice in COPYING + */ +#ifndef WAV_READER_H +#define WAV_READER_H + +#include "lpcm.h" + +enum wav_error_code { + WAV_IO_ERROR = 1, + WAV_NO_MEMORY, + WAV_INVALID_FORMAT, + WAV_UNSUPPORTED_FORMAT +}; + +typedef int (*wav_read_callback)(void *cookie, void *data, uint32_t size); +typedef int (*wav_seek_callback)(void *cookie, int64_t off, int whence); + +typedef struct wav_io_context_t { + wav_read_callback read; + wav_seek_callback seek; +} wav_io_context_t; + +typedef struct wav_reader_t wav_reader_t; + +wav_reader_t *wav_open(wav_io_context_t *io_ctx, void *io_cookie, + int ignore_length); +const pcm_sample_description_t *wav_get_format(wav_reader_t *reader); +int wav_read_frames(wav_reader_t *reader, void *buffer, unsigned nframes); +int64_t wav_get_length(wav_reader_t *reader); +int64_t wav_get_position(wav_reader_t *reader); +void wav_teardown(wav_reader_t **reader); + +#endif diff --git a/version.h b/version.h new file mode 100644 index 0000000..dd49c39 --- /dev/null +++ b/version.h @@ -0,0 +1,4 @@ +#ifndef VERSION_H +#define VERSION_H +const char *fdkaac_version = "0.0.1"; +#endif