ffmpeg音频编码,PCM编码为ACM

基本流程

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.sizepacket.data,将这两部分内容发到对端,对端自己声明并初始化一个AVPacket,然后将size和data的内容拷贝进去,将新生成的packet进行解码就可以了。

你可能感兴趣的:(ffmpeg音频编码,PCM编码为ACM)