1、基本原理说明
本文采用FFMpeg编码库,将Mp3文件转换成Pcm文件,具体的API知识点整理在后面给出。
FFmpeg作为常用的编解码库,其有着广泛的用途。现在主要介绍下FFmpeg的各个插件库的功能。
FFmpeg主要插件和其功能罗列如下:
插件名字 | 功能 |
---|---|
libavformat | 其主要应用于生成各种音视频格式和解析。其中包含解码所需要的信息、解码上下文句柄等。音还包括音视频的格式协议 。为提供音频和视频源。 |
libavcodec | 应用于audio/video的编码和解码。是整个编码库的核心。 |
libavdevice | 作为硬件采集、加速和显示的插件。用于操作计算中的各种audio/video设备。 |
libavfilter | audio/video中的滤波器开发。 |
libavuti | 基本的功能库。 |
libavresample | 音视频封转编解码格式预设等。 |
libpostproc | 用于时间同步pts dts;音视频应用的后处理,如图像的去块效应。 |
ffmpeg | ffmpeg的工具,可用于格式转换、解码或电视卡即时编码等。 |
ffsever | ffmpeg的HTTP 多媒体即时广播串流服务器。 |
ffplay | ffmpeg的播放器,用于一般性测试。 |
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的各个函数接口的调用。