DirectSoundとC#を使用して、マイクからスピーカーにサウンド サンプルをストリーミングしようとしています。「マイクを聞く」に似ているはずですが、後でこれを別の用途に使用したいと考えています。私のアプローチをテストすることで、バックグラウンドで静かなくすぐり、クラッキング ノイズに気付きました。これは、バッファの書き込みと再生の間の遅延と関係があると思います。これは、チャンクを書き込むための待ち時間よりも大きくなければなりません。
録音と再生の間の遅延を 50ms 未満に設定した場合。ほとんどの場合は機能しますが、非常に大きなクラッキング ノイズが発生することがあります。そのため、少なくとも 50 ミリ秒の遅延を決定しました。これは私にとっては問題ありませんが、システムの「デバイスをリッスンする」の遅延ははるかに短いようです。約 15 ~ 30 ミリ秒で、ほとんど目立たないと思います。50 ミリ秒で、少なくとも少しのリバーブ効果が得られます。
以下に、私のマイクコードを (部分的に) 示します。 初期化は次のように行われます。
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();
再生バッファの充填と再生バッファの開始/停止は、別のスレッドによって行われます。
// 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
は、最後の書き込み位置を表すandをthis.buffer.Write(wp, data, LockFlag.None);
考慮して、エンキューされたチャンクをバッファーに書き込むだけです。これが私のコードで重要なことのすべてだと思います。定義が欠落している可能性があり、少なくともスレッドを開始/停止=制御する別の方法があります。bufferSize
chunkSize
wp
バッファーの入力を間違えた場合や初期化が間違っている場合に備えて、このコードを投稿しました。しかし、C# バイトコードの実行が遅すぎるなどの理由で、この問題が発生すると思います。しかし、最終的に私の質問は未解決です。私の質問は、レイテンシーを減らす方法と、存在してはならないノイズを回避する方法です。