在上一篇文章中,对FFmpeg的视频解码过程做了一个总结。由于才接触FFmpeg,还是挺陌生的,这里就解码过程再做一个总结。

本文的总结分为以下两个部分:

  • 数据读取,主要关注在解码过程中所用到的FFmpeg中的结构体。
  • 解码过程中所调用的函数

在学习的过程主要参考的是dranger tutorial,所以跟着教程在本文的最后使用SDL2.0将解码后的数据输出到屏幕上。

数据的读取

一个多媒体文件包含有多个流(视频流 video stream,音频流 audio stream,字幕等);流是一种抽象的概念,表示一连串的数据元素;

流中的数据元素称为帧Frame。也就是说多媒体文件中,主要有两种数据:流Stream 及其数据元素 帧Frame,在FFmpeg自然有与这两种数据相对应的抽象:AVStream和AVPacket

使用FFmpeg的解码,数据的传递过程可归纳如下:

  1. 调用avformat_open_input打开流,将信息填充到AVFormatContext
  2. 调用av_read_frame从流中读取数据帧到 AVPacketAVPacket保存仍然是未解码的数据
  3. 调用avcodec_decode_video2AVPacket的数据解码,并将解码后的数据填充到AVFrame中,AVFrame中保存的是解码后的原始数据

上述过程可以使用下图表示:

结构体的存储空间的分配与释放

FFmpeg并没有垃圾回收机制,所分配的空间都需要自己维护。而由于视频处理过程中数据量是非常大,对于动态内存的使用更要谨慎。

本小节主要介绍解码过程使用到的结构体存储空间的分配与释放。

  • AVFormatContext 在FFmpeg中有很重要的作用,描述一个多媒体文件的构成及其基本信息,存放了视频编解码过程中的大部分信息。通常该结构体由avformat_open_input分配

    存储空间,在最后调用avformat_input_close关闭。

  • AVStream 描述一个媒体流,在解码的过程中,作为AVFormatContext的一个字段存在,不需要单独的处理。

  • AVpacket 用来存放解码之前的数据,它只是一个容器,其data成员指向实际的数据缓冲区,在解码的过程中可有av_read_frame创建和填充AVPacket中的数据缓冲区,

    当数据缓冲区不再使用的时候可以调用av_free_apcket释放这块缓冲区。

  • AVFrame 存放从AVPacket中解码出来的原始数据,其必须通过av_frame_alloc来创建,通过av_frame_free来释放。和AVPacket类似,AVFrame中也有一块数据缓存空间,

    在调用av_frame_alloc的时候并不会为这块缓存区域分配空间,需要使用其他的方法。在解码的过程使用了两个AVFrame,这两个AVFrame分配缓存空间的方法也不相同

    • 一个AVFrame用来存放从AVPacket中解码出来的原始数据,这个AVFrame的数据缓存空间通过调avcodec_decode_video分配和填充。
    • 另一个AVFrame用来存放将解码出来的原始数据变换为需要的数据格式(例如RGB,RGBA)的数据,这个AVFrame需要手动的分配数据缓存空间。代码如下:
AVFrame* pFrameYUV;
pFrameYUV = av_frame_alloc();
// 手动为 pFrameYUV分配数据缓存空间
int numBytes = avpicture_get_size(AV_PIX_FMT_YUV420P,pCodecCtx->widht,pCodecCtx->width);
uint8_t* buffer = (uint8_t*)av_malloc(numBytes * sizeof(uint8_t));
// 将分配的数据缓存空间和AVFrame关联起来
avpicture_fill((AVPicture *)pFrameYUV, buffer, AV_PIX_FMT_YUV420P,pCodecCtx->width, pCodecCtx->height)

首先计算需要缓存空间大小,调用av_malloc分配缓存空间,最后调用avpicture_fill将分配的缓存空间和AVFrame关联起来。

调用av_frame_free来释放AVFrame,该函数不止释放AVFrame本身的空间,还会释放掉包含在其内的其他对象动态申请的空间,例如上面的缓存空间。

  • av_malloc和av_free,FFmpeg并没有提供垃圾回收机制,所有的内存管理都要手动进行。av_malloc只是在申请内存空间的时候会考虑到内存对齐(2字节,4字节对齐),

    其申请的空间要调用av_free释放。

调用的函数

  • av_register_all 这个函数不用多说了,注册库所支持的容器格式及其对应的CODEC。
  • avformat_open_input 打开多媒体文件流,并读取文件的头,将读取到的信息填充到AVFormatContext结构体中。在使用结束后,要调用avformat_close_input关闭打开的流
  • avformat_find_stream_info 上面提到,avformat_open_input只是读取文件的头来得到多媒体文件的信息,但是有些文件没有文件头或者文件头的格式不正确,这就造成只调用

    avformat_open_input可能得不到解码所需要的必要信息,需要调用avformat_find_stream_info进一步得到流的信息。

通过上面的三个函数已经获取了对多媒体文件进行解码的所需要信息,下面要做的就是根据这些信息得到相应的解码器。

结构体AVCodecContext描述了编解码器的上下文信息,包含了流中所使用的关于编解码器的所有信息,可以通过 AVFormatContext->AVStream->AVCodecContext来得到,在有了AVCodecContext后,可以通过codec_id来找到相应的解码器,具体代码如下:

AVCodec* pCodec = nullptr;
pCodecCtxOrg = pFormatCtx->streams[videoStream]->codec; // codec context
// 找到video stream的 decoder
pCodec = avcodec_find_decoder(pCodecCtxOrg->codec_id);
  • avcodec_find_decoder 可以通过codec_id或者名称来找到相应的解码器,返回值是一个AVCodec的指针。
  • avcodec_open2 打开相应的编解码器
  • av_read_frame 从流中读取数据帧暂存到AVPacket中
  • avcodec_decode_video2 从AVPacket中解码数据到AVFrame中

经过以上的过程,AVFrame中的数据缓存中存放的就是解码后的原始数据了。整个流程梳理如下:

使用SDL2.0显示视频

使用SDL2.0,dranger tutorial中的显示视频部分的代码就不是很适用了,需要做一些修改。不过,SDL2.0显示图像还是挺简单的。

    SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER);
SDL_Window* window = SDL_CreateWindow("FFmpeg Decode", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
pCodecCtx->width, pCodecCtx->height, SDL_WINDOW_OPENGL | SDL_WINDOW_MAXIMIZED);
SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, 0);
SDL_Texture* bmp = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12, SDL_TEXTUREACCESS_STREAMING,
pCodecCtx->width, pCodecCtx->height);
SDL_Rect rect;
rect.x = 0;
rect.y = 0;
rect.w = pCodecCtx->width;
rect.h = pCodecCtx->height;
SDL_Event event;

上述代码为初始化后SDL显示图像所需要的环境,在使用FFmpeg解码数据后

    int frameFinished = 0;
avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);
if (frameFinished)
{
sws_scale(sws_ctx, (uint8_t const * const *)pFrame->data, pFrame->linesize, 0,
pCodecCtx->height, pFrameRGB->data, pFrameRGB->linesize);
SDL_UpdateTexture(bmp, &rect, pFrameRGB->data[0], pFrameRGB->linesize[0]);
SDL_RenderClear(renderer);
SDL_RenderCopy(renderer, bmp, &rect, &rect);
SDL_RenderPresent(renderer);
}

上面代码就将解码得到的图像帧使用SDL显示了出来。不过,这里真的只是显示而已,以能够解码速度快速的将整个视频的图像帧显示一遍。

本节示例代码 FFmpeg1.cpp

FFmpeg学习2:解码数据结构及函数总结的更多相关文章

  1. FFmpeg 学习(五):FFmpeg 编解码 API 分析

    在上一篇文章 FFmpeg学习(四):FFmpeg API 介绍与通用 API 分析 中,我们简单的讲解了一下FFmpeg 的API基本概念,并分析了一下通用API,本文我们将分析 FFmpeg 在编 ...

  2. FFmpeg学习5:多线程播放视音频

    在前面的学习中,视频和音频的播放是分开进行的.这主要是为了学习的方便,经过一段时间的学习,对FFmpeg的也有了一定的了解,本文就介绍了 如何使用多线程同时播放音频和视频(未实现同步),并对前面的学习 ...

  3. FFmpeg 学习(七):FFmpeg 学习整理总结

    一.FFmpeg 播放视频的基本流程整理 播放流程: video.avi(Container) -> 打开得到 Video_Stream -> 读取Packet -> 解析到 Fra ...

  4. ffmpeg学习笔记-初识ffmpeg

    ffmpeg用来对音视频进行处理,那么在使用ffmpeg前就需要ffmpeg有一个大概的了解,这里使用雷神的ppt素材进行整理,以便于复习 音视频基础知识 视频播放器的原理 播放视频的流程大致如下: ...

  5. ffmpeg 学习:000-概述和库的组成

    背景 ffmpeg bin工具 可能无法满足产品的使用,于是需要通过传参调用ffmpeg库,即在通过更底层的方式使用它. FFmpeg 介绍 FFmpeg是领先的多媒体框架,能够解码,编码,转码,复用 ...

  6. [原]如何在Android用FFmpeg+SDL2.0解码图像线程

    关于如何在Android上用FFmpeg+SDL2.0解码显示图像参考[原]如何在Android用FFmpeg+SDL2.0解码显示图像 ,关于如何在Android使用FFmpeg+SDL2.0解码声 ...

  7. [原]如何在Android用FFmpeg+SDL2.0解码声音

    关于如何在Android上用FFmpeg+SDL2.0解码显示图像参考[原]如何在Android用FFmpeg+SDL2.0解码显示图像 ,本文是基于上述文章和[原]零基础学习视频解码之解码声音 来移 ...

  8. FFmpeg源代码结构图 - 解码

    ===================================================== FFmpeg的库函数源代码分析文章列表: [架构图] FFmpeg源代码结构图 - 解码 F ...

  9. Python学习 Part3:数据结构

    Python学习 Part3:数据结构 1. 深入列表: 所有的列表对象方法 list.append(x): 在列表的末尾添加一个元素 list.extend(L): 在列表的末尾添加一个指定列表的所 ...

随机推荐

  1. ABP文档 - 导航

    文档目录 本节内容: 创建菜单 注册导航供应器 显示菜单 每个web应用都有一些菜单用来在页面/屏幕之间导航,ABP提供了一个通用的基础框架创建并显示菜单给用户. 创建菜单 一个应用可能由不同模块组成 ...

  2. 通过重建Hosting系统理解HTTP请求在ASP.NET Core管道中的处理流程[下]:管道是如何构建起来的?

    在<中篇>中,我们对管道的构成以及它对请求的处理流程进行了详细介绍,接下来我们需要了解的是这样一个管道是如何被构建起来的.总的来说,管道由一个服务器和一个HttpApplication构成 ...

  3. 谈谈一些有趣的CSS题目(十)-- 结构性伪类选择器

    开本系列,谈谈一些有趣的 CSS 题目,题目类型天马行空,想到什么说什么,不仅为了拓宽一下解决问题的思路,更涉及一些容易忽视的 CSS 细节. 解题不考虑兼容性,题目天马行空,想到什么说什么,如果解题 ...

  4. Velocity笔记--使用Velocity获取动态Web项目名的问题

    以前使用jsp开发的时候,可以通过request很轻松的获取到根项目名,现在换到使用velocity渲染视图,因为已经不依赖servlet,request等一些类的环境,而Web项目的根项目名又不是写 ...

  5. JavaScript基础知识总结(二)

    JavaScript语法 二.数据类型 程序把这些量.值分为几大类,每一类分别叫什么名称,有什么特点,就叫数据类型. 1.字符串(string) 字符串由零个或多个字符构成,字符包括字母,数字,标点符 ...

  6. SQL Server事务、视图和索引

    废话不多说,直接上干货 14:13:23 事务 概括:事务是一种机制,一个操作序列,包含一组数据库操作命令,并且把所有的命令作为一个整体一起 向系统提交或撤销操作 请求. 事务的特性:   1.原子性 ...

  7. 技术笔记:Indy IdSMTP支持腾讯QQ邮箱邮件发送

    1.腾讯QQ邮箱的授权码问题 因为腾讯邮箱折腾了个底朝天,其要搞什么授权码登录第三方客户端,否则会报这个错误: 'Error: 请使用授权码登录.详情请看: http://service.mail.q ...

  8. 【腾讯Bugly干货分享】Android进程保活招式大全

    本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/57ac4a0ea374c75371c08ce8 作者:腾讯——张兴华 目前市面上 ...

  9. 升讯威ADO.NET增强组件(源码):送给喜欢原生ADO.NET的你

    目前我们所接触到的许多项目开发,大多数都应用了 ORM 技术来实现与数据库的交互,ORM 虽然有诸多好处,但是在实际工作中,特别是在大型项目开发中,容易发现 ORM 存在一些缺点,在复杂场景下,反而容 ...

  10. 死去活来,而不变质:Domain Model(领域模型) 和 EntityFramework 如何正确进行对象关系映射?

    写在前面 阅读目录: 设计误区 数据库已死 枚举映射 关联映射 后记 在上一篇<一缕阳光:DDD(领域驱动设计)应对具体业务场景,如何聚焦 Domain Model(领域模型)?>博文中, ...