本文介绍基于FFmpeg开源代码实现音频编码器,将音频由一种编码格式转换为另一种编码格式,以及实现中的注意事项。
下图首先呈现一个PCM音频流通过转码为其它编码格式的流程。
一、音频介绍
1、为什么要对音频做编码
音频编码的目的是将音频采样数据(PCM等)压缩成为音频码流,从而降低音频码流的数据量。在保证音频质量的情况下节省网络带宽占用。
2、音频三要素:音色、音调、音量
音色:声音的特色,在数字频谱上取决于声音波形不同。如电话中能分辨出对方是谁,演奏时同一个音时不同的乐器,我们能区分是哪种乐器。
音调:体现声音的高低。如弹奏古筝,同一个音dao,会有高音和低音之分。而在数字频谱上体现的是声音的频率不同,则同一个音的音调就有高有低。
音量:又叫响度,即声音的大小。在数字频谱上体现的是波形震动幅度大小。
对于ffmpeg音频的开发,则体现在:sample值的大小就是声音的振幅;sample值的离散波形图,就是音色;波形图的周期(的倒数)就是模拟信号频率即音调。
3、音频在FFPMEG的表现形式
正常人听觉的频率范围大约在20Hz~20kHz之间:此处频率是指模拟信号
采样频率是指将模拟声音波形进行数字化时,每秒钟抽取声波幅度样本的次数。
根据奈奎斯特采样理论,为了保证声音不失真,采样频率应该在40kHz左右。常用的音频采样频率有8kHz、11.025kHz、22.05kHz、16kHz、37.8kHz、44.1kHz、48kHz等,如果采用更高的采样频率,还可以达到DVD的音质,目前mp4常见的频率为44.8KHz和48kHz。
通常接触到视频中的音频帧,每帧含有1024个采样点,对采样率为44.1kHz的AAC音频进行解码时,一帧的展示时间在23.22毫秒左右(1024/(44.1*1000)*1000=23.22)。
二、基于FFMPEG音频重编码
音频重编码处理流程
对音频PCM做重编码操作,可能存在采样率(48k->41k)、样本格式(AV_SAMPLE_FMT_S16[PCM_S16BE]->AV_SAMPLE_FMT_FLTP[AAC])发生变化时,则需要使用FFMPEG的swr接口做重采样操作,生成目标音频。本文暂只说明在采样率不发生变化,样本格式发生变化的处理。
三、关键数据结构介绍
1、样本格式说明
在对PCM数据进行编码时,需要识别出音频数据存放是按平面存放,还是非平面存放。两种数据存放格式在编码时存在不同的处理逻辑。
非平面模式存放下数据的存储样式:sample按左声道、右声道、左声道轮流存放至data[0]中,如下图所示
平面模式数据存储,每个data[0]就是左声道,data[1]就是右声道,如下图所示
如何识别音频数据按何种格式存储,可以获取PCM音频sample_format信息获取,不同sample_format格式下每个sample大小,以及是否按平面存放如下图结构体所示:
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_N ///< Number of sample formats. DO NOT USE if inkingdynamically
};
2、音频编码关键信息设置
typedef struct AVCodecContext
{
enumAVCodecID codec_id; //编码ID,采用何种编码:AAC/MP3
int64_t bit_rate; //编码码率
AVRationaltime_base;
int sample_rate; ///每秒sample个数,采样率
intchannels; ///音频通道数
enumAVSampleFormat sample_fmt; ///音频sample格式,
uint64_tchannel_layout; //音频布局,是立体声还是环绕声
}
四、音频重采样编码示例介绍
示例实现范围:将一以解码PCM数据,PCM_S16BE 编码,AV_SAMPLE_FMT_S16 样本格式转码为AAC编码,AV_SAMPLE_FMT_FLTP样本格式。Sample值从16位有符号转为平面模式下32位float型。
1、申请输出文件封装句柄
avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL,outputFile);
API会根据outputFile后缀得出期望的输出封装格式:如MP3、AAC等;
2、获取一个音视频流通道,给ofmt_ctx使用
AVStream *out_stream = avformat_new_stream(ofmt_ctx,NULL);
3、根据生成音频转码要求,打开对应音频转码器,如本例子需要转码为AAC编码,AV_SAMPLE_FMT_FLTP样本格式,其它和PCM源保持一致
A、通过AAC编码ID查找到AAC对应的编码器
AVCodec *encoder = avcodec_find_encoder(AV_CODEC_ID_AAC);
B、申请编码器对应的上下文信息
AVCodecContext *enc_ctx = avcodec_alloc_context3(encoder);
C、设置音频编码器关键参数信息
enc_ctx->sample_fmt = AV_SAMPLE_FMT_FLTP;//AAC编码sample格式
enc_ctx->bit_rate = 64000;//码率63kbps
enc_ctx->sample_rate = 44800;//此处取和原片保持一致,不做采样率变化
enc_ctx->channel_layout = AV_CH_LAYOUT_STEREO;//立体声
enc_ctx->channels =av_get_channel_layout_nb_channels(enc_ctx->channel_layout);//音频通道数
enc_ctx->time_base.num = dec_ctx->time_base.num;//和原片保持一致
enc_ctx->time_base.den = dec_ctx->time_base.den; //和原片保持一致
4、打开编码器,将编码信息存至上下文件句柄
avcodec_open2(enc_ctx, encoder, &opts)
5、将编码器上下文内容存入音频流通道
avcodec_parameters_from_context(out_stream->codecpar,audio_enc_ctx);
6、创建初始化AvioContext资源
avio_open(&ofmt_ctx->pb, outputFile,AVIO_FLAG_WRITE);
7、写入文件头到音频文件
avformat_write_header(ofmt_ctx, NULL);
8、音频重采样
A、生成SwrContext *audio_swr_ctx句柄,并初始化关键信息
audio_swr_ctx = swr_alloc_set_opts(NULL,
av_get_default_channel_layout(audio_enc_ctx->channels),//编码后音频布局
audio_enc_ctx->sample_fmt,//编码后sample格式
audio_enc_ctx->sample_rate, //编码后采样率 av_get_default_channel_layout(inframe->channels),//原始音频布局
(AVSampleFormat)(inframe->format), //原始音频sample格式
inframe->sample_rate, //原始音频采样率
0, NULL);
swr_init(audio_swr_ctx);
B、因为需要从原始的16位非平面重采样转码为float平面型数据,需要将原始帧LRLR数据分为LLRR分别放到data[0]和data[1],则需要额外申请资源用于存放重采样后的数据。convert_data[0]存放左声道数据,convert_data[1]存放右声道数据。
convert_data =(uint8_t**)calloc(audio_enc_ctx->channels, sizeof(*convert_data));
C、为左右声道分别申请内存存放frame_size个sample大小为sample_fmt内存
av_samples_alloc(convert_data, NULL, audio_enc_ctx->channels,
audio_enc_ctx->frame_size,audio_enc_ctx->sample_fmt,0);
9、读取pcm一帧数据,并通过重采样接口转换为目标数据至convert_data中
swr_convert(audio_swr_ctx,
convert_data, //存放目标数据
audio_enc_ctx->frame_size,//每个通道sample数
(const uint8_t**)&Buf, //原始音频数据
inframe->nb_samples //原始数据sample数
);
//将数据放入音频帧中
for (int ch=0; ch<audio_enc_ctx->channels; ch++)
{
inframe->data[ch]= convert_data[ch];
inframe->extended_data[ch]= convert_data[ch];
}
10、调用API,将音频帧数据发送编码,等待编码后的包,并根据封装做帧时间戳转换后,将音频帧写入最终的音频文件
avcodec_send_frame(audio_enc_ctx, inframe);
avcodec_receive_packet(enc_ctx, &enc_pkt);
av_packet_rescale_ts(&enc_pkt, enc_ctx->time_base,out_stream->time_base);
av_interleaved_write_frame(ofmt_ctx, &enc_pkt);
11、重复第10步、11步操作完成音频转码功能。
作者:青榴实验室
版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至1393616908@qq.com 举报,一经查实,本站将立刻删除。