mirror of https://gitlab.com/ecodis/exhale.git
1420 lines
57 KiB
C++
1420 lines
57 KiB
C++
/* exhaleApp.cpp - source file with main() routine for exhale application executable
|
||
* written by C. R. Helmrich, last modified in 2023 - see License.htm for legal notices
|
||
*
|
||
* The copyright in this software is being made available under the exhale Copyright 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-2024 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 <direct.h>
|
||
#include <windows.h>
|
||
#ifdef __MINGW32__
|
||
#include <share.h>
|
||
#endif
|
||
|
||
#define EXHALE_APP_WIN
|
||
#if defined (_MSC_VER) || defined (__INTEL_COMPILER) || defined (__MINGW32__) // || defined (__GNUC__)
|
||
#define EXHALE_APP_WCHAR
|
||
#ifndef _O_U16TEXT
|
||
# define _O_U16TEXT 0x20000
|
||
#endif
|
||
#define _ERROR1(fmt) fwprintf_s (stderr, L##fmt)
|
||
#define _ERROR2(fmt, dat) fwprintf_s (stderr, L##fmt, dat)
|
||
#define _GETCWD _wgetcwd
|
||
#define _SOPENS _wsopen_s
|
||
#define _STRLEN wcslen
|
||
#else
|
||
#define _ERROR1(fmt) fprintf_s (stderr, fmt)
|
||
#define _ERROR2(fmt, dat) fprintf_s (stderr, fmt, dat)
|
||
#define _GETCWD _getcwd
|
||
#define _SOPENS _sopen_s
|
||
#define _STRLEN strlen
|
||
#endif
|
||
#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 _ERROR1(fmt) fprintf_s (stderr, fmt)
|
||
#define _ERROR2(fmt, dat) fprintf_s (stderr, fmt, dat)
|
||
#define _GETCWD getcwd
|
||
#define _STRLEN strlen
|
||
|
||
#if 0 // change this to "#if 1" to avoid garbage text in some terminals
|
||
#define EXHALE_TEXT_INIT ""
|
||
#define EXHALE_TEXT_BLUE ""
|
||
#define EXHALE_TEXT_PINK ""
|
||
#else
|
||
#define EXHALE_TEXT_INIT "\x1b[0m"
|
||
#define EXHALE_TEXT_BLUE "\x1b[36m"
|
||
#define EXHALE_TEXT_PINK "\x1b[35m"
|
||
#endif
|
||
#endif
|
||
|
||
// constants, experimental macros
|
||
#if LE_ACCURATE_CALC
|
||
#define EA_LOUD_INIT 16384u // bsSamplePeakLevel = 0 & methodValue = 0
|
||
#else
|
||
#define EA_LOUD_INIT 16399u // bsSamplePeakLevel = 0 & methodValue = 0
|
||
#endif
|
||
#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 EA_USE_WORK_DIR 1 // 1: use working instead of app directory
|
||
#define ENABLE_STDOUT_LOAS 0 // 1: experimental LOAS packed pipe output
|
||
#define FULL_FRM_LOOKAHEAD // on: encoder delay = zero or frame length
|
||
|
||
static const int16_t usfc2x[32] = { // 2x upsampling filter coefficients
|
||
(83359-65536), -27563, 16273, -11344, 8541, -6708, 5403, -4419, 3647, -3025, 2514, -2088, 1730,
|
||
-1428, 1173, -957, 775, -622, 494, -388, 300, -230, 172, -127, 91, -63, 43, -27, 16, -9, 4, -1
|
||
};
|
||
|
||
static const int16_t rsfc3x[128] = {// 3x resampling filter coefficients
|
||
21846, 6711, (36099-32768), 0, -18000, -14370, 0, 10208, 8901, 0, -7062, -6389, 0, 5347, 4934, 0, -4258,
|
||
-3977, 0, 3499, 3294, 0, -2937, -2780, 0, 2501, 2376, 0, -2151, -2050, 0, 1864, 1779, 0, -1623, -1551,
|
||
0, 1417, 1355, 0, -1240, -1187, 0, 1086, 1040, 0, -952, -910, 0, 833, 797, 0, -728, -696, 0, 635, 607,
|
||
0, -553, -528, 0, 480, 457, 0, -415, -395, 0, 358, 340, 0, -307, -291, 0, 262, 248, 0, -223, -211, 0,
|
||
188, 177, 0, -158, -149, 0, 132, 124, 0, -109, -102, 0, 90, 84, 0, -73, -68, 0, 59, 55, 0, -47, -43,
|
||
0, 37, 34, 0, -29, -26, 0, 22, 20, 0, -16, -15, 0, 12, 10, 0, -8, -7, 0, 5, 5, 0, -3, -3, 0, 2
|
||
};
|
||
|
||
static bool eaInitUpsampler2x (int32_t** upsampleBuffer, const uint16_t bitRateMode, const uint16_t sampleRate,
|
||
const uint16_t frameSize, const uint16_t numChannels)
|
||
{
|
||
const uint16_t inLength = frameSize >> 1;
|
||
const uint16_t chLength = inLength + (32 << 1);
|
||
const bool useUpsampler = (frameSize > (32 << 1) && bitRateMode * 3675 > sampleRate);
|
||
|
||
if (useUpsampler)
|
||
{
|
||
if ((*upsampleBuffer = (int32_t*) malloc (chLength * numChannels * sizeof (int32_t))) == nullptr) return false;
|
||
|
||
for (uint16_t ch = 0; ch < numChannels; ch++)
|
||
{
|
||
memset (*upsampleBuffer + inLength + chLength * ch, 0, (chLength - inLength) * sizeof (int32_t));
|
||
}
|
||
}
|
||
return useUpsampler;
|
||
}
|
||
|
||
static bool eaInitDownsampler (int32_t** resampleBuffer, const uint16_t bitRateMode, const uint16_t sampleRate,
|
||
const uint16_t frameSize, const uint16_t numChannels)
|
||
{
|
||
const uint16_t inLength = (frameSize * 3u) >> 1;
|
||
const uint16_t chLength = inLength + (frameSize >> 3);
|
||
const bool useResampler = (frameSize >= 512 && bitRateMode <= 1 && sampleRate == 48000);
|
||
|
||
if (useResampler)
|
||
{
|
||
if ((*resampleBuffer = (int32_t*) malloc (chLength * numChannels * sizeof (int32_t))) == nullptr) return false;
|
||
|
||
for (uint16_t ch = 0; ch < numChannels; ch++)
|
||
{
|
||
memset (*resampleBuffer + inLength + chLength * ch, 0, (chLength - inLength) * sizeof (int32_t));
|
||
}
|
||
}
|
||
return useResampler;
|
||
}
|
||
|
||
static void eaApplyUpsampler2x (int32_t* const pcmBuffer, int32_t* const upsampleBuffer,
|
||
const uint16_t frameSize, const uint16_t numChannels, const bool firstFrame = false)
|
||
{
|
||
const int16_t lookahead = 32;
|
||
const uint16_t inLength = (frameSize >> 1) + (firstFrame ? lookahead : 0);
|
||
const uint16_t chLength = (frameSize >> 1) + (lookahead << 1);
|
||
uint16_t ch;
|
||
|
||
for (ch = 0; ch < numChannels; ch++) // step 1: add deinterleaved input samples to resampling buffer
|
||
{
|
||
int32_t* chPcmBuf = &pcmBuffer[ch];
|
||
int32_t* chUpsBuf = &upsampleBuffer[chLength * ch];
|
||
|
||
if (firstFrame) // construct leading sample values via extrapolation
|
||
{
|
||
for (int8_t i = 0; i < 32; i++) chUpsBuf[i] = (*chPcmBuf * i + (32 >> 1)) >> 5;
|
||
}
|
||
else
|
||
{
|
||
memcpy (chUpsBuf, &chUpsBuf[inLength], (chLength - inLength) * sizeof (int32_t)); // update memory
|
||
}
|
||
chUpsBuf += chLength - inLength;
|
||
|
||
for (uint16_t i = inLength; i > 0; i--, chPcmBuf += numChannels, chUpsBuf++)
|
||
{
|
||
*chUpsBuf = *chPcmBuf; // deinterleave, store in resampling buffer
|
||
}
|
||
}
|
||
|
||
for (ch = 0; ch < numChannels; ch++) // step 2: upsample, reinterleave, and save to PCM input buffer
|
||
{
|
||
/*in*/int32_t* chPcmBuf = &pcmBuffer[ch];
|
||
const int32_t* chUpsBuf = &upsampleBuffer[chLength * ch + lookahead];
|
||
|
||
for (uint16_t i = frameSize >> 1; i > 0; i--, chUpsBuf++)
|
||
{
|
||
int64_t r = (chUpsBuf[0] + (int64_t) chUpsBuf[1]) * (usfc2x[0] - 2 * SHRT_MIN);
|
||
|
||
for (int16_t c = lookahead - 1; c > 0; c--)
|
||
{
|
||
r += (chUpsBuf[-c] + (int64_t) chUpsBuf[c + 1]) * usfc2x[c];
|
||
}
|
||
*chPcmBuf = *chUpsBuf; // no filtering necessary, just copy sample
|
||
if (*chPcmBuf < MIN_VALUE_AUDIO24) *chPcmBuf = MIN_VALUE_AUDIO24;
|
||
else
|
||
if (*chPcmBuf > MAX_VALUE_AUDIO24) *chPcmBuf = MAX_VALUE_AUDIO24;
|
||
chPcmBuf += numChannels;
|
||
|
||
*chPcmBuf = int32_t ((r - 2 * SHRT_MIN) >> 17); // interp. sample
|
||
if (*chPcmBuf < MIN_VALUE_AUDIO24) *chPcmBuf = MIN_VALUE_AUDIO24;
|
||
else
|
||
if (*chPcmBuf > MAX_VALUE_AUDIO24) *chPcmBuf = MAX_VALUE_AUDIO24;
|
||
chPcmBuf += numChannels;
|
||
}
|
||
}
|
||
}
|
||
|
||
static void eaApplyDownsampler (int32_t* const pcmBuffer, int32_t* const resampleBuffer,
|
||
const uint16_t frameSize, const uint16_t numChannels, const bool firstFrame = false)
|
||
{
|
||
const int16_t lookahead = frameSize >> 4;
|
||
const uint16_t inLength = ((frameSize * 3u) >> 1) + (firstFrame ? lookahead : 0);
|
||
const uint16_t chLength = ((frameSize * 3u) >> 1) + (lookahead << 1);
|
||
uint16_t ch;
|
||
|
||
for (ch = 0; ch < numChannels; ch++) // step 1: add deinterleaved input samples to resampling buffer
|
||
{
|
||
int32_t* chPcmBuf = &pcmBuffer[ch];
|
||
int32_t* chResBuf = &resampleBuffer[chLength * ch];
|
||
|
||
if (firstFrame) // construct leading sample values via extrapolation
|
||
{
|
||
memset (chResBuf, 0, (lookahead - 32) * sizeof (int32_t));
|
||
for (int8_t i = 0; i < 32; i++) chResBuf[lookahead - 32 + i] = (*chPcmBuf * i + (32 >> 1)) >> 5;
|
||
}
|
||
else
|
||
{
|
||
memcpy (chResBuf, &chResBuf[inLength], (chLength - inLength) * sizeof (int32_t)); // update memory
|
||
}
|
||
chResBuf += chLength - inLength;
|
||
|
||
for (uint16_t i = inLength; i > 0; i--, chPcmBuf += numChannels, chResBuf++)
|
||
{
|
||
*chResBuf = *chPcmBuf; // deinterleave, store in resampling buffer
|
||
}
|
||
}
|
||
|
||
for (ch = 0; ch < numChannels; ch++) // step 2: resample, reinterleave, and save to PCM input buffer
|
||
{
|
||
/*in*/int32_t* chPcmBuf = &pcmBuffer[ch];
|
||
const int32_t* chResBuf = &resampleBuffer[chLength * ch + lookahead];
|
||
|
||
for (uint16_t i = frameSize >> 1; i > 0; i--, chResBuf += 3)
|
||
{
|
||
int64_t r1 = (int64_t) chResBuf[0] * (rsfc3x[0] - 2 * SHRT_MIN) - (chResBuf[-1] + (int64_t) chResBuf[1]) * SHRT_MIN +
|
||
(int64_t) chResBuf[-lookahead] + (int64_t) chResBuf[lookahead];
|
||
int64_t r2 = (chResBuf[1] + (int64_t) chResBuf[2]) * (rsfc3x[1] - 2 * SHRT_MIN);
|
||
|
||
for (int16_t c = lookahead - 1; c > 0; c--)
|
||
{
|
||
r1 += (chResBuf[-c] + (int64_t) chResBuf[c]) * rsfc3x[c << 1];
|
||
r2 += (chResBuf[1 - c] + (int64_t) chResBuf[c + 2]) * rsfc3x[(c << 1) + 1];
|
||
}
|
||
*chPcmBuf = int32_t ((r1 - 2 * SHRT_MIN) >> 17); // lowpass sample
|
||
if (*chPcmBuf < MIN_VALUE_AUDIO24) *chPcmBuf = MIN_VALUE_AUDIO24;
|
||
else
|
||
if (*chPcmBuf > MAX_VALUE_AUDIO24) *chPcmBuf = MAX_VALUE_AUDIO24;
|
||
chPcmBuf += numChannels;
|
||
|
||
*chPcmBuf = int32_t ((r2 - 2 * SHRT_MIN) >> 17); // interp. sample
|
||
if (*chPcmBuf < MIN_VALUE_AUDIO24) *chPcmBuf = MIN_VALUE_AUDIO24;
|
||
else
|
||
if (*chPcmBuf > MAX_VALUE_AUDIO24) *chPcmBuf = MAX_VALUE_AUDIO24;
|
||
chPcmBuf += numChannels;
|
||
}
|
||
}
|
||
}
|
||
|
||
static uint16_t eaApplyLevelNorm (int32_t* /*o*/ pcmBuffer, uint16_t* oldLoudness, const uint16_t currLoudness,
|
||
const uint16_t frameSize, const uint16_t numChannels)
|
||
{
|
||
const int64_t gainOld = __min (MAX_VALUE_AUDIO24, int64_t (0.5f + MAX_VALUE_AUDIO24 * pow (10.0f, (77.0f - *oldLoudness / 512.f) / 20.0f)));
|
||
const int64_t gainNew = __min (MAX_VALUE_AUDIO24, int64_t (0.5f + MAX_VALUE_AUDIO24 * pow (10.0f, (77.0f - currLoudness / 512.f) / 20.0f)));
|
||
const int64_t gRShift = 32 + __min (3, frameSize >> 10);
|
||
const int64_t gOffset = (int64_t) 1 << (gRShift - 1);
|
||
uint16_t ch, i;
|
||
|
||
if ((frameSize & (frameSize - 1)) || !pcmBuffer) return 0;
|
||
|
||
for (i = frameSize; i > 0; i--)
|
||
{
|
||
const int64_t gainI = gainOld * i + gainNew * (frameSize - i);
|
||
|
||
for (ch = numChannels; ch > 0; ch--, pcmBuffer++)
|
||
{
|
||
*pcmBuffer = int32_t ((gOffset + *pcmBuffer * gainI) >> gRShift);
|
||
}
|
||
}
|
||
return (*oldLoudness = currLoudness);
|
||
}
|
||
|
||
#if ENABLE_STDOUT_LOAS
|
||
static uint16_t eaInitLoasHeader (uint8_t* const loasHeader, // sets up LATM/LOAS header, returns payload offset
|
||
const uint8_t* const ascUcBuf, const uint32_t ascUcSize)
|
||
{
|
||
if (ascUcSize == 0) return 0;
|
||
|
||
loasHeader[0] = 0x56; // 11-bit sync. word
|
||
loasHeader[1] = 0xE0; // AudioSyncStream()
|
||
// audioMuxLengthBytes will be placed here
|
||
loasHeader[3] = 0x20; // AudioMuxElement()
|
||
loasHeader[4] = 0x00;
|
||
memcpy (loasHeader + 5, ascUcBuf, ascUcSize); // 3 trailing bits
|
||
loasHeader[4 + ascUcSize] &= 0xE0;
|
||
loasHeader[4 + ascUcSize] |= 0x3; // first 5 bits after ASC + UC
|
||
loasHeader[5 + ascUcSize] = 0xFC;
|
||
|
||
return uint16_t (6 + ascUcSize);
|
||
}
|
||
|
||
static uint32_t eaWriteLoasFrame (const int fileHandle, uint8_t* const loasHeader, const uint16_t payloadOffset,
|
||
const uint8_t* const auBuffer, const uint32_t auSize)
|
||
{
|
||
const uint32_t audioMuxLengthBytes = payloadOffset + 1 + auSize / 255 + auSize;
|
||
uint32_t size = payloadOffset, tmp = auSize;
|
||
|
||
if (audioMuxLengthBytes > 8191) return 0;
|
||
|
||
loasHeader[1] = uint8_t (audioMuxLengthBytes >> 8) | 0xE0; // set AudioSyncStream()
|
||
loasHeader[2] = uint8_t (audioMuxLengthBytes & UCHAR_MAX);
|
||
|
||
loasHeader[size++] = (uint8_t) __min (UCHAR_MAX, tmp); // write PayloadLengthInfo()
|
||
while (tmp >= UCHAR_MAX)
|
||
{
|
||
tmp -= UCHAR_MAX;
|
||
loasHeader[size++] = (uint8_t) __min (UCHAR_MAX, tmp);
|
||
}
|
||
if ( (uint32_t) _WRITE (fileHandle, loasHeader, size) != size) return 0;
|
||
return (uint32_t) _WRITE (fileHandle, auBuffer, auSize); // then write PayloadMux()
|
||
}
|
||
#endif // ENABLE_STDOUT_LOAS
|
||
|
||
// main routine
|
||
#ifdef EXHALE_APP_WCHAR
|
||
extern "C" int wmain (const int argc, wchar_t* argv[])
|
||
#else
|
||
int main (const int argc, char* argv[])
|
||
#endif
|
||
{
|
||
if (argc <= 0) return argc; // for safety
|
||
|
||
const bool readStdin = (argc == 3 || argc == 5);
|
||
BasicWavReader wavReader;
|
||
int32_t* inPcmData = nullptr; // 24-bit WAVE audio input buffer
|
||
int32_t* inPcmRsmp = nullptr; // temporary buffer for resampler
|
||
uint8_t* outAuData = nullptr; // access unit (AU) output buffer
|
||
int inFileHandle = -1, outFileHandle = -1;
|
||
uint32_t loudStats = EA_LOUD_INIT; // valid empty loudness data
|
||
#ifdef EXHALE_APP_WCHAR
|
||
# if EA_USE_WORK_DIR
|
||
wchar_t* currPath = nullptr;
|
||
# endif
|
||
const wchar_t* exePath = argv[0];
|
||
#else
|
||
# if EA_USE_WORK_DIR
|
||
char* currPath = nullptr;
|
||
# endif
|
||
const char* exePath = argv[0];
|
||
#endif
|
||
uint16_t i, exePathEnd = 0, loudMemory = 0; // for LUFS leveling
|
||
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
|
||
uint8_t zeroDelayForSbrEncoding = (argc >= 5 && (argv[2][0] == 's' || argv[2][0] == 'S') && argv[2][1] == 0 ? 1 : 0);
|
||
#if ENABLE_STDOUT_LOAS
|
||
const bool writeStdout = (zeroDelayForSbrEncoding != 0 && argv[1][0] >= 'a' && argv[argc - 1][0] == '-' && argv[argc - 1][1] == 0);
|
||
uint16_t loasMuxOffset = 0;
|
||
uint8_t loasHeader[64] = {0};
|
||
#endif
|
||
bool enableLufsLevel = (argc >= 5 && (argv[2][0] == 'l' || argv[2][0] == 'L') && argv[2][1] == 0);
|
||
#ifdef EXHALE_APP_WIN
|
||
const HANDLE hConsole = GetStdHandle (STD_OUTPUT_HANDLE);
|
||
CONSOLE_SCREEN_BUFFER_INFO csbi;
|
||
#endif
|
||
|
||
for (i = 0; (exePath[i] != 0) && (i < USHRT_MAX); i++)
|
||
{
|
||
#ifdef EXHALE_APP_WIN
|
||
if (exePath[i] == '\\') exePathEnd = i + 1;
|
||
#else
|
||
if (exePath[i] == '/' ) exePathEnd = i + 1;
|
||
#endif
|
||
}
|
||
#ifdef EXHALE_APP_WCHAR
|
||
_setmode (_fileno (stderr), _O_U16TEXT);
|
||
|
||
const wchar_t* const exeFileName = exePath + exePathEnd;
|
||
#else
|
||
const char* const exeFileName = exePath + exePathEnd;
|
||
#endif
|
||
if ((exeFileName[0] == 0) || (i == USHRT_MAX))
|
||
{
|
||
_ERROR1 (" ERROR reading executable name or path: the string is invalid!\n\n");
|
||
|
||
return 32768; // bad executable string
|
||
}
|
||
|
||
// print program header with compile info in plain text if we pass -V
|
||
#ifdef EXHALE_APP_WCHAR
|
||
if ((argc > 1) && (wcscmp (argv[1], L"-V") == 0 || wcscmp (argv[1], L"-v") == 0))
|
||
#else
|
||
if ((argc > 1) && (strcmp (argv[1], "-V") == 0 || strcmp (argv[1], "-v") == 0))
|
||
#endif
|
||
{
|
||
#if defined (__arm__) || defined (__aarch64__) || defined (__arm64__)
|
||
fprintf_s (stdout, "exhale %s.%s%s (ARM",
|
||
#elif defined (_WIN64) || defined (WIN64) || defined (_LP64) || defined (__LP64__) || defined (__x86_64) || defined (__x86_64__)
|
||
fprintf_s (stdout, "exhale %s.%s%s (x64",
|
||
#else // 32-bit OS
|
||
fprintf_s (stdout, "exhale %s.%s%s (x86",
|
||
#endif
|
||
EXHALELIB_VERSION_MAJOR, EXHALELIB_VERSION_MINOR, EXHALELIB_VERSION_BUGFIX);
|
||
#ifdef EXHALE_APP_WCHAR
|
||
if (wcscmp (argv[1], L"-V") == 0)
|
||
#else
|
||
if (strcmp (argv[1], "-V") == 0)
|
||
#endif
|
||
{
|
||
char fts[] = __TIMESTAMP__; // append month and year of file time
|
||
fts[7] = 0;
|
||
#ifdef EXHALE_APP_WCHAR
|
||
fprintf_s (stdout, ", Unicode");
|
||
#endif
|
||
fprintf_s (stdout, ", %s %s)\n", &fts[4], &fts[sizeof (fts) - 5]);
|
||
}
|
||
else fprintf_s (stdout, ")\n");
|
||
|
||
return 0;
|
||
}
|
||
|
||
// print program header with compile info
|
||
#if ENABLE_STDOUT_LOAS
|
||
if (writeStdout)
|
||
{
|
||
# ifdef EXHALE_APP_WCHAR
|
||
wchar_t dateStr[12] = {0};
|
||
|
||
mbstowcs_s (nullptr, dateStr, 12, __DATE__, _TRUNCATE);
|
||
_ERROR1 ("\n ----------------------------------------------------------------------\n");
|
||
_ERROR2 (" | exhale (stdout mode, built on %s) - written by C.R.Helmrich |\n", dateStr);
|
||
# else
|
||
_ERROR1 ("\n ----------------------------------------------------------------------\n");
|
||
_ERROR2 (" | exhale (stdout mode, built on %s) - written by C.R.Helmrich |\n", __DATE__);
|
||
# endif
|
||
_ERROR1 (" ----------------------------------------------------------------------\n\n");
|
||
}
|
||
else
|
||
{
|
||
#endif
|
||
fprintf_s (stdout, "\n ---------------------------------------------------------------------\n");
|
||
fprintf_s (stdout, " | ");
|
||
#ifdef EXHALE_APP_WIN
|
||
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
|
||
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 (__arm__) || defined (__aarch64__) || defined (__arm64__)
|
||
fprintf_s (stdout, " | version %s.%s%s (ARM, built on %s) - written by C.R.Helmrich |\n",
|
||
#elif 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");
|
||
#if ENABLE_STDOUT_LOAS
|
||
}
|
||
#endif
|
||
|
||
// check arg. list, print usage if needed
|
||
if ((argc < 3) || (argc > 6) || (argc > 1 && argv[1][1] != 0))
|
||
{
|
||
fprintf_s (stdout, " Copyright 2018-2024 C.R.Helmrich, project ecodis. See License.htm for details.\n\n");
|
||
|
||
fprintf_s (stdout, " This software is made available under the exhale Copyright 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");
|
||
#ifdef EXHALE_APP_WIN
|
||
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
|
||
#ifdef EXHALE_APP_WCHAR
|
||
fwprintf_s (stdout, L"%s preset [inputWaveFile.wav] outputMP4File.m4a\n\n where\n\n", exeFileName);
|
||
#else
|
||
fprintf_s (stdout, "%s preset [inputWaveFile.wav] outputMP4File.m4a\n\n where\n\n", exeFileName);
|
||
#endif
|
||
#ifdef EXHALE_APP_WIN
|
||
fprintf_s (stdout, " preset\t= # (0-9) low-complexity ISO/MPEG-D Extended HE-AAC at 16<31>#+48 kbit/s\n");
|
||
fprintf_s (stdout, " \t (a-g) low-complexity Extended HE-AAC using eSBR at 12<31>#+36 kbit/s\n");
|
||
#else
|
||
fprintf_s (stdout, " preset\t= # (0-9) low-complexity ISO/MPEG-D Extended HE-AAC at 16*#+48 kbit/s\n");
|
||
fprintf_s (stdout, " \t (a-g) low-complexity Extended HE-AAC using eSBR at 12*#+36 kbit/s\n");
|
||
#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");
|
||
#ifdef EXHALE_APP_WIN
|
||
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 !EA_USE_WORK_DIR
|
||
if (exePathEnd > 0)
|
||
{
|
||
# ifdef EXHALE_APP_WIN
|
||
# ifdef EXHALE_APP_WCHAR
|
||
fwprintf_s (stdout, L" \tUse filename prefix .\\ for the current directory if this executable was\n\tcalled with a path (call: %s).\n", exePath);
|
||
# else
|
||
fprintf_s (stdout, " \tUse filename prefix .\\ for the current directory if this executable was\n\tcalled with a path (call: %s).\n", exePath);
|
||
# endif
|
||
# else
|
||
fprintf_s (stdout, " \tUse filename prefix ./ for the current directory if this executable was\n\tcalled with a path (call: %s).\n", exePath);
|
||
# endif
|
||
}
|
||
#endif
|
||
return 0; // no arguments, which is OK
|
||
}
|
||
|
||
// check preset mode, derive coder config
|
||
if ((*argv[1] >= '0' && *argv[1] <= '9') || (*argv[1] >= 'a' && *argv[1] <= 'g'))
|
||
{
|
||
i = (uint16_t) argv[1][0];
|
||
compatibleExtensionFlag = (i & 0x40) >> 6;
|
||
coreSbrFrameLengthIndex = (i > 0x60 ? 5 : (i & 0x20) >> 5);
|
||
variableCoreBitRateMode = (i & 0x0F) - (i >> 6);
|
||
}
|
||
else if (*argv[1] == '#') // default mode
|
||
{
|
||
#if ENABLE_STDOUT_LOAS
|
||
if (writeStdout)
|
||
{
|
||
_ERROR2 (" Default preset is specified, encoding to low-complexity xHE-AAC, preset mode %d\n\n", variableCoreBitRateMode);
|
||
}
|
||
else
|
||
#endif
|
||
fprintf_s (stdout, " Default preset is specified, encoding to low-complexity xHE-AAC, preset mode %d\n\n", variableCoreBitRateMode);
|
||
}
|
||
else
|
||
{
|
||
_ERROR2 (" ERROR reading preset mode: character %s is not supported! Use 0-9 or a-g.\n\n", argv[1]);
|
||
|
||
return 16384; // preset isn't supported
|
||
}
|
||
|
||
const bool enableSbrCoding = (coreSbrFrameLengthIndex >= 3); // SBR coding flag
|
||
const unsigned frameLength = (3 + coreSbrFrameLengthIndex) << 8; // dec. output
|
||
const unsigned startLength = (frameLength * 25) >> 4; // encoder PCM look-ahead
|
||
|
||
if (readStdin) // configure stdin
|
||
{
|
||
#ifdef EXHALE_APP_WIN
|
||
inFileHandle = _fileno (stdin);
|
||
if (_setmode (inFileHandle, _O_RDONLY | _O_BINARY) == -1)
|
||
{
|
||
_ERROR1 (" ERROR while trying to set stdin to binary mode! Has stdin been closed?\n\n");
|
||
inFileHandle = -1;
|
||
|
||
goto mainFinish; // stdin setup error
|
||
}
|
||
#else
|
||
inFileHandle = fileno (stdin);
|
||
#endif
|
||
}
|
||
else // argc = 4, open input file
|
||
{
|
||
#ifdef EXHALE_APP_WCHAR
|
||
const wchar_t* inFileName = argv[argc - 2];
|
||
#else
|
||
const char* inFileName = argv[argc - 2];
|
||
#endif
|
||
uint16_t inPathEnd = 0;
|
||
|
||
for (i = 0; (inFileName[i] != 0) && (i < USHRT_MAX); i++)
|
||
{
|
||
#ifdef EXHALE_APP_WIN
|
||
if (inFileName[i] == '\\') inPathEnd = i + 1;
|
||
#else
|
||
if (inFileName[i] == '/' ) inPathEnd = i + 1;
|
||
#endif
|
||
}
|
||
if ((inFileName[0] == 0) || (i == USHRT_MAX))
|
||
{
|
||
_ERROR1 (" 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
|
||
{
|
||
#if EA_USE_WORK_DIR
|
||
# ifdef __linux__
|
||
if ((currPath == nullptr) && (currPath = _GETCWD (NULL, 0)) != nullptr)
|
||
# else
|
||
if ((currPath == nullptr) && (currPath = _GETCWD (NULL, 1)) != nullptr)
|
||
# endif
|
||
{
|
||
exePath = currPath;
|
||
exePathEnd = (uint16_t) __min (USHRT_MAX - 1, _STRLEN (currPath));
|
||
# ifdef EXHALE_APP_WIN
|
||
if (currPath[exePathEnd - 1] != '\\') currPath[exePathEnd++] = '\\';
|
||
# else
|
||
if (currPath[exePathEnd - 1] != '/' ) currPath[exePathEnd++] = '/';
|
||
# endif
|
||
}
|
||
#endif
|
||
#ifdef EXHALE_APP_WCHAR
|
||
inFileName = (const wchar_t*) malloc ((exePathEnd + i + 1) * sizeof (wchar_t)); // 0-terminated
|
||
memcpy ((void*) inFileName, exePath, exePathEnd * sizeof (wchar_t)); // prepend executable path
|
||
memcpy ((void*)(inFileName + exePathEnd), argv[argc - 2], (i + 1) * sizeof (wchar_t));// to name
|
||
#else
|
||
inFileName = (const char*) malloc ((exePathEnd + i + 1) * sizeof (char)); // 0-terminated string
|
||
memcpy ((void*) inFileName, exePath, exePathEnd * sizeof (char)); // prepend executable path ...
|
||
memcpy ((void*)(inFileName + exePathEnd), argv[argc - 2], (i + 1) * sizeof (char)); //...to name
|
||
#endif
|
||
}
|
||
|
||
#ifdef EXHALE_APP_WIN
|
||
if (_SOPENS (&inFileHandle, inFileName, _O_RDONLY | _O_SEQUENTIAL | _O_BINARY, _SH_DENYWR, _S_IREAD) != 0)
|
||
#else
|
||
if ((inFileHandle = ::open (inFileName, O_RDONLY, 0666)) == -1)
|
||
#endif
|
||
{
|
||
_ERROR2 (" 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);
|
||
}
|
||
|
||
#ifdef EXHALE_APP_WIN
|
||
if ((wavReader.open (inFileHandle, startLength, readStdin ? LLONG_MAX : _filelengthi64 (inFileHandle)) != 0) ||
|
||
#else
|
||
if ((wavReader.open (inFileHandle, startLength, readStdin ? LLONG_MAX : lseek (inFileHandle, 0, 2 /*SEEK_END*/)) != 0) ||
|
||
#endif
|
||
(wavReader.getSampleRate () >= 1000 && wavReader.getSampleRate () < 22050 && enableSbrCoding) || (wavReader.getNumChannels () >= (enableSbrCoding ? 3u : 7u)))
|
||
{
|
||
_ERROR1 (" ERROR while trying to open WAVE file: invalid or unsupported audio format!\n\n");
|
||
|
||
if (wavReader.getSampleRate () >= 1000 && wavReader.getSampleRate () < 22050 && enableSbrCoding)
|
||
{
|
||
_ERROR2 (" The sampling rate is %d kHz but encoding using eSBR requires at least 22 kHz.\n\n", wavReader.getSampleRate () / 1000);
|
||
}
|
||
if (wavReader.getNumChannels () >= 3 && enableSbrCoding)
|
||
{
|
||
_ERROR2 (" The channel count is %d but exhale can't encode multichannel audio with eSBR.\n\n", wavReader.getNumChannels ());
|
||
}
|
||
i = 8192; // return value
|
||
|
||
goto mainFinish; // audio format invalid
|
||
}
|
||
#if ENABLE_STDOUT_LOAS
|
||
else if (writeStdout) // configure stdout
|
||
{
|
||
if (wavReader.getNumChannels () != 2)
|
||
{
|
||
_ERROR1 (" ERROR during encoding! Input audio must be stereo for encoding to stdout!\n\n");
|
||
i = 8192; // return value
|
||
|
||
goto mainFinish; // stdout audio error
|
||
}
|
||
|
||
fflush (stdout);
|
||
# ifdef EXHALE_APP_WIN
|
||
outFileHandle = _fileno (stdout);
|
||
if (_setmode (outFileHandle, _O_BINARY) == -1)
|
||
{
|
||
_ERROR1 (" ERROR while trying to set stdout to binary mode! Has stdout been closed?\n\n");
|
||
outFileHandle = -1;
|
||
|
||
goto mainFinish; // stdout setup error
|
||
}
|
||
# else
|
||
outFileHandle = fileno (stdout);
|
||
# endif
|
||
|
||
if ((wavReader.getSampleRate () > 32100 + (unsigned) variableCoreBitRateMode * 12000 + (variableCoreBitRateMode >> 2) * 3900) &&
|
||
(variableCoreBitRateMode > 1 || wavReader.getSampleRate () != 48000) && !enableSbrCoding)
|
||
{
|
||
i = (variableCoreBitRateMode > 4 ? 96 : __min (64, 32 + variableCoreBitRateMode * 12));
|
||
# ifdef EXHALE_APP_WCHAR
|
||
fwprintf_s (stderr, L" ERROR during encoding! Input sample rate must be <=%d kHz for preset mode %d!\n\n", i, variableCoreBitRateMode);
|
||
# else
|
||
fprintf_s (stderr, " ERROR during encoding! Input sample rate must be <=%d kHz for preset mode %d!\n\n", i, variableCoreBitRateMode);
|
||
# endif
|
||
i = 4096; // return value
|
||
|
||
goto mainFinish; // ask for resampling
|
||
}
|
||
if ((wavReader.getSampleRate () > 32000) && !enableSbrCoding && (variableCoreBitRateMode <= 1))
|
||
{
|
||
if (wavReader.getSampleRate () == 48000)
|
||
{
|
||
_ERROR2 (" NOTE: Downsampling the input audio from 48 kHz to 32 kHz with preset mode %d\n\n", variableCoreBitRateMode);
|
||
}
|
||
else
|
||
{
|
||
_ERROR2 (" WARNING: The input sampling rate should be 32 kHz or less for preset mode %d!\n\n", variableCoreBitRateMode);
|
||
}
|
||
}
|
||
}
|
||
#endif
|
||
else // WAVE OK, open output file
|
||
{
|
||
#ifdef EXHALE_APP_WCHAR
|
||
const wchar_t* outFileName = argv[argc - 1];
|
||
#else
|
||
const char* outFileName = argv[argc - 1];
|
||
#endif
|
||
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++)
|
||
{
|
||
#ifdef EXHALE_APP_WIN
|
||
if (outFileName[i] == '\\') outPathEnd = i + 1;
|
||
#else
|
||
if (outFileName[i] == '/' ) outPathEnd = i + 1;
|
||
#endif
|
||
}
|
||
if ((outFileName[0] == 0) || (i == USHRT_MAX))
|
||
{
|
||
_ERROR1 (" ERROR reading output file name or path: the string is invalid!\n\n");
|
||
|
||
goto mainFinish; // bad output string
|
||
}
|
||
|
||
if ((wavReader.getSampleRate () > 32100 + (unsigned) variableCoreBitRateMode * 12000 + (variableCoreBitRateMode >> 2) * 3900) &&
|
||
(variableCoreBitRateMode > 1 || wavReader.getSampleRate () != 48000) && !enableSbrCoding)
|
||
{
|
||
i = (variableCoreBitRateMode > 4 ? 96 : __min (64, 32 + variableCoreBitRateMode * 12));
|
||
#ifdef EXHALE_APP_WCHAR
|
||
fwprintf_s (stderr, L" ERROR during encoding! Input sample rate must be <=%d kHz for preset mode %d!\n\n", i, variableCoreBitRateMode);
|
||
#else
|
||
fprintf_s (stderr, " ERROR during encoding! Input sample rate must be <=%d kHz for preset mode %d!\n\n", i, variableCoreBitRateMode);
|
||
#endif
|
||
i = 4096; // return value
|
||
|
||
goto mainFinish; // ask for resampling
|
||
}
|
||
if ((wavReader.getSampleRate () > 32000) && !enableSbrCoding && (variableCoreBitRateMode <= 1))
|
||
{
|
||
if (wavReader.getSampleRate () == 48000)
|
||
{
|
||
fprintf_s (stdout, " NOTE: Downsampling the input audio from 48 kHz to 32 kHz with preset mode %d\n\n", variableCoreBitRateMode);
|
||
}
|
||
else
|
||
{
|
||
_ERROR2 (" WARNING: The input sampling rate should be 32 kHz or less for preset mode %d!\n\n", variableCoreBitRateMode);
|
||
}
|
||
}
|
||
|
||
if (outPathEnd == 0) // name has no path
|
||
{
|
||
#if EA_USE_WORK_DIR
|
||
# ifdef __linux__
|
||
if ((currPath != exePath) && (currPath = _GETCWD (NULL, 0)) != nullptr)
|
||
# else
|
||
if ((currPath != exePath) && (currPath = _GETCWD (NULL, 1)) != nullptr)
|
||
# endif
|
||
{
|
||
exePath = currPath;
|
||
exePathEnd = (uint16_t) __min (USHRT_MAX - 1, _STRLEN (currPath));
|
||
# ifdef EXHALE_APP_WIN
|
||
if (currPath[exePathEnd - 1] != '\\') currPath[exePathEnd++] = '\\';
|
||
# else
|
||
if (currPath[exePathEnd - 1] != '/' ) currPath[exePathEnd++] = '/';
|
||
# endif
|
||
}
|
||
#endif
|
||
#ifdef EXHALE_APP_WCHAR
|
||
outFileName = (const wchar_t*) malloc ((exePathEnd + i + 1) * sizeof (wchar_t)); // 0-terminated
|
||
memcpy ((void*) outFileName, exePath, exePathEnd * sizeof (wchar_t)); // prepend executable path
|
||
memcpy ((void*)(outFileName + exePathEnd), argv[argc - 1], (i + 1) * sizeof (wchar_t));// to name
|
||
#else
|
||
outFileName = (const char*) malloc ((exePathEnd + i + 1) * sizeof (char)); // 0-terminated string
|
||
memcpy ((void*) outFileName, exePath, exePathEnd * sizeof (char)); // prepend executable path ...
|
||
memcpy ((void*)(outFileName + exePathEnd), argv[argc - 1], (i + 1) * sizeof (char)); //...to name
|
||
#endif
|
||
}
|
||
|
||
i = (readStdin ? O_RDWR : O_WRONLY);
|
||
#ifdef EXHALE_APP_WIN
|
||
if (_SOPENS (&outFileHandle, outFileName, i | _O_SEQUENTIAL | _O_CREAT | _O_EXCL | _O_BINARY, _SH_DENYRD, _S_IWRITE) != 0)
|
||
#else
|
||
if ((outFileHandle = ::open (outFileName, i | O_CREAT | O_EXCL, 0666)) == -1)
|
||
#endif
|
||
{
|
||
_ERROR2 (" 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 (i == 22050 && enableSbrCoding) loudStats |= 0xC0000000; // fix dflt_freq_scale in SBR header for 22050-Hz input
|
||
|
||
if ((wavReader.getNumChannels () > 3 || enableSbrCoding) && (i == 57600 || i == 38400 || i == 28800 || i == 19200)) // BL USAC
|
||
{
|
||
#ifdef EXHALE_APP_WCHAR
|
||
fwprintf_s (stderr, L" ERROR: exhale does not support %d-channel coding with %d Hz sampling rate.\n\n", wavReader.getNumChannels (), i);
|
||
#else
|
||
fprintf_s (stderr, " ERROR: exhale does not support %d-channel coding with %d Hz sampling rate.\n\n", wavReader.getNumChannels (), i);
|
||
#endif
|
||
goto mainFinish; // encoder config error
|
||
}
|
||
else
|
||
{
|
||
const unsigned numChannels = wavReader.getNumChannels ();
|
||
const unsigned inSampDepth = wavReader.getBitDepth ();
|
||
const unsigned sbrEncDelay = (enableSbrCoding ? 962 : 0);
|
||
const bool enableUpsampler = eaInitUpsampler2x (&inPcmRsmp, variableCoreBitRateMode, i, frameLength, numChannels);
|
||
const bool enableResampler = (enableSbrCoding ? false : // no 3:2 downsampling required when encoding in SBR mode
|
||
eaInitDownsampler (&inPcmRsmp, variableCoreBitRateMode, i, frameLength, numChannels));
|
||
const uint16_t firstLength = uint16_t (enableUpsampler ? (frameLength >> 1) + 32 : (enableResampler ? startLength : frameLength));
|
||
const unsigned inFrameSize = (enableResampler ? startLength : frameLength) * sizeof (int32_t); // max buffer size
|
||
const unsigned resampRatio = (enableResampler ? 3 : 1); // for resampling ratio
|
||
const unsigned resampShift = (enableResampler || enableUpsampler ? 1 : 0);
|
||
#ifdef FULL_FRM_LOOKAHEAD
|
||
const uint16_t inPadLength = uint16_t ((((frameLength << 1) - startLength) * resampRatio) >> resampShift) +
|
||
(enableSbrCoding ? firstLength - (sbrEncDelay >> resampShift) - (enableUpsampler ? 32 : 0) : 0);
|
||
#endif
|
||
const int64_t expectLength = (wavReader.getDataBytesLeft () << resampShift) / int64_t ((numChannels * inSampDepth * resampRatio) >> 3);
|
||
|
||
if (enableUpsampler) // notify by printf
|
||
{
|
||
#if ENABLE_STDOUT_LOAS
|
||
if (writeStdout) // relocate to stderr
|
||
{
|
||
# ifdef EXHALE_APP_WCHAR
|
||
fwprintf_s (stderr, L" NOTE: Upsampling the input audio from %d kHz to %d kHz with preset mode %d\n\n",
|
||
# else
|
||
fprintf_s (stderr, " NOTE: Upsampling the input audio from %d kHz to %d kHz with preset mode %d\n\n",
|
||
# endif
|
||
i / 1000, i / 500, variableCoreBitRateMode);
|
||
}
|
||
else
|
||
#endif
|
||
fprintf_s (stdout, " NOTE: Upsampling the input audio from %d kHz to %d kHz with preset mode %d\n\n", i / 1000, i / 500, variableCoreBitRateMode);
|
||
}
|
||
|
||
if (variableCoreBitRateMode == 0)
|
||
{
|
||
if (enableSbrCoding)
|
||
_ERROR1 (" WARNING: The usage of preset mode a is not recommended since the audio quality\n resulting from preset a does not reflect the full capabilities of the Extended\n HE-AAC standard. Therefore, use the lowest bit-rate modes only when necessary!\n\n");
|
||
else
|
||
_ERROR1 (" WARNING: The usage of preset mode 0 is not recommended since the audio quality\n resulting from preset 0 does not reflect the full capabilities of the Extended\n HE-AAC standard. Therefore, use the lowest bit-rate modes only when necessary!\n\n");
|
||
}
|
||
|
||
// allocate dynamic frame memory buffers
|
||
inPcmData = (int32_t*) malloc (inFrameSize * numChannels); // max frame in size
|
||
#ifdef NO_PREROLL_DATA
|
||
outAuData = (uint8_t*) malloc ((6144 >> 3) * numChannels); // max frame AU size
|
||
#else
|
||
outAuData = (uint8_t*) malloc ((9984 >> 3) * numChannels); // max frame AU size
|
||
#endif
|
||
if ((inPcmData == nullptr) || (outAuData == nullptr))
|
||
{
|
||
_ERROR1 (" ERROR while trying to allocate dynamic memory! Not enough free RAM available!\n\n");
|
||
i = 2048; // return value
|
||
|
||
goto mainFinish; // memory alloc error
|
||
}
|
||
|
||
#ifdef FULL_FRM_LOOKAHEAD
|
||
memset (inPcmData, 0, inPadLength * numChannels * sizeof (int32_t)); // padding
|
||
|
||
if (inPadLength + wavReader.read (inPcmData + inPadLength * numChannels, firstLength - inPadLength) != firstLength)
|
||
#else
|
||
if (wavReader.read (inPcmData, firstLength) != firstLength) // full first frame
|
||
#endif
|
||
{
|
||
_ERROR1 (" 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 () << resampShift) / resampRatio;
|
||
const bool userIndepPeriod = (argc >= 5 && argv[3][0] > '0' && argv[3][0] <= '9' && argv[3][1] >= '0' && argv[3][1] <= '9' && argv[3][2] == 0);
|
||
const unsigned indepPeriod = (userIndepPeriod ? 10 * (argv[3][0] - 48) + (argv[3][1] - 48) : (sampleRate < 48000 ? sampleRate - 320u : 50u << 10u) / frameLength);
|
||
#if ENABLE_STDOUT_LOAS
|
||
const unsigned mod3Percent = (writeStdout ? 0 : unsigned ((expectLength * (3 + (coreSbrFrameLengthIndex & 3))) >> 17));
|
||
uint32_t byteCount = 0, bw = (numChannels < 7 ? loudStats | (writeStdout ? 0x4A0022CB /*-23 LUFS*/ : 0) : 0);
|
||
#else
|
||
const unsigned mod3Percent = unsigned ((expectLength * (3 + (coreSbrFrameLengthIndex & 3))) >> 17);
|
||
uint32_t byteCount = 0, bw = (numChannels < 7 ? loudStats : 0);
|
||
#endif
|
||
uint32_t br, bwMax = 0, bwTmp = 0; // br will 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
|
||
+ (enableUpsampler && (variableCoreBitRateMode < 9) ? 1 : 0)
|
||
#if !RESTRICT_TO_AAC
|
||
, !(argc >= 5 && (argv[2][0] == 'n' || argv[2][0] == 'N') && argv[2][1] == 0), compatibleExtensionFlag > 0
|
||
#endif
|
||
);
|
||
BasicMP4Writer mp4Writer; // .m4a file
|
||
|
||
// init encoder, generate UsacConfig()
|
||
memset (outAuData, 0, 108 * sizeof (uint8_t)); // max. allowed ASC + UC size
|
||
#ifdef FULL_FRM_LOOKAHEAD
|
||
if (!enableSbrCoding) zeroDelayForSbrEncoding = 0;
|
||
|
||
if (zeroDelayForSbrEncoding)
|
||
{
|
||
// resample PCM priming if necessary
|
||
if (enableUpsampler) eaApplyUpsampler2x (inPcmData, inPcmRsmp, frameLength, numChannels, true);
|
||
else
|
||
if (enableResampler) eaApplyDownsampler (inPcmData, inPcmRsmp, frameLength, numChannels, true);
|
||
}
|
||
// extrapolate samples in padding region of first frame since exhaleLib can't
|
||
// take over this job when inPadLength > 0. Improves gapless playback.
|
||
else if (inPadLength > 0) eaExtrapolate (inPcmData, inPadLength, frameLength, numChannels, true); // fade-in
|
||
|
||
// signal 1-frame skip and PCM priming
|
||
outAuData[0] = 1 | zeroDelayForSbrEncoding * (uint8_t) __min (254, (firstLength - inPadLength) << (resampShift + 1));
|
||
#endif
|
||
i = exhaleEnc.initEncoder (outAuData, &bw); // bw stores actual ASC + UC size
|
||
#ifdef FULL_FRM_LOOKAHEAD
|
||
if ((i == 0) && (zeroDelayForSbrEncoding))
|
||
{
|
||
if (wavReader.read (inPcmData, (frameLength * resampRatio) >> resampShift) == 0) i = 1; // discard priming
|
||
}
|
||
#endif
|
||
|
||
#if ENABLE_STDOUT_LOAS
|
||
if ((i == 0) && writeStdout)
|
||
{
|
||
br = 0; // init frame count & header
|
||
if ((loasMuxOffset = eaInitLoasHeader (loasHeader, outAuData, bw)) == 0) i = 1;
|
||
else enableLufsLevel = true;
|
||
}
|
||
else
|
||
#endif
|
||
if ((i |= mp4Writer.open (outFileHandle, sampleRate, numChannels, inSampDepth, frameLength,
|
||
#ifdef FULL_FRM_LOOKAHEAD
|
||
(frameLength << (enableSbrCoding && !zeroDelayForSbrEncoding ? 1 : 0))
|
||
#else
|
||
startLength + sbrEncDelay
|
||
#endif
|
||
#ifndef NO_PREROLL_DATA
|
||
- frameLength
|
||
#endif
|
||
, indepPeriod, outAuData, bw, (time (nullptr) + 2082844800) & UINT_MAX, (char) variableCoreBitRateMode)) != 0)
|
||
{
|
||
_ERROR2 (" ERROR while trying to initialize exhale 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
|
||
{
|
||
#if ENABLE_STDOUT_LOAS
|
||
if (writeStdout) // print to stderr
|
||
{
|
||
# ifdef EXHALE_APP_WCHAR
|
||
fwprintf_s (stderr, L" Encoding %d-kHz %d-channel %d-bit WAVE to low-complexity xHE-AAC at %d kbit/s\n\n", sampleRate / 1000,
|
||
# else
|
||
fprintf_s (stderr, " Encoding %d-kHz %d-channel %d-bit WAVE to low-complexity xHE-AAC at %d kbit/s\n\n", sampleRate / 1000,
|
||
# endif
|
||
numChannels, inSampDepth, __min (5, numChannels) * (((24 + variableCoreBitRateMode * 8) * (enableSbrCoding ? 3 : 4)) >> 2));
|
||
}
|
||
else
|
||
#endif
|
||
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 (5, numChannels) * (((24 + variableCoreBitRateMode * 8) * (enableSbrCoding ? 3 : 4)) >> 2));
|
||
}
|
||
if (!readStdin && (mod3Percent > 0))
|
||
{
|
||
#ifdef EXHALE_APP_WIN
|
||
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 ENABLE_STDOUT_LOAS
|
||
if (!readStdin && !writeStdout) // reserve space required for MP4 file header
|
||
#else
|
||
if (!readStdin) // reserve space for MP4 file header. TODO: nasty, avoid this
|
||
#endif
|
||
{
|
||
if ((headerRes = (uint32_t) mp4Writer.initHeader (uint32_t (__min (UINT_MAX - startLength, expectLength)), sbrEncDelay >> 2)) < 666)
|
||
{
|
||
_ERROR2 ("\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
|
||
}
|
||
}
|
||
#if 0
|
||
std::cout << "\n" << "headerSizeBytes " << (headerRes - 34) << "\n";
|
||
#endif
|
||
i = 1; // for progress bar
|
||
|
||
// resample initial frame if necessary
|
||
if (enableUpsampler) eaApplyUpsampler2x (inPcmData, inPcmRsmp, frameLength, numChannels, !zeroDelayForSbrEncoding);
|
||
else
|
||
if (enableResampler) eaApplyDownsampler (inPcmData, inPcmRsmp, frameLength, numChannels, !zeroDelayForSbrEncoding);
|
||
|
||
// initial frame, encode look-ahead AU
|
||
if ((bw = exhaleEnc.encodeLookahead ()) < 3)
|
||
{
|
||
_ERROR2 ("\n ERROR while trying to create first audio 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
|
||
}
|
||
#ifdef FULL_FRM_LOOKAHEAD
|
||
if (zeroDelayForSbrEncoding)
|
||
{
|
||
if (loudnessEst.addNewPcmData (frameLength))
|
||
{
|
||
# if USE_EXHALELIB_DLL
|
||
exhaleDelete (&exhaleEnc);
|
||
# endif
|
||
goto mainFinish; // loudness error
|
||
}
|
||
}
|
||
wavReader.read (inPcmData, (frameLength * resampRatio) >> resampShift); // discard the initial look-ahead AU
|
||
|
||
// resample leading frame if necessary
|
||
if (enableUpsampler) eaApplyUpsampler2x (inPcmData, inPcmRsmp, frameLength, numChannels);
|
||
else
|
||
if (enableResampler) eaApplyDownsampler (inPcmData, inPcmRsmp, frameLength, numChannels);
|
||
|
||
// leading frame, actual look-ahead AU
|
||
if ((bw = exhaleEnc.encodeFrame ()) < 3)
|
||
{
|
||
_ERROR2 ("\n ERROR while trying to create first audio 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
|
||
}
|
||
#endif
|
||
bwTmp = bw;
|
||
#ifdef NO_PREROLL_DATA
|
||
if (bwMax < bw) bwMax = bw;
|
||
|
||
// write first AU, add frame to header
|
||
if ((mp4Writer.addFrameAU (outAuData, bw) != (int) bw) || loudnessEst.addNewPcmData (frameLength))
|
||
{
|
||
# if USE_EXHALELIB_DLL
|
||
exhaleDelete (&exhaleEnc);
|
||
# endif
|
||
goto mainFinish; // writeout error
|
||
}
|
||
byteCount += bw;
|
||
#else
|
||
if (loudnessEst.addNewPcmData (frameLength))
|
||
{
|
||
# if USE_EXHALELIB_DLL
|
||
exhaleDelete (&exhaleEnc);
|
||
# endif
|
||
goto mainFinish; // estimation error
|
||
}
|
||
#endif
|
||
|
||
while (wavReader.read (inPcmData, (frameLength * resampRatio) >> resampShift) > 0) // read a new audio frame
|
||
{
|
||
// resample audio frame if necessary
|
||
if (enableUpsampler) eaApplyUpsampler2x (inPcmData, inPcmRsmp, frameLength, numChannels);
|
||
else
|
||
if (enableResampler) eaApplyDownsampler (inPcmData, inPcmRsmp, frameLength, numChannels);
|
||
|
||
// frame coding loop, encode next AU
|
||
loudnessEst.addNewPcmData (frameLength);
|
||
if (enableLufsLevel) eaApplyLevelNorm (inPcmData, &loudMemory, loudnessEst.getStatistics () >> 16, frameLength, numChannels);
|
||
|
||
if ((bw = exhaleEnc.encodeFrame ()) < 3)
|
||
{
|
||
_ERROR2 ("\n ERROR while trying to create audio frame: error value %d was returned!\n\n", bw);
|
||
i = 2; // return value
|
||
#if USE_EXHALELIB_DLL
|
||
exhaleDelete (&exhaleEnc);
|
||
#endif
|
||
goto mainFinish; // encoding error
|
||
}
|
||
bwTmp = (enableSbrCoding ? bw : (bwTmp + bw) >> 1u);
|
||
if (bwMax < bwTmp) bwMax = bwTmp;
|
||
bwTmp = bw;
|
||
|
||
// write new AU, add frame to header
|
||
#if ENABLE_STDOUT_LOAS
|
||
if (writeStdout)
|
||
{
|
||
if (eaWriteLoasFrame (outFileHandle, loasHeader, loasMuxOffset, outAuData, bw) != bw)
|
||
{
|
||
# if USE_EXHALELIB_DLL
|
||
exhaleDelete (&exhaleEnc);
|
||
# endif
|
||
goto mainFinish; // writer error
|
||
}
|
||
if (br < UINT_MAX) br++;
|
||
}
|
||
else
|
||
#endif
|
||
if (mp4Writer.addFrameAU (outAuData, bw) != (int) bw)
|
||
{
|
||
#if USE_EXHALELIB_DLL
|
||
exhaleDelete (&exhaleEnc);
|
||
#endif
|
||
goto mainFinish; // writeout error
|
||
}
|
||
byteCount += bw;
|
||
|
||
if (!readStdin && (mod3Percent > 0) && !(mp4Writer.getFrameCount () % mod3Percent))
|
||
{
|
||
if ((i++) < (enableSbrCoding ? 17 : 34))
|
||
{
|
||
fprintf_s (stdout, "-"); fflush (stdout);
|
||
}
|
||
}
|
||
} // frame loop
|
||
|
||
// resample the last frame if necessary
|
||
if (enableUpsampler) eaApplyUpsampler2x (inPcmData, inPcmRsmp, frameLength, numChannels);
|
||
else
|
||
if (enableResampler) eaApplyDownsampler (inPcmData, inPcmRsmp, frameLength, numChannels);
|
||
|
||
// end of coding loop, encode final AU
|
||
loudnessEst.addNewPcmData (frameLength);
|
||
if (enableLufsLevel) eaApplyLevelNorm (inPcmData, &loudMemory, loudnessEst.getStatistics () >> 16, frameLength, numChannels);
|
||
|
||
if ((bw = exhaleEnc.encodeFrame ()) < 3)
|
||
{
|
||
_ERROR2 ("\n ERROR while trying to create audio 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
|
||
}
|
||
bwTmp = (enableSbrCoding ? bw : (bwTmp + bw) >> 1u);
|
||
if (bwMax < bwTmp) bwMax = bwTmp;
|
||
bwTmp = bw;
|
||
|
||
// write final AU, add frame to header
|
||
#if ENABLE_STDOUT_LOAS
|
||
if (writeStdout)
|
||
{
|
||
if (eaWriteLoasFrame (outFileHandle, loasHeader, loasMuxOffset, outAuData, bw) != bw)
|
||
{
|
||
# if USE_EXHALELIB_DLL
|
||
exhaleDelete (&exhaleEnc);
|
||
# endif
|
||
goto mainFinish; // writeout error
|
||
}
|
||
if (br < UINT_MAX) br++;
|
||
}
|
||
else
|
||
#endif
|
||
if (mp4Writer.addFrameAU (outAuData, bw) != (int) bw)
|
||
{
|
||
#if USE_EXHALELIB_DLL
|
||
exhaleDelete (&exhaleEnc);
|
||
#endif
|
||
goto mainFinish; // writeout error
|
||
}
|
||
byteCount += bw;
|
||
|
||
const int64_t actualLength = (wavReader.getDataBytesRead () << resampShift) / int64_t ((numChannels * inSampDepth * resampRatio) >> 3);
|
||
const int64_t inFileLength = wavReader.getDataBytesRead () / int64_t ((numChannels * inSampDepth) >> 3);
|
||
const unsigned inFrmLength = (frameLength * resampRatio) >> resampShift;
|
||
const unsigned resampDelay = (enableUpsampler ? 32 : (enableResampler ? 64 : 0));
|
||
#ifdef FULL_FRM_LOOKAHEAD
|
||
const unsigned flushLength = (inFileLength - resampDelay + inPadLength) % inFrmLength;
|
||
#else
|
||
const unsigned flushLength = (inFileLength - resampDelay) % inFrmLength;
|
||
#endif
|
||
if ((flushLength + ((startLength * resampRatio) >> resampShift) - inFrmLength + resampDelay
|
||
- (resampDelay >> 6)/*rnd*/+ wavReader.getSampleRate () / 200 > inFrmLength
|
||
- ((2 + sbrEncDelay * 3) >> 2)) || (flushLength == 0)) // flush last frame
|
||
{
|
||
memset (inPcmData, 0, inFrameSize * numChannels);
|
||
|
||
// resample flush frame if necessary
|
||
if (enableUpsampler) eaApplyUpsampler2x (inPcmData, inPcmRsmp, frameLength, numChannels);
|
||
else
|
||
if (enableResampler) eaApplyDownsampler (inPcmData, inPcmRsmp, frameLength, numChannels);
|
||
|
||
// flush remaining audio into new AU
|
||
// no loudnessEst.addNewPcmData call
|
||
if (enableLufsLevel) eaApplyLevelNorm (inPcmData, &loudMemory, loudnessEst.getStatistics () >> 16, frameLength, numChannels);
|
||
|
||
if ((bw = exhaleEnc.encodeFrame ()) < 3)
|
||
{
|
||
_ERROR2 ("\n ERROR while trying to create last audio frame: error value %d was returned!\n\n", bw);
|
||
i = 2; // return value
|
||
#if USE_EXHALELIB_DLL
|
||
exhaleDelete (&exhaleEnc);
|
||
#endif
|
||
goto mainFinish; // encoding error
|
||
}
|
||
bwTmp = (enableSbrCoding ? bw : (bwTmp + bw) >> 1u);
|
||
if (bwMax < bwTmp) bwMax = bwTmp;
|
||
bwTmp = bw;
|
||
|
||
// the flush AU, add frame to header
|
||
#if ENABLE_STDOUT_LOAS
|
||
if (writeStdout)
|
||
{
|
||
if (eaWriteLoasFrame (outFileHandle, loasHeader, loasMuxOffset, outAuData, bw) != bw)
|
||
{
|
||
# if USE_EXHALELIB_DLL
|
||
exhaleDelete (&exhaleEnc);
|
||
# endif
|
||
goto mainFinish; // writer error
|
||
}
|
||
if (br < UINT_MAX) br++;
|
||
}
|
||
else
|
||
#endif
|
||
if (mp4Writer.addFrameAU (outAuData, bw) != (int) bw)
|
||
{
|
||
#if USE_EXHALELIB_DLL
|
||
exhaleDelete (&exhaleEnc);
|
||
#endif
|
||
goto mainFinish; // writeout error
|
||
}
|
||
byteCount += bw;
|
||
} // trailing frame
|
||
|
||
#if ENABLE_STDOUT_LOAS
|
||
if (readStdin && !writeStdout) // reserve space necessary for MP4 file header
|
||
#else
|
||
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)), sbrEncDelay >> 2)) < 666)
|
||
{
|
||
_ERROR2 ("\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
|
||
bw = loudStats;
|
||
loudStats = loudnessEst.getStatistics ();
|
||
#if ENABLE_STDOUT_LOAS
|
||
if ((numChannels < 7) && !writeStdout)
|
||
#else
|
||
if (numChannels < 7)
|
||
#endif
|
||
{
|
||
// quantize for loudnessInfo() reset
|
||
const uint32_t qLoud = uint32_t (enableLufsLevel ? 139/* -23 LUFS */ : 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);
|
||
// NOTE: In case of enableLufsLevel, the input peak is also the approximate
|
||
// target peak - just as the -23.0 LUFS is the approximate target loudness.
|
||
|
||
// recreate ASC + UC + loudness data
|
||
bw |= (qPeak << 18) | (qLoud << 6) | 11; // measurementSystem & reliability
|
||
memset (outAuData, 0, 108 * sizeof (uint8_t)); // max allowed ASC + UC size
|
||
i = exhaleEnc.initEncoder (outAuData, &bw); // with finished loudnessInfo()
|
||
#ifndef NO_PREROLL_DATA
|
||
if (i == 0)
|
||
{
|
||
i = __min (USHRT_MAX, wavReader.getSampleRate ());
|
||
i = (uint16_t) mp4Writer.updateIPFs (outAuData, bw, (i == 57600 || i == 38400 || i == 28800 || i == 19200 /*BL USAC*/ ? 6 : 3));
|
||
}
|
||
#endif
|
||
}
|
||
// mean & max. bit-rate of encoded AUs
|
||
#if ENABLE_STDOUT_LOAS
|
||
if (writeStdout)
|
||
{
|
||
br = uint32_t (((actualLength >> 1) + 8 * (byteCount + loasMuxOffset * (int64_t) br) * sampleRate) / actualLength);
|
||
bw = 0; // print encoding statistics
|
||
|
||
_ERROR2 (" Done, actual average %.1f kbit/s,", (float) br * 0.001f);
|
||
_ERROR2 (" program loudness in %.2f => out -23 LUFS\n\n", __max (3u, loudStats >> 16) / 512.f - 100.0f);
|
||
}
|
||
else
|
||
{
|
||
#endif
|
||
br = uint32_t (((actualLength >> 1) + 8 * (byteCount + 4 * (int64_t) mp4Writer.getFrameCount ()) * sampleRate) / actualLength);
|
||
bw = uint32_t (((frameLength >> 1) + 8 * (bwMax + 4u /* max. 2048 AU size + stsz as bit-rate */) * sampleRate) / frameLength);
|
||
bw = mp4Writer.finishFile (br, bw, uint32_t (__min (UINT_MAX - startLength, actualLength)), (time (nullptr) + 2082844800) & UINT_MAX,
|
||
(i == 0) && (numChannels < 7) ? outAuData : nullptr);
|
||
// print out collected file statistics
|
||
if (enableSbrCoding)
|
||
{
|
||
fprintf_s (stdout, " Done, actual average incl. SBR data %.2f kbit/s\n\n", (float) br * 0.001f);
|
||
}
|
||
else
|
||
{
|
||
fprintf_s (stdout, " Done, actual average %.1f kbit/s\n\n", (float) br * 0.001f);
|
||
}
|
||
if (numChannels < 7)
|
||
{
|
||
fprintf_s (stdout, " Input statistics: File 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 ENABLE_STDOUT_LOAS
|
||
} // writeStdout
|
||
#endif
|
||
|
||
if (!readStdin && (actualLength != expectLength || bw != headerRes))
|
||
{
|
||
if (actualLength != expectLength)
|
||
#ifdef EXHALE_APP_WCHAR
|
||
fwprintf_s (stderr, L" WARNING: %lld sample frames read but %lld sample frames expected!\n", (long long) actualLength, (long long) expectLength);
|
||
#else
|
||
fprintf_s (stderr, " WARNING: %lld sample frames read but %lld sample frames expected!\n", (long long) actualLength, (long long) expectLength);
|
||
#endif
|
||
if (bw != headerRes) _ERROR1 (" WARNING: The encoded MPEG-4 bit-stream is likely to be unreadable!\n");
|
||
_ERROR1 ("\n");
|
||
}
|
||
#if USE_EXHALELIB_DLL
|
||
exhaleDelete (&exhaleEnc);
|
||
#endif
|
||
} // end coding loop and stats print-out
|
||
}
|
||
|
||
mainFinish:
|
||
|
||
// free all dynamic memory
|
||
MFREE (inPcmData);
|
||
MFREE (inPcmRsmp);
|
||
#if EA_USE_WORK_DIR
|
||
MFREE (currPath);
|
||
#endif
|
||
MFREE (outAuData);
|
||
|
||
// close input file
|
||
if (inFileHandle != -1)
|
||
{
|
||
if (_CLOSE (inFileHandle) != 0)
|
||
{
|
||
if (readStdin) // stdin
|
||
{
|
||
_ERROR1 (" ERROR while trying to close stdin stream! Has it already been closed?\n\n");
|
||
}
|
||
else // argc = 4, file
|
||
{
|
||
_ERROR2 (" ERROR while trying to close input file %s! Does it still exist?\n\n", argv[argc - 2]);
|
||
}
|
||
}
|
||
inFileHandle = 0;
|
||
}
|
||
// close output file
|
||
if (outFileHandle != -1)
|
||
{
|
||
#if ENABLE_STDOUT_LOAS
|
||
if (!writeStdout)
|
||
#endif
|
||
if (_CLOSE (outFileHandle) != 0)
|
||
{
|
||
_ERROR2 (" ERROR while trying to close output file %s! Does it still exist?\n\n", argv[argc - 1]);
|
||
}
|
||
outFileHandle = 0;
|
||
}
|
||
|
||
return (inFileHandle | outFileHandle | i);
|
||
}
|