Пример устройства: MP3 плеер |
Предыдущая Содержание Следующая |
Рисунок 13.4 показывает операции, необходимые для проигрывания звука, на примере управляемого по Bluetooth MP3 плеера Linux, построенного на встроенном микропроцессоре. Вы можете запрограммировать сотовый телефон на Linux (который мы использовали в Главе 6, "Драйверы последовательных портов", и в Главе 12, "Драйверы Видео"), чтобы загрузить песни из интернета ночью, когда телефонные тарифы предположительно дешевле, и загрузить их на Compact Flash (CF) диск MP3 плеера через Bluetooth, чтобы можно было послушать песни на следующий день во время поездки до офиса.
Рисунок 13.4. Звук в MP3 плеере на Linux.
Наша задача заключается в разработке звукового программного обеспечения для этого устройства. Приложение на плеере считывает песни с CF диска и декодирует их в системной памяти. Драйвер ALSA в ядре собирает музыкальные данные из системной памяти и отправляет их для передачи буферов, которые являются частью звукового контроллера процессора. Эти данные PCM направляются в кодек, который проигрывает музыку через динамик устройства. Как и в случае навигационной системы, которая обсуждалась в предыдущей главе, мы будем считать, что Linux поддерживает этот процессор, и что все архитектурно-зависимые сервисы, такие как DMA, поддерживаются ядром.
Таким образом, звуковое программное обеспечение для этого MP3 плеера состоит из двух частей:
1.Пользовательская программа, которая декодирует MP3 файлы, читаемые с CF диска, и преобразует их в простой PCM. Чтобы написать "родное" приложение-декодер ALSA, можно использовать вспомогательные процедуры, предлагаемые библиотекой alsa-lib. В разделе "Программирование ALSA" показано, как ALSA приложения взаимодействуют с драйверами ALSA. 2.Низкоуровневый аудио ALSA драйвер ядра. Следующий раздел посвящён написанию этого драйвера.
Одна из возможных аппаратных реализаций устройства показана на Рисунке 13.4 и заключается в использовании PowerPC 405LP SoC и аудио кодека Texas Instruments TLV320. Процессорным ядром в этом случае является процессор 405, а встроенным интерфейсом аудио контроллера является Codec Serial Interface, последовательный интерфейс кодека (CSI). Микроконтроллеры обычно имеют высокопроизводительную внутреннюю шину, которая соединяет такие контроллеры, как DRAM и видео, и отдельную встроенную периферийную шину для связи с низкоскоростными периферийными устройствами, такими как последовательные порты, I2C и GPIO. В случае 405LP, первая называется Локальная шина процессора, Processor Local Bus (PLB), а вторая называется Встроенная периферийная шина, Onchip Peripheral Bus (OPB). Контроллер PCMCIA/CF подключён к PLB, а интерфейс аудио контроллера подключён к OPB.
Аудио драйвер состоит из трёх основных компонентов:
1.Процедуры, которые занимаются воспроизведением 2.Процедуры, которые занимаются захватом звука 3.Функции управления микшированием
Наш драйвер реализует воспроизведение, но не поддерживает запись, так как MP3 плеер в данном примере не имеет микрофона. Драйвер также упрощает функцию микширования. Вместо того, чтобы предлагать полный комплекс для управления уровнем громкости, то есть громкоговорителем, наушниками и линейным выходом, он позволяет использовать только один общий регулятор громкости.
Назначение регистров аудиооборудования MP3 плеера приведено в Таблице 13.1 и отражает эти допущения и упрощения, и не соответствует стандартам, таким как упоминавшийся ранее AC'97. Таким образом, кодек имеет SAMPLING_RATE_REGISTER для настройки частота дискретизации воспроизведения (цифро-аналогового преобразования), но не имеет регистров для настройки частоты записи (аналого-цифрового преобразования). VOLUME_REGISTER настраивает одну общую громкость.
Таблица 13.1. Назначение регистров аудио оборудования, показанного на Рисунке 13.4
Распечатка 13.1 является скелетом ALSA аудио драйвера для MP3 плеера и щедро использует псевдо-код (в комментариях), чтобы убрать посторонние детали. ALSA является сложным ядром и соответствующие аудио драйверы обычно имеют несколько тысяч строк. В Распечатке 13.1 вы только знакомитесь с аудио драйвером. Для продолжения обучения стоит углубиться в исходные коды Linux внутри каталога верхнего уровня sound/. Методы и структуры драйвераВ нашем примере драйвер реализован в виде драйвера платформы. Давайте рассмотрим шаги, выполняемые методом probe() драйвера платформы, mycard_audio_probe(). Мы будем немного отвлекаться на каждом шагу, чтобы объяснить соответствующие концепции и важные структуры данных, с которыми сталкиваемся, и это приведёт нас к другим частям драйвера и поможет связать всё воедино.
mycard_audio_probe() выполняет следующие действия:
1.Создаёт экземпляр звуковой карты, вызывая snd_card_new():
2.Создаёт экземпляр объекта для воспроизведения PCM и связывает его с картой, созданной на Шаге 1, используя snd_pcm_new():
3.Подключает операции воспроизведения к экземпляру PCM, созданному на Шаге 2, вызывая snd_pcm_set_ops(). Структура snd_pcm_ops определяет эти операции для передачи PCM звука в кодек. Распечатка 13.1 выполняет это следующим образом: 4.Заранее выделяет память для буферов с использованием snd_pcm_lib_preallocate_pages_for_all(). В дальнейшем mycard_hw_params() получает из этой заранее созданной области буферы DMA с помощью snd_pcm_lib_malloc_pages() и сохраняет их в созданном во время работы экземпляре PCM, о котором рассказывалось на Шаге 3. mycard_pb_trigger() подключает этот буфер к DMA во время запуска операции PCM и отключает его при остановке PCM операции.
5.И, наконец, регистрирует звуковую карту в ядре ALSA:
codec_write_reg() (используется, но оставлена нереализованной в Распечатке 13.1) пишет значения в регистры кодека, общаясь через шину, которая соединяет аудио контроллер в процессоре с внешним кодеком. Если, например, шинным протоколом является I2C или SPI, codec_write_reg() использует функции интерфейса, о которых говорилось в Главе 8, "Протокол связи между микросхемами".
Если вы хотите создать в вашем драйвере для распечатки регистров во время отладки интерфейс /proc или экспортировать какой-либо параметр при нормальной эксплуатации, пользуйтесь услугами snd_card_proc_new() и ей подобных. В Распечатке 13.1 файлы интерфейса /proc не используются.
Если вы соберёте и загрузите модуль драйвера из Распечатки 13.1, то увидите два новых узла устройств, появившихся в MP3 плеере: /dev/snd/pcmC0D0p и /dev/snd/controlC0. Первый является интерфейсом для воспроизведения звука, а второй представляет собой интерфейс для управления микшером. Приложение декодирует MP3 с помощью alsa-lib и управляет потоком музыки через эти узлы устройства.
Распечатка 13.1. ALSA драйвер для MP3 плеера на Linux
Код: #include <linux/platform_device.h> #include <linux/soundcard.h> #include <sound/driver.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/initval.h> #include <sound/control.h>
/* Частоты воспроизведения, поддерживаемые кодеком */ static unsigned int mycard_rates[] = { 8000, 48000, };
/* Аппаратные ограничения для канала воспроизведения */ static struct snd_pcm_hw_constraint_list mycard_playback_rates = { .count = ARRAY_SIZE(mycard_rates), .list = mycard_rates, .mask = 0, };
static struct platform_device *mycard_device; static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
/* Аппаратные возможности для PCM потока */ static struct snd_pcm_hardware mycard_playback_stereo = { .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_BLOCK_TRANSFER), .formats = SNDRV_PCM_FMTBIT_S16_LE, /* 16 бит на канал, сначала младший */ .rates = SNDRV_PCM_RATE_8000_48000, /* Диапазон частот дискретизации ЦАП */ .rate_min = 8000, /* Минимальная частота дискретизации */ .rate_max = 48000, /* Максимальная частота дискретизации */ .channels_min = 2, /* Поддерживается левый и правый канал */ .channels_max = 2, /* Поддерживается левый и правый канал */ .buffer_bytes_max = 32768, /* Максимальный размер буфера */ };
/* Открываем устройство в режиме воспроизведения */ static int mycard_pb_open(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime;
/* Инициализируем структуры драйвера */ /* ... */ /* Инициализируем регистры кодека */ /* ... */ /* Подключаем возможности оборудования этого компонента PCM */ runtime->hw = mycard_playback_stereo;
/* Информируем ядро ALSA об ограничениях, которые имеет кодек. Например, в данном случае, он поддерживает частоты дискретизации PCM только от 8000 Гц до 48000 Гц */ snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &mycard_playback_rates); return 0; }
/* Закрытие */ static int mycard_pb_close(struct snd_pcm_substream *substream) { /* Выключение кодека, остановка DMA, освобождение структур данных */ /* ... */ return 0; }
/* Запись в регистры кодека через шину, соединяющую процессор и кодек */ void codec_write_reg(uint codec_register, uint value) { /* ... */ }
/* Подготовка к передаче потока звука в кодек */ static int mycard_pb_prepare(struct snd_pcm_substream *substream) { /* Разрешаем прерывание по завершении передачи DMA, записывая CONTROL_REGISTER с помощью codec_write_reg() */
/* Устанавливаем частоту дискретизации, записывая SAMPLING_RATE_REGISTER */
/* Конфигурируем источник частоты и включаем тактирование, делая запись в CLOCK_INPUT_REGISTER */
/* Выделение дескрипторов DMA для передачи звука */
return 0; }
/* Включение звука/стоп/... */ static int mycard_pb_trigger(struct snd_pcm_substream *substream, int cmd) { switch (cmd) { case SNDRV_PCM_TRIGGER_START: /* Связываем созданный буфер звука подпотока (который является смещением в runtime->dma_area) с помощью dma_map_single(), помещает полученный в результате адрес в регистр контроллер звука DMA_ADDRESS_REGISTER и начинаем DMA */ /* ... */ break;
case SNDRV_PCM_TRIGGER_STOP: /* Останавливаем поток. Отключаем буфер DMA с помощью dma_unmap_single() */ /* ... */ break;
default: return -EINVAL; break; }
return 0; }
/* Создание буферов DMA с помощью заранее выделенной для DMA памяти в методе probe(). dma_[map|unmap]_single() выполняется для этой области позже */ static int mycard_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *hw_params) { /* Для удовлетворения этого запроса памяти используем память, заранее выделенную в mycard_audio_probe() */ return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); }
/* Обратна для mycard_hw_params() */ static int mycard_hw_free(struct snd_pcm_substream *substream) { return snd_pcm_lib_free_pages(substream); }
/* Информация о громкости */ static int mycard_pb_vol_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;/* Целочисленный тип */ uinfo->count = 1; /* Число регуляторов */ uinfo->value.integer.min = 0; /* Минимальный уровень громкости */ uinfo->value.integer.max = 10; /* Максимальный уровень громкости */ uinfo->value.integer.step = 1; /* Шаг изменения */ return 0; }
/* Регулятор громкости воспроизведения */ static int mycard_pb_vol_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *uvalue) { int global_volume = uvalue->value.integer.value[0];
/* Записываем global_volume в VOLUME_REGISTER с помощью codec_write_reg() */ /* ... */ /* Если громкость изменилась относительно текущего значения, возвращаем 1. Если получена ошибка, возвращаем отрицательное значение. Иначе возвращаем 0 */ }
/* Получение громкости воспроизведения */ static int mycard_pb_vol_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *uvalue) { /* Читаем global_volume из VOLUME_REGISTER и возвращаем её через uvalue->integer.value[0] */ /* ... */ return 0; }
/* Точки входа для микшера воспроизведения */ static struct snd_kcontrol_new mycard_playback_vol = { .iface = SNDRV_CTL_ELEM_IFACE_MIXER,/* Управление имеет тип MIXER */ .name = "MP3 Volume", /* Имя */ .index = 0, /* Номер кодека: 0 */ .info = mycard_pb_vol_info, /* Информация о громкости */ .get = mycard_pb_vol_get, /* Получение громкости */ .put = mycard_pb_vol_put, /* Установка громкости */ };
/* Операторы для потока воспроизведения PCM */ static struct snd_pcm_ops mycard_playback_ops = { .open = mycard_playback_open, /* Открытие */ .close = mycard_playback_close, /* Закрытие */ .ioctl = snd_pcm_lib_ioctl, /* Универсальный обработчик ioctl */ .hw_params = mycard_hw_params, /* Параметры оборудования */ .hw_free = mycard_hw_free, /* Освобождение параметров h/w */ .prepare = mycard_playback_prepare, /* Подготовка для передачи аудио потока */ .trigger = mycard_playback_trigger, /* Вызывается, когда движок PCM стартует/останавливается/встаёт в паузу */ };
/* Метод драйвера платформы probe() */ static int __init mycard_audio_probe(struct platform_device *dev) { struct snd_card *card; struct snd_pcm *pcm; int myctl_private;
/* Создаём экземпляр структуры snd_card */ card = snd_card_new(-1, id[dev->id], THIS_MODULE, 0);
/* Создаём новый экземпляр PCM с 1 потоком воспроизведения и 0 потоков захвата */ snd_pcm_new(card, "mycard_pcm", 0, 1, 0, &pcm);
/* Создаём свои начальные буферы DMA */ snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS, snd_dma_continuous_data (GFP_KERNEL), 256*1024, 256*1024);
/* Объединяем операции воспроизведения с экземпляром PCM */ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &mycard_playback_ops);
/* Связываем элемент управления микшером с этой картой */ snd_ctl_add(card, snd_ctl_new1(&mycard_playback_vol, &myctl_private)); strcpy(card->driver, "mycard");
/* Регистрируем звуковую карту */ snd_card_register(card);
/* Сохраняем карту для доступа из других методов */ platform_set_drvdata(dev, card);
return 0; }
/* Метод драйвера платформы remove() */ static int mycard_audio_remove(struct platform_device *dev) { snd_card_free(platform_get_drvdata(dev)); platform_set_drvdata(dev, NULL); return 0; }
/* Определение драйвера платформы */ static struct platform_driver mycard_audio_driver = { .probe = mycard_audio_probe, /* Метод probe */ .remove = mycard_audio_remove, /* Метод remove */ .driver = { .name = "mycard_ALSA", }, };
/* Инициализация драйвера */ static int __init mycard_audio_init(void) { /* Регистрируем драйвер платформы и устройство */ platform_driver_register(&mycard_audio_driver); mycard_device = platform_device_register_simple("mycard_ALSA", -1, NULL, 0); return 0; }
/* Отключение драйвера */ static void __exit mycard_audio_exit(void) { platform_device_unregister(mycard_device); platform_driver_unregister(&mycard_audio_driver); }
module_init(mycard_audio_init); module_exit(mycard_audio_exit); MODULE_LICENSE("GPL");
Программирование ALSAЧтобы понять, как библиотека пользовательского пространства alsa-lib взаимодействует с ALSA драйверами пространства ядра, давайте напишем простое приложение, которое устанавливает уровень громкости MP3 плеера. Мы свяжем сервисы alsa-lib, используемые приложением, с методами управления микшером, определёнными в Распечатке 13.1. Начнём с загрузки драйвера и изучения возможностей микшера:
bash> amixer contents ... numid=3,iface=MIXER,name="MP3 Volume" ; type=INTEGER,... ...
Сначала в приложении для управления громкостью выделяется пространство для объектов alsa-lib, необходимых для выполнения операции регулировки громкости:
#include <alsa/asoundlib.h> snd_ctl_elem_value_t *nav_control; snd_ctl_elem_id_t *nav_id; snd_ctl_elem_info_t *nav_info;
snd_ctl_elem_value_alloca(&nav_control); snd_ctl_elem_id_alloca(&nav_id); snd_ctl_elem_info_alloca(&nav_info);
Далее устанавливается тип интерфейса в SND_CTL_ELEM_IFACE_MIXER, как указано в структуре mycard_playback_vol в Распечатке 13.1:
snd_ctl_elem_id_set_interface(nav_id, SND_CTL_ELEM_IFACE_MIXER);
Теперь устанавливается numid для громкости MP3, полученный из вышеприведённого вывода из amixer:
snd_ctl_elem_id_set_numid(nav_id, 3); /* num_id=3 */
Открывается микшер узел, /dev/snd/controlC0. Третий аргумент для snd_ctl_open() определяет номер карты в имени узла:
snd_ctl_open(&nav_handle, card, 0); /* Connect data structures */ snd_ctl_elem_info_set_id(nav_info, nav_id); snd_ctl_elem_info(nav_handle, nav_info);
Проверяется тип поля в структуре snd_ctl_elem_info, определённой в mycard_pb_vol_info() в Распечатке 13.1, следующим образом:
if (snd_ctl_elem_info_get_type(nav_info) != SND_CTL_ELEM_TYPE_INTEGER) { printk("Mismatch in control type\n"); }
Запрашивается поддерживаемый кодеком диапазон гормкости с помощью метода драйвера mycard_pb_vol_info():
long desired_volume = 5; long min_volume = snd_ctl_elem_info_get_min(nav_info); long max_volume = snd_ctl_elem_info_get_max(nav_info); /* Убеждаемся, что desired_volume в диапазоне от min_volume до max_volume */ /* ... */
Как это определено в mycard_pb_vol_info() в Распечатке 13.1, минимальное и максимальное значения, которые возвращаются показанными выше вспомогательными процедурами alsa-lib, равны соответственно 0 и 10.
Наконец, устанавливается желаемая громкость и записывается в кодек:
snd_ctl_elem_value_set_integer(nav_control, 0, desired_volume); snd_ctl_elem_write(nav_handle, nav_control);
Вызов snd_ctl_elem_write() приводит к вызову mycard_pb_vol_put(), которая записывает желаемый уровень громкости в VOLUME_REGISTER кодека.
| |||||||||||||||||
Предыдущая Содержание Следующая |