阅读 83

音视频学习笔记

这是本人在某某网的学习音视频笔记,主要包括音视频的入门和ffmpeg的实战。笔记内容按照上课流程进行排版的,每个人的基础不一样,我只把我自己认为需要记的才会写入笔记;本人五年iOS开发,了解一下~~ ?

待我全部学习完后,会重新整理一下,干货满满,我劝您收藏!

音视频入门常见问题

2021年4月23日


FFmpeg 相关命令:

  1. 推流: ffmpeg -re -i 视频地址 -c copy -f flv 流媒体服务器地址

    -re: 按照原视频速率推, 解决音视频速率于原视频不相同导致失败的
    -c:v copy: 复制原有s视频的编码方式,解决推流不清晰的问题

  2. 播流: 慢慢完善补充

  3. 采集音频: ffmpeg -f(框架名称) avfoundation -i(设备,名称或索引 ) :0 文件名

  4. 播放pcm数据: ffplay -ar(采样率) 44100 -ac(通道数) 2 -f(采样大小)f32le 文件名

  5. 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);
http://img.mukewang.com/szimg/60826de709a1312a12181172.jpg

音频处理流程:

1. 直播客户端的处理流程

    1. 音视频采集 

    2. 音视频编码: 有损编码  无损编码

    3. 传输到观看端 

    4. 观看端解码 - 渲染

2. 音频数据的流转

    1. 将采集到模拟信号准成数字信号,数据的格式一般为PCM;

    2. 压缩 编码为 aac/mp3 等;

    3. 生成多媒体文件,相当于再从外面套个套 如: MP4/flv;

声音的基础知识:

1. 人的听觉范围: 20HZ ~ 20KHZ

2. 正常人说话的频率为  85HZ - 1100HZ

3. 声音的三要素:

    1. 音调:音频的快慢  由低到高 男生 - 女生 - 儿童 音频越高声音就越好听;

    2. 音量: 物体振动的幅度;

    3. 音色: 谐波  由很多不同的频率的声音组成的,
http://img.mukewang.com/szimg/60827b33096974e206720594.jpg

模数的转换:就是将模拟信号转换成数字信号 即可以将模拟信号转换为计算机能够识别的方波

1. 对声音进行量化采样,例如下图:对一段频率每0.25 进行一次采样。实际上一般的采样率48000次;采样率越大 数据越大 还原度越高

2. 采样大小:一个采样用多少bit存放。常用的是16bit;

3. 采样率:常用采样:8k 16k 32k 44.1k 48k;采样率越大 数据越大 还原度越高;

4. 声道数:单声道 双声道 多声道

PCM 就是一秒钟采样的数据 = 采样大小 * 采样频率 * 声道数;
http://img.mukewang.com/szimg/60827c8309ef45ee06940604.jpg

音频原始数据

PCM 原始音频数据

WAV  既可以存储原始数据 也可以存储压缩数据,就是在原始数据上加了一个header,方便识别处理;
http://img.mukewang.com/szimg/6082844209791cab12201086.jpg
http://img.mukewang.com/szimg/6082846009480ab714481088.jpg

2021年04月25日


音频录制

  1. 音频采集

    • 打开设备
      在引入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日


音频编码原理:

  1. 有损压缩

    剔除人耳听觉范围外的音频信号以及被被掩蔽的音频信号,信号的遮蔽可以分为频域遮蔽和时域遮蔽;

  • 频域遮蔽效应:70分贝以下,20HZ~20000HZ以上,两个频率相近发出的差不多的声音,去除低强度的

    http://img.mukewang.com/szimg/608581a20907a00909500592.jpg
    • 时域遮蔽效应: 根根时间推移,在一个高度的声音强度的时间段的前后杂音去除,前遮蔽50毫秒,后者比200毫秒,在这段内声音的声音强度越接近就会被屏蔽。

      http://img.mukewang.com/szimg/6085832d0973e9fd08140552.jpg
2. 无损编码: 包括 熵编码:哈夫曼编码(用一个很小的二进制数代替一个长的字符串,频率越高,编码越小,频率越低,编码越长) 算术编码(利用小数)香农编码

音频编码过程:数据先同时通过 时域转频域变换器和心理学模型处理数据,前者将数据转换成多种频段的数据,然后剔除不需要的频段数据,后者会去除非人耳听到的范围声音和一些复合声音,最后将两者合并经过量化编码,无损编码之类的,形成比特流数据,在此之前还会有一些辅助数据,此后数据就会变得非常小;

http://img.mukewang.com/szimg/6085873f096e726d09480542.jpg

常见的音频编码器:opus、aac、Ogg、Speex、iLBC、AMR、G.711, 最常用的编码器是opus aac。其中opus常用于直播,webrtc默认使用opus,AAC是应用最广泛的编解码;Ogg收费;Speex支持回音消除;G.711一般用于固话,声音损耗严重;

http://img.mukewang.com/szimg/608588d7097ff0a207560602.jpg
http://img.mukewang.com/szimg/6085894209be9ae807500616.jpg

AAC编码器:目前应用最广泛,最多的,主要学习这个编码器

http://img.mukewang.com/szimg/6085ed6709ba734917520600.jpg

AAC + SBR = AAC HE V1 AAC + SBR + PS = AAV HE V2

目前AAC HE V1 已经被取代 V2 取代了;

http://img.mukewang.com/szimg/6085ee3509519fbf06960614.jpg
http://img.mukewang.com/szimg/6085ef4f090c686610540554.jpg

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:表示的是采样率

  • 剩余的之后补上

    image.png

其中每一十进制数对应的含义:

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库

  1. 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;
  1. swr_init 初始化上下文
  2. 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


    http://img.mukewang.com/szimg/6086b8b6097e58ad07500092.jpg
  • 将输出数据写入文件


    http://img.mukewang.com/szimg/6086b8e109bcbf5c06440032.jpg
  1. swr_free释放 还有输入输出缓冲区的释放

2021年04月29日


ffmpeg 编码:

创建编码器 avcodec

  1. avcodec_find_encoder 一种通过名字查找 一种是通过id查找
  • AV_CODEC_ID_AAC | opus 其他编码器

  • "libfdk_aac"

创建上下文 avcodexcontext

  1. 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 才有用) 可选设置

打开编码器

  1. avcodex_opne2

送数据给编码器- 编码器内部有一个缓冲区,缓冲一部分数据才进行编码

编码

  1. 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的时候表明有数据已经在编码缓冲区了;

  1. avcodec_receive_packet 读取编码好的数据 avpacket
  • av_packet_alloc 分配编码后的数据空间

因为编码器上下文中有一个缓冲区,其中会缓存多个frame,因此并不是每塞一个frame就会有一个packet出来,所以需要通过一个while循环判断编码器的数据是否>=0,再通过avcodec_receive_packet获取packet,该函数也会返回一个int,如果返回值>=0表明获取成功,如果失败直接退出编码,这个值返回值还有其他含义,需要判断eagain 表明编码器没有数据了或者是有数据但是不够编码 这个eagain需要用AVERROR包装成一个附属(不知道为啥这么做) averror_eof 表明一点数据都没有了;

  1. 最后将数据编码后的数据写入到文件pkt->data,数据格式就是aac了;

在停止录制的时候,由于编码的缓存区可能还有数据,在最后关闭之前,再去取一遍编码数据放入文件;

  1. 释放资源

在结束的时候释放frame(av_frame_free) 和packet(av_packet_frame);

------------------------Keep Fighting------------------------

作者:东也_

原文链接:https://www.jianshu.com/p/037f3652e55a

文章分类
后端
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐