关于ffmpeg:FFmpeg开发笔记四ffmpeg解码的基本流程详解

7次阅读

共计 10563 个字符,预计需要花费 27 分钟才能阅读完成。

若该文为原创文章,未经容许不得转载
原博主博客地址:https://blog.csdn.net/qq21497936
原博主博客导航:https://blog.csdn.net/qq21497936/article/details/102478062
本文章博客地址:https://blog.csdn.net/qq21497936/article/details/108573195
各位读者,常识无穷而人力有穷,要么改需要,要么找专业人士,要么本人钻研

红瘦子 (红模拟) 的博文大全:开发技术汇合(蕴含 Qt 实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软硬联合等等)继续更新中 …(点击传送门)

FFmpeg 开发专栏(点击传送门)

上一篇:《FFmpeg 开发笔记(三):ffmpeg 介绍、windows 编译以及开发环境搭建》
下一篇:敬请期待

前言

  ffmpeg 波及了很多,循序渐进,本篇形容根本的解码流程。

Demo

  

ffmpeg 解码流程

  ffmpeg 的解码和编码都遵循其根本的执行流程。
  根本流程如下:
  

步骤一:注册:

  应用 ffmpeg 对应的库,都须要进行注册,能够注册子项也能够注册全副。

步骤二:关上文件:

  关上文件,依据文件名信息获取对应的 ffmpeg 全局上下文。

步骤三:探测流信息:

  肯定要探测流信息,拿到流编码的编码格局,不探测流信息则其流编码器拿到的编码类型可能为空,后续进行数据转换的时候就无奈通晓原始格局,导致谬误。

步骤四:查找对应的解码器

  根据流的格局查找解码器,软解码还是硬解码是在此处决定的,然而特地留神是否反对硬件,须要本人查找本地的硬件解码器对应的标识,并查问其是否反对。广泛操作是,枚举反对文件后缀解码的所有解码器进行查找,查找到了就是能够硬解了(此处,不做过多的探讨,对应硬解码后续会有文章进行进一步钻研)。
(留神:解码时查找解码器,编码时查找编码器,两者函数不同,不要弄错了,否则后续能关上然而数据是错的)

步骤五:关上解码器

  关上获取到的解码器。

步骤六:申请缩放数据格式转换构造体

  此处特地留神,基本上解码的数据都是 yuv 系列格局,然而咱们显示的数据是 rgb 等相干色彩空间的数据,所以此处转换构造体就是进行转换前到转换后的形容,给后续转换函数提供转码根据,是很要害并且十分罕用的构造体。

步骤七:申请缓存区

  申请一个缓存区 outBuffer,fill 到咱们指标帧数据的 data 上,比方 rgb 数据,QAVFrame 的 data 上存是有指定格局的数据,且存储有规定,而 fill 到 outBuffer(本人申请的指标格局一帧缓存区),则是咱们须要的数据格式存储程序。
  举个例子,解码转换后的数据为 rgb888,理论间接用 data 数据是谬误的,然而用 outBuffer 就是对的,所以此处应该是 ffmpeg 的 fill 函数做了一些转换。
进入循环解码:

步骤八:获取一帧 packet

  拿取封装的一个 packet,判断 packet 数据的类型进行解码拿到存储的编码数据

步骤九:数据转换

  应用转换函数联合转换构造体对编码的数据进行转换,那拿到须要的指标宽度、高度和指定存储格局的原始数据。

步骤十:自行处理

  拿到了原始数据自行处理。
  一直循环,直到拿取 pakcet 函数胜利,然而无奈 got 一帧数据,则代表文件解码曾经实现。
  帧率须要本人管制循环,此处只是循环拿取,可加提早等。

步骤十一:开释 QAVPacket

  此处要独自列出是因为,其实很多网上和开发者的代码:
  在进入循环解码前进行了 av_new_packet,循环中未 av_free_packet,造成内存溢出;
  在进入循环解码前进行了 av_new_packet,循环中进行 av_free_pakcet,那么一次 new 对应无数次 free,在编码器上是不合乎前后一一对应标准的。
  查看源代码,其实能够发现 av_read_frame 时,主动进行了 av_new_packet(),那么其实对于 packet,只须要进行一次 av_packet_alloc()即可,解码完后 av_free_packet。
  执行完后,返回执行“步骤八:获取一帧 packet”,一次循环完结。

步骤十二:开释转换构造体

  全副解码实现后,装置申请程序,进行对应资源的开释。

步骤十三:敞开解码 / 编码器

  敞开之前关上的解码 / 编码器。

步骤十四:敞开上下文

  敞开文件上下文后,要对之前申请的变量依照申请的程序,顺次开释。
  另附上实现的具体解码流程图:
  

本文章博客地址:https://blog.csdn.net/qq21497936/article/details/108573195

ffmpeg 解码相干变量

AVFormatContext

  AVFormatContext 形容了一个媒体文件或媒体流的形成和根本信息,位于 avformat.h 文件中。

AVInputFormat

  AVInputFormat 是相似 COM 接口的数据结构,示意输出文件容器格局,着重于性能函数,一种文件容器格局对应一个 AVInputFormat 构造,在程序运行时有多个实例,位于 avoformat.h 文件中。

AVDictionary

  AVDictionary 是一个字典汇合,键值对,用于配置相干信息。

AVCodecContext

  AVCodecContext 是一个形容编解码器上下文的数据结构,蕴含了泛滥编解码器须要的参数信息,位于 avcodec.h 文件中。

AVPacket

  AVPacket 是 FFmpeg 中很重要的一个数据结构,它保留理解复用(demuxer)之后,解码(decode)之前的数据(依然是压缩后的数据)和对于这些数据的一些附加的信息,如显示工夫戳(pts),解码工夫戳(dts), 数据时长(duration),所在流媒体的索引(stream_index)等等。
  应用前,应用 av_packet_alloc()调配,

AVCodec

  AVCodec 是存储编解码器信息的构造体,位于 avcodec.h 文件中。

AVFrame

  AVFrame 中存储的是通过解码后的原始数据。在解码中,AVFrame 是解码器的输入;在编码中,AVFrame 是编码器的输出。
  应用前,应用 av_frame_alloc()进行调配。

struct SwsContext

  应用前,应用 sws_getContext()进行获取,次要用于视频图像的转换。

ffmpeg 解码流程相干函数原型

av_register_all

void av_register_all(void);

  初始化 libavformat 并注册所有 muxer、demuxer 和协定。如果不调用此函数,则能够抉择想要指定注册反对的哪种格局,通过 av_register_input_format()、av_register_output_format()。

avformat_open_input

int avformat_open_input(AVFormatContext **ps,
                        const char *url,
                        AVInputFormat *fmt, 
                        AVDictionary **options);

  关上输出流并读取标头。编解码器未关上。流必须应用 avformat_close_input()敞开,返回 0 - 胜利,<0- 失败错误码。

  • 参数一:指向用户提供的 AVFormatContext(由 avformat_alloc_context 调配)的指针。
  • 参数二:要关上的流的 url
  • 参数三:fmt 如果非空,则此参数强制应用特定的输出格局。否则将自动检测格局。
  • 参数四:蕴含 AVFormatContext 和 demuxer 公有选项的字典。返回时,此参数将被销毁并替换为蕴含找不到的选项。都无效则返回为空。

avformat_find_stream_info

int avformat_find_stream_info(AVFormatContext ic, AVDictionary *options);
读取查看媒体文件的数据包以获取具体的流信息,如媒体存入的编码格局。

  • 参数一:媒体文件上下文。
  • 参数二:字典,一些配置选项。

avcodec_find_decoder

AVCodec *avcodec_find_decoder(enum AVCodecID id);

  查找具备匹配编解码器 ID 的已注册解码器,解码时,曾经获取到了,注册的解码器能够通过枚举查看,枚举太多,略。

avcodec_open2

int avcodec_open2(AVCodecContext *avctx, 
                  const AVCodec *codec, 
                  AVDictionary **options);

  初始化 AVCodeContext 以应用给定的 AVCodec。

sws_getContext

struct SwsContext *sws_getContext(int srcW, 
                                  int srcH, 
                                  enum AVPixelFormat srcFormat,
                                  int dstW,
                                  int dstH, 
                                  enum AVPixelFormat dstFormat,
                                  int flags, SwsFilter *srcFilter,
                                  SwsFilter *dstFilter,
                                  const double *param);

  调配并返回一个 SwsContext。须要它来执行 sws_scale()进行缩放 / 转换操作。

avpicture_get_size

int avpicture_get_size(enum AVPixelFormat pix_fmt, int width, int height);

  返回存储具备给定参数的图像的缓存区域大小。

  • 参数一:图像的像素格局
  • 参数二:图像的像素宽度
  • 参数三:图像的像素高度

avpicture_fill

int avpicture_fill(AVPicture *picture,
              const uint8_t *ptr,
              enum AVPixelFormat pix_fmt,
              int width,
              int height);

  依据指定的图像、提供的数组设置数据指针和线条大小参数。

  • 参数一:输出 AVFrame 指针,强制转换为 AVPciture 即可。
  • 参数二:映射到的缓存区,开发者本人申请的寄存图像数据的缓存区。
  • 参数三:图像数据的编码格局。
  • 参数四:图像像素宽度。
  • 参数五:图像像素高度。

av_read_frame

int av_read_frame(AVFormatContext *s, AVPacket *pkt);

  返回流的下一帧。此函数返回存储在文件中的内容,不对无效的帧进行验证。获取存储在文件中的帧中,并为每个调用返回一个。不会的省略无效帧之间的有效数据,以便给解码器最大可用于解码的信息。
  返回 0 是胜利,小于 0 则是谬误,大于 0 则是文件开端,所以大于等于 0 是返回胜利。

avcodec_decode_video2

int avcodec_decode_video2(AVCodecContext *avctx,
                          AVFrame *picture,
                          int *got_picture_ptr,
                          const AVPacket *avpkt);

  将大小为 avpkt->size from avpkt->data 的视频帧解码为图片。一些解码器能够反对单个 avpkg 包中的多个帧,解码器将只解码第一帧。出错时返回负值,否则返回字节数,如果没有帧能够解压缩,则为 0。

  • 参数一:编解码器上下文。
  • 参数二:将解码视频帧存储在 AVFrame 中。
  • 参数三:输出缓冲区的 AVPacket。
  • 参数四:如果没有帧能够解压,那么失去的图片是 0,否则,它是非零的。

sws_scale

int sws_scale(struct SwsContext *c,
              const uint8_t *const srcSlice[],
              const int srcStride[],
              int srcSliceY,
              int srcSliceH,
              uint8_t *const dst[],
              const int dstStride[]);

  在 srcSlice 中缩放图像切片并将后果缩放在 dst 中切片图像。切片是间断的序列图像中的行。

  • 参数一:以前用创立的缩放上下文 *sws_getContext()。
  • 参数二:蕴含指向源片段,就是 AVFrame 的 data。
  • 参数三:蕴含每个立体的跨步的数组,其实就是 AVFrame 的 linesize。
  • 参数四:切片在源图像中的地位,从开始计数 0 对应切片第一行的图像,所以间接填 0 即可。
  • 参数五:源切片的像素高度。
  • 参数六:指标数据地址映像,是指标 AVFrame 的 data。
  • 参数七:指标每个立体的跨步的数组,就是 linesize。

av_free_packet

void av_free_packet(AVPacket *pkt);

  开释一个包。

avcodec_close

int avcodec_close(AVCodecContext *avctx);

  敞开给定的 avcodeContext 并开释与之关联的所有数据(但不是 AVCodecContext 自身)。

avformat_close_input

void avformat_close_input(AVFormatContext **s);

  敞开关上的输出 AVFormatContext。开释它和它的所有内容并将 * s 设置为空。

Demo 源码

void FFmpegManager::testDecode()
{
//    QString fileName = "test/1.avi";
    QString fileName = "test/1.mp4";

    // ffmpeg 相干变量事后定义与调配
    AVFormatContext *pAVFormatContext = 0;          // ffmpeg 的全局上下文,所有 ffmpeg 操作都须要
    AVInputFormat *pAVInputFormat = 0;              // ffmpeg 的输出格局构造体
    AVDictionary *pAVDictionary = 0;                // ffmpeg 的字典 option,各种参数给格局编解码配置参数的
    AVCodecContext *pAVCodecContext = 0;            // ffmpeg 编码上下文
    AVCodec *pAVCodec = 0;                          // ffmpeg 编码器
    AVPacket *pAVPacket = 0;                        // ffmpag 单帧数据包
    AVFrame *pAVFrame = 0;                          // ffmpeg 单帧缓存
    AVFrame *pAVFrameRGB32 = 0;                     // ffmpeg 单帧缓存转换色彩空间后的缓存
    struct SwsContext *pSwsContext = 0;             // ffmpag 编码数据格式转换

    int ret = 0;                                    // 函数执行后果
    int videoIndex = -1;                            // 音频流所在的序号
    int gotPicture = 0;                             // 解码时数据是否解码胜利
    int numBytes = 0;                               // 解码后的数据长度
    uchar *outBuffer = 0;                           // 解码后的数据寄存缓存区

    pAVFormatContext = avformat_alloc_context();     // 调配
    pAVPacket = av_packet_alloc();                  // 调配
    pAVFrame = av_frame_alloc();                   // 调配
    pAVFrameRGB32 = av_frame_alloc();             // 调配
    if(!pAVFormatContext || !pAVPacket || !pAVFrame || !pAVFrameRGB32)
    {
        LOG << "Failed to alloc";
        goto END;
    }
    // 步骤一:注册所有容器和编解码器(也能够只注册一类,如注册容器、注册编码器等)av_register_all();

    // 步骤二:关上文件(ffmpeg 胜利则返回 0)
    LOG << "文件:" << fileName << ",是否存在:" << QFile::exists(fileName);
    ret = avformat_open_input(&pAVFormatContext, fileName.toUtf8().data(), pAVInputFormat, 0);
    if(ret)
    {
        LOG << "Failed";
        goto END;
    }

    // 步骤三:探测流媒体信息
    // Assertion desc failed at libswscale/swscale_internal.h:668
    // 入坑:因为 pix_fmt 为空,须要对编码器上下文进一步探测
    ret = avformat_find_stream_info(pAVFormatContext, 0);
    if(ret < 0)
    {LOG << "Failed to avformat_find_stream_info(pAVCodecContext, 0)";
        goto END;
    }
    // 打印文件信息
    LOG << "视频文件蕴含流信息的数量:" << pAVFormatContext->nb_streams;
    // 在 Qt 中 av_dump_format 不会进行命令行输入
//    av_dump_format(pAVFormatContext, 1, fileName.toUtf8().data(), 0);

    // 步骤三:提取流信息, 提取视频信息
    for(int index = 0; index < pAVFormatContext->nb_streams; index++)
    {pAVCodecContext = pAVFormatContext->streams[index]->codec;
        switch (pAVCodecContext->codec_type)
        {
        case AVMEDIA_TYPE_UNKNOWN:
            LOG << "流序号:" << index << "类型为:" << "AVMEDIA_TYPE_UNKNOWN";
            break;
        case AVMEDIA_TYPE_VIDEO:
            LOG << "流序号:" << index << "类型为:" << "AVMEDIA_TYPE_VIDEO";
            videoIndex = index;
            LOG;
            break;
        case AVMEDIA_TYPE_AUDIO:
            LOG << "流序号:" << index << "类型为:" << "AVMEDIA_TYPE_AUDIO";
            break;
        case AVMEDIA_TYPE_DATA:
            LOG << "流序号:" << index << "类型为:" << "AVMEDIA_TYPE_DATA";
            break;
        case AVMEDIA_TYPE_SUBTITLE:
            LOG << "流序号:" << index << "类型为:" << "AVMEDIA_TYPE_SUBTITLE";
            break;
        case AVMEDIA_TYPE_ATTACHMENT:
            LOG << "流序号:" << index << "类型为:" << "AVMEDIA_TYPE_ATTACHMENT";
            break;
        case AVMEDIA_TYPE_NB:
            LOG << "流序号:" << index << "类型为:" << "AVMEDIA_TYPE_NB";
            break;
        default:
            break;
        }
        // 曾经找打视频品流
        if(videoIndex != -1)
        {break;}
    }
    if(videoIndex == -1 || !pAVCodecContext)
    {
        LOG << "Failed to find video stream";
        goto END;
    }
    // 步骤四:对找到的视频流寻解码器
    pAVCodec = avcodec_find_decoder(pAVCodecContext->codec_id);
    if(!pAVCodec)
    {LOG << "Fialed to avcodec_find_decoder(pAVCodecContext->codec_id):"
            << pAVCodecContext->codec_id;
        goto END;
    }

    // 步骤五:关上解码器
    ret = avcodec_open2(pAVCodecContext, pAVCodec, NULL);
    if(ret)
    {LOG << "Failed to avcodec_open2(pAVCodecContext, pAVCodec, pAVDictionary)";
        goto END;
    }
    LOG << pAVCodecContext->width << "x" << pAVCodecContext->height;
    // 步骤六:对拿到的原始数据格式进行缩放转换为指定的格局高宽大小
    // Assertion desc failed at libswscale/swscale_internal.h:668
    // 入坑:因为 pix_fmt 为空,须要对编码器上下文进一步探测
    pSwsContext = sws_getContext(pAVCodecContext->width,
                                 pAVCodecContext->height,
                                 pAVCodecContext->pix_fmt,
                                 pAVCodecContext->width,
                                 pAVCodecContext->height,
                                 AV_PIX_FMT_RGBA,
                                 SWS_FAST_BILINEAR,
                                 0,
                                 0,
                                 0);
    numBytes = avpicture_get_size(AV_PIX_FMT_RGBA,
                                  pAVCodecContext->width,
                                  pAVCodecContext->height);
    outBuffer = (uchar *)av_malloc(numBytes);
    // pAVFrame32 的 data 指针指向了 outBuffer
    avpicture_fill((AVPicture *)pAVFrameRGB32,
                   outBuffer,
                   AV_PIX_FMT_RGBA,
                   pAVCodecContext->width,
                   pAVCodecContext->height);
    // 此处无需调配
    // av_read_frame 时他会调配,av_new_packet 多此一举,正好解释了一次 new 和屡次 free 的问题
//    av_new_packet(pAVPacket, pAVCodecContext->width * pAVCodecContext->height);
    // 步骤七:读取一帧数据的数据包
    while(av_read_frame(pAVFormatContext, pAVPacket) >= 0)
    {if(pAVPacket->stream_index == videoIndex)
        {
            // 步骤八:对读取的数据包进行解码
            ret = avcodec_decode_video2(pAVCodecContext, pAVFrame, &gotPicture, pAVPacket);
            if(ret < 0)
            {LOG << "Failed to avcodec_decode_video2(pAVFormatContext, pAVFrame, &gotPicture, pAVPacket)";
                break;
            }
            // 等于 0 代表拿到了解码的帧数据
            if(!gotPicture)
            {
                LOG << "no data";
                break;
            }else{
                sws_scale(pSwsContext,
                          (const uint8_t * const *)pAVFrame->data,
                          pAVFrame->linesize,
                          0,
                          pAVCodecContext->height,
                          pAVFrameRGB32->data,
                          pAVFrameRGB32->linesize);
                QImage imageTemp((uchar *)outBuffer,
                                 pAVCodecContext->width,
                                 pAVCodecContext->height,
                                 QImage::Format_RGBA8888);
                QImage image = imageTemp.copy();
                LOG << image.save("1.jpg");
            }
            av_free_packet(pAVPacket);
        }
        QThread::msleep(100);
    }
END:
    LOG << "开释回收资源";
    if(outBuffer)
    {av_free(outBuffer);
        outBuffer = 0;
    }
    if(pSwsContext)
    {sws_freeContext(pSwsContext);
        pSwsContext = 0;
        LOG << "sws_freeContext(pSwsContext)";
    }
    if(pAVFrameRGB32)
    {av_frame_free(&pAVFrameRGB32);
        pAVFrame = 0;
        LOG << "av_frame_free(pAVFrameRGB888)";
    }
    if(pAVFrame)
    {av_frame_free(&pAVFrame);
        pAVFrame = 0;
        LOG << "av_frame_free(pAVFrame)";
    }
    if(pAVPacket)
    {av_free_packet(pAVPacket);
        pAVPacket = 0;
        LOG << "av_free_packet(pAVPacket)";
    }
    if(pAVCodecContext)
    {avcodec_close(pAVCodecContext);
        pAVCodecContext = 0;
        LOG << "avcodec_close(pAVCodecContext);";
    }
    if(pAVFormatContext)
    {avformat_free_context(pAVFormatContext);
        pAVFormatContext = 0;
        LOG << "avformat_free_context(pAVFormatContext)";
    }
}

工程模板 v1.1.0

  对应工程模板 v1.1.0

上一篇:《FFmpeg 开发笔记(三):ffmpeg 介绍、windows 编译以及开发环境搭建》
下一篇:敬请期待

原博主博客地址:https://blog.csdn.net/qq21497936
原博主博客导航:https://blog.csdn.net/qq21497936/article/details/102478062
本文章博客地址:https://blog.csdn.net/qq21497936/article/details/108573195

正文完
 0