基于FFmpeg实现音频编码器

本文介绍基于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 举报,一经查实,本站将立刻删除。

(0)

相关推荐

发表回复

登录后才能评论