首先需要创建音频编码的上下文以及指定编码器代码如下
//音频编码上下文
-(AVCodecContext *)get_avcodecContext_fa{
AVCodecContext *c_ctx = NULL;
AVCodec *codec = NULL;
codec = avcodec_find_encoder(AV_CODEC_ID_AAC);
c_ctx = avcodec_alloc_context3(codec);
c_ctx->flags = AV_CODEC_FLAG_GLOBAL_HEADER;
c_ctx->codec_id = AV_CODEC_ID_AAC;
c_ctx->sample_rate = select_sample_rate_f(codec);
c_ctx->channel_layout = select_channel_layout_f(codec);
c_ctx->channels = av_get_channel_layout_nb_channels(c_ctx->channel_layout);
c_ctx->sample_fmt = AV_SAMPLE_FMT_FLTP;
c_ctx->bit_rate = 128*1024;
c_ctx->profile = FF_PROFILE_AAC_LOW;
c_ctx->codec_type = AVMEDIA_TYPE_AUDIO;
output_en_codec_a = codec;
output_en_ctx_a = c_ctx;
int is_open = avcodec_open2(c_ctx, codec, NULL);
if (is_open < 0) {
fprintf(stderr, "video_c_ctx open failed");
return NULL;
}
if (!c_ctx) {
fprintf(stderr, "Failed to open c_ctx");
return NULL;
}
return c_ctx;
}
配置编码使用的AVFrame
AVFrame* get_avframe_audio(AVCodecContext* output_en_ctx_a){
AVFrame *av_frame = NULL;
av_frame = av_frame_alloc();
if (!av_frame) {
fprintf(stderr,"av_frame_alloc failed");
goto __ERROR;
}
av_frame->nb_samples = output_en_ctx_a->frame_size;
av_frame->format = output_en_ctx_a->sample_fmt;
av_frame->channel_layout = output_en_ctx_a->channel_layout;
av_frame->channels = av_get_channel_layout_nb_channels(av_frame->channel_layout);
av_frame_get_buffer(av_frame, 0);
if (!av_frame->data[0]) {
printf("Error, Failed to alloc buf in frame!\n");
return NULL;
}
return av_frame;
__ERROR:
if (!av_frame) {
av_frame_free(&av_frame);
}
return NULL;
}
由ffmpeg 编码后的avpacket 可以使用ffmpeg 进行推流 也可以使用rtmp 进行推流 如果使用rtmp进行推流 则需要自己构建rtmppacket
avpacket如何rtmppacket的构建在下个博客中进行讲解
这里编码出的avpacket 可以直接写入文件生成.aac 文件 需要注意的是 在编码时需要指定时间基 时间基的指定可以有多种方式
1.每次自增1024
2.采取当前时间戳同步来赋值
这两种方式都可以 正常的播放
如果不指定时间戳,写入的.aac文件在播放时会特别快 再有就是 如果写入flv文件 需要注意的问题是 在停止采集时需要向编码器发送空数据,将编码器缓冲区的数据取出
编码后的aac为裸数据如果需要写入可直接播放的aac 文件需要写入 adts头或adif头
我是使用的adts头的方式来进行写入 生成adts头的代码如下
static void get_adts_header_f(AVCodecContext *ctx, uint8_t *adts_header, int aac_length){
uint8_t freq_idx = 0; //0: 96000 Hz 3: 48000 Hz 4: 44100 Hz
switch (ctx->sample_rate) {
case 96000: freq_idx = 0; break;
case 88200: freq_idx = 1; break;
case 64000: freq_idx = 2; break;
case 48000: freq_idx = 3; break;
case 44100: freq_idx = 4; break;
case 32000: freq_idx = 5; break;
case 24000: freq_idx = 6; break;
case 22050: freq_idx = 7; break;
case 16000: freq_idx = 8; break;
case 12000: freq_idx = 9; break;
case 11025: freq_idx = 10; break;
case 8000: freq_idx = 11; break;
case 7350: freq_idx = 12; break;
default: freq_idx = 4; break;
}
uint8_t chanCfg = ctx->channels;
uint32_t frame_length = aac_length + 7;
adts_header[0] = 0xFF;
adts_header[1] = 0xF1;
adts_header[2] = ((ctx->profile) << 6) + (freq_idx << 2) + (chanCfg >> 2);
adts_header[3] = (((chanCfg & 3) << 6) + (frame_length >> 11));
adts_header[4] = ((frame_length & 0x7FF) >> 3);
adts_header[5] = (((frame_length & 7) << 5) + 0x1F);
adts_header[6] = 0xFC;
}
关于adts头的介绍以及字节的拼接 我会单独在发一个博客来对其进行说明
软编码音频流程同ffmpeg进行h264编码流程类似代码如下
- (BOOL)encode_audio:(AVCodecContext *)output_ctx and_packet:(AVPacket *)output_avpacket and_frame:(AVFrame *)output_avframe with_aacfile:(FILE*)aac_file{
BOOL is_encode_success = NO;
int ret = -1;
ret = avcodec_send_frame(output_ctx, output_avframe);
while (ret >= 0) {
ret = avcodec_receive_packet(output_ctx, output_avpacket);
if(ret == AVERROR(EAGAIN) || ret == AVERROR_EOF){
return is_encode_success;
}else if( ret < 0){
return is_encode_success;
}
if (aac_file) {
uint8_t aac_header[7];
get_adts_header_f(output_en_ctx_a, aac_header, output_avpacket->size);
size_t len = 0;
len = fwrite(aac_header, 1, 7, aac_file);
if(len != 7) {
fprintf(stderr, "fwrite aac_header failed\n");
return NO;
}
len = fwrite(output_avpacket->data, 1, output_avpacket->size, aac_file);
if(len != output_avpacket->size) {
fprintf(stderr, "fwrite aac data failed\n");
return NO;
}
}else{
[self separation_avpacket_with_avpacket:output_avpacket];
}
is_encode_success = YES;
av_packet_unref(output_avpacket);
}
return is_encode_success;
}
如何构建rtmppacket代码会在另外一篇博客中