到目前为止,我已根据以下specifications设法实施了杜比的矩阵解码器:
Left Right
Center 0.707 0.707
Left 1 0
Right 0 1
SLeft 0.871 0.489
SRight 0.489 0.871
但是在测试了我的矩阵后,我发现它有很多剪辑,听起来不像杜比的解码器。我对DSP比较陌生,虽然我很确定我理解大多数基础知识;但我仍然无法理解导致这种情况发生的原因,我是否遗漏了规格中的内容,还是仅仅是我的代码?
我目前的矩阵解码器,
private static void DolbyProLogicII(List<float> leftSamples, List<float> rightSamples, int sampleRate, string outputDirectory)
{
// WavFileWrite is a wrapper class for NAudio to create Wav files.
var meta = new WaveFormat(sampleRate, 16, 1);
var c = new WavFileWrite { MetaData = meta, FileName = Path.Combine(outputDirectory, "c.wav") };
var l = new WavFileWrite { MetaData = meta, FileName = Path.Combine(outputDirectory, "l.wav") };
var r = new WavFileWrite { MetaData = meta, FileName = Path.Combine(outputDirectory, "r.wav") };
var sl = new WavFileWrite { MetaData = meta, FileName = Path.Combine(outputDirectory, "sl.wav") };
var sr = new WavFileWrite { MetaData = meta, FileName = Path.Combine(outputDirectory, "sr.wav") };
var ii = (leftSamples.Count > rightSamples.Count ? rightSamples.Count : leftSamples.Count);
// Process center channel.
for (var i = 0; i < ii; i++)
{
c.MonoChannelAudioData.Add((leftSamples[i] * 0.707) + (rightSamples[i] * 0.707));
}
c.Flush();
// Process left channel.
l.MonoChannelAudioData = leftSamples;
l.Flush();
// Process right channel.
r.MonoChannelAudioData = rightSamples;
r.Flush();
// Process surround left channel.
for (var i = 0; i < ii - 1; i++)
{
sl.MonoChannelAudioData.Add((leftSamples[i] * 0.871) + (rightSamples[i] * 0.489));
}
sl.Flush();
// Process surround right channel.
for (var i = 0; i < ii - 1; i++)
{
sr.MonoChannelAudioData.Add((leftSamples[i] * 0.489) + (rightSamples[i] * 0.871));
}
sr.Flush();
}
答案 0 :(得分:11)
为了严格地将您的实施与杜比的规格进行比较,您遗漏了几件事,
解码器的混合级别(不是比率)不正确,
LFE channel尚未处理,
左环绕&amp;环绕右声道不是phase shifted,delayed,也不是通过HPF,
中心频道未通过Bandpass。
裁剪问题是您的maxtrix混音级别(比率很好)的结果,例如,41.4%
以来0.707 + 0.707 = 1.414
的中心声道超过了安培。因此,要保持正确的比例,只需将混合水平减半即可。
考虑到这一点,你的maxtrix现在应该是这样的,
Left Right
Center 0.354 0.354
Left 0.5 0
Right 0 0.5
SLeft 0.436 0.245
SRight 0.245 0.436
如果您没有故意遗漏LFE频道,您可以像中央频道一样对其进行解码,但您必须以120Hz(杜比标准)应用LPF。
要处理左环绕和环绕右声道,您需要通过HPF以100 Hz的频率传递左右声道然后应用90 o 相移(然后反转左侧);混合,然后添加一个延迟(我相信杜比没有指定确切的时间量,但经过一些测试后我发现“甜点”似乎是大约 10毫秒)。
(如果你打算延迟使用,我建议在5ms到12.5ms之间这样做(随意试验)。如果延迟太小,你最终会得到一个“压缩/浓缩”混音太长,你最终会在后台发出可怕的高频回声。理想情况下,最终结果听起来应该是“通风/开放”,但没有任何回音的暗示/混响。)
leftSamples -> HPF -> phase shift -> invert -> mix \
-> delay
rightSamples -> HPF -> phase shift -> mix /
杜比还规定中心声道需要通过70Hz至20kHz的带通(混合后)。
所以,把所有这些放在一起你应该得到以下结果,
public void DolbyProLogicII(float[] leftSamples, float[] rightSamples, int sampleRate)
{
var ii = Math.Min(leftSamples.Length, rightSamples.Length);
var c = new float[ii];
var l = new float[ii];
var r = new float[ii];
var sl = new float[ii];
var sr = new float[ii];
var lfe = new float[ii];
// Process center channel
for (var i = 0; i < ii; i++)
{
// Best to be as precise as possible.
c[i] = (leftSamples[i] * 0.35355339059327376220042218105242f) + (rightSamples[i] * 0.35355339059327376220042218105242f);
}
c = LinkwitzRileyHighPass(c, sampleRate, 70);
c = LinkwitzRileyLowPass(c, sampleRate, 20000);
// Process front left channel
for (var i = 0; i < ii; i++)
{
l[i] = leftSamples[i] * 0.5f;
}
// Process front right channel
for (var i = 0; i < ii; i++)
{
r[i] = rightSamples[i] * 0.5f;
}
// Process left samples for SL channel
var slL = new float[ii];
for (var i = 0; i < ii; i++)
{
slL[ii] = leftSamples[i] * 0.43588989435406735522369819838596f;
}
slL = LinkwitzRileyHighPass(slL, sampleRate, 100);
slL = PhaseShift(slL, sampleRate, true);
// Process right samples for SL channel.
var slR = new float[ii];
for (var i = 0; i < ii; i++)
{
slR[i] = rightSamples[i] * 0.24494897427831780981972840747059f;
}
slR = LinkwitzRileyHighPass(slR, sampleRate, 100);
slR = PhaseShift(slR, sampleRate);
// Combine new left & right samples for SL channel
for (var i = 0; i < ii - 1; i++)
{
sl[i] = slL[i] + slR[i];
}
sl = Delay(sl, sampleRate, 10);
// Process left samples for SR channel
var srL = new float[ii];
for (var i = 0; i < ii; i++)
{
srL[i] = leftSamples[i] * 0.24494897427831780981972840747059f;
}
srL = LinkwitzRileyHighPass(srL, sampleRate, 100);
srL = PhaseShift(srL, sampleRate, true);
// Process right samples for SR channel
var srR = new float[ii];
for (var i = 0; i < ii; i++)
{
srR[i] = rightSamples[i] * 0.43588989435406735522369819838596f;
}
srR = LinkwitzRileyHighPass(srR, sampleRate, 100);
srR = PhaseShift(srR, sampleRate);
// Combine new left & right samples for SR channel
for (var i = 0; i < ii - 1; i++)
{
sr[i] = srL[i] + srR[i];
}
sr = Delay(sr, sampleRate, 10);
// Process LFE channel.
for (var i = 0; i < ii; i++)
{
lfe[i] = (leftSamples[i] * 0.35355339059327376220042218105242f) + (rightSamples[i] * 0.35355339059327376220042218105242f);
}
lfe = LinkwitzRileyLowPass(lfe, sampleRate, 120);
}
public float[] PhaseShift(float[] samples, double sampleRate, bool invertOutput = false)
{
var depth = 4.0;
var delay = 100.0;
var rate = 0.1;
var newSamples = new float[samples.Length];
double wp, min_wp, max_wp, range, coef, sweepfac;
double inval, x1, outval = 0.0;
double lx1 = 0.0, ly1 = 0.0, lx2 = 0.0, ly2 = 0.0, lx3 = 0.0, ly3 = 0.0, lx4 = 0.0, ly4 = 0.0;
// calc params for sweeping filters
wp = min_wp = (Math.PI * delay) / sampleRate;
range = Math.Pow(2.0, depth);
max_wp = (Math.PI * delay * range) / sampleRate;
rate = Math.Pow(range, rate / (sampleRate / 2));
sweepfac = rate;
for (var i = 0; i < samples.Length; i++)
{
coef = (1.0 - wp) / (1.0 + wp); // calc coef for current freq
x1 = (inval = (double)samples[i]);
ly1 = coef * (ly1 + x1) - lx1; // do 1st filter
lx1 = x1;
ly2 = coef * (ly2 + ly1) - lx2; // do 2nd filter
lx2 = ly1;
ly3 = coef * (ly3 + ly2) - lx3; // do 3rd filter
lx3 = ly2;
ly4 = coef * (ly4 + ly3) - lx4; // do 4th filter
lx4 = ly3;
// final output
outval = ly4;
if (invertOutput)
{
newSamples[i] = -(float)outval;
}
else
{
newSamples[i] = (float)outval;
}
wp *= sweepfac; // adjust freq of filters
if (wp > max_wp) // max?
{
sweepfac = 1.0 / rate; // sweep back down
}
else
{
if (wp < min_wp) // min?
{
sweepfac = rate; // sweep back up
}
}
}
return newSamples;
}
public float[] Delay(float[] samples, int sampleRate, double milliseconds)
{
var output = new List<float>(samples);
var ii = (sampleRate / 1000) * milliseconds;
for (var i = 0; i < ii; i++)
{
output.Insert(0, 0);
}
return output.ToArray();
}
public float[] LinkwitzRileyHighPass(float[] samples, int sampleRate, double cutoff)
{
if (cutoff <= 0 && cutoff >= sampleRate / 2)
{
throw new ArgumentOutOfRangeException("cutoff", "The cutoff frequency must be between 0 and \"sampleRate\" / 2.");
}
if (sampleRate <= 0)
{
throw new ArgumentOutOfRangeException("sampleRate", "The sample rate must be more than 0.");
}
if (samples == null || samples.Length == 0)
{
throw new ArgumentNullException("samples", "\"samples\" can not be null or empty.");
}
var newSamples = new float[samples.Length];
var wc = 2 * Math.PI * cutoff;
var wc2 = wc * wc;
var wc3 = wc2 * wc;
var wc4 = wc2 * wc2;
var k = wc / Math.Tan(Math.PI * cutoff / sampleRate);
var k2 = k * k;
var k3 = k2 * k;
var k4 = k2 * k2;
var sqrt2 = Math.Sqrt(2);
var sq_tmp1 = sqrt2 * wc3 * k;
var sq_tmp2 = sqrt2 * wc * k3;
var a_tmp = 4 * wc2 * k2 + 2 * sq_tmp1 + k4 + 2 * sq_tmp2 + wc4;
var b1 = (4 * (wc4 + sq_tmp1 - k4 - sq_tmp2)) / a_tmp;
var b2 = (6 * wc4 - 8 * wc2 * k2 + 6 * k4) / a_tmp;
var b3 = (4 * (wc4 - sq_tmp1 + sq_tmp2 - k4)) / a_tmp;
var b4 = (k4 - 2 * sq_tmp1 + wc4 - 2 * sq_tmp2 + 4 * wc2 * k2) / a_tmp;
var a0 = k4 / a_tmp;
var a1 = -4 * k4 / a_tmp;
var a2 = 6 * k4 / a_tmp;
var a3 = a1;
var a4 = a0;
double ym1 = 0.0, ym2 = 0.0, ym3 = 0.0, ym4 = 0.0, xm1 = 0.0, xm2 = 0.0, xm3 = 0.0, xm4 = 0.0, tempy = 0.0;
for (var i = 0; i < samples.Length; i++)
{
var tempx = samples[i];
tempy = a0 * tempx + a1 * xm1 + a2 * xm2 + a3 * xm3 + a4 * xm4 - b1 * ym1 - b2 * ym2 - b3 * ym3 - b4 * ym4;
xm4 = xm3;
xm3 = xm2;
xm2 = xm1;
xm1 = tempx;
ym4 = ym3;
ym3 = ym2;
ym2 = ym1;
ym1 = tempy;
newSamples[i] = (float)tempy;
}
return newSamples;
}
public float[] LinkwitzRileyLowPass(float[] samples, int sampleRate, double cutoff)
{
if (cutoff <= 0 && cutoff >= sampleRate / 2)
{
throw new ArgumentOutOfRangeException("cutoff", "The cutoff frequency must be between 0 and \"sampleRate\" / 2.");
}
if (sampleRate <= 0)
{
throw new ArgumentOutOfRangeException("sampleRate", "The sample rate must be more than 0.");
}
if (samples == null || samples.Length == 0)
{
throw new ArgumentNullException("samples", "\"samples\" can not be null or empty.");
}
var newSamples = new float[samples.Length];
var wc = 2 * Math.PI * cutoff;
var wc2 = wc * wc;
var wc3 = wc2 * wc;
var wc4 = wc2 * wc2;
var k = wc / Math.Tan(Math.PI * cutoff / sampleRate);
var k2 = k * k;
var k3 = k2 * k;
var k4 = k2 * k2;
var sqrt2 = Math.Sqrt(2);
var sq_tmp1 = sqrt2 * wc3 * k;
var sq_tmp2 = sqrt2 * wc * k3;
var a_tmp = 4 * wc2 * k2 + 2 * sq_tmp1 + k4 + 2 * sq_tmp2 + wc4;
var b1 = (4 * (wc4 + sq_tmp1 - k4 - sq_tmp2)) / a_tmp;
var b2 = (6 * wc4 - 8 * wc2 * k2 + 6 * k4) / a_tmp;
var b3 = (4 * (wc4 - sq_tmp1 + sq_tmp2 - k4)) / a_tmp;
var b4 = (k4 - 2 * sq_tmp1 + wc4 - 2 * sq_tmp2 + 4 * wc2 * k2) / a_tmp;
var a0 = wc4 / a_tmp;
var a1 = 4 * wc4 / a_tmp;
var a2 = 6 * wc4 / a_tmp;
var a3 = a1;
var a4 = a0;
double ym1 = 0.0, ym2 = 0.0, ym3 = 0.0, ym4 = 0.0, xm1 = 0.0, xm2 = 0.0, xm3 = 0.0, xm4 = 0.0, tempy = 0.0;
for (var i = 0; i < samples.Length; i++)
{
var tempx = samples[i];
tempy = a0 * tempx + a1 * xm1 + a2 * xm2 + a3 * xm3 + a4 * xm4 - b1 * ym1 - b2 * ym2 - b3 * ym3 - b4 * ym4;
xm4 = xm3;
xm3 = xm2;
xm2 = xm1;
xm1 = tempx;
ym4 = ym3;
ym3 = ym2;
ym2 = ym1;
ym1 = tempy;
newSamples[i] = (float)tempy;
}
return newSamples;
}