赞
踩
视频帧图像的格式转换、缩放等处理,主要使用 libswscale库中的API函数完成的
sws_getContext() 根据要输入输出图像的 宽高和 像素格式 创建转换器
sws_scale() 根据输入图像数据进行实际的转换操作,结果输出到输出缓冲区上
sws_freeContext()释放转换器
这几个API函数的功能比较明确,关键是参数的设置,特别是sws_scale()几个参数的设置,具体的参数值依赖于相应的视频帧图像格式。
在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)了
RGBA图像格式在大部分操作系统的GUI Framework和控件中比较常用,在RGBA的存储格式中,每个像素点分别使用R、G、B、A四个通道交错存放。在AVFrame结构体中仅需要用到data[0]字段来指定数据存储区域,linesize[0]通常为 (width*4)
RGBA图像格式的内存布局
在FFMPEG中视频帧格式主要分为 YUV 和 RGB/RGBA 两大系列类型,在这两大类型中又根据不同存储和内存布局有更多的细分,详细可以参考FFMPEG相关的说明文档。
在这两种类型中主要注意的是:
如果格式后面带'P'的表示是 planar存储模式,通常不同的通道数据分别存储在data[0], data[1], data[2], ..... 中
其他的格式是packed存储模式,即:各个通道数据是交错存储在一起,此时数据访问只能通过data[0]来进行偏移计算, data[1], data[2],..... 等都是空的
直接使用 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; }
这里 libswscale 是FFMPEG本身自带的图像格式转换和缩放库,但实际项目中这个库的性能不太高,如果要考虑高性能的图像格式转换,可以考虑使用 libyuv 这个开源库。
★文末名片可以免费领取音视频开发学习资料,内容包括(FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,srs)以及音视频学习路线图等等。
见下方!↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。