当前位置: 首页 > 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…...

SciencePlots——绘制论文中的图片

文章目录 安装一、风格二、1 资源 安装 # 安装最新版 pip install githttps://github.com/garrettj403/SciencePlots.git# 安装稳定版 pip install SciencePlots一、风格 简单好用的深度学习论文绘图专用工具包–Science Plot 二、 1 资源 论文绘图神器来了&#xff1a;一行…...

macOS多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用

文章目录 问题现象问题原因解决办法 问题现象 macOS启动台&#xff08;Launchpad&#xff09;多出来了&#xff1a;Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用。 问题原因 很明显&#xff0c;都是Google家的办公全家桶。这些应用并不是通过独立安装的…...

屋顶变身“发电站” ,中天合创屋面分布式光伏发电项目顺利并网!

5月28日&#xff0c;中天合创屋面分布式光伏发电项目顺利并网发电&#xff0c;该项目位于内蒙古自治区鄂尔多斯市乌审旗&#xff0c;项目利用中天合创聚乙烯、聚丙烯仓库屋面作为场地建设光伏电站&#xff0c;总装机容量为9.96MWp。 项目投运后&#xff0c;每年可节约标煤3670…...

LLM基础1_语言模型如何处理文本

基于GitHub项目&#xff1a;https://github.com/datawhalechina/llms-from-scratch-cn 工具介绍 tiktoken&#xff1a;OpenAI开发的专业"分词器" torch&#xff1a;Facebook开发的强力计算引擎&#xff0c;相当于超级计算器 理解词嵌入&#xff1a;给词语画"…...

selenium学习实战【Python爬虫】

selenium学习实战【Python爬虫】 文章目录 selenium学习实战【Python爬虫】一、声明二、学习目标三、安装依赖3.1 安装selenium库3.2 安装浏览器驱动3.2.1 查看Edge版本3.2.2 驱动安装 四、代码讲解4.1 配置浏览器4.2 加载更多4.3 寻找内容4.4 完整代码 五、报告文件爬取5.1 提…...

Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决

Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决 问题背景 在一个基于 Spring Cloud Gateway WebFlux 构建的微服务项目中&#xff0c;新增了一个本地验证码接口 /code&#xff0c;使用函数式路由&#xff08;RouterFunction&#xff09;和 Hutool 的 Circle…...

AGain DB和倍数增益的关系

我在设置一款索尼CMOS芯片时&#xff0c;Again增益0db变化为6DB&#xff0c;画面的变化只有2倍DN的增益&#xff0c;比如10变为20。 这与dB和线性增益的关系以及传感器处理流程有关。以下是具体原因分析&#xff1a; 1. dB与线性增益的换算关系 6dB对应的理论线性增益应为&…...

面向无人机海岸带生态系统监测的语义分割基准数据集

描述&#xff1a;海岸带生态系统的监测是维护生态平衡和可持续发展的重要任务。语义分割技术在遥感影像中的应用为海岸带生态系统的精准监测提供了有效手段。然而&#xff0c;目前该领域仍面临一个挑战&#xff0c;即缺乏公开的专门面向海岸带生态系统的语义分割基准数据集。受…...

Go语言多线程问题

打印零与奇偶数&#xff08;leetcode 1116&#xff09; 方法1&#xff1a;使用互斥锁和条件变量 package mainimport ("fmt""sync" )type ZeroEvenOdd struct {n intzeroMutex sync.MutexevenMutex sync.MutexoddMutex sync.Mutexcurrent int…...

MacOS下Homebrew国内镜像加速指南(2025最新国内镜像加速)

macos brew国内镜像加速方法 brew install 加速formula.jws.json下载慢加速 &#x1f37a; 最新版brew安装慢到怀疑人生&#xff1f;别怕&#xff0c;教你轻松起飞&#xff01; 最近Homebrew更新至最新版&#xff0c;每次执行 brew 命令时都会自动从官方地址 https://formulae.…...