Как избежать тихого тиканья при передаче звука с микрофона на динамики с помощью DirectSound и C #? - PullRequest
0 голосов
/ 22 ноября 2011

Я пытаюсь передавать звуковые образцы с моего микрофона на мои колонки , используя DirectSound и C #, Это должно быть похоже на «прослушивание микрофона», но позже я хочу использовать это для чего-то другого. Протестировав мой подход, я заметил тихий тиклинг, взрывающиеся шумы на заднем плане. Я предполагаю, что это связано с задержкой между записью и воспроизведением буфера, которая должна быть больше, чем задержка при записи фрагментов.

Если установить задержку между записью и воспроизведением менее 50 мс. В основном это работает, но иногда я получаю действительно громкие трескучие шумы . Поэтому я решил с задержкой не менее 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 просто записывает в очередь блок в буфер на this.buffer.Write(wp, data, LockFlag.None); и заботится о bufferSize и chunkSize и wp, которые представляют последнюю позицию записи. Я думаю, что это все, что важно в моем коде. Возможно, определения отсутствуют, и, по крайней мере, есть другой метод, который запускает / останавливает = контролирует потоки.

Я разместил этот код на тот случай, если я допустил ошибку при заполнении буфера или неверная инициализация. Но я предполагаю, что эта проблема возникает из-за того, что выполнение байт-кода C # выполняется слишком медленно или что-то в этом роде. Но, в конце концов, мой вопрос все еще открыт: Мой вопрос заключается в том, как уменьшить задержку и как избежать помех, которых не должно быть?

Ответы [ 2 ]

1 голос
/ 08 августа 2012

Я знаю причину вашей проблемы и то, как вы можете ее решить, но я не могу реализовать ее в C # и .Net, поэтому я объясню это в надежде, что вы найдете свой путь.

Аудио будет записываться вашим микрофоном. с указанной частотой (например, 44100), а затем воспроизводится на звуковой карте с той же частотой дискретизации (снова 44100), проблема заключается в кристалле, который считает время на устройстве ввода (например, микрофон), не такой же, как кристалл, который Воспроизвести звук на звуковой карте. Кроме того, разница настолько мала, что они не одинаковы (во всем мире нет двух одинаковых кристаллов), поэтому через некоторое время в ваших программах воспроизведения будет пробел.

Теперь решение заключается в повторной выборке данных в соответствии с частотой дискретизации выходных данных, но я не знаю, как это сделать в C # и .Net

0 голосов
/ 08 августа 2012

Давным-давно я выяснил, что эта проблема вызвана Thread.Sleep(1); в сочетании с высокой загрузкой процессора. Из-за того, что разрешение окна таймера составляет 15,6 мс по умолчанию, этот спящий режим не означает спящий режим в течение 1 мс, а спящий режим до достижения следующего прерывания тактового сигнала. (Подробнее читайте , этот документ ). В сочетании с высокой загрузкой процессора он может составлять до длины фрагмента или даже больше.

Например: Если мой размер фрагмента равен 40 мс, это может быть около 46,8 мс (3 * 15,6 мс), и это вызывает тикклинг. Одним из решений для этого является установка разрешения до 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, если по какой-то причине вы пропускаете один блок. В этом случае требуется повторная синхронизация.

...