一、前言
本系列文章是对音视频技术入门知识的整理和复习,为进一步深入系统研究音视频技术巩固基础。文章列表:
- 01-📝音视频技术核心知识|了解音频技术【移动通信技术的发展、声音的本质、深入了解音频】
- 02-📝音视频技术核心知识|搭建开发环境【FFmpeg与Qt、Windows开发环境搭建、Mac开发环境搭建、Qt开发基础】
- 03-📝音视频技术核心知识|Qt开发基础【
.pro
文件的配置、Qt控件基础、信号与槽】 - 04-📝音视频技术核心知识|音频录制【命令行、C++编程】
- 05-📝音视频技术核心知识|音频播放【播放PCM、WAV、PCM转WAV、PCM转WAV、播放WAV】
- 06-📝音视频技术核心知识|音频重采样【音频重采样简介、用命令行进行重采样、通过编程重采样】
- 07-📝音视频技术核心知识|AAC编码【AAC编码器解码器、编译FFmpeg、AAC编码实战、AAC解码实战】
- 08-📝音视频技术核心知识|成像技术【重识图片、详解YUV、视频录制、显示BMP图片、显示YUV图片】
- 09-📝音视频技术核心知识|视频编码解码【了解H.264编码、H.264编码、H.264编码解码】
- 10-📝音视频技术核心知识|RTMP服务器搭建【流媒体、服务器环境】
二、通过命令行进行音频录制
终于要开始进行FFmpeg实战了,一起来感受一下FFmpeg的强大吧。
1. 命令简介
FFmpeg的bin目录中提供了3个命令(可执行程序),可以直接在命令行上使用。
1.1 ffmpeg
ffmpeg的主要作用:对音视频进行编解码。
# 将MP3文件转成WAV文件
ffmpeg -i xx.mp3 yy.wav
当输入命令ffmpeg时,可以看到ffmpeg命令的使用格式是:
ffmpeg [options] [[infile options] -i infile]... {[outfile options] outfile}...
简化一下,常用格式是:
ffmpeg arg1 arg2 -i arg3 arg4 arg5
- arg1:全局参数
- arg2:输入文件参数
- arg3:输入文件
- arg4:输出文件参数
- arg5:输出文件
更多详细用法,可以参考官方文档:ffmpeg-all.html,或者使用以下命令查看:
# 简易版
ffmpeg -h
# 详细版
ffmpeg -h long
# 完整版
ffmpeg -h full
# 或者使用
# ffmpeg -help
# ffmpeg -help long
# ffmpeg -help full
1.2 ffprobe
ffprobe的主要作用:查看音视频的参数信息。
# 可以查看MP3文件的采样率、比特率、时长等信息
ffprobe xx.mp3
当输入命令ffprobe时,可以看到ffprobe命令的使用格式是:
ffprobe [OPTIONS] [INPUT_FILE]
# OPTIONS:参数
# INPUT_FILE:输入文件
更多详细用法,可以参考官方文档:ffprobe-all.html,或者使用以下命令查看:
# 简易版
ffprobe -h
# 详细版
ffprobe -h long
# 完整版
ffprobe -h full
# 或者使用
# ffprobe -help
# ffprobe -help long
# ffprobe -help full
1.3 ffplay
ffplay的主要作用:播放音视频。
# 播放MP3文件
ffplay xx.mp3
当输入命令ffplay时,可以看到ffplay命令的使用格式是:
ffplay [options] input_file
# options:参数
# input_file:输入文件
更多详细用法,可以参考官方文档:ffplay-all.html,或者使用以下命令查看:
# 简易版
ffplay -h
# 详细版
ffplay -h long
# 完整版
ffplay -h full
# 或者使用
# ffplay -help
# ffplay -help long
# ffplay -help full
1.4 hide_banner
增加*-hide_bannder*参数可以隐藏一些冗余的描述信息,可以去实践比较以下2条命令的区别:
ffprobe xx.mp3
ffprobe -hide_banner xx.mp3
# ffmpeg、ffprobe、ffplay都适用
2. 通过命令行录音
2.1 查看可用设备
使用命令行查看当前平台的可用设备:
ffmpeg -devices
Windows的输出结果如下所示:
- 列表中有个dshow,全名叫DirectShow,是Windows平台的多媒体系统库
- 我们可以使用dshow去操作多媒体输入设备(比如录音设备)
Devices:
D. = Demuxing supported
.E = Muxing supported
--
E caca caca (color ASCII art) output device
D dshow DirectShow capture
D gdigrab GDI API Windows frame grabber
D lavfi Libavfilter virtual input device
D libcdio
E sdl,sdl2 SDL2 output device
D vfwcap VfW video capture
Mac的输出结果如下所示:
- 列表中有个avfoundation,是Mac平台的多媒体系统库
- 我们可以使用avfoundation去操作多媒体输入设备(比如录音设备)
Devices:
D. = Demuxing supported
.E = Muxing supported
--
D avfoundation AVFoundation input device
D lavfi Libavfilter virtual input device
E sdl,sdl2 SDL2 output device
2.2 查看dshow支持的设备
# 查看dshow支持的设备
ffmpeg -f dshow -list_devices true -i dummy
# 或者
# ffmpeg -list_devices true -f dshow -i ''
# ffmpeg -list_devices true -f dshow -i ""
-
-f dshow
- dshow支持的
-
-list_devices true
- 打印出所有的设备
-
-i dummy 或 -i '' 或 -i ""
- 立即退出
我的笔记本外接了一只麦克风。
因此,命令的执行结果大致如下所示:
DirectShow video devices (some may be both video and audio devices)
"Integrated Camera"
DirectShow audio devices
"线路输入 (3- 魅声T800)"
"麦克风阵列 (Realtek(R) Audio)"
- dshow支持的视频设备
- Integrated Camera:笔记本自带的摄像头
- dshow支持的音频设备
- 线路输入 (3- 魅声T800):外接的麦克风
- 麦克风阵列 (Realtek(R) Audio):笔记本自带的麦克风
2.3 查看avfoundation支持的设备
在Mac平台,使用的是avfoundation,而不是dshow。
ffmpeg -f avfoundation -list_devices true -i ''
输出结果如下所示:
AVFoundation video devices:
[0] FaceTime高清摄像头(内建)
[1] Capture screen 0
AVFoundation audio devices:
[0] MS-T800
[1] Edu Audio Device
[2] MacBook Pro麦克风
列表中的MS-T800是外接的麦克风。在Mac上,FFmpeg还给每一个视频、音频设备进行了编号,比如MS-T800的编号是0、Mac自带麦克风的编号是2。
2.4 指定设备进行录音
# 使用外接的麦克风进行录音,最后生成一个wav文件
ffmpeg -f dshow -i audio="麦克风阵列 (Realtek(R) Audio)" out.wav
# 在Mac上通过编号指定设备
ffmpeg -f avfoundation -i :2 out.wav
# :0表示使用0号音频设备
# 0:2表示使用0号视频设备和2号音频设备
- 可以使用快捷键Ctrl + C终止录音
- 我这边的测试结果显示,音频参数是:
- Windows:44100Hz采样率、16位深度、2声道、1411Kbps比特率
- Mac:48000Hz采样率、16位深度、2声道、1536Kbps比特率
2.5 设置dshow的参数
先通过命令查看一下dshow可以使用的参数,详情可以查看官方文档:dshow参数。
# 从ffmpeg -devices命令的结果可以看得出来:dshow属于demuxer,而不是muxer
ffmpeg -h demuxer=dshow
部分输出结果如下所示:
# 采样率
-sample_rate <int> set audio sample rate (from 0 to INT_MAX)
# 采样大小(位深度)
-sample_size <int> set audio sample size (from 0 to 16)
# 声道数
-channels <int> set number of audio channels, such as 1 or 2 (from 0 to INT_MAX)
# 列出特定设备支持的参数
-list_options <boolean> list available options for specified device (default false)
然后再看看你的设备支持哪些参数。
ffmpeg -f dshow -list_options true -i audio="麦克风阵列 (Realtek(R) Audio)"
输出结果如下所示:
DirectShow audio only device options (from audio devices)
Pin "Capture" (alternative pin name "Capture")
min ch=1 bits=8 rate= 11025 max ch=2 bits=16 rate= 44100
# 可以看出来:采样率范围是11025~44100Hz
接下来设置录音时的音频参数。
ffmpeg -f dshow -sample_rate 15000 -sample_size 16 -channels 1 -i audio="麦克风阵列 (Realtek(R) Audio)" out.wav
二、通过编程进行音频录制
1. 通过编程录音
开发录音功能的主要步骤是:
-
- 打开设备
-
- 注册设备
-
- 打开设备
-
-
- 设置 设备 采集的上下文
-
-
-
- 设置 选中的设备
-
-
-
- 设置 选中的输入格式(
不同平台的格式不同,要提前 配置好,若是要跨平台,需要提前做好条件编译
)
- 设置 选中的输入格式(
-
-
-
- 设置采集的 配置信息
-
-
- 采集数据
-
- 获取 设备 的采集上下文对象
-
- 采集数据
-
-
- 获取采集数据的每一个当前采集好好的数据包(
采集的数据不是马上通过IO存储在系统硬盘的
,是先存储在缓冲区的
,这属于不同平台对文件IO的优化
,因为一直进行系统的IO操作,会影响执行效率:存入缓存比存入硬盘快
)
- 获取采集数据的每一个当前采集好好的数据包(
-
-
-
- 将数据包写入文件系统
-
-
-
- 释放写好的数据包
-
-
-
- 继续采集下一个数据包/退出采集
-
-
-
- ....(Tips:1. 要写好采集的开始和结束的控制逻辑 2. 要避免主线程采集,卡顿主线程下)
-
-
- 通过文件存储API 存储 采集好的 数据
-
-
- 打开文件操作
-
-
-
- 写入数据
-
-
-
- 写完要关闭文件操作
-
-
- 释放资源
需要用到的FFmpeg库有4个。
extern "C" {
// 设备相关API
#include <libavdevice/avdevice.h>
// 格式相关API
#include <libavformat/avformat.h>
// 工具相关API(比如错误处理)
#include <libavutil/avutil.h>
// 编码相关API
#include <libavcodec/avcodec.h>
}
1.1 权限申请
在Mac平台,有2个注意点:(可以在编译之后的软件包上改info.plist、也可以自定义info.plist)
- 需要在Info.plist中添加麦克风的使用说明,申请麦克风的使用权限
- 使用Debug模式运行程序
- 不然会出现闪退的情况
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSMicrophoneUsageDescription</key>
<string>使用麦克风采集您的天籁之音</string>
</dict>
</plist>
1.2 注册设备
在整个程序的运行过程中,只需要执行1次注册设备的代码。
// 初始化libavdevice并注册所有输入和输出设备
avdevice_register_all();
1.3 获取输入格式对象
1.3.1 宏定义
Windows和Mac环境的格式名称、设备名称都是不同的,所以使用条件编译实现跨平台。
// 格式名称、设备名称目前暂时使用宏定义固定死
#ifdef Q_OS_WIN
// 格式名称
#define FMT_NAME "dshow"
// 设备名称
#define DEVICE_NAME "audio=麦克风阵列 (Realtek(R) Audio)"
#else
#define FMT_NAME "avfoundation"
#define DEVICE_NAME ":0"
#endif
1.3.2 核心代码
根据格式名称获取输入格式对象,后面需要利用输入格式对象打开设备。
AVInputFormat *fmt = av_find_input_format(FMT_NAME);
if (!fmt) {
// 如果找不到输入格式
qDebug() << "找不到输入格式" << FMT_NAME;
return;
}
1.4 打开设备
// 格式上下文(后面通过格式上下文操作设备)
AVFormatContext *ctx = nullptr;
// 打开设备
int ret = avformat_open_input(&ctx, DEVICE_NAME, fmt, nullptr);
// 如果打开设备失败
if (ret < 0) {
char errbuf[1024] = {0};
// 根据函数返回的错误码获取错误信息
av_strerror(ret, errbuf, sizeof (errbuf));
qDebug() << "打开设备失败" << errbuf;
return;
}
1.5 采集数据
1.5.1 宏定义
#ifdef Q_OS_WIN
// PCM文件的文件名
#define FILENAME "F:/out.pcm"
#else
#define FILENAME "/Users/mj/Desktop/out.pcm"
#endif
1.5.2 核心代码
#include <QFile>
// 文件
QFile file(FILENAME);
// WriteOnly:只写模式。如果文件不存在,就创建文件;如果文件存在,就删除文件内容
if (!file.open(QFile::WriteOnly)) {
qDebug() << "文件打开失败" << FILENAME;
// 关闭设备
avformat_close_input(&ctx);
return;
}
// 暂时假定只采集50个数据包
int count = 50;
// 数据包
AVPacket *pkt = av_packet_alloc();
while (count-- > 0) {
// 从设备中采集数据,返回值为0,代表采集数据成功
ret = av_read_frame(ctx, pkt);
if (ret == 0) { // 读取成功
// 将数据写入文件
file.write((const char *) pkt->data, pkt->size);
// 释放资源
av_packet_unref(pkt);
} else if (ret == AVERROR(EAGAIN)) { // 资源临时不可用
continue;
} else { // 其他错误
char errbuf[1024];
av_strerror(ret, errbuf, sizeof (errbuf));
qDebug() << "av_read_frame error" << errbuf << ret;
break;
}
}
1.6 释放资源
// 关闭文件
file.close();
// 释放资源
av_packet_free(&pkt);
// 关闭设备
avformat_close_input(&ctx);
想要了解每一个函数的具体作用,可以查询:官方API文档。
1.7 获取录音设备的相关参数
// 从AVFormatContext中获取录音设备的相关参数
void showSpec(AVFormatContext *ctx) {
// 获取输入流
AVStream *stream = ctx->streams[0];
// 获取音频参数
AVCodecParameters *params = stream->codecpar;
// 声道数
qDebug() << params->channels;
// 采样率
qDebug() << params->sample_rate;
// 采样格式
qDebug() << params->format;
// 每一个样本的一个声道占用多少个字节
qDebug() << av_get_bytes_per_sample((AVSampleFormat) params->format);
// 编码ID(可以看出采样格式)
qDebug() << params->codec_id;
// 每一个样本的一个声道占用多少位(这个函数需要用到avcodec库)
qDebug() << av_get_bits_per_sample(params->codec_id);
}
2. 多线程
录音属于耗时操作,为了避免阻塞主线程,最好在子线程中进行录音操作。这里创建了一个继承自QThread的线程类,线程一旦启动(start),就会自动调用 run 函数。
2.1 .h
#include <QThread>
class AudioThread : public QThread {
Q_OBJECT
private:
void run();
public:
explicit AudioThread(QObject *parent = nullptr);
~AudioThread();
};
2.2 .cpp
AudioThread::AudioThread(QObject *parent,
AVInputFormat *fmt,
const char *deviceName)
: QThread(parent), _fmt(fmt), _deviceName(deviceName) {
// 在线程结束时自动回收线程的内存
connect(this, &AudioThread::finished,
this, &AudioThread::deleteLater);
}
AudioThread::~AudioThread() {
// 线程对象的内存回收时,正常结束线程
requestInterruption();
quit();
wait();
}
void AudioThread::run() {
// 录音操作
// ...
}
2.3 开启线程
AudioThread *audioThread = new AudioThread(this);
audioThread->start();
2.4 结束线程
// 外部调用线程的requestInterruption,请求结束线程
audioThread->requestInterruption();
// 线程内部的逻辑
void AudioThread::run() {
// 可以通过isInterruptionRequested判断是否要结束线程
// 当调用过线程的requestInterruption时,isInterruptionRequested返回值就为true,否则为false
while (!isInterruptionRequested()) {
// ...
}
}
2.5 改造录音代码
// 数据包
AVPacket *pkt = av_packet_alloc();
while (!isInterruptionRequested()) {
// 从设备中采集数据,返回值为0,代表采集数据成功
ret = av_read_frame(ctx, pkt);
if (ret == 0) { // 读取成功
// 将数据写入文件
file.write((const char *) pkt->data, pkt->size);
// 释放资源
av_packet_unref(pkt);
} else if (ret == AVERROR(EAGAIN)) { // 资源临时不可用
continue;
} else { // 其他错误
char errbuf[1024];
av_strerror(ret, errbuf, sizeof (errbuf));
qDebug() << "av_read_frame error" << errbuf << ret;
break;
}
}
专题系列文章
1. 前知识
- 01-探究iOS底层原理|综述
- 02-探究iOS底层原理|编译器LLVM项目【Clang、SwiftC、优化器、LLVM】
- 03-探究iOS底层原理|LLDB
- 04-探究iOS底层原理|ARM64汇编
2. 基于OC语言探索iOS底层原理
- 05-探究iOS底层原理|OC的本质
- 06-探究iOS底层原理|OC对象的本质
- 07-探究iOS底层原理|几种OC对象【实例对象、类对象、元类】、对象的isa指针、superclass、对象的方法调用、Class的底层本质
- 08-探究iOS底层原理|Category底层结构、App启动时Class与Category装载过程、load 和 initialize 执行、关联对象
- 09-探究iOS底层原理|KVO
- 10-探究iOS底层原理|KVC
- 11-探究iOS底层原理|探索Block的本质|【Block的数据类型(本质)与内存布局、变量捕获、Block的种类、内存管理、Block的修饰符、循环引用】
- 12-探究iOS底层原理|Runtime1【isa详解、class的结构、方法缓存cache_t】
- 13-探究iOS底层原理|Runtime2【消息处理(发送、转发)&&动态方法解析、super的本质】
- 14-探究iOS底层原理|Runtime3【Runtime的相关应用】
- 15-探究iOS底层原理|RunLoop【两种RunloopMode、RunLoopMode中的Source0、Source1、Timer、Observer】
- 16-探究iOS底层原理|RunLoop的应用
- 17-探究iOS底层原理|多线程技术的底层原理【GCD源码分析1:主队列、串行队列&&并行队列、全局并发队列】
- 18-探究iOS底层原理|多线程技术【GCD源码分析1:dispatch_get_global_queue与dispatch_(a)sync、单例、线程死锁】
- 19-探究iOS底层原理|多线程技术【GCD源码分析2:栅栏函数dispatch_barrier_(a)sync、信号量dispatch_semaphore】
- 20-探究iOS底层原理|多线程技术【GCD源码分析3:线程调度组dispatch_group、事件源dispatch Source】
- 21-探究iOS底层原理|多线程技术【线程锁:自旋锁、互斥锁、递归锁】
- 22-探究iOS底层原理|多线程技术【原子锁atomic、gcd Timer、NSTimer、CADisplayLink】
- 23-探究iOS底层原理|内存管理【Mach-O文件、Tagged Pointer、对象的内存管理、copy、引用计数、weak指针、autorelease
3. 基于Swift语言探索iOS底层原理
关于函数
、枚举
、可选项
、结构体
、类
、闭包
、属性
、方法
、swift多态原理
、String
、Array
、Dictionary
、引用计数
、MetaData
等Swift基本语法和相关的底层原理文章有如下几篇:
- 01-📝Swift5常用核心语法|了解Swift【Swift简介、Swift的版本、Swift编译原理】
- 02-📝Swift5常用核心语法|基础语法【Playground、常量与变量、常见数据类型、字面量、元组、流程控制、函数、枚举、可选项、guard语句、区间】
- 03-📝Swift5常用核心语法|面向对象【闭包、结构体、类、枚举】
- 04-📝Swift5常用核心语法|面向对象【属性、inout、类型属性、单例模式、方法、下标、继承、初始化】
- 05-📝Swift5常用核心语法|高级语法【可选链、协议、错误处理、泛型、String与Array、高级运算符、扩展、访问控制、内存管理、字面量、模式匹配】
- 06-📝Swift5常用核心语法|编程范式与Swift源码【从OC到Swift、函数式编程、面向协议编程、响应式编程、Swift源码分析】
4. C++核心语法
- 01-C++核心语法|C++概述【C++简介、C++起源、可移植性和标准、为什么C++会成功、从一个简单的程序开始认识C++】
- 02-📝C++核心语法|C++对C的扩展【::作用域运算符、名字控制、struct类型加强、C/C++中的const、引用(reference)、函数】
- 03-📝C++核心语法|面向对象1【 C++编程规范、类和对象、面向对象程序设计案例、对象的构造和析构、C++面向对象模型初探】
- 04-📝C++核心语法|面向对象2【友元、内部类与局部类、强化训练(数组类封装)、运算符重载、仿函数、模板、类型转换、 C++标准、错误&&异常、智能指针】
- 05-📝C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】
5. Vue全家桶
- 01-📝Vue全家桶核心知识|Vue基础【Vue概述、Vue基本使用、Vue模板语法、基础案例、Vue常用特性、综合案例】
- 02-📝Vue全家桶核心知识|Vue常用特性【表单操作、自定义指令、计算属性、侦听器、过滤器、生命周期、综合案例】
- 03-📝Vue全家桶核心知识|组件化开发【组件化开发思想、组件注册、Vue调试工具用法、组件间数据交互、组件插槽、基于组件的
- 04-📝Vue全家桶核心知识|多线程与网络【前后端交互模式、promise用法、fetch、axios、综合案例】
- 05-📝Vue全家桶核心知识|Vue Router【基本使用、嵌套路由、动态路由匹配、命名路由、编程式导航、基于vue-router的案例】
- 06-📝Vue全家桶核心知识|前端工程化【模块化相关规范、webpack、Vue 单文件组件、Vue 脚手架、Element-UI 的基本使用】
- 07-📝Vue全家桶核心知识|Vuex【Vuex的基本使用、Vuex中的核心特性、vuex案例】
6. 音视频技术核心知识
- 01-📝音视频技术核心知识|了解音频技术【移动通信技术的发展、声音的本质、深入了解音频】
- 02-📝音视频技术核心知识|搭建开发环境【FFmpeg与Qt、Windows开发环境搭建、Mac开发环境搭建、Qt开发基础】
- 03-📝音视频技术核心知识|Qt开发基础【
.pro
文件的配置、Qt控件基础、信号与槽】 - 04-📝音视频技术核心知识|音频录制【命令行、C++编程】
- 05-📝音视频技术核心知识|音频播放【播放PCM、WAV、PCM转WAV、PCM转WAV、播放WAV】
- 06-📝音视频技术核心知识|音频重采样【音频重采样简介、用命令行进行重采样、通过编程重采样】
- 07-📝音视频技术核心知识|AAC编码【AAC编码器解码器、编译FFmpeg、AAC编码实战、AAC解码实战】
- 08-📝音视频技术核心知识|成像技术【重识图片、详解YUV、视频录制、显示BMP图片、显示YUV图片】
- 09-📝音视频技术核心知识|视频编码解码【了解H.264编码、H.264编码、H.264编码解码】
- 10-📝音视频技术核心知识|RTMP服务器搭建【流媒体、服务器环境】
其它底层原理专题
1. 底层原理相关专题
2. iOS相关专题
- 01-iOS底层原理|iOS的各个渲染框架以及iOS图层渲染原理
- 02-iOS底层原理|iOS动画渲染原理
- 03-iOS底层原理|iOS OffScreen Rendering 离屏渲染原理
- 04-iOS底层原理|因CPU、GPU资源消耗导致卡顿的原因和解决方案