一、前语
本系列文章是对音视频技能入门常识的整理和复习,为进一步深入系统研究音视频技能巩固根底。文章列表:
- 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服务器建立【流媒体、服务器环境】
二、播映PCM
2.1 ffplay
能够运用ffplay播映《音频录制02_编程》中录制好的PCM文件,测试一下是否录制成功。
播映PCM需求指定相关参数:
- ar:采样率
- ac:声道数
-
f:采样格局
- s16le:PCM signed 16-bit little-endian
- 更多PCM的采样格局能够运用指令检查
- Windows:ffmpeg -formats | findstr PCM
- Mac:ffmpeg -formats | grep PCM
ffplay -ar 44100 -ac 2 -f s16le out.pcm
接下来演示一下,怎么经过编程的办法播映PCM数据。
2.2 SDL
ffplay是根据FFmpeg、SDL两个库完成的。经过编程的办法播映音视频,也是需求用到这2个库。FFmpeg我们都现已清楚了,比较陌生的是SDL。
2.2.1 简介
SDL(Simple DirectMedia Layer),是一个跨渠道的C语言多媒体开发库。
- 支撑Windows、Mac OS X、Linux、iOS、Android
- 供给对音频、键盘、鼠标、游戏操纵杆、图形硬件的底层访问
- 很多的视频播映软件、模拟器、受欢迎的游戏都在运用它
- 目前最新的稳定版是:2.0.14
- API文档:wiki
2.2.2 下载
SDL官网下载地址:download-sdl2。
2.2.2.1 Windows
由于我们运用的是MinGW编译器,所以选择下载SDL2-devel-2.0.14-mingw.tar.gz。
解压后的目录结构如下图所示,跟FFmpeg的目录结构相似,因而就不再赘述每个文件夹的效果。
2.2.2.2 Mac
从brew官网能够看得出来:之前执行brew install ffmpeg时,现已顺带装置了SDL,装置目录是:/usr/local/Cellar/sdl2。
假如没有这个目录,就执行brew install sdl2进行装置即可。
2.3 HelloWorld
来个简略的SDL HelloWorld吧,打印一下SDL的版本号。
2.3.1 .pro
文件
win32 {
FFMPEG_HOME = F:/Dev/ffmpeg-4.3.2
SDL_HOME = F:/Dev/SDL2-2.0.14/x86_64-w64-mingw32
}
macx {
FFMPEG_HOME = /usr/local/Cellar/ffmpeg/4.3.2
SDL_HOME = /usr/local/Cellar/sdl2/2.0.14_1
}
INCLUDEPATH += $${FFMPEG_HOME}/include
LIBS += -L$${FFMPEG_HOME}/lib \
-lavdevice \
-lavcodec \
-lavformat \
-lavutil
INCLUDEPATH += $${SDL_HOME}/include
LIBS += -L$${SDL_HOME}/lib \
-lSDL2
在Windows环境中,还需求处理一下dll文件,参阅:《dll文件处理》。
2.3.2 cpp
代码
#include <SDL2/SDL.h>
SDL_version v;
SDL_VERSION(&v);
// 2 0 14
qDebug() << v.major << v.minor << v.patch;
2.4 播映PCM
2.4.1 初始化子系统
SDL分红好多个子系统(subsystem):
- Video:显现和窗口办理
- Audio:音频设备办理
- Joystick:游戏摇杆操控
- Timers:定时器
- …
目前只用到了音频功用,所以只需求经过SDL_init函数初始化Audio子系统即可。
// 初始化Audio子系统
if (SDL_Init(SDL_INIT_AUDIO)) {
// 返回值不是0,就代表失利
qDebug() << "SDL_Init Error" << SDL_GetError();
return;
}
2.4.2 翻开音频设备
/* 一些宏界说 */
// 采样率
#define SAMPLE_RATE 44100
// 采样格局
#define SAMPLE_FORMAT AUDIO_S16LSB
// 采样巨细
#define SAMPLE_SIZE SDL_AUDIO_BITSIZE(SAMPLE_FORMAT)
// 声道数
#define CHANNELS 2
// 音频缓冲区的样本数量
#define SAMPLES 1024
// 用于存储读取的音频数据和长度
typedef struct {
int len = 0;
int pullLen = 0;
Uint8 *data = nullptr;
} AudioBuffer;
// 音频参数
SDL_AudioSpec spec;
// 采样率
spec.freq = SAMPLE_RATE;
// 采样格局(s16le)
spec.format = SAMPLE_FORMAT;
// 声道数
spec.channels = CHANNELS;
// 音频缓冲区的样本数量(这个值必须是2的幂)
spec.samples = SAMPLES;
// 回调
spec.callback = pull_audio_data;
// 传递给回调的参数
AudioBuffer buffer;
spec.userdata = &buffer;
// 翻开音频设备
if (SDL_OpenAudio(&spec, nullptr)) {
qDebug() << "SDL_OpenAudio Error" << SDL_GetError();
// 铲除一切初始化的子系统
SDL_Quit();
return;
}
2.4.3 翻开文件
#define FILENAME "F:/in.pcm"
// 翻开文件
QFile file(FILENAME);
if (!file.open(QFile::ReadOnly)) {
qDebug() << "文件翻开失利" << FILENAME;
// 封闭音频设备
SDL_CloseAudio();
// 铲除一切初始化的子系统
SDL_Quit();
return;
}
2.4.4 开端播映
// 每个样本占用多少个字节
#define BYTES_PER_SAMPLE ((SAMPLE_SIZE * CHANNELS) / 8)
// 文件缓冲区的巨细
#define BUFFER_SIZE (SAMPLES * BYTES_PER_SAMPLE)
// 开端播映
SDL_PauseAudio(0);
// 存放文件数据
Uint8 data[BUFFER_LEN];
while (!isInterruptionRequested()) {
// 只要从文件中读取的音频数据,还没有填充结束,就跳过
if (buffer.len > 0) continue;
buffer.len = file.read((char *) data, BUFFER_SIZE);
// 文件数据现已读取结束
if (buffer.len <= 0) {
// 剩下的样本数量
int samples = buffer.pullLen / BYTES_PER_SAMPLE;
int ms = samples * 1000 / SAMPLE_RATE;
SDL_Delay(ms);
break;
}
// 读取到了文件数据
buffer.data = data;
}
2.4.5 回调函数
// userdata:SDL_AudioSpec.userdata
// stream:音频缓冲区(需求将音频数据填充到这个缓冲区)
// len:音频缓冲区的巨细(SDL_AudioSpec.samples * 每个样本的巨细)
void pull_audio_data(void *userdata, Uint8 *stream, int len) {
// 清空stream
SDL_memset(stream, 0, len);
// 取出缓冲信息
AudioBuffer *buffer = (AudioBuffer *) userdata;
if (buffer->len == 0) return;
// 取len、bufferLen的最小值(为了确保数据安全,防止指针越界)
buffer->pullLen = (len > buffer->len) ? buffer->len : len;
// 填充数据
SDL_MixAudio(stream,
buffer->data,
buffer->pullLen,
SDL_MIX_MAXVOLUME);
buffer->data += buffer->pullLen;
buffer->len -= buffer->pullLen;
}
2.4.6 开释资源
// 封闭文件
file.close();
// 封闭音频设备
SDL_CloseAudio();
// 整理一切初始化的子系统
SDL_Quit();
三、PCM转WAV
播映器是无法直接播映PCM的,由于播映器并不知道PCM的采样率、声道数、位深度等参数。当PCM转成某种特定的音频文件格局后(比方转成WAV),就能够被播映器辨认播映了。
本文经过2种办法(指令行、编程)演示一下:怎么将PCM转成WAV。
1. WAV文件格局
在进行PCM转WAV之前,先再来认识一下WAV的文件格局。
- WAV、AVI文件都是根据RIFF标准的文件格局
- RIFF(Resource Interchange File Format,资源交流文件格局)由Microsoft和IBM提出
- 所以WAV、AVI文件的最前面4个字节都是RIFF四个字符
找遍了全网,并没有找到令我十分满意的WAV文件格局图,于是按照自己的理解画了一张表格,个人觉得还是极端通俗易懂的。
每一个chunk(数据块)都由3部分组成:
- id:chunk的标识
- data size:chunk的数据部分巨细,字节为单位
- data,chunk的数据部分
整个WAV文件是一个RIFF chunk,它的data由3部分组成:
- format:文件类型
-
fmt chunk
- 音频参数相关的chunk
- 它的data里边有采样率、声道数、位深度等参数信息
-
data chunk
- 音频数据相关的chunk
- 它的data就是真正的音频数据(比方PCM数据)
RIFF chunk除去data chunk的data(音频数据)后,剩下的内容能够称为:WAV文件头,一般是44字节。
四、PCM转WAV
1. 指令行
经过下面的指令能够将PCM转成WAV。
ffmpeg -ar 44100 -ac 2 -f s16le -i out.pcm out.wav
需求注意的是:上面指令生成的WAV文件头有78字节。比照44字节的文件头,它多添加了一个34字节巨细的LIST chunk。
关于LIST chunk的参阅资料:
- What is a “LIST” chunk in a RIFF/Wav header?
- List chunk (of a RIFF file)
加上一个输出文件参数*-bitexact*能够去掉LIST Chunk。
ffmpeg -ar 44100 -ac 2 -f s16le -i out.pcm -bitexact out2.wav
2. 编程
在PCM数据的前面插入一个44字节的WAV文件头,就能够将PCM转成WAV。
2.1 WAV的文件头结构
WAV的文件头结构大概如下所示:
#define AUDIO_FORMAT_PCM 1
#define AUDIO_FORMAT_FLOAT 3
// WAV文件头(44字节)
typedef struct {
// RIFF chunk的id
uint8_t riffChunkId[4] = {'R', 'I', 'F', 'F'};
// RIFF chunk的data巨细,即文件总长度减去8字节
uint32_t riffChunkDataSize;
// "WAVE"
uint8_t format[4] = {'W', 'A', 'V', 'E'};
/* fmt chunk */
// fmt chunk的id
uint8_t fmtChunkId[4] = {'f', 'm', 't', ' '};
// fmt chunk的data巨细:存储PCM数据时,是16
uint32_t fmtChunkDataSize = 16;
// 音频编码,1表明PCM,3表明Floating Point
uint16_t audioFormat = AUDIO_FORMAT_PCM;
// 声道数
uint16_t numChannels;
// 采样率
uint32_t sampleRate;
// 字节率 = sampleRate * blockAlign
uint32_t byteRate;
// 一个样本的字节数 = bitsPerSample * numChannels >> 3
uint16_t blockAlign;
// 位深度
uint16_t bitsPerSample;
/* data chunk */
// data chunk的id
uint8_t dataChunkId[4] = {'d', 'a', 't', 'a'};
// data chunk的data巨细:音频数据的总长度,即文件总长度减去文件头的长度(一般是44)
uint32_t dataChunkDataSize;
} WAVHeader;
2.2 PCM转WAV中心完成
封装到了FFmpegs类的pcm2wav函数中。
#include <QFile>
#include <QDebug>
class FFmpegs {
public:
FFmpegs();
static void pcm2wav(WAVHeader &header,
const char *pcmFilename,
const char *wavFilename);
};
void FFmpegs::pcm2wav(WAVHeader &header,
const char *pcmFilename,
const char *wavFilename) {
header.blockAlign = header.bitsPerSample * header.numChannels >> 3;
header.byteRate = header.sampleRate * header.blockAlign;
// 翻开pcm文件
QFile pcmFile(pcmFilename);
if (!pcmFile.open(QFile::ReadOnly)) {
qDebug() << "文件翻开失利" << pcmFilename;
return;
}
header.dataChunkDataSize = pcmFile.size();
header.riffChunkDataSize = header.dataChunkDataSize
+ sizeof (WAVHeader) - 8;
// 翻开wav文件
QFile wavFile(wavFilename);
if (!wavFile.open(QFile::WriteOnly)) {
qDebug() << "文件翻开失利" << wavFilename;
pcmFile.close();
return;
}
// 写入头部
wavFile.write((const char *) &header, sizeof (WAVHeader));
// 写入pcm数据
char buf[1024];
int size;
while ((size = pcmFile.read(buf, sizeof (buf))) > 0) {
wavFile.write(buf, size);
}
// 封闭文件
pcmFile.close();
wavFile.close();
}
2.3 调用函数
// 封装WAV的头部
WAVHeader header;
header.numChannels = 2;
header.sampleRate = 44100;
header.bitsPerSample = 16;
// 调用函数
FFmpegs::pcm2wav(header, "F:/in.pcm", "F:/out.wav");
五、播映WAV
关于WAV文件来说,能够直接运用ffplay指令播映,并且不必像PCM那样添加额外的参数。由于WAV的文件头中现已包含了相关的音频参数信息。
ffplay in.wav
接下来演示一下怎么运用SDL播映WAV文件。
1. 初始化子系统
// 初始化Audio子系统
if (SDL_Init(SDL_INIT_AUDIO)) {
qDebug() << "SDL_Init error:" << SDL_GetError();
return;
}
2. 加载WAV文件
// 存放WAV的PCM数据和数据长度
typedef struct {
Uint32 len = 0;
int pullLen = 0;
Uint8 *data = nullptr;
} AudioBuffer;
// WAV中的PCM数据
Uint8 *data;
// WAV中的PCM数据巨细(字节)
Uint32 len;
// 音频参数
SDL_AudioSpec spec;
// 加载wav文件
if (!SDL_LoadWAV(FILENAME, &spec, &data, &len)) {
qDebug() << "SDL_LoadWAV error:" << SDL_GetError();
// 铲除一切的子系统
SDL_Quit();
return;
}
// 回调
spec.callback = pull_audio_data;
// 传递给回调函数的userdata
AudioBuffer buffer;
buffer.len = len;
buffer.data = data;
spec.userdata = &buffer;
假如想要轻松加载MP3、Ogg、FLAC等格局的音频文件,能够运用第三方库:SDL_mixer。
3. 翻开音频设备
// 翻开设备
if (SDL_OpenAudio(&spec, nullptr)) {
qDebug() << "SDL_OpenAudio error:" << SDL_GetError();
// 开释文件数据
SDL_FreeWAV(data);
// 铲除一切的子系统
SDL_Quit();
return;
}
开端播映
// 开端播映(0是取消暂停)
SDL_PauseAudio(0);
while (!isInterruptionRequested()) {
if (buffer.len > 0) continue;
// 每一个样本的巨细
int size = spec.channels * SDL_AUDIO_BITSIZE(spec.format) / 8;
// 最终一次播映的样本数量
int samples = buffer.pullLen / size;
// 最终一次播映的时长
int ms = samples * 1000 / spec.freq;
SDL_Delay(ms);
break;
}
4. 回调函数
// 等候音频设备回调(会回调屡次)
void pull_audio_data(void *userdata,
// 需求往stream中填充PCM数据
Uint8 *stream,
// 希望填充的巨细(samples * format * channels / 8)
int len
) {
// 清空stream
SDL_memset(stream, 0, len);
AudioBuffer *buffer = (AudioBuffer *) userdata;
// 文件数据还没准备好
if (buffer->len <= 0) return;
// 取len、bufferLen的最小值
buffer->pullLen = (len > (int) buffer->len) ? buffer->len : len;
// 填充数据
SDL_MixAudio(stream,
buffer->data,
buffer->pullLen,
SDL_MIX_MAXVOLUME);
buffer->data += buffer->pullLen;
buffer->len -= buffer->pullLen;
}
5. 开释资源
// 开释WAV文件数据
SDL_FreeWAV(data);
// 封闭设备
SDL_CloseAudio();
// 铲除一切的子系统
SDL_Quit();
专题系列文章
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. 底层原理相关专题
- 01-计算机原理|计算机图形烘托原理这篇文章
- 02-计算机原理|移动终端屏幕成像与卡顿
2. iOS相关专题
- 01-iOS底层原理|iOS的各个烘托结构以及iOS图层烘托原理
- 02-iOS底层原理|iOS动画烘托原理
- 03-iOS底层原理|iOS OffScreen Rendering 离屏烘托原理
- 04-iOS底层原理|因CPU、GPU资源消耗导致卡顿的原因和解决计划
3. webApp相关专题
- 01-Web和类RN大前端的烘托原理
4. 跨渠道开发计划相关专题
- 01-Flutter页面烘托原理
5. 阶段性总结:Native、WebApp、跨渠道开发三种计划功能比较
- 01-Native、WebApp、跨渠道开发三种计划功能比较
6. Android、HarmonyOS页面烘托专题
- 01-Android页面烘托原理
- 02-HarmonyOS页面烘托原理 (
待输出
)
7. 小程序页面烘托专题
- 01-小程序结构烘托原理