exhale/src/app/exhaleApp.cpp
Christian R. Helmrich 11ac123906 fix gcc compilation
2020-02-28 12:00:12 +01:00

650 lines
27 KiB
C++
Raw Blame History

/* exhaleApp.cpp - source file with main() routine for exhale application executable
* written by C. R. Helmrich, last modified in 2020 - see License.htm for legal notices
*
* The copyright in this software is being made available under a Modified BSD-Style License
* and comes with ABSOLUTELY NO WARRANTY. This software may be subject to other third-
* party rights, including patent rights. No such rights are granted under this License.
*
* Copyright (c) 2018-2020 Christian R. Helmrich, project ecodis. All rights reserved.
*/
#include "exhaleAppPch.h"
#include "basicMP4Writer.h"
#include "basicWavReader.h"
#include "loudnessEstim.h"
// #define USE_EXHALELIB_DLL (defined (_WIN32) || defined (WIN32) || defined (_WIN64) || defined (WIN64))
#if USE_EXHALELIB_DLL
#include "exhaleDecl.h"
#else
#include "../lib/exhaleEnc.h"
#endif
#include "version.h"
#include <iostream>
#include <sys/stat.h>
#include <fcntl.h>
#include <time.h>
#if defined (_WIN32) || defined (WIN32) || defined (_WIN64) || defined (WIN64)
#include <windows.h>
#define EXHALE_TEXT_BLUE (FOREGROUND_INTENSITY | FOREGROUND_BLUE | FOREGROUND_GREEN)
#define EXHALE_TEXT_PINK (FOREGROUND_INTENSITY | FOREGROUND_BLUE | FOREGROUND_RED)
#else // Linux, MacOS, Unix
#define EXHALE_TEXT_INIT "\x1b[0m"
#define EXHALE_TEXT_BLUE "\x1b[36m"
#define EXHALE_TEXT_PINK "\x1b[35m"
#endif
// constants, experimental macros
#define EA_LOUD_INIT 16399u // bsSamplePeakLevel = 0 & methodValue = 0
#define EA_LOUD_NORM -42.25f // -100 + 57.75 of ISO 23003-4, Table A.48
#define EA_PEAK_NORM -96.33f // 20 * log10(2^-16), 16-bit normalization
#define EA_PEAK_MIN 0.262f // 20 * log10() + EA_PEAK_NORM = -108 dbFS
#define IGNORE_WAV_LENGTH 0 // 1: ignore input size indicators (nasty)
#define XHE_AAC_LOW_DELAY 0 // 1: allow encoding with 768 frame length
// main routine
int main (const int argc, char* argv[])
{
if (argc <= 0) return argc; // for safety
const bool readStdin = (argc == 3);
BasicWavReader wavReader;
int32_t* inPcmData = nullptr; // 24-bit WAVE audio input buffer
uint8_t* outAuData = nullptr; // access unit (AU) output buffer
int inFileHandle = -1, outFileHandle = -1;
uint32_t loudStats = EA_LOUD_INIT; // valid empty loudness data
uint16_t i, exePathEnd = 0;
uint16_t compatibleExtensionFlag = 0; // 0: disabled, 1: enabled
uint16_t coreSbrFrameLengthIndex = 1; // 0: 768, 1: 1024 samples
uint16_t variableCoreBitRateMode = 3; // 0: lowest... 9: highest
#if defined (_WIN32) || defined (WIN32) || defined (_WIN64) || defined (WIN64)
const HANDLE hConsole = GetStdHandle (STD_OUTPUT_HANDLE);
CONSOLE_SCREEN_BUFFER_INFO csbi;
#endif
for (i = 0; (argv[0][i] != 0) && (i < USHRT_MAX); i++)
{
#if defined (_WIN32) || defined (WIN32) || defined (_WIN64) || defined (WIN64)
if (argv[0][i] == '\\') exePathEnd = i + 1;
#else // Linux, MacOS, Unix
if (argv[0][i] == '/' ) exePathEnd = i + 1;
#endif
}
const char* const exeFileName = argv[0] + exePathEnd;
if ((exeFileName[0] == 0) || (i == USHRT_MAX))
{
fprintf_s (stderr, " ERROR reading executable name or path: the string is invalid!\n\n");
return 32768; // bad executable string
}
// print program header with compile info
fprintf_s (stdout, "\n ---------------------------------------------------------------------\n");
fprintf_s (stdout, " | ");
#if defined (_WIN32) || defined (WIN32) || defined (_WIN64) || defined (WIN64)
GetConsoleScreenBufferInfo (hConsole, &csbi); // save the text color
SetConsoleTextAttribute (hConsole, EXHALE_TEXT_PINK); fprintf_s (stdout, "exhale");
SetConsoleTextAttribute (hConsole, csbi.wAttributes); fprintf_s (stdout, " - ");
SetConsoleTextAttribute (hConsole, EXHALE_TEXT_PINK); fprintf_s (stdout, "e");
SetConsoleTextAttribute (hConsole, csbi.wAttributes); fprintf_s (stdout, "codis e");
SetConsoleTextAttribute (hConsole, EXHALE_TEXT_PINK); fprintf_s (stdout, "x");
SetConsoleTextAttribute (hConsole, csbi.wAttributes); fprintf_s (stdout, "tended ");
SetConsoleTextAttribute (hConsole, EXHALE_TEXT_PINK); fprintf_s (stdout, "h");
SetConsoleTextAttribute (hConsole, csbi.wAttributes); fprintf_s (stdout, "igh-efficiency ");
SetConsoleTextAttribute (hConsole, EXHALE_TEXT_PINK); fprintf_s (stdout, "a");
SetConsoleTextAttribute (hConsole, csbi.wAttributes); fprintf_s (stdout, "nd ");
SetConsoleTextAttribute (hConsole, EXHALE_TEXT_PINK); fprintf_s (stdout, "l");
SetConsoleTextAttribute (hConsole, csbi.wAttributes); fprintf_s (stdout, "ow-complexity ");
SetConsoleTextAttribute (hConsole, EXHALE_TEXT_PINK); fprintf_s (stdout, "e");
SetConsoleTextAttribute (hConsole, csbi.wAttributes); fprintf_s (stdout, "ncoder |\n");
#else // Linux, MacOS, Unix
fprintf_s (stdout, EXHALE_TEXT_PINK "exhale");
fprintf_s (stdout, EXHALE_TEXT_INIT " - ");
fprintf_s (stdout, EXHALE_TEXT_PINK "e");
fprintf_s (stdout, EXHALE_TEXT_INIT "codis e");
fprintf_s (stdout, EXHALE_TEXT_PINK "x");
fprintf_s (stdout, EXHALE_TEXT_INIT "tended ");
fprintf_s (stdout, EXHALE_TEXT_PINK "h");
fprintf_s (stdout, EXHALE_TEXT_INIT "igh-efficiency ");
fprintf_s (stdout, EXHALE_TEXT_PINK "a");
fprintf_s (stdout, EXHALE_TEXT_INIT "nd ");
fprintf_s (stdout, EXHALE_TEXT_PINK "l");
fprintf_s (stdout, EXHALE_TEXT_INIT "ow-complexity ");
fprintf_s (stdout, EXHALE_TEXT_PINK "e");
fprintf_s (stdout, EXHALE_TEXT_INIT "ncoder |\n");
#endif
fprintf_s (stdout, " | |\n");
#if defined (_WIN64) || defined (WIN64) || defined (_LP64) || defined (__LP64__) || defined (__x86_64) || defined (__x86_64__)
fprintf_s (stdout, " | version %s.%s%s (x64, built on %s) - written by C.R.Helmrich |\n",
#else // 32-bit OS
fprintf_s (stdout, " | version %s.%s%s (x86, built on %s) - written by C.R.Helmrich |\n",
#endif
EXHALELIB_VERSION_MAJOR, EXHALELIB_VERSION_MINOR, EXHALELIB_VERSION_BUGFIX, __DATE__);
fprintf_s (stdout, " ---------------------------------------------------------------------\n\n");
// check arg. list, print usage if needed
if ((argc < 3) || (argc > 4) || (argc > 1 && argv[1][1] != 0))
{
fprintf_s (stdout, " Copyright 2018-2020 C.R.Helmrich, project ecodis. See License.htm for details.\n\n");
fprintf_s (stdout, " This software is being made available under a Modified BSD License and comes\n");
fprintf_s (stdout, " with ABSOLUTELY NO WARRANTY. This software may be subject to other third-party\n");
fprintf_s (stdout, " rights, including patent rights. No such rights are granted under this License.\n\n");
#if defined (_WIN32) || defined (WIN32) || defined (_WIN64) || defined (WIN64)
SetConsoleTextAttribute (hConsole, EXHALE_TEXT_BLUE); fprintf_s (stdout, " Usage:\t");
SetConsoleTextAttribute (hConsole, csbi.wAttributes);
#else
fprintf_s (stdout, EXHALE_TEXT_BLUE " Usage:\t" EXHALE_TEXT_INIT);
#endif
fprintf_s (stdout, "%s preset [inputWaveFile.wav] outputMP4File.m4a\n\n where\n\n", exeFileName);
#if defined (_WIN32) || defined (WIN32) || defined (_WIN64) || defined (WIN64)
fprintf_s (stdout, " preset\t= # (1-9) low-complexity standard compliant xHE-AAC at 16<31>#+48 kbit/s\n");
# if XHE_AAC_LOW_DELAY
// fprintf_s (stdout, " \t (a-i) low-complexity compatible xHE-AAC with BE at 16<31>#+48 kbit/s\n");
fprintf_s (stdout, " \t (A-I) 41ms low-delay compatible xHE-AAC with BE at 16<31>#+48 kbit/s\n");
# endif
#else
fprintf_s (stdout, " preset\t= # (1-9) low-complexity standard compliant xHE-AAC at 16*#+48 kbit/s\n");
# if XHE_AAC_LOW_DELAY
// fprintf_s (stdout, " \t (a-i) low-complexity compatible xHE-AAC with BE at 16*#+48 kbit/s\n");
fprintf_s (stdout, " \t (A-I) 41ms low-delay compatible xHE-AAC with BE at 16*#+48 kbit/s\n");
# endif
#endif
fprintf_s (stdout, "\n inputWaveFile.wav lossless WAVE audio input, read from stdin if not specified\n\n");
fprintf_s (stdout, " outputMP4File.m4a encoded MPEG-4 bit-stream, extension should be .m4a or .mp4\n\n\n");
#if defined (_WIN32) || defined (WIN32) || defined (_WIN64) || defined (WIN64)
SetConsoleTextAttribute (hConsole, EXHALE_TEXT_BLUE); fprintf_s (stdout, " Notes:\t");
SetConsoleTextAttribute (hConsole, csbi.wAttributes);
#else
fprintf_s (stdout, EXHALE_TEXT_BLUE " Notes:\t" EXHALE_TEXT_INIT);
#endif
fprintf_s (stdout, "The above bit-rates are for stereo and change for mono or multichannel.\n");
if (exePathEnd > 0)
{
#if defined (_WIN32) || defined (WIN32) || defined (_WIN64) || defined (WIN64)
fprintf_s (stdout, " \tUse filename prefix .\\ for the current directory if this executable was\n\tcalled with a path (call: %s).\n", argv[0]);
#else // Linux, MacOS, Unix
fprintf_s (stdout, " \tUse filename prefix ./ for the current directory if this executable was\n\tcalled with a path (call: %s).\n", argv[0]);
#endif
}
return 0; // no arguments, which is OK
}
// check preset mode, derive coder config
#if XHE_AAC_LOW_DELAY
if ((*argv[1] >= '1' && *argv[1] <= '9') || (*argv[1] >= 'a' && *argv[1] <= 'i') || (*argv[1] >= 'A' && *argv[1] <= 'I'))
#else
if ((*argv[1] >= '1' && *argv[1] <= '9') || (*argv[1] >= 'a' && *argv[1] <= 'i'))
#endif
{
i = (uint16_t) argv[1][0];
compatibleExtensionFlag = (i & 0x40) >> 6;
coreSbrFrameLengthIndex = (i & 0x20) >> 5;
variableCoreBitRateMode = (i & 0x0F);
}
else if (*argv[1] == '#') // default mode
{
fprintf_s (stdout, " Default preset is specified, encoding to low-complexity xHE-AAC, preset mode %d\n\n", variableCoreBitRateMode);
}
else
{
#if XHE_AAC_LOW_DELAY
fprintf_s (stderr, " ERROR reading preset mode: character %s is not supported! Use 1-9 or A-I.\n\n", argv[1]);
#else
fprintf_s (stderr, " ERROR reading preset mode: character %s is not supported! Please use 1-9.\n\n", argv[1]);
#endif
return 16384; // preset isn't supported
}
const unsigned frameLength = (3 + coreSbrFrameLengthIndex) << 8;
if (readStdin) // configure stdin
{
#if defined (_WIN32) || defined (WIN32) || defined (_WIN64) || defined (WIN64)
inFileHandle = _fileno (stdin);
if (_setmode (inFileHandle, _O_RDONLY | _O_BINARY) == -1)
{
fprintf_s (stderr, " ERROR while trying to set stdin to binary mode! Has stdin been closed?\n\n");
inFileHandle = -1;
goto mainFinish; // stdin setup error
}
#else // Linux, MacOS, Unix
inFileHandle = fileno (stdin);
#endif
}
else // argc = 4, open input file
{
const char* inFileName = argv[2];
uint16_t inPathEnd = 0;
for (i = 0; (inFileName[i] != 0) && (i < USHRT_MAX); i++)
{
#if defined (_WIN32) || defined (WIN32) || defined (_WIN64) || defined (WIN64)
if (inFileName[i] == '\\') inPathEnd = i + 1;
#else // Linux, MacOS, Unix
if (inFileName[i] == '/' ) inPathEnd = i + 1;
#endif
}
if ((inFileName[0] == 0) || (i == USHRT_MAX))
{
fprintf_s (stderr, " ERROR reading input file name or path: the string is invalid!\n\n");
goto mainFinish; // bad input string
}
if (inPathEnd == 0) // name has no path
{
inFileName = (const char*) malloc ((exePathEnd + i + 1) * sizeof (char)); // 0-terminated string
memcpy ((void*) inFileName, argv[0], exePathEnd * sizeof (char)); // prepend executable path ...
memcpy ((void*)(inFileName + exePathEnd), argv[2], (i + 1) * sizeof (char)); // ... to file name
}
#if defined (_WIN32) || defined (WIN32) || defined (_WIN64) || defined (WIN64)
if (_sopen_s (&inFileHandle, inFileName, _O_RDONLY | _O_SEQUENTIAL | _O_BINARY, _SH_DENYWR, _S_IREAD) != 0)
#else // Linux, MacOS, Unix
if ((inFileHandle = ::open (inFileName, O_RDONLY, 0666)) == -1)
#endif
{
fprintf_s (stderr, " ERROR while trying to open input file %s! Does it already exist?\n\n", inFileName);
inFileHandle = -1;
if (inPathEnd == 0) free ((void*) inFileName);
goto mainFinish; // input file error
}
if (inPathEnd == 0) free ((void*) inFileName);
}
#if defined (_WIN32) || defined (WIN32) || defined (_WIN64) || defined (WIN64)
if ((wavReader.open (inFileHandle, frameLength, readStdin ? LLONG_MAX : _filelengthi64 (inFileHandle)) != 0) ||
#else // Linux, MacOS, Unix
if ((wavReader.open (inFileHandle, frameLength, readStdin ? LLONG_MAX : lseek (inFileHandle, 0, 2 /*SEEK_END*/)) != 0) ||
#endif
(wavReader.getNumChannels () >= 7))
{
fprintf_s (stderr, " ERROR while trying to open WAVE file: invalid or unsupported audio format!\n\n");
i = 8192; // return value
goto mainFinish; // audio format invalid
}
else // WAVE OK, open output file
{
const char* outFileName = argv[argc - 1];
uint16_t outPathEnd = readStdin ? 1 : 0; // no path prepends when the input is read from stdin
for (i = 0; (outFileName[i] != 0) && (i < USHRT_MAX); i++)
{
#if defined (_WIN32) || defined (WIN32) || defined (_WIN64) || defined (WIN64)
if (outFileName[i] == '\\') outPathEnd = i + 1;
#else // Linux, MacOS, Unix
if (outFileName[i] == '/' ) outPathEnd = i + 1;
#endif
}
if ((outFileName[0] == 0) || (i == USHRT_MAX))
{
fprintf_s (stderr, " ERROR reading output file name or path: the string is invalid!\n\n");
goto mainFinish; // bad output string
}
if ((variableCoreBitRateMode < 2) && (wavReader.getSampleRate () > 24000))
{
fprintf_s (stderr, " ERROR during encoding! Input sample rate must be <=24 kHz for preset mode %d!\n\n", variableCoreBitRateMode);
i = 4096; // return value
goto mainFinish; // resample to 24 kHz
}
if ((variableCoreBitRateMode < 3) && (wavReader.getSampleRate () > 32000))
{
fprintf_s (stderr, " ERROR during encoding! Input sample rate must be <=32 kHz for preset mode %d!\n\n", variableCoreBitRateMode);
i = 4096; // return value
goto mainFinish; // resample to 32 kHz
}
if (outPathEnd == 0) // name has no path
{
outFileName = (const char*) malloc ((exePathEnd + i + 1) * sizeof (char)); // 0-terminated string
memcpy ((void*) outFileName, argv[0], exePathEnd * sizeof (char)); // prepend executable path ...
memcpy ((void*)(outFileName + exePathEnd), argv[argc - 1], (i + 1) * sizeof (char)); //...to name
}
i = (readStdin ? O_RDWR : O_WRONLY);
#if defined (_WIN32) || defined (WIN32) || defined (_WIN64) || defined (WIN64)
if (_sopen_s (&outFileHandle, outFileName, i | _O_SEQUENTIAL | _O_CREAT | _O_EXCL | _O_BINARY, _SH_DENYRD, _S_IWRITE) != 0)
#else // Linux, MacOS, Unix
if ((outFileHandle = ::open (outFileName, i | O_CREAT | O_EXCL, 0666)) == -1)
#endif
{
fprintf_s (stderr, " ERROR while trying to open output file %s! Does it already exist?\n\n", outFileName);
outFileHandle = -1;
if (outPathEnd == 0) free ((void*) outFileName);
goto mainFinish; // output file error
}
if (outPathEnd == 0) free ((void*) outFileName);
}
// enforce executable specific constraints
i = __min (USHRT_MAX, wavReader.getSampleRate ());
if ((wavReader.getNumChannels () > 3) && (i == 57600 || i == 51200 || i == 40000 || i == 38400 || i == 34150 ||
i == 28800 || i == 25600 || i == 20000 || i == 19200 || i == 17075 || i == 14400 || i == 12800 || i == 9600))
{
fprintf_s (stderr, " ERROR: exhale does not support %d-channel coding with %d Hz sampling rate.\n\n", wavReader.getNumChannels (), i);
goto mainFinish; // encoder config error
}
else
{
const unsigned startLength = (frameLength * 25) >> 4; // encoder PCM look-ahead
const unsigned numChannels = wavReader.getNumChannels ();
const unsigned inFrameSize = frameLength * sizeof (int32_t);
const unsigned inSampDepth = wavReader.getBitDepth ();
const int64_t expectLength = wavReader.getDataBytesLeft () / int64_t (numChannels * inSampDepth >> 3);
// allocate dynamic frame memory buffers
inPcmData = (int32_t*) malloc (inFrameSize * numChannels); // max frame in size
outAuData = (uint8_t*) malloc ((6144 >> 3) * numChannels); // max frame AU size
if ((inPcmData == nullptr) || (outAuData == nullptr))
{
fprintf_s (stderr, " ERROR while trying to allocate dynamic memory! Not enough free RAM available!\n\n");
i = 2048; // return value
goto mainFinish; // memory alloc error
}
if (wavReader.read (inPcmData, frameLength) != frameLength) // full first frame
{
fprintf_s (stderr, " ERROR while trying to encode input audio data! The audio stream is too short!\n\n");
i = 1024; // return value
goto mainFinish; // audio is too short
}
else // start coding loop, show progress
{
const unsigned sampleRate = wavReader.getSampleRate ();
const unsigned indepPeriod = (sampleRate < 48000 ? sampleRate / frameLength : 45 /*for 50-Hz video, use 50 for 60-Hz video*/);
const unsigned mod3Percent = unsigned ((expectLength * (3 + coreSbrFrameLengthIndex)) >> 17);
uint32_t byteCount = 0, bw = (numChannels < 7 ? loudStats : 0);
uint32_t br, bwMax = 0; // br will be used to hold bytes read and/or bit-rate
uint32_t headerRes = 0;
// initialize LoudnessEstimator object
LoudnessEstimator loudnessEst (inPcmData, 24 /*bit*/, sampleRate, numChannels);
// open & prepare ExhaleEncoder object
#if USE_EXHALELIB_DLL
ExhaleEncAPI& exhaleEnc = *exhaleCreate (inPcmData, outAuData, sampleRate, numChannels, frameLength, indepPeriod, variableCoreBitRateMode +
#else
ExhaleEncoder exhaleEnc (inPcmData, outAuData, sampleRate, numChannels, frameLength, indepPeriod, variableCoreBitRateMode +
#endif
(sampleRate > 24000 ? 0 : 1 - (variableCoreBitRateMode >> 2)) // compensate for low sampling rates
#if !RESTRICT_TO_AAC
, true /*noise filling*/, compatibleExtensionFlag > 0
#endif
);
BasicMP4Writer mp4Writer; // .m4a file
// init encoder, generate UsacConfig()
memset (outAuData, 0, 108 * sizeof (uint8_t)); // max. allowed ASC + UC size
i = exhaleEnc.initEncoder (outAuData, &bw); // bw stores actual ASC + UC size
if ((i |= mp4Writer.open (outFileHandle, sampleRate, numChannels, inSampDepth, frameLength, startLength,
indepPeriod, outAuData, bw, time (nullptr) & UINT_MAX, (char) variableCoreBitRateMode)) != 0)
{
fprintf_s (stderr, " ERROR while trying to initialize xHE-AAC encoder: error value %d was returned!\n\n", i);
i <<= 2; // return value
#if USE_EXHALELIB_DLL
exhaleDelete (&exhaleEnc);
#endif
goto mainFinish; // coder init error
}
if (*argv[1] != '#') // user-def. mode
{
fprintf_s (stdout, " Encoding %d-kHz %d-channel %d-bit WAVE to low-complexity xHE-AAC at %d kbit/s\n\n",
sampleRate / 1000, numChannels, inSampDepth, __min (4, numChannels) * (24 + variableCoreBitRateMode * 8));
}
if (!readStdin && (mod3Percent > 0))
{
#if defined (_WIN32) || defined (WIN32) || defined (_WIN64) || defined (WIN64)
SetConsoleTextAttribute (hConsole, EXHALE_TEXT_BLUE);
fprintf_s (stdout, " Progress: ");
SetConsoleTextAttribute (hConsole, csbi.wAttributes); // initial text color
fprintf_s (stdout, "-"); fflush (stdout);
#else
fprintf_s (stdout, EXHALE_TEXT_BLUE " Progress: " EXHALE_TEXT_INIT "-"); fflush (stdout);
#endif
}
#if !IGNORE_WAV_LENGTH
if (!readStdin) // reserve space for MP4 file header. TODO: nasty, avoid this
{
if ((headerRes = (uint32_t) mp4Writer.initHeader (uint32_t (__min (UINT_MAX - startLength, expectLength)))) < 666)
{
fprintf_s (stderr, "\n ERROR while trying to write MPEG-4 bit-stream header: stopped after %d bytes!\n\n", headerRes);
i = 3; // return value
# if USE_EXHALELIB_DLL
exhaleDelete (&exhaleEnc);
# endif
goto mainFinish; // writeout error
}
}
#endif
i = 1; // for progress bar
// initial frame, encode look-ahead AU
if ((bw = exhaleEnc.encodeLookahead ()) < 3)
{
fprintf_s (stderr, "\n ERROR while trying to create first xHE-AAC frame: error value %d was returned!\n\n", bw);
i = 2; // return value
#if USE_EXHALELIB_DLL
exhaleDelete (&exhaleEnc);
#endif
goto mainFinish; // coder-time error
}
if (bwMax < bw) bwMax = bw;
// write first AU, add frame to header
if ((mp4Writer.addFrameAU (outAuData, bw) != bw) || loudnessEst.addNewPcmData (frameLength))
{
#if USE_EXHALELIB_DLL
exhaleDelete (&exhaleEnc);
#endif
goto mainFinish; // writeout error
}
byteCount += bw;
while (wavReader.read (inPcmData, frameLength) > 0) // read a new audio frame
{
// frame coding loop, encode next AU
if ((bw = exhaleEnc.encodeFrame ()) < 3)
{
fprintf_s (stderr, "\n ERROR while trying to create xHE-AAC frame: error value %d was returned!\n\n", bw);
i = 2; // return value
#if USE_EXHALELIB_DLL
exhaleDelete (&exhaleEnc);
#endif
goto mainFinish; // encoding error
}
if (bwMax < bw) bwMax = bw;
// write new AU, add frame to header
if ((mp4Writer.addFrameAU (outAuData, bw) != bw) || loudnessEst.addNewPcmData (frameLength))
{
#if USE_EXHALELIB_DLL
exhaleDelete (&exhaleEnc);
#endif
goto mainFinish; // writeout error
}
byteCount += bw;
if (!readStdin && (mod3Percent > 0) && !(mp4Writer.getFrameCount () % mod3Percent))
{
if ((i++) < 34) // for short files
{
fprintf_s (stdout, "-"); fflush (stdout);
}
}
} // frame loop
// end of coding loop, encode final AU
if ((bw = exhaleEnc.encodeFrame ()) < 3)
{
fprintf_s (stderr, "\n ERROR while trying to create xHE-AAC frame: error value %d was returned!\n\n", bw);
i = 2; // return value
#if USE_EXHALELIB_DLL
exhaleDelete (&exhaleEnc);
#endif
goto mainFinish; // coder-time error
}
if (bwMax < bw) bwMax = bw;
// write final AU, add frame to header
if ((mp4Writer.addFrameAU (outAuData, bw) != bw) || loudnessEst.addNewPcmData (frameLength))
{
#if USE_EXHALELIB_DLL
exhaleDelete (&exhaleEnc);
#endif
goto mainFinish; // writeout error
}
byteCount += bw;
const int64_t actualLength = wavReader.getDataBytesRead () / int64_t (numChannels * inSampDepth >> 3);
if (((actualLength + startLength) % frameLength) > 0) // flush trailing audio
{
memset (inPcmData, 0, inFrameSize * numChannels);
// flush remaining audio into new AU
if ((bw = exhaleEnc.encodeFrame ()) < 3)
{
fprintf_s (stderr, "\n ERROR while trying to create last xHE-AAC frame: error value %d was returned!\n\n", bw);
i = 2; // return value
#if USE_EXHALELIB_DLL
exhaleDelete (&exhaleEnc);
#endif
goto mainFinish; // encoding error
}
if (bwMax < bw) bwMax = bw;
// the flush AU, add frame to header
if (mp4Writer.addFrameAU (outAuData, bw) != bw) // zero, no loudness update
{
#if USE_EXHALELIB_DLL
exhaleDelete (&exhaleEnc);
#endif
goto mainFinish; // writeout error
}
byteCount += bw;
} // trailing frame
#if !IGNORE_WAV_LENGTH
if (readStdin) // reserve space for MP4 file header (is there an easier way?)
#endif
{
int64_t pos = _SEEK (outFileHandle, 0, 1 /*SEEK_CUR*/);
if ((headerRes = (uint32_t) mp4Writer.initHeader (uint32_t (__min (UINT_MAX - startLength, actualLength)))) < 666)
{
fprintf_s (stderr, "\n ERROR while trying to write MPEG-4 bit-stream header: stopped after %d bytes!\n\n", headerRes);
i = 3; // return value
#if USE_EXHALELIB_DLL
exhaleDelete (&exhaleEnc);
#endif
goto mainFinish; // writeout error
}
// move AU data forward to make room for actual MP4 header at start of file
br = inFrameSize * numChannels;
while ((pos -= br) > 0) // move loop
{
_SEEK (outFileHandle, pos, 0 /*SEEK_SET*/);
bw = _READ (outFileHandle, inPcmData, br);
_SEEK (outFileHandle, pos + headerRes, 0 /*SEEK_SET*/);
bw = _WRITE(outFileHandle, inPcmData, br);
}
if ((br = (uint32_t) __max (0, pos + br)) > 0) // remainder of data to move
{
_SEEK (outFileHandle, 0, 0 /*SEEK_SET*/);
bw = _READ (outFileHandle, inPcmData, br);
_SEEK (outFileHandle, headerRes, 0 /*SEEK_SET*/);
bw = _WRITE(outFileHandle, inPcmData, br);
}
}
i = 0; // no errors
// loudness and sample peak of program
loudStats = loudnessEst.getStatistics ();
if (numChannels < 7)
{
// quantize for loudnessInfo() reset
const uint32_t qLoud = uint32_t (4.0f * __max (0.0f, (loudStats >> 16) / 512.f + EA_LOUD_NORM) + 0.5f);
const uint32_t qPeak = uint32_t (32.0f * (20.0f - 20.0f * log10 (__max (EA_PEAK_MIN, float (loudStats & USHRT_MAX))) - EA_PEAK_NORM) + 0.5f);
// recreate ASC + UC + loudness data
bw = EA_LOUD_INIT | (qPeak << 18) | (qLoud << 6); // measurementSystem is 3
memset (outAuData, 0, 108 * sizeof (uint8_t)); // max allowed ASC + UC size
i = exhaleEnc.initEncoder (outAuData, &bw); // with finished loudnessInfo()
}
// mean & max. bit-rate of encoded AUs
br = uint32_t (((actualLength >> 1) + 8 * (byteCount + 4 * (int64_t) mp4Writer.getFrameCount ()) * sampleRate) / actualLength);
bw = uint32_t (((frameLength >> 1) + 8 * (bwMax + 4u /* maximum AU size + stsz as a bit-rate */) * sampleRate) / frameLength);
bw = mp4Writer.finishFile (br, bw, uint32_t (__min (UINT_MAX - startLength, actualLength)), time (nullptr) & UINT_MAX,
(i == 0) && (numChannels < 7) ? outAuData : nullptr);
// print out collected file statistics
fprintf_s (stdout, " Done, actual average %.1f kbit/s\n\n", (float) br * 0.001f);
if (numChannels < 7)
{
fprintf_s (stdout, " Input statistics: Mobile loudness %.2f LUFS,\tsample peak level %.2f dBFS\n\n",
__max (3u, loudStats >> 16) / 512.f - 100.0f, 20.0f * log10 (__max (EA_PEAK_MIN, float (loudStats & USHRT_MAX))) + EA_PEAK_NORM);
}
if (!readStdin && (actualLength != expectLength || bw != headerRes))
{
fprintf_s (stderr, " WARNING: %lld sample frames read but %lld sample frames expected!\n", (long long) actualLength, (long long) expectLength);
if (bw != headerRes) fprintf_s (stderr, " The encoded MPEG-4 bit-stream is likely to be unreadable!\n");
fprintf_s (stderr, "\n");
}
#if USE_EXHALELIB_DLL
exhaleDelete (&exhaleEnc);
#endif
} // end coding loop and stats print-out
}
mainFinish:
// free all dynamic memory
if (inPcmData != nullptr)
{
free ((void*) inPcmData);
inPcmData = nullptr;
}
if (outAuData != nullptr)
{
free ((void*) outAuData);
outAuData = nullptr;
}
// close input file
if (inFileHandle != -1)
{
if (_CLOSE (inFileHandle) != 0)
{
if (readStdin) // stdin
{
fprintf_s (stderr, " ERROR while trying to close stdin stream! Has it already been closed?\n\n");
}
else // argc = 4, file
{
fprintf_s (stderr, " ERROR while trying to close input file %s! Does it still exist?\n\n", argv[2]);
}
}
inFileHandle = 0;
}
// close output file
if (outFileHandle != -1)
{
if (_CLOSE (outFileHandle) != 0)
{
fprintf_s (stderr, " ERROR while trying to close output file %s! Does it still exist?\n\n", argv[argc - 1]);
}
outFileHandle = 0;
}
return (inFileHandle | outFileHandle | i);
}