FFmpeg学习(三)音频基础
一:音频入门
(一)声音三要素
1.音调(音频)
2.音量(振幅)
3.音色(谐波)
粉色曲线是最接近自然界中的波形(基频+多种不同频率音频合并:如黄色、蓝色)
绿色曲线为基频(主频率),可以看到粉色曲线都是在主频率上微调(走势是基本一致的)
越接近正弦波,声音一般越好听,畸形或产生噪波
(二)模数转换
模拟信号和数字信号之间可以相互转换:
模拟信号一般通过PCM脉码调制(Pulse Code Modulation)方法量化为数字信号,即让模拟信号的不同幅度(采样大小)分别对应不同的二进制值,例如采用8位编码可将模拟信号量化为2^8=256个量级,实用中常采取24位或30位编码;
数字信号一般通过对载波进行移相(Phase Shift)的方法转换为模拟信号。
补充:通常的采样率(每秒采样次数)为48000、32000、16000等等;采样率越高,数据信号还原为模拟信号的还原度越高!!!
计算机、计算机局域网与城域网中均使用二进制数字信号,目前在计算机广域网中实际传送的则既有二进制数字信号,也有由数字信号转换而得的模拟信号。但是更具应用发展前景的是数字信号。
(三)音频原始数据
音视频常用格式有以下两种:
PCM数据:纯音频数据;
WAV(多媒体文件):既可以存储PCM原始数据(多用),又可以存储压缩数据;
其实WAV就是在PCM原始数据之上,加上了一个头,包含了基本信息(使得播放器使用正确的参数去播放PCM数据)
采样率48000,位深度 16bit ,通道数2 知道这三个参数,那么基本我们就知道了 设备1秒内可以采集到多少音频数据是: 48000 * 16 * 2 = 1536000 位 48000 * 16 * 2 / 8 = 192000 字节. 也就是我的设备在一秒内可以采集192000
这么大的码流显然无法在我们的网络中传输!!!(不能带宽全给音视频传输吧),所以要进行音频数据压缩!!!
(四)音频帧大小的计算(采样率和时间间隔的区别)
采样率是指在1秒中的采样次数,而时间间隔是指每采取一帧音频数据所要时间间隔!!!
假设音频采样率 = 8000,采样通道 = 2,位深度 = 16,采样间隔 = 20ms 首先我们计算一秒钟总的数据量,采样间隔采用20ms的话,说明每秒钟需采集50次,这个计算大家应该都懂,那么总的数据量计算为 一秒钟总的数据量 =8000 * 2*16/8 = 32000 所以每帧音频数据大小 = 32000/50 = 640 每个通道样本数 = 640/2 = 320
解析:#define MAX_AUDIO_FRAME_SIZE 192000
是指双通道下,采用48k采样率,位深为16位,采样时间为1s
48000×2×16/8=192000字节
(五)PCM存储格式
PCM存储格式大体分为两种Planner和Packed
我们以双声道为例,L表示左声道,R表示右声道,如下为两种格式的存储方式:
Planner
LLLLLLLL… RRRRRRRR…
Packed
LRLRLRLRLR…
FFMpeg中对音频Format定义如下:
enum AVSampleFormat { AV_SAMPLE_FMT_NONE = -1, AV_SAMPLE_FMT_U8, ///< unsigned 8 bits AV_SAMPLE_FMT_S16, ///< signed 16 bits AV_SAMPLE_FMT_S32, ///< signed 32 bits AV_SAMPLE_FMT_FLT, ///< float AV_SAMPLE_FMT_DBL, ///< double AV_SAMPLE_FMT_U8P, ///< unsigned 8 bits, planar AV_SAMPLE_FMT_S16P, ///< signed 16 bits, planar AV_SAMPLE_FMT_S32P, ///< signed 32 bits, planar AV_SAMPLE_FMT_FLTP, ///< float, planar AV_SAMPLE_FMT_DBLP, ///< double, planar AV_SAMPLE_FMT_S64, ///< signed 64 bits AV_SAMPLE_FMT_S64P, ///< signed 64 bits, planar AV_SAMPLE_FMT_NB ///< Number of sample formats. DO NOT USE if linking dynamically };
我们这里以WASAPI为例,在windows中WASAPI捕获的数据格式总是FLT,即浮点Packed格式,而新版的FFMpeg中仅仅支持FLTP格式压缩(应该是需要自行编译FFMpeg库并附加其他压缩库,如有错请指正),目前网络中的大部分博客均是老版FFMpeg所以还都在使用其他格式。因此我们需要对PCM数据进行重新采样。
(六)WAV header
在查看音频数据压缩方式前,先了解WAV header的数据结构:
WAV例子:
因为是4字节对齐,所以每个sample大小4字节;而之所以是4字节,取决于blockAlign字段=(幅度大小×通道数);
二:音频处理流程
(一)直播客户端的处理流程
我们讲解的音视频都是基于娱乐直播进行讲解的,所以我们必须对整个流程非常清楚。
如下图,是一个基本的直播客户端的处理流程图:
直播客户端分为几个模块,我们需要对每个模块做的事情非常清楚。总的来说我们直播客户端分为两个端:共享端和观看端。
共享端包含:音视频采集,音视频编码两个模块
观看端包含:音视频解码,音视频渲染两个模块。
1.音视频采集:
音频采集一般我们只需要简单调用一下api就能实现,每个操作系统都提供了相对应的api, 只是对于不同的平台各不相同。
如Android端有audioRecord, mediaRecord等,我们什么时候使用audioRecord,什么时候使用MediaRecord,这个我们需要非常清楚它的应用场景。
同样在IOS 端有苹果提供的AVFoundation框架提供了很多音视频采集的方法,其中有很常用的底层方法AudioUnit。
2.音视频编码:
我们采集到音视频之后并不能直接传输,因为这个数据太大了,超出了我们网路的设备的负载。
如果我们直接传输这么大量的数据,很容易出现各种问题,因此我们必须对这些数据进行压缩后再进行传输,这个压缩的过程就是对音视频的编码处理。
编码后的数据就是非常小的数据了。
对于编码这块的知识也非常多,我们后续也会详细讲解。
编码分为有损编码和无损编码。我们什么时候用有损编码,什么时候使用无损编码? 有损编码去掉的是哪些数据?这些原理我们都需要很清楚明白。
3.音视频传输:
编码时候,我们就需要把数据传输到对端,这个传输的过程也是非常复杂的,需要对网络知识有个较好的理解。后续我也讲到这些。
4.音视频解码:
对端收到传输的数据后,需要对编码的数据进行解码,把压缩的数据还原后才能进行播放渲染。解码这块是跟编码相对应的,用什么方式编码,就需要用对应的方式解码。
5.音视频渲染:
解码获取得到原始数据之后,音频交给扬声器播放(实际是交给驱动,驱动交给驱动硬件模块进行处理),视频交给渲染器进行渲染。
(二)音频数据的流转(格式的转换)
我们需要知道我们采集数据后,采集的是什么数据,是PCM数据(模拟数据转数字信号,数字信号就是PCM)。然后经过编码之后,编码出的是什么格式的数据(aac/mp3)。这都是我们需要理解的。
对于录制的视频,我们最后将音视频保存入容器中;对于直播来说,我们并不需要最后一步,不需要将他存入容器中!!!
如下图是音频数据流的流转过程:
三:音频采集
(一)各平台音频采集方式
1.Android端音频采集:https://blog.csdn.net/u010029439/article/details/85056767
android平台上的音频采集一般就两种方式:
(1)使用MediaRecorder进行音频采集。
MediaRecorder 是基于 AudioRecorder 的 API(最终还是会创建AudioRecord用来与AudioFlinger进行交互) ,它可以直接将采集到的音频数据转化为执行的编码格式,并保存。
这种方案相较于调用系统内置的用用程序,便于开发者在UI界面上布局,而且系统封装的很好,便于使用,唯一的缺点是使用它录下来的音频是经过编码的,没有办法的得到原始的音频。
同时MediaRecorder即可用于音频的捕获也可以用于视频的捕获相当的强大。实际开发中没有特殊需求的话,用的是比较多的!
(2)使用AudioRecord进行音频采集。
AudioRecord 是一个比较偏底层的API,它可以获取到一帧帧PCM数据,之后可以对这些数据进行处理。
AudioRecord这种方式采集最为灵活,使开发者最大限度的处理采集的音频,同时它捕获到的音频是原始音频PCM格式的!
像做变声处理的需要就必须要用它收集音频。
在实际开发中,它也是最常用来采集音频的手段。如直播技术采用的就是AudioRecorder采集音频数据。
2.Ios端音频采集:https://blog.51cto.com/u_13505171/2057075
3.Windows音频采集:https://blog.csdn.net/machh/article/details/83546258
(二)FFMpeg采集音频方式(集成上面所有平台)
(1)通过命令方式(不同平台可能不同):https://www.cnblogs.com/ssyfj/p/14576359.html
先查看可用设备:
其中card中:PCH与NVidia是声卡类型,可以通过查看cat /proc/asound/cards文件查看所有在主机中注册的声卡列表:
其中device设备中:
HDMI:是高清多媒体接口(High Definition Multimedia Interface,HDMI)是一种全数字化视频和声音发送接口,可以发送未压缩的音频及视频信号。 alc662是声卡:声卡 (Sound Card)也叫音频卡(港台称之为声效卡),是计算机多媒体系统中最基本的组成部分,是实现声波/数字信号相互转换的一种硬件。声卡的基本功能是把来自话筒、磁带、光盘的原始声音信号加以转换,输出到耳机、扬声器、扩音机、录音机等声响设备,或通过音乐设备数字接口(MIDI)发出合成乐器的声音
所以:想要录取声音,我们必须选取card:0,其他的device与subdevice可以任意
选取对应设备获取输入:
ffmpeg -f alsa -i hw:0 alsaout.wav
(2)通过API方式:如下(三)
(三)FFmpeg编程采集音频
1.打开输入设备
#include <libavutil/log.h> #include <libavcodec/avcodec.h> #include <libavdevice/avdevice.h> #include <libavformat/avformat.h> int main(int argc,char* argv) { char* devicename = "hw:0"; char errors[1024]; int ret; av_register_all(); av_log_set_level(AV_LOG_DEBUG); //注册所有的设备,包括我们需要的音频设备 avdevice_register_all(); //获取输入(采集)格式 AVInputFormat *iformat = av_find_input_format("alsa"); //打开输入设备 AVFormatContext* fmt_ctx=NULL; AVDictionary* options=NULL; ret = avformat_open_input(&fmt_ctx,devicename,iformat,&options); if(ret<0){ av_strerror(ret,errors,1024); av_log(NULL,AV_LOG_ERROR,"Failed to open audio device,[%d]%s\n",ret,errors); } av_log(NULL,AV_LOG_INFO,"Success to open audio device\n"); return 0; }
gcc -o od 01OpenDevice.c -I /usr/local/ffmpeg/include/ -L /usr/local/ffmpeg/lib/ -lavutil -lavformat -lavcodec -lavdevice
2.从音频设备中读取音频数据
#include <libavutil/log.h> #include <libavcodec/avcodec.h> #include <libavdevice/avdevice.h> #include <libavformat/avformat.h> int main(int argc,char* argv) { char* devicename = "hw:0"; char errors[1024]; int ret,count=0; AVFormatContext* fmt_ctx=NULL; AVDictionary* options=NULL; AVInputFormat *iformat=NULL; AVPacket packet; //包结构 av_register_all(); av_log_set_level(AV_LOG_DEBUG); //注册所有的设备,包括我们需要的音频设备 avdevice_register_all(); //获取输入(采集)格式 iformat = av_find_input_format("alsa"); //打开输入设备 ret = avformat_open_input(&fmt_ctx,devicename,iformat,&options); if(ret<0){ av_strerror(ret,errors,1024); av_log(NULL,AV_LOG_ERROR,"Failed to open audio device,[%d]%s\n",ret,errors); } av_log(NULL,AV_LOG_INFO,"Success to open audio device\n"); //开始从设备中读取数据 while((ret=av_read_frame(fmt_ctx,&packet))==0&&count++<500){ av_log(NULL,AV_LOG_INFO,"Packet size:%d(%p),cout:%d\n",packet.size,packet.data,count); //释放空间 av_packet_unref(&packet); } //关闭设备、释放上下文空间 avformat_close_input(&fmt_ctx); return 0; }
gcc -o gap 02GetAudioPacket.c -I /usr/local/ffmpeg/include/ -L /usr/local/ffmpeg/lib/ -lavutil -lavformat -lavcodec -lavdevice
包的大小同码率/8=字节数
3.录制成为音频文件
创建文件--->将音频数据写入到文件中--->关闭文件
#include <stdio.h> #include <libavutil/log.h> #include <libavcodec/avcodec.h> #include <libavdevice/avdevice.h> #include <libavformat/avformat.h> int main(int argc,char* argv) { char* devicename = "hw:0"; char errors[1024]; int ret,count=0,len; FILE* fp = NULL; AVFormatContext* fmt_ctx=NULL; AVDictionary* options=NULL; AVInputFormat *iformat=NULL; AVPacket packet; //包结构 av_register_all(); av_log_set_level(AV_LOG_DEBUG); //注册所有的设备,包括我们需要的音频设备 avdevice_register_all(); //获取输入(采集)格式 iformat = av_find_input_format("alsa"); //打开输入设备 ret = avformat_open_input(&fmt_ctx,devicename,iformat,&options); if(ret<0){ av_strerror(ret,errors,1024); av_log(NULL,AV_LOG_ERROR,"Failed to open audio device,[%d]%s\n",ret,errors); } av_log(NULL,AV_LOG_INFO,"Success to open audio device\n"); //打开文件 fp = fopen("./audio.pcm","wb"); if(fp==NULL){ av_log(NULL,AV_LOG_ERROR,"Failed to open out file,[%d]%s\n",ret,errors); goto fail; } //开始从设备中读取数据 while((ret=av_read_frame(fmt_ctx,&packet))==0&&count++<500){ av_log(NULL,AV_LOG_INFO,"Packet size:%d(%p),cout:%d\n",packet.size,packet.data,count); len = fwrite(packet.data,packet.size,1,fp); fflush(fp); if(len!=packet.size){ av_log(NULL,AV_LOG_WARNING,"Warning,Packet size:%d not equal writen size:%d\n",len,packet.size); }else{ av_log(NULL,AV_LOG_INFO,"Success write Packet to file"); } //释放空间 av_packet_unref(&packet); } fail: if(fp) fclose(fp); //关闭设备、释放上下文空间 avformat_close_input(&fmt_ctx); return 0; }
gcc -o wad 03WriteAudioData.c -I /usr/local/ffmpeg/include/ -L /usr/local/ffmpeg/lib/ -lavutil -lavformat -lavcodec -lavdevice
注意:播放时需要指定格式,通过查看配置文件获取采样率等信息
sudo gedit /etc/pulse/daemon.conf
ffplay -ar 44100 -ac 2 -f s16le audio.pcm
四:音频压缩
(一)音频有损压缩技术(消除冗余信息)
注意:频域遮蔽效应和时域遮蔽效应的纵轴都是声音强度;
频域遮蔽的横轴是频率,是指在频率相近的声音,在一定的范围内(遮蔽门槛下的声音),声音强度大的会遮蔽声音强度小的声音。
时域遮蔽的横轴是时间,是指在时间相近的声音,声音强度大的会遮蔽声音强度小的声音,而且在发声前的部分较弱的声音(前遮蔽)和发声后的部分较弱的声音都会被屏蔽掉(后遮蔽)。
(二)音频无损压缩技术
哈夫曼编码
算术编码
香农编码
(三)音频编码过程
原始数据--->传入给两个模块进行处理-------------------------------->将两种数据(真正需要编码的数据)汇总,进行量化,编码-------->形成比特流
1.时域转频域变换(将一段长时间的数据,转换为多种频段的数据,从而获取我们需要的频段数据)
2.心理声学模型(前面的有损压缩过程)
(四)常见的音频编码器
其中最常见的是OPUS和AAC:延迟小,压缩率高
实时互动系统可以用opus(在线教育、会议),其中WebRTC默认使用OPUS
泛娱乐化直播一般使用AAC(最广泛),opus一般不支持,推广上有些困难
两个系统融合,需要将opus与AAC互转
speex:回音消除,降噪模块等可实现。 G.711:有些会与固话相联系,固话用的就是G.711,或者G.722 (声音损失严重,但是可以在窄宽带下传输)
音频编码器性能质量对比:
横轴:比特率越大(质量越好),但是传输速度可能较低;所以对于实时性要求高的,如OPus,在窄带时,会降低质量,从而提高传输速度
纵轴:带宽质量(窄带、宽带、全带),硬件设施有关
音频编码码率对比:(结合上面性能图)
纵轴是延迟(0~200ms),横轴是码率。延迟越低,码率越小:
Opus延迟较低,所以在20ms内,码率从0~20kb/s 而在码率较小时(0~20kb/s),AAC延迟则较大,在200ms附近,所以AAC不适用于实时性直播,适合于有一定延迟的直播。如果使用AAC于实时通信,那么可以选择AAC-LD低延迟类型
五:AAC编码
(一)AAC编码器介绍
MP3相对来说存储的压缩比较低,压缩后的文件还是比较大。而AAC压缩率高,压缩后文件较小,并且保真性能好,还原数据后,与原始数据相似性高。
AAC常用规格中:AAC HE V1使用较少,因为被V2取代。通过下面AAC规格可以了解:
AAC LC 最基础
AAC HE V1 = AAC LC + SBR
AAC HE V2 = AAC HE V1 + PS
AAC LC:码流越大(AAC LC,128k),存储信息量越大,音质越好,保真性高。码流越小,压缩比越高,去除的冗余信息多,会对重要的数据造成一定的损失。
AAC HE:按频谱分别保存;低频(基频)保存主要成分,高频(谐频)和音色有很大关系,将高频单独放大去保证音质。实现进一步压缩。
AAC HE V2:参数化,保存差异,进一步减少码流大小。
对于AAC格式:ADTS相对于ADIF而言,虽然每一帧前面都有header信息,但是却可以实现随时拖动播放,不必每次从头播放
补充:音频一帧数据计算---假设音频采样率 = 8000,采样通道 = 2,位深度 = 8,采样间隔 = 20ms
首先我们计算一秒钟总的数据量,采样间隔采用20ms的话,说明每秒钟需采集50次(1s=1000ms),那么总的数据量计算为
一秒钟总的数据量 =8000 * 2*8/8 = 16000(Byte) 所以每帧音频数据大小 = 16000/50 =320(Byte) 每个通道样本数 = 320/2 = 160(Byte)
(二)ADTS格式
ADTS头,适用性广,更加适用于流的传输,尤其对于直播系统
每一帧的ADTS的头文件都包含了音频的采样率,声道,帧长度等信息,这样解码器才能解析读取。
一般情况下ADTS的头信息都是7个字节,分为2部分:
adts_fixed_header();
adts_variable_header();
1.adts_fixed_header();
syncword :总是0xFFF, 代表一个ADTS帧的开始, 用于同步。解码器可通过0xFFF确定每个ADTS的开始位置。因为它的存在,解码可以在这个流中任何位置开始, 即可以在任意帧解码。 ID:MPEG Version: 0 for MPEG-4,1 for MPEG-2 Layer:always: '00' protection_absent:Warning, set to 1 if there is no CRC and 0 if there is CRC #0表示需要CRC校验,1不需要 profile:表示使用哪个级别的AAC,如01 Low Complexity(LC) -- AAC LC; profile的值等于 Audio Object Type的值减1。 profile=(audio_object_type - 1)
sampling_frequency_index:采样率的下标
channel_configuration:声道数,比如2表示立体声双声道
2.adts_variable_header();
aac_frame_length:一个ADTS帧的长度包括ADTS头和AAC原始流。frame length, this value must include 7 or 9 bytes of header length: aac_frame_length = (protection_absent == 1 ? 7 : 9) + size(AACFrame) protection_absent=0时, header length=9bytes protection_absent=1时, header length=7bytes adts_buffer_fullness:0x7FF 说明是码率可变的码流。 number_of_raw_data_blocks_in_frame:表示ADTS帧中有number_of_raw_data_blocks_in_frame + 1个AAC原始帧。 所以说number_of_raw_data_blocks_in_frame == 0 表示说ADTS帧中有一个AAC数据块。 (一个AAC原始帧包含一段时间内1024个采样及相关数据)
3.代码实现头部的添加:https://www.cnblogs.com/ssyfj/p/14579909.html
void adts_header(char *szAdtsHeader, int dataLen){ int audio_object_type = 2; //通过av_dump_format显示音频信息或者ffplay获取多媒体文件的音频流编码acc(LC),对应表格中Object Type ID -- 2 int sampling_frequency_index = 4; //音频信息中采样率为44100 Hz 对应采样率索引0x4 int channel_config = 2; //音频信息中音频通道为双通道2 int adtsLen = dataLen + 7; //采用头长度为7字节,所以protection_absent=1 =0时为9字节,表示含有CRC校验码 szAdtsHeader[0] = 0xff; //syncword :总是0xFFF, 代表一个ADTS帧的开始, 用于同步. 高8bits szAdtsHeader[1] = 0xf0; //syncword:0xfff 低4bits szAdtsHeader[1] |= (0 << 3); //MPEG Version:0 : MPEG-4(mp4a),1 : MPEG-2 1bit szAdtsHeader[1] |= (0 << 1); //Layer:0 2bits szAdtsHeader[1] |= 1; //protection absent:1 没有CRC校验 1bit szAdtsHeader[2] = (audio_object_type - 1)<<6; //profile=(audio_object_type - 1) 表示使用哪个级别的AAC 2bits szAdtsHeader[2] |= (sampling_frequency_index & 0x0f)<<2; //sampling frequency index:sampling_frequency_index 4bits szAdtsHeader[2] |= (0 << 1); //private bit:0 1bit szAdtsHeader[2] |= (channel_config & 0x04)>>2; //channel configuration:channel_config 高1bit szAdtsHeader[3] = (channel_config & 0x03)<<6; //channel configuration:channel_config 低2bits szAdtsHeader[3] |= (0 << 5); //original:0 1bit szAdtsHeader[3] |= (0 << 4); //home:0 1bit ----------------固定头完结,开始可变头 szAdtsHeader[3] |= (0 << 3); //copyright id bit:0 1bit szAdtsHeader[3] |= (0 << 2); //copyright id start:0 1bit szAdtsHeader[3] |= ((adtsLen & 0x1800) >> 11); //frame length:value 高2bits 000|1 1000|0000 0000 szAdtsHeader[4] = (uint8_t)((adtsLen & 0x7f8) >> 3); //frame length:value 中间8bits 0000 0111 1111 1000 szAdtsHeader[5] = (uint8_t)((adtsLen & 0x7) << 5); //frame length:value 低 3bits 0000 0000 0000 0111 //number_of_raw_data_blocks_in_frame:表示ADTS帧中有number_of_raw_data_blocks_in_frame + 1个AAC原始帧。所以说number_of_raw_data_blocks_in_frame == 0 表示说ADTS帧中有一个AAC数据块。(一个AAC原始帧包含一段时间内1024个采样及相关数据) szAdtsHeader[5] |= 0x1f; //buffer fullness:0x7ff 高5bits 0x7FF 说明是码率可变的码流 ---> 111 1111 1111 00----> 1 1111 1111 1100--->0x1f与0xfc szAdtsHeader[6] = 0xfc; }
4.AAC首部分析:https://www.p23.nl/projects/aac-header/
5.命令行提取AAC文件
部分参数可以通过ffplay播放来获取:
ffmpeg -i gfxm.mp4 -vn -c:a aac -ar 44100 -channels 2 -profile:a 2 1.aac
ffmpeg -i gfxm.mp4 -vn -c:a libfdk_aac -ar 44100 -channel 2 -profile:a aac_he_v2 1.aac
补充:通过profile参数,修改编码级别,2表示修改为AAC SSR级别
ffplay 1.aac
编程实现编码器:如七所示
六:音频重采样
(一)什么是音频重采样
1.为什么需要重采样?
2.如何知道是否需要进行重采样?
3.重采样的步骤
4.重采样相关API
(二)编程实现音频重采样
1.补充:
音频通道布局
FFMpeg笔记(三) 音频处理基本概念及音频重采样
2.代码实现
#include <stdio.h> #include <libavutil/log.h> #include <libavdevice/avdevice.h> #include <libavformat/avformat.h> #include <libswresample/swresample.h> void rec_audio(){ char* devicename = "hw:0"; char errors[1024]; int ret,count=0,len; FILE* fp = NULL; AVFormatContext* fmt_ctx=NULL; AVDictionary* options=NULL; AVInputFormat *iformat=NULL; AVPacket packet; //包结构 av_register_all(); av_log_set_level(AV_LOG_DEBUG); //注册所有的设备,包括我们需要的音频设备 avdevice_register_all(); //获取输入(采集)格式 iformat = av_find_input_format("alsa"); //打开输入设备 ret = avformat_open_input(&fmt_ctx,devicename,iformat,&options); if(ret<0){ av_strerror(ret,errors,1024); av_log(NULL,AV_LOG_ERROR,"Failed to open audio device,[%d]%s\n",ret,errors); } av_log(NULL,AV_LOG_INFO,"Success to open audio device\n"); //打开文件 fp = fopen("./audio_2.pcm","wb"); if(fp==NULL){ av_log(NULL,AV_LOG_ERROR,"Failed to open out file\n"); goto fail; } //----------创建重采样的上下文 SwrContext* swr_cxt = NULL; swr_cxt = swr_alloc_set_opts(NULL, //设置已经创建好的上下文,如果没有,则为NULL AV_CH_LAYOUT_STEREO, //设置输出目标的通道布局(双声道,立体声,...,方位增宽) AV_SAMPLE_FMT_FLT, //设置输出目标的采样格式,设置为32位浮点型 44100, //设置输出目标的采样率 AV_CH_LAYOUT_STEREO, //输入数据的通道布局,是双声道 AV_SAMPLE_FMT_S16, //输入数据的采样格式为s16le 44100, //输入的采样率 0, //日志级别 NULL); //日志上下文 if(!swr_cxt){ av_log(NULL,AV_LOG_ERROR,"Failed to set swr context\n"); goto fail; } //----------初始化上下文 if(swr_init(swr_cxt)<0){ av_log(NULL,AV_LOG_ERROR,"Failed to initial swr context\n"); goto fail; } //----------构造输入、输出空间 uint8_t** src_data = NULL; uint8_t** dst_data = NULL; int src_linesize = 0,dst_linesize=0; av_samples_alloc_array_and_samples(&src_data, //前面两个传入地址,用作输出;其他作为输入数据 &src_linesize, 2, //双通道 512, //单通道采样个数 每个packet大小2048,2048/2(和下面采样格式有关)=1024个采样个数,1024/2(这里是双通道)=512(是单通道) AV_SAMPLE_FMT_S16, //采样格式 0); //对齐 av_samples_alloc_array_and_samples(&dst_data, //前面两个传入地址,用作输出;其他作为输入数据 &dst_linesize, 2, //双通道 512, //输出的单通道采样个数,和上面一致即可,只是对每个采样进行了数据转换,对内部数据重采样,没有修改采样个数 AV_SAMPLE_FMT_FLT, //采样格式 0); //对齐 //开始从设备中读取数据 while((ret=av_read_frame(fmt_ctx,&packet))==0&&count++<500){ av_log(NULL,AV_LOG_INFO,"Packet size:%d(%p),cout:%d\n",packet.size,packet.data,count); //----------先对数据进行重采样,然后写入文件中 memcpy((void*)src_data[0],(void*)packet.data,packet.size); swr_convert(swr_cxt, //上下文 dst_data, //输出数组(双指针) 512, //每个通道的采样数 (const uint8_t**)src_data, //输入数据,来自与packet.data,要改造格式 512); //输入的通道采样数 len = fwrite(dst_data[0],1,dst_linesize,fp); fflush(fp); if(len!=dst_linesize){ av_log(NULL,AV_LOG_WARNING,"Warning,Packet size:%d not equal writen size:%d\n",len,packet.size); }else{ av_log(NULL,AV_LOG_INFO,"Success write Packet to file\n"); } //释放空间 av_packet_unref(&packet); } fail: if(fp) fclose(fp); //----------释放空间 if(src_data){ av_freep(&src_data[0]); } av_freep(&src_data); if(dst_data){ av_freep(&dst_data[0]); } av_freep(&dst_data); swr_free(&swr_cxt); //关闭设备、释放上下文空间 avformat_close_input(&fmt_ctx); return ; } int main(int argc,char* argv) { rec_audio(); return 0; }
gcc -o ra 04ResampleAudio.c -I /usr/local/ffmpeg/include/ -L /usr/local/ffmpeg/lib/ -lavutil -lavformat -lavcodec -lavdevice -lswresample
七:创建AAC编码器
见六:Audio:pcm_s16le,说明数据为原始pcm数据,没有进行编码处理!!!
(一)FFmpeg编码过程
1.创建并打开编码器
2.获取输入输出数据
avcodec_send_frame:将帧(frame)发送给编码器
avcodedc_receive_packet:通过receive_packet获取编码后的数据(packet)
一般来说AVPacket中存放的是编码后的数据,而AVFrame中存放的未编码的数据!!
疑问:为什么前面实现代码,是从设备中获取packet,而不是上面所说的frame??
与前面avformat_open_input有关,是将打开的设备当作多媒体文件进行处理。而多媒体文件肯定是编码后的数据。所以会认为从设备中读取的数据应该是编码后的数据,使用pakcet获取。而实际上数据类型应该是frame。
所以从设备中读取的数据并没有走编码过程,实际数据还是pcm数据(见六图中Audio:pcm_s16le)
(二)FFmpeg代码实现(重采样+编码为AAC)
#include <stdio.h> #include <libavutil/log.h> #include <libavcodec/avcodec.h> #include <libavdevice/avdevice.h> #include <libavformat/avformat.h> #include <libswresample/swresample.h> SwrContext* initSwrCxt(uint8_t*** src_data,int* src_linesize,uint8_t*** dst_data,int* dst_linesize){ //创建重采样的上下文 SwrContext* swr_cxt = NULL; swr_cxt = swr_alloc_set_opts(NULL, //设置已经创建好的上下文,如果没有,则为NULL AV_CH_LAYOUT_STEREO, //设置输出目标的通道布局(双声道,立体声,...,方位增宽) AV_SAMPLE_FMT_S16, //设置输出目标的采样格式,设置为32位浮点型 44100, //设置输出目标的采样率 AV_CH_LAYOUT_STEREO, //输入数据的通道布局,是双声道 AV_SAMPLE_FMT_S16, //输入数据的采样格式为s16le 44100, //输入的采样率 0, //日志级别 NULL); //日志上下文 if(!swr_cxt){ av_log(NULL,AV_LOG_ERROR,"Failed to set swr context\n"); return NULL; } //初始化上下文 if(swr_init(swr_cxt)<0){ av_log(NULL,AV_LOG_ERROR,"Failed to initial swr context\n"); return NULL; } //构造输入、输出空间 av_samples_alloc_array_and_samples(src_data, //前面两个传入地址,用作输出;其他作为输入数据 src_linesize, 2, //双通道 512, //单通道采样个数 每个packet大小2048,2048/2(和下面采样格式有关)=1024个采样个数,1024/2(这里是双通道)=512(是单通道) AV_SAMPLE_FMT_S16, //采样格式 0); //对齐 av_samples_alloc_array_and_samples(dst_data, //前面两个传入地址,用作输出;其他作为输入数据 dst_linesize, 2, //AV_CH_LAYOUT_SURROUND 三通道 512, //输出的单通道采样个数,和上面一致即可,只是对每个采样进行了数据转换,对内部数据重采样,没有修改采样个数 AV_SAMPLE_FMT_S16, //采样格式 0); //对齐 return swr_cxt; } AVCodecContext* openCodec(){ //---------1.打开编码器 AVCodec* codec = avcodec_find_encoder_by_name("libfdk_aac"); //内部要求的采样大小就是是s16le------重点 //---------2.创建上下文 AVCodecContext* codec_ctx = avcodec_alloc_context3(codec); //------------------设置上下文参数 codec_ctx->sample_fmt = AV_SAMPLE_FMT_S16; //设置采样格式(该字段是被固定了),上面说到,libfdk_aac处理16位,所以设置为16位。所以我们一般是将其他格式进行重采样为16位 codec_ctx->channel_layout = AV_CH_LAYOUT_STEREO; //设置通道布局 codec_ctx->channels = 2; //设置通道数(其实和上面一样) codec_ctx->sample_rate = 44100; //设置采样率 codec_ctx->profile = FF_PROFILE_AAC_HE; //设置AAC编码格式,如果设置了这个字段,就不需要设置下面的比特率了 //codec_ctx->bit_rate = 64000; //设置比特率,64k;对于每个编码方式,都有最低编码码率。AAC_LC:128k AAC HE:64k AAC HE V2:32k //---------3.打开编码器 if(avcodec_open2(codec_ctx,codec,NULL)<0){ av_log(NULL,AV_LOG_ERROR,"Failed to open libfdk_aac context\n"); return NULL; } return codec_ctx; } void encode(AVCodecContext* codec_ctx,AVFrame* frame,AVPacket* newpkt,FILE* fp){ //--------开始进行编码操作---------重点 int ret = avcodec_send_frame(codec_ctx,frame); //将frame交给编码器进行编码;内部会将一帧数据挂到编码器的缓冲区 while(ret>=0){ //只有当frame被放入缓冲区之后(数据设置成功),并且下面ret表示获取缓冲区数据完成后才退出循环。因为可能一个frame对应1或者多个packet,或者多个frame对应1个packet ret = avcodec_receive_packet(codec_ctx,newpkt); //从编码器中获取编码后的packet数据,处理多种情况 if(ret<0){ if(ret==AVERROR(EAGAIN)||ret==AVERROR_EOF){ //读完数据 return; }else{ //编码器出错 av_log(NULL,AV_LOG_ERROR,"avcodec_receive_packet error! [%d] %s\n",ret,av_err2str(ret)); return; } } int len = fwrite(newpkt->data,1,newpkt->size,fp); fflush(fp); if(len!=newpkt->size){ av_log(NULL,AV_LOG_WARNING,"Warning,newpkt size:%d not equal writen size:%d\n",len,newpkt->size); }else{ av_log(NULL,AV_LOG_INFO,"Success write newpkt to file\n"); } } } AVFrame* initFrame(){ AVFrame* frame = av_frame_alloc(); //分配frame空间,但是数据真正被存放在buffer中 if(!frame){ av_log(NULL,AV_LOG_ERROR,"Failed to create frame\n"); return NULL; } frame->nb_samples = 512; //单通道采样数,和前面重采样配置一样 frame->format = AV_SAMPLE_FMT_S16; //采样大小 frame->channel_layout = AV_CH_LAYOUT_STEREO; av_frame_get_buffer(frame,0); //第二个参数是对齐 512*2*2 = 2048 if(!frame->data[0]){ av_log(NULL,AV_LOG_ERROR,"Failed to create frame buffer\n"); return NULL; } return frame; } void rec_audio(){ char* devicename = "hw:0"; char errors[1024]; int ret,count=0,len; FILE* fp = NULL; AVFormatContext* fmt_ctx=NULL; //格式上下文获取-----av_read_frame获取packet AVDictionary* options=NULL; AVInputFormat *iformat=NULL; AVPacket packet; //包结构 av_register_all(); av_log_set_level(AV_LOG_DEBUG); //注册所有的设备,包括我们需要的音频设备 avdevice_register_all(); //获取输入(采集)格式 iformat = av_find_input_format("alsa"); //打开输入设备 ret = avformat_open_input(&fmt_ctx,devicename,iformat,&options); //----打开输入设备,初始化格式上下文和选项 if(ret<0){ av_strerror(ret,errors,1024); av_log(NULL,AV_LOG_ERROR,"Failed to open audio device,[%d]%s\n",ret,errors); } av_log(NULL,AV_LOG_INFO,"Success to open audio device\n"); //打开文件 fp = fopen("./audio_2.aac","wb"); if(fp==NULL){ av_log(NULL,AV_LOG_ERROR,"Failed to open out file\n"); goto fail; } //创建重采样的上下文 SwrContext* swr_cxt = NULL; //------根据initSwrCxt中的swr_alloc_set_opts创建重采样上下文 uint8_t** src_data = NULL; uint8_t** dst_data = NULL; int src_linesize = 0,dst_linesize=0; swr_cxt = initSwrCxt(&src_data,&src_linesize,&dst_data,&dst_linesize); if(!swr_cxt){ av_log(NULL,AV_LOG_ERROR,"Failed to set swr context or initial swr context\n"); goto fail; } //-------获取编码器上下文 AVCodecContext* codec_ctx = openCodec(); //---------根据openCodec中的avcodec_alloc_context3分配编码器的上下文 if(!codec_ctx) goto fail; //-------设置输入数据frame AVFrame* frame = initFrame(); //分配frame空间,但是数据真正被存放在buffer中 if(!frame){ av_log(NULL,AV_LOG_ERROR,"Failed to create frame\n"); goto fail; } //-------设置输入数据packet AVPacket* newpkt = av_packet_alloc(); if(!newpkt){ av_log(NULL,AV_LOG_ERROR,"Failed to create newpkt\n"); goto fail; } //开始从设备中读取数据 while((ret=av_read_frame(fmt_ctx,&packet))==0&&count++<500){ av_log(NULL,AV_LOG_INFO,"Packet size:%d(%p),cout:%d\n",packet.size,packet.data,count); //先对数据进行重采样,然后写入文件中 memcpy((void*)src_data[0],(void*)packet.data,packet.size); swr_convert(swr_cxt, //上下文 dst_data, //输出数组(双指针) 512, //每个通道的采样数 (const uint8_t**)src_data, //输入数据,来自与packet.data,要改造格式 512); //输入的通道采样数 //--------将重采样后的数据转存入frame中去 memcpy((void*)frame->data[0],dst_data[0],dst_linesize); //将编码数据写入文件 encode(codec_ctx,frame,newpkt,fp); //释放空间 av_packet_unref(&packet); } //---------强制将编码器缓冲区中的音频数据进行编码输出 encode(codec_ctx,NULL,newpkt,fp); fail: if(fp) fclose(fp); //释放空间 if(src_data){ av_freep(&src_data[0]); } av_freep(&src_data); if(dst_data){ av_freep(&dst_data[0]); } av_freep(&dst_data); //释放重采样上下文 swr_free(&swr_cxt); //释放packet和frame if(frame){ av_frame_free(&frame); } if(newpkt){ av_packet_free(&newpkt); } //释放编码器上下文 if(codec_ctx){ avcodec_free_context(&codec_ctx); } //关闭设备、释放上下文空间 avformat_close_input(&fmt_ctx); return ; } int main(int argc,char* argv) { rec_audio(); return 0; }
gcc -o ce 05CreateEncoder.c -I /usr/local/ffmpeg/include/ -L /usr/local/ffmpeg/lib/ -lavutil -lavformat -lavcodec -lavdevice -lswresample