如何生成带头部信息的OGG页面流?Windows C++下PCM转OGG求助
实现16位立体声PCM转OGG封装Opus流并通过WebSocket传输的方案
针对你的需求(Windows+VS2019+C++32位),以下是分步实现指南,涵盖Opus编码、OGG页面封装、WebSocket发送及服务器端解码要点:
一、VS2019环境配置(Opus库准备)
- 编译32位Opus库
- 下载Opus官方源码,使用CMake生成VS2019 32位工程:
- 打开CMake GUI,指定源码路径和输出路径,点击
Configure,选择Visual Studio 16 2019并勾选Win32,完成配置后点击Generate。 - 打开生成的VS工程,编译
opus项目(推荐静态库,避免部署DLL)。
- 打开CMake GUI,指定源码路径和输出路径,点击
- 下载Opus官方源码,使用CMake生成VS2019 32位工程:
- 项目配置
- 右键项目→属性→C/C++→常规:添加Opus源码的
include目录到附加包含目录。 - 链接器→常规:添加Opus编译生成的
lib目录到附加库目录。 - 链接器→输入:添加
opus.lib到附加依赖项。
- 右键项目→属性→C/C++→常规:添加Opus源码的
二、Opus编码器初始化与PCM编码
Opus仅负责编码PCM为Opus帧,OGG封装需单独处理。以下是核心代码:
#include <opus/opus.h> #include <cstdlib> // 固定配置(推荐48kHz,Opus最优支持) const int SAMPLE_RATE = 48000; const int CHANNELS = 2; // 立体声 const int FRAME_SIZE = 960; // 20ms帧长(48000*0.02),单通道样本数 const int BITRATE = 128000; // 比特率,可根据需求调整 int main() { int error; // 初始化Opus编码器 OpusEncoder* encoder = opus_encoder_create(SAMPLE_RATE, CHANNELS, OPUS_APPLICATION_AUDIO, &error); if (error != OPUS_OK) { // 处理初始化失败,如打印opus_strerror(error) return -1; } // 设置编码器参数 opus_encoder_ctl(encoder, OPUS_SET_BITRATE(BITRATE)); opus_encoder_ctl(encoder, OPUS_SET_COMPLEXITY(10)); // 10为最高复杂度,平衡质量与性能 // 模拟输入:16位立体声PCM数据(实际从你的数据流读取) opus_int16 pcm_buffer[FRAME_SIZE * CHANNELS]; // 每帧总样本数=单通道帧长*通道数 // 填充pcm_buffer... // 编码Opus帧 unsigned char opus_frame[4096]; int opus_frame_len = opus_encode(encoder, pcm_buffer, FRAME_SIZE, opus_frame, sizeof(opus_frame)); if (opus_frame_len < 0) { // 处理编码错误 opus_encoder_destroy(encoder); return -1; } // 后续OGG封装、WebSocket发送... opus_encoder_destroy(encoder); return 0; }
三、OGG页面封装(Opus规范适配)
OGG需封装OpusHead(必选)、OpusTags(可选但规范要求)及编码后的Opus帧,使用libogg库处理页面组装:
- 添加libogg依赖:同Opus库,编译32位libogg并配置到项目中。
- 核心封装代码:
#include <ogg/ogg.h> #include <cstring> // 构造OpusHead包(OGG流首包) void create_opus_head_packet(ogg_packet& op) { unsigned char head_data[18]; // OpusHead结构:"OpusHead" + 版本 + 通道数 + 预跳过 + 采样率 + 输出增益 + 映射表 memcpy(head_data, "OpusHead", 8); head_data[8] = 1; // 版本1 head_data[9] = CHANNELS; // 通道数 *(opus_int16*)(head_data+10) = 0; // 预跳过(0即可) *(opus_int32*)(head_data+12) = SAMPLE_RATE; // 采样率 *(opus_int16*)(head_data+16) = 0; // 输出增益 head_data[17] = 0; // 映射表(立体声无需映射) op.packet = head_data; op.bytes = 18; op.b_o_s = 1; // 流起始标记 op.e_o_s = 0; op.granulepos = 0; op.packetno = 0; } // 封装并发送OGG页面 void process_ogg_stream(ogg_stream_state& os, ogg_packet& op, void (*send_func)(const char*, size_t)) { ogg_page og; ogg_stream_packetin(&os, &op); // 生成所有可用OGG页面 while (ogg_stream_pageout(&os, &og)) { // 发送完整OGG页面(header+body) send_func((const char*)og.header, og.header_len); send_func((const char*)og.body, og.body_len); } } // 在主流程中调用: int main() { // ... 编码器初始化、编码Opus帧 ... // 初始化OGG流(随机生成serial number) ogg_stream_state os; ogg_stream_init(&os, rand()); // 发送OpusHead ogg_packet op; create_opus_head_packet(op); process_ogg_stream(os, op, send_websocket_binary); // 发送OpusTags(可选,示例为空标签) unsigned char tags_data[12]; memcpy(tags_data, "OpusTags", 8); *(opus_int32*)(tags_data+8) = 0; // 无注释 op.packet = tags_data; op.bytes = 12; op.b_o_s = 0; op.e_o_s = 0; op.granulepos = 0; op.packetno = 1; process_ogg_stream(os, op, send_websocket_binary); // 发送编码后的Opus帧 static ogg_int64_t granule_pos = 0; static int packet_no = 2; op.packet = opus_frame; op.bytes = opus_frame_len; op.b_o_s = 0; op.e_o_s = 0; granule_pos += FRAME_SIZE; // Opus granulepos为单通道累计样本数 op.granulepos = granule_pos; op.packetno = packet_no++; process_ogg_stream(os, op, send_websocket_binary); // 流结束时发送终止包 op.packet = nullptr; op.bytes = 0; op.e_o_s = 1; op.granulepos = granule_pos; op.packetno = packet_no++; process_ogg_stream(os, op, send_websocket_binary); ogg_stream_clear(&os); // ... 清理资源 ... }
四、WebSocket发送实现
使用Windows原生WebSocket API(依赖ws2_32.lib)发送二进制OGG页面:
#include <winsock2.h> #include <websocket.h> #pragma comment(lib, "ws2_32.lib") #pragma comment(lib, "websocket.lib") SOCKET ws_socket; // 假设已完成WebSocket握手 void send_websocket_binary(const char* data, size_t len) { WSABUF buf; buf.buf = (char*)data; buf.len = len; DWORD sent; // 发送二进制帧 int ret = WSASend(ws_socket, &buf, 1, &sent, WS_OPCODE_BINARY, nullptr, nullptr); if (ret == SOCKET_ERROR) { // 处理发送错误,如WSAGetLastError() } }
五、服务器端解码注意事项
服务器使用DecodeAudioData时需注意:
- 必须接收完整的OGG流(包含
OpusHead和OpusTags),否则解码失败。 - 拼接WebSocket接收的二进制数据,再传入
decodeAudioData:
const audioCtx = new AudioContext(); let oggStream = new Uint8Array(); websocket.onmessage = (e) => { if (e.data instanceof ArrayBuffer) { const newChunk = new Uint8Array(e.data); // 拼接流数据 const combined = new Uint8Array(oggStream.length + newChunk.length); combined.set(oggStream); combined.set(newChunk, oggStream.length); oggStream = combined; // 尝试解码(若数据不足会自动失败,继续等待) audioCtx.decodeAudioData(oggStream.buffer) .then(audioBuffer => { // 解码成功:audioBuffer.getChannelData(0)为左声道PCM,getChannelData(1)为右声道 oggStream = new Uint8Array(); // 重置流,准备接收下一段 }) .catch(err => { // 数据不足,忽略错误继续等待 }); } };
关键注意事项
- 帧长匹配:Opus仅支持特定帧长(2.5/5/10/20/40/60ms),需与采样率对应,如48kHz下20ms对应960单通道样本。
- 32位兼容性:所有依赖库(Opus、libogg、WebSocket相关)必须编译为32位,否则会出现链接或运行时错误。
- OGG页面完整性:WebSocket发送时必须保证单个OGG页面不拆分,否则服务器无法解析。
- 资源清理:使用完Opus编码器和OGG流状态后,必须调用
opus_encoder_destroy和ogg_stream_clear释放资源。
内容的提问来源于stack exchange,提问作者Arjun Natarajan
相关产品推荐
相关产品推荐

