2017-10-19 50 views
0

提供一点上下文。我正尝试从c#应用程序中的相机输出实时音频。做了一些研究后,在C++托管的dll中执行它似乎很明显。我选择了XAudio2 API,因为它应该很容易实现和使用动态音频内容。XAudio2 - 使用动态缓冲区时打开输出

所以这个想法是用C++创建一个空缓冲区的XAudio设备,并从C#代码端推入音频。音频块每隔50ms被推送一次,因为我希望尽可能缩短延迟。定时器运行它

// SampleRate = 44100; Channels = 2; BitPerSample = 16; 
var blockAlign = (Channels * BitsPerSample)/8; 
var avgBytesPerSecond = SampleRate * blockAlign; 
var avgBytesPerMillisecond = avgBytesPerSecond/1000; 
var bufferSize = avgBytesPerMillisecond * Time; 
_sampleBuffer = new byte[bufferSize]; 

每次获取音频缓冲器的指针,从音频读取数据,将数据复制到指针并调用PushAudio方法。 我也使用秒表来检查处理过程花了多长时间,并再次计算定时器的时间间隔以包含处理时间。

private void PushAudioChunk(object sender, ElapsedEventArgs e) 
{ 
    unsafe 
    { 
     _pushAudioStopWatch.Reset(); 
     _pushAudioStopWatch.Start(); 

     var audioBufferPtr = Output.AudioCapturerBuffer(); 
     FillBuffer(_sampleBuffer); 
     Marshal.Copy(_sampleBuffer, 0, (IntPtr)audioBufferPtr, _sampleBuffer.Length); 

     Output.PushAudio(); 

     _pushTimer.Interval = Time - _pushAudioStopWatch.ElapsedMilliseconds; 
     _pushAudioStopWatch.Stop(); 
     DIX.Log.WriteLine("Push audio took: {0}ms", _pushAudioStopWatch.ElapsedMilliseconds);     
    } 
} 

这是C++部分的实现。

关于msdn的文档,我创建了一个XAudio2设备并添加了MasterVoice和SourceVoice。起初缓冲区是空的,因为c#部分负责推入音频数据。

namespace Audio 
{ 
    using namespace System; 

    template <class T> void SafeRelease(T **ppT) 
    { 
     if (*ppT) 
     { 
      (*ppT)->Release(); 
      *ppT = NULL; 
     } 
    } 

    WAVEFORMATEXTENSIBLE wFormat; 

    XAUDIO2_BUFFER buffer = { 0 }; 

    IXAudio2* pXAudio2 = NULL; 
    IXAudio2MasteringVoice* pMasterVoice = NULL; 
    IXAudio2SourceVoice* pSourceVoice = NULL;   

    WaveOut::WaveOut(int bufferSize) 
    { 
     audioBuffer = new Byte[bufferSize]; 

     wFormat.Format.wFormatTag = WAVE_FORMAT_PCM; 
     wFormat.Format.nChannels = 2; 
     wFormat.Format.nSamplesPerSec = 44100; 
     wFormat.Format.wBitsPerSample = 16; 
     wFormat.Format.nBlockAlign = (wFormat.Format.nChannels * wFormat.Format.wBitsPerSample)/8; 
     wFormat.Format.nAvgBytesPerSec = wFormat.Format.nSamplesPerSec * wFormat.Format.nBlockAlign; 
     wFormat.Format.cbSize = 0; 
     wFormat.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; 

     HRESULT hr = XAudio2Create(&pXAudio2, 0, XAUDIO2_DEFAULT_PROCESSOR); 

     if (SUCCEEDED(hr)) 
     { 
      hr = pXAudio2->CreateMasteringVoice(&pMasterVoice); 
     } 

     if (SUCCEEDED(hr)) 
     { 
      hr = pXAudio2->CreateSourceVoice(&pSourceVoice, (WAVEFORMATEX*)&wFormat, 
       0, XAUDIO2_DEFAULT_FREQ_RATIO, NULL, NULL, NULL); 
     } 

     buffer.pAudioData = (BYTE*)audioBuffer; 
     buffer.AudioBytes = bufferSize; 
     buffer.Flags = 0; 

     if (SUCCEEDED(hr)) 
     { 
      hr = pSourceVoice->Start(0); 
     } 
    } 

    WaveOut::~WaveOut() 
    { 

    } 

    WaveOut^ WaveOut::CreateWaveOut(int bufferSize) 
    { 
     return gcnew WaveOut(bufferSize); 
    } 

    uint8_t* WaveOut::AudioCapturerBuffer() 
    { 
     if (!audioBuffer) 
     { 
      throw gcnew Exception("Audio buffer is not initialized. Did you forget to set up the audio container?"); 
     } 

     return (BYTE*)audioBuffer; 
    } 

    int WaveOut::PushAudio() 
    { 
     HRESULT hr = pSourceVoice->SubmitSourceBuffer(&buffer); 

     if (FAILED(hr)) 
     { 
      return -1; 
     } 

     return 0; 
    } 
} 

我面临的问题是,我总是有一些输出开裂。我试图增加定时器的间隔或增加缓冲区大小。每次都有相同的结果。

我在做什么错?

更新:

我在3个缓冲区XAudio引擎可以顺利通过。裂开了。现在缺少的部分是在c#部分的正确时间填充缓冲区,以避免使用相同数据的缓冲区。

void Render(void* param) 
{ 
    std::vector<byte> audioBuffers[BUFFER_COUNT]; 
    size_t currentBuffer = 0; 

    // Get the current state of the source voice 
    while (BackgroundThreadRunning && pSourceVoice) 
    { 
     if (pSourceVoice) 
     { 
      pSourceVoice->GetState(&state); 
     } 

     while (state.BuffersQueued < BUFFER_COUNT) 
     { 
      std::vector<byte> resultData; 
      resultData.resize(DATA_SIZE); 
      CopyMemory(&resultData[0], pAudioBuffer, DATA_SIZE); 

      // Retreive the next buffer to stream from MF Music Streamer 
      audioBuffers[currentBuffer] = resultData; 

      // Submit the new buffer 
      XAUDIO2_BUFFER buf = { 0 }; 
      buf.AudioBytes = static_cast<UINT32>(audioBuffers[currentBuffer].size()); 
      buf.pAudioData = &audioBuffers[currentBuffer][0]; 

      pSourceVoice->SubmitSourceBuffer(&buf); 

      // Advance the buffer index 
      currentBuffer = ++currentBuffer % BUFFER_COUNT; 

      // Get the updated state 
      pSourceVoice->GetState(&state); 
     } 

     Sleep(30); 
    } 
} 
+0

在黑暗中野生刺伤 - 你有没有尝试清空缓冲区? – BugFinder

+0

你好。不,我没有。我应该怎么做?在推新音频块之前? – datoml

+0

定义它之后,只需将每个字节设置为0 – BugFinder

回答

1

XAudio2不在您通过SubmitSourceBuffer提交的时间复制源数据缓冲区。您必须保持该数据(位于您的应用程序内存中)有效,并且分配给XAudio2需要从中读取数据以便处理数据的整个时间。这样做是为了提高效率,以避免需要额外的副本,但是将多线程负担保持在可用内存,直到完成播放为止。这也意味着你不能修改播放缓冲区。

您当前的代码只是重复使用相同的缓冲区,导致弹出,因为您在播放时更改数据。你可以通过在两个或三个缓冲区之间旋转来解决这个问题。 XAudio2源语音具有状态信息,您可以使用它来确定播放缓冲区的时间,或者您可以注册显式回调函数,告诉您何时不再使用缓冲区。

有关使用XAudio2的示例,请参阅DirectX Tool Kit for Audioclassic XAudio2 samples

+0

感谢您的信息!我在上周五实施了这种解决方案,并且它变得更好。我在上面更新我的问题,以便我们看看是否可能做错了。 – datoml