2017-10-19 137 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