使用FFMPEG进行解码的音视频播放器-完整实现

一、FFMPEG结构

    1.AVFormatContext

  存储音视频封装格式中包含的信息的结构体,也是FFmpeg中统领全局的结构体,对文件的封装、编码操作从这里开始。 FFmpeg 解封装(flv,mp4,rmvb,avi)功能的结构体。封装格式上下文结构体,保存了视频文件封装格式相关信息。

  • 1.iformat:输入视频的AVInputFormat
  • 2.nb_streams:输入视频的AVStream个数
  • 3.streams:输入视频的AVStream[]数组
  • 4.duration:输入视频的时长
  • 5.bit_rate:输入视频的码率

    2.AVCodecContext

    当前解码器上下文结构。

        AVCodecContext *pCodecCtx;           //当前解码器上下文结构

        AVCodec *pCodec; //解码器

        AVFrame *pFrame,*pFrameYUV; //解码输入的图像结构,存储一帧解码后像素数据。视 频对应RGB/YUV像素数据,音频对应PCM采样数据

        unsigned char *out_buffer; //输出缓存区

        AVPacket *packet; //输出的码流包,存储一帧压缩编码数据包。视频对应 H.264等码流数据,音频对应AAC/MP3等码流数据

        AVIOContext: 输入输出对应的结构体,用于输入输出(读写文件,RTMP协议等)。

        AVStream,AVCodecContext: 视音频流对应的结构体,用于视音频编解码。

        3. SwsContext  

        它是设计回调函数从输入文件中读取数据。与FFMPEG 官方给出的avio_reading.c不同的是,雷神给的例子是当需要数据的时候,回调函数才去从输入文件读取数据,而avio_reading.c 则是直接全部数据读取到内存中待后面处理。

        在我的这个实例中,我是将读取到的输入文件解码成YUV420P数据格式。同时可以通过设置不同的数据格式和尺寸实现输出图像的拉伸缩小或是数据格式的装换。

        处理图片像素数据, 可以完成图片像素格式的转换, 图片的拉伸等工作。

        

二、解封装

        1.初始化

        av_register_all();

        完成编码器和解码器的初始化,只有初始化了编码器和解码器才能正常使用,否则会在打开编解码器的时候失败。

        初始化封装库

        在新版本中av_register_all()被弃用了,可以根据代码里有无此函数判断ffmpeg版本

        avformat_network_init();

       执行网络库的全局初始化。这是可选的,不再推荐。此函数仅用于解决旧GnuTLS或OpenSSL库的线程安全问题。如果libavformat链接到这些库的较新版本,或者不使用它们,则不需要调用此函数。否则,您需要在启动使用该函数的任何其他线程之前调用该函数。一旦删除对旧GnuTLS和OpenSSL库的支持,此函数将被弃用,并且此函数没有任何用途。注册网络协议

        初始化网络库(可以打开rtsp,rtmp,http协议的流媒体视频)

        2.分配一个AVFormatContext

        avformat_alloc_context()

        主要完成AVFormatContext的空间分配,注意分配在堆上;给AVFormatContext的成员赋默认值;完成AVFormatContext内部使用对象AVFormatInternal结构体的空间分配及其部分成员字段的赋值。

        解封装上下文AVFormatContext,是存储音视频封装格式中包含信息的结构体

av_malloc

        是libavutil文件中的函数,该函数作用在于给对象分配内存块,并且是内存对齐的。

        3.打开视频文件

        avformat_open_input(ps,url,fmt,options)

        功能:打开音视频资源(不翻译成流是为了与AVStream概念区分),并读取文件头,获取必要的信息,并填充AVFormatContext结构体。注意解码器并未打开,并未对音视频包进行解码。打开输入视频文件

        参数ps:该参数可空,为空时,本函数内部将创建AVFormatContext。在需要使用自定义的I/O层回调函数方法时,那就必须要在该函数外部创建AVFormatContext,并创建AVIOContext并赋值给AVFormatContext.pb成员。确保后续操作有一个非空的并且是合法创建的AVFormatContext对象可以使用

        参数url:音视频资源的url,文件路径信息

        参数fmt:如果不为空,则表示强制指定了输入文件的文件格式,不再进行文件格式的探测,如果为空,则本函数内部将对输入文件参数进行自动的探测。ffmpeg工具命令行中的-f选项用以指定该输入格式。判断是否强制指定了输入文件格式,若指定了,那么将该文件格式挂载于AVFormatContext.iformat成员上,后续将不再自动探测音视频文件格式。

     参数options:该选项信息将被应用到AVFormatContext的成员字段,以及解封装器(AVInputFormat)的私有字段上去。先将选项信息拷贝到内部临时变量,然后调用av_opt_set_dict()方法来应用选项信息到AVFormatContext。

        avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options)

        1)该函数将读取媒体文件的音视频包去获取流信息。本函数常用于avformat_open_input()函数之后,在avformat_open_input()函数中会调用输入文件格式的read_header()函数,比如flv格式的flv_read_header()函数来读取文件头,由于flv格式的头很简单,只能知道是否存在音频流和视频流,获取不到流的编码信息,因此,对于flv格式来说,本函数就非常重要,本函数会读取flv文件中的音视频包,从这些包中获知流的编解码信息。对于没有文件头的MPEG格式来说存在同样的情况。获取视频信息

        2)本函数还会在MPEG的重复帧模式下计算真实的帧率

       3)本函数不会改变文件的逻辑位置(即程序访问文件时的文件偏移offset),那些读取并用来做检测的数据包将被缓冲,并留作后续处理使用。

       4)  本函数的入参AVDictionary **options如果不为空,那么该入参是一个AVDictionary列表,第几个AVDictionary就作用于AVFormatContext.nb_streams的第几个流,如果在对应的流中找不到相应的选项,函数返回时,那么该入参中还会保留着没有找到的选项

      5)本函数不会保证打开所有的编解码器,因此,在函数返回时,选项非空是正常行为。

     6)本函数通过options来让用户决定哪些信息是需要的,因此,不浪费时间在获取一些用户根本需要的信息。

     4.查找文件中的视频流

      for(i=0; i<pFormatCtx->nb_streams; i++)

       循环查找视频中包含的流信息,直到找到视频类型的流,便将其记录下来保存到videoStream变量中。  对视频中所有的流进行遍历

     5.解码器解码

       avcodec_find_decoder()

      找合适的视频解码器,得到流对应的解码器编号。视频文件中有视频流与音频流,流都存储在streams[]数组里,因此需要进行区分,得到具体的流在数组里的下标值(videoindex:视频流索引)。解码器型号存储于该视频流的streams[videoindex]->codec->codec_id里。

                                                查找解码器

      avcodec_open2()

      打开解码器上下文。         打开解码器

    6.读取视频

      AVFrame结构体描述了解码后的(原始)音频或视频数据。AVFrame通常被分配一次,然后多次重复使用以持有不同的数据。

        它比较重要的字段有:

*data[AV_NUM_DATA_POINTERS]:存放解码后的原始媒体数据的指针数组。

对于视频数据而言,planar(YUV420)格式的数据

对于音频数据而言,data数组中,存放的是channel的数据,

key_frame:当前帧是否为关键帧,1表示是,0表示不是。

pts:以time_base为单位的呈现时间戳(应向用户显示帧的时间)。

        av_frame_alloc();

        分配AVFrame,并将其字段设置为默认值。主要该函数只分配AVFrame的空间,它的data字段的指定的buffer需要其它函数分配。   

                           申请AVFrame,用于原视频和yuv视频

        av_image_get_buffer_size(enum AVPixelFormat pix_fmt, int width, int height, int align)

        通过指定像素格式、图像宽、图像高来计算所需的内存大小。

     参数align:此参数是设定内存对齐的对齐数,也就是按多大的字节进行内存对齐。                           获取图片所需内存,用于图像格式转换

        av_malloc()

        按计算的内存大小申请所需内存。分配具有大小w和h以及像素格式pix_fmt的图像,并相应地填充指针和线条大小。               分配所需内存

        av_image_fill_arrays()

        对申请的内存进行格式化。根据指定的图像参数和提供的数组设置数据指针和行大小。   

                                                瓜分分配到的内存

         av_malloc(sizeof(AVPacket)                         

        分配一个packet。                                  分配码流包空间

        av_dump_format()

        将 AVFormatContext 中的媒体信息转存到输出。 输出tbn、tbc、tbr、PAR、DAR的含义。     

         sws_getContext()

         初始化一个SwsContext。                           申请转换上下文

      7.SDL播放视频

        (1)初始化

        SDL_Init()

        SDL的初始化函数SDL_Init()。该函数可以确定希望激活的子系统(音频、视频、定时器)。                                  初始化

        SDL_Window* SDL_CreateWindow(const char* title,

                             int         x,

                             int         y,

                             int         w,

                             int         h,

                             Uint32      flags)

        用此函数在指定的位置,指定窗口大小,以及相应标志来创建窗口。

                                创建窗口

        SDL_Renderer* SDL_CreateRenderer(SDL_Window* window, int index, Uint32 flags)

       为指定窗口创建渲染器上下文。SDL_Renderer是一个结构体,用来表示SDL2中渲染器的状态。SDL_Renderer实例由SDL_CreateRenderer函数创建。

                                创建渲染器

        SDL_Texture* SDL_CreateTexture(SDL_Renderer* renderer,

                               Uint32        format,

                               int           access,

                               int           w,

                               int           h)

        使用此函数可为渲染器上下文创建纹理。对于SDL_Texture本身来说,它包含了显示驱动用于显示的特定数据结构。SDL_Texture的创建一半通过SDL_CreateTexture创建,创建一次后,可以重复使用,通过SDL_UpdateTexture函数更新数据。 创建纹理

     (2)循环渲染数据

          int SDLCALL SDL_UpdateTexture(SDL_Texture * texture,

                          const SDL_Rect * rect,

                          const void *pixels, int pitch);

         设置纹理的像素数据。

         SDL_RenderCopy()  

         纹理复制给渲染器。

         SDL_RenderPresent()

         显示窗口。

     8.视频读取

      av_read_frame(AVFormatContext *s, AVPacket *pkt);

读取码流中的音频若干帧或者视频一帧。读取音频流、视频流、字幕流,得到AVPacket数据包。该方法返回文件存储的帧数据,但不会为解码器校验帧是否有效。把文件拆分为若干个帧,读取输入文件的一帧压缩数据,每次调用返回一帧数据包(AVpacket)。                                 

                        读取码流返回数据包

三、解码

        1.解码

        avcodec_decode_video2(AVCodecContext *avctx, AVFrame *picture,

                         int *got_picture_ptr,

                         const AVPacket *avpkt);

        解码一帧视频压缩数据。输入一个压缩编码的结构体AVPacket,输出一个解码后的结构体AVFrame。 解码AVPacket返回AVFrame

        2.处理图像数据

        int sws_scale(struct SwsContext *c, const uint8_t *const srcSlice[],  

              const int srcStride[], int srcSliceY, int srcSliceH,  

              uint8_t *const dst[], const int dstStride[]);

        基本上所有解码器解码之后得到的图像数据都是YUV420的格式,而这里我们需要将其保存成图片文件,因此需要将得到的YUV420数据转换成RGB格式,转换格式也是直接使用FFMPEG来完成。  像素格式转换

        3.其他帧处理

        未解码到一帧,可能是结尾B帧或延迟帧,做flush decoder处理。

        4.释放资源

 四、核心解码部分

循环解码解读:

  1. av_read_frame读取输入文件中(音频若干帧或者视频一帧)的压缩数据,返回AVPacket;
  2. 得到packet后;
  3. avcodec_decode_video2对AVPacket中的(音频若干帧或者视频一帧)的压缩数据,解码输出一个结构体AVFrame;
  4. 得到picture进行处理(像素格式大小)渲染显示;
  5. av_free_packet释放packet,av_read_frame重新读取,存入,再循环处理
  6. 未解码到一帧,可能是结尾B帧或延迟帧,做flush decoder处理

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值