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()
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)
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
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.

View File

@ -25,9 +25,14 @@
<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>
<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>&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>
<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>

View File

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

View File

@ -230,6 +230,33 @@ static void eaApplyDownsampler (int32_t* const pcmBuffer, int32_t* const resampl
}
#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
#ifdef EXHALE_APP_WCHAR
# ifdef __MINGW32__
@ -242,7 +269,7 @@ int main (const int argc, char* argv[])
{
if (argc <= 0) return argc; // for safety
const bool readStdin = (argc == 3);
const bool readStdin = (argc == 3 || argc == 5);
BasicWavReader wavReader;
int32_t* inPcmData = nullptr; // 24-bit WAVE audio input buffer
#if ENABLE_RESAMPLING
@ -360,7 +387,7 @@ int main (const int argc, char* argv[])
fprintf_s (stdout, " ---------------------------------------------------------------------\n\n");
// 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");
@ -481,12 +508,12 @@ int main (const int argc, char* argv[])
inFileHandle = fileno (stdin);
#endif
}
else // argc = 4, open input file
else // argc = 4/6, open WAV file
{
#ifdef EXHALE_APP_WCHAR
const wchar_t* inFileName = argv[2];
const wchar_t* inFileName = argv[argc - 2];
#else
const char* inFileName = argv[2];
const char* inFileName = argv[argc - 2];
#endif
uint16_t inPathEnd = 0;
@ -510,11 +537,11 @@ int main (const int argc, char* argv[])
#ifdef EXHALE_APP_WCHAR
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 + 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
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
memcpy ((void*)(inFileName + exePathEnd), argv[argc - 2], (i + 1) * sizeof (char)); //...to name
#endif
}
@ -716,7 +743,7 @@ int main (const int argc, char* argv[])
#endif
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);
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 headerRes = 0;
// initialize LoudnessEstimator object
@ -739,6 +766,7 @@ int main (const int argc, char* argv[])
// 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 == 256) && (argc > 4)) i = 0; // clear LUFS warning
if ((i |= mp4Writer.open (outFileHandle, sampleRate, numChannels, inSampDepth, frameLength, startLength
#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);
// 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
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");
}
else // argc = 4, file
else // argc = 4/6, WAV
{
#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
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
}
}

View File

@ -13,7 +13,7 @@
0 ICON "exhaleApp.ico"
VS_VERSION_INFO VERSIONINFO
FILEVERSION 1,1,2
FILEVERSION 1,1,3
BEGIN
BLOCK "StringFileInfo"
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 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 !RESTRICT_TO_AAC
@ -816,8 +819,12 @@ unsigned BitStreamWriter::createAudioConfig (const char samplingFrequencyIndex,
bitCount += (8 - m_auBitStream.heldBitCount) & 7;
writeByteAlignment (); // flush bytes
memcpy (audioConfig, &m_auBitStream.stream.front (), __min (17u + fli, bitCount >> 3));
auLen = __min (18u + 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
}
@ -854,7 +861,7 @@ unsigned BitStreamWriter::createAudioFrame (CoreCoderData** const elementData,
if ((ipf == 2) || (ipf == 1 && (numElements > 1 || !noiseFilling[0])))
{
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
m_auBitStream.reset ();
@ -869,19 +876,28 @@ unsigned BitStreamWriter::createAudioFrame (CoreCoderData** const elementData,
{
const uint16_t idxPreRollExt = elementData[0]->elementType & 1;
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 (CLIP_UCHAR (payloadLength), 8);
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 (payloadLength - 3, 16); // auLen
m_auBitStream.write (payloadLength - extraLength, 16); // auLen
if (lowRatePreRollExt)
{
bitCount = payloadLength - 3;
bitCount = payloadLength - extraLength;
memcpy (tempBuffer, zeroAu[idxPreRollExt], bitCount);
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
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++; // for ElementPresent flag

View File

@ -16,8 +16,8 @@
// constants, experimental macros
#define CORE_MODE_FD 0
#define ID_EXT_LOUDNESS_INFO 2
#define ID_EXT_ELE_FILL 0
#define ID_EXT_LOUDNESS_INFO 2
#define SFB_PER_PRED_BAND 2
// output bit-stream writer class
@ -30,6 +30,10 @@ private:
uint32_t m_frameLength; // number of samples in full frame
uint8_t m_numSwbShort; // max. SFB count in short windows
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
void writeByteAlignment (); // write 0s for byte alignment
@ -52,7 +56,8 @@ private:
public:
// 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
~BitStreamWriter() { m_auBitStream.reset (); }
// public functions