Версия для печати
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум на Исходниках.RU > Программирование звука > Проблемы двойной буфферизации и WaveOut


Автор: junkers1989 13.01.15, 16:14
Доброго всем времени суток!
Пишу прогу для работы с файлами ImRe формата, на начальном этапе столкнулся с тем что при подаче данных в WaveOutWrite звук "заикается" несмотря на использование двойной буфферизации. Делаю следующе:

1. Получаю контекст на устройство так:

<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    procedure Tform1.Initsound(band:integer);
    var i:integer;
     
    begin
     
    // вормат выводных данных
     with header do begin
       wFormatTag := WAVE_FORMAT_PCM;
       nChannels := 1;
       nSamplesPerSec := band*2;
       wBitsPerSample := 16;
       nBlockAlign := nChannels * (wBitsPerSample div 8);
       nAvgBytesPerSec := nSamplesPerSec * nBlockAlign;
       cbSize := 0;
     end;
     
    // получаю контекст устройства и говорю ему передавать мессагу окну
     if WaveOutOpen(addr(waveOut), WAVE_MAPPER, @header,Handle, 0,CALLBACK_Window)<>0 then
      begin
      showmessage('Ошибка открытия звукового устройства!');
      exit;
      end;
     
     
    // создаю два буфера под вывод
    for i:=0 to 1 do begin
    pWaveBufHan[i] := GlobalAlloc(GMEM_MOVEABLE and GMEM_SHARE, soundbuflength*4);
    pWaveBuffer[i]:=GlobalLock(pWaveBufHan[i]);
     
      with waveoutHdr[i] do begin
       lpData := pwavebuffer[i];
       dwBufferLength :=soundbuflength*2;
        dwUser := 0;
        dwFlags := 0;
       dwLoops := 0;
     end;
     
    // инициирую заголовки
    if WaveOutPrepareHeader(waveOut, @waveoutHdr[i], sizeof(waveoutHdr))<>0 then
       begin
      showmessage('Ошибка инициализации звукового устройства!');
      exit;
      end;
    nspwbZero(pwaveBuffer[i],soundbuflength); // обнуляю буферы
    end;
     
    Psigtosound:=nspvMalloc(soundbuflength); // буфер понадобится для считывания квадратур из файла
     
     
    //закидываю первый буфер в устройство чтобы начать получать MM_WOM_DONE
    waveOutWrite(waveOut, @waveoutHdr[1], sizeof(waveoutHdr));
     bufnum:=0;
    end;


2. Обрабатываю мессаги MM_WOM_DONE через procedure SoundComplite (var MSG:Tmessage); message mm_wom_done;
(пробовал hook-ом; - проблемы те же, да в принципе и смысл тот же)

<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    // при получении MM_WOM_DONE
    procedure  Tform1.SoundComplite(var msg: Tmessage);
    var i:integer;
    begin
     //скидываю ранее сгенерированный буфер в устройство
     waveOutWrite(waveOut, @waveoutHdr[bufnum], sizeof(waveoutHdr));
     //переключаю счетчик на следующий буфер
     bufnum:=bufnum xor 1;
     
    // закидываю данные в новый буфер
    globalpos:=globalpos+soundbuflength  ; //позиция отсчета в файле
    if globalpos>= globalmax then // нет ли конца файла
      begin
      globalpos:=globalmax;
      play:=false;     // торможу воспроизведение
      form1.closesound;// и закрываю устройство
      end;
    afile.Position:=globalpos*4+110; // введено для расчета нужной позиции в filestream 109- величина заголовка 4-размер квадратурного отсчета Re:short Im:short
     
    Afile.Read(Psigtosound^,soundbuflength*4);
     
    // пока что делаю только выделение действительной части и пытаюсь ее воспроизводить
    // далее планируется здесь сделать небольшуюу обработку
    nspvbReal(Psigtosound,pwaveBuffer[bufnum],soundbuflength) ;
    // pwaveBuffer[bufnum] - готов при следующем вызове MM_WOM_DONE он будет подан в устройство
    end;

Сама проблема заключается в том что звучание кратковременно прерываетя при смене буфера хотя двойная буферизация должна это устранять.

Что характерно усли убрать строчку
bufnum:=bufnum xor 1;
то есть работать только с одним буфером - "заикание" (на слух) точно такое же как и с двумя буферами.

раньше писал похожие вещи (делал нечто похожее на сетевое радио) - доооооолго мучался и сам не понял как но такие же заикания устранил, а вот как - хоть убей не помню :wall:

Заранее благодарен если у кого найдется время посмотреть.

Добавлено
Небольшое уточнение - использовал буфферы разного размера от 1 до 24 Кб заикания такие же только происходят реже с увеличением размера буфера

Автор: Pavia 13.01.15, 19:10
Очевидных ошибок невидно. Разве что в nspvbReal происходит разрыв фазы.
Я бы число буферов увеличил бы. Запас на всякий пожарный случай.

Автор: Prince 13.01.15, 19:47
имхо, вы где-то накосячили с размерами массивов.
Выделяете в памяти soundbuflength*4 байт
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    pWaveBufHan[i] := GlobalAlloc(GMEM_MOVEABLE and GMEM_SHARE, soundbuflength*4);

а драйверу отдаёте половину
<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
      with waveoutHdr[i] do begin
       lpData := pwavebuffer[i];
       dwBufferLength :=soundbuflength*2;

Также и при заполнении буфера, размеры буфера и границы фактических данных в нём могут не совпадать, проверяйте.

<{CODE_COLLAPSE_OFF}><{CODE_WRAP_OFF}>
    procedure  Tform1.SoundComplite(var msg: Tmessage);
    var i:integer;
    begin
     //скидываю ранее сгенерированный буфер в устройство
     waveOutWrite(waveOut, @waveoutHdr[bufnum], sizeof(waveoutHdr));
     //переключаю счетчик на следующий буфер
     bufnum:=bufnum xor 1;

Это ошибочный подход.
MM_WOM_DONE собщает адрес заголовка, с ним и следует работать дальше.
Если вы ставите в очередь несколько буферов(2/3/40/100), неважно, на каждый MM_WOM_DONE вы получаете от драйвера один заголовок буфера, костылей в виде левых индексов не требуется.
Цитата
от 1 до 24 Кб

Я бы рекомендовал "безопасную" (с точки зрения возможных затыков воспроизведения при недостаточной производительности системы) длительность буфера в 100 и более мс.
Цитата
CALLBACK_Window

Нехорошо для практической реализации, но для примера некритично.

Автор: Pavia 13.01.15, 21:08
Цитата Prince @
Это ошибочный подход.
MM_WOM_DONE собщает адрес заголовка, с ним и следует работать дальше.

А почему МС так не делает?
http://msdn.microsoft.com/ru-ru/library/wi...3(v=vs.85).aspx
Правда разработчики VCL с вами согласны.

Цитата Prince @
Я бы рекомендовал "безопасную" (с точки зрения возможных затыков воспроизведения при недостаточной производительности системы) длительность буфера в 100 и более мс.

А откуда число 100 мс? Откуда дровишки? Хотелось бы 20 мс.

Автор: Prince 13.01.15, 21:17
Цитата
А почему МС так не делает?

Потому что то пример, потому что кто-то в МС был не в курсе, что в ихних месседжах передаётся? :-?
Цитата
А откуда число 100 мс? Откуда дровишки? Хотелось бы 20 мс.

По опыту, на процах от 500 МГц до современных. Хотелось бы, но когда оно реально надо, т.е., такой мелкий буфер.

Автор: Pavia 13.01.15, 21:23
Цитата Prince @
Хотелось бы, когда оно реально надо, т.е., такой мелкий буфер.

RFC хочет 20 мс. Вот и спросил реально на waveOutWrite получить? Или куда копать?

Автор: Prince 13.01.15, 22:04
:scratch:
На новых машинах и новых осях нужно проверять.
На XP, со скрипом, абстрактный 20 мс буфер получить можно.
Цитата
Или куда копать?

А суть задачи?

По большей части интуитивно, всё, что требует реакции на интервалах, сравнимых с квантом времени, отведенным процессу, или меньше, считаю не надёжным(на уровне приложения), независимо от интерфейса. Драйвер вполне успевает на уровне ядра переключать 2 буфера без разрывов аудиопотока, но приложение ещё должно и успевать их заполнять(обрабатывать) своевременно.

Автор: junkers1989 14.01.15, 04:51
Цитата Pavia @
Я бы число буферов увеличил бы. Запас на всякий пожарный случай.

Пробовал - ситуация та же и как писал даже если оставить только один буфер ничего не меняется :-?

Добавлено
Цитата Prince @
имхо, вы где-то накосячили с размерами массивов.

Да есть такое должно быть не *4 а *2, но больше - не меньше проблема точно не в этом.

Добавлено
Цитата Prince @
Также и при заполнении буфера, размеры буфера и границы фактических данных в нём могут не совпадать, проверяйте.

Это ошибочный подход.
MM_WOM_DONE собщает адрес заголовка, с ним и следует работать дальше.

А в каком параметре мессаги или шапки должно храниться количество воспроизведенных реально сэмплов - помню с микрофоном такая же беда была что устройство не успевало заполнить весь буфер и скидывало то что есть.

Добавлено
Цитата Prince @
Нехорошо для практической реализации, но для примера некритично

Использование программы планируется на высокопроизводительных системах поэтому очень большие буферы делать смысла нет, чтобы не создавать пользователю ощущуние "задумчивости" компа.

А какой подход будет правильнее в практике вместо callback-window. Поток писать не хочется синхронизировать не удобно убудет с учётом обработки данных

Добавлено
Цитата Prince @
Драйвер вполне успевает на уровне ядра переключать 2 буфера без разрывов аудиопотока, но приложение ещё должно и успевать их заполнять(обрабатывать) своевременно.

На примере использовал частоду вывода 8000 Гц и буфер 4К то есть у приложения почти пол-секунды на заполнение буфера, при отсутствии сложных вычислений (а их там нет) даже слабенькая система должна успевать.

Я так понимаю копать действительно нужно в направлении размера фактически воспроизведенных данных на момент mm_wom_done, вот только где их взять?

Автор: Prince 14.01.15, 05:59
Цитата
проблема точно не в этом

Запишите звук,который выдаёт программа, и в звукоредакторе увидите, в каком месте разрывы, а потом и ищите, в чём причина(где ошибка). Но она точно не в "двойной буферизации".
Цитата
Я так понимаю копать действительно нужно в направлении размера фактически воспроизведенных данных на момент mm_wom_done

В направлении поиска ошибок в программе. :yes:
Цитата
А в каком параметре мессаги или шапки должно храниться количество воспроизведенных реально сэмплов - помню с микрофоном такая же беда была что устройство не успевало заполнить весь буфер и скидывало то что есть.

Реально воспроизведенных семплов - такого понятия, в контексте обработки mm_wom_done, нет. Сколько БАЙТ вы указали в dwBufferLength(это количество должно быть кратным размеру nBlockAlign структуры waveformatex), столько буфер и отыграет, до того, как драйвер его вернёт приложению. Меньше (байт) он может воспроизвести в том случае, если вы выполнили Reset устройства воспроизведения, в этом случае все буферы, в том числе и "незавершенные", выбрасываются из очереди. Но такое действие означает, что вам, в общем, уже неважно, кто там сколько воспроизвёл, вы хотите прекратить воспроизведение.
При записи, да, есть возможность узнать, сколько реально было байт записано, но практически, эта возможность востребована, когда буфер выбрасывается из очереди "нештатно". В случае нормального последовательного обмена буферов, сколько вы запросили, столько и получаете на выходе.
Цитата
Использование программы планируется на высокопроизводительных системах поэтому очень большие буферы делать смысла нет, чтобы не создавать пользователю ощущуние "задумчивости" компа.

Пользователь ничего не ощутит. Нажал кнопку - началось воспроизведение, ещё раз - прекратилось. Буфер может быть хоть 1 секунда. Или я вас не понял.
Цитата
Поток писать не хочется синхронизировать не удобно убудет с учётом обработки данных

Поток или подпрограмма (она самой виндой выполняется в контексте отдельного потока, если не ошбаюсь). Неудобно синхронизировать -> менять структуру программы и способы обработки данных, чтобы было удобно.
Если вы гонитесь за минимальными задержками, то зачем к издержкам на обработку аудио добавлять издержки и задержки GUI и пр. GUI в какой-то момент может подвесить всю вашу обработку аудио, и разрывы в звуке будут следствием активных действий пользователя мышой или тяжёлого интерфейса, требующего постоянной прорисовки.

Автор: junkers1989 14.01.15, 07:47
Цитата Prince @
Запишите звук,который выдаёт программа, и в звукоредакторе увидите, в каком месте разрывы, а потом и ищите, в чём причина(где ошибка). Но она точно не в "двойной буферизации".

Именно так сейчас и делаю - прямо с головы выдернули :)

Добавлено
Цитата Prince @
Нажал кнопку - началось воспроизведение, ещё раз - прекратилось. Буфер может быть хоть 1 секунда.

Не совсем так- перед воспроизведением исходные отсчеты звука будут обрабатываться в зависимости от параметров указанных пользователем. В случае буфера 1с реакция в динамиках на изменение параметров (в худшем случае) тоже будет около секунды- для пользователя это будет казаться лагами.

Спасибо за помощь. Как будет результат отпишу что за ошибка была

Автор: Prince 14.01.15, 08:15
Цитата
В случае буфера 1с реакция в динамиках на изменение параметров (в худшем случае) тоже будет около секунды- для пользователя это будет казаться лагами.

А, в этом смысле. Но вы попробуйте 100 мс /50 мс. Мне кажется, для пользователя достаточно комфортно. :-?

Автор: Prince 15.01.15, 06:47
Цитата Pavia @
Цитата Prince @
Хотелось бы, когда оно реально надо, т.е., такой мелкий буфер.

RFC хочет 20 мс. Вот и спросил реально на waveOutWrite получить? Или куда копать?

А всё-таки, что за задача? Спрашиваю для "поразмышлять над поисками возможных решений". Возможно, waveOutWrite вполне справится, если поэкспериментировать.

Автор: Pavia 15.01.15, 07:52
Prince
Задача. Демонстрация принципов работы со звуком в потоковом режиме. На примере голосового чата.
Способ синхронизации на основе TDM(считай жёсткое реальное время). Группа протоколов связи стандартная сцепка SIP+RTP. Сжатие стандартное, насколько понял rfc рекомендует делать запись фреймами по 20 мс.

Автор: Prince 15.01.15, 21:41
Рекомендованые 20 мс - относительно безопасный размер фрейма, на случай пропадания пакета в системе передачи. Если фрейм потеряется, разборчивость речи не особо пострадает. Под этот размер фрейма заточены и кодеки, если не ошибаюсь.
Задержка в канале будет определяться временем накопления фрейма(20мс) на передающей стороне, задержкой кодирования/декодирования фрейма, задержкой передачи пакетов по сети(от пары мс до упора), размером приёмного буфера(джиттер-буфера) на приёмной стороне.
Регламентирована ли эта суммарная задержка, и в каких рекомендациях - не в курсе.

Вполне допустимо на передающей стороне использовать, допустим, 10 буферов по 20 мс. Неразрывность потока аудио на передающей стороне будет обеспечиваться количеством буферов. На приёмной тоже, но там процесс будет сложнее, за счёт неравномерной задержки при передаче пакетов по сети, нужно думать. А, и частоты дискретизации на передающей/приёмной стороне не совпадают, придётся как-то и с этим разбираться, при разработке общего механизма синхронизации.
В общем, wavein/waveout с 20 мс буферами можно бы попробовать, я бы сказал. :scratch:

Автор: junkers1989 19.01.15, 13:22
Доэкспериментировался - пришел к выводу что всегда достаточно двух буферов, если программа не успевает с двумя то делать их больше нет смысла - т.к. малые задержки на каждом буфере будут накапливаться до тех пока суммарная задержка не станет равна длине буфера и в этот момент в динамиках будет щелчек.

Цитата Prince @
Поток или подпрограмма (она самой виндой выполняется в контексте отдельного потока, если не ошбаюсь). Неудобно синхронизировать -> менять структуру программы и способы обработки данных, чтобы было удобно.



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

Добавлено
Всем спасибо за помощь - проблема снята.

Автор: Prince 19.01.15, 14:02
Цитата
Доэкспериментировался - пришел к выводу что всегда достаточно двух буферов, если программа не успевает с двумя то делать их больше нет смысла - т.к. малые задержки на каждом буфере будут накапливаться до тех пока суммарная задержка не станет равна длине буфера и в этот момент в динамиках будет щелчек.

Если буферов больше, чем 2(и они относительно "мелкие"), поток при получении управления, обрабатывает(может обработать) несколько мессаджей(буферов), которые пришли от звуковой подсистемы, пока поток "спал". Притом, что драйвер продолжает вовремя переключать относительно мелкие буферы. А вот если буферов всего 2, тогда будут разрывы, так как один буфер, допустим, обрабатывается приложением, а у драйвера в момент заполнения второго буфера не оказывается "резерва" для дальнейшей работы.

Можно провести такой ээксперимент.
Со входа звуковушки записываем в файл чистый синус. Запись при помощи wavein функций.
Т.е., ставим сразу в очередь 100 буферов по 50 мс, дожидаемся, пока все они заполнятся и сбрасываем в файл. В редакторе смотрим, есть ли разрывы/глюки в записанном синусе. Уменьшаем длительность буферов и повторяем. И так до тех пор, пока не появятся разрывы. Одновременно смотрим на загрузку процессора, для наглядности.

Автор: junkers1989 19.01.15, 14:21
Действительной если нагрузка в системе очень неравномерна, то смысл есть, а для "чистой" работы программы (когда другие процессы не грузят систему) - она должна справляться с двумя буферами, если нет то надо оптимизировать код.

Автор: Prince 19.01.15, 14:38
Цитата
Действительной если нагрузка в системе очень неравномерна,

Тут дело не столько в неравномерности, сколько в нереалтаймовости винды. Пока поток "спит", он ничего не может поделать с мессагами, приходящими в это время. Зато когда просыпается, может успеть за отведённое время разгрести "кучу бумаг", накопившихся, пока он спал.

Добавлено
Цитата
она должна справляться с двумя буферами

При достаточном размере буфера да. Почему я и рекомендовал 100 мс. Но потоковая модель работы с буферами не подразумевает в обязательном порядке, что в очереди "зациклены" n буферов одинакового размера. После обработки буфера он может уничтожаться, а в очередь по мере поступления могут добавляться новые(с выделением памяти, prepare), например прилетевшие по сети, причём вы заранее, допустим, не знаете, какой у них размер.

Автор: junkers1989 19.01.15, 17:09
Согласен случаи могут быть разными, отсюда и подходы разные.

Автор: Vituskosoy 19.01.15, 22:53
...для статистики, в свей программе использую 20мс буферы (8кHz 16bit stereo) в количестве 50шт (1сек) - waveOut/In отрабатывают замечательно.

Автор: Serafim 22.04.15, 16:01
Цитата junkers1989 @
Доэкспериментировался - пришел к выводу что всегда достаточно двух буферов, если программа не успевает с двумя то делать их больше нет смысла - т.к. малые задержки на каждом буфере будут накапливаться до тех пока суммарная задержка не станет равна длине буфера и в этот момент в динамиках будет щелчек.

Прошу прощения за ярый оффтоп, но у меня примерно такая-же ситуация. С той лишь разницей, что "щелчок" возникает при остановке воспроизведения аудио. Методом тыка удалось убрать эту проблему, но костыльным образом -- выводить программно звук в ноль, а потом уже останавливать. Мне стоит создать отдельную тему или эту ситуацию можно одним словом описать\решить?

Добавлено
Хотя наверное это уже некрофилизм темы, надеюсь простите :rolleyes:

Автор: Prince 22.04.15, 17:31
Цитата
Прошу прощения за ярый оффтоп, но у меня примерно такая-же ситуация. С той лишь разницей, что "щелчок" возникает при остановке воспроизведения аудио. Методом тыка удалось убрать эту проблему, но костыльным образом -- выводить программно звук в ноль, а потом уже останавливать. Мне стоит создать отдельную тему или эту ситуацию можно одним словом описать\решить?

Такая же? А какая была у ТС?
Имхо, стоит создать новую тему.
Чтобы ответить, нужны подробности, с частью кода и пояснениями.
А "щёлчок" можно записать и приаттачить. Может, никакого щелчка и нет.

Автор: Prince 23.04.15, 14:47
Хоть на словах, как происходит "остановка воспроизведения"?

Автор: Serafim 23.04.15, 15:48
Меня интересуют общие причины из-за чего такое может случаться. А дальше я сам додумаюсь как поправить. Именно по этому я решил не создавать новую тему. Одна из причин - неполный буфер, но не в моём случае.


Цитата Prince @
Хоть на словах, как происходит "остановка воспроизведения"?

Общими словами сложно описать. Закрывается файловый стрим и очищается буфер.
Отсюда: https://github.com/jphp-compiler/jphp-audio...oTrack.java#L74
Идёт сюда: https://github.com/jphp-compiler/jphp-audio...Player.java#L76
Затем сюда: https://github.com/jphp-compiler/jphp-audio...Output.java#L70

Но я не думаю что это вообще хоть как-то поможет, скорее наоборот.

Добавлено
Цитата Serafim @
Одна из причин - неполный буфер, но не в моём случае.

Хотя вполне возможно, что буфер закрывается раньше, нежели вывод в Output и из-за этого туда влетает пустой фрагмент, который и фигачит таким образом. Но это скорее теория, хз

Автор: Prince 23.04.15, 16:01
Цитата
Меня интересуют общие причины из-за чего такое может случаться.

Сначала хотя бы понять, что такое "такое". Можете приаттачить щелчок(а лучше 2 щелчка, 3 щелчка... :) ), желательно вместе с "до щелчка" и "после щелчка".
Цитата
Но я не думаю что это вообще хоть как-то поможет, скорее наоборот.

Пожалуй. Ни разу не waveout.

Powered by Invision Power Board (https://www.invisionboard.com)
© Invision Power Services (https://www.invisionpower.com)