You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C++音频数据缓冲区正确操作及语音识别断词问题排查

问题描述

已基于C++实现Windows平台下麦克风音频录制、回放功能,后续需要对接语音识别业务做音频数据处理。为避免识别时出现词语截断问题,原本计划将采集到的小块音频数据拼接写入大容量连续缓冲区,当前使用memcpy实现小块采集缓冲区到大容量缓冲区的拷贝拼接,但程序运行后仅能识别到部分词语,功能不符合预期。需要排查当前缓冲区操作的错误点,同时寻找更便捷合理的音频数据缓冲区操作方案。

现有实现代码
#include <stdio.h>
#include <Windows.h>
#include <mmsystem.h>
#include <iostream>
#include <fstream>

using namespace std;

#pragma comment(lib, "winmm.lib")
#define Samples 16000
#define NUM_FRAMES Samples*2
#define Channels 1
const int NUM_BUF = 4;

int main()
{
    HWAVEIN   inStream;
    HWAVEOUT outStream;
    WAVEFORMATEX waveFormat;
    WAVEHDR buffer[NUM_BUF];

    waveFormat.cbSize = 0;    
    waveFormat.wFormatTag = WAVE_FORMAT_PCM;     
    waveFormat.nChannels = Channels;                
    waveFormat.nSamplesPerSec = Samples;
    waveFormat.wBitsPerSample = 16;           
    waveFormat.nBlockAlign = waveFormat.nChannels * waveFormat.wBitsPerSample / 8;
    waveFormat.nAvgBytesPerSec = waveFormat.nBlockAlign * waveFormat.nSamplesPerSec;
    HANDLE event = CreateEventA(NULL, TRUE, FALSE, "waveout event");
    MMRESULT res = MMSYSERR_NOERROR;
    res = waveInOpen(&inStream, WAVE_MAPPER, &waveFormat, (unsigned long)event, 0, CALLBACK_EVENT);
    if (res != MMSYSERR_NOERROR) {
        printf("error in waveInOpen\n");
        return -1;
    }
    res = waveOutOpen(&outStream, WAVE_MAPPER, &waveFormat, (unsigned long)event, 0, CALLBACK_EVENT);
    if (res != MMSYSERR_NOERROR) {
        printf("error in waveOutOpen\n");
        return -2;
    }
     short int  *_pBuf;
    size_t bpbuff = 16000*2;
    _pBuf = new short int [bpbuff * NUM_BUF];
    for ( int i = 0; i < NUM_BUF; i++ )
    {
        buffer[i].lpData = (LPSTR)&_pBuf [i * bpbuff];
        buffer[i].dwBufferLength = bpbuff*sizeof(*_pBuf);
        buffer[i].dwFlags = 0L;
        buffer[i].dwLoops = 0L;
        waveInPrepareHeader(inStream, & buffer[i], sizeof(WAVEHDR));
    }
    ResetEvent(event);
    for (int index = 0; index < NUM_BUF; index++) // queue all buffers for input
        waveInAddBuffer(inStream, &buffer[index], sizeof(WAVEHDR));
    waveInStart(inStream);
    int len_buff = buffer[0].dwBufferLength*6 + 1;
    int limit_buff = buffer[0].dwBufferLength*5 + 1;
    int size = buffer[0].dwBufferLength;
    int rl = 0;
    int flagg = 0;
    char * buff1 = new char[len_buff];
    char * buff2 = new char[len_buff];
    int flag_buf = 0;
    int flag1 = 0, flag2 = 0;
    int i = 0;
    int inIndex = 0, outIndex = 0; // the next input and output to watch
    while (true) { 
        if (buffer[inIndex].dwFlags & WHDR_DONE & flagg!=1) 
        {
            flagg = 1;
            waveInAddBuffer(inStream, &buffer[inIndex], sizeof(WAVEHDR));
            inIndex = (inIndex + 1) % NUM_BUF;
        }
        
        if (buffer[outIndex].dwFlags & WHDR_DONE & flagg!=0) {
            flagg = 0;
            if (flag_buf == 0) 
            {
                if (rl<limit_buff) 
                {
                    cout << rl << endl;
                    if (flag1 == 0) 
                    {
                        //strcpy(buff1, buffer[outIndex].lpData);
                        memcpy(buff1, buffer[outIndex].lpData, size);
                        flag1 = 1;
                        rl = size + 1;
                    }
                    else
                    {
                        //strcat(buff1, buffer[outIndex].lpData);
                        memcpy(buff1 + rl, buffer[outIndex].lpData, size);
                        rl = rl + size;
                    }
                }
                else
                {
                    //recognize buff1
                    flag_buf = 1;
                    flag1 = 0;
                    rl = 0;
                }
            }
            else
            {
                if (rl<limit_buff) 
                {
                    if (flag2 == 0) 
                    {
                        memcpy(buff2, buffer[outIndex].lpData, size);
                        flag2 = 1;
                        rl = size + 1;
                    }
                    else
                    {
                        memcpy(buff2 + rl, buffer[outIndex].lpData, size);
                        rl = rl + size;
                    }
                }
                else
                {
                    //recognize buff2 
                    flag_buf = 0;
                    flag2 = 0;
                    rl = 0;
                }
            }
            waveOutWrite(outStream, &buffer[outIndex], sizeof(WAVEHDR));
            outIndex = (outIndex + 1) % NUM_BUF;  


            printf("N_buff_%i %i\n",outIndex , i);
            i++;
        }
        
    }
    for (int index = 0; index < 4; index++)
        waveInUnprepareHeader(inStream, &buffer[inIndex], sizeof(WAVEHDR));
    free(buffer);
    
}
现有实现的错误点
  • 缓冲区写入偏移计算错误:首次写入大容量缓冲区后,rl被设置为size + 1,多偏移了1字节,后续每次memcpy的目标地址都会错位1字节,直接导致PCM音频数据解析错乱,语音识别只能识别到零散词语。
  • 位运算逻辑优先级错误:判断缓冲区状态的条件buffer[inIndex].dwFlags & WHDR_DONE & flagg!=1存在运算优先级问题,!=优先级高于&,实际执行逻辑和预期完全不符,会导致采集缓冲区队列提交不及时、已填充数据的缓冲区被重复覆盖,出现音频丢块。
  • 双缓冲切换丢数据:当累计数据长度达到limit_buff触发识别时,直接将rl重置为0、切换到另一块缓冲区,当前已经填充到缓冲区里、不足一块的剩余音频数据被直接丢弃,必然出现词语截断。
  • 资源释放逻辑错误:buffer是栈上数组,末尾调用free(buffer)属于非法内存释放;waveInUnprepareHeader循环固定使用inIndex对应的缓冲区,没有遍历所有已提交的采集缓冲区做资源回收,会触发内存泄漏和API调用异常。
  • 事件回调使用错误:waveInOpen和waveOutOpen绑定了同一个事件句柄,音频采集完成、播放完成都会触发同一个事件,代码没有分别判断是采集还是播放触发的事件,会出现采集、播放流程互相干扰的问题。
更合理的音频缓冲区操作方案
  • 采用**环形缓冲区(循环队列)**作为音频采集的统一大容量缓存,不需要做双缓冲硬切换:维护读指针、写指针两个位置标记,采集到新音频块时直接从写指针位置写入、移动写指针;语音识别模块从读指针位置读取固定长度(满足识别输入要求的帧长,比如16k采样率下10ms对应160采样点、320字节)的数据、移动读指针,读写位置通过取模实现环形复用,从根源上避免数据截断和拷贝错位。
  • 偏移计算严格按照字节数对齐:PCM数据是16位单声道格式,所有长度、偏移计算统一以字节为单位,不要额外加无意义的+1偏移,拷贝前校验目标缓冲区剩余空间,避免越界。
  • 分离采集、播放的事件通知:给音频输入、输出分别创建独立的事件句柄,事件触发后分别判断对应设备的缓冲区状态,避免流程串扰。
  • 采集缓冲区回收逻辑和数据拷贝逻辑解耦:采集到满块数据后,第一时间将该块重新提交给采集队列,避免采集丢帧,数据拷贝到环形缓冲区的操作单独处理,不要阻塞采集缓冲区的重新入队。
  • 语音识别取数采用滑动窗口逻辑:环形缓冲区中累计的数据长度满足识别要求的最小帧长时,就可以取固定长度的数据送入识别模块,不需要等攒满固定的大块再处理,同时保留前后帧的重叠部分(比如每次取30ms数据,步进10ms),进一步降低词语截断的概率。

内容的提问来源于stack exchange,提问作者Saivaks

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 18:27:34