自分のやってることに近いことが分かっているのでMonkey's Audioの実装を見てみよう。まず、NewPredictor.cppの予測処理。
// aiki: サンプルnA, nBを圧縮。ステレオ信号ならnA, nB両方にデータが入る
// aiki: 片方のチャンネルが無音の場合はnBは0になる。
// aiki: nA, nBの呼び分けは CAPECompressCore::EncodeFrame にて行われる
int64 CPredictorCompressNormal::CompressValue(int64 nA, int64 nB)
{
// roll the buffers if necessary
if (m_nCurrentIndex == WINDOW_BLOCKS)
{
m_rbPrediction.Roll(); m_rbAdapt.Roll();
m_nCurrentIndex = 0;
}
// stage 1: simple, non-adaptive order 1 prediction
// aiki: 固定係数フィルタによる予測。これはTTAと全く同じ(!)プリエンファシスフィルタ
nA = m_Stage1FilterA.Compress(nA);
nB = m_Stage1FilterB.Compress(nB);
// stage 2: adaptive offset filter(s)
// aiki: 適応フィルタによる予測。コードを見る限り4係数
m_rbPrediction[0] = nA;
m_rbPrediction[-2] = m_rbPrediction[-1] - m_rbPrediction[-2];
m_rbPrediction[-5] = nB;
m_rbPrediction[-6] = m_rbPrediction[-5] - m_rbPrediction[-6];
int64 * paryM = &m_aryM[8];
int64 nPredictionA = (m_rbPrediction[-1] * paryM[0]) + (m_rbPrediction[-2] * paryM[-1]) + (m_rbPrediction[-3] * paryM[-2]) + (m_rbPrediction[-4] * paryM[-3]);
int64 nPredictionB = (m_rbPrediction[-5] * paryM[-4]) + (m_rbPrediction[-6] * paryM[-5]) + (m_rbPrediction[-7] * paryM[-6]) + (m_rbPrediction[-8] * paryM[-7]) + (m_rbPrediction[-9] * paryM[-8]);
int64 nOutput = nA - ((nPredictionA + (nPredictionB >> 1)) >> 10);
// adapt
// aiki: 出力の符号を取る
// aiki: ((x >> 30) & 2) - 1 は符号関数の符号を反転したもの
// aiki: (x >> 30)で0(>0), -1(<0)となり, (x >> 30) & 2で0(>0), 2(<0),
// aiki: ((x >> 30) & 2) - 1 によって-1(>0), 1(<0)
m_rbAdapt[0] = (m_rbPrediction[-1]) ? ((m_rbPrediction[-1] >> 30) & 2) - 1 : 0;
m_rbAdapt[-1] = (m_rbPrediction[-2]) ? ((m_rbPrediction[-2] >> 30) & 2) - 1 : 0;
m_rbAdapt[-4] = (m_rbPrediction[-5]) ? ((m_rbPrediction[-5] >> 30) & 2) - 1 : 0;
m_rbAdapt[-5] = (m_rbPrediction[-6]) ? ((m_rbPrediction[-6] >> 30) & 2) - 1 : 0;
// aiki: 係数の更新。残差符号によって更新符号が変わる。
// aiki: すなわちSign-Algorithmで出力フィードバックしている。
if (nOutput > 0)
{
int64 * pM = &paryM[-8]; int64 * pAdapt = &m_rbAdapt[-8];
EXPAND_9_TIMES(*pM++ -= *pAdapt++;)
}
else if (nOutput < 0)
{
int64 * pM = &paryM[-8]; int64 * pAdapt = &m_rbAdapt[-8];
EXPAND_9_TIMES(*pM++ += *pAdapt++;)
}
// stage 3: NNFilters
// aiki: CNNフィルタによる予測。圧縮オプションにより使うフィルタ段数が異なる
if (m_pNNFilter)
{
nOutput = m_pNNFilter->Compress(nOutput);
if (m_pNNFilter1)
{
nOutput = m_pNNFilter1->Compress(nOutput);
if (m_pNNFilter2)
nOutput = m_pNNFilter2->Compress(nOutput);
}
}
m_rbPrediction.IncrementFast(); m_rbAdapt.IncrementFast();
m_nCurrentIndex++;
return nOutput;
}
注目のCNNの圧縮(予測)処理は以下。NNFilter.cpp にある
int64 CNNFilter::Compress(int64 nInput)
{
if (m_nBitdepth == 16)
{
// convert the input to a short and store it
// aiki: short(たぶん16bit)幅に丸め込む。範囲外の値は0x7FFF(>0), 0x8000(<0)に飽和する (NNFilter.h)
m_rbInput16[0] = GetSaturatedShortFromInt(nInput);
// figure a dot product
// aiki: 内積実行(指定された次数のフィルタを畳み込む)。
int64 nDotProduct;
#ifdef ENABLE_AVX_ASSEMBLY
if (this->useAVX2())
nDotProduct = CalculateDotProductAVXx16(&m_rbInput16[-m_nOrder], &m_paryM16[0], m_nOrder);
else
#endif
#ifdef ENABLE_SSE_ASSEMBLY
if (this->useSSE2())
nDotProduct = CalculateDotProductSSEx16(&m_rbInput16[-m_nOrder], &m_paryM16[0], m_nOrder);
else
#endif
nDotProduct = CalculateDotProductx16(&m_rbInput16[-m_nOrder], &m_paryM16[0], m_nOrder);
// calculate the output
// aiki: 残差計算。シフト量 m_nShift はコンストラクタで指定
int64 nOutput = int64(nInput) - int64((int64(nDotProduct) + (int64(1) << (int64(m_nShift) - 1))) >> int64(m_nShift));
// adapt
// aiki: 適応。nOutputの符号を見て更新方向を決め、m_rbDeltaM16だけ増減する。Sign-Algorithmやな。
#ifdef ENABLE_AVX_ASSEMBLY
if (this->useAVX2())
AdaptAVXx16(&m_paryM16[0], &m_rbDeltaM16[-m_nOrder], nOutput, m_nOrder);
else
#endif
#ifdef ENABLE_SSE_ASSEMBLY
if (this->useSSE2())
AdaptSSEx16(&m_paryM16[0], &m_rbDeltaM16[-m_nOrder], nOutput, m_nOrder);
else
#endif
Adaptx16(&m_paryM16[0], &m_rbDeltaM16[-m_nOrder], nOutput, m_nOrder);
int64 nTempABS = llabs(nInput);
// aiki: 入力の絶対値に応じて更新量の更新。入力を量子化。Sign-Algorithmだけど入力を荒くしている。
if (nTempABS > (m_nRunningAverage * 3))
m_rbDeltaM16[0] = ((nInput >> 25) & 64) - 32;
else if (nTempABS > (m_nRunningAverage * 4) / 3)
m_rbDeltaM16[0] = ((nInput >> 26) & 32) - 16;
else if (nTempABS > 0)
m_rbDeltaM16[0] = ((nInput >> 27) & 16) - 8;
else
m_rbDeltaM16[0] = 0;
// aiki: 入力の絶対値の移動平均値の更新。
m_nRunningAverage += (nTempABS - m_nRunningAverage) / 16;
m_rbDeltaM16[-1] >>= 1;
m_rbDeltaM16[-2] >>= 1;
m_rbDeltaM16[-8] >>= 1;
// increment and roll if necessary
m_rbInput16.IncrementSafe();
m_rbDeltaM16.IncrementSafe();
return nOutput;
}
else // m_nBitdepth == 32
{
// aiki: (snip) ビット幅32の場合の処理。
}
}
大雑把に見ると、層ごとにサンプルごとに適応している。たしかにこれなら圧縮率は高いだろう。
CNNの設定は以下で決まる。これも NewPredictor.cpp にある。CNNFilterのコンストラクタの第一引数は次数。第二引数はシフト量。
CPredictorCompressNormal::CPredictorCompressNormal(intn nCompressionLevel, intn nBitsPerSample)
: IPredictorCompress(nCompressionLevel)
{
if (nCompressionLevel == MAC_COMPRESSION_LEVEL_FAST)
{
m_pNNFilter = NULL;
m_pNNFilter1 = NULL;
m_pNNFilter2 = NULL;
}
else if (nCompressionLevel == MAC_COMPRESSION_LEVEL_NORMAL)
{
m_pNNFilter = new CNNFilter(16, 11, MAC_FILE_VERSION_NUMBER, (nBitsPerSample == 32), NULL);
m_pNNFilter1 = NULL;
m_pNNFilter2 = NULL;
}
else if (nCompressionLevel == MAC_COMPRESSION_LEVEL_HIGH)
{
m_pNNFilter = new CNNFilter(64, 11, MAC_FILE_VERSION_NUMBER, (nBitsPerSample == 32), NULL);
m_pNNFilter1 = NULL;
m_pNNFilter2 = NULL;
}
else if (nCompressionLevel == MAC_COMPRESSION_LEVEL_EXTRA_HIGH)
{
m_pNNFilter = new CNNFilter(256, 13, MAC_FILE_VERSION_NUMBER, (nBitsPerSample == 32), NULL);
m_pNNFilter1 = new CNNFilter(32, 10, MAC_FILE_VERSION_NUMBER, (nBitsPerSample == 32), NULL);
m_pNNFilter2 = NULL;
}
else if (nCompressionLevel == MAC_COMPRESSION_LEVEL_INSANE)
{
m_pNNFilter = new CNNFilter(1024 + 256, 15, MAC_FILE_VERSION_NUMBER, (nBitsPerSample == 32), NULL);
m_pNNFilter1 = new CNNFilter(256, 13, MAC_FILE_VERSION_NUMBER, (nBitsPerSample == 32), NULL);
m_pNNFilter2 = new CNNFilter(16, 11, MAC_FILE_VERSION_NUMBER, (nBitsPerSample == 32), NULL);
}
else
{
throw(1);
}
}
Extra High以上では多層構成になっている。 Extra Highでは一段目は256次数、二段目は32次数。二段目の方により弱いフィルタを噛ましている。INSANE頭おかしい…(小声) 層を増やすたびに次数が急激に減り過ぎでは、という印象。 他にも圧縮オプションに応じてフレームサイズが変わっていた。(CAPECompressCreate::Start 関数)
// initialize (creates the base classes)
m_nSamplesPerFrame = 73728;
if (nCompressionLevel == MAC_COMPRESSION_LEVEL_EXTRA_HIGH)
m_nSamplesPerFrame *= 4;
else if (nCompressionLevel == MAC_COMPRESSION_LEVEL_INSANE)
m_nSamplesPerFrame *= 16;
クソデカフレームサイズだな。
だいたい理解したと思いこんでいる。不足があればもっと読み込んでみる。