王尘宇王尘宇

研究百度干SEO做推广变成一个被互联网搞的人

音视频学习笔记

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");
      
        //打开音频设备
        //里面的识别格式为[:]  这里写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————————

相关文章

评论列表

发表评论:
验证码

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。