(二) FFmpeg解码视频学习

桃扇骨 2022-06-01 01:13 503阅读 0赞

目录

  • 目录
  • 前言
  • 概述
  • 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关键结构体概述

  • 解协议(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结构体详解

  • 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 {

    1. AVMEDIA_TYPE_UNKNOWN = -1, ///< Usually treated as AVMEDIA_TYPE_DATA
    2. AVMEDIA_TYPE_VIDEO,
    3. AVMEDIA_TYPE_AUDIO,
    4. AVMEDIA_TYPE_DATA, ///< Opaque data information usually continuous
    5. AVMEDIA_TYPE_SUBTITLE,
    6. AVMEDIA_TYPE_ATTACHMENT, ///< Opaque data information usually sparse
    7. 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解码器

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

遍历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

    1. uint8_t* data [AV_NUM_DATA_POINTERS]; // 指向图像数据
    2. int linesize [AV_NUM_DATA_POINTERS]; // 行的长度
    3. int width; //图像的宽
    4. int height; //图像的高
    5. int format; //图像格式
    6. ……

    }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结构体指针的图像数据指针赋值,也就是给图像数据指针和指向的内存之间做关联
    avpicturefill((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的函数

  • 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下位图结构解析(二)

  1. bool saveAsBitMap(AVFrame* pFrameRGB, int width, int height, int iFrame)
  2. {
  3. FILE *pFile = NULL;
  4. BITMAPFILEHEADER_t bmpHeader;
  5. BITMAPINFOHEADER_t bmpInfoHeader;
  6. char fileName[32];
  7. int bpp = 24;
  8. // open file
  9. sprintf(fileName, "frame%d.bmp", iFrame);
  10. pFile = fopen(fileName, "wb");
  11. if(!pFile)
  12. {
  13. return false;
  14. }
  15. bmpHeader.bfType = ('M'<<8)|'B'; // 0x4D42
  16. bmpHeader.bfReserved = 0;
  17. bmpHeader.bfOffBits = sizeof(BITMAPFILEHEADER_t)+sizeof(BITMAPINFOHEADER_t);
  18. bmpHeader.bfSize = bmpHeader.bfOffBits + width*height*bpp/8;
  19. bmpInfoHeader.biSize = sizeof(BITMAPINFOHEADER_t);
  20. bmpInfoHeader.biWidth = width;
  21. bmpInfoHeader.biHeight = -height;
  22. bmpInfoHeader.biPlanes = 1;
  23. bmpInfoHeader.biBitCount = bpp;
  24. bmpInfoHeader.biCompression = 0;
  25. bmpInfoHeader.biSizeImage = 0;
  26. bmpInfoHeader.biXPelsPerMeter = 0;
  27. bmpInfoHeader.biYPelsPerMeter = 0;
  28. bmpInfoHeader.biClrUsed = 0;
  29. bmpInfoHeader.biClrImportant = 0;
  30. fwrite(&bmpHeader, sizeof(BITMAPFILEHEADER_t), 1, pFile);
  31. fwrite(&bmpInfoHeader, sizeof(BITMAPINFOHEADER_t), 1, pFile);
  32. uint8_t *buffer = pFrameRGB->data[0];
  33. for(int h=0; h<height; h++)
  34. {
  35. for(int w=0; w<width; w++)
  36. {
  37. fwrite(buffer+2, 1, 1, pFile);
  38. fwrite(buffer+1, 1, 1, pFile);
  39. fwrite(buffer, 1, 1, pFile);
  40. buffer += 3;
  41. }
  42. }
  43. fclose(pFile);
  44. return true;
  45. }

总结

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

链接地址

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

发表评论

表情:
评论列表 (有 0 条评论,503人围观)

还没有评论,来说两句吧...

相关阅读