C# · 12月 23, 2021

c# – 正确实施杜比定向逻辑II

到目前为止,我已经成功实现了基于以下 specifications的杜比矩阵解码器:

左右
中心0.707 0.707
离开1 0
正确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,”l.wav”) }; var r = new WavFileWrite { MetaData = Meta,”r.wav”) }; var sl = new WavFileWrite { MetaData = Meta,”sl.wav”) }; var sr = new WavFileWrite { MetaData = Meta,”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();}解决方法 文艺青年最爱的

要严格地将您的实施与杜比的规格进行比较,您会遗漏几件事,

>解码器的混音级别(不是比率)不正确,
> LFE channel未处理,
>左环绕&环绕右声道不是phase shifted,delayed,也不是通过HPF,
>中心频道不通过Bandpass.

正确的组合

削波问题是你的maxtrix混合水平(比率很好)的结果,例如,自0.707 0.707 = 1.414以来,中心通道超过了41.4%;因此,要保持正确的比例,只需将混合水平减半即可.

考虑到这一点,你的maxtrix现在应该是这样的,

左右
中心0.354 0.354
左0.5 0
正确0 0.5
SLeft 0.436 0.245
SRight 0.245 0.436

添加LFE

如果您没有故意遗漏LFE频道,您可以像中央频道一样对其进行解码,但您必须以120Hz(杜比标准)应用LPF.

使环绕声道实际上“环绕”

要处理左环绕和右环绕通道,您需要通过HPF以100 Hz的频率通过左右声道,然后应用90°相移(然后反转左侧);混合,然后添加一个延迟(我相信杜比没有指定确切的时间量,但经过一些测试后,我发现“甜点”似乎是大约10毫秒).

(如果你打算延迟使用,我建议在5ms到12.5ms之间这样做(随意试验).如果延迟太小,你最终会得到“压缩/浓缩”的声音混合,太长时间,你最终会在后台发出可怕的高频回声.理想情况下,最终结果听起来应该是“通风/开放”,但没有任何回音/混响的暗示.)

leftSamples -> HPF -> phase shift -> invert -> mix \ -> delayrightSamples -> 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,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,100); slL = PhaseShift(slL,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,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,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,100); srL = PhaseShift(srL,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,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,10); // Process LFE channel. for (var i = 0; i < ii; i++) { lfe[i] = (leftSamples[i] * 0.35355339059327376220042218105242f) + (rightSamples[i] * 0.35355339059327376220042218105242f); } lfe = LinkwitzRileyLowPass(lfe,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,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,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,”\”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,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;}