一.概述
iOS 使用FFmpeg 实现音视频软编码
上一篇文章中写到的AAC
音频编码,因缺少真机测试,有挺多问题,编码后的音频全是噪声。这篇文章使用Mac OS环境,将逐一解决这些问题且编码成功。本文使用的是FFmpeg
4.2版本。
二.编码器打开失败问题
FFmpeg编码器不支持AV_SAMPLE_FMT_S16
格式。此为packet
格式,声道的pcm
数据全部放在AVFrame
的data[0]
中,交替存储。
FFmpeg编码器支持AV_SAMPLE_FMT_FLTP
格式。此为planar
格式,声道的pcm
数据会单独保存。例如双声道,左右声道pcm
数据会分别保存在AVFrame
的data[0]
和data[1]
中。
//找到aac编码器
pCodec = avcodec_find_encoder(AV_CODEC_ID_AAC);
if (!pCodec) {
printf("Codec not found\n");
return;
}
pCodecContext = avcodec_alloc_context3(pCodec);
pCodecContext->codec_id = AV_CODEC_ID_AAC;
pCodecContext->codec_type = AVMEDIA_TYPE_AUDIO;
//ffmpeg目前只支持FLTP/FLT格式,否则编码器无法打开
pCodecContext->sample_fmt = AV_SAMPLE_FMT_FLTP;
pCodecContext->sample_rate = 44100;
pCodecContext->channel_layout = AV_CH_LAYOUT_STEREO;
pCodecContext->channels = av_get_channel_layout_nb_channels(pCodecContext->channel_layout);
pCodecContext->bit_rate = 64000;
pCodecContext->profile = FF_PROFILE_AAC_LOW ;
pCodecContext->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL;
三、AVFrame初始化内存问题
之前使用的初始化内存方法:
int size = av_samples_get_buffer_size(NULL, _pCodecContext->channels, _pCodecContext->frame_size, _pCodecContext->sample_fmt, 1);
uint8_t *buffer = av_malloc(size);
avcodec_fill_audio_frame(_pFrame, _pCodecContext->channels, _pCodecContext->sample_fmt, buffer, size, 1);
实际上可以用一句代码:
av_frame_get_buffer(pFrame, 0);
有个疑惑的地方是,当选择双声道时,初始化后data[0]
和data[1]
正常,对应的linesize[0] = 4096
,而linesize[1]=0
,但是编码后双声道的声音都正常。
四、重采样问题
demo中使用的是Audio Unit
进行采样,配置AudioStreamBasicDescription
中的是:
dataFormat.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked;
即样本格式为AV_SAMPLE_FMT_S16
,而编码器格式是AV_SAMPLE_FMT_FLTP
,而且如果采样率,位元深度,声道数不一致,也需要进行重采样:
//根据asbd参数设置重采样参数
SwrContext *swr = swr_alloc();
uint64_t in_channel_layout = av_get_default_channel_layout(audioDescription.mChannelsPerFrame);
av_opt_set_int(swr, "in_channel_layout", in_channel_layout, 0);
av_opt_set_int(swr, "out_channel_layout", pCodecContext->channel_layout, 0);
av_opt_set_int(swr, "in_channel_count", audioDescription.mChannelsPerFrame, 0);
av_opt_set_int(swr, "out_channel_count", pCodecContext->channels, 0);
av_opt_set_int(swr, "in_sample_rate", audioDescription.mSampleRate,0);
av_opt_set_int(swr, "out_sample_rate", pCodecContext->sample_rate,0);
av_opt_set_sample_fmt(swr, "in_sample_fmt", AV_SAMPLE_FMT_S16, 0);
av_opt_set_sample_fmt(swr, "out_sample_fmt", AV_SAMPLE_FMT_FLTP, 0);
swr_init(swr);
//根据Audio Unit传递的数据量,计算样本量
int in_nb_samples = (int)pcm_size/(audioDescription.mChannelsPerFrame * av_get_bytes_per_sample(AV_SAMPLE_FMT_S16));
//初始化输入数据内存
uint8_t **input = NULL;
int src_linesize;
av_samples_alloc_array_and_samples(&input, &src_linesize,audioDescription.mChannelsPerFrame, in_nb_samples,AV_SAMPLE_FMT_S16, 0);
*input = pcm_data;
//初始化输出数据内存
uint8_t *output = NULL;
//计算实际的样本量,因为不同分辨率转换后,会出现样本量不一致情况
int out_samples = (int)av_rescale_rnd(swr_get_delay(swr, audioDescription.mSampleRate) + in_nb_samples, pCodecContext->sample_rate, audioDescription.mSampleRate, AV_ROUND_UP);
av_samples_alloc(&output, NULL, pCodecContext->channels, out_samples, pCodecContext->sample_fmt, 0);
//转换音频格式
int out_nb_samples = swr_convert(swr,&output,out_samples, (const uint8_t **)input, in_nb_samples);
if (out_nb_samples < 0) {
fprintf(stderr, "Could not convert input samples (error )\n");
return;
}
五、缓冲问题
采集到的pcm
数据一次是1024个字节,经过转换后输出的samples
是512个。此demo中AAC编码器需凑齐1024个samples
才能进行编码,所以需要使用AVAudioFifo
音频缓冲队列:
//根据实际情况realloc队列内存
ret = av_audio_fifo_realloc(audioFifo, av_audio_fifo_size(audioFifo)+ (int)out_nb_samples);
if(ret <0){
printf("av_audio_fifo_realloc == %s\n", av_err2str(ret));
return;
}
//写入队列
ret = av_audio_fifo_write(audioFifo, (void **)&output,out_nb_samples);
if(ret <0){
printf("av_audio_fifo_write == %s\n", av_err2str(ret));
return;
}
//判断达到aac的1024个样本量后,再提取出来编码
while(av_audio_fifo_size(audioFifo) >= pCodecContext->frame_size)
{
int frame_size = FFMIN(av_audio_fifo_size(audioFifo), pCodecContext->frame_size);
//从队列中提取1024个样本量数据,写入avframe中
av_audio_fifo_read(audioFifo, (void **)pFrame->data, frame_size);
(省略)
}
六、AAC的ADTS头问题
demo中使用的是4.2版FFmpeg,编码中不会自动添加ADTS头,需在每一包编码后的数据前手动添加ADTS头:
void addADTSHead(uint8_t *head,int length,int channels){
length &= 0x1FFF;
int sample_index = 4;//此处为44100采样率对应的index
head[0] = (char)0xff;
head[1] = (char)0xf1;
head[2] = (char)(0x40 | (sample_index << 2) | (channels >> 2));
head[3] = (char)((channels & 0x3) << 6 | (length >> 11));
head[4] = (char)(length >> 3) & 0xff;
head[5] = (char)(((length & 0x7) << 5) & 0xff) | 0x1f;
head[6] = (char)0xfc;
}
七、变速,音量问题(未解决)
当仅仅是音频采集和编码,CPU的占用率在20%~30%,生成的aac文件就很正常。
当开启RTMP
,推流到本地的nginx
服务器,此过程CPU的占用率在100%~200%,此时生成的aac文件就会有变速,音量问题。此问题暂未解决,有了解的同学望不吝赐教。