Глава 5. Интерфейс PCM

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

Общие сведения

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

 

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

 

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

 

Экземпляр PCM содержит PCM потоки воспроизведения и захвата, а каждый PCM поток состоит из одного или более субпотоков PCM. Некоторые звуковые карты поддерживают сложные функции воспроизведения. Например, emu10k1 имеет возможность воспроизведение PCM потока из 32-х стерео субпотоков. В этом случае во время каждого открытия (как правило) автоматически выбирается и открывается свободный субпоток. Между тем, если существует только один субпоток и он был уже открыт, успешное открытое будет либо блокироваться, либо выдаваться ошибка EAGAIN, в зависимости от режима открытия файла. Но вы не должны заботиться о таких деталях в своём драйвере. О таком поведении будет заботиться центральный уровень PCM.

Полный пример кода

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

 

#include <sound/pcm.h>

....

 

/* параметры оборудования */

static struct snd_pcm_hardware snd_mychip_playback_hw = {

    .info = (SNDRV_PCM_INFO_MMAP |

             SNDRV_PCM_INFO_INTERLEAVED |

             SNDRV_PCM_INFO_BLOCK_TRANSFER |

             SNDRV_PCM_INFO_MMAP_VALID),

    .formats =          SNDRV_PCM_FMTBIT_S16_LE,

    .rates =            SNDRV_PCM_RATE_8000_48000,

    .rate_min =         8000,

    .rate_max =         48000,

    .channels_min =     2,

    .channels_max =     2,

    .buffer_bytes_max = 32768,

    .period_bytes_min = 4096,

    .period_bytes_max = 32768,

    .periods_min =      1,

    .periods_max =      1024,

};

 

/* параметры оборудования */

static struct snd_pcm_hardware snd_mychip_capture_hw = {

    .info = (SNDRV_PCM_INFO_MMAP |

             SNDRV_PCM_INFO_INTERLEAVED |

             SNDRV_PCM_INFO_BLOCK_TRANSFER |

             SNDRV_PCM_INFO_MMAP_VALID),

    .formats =          SNDRV_PCM_FMTBIT_S16_LE,

    .rates =            SNDRV_PCM_RATE_8000_48000,

    .rate_min =         8000,

    .rate_max =         48000,

    .channels_min =     2,

    .channels_max =     2,

    .buffer_bytes_max = 32768,

    .period_bytes_min = 4096,

    .period_bytes_max = 32768,

    .periods_min =      1,

    .periods_max =      1024,

};

 

/* обратный вызов open */

static int snd_mychip_playback_open(struct snd_pcm_substream *substream)

{

    struct mychip *chip = snd_pcm_substream_chip(substream);

    struct snd_pcm_runtime *runtime = substream->runtime;

 

    runtime->hw = snd_mychip_playback_hw;

    /* здесь будет дополнительный код инициализации оборудования */

    ....

    return 0;

}

 

/* обратный вызов close */

static int snd_mychip_playback_close(struct snd_pcm_substream *substream)

{

    struct mychip *chip = snd_pcm_substream_chip(substream);

    /* здесь будет дополнительный код инициализации оборудования */

    ....

    return 0;

}

 

/* обратный вызов open */

static int snd_mychip_capture_open(struct snd_pcm_substream *substream)

{

    struct mychip *chip = snd_pcm_substream_chip(substream);

    struct snd_pcm_runtime *runtime = substream->runtime;

 

    runtime->hw = snd_mychip_capture_hw;

    /* здесь будет дополнительный код инициализации оборудования */

    ....

    return 0;

}

 

/* обратный вызов close */

static int snd_mychip_capture_close(struct snd_pcm_substream *substream)

{

    struct mychip *chip = snd_pcm_substream_chip(substream);

    /* здесь будет код, зависящий от оборудования */

    ....

    return 0;

}

 

/* обратный вызов hw_params */

static int snd_mychip_pcm_hw_params(struct snd_pcm_substream *substream,

                         struct snd_pcm_hw_params *hw_params)

{

    return snd_pcm_lib_malloc_pages(substream,

                               params_buffer_bytes(hw_params));

}

 

/* обратный вызов hw_free */

static int snd_mychip_pcm_hw_free(struct snd_pcm_substream *substream)

{

    return snd_pcm_lib_free_pages(substream);

}

 

/* обратный вызов prepare */

static int snd_mychip_pcm_prepare(struct snd_pcm_substream *substream)

{

    struct mychip *chip = snd_pcm_substream_chip(substream);

    struct snd_pcm_runtime *runtime = substream->runtime;

 

    /* настраиваем оборудование, используя текущую конфигурацию,

     * например...

     */

    mychip_set_sample_format(chip, runtime->format);

    mychip_set_sample_rate(chip, runtime->rate);

    mychip_set_channels(chip, runtime->channels);

    mychip_set_dma_setup(chip, runtime->dma_addr,

                         chip->buffer_size,

                         chip->period_size);

    return 0;

}

 

/* обратный вызов trigger */

static int snd_mychip_pcm_trigger(struct snd_pcm_substream *substream,

                                  int cmd)

{

    switch (cmd) {

    case SNDRV_PCM_TRIGGER_START:

        /* делаем что-нибудь для запуска работы с PCM */

        ....

        break;

    case SNDRV_PCM_TRIGGER_STOP:

        /* делаем что-нибудь для остановки работы с PCM */

        ....

        break;

    default:

        return -EINVAL;

    }

}

 

/* обратный вызов pointer */

static snd_pcm_uframes_t

snd_mychip_pcm_pointer(struct snd_pcm_substream *substream)

{

    struct mychip *chip = snd_pcm_substream_chip(substream);

    unsigned int current_ptr;

 

    /* получаем текущий указатель на оборудование */

    current_ptr = mychip_get_hw_pointer(chip);

    return current_ptr;

}

 

/* операции */

static struct snd_pcm_ops snd_mychip_playback_ops = {

    .open =        snd_mychip_playback_open,

    .close =       snd_mychip_playback_close,

    .ioctl =       snd_pcm_lib_ioctl,

    .hw_params =   snd_mychip_pcm_hw_params,

    .hw_free =     snd_mychip_pcm_hw_free,

    .prepare =     snd_mychip_pcm_prepare,

    .trigger =     snd_mychip_pcm_trigger,

    .pointer =     snd_mychip_pcm_pointer,

};

 

/* операции */

static struct snd_pcm_ops snd_mychip_capture_ops = {

    .open =        snd_mychip_capture_open,

    .close =       snd_mychip_capture_close,

    .ioctl =       snd_pcm_lib_ioctl,

    .hw_params =   snd_mychip_pcm_hw_params,

    .hw_free =     snd_mychip_pcm_hw_free,

    .prepare =     snd_mychip_pcm_prepare,

    .trigger =     snd_mychip_pcm_trigger,

    .pointer =     snd_mychip_pcm_pointer,

};

 

/*

 *  определения захвата здесь опущены...

 */

 

/* создание pcm устройства */

static int __devinit snd_mychip_new_pcm(struct mychip *chip)

{

    struct snd_pcm *pcm;

    int err;

 

    err = snd_pcm_new(chip->card, "My Chip", 0, 1, 1, &pcm);

    if (err < 0) 

        return err;

    pcm->private_data = chip;

    strcpy(pcm->name, "My Chip");

    chip->pcm = pcm;

    /* устанавливаем операции */

    snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,

                    &snd_mychip_playback_ops);

    snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,

                    &snd_mychip_capture_ops);

    /* предварительное создание буферов */

    /* ЗАМЕЧАНИЕ: это может окончиться неудачей */

    snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,

                                          snd_dma_pci_data(chip->pci),

                                          64*1024, 64*1024);

    return 0;

}

 

Конструктор

Экземпляр PCM создаётся функцией snd_pcm_new(). Но лучше создать конструктор для PCM, а именно:

 

static int __devinit snd_mychip_new_pcm(struct mychip *chip)

{

    struct snd_pcm *pcm;

    int err;

 

    err = snd_pcm_new(chip->card, "My Chip", 0, 1, 1, &pcm);

    if (err < 0)

        return err;

    pcm->private_data = chip;

    strcpy(pcm->name, "My Chip");

    chip->pcm = pcm;

....

    return 0;

}

 

Функция snd_pcm_new() принимает четыре аргумента. Первым аргументом является указатель на карту, для которой предназначен этот PCM, а вторым - строка идентификатора.

 

Третий аргумент (индекс, 0 в приведённом выше примере) - индекс этого нового PCM. Он начинается с нуля. Если вы создаёте более одного экземпляра PCM, указывайте другие числа в этом аргументе. Например, для второго устройства PCM индекс = 1.

 

Четвертый и пятый аргументы представляют собой количество субпотоков для воспроизведения и захвата, соответственно. Здесь для обоих аргументов использовано 1. Если нет субпотоков воспроизведения или захвата, в соответствующем аргументе передаётся 0.

 

Если чип поддерживает несколько субпотоков воспроизведения или захвата, можно указать большее количество, но они должны быть правильно обработаны во время открытия/закрытия и других обратных вызовах. Если необходимо узнать, какой субпоток имеется в виду, его можно получить из структуры данных snd_pcm_substream, передаваемой в каждом обратном вызове, следующим образом:

 

struct snd_pcm_substream *substream;

int index = substream->number;

 

После создания PCM необходимо установить операции для каждого потока PCM.

 

snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,

                &snd_mychip_playback_ops);

snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,

                &snd_mychip_capture_ops);

 

Операции определяются обычно следующим образом:

 

static struct snd_pcm_ops snd_mychip_playback_ops = {

    .open      = snd_mychip_pcm_open,

    .close     = snd_mychip_pcm_close,

    .ioctl     = snd_pcm_lib_ioctl,

    .hw_params = snd_mychip_pcm_hw_params,

    .hw_free   = snd_mychip_pcm_hw_free,

    .prepare   = snd_mychip_pcm_prepare,

    .trigger   = snd_mychip_pcm_trigger,

    .pointer   = snd_mychip_pcm_pointer,

};

 

Все обратные вызовы описаны в подразделе Операции.

 

После установки операций, вы, вероятно, захотите предварительно выделить память для буфера. Для предварительного выделения просто вызовите следующую функцию:

 

snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,

                                      snd_dma_pci_data(chip->pci),

                                      64*1024, 64*1024);

 

По умолчанию она выделяет буфер до 64 КБ. Подробности управления буфером будут описаны дальше в разделе Управление буфером и памятью.

 

В pcm->info_flags можно дополнительно установить некоторую дополнительную информацию для этого PCM. Допустимые значения определяются в <sound/asound.h> как SNDRV_PCM_INFO_XXX, которые используются для указания параметров оборудования (смотрите ниже). Если ваша звуковая микросхема поддерживает только полудуплекс, укажите это следующим образом:

 

pcm->info_flags = SNDRV_PCM_INFO_HALF_DUPLEX;

 

... А деструктор?

Деструктор для экземпляра PCM необходим не всегда. Так как PCM устройство будет освобождаться в кодом центрального уровня автоматически, вы не должны вызывать этот деструктор явно.

 

Деструктор будет необходим, если вы создали специальные внутренние объекты и необходимо их освободить. В таком случае укажите функцию деструктора в pcm->private_free:

 

Пример 5.2. Экземпляр PCM с деструктором

 

static void mychip_pcm_free(struct snd_pcm *pcm)

{

    struct mychip *chip = snd_pcm_chip(pcm);

    /* освобождаем свои данные */

    kfree(chip->my_private_pcm_data);

    /* делаем что-либо ещё */

    ....

}

 

static int __devinit snd_mychip_new_pcm(struct mychip *chip)

{

    struct snd_pcm *pcm;

    ....

    /* выделяем память для своих данных */

    chip->my_private_pcm_data = kmalloc(...);

    /* указываем деструктор */

    pcm->private_data = chip;

    pcm->private_free = mychip_pcm_free;

    ....

}

 

Рабочий указатель - хранение информации PCM

Когда открывается субпоток PCM, создаётся рабочий экземпляр PCM и присваивается субпотоку. Этот указатель доступен через substream->runtime. Это рабочий указатель хранит наибольшее количество информации, необходимой для управления PCM: копию конфигураций hw_params и sw_params, указатели буферов, данные о mmap, спин-блокировки и другое.

 

Определение рабочего экземпляра находится в <sound/pcm.h>. Вот содержимое этого файла:

 

struct _snd_pcm_runtime {

    /* -- Состояние -- */

    struct snd_pcm_substream *trigger_master;

    snd_timestamp_t trigger_tstamp; /* метка времени триггера */

    int overrange;

    snd_pcm_uframes_t avail_max;

    snd_pcm_uframes_t hw_ptr_base; /* Позиция во время перезагрузки буфера */

    snd_pcm_uframes_t hw_ptr_interrupt; /* Позиция во время прерывания */

 

    /* -- параметры оборудования -- */

    snd_pcm_access_t access; /* режим доступа */

    snd_pcm_format_t format; /* SNDRV_PCM_FORMAT_* */

    snd_pcm_subformat_t subformat; /* субформат */

    unsigned int rate; /* частота в Гц */

    unsigned int channels; /* каналы */

    snd_pcm_uframes_t period_size; /* размер периода */

    unsigned int periods; /* периоды */

    snd_pcm_uframes_t buffer_size; /* размер буфера */

    unsigned int tick_time; /* время тика */

    snd_pcm_uframes_t min_align; /* минимальное выравнивание для данного формата */

    size_t byte_align;

    unsigned int frame_bits;

    unsigned int sample_bits;

    unsigned int info;

    unsigned int rate_num;

    unsigned int rate_den;

 

    /* -- программные параметры -- */

    struct timespec tstamp_mode; /* обновляемая метка времени mmap */

    unsigned int period_step;

    unsigned int sleep_min; /* минимальное число тиков для сна */

    snd_pcm_uframes_t start_threshold;

    snd_pcm_uframes_t stop_threshold;

    snd_pcm_uframes_t silence_threshold; /* Если шум меньше этого, происходит заполнение тишиной */

    snd_pcm_uframes_t silence_size; /* размер заполнения тишиной */

    snd_pcm_uframes_t boundary; /* указывает на точку перехода в начало */

 

    snd_pcm_uframes_t silenced_start;

    snd_pcm_uframes_t silenced_size;

 

    snd_pcm_sync_id_t sync; /* идентификатор синхронизации оборудования */

 

    /* -- mmap -- */

    volatile struct snd_pcm_mmap_status *status;

    volatile struct snd_pcm_mmap_control *control;

    atomic_t mmap_count;

 

    /* -- блокировка / планировщик -- */

    spinlock_t lock;

    wait_queue_head_t sleep;

    struct timer_list tick_timer;

    struct fasync_struct *fasync;

 

    /* -- закрытая секция -- */

    void *private_data;

    void (*private_free)(struct snd_pcm_runtime *runtime);

 

    /* -- описание оборудования -- */

    struct snd_pcm_hardware hw;

    struct snd_pcm_hw_constraints hw_constraints;

 

    /* -- обратные вызовы прерывания -- */

    void (*transfer_ack_begin)(struct snd_pcm_substream *substream);

    void (*transfer_ack_end)(struct snd_pcm_substream *substream);

 

    /* -- таймер -- */

    unsigned int timer_resolution; /* точность таймера */

 

    /* -- DMA -- */

    unsigned char *dma_area; /* область DMA */

    dma_addr_t dma_addr; /* адрес физической шины (недоступной основному CPU) */

    size_t dma_bytes; /* размер области DMA */

 

    struct snd_dma_buffer *dma_buffer_p; /* выделенный буфер */

 

#if defined(CONFIG_SND_PCM_OSS) || defined(CONFIG_SND_PCM_OSS_MODULE)

    /* -- относящееся к OSS -- */

    struct snd_pcm_oss_runtime oss;

#endif

};

 

Для операций (обратных вызовов) каждого звукового драйвера большинство из этих записей должны быть только читаемыми. Их изменяет/обновляет только центральный уровень PCM. Исключениями являются: описание оборудования (hw), обратные вызовы прерывания (transfer_ack_xxx), информация о буфере DMA и закрытые данные. Кроме того, при использовании стандартного метода выделения буфера с помощью snd_pcm_lib_malloc_pages() нет необходимости самостоятельно устанавливать информацию о DMA буфере.

 

Важные записи объясняются в следующих разделах.

Параметры оборудования

Дескриптор оборудования (структура snd_pcm_hardware) содержит параметры основной конфигурации оборудования. Прежде всего, необходимо определить их в обратном вызове open. Обратите внимание, рабочий экземпляр имеет копию данного дескриптора, а не указатель на существующий дескриптор. То есть в обратном вызове open можно изменить скопированный дескриптор (runtime->hw), как это требуется. Например, если максимальное число каналов равно 1 только на некоторых моделях чипа, можно по-прежнему использовать один и тот же дескриптор оборудования и изменять channels_max позже:

 

struct snd_pcm_runtime *runtime = substream->runtime;

...

runtime->hw = snd_mychip_playback_hw; /* обычное определение */

if (chip->model == VERY_OLD_ONE)

    runtime->hw.channels_max = 1;

 

Как правило, дескриптор оборудования будет таким, как показано ниже:

 

static struct snd_pcm_hardware snd_mychip_playback_hw = {

    .info = (SNDRV_PCM_INFO_MMAP |

             SNDRV_PCM_INFO_INTERLEAVED |

             SNDRV_PCM_INFO_BLOCK_TRANSFER |

             SNDRV_PCM_INFO_MMAP_VALID),

    .formats          = SNDRV_PCM_FMTBIT_S16_LE,

    .rates            = SNDRV_PCM_RATE_8000_48000,

    .rate_min         = 8000,

    .rate_max         = 48000,

    .channels_min     = 2,

    .channels_max     = 2,

    .buffer_bytes_max = 32768,

    .period_bytes_min = 4096,

    .period_bytes_max = 32768,

    .periods_min      = 1,

    .periods_max      = 1024,

};

 

Поле info содержит тип и возможности этого PCM. Битовые флаги определены в <sound/asound.h> как SNDRV_PCM_INFO_XXX. Здесь, как минимум, необходимо указать, поддерживается ли mmap и какой из форматов  чередования данных поддерживается. Если поддерживается, добавьте здесь флаг SNDRV_PCM_INFO_MMAP. В зависимости от того, поддерживает ли оборудование формат с чередованием или без чередования, должен быть установлен флаг SNDRV_PCM_INFO_INTERLEAVED или SNDRV_PCM_INFO_NONINTERLEAVED, соответственно. Если оба они поддерживаются, можно также установить оба.
 
В приведённом выше примере, для режима OSS mmap указаны MMAP_VALID и BLOCK_TRANSFER. Обычно устанавливаются оба. Конечно, MMAP_VALID устанавливается только если mmap действительно поддерживается.
 
Другими возможными флагами являются SNDRV_PCM_INFO_PAUSE и SNDRV_PCM_INFO_RESUME. Бит PAUSE означает, что этот PCM поддерживает операцию "пауза", а бит RESUME означает, что данный PCM поддерживает полноценную операцию "приостановка/возобновление". Если установлен флаг PAUSE, обратный вызов trigger, описанный ниже, должен обрабатывать соответствующие команды (нажатие/отпускание паузы). Команды триггера приостановка/возобновление могут быть определены даже без флага RESUME. Подробности описаны в разделе Управления питанием.
 
Если субпотоки PCM могут быть синхронизированы (как правило, синхронизированы потоки при начале/остановке воспроизведения и потоки захвата), можно также указать SNDRV_PCM_INFO_SYNC_START. В этом случае необходимо проверить связанный список субпотоков PCM в обратном вызове trigger. Это будет описано в следующем разделе.
 

Поле formats содержит битовые флаги поддерживаемых форматов (SNDRV_PCM_FMTBIT_XXX). Если оборудование поддерживает более одного формата, укажите все, объединив их с помощью ИЛИ. В приведённом выше примере указан знаковый 16-ти разрядный формат с прямым порядком байтов (сначала младший).
 

Поле rates содержит битовые флаги поддерживаемых частот дискретизации (SNDRV_PCM_RATE_XXX). Если чип поддерживает произвольные частоты, дополнительно установите бит CONTINUOUS. Предопределённые биты частот дискретизации предоставляются только для типичных частот. Если ваш чип поддерживает нетрадиционные частоты, необходимо добавить бит KNOT и настроить ограничение оборудования вручную (объяснено далее).
 

rate_min и rate_max определяют минимальную и максимальную частоту дискретизации. Они должны соответствовать битам, описывающим частоты дискретизации.
 

channel_min и channel_max определяют, как вы могли уже ожидать, минимальное и максимальное количество каналов.
 

buffer_bytes_max определяет максимальный размер буфера в байтах. Не существует поля buffer_bytes_min, так как оно может быть вычислено исходя из минимального размера периода и минимального числа периодов. Таким образом, period_bytes_min и определяет минимальный и максимальный размер периода в байтах. periods_max и periods_min определяют максимальное и минимальное число периодов в буфере.
 
"Период" - это термин, соответствующий в мире OSS фрагменту. Период определяет частоту с которой генерируются прерывания PCM. Эта частота сильно зависит от оборудования. Вообще, меньший размер периода даст вам большее число прерываний, то есть более точное управление. В случае захвата этот размер определяет входную задержку. С другой стороны, выходную задержку при воспроизведении определяет размер полного буфера.
 

Существует также поле fifo_size. Оно определяет размер аппаратного буфера FIFO, но в настоящее время оно не используется ни в драйвере, ни в ALSA-Lib. Таким образом, вы можете игнорировать это поле.

 

Конфигурации PCM

Итак, давайте снова вернёмся к полям рабочего указателя PCM. Наиболее часто упоминаемыми полями в рабочем экземпляре являются конфигурации PCM. Конфигурации PCM сохраняются в рабочем экземпляре после того, как приложение отправляет данные hw_params через ALSA-Lib. Многие полей скопированы из структур hw_params и sw_params. Например, format хранит тип формата, выбранного приложением. Это поле содержит значение перечисления SNDRV_PCM_FORMAT_XXX.

 

Единственно, следует отметить, что во время выполнения размеры настроенного буфера и периода хранятся в "кадрах" . В мире ALSA 1 кадр = число каналов * размер сэмпла. Для преобразования между кадрами и байтами можно использовать вспомогательные функции frames_to_bytes() и bytes_to_frames().

 

period_bytes = frames_to_bytes(runtime, runtime->period_size);

 

Кроме того, в кадрах так же хранятся многие программные параметры (sw_params). Пожалуйста, проверяйте тип поля. Для кадров как целое число без знака используется snd_pcm_uframes_t, а как целое знаковое число - snd_pcm_sframes_t.

Информация о буфере DMA

Буфер DMA определяется следующими четырьмя полями: dma_area, dma_addr, dma_bytes и dma_private. dma_area хранит указатель на буфер (логический адрес). Для копирования из/в этот указатель можно вызывать memcpy. Физический адрес буфера хранит dma_addr. Это поле указывается только когда буфер является линейным буфером. dma_bytes хранит размер буфера в байтах. dma_private используется для распределителя памяти ALSA DMA.

 

Если для выделения буфера вы используете стандартную функцию ALSA, snd_pcm_lib_malloc_pages(), эти поля устанавливаются центральным уровнем ALSA и вы не должны изменять их самостоятельно. Вы можете читать их, но не записывать в них. С другой стороны, если вы хотите выделять буфер самостоятельно, вам необходимо управлять им в обратном вызове hw_params. Обязательным является, по крайней мере, dma_bytes. dma_area необходимо, если буфер используется для mmap. Если ваш драйвер не поддерживает mmap, это поле не является необходимым. dma_addr также является необязательным. Вы также можете использовать по своему желанию dma_private.

Статус выполнения

Статус выполнения может быть передан с помощью runtime->status. Это указатель на структуру snd_pcm_mmap_status. Например, можно получить текущий аппаратный указатель DMA с помощью runtime->status->hw_ptr.

 

Указатель DMA приложения может быть получен с помощью runtime->control, который указывает на структуру snd_pcm_mmap_control. Однако, непосредственный доступ к этому параметру не рекомендуется.

Закрытые данные

Вы можете выделить объект для субпотока и сохранить его в runtime->private_data. Обычно это делается в обратном вызове open. Не путайте его с pcm->private_data. pcm->private_data обычно указывает на экземпляр статически созданного  объекта чипа при создании PCM, а runtime->private_data указывает на динамическую структуру данных, созданную в обратном вызове PCM open.

 

static int snd_xxx_open(struct snd_pcm_substream *substream)

{

    struct my_pcm_data *data;

    ....

    data = kmalloc(sizeof(*data), GFP_KERNEL);

    substream->runtime->private_data = data;

    ....

}

 

Выделенный объект должен быть освобождён в обратном вызове close.

Обратные вызовы прерывания

Функция transfer_ack_begin и transfer_ack_end вызывается в начале и в конце snd_pcm_period_elapsed(), соответственно.

Операции

Итак, теперь позвольте представить подробную информацию о каждом обратном вызове PCM (ops). В общем, каждый обратный вызов должен возвращать 0 в случае успеха или отрицательное число ошибки, такое, как -EINVAL. Для выбора подходящего номера ошибки рекомендуется проверить, какие значения возвращают другие части ядра, когда такой же вид запроса оканчивается неудачей.

 

Функции обратного вызова требуется по крайней мере один аргумент с указателем snd_pcm_substream. Чтобы получить объект чипа из данного экземпляра субпотока, можно использовать следующий макрос:

 

int xxx() {

    struct mychip *chip = snd_pcm_substream_chip(substream);

    ....

}

 

Макрос читает substream->private_data, которые являются копией pcm->private_data. Вы можете изменить substream->private_data, если  необходимо указать другие значения данных для субпотока PCM. Например, драйвер cmi8330 назначает  другие private_data для воспроизведения и захвата, потому что он использует для воспроизведения и захвата два разных кодека (SB- и AD-совместимый).

Обратный вызов open

 

static int snd_xxx_open(struct snd_pcm_substream *substream);

 

Он вызывается, когда субпоток PCM открывается.

 

Здесь вы как минимум должны инициализировать параметр runtime->hw. Как правило, это делается так:

 

static int snd_xxx_open(struct snd_pcm_substream *substream)

{

    struct mychip *chip = snd_pcm_substream_chip(substream);

    struct snd_pcm_runtime *runtime = substream->runtime;

 

    runtime->hw = snd_mychip_playback_hw;

    return 0;

}

 

где snd_mychip_playback_hw является заранее сделанным описанием оборудования.

 

В этом обратном вызове можно выделить память для закрытых данных, как описано в разделе Закрытые данные.

 

Если конфигурация оборудования требует больше ограничений, установите эти аппаратные ограничения здесь. Для более подробной информации смотрите Ограничения.

Обратный вызов close

 

static int snd_xxx_close(struct snd_pcm_substream *substream);

 

Очевидно, что он вызывается, когда субпоток PCM закрывается.

 

Любой экземпляр закрытых данных для субпотока PCM, созданный в обратном вызове open, будет здесь освобождаться.

 

static int snd_xxx_close(struct snd_pcm_substream *substream)

{

    ....

    kfree(substream->runtime->private_data);

    ....

}

 

Обратный вызов ioctl

Он используется для всех специальных вызовов ioctl PCM. Но обычно можно передавать универсальный обратный вызов ioctl, snd_pcm_lib_ioctl.

Обратный вызов hw_params

 

static int snd_xxx_hw_params(struct snd_pcm_substream *substream,

                             struct snd_pcm_hw_params *hw_params);

 

Он вызывается, когда приложением устанавливается параметр оборудования (hw_params), то есть после того, как для субпотока PCM определены размер буфера, величина периода, формат и другие.

 

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

 

Параметры для инициализации извлекаются макросом params_xxx(). Чтобы выделить буфер, можно вызвать вспомогательную функцию,

 

snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));

 

snd_pcm_lib_malloc_pages() доступна только если буферы DMA были выделены заранее. Для более подробной информации смотрите раздел Типы буферов.

 

Заметим, что этот обратный вызов и prepare могут быть вызваны для инициализации несколько раз. Например, эмуляция OSS может вызывать эти обратные вызовы при всех изменениях через его ioctl.

 

Таким образом, вы должны быть осторожны и не выделять одни и те же буферы много раз, что приведёт к утечкам памяти! Вызов вспомогательной функции во много раз лучше. Он освободит предыдущий буфер автоматически, если он уже был выделен.

 

Обратите также внимание, что это обратный вызов не атомарный (планируемый). Это важно, потому что обратный вызов trigger является атомарным (не планируемым). То есть в обратном вызове trigger мьютексы или любые другие функции, связанные с планировщиком, недоступны. Подробности смотрите в подразделе Атомарность.

Обратный вызов hw_free

 

static int snd_xxx_hw_free(struct snd_pcm_substream *substream);

 

Он вызывается для освобождения ресурсов, выделенных через hw_params. Например, освобождение буфера, выделенного через snd_pcm_lib_malloc_pages(), осуществляется следующим вызовом:

 

snd_pcm_lib_free_pages(substream);

 

Эта функция всегда вызывается перед вызовом обратного вызова close. Кроме того, этот метод так же может быть вызван несколько раз. Проверяйте, не был ли ресурс уже освобождён.

Обратный вызов prepare

 

static int snd_xxx_prepare(struct snd_pcm_substream *substream);

 

Этот обратный вызов вызывается, когда PCM является "подготавливаемым". Здесь можно установить тип формата, частоту дискретизации и так далее. Отличие от hw_params в том, что обратный вызов prepare будет вызываться каждый раз, когда вызывается snd_pcm_prepare(), то есть при восстановлении после опустошения буфера и так далее.

 

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

 

В этом и следующих обратных вызовах можно обращаться к параметрам с помощью объекта runtime, substream->runtime. Например, чтобы получить текущую частоту дискретизации, формат или число каналов, обращаясь к runtime->rate, runtime->format или runtime->channels, соответственно. Физический адрес выделенного буфера находится в runtime->dma_area.  Размеры буфера и периода находятся в runtime->buffer_size и runtime->period_size, соответственно.

 

Будьте осторожны, потому что этот обратный вызов тоже будет вызываться много раз при каждой установке параметров.

Обратный вызов trigger

 

static int snd_xxx_trigger(struct snd_pcm_substream *substream, int cmd);

 

Он вызывается, когда PCM запускается, останавливается или приостанавливается (ставится в паузу).

 

Действие указывается во втором аргументе, SNDRV_PCM_TRIGGER_XXX в <sound/pcm.h>. В этом обратном вызове должны быть определены по крайней мере команды START и STOP.

 

switch (cmd) {

case SNDRV_PCM_TRIGGER_START:

    /* делаем что-нибудь для запуска движка PCM */

    break;

case SNDRV_PCM_TRIGGER_STOP:

    /* делаем что-нибудь для остановки движка PCM */

    break;

default:

    return -EINVAL;

}

 

Если PCM поддерживает операцию "пауза" (указываемую в поле info при описании оборудования), здесь также должны быть обработаны команды PAUSE_PUSE и PAUSE_RELEASE. Первая является командой приостановки PCM, а последняя - запуском PCM снова.

 

Если PCM поддерживает операцию приостановить/возобновить, независимо от полной или частичной поддержки приостановки/возобновления также должны быть обработаны команды SUSPEND и RESUME. Эти команды выдаются, когда изменяется статус управления питанием. Очевидно, что команды SUSPEND и RESUME приостанавливают и возобновляют субпоток PCM, и, как правило, они идентичны командам STOP и START, соответственно. Подробности смотрите в разделе Управление питанием.

 

Как упоминалось, этот обратный вызов является атомарным. Нельзя вызывать функции, которые могут заснуть. Обратный вызов trigger должен быть настолько малым, как возможно, действительно только переключая DMA. Остальное должно быть  правильно проинициализировано заранее обратными вызовами hw_params и prepare.

Обратный вызов pointer

 

static snd_pcm_uframes_t snd_xxx_pointer(struct snd_pcm_substream *substream)

 

Этот обратный вызов вызывается, когда центральный уровень PCM запрашивает текущее положение в аппаратном буфере. Позиция должна быть возвращена в кадрах, в диапазоне от 0 до buffer_size - 1.

 

Он обычно вызывается из процедуры обновления буфера в центральном уровне PCM, которая вызывается, если в процедуре обработки прерывания вызывается snd_pcm_period_elapsed(). Затем центральный уровень PCM обновляет позицию и рассчитывает свободное пространство, будит спящие потоки опроса и так далее.

 

Этот обратный вызов также атомарен.

Обратные вызовы copy и silence

Эти обратные вызовы не являются обязательными и в большинстве случаев могут быть опущены. Эти обратные вызовы используются, когда аппаратный буфер не может быть в обычном пространстве памяти. Некоторые чипы имеют свой собственный аппаратный буфер, который не является отображаемым. В таком случае, необходимо передавать данные вручную из буфера в памяти в аппаратный буфер. Эти обратные вызовы также должны быть определены в случае, если буфер не непрерывен и в физическом, и виртуальном пространстве памяти.

 

Если эти два обратных вызова определены, ими выполняются операции копирования и установки тишины. Подробности будут описаны позже в разделе Управление буфером и памятью.

Обратный вызов ack

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

 

Этот обратный вызов атомарен.

Обратный вызов page

Этот обратный вызов тоже не является обязательным. Этот обратный вызов используется в основном для не непрерывных буферов. mmap делает этот обратный вызов, чтобы получить адрес страницы. Некоторые примеры будут также объяснены позже в разделе Управление буфером и памятью.

Обработчик прерывания

Последней составляющей PCM является обработчик прерывания PCM. Роль обработчика прерывания PCM в звуковом драйвере - обновление позиции в буфере и информирование центрального уровня PCM, когда позиция буфера проходит через установленный размер периода. Чтобы сообщить это, вызывается функция snd_pcm_period_elapsed().

 

Есть несколько типов звуковых чипов для генерации прерываний.

Прерывания на границе периода (фрагмента)

Это наиболее часто встречающийся тип: оборудование генерирует прерывание на границе каждого периода. В этом случае можно вызывать snd_pcm_period_elapsed() в каждом прерывании.

 

snd_pcm_period_elapsed() принимает в качестве аргумента указатель субпотока. Таким образом, необходимо сохранять указатель субпотока доступным из экземпляра чипа. Например, определите поле substream в объекте чипа для хранения рабочего указателя субпотока, и установите значение указателя в обратном вызове open (и очистите в обратном вызове close).

 

Если вы захватили спин-блокировку в обработчике прерывания и эта блокировка также используется в других обратных вызовах PCM, то вы должны снять блокировку перед вызовом snd_pcm_period_elapsed(), потому что snd_pcm_period_elapsed() вызывает внутри другие функции обратного вызова PCM.

 

Типичный код будет таким:

 

Пример 5.3. Случай обработчика прерываний #1

 

static irqreturn_t snd_mychip_interrupt(int irq, void *dev_id)

{

    struct mychip *chip = dev_id;

    spin_lock(&chip->lock);

    ....

    if (pcm_irq_invoked(chip)) {

        /* вызов обновления, перед этим снимаем блокировку */

        spin_unlock(&chip->lock);

        snd_pcm_period_elapsed(chip->substream);

        spin_lock(&chip->lock);

        /* подтверждение прерывания, если необходимо */

    }

    ....

    spin_unlock(&chip->lock);

    return IRQ_HANDLED;

}

 

Прерывания по высокочастотному таймеру

Это происходит, когда оборудование не может генерировать прерывания на границе периода, но выдаёт прерывания по таймеру на фиксированной частоте таймера (например, драйверы es1968 или ymfpci). В этом случае во время каждого прерывания необходимо проверять текущую позицию оборудования и накапливать длины обрабатываемых кусочков. Когда накопленный размер превысит размер периода, вызовите snd_pcm_period_elapsed() и сбросьте значение накопителя.

 

Типичный код был бы примерно таким.

 

Пример 5.4. Случай обработчика прерываний #2

 

static irqreturn_t snd_mychip_interrupt(int irq, void *dev_id)

{

    struct mychip *chip = dev_id;

    spin_lock(&chip->lock);

    ....

    if (pcm_irq_invoked(chip)) {

        unsigned int last_ptr, size;

        /* получаем текущее значение указателя оборудования (в кадрах) */

        last_ptr = get_hw_ptr(chip);

        /* вычисляем число обработанных кадров со времени

         * последнего обновления

         */

        if (last_ptr < chip->last_ptr)

            size = runtime->buffer_size + last_ptr

                    - chip->last_ptr;

        else

            size = last_ptr - chip->last_ptr;

        /* запоминаем последнюю точку обновления */

        chip->last_ptr = last_ptr;

        /* накапливаем длину */

        chip->size += size;

        /* граница периода превышена? */

        if (chip->size >= runtime->period_size) {

            /* сбрасываем значение накопителя */

            chip->size %= runtime->period_size;

            /* вызываем функцию обновления */

            spin_unlock(&chip->lock);

            snd_pcm_period_elapsed(substream);

            spin_lock(&chip->lock);

        }

        /* подтверждение прерывания, если необходимо */

    }

    ....

    spin_unlock(&chip->lock);

    return IRQ_HANDLED;

}

 

О вызове snd_pcm_period_elapsed()

В обоих случаях, даже если истёк более, чем один период, вы не должны вызывать snd_pcm_period_elapsed() много раз.  Вызывайте только один раз. А уровень PCM проверит текущий указатель оборудования и обновит до последнего значения статуса.

Атомарность

Одной из наиболее важных (и, таким образом, трудной для отлаживания) проблем в программировании ядра является состояние гонок. В ядре Linux, они, как правило, избегаются с помощью спин-блокировок, мьютексов или семафоров. В общем, если в обработчике прерывания может произойти состояние гонок, он должен быть управляемым атомарно, а для защиты критических секций вы должны использовать спин-блокировки. Если критическая секция не является кодом обработчика прерывания и если приемлемо относительно большое время выполнения, вы должны взамен использовать мьютексы или семафоры.

 

Как уже говорилось, некоторые обратные вызовы PCM являются атомарными, а некоторые - нет. Например, обратный вызов hw_params не является атомарным, а обратный вызов trigger - атомарный. Это означает, что последний вызывается при уже  удерживаемой центральным уровнем PCM спин-блокировке. Пожалуйста, примите эту атомарность во внимание при выборе схемы блокировки в обратных вызовах.

 

В атомарных обратных вызовах вы не можете использовать функции, которые могут вызывать планировщик или заснуть. Семафоры и мьютексы могут спать, и, следовательно, они не могут быть использованы внутри атомарных обратных вызовах (например, в обратном вызове trigger). Для реализации какой-либо задержки в таком обратном вызове, пожалуйста, используйте udelay() или mdelay().

 

Все три атомарные обратные вызовы (trigger, pointer и ack) вызываются с запрещёнными локальными прерываниями.

Ограничения

Если ваш чип поддерживает нетрадиционные частоты дискретизации или только некоторые типы сэмплов, необходимо установить ограничение для использования.

 

Например, чтобы ограничить частоты дискретизации до некоторых поддерживаемых значений, используйте snd_pcm_hw_constraint_list(). Вы должны вызвать эту функцию в обратном вызове open.

 

Пример 5.5. Пример аппаратных ограничений

 

static unsigned int rates[] =

    {4000, 10000, 22050, 44100};

static struct snd_pcm_hw_constraint_list constraints_rates = {

    .count = ARRAY_SIZE(rates),

    .list = rates,

    .mask = 0,

};

 

static int snd_mychip_pcm_open(struct snd_pcm_substream *substream)

{

    int err;

    ....

    err = snd_pcm_hw_constraint_list(substream->runtime, 0,

                                     SNDRV_PCM_HW_PARAM_RATE,

                                     &constraints_rates);

    if (err < 0)

        return err;

    ....

}

 

Есть много разных ограничений. Для получения полного списка посмотрите в sound/pcm.h. Вы даже можете определить свои собственные ограничивающие правила. Например, предположим, что my_chip может управлять субпотоком из 1 канала, только если форматом является S16_LE, в противном случае он поддерживает любой формат, указанный в структуре snd_pcm_hardware (или в любом другом constraint_list). Вы можете создать правило так:

 

Пример 5.6. Пример аппаратных ограничений для каналов

 

static int hw_rule_format_by_channels(struct snd_pcm_hw_params *params,

                                      struct snd_pcm_hw_rule *rule)

{

    struct snd_interval *c = hw_param_interval(params,

                SNDRV_PCM_HW_PARAM_CHANNELS);

    struct snd_mask *f = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);

    struct snd_mask fmt;

 

    snd_mask_any(&fmt); /* Инициализируем структуру */

    if (c->min < 2) {

        fmt.bits[0] &= SNDRV_PCM_FMTBIT_S16_LE;

        return snd_mask_refine(f, &fmt);

    }

    return 0;

}

 

Затем необходимо вызвать эту функцию, чтобы добавить правило:

 

snd_pcm_hw_rule_add(substream->runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,

                    hw_rule_channels_by_format, 0, SNDRV_PCM_HW_PARAM_FORMAT,

                    -1);

 

Функция установки правила вызывается, когда приложение устанавливает число каналов. Но приложение может установить формат перед числом каналов. Таким образом, необходимо также определить обратное правило:

 

Пример 5.7. Пример аппаратных ограничений для каналов

 

static int hw_rule_channels_by_format(struct snd_pcm_hw_params *params,

                                      struct snd_pcm_hw_rule *rule)

{

    struct snd_interval *c = hw_param_interval(params,

                    SNDRV_PCM_HW_PARAM_CHANNELS);

    struct snd_mask *f = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT);

    struct snd_interval ch;

 

    snd_interval_any(&ch);

    if (f->bits[0] == SNDRV_PCM_FMTBIT_S16_LE) {

        ch.min = ch.max = 1;

        ch.integer = 1;

        return snd_interval_refine(c, &ch);

    }

    return 0;

}

 

... а в обратном вызове open:

 

snd_pcm_hw_rule_add(substream->runtime, 0, SNDRV_PCM_HW_PARAM_FORMAT,

                    hw_rule_format_by_channels, 0, SNDRV_PCM_HW_PARAM_CHANNELS,

                    -1);

 

Я не буду давать здесь более подробную информацию, сказав, "используйте исходники".

 

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