资讯详情

资讯详情

libwebsockets 原始音频示例深度解析:用 lws 事件循环驱动 ALSA 播放与采集

后端网络通信【免费下载链接】libwebsocketscanonical libwebsockets.org networking library项目地址https://gitcode.com/gh_mirrors/li/libwebsockets点击查看免费下载本指南以 libwebsockets 仓库中的minimal-raw-audio最小示例为核心讲解如何把 ALSA 播放Playback与采集Capture设备以「原始文件描述符」的形式接入 lws 事件循环并利用环形缓冲区实现带延迟的麦克风到扬声器回环通路。读完本文你将掌握lws_adopt_descriptor_vhost()收养外部描述符的方法、RAW 文件回调的使用方式以及 ALSA 这类不支持直接挂入事件循环的设备如何借道POLLIN与 lws 无缝协作的工程技巧。示例概览从麦克风到扬声器的 lws 事件循环通路minimal-raw-audio位于 minimal-examples-lowlevel/raw/minimal-raw-audio 目录是 libwebsockets「RAW 文件描述符」系列示例minimal-examples-lowlevel/raw/README.md中的一员。它的核心行为一句话可以概括打开系统默认的 ALSA 播放与采集设备把采集到的音频数据写入环形缓冲区再以超过 1 秒的延迟送入播放设备形成一条「麦克风 → 环形缓冲 → 扬声器」的本地回环通路。整体数据流如下ALSA Capture (默认设备, 16000Hz) ──POLLIN──▶ lws 事件循环 │ LWS_CALLBACK_RAW_RX_FILE ▼ simplebuf[64KiB] 环形缓冲区 │ snd_pcm_writei ▼ ALSA Playback (默认设备, 16000Hz)示例的实用价值在于它展示了 lws 的事件循环并不只服务于网络 socket任何能提供文件描述符的 I/O 源——设备节点、FIFO、普通文件——都可以通过「收养adopt」的方式交给 lws 统一调度。这为在同一个进程、同一个事件循环里同时处理网络连接与音频设备打开了可能性。前置条件LWS_WITH_ALSA编译开关README 明确要求构建 libwebsockets 时必须开启 cmake 选项-DLWS_WITH_ALSA1这个开关在仓库根目录 CMakeLists.txt 中默认是关闭的option(LWS_WITH_ALSA Enable alsa audio example OFF)开启后根 CMake 会通过find_path/find_library查找 ALSA 开发包alsa/asoundlib.h与libasound若系统缺少对应开发库会直接报错终止if (LWS_WITH_ALSA) find_path(LWS_ALSA_INCLUDE_DIRS NAMES alsa/asoundlib.h) find_library(LWS_ALSA_LIBRARIES NAMES asound) if (LWS_ALSA_INCLUDE_DIRS AND LWS_ALSA_LIBRARIES) set(LWS_HAVE_ALSA ON) ... else() message(FATAL_ERROR LWS_WITH_ALSA is set but alsa/asoundlib.h or libasound not found) endif() endif()在 Debian/Ubuntu 等发行版上需要安装libasound2-dev才能满足该依赖。此外示例自身的 CMakeLists.txt 在构建时还会做两项能力检查任一不满足都不会生成可执行文件require_lws_config(LWS_WITH_ALSA 1 requirements) require_lws_config(LWS_WITH_NETWORK 1 requirements)链接阶段同时挂上websockets或websockets_shared与asoundtarget_link_libraries(${SAMP} websockets_shared asound ${LIBWEBSOCKETS_DEP_LIBS})也就是说libwebsockets 库本身、网络功能、ALSA 三者缺一不可。另外需要一台实际可用的声卡环境——示例打开的是 ALSA 默认设备default采集侧静默或播放侧无声都会让演示效果大打折扣。构建与运行在配置好LWS_WITH_ALSA1的 lws 构建树内或已安装 libwebsockets cmake 配置的环境中按 README 的步骤构建$ cmake . make生成的可执行文件名为lws-minimal-raw-audio直接运行即可$ ./lws-minimal-raw-audio [2019/10/14 18:58:49:3288] U: LWS minimal raw audio [2019/10/14 18:58:50:3438] N: LWS_CALLBACK_RAW_ADOPT_FILE [2019/10/14 18:58:50:3455] N: LWS_CALLBACK_RAW_ADOPT_FILE [2019/10/14 18:58:50:4764] N: LWS_CALLBACK_RAW_RX_FILE: 2062 samples [2019/10/14 18:58:50:6132] N: LWS_CALLBACK_RAW_RX_FILE: 2205 samples [2019/10/14 18:58:50:7592] N: LWS_CALLBACK_RAW_RX_FILE: 2328 samples ... ^C[2019/10/14 18:58:56:8460] N: LWS_CALLBACK_RAW_CLOSE_FILE [2019/10/14 18:58:56:8461] N: LWS_CALLBACK_RAW_CLOSE_FILE [2019/10/14 18:58:56:8461] N: LWS_CALLBACK_PROTOCOL_DESTROY $日志要点逐条解读两条LWS_CALLBACK_RAW_ADOPT_FILE播放、采集两个 PCM 设备各自被收养进事件循环各对应一个 wsi反复出现的LWS_CALLBACK_RAW_RX_FILE: N samples事件循环检测到采集通道可读回调中读出 N 个采样16kHz、单声道、S16 格式下每次约 20002300 个采样^C触发SIGINTLWS_CALLBACK_RAW_CLOSE_FILE出现两次两个设备描述符各自关闭随后LWS_CALLBACK_PROTOCOL_DESTROY表示协议销毁、资源回收完成。运行期间对着麦克风说话扬声器里会传出延迟约 1 秒多的同一声音直观验证了「采集 → 缓冲 → 播放」的通路。源码逐段剖析示例的全部逻辑集中在单个文件 audio.c 中下面按职责拆解。程序骨架与协议注册main()audio.c#L196-L221的结构非常精简signal(SIGINT, sigint_handler); lws_context_info_defaults(info, NULL); lws_cmdline_option_handle_builtin(argc, argv, info); info.port CONTEXT_PORT_NO_LISTEN_SERVER; /* 本示例不监听任何网络端口 */ info.protocols protocols; context lws_create_context(info); ... while (n 0) n lws_service(context, 0); /* 纯事件循环不设超时 */两点值得注意CONTEXT_PORT_NO_LISTEN_SERVER表示该上下文不创建监听端口——这是一个纯粹的、面向本地文件描述符的事件循环无需任何网络入口SIGINT处理器调用lws_default_loop_exit(context)audio.c#L191-L194让Ctrl-C能优雅地退出lws_service循环并触发销毁流程。协议表只注册了一个协议audio.c#L184-L187static struct lws_protocols protocols[] { { lws-audio-test, callback_raw_test, 0, 0, 0, NULL, 0 }, LWS_PROTOCOL_LIST_TERM };音频相关的所有状态保存在协议私有数据per-vhost private datastruct raw_vhd中audio.c#L24-L34struct raw_vhd { uint8_t simplebuf[32768 * 2]; /* 64KiB 环形缓冲区 */ snd_pcm_t *pcm_capture; snd_pcm_t *pcm_playback; snd_pcm_hw_params_t *params; snd_pcm_uframes_t frames; int filefd; int rpos; /* 播放读游标 */ int wpos; /* 采集写游标 */ int times; /* 采集回调次数计数用于制造延迟 */ };set_hw_params把 ALSA 设备接进 lws 事件循环的关键set_hw_params()audio.c#L36-L102) 是整段代码的精华它同时承担「配置 ALSA 硬件参数」与「将设备描述符交给 lws」两个任务n snd_pcm_open(pcm, default, type, SND_PCM_NONBLOCK); /* 非阻塞打开 */ ... if (snd_pcm_poll_descriptors(*pcm, pfd, 1) ! 1) { /* 取轮询 fd */ lwsl_err(%s: failed to get playback desc\n, __func__); return -1; } u.filefd (lws_filefd_type)(long long)pfd.fd; wsi1 lws_adopt_descriptor_vhost(vh, LWS_ADOPT_RAW_FILE_DESC, u, lws-audio-test, NULL); if (!wsi1) { ... } if (type SND_PCM_STREAM_PLAYBACK) lws_rx_flow_control(wsi1, 0); /* 播放通道关闭 POLLIN */几个关键点非阻塞打开SND_PCM_NONBLOCK保证 ALSA 调用不会阻塞事件循环线程获取轮询描述符ALSA PCM 通过snd_pcm_poll_descriptors()暴露一个可被poll()监听的 fd这正是 lws 事件循环可以接管它的接口示例断言该函数恰好返回 1 个描述符收养描述符lws_adopt_descriptor_vhost()声明见 include/libwebsockets/lws-adopt.h#L148-L151把外部 fd 包装成 lws 的 wsi绑定到协议lws-audio-test此后该 fd 的可读/可写事件就由 lws 统一轮询并回调播放通道屏蔽 POLLINlws_rx_flow_control(wsi1, 0)API 说明见 include/libwebsockets/lws-misc.h#L1407-L1426关闭播放 wsi 的读事件。因为播放设备本身不需要「读取」只会在采集事件驱动下被写入避免无意义的 POLLIN 干扰。随后是标准的 ALSA 硬件参数配置audio.c#L69-L92snd_pcm_hw_params_malloc(params); snd_pcm_hw_params_any(*pcm, params); snd_pcm_hw_params_set_access(*pcm, params, SND_PCM_ACCESS_RW_INTERLEAVED); snd_pcm_hw_params_set_format(*pcm, params, SND_PCM_FORMAT_S16_LE); snd_pcm_hw_params_set_channels(*pcm, params, 1); snd_pcm_hw_params_set_rate_near(*pcm, params, rate, 0); snd_pcm_hw_params(*pcm, params);采用的音频参数为交错读写interleaved、16 位小端有符号S16_LE、单声道、采样率 16000Hzrate_near允许驱动选择最接近的值。播放与采集两侧参数一致是回环不失真的基础。回调驱动的音频搬运环形缓冲与「1 秒多」的延迟所有 I/O 事件都汇聚到callback_raw_test()audio.c#L104-L182。在LWS_CALLBACK_PROTOCOL_INIT阶段通过lws_protocol_vh_priv_zalloc()分配raw_vhd并依次调用set_hw_params()打开播放、采集两个 PCM 设备audio.c#L113-L132。注意这里的顺序先播放后采集任何一步失败都会返回 -1 终止协议初始化。核心搬运逻辑在LWS_CALLBACK_RAW_RX_FILE分支audio.c#L147-L175。先看 lws 对这一回调的官方语义include/libwebsockets/lws-callbacks.h#L823-L827LWS_CALLBACK_RAW_RX_FILE指示 RAW 模式文件有内容可读。该回调并不会真的去读文件传入的len恒为 0需要应用代码自行执行读取。因此示例在回调里自行完成两次 PCM 传输每次采集事件触发时先播放仅当vhd-times 6把环形缓冲区头部rpos处的可用帧写入播放设备if (vhd-times 6) { /* delay amount decided by this */ r snd_pcm_writei(vhd-pcm_playback, vhd-simplebuf[vhd-rpos], (snd_pcm_uframes_t)(((unsigned int)(vhd-wpos - vhd-rpos)) (sizeof(vhd-simplebuf) - 1)) / 2); if (r 0) { n (int)r; vhd-rpos (int)(((unsigned int)(vhd-rpos (n * 2))) (sizeof(vhd-simplebuf) - 1)); } }再采集把新数据写入缓冲区尾部wpos处读入帧数按「到缓冲区末尾的剩余空间」计算r snd_pcm_readi(vhd-pcm_capture, vhd-simplebuf[vhd-wpos], (snd_pcm_uframes_t)(sizeof(vhd-simplebuf) - (size_t)vhd-wpos) / 2); if (r 0) { n (int)r; lwsl_notice(LWS_CALLBACK_RAW_RX_FILE: %d samples\n, n); vhd-times; vhd-wpos (int)(((unsigned int)(vhd-wpos (n * 2))) (sizeof(vhd-simplebuf) - 1)); }这里有几个值得展开的细节环形缓冲区的构造simplebuf大小为32768 * 2 65536字节恰好是 2 的幂。读写游标的更新都通过与(sizeof(buf) - 1)做按位与来实现取模回绕无需取余运算。按 16kHz × 2 字节/采样计算整个缓冲区可容纳约2 秒的音频采样单位换算wpos - rpos是字节差除以 2 换算成 S16 帧数传给snd_pcm_writei写入成功后rpos前进r * 2字节延迟的实现机制播放被vhd-times 6这道门闸挡住——必须等采集回调发生 6 次每次约 2000 多个采样对应百余毫秒之后才开始放音由此在缓冲区里积累了约 1 秒多的数据形成 README 所说的 something over 1s delay。注释/* delay amount decided by this */直接点明调节这个阈值即可改变回环延迟采集通道充当播放的时间源README 特别强调ALSA 并不天然适合直接用于事件循环因此示例选择「把会产生 POLLIN 的采集通道同时当作播放的节拍器」——播放不依赖自己的事件而是跟随采集事件的节奏被驱动两侧自然保持同一 16kHz 采样率。优雅收尾LWS_CALLBACK_PROTOCOL_DESTROYCtrl-C退出循环后LWS_CALLBACK_PROTOCOL_DESTROY分支audio.c#L134-L145负责资源回收if (vhd vhd-pcm_playback) { snd_pcm_drain(vhd-pcm_playback); /* 排空未播完的缓冲 */ snd_pcm_close(vhd-pcm_playback); vhd-pcm_playback NULL; } if (vhd vhd-pcm_capture) { snd_pcm_close(vhd-pcm_capture); vhd-pcm_capture NULL; }对播放设备先snd_pcm_drain()再关闭确保缓冲区里尚未播出的数据被完整送出避免截断爆音采集设备则直接关闭。关键 API 与回调速查收养类型lws_adoption_type示例使用的LWS_ADOPT_RAW_FILE_DESC只是收养类型枚举include/libwebsockets/lws-adopt.h#L67-L80中的一种其余常规定义如下类型常量值含义LWS_ADOPT_RAW_FILE_DESC0原始文件描述符便捷常量本示例所用LWS_ADOPT_HTTP1标志位以 HTTP 模式收养缺省表示 RAWLWS_ADOPT_SOCKET2标志位收养的是 socket缺省表示文件LWS_ADOPT_ALLOW_SSL4标志位允许 TLSLWS_ADOPT_FLAG_UDP16标志位socket 为 UDPLWS_ADOPT_FLAG_RAW_PROXY32标志位raw 代理LWS_ADOPT_FLAG_NO_NONBLOCK64标志位fd 无法设为非阻塞且读取不会阻塞lws_adopt_descriptor_vhost()接受的 fd 通过联合体 lws_sock_file_fd_type 传入要么填sockfdsocket要么填filefd文件/设备节点本示例正是把 ALSA 的 poll fd 填入filefd字段。如果调用失败lws 会关闭该 fd 并返回NULL调用方无需再自行清理。RAW 文件回调RAW 文件事件拥有独立的一套回调原因include/libwebsockets/lws-callbacks.h#L816-L836与 HTTP/WS 回调互不冲突可以共存于同一个协议回调编号触发时机LWS_CALLBACK_RAW_ADOPT_FILE63文件描述符被收养成功等价于「wsi 已创建」LWS_CALLBACK_RAW_RX_FILE64文件有内容可读不代为读取需自行 readLWS_CALLBACK_RAW_WRITEABLE_FILE65文件可写LWS_CALLBACK_RAW_CLOSE_FILE66收养了文件的 wsi 正在关闭运行日志中两条LWS_CALLBACK_RAW_ADOPT_FILE与两条LWS_CALLBACK_RAW_CLOSE_FILE正是播放、采集两个被收养设备各自的生命周期记录。仓库中的高层 ALSA 封装另一条实现路径值得说明的是minimal-raw-audio演示的是最底层的接入方式——直接调用 ALSA C API 并与lws_adopt_descriptor_vhost()组合。而本仓库同时提供了更上层的 ALSA 封装位于 lib/media/alsa/alsa.c其 API 声明集中在 include/libwebsockets/lws-alsa.hlws_alsa_create_capture(const struct lws_alsa_info *info)按lws_alsa_info设备名、采样率、声道数、每帧采样数创建采集上下文内部同样以SND_PCM_NONBLOCK打开设备并配置 S16_LE 参数见 alsa.c#L38-L88lws_alsa_get_fd()通过snd_pcm_poll_descriptors()取出可被事件循环监听的 fd见 alsa.c#L106-L118——与示例中的做法如出一辙lws_alsa_read()封装snd_pcm_readi()并对-EPIPE缓冲区欠载做snd_pcm_prepare()恢复见 alsa.c#L120-L136lws_alsa_enum_controls()/lws_alsa_set_control()通过 ALSA mixer 枚举、设置采集音量等控制项。若同时开启LWS_WITH_OPUSlib/media/alsa/alsa-opus.c 还提供lws_alsa_opus_capture_create()等接口把 ALSA 采集的 PCM 实时编码为 Opus 并通过回调交付见 alsa-opus.c#L35-L72适合 VoIP 等场景。这两套路径的取舍很清晰追求最小依赖、想理解底层机制时照抄minimal-raw-audio的写法追求工程效率时直接使用lib/media/alsa封装。注意事项与扩展方向为什么不能用「直接读 ALSA」替代事件循环ALSA 的阻塞读写无法与 lws 的单线程事件模型共存非阻塞打开 poll fd 收养是正确姿势本例更进一步让采集通道的 POLLIN 兼任播放的时间源规避了播放设备无独立事件的问题。延迟可调修改callback_raw_test()中vhd-times 6的门限即可改变回环延迟缓冲区容量为 2 秒门限对应的缓冲量需保持在其范围内。参考姊妹示例RAW 文件描述符的通用玩法收养 stdin、串口/dev/ttyUSB0、输入设备/dev/input/eventn等可参考 minimal-raw-file/README.md该系列下还有 raw adopt TCP/UDP、raw proxy、raw vhost 等示例见 minimal-examples-lowlevel/raw/README.md。运行前提需要真实可用的 ALSA 默认播放/采集设备无麦克风的机器上采集侧可能静默观察不到 RX 日志。总的来说minimal-raw-audio以不到 200 行的代码完整示范了「外部描述符收养 RAW 文件回调 环形缓冲 事件驱动搬运」这一组合拳是理解 lws 事件循环通用性的绝佳入门样例。赞分享后端网络通信【免费下载链接】libwebsocketscanonical libwebsockets.org networking library项目地址https://gitcode.com/gh_mirrors/li/libwebsockets点击查看免费下载相关推荐libwebsockets minimal-raw-audio 实战用 lws 事件循环驱动 ALSA 录音与回放libwebsockets minimal raw audio 实战用 lws 事件循环驱动 ALSA 录音与回放 导读 lws minimal raw au人工智能AI Agent多模态语音AI 应用libwebsocketslws集成外来事件循环minimal-http-server-eventlib-foreign 示例深度解析libwebsocketslws集成外来事件循环minimal http server eventlib foreign 示例深度解析 本篇文章围绕 TE人工智能AI Agent多模态语音AI 应用libwebsockets 自定义事件循环集成实战解析 lws-minimal-http-server-eventlib-custom 示例libwebsockets 自定义事件循环集成实战解析 lws minimal http server eventlib custom 示例 导读 lws m人工智能AI Agent多模态语音AI 应用上一篇技术揭秘R3nzSkin如何通过内存注入与钩子技术实现LOL皮肤实时替换下一篇终极网盘下载解决方案LinkSwift开源工具全面指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →