811 lines
22 KiB
C++
811 lines
22 KiB
C++
/*
|
|
* Copyright (C) 2005-2010 Team XBMC
|
|
* http://www.xbmc.org
|
|
*
|
|
* This Program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2, or (at your option)
|
|
* any later version.
|
|
*
|
|
* This Program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with XBMC; see the file COPYING. If not, write to
|
|
* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
* http://www.gnu.org/copyleft/gpl.html
|
|
*
|
|
*/
|
|
|
|
#ifndef __STDC_LIMIT_MACROS
|
|
#define __STDC_LIMIT_MACROS
|
|
#endif
|
|
|
|
#include <cstdlib>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <math.h>
|
|
|
|
#include "MathUtils.h"
|
|
#include "PCMRemap.h"
|
|
#include "utils/log.h"
|
|
#ifdef _WIN32
|
|
#include "../win32/PlatformDefs.h"
|
|
#endif
|
|
|
|
static enum PCMChannels PCMLayoutMap[PCM_MAX_LAYOUT][PCM_MAX_CH + 1] =
|
|
{
|
|
/* 2.0 */ {PCM_FRONT_LEFT, PCM_FRONT_RIGHT, PCM_INVALID},
|
|
/* 2.1 */ {PCM_FRONT_LEFT, PCM_FRONT_RIGHT, PCM_LOW_FREQUENCY, PCM_INVALID},
|
|
/* 3.0 */ {PCM_FRONT_LEFT, PCM_FRONT_RIGHT, PCM_FRONT_CENTER, PCM_INVALID},
|
|
/* 3.1 */ {PCM_FRONT_LEFT, PCM_FRONT_RIGHT, PCM_FRONT_CENTER, PCM_LOW_FREQUENCY, PCM_INVALID},
|
|
/* 4.0 */ {PCM_FRONT_LEFT, PCM_FRONT_RIGHT, PCM_BACK_LEFT, PCM_BACK_RIGHT, PCM_INVALID},
|
|
/* 4.1 */ {PCM_FRONT_LEFT, PCM_FRONT_RIGHT, PCM_BACK_LEFT, PCM_BACK_RIGHT, PCM_LOW_FREQUENCY, PCM_INVALID},
|
|
/* 5.0 */ {PCM_FRONT_LEFT, PCM_FRONT_RIGHT, PCM_FRONT_CENTER, PCM_BACK_LEFT, PCM_BACK_RIGHT, PCM_INVALID},
|
|
/* 5.1 */ {PCM_FRONT_LEFT, PCM_FRONT_RIGHT, PCM_FRONT_CENTER, PCM_BACK_LEFT, PCM_BACK_RIGHT, PCM_LOW_FREQUENCY, PCM_INVALID},
|
|
/* 7.0 */ {PCM_FRONT_LEFT, PCM_FRONT_RIGHT, PCM_FRONT_CENTER, PCM_SIDE_LEFT, PCM_SIDE_RIGHT, PCM_BACK_LEFT, PCM_BACK_RIGHT, PCM_INVALID},
|
|
/* 7.1 */ {PCM_FRONT_LEFT, PCM_FRONT_RIGHT, PCM_FRONT_CENTER, PCM_SIDE_LEFT, PCM_SIDE_RIGHT, PCM_BACK_LEFT, PCM_BACK_RIGHT, PCM_LOW_FREQUENCY, PCM_INVALID}
|
|
};
|
|
|
|
/*
|
|
map missing output into channel @ volume level
|
|
the order of this table is important, mix tables can not depend on channels that have not been defined yet
|
|
eg, FC can only be mixed into FL, FR as they are the only channels that have been defined
|
|
*/
|
|
#define PCM_MAX_MIX 3
|
|
static struct PCMMapInfo PCMDownmixTable[PCM_MAX_CH][PCM_MAX_MIX] =
|
|
{
|
|
/* PCM_FRONT_LEFT */
|
|
{
|
|
{PCM_INVALID}
|
|
},
|
|
/* PCM_FRONT_RIGHT */
|
|
{
|
|
{PCM_INVALID}
|
|
},
|
|
/* PCM_FRONT_CENTER */
|
|
{
|
|
{PCM_FRONT_LEFT_OF_CENTER , 1.0},
|
|
{PCM_FRONT_RIGHT_OF_CENTER, 1.0},
|
|
{PCM_INVALID}
|
|
},
|
|
/* PCM_LOW_FREQUENCY */
|
|
{
|
|
/*
|
|
A/52B 7.8 paragraph 2 recomends +10db
|
|
but due to horrible clipping when normalize
|
|
is disabled we set this to 1.0
|
|
*/
|
|
{PCM_FRONT_LEFT , 1.0},//3.5},
|
|
{PCM_FRONT_RIGHT , 1.0},//3.5},
|
|
{PCM_INVALID}
|
|
},
|
|
/* PCM_BACK_LEFT */
|
|
{
|
|
{PCM_FRONT_LEFT , 1.0},
|
|
{PCM_INVALID}
|
|
},
|
|
/* PCM_BACK_RIGHT */
|
|
{
|
|
{PCM_FRONT_RIGHT , 1.0},
|
|
{PCM_INVALID}
|
|
},
|
|
/* PCM_FRONT_LEFT_OF_CENTER */
|
|
{
|
|
{PCM_FRONT_LEFT , 1.0},
|
|
{PCM_FRONT_CENTER , 1.0, true},
|
|
{PCM_INVALID}
|
|
},
|
|
/* PCM_FRONT_RIGHT_OF_CENTER */
|
|
{
|
|
{PCM_FRONT_RIGHT , 1.0},
|
|
{PCM_FRONT_CENTER , 1.0, true},
|
|
{PCM_INVALID}
|
|
},
|
|
/* PCM_BACK_CENTER */
|
|
{
|
|
{PCM_BACK_LEFT , 1.0},
|
|
{PCM_BACK_RIGHT , 1.0},
|
|
{PCM_INVALID}
|
|
},
|
|
/* PCM_SIDE_LEFT */
|
|
{
|
|
{PCM_FRONT_LEFT , 1.0},
|
|
{PCM_BACK_LEFT , 1.0},
|
|
{PCM_INVALID}
|
|
},
|
|
/* PCM_SIDE_RIGHT */
|
|
{
|
|
{PCM_FRONT_RIGHT , 1.0},
|
|
{PCM_BACK_RIGHT , 1.0},
|
|
{PCM_INVALID}
|
|
},
|
|
/* PCM_TOP_FRONT_LEFT */
|
|
{
|
|
{PCM_FRONT_LEFT , 1.0},
|
|
{PCM_INVALID}
|
|
},
|
|
/* PCM_TOP_FRONT_RIGHT */
|
|
{
|
|
{PCM_FRONT_RIGHT , 1.0},
|
|
{PCM_INVALID}
|
|
},
|
|
/* PCM_TOP_FRONT_CENTER */
|
|
{
|
|
{PCM_TOP_FRONT_LEFT , 1.0},
|
|
{PCM_TOP_FRONT_RIGHT , 1.0},
|
|
{PCM_INVALID}
|
|
},
|
|
/* PCM_TOP_CENTER */
|
|
{
|
|
{PCM_TOP_FRONT_LEFT , 1.0},
|
|
{PCM_TOP_FRONT_RIGHT , 1.0},
|
|
{PCM_INVALID}
|
|
},
|
|
/* PCM_TOP_BACK_LEFT */
|
|
{
|
|
{PCM_BACK_LEFT , 1.0},
|
|
{PCM_INVALID}
|
|
},
|
|
/* PCM_TOP_BACK_RIGHT */
|
|
{
|
|
{PCM_BACK_RIGHT , 1.0},
|
|
{PCM_INVALID}
|
|
},
|
|
/* PCM_TOP_BACK_CENTER */
|
|
{
|
|
{PCM_TOP_BACK_LEFT , 1.0},
|
|
{PCM_TOP_BACK_RIGHT , 1.0},
|
|
{PCM_INVALID}
|
|
}
|
|
};
|
|
|
|
CPCMRemap::CPCMRemap() :
|
|
m_inSet (false),
|
|
m_outSet (false),
|
|
m_inChannels (0),
|
|
m_outChannels (0),
|
|
m_inSampleSize(0),
|
|
m_ignoreLayout(false),
|
|
m_buf(NULL),
|
|
m_bufsize(0),
|
|
m_attenuation (1.0),
|
|
m_attenuationInc(0.0),
|
|
m_attenuationMin(1.0),
|
|
m_sampleRate (48000.0), //safe default
|
|
m_holdCounter (0),
|
|
m_limiterEnabled(false)
|
|
{
|
|
Dispose();
|
|
}
|
|
|
|
CPCMRemap::~CPCMRemap()
|
|
{
|
|
Dispose();
|
|
}
|
|
|
|
void CPCMRemap::Dispose()
|
|
{
|
|
free(m_buf);
|
|
m_buf = NULL;
|
|
m_bufsize = 0;
|
|
}
|
|
|
|
/* resolves the channels recursively and returns the new index of tablePtr */
|
|
struct PCMMapInfo* CPCMRemap::ResolveChannel(enum PCMChannels channel, float level, bool ifExists, std::vector<enum PCMChannels> path, struct PCMMapInfo *tablePtr)
|
|
{
|
|
if (channel == PCM_INVALID) return tablePtr;
|
|
|
|
/* if its a 1 to 1 mapping, return */
|
|
if (m_useable[channel])
|
|
{
|
|
tablePtr->channel = channel;
|
|
tablePtr->level = level;
|
|
|
|
++tablePtr;
|
|
tablePtr->channel = PCM_INVALID;
|
|
return tablePtr;
|
|
} else
|
|
if (ifExists)
|
|
level /= 2;
|
|
|
|
struct PCMMapInfo *info;
|
|
std::vector<enum PCMChannels>::iterator itt;
|
|
|
|
for(info = PCMDownmixTable[channel]; info->channel != PCM_INVALID; ++info)
|
|
{
|
|
/* make sure we are not about to recurse into ourself */
|
|
bool found = false;
|
|
for(itt = path.begin(); itt != path.end(); ++itt)
|
|
if (*itt == info->channel)
|
|
{
|
|
found = true;
|
|
break;
|
|
}
|
|
|
|
if (found)
|
|
continue;
|
|
|
|
path.push_back(channel);
|
|
float l = (info->level * (level / 100)) * 100;
|
|
tablePtr = ResolveChannel(info->channel, l, info->ifExists, path, tablePtr);
|
|
path.pop_back();
|
|
}
|
|
|
|
return tablePtr;
|
|
}
|
|
|
|
/*
|
|
Builds a lookup table without extra adjustments, useful if we simply
|
|
want to find out which channels are active.
|
|
For final adjustments, BuildMap() is used.
|
|
*/
|
|
void CPCMRemap::ResolveChannels()
|
|
{
|
|
unsigned int in_ch, out_ch;
|
|
bool hasSide = false;
|
|
bool hasBack = false;
|
|
|
|
memset(m_useable, 0, sizeof(m_useable));
|
|
|
|
if (!m_outSet)
|
|
{
|
|
/* Output format is not known yet, assume the full configured map.
|
|
* Note that m_ignoreLayout-using callers normally ignore the result of
|
|
* this function when !m_outSet, when it is called only for an advice for
|
|
* the caller of SetInputFormat about the best possible output map, and
|
|
* they can still set their output format arbitrarily in their call to
|
|
* SetOutputFormat. */
|
|
for (enum PCMChannels *chan = PCMLayoutMap[m_channelLayout]; *chan != PCM_INVALID; ++chan)
|
|
m_useable[*chan] = true;
|
|
}
|
|
else if (m_ignoreLayout)
|
|
{
|
|
for(out_ch = 0; out_ch < m_outChannels; ++out_ch)
|
|
m_useable[m_outMap[out_ch]] = true;
|
|
}
|
|
else
|
|
{
|
|
/* figure out what channels we have and can use */
|
|
for(enum PCMChannels *chan = PCMLayoutMap[m_channelLayout]; *chan != PCM_INVALID; ++chan)
|
|
{
|
|
for(out_ch = 0; out_ch < m_outChannels; ++out_ch)
|
|
if (m_outMap[out_ch] == *chan)
|
|
{
|
|
m_useable[*chan] = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* force mono audio to front left and front right */
|
|
if (!m_ignoreLayout && m_inChannels == 1 && m_inMap[0] == PCM_FRONT_CENTER
|
|
&& m_useable[PCM_FRONT_LEFT] && m_useable[PCM_FRONT_RIGHT])
|
|
{
|
|
CLog::Log(LOGDEBUG, "CPCMRemap: Mapping mono audio to front left and front right");
|
|
m_useable[PCM_FRONT_CENTER] = false;
|
|
m_useable[PCM_FRONT_LEFT_OF_CENTER] = false;
|
|
m_useable[PCM_FRONT_RIGHT_OF_CENTER] = false;
|
|
}
|
|
|
|
/* see if our input has side/back channels */
|
|
for(in_ch = 0; in_ch < m_inChannels; ++in_ch)
|
|
switch(m_inMap[in_ch])
|
|
{
|
|
case PCM_SIDE_LEFT:
|
|
case PCM_SIDE_RIGHT:
|
|
hasSide = true;
|
|
break;
|
|
|
|
case PCM_BACK_LEFT:
|
|
case PCM_BACK_RIGHT:
|
|
hasBack = true;
|
|
break;
|
|
|
|
default:;
|
|
}
|
|
|
|
/* if our input has side, and not back channels, and our output doesnt have side channels */
|
|
if (hasSide && !hasBack && (!m_useable[PCM_SIDE_LEFT] || !m_useable[PCM_SIDE_RIGHT]))
|
|
{
|
|
CLog::Log(LOGDEBUG, "CPCMRemap: Forcing side channel map to back channels");
|
|
for(in_ch = 0; in_ch < m_inChannels; ++in_ch)
|
|
if (m_inMap[in_ch] == PCM_SIDE_LEFT ) m_inMap[in_ch] = PCM_BACK_LEFT;
|
|
else if (m_inMap[in_ch] == PCM_SIDE_RIGHT) m_inMap[in_ch] = PCM_BACK_RIGHT;
|
|
}
|
|
|
|
/* resolve all the channels */
|
|
struct PCMMapInfo table[PCM_MAX_CH + 1], *info, *dst;
|
|
std::vector<enum PCMChannels> path;
|
|
|
|
for (int i = 0; i < PCM_MAX_CH + 1; i++)
|
|
{
|
|
for (int j = 0; j < PCM_MAX_CH + 1; j++)
|
|
m_lookupMap[i][j].channel = PCM_INVALID;
|
|
}
|
|
|
|
memset(m_counts, 0, sizeof(m_counts));
|
|
for(in_ch = 0; in_ch < m_inChannels; ++in_ch) {
|
|
|
|
for (int i = 0; i < PCM_MAX_CH + 1; i++)
|
|
table[i].channel = PCM_INVALID;
|
|
|
|
ResolveChannel(m_inMap[in_ch], 1.0f, false, path, table);
|
|
for(info = table; info->channel != PCM_INVALID; ++info)
|
|
{
|
|
/* find the end of the table */
|
|
for(dst = m_lookupMap[info->channel]; dst->channel != PCM_INVALID; ++dst);
|
|
|
|
/* append it to the table and set its input offset */
|
|
dst->channel = m_inMap[in_ch];
|
|
dst->in_offset = in_ch * 2;
|
|
dst->level = info->level;
|
|
m_counts[dst->channel]++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
builds a lookup table to convert from the input mapping to the output
|
|
mapping, this decreases the amount of work per sample to remap it.
|
|
*/
|
|
void CPCMRemap::BuildMap()
|
|
{
|
|
struct PCMMapInfo *dst;
|
|
unsigned int out_ch;
|
|
|
|
if (!m_inSet || !m_outSet) return;
|
|
|
|
m_inStride = m_inSampleSize * m_inChannels ;
|
|
m_outStride = m_inSampleSize * m_outChannels;
|
|
|
|
/* see if we need to normalize the levels */
|
|
bool dontnormalize = m_dontnormalize;
|
|
CLog::Log(LOGDEBUG, "CPCMRemap: Downmix normalization is %s", (dontnormalize ? "disabled" : "enabled"));
|
|
|
|
ResolveChannels();
|
|
|
|
/* convert the levels into RMS values */
|
|
float loudest = 0.0;
|
|
bool hasLoudest = false;
|
|
|
|
for(out_ch = 0; out_ch < m_outChannels; ++out_ch)
|
|
{
|
|
float scale = 0;
|
|
int count = 0;
|
|
for(dst = m_lookupMap[m_outMap[out_ch]]; dst->channel != PCM_INVALID; ++dst)
|
|
{
|
|
dst->copy = false;
|
|
dst->level = dst->level / sqrt((float)m_counts[dst->channel]);
|
|
scale += dst->level;
|
|
++count;
|
|
}
|
|
|
|
/* if there is only 1 channel to mix, and the level is 1.0, then just copy the channel */
|
|
dst = m_lookupMap[m_outMap[out_ch]];
|
|
if (count == 1 && dst->level > 0.99 && dst->level < 1.01)
|
|
dst->copy = true;
|
|
|
|
/* normalize the levels if it is turned on */
|
|
if (!dontnormalize)
|
|
for(dst = m_lookupMap[m_outMap[out_ch]]; dst->channel != PCM_INVALID; ++dst)
|
|
{
|
|
dst->level /= scale;
|
|
/* find the loudest output level we have that is not 1-1 */
|
|
if (dst->level < 1.0 && loudest < dst->level)
|
|
{
|
|
loudest = dst->level;
|
|
hasLoudest = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* adjust the channels that are too loud */
|
|
for(out_ch = 0; out_ch < m_outChannels; ++out_ch)
|
|
{
|
|
CStdString s = "", f;
|
|
for(dst = m_lookupMap[m_outMap[out_ch]]; dst->channel != PCM_INVALID; ++dst)
|
|
{
|
|
if (hasLoudest && dst->copy)
|
|
{
|
|
dst->level = loudest;
|
|
dst->copy = false;
|
|
}
|
|
|
|
f.Format("%s(%f%s) ", PCMChannelStr(dst->channel).c_str(), dst->level, dst->copy ? "*" : "");
|
|
s += f;
|
|
}
|
|
CLog::Log(LOGDEBUG, "CPCMRemap: %s = %s\n", PCMChannelStr(m_outMap[out_ch]).c_str(), s.c_str());
|
|
}
|
|
}
|
|
|
|
void CPCMRemap::DumpMap(CStdString info, unsigned int channels, enum PCMChannels *channelMap)
|
|
{
|
|
if (channelMap == NULL)
|
|
{
|
|
CLog::Log(LOGINFO, "CPCMRemap: %s channel map: NULL", info.c_str());
|
|
return;
|
|
}
|
|
|
|
CStdString mapping;
|
|
for(unsigned int i = 0; i < channels; ++i)
|
|
mapping += ((i == 0) ? "" : ",") + PCMChannelStr(channelMap[i]);
|
|
|
|
CLog::Log(LOGINFO, "CPCMRemap: %s channel map: %s\n", info.c_str(), mapping.c_str());
|
|
}
|
|
|
|
void CPCMRemap::Reset()
|
|
{
|
|
m_inSet = false;
|
|
m_outSet = false;
|
|
Dispose();
|
|
}
|
|
|
|
/* sets the input format, and returns the requested channel layout */
|
|
enum PCMChannels *CPCMRemap::SetInputFormat(unsigned int channels, enum PCMChannels *channelMap, unsigned int sampleSize, unsigned int sampleRate, enum PCMLayout channelLayout, bool dontnormalize)
|
|
{
|
|
m_inChannels = channels;
|
|
m_inSampleSize = sampleSize;
|
|
m_sampleRate = sampleRate;
|
|
m_inSet = channelMap != NULL;
|
|
if (channelMap)
|
|
memcpy(m_inMap, channelMap, sizeof(enum PCMChannels) * channels);
|
|
|
|
/* get the audio layout, and count the channels in it */
|
|
m_channelLayout = channelLayout;
|
|
m_dontnormalize = dontnormalize;
|
|
if (m_channelLayout >= PCM_MAX_LAYOUT) m_channelLayout = PCM_LAYOUT_2_0;
|
|
|
|
|
|
DumpMap("I", channels, channelMap);
|
|
BuildMap();
|
|
|
|
/* now remove the empty channels from PCMLayoutMap;
|
|
* we don't perform upmixing so we want the minimum amount of those */
|
|
if (channelMap) {
|
|
if (!m_outSet)
|
|
ResolveChannels(); /* Do basic channel resolving to find out the empty channels;
|
|
* If m_outSet == true, this was done already by BuildMap() above */
|
|
int i = 0;
|
|
for (enum PCMChannels *chan = PCMLayoutMap[m_channelLayout]; *chan != PCM_INVALID; ++chan)
|
|
if (m_lookupMap[*chan][0].channel != PCM_INVALID) {
|
|
/* something is mapped here, so add the channel */
|
|
m_layoutMap[i++] = *chan;
|
|
}
|
|
m_layoutMap[i] = PCM_INVALID;
|
|
} else
|
|
memcpy(m_layoutMap, PCMLayoutMap[m_channelLayout], sizeof(PCMLayoutMap[m_channelLayout]));
|
|
|
|
m_attenuation = 1.0;
|
|
m_attenuationInc = 1.0;
|
|
m_holdCounter = 0;
|
|
|
|
return m_layoutMap;
|
|
}
|
|
|
|
/* sets the output format supported by the audio renderer */
|
|
void CPCMRemap::SetOutputFormat(unsigned int channels, enum PCMChannels *channelMap, bool ignoreLayout/* = false */)
|
|
{
|
|
m_outChannels = channels;
|
|
m_outSet = channelMap != NULL;
|
|
m_ignoreLayout = ignoreLayout;
|
|
if (channelMap)
|
|
memcpy(m_outMap, channelMap, sizeof(enum PCMChannels) * channels);
|
|
|
|
DumpMap("O", channels, channelMap);
|
|
BuildMap();
|
|
|
|
m_attenuation = 1.0;
|
|
m_attenuationInc = 1.0;
|
|
m_holdCounter = 0;
|
|
}
|
|
|
|
#if 0
|
|
void CPCMRemap::Remap(void *data, void *out, unsigned int samples, long drc)
|
|
{
|
|
float gain = 1.0f;
|
|
if (drc > 0)
|
|
gain = pow(10.0f, (float)drc / 1000.0f);
|
|
|
|
Remap(data, out, samples, gain);
|
|
}
|
|
|
|
/* remap the supplied data into out, which must be pre-allocated */
|
|
void CPCMRemap::Remap(void *data, void *out, unsigned int samples, float gain /*= 1.0f*/)
|
|
{
|
|
CheckBufferSize(samples * m_outChannels * sizeof(float));
|
|
|
|
//set output buffer to 0
|
|
memset(out, 0, samples * m_outChannels * m_inSampleSize);
|
|
|
|
//set intermediate buffer to 0
|
|
memset(m_buf, 0, m_bufsize);
|
|
|
|
ProcessInput(data, out, samples, gain);
|
|
//AddGain(m_buf, samples * m_outChannels, gain);
|
|
//ProcessLimiter(samples, gain);
|
|
ProcessOutput(out, samples, gain);
|
|
}
|
|
|
|
void CPCMRemap::CheckBufferSize(int size)
|
|
{
|
|
if (m_bufsize < size)
|
|
{
|
|
m_bufsize = size;
|
|
m_buf = (float*)realloc(m_buf, m_bufsize);
|
|
}
|
|
}
|
|
|
|
void CPCMRemap::ProcessInput(void* data, void* out, unsigned int samples, float gain)
|
|
{
|
|
for (unsigned int ch = 0; ch < m_outChannels; ch++)
|
|
{
|
|
struct PCMMapInfo *info = m_lookupMap[m_outMap[ch]];
|
|
if (info->channel == PCM_INVALID)
|
|
continue;
|
|
|
|
if (info->copy && gain == 1.0f) //do direct copy
|
|
{
|
|
uint8_t* src = (uint8_t*)data + info->in_offset;
|
|
uint8_t* dst = (uint8_t*)out + ch * m_inSampleSize;
|
|
uint8_t* dstend = dst + samples * m_outStride;
|
|
while (dst < dstend)
|
|
{
|
|
*(float*)dst = *(float*)src;
|
|
src += m_inStride;
|
|
dst += m_outStride;
|
|
}
|
|
}
|
|
else //needs some volume change or mixing, put into intermediate buffer
|
|
{
|
|
for(; info->channel != PCM_INVALID; info++)
|
|
{
|
|
uint8_t* src = (uint8_t*)data + info->in_offset;
|
|
float* dst = m_buf + ch;
|
|
float* dstend = dst + samples * m_outChannels;
|
|
while (dst < dstend)
|
|
{
|
|
*dst += (float)(*(float*)src) * info->level;
|
|
src += m_inStride;
|
|
dst += m_outChannels;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CPCMRemap::AddGain(float* buf, unsigned int samples, float gain)
|
|
{
|
|
if (gain != 1.0f) //needs a gain change
|
|
{
|
|
float* ptr = m_buf;
|
|
float* end = m_buf + samples;
|
|
while (ptr < end)
|
|
*(ptr++) *= gain;
|
|
}
|
|
}
|
|
|
|
void CPCMRemap::ProcessLimiter(unsigned int samples, float gain)
|
|
{
|
|
//check total gain for each output channel
|
|
float highestgain = 1.0f;
|
|
for (unsigned int ch = 0; ch < m_outChannels; ch++)
|
|
{
|
|
struct PCMMapInfo *info = m_lookupMap[m_outMap[ch]];
|
|
if (info->channel == PCM_INVALID)
|
|
continue;
|
|
|
|
float chgain = 0.0f;
|
|
for(; info->channel != PCM_INVALID; info++)
|
|
chgain += info->level * gain;
|
|
|
|
if (chgain > highestgain)
|
|
highestgain = chgain;
|
|
}
|
|
|
|
m_attenuationMin = 1.0f;
|
|
|
|
//if one of the channels can clip, enable a limiter
|
|
if (highestgain > 1.0001f)
|
|
{
|
|
m_attenuationMin = m_attenuation;
|
|
|
|
if (!m_limiterEnabled)
|
|
{
|
|
CLog::Log(LOGDEBUG, "CPCMRemap:: max gain: %f, enabling limiter", highestgain);
|
|
m_limiterEnabled = true;
|
|
}
|
|
|
|
for (unsigned int i = 0; i < samples; i++)
|
|
{
|
|
//for each collection of samples, get the highest absolute value
|
|
float maxAbs = 0.0f;
|
|
for (unsigned int outch = 0; outch < m_outChannels; outch++)
|
|
{
|
|
float absval = fabs(m_buf[i * m_outChannels + outch]) / 32768.0f;
|
|
if (maxAbs < absval)
|
|
maxAbs = absval;
|
|
}
|
|
|
|
//if attenuatedAbs is higher than 1.0f, audio is clipping
|
|
float attenuatedAbs = maxAbs * m_attenuation;
|
|
if (attenuatedAbs > 1.0f)
|
|
{
|
|
//set m_attenuation so that m_attenuation * sample is the maximum output value
|
|
m_attenuation = 1.0f / maxAbs;
|
|
if (m_attenuation < m_attenuationMin)
|
|
m_attenuationMin = m_attenuation;
|
|
//value to add to m_attenuation to make it 1.0f
|
|
m_attenuationInc = 1.0f - m_attenuation;
|
|
//amount of samples to hold m_attenuation
|
|
m_holdCounter = MathUtils::round_int(m_sampleRate * 0.025f);
|
|
}
|
|
else if (m_attenuation < 1.0f && attenuatedAbs > 0.95f)
|
|
{
|
|
//if we're attenuating and we get within 5% of clipping, hold m_attenuation
|
|
m_attenuationInc = 1.0f - m_attenuation;
|
|
m_holdCounter = MathUtils::round_int(m_sampleRate * 0.025f);
|
|
}
|
|
|
|
//apply attenuation
|
|
for (unsigned int outch = 0; outch < m_outChannels; outch++)
|
|
m_buf[i * m_outChannels + outch] *= m_attenuation;
|
|
|
|
if (m_holdCounter)
|
|
{
|
|
//hold m_attenuation
|
|
m_holdCounter--;
|
|
}
|
|
else if (m_attenuationInc > 0.0f)
|
|
{
|
|
//move m_attenuation to 1.0 in g_advancedSettings.m_limiterRelease seconds
|
|
m_attenuation += m_attenuationInc / m_sampleRate / 0.1f;
|
|
if (m_attenuation > 1.0f)
|
|
{
|
|
m_attenuation = 1.0f;
|
|
m_attenuationInc = 0.0f;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (m_limiterEnabled)
|
|
{
|
|
CLog::Log(LOGDEBUG, "CPCMRemap:: max gain: %f, disabling limiter", highestgain);
|
|
m_limiterEnabled = false;
|
|
}
|
|
|
|
//reset the limiter
|
|
m_attenuation = 1.0f;
|
|
m_attenuationInc = 0.0f;
|
|
m_holdCounter = 0;
|
|
}
|
|
}
|
|
|
|
void CPCMRemap::ProcessOutput(void* out, unsigned int samples, float gain)
|
|
{
|
|
//copy from intermediate buffer to output
|
|
for (unsigned int ch = 0; ch < m_outChannels; ch++)
|
|
{
|
|
struct PCMMapInfo *info = m_lookupMap[m_outMap[ch]];
|
|
if (info->channel == PCM_INVALID)
|
|
continue;
|
|
|
|
if (!info->copy || gain != 1.0f)
|
|
{
|
|
float* src = m_buf + ch;
|
|
uint8_t* dst = (uint8_t*)out + ch * m_inSampleSize;
|
|
uint8_t* dstend = dst + samples * m_outStride;
|
|
|
|
while(dst < dstend)
|
|
{
|
|
*(float*)dst = MathUtils::round_int(std::min(std::max(*src, -1.0f), 1.0f));
|
|
src += m_outChannels;
|
|
dst += m_outStride;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool CPCMRemap::CanRemap()
|
|
{
|
|
return (m_inSet && m_outSet);
|
|
}
|
|
|
|
int CPCMRemap::InBytesToFrames(int bytes)
|
|
{
|
|
return bytes / m_inSampleSize / m_inChannels;
|
|
}
|
|
|
|
int CPCMRemap::FramesToOutBytes(int frames)
|
|
{
|
|
return frames * m_inSampleSize * m_outChannels;
|
|
}
|
|
|
|
int CPCMRemap::FramesToInBytes(int frames)
|
|
{
|
|
return frames * m_inSampleSize * m_inChannels;
|
|
}
|
|
#endif
|
|
CStdString CPCMRemap::PCMChannelStr(enum PCMChannels ename)
|
|
{
|
|
const char* PCMChannelName[] =
|
|
{
|
|
"FL",
|
|
"FR",
|
|
"CE",
|
|
"LFE",
|
|
"BL",
|
|
"BR",
|
|
"FLOC",
|
|
"FROC",
|
|
"BC",
|
|
"SL",
|
|
"SR",
|
|
"TFL",
|
|
"TFR",
|
|
"TFC",
|
|
"TC",
|
|
"TBL",
|
|
"TBR",
|
|
"TBC"
|
|
};
|
|
|
|
int namepos = (int)ename;
|
|
CStdString namestr;
|
|
|
|
if (namepos < 0 || namepos >= (int)(sizeof(PCMChannelName) / sizeof(const char*)))
|
|
namestr.Format("UNKNOWN CHANNEL:%i", namepos);
|
|
else
|
|
namestr = PCMChannelName[namepos];
|
|
|
|
return namestr;
|
|
}
|
|
#if 0
|
|
CStdString CPCMRemap::PCMLayoutStr(enum PCMLayout ename)
|
|
{
|
|
const char* PCMLayoutName[] =
|
|
{
|
|
"2.0",
|
|
"2.1",
|
|
"3.0",
|
|
"3.1",
|
|
"4.0",
|
|
"4.1",
|
|
"5.0",
|
|
"5.1",
|
|
"7.0",
|
|
"7.1"
|
|
};
|
|
|
|
int namepos = (int)ename;
|
|
CStdString namestr;
|
|
|
|
if (namepos < 0 || namepos >= (int)(sizeof(PCMLayoutName) / sizeof(const char*)))
|
|
namestr.Format("UNKNOWN LAYOUT:%i", namepos);
|
|
else
|
|
namestr = PCMLayoutName[namepos];
|
|
|
|
return namestr;
|
|
}
|
|
#endif
|
|
|
|
|
|
void CPCMRemap::GetDownmixMatrix(float *downmix)
|
|
{
|
|
for (int i=0; i<8*8; i++)
|
|
downmix[i] = 0.0f;
|
|
|
|
for (unsigned int ch = 0; ch < m_outChannels; ch++)
|
|
{
|
|
struct PCMMapInfo *info = m_lookupMap[m_outMap[ch]];
|
|
if (info->channel == PCM_INVALID)
|
|
continue;
|
|
|
|
for(; info->channel != PCM_INVALID; info++)
|
|
downmix[8*ch + (info->in_offset>>1)] = info->level;
|
|
}
|
|
}
|