音视频编解码:视频编解码基础2-FFmpeg解码实践

音视频编解码:视频编解码基础1-FFmpeg结构与API摘要
音视频编解码:视频编解码基础2-FFmpeg解码实践
音视频编解码:视频编解码基础篇3-FFmpeg转码实践
音视频编解码:视频编解码基础篇4-FFmpeg解码播放

一.视频解码

本地视频和网路视频解码,由于本地视频解码某种意义上相当于网路视频解码的子集这里所以直接上网路解码.

解码初始化

avformat_open_input(AVFormatContext **ps, const char *filename,
AVInputFormat *fmt, AVDictionary **options)这里的options也就是代码中的参数四,可以根据具体情况进行具体分析配置
1.画质优化
原生的ffmpeg参数在对1920x1080的RTSP流进行播放时,花屏现象很严重,根据网上查的资料,可以通过增大“buffer_size”参数来提高画质,减少花屏现象
如:av_dict_set(&options, "buffer_size", "1080000", 0);
2.RTSP连接不上导致卡死的问题
原生的ffmpeg参数在打开RTSP流时,若连接不上,会出现卡死在打开函数的情况,在有些情况下这是很不好的,可以通过设置超时来改变卡死的情况
如设置20s超时:av_dict_set(&options, "stimeout", "20000000", 0); //设置超时断开连接时间
3.其他
可以设置的参数还有很多,如可以设置连接为TCP,设置最大延时等等
av_dict_set(&options, "max_delay", "300000", 0);
av_dict_set(&options, "rtsp_transport", "tcp", 0); //以udp方式打开,如果以tcp方式打开将udp替换为tcp

//第一步:组册组件
    av_register_all();
    avformat_network_init();
//第二步:打开封装格式->打开文件
    //参数一:封装格式上下文
    //作用:保存整个视频信息(解码器、编码器等等...)
    //信息:码率、帧率等...
    AVFormatContext* avformat_context = avformat_alloc_context();
    //参数二:视频路径
    const char *url = [urlString cStringUsingEncoding:NSUTF8StringEncoding];
    //参数三:指定输入的格式
    //参数四:设置默认参数 AVDictionary* options = NULL;
    int avformat_open_input_result = avformat_open_input(&avformat_context, url, NULL, NULL);
if (avformat_open_input_result != 0){
        NSLog("打开文件失败");
 if (formatCtx){
                释放上下文
                avformat_free_context(avformat_context);
            }
        return;
    }
//第三步:查找视频流->拿到视频信息
    //参数一:封装格式上下文
    //参数二:指定默认配置
    int avformat_find_stream_info_result = avformat_find_stream_info(avformat_context, NULL);
    if (avformat_find_stream_info_result < 0){
        NSLog(@"查找失败");
        NSLog(@"avformat_find_stream_info error, %s", av_err2str(avformat_find_stream_info_result));
        return;
    }
 //打印流信息
    /*Input #0, rtsp, from 'rtsp://184.72.239.149/vod/mp4://BigBuckBunny_175k.mov':
     Metadata:
     title           : BigBuckBunny_175k.mov
     Duration: 00:09:56.46, start: 0.000000, bitrate: N/A
     Stream #0:0: Audio: aac (LC), 48000 Hz, stereo, fltp
     Stream #0:1: Video: h264 (Constrained Baseline), yuv420p(progressive), 240x160, 24 fps, 24 tbr, 90k tbn, 48 tbc*/
    av_dump_format(avformat_context, 0, url, false);
//第四步:查找视频解码器
    //1、查找视频流索引位置
    int av_stream_index = -1;
    for (int i = 0; i < avformat_context->nb_streams; ++i) {
        //判断流类型:视频流、音频流、字母流等等...
        if (avformat_context->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO){
            av_stream_index = i;
            break;
        }
    }
    
    //2、根据视频流索引,获取解码器上下文
    AVCodecContext *avcodec_context = avformat_context->streams[av_stream_index]->codec;
    
    //3、根据解码器上下文,获得解码器ID,然后查找解码器
    AVCodec *avcodec = avcodec_find_decoder(avcodec_context->codec_id);
    
    //第五步:打开解码器
    int avcodec_open2_result = avcodec_open2(avcodec_context, avcodec, NULL);
    if (avcodec_open2_result != 0){
        NSLog(@"打开解码器失败");
        return;
    }
    
    //测试一下
    //打印信息
    NSLog(@"解码器名称:%s", avcodec->name);
    
    //第六步:读取视频压缩数据->循环读取
    //1、分析av_read_frame参数
    //参数一:封装格式上下文
    //参数二:一帧压缩数据 = 一张图片
    //av_read_frame()
    //结构体大小计算:字节对齐原则
    AVPacket* packet = (AVPacket*)av_malloc(sizeof(AVPacket));
    
    //3.2 解码一帧视频压缩数据->进行解码(作用:用于解码操作)
    //开辟一块内存空间
    AVFrame* avframe_in = av_frame_alloc();
    int decode_result = 0;

    //4、注意:在这里我们不能够保证解码出来的一帧视频像素数据格式是yuv格式
    //参数一:源文件->原始视频像素数据格式宽
    //参数二:源文件->原始视频像素数据格式高
    //参数三:源文件->原始视频像素数据格式类型
    //参数四:目标文件->目标视频像素数据格式宽
    //参数五:目标文件->目标视频像素数据格式高
    //参数六:目标文件->目标视频像素数据格式类型
    struct SwsContext *swscontext = sws_getContext(avcodec_context->width,
                                            avcodec_context->height,
                                            avcodec_context->pix_fmt,
                                            avcodec_context->width,
                                            avcodec_context->height,
                                            AV_PIX_FMT_YUV420P,
                                            SWS_BICUBIC,
                                            NULL,
                                            NULL,
                                            NULL);
    
    //创建一个yuv420视频像素数据格式缓冲区(一帧数据)
    AVFrame* avframe_yuv420p = av_frame_alloc();
    //给缓冲区设置类型->yuv420类型
    //得到YUV420P缓冲区大小
    //参数一:视频像素数据格式类型->YUV420P格式
    //参数二:一帧视频像素数据宽 = 视频宽
    //参数三:一帧视频像素数据高 = 视频高
    //参数四:字节对齐方式->默认是1
    int buffer_size = av_image_get_buffer_size(AV_PIX_FMT_YUV420P,
                                               avcodec_context->width,
                                               avcodec_context->height,
                                               1);
    
    //开辟一块内存空间
    uint8_t *out_buffer = (uint8_t *)av_malloc(buffer_size);
    //向avframe_yuv420p->填充数据
    //参数一:目标->填充数据(avframe_yuv420p)
    //参数二:目标->每一行大小
    //参数三:原始数据
    //参数四:目标->格式类型
    //参数五:宽
    //参数六:高
    //参数七:字节对齐方式
    av_image_fill_arrays(avframe_yuv420p->data,
                         avframe_yuv420p->linesize,
                         out_buffer,
                         AV_PIX_FMT_YUV420P,
                         avcodec_context->width,
                         avcodec_context->height,
                         1);
    
    int y_size, u_size, v_size;
    
    
    //5.2 将yuv420p数据写入.yuv文件中
    //打开写入文件
    const char *outfile = [outFilePath UTF8String];
    FILE* file_yuv420p = fopen(outfile, "wb+");
    if (file_yuv420p == NULL){
        NSLog(@"输出文件打开失败");
        return;
    }
    
    int currentIndex = 0;
    
    while (av_read_frame(avformat_context, packet) >= 0){
        // >=:读取到了
        // <0:读取错误或者读取完毕
        // 2、是否是我们的视频流
        if (packet->stream_index == av_stream_index){
            //第七步:解码
            //学习一下C基础,结构体
            //3、解码一帧压缩数据->得到视频像素数据->yuv格式
            //采用新的API
            //3.1 发送一帧视频压缩数据
            avcodec_send_packet(avcodec_context, packet);
            //3.2 解码一帧视频压缩数据->进行解码(作用:用于解码操作)
            decode_result = avcodec_receive_frame(avcodec_context, avframe_in);
            if (decode_result == 0){
                //解码成功
                //4、注意:在这里我们不能够保证解码出来的一帧视频像素数据格式是yuv格式
                //视频像素数据格式很多种类型: yuv420P、yuv422p、yuv444p等等...
                //保证:我的解码后的视频像素数据格式统一为yuv420P->通用的格式
                //进行类型转换: 将解码出来的视频像素点数据格式->统一转类型为yuv420P
                //sws_scale作用:进行类型转换的yuv420P,<~>RGB
                //参数一:视频像素数据格式上下文
                //参数二:原来的视频像素数据格式->输入数据
                //参数三:原来的视频像素数据格式->输入画面每一行大小
                //参数四:原来的视频像素数据格式->输入画面每一行开始位置(填写:0->表示从原点开始读取)
                //参数五:原来的视频像素数据格式->输入数据行数
                //参数六:转换类型后视频像素数据格式->输出数据
                //参数七:转换类型后视频像素数据格式->输出画面每一行大小
                sws_scale(swscontext,
                          (const uint8_t *const *)avframe_in->data,
                          avframe_in->linesize,
                          0,
                          avcodec_context->height,
                          avframe_yuv420p->data,
                          avframe_yuv420p->linesize);
                
                //1:直接显示视频上面去
                //2:写入yuv文件格式
                //5、将yuv420p数据写入.yuv文件中
                //5.1 计算YUV大小
                //分析一下原理?
                //Y表示:亮度
                //UV表示:色度
                //YUV420P格式:Y结构表示一个像素(一个像素对应一个Y)
                  4个像素点对应一个(U和V: 4Y = U = V)
                y_size = avcodec_context->width * avcodec_context->height;
                u_size = y_size / 4;
                v_size = y_size / 4;
                //5.2 写入.yuv文件
                //Y数据
                fwrite(avframe_yuv420p->data[0], 1, y_size, file_yuv420p);
                //U数据
                fwrite(avframe_yuv420p->data[1], 1, u_size, file_yuv420p);
                //V数据
                fwrite(avframe_yuv420p->data[2], 1, v_size, file_yuv420p);
                
                currentIndex++;
                NSLog(@"解码第%d帧", currentIndex);
            }
        }
    }
    
    释放内存资源,关闭解码器
    av_packet_free(&packet);
    fclose(file_yuv420p);
    av_frame_free(&avframe_in);
    av_frame_free(&avframe_yuv420p);
    free(out_buffer);
    avcodec_close(avcodec_context);
    avformat_free_context(avformat_context);

二.小结以上只是解码过程的基本流程和配置,后续还会对细节做处理,增加播放和音视频同步等等操作.

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