ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

sherpa-onnx 的 Pascal 音频 I/O 实战:基于 PortAudio 的播放与录音示例解析

2026/9/14 9:18:02 拓冰建站 浏览量
sherpa-onnx 的 Pascal 音频 I/O 实战:基于 PortAudio 的播放与录音示例解析 sherpa-onnx 的 Pascal 音频 I/O 实战基于 PortAudio 的播放与录音示例解析【免费下载链接】sherpa-onnxSpeech-to-text, text-to-speech, speaker diarization, speech enhancement, source separation, and VAD using next-gen Kaldi with onnxruntime without Internet connection. Support embedded systems, Android, iOS, HarmonyOS, Raspberry Pi, RISC-V, RK NPU, Axera NPU, Ascend NPU, x86_64 servers, websocket server/client, support 12 programming languages项目地址: https://gitcode.com/GitHub_Trending/sh/sherpa-onnx导读在 sherpa-onnx 的 Pascal 语言示例集合中pascal-api-examples/portaudio-test目录提供了一个最基础的音频 I/O 能力验证入口它借助跨平台实时音频库 PortAudio演示了从麦克风录音并保存为 WAV与读取 WAV 并播放两个完整闭环。本文以该目录的 README 为骨架结合其 Pascal 源码test-play.pas、test-record.pas、一键编译运行脚本run-test-play.sh、run-test-record.sh以及 Pascal 绑定层sherpa-onnx/pascal-api/portaudio.pas、sherpa_onnx.pas展开讲解。读完本文你将掌握 PortAudio 在 macOS/Linux 下的安装方法、Pascal 程序中设备枚举与流回调的写法以及如何用 sherpa-onnx 的 C API 封装读取/写出 WAV 文件为后续把音频喂给 ASR、VAD 等模型打牢基础。一、目录定位portaudio-test 在整个 Pascal 示例中的角色在 pascal-api-examples 的众多示例中非流式 ASR、流式 ASR、VAD、TTS、说话人分离等portaudio-test并不直接调用任何识别模型而是专注于音频采集与回放这一前置环节。目录结构如下README.md说明依赖与安装方式test-play.pas播放示例读取./record.wav并输出到默认音频设备test-record.pas录音示例采集 10 秒麦克风输入并保存为./record.wavrun-test-play.sh / run-test-record.sh一键编译并运行上述两个程序的脚本。两个示例组合起来恰好构成录音 → 播放的完整链路先运行test-record生成record.wav再运行test-play将其回放即可直观验证 PortAudio 绑定层与 sherpa-onnx 的 WAV 读写封装是否工作正常。二、依赖安装让 portaudio.pas 找到系统库README 的开篇明确指出portaudio.pas requires that the portaudio library is installed on your system.即 Pascal 程序本身只是一个绑定binding真正干活的动态库是系统级的 PortAudio。在 macOS 上README 给出的安装命令为brew install portaudio安装完成后PortAudio 会被装进 Homebrew 的 Cellar 目录README 以常见版本为例给出路径/usr/local/Cellar/portaudio/19.7.0该路径并非写死在源码中的魔法值而是被run-test-*.sh脚本显式使用——脚本通过fpc的-Fl库搜索路径参数把它传给链接器-Fl/usr/local/Cellar/portaudio/19.7.0/lib如果你的 Homebrew 前缀不同例如 Apple Silicon 机器为/opt/homebrew或安装的 PortAudio 版本号不同需要同步修改脚本中的这条路径也可改用pkg-config --libs portaudio动态获取。绑定层如何加载库从绑定实现看portaudio.pas该文件头部注明拷贝自 UltraStar-Deluxe/USDX 项目在不同平台加载不同库名{$IF Defined(MSWINDOWS)} LibName portaudio_x86.dll; {$ELSEIF Defined(UNIX)} LibName portaudio; {$LINKLIB portaudio} {$IFEND}Windows 下加载portaudio_x86.dllUnix/Linux/macOS 下加载名为portaudio的共享库并声明{$LINKLIB portaudio}让链接器参与解析。这与脚本中-Fl指向 Cellar 目录、运行时通过LD_LIBRARY_PATH/DYLD_LIBRARY_PATH提供动态库路径的机制一一对应详见下文编译运行一节。三、录音示例 test-record.pas10 秒采集并写 WAVtest-record.pas 的注释说明了它的行为This file shows how to use portaudio for recording. It records for 10 seconds and saves the audio samples to ./record.wav3.1 初始化与设备枚举程序入口先打印 PortAudio 版本并初始化Version : String(Pa_GetVersionText); WriteLn(Version is , Version); Status : Pa_Initialize; if Status paNoError then begin WriteLn(Failed to initialize portaudio, , Pa_GetErrorText(Status)); Exit; end;接着获取设备总数并选择默认输入设备NumDevices : Pa_GetDeviceCount; WriteLn(Num devices: , NumDevices); DeviceIndex : Pa_GetDefaultInputDevice; if DeviceIndex paNoDevice then begin WriteLn(No default input device found); Pa_Terminate; Exit; end;3.2 通过环境变量覆盖设备索引示例支持用环境变量SHERPA_ONNX_MIC_DEVICE指定输入设备索引这在多麦克风环境下非常实用EnvStr : GetEnv(SHERPA_ONNX_MIC_DEVICE); if EnvStr then begin DeviceIndex : StrToIntDef(EnvStr, DeviceIndex); WriteLn(Use device index from environment variable SHERPA_ONNX_MIC_DEVICE: , EnvStr); end;随后用循环列出全部设备并在选中的设备前打上*标记同时打印其名称与最大输入通道数for I : 0 to (NumDevices - 1) do begin DeviceInfo : Pa_GetDeviceInfo(I); if I DeviceIndex then WriteLn(Format( * %d %s, [I, AnsiString(DeviceInfo^.Name)])) else WriteLn(Format( %d %s, [I, AnsiString(DeviceInfo^.Name)])); end; WriteLn(Use device , DeviceIndex); WriteLn( Name , Pa_GetDeviceInfo(DeviceIndex)^.Name); WriteLn( Max input channels , Pa_GetDeviceInfo(DeviceIndex)^.MaxInputChannels);这里的Pa_GetDeviceInfo返回TPaDeviceInfo记录其中name、maxInputChannels、defaultSampleRate、defaultLow/HighInputLatency等字段在 portaudio.pas 中有完整定义。3.3 配置输入流参数流参数通过TPaStreamParameters结构体配置Initialize(Param); Param.Device : DeviceIndex; Param.ChannelCount : 1; Param.SampleFormat : paFloat32; param.SuggestedLatency : Pa_GetDeviceInfo(DeviceIndex)^.DefaultHighInputLatency; param.HostApiSpecificStreamInfo : nil;要点如下ChannelCount : 1单声道采集符合语音识别对单通道输入的一般预期SampleFormat : paFloat32使用 32 位浮点采样格式归一化到 [-1.0, 1.0]这是 portaudio.pas 中定义的paFloat32 $00000001SuggestedLatency取自设备的DefaultHighInputLatency面向非交互应用的稳健延迟值HostApiSpecificStreamInfo : nil不使用宿主 API 特定配置。3.4 回调把输入帧推入环形缓冲录音回调非常简洁将 PortAudio 传入的输入缓冲区直接压入TSherpaOnnxCircularBufferfunction RecordCallback( input: Pointer; output: Pointer; frameCount: culong; timeInfo: PPaStreamCallbackTimeInfo; statusFlags: TPaStreamCallbackFlags; userData: Pointer ): cint; cdecl; begin Buffer.Push(pcfloat(input), frameCount); Result : paContinue; end;环形缓冲区大小为 20 秒容量Round(SampleRate) * 20采样率固定 48000 Hz保证 10 秒录音期间不丢数据SampleRate : 48000; Buffer : TSherpaOnnxCircularBuffer.Create(Round(SampleRate) * 20);3.5 打开、启动、等待与收尾Status : Pa_OpenStream(stream, Param, nil, SampleRate, paFramesPerBufferUnspecified, paNoFlag, PPaStreamCallback(RecordCallback), nil);注意Pa_OpenStream的参数顺序输入参数指针在前、输出参数指针为nil仅输入流framesPerBuffer使用paFramesPerBufferUnspecified0由宿主机决定最优帧数标志位用paNoFlag随后传入回调指针与userData此处为 nil回调通过全局变量访问 Buffer。启动后程序主线程Pa_Sleep(10000)睡眠 10 秒期间回调在 PortAudio 自己的线程中被持续调用Status : Pa_StartStream(stream); ... WriteLn(Please speak! It will exit after 10 seconds.); Pa_Sleep(10000); {sleep for 10 seconds }3.6 写 WAV对接 sherpa-onnx 的 C API录音结束后关闭流把环形缓冲区中的全部样本取出交给 sherpa-onnx 的 WAV 写出接口落盘Status : Pa_CloseStream(stream); ... AllSamples : Buffer.Get(0, Buffer.Size); SherpaOnnxWriteWave(record.wav, AllSamples, Round(SampleRate)); WriteLn(Saved to record.wav);这里调用的是 sherpa_onnx.pas 中声明的SherpaOnnxWriteWave内部经SherpaOnnxWriteWaveWrapper转发到 C API 的SherpaOnnxWriteWave它接受采样数组、样本数与采样率三个参数。最终Pa_Terminate释放 PortAudio 资源。生成的record.wav正是下一步test-play要读取的文件。四、播放示例 test-play.pas从 WAV 到扬声器test-play.pas 的方向正好相反读入./record.wav通过输出流播放。4.1 读取 WAV 并准备环形缓冲先用 sherpa-onnx 的SherpaOnnxReadWave读取文件返回TSherpaOnnxWave包含Samples与SampleRate字段失败时打印提示并退出Wave : SherpaOnnxReadWave(./record.wav); if Wave.Samples nil then begin WriteLn(Failed to read ./record.wav); Pa_Terminate; Exit; end;随后把全部样本推入环形缓冲作为播放源Buffer : TSherpaOnnxCircularBuffer.Create(Length(Wave.Samples)); Buffer.Push(Wave.Samples);4.2 配置输出流与录音对称输出参数指针为 nil仅输出流设备选择默认输出设备Pa_GetDefaultOutputDevice同样支持SHERPA_ONNX_MIC_DEVICE环境变量覆盖延迟取DefaultHighOutputLatency采样率沿用 WAV 自身的采样率Initialize(Param); Param.Device : DeviceIndex; Param.ChannelCount : 1; Param.SampleFormat : paFloat32; param.SuggestedLatency : Pa_GetDeviceInfo(DeviceIndex)^.DefaultHighOutputLatency; param.HostApiSpecificStreamInfo : nil; Status : Pa_OpenStream(stream, nil, Param, Wave.SampleRate, paFramesPerBufferUnspecified, paNoFlag, PPaStreamCallback(PlayCallback), nil);4.3 播放回调从环形缓冲取数并填充输出播放回调负责把缓冲中的样本逐帧写入 PortAudio 的输出缓冲区并处理缓冲不足的边缘情况用 0 补齐不足的帧function PlayCallback(...): cint; cdecl; begin if Buffer.Size frameCount then begin Samples : Buffer.Get(Buffer.Head, FrameCount); Buffer.Pop(FrameCount); end else begin Samples : Buffer.Get(Buffer.Head, Buffer.Size); Buffer.Pop(Buffer.Size); SetLength(Samples, frameCount); // 剩余不足时补零 end; for I : 0 to frameCount - 1 do pcfloat(output)[I] : Samples[I]; if Buffer.Size 0 then Result : paContinue else Result : paComplete; // 播完即通知 PortAudio 结束 end;注意回调的返回值语义缓冲未耗尽时返回paContinue继续播放耗尽时返回paCompletePortAudio 播完剩余缓冲后自动停止。主线程则用轮询Buffer.Size 0Pa_Sleep(100)的方式等待播放完成最后依次Pa_CloseStream、Pa_Terminate收尾。4.4 环形缓冲区与 WAV 工具的实现依据上述代码用到的TSherpaOnnxCircularBuffer、TSherpaOnnxWave、SherpaOnnxReadWave/SherpaOnnxWriteWave均可在 sherpa_onnx.pas 中找到声明与实现TSherpaOnnxCircularBuffer约 L616 声明、L2609-L2665 实现提供Create(Capacity)、Push数组与指针两种重载、Get(StartIndex, N)、Pop(N)、Reset、Size、Head等方法SherpaOnnxReadWave/SherpaOnnxWriteWaveL802-L804 声明L1639-L1653 实现通过SherpaOnnxReadWaveWrapper/SherpaOnnxWriteWaveWrapper调用 C API 的SherpaOnnxReadWave/SherpaOnnxWriteWave即它们本质上是 sherpa-onnx C API 的 Pascal 封装。五、一键编译与运行run-test-*.sh 脚本解析两个脚本逻辑完全一致仅目标.pas文件不同。以 run-test-record.sh 为例它分为三个阶段。5.1 首次构建 sherpa-onnx C 动态库脚本首先检查build/install/lib下是否存在libsherpa-onnx-c-api.dylib/libsherpa-onnx-c-api.so/sherpa-onnx-c-api.dll分别对应 macOS / Linux / Windows若不存在则先配置并安装if [[ ! -f ../../build/install/lib/libsherpa-onnx-c-api.dylib ! -f ../../build/install/lib/libsherpa-onnx-c-api.so ! -f ../../build/install/lib/sherpa-onnx-c-api.dll ]]; then mkdir -p ../../build pushd ../../build cmake \ -DCMAKE_INSTALL_PREFIX./install \ -DSHERPA_ONNX_ENABLE_PYTHONOFF \ -DSHERPA_ONNX_ENABLE_TESTSOFF \ -DSHERPA_ONNX_ENABLE_CHECKOFF \ -DBUILD_SHARED_LIBSON \ -DSHERPA_ONNX_ENABLE_PORTAUDIOOFF \ .. cmake --build . --target install --config Release popd fi几个值得注意的开关BUILD_SHARED_LIBSON构建共享库便于 Pascal 程序在运行时动态加载SHERPA_ONNX_ENABLE_PORTAUDIOOFF本示例自己直接调用系统 PortAudio因此不需要 sherpa-onnx 内置的 PortAudio 模块SHERPA_ONNX_ENABLE_PYTHON/OFF、SHERPA_ONNX_ENABLE_TESTSOFF、SHERPA_ONNX_ENABLE_CHECKOFF跳过无关组件以加快构建。5.2 用 Free Pascal Compiler 编译随后调用fpc编译 Pascal 源文件关键参数逐一说明fpc \ -g \ -dSHERPA_ONNX_USE_SHARED_LIBS \ -Fu$SHERPA_ONNX_DIR/sherpa-onnx/pascal-api \ -Fl$SHERPA_ONNX_DIR/build/install/lib \ -Fl/usr/local/Cellar/portaudio/19.7.0/lib \ ./test-record.pas-g生成调试信息-dSHERPA_ONNX_USE_SHARED_LIBS定义该宏让 sherpa_onnx.pas 走共享库加载路径而非静态链接-Fu$SHERPA_ONNX_DIR/sherpa-onnx/pascal-api把 Pascal API 目录加入单元搜索路径使uses sherpa_onnx, portaudio能够解析-Fl两条分别是 sherpa-onnx C 库与 PortAudio 库的链接搜索路径后者对应 README 中的 Homebrew 安装路径。5.3 设置动态库路径并运行export LD_LIBRARY_PATH$SHERPA_ONNX_DIR/build/install/lib:$LD_LIBRARY_PATH export DYLD_LIBRARY_PATH$SHERPA_ONNX_DIR/build/install/lib:$DYLD_LIBRARY_PATH ./test-recordLD_LIBRARY_PATH对应 LinuxDYLD_LIBRARY_PATH对应 macOS二者都指向构建出的build/install/lib确保程序启动时能找到libsherpa-onnx-c-api动态库PortAudio 库则依赖系统默认路径或-Fl指定的路径。六、运行流程与结果验证按顺序执行两个脚本即可完成录音 → 回放的端到端验证# 1. 录音 10 秒生成 ./record.wav ./run-test-record.sh # 2. 播放 ./record.wav ./run-test-play.shtest-record运行时的典型输出包括PortAudio 版本文本、设备数量、带*标记的选中设备及其名称/最大输入通道数然后提示 Please speak! It will exit after 10 seconds.10 秒后打印 Saved to record.wav。test-play则打印同样的版本/设备信息后开始播放播完自动退出。若机器上有多个音频设备可通过环境变量指定SHERPA_ONNX_MIC_DEVICE2 ./test-record该环境变量在 test-play.pas 与 test-record.pas 中均有读取逻辑可用脚本输出的设备列表先确认索引。七、从示例到实战接入 ASR / VAD 的思路虽然本示例只做音频 I/O但它揭示了在 Pascal 中驱动 sherpa-onnx 的通用模式采集PortAudio 回调把pcfloat(input)推进环形缓冲对应TSherpaOnnxCircularBuffer.Push喂给模型从缓冲取出样本数组Buffer.Get即可交给 sherpa_onnx.pas 中声明的离线识别器CreateOfflineRecognizer、AcceptWaveform或流式识别器CreateOnlineStream、AcceptWaveform等接口回放/保存识别结果如需合成语音输出可像test-play一样用环形缓冲驱动输出流或像test-record一样用SherpaOnnxWriteWave落盘。例如在 pascal-api-examples/vad-with-non-streaming-asr 或 pascal-api-examples/streaming-asr 等目录中读者可以看到上述 I/O 能力与真实模型推理组合的完整示例。结语portaudio-test是 sherpa-onnx Pascal 示例体系中最小却不可缺的一环。通过本文梳理可以看到一条清晰的依赖链系统 PortAudio 库brew install portaudio→ portaudio.pas 绑定 → Pascal 程序中的流回调与环形缓冲 → sherpa_onnx.pas 封装的 WAV 读写接口。理解这一链路既能让你独立验证本地音频环境是否就绪也为在 Pascal 中调用 sherpa-onnx 的语音识别、VAD、TTS 等能力扫清了音频输入输出的障碍。【免费下载链接】sherpa-onnxSpeech-to-text, text-to-speech, speaker diarization, speech enhancement, source separation, and VAD using next-gen Kaldi with onnxruntime without Internet connection. Support embedded systems, Android, iOS, HarmonyOS, Raspberry Pi, RISC-V, RK NPU, Axera NPU, Ascend NPU, x86_64 servers, websocket server/client, support 12 programming languages项目地址: https://gitcode.com/GitHub_Trending/sh/sherpa-onnx创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考