当前位置: 首页 > news >正文

Linux 内核音频数据传递主要流程

Linux 用户空间应用程序通过声卡驱动程序(一般牵涉到多个设备驱动程序)和 Linux 内核 ALSA 框架导出的 PCM 设备文件,如 /dev/snd/pcmC0D0c/dev/snd/pcmC0D0p 等,与 Linux 内核音频设备驱动程序和音频硬件进行数据传递。PCM 设备文件的文件操作定义 (位于 sound/core/pcm_native.c) 如下:

const struct file_operations snd_pcm_f_ops[2] = {{.owner =		THIS_MODULE,.write =		snd_pcm_write,.write_iter =		snd_pcm_writev,.open =			snd_pcm_playback_open,.release =		snd_pcm_release,.llseek =		no_llseek,.poll =			snd_pcm_poll,.unlocked_ioctl =	snd_pcm_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,.read_iter =		snd_pcm_readv,.open =			snd_pcm_capture_open,.release =		snd_pcm_release,.llseek =		no_llseek,.poll =			snd_pcm_poll,.unlocked_ioctl =	snd_pcm_ioctl,.compat_ioctl = 	snd_pcm_ioctl_compat,.mmap =			snd_pcm_mmap,.fasync =		snd_pcm_fasync,.get_unmapped_area =	snd_pcm_get_unmapped_area,}
};

大多数情况下,音频设备会同时提供播放和录制功能,用于播放和录制的 PCM 设备文件是一起导出的,播放和录制的 PCM 设备文件的文件操作也是一起定义的,其中索引为 SNDRV_PCM_STREAM_PLAYBACK,也就是 0 的文件操作用于播放,索引为 SNDRV_PCM_STREAM_CAPTURE,也就是 1 的文件操作用于录制。

Linux 用户空间有 alsa-libtinyalsa 等库可用于与 Linux 内核 ALSA 框架交互。alsa-libtinyalsa 等库主要通过 ioctl 命令与 Linux 内核 ALSA 框架交互。PCM 设备文件的 ioctl 操作 snd_pcm_ioctl() 函数定义 (位于 sound/core/pcm_native.c) 如下:

static int snd_pcm_common_ioctl(struct file *file,struct snd_pcm_substream *substream,unsigned int cmd, void __user *arg)
{struct snd_pcm_file *pcm_file = file->private_data;int res;if (PCM_RUNTIME_CHECK(substream))return -ENXIO;res = snd_power_wait(substream->pcm->card, SNDRV_CTL_POWER_D0);if (res < 0)return res;switch (cmd) {case SNDRV_PCM_IOCTL_PVERSION:return put_user(SNDRV_PCM_VERSION, (int __user *)arg) ? -EFAULT : 0;case SNDRV_PCM_IOCTL_INFO:return snd_pcm_info_user(substream, arg);case SNDRV_PCM_IOCTL_TSTAMP:	/* just for compatibility */return 0;case SNDRV_PCM_IOCTL_TTSTAMP:return snd_pcm_tstamp(substream, arg);case SNDRV_PCM_IOCTL_USER_PVERSION:if (get_user(pcm_file->user_pversion,(unsigned int __user *)arg))return -EFAULT;return 0;case SNDRV_PCM_IOCTL_HW_REFINE:return snd_pcm_hw_refine_user(substream, arg);case SNDRV_PCM_IOCTL_HW_PARAMS:return snd_pcm_hw_params_user(substream, arg);case SNDRV_PCM_IOCTL_HW_FREE:return snd_pcm_hw_free(substream);case SNDRV_PCM_IOCTL_SW_PARAMS:return snd_pcm_sw_params_user(substream, arg);case SNDRV_PCM_IOCTL_STATUS32:return snd_pcm_status_user32(substream, arg, false);case SNDRV_PCM_IOCTL_STATUS_EXT32:return snd_pcm_status_user32(substream, arg, true);case SNDRV_PCM_IOCTL_STATUS64:return snd_pcm_status_user64(substream, arg, false);case SNDRV_PCM_IOCTL_STATUS_EXT64:return snd_pcm_status_user64(substream, arg, true);case SNDRV_PCM_IOCTL_CHANNEL_INFO:return snd_pcm_channel_info_user(substream, arg);case SNDRV_PCM_IOCTL_PREPARE:return snd_pcm_prepare(substream, file);case SNDRV_PCM_IOCTL_RESET:return snd_pcm_reset(substream);case SNDRV_PCM_IOCTL_START:return snd_pcm_start_lock_irq(substream);case SNDRV_PCM_IOCTL_LINK:return snd_pcm_link(substream, (int)(unsigned long) arg);case SNDRV_PCM_IOCTL_UNLINK:return snd_pcm_unlink(substream);case SNDRV_PCM_IOCTL_RESUME:return snd_pcm_resume(substream);case SNDRV_PCM_IOCTL_XRUN:return snd_pcm_xrun(substream);case SNDRV_PCM_IOCTL_HWSYNC:return snd_pcm_hwsync(substream);case SNDRV_PCM_IOCTL_DELAY:{snd_pcm_sframes_t delay;snd_pcm_sframes_t __user *res = arg;int err;err = snd_pcm_delay(substream, &delay);if (err)return err;if (put_user(delay, res))return -EFAULT;return 0;}case __SNDRV_PCM_IOCTL_SYNC_PTR32:return snd_pcm_ioctl_sync_ptr_compat(substream, arg);case __SNDRV_PCM_IOCTL_SYNC_PTR64:return snd_pcm_sync_ptr(substream, arg);
#ifdef CONFIG_SND_SUPPORT_OLD_APIcase SNDRV_PCM_IOCTL_HW_REFINE_OLD:return snd_pcm_hw_refine_old_user(substream, arg);case SNDRV_PCM_IOCTL_HW_PARAMS_OLD:return snd_pcm_hw_params_old_user(substream, arg);
#endifcase SNDRV_PCM_IOCTL_DRAIN:return snd_pcm_drain(substream, file);case SNDRV_PCM_IOCTL_DROP:return snd_pcm_drop(substream);case SNDRV_PCM_IOCTL_PAUSE:return snd_pcm_pause_lock_irq(substream, (unsigned long)arg);case SNDRV_PCM_IOCTL_WRITEI_FRAMES:case SNDRV_PCM_IOCTL_READI_FRAMES:return snd_pcm_xferi_frames_ioctl(substream, arg);case SNDRV_PCM_IOCTL_WRITEN_FRAMES:case SNDRV_PCM_IOCTL_READN_FRAMES:return snd_pcm_xfern_frames_ioctl(substream, arg);case SNDRV_PCM_IOCTL_REWIND:return snd_pcm_rewind_ioctl(substream, arg);case SNDRV_PCM_IOCTL_FORWARD:return snd_pcm_forward_ioctl(substream, arg);}pcm_dbg(substream->pcm, "unknown ioctl = 0x%x\n", cmd);return -ENOTTY;
}static long snd_pcm_ioctl(struct file *file, unsigned int cmd,unsigned long arg)
{struct snd_pcm_file *pcm_file;pcm_file = file->private_data;if (((cmd >> 8) & 0xff) != 'A')return -ENOTTY;return snd_pcm_common_ioctl(file, pcm_file->substream, cmd,(void __user *)arg);
}

alsa-libtinyalsa 等库主要通过 SNDRV_PCM_IOCTL_WRITEI_FRAMESSNDRV_PCM_IOCTL_READI_FRAMESSNDRV_PCM_IOCTL_WRITEN_FRAMESSNDRV_PCM_IOCTL_READN_FRAMES 等四个 ioctl 命令与 Linux 内核 ALSA 框架交换数据。这几个 ioctl 命令主要的区别在于,传递的数据的格式不同,SNDRV_PCM_IOCTL_READI_FRAMESSNDRV_PCM_IOCTL_WRITEI_FRAMES 命令用于读写 interleaved 格式的音频数据,SNDRV_PCM_IOCTL_READN_FRAMESSNDRV_PCM_IOCTL_WRITEN_FRAMES 命令则用于读写 noninterleaved 格式的音频数据。

SNDRV_PCM_IOCTL_READI_FRAMESSNDRV_PCM_IOCTL_WRITEI_FRAMES 命令由 snd_pcm_xferi_frames_ioctl() 函数处理,SNDRV_PCM_IOCTL_READN_FRAMESSNDRV_PCM_IOCTL_WRITEN_FRAMES 命令由 snd_pcm_xfern_frames_ioctl() 函数处理,这两个函数定义 (位于 sound/core/pcm_native.c) 如下:

static int snd_pcm_xferi_frames_ioctl(struct snd_pcm_substream *substream,struct snd_xferi __user *_xferi)
{struct snd_xferi xferi;struct snd_pcm_runtime *runtime = substream->runtime;snd_pcm_sframes_t result;if (runtime->status->state == SNDRV_PCM_STATE_OPEN)return -EBADFD;if (put_user(0, &_xferi->result))return -EFAULT;if (copy_from_user(&xferi, _xferi, sizeof(xferi)))return -EFAULT;if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)result = snd_pcm_lib_write(substream, xferi.buf, xferi.frames);elseresult = snd_pcm_lib_read(substream, xferi.buf, xferi.frames);if (put_user(result, &_xferi->result))return -EFAULT;return result < 0 ? result : 0;
}static int snd_pcm_xfern_frames_ioctl(struct snd_pcm_substream *substream,struct snd_xfern __user *_xfern)
{struct snd_xfern xfern;struct snd_pcm_runtime *runtime = substream->runtime;void *bufs;snd_pcm_sframes_t result;if (runtime->status->state == SNDRV_PCM_STATE_OPEN)return -EBADFD;if (runtime->channels > 128)return -EINVAL;if (put_user(0, &_xfern->result))return -EFAULT;if (copy_from_user(&xfern, _xfern, sizeof(xfern)))return -EFAULT;bufs = memdup_user(xfern.bufs, sizeof(void *) * runtime->channels);if (IS_ERR(bufs))return PTR_ERR(bufs);if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)result = snd_pcm_lib_writev(substream, bufs, xfern.frames);elseresult = snd_pcm_lib_readv(substream, bufs, xfern.frames);kfree(bufs);if (put_user(result, &_xfern->result))return -EFAULT;return result < 0 ? result : 0;
}

snd_pcm_xferi_frames_ioctl()snd_pcm_xfern_frames_ioctl() 函数将参数复制到内核空间栈上,并通过 snd_pcm_lib_write()snd_pcm_lib_read()snd_pcm_lib_writev()snd_pcm_lib_readv() 四个函数执行读写操作。这四个函数定义 (位于 include/sound/pcm.h) 如下:

static inline snd_pcm_sframes_t
snd_pcm_lib_write(struct snd_pcm_substream *substream,const void __user *buf, snd_pcm_uframes_t frames)
{return __snd_pcm_lib_xfer(substream, (void __force *)buf, true, frames, false);
}static inline snd_pcm_sframes_t
snd_pcm_lib_read(struct snd_pcm_substream *substream,void __user *buf, snd_pcm_uframes_t frames)
{return __snd_pcm_lib_xfer(substream, (void __force *)buf, true, frames, false);
}static inline snd_pcm_sframes_t
snd_pcm_lib_writev(struct snd_pcm_substream *substream,void __user **bufs, snd_pcm_uframes_t frames)
{return __snd_pcm_lib_xfer(substream, (void *)bufs, false, frames, false);
}static inline snd_pcm_sframes_t
snd_pcm_lib_readv(struct snd_pcm_substream *substream,void __user **bufs, snd_pcm_uframes_t frames)
{return __snd_pcm_lib_xfer(substream, (void *)bufs, false, frames, false);
}

所有的音频数据传递操作最终都由 __snd_pcm_lib_xfer() 函数完成。__snd_pcm_lib_xfer() 函数定义 (位于 sound/core/pcm_lib.c) 如下:

typedef int (*pcm_transfer_f)(struct snd_pcm_substream *substream,int channel, unsigned long hwoff,void *buf, unsigned long bytes);typedef int (*pcm_copy_f)(struct snd_pcm_substream *, snd_pcm_uframes_t, void *,snd_pcm_uframes_t, snd_pcm_uframes_t, pcm_transfer_f);. . . . . .
/* sanity-check for read/write methods */
static int pcm_sanity_check(struct snd_pcm_substream *substream)
{struct snd_pcm_runtime *runtime;if (PCM_RUNTIME_CHECK(substream))return -ENXIO;runtime = substream->runtime;if (snd_BUG_ON(!substream->ops->copy_user && !runtime->dma_area))return -EINVAL;if (runtime->status->state == SNDRV_PCM_STATE_OPEN)return -EBADFD;return 0;
}static int pcm_accessible_state(struct snd_pcm_runtime *runtime)
{switch (runtime->status->state) {case SNDRV_PCM_STATE_PREPARED:case SNDRV_PCM_STATE_RUNNING:case SNDRV_PCM_STATE_PAUSED:return 0;case SNDRV_PCM_STATE_XRUN:return -EPIPE;case SNDRV_PCM_STATE_SUSPENDED:return -ESTRPIPE;default:return -EBADFD;}
}/* update to the given appl_ptr and call ack callback if needed;* when an error is returned, take back to the original value*/
int pcm_lib_apply_appl_ptr(struct snd_pcm_substream *substream,snd_pcm_uframes_t appl_ptr)
{struct snd_pcm_runtime *runtime = substream->runtime;snd_pcm_uframes_t old_appl_ptr = runtime->control->appl_ptr;int ret;if (old_appl_ptr == appl_ptr)return 0;runtime->control->appl_ptr = appl_ptr;if (substream->ops->ack) {ret = substream->ops->ack(substream);if (ret < 0) {runtime->control->appl_ptr = old_appl_ptr;return ret;}}trace_applptr(substream, old_appl_ptr, appl_ptr);return 0;
}/* the common loop for read/write data */
snd_pcm_sframes_t __snd_pcm_lib_xfer(struct snd_pcm_substream *substream,void *data, bool interleaved,snd_pcm_uframes_t size, bool in_kernel)
{struct snd_pcm_runtime *runtime = substream->runtime;snd_pcm_uframes_t xfer = 0;snd_pcm_uframes_t offset = 0;snd_pcm_uframes_t avail;pcm_copy_f writer;pcm_transfer_f transfer;bool nonblock;bool is_playback;int err;err = pcm_sanity_check(substream);if (err < 0)return err;is_playback = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;if (interleaved) {if (runtime->access != SNDRV_PCM_ACCESS_RW_INTERLEAVED &&runtime->channels > 1)return -EINVAL;writer = interleaved_copy;} else {if (runtime->access != SNDRV_PCM_ACCESS_RW_NONINTERLEAVED)return -EINVAL;writer = noninterleaved_copy;}if (!data) {if (is_playback)transfer = fill_silence;elsereturn -EINVAL;} else if (in_kernel) {if (substream->ops->copy_kernel)transfer = substream->ops->copy_kernel;elsetransfer = is_playback ?default_write_copy_kernel : default_read_copy_kernel;} else {if (substream->ops->copy_user)transfer = (pcm_transfer_f)substream->ops->copy_user;elsetransfer = is_playback ?default_write_copy : default_read_copy;}if (size == 0)return 0;nonblock = !!(substream->f_flags & O_NONBLOCK);snd_pcm_stream_lock_irq(substream);err = pcm_accessible_state(runtime);if (err < 0)goto _end_unlock;runtime->twake = runtime->control->avail_min ? : 1;if (runtime->status->state == SNDRV_PCM_STATE_RUNNING)snd_pcm_update_hw_ptr(substream);/** If size < start_threshold, wait indefinitely. Another* thread may start capture*/if (!is_playback &&runtime->status->state == SNDRV_PCM_STATE_PREPARED &&size >= runtime->start_threshold) {err = snd_pcm_start(substream);if (err < 0)goto _end_unlock;}avail = snd_pcm_avail(substream);while (size > 0) {snd_pcm_uframes_t frames, appl_ptr, appl_ofs;snd_pcm_uframes_t cont;if (!avail) {if (!is_playback &&runtime->status->state == SNDRV_PCM_STATE_DRAINING) {snd_pcm_stop(substream, SNDRV_PCM_STATE_SETUP);goto _end_unlock;}if (nonblock) {err = -EAGAIN;goto _end_unlock;}runtime->twake = min_t(snd_pcm_uframes_t, size,runtime->control->avail_min ? : 1);err = wait_for_avail(substream, &avail);if (err < 0)goto _end_unlock;if (!avail)continue; /* draining */}frames = size > avail ? avail : size;appl_ptr = READ_ONCE(runtime->control->appl_ptr);appl_ofs = appl_ptr % runtime->buffer_size;cont = runtime->buffer_size - appl_ofs;if (frames > cont)frames = cont;if (snd_BUG_ON(!frames)) {err = -EINVAL;goto _end_unlock;}if (!atomic_inc_unless_negative(&runtime->buffer_accessing)) {err = -EBUSY;goto _end_unlock;}snd_pcm_stream_unlock_irq(substream);err = writer(substream, appl_ofs, data, offset, frames,transfer);snd_pcm_stream_lock_irq(substream);atomic_dec(&runtime->buffer_accessing);if (err < 0)goto _end_unlock;err = pcm_accessible_state(runtime);if (err < 0)goto _end_unlock;appl_ptr += frames;if (appl_ptr >= runtime->boundary)appl_ptr -= runtime->boundary;err = pcm_lib_apply_appl_ptr(substream, appl_ptr);if (err < 0)goto _end_unlock;offset += frames;size -= frames;xfer += frames;avail -= frames;if (is_playback &&runtime->status->state == SNDRV_PCM_STATE_PREPARED &&snd_pcm_playback_hw_avail(runtime) >= (snd_pcm_sframes_t)runtime->start_threshold) {err = snd_pcm_start(substream);if (err < 0)goto _end_unlock;}}_end_unlock:runtime->twake = 0;if (xfer > 0 && err >= 0)snd_pcm_update_state(substream, runtime);snd_pcm_stream_unlock_irq(substream);return xfer > 0 ? (snd_pcm_sframes_t)xfer : err;
}
EXPORT_SYMBOL(__snd_pcm_lib_xfer);

__snd_pcm_lib_xfer() 函数的执行过程如下:

  1. 调用 pcm_sanity_check() 函数执行合理性检查,pcm_sanity_check() 函数检查 DMA buffer 和状态。音频设备驱动程序需要保证,在 __snd_pcm_lib_xfer() 函数执行时,DMA buffer 已经准备好。

  2. 根据传入的参数,即数据格式是 interleaved 还是 noninterleaved,播放还是录制,数据缓冲区是否为空,操作是由内核空间代码发起还是由用户空间代码发起等,选择 writertransfer 操作。writertransfer 操作主要用于在传入的数据缓冲区和 DMA buffer 之间传递数据。

  3. 检查 runtime 的状态,当状态不为 SNDRV_PCM_STATE_PREPAREDSNDRV_PCM_STATE_RUNNINGSNDRV_PCM_STATE_PAUSED 时,报错并返回。

  4. runtime 状态为 SNDRV_PCM_STATE_RUNNING 时,调用 snd_pcm_update_hw_ptr(substream) 函数更新 hw_ptr。

  5. 对于音频录制,如果 runtime 状态为 SNDRV_PCM_STATE_PREPARED,且请求录制的帧数超过发起阈值,则调用 snd_pcm_start(substream) 函数触发录制。

  6. 调用 snd_pcm_avail(substream) 函数获得 DMA buffer 中以帧为单位的可用空间大小。
    用户空间应用程序和 Linux 内核设备驱动程序对 DMA buffer 的访问是典型的读者-写者模型。对于播放来说,用户空间应用程序向 DMA buffer 写入数据,Linux 内核设备驱动程序从 DMA buffer 读取数据并发送给硬件设备。对于录制来说,Linux 内核设备驱动程序从硬件设备获得数据并写入 DMA buffer,用户空间应用程序从 DMA buffer 读取数据并作进一步处理。
    这里的可用空间大小是站在用户空间应用程序的视角来说的,即对于播放来说,可用空间大小指还可以向 DMA buffer 写入的数据量,对于录制来说,则指可以从 DMA buffer 读取的数据量。

  7. 通过一个循环,处理所有的数据传递请求。
    (1). 当 DMA buffer 的可用空间为 0 时,则等待直到可用空间大于 0。
    (2). 计算拷贝的数据量大小,拷贝的目的内存在 DMA buffer 中的位置等。
    (3). 调用 writertransfer 操作在 DMA buffer 和传入的缓冲区之间传递数据,appl_ofs 作为 hwoff 参数传给 writer 操作。
    (4). 检查 runtime 的状态。
    (5). 更新并调用 pcm_lib_apply_appl_ptr() 函数应用新的 appl_ptr。
    (6). 根据传递的帧数,更新偏移量、要传递的帧数、已经传递的帧数和 DMA buffer 中可用的空间大小。
    (7). 对于音频播放,如果 runtime 状态为 SNDRV_PCM_STATE_PREPARED,且 DMA buffer 中可以播放的数据量超过发起阈值,则调用 snd_pcm_start(substream) 函数触发播放。

不同情况下,用于在传入的数据缓冲区和 DMA buffer 之间传递数据的 writertransfer 操作定义 (位于 sound/core/pcm_lib.c) 如下:

/* calculate the target DMA-buffer position to be written/read */
static void *get_dma_ptr(struct snd_pcm_runtime *runtime,int channel, unsigned long hwoff)
{return runtime->dma_area + hwoff +channel * (runtime->dma_bytes / runtime->channels);
}/* default copy_user ops for write; used for both interleaved and non- modes */
static int default_write_copy(struct snd_pcm_substream *substream,int channel, unsigned long hwoff,void *buf, unsigned long bytes)
{if (copy_from_user(get_dma_ptr(substream->runtime, channel, hwoff),(void __user *)buf, bytes))return -EFAULT;return 0;
}/* default copy_kernel ops for write */
static int default_write_copy_kernel(struct snd_pcm_substream *substream,int channel, unsigned long hwoff,void *buf, unsigned long bytes)
{memcpy(get_dma_ptr(substream->runtime, channel, hwoff), buf, bytes);return 0;
}/* fill silence instead of copy data; called as a transfer helper* from __snd_pcm_lib_write() or directly from noninterleaved_copy() when* a NULL buffer is passed*/
static int fill_silence(struct snd_pcm_substream *substream, int channel,unsigned long hwoff, void *buf, unsigned long bytes)
{struct snd_pcm_runtime *runtime = substream->runtime;if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK)return 0;if (substream->ops->fill_silence)return substream->ops->fill_silence(substream, channel,hwoff, bytes);snd_pcm_format_set_silence(runtime->format,get_dma_ptr(runtime, channel, hwoff),bytes_to_samples(runtime, bytes));return 0;
}/* default copy_user ops for read; used for both interleaved and non- modes */
static int default_read_copy(struct snd_pcm_substream *substream,int channel, unsigned long hwoff,void *buf, unsigned long bytes)
{if (copy_to_user((void __user *)buf,get_dma_ptr(substream->runtime, channel, hwoff),bytes))return -EFAULT;return 0;
}/* default copy_kernel ops for read */
static int default_read_copy_kernel(struct snd_pcm_substream *substream,int channel, unsigned long hwoff,void *buf, unsigned long bytes)
{memcpy(buf, get_dma_ptr(substream->runtime, channel, hwoff), bytes);return 0;
}/* call transfer function with the converted pointers and sizes;* for interleaved mode, it's one shot for all samples*/
static int interleaved_copy(struct snd_pcm_substream *substream,snd_pcm_uframes_t hwoff, void *data,snd_pcm_uframes_t off,snd_pcm_uframes_t frames,pcm_transfer_f transfer)
{struct snd_pcm_runtime *runtime = substream->runtime;/* convert to bytes */hwoff = frames_to_bytes(runtime, hwoff);off = frames_to_bytes(runtime, off);frames = frames_to_bytes(runtime, frames);return transfer(substream, 0, hwoff, data + off, frames);
}/* call transfer function with the converted pointers and sizes for each* non-interleaved channel; when buffer is NULL, silencing instead of copying*/
static int noninterleaved_copy(struct snd_pcm_substream *substream,snd_pcm_uframes_t hwoff, void *data,snd_pcm_uframes_t off,snd_pcm_uframes_t frames,pcm_transfer_f transfer)
{struct snd_pcm_runtime *runtime = substream->runtime;int channels = runtime->channels;void **bufs = data;int c, err;/* convert to bytes; note that it's not frames_to_bytes() here.* in non-interleaved mode, we copy for each channel, thus* each copy is n_samples bytes x channels = whole frames.*/off = samples_to_bytes(runtime, off);frames = samples_to_bytes(runtime, frames);hwoff = samples_to_bytes(runtime, hwoff);for (c = 0; c < channels; ++c, ++bufs) {if (!data || !*bufs)err = fill_silence(substream, c, hwoff, NULL, frames);elseerr = transfer(substream, c, hwoff, *bufs + off,frames);if (err < 0)return err;}return 0;
}

transfer 操作用于执行数据拷贝。根据发起数据传递的来源是内核还是用户空间应用程序,以及是播放还是录制,transfer 操作有四个默认实现,分别为 default_write_copy_kernel()default_read_copy_kernel()default_write_copy()default_read_copy(),这几个函数根据 channelhwoff 通过 get_dma_ptr() 函数获得 DMA 指针,并通过内核提供的 copy_from_user()memcpy()copy_to_user() 等函数在传入的数据缓冲区和 DMA buffer 之间传递数据。

get_dma_ptr() 函数可以看到,当数据格式为 noninterleaved 时,在 DMA buffer 中,各个 channel 的数据的布局。

writer 操作用于为数据拷贝做准备,它将帧为单位的偏移量和大小等数据拷贝参数转为以字节为单位,并调用 transfer 操作执行数据拷贝。writer 操作有两个默认实现,分别为 interleaved_copy()noninterleaved_copy()。当数据格式为 noninterleaved 时,writer 操作分别拷贝各个 channel 的数据,传入的音频数据保存在多个不同的缓冲区中,每个通道一个,音频数据通过指针数组的方式传递。

writertransfer 操作中用到的指向 DMA buffer 的 hwoff 偏移量,由 runtime->control->appl_ptr 计算而来,在关于 DMA buffer 访问的读者-写者模型中,对于播放,runtime->control->appl_ptr 是写指针,对于录制,它是读指针。

snd_pcm_avail() 函数用于获得 DMA buffer 中,用户空间应用程序视角的,以帧为单位的可用空间大小,Linux 内核还提供了另一个函数 snd_pcm_hw_avail(),用于获得音频硬件设备驱动视角的,以帧为单位的可用空间大小,这两个函数定义 (位于 sound/core/pcm_local.h) 如下:

static inline snd_pcm_uframes_t
snd_pcm_avail(struct snd_pcm_substream *substream)
{if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)return snd_pcm_playback_avail(substream->runtime);elsereturn snd_pcm_capture_avail(substream->runtime);
}static inline snd_pcm_uframes_t
snd_pcm_hw_avail(struct snd_pcm_substream *substream)
{if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)return snd_pcm_playback_hw_avail(substream->runtime);elsereturn snd_pcm_capture_hw_avail(substream->runtime);
}

这两个函数根据流的类型是播放还是录制,将操作分派给另外四个函数 snd_pcm_playback_avail()snd_pcm_capture_avail()snd_pcm_playback_hw_avail()snd_pcm_capture_hw_avail()snd_pcm_playback_avail() 等函数定义 (位于 include/sound/pcm.h) 如下:

/*** snd_pcm_playback_avail - Get the available (writable) space for playback* @runtime: PCM runtime instance** Result is between 0 ... (boundary - 1)*/
static inline snd_pcm_uframes_t snd_pcm_playback_avail(struct snd_pcm_runtime *runtime)
{snd_pcm_sframes_t avail = runtime->status->hw_ptr + runtime->buffer_size - runtime->control->appl_ptr;if (avail < 0)avail += runtime->boundary;else if ((snd_pcm_uframes_t) avail >= runtime->boundary)avail -= runtime->boundary;return avail;
}/*** snd_pcm_capture_avail - Get the available (readable) space for capture* @runtime: PCM runtime instance** Result is between 0 ... (boundary - 1)*/
static inline snd_pcm_uframes_t snd_pcm_capture_avail(struct snd_pcm_runtime *runtime)
{snd_pcm_sframes_t avail = runtime->status->hw_ptr - runtime->control->appl_ptr;if (avail < 0)avail += runtime->boundary;return avail;
}/*** snd_pcm_playback_hw_avail - Get the queued space for playback* @runtime: PCM runtime instance*/
static inline snd_pcm_sframes_t snd_pcm_playback_hw_avail(struct snd_pcm_runtime *runtime)
{return runtime->buffer_size - snd_pcm_playback_avail(runtime);
}/*** snd_pcm_capture_hw_avail - Get the free space for capture* @runtime: PCM runtime instance*/
static inline snd_pcm_sframes_t snd_pcm_capture_hw_avail(struct snd_pcm_runtime *runtime)
{return runtime->buffer_size - snd_pcm_capture_avail(runtime);
}

关于 DMA buffer 访问的读者-写者模型中,对于播放,runtime->status->hw_ptr 是读指针,(runtime->control->appl_ptr - runtime->status->hw_ptr) 是已经写入,但还未播放的数据量,对于录制,runtime->status->hw_ptr 是写指针。

__snd_pcm_lib_xfer() 函数调用 snd_pcm_start() 函数触发数据传递开始执行,这个函数定义 (位于 sound/core/pcm_native.c) 如下:

/** start callbacks*/
static int snd_pcm_pre_start(struct snd_pcm_substream *substream,snd_pcm_state_t state)
{struct snd_pcm_runtime *runtime = substream->runtime;if (runtime->status->state != SNDRV_PCM_STATE_PREPARED)return -EBADFD;if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&!snd_pcm_playback_data(substream))return -EPIPE;runtime->trigger_tstamp_latched = false;runtime->trigger_master = substream;return 0;
}static int snd_pcm_do_start(struct snd_pcm_substream *substream,snd_pcm_state_t state)
{if (substream->runtime->trigger_master != substream)return 0;return substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_START);
}static void snd_pcm_undo_start(struct snd_pcm_substream *substream,snd_pcm_state_t state)
{if (substream->runtime->trigger_master == substream)substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_STOP);
}static void snd_pcm_post_start(struct snd_pcm_substream *substream,snd_pcm_state_t state)
{struct snd_pcm_runtime *runtime = substream->runtime;snd_pcm_trigger_tstamp(substream);runtime->hw_ptr_jiffies = jiffies;runtime->hw_ptr_buffer_jiffies = (runtime->buffer_size * HZ) / runtime->rate;runtime->status->state = state;if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&runtime->silence_size > 0)snd_pcm_playback_silence(substream, ULONG_MAX);snd_pcm_timer_notify(substream, SNDRV_TIMER_EVENT_MSTART);
}static const struct action_ops snd_pcm_action_start = {.pre_action = snd_pcm_pre_start,.do_action = snd_pcm_do_start,.undo_action = snd_pcm_undo_start,.post_action = snd_pcm_post_start
};/*** snd_pcm_start - start all linked streams* @substream: the PCM substream instance** Return: Zero if successful, or a negative error code.* The stream lock must be acquired before calling this function.*/
int snd_pcm_start(struct snd_pcm_substream *substream)
{return snd_pcm_action(&snd_pcm_action_start, substream,SNDRV_PCM_STATE_RUNNING);
}

这里调用音频硬件设备驱动程序的 trigger 操作,触发发起硬件的数据传输。

相关文章:

Linux 内核音频数据传递主要流程

Linux 用户空间应用程序通过声卡驱动程序&#xff08;一般牵涉到多个设备驱动程序&#xff09;和 Linux 内核 ALSA 框架导出的 PCM 设备文件&#xff0c;如 /dev/snd/pcmC0D0c 和 /dev/snd/pcmC0D0p 等&#xff0c;与 Linux 内核音频设备驱动程序和音频硬件进行数据传递。PCM 设…...

torch.device函数

torch.device 是 PyTorch 中用于表示计算设备&#xff08;如CPU或GPU&#xff09;的类。它允许你在代码中指定你希望在哪个设备上执行张量和模型操作&#xff0c;本文主要介绍了 torch.device 函数的用法和功能。 本文主要包含以下内容&#xff1a; 1.创建设备对象2.将张量和模…...

火车头采集器AI伪原创【php源码】

大家好&#xff0c;本文将围绕python作业提交什么文件展开说明&#xff0c;python123怎么提交作业是一个很多人都想弄明白的事情&#xff0c;想搞清楚python期末作业程序需要先了解以下几个事情。 火车头采集ai伪原创插件截图&#xff1a; I have a python project, whose fold…...

Python中常见的6种数据类型

数字&#xff08;Numbers&#xff09;&#xff1a;数字类型用于表示数值&#xff0c;包括整数&#xff08;int&#xff09;和浮点数&#xff08;float&#xff09;。 字符串&#xff08;Strings&#xff09;&#xff1a;字符串类型用于表示文本&#xff0c;由一系列字符组成。字…...

消息队列项目(2)

我们使用 SQLite 来进行对 Exchange, Queue, Binding 的硬盘保存 对 Message 就保存在硬盘的文本中 SQLite 封装 这里是在 application.yaml 中来引进对 SQLite 的封装 spring:datasource:url: jdbc:sqlite:./data/meta.dbusername:password:driver-class-name: org.sqlite.…...

解决MAC M1处理器运行Android protoc时出现的错误

Protobuf是Google开发的一种新的结构化数据存储格式&#xff0c;一般用于结构化数据的序列化&#xff0c;也就是我们常说的数据序列化。这个序列化协议非常轻量级和高效&#xff0c;并且是跨平台的。目前&#xff0c;它支持多种主流语言&#xff0c;比传统的XML、JSON等方法更具…...

C#使用SnsSharp实现鼠标键盘钩子,实现全局按键响应

gitee下载地址&#xff1a;https://gitee.com/linsns/snssharp 一、键盘事件&#xff0c;使用SnsKeyboardHook 按键事件共有3个&#xff1a; KeyDown(按键按下) KeyUp(按键松开) KeyPress(按键按下并松开) 以KeyDown事件为例&#xff0c;使用代码如下&…...

Zookeeper基础操作

搭建Zookeeper服务器 windows下部署 下载地址: https://mirrors.cloud.tencent.com/apache/zookeeper/zookeeper-3.7.1/ 修改配置文件 打开conf目录&#xff0c;将 zoo_sample.cfg复制一份&#xff0c;命名为 zoo.cfg打开 zoo.cfg&#xff0c;修改 dataDir路径&#xff0c…...

【CSS】说说响应式布局

目录 一、是什么 二、怎么实现 1、媒体查询 2、百分比 3、vw/vh 4、小结 三、总结 一、是什么 响应式设计简而言之&#xff0c;就是一个网站能够兼容多个终端——而不是为每个终端做一个特定的版本。 响应式网站常见特点&#xff1a; 同时适配PC 平板 手机等…...

数据结构 | 利用二叉堆实现优先级队列

目录 一、二叉堆的操作 二、二叉堆的实现 2.1 结构属性 2.2 堆的有序性 2.3 堆操作 队列有一个重要的变体&#xff0c;叫作优先级队列。和队列一样&#xff0c;优先级队列从头部移除元素&#xff0c;不过元素的逻辑顺序是由优先级决定的。优先级最高的元素在最前&#xff…...

Javascript怎样阻止事件传播?

在 JavaScript 中&#xff0c;可以使用事件对象的方法来阻止事件传播。事件传播指的是当一个元素上触发了一个事件&#xff0c;该事件会在事件流中传播到父元素或祖先元素&#xff0c;从而影响到它们。 事件传播有三个阶段&#xff1a;捕获阶段、目标阶段和冒泡阶段。阻止事件…...

web-csrf

目录 CSRF与XSS的区别&#xff1a; get请求 原理&#xff1a; pikachu为例 post请求 pikachu为例 CSRF与XSS的区别&#xff1a; CSRF是借用户的权限完成攻击&#xff0c;攻击者并没有拿到用户的权限&#xff0c;而XSS是直接盗取到了用户的权限 get请求 原理&#xff1a;…...

数据结构—图的存储结构

6.图 回顾&#xff1a;数据的逻辑结构 集合——数据元素间除 “同属于一个集合” 外&#xff0c;无其他关系。 线性结构——一个对一个&#xff0c;如线性表、栈、队列 树形结构——一个对多个&#xff0c;如树 图形结构——多个对多个&#xff0c;如图 6.1图的定义和术语 图:…...

Vue3 中 setup,ref 和 reactive 的理解

setup Vue3中使用了Composition API这种写法&#xff0c;使得所有的组合API函数都在此使用, 只在初始化时执行一次。 函数如果返回对象, 对象中的属性或方法, 模板中可以直接使用 ref 作用&#xff1a;定义一个数据的响应式 语法&#xff1a;const xxx ref(initValue) 一般用来…...

BL302嵌入式ARM控制器进行SQLite3数据库操作的实例演示

本文主要讲述了在钡铼技术BL302嵌入式arm控制器上运行 SQLite3 数据库的命令示例。SQLite3 是一个轻型的嵌入式数据库&#xff0c;不需要安装数据库服务器进程&#xff0c;占用资源低且处理速度快。 首先&#xff0c;需要将对应版本的 SQLite3 文件复制到设备的 /usr/ 目录下&…...

C++ 多线程:std::future

std::future std::future 简介示例1博客引用来源 std::future 简介 我们前面介绍的std::thread 是C11中提供异步创建多线程的工具&#xff0c;只能是异步运行任务&#xff0c;却无法获取任务执行的结果&#xff0c;一般都是依靠全局对象&#xff0c;全局对象在多线程下是及其不…...

断路器回路电阻试验

试验目的 断路器回路电阻主要取决于断路器动、 静触头的接触电阻, 其大小直接影响正常 运行时的发热情况及切断短路电流的性能, 是反应安装检修质量的重要数据。 试验设备 回路电阻测试仪 厂家&#xff1a; 湖北众拓高试代销 试验接线 对于单断口的断路器, 通过断口两端的接线…...

Python中的CALL_FUNCTION指令

在Python字节码中&#xff0c;CALL_FUNCTION指令后跟的数字代表这次函数调用需要从栈上取出的参数的数量。具体来说&#xff0c;这个数字包括位置参数和关键字参数的数量。 这个数字的低两位表示位置参数的数量&#xff0c;然后每两位表示一个关键字参数的数量。因此&#xff…...

微服务——es数据聚合+RestClient实现聚合

数据聚合 聚合的种类 DSL实现Bucket聚合 如图所示&#xff0c;设置了10个桶&#xff0c;那么就显示了数量最多的前10个桶&#xff0c;品牌含有7天酒店的有30家&#xff0c; 品牌含有如家的也有30家。 修改排序规则 限定聚合范围 DSL实现Metrics聚合 如下案例要求对不同的品…...

代码分析Java中的BIO与NIO

开发环境 OS&#xff1a;Win10&#xff08;需要开启telnet服务&#xff0c;或使用第三方远程工具&#xff09; Java版本&#xff1a;8 BIO 概念 BIO(Block IO)&#xff0c;即同步阻塞IO&#xff0c;特点为当客户端发起请求后&#xff0c;在服务端未处理完该请求之前&#xff…...

visual studio 2022更改主题为深色

visual studio 2022更改主题为深色 点击visual studio 上方的 工具-> 选项 在选项窗口中&#xff0c;选择 环境 -> 常规 &#xff0c;将其中的颜色主题改成深色 点击确定&#xff0c;更改完成...

前端导出带有合并单元格的列表

// 导出async function exportExcel(fileName "共识调整.xlsx") {// 所有数据const exportData await getAllMainData();// 表头内容let fitstTitleList [];const secondTitleList [];allColumns.value.forEach(column > {if (!column.children) {fitstTitleL…...

STM32标准库-DMA直接存储器存取

文章目录 一、DMA1.1简介1.2存储器映像1.3DMA框图1.4DMA基本结构1.5DMA请求1.6数据宽度与对齐1.7数据转运DMA1.8ADC扫描模式DMA 二、数据转运DMA2.1接线图2.2代码2.3相关API 一、DMA 1.1简介 DMA&#xff08;Direct Memory Access&#xff09;直接存储器存取 DMA可以提供外设…...

Qwen3-Embedding-0.6B深度解析:多语言语义检索的轻量级利器

第一章 引言&#xff1a;语义表示的新时代挑战与Qwen3的破局之路 1.1 文本嵌入的核心价值与技术演进 在人工智能领域&#xff0c;文本嵌入技术如同连接自然语言与机器理解的“神经突触”——它将人类语言转化为计算机可计算的语义向量&#xff0c;支撑着搜索引擎、推荐系统、…...

mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包

文章目录 现象&#xff1a;mysql已经安装&#xff0c;但是通过rpm -q 没有找mysql相关的已安装包遇到 rpm 命令找不到已经安装的 MySQL 包时&#xff0c;可能是因为以下几个原因&#xff1a;1.MySQL 不是通过 RPM 包安装的2.RPM 数据库损坏3.使用了不同的包名或路径4.使用其他包…...

VM虚拟机网络配置(ubuntu24桥接模式):配置静态IP

编辑-虚拟网络编辑器-更改设置 选择桥接模式&#xff0c;然后找到相应的网卡&#xff08;可以查看自己本机的网络连接&#xff09; windows连接的网络点击查看属性 编辑虚拟机设置更改网络配置&#xff0c;选择刚才配置的桥接模式 静态ip设置&#xff1a; 我用的ubuntu24桌…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

Selenium常用函数介绍

目录 一&#xff0c;元素定位 1.1 cssSeector 1.2 xpath 二&#xff0c;操作测试对象 三&#xff0c;窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四&#xff0c;弹窗 五&#xff0c;等待 六&#xff0c;导航 七&#xff0c;文件上传 …...

PostgreSQL——环境搭建

一、Linux # 安装 PostgreSQL 15 仓库 sudo dnf install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-$(rpm -E %{rhel})-x86_64/pgdg-redhat-repo-latest.noarch.rpm# 安装之前先确认是否已经存在PostgreSQL rpm -qa | grep postgres# 如果存在&#xff0…...

springboot 日志类切面,接口成功记录日志,失败不记录

springboot 日志类切面&#xff0c;接口成功记录日志&#xff0c;失败不记录 自定义一个注解方法 import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;/***…...