音视频学习笔记
这是本人在某某网的学习音视频笔记,主要包括音视频的入门和ffmpeg的实战。笔记内容按照上课流程进行排版的,每个人的基础不一样,我只把我自己认为需要记的才会写入笔记;本人五年iOS开发,了解一下~~ ?
待我全部学习完后,会重新整理一下,干货满满,我劝您收藏!
2021年4月23日
FFmpeg 相关命令:
-
推流: ffmpeg -re -i 视频地址 -c copy -f flv 流媒体服务器地址
-re: 按照原视频速率推, 解决音视频速率于原视频不相同导致失败的
-c:v copy: 复制原有s视频的编码方式,解决推流不清晰的问题 播流: 慢慢完善补充
采集音频: ffmpeg -f(框架名称) avfoundation -i(设备,名称或索引 ) :0 文件名
播放pcm数据: ffplay -ar(采样率) 44100 -ac(通道数) 2 -f(采样大小)f32le 文件名
ffmpeg 生成AAC文件命令:ffmpeg -i(输入文件) xxx.mp4 -vn(video none过滤视频) -c:a(编码器codec: audio) libfdk_aac(性能最好的一款编码器) -ar 44100 -channels 2 -profile:a(对编解码器设置参数用的 a = 音频) aac_he_v2 xxx.aac
默认ffmpeg没有带fdk_aac编码器,所以需要下载fdk,再重新安装,我的mac安装也挺曲折的,需要使用brew install homebrew-ffmpeg/ffmpeg/ffmpeg --with-fdk-aac,如果brew版本低 ,下载不成功,有些东西可能下载不下来,需要更新brew,当我更新完成,又说command line tool版本太低,需要更新,我直接用的brew提示的安装命令,直接把系统也更新了,折腾了一整个下午才弄好;
libfdk编解码器更多参数的参考网址:http://ffmpeg.org/ffmpeg-codecs.html#libfdk_oo5faac
Linux基础:
1. 基础命令
1. ls 显示当前目录的子目录
2. cd 进入某个目录
3. pwd 获取当前目录
4. mkdir 创建文件夹
5. cp 拷贝一个文件到另外一个地方
6. rm 删除单个文件 rm -rf 循环删除 子目录
7. sudo 使用管理员身份 操作
8. pkg-config 链接 c/C++ 库
9. echo 写数据到一个文件 如果没有这个文件 就创建 如: echo "dada" test.txt
10. cat 查看文件内容
11. which 获取某个环境变量的目录地址
12. | grep 查找某个命令的详情
2. vim基本命令 详细命令地址: [https://](https://)www.runoob.com/linux/linux-vim.html
1. :w 保存
2. :q 退出
3. i 编辑文件
4. h 光标左移 j 上移 k 下移 L 右移
3. linux中的环境变量
mac中环境变量的地址:~/.bash_profile 使用source命令使环境变量生效, pkg-config命令 直接获取PKG_CONFIG_PATH环境中的地址去查找.pc文件 如:pkg-config --libs --cflags 库名 其中--libs指库的地址 --cflags库的头文件
详细了解地址:https://www.cnblogs.com/sddai/p/10266624.html
1. PATH 可执行的二进制文件,添加进去后可以作为命令调用
2. PKG_CONFIG_PATH 用于放置.pc库,同LD_LIBRARY_PATH
3. LD_LIBARARY_PATH 用于放置.so库,如果自己安装的.so库不在/usr/lib:/usr/lib64:/lib:/lib64:/usr/local/lib:/usr/local/lib64下, 则需要加到该环境变量中,系统才能调用到该库
Mac下编译安装ffmpeg
下载ffmpeg地址: [http://ffmpeg.org/download.html](http://ffmpeg.org/download.html)
编译ffmpeg
cd 到下载的ffmpeg文件执行一下命令
./configure --prefix=安装地址(/usr/local/ffmpeg)--enable-debug=3 (允许调试) --disable-static --enable-shared make -j(指定多少个进程并发进行,增加编译速度) 4 make install
C 的编译与执行
1. 编译: gcc/clang -g(debug模式) -o(指定输出的可执行文件名称) bin xxx.c;
2. 执行: ./文件名
C语言基础:
1. 指针 可以对指针进行操作 也可以获取指针指向的内容进行操作 获取指针的内容 *变量名
2. 堆内存的分配和释放
1. 内存分配 void *v = malloc(size) malloc属于”stdlib“库;
2. 内存释放 free(v) v = NULL;
3. C语言的一些较难的使用
1. 函数指针 声明写法:返回值类型 (*函数指针变量名) (形参列表) 面向对象中的多态实际上就是用的函数指针实现的,还有iOS开发中的runtime中的message_invoke和message_send方法也是使用的函数指针进行发消息的;
2. 文件操作
1. FILE *file
2. 打开文件 FILE* fopen(path, mode)其中mode w == write r == read ;
3. 写文件 fwrite(字符串指针,每个字符的大小,总大小,FILE);
4. 读文件 fread(buffer,每个字符的大小,总大小,FILE);
5. 关闭文件 fclose(FILE);
音频处理流程:
1. 直播客户端的处理流程
1. 音视频采集
2. 音视频编码: 有损编码 无损编码
3. 传输到观看端
4. 观看端解码 - 渲染
2. 音频数据的流转
1. 将采集到模拟信号准成数字信号,数据的格式一般为PCM;
2. 压缩 编码为 aac/mp3 等;
3. 生成多媒体文件,相当于再从外面套个套 如: MP4/flv;
声音的基础知识:
1. 人的听觉范围: 20HZ ~ 20KHZ
2. 正常人说话的频率为 85HZ - 1100HZ
3. 声音的三要素:
1. 音调:音频的快慢 由低到高 男生 - 女生 - 儿童 音频越高声音就越好听;
2. 音量: 物体振动的幅度;
3. 音色: 谐波 由很多不同的频率的声音组成的,
模数的转换:就是将模拟信号转换成数字信号 即可以将模拟信号转换为计算机能够识别的方波
1. 对声音进行量化采样,例如下图:对一段频率每0.25 进行一次采样。实际上一般的采样率48000次;采样率越大 数据越大 还原度越高
2. 采样大小:一个采样用多少bit存放。常用的是16bit;
3. 采样率:常用采样:8k 16k 32k 44.1k 48k;采样率越大 数据越大 还原度越高;
4. 声道数:单声道 双声道 多声道
PCM 就是一秒钟采样的数据 = 采样大小 * 采样频率 * 声道数;
音频原始数据
PCM 原始音频数据
WAV 既可以存储原始数据 也可以存储压缩数据,就是在原始数据上加了一个header,方便识别处理;
2021年04月25日
音频录制
-
音频采集
- 打开设备
在引入ffmpeg动态库的时候需要对动态库进行签名,我在签名完后,编译还是会报错没签名,我的操作很奇怪,需要把库从xcode删除后,再一个一个的把库引进去才不会报错,一次性托进去也会报错void openDevice(AVFormatContext **context) { //注册设备 avdevice_register_all(); //设置采集方式 mac os 下是AVfoundation Windows下是sdhow linux 下是alsa AVInputFormat *format = av_find_input_format("avfoundation"); //打开音频设备 //里面的识别格式为[[video device]:[audio device]] 这里写0 是获取第1个音频设备 char *deviceName = ":0"; AVDictionary *options = NULL; int result = avformat_open_input(&*context, deviceName, format, &options); if (result != 0) { char error[1024]; char *errorStr = av_make_error_string(error, 1024, result); printf("打开音频设备失败:%s",errorStr); return; } }
- 读取数据
//从上下文中读取数据 void read_audio_frame(AVFormatContext **context) { AVPacket pkt; int result = 0; int count = 0; while (count < 500) { result = av_read_frame(*context, &pkt); if (pkt.size > 0) { av_log(NULL, AV_LOG_INFO, "audio frame size == %d data == %p count == %d\n", pkt.size, pkt.data,count); av_packet_unref(&pkt); count++; } else { //Resource temporarily unavailable 因为获取太频繁 设备未准备好,还正在处理数据 if (result != -35) { char error[1024] = {0,}; av_make_error_string(error, 1024, result); av_log(NULL, AV_LOG_ERROR, "read audio frame failured: %s errorcode == %d\n",error,result); } } } //关闭音频录制 avformat_close_input(&*context); av_log(NULL, AV_LOG_DEBUG, "recorder finished"); }
- 将数据存储到文件
打开文件
开始写入const char *adrress = "/Users/wangning/Desktop/learning/ady_audio.pcm"; //创建并打开文件 w写入 b二进制 + 创建 FILE *file = fopen(adrress, "wb+");
//将数据写入到文件 void write_audio_file(FILE *file,AVPacket pkt) { fwrite(pkt.data, pkt.size, 1, file); //由于系统为了提升效率,在写入的时候并没有立刻写入到文件,而是将数据存储到缓冲区,到了一定量的时候再拷贝到文件,设备有可能出问题导致数据丢失,写后再添加fflush(FILE); fflush(file); if (pkt.size == 0) { fclose(file); printf("完成文件写入"); } }
- 打开设备
-
播放
ffplay 播放pcm数据: ffplay -ar(采样率) 44100 -ac(通道数) 2 -f(采样大小)f32le 文件名
2021年04月26日
音频编码原理:
-
有损压缩
剔除人耳听觉范围外的音频信号以及被被掩蔽的音频信号,信号的遮蔽可以分为频域遮蔽和时域遮蔽;
-
频域遮蔽效应:70分贝以下,20HZ~20000HZ以上,两个频率相近发出的差不多的声音,去除低强度的
-
时域遮蔽效应: 根根时间推移,在一个高度的声音强度的时间段的前后杂音去除,前遮蔽50毫秒,后者比200毫秒,在这段内声音的声音强度越接近就会被屏蔽。
-
2. 无损编码: 包括 熵编码:哈夫曼编码(用一个很小的二进制数代替一个长的字符串,频率越高,编码越小,频率越低,编码越长) 算术编码(利用小数)香农编码
音频编码过程:数据先同时通过 时域转频域变换器和心理学模型处理数据,前者将数据转换成多种频段的数据,然后剔除不需要的频段数据,后者会去除非人耳听到的范围声音和一些复合声音,最后将两者合并经过量化编码,无损编码之类的,形成比特流数据,在此之前还会有一些辅助数据,此后数据就会变得非常小;
常见的音频编码器:opus、aac、Ogg、Speex、iLBC、AMR、G.711, 最常用的编码器是opus aac。其中opus常用于直播,webrtc默认使用opus,AAC是应用最广泛的编解码;Ogg收费;Speex支持回音消除;G.711一般用于固话,声音损耗严重;
AAC编码器:目前应用最广泛,最多的,主要学习这个编码器
AAC + SBR = AAC HE V1 AAC + SBR + PS = AAV HE V2
目前AAC HE V1 已经被取代 V2 取代了;
AAC 中header有两种格式:
- ADIF(Audio data interchange format): 可以确定找个这个数据的开始,就相当于在aac数据前面加了个Header,header里面就会包含aac数据的一些信息,方便进行编解码。特点是只能从头开始解码,不能从音频数据中间开始,这种格式常用于磁盘文件中
- ADTS(Audio Data Transport Format):在每一帧的数据里面都会有一个同步字,所以他可以在任意的位置开始进行解码,就像流式数据;
ADTS结构: 由7-9个字节组成
1-12bit:全部是1也就是0xFFF,表示是同步字;
13:编码规范 0 = MPEG-4 1 = MPEG-2;
14~15:总是0;
16:是否有保护 1 代表 没有 CRC 0 代表有CRC;
17-18:表示的是MPEG-4的音频类型:AAC LC、 AAC HE V1 、AAC HE V2
19-22:表示的是采样率
-
剩余的之后补上
其中每一十进制数对应的含义:
Audio Object Type: 1. AAC main 2. AAC LC 5. SBR == HE V1 29. ps == HE V2
sample frequency:0:96000Hz 1:88200HZ 等
通过网址 可以更详细看到其中的含义
2021年04月27日
音频重采样
将音频三元组(采样率 采样大小 通道数)的值转成另外一组值
应用场景:
从设备采集的音频数据与编码器要求的不一致;
扬声器要求的音频数据与要播放的音频数据不一致;
方便运算:例如回音消除 将多声道变为单声道;
如何判断是否需要重采样
- 了解音频设备的参数
- 查看ffmpeg源码
重采样的步骤
创建重采样上下文
设置参数
初始化重采样
进行重采样
api:需要使用libswresample库
- swr_alloc_set_opts 获取上下文
- out_ch_layout:表示声道也可以是布局(扬声器的布局)AV_CH_LAYOUT_STEREO 立体声
- out_sample_fmt:输出的采样格式 16 = AV_SAMPLE_FMT_S16 或者 32 = AV_SAMPLE_FMT_FLT
- av_sample_fmt_s16 in_ch_layout:输入的声道布局
- in_sample_fmt 输入的采样格式
- in_sample_rate: 输入的采样率
- 后两位是log相关 0,null;
- swr_init 初始化上下文
- swr_convert 开始转换 out:输出结果缓冲区 out_count:每个通道的采样数 in:输入的缓冲区 in_count:输入的单个通道的采样数
- 因为重采样的数据需要重新构造所以需要
- 创建输入缓冲区和输出缓冲区av_sample_array_and_samples audio_data:输出缓冲区的地址 其中的采样数nb_samples == pkt.size / (32 / 8) / 2 linessize:缓冲区大小 align:对齐 0
-
还需要将pkt的data按字节拷贝memcpy成输入数据组,需要引用string.h
-
将输出数据写入文件
- swr_free释放 还有输入输出缓冲区的释放
2021年04月29日
ffmpeg 编码:
创建编码器 avcodec
- avcodec_find_encoder 一种通过名字查找 一种是通过id查找
AV_CODEC_ID_AAC | opus 其他编码器
"libfdk_aac"
创建上下文 avcodexcontext
- avcodec_alloc_context3 3表示第三个版本
sample_fmt = av_sample_FMT_S16 aac编码器不支持flt 32位
chnnel_layout = AV_CH_LAYOUT_STEREO( 或者chanels = 2)
sample_rate = 44100
bit_rate = 64000; (KB 码率)可选设置
profile = FF_PROFILE_AAC_HE_V2; (只有bit_rate=0 才有用) 可选设置
打开编码器
- avcodex_opne2
送数据给编码器- 编码器内部有一个缓冲区,缓冲一部分数据才进行编码
编码
- avcodec_send_frame 发送数据给编码器 avframe
- av_frame_alloc 堆区初始化frame
nb_samples 单通道一个数据帧采样数 512
format 每个采样的大小 av_sample_fmt_s16
channel_layout 声道 av_ch_layout_stereo
- av_frame_get_buffer 分配frame里面buffer的大小
还要判断frame的frame的buffer
将重采样后的数据memcpy到frame->data中
再将frame中的数据塞到编码器上下文中 avcodec_send_frame,该函数会返回一个int ,当结果>=0的时候表明有数据已经在编码缓冲区了;
- avcodec_receive_packet 读取编码好的数据 avpacket
- av_packet_alloc 分配编码后的数据空间
因为编码器上下文中有一个缓冲区,其中会缓存多个frame,因此并不是每塞一个frame就会有一个packet出来,所以需要通过一个while循环判断编码器的数据是否>=0,再通过avcodec_receive_packet获取packet,该函数也会返回一个int,如果返回值>=0表明获取成功,如果失败直接退出编码,这个值返回值还有其他含义,需要判断eagain 表明编码器没有数据了或者是有数据但是不够编码 这个eagain需要用AVERROR包装成一个附属(不知道为啥这么做) averror_eof 表明一点数据都没有了;
- 最后将数据编码后的数据写入到文件pkt->data,数据格式就是aac了;
在停止录制的时候,由于编码的缓存区可能还有数据,在最后关闭之前,再去取一遍编码数据放入文件;
- 释放资源
在结束的时候释放frame(av_frame_free) 和packet(av_packet_frame);
------------------------Keep Fighting------------------------
作者:东也_
原文链接:https://www.jianshu.com/p/037f3652e55a