Драйвер звуковой карты Linux ALSA 3: создание устройства PCM
Драйвер звуковой карты Linux ALSA 3: создание устройства PCM

1. Что такое ПКМ:

PCM — это аббревиатура английского слова «импульсно-кодовая модуляция», а китайский перевод — «импульсно-кодовая модуляция». Мы знаем, что в реальной жизни звуки, слышимые человеческими ушами, представляют собой аналоговые сигналы. PCM — это технология, которая преобразует звуки из аналоговых сигналов в цифровые. Ее принцип заключается в простом использовании фиксированной частоты для выборки аналоговых сигналов. серия непрерывных импульсов с различной амплитудой в форме волны. Амплитуды этих импульсов квантуются с определенной точностью. Эти квантованные значения непрерывно выводятся, передаются, обрабатываются или записываются на носители данных, которые составляют цифровой звук. процесс генерации.

Двумя важными показателями сигналов PCM являются частота дискретизации и точность квантования. В настоящее время частота дискретизации звука компакт-диска обычно составляет 44100 Гц, а точность квантования — 16 бит. Обычно при воспроизведении музыки приложение считывает аудиоданные (MP3, WMA, AAC...) с носителя. После декодирования данные PCM наконец отправляются в аудиодрайвер, в свою очередь, при записи аудио. Драйвер непрерывно отправляет выборочные данные PCM обратно в прикладную программу, а прикладная программа выполняет такие задачи, как сжатие и сохранение. Таким образом, две основные задачи аудиодрайвера:

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

2. Средний уровень PCM в драйвере alsa:

ALSA уже реализовала для нас мощный промежуточный уровень PCM. В своем собственном драйвере ей нужно реализовать только некоторые низкоуровневые функции, требующие доступа к оборудованию.

Для доступа к коду среднего уровня PCM,тыпервый Чтобы включить файлы заголовков<sound/pcm.h>,кроме того,Если вам нужен доступ к какой-либо функции, связанной с hw_param,может также включать<sound/pcm_params.h>。

Каждая звуковая карта может содержать до 4 экземпляров PCM, и каждый экземпляр PCM соответствует файлу устройства PCM. Это ограничение на количество экземпляров PCM связано с битовым размером номера устройства Linux. Если в будущем будут использоваться 64-битные номера устройств, мы сможем создавать больше экземпляров PCM. Но в большинстве случаев во встраиваемых устройствах экземпляра pcm достаточно.

Экземпляр PCM состоит из потока воспроизведения и потока захвата, каждый из которых состоит из одного или нескольких подпотоков.

Во встраиваемых системах это обычно не так сложно, как на рисунке 2.1. В большинстве случаев это звуковая карта и экземпляр PCM. Под PCM есть поток воспроизведения и захвата, а под поток воспроизведения и захвата.

На следующем рисунке перечислены несколько важных структур среднего уровня PCM. Это позволяет нам взглянуть на взаимосвязь между этими структурами с точки зрения UML, прояснить взаимосвязь между ними и помочь нам понять реализацию среднего уровня PCM. .

  • snd_pcm — это snd_device, висящий под snd_card
  • Поля в snd_pcm: потоки[2],Два элемента в этом множестве указывают на две структуры snd_pcm_str.,Представляют воспроизведение соответственно транслировать и захватывать stream
  • Поле substream в snd_pcm_str указывает на структуру snd_pcm_substream.
  • snd_pcm_substream — ядро ​​среднего слоя PCM.,Большинство задач обрабатываются в подпотоке,Особенно его поле ops (snd_pcm_ops),Запросы многих приложений пользовательского пространства к драйверам через alsa-lib обрабатываются функцией в этой структуре. Его поле времени выполнения указывает на структуру snd_pcm_runtime.,snd_pcm_runtime записывает некоторые важные программные и аппаратные операционные среды и параметры этого подпотока.

3. Создайте новый ПКМ:

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

Язык кода:javascript
копировать
int snd_pcm_new(struct snd_card *card, const char *id, int device, int playback_count, int capture_count,\
          struct snd_pcm ** rpcm);

Параметр устройство указывает, какой PCM под звуковой картой создан в данный момент. Первое устройство PCM начинается с 0.

Параметр воспроизведения_count указывает, сколько подпотоков воспроизведения будет иметь этот PCM.

Параметр capture_count указывает, сколько подпотоков захвата будет иметь этот PCM.

Еще один API для настройки интерфейса функции работы PCM:

Язык кода:javascript
копировать
void snd_pcm_set_ops(struct snd_pcm *pcm, int direction, struct snd_pcm_ops *ops);

Создание нового PCM можно описать следующей диаграммой последовательности вызова создания нового PCM:

Рисунок 3.1 Диаграмма последовательности нового ПКМ

  • snd_card_create pcm — это устройство (компонент) под звуковую карту, поэтому первым делом создаём звуковую карту
  • snd_pcm_new вызывает этот API для создания файла PCM.,Это верноapiМы сделаем следующие вещи:
    • Если да, создайте поток воспроизведения и соответствующий подпоток одновременно.
    • Если да, создайте поток захвата, и соответствующий подпоток будет создан одновременно.
    • Вызовите snd_device_new(), чтобы повесить PCM на звуковую карту.,Поле dev_register в параметре ops указывает на Functionsnd_pcm_dev_register.,Эта функция обратного вызова будет вызвана на этапе регистрации звуковой карты.
  • snd_pcm_set_ops устанавливает функцию интерфейса управления/управления для работы PCM. Функция в структуре snd_pcm_ops в параметре обычно является функцией, которую хочет реализовать наш драйвер.
  • snd_card_register зарегистрировать звуковую карту,На этом этапе будут пройдены все логические устройства под звуковой картой.,И вызовите обратный вызов регистрации каждой функции устройства.,для ПКМ,Это snd_pcm_dev_registerфункция, упомянутая на втором этапе.,Эта функция обратного вызова устанавливает узлы файлов устройства, используемые для связи с приложением пользовательского пространства (alsa-lib): /dev/snd/pcmCxxDxxp и /dev/snd/pcmCxxDxxc.

4. Создание узлов файла устройства (dev/snd/pcmCxxDxxp, ​​pcmCxxDxxc):

4.1 struct snd_minor:

Каждая структура snd_minor сохраняет контекстную информацию определенного логического устройства под звуковой картой. Она заполняется на этапе создания логического устройства. При использовании логического устройства соответствующую информацию можно получить из структуры. Оборудование PCM не является исключением и также должно использовать эту структуру. Эта структура определена в include/sound/core.h.

Язык кода:javascript
копировать
struct snd_minor {
 int type;   /* SNDRV_DEVICE_TYPE_XXX */
 int card;   /* card number */
 int device;   /* device number */
 const struct file_operations *f_ops; /* file operations */
 void *private_data;  /* private data for f_ops->open */
 struct device *dev;  /* device for sysfs */
};

Глобальный массив указателей snd_minor определен в файле sound/sound.c:

Язык кода:javascript
копировать
static struct snd_minor *snd_minors[256];

Как упоминалось ранее, на этапе регистрации звуковой карты (snd_card_register) будет вызвана функция обратного вызова pcm snd_pcm_dev_register(). В этой функции будет вызвана функция snd_register_device_for_dev():

Язык кода:javascript
копировать
static int snd_pcm_dev_register(struct snd_device *device)
{
    ......

 /* register pcm */
 err = snd_register_device_for_dev(devtype, pcm->card,
             pcm->device,
     &snd_pcm_f_ops[cidx],
     pcm, str, dev);
    ......
}

Давайте снова введем snd_register_device_for_dev():

Язык кода:javascript
копировать
int snd_register_device_for_dev(int type, struct snd_card *card, int dev,
    const struct file_operations *f_ops,
    void *private_data,
    const char *name, struct device *device)
{
 int minor;
 struct snd_minor *preg;

 if (snd_BUG_ON(!name))
  return -EINVAL;
 preg = kmalloc(sizeof *preg, GFP_KERNEL);
 if (preg == NULL)
  return -ENOMEM;
 preg->type = type;
 preg->card = card ? card->number : -1;
 preg->device = dev;
 preg->f_ops = f_ops;
 preg->private_data = private_data;
 mutex_lock(&sound_mutex);
#ifdef CONFIG_SND_DYNAMIC_MINORS
 minor = snd_find_free_minor();
#else
 minor = snd_kernel_minor(type, card, dev);
 if (minor >= 0 && snd_minors[minor])
  minor = -EBUSY;
#endif
 if (minor < 0) {
  mutex_unlock(&sound_mutex);
  kfree(preg);
  return minor;
 }
 snd_minors[minor] = preg;
 preg->dev = device_create(sound_class, device, MKDEV(major, minor),
      private_data, "%s", name);
 if (IS_ERR(preg->dev)) {
  snd_minors[minor] = NULL;
  mutex_unlock(&sound_mutex);
  minor = PTR_ERR(preg->dev);
  kfree(preg);
  return minor;
 }

 mutex_unlock(&sound_mutex);
 return 0;
}

  • первый,выделить и инициализироватьsnd_minorполя в структуре
    • type:SNDRV_DEVICE_TYPE_PCM_PLAYBACK/SNDRV_DEVICE_TYPE_PCM_CAPTURE
    • карта: номер карты
    • устройство: номер экземпляра PCM, в большинстве случаев 0
    • f_ops:snd_pcm_f_ops
    • Private_data: указывает на экземпляр PCM.
  • По типу,номера карт и ПКМ,Определить минорное значение индекса из множества,минорный также используется в качестве номера устройства PCM.
  • Поместите адрес структуры snd_minor в глобальное числоsnd_minors[minor]
  • Наконец, вызовите device_create, чтобы создать узел устройства.

4.2 Создание файлов устройства:

В конце раздела 4.1 был создан файл устройства, но в разделе 4.1 основное внимание уделяется процессу присвоения массива snd_minors. В этом разделе мы сосредоточимся на файле устройства.

Вернитесь к функции обратного вызова pcm snd_pcm_dev_register():

Язык кода:javascript
копировать
static int snd_pcm_dev_register(struct snd_device *device)
{
 int cidx, err;
 char str[16];
 struct snd_pcm *pcm;
 struct device *dev;

 pcm = device->device_data;
         ......
 for (cidx = 0; cidx < 2; cidx++) {
                  ......
  switch (cidx) {
  case SNDRV_PCM_STREAM_PLAYBACK:
   sprintf(str, "pcmC%iD%ip", pcm->card->number, pcm->device);
   devtype = SNDRV_DEVICE_TYPE_PCM_PLAYBACK;
   break;
  case SNDRV_PCM_STREAM_CAPTURE:
   sprintf(str, "pcmC%iD%ic", pcm->card->number, pcm->device);
   devtype = SNDRV_DEVICE_TYPE_PCM_CAPTURE;
   break;
  }
  /* device pointer to use, pcm->dev takes precedence if
   * it is assigned, otherwise fall back to card's device
   * if possible */
  dev = pcm->dev;
  if (!dev)
   dev = snd_card_get_device_link(pcm->card);
  /* register pcm */
  err = snd_register_device_for_dev(devtype, pcm->card,
        pcm->device,
        &snd_pcm_f_ops[cidx],
        pcm, str, dev);
                  ......
 }
         ......
}

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

  • воспроизведение -- pcmCxDxp, обычно в системе имеется только одна звуковая карта и один PCM, то есть pcmC0D0p
  • захват -- pcmCxDxc, обычно в системе только одна звуковая карта и один PCM, то есть pcmC0D0c

snd_pcm_f_ops, snd_pcm_f_ops — это стандартный массив структур файловой системы file_operations, который определен в файле sound/core/pcm_native.c:

Язык кода:javascript
копировать
const struct file_operations snd_pcm_f_ops[2] = {
 {
  .owner =  THIS_MODULE,
  .write =  snd_pcm_write,
  .aio_write =  snd_pcm_aio_write,
  .open =   snd_pcm_playback_open,
  .release =  snd_pcm_release,
  .llseek =  no_llseek,
  .poll =   snd_pcm_playback_poll,
  .unlocked_ioctl = snd_pcm_playback_ioctl,
  .compat_ioctl =  snd_pcm_ioctl_compat,
  .mmap =   snd_pcm_mmap,
  .fasync =  snd_pcm_fasync,
  .get_unmapped_area = snd_pcm_get_unmapped_area,
 },
 {
  .owner =  THIS_MODULE,
  .read =   snd_pcm_read,
  .aio_read =  snd_pcm_aio_read,
  .open =   snd_pcm_capture_open,
  .release =  snd_pcm_release,
  .llseek =  no_llseek,
  .poll =   snd_pcm_capture_poll,
  .unlocked_ioctl = snd_pcm_capture_ioctl,
  .compat_ioctl =  snd_pcm_ioctl_compat,
  .mmap =   snd_pcm_mmap,
  .fasync =  snd_pcm_fasync,
  .get_unmapped_area = snd_pcm_get_unmapped_area,
 }
};

snd_pcm_f_ops передается как параметр snd_register_device_for_dev и записывается в поле f_ops в snd_minors[minor]. Наконец, создайте узел устройства в snd_register_device_for_dev:

Язык кода:javascript
копировать
 snd_minors[minor] = preg;
 preg->dev = device_create(sound_class, device, MKDEV(major, minor),
      private_data, "%s", name);

4.3. Идем глубже, от приложения к уровню драйвера pcm:

4.3.1 Регистрация символьного устройства:

В файле sound/core/sound.c есть функция alsa_sound_init(), которая определяется следующим образом:

Язык кода:javascript
копировать
static int __init alsa_sound_init(void)
{
 snd_major = major;
 snd_ecards_limit = cards_limit;
 if (register_chrdev(major, "alsa", &snd_fops)) {
  snd_printk(KERN_ERR "unable to register native major device number %d/n", major);
  return -EIO;
 }
 if (snd_info_init() < 0) {
  unregister_chrdev(major, "alsa");
  return -ENOMEM;
 }
 snd_info_minor_register();
 return 0;
}

Параметр Major в Register_chrdev такой же, как и основной, когда устройство PCM было создано до использования Device_create. В результате, когда приложение открывает файл устройства /dev/snd/pcmCxDxp, оно вводит функцию обратного вызова открытия snd_fops. будет следующий В разделе описан процесс открытия.

4.3.2 Откройте устройство PCM:

Из предыдущего раздела мы знаем, что при открытии устройства PCM будет вызвана функция обратного вызова snd_fops. Давайте сначала посмотрим на определение snd_fops:

Язык кода:javascript
копировать
static const struct file_operations snd_fops =
{
 .owner = THIS_MODULE,
 .open =  snd_open
};

Следовать заsnd_openфункция,Сначала он берет этот номер устройства из индексного дескриптора,Затем используйте младший номер устройства в качестве индекса.,Удалить структуру snd_minor, заполняемую при регистрации устройства PCM, из глобального множества snd_minors (см. раздел 4.1),Затем возьмем f_ops устройства PCM из структуры snd_minor,и положитьfile->f_opЗаменить наpcmоборудованиеf_ops,Тогда позвоните напрямуюpcmоборудованиеf_ops->open(),затем вернись。потому чтоfile->f_opбыл заменен,после,Все вызовы чтения/записи/ioctl приложения перейдут в собственную функцию обратного вызова устройства PCM.,То есть обратный вызов, определенный в структуре snd_pcm_f_ops, упомянутой в разделе 4.2.

Язык кода:javascript
копировать
static int snd_open(struct inode *inode, struct file *file)
{
 unsigned int minor = iminor(inode);
 struct snd_minor *mptr = NULL;
 const struct file_operations *old_fops;
 int err = 0;

 if (minor >= ARRAY_SIZE(snd_minors))
  return -ENODEV;
 mutex_lock(&sound_mutex);
 mptr = snd_minors[minor];
 if (mptr == NULL) {
  mptr = autoload_device(minor);
  if (!mptr) {
   mutex_unlock(&sound_mutex);
   return -ENODEV;
  }
 }
 old_fops = file->f_op;
 file->f_op = fops_get(mptr->f_ops);
 if (file->f_op == NULL) {
  file->f_op = old_fops;
  err = -ENODEV;
 }
 mutex_unlock(&sound_mutex);
 if (err < 0)
  return err;

 if (file->f_op->open) {
  err = file->f_op->open(inode, file);
  if (err) {
   fops_put(file->f_op);
   file->f_op = fops_get(old_fops);
  }
 }
 fops_put(old_fops);
 return err;
}

На диаграмме последовательности ниже показано, как приложение в конечном итоге вызывает функцию обратного вызова в структуре snd_pcm_f_ops:

Рисунок 4.3.2.1 Приложение, работающее с устройством PCM

Ссылка: http://t.csdnimg.cn/IQCeC.

boy illustration
Неразрушающее увеличение изображений одним щелчком мыши, чтобы сделать их более четкими артефактами искусственного интеллекта, включая руководства по установке и использованию.
boy illustration
Копикодер: этот инструмент отлично работает с Cursor, Bolt и V0! Предоставьте более качественные подсказки для разработки интерфейса (создание навигационного веб-сайта с использованием искусственного интеллекта).
boy illustration
Новый бесплатный RooCline превосходит Cline v3.1? ! Быстрее, умнее и лучше вилка Cline! (Независимое программирование AI, порог 0)
boy illustration
Разработав более 10 проектов с помощью Cursor, я собрал 10 примеров и 60 подсказок.
boy illustration
Я потратил 72 часа на изучение курсорных агентов, и вот неоспоримые факты, которыми я должен поделиться!
boy illustration
Идеальная интеграция Cursor и DeepSeek API
boy illustration
DeepSeek V3 снижает затраты на обучение больших моделей
boy illustration
Артефакт, увеличивающий количество очков: на основе улучшения характеристик препятствия малым целям Yolov8 (SEAM, MultiSEAM).
boy illustration
DeepSeek V3 раскручивался уже три дня. Сегодня я попробовал самопровозглашенную модель «ChatGPT».
boy illustration
Open Devin — инженер-программист искусственного интеллекта с открытым исходным кодом, который меньше программирует и больше создает.
boy illustration
Эксклюзивное оригинальное улучшение YOLOv8: собственная разработка SPPF | SPPF сочетается с воспринимаемой большой сверткой ядра UniRepLK, а свертка с большим ядром + без расширения улучшает восприимчивое поле
boy illustration
Популярное и подробное объяснение DeepSeek-V3: от его появления до преимуществ и сравнения с GPT-4o.
boy illustration
9 основных словесных инструкций по доработке академических работ с помощью ChatGPT, эффективных и практичных, которые стоит собрать
boy illustration
Вызовите deepseek в vscode для реализации программирования с помощью искусственного интеллекта.
boy illustration
Познакомьтесь с принципами сверточных нейронных сетей (CNN) в одной статье (суперподробно)
boy illustration
50,3 тыс. звезд! Immich: автономное решение для резервного копирования фотографий и видео, которое экономит деньги и избавляет от беспокойства.
boy illustration
Cloud Native|Практика: установка Dashbaord для K8s, графика неплохая
boy illustration
Краткий обзор статьи — использование синтетических данных при обучении больших моделей и оптимизации производительности
boy illustration
MiniPerplx: новая поисковая система искусственного интеллекта с открытым исходным кодом, спонсируемая xAI и Vercel.
boy illustration
Конструкция сервиса Synology Drive сочетает проникновение в интрасеть и синхронизацию папок заметок Obsidian в облаке.
boy illustration
Центр конфигурации————Накос
boy illustration
Начинаем с нуля при разработке в облаке Copilot: начать разработку с минимальным использованием кода стало проще
boy illustration
[Серия Docker] Docker создает мультиплатформенные образы: практика архитектуры Arm64
boy illustration
Обновление новых возможностей coze | Я использовал coze для создания апплета помощника по исправлению домашних заданий по математике
boy illustration
Советы по развертыванию Nginx: практическое создание статических веб-сайтов на облачных серверах
boy illustration
Feiniu fnos использует Docker для развертывания личного блокнота Notepad
boy illustration
Сверточная нейронная сеть VGG реализует классификацию изображений Cifar10 — практический опыт Pytorch
boy illustration
Начало работы с EdgeonePages — новым недорогим решением для хостинга веб-сайтов
boy illustration
[Зона легкого облачного игрового сервера] Управление игровыми архивами
boy illustration
Развертывание SpringCloud-проекта на базе Docker и Docker-Compose