FFMPEG音视频处理--FFMPEG解码

目录

一:视频解码流程

二:FFMPEG解码流程

三:FFmpeg解码函数 

四:FFmpeg解码的数据结构 

五:FFmpeg数据结构简介

六:FFmpeg数据结构分析

七:像素数据转换 

八:FFMPEG解码

1.注册所有组件

2.打开视频输入文件 

3.查找视频流信息 

4.查找解码器 

5.打开解码器 

6.循环解码 

7.关闭所有解码组件 

九:FFMPEG解码-视频播放 


一:视频解码流程

1.1 纯净的视频解码流程

压缩编码数据->像素数据。例如解码H.264,就是“H.264码流->YUV”。

1.2 一般的视频解码流程

视频码流一般存储在一定的封装格式(例如MP4、AVI等)中。

封装格式中通常还包含音频码流等内容。

对于封装格式中的视频,需要先从封装格式中提取中视频码流,然后再进行解码。

例如解码MKV格式的视频文件,就是“MKV->H.264码流->YUV”。

二:FFMPEG解码流程

三:FFmpeg解码函数 

av_register_all():注册所有组件。
avformat_open_input():打开输入视频文件。
avformat_find_stream_info():获取视频文件信息。
avcodec_find_decoder():查找解码器。
avcodec_open2():打开解码器。
av_read_frame():从输入文件读取一帧压缩数据。
avcodec_decode_video2():解码一帧压缩数据。
avcodec_close():关闭解码器。
avformat_close_input():关闭输入视频文件。

四:FFmpeg解码的数据结构 

五:FFmpeg数据结构简介

AVFormatContext

封装格式上下文结构体,也是统领全局的结构体,保存了视频文件封装格式相关信息

AVInputFormat

每种封装格式(例如FLV, MKV, MP4, AVI)对应一个该结构体。

AVStream[2]

视频文件中每个视频(音频)流对应一个该结构体。

 AVCodecContext

编码器上下文结构体,保存了视频(音频)编解码相关信息。

AVCodec

每种视频(音频)编解码器(例如H.264解码器)对应一个该结构体。

AVPacket

存储一帧压缩编码数据。

AVFrame

存储一帧解码后像素(采样)数据。

六:FFmpeg数据结构分析

七:像素数据转换 

解码后YUV格式的视频像素数据保存在AVFrame的data[0]、data[1]、data[2]中。

但是这些像素值并不是连续存储的,每行有效像素之后存储了一些无效像素 。

以亮度 Y 数据为例 , data[0] 中一共包含了linesize[0]* height个数据。

但是出于优化等方面的考虑,linesize[0]实际上并不等于宽度width,而是一个比宽度大一些的值。

因此需要使用sws_scale()进行转换。转换后去除了无效数据,width和linesize[0]取值相等。

八:FFMPEG解码

实现步骤

1.注册所有组件

av_register_all();

2.打开视频输入文件 

    //文件路径设置                        程序运行当前路径-exe所存在的路径
    QString filename = QCoreApplication::applicationDirPath();
    qDebug()<<"获取程序运行目录 "<<filename;
    //文件名称中文乱码-建议使用英文
    QString cinputFilePath = "test.avi";  //本地视频文件放入程序运行目录
    //指针开空间
    avformat_context = avformat_alloc_context();
    //参数一:封装格式上下文->AVFormatContext->包含了视频信息(视频格式、大小等等...)
    //参数二:打开文件(入口文件)->url
    qDebug()<<"打开"<<videoname<<"视频文件进行播放";
    //打开文件把文件详细信息传入avformat_context
    //形参代表什么 返回值什么含义                                       标准C++的String类型转C的char*类型
    int avformat_open_result = avformat_open_input(&avformat_context,videoname.toStdString().c_str(),NULL,NULL);
    if (avformat_open_result != 0)
    {
        //获取异常信息--打开视频文件失败--具体失败原因
        char* error_info = new char[32];
        av_strerror(avformat_open_result, error_info, 1024);
        qDebug()<<QString("异常信息 %1").arg(error_info);
    };

3.查找视频流信息 

    //参数一:封装格式上下文->AVFormatContext
    //参数二:配置
    //返回值:0>=返回OK,否则失败                  查找流信息
    int avformat_find_stream_info_result = avformat_find_stream_info(avformat_context, NULL);
    if (avformat_find_stream_info_result < 0){
        //获取失败--没有找到流信息
        char* error_info = new char[32];
        av_strerror(avformat_find_stream_info_result, error_info, 1024);
        qDebug()<<QString("异常信息 %1").arg(error_info);
    }

4.查找解码器 

    //第一点:获取当前解码器是属于什么类型解码器->找到了视频流
    //音频解码器、视频解码器、字幕解码器等等...
    //获取视频解码器流引用
    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;
        }
    }
    if (av_stream_index == -1)
    {
        qDebug()<<QString("没有找到视频流");
    }
    //第二点:根据视频流->查找到视频解码器上下文->视频压缩数据
    //编解码器上下文---是否有合适的编解码器
    avcodec_context = avformat_context->streams[av_stream_index]->codec;

    //第三点:根据解码器上下文->获取解码器ID
    avcodec = avcodec_find_decoder(avcodec_context->codec_id);
    if (avcodec == NULL)
    {
        qDebug()<<QString("没有找到视频解码器");
    }

5.打开解码器 

    int avcodec_open2_result = avcodec_open2(avcodec_context,avcodec,NULL);
    if (avcodec_open2_result != 0)
    {
        char* error_info = new char[32];
        av_strerror(avformat_find_stream_info_result, error_info, 1024);
        qDebug()<<QString("异常信息 %1").arg(error_info);
    }

    qDebug()<<"视频详细信息输出";
    //此函数自动打印输入或输出的详细信息--退出时候才会有信息显示
    av_dump_format(avformat_context, 0, cinputFilePath.toStdString().c_str(), 0);
    qDebug()<<"----------------解码准备工作完成-----------------";

6.循环解码 

    //读取帧数据换成到哪里->缓存到packet里面
    av_packet = (AVPacket*)av_malloc(sizeof(AVPacket));

    //输入->环境一帧数据->缓冲区->类似于一张图
    pFramein = av_frame_alloc();
    //输出->帧数据->数据格式->RGB
    pFrameRGB = av_frame_alloc();
    //只有指定了AVFrame的像素格式、画面大小才能真正分配内存
    //缓冲区分配内存
    pOutbuffer = (uint8_t *)av_malloc(avpicture_get_size(
                                          AV_PIX_FMT_RGB32, avcodec_context->width, avcodec_context->height));
    //初始化缓冲区 类似于内存的memset-开辟完清理操作
    avpicture_fill((AVPicture *)pFrameRGB, pOutbuffer,
                   AV_PIX_FMT_RGB32, avcodec_context->width, avcodec_context->height);

    //解码的状态类型(0:表示解码完毕,非0:表示正在解码)
    //    int current_frame_index = 0;

    //用于转码(缩放)的参数,转之前的宽高,转之后的宽高,格式等
    //准备一个视频像素数据格式上下文
    //参数一:输入帧数据宽
    //参数二:输入帧数据高
    //参数三:输入帧数据格式
    //参数四:输出帧数据宽
    //参数五:输出帧数据高
    //参数六:输出帧数据格式->AV_PIX_FMT_RGB32
    //参数七:视频像素数据格式转换算法类型
    //参数八:字节对齐类型(C/C++里面)->提高读取效率
    SwsContext* pSwsContext = sws_getContext(avcodec_context->width,
                                             avcodec_context->height,
                                             avcodec_context->pix_fmt,
                                             avcodec_context->width,
                                             avcodec_context->height,
                                             AV_PIX_FMT_RGB32,
                                             SWS_BICUBIC,NULL,NULL,NULL);
    int ret;//解码结果--处理出来的图片

    //解码的状态类型(0:表示解码完毕,非0:表示正在解码)--在循环体外进行操作
    int current_frame_index = 0;

    //线程标志位
    while (m_stop == false)
    {
        //>=0:说明有数据,继续读取   <0:说明读取完毕,结束
        //从视频文件上下文中读取包--- 有数据就一直读取
        //判断--有数据的话才会一直读取
        if (av_read_frame(avformat_context,av_packet) >= 0)
        {
            //解码什么类型流(视频流、音频流、字幕流等等...)
            if (av_packet->stream_index == av_stream_index)
            {
                //解码一帧视频数据
                avcodec_send_packet(avcodec_context, av_packet);

                //接收一帧数据->解码一帧      处理出来的图片存储到pFramein
                ret = avcodec_receive_frame(avcodec_context,pFramein);
                //处理出来的图片是否可行
                if (ret == 0)//解码成功
                {
                    //图片的转换的相关操作  输入pFramein 输出pFrameRGB
                    sws_scale(pSwsContext, (const unsigned char* const*)pFramein->data, pFramein->linesize, 0, avcodec_context->height,
                              pFrameRGB->data,  pFrameRGB->linesize);

                    QImage  *tmpImg  = new QImage((uchar *)pOutbuffer, avcodec_context->width,
                                                  avcodec_context->height,QImage::Format_RGB32);

                    QImage image=tmpImg->copy();

                    qDebug()<<"接收图片信号"<<image;
                    //解码得到的
                    //一部分做显示  发送信号(图片)--emit
                    emit sigGetOneFrame(image);
                    //一部分做编码
                    //循环 编码一帧数据
                    pvideoCode->codeingOneFrame(pFramein);

                    //遍历每一帧的信息进行打印
                    current_frame_index++;
                    //发送信号-emit 解码信息放在窗口中进行显示
                    emit SendOneData(current_frame_index);
                    //延时操作  1秒显示25帧--1000/25=40
                    QThread::msleep(40);
                    //获取的视频信息
                    qDebug()<<QString("当前遍历第 %1 帧").arg(current_frame_index);
                }
            }
        }

        av_free_packet(av_packet);

7.关闭所有解码组件 

    av_packet_free(&av_packet);
    //关闭流
    avcodec_close(avcodec_context);
    avformat_free_context(avformat_context);

九:FFMPEG解码-视频播放 

线程简介

•线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

线程状态 

线程启动和停止 

•线程启动调用start()函数。
•RUN函数执行完表示线程退出。

QThread 使用测试

猜你喜欢

转载自blog.csdn.net/m0_56051805/article/details/125136815