audio_open函数分析

news2025/1/23 7:13:16

audio_open() 的作用,就如同它的名字那样,就是打开音频设备。流程图如下:


SDL 库播放音频数据有两种方式。

1,调用层定时往 SDL 接口塞数据。

2,设置SDL回调函数,让 SDL 来主动执行回调函数来取数据。

第二种方式的实时性更好,ffplay 也是用的第二种。


先介绍一下 audio_open() 函数的各个参数,如下:

static int audio_open(void *opaque, int64_t wanted_channel_layout, int wanted_nb_channels, int wanted_sample_rate, struct AudioParams *audio_hw_params)

1,void *opaque,传递给 SDL 回调函数的参数。

2,wanted_channel_layoutwanted_nb_channelswanted_sample_rate,希望用 这样的采样率,声道数,声道布局打开音频硬件设备。

3,struct AudioParams *audio_hw_params,实际打开的音频硬件设备的音频格式信息。这里的 hw 是 Hardware 的意思,也就是硬件,不过不是指硬件编解码加速,而是指打开的硬件设备。

这些参数是这样的,想要的音频格式 跟 实际打开的音频格式 不一定一样,例如,MP4 里面的音频流是 48000 采样的,肯定想要用 48000 的格式打开音响设备,这样才能听到最好的音质。但是难免有些音响设备太差,只支持 44100。


下面来分析一下 audio_open() 函数的重点代码,如下:

第一个重点就是这两个数组变量,next_nb_channels[] 跟 next_sample_rates[],这两个变量会让人摸不着头脑,即便看了后面的 while 逻辑,也不太容易看懂他们的含义。

现在我直接剧透这量个变量的作用。首先是 next_nb_channels[],这 其实是一个map表,声道切换映射表。举个例子,如果音响设备不支持 7 声道的数据播放,肯定不能直接报错,还要尝试一下其他声道能不能成功打开设备吧。这个其他声道就是 next_nb_channels[]

  • next_nb_channels[7] = 6,从7声道切换到6声道打开音频设备
  • next_nb_channels[6] = 4,从6声道切换到4声道打开音频设备
  • next_nb_channels[5] = 6,从5声道切换到6声道打开音频设备
  • next_nb_channels[4] = 2,从4声道切换到2声道打开音频设备
  • next_nb_channels[3] = 6,从3声道切换到6声道打开音频设备
  • next_nb_channels[2] = 1,从双声道切换到单声道打开音频设备
  • next_nb_channels[1] = 0,单声道都打不开音频设备,无法再切换,需要降低采样率播放。
  • next_nb_channels[0] = 0,0声道都打不开音频设备,无法再切换,需要降低采样率播放。

而 next_sample_rates[] 变量存储的仅仅是采样率,当切换所有声道都无法成功打开音频设备,就需要从 next_sample_rates[] 取一个比当前更小的采样率来尝试。

所以可以看到 next_sample_rates[] 的元素都是从小到大的。

audio_open() 函数的第二个重点是 声道数,声道布局等参数的校验,如下:

ffplay 经常会校验 声道布局 跟 声道数是否一致,可能是担心用户在命令行输入错误的参数。

此外还需注意一下 next_sample_rate_idx 变量,这个变量存的是比 想要打开的音频采样率 更小一点的采样率的索引。

while (next_sample_rate_idx && next_sample_rates[next_sample_rate_idx] >= wanted_spec.freq)
        next_sample_rate_idx--;

audio_open() 函数的第三个重点是 设置打开参数,设置回调函数 ,如下:

可以看到,ffpaly 是写死了采样格式,写死成了 AUDIO_S16SYS 格式。也就是说,无论MP4里面的音频采样格式是 64 还是 32 位,还是其他,统统都会先提前转成 AUDIO_S16SYS,然后再丢给 SDL 播放。

另外,wanted_spec.samples 的作用是告诉 SDL 每次回调取多少样本数来播放,实际上就是控制 SDL 每秒调用回调函数的次数。

因为播放采样率是固定的,例如 48000。也就是音频设备每秒要播放 48000 个样本,如果调一次 sdl_audio_callback 只能取4800个样本,那他一秒内就需要调 10 次 sdl_audio_callback

同理,只要固定了调用次数,也就能计算出 SDL 每次回调需要取多少样本数。而 ffplay 就固定了每秒回调 SDL_AUDIO_MAX_CALLBACKS_PER_SEC (30次)。

wanted_spec.samples 的计算方式有点难懂,如下:

wanted_spec.samples = FFMAX(SDL_AUDIO_MIN_BUFFER_SIZE, 2 << av_log2(wanted_spec.freq / SDL_AUDIO_MAX_CALLBACKS_PER_SEC));

首先,SDL_AUDIO_MIN_BUFFER_SIZE 宏是 512,所以最小值是 512。但是 后面的用 av_log2 取指数,又 << 位移,是干什么呢?

答:wanted_spec.freq 是采样率,而 SDL_AUDIO_MAX_CALLBACKS_PER_SEC 代表 SDL 每秒调多少次回调函数。两者相除就能得出每次回调需要读取多少个样本数。这个逻辑非常容易理解。

av_log2 取指数 跟 << 位移 只是想把 样本数数量 变成 2 的倍数。这是 SDL 文档建议的,推荐阅读《SDL_OpenAudioDevice》。

疑问:打开音频设备失败之后,会进行降低采样率播放操作,wanted_spec.freq 可能会变,但是 wanted_spec.samples 没有跟着变。我也不清楚会不会有问题。


audio_open() 函数的第四个重点是 用 while 循环不断尝试各种 声道 跟 采样的组合来打开音频硬件设备,如下:

比较难懂的就是 next_nb_channels[FFMIN(7, wanted_spec.channels)],大家可以假设一下 wanted_spec.channels 如果是 7, next_nb_channels[FFMIN(7, wanted_spec.channels)] 等于多少呢?,推算过程如下:

next_nb_channels[FFMIN(7, 7)]
next_nb_channels[7] = 6

可以看到,如果 7 声道打开失败,就会切换成 6 声道进行尝试,跟上面说的那个映射是一致的。

而 next_sample_rates[next_sample_rate_idx--] 就是进行降低采样率,再尝试打开音频设备,之前说过,next_sample_rate_idx 是比 想要的采样率更小一点的值。

注意他的那句日志 No more combinations to try, audio open failed,其实这句日志就说明了这段代码的逻辑,就是尝试各种组合


提醒:SDL_OpenAudioDevice() 函数打开音频设备之后,回调函数是不会立即执行的,需要调 SDL_PauseAudioDevice() 来启动音频设备,这样回调函数才会执行。

if ((ret = decoder_start(&is->auddec, audio_thread, "audio_decoder", is)) < 0)
    goto out;
SDL_PauseAudioDevice(audio_dev, 0);
break;

最后一个重点就是,打开硬件设备成功之后,要记录一下是以什么样的采样率,声道数等等格式打开硬件设备的。当 AVFrame 与 硬件音频格式不一致时候需要进行转换。

audio_hw_params 是指硬件设备参数的意思,我个人看到 hw 第一反应会以为是硬件编解码加速,其实不是。

硬件参数有两个字段需要特别讲解一下 :

1,audio_hw_params->frame_size,一个音频样本占多少内存。

2,audio_hw_params->bytes_per_secaudio_hw_params->freq 数量的音频样本占多少内存。也就是一秒钟要播放多少内存的音频数据。

这两个字段都是用av_samples_get_buffer_size() 来计算的。

注意 :audio_hw_params 实际上就是 is->audio_tgt ,只是在函数内部换了个名字而已。


至此,audio_open() 函数就讲解完毕了,但是还要注意一下这个函数的返回值,如下:

audio_open() 的返回值,最后是赋值给 is->audio_hw_buf_size 的,如下:

我们可以看一些,SDL 头文件对这个 spec.size 的定义,如下:

typedef struct SDL_AudioSpec
{
    int freq;                   /**< DSP frequency -- samples per second */
    SDL_AudioFormat format;     /**< Audio data format */
    Uint8 channels;             /**< Number of channels: 1 mono, 2 stereo */
    Uint8 silence;              /**< Audio buffer silence value (calculated) */
    Uint16 samples;             /**< Audio buffer size in sample FRAMES (total samples divided by channel count) */
    Uint16 padding;             /**< Necessary for some compile environments */
    Uint32 size;                /**< Audio buffer size in bytes (calculated) */
    SDL_AudioCallback callback; /**< Callback that feeds the audio device (NULL to use SDL_QueueAudio()). */
    void *userdata;             /**< Userdata passed to callback (ignored for NULL callbacks). */
} SDL_AudioSpec;

Audio buffer size in bytes 指的是 SDL 内部音频数据缓存的大小,代表 SDL线程执行 sdl_audio_callback() 的时候,SDL 硬件内部还有多少字节音频的数据没有播放。

没错,SDL线程并不是没有音频数据可以播放了才调 sdl_audio_callback() 来拿数据,而是他内部还剩 audio_hw_buf_size 长度的数据就会调 sdl_audio_callback() 来拿数据,是提前拿数据的。这个 概念 以及 audio_hw_buf_size 变量 都特别重要,后面会用到。


推荐一个零声学院免费公开课程,个人觉得老师讲得不错,分享给大家:

Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK等技术内容,立即学习

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/105381.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ABAP 字符处理

场景1:是否只包含数字 str1 CO 0123456789 涉及关键字&#xff1a;CO&#xff0c;使用公式 str1 CO str2 。表示 str1 中 的每个字符 在 str2 中都能找到 类比&#xff1a;无 场景2&#xff1a;字符串1 是否 包含 字符串2 DATA str1 TYPE STRING VALUE hello world. DATA …

Pytest测试框架搭建需求及实现方案

目录 框架需求及实现方案 框架需求 实现方案 支持接口自动化、Web UI自动化及App自动化 可以批量运行用例并生成测试报告 测试完成发送邮件 提供灵活的运行方式&#xff0c;如按功能模块运行、按脚本运行、按用例等级运行等等 提供运行日志方便定位问题 支持切换环境 …

李沐精读论文:MAE 《Masked Autoencoders Are Scalable Vision Learners》

论文&#xff1a;Masked Autoencoders Are Scalable Vision Learners 别再无聊地吹捧了&#xff0c;一起来动手实现 MAE(Masked Autoencoders Are Scalable Vision Learners) 玩玩吧&#xff01; - 知乎 参考博文&#xff1a;MAE 论文逐段精读【论文精读】 - 哔哩哔哩 神洛华…

QT 学习笔记(十)

文章目录一、绘图1. 理论知识储备2. 画背景图3. 简单绘图4. 手动刷新窗口二、绘图实现代码1. 主窗口头文件 widget.h2. 主窗口头文件 widget.cpp由于每次代码都是在原有程序上修改&#xff0c;因此除了新建项目&#xff0c;不然一般会在学完后统一展示代码。 提示&#xff1a;具…

【Python机器学习】卷积神经网络卷积层、池化层、Flatten层、批标准化层的讲解(图文解释)

卷积神经网络 卷积神经网络&#xff08;convolutional neural network, CNN&#xff09;在提出之初被成功应用于手写字符图像识别&#xff0c;2012年的AlexNet网络在图像分类任务中取得成功&#xff0c;此后&#xff0c;卷积神经网络发展迅速&#xff0c;现在已经被广泛应用于…

怎样在Odoo 16中启用完整的财务功能

Odoo是目前市场上最好的ERP软件之一。Odoo提供两种类型的版本&#xff0c;社区版和企业版。Odoo社区版是由开源软件支持的免费基本版。Odoo社区版本中没有一些模块和功能。但企业版付费版&#xff0c;升级版更适合更高的价值。Odoo企业版具有无限的功能支持和完整的功能。性能和…

「集合底层」深入浅出HashMap底层源码

「集合底层」深入浅出HashMap底层源码 一、HashMap介绍 HashMap底层采用了哈希表&#xff0c;而哈希表是由数组和链表实现的。数组和链表各有自己的特点&#xff1a; 数组&#xff1a;占用空间连续。 寻址容易&#xff0c;查询速度快。但是&#xff0c;增加和删除效率非常低…

倒序打印链表

在做这个题的时候我闹了一个大笑话&#xff0c;我用了反转链表做&#xff0c;哈哈哈哈&#xff0c; 这个题目思路很简单&#xff0c;用到了数组的头插法&#xff0c;注意题目要求返回数组 遍历链表&#xff0c;将链表的val放到数组中 下面来看代码 import java.util.ArrayLis…

flash基础知识

flash基础手册一、flash概念&#xff08;一&#xff09;特性&#xff08;二&#xff09;FLASH的块/扇区/页关系&#xff08;三&#xff09;常用FLASH型号大小&#xff08;四&#xff09;常用FLASH擦写规则&#xff08;五&#xff09;存储器类型参考二、与其他类型存储器件对照&…

PDF文件怎么加密?推荐3种方法给你

在我们的工作学习上&#xff0c;应该有不少人都需要使用到PDF文件格式&#xff0c;毕竟这个格式它兼容性较广&#xff0c;且不易编辑&#xff0c;能较好的保存文件。不过&#xff0c;我们有时为了不让它被其它人随意查看&#xff0c;会给这个文件进行加密的操作。那你们知道如何…

python实现基于TNDADATASET的人体行为识别

首先来看下TNDADATASET&#xff1a; 随便打开一个文件简单看下如下所示&#xff1a; 可以大概推测出来&#xff0c;这里面不同维度的数据集应该是由不同的穿戴式传感器采集得到的&#xff0c;最后一列的class表示的是当前的行为类型。 在我之前的博文里面已经做过了相关的工作…

计算机毕设Python+Vue养老机构管理信息系统(程序+LW+部署)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

Sigrity常见问题解决方法(持续更新)

Sigrity常见问题解决方法&#xff08;持续更新&#xff09; 1.使用SpeedEM仿真时&#xff0c;报placement error 错误信息如下&#xff1a; 该问题是因为Mesh设置的过大&#xff0c;导致via和note在同一个mesh网格上存在粘连 解决方法是&#xff1a;将mesh进行auto设置 2…

鲍鱼年龄预测 knn svm 逻辑回归

数据&#xff1a; M,0.455,0.365,0.095,0.514,0.2245,0.101,0.15,15 M,0.35,0.265,0.09,0.2255,0.0995,0.0485,0.07,7 F,0.53,0.42,0.135,0.677,0.2565,0.1415,0.21,9 M,0.44,0.365,0.125,0.516,0.2155,0.114,0.155,10 I,0.33,0.255,0.08,0.205,0.0895,0.0395,0.055,7 I,0.425…

基于pytorch搭建CNN 人像口罩识别检测项目

项目介绍 我们将使用 CNN (卷积神经网络)和机器学习分类器创建一个检测一个人是否戴着口罩的分类器。它将检测一个人是否戴着口罩。 我们将从头开始学习,我将对每一步进行解释。我需要你对机器学习和数据科学有基本的了解。我已经在本地 Windows 10 机器上实现了它,如果你…

深入浅出JVM(七)之执行引擎的解释执行与编译执行

执行引擎 hotspot执行引擎结构图 执行引擎分为解释器、JIT即时编译器以及垃圾收集器 执行引擎通过解释器/即时编译器将字节码指令解释/编译为对应OS上的的机器指令 本篇文章主要围绕解释器与即时编译器&#xff0c;垃圾收集器将在后续文章解析 解释执行与编译执行 Java虚拟机…

方向图与天线增益

目录 一、方向图 二、增益 一、方向图 天线的方向性是指天线向一定方向辐射电磁波的能力。对于接收天线而言&#xff0c;方向性表示天线对不同方向传来的电磁波具有不同的接收能力。天线的方向性通常用方向图来表示。 在数学里&#xff0c;球坐标系是一种利用球坐标表示一个点…

Linux学习-90-Tomcat下载安装(tar压缩包)

17.20 Tomcat下载安装&#xff08;tar压缩包&#xff09; 访问apache官网下载tomcat压缩包。访问以下链接进行下载tomcat-8.5.83版本&#xff0c;高版本的tomcat存在一些问题影响使用&#xff0c;然后使用 Xftp 上传到/usr/local/src目录中或者使用wget命令直接到/usr/local/s…

SpringBoot:模块探究之spring-boot-cli

Spring Boot CLI 是运行 SpringBoot 命令的命令行工具&#xff0c;能够帮助你快速的构建 Spring Boot 项目。只需要编写简单的 groovy 脚本&#xff0c;即可用最少的代码构建并运行一个完整的 Spring Boot 项目。 Spring Boot CLI 为 SpringCloud 提供了 SpringBoot 命令行功能…

Java面试--CAS

这里写目录标题一、概念二、CAS 如何保证原子性2.1、总线锁定2.2、缓存锁定二、底层原理三、CAS典型应用四、CAS问题4.1、循环时间长&#xff0c;开销很大4.2、只能保证一个共享变量的原子操作4.3、引出来 ABA 问题一、概念 判断内存中某个位置的值是否为预期值&#xff0c;如…