在上一篇文章中,对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. .NET Core中间件的注册和管道的构建(3) ---- 使用Map/MapWhen扩展方法

    .NET Core中间件的注册和管道的构建(3) ---- 使用Map/MapWhen扩展方法 0x00 为什么需要Map(MapWhen)扩展 如果业务逻辑比较简单的话,一条主管道就够了,确实用不到 ...

  2. JAVA for mac 的学习之路

    要学习一门新技术,首先得下载相关的工具. 一 . 下载相关工具 1. 下载 jdk formac 下载地址为:http://www.oracle.com/technetwork/java/javase ...

  3. 4.Windows Server2012 R2里面部署 MVC 的网站

    网站部署之~Windows Server | 本地部署:http://www.cnblogs.com/dunitian/p/4822808.html#iis 后期会在博客首发更新:http://dnt ...

  4. Android业务组件化之URL Scheme使用

    前言: 最近公司业务发展迅速,单一的项目工程不再适合公司发展需要,所以开始推进公司APP业务组件化,很荣幸自己能够牵头做这件事,经过研究实现组件化的通信方案通过URL Scheme,所以想着现在还是在 ...

  5. 计算机程序的思维逻辑 (60) - 随机读写文件及其应用 - 实现一个简单的KV数据库

    57节介绍了字节流, 58节介绍了字符流,它们都是以流的方式读写文件,流的方式有几个限制: 要么读,要么写,不能同时读和写 不能随机读写,只能从头读到尾,且不能重复读,虽然通过缓冲可以实现部分重读,但 ...

  6. 解构C#游戏框架uFrame兼谈游戏架构设计

    1.概览 uFrame是提供给Unity3D开发者使用的一个框架插件,它本身模仿了MVVM这种架构模式(事实上并不包含Model部分,且多出了Controller部分).因为用于Unity3D,所以它 ...

  7. HTML 5 应用程序缓存manifest

    什么是应用程序缓存(Application Cache)? HTML5 引入了应用程序缓存,这意味着 web 应用可进行缓存,并可在没有因特网连接时进行访问. 应用程序缓存为应用带来三个优势: 离线浏 ...

  8. Android菜单项内容大全

    一.介绍: 菜单是许多应用中常见的用户界面组件. Android3.0版本以前,Android设备会提供一个专用"菜单"按钮呈现常用的一些用户操作, Android3.0版本以后, ...

  9. NodeJS使用mysql

    1.环境准备 手动添加数据库依赖: 在package.json的dependencies中新增, "mysql" : "latest", { "nam ...

  10. .NET面试题系列[3] - C# 基础知识(1)

    1 类型基础 面试出现频率:基本上肯定出现 重要程度:10/10,身家性命般重要.通常这也是各种招聘工作的第一个要求,即“熟悉C#”的一部分.连这部分都不清楚的人,可以说根本不知道自己每天都在干什么. ...