正确实施杜比定向逻辑II

时间:2014-05-17 16:27:31

标签: c# .net audio signal-processing

到目前为止,我已根据以下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();
}

1 个答案:

答案 0 :(得分:11)

铊组成; dr

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

正确的组合

裁剪问题是您的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

如果您没有故意遗漏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;
}