(二) FFmpeg解码视频学习

news/2024/7/10 22:14:38 标签: ffmpeg

目录

  • 目录
  • 前言
  • 概述
  • FFmpeg结构体和函数
    • FFmpeg的关键结构体
      • FFmpeg关键结构体概述
      • FFmpeg结构体详解
    • FFmpeg的函数
  • 解码视频代码
  • 总结
  • 链接地址

前言

概述

编码(encode):通过特定的压缩技术,将某个视频的视频流格式转换成另一种视频格式的视频流方式。比如视频:YUV420/422->h264;音频:PCM(原始)->AAC
解码(decode):通过特定的解压缩技术,将某个视频格式的视频流转换成另一种视频格式的视频流方式。
转码(transcode):视频转码技术将视频信号从一种格式转换成另一种格式。比如:视频包括改变分辨率;改变帧率;改变比特率(bir rate)等编码参数;音频包括改变采样率;改变通道数(双通道,单通道);改变位宽(sample format)。
封装(mux):复用,按一定格式组织原始音视频流。
解封装(demux):解复用,按一定格式解析出原始音视频流。
封装可以看做是对原始数据的一些解释,就如同bmp图像数据有文件头一样。
ES流:原始流,直接从编码器出来的数据流。
rtsp流:RTSP(Real Time Streaming Protocol) RFC2326s,实时流传输协议,是TCP/IP协议系中的一个应用层协议。
rtmp流:Real Time Messaging Protocol(实时消息传输协议),是adobe公司的协议
服务端:服务端是为客户端服务的,向客户端提供资源,保存客户端数据。
客户端:也可以称用户端,与服务端相对应,为客户提供本地服务的程序。
流媒体:采用流式传输方式在Internet播放的媒体格式。
推模式:当通知消息来之时,把所有相关信息都通过参数的形式”推给”观察者。就比如微信公众号的推送通知。
拉模式:当通知消息来之时,通知的函数不带任何相关的信息,而是要观察者主动去”拉”信息。就比如我们刷微博。
实时流:Real Time stream 实时传输的音视频流。比如直播。

ffmpeg结构体和函数">FFmpeg结构体和函数

ffmpeg的关键结构体">FFmpeg的关键结构体

ffmpeg关键结构体概述">FFmpeg关键结构体概述

  • 解协议(http,rtsp,rtmp,mms)
    AVIOContextURLContextURLContext主要存储视音频使用的协议类型以及状态。URLProtocol存储输入视音频使用的封装格式。每种协议都对应URLProtocol结构。(注意:FFmpeg中文件也被当作一种协议)
  • 解封装(flv,avi,rmvb,mp4)
    AVFormatContext主要存储视音频封装格式中包含的信息;AVInputFormat存储输入视音频使用的封装格式。每种视音频的封装格式都对应一个AVInputFormat
  • 解码(h264,mpeg2,aac, mp3)
    每个AVStream存储一个视频/音频流的相关数据;每个AVStream对应于AVCodecContext,存储该视频/音频流使用解码方式的相关数据;每个AVCodecContext中对应一个AVCodec,包含该视频/音频对应的解码器。每种解码器都对应一个AVCodec结构。
  • 存数据
    视频的话,每个结构一般是存一帧;音频可能是好几帧。
    解码前数据:AVPacket
    解码后数据:AVFrame
    0
    从图中可以看出AVFormat Context这个结构体包含协议层结构体,封装层结构体,编码层结构体等。总之,这个结构体中贯穿编解码过程。

ffmpeg结构体详解">FFmpeg结构体详解

  • AVFormatContext
    AVFormatContext是FFmpeg解封装(flv,mp4,rmvb,avi)功能的结构体。
struct AVInputFormat *iformat:输入数据的封装格式
AVIOContext* pb:输入数据缓存
unsigned int nb_streams:视音频流的个数
AVStream **streams:视音频流
char filename[1024]:文件名

FFMPEG结构体分析:AVFormatContext

  • AVIOContext
    AVIOContext是FFmpeg管理输入输出数据的结构体。
unsigned char *buffer:缓存开始位置
int buffer_size:缓存大小
unsigned char* buf_ptr:当前指针读取到的位置
unsigned char* buf_end:缓存结束的位置
void *opaque:URLContext结构体,URLContext结构体中有一个结构体URLProtocol.注:每种协议(rtp,rtmp,file等)对应一个URLProtocol.

在解码的情况下,buffer用于存储ffmpeg读入的数据。例如:打开一个视频文件的时候,先把数据从硬盘读入buffer,然后送给解码器用于解码。
FFMPEG结构体分析:AVIOContext

  • AVStream
    AVStream是存储每一个视频/音频流信息的结构体。
int index:标识该视频/音频
AVCodecContex *codec:指向该视频/音频流的AVCodecContext
int64_t duration:该视频/音频流长度

FFMPEG结构体分析:AVStream

  • AVCodecContext
enum AVMeiaType codec_type:编解码器类型(视频、音频)
enum AVMediaType {
    AVMEDIA_TYPE_UNKNOWN = -1,  ///< Usually treated as AVMEDIA_TYPE_DATA
    AVMEDIA_TYPE_VIDEO,
    AVMEDIA_TYPE_AUDIO,
    AVMEDIA_TYPE_DATA,          ///< Opaque data information usually continuous
    AVMEDIA_TYPE_SUBTITLE,
    AVMEDIA_TYPE_ATTACHMENT,    ///< Opaque data information usually sparse
    AVMEDIA_TYPE_NB
};
enum AVMediaType codec_type; // 编码器类型
enum AVSampleFormat sample_fmt; // 采样格式

每个AVStream存储一个视频/音频流的相关数据;每一个AVStream对应一个AVCodecContext,存储该视频/音频流使用解码方式的相关数据;每个AVCodecContext中对应一个AVCodec,包含该视频/音频对应的解码器。每种解码器都对应一个AVCodec结构。
FFMPEG结构体分析:AVCodecContext

  • AVCodec
    AVCodec是视频/音频对应的解码器。
const char* name:编解码器的名字
const char* long_name:编解码其的名字,全称
enum AVMediaType type: 指明类型,视频,音频,字母
enum AVCodecID id:ID
const AVRational *supported_framerates:支持的帧率(仅视频)
const enum AVPixelFormat *pix_fmts:支持的像素格式(仅视频)
const int *supported_samplerates:支持的采样率(仅音频)
const enum AVSampleFormat *sample_fmts:支持的采样格式(仅音频)
const uint64_t *channel_layouts:支持的声道数(仅音频)
int priv_data_size:私有数据的大小

每一个编解码器对应一个该结构体:H.264解码器

AVCodec ff_h264_decoder = {  
    .name           = "h264",  
    .type           = AVMEDIA_TYPE_VIDEO,  
    .id             = CODEC_ID_H264,  
    .priv_data_size = sizeof(H264Context),  
    .init           = ff_h264_decode_init,  
    .close          = ff_h264_decode_end,  
    .decode         = decode_frame,  
    .capabilities   = /*CODEC_CAP_DRAW_HORIZ_BAND |*/ CODEC_CAP_DR1 | CODEC_CAP_DELAY | CODEC_CAP_SLICE_THREADS | CODEC_CAP_FRAME_THREADS,  
    .flush= flush_dpb,  
    .long_name = NULL_IF_CONFIG_SMALL("H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10"),  
    .init_thread_copy      = ONLY_IF_THREADS_ENABLED(decode_init_thread_copy),  
    .update_thread_context = ONLY_IF_THREADS_ENABLED(decode_update_thread_context),  
    .profiles = NULL_IF_CONFIG_SMALL(profiles),  
    .priv_class     = &h264_class,  
};  

遍历ffmpeg中的解码器信息的方法:
1. 注册所有的编解码器:av_register_all();
2. 声明一个AVCodec类型的指针,比如:AVCodec* first_c;
3. 调用av_codec_next()函数,即可获得指向链表下一个解码器的指针,循环往复可以获得所有解码器的信息。注意,如果想要获得指向第一个解码器的指针,则需要将该函数的参数设置为NULL.
FFMPEG结构体分析:AVCodec
- AVPacket
AVPacket是存储压缩编码数据相关信息的结构体。

uint8_t *data:压缩编码的数据
int size:data的大小
int64_t pts:显示时间戳
int64_t dts:解码时间戳
int stream_index:标识该AVPacket所属的视频/音频流

FFMPEG结构体分析:AVPacket

  • AVFrame
    AVFrame结构体一般用于存储原始数据(即非压缩数据,例如对视频来说是YUV/RGB,对音频来说是PCM),此外还包含一些相关信息。比如:解码时存储宏块类型表,QP表,运动矢量表等数据。编码时也存储了相关数据。比如:
typedef struct AVFrame  
{  
#define AV_NUM_DATA_POINTERS 8  
    uint8_t* data [AV_NUM_DATA_POINTERS]; // 指向图像数据   
    int linesize [AV_NUM_DATA_POINTERS];   // 行的长度
    int width; //图像的宽  
    int height; //图像的高  
    int format;  //图像格式  
     ……  
}AVFrame;  
AVFrame* frame = av_frame_alloc();  // 只是分配AVFrame结构体 data指向的内存并没有分配
int bytes_num = avpicture_get_size(AV_PIX_FMT_YUV420P, width, height); // AV_PIX_FMT_YUV420P是FFmpeg定义的标明YUV420P图像格式的宏定义
// 这个函数中会帮我们计算这个格式的图片大小

// 申请空间来存放图片数据。包含源数据和目标数据  
uint8_t* buff = (uint8_t*)av_malloc(bytes_num); 

// 把AVFrame结构体指针的图像数据指针赋值,也就是给图像数据指针和指向的内存之间做关联
avpicture_fill((AVPicture*)frame, buff, AV_PIX_FMT_ YUV420P,width, height); 

uint8_t* data [AV_NUM_DATA_POINTERS];指向图像数据,图像有存储方式有planarpacked两种模式。对于planar模式的YUVdata[0]指向Y分量的开始位置、data[1]指向U分量的开始位置、data[2]指向V分量的开始位置。对于packed模式YUVdata[0]指向数据的开始位置,而data[1]data[2]都为NULL

FFMPEG结构体分析:AVFrame
图像视频编码和FFmpeg(3)—–用FFmpeg进行图像格式转换和AVFrame简介

  • SwsContext
    记录进行图像格式转换时,源图像和目标图像的格式、大小分别是什么。

ffmpeg的函数">FFmpeg的函数

  • av_register_all()
    av_register_all()用于注册复用器,该函数在所有基于ffmpeg的应用程序中几乎都是第一个被调用的。只有调用了该函数,才能使用复用器,编码器等。
  • avformat_open_input()
    avformat_open_input用于打开多媒体数据并且获得一些相关的信息。
int avformat_open_input(AVFormatContext **ps, const char *filename, AVInputFormat *fmt, AVDictionary **options); 
ps: 函数调用之后处理过的`AVFormatContext`结构体
file: 打开的视音频流的URL
fmt:强制指定AVFormatContext中AVInputFormat。这个参数一般情况下可以设置为NULL
dictionary:附加的一些选项,一般情况下可以设置为NULL.

在这个函数中,FFmpeg完成了:
1. 输入输出结构体AVIOContext的初始化
2. 输入数据的协议的识别(例如:RTMP, 或者file)的识别:判断文件名后缀;读取文件头的数据进行比对。
3. 使用获得最高分的文件协议对应的URLProtocol,通过函数指针的方式,与FFmpeg连接。
4. 调用该URLProtocol的函数进行open, read等操作。
- avformat_find_stream_info()
avformat_find_stream_info()可以读取一部分视音频数据并且获得一些相关信息。这个函数主要用于给每个媒体流(音频/视频)的AVStream结构体赋值。据雷神说这个函数已经实现了解码器的查找,解码器的打开,视音频帧的读取,视音频帧的解码等工作。换句话说,该函数实际上已经”走通”了解码的整个流程。

  • avcodec_open2()
    1) 为各种结构体分配内存
    2) 将输入的AVDictionary形式的选项设置为AVCodecContext
    3) 一些其他检查,比如检测编解码其是否处于”实验”阶段
    4) 如果是编码器,检测输入参数是否符合编码器的要求
    5) 调用AVCodecinit()初始化具体的解码器

  • sws_getContext()
    libswscale主要用于处理图片像素数据的类库,可以完成图片像素格式的转换,图片的拉伸等工作。sws_getContext()用于初始化一个SwsContext

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);  
srcW:源图像的宽
srcH:源图像的高
srcFormat:源图像的像素格式
dstW:目标图像的宽
dstH:目标图像的高
dstFormat:目标图像的像素格式
flags:设定图像拉伸使用的算法
  • avcodec_decode_video2()
    avcodec_decode_video2()的作用是实现压缩视频的解码,解码一帧视频数据。输入一个压缩编码的结构体AVPacket,输出一个解码后的结构体AVFrame
int avcodec_decode_video2(AVCodecContext *avctx, AVFrame *picture, int *got_picture_ptr, const AVPacket *avpkt);  

AVCodecContext* avctx:编解码上下文环境,定义了编解码操作的一些细节;
AVFrame* picture:输出参数,传递到该方法的对象本身必须在外部有av_frame_alloc()分配空间。
got_picture_ptr:该值为0表示没有图像可以解码,否则表明有图像可以解码。
avpkt:输入参数,包含待解码数据

FFmpeg源代码简单分析:libswscale的sws_getContext()

解码视频代码

参考这篇从零开始学习音视频编程技术(五) 使用FFMPEG解码视频之保存成图片博文。我直接在Ubuntu下成功运行了这篇博文中运行的代码,但是ppm格式不好打开,正好之前有写过保存成bmp图片的文件,但是写得不是很好,顺便把这一部分整理一下!顺便测试一下解码视频是否成功!附上修改的保存bmp的函数!bmp格式参考 linux下位图结构解析(二)

bool saveAsBitMap(AVFrame* pFrameRGB, int width, int height, int iFrame)
{
    FILE *pFile = NULL;
    BITMAPFILEHEADER_t bmpHeader;
    BITMAPINFOHEADER_t bmpInfoHeader;

    char fileName[32];
    int bpp = 24;

    // open file
    sprintf(fileName, "frame%d.bmp", iFrame);
    pFile = fopen(fileName, "wb");
    if(!pFile)
    {
        return false;
    }

    bmpHeader.bfType = ('M'<<8)|'B';    // 0x4D42
    bmpHeader.bfReserved = 0;
    bmpHeader.bfOffBits = sizeof(BITMAPFILEHEADER_t)+sizeof(BITMAPINFOHEADER_t);
    bmpHeader.bfSize = bmpHeader.bfOffBits + width*height*bpp/8;

    bmpInfoHeader.biSize =  sizeof(BITMAPINFOHEADER_t);
    bmpInfoHeader.biWidth = width;
    bmpInfoHeader.biHeight = -height;
    bmpInfoHeader.biPlanes = 1;
    bmpInfoHeader.biBitCount = bpp;
    bmpInfoHeader.biCompression = 0;
    bmpInfoHeader.biSizeImage = 0;
    bmpInfoHeader.biXPelsPerMeter = 0;
    bmpInfoHeader.biYPelsPerMeter = 0;
    bmpInfoHeader.biClrUsed = 0;
    bmpInfoHeader.biClrImportant = 0;

    fwrite(&bmpHeader, sizeof(BITMAPFILEHEADER_t), 1, pFile);
    fwrite(&bmpInfoHeader, sizeof(BITMAPINFOHEADER_t), 1, pFile);

    uint8_t *buffer = pFrameRGB->data[0];
    for(int h=0; h<height; h++)
    {
        for(int w=0; w<width; w++)
        {
            fwrite(buffer+2, 1, 1, pFile);
            fwrite(buffer+1, 1, 1, pFile);
            fwrite(buffer,   1, 1, pFile);
            buffer += 3;
        }
    }
    fclose(pFile);

    return true;

}

总结

网上各种查资料,就着前辈们的代码,完成了FFmpeg解码视频的学习。虽然只是刚刚跑通了一个小例子。但是也算是个小入门了!在这个时代,资源比以前多很多,感谢大神们的无私奉献!同时也要明白资源好找,别人可以很快达到这个水平,要更加努力才行呢!

链接地址

从零开始学习音视频编程技术(五) 使用FFMPEG解码视频之保存成图片


http://www.niftyadmin.cn/n/1644347.html

相关文章

Linux网络编程学习——TCP/IP基础(一)

目录 目录前言TCPIP基础ISOOSI参考模型TCPIP四层模型基本概念对等通信封装分用端口 总结链接地址 前言 为了把全世界所有不同类型的计算机都连接起来&#xff0c;就必须规定一套全球通用的协议&#xff0c;为了实现互联网这个目标&#xff0c;互联网协议族(Internet Protocol S…

linux下编译GDAL

目录 目录前言编译过程tiff格式图像转换成opencv的Mat格式图像总结链接地址 前言 中间任务&#xff0c;对于这种编译的东西&#xff0c;记录一次&#xff0c;以后就可以沿用&#xff01;所以记录是很有意义的&#xff01; 编译过程 下载地址和下载版本 gdal-2.2.3.tar.gz解…

Win10下VS2013配置FFmpeg

目录 目录前言概述使用dev和shared版本搭建环境下载地址VS2013中配置 编译源码过程下载地址安装过程MinGW安装下载yasmmsysbat配置FFmpeg编译 总结链接地址 前言 虽然最终开发是要在linux下&#xff0c;但是看网上有教程是windows下的&#xff0c;所以顺便记录下windows下的F…

Win10(64位) VS2013配置编译GDAL

目录 目录前言编译过程记录下载地址配置过程 在VS2013中配置引入GDAL结果总结 前言 首先GDAL库在linux平台测试通过了&#xff0c;可是万恶的要转到windows平台&#xff0c;感觉都是坑&#xff0c;刚刚编译了FFmpeg失败&#xff0c;希望现在编译GDAL成功吧&#xff01; 编译过…

Linux网络编程(三)

目录 目录前言socket什么是socket套接字的作用IPv4套接口地址结构字节序主机字节序网络字节序字节序转换函数地址转换函数套接字类型 客户服务器模型CS模型什么是客户端和服务器TCP客户服务器模型回射客户服务器编程接口编程代码 总结链接地址 前言 应用编程接口API和系统调用…

(三) FFmpeg结合Qt实现视频播放器(一)

目录 目录前言SDL的使用SDL概述SDL下载地址SDL的linux下编译SDL的使用 SDL的播放音频播放音频流程SDL结构体SDL的APISDL互斥量和条件变量互斥量条件变量 代码 FFmpeg函数解析结构体函数 总结参考链接 前言 (二) FFmpeg解码视频学习已经完成了Qt实现视频播放器的一部分&#xff…

opencv相似图片搜索

目录 目录前言感知哈希算法ahashahash理论 增强版pHash算法理论部分代码部分 总结链接地址 前言 本文主要想实现的是获取两个文件夹下的所有图片&#xff0c;然后匹配出相近的图片。网上有许多关于图片相似匹配的资料。很多都是java/python的&#xff0c;据说实现起来快捷方便…

Ubuntu16.04下交叉编译ffmpeg

目录 目录前言x264和x265库SDLFFmpeg的编译选项FFmpeg的交叉编译Ubuntu1604下编译FFmpeg 总结参考链接 前言 因为项目有需求&#xff0c;需要用到ffmepg。所以就只能硬着头皮开始新的学习之路&#xff0c;搭建好环境是最基本的了&#xff01; 学习&#xff01;分享&#xff0…