FFMPEG音频解码浅析

结合各种资料和自己的理解,估计有些浅显。

FFMPEG解码流程:

  1. 注册所有容器格式和CODEC: av_register_all()

  2. 打开文件: av_open_input_file()

  3. 从文件中提取流信息: av_find_stream_info()

  4. 穷举所有的流,查找其中种类为CODEC_TYPE_AUDIO

  5. 查找对应的解码器: avcodec_find_decoder()

  6. 打开编解码器: avcodec_open()

  7. 为解码帧分配内存: avcodec_alloc_frame()

  8. 不停地从码流中提取出帧数据: av_read_frame()

  9. 对于音频帧调用: avcodec_decode_audio()

  10.解码完后,释放解码器:avcodec_close()

  11.关闭输入文件:avformat_close_input_file()

 

 

Ffmpeg音频解码重要的数据结构分析:

AVFormatContext :这个结构体描述了一个媒体文件或媒体流的构成和基本信息。

这个结构体是媒体流打开文件av_open_input_file()时在内部创建并且以缺省值完成部分成员的初始化。

然后由av_find_stream_info ()从文件中提取流信息。

得到流信息后,其他的上下文信息可以从它得到,是其他所有结构的根,是一个多媒体文件或流的根本抽象,比如。      

AVCodecContext*pCodeCtx = pFmtCtx->streams[audioStream]->codec;

AVCodec *pCodec =avcodec_find_decoder(pFmtCtx->streams[audioStream]->codec->codec_id)

start_time和duration是从streams数组的各个AVStream中推断出的多媒体文件的起始时间和长度,以微妙为单位。

nb_streamsstreams所表示的AVStream结构指针数组包含了所有内嵌媒体流的描述;

iformatoformat指向对应的demuxermuxer指针,解码和编码的格式,比如在解码过程中strcmp(pFmtCtx->iformat->name, "mp3");判断是Mp3文件。

解码的过程,每一个packet取得也是由AVFormatContext得到的流信息取得,av_read_frame(pFmtCtx,&packet)。

      

AVStream:媒体流pFmtCtx->streams[]中的一个-,主要域的释义如下,其中大部分域的值可以由av_open_input_file根据文件头的信息确定,缺少的信息需要通过调用av_find_stream_info读帧及软解码进一步获取。

index/idindex对应流的索引,这个数字是自动生成的,根据index可以从AVFormatContext::streams表中索引到该流;而id则是流的标识,依赖于具体的容器格式。比如对于MPEG TS格式,id就是pid

time_base:流的时间基准,是一个实数,该流中媒体数据的ptsdts都将以这个时间基准为粒度。通常,使用av_rescale/av_rescale_q可以实现不同时间基准的转换。

start_time:流的起始时间,以流的时间基准为单位,通常是该流中第一个帧的pts

duration:流的总时间,以流的时间基准为单位。

need_parsing:对该流parsing过程的控制域。

nb_frames:流内的帧数目。

r_frame_rate/framerate/avg_frame_rate:帧率相关。

codec:指向该流对应的AVCodecContext结构,调用av_open_input_file时生成。

parser:指向该流对应的AVCodecParserContext结构,调用av_find_stream_info时生成。

 

AVCodecContext:描述编解码器上下文的数据结构,包含了众多编解码器需要的参数信息。AVCodecContext *pCodeCtx =pFmtCtx->streams[audioStream]->codec;由流信息直接得到,

其中几个主要 域的释义如下:

extradata/extradata_size: 这个buffer中存放了解码器可能会用到的额外信息,在av_read_frame中填充。一般来说,首先,某种具体格式的demuxer在读取格式头 信息的时候会填充extradata,其次,如果demuxer没有做这个事情,比如可能在头部压根儿就没有相关的编解码信息,则相应的parser会继 续从已经解复用出来的媒体流中继续寻找。在没有找到任何额外信息的情况下,这个buffer指针为空。

time_base:

width/height:视频的宽和高。

sample_rate/channels:音频的采样率和信道数目。

sample_fmt:音频的原始采样格式。

codec_name/codec_type/codec_id/codec_tag:编解码器的信息。

 

AVPacket

 FFMPEG使用AVPacket来暂存解复用之后、解码之前的媒体数据(一个音/视频帧、一个字幕包等)及附加信息(解码时间戳、显示时间戳、时长等)。其中:

具体在解码的过程得到,如av_read_frame(pFmtCtx,&packet)。

每次获得一个packet的信息,解码的单位也是按照一个packet的操作完成,解码的具体过程就是连续的取packet然后对其进行操作的。

dts 表示解码时间戳,pts表示显示时间戳,它们的单位是所属媒体流的时间基准。这个在获取进度信息的时候会用到,如果解码的是视频的话,通常会跟音频和视频的同步用到。

   stream_index 给出所属媒体流的索引;

   data 为数据缓冲区指针,是其具体的内容,size为长度;

   duration 为数据的时长,也是以所属媒体流的时间基准为单位;

   pos 表示该数据在媒体流中的字节偏移量(个人觉得这个也可以做跳转的);

   destruct 为用于释放数据缓冲区的函数指针;

   flags 为标志域,其中,最低为置1表示该数据是一个关键帧。

 AVPacket 结构本身只是个容器,它使用data成员指向实际的数据缓冲区,使用之后需要通过调用av_free_packet释放。

 

具体解码过程:

结合着一个简单的解码的代码:

#include <stdio.h>

#include <stdlib.h>

extern "C"{//

#include "avcodec.h"

#include "avformat.h"

}

 

int main(char arg,char *argv[])

{

   char *filename ="02.swf";

   av_register_all();//注册所有可解码类型

   AVFormatContext *pInFmtCtx=NULL;//文件格式

   AVCodecContext *pInCodecCtx=NULL;//编码格式

   if (av_open_input_file(&pInFmtCtx,filename,NULL, 0, NULL)!=0)//获取文件格式

       printf("av_open_input_file error\n");

   if(av_find_stream_info(pInFmtCtx) < 0)//获取文件内音视频流的信息

       printf("av_find_stream_info error\n");

   

   unsigned int j;

// Find thefirst audio stream

 

   int    audioStream = -1;

   for(j=0; j<pInFmtCtx->nb_streams; j++)//找到音频对应的stream

       if(pInFmtCtx->streams[j]->codec->codec_type==CODEC_TYPE_AUDIO)

       {

           audioStream=j;

           break;

       }

       if(audioStream==-1)

       {

           printf("input file has no audio stream\n");

           return 0; // Didn't find a audio stream

 

       }

       printf("audio stream num: %d\n",audioStream);

       pInCodecCtx = pInFmtCtx->streams[audioStream]->codec;//音频的编码上下文

       AVCodec *pInCodec=NULL;

       pInCodec = avcodec_find_decoder(pInCodecCtx->codec_id);//根据编码ID找到用于解码的结构体

       if(pInCodec==NULL)

       {

           printf("error no Codec found\n");

           return -1 ; // Codec not found

       }

 

       //使用test的代替pInCodecCtx也可以完成解码,可以看出只要获取以下几个重要信息就可以实现解码和重采样

       AVCodecContext *test = avcodec_alloc_context();

       test->bit_rate = pInCodecCtx->bit_rate;//重采样用

       test->sample_rate = pInCodecCtx->sample_rate;//重采样用

       test->channels = pInCodecCtx->channels;//重采样用

       test->extradata = pInCodecCtx->extradata;//若有则必有

       test->extradata_size = pInCodecCtx->extradata_size;//若有则必要

       test->codec_type = CODEC_TYPE_AUDIO;//不必要

       test->block_align = pInCodecCtx->block_align ;//必要

 

       if(avcodec_open(test, pInCodec)<0)//将两者结合以便在下面的解码函数中调用pInCodec中的对应解码函数

       {

           printf("error avcodec_open failed.\n");

           return -1; // Could not open codec

 

       }

       

       if(avcodec_open(pInCodecCtx, pInCodec)<0)

       {

           printf("error avcodec_open failed.\n");

           return -1; // Could not opencodec

 

       }

       

       static AVPacket packet;

       

       printf(" bit_rate = %d \r\n", pInCodecCtx->bit_rate);

       printf(" sample_rate = %d \r\n", pInCodecCtx->sample_rate);

       printf(" channels = %d \r\n", pInCodecCtx->channels);

       printf(" code_name = %s \r\n",pInCodecCtx->codec->name);

       uint8_t *pktdata;

       int pktsize;

       int out_size = AVCODEC_MAX_AUDIO_FRAME_SIZE*100;

       uint8_t * inbuf = (uint8_t *)malloc(out_size);

       FILE* pcm,*packetinfo;

       packetinfo = fopen("packetinfo.txt","w");

       pcm = fopen("result.pcm","wb");

       long start = clock();

       while(av_read_frame(pInFmtCtx, &packet)>=0)//pInFmtCtx中调用对应格式的packet获取函数

       {

           if(packet.stream_index==audioStream)//如果是音频

           {

                pktdata = packet.data;

                pktsize = packet.size;

                while(pktsize>0)

                {

                    out_size = AVCODEC_MAX_AUDIO_FRAME_SIZE*100;

                    //解码

        len =avcodec_decode_audio2(pInCodecCtx,(short*)inbuf,&out_size,pktdata,pktsize);

                    if (len<0)

                    {

                        printf("Errorwhile decoding.\n");

                        break;

                    }

                    if(out_size>0)

                    {

                       fwrite(inbuf,1,out_size,pcm);//pcm记录

                        fflush(pcm);

                    }

                    pktsize -= len;

                    pktdata += len;

                }

           }

           av_free_packet(&packet);

       }

       long end = clock();

       printf("cost time:%f\n",double(end-start)/(double)CLOCKS_PER_SEC);

       free(inbuf);

       fclose(pcm);

       fclose(packetinfo);

       if (pInCodecCtx!=NULL)

       {

           avcodec_close(pInCodecCtx);

       }

       if (test!=NULL)

       {

           avcodec_close(test);

       }

       av_free(test);

       av_close_input_file(pInFmtCtx);

       return 0;

}

       其实有上面的解码的流程简介,加上重要的结构体的详细介绍,想要完成解码过程已经很简单了,其他就是一些细节的问题了。

       首先是注册所有容器格式和CODEC:

av_register_all(),初始化 libavformat和注册所有的muxers、demuxers和protocols。是一种比较直接简便的方法,同样也可以对不同的音视频格式、协议等独立注册,这里就不一一列举了。

avformat_alloc_context()和avformat_free_context():

AVFormatContext*avformat_alloc_context(void);

 

分配一个AVFormatContext结构

引入头文件:#include "libavformat/avformat.h"

其中负责申请一个AVFormatContext结构的内存,并进行简单初始化,

avformat_free_context()可以用来释放该结构里的所有东西以及该结构本身,也是就说使用 avformat_alloc_context()分配的结构,需要使用avformat_free_context()来释放,有些版本中函数名可能为:av_alloc_format_context()。

 

av_open_input_file():

attribute_deprecatedint av_open_input_file(AVFormatContext **ic_ptr, const char *filename,AVInputFormat *fmt, int buf_size, AVFormatParameters *ap);

以输入方式打开一个媒体文件,也即源文件,codecs并没有打开,只读取了文件的头信息;AVFormatContext**ic_ptr 输入文件容器;

const char *filename 输入文件名,全路径,并且保证文件存在;

AVInputFormat *fmt 输入文件格式,填NULL即可;

int buf_size,缓冲区大小,直接填0即可;

AVFormatParameters *ap, 格式参数,添NULL即可。

 

av_find_stream_info():

intav_find_stream_info(AVFormatContext *ic);

通过读取媒体文件的中的包来获取媒体文件中的流信息,对于没有头信息的文件如(mpeg)非常有用的,该函数通常重算类似mpeg-2帧模式的真实帧率,该函数并未改变逻辑文件position.引入头文件:#include "libavformat/avformat.h"

也就是把媒体文件中的音视频流等信息读出来,保存在容器中,以便解码时使用

 

 

avcodec_find_decoder()

AVCodec *avcodec_find_decoder(enum CodecIDid);

 通过code ID查找一个已经注册的音视频解码器

引入 #include "libavcodec/avcodec.h"

实现在: \ffmpeg\libavcodec\utils.c;

查找解码器之前,必须先调用av_register_all注册所有支持的解码器;

查找成功返回解码器指针,否则返回NULL;

音视频解码器保存在一个链表中,查找过程中,函数从头到尾遍历链表,通过比较解码器的ID来查找;

 

av_read_frame()

intav_read_frame(AVFormatContext *s, AVPacket *pkt);

// 从输入源文件容器中读取一个AVPacket数据包

// 该函数读出的包并不每次都是有效的,对于读出的包我们都应该进行相应的解码(视频解码/音频解码),

// 在返回值>=0时,循环调用该函数进行读取,循环调用之前请调用av_free_packet函数清理AVPacket

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值