如何在Common Lisp中通过cl-portaudio正确播放FM解调信号?
解决cl-portaudio播放FM解调信号的问题
咱们一步步来拆解你遇到的三个核心问题:信号削波、类型警告,以及ALSA underrun错误,逐个解决后就能正常播放FM信号了。
1. 修复信号幅度范围,消除削波声
你现在听到的削波声,本质是解调后的信号幅度超出了音频设备能处理的范围:
- 首先,
atan-complex-number的输出范围是[-π, π](大概-3.14到3.14),但音频播放要求信号归一化到[-1.0, 1.0]的单精度浮点数。所以要把解调结果除以π,把幅度压缩到合法区间:(vector-push (/ (atan-complex-number dif-complex) pi) samples-array) - 其次,你的IQ采样是sc16-q11格式:16位有符号整数,但只有低11位是有效数据,高5位是符号扩展。在转换为复数前,必须先把采样值归一化到
[-1.0, 1.0],否则复数幅度会过大,导致解调信号波动更剧烈:;; 把sc16-q11采样转换为标准化浮点数 (let* ((previous-i (/ (mem-aref rx-samples :int16 (1+ i)) (expt 2 10))) ; q11的缩放因子是2^10 (previous-q (/ (mem-aref rx-samples :int16 i) (expt 2 10))) (current-i (/ (mem-aref rx-samples :int16 (+ i 3)) (expt 2 10))) (current-q (/ (mem-aref rx-samples :int16 (+ i 2)) (expt 2 10))) ...)
2. 消除类型警告,确保数组类型严格匹配
cl-portaudio给出的类型警告,是因为你创建的数组虽然元素是float,但没有明确指定是单精度浮点(对应C的float类型,也就是cl-portaudio要求的:float格式)。修改数组初始化代码,明确类型:
(samples-array (make-array 4096 ; 你每次处理4096个IQ对,对应4096个解调样本 :element-type 'single-float :initial-element 0.0s0)) ; 用0.0s0表示单精度浮点数
另外,你之前把数组大小设为8192是多余的,因为循环只生成4096个样本,调整后还能避免空间浪费。
3. 解决ALSA Underrun(缓冲区欠载)问题
这个错误说明音频设备的播放缓冲区空了——你的数据供给速度跟不上播放速度,核心问题有两个:
- 每次调用
fm-demodulate-sc16-q11-samples都重复初始化bladeRF设备(配置信道、启动同步、启用模块),这会带来巨大的延迟; - 解调后的信号采样率(和bladeRF的采样率一致,你这里设的是40MHz)远高于音频播放的44100Hz,直接播放会导致设备处理不过来。
修复方案:重构代码,复用设备初始化+重采样
第一步:拆分设备初始化和解调逻辑
把设备初始化单独抽出来,只执行一次,不要每次获取样本都重复操作:
(defun init-fm-receiver (device frequency) ;; 注意:这里的configure-channel参数需要确认你的绑定定义,通常是(信道 频率 带宽 采样率 增益) ;; 建议把采样率调低到合理值,比如4MHz,减少后续重采样压力 (configure-channel device (channel-rx 0) frequency 1000000 4000000 30) (init-sync device) (enable-module device (channel-rx 0) t))
第二步:实现持续获取解调样本的函数
(defun get-demodulated-fm-samples (device num-iq-pairs) (with-foreign-objects ((rx-samples :uint16 (* num-iq-pairs 2)) ; 每个IQ样本占2个uint16 (metadata :pointer)) (let ((status (bladerf_sync_rx (mem-ref device :pointer) rx-samples num-iq-pairs metadata 5000)) (samples-array (make-array num-iq-pairs :element-type 'single-float :initial-element 0.0s0))) (if (< status 0) (error "Failed to receive IQ samples error: ~S" status) (progn (loop for i below (* num-iq-pairs 2) by 2 for idx from 0 do (let* ((prev-i (/ (mem-aref rx-samples :int16 (+ i 1)) (expt 2 10))) (prev-q (/ (mem-aref rx-samples :int16 i) (expt 2 10))) (curr-i (/ (mem-aref rx-samples :int16 (+ i 3)) (expt 2 10))) (curr-q (/ (mem-aref rx-samples :int16 (+ i 2)) (expt 2 10))) (prev-complex (complex prev-i prev-q)) (curr-complex (complex curr-i curr-q)) (dif-complex (* (conjugate prev-complex) curr-complex))) (setf (aref samples-array idx) (/ (atan-complex-number dif-complex) pi)))) samples-array)))))
第三步:播放时重采样,匹配音频采样率
必须把高采样率的FM解调信号重采样到44100Hz才能正常播放。这里用简单的抽取示例,实际可以用cl-resample库获得更平滑的音质:
(defconstant +frames-per-buffer+ 1024) (defconstant +sample-rate+ 44100d0) (defconstant +sample-format+ :float) (defconstant +num-channels+ 2) (defun play-demodulated-fm (device) (let ((fm-sample-rate 4000000) ; 对应init-fm-receiver里设置的bladeRF采样率 (audio-sample-rate +sample-rate+)) (with-audio (with-default-audio-stream (astream +num-channels+ +num-channels+ :sample-format +sample-format+ :sample-rate audio-sample-rate :frames-per-buffer +frames-per-buffer+) ;; 只初始化一次FM接收器 (init-fm-receiver device 863000000) (unwind-protect (loop ;; 获取足够的FM样本,计算需要的IQ对数量 (let* ((required-fm-samples (round (* +frames-per-buffer+ (/ fm-sample-rate audio-sample-rate)))) (fm-samples (get-demodulated-fm-samples device required-fm-samples)) ;; 重采样:抽取对应位置的样本 (audio-samples (make-array +frames-per-buffer+ :element-type 'single-float))) (loop for i below +frames-per-buffer+ do (setf (aref audio-samples i) (aref fm-samples (round (* i (/ fm-sample-rate audio-sample-rate)))))) ;; 转换为双声道(复制单声道到左右声道) (let ((stereo-samples (make-array (* +frames-per-buffer+ 2) :element-type 'single-float))) (loop for i below +frames-per-buffer+ do (setf (aref stereo-samples (* i 2)) (aref audio-samples i) (aref stereo-samples (+ (* i 2) 1)) (aref audio-samples i))) (write-stream astream stereo-samples)))) ;; 播放结束后清理设备 (enable-module device (channel-rx 0) nil))))))
额外小贴士
- 选择合适的bladeRF采样率:不要用40MHz这么高的采样率,不仅浪费资源,重采样也更麻烦。根据FM带宽(通常100kHz-2MHz),选择2-4MHz的采样率足够。
- 使用专业重采样库:简单抽取会导致音质损失,推荐用
cl-resample库实现线性或插值重采样,获得更好的听觉效果。 - 错误处理:可以在循环里添加超时或错误判断,避免设备异常时程序崩溃。
内容的提问来源于stack exchange,提问作者mwanamutapa
相关产品推荐
相关产品推荐

