Пример устройства: MP3 плеер

Предыдущая  Содержание  Следующая V*D*V

Рисунок 13.4 показывает операции, необходимые для проигрывания звука, на примере управляемого по Bluetooth MP3 плеера Linux, построенного на встроенном микропроцессоре. Вы можете запрограммировать сотовый телефон на Linux (который мы использовали в Главе 6, "Драйверы последовательных портов", и в Главе 12, "Драйверы Видео"), чтобы загрузить песни из интернета ночью, когда телефонные тарифы предположительно дешевле, и загрузить их на Compact Flash (CF) диск MP3 плеера через Bluetooth, чтобы можно было послушать песни на следующий день во время поездки до офиса.

 

Рисунок 13.4. Звук в MP3 плеере на Linux.

Рисунок 13.4. Звук в MP3 плеере на Linux.

 

Наша задача заключается в разработке звукового программного обеспечения для этого устройства. Приложение на плеере считывает песни с CF диска и декодирует их в системной памяти. Драйвер ALSA в ядре собирает музыкальные данные из системной памяти и отправляет их для передачи буферов, которые являются частью звукового контроллера процессора. Эти данные PCM направляются в кодек, который проигрывает музыку через динамик устройства. Как и в случае навигационной системы, которая обсуждалась в предыдущей главе, мы будем считать, что Linux поддерживает этот процессор, и что все архитектурно-зависимые сервисы, такие как DMA, поддерживаются ядром.

 

Таким образом, звуковое программное обеспечение для этого MP3 плеера состоит из двух частей:

 

1.Пользовательская программа, которая декодирует MP3 файлы, читаемые с CF диска, и преобразует их в простой PCM. Чтобы написать "родное" приложение-декодер ALSA, можно использовать вспомогательные процедуры, предлагаемые библиотекой alsa-lib. В разделе "Программирование ALSA" показано, как ALSA приложения взаимодействуют с драйверами ALSA.
 
Для создания такого устройства у вас также есть возможность использовать общедоступные MP3 плееры, таких как madplay (http://sourceforge.net/projects/mad/).
 

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

 

Имя регистра

Используется для конфигурации

VOLUME_REGISTER

Управляет общей громкостью кодека

SAMPLING_RATE_REGISTER

Устанавливает частоту дискретизации цифро-аналогового преобразования.

CLOCK_INPUT_REGISTER

Конфигурирует частоты, делители кодека и так далее.

CONTROL_REGISTER

Разрешает прерывания, конфигурирует причину прерывания (например, завершение передачи буфера), перезапускает оборудование, включает/выключает управление на шине и так далее.

STATUS_REGISTER

Состояние аудио событий кодека.

DMA_ADDRESS_REGISTER

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

DMA_SIZE_REGISTER

Хранит размер передач DMA к/от процессора.

 

Распечатка 13.1 является скелетом ALSA аудио драйвера для MP3 плеера и щедро использует псевдо-код (в комментариях), чтобы убрать посторонние детали. ALSA является сложным ядром и соответствующие аудио драйверы обычно имеют несколько тысяч строк.  В Распечатке 13.1 вы только знакомитесь с аудио драйвером. Для продолжения обучения стоит углубиться в исходные коды Linux внутри каталога верхнего уровня sound/.

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

В нашем примере драйвер реализован в виде драйвера платформы. Давайте рассмотрим шаги, выполняемые методом probe() драйвера платформы, mycard_audio_probe(). Мы будем немного отвлекаться на каждом шагу, чтобы объяснить соответствующие концепции и важные структуры данных, с которыми сталкиваемся, и это приведёт нас к другим частям драйвера и поможет связать всё воедино.

 

mycard_audio_probe() выполняет следующие действия:

 

1.Создаёт экземпляр звуковой карты, вызывая snd_card_new():
 
struct snd_card *card = snd_card_new(-1, id[dev->id], THIS_MODULE, 0);
 
Первым аргументом snd_card_new() является индекс карты (который идентифицирует эту карту среди нескольких звуковых карт системы), вторым аргументом является идентификатор, который будет храниться в поле id, возвращённой структуры snd_card, третьим аргументом является владелец модуля, а последним аргументом является размер поля собственных данных, которое будет предоставляться через поле private_data возвращаемой структуры snd_card (обычно для хранения данных, зависящих от микросхемы, таких как уровни прерывания, адреса ввода/вывода).
 
snd_card представляет созданную звуковую карту и в include/sound/core.h определяется следующим образом:
 
struct snd_card {
   int number;            /* Индекс карты */
   char id[16];           /* ID карты */
   /* ... */
   struct module *module; /* Владелец модуля */
   void *private_data;    /* Внутренние данные */
   /* ... */
   struct list_head controls;/* Список управления для этой карты */
   struct device *dev;    /* Устройство, связанное с этой картой */
   /* ... */
};
 
remove(), обратный методу probe, mycard_audio_remove(), отключает snd_card от ядра ALSA с помощью snd_card_free().

 

2.Создаёт экземпляр объекта для воспроизведения PCM и связывает его с картой, созданной на Шаге 1, используя snd_pcm_new():
 
int snd_pcm_new(struct snd_card *card, char *id,
               int device,
               int playback_count, int capture_count,
               struct snd_pcm **pcm);
 
Аргументами являются, соответственно, экземпляр звуковой карты, созданной на Шаге 1, строка идентификации, индекс устройства, количество поддерживаемых потоков воспроизведения, количество поддерживаемых потоков захвата (0 в нашем примере) и указатель для сохранения созданного экземпляра PCM. Созданный экземпляр PCM определяется в include/sound/pcm.h следующим образом:
 
Код:
struct snd_pcm {
struct snd_card *card;         /* Связанная snd_card */
/* ... */
struct snd_pcm_str streams[2]; /* Потоки воспроизведения и захвата этого компонента
                                 PCM. Каждый поток может поддерживать подпотоки,
                                 если ваше оборудование их поддерживает */
/* ... */
struct device *dev;            /* Связанное аппаратное устройство */
};
 
Процедура snd_device_new() лежит в основе snd_pcm_new(), как и другие подобные функции создания экземпляра компонента. snd_device_new() связывает компонент и набор операций с указанной snd_card (смотрите Шаг 3).

 

3.Подключает операции воспроизведения к экземпляру PCM, созданному на Шаге 2, вызывая snd_pcm_set_ops(). Структура snd_pcm_ops определяет эти операции для передачи PCM звука в кодек. Распечатка 13.1 выполняет это следующим образом:
 
Код:
/* Операторы для воспроизведения потока PCM */
static struct snd_pcm_ops mycard_playback_ops = {
   .open      = mycard_pb_open,   /* Открытие */
   .close     = mycard_pb_close,  /* Закрытие */
   .ioctl     = snd_pcm_lib_ioctl,/* Используйте для обработки специальных команд,
                                     в противном случае укажите общий обработчик
                                     ioctl, snd_pcm_lib_ioctl() */
   .hw_params = mycard_hw_params, /* Вызывается, когда вышележащие уровни устанавливают
                                     параметры оборудования,такие как формат звука.
                                     Выделение буфера DMA также выполняется здесь */
   .hw_free   = mycard_hw_free,   /* Освобождение ресурсов, выделенных в
                                     mycard_hw_params() */
   .prepare   = mycard_pb_prepare,/* Подготовка к передаче потока звука.
                                     Устанавливает формат звука, например, S16_LE
                                     (обсуждается позже), разрешает прерывания,... */
   .trigger   = mycard_pb_trigger,/* Вызывается, когда движок PCM стартует,
                                     останавливается, или делает паузу. Второй аргумент
                                     указывает причину вызова. Эта функция
                                     не может спать */
};
 
/* Связывание операций с экземпляром PCM */
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &mycard_playback_ops);
 
В Распечатке 13.1 mycard_pb_prepare() настраивает частоту дискретизации в SAMPLING_RATE_REGISTER, частоту источника в CLOCK_INPUT_REGISTER и передаёт полное разрешение прерывания в CONTROL_REGISTER. Метод trigger(), mycard_pb_trigger(), отображает на лету аудио буфер, полученный от ядра ALSA, используя dma_map_single(). (Мы обсудили потоковый DMA в Главе 10, "Подключение компонентов периферии".) Адрес отображённого буфера DMA запрограммирован в DMA_ADDRESS_REGISTER. Этот регистр является частью звукового контроллера процессора, в отличие от предыдущих регистров, которые находятся внутри кодека. Звуковой контроллер направляет данные DMA в кодек для воспроизведения.
 
Другим связанным объектом является структура snd_pcm_hardware, которая объявляет аппаратные возможности компонента  PCM. В нашем примере устройства она определена в Распечатке 13.1 следующим образом:
 
Код:
/* Возможности оборудования для проигрывания PCM потока */
static struct snd_pcm_hardware mycard_playback_stereo = {
   .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_PAUSE |
           SNDRV_PCM_INFO_RESUME);    /* поддерживается mmap(). Поток имеет возможности
                                         для паузы/продолжения воспроизведения */
   .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,         /* Максимальный размер буфера */
};
 
Этот объект связан с соответствующей snd_pcm из оператора open(), mycard_playback_open(), с помощью созданного во время выполнения экземпляра PCM. Каждый открытый поток PCM имеет объект выполнения, называемый snd_pcm_runtime, который содержит всю информацию, необходимую для управления этим потоком. Это гигантская структура конфигурации программного обеспечения и оборудования определена в include/sound/pcm.h и содержит в качестве одного из своих полей snd_pcm_hardware.
 

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 операции.
 
Связывает элемент управления микшером звуковой карты для глобального контроля громкости с помощью snd_ctl_add():
 
snd_ctl_add(card, snd_ctl_new1(&mycard_playback_vol, &myctl_private));
 
snd_ctl_new1() принимает структуру snd_kcontrol_new в качестве первого аргумента и возвращает указатель на структуру snd_kcontrol. Распечатка 13.1 определяет её следующим образом:
 
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,         /* Установка громкости */
};
 
Структура snd_kcontrol описывает элемент управления. Наш драйвер использует его в качестве ручки общего регулятора громкости. snd_ctl_add() регистрирует элемент snd_kcontrol в ядре ALSA. Указанные методы управления вызываются, когда  выполняются такие пользовательские приложения, как alsamixer. В Распечатке 13.1 метод put() snd_kcontrol, mycard_playback_volume_put(), записывает запрошенные настройки громкости в VOLUME_REGISTER кодека.

 

5.И, наконец, регистрирует звуковую карту в ядре ALSA:
 
snd_card_register(card);

 

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 кодека.

 

 

Сложность декодирования MP3

 

Приложение-декодер MP3 работающее на плеере, как показано на Рисунке 13.4, требует постоянную поставку фреймов MP3 с диска CF, который может поддерживать часто используемый MP3 со скоростью 128 кБит/с. Обычно это не является проблемой для большинства маломощных устройств, но в данном случае, требуется буферизация каждой песни в памяти перед её декодированием. (Фреймы MP3 при 128 кБит/с потребляют примерно 1 Мб на минуту музыки.)

 

MP3 декодирование является простой операцией и обычно происходит на лету, но MP3 кодирование является тяжёлой операцией, и не может быть выполнено в режиме реального времени без помощи оборудования. Голосовые кодеки, такие как G.711 и G.729, используемые в средах передачи Голоса поверх IP (VoIP), могут, однако, кодировать и декодировать аудио данные в реальном времени.

 

 

Предыдущая  Содержание  Следующая