FFmpeg学习2:解码数据结构及函数总结
2016-08-06 21:47
363 查看
在上一篇文章中,对FFmpeg的视频解码过程做了一个总结。由于才接触FFmpeg,还是挺陌生的,这里就解码过程再做一个总结。
本文的总结分为以下两个部分:
数据读取,主要关注在解码过程中所用到的FFmpeg中的结构体。
解码过程中所调用的函数
在学习的过程主要参考的是dranger tutorial,所以跟着教程在本文的最后使用SDL2.0将解码后的数据输出到屏幕上。
流中的数据元素称为帧Frame。也就是说多媒体文件中,主要有两种数据:流Stream 及其数据元素 帧Frame,在FFmpeg自然有与这两种数据相对应的抽象:AVStream和AVPacket。
使用FFmpeg的解码,数据的传递过程可归纳如下:
调用
调用
调用
上述过程可以使用下图表示:
本小节主要介绍解码过程使用到的结构体存储空间的分配与释放。
AVFormatContext 在FFmpeg中有很重要的作用,描述一个多媒体文件的构成及其基本信息,存放了视频编解码过程中的大部分信息。通常该结构体由
存储空间,在最后调用
AVStream 描述一个媒体流,在解码的过程中,作为AVFormatContext的一个字段存在,不需要单独的处理。
AVpacket 用来存放解码之前的数据,它只是一个容器,其data成员指向实际的数据缓冲区,在解码的过程中可有
当数据缓冲区不再使用的时候可以调用
AVFrame 存放从AVPacket中解码出来的原始数据,其必须通过
在调用
一个AVFrame用来存放从AVPacket中解码出来的原始数据,这个AVFrame的数据缓存空间通过调avcodec_decode_video分配和填充。
另一个AVFrame用来存放将解码出来的原始数据变换为需要的数据格式(例如RGB,RGBA)的数据,这个AVFrame需要手动的分配数据缓存空间。代码如下:
首先计算需要缓存空间大小,调用
调用
av_malloc和av_free,FFmpeg并没有提供垃圾回收机制,所有的内存管理都要手动进行。
其申请的空间要调用
avformat_open_input 打开多媒体文件流,并读取文件的头,将读取到的信息填充到AVFormatContext结构体中。在使用结束后,要调用
avformat_find_stream_info 上面提到,avformat_open_input只是读取文件的头来得到多媒体文件的信息,但是有些文件没有文件头或者文件头的格式不正确,这就造成只调用
avformat_open_input可能得不到解码所需要的必要信息,需要调用
通过上面的三个函数已经获取了对多媒体文件进行解码的所需要信息,下面要做的就是根据这些信息得到相应的解码器。
结构体
avcodec_find_decoder 可以通过codec_id或者名称来找到相应的解码器,返回值是一个AVCodec的指针。
avcodec_open2 打开相应的编解码器
av_read_frame 从流中读取数据帧暂存到AVPacket中
avcodec_decode_video2 从AVPacket中解码数据到AVFrame中
经过以上的过程,AVFrame中的数据缓存中存放的就是解码后的原始数据了。整个流程梳理如下:
上述代码为初始化后SDL显示图像所需要的环境,在使用FFmpeg解码数据后
上面代码就将解码得到的图像帧使用SDL显示了出来。不过,这里真的只是显示而已,以能够解码速度快速的将整个视频的图像帧显示一遍。
本文的总结分为以下两个部分:
数据读取,主要关注在解码过程中所用到的FFmpeg中的结构体。
解码过程中所调用的函数
在学习的过程主要参考的是dranger tutorial,所以跟着教程在本文的最后使用SDL2.0将解码后的数据输出到屏幕上。
数据的读取
一个多媒体文件包含有多个流(视频流 video stream,音频流 audio stream,字幕等);流是一种抽象的概念,表示一连串的数据元素;流中的数据元素称为帧Frame。也就是说多媒体文件中,主要有两种数据:流Stream 及其数据元素 帧Frame,在FFmpeg自然有与这两种数据相对应的抽象:AVStream和AVPacket。
使用FFmpeg的解码,数据的传递过程可归纳如下:
调用
avformat_open_input打开流,将信息填充到
AVFormatContext中
调用
av_read_frame从流中读取数据帧到
AVPacket,
AVPacket保存仍然是未解码的数据。
调用
avcodec_decode_video2将
AVPacket的数据解码,并将解码后的数据填充到
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:解码数据结构及函数总结
- FFmpeg学习2:解码数据结构及函数总结
- [C学习]总结(从数据类型到函数)
- 数据结构及算法学习总结(一)
- 学习OpenCV 函数方法结构总结
- Redis学习总结_1_底层数据结构
- 数据结构及算法学习总结(一)
- [原]零基础学习视频解码之FFMpeg中比较重要的函数以及数据结构
- MySQL学习总结(四)数据的基本操作以及MySQL运算符和常用函数
- ffmpeg解码数据转为Mat通过opencv函数显示
- 零基础学习视频解码之FFMpeg中比较重要的函数以及数据结构(转)
- python学习总结之数据结构
- 网络设备发送队列相关数据结构及其创建函数 (linux网络子系统学习 第十节 )
- [原]零基础学习视频解码之FFMpeg中比较重要的函数以及数据结构
- 数据结构(严蔚敏版)学习要点总结(一)
- 【ffmpeg学习】利用SDL2.0显示ffmpeg解码出来的数据
- python学习总结之数据结构
- 【学习总结】数据结构-高精度
- 「学习总结-Haskell-3] Haskell常用数据结构
- 【学习总结】数据结构之循环链表和循环链表合并