数据转换为音频再返回。使用源代码进行调制/解调。

26

我有一串二进制数据,想把它转换成原始的波形声音数据,并将其发送到扬声器。

这就是旧式调制解调器在电话线上传输二进制数据时所做的事情(产生典型的拨号音)。 这被称为调制。

然后,我需要一个反向过程 - 从原始波形采样中,我想获得精确的二进制数据。 这被称为解调。

  • 任何比特率都可以作为起点。
  • 使用计算机扬声器播放声音并使用麦克风进行采样。
  • 带宽可能会很低(低品质麦克风)。
  • 有一些背景噪音,但不多。

我找到了一种特定的方法来实现这一点 - 频移键控。 问题是我找不到任何源代码。

请问你能否指向任何语言中实现FSK的代码?
或提供任何可用源代码的二进制<->声音替代编码方式?


1
我下面给出了一个通用的答案,但如果您详细说明您的要求,我可以更加具体地回答。您希望使用什么比特率?音频信号是如何播放的(电脑扬声器?)和采样的(麦克风?)。有关音频带宽和噪声方面的任何细节,您都有什么期望... - Guy Sirton
非常感谢。任何比特率都可以作为起点。声音使用计算机扬声器播放,并使用麦克风进行采样。带宽不会很宽(低质量麦克风)。有一些背景噪音,但不多。 - Martin Konicek
1
这并不像听起来那么老派(wifi,手机)。虽然今天我们可能使用的是与声音不同的东西,但它仍然是数据转换成波形再转回去。 - corsiKa
2个回答

24
最简单的调制方案是幅度调制(在数字领域中,这通常被称为幅移键控)。取一个固定频率(比如说10KHz)作为你的“载波波形”,使用二进制数据中的位来开关它。如果你的数据速率是每秒10位,那么你将以该速率切换10KHz信号的开和关。解调可以是(可选的)10KHz滤波器,接着与门限值进行比较。这是一个相当简单的实现方案。通常情况下,信号频率越高,可用带宽越大,你就能更快地开关该信号。
一个非常酷/有趣的应用是将其编码/解码为摩尔斯电码,看看你能走多快。
FSK,在两个频率之间切换,更有效地利用了带宽,更具抗噪性,但会使解调器更加复杂,因为你需要区分这两个频率。
先进的调制方案,例如相移键控,在给定带宽和信噪比的情况下能够获得最高的比特率,但实现起来更加复杂。模拟电话调制解调器需要处理特定的带宽(例如至少3Khz)和噪声限制。如果您需要在带宽和噪声限制下获得最高可能的比特率,则可以选择这种方式。
对于先进调制方案的实际代码示例,我建议查阅DSP供应商(例如TIAnalog Devices)的应用说明,因为这些是DSP的常见应用。 使用TMS320C50实现PI/4移位D-QPSK基带调制解调器

QPSK调制揭秘

TMS320C50 DSP上的V.34发射机和接收机实现

另一种非常简单但不太有效的方法是使用DTMF。这些是由电话键盘生成的音调,每个符号都是两个频率的组合。如果你谷歌一下,你会找到很多源代码。根据您的应用/要求,这可能是一个简单的解决方案。

让我们深入了解一些简单的方案实现细节,比如我之前提到的莫尔斯电码。我们可以使用“点”表示0,“划”表示1。莫尔斯电码的一个优点是它还解决了帧同步问题,因为您可以在每个空格后重新同步采样。为了简单起见,让我们将“载波波形”的频率选为11KHz,并假设您的波形输出为44Khz、16位、单声道。我们还将使用方波,这将产生谐波,但我们并不真正关心。如果11KHz超出了您的麦克风频率响应范围,那么只需将所有频率除以2即可。我们将选择一些任意水平10000,因此我们的“开启”波形看起来像这样:
{10000, 10000, 0, 0, 10000, 10000, 0, 0, 10000, 0, 0, ...} // 4 samples = 11Khz period

我们的“关”波形只是全零。我将编码这部分留给读者作为练习。
因此,我们有类似以下内容的东西:
const int dot_samples = 400; // ~10ms - speed up later
const int space_samples = 400; // ~10ms
const int dash_samples = 800; // ~20ms

void encode( uint8_t* source, int length, int16_t* target ) // assumes enough room in target
{
  for(int i=0; i<length; i++)
  {
    for(int j=0; j<8; j++)
    {
      if((source[i]>>j) & 1) // If data bit is 1 we'll encode a dot
      {
        generate_on(&target, dash_samples); // Generate ON wave for n samples and update target ptr
      }
      else // otherwise a dash
      {
        generate_on(&target, dot_samples); // Generate ON wave for n samples and update target ptr
      }
      generate_off(&target, space_samples); // Generate zeros
    } 
  }
}

解码器稍微复杂一些,但是这里有一个概述:
  1. 可选地对11KHz左右的采样信号进行带通滤波。这将提高在嘈杂环境中的性能。FIR滤波器非常简单,有一些在线设计程序可以为您生成滤波器。
  2. 对信号进行阈值处理。每个值大于最大振幅的1/2的值为1,每个值小于最大振幅的1/2的值为0。这假设您已经对整个信号进行了采样。如果这是实时的,您可以选择一个固定的阈值或者进行某种自动增益控制,在一段时间内跟踪最大信号水平。
  3. 扫描点或划线的开始。您可能希望在点周期内至少看到一定数量的1来考虑样本为点。然后继续扫描以查看这是否为划线。不要期望完美的信号-您会在1的中间看到一些0和在0的中间看到一些1。如果噪声很小,将"开启"周期与"关闭"周期区分开来应该相当容易。
  4. 然后反转上述过程。如果您看到划线,则将1位推入缓冲区,如果是点,则将零推入缓冲区。

1
非常感谢您!看起来解码不太容易,我还没有找到任何可用的代码。最终,我们使用了完全不同的有损方法,只适用于传输图片:http://www.mobreactor.com/soundwise - Martin Konicek
在解码(解调)方面,有一个用于FSK解调的良好且快速的算法,称为Goertzel算法。这个PDF提供了一个很好的概述。 - dodgy_coder
许多年后,这里有一个移动应用程序的演示视频。它再次使用了一种有损方法(光谱图),不同于上述描述的方法。它只能传输图片。这是因为我对DSP还很陌生,在一次黑客马拉松中实现了相对简单的东西,而且只有一天时间:https://www.youtube.com/watch?v=ElkmzoqD9PM - Martin Konicek

2
调制/解调的一个目的是适应信道特性。例如,信道可能无法传递直流信号。另一个目的是在仍能以给定错误率传输数据的情况下,克服信道中给定数量和类型的噪声。
对于FSK,您只需要编写程序,在发送端生成两个不同频率的正弦波,并在接收端过滤和检测这两个不同频率的信号即可。每个正弦波段的长度、频率间隔和振幅将取决于数据速率和需要克服的噪声量。
在最简单的情况下,即零噪声情况下,只需在连续的固定时间帧内产生N或2N个正弦波段即可。类似于:
x[i] = amplitude * sin( i * 2 * pi * (data[j] ? 1.0 : 2.0) * freq) / sampleRate )

在接收端,您可以对信号进行超过两倍最大频率的采样,并测量零交叉点之间的距离,看是否发现了一堆短周期或长周期波形。在存在非零噪声的情况下,可以使用更高级的数字信号处理滤波器(IIR、FIR等)和各种统计检测器。


我认为最好不要让一个频率是另一个频率的两倍,因为这样更高的频率就是较低频率的谐波。一般来说,你希望频率尽可能接近,但不要太接近以至于无法区分它们。300bps调制解调器在一个方向上使用1180Hz和980Hz,在另一个方向上使用1850Hz和1650Hz。 - Guy Sirton
@Guy:没错。但是频率分离和数据速率之间存在权衡,这取决于其他因素,如带宽、S/N和误码率要求。 - hotpaw2

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接