FFmpeg学习之MP3文件转PCM文件

1、基本原理说明

本文采用FFMpeg编码库,将Mp3文件转换成Pcm文件,具体的API知识点整理在后面给出。
FFmpeg作为常用的编解码库,其有着广泛的用途。现在主要介绍下FFmpeg的各个插件库的功能。

FFmpeg主要插件和其功能罗列如下:

插件名字功能
libavformat其主要应用于生成各种音视频格式和解析。其中包含解码所需要的信息、解码上下文句柄等。音还包括音视频的格式协议 。为提供音频和视频源。
libavcodec应用于audio/video的编码和解码。是整个编码库的核心。
libavdevice作为硬件采集、加速和显示的插件。用于操作计算中的各种audio/video设备。
libavfilteraudio/video中的滤波器开发。
libavuti基本的功能库。
libavresample音视频封转编解码格式预设等。
libpostproc用于时间同步pts dts;音视频应用的后处理,如图像的去块效应。
ffmpegffmpeg的工具,可用于格式转换、解码或电视卡即时编码等。
ffseverffmpeg的HTTP 多媒体即时广播串流服务器。
ffplayffmpeg的播放器,用于一般性测试。

MP3转PCM基本流程图如下:
在这里插入图片描述

下面是MP3文件转换成Pcm文件的基本。
基本结构定义:

typedef struct AStream {
	enum AVSampleFormat      eSfmt;
	AVPacket 				 * pPkt;
	AVCodec 		         * pCodec;
	AVFrame					 * pFrameMP3;
	AVCodecContext 			 * pCodecCtx;
	AVCodecParserContext	 * pCodecParCtx;
}tAStream;

typedef struct ASampleFmtItem {
	enum AVSampleFormat       fmt;
	const char			  * fmtbe;
	const char		      * fmtle;
}tASampleFmtItem;

tASampleFmtItem ASampleFmtList[] = {
		{ AV_SAMPLE_FMT_U8,  "u8",    "u8"    },
		{ AV_SAMPLE_FMT_S16, "s16be", "s16le" },
		{ AV_SAMPLE_FMT_S32, "s32be", "s32le" },
		{ AV_SAMPLE_FMT_FLT, "f32be", "f32le" },
		{ AV_SAMPLE_FMT_DBL, "f64be", "f64le" },
};

程序源码如下:

/*************************************************************************
	> File   : decodec_MP3_ToPcm.c
	> Author : 小和尚念经敲木鱼
	> Email  : null
	> Time   : Sun 07 Feb 2021 07:19:46 PM CST
*************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <libavutil/frame.h>
#include <libavutil/mem.h>
#include <libavcodec/avcodec.h>

#define AUDIO_INPUT_SIZE (2048*10)
#define AUDIO_REFILE_THRESH (4096)

typedef struct AStream {
	enum AVSampleFormat      eSfmt;
	AVPacket 				 * pPkt;
	AVCodec 				 * pCodec;
	AVFrame					 * pFrameMP3;
	AVCodecContext 		     * pCodecCtx;
	AVCodecParserContext	 * pCodecParCtx;
}tAStream;

typedef struct ASampleFmtItem {
	enum AVSampleFormat       fmt;
	const char			    * fmtbe;
	const char				* fmtle;
}tASampleFmtItem;

tASampleFmtItem ASampleFmtList[] = {
		{ AV_SAMPLE_FMT_U8,  "u8",    "u8"    },
		{ AV_SAMPLE_FMT_S16, "s16be", "s16le" },
		{ AV_SAMPLE_FMT_S32, "s32be", "s32le" },
		{ AV_SAMPLE_FMT_FLT, "f32be", "f32le" },
		{ AV_SAMPLE_FMT_DBL, "f64be", "f64le" },
};

static int getFromatByFmt(const char ** fmt, enum AVSampleFormat samplefmt)
{
	int i;
	*fmt = NULL;

	for (i = 0; i < FF_ARRAY_ELEMS(ASampleFmtList); i++) {
		tASampleFmtItem * item = &ASampleFmtList[i];
		if (samplefmt == item->fmt) {
			*fmt = AV_NE(item->fmtbe,item->fmtle);	
			return 0;
		}
	}
	//返回AVSampleFormat的名字
	printf("sample format %s error!\n",av_get_sample_fmt_name(samplefmt));
	return -1;
}

static void decodec(AVCodecContext * decctx, AVPacket * pkt, AVFrame * frame, FILE * outfile)
{
	int i,ch,ret,data_size;
	
	if (NULL == decctx) {
		printf("decctx is null\n");
		return;
	}
	if (NULL == pkt) {
		printf("pkt is null\n");
		return;
	}
	if (NULL == frame) {
		printf("frame is null\n");
		return;
	}
	if (NULL == outfile) {
		printf("outfile is null\n");
		return;
	}
	//给解码器提供原始数据
	ret = avcodec_send_packet(decctx, pkt);
	if (ret < 0) {
		fprintf(stderr, "Error submitting the packet to the decoder ret : %d \n",ret);
		exit(1);
    }
	while (ret >= 0) {
		ret = avcodec_receive_frame(decctx, frame);
		if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
			return;
		}	else if (ret < 0) {
			fprintf(stderr, "Error during decoding\n");
			exit(1);
		}
		if ((data_size = av_get_bytes_per_sample(decctx->sample_fmt)) < 0) {
			printf("Failed to calculate data...\n");
			exit(1);
		}
		for (i = 0; i < frame->nb_samples; i++) {
			for (ch = 0; ch < decctx->channels; ch++)
				fwrite(frame->data[ch] + data_size * i, 1, data_size, outfile);
		}
	}
}

int main(int argc,char ** argv[])
{
	const char * out_file_path,*in_file_path;

	tAStream  dec_astream;
	enum AVSampleFormat sfmt;

	int n_channels = 0;
	const char *fmt;
	FILE * in_file,* out_file;

	int ret;
	uint8_t input_buffer[AUDIO_INPUT_SIZE + AV_INPUT_BUFFER_PADDING_SIZE];
	size_t data_size;
	uint8_t * data;
	int length;

	memset(&dec_astream,0,sizeof(struct AStream));

	if (argc <= 2) {
		fprintf(stderr, "Usage: %s [input file] [output file]\n", argv[0]);
		exit(0);
	}

	dec_astream.pPkt = av_packet_alloc();
	dec_astream.pCodec = avcodec_find_decoder(AV_CODEC_ID_MP2);
	if (NULL == dec_astream.pCodec) {
		printf("Codec not Found!\n");
		exit(1);
	}
	dec_astream.pCodecParCtx = av_parser_init(dec_astream.pCodec->id);
	if (NULL == dec_astream.pCodecParCtx) {
		printf("Parser not Found!\n");
		exit(1);
	}
	dec_astream.pCodecCtx = avcodec_alloc_context3(dec_astream.pCodec);
	if (NULL == dec_astream.pCodecCtx) {
		printf("pCodecCtx not Found!\n");
		exit(1);
	}
	if (avcodec_open2(dec_astream.pCodecCtx,dec_astream.pCodec,NULL) < 0) {
		printf("Could not open codec");
		exit(1);
	}
	dec_astream.pFrameMP3 = av_frame_alloc();
	if (NULL == dec_astream.pFrameMP3) {
		printf("Frame alloc Failed.\n");
		exit(1);
	}

	in_file_path  = argv[1];
	out_file_path = argv[2];

	printf("input_file = %s output_file = %s\n",in_file_path,out_file_path);

	in_file = fopen(in_file_path,"rb");
	if (in_file == NULL) {
		printf("Open inout file Error!");
		exit(1);
	}
	out_file = fopen(out_file_path,"wb");
	if (out_file == NULL) {
		printf("open input file or open out file Error!\n");
		av_free(dec_astream.pCodecCtx);
		exit(1);
	}
	uint8_t header[10];
	fread(header, 10, 1, in_file);
	long frame_size = (header[6] & 0xff) << 21 | (header[7] & 0xff) << 14 | (header[8] & 0xff) << 7 | header[9] & 0xff;
	data = input_buffer;
	fseek(in_file, frame_size + 10, 0);
	data      = input_buffer;
	data_size = fread(input_buffer, 1, AUDIO_INPUT_SIZE, in_file);

	while ( data_size > 0 ) {
		ret = av_parser_parse2(dec_astream.pCodecParCtx,dec_astream.pCodecCtx,\
													 &(dec_astream.pPkt)->data,&(dec_astream.pPkt)->size,\
													 data,data_size,AV_NOPTS_VALUE,AV_NOPTS_VALUE,0);
		if(ret < 0 ) {
			exit(1);
		}

		data      += ret;
		data_size -= ret;

		if (dec_astream.pPkt->size)
				decodec(dec_astream.pCodecCtx, dec_astream.pPkt, dec_astream.pFrameMP3, out_file);

		if (data_size < AUDIO_INPUT_SIZE) {
				memmove(input_buffer, data, data_size);
				data = input_buffer;
				length = fread(data + data_size, 1,\
				AUDIO_INPUT_SIZE - data_size, in_file);

				if (length > 0)
						data_size += length;
		}
  }

	dec_astream.pPkt->data = NULL;
	dec_astream.pPkt->size = 0;

	decodec(dec_astream.pCodecCtx,dec_astream.pPkt,dec_astream.pFrameMP3,out_file);

	sfmt = dec_astream.eSfmt; 

	if (av_sample_fmt_is_planar(sfmt)) {
		const char * packed_target = av_get_sample_fmt_name(sfmt);
		printf("Warning: the sample format the decoder produced is planar "
							"(%s). This example will output the first channel only.\n",
							packed_target ? packed_target : "?");

		sfmt = av_get_packed_sample_fmt(sfmt);
	}

	n_channels = dec_astream.pCodecCtx->channels;
	if ((ret = getFromatByFmt(&fmt,sfmt)) < 0 )
		goto RETURN_1;

	printf("Decodec output file from mp3 file %s channels: %d \n",out_file_path,n_channels);

RETURN_1:
	fclose(out_file);
	fclose(in_file);
	avcodec_free_context(&dec_astream.pCodecCtx);
	av_parser_close(dec_astream.pCodecParCtx);
	av_frame_free(&dec_astream.pFrameMP3);
	av_packet_free(&dec_astream.pPkt);

  return 0;
}

2、主要API功能介绍和知识点整理

1、初始化基本结构体部分

1.1 AVPacket *av_packet_alloc(void)
	/*
	@function :创建AVPacket实例
	@param[in]:null
	@return   :返回AVPacket的结构体指针
	*/
AVPacket *av_packet_alloc(void);

1.2 AVCodecContext *avcodec_alloc_context3(const AVCodec *codec)
	/*
	@function :申请AVCodecContext空间,需要传递一个编码器,也可以不传,如果不传则创建一个私有数据并初始化为默认值。
	@param[in]:codec 编码器
	@return   :成功返回AVCodecContext,其他或者空值为失败。
	*/
AVCodecContext *avcodec_alloc_context3(const AVCodec *codec);

1.3 int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options)
	/*
	@function :初始化一个视音频编解码器的AVCodecContext。
	@param[in]:avctx初始化的上下文结构体 AVCodecContext
	@param[in]:codec采用AVCodec打开上下文,如果是一个非空的编码器,则codec必须是先前申请的。
	对于这个context,这个参数要么为空要么空,要么是先前传递的编码器。
	@param[in]:options参数词典。
	@return:
	*/
int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options);

	/*Example
	* @code
	* avcodec_register_all();
	* av_dict_set(&opts, "b", "2.5M", 0);
	* codec = avcodec_find_decoder(AV_CODEC_ID_H264);
	* if (!codec)
	*     exit(1);
	*
	* context = avcodec_alloc_context3(codec);
	*
	* if (avcodec_open2(context, codec, opts) < 0)
	*     exit(1);
	* @endcode
	*/

1.4 AVCodecParserContext *av_parser_init(int codec_id)
	/*
	*@function :根据编码器ID初始化。
	*@param[in]:codec_id编码器ID。
	*return    : 返回上下文实例指针AVCodecParserContext。
	*/
AVCodecParserContext *av_parser_init(int codec_id)1.5 AVFrame *av_frame_alloc(void)
	/*
	@function :申请一个AVFrame,并将其设置成默认值。结果必须av_frame_free()释放。
	@return   :返回AVFrame
	*/
AVFrame *av_frame_alloc(void)1.6 int av_sample_fmt_is_planar(enum AVSampleFormat sample_fmt);
	/*
	@function :检测sample_fmt是否是planar的。
	@param[in]:AVSampleFormat
	*/
int av_sample_fmt_is_planar(enum AVSampleFormat sample_fmt);

2、解码部分

 2.1 int avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt)
	/*
	  @function :给解码器提供原始数据。
           会拷贝相关的AVCodecContext结构变量,将这些结构变量应用到解码的每一个包
	  @param[in]:avctx AVCodecContext编码器,存储着该视频/音频使用的解码方式的相关数据、
           还包含音视频流的相关信息、如宽高、比特率、声道数
	  @param[in]:avpkt AVPacket 压缩编码数据相关信息的结构体。
	  @return   :
				    0 				    : success
			    AVERROR(EAGAIN) : 当前不支持用户输入,需要调用avcodec_receive_frame()读取所有数据之后重新发送数据包,
								            然后使用EAGIN调用才不会失败。
			    AVERROR_EOF     : 解码器已经被刷新,无法创建新的数据包。
			    AVERROR(EINVAL) : 解码器未打开,它是一个编辑器,或者刷新。
			    AVERROR(ENOMEM) : 无法天剑数据包到内部队列中。
				other errors      : 合法编码错误。
	 */
	 int avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt);

2.2 int avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame)
	/*
	  @function :从解码器中返回解码的数据。
	  @param[in]:avctx AVCodecContext:编码器,存储着该视频/音频使用的解码方式的相关数据、
           还包含音视频流的相关信息、如宽高、比特率、声道数
	  @param[in]:avpkt AVFrame:它当中存储的是解码后的原始数据。解码中,AVFrame是解码器的输出;
				  在编码中,AVFrame是编码器的输入。
	  @return   :
				0 			   : success,返回格式,从解码器返回解码的输入数据。
			    AVERROR(EAGAIN): 当前不支持用户输入,需要用户重新发送一个Input发送数据包,
								 然后使用EAGIN调用才不会失败。
			    AVERROR_EOF	   : 解码器已经被刷新,无法创建新的数据包。
			    AVERROR(EINVAL): 解码器未打开,它是一个编辑器。
			    AVERROR(ENOMEM): 无法天剑数据包到内部队列中。
				other errors   : 合法编码错误。
	 */
	 int avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame);

2.3 int av_parser_parse2(AVCodecParserContext *s,AVCodecContext *avctx,uint8_t **poutbuf,
		      int *poutbuf_size,const   uint8_t *buf, int buf_size,int64_t pts, int64_t dts,int64_t pos);
	/*
	  @function:解析数据包
	  @param[in]:avctx :正常的 AVCodecContext
	  @param[in]:poutbuf:初始化后的avpkt的avpkt.data
	  @param[in]:poutbuf_size:初始化后的avpkt的avpkt.size
	  @param[in]:buf:一次接收的数据包。
	  @param[in]:buf_size:本次接收数据包的长度。
	  @param[in]:pts没有的话,可以在声明后直接用int pts,单单h264的话直接挂上pts即可。
	  @param[in]:dts:同上。
	  @param[in]:pos:输入流的直接数偏移。
      @return:返回所用流的字节数
	 */
	 int av_parser_parse2(AVCodecParserContext *s,
	                     AVCodecContext *avctx,
	                     uint8_t **poutbuf, int *poutbuf_size,
	                     const uint8_t *buf, int buf_size,
	                     int64_t pts, int64_t dts,
	                     int64_t pos);
	 /**
	 * Example:
	 * @code
	 *   while(in_len){
	 *       len = av_parser_parse2(myparser, AVCodecContext, &data, &size,
	 *                                        in_data, in_len,
	 *                                        pts, dts, pos);
	 *       in_data += len;
	 *       in_len  -= len;
	 *
	 *       if(size)
	 *          decode_frame(data, size);
	 *   }
	 * @endcode
	 */

3、获取参数部分

 3.1 const char *av_get_sample_fmt_name(enum AVSampleFormat sample_fmt)
		/*
		  @function :根据采样率获取采样字符格式名
		  @param[in]:AVSampleFormat 采样格式
		  @return   :格式字符名
		 */
	const char *av_get_sample_fmt_name(enum AVSampleFormat sample_fmt);

3.2 int av_get_bytes_per_sample(enum AVSampleFormat sample_fmt)
		/*
		  @function :根据采样率获取采样字节数
		  @param[in]:AVSampleFormat 采样格式
		  @return   :字节数
		 */
	int av_get_bytes_per_sample(enum AVSampleFormat sample_fmt);

3.3 enum AVSampleFormat av_get_packed_sample_fmt(enum AVSampleFormat sample_fmt)
		/*
		  @function :根据采样率获取采样字节数
		  @param[in]:AVSampleFormat 采样格式
		  @return   :字节数
		 */
	enum AVSampleFormat av_get_packed_sample_fmt(enum AVSampleFormat sample_fmt);

4、释放资源部分

4.1 void avcodec_free_context(AVCodecContext **avctx)
		/*
		  @function :释放上下文
		  @param[in]:AVCodecContext实例指针
		 */
		 void avcodec_free_context(AVCodecContext **avctx);

4.2 void av_parser_close(AVCodecParserContext *s)
		/*
		  @function :关闭上下文
		  @param[in]:AVCodecParserContext实例指针
		 */
		 void av_parser_close(AVCodecParserContext *s);

4.3 void av_frame_free(AVFrame **frame)
		/*
		  @function :释放格式指针
		  @param[in]:AVFrame实例指针
		 */
		 void av_frame_free(AVFrame **frame);

4.4 void av_packet_free(AVPacket **pkt)
		/*
		  @function :释放包
		  @param[in]:AVPacket实例指针
		 */
		 void av_packet_free(AVPacket **pkt);

总结

基本参考FFmpeg示例代码一步步走下来的,可以实现MP3文件转换成PCM文件。主要还是需要熟悉FFmpeg的各个函数接口的调用。

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

给大佬递杯卡布奇诺

你们的鼓励就是我传作的动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值