improve IPF writing

This commit is contained in:
Christian R. Helmrich
2021-02-28 19:00:03 +01:00
parent 7d2b818e5a
commit 86f24988e2
8 changed files with 84 additions and 28 deletions

View File

@ -16,7 +16,7 @@ if("${CMAKE_CURRENT_SOURCE_DIR}" STREQUAL "${CMAKE_CURRENT_BINARY_DIR}")
endif() endif()
project(exhale VERSION 1.1.2 LANGUAGES CXX) project(exhale VERSION 1.1.3 LANGUAGES CXX)
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE Release set(CMAKE_BUILD_TYPE Release

View File

@ -33,7 +33,7 @@ exhale is being made available under an open-source license which is
based on the 3-clause BSD license but modified to address particular based on the 3-clause BSD license but modified to address particular
aspects dictated by the nature and the output of this application. aspects dictated by the nature and the output of this application.
The license text and release notes for the current version 1.1.2 can The license text and release notes for the current version 1.1.3 can
be found in the `include` subdirectory of the exhale distribution. be found in the `include` subdirectory of the exhale distribution.

View File

@ -25,9 +25,14 @@
<td valign="top"> <td valign="top">
<h1><br><span class="pink">exhale</span> - <span class="pink">e</span>codis e<span class="pink">x</span>tended <span class="pink">h</span>igh-efficiency <span class="pink">a</span>nd <span class="pink">l</span>ow-complexity <span class="pink">e</span>ncoder<br><span class="gray"><sup><br>Software Release Notes, Version History, Known Issues, Upcoming Feature Roadmap</sup></span><br><br></h1> <h1><br><span class="pink">exhale</span> - <span class="pink">e</span>codis e<span class="pink">x</span>tended <span class="pink">h</span>igh-efficiency <span class="pink">a</span>nd <span class="pink">l</span>ow-complexity <span class="pink">e</span>ncoder<br><span class="gray"><sup><br>Software Release Notes, Version History, Known Issues, Upcoming Feature Roadmap</sup></span><br><br></h1>
<h3>&nbsp; &nbsp;The version of this distribution of the &laquo;exhale&raquo; software release is <b>1.1.2</b> (official pub&shy;lic minor release) from January 2021. Please check <a href="http://www.ecodis.de/audio.htm#mpeg">www.ecodis.de</a> regularly for new versions of this software. A summary of each version up to this release, a list of known issues with this release, and a roadmap of additional functionality are provided below.</h3> <h3>&nbsp; &nbsp;The version of this distribution of the &laquo;exhale&raquo; software release is <b>1.1.3</b> (official pub&shy;lic minor release) from February 2021. Please check <a href="http://www.ecodis.de/audio.htm#mpeg">www.ecodis.de</a> regularly for new versions of this software. A summary of each version up to this release, a list of known issues with this release, and a roadmap of additional functionality are provided below.</h3>
<h3><br><b>Chronological Version History</b></h3> <h3><br><b>Chronological Version History</b></h3>
<h3>&nbsp; &nbsp;Version <b>1.1.2 <span class="gray">&nbsp;Jan. 2021, this release</span></b></h3> <h3>&nbsp; &nbsp;Version <b>1.1.3 <span class="gray">&nbsp;Feb. 2021, this release</span></b></h3>
<ul>
<li><h3>exhaleApp: allow specifying loudness (LUFS) and peak sample (dBFS) after preset</h3></li>
<li><h3>exhaleLib: write UsacConfig in immediate playout frames (increases compatibility)</h3></li>
</ul>
<h3>&nbsp; &nbsp;Version <b>1.1.2 <span class="gray">&nbsp;Jan. 2021</span></b></h3>
<ul> <ul>
<li><h3>further improved file interoperability and seekability with some playback software</h3></li> <li><h3>further improved file interoperability and seekability with some playback software</h3></li>
<li><h3>exhaleLib: write all frames in &laquo;stss&raquo; data as immediate playout frames (issue 15)</h3></li> <li><h3>exhaleLib: write all frames in &laquo;stss&raquo; data as immediate playout frames (issue 15)</h3></li>

View File

@ -15,5 +15,5 @@
# define EXHALELIB_VERSION_MINOR "1" # define EXHALELIB_VERSION_MINOR "1"
#endif #endif
#ifndef EXHALELIB_VERSION_BUGFIX #ifndef EXHALELIB_VERSION_BUGFIX
# define EXHALELIB_VERSION_BUGFIX ".2" // "RC" or ".0", ".1", ... # define EXHALELIB_VERSION_BUGFIX ".3" // "RC" or ".0", ".1", ...
#endif #endif

View File

@ -230,6 +230,33 @@ static void eaApplyDownsampler (int32_t* const pcmBuffer, int32_t* const resampl
} }
#endif // ENABLE_RESAMPLING #endif // ENABLE_RESAMPLING
static uint32_t eaInitLoudnessInfo (const uint32_t defaultLoudStats, const bool loudnessInfoProvided,
#ifdef EXHALE_APP_WCHAR
const wchar_t* const strings[])
#else
const char* const strings[])
#endif
{
float loudnessInfo[2];
uint32_t qLoud, qPeak;
if (!loudnessInfoProvided) return defaultLoudStats;
for (int i = 0; i < 2; i++)
{
char cString[8];
for (int c = 0; c < 8; c++) cString[c] = char (strings[i][c]); // make sure string is of type char
cString[7] = '\0';
loudnessInfo[i] = (float) atof (cString);
}
qLoud = uint32_t (4.0f * __max (0.0f, loudnessInfo[0] + 57.75f) + 0.5f); // quantize LUFS to 8 bits
qPeak = uint32_t (32.0f * __max (0.0f, 20.0f - loudnessInfo[1]) + 0.5f); // quantize peak to 12 bits
return EA_LOUD_INIT | (qPeak << 18) | (qLoud << 6) | 11u; // measurementSystem = 2, reliability = 3
}
// main routine // main routine
#ifdef EXHALE_APP_WCHAR #ifdef EXHALE_APP_WCHAR
# ifdef __MINGW32__ # ifdef __MINGW32__
@ -242,7 +269,7 @@ int main (const int argc, char* argv[])
{ {
if (argc <= 0) return argc; // for safety if (argc <= 0) return argc; // for safety
const bool readStdin = (argc == 3); const bool readStdin = (argc == 3 || argc == 5);
BasicWavReader wavReader; BasicWavReader wavReader;
int32_t* inPcmData = nullptr; // 24-bit WAVE audio input buffer int32_t* inPcmData = nullptr; // 24-bit WAVE audio input buffer
#if ENABLE_RESAMPLING #if ENABLE_RESAMPLING
@ -360,7 +387,7 @@ int main (const int argc, char* argv[])
fprintf_s (stdout, " ---------------------------------------------------------------------\n\n"); fprintf_s (stdout, " ---------------------------------------------------------------------\n\n");
// check arg. list, print usage if needed // check arg. list, print usage if needed
if ((argc < 3) || (argc > 4) || (argc > 1 && argv[1][1] != 0)) if ((argc < 3) || (argc > 6) || (argc > 1 && argv[1][1] != 0))
{ {
fprintf_s (stdout, " Copyright 2018-2021 C.R.Helmrich, project ecodis. See License.htm for details.\n\n"); fprintf_s (stdout, " Copyright 2018-2021 C.R.Helmrich, project ecodis. See License.htm for details.\n\n");
@ -481,12 +508,12 @@ int main (const int argc, char* argv[])
inFileHandle = fileno (stdin); inFileHandle = fileno (stdin);
#endif #endif
} }
else // argc = 4, open input file else // argc = 4/6, open WAV file
{ {
#ifdef EXHALE_APP_WCHAR #ifdef EXHALE_APP_WCHAR
const wchar_t* inFileName = argv[2]; const wchar_t* inFileName = argv[argc - 2];
#else #else
const char* inFileName = argv[2]; const char* inFileName = argv[argc - 2];
#endif #endif
uint16_t inPathEnd = 0; uint16_t inPathEnd = 0;
@ -510,11 +537,11 @@ int main (const int argc, char* argv[])
#ifdef EXHALE_APP_WCHAR #ifdef EXHALE_APP_WCHAR
inFileName = (const wchar_t*) malloc ((exePathEnd + i + 1) * sizeof (wchar_t)); // 0-terminated inFileName = (const wchar_t*) malloc ((exePathEnd + i + 1) * sizeof (wchar_t)); // 0-terminated
memcpy ((void*) inFileName, argv[0], exePathEnd * sizeof (wchar_t)); // prepend executable path memcpy ((void*) inFileName, argv[0], exePathEnd * sizeof (wchar_t)); // prepend executable path
memcpy ((void*)(inFileName + exePathEnd), argv[2], (i + 1) * sizeof (wchar_t)); // to file name memcpy ((void*)(inFileName + exePathEnd), argv[argc - 2], (i + 1) * sizeof (wchar_t));// to name
#else #else
inFileName = (const char*) malloc ((exePathEnd + i + 1) * sizeof (char)); // 0-terminated string 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, argv[0], exePathEnd * sizeof (char)); // prepend executable path ...
memcpy ((void*)(inFileName + exePathEnd), argv[2], (i + 1) * sizeof (char)); // ... to file name memcpy ((void*)(inFileName + exePathEnd), argv[argc - 2], (i + 1) * sizeof (char)); //...to name
#endif #endif
} }
@ -716,7 +743,7 @@ int main (const int argc, char* argv[])
#endif #endif
const unsigned indepPeriod = (sampleRate < 48000 ? (sampleRate - 320) / frameLength : 45 /*for 50-Hz video, use 50 for 60-Hz video*/); const unsigned indepPeriod = (sampleRate < 48000 ? (sampleRate - 320) / frameLength : 45 /*for 50-Hz video, use 50 for 60-Hz video*/);
const unsigned mod3Percent = unsigned ((expectLength * (3 + (coreSbrFrameLengthIndex & 3))) >> 17); const unsigned mod3Percent = unsigned ((expectLength * (3 + (coreSbrFrameLengthIndex & 3))) >> 17);
uint32_t byteCount = 0, bw = (numChannels < 7 ? loudStats : 0); uint32_t byteCount = 0, bw = (numChannels < 7 ? eaInitLoudnessInfo (loudStats, argc > 4, &argv[2]) : 0);
uint32_t br, bwMax = 0; // br will be used to hold bytes read and/or bit-rate uint32_t br, bwMax = 0; // br will be used to hold bytes read and/or bit-rate
uint32_t headerRes = 0; uint32_t headerRes = 0;
// initialize LoudnessEstimator object // initialize LoudnessEstimator object
@ -739,6 +766,7 @@ int main (const int argc, char* argv[])
// init encoder, generate UsacConfig() // init encoder, generate UsacConfig()
memset (outAuData, 0, 108 * sizeof (uint8_t)); // max. allowed ASC + UC size memset (outAuData, 0, 108 * sizeof (uint8_t)); // max. allowed ASC + UC size
i = exhaleEnc.initEncoder (outAuData, &bw); // bw stores actual ASC + UC size i = exhaleEnc.initEncoder (outAuData, &bw); // bw stores actual ASC + UC size
if ((i == 256) && (argc > 4)) i = 0; // clear LUFS warning
if ((i |= mp4Writer.open (outFileHandle, sampleRate, numChannels, inSampDepth, frameLength, startLength if ((i |= mp4Writer.open (outFileHandle, sampleRate, numChannels, inSampDepth, frameLength, startLength
#if ENABLE_SIMPLE_SBR #if ENABLE_SIMPLE_SBR
@ -984,7 +1012,7 @@ int main (const int argc, char* argv[])
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); 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 // recreate ASC + UC + loudness data
bw = EA_LOUD_INIT | (qPeak << 18) | (qLoud << 6) | 11; // measurementSystem bw = EA_LOUD_INIT | (qPeak << 18) | (qLoud << 6) | 11u;
memset (outAuData, 0, 108 * sizeof (uint8_t)); // max allowed ASC + UC size memset (outAuData, 0, 108 * sizeof (uint8_t)); // max allowed ASC + UC size
i = exhaleEnc.initEncoder (outAuData, &bw); // with finished loudnessInfo() i = exhaleEnc.initEncoder (outAuData, &bw); // with finished loudnessInfo()
} }
@ -1049,12 +1077,12 @@ mainFinish:
{ {
fprintf_s (stderr, " ERROR while trying to close stdin stream! Has it already been closed?\n\n"); fprintf_s (stderr, " ERROR while trying to close stdin stream! Has it already been closed?\n\n");
} }
else // argc = 4, file else // argc = 4/6, WAV
{ {
#ifdef EXHALE_APP_WCHAR #ifdef EXHALE_APP_WCHAR
fwprintf_s (stderr, L" ERROR while trying to close input file %s! Does it still exist?\n\n", argv[2]); fwprintf_s (stderr, L" ERROR while trying to close input file %s! Does it still exist?\n\n", argv[argc - 2]);
#else #else
fprintf_s (stderr, " ERROR while trying to close input file %s! Does it still exist?\n\n", argv[2]); fprintf_s (stderr, " ERROR while trying to close input file %s! Does it still exist?\n\n", argv[argc - 2]);
#endif #endif
} }
} }

View File

@ -13,7 +13,7 @@
0 ICON "exhaleApp.ico" 0 ICON "exhaleApp.ico"
VS_VERSION_INFO VERSIONINFO VS_VERSION_INFO VERSIONINFO
FILEVERSION 1,1,2 FILEVERSION 1,1,3
BEGIN BEGIN
BLOCK "StringFileInfo" BLOCK "StringFileInfo"
BEGIN BEGIN

View File

@ -715,7 +715,10 @@ unsigned BitStreamWriter::createAudioConfig (const char samplingFrequencyIndex,
{ {
const uint8_t fli = (sbrRatioShiftValue == 0 ? 1 /*no SBR*/ : __min (2, sbrRatioShiftValue) + 2); const uint8_t fli = (sbrRatioShiftValue == 0 ? 1 /*no SBR*/ : __min (2, sbrRatioShiftValue) + 2);
const int8_t usfi = __max (0, samplingFrequencyIndex - 3 * sbrRatioShiftValue); // TODO: non-standard sampling rates const int8_t usfi = __max (0, samplingFrequencyIndex - 3 * sbrRatioShiftValue); // TODO: non-standard sampling rates
unsigned bitCount = 37; unsigned bitCount = 37, auLen;
#ifndef NO_PREROLL_DATA
unsigned ucOffset = (samplingFrequencyIndex < AAC_NUM_SAMPLE_RATES ? 2 : 5);
#endif
if ((elementType == nullptr) || (audioConfig == nullptr) || (chConfigurationIndex >= USAC_MAX_NUM_ELCONFIGS) || if ((elementType == nullptr) || (audioConfig == nullptr) || (chConfigurationIndex >= USAC_MAX_NUM_ELCONFIGS) ||
#if !RESTRICT_TO_AAC #if !RESTRICT_TO_AAC
@ -816,8 +819,12 @@ unsigned BitStreamWriter::createAudioConfig (const char samplingFrequencyIndex,
bitCount += (8 - m_auBitStream.heldBitCount) & 7; bitCount += (8 - m_auBitStream.heldBitCount) & 7;
writeByteAlignment (); // flush bytes writeByteAlignment (); // flush bytes
auLen = __min (18u + fli, bitCount >> 3);
memcpy (audioConfig, &m_auBitStream.stream.front (), __min (17u + fli, bitCount >> 3)); #ifndef NO_PREROLL_DATA
m_usacConfigLen = uint16_t (__max (9, auLen - ucOffset)); // excl. ASC payload
memcpy (m_usacConfig, &m_auBitStream.stream.at (ucOffset), auLen - ucOffset);
#endif
memcpy (audioConfig, &m_auBitStream.stream.front (), auLen);
return (bitCount >> 3); // byte count return (bitCount >> 3); // byte count
} }
@ -854,7 +861,7 @@ unsigned BitStreamWriter::createAudioFrame (CoreCoderData** const elementData,
if ((ipf == 2) || (ipf == 1 && (numElements > 1 || !noiseFilling[0]))) if ((ipf == 2) || (ipf == 1 && (numElements > 1 || !noiseFilling[0])))
{ {
bitCount = __min (nSamplesInFrame << 2, (uint32_t) m_auBitStream.stream.size ()); bitCount = __min (nSamplesInFrame << 2, (uint32_t) m_auBitStream.stream.size ());
memcpy (tempBuffer, &m_auBitStream.stream.front (), bitCount); memcpy (tempBuffer, &m_auBitStream.stream.front (), bitCount); // prev fr AU
} }
#endif #endif
m_auBitStream.reset (); m_auBitStream.reset ();
@ -869,19 +876,28 @@ unsigned BitStreamWriter::createAudioFrame (CoreCoderData** const elementData,
{ {
const uint16_t idxPreRollExt = elementData[0]->elementType & 1; const uint16_t idxPreRollExt = elementData[0]->elementType & 1;
const bool lowRatePreRollExt = (ipf == 1 && numElements == 1 && noiseFilling[0]); const bool lowRatePreRollExt = (ipf == 1 && numElements == 1 && noiseFilling[0]);
const unsigned payloadLength = (lowRatePreRollExt ? (8 + idxPreRollExt * 6) >> (sbrRatioShiftValue > 0 ? 0 : 1) : bitCount) + 3; // in bytes! const unsigned extraLength = (m_usacConfigLen > 14 ? 4 : 3) + m_usacConfigLen;
const unsigned payloadLength = (lowRatePreRollExt ? (8 + idxPreRollExt * 6) >> (sbrRatioShiftValue > 0 ? 0 : 1) : bitCount) + extraLength; // in bytes
m_auBitStream.write (0, 1); // usacExtElementUseDefaultLength = 0 (variable) m_auBitStream.write (0, 1); // usacExtElementUseDefaultLength = 0 (variable)
m_auBitStream.write (CLIP_UCHAR (payloadLength), 8); m_auBitStream.write (CLIP_UCHAR (payloadLength), 8);
if (payloadLength > 254) m_auBitStream.write (payloadLength - 253, 16); if (payloadLength > 254) m_auBitStream.write (payloadLength - 253, 16);
m_auBitStream.write (0, 6); // start AudioPreRoll - configLen = reserved = 0 m_auBitStream.write (__min (15, m_usacConfigLen), 4); // configLen (part #1)
if (m_usacConfigLen > 14) m_auBitStream.write (m_usacConfigLen - 15, 4);
m_auBitStream.write (m_usacConfig[ci++] & 31, 5); // 1st 3 bits are from ASC
while (ci < m_usacConfigLen) m_auBitStream.write (m_usacConfig[ci++], 8);
ci = 0;
m_auBitStream.write (0, 8 - 5); // pad end of UsacConfig() data
m_auBitStream.write (0, 2); // applyCrossfade = 0 and reserved = 0 (part #2)
m_auBitStream.write (1, 2); // numPreRollFrames, only one supported for now! m_auBitStream.write (1, 2); // numPreRollFrames, only one supported for now!
m_auBitStream.write (payloadLength - 3, 16); // auLen m_auBitStream.write (payloadLength - extraLength, 16); // auLen
if (lowRatePreRollExt) if (lowRatePreRollExt)
{ {
bitCount = payloadLength - 3; bitCount = payloadLength - extraLength;
memcpy (tempBuffer, zeroAu[idxPreRollExt], bitCount); memcpy (tempBuffer, zeroAu[idxPreRollExt], bitCount);
if (elementData[0]->elementType < ID_USAC_LFE) // correct window_sequence if (elementData[0]->elementType < ID_USAC_LFE) // correct window_sequence
{ {
@ -893,6 +909,8 @@ unsigned BitStreamWriter::createAudioFrame (CoreCoderData** const elementData,
} }
while (ci < bitCount) m_auBitStream.write (tempBuffer[ci++], 8); // write AU while (ci < bitCount) m_auBitStream.write (tempBuffer[ci++], 8); // write AU
ci = 0; ci = 0;
if (m_usacConfigLen > 14) m_auBitStream.write (0, 4); // pad end of ext data
bitCount = (payloadLength > 254 ? 26 : 10) + (payloadLength << 3); // for PR bitCount = (payloadLength > 254 ? 26 : 10) + (payloadLength << 3); // for PR
} }
bitCount++; // for ElementPresent flag bitCount++; // for ElementPresent flag

View File

@ -16,8 +16,8 @@
// constants, experimental macros // constants, experimental macros
#define CORE_MODE_FD 0 #define CORE_MODE_FD 0
#define ID_EXT_LOUDNESS_INFO 2
#define ID_EXT_ELE_FILL 0 #define ID_EXT_ELE_FILL 0
#define ID_EXT_LOUDNESS_INFO 2
#define SFB_PER_PRED_BAND 2 #define SFB_PER_PRED_BAND 2
// output bit-stream writer class // output bit-stream writer class
@ -30,6 +30,10 @@ private:
uint32_t m_frameLength; // number of samples in full frame uint32_t m_frameLength; // number of samples in full frame
uint8_t m_numSwbShort; // max. SFB count in short windows uint8_t m_numSwbShort; // max. SFB count in short windows
uint8_t* m_uCharBuffer; // temporary buffer for ungrouping uint8_t* m_uCharBuffer; // temporary buffer for ungrouping
#ifndef NO_PREROLL_DATA
uint8_t m_usacConfig[20]; // buffer for UsacConfig in IPF
uint16_t m_usacConfigLen; // length of UsacConfig in bytes
#endif
// helper functions // helper functions
void writeByteAlignment (); // write 0s for byte alignment void writeByteAlignment (); // write 0s for byte alignment
@ -52,7 +56,8 @@ private:
public: public:
// constructor // constructor
BitStreamWriter () { m_auBitStream.reset (); m_frameLength = 0; m_numSwbShort = 0; m_uCharBuffer = nullptr; } BitStreamWriter () { m_auBitStream.reset (); m_frameLength = 0; m_numSwbShort = 0; m_uCharBuffer = nullptr;
memset (m_usacConfig, 0, 20u); m_usacConfigLen = 0; }
// destructor // destructor
~BitStreamWriter() { m_auBitStream.reset (); } ~BitStreamWriter() { m_auBitStream.reset (); }
// public functions // public functions