FFmepg-- 38-ffplay源码-缓冲区 audio_buf调试


缓冲区 audio_buf 调试过程

采样率(Sample Rate)

定义:每秒钟对音频信号进行多少次采样。
单位:Hz(赫兹)
例子:48,000 Hz 表示每秒采集 48,000 个样本(每个声道)。
作用:决定音频的频率响应范围(根据奈奎斯特采样定理,最高可还原频率为采样率的一半,即 24 kHz)。
注意:采样率是时间维度上的密度,和“一帧有多少样本”无关。

每帧样本数(Samples per Frame)

定义:一次处理/传输的音频块中包含多少个采样点(每个声道)。
例子:1024 样本/帧 表示这一帧音频中,左声道有 1024 个 float 值,右声道也有 1024 个 float 值(因为是立体声)。
作用:影响延迟和处理效率。帧越大,延迟越高但 CPU 开销可能更低。
关键点:这是“批量处理”的单位,和采样率无直接关系,但结合采样率可以算出一帧持续多长时间:

帧时长= 1024  samples 48000  samples/sec ≈ 21.33  ms \frac{1024 \text{ samples}}{48000 \text{ samples/sec}} ≈ 21.33 \text{ ms} 48000 samples/sec1024 samples21.33 ms

总字节数的计算(重采样后,S16 格式)

重采样后的格式是 AV_SAMPLE_FMT_S16(packed signed 16-bit integer),立体声(2 声道),每帧仍是 1024 个样本(每声道)。

每个样本占多少字节?
S16:16 位 = 2 字节。

每帧总样本数是多少?
立体声:2 声道 × 1024 样本/声道 = 2048 个样本(注意:这里是“样本总数”,不是“每声道样本数”)。

总字节数 = 样本总数 × 每样本字节数
2048 samples × 2 bytes/sample = 4096 bytes
所以 4096 字节是正确的。

补充:为什么 SDL 回调请求 2048 字节?

SDL 音频回调函数每次被调用时,会要求提供 len 字节的 PCM 数据(这里是 2048 字节)。

2048 字节(S16, stereo)对应多少样本?
2048  bytes 2  bytes/sample × 2  channels = 512  samples per channel \frac{2048 \text{ bytes}}{2 \text{ bytes/sample} × 2 \text{ channels}} = 512 \text{ samples per channel} 2 bytes/sample×2 channels2048 bytes=512 samples per channel

一帧 1024 样本(每声道)可满足两次 SDL 回调(因为 1024 ÷ 512 = 2)。

解码输出的 AVFrame

格式:AV_SAMPLE_FMT_FLTP(planar float)
采样率:48,000 Hz
声道数:2(立体声)
每帧样本数:1024(每个声道 1024 个 float 样本)

SDL 设备实际支持格式(is->audio_tgt)

格式:AV_SAMPLE_FMT_S16(packed signed 16-bit int)
采样率:48,000 Hz
声道数:2
无需改变采样率或声道,但需格式转换(FLTP → S16)

重采样后数据大小计算

每样本 2 字节(S16)
总字节数 = 1024 samples × 2 channels × 2 bytes = 4096 bytes
SDL 回调每次请求:len = 2048 bytes(即每次要 2048 字节 PCM 数据)

缓冲区变量

is->audio_buf_size
含义:当前 audio_buf 缓冲区中有效 PCM 数据的总字节数。
单位:字节(bytes)
何时设置:
每次调用 audio_decode_frame() 成功后;
该函数完成解码 + 重采样,并将结果写入 is->audio_buf;
然后将实际写入的字节数赋值给 is->audio_buf_size。

📌 举例:
若重采样输出 1024 个 stereo S16 样本,则:
audio_buf_size = 1024 × 2(声道) × 2(字节/S16) = 4096

is->audio_buf_index
含义:下一次从 audio_buf 中读取数据的起始位置(偏移量)。
单位:字节(bytes)
初始值:每次填入新数据后被重置为 0
变化方式:
在 sdl_audio_callback 中,每拷贝一段数据到 SDL,就增加相应的字节数;
当 audio_buf_index >= audio_buf_size 时,表示当前缓冲区已读完,需要加载下一帧。

全局状态变量初始值(在播放开始前)

is->audio_buf        = NULL;     // 尚未分配
is->audio_buf_size   = 0;
is->audio_buf_index  = 0;

第一次 SDL 回调发生

SDL 调用回调

sdl_audio_callback(is, stream, 2048);

检查缓冲区是否可用

if (is->audio_buf_index >= is->audio_buf_size)  // 0 >= 0 → true

缓冲区为空,需要填充新数据。

调用 audio_decode_frame(is)
从音频帧队列 sampq 中取出一帧(FLTP, 48kHz, 2ch, 1024 samples)
检查格式:frame.format != is->audio_src.fmt(因为 audio_src 初始等于 audio_tgt,即 S16) → 需要重采样
创建 SwrContext(FLTP → S16,其他参数相同)
调用 swr_convert(…),输出 4096 字节 S16 PCM 数据
分配/扩容 is->audio_buf 至至少 4096 字节
将重采样结果写入 is->audio_buf
设置:

is->audio_buf_size  = 4096;
is->audio_buf_index = 0;   // 重置读指针

从 audio_buf 拷贝数据到 SDL 的 stream

len1 = min(4096 - 0, 2048) = 2048
memcpy(stream, is->audio_buf + 0, 2048);

更新状态

is->audio_buf_index = 0 + 2048 = 2048;
// len -= 2048 → 0,退出 while 循环

第一次回调结束

已播放前 2048 字节(即前 512 个 stereo 样本)
audio_buf 还剩 2048 字节未读

第二次 SDL 回调发生(约 21.3ms 后)

48kHz 立体声下,2048 字节 = 2048 / (2×2) = 512 samples → 播放时长 = 512 / 48000 ≈ 10.7ms
两次回调间隔约 10~20ms,取决于 SDL 缓冲设置

SDL 调用回调

sdl_audio_callback(is, stream, 2048);

检查缓冲区

is->audio_buf_index = 2048
is->audio_buf_size  = 4096

2048 < 4096 → 缓冲区还有数据!跳过 audio_decode_frame

直接拷贝剩余数据

len1 = min(4096 - 2048, 2048) = 2048
memcpy(stream, is->audio_buf + 2048, 2048);

更新状态

is->audio_buf_index = 2048 + 2048 = 4096;

第二次回调结束

播放完剩下的 2048 字节(后 512 个 stereo 样本)
整个音频帧(1024 samples)已全部送出
audio_buf_index == audio_buf_size → 下次回调将触发新帧加载

第三次 SDL 回调发生

SDL 调用回调

sdl_audio_callback(is, stream, 2048);

检查缓冲区

is->audio_buf_index (4096) >= is->audio_buf_size (4096) → true

缓冲区耗尽,需要新数据。

再次调用 audio_decode_frame(is)
从队列取下一帧(假设同样是 FLTP, 48kHz, 2ch, 1024 samples)
此时 is->audio_src 已更新为上一帧的格式(FLTP…),而新帧格式相同 → 无需重建 SwrContext(复用已有上下文)
swr_convert 输出新的 4096 字节 S16 数据
覆盖写入 is->audio_buf(或原地重用内存)
重置:

is->audio_buf_size  = 4096;
is->audio_buf_index = 0;

拷贝前 2048 字节

memcpy(stream, is->audio_buf + 0, 2048);
is->audio_buf_index = 2048;

第三次回调结束

开始播放第二帧的前半部分
流程回到与第一次回调相同的状态

循环往复

只要播放不停止,这个“填满 → 分两次读完 → 再填满”的过程就会持续下去。

补充说明:边界情况处理

情况 1:一帧小于回调需求(如只有 1000 字节)
第一次回调:拷贝全部 1000 字节;
仍缺 1048 字节 → 继续循环,再次调用 audio_decode_frame 取下一帧;
从新帧中拷贝剩余 1048 字节;
→ 一次回调可能消耗多个音频帧

情况 2:队列为空(如刚 seek 或 EOF)
audio_decode_frame 返回 < 0;
ffplay 填充静音(silence_buf,全零);
避免回调无数据导致爆音或崩溃。

总结:数据流与状态变迁

回调次数触发动作audio_buf_index 变化数据来源
1首次填充帧 → 重采样0 → 2048第1帧(重采样后)
2直接读剩余2048 → 4096第1帧(剩余)
3填充新帧 → 重采样(复用上下文)0 → 2048第2帧(重采样后)
4读剩余2048 → 4096第2帧(剩余)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

八月的雨季997

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值