Общие практики выполнения пользовательских задач
Данная глава включает в себя:
Программные процессы при воспроизведении и захвате во многом схожи. В данной статье рассматриваются общие принципы.
Первое, чем необходимо озаботиться – создать соединние (открыть) соответствующее PCM устройство воспроизведения или захвата. Вызовы API, позволяющие это выполнить:
Оба вызова возвращают дескриптор PCM соединения, которое будет использоваться в последующих операциях API. Дескриптор во многом схож с дескриптором файлового потока и имеет тип указателя на структуру snd_pcm_t
, являющуюся прозрачной для драйвера. Обе функции применимы как для каналов захвата, так и каналов воспроизведения, что определяется параметром mode. Он может принимать значения:
Фрагмент кода из примера 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_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_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; // 00000001matrix[1] = 0x2; // 00000010
приводит к выводу звука только на двух передних каналах, в то время, как массив
matrix[0] = 0x5; // 00000101matrix[1] = 0x2; // 00000010
заставляет потока выводиться на первых четырех каналах (вероятно, на передней и задней паре, но не на центральном канале или LFE).
Битовый массив используется для описания оборудования (в части столбцов) и зависит от его настройки и возможностей, например:
Если количество голосов в приложении и у драйвера совпадает, конвертер не вызывается. В этом случае настроить перенаправление голосов не получится. |
Если перед инициализацией плагина конвертирования голосов вызваны 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.
Если приложение может поставлять данные в нескольких форматах, то остановиться лучше на том, которое поддерживается оборудованием. Это снизит издержки CPU, ресурсы которого будут расходоваться на непрерывное конвертирование данных. |
При воспроизведении состояние PCM устройства поступательно меняется:
Переход из одного состояния в другое есть результат успешного вызова функции API или внутреннего события устройства:
Подробности можно почерпнуть в описании указанных функций.
Данные могут быть отправлены в субканал одним из следующих способов, в зависимости от того, используются ли плагины конвертирования или нет:
Поддерживается полностью неблокирующий режим записи. Для этого нужно, чтобы дескриптор был открыт с помощью функции snd_pcm_nonblock_mode().
Это также приводит к функционированию в режиме поллинга (активного опроса), что не является рекомендуемым режимом работы. |
Другой способ устранения блокировок по записи – использование функции select() для опроса и ожидания готовности PCM субканала к приему данных. Эта техника реализована в примере wave.c. Такой подход позволяет одновременно ожидать данных от пользователя и передавать их устройству.
Получить файловый дескриптор, пригодный для использования в select(), может быть получен с помощью snd_pcm_file_descriptor().
В рассматриваемом случае select() возвращает управление в том случае, когда субканал готов принять порцию данных размером frag_size байт. Если приложение попытается передать больше данных, то появляется вероятность блокировки на вызове, осуществляющем запись. |
При воспроизведении PCM субканал останавливается, если данные в буфере заканчиваются. Это может произойти в том случае, если приложение не успевает поставлять данные, например по причине вытеснения более приоритетным приложением. Если такое вытеснение продлится достаточно долго, то данные в буфере могут исчерпаться.
В этом случае субканал изменит свое состояние на SND_PCM_STATUS_UNDERRUN
. В нем данные более не принимаются (например, snd_pcm_write() и snd_pcm_plugin_write() завершатся с ошибкой) и субканал не перезапускает воспроизведение.
Единственным способом выйти из этого состояния является закрытие субканала или его повторная подготовка (см. "Подготовка субканалов" выше). Это заставляет приложение предпринять некоторые действия для подготовки новых данных, что полезно для приложений, которые хотят синхронизировать звук с чем-то еще.
Если приложение желает остановить воспроизведение, то один из способов (не самый лучший) описан в предыдущем параграфе. Для прерывания воспроизведения и удаления необработанных данных из буфера также можно использовать:
Если при остановке воспроизведения требуется дождаться завершения обработки буфера, следует воспользоваться функциями:
Под синхронизацией подразумевается определение позиции в аппаратном буфере, которое соответсвует текущим обрабатываемым данным. Резрешение (ед. измерения) этой позиции зависит от конкретного драйвера.
Получить данную информацию можно с помощью вызовов API:
Обе функции заполняют структуру snd_pcm_channel_status_t. Приложению следует проанализировать следующие поля структуры:
Поле 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 устройства поступательно меняется:
Переход из одного состояния в другое есть результат успешного вызова функции API или внутреннего события устройства:
PREPARED
, функция select() для файловых дескрипторов субканалов захвата переводят драйвер в состояние RUNNING
. Подробности можно почерпнуть в описании указанных функций.
Есть несколько способов получить данные из субканала, зависящих от необходимости использования плагинов конвертирования:
Поддерживается неблокирующий режим чтения. Его можно активировать при открытии дескриптора или при использовании вызова snd_pcm_nonblock_mode().
Это также приводит к функционированию в режиме поллинга (активного опроса), что не является рекомендуемым режимом работы. |
Другой способ избегания блокировок состоит в том, чтобы использовать select() для ожидания готовности данных в субканале. Эта техника применена в waverec.c, что позволяет программе одновременно ожидать ввода данных от пользователя и захватывать данные из субканала.
Для получения файлового дескриптора, пригодного для использования в select(), может использоваться вызов snd_pcm_file_descriptor().
В рассматриваемом случае select() возвращает управление в том случае, когда субканал готов передать порцию данных размером frag_size байт. Если приложение попытается считать больше данных, то появляется вероятность блокировки на вызове, осуществляющем чтение. |
При захвате субканал останавливается, если оборудование не располагает местом в буфере для записи новых данных. Это становится возможным, если приложение не успевает обрабатывать захватываемые данные, например по причине вытеснения более приоритетным приложением. Если такое вытеснение продлится достаточно долго, то данные могут переполнить аппаратный буфер.
В этом случае субканал изменит свое состояние на SND_PCM_STATUS_OVERRUN
. В нем данные более не захватываются (например, snd_pcm_read() и snd_pcm_plugin_read() завершатся с ошибкой) и субканал не перезапускает захват.
Единственным способом выйти из этого состояния является закрытие субканала или его повторная подготовка (см. "Подготовка субканалов" выше). Это заставляет приложение предпринять некоторые действия для подготовки новых данных, что полезно для приложений, которые хотят синхронизировать звук с чем-то еще.
Если приложение желает остановить захват, то один из способов (не самый лучший) описан в предыдущем параграфе. Для прерывания захвата и удаления необработанных данных из буфера также можно использовать:
Под синхронизацией подразумевается определение позиции в аппаратном буфере, которое соответсвует текущим обрабатываемым данным. Резрешение (ед. измерения) этой позиции зависит от конкретного драйвера.
Получить данную информацию можно с помощью вызовов API:
Обе функции заполняют структуру snd_pcm_channel_status_t. Приложению следует проанализировать следующие поля структуры:
Поле count не используется, если активен mmap-плагин. Его можно отключить с помощью snd_pcm_plugin_set_disable(). |
Предыдущий раздел: Библиотека libasound