操作说明:对磁盘中的声音进行流式处理

注意

此内容仅适用于桌面应用(并且需要修订才能在 UWP 应用中运行)。 请参阅 CreateFile2CreateEventExWaitForSingleObjectExSetFilePointerExGetOverlappedResultEx 的文档。 请参阅现已存档的 Windows SDK 示例库中的 XAudio2 音频流效果 Windows 8 示例应用

可以通过创建一个单独的线程在 XAudio2 中流式传输音频数据,并在流式线程中执行音频数据的缓冲区读取,然后使用回调来控制该线程。

在流式处理线程中执行缓冲区读取

若要在流式处理线程中执行缓冲区读取,请执行以下步骤:

  1. 创建读取缓冲区数组。

    #define STREAMING_BUFFER_SIZE 65536
    #define MAX_BUFFER_COUNT 3
    BYTE buffers[MAX_BUFFER_COUNT][STREAMING_BUFFER_SIZE];
    
  2. 初始化 OVERLAPPED 结构。

    该结构用于检查异步磁盘读取完成的时间。

    OVERLAPPED Overlapped = {0};
    Overlapped.hEvent = CreateEvent( NULL, TRUE, FALSE, NULL );
    
  3. 在将播放流式处理音频的源语音上调用 Start 函数。

    hr = pSourceVoice->Start( 0, 0 );
    
  4. 在当前读取位置未传递到音频文件的末尾时循环。

    CurrentDiskReadBuffer = 0;
    CurrentPosition = 0;
    while ( CurrentPosition < cbWaveSize )
    {
        ...
    }
    

    在循环中,执行以下操作:

    1. 将磁盘上的数据块读取到当前的读取缓冲区中。

      DWORD dwRead;
      if( SUCCEEDED(hr) && 0 == ReadFile( hFile, pData, dwDataSize, &dwRead, pOverlapped ) )
          hr = HRESULT_FROM_WIN32( GetLastError() );
          DWORD cbValid = min( STREAMING_BUFFER_SIZE, cbWaveSize - CurrentPosition );
          DWORD dwRead;
          if( 0 == ReadFile( hFile, buffers[CurrentDiskReadBuffer], STREAMING_BUFFER_SIZE, &dwRead, &Overlapped ) )
              hr = HRESULT_FROM_WIN32( GetLastError() );
          Overlapped.Offset += cbValid;
      
          //update the file position to where it will be once the read finishes
          CurrentPosition += cbValid;
      
    2. 使用 GetOverlappedResult 函数等待指示读取已完成的事件。

      DWORD NumberBytesTransferred;
      ::GetOverlappedResult(hFile,&Overlapped,&NumberBytesTransferred, TRUE);
      
    3. 等待源语音上排队的缓冲区数量小于读取缓冲区的数量。

      使用 GetState 函数检查源语音的状态。

      XAUDIO2_VOICE_STATE state;
      while( pSourceVoice->GetState( &state ), state.BuffersQueued >= MAX_BUFFER_COUNT - 1)
      {
          WaitForSingleObject( Context.hBufferEndEvent, INFINITE );
      }
      
    4. 使用 SubmitSourceBuffer 函数将当前读取缓冲区提交到源语音

      XAUDIO2_BUFFER buf = {0};
      buf.AudioBytes = cbValid;
      buf.pAudioData = buffers[CurrentDiskReadBuffer];
      if( CurrentPosition >= cbWaveSize )
      {
          buf.Flags = XAUDIO2_END_OF_STREAM;
      }
      pSourceVoice->SubmitSourceBuffer( &buf );
      
    5. 将当前读取缓冲区索引设置为下一个缓冲区。

      CurrentDiskReadBuffer++;
      CurrentDiskReadBuffer %= MAX_BUFFER_COUNT;
      
  5. 循环完成后,等待剩余的排队缓冲区完成播放。

    当剩余的缓冲区播放完毕后,声音将停止,线程可以退出或重新用于流式传输另一个声音。

    XAUDIO2_VOICE_STATE state;
    while( pSourceVoice->GetState( &state ), state.BuffersQueued > 0 )
    {
        WaitForSingleObjectEx( Context.hBufferEndEvent, INFINITE, TRUE );
    }
    

创建回调类

若要创建回调类,请创建继承自 IXAudio2VoiceCallback 接口类。

该类应在其 OnBufferEnd 方法中设置事件。 这允许流式处理线程将自身置于睡眠状态,直到事件发出 XAudio2 已完成从音频缓冲区读取的信号。 有关将回调与 XAudio2 配合使用的详细信息,请参阅操作指南:使用源语音回调

struct StreamingVoiceContext : public IXAudio2VoiceCallback
{
    HANDLE hBufferEndEvent;
    StreamingVoiceContext(): hBufferEndEvent( CreateEvent( NULL, FALSE, FALSE, NULL ) ){}
    ~StreamingVoiceContext(){ CloseHandle( hBufferEndEvent ); }
    void OnBufferEnd( void* ){ SetEvent( hBufferEndEvent ); }
    ...
};

流式处理音频数据

XAudio2 回调

XAudio2 编程指南

如何:构建基本的音频处理图

如何:使用源语音回调