跨平台ffmpeg播放器开发(三)对FFmpeg解码的函数以及解码的数据结构进行分析(附iOS平台解码代码)

github源码地址

前言

在上一节我们已经配置好了Xcode环境,所以这一节我们将会对其做更深入的了解,下面的文章是基于雷博的文章写的,使用Xcode进行编写.

FFmpeg库简介

跨平台ffmpeg播放器开发(三)对FFmpeg解码的函数以及解码的数据结构进行分析(附iOS平台解码代码)_第1张图片
FFmpeg库简介

FFmpeg解码流程图

跨平台ffmpeg播放器开发(三)对FFmpeg解码的函数以及解码的数据结构进行分析(附iOS平台解码代码)_第2张图片
FFmpeg函数解码的流程图
  • FFmpeg所有的初始化都要用到"av_register_all()"这个函数来注册所有的组件

  • 接下来"avformat_open_input()"是打开视频流

  • "avformat_find_stream_info()"打开视频文件,查看视频流信息(例如这个视频是多宽多高,解码器类型)

  • "avcodec_find_decoder()"找出视频的解码器

  • "avcodec_open2()"将它打开

接下来进入一个循环

  • "av_read_frame()"调用时它会读取一帧的压缩数据(h.264码流)

  • 读取完后它会执行"Get Packet",若为true则说明读取到了数据,则进行下一步,若没读取到则说明视频流已经读取完毕就退出了

  • "AVPacket"是一个结构体,里面装的是h.264

  • "avcodec_decode_video2()"这是解码中最重要的函数,他负责将AVPacket->AVFream

  • "AVFream"里面装的是yuv数据

  • "show on screen"这一步我们后边会使用SDL将它展示在我们的屏幕上,然后再重新读取数据,进入循环

这就是FFmpeg解码的一个基本流程

跨平台ffmpeg播放器开发(三)对FFmpeg解码的函数以及解码的数据结构进行分析(附iOS平台解码代码)_第3张图片
雷哥对FFmpeg解码函数的简介

FFmpeg解码的数据结构

跨平台ffmpeg播放器开发(三)对FFmpeg解码的函数以及解码的数据结构进行分析(附iOS平台解码代码)_第4张图片
FFmpeg解码的数据结构
跨平台ffmpeg播放器开发(三)对FFmpeg解码的函数以及解码的数据结构进行分析(附iOS平台解码代码)_第5张图片
这是雷哥对上图的简介

下面我对上图介绍不详细的地方进行一些补充

  • AVStream是一个数组,数量是不确定的,一般第0个都是视频流,第1个都是音频流

  • AVCodec指明了编码器是什么类型的

  • AVCodecContext被更新了,现在使用AVCodecParameters,但是很多api并没有更新所以我们依旧使用AVCodecContext

FFmpeg数据结构的内容

跨平台ffmpeg播放器开发(三)对FFmpeg解码的函数以及解码的数据结构进行分析(附iOS平台解码代码)_第6张图片
跨平台ffmpeg播放器开发(三)对FFmpeg解码的函数以及解码的数据结构进行分析(附iOS平台解码代码)_第7张图片
跨平台ffmpeg播放器开发(三)对FFmpeg解码的函数以及解码的数据结构进行分析(附iOS平台解码代码)_第8张图片

iOS平台下写的一个最简单的解码部分

是照着雷哥的C++代码仿的,AVCodecContext这个结构体过时了,但因为API没更新所以我继续使用了,原文没有备注,这里的备注都是我添加的,如有错误欢迎指出,谢谢.github源码地址,后面的代码更新都会在这个仓储.

-(void)decode{
        
        //初始化所有组件
        av_register_all();
        
        //声明上下文
        AVFormatContext *pFormatCtx;
        
        //初始化上下文
        pFormatCtx = avformat_alloc_context();
        
        //获取文件路径
        NSString * filePath = [[NSBundle mainBundle]pathForResource:@"ds.mov" ofType:nil];
        
        const char * path = [filePath UTF8String];
        
        //打开视频流
        if(avformat_open_input(&pFormatCtx,path,NULL,NULL)!=0){
            
            NSLog(@"不能打开流");
            
            return ;
        }
        
        //查看视频流信息
        if(avformat_find_stream_info(pFormatCtx,NULL)<0){
            
            NSLog(@"不能成功查看视频流信息");
            
            return ;
        }
        
        int i,videoIndex;
        
        videoIndex = -1;
        
        //对上下文中的视频流进行遍历
        for (i = 0; inb_streams; i++) {
            
            //找到视频流信息后跳出循环
            if(pFormatCtx->streams[i]->codecpar->codec_type==AVMEDIA_TYPE_VIDEO){
                
                videoIndex=i;
                
                break;
                
            }
        }
        
        //若videoIndex还为初值那么说明没有找到视频流
        if(videoIndex==-1){
            
            NSLog(@"没有找到视频流");
            
            return ;
        }
        
        //声明编码器上下文结构体
        //这里新版本不再使用AVCodecContext这个结构体了,具体原因我也不太清楚,好像是AVCodecContext过于臃肿
        AVCodecContext * pCodecCtx;
        
        pCodecCtx = pFormatCtx->streams[videoIndex]->codec;
        
        //声明解码器类型
        AVCodec *pCodec;
        
        //查找解码器
        pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
        
        if (pCodec == NULL) {
            
            NSLog(@"解码器没找到");
            
            return;
        }
        
        
        //打开解码器
        if(avcodec_open2(pCodecCtx, pCodec,NULL)<0){
            NSLog(@"解码器打开失败");
            return;
        }
        
        //解码后的数据
        AVFrame *pFream,*pFreamYUV;
        
        pFream = av_frame_alloc();
        
        pFreamYUV = av_frame_alloc();
        
        uint8_t *out_buffer;
        
        //分配内存
        //根据像素格式,宽高分配
        out_buffer = (uint8_t *)av_malloc(avpicture_get_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height));
        
        //用ptr中的内容根据文件格式(YUV…) 和分辨率填充picture。这里由于是在初始化阶段,所以填充的可能全是零。
        avpicture_fill((AVPicture*)pFreamYUV, out_buffer, AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height);
        
        //解码前的数据
        AVPacket *packet;
        
        //开辟空间
        packet =(AVPacket *)av_malloc(sizeof(AVPacket));
        
        /*******************************输出信息*********************************************/
        
        NSLog(@"--------------- File Information ----------------");
        
        //打印视频信息,av_dump_format()是一个手工调试的函数,能使我们看到pFormatCtx->streams里面有什么内容
        av_dump_format(pFormatCtx, 0, path, 0);
        
        NSLog(@"-------------------------------------------------");
        
        //主要用来对图像进行变化,这里是为了缩放,把黑边裁去
        struct SwsContext * img_convert_ctx;
        
        
        /**
         该函数包含以下参数:
         srcW:源图像的宽
         srcH:源图像的高
         srcFormat:源图像的像素格式
         dstW:目标图像的宽
         dstH:目标图像的高
         dstFormat:目标图像的像素格式
         flags:设定图像拉伸使用的算法
         成功执行的话返回生成的SwsContext,否则返回NULL。
         */
        img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt,
                                         pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);
        
        //解码序号
        int frame_cnt = 0;
        
        int got_picture_ptr = 0;
        
        //循环读取每一帧
        while (av_read_frame(pFormatCtx, packet)>=0) {
            
            //若为视频流信息
            if (packet->stream_index == videoIndex) {
                
                //解码一帧数据,输入一个压缩编码的结构体AVPacket,输出一个解码后的结构体AVFrame。
                int ret = avcodec_decode_video2(pCodecCtx, pFream, &got_picture_ptr, packet);
                
                //当解码失败
                if (ret < 0) {
                    
                    NSLog(@"解码失败");
                    
                    return;
                }
                
                if (got_picture_ptr) {
                    //处理图像数据,用于转换像素
                    //使用这个api yuv->rgb非常耗性能,一般交给opengl es来使用硬解码
                    //data解码后的图像像素数据
                    //linesize对视频来说是一行像素的大小
                    sws_scale(img_convert_ctx, (const uint8_t * const *)pFream->data, pFream->linesize, 0, pCodecCtx->height, pFreamYUV->data, pFreamYUV->linesize);
                    
                    NSLog(@"解码序号%d",frame_cnt);
                    
                    frame_cnt ++;
                }
            }
            
            //销毁packet
            av_free_packet(packet);
        }
        
        //销毁
        sws_freeContext(img_convert_ctx);
        
        av_frame_free(&pFreamYUV);
        
        av_frame_free(&pFream);
        
        avcodec_close(pCodecCtx);
        
        avformat_close_input(&pFormatCtx);
    }

你可能感兴趣的:(跨平台ffmpeg播放器开发(三)对FFmpeg解码的函数以及解码的数据结构进行分析(附iOS平台解码代码))