ffmpeg学习 函数分析sws_scale

有关ffmpeg中主要的api函数源码解析参考雷神系列文章,整理如下ffmpeg学习(2)获取和使用,源码分析。

函数sws_scale位于libswscale库,该库是一个主要用于处理图片像素数据的类库。可以完成图片像素格式的转换,图片的拉伸等工作。libswscale的使用参考文章:

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

最简单的基于FFmpeg的libswscale的示例(YUV转RGB)

本文仅演示的程序将像素格式为YUV420P,分辨率为480x272的视频转换为像素格式为RGB24。另外,还记录将非1字节对齐的AVFrame转换为1字节对齐,便于保存处理。

流程

简单的初始化方法

Libswscale使用起来很方便,最主要的函数只有3个:
(1) sws_getContext():使用参数初始化SwsContext结构体。
(2) sws_scale():转换一帧图像。
(3) sws_freeContext():释放SwsContext结构体。

其中sws_getContext()也可以用另一个接口函数sws_getCachedContext()取代。

复杂但是更灵活的初始化方法

初始化SwsContext除了调用sws_getContext()之外还有另一种方法,更加灵活,可以配置更多的参数。该方法调用的函数如下所示。
(1) sws_alloc_context():为SwsContext结构体分配内存。
(2) av_opt_set_XXX():通过av_opt_set_int()av_opt_set()…等等一系列方法设置SwsContext结构体的值。在这里需要注意,SwsContext结构体的定义看不到,所以不能对其中的成员变量直接进行赋值,必须通过av_opt_set()这类的API才能对其进行赋值。
(3) sws_init_context():初始化SwsContext结构体。

这种复杂的方法可以配置一些sws_getContext()配置不了的参数。比如说设置图像的YUV像素的取值范围是JPEG标准(Y、U、V取值范围都是0-255)还是MPEG标准(Y取值范围是16-235,U、V的取值范围是16-240)。

后面使用函数sws_getContext()sws_scale()完成本文目标。

几个知识点

下文记录几个图像像素数据处理过程中的几个基本知识点:像素格式,图像拉伸,YUV像素取值范围,色域。这里仅说明像素格式,其他可以参考相关博客。

像素格式

像素格式的知识此前已经记录过,不再重复。在这里记录一下FFmpeg支持的像素格式。有几点注意事项:

(1) 所有的像素格式的名称都是以“AV_PIX_FMT_”开头

(2) 像素格式名称后面有“P”的,代表是planar格式,否则就是packed格式。Planar格式不同的分量分别存储在不同的数组中,例如AV_PIX_FMT_YUV420P存储方式如下:

data[0]: Y1, Y2, Y3, Y4, Y5, Y6, Y7, Y8……
data[1]: U1, U2, U3, U4……
data[2]: V1, V2, V3, V4……

Packed格式的数据都存储在同一个数组中,例如AV_PIX_FMT_RGB24存储方式如下:

data[0]: R1, G1, B1, R2, G2, B2, R3, G3, B3, R4, G4, B4……

(3) 像素格式名称后面有“BE”的,代表是Big Endian格式;名称后面有“LE”的,代表是Little Endian格式。

FFmpeg支持的像素格式的定义位于libavutil\pixfmt.h,是一个名称为AVPixelFormat的枚举类型。

FFmpeg有一个专门用于描述像素格式的结构体AVPixFmtDescriptor。该结构体的定义位于libavutil\pixdesc.h。通过av_pix_fmt_desc_get()可以获得指定像素格式的AVPixFmtDescriptor结构体。通过AVPixFmtDescriptor结构体可以获得不同像素格式的一些信息。例如下文中用到了av_get_bits_per_pixel(),通过该函数可以获得指定像素格式每个像素占用的比特数(Bit Per Pixel)

示例程序一

示例程序包含一个输入和一个输出,实现了从输入图像格式(YUV420P)到输出图像格式(RGB24)之间的转换,并且调整了分辨率。

#include 

#ifdef __cplusplus  
extern "C" {
     
#endif  

#include "libswscale/swscale.h"
#include "libavutil/pixdesc.h"
#include "libavutil/frame.h"
#include "libavutil/imgutils.h"

#ifdef __cplusplus  
}
#endif 


int main()
{
     
    //输入数据和参数
    FILE *src_file = fopen("Titanic_640x272_yuv420p.yuv", "rb");
    const int src_w = 640, src_h = 272;
    AVPixelFormat src_pixfmt = AV_PIX_FMT_YUV420P;
    int src_bpp = av_get_bits_per_pixel(av_pix_fmt_desc_get(src_pixfmt));

    FILE *dst_file = fopen("Titanic_640x480_rgb24.rgb", "wb");
    const int dst_w = 640, dst_h = 480;
    AVPixelFormat dst_pixfmt = AV_PIX_FMT_RGB24;
    int dst_bpp = av_get_bits_per_pixel(av_pix_fmt_desc_get(dst_pixfmt));

    //使用AVFrame分配缓存yuv和rgb数据,用于转换
    AVFrame *frame_yuv = av_frame_alloc();
    av_image_alloc(frame_yuv->data, frame_yuv->linesize, src_w, src_h, src_pixfmt, 1);
    
    AVFrame *frame_rgb = av_frame_alloc();
    av_image_alloc(frame_rgb->data, frame_rgb->linesize, dst_w, dst_h, dst_pixfmt, 1);

    // 读取yuv图像数据到frame_yuv缓存
    if(fread(frame_yuv->data[0], 1, src_w*src_h*src_bpp / 8, src_file) != src_w*src_h*src_bpp / 8) {
     
        return 0;
    }

    // 转换
    SwsContext *img_convert_ctx;
    img_convert_ctx = sws_getContext(src_w, src_h, src_pixfmt, 
                                     dst_w,dst_h, dst_pixfmt,
                                     SWS_BILINEAR,
                                     NULL, NULL,NULL);

    sws_scale(img_convert_ctx, 
              frame_yuv->data, frame_yuv->linesize, 0, src_h, 
              frame_rgb->data, frame_rgb->linesize);

    // 保存rgb数据, Packed方式,数据全部保存在 frame_rgb->data[0]中
    fwrite(frame_rgb->data[0], 1, dst_w*dst_h * dst_bpp / 8, dst_file);   // Packed


    // 释放
    sws_freeContext(img_convert_ctx);
    av_frame_free(&frame_yuv);
    av_frame_free(&frame_rgb);
    fclose(src_file);
    fclose(dst_file);
}

结果如下分别打开yuv和rgb文件,显示正常。
ffmpeg学习 函数分析sws_scale_第1张图片

示例程序二

在前面博客 ffmpeg学习(5)视频解码 中,解码出来AVFrame缓存的yuv数据非1字节对齐,并且三个分量保存在不同的内存区域,保存方式相对繁琐。这里再次给出

这里使用libswscale库函数sws_scale将解码后的图像转换为1字节对齐的yuv420p数据。

在源代码中添加 以下变量声明与初始化部分

// yuv420p对齐处理 变量
AVFrame *frame_yuv = av_frame_alloc();
// 分配缓冲区,接收转换后yuv420p的1字节对齐数据,分辨率不改变
frame_yuv->width = video_decoder_ctx->width;
frame_yuv->height = video_decoder_ctx->height;
av_image_alloc(frame_yuv->data, frame_yuv->linesize,
               frame_yuv->width, frame_yuv->height, AV_PIX_FMT_YUV420P, 1);

// SwsContext上下文,用于sws_scale调用
SwsContext *sws_ctx = 
    sws_getContext(video_decoder_ctx->width, video_decoder_ctx->height, video_decoder_ctx->pix_fmt, // 输入格式
                   frame_yuv->width, frame_yuv->height, AV_PIX_FMT_YUV420P,                         // 输出格式
                   SWS_BILINEAR, NULL, NULL, NULL);                                                 // 变换处理

只会在解码后使用如下代码,进行yuv数据处理并保存在文件中

// 解码的视频数据处理
sws_scale(sws_ctx,
          frame->data, frame->linesize, 0, frame->height,
          frame_yuv->data, frame_yuv->linesize);

fwrite(frame_yuv->data[0], 1, frame_yuv->width*frame_yuv->height * 3 / 2, fyuv);
printf("\rSucceed to decode frame %d\n", frameCnt++);

这种方式更为简单。

你可能感兴趣的:(音视频编解码,ffmpeg,视频编解码,libswscale,AVFrame)