1、获取编码器
2、设置编码前的结构
3、转码准备
4、编码处理
5、清理
//初始化
av_register_all();
//根据输出文件后缀,获取流的基本信息
avformat_alloc_output_context2(&pFormatCtx, NULL, NULL, out_filename);
//打开输出文件
if (avio_open(&pFormatCtx->pb, out_filename, AVIO_FLAG_READ_WRITE) < 0)
{
printf("打开输出文件失败\n");
return;
}
//创建新的输出流到文件
audio_st = avformat_new_stream(pFormatCtx, 0);
if (audio_st == NULL)
{
printf("创建输出流失败\n");
return;
}
//找到AAC编码器
pCodec = avcodec_find_encoder(pFormatCtx->oformat->audio_codec);
if (!pCodec)
{
printf("没有找到编码器\n");
return;
}
//创建编码器上下文
pCodeCtx = avcodec_alloc_context3(pCodec);
pCodeCtx->codec_id = pFormatCtx->oformat->audio_codec;
pCodeCtx->codec_type = AVMEDIA_TYPE_AUDIO;
pCodeCtx->sample_fmt = AV_SAMPLE_FMT_FLTP;
pCodeCtx->sample_rate = nSampleRate; //采样率
pCodeCtx->channel_layout = channel_layout; //音频通道布局
pCodeCtx->channels = audio_channels; //声道数
//pCodeCtx->bit_rate = 64000;
//根据编码器上下文打开编码器
if (avcodec_open2(pCodeCtx, pCodec, NULL) < 0)
{
printf("无法打开编码器\n");
return;
}
//将AVCodecContext的成员复制到AVCodecParameters结构体,防止警告
avcodec_parameters_from_context(audio_st->codecpar, pCodeCtx);
//设置从数据源取每帧数据
pFrame = av_frame_alloc();
pFrame->nb_samples = pCodeCtx->frame_size; //1024 单个个通道的样本数
pFrame->format = pCodeCtx->sample_fmt; //AV_SAMPLE_FMT_FLTP 样本格式
pFrame->channels = pCodeCtx->channels; //声道数
//获取给定音频参数的缓冲区的大小,全部通道的所有样本的大小
int framebuf_size = 0;
framebuf_size = av_samples_get_buffer_size(NULL, pFrame->channels, pFrame->nb_samples, (AVSampleFormat)pFrame->format, 0); //双通道数据一帧有8192,但是原始数据一帧是4096
uint8_t* frame_buf;
frame_buf = (uint8_t *)av_malloc(framebuf_size);
//填充Frame音频数据并调整指针位置
//此时data[0],data[1]分别指向fram_buf的起始位置和中间位置
if (avcodec_fill_audio_frame(pFrame, pFrame->channels, (AVSampleFormat)pFrame->format, (const uint8_t*)frame_buf, framebuf_size, 0) < 0)
{
printf("Frame缓冲区分配出错\n");
return;
}
由于原音频文件是AV_SAMPLE_FMT_S16格式,而AAC格式是AV_SAMPLE_FMT_FLTP。如果不进行转码重采样,进行编码的时候,编码器会报 -22 的错误号。
//转换格式类型,新版库支持AV_SAMPLE_FMT_FLTP类型,但是pcm支持AV_SAMPLE_FMT_S16
SwrContext *swrCtx;
swrCtx = swr_alloc();
av_opt_set_int(swrCtx, "in_channel_layout", pCodeCtx->channel_layout, 0);
av_opt_set_int(swrCtx, "out_channel_layout", pCodeCtx->channel_layout, 0);
av_opt_set_int(swrCtx, "in_sample_rate", pCodeCtx->sample_rate, 0);
av_opt_set_int(swrCtx, "out_sample_rate", pCodeCtx->sample_rate, 0);
av_opt_set_sample_fmt(swrCtx, "in_sample_fmt", AV_SAMPLE_FMT_S16, 0);
av_opt_set_sample_fmt(swrCtx, "out_sample_fmt", AV_SAMPLE_FMT_FLTP, 0);
swr_init(swrCtx);
//设置转码后的内存
uint8_t **outs = (uint8_t **)calloc(pCodeCtx->channels, sizeof(*outs));
av_samples_alloc(outs, NULL, pCodeCtx->channels, pCodeCtx->frame_size, (AVSampleFormat)pCodeCtx->sample_fmt, 0);
这里需要注意的是,从原始音频文件中取数据时,要控制好一帧数据的量。
即计算in_buffer_size时,用到的应该是PCM格式的AV_SAMPLE_FMT_S16,而不是AAC格式的AV_SAMPLE_FMT_FLTP。
最开始我做的时候,没注意到这个问题,导致每次都是取2帧PCM数据,写到1帧AAC数据里,导致音频的大小减半,播放出来的速度加倍。
//写输出流的头
avformat_write_header(pFormatCtx, NULL);
int in_buffer_size = av_samples_get_buffer_size(NULL, audio_channels, 1024, AV_SAMPLE_FMT_S16, 0); //4096
//开始编码
for (framecount = 0;; framecount++)
{
//
av_init_packet(&packet);
packet.data = NULL;
packet.size = 0;
if (fread(frame_buf, 1, in_buffer_size, in_File) <= 0) //取一帧的数据,即4906的长度
{
//printf("读取源文件失败\n");
break;
}
else if (feof(in_File))
{
break;
}
//将取出的pcm数据进行重新采样,extended_data包括左右声道的全部数据
swr_convert(swrCtx, outs, pCodeCtx->frame_size, (const uint8_t **)pFrame->extended_data, pCodeCtx->frame_size);
if (audio_channels == 1) //单声道,每个声道一帧的长度
{
memcpy(pFrame->data[0], outs[0], framebuf_size);
}
else if (audio_channels == 2) //双声道,每个声道一帧的长度framebuf_size / 2
{
memcpy(pFrame->data[0], outs[0], framebuf_size / 2);
memcpy(pFrame->data[1], outs[1], framebuf_size / 2);
}
pFrame->pts = framecount; //设置时间戳
got_frame = 0;
//编码
int ret = 0;
avcodec_send_frame(pCodeCtx, pFrame);
while (!avcodec_receive_packet(pCodeCtx, &packet))
{
if (packet.data == NULL)
{
av_free_packet(&packet);
memset(frame_buf, 0, framebuf_size);
continue;
}
packet.stream_index = audio_st->index; //标识音频
if (av_write_frame(pFormatCtx, &packet) < 0) //写入文件
{
printf("编码后内容写文件失败\n");
return;
}
av_free_packet(&packet);
memset(frame_buf, 0, framebuf_size);
}
}
//flush buffer
for (got_frame = 1; got_frame; framecount++)
{
pFrame->pts = framecount;
if (avcodec_encode_audio2(pCodeCtx, &packet, NULL, &got_frame))
{
printf("Error encoding frame\n");
break;
}
if (got_frame)
{
packet.stream_index = audio_st->index; //标识音频
if (av_write_frame(pFormatCtx, &packet) < 0) //写入文件
{
printf("编码后内容写文件失败\n");
return;
}
av_free_packet(&packet);
}
}
av_write_trailer(pFormatCtx); //写入数据流尾,并释放私有数据
//清理
if (audio_st)
{
avcodec_close(audio_st->codec);
av_free(pFrame);
av_free(frame_buf);
}
av_freep(&outs[0]);
avio_close(pFormatCtx->pb);
avformat_free_context(pFormatCtx);
swr_free(&swrCtx);
fclose(in_File);
项目下载地址:
https://download.csdn.net/download/zeroling96/11939048
==================================================================================
后面写 语音聊天 程序的时候发现,如果通过UDP发送编码后的packet包,然后在对端进行解码播放这种方法。
只需要知道 packet.size 和 packet.data,将这两部分内容发到对端,对端自己声明并初始化一个AVPacket,然后将size和data的内容拷贝进去,将新生成的packet进行解码就可以了。