LINUX.ORG.RU

Кто-то сейчас работает с callback-driven аудио?

 , , ,


0

2

Приветик!

Я тут поковырялся в ALSA, и пришёл к печальному выводу, что ихнее API тот еще кал, а как его использовать знает ограниченное кол-во лиц, на слова которых где-то в дебрях ссылаются многие гуглимые источники.
И/или порог вхождения там слишком высок, что понять всё волшебство и красоту мне не по силам.

Короче, решил взять какое-то кроссплатформенное API. Остановился на portaudio. Там сразу рекомендуют для non-glitchy playback/capture использовать callback. Неплохо. Пусть будет callback. Тем более, судя по современным тенденциям, это более удобный и безглючный способ передавать данные в/из DSP (DAC/ADC).

У меня пара вопросов:

1) В какой момент времени выполняется callback? Документация гласит, что «как только аудио API понадобились новые данные». Когда этот момент наступает? Когда все данные (читай - сэмплы) уже проиграны, и брать новые неоткуда? Но ведь в таком случае времени на «предоставление» этих данных и/или генерацию новых в callback-функции крайне мало, и каждая миллисекунда грозит «разрыву» в проигрывавнии.

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

2) Что случается, если времени будет недостаточно? Например, из-за объемности выполняемого кода (сложности алгоримта?), недостаточному кол-ву процессорного времени (CPU жрут другие процессы, после вызова callback-а тело функции не выполняется, а время отдаётся другим процессам), отсутствию/недостаче данных для выдачи (read() из файла не вернул достаточно байт или вообще не вернул управление и/или аналогичная ситуация с сокетами)? Можно ли просто «дропнуть» недостающую часть пустотой?

3) callback вызывается в отдельном треде?

Буду рад если поделитесь опытом. Буду неистово и искренне рад комментариям waker, i-rinat.

З.Ы. pulseaudio API не предлагать - отличие от portaudio там нет кроссплатформенности

★★★★★

1) В какой момент времени выполняется callback? Документация гласит, что «как только аудио API понадобились новые данные». Когда этот момент наступает? Когда все данные (читай - сэмплы) уже проиграны, и брать новые неоткуда? Но ведь в таком случае времени на «предоставление» этих данных и/или генерацию новых в callback-функции крайне мало, и каждая миллисекунда грозит «разрыву» в проигрывавнии.

типично, есть несколько (2+) буферов, которые играют по очереди. callback вызывается как только в очереди есть пустой (свободный / уже проигранный) буфер.

2) Что случается, если времени будет недостаточно? Например, из-за объемности выполняемого кода (сложности алгоримта?), недостаточному кол-ву процессорного времени (CPU жрут другие процессы, после вызова callback-а тело функции не выполняется, а время отдаётся другим процессам), отсутствию/недостаче данных для выдачи (read() из файла не вернул достаточно байт или вообще не вернул управление и/или аналогичная ситуация с сокетами)? Можно ли просто «дропнуть» недостающую часть пустотой?

да, заполнять нулями лучше всего (IMO).

3) callback вызывается в отдельном треде?

отдельном относительно чего? главного? да. однозначно отдельном.

waker ★★★★★
()
Последнее исправление: waker (всего исправлений: 1)

p.s. если что, я отвечал в общем, на основании работы с десятком разных audio api. конкретно с portaudio дела не имел.

waker ★★★★★
()
Ответ на: комментарий от waker

отдельном относительно чего? главного? да. однозначно отдельном.

Это просто замечательно :)

да, заполнять нулями лучше всего (IMO).

Да, я тоже так думал, что лучше проиграть имеющиеся семплы и дропнуть остальные, нежели не проиграть ничего. Но что случается, если функция выполняется дольше обычного? Например, если я вытаскиваю данные из файла или сокета, и блокирующий вызов выполняется дольше обычного?

Я также читал, что внутри callback-функции не стоит выполнять системных вызовов. Но как тогда выполнять синхронизацию? Допустим, если я хочу захватывать сигнал с входа и сразу его воспроизводить.

p.s. если что, я отвечал в общем, на основании работы с десятком разных audio api. конкретно с portaudio дела не имел.

А если не секрет, с какими работал? Я выбрал portaudio из-за того, что в будущем надобно будет иметь бинарники под венду, не хотелось бы лишний раз иметь с ней дело как разработчику.

anonymous
()

В какой момент времени выполняется callback? Документация гласит, что «как только аудио API понадобились новые данные». Когда этот момент наступает? Когда все данные (читай - сэмплы) уже проиграны, и брать новые неоткуда?

Вообще говоря, тебя не особо должно волновать, когда и как это происходит. Тебя ограждают от деталей реализации интерфейсом с колбеком.

В ALSA событие генерируется каждый раз, как заканчивается период. Если работаешь с hw, то событие генерируется аудиокартой. Если работаешь с dmix, событие генерируется таймером на CPU. Поэтому, например, для hw использовать два периода на буфер это нормально, а для dmix приводит к заиканиям. Там нужно три или четыре.

В PulseAudio «событие» генерируется самим PulseAudio. Он пытается угадать соотношение скоростей таймера на CPU и на аудиокарте, и исходя из оценки потраченного тобой времени в колбеке угадать, через какое время позвать тебя снова. Это уменьшает число прерываний CPU.

Что случается, если времени будет недостаточно?

У аудиокарты кончатся данные для проигрывания.

i-rinat ★★★★★
()

Мне недавно приходилось копаться и с portaudio и пытался aplay/arecord (из состава ALSA) перепилить под свои нужды - первое оказалось полурабочим шлаком, который на чуть нестандратном железе стало падать без причин, ну а ALSA API годится удобрять поля...

I-Love-Microsoft ★★★★★
()
Ответ на: комментарий от i-rinat

Вообще говоря, тебя не особо должно волновать, когда и как это происходит.

Меня это волнует по той причине, что хотелось бы знать сколько у меня времени внутри коллбека. Если у меня время почти отсутствует (т.е. данные нужны вот прям сейчас), то всё, что я могу сделать внутри коллбека, это дёрнуть какой-нибудь memcpy(apis_buffer, my_own_buffer, size), и тогда мне нужно будет подготавливать данные внутри my_own_buffer заранее.

В ALSA событие генерируется каждый раз, как заканчивается период.

Какой событие? Какой именно период? ALSA получает буффер на проигрывание, отправляет их на DSP, DSP их начинает проигрывать. Я так понимаю начиная с этой точки времени ALSA «генерирует событие» (что бы это не значило).

Если работаешь с hw, то событие генерируется аудиокартой. Если работаешь с dmix, событие генерируется таймером на CPU. Поэтому, например, для hw использовать два периода на буфер это нормально, а для dmix приводит к заиканиям. Там нужно три или четыре.

Вот тут не понял. Можно детаельнее?

В PulseAudio «событие» генерируется самим PulseAudio.

Логично. Он же в теории абстрагирует разработчика, ему виднее.

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

вау. Прикольно

У аудиокарты кончатся данные для проигрывания.

...а когда мой коллбек закончит работу - они снова появятся и начнут работать как ни в чём не бывало, верно?

anonymous
()
Ответ на: комментарий от I-Love-Microsoft

Мне недавно приходилось копаться и с portaudio и пытался aplay/arecord (из состава ALSA) перепилить под свои нужды - первое оказалось полурабочим шлаком, который на чуть нестандратном железе стало падать без причин, ну а ALSA API годится удобрять поля...

Ну хз, audacity работает вполне неплохо и на онтопике и на оффтопике. Правда, бывает, что на онтопике через минут 20 работы audacity тупо виснет без всякой причины, но подозреваю libav либы...

Так на чём ты остановился в итоге?

anonymous
()

Я тут поковырялся в ALSA, и пришёл к печальному выводу, что ихнее API тот еще кал, а как его использовать знает ограниченное кол-во лиц, на слова которых где-то в дебрях ссылаются многие гуглимые источники. И/или порог вхождения там слишком высок, что понять всё волшебство и красоту мне не по силам.

не знаю, мне всё просто и понятно показалось, когда я его разбирал

Harald ★★★★★
()
Ответ на: комментарий от Harald

Не, ну я знаю что ты на словах жёсткий.

Но вообще, когда я разбирал тоже поначалу казалось всё просто и понятно. А когда начал для теста что-то писать самому и/или использовать примеры из интернета - реалии оказались не такими радужными: без напильника и критических правок не обходилось, да работало далеко не гладко.

anonymous
()

Ах да, еще есть некий libao. Его кто-то палочкой тыкал?

anonymous
()
Ответ на: комментарий от anonymous

вот что конкретно тебе там сложным показалось

Harald ★★★★★
()
Ответ на: комментарий от anonymous

сколько у меня времени внутри коллбека

Лучше считать, что времени у тебя там абсолютный минимум. В случае с ALSA это время можно оценить. Там есть вызов, который даст тебе количество данных в буфере. Делишь на скорость потребления данных, получаешь время. В PulseAudio тоже вроде есть функции для оценки, не вникал.

Вот тут не понял. Можно детаельнее?

https://www.alsa-project.org/main/index.php/FramesPeriods

и начнут работать как ни в чём не бывало, верно?

В ALSA нужно переинициализировать адаптер, без этого дальше работать не будет. В более высокоуровневом API это могут сделать за тебя.

Смотри на API. Там должно быть написано, что и когда делать.

i-rinat ★★★★★
()
Ответ на: комментарий от anonymous

если системный из репозитория то пофиг, если самому собирать и с собой тащить, можно всё лишнее, не относящееся к аудио, выкинуть

Harald ★★★★★
()
Ответ на: комментарий от I-Love-Microsoft

Мне недавно приходилось копаться и с portaudio и пытался aplay/arecord (из состава ALSA) перепилить под свои нужды - первое оказалось полурабочим шлаком, который на чуть нестандратном железе стало падать без причин, ну а ALSA API годится удобрять поля...

Подписываюсь под каждым словом.

Waterlaz ★★★★★
()
Ответ на: комментарий от Harald

не знаю, мне всё просто и понятно показалось, когда я его разбирал

Там чуть что куча подводных камней. У всяких там pulseaudio немало проблем было с реализацией API ALSA.

Waterlaz ★★★★★
()
Ответ на: комментарий от anonymous

Да, я тоже так думал, что лучше проиграть имеющиеся семплы и дропнуть остальные, нежели не проиграть ничего.

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

Но что случается, если функция выполняется дольше обычного? Например, если я вытаскиваю данные из файла или сокета, и блокирующий вызов выполняется дольше обычного?

это следует делать в отдельном потоке. аудио-поток нельзя блокировать.

Я также читал, что внутри callback-функции не стоит выполнять системных вызовов. Но как тогда выполнять синхронизацию? Допустим, если я хочу захватывать сигнал с входа и сразу его воспроизводить.

в отдельном потоке, захватываешь с максимально возможной скоростью, и сохраняешь в памяти.

в аудио-потоке, берешь сохраненное первым потоком, и пишешь в аудио-буфер.

А если не секрет, с какими работал?

alsa, pulse, oss, jack, coreaudio, android (AudioTrack), multistream, xaudio2, directsound, miles, win32 waveout, openal, sdl, fmod - то что сходу вспомнилось

waker ★★★★★
()

«как только аудио API понадобились новые данные»

Пять раз прочитал, так и не могу распарсить. Вроде как дефис нужен в «аудио-API», но все равно бред получается.

anonymous
()
Ответ на: комментарий от waker

Премного благодарю еще раз за развёрнутый ответ!

Поразмыслил, пришёл к выводу что имеет смысл юзнуть ring buffer дабы не велосипедировать некую фиктивную синхроинзацию.
Плюс читал что серьезные дядьки в коде своих DAW так делают.

я на верном пути?

reprimand ★★★★★
() автор топика
Ответ на: комментарий от mittorn

Будут заикания при тормозах, но они обычно некритичны

при каких именно тормозах? когда не хватает входящих данных, как я понимаю. Ибо тормоза аудио подсистемы если правильно её использовать относительно редки́.

reprimand ★★★★★
() автор топика
Ответ на: комментарий от mittorn

Да, я именно это и имел ввиду.

Ну вообще да, в этом-то и суть - если источник не успевает за реальным временем, то «дропов» не избежать.

reprimand ★★★★★
() автор топика
Ответ на: комментарий от reprimand

заикания - наиболее безобидные дропы. Если заполнять нулями - будет провал громкости, он больше заметен.

mittorn ★★★★★
()
Ответ на: комментарий от waker

для случая «1 поток в 1 формате» — путь нормальный.

Я так понимаю ввиду, что имеется ввиду, что я в 1 потоке буду работать лишь с каким-нибудь pcm16le signed.

Но я ринг буффер предполагал использовать из-за нестабильности источника... Сеть, к примеру.

reprimand ★★★★★
() автор топика
Ответ на: комментарий от reprimand

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

вместо этого делается очередь буферов, в каждом буфере кусок PCM + waveformat. а уже эти буферы организуются в циклическую очередь.

waker ★★★★★
()
Ответ на: комментарий от waker

Да, я это и имел ввиду. Нет, у меня будет 1 формат, 1 источник

reprimand ★★★★★
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.