From 48e2f01c5653c8f643b2a763e1cfa2efc1eccd60 Mon Sep 17 00:00:00 2001 From: nu774 Date: Sat, 5 Jan 2013 01:10:05 +0900 Subject: [PATCH 1/1] initial commit --- AUTHORS | 0 COPYING | 17 + ChangeLog | 0 INSTALL | 370 ++++++++++++++ Makefile.am | 25 + NEWS | 0 README | 14 + config.rpath | 0 configure.ac | 52 ++ m4/.gitkeep | 0 missings/getopt.c | 630 +++++++++++++++++++++++ missings/getopt.h | 90 ++++ src/aacenc.c | 172 +++++++ src/aacenc.h | 38 ++ src/compat.h | 24 + src/compat_posix.c | 138 +++++ src/compat_win32.c | 128 +++++ src/lpcm.c | 234 +++++++++ src/lpcm.h | 37 ++ src/m4af.c | 1188 ++++++++++++++++++++++++++++++++++++++++++++ src/m4af.h | 114 +++++ src/m4af_endian.h | 85 ++++ src/main.c | 522 +++++++++++++++++++ src/progress.c | 89 ++++ src/progress.h | 21 + src/wav_reader.c | 333 +++++++++++++ src/wav_reader.h | 35 ++ version.h | 4 + 28 files changed, 4360 insertions(+) create mode 100644 AUTHORS create mode 100644 COPYING create mode 100644 ChangeLog create mode 100644 INSTALL create mode 100644 Makefile.am create mode 100644 NEWS create mode 100644 README create mode 100644 config.rpath create mode 100644 configure.ac create mode 100644 m4/.gitkeep create mode 100644 missings/getopt.c create mode 100644 missings/getopt.h create mode 100644 src/aacenc.c create mode 100644 src/aacenc.h create mode 100644 src/compat.h create mode 100644 src/compat_posix.c create mode 100644 src/compat_win32.c create mode 100644 src/lpcm.c create mode 100644 src/lpcm.h create mode 100644 src/m4af.c create mode 100644 src/m4af.h create mode 100644 src/m4af_endian.h create mode 100644 src/main.c create mode 100644 src/progress.c create mode 100644 src/progress.h create mode 100644 src/wav_reader.c create mode 100644 src/wav_reader.h create mode 100644 version.h 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 -- 2.39.2