如何:对磁盘中的声音进行流式处理

注意

此内容仅适用于桌面应用,需要修订才能在 Windows 应用商店应用中正常运行。 请参阅 CreateFile2CreateEventExWaitForSingleObjectExSetFilePointerExGetOverlappedResultEx 的文档。 请参阅 Windows SDK 示例库中的 StreamEffect 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 编程指南

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

如何:使用源语音回调