/*
 * FAAC - Freeware Advanced Audio Coder
 * Copyright (C) 2001 Menno Bakker
 * Copyright (C) 2002-2017 Krzysztof Nikiel
 * Copyright (C) 2004 Dan Villiom P. Christiansen
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 */


#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#ifdef _WIN32
#include <windows.h>
#include <fcntl.h>
#else
#include <signal.h>
#endif

/* the BSD derivatives don't define __unix__ */
#if defined(__APPLE__) || defined(__NetBSD__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__bsdi__)
#define __unix__
#endif

#ifdef __unix__
#include <sys/resource.h>
#include <unistd.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <time.h>
#include <sys/stat.h>
#include <errno.h>

#ifdef HAVE_GETOPT_H
# include <getopt.h>
#else
# include "getopt.h"
# include "getopt.c"
#endif

#include "mp4write.h"

#ifdef _WIN32
# undef stderr
# define stderr stdout
#endif

#include "input.h"

#include <faac.h>

#define FALSE 0
#define TRUE 1

enum flags
{
    SHORTCTL_FLAG = 300,
    MPEGVERS_FLAG,
    ARTIST_FLAG,
    ARTIST_SORT_FLAG,
    TITLE_FLAG,
    GENRE_FLAG,
    ALBUM_FLAG,
    ALBUM_SORT_FLAG,
    ALBUM_ARTIST_FLAG,
    ALBUM_ARTIST_SORT_FLAG,
    TRACK_FLAG,
    DISC_FLAG,
    YEAR_FLAG,
    COVER_ART_FLAG,
    COMMENT_FLAG,
    WRITER_FLAG,
    WRITER_SORT_FLAG,
    TAG_FLAG,
    CREATION_TIME_FLAG,
    HELP_QUAL,
    HELP_IO,
    HELP_MP4,
    HELP_ADVANCED,
    OPT_JOINT,
    OPT_PNS,
    OBJTYPE_FLAG
};

typedef struct {
    char *shorthelp;
    char *longhelp;
} help_t;

const char *usage =
    "Usage: %s [options] infile\n\n";

static help_t help_qual[] = {
    {"-q <quality>\tSet encoding quality.\n",
    "\t\tSet default variable bitrate (VBR) quantizer quality in percent.\n"
    "\t\tmax. 5000, min. 10.\n"
    "\t\tdefault: 100, averages at approx. 120 kbps VBR for a normal\n"
    "\t\tstereo input file with 16 bit and 44.1 kHz sample rate\n"
    },
    {"-b <bitrate>\tSet average bitrate to x kbps. (ABR)\n",
    "\t\tSet average bitrate (ABR) to approximately <bitrate> kbps.\n"
    "\t\tmax. ~500 (stereo)\n"},
    {"-c <freq>\tSet the bandwidth in Hz.\n",
    "\t\tThe actual frequency is adjusted to maximize upper spectral band\n"
    "\t\tusage.\n"},
    {NULL, NULL}
};

static help_t help_io[] = {
    {"-o <filename>\tSet output file to X (only for one input file)\n",
    "\t\tonly for one input file; you can use *.aac, *.mp4, *.m4a or\n"
    "\t\t*.m4b as file extension, and the file format will be set\n"
    "\t\tautomatically to ADTS or MP4).\n"},
    {"-\t\tUse stdin/stdout\n",
    "\t\tIf you simply use a hyphen/minus sign instead\n"
    "\t\tof a filename, FAAC can encode directly from stdin,\n"
    "\t\tthus enabling piping from other applications and utilities. The\n"
    "\t\tsame works for stdout as well, so FAAC can pipe its output to\n"
    "\t\tother apps such as a server.\n"},
    {"-v <verbose>\t\tverbosity level (-v0 is quiet mode)\n", NULL},
    {"-r\t\tUse RAW AAC output file.\n",
    "\t\tGenerate raw AAC bitstream (i.e. without any headers).\n"
    "\t\tNot advised!!!, RAW AAC files are practically useless!!!\n"},
    {"-P\t\tRaw PCM input mode (default 44100Hz 16bit stereo).\n",
    "\t\tRaw PCM input mode (default: off, i.e. expecting a WAV header;\n"
    "\t\tnecessary for input files or bitstreams without a header; using\n"
    "\t\tonly -P assumes the default values for -R, -B and -C in the\n"
    "\t\tinput file).\n"},
    {"-R <samplerate>\tRaw PCM input rate.\n",
    "\t\tRaw PCM input sample rate in Hz (default: 44100 Hz, max. 96 kHz)\n"},
    {"-B <samplebits>\tRaw PCM input sample size (8, 16 (default), 24 or 32bits).\n",
    "\t\tRaw PCM input sample size (default: 16, also possible 8, 24, 32\n"
    "\t\tbit fixed or float input).\n"},
    {"-C <channels>\tRaw PCM input channels.\n",
    "\t\tRaw PCM input channels (default: 2, max. 8).\n"},
    {"-X\t\tRaw PCM swap input bytes\n",
    "\t\tRaw PCM swap input bytes (default: bigendian).\n"},
    {"-I <C[,LFE]>\tInput channel config, default is 3,4 (Center third, LF fourth)\n",
    "\t\tInput multichannel configuration (default: 3,4 which means\n"
    "\t\tCenter is third and LFE is fourth like in 5.1 WAV, so you only\n"
    "\t\thave to specify a different position of these two mono channels\n"
    "\t\tin your multichannel input files if they haven't been reordered\n"
    "\t\talready).\n"},
    {"--ignorelength\tIgnore wav length from header (useful with files over 4 GB)\n", NULL},
    {"--overwrite\t\tOverwrite existing output file", NULL},
    {NULL, NULL}
};

static help_t help_mp4[] = {
    {"-w\tWrap AAC data in MP4 container (default for *.mp4, *.m4a and *.m4b)\n", NULL},
    {"--tag <tagname,tagvalue> Add named tag (iTunes '----')\n", NULL},
    {"--artist <name>\tSet artist name\n", NULL},
    {"--artistsort <name>\tSet artist sort order\n", NULL},
    {"--composer <name>\tSet composer name\n", NULL},
    {"--composersort <name>\tSet composer sort order\n", NULL},
    {"--title <name>\tSet title/track name\n", NULL},
    {"--genre <number>\tSet genre number\n", NULL},
    {"--album <name>\tSet album/performer\n", NULL},
    {"--albumartist <name>\tSet album artist\n", NULL},
    {"--albumartistsort <name>\tSet album artist sort order\n", NULL},
    {"--albumsort <name>\tSet album sort order\n", NULL},
    {"--compilation\tMark as compilation\n", NULL},
    {"--track <number/total>\tSet track number\n", NULL},
    {"--disc <number/total>\tSet disc number\n", NULL},
    {"--year <number>\tSet year\n", NULL},
    {"--cover-art <filename>\tRead cover art from file X\n",
    "\t\tSupported image formats are GIF, JPEG, and PNG.\n"},
    {"--comment <string>\tSet comment\n", NULL},
    {"--creation-time <value>\tSet creation/modification time (auto, now, or timestamp)\n", NULL},
    {NULL, NULL}
};

static help_t help_advanced[] = {
    {"--tns  \tEnable coding of TNS, temporal noise shaping.\n", NULL},
    {"--no-tns\tDisable coding of TNS, temporal noise shaping.\n", NULL},
    {"--joint 0\tDisable joint stereo coding.\n", NULL},
    {"--joint 1\tUse Mid/Side coding.\n", NULL},
    {"--joint 2\tUse Intensity Stereo coding.\n", NULL},
    {"--joint 3\tUse Mixed Mode (dynamic M/S and IS) coding (default).\n", NULL},
    {"--pns <0 .. 10>\tPNS level; 0=disabled.\n", NULL},
    {"--mpeg-vers X\tForce AAC MPEG version, X can be 2 or 4\n", NULL},
    {"--object-type X\tForce AAC object type: lc, he-aac-v1, or auto (default)\n", NULL},
    {"--shortctl X\tEnforce block type (0 = both (default); 1 = no short; 2 = no\n"
    "\t\tlong).\n", NULL},
    {NULL, NULL}
};

static struct {
    int id;
    char *name;
    char *option;
    help_t *help;
} g_help[] = {
    {HELP_QUAL, "Quality-related options", "--help-qual", help_qual},
    {HELP_IO, "Input/output options", "--help-io", help_io},
    {HELP_MP4, "MP4 specific options", "--help-mp4", help_mp4},
    {HELP_ADVANCED, "Advanced options, only for testing purposes", "--help-advanced", help_advanced},
    {0}
};

char *license =
    "\nPlease note that the use of this software may require the payment of patent\n"
    "royalties. You need to consider this issue before you start building derivative\n"
    "works. We are not warranting or indemnifying you in any way for patent\n"
    "royalities! YOU ARE SOLELY RESPONSIBLE FOR YOUR OWN ACTIONS!\n"
    "\n"
    "FAAC is free software, licensed under the GNU Lesser General Public\n"
    "License (LGPL), version 2.1 or later:\n"
    "\n"
    "FAAC - Freeware Advanced Audio Coder\n"
    "Copyright (C) 1999-2001, Menno Bakker\n"
    "Copyright (C) 2002-2017, Krzysztof Nikiel\n"
    "Copyright (C) 2004, Dan Villiom P. Christiansen\n"
    "Copyright (C) 2005-2026, Fabian Greffrath\n"
    "Copyright (C) 2026, Nils Schimmelmann\n"
    "\n"
    "This library is free software; you can redistribute it and/or\n"
    "modify it under the terms of the GNU Lesser General Public\n"
    "License as published by the Free Software Foundation; either\n"
    "version 2.1 of the License, or (at your option) any later version.\n"
    "\n"
    "This library is distributed in the hope that it will be useful,\n"
    "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
    "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n"
    "Lesser General Public License for more details.\n"
    "\n";

#ifndef min
#define min(a,b) ( (a) < (b) ? (a) : (b) )
#endif

/* globals */
char *progName;
#ifndef _WIN32
volatile int running = 1;
#endif

enum container_format
{
    NO_CONTAINER,
    MP4_CONTAINER,
};

#ifndef _WIN32
void signal_handler(int signal)
{
    (void)signal;
    running = 0;
}
#endif

static void help0(help_t *h, int l)
{
    int cnt;

    for (cnt = 0; h[cnt].shorthelp; cnt++)
    {
        printf("    %s", h[cnt].shorthelp);
        if (l && h[cnt].longhelp)
            printf("%s", h[cnt].longhelp);
    }
    printf("\n\n");
}

static void help(int mode)
{
    int cnt;
    static const char *name = "faac";

    printf(usage, name);
    switch (mode)
    {
    case '?':
    case 'h':
    case 'H':
        printf("Help options:\n"
                "\t-h\t\tShort help on using FAAC\n"
                "\t-H\t\tDescription of all options for FAAC.\n"
                "\t--license\tLicense terms for FAAC.\n");
        for (cnt = 0; g_help[cnt].id; cnt++)
            printf("\t%s\t%s\n", g_help[cnt].option, g_help[cnt].name);
        if (mode == 'h')
        {
        for (cnt = 0; cnt < 2; cnt++)
        {
            printf("%s:\n", g_help[cnt].name);
            help0(g_help[cnt].help, 0);
        }
        }
        if (mode == 'H')
        {
        for (cnt = 0; cnt < g_help[cnt].id; cnt++)
        {
            printf("%s:\n", g_help[cnt].name);
            help0(g_help[cnt].help, 1);
        }
        }
        break;
    default:
        for (cnt = 0; g_help[cnt].id; cnt++)
            if (g_help[cnt].id == mode)
            {
                printf("%s:\n", g_help[cnt].name);
                help0(g_help[cnt].help, 1);
                break;
            }
        break;
    }
}

static int check_image_header(const char *buf)
{
    if (!strncmp(buf, "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A", 8))
        return 1;               /* PNG */
    else if (!strncmp(buf, "\xFF\xD8\xFF\xE0", 4) ||
             !strncmp(buf, "\xFF\xD8\xFF\xE1", 4))
        return 1;               /* JPEG */
    else if (!strncmp(buf, "GIF87a", 6) || !strncmp(buf, "GIF89a", 6))
        return 1;               /* GIF */
    else
        return 0;
}

static int *mkChanMap(int channels, int center, int lf)
{
    int *map;
    int inpos;
    int outpos;

    if (!center && !lf)
        return NULL;

    if (channels < 3)
        return NULL;

    if (lf > 0)
        lf--;
    else
        lf = channels - 1;      // default AAC position

    if (center > 0)
        center--;
    else
        center = 0;             // default AAC position

    map = malloc(channels * sizeof(map[0]));
    if (!map) return NULL;
    memset(map, 0, channels * sizeof(map[0]));

    outpos = 0;
    if ((center >= 0) && (center < channels))
        map[outpos++] = center;

    inpos = 0;
    for (; outpos < (channels - 1) && inpos < channels; inpos++)
    {
        if (inpos == center)
            continue;
        if (inpos == lf)
            continue;

        map[outpos++] = inpos;
    }
    if (outpos < channels)
    {
        if ((lf >= 0) && (lf < channels))
            map[outpos] = lf;
        else if (inpos < channels)
            map[outpos] = inpos;
    }

    return map;
}

#define fprintf if(verbose)fprintf

int main(int argc, char *argv[])
{
    int frames, currentFrame;
    faac_encoder *hEncoder = NULL;
    pcmfile_t *infile = NULL;

    unsigned long samplesInput, maxBytesOutput, totalBytesWritten = 0;

    faac_params params;
    faac_status fstatus;
    enum faac_mpeg_version mpegVersion = FAAC_MPEG4;
    enum faac_object_type objectType = FAAC_OBJ_AUTO;
    int jointmode = -1;
    int pnslevel = -1;
    static int useTns = 0;
    enum container_format container = NO_CONTAINER;
    enum faac_stream_format stream = FAAC_STREAM_ADTS;
    int cutOff = -1;
    int bitRate = 0;
    unsigned long quantqual = 0;
    int chanC = 3;
    int chanLF = 4;

    char *audioFileName = NULL;
    char *aacFileName = NULL;
    char *aacFileExt = NULL;
    int aacFileNameGiven = 0;

    float *pcmbuf;
    int *chanmap = NULL;

    unsigned char *bitbuf;
    int samplesRead = 0;
    const char *dieMessage = NULL;

    int rawChans = 0;           // disabled by default
    int rawBits = 16;
    int rawRate = 44100;
    int rawEndian = 1;

    int shortctl = FAAC_SHORTCTL_NORMAL;

    FILE *outfile = NULL;

    unsigned int ntracks = 0, trackno = 0;
    unsigned int ndiscs = 0, discno = 0;
    static int compilation = 0;
    const char *artist = NULL, *artistsort = NULL, *title = NULL,
        *album = NULL, *albumartist = NULL,
        *albumartistsort = NULL, *albumsort = NULL,
        *year = NULL, *comment = NULL, *composer = NULL,
        *composersort = NULL, *tagname = 0, *tagval = 0,
        *creation_time_str = NULL;
    int genre = 0;
    uint8_t *artData = NULL;
    uint64_t artSize = 0;
    uint64_t encoded_samples = 0;
    unsigned int frameSize;
    uint64_t input_samples = 0;
    const char *faac_id_string;
    const char *faac_copyright_string;
    static int ignorelen = 0;
    int verbose = 1;
    static int overwrite = 0;

#ifndef _WIN32
    // install signal handler
    signal(SIGINT, signal_handler);
    signal(SIGTERM, signal_handler);
#endif

    // get faac version
    faac_library_info libinfo = { .struct_size = sizeof(libinfo) };
    faac_get_library_info(&libinfo);
    faac_id_string = libinfo.version;
    faac_copyright_string = libinfo.copyright;
    if (!strcmp(faac_id_string, PACKAGE_VERSION))
    {
        fprintf(stderr, "Freeware Advanced Audio Coder\nFAAC %s\n\n",
                faac_id_string);
    }
    else
    {
        fprintf(stderr, __FILE__ "(%d): wrong libfaac version "
                "(expected %s, found %s)\n", __LINE__,
                PACKAGE_VERSION, faac_id_string);
        return 1;
    }

    /* begin process command line */
    progName = argv[0];
    if (argc < 2)
    {
        help('?');
        return 1;
    }
    while (1)
    {
        static struct option long_options[] = {
            {"help", 0, 0, 'h'},
            {"help-qual", 0, 0, HELP_QUAL},
            {"help-io", 0, 0, HELP_IO},
            {"help-mp4", 0, 0, HELP_MP4},
            {"help-advanced", 0, 0, HELP_ADVANCED},
            {"raw", 0, 0, 'r'},
            {"joint", required_argument, 0, OPT_JOINT},
            {"pns", required_argument, 0, OPT_PNS},
            {"cutoff", 1, 0, 'c'},
            {"quality", 1, 0, 'q'},
            {"pcmraw", 0, 0, 'P'},
            {"pcmsamplerate", 1, 0, 'R'},
            {"pcmsamplebits", 1, 0, 'B'},
            {"pcmchannels", 1, 0, 'C'},
            {"shortctl", 1, 0, SHORTCTL_FLAG},
            {"tns", 0, &useTns, 1},
            {"no-tns", 0, &useTns, 0},
            {"mpeg-version", 1, 0, MPEGVERS_FLAG},
            {"object-type", 1, 0, OBJTYPE_FLAG},
            {"license", 0, 0, 'L'},
            {"createmp4", 0, 0, 'w'},
            {"artist", 1, 0, ARTIST_FLAG},
            {"artistsort", 1, 0, ARTIST_SORT_FLAG},
            {"title", 1, 0, TITLE_FLAG},
            {"album", 1, 0, ALBUM_FLAG},
            {"albumartist", 1, 0, ALBUM_ARTIST_FLAG},
            {"albumartistsort", 1, 0, ALBUM_ARTIST_SORT_FLAG},
            {"albumsort", 1, 0, ALBUM_SORT_FLAG},
            {"track", 1, 0, TRACK_FLAG},
            {"disc", 1, 0, DISC_FLAG},
            {"genre", 1, 0, GENRE_FLAG},
            {"year", 1, 0, YEAR_FLAG},
            {"cover-art", 1, 0, COVER_ART_FLAG},
            {"comment", 1, 0, COMMENT_FLAG},
            {"composer", 1, 0, WRITER_FLAG},
            {"composersort", 1, 0, WRITER_SORT_FLAG},
            {"compilation", 0, &compilation, 1},
            {"pcmswapbytes", 0, 0, 'X'},
            {"ignorelength", 0, &ignorelen, 1},
            {"tag", 1, 0, TAG_FLAG},
            {"overwrite", 0, &overwrite, 1},
            {"creation-time", 1, 0, CREATION_TIME_FLAG},
            {0, 0, 0, 0}
        };
        int c = -1;
        int option_index = 0;

        c = getopt_long(argc, argv, "Hhb:m:o:rnc:q:PR:B:C:I:Xwv:",
                        long_options, &option_index);

        if (c == -1)
            break;

        if (!c)
            continue;

        switch (c)
        {
        case 'o':
            {
                int l = strlen(optarg);
                aacFileName = malloc(l + 1);
                if (!aacFileName)
                {
                    fprintf(stderr, "out of memory\n");
                    return 1;
                }
                memcpy(aacFileName, optarg, l);
                aacFileName[l] = '\0';
                aacFileNameGiven = 1;
            }
            break;
        case 'r':
            {
                stream = FAAC_STREAM_RAW;
                break;
            }
        case 'c':
            {
                unsigned int i;
                if (sscanf(optarg, "%u", &i) > 0)
                {
                    cutOff = i;
                }
                break;
            }
        case 'b':
            {
                unsigned int i;
                if (sscanf(optarg, "%u", &i) > 0)
                {
                    bitRate = 1000 * i;
                }
                break;
            }
        case 'q':
            {
                unsigned int i;
                if (sscanf(optarg, "%u", &i) > 0)
                {
                    if (i > 0)
                        quantqual = i;
                }
                break;
            }
        case 'I':
            sscanf(optarg, "%d,%d", &chanC, &chanLF);
            break;
        case 'P':
            rawChans = 2;       // enable raw input
            break;
        case 'R':
            {
                unsigned int i;
                if (sscanf(optarg, "%u", &i) > 0)
                {
                    rawRate = i;
                    rawChans = (rawChans > 0) ? rawChans : 2;
                }
                break;
            }
        case 'B':
            {
                unsigned int i;
                if (sscanf(optarg, "%u", &i) > 0)
                {
                    if (i > 32)
                        i = 32;
                    if (i < 8)
                        i = 8;
                    rawBits = i;
                    rawChans = (rawChans > 0) ? rawChans : 2;
                }
                break;
            }
        case 'C':
            {
                unsigned int i;
                if (sscanf(optarg, "%u", &i) > 0)
                    rawChans = i;
                break;
            }
        case 'w':
            container = MP4_CONTAINER;
            break;
        case ARTIST_FLAG:
            artist = optarg;
            break;
        case ARTIST_SORT_FLAG:
            artistsort = optarg;
            break;
        case WRITER_FLAG:
            composer = optarg;
            break;
        case WRITER_SORT_FLAG:
            composersort = optarg;
            break;
        case TITLE_FLAG:
            title = optarg;
            break;
        case ALBUM_FLAG:
            album = optarg;
            break;
        case ALBUM_ARTIST_FLAG:
            albumartist = optarg;
            break;
        case ALBUM_ARTIST_SORT_FLAG:
            albumartistsort = optarg;
            break;
        case ALBUM_SORT_FLAG:
            albumsort = optarg;
            break;
        case TRACK_FLAG:
            if (sscanf(optarg, "%u/%u", &trackno, &ntracks) < 1)
                dieMessage = "Wrong track number.\n";
            break;
        case DISC_FLAG:
            if (sscanf(optarg, "%u/%u", &discno, &ndiscs) < 1)
                dieMessage = "Wrong disc number.\n";
            break;
        case GENRE_FLAG:
            genre = atoi(optarg);
            if ((genre < 0) || (genre > 255))
                dieMessage = "Genre number out of range.\n";
            genre++;
            break;
        case YEAR_FLAG:
            year = optarg;
            break;
        case COMMENT_FLAG:
            comment = optarg;
            break;
        case TAG_FLAG:
            tagname = optarg;
            if (!(tagval = strchr(optarg, ',')))
                dieMessage = "Missing tag value.\n";
            else
                *(char *)tagval++ = 0;
            if (!dieMessage && mp4_add_custom_tag(tagname, tagval))
                dieMessage = "Couldn't add tag (out of memory).\n";
            break;
        case CREATION_TIME_FLAG:
            creation_time_str = optarg;
            break;
        case COVER_ART_FLAG:
            {
                FILE *artFile = fopen(optarg, "rb");

                if (artFile)
                {
                    uint64_t r;

                    fseek(artFile, 0, SEEK_END);
                    artSize = ftell(artFile);

                    artData = malloc(artSize);
                    if (!artData)
                    {
                        fprintf(stderr, "out of memory\n");
                        fclose(artFile);
                        return 1;
                    }

                    fseek(artFile, 0, SEEK_SET);
                    clearerr(artFile);

                    r = fread(artData, artSize, 1, artFile);

                    if (r != 1)
                    {
                        dieMessage = "Error reading cover art file!\n";
                        free(artData);
                        artData = NULL;
                    }
                    else if (artSize < 12
                             || !check_image_header((const char *) artData))
                    {
                        /* the above expression checks the image signature */
                        dieMessage = "Unsupported cover image file format!\n";
                        free(artData);
                        artData = NULL;
                    }

                    fclose(artFile);
                }
                else
                {
                    dieMessage = "Error opening cover art file!\n";
                }

                break;
            }
        case SHORTCTL_FLAG:
            shortctl = atoi(optarg);
            break;
        case MPEGVERS_FLAG:
            switch (atoi(optarg))
            {
            case 2:
                mpegVersion = FAAC_MPEG2;
                break;
            case 4:
                mpegVersion = FAAC_MPEG4;
                break;
            default:
                dieMessage = "Unrecognised MPEG version!\n";
            }
            break;
        case OBJTYPE_FLAG:
            if (!strcmp(optarg, "lc"))
                objectType = FAAC_OBJ_LOW;
            else if (!strcmp(optarg, "he-aac-v1"))
                objectType = FAAC_OBJ_HE_AAC_V1;
            else if (!strcmp(optarg, "auto"))
                objectType = FAAC_OBJ_AUTO;
            else
                dieMessage = "Unrecognised object type (use lc, he-aac-v1, or auto)!\n";
            break;
        case 'L':
            fprintf(stderr, "%s", faac_copyright_string);
            dieMessage = license;
            break;
        case 'X':
            rawEndian = 0;
            break;
        case 'v':
            verbose = atoi(optarg);
            break;
        case HELP_QUAL:
        case HELP_IO:
        case HELP_MP4:
        case HELP_ADVANCED:
        case 'H':
        case 'h':
            help(c);
            return 1;
            break;
        case OPT_JOINT:
            jointmode = atoi(optarg);
            break;
        case OPT_PNS:
            pnslevel = atoi(optarg);
            break;
        case '?':
        default:
            help('?');
            return 1;
            break;
        }
    }

    /* check that we have at least one non-option arguments */
    if (!dieMessage && (argc - optind) > 1 && aacFileNameGiven)
        dieMessage =
            "Cannot encode several input files to one output file.\n";

    if (argc - optind < 1 || dieMessage)
    {
        fprintf(stderr, dieMessage, progName, progName, progName, progName);
        return 1;
    }

    while (argc - optind > 0)
    {
        /* get the input file name */
        audioFileName = argv[optind++];
    }

    /* generate the output file name, if necessary */
    if (!aacFileNameGiven)
    {
        char *t = strrchr(audioFileName, '.');
        int l = t ? strlen(audioFileName) - strlen(t) : strlen(audioFileName);

        aacFileExt = container == MP4_CONTAINER ? ".m4a" : ".aac";

        aacFileName = malloc(l + 1 + 4);
        if (!aacFileName)
        {
            fprintf(stderr, "out of memory\n");
            return 1;
        }
        memcpy(aacFileName, audioFileName, l);
        memcpy(aacFileName + l, aacFileExt, 4);
        aacFileName[l + 4] = '\0';
    }
    else
    {
        aacFileExt = strrchr(aacFileName, '.');

        if (aacFileExt
            && (!strcmp(".m4a", aacFileExt) || !strcmp(".m4b", aacFileExt)
                || !strcmp(".mp4", aacFileExt)))
            container = MP4_CONTAINER;
    }

    /* open the audio input file */
    if (rawChans > 0)           // use raw input
    {
        infile = wav_open_read(audioFileName, 1);
        if (infile)
        {
            infile->bigendian = rawEndian;
            infile->channels = rawChans;
            infile->samplebytes = rawBits / 8;
            infile->samplerate = rawRate;
            infile->samples /= (infile->channels * infile->samplebytes);
        }
    }
    else                        // header input
        infile = wav_open_read(audioFileName, 0);

    if (infile == NULL)
    {
        fprintf(stderr, "Couldn't open input file %s\n", audioFileName);
        return 1;
    }

    if (container != MP4_CONTAINER && (ntracks || trackno || artist ||
                                       artistsort || title ||
                                       album || albumartist ||
                                       albumartistsort ||
                                       albumsort || year || artData ||
                                       genre || comment || discno || ndiscs ||
                                       composer || composersort ||
                                       compilation))
    {
        fprintf(stderr, "Metadata requires MP4 output!\n");
        return 1;
    }

    if (container == MP4_CONTAINER)
    {
        mpegVersion = FAAC_MPEG4;
        stream = FAAC_STREAM_RAW;
    }

    if (cutOff <= 0)
    {
        if (cutOff < 0)         // default
            cutOff = 0;
        else                    // disabled
            cutOff = infile->samplerate / 2;
    }
    if (cutOff > (infile->samplerate / 2))
        cutOff = infile->samplerate / 2;

    if (shortctl == FAAC_SHORTCTL_NOSHORT) {
        fprintf(stderr, "disabling short blocks\n");
    } else if (shortctl == FAAC_SHORTCTL_NOLONG) {
        fprintf(stderr, "disabling long blocks\n");
    }

    if (pnslevel > 0 && mpegVersion == FAAC_MPEG2)
    {
        fprintf(stderr, "PNS not allowed in MPEG-2 mode, disabling PNS\n");
        pnslevel = 0;
    }

    /* put the options into the parameter struct and open the encoder */
    faac_params_init(&params);
    params.sample_rate   = infile->samplerate;
    params.num_channels  = infile->channels;
    params.mpeg_version  = mpegVersion;
    params.object_type   = objectType;
    params.joint_mode    = (jointmode >= 0) ? (enum faac_joint_mode)jointmode
                                            : params.joint_mode;
    params.use_lfe       = (infile->channels >= 6);
    params.use_tns       = useTns ? true : false;
    params.short_control = (enum faac_shortctl_mode)shortctl;
    if (pnslevel >= 0)
        params.pns_level = pnslevel;
    if (quantqual > 0)
    {
        params.quant_quality = quantqual;
        params.bit_rate = 0;
    }
    if (bitRate)
        params.bit_rate = bitRate / infile->channels;
    params.bandwidth     = cutOff;
    params.output_format = stream;
    params.input_format  = FAAC_INPUT_FLOAT;

    /* Reject too many channels with a specific message, rather than the generic
     * "invalid argument" that faac_encoder_open would otherwise return. */
    {
        faac_library_info libinfo = { .struct_size = sizeof(libinfo) };
        faac_get_library_info(&libinfo);
        if ((unsigned)infile->channels > libinfo.max_channels)
        {
            fprintf(stderr, "Input file %s has %u channels, but this build of "
                    "libfaac supports at most %u.\n",
                    audioFileName, (unsigned)infile->channels, libinfo.max_channels);
            wav_close(infile);
            return 1;
        }
    }

    fstatus = faac_encoder_open(&params, &hEncoder);
    if (fstatus != FAAC_OK)
    {
        fprintf(stderr, "Couldn't open encoder instance for input file %s: %s\n",
                audioFileName, faac_strerror(fstatus));
        wav_close(infile);
        return 1;
    }

    /* buffer sizes and resolved settings come from the now-configured encoder */
    faac_encoder_info info;
    info.struct_size = sizeof(info);
    faac_encoder_get_info(hEncoder, &info);
    samplesInput   = (unsigned long)info.frame_samples * infile->channels;
    maxBytesOutput = info.max_output_bytes;
    frameSize = samplesInput / infile->channels;
    pcmbuf = (float *) malloc(samplesInput * sizeof(float));
    bitbuf = (unsigned char *) malloc(maxBytesOutput * sizeof(unsigned char));
    if (!pcmbuf || !bitbuf)
    {
        fprintf(stderr, "out of memory\n");
        return 1;
    }
    chanmap = mkChanMap(infile->channels, chanC, chanLF);
    if (chanmap)
    {
        fprintf(stderr, "Remapping input channels: Center=%d, LFE=%d\n",
                chanC, chanLF);
    }

    /* AUTO may have resolved to LC or HE-AAC; read back the decision. */
    objectType = info.object_type;

    /* initialize MP4 creation */
    if (container == MP4_CONTAINER)
    {
        if (!strcmp(aacFileName, "-"))
        {
            fprintf(stderr, "cannot encode MP4 to stdout\n");
            return 1;
        }

        if (mp4_open(aacFileName, overwrite))
        {
            fprintf(stderr, "Couldn't create output file %s\n", aacFileName);
            return 1;
        }

        mp4_set_format(infile->samplerate, infile->channels, infile->samplebytes * 8);
    }
    else
    {
        /* open the aac output file */
        if (!strcmp(aacFileName, "-"))
        {
            outfile = stdout;
        }
        else
        {
            outfile = fopen(aacFileName, "wb");
        }
        if (!outfile)
        {
            fprintf(stderr, "Couldn't create output file %s\n", aacFileName);
            return 1;
        }
    }

    /* report the effective settings the encoder resolved */
    cutOff = info.bandwidth;
    quantqual = info.quant_quality;
    bitRate = info.bit_rate;
    int resolvedPns = info.pns_level;
    if (bitRate)
    {
        fprintf(stderr, "Initial quantization quality: %ld\n", quantqual);
        fprintf(stderr, "Average bitrate: %d kbps/channel\n",
                (bitRate + 500) / 1000);
    }
    else
        fprintf(stderr, "Quantization quality: %ld\n", quantqual);
    fprintf(stderr, "Bandwidth: %d Hz\n", cutOff);
    if (resolvedPns > 0)
        fprintf(stderr, "PNS level: %d\n", resolvedPns);
    fprintf(stderr, "Object type: %s",
            (objectType == FAAC_OBJ_HE_AAC_V1) ? "HE-AAC v1" : "Low Complexity");
    fprintf(stderr, " (MPEG-%d)", (mpegVersion == FAAC_MPEG4) ? 4 : 2);
    if (params.use_tns)
        fprintf(stderr, " + TNS");

    switch(params.joint_mode) {
    case FAAC_JOINT_MS:
        fprintf(stderr, " + M/S");
        break;
    case FAAC_JOINT_IS:
        fprintf(stderr, " + IS");
        break;
    case FAAC_JOINT_MIXED:
        fprintf(stderr, " + Mixed");
        break;
    default:
        break;
    }
    if (resolvedPns > 0)
        fprintf(stderr, " + PNS");
    fprintf(stderr, "\n");

    fprintf(stderr, "Container format: ");
    switch (container)
    {
    case NO_CONTAINER:
        switch (stream)
        {
        case FAAC_STREAM_RAW:
            fprintf(stderr, "Headerless AAC (RAW)\n");
            break;
        case FAAC_STREAM_ADTS:
            fprintf(stderr, "Transport Stream (ADTS)\n");
            break;
        default:
            break;
        }
        break;
    case MP4_CONTAINER:
        fprintf(stderr, "MPEG-4 File Format (MP4)\n");
        break;
    }

    int showcnt = 0;
#ifdef _WIN32
    long begin = GetTickCount();
#endif
    if (infile->samples)
        frames = ((infile->samples + frameSize - 1) / frameSize) + 1;
    else
        frames = 0;
    currentFrame = 0;

    fprintf(stderr, "Encoding %s to %s\n", audioFileName, aacFileName);
    if (frames != 0)
    {
        fprintf(stderr, "         frame         | bitrate | elapsed/estim | "
                "play/CPU | ETA\n");
    }
    else
    {
        fprintf(stderr, "  frame  | elapsed | play/CPU\n");
    }

    /* encoding loop */
#ifdef _WIN32
    for (;;)
#else
    while (running)
#endif
    {
        int bytesWritten;

        if (!ignorelen)
        {
            if (input_samples < (uint64_t)infile->samples || infile->samples == 0)
                samplesRead =
                    wav_read_float32(infile, pcmbuf, samplesInput, chanmap);
            else
                samplesRead = 0;

            if (input_samples + (samplesRead / infile->channels) >
                (uint64_t)infile->samples && infile->samples != 0)
                samplesRead =
                    (infile->samples - input_samples) * infile->channels;
        }
        else
            samplesRead =
                wav_read_float32(infile, pcmbuf, samplesInput, chanmap);

        input_samples += samplesRead / infile->channels;

        /* call the actual encoding routine */
        {
            uint32_t nbytes = 0;
            fstatus = faac_encoder_encode(hEncoder, pcmbuf, (uint32_t)samplesRead,
                                          bitbuf, (uint32_t)maxBytesOutput, &nbytes);
            bytesWritten = (fstatus == FAAC_OK) ? (int)nbytes : -1;
        }

        if (bytesWritten)
        {
            currentFrame++;
            showcnt--;
            totalBytesWritten += bytesWritten;
        }

        if ((showcnt <= 0) || !bytesWritten)
        {
            double timeused;
#ifdef __unix__
            struct rusage usage;
#endif
#ifdef _WIN32
            char percent[MAX_PATH + 20];
            timeused = (GetTickCount() - begin) * 1e-3;
#else
#ifdef __unix__
            if (getrusage(RUSAGE_SELF, &usage) == 0)
            {
                timeused = (double) usage.ru_utime.tv_sec +
                    (double) usage.ru_utime.tv_usec * 1e-6;
            }
            else
                timeused = 0;
#else
            timeused = (double) clock() * (1.0 / CLOCKS_PER_SEC);
#endif
#endif
            if (currentFrame && (timeused > 0.1))
            {
                showcnt += 50;

                if (frames != 0)
                {
                    fprintf(stderr,
                            "\r%7d/%-7d (%3d%%) |  %5.1f  | %6.1f/%-6.1f | %7.2fx | %.1f ",
                            currentFrame, frames, currentFrame * 100 / frames,
                            ((double) totalBytesWritten * 8.0 / 1000.0) /
                            ((double) infile->samples / infile->samplerate *
                             currentFrame / frames), timeused,
                            timeused * frames / currentFrame,
                            ((double)frameSize * currentFrame / infile->samplerate) /
                            timeused,
                            timeused * (frames -
                                        currentFrame) / currentFrame);
                }
                else
                {
                    fprintf(stderr,
                            "\r %7d | %7.1f | %7.2fx ",
                            currentFrame,
                            timeused,
                            ((double)frameSize * currentFrame / infile->samplerate) /
                            timeused);
                }

                fflush(stderr);
#ifdef _WIN32
                if (frames != 0)
                {
                    snprintf(percent, sizeof(percent), "%.2f%% encoding %s",
                            100.0 * currentFrame / frames, audioFileName);
                    SetConsoleTitle(percent);
                }
#endif
            }
        }

        /* all done, bail out */
        if (!samplesRead && !bytesWritten)
            break;

        if (bytesWritten < 0)
        {
            fprintf(stderr, "faac_encoder_encode() failed: %s\n",
                    faac_strerror(fstatus));
            break;
        }

        if (bytesWritten > 0)
        {
            uint64_t frame_samples = input_samples - encoded_samples;
            if (frame_samples > frameSize)
                frame_samples = frameSize;

            if (container == MP4_CONTAINER) {
                if (mp4_write_frame(bitbuf, (uint32_t)bytesWritten, (uint32_t)frame_samples))
                {
                    fprintf(stderr, "mp4_write_frame() failed\n");
                    break;
                }
            } else
                fwrite(bitbuf, 1, bytesWritten, outfile);

            encoded_samples += frame_samples;
        }
    }
    fprintf(stderr, "\n");

    if (container == MP4_CONTAINER)
    {
        char *version_string = malloc(strlen(faac_id_string) + 6);
        if (!version_string)
        {
            fprintf(stderr, "out of memory\n");
            return 1;
        }
        const uint8_t *ascData = NULL;
        uint32_t ascSize = 0;

        faac_encoder_asc(hEncoder, &ascData, &ascSize);
        mp4_set_decoder_config((unsigned char *)ascData, ascSize);
        snprintf(version_string, strlen(faac_id_string) + 6, "FAAC %s", faac_id_string);

        mp4_set_encoder(version_string);

#define SETTAG(id, x) if(x) mp4_set_tag(id, x)
        SETTAG(MP4TAG_ARTIST, artist);
        SETTAG(MP4TAG_ARTISTSORT, artistsort);
        SETTAG(MP4TAG_COMPOSER, composer);
        SETTAG(MP4TAG_COMPOSERSORT, composersort);
        SETTAG(MP4TAG_TITLE, title);
        SETTAG(MP4TAG_ALBUM, album);
        SETTAG(MP4TAG_ALBUMARTIST, albumartist);
        SETTAG(MP4TAG_ALBUMARTISTSORT, albumartistsort);
        SETTAG(MP4TAG_ALBUMSORT, albumsort);
        SETTAG(MP4TAG_YEAR, year);
        SETTAG(MP4TAG_COMMENT, comment);
#undef SETTAG
        if (trackno) mp4_set_track(trackno, ntracks);
        if (discno) mp4_set_disc(discno, ndiscs);
        if (compilation) mp4_set_compilation(compilation);
        if (genre) mp4_set_genre(genre);
        if (artData && artSize)
            mp4_set_cover(artData, (int)artSize);

        {
            uint32_t final_creation_time = 0;
            if (creation_time_str)
            {
                if (!strcmp(creation_time_str, "auto"))
                {
                    if (strcmp(audioFileName, "-") == 0)
                    {
                        fprintf(stderr, "cannot use --creation-time auto with stdin, defaulting to 0\n");
                    }
                    else
                    {
                        struct stat st;
                        if (stat(audioFileName, &st) == 0)
                        {
                            final_creation_time = (uint32_t)st.st_mtime;
                        }
                        else
                        {
                            fprintf(stderr, "couldn't stat() input file %s, defaulting to 0\n", audioFileName);
                        }
                    }
                }
                else if (!strcmp(creation_time_str, "now"))
                {
                    final_creation_time = (uint32_t)time(NULL);
                }
                else
                {
                    char *endptr;
                    errno = 0;
                    final_creation_time = (uint32_t)strtoul(creation_time_str, &endptr, 10);
                    if (errno != 0 || *endptr != '\0')
                    {
                        fprintf(stderr, "invalid creation time %s, defaulting to 0\n", creation_time_str);
                        final_creation_time = 0;
                    }
                }
            }
            else
            {
                const char *sde = getenv("SOURCE_DATE_EPOCH");
                if (sde)
                {
                    char *endptr;
                    errno = 0;
                    final_creation_time = (uint32_t)strtoul(sde, &endptr, 10);
                    if (errno != 0 || *endptr != '\0')
                    {
                        fprintf(stderr, "invalid SOURCE_DATE_EPOCH %s, ignoring\n", sde);
                        final_creation_time = 0;
                    }
                }
            }
            mp4_set_creation_time(final_creation_time);
        }

        if (mp4_finish())
            fprintf(stderr, "mp4_finish() failed: output file may be incomplete\n");
        mp4_close();

        free(version_string);

        if (verbose >= 2)
        {
            fprintf(stderr, "%u frames\n", mp4_frame_count());
            fprintf(stderr, "%u output samples\n", mp4_sample_count());
            fprintf(stderr, "max bitrate: %u\n", mp4_max_bitrate());
            fprintf(stderr, "avg bitrate: %u\n", mp4_avg_bitrate());
            fprintf(stderr, "max frame size: %u\n", mp4_max_frame_size());
        }
    }
    else
    {
        fclose(outfile);
    }

    faac_encoder_close(&hEncoder);

    wav_close(infile);

    if (artData)
        free(artData);
    if (pcmbuf)
        free(pcmbuf);
    if (bitbuf)
        free(bitbuf);
    if (aacFileNameGiven)
        free(aacFileName);
    if (chanmap)
        free(chanmap);

    return 0;
}
