Skip to content

Instantly share code, notes, and snippets.

@takaswie
Last active June 23, 2024 12:54
Show Gist options
  • Save takaswie/d8a9553c230971d974a3dfc759b2ff67 to your computer and use it in GitHub Desktop.
Save takaswie/d8a9553c230971d974a3dfc759b2ff67 to your computer and use it in GitHub Desktop.
ALSA PCM application to utilize intermediate buffer inner kernel space
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <stdint.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <string.h>
#include <linux/version.h>
#include <sound/asound.h>
#define MY_SAMPLE_FORMAT SNDRV_PCM_FORMAT_S16_LE
#define MY_SAMPLES_PER_FRAME 2
#define MY_FRAMES_PER_SECOND 48000
#define MY_PLAYBACK_DURATION 1
#define ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0]))
void print_help(const char *prog_name)
{
printf("%s CDEV\n", prog_name);
printf(" CDEV: The path to ALSA PCM character device.\n");
}
static const char *const class_labels[] = {
[SNDRV_PCM_CLASS_GENERIC] = "generic",
[SNDRV_PCM_CLASS_MULTI] = "multi",
[SNDRV_PCM_CLASS_MODEM] = "modem",
[SNDRV_PCM_CLASS_DIGITIZER] = "digitizer"
};
static const char *const subclass_labels[] = {
[SNDRV_PCM_SUBCLASS_GENERIC_MIX] = "generic-mix",
[SNDRV_PCM_SUBCLASS_MULTI_MIX] = "multi-mix"
};
static const char *const direction_labels[] = {
[SNDRV_PCM_STREAM_PLAYBACK] = "playback",
[SNDRV_PCM_STREAM_CAPTURE] = "capture"
};
static const char *const param_labels[] = {
[SNDRV_PCM_HW_PARAM_ACCESS] = "access",
[SNDRV_PCM_HW_PARAM_FORMAT] = "format",
[SNDRV_PCM_HW_PARAM_SUBFORMAT] = "subformat",
[SNDRV_PCM_HW_PARAM_SAMPLE_BITS] = "sample-bits",
[SNDRV_PCM_HW_PARAM_FRAME_BITS] = "frame-bits",
[SNDRV_PCM_HW_PARAM_CHANNELS] = "channels",
[SNDRV_PCM_HW_PARAM_RATE] = "rate",
[SNDRV_PCM_HW_PARAM_PERIOD_TIME] = "period-time",
[SNDRV_PCM_HW_PARAM_PERIOD_SIZE] = "period-size",
[SNDRV_PCM_HW_PARAM_PERIOD_BYTES] = "period-bytes",
[SNDRV_PCM_HW_PARAM_PERIODS] = "periods",
[SNDRV_PCM_HW_PARAM_BUFFER_TIME] = "buffer-time",
[SNDRV_PCM_HW_PARAM_BUFFER_SIZE] = "buffer-size",
[SNDRV_PCM_HW_PARAM_BUFFER_BYTES] = "buffer-bytes",
[SNDRV_PCM_HW_PARAM_TICK_TIME] = "tick-time",
};
static const char *const access_labels[] = {
[SNDRV_PCM_ACCESS_MMAP_INTERLEAVED] = "mmap-interleaved",
[SNDRV_PCM_ACCESS_MMAP_NONINTERLEAVED] = "mmap-noninterleaved",
[SNDRV_PCM_ACCESS_MMAP_COMPLEX] = "mmap-complex",
[SNDRV_PCM_ACCESS_RW_INTERLEAVED] = "readwrite-interleaved",
[SNDRV_PCM_ACCESS_RW_NONINTERLEAVED] = "readwrite-noninterleaved",
};
static const char *const format_labels[] = {
[SNDRV_PCM_FORMAT_S8] = "s8",
[SNDRV_PCM_FORMAT_U8] = "u8",
[SNDRV_PCM_FORMAT_S16_LE] = "s16-le",
[SNDRV_PCM_FORMAT_S16_BE] = "s16-be",
[SNDRV_PCM_FORMAT_U16_LE] = "u16-le",
[SNDRV_PCM_FORMAT_U16_BE] = "u16-be",
[SNDRV_PCM_FORMAT_S24_LE] = "s24-le",
[SNDRV_PCM_FORMAT_S24_BE] = "s24-be",
[SNDRV_PCM_FORMAT_U24_LE] = "u24-le",
[SNDRV_PCM_FORMAT_U24_BE] = "u24-be",
[SNDRV_PCM_FORMAT_S32_LE] = "s32-le",
[SNDRV_PCM_FORMAT_S32_BE] = "s32-be",
[SNDRV_PCM_FORMAT_U32_LE] = "u32-le",
[SNDRV_PCM_FORMAT_U32_BE] = "u32-be",
[SNDRV_PCM_FORMAT_FLOAT_LE] = "float-le",
[SNDRV_PCM_FORMAT_FLOAT_BE] = "float-be",
[SNDRV_PCM_FORMAT_FLOAT64_LE] = "float64-be",
[SNDRV_PCM_FORMAT_FLOAT64_BE] = "float64-le",
[SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE] = "iec958subframe-le",
[SNDRV_PCM_FORMAT_IEC958_SUBFRAME_BE] = "iec958subframe-be",
[SNDRV_PCM_FORMAT_MU_LAW] = "mu-law",
[SNDRV_PCM_FORMAT_A_LAW] = "a-law",
[SNDRV_PCM_FORMAT_IMA_ADPCM] = "ima-adpcm",
[SNDRV_PCM_FORMAT_MPEG] = "mpg",
[SNDRV_PCM_FORMAT_GSM] = "gsm",
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,16,0)
[SNDRV_PCM_FORMAT_S20_LE] = "s20-le",
[SNDRV_PCM_FORMAT_S20_BE] = "s20-be",
[SNDRV_PCM_FORMAT_U20_LE] = "u20-le",
[SNDRV_PCM_FORMAT_U20_BE] = "u20-be",
#endif
/* Entries for 25-30 are absent. */
[SNDRV_PCM_FORMAT_SPECIAL] = "special",
[SNDRV_PCM_FORMAT_S24_3LE] = "s24-3le",
[SNDRV_PCM_FORMAT_S24_3BE] = "s24-3be",
[SNDRV_PCM_FORMAT_U24_3LE] = "u24-3le",
[SNDRV_PCM_FORMAT_U24_3BE] = "u24-3be",
[SNDRV_PCM_FORMAT_S20_3LE] = "s20-3le",
[SNDRV_PCM_FORMAT_S20_3BE] = "s20-3be",
[SNDRV_PCM_FORMAT_U20_3LE] = "u20-3le",
[SNDRV_PCM_FORMAT_U20_3BE] = "u20-3be",
[SNDRV_PCM_FORMAT_S18_3LE] = "s18-3le",
[SNDRV_PCM_FORMAT_S18_3BE] = "s18-3be",
[SNDRV_PCM_FORMAT_U18_3LE] = "u18-3le",
[SNDRV_PCM_FORMAT_U18_3BE] = "u18-3be",
[SNDRV_PCM_FORMAT_G723_24] = "g723-24",
[SNDRV_PCM_FORMAT_G723_24_1B] = "g723-241b",
[SNDRV_PCM_FORMAT_G723_40] = "g723-40",
[SNDRV_PCM_FORMAT_G723_40_1B] = "g723-401b",
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,10,0)
[SNDRV_PCM_FORMAT_DSD_U8] = "dsd-u8",
[SNDRV_PCM_FORMAT_DSD_U16_LE] = "dsd-u16-le",
#endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,18,0)
[SNDRV_PCM_FORMAT_DSD_U32_LE] = "dsd-u32-le",
[SNDRV_PCM_FORMAT_DSD_U16_BE] = "dsd-u16-be",
[SNDRV_PCM_FORMAT_DSD_U32_BE] = "dsd-u23-be",
#endif
};
static const char *const subformat_labels[] = {
[SNDRV_PCM_SUBFORMAT_STD] = "std",
};
static const char *const flag_labels[] = {
[0] = "noresample",
[1] = "export-buffer",
[2] = "no-period-wakeup",
};
static const char *const info_labels[] = {
[0] = "mmap",
[1] = "mmap-valid",
[2] = "double",
[3] = "batch",
[4] = "interleaved",
[5] = "non-interleaved",
[6] = "complex",
[7] = "block-transfer",
[8] = "overrange",
[9] = "resume",
[10] = "pause",
[11] = "half-duplex",
[12] = "joint-duplex",
[13] = "sync-start",
[14] = "no-period-wakeup",
[15] = "has-wall-clock",
[16] = "has-link-atime",
[17] = "has-link-absolute-atime",
[18] = "has-link-estimated-atime",
[19] = "has-link-synchronized-atime",
/* Does not be disclosed to userspace. */
[20] = "drain-trigger",
[21] = "fifo-in-frames",
};
static const int info_flags[] = {
[0] = SNDRV_PCM_INFO_MMAP,
[1] = SNDRV_PCM_INFO_MMAP_VALID,
[2] = SNDRV_PCM_INFO_DOUBLE,
[3] = SNDRV_PCM_INFO_BATCH,
[4] = SNDRV_PCM_INFO_INTERLEAVED,
[5] = SNDRV_PCM_INFO_NONINTERLEAVED,
[6] = SNDRV_PCM_INFO_COMPLEX,
[7] = SNDRV_PCM_INFO_BLOCK_TRANSFER,
[8] = SNDRV_PCM_INFO_OVERRANGE,
[9] = SNDRV_PCM_INFO_RESUME,
[10] = SNDRV_PCM_INFO_PAUSE,
[11] = SNDRV_PCM_INFO_HALF_DUPLEX,
[12] = SNDRV_PCM_INFO_JOINT_DUPLEX,
[13] = SNDRV_PCM_INFO_SYNC_START,
[14] = SNDRV_PCM_INFO_NO_PERIOD_WAKEUP,
[15] = SNDRV_PCM_INFO_HAS_WALL_CLOCK,
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,1,0)
[16] = SNDRV_PCM_INFO_HAS_LINK_ATIME,
[17] = SNDRV_PCM_INFO_HAS_LINK_ABSOLUTE_ATIME,
[18] = SNDRV_PCM_INFO_HAS_LINK_ESTIMATED_ATIME,
[19] = SNDRV_PCM_INFO_HAS_LINK_SYNCHRONIZED_ATIME,
#endif
/* Does not be disclosed to userspace. */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,0,0)
[20] = SNDRV_PCM_INFO_DRAIN_TRIGGER,
#endif
[21] = SNDRV_PCM_INFO_FIFO_IN_FRAMES,
};
static const char *const state_labels[] = {
[SNDRV_PCM_STATE_OPEN] = "open",
[SNDRV_PCM_STATE_SETUP] = "setup",
[SNDRV_PCM_STATE_PREPARED] = "prepared",
[SNDRV_PCM_STATE_RUNNING] = "running",
[SNDRV_PCM_STATE_XRUN] = "xrun",
[SNDRV_PCM_STATE_DRAINING] = "draining",
[SNDRV_PCM_STATE_PAUSED] = "paused",
[SNDRV_PCM_STATE_SUSPENDED] = "suspended",
[SNDRV_PCM_STATE_DISCONNECTED] = "disconnected",
};
static unsigned int get_mask_count(void)
{
return SNDRV_PCM_HW_PARAM_LAST_MASK - SNDRV_PCM_HW_PARAM_FIRST_MASK + 1;
}
static unsigned int get_interval_count(void)
{
return SNDRV_PCM_HW_PARAM_LAST_INTERVAL - SNDRV_PCM_HW_PARAM_FIRST_INTERVAL + 1;
}
static unsigned int mask_index_to_type(unsigned int index)
{
return index + SNDRV_PCM_HW_PARAM_FIRST_MASK;
}
static unsigned int interval_index_to_type(unsigned int index)
{
return index + SNDRV_PCM_HW_PARAM_FIRST_INTERVAL;
}
static unsigned int mask_type_to_index(unsigned int type)
{
return type - SNDRV_PCM_HW_PARAM_FIRST_MASK;
}
static unsigned int interval_type_to_index(unsigned int type)
{
return type - SNDRV_PCM_HW_PARAM_FIRST_INTERVAL;
}
static struct snd_mask *refer_mask(struct snd_pcm_hw_params *params, unsigned int type)
{
return &params->masks[mask_type_to_index(type)];
}
static struct snd_interval *refer_interval(struct snd_pcm_hw_params *params, unsigned int type)
{
return &params->intervals[interval_type_to_index(type)];
}
static void change_mask(struct snd_pcm_hw_params *params, unsigned int type, unsigned int pos,
bool enable, bool exclusive)
{
unsigned int index = pos / sizeof(params->masks[0].bits[0]);
unsigned int offset = pos % sizeof(params->masks[0].bits[0]);
struct snd_mask *mask = refer_mask(params, type);
if (exclusive) {
int i;
for (i = 0; i < ARRAY_SIZE(mask->bits); ++i)
mask->bits[i] = 0x00;
}
mask->bits[index] &= ~(1 << offset);
if (enable)
mask->bits[index] |= (1 << offset);
params->rmask |= 1 << type;
}
static void change_interval(struct snd_pcm_hw_params *params, unsigned int type,
unsigned int min, unsigned int max, bool openmin, bool openmax,
bool integer, bool empty)
{
struct snd_interval *interval = refer_interval(params, type);
interval->min = min;
interval->max = max;
interval->openmin = (int)openmin;
interval->openmax = (int)openmax;
interval->integer = (int)integer;
interval->empty = (int)empty;
params->rmask |= 1 << type;
}
static void dump_mask_param(const struct snd_pcm_hw_params *hw_params,
unsigned int type, const char *const labels[],
unsigned int label_entries)
{
const struct snd_mask *mask;
unsigned int index;
int i, j;
if (type < SNDRV_PCM_HW_PARAM_FIRST_MASK ||
type > SNDRV_PCM_HW_PARAM_LAST_MASK ||
type >= ARRAY_SIZE(param_labels))
return;
printf(" %s:\n", param_labels[type]);
mask = refer_mask((struct snd_pcm_hw_params *)hw_params, type);
for (i = 0; i < ARRAY_SIZE(mask->bits); ++i) {
for (j = 0; j < sizeof(mask->bits[0]) * 8; ++j) {
index = i * sizeof(mask->bits[0]) * 8 + j;
if (index >= label_entries)
return;
if ((mask->bits[i] & (1 << j)) && labels[index] != NULL)
printf(" %s\n", labels[index]);
}
}
}
static void dump_interval_param(const struct snd_pcm_hw_params *hw_params,
unsigned int type)
{
const struct snd_interval *interval;
if (type < SNDRV_PCM_HW_PARAM_FIRST_INTERVAL ||
type > SNDRV_PCM_HW_PARAM_LAST_INTERVAL ||
type >= ARRAY_SIZE(param_labels))
return;
printf(" %s:\n", param_labels[type]);
interval = refer_interval((struct snd_pcm_hw_params *)hw_params, type);
printf(" %c%u, %u%c, ",
interval->openmin ? '(' : '[', interval->min,
interval->max, interval->openmax ? ')' : ']');
if (interval->integer > 0)
printf("integer, ");
if (interval->empty > 0)
printf("empty, ");
printf("\n");
}
static int dump_hw_params_cap(const struct snd_pcm_hw_params *hw_params)
{
int i;
printf(" Changed parameters:\n");
for (i = 0; i < ARRAY_SIZE(param_labels); ++i) {
if (hw_params->cmask & (1 << i))
printf(" %s\n", param_labels[i]);
}
printf(" Runtime parameters:\n");
dump_mask_param(hw_params, SNDRV_PCM_HW_PARAM_ACCESS, access_labels,
ARRAY_SIZE(access_labels));
dump_mask_param(hw_params, SNDRV_PCM_HW_PARAM_FORMAT, format_labels,
ARRAY_SIZE(format_labels));
dump_mask_param(hw_params, SNDRV_PCM_HW_PARAM_SUBFORMAT, subformat_labels,
ARRAY_SIZE(subformat_labels));
dump_interval_param(hw_params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS);
dump_interval_param(hw_params, SNDRV_PCM_HW_PARAM_FRAME_BITS);
dump_interval_param(hw_params, SNDRV_PCM_HW_PARAM_CHANNELS);
dump_interval_param(hw_params, SNDRV_PCM_HW_PARAM_RATE);
dump_interval_param(hw_params, SNDRV_PCM_HW_PARAM_PERIOD_TIME);
dump_interval_param(hw_params, SNDRV_PCM_HW_PARAM_PERIOD_SIZE);
dump_interval_param(hw_params, SNDRV_PCM_HW_PARAM_PERIODS);
dump_interval_param(hw_params, SNDRV_PCM_HW_PARAM_BUFFER_TIME);
dump_interval_param(hw_params, SNDRV_PCM_HW_PARAM_BUFFER_SIZE);
dump_interval_param(hw_params, SNDRV_PCM_HW_PARAM_BUFFER_BYTES);
dump_interval_param(hw_params, SNDRV_PCM_HW_PARAM_TICK_TIME);
if (hw_params->flags > 0) {
printf(" flags: \n");
for (i = 0; i < ARRAY_SIZE(flag_labels); ++i) {
if (hw_params->flags & (1 << i))
printf(" %s\n", flag_labels[i]);
}
}
printf(" info:\n");
for (i = 0; i < ARRAY_SIZE(info_flags); ++i) {
if (hw_params->info & info_flags[i])
printf(" %s\n", info_labels[i]);
}
if (hw_params->msbits > 0)
printf(" most-significant-bits: %u\n", hw_params->msbits);
if (hw_params->rate_num > 0 && hw_params->rate_den > 0) {
printf(" rate_num: %u\n", hw_params->rate_num);
printf(" rate_den: %u\n", hw_params->rate_den);
}
return 0;
}
static void initialize_hw_params(struct snd_pcm_hw_params *params)
{
unsigned int type;
int i;
for (i = 0; i < get_mask_count(); i++) {
int j;
type = mask_index_to_type(i);
for (j = 0; j < SNDRV_MASK_MAX; ++j)
change_mask(params, type, j, true, false);
}
for (i = 0; i < get_interval_count(); i++) {
type = interval_index_to_type(i);
change_interval(params, type, 0, UINT_MAX, false, false, false, false);
}
params->cmask = 0;
params->info = 0;
}
int configure_hardware(int fd, unsigned int access, snd_pcm_format_t sample_format,
unsigned int samples_per_frame, unsigned int frames_per_second,
unsigned int *frames_per_buffer, unsigned int *frames_per_period)
{
struct snd_pcm_hw_params params = {0};
struct snd_interval *interval;
int err;
initialize_hw_params(&params);
err = ioctl(fd, SNDRV_PCM_IOCTL_HW_REFINE, &params);
if (err < 0) {
printf("Fail to request HW_REFINE: %s\n", strerror(errno));
return err;
}
printf("Available hardware parameters:\n");
dump_hw_params_cap(&params);
change_mask(&params, SNDRV_PCM_HW_PARAM_ACCESS, access, true, true);
change_mask(&params, SNDRV_PCM_HW_PARAM_FORMAT, sample_format, true, true);
change_mask(&params, SNDRV_PCM_HW_PARAM_SUBFORMAT, SNDRV_PCM_SUBFORMAT_STD, true, true);
change_interval(&params, SNDRV_PCM_HW_PARAM_CHANNELS, samples_per_frame, samples_per_frame,
false, false, true, false);
change_interval(&params, SNDRV_PCM_HW_PARAM_RATE, frames_per_second, frames_per_second,
false, false, true, false);
err = ioctl(fd, SNDRV_PCM_IOCTL_HW_PARAMS, &params);
if (err < 0) {
printf("Fail to request HW_PARAMS: %s\n", strerror(errno));
return err;
}
printf("Current hardware parameters:\n");
dump_hw_params_cap(&params);
interval = refer_interval(&params, SNDRV_PCM_HW_PARAM_BUFFER_SIZE);
*frames_per_buffer = interval->min;
interval = refer_interval(&params, SNDRV_PCM_HW_PARAM_PERIOD_SIZE);
*frames_per_period = interval->min;
return 0;
}
static int configure_software(int fd, unsigned int frames_per_buffer, unsigned int frames_per_period)
{
struct snd_pcm_sw_params params = {0};
int err;
// We can start transmission by hand.
params.start_threshold = frames_per_buffer;
// We should keep frames one third of intermediate buffer to avoid stopping transmission automatically.
params.stop_threshold = frames_per_buffer / 3;
// Relevant to time to wake from poll wait but not used.
params.avail_min = frames_per_period;
// No need to fill with silence frame.
params.silence_threshold = 0;
params.silence_size = 0;
// Timestamping is not required in this program.
params.tstamp_mode = SNDRV_PCM_TSTAMP_NONE;
params.proto = 0;
params.tstamp_type = SNDRV_PCM_TSTAMP_TYPE_GETTIMEOFDAY;
// Nothing in kernel space.
params.period_step = 1;
params.sleep_min = 1;
err = ioctl(fd, SNDRV_PCM_IOCTL_SW_PARAMS, &params);
if (err < 0) {
printf("Fail to request SW_PARAMS: %s\n", strerror(errno));
return err;
}
return 0;
}
static int prepare_hardware(int fd)
{
struct snd_xferi xfer = {0};
int err;
err = ioctl(fd, SNDRV_PCM_IOCTL_PREPARE);
if (err < 0) {
printf("Fail to request PREPARE: %s\n", strerror(errno));
return err;
}
return 0;
}
static int transfer_frames(int fd, uint8_t *buf, unsigned int frames_per_buffer)
{
unsigned int accumulate_frame_count;
struct snd_pcm_status status = {0};
struct snd_xferi xfer = {0};
bool at_first_iteration;
int err;
err = ioctl(fd, SNDRV_PCM_IOCTL_STATUS, &status);
if (err < 0) {
printf("Fail to request STATUS: %s\n", strerror(errno));
return err;
}
printf("This should be prepared: %s\n", state_labels[status.state]);
// Supply initial frames here.
xfer.result = 0;
xfer.buf = buf;
xfer.frames = frames_per_buffer * 2 / 3;
err = ioctl(fd, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &xfer);
if (err < 0) {
printf("Fail to request initial WRITEI_FRAMES: %s\n", strerror(errno));
return err;
}
accumulate_frame_count = xfer.result;
printf("Initial frames are copied to intermediate buffer: %ld\n", xfer.result);
err = ioctl(fd, SNDRV_PCM_IOCTL_STATUS, &status);
if (err < 0) {
printf("Fail to request STATUS: %s\n", strerror(errno));
return err;
}
printf("This should be prepared as well: %s\n", state_labels[status.state]);
err = ioctl(fd, SNDRV_PCM_IOCTL_START);
if (err < 0) {
printf("Fail to request START: %s\n", strerror(errno));
return err;
}
err = ioctl(fd, SNDRV_PCM_IOCTL_STATUS, &status);
if (err < 0) {
printf("Fail to request STATUS: %s\n", strerror(errno));
return err;
}
printf("This should be running: %s\n", state_labels[status.state]);
at_first_iteration = true;
while (accumulate_frame_count < MY_FRAMES_PER_SECOND * MY_PLAYBACK_DURATION) {
unsigned int rest_of_intermediate_buffer;
unsigned int frames_copied_to_intermediate_buffer;
err = ioctl(fd, SNDRV_PCM_IOCTL_STATUS, &status);
if (err < 0) {
printf("Fail to request STATUS: %s\n", strerror(errno));
break;
}
printf(" State of intermediate buffer:\n");
rest_of_intermediate_buffer = status.avail;
if (at_first_iteration) {
printf(" %lu frames are transferred to device since starting.\n",
status.avail + xfer.result - frames_per_buffer);
at_first_iteration = false;
}
printf(" %u frames are waiting to transfer\n",
frames_per_buffer - rest_of_intermediate_buffer);
// Keep frames as half of intermediate buffer.
xfer.result = 0;
xfer.buf = buf;
xfer.frames = frames_per_buffer / 2;
// This operation can block user process till all of given frames are copied to
// intermediate buffer.
err = ioctl(fd, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &xfer);
if (err < 0) {
printf("Fail to request WRITEI_FRAMES: %s\n", strerror(errno));
break;
}
frames_copied_to_intermediate_buffer = (unsigned int)xfer.result;
printf(" %u frames are transferred to device\n",
frames_copied_to_intermediate_buffer - rest_of_intermediate_buffer);
printf(" %u frames are copied from userspace\n", (unsigned int)xfer.result);
accumulate_frame_count += xfer.result;
}
if (err >= 0) {
// For playback direction, need to wait till all of frames in intermediate buffer are
// actually transferred. This blocks the user process.
int e = ioctl(fd, SNDRV_PCM_IOCTL_DRAIN);
if (e < 0)
printf("Fail to request DRAIN: %s\n", strerror(errno));
}
printf("%u frames are copied and transferred\n", accumulate_frame_count);
return err;
}
static int free_hardware(int fd)
{
int err;
err = ioctl(fd, SNDRV_PCM_IOCTL_HW_FREE);
if (err < 0) {
printf("Fail to request HW_FREE: %s\n", strerror(errno));
return err;
}
return 0;
}
int main(int argc, const char **argv)
{
const char *cdev;
int fd;
int protocol_version;
unsigned int frames_per_buffer;
unsigned int frames_per_period;
uint8_t *buf;
int err;
if (argc < 2) {
print_help(argv[0]);
return EXIT_FAILURE;
}
cdev = argv[1];
fd = open(cdev, 0);
if (fd < 0) {
printf("Fail to open '%s': %s\n", cdev, strerror(errno));
return EXIT_FAILURE;
}
err = ioctl(fd, SNDRV_PCM_IOCTL_PVERSION, &protocol_version);
if (err < 0) {
printf("Fail to request PVERSION: %s\n", strerror(errno));
goto end_open;
}
printf("Current protocol version over PCM interface: %d.%d.%d\n",
SNDRV_PROTOCOL_MAJOR(protocol_version),
SNDRV_PROTOCOL_MINOR(protocol_version),
SNDRV_PROTOCOL_MICRO(protocol_version));
err = configure_hardware(fd, SNDRV_PCM_ACCESS_RW_INTERLEAVED,
MY_SAMPLE_FORMAT,
MY_SAMPLES_PER_FRAME,
MY_FRAMES_PER_SECOND,
&frames_per_buffer, &frames_per_period);
if (err < 0)
goto end_open;
err = configure_software(fd, frames_per_buffer, frames_per_period);
if (err < 0)
goto end_open;
buf = calloc(2 * 2, frames_per_buffer);
if (buf == NULL)
goto end_open;
err = prepare_hardware(fd);
if (err < 0)
goto end_buf;
err = transfer_frames(fd, buf, frames_per_buffer);
err = free_hardware(fd);
end_buf:
free(buf);
end_open:
close(fd);
if (err < 0)
return EXIT_FAILURE;
return EXIT_SUCCESS;
}
@ryan-mcclue
Copy link

ryan-mcclue commented May 18, 2021

When I run /pcm-rw-interleaved "/dev/snd/pcmC0D0p"
I get:

# ...
This should be prepared: prepared
Initial frames are copied to intermediate buffer: 682
This should be prepared as well: xrun
Fail to request START: File descriptor in bad state
Fail to request HW_FREE: File descriptor in bad state

Relevent dmesg output:
[ 69.013084] snd_hda_intel 0000:00:1f.3: Too big adjustment 128

What is happening here?

@takaswie
Copy link
Author

takaswie commented May 19, 2021

Relevent dmesg output:

[ 69.013084] snd_hda_intel 0000:00:1f.3: Too big adjustment 128

What is happening here?

The line is output here:

It looks like an issue related to ALSA Intel HDA driver, so I suggest you to post to alsa-devel if you require explanation about it since I'm not good at Intel HDA itself...

P.S. BDL is Buffer Descriptor List in Intel HDA specification. I guess that some Intel platform requires some arrangements about the size of list, decided by the size of PCM frames per period.

@ryan-mcclue
Copy link

From alsa-devel:

The warning message is likely a red herring and it's irrelevant from
your bug itself. The driver has some small number of periods to be
put at the beginning of the buffer for correcting the reported
position. Judging from the message, you seem to have set a quite high
rate and a very shot number, and that's possibly too tight to operate
properly.
Takashi Iwai

I'm not sure what to change in this code for it to work. I tried altering MY_PLAYBACK_DURATION and params.start_threshold but get the same error.

@takaswie
Copy link
Author

The program dumps configured hardware parameters. In my opinion, you need to report the configurations to the maintainer to get advices.

Current hardware parameters:
  Changed parameters:
    access
    format
  ...

@ryan-mcclue
Copy link

ryan-mcclue commented Jul 29, 2021

I have tested using the virtual driver snd-dummy and still get an error which makes me think it may be an application error.
snd-dummy output

Current protocol version over PCM interface: 2.0.15
Available hardware parameters:
  Changed parameters:
    access
    format
    subformat
    sample-bits
    frame-bits
    channels
    rate
    period-time
    period-size
    period-bytes
    periods
    buffer-time
    buffer-size
    buffer-bytes
  Runtime parameters:
    access:
      mmap-interleaved
      readwrite-interleaved
    format:
      u8
      s16-le
    subformat:
      std
    sample-bits:
      [8, 16], integer, 
    frame-bits:
      [8, 32], integer, 
    channels:
      [1, 2], integer, 
    rate:
      [5500, 48000], 
    period-time:
      (333, 11915637), 
    period-size:
      [16, 65536], integer, 
    periods:
      [1, 1024], 
    buffer-time:
      (333, 11915637), 
    buffer-size:
      [16, 65536], integer, 
    buffer-bytes:
      [64, 65536], integer, 
    tick-time:
      [0, 4294967295], 
    info:
      mmap
      mmap-valid
      interleaved
      resume
Current hardware parameters:
  Changed parameters:
    access
    format
    subformat
    sample-bits
    frame-bits
    channels
    rate
    period-time
    period-size
    period-bytes
    periods
    buffer-time
    buffer-size
    buffer-bytes
    tick-time
  Runtime parameters:
    access:
      readwrite-interleaved
    format:
      s16-le
    subformat:
      std
    sample-bits:
      [16, 16], integer, 
    frame-bits:
      [32, 32], integer, 
    channels:
      [2, 2], integer, 
    rate:
      [48000, 48000], integer, 
    period-time:
      (333, 334), 
    period-size:
      [16, 16], integer, 
    periods:
      [1024, 1024], integer, 
    buffer-time:
      (341333, 341334), 
    buffer-size:
      [16384, 16384], integer, 
    buffer-bytes:
      [65536, 65536], integer, 
    tick-time:
      [0, 0], integer, 
    info:
      mmap
      mmap-valid
      interleaved
      resume
      most-significant-bits:    16
      rate_num: 48000
      rate_den: 1
This should be prepared: prepared
Initial frames are copied to intermediate buffer: 10922
This should be prepared as well: xrun
Fail to request START: File descriptor in bad state
Fail to request HW_FREE: File descriptor in bad state

snd-intel-hda output

Current protocol version over PCM interface: 2.0.15
Available hardware parameters:
  Changed parameters:
    access
    format
    subformat
    sample-bits
    frame-bits
    channels
    rate
    period-time
    period-size
    period-bytes
    periods
    buffer-time
    buffer-size
    buffer-bytes
  Runtime parameters:
    access:
      mmap-interleaved
      readwrite-interleaved
    format:
      s16-le
    subformat:
      std
    sample-bits:
      [16, 16], integer, 
    frame-bits:
      [32, 32], integer, 
    channels:
      [2, 2], integer, 
    rate:
      [48000, 192000], 
    period-time:
      (166, 356000000], 
    period-size:
      [32, 17088000], integer, 
    periods:
      [2, 32], integer, 
    buffer-time:
      (333, 178000000], 
    buffer-size:
      [64, 34176000], integer, 
    buffer-bytes:
      [256, 136704000], integer, 
    tick-time:
      [0, 4294967295], 
    info:
      mmap
      mmap-valid
      interleaved
      block-transfer
      pause
      sync-start
      no-period-wakeup
      has-wall-clock
      has-link-atime
      has-link-synchronized-atime
      most-significant-bits:    16
Current hardware parameters:
  Changed parameters:
    access
    format
    subformat
    sample-bits
    frame-bits
    channels
    rate
    period-time
    period-size
    period-bytes
    periods
    buffer-time
    buffer-size
    buffer-bytes
    tick-time
  Runtime parameters:
    access:
      readwrite-interleaved
    format:
      s16-le
    subformat:
      std
    sample-bits:
      [16, 16], integer, 
    frame-bits:
      [32, 32], integer, 
    channels:
      [2, 2], integer, 
    rate:
      [48000, 48000], integer, 
    period-time:
      (666, 667), 
    period-size:
      [32, 32], integer, 
    periods:
      [32, 32], integer, 
    buffer-time:
      (21333, 21334), 
    buffer-size:
      [1024, 1024], integer, 
    buffer-bytes:
      [4096, 4096], integer, 
    tick-time:
      [0, 0], integer, 
    info:
      mmap
      mmap-valid
      interleaved
      block-transfer
      pause
      sync-start
      no-period-wakeup
      has-wall-clock
      has-link-atime
      has-link-synchronized-atime
      most-significant-bits:    16
      rate_num: 48000
      rate_den: 1
This should be prepared: prepared
Initial frames are copied to intermediate buffer: 682
This should be prepared as well: xrun
Fail to request START: File descriptor in bad state
Fail to request HW_FREE: File descriptor in bad state

What driver did you test it on where it worked?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment