2011-11-22 101 views
0

我嘗試流的聲音樣本從我麥克風揚聲器使用的DirectSoundC#。它應該類似於'聽麥克風',但後來我想用它來做其他事情。通過測試我的方法,我注意到無聲的發癢,在背景中破解噪音。我想這與寫入和播放緩衝區之間的延遲有關,它必須大於寫入區塊的延遲。如何在使用DirectSound和C#將聲音從麥克風傳輸到揚聲器時避免無聲的發癢噪音?

如果我將錄製和播放之間的延遲設置爲小於50ms。大多數情況下,它的工作,但有時我真的響亮的開裂噪音。所以我決定延遲至少50ms。這對我來說可行,但系統「聽設備」的延遲似乎要短得多。我猜測它大約是15-30ms,幾乎不明顯。對於50ms,我至少會得到一點混響效果。

下面我會告訴你我的麥克風碼(部分): 初始化像這樣做:

 capture = new Capture(device); 

     // Creating the buffer 
     // Determining the buffer size 
     bufferSize = format.AverageBytesPerSecond * bufferLength/1000; 
     while (bufferSize % format.BlockAlign != 0) bufferSize += 1; 
     chunkSize = Math.Max(bufferSize, 256); 
     bufferSize = chunkSize * BUFFER_CHUNKS; 
     this.bufferLength = chunkSize * 1000/format.AverageBytesPerSecond; // Redetermining the buffer Length that will be used. 

     captureBufferDescription = new CaptureBufferDescription(); 
     captureBufferDescription.BufferBytes = bufferSize; 
     captureBufferDescription.Format = format; 
     captureBuffer = new CaptureBuffer(captureBufferDescription, capture); 

     // Creating Buffer control   
     bufferARE = new AutoResetEvent(false); 
     // Adding notifier to buffer. 
     bufferNotify = new Notify(captureBuffer); 
     BufferPositionNotify[] bpns = new BufferPositionNotify[BUFFER_CHUNKS]; 
     for(int i = 0 ; i < BUFFER_CHUNKS ; i ++) bpns[i] = new BufferPositionNotify() { Offset = chunkSize * (i+1) - 1, EventNotifyHandle = bufferARE.SafeWaitHandle.DangerousGetHandle() }; 
     bufferNotify.SetNotificationPositions(bpns); 

的拍攝將在一個額外的線程中運行這樣的:

 // Initializing 
     MemoryStream tempBuffer = new MemoryStream(); 

     // Capturing 
     while (isCapturing && captureBuffer.Capturing) 
     { 
      bufferARE.WaitOne(); 
      if (isCapturing && captureBuffer.Capturing) 
      { 
       captureBuffer.Read(currentBufferPart * chunkSize, tempBuffer, chunkSize, LockFlag.None); 
       ReportChunk(applyVolume(tempBuffer.GetBuffer())); 
       currentBufferPart = (currentBufferPart + 1) % BUFFER_CHUNKS; 
       tempBuffer.Dispose(); 
       tempBuffer = new MemoryStream(); // Reset Buffer; 
      } 
     } 

     // Finalizing 
     isCapturing = false; 
     tempBuffer.Dispose(); 
     captureBuffer.Stop(); 
     if (bufferARE.WaitOne(bufferLength + 1)) currentBufferPart = (currentBufferPart + 1) % BUFFER_CHUNKS; // That on next start the correct bufferpart will be read. 
     stateControlARE.Set(); 

捕獲時ReportChunk將數據作爲可以訂閱的事件發送給發言者。所述揚聲器部分被初始化這樣的:

 // Creating the dxdevice. 
     dxdevice = new Device(device); 
     dxdevice.SetCooperativeLevel(hWnd, CooperativeLevel.Normal); 

     // Creating the buffer 
     bufferDescription = new BufferDescription(); 
     bufferDescription.BufferBytes = bufferSize; 
     bufferDescription.Format = input.Format; 
     bufferDescription.ControlVolume = true; 

     bufferDescription.GlobalFocus = true; // That sound doesn't stop if the hWnd looses focus. 
     bufferDescription.StickyFocus = true; // - " - 
     buffer = new SecondaryBuffer(bufferDescription, dxdevice); 
     chunkQueue = new Queue<byte[]>(); 

     // Creating buffer control 
     bufferARE = new AutoResetEvent(false); 

     // Register at input device 
     input.ChunkCaptured += new AInput.ReportBuffer(input_ChunkCaptured); 

的數據由所述事件方法放入隊列,簡單地通過:

 chunkQueue.Enqueue(buffer); 
     bufferARE.Set(); 

灌裝playbackbuffer和開始/停止播放緩衝區被另一個線程來完成:

 // Initializing 
     int wp = 0; 
     bufferARE.WaitOne(); // wait for first chunk 

     // Playing/writing data to play buffer. 
     while (isPlaying) 
     { 
      Thread.Sleep(1); 
      bufferARE.WaitOne(BufferLength * 3); // If a chunk is played and there is no new chunk we try to continue and may stop playing, else may the buffer runs out. 
      // Note that this may fails if the sender was interrupted within one chunk 
      if (isPlaying) 
      { 
       if (chunkQueue.Count > 0) 
       { 
        while (chunkQueue.Count > 0) wp = writeToBuffer(chunkQueue.Dequeue(), wp); 
        if (buffer.PlayPosition > wp - chunkSize * 3/2) buffer.SetCurrentPosition(((wp - chunkSize * 2 + bufferSize) % bufferSize)); 
        if (!buffer.Status.Playing) 
        { 
         buffer.SetCurrentPosition(((wp - chunkSize * 2 + bufferSize) % bufferSize)); // We have 2 chunks buffered so we step back 2 chunks and play them while getting new chunks. 
         buffer.Play(0, BufferPlayFlags.Looping); 
        } 
       } 
       else 
       { 
        buffer.Stop(); 
        bufferARE.WaitOne(); // wait for a filling chunk 
       } 
      } 
     } 

     // Finalizing 
     isPlaying = false; 
     buffer.Stop(); 
     stateControlARE.Set(); 

writeToBuffer簡單令狀通過this.buffer.Write(wp, data, LockFlag.None);並且關注大約bufferSizechunkSizewp,其代表最後的寫作位置。我認爲這是對我的代碼很重要的一切。也許定義缺失,至少有另一種方法開始/停止=控制線程。

我已經發布了這段代碼,以防我在填充緩衝區時發生錯誤或者我的初始化錯誤。但是我猜測會出現這個問題,因爲C#字節碼的執行速度太慢或者類似的問題。但最終我的問題仍然是開放的:我的問題是如何減少延遲以及如何避免不應該出現的噪音?

+0

PC輸入往往很嘈雜,尤其是對於板載音頻硬件。音頻芯片通常與實際插孔相距一定距離,主板上的走線可以拾取各種噪音。這種噪音是不可避免的,因爲它是一個物理問題,而不是由軟件引起的。 –

+0

你所說的簡單白化不是我的問題。我的問題是有時會發生無聲的破解。當我在我的系統設置中使用「收聽設備」時,不會發生這種情況。 –

+0

你是怎麼定義「沉默」的? – sq33G

回答

1

我知道你的問題的原因和解決方法,但我不能在C#和.Net中實現它,所以我會解釋它,希望你能找到自己的方式。

音頻將由您的麥克風錄製。以指定的頻率(例如44100),然後以相同的採樣率(又是44100)在聲卡上播放,問題在於計算輸入設備中時間的晶體(例如麥克風)與晶體不同在聲卡中播放聲音。 也差別很小,它們不一樣(全世界沒有2個完全相同的水晶),所以過了一段時間後,你的播放程序就會出現間隙。

現在的解決方案是重樣的數據來匹配輸出的採樣率,但我不知道該怎麼做,在C#和.NET

+0

我聽說過這個,但據我所知,這應該由聲卡驅動程序解決,或者至少由directx解決。沒有人會注意到一個缺少的樣本,如果它填滿零。順便說一下,自從這個問題在一年前被問及之後,我已經找到了答案,我會添加它。 –

0

很久以前,我想通了,這個問題是由於Thread.Sleep(1);與高CPU使用率相結合造成的。由於默認情況下窗口定時器分辨率爲15.6ms,因此此睡眠並不意味着睡眠1ms,而是直到達到下一個時鐘中斷爲止。 (欲瞭解更多信息,請參閱this paper)與高CPU使用率相結合,它可能會堆積到一塊或更多。

例如:如果我的大塊大小是40ms,這可能是大約46.8ms(3 * 15,6ms),這會導致蜱蟲。一種解決方案是將分辨率降至1毫秒。據

[DllImport("winmm.dll", EntryPoint="timeBeginPeriod", SetLastError=true)] 
private static extern uint timeBeginPeriod(uint uiPeriod); 

[DllImport("winmm.dll", EntryPoint="timeEndPeriod", SetLastError=true)] 
private static extern uint timeEndPeriod(uint uiPeriod); 

void routine() 
{ 
    Thead.Sleep(1); // May takes about 15,6ms or even longer. 
    timeBeginPeriod(1); // Should be set at the startup of the application. 
    Thead.Sleep(1); // May takes about 1, 2 or 3 ms depending on the CPU usage. 

    // ... time depending routines goes here ... 

    timeEndPeriod(1); // Should end at application shutdown. 
} 

,因爲我知道這應該由進行的DirectX已經做了:這可以通過這種方式來完成。但由於此設置是全局設置,應用程序或其他應用程序的其他部分可能會改變它。如果應用程序設置並撤消一次設置,則不會發生這種情況。但不知怎的,它似乎發生由任何髒編程的部分或其他正在運行的應用程序引起的。

還有一件事需要注意,就是您是否仍在使用directx緩衝區的正確位置,是否因任何原因跳過了一個塊。在這種情況下,需要重新同步。

相關問題