Воспроизведение и захват аудио данных

Общие практики выполнения пользовательских задач

Данная глава включает в себя:

Обслуживание PCM устройств
Открытие устройства
Конфигурирование устройства
Контроль конвертирования голосов в потоке
Подготовка субканалов
Закрытие субканалов
Воспроизведение аудио данных
Состояния воспроизведения
Отправка данных в субканал
Действия при остановке воспроизведения в субканале
Остановка воспроизведения
Синхронизация с субканалом
Захват аудио данных
Выбор источника данных для захвата
Состояния захвата
Получение данных из субканала
Действия при остановке захвата в субканале
Остановка захвата
Синхронизация с субканалом

Обслуживание PCM устройств

Программные процессы при воспроизведении и захвате во многом схожи. В данной статье рассматриваются общие принципы.

Открытие устройства

Первое, чем необходимо озаботиться – создать соединние (открыть) соответствующее PCM устройство воспроизведения или захвата. Вызовы API, позволяющие это выполнить:

snd_pcm_open()
Используется для открытия конкретного устройства по его целочисленным идентификаторам.
snd_pcm_open_preferred()
Используется для открытия предпочтительного устройства. Это позволяет сделать приложение более гибким и получить в ответ идентификаторы открытого устройства.

Оба вызова возвращают дескриптор PCM соединения, которое будет использоваться в последующих операциях API. Дескриптор во многом схож с дескриптором файлового потока и имеет тип указателя на структуру snd_pcm_t, являющуюся прозрачной для драйвера. Обе функции применимы как для каналов захвата, так и каналов воспроизведения, что определяется параметром mode. Он может принимать значения:

SND_PCM_OPEN_CAPTURE
SND_PCM_OPEN_PLAYBACK

Фрагмент кода из примера wave.c, демонстрирующий пример открытия устройства:

if ( card == -1 )
{
if ( (rtn = snd_pcm_open_preferred( &pcm_handle, &card, &dev, SND_PCM_OPEN_PLAYBACK )) < 0 )
return err( "device open" );
} else {
if ( (rtn = snd_pcm_open( &pcm_handle, card, dev, SND_PCM_OPEN_PLAYBACK )) < 0 )
return err( "device open" );
}

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

Конфигурирование устройства

Следующим шагом следует проинформировать устройство о формате принимаемых/передаваемых данных. Для этого заполняется структура snd_pcm_channel_params_t и вызывается функция snd_pcm_channel_params(), либо snd_pcm_plugin_params(). Разница между ними заключается в том, что последняя использует Плагины конвертирования данных. Если устройство не поддерживает запрошенный формат или все субканалы заняты, функция вернет ошибку.

Определить текущие возможности PCM устройства можно с помощью:

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


Note: Обе функции принимают в качестве аргумента указатель на структуру snd_pcm_channel_info_t. Перед вызовом следует установить поле channel стркутуры в значение SND_PCM_CHANNEL_CAPTURE или SND_PCM_CHANNEL_PLAYBACK. остальные поля будут заполнены при успешном завершении.

Несколько клиентов могут одновременно открыть PCM устройство с единственным субканалом, но лишь один из них может его настроить. После создания клиентом субканала, он не вернется в пул свободных ресурсов до того момента, пока последний клиент не закроет его дескриптор. Из-за этого перечень доступных ресурсов (и возможностей) PCM устройства динамически меняется в зависимости от перечня свободных субканалов. Важно отметить, что функции разных субканалов могут отличаться. Кроме того, настройка/создание субканала клиентом переводит его состояние из SND_PCM_STATUS_NOTREADY в SND_PCM_STATUS_READY.

Если вызов API завершается успешно, все указанные параметры будут гарантировано применены, кроме frag_size который носит лишь рекомендательный для оборудования характер. Оборудование может подстраивать размер фрагмента в зависимости от требуемой операции. Например, если оборудование не может работать с фрагментами, чей размер не кратен 64-килобайтам, но frag_size равен 60Кб, то драйвер скорее всего проигнорирует данное поле и установит регламентируемый устройством размер.

Другой аспект настройки затрагивает предпочтительный размер аппаратного буфера данных. Он также определяет задержку приложения при отправке/приеме данных от драйвера. Размер буфера задается как frag_size умноженный на max_frags. Учитывая вышесказанное для определения размера буфера нужно знать фактический размер фрагмента frag_size, установленный драйвером.

Сделать это можно, вызвав snd_pcm_channel_setup() или snd_pcm_plugin_setup(), в зависимости от того, использует ли приложение плагины конвертирования данных. Обе функции принимают указатель на структуру snd_pcm_channel_setup_t, которая будет заполнена информацией о фактической конфигурации канала, включая frag_size.

Контроль конвертирования голосов в потоке

Библиотека libasound поддерживает устройства с 8 и менее голосов в зависимости от того, что поддерживается оборудованием. Если число входящих и исходящих голосов отличается, функция snd_pcm_plugin_params() позволяет создать преобразователь голосов.

Поведение подобного преобразователя соответствует таблице:

Входящий поток Исходящий поток Логика преобразования
Моно Стерео Репликация канала 1 (левый) в канал 2 (правый)
Стерео Моно Удаление канала 2 (правый)
Моно 4-канальный Репликация канала 1 на все остальные каналы
Стерео 4-канальный Репликация каналов 1 (передний левый) в канал 3 (задний левый) и 2 (передний правый) в канал 4 (задний правый)

Для настройки преобразования и указания соответствующих src/dst каналов могут применяться следующие вызовы:

snd_pcm_plugin_get_voice_conversion()
Получение текущей структуры конвертирования голосов для канала.
snd_pcm_plugin_set_voice_conversion()
Установка текущей структуры конвертирования голосов для канала.

Параметры преобразования контролируются структурой snd_pcm_voice_conversion_t:

typedef struct snd_pcm_voice_conversion {
uint32_t app_voices;
uint32_t hw_voices;
uint32_t matrix[32];
} snd_pcm_voice_conversion_t

Поле matrix имеет формат двумерной матрицы 32x32 бита для определения способа конвертирования голосов. Строки представляют голоса в приложении (голос 0 первый), столбцы соответствуют аппаратным (низкий голос выровнен по LSB и увеличивается справа налево).

Пример: направление моно потока из приложения в устройство с 4-мя голосами. В этом случае массив

matrix[0] = 0x1; // 00000001

приводит к выводу звука только на первый аппаратный канал. Массив

matrix[0] = 0x9; // 00001001

приводит к выводу звука на первый и последний аппаратный каналы. Другой пример: вывод стерео потока из приложения в устройство с 6 каналами (5.1). В этом случае массив

matrix[0] = 0x1; // 00000001
matrix[1] = 0x2; // 00000010

приводит к выводу звука только на двух передних каналах, в то время, как массив

matrix[0] = 0x5; // 00000101
matrix[1] = 0x2; // 00000010

заставляет потока выводиться на первых четырех каналах (вероятно, на передней и задней паре, но не на центральном канале или LFE).

Битовый массив используется для описания оборудования (в части столбцов) и зависит от его настройки и возможностей, например:


Caution: Если количество голосов в приложении и у драйвера совпадает, конвертер не вызывается. В этом случае настроить перенаправление голосов не получится.

Если перед инициализацией плагина конвертирования голосов вызваны snd_pcm_plugin_get_voice_conversion() или snd_pcm_plugin_set_voice_conversion(), настройка преобразования приведет к ошибке -ENOENT.

Подготовка субканалов

Следует подготовить созданный субканал к работе:

Этот этап и состояние SND_PCM_STATUS_PREPARED могут показаться ненужными, но они требуются для корректной обработки underrun-состояний при воспроизведении и overrun-состояний при захвате звука. Подробнее см. в параграфах "Действия при остановке воспроизведения в субканале" и "Действия при остановке захвата в субканале" ниже.

Закрытие субканалов

При завершении захвата или воспроизведения аудио данных следует закрыть субканал, посредством snd_pcm_close(). Этот вызов освобождает ресурсы субканала и закрывает его дескриптор.

Воспроизведение аудио данных

После открытия и конфигурирования PCM устройства воспроизведения можно приступать к передаче аудио данных. Полный пример воспроизведения доступен по ссылке wave.c.


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

Состояния воспроизведения

При воспроизведении состояние PCM устройства поступательно меняется:

playback_states.png
Рисунок 1. Диаграмма состояний PCM устройства при воспроизведении

Переход из одного состояния в другое есть результат успешного вызова функции API или внутреннего события устройства:

SND_PCM_STATUS_NOTREADY .. SND_PCM_STATUS_READY
snd_pcm_channel_params() или snd_pcm_plugin_params().
SND_PCM_STATUS_READY .. SND_PCM_STATUS_PREPARED
snd_pcm_channel_prepare(), snd_pcm_playback_prepare() или snd_pcm_plugin_prepare().
SND_PCM_STATUS_PREPARED .. SND_PCM_STATUS_RUNNING
snd_pcm_write() или snd_pcm_plugin_write().
SND_PCM_STATUS_RUNNING .. SND_PCM_STATUS_UNDERRUN
The hardware buffer becomes empty during playback.
SND_PCM_STATUS_UNDERRUN .. SND_PCM_STATUS_PREPARED
snd_pcm_channel_prepare(), snd_pcm_playback_prepare() или snd_pcm_plugin_prepare().

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

Отправка данных в субканал

Данные могут быть отправлены в субканал одним из следующих способов, в зависимости от того, используются ли плагины конвертирования или нет:

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

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


Caution: Это также приводит к функционированию в режиме поллинга (активного опроса), что не является рекомендуемым режимом работы.

Другой способ устранения блокировок по записи – использование функции select() для опроса и ожидания готовности PCM субканала к приему данных. Эта техника реализована в примере wave.c. Такой подход позволяет одновременно ожидать данных от пользователя и передавать их устройству.

Получить файловый дескриптор, пригодный для использования в select(), может быть получен с помощью snd_pcm_file_descriptor().


Note: В рассматриваемом случае select() возвращает управление в том случае, когда субканал готов принять порцию данных размером frag_size байт. Если приложение попытается передать больше данных, то появляется вероятность блокировки на вызове, осуществляющем запись.

Действия при остановке воспроизведения в субканале

При воспроизведении PCM субканал останавливается, если данные в буфере заканчиваются. Это может произойти в том случае, если приложение не успевает поставлять данные, например по причине вытеснения более приоритетным приложением. Если такое вытеснение продлится достаточно долго, то данные в буфере могут исчерпаться.

В этом случае субканал изменит свое состояние на SND_PCM_STATUS_UNDERRUN. В нем данные более не принимаются (например, snd_pcm_write() и snd_pcm_plugin_write() завершатся с ошибкой) и субканал не перезапускает воспроизведение.

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

Остановка воспроизведения

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

Если при остановке воспроизведения требуется дождаться завершения обработки буфера, следует воспользоваться функциями:

Синхронизация с субканалом

Под синхронизацией подразумевается определение позиции в аппаратном буфере, которое соответсвует текущим обрабатываемым данным. Резрешение (ед. измерения) этой позиции зависит от конкретного драйвера.

Получить данную информацию можно с помощью вызовов API:

Обе функции заполняют структуру snd_pcm_channel_status_t. Приложению следует проанализировать следующие поля структуры:

scount
Аппаратная позиция в байтах, относительно начала потока и с момента последней подготовки канала. Каждая последующая подготовка канала сбрасывает этот счетчик.
count
Позиция воспроизведения в байтах, относительно общего числа байт данных, записанных в устройство.


Note: Поле count не используется, если активен mmap-плагин. Его можно отключить с помощью snd_pcm_plugin_set_disable().

Допустим, в потоке записано 1 000 000 байт данных. Если статус содержит в поле scount значение 999 000 и 1000 в поле count, то это значит, что 1000 байт данных осталось воспроизвести, а 999 000 байт уже проиграны.

Захват аудио данных

После открытия, настройки PCM устройства захвата и подготовки субканала (см. "Обслуживание PCM устройств" выше), возникают условия для программного захвата аудио данных. Полный пример захвата приведен в waverec.c.

Выбор источника данных для захвата

Большинство устройств позволяют подключить только один канал к АЦП. Этот источник должен определить пользователь. Если же устройство позволяет подключить несколько сигналов, стоит убедиться, что искомый сигнал среди них. Вызов API snd_mixer_group_write() позволяет контролировать микшер со стороны приложения и определять входные сигналы (подробнее см. в главе Архитектура микшера). При изучении примера waverec.c достаточно воспользоваться приложением mixer, которое является частью Photon и позволяет в том числе выбирать вход микшера.

Состояния захвата

При захвате состояние PCM устройства поступательно меняется:

capture_states.png
Рисунок 2. Диаграмма состояний PCM устройства при захвате

Переход из одного состояния в другое есть результат успешного вызова функции API или внутреннего события устройства:

SND_PCM_STATUS_NOTREADY .. SND_PCM_STATUS_READY
snd_pcm_channel_params() или snd_pcm_plugin_params().
SND_PCM_STATUS_READY .. SND_PCM_STATUS_PREPARED
snd_pcm_capture_prepare(), snd_pcm_channel_prepare() или snd_pcm_plugin_prepare().
SND_PCM_STATUS_PREPARED .. SND_PCM_STATUS_RUNNING
snd_pcm_read() или snd_pcm_plugin_read().
select()
Если устройство находится в состоянии PREPARED, функция select() для файловых дескрипторов субканалов захвата переводят драйвер в состояние RUNNING.
SND_PCM_STATUS_RUNNING .. SND_PCM_STATUS_OVERRUN
Аппаратный буфер переполняется в момент захвата. В этом случае snd_pcm_read() и snd_pcm_plugin_read() завершатся с ошибкой.
SND_PCM_STATUS_OVERRUN .. SND_PCM_STATUS_PREPARED
snd_pcm_capture_prepare(), snd_pcm_channel_prepare() или snd_pcm_plugin_prepare().

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

Получение данных из субканала

Есть несколько способов получить данные из субканала, зависящих от необходимости использования плагинов конвертирования:

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

Поддерживается неблокирующий режим чтения. Его можно активировать при открытии дескриптора или при использовании вызова snd_pcm_nonblock_mode().


Caution: Это также приводит к функционированию в режиме поллинга (активного опроса), что не является рекомендуемым режимом работы.

Другой способ избегания блокировок состоит в том, чтобы использовать select() для ожидания готовности данных в субканале. Эта техника применена в waverec.c, что позволяет программе одновременно ожидать ввода данных от пользователя и захватывать данные из субканала.

Для получения файлового дескриптора, пригодного для использования в select(), может использоваться вызов snd_pcm_file_descriptor().


Note: В рассматриваемом случае select() возвращает управление в том случае, когда субканал готов передать порцию данных размером frag_size байт. Если приложение попытается считать больше данных, то появляется вероятность блокировки на вызове, осуществляющем чтение.

Действия при остановке захвата в субканале

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

В этом случае субканал изменит свое состояние на SND_PCM_STATUS_OVERRUN. В нем данные более не захватываются (например, snd_pcm_read() и snd_pcm_plugin_read() завершатся с ошибкой) и субканал не перезапускает захват.

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

Остановка захвата

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

Синхронизация с субканалом

Под синхронизацией подразумевается определение позиции в аппаратном буфере, которое соответсвует текущим обрабатываемым данным. Резрешение (ед. измерения) этой позиции зависит от конкретного драйвера.

Получить данную информацию можно с помощью вызовов API:

Обе функции заполняют структуру snd_pcm_channel_status_t. Приложению следует проанализировать следующие поля структуры:

scount
Аппаратная позиция в байтах, относительно начала потока и с момента последней подготовки канала. Каждая последующая подготовка канала сбрасывает этот счетчик.
count
Позиция захвата в байтах в аппаратном буфере.


Note: Поле count не используется, если активен mmap-плагин. Его можно отключить с помощью snd_pcm_plugin_set_disable().




Предыдущий раздел: Библиотека libasound