FFmpeg 音频编码

1.简介

编码音频数据,把pcm原始数据编码为MP3或者AAC。

2.流程

2.1在使用FFmpeg API之前,需要先注册API,然后才能使用API。当然,新版本的库不需要再调用下面的方法。

av_register_all()

2.2查找编码器,本例演示编码MP3

	//找到编码器
	codec = avcodec_find_encoder(AV_CODEC_ID_MP3);
	if (!codec) 
	{
		fprintf(stderr, "Codec not found\n");
		exit(1);
	}

 2.3申请AVCodecContext

    //申请AVCodecContext
    AVCodecContext* codec_ctx = nullptr;
	codec_ctx = avcodec_alloc_context3(codec);
	if (!codec_ctx)
	{
		fprintf(stderr, "Could not allocate audio codec context\n");
		exit(1);
	}

2.4设置音频参数

需要先指定配置编码器的一些参数。像音频的话,比如它的采样率,声道数,编码格式等。也需要配置编码后输出的数据的参数,如下,我们这里参数的设定是输入的PCM是48000,FLT,码率25600,输出的参数跟codec_ctx参数基本相似

	//设置参数
	codec_ctx->bit_rate = 256000;
	codec_ctx->sample_fmt = AV_SAMPLE_FMT_FLTP;

	//检查支不支持这个格式
	if (!check_sample_fmt(codec, codec_ctx->sample_fmt)) 
	{
		fprintf(stderr, "Encoder does not support sample format %s",av_get_sample_fmt_name(codec_ctx->sample_fmt));
		exit(1);
	}

    codec_ctx->sample_rate = 48000;
	codec_ctx->channel_layout = select_channel_layout(codec);
	codec_ctx->channels = av_get_channel_layout_nb_channels(codec_ctx->channel_layout);



	frame->nb_samples = codec_ctx->frame_size;
	frame->format = codec_ctx->sample_fmt;
	frame->channel_layout = codec_ctx->channel_layout;
	frame->channels = codec_ctx->channels;

2.5打开编码器

	//打开编码器
	if (avcodec_open2(codec_ctx, codec, NULL) < 0)
	{
		fprintf(stderr, "Could not open codec\n");
		exit(1);
	}

2.6分配帧空间

	/* allocate the data buffers */
	int ret = av_frame_get_buffer(frame, 0);
	if (ret < 0) 
	{
		fprintf(stderr, "Could not allocate audio data buffers\n");
		exit(1);
	}

2.7计算音频帧数据大小

扫描二维码关注公众号,回复: 14470695 查看本文章

设置完编码器的参数后和需要编码的数据帧相关的参数后,可以根据几个参数计算出音频采样buffer的大小。

	//计算音频帧信息
	int buffer_size = av_samples_get_buffer_size(NULL, codec_ctx->channels, codec_ctx->frame_size, codec_ctx->sample_fmt, 0);
	if (buffer_size < 0)
	{
		exit(1);
	}

2.8读取pcm文件,开始编码,写入到本地MP3文件。

准备就绪后,我们就可以开始编码。音频编码的新接口同样是通过发送给编码器,然后接收的方式进行编码。编码的核心代码如下,编码后的数据存放在AVPacket中,将pkt中的数据写入到本地文件即可。

    while (!feof(fp))
	{
		//从文件读取数据
		int count = fread(pcmBuffer, sizeof(char), buffer_size, fp);

		//本地读取到的packed数据转换为planar
		f32le_convert_to_fltp((float*)pcmBuffer, (float*)pcmBuffer2, frame->nb_samples);
	
		ret = av_samples_fill_arrays(frame->data, frame->linesize,
			(const uint8_t*)pcmBuffer2, frame->channels,
			frame->nb_samples, AV_SAMPLE_FMT_FLTP, 0);
	
		//开始编码
		ret = avcodec_send_frame(codec_ctx, frame);

		while (ret >= 0)
		{
			ret = avcodec_receive_packet(codec_ctx, pkt);
			if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
			{
				break;
			}
			else if (ret < 0)
			{
				break;
			}

			//编码后的数据写到本地文件
			fwrite(pkt->data, 1, pkt->size, f);
			av_packet_unref(pkt);
		}
	}

3.源码

本例演示从本地读取pcm文件,编码完成后写入到MP3文件中。

#include "pch.h"
#include <iostream>

extern "C"
{
#include "libavformat/avformat.h"
#include "libavutil/dict.h"
#include "libavutil/opt.h"
#include "libavutil/timestamp.h"
#include "libswscale/swscale.h"
#include "libswresample/swresample.h"
#include "libavutil/imgutils.h" 
};
/* check that a given sample format is supported by the encoder */
static int check_sample_fmt(const AVCodec* codec, enum AVSampleFormat sample_fmt)
{
	const enum AVSampleFormat* p = codec->sample_fmts;

	while (*p != AV_SAMPLE_FMT_NONE) 
	{
		if (*p == sample_fmt)
			return 1;
		p++;
	}
	return 0;
}

/* just pick the highest supported samplerate */
static int select_sample_rate(const AVCodec* codec)
{
	const int* p;
	int best_samplerate = 0;

	if (!codec->supported_samplerates)
		return 44100;

	p = codec->supported_samplerates;
	while (*p)
	{
		if (!best_samplerate || abs(44100 - *p) < abs(44100 - best_samplerate))
			best_samplerate = *p;
		p++;
	}
	return best_samplerate;
}

/* select layout with the highest channel count */
static int select_channel_layout(const AVCodec* codec)
{
	const uint64_t* p;
	uint64_t best_ch_layout = 0;
	int best_nb_channels = 0;
	if (!codec->channel_layouts)
		return AV_CH_LAYOUT_STEREO;
	p = codec->channel_layouts;
	while (*p) {
		int nb_channels = av_get_channel_layout_nb_channels(*p);
		if (nb_channels > best_nb_channels) {
			best_ch_layout = *p;
			best_nb_channels = nb_channels;
		}
		p++;
	}
	return best_ch_layout;
}


void f32le_convert_to_fltp(float* f32le, float* fltp, int nb_samples) 
{
	float* fltp_l = fltp;   // 左通道
	float* fltp_r = fltp + nb_samples;   // 右通道
	for (int i = 0; i < nb_samples; i++)
	{
		fltp_l[i] = f32le[i * 2];     // 0 1   - 2 3
		fltp_r[i] = f32le[i * 2 + 1];   // 可以尝试注释左声道或者右声道听听声音
	}
}

int main()
{
	//av_register_all();
	avformat_network_init();

    AVCodec* codec = nullptr;

	//找到编码器
	codec = avcodec_find_encoder(AV_CODEC_ID_MP3);
	if (!codec) 
	{
		fprintf(stderr, "Codec not found\n");
		exit(1);
	}

    //申请AVCodecContext
    AVCodecContext* codec_ctx = nullptr;
	codec_ctx = avcodec_alloc_context3(codec);
	if (!codec_ctx)
	{
		fprintf(stderr, "Could not allocate audio codec context\n");
		exit(1);
	}

	//设置参数
	codec_ctx->bit_rate = 256000;
	codec_ctx->sample_fmt = AV_SAMPLE_FMT_FLTP;

	//检查支不支持这个格式
	if (!check_sample_fmt(codec, codec_ctx->sample_fmt)) 
	{
		fprintf(stderr, "Encoder does not support sample format %s",av_get_sample_fmt_name(codec_ctx->sample_fmt));
		exit(1);
	}

    codec_ctx->sample_rate = 48000;
	codec_ctx->channel_layout = select_channel_layout(codec);
	codec_ctx->channels = av_get_channel_layout_nb_channels(codec_ctx->channel_layout);

	//打开编码器
	if (avcodec_open2(codec_ctx, codec, NULL) < 0)
	{
		fprintf(stderr, "Could not open codec\n");
		exit(1);
	}

	AVPacket *pkt = av_packet_alloc();
	if (!pkt)
	{
		fprintf(stderr, "could not allocate the packet\n");
		exit(1);
	}

	AVFrame *frame = av_frame_alloc();
	if (!frame) 
	{
		fprintf(stderr, "Could not allocate audio frame\n");
		exit(1);
	}

	frame->nb_samples = codec_ctx->frame_size;
	frame->format = codec_ctx->sample_fmt;
	frame->channel_layout = codec_ctx->channel_layout;
	frame->channels = codec_ctx->channels;

	/* allocate the data buffers */
	int ret = av_frame_get_buffer(frame, 0);
	if (ret < 0) 
	{
		fprintf(stderr, "Could not allocate audio data buffers\n");
		exit(1);
	}


	//计算音频帧信息
	int buffer_size = av_samples_get_buffer_size(NULL, codec_ctx->channels, codec_ctx->frame_size, codec_ctx->sample_fmt, 0);
	if (buffer_size < 0)
	{
		exit(1);
	}

	uint8_t* pcmBuffer = (uint8_t*)av_malloc(buffer_size);
	if (!pcmBuffer)
	{
		exit(1);
	}

	uint8_t* pcmBuffer2 = (uint8_t*)av_malloc(buffer_size);
	if (!pcmBuffer2)
	{
		exit(1);
	}

	//打开输出的文件
	char fileName[20] = "output.mp3";
	FILE* f = fopen(fileName, "wb");
	if (!f)
	{
		fprintf(stderr, "Could not open %s\n", fileName);
		exit(1);
	}

	//打开输入文件
	const char* inputUrl = "test.pcm";
	FILE* fp = fopen(inputUrl, "rb");
	if (!fp)
	{
		fprintf(stderr, "Could not open %s\n", inputUrl);
		exit(1);
	}

	while (!feof(fp))
	{
		//从文件读取数据
		int count = fread(pcmBuffer, sizeof(char), buffer_size, fp);

		//本地读取到的packed数据转换为planar
		f32le_convert_to_fltp((float*)pcmBuffer, (float*)pcmBuffer2, frame->nb_samples);
	
		ret = av_samples_fill_arrays(frame->data, frame->linesize,
			(const uint8_t*)pcmBuffer2, frame->channels,
			frame->nb_samples, AV_SAMPLE_FMT_FLTP, 0);
	
		//开始编码
		ret = avcodec_send_frame(codec_ctx, frame);

		while (ret >= 0)
		{
			ret = avcodec_receive_packet(codec_ctx, pkt);
			if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
			{
				break;
			}
			else if (ret < 0)
			{
				break;
			}

			//编码后的数据写到本地文件
			fwrite(pkt->data, 1, pkt->size, f);
			av_packet_unref(pkt);
		}
	}


	fclose(fp);
	fclose(f);

	avcodec_close(codec_ctx);
	avcodec_free_context(&codec_ctx);
	av_frame_free(&frame);
	av_packet_free(&pkt);

    return 0;
}

猜你喜欢

转载自blog.csdn.net/wzz953200463/article/details/125920862