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

github源码地址

前言

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

FFmpeg库简介

FFmpeg库简介

FFmpeg解码流程图

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解码的数据结构

FFmpeg解码的数据结构
这是雷哥对上图的简介

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

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

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

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

FFmpeg数据结构的内容

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; i<pFormatCtx->nb_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);
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,458评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,454评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,171评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,062评论 0 207
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,440评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,661评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,906评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,609评论 0 200
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,379评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,600评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,085评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,409评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,072评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,088评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,860评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,704评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,608评论 2 270

推荐阅读更多精彩内容