PCM data flow - 7 - Frame & Period

2023-10-27

后面章节将分析 dma buffer 的管理,其中细节需要对音频数据相关概念有一定的了解。因此本章说明下音频数据中的几个重要概念:

  • Sample:样本长度,音频数据最基本的单位,常见的有 8 位和 16 位;
  • Channel:声道数,分为单声道 mono 和立体声 stereo;
  • Frame:帧,构成一个完整的声音单元,Frame = Sample * channel
  • Rate:又称 sample rate,采样率,即每秒的采样次数,针对帧而言;
  • Period size:周期,每次硬件中断处理音频数据的帧数,对于音频设备的数据读写,以此为单位;
  • Buffer size:数据缓冲区大小,这里指 runtime 的 buffer size,而不是结构图 snd_pcm_hardware 中定义的 buffer_bytes_max;一般来说 buffer_size = period_size * period_count, period_count 相当于处理完一个 buffer 数据所需的硬件中断次数。

下面一张图直观的表示 buffer/period/frame/sample 之间的关系:
pcm_data_concept
这个 buffer 中有 16 个 period,每当 DMA 搬运完一个 period 的数据就会出生一次中断,因此搬运这个 buffer中的数据将产生 16 次中断。ALSA 为什么这样做?因为数据缓存区可能很大,一次传输可能会导致不可接受的延迟;为了解决这个问题,所以将缓存区拆分成多个周期 period,以周期 period 为单元传输数据。

7.1. Frames & Periods

敏感的读者会察觉到 period 和 buffer size 在 PCM 数据搬运中扮演着非常重要角色。下面引用两段来自 alsa 官网对 Period 的详细解释:
Period

The interval between interrupts from the hardware. This defines the input latency, since the CPU will not have any idea that there is data waiting until the audio interface interrupts it.
The audio interface has a “pointer” that marks the current position for read/write in its h/w buffer. The pointer circles around the buffer as long as the interface is running.
Typically, there are an integral number of periods per traversal of the h/w buffer, but not always. There is at least one card (ymfpci) that generates interrupts at a fixed rate indepedent of the buffer size (which can be changed), resulting in some “odd” effects compared to more traditional designs.
Note: h/w generally defines the interrupt in frames, though not always.
Alsa’s period size setting will affect how much work the CPU does. if you set the period size low, there will be more interrupts and the work that is done every interrupt will be done more often. So, if you don’t care about low latency, set the period size large as possible and you’ll have more CPU cycles for other things. The defaults that ALSA provides are in the middle of the range, typically.
(from an old AlsaDevel thread[1], quoting Paul Davis)
Retrieved from “http://alsa.opensrc.org/Period

FramesPeriods

A frame is equivalent of one sample being played, irrespective of the number of channels or the number of bits. e.g.
- 1 frame of a Stereo 48khz 16bit PCM stream is 4 bytes.
- 1 frame of a 5.1 48khz 16bit PCM stream is 12 bytes.
A period is the number of frames in between each hardware interrupt. The poll() will return once a period.
The buffer is a ring buffer. The buffer size always has to be greater than one period size. Commonly this is 2*period size, but some hardware can do 8 periods per buffer. It is also possible for the buffer size to not be an integer multiple of the period size.
Now, if the hardware has been set to 48000Hz , 2 periods, of 1024 frames each, making a buffer size of 2048 frames. The hardware will interrupt 2 times per buffer. ALSA will endeavor to keep the buffer as full as possible. Once the first period of samples has been played, the third period of samples is transfered into the space the first one occupied while the second period of samples is being played. (normal ring buffer behaviour).

Additional example
Here is an alternative example for the above discussion.
Say we want to work with a stereo, 16-bit, 44.1 KHz stream, one-way (meaning, either in playback or in capture direction). Then we have:
- ‘stereo’ = number of channels: 2
- 1 analog sample is represented with 16 bits = 2 bytes
- 1 frame represents 1 analog sample from all channels; here we have 2 channels, and so: 1 frame = (num_channels) * (1 sample in bytes) = (2 channels) * (2 bytes (16 bits) per sample) = 4 bytes (32 bits)
- To sustain 2x 44.1 KHz analog rate - the system must be capable of data transfer rate, in Bytes/sec: Bps_rate = (num_channels) * (1 sample in bytes) * (analog_rate) = (1 frame) * (analog_rate) = ( 2 channels ) * (2 bytes/sample) * (44100 samples/sec) = 2*2*44100 = 176400 Bytes/sec

Now, if ALSA would interrupt each second, asking for bytes - we’d need to have 176400 bytes ready for it (at end of each second), in order to sustain analog 16-bit stereo @ 44.1Khz.
- If it would interrupt each half a second, correspondingly for the same stream we’d need 176400/2 = 88200 bytes ready, at each interrupt;
- if the interrupt hits each 100 ms, we’d need to have 176400*(0.1/1) = 17640 bytes ready, at each interrupt.

We can control when this PCM interrupt is generated, by setting a period size, which is set in frames.
- Thus, if we set 16-bit stereo @ 44.1Khz, and the period_size to 4410 frames => (for 16-bit stereo @ 44.1Khz, 1 frame equals 4 bytes - so 4410 frames equal 4410*4 = 17640 bytes) => an interrupt will be generated each 17640 bytes - that is, each 100 ms.
- Correspondingly, buffer_size should be at least 2*period_size = 2*4410 = 8820 frames (or 8820*4 = 35280 bytes).

It seems (writing-an-alsa-driver.pdf), however, that it is the ALSA runtime that decides on the actual buffer_size and period_size, depending on: the requested number of channels, and their respective properties (rate and sampling resolution) - as well as the parameters set in the snd_pcm_hardware structure (in the driver).
Also, the following quote may be relevant, from “(alsa-devel) Questions about writing a new ALSA driver for a very limitted device”:

The “frame” represents the unit, 1 frame = # channels x sample_bytes.
In your case, 1 frame corresponds to 2 channels x 16 bits = 4 bytes.

The periods is the number of periods in a ring-buffer. In OSS, called
as “fragments”.

So,
- buffer_size = period_size * periods
- period_bytes = period_size * bytes_per_frame
- bytes_per_frame = channels * bytes_per_sample

I still don’t understand what ‘period_size’ and a ‘period’ is?

The “period” defines the frequency to update the status, usually via the invokation of interrupts. The “period_size” defines the frame sizes corresponding to the “period time”. This term corresponds to the “fragment size” on OSS. On major sound hardwares, a ring-buffer is divided to several parts and an irq is issued on each boundary. The period_size defines the size of this chunk.

On some hardwares, the irq is controlled on the basis of a timer. In this case, the period is defined as the timer frequency to invoke an irq.

这里不做翻译了,简单说下 Frame 和 Period 要点:

  • Frame:帧,构成一个完整的声音单元,它的大小等于 sample_bits * channels;
  • Peroid:周期大小,即每次 dma 硬件中断处理音频数据的帧数。如果周期大小设定得较大,则单次处理的数据较多,这意味着单位时间内硬件中断的次数较少,CPU 也就有更多时间处理其他任务,功耗也更低,但这样也带来一个显著的弊端——数据处理的时延会增大。

再说说 period bytes,对于 dma 处理来说,它关心的是数据大小,而不管 period size 和 period count,因此有个转换关系:

period_bytes = period_size * sample_bits * channels / 8

由于 I2S 总线采样率是稳定的,我们可以计算 I2S 传输一个周期的数据所需的时间:

transfer_time = 1 * period_size / sample_rate, in second

例如 period_size = 1024,sample_rate = 48KHz 时,那么传输时间为 1 * 1024 / 48000 = 21.3 (ms)。

7.2. hrtimer 模拟 PCM 周期中断

4.2.1. pcm operations 章节中,我们提到:每次 dma 传输完成一个周期的数据传输后,都要调用 snd_pcm_period_elapsed() 告知 pcm native 一个周期的数据已经传送到 FIFO 上了,然后再次调用 dma 传输音频数据…如此循环。
但有些 Platform 可能由于设计如此或设计缺陷,dma 传输完一个周期的数据不会产生硬件中断。这样系统如何知道什么时候传输完一个周期的数据了呢?在上个章节的最后,我们提到 I2S 总线传输一个周期的数据所需的时间,这其实也是 dma 搬运一个周期的数据所需的时间,这很容易理解:I2S FIFO 消耗完一个周期的数据,dma 才接着搬运一个周期的数据到 I2S FIFO。
因此我们可以用定时器来模拟这种硬件中断:

  • 1). 触发dma搬运数据时,启动定时器开始计时;
  • 2). 当定时到 1 * period_size / sample_rate,这时 I2S 已传输完一个周期的音频数据了,进入定时器中断处理:调用 snd_pcm_period_elapsed() 告知 pcm native 一个周期的数据已经处理完毕了,同时准备下一次的数据搬运;
  • 3). 继续执行步骤 1)…

为了更好保证数据传输的实时性,建议采用高精度定时器 hrtimer。

作者见过至少两家芯片在传输音频数据时需要用定时器模拟周期中断,一是 MTK 的智能手机处理器,二是 Freescale 的 i.MX 系列处理器。后者已经合入 Linux 内核代码,具体见:sound/soc/imx/imx-pcm-fiq.c,这里简略分析:

// 定时器中断处理例程
static enum hrtimer_restart snd_hrtimer_callback(struct hrtimer *hrt)
{
    ...

    /* If we've transferred at least a period then report it and
     * reset our poll time */
    if (delta >= iprtd->period) {
        snd_pcm_period_elapsed(substream); // 告知 pcm native 一个周期的数据已经处理完毕
        iprtd->last_offset = iprtd->offset;
    }

    hrtimer_forward_now(hrt, ns_to_ktime(iprtd->poll_time_ns)); // 重新计时,poll_time_ns:I2S 传输一个周期的数据所需的时间
    return HRTIMER_RESTART;
}

// hw_params 回调,数据传输开始前,先设置 dma 传输参数
static int snd_imx_pcm_hw_params(struct snd_pcm_substream *substream,
                struct snd_pcm_hw_params *params)
{
    struct snd_pcm_runtime *runtime = substream->runtime;
    struct imx_pcm_runtime_data *iprtd = runtime->private_data;

    iprtd->size = params_buffer_bytes(params);    // dma 缓冲区大小
    iprtd->periods = params_periods(params);      // 周期数
    iprtd->period = params_period_bytes(params) ; // 周期大小
    iprtd->offset = 0;
    iprtd->last_offset = 0;
    iprtd->poll_time_ns = 1000000000 / params_rate(params) *
                params_period_size(params);       // 计算 I2S 传输一个周期的数据所需的时间
    snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); // 设置 dma 缓冲区

    return 0;
}

// trigger 回调,触发 dma 传输或停止
static int snd_imx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
{
    struct snd_pcm_runtime *runtime = substream->runtime;
    struct imx_pcm_runtime_data *iprtd = runtime->private_data;

    switch (cmd) {
    case SNDRV_PCM_TRIGGER_START:
    case SNDRV_PCM_TRIGGER_RESUME:
    case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
        atomic_set(&iprtd->running, 1);
        hrtimer_start(&iprtd->hrt, ns_to_ktime(iprtd->poll_time_ns), // 准备传输数据,启动定时器,开始计时
              HRTIMER_MODE_REL);
        if (++fiq_enable == 1)
            enable_fiq(imx_pcm_fiq); // 开始 dma 传输

        break;

    case SNDRV_PCM_TRIGGER_STOP:
    case SNDRV_PCM_TRIGGER_SUSPEND:
    case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
        atomic_set(&iprtd->running, 0);

        if (--fiq_enable == 0)
            disable_fiq(imx_pcm_fiq); // 停止 dma 传输

        break;
    default:
        return -EINVAL;
    }

    return 0;
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

PCM data flow - 7 - Frame & Period 的相关文章

  • alsa音频调试

    alsa音频调试 1 找不到配置项 在amixer controls指令找不到Master Playback Volume配置项 xff0c 查阅资料可知 softval类型有些需要配置一次才能出现 xff0c 执行指令 在etc asou
  • 菜鸟修炼笔记-alsa-调节音频音量大小

    alsa 调节音频音量大小 前言一 方法一 xff1a 直接放大缓存中的数据1 基本原理2 相关尝试和结果2 1 在播放前放大音频缓存数据2 2 在录制前放大缓存 二 方法二 xff1a 在linux终端直接设置alsa的参数 1 基本原理
  • Linux 下ALSA音频工具amixer,aplay,arecord使用

    ALSA音频工具amixer aplay arecord ALSA音频工具编译安装 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61
  • alsa amixer 使用介绍

    alsa utils 提供的工具中 xff0c arecord 可以用来录音 xff0c aplay 可以用来播放 xff0c amixer 可以用来控制音量 增益等 amixer controls numid 61 34 iface 61
  • PCM data flow - 7 - Frame & Period

    后面章节将分析 dma buffer 的管理 其中细节需要对音频数据相关概念有一定的了解 因此本章说明下音频数据中的几个重要概念 Sample 样本长度 音频数据最基本的单位 常见的有 8 位和 16 位 Channel 声道数 分为单声道
  • DAPM之二:audio paths与dapm kcontrol

    在用alsa amixer controls时 除了我们之前提到的snd soc add controls添加的kcontrols外 还有一些多出来的controls 其实多出来的那些都是属于dapm kcontrol 主要用于切换音频路径
  • 以 root 身份运行 python 脚本

    我有以下脚本 usr bin env python import sys import pyttsx def main print running
  • 使用 PyAudio 防止 ALSA 欠载

    我编写了一个小程序 它记录麦克风的声音并将其通过网络发送并在那里播放 我正在使用 PyAudio 来完成此任务 它工作得几乎很好 但在两台计算机上我都从 ALSA 收到错误 表明发生了欠载 我在谷歌上搜索了很多相关内容 现在我知道什么是欠载
  • GNU Radio:使用声音输出作为输入源

    In gnuradio 伴侣我使用音频源块作为下一个块的输入信号 一切工作几乎都很好 唯一的小问题是我从麦克风收到信号 这是正常行为 我宁愿直接播放音频信号 而不必通过我的扬声器 我房间的空气和麦克风 所有这些都会产生信号损失并增加噪声 我
  • 从 C 代码设置 ALSA 主音量

    我一直在寻找一个简单的 C 代码示例来设置 ALSA 混音器的主音量 但找不到任何简单的内容来完成这个所谓的常见操作 我对 ALSA 完全不熟悉 所以制作我自己的最小示例需要时间 如果有人能提供一个 我会很高兴 以下内容对我有用 参数体积应
  • 在 NodeJS 中写入音频文件时读取音频文件

    我正在使用 ffmpeg 通过 alsa 捕获音频并将其写入 wav 文件 但在编写过程中 我需要将捕获的音频发送给第三方 我尝试过几种方法 包括节点生长文件但没能成功 有没有一种方法可以将文件作为流读取 只要它正在写入并根据需要进行处理
  • Alsa全双工通信

    我想使用alsa实现全双工通信 我首先编写了捕获和回放程序 并使用 UDP 通信将数据从捕获的进程传输到回放进程 当我运行两个进程时工作正常 其中一个正在捕获 另一个正在播放 将其视为从 A 到 B 的半双工 当我尝试实现另一个半双工 从
  • alsa_aplay 不在 Android 上录制

    我刚刚开始研究 android 我试图了解 android 音频子系统 alsa 是如何工作的 我正在 windows 7 64 位 上运行的虚拟盒中运行 android 映像 我正在摆弄 alsa utils 来录制声音 我试过alsa
  • ALSA:不支持非交错访问?

    ALSA s snd pcm hw params set access http www alsa project org alsa doc alsa lib group p c m h w params html ga4c8f1c6329
  • arecord 创建的多个文件

    我使用 buildroot 进行了自定义分发 并为 ARMv7 处理器提供了硬流 一切正常 除了 arecord D hw 0 0 fdat d 5 test wav 这会生成多个文件 其中有数千人 rw r r 1 root root 9
  • 以编程方式在 Linux 上查找可用的声卡

    有没有办法使用 asoundlib 和 C 以编程方式获取系统上可用声卡的列表 我想要它具有相同的信息 proc asound cards 您可以使用迭代卡片snd card next 从值 1开始获得第0张牌 这是示例代码 编译它gcc
  • ALSA 记录 - 了解内存映射

    我尝试使用 ALSA 从 USB 音频设备获取输入并将其作为一系列内容写入磁盘signed short价值观 我最终得到的是看似有效的数据块 其中散布着大块的零 我猜测我的缓冲区设置不正确并且没有正确使用内存映射 我正在尝试什么 采样率 8
  • 枚举捕获 ALSA 设备并从中捕获

    我正在编写一个 C 程序 我想枚举系统中的所有捕获设备 实际上 我知道我有三个网络摄像头加上 集成 麦克风 识别它们并同时开始捕获它们 我使用 snd device name hint 枚举所有 PCM 设备 然后使用 snd device
  • 通话录音 - 使其在 Nexus 5X 上运行(可以生根或定制 ROM)

    我正在尝试使用AudioRecord with AudioSource VOICE DOWNLINK在 Nexus 5X Android 7 1 我自己的 AOSP 版本 上 我已经过了权限阶段 将我的 APK 移至特权应用程序 并进行了调
  • 在 Linux 上使用 PyAudio 列出设备

    在 Linux 上列出音频设备时 我尝试使用 Raspbian RaspberryPi import pyaudio p pyaudio PyAudio for i in range p get device count print p g

随机推荐

  • Parcel是如何存储数据

    Parcel学习 Parcel是如何存储数据的 第一步 第二步 第三步 如何读取呢 Parcel和serializable Parcel是如何存储数据的 疑问 平时java使用它进行序列化要关注的点就是读写顺序 为什么要注意读写顺序呢 在这
  • python基础十一:异常处理以及文件操作

    1 异常 1 1异常简介 程序在运行过程中可能会出现一些错误 比如 使用了不存在的索引 两个不同类型的数据相加 这些错误我们称之为异常 处理异常 程序运行时出现异常 目的并不是让我们的程序直接终止 Python是希望在出现异常时 我们可以编
  • 前端js计算导致的精度问题解决方案

    前言 js计算会导致很多奇奇怪怪的问题 这是由于js使用二进制 有一些浮点数用二进制表示时是无穷的 而为节省存储空间只存储64位 精度丢失从而导致计算出现问题 一 写个方法 自己写一个方法来解决 二 使用插件 使用 number preci
  • (二)ChatGLM-6B模型部署以及ptuning微调详细教程

    文章目录 介绍什么是ChatGLM 6B Torch 安装ChatGLM 6B模型 安装过程 Ptuning微调 安装过程 初始化环境 训练 准备自己的数据集 推理 验证 问题和思考 泛化学习 simbert 不属于必学 介绍什么是Chat
  • CSS Modules

    CSS Modules CSS模块 对CSS进行模块化处理 目的 解决在 React 开发时 组件之间类名重复导致的样式冲突问题 使用后 会自动生成类名 类名格式 filename classname hash filename 文件名 c
  • 华为防火墙IPsec点对点配置解析

    一 完成基本互联 主机直连的接口为trust区域 防火墙之间互联的接口为untrust区域 二 左边的防火墙IPsec的配置 1 Ike Proposal 的创建 ike proposal xx 首先创建ike proposal xx 这一
  • 在PHP脚本中的转义字符 \

    在编写PHP脚本的过程中 经常在遇到写路径或者某些特殊字符时 要用到转义字符 反斜线 比如 在使用fopen 函数的时候 我们要写参数 即要打开的路径 fp fopen DOCUMENT ROOT orders orders txt w 在
  • Triangle Tessellation with OpenGL 4.0

    FROM http prideout net blog p 48 This is the first of a two part article on tessellation shaders with OpenGL 4 0 This en
  • AVPlayer 视频播放

    1 AVPlayer AVPlayer 是一个用来播放基于时间的视听媒体的控制器对象 一个队播放和资源时间相隔信息进行管理的对象 而非一个视图或窗口控制器 AVPlayer支持播放从本地 分步下载或通过HTTP Live Streaming
  • 2023版golang面试题100道(map)

    面试题合集目录 map查找 假设当前 B 4 即桶数量为2 B 16个 要从map中获取k4对应的value 外链图片转存失败 源站可能有防盗链机制 建议将图片保存下来直接上传 k4的查找步骤 计算k4的hash值 通过低B位来确定在哪号桶
  • 静态代码和动态代码的区别_静态和动态代码分析之间有什么区别,您如何知道使用哪个?...

    让我们从一个运动类比开始 以帮助说明这两种方法之间的差异 静态代码分析类似于练习网和投球机练习棒球挥杆 最小的惊喜 经过几次挥杆后 您每次都知道球的确切位置 这有助于处理基础知识并确保您拥有良好的形式 虽然这有助于改善你的游戏 但它只能让你
  • 订单管理系统

    本专栏介绍了使用Qt开发的一些小型桌面软件 其中包括软件功能介绍 软件截图 主要代码等内容 此外 本专栏还提供完整的软件源码和安装包供有需要的同学下载 我的目标是开发一些简洁美观且实用的客户端小软件 如果能够为大家提供有用的软件或对学习有益
  • Hypertable 快速安装,仅需上载一个RPM包,零编译

    Hypertable 快速安装 仅需上载一个RPM包 零编译 Hypertable 快速安装 仅需下载一个RPM包 零编译 本文采用 单机安装 1 Hypertable 安装 Hypertable 的几种安装方式 单机 安装于单机 采用本地
  • Arduino core for the ESP32 安装失败问题处理方法

    文章目录 目的 离线开发板数据包 鱼 安装最新开发板数据包 渔 总结 目的 理论上Arduino IDE安装开发板数据包是非常方便的 不过在国内的网络环境下有时候就会很纠结 另外Arduino IDE对于下载数据这块也存在问题 经常下着下着
  • SQL语句连接筛选条件放在on和where后的区别(一篇足矣)

    sql查询这个东西 要说它简单 可以很简单 通常情况下只需使用增删查改配合编程语言的逻辑表达能力 就能实现所有功能 但是增删查改并不能代表sql语句的所有 完整的sql功能会另人望而生畏 就拿比普通增删查改稍微复杂一个层次的连接查询来说 盲
  • 【BP时序预测】基于BP神经网络的时间序列预测附matlab完整代码

    作者简介 热爱科研的Matlab仿真开发者 修心和技术同步精进 matlab项目合作可私信 个人主页 Matlab科研工作室 个人信条 格物致知 更多Matlab仿真内容点击 智能优化算法 神经网络预测 雷达通信 无线传感器 电力系统 信号
  • PY32F003F18之RS485通讯

    PY32F003F18将USART2连接到RS485芯片 和其它RS485设备实现串口接收后再转发的功能 一 测试电路 二 测试程序 include USART2 h include stdio h getchar putchar scan
  • 单链表中什么时候使用二级指针

    在使用单链表时 一直有一个疑惑 初始化单链表时为什么要用二级指针 代码如下 typedef int ElemType ElemType类型根据实际情况而定 这里假设为int typedef struct Node ElemType data
  • CH9-HarmonyOS传感器和媒体管理

    文章目录 前言 目标 传感器概述 运动类传感器 运动类传感器工作原理 主流传感器表示 运作机制 核心模块 接口说明 开发步骤 使用传感器 方向传感器调用示例 相机调用 基本概念 主要接口 位置传感器 位置能力 基本概念 运作机制 获取设备的
  • PCM data flow - 7 - Frame & Period

    后面章节将分析 dma buffer 的管理 其中细节需要对音频数据相关概念有一定的了解 因此本章说明下音频数据中的几个重要概念 Sample 样本长度 音频数据最基本的单位 常见的有 8 位和 16 位 Channel 声道数 分为单声道