参考: 最简单的基于FFMPEG的音频编码器(PCM编码为AAC)
一些参数的意义:
比特率(bitRate):
指编码成AAC文件后的,每秒文件流的大小
实际是用第三方库libfdk_aac
编写AAC
1.需要做交叉编译的时候将libfdk_aac库编译到FFmpeg中去 (目前还不太清楚怎么弄的,后续再研究)
AAC:Advanced Audio Coding(高级音频编码)主要为了替代mp3编码, 定义在MPEG-4的第三部分.对于音频使用.m4a扩展名存储.FFmpeg目前支持AAC-LC编码和HE-AAC(V1/V2)编码.而HE-AAC的实现是在libfdk_aac模块中.
FDK_AAC介绍:
可以理解为当前最高质量的AAC编码.编码模式分为CBR和VBR.
主要步骤
1.注册组件,初始化AVFormatContext
,打开输出文件格式,
2.编码参数设置,通过AVCodecContext
设置码率,采样率,声道数,采样格式,采样类型等
3.获取编码器,将编码器ID赋值给AVCodecContext
的codec_id
,这样,AVCodecContext
编码时就知道自己要使用什么类型的编码器,然后使用int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options)
为编码器上下文打开这个编码器,接下来为编码器指定frame_size的大小,一般指定1024作为一帧的大小,至此我们就把编码器部分给分配好了
同时也需要判断编码器所支持的格式是否包含AVCodecContext
设置的格式,如果不包含,则需要对AVCodecContext
的格式进行转换设置
4.根据AVCodecContext
设置输入帧的大小,AVFrame
只包含数据和数据大小等,不包含音频总时间,时间点等参数
5.将AVFrame
帧数据转换成AVPacket
报数据,里面包含有音频总时间,时间点等参数
主要使用的方法
av_register_all():注册FFmpeg所有编解码器。
avformat_alloc_output_context2():初始化输出码流的AVFormatContext。
avio_open():打开输出文件。
av_new_stream():创建输出码流的AVStream。
avcodec_find_encoder():查找编码器。
avcodec_open2():打开编码器。
avformat_write_header():写文件头(对于某些没有文件头的封装格式,不需要此函数。比如说MPEG2TS)。
avcodec_encode_audio2():编码音频。即将AVFrame(存储PCM采样数据)编码为AVPacket(存储AAC,MP3等格式的码流数据)。
av_write_frame():将编码后的视频码流写入文件。
av_write_trailer():写文件尾(对于某些没有文件头的封装格式,不需要此函数。比如说MPEG2TS)。
1.首先需要拿到格式上下文AVFormatContext
//1.注册ffmpeg组件
avcodec_register_all();
//2.给_avFormatContext分配内存
_avFormatContext = avformat_alloc_context();
//初始化输出码流的AVFormatContext,根据传入的参数猜测编码格式 内部实际是根据猜测创建了一个AVIOContext然后赋值给了_avFormatContext->pb
int ret = avformat_alloc_output_context2(&_avFormatContext, NULL, NULL, aacFilePath);
//4.打开输出文件,内部初始化一个URLContext和AVIOContext,根据文件路径查找合适的URLProtocol (URLProtocol可以操作文件的打开,读写,关闭)
avio_open2(&_avFormatContext->pb, aacFilePath, AVIO_FLAG_WRITE, NULL, NULL);
2.根据AVFormatContext
创建输出码流AVStream
,并从AVStream
获取编码格式上下文AVCodecContext
// 创建一个AVStream通道,并分配内存,初始化AVStream一些数据值
_audioStream = avformat_new_stream(_avFormatContext, NULL);
//获取编码格式上下文AVCodecContext
_avCodecContext = _audioStream->codec;
_avCodecContext->codec_type = AVMEDIA_TYPE_AUDIO;
_avCodecContext->sample_rate = audioSampleRate;
_avCodecContext->bit_rate = _publishBitRate;
_avCodecContext->sample_fmt = AV_SAMPLE_FMT_S16;
_avCodecContext->channel_layout = _audioChannels == 1 ? AV_CH_LAYOUT_MONO : AV_CH_LAYOUT_STEREO;
//根据通道的layout返回通道的个数
_avCodecContext->channels = av_get_channel_layout_nb_channels(_avCodecContext->channel_layout);
LOGI("_avCodecContext->channels is %d", _avCodecContext->channels);
//编码类型,低频率AAC
_avCodecContext->profile = FF_PROFILE_AAC_LOW;
//|= 按位或
_avCodecContext->flags |= CODEC_FLAG_GLOBAL_HEADER;
3.获取编码器,根据名字寻找对应的编码器, 此处寻找的是libfdk_aac
AVCodec *codec = avcodec_find_encoder_by_name(codec_name);
//也可以根据AVCodecID id查找编码器
// avcodec_find_encoder(_avCodecContext->codec_id);
_avCodecContext->codec_id = codec->id;
if (codec->sample_fmts) {
/* 判断编码器所支持的采样格式 */
const enum AVSampleFormat *p = codec->sample_fmts;
for (; *p != -1; p++) {
if (*p == audioStream->codec->sample_fmt)
break;
}
if (*p == -1) {
LOGI("sample format incompatible with codec. Defaulting to a format known to work.........");
/* sample format incompatible with codec. Defaulting to a format known to work */
avCodecContext->sample_fmt = codec->sample_fmts[0];
}
}
if (codec->supported_samplerates) {
//判断编码器所支持的采样率,如果没有avCodecContext说设置的采样率,则取最相近的采样率(绝对值最小)
const int *p = codec->supported_samplerates;
int best = 0;
int best_dist = INT_MAX;
for (; *p; p++) {
int dist = abs(audioStream->codec->sample_rate - *p); //取采样率最接近的类型去支持
LOGI("* is %d", *p);
if (dist < best_dist) {
best_dist = dist;
best = *p;
}
}
/* best is the closest supported sample rate (same as selected if best_dist == 0) */
avCodecContext->sample_rate = best;
}
if ( preferedChannels != avCodecContext->channels
|| preferedSampleRate != avCodecContext->sample_rate
|| preferedSampleFMT != avCodecContext->sample_fmt) {
LOGI("channels is {%d, %d}", preferedChannels, audioStream->codec->channels);
LOGI("sample_rate is {%d, %d}", preferedSampleRate, audioStream->codec->sample_rate);
LOGI("sample_fmt is {%d, %d}", preferedSampleFMT, audioStream->codec->sample_fmt);
LOGI("AV_SAMPLE_FMT_S16P is %d AV_SAMPLE_FMT_S16 is %d AV_SAMPLE_FMT_FLTP is %d", AV_SAMPLE_FMT_S16P, AV_SAMPLE_FMT_S16, AV_SAMPLE_FMT_FLTP);
swrContext = swr_alloc_set_opts(NULL,
av_get_default_channel_layout(avCodecContext->channels),
(AVSampleFormat)avCodecContext->sample_fmt, avCodecContext->sample_rate,
av_get_default_channel_layout(preferedChannels),
preferedSampleFMT, preferedSampleRate,
0, NULL);
if (!swrContext || swr_init(swrContext)) {
if (swrContext)
swr_free(&swrContext);
return -1;
}
}
//编码器上下文打开这个编码器
if (avcodec_open2(avCodecContext, codec, NULL) < 0) {
LOGI("Couldn't open codec");
return -2;
}
编码中使用到的API介绍
int avcodec_fill_audio_frame(AVFrame *frame, int nb_channels, enum AVSampleFormat sample_fmt, const uint8_t *buf, int buf_size, int align);
填充AVFrame音频数据并调整指针大小。
缓冲区buf必须是一个预先分配的缓冲区,其大小足以容纳指定的样本量。 填充的AVFrame数据指针将指向此缓冲区。如果需要平面音频,则会分配AVFrame extended_data通道指针。
AVFrame *frame
必须在调用函数之前设置frame-> nb_samples。 此函数填充frame-> data,frame-> extended_data,frame-> linesize [0]
int nb_channels
声道数
AVSampleFormat sample_fmt
输入的格式
const uint8_t *buf
用于帧数据的缓冲区
int buf_size
缓存数据大小
int align
plane size sample alignment
4.根据AVCodecContext
设置输入帧的大小,AVFrame
只包含数据和数据大小等,不包含音频总时间,时间点等参数
int ret = 0;
AVSampleFormat preferedSampleFMT = AV_SAMPLE_FMT_S16;
int preferedChannels = audioChannels;
int preferedSampleRate = audioSampleRate;
input_frame = av_frame_alloc();
if (!input_frame) {
LOGI("Could not allocate audio frame\n");
return -1;
}
input_frame->nb_samples = avCodecContext->frame_size;
input_frame->format = preferedSampleFMT;
input_frame->channel_layout = preferedChannels == 1 ? AV_CH_LAYOUT_MONO : AV_CH_LAYOUT_STEREO;
input_frame->sample_rate = preferedSampleRate;
buffer_size = av_samples_get_buffer_size(NULL, av_get_channel_layout_nb_channels(input_frame->channel_layout),
input_frame->nb_samples, preferedSampleFMT, 0);
samples = (uint8_t*) av_malloc(buffer_size);
samplesCursor = 0;
if (!samples) {
LOGI("Could not allocate %d bytes for samples buffer\n", buffer_size);
return -2;
}
LOGI("allocate %d bytes for samples buffer\n", buffer_size);
/* setup the data pointers in the AVFrame */
ret = avcodec_fill_audio_frame(input_frame, av_get_channel_layout_nb_channels(input_frame->channel_layout),
preferedSampleFMT, samples, buffer_size, 0);
5.将AVFrame
帧数据转换成AVPacket
报数据,里面包含有音频总时间,时间点等参数
int ret, got_output;
AVPacket pkt;
av_init_packet(&pkt);
AVFrame* encode_frame;
encode_frame = input_frame;
pkt.stream_index = 0;
pkt.duration = (int) AV_NOPTS_VALUE; //数据的时长,以所属媒体流的时间基准为单位,未知则值为默认值0
pkt.pts = pkt.dts = 0; //编码,所以显示时间和解码时长都可以为0
pkt.data = samples;
pkt.size = buffer_size;
//根据音频编码器和音频frame数据编码成音频packt数据
/**
avctx:编码器的AVCodecContext。
avpkt:编码输出的AVPacket。
frame:编码输入的AVFrame。
got_packet_ptr:成功编码一个AVPacket的时候设置为1。
*/
ret = avcodec_encode_audio2(avCodecContext, &pkt, encode_frame, &got_output);
if (ret < 0) {
LOGI("Error encoding audio frame\n");
return;
}
if (got_output) {//pts dts 显示时间和解码时长, 此处pack包是编码后的时长,所以应该计算pts和dts
if (avCodecContext->coded_frame && avCodecContext->coded_frame->pts != AV_NOPTS_VALUE)
//函数用于time_base之间转换,av_rescale_q(a,b,c)作用相当于执行a*b/c,通过设置b,c的值,可以很方便的实现time_base之间转换
pkt.pts = av_rescale_q(avCodecContext->coded_frame->pts, avCodecContext->time_base, audioStream->time_base);
pkt.flags |= AV_PKT_FLAG_KEY;
this->duration = pkt.pts * av_q2d(audioStream->time_base);
//此函数负责交错地输出一个媒体包。如果调用者无法保证来自各个媒体流的包正确交错,则最好调用此函数输出媒体包,反之,可以调用av_write_frame以提高性能。
int writeCode = av_interleaved_write_frame(avFormatContext, &pkt);
}
av_free_packet(&pkt);
格式转换
当编码器不支持当前设置的编码参数时,需要进行转换
swrContext = swr_alloc_set_opts(NULL,
av_get_default_channel_layout(avCodecContext->channels),
(AVSampleFormat)avCodecContext->sample_fmt, avCodecContext->sample_rate,
av_get_default_channel_layout(preferedChannels),
preferedSampleFMT, preferedSampleRate,
0, NULL);
if (!swrContext || swr_init(swrContext)) {
if (swrContext)
swr_free(&swrContext);
return -1;
}
输入帧AVFrame转换
if(swrContext) {
if (av_sample_fmt_is_planar(avCodecContext->sample_fmt)) {
LOGI("Codec Context SampleFormat is Planar...");
}
/* 分配空间 */
convert_data = (uint8_t**)calloc(avCodecContext->channels,
sizeof(*convert_data));
av_samples_alloc(convert_data, NULL,
avCodecContext->channels, avCodecContext->frame_size,
avCodecContext->sample_fmt, 0);
swrBufferSize = av_samples_get_buffer_size(NULL, avCodecContext->channels, avCodecContext->frame_size, avCodecContext->sample_fmt, 0);
swrBuffer = (uint8_t *)av_malloc(swrBufferSize);
LOGI("After av_malloc swrBuffer");
/* 此时data[0],data[1]分别指向frame_buf数组起始、中间地址 */
swrFrame = av_frame_alloc();
if (!swrFrame) {
LOGI("Could not allocate swrFrame frame\n");
return -1;
}
swrFrame->nb_samples = avCodecContext->frame_size;
swrFrame->format = avCodecContext->sample_fmt;
swrFrame->channel_layout = avCodecContext->channels == 1 ? AV_CH_LAYOUT_MONO : AV_CH_LAYOUT_STEREO;
swrFrame->sample_rate = avCodecContext->sample_rate;
ret = avcodec_fill_audio_frame(swrFrame, avCodecContext->channels, avCodecContext->sample_fmt, (const uint8_t*)swrBuffer, swrBufferSize, 0);
LOGI("After avcodec_fill_audio_frame");
if (ret < 0) {
LOGI("avcodec_fill_audio_frame error ");
return -1;
}
}
swr_convert(swrContext, convert_data, avCodecContext->frame_size,
(const uint8_t**)input_frame->data, avCodecContext->frame_size);
int length = avCodecContext->frame_size * av_get_bytes_per_sample(avCodecContext->sample_fmt);
for (int k = 0; k < 2; ++k) {
for (int j = 0; j < length; ++j) {
swrFrame->data[k][j] = convert_data[k][j];
}
}
AVFrame
AVFrame:存储非压缩的数据(视频对应RGB/YUV像素数据,音频对应PCM采样数据)
AVPacket
AVPacket:存储压缩数据(视频对应H.264等码流数据,音频对应AAC/MP3等码流数据)