The website "dmilvdv.narod.ru." is not registered with uCoz.
If you are absolutely sure your website must be here,
please contact our Support Team.
If you were searching for something on the Internet and ended up here, try again:

About uCoz web-service

Community

Legal information

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

Пример устройства: 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), могут, однако, кодировать и декодировать аудио данные в реальном времени.

 

 

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