add source code

This commit is contained in:
Christian R. Helmrich
2020-01-02 02:05:09 +01:00
parent 30f3efc66e
commit 2e1a6c97b3
47 changed files with 9322 additions and 0 deletions

38
exhale_vs2012.sln Normal file
View File

@ -0,0 +1,38 @@
Microsoft Visual Studio Solution File, Format Version 11.00
# Visual Studio 2012
Project("{EC0D1500-2018-1700-4865-6C6D72696368}") = "exhaleLib", "src\lib\exhaleLib_vs2012.vcxproj", "{EC0D1501-2018-1700-4865-6C6D72696368}"
EndProject
Project("{EC0D1500-2018-1700-4865-6C6D72696368}") = "exhaleApp", "src\app\exhaleApp_vs2012.vcxproj", "{EC0D1502-2018-1700-4865-6C6D72696368}"
ProjectSection(ProjectDependencies) = postProject
{EC0D1501-2018-1700-4865-6C6D72696368} = {EC0D1501-2018-1700-4865-6C6D72696368}
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Win32 = Debug|Win32
Debug|x64 = Debug|x64
Release|Win32 = Release|Win32
Release|x64 = Release|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{EC0D1501-2018-1700-4865-6C6D72696368}.Debug|Win32.ActiveCfg = Debug|Win32
{EC0D1501-2018-1700-4865-6C6D72696368}.Debug|Win32.Build.0 = Debug|Win32
{EC0D1501-2018-1700-4865-6C6D72696368}.Debug|x64.ActiveCfg = Debug|x64
{EC0D1501-2018-1700-4865-6C6D72696368}.Debug|x64.Build.0 = Debug|x64
{EC0D1501-2018-1700-4865-6C6D72696368}.Release|Win32.ActiveCfg = Release|Win32
{EC0D1501-2018-1700-4865-6C6D72696368}.Release|Win32.Build.0 = Release|Win32
{EC0D1501-2018-1700-4865-6C6D72696368}.Release|x64.ActiveCfg = Release|x64
{EC0D1501-2018-1700-4865-6C6D72696368}.Release|x64.Build.0 = Release|x64
{EC0D1502-2018-1700-4865-6C6D72696368}.Debug|Win32.ActiveCfg = Debug|Win32
{EC0D1502-2018-1700-4865-6C6D72696368}.Debug|Win32.Build.0 = Debug|Win32
{EC0D1502-2018-1700-4865-6C6D72696368}.Debug|x64.ActiveCfg = Debug|x64
{EC0D1502-2018-1700-4865-6C6D72696368}.Debug|x64.Build.0 = Debug|x64
{EC0D1502-2018-1700-4865-6C6D72696368}.Release|Win32.ActiveCfg = Release|Win32
{EC0D1502-2018-1700-4865-6C6D72696368}.Release|Win32.Build.0 = Release|Win32
{EC0D1502-2018-1700-4865-6C6D72696368}.Release|x64.ActiveCfg = Release|x64
{EC0D1502-2018-1700-4865-6C6D72696368}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

53
include/License.htm Normal file
View File

@ -0,0 +1,53 @@
<!-- www.ecodis.de/exhale/license.htm - created by Christian R. Helmrich - Copyright (c) 2018-2019 Christian Helmrich -->
<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<link rel="stylesheet" type="text/css" href="styles.css">
<meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
<meta name="author" content="Dr.-Ing. Christian R. Helmrich">
<meta name="description" content="exhale License Specification">
<meta name="keywords" content="audio, data compression, perceptual coding, subjective evaluation, video">
<title>ecodis :: exhale :: License Text</title>
</head>
<body>
<table align="center" cellpadding="0" cellspacing="0">
<colgroup>
<col width="2">
<col width="60">
<col width="595">
<col width="60">
<col width="2">
</colgroup>
<tr>
<td colspan="2"></td>
<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>referred to as &laquo;Software&raquo; below; additions to original BSD license text marked by [ ]</sup></span><br><br></h1>
<h3>&nbsp; &nbsp;This Software is being made available and/or distributed under the following Modified <a href="http://www.linfo.org/bsdlicense.html">BSD</a>-style License. For a list of authors which have contributed to this Software, called &laquo;contributors&raquo; in the text below, please refer to the respective files provided with this distribution (source files or binary executable) to which modifications were contributed.</h3>
<h3><br><b>Licensor's Copyright Notice</b></h3>
<h3>&nbsp; &nbsp;Copyright &copy; 2018&#x2013;2019 Christian R. Helmrich, <a href="http://www.ecodis.de">ecodis</a> (Licensor). All rights reserved.</h3>
<h3><br><b>List of License Conditions</b></h3>
<h3>&nbsp; &nbsp;Redistribution and use <span class="gray">[</span>of this Software<span class="gray">]</span> in source and binary forms, with or without modification, are permitted provided that<sub>&nbsp;</sub><span class="gray">[</span>all of<span class="gray">]</span><sub>&nbsp;</sub>the following<sub>&nbsp;</sub><span class="gray">[</span>four<span class="gray">]</span><sub>&nbsp;</sub>conditions are met:</h3>
<ul>
<li><h3>Redistributions of source code must retain the above copyright notice <span class="gray">[</span>in each file provided with the distribution<span class="gray">]</span>, this list of conditions, and the following disclaimer.</h3></li>
<li><h3>Redistributions in binary form must reproduce the above copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other files provided with the distribution.</h3></li>
<li><h3><span class="gray">[</span>Redistributions of modified versions of this Software, whether in source or binary form, must reproduce a notice in each file provided with the distribution and affec&shy;ted by the modification, documenting the name of the contributor having authored the modification and the year of the modification.<span class="gray">]</span></h3></li>
<li><h3>Neither the name of the Licensor nor the names of the contributors may be used to endorse or promote products derived from this Software without specific prior written permission <span class="gray">[</span>from the Licensor<span class="gray">]</span>.</h3></li>
</ul>
<h3><br><b>Liability and Patent Disclaimer</b></h3>
<h3>&nbsp; &nbsp;THIS SOFTWARE IS PROVIDED BY THE LICENSOR AND THE CONTRIBUTORS &laquo;<b>AS IS</b>&raquo; AND ANY EXPRESS OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PUR&shy;POSE) ARE DISCLAIMED. IN NO EVENT SHALL THE LICENSOR OR ANY CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.</h3>
<h3>&nbsp; <span class="gray">[</span>This Software may be subject to other third-party and/or contributor rights, including patent rights. In particular, this Software may implement methods and/or technologies required to perform in compliance with certain specifications of the ISO/IEC (MPEG-D) international standard <a href="https://www.iso.org/standard/76385.html">23003-3</a>, which may be subject to said other rights. NO express or implied licenses to any patent claims related to the use of this Software are granted under this license, and the Licensor provides NO WARRANTY of patent non-infringement with respect to this Software. Patent licenses required for the use of this Software to generate digital bit-streams according to any specifications of ISO/IEC 23003-3 may be obtained through <a href="https://www.via-corp.com/licensing/aac/">Via Licensing</a> and/or the corresponding patent holders individually.<span class="gray">]</span><br><br></h3>
<h4><span class="gray">Written by C. R. Helmrich for exhale 1.0.0, Dec. 2019. Available at www.ecodis.de/exhale/license.htm.</span><br><br></h4>
</td>
<td valign="top" colspan="2">
<p><br>&nbsp; &nbsp;<img src="../src/app/exhaleApp.ico" alt="exhale" title="exhale" height="48" width="48"></p>
</td>
</tr>
</table>
<p></p>
</body>
</html>

66
include/Release.htm Normal file
View File

@ -0,0 +1,66 @@
<!-- www.ecodis.de/exhale/release.htm - created by Christian R. Helmrich - Copyright (c) 2018-2019 Christian Helmrich -->
<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<link rel="stylesheet" type="text/css" href="styles.css">
<meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
<meta name="author" content="Dr.-Ing. Christian R. Helmrich">
<meta name="description" content="exhale Software Release Notes">
<meta name="keywords" content="audio, data compression, perceptual coding, subjective evaluation, video">
<title>ecodis :: exhale :: Release Notes</title>
</head>
<body>
<table align="center" cellpadding="0" cellspacing="0">
<colgroup>
<col width="2">
<col width="60">
<col width="595">
<col width="60">
<col width="2">
</colgroup>
<tr>
<td colspan="2"></td>
<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.0.0</b> (official pub&shy;lic release candidate) from January 2020. Please check <a href="http://www.ecodis.de">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.0.0 <span class="gray">&nbsp;Jan. 2020, this release</span></b></h3>
<ul>
<li><h3>compilation fixes and executable printout changes for Linux and MacOS&trade; platform</h3></li>
</ul>
<h3>&nbsp; &nbsp;Version <b>1.0RC <span class="gray">Dec. 2019</span></b></h3>
<ul>
<li><h3>initial release for testing with only basic channel-independent coding functionality</h3></li>
<li><h3>only support for Microsoft Windows&trade; (32-bit and 64-bit) platform provided so far.</h3></li>
</ul>
<h3><br><b>Known Issues with This Release</b></h3>
<h3>&nbsp; &nbsp;If you notice an issue with this release <b>not</b> mentioned below, please contact ecodis or a contributor with the details (configuration, input file) needed to reproduce the issue.</h3>
<ul>
<li><h3>exhaleLib: Coding of stereo or multichannel input yields suboptimal audio quality because the joint-channel coding functionality provided by the ISO/IEC <a href="https://www.iso.org/standard/76385.html">23003-3</a> standard has not been implemented so far. See the functionality roadmap below.</h3></li>
<li><h3>exhaleApp: Only basic WAVE input file reading functionality has been implemen&shy;ted. Specifically, 8-bit WAVE input is assumed to contain an even number of audio samples, and the Broadcast and Extensible WAVE file formats are not supported.</h3></li>
</ul>
<h3><br><b>Roadmap of Upcoming Features</b></h3>
<h3>&nbsp; &nbsp;If you are in need of an additional library or application feature <b>not</b> mentioned below, please contact ecodis or a contributor with a request, and we will see what we can do.</h3>
<ul>
<li><h3>support for MPEG-D DRC-style peak-level and loudness metadata, no version plan</h3></li>
<li><h3>support for compilation as dynamically linked library in Windows&trade;, no version plan</h3></li>
<li><h3>exhaleLib: quality tuning and bug fixing for low-bitrate mono coding, version 1.0.1</h3></li>
<li><h3>exhaleLib: finalized integration of joint-channel coding functionality, version 1.0.2</h3></li>
<li><h3>exhaleLib: quality tuning and bug fixing for low-rate stereo coding, version 1.0.3.</h3></li>
</ul>
<h3><br></h3>
<h4><span class="gray">Written by C. R. Helmrich for exhale 1.0.0, Dec. 2019. Available at www.ecodis.de/exhale/release.htm.</span><br><br></h4>
</td>
<td valign="top" colspan="2">
<p><br>&nbsp; &nbsp;<img src="../src/app/exhaleApp.ico" alt="exhale" title="exhale" height="48" width="48"></p>
</td>
</tr>
</table>
<p></p>
</body>
</html>

22
include/exhaleDecl.h Normal file
View File

@ -0,0 +1,22 @@
/* exhaleDecl.h - header file with declarations for exhale DLL export under Windows
* written by C. R. Helmrich, last modified in 2019 - 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.
*/
#ifndef _EXHALE_DECL_H_
#define _EXHALE_DECL_H_
#if defined (_WIN32) || defined (WIN32) || defined (_WIN64) || defined (WIN64)
# ifdef EXHALE_DYN_LINK
# define EXHALE_DECL __declspec (dllexport)
# else
# define EXHALE_DECL
# endif
#endif
#endif // _EXHALE_DECL_H_

39
include/styles.css Normal file
View File

@ -0,0 +1,39 @@
a:link, a:visited
{ color:#36f; font-weight:bold; text-decoration:none; }
a:active, a:focus, a:hover
{ color:#f36; font-weight:bold; text-decoration:none; }
a.current:link, a.current:visited
{ background:#fff; color:#000; }
body
{ margin:0px; text-align:center; }
h1
{ color:#666; font-family:Segoe UI, Arial, sans-serif; font-size:13pt; font-weight:bold; }
h2
{ color:#36f; font-family:Segoe UI, Arial, sans-serif; font-size:13pt; font-weight:bold; }
h3
{ color:#000; font-family:Verdana, Arial, sans-serif; font-size:10pt; font-weight:normal; line-height:16pt; max-width:594px; text-align:justify; }
h4
{ color:#000; font-family:Verdana, Arial, sans-serif; font-size:8pt; font-weight:normal; line-height:14pt; max-width:594px; }
hr
{ border:1px solid; color:#ccc; height:1px; }
img
{ border-width:0px; }
td
{ background:#fff; }
ul
{ list-style:square; }
.block
{ text-align:justify; }
.center
{ text-align:center; }
.gray
{ color:#ccc; }
.pink
{ color:#f36; }

19
include/version.h Normal file
View File

@ -0,0 +1,19 @@
/* version.h - header file with major and minor library version numbers as characters
* written by C. R. Helmrich, last modified in 2019 - 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.
*/
#ifndef EXHALELIB_VERSION_MAJOR
# define EXHALELIB_VERSION_MAJOR "1"
#endif
#ifndef EXHALELIB_VERSION_MINOR
# define EXHALELIB_VERSION_MINOR "0"
#endif
#ifndef EXHALELIB_VERSION_BUGFIX
# define EXHALELIB_VERSION_BUGFIX "RC" // "RC" or ".0", ".1", ...
#endif

30
makefile Normal file
View File

@ -0,0 +1,30 @@
## makefile - master user make-file for compiling exhale on Linux and MacOS platforms
# written by C. R. Helmrich, last modified 2019 - see License.txt for legal notices
#
# The copyright in this software is being made available under a Modified BSD 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-2019 Christian R. Helmrich, project ecodis. All rights reserved.
##
## BUILD32=1: compile for 32-bit platforms, BUILD32=0: compile for 64-bit platforms
BUILD32?=0
export BUILD32
all:
$(MAKE) -C src/lib MM32=$(BUILD32)
$(MAKE) -C src/app MM32=$(BUILD32)
debug:
$(MAKE) -C src/lib debug MM32=$(BUILD32)
$(MAKE) -C src/app debug MM32=$(BUILD32)
release:
$(MAKE) -C src/lib release MM32=$(BUILD32)
$(MAKE) -C src/app release MM32=$(BUILD32)
clean:
$(MAKE) -C src/lib clean MM32=$(BUILD32)
$(MAKE) -C src/app clean MM32=$(BUILD32)

361
src/app/basicMP4Writer.cpp Normal file
View File

@ -0,0 +1,361 @@
/* basicMP4Writer.cpp - source file for class with basic MPEG-4 file writing capability
* written by C. R. Helmrich, last modified in 2019 - 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"
#if 0 // DEBUG
static const uint8_t muLawHeader[44] = {
0x52, 0x49, 0x46, 0x46, 0xF0, 0xFF, 0xFF, 0xFF, 0x57, 0x41, 0x56, 0x45, 0x66, 0x6D, 0x74, 0x20,
0x10, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x08, 0x00, 0x64, 0x61, 0x74, 0x61, 0xF0, 0xFF, 0xFF, 0xFF
};
#endif
static const uint8_t staticHeaderTemplate[STAT_HEADER_SIZE] = {
0x00, 0x00, 0x00, 0x18, 0x66, 0x74, 0x79, 0x70, 0x6D, 0x70, 0x34, 0x32, 0x00, 0x00, 0x00, 0x00, // ftyp
0x6D, 0x70, 0x34, 0x32, 0x69, 0x73, 0x6F, 0x6D, 0x00, 0x00, MOOV_BSIZE, 0x6D, 0x6F, 0x6F, 0x76, // moov
0x00, 0x00, 0x00, 0x6C, 0x6D, 0x76, 0x68, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mvhd
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, // end atom 2.1 (mvhd)
0x00, 0x00, 0x00, 0x18, 0x69, 0x6F, 0x64, 0x73, 0x00, 0x00, 0x00, 0x00, 0x10, 0x80, 0x80, 0x80, // iods
0x07, 0x00, 0x4F, 0xFF, 0xFF, 0x49, 0xFF, 0xFF, 0x00, 0x00, TRAK_BSIZE, // end atom 2.2 (iods)
0x74, 0x72, 0x61, 0x6B, 0x00, 0x00, 0x00, 0x5C, 0x74, 0x6B, 0x68, 0x64, 0x00, 0x00, 0x00, 0x07, // tkhd
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x24, 0x65, 0x64, 0x74, 0x73, 0x00, 0x00, 0x00, 0x1C, 0x65, 0x6C, 0x73, 0x74, // elst
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, MDIA_BSIZE, 0x6D, 0x64, 0x69, 0x61, 0x00, 0x00, 0x00, 0x20, // mdhd
0x6D, 0x64, 0x68, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0xC4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24,
0x68, 0x64, 0x6C, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x73, 0x6F, 0x75, 0x6E, // hdlr
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x72, 0x68, 0x00,
0x00, 0x00, MINF_BSIZE, 0x6D, 0x69, 0x6E, 0x66, 0x00, 0x00, 0x00, 0x10, 0x73, 0x6D, 0x68, 0x64,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x64, 0x69, 0x6E, 0x66, // dinf
0x00, 0x00, 0x00, 0x1C, 0x64, 0x72, 0x65, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x0C, 0x75, 0x72, 0x6C, 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, STBL_BSIZE,
0x73, 0x74, 0x62, 0x6C, 0x00, 0x00, 0x00, 0x20, 0x73, 0x74, 0x74, 0x73, 0x00, 0x00, 0x00, 0x00, // stts
0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, STSD_BSIZE, 0x73, 0x74, 0x73, 0x64, 0x00, 0x00, 0x00, 0x00, // stsd
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, MP4A_BSIZE, 0x6D, 0x70, 0x34, 0x61, 0x00, 0x00, 0x00, 0x00, // mp4a
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ESDS_BSIZE, 0x65, 0x73, 0x64, 0x73, // esds
0x00, 0x00, 0x00, 0x00, 0x03, 0x80, 0x80, 0x80, 0x25, 0x00, 0x00, 0x00, 0x04, 0x80, 0x80, 0x80, // tag4
0x17, 0x40, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x80, // tag5
0x80, 0x80, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00 // ASC continued in m_dynamicHeader if >5 bytes
};
// static helper functions
static uint32_t toBigEndian (const unsigned ui) // Motorola endianness
{
return ((ui & UCHAR_MAX) << 24) | (((ui >> 8) & UCHAR_MAX) << 16) | (((ui >> 16) & UCHAR_MAX) << 8) | ((ui >> 24) & UCHAR_MAX);
}
static uint16_t toUShortValue (const uint8_t hiByte, const uint8_t loByte)
{
return ((uint16_t) hiByte << 8) | (uint16_t) loByte;
}
// public functions
int BasicMP4Writer::addFrameAU (const uint8_t* byteBuf, const uint32_t byteOffset, const uint32_t byteCount)
{
if ((m_fileHandle == -1) || (m_m4aMdatSize > 0xFFFFFFF0u - byteCount))
{
return 1; // invalid file handle or file getting too big
}
// add frame byte-size, in Big Endian format, to frame size list (stsz)
m_dynamicHeader.push_back ((byteCount >> 24) & UCHAR_MAX);
m_dynamicHeader.push_back ((byteCount >> 16) & UCHAR_MAX);
m_dynamicHeader.push_back ((byteCount >> 8) & UCHAR_MAX);
m_dynamicHeader.push_back ( byteCount & UCHAR_MAX);
if (((m_frameCount++) % m_rndAccPeriod) == 0) // add RAP to list (stco)
{
m_rndAccOffsets.push_back (m_m4aMdatSize);
}
m_m4aMdatSize += byteCount;
return _WRITE (m_fileHandle, byteBuf, byteCount); // write access unit
}
int BasicMP4Writer::finishFile (const unsigned avgBitrate, const unsigned maxBitrate, const uint32_t audioLength,
const uint32_t modifTime /*= 0*/)
{
const unsigned numFramesFirstPeriod = __min (m_frameCount, m_rndAccPeriod);
const unsigned numFramesFinalPeriod = (m_frameCount <= m_rndAccPeriod ? 0 : m_frameCount % m_rndAccPeriod);
const unsigned numSamplesFinalFrame = (audioLength + m_pregapLength) % m_frameLength;
const uint32_t stszAtomSize = STSX_BSIZE + 4 /*bytes for sampleSize*/ + m_frameCount * 4;
const uint32_t stscAtomSize = STSX_BSIZE + (numFramesFinalPeriod == 0 ? 12 : 24);
const uint32_t stcoAtomSize = STSX_BSIZE + (uint32_t) m_rndAccOffsets.size () * 4;
const uint32_t stblIncrSize = m_ascSizeM5 + stszAtomSize + stscAtomSize + stcoAtomSize;
const uint32_t moovAtomSize = toBigEndian (toUShortValue (MOOV_BSIZE) + stblIncrSize);
const uint32_t trakAtomSize = toBigEndian (toUShortValue (TRAK_BSIZE) + stblIncrSize);
const uint32_t mdiaAtomSize = toBigEndian (toUShortValue (MDIA_BSIZE) + stblIncrSize);
const uint32_t minfAtomSize = toBigEndian (toUShortValue (MINF_BSIZE) + stblIncrSize);
const uint32_t stblAtomSize = toBigEndian (toUShortValue (STBL_BSIZE) + stblIncrSize);
const uint32_t numSamplesBE = toBigEndian (audioLength);
const uint32_t timeStampBE = toBigEndian (modifTime);
const uint32_t headerBytes = STAT_HEADER_SIZE + (uint32_t) m_dynamicHeader.size () + stscAtomSize + stcoAtomSize;
uint32_t* const header4Byte = (uint32_t* const) m_staticHeader;
int bytesWritten = 0;
if ((m_fileHandle == -1) || (m_m4aMdatSize > 0xFFFFFFF0u - headerBytes))
{
return 1; // invalid file handle or file getting too big
}
// finish setup of fixed-length part of MPEG-4 file header
if (modifTime > 0)
{
header4Byte[ 48>>2] = timeStampBE; // mvhd
header4Byte[188>>2] = timeStampBE; // tkhd
header4Byte[324>>2] = timeStampBE; // mdhd
}
header4Byte[ 24>>2] = moovAtomSize;
header4Byte[ 56>>2] = numSamplesBE;
header4Byte[164>>2] = trakAtomSize;
header4Byte[200>>2] = numSamplesBE;
header4Byte[300>>2] = mdiaAtomSize;
header4Byte[332>>2] = toBigEndian (audioLength + m_pregapLength);
header4Byte[376>>2] = minfAtomSize;
header4Byte[288>>2] = numSamplesBE; // elst
header4Byte[436>>2] = stblAtomSize;
header4Byte[460>>2] = toBigEndian (m_frameCount - 1); // 2 entries used
header4Byte[472>>2] = toBigEndian (numSamplesFinalFrame == 0 ? m_frameLength : numSamplesFinalFrame);
m_staticHeader[558] = ((maxBitrate >> 24) & UCHAR_MAX);
m_staticHeader[559] = ((maxBitrate >> 16) & UCHAR_MAX);
m_staticHeader[560] = ((maxBitrate >> 8) & UCHAR_MAX);
m_staticHeader[561] = ( maxBitrate & UCHAR_MAX);
m_staticHeader[562] = ((avgBitrate >> 24) & UCHAR_MAX);
m_staticHeader[563] = ((avgBitrate >> 16) & UCHAR_MAX);
m_staticHeader[564] = ((avgBitrate >> 8) & UCHAR_MAX);
m_staticHeader[565] = ( avgBitrate & UCHAR_MAX);
// finish dynamically-sized 2nd part of MPEG-4 file header
m_dynamicHeader.at (m_ascSizeM5 + 6) = ((stszAtomSize >> 24) & UCHAR_MAX);
m_dynamicHeader.at (m_ascSizeM5 + 7) = ((stszAtomSize >> 16) & UCHAR_MAX);
m_dynamicHeader.at (m_ascSizeM5 + 8) = ((stszAtomSize >> 8) & UCHAR_MAX);
m_dynamicHeader.at (m_ascSizeM5 + 9) = ( stszAtomSize & UCHAR_MAX);
m_dynamicHeader.at (m_ascSizeM5 + 22) = ((m_frameCount >> 24) & UCHAR_MAX);
m_dynamicHeader.at (m_ascSizeM5 + 23) = ((m_frameCount >> 16) & UCHAR_MAX);
m_dynamicHeader.at (m_ascSizeM5 + 24) = ((m_frameCount >> 8) & UCHAR_MAX);
m_dynamicHeader.at (m_ascSizeM5 + 25) = ( m_frameCount & UCHAR_MAX);
m_dynamicHeader.push_back (0x00); m_dynamicHeader.push_back (0x00);
m_dynamicHeader.push_back (0x00); m_dynamicHeader.push_back (stscAtomSize);
m_dynamicHeader.push_back (0x73); m_dynamicHeader.push_back (0x74);
m_dynamicHeader.push_back (0x73); m_dynamicHeader.push_back (0x63); // stsc
m_dynamicHeader.push_back (0x00); m_dynamicHeader.push_back (0x00);
m_dynamicHeader.push_back (0x00); m_dynamicHeader.push_back (0x00);
m_dynamicHeader.push_back (0x00); m_dynamicHeader.push_back (0x00);
m_dynamicHeader.push_back (0x00); m_dynamicHeader.push_back (numFramesFinalPeriod == 0 ? 1 : 2);
m_dynamicHeader.push_back (0x00); m_dynamicHeader.push_back (0x00);
m_dynamicHeader.push_back (0x00); m_dynamicHeader.push_back (0x01); // 1st
m_dynamicHeader.push_back ((numFramesFirstPeriod >> 24) & UCHAR_MAX);
m_dynamicHeader.push_back ((numFramesFirstPeriod >> 16) & UCHAR_MAX);
m_dynamicHeader.push_back ((numFramesFirstPeriod >> 8) & UCHAR_MAX);
m_dynamicHeader.push_back ( numFramesFirstPeriod & UCHAR_MAX);
m_dynamicHeader.push_back (0x00); m_dynamicHeader.push_back (0x00);
m_dynamicHeader.push_back (0x00); m_dynamicHeader.push_back (0x01); // idx
if (numFramesFinalPeriod > 0)
{
m_dynamicHeader.push_back ((m_rndAccOffsets.size () >> 24) & UCHAR_MAX);
m_dynamicHeader.push_back ((m_rndAccOffsets.size () >> 16) & UCHAR_MAX);
m_dynamicHeader.push_back ((m_rndAccOffsets.size () >> 8) & UCHAR_MAX);
m_dynamicHeader.push_back ( m_rndAccOffsets.size () & UCHAR_MAX);
m_dynamicHeader.push_back ((numFramesFinalPeriod >> 24) & UCHAR_MAX);
m_dynamicHeader.push_back ((numFramesFinalPeriod >> 16) & UCHAR_MAX);
m_dynamicHeader.push_back ((numFramesFinalPeriod >> 8) & UCHAR_MAX);
m_dynamicHeader.push_back ( numFramesFinalPeriod & UCHAR_MAX);
m_dynamicHeader.push_back (0x00); m_dynamicHeader.push_back (0x00);
m_dynamicHeader.push_back (0x00); m_dynamicHeader.push_back (0x01);// idx
}
m_dynamicHeader.push_back ((stcoAtomSize >> 24) & UCHAR_MAX);
m_dynamicHeader.push_back ((stcoAtomSize >> 16) & UCHAR_MAX);
m_dynamicHeader.push_back ((stcoAtomSize >> 8) & UCHAR_MAX);
m_dynamicHeader.push_back ( stcoAtomSize & UCHAR_MAX);
m_dynamicHeader.push_back (0x73); m_dynamicHeader.push_back (0x74);
m_dynamicHeader.push_back (0x63); m_dynamicHeader.push_back (0x6F); // stco
m_dynamicHeader.push_back (0x00); m_dynamicHeader.push_back (0x00);
m_dynamicHeader.push_back (0x00); m_dynamicHeader.push_back (0x00);
m_dynamicHeader.push_back ((m_rndAccOffsets.size () >> 24) & UCHAR_MAX);
m_dynamicHeader.push_back ((m_rndAccOffsets.size () >> 16) & UCHAR_MAX);
m_dynamicHeader.push_back ((m_rndAccOffsets.size () >> 8) & UCHAR_MAX);
m_dynamicHeader.push_back ( m_rndAccOffsets.size () & UCHAR_MAX);
// add header size corrected random-access offsets to file
for (unsigned i = 0; i < m_rndAccOffsets.size (); i++)
{
const uint32_t rndAccOffset = m_rndAccOffsets.at (i) + headerBytes;
m_dynamicHeader.push_back ((rndAccOffset >> 24) & UCHAR_MAX);
m_dynamicHeader.push_back ((rndAccOffset >> 16) & UCHAR_MAX);
m_dynamicHeader.push_back ((rndAccOffset >> 8) & UCHAR_MAX);
m_dynamicHeader.push_back ( rndAccOffset & UCHAR_MAX);
}
m_dynamicHeader.push_back ((m_m4aMdatSize >> 24) & UCHAR_MAX);
m_dynamicHeader.push_back ((m_m4aMdatSize >> 16) & UCHAR_MAX);
m_dynamicHeader.push_back ((m_m4aMdatSize >> 8) & UCHAR_MAX);
m_dynamicHeader.push_back ( m_m4aMdatSize & UCHAR_MAX);
m_dynamicHeader.push_back (0x6D); m_dynamicHeader.push_back (0x64);
m_dynamicHeader.push_back (0x61); m_dynamicHeader.push_back (0x74); // mdat
_SEEK (m_fileHandle, 0, 0 /*SEEK_SET*/); // back to start
bytesWritten += _WRITE (m_fileHandle, m_staticHeader, STAT_HEADER_SIZE);
bytesWritten += _WRITE (m_fileHandle, &m_dynamicHeader.front (), (unsigned) m_dynamicHeader.size ());
return bytesWritten;
}
int BasicMP4Writer::initHeader (const uint32_t audioLength) // reserve bytes for header in file
{
#if 0 // DEBUG
const uint8_t numChannels = m_staticHeader[517];
// write basic <20>-Law WAVE header for testing
memcpy (m_staticHeader, muLawHeader, 44 * sizeof (uint8_t));
m_staticHeader[22] = numChannels;
m_staticHeader[24] = uint8_t (m_sampleRate & 0xFF);
m_staticHeader[25] = uint8_t (m_sampleRate >> 8u);
m_staticHeader[26] = uint8_t (m_sampleRate >> 16u);
m_staticHeader[28] = uint8_t ((m_sampleRate * numChannels) & 0xFF);
m_staticHeader[29] = uint8_t ((m_sampleRate * numChannels) >> 8u);
m_staticHeader[30] = uint8_t ((m_sampleRate * numChannels) >> 16u);
m_staticHeader[32] = numChannels; // byte count per frame
return _WRITE (m_fileHandle, m_staticHeader, 44);
#else
const bool flushFrameUsed = ((audioLength + m_pregapLength) % m_frameLength) > 0;
const unsigned frameCount = ((audioLength + m_frameLength - 1) / m_frameLength) + (flushFrameUsed ? 2 : 1);
const unsigned chunkCount = ((frameCount + m_rndAccPeriod - 1) / m_rndAccPeriod);
const unsigned finalChunk = (frameCount <= m_rndAccPeriod ? 0 : frameCount % m_rndAccPeriod);
const int estimHeaderSize = STAT_HEADER_SIZE + m_ascSizeM5 + 6+4 + frameCount * 4 /*stsz*/ + STSX_BSIZE * 3 +
(finalChunk == 0 ? 12 : 24) /*stsc*/ + chunkCount * 4 /*stco*/ + 8 /*mdat*/;
int bytesWritten = 0;
for (int i = estimHeaderSize; i > 0; i -= STAT_HEADER_SIZE)
{
bytesWritten += _WRITE (m_fileHandle, m_staticHeader, __min (i, STAT_HEADER_SIZE));
}
return bytesWritten;
#endif
}
unsigned BasicMP4Writer::open (const int mp4FileHandle, const unsigned sampleRate, const unsigned numChannels,
const unsigned bitDepth, const unsigned frameLength, const unsigned pregapLength,
const unsigned raPeriod, const uint8_t* ascBuf, const unsigned ascSize,
const uint32_t creatTime /*= 0*/, const char vbrQuality /*= 0*/)
{
const uint32_t frameSizeBE = toBigEndian (frameLength);
const uint32_t pregapSizeBE = toBigEndian (pregapLength);
const uint32_t sampleRateBE = toBigEndian (sampleRate);
const uint32_t timeStampBE = toBigEndian (creatTime);
uint32_t* const header4Byte = (uint32_t* const) m_staticHeader;
if ((mp4FileHandle == -1) || (frameLength == 0) || (sampleRate == 0) || (numChannels == 0) || (numChannels * 3 > UCHAR_MAX) ||
(raPeriod == 0) || (ascBuf == nullptr) || (ascSize < 5) || (ascSize > 108) || (bitDepth == 0) || (bitDepth > UCHAR_MAX))
{
return 1; // invalid file handle or other input variable
}
m_fileHandle = mp4FileHandle;
reset (frameLength, pregapLength, raPeriod);
#if 0 // DEBUG
m_sampleRate = sampleRate;
#endif
// create fixed-length 576-byte part of MPEG-4 file header
memcpy (m_staticHeader, staticHeaderTemplate, STAT_HEADER_SIZE * sizeof (uint8_t));
header4Byte[ 44>>2] = timeStampBE;
header4Byte[ 48>>2] = timeStampBE;
header4Byte[ 52>>2] = sampleRateBE;
header4Byte[184>>2] = timeStampBE;
header4Byte[188>>2] = timeStampBE;
header4Byte[292>>2] = pregapSizeBE; // pregap size in elst
header4Byte[320>>2] = timeStampBE;
header4Byte[324>>2] = timeStampBE;
header4Byte[328>>2] = sampleRateBE;
header4Byte[332>>2] = pregapSizeBE; // +audio length later
header4Byte[464>>2] = frameSizeBE;
m_staticHeader[339] = vbrQuality;
m_staticHeader[517] = (uint8_t) numChannels;
m_staticHeader[519] = (uint8_t) bitDepth;
m_staticHeader[523] = (sampleRate >> 16) & UCHAR_MAX; // ?
m_staticHeader[524] = (sampleRate >> 8) & UCHAR_MAX;
m_staticHeader[525] = sampleRate & UCHAR_MAX;
m_staticHeader[556] = (uint8_t) numChannels * 3; // 6144 bit/chan
memcpy (&m_staticHeader[571], ascBuf, 5 * sizeof (uint8_t));
if (ascSize > 5) // increase atom byte-sizes
{
const uint8_t inc = m_ascSizeM5 = ascSize - 5;
m_staticHeader[ 27] += inc; // MOOV_BSIZE
m_staticHeader[167] += inc; // TRAK_BSIZE
m_staticHeader[303] += inc; // MDIA_BSIZE
if (m_staticHeader[379] + m_ascSizeM5 > UCHAR_MAX) m_staticHeader[378]++;
m_staticHeader[379] += inc; // MINF_BSIZE
m_staticHeader[439] += inc; // STBL_BSIZE
m_staticHeader[479] += inc; // STSD_BSIZE
m_staticHeader[495] += inc; // MP4A_BSIZE
m_staticHeader[531] += inc; // ESDS_BSIZE
m_staticHeader[544] += inc; // esds tag 3
m_staticHeader[552] += inc; // esds tag 4
m_staticHeader[570] += inc; // esds tag 5
for (unsigned i = 0; i < m_ascSizeM5; i++) m_dynamicHeader.push_back (ascBuf[5 + i]);
}
// prepare variable-length remainder of MPEG-4 file header
m_dynamicHeader.push_back (0x06); m_dynamicHeader.push_back (0x80); // esds
m_dynamicHeader.push_back (0x80); m_dynamicHeader.push_back (0x80);
m_dynamicHeader.push_back (0x01); m_dynamicHeader.push_back (0x02);
m_dynamicHeader.push_back (0x00); m_dynamicHeader.push_back (0x00); // + 4N
m_dynamicHeader.push_back (0x00); m_dynamicHeader.push_back (STSX_BSIZE + 4);
m_dynamicHeader.push_back (0x73); m_dynamicHeader.push_back (0x74);
m_dynamicHeader.push_back (0x73); m_dynamicHeader.push_back (0x7A); // stsz
m_dynamicHeader.push_back (0x00); m_dynamicHeader.push_back (0x00);
m_dynamicHeader.push_back (0x00); m_dynamicHeader.push_back (0x00);
m_dynamicHeader.push_back (0x00); m_dynamicHeader.push_back (0x00);
m_dynamicHeader.push_back (0x00); m_dynamicHeader.push_back (0x00);
m_dynamicHeader.push_back (0x00); m_dynamicHeader.push_back (0x00); // := N
m_dynamicHeader.push_back (0x00); m_dynamicHeader.push_back (0x00);
return 0; // correct operation
}
void BasicMP4Writer::reset (const unsigned frameLength /*= 0*/, const unsigned pregapLength /*= 0*/, const unsigned raPeriod /*= 0*/)
{
m_ascSizeM5 = 0;
m_frameCount = 0;
m_frameLength = frameLength;
m_m4aMdatSize = 8; // bytes for mdat header
m_pregapLength = pregapLength;
m_rndAccPeriod = raPeriod;
m_sampleRate = 0;
m_dynamicHeader.clear ();
m_rndAccOffsets.clear ();
if (m_fileHandle != -1) _SEEK (m_fileHandle, 0, 0 /*SEEK_SET*/);
}

65
src/app/basicMP4Writer.h Normal file
View File

@ -0,0 +1,65 @@
/* basicMP4Writer.h - header file for class with basic MPEG-4 file writing capability
* written by C. R. Helmrich, last modified in 2019 - 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.
*/
#ifndef _BASIC_MP4_WRITER_H_
#define _BASIC_MP4_WRITER_H_
#include "exhaleAppPch.h"
// constant data sizes in bytes
#define STAT_HEADER_SIZE 576
#define STSX_BSIZE 0x10
#define ESDS_BSIZE 0x00, 0x36 // esds: 54 (+ m_ascSizeM5 later)
#define MP4A_BSIZE 0x00, 0x5A // mp4a: 36 + ESDS_BSIZE
#define STSD_BSIZE 0x00, 0x6A // mp4a: 16 + MP4A_BSIZE
#define STBL_BSIZE 0x00, 0x92 // stbl: 8 + 32 + STSD_BSIZE (+ rem later)
#define MINF_BSIZE 0x00, 0xCE // minf: 8 + 16 + 36 + STBL_BSIZE
#define MDIA_BSIZE 0x01, 0x1A // mdia: 8 + 32 + 36 + MINF_BSIZE
#define TRAK_BSIZE 0x01, 0xA2 // trak: 8 + 92 + 36 + MDIA_BSIZE
#define MOOV_BSIZE 0x02, 0x2E // moov: 8 +108 + 24 + TRAK_BSIZE
// basic MPEG-4 write-out class
class BasicMP4Writer
{
private:
// member variables
unsigned m_ascSizeM5; // ASC + UsacConfig byte-size - 5
int m_fileHandle;
unsigned m_frameCount;
unsigned m_frameLength;
unsigned m_m4aMdatSize;
unsigned m_pregapLength; // encoder look-ahead, pre-roll
unsigned m_rndAccPeriod; // random-access (RA) interval
unsigned m_sampleRate;
uint8_t m_staticHeader[STAT_HEADER_SIZE]; // fixed-size
std::vector <uint8_t> m_dynamicHeader; // variable-sized
std::vector <uint32_t> m_rndAccOffsets; // random access
public:
// constructor
BasicMP4Writer () { m_fileHandle = -1; reset (); }
// destructor
~BasicMP4Writer() { m_dynamicHeader.clear (); m_rndAccOffsets.clear (); }
// public functions
int addFrameAU (const uint8_t* byteBuf, const uint32_t byteOffset, const uint32_t byteCount);
int finishFile (const unsigned avgBitrate, const unsigned maxBitrate, const uint32_t audioLength,
const uint32_t modifTime = 0);
unsigned getFrameCount () const { return m_frameCount; }
int initHeader (const uint32_t audioLength);
unsigned open (const int mp4FileHandle, const unsigned sampleRate, const unsigned numChannels,
const unsigned bitDepth, const unsigned frameLength, const unsigned pregapLength,
const unsigned raPeriod, const uint8_t* ascBuf, const unsigned ascSize,
const uint32_t creatTime = 0, const char vbrQuality = 0);
void reset (const unsigned frameLength = 0, const unsigned pregapLength = 0, const unsigned raPeriod = 0);
}; // BasicMP4Writer
#endif // _BASIC_MP4_WRITER_H_

415
src/app/basicWavReader.cpp Normal file
View File

@ -0,0 +1,415 @@
/* basicWavReader.cpp - source file for class with basic WAVE file reading capability
* written by C. R. Helmrich, last modified in 2019 - 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 "basicWavReader.h"
// static helper functions
static unsigned reverseFourBytes (const uint8_t* b)
{
return ((unsigned) b[3] << 24) | ((unsigned) b[2] << 16) | ((unsigned) b[1] << 8) | (unsigned) b[0];
}
static int64_t fourBytesToLength (const uint8_t* b, const int64_t lengthLimit)
{
int64_t chunkLength = (int64_t) reverseFourBytes (b);
chunkLength += chunkLength & 1; // make sure it is even
return __min (lengthLimit, chunkLength); // for security
}
// private reader functions
bool BasicWavReader::readRiffHeader ()
{
uint8_t b[FILE_HEADER_SIZE] = {0}; // temp. byte buffer
if ((m_bytesRead = _READ (m_fileHandle, b, FILE_HEADER_SIZE)) != FILE_HEADER_SIZE) return false; // error
m_bytesRemaining -= m_bytesRead;
m_chunkLength = fourBytesToLength (&b[4], m_bytesRemaining) - 4; // minus 4 bytes for WAVE tag
return (b[0] == 'R' && b[1] == 'I' && b[2] == 'F' && b[3] == 'F' &&
b[8] == 'W' && b[9] == 'A' && b[10]== 'V' && b[11]== 'E' &&
m_bytesRemaining > 32); // true: RIFF supported
}
bool BasicWavReader::readFormatChunk ()
{
uint8_t b[CHUNK_FORMAT_MAX] = {0}; // temp. byte buffer
if (!seekToChunkTag (b, 0x20746D66 /*fmt */) || (m_chunkLength < CHUNK_FORMAT_SIZE) || (m_chunkLength > CHUNK_FORMAT_MAX))
{
return false; // fmt_ chunk invalid or read incomplete
}
if ((m_bytesRead = _READ (m_fileHandle, b, (unsigned) m_chunkLength)) != m_chunkLength) return false; // error
m_bytesRemaining -= m_bytesRead;
m_waveDataType = WAV_TYPE (b[0]-1); // 1: PCM, 3: float
m_waveChannels = b[2]; // only 1, 2, ..., 63 supported
m_waveFrameRate = reverseFourBytes (&b[4]); // frames/s
m_waveBitRate = reverseFourBytes (&b[8]) * 8; // bit/s
m_waveFrameSize = b[12]; // bytes/s divided by frames/s
m_waveBitDepth = b[14]; // only 8, 16, 24, 32 supported
return ((m_waveDataType == WAV_PCM || (m_waveDataType == WAV_FLOAT && (m_waveBitDepth & 15) == 0)) &&
(m_waveChannels > 0 && m_waveChannels <= 63) && isSamplingRateSupported (m_waveFrameRate) &&
(m_waveBitRate == 8 * m_waveFrameRate * m_waveFrameSize) && (b[ 1] == 0) && (b[ 3] == 0) &&
(m_waveFrameSize * 8 == m_waveBitDepth * m_waveChannels) && (b[13] == 0) && (b[15] == 0) &&
(m_waveBitDepth >= 8 && m_waveBitDepth <= 32 && (m_waveBitDepth & 7) == 0) &&
m_bytesRemaining > 8); // true: format supported
}
bool BasicWavReader::readDataHeader ()
{
uint8_t b[CHUNK_HEADER_SIZE] = {0}; // temp. byte buffer
if (!seekToChunkTag (b, 0x61746164 /*data*/))
{
return false; // data chunk invalid or read incomplete
}
return (m_chunkLength > 0); // true: WAVE data available
}
// private helper function
bool BasicWavReader::seekToChunkTag (uint8_t* const buf, const uint32_t tagID)
{
if ((m_bytesRead = _READ (m_fileHandle, buf, CHUNK_HEADER_SIZE)) != CHUNK_HEADER_SIZE) return false; // error
m_bytesRemaining -= m_bytesRead;
m_chunkLength = fourBytesToLength (&buf[4], m_bytesRemaining);
while ((*((uint32_t* const) buf) != tagID) &&
(m_bytesRemaining > 0)) // seek until tagID found
{
if ((m_readOffset = _SEEK (m_fileHandle, m_chunkLength, 1 /*SEEK_CUR*/)) == -1)
{
// for stdin compatibility, don't abort, try reading
for (int64_t i = m_chunkLength >> 1; i > 0; i--)
{
_READ (m_fileHandle, buf, 2); // as length is even
}
}
m_bytesRemaining -= m_chunkLength;
if (m_bytesRemaining <= 0)
{
return false; // an error which should never happen!
}
if ((m_bytesRead = _READ (m_fileHandle, buf, CHUNK_HEADER_SIZE)) != CHUNK_HEADER_SIZE) return false; // error
m_bytesRemaining -= m_bytesRead;
m_chunkLength = fourBytesToLength (&buf[4], m_bytesRemaining);
}
return (m_bytesRemaining > 0);
}
// static reading functions
unsigned BasicWavReader::readDataFloat16 (const int fileHandle, int32_t* frameBuf, const unsigned frameCount,
const unsigned chanCount, void* tempBuf)
{
#if BWR_BUFFERED_READ
const int16_t* fBuf = (const int16_t*) tempBuf; // words
const int bytesRead = _READ (fileHandle, tempBuf, frameCount * chanCount * 2);
unsigned framesRead = __max (0, bytesRead / (chanCount * 2));
for (unsigned i = framesRead * chanCount; i > 0; i--)
{
const int16_t i16 = *(fBuf++);
const int32_t e = ((i16 & 0x7C00) >> 10) - 18; // exp.
// an exponent e <= -12 will lead to zero-quantization
*frameBuf = int32_t (e < 0 ? (1024 + (i16 & 0x03FF) + (1 << (-1 - e)) /*rounding offset*/) >> -e
: (e > 12 ? MAX_VALUE_AUDIO24 /*inf*/ : (1024 + (i16 & 0x03FF)) << e));
if ((i16 & 0x8000) != 0) *frameBuf *= -1; // neg. sign
frameBuf++;
}
if (framesRead < frameCount) // zero out missing samples
{
memset (frameBuf, 0, (frameCount - framesRead) * chanCount * sizeof (int32_t));
}
return framesRead;
#else
unsigned bytesRead = 0;
for (unsigned i = frameCount * chanCount; i > 0; i--)
{
int16_t i16 = 0;
bytesRead += _READ (fileHandle, &i16, 2); // two bytes
const int32_t e = ((i16 & 0x7C00) >> 10) - 18; // exp.
// an exponent e <= -12 will lead to zero-quantization
*frameBuf = int32_t (e < 0 ? (1024 + (i16 & 0x03FF) + (1 << (-1 - e)) /*rounding offset*/) >> -e
: (e > 12 ? MAX_VALUE_AUDIO24 /*inf*/ : (1024 + (i16 & 0x03FF)) << e));
if ((i16 & 0x8000) != 0) *frameBuf *= -1; // neg. sign
frameBuf++;
}
return bytesRead / (chanCount * 2);
#endif
}
unsigned BasicWavReader::readDataFloat32 (const int fileHandle, int32_t* frameBuf, const unsigned frameCount,
const unsigned chanCount, void* tempBuf)
{
#if BWR_BUFFERED_READ
const float* fBuf = (const float*) tempBuf; // 4 bytes
const int bytesRead = _READ (fileHandle, tempBuf, frameCount * chanCount * 4);
unsigned framesRead = __max (0, bytesRead / (chanCount * 4));
for (unsigned i = framesRead * chanCount; i > 0; i--)
{
const float f32 = *fBuf * float (1 << 23); // * 2^23
fBuf++;
*frameBuf = int32_t (f32 + (f32 < 0.0 ? -0.5 : 0.5)); // rounding
if (*frameBuf < MIN_VALUE_AUDIO24) *frameBuf = MIN_VALUE_AUDIO24;
else
if (*frameBuf > MAX_VALUE_AUDIO24) *frameBuf = MAX_VALUE_AUDIO24;
frameBuf++;
}
if (framesRead < frameCount) // zero out missing samples
{
memset (frameBuf, 0, (frameCount - framesRead) * chanCount * sizeof (int32_t));
}
return framesRead;
#else
unsigned bytesRead = 0;
for (unsigned i = frameCount * chanCount; i > 0; i--)
{
float f32 = 0.0; // IEEE-754 normalized floating point
bytesRead += _READ (fileHandle, &f32, 4); // 4 bytes
*frameBuf = int32_t (f32 * (1 << 23) + (f32 < 0.0 ? -0.5 : 0.5)); // * 2^23 with rounding
if (*frameBuf < MIN_VALUE_AUDIO24) *frameBuf = MIN_VALUE_AUDIO24;
else
if (*frameBuf > MAX_VALUE_AUDIO24) *frameBuf = MAX_VALUE_AUDIO24;
frameBuf++;
}
return bytesRead / (chanCount * 4);
#endif
}
unsigned BasicWavReader::readDataLnPcm08 (const int fileHandle, int32_t* frameBuf, const unsigned frameCount,
const unsigned chanCount, void* tempBuf)
{
#if BWR_BUFFERED_READ
const uint8_t* iBuf = (uint8_t*) tempBuf;
const int bytesRead = _READ (fileHandle, tempBuf, frameCount * chanCount);
unsigned framesRead = __max (0, bytesRead / chanCount);
for (unsigned i = framesRead * chanCount; i > 0; i--)
{
*(frameBuf++) = ((int32_t) *(iBuf++) - 128) << 16; // * 2^16
}
if (framesRead < frameCount) // zero out missing samples
{
memset (frameBuf, 0, (frameCount - framesRead) * chanCount * sizeof (int32_t));
}
return framesRead;
#else
unsigned bytesRead = 0;
for (unsigned i = frameCount * chanCount; i > 0; i--)
{
uint8_t ui8 = 128;
bytesRead += _READ (fileHandle, &ui8, 1); // one byte
*(frameBuf++) = ((int32_t) ui8 - 128) << 16; // * 2^16
}
return bytesRead / chanCount;
#endif
}
unsigned BasicWavReader::readDataLnPcm16 (const int fileHandle, int32_t* frameBuf, const unsigned frameCount,
const unsigned chanCount, void* tempBuf)
{
#if BWR_BUFFERED_READ
const int16_t* iBuf = (const int16_t*) tempBuf; // words
const int bytesRead = _READ (fileHandle, tempBuf, frameCount * chanCount * 2);
unsigned framesRead = __max (0, bytesRead / (chanCount * 2));
for (unsigned i = framesRead * chanCount; i > 0; i--)
{
*(frameBuf++) = (int32_t) *(iBuf++) << 8; // * 2^8
}
if (framesRead < frameCount) // zero out missing samples
{
memset (frameBuf, 0, (frameCount - framesRead) * chanCount * sizeof (int32_t));
}
return framesRead;
#else
unsigned bytesRead = 0;
for (unsigned i = frameCount * chanCount; i > 0; i--)
{
int16_t i16 = 0;
bytesRead += _READ (fileHandle, &i16, 2); // two bytes
*(frameBuf++) = (int32_t) i16 << 8; // * 2^8
}
return bytesRead / (chanCount * 2);
#endif
}
unsigned BasicWavReader::readDataLnPcm24 (const int fileHandle, int32_t* frameBuf, const unsigned frameCount,
const unsigned chanCount, void* tempBuf)
{
#if BWR_BUFFERED_READ
const uint8_t* iBuf = (uint8_t*) tempBuf;
const int bytesRead = _READ (fileHandle, tempBuf, frameCount * chanCount * 3);
unsigned framesRead = __max (0, bytesRead / (chanCount * 3));
for (unsigned i = framesRead * chanCount; i > 0; i--)
{
const int32_t i24 = (int32_t) iBuf[0] | ((int32_t) iBuf[1] << 8) | ((int32_t) iBuf[2] << 16);
iBuf += 3;
*(frameBuf++) = (i24 > MAX_VALUE_AUDIO24 ? i24 + 2 * MIN_VALUE_AUDIO24 : i24);
}
if (framesRead < frameCount) // zero out missing samples
{
memset (frameBuf, 0, (frameCount - framesRead) * chanCount * sizeof (int32_t));
}
return framesRead;
#else
unsigned bytesRead = 0;
for (unsigned i = frameCount * chanCount; i > 0; i--)
{
int32_t i24 = 0;
bytesRead += _READ (fileHandle, &i24, 3); // 3 bytes
*(frameBuf++) = (i24 > MAX_VALUE_AUDIO24 ? i24 + 2 * MIN_VALUE_AUDIO24 : i24);
}
return bytesRead / (chanCount * 3);
#endif
}
unsigned BasicWavReader::readDataLnPcm32 (const int fileHandle, int32_t* frameBuf, const unsigned frameCount,
const unsigned chanCount, void* tempBuf)
{
#if BWR_BUFFERED_READ
const int32_t* iBuf = (const int32_t*) tempBuf; // dword
const int bytesRead = _READ (fileHandle, tempBuf, frameCount * chanCount * 4);
unsigned framesRead = __max (0, bytesRead / (chanCount * 4));
for (unsigned i = framesRead * chanCount; i > 0; i--)
{
const int32_t i24 = ((*iBuf >> 1) + (1 << 6)) >> 7; // * 2^-8 with rounding, overflow-safe
iBuf++;
*(frameBuf++) = __min (MAX_VALUE_AUDIO24, i24);
}
if (framesRead < frameCount) // zero out missing samples
{
memset (frameBuf, 0, (frameCount - framesRead) * chanCount * sizeof (int32_t));
}
return framesRead;
#else
unsigned bytesRead = 0;
for (unsigned i = frameCount * chanCount; i > 0; i--)
{
int32_t i24 = 0;
bytesRead += _READ (fileHandle, &i24, 4); // 4 bytes
i24 = ((i24 >> 1) + (1 << 6)) >> 7; // * 2^-8 with rounding, overflow-safe
*(frameBuf++) = __min (MAX_VALUE_AUDIO24, i24);
}
return bytesRead / (chanCount * 4);
#endif
}
// public functions
unsigned BasicWavReader::open (const int wavFileHandle, const uint16_t maxFrameRead, const int64_t fileLength /*= LLONG_MAX*/)
{
m_bytesRemaining = fileLength;
m_fileHandle = wavFileHandle;
if ((m_fileHandle == -1) || (fileLength <= 44))
{
return 1; // file handle invalid or file too small
}
#if defined (_WIN32) || defined (WIN32) || defined (_WIN64) || defined (WIN64)
if ((fileLength < LLONG_MAX) && (m_readOffset = _telli64 (m_fileHandle)) != 0)
#else // Linux, MacOS, Unix
if ((fileLength < LLONG_MAX) && (m_readOffset = lseek (m_fileHandle, 0, 1 /*SEEK_CUR*/)) != 0)
#endif
{
m_readOffset = _SEEK (m_fileHandle, 0, 0 /*SEEK_SET*/);
}
if ((m_readOffset != 0) || !readRiffHeader ())
{
return 2; // file type invalid or file seek failed
}
if (!readFormatChunk ())
{
return 3; // audio format invalid or not supported
}
if (!readDataHeader ())
{
return 4; // WAVE data part invalid or unsupported
}
if ((m_byteBuffer = (char*) malloc (m_waveFrameSize * maxFrameRead)) == nullptr)
{
return 5; // read-in byte buffer allocation failed
}
m_frameLimit = maxFrameRead;
// ready to read audio data: initialize byte counter
if (m_bytesRemaining > m_chunkLength)
{
m_bytesRemaining = m_chunkLength;
}
m_chunkLength = 0;
if (m_waveDataType == WAV_PCM) // & function pointer
{
switch (m_waveBitDepth)
{
case 8:
m_readDataFunc = readDataLnPcm08; break;
case 16:
m_readDataFunc = readDataLnPcm16; break;
case 24:
m_readDataFunc = readDataLnPcm24; break;
default:
m_readDataFunc = readDataLnPcm32; break;
}
}
else m_readDataFunc = (m_waveBitDepth == 16 ? readDataFloat16 : readDataFloat32);
return (m_readDataFunc == nullptr ? 6 : 0); // 0: OK
}
unsigned BasicWavReader::read (int32_t* const frameBuf, const uint16_t frameCount)
{
unsigned framesRead;
if ((frameBuf == nullptr) || (m_fileHandle == -1) || (__min (m_frameLimit, frameCount) == 0) || (m_byteBuffer == nullptr))
{
return 0; // invalid args or class not initialized
}
framesRead = m_readDataFunc (m_fileHandle, frameBuf, __min (m_frameLimit, frameCount), m_waveChannels, m_byteBuffer);
m_bytesRead = m_waveFrameSize * framesRead;
m_bytesRemaining -= m_bytesRead;
m_chunkLength += m_bytesRead;
return framesRead;
}
void BasicWavReader::reset ()
{
m_byteBuffer = nullptr;
m_bytesRead = 0;
m_bytesRemaining = 0;
m_chunkLength = 0;
m_frameLimit = 0;
m_readDataFunc = nullptr;
m_readOffset = 0;
m_waveBitDepth = 0;
m_waveChannels = 0;
m_waveFrameRate = 0;
if (m_fileHandle != -1) _SEEK (m_fileHandle, 0, 0 /*SEEK_SET*/);
}

92
src/app/basicWavReader.h Normal file
View File

@ -0,0 +1,92 @@
/* basicWavReader.h - header file for class with basic WAVE file reading capability
* written by C. R. Helmrich, last modified in 2019 - 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.
*/
#ifndef _BASIC_WAV_READER_H_
#define _BASIC_WAV_READER_H_
#include "exhaleAppPch.h"
// constant data sizes & limits
#define BWR_BUFFERED_READ 1 // faster reader
#define CHUNK_FORMAT_MAX 20
#define CHUNK_FORMAT_SIZE 16
#define CHUNK_HEADER_SIZE 8
#define FILE_HEADER_SIZE 12
#define MAX_VALUE_AUDIO24 8388607 // (1 << 23) - 1
#define MIN_VALUE_AUDIO24 -8388608 // (1 << 23) *-1
// WAVE data format definitions
typedef enum WAV_TYPE
{
WAV_PCM = 0, // linear PCM
WAV_ADPCM, // ADPCM
WAV_FLOAT // IEEE float
} WAV_TYPE;
// data reader function pointer
typedef unsigned (*ReadFunc) (const int, int32_t*, const unsigned, const unsigned, void*);
// basic WAV audio reader class
class BasicWavReader
{
private:
// member variables
char* m_byteBuffer;
unsigned m_bytesRead;
int64_t m_bytesRemaining;
int64_t m_chunkLength;
int m_fileHandle;
unsigned m_frameLimit;
ReadFunc m_readDataFunc;
int64_t m_readOffset;
unsigned m_waveBitDepth;
unsigned m_waveBitRate;
unsigned m_waveChannels;
WAV_TYPE m_waveDataType;
unsigned m_waveFrameRate;
unsigned m_waveFrameSize;
// private reader functions
bool readRiffHeader ();
bool readFormatChunk();
bool readDataHeader ();
// private helper function
bool seekToChunkTag (uint8_t* const buf, const uint32_t tagName);
// static reading functions
static unsigned readDataFloat16 (const int fileHandle, int32_t* frameBuf, const unsigned frameCount,
const unsigned chanCount, void* tempBuf);
static unsigned readDataFloat32 (const int fileHandle, int32_t* frameBuf, const unsigned frameCount,
const unsigned chanCount, void* tempBuf);
static unsigned readDataLnPcm08 (const int fileHandle, int32_t* frameBuf, const unsigned frameCount,
const unsigned chanCount, void* tempBuf);
static unsigned readDataLnPcm16 (const int fileHandle, int32_t* frameBuf, const unsigned frameCount,
const unsigned chanCount, void* tempBuf);
static unsigned readDataLnPcm24 (const int fileHandle, int32_t* frameBuf, const unsigned frameCount,
const unsigned chanCount, void* tempBuf);
static unsigned readDataLnPcm32 (const int fileHandle, int32_t* frameBuf, const unsigned frameCount,
const unsigned chanCount, void* tempBuf);
public:
// constructor
BasicWavReader () { m_fileHandle = -1; reset (); }
// destructor
~BasicWavReader() { if (m_byteBuffer != nullptr) free ((void*) m_byteBuffer); }
// public functions
int64_t getDataBytesLeft () const { return m_bytesRemaining; }
int64_t getDataBytesRead () const { return m_chunkLength; }
unsigned getBitDepth () const { return m_waveBitDepth; }
unsigned getNumChannels () const { return m_waveChannels; }
unsigned getSampleRate () const { return m_waveFrameRate; }
unsigned open (const int wavFileHandle, const uint16_t maxFrameRead, const int64_t fileLength = LLONG_MAX /*for stdin*/);
unsigned read (int32_t* const frameBuf, const uint16_t frameCount);
void reset ();
}; // BasicWavReader
#endif // _BASIC_WAV_READER_H_

View File

@ -0,0 +1,5 @@
basicWavReader.cpp - known issues
_________________________________
2019: for 8-bit PCM audio, the WAVE file must contain an even number of samples.
This is guaranteed for an even number of channels (e.g., 2-channel stereo)

545
src/app/exhaleApp.cpp Normal file
View File

@ -0,0 +1,545 @@
/* exhaleApp.cpp - source file with main() routine for exhale application executable
* written by C. R. Helmrich, last modified in 2019 - 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 "../lib/exhaleEnc.h"
#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)
#endif
#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;
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 < 65535); 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 == 65535))
{
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");
#if defined (_WIN32) || defined (WIN32) || defined (_WIN64) || defined (WIN64)
GetConsoleScreenBufferInfo (hConsole, &csbi); /*save the text color*/ fprintf_s (stdout, " | ");
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 - ecodis extended high-efficiency and low-complexity encoder |\n");
#endif
fprintf_s (stdout, " | |\n");
#if defined (_WIN64) || defined (WIN64) || 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-2019 C.R.Helmrich, project ecodis. See License.txt 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, " Usage:\t");
#endif
fprintf_s (stdout, "%s preset [inputWaveFile.wav] outputMP4File.m4a\n\n where\n\n", exeFileName);
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");
#else
// fprintf_s (stdout, " \t (a-i) low-complexity compatible xHE-AAC with BE at 16<31>#+48 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");
#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, " Notes:\t");
#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, a-i, or A-I.\n\n", argv[1]);
#else
fprintf_s (stderr, " ERROR reading preset mode: character %s is not supported! Use 1-9 or a-i.\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)
#else // Linux, MacOS, Unix
inFileHandle = 0; // TODO
// if (::setmode (inFileHandle, O_RDONLY) == -1)
#endif
{
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 // argc = 4, open input file
{
const char* inFileName = argv[2];
uint16_t inPathEnd = 0;
for (i = 0; (inFileName[i] != 0) && (i < 65535); 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 == 65535))
{
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 | O_LARGEFILE, 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 < 65535); 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 == 65535))
{
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_LARGEFILE | 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);
// open & prepare ExhaleEncoder object
uint32_t byteCount = 0, bw = 0, bwMax = 0, br; // for bytes read and bit-rate
uint32_t headerRes = 0;
ExhaleEncoder exhaleEnc (inPcmData, outAuData, sampleRate, numChannels, frameLength, indepPeriod, variableCoreBitRateMode +
(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 holds 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
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, " Progress: -"); 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
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
goto mainFinish; // coder-time error
}
if (bwMax < bw) bwMax = bw;
// write first AU, add frame to header
if (mp4Writer.addFrameAU (outAuData, byteCount, bw) != bw) goto mainFinish;
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
goto mainFinish;
}
if (bwMax < bw) bwMax = bw;
// write new AU, add frame to header
if (mp4Writer.addFrameAU (outAuData, byteCount, bw) != bw) goto mainFinish;
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
goto mainFinish; // coder-time error
}
if (bwMax < bw) bwMax = bw;
// write final AU, add frame to header
if (mp4Writer.addFrameAU (outAuData, byteCount, bw) != bw) goto mainFinish;
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
goto mainFinish;
}
if (bwMax < bw) bwMax = bw;
// the flush AU, add frame to header
if (mp4Writer.addFrameAU (outAuData, byteCount, bw) != bw) goto mainFinish;
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
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*/);
_READ (outFileHandle, inPcmData, br);
_SEEK (outFileHandle, pos + headerRes, 0 /*SEEK_SET*/);
_WRITE(outFileHandle, inPcmData, br);
}
if ((br = (uint32_t) __max (0, pos + br)) > 0) // remainder of data to move
{
_SEEK (outFileHandle, 0, 0 /*SEEK_SET*/);
_READ (outFileHandle, inPcmData, br);
_SEEK (outFileHandle, headerRes, 0 /*SEEK_SET*/);
_WRITE(outFileHandle, inPcmData, br);
}
}
// 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);
fprintf_s (stdout, " Done, actual average %.1f kbit/s\n\n", (float) br * 0.001f);
i = 0; // no errors
if (!readStdin && (actualLength != expectLength || bw != headerRes))
{
fprintf_s (stderr, " WARNING: %ld sample frames read but %ld sample frames expected!\n", actualLength, expectLength);
if (bw != headerRes) fprintf_s (stderr, " The encoded MPEG-4 bit-stream is likely to be unreadable!\n");
fprintf_s (stderr, "\n");
}
} // 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);
}

BIN
src/app/exhaleApp.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

35
src/app/exhaleApp.rc Normal file
View File

@ -0,0 +1,35 @@
/* exhaleApp.rc - resource file for exhale application binaries compiled under Windows
* written by C. R. Helmrich, last modified in 2019 - 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 "..\..\include\version.h" // for EXHALELIB_VERSION_... strings
#include "winres.h"
0 ICON "exhaleApp.ico"
VS_VERSION_INFO VERSIONINFO
FILEVERSION 1,0,0
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904b0"
BEGIN
VALUE "CompanyName", "ecodis"
VALUE "FileDescription", "exhale - ecodis extended high-efficiency and low-complexity encoder"
VALUE "InternalName", "exhaleApp.exe"
VALUE "LegalCopyright", "<22> 2018-2020 C. R. Helmrich, ecodis"
VALUE "OriginalFilename", "exhale.exe"
VALUE "ProductName", "exhaleApp"
VALUE "ProductVersion", EXHALELIB_VERSION_MAJOR "." EXHALELIB_VERSION_MINOR EXHALELIB_VERSION_BUGFIX
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200
END
END

27
src/app/exhaleAppPch.cpp Normal file
View File

@ -0,0 +1,27 @@
/* exhaleAppPch.cpp - pre-compiled source file for source code of exhale application
* written by C. R. Helmrich, last modified in 2019 - 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"
// ISO/IEC 23003-3 USAC Table 67
static const unsigned supportedSamplingRates[26] = {
96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350, // AAC
57600, 51200, 40000, 38400, 34150, 28800, 25600, 20000, 19200, 17075, 14400, 12800, 9600 // USAC
};
// public sampling rate function
bool isSamplingRateSupported (const unsigned samplingRate)
{
for (unsigned i = 0; i < 26; i++)
{
if (samplingRate == supportedSamplingRates[i]) return true;
}
return false; // not supported
}

48
src/app/exhaleAppPch.h Normal file
View File

@ -0,0 +1,48 @@
/* exhaleAppPch.h - pre-compiled header file for source code of exhale application
* written by C. R. Helmrich, last modified in 2019 - 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.
*/
#ifndef _EXHALE_APP_PCH_H_
#define _EXHALE_APP_PCH_H_
#include <limits.h> // for .._MAX, .._MIN
#include <stdint.h> // for (u)int8_t, (u)int16_t, (u)int32_t, (u)int64_t
#include <stdlib.h> // for abs, div, calloc, malloc, free, (__)max, (__)min, (s)rand
#include <string.h> // for memcpy, memset
#include <vector> // for std::vector <>
#if defined (_WIN32) || defined (WIN32) || defined (_WIN64) || defined (WIN64)
# include <io.h>
# define _CLOSE _close
# define _READ _read
# define _SEEK _lseeki64
# define _WRITE _write
#else // Linux, MacOS, Unix
# include <unistd.h>
# define _CLOSE ::close
# define _READ ::read
# define _SEEK ::lseek
# define _WRITE ::write
#endif
#ifndef __max
# define __max(a, b) ((a) > (b) ? (a) : (b))
#endif
#ifndef __min
# define __min(a, b) ((a) < (b) ? (a) : (b))
#endif
#ifndef fprintf_s
# define fprintf_s fprintf
#endif
// public sampling rate function
bool isSamplingRateSupported (const unsigned samplingRate);
#endif // _EXHALE_APP_PCH_H_

View File

@ -0,0 +1,181 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{EC0D1502-2018-1700-4865-6C6D72696368}</ProjectGuid>
<ProjectName>exhaleApp</ProjectName>
<RootNamespace>exhaleApp</RootNamespace>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<PlatformToolset>v110</PlatformToolset>
<UseDebugLibraries>true</UseDebugLibraries>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<PlatformToolset>v110</PlatformToolset>
<UseDebugLibraries>true</UseDebugLibraries>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<PlatformToolset>v110</PlatformToolset>
<UseDebugLibraries>false</UseDebugLibraries>
<WholeProgramOptimization>true</WholeProgramOptimization>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<PlatformToolset>v110</PlatformToolset>
<UseDebugLibraries>false</UseDebugLibraries>
<WholeProgramOptimization>true</WholeProgramOptimization>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings" />
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<IntDir>$(SolutionDir)build\$(PlatformToolset)\$(Platform)\$(Configuration)\</IntDir>
<OutDir>$(SolutionDir)bin\$(PlatformToolset)\$(Platform)\$(Configuration)\</OutDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<IntDir>$(SolutionDir)build\$(PlatformToolset)\$(Platform)\$(Configuration)\</IntDir>
<OutDir>$(SolutionDir)bin\$(PlatformToolset)\$(Platform)\$(Configuration)\</OutDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<LinkIncremental>false</LinkIncremental>
<IntDir>$(SolutionDir)build\$(PlatformToolset)\$(Platform)\$(Configuration)\</IntDir>
<OutDir>$(SolutionDir)bin\$(PlatformToolset)\$(Platform)\$(Configuration)\</OutDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LinkIncremental>false</LinkIncremental>
<IntDir>$(SolutionDir)build\$(PlatformToolset)\$(Platform)\$(Configuration)\</IntDir>
<OutDir>$(SolutionDir)bin\$(PlatformToolset)\$(Platform)\$(Configuration)\</OutDir>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<AdditionalIncludeDirectories>$(SolutionDir)include</AdditionalIncludeDirectories>
<Optimization>Disabled</Optimization>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>exhaleAppPch.h</PrecompiledHeaderFile>
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<TreatWarningAsError>true</TreatWarningAsError>
<WarningLevel>Level3</WarningLevel>
</ClCompile>
<Link>
<AdditionalLibraryDirectories>$(SolutionDir)lib\$(PlatformToolset)\$(Platform)\$(Configuration)\</AdditionalLibraryDirectories>
<GenerateDebugInformation>true</GenerateDebugInformation>
<SubSystem>Console</SubSystem>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<AdditionalIncludeDirectories>$(SolutionDir)include</AdditionalIncludeDirectories>
<Optimization>Disabled</Optimization>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>exhaleAppPch.h</PrecompiledHeaderFile>
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<TreatWarningAsError>true</TreatWarningAsError>
<WarningLevel>Level3</WarningLevel>
</ClCompile>
<Link>
<AdditionalLibraryDirectories>$(SolutionDir)lib\$(PlatformToolset)\$(Platform)\$(Configuration)\</AdditionalLibraryDirectories>
<GenerateDebugInformation>true</GenerateDebugInformation>
<SubSystem>Console</SubSystem>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<AdditionalIncludeDirectories>$(SolutionDir)include</AdditionalIncludeDirectories>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<Optimization>MaxSpeed</Optimization>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>exhaleAppPch.h</PrecompiledHeaderFile>
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<TreatWarningAsError>true</TreatWarningAsError>
<WarningLevel>Level3</WarningLevel>
</ClCompile>
<Link>
<AdditionalLibraryDirectories>$(SolutionDir)lib\$(PlatformToolset)\$(Platform)\$(Configuration)\</AdditionalLibraryDirectories>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<GenerateDebugInformation>false</GenerateDebugInformation>
<OptimizeReferences>true</OptimizeReferences>
<SubSystem>Console</SubSystem>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<AdditionalIncludeDirectories>$(SolutionDir)include</AdditionalIncludeDirectories>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<Optimization>MaxSpeed</Optimization>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>exhaleAppPch.h</PrecompiledHeaderFile>
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<TreatWarningAsError>true</TreatWarningAsError>
<WarningLevel>Level3</WarningLevel>
</ClCompile>
<Link>
<AdditionalLibraryDirectories>$(SolutionDir)lib\$(PlatformToolset)\$(Platform)\$(Configuration)\</AdditionalLibraryDirectories>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<GenerateDebugInformation>false</GenerateDebugInformation>
<OptimizeReferences>true</OptimizeReferences>
<SubSystem>Console</SubSystem>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="..\..\include\exhaleDecl.h" />
<ClInclude Include="..\..\include\version.h" />
<ClInclude Include="basicMP4Writer.h" />
<ClInclude Include="basicWavReader.h" />
<ClInclude Include="exhaleAppPch.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="basicMP4Writer.cpp" />
<ClCompile Include="basicWavReader.cpp" />
<ClCompile Include="exhaleApp.cpp" />
<ClCompile Include="exhaleAppPch.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="exhaleApp.rc" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\lib\exhaleLib_vs2012.vcxproj">
<Project>{EC0D1501-2018-1700-4865-6C6D72696368}</Project>
</ProjectReference>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
</Project>

View File

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Header Files">
<UniqueIdentifier>{EC0D1506-2018-1700-4865-6C6D72696368}</UniqueIdentifier>
<Extensions>h;hpp</Extensions>
</Filter>
<Filter Include="Source Files">
<UniqueIdentifier>{EC0D1507-2018-1700-4865-6C6D72696368}</UniqueIdentifier>
<Extensions>cpp;c;asm;asmx</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{EC0D1508-2018-1700-4865-6C6D72696368}</UniqueIdentifier>
<Extensions>rc;ico</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\..\include\exhaleDecl.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\include\version.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="basicMP4Writer.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="basicWavReader.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="exhaleAppPch.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="basicMP4Writer.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="basicWavReader.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="exhaleApp.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="exhaleAppPch.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="exhaleApp.rc">
<Filter>Resource Files</Filter>
</ResourceCompile>
</ItemGroup>
</Project>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<LocalDebuggerCommandArguments># C:\ha2010.wav debug.m4a &gt; ..\..\debug.txt</LocalDebuggerCommandArguments>
<DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
</PropertyGroup>
</Project>

50
src/app/makefile Normal file
View File

@ -0,0 +1,50 @@
## makefile - application make-file for compiling exhale on Linux and MacOS platforms
# written by C. R. Helmrich, last modified 2019 - see License.txt for legal notices
#
# The copyright in this software is being made available under a Modified BSD 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-2019 Christian R. Helmrich, project ecodis. All rights reserved.
##
# define as console application
CONFIG = CONSOLE
# source and output directories
DIR_BIN = ../../bin
DIR_OBJ = ../../build
DIR_INC = ../../include
DIR_LIB = ../../lib
DIR_SRC = ../../src/app
# build with large file support
DEFS = -DMSYS_LINUX -DMSYS_UNIX_LARGEFILE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64
# name of product / binary file
PRD_NAME = Exhale
# name of temporary object file
OBJS = \
$(DIR_OBJ)/basicMP4Writer.o \
$(DIR_OBJ)/basicWavReader.o \
$(DIR_OBJ)/exhaleApp.o \
$(DIR_OBJ)/exhaleAppPch.o \
# define libraries to link with
LIBS = -ldl
DYN_LIBS =
STAT_LIBS = -lpthread
DYN_DEBUG_LIBS = -lExhaleDynd
DYN_DEBUG_PREREQS = $(DIR_LIB)/libExhaleDynd.a
STAT_DEBUG_LIBS = -lExhaled
STAT_DEBUG_PREREQS = $(DIR_LIB)/libExhaled.a
DYN_RELEASE_LIBS = -lExhale
DYN_RELEASE_PREREQS = $(DIR_LIB)/libExhale.a
STAT_RELEASE_LIBS = -lExhale
STAT_RELEASE_PREREQS = $(DIR_LIB)/libExhale.a
# include common makefile.base
include ../makefile.base

347
src/lib/bitAllocation.cpp Normal file
View File

@ -0,0 +1,347 @@
/* bitAllocation.cpp - source file for class needed for psychoacoustic bit-allocation
* written by C. R. Helmrich, last modified in 2019 - 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 "exhaleLibPch.h"
#include "bitAllocation.h"
// static helper functions
static inline uint32_t jndModel (const uint32_t val, const uint32_t mean,
const unsigned expTimes512, const unsigned mulTimes512)
{
const double exp = (double) expTimes512 / 512.0; // exponent
const double mul = (double) mulTimes512 / 512.0; // a factor
const double res = pow (mul * (double) val, exp) * pow ((double) mean, 1.0 - exp);
return uint32_t (__min ((double) UINT_MAX, res + 0.5));
}
static void jndPowerLawAndPeakSmoothing (uint32_t* const stepSizes, const unsigned nStepSizes,
const uint32_t avgStepSize, const uint8_t sfm, const uint8_t tfm)
{
const unsigned expTimes512 = 512u - sfm; // 1.0 - sfm / 2.0
const unsigned mulTimes512 = __min (expTimes512, 512u - tfm);
uint32_t stepSizeM3 = 0, stepSizeM2 = 0, stepSizeM1 = 99 + BA_EPS; // hearing threshold around DC
unsigned b;
for (b = 0; b < __min (2, nStepSizes); b++)
{
stepSizeM3 = stepSizeM2;
stepSizeM2 = stepSizeM1;
stepSizeM1 = stepSizes[b] = jndModel (stepSizes[b], avgStepSize, expTimes512, mulTimes512);
}
stepSizes[0] = __min (stepSizeM1, stepSizes[0]); // `- becomes --
for (/*b*/; b < nStepSizes; b++)
{
const uint32_t stepSizeB = jndModel (stepSizes[b], avgStepSize, expTimes512, mulTimes512);
if ((stepSizeM3 <= stepSizeM2) && (stepSizeM3 <= stepSizeM1) && (stepSizeB <= stepSizeM2) && (stepSizeB <= stepSizeM1))
{
const uint32_t maxM3M0 = __max (stepSizeM3, stepSizeB); // smoothen local spectral peak of _<>`- shape
stepSizes[b - 2] = __min (maxM3M0, stepSizes[b - 2]); // _-`-
stepSizes[b - 1] = __min (maxM3M0, stepSizes[b - 1]); // _---
}
stepSizeM3 = stepSizeM2;
stepSizeM2 = stepSizeM1;
stepSizeM1 = (stepSizes[b] = stepSizeB); // modified step-size may be smoothened in next loop iteration
}
}
// constructor
BitAllocator::BitAllocator ()
{
for (unsigned ch = 0; ch < USAC_MAX_NUM_CHANNELS; ch++)
{
m_avgStepSize[ch] = 0;
m_avgSpecFlat[ch] = 0;
m_avgTempFlat[ch] = 0;
}
}
// public functions
void BitAllocator::getChAverageSpecFlat (uint8_t meanSpecFlatInCh[USAC_MAX_NUM_CHANNELS], const unsigned nChannels)
{
if ((meanSpecFlatInCh == nullptr) || (nChannels > USAC_MAX_NUM_CHANNELS))
{
return;
}
memcpy (meanSpecFlatInCh, m_avgSpecFlat, nChannels * sizeof (uint8_t));
}
void BitAllocator::getChAverageTempFlat (uint8_t meanTempFlatInCh[USAC_MAX_NUM_CHANNELS], const unsigned nChannels)
{
if ((meanTempFlatInCh == nullptr) || (nChannels > USAC_MAX_NUM_CHANNELS))
{
return;
}
memcpy (meanTempFlatInCh, m_avgTempFlat, nChannels * sizeof (uint8_t));
}
uint8_t BitAllocator::getScaleFac (const uint32_t sfbStepSize, const int32_t* const sfbSignal, const uint8_t sfbWidth,
const uint32_t sfbRmsValue)
{
uint8_t sf;
uint32_t u;
#if !RESTRICT_TO_AAC
uint64_t meanSpecLoudness = 0;
double d;
#endif
if ((sfbSignal == nullptr) || (sfbWidth == 0) || (sfbRmsValue < 46))
{
return 0; // use lowest scale factor
}
#if RESTRICT_TO_AAC
u = 0;
for (sf = 0; sf < sfbWidth; sf++)
{
u += uint32_t (0.5 + sqrt (abs ((double) sfbSignal[sf])));
}
u = uint32_t ((u * 16384ui64 + (sfbWidth >> 1)) / sfbWidth);
u = uint32_t (0.5 + sqrt ((double) u) * 128.0);
if (u < 42567) return 0;
u = uint32_t ((sfbStepSize * 42567ui64 + (u >> 1)) / u);
sf = (u > 1 ? uint8_t (0.5 + 17.7169498394 * log10 ((double) u)) : 4);
#else
for (sf = 0; sf < sfbWidth; sf++) // simple, low-complexity derivation method for USAC's arithmetic coder
{
const int64_t temp = ((int64_t) sfbSignal[sf] + 8) >> 4; // avoid overflow
meanSpecLoudness += temp * temp;
}
meanSpecLoudness = uint64_t (0.5 + pow (256.0 * (double) meanSpecLoudness / sfbWidth, 0.25));
u = uint32_t (0.5 + pow ((double) sfbRmsValue, 0.75) * 256.0); // u = 2^8 * (sfbRmsValue^0.75)
u = uint32_t ((meanSpecLoudness * sfbStepSize * 665 + (u >> 1)) / u); // u = sqrt(6.75) * m*thr/u
d = (u > 1 ? log10 ((double) u) : 0.25);
u = uint32_t (0.5 + pow ((double) sfbRmsValue, 0.25) * 16384.0); // u = 2^14 * (sfbRmsValue^0.25)
u = uint32_t (((uint64_t) sfbStepSize * 42567 + (u >> 1)) / u); // u = sqrt(6.75) * thr/u
d += (u > 1 ? log10 ((double) u) : 0.25);
sf = uint8_t (0.5 + 8.8584749197 * d); // sf = (8/3) * log2(u1*u2) = (8/3) * (log2(u1)+log2(u2))
#endif
return __min (SCHAR_MAX, sf);
}
unsigned BitAllocator::initSfbStepSizes (const SfbGroupData* const groupData[USAC_MAX_NUM_CHANNELS], const uint8_t numSwbShort,
const uint32_t specAnaStats[USAC_MAX_NUM_CHANNELS],
const uint32_t tempAnaStats[USAC_MAX_NUM_CHANNELS],
const unsigned nChannels, const unsigned samplingRate, uint32_t* const sfbStepSizes,
const unsigned lfeChannelIndex /*= USAC_MAX_NUM_CHANNELS*/, const bool tnsDisabled /*= false*/)
{
// equal-loudness weighting based on data from: K. Kurakata, T. Mizunami, and K. Matsushita, "Percentiles
// of Normal Hearing-Threshold Distribution Under Free-Field Listening Conditions in Numerical Form," Ac.
// Sci. Tech, vol. 26, no. 5, pp. 447-449, Jan. 2005, https://www.researchgate.net/publication/239433096.
const unsigned HF/*idx*/= ((123456 - samplingRate) >> 11) + (samplingRate <= 34150 ? 2 : 0); // start SFB
const unsigned LF/*idx*/= 9;
const unsigned MF/*idx*/= (samplingRate < 28800 ? HF : __min (HF, 30u));
const unsigned msShift = (samplingRate + 36736) >> 15; // TODO: 768 smp
const unsigned msOffset = 1 << (msShift - 1);
uint32_t nMeans = 0, sumMeans = 0;
if ((groupData == nullptr) || (specAnaStats == nullptr) || (tempAnaStats == nullptr) || (sfbStepSizes == nullptr) ||
(numSwbShort < MIN_NUM_SWB_SHORT) || (numSwbShort > MAX_NUM_SWB_SHORT) || (nChannels > USAC_MAX_NUM_CHANNELS) ||
(samplingRate < 7350) || (samplingRate > 96000) || (lfeChannelIndex > USAC_MAX_NUM_CHANNELS))
{
return 1; // invalid arguments error
}
for (unsigned ch = 0; ch < nChannels; ch++)
{
const SfbGroupData& grpData = *groupData[ch];
const uint32_t maxSfbInCh = grpData.sfbsPerGroup;
const uint32_t nBandsInCh = grpData.numWindowGroups * maxSfbInCh;
const uint32_t* rms = grpData.sfbRmsValues;
uint32_t* stepSizes = &sfbStepSizes[ch * numSwbShort * NUM_WINDOW_GROUPS];
// --- apply INTRA-channel simultaneous masking, equal-loudness weighting, and thresholding to SFB RMS data
uint32_t maskingSlope = LF, b, elw = 58254; // 8/9
uint32_t rmsEqualLoud = 0;
uint32_t sumStepSizes = 0;
m_avgStepSize[ch] = 0;
b = ((specAnaStats[ch] >> 16) & UCHAR_MAX); // start with squared spec. flatness from spectral analysis
b = __max (b * b, (tempAnaStats[ch] >> 24) * (tempAnaStats[ch] >> 24)); // ..and from temporal analysis
m_avgSpecFlat[ch] = uint8_t ((b + (1 << 7)) >> 8); // normalized maximum
b = ((tempAnaStats[ch] >> 16) & UCHAR_MAX); // now derive squared temp. flatness from temporal analysis
b = __max (b * b, (specAnaStats[ch] >> 24) * (specAnaStats[ch] >> 24)); // ..and from spectral analysis
m_avgTempFlat[ch] = uint8_t ((b + (1 << 7)) >> 8); // normalized maximum
if ((nBandsInCh == 0) || (grpData.numWindowGroups > NUM_WINDOW_GROUPS))
{
continue;
}
if ((ch == lfeChannelIndex) || (grpData.numWindowGroups != 1)) // LFE, SHORT windows: no masking or ELW
{
for (unsigned gr = 0; gr < grpData.numWindowGroups; gr++)
{
const uint32_t* gRms = &rms[numSwbShort * gr];
uint32_t* gStepSizes = &stepSizes[numSwbShort * gr];
for (b = numSwbShort - 1; b >= maxSfbInCh; b--)
{
gStepSizes[b] = 0;
}
for (/*b*/; b > 0; b--)
{
gStepSizes[b] = __max (gRms[b], BA_EPS);
sumStepSizes += unsigned (0.5 + sqrt ((double) gStepSizes[b]));
}
gStepSizes[0] = __max (gRms[0], maskingSlope + BA_EPS);
sumStepSizes += unsigned (0.5 + sqrt ((double) gStepSizes[0]));
} // for gr
if (ch != lfeChannelIndex)
{
// --- SHORT windows: apply perceptual just noticeable difference (JND) model and local band-peak smoothing
nMeans++;
m_avgStepSize[ch] = __min (USHRT_MAX, uint32_t ((sumStepSizes + (nBandsInCh >> 1)) / nBandsInCh));
sumMeans += m_avgStepSize[ch];
m_avgStepSize[ch] *= m_avgStepSize[ch];
for (unsigned gr = 0; gr < grpData.numWindowGroups; gr++) // separate peak smoothing for each group
{
jndPowerLawAndPeakSmoothing (&stepSizes[numSwbShort * gr], maxSfbInCh, m_avgStepSize[ch], m_avgSpecFlat[ch], 0);
}
}
continue;
}
stepSizes[0] = __max (rms[0], maskingSlope + BA_EPS);
for (b = 1; b < __min (LF, maxSfbInCh); b++) // apply steeper low-frequency simultaneous masking slopes
{
maskingSlope = (stepSizes[b - 1] + (msOffset << (9u - b))) >> (msShift + 9u - b);
stepSizes[b] = __max (rms[b], maskingSlope + BA_EPS);
}
for (/*b*/; b < __min (MF, maxSfbInCh); b++) // apply typical mid-frequency simultaneous masking slopes
{
maskingSlope = (stepSizes[b - 1] + msOffset) >> msShift;
stepSizes[b] = __max (rms[b], maskingSlope + BA_EPS);
}
if ((samplingRate >= 28800) && (samplingRate <= 64000))
{
for (/*b*/; b < __min (HF, maxSfbInCh); b++) // compensate high-frequency slopes for linear SFB width
{
maskingSlope = ((uint64_t) stepSizes[b - 1] * (9u + b - MF) + (msOffset << 3u)) >> (msShift + 3u);
stepSizes[b] = __max (rms[b], maskingSlope + BA_EPS);
}
for (/*b = HF region*/; b < maxSfbInCh; b++) // apply extra high-frequency equal-loudness attenuation
{
for (unsigned d = b - HF; d > 0; d--)
{
elw = (elw * 52430 - SHRT_MIN) >> 16; // elw *= 4/5
}
rmsEqualLoud = uint32_t (((uint64_t) rms[b] * elw - SHRT_MIN) >> 16); // equal loudness weighting
maskingSlope = ((uint64_t) stepSizes[b - 1] * (9u + b - MF) + (msOffset << 3u)) >> (msShift + 3u);
stepSizes[b] = __max (rmsEqualLoud, maskingSlope + BA_EPS);
}
}
else // no equal-loudness weighting for low or high rates
{
for (/*b = MF region*/; b < maxSfbInCh; b++) // compensate high-frequency slopes for linear SFB width
{
maskingSlope = ((uint64_t) stepSizes[b - 1] * (9u + b - MF) + (msOffset << 3u)) >> (msShift + 3u);
stepSizes[b] = __max (rms[b], maskingSlope + BA_EPS);
}
}
for (b -= 1; b > __min (MF, maxSfbInCh); b--) // complete simultaneous masking by reversing the pattern
{
sumStepSizes += unsigned (0.5 + sqrt ((double) stepSizes[b]));
maskingSlope = ((uint64_t) stepSizes[b] * (8u + b - MF) + (msOffset << 3u)) >> (msShift + 3u);
stepSizes[b - 1] = __max (stepSizes[b - 1], maskingSlope);
}
for (/*b*/; b > __min (LF, maxSfbInCh); b--) // typical reversed mid-freq. simultaneous masking slopes
{
sumStepSizes += unsigned (0.5 + sqrt ((double) stepSizes[b]));
maskingSlope = (stepSizes[b] + msOffset) >> msShift;
stepSizes[b - 1] = __max (stepSizes[b - 1], maskingSlope);
}
for (/*b = min (9, maxSfbInCh)*/; b > 0; b--) // steeper reversed low-freq. simultaneous masking slopes
{
sumStepSizes += unsigned (0.5 + sqrt ((double) stepSizes[b]));
maskingSlope = (stepSizes[b] + (msOffset << (10u - b))) >> (msShift + 10u - b);
stepSizes[b - 1] = __max (stepSizes[b - 1], maskingSlope);
}
sumStepSizes += unsigned (0.5 + sqrt ((double) stepSizes[0]));
// --- LONG window: apply perceptual JND model and local band-peak smoothing, undo equal-loudness weighting
nMeans++;
m_avgStepSize[ch] = __min (USHRT_MAX, uint32_t ((sumStepSizes + (nBandsInCh >> 1)) / nBandsInCh));
sumMeans += m_avgStepSize[ch];
m_avgStepSize[ch] *= m_avgStepSize[ch];
jndPowerLawAndPeakSmoothing (stepSizes, maxSfbInCh, m_avgStepSize[ch], m_avgSpecFlat[ch], tnsDisabled ? m_avgTempFlat[ch] : 0);
if ((samplingRate >= 28800) && (samplingRate <= 64000))
{
elw = 36; // 36/32 = 9/8
for (b = HF; b < grpData.sfbsPerGroup; b++) // undo additional high-freq. equal-loudness attenuation
{
for (unsigned d = b - HF; d > 0; d--)
{
elw = (16u + elw * 40) >> 5; // inverse elw *= 5/4. NOTE: this may overflow for 64 kHz, that's OK
}
if (elw == 138 || elw >= 1024) elw--;
rmsEqualLoud = uint32_t (__min (UINT_MAX, (16u + (uint64_t) stepSizes[b] * elw) >> 5));
stepSizes[b] = rmsEqualLoud;
}
}
} // for ch
if ((nMeans < 2) || (sumMeans <= nMeans * BA_EPS)) // in case of one channel or low-RMS input, we're done
{
return 0; // no error
}
sumMeans = (sumMeans + (nMeans >> 1)) / nMeans;
sumMeans *= sumMeans; // since we've averaged square-roots
#if BA_INTER_CHAN_SIM_MASK
if (nMeans > 3)
{
// TODO: cross-channel simultaneous masking for 4.0 - 7.1
}
#endif
for (unsigned ch = 0; ch < nChannels; ch++)
{
const SfbGroupData& grpData = *groupData[ch];
const uint32_t maxSfbInCh = grpData.sfbsPerGroup;
const uint32_t nBandsInCh = grpData.numWindowGroups * maxSfbInCh;
const uint32_t chStepSize = m_avgStepSize[ch];
uint32_t* stepSizes = &sfbStepSizes[ch * numSwbShort * NUM_WINDOW_GROUPS];
// --- apply INTER-channel simultaneous masking and JND modeling to calculated INTRA-channel step-size data
uint64_t mAvgStepSize; // modified and averaged step-size
if ((nBandsInCh == 0) || (grpData.numWindowGroups > NUM_WINDOW_GROUPS) || (ch == lfeChannelIndex))
{
continue;
}
mAvgStepSize = jndModel (chStepSize, sumMeans, 7 << 6 /*7/8*/, 512);
for (unsigned gr = 0; gr < grpData.numWindowGroups; gr++)
{
uint32_t* gStepSizes = &stepSizes[numSwbShort * gr];
for (unsigned b = 0; b < maxSfbInCh; b++)
{
gStepSizes[b] = uint32_t (__min (UINT_MAX, (mAvgStepSize * gStepSizes[b] + (chStepSize >> 1)) / chStepSize));
}
} // for gr
m_avgStepSize[ch] = (uint32_t) mAvgStepSize;
} // for ch
return 0; // no error
}

48
src/lib/bitAllocation.h Normal file
View File

@ -0,0 +1,48 @@
/* bitAllocation.h - header file for class needed for psychoacoustic bit-allocation
* written by C. R. Helmrich, last modified in 2019 - 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.
*/
#ifndef _BIT_ALLOCATION_H_
#define _BIT_ALLOCATION_H_
#include "exhaleLibPch.h"
// constants, experimental macros
#define BA_EPS 1
#define BA_INTER_CHAN_SIM_MASK 0 // cross-channel simultaneous masking for surround
// class for audio bit-allocation
class BitAllocator
{
private:
// member variables
uint32_t m_avgStepSize[USAC_MAX_NUM_CHANNELS];
uint8_t m_avgSpecFlat[USAC_MAX_NUM_CHANNELS];
uint8_t m_avgTempFlat[USAC_MAX_NUM_CHANNELS];
public:
// constructor
BitAllocator ();
// destructor
~BitAllocator () { }
// public functions
void getChAverageSpecFlat (uint8_t meanSpecFlatInCh[USAC_MAX_NUM_CHANNELS], const unsigned nChannels);
void getChAverageTempFlat (uint8_t meanTempFlatInCh[USAC_MAX_NUM_CHANNELS], const unsigned nChannels);
uint8_t getScaleFac (const uint32_t sfbStepSize, const int32_t* const sfbSignal, const uint8_t sfbWidth,
const uint32_t sfbRmsValue);
unsigned initSfbStepSizes (const SfbGroupData* const groupData[USAC_MAX_NUM_CHANNELS], const uint8_t numSwbShort,
const uint32_t specAnaStats[USAC_MAX_NUM_CHANNELS],
const uint32_t tempAnaStats[USAC_MAX_NUM_CHANNELS],
const unsigned nChannels, const unsigned samplingRate, uint32_t* const sfbStepSizes,
const unsigned lfeChannelIndex = USAC_MAX_NUM_CHANNELS, const bool tnsDisabled = false);
}; // BitAllocator
#endif // _BIT_ALLOCATION_H_

559
src/lib/bitStreamWriter.cpp Normal file
View File

@ -0,0 +1,559 @@
/* bitStreamWriter.cpp - source file for class with basic bit-stream writing capability
* written by C. R. Helmrich, last modified in 2019 - 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 "exhaleLibPch.h"
#include "bitStreamWriter.h"
// private helper functions
void BitStreamWriter::writeByteAlignment () // write '0' bits until stream is byte-aligned
{
if (m_auBitStream.heldBitCount > 0)
{
m_auBitStream.stream.push_back (m_auBitStream.heldBitChunk);
m_auBitStream.heldBitChunk = 0;
m_auBitStream.heldBitCount = 0;
}
}
unsigned BitStreamWriter::writeChannelWiseIcsInfo (const IcsInfo& icsInfo) // ics_info()
{
#if RESTRICT_TO_AAC
m_auBitStream.write ((unsigned) icsInfo.windowSequence, 2);
#else
m_auBitStream.write (unsigned (icsInfo.windowSequence == STOP_START ? LONG_START : icsInfo.windowSequence), 2);
#endif
m_auBitStream.write ((unsigned) icsInfo.windowShape, 1);
if (icsInfo.windowSequence == EIGHT_SHORT)
{
m_auBitStream.write (icsInfo.maxSfb, 4);
m_auBitStream.write (icsInfo.windowGrouping, 7); // scale_factor_grouping
return 14;
}
m_auBitStream.write (icsInfo.maxSfb, 6);
return 9;
}
unsigned BitStreamWriter::writeChannelWiseTnsData (const TnsData& tnsData, const bool eightShorts)
{
const unsigned numWindows = (eightShorts ? 8 : 1);
const unsigned offsetBits = (eightShorts ? 1 : 2);
unsigned bitCount = 0, i;
for (unsigned w = 0; w < numWindows; w++)
{
bitCount += offsetBits;
if (w != tnsData.filteredWindow)
{
m_auBitStream.write (0/*n_filt[w] = 0*/, offsetBits);
}
else
{
m_auBitStream.write (tnsData.numFilters, offsetBits);
if (tnsData.numFilters > 0)
{
m_auBitStream.write (tnsData.coeffResLow ? 0 : 1, 1); // coef_res[w]
bitCount++;
for (unsigned f = 0; f < tnsData.numFilters; f++)
{
const unsigned order = tnsData.filterOrder[f];
m_auBitStream.write (tnsData.filterLength[f], 2 + offsetBits * 2);
m_auBitStream.write (order, 2 + offsetBits);
bitCount += 4 + offsetBits * 3;
if (order > 0)
{
const int8_t* coeff = tnsData.coeff[f];
unsigned coefBits = (tnsData.coeffResLow ? 3 : 4);
char coefMaxValue = (tnsData.coeffResLow ? 2 : 4);
bool dontCompress = false;
m_auBitStream.write (tnsData.filterDownward[f] ? 1 : 0, 1);
for (i = 0; i < order; i++) // get coef_compress, then write coef
{
dontCompress |= ((coeff[i] < -coefMaxValue) || (coeff[i] >= coefMaxValue));
}
m_auBitStream.write (dontCompress ? 0 : 1, 1);
coefMaxValue <<= 1;
if (dontCompress) coefMaxValue <<= 1; else coefBits--;
for (i = 0; i < order; i++)
{
m_auBitStream.write (unsigned (coeff[i] < 0 ? coefMaxValue + coeff[i] : coeff[i]), coefBits);
}
bitCount += 2 + order * coefBits;
}
}
} // if (n_filt[w])
}
} // for w
return bitCount;
}
unsigned BitStreamWriter::writeFDChannelStream (const CoreCoderData& elData, EntropyCoder& entrCoder, const unsigned ch,
const int32_t* const mdctSignal, const uint8_t* const mdctQuantMag,
#if !RESTRICT_TO_AAC
const bool timeWarping, const bool noiseFilling,
#endif
const bool indepFlag /*= false*/)
{
const IcsInfo& icsInfo = elData.icsInfoCurr[ch];
const TnsData& tnsData = elData.tnsData[ch];
const SfbGroupData& grp = elData.groupingData[ch];
const unsigned maxSfb = grp.sfbsPerGroup;
const bool eightShorts = icsInfo.windowSequence == EIGHT_SHORT;
uint8_t* const sf = (uint8_t* const) grp.scaleFactors;
uint8_t sfIdxPred = CLIP_UCHAR (sf[0] > SCHAR_MAX ? 0 : sf[0] + (eightShorts ? 68 : 80));
unsigned bitCount = 8, g, b, i;
m_auBitStream.write (sfIdxPred, 8); // adjusted global_gain
#if !RESTRICT_TO_AAC
if (noiseFilling)
{
m_auBitStream.write (elData.specFillData[ch], 8); // noise level | offset
bitCount += 8;
}
#endif
if (!elData.commonWindow)
{
bitCount += writeChannelWiseIcsInfo (icsInfo); // ics_info
}
#if !RESTRICT_TO_AAC
if (timeWarping) // && (!common_tw)
{
m_auBitStream.write (0, 1); // enforce tw_data_present = 0
bitCount++;
}
#endif
sfIdxPred = sf[0]; // scale factors
for (g = 0; g < grp.numWindowGroups; g++)
{
uint8_t* const gSf = &sf[m_numSwbShort * g];
for (b = 0; b < maxSfb; b++)
{
uint8_t sfIdx = gSf[b];
if ((g + 1 < grp.numWindowGroups) && (b + 1 == maxSfb) && ((unsigned) sfIdx + INDEX_OFFSET < sf[m_numSwbShort * (g + 1)]))
{
// ugly, avoidable if each gr. had its own global_gain
gSf[b] = sfIdx = sf[m_numSwbShort * (g + 1)] - INDEX_OFFSET;
}
if ((g > 0) || (b > 0))
{
int sfIdxDpcm = (int) sfIdx - sfIdxPred;
unsigned sfBits;
if (sfIdxDpcm > INDEX_OFFSET) // just as sanity checks
{
sfIdxDpcm = INDEX_OFFSET;
sfIdxPred += INDEX_OFFSET;
}
else if (sfIdxDpcm < -INDEX_OFFSET) // highly unlikely
{
sfIdxDpcm = -INDEX_OFFSET;
sfIdxPred -= INDEX_OFFSET;
}
else // scale factor range OK
{
sfIdxPred = sfIdx;
}
sfBits = entrCoder.indexGetBitCount (sfIdxDpcm);
m_auBitStream.write (entrCoder.indexGetHuffCode (sfIdxDpcm), sfBits);
bitCount += sfBits;
}
}
} // for g
if (!elData.commonTnsData && (tnsData.numFilters > 0))
{
bitCount += writeChannelWiseTnsData (tnsData, eightShorts);
}
bitCount += (indepFlag ? 1 : 2); // arith_reset_flag, fac_data_present bits
if (maxSfb == 0) // zeroed spectrum
{
entrCoder.initWindowCoding (!eightShorts /*reset*/, eightShorts);
if (!indepFlag) m_auBitStream.write (1, 1); // force reset
}
else // not zeroed, nasty since SFB ungrouping may be needed
{
const uint16_t* grpOff = grp.sfbOffsets;
uint8_t grpLen = grp.windowGroupLength[0];
uint8_t grpWin = 0;
uint8_t swbSize[MAX_NUM_SWB_SHORT];
const uint8_t* winMag = (grpLen > 1 ? m_uCharBuffer : mdctQuantMag);
const uint16_t lg = (grpLen > 1 ? grpOff[maxSfb] / grpLen : grpOff[maxSfb]);
if (eightShorts || (grpLen > 1)) // ungroup the SFB widths
{
for (b = 0, i = oneTwentyEightOver[grpLen]; b < maxSfb; b++)
{
swbSize[b] = ((grpOff[b+1] - grpOff[b]) * i) >> 7; // sfbWidth/grpLen
}
}
g = 0;
for (int w = 0; w < (eightShorts ? 8 : 1); w++, grpWin++) // window loop
{
if (grpWin >= grpLen) // next g
{
grpOff += m_numSwbShort;
grpLen = grp.windowGroupLength[++g];
grpWin = 0;
winMag = (grpLen > 1 ? m_uCharBuffer : &mdctQuantMag[grpOff[0]]);
}
if (eightShorts && (grpLen > 1))
{
for (b = i = 0; b < maxSfb; b++) // ungroup magnitudes
{
memcpy (&m_uCharBuffer[i], &mdctQuantMag[grpOff[b] + grpWin * swbSize[b]], swbSize[b] * sizeof (uint8_t));
i += swbSize[b];
}
}
entrCoder.initWindowCoding (indepFlag && (w == 0), eightShorts);
if (!indepFlag && (w == 0)) // optimize arith_reset_flag
{
if ((b = entrCoder.arithGetResetBit (winMag, 0, lg)) != 0)
{
entrCoder.arithResetMemory ();
entrCoder.arithSetCodState (USHRT_MAX << 16);
entrCoder.arithSetCtxState (0);
}
m_auBitStream.write (b, 1); // write adapted reset bit
}
bitCount += entrCoder.arithCodeSigMagn (winMag, 0, lg, true, &m_auBitStream);
if (eightShorts && (grpLen > 1))
{
for (b = i = 0; b < maxSfb; b++) // ungroup coef signs
{
const int32_t* const swbSig = &mdctSignal[grpOff[b] + grpWin * swbSize[b]];
for (unsigned j = 0; j < swbSize[b]; j++, i++)
{
if (winMag[i] != 0)
{
m_auBitStream.write (swbSig[j] < 0 ? 0 : 1, 1); // - = 0, + = 1
bitCount++;
}
}
}
}
else // not grouped long window
{
const int32_t* const winSig = &mdctSignal[grpOff[0]];
for (i = 0; i < lg; i++)
{
if (winMag[i] != 0)
{
m_auBitStream.write (winSig[i] < 0 ? 0 : 1, 1); // -1 = 0, +1 = 1
bitCount++;
}
}
}
} // for w
} // if (maxSfb == 0)
m_auBitStream.write (0, 1); // fac_data_present, no fac_data
return bitCount;
}
unsigned BitStreamWriter::writeStereoCoreToolInfo (const CoreCoderData& elData,
#if !RESTRICT_TO_AAC
const bool timeWarping,
#endif
const bool indepFlag /*= false*/)
{
const IcsInfo& icsInfo0 = elData.icsInfoCurr[0];
const IcsInfo& icsInfo1 = elData.icsInfoCurr[1];
const TnsData& tnsData0 = elData.tnsData[0];
const TnsData& tnsData1 = elData.tnsData[1];
unsigned bitCount = 2, g, b;
m_auBitStream.write (elData.tnsActive ? 1 : 0, 1); // tns_active
m_auBitStream.write (elData.commonWindow ? 1 : 0, 1);
if (elData.commonWindow)
{
const unsigned maxSfbSte = __max (icsInfo0.maxSfb, icsInfo1.maxSfb);
const unsigned sfb1Bits = icsInfo1.windowSequence == EIGHT_SHORT ? 4 : 6;
bitCount += writeChannelWiseIcsInfo (icsInfo0); // ics_info()
m_auBitStream.write (elData.commonMaxSfb ? 1 : 0, 1);
if (!elData.commonMaxSfb)
{
m_auBitStream.write (icsInfo1.maxSfb, sfb1Bits); // max_sfb1
bitCount += sfb1Bits;
}
m_auBitStream.write (__min (3, elData.stereoMode), 2); // ms_mask_present
bitCount += 3;
if (elData.stereoMode == 1) // write SFB-wise ms_used[][] flag
{
for (g = 0; g < elData.groupingData[0].numWindowGroups; g++)
{
const char* const gMsUsed = &elData.stereoData[m_numSwbShort * g];
for (b = 0; b < maxSfbSte; b++)
{
m_auBitStream.write (gMsUsed[b] > 0 ? 1 : 0, 1);
}
}
bitCount += maxSfbSte * g;
}
#if !RESTRICT_TO_AAC
else if (elData.stereoMode >= 3) // SFB-wise cplx_pred_data()
{
m_auBitStream.write (elData.stereoMode - 3, 1); // _pred_all
if (elData.stereoMode == 3)
{
for (g = 0; g < elData.groupingData[0].numWindowGroups; g++)
{
const char* const gCplxPredUsed = &elData.stereoData[m_numSwbShort * g];
for (b = 0; b < maxSfbSte; b += SFB_PER_PRED_BAND)
{
m_auBitStream.write (gCplxPredUsed[b] > 0 ? 1 : 0, 1);
}
}
bitCount += ((maxSfbSte + 1) / SFB_PER_PRED_BAND) * g;
}
// pred_dir and complex_coef. TODO: rest of cplx_pred_data()
m_auBitStream.write (elData.stereoConfig & 3, 2);
bitCount += 3;
}
#endif
} // common_window
#if !RESTRICT_TO_AAC
if (timeWarping)
{
m_auBitStream.write (0, 1); // common_tw not needed in xHE-AAC
bitCount++;
} // tw_mdct
#endif
if (elData.tnsActive)
{
if (elData.commonWindow)
{
m_auBitStream.write (elData.commonTnsData ? 1 : 0, 1);
bitCount++;
}
m_auBitStream.write (elData.tnsOnLeftRight ? 1 : 0, 1);
bitCount++;
if (elData.commonTnsData)
{
bitCount += writeChannelWiseTnsData (tnsData0, icsInfo0.windowSequence == EIGHT_SHORT);
}
else // tns_present_both and tns_data_present[1]
{
const bool tnsPresentBoth = (tnsData0.numFilters > 0) && (tnsData1.numFilters > 0);
m_auBitStream.write (tnsPresentBoth ? 1 : 0, 1);
bitCount++;
if (!tnsPresentBoth)
{
m_auBitStream.write (tnsData1.numFilters > 0 ? 1 : 0, 1);
bitCount++;
}
}
} // tns_active
return bitCount;
}
// public functions
unsigned BitStreamWriter::createAudioConfig (const char samplingFrequencyIndex, const bool shortFrameLength,
const uint8_t chConfigurationIndex, const uint8_t numElements,
const ELEM_TYPE* const elementType, const bool configExtensionPresent,
#if !RESTRICT_TO_AAC
const bool* const tw_mdct /*N/A*/, const bool* const noiseFilling,
#endif
unsigned char* const audioConfig)
{
unsigned bitCount = 37;
if ((elementType == nullptr) || (audioConfig == nullptr) || (chConfigurationIndex >= USAC_MAX_NUM_ELCONFIGS) ||
#if !RESTRICT_TO_AAC
(noiseFilling == nullptr) || (tw_mdct == nullptr) ||
#endif
(numElements == 0) || (numElements > USAC_MAX_NUM_ELEMENTS) || (samplingFrequencyIndex < 0) || (samplingFrequencyIndex >= 0x1F))
{
return 0; // invalid arguments error
}
m_auBitStream.reset ();
// --- AudioSpecificConfig(): https://wiki.multimedia.cx/index.php/MPEG-4_Audio/
m_auBitStream.write (0x7CA, 11); // audio object type (AOT) 32 (esc) + 10 = 42
if (samplingFrequencyIndex < AAC_NUM_SAMPLE_RATES)
{
m_auBitStream.write (samplingFrequencyIndex, 4);
}
else
{
m_auBitStream.write (0xF, 4); // esc
m_auBitStream.write (toSamplingRate (samplingFrequencyIndex), 24);
bitCount += 24;
}
// for multichannel audio, refer to channel mapping of AotSpecificConfig below
m_auBitStream.write (chConfigurationIndex > 2 ? 0 : chConfigurationIndex, 4);
// --- AotSpecificConfig(): UsacConfig()
m_auBitStream.write (samplingFrequencyIndex, 5); // usacSamplingFrequencyIndex
m_auBitStream.write (shortFrameLength ? 0 : 1, 3); // coreSbrFrameLengthIndex
m_auBitStream.write (chConfigurationIndex, 5); // channelConfigurationIndex
m_auBitStream.write (numElements - 1, 4); // numElements in UsacDecoderConfig
for (unsigned el = 0; el < numElements; el++) // el element loop
{
m_auBitStream.write ((unsigned) elementType[el], 2); // usacElementType[el]
bitCount += 2;
if (elementType[el] < ID_USAC_LFE) // SCE, CPE: UsacCoreConfig
{
#if RESTRICT_TO_AAC
m_auBitStream.write (0, 2); // time warping and noise filling not allowed
#else
m_auBitStream.write ((tw_mdct[el] ? 2 : 0) | (noiseFilling[el] ? 1 : 0), 2);
#endif
bitCount += 2;
}
} // for el
m_auBitStream.write (configExtensionPresent ? 1 : 0, 1); // usacConfigExten...
if (configExtensionPresent) // 23003-4: loudnessInfo
{
m_auBitStream.write (0, 2); // numConfigExtensions
m_auBitStream.write (ID_EXT_LOUDNESS_INFO, 4);
m_auBitStream.write (8, 4); // usacConfigExtLength
m_auBitStream.write (1, 12);// loudnessInfoCount=1
m_auBitStream.write (1, 14); // peakLevelPresent=1
m_auBitStream.write (0, 12); // bsSamplePeakLevel
m_auBitStream.write (1, 5); // measurementCount=1
m_auBitStream.write (1, 4); // methodDefinition=1
m_auBitStream.write (0, 8); // methodValue storage
m_auBitStream.write (0, 4); // measurementSystem=0
m_auBitStream.write (3, 2); // reliability=3, good
m_auBitStream.write (0, 1); // ...SetExtPresent=0
bitCount += 72;
}
bitCount += (8 - m_auBitStream.heldBitCount) & 7;
writeByteAlignment (); // flush bytes
memcpy (audioConfig, &m_auBitStream.stream.front (), __min (16, bitCount >> 3));
return (bitCount >> 3); // byte count
}
unsigned BitStreamWriter::createAudioFrame (CoreCoderData** const elementData, EntropyCoder* const entropyCoder,
int32_t** const mdctSignals, uint8_t** const mdctQuantMag,
const bool usacIndependencyFlag, const uint8_t numElements,
const uint8_t numSwbShort, uint8_t* const tempBuffer,
#if !RESTRICT_TO_AAC
const bool* const tw_mdct /*N/A*/, const bool* const noiseFilling,
#endif
unsigned char* const accessUnit, const unsigned nSamplesInFrame /*= 1024*/)
{
unsigned bitCount = 1, ci = 0;
if ((elementData == nullptr) || (entropyCoder == nullptr) || (tempBuffer == nullptr) ||
(mdctSignals == nullptr) || (mdctQuantMag == nullptr) || (accessUnit == nullptr) || (nSamplesInFrame > 2048) ||
#if !RESTRICT_TO_AAC
(noiseFilling == nullptr) || (tw_mdct == nullptr) ||
#endif
(numElements == 0) || (numElements > USAC_MAX_NUM_ELEMENTS) || (numSwbShort < MIN_NUM_SWB_SHORT) || (numSwbShort > MAX_NUM_SWB_SHORT))
{
return 0; // invalid arguments error
}
m_auBitStream.reset ();
m_frameLength = nSamplesInFrame;
m_numSwbShort = numSwbShort;
m_uCharBuffer = tempBuffer;
m_auBitStream.write (usacIndependencyFlag ? 1 : 0, 1);
for (unsigned el = 0; el < numElements; el++) // el element loop
{
const CoreCoderData* const elData = elementData[el];
if (elData == nullptr)
{
return 0; // internal memory error
}
switch (elData->elementType) // write out UsacCoreCoderData()
{
case ID_USAC_SCE: // UsacSingleChannelElement()
{
m_auBitStream.write (CORE_MODE_FD, 1);
m_auBitStream.write (elData->tnsActive ? 1 : 0, 1); // tns_data_present
bitCount += 2;
bitCount += writeFDChannelStream (*elData, entropyCoder[ci], 0,
mdctSignals[ci], mdctQuantMag[ci],
#if !RESTRICT_TO_AAC
tw_mdct[el], noiseFilling[el],
#endif
usacIndependencyFlag);
ci++;
break;
}
case ID_USAC_CPE: // UsacChannelPairElement()
{
m_auBitStream.write (CORE_MODE_FD, 1); // L
m_auBitStream.write (CORE_MODE_FD, 1); // R
bitCount += 2;
bitCount += writeStereoCoreToolInfo (*elData,
#if !RESTRICT_TO_AAC
tw_mdct[el],
#endif
usacIndependencyFlag);
bitCount += writeFDChannelStream (*elData, entropyCoder[ci], 0, // L
mdctSignals[ci], mdctQuantMag[ci],
#if !RESTRICT_TO_AAC
tw_mdct[el], noiseFilling[el],
#endif
usacIndependencyFlag);
ci++;
bitCount += writeFDChannelStream (*elData, entropyCoder[ci], 1, // R
mdctSignals[ci], mdctQuantMag[ci],
#if !RESTRICT_TO_AAC
tw_mdct[el], noiseFilling[el],
#endif
usacIndependencyFlag);
ci++;
break;
}
case ID_USAC_LFE: // UsacLfeElement()
{
bitCount += writeFDChannelStream (*elData, entropyCoder[ci], 0,
mdctSignals[ci], mdctQuantMag[ci],
#if !RESTRICT_TO_AAC
false, false,
#endif
usacIndependencyFlag);
ci++;
break;
}
default: break;
}
} // for el
bitCount += (8 - m_auBitStream.heldBitCount) & 7;
writeByteAlignment (); // flush bytes
memcpy (accessUnit, &m_auBitStream.stream.front (), __min (768 * ci, bitCount >> 3));
return (bitCount >> 3); // byte count
}

74
src/lib/bitStreamWriter.h Normal file
View File

@ -0,0 +1,74 @@
/* bitStreamWriter.h - header file for class with basic bit-stream writing capability
* written by C. R. Helmrich, last modified in 2019 - 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.
*/
#ifndef _BIT_STREAM_WRITER_H_
#define _BIT_STREAM_WRITER_H_
#include "exhaleLibPch.h"
#include "entropyCoding.h"
// constants, experimental macros
#define CORE_MODE_FD 0
#define ID_EXT_LOUDNESS_INFO 2
#define ID_EXT_ELE_FILL 0
#define SFB_PER_PRED_BAND 2
// output bit-stream writer class
class BitStreamWriter
{
private:
// member variables
OutputStream m_auBitStream; // access unit bit-stream to write
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
// helper functions
void writeByteAlignment (); // write 0s for byte alignment
unsigned writeChannelWiseIcsInfo (const IcsInfo& icsInfo); // ics_info()
unsigned writeChannelWiseTnsData (const TnsData& tnsData, const bool eightShorts);
unsigned writeFDChannelStream (const CoreCoderData& elData, EntropyCoder& entrCoder, const unsigned ch,
const int32_t* const mdctSignal, const uint8_t* const mdctQuantMag,
#if !RESTRICT_TO_AAC
const bool timeWarping, const bool noiseFilling,
#endif
const bool indepFlag = false);
unsigned writeStereoCoreToolInfo (const CoreCoderData& elData,
#if !RESTRICT_TO_AAC
const bool timeWarping,
#endif
const bool indepFlag = false);
public:
// constructor
BitStreamWriter () { m_auBitStream.reset (); m_frameLength = 0; m_numSwbShort = 0; m_uCharBuffer = nullptr; }
// destructor
~BitStreamWriter() { m_auBitStream.reset (); }
// public functions
unsigned createAudioConfig (const char samplingFrequencyIndex, const bool shortFrameLength,
const uint8_t chConfigurationIndex, const uint8_t numElements,
const ELEM_TYPE* const elementType, const bool configExtensionPresent,
#if !RESTRICT_TO_AAC
const bool* const tw_mdct /*N/A*/, const bool* const noiseFilling,
#endif
unsigned char* const audioConfig);
unsigned createAudioFrame (CoreCoderData** const elementData, EntropyCoder* const entropyCoder,
int32_t** const mdctSignals, uint8_t** const mdctQuantMag,
const bool usacIndependencyFlag, const uint8_t numElements,
const uint8_t numSwbShort, uint8_t* const tempBuffer,
#if !RESTRICT_TO_AAC
const bool* const tw_mdct /*N/A*/, const bool* const noiseFilling,
#endif
unsigned char* const audioFrame, const unsigned nSamplesInFrame = 1024);
}; // BitStreamWriter
#endif // _BIT_STREAM_WRITER_H_

605
src/lib/entropyCoding.cpp Normal file
View File

@ -0,0 +1,605 @@
/* entropyCoding.cpp - source file for class with lossless entropy coding capability
* written by C. R. Helmrich, last modified in 2019 - 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 "exhaleLibPch.h"
#include "entropyCoding.h"
// ISO/IEC 14496-3, Annex 4.A.1
static const uint32_t huffScf[INDEX_SIZE] = { // upper 3 bytes: value, lower byte: length
0x03FFE812, 0x03FFE612, 0x03FFE712, 0x03FFE512, 0x07FFF513, 0x07FFF113, 0x07FFED13, 0x07FFF613,
0x07FFEE13, 0x07FFEF13, 0x07FFF013, 0x07FFFC13, 0x07FFFD13, 0x07FFFF13, 0x07FFFE13, 0x07FFF713,
0x07FFF813, 0x07FFFB13, 0x07FFF913, 0x03FFE412, 0x07FFFA13, 0x03FFE312, 0x01FFEF11, 0x01FFF011,
0x00FFF510, 0x01FFEE11, 0x00FFF210, 0x00FFF310, 0x00FFF410, 0x00FFF110, 0x007FF60F, 0x007FF70F,
0x003FF90E, 0x003FF50E, 0x003FF70E, 0x003FF30E, 0x003FF60E, 0x003FF20E, 0x001FF70D, 0x001FF50D,
0x000FF90C, 0x000FF70C, 0x000FF60C, 0x0007F90B, 0x000FF40C, 0x0007F80B, 0x0003F90A, 0x0003F70A,
0x0003F50A, 0x0001F809, 0x0001F709, 0x0000FA08, 0x0000F808, 0x0000F608, 0x00007907, 0x00003A06,
0x00003806, 0x00001A05, 0x00000B04, 0x00000403, 0x00000001, 0x00000A04, 0x00000C04, 0x00001B05, 0x00003906,
0x00003B06, 0x00007807, 0x00007A07, 0x0000F708, 0x0000F908, 0x0001F609, 0x0001F909, 0x0003F40A,
0x0003F60A, 0x0003F80A, 0x0007F50B, 0x0007F40B, 0x0007F60B, 0x0007F70B, 0x000FF50C, 0x000FF80C,
0x001FF40D, 0x001FF60D, 0x001FF80D, 0x003FF80E, 0x003FF40E, 0x00FFF010, 0x007FF40F, 0x00FFF610,
0x007FF50F, 0x03FFE212, 0x07FFD913, 0x07FFDA13, 0x07FFDB13, 0x07FFDC13, 0x07FFDD13, 0x07FFDE13,
0x07FFD813, 0x07FFD213, 0x07FFD313, 0x07FFD413, 0x07FFD513, 0x07FFD613, 0x07FFF213, 0x07FFDF13,
0x07FFE713, 0x07FFE813, 0x07FFE913, 0x07FFEA13, 0x07FFEB13, 0x07FFE613, 0x07FFE013, 0x07FFE113,
0x07FFE213, 0x07FFE313, 0x07FFE413, 0x07FFE513, 0x07FFD713, 0x07FFEC13, 0x07FFF413, 0x07FFF313
};
// ISO/IEC 23003-3, Annex C.1
static const uint8_t arithLookupM[ARITH_SIZE] = { // arith_lookup_m
0x01, 0x34, 0x0D, 0x13, 0x12, 0x25, 0x00, 0x3A, 0x05, 0x00, 0x21, 0x13, 0x1F, 0x1A, 0x1D, 0x36,
0x24, 0x2B, 0x1B, 0x33, 0x37, 0x29, 0x1D, 0x33, 0x37, 0x33, 0x37, 0x33, 0x37, 0x33, 0x2C, 0x00,
0x21, 0x13, 0x25, 0x2A, 0x00, 0x21, 0x24, 0x12, 0x2C, 0x1E, 0x37, 0x24, 0x1F, 0x35, 0x37, 0x24,
0x35, 0x37, 0x35, 0x37, 0x38, 0x2D, 0x21, 0x29, 0x1E, 0x21, 0x13, 0x2D, 0x36, 0x38, 0x29, 0x36,
0x37, 0x24, 0x36, 0x38, 0x37, 0x38, 0x00, 0x20, 0x23, 0x20, 0x23, 0x36, 0x38, 0x24, 0x3B, 0x24,
0x26, 0x29, 0x1F, 0x30, 0x2D, 0x0D, 0x12, 0x3F, 0x2D, 0x21, 0x1C, 0x2A, 0x00, 0x21, 0x12, 0x1E,
0x36, 0x38, 0x36, 0x37, 0x3F, 0x1E, 0x0D, 0x1F, 0x2A, 0x1E, 0x21, 0x24, 0x12, 0x2A, 0x3C, 0x21,
0x24, 0x1F, 0x3C, 0x21, 0x29, 0x36, 0x38, 0x36, 0x37, 0x38, 0x21, 0x1E, 0x00, 0x3B, 0x25, 0x1E,
0x20, 0x10, 0x1F, 0x3C, 0x20, 0x23, 0x29, 0x08, 0x23, 0x12, 0x08, 0x23, 0x21, 0x38, 0x00, 0x20,
0x13, 0x20, 0x3B, 0x1C, 0x20, 0x3B, 0x29, 0x20, 0x23, 0x24, 0x21, 0x24, 0x21, 0x24, 0x3B, 0x13,
0x23, 0x26, 0x23, 0x13, 0x21, 0x24, 0x26, 0x29, 0x12, 0x22, 0x2B, 0x02, 0x1E, 0x0D, 0x1F, 0x2D,
0x00, 0x0D, 0x12, 0x00, 0x3C, 0x21, 0x29, 0x3C, 0x21, 0x2A, 0x3C, 0x3B, 0x22, 0x1E, 0x20, 0x10,
0x1F, 0x3C, 0x0D, 0x29, 0x3C, 0x21, 0x24, 0x08, 0x23, 0x20, 0x38, 0x39, 0x3C, 0x20, 0x13, 0x3C,
0x00, 0x0D, 0x13, 0x1F, 0x3C, 0x09, 0x26, 0x1F, 0x08, 0x09, 0x26, 0x12, 0x08, 0x23, 0x29, 0x20,
0x23, 0x21, 0x24, 0x20, 0x13, 0x20, 0x3B, 0x16, 0x20, 0x3B, 0x29, 0x20, 0x3B, 0x29, 0x20, 0x3B,
0x13, 0x21, 0x24, 0x29, 0x0B, 0x13, 0x09, 0x3B, 0x13, 0x09, 0x3B, 0x13, 0x21, 0x3B, 0x13, 0x0D,
0x26, 0x29, 0x26, 0x29, 0x3D, 0x12, 0x22, 0x28, 0x2E, 0x04, 0x08, 0x13, 0x3C, 0x3B, 0x3C, 0x20,
0x10, 0x3C, 0x21, 0x07, 0x08, 0x10, 0x00, 0x08, 0x0D, 0x29, 0x08, 0x0D, 0x29, 0x08, 0x09, 0x13,
0x20, 0x23, 0x39, 0x08, 0x09, 0x13, 0x08, 0x09, 0x16, 0x08, 0x09, 0x10, 0x12, 0x20, 0x3B, 0x3D,
0x09, 0x26, 0x20, 0x3B, 0x24, 0x39, 0x09, 0x26, 0x20, 0x0D, 0x13, 0x00, 0x09, 0x13, 0x20, 0x0D,
0x26, 0x12, 0x20, 0x3B, 0x13, 0x21, 0x26, 0x0B, 0x12, 0x09, 0x3B, 0x16, 0x09, 0x3B, 0x3D, 0x09,
0x26, 0x0D, 0x13, 0x26, 0x3D, 0x1C, 0x12, 0x1F, 0x28, 0x2E, 0x07, 0x0B, 0x08, 0x09, 0x00, 0x39,
0x0B, 0x08, 0x26, 0x08, 0x09, 0x13, 0x20, 0x0B, 0x39, 0x10, 0x39, 0x0D, 0x13, 0x20, 0x10, 0x12,
0x09, 0x13, 0x20, 0x3B, 0x13, 0x09, 0x26, 0x0B, 0x09, 0x3B, 0x1C, 0x09, 0x3B, 0x13, 0x20, 0x3B,
0x13, 0x09, 0x26, 0x0B, 0x16, 0x0D, 0x13, 0x09, 0x13, 0x09, 0x13, 0x26, 0x3D, 0x1C, 0x1F, 0x28,
0x2E, 0x07, 0x10, 0x39, 0x0B, 0x39, 0x39, 0x13, 0x39, 0x0B, 0x39, 0x0B, 0x39, 0x26, 0x39, 0x10,
0x20, 0x3B, 0x16, 0x20, 0x10, 0x09, 0x26, 0x0B, 0x13, 0x09, 0x13, 0x26, 0x1C, 0x0B, 0x3D, 0x1C,
0x1F, 0x28, 0x2B, 0x07, 0x0C, 0x39, 0x0B, 0x39, 0x0B, 0x0C, 0x0B, 0x26, 0x0B, 0x26, 0x3D, 0x0D,
0x1C, 0x14, 0x28, 0x2B, 0x39, 0x0B, 0x0C, 0x0E, 0x3D, 0x1C, 0x0D, 0x12, 0x22, 0x2B, 0x07, 0x0C,
0x0E, 0x3D, 0x1C, 0x10, 0x1F, 0x2B, 0x0C, 0x0E, 0x19, 0x14, 0x10, 0x1F, 0x28, 0x0C, 0x0E, 0x19,
0x14, 0x26, 0x22, 0x2B, 0x0C, 0x0E, 0x19, 0x14, 0x26, 0x28, 0x0E, 0x19, 0x14, 0x26, 0x28, 0x0E,
0x19, 0x14, 0x28, 0x0E, 0x19, 0x14, 0x22, 0x28, 0x2B, 0x0E, 0x14, 0x2B, 0x31, 0x00, 0x3A, 0x3A,
0x05, 0x05, 0x1B, 0x1D, 0x33, 0x06, 0x35, 0x35, 0x20, 0x21, 0x37, 0x21, 0x24, 0x05, 0x1B, 0x2C,
0x2C, 0x2C, 0x06, 0x34, 0x1E, 0x34, 0x00, 0x08, 0x36, 0x09, 0x21, 0x26, 0x1C, 0x2C, 0x00, 0x02,
0x02, 0x02, 0x3F, 0x04, 0x04, 0x04, 0x34, 0x39, 0x20, 0x0A, 0x0C, 0x39, 0x0B, 0x0F, 0x07, 0x07,
0x07, 0x07, 0x34, 0x39, 0x39, 0x0A, 0x0C, 0x39, 0x0C, 0x0F, 0x07, 0x07, 0x07, 0x00, 0x39, 0x39,
0x0C, 0x0F, 0x07, 0x07, 0x39, 0x0C, 0x0F, 0x07, 0x39, 0x0C, 0x0F, 0x39, 0x39, 0x0C, 0x0F, 0x39,
0x0C, 0x39, 0x0C, 0x0F, 0x00, 0x11, 0x27, 0x17, 0x2F, 0x27, 0x00, 0x27, 0x17, 0x00, 0x11, 0x17,
0x00, 0x11, 0x17, 0x11, 0x00, 0x27, 0x15, 0x11, 0x17, 0x01, 0x15, 0x11, 0x15, 0x11, 0x15, 0x15,
0x17, 0x00, 0x27, 0x01, 0x27, 0x27, 0x15, 0x00, 0x27, 0x11, 0x27, 0x15, 0x15, 0x15, 0x27, 0x15,
0x15, 0x15, 0x15, 0x17, 0x2F, 0x11, 0x17, 0x27, 0x27, 0x27, 0x11, 0x27, 0x15, 0x27, 0x27, 0x15,
0x15, 0x27, 0x17, 0x2F, 0x27, 0x17, 0x2F, 0x27, 0x17, 0x2F, 0x27, 0x17, 0x2F, 0x27, 0x17, 0x2F,
0x27, 0x17, 0x2F, 0x27, 0x17, 0x2F, 0x27, 0x17, 0x2F, 0x27, 0x17, 0x2F, 0x27, 0x17, 0x2F, 0x27,
0x17, 0x2F, 0x27, 0x17, 0x2F, 0x27, 0x17, 0x2F, 0x17, 0x2F, 0x2B, 0x00, 0x27, 0x00, 0x00, 0x11,
0x15, 0x00, 0x11, 0x11, 0x27, 0x27, 0x15, 0x17, 0x15, 0x17, 0x15, 0x17, 0x27, 0x17, 0x27, 0x17,
0x27, 0x17, 0x27, 0x17, 0x27, 0x17, 0x27, 0x17, 0x27, 0x17, 0x27, 0x17, 0x27, 0x17, 0x27, 0x17,
0x27, 0x15, 0x27, 0x27, 0x15, 0x27
};
// ISO/IEC 23003-3, Annex C.2
static const uint32_t arithHashM[ARITH_SIZE] = { // arith_hash_m
0x00000104, 0x0000030A, 0x00000510, 0x00000716, 0x00000A1F, 0x00000F2E, 0x00011100, 0x00111103,
0x00111306, 0x00111436, 0x00111623, 0x00111929, 0x00111F2E, 0x0011221B, 0x00112435, 0x00112621,
0x00112D12, 0x00113130, 0x0011331D, 0x00113535, 0x00113938, 0x0011411B, 0x00114433, 0x00114635,
0x00114F29, 0x00116635, 0x00116F24, 0x00117433, 0x0011FF0F, 0x00121102, 0x0012132D, 0x00121436,
0x00121623, 0x00121912, 0x0012213F, 0x0012232D, 0x00122436, 0x00122638, 0x00122A29, 0x00122F2B,
0x0012322D, 0x00123436, 0x00123738, 0x00123B29, 0x0012411D, 0x00124536, 0x00124938, 0x00124F12,
0x00125535, 0x00125F29, 0x00126535, 0x0012B837, 0x0013112A, 0x0013131E, 0x0013163B, 0x0013212D,
0x0013233C, 0x00132623, 0x00132F2E, 0x0013321E, 0x00133521, 0x00133824, 0x0013411E, 0x00134336,
0x00134838, 0x00135135, 0x00135537, 0x00135F12, 0x00137637, 0x0013FF29, 0x00140024, 0x00142321,
0x00143136, 0x00143321, 0x00143F25, 0x00144321, 0x00148638, 0x0014FF29, 0x00154323, 0x0015FF12,
0x0016F20C, 0x0018A529, 0x00210031, 0x0021122C, 0x00211408, 0x00211713, 0x00211F2E, 0x0021222A,
0x00212408, 0x00212710, 0x00212F2E, 0x0021331E, 0x00213436, 0x00213824, 0x0021412D, 0x0021431E,
0x00214536, 0x00214F1F, 0x00216637, 0x00220004, 0x0022122A, 0x00221420, 0x00221829, 0x00221F2E,
0x0022222D, 0x00222408, 0x00222623, 0x00222929, 0x00222F2B, 0x0022321E, 0x00223408, 0x00223724,
0x00223A29, 0x0022411E, 0x00224436, 0x00224823, 0x00225134, 0x00225621, 0x00225F12, 0x00226336,
0x00227637, 0x0022FF29, 0x0023112D, 0x0023133C, 0x00231420, 0x00231916, 0x0023212D, 0x0023233C,
0x00232509, 0x00232929, 0x0023312D, 0x00233308, 0x00233509, 0x00233724, 0x0023413C, 0x00234421,
0x00234A13, 0x0023513C, 0x00235421, 0x00235F1F, 0x00236421, 0x0023FF29, 0x00240024, 0x0024153B,
0x00242108, 0x00242409, 0x00242726, 0x00243108, 0x00243409, 0x00243610, 0x00244136, 0x00244321,
0x00244523, 0x00244F1F, 0x00245423, 0x0024610A, 0x00246423, 0x0024FF29, 0x00252510, 0x00253121,
0x0025343B, 0x00254121, 0x00254510, 0x00254F25, 0x00255221, 0x0025FF12, 0x00266513, 0x0027F529,
0x0029F101, 0x002CF224, 0x00310030, 0x0031122A, 0x00311420, 0x00311816, 0x0031212C, 0x0031231E,
0x00312408, 0x00312710, 0x0031312A, 0x0031321E, 0x00313408, 0x00313623, 0x0031411E, 0x0031433C,
0x00320007, 0x0032122D, 0x00321420, 0x00321816, 0x0032212D, 0x0032233C, 0x00322509, 0x00322916,
0x0032312D, 0x00323420, 0x00323710, 0x00323F2B, 0x00324308, 0x00324623, 0x00324F25, 0x00325421,
0x00325F1F, 0x00326421, 0x0032FF29, 0x00331107, 0x00331308, 0x0033150D, 0x0033211E, 0x00332308,
0x00332420, 0x00332610, 0x00332929, 0x0033311E, 0x00333308, 0x0033363B, 0x00333A29, 0x0033413C,
0x00334320, 0x0033463B, 0x00334A29, 0x0033510A, 0x00335320, 0x00335824, 0x0033610A, 0x00336321,
0x00336F12, 0x00337623, 0x00341139, 0x0034153B, 0x00342108, 0x00342409, 0x00342610, 0x00343108,
0x00343409, 0x00343610, 0x00344108, 0x0034440D, 0x00344610, 0x0034510A, 0x00345309, 0x0034553B,
0x0034610A, 0x00346309, 0x0034F824, 0x00350029, 0x00352510, 0x00353120, 0x0035330D, 0x00353510,
0x00354120, 0x0035430D, 0x00354510, 0x00354F28, 0x0035530D, 0x00355510, 0x00355F1F, 0x00356410,
0x00359626, 0x0035FF12, 0x00366426, 0x0036FF12, 0x0037F426, 0x0039D712, 0x003BF612, 0x003DF81F,
0x00410004, 0x00411207, 0x0041150D, 0x0041212A, 0x00412420, 0x0041311E, 0x00413308, 0x00413509,
0x00413F2B, 0x00414208, 0x00420007, 0x0042123C, 0x00421409, 0x00422107, 0x0042223C, 0x00422409,
0x00422610, 0x0042313C, 0x00423409, 0x0042363B, 0x0042413C, 0x00424320, 0x0042463B, 0x00425108,
0x00425409, 0x0042FF29, 0x00431107, 0x00431320, 0x0043153B, 0x0043213C, 0x00432320, 0x00432610,
0x0043313C, 0x00433320, 0x0043353B, 0x00433813, 0x00434108, 0x00434409, 0x00434610, 0x00435108,
0x0043553B, 0x00435F25, 0x00436309, 0x0043753B, 0x0043FF29, 0x00441239, 0x0044143B, 0x00442139,
0x00442309, 0x0044253B, 0x00443108, 0x00443220, 0x0044353B, 0x0044410A, 0x00444309, 0x0044453B,
0x00444813, 0x0044510A, 0x00445309, 0x00445510, 0x00445F25, 0x0044630D, 0x00450026, 0x00452713,
0x00453120, 0x0045330D, 0x00453510, 0x00454120, 0x0045430D, 0x00454510, 0x00455120, 0x0045530D,
0x00456209, 0x00456410, 0x0045FF12, 0x00466513, 0x0047FF22, 0x0048FF25, 0x0049F43D, 0x004BFB25,
0x004EF825, 0x004FFF18, 0x00511339, 0x00512107, 0x00513409, 0x00520007, 0x00521107, 0x00521320,
0x00522107, 0x00522409, 0x0052313C, 0x00523320, 0x0052353B, 0x00524108, 0x00524320, 0x00531139,
0x00531309, 0x00532139, 0x00532309, 0x0053253B, 0x00533108, 0x0053340D, 0x00533713, 0x00534108,
0x0053453B, 0x00534F2B, 0x00535309, 0x00535610, 0x00535F25, 0x0053643B, 0x00541139, 0x00542139,
0x00542309, 0x00542613, 0x00543139, 0x00543309, 0x00543510, 0x00543F2B, 0x00544309, 0x00544510,
0x00544F28, 0x0054530D, 0x0054FF12, 0x00553613, 0x00553F2B, 0x00554410, 0x0055510A, 0x0055543B,
0x00555F25, 0x0055633B, 0x0055FF12, 0x00566513, 0x00577413, 0x0059FF28, 0x005CC33D, 0x005EFB28,
0x005FFF18, 0x00611339, 0x00612107, 0x00613320, 0x0061A724, 0x00621107, 0x0062140B, 0x00622107,
0x00622320, 0x00623139, 0x00623320, 0x00631139, 0x0063130C, 0x00632139, 0x00632309, 0x00633139,
0x00633309, 0x00633626, 0x00633F2B, 0x00634309, 0x00634F2B, 0x0063543B, 0x0063FF12, 0x0064343B,
0x00643F2B, 0x0064443B, 0x00645209, 0x00665513, 0x0066610A, 0x00666526, 0x0067A616, 0x0069843D,
0x006CF612, 0x006EF326, 0x006FFF18, 0x0071130C, 0x00721107, 0x00722239, 0x0072291C, 0x0072340B,
0x00731139, 0x00732239, 0x0073630B, 0x0073FF12, 0x0074430B, 0x00755426, 0x00776F28, 0x00777410,
0x0078843D, 0x007CF416, 0x007EF326, 0x007FFF18, 0x00822239, 0x00831139, 0x0083430B, 0x0084530B,
0x0087561C, 0x00887F25, 0x00888426, 0x008AF61C, 0x008F0018, 0x008FFF18, 0x00911107, 0x0093230B,
0x0094530B, 0x0097743D, 0x00998C25, 0x00999616, 0x009EF825, 0x009FFF18, 0x00A3430B, 0x00A4530B,
0x00A7743D, 0x00AA9F2B, 0x00AAA616, 0x00ABD61F, 0x00AFFF18, 0x00B3330B, 0x00B44426, 0x00B7643D,
0x00BB971F, 0x00BBB53D, 0x00BEF512, 0x00BFFF18, 0x00C22139, 0x00C5330E, 0x00C7633D, 0x00CCAF2E,
0x00CCC616, 0x00CFFF18, 0x00D4440E, 0x00D6420E, 0x00DDCF2E, 0x00DDD516, 0x00DFFF18, 0x00E4330E,
0x00E6841C, 0x00EEE61C, 0x00EFFF18, 0x00F3320E, 0x00F55319, 0x00F8F41C, 0x00FAFF2E, 0x00FF002E,
0x00FFF10C, 0x00FFF33D, 0x00FFF722, 0x00FFFF18, 0x01000232, 0x0111113E, 0x01112103, 0x0111311A,
0x0112111A, 0x01122130, 0x01123130, 0x0112411D, 0x01131102, 0x01132102, 0x01133102, 0x01141108,
0x01142136, 0x01143136, 0x01144135, 0x0115223B, 0x01211103, 0x0121211A, 0x01213130, 0x01221130,
0x01222130, 0x01223102, 0x01231104, 0x01232104, 0x01233104, 0x01241139, 0x01241220, 0x01242220,
0x01251109, 0x0125223B, 0x0125810A, 0x01283212, 0x0131111A, 0x01312130, 0x0131222C, 0x0131322A,
0x0132122A, 0x0132222D, 0x0132322D, 0x01331207, 0x01332234, 0x01333234, 0x01341139, 0x01343134,
0x01344134, 0x01348134, 0x0135220B, 0x0136110B, 0x01365224, 0x01411102, 0x01412104, 0x01431239,
0x01432239, 0x0143320A, 0x01435134, 0x01443107, 0x01444134, 0x01446134, 0x0145220E, 0x01455134,
0x0147110E, 0x01511102, 0x01521239, 0x01531239, 0x01532239, 0x01533107, 0x0155220E, 0x01555134,
0x0157110E, 0x01611107, 0x01621239, 0x01631239, 0x01661139, 0x01666134, 0x01711107, 0x01721239,
0x01745107, 0x0177110C, 0x01811107, 0x01821107, 0x0185110C, 0x0188210C, 0x01911107, 0x01933139,
0x01A11107, 0x01A31139, 0x01F5220E, 0x02000001, 0x02000127, 0x02000427, 0x02000727, 0x02000E2F,
0x02110000, 0x02111200, 0x02111411, 0x02111827, 0x02111F2F, 0x02112411, 0x02112715, 0x02113200,
0x02113411, 0x02113715, 0x02114200, 0x02121200, 0x02121301, 0x02121F2F, 0x02122200, 0x02122615,
0x02122F2F, 0x02123311, 0x02123F2F, 0x02124411, 0x02131211, 0x02132311, 0x02133211, 0x02184415,
0x02211200, 0x02211311, 0x02211F2F, 0x02212311, 0x02212F2F, 0x02213211, 0x02221201, 0x02221311,
0x02221F2F, 0x02222311, 0x02222F2F, 0x02223211, 0x02223F2F, 0x02231211, 0x02232211, 0x02232F2F,
0x02233211, 0x02233F2F, 0x02287515, 0x022DAB17, 0x02311211, 0x02311527, 0x02312211, 0x02321211,
0x02322211, 0x02322F2F, 0x02323311, 0x02323F2F, 0x02331211, 0x02332211, 0x02332F2F, 0x02333F2F,
0x0237FF17, 0x02385615, 0x023D9517, 0x02410027, 0x02487827, 0x024E3117, 0x024FFF2F, 0x02598627,
0x025DFF2F, 0x025FFF2F, 0x02687827, 0x026DFA17, 0x026FFF2F, 0x02796427, 0x027E4217, 0x027FFF2F,
0x02888727, 0x028EFF2F, 0x028FFF2F, 0x02984327, 0x029F112F, 0x029FFF2F, 0x02A76527, 0x02AEF717,
0x02AFFF2F, 0x02B7C827, 0x02BEF917, 0x02BFFF2F, 0x02C66527, 0x02CD5517, 0x02CFFF2F, 0x02D63227,
0x02DDD527, 0x02DFFF2B, 0x02E84717, 0x02EEE327, 0x02EFFF2F, 0x02F54527, 0x02FCF817, 0x02FFEF2B,
0x02FFFA2F, 0x02FFFE2F, 0x03000127, 0x03000201, 0x03111200, 0x03122115, 0x03123200, 0x03133211,
0x03211200, 0x03213127, 0x03221200, 0x03345215, 0x04000F17, 0x04122F17, 0x043F6515, 0x043FFF17,
0x044F5527, 0x044FFF17, 0x045F0017, 0x045FFF17, 0x046F6517, 0x04710027, 0x047F4427, 0x04810027,
0x048EFA15, 0x048FFF2F, 0x049F4427, 0x049FFF2F, 0x04AEA727, 0x04AFFF2F, 0x04BE9C15, 0x04BFFF2F,
0x04CE5427, 0x04CFFF2F, 0x04DE3527, 0x04DFFF17, 0x04EE4627, 0x04EFFF17, 0x04FEF327, 0x04FFFF2F,
0x06000F27, 0x069FFF17, 0x06FFFF17, 0x08110017, 0x08EFFF15, 0xFFFFFF00
};
// ISO/IEC 23003-3, Annex C.3
static const uint16_t arithCumFreqM[64][ARITH_ESCAPE + 1] = { // arith_cf_m
{ 708, 706, 579, 569, 568, 567, 479, 469, 297, 138, 97, 91, 72, 52, 38, 34, 0},
{ 7619, 6917, 6519, 6412, 5514, 5003, 4683, 4563, 3907, 3297, 3125, 3060, 2904, 2718, 2631, 2590, 0},
{ 7263, 4888, 4810, 4803, 1889, 415, 335, 327, 195, 72, 52, 49, 36, 20, 15, 14, 0},
{ 3626, 2197, 2188, 2187, 582, 57, 47, 46, 30, 12, 9, 8, 6, 4, 3, 2, 0},
{ 7806, 5541, 5451, 5441, 2720, 834, 691, 674, 487, 243, 179, 167, 139, 98, 77, 70, 0},
{ 6684, 4101, 4058, 4055, 1748, 426, 368, 364, 322, 257, 235, 232, 228, 222, 217, 215, 0},
{ 9162, 5964, 5831, 5819, 3269, 866, 658, 638, 535, 348, 258, 244, 234, 214, 195, 186, 0},
{10638, 8491, 8365, 8351, 4418, 2067, 1859, 1834, 1190, 601, 495, 478, 356, 217, 174, 164, 0},
{13389,10514,10032, 9961, 7166, 3488, 2655, 2524, 2015, 1140, 760, 672, 585, 426, 325, 283, 0},
{14861,12788,12115,11952, 9987, 6657, 5323, 4984, 4324, 3001, 2205, 1943, 1764, 1394, 1115, 978, 0},
{12876,10004, 9661, 9610, 7107, 3435, 2711, 2595, 2257, 1508, 1059, 952, 893, 753, 609, 538, 0},
{15125,13591,13049,12874,11192, 8543, 7406, 7023, 6291, 4922, 4104, 3769, 3465, 2890, 2486, 2275, 0},
{14574,13106,12731,12638,10453, 7947, 7233, 7037, 6031, 4618, 4081, 3906, 3465, 2802, 2476, 2349, 0},
{15070,13179,12517,12351,10742, 7657, 6200, 5825, 5264, 3998, 3014, 2662, 2510, 2153, 1799, 1564, 0},
{15542,14466,14007,13844,12489,10409, 9481, 9132, 8305, 6940, 6193, 5867, 5458, 4743, 4291, 4047, 0},
{15165,14384,14084,13934,12911,11485,10844,10513,10002, 8993, 8380, 8051, 7711, 7036, 6514, 6233, 0},
{15642,14279,13625,13393,12348, 9971, 8405, 7858, 7335, 6119, 4918, 4376, 4185, 3719, 3231, 2860, 0},
{13408,13407,11471,11218,11217,11216, 9473, 9216, 6480, 3689, 2857, 2690, 2256, 1732, 1405, 1302, 0},
{16098,15584,15191,14931,14514,13578,12703,12103,11830,11172,10475, 9867, 9695, 9281, 8825, 8389, 0},
{15844,14873,14277,13996,13230,11535,10205, 9543, 9107, 8086, 7085, 6419, 6214, 5713, 5195, 4731, 0},
{16131,15720,15443,15276,14848,13971,13314,12910,12591,11874,11225,10788,10573,10077, 9585, 9209, 0},
{16331,16330,12283,11435,11434,11433, 8725, 8049, 6065, 4138, 3187, 2842, 2529, 2171, 1907, 1745, 0},
{16011,15292,14782,14528,14008,12767,11556,10921,10591, 9759, 8813, 8043, 7855, 7383, 6863, 6282, 0},
{16380,16379,15159,14610,14609,14608,12859,12111,11046, 9536, 8348, 7713, 7216, 6533, 5964, 5546, 0},
{16367,16333,16294,16253,16222,16143,16048,15947,15915,15832,15731,15619,15589,15512,15416,15310, 0},
{15967,15319,14937,14753,14010,12638,11787,11360,10805, 9706, 8934, 8515, 8166, 7456, 6911, 6575, 0},
{ 4906, 3005, 2985, 2984, 875, 102, 83, 81, 47, 17, 12, 11, 8, 5, 4, 3, 0},
{ 7217, 4346, 4269, 4264, 1924, 428, 340, 332, 280, 203, 179, 175, 171, 164, 159, 157, 0},
{16010,15415,15032,14805,14228,13043,12168,11634,11265,10419, 9645, 9110, 8892, 8378, 7850, 7437, 0},
{ 8573, 5218, 5046, 5032, 2787, 771, 555, 533, 443, 286, 218, 205, 197, 181, 168, 162, 0},
{11474, 8095, 7822, 7796, 4632, 1443, 1046, 1004, 748, 351, 218, 194, 167, 121, 93, 83, 0},
{16152,15764,15463,15264,14925,14189,13536,13070,12846,12314,11763,11277,11131,10777,10383,10011, 0},
{14187,11654,11043,10919, 8498, 4885, 3778, 3552, 2947, 1835, 1283, 1134, 998, 749, 585, 514, 0},
{14162,11527,10759,10557, 8601, 5417, 4105, 3753, 3286, 2353, 1708, 1473, 1370, 1148, 959, 840, 0},
{16205,15902,15669,15498,15213,14601,14068,13674,13463,12970,12471,12061,11916,11564,11183,10841, 0},
{15043,12972,12092,11792,10265, 7446, 5934, 5379, 4883, 3825, 3036, 2647, 2507, 2185, 1901, 1699, 0},
{15320,13694,12782,12352,11191, 8936, 7433, 6671, 6255, 5366, 4622, 4158, 4020, 3712, 3420, 3198, 0},
{16255,16020,15768,15600,15416,14963,14440,14006,13875,13534,13137,12697,12602,12364,12084,11781, 0},
{15627,14503,13906,13622,12557,10527, 9269, 8661, 8117, 6933, 5994, 5474, 5222, 4664, 4166, 3841, 0},
{16366,16365,14547,14160,14159,14158,11969,11473, 8735, 6147, 4911, 4530, 3865, 3180, 2710, 2473, 0},
{16257,16038,15871,15754,15536,15071,14673,14390,14230,13842,13452,13136,13021,12745,12434,12154, 0},
{15855,14971,14338,13939,13239,11782,10585, 9805, 9444, 8623, 7846, 7254, 7079, 6673, 6262, 5923, 0},
{ 9492, 6318, 6197, 6189, 3004, 652, 489, 477, 333, 143, 96, 90, 78, 60, 50, 47, 0},
{16313,16191,16063,15968,15851,15590,15303,15082,14968,14704,14427,14177,14095,13899,13674,13457, 0},
{ 8485, 5473, 5389, 5383, 2411, 494, 386, 377, 278, 150, 117, 112, 103, 89, 81, 78, 0},
{10497, 7154, 6959, 6943, 3788, 1004, 734, 709, 517, 238, 152, 138, 120, 90, 72, 66, 0},
{16317,16226,16127,16040,15955,15762,15547,15345,15277,15111,14922,14723,14671,14546,14396,14239, 0},
{16382,16381,15858,15540,15539,15538,14704,14168,13768,13092,12452,11925,11683,11268,10841,10460, 0},
{ 5974, 3798, 3758, 3755, 1275, 205, 166, 162, 95, 35, 26, 24, 18, 11, 8, 7, 0},
{ 3532, 2258, 2246, 2244, 731, 135, 118, 115, 87, 45, 36, 34, 29, 21, 17, 16, 0},
{ 7466, 4882, 4821, 4811, 2476, 886, 788, 771, 688, 531, 469, 457, 437, 400, 369, 361, 0},
{ 9580, 5772, 5291, 5216, 3444, 1496, 1025, 928, 806, 578, 433, 384, 366, 331, 296, 273, 0},
{10692, 7730, 7543, 7521, 4679, 1746, 1391, 1346, 1128, 692, 495, 458, 424, 353, 291, 268, 0},
{11040, 7132, 6549, 6452, 4377, 1875, 1253, 1130, 958, 631, 431, 370, 346, 296, 253, 227, 0},
{12687, 9332, 8701, 8585, 6266, 3093, 2182, 2004, 1683, 1072, 712, 608, 559, 458, 373, 323, 0},
{13429, 9853, 8860, 8584, 6806, 4039, 2862, 2478, 2239, 1764, 1409, 1224, 1178, 1077, 979, 903, 0},
{14685,12163,11061,10668, 9101, 6345, 4871, 4263, 3908, 3200, 2668, 2368, 2285, 2106, 1942, 1819, 0},
{13295,11302,10999,10945, 7947, 5036, 4490, 4385, 3391, 2185, 1836, 1757, 1424, 998, 833, 785, 0},
{ 4992, 2993, 2972, 2970, 1269, 575, 552, 549, 530, 505, 497, 495, 493, 489, 486, 485, 0},
{15419,13862,13104,12819,11429, 8753, 7220, 6651, 6020, 4667, 3663, 3220, 2995, 2511, 2107, 1871, 0},
{12468, 9263, 8912, 8873, 5758, 2193, 1625, 1556, 1187, 589, 371, 330, 283, 200, 149, 131, 0},
{15870,15076,14615,14369,13586,12034,10990,10423, 9953, 8908, 8031, 7488, 7233, 6648, 6101, 5712, 0},
{ 1693, 978, 976, 975, 194, 18, 16, 15, 11, 7, 6, 5, 4, 3, 2, 1, 0},
{ 7992, 5218, 5147, 5143, 2152, 366, 282, 276, 173, 59, 38, 35, 27, 16, 11, 10, 0}
};
// ISO/IEC 23003-3, Annex C.4
static const uint16_t arithCumFreqR[3][4] = { // arith_cf_r
{12571, 10569, 3696, 0},
{12661, 5700, 3751, 0},
{10827, 6884, 2929, 0}
};
// static helper functions
static inline unsigned arithGetPkIndex (const unsigned ctx) // cumul. frequency table index pki = arith_get_pk(c)
{
int iMax = ARITH_SIZE - 1;
int iMin = -1;
int i = iMin;
uint32_t j, k;
while (iMax > iMin + 1)
{
i = iMin + ((iMax - iMin) >> 1);
j = arithHashM[i];
k = j >> 8;
if (ctx < k) iMax = i;
else if (ctx > k) iMin = i;
else return j & UCHAR_MAX;
}
return arithLookupM[iMax]; // pki
}
static inline unsigned writeSymbol (OutputStream* const stream, const bool leadingBitIs1, const uint16_t trailingBits)
{
stream->write (leadingBitIs1 ? 1 : 0, 1);
stream->write (leadingBitIs1 ? 0 : (1u << trailingBits) - 1, (uint8_t) trailingBits);
return trailingBits + 1; // count
}
// private helper functions
unsigned EntropyCoder::arithCodeSymbol (const uint16_t symbol, const uint16_t* table, OutputStream* const stream /*= nullptr*/)
{
const unsigned range = m_acHigh + 1 - m_acLow;
uint16_t high = m_acHigh;
uint16_t low = m_acLow;
unsigned bitCount = 0;
if (symbol > 0)
{
high = low + ((range * table[symbol - 1]) >> 14) - 1;
}
low += (range * table[symbol]) >> 14; // NOTE: the spec incorrectly reads "[symbol-1]" here
if (stream != nullptr) // write-out
{
while (true)
{
if (high <= SHRT_MAX)
{
bitCount += writeSymbol (stream, false, m_acBits);
m_acBits = 0;
}
else if (low > SHRT_MAX)
{
bitCount += writeSymbol (stream, true, m_acBits);
m_acBits = 0;
high += SHRT_MIN;
low += SHRT_MIN;
}
else if ((low > (SHRT_MAX >> 1)) && (high < ((-3 * SHRT_MIN) >> 1)))
{
m_acBits++;
high += SHRT_MIN >> 1;
low += SHRT_MIN >> 1;
}
else break;
high <<= 1; high |= 1;
low <<= 1;
}
}
else // stream == nullptr, counting
{
while (true)
{
if (high <= SHRT_MAX)
{
bitCount += m_acBits + 1;
m_acBits = 0;
}
else if (low > SHRT_MAX)
{
bitCount += m_acBits + 1;
m_acBits = 0;
high += SHRT_MIN;
low += SHRT_MIN;
}
else if ((low > (SHRT_MAX >> 1)) && (high < ((-3 * SHRT_MIN) >> 1)))
{
m_acBits++;
high += SHRT_MIN >> 1;
low += SHRT_MIN >> 1;
}
else break;
high <<= 1; high |= 1;
low <<= 1;
}
}
m_acHigh = high;
m_acLow = low;
return bitCount;
}
unsigned EntropyCoder::arithGetContext (const unsigned ctx, const unsigned idx) // c = arith_get_context(c, i, N)
{
unsigned c = (ctx & 0xFFFF) >> 4; // NOTE: the "& 0xFFFF" was part of some USAC corrigendum
c = (c | ((unsigned) m_qcPrev[idx + 1] << 12)) & 0xFFF0; // add top-left previous neighbor
if (idx > 0) // lower neighbor(s)
{
c |= (unsigned) m_qcCurr[idx - 1];
if ((idx > 3) && (m_qcCurr[idx - 3] + m_qcCurr[idx - 2] + m_qcCurr[idx - 1] < 5))
{
return c | 0x10000;
}
}
return c; // updated context ctx
}
unsigned EntropyCoder::arithMapContext (const bool arithResetFlag) // c = arith_map_context(N, arith_reset_flag)
{
if (arithResetFlag)
{
memset (m_qcPrev, 0, m_maxTupleLength * sizeof (uint8_t));
}
else if (m_shortTrafoCurr == m_shortTrafoPrev)
{
memcpy (m_qcPrev, m_qcCurr, m_acSize * sizeof (uint8_t));
}
else if (m_shortTrafoCurr && !m_shortTrafoPrev)
{
for (int i = m_acSize - 1; i >= 0; i--)
{
m_qcPrev[i] = m_qcCurr[i << 3];
}
}
else // (!m_shortTrafoCurr && m_shortTrafoPrev)
{
for (int i = m_acSize - 1; i >= 0; i--)
{
m_qcPrev[i] = m_qcCurr[i >> 3];
}
}
m_qcPrev[m_acSize] = 0; // for encoder speed-up
return (unsigned) m_qcPrev[0] << 12; // initial context ctx with top-left previous neighbor
}
#if EC_TRELLIS_OPT_CODING
void EntropyCoder::arithSetContext (const unsigned newCtxState, const uint16_t sigEnd)
{
m_csCurr = newCtxState;
m_acBits = (m_csCurr >> 17) & 31;
for (uint16_t s = 1; s < 4; s++)
{
if (sigEnd >= s) m_qcCurr[sigEnd - s] = (m_csCurr >> (18 + 4 * s)) & 0xF;
}
}
#endif
// constructor
EntropyCoder::EntropyCoder ()
{
// initialize all helper buffers
m_qcCurr = nullptr;
m_qcPrev = nullptr;
// initialize encoding variables
m_acBits = 0;
m_acHigh = USHRT_MAX;
m_acLow = 0;
m_acSize = 0;
m_csCurr = 0;
m_maxTupleLength = 0;
m_shortTrafoCurr = false;
m_shortTrafoPrev = false;
}
// destructor
EntropyCoder::~EntropyCoder ()
{
// free allocated helper buffers
MFREE (m_qcCurr);
MFREE (m_qcPrev);
}
// public functions
unsigned EntropyCoder::arithCodeSigMagn (const uint8_t* const magn, const uint16_t sigOffset, const uint16_t sigLength,
const bool arithFinish /*= false*/, OutputStream* const stream /*= nullptr*/)
{
const uint8_t* a = &magn[sigOffset ];
const uint8_t* b = &magn[sigOffset + 1];
unsigned c = m_csCurr & 0x1FFFF;
unsigned bitCount = 0;
uint16_t r[7];
uint16_t sigEnd = (sigOffset >> 1) + (sigLength >> 1);
if (arithFinish && (sigLength > 0)) // try to save bits via signalling of ARITH_STOP symbol
{
int i = sigLength - 2;
while ((i >= 0) && ((a[i] | b[i]) == 0)) i -= 2;
i = (sigOffset + i + 2) >> 1;
if (i + 28 < (int) sigEnd) sigEnd = (uint16_t) i;
}
for (uint16_t s = sigOffset >> 1; s < sigEnd; s++)
{
uint32_t lev = 0;
uint16_t a1 = *a;
uint16_t b1 = *b;
a += 2; b += 2;
// arith_get_context, cf Scl. 7.4
c = arithGetContext (c, s);
// arith_update_context, Scl. 7.4
m_qcCurr[s] = __min (0xF, a1 + b1 + 1);
// MSB encoding as in Scl. B.25.3
while ((a1 > 3) || (b1 > 3))
{
// write escaped codeword value
bitCount += arithCodeSymbol (ARITH_ESCAPE, arithCumFreqM[arithGetPkIndex (c | (lev << 17))], stream);
// store LSBs in r, right-shift
r[lev++] = (a1 & 1) | ((b1 & 1) << 1);
a1 >>= 1; b1 >>= 1;
}
// write the m MSB codeword value
bitCount += arithCodeSymbol (a1 | (b1 << 2), arithCumFreqM[arithGetPkIndex (c | (lev << 17))], stream);
// LSB encoding, Table 38, B.25.3
while (lev--)
{
const uint16_t rLev = r[lev];
bitCount += arithCodeSymbol (rLev, arithCumFreqR[a1 == 0 ? 1 : (b1 == 0 ? 0 : 2)], stream);
a1 = (a1 << 1) | (rLev & 1);
b1 = (b1 << 1) | ((rLev >> 1) & 1);
}
} // for s
if (arithFinish) // flush last bits
{
// NOTE: the spec incorrectly reads "m_acBits" below (bits_to_follow++ missing in B.25.3)
if (sigLength > 0)
{
if (sigEnd < (sigOffset >> 1) + (sigLength >> 1)) // write ARITH_STOP flag to save bits
{
c = arithGetContext (c, sigEnd);
bitCount += arithCodeSymbol (ARITH_ESCAPE, arithCumFreqM[arithGetPkIndex (c)], stream);
bitCount += arithCodeSymbol (0 /*m=STOP*/, arithCumFreqM[arithGetPkIndex (c | (1 << 17))], stream);
}
bitCount += writeSymbol (stream, m_acLow > (SHRT_MAX >> 1), m_acBits + 1);
}
m_csCurr = m_acBits = 0;
}
else
{
m_csCurr = 0;
for (uint16_t s = 1; s < 4; s++)
{
if (sigEnd >= s) m_csCurr |= __min (255u >> (2 * s), m_qcCurr[sigEnd - s]) << (18 + 4 * s);
}
}
m_csCurr |= ((unsigned) m_acBits << 17) | c;
return bitCount;
}
unsigned EntropyCoder::arithGetResetBit (const uint8_t* const magn, const uint16_t sigOffset, const uint16_t sigLength)
{
const uint16_t sigEnd = (sigOffset >> 1) + (sigLength >> 1);
const uint8_t* a = &magn[sigOffset ];
const uint8_t* b = &magn[sigOffset + 1];
unsigned qcDiff = 0;
for (uint16_t s = sigOffset >> 1; s < sigEnd; s++)
{
const int qcCurrS = __min (0xF, (int)*a + (int)*b);
const int qcDiffS = qcCurrS - m_qcPrev[s];
qcDiff += qcDiffS * qcDiffS;
a += 2; b += 2;
}
return (qcDiff * 2u > sigLength * 7u ? 1 : 0); // use reset if difference exceeds threshold
}
unsigned EntropyCoder::indexGetBitCount (const int scaleFactorDelta) const
{
return huffScf[CLIP_PM (scaleFactorDelta, INDEX_OFFSET) + INDEX_OFFSET] & UCHAR_MAX;
}
unsigned EntropyCoder::indexGetHuffCode (const int scaleFactorDelta) const
{
return huffScf[CLIP_PM (scaleFactorDelta, INDEX_OFFSET) + INDEX_OFFSET] >> 8;
}
unsigned EntropyCoder::initCodingMemory (const unsigned maxTransfLength)
{
const unsigned max2TupleLength = maxTransfLength >> 1; // tuple buffer size, maxWinLength/4
if ((maxTransfLength < 128) || (maxTransfLength > 8192) || (maxTransfLength & 7))
{
return 1; // invalid arguments error
}
m_maxTupleLength = max2TupleLength;
MFREE (m_qcCurr);
MFREE (m_qcPrev);
if ((m_qcCurr = (uint8_t*) malloc (max2TupleLength * sizeof (uint8_t))) == nullptr ||
(m_qcPrev = (uint8_t*) malloc ((max2TupleLength + 1) * sizeof (uint8_t))) == nullptr)
{
return 2; // memory allocation error
}
memset (m_qcCurr, 0, max2TupleLength * sizeof (uint8_t));
return 0; // no error
}
unsigned EntropyCoder::initWindowCoding (const bool forceArithReset, const bool shortWin /*= false*/)
{
// arith_first_symbol() in Scl. B.25.3
m_acBits = 0;
m_acHigh = USHRT_MAX;
m_acLow = 0;
m_acSize = (shortWin ? m_maxTupleLength >> 3 : m_maxTupleLength);
m_shortTrafoPrev = m_shortTrafoCurr;
m_shortTrafoCurr = shortWin;
m_csCurr = arithMapContext (forceArithReset); // m_qcPrev
memset (m_qcCurr, 1, m_acSize * sizeof (uint8_t)); // reset m_qcCurr, see also arith_finish
return 0; // no error
}

79
src/lib/entropyCoding.h Normal file
View File

@ -0,0 +1,79 @@
/* entropyCoding.h - header file for class with lossless entropy coding capability
* written by C. R. Helmrich, last modified in 2019 - 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.
*/
#ifndef _ENTROPY_CODING_H_
#define _ENTROPY_CODING_H_
#include "exhaleLibPch.h"
// constants, experimental macro
#define ARITH_ESCAPE 16
#define ARITH_SIZE 742
#define INDEX_OFFSET 60
#define INDEX_SIZE 121
#define EC_TRELLIS_OPT_CODING 1
// lossless entropy coding class
class EntropyCoder
{
private:
// member variables
uint8_t* m_qcCurr; // curr. window's quantized context q[1]
uint8_t* m_qcPrev; // prev. window's quantized context q[0]
uint16_t m_acBits; // bits_to_follow in arith_encode, 0..31
uint16_t m_acHigh; // high in arith_encode as in Annex B.25
uint16_t m_acLow; // low in arith_encode, as in Annex B.25
uint16_t m_acSize; // context window size (N/4 in Scl. 7.4)
uint32_t m_csCurr; // context state, see initWindowCoding()
unsigned m_maxTupleLength; // maximum half-transform length (<4096)
bool m_shortTrafoCurr; // used to derive N in Scl. 7.4 and B.25
bool m_shortTrafoPrev; // used to derive previous_N in Scl. 7.4
// helper functions
unsigned arithCodeSymbol (const uint16_t symbol, const uint16_t* table, OutputStream* const stream = nullptr);
unsigned arithGetContext (const unsigned ctx, const unsigned idx);
unsigned arithMapContext (const bool arithResetFlag);
#if EC_TRELLIS_OPT_CODING
void arithSetContext (const unsigned newCtxState, const uint16_t sigEnd);
#endif
public:
// constructor
EntropyCoder ();
// destructor
~EntropyCoder ();
// public functions
unsigned arithCodeSigMagn (const uint8_t* const magn, const uint16_t sigOffset, const uint16_t sigLength,
const bool arithFinish = false, OutputStream* const stream = nullptr);
unsigned arithGetCodState () const { return ((unsigned) m_acHigh << 16) | (unsigned) m_acLow; }
unsigned arithGetCtxState () const { return m_csCurr; }
unsigned arithGetResetBit (const uint8_t* const magn, const uint16_t sigOffset, const uint16_t sigLength);
char* arithGetTuplePtr () const { return (char*) m_qcCurr; }
void arithResetMemory () { memset (m_qcPrev, 0, (m_maxTupleLength + 1) * sizeof (uint8_t)); m_acBits = 0; }
void arithSetCodState (const unsigned newCodState) { m_acHigh = newCodState >> 16; m_acLow = newCodState & USHRT_MAX; }
#if EC_TRELLIS_OPT_CODING
void arithSetCtxState (const unsigned newCtxState, const uint16_t sigOffset = 0) { arithSetContext (newCtxState, sigOffset >> 1); }
#else
void arithSetCtxState (const unsigned newCtxState) { m_csCurr = newCtxState; }
#endif
unsigned indexGetBitCount (const int scaleFactorDelta) const;
unsigned indexGetHuffCode (const int scaleFactorDelta) const;
unsigned initCodingMemory (const unsigned maxTransfLength);
unsigned initWindowCoding (const bool forceArithReset, const bool shortWin = false);
bool getIsShortWindow () const { return m_shortTrafoCurr; }
void setIsShortWindow (const bool shortWin) { m_shortTrafoCurr = shortWin; }
}; // EntropyCoder
#endif // _ENTROPY_CODING_H_

1590
src/lib/exhaleEnc.cpp Normal file

File diff suppressed because it is too large Load Diff

143
src/lib/exhaleEnc.h Normal file
View File

@ -0,0 +1,143 @@
/* exhaleEnc.h - header file for class providing Extended HE-AAC encoding capability
* written by C. R. Helmrich, last modified in 2019 - 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.
*/
#ifndef _EXHALE_ENC_H_
#define _EXHALE_ENC_H_
#include "exhaleLibPch.h"
#include "bitAllocation.h"
#include "bitStreamWriter.h"
#include "entropyCoding.h"
#include "lappedTransform.h"
#include "linearPrediction.h"
#include "quantization.h"
#include "specAnalysis.h"
#include "specGapFilling.h"
#include "tempAnalysis.h"
// constant and experimental macro
#define WIN_SCALE double (1 << 23)
#define EE_OPT_TNS_SPEC_RANGE 1
// channelConfigurationIndex setup
typedef enum USAC_CCI : char
{
CCI_UNDEF = -1,
CCI_CONF = 0, // channel-to-speaker mapping defined in UsacChannelConfig() (not to be used here!)
CCI_1_CH = 1, // 1.0: front-center
CCI_2_CH = 2, // 2.0: front-left, front-right
CCI_3_CH = 3, // 3.0: front-center, front-left, front-right
CCI_4_CH = 4, // 4.0: front-center, front-left, front-right, back-center
CCI_5_CH = 5, // 5.0: front-center, front-left, front-right, back-left, back-right
CCI_6_CH = 6, // 5.1: front-center, front-left, front-right, back-left, back-right, LFE
CCI_8_CH = 7, // 7.1: front-center, front-left, front-right, side-left, side-right, back-left, back-right, LFE
CCI_2_CHM = 8, // 2.0, dual-mono: channel1, channel2
CCI_3_CHR = 9, // 3.0, R-rotated: front-left, front-right, back-center
CCI_4_CHR = 10, // 4.0, R-rotated: front-left, front-right, back-left, back-right
CCI_7_CH = 11, // 6.1: front-center, front-left, front-right, back-left, back-right, back-center, LFE
CCI_8_CHS = 12 // 7.1, surround: front-center, front-L, front-R, surround-L, surround-R, back-L, back-R, LFE
} USAC_CCI;
// coreCoderFrameLength definition
typedef enum USAC_CCFL : short
{
CCFL_UNDEF = -1,
#if !RESTRICT_TO_AAC
CCFL_768 = 768, // LD
#endif
CCFL_1024 = 1024 // LC
} USAC_CCFL;
// overall xHE-AAC encoding class
class ExhaleEncoder
{
private:
// member variables
uint16_t m_bandwidCurr[USAC_MAX_NUM_CHANNELS];
uint16_t m_bandwidPrev[USAC_MAX_NUM_CHANNELS];
BitAllocator m_bitAllocator; // for scale factor init
uint8_t m_bitRateMode;
USAC_CCI m_channelConf;
CoreCoderData* m_elementData[USAC_MAX_NUM_ELEMENTS];
EntropyCoder m_entropyCoder[USAC_MAX_NUM_CHANNELS];
uint32_t m_frameCount;
USAC_CCFL m_frameLength;
char m_frequencyIdx;
bool m_indepFlag; // usacIndependencyFlag bit
uint32_t m_indepPeriod;
LinearPredictor m_linPredictor; // for pre-roll est, TNS
uint8_t* m_mdctQuantMag[USAC_MAX_NUM_CHANNELS];
int32_t* m_mdctSignals[USAC_MAX_NUM_CHANNELS];
int32_t* m_mdstSignals[USAC_MAX_NUM_CHANNELS];
#if !RESTRICT_TO_AAC
bool m_noiseFilling[USAC_MAX_NUM_ELEMENTS];
bool m_nonMpegExt;
#endif
uint8_t m_numElements;
uint8_t m_numSwbShort;
uint8_t* m_outAuData;
BitStreamWriter m_outStream; // for access unit creation
int32_t* m_pcm24Data;
SfbGroupData* m_scaleFacData[USAC_MAX_NUM_CHANNELS];
SfbQuantizer m_sfbQuantizer; // powerlaw quantization
SpecAnalyzer m_specAnalyzer; // for spectral analysis
uint32_t m_specAnaCurr[USAC_MAX_NUM_CHANNELS];
uint32_t m_specAnaPrev[USAC_MAX_NUM_CHANNELS];
#if !RESTRICT_TO_AAC
SpecGapFiller m_specGapFiller;// for noise/gap filling
#endif
uint8_t m_swbTableIdx;
TempAnalyzer m_tempAnalyzer; // for temporal analysis
uint32_t m_tempAnaCurr[USAC_MAX_NUM_CHANNELS];
uint32_t m_tempAnaNext[USAC_MAX_NUM_CHANNELS];
int32_t* m_tempIntBuf; // temporary int32 buffer
int32_t* m_timeSignals[USAC_MAX_NUM_CHANNELS];
#if !RESTRICT_TO_AAC
bool m_timeWarping[USAC_MAX_NUM_ELEMENTS];
#endif
int32_t* m_timeWindowL[2]; // long window halves
int32_t* m_timeWindowS[2]; // short window halves
int16_t m_tranLocCurr[USAC_MAX_NUM_CHANNELS];
int16_t m_tranLocNext[USAC_MAX_NUM_CHANNELS];
LappedTransform m_transform; // time-frequency transform
// helper functions
unsigned applyTnsToWinGroup (TnsData& tnsData, SfbGroupData& grpData, const bool eightShorts, const uint8_t maxSfb,
const unsigned channelIndex);
unsigned eightShortGrouping (SfbGroupData& grpData, uint16_t* const grpOffsets, int32_t* const mdctSignal);
unsigned getOptParCorCoeffs (const int32_t* const mdctSignal, const SfbGroupData& grpData, const uint8_t maxSfb,
const unsigned channelIndex, TnsData& tnsData, const uint8_t firstGroupIndexToTest = 0);
unsigned psychBitAllocation ();
unsigned quantizationCoding ();
unsigned spectralProcessing ();
unsigned temporalProcessing ();
public:
// constructor
ExhaleEncoder (int32_t* const inputPcmData, unsigned char* const outputAuData,
const unsigned sampleRate = 44100, const unsigned numChannels = 2,
const unsigned frameLength = 1024, const unsigned indepPeriod = 45,
const unsigned varBitRateMode = 3
#if !RESTRICT_TO_AAC
, const bool useNoiseFilling = true, const bool useEcodisExt = false
#endif
);
// destructor
~ExhaleEncoder ();
// public functions
unsigned encodeLookahead ();
unsigned encodeFrame ();
unsigned initEncoder (unsigned char* const audioConfigBuffer, uint32_t* const audioConfigBytes = nullptr);
}; // ExhaleEncoder
#endif // _EXHALE_ENC_H_

85
src/lib/exhaleLibPch.cpp Normal file
View File

@ -0,0 +1,85 @@
/* exhaleLibPch.cpp - pre-compiled source file for classes of exhaleLib coding library
* written by C. R. Helmrich, last modified in 2019 - 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 "exhaleLibPch.h"
// public bit-stream functions
void OutputStream::reset () // clear writer states and byte buffer
{
heldBitChunk = 0;
heldBitCount = 0;
stream.clear ();
}
void OutputStream::write (const uint32_t bitChunk, const uint8_t bitCount)
{
if (bitCount > 32) return; // only a maximum of 32 bits is writable at once
const uint8_t totalBitCount = bitCount + heldBitCount;
const uint8_t totalByteCount = totalBitCount >> 3; // to be written
const uint8_t newHeldBitCount = totalBitCount & 7; // not yet written
const uint8_t newHeldBitChunk = (bitChunk << (8 - newHeldBitCount)) & UCHAR_MAX;
if (totalByteCount == 0) // not enough bits to write, only update held bits
{
heldBitChunk |= newHeldBitChunk;
}
else // write bits
{
const uint32_t writtenChunk = (heldBitChunk << uint32_t ((bitCount - newHeldBitCount) & ~7)) | (bitChunk >> newHeldBitCount);
switch (totalByteCount)
{
case 4: stream.push_back (writtenChunk >> 24);
case 3: stream.push_back (writtenChunk >> 16);
case 2: stream.push_back (writtenChunk >> 8);
case 1: stream.push_back (writtenChunk);
}
heldBitChunk = newHeldBitChunk;
}
heldBitCount = newHeldBitCount;
}
// ISO/IEC 23003-3, Table 67
static const unsigned allowedSamplingRates[USAC_NUM_SAMPLE_RATES] = {
96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350, // AAC
57600, 51200, 40000, 38400, 34150, 28800, 25600, 20000, 19200, 17075, 14400, 12800, 9600 // USAC
};
// public sampling rate functions
char toSamplingFrequencyIndex (const unsigned samplingRate)
{
for (char i = 0; i < AAC_NUM_SAMPLE_RATES; i++)
{
if (samplingRate == allowedSamplingRates[(int) i]) // AAC rate
{
return i;
}
#if !RESTRICT_TO_AAC
if (samplingRate == allowedSamplingRates[i + AAC_NUM_SAMPLE_RATES])
{
return i + AAC_NUM_SAMPLE_RATES + 2; // skip reserved entry
}
#endif
}
return -1; // no index found
}
unsigned toSamplingRate (const char samplingFrequencyIndex)
{
#if RESTRICT_TO_AAC
if ((samplingFrequencyIndex < 0) || (samplingFrequencyIndex >= AAC_NUM_SAMPLE_RATES))
#else
if ((samplingFrequencyIndex < 0) || (samplingFrequencyIndex >= USAC_NUM_SAMPLE_RATES + 2))
#endif
{
return 0; // invalid index
}
return allowedSamplingRates[samplingFrequencyIndex > AAC_NUM_SAMPLE_RATES ? samplingFrequencyIndex - 2 : samplingFrequencyIndex];
}

176
src/lib/exhaleLibPch.h Normal file
View File

@ -0,0 +1,176 @@
/* exhaleLibPch.h - pre-compiled header file for classes of exhaleLib coding library
* written by C. R. Helmrich, last modified in 2019 - 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.
*/
#ifndef _EXHALE_LIB_PCH_H_
#define _EXHALE_LIB_PCH_H_
#include <limits.h> // for .._MAX, .._MIN
#include <math.h> // for pow, sin, sqrt
#include <stdint.h> // for (u)int8_t, (u)int16_t, (u)int32_t, (u)int64_t
#include <stdlib.h> // for abs, div, calloc, malloc, free, (__)max, (__)min, (s)rand
#include <string.h> // for memcpy, memset
#include <vector> // for std::vector <>
// constants, experimental macros
#define AAC_NUM_SAMPLE_RATES 13
#define MAX_PREDICTION_ORDER 4
#define MAX_NUM_SWB_LFE 6
#define MAX_NUM_SWB_SHORT 15
#define MIN_NUM_SWB_SHORT 12
#define NUM_WINDOW_GROUPS 4 // must be between 4 and 8
#define USAC_MAX_NUM_CHANNELS 8
#define USAC_MAX_NUM_ELCONFIGS 13
#define USAC_MAX_NUM_ELEMENTS 5
#define USAC_NUM_FREQ_TABLES 6
#define USAC_NUM_SAMPLE_RATES (2 * AAC_NUM_SAMPLE_RATES)
#define RESTRICT_TO_AAC 0 // allow only AAC tool-set
#if RESTRICT_TO_AAC
# define LFE_MAX 12
#else
# define LFE_MAX 24
#endif
#ifndef __max
# define __max(a, b) ((a) > (b) ? (a) : (b))
#endif
#ifndef __min
# define __min(a, b) ((a) < (b) ? (a) : (b))
#endif
#ifndef CLIP_PM
# define CLIP_PM(x, clip) (__max (-clip, __min (+clip, x)))
#endif
#ifndef CLIP_UCHAR
# define CLIP_UCHAR(x) (__max (0, __min (UCHAR_MAX, x)))
#endif
#ifndef MFREE
# define MFREE(x) if (x != nullptr) { free ((void*) x); x = nullptr; }
#endif
// usacElementType[el] definition
typedef enum ELEM_TYPE : int8_t
{
ID_EL_UNDEF = -1,
ID_USAC_SCE = 0, // single-channel element (CCI_1_CH)
ID_USAC_CPE = 1, // channel-pair element (CCI_2_CH)
ID_USAC_LFE = 2, // low-frequency effects element (CCI_1_CH) with ONLY_LONG_SEQUENCE, no TNS
ID_USAC_EXT = 3 // extension element (not to be used here!)
} ELEM_TYPE;
// window_sequence defines for ICS
typedef enum USAC_WSEQ : uint8_t
{
ONLY_LONG = 0, // one symmetric long window .<2E>`.
LONG_START = 1, // asymmet. long-start window .<2E>|_
EIGHT_SHORT = 2, // 8 symmetric short windows _MM_
LONG_STOP = 3, // asymmet. long-stop window _|`.
STOP_START = 4 // symmet. stop-start window _||_
} USAC_WSEQ;
// window_shape definition for ICS
typedef enum USAC_WSHP : uint8_t
{
WINDOW_SINE = 0, // half-sine window = sin(pi*((0:L-1)'+0.5)/L)
WINDOW_KBD = 1 // Kaiser-Bessel derived (KBD) window by Dolby
} USAC_WSHP;
// ics_info(): channel data struct
struct IcsInfo
{
uint8_t maxSfb; // max_sfb(1)
uint8_t windowGrouping; // scale_factor_grouping (index)
USAC_WSEQ windowSequence; // window_sequence
USAC_WSHP windowShape; // window_shape
};
// tns_data(): channel data struct
struct TnsData
{
int8_t coeff[3][MAX_PREDICTION_ORDER];
int16_t coeffParCor[MAX_PREDICTION_ORDER];
bool coeffResLow; // means coef_res[w]=0
uint8_t filteredWindow; // filtered window w
bool filterDownward[3]; // direction[f]=1
uint8_t filterLength[3]; // filter length[f]
uint8_t filterOrder[3]; // filter order[f]
uint8_t numFilters; // n_filt for window w
};
// scale factor group. data struct
struct SfbGroupData
{
uint16_t numWindowGroups; // 1 | NUM_WINDOW_GROUPS, num_window_groups
uint16_t sfbOffsets[1+MAX_NUM_SWB_SHORT * NUM_WINDOW_GROUPS];
uint32_t sfbRmsValues[MAX_NUM_SWB_SHORT * NUM_WINDOW_GROUPS];
uint8_t scaleFactors[MAX_NUM_SWB_SHORT * NUM_WINDOW_GROUPS]; // sf[]
uint8_t sfbsPerGroup; // max_sfb(1) duplicate needed by BitAllocator
uint8_t windowGroupLength[NUM_WINDOW_GROUPS]; // window_group_length
};
// UsacCoreCoderData() data struct
struct CoreCoderData
{
bool commonMaxSfb; // common_max_sfb in StereoCoreToolInfo()
bool commonTnsData; // common_tns in StereoCoreToolInfo()
bool commonWindow; // common_window in StereoCoreToolInfo()
ELEM_TYPE elementType; // usacElementType in UsacDecoderConfig()
SfbGroupData groupingData[2]; // window grouping and SFB offset data
IcsInfo icsInfoCurr[2]; // current ics_info() for each channel
IcsInfo icsInfoPrev[2]; // previous ics_info() for each channel
#if !RESTRICT_TO_AAC
uint8_t specFillData[2]; // noise filling data for each channel
#endif
uint8_t stereoConfig; // cplx_pred_data() config: pred_dir etc.
char stereoData[MAX_NUM_SWB_SHORT * NUM_WINDOW_GROUPS];
uint8_t stereoMode; // ms_mask_present in StereoCoreToolInfo()
bool tnsActive; // tns_active flag in StereoCoreToolInfo()
TnsData tnsData[2]; // current tns_data() for each channel
bool tnsOnLeftRight; // tns_on_lr in StereoCoreToolInfo()
};
// bit-stream encoding data struct
struct OutputStream
{
uint8_t heldBitChunk; // bits not yet flushed to buffer
uint8_t heldBitCount; // number of bits not yet flushed
std::vector <uint8_t> stream; // FIFO bit-stream buffer
// constructor
OutputStream () { reset (); }
// destructor
~OutputStream() { stream.clear (); }
// public functions
void reset (); // clear writer states and byte buffer
void write (const uint32_t bitChunk, const uint8_t bitCount);
}; // OutputStream
// fast calculation of sqrt (256 - x): (4 + eightTimesSqrt256Minus[x]) >> 3, for 0 <= x <= 255
const uint8_t eightTimesSqrt256Minus[256] = {
128, 128, 127, 127, 127, 127, 126, 126, 126, 126, 125, 125, 125, 125, 124, 124, 124, 124, 123, 123, 123, 123, 122, 122, 122, 122,
121, 121, 121, 121, 120, 120, 120, 119, 119, 119, 119, 118, 118, 118, 118, 117, 117, 117, 116, 116, 116, 116, 115, 115, 115, 115,
114, 114, 114, 113, 113, 113, 113, 112, 112, 112, 111, 111, 111, 111, 110, 110, 110, 109, 109, 109, 109, 108, 108, 108, 107, 107,
107, 106, 106, 106, 106, 105, 105, 105, 104, 104, 104, 103, 103, 103, 102, 102, 102, 102, 101, 101, 101, 100, 100, 100, 99, 99,
99, 98, 98, 98, 97, 97, 97, 96, 96, 96, 95, 95, 95, 94, 94, 94, 93, 93, 93, 92, 92, 92, 91, 91, 91, 90,
90, 89, 89, 89, 88, 88, 88, 87, 87, 87, 86, 86, 85, 85, 85, 84, 84, 84, 83, 83, 82, 82, 82, 81, 81, 80,
80, 80, 79, 79, 78, 78, 78, 77, 77, 76, 76, 75, 75, 75, 74, 74, 73, 73, 72, 72, 72, 71, 71, 70, 70, 69,
69, 68, 68, 67, 67, 66, 66, 65, 65, 64, 64, 63, 63, 62, 62, 61, 61, 60, 60, 59, 59, 58, 58, 57, 57, 56,
55, 55, 54, 54, 53, 52, 52, 51, 51, 50, 49, 49, 48, 47, 47, 46, 45, 45, 44, 43, 42, 42, 41, 40, 39, 38,
38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 25, 24, 23, 21, 20, 18, 16, 14, 11, 8
};
// fast calculation of x / den: (x * oneTwentyEightOver[den]) >> 7, accurate for 0 <= x <= 162
const uint8_t oneTwentyEightOver[14] = {0, 128, 64, 43, 32, 26, 22, 19, 16, 15, 13, 12, 11, 10};
// public sampling rate functions
char toSamplingFrequencyIndex (const unsigned samplingRate);
unsigned toSamplingRate (const char samplingFrequencyIndex);
#endif // _EXHALE_LIB_PCH_H_

View File

@ -0,0 +1,188 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{EC0D1501-2018-1700-4865-6C6D72696368}</ProjectGuid>
<ProjectName>exhaleLib</ProjectName>
<RootNamespace>exhaleLib</RootNamespace>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<PlatformToolset>v110</PlatformToolset>
<UseDebugLibraries>true</UseDebugLibraries>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<PlatformToolset>v110</PlatformToolset>
<UseDebugLibraries>true</UseDebugLibraries>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<PlatformToolset>v110</PlatformToolset>
<UseDebugLibraries>false</UseDebugLibraries>
<WholeProgramOptimization>true</WholeProgramOptimization>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<PlatformToolset>v110</PlatformToolset>
<UseDebugLibraries>false</UseDebugLibraries>
<WholeProgramOptimization>true</WholeProgramOptimization>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings" />
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<IntDir>$(SolutionDir)build\$(PlatformToolset)\$(Platform)\$(Configuration)\</IntDir>
<OutDir>$(SolutionDir)lib\$(PlatformToolset)\$(Platform)\$(Configuration)\</OutDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<IntDir>$(SolutionDir)build\$(PlatformToolset)\$(Platform)\$(Configuration)\</IntDir>
<OutDir>$(SolutionDir)lib\$(PlatformToolset)\$(Platform)\$(Configuration)\</OutDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<LinkIncremental>false</LinkIncremental>
<IntDir>$(SolutionDir)build\$(PlatformToolset)\$(Platform)\$(Configuration)\</IntDir>
<OutDir>$(SolutionDir)lib\$(PlatformToolset)\$(Platform)\$(Configuration)\</OutDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LinkIncremental>false</LinkIncremental>
<IntDir>$(SolutionDir)build\$(PlatformToolset)\$(Platform)\$(Configuration)\</IntDir>
<OutDir>$(SolutionDir)lib\$(PlatformToolset)\$(Platform)\$(Configuration)\</OutDir>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<AdditionalIncludeDirectories>$(SolutionDir)include</AdditionalIncludeDirectories>
<Optimization>Disabled</Optimization>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>exhaleLibPch.h</PrecompiledHeaderFile>
<PreprocessorDefinitions>WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<TreatWarningAsError>true</TreatWarningAsError>
<WarningLevel>Level3</WarningLevel>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
<SubSystem>Windows</SubSystem>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<AdditionalIncludeDirectories>$(SolutionDir)include</AdditionalIncludeDirectories>
<Optimization>Disabled</Optimization>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>exhaleLibPch.h</PrecompiledHeaderFile>
<PreprocessorDefinitions>WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<TreatWarningAsError>true</TreatWarningAsError>
<WarningLevel>Level3</WarningLevel>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
<SubSystem>Windows</SubSystem>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<AdditionalIncludeDirectories>$(SolutionDir)include</AdditionalIncludeDirectories>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<Optimization>MaxSpeed</Optimization>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>exhaleLibPch.h</PrecompiledHeaderFile>
<PreprocessorDefinitions>WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<TreatWarningAsError>true</TreatWarningAsError>
<WarningLevel>Level3</WarningLevel>
</ClCompile>
<Link>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<GenerateDebugInformation>false</GenerateDebugInformation>
<OptimizeReferences>true</OptimizeReferences>
<SubSystem>Windows</SubSystem>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<AdditionalIncludeDirectories>$(SolutionDir)include</AdditionalIncludeDirectories>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<Optimization>MaxSpeed</Optimization>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>exhaleLibPch.h</PrecompiledHeaderFile>
<PreprocessorDefinitions>WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<TreatWarningAsError>true</TreatWarningAsError>
<WarningLevel>Level3</WarningLevel>
</ClCompile>
<Link>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<GenerateDebugInformation>false</GenerateDebugInformation>
<OptimizeReferences>true</OptimizeReferences>
<SubSystem>Windows</SubSystem>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="..\..\include\exhaleDecl.h" />
<ClInclude Include="..\..\include\version.h" />
<ClInclude Include="bitAllocation.h" />
<ClInclude Include="bitStreamWriter.h" />
<ClInclude Include="entropyCoding.h" />
<ClInclude Include="exhaleEnc.h" />
<ClInclude Include="exhaleLibPch.h" />
<ClInclude Include="lappedTransform.h" />
<ClInclude Include="linearPrediction.h" />
<ClInclude Include="quantization.h" />
<ClInclude Include="specAnalysis.h" />
<ClInclude Include="specGapFilling.h" />
<ClInclude Include="tempAnalysis.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="bitAllocation.cpp" />
<ClCompile Include="bitStreamWriter.cpp" />
<ClCompile Include="entropyCoding.cpp" />
<ClCompile Include="exhaleEnc.cpp" />
<ClCompile Include="exhaleLibPch.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="lappedTransform.cpp" />
<ClCompile Include="linearPrediction.cpp" />
<ClCompile Include="quantization.cpp" />
<ClCompile Include="specAnalysis.cpp" />
<ClCompile Include="specGapFilling.cpp" />
<ClCompile Include="tempAnalysis.cpp" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
</Project>

View File

@ -0,0 +1,93 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Header Files">
<UniqueIdentifier>{EC0D1503-2018-1700-4865-6C6D72696368}</UniqueIdentifier>
<Extensions>h;hpp</Extensions>
</Filter>
<Filter Include="Source Files">
<UniqueIdentifier>{EC0D1504-2018-1700-4865-6C6D72696368}</UniqueIdentifier>
<Extensions>cpp;c;asm;asmx</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{EC0D1505-2018-1700-4865-6C6D72696368}</UniqueIdentifier>
<Extensions>rc;ico</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\..\include\exhaleDecl.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\include\version.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="bitAllocation.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="bitStreamWriter.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="entropyCoding.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="exhaleEnc.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="exhaleLibPch.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="lappedTransform.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="linearPrediction.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="quantization.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="specAnalysis.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="specGapFilling.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="tempAnalysis.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="bitAllocation.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="bitStreamWriter.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="entropyCoding.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="exhaleEnc.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="exhaleLibPch.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="lappedTransform.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="linearPrediction.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="quantization.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="specAnalysis.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="specGapFilling.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="tempAnalysis.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
</Project>

431
src/lib/lappedTransform.cpp Normal file
View File

@ -0,0 +1,431 @@
/* lappedTransform.cpp - source file for class providing time-frequency transformation
* written by C. R. Helmrich, last modified in 2019 - 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 "exhaleLibPch.h"
#include "lappedTransform.h"
#ifdef _MSC_VER
# include <intrin.h> // _BitScanReverse
# pragma intrinsic (_BitScanReverse)
#endif
// static helper functions
static short* createPermutTable (const short tableSize)
{
const short lOver2 = tableSize >> 1;
short* permutTable = nullptr;
short i = 0;
if ((permutTable = (short*) malloc (tableSize * sizeof (short))) == nullptr)
{
return nullptr; // allocation error
}
permutTable[0] = 0;
for (short s = 1; s < tableSize; s++)
{
short l = lOver2;
while (i >= l)
{
i -= l;
l >>= 1;
}
permutTable[s] = (i += l);
}
return permutTable;
}
static inline int shortIntLog2 (uint16_t s)
{
#ifdef _MSC_VER
unsigned long l;
_BitScanReverse (&l, s);
return (int) l;
#else
// fast base-2 integer logarithm by Todd Lehman (Stack Overflow), July 2014
int i = 0;
# define S(k) if (s >= (1u << k)) { i += k; s >>= k; }
S (8); S (4); S (2); S (1);
# undef S
return i;
#endif
}
// private helper functions
void LappedTransform::applyHalfSizeFFT (int32_t* const iR/*eal*/, int32_t* const iI/*mag*/, const bool shortTransform) // works in-place
{
// int32 FFT version based on http://paulbourke.net/miscellaneous/dft, 1993
const int l = (shortTransform ? m_transfLengthS : m_transfLengthL) >> 1;
const short* p = (shortTransform ? m_fftPermutS : m_fftPermutL); // look-up
int l2 = 1, l3 = m_transfLengthL >> 1;
if (iR == nullptr)
{
return; // null-pointer input error
}
// sort input with permutation look-up table
if (iI != nullptr)
{
for (int i = l - 1; i >= 0; i--)
{
const int j = p[i];
if (j > i) // swap input data at i and j
{
const int32_t iRTmp = iR[i]; // use re-
const int32_t iITmp = iI[i]; // gisters
iR[i] = iR[j];
iI[i] = iI[j];
iR[j] = iRTmp;
iI[j] = iITmp;
}
}
}
else // real-valued input, no imaginary part
{
for (int i = l - 1; i >= 0; i--)
{
const int j = p[i];
if (j > i) // swap real input at i and j
{
const int32_t iRTmp = iR[i];
iR[i] = iR[j];
iR[j] = iRTmp;
}
iI[i] = 0; // zero imaginary input data
}
}
// get length-l Fast Fourier Transform (FFT)
for (int k = shortIntLog2 ((uint16_t) l) - 1; k >= 0; k--)
{
const int l1 = l2;
l2 <<= 1;
l3 >>= 1;
for (int j = l1 - 1; j >= 0; j--)
{
const int jTl3 = j * l3;
const int64_t cosjl3 = m_fftHalfCos[jTl3]; // cos/sin
const int64_t sinjl3 = m_fftHalfSin[jTl3]; // look-up
for (int i = j; i < l; i += l2)
{
const int iPl1 = i + l1;
const int32_t rotR = int32_t ((cosjl3 * iR[iPl1] + sinjl3 * iI[iPl1] + LUT_OFFSET) >> LUT_SHIFT); // clockwise
const int32_t rotI = int32_t ((cosjl3 * iI[iPl1] - sinjl3 * iR[iPl1] + LUT_OFFSET) >> LUT_SHIFT); // rotation
iR[iPl1] = iR[i] + rotR; iR[i] -= rotR;
iI[iPl1] = iI[i] + rotI; iI[i] -= rotI;
}
}
}
}
void LappedTransform::windowAndFoldInL (const int32_t* inputL, const bool shortTransform, const bool kbdWindowL, const bool lowOverlapL,
const bool mdstKernel, int32_t* const output)
{
const unsigned ws = (kbdWindowL ? 1 : 0); // shape
const int32_t* wl = (lowOverlapL ? m_timeWindowS[ws] : m_timeWindowL[ws]);
const int Mo2 = (shortTransform ? m_transfLengthS : m_transfLengthL) >> 1;
const int Mm1 = Mo2 * 2 - 1;
const int Mo2m1 = Mo2 - 1;
const int Mo2mO = (lowOverlapL ? Mo2 - (m_transfLengthS >> 1) : 0);
const int Mm1mO = Mm1 - Mo2mO; // overlap offset
int n;
if (mdstKernel) // time-reversal and TDA sign flip
{
for (n = Mo2m1; n >= Mo2mO; n--) // windowed pt.
{
const int64_t i64 = (int64_t) inputL[Mm1 - n] * wl[Mm1mO - n] + (int64_t) inputL[n] * wl[n - Mo2mO];
output[Mo2m1 - n] = int32_t ((i64 + WIN_OFFSET) >> WIN_SHIFT);
}
for (/*Mo2mO-1*/; n >= 0; n--) // unwindowed pt.
{
output[Mo2m1 - n] = (inputL[Mm1 - n] + 2) >> 2;
}
}
else // MDCT kernel, no time-reversal or sign flip
{
for (n = Mo2m1; n >= Mo2mO; n--) // windowed pt.
{
const int64_t i64 = (int64_t) inputL[Mm1 - n] * wl[Mm1mO - n] - (int64_t) inputL[n] * wl[n - Mo2mO];
output[Mo2 + n] = int32_t ((i64 + WIN_OFFSET) >> WIN_SHIFT);
}
for (/*Mo2mO-1*/; n >= 0; n--) // unwindowed pt.
{
output[Mo2 + n] = (inputL[Mm1 - n] + 2) >> 2;
}
}
}
void LappedTransform::windowAndFoldInR (const int32_t* inputR, const bool shortTransform, const bool kbdWindowR, const bool lowOverlapR,
const bool mdstKernel, int32_t* const output)
{
const unsigned ws = (kbdWindowR ? 1 : 0); // shape
const int32_t* wr = (lowOverlapR ? m_timeWindowS[ws] : m_timeWindowL[ws]);
const int Mo2 = (shortTransform ? m_transfLengthS : m_transfLengthL) >> 1;
const int Mm1 = Mo2 * 2 - 1;
const int Mo2m1 = Mo2 - 1;
const int Mo2mO = (lowOverlapR ? Mo2 - (m_transfLengthS >> 1) : 0);
const int Mm1mO = Mm1 - Mo2mO; // overlap offset
int n;
if (mdstKernel) // time-reversal and TDA sign flip
{
for (n = Mo2m1; n >= Mo2mO; n--) // windowed pt.
{
const int64_t i64 = (int64_t) inputR[n] * wr[Mm1mO - n] - (int64_t) inputR[Mm1 - n] * wr[n - Mo2mO];
output[Mo2 + n] = int32_t ((i64 + WIN_OFFSET) >> WIN_SHIFT);
}
for (/*Mo2mO-1*/; n >= 0; n--) // unwindowed pt.
{
output[Mo2 + n] = (inputR[n] + 2) >> 2;
}
}
else // MDCT kernel, no time-reversal or sign flip
{
for (n = Mo2m1; n >= Mo2mO; n--) // windowed pt.
{
const int64_t i64 = (int64_t) inputR[n] * wr[Mm1mO - n] + (int64_t) inputR[Mm1 - n] * wr[n - Mo2mO];
output[Mo2m1 - n] = int32_t ((i64 + WIN_OFFSET) >> WIN_SHIFT);
}
for (/*Mo2mO-1*/; n >= 0; n--) // unwindowed pt.
{
output[Mo2m1 - n] = (inputR[n] + 2) >> 2;
}
}
}
// constructor
LappedTransform::LappedTransform ()
{
// initialize all helper buffers
m_dctRotCosL = nullptr;
m_dctRotCosS = nullptr;
m_dctRotSinL = nullptr;
m_dctRotSinS = nullptr;
m_fftHalfCos = nullptr;
m_fftHalfSin = nullptr;
m_fftPermutL = nullptr;
m_fftPermutS = nullptr;
m_tempIntBuf = nullptr;
// initialize all window buffers
for (short s = 0; s < 2; s++)
{
m_timeWindowL[s] = nullptr;
m_timeWindowS[s] = nullptr;
}
m_transfLengthL = 0;
m_transfLengthS = 0;
}
// destructor
LappedTransform::~LappedTransform ()
{
// free allocated helper buffers
MFREE (m_dctRotCosL);
MFREE (m_dctRotCosS);
MFREE (m_dctRotSinL);
MFREE (m_dctRotSinS);
MFREE (m_fftHalfCos);
MFREE (m_fftHalfSin);
MFREE (m_fftPermutL);
MFREE (m_fftPermutS);
m_tempIntBuf = nullptr;
}
// public functions
unsigned LappedTransform::applyNegDCT4 (int32_t* const signal, const bool shortTransform) // works in-place
{
// int32 negative-DCT-IV version based on http://www.ee.columbia.edu/~marios/mdct/mdct_giraffe.html, 2003
// NOTE: amplifies short-transform results (8 times shorter than long-transform results) by a factor of 8
const int lm1 = (shortTransform ? m_transfLengthS : m_transfLengthL) - 1;
const int lm1o2 = lm1 >> 1;
const int32_t* rotatCos = (shortTransform ? m_dctRotCosS : m_dctRotCosL);
const int32_t* rotatSin = (shortTransform ? m_dctRotSinS : m_dctRotSinL);
const int64_t rotOffset = (shortTransform ? LUT_OFFSET>>3 : LUT_OFFSET);
const int64_t rotShift = (shortTransform ? LUT_SHIFT - 3 : LUT_SHIFT);
int32_t* const tempReal = m_tempIntBuf;
int32_t* const tempImag = &m_tempIntBuf[lm1o2 + 1];
int i, i2;
if (signal == nullptr)
{
return 1; // null-pointer input error
}
// resort, separate, pre-twiddle signal
for (i = lm1o2, i2 = lm1 - 1; i >= 0; i--, i2 -= 2)
{
const int64_t c = rotatCos[i];
const int64_t s = rotatSin[i];
const int64_t e = signal[i2/*even*/];
const int64_t o = signal[lm1 - i2];
tempReal[i] = int32_t ((rotOffset + e * c - o * s) >> rotShift);
tempImag[i] = int32_t ((rotOffset + o * c + e * s) >> rotShift);
}
applyHalfSizeFFT (tempReal, tempImag, shortTransform);
// post-twiddle, combine, resort output
for (i = lm1o2, i2 = lm1 - 1; i >= 0; i--, i2 -= 2)
{
const int64_t c = rotatCos[i];
const int64_t s = rotatSin[i];
const int64_t e = tempReal[i];
const int64_t o = tempImag[i];
signal[i2/*even*/] = int32_t ((LUT_OFFSET + o * s - e * c) >> LUT_SHIFT);
signal[lm1 - i2] = int32_t ((LUT_OFFSET + e * s + o * c) >> LUT_SHIFT);
}
return 0; // no error
}
unsigned LappedTransform::applyMCLT (const int32_t* timeSig, const bool eightTransforms, bool kbdWindowL, const bool kbdWindowR,
const bool lowOverlapL, const bool lowOverlapR, int32_t* const outMdct, int32_t* const outMdst)
{
if ((timeSig == nullptr) || (outMdct == nullptr) || (outMdst == nullptr))
{
return 1; // invalid arguments error
}
if (eightTransforms) // short windows
{
const int32_t* tSigS = &timeSig[(m_transfLengthL - m_transfLengthS) >> 1];
int32_t* outMdctS = outMdct;
int32_t* outMdstS = outMdst;
for (unsigned w = 0; w < 8; w++)
{
windowAndFoldInL (tSigS /*window half 1*/, true, kbdWindowL, lowOverlapL, false, outMdctS);
windowAndFoldInR (&tSigS[m_transfLengthS], true, kbdWindowR, lowOverlapR, false, outMdctS);
windowAndFoldInL (tSigS /*window half 1*/, true, kbdWindowL, lowOverlapL, true, outMdstS);
windowAndFoldInR (&tSigS[m_transfLengthS], true, kbdWindowR, lowOverlapR, true, outMdstS);
// MDCT via DCT-IV
applyNegDCT4 (outMdctS, true);
// MDST via DCT-IV
applyNegDCT4 (outMdstS, true);
#if 1 // not needed here
for (int i = m_transfLengthS - 2; i >= 0; i -= 2)
{
outMdstS[i] *= -1; // DCT to DST
}
#endif
kbdWindowL = kbdWindowR; // only first window uses last frame's shape
tSigS += m_transfLengthS;
outMdctS += m_transfLengthS;
outMdstS += m_transfLengthS;
}
}
else // 1 long window
{
windowAndFoldInL (timeSig /*window half 1*/, false, kbdWindowL, lowOverlapL, false, outMdct);
windowAndFoldInR (&timeSig[m_transfLengthL], false, kbdWindowR, lowOverlapR, false, outMdct);
windowAndFoldInL (timeSig /*window half 1*/, false, kbdWindowL, lowOverlapL, true, outMdst);
windowAndFoldInR (&timeSig[m_transfLengthL], false, kbdWindowR, lowOverlapR, true, outMdst);
// MDCT using DCT-IV
applyNegDCT4 (outMdct, false);
// MDST using DCT-IV
applyNegDCT4 (outMdst, false);
#if 1 // not needed here
for (int i = m_transfLengthL - 2; i >= 0; i -= 2)
{
outMdst[i] *= -1; // DCT to DST
}
#endif
}
return 0; // no error
}
unsigned LappedTransform::initConstants (int32_t* const tempIntBuf, int32_t* const timeWindowL[2], int32_t* const timeWindowS[2],
const unsigned maxTransfLength)
{
const short halfLength = short (maxTransfLength >> 1);
const short sixtLength = short (maxTransfLength >> 4);
const double dNormL = 3.141592653589793 / (2.0 * halfLength);
const double dNormS = 3.141592653589793 / (2.0 * sixtLength);
const double dNormL4 = dNormL * 4.0;
short s;
if ((tempIntBuf == nullptr) || (timeWindowL == nullptr) || (timeWindowS == nullptr) ||
(maxTransfLength < 128) || (maxTransfLength > 8192) || (maxTransfLength & (maxTransfLength - 1)))
{
return 1; // invalid arguments error
}
m_transfLengthL = 2 * halfLength;
m_transfLengthS = 2 * sixtLength;
if ((m_dctRotCosL = (int32_t*) malloc (halfLength * sizeof (int32_t))) == nullptr ||
(m_dctRotCosS = (int32_t*) malloc (sixtLength * sizeof (int32_t))) == nullptr ||
(m_dctRotSinL = (int32_t*) malloc (halfLength * sizeof (int32_t))) == nullptr ||
(m_dctRotSinS = (int32_t*) malloc (sixtLength * sizeof (int32_t))) == nullptr ||
(m_fftHalfCos = (int32_t*) malloc ((halfLength >> 1) * sizeof (int32_t))) == nullptr ||
(m_fftHalfSin = (int32_t*) malloc ((halfLength >> 1) * sizeof (int32_t))) == nullptr ||
(m_fftPermutL = createPermutTable (halfLength)) == nullptr ||
(m_fftPermutS = createPermutTable (sixtLength)) == nullptr)
{
return 2; // memory allocation error
}
// obtain cosine and sine coefficients
for (s = 0; s < halfLength; s++)
{
m_dctRotCosL[s] = int32_t (cos (dNormL * (s + 0.125)) * (INT_MAX + 1.0) + 0.5);
m_dctRotSinL[s] = int32_t (sin (dNormL * (s + 0.125)) * INT_MIN - 0.5);
}
for (s = 0; s < sixtLength; s++)
{
m_dctRotCosS[s] = int32_t (cos (dNormS * (s + 0.125)) * (INT_MAX + 1.0) + 0.5);
m_dctRotSinS[s] = int32_t (sin (dNormS * (s + 0.125)) * INT_MIN - 0.5);
}
for (s = 0; s < m_transfLengthS; s++)
{
m_fftHalfSin[s] = int32_t (sin (dNormL4 * s) * INT_MIN - 0.5);
m_fftHalfCos[m_transfLengthS + s] = -m_fftHalfSin[s];
}
// complete missing entries by copying
m_fftHalfSin[s] = INT_MIN;
m_fftHalfCos[0] = INT_MIN;
for (s = 1; s < m_transfLengthS; s++)
{
m_fftHalfSin[m_transfLengthS + s] = m_fftHalfSin[m_transfLengthS - s];
m_fftHalfCos[m_transfLengthS - s] = m_fftHalfSin[s];
}
// adopt helper/window buffer pointers
m_tempIntBuf = tempIntBuf;
for (s = 0; s < 2; s++)
{
m_timeWindowL[s] = timeWindowL[s];
m_timeWindowS[s] = timeWindowS[s];
}
return 0; // no error
}

63
src/lib/lappedTransform.h Normal file
View File

@ -0,0 +1,63 @@
/* lappedTransform.h - header file for class providing time-frequency transformation
* written by C. R. Helmrich, last modified in 2019 - 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.
*/
#ifndef _LAPPED_TRANSFORM_H_
#define _LAPPED_TRANSFORM_H_
#include "exhaleLibPch.h"
// constants, experimental macros
#define LUT_OFFSET (1 << 30)
#define LUT_SHIFT 31
#define WIN_OFFSET (1 << 24)
#define WIN_SHIFT 25
// time-frequency transform class
class LappedTransform
{
private:
// member variables
int32_t* m_dctRotCosL;
int32_t* m_dctRotCosS;
int32_t* m_dctRotSinL;
int32_t* m_dctRotSinS;
int32_t* m_fftHalfCos;
int32_t* m_fftHalfSin;
short* m_fftPermutL;
short* m_fftPermutS;
int32_t* m_tempIntBuf; // pointer to temporary helper buffer
int32_t* m_timeWindowL[2]; // pointer to two long window halves
int32_t* m_timeWindowS[2]; // pointer to two short window halves
short m_transfLengthL;
short m_transfLengthS;
// helper functions
void applyHalfSizeFFT (int32_t* const iR/*eal*/, int32_t* const iI/*mag*/, const bool shortTransform);
void windowAndFoldInL (const int32_t* inputL, const bool shortTransform, const bool kbdWindowL, const bool lowOverlapL,
const bool mdstKernel, int32_t* const output);
void windowAndFoldInR (const int32_t* inputR, const bool shortTransform, const bool kbdWindowR, const bool lowOverlapR,
const bool mdstKernel, int32_t* const output);
public:
// constructor
LappedTransform ();
// destructor
~LappedTransform ();
// public functions
unsigned applyNegDCT4 (int32_t* const signal, const bool shortTransform);
unsigned applyMCLT (const int32_t* timeSig, const bool eightTransforms, bool kbdWindowL, const bool kbdWindowR,
const bool lowOverlapL, const bool lowOverlapR, int32_t* const outMdct, int32_t* const outMdst);
unsigned initConstants (int32_t* const tempIntBuf, int32_t* const timeWindowL[2], int32_t* const timeWindowS[2],
const unsigned maxTransfLength);
}; // LappedTransform
#endif // _LAPPED_TRANSFORM_H_

View File

@ -0,0 +1,421 @@
/* linearPrediction.cpp - source file for class providing linear prediction capability
* written by C. R. Helmrich, last modified in 2019 - 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 "exhaleLibPch.h"
#include "linearPrediction.h"
// reconstructed 3-bit TNS coefficients
static const short tnsQuantCoeff3[9 /*2^3+1*/] = { // = round (2^11 * sin (x * pi / (x < 0 ? 9 : 7)))
-2017, -1774, -1316, -700, 0, 889, 1601, 1997, 1997
};
// reconstructed 4-bit TNS coefficients
static const short tnsQuantCoeff4[17/*2^4+1*/] = { // = round (2^11 * sin (x * pi / (x < 0 ? 17 : 15)))
-2039, -1970, -1833, -1634, -1380, -1078, -740, -376, 0, 426, 833, 1204, 1522, 1774, 1948, 2037, 2037
};
static const short* tnsQuantCoeff[2/*coefRes*/] = {tnsQuantCoeff3, tnsQuantCoeff4};
// ISO/IEC 14496-3, Sec. 4.6.9.3, 3-bit
static const char tnsQuantIndex3[SCHAR_MAX + 1] = { // = round (asin (x / 64) * (x < 0 ? 9 : 7) / pi)
-4, -4, -4, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3
};
// ISO/IEC 14496-3, Sec. 4.6.9.3, 4-bit
static const char tnsQuantIndex4[SCHAR_MAX + 1] = { // = round (asin (x / 64) * (x < 0 ? 17 : 15) / pi)
-8, -7, -7, -7, -6, -6, -6, -6, -6, -5, -5, -5, -5, -5, -5, -5, -4, -4, -4, -4, -4, -4, -4, -4, -4, -3, -3, -3, -3, -3, -3, -3,
-3, -3, -3, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7
};
static const char* tnsQuantIndex[2/*coefRes*/] = {tnsQuantIndex3, tnsQuantIndex4};
// static helper functions
static int quantizeParCorCoeffs (const short* const parCorCoeffs, const uint16_t nCoeffs, const short bitDepth, int8_t* const quantCoeffs,
const bool lowRes)
{
const short bitShift = bitDepth - 7;
const unsigned tabIdx = (lowRes ? 0 : 1);
const char tabOffset = 4 << tabIdx;
const short* coeffTab = tnsQuantCoeff[tabIdx];
const char* indexTab = tnsQuantIndex[tabIdx];
int dist0, dist1, distTotal = 0;
for (uint16_t s = 0; s < nCoeffs; s++)
{
const short coeff = (bitShift < 0 ? parCorCoeffs[s] << -bitShift : parCorCoeffs[s] >> bitShift);
const char coeff1 = indexTab[coeff + 1 + (SCHAR_MAX >> 1)];
const char coeff0 = (coeff1 <= -tabOffset ? coeff1 : coeff1 - 1);
dist0 = (int) coeffTab[coeff0 + tabOffset] - parCorCoeffs[s];
dist0 *= dist0;
dist1 = (int) coeffTab[coeff1 + tabOffset] - parCorCoeffs[s];
dist1 *= dist1;
if (dist0 < dist1) // use down-rounded coeff
{
quantCoeffs[s] = coeff0;
distTotal += dist0;
}
else // dist0 >= dist1, use up-rounded coeff
{
quantCoeffs[s] = ((dist0 == dist1) && (abs (coeff0) < abs (coeff1)) ? coeff0 : coeff1);
distTotal += dist1;
}
} // for s
return distTotal; // total quantization error
}
// constructor
LinearPredictor::LinearPredictor ()
{
// initialize member variables
memset (m_tempBuf, 0, 2 * MAX_PREDICTION_ORDER * sizeof (int64_t));
}
// public functions
uint32_t LinearPredictor::calcParCorCoeffs (const int32_t* const anaSignal, const uint16_t nAnaSamples, const uint16_t nCoeffs,
short* const parCorCoeffs) // returns 256 - 256 / prediction gain per filter order, or 0
{
// int64 version of algorithm in Figure 1 of J. LeRoux and C. Gueguen, "A Fixed Point Computation
// of Partial Correlation Coefficients," IEEE Trans. ASSP, vol. 27, no. 3, pp. 257-259, June 1977.
int64_t* const acf = m_tempBuf; // correlation
uint32_t pg[MAX_PREDICTION_ORDER] = {0, 0, 0, 0};
int64_t pgDen, pgOff; // for prediction gains
short s = nAnaSamples - 1;
if ((anaSignal == nullptr) || (parCorCoeffs == nullptr) || (nCoeffs == 0) || (nCoeffs > MAX_PREDICTION_ORDER) || (nAnaSamples <= nCoeffs))
{
return 0;
}
if (nCoeffs >= 2) // high predictor order, 2-4
{
int64_t* const EN = &m_tempBuf[0];
int64_t* const EP = &m_tempBuf[MAX_PREDICTION_ORDER];
int64_t sampleHO = anaSignal[s];
// calculate autocorrelation function values
acf[0] = sampleHO * sampleHO;
sampleHO = anaSignal[--s];
acf[0] += sampleHO * sampleHO;
acf[1] = sampleHO * anaSignal[s + 1];
sampleHO = anaSignal[--s];
acf[0] += sampleHO * sampleHO;
acf[1] += sampleHO * anaSignal[s + 1];
acf[2] = sampleHO * anaSignal[s + 2];
if (nCoeffs == 2)
{
for (s--; s >= 0; s--)
{
const int64_t sample = anaSignal[s];
acf[0] += sample * sample;
acf[1] += sample * anaSignal[s + 1];
acf[2] += sample * anaSignal[s + 2];
}
}
else // order 3-4
{
sampleHO = anaSignal[--s];
acf[0] += sampleHO * sampleHO;
acf[1] += sampleHO * anaSignal[s + 1];
acf[2] += sampleHO * anaSignal[s + 2];
acf[3] = sampleHO * anaSignal[s + 3];
if (nCoeffs == 3)
{
for (s--; s >= 0; s--)
{
const int64_t sample = anaSignal[s];
acf[0] += sample * sample;
acf[1] += sample * anaSignal[s + 1];
acf[2] += sample * anaSignal[s + 2];
acf[3] += sample * anaSignal[s + 3];
}
}
else // order 4
{
acf[4] = 0;
for (s--; s >= 0; s--)
{
const int64_t sample = anaSignal[s];
acf[0] += sample * sample;
acf[1] += sample * anaSignal[s + 1];
acf[2] += sample * anaSignal[s + 2];
acf[3] += sample * anaSignal[s + 3];
acf[4] += sample * anaSignal[s + 4];
}
}
}
// reduce correlation value range to <32 bit
acf[0] = (acf[0] - INT_MIN/*eps*/) >> 31;
EP[3] = (acf[4] - (INT_MIN >> 1)) >> 31;
EN[3] = (acf[3] - (INT_MIN >> 1)) >> 31;
EP[2] = EN[3];
EN[2] = (acf[2] - (INT_MIN >> 1)) >> 31;
EP[1] = EN[2];
EN[1] = (acf[1] - (INT_MIN >> 1)) >> 31;
EP[0] = EN[1]; // finish EP buffer creation
EN[0] = acf[0]; // finish EN buffer creation
pgOff = acf[0];
pgDen = pgOff << 1;
for (s = 0; s < (short) nCoeffs; s++)
{
uint16_t p;
// ParCor coefficient Ks & prediction gain
int64_t Ks = (EP[s] * -512) / (EN[0] + LP_EPS);
Ks = CLIP_PM (Ks, 511); // enforce |Ks|<1
parCorCoeffs[s] = (short) Ks;
for (p = s; p < nCoeffs; p++)
{
sampleHO = EN[p - s]; // use register?
EN[p - s] = (EN[p - s] << 9) + EP[p] * Ks;
EP[p] = (EP[p] << 9) + sampleHO * Ks;
}
if (s > 0 && EN[0] < 0) // EN wrap-around
{
pg[s] = pg[s - 1]; // "invent" some gain
}
else
{
sampleHO = (1 << (s * 9)) >> 1;// offset
pg[s] = (pgOff <= LP_EPS ? 0 : (1 << 8) - uint32_t ((((EN[0] + sampleHO) >> (s * 9)) + pgOff) / pgDen));
}
} // for s
}
else // nCoeffs == 1, minimum predictor order
{
int64_t sampleMO = anaSignal[s];
acf[0] = sampleMO * sampleMO;
acf[1] = 0;
for (s--; s >= 0; s--)
{
const int64_t sample = anaSignal[s];
acf[0] += sample * sample;
acf[1] += sample * anaSignal[s + 1];
}
// reduce correlation value range to <32 bit
acf[0] = (acf[0] - INT_MIN/*eps*/) >> 31;
acf[1] = (acf[1] - (INT_MIN >> 1)) >> 31;
pgOff = acf[0];
pgDen = pgOff << 1;
// 1st-order coefficient K & prediction gain
sampleMO = (acf[1] * -512) / (acf[0] + LP_EPS);
sampleMO = CLIP_PM (sampleMO, 511); // |K|<1
parCorCoeffs[0] = (short) sampleMO;
sampleMO = (acf[0] << 9) + acf[1] * sampleMO;
pg[0] = (pgOff <= LP_EPS ? 0 : (1 << 8) - uint32_t ((sampleMO + pgOff) / pgDen));
}
return (CLIP_UCHAR (pg[3]) << 24) | (CLIP_UCHAR (pg[2]) << 16) | (CLIP_UCHAR (pg[1]) << 8) | CLIP_UCHAR (pg[0]);
}
uint8_t LinearPredictor::calcOptTnsCoeffs (short* const parCorCoeffs, int8_t* const quantCoeffs, bool* const lowCoeffRes,
const uint16_t maxOrder, const uint8_t predGain, const uint8_t tonality /*= 0*/,
const uint16_t parCorCoeffBitDepth /*= 10*/) // returns optimized filter order for TNS
{
const short bitShift = LP_DEPTH - (short) parCorCoeffBitDepth;
short shortBuf[MAX_PREDICTION_ORDER];
uint16_t s, order = __min (maxOrder, MAX_PREDICTION_ORDER);
int d, i;
if ((parCorCoeffs == nullptr) || (quantCoeffs == nullptr) || (maxOrder == 0) || (maxOrder > MAX_PREDICTION_ORDER) || (parCorCoeffBitDepth < 2) || (bitShift < 0))
{
if (quantCoeffs) memset (quantCoeffs, 0, order * sizeof (char));
return 0; // invalid input arguments error
}
// determine direct-form filter damping factor
parCorCoeffs[0] <<= bitShift;
i = abs (parCorCoeffs[0]);
for (s = 1; s < order; s++)
{
parCorCoeffs[s] <<= bitShift; // scale coeff
i = __max (i, abs (parCorCoeffs[s]));
}
for (/*s*/; s < MAX_PREDICTION_ORDER; s++)
{
parCorCoeffs[s] = 0; // similarParCorCoeffs
}
if (predGain < 41 + (tonality >> 3)) // 1.5 dB
{
memset (quantCoeffs, 0, order * sizeof (char));
return 0; // LPC prediction gain is too low
}
d = (7 << (LP_DEPTH - 1)) >> 3;
if (i > d) // apply direct-form filter damping
{
i = ((i >> 1) + d * (1 << LP_SHIFT)) / i;
d = i;
if (parCorToLpCoeffs (parCorCoeffs, order, shortBuf, LP_DEPTH) > 0)
{
return 0; // coefficient conversion error
}
for (s = 0; s < order; s++)
{
shortBuf[s] = short ((LP_OFFSET + d * shortBuf[s]) >> LP_SHIFT);
d = (LP_OFFSET + d * i) >> LP_SHIFT;
}
// verify order and go back to ParCor domain
if ((s > 0) && (shortBuf[s - 1] == 0))
{
order--;
}
if (lpToParCorCoeffs (shortBuf, order, parCorCoeffs, LP_DEPTH) > 0)
{
return 0; // coefficient conversion error
}
}
// high-res quantizer, obtain coeff distortion
d = quantizeParCorCoeffs (parCorCoeffs, order, LP_DEPTH, quantCoeffs, false);
if ((lowCoeffRes != nullptr) && (quantizeParCorCoeffs (parCorCoeffs, order, LP_DEPTH, (int8_t* const) m_tempBuf, true) < d))
{
// low-res quantizer yields lower distortion
*lowCoeffRes = true;
memcpy (quantCoeffs, m_tempBuf, order * sizeof (char));
}
for (; order > 0; order--) // return opt order
{
if (quantCoeffs[order - 1] != 0) return (uint8_t) order;
}
return 0;
}
unsigned LinearPredictor::lpToParCorCoeffs (/*mod!*/short* const lpCoeffs, const uint16_t nCoeffs, short* const parCorCoeffs,
const uint16_t parCorCoeffBitDepth /*= 10*/)
{
int* const intBuf = (int* const) m_tempBuf;
const int shift = parCorCoeffBitDepth - 1;
const int offset = 1 << (shift - 1);
if ((lpCoeffs == nullptr) || (parCorCoeffs == nullptr) || (nCoeffs == 0) || (nCoeffs > MAX_PREDICTION_ORDER) || (parCorCoeffBitDepth < 2))
{
return 1; // error
}
for (uint16_t p, s = nCoeffs - 1; s > 0; s--)
{
const int i = (parCorCoeffs[s] = lpCoeffs[s]);
const int d = (1 << shift) - ((offset + i * i) >> shift);
const int o = d >> 1;
if (d <= 0) return s; // invalid coefficient
for (p = 0; p < s; p++)
{
intBuf[p] = lpCoeffs[s - 1 - p];
}
for (p = 0; p < s; p++)
{
lpCoeffs[p] = short ((o + ((int) lpCoeffs[p] << shift) - intBuf[p] * i) / d);
}
}
parCorCoeffs[0] = lpCoeffs[0];
return 0; // no error
}
unsigned LinearPredictor::parCorToLpCoeffs (const short* const parCorCoeffs, const uint16_t nCoeffs, short* const lpCoeffs,
const uint16_t parCorCoeffBitDepth /*= 10*/)
{
int* const intBuf = (int* const) m_tempBuf;
const int shift = parCorCoeffBitDepth - 1;
const int offset = 1 << (shift - 1);
if ((parCorCoeffs == nullptr) || (lpCoeffs == nullptr) || (nCoeffs == 0) || (nCoeffs > MAX_PREDICTION_ORDER) || (parCorCoeffBitDepth < 2))
{
return 1; // error
}
lpCoeffs[0] = parCorCoeffs[0];
for (uint16_t p, s = 1; s < nCoeffs; s++)
{
const int i = (lpCoeffs[s] = parCorCoeffs[s]);
if (abs (i) > (1 << shift)) return s; // > 1
for (p = 0; p < s; p++)
{
intBuf[p] = lpCoeffs[s - 1 - p];
}
for (p = 0; p < s; p++)
{
lpCoeffs[p] += short ((offset + intBuf[p] * i) >> shift);
}
}
return 0; // no error
}
unsigned LinearPredictor::quantTnsToLpCoeffs (const int8_t* const quantTnsCoeffs, const uint16_t nCoeffs, const bool lowCoeffRes,
short* const parCorCoeffs, short* const lpCoeffs)
{
const unsigned tabIdx = (lowCoeffRes ? 0 : 1);
const char tabOffset = 4 << tabIdx;
const short* coeffTab = tnsQuantCoeff[tabIdx];
if ((quantTnsCoeffs == nullptr) || (parCorCoeffs == nullptr) || (lpCoeffs == nullptr) || (nCoeffs == 0) || (nCoeffs > MAX_PREDICTION_ORDER))
{
return 1; // error
}
for (uint16_t s = 0; s < nCoeffs; s++)
{
parCorCoeffs[s] = coeffTab[CLIP_PM (quantTnsCoeffs[s], tabOffset) + tabOffset];
}
return parCorToLpCoeffs (parCorCoeffs, nCoeffs, lpCoeffs, LP_DEPTH);
}
bool LinearPredictor::similarParCorCoeffs (const short* const parCorCoeffs1, const short* const parCorCoeffs2, const uint16_t nCoeffs,
const uint16_t parCorCoeffBitDepth /*= 10*/)
{
unsigned sumAbsDiff = 0;
if ((parCorCoeffs1 == nullptr) || (parCorCoeffs2 == nullptr) || (nCoeffs == 0) || (nCoeffs > MAX_PREDICTION_ORDER) || (parCorCoeffBitDepth < 2))
{
return false; // error
}
for (uint16_t s = 0; s < nCoeffs; s++)
{
sumAbsDiff += abs (parCorCoeffs1[s] - parCorCoeffs2[s]);
}
return (sumAbsDiff < ((unsigned) nCoeffs << (parCorCoeffBitDepth >> 1)));
}

View File

@ -0,0 +1,52 @@
/* linearPrediction.h - header file for class providing linear prediction capability
* written by C. R. Helmrich, last modified in 2019 - 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.
*/
#ifndef _LINEAR_PREDICTION_H_
#define _LINEAR_PREDICTION_H_
#include "exhaleLibPch.h"
// constants, experimental macros
#define LP_EPS 1
#define LP_SHIFT 15
#define LP_DEPTH (1 + LP_SHIFT - MAX_PREDICTION_ORDER)
#define LP_OFFSET (1 << (LP_SHIFT - 1))
// linear predictive filter class
class LinearPredictor
{
private:
// temporary buffer
int64_t m_tempBuf[2 * MAX_PREDICTION_ORDER];
public:
// constructor
LinearPredictor ();
// destructor
~LinearPredictor () { }
// public functions
uint32_t calcParCorCoeffs (const int32_t* const anaSignal, const uint16_t nAnaSamples, const uint16_t nCoeffs,
short* const parCorCoeffs); // returns 256 - 256 / prediction gain per filter order, or 0
uint8_t calcOptTnsCoeffs (short* const parCorCoeffs, int8_t* const quantCoeffs, bool* const lowCoeffRes,
const uint16_t maxOrder, const uint8_t predGain, const uint8_t tonality = 0,
const uint16_t parCorCoeffBitDepth = 10); // returns optimized filter order for TNS
unsigned lpToParCorCoeffs (short* const lpCoeffs, const uint16_t nCoeffs, short* const parCorCoeffs,
const uint16_t parCorCoeffBitDepth = 10);
unsigned parCorToLpCoeffs (const short* const parCorCoeffs, const uint16_t nCoeffs, short* const lpCoeffs,
const uint16_t parCorCoeffBitDepth = 10);
unsigned quantTnsToLpCoeffs(const int8_t* const quantCoeffs, const uint16_t nCoeffs, const bool lowCoeffRes,
short* const parCorCoeffs, short* const lpCoeffs);
bool similarParCorCoeffs (const short* const parCorCoeffs1, const short* const parCorCoeffs2, const uint16_t nCoeffs,
const uint16_t parCorCoeffBitDepth = 10);
}; // LinearPredictor
#endif // _LINEAR_PREDICTION_H_

52
src/lib/makefile Normal file
View File

@ -0,0 +1,52 @@
## makefile - code library make-file for compiling exhale on Linux and MacOS platforms
# written by C. R. Helmrich, last modified 2019 - see License.txt for legal notices
#
# The copyright in this software is being made available under a Modified BSD 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-2019 Christian R. Helmrich, project ecodis. All rights reserved.
##
# define as source code library
CONFIG = LIBRARY
# source and output directories
DIR_BIN = ../../bin
DIR_OBJ = ../../build
DIR_INC = ../../include
DIR_LIB = ../../lib
DIR_SRC = ../../src/lib
# build with large file support
DEFS = -DMSYS_LINUX -DMSYS_UNIX_LARGEFILE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64
# name of product / binary file
PRD_NAME = Exhale
# name of temporary object file
OBJS = \
$(DIR_OBJ)/bitAllocation.o \
$(DIR_OBJ)/bitStreamWriter.o \
$(DIR_OBJ)/entropyCoding.o \
$(DIR_OBJ)/exhaleEnc.o \
$(DIR_OBJ)/exhaleLibPch.o \
$(DIR_OBJ)/lappedTransform.o \
$(DIR_OBJ)/linearPrediction.o \
$(DIR_OBJ)/quantization.o \
$(DIR_OBJ)/specAnalysis.o \
$(DIR_OBJ)/specGapFilling.o \
$(DIR_OBJ)/tempAnalysis.o \
# define libraries to link with
LIBS = -lpthread
DYN_LIBS = -ldl
STAT_LIBS =
DYN_DEBUG_LIBS =
DYN_RELEASE_LIBS =
STAT_DEBUG_LIBS =
STAT_RELEASE_LIBS =
# include common makefile.base
include ../makefile.base

788
src/lib/quantization.cpp Normal file
View File

@ -0,0 +1,788 @@
/* quantization.cpp - source file for class with nonuniform quantization functionality
* written by C. R. Helmrich, last modified in 2019 - 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 "exhaleLibPch.h"
#include "quantization.h"
// static helper function
static inline short getBitCount (EntropyCoder& entrCoder, const int sfIndex, const int sfIndexPred,
const unsigned char groupLength, const unsigned char* coeffQuant,
const uint16_t coeffOffset, const uint16_t numCoeffs)
{
unsigned bitCount = (sfIndex != UCHAR_MAX && sfIndexPred == UCHAR_MAX ? 8 : entrCoder.indexGetBitCount (sfIndex - sfIndexPred));
if (groupLength == 1) // include arithmetic coding in bit count
{
bitCount += entrCoder.arithCodeSigMagn (coeffQuant, coeffOffset, numCoeffs);
}
return (short) __min (SHRT_MAX, bitCount); // exclude sign bits
}
// private helper functions
double SfbQuantizer::getQuantDist (const unsigned* const coeffMagn, const unsigned char scaleFactor,
const unsigned char* coeffQuant, const uint16_t numCoeffs)
{
const double stepSizeDiv = m_lutSfNorm[scaleFactor];
double dDist = 0.0;
for (int i = numCoeffs - 1; i >= 0; i--)
{
const double d = m_lutXExp43[coeffQuant[i]] - coeffMagn[i] * stepSizeDiv;
dDist += d * d; // TODO: do this in fixed-point and with SIMD
}
// consider quantization step-size in calculation of distortion
return dDist * m_lut2ExpX4[scaleFactor] * m_lut2ExpX4[scaleFactor];
}
unsigned char SfbQuantizer::quantizeMagn (const unsigned* const coeffMagn, const unsigned char scaleFactor,
unsigned char* const coeffQuant, const uint16_t numCoeffs,
short* const sigMaxQ /*= NULL*/, short* const sigNumQ /*= NULL*/)
{
const double stepSizeDiv = m_lutSfNorm[scaleFactor];
double dNum = 0.0, dDen = 0.0;
short maxQ = 0, numQ = 0;
for (int i = numCoeffs - 1; i >= 0; i--)
{
const double normalizedMagn = (double) coeffMagn[i] * stepSizeDiv;
#if SFB_QUANT_FAST_POW
short q;
if (normalizedMagn < 28.5) // fast approximate pow (d, 0.75)
{
// based on code from: N. N. Schraudolph, "A Fast, Compact Approximation of the Expo-
// nential Function," Neural Comput., vol. 11, pp. 853-862, 1998 and M. Ankerl, 2007,
// https://martin.ankerl.com/2007/10/04/optimized-pow-approximation-for-java-and-c-c/
union { double d; int32_t i[2]; } u = { normalizedMagn };
u.i[1] = int32_t (0.75 * (u.i[1] - 1072632447) + 1072632447.0);
u.i[0] = 0;
q = short (u.d + (u.d < 1.0 ? 0.3822484 : 0.2734375));
}
else
{
q = short (SFB_QUANT_OFFSET + pow (normalizedMagn, 0.75));
}
#else
short q = short (SFB_QUANT_OFFSET + pow (normalizedMagn, 0.75));
#endif
if (q > 0)
{
if (q >= SCHAR_MAX)
{
if (maxQ < q)
{
maxQ = q; // find maximum quantized magnitude in vector
}
q = SCHAR_MAX;
}
else
{
const double diffRoundD = normalizedMagn - m_lutXExp43[q];
const double diffRoundU = normalizedMagn - m_lutXExp43[q + 1];
if (diffRoundU * diffRoundU < diffRoundD * diffRoundD)
{
q++; // round-up gives lower distortion than round-down
}
}
if (maxQ < q)
{
maxQ = q;
}
numQ++;
dNum += m_lutXExp43[q] * normalizedMagn;
dDen += m_lutXExp43[q] * m_lutXExp43[q];
}
#if SFB_QUANT_PERCEPT_OPT
else // q == 0, assume perceptual transparency for code below
{
dNum += normalizedMagn * normalizedMagn;
dDen += normalizedMagn * normalizedMagn;
}
#endif
coeffQuant[i] = (unsigned char) q;
}
if (sigMaxQ) *sigMaxQ = maxQ; // max. quantized value magnitude
if (sigNumQ) *sigNumQ = numQ; // nonzero coeff. count (L0 norm)
// compute least-squares optimal gain multiplied onto step-size
numQ = scaleFactor + short (SF_QUANT_OFFSET + FOUR_LOG102 * log10 (dNum <= 0.0 ? 1.0 : dNum / dDen) - (dNum < dDen ? 1.0 : 0.0));
return (unsigned char) __max (0, numQ); // optimal scale factor
}
// constructor
SfbQuantizer::SfbQuantizer ()
{
// initialize all helper buffers
m_coeffMagn = nullptr;
m_lut2ExpX4 = nullptr;
m_lutSfNorm = nullptr;
m_lutXExp43 = nullptr;
m_maxSfIndex = 0;
#if EC_TRELLIS_OPT_CODING
m_numCStates = 0;
for (unsigned b = 0; b < 52; b++)
{
m_quantDist[b] = nullptr;
m_quantInSf[b] = nullptr;
m_quantRate[b] = nullptr;
}
#endif
}
// destructor
SfbQuantizer::~SfbQuantizer ()
{
// free allocated helper buffers
MFREE (m_coeffMagn);
MFREE (m_lut2ExpX4);
MFREE (m_lutSfNorm);
MFREE (m_lutXExp43);
#if EC_TRELLIS_OPT_CODING
for (unsigned b = 0; b < 52; b++)
{
MFREE (m_quantDist[b]);
MFREE (m_quantInSf[b]);
MFREE (m_quantRate[b]);
}
#endif
}
// public functions
unsigned SfbQuantizer::initQuantMemory (const unsigned maxTransfLength,
#if EC_TRELLIS_OPT_CODING
const unsigned char numSwb, const unsigned char bitRateMode,
#endif
const unsigned char maxScaleFacIndex /*= SCHAR_MAX*/)
{
const unsigned numScaleFactors = (unsigned) maxScaleFacIndex + 1;
#if EC_TRELLIS_OPT_CODING
const unsigned char numIStates = 8 - __min (5, (bitRateMode + 2) >> 2); // states per SFB
const unsigned char numDStates = numIStates * numIStates; // interdependent states per SFB
#endif
unsigned x;
if ((maxTransfLength < 128) || (maxTransfLength > 8192) || (maxTransfLength & 7) || (maxScaleFacIndex == 0) || (maxScaleFacIndex > SCHAR_MAX))
{
return 1; // invalid arguments error
}
m_maxSfIndex = maxScaleFacIndex;
if ((m_coeffMagn = (unsigned*) malloc (maxTransfLength * sizeof (unsigned))) == nullptr ||
(m_lut2ExpX4 = (double* ) malloc (numScaleFactors * sizeof (double ))) == nullptr ||
(m_lutSfNorm = (double* ) malloc (numScaleFactors * sizeof (double ))) == nullptr ||
(m_lutXExp43 = (double* ) malloc ((SCHAR_MAX + 1) * sizeof (double ))) == nullptr)
{
return 2; // memory allocation error
}
#if EC_TRELLIS_OPT_CODING
m_numCStates = numIStates;
for (x = 0; x < __min (52u, numSwb); x++)
{
if ((m_quantDist[x] = (double* ) malloc (numIStates * sizeof (double ))) == nullptr ||
(m_quantInSf[x] = (uint8_t* ) malloc (numIStates * sizeof (uint8_t ))) == nullptr ||
(m_quantRate[x] = (uint16_t*) malloc (numDStates * sizeof (uint16_t))) == nullptr)
{
return 2;
}
}
#endif
memset (m_coeffTemp, 0, sizeof (m_coeffTemp));
// calculate scale factor gain 2^(x/4)
for (x = 0; x < numScaleFactors; x++)
{
m_lut2ExpX4[x] = pow (2.0, (double) x / 4.0);
m_lutSfNorm[x] = 1.0 / m_lut2ExpX4[x];
}
// calculate dequantized coeff x^(4/3)
for (x = 0; x < (SCHAR_MAX + 1); x++)
{
m_lutXExp43[x] = pow ((double) x, 4.0 / 3.0);
}
return 0; // no error
}
uint8_t SfbQuantizer::quantizeSpecSfb (EntropyCoder& entropyCoder, const int32_t* const inputCoeffs, const unsigned char grpLength,
const uint16_t* const grpOffsets, uint32_t* const grpStats, // quant./coding statistics
const unsigned sfb, const unsigned char sfIndex, const unsigned char sfIndexPred /*= UCHAR_MAX*/,
unsigned char* const quantCoeffs /*= nullptr*/) // returns the RD optimized scale factor index
{
unsigned char sfBest = sfIndex;
if ((inputCoeffs == nullptr) || (grpOffsets == nullptr) || (sfb >= 52) || (sfIndex > m_maxSfIndex))
{
return UCHAR_MAX; // invalid input error
}
#if EC_TRELLIS_OPT_CODING
if (grpLength == 1) // references for RDOC
{
m_quantDist[sfb][1] = -1.0;
m_quantInSf[sfb][1] = sfIndex;
m_quantRate[sfb][1] = 0; // for sgn bits
m_quantRate[sfb][0] = entropyCoder.arithGetCtxState () & USHRT_MAX; // ref start context
}
#endif
if ((sfIndex == 0) || (sfIndexPred <= m_maxSfIndex && sfIndex + INDEX_OFFSET < sfIndexPred))
{
const uint16_t grpStart = grpOffsets[0];
const uint16_t sfbStart = grpOffsets[sfb];
const uint16_t sfbWidth = grpOffsets[sfb + 1] - sfbStart;
uint32_t* const coeffMagn = &m_coeffMagn[sfbStart];
for (int i = sfbWidth - 1; i >= 0; i--) // back up magnitudes. TODO: use SIMD for speed?
{
coeffMagn[i] = abs (inputCoeffs[sfbStart + i]);
}
if (quantCoeffs)
{
memset (&quantCoeffs[sfbStart], 0, sfbWidth * sizeof (unsigned char)); // zero output
if (grpStats) // approximate bit count
{
grpStats[sfb] = getBitCount (entropyCoder, 0, 0, grpLength, &quantCoeffs[grpStart], sfbStart - grpStart, sfbWidth);
}
}
return sfIndexPred - (sfIndex == 0 ? 0 : INDEX_OFFSET); // save delta bits if applicable
}
else // nonzero sf, optimized quantization
{
const uint16_t grpStart = grpOffsets[0];
const uint16_t sfbStart = grpOffsets[sfb];
const uint16_t sfbWidth = grpOffsets[sfb + 1] - sfbStart;
const uint16_t cpyWidth = sfbWidth * sizeof (unsigned char);
uint32_t* const coeffMagn = &m_coeffMagn[sfbStart];
unsigned codStart = 0, ctxStart = 0;
unsigned codFinal = 0, ctxFinal = 0;
double distBest = 0, distCurr = 0;
short maxQBest = 0, maxQCurr = 0;
short numQBest = 0, numQCurr = 0;
#if EC_TRELLIS_OPT_CODING
bool rdOptimQuant = (grpLength != 1);
#else
bool rdOptimQuant = true;
#endif
unsigned char* ptrBest = &m_coeffTemp[0];
unsigned char* ptrCurr = &m_coeffTemp[100];
unsigned char sfCurr = sfIndex;
for (int i = sfbWidth - 1; i >= 0; i--) // back up magnitudes. TODO: use SIMD for speed?
{
coeffMagn[i] = abs (inputCoeffs[sfbStart + i]);
}
// --- determine default quantization result using range limited scale factor as a reference
sfBest = quantizeMagn (coeffMagn, sfCurr, ptrBest, sfbWidth, &maxQBest, &numQBest);
if (maxQBest > SCHAR_MAX) // limit SNR via scale factor index
{
for (unsigned char c = 0; (c < 2) && (maxQBest > SCHAR_MAX); c++) // rarely done twice
{
sfCurr += getScaleFacOffset (pow ((double) maxQBest, 4.0 / 3.0) / m_lutXExp43[SCHAR_MAX]) + c;
sfBest = quantizeMagn (coeffMagn, sfCurr, ptrBest, sfbWidth, &maxQBest, &numQBest);
}
rdOptimQuant = false;
}
else if ((sfBest < sfCurr) && (sfBest != sfIndexPred)) // re-optimize above quantization
{
sfBest = quantizeMagn (coeffMagn, --sfCurr, ptrBest, sfbWidth, &maxQBest, &numQBest);
rdOptimQuant &= (maxQBest <= SCHAR_MAX);
}
#if EC_TRELLIS_OPT_CODING
if (grpLength == 1) // ref masking level
{
m_quantInSf[sfb][1] = __min (sfCurr, m_maxSfIndex);
}
#endif
if (maxQBest == 0) // SFB was quantized to zero - zero output
{
if (quantCoeffs)
{
memset (&quantCoeffs[sfbStart], 0, cpyWidth);
if (grpStats) // estimated bit count
{
grpStats[sfb] = getBitCount (entropyCoder, 0, 0, grpLength, &quantCoeffs[grpStart], sfbStart - grpStart, sfbWidth);
}
}
return sfIndexPred; // repeat scale factor, save delta bits
}
// --- check whether optimized quantization and coding results in lower rate-distortion cost
distBest = getQuantDist (coeffMagn, sfBest, ptrBest, sfbWidth);
#if EC_TRELLIS_OPT_CODING
if (grpLength == 1) // ref band-wise NMR
{
const double refSfbNmrDiv = m_lutSfNorm[m_quantInSf[sfb][1]];
m_quantDist[sfb][1] = distBest * refSfbNmrDiv * refSfbNmrDiv;
m_quantRate[sfb][1] = numQBest; // sgn
}
#endif
if (quantCoeffs)
{
memcpy (&quantCoeffs[sfbStart], ptrBest, cpyWidth);
codStart = entropyCoder.arithGetCodState (); // start state
ctxStart = entropyCoder.arithGetCtxState ();
numQBest += getBitCount (entropyCoder, sfBest, sfIndexPred, grpLength, &quantCoeffs[grpStart], sfbStart - grpStart, sfbWidth);
codFinal = entropyCoder.arithGetCodState (); // final state
ctxFinal = entropyCoder.arithGetCtxState ();
}
rdOptimQuant &= (distBest > 0.0);
if ((sfBest < sfCurr) && (sfBest != sfIndexPred) && rdOptimQuant) // R/D re-optimization
{
sfCurr = quantizeMagn (coeffMagn, sfCurr - 1, ptrCurr, sfbWidth, &maxQCurr, &numQCurr);
if (maxQCurr <= maxQBest)
{
distCurr = getQuantDist (coeffMagn, sfCurr, ptrCurr, sfbWidth);
if (quantCoeffs)
{
memcpy (&quantCoeffs[sfbStart], ptrCurr, cpyWidth);
entropyCoder.arithSetCodState (codStart);// reset state
entropyCoder.arithSetCtxState (ctxStart);
numQCurr += getBitCount (entropyCoder, sfCurr, sfIndexPred, grpLength, &quantCoeffs[grpStart], sfbStart - grpStart, sfbWidth);
}
// rate-distortion decision with empirical rate threshold
if ((numQCurr <= numQBest + (distCurr >= distBest ? -1 : short (0.5 + distBest / __max (1.0, distCurr)))))
{
unsigned char* ptrTemp = ptrBest;
ptrBest = ptrCurr;
ptrCurr = ptrTemp;
distBest = distCurr;
maxQBest = maxQCurr;
numQBest = numQCurr;
sfBest = sfCurr;
}
else if (quantCoeffs) // discard result, recover best try
{
memcpy (&quantCoeffs[sfbStart], ptrBest, cpyWidth);
entropyCoder.arithSetCodState (codFinal); // set state
entropyCoder.arithSetCtxState (ctxFinal);
}
} // if (maxQCurr <= maxQBest)
}
else sfCurr = sfBest;
rdOptimQuant &= (distBest > 0.0);
if ((sfCurr != sfIndexPred) && (sfBest != sfIndexPred) && rdOptimQuant && (sfIndexPred > 0) && (sfIndexPred < m_maxSfIndex))
{
unsigned char sf;
// try quantization with repeated scale factor to save bits
for (sf = (sfCurr = sfIndexPred + 1); (sf >= sfIndexPred) && (sfCurr > sfIndexPred); sf--)
{
sfCurr = quantizeMagn (coeffMagn, sf, ptrCurr, sfbWidth, &maxQCurr, &numQCurr);
}
if ((sfCurr <= sfIndexPred) && (maxQCurr == maxQBest))
{
bool calcRate = (quantCoeffs != nullptr);
double distTemp;
codFinal = entropyCoder.arithGetCodState (); // new state
ctxFinal = entropyCoder.arithGetCtxState ();
distCurr = getQuantDist (coeffMagn, sfIndexPred, ptrCurr, sfbWidth);
if (sfCurr < sfIndexPred) // repeated index isn't optimal
{
distTemp = getQuantDist (coeffMagn, sfIndexPred, ptrBest, sfbWidth);
if (distTemp < distCurr) // best gives lower distortion
{
#if SFB_QUANT_MORE_TESTS
ptrCurr = ptrBest;
#endif
distCurr = distTemp;
maxQCurr = maxQBest;
numQCurr = numQBest;
sfCurr = __min (sfBest, sfIndexPred);
calcRate = false; // (num)qBest are already complete
}
}
#if SFB_QUANT_MORE_TESTS
if (quantCoeffs && (sf >= sfIndexPred)) // a missing test
{
const short maxQTemp = maxQCurr;
const short numQTemp = numQCurr;
sf = quantizeMagn (coeffMagn, sf, &quantCoeffs[sfbStart], sfbWidth, &maxQCurr, &numQCurr);
distTemp = getQuantDist (coeffMagn, sfIndexPred, &quantCoeffs[sfbStart], sfbWidth);
if (distTemp < distCurr) // also gives lower distortion
{
# if 1
ptrCurr = &quantCoeffs[sfbStart];
# endif
distCurr = distTemp;
entropyCoder.arithSetCodState (codStart);// set state
entropyCoder.arithSetCtxState (ctxStart);
numQCurr += getBitCount (entropyCoder, sfIndexPred, sfIndexPred, grpLength, &quantCoeffs[grpStart], sfbStart - grpStart, sfbWidth);
sfCurr = __min (sf, sfIndexPred);
calcRate = false;
}
else // distortion is not lower - restore maxQ and numQ
{
maxQCurr = maxQTemp;
numQCurr = numQTemp;
if (!calcRate) memcpy (&quantCoeffs[sfbStart], ptrCurr, cpyWidth);
}
}
#endif
if (calcRate)
{
memcpy (&quantCoeffs[sfbStart], ptrCurr, cpyWidth);
entropyCoder.arithSetCodState (codStart);// reset state
entropyCoder.arithSetCtxState (ctxStart);
numQCurr += getBitCount (entropyCoder, sfIndexPred, sfIndexPred, grpLength, &quantCoeffs[grpStart], sfbStart - grpStart, sfbWidth);
}
// rate-distortion decision with empirical rate threshold
if ((numQCurr <= numQBest + (distCurr >= distBest ? -1 : short (0.5 + distBest / __max (1.0, distCurr)))))
{
#if SFB_QUANT_MORE_TESTS
if ((sfCurr < sfIndexPred) && (distCurr >= distBest) && (numQCurr + 1 + sfIndexPred - sfCurr < numQBest) &&
(getQuantDist (coeffMagn, sfCurr, ptrCurr, sfbWidth) < distCurr))
{
numQCurr += 1 + sfIndexPred - sfCurr; // 14496, 4.A.1
sfBest = sfCurr;
}
else
#endif
sfBest = sfIndexPred;
maxQBest = maxQCurr;
numQBest = numQCurr;
}
else if (quantCoeffs) // discard result, recover best try
{
memcpy (&quantCoeffs[sfbStart], ptrBest, cpyWidth);
entropyCoder.arithSetCodState (codFinal); // set state
entropyCoder.arithSetCtxState (ctxFinal);
}
} // if ((sfCurr <= sfIndexPred) && (maxQCurr >= maxQBest))
}
if (grpStats)
{
grpStats[sfb] = ((uint32_t) maxQBest << 16) | numQBest; // max magnitude and bit count
}
} // if (sfIndex == 0)
return __min (sfBest, m_maxSfIndex);
}
#if EC_TRELLIS_OPT_CODING
# define EC_TRAIN 0
unsigned SfbQuantizer::quantizeSpecRDOC (EntropyCoder& entropyCoder, unsigned char* const optimalSf, const unsigned targetBitCount,
const uint16_t* const grpOffsets, uint32_t* const grpStats, // quant./coding statistics
const unsigned numSfb, unsigned char* const quantCoeffs) // returns RD optimization bit count
{
// numSfb: number of trellis stages. Based on: A. Aggarwal, S. L. Regunathan, and K. Rose,
// "Trellis-Based Optimization of MPEG-4 Advanced Audio Coding," in Proc. IEEE Workshop on
// Speech Coding, pp. 142-144, Sep. 2000. Modified for arithmetic instead of Huffman coder
const uint32_t codStart = USHRT_MAX << 16;
const uint32_t ctxStart = m_quantRate[0][0]; // start context before quantizeSfb()
const uint32_t codFinal = entropyCoder.arithGetCodState ();
const uint32_t ctxFinal = entropyCoder.arithGetCtxState (); // after quantizeSfb()
const uint16_t grpStart = grpOffsets[0];
unsigned char* const inScaleFac = &m_coeffTemp[716];
uint32_t prevCodState[8] = {0, 0, 0, 0, 0, 0, 0, 0};
uint32_t prevCtxState[8] = {0, 0, 0, 0, 0, 0, 0, 0};
unsigned char prevScaleFac[8] = {0, 0, 0, 0, 0, 0, 0, 0};
double prevVtrbCost[8] = {0, 0, 0, 0, 0, 0, 0, 0};
uint32_t tempCodState[8] = {0, 0, 0, 0, 0, 0, 0, 0};
uint32_t tempCtxState[8] = {0, 0, 0, 0, 0, 0, 0, 0};
unsigned char tempScaleFac[8] = {0, 0, 0, 0, 0, 0, 0, 0};
double tempVtrbCost[8] = {0, 0, 0, 0, 0, 0, 0, 0};
unsigned tempBitCount, sfb, is;
int ds;
#if EC_TRAIN
double refGrpDist = 0.0, tempGrpDist;
#else
const double lambda = (108.0 + targetBitCount * targetBitCount) / 1024.0; // Lagrange par.
#endif
if ((optimalSf == nullptr) || (grpOffsets == nullptr) || (numSfb == 0) || (numSfb > 52) || (targetBitCount == 0) || (targetBitCount > SHRT_MAX))
{
return 0; // invalid input error
}
for (sfb = 0; sfb < numSfb; sfb++) // SFB-wise scale factor, weighted distortion, and rate
{
const unsigned char refSf = m_quantInSf[sfb][1];
const uint16_t refNumQ = m_quantRate[sfb][1];
const double refQuantDist = m_quantDist[sfb][1];
const double refQuantNorm = m_lutSfNorm[refSf] * m_lutSfNorm[refSf];
const uint16_t sfbStart = grpOffsets[sfb];
const uint16_t sfbWidth = grpOffsets[sfb + 1] - sfbStart;
uint32_t* const coeffMagn = &m_coeffMagn[sfbStart];
unsigned char* const tempMagn = &m_coeffTemp[sfbStart];
bool maxSnrReached = false;
if (refQuantDist < 0.0) memset (tempMagn, 0, sfbWidth * sizeof (unsigned char));
#if EC_TRAIN
else refGrpDist += refQuantDist;
#endif
if (grpStats)
{
grpStats[sfb] = (grpStats[sfb] & (USHRT_MAX << 16)) | refNumQ; // keep magn, sign bits
}
for (is = 0; is < m_numCStates; is++) // populate the trellis
{
unsigned char* const mag = (is != 1 || quantCoeffs == nullptr ? &m_coeffTemp[grpStart] : &quantCoeffs[grpStart]);
double* const currDist = &m_quantDist[sfb][is];
uint16_t* currRate = &m_quantRate[sfb][is * m_numCStates];
unsigned char sfBest = optimalSf[sfb]; // optimal refSf
short maxQCurr = 0, numQCurr = 0; // for sign bits counting
if (refQuantDist < 0.0) // -1.0 means SFB is zero-quantized
{
*currDist = -1.0;
m_quantInSf[sfb][is] = refSf;
}
else if (is != 1) // quantization & distortion not computed
{
const unsigned char sfCurr = __max (0, __min (m_maxSfIndex, refSf + 1 - (int) is));
*currDist = -1.0;
if ((sfCurr == 0) || maxSnrReached)
{
maxSnrReached = true;
}
else // sfCurr > 0 && sfCurr <= m_maxSfIndex, re-quantize
{
sfBest = quantizeMagn (coeffMagn, sfCurr, tempMagn, sfbWidth, &maxQCurr, &numQCurr);
if (maxQCurr > SCHAR_MAX)
{
maxSnrReached = true; numQCurr = 0;
}
else
{
*currDist = getQuantDist (coeffMagn, sfBest, tempMagn, sfbWidth) * refQuantNorm;
}
}
if (*currDist < 0.0) memset (tempMagn, 0, sfbWidth * sizeof (unsigned char));
m_quantInSf[sfb][is] = sfCurr; // store initial scale fac
}
else // is == 1, quant. & dist. computed with quantizeSfb()
{
numQCurr = refNumQ;
}
if (sfb == 0) // first SFB, having sfbStart - grpStart == 0
{
entropyCoder.arithSetCodState (codStart); // group start
entropyCoder.arithSetCtxState (ctxStart, 0);
tempBitCount = (maxSnrReached ? USHRT_MAX : numQCurr + getBitCount (entropyCoder, sfBest, UCHAR_MAX, 1, mag, 0, sfbWidth));
for (ds = m_numCStates - 1; ds >= 0; ds--)
{
currRate[ds] = (uint16_t) tempBitCount;
}
tempCodState[is] = entropyCoder.arithGetCodState ();
tempCtxState[is] = entropyCoder.arithGetCtxState ();
}
else // sfb > 0, rate depends on decisions in preceding SFB
{
for (ds = m_numCStates - 1; ds >= 0; ds--)
{
const uint16_t prevRate = m_quantRate[sfb - 1][ds * m_numCStates];
entropyCoder.arithSetCodState (prevCodState[ds]);
entropyCoder.arithSetCtxState (prevCtxState[ds], sfbStart - grpStart);
tempBitCount = (maxSnrReached || (prevRate == USHRT_MAX) ? USHRT_MAX : numQCurr + getBitCount (entropyCoder,
(numQCurr == 0 ? prevScaleFac[ds] : sfBest), prevScaleFac[ds], 1, mag, sfbStart - grpStart, sfbWidth));
currRate[ds] = (uint16_t) tempBitCount;
if ((sfb + 1 == numSfb) && (tempBitCount != USHRT_MAX))
{
currRate[ds] += ((entropyCoder.arithGetCtxState () >> 17) & 31) + 2; // m_acBits+2
}
if (ds == 1) // statistically best place to save states
{
tempCodState[is] = entropyCoder.arithGetCodState ();
tempCtxState[is] = entropyCoder.arithGetCtxState ();
}
}
}
tempScaleFac[is] = sfBest; // optimized factor for next SFB
} // for is
memcpy (prevCodState, tempCodState, m_numCStates * sizeof (uint32_t));
memcpy (prevCtxState, tempCtxState, m_numCStates * sizeof (uint32_t));
memcpy (prevScaleFac, tempScaleFac, m_numCStates * sizeof (unsigned char));
} // for sfb
entropyCoder.arithSetCodState (codFinal); // back to last state
entropyCoder.arithSetCtxState (ctxFinal, grpOffsets[numSfb] - grpStart);
#if EC_TRAIN
tempBitCount = targetBitCount + 1; // Viterbi search for minimum distortion at target rate
for (double lambda = 0.015625; (lambda <= 0.375) && (tempBitCount > targetBitCount); lambda += 0.0078125)
#endif
{
double* const prevCost = prevVtrbCost;
unsigned char* const prevPath = m_coeffTemp; // for backtrack
double costMinIs = (double) UINT_MAX;
#if EC_TRAIN
double tempGrpDist = 0.0;
#endif
unsigned pathMinIs = 1;
for (is = 0; is < m_numCStates; is++) // initial minimum path
{
const uint16_t currRate = m_quantRate[0][is * m_numCStates];
prevCost[is] = (currRate >= USHRT_MAX ? (double) UINT_MAX : lambda * currRate + __max (0.0, m_quantDist[0][is]));
prevPath[is] = 0;
}
for (sfb = 1; sfb < numSfb; sfb++) // search for minimum path
{
double* const currCost = tempVtrbCost;
unsigned char* const currPath = &prevPath[sfb * m_numCStates];
for (is = 0; is < m_numCStates; is++) // SFB's minimum path
{
uint16_t* currRate = &m_quantRate[sfb][is * m_numCStates];
double costMinDs = (double) UINT_MAX;
unsigned char pathMinDs = 1;
for (ds = m_numCStates - 1; ds >= 0; ds--) // transitions
{
const double costCurr = (currRate[ds] >= USHRT_MAX ? (double) UINT_MAX : prevCost[ds] + lambda * currRate[ds]);
if (costMinDs > costCurr)
{
costMinDs = costCurr;
pathMinDs = (unsigned char) ds;
}
}
if (costMinDs < UINT_MAX) costMinDs += __max (0.0, m_quantDist[sfb][is]);
currCost[is] = costMinDs;
currPath[is] = pathMinDs;
} // for is
memcpy (prevCost, currCost, m_numCStates * sizeof (double)); // TODO: avoid memcpy, use pointer swapping instead for speed
} // for sfb
for (sfb--, is = 0; is < m_numCStates; is++) // group minimum
{
if (costMinIs > prevCost[is])
{
costMinIs = prevCost[is];
pathMinIs = is;
}
}
for (tempBitCount = 0; sfb > 0; sfb--) // min-cost group rate
{
const unsigned char* currPath = &prevPath[sfb * m_numCStates];
const unsigned char pathMinDs = currPath[pathMinIs];
inScaleFac[sfb] = (m_quantDist[sfb][pathMinIs] < 0.0 ? UCHAR_MAX : m_quantInSf[sfb][pathMinIs]);
tempBitCount += m_quantRate[sfb][pathMinDs + pathMinIs * m_numCStates];
#if EC_TRAIN
tempGrpDist += __max (0.0, m_quantDist[sfb][pathMinIs]);
#endif
pathMinIs = pathMinDs;
}
inScaleFac[0] = (m_quantDist[0][pathMinIs] < 0.0 ? UCHAR_MAX : m_quantInSf[0][pathMinIs]);
tempBitCount += m_quantRate[0][pathMinIs * m_numCStates];
#if EC_TRAIN
tempGrpDist += __max (0.0, m_quantDist[0][pathMinIs]);
#endif
} // Viterbi search
#if EC_TRAIN
if ((quantCoeffs != nullptr) && (tempGrpDist <= refGrpDist || tempBitCount <= targetBitCount))
#else
if (quantCoeffs != nullptr)
#endif
{
unsigned char sfIndexPred = UCHAR_MAX;
if (grpStats)
{
entropyCoder.arithSetCodState (codStart);// set group start
entropyCoder.arithSetCtxState (ctxStart, 0);
tempBitCount = 0;
}
for (sfb = 0; sfb < numSfb; sfb++) // re-quantize spectrum with R/D optimized parameters
{
const uint16_t sfbStart = grpOffsets[sfb];
const uint16_t sfbWidth = grpOffsets[sfb + 1] - sfbStart;
if (inScaleFac[sfb] == UCHAR_MAX) // forced zero-quantized
{
optimalSf[sfb] = sfIndexPred;
memset (&quantCoeffs[sfbStart], 0, sfbWidth * sizeof (unsigned char));
}
else if (inScaleFac[sfb] != m_quantInSf[sfb][1]) // speedup
{
short maxQBest = 0, numQBest = 0;
optimalSf[sfb] = quantizeMagn (&m_coeffMagn[sfbStart], inScaleFac[sfb], &quantCoeffs[sfbStart], sfbWidth, &maxQBest, &numQBest);
if (maxQBest == 0) optimalSf[sfb] = sfIndexPred; // empty
if (grpStats)
{
grpStats[sfb] = ((uint32_t) maxQBest << 16) | numQBest; // max magn. and sign bits
}
}
if (grpStats) // complete statistics with per-SFB bit count
{
grpStats[sfb] += getBitCount (entropyCoder, optimalSf[sfb], sfIndexPred, 1, &quantCoeffs[grpStart], sfbStart - grpStart, sfbWidth);
tempBitCount += grpStats[sfb] & USHRT_MAX;
}
if ((sfb > 0) && (optimalSf[sfb] < UCHAR_MAX) && (sfIndexPred == UCHAR_MAX))
{
memset (optimalSf, optimalSf[sfb], sfb * sizeof (unsigned char)); // back-propagate
}
sfIndexPred = optimalSf[sfb];
} // for sfb
return tempBitCount + (grpStats ? ((entropyCoder.arithGetCtxState () >> 17) & 31) /*m_acBits*/ + 2 : 0);
}
return targetBitCount;
}
#endif // EC_TRELLIS_OPT_CODING

85
src/lib/quantization.h Normal file
View File

@ -0,0 +1,85 @@
/* quantization.h - header file for class with nonuniform quantization functionality
* written by C. R. Helmrich, last modified in 2019 - 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.
*/
#ifndef _QUANTIZATION_H_
#define _QUANTIZATION_H_
#include "exhaleLibPch.h"
#include "entropyCoding.h"
// constants, experimental macros
#define FOUR_LOG102 13.28771238 // 4 / log10 (2)
#define SF_QUANT_OFFSET 0.4783662 // for scale fac
#define SFB_QUANT_FAST_POW 1 // faster pow ()
#define SFB_QUANT_MORE_TESTS 1 // longer search
#define SFB_QUANT_PERCEPT_OPT 1 // psych. quant.
#if SFB_QUANT_FAST_POW
#define SFB_QUANT_OFFSET 0.496094 // 13 - 29^(3/4)
#else
#define SFB_QUANT_OFFSET 0.405396 // 1 - 0.5^(3/4)
#endif
// class for xHE-AAC quantization
class SfbQuantizer
{
private:
// member variables
unsigned* m_coeffMagn; // temp memory
#if EC_TRELLIS_OPT_CODING
uint8_t m_coeffTemp[1024]; // TODO?
#else
uint8_t m_coeffTemp[200]; // 40 * 5 - NOTE: increase this when maximum grpLength > 5
#endif
double* m_lut2ExpX4; // for 2^(X/4)
double* m_lutSfNorm; // 1 / 2^(X/4)
double* m_lutXExp43; // for X^(4/3)
uint8_t m_maxSfIndex; // 1,..., 127
#if EC_TRELLIS_OPT_CODING
uint8_t m_numCStates; // states/SFB
// trellis memory, max. 8 KB @ num_swb=51
double* m_quantDist[52]; // quantizing distortion
uint8_t* m_quantInSf[52]; // initial scale factors
uint16_t* m_quantRate[52]; // MDCT and SF bit count
#endif
// helper functions
double getQuantDist (const unsigned* const coeffMagn, const uint8_t scaleFactor,
const uint8_t* const coeffQuant, const uint16_t numCoeffs);
uint8_t quantizeMagn (const unsigned* const coeffMagn, const uint8_t scaleFactor,
/*mod*/uint8_t* const coeffQuant, const uint16_t numCoeffs,
short* const sigMaxQ = nullptr, short* const sigNumQ = nullptr);
public:
// constructor
SfbQuantizer ();
// destructor
~SfbQuantizer ();
// public functions
unsigned* getCoeffMagnPtr () const { return m_coeffMagn; }
double* getSfNormTabPtr () const { return m_lutSfNorm; }
uint8_t getScaleFacOffset (const double absValue) const { return uint8_t (SF_QUANT_OFFSET + FOUR_LOG102 * log10 (__max (1.0, absValue))); }
unsigned initQuantMemory (const unsigned maxTransfLength,
#if EC_TRELLIS_OPT_CODING
const uint8_t numSwb, const uint8_t bitRateMode,
#endif
const uint8_t maxScaleFacIndex = SCHAR_MAX);
uint8_t quantizeSpecSfb (EntropyCoder& entropyCoder, const int32_t* const inputCoeffs, const uint8_t grpLength,
const uint16_t* const grpOffsets, uint32_t* const grpStats, // quant./coding statistics
const unsigned sfb, const uint8_t sfIndex, const uint8_t sfIndexPred = UCHAR_MAX,
uint8_t* const quantCoeffs = nullptr); // returns index of the RD optimized scale factor
#if EC_TRELLIS_OPT_CODING
unsigned quantizeSpecRDOC (EntropyCoder& entropyCoder, uint8_t* const optimalSf, const unsigned targetBitCount,
const uint16_t* const grpOffsets, uint32_t* const grpStats, // quant./coding statistics
const unsigned numSfb, uint8_t* const quantCoeffs); // returns RD optimization bit count
#endif
}; // SfbQuantizer
#endif // _QUANTIZATION_H_

342
src/lib/specAnalysis.cpp Normal file
View File

@ -0,0 +1,342 @@
/* specAnalysis.cpp - source file for class providing spectral analysis of MCLT signals
* written by C. R. Helmrich, last modified in 2019 - 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 "exhaleLibPch.h"
#include "specAnalysis.h"
// static helper function
static inline uint32_t packAvgSpecAnalysisStats (const uint64_t sumAvgBand, const uint64_t sumMaxBand,
const unsigned char predGain,
const uint16_t idxMaxSpec, const uint16_t idxLpStart)
{
// temporal flatness, normalized for a value of 256 for a linear prediction gain of 1 (0 dB)
const unsigned flatTemp = predGain;
// spectral flatness, normalized for a value of 256 for steady low or mid-frequency sinusoid
const int32_t flatSpec = 256 - int (((sumAvgBand + SA_EPS) * 402) / (sumMaxBand + SA_EPS));
return (flatTemp << 24) | (CLIP_UCHAR (flatSpec) << 16) | (__min (2047, idxMaxSpec) << 5) | __min (31, idxLpStart);
}
// constructor
SpecAnalyzer::SpecAnalyzer ()
{
for (unsigned ch = 0; ch < USAC_MAX_NUM_CHANNELS; ch++)
{
m_bandwidthOff[ch] = 0;
m_numAnaBands [ch] = 0;
m_specAnaStats[ch] = 0;
memset (m_parCorCoeffs[ch], 0, MAX_PREDICTION_ORDER * sizeof (short));
}
m_tnsPredictor = nullptr;
}
// public functions
unsigned SpecAnalyzer::getLinPredCoeffs (short parCorCoeffs[MAX_PREDICTION_ORDER], const unsigned channelIndex) // returns best filter order
{
unsigned bestOrder = MAX_PREDICTION_ORDER, predGainCurr, predGainPrev;
if ((parCorCoeffs == nullptr) || (channelIndex >= USAC_MAX_NUM_CHANNELS))
{
return 0; // invalid arguments error
}
memcpy (parCorCoeffs, m_parCorCoeffs[channelIndex], MAX_PREDICTION_ORDER * sizeof (short));
predGainCurr = (m_tnsPredGains[channelIndex] >> 24) & UCHAR_MAX;
predGainPrev = (m_tnsPredGains[channelIndex] >> 16) & UCHAR_MAX;
while ((bestOrder > 1) && (predGainPrev >= predGainCurr)) // find lowest-order gain maximum
{
bestOrder--;
predGainCurr = predGainPrev;
predGainPrev = (m_tnsPredGains[channelIndex] >> (8 * bestOrder - 16)) & UCHAR_MAX;
}
return ((bestOrder == 1) && (m_parCorCoeffs[channelIndex][0] == 0) ? 0 : bestOrder);
}
unsigned SpecAnalyzer::getMeanAbsValues (const int32_t* const mdctSignal, const int32_t* const mdstSignal, const unsigned nSamplesInFrame,
const unsigned channelIndex, const uint16_t* const bandStartOffsets, const unsigned nBands,
uint32_t* const meanBandValues)
{
if ((mdctSignal == nullptr) || (bandStartOffsets == nullptr) || (meanBandValues == nullptr) || (channelIndex >= USAC_MAX_NUM_CHANNELS) ||
(nSamplesInFrame > 2048) || (nSamplesInFrame < 2) || (nBands > nSamplesInFrame))
{
return 1; // invalid arguments error
}
if (mdstSignal != nullptr) // use complex-valued spectral data
{
for (unsigned b = 0; b < nBands; b++)
{
const unsigned bandOffset = __min (nSamplesInFrame, bandStartOffsets[b]);
const unsigned bandWidth = __min (nSamplesInFrame, bandStartOffsets[b + 1]) - bandOffset;
const unsigned anaBandIdx = bandOffset >> SA_BW_SHIFT;
if ((anaBandIdx < m_numAnaBands[channelIndex]) && (bandOffset == (anaBandIdx << SA_BW_SHIFT)) && ((bandWidth & (SA_BW - 1)) == 0))
{
const uint32_t* const anaAbsVal = &m_meanAbsValue[channelIndex][anaBandIdx];
// data available from previous call to spectralAnalysis
meanBandValues[b] = (bandWidth == SA_BW ? *anaAbsVal : uint32_t (((int64_t) anaAbsVal[0] + (int64_t) anaAbsVal[1] + 1) >> 1));
}
else // no previous data available, compute mean magnitude
{
const int32_t* const bMdct = &mdctSignal[bandOffset];
const int32_t* const bMdst = &mdstSignal[bandOffset];
uint64_t sumAbsVal = 0;
for (int s = bandWidth - 1; s >= 0; s--)
{
#if SA_EXACT_COMPLEX_ABS
const double complexSqr = (double) bMdct[s] * (double) bMdct[s] + (double) bMdst[s] * (double) bMdst[s];
const unsigned absSample = unsigned (sqrt (complexSqr) + 0.5);
#else
const unsigned absReal = abs (bMdct[s]); // Richard Lyons, 1997; en.wikipedia.org/
const unsigned absImag = abs (bMdst[s]); // wiki/Alpha_max_plus_beta_min_algorithm
const unsigned absSample = (absReal > absImag ? absReal + ((absImag * 3) >> 3) : absImag + ((absReal * 3) >> 3));
#endif
sumAbsVal += absSample;
}
// average spectral sample magnitude across current band
meanBandValues[b] = uint32_t ((sumAbsVal + (bandWidth >> 1)) / bandWidth);
}
} // for b
}
else // no imaginary part available, real-valued spectral data
{
for (unsigned b = 0; b < nBands; b++)
{
const unsigned bandOffset = __min (nSamplesInFrame, bandStartOffsets[b]);
const unsigned bandWidth = __min (nSamplesInFrame, bandStartOffsets[b + 1]) - bandOffset;
const int32_t* const bMdct = &mdctSignal[bandOffset];
uint64_t sumAbsVal = 0;
for (int s = bandWidth - 1; s >= 0; s--)
{
const unsigned absSample = abs (bMdct[s]);
sumAbsVal += absSample;
}
// average spectral sample magnitude across frequency band
meanBandValues[b] = uint32_t ((sumAbsVal + (bandWidth >> 1)) / bandWidth);
} // for b
}
m_numAnaBands[channelIndex] = 0; // mark spectral data as used
return 0; // no error
}
void SpecAnalyzer::getSpecAnalysisStats (uint32_t avgSpecAnaStats[USAC_MAX_NUM_CHANNELS], const unsigned nChannels)
{
if ((avgSpecAnaStats == nullptr) || (nChannels > USAC_MAX_NUM_CHANNELS))
{
return;
}
memcpy (avgSpecAnaStats, m_specAnaStats, nChannels * sizeof (uint32_t));
}
void SpecAnalyzer::getSpectralBandwidth (uint16_t bandwidthOffset[USAC_MAX_NUM_CHANNELS], const unsigned nChannels)
{
if ((bandwidthOffset == nullptr) || (nChannels > USAC_MAX_NUM_CHANNELS))
{
return;
}
memcpy (bandwidthOffset, m_bandwidthOff, nChannels * sizeof (uint16_t));
}
unsigned SpecAnalyzer::initLinPredictor (LinearPredictor* const linPredictor)
{
if (linPredictor == nullptr)
{
return 1; // invalid arguments error
}
m_tnsPredictor = linPredictor;
return 0; // no error
}
#if SA_OPT_WINDOW_GROUPING
unsigned SpecAnalyzer::optimizeGrouping (const unsigned channelIndex, const unsigned prefBandwidth, const unsigned prefGroupingIndex)
{
const uint32_t* meanAbsValCurr = m_meanAbsValue[channelIndex];
const uint32_t numAnaBandsInCh = m_numAnaBands [channelIndex];
unsigned grpIdxCurr = prefGroupingIndex, maxBands, numBands;
uint64_t energyCurrHF, energyPrefHF;
uint32_t energyCurrLF, energyPrefLF;
unsigned b;
if ((prefBandwidth > 2048) || (grpIdxCurr == 0) || (grpIdxCurr >= 8) || (channelIndex >= USAC_MAX_NUM_CHANNELS) || (numAnaBandsInCh == 0))
{
return 8; // invalid arguments error, or pypassing
}
numBands = numAnaBandsInCh >> 3;
maxBands = numAnaBandsInCh << SA_BW_SHIFT; // available bandwidth, equal to nSamplesInFrame
maxBands = (numBands * __min (maxBands, prefBandwidth) + (maxBands >> 1)) / maxBands;
if (maxBands * numBands == 0) return 8; // low/no BW
if (grpIdxCurr < 7) grpIdxCurr++; // after transient
meanAbsValCurr += grpIdxCurr * numBands;
grpIdxCurr++;
energyPrefLF = meanAbsValCurr[0] >> 1; // - 6 dB
energyPrefHF = 0;
for (b = maxBands - 1; b > 0; b--) // avoid LF band
{
energyPrefHF += meanAbsValCurr[b];
}
energyPrefHF >>= 1; // - 6 dB
do // check whether HF or LF transient starts earlier than preferred grouping index suggests
{
meanAbsValCurr -= numBands;
grpIdxCurr--;
energyCurrLF = meanAbsValCurr[0];
energyCurrHF = 0;
for (b = maxBands - 1; b > 0; b--) // prev. window
{
energyCurrHF += meanAbsValCurr[b];
}
}
while ((grpIdxCurr > 1) && (energyCurrHF >= energyPrefHF) && (energyCurrLF >= energyPrefLF));
return __min (grpIdxCurr, prefGroupingIndex); // final optimized grouping index
}
#endif // SA_OPT_WINDOW_GROUPING
unsigned SpecAnalyzer::spectralAnalysis (const int32_t* const mdctSignals[USAC_MAX_NUM_CHANNELS],
const int32_t* const mdstSignals[USAC_MAX_NUM_CHANNELS],
const unsigned nChannels, const unsigned nSamplesInFrame, const unsigned samplingRate,
const unsigned lfeChannelIndex /*= USAC_MAX_NUM_CHANNELS*/) // to skip an LFE channel
{
const unsigned lpcStopBand16k = (samplingRate <= 32000 ? nSamplesInFrame : (32000 * nSamplesInFrame) / samplingRate) >> SA_BW_SHIFT;
const unsigned thresholdSlope = (48000 + SA_EPS * samplingRate) / 96000;
const unsigned thresholdStart = samplingRate >> 15;
if ((mdctSignals == nullptr) || (nChannels > USAC_MAX_NUM_CHANNELS) || (lfeChannelIndex > USAC_MAX_NUM_CHANNELS) ||
(nSamplesInFrame > 2048) || (nSamplesInFrame < 2) || (samplingRate < 7350) || (samplingRate > 96000))
{
return 1; // invalid arguments error
}
for (unsigned ch = 0; ch < nChannels; ch++)
{
const int32_t* const chMdct = mdctSignals[ch];
const int32_t* const chMdst = (mdstSignals == nullptr ? nullptr : mdstSignals[ch]);
// --- get L1 norm and max value in each band
uint16_t idxMaxSpec = 0;
uint64_t sumAvgBand = 0;
uint64_t sumMaxBand = 0;
uint32_t valMaxSpec = 0;
int b;
if (ch == lfeChannelIndex) // no analysis
{
m_bandwidthOff[ch] = LFE_MAX;
m_numAnaBands [ch] = 0;
m_specAnaStats[ch] = 0; // flat/stationary frame
continue;
}
m_bandwidthOff[ch] = 0;
m_numAnaBands [ch] = nSamplesInFrame >> SA_BW_SHIFT;
for (b = m_numAnaBands[ch] - 1; b >= 0; b--)
{
const uint16_t offs = b << SA_BW_SHIFT; // start offset of current analysis band
const int32_t* const bMdct = &chMdct[offs];
const int32_t* const bMdst = (chMdst == nullptr ? nullptr : &chMdst[offs]);
uint16_t maxAbsIdx = 0;
uint32_t maxAbsVal = 0, tmp = UINT_MAX;
uint64_t sumAbsVal = 0;
if (bMdst != nullptr) // complex-valued spectrum
{
for (int s = SA_BW - 1; s >= 0; s--)
{
// sum absolute values of complex signal, derive L1 norm, peak value, and peak index
#if SA_EXACT_COMPLEX_ABS
const double complexSqr = (double) bMdct[s] * (double) bMdct[s] + (double) bMdst[s] * (double) bMdst[s];
const unsigned absSample = unsigned (sqrt (complexSqr) + 0.5);
#else
const unsigned absReal = abs (bMdct[s]); // Richard Lyons, 1997; en.wikipedia.org/
const unsigned absImag = abs (bMdst[s]); // wiki/Alpha_max_plus_beta_min_algorithm
const unsigned absSample = (absReal > absImag ? absReal + ((absImag * 3) >> 3) : absImag + ((absReal * 3) >> 3));
#endif
sumAbsVal += absSample;
if (offs + s > 0) // exclude DC from max/min
{
if (maxAbsVal < absSample) // maximum data
{
maxAbsVal = absSample;
maxAbsIdx = (uint16_t) s;
}
if (tmp/*min*/> absSample) // minimum data
{
tmp/*min*/= absSample;
}
} // b > 0
}
}
else // real-valued spectrum, no imaginary part
{
for (int s = SA_BW - 1; s >= 0; s--)
{
// obtain absolute values of real signal, derive L1 norm, peak value, and peak index
const unsigned absSample = abs (bMdct[s]);
sumAbsVal += absSample;
if (offs + s > 0) // exclude DC from max/min
{
if (maxAbsVal < absSample) // maximum data
{
maxAbsVal = absSample;
maxAbsIdx = (uint16_t) s;
}
if (tmp/*min*/> absSample) // minimum data
{
tmp/*min*/= absSample;
}
}
}
}
// bandwidth detection
if ((m_bandwidthOff[ch] == 0) && (maxAbsVal > __max (thresholdSlope * (thresholdStart + b), SA_EPS)))
{
m_bandwidthOff[ch] = __max (maxAbsIdx + 5/*guard*/, SA_BW) + offs;
m_bandwidthOff[ch] = __min (m_bandwidthOff[ch], nSamplesInFrame);
}
// save mean magnitude
tmp/*mean*/ = uint32_t ((sumAbsVal + (1 << (SA_BW_SHIFT - 1))) >> SA_BW_SHIFT);
m_meanAbsValue[ch][b] = tmp;
// spectral statistics
if (b > 0)
{
sumAvgBand += tmp;
sumMaxBand += maxAbsVal;
}
if (valMaxSpec < maxAbsVal)
{
valMaxSpec = maxAbsVal;
idxMaxSpec = maxAbsIdx + offs;
}
} // for b
// --- spectral analysis statistics for frame
b = 1;
while (((unsigned) b + 1 < lpcStopBand16k) && ((uint64_t) m_meanAbsValue[ch][b] * (m_numAnaBands[ch] - 1) > sumAvgBand)) b++;
b = __min (m_bandwidthOff[ch], b << SA_BW_SHIFT);
// obtain prediction gain across spectrum
m_tnsPredGains[ch] = m_tnsPredictor->calcParCorCoeffs (&chMdct[b], __min (m_bandwidthOff[ch], lpcStopBand16k << SA_BW_SHIFT) - b,
MAX_PREDICTION_ORDER, m_parCorCoeffs[ch]);
m_specAnaStats[ch] = packAvgSpecAnalysisStats (sumAvgBand, sumMaxBand, m_tnsPredGains[ch] >> 24, idxMaxSpec, (unsigned) b >> SA_BW_SHIFT);
} // for ch
return 0; // no error
}

61
src/lib/specAnalysis.h Normal file
View File

@ -0,0 +1,61 @@
/* specAnalysis.h - header file for class providing spectral analysis of MCLT signals
* written by C. R. Helmrich, last modified in 2019 - 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.
*/
#ifndef _SPEC_ANALYSIS_H_
#define _SPEC_ANALYSIS_H_
#include "exhaleLibPch.h"
#include "linearPrediction.h"
// constants, experimental macros
#define SA_BW_SHIFT 5
#define SA_BW (1 << SA_BW_SHIFT)
#define SA_EPS 1024
#define SA_EXACT_COMPLEX_ABS 0
#define SA_OPT_WINDOW_GROUPING 1
// spectral signal analysis class
class SpecAnalyzer
{
private:
// member variables
uint16_t m_bandwidthOff[USAC_MAX_NUM_CHANNELS];
uint32_t m_meanAbsValue[USAC_MAX_NUM_CHANNELS][1024 >> SA_BW_SHIFT];
uint16_t m_numAnaBands [USAC_MAX_NUM_CHANNELS];
short m_parCorCoeffs[USAC_MAX_NUM_CHANNELS][MAX_PREDICTION_ORDER];
uint32_t m_specAnaStats[USAC_MAX_NUM_CHANNELS];
uint32_t m_tnsPredGains[USAC_MAX_NUM_CHANNELS];
LinearPredictor* m_tnsPredictor;
public:
// constructor
SpecAnalyzer ();
// destructor
~SpecAnalyzer () { }
// public functions
unsigned getLinPredCoeffs (short parCorCoeffs[MAX_PREDICTION_ORDER], const unsigned channelIndex); // returns best filter order
unsigned getMeanAbsValues (const int32_t* const mdctSignal, const int32_t* const mdstSignal, const unsigned nSamplesInFrame,
const unsigned channelIndex, const uint16_t* const bandStartOffsets, const unsigned nBands,
uint32_t* const meanBandValues);
void getSpecAnalysisStats (uint32_t avgSpecAnaStats[USAC_MAX_NUM_CHANNELS], const unsigned nChannels);
void getSpectralBandwidth (uint16_t bandwidthOffset[USAC_MAX_NUM_CHANNELS], const unsigned nChannels);
unsigned initLinPredictor (LinearPredictor* const linPredictor);
#if SA_OPT_WINDOW_GROUPING
unsigned optimizeGrouping (const unsigned channelIndex, const unsigned preferredBandwidth, const unsigned preferredGrouping);
#endif
unsigned spectralAnalysis (const int32_t* const mdctSignals[USAC_MAX_NUM_CHANNELS],
const int32_t* const mdstSignals[USAC_MAX_NUM_CHANNELS],
const unsigned nChannels, const unsigned nSamplesInFrame, const unsigned samplingRate,
const unsigned lfeChannelIndex = USAC_MAX_NUM_CHANNELS); // to skip an LFE channel
}; // SpecAnalyzer
#endif // _SPEC_ANALYSIS_H_

223
src/lib/specGapFilling.cpp Normal file
View File

@ -0,0 +1,223 @@
/* specGapFilling.cpp - source file for class with spectral gap filling coding methods
* written by C. R. Helmrich, last modified in 2019 - 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 "exhaleLibPch.h"
#include "specGapFilling.h"
// ISO/IEC 23003-3, Table 109
static const uint16_t noiseFillingStartOffset[2 /*long/short*/][2 /*768/1024*/] = {{120, 160}, {15, 20}};
// constructor
SpecGapFiller::SpecGapFiller ()
{
m_1stGapFillSfb = 0;
memset (m_1stNonZeroSfb, 0, sizeof (m_1stNonZeroSfb));
}
// public functions
unsigned char SpecGapFiller::getSpecGapFillParams (const SfbQuantizer& sfbQuantizer, const unsigned char* const quantMagn,
const unsigned char numSwbShort, SfbGroupData& grpData /*modified*/,
const unsigned nSamplesInFrame /*= 1024*/)
{
const unsigned* const coeffMagn = sfbQuantizer.getCoeffMagnPtr ();
const double* const sfNormFacs = sfbQuantizer.getSfNormTabPtr ();
const uint16_t sfbsPerGrp = grpData.sfbsPerGroup;
const uint16_t windowNfso = noiseFillingStartOffset[grpData.numWindowGroups == 1 ? 0 : 1][nSamplesInFrame >> 10];
unsigned char scaleFactorLimit = 0;
uint16_t u = 0;
short diff = 0, s = 0;
double magnSum = 0.0;
if ((coeffMagn == nullptr) || (sfNormFacs == nullptr) || (quantMagn == nullptr) ||
(numSwbShort < MIN_NUM_SWB_SHORT) || (numSwbShort > MAX_NUM_SWB_SHORT) || (nSamplesInFrame > 1024))
{
return 1; // invalid arguments error
}
// --- determine noise_level as mean of all coeff magnitudes at zero-quantized coeff indices
m_1stGapFillSfb = 0;
memset (m_1stNonZeroSfb, -1, sizeof (m_1stNonZeroSfb));
for (uint16_t gr = 0; gr < grpData.numWindowGroups; gr++)
{
const uint16_t* grpOff = &grpData.sfbOffsets[numSwbShort * gr];
const uint32_t* grpRms = &grpData.sfbRmsValues[numSwbShort * gr]; // quant./coding stats
const unsigned char* grpScFacs = &grpData.scaleFactors[numSwbShort * gr];
const uint16_t grpLength = grpData.windowGroupLength[gr];
const uint16_t grpNfso = grpOff[0] + grpLength * windowNfso;
const uint16_t sfbLimit = (grpData.numWindowGroups == 1 ? sfbsPerGrp - (grpOff[sfbsPerGrp] >= nSamplesInFrame ? 1 : 0)
: __min (sfbsPerGrp, numSwbShort - 1)); // no high frequencies
for (uint16_t b = 0; b < sfbLimit; b++) // find first gap-fill SFB and noise_level
{
const uint16_t sfbStart = grpOff[b];
const uint16_t sfbWidth = grpOff[b + 1] - sfbStart;
const unsigned* const sfbMagn = &coeffMagn[sfbStart];
const unsigned char* sfbQuant = &quantMagn[sfbStart];
const unsigned char sFac = grpScFacs[b];
if (sfbStart < grpNfso) // SFBs below noiseFillingStartOffset
{
if ((grpRms[b] >> 16) > 0) // the SFB is non-zero quantized
{
if (m_1stNonZeroSfb[gr] < 0) m_1stNonZeroSfb[gr] = b;
if (scaleFactorLimit < sFac) scaleFactorLimit = sFac;
}
}
else // sfbStart >= grpNfso, so above noiseFillingStartOffset
{
if (m_1stNonZeroSfb[gr] < 0) m_1stNonZeroSfb[gr] = b;
if (m_1stGapFillSfb == 0) m_1stGapFillSfb = b;
if ((grpRms[b] >> 16) > 0) // the SFB is non-zero quantized
{
unsigned sfbMagnSum = 0; // NOTE: may overflow, but unlikely, and 32 bit is faster
if (scaleFactorLimit < sFac) scaleFactorLimit = sFac;
#if SGF_OPT_SHORT_WIN_CALC
if (grpLength > 1) // eight-short windows: SFB ungrouping
{
const uint32_t* sfbMagnPtr = sfbMagn;
const unsigned char* sfbQuantPtr = sfbQuant;
const int swbLength = (sfbWidth * oneTwentyEightOver[grpLength]) >> 7; // sfbWidth / grpLength
unsigned sfbMagnMin = USHRT_MAX;
uint16_t uMin = 0;
for (uint16_t w = 0; w < grpLength; w++)
{
unsigned sfbMagnWin = 0;
uint16_t uWin = 0;
for (int i = swbLength - 1; i >= 0; i--, sfbMagnPtr++, sfbQuantPtr++)
{
if ((*sfbQuantPtr == 0) && (i == 0 || i == swbLength - 1 || *(sfbQuantPtr-1) + *(sfbQuantPtr+1) < 2))
{
sfbMagnWin += *sfbMagnPtr;
uWin++;
}
}
if (sfbMagnWin * (uint64_t) uMin < sfbMagnMin * (uint64_t) uWin) // new minimum
{
sfbMagnMin = sfbMagnWin;
uMin = uWin;
}
} // for w
sfbMagnSum += sfbMagnMin * grpLength; // scaled minimum
u += uMin * grpLength;
}
else
#endif
for (int i = sfbWidth - 1; i >= 0; i--)
{
if ((sfbQuant[i] == 0) && (sfbQuant[i - 1] + sfbQuant[i + 1] < 2))
{
sfbMagnSum += sfbMagn[i];
u++;
}
}
magnSum += sfbMagnSum * sfNormFacs[grpScFacs[b]];
}
}
} // for b
// clip to non-negative value for get function and memset below
if (m_1stNonZeroSfb[gr] < 0) m_1stNonZeroSfb[gr] = 0;
} // for gr
// determine quantized noise_level from normalized mean magnitude
if ((u < 4) || (magnSum * 359.0 < u * 16.0))
{
if (sfbsPerGrp <= m_1stGapFillSfb) return 0; // silent, level 0
magnSum = 1.0; u = 4; // max. level
}
u = __min (7, uint16_t (14.47118288 + 9.965784285 * log10 (magnSum / (double) u)));
magnSum = pow (2.0, (14 - u) / 3.0); // noiseVal^-1, 23003-3, 7.2
// --- calculate gap-fill scale factors for zero quantized SFBs, then determine noise_offset
u <<= 5; // left-shift for bit-stream
if (scaleFactorLimit < SGF_LIMIT) scaleFactorLimit = SGF_LIMIT;
for (uint16_t gr = 0; gr < grpData.numWindowGroups; gr++)
{
const uint16_t* grpOff = &grpData.sfbOffsets[numSwbShort * gr];
const uint32_t* grpRms = &grpData.sfbRmsValues[numSwbShort * gr]; // quant./coding stats
unsigned char* const grpScFacs = &grpData.scaleFactors[numSwbShort * gr];
for (uint16_t b = m_1stGapFillSfb; b < sfbsPerGrp; b++) // calculate scale factors
{
if ((grpRms[b] >> 16) == 0) // the SFB is all-zero quantized
{
if (grpScFacs[b] > 0)
{
const uint16_t sfbStart = grpOff[b];
const int16_t sfbWidthM1 = grpOff[b + 1] - sfbStart - 1;
const unsigned* sfbMagn = &coeffMagn[sfbStart];
unsigned sfbMagnMax = 0;
unsigned sfbMagnSum = 0; // NOTE: may overflow, but unlikely, and 32 bit is faster
for (int i = sfbWidthM1; i >= 0; i--)
{
sfbMagnSum += sfbMagn[i];
if (sfbMagnMax < sfbMagn[i]) sfbMagnMax = sfbMagn[i]; // sum up without maximum
}
grpScFacs[b] = sfbQuantizer.getScaleFacOffset (((sfbMagnSum - sfbMagnMax) * magnSum) / (double) sfbWidthM1);
if (grpScFacs[b] > scaleFactorLimit) grpScFacs[b] = scaleFactorLimit;
}
#if SGF_SF_PEAK_SMOOTHING
// save delta-code bits by smoothing scale factor peaks in zero quantized SFB ranges
if ((b > m_1stGapFillSfb) && ((grpRms[b - 1] >> 16) == 0) && ((grpRms[b - 2] >> 16) == 0) &&
(grpScFacs[b - 1] > grpScFacs[b]) && (grpScFacs[b - 1] > grpScFacs[b - 2]))
{
grpScFacs[b - 1] = (grpScFacs[b - 1] + __max (grpScFacs[b], grpScFacs[b - 2])) >> 1;
}
#endif
}
if ((b > m_1stGapFillSfb) && (((grpRms[b - 1] >> 16) > 0) ^ ((grpRms[b - 2] >> 16) > 0)))
{
diff += (int) grpScFacs[b - 1] - (int) grpScFacs[b - 2]; // sum up transition deltas
s++;
}
} // for b
} // for gr
if (s > 0)
{
diff = (diff + (s >> 1)*(diff < 0 ? -1 : 1)) / s; // mean delta
if (diff < -16) diff = -16;
else
if (diff >= 16) diff = 15;
}
s = __max (-diff, (short) scaleFactorLimit - SGF_LIMIT); // limit
for (uint16_t gr = 0; gr < grpData.numWindowGroups; gr++)
{
const uint32_t* grpRms = &grpData.sfbRmsValues[numSwbShort * gr]; // quant./coding stats
unsigned char* const grpScFacs = &grpData.scaleFactors[numSwbShort * gr];
for (uint16_t b = m_1stGapFillSfb; b < sfbsPerGrp; b++) // account f. noise_offset
{
if ((grpRms[b] >> 16) == 0) // the SFB is all-zero quantized
{
grpScFacs[b] = (unsigned char) __max (s, grpScFacs[b] - diff);
if (grpScFacs[b] > scaleFactorLimit) grpScFacs[b] = scaleFactorLimit;
}
} // for b
// repeat first significant scale factor downwards to save bits
memset (grpScFacs, grpScFacs[m_1stNonZeroSfb[gr]], m_1stNonZeroSfb[gr] * sizeof (unsigned char));
} // for gr
return CLIP_UCHAR (u | (diff + 16)); // combined level and offset
}

44
src/lib/specGapFilling.h Normal file
View File

@ -0,0 +1,44 @@
/* specGapFilling.h - header file for class with spectral gap filling coding methods
* written by C. R. Helmrich, last modified in 2019 - 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.
*/
#ifndef _SPEC_GAP_FILLING_H_
#define _SPEC_GAP_FILLING_H_
#include "exhaleLibPch.h"
#include "quantization.h"
// constants, experimental macro
#define SGF_LIMIT 2*INDEX_OFFSET
#define SGF_OPT_SHORT_WIN_CALC 1
#define SGF_SF_PEAK_SMOOTHING 1
// MDCT-domain gap filling class
class SpecGapFiller
{
private:
// member variables
uint16_t m_1stGapFillSfb;
int16_t m_1stNonZeroSfb[NUM_WINDOW_GROUPS];
public:
// constructor
SpecGapFiller ();
// destructor
~SpecGapFiller () { }
// public functions
uint16_t getFirstGapFillSfb () const { return m_1stGapFillSfb; }
uint8_t getSpecGapFillParams (const SfbQuantizer& sfbQuantizer, const uint8_t* const quantMagn,
const uint8_t numSwbShort, SfbGroupData& grpData /*modified*/,
const unsigned nSamplesInFrame = 1024);
}; // SpecGapFiller
#endif // _SPEC_GAP_FILLING_H_

270
src/lib/tempAnalysis.cpp Normal file
View File

@ -0,0 +1,270 @@
/* tempAnalysis.cpp - source file for class providing temporal analysis of PCM signals
* written by C. R. Helmrich, last modified in 2019 - 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 "exhaleLibPch.h"
#include "tempAnalysis.h"
// static helper functions
static unsigned updateAbsStats (const int32_t* const chSig, const int nSamples, unsigned* const maxAbsVal, int16_t* const maxAbsIdx)
{
const int32_t* const chSigM1 = chSig - 1; // for first-order high-pass
unsigned sumAbs = 0;
for (int s = nSamples - 1; s >= 0; s--)
{
// compute absolute values of high-pass signal, obtain L1 norm, peak value, and peak index
const unsigned absSample = abs (chSig[s] - chSigM1[s]);
sumAbs += absSample;
if (*maxAbsVal < absSample)
{
*maxAbsVal = absSample;
*maxAbsIdx = (int16_t) s;
}
}
return sumAbs;
}
static unsigned applyPitchPred (const int32_t* const chSig, const int nSamples, const int pitchLag, const int pitchSign = 1)
{
const int32_t* const chSigM1 = chSig - 1; // for first-order high-pass
const int32_t* const plSig = chSig - pitchLag; // & pitch prediction
const int32_t* const plSigM1 = plSig - 1;
unsigned sumAbs = 0;
for (int s = nSamples - 1; s >= 0; s--)
{
// compute absolute values of pitch-predicted high-pass signal, obtain L1 norm, peak value
sumAbs += abs (chSig[s] - chSigM1[s] - pitchSign * (plSig[s] - plSigM1[s]));
}
return sumAbs;
}
static inline uint32_t packAvgTempAnalysisStats (const unsigned avgAbsHpL, const unsigned avgAbsHpR, const unsigned avgAbsHpP,
const unsigned avgAbsPpLR, const unsigned maxAbsHpLR)
{
// spectral flatness, normalized for a value of 256 for noise-like, spectrally flat waveform
const unsigned flatSpec = 256 - int ((int64_t (avgAbsPpLR/*L+R sum*/ + TA_EPS) * 256) / (int64_t (avgAbsHpL + avgAbsHpR + TA_EPS)));
// temporal flatness, normalized for a value of 256 for steady low or mid-frequency sinusoid
const int32_t flatTemp = 256 - int ((int64_t (avgAbsHpL + avgAbsHpR + TA_EPS) * 402) / (int64_t (maxAbsHpLR/*L+R sum*/ + TA_EPS)));
// temporal stationarity, two sides, normalized for values of 256 for L1-stationary waveform
const int32_t statTmpL = 256 - int (((__min (avgAbsHpP, avgAbsHpL) + TA_EPS) * 256) / ((__max (avgAbsHpP, avgAbsHpL) + TA_EPS)));
const int32_t statTmpR = 256 - int (((__min (avgAbsHpL, avgAbsHpR) + TA_EPS) * 256) / ((__max (avgAbsHpL, avgAbsHpR) + TA_EPS)));
return (CLIP_UCHAR (flatSpec) << 24) | (CLIP_UCHAR (flatTemp) << 16) | (CLIP_UCHAR (statTmpL) << 8) | CLIP_UCHAR (statTmpR);
}
static inline int16_t getMaxAbsHpValueLocation (const unsigned maxAbsValL, const unsigned maxAbsValR, const unsigned maxAbsValP,
const int16_t maxAbsIdxL, const int16_t maxAbsIdxR)
{
if ((maxAbsValP * 8 < maxAbsValL * 3) || (maxAbsValL * 8 < maxAbsValR * 3)) // has transient
{
return maxAbsValR > maxAbsValL ? maxAbsIdxR : maxAbsIdxL;
}
return -1; // no transient
}
// constructor
TempAnalyzer::TempAnalyzer ()
{
for (unsigned ch = 0; ch < USAC_MAX_NUM_CHANNELS; ch++)
{
m_avgAbsHpPrev[ch] = 0;
m_maxAbsHpPrev[ch] = 0;
m_maxIdxHpPrev[ch] = 1;
m_pitchLagPrev[ch] = 0;
m_tempAnaStats[ch] = 0;
m_transientLoc[ch] = -1;
}
}
// public functions
void TempAnalyzer::getTempAnalysisStats (uint32_t avgTempAnaStats[USAC_MAX_NUM_CHANNELS], const unsigned nChannels)
{
if ((avgTempAnaStats == nullptr) || (nChannels > USAC_MAX_NUM_CHANNELS))
{
return;
}
memcpy (avgTempAnaStats, m_tempAnaStats, nChannels * sizeof (uint32_t));
}
void TempAnalyzer::getTransientLocation (int16_t maxHighPassValueLocation[USAC_MAX_NUM_CHANNELS], const unsigned nChannels)
{
if ((maxHighPassValueLocation == nullptr) || (nChannels > USAC_MAX_NUM_CHANNELS))
{
return;
}
memcpy (maxHighPassValueLocation, m_transientLoc, nChannels * sizeof (int16_t));
}
unsigned TempAnalyzer::temporalAnalysis (const int32_t* const timeSignals[USAC_MAX_NUM_CHANNELS], const unsigned nChannels,
const int nSamplesInFrame, const unsigned lookaheadOffset,
const unsigned lfeChannelIndex /*= USAC_MAX_NUM_CHANNELS*/) // to skip an LFE channel
{
const int halfFrameOffset = nSamplesInFrame >> 1;
if ((timeSignals == nullptr) || (nChannels > USAC_MAX_NUM_CHANNELS) || (lfeChannelIndex > USAC_MAX_NUM_CHANNELS) ||
(nSamplesInFrame > 2048) || (nSamplesInFrame < 2) || (lookaheadOffset > 2048) || (lookaheadOffset == 0))
{
return 1;
}
for (unsigned ch = 0; ch < nChannels; ch++)
{
const int32_t* const chSig = &timeSignals[ch][lookaheadOffset];
const int32_t* const chSigM1 = chSig - 1; // for first-order high-pass
// --- get L1 norm and pitch lag of both sides
unsigned sumAbsValL = 0, sumAbsValR = 0;
unsigned maxAbsValL = 0, maxAbsValR = 0;
int16_t maxAbsIdxL = 0, maxAbsIdxR = 0;
int splitPtL = 0;
int splitPtC = halfFrameOffset;
int splitPtR = nSamplesInFrame;
unsigned uL0 = abs (chSig[splitPtL ] - chSigM1[splitPtL ]);
unsigned uL1 = abs (chSig[splitPtC - 1] - chSigM1[splitPtC - 1]);
unsigned uR0 = abs (chSig[splitPtC ] - chSigM1[splitPtC ]);
unsigned uR1 = abs (chSig[splitPtR - 1] - chSigM1[splitPtR - 1]);
unsigned u; // temporary value - register?
if (ch == lfeChannelIndex) // no analysis
{
m_tempAnaStats[ch] = 0; // flat/stationary frame
m_transientLoc[ch] = -1;
continue;
}
do // find last sample of left-side region
{
sumAbsValL += (u = uL1);
splitPtC--;
}
while ((splitPtC > /*start +*/1) && (uL1 = abs (chSig[splitPtC - 1] - chSigM1[splitPtC - 1])) < u);
do // find first sample of left-side range
{
sumAbsValL += (u = uL0);
splitPtL++;
}
while ((splitPtL < splitPtC - 1) && (uL0 = abs (chSig[splitPtL] - chSigM1[splitPtL])) < u);
sumAbsValL += updateAbsStats (&chSig[splitPtL], splitPtC - splitPtL, &maxAbsValL, &maxAbsIdxL);
maxAbsIdxL += splitPtL; // left-side stats
if ((maxAbsIdxL == 1) && (maxAbsValL <= u))
{
maxAbsValL = u;
maxAbsIdxL--;
}
splitPtC = halfFrameOffset;
do // find last sample of right-side region
{
sumAbsValR += (u = uR1);
splitPtR--;
}
while ((splitPtR > splitPtC + 1) && (uR1 = abs (chSig[splitPtR - 1] - chSigM1[splitPtR - 1])) < u);
do // find first sample of right-side range
{
sumAbsValR += (u = uR0);
splitPtC++;
}
while ((splitPtC < splitPtR - 1) && (uR0 = abs (chSig[splitPtC] - chSigM1[splitPtC])) < u);
sumAbsValR += updateAbsStats (&chSig[splitPtC], splitPtR - splitPtC, &maxAbsValR, &maxAbsIdxR);
maxAbsIdxR += splitPtC; // right-side stats
if ((maxAbsIdxR == halfFrameOffset + 1) && (maxAbsValR <= u))
{
maxAbsValR = u;
maxAbsIdxR--;
}
// --- find best pitch lags minimizing L1 norms
if (sumAbsValL == 0 && sumAbsValR == 0)
{
m_tempAnaStats[ch] = 0; // flat/stationary frame
m_transientLoc[ch] = -1;
// re-init stats history for this channel
m_avgAbsHpPrev[ch] = 0; // sumAbsValR >> 9
m_maxAbsHpPrev[ch] = 0; // maxAbsValR
m_maxIdxHpPrev[ch] = 1; // (unsigned) maxAbsIdxR
m_pitchLagPrev[ch] = 0; // (unsigned) pLagBestR
}
else // nonzero signal in the current frame
{
const int maxAbsIdxP = __max ((int) m_maxIdxHpPrev[ch] - nSamplesInFrame, 1 - (int) lookaheadOffset);
unsigned sumAbsHpL = sumAbsValL, sumAbsHpR = sumAbsValR; // after high-pass filter
unsigned sumAbsPpL = sumAbsValL, sumAbsPpR = sumAbsValR; // after pitch prediction
int pLag, pLagBestR = 0, pSgn;
// test left-side pitch lag on this frame
pLag = __min (maxAbsIdxL - maxAbsIdxP, (int) lookaheadOffset - 1);
pSgn = (((chSig[maxAbsIdxL] - chSigM1[maxAbsIdxL] > 0) && (chSig[maxAbsIdxP] - chSigM1[maxAbsIdxP] < 0)) ||
((chSig[maxAbsIdxL] - chSigM1[maxAbsIdxL] < 0) && (chSig[maxAbsIdxP] - chSigM1[maxAbsIdxP] > 0)) ? -1 : 1);
if ((sumAbsValL = applyPitchPred (chSig, halfFrameOffset, pLag, pSgn)) < sumAbsPpL)
{
sumAbsPpL = sumAbsValL; // left side
}
#if TA_MORE_PITCH_TESTS
if ((sumAbsValR = applyPitchPred (chSig + halfFrameOffset, halfFrameOffset, pLag, pSgn)) < sumAbsPpR)
{
sumAbsPpR = sumAbsValR; // right side
pLagBestR = pLag;
}
#endif
// test right-side pitch lag on the frame
pLag = __min (maxAbsIdxR - maxAbsIdxL, (int) lookaheadOffset - 1);
pSgn = (((chSig[maxAbsIdxR] - chSigM1[maxAbsIdxR] > 0) && (chSig[maxAbsIdxL] - chSigM1[maxAbsIdxL] < 0)) ||
((chSig[maxAbsIdxR] - chSigM1[maxAbsIdxR] < 0) && (chSig[maxAbsIdxL] - chSigM1[maxAbsIdxL] > 0)) ? -1 : 1);
#if TA_MORE_PITCH_TESTS
if ((sumAbsValL = applyPitchPred (chSig, halfFrameOffset, pLag, pSgn)) < sumAbsPpL)
{
sumAbsPpL = sumAbsValL; // left side
}
#endif
if ((sumAbsValR = applyPitchPred (chSig + halfFrameOffset, halfFrameOffset, pLag, pSgn)) < sumAbsPpR)
{
sumAbsPpR = sumAbsValR; // right side
pLagBestR = pLag;
}
// try previous frame's lag on this frame
pLag = (m_pitchLagPrev[ch] > 0 ? (int) m_pitchLagPrev[ch] : __min (halfFrameOffset, (int) lookaheadOffset - 1));
pSgn = (((chSig[maxAbsIdxL] - chSigM1[maxAbsIdxL] > 0) && (chSig[maxAbsIdxL-pLag] - chSigM1[maxAbsIdxL-pLag] < 0)) ||
((chSig[maxAbsIdxL] - chSigM1[maxAbsIdxL] < 0) && (chSig[maxAbsIdxL-pLag] - chSigM1[maxAbsIdxL-pLag] > 0)) ? -1 : 1);
if ((sumAbsValL = applyPitchPred (chSig, halfFrameOffset, pLag, pSgn)) < sumAbsPpL)
{
sumAbsPpL = sumAbsValL; // left side
}
if ((sumAbsValR = applyPitchPred (chSig + halfFrameOffset, halfFrameOffset, pLag, pSgn)) < sumAbsPpR)
{
sumAbsPpR = sumAbsValR; // right side
pLagBestR = pLag;
}
// convert L1 norms into average values
sumAbsHpL = (sumAbsHpL + unsigned (halfFrameOffset >> 1)) / unsigned (halfFrameOffset);
sumAbsHpR = (sumAbsHpR + unsigned (halfFrameOffset >> 1)) / unsigned (halfFrameOffset);
sumAbsPpL = (sumAbsPpL + unsigned (halfFrameOffset >> 1)) / unsigned (halfFrameOffset);
sumAbsPpR = (sumAbsPpR + unsigned (halfFrameOffset >> 1)) / unsigned (halfFrameOffset);
// --- temporal analysis statistics for frame
m_tempAnaStats[ch] = packAvgTempAnalysisStats (sumAbsHpL, sumAbsHpR, m_avgAbsHpPrev[ch],
sumAbsPpL + sumAbsPpR, maxAbsValL + maxAbsValR);
m_transientLoc[ch] = getMaxAbsHpValueLocation (maxAbsValL, maxAbsValR, m_maxAbsHpPrev[ch],
maxAbsIdxL, maxAbsIdxR);
// update stats history for this channel
m_avgAbsHpPrev[ch] = sumAbsHpR;
m_maxAbsHpPrev[ch] = maxAbsValR;
m_maxIdxHpPrev[ch] = (unsigned) maxAbsIdxR;
m_pitchLagPrev[ch] = (unsigned) pLagBestR;
} // if sumAbsValL == 0 && sumAbsValR == 0
} // for ch
return 0; // no error
}

47
src/lib/tempAnalysis.h Normal file
View File

@ -0,0 +1,47 @@
/* tempAnalysis.h - header file for class providing temporal analysis of PCM signals
* written by C. R. Helmrich, last modified in 2019 - 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.
*/
#ifndef _TEMP_ANALYSIS_H_
#define _TEMP_ANALYSIS_H_
#include "exhaleLibPch.h"
// constants, experimental macros
#define TA_EPS 4096
#define TA_MORE_PITCH_TESTS 0
// temporal signal analysis class
class TempAnalyzer
{
private:
// member variables
unsigned m_avgAbsHpPrev[USAC_MAX_NUM_CHANNELS];
unsigned m_maxAbsHpPrev[USAC_MAX_NUM_CHANNELS];
unsigned m_maxIdxHpPrev[USAC_MAX_NUM_CHANNELS];
unsigned m_pitchLagPrev[USAC_MAX_NUM_CHANNELS];
uint32_t m_tempAnaStats[USAC_MAX_NUM_CHANNELS];
int16_t m_transientLoc[USAC_MAX_NUM_CHANNELS];
public:
// constructor
TempAnalyzer ();
// destructor
~TempAnalyzer () { }
// public functions
void getTempAnalysisStats (uint32_t avgTempAnaStats[USAC_MAX_NUM_CHANNELS], const unsigned nChannels);
void getTransientLocation (int16_t maxHighPassValueLocation[USAC_MAX_NUM_CHANNELS], const unsigned nChannels);
unsigned temporalAnalysis (const int32_t* const timeSignals[USAC_MAX_NUM_CHANNELS], const unsigned nChannels,
const int nSamplesInFrame, const unsigned lookaheadOffset,
const unsigned lfeChannelIndex = USAC_MAX_NUM_CHANNELS); // to skip an LFE channel
}; // TempAnalyzer
#endif // _TEMP_ANALYSIS_H_

305
src/makefile.base Normal file
View File

@ -0,0 +1,305 @@
## makefile.base - common make-file for compiling exhale on Linux and MacOS platforms
# written by C. R. Helmrich, last modified 2019 - see License.txt for legal notices
#
# The copyright in this software is being made available under a Modified BSD 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-2019 Christian R. Helmrich, project ecodis. All rights reserved.
##
#########################################################
# check CONFIG parameter
#########################################################
ifneq ($(CONFIG), CONSOLE)
ifneq ($(CONFIG), LIBRARY)
CONFIG_ERR = TRUE
endif
endif
#########################################################
# executables used
#########################################################
AR = ar
ASM = nasm
CPP = g++
LD = $(CPP)
#########################################################
# output file names and version information
#########################################################
ifeq ($(CONFIG), CONSOLE)
STAT_DEBUG_OUT = $(DIR_BIN)/$(PRD_NAME)d
STAT_RELEASE_OUT = $(DIR_BIN)/$(PRD_NAME)
DYN_DEBUG_OUT = $(DIR_BIN)/$(PRD_NAME)Dynd
DYN_RELEASE_OUT = $(DIR_BIN)/$(PRD_NAME)Dyn
else
ifeq ($(CONFIG), LIBRARY)
STAT_DEBUG_OUT = $(DIR_LIB)/lib$(PRD_NAME)d.a
STAT_RELEASE_OUT = $(DIR_LIB)/lib$(PRD_NAME).a
DYN_DEBUG_OUT = $(DIR_LIB)/lib$(PRD_NAME)Dynd.so
DYN_RELEASE_OUT = $(DIR_LIB)/lib$(PRD_NAME)Dyn.so
endif
endif
#########################################################
# c compiler flags
#########################################################
# default cpp flags for all configurations
#CPPFLAGS = -Wall -fPIC $(DEFS) -I$(CURDIR)/$(DIR_INC)
CPPFLAGS = -fPIC $(DEFS) -I$(CURDIR)/$(DIR_INC) -Wall -Wshadow -Wno-sign-compare -Werror -D_FILE_OFFSET_BITS=64
##########
# enforce 32-bit build : 1=yes, 0=no
##########
M32?= 0
ifeq ($(M32),1)
CPPFLAGS+=-m32
endif
##########
#
# debug cpp flags
DEBUG_CPPFLAGS = -g -D_DEBUG
#
# release cpp
RELEASE_CPPFLAGS = -O3 -Wuninitialized
#########################################################
# linker flags
#########################################################
# linker flags for all
ALL_LDFLAGS = -Wall
##########
# enforce 32-bit build : 1=yes, 0=no
##########
ifeq ($(M32),1)
ALL_LDFLAGS+=-m32
endif
##########
ifeq ($(CONFIG), LIBRARY)
# linker flags for library
# LDFLAGS = $(ALL_LDFLAGS) -shared -Wl,-Bsymbolic
LDFLAGS = $(ALL_LDFLAGS) -shared
#
# debug linker flags for library
DEBUG_LDFLAGS = -Wl,-soname,lib$(PRD_NAME)d.so
#
# release linker flags for library
RELEASE_LDFLAGS = -Wl,-soname,lib$(PRD_NAME).so
#
else
ifeq ($(CONFIG), CONSOLE)
# linker flags for console
LDFLAGS = $(ALL_LDFLAGS)
#
# debug linker flags for console
DEBUG_LDFLAGS =
#
# release linker flags for console
RELEASE_LDFLAGS =
#
endif
endif
#########################################################
# objects that have to be created
#########################################################
# the object types that have to be created
RELEASE_OBJS = $(OBJS:.o=.r.o)
DEBUG_OBJS = $(OBJS:.o=.d.o)
#########################################################
# rules
#########################################################
# suffixes
.SUFFIXES: .asm .cpp .d.o .r.o
## specification of assembler rules
ASMFLAGS = -f elf $(DEFS)
DEBUG_ASMFLAGS = -g
RELEASE_ASMFLAGS =
# creation of ASM debug objects
$(DIR_OBJ)/%.d.o: $(DIR_SRC)/%.asm
$(ASM) $(ASMFLAGS) $(DEBUG_ASMFLAGS) -o $@ $<
# creation of ASM release objects
$(DIR_OBJ)/%.r.o: $(DIR_SRC)/%.asm
$(ASM) $(ASMFLAGS) $(RELEASE_ASMFLAGS) -o $@ $<
## specification of C and C++ rules
define COMPILE_AND_DEPEND_DEBUG
$(CPP) -c -MMD -MF $(DIR_OBJ)/$*.d.d -MT $(DIR_OBJ)/$*.d.o $(CPPFLAGS) $(DEBUG_CPPFLAGS) -o $@ $(CURDIR)/$<
@cp $(DIR_OBJ)/$*.d.d $(DIR_OBJ)/$*.d.p; \
sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \
-e '/^$$/ d' -e 's/$$/ :/' < $(DIR_OBJ)/$*.d.d >> $(DIR_OBJ)/$*.d.p; \
rm -f $(DIR_OBJ)/$*.d.d
endef
define COMPILE_AND_DEPEND_RELEASE
$(CPP) -c -MMD -MF $(DIR_OBJ)/$*.r.d -MT $(DIR_OBJ)/$*.r.o $(CPPFLAGS) $(RELEASE_CPPFLAGS) -o $@ $(CURDIR)/$<
@cp $(DIR_OBJ)/$*.r.d $(DIR_OBJ)/$*.r.p; \
sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \
-e '/^$$/ d' -e 's/$$/ :/' < $(DIR_OBJ)/$*.r.d >> $(DIR_OBJ)/$*.r.p; \
rm -f $(DIR_OBJ)/$*.r.d
endef
# creation of C++ debug objects
$(DIR_OBJ)/%.d.o: $(DIR_SRC)/%.cpp
$(COMPILE_AND_DEPEND_DEBUG)
# creation of C++ release objects
$(DIR_OBJ)/%.r.o: $(DIR_SRC)/%.cpp
$(COMPILE_AND_DEPEND_RELEASE)
# creation of C debug objects
$(DIR_OBJ)/%.d.o: $(DIR_SRC)/%.c
$(COMPILE_AND_DEPEND_DEBUG)
# creation of C release objects
$(DIR_OBJ)/%.r.o: $(DIR_SRC)/%.c
$(COMPILE_AND_DEPEND_RELEASE)
## config dependent directory setup
ifeq ($(CONFIG), CONSOLE)
CHECK_DIRS = $(DIR_OBJ) $(DIR_BIN)
else
ifeq ($(CONFIG), LIBRARY)
CHECK_DIRS = $(DIR_OBJ) $(DIR_LIB)
endif
endif
## specification of build targets
all: check_errors debug release
debug: check_errors \
$(CHECK_DIRS) \
$(STAT_DEBUG_OUT)
# $(DYN_DEBUG_OUT) \
release: check_errors \
$(CHECK_DIRS) \
$(STAT_RELEASE_OUT)
# $(DYN_RELEASE_OUT) \
## check for configuration errors
check_errors:
@if [ "$(CONFIG_ERR)" = "TRUE" ]; then\
echo "ERROR: Wrong CONFIG parameter specified: $(CONFIG)";\
false;\
fi
## creation of output directories
$(DIR_BIN):
@if [ ! -d $(DIR_BIN) ]; then\
mkdir $(DIR_BIN);\
fi
$(DIR_OBJ):
@if [ ! -d $(DIR_OBJ) ]; then\
mkdir $(DIR_OBJ);\
fi
$(DIR_LIB):
@if [ ! -d $(DIR_LIB) ]; then\
mkdir $(DIR_LIB);\
fi
## creation of binary output files
ifeq ($(CONFIG), LIBRARY)
#
# create static debug out
$(STAT_DEBUG_OUT): $(DEBUG_OBJS)
$(AR) -crs $@ $(DEBUG_OBJS)
#
#
# create release debug out
$(STAT_RELEASE_OUT): $(RELEASE_OBJS)
$(AR) -crs $@ $(RELEASE_OBJS)
#
#
# create dynamic debug out
$(DYN_DEBUG_OUT): $(DYN_DEBUG_OUT)
ln -fs lib$(PRD_NAME)d.so $@
#
# create dynamic debug out
$(DYN_DEBUG_OUT): $(DEBUG_OBJS)
$(LD) $(LDFLAGS) $(DEBUG_LDFLAGS) -o $@ $(DEBUG_OBJS) -L$(DIR_LIB) $(LIBS) $(DYN_LIBS) $(DYN_DEBUG_LIBS)
#
#
# create dynamic release out
$(DYN_RELEASE_OUT): $(DYN_RELEASE_OUT)
ln -fs lib$(PRD_NAME).so $@
#
# create dynamic release out
$(DYN_RELEASE_OUT): $(RELEASE_OBJS)
$(LD) $(LDFLAGS) $(RELEASE_LDFLAGS) -o $@ $(RELEASE_OBJS) -L$(DIR_LIB) $(LIBS) $(DYN_LIBS) $(DYN_RELEASE_LIBS)
#
#
#
#
#
else
ifeq ($(CONFIG), CONSOLE)
#
# added linked libraries to target prerequisites - $(*_PREREQS) variables - to force relinking when libraries have been rebuilt
#
# create static debug out
$(STAT_DEBUG_OUT): $(DEBUG_OBJS) $(STAT_DEBUG_PREREQS)
$(LD) -o $@ $(LDFLAGS) $(DEBUG_LDFLAGS) $(DEBUG_OBJS) -L$(DIR_LIB) $(LIBS) $(STAT_LIBS) $(STAT_DEBUG_LIBS)
#
#
# create static release out
$(STAT_RELEASE_OUT): $(RELEASE_OBJS) $(STAT_RELEASE_PREREQS)
$(LD) -o $@ $(LDFLAGS) $(RELEASE_LDFLAGS) $(RELEASE_OBJS) -L$(DIR_LIB) $(LIBS) $(STAT_LIBS) $(STAT_RELEASE_LIBS)
#
#
# create dynamic debug out
$(DYN_DEBUG_OUT): $(DEBUG_OBJS) $(DYN_DEBUG_PREREQS)
$(LD) -o $@ $(LDFLAGS) $(DEBUG_LDFLAGS) $(DEBUG_OBJS) -L$(DIR_LIB) $(LIBS) $(DYN_LIBS) $(DYN_DEBUG_LIBS)
#
#
# create dynamic release out
$(DYN_RELEASE_OUT): $(RELEASE_OBJS) $(DYN_RELEASE_PREREQS)
$(LD) -o $@ $(LDFLAGS) $(RELEASE_LDFLAGS) $(RELEASE_OBJS) -L$(DIR_LIB) $(LIBS) $(DYN_LIBS) $(DYN_RELEASE_LIBS)
#
#
endif
endif
## clean: delete all created files
clean:
/bin/rm -rf $(DYN_DEBUG_OUT)
/bin/rm -rf $(DYN_RELEASE_OUT)
/bin/rm -rf $(STAT_DEBUG_OUT)
/bin/rm -rf $(STAT_RELEASE_OUT)
/bin/rm -rf $(DIR_OBJ)
## include needed dependency files
-include $(OBJS:.o=.d.p)
-include $(OBJS:.o=.r.p)