美文网首页FFmpeg
第4讲-FFmepg-视频解码

第4讲-FFmepg-视频解码

作者: 泰克2008 | 来源:发表于2017-11-21 10:02 被阅读17次

    内容一:FFmpeg-命令行补充?

    案例:视频转为高质量 GIF 动图?
    
    命令:./ffmpeg -ss 00:00:03 -t 3 -i Test.mov -s 640x360 -r  15 dongtu.gif
    
    解释:
        1、ffmpeg 是你刚才安装的程序;
    
        2、-ss 00:00:03 表示从第 00 分钟 03 秒开始制作 GIF,如果你想从第 9 秒开始,则输入 -ss 00:00:09,或者 -ss 9,支持小数点,所以也可以输入 -ss 00:00:11.3,或者 -ss 34.6 之类的,如果不加该命令,则从 0 秒开始制作;
    
        3、-t 3 表示把持续 3 秒的视频转换为 GIF,你可以把它改为其他数字,例如 1.5,7 等等,时间越长,GIF 体积越大,如果不加该命令,则把整个视频转为 GIF;
    
        4、-i 表示 invert 的意思,转换;
    
        5、Test.mov 就是你要转换的视频,名称最好不要有中文,不要留空格,支持多种视频格式;
    
        6、-s 640x360 是 GIF 的分辨率,视频分辨率可能是 1080p,但你制作的 GIF 可以转为 720p 等,允许自定义,分辨率越高体积越大,如果不加该命令,则保持分辨率不变;
    
        7、-r  15 表示帧率,网上下载的视频帧率通常为 24,设为 15 效果挺好了,帧率越高体积越大,如果不加该命令,则保持帧率不变;
    
        8、dongtu.gif:就是你要输出的文件,你也可以把它命名为 hello.gif 等等。
    

    内容二:FFmpeg-视频解码?

    >内容:将音视频解码在Android平台进行实现
    >作业:然后移植到iOS平台(提示:将iOS环境搭建好,然后童鞋们填写代码)
        功能实现是一模一样,没有区别?
        下一节课讲解?写好了直接发到我的邮箱?
    

    第一点:确定音视频编解码流程学习?

        第一步:组册组件
            av_register_all()
            例如:编码器、解码器等等…
    
        第二步:打开封装格式->打开文件
            例如:.mp4、.mov、.wmv文件等等...
            avformat_open_input();
    
        第三步:查找视频流
            如果是视频解码,那么查找视频流,如果是音频解码,那么就查找音频流
            avformat_find_stream_info();
    
        第四步:查找视频解码器
            1、查找视频流索引位置
            2、根据视频流索引,获取解码器上下文
            3、根据解码器上下文,获得解码器ID,然后查找解码器
    
        第五步:打开解码器
            avcodec_open2();
    
        第六步:读取视频压缩数据->循环读取
            每读取一帧数据,立马解码一帧数据
    
        第七步:视频解码->播放视频->得到视频像素数据
    
        第八步:关闭解码器->解码完成
    

    第二点:实现功能->写代码

    #include <jni.h>
    #include <string>
    #include <android/log.h>
    
    //特殊处理:在安卓底层开发中,需要注意的
    //因为要支持C/C++混合编程
    extern "C" {
    //核心库
    #include "libavcodec/avcodec.h"
    //封装格式处理库
    #include "libavformat/avformat.h"
    //工具库
    #include "libavutil/imgutils.h"
    //视频像素数据格式库
    #include "libswscale/swscale.h"
    
    //视频解码
    JNIEXPORT void JNICALL Java_com_tz_dream_ffmpeg_decodevideo_MainActivity_ffmepgDecodeVideo(JNIEnv *env, jobject jobj,
                                                                             jstring jinFilePath,
                                                                             jstring joutFilePath);
    
    }
    
    
    JNIEXPORT void JNICALL Java_com_tz_dream_ffmpeg_decodevideo_MainActivity_ffmepgDecodeVideo(
            JNIEnv *env,
            jobject jobj, jstring jinFilePath, jstring joutFilePath) {
    
        //第一步:组册组件
        av_register_all();
    
        //第二步:打开封装格式->打开文件
        //参数一:封装格式上下文
        //作用:保存整个视频信息(解码器、编码器等等...)
        //信息:码率、帧率等...
        AVFormatContext* avformat_context = avformat_alloc_context();
        //参数二:视频路径
        const char *url = env->GetStringUTFChars(jinFilePath, NULL);
        //在我们iOS里面
        //NSString* path = @"/user/dream/test.mov";
        //const char *url = [path UTF8String]
        //参数三:指定输入的格式
        //参数四:设置默认参数
        int avformat_open_input_result = avformat_open_input(&avformat_context, url, NULL, NULL);
        if (avformat_open_input_result != 0){
            //安卓平台下log
            __android_log_print(ANDROID_LOG_INFO, "main", "打开文件失败");
            //iOS平台下log
            //NSLog("打开文件失败");
            //不同的平台替换不同平台log日志
            return;
        }
    
        //第三步:查找视频流->拿到视频信息
        //参数一:封装格式上下文
        //参数二:指定默认配置
        int avformat_find_stream_info_result = avformat_find_stream_info(avformat_context, NULL);
        if (avformat_find_stream_info_result < 0){
            __android_log_print(ANDROID_LOG_INFO, "main", "查找失败");
            return;
        }
    
        //第四步:查找视频解码器
        //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){
            __android_log_print(ANDROID_LOG_INFO, "main", "打开解码器失败");
            return;
        }
    
        //测试一下
        //打印信息
        __android_log_print(ANDROID_LOG_INFO, "main", "解码器名称:%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格式
        //参数一:源文件->原始视频像素数据格式宽
        //参数二:源文件->原始视频像素数据格式高
        //参数三:源文件->原始视频像素数据格式类型
        //参数四:目标文件->目标视频像素数据格式宽
        //参数五:目标文件->目标视频像素数据格式高
        //参数六:目标文件->目标视频像素数据格式类型
        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 = env->GetStringUTFChars(joutFilePath, NULL);
        FILE* file_yuv420p = fopen(outfile, "wb+");
        if (file_yuv420p == NULL){
            __android_log_print(ANDROID_LOG_INFO, "main", "输出文件打开失败");
            return;
        }
    
        int current_index = 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作用:进行类型转换的
                    //参数一:视频像素数据格式上下文
                    //参数二:原来的视频像素数据格式->输入数据
                    //参数三:原来的视频像素数据格式->输入画面每一行大小
                    //参数四:原来的视频像素数据格式->输入画面每一行开始位置(填写:0->表示从原点开始读取)
                    //参数五:原来的视频像素数据格式->输入数据行数
                    //参数六:转换类型后视频像素数据格式->输出数据
                    //参数七:转换类型后视频像素数据格式->输出画面每一行大小
                    sws_scale(swscontext,
                              (const uint8_t *const *)avframe_in->data,
                              avframe_in->linesize,
                              0,
                              avcodec_context->height,
                              avframe_yuv420p->data,
                              avframe_yuv420p->linesize);
    
                    //方式一:直接显示视频上面去
                    //方式二:写入yuv文件格式
                    //5、将yuv420p数据写入.yuv文件中
                    //5.1 计算YUV大小
                    //分析一下原理?
                    //Y表示:亮度
                    //UV表示:色度
                    //有规律
                    //YUV420P格式规范一:Y结构表示一个像素(一个像素对应一个Y)
                    //YUV420P格式规范二: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);
    
                    current_index++;
                    __android_log_print(ANDROID_LOG_INFO, "main", "当前解码第%d帧", current_index);
                }
    
            }
        }
    
        //第八步:释放内存资源,关闭解码器
        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);
    }
    

    实例工程

    iOS工程 https://pan.baidu.com/s/1dFlhDdB
    Android工程 https://pan.baidu.com/s/1jHAzHzw

    相关文章

      网友评论

        本文标题:第4讲-FFmepg-视频解码

        本文链接:https://www.haomeiwen.com/subject/vteivxtx.html