当前位置:   article > 正文

FFMPEG开发快速入坑——视频转换处理_ffmpeg 视频格式转换

ffmpeg 视频格式转换

1、视频格式转换的基本API

视频帧图像的格式转换、缩放等处理,主要使用 libswscale库中的API函数完成的

  1. sws_getContext() 根据要输入输出图像的 宽高和 像素格式 创建转换器

  2. sws_scale() 根据输入图像数据进行实际的转换操作,结果输出到输出缓冲区上

  3. sws_freeContext()释放转换器

这几个API函数的功能比较明确,关键是参数的设置,特别是sws_scale()几个参数的设置,具体的参数值依赖于相应的视频帧图像格式。

2、常用的视频帧图像格式

2.1 YUV420P格式

在YUV420P的存储格式中,每4个像素点占用4个Y、1个U、1个V,Y分量、U分量、V分量的数据分别单独存放,对应AVFrame结构体中字段

typedef struct AVFrame {
  ......
  //
  // 视频帧图像数据 或者 音频帧PCM数据, 根据不同的格式有不同的存放方式
  // 对于视频帧:RGB/RGBA 格式时 data[0] 中一次存放每个像素的RGB/RGBA数据
  //            YUV420 格式时 data[0]存放Y数据;  data[1]存放U数据; data[2]存放V数据
  // 对于音频帧: data[0]存放左声道数据;  data[1]存放右声道数据
  //
  uint8_t *data[AV_NUM_DATA_POINTERS];  
  
  //
  // 行字节跨度, 相当于stride
  // 对于视频帧: 上下两行同一列像素相差的字节数,例如:对于RGBA通常是(width*4), 但是有时FFMPEG内部会有扩展, 可能会比这个值大
  // 对于音频帧: 单个通道中所有采样占用的字节数
  //
  int linesize[AV_NUM_DATA_POINTERS];
  
  int format;         // 对于视频帧是图像格式; 对于音频帧是采样格式  
  int64_t pts;        // 当前数据帧的时间戳  
  int width, height;  // 仅用于视频帧, 宽度高度
  int key_frame;      // 仅用于视频, 当前是否是I帧
  ......
}

以一张 720*1280的视频帧图像为例,AVFrame中的各个字段值:

  uint8_t* yuvData[4]  = {nullptr};
  int      linesize[4] = {0};
  av_image_alloc(yuvData, linesize, 720, 1280, AV_PIX_FMT_YUV420P, 1); // 分配内存空间
  // 将 Y & U & V 数据分别填充到 yuvData[0] yuvData[1] yuvData[2] 中
​
  AVFrame* pFrame = av_frame_alloc();
  pFrame->format  = AV_PIX_FMT_YUV420P;
  pFrame->width   = 720;
  pFrame->height  = 1280;
  pFrame->data[0] = yuvData[0];
  pFrame->data[1] = yuvData[1];
  pFrame->data[2] = yuvData[2];
  pFrame->linesize[0] = linesize[0];  // Y数据行长字节数, 720
  pFrame->linesize[1] = linesize[1];  // U数据行长字节数, 360
  pFrame->linesize[2] = linesize[1];  // V数据行长字节数, 360
 ......

这里的linesize[]的值需要特别注意一下,这个地方可能会让很多开发者疑惑:

既然linesize[]表示一个通道中一行像素数据的字节数,那么直接根据通道类型计算就可以了(例如:对于Y通道来说,图像宽度720,那么一行像素的Y数据就固定是720个字节),为什么要单独使用一个字段表示? 。

具体原因:这里会涉及到一个内存管理的处理,如果AVFrame的缓冲区是开发者自己创建的,上面的lineSize[]的确可以自己计算。 但是在FFMPEG内部编解码器处理时需要处理图像边界等问题,很多时候为了方便优化处理,通常内部创建的图像缓冲区要比输出的原始图像要大一些,而输出的图像内容只是出于缓冲区中的一部分,此时要输出图像要么创建一个新的缓冲区做一次图像拷贝,要么直接将这个缓冲区输出,避免一次拷贝,FFMPEG就是采用后面一种方式少了一次拷贝。

例如:.mp4文件中视频帧原始图像大小是 720*1280,解码器内部实际为了解码优化,实际创建的图像缓冲区可能是768*1328大小,在调用 avcodec_receive_frame()获取到的解码后的视频帧数据 pFrame中 data[0] 地址是通过pCacheBuffer直接地址偏移得到,同样 linesize[0] = 768; linsize[1] = 384; linesize[2] = 384; 这样通过data[0] 找到下一行第一个像素的Y数据就是 (data[0]+linsize[0]) 而不是直接 (data[0]+720)了

2.2RGBA格式

RGBA图像格式在大部分操作系统的GUI Framework和控件中比较常用,在RGBA的存储格式中,每个像素点分别使用R、G、B、A四个通道交错存放。在AVFrame结构体中仅需要用到data[0]字段来指定数据存储区域,linesize[0]通常为 (width*4)

RGBA图像格式的内存布局

2.3其他视频图像格式

在FFMPEG中视频帧格式主要分为 YUV 和 RGB/RGBA 两大系列类型,在这两大类型中又根据不同存储和内存布局有更多的细分,详细可以参考FFMPEG相关的说明文档。

在这两种类型中主要注意的是:

如果格式后面带'P'的表示是 planar存储模式,通常不同的通道数据分别存储在data[0], data[1], data[2], ..... 中

其他的格式是packed存储模式,即:各个通道数据是交错存储在一起,此时数据访问只能通过data[0]来进行偏移计算, data[1], data[2],..... 等都是空的

3、转换示例代码

直接使用 libswscale库进行转换

//
// 视频帧格式转换
//
int32_t VideoConvert(
  const AVFrame*  pInFrame,           // 输入视频帧  
  AVPixelFormat   eOutFormat,         // 输出视频格式
  int32_t         nOutWidth,          // 输出视频宽度
  int32_t         nOutHeight,         // 输出视频高度
  AVFrame**       ppOutFrame    )     // 输出视频帧
{
  struct SwsContext* pSwsCtx = nullptr;
  AVFrame*           pOutFrame = nullptr;
​
  // 创建格式转换器, 指定缩放算法,转换过程中不增加任何滤镜特效处理
  pSwsCtx = sws_getContext(pInFrame->width, pInFrame->height, (AVPixelFormat)pInFrame->format,
                            nOutWidth, nOutHeight, eOutFormat,
                            SWS_BICUBIC,  nullptr,  nullptr, nullptr);
  if (pSwsCtx == nullptr)
  {
    LOGE("<VideoConvert> [ERROR] fail to sws_getContext()\n");
    return -1;
  }
​
​
  // 创建输出视频帧对象以及分配相应的缓冲区
  uint8_t* data[4]     = {nullptr};
  int      linesize[4] = {0};
  int res = av_image_alloc(data, linesize, nOutWidth, nOutHeight, eOutFormat, 1);
  if (res < 0)
  {
    LOGE("<VideoConvert> [ERROR] fail to av_image_alloc(), res=%d\n", res);
    sws_freeContext(pSwsCtx);
    return -2;
  }
  pOutFrame                = av_frame_alloc();
  pOutFrame->format        = eOutFormat;
  pOutFrame->width         = nOutWidth;
  pOutFrame->height        = nOutHeight;
  pOutFrame->data[0]       = data[0];
  pOutFrame->data[1]       = data[1];
  pOutFrame->data[2]       = data[2];
  pOutFrame->data[3]       = data[3];
  pOutFrame->linesize[0]   = linesize[0];
  pOutFrame->linesize[1]   = linesize[1];
  pOutFrame->linesize[2]   = linesize[2];
  pOutFrame->linesize[3]   = linesize[3];
​
  // 进行格式转换处理
  res = sws_scale(pSwsCtx,
                  static_cast<const uint8_t* const*>(pInFrame->data),
                  pInFrame->linesize,
                  0,
                  pOutFrame->height,
                  pOutFrame->data,
                  pOutFrame->linesize);
  if (res < 0)
  {
    LOGE("<VideoConvert> [ERROR] fail to sws_scale(), res=%d\n", res);
    sws_freeContext(pSwsCtx);
    av_frame_free(&pOutFrame);
    return -3;
  }
​
  (*ppOutFrame) = pOutFrame;
  sws_freeContext(pSwsCtx);  // 释放转换器
  return 0;
}

4、其他格式转换库

这里 libswscale 是FFMPEG本身自带的图像格式转换和缩放库,但实际项目中这个库的性能不太高,如果要考虑高性能的图像格式转换,可以考虑使用 libyuv 这个开源库。

原文FFMPEG开发快速入坑——视频转换处理 - 知乎

★文末名片可以免费领取音视频开发学习资料,内容包括(FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,srs)以及音视频学习路线图等等。

见下方!↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/126185
推荐阅读
相关标签
  

闽ICP备14008679号