FSK解调 - 解析日本EWS数据

时间:2013-12-06 07:41:19

标签: c# algorithm audio signal-processing frequency-analysis

【这不是重复的。类似的问题涉及人们可以控制源数据的情况。我没有。】

在日本有一种叫做“紧急警告广播系统”的东西。激活时看起来像这样:http://www.youtube.com/watch?v=9hjlYvp9Pxs

在上面的视频中,在2:37左右,发送FSK调制信号。我想解析这个信号;即,给定一个包含信号的WAV文件,我想最终得到一个包含0和1的StringBuilder,以便稍后处理它们。我有二进制数据的规范和所有,但问题是我对音频编程一无所知。 :(

这只是一个爱好项目,但我迷上了。电视和广播制造商可以接收到这个信号并让他们的设备做出反应,所以它不会那么难,对吧? :(

关于信号的事实:

  • 标记音为1024Hz,停止音为640Hz
  • 每个音调长15.625ms
  • 在信号开始之前和结束之后暂停2秒(可能用于检测目的)

到目前为止我做了什么:

  1. 编写一个简单的RIFF解析器,接受8位单声道WAV文件,并允许我从中获取样本。我已经对它进行了测试,但它确实有效。
  2. 一个需要15.625ms样本的循环,并且:
    1. 使用RMS寻找两秒钟的沉默
    2. 使用Goertzel算法确定信号是1024Hz还是640Hz
  3. 我遇到的问题:

      根据测试数据,
    • 0s和1s在循环期间被吞下。
      • 鉴于信号的清晰度(YouTube-to-MP3 rip),这不应该发生。
      • 如果我在Audacity中生成重复的01序列30次,我的程序将会获取01对中的大约10对,而不是30
    • 有时交换0和1(上面的副作用?)
    • 如果我调整代码以使其与一个测试声音文件一起使用,则其他测试声音文件将停止工作

    我的问题:

    • 有人能给我一个关于如何在软件中正确完成FSK解码的高级概述吗?
    • 我是否需要应用某种过滤器,将信号限制在640Hz + 1024Hz并使其他一切静音?
    • 保持时机正确的最佳方法是什么?也许我做错了?
    • 有关此类音频处理的初学者文献的任何链接?我真的很想学习并开始工作。

    读取样本的代码是(简化):

    StringBuilder ews_bits = new StringBuilder();
    double[] samples = new double[(int)(samplesPerMs * 16.625D)];
    int index = 0, readTo = /* current offset + RIFF subChunk2Size */;
    BinaryReader br = /* at start of PCM data */;
    
    while (br.BaseStream.Position < readTo)
    {
        switch (bitsPerSample / 8)
        {
            case 1: // 8bit
                samples[index++] = ((double)br.ReadByte() - 127.5D) / 256D;
                break;
            case 2: // 16bit
                samples[index++] = (double)br.ReadInt16() / 32768D;
                break;
        }
    
        if (index != samples.Length)
            continue;
    
        /****** The sample buffer is full and we must process it. ******/
    
        if (AudioProcessor.IsSilence(ref samples))
        {
            silence_count++;
            if (state == ParserState.Decoding && silence_count > 150)
            {
                // End of EWS broadcast reached.
                EwsSignalParser.Parse(ews_bits.ToString());
    
                /* ... reset state; go back looking for silence... */
            }
            goto Done;
        }
    
        /****** The signal was not silence. ******/
    
        if (silence_count > 120 && state == ParserState.SearchingSilence)
            state = ParserState.Decoding;
    
        if (state == ParserState.Decoding)
        {
            AudioProcessor.Decode(ref samples, sampleRate, ref ews_bits);
    
            bool continue_decoding = /* check first 20 bits for signature */;
            if (continue_decoding) goto Done;
    
            // If we get here, we were decoding a junk signal.
            state = ParserState.SearchingSilence;
        }
    
        /* Not enough silence yet */
        silence_count = 0;
    Done:
        index = 0;
    }
    

    音频处理器只是一个类:

    public static void Decode(ref double[] samples, int sampleRate, ref StringBuilder bitHolder)
    {
        double freq_640 = GoertzelMagnitude(ref samples, 640, sampleRate);
        double freq_1024 = GoertzelMagnitude(ref samples, 1024, sampleRate);
    
        if (freq_640 > freq_1024)
            bitHolder.Append("0");
        else
            bitHolder.Append("1");
    }
    
    public static bool IsSilence(ref double[] samples)
    {
        // power_RMS = sqrt(sum(x^2) / N)
    
        double sum = 0;
    
        for (int i = 0; i < samples.Length; i++)
            sum += samples[i] * samples[i];
    
        double power_RMS = Math.Sqrt(sum / samples.Length);
    
        return power_RMS < 0.01;
    }
    
    
    /// <remarks>http://www.embedded.com/design/embedded/4024443/The-Goertzel-Algorithm</remarks>
    private static double GoertzelMagnitude(ref double[] samples, double targetFrequency, int sampleRate)
    {
        double n = samples.Length;
        int k = (int)(0.5D + ((double)n * targetFrequency) / (double)sampleRate);
        double w = (2.0D * Math.PI / n) * k;
        double cosine = Math.Cos(w);
        double sine = Math.Sin(w);
        double coeff = 2.0D * cosine;
    
        double q0 = 0, q1 = 0, q2 = 0;
    
        for (int i = 0; i < samples.Length; i++)
        {
            double sample = samples[i];
    
            q0 = coeff * q1 - q2 + sample;
            q2 = q1;
            q1 = q0;
        }
    
        double magnitude = Math.Sqrt(q1 * q1 + q2 * q2 - q1 * q2 * coeff);
    
        return magnitude;
    }
    

    感谢阅读。我希望你能帮助我。

2 个答案:

答案 0 :(得分:2)

我就是这样做的(高级别描述)

  1. 通过FFT
  2. 运行您的信号
  3. 寻找大约640Hz + 1024Hz的稳定峰值(我想说至少+/- 10Hz)
  4. 如果信号稳定约10毫秒(稳定,我的意思是约95%的样本在640Hz +/- 10Hz(或1024Hz +/- 10Hz)的相同范围内)将其作为音调的检测。检测还可以同步您的计时器,告诉您何时需要下一个音。

答案 1 :(得分:1)

在重写样本解析循环和静音检测部分后,我得到了大约90%的工作。我的实施中存在两个主要问题。第一个是静音检测器过于活跃,因此我将其从处理每毫秒样本变为每半毫秒样本。这让我完全接受了FSK数据的开始。

接下来的问题是,我认为我可以天真地让解调器查看15.625ms的样本,因为它通过WAV文件工作。事实证明,虽然这对前90位左右很有效,但最终音调变得比预期稍长或短,并且解调器失去同步。当前代码使用这种时序不匹配来查找和纠正13位。特别容易受到影响的是信号从标记变为空间的点,反之亦然。

猜猜“模拟”这个词包含“肛门”是有原因的。它是。我真的希望我对信号理论和数字信号处理有更多了解。 :(

我是如何发现这一切的:我使用Audacity导入MP3并将其修剪到FSK部分。然后我让Audacity为每一位生成标签。之后,我按照标签突出显示了一些内容。