• 如何使用libswscale库将YUV420P格式的图像序列转换为RGB24格式输出?


    一.视频格式转换初始化

      将视频中的图像帧按照一定比例缩放或指定宽高进行放大和缩小是视频编辑中最为常见的操作之一,这里我们将1920x1080的yuv图像序列转换成640x480的rgb图像序列,并输出到文件。视频图像转换的核心为一个SwsContext结构,其中保存了输入图像和输出图像的宽高以及像素格式等多种参数。我们通过调用sws_getContext()函数就可以十分方便地创建并获取SwsContext结构的实例。下面给出初始化的代码:

    复制代码
    //video_swscale_core.cpp
    static AVFrame *input_frame= nullptr;
    static struct SwsContext *sws_ctx;
    static int32_t src_width=0,src_height=0,dst_width=0,dst_height=0;
    static enum AVPixelFormat src_pix_fmt=AV_PIX_FMT_NONE,dst_pix_fmt=AV_PIX_FMT_NONE;
    int32_t init_video_swscale(const char *src_size,const char *src_fmt,const char *dst_size,const char *dst_fmt){
        int32_t result=0;
        result=av_parse_video_size(&src_width,&src_height,src_size);
        if(result<0){
            cerr<<"Error:av_parse_video_size failed."<<endl;
            return -1;
        }
        result= av_parse_video_size(&dst_width,&dst_height,dst_size);
        if(result<0){
            cerr<<"Error:av_parse_video_size failed."<<endl;
            return -1;
        }
        //选择输入视频和输出视频的图像格式
        if(!strcasecmp(src_fmt,"YUV420P")){
            src_pix_fmt=AV_PIX_FMT_YUV420P;
        }
        else if(!strcasecmp(src_fmt,"RGB24")){
            src_pix_fmt=AV_PIX_FMT_RGB24;
        }
        else{
            cerr<<"Error:Unsupported input pixel format."<<endl;
            return -1;
        }
        if(!strcasecmp(dst_fmt,"YUV420P")){
            dst_pix_fmt=AV_PIX_FMT_YUV420P;
        }
        else if(!strcasecmp(dst_fmt,"RGB24")){
            dst_pix_fmt=AV_PIX_FMT_RGB24;
        }
        else{
            cerr<<"Error:Unsupported output pixel format."<<endl;
            return -1;
        }
        //获取SwsContext结构
        sws_ctx=sws_getContext(src_width,src_height,src_pix_fmt,dst_width,dst_height,dst_pix_fmt,SWS_BILINEAR, nullptr,
                               nullptr, nullptr);
        if(!sws_ctx){
            cerr<<"Error:failed to get SwsContext."<<endl;
            return -1;
        }
        //初始化AVFrame结构
        result= init_frame(src_width,src_height,src_pix_fmt);
        if(result<0){
            cerr<<"Error:init_frame failed."<<endl;
            return -1;
        }
        return 0;
    }
    复制代码

      初始化保存输入视频的AVFrame结构,并分配内存空间:

    复制代码
    //video_swscale_core.cpp
    static int32_t init_frame(int32_t width,int32_t height,enum AVPixelFormat pix_fmt){
        int result=0;
        input_frame=av_frame_alloc();
        if(!input_frame){
            cerr<<"Error:av_frame_alloc failed."<<endl;
            return -1;
        }
        input_frame->width=width;
        input_frame->height=height;
        input_frame->format=pix_fmt;
        result= av_frame_get_buffer(input_frame,0);
        if(result<0){
            cerr<<"Error:av_frame_get_buffer failed."<<endl;
            return -1;
        }
        result= av_frame_make_writable(input_frame);
        if(result<0){
            cerr<<"Error:av_frame_make_writable failed."<<endl;
            return -1;
        }
        return 0;
    }
    复制代码

    二.视频图像帧的循环转换

      视频格式转换的核心函数是sws_scale(),我们需要给出输出图像的缓存地址和缓存宽度,然后循环处理即可。下面给出代码:

    复制代码
    //video_swscale_core.cpp
    int32_t transforming(int32_t frame_cnt){
        int32_t result=0;
        uint8_t *dst_data[4];
        int32_t dst_linesize[4]={0},dst_bufsize=0;
        result= av_image_alloc(dst_data,dst_linesize,dst_width,dst_height,dst_pix_fmt,1);
        if(result<0){
            cerr<<"Error:av_image_alloc failed."<<endl;
            return -1;
        }
        dst_bufsize=result;
        for(int i=0;i){
            result= read_yuv_to_frame(input_frame);
            if(result<0){
                cerr<<"Error:read_yuv_to_frame failed."<<endl;
                return -1;
            }
            sws_scale(sws_ctx,input_frame->data,input_frame->linesize,0,src_height,dst_data,dst_linesize);
            //write_packed_data_to_file(dst_data[0],dst_bufsize);
            write_packed_data_to_file2(dst_data[0],dst_linesize[0],dst_width,dst_height);
        }
        av_freep(&dst_data[0]);
        return 0;
    }
    复制代码

    三.将转换后的图像帧写入输出文件

      这里需要注意的是,由于我们转换后的图像格式是rgb24,是按packed方式存储的,也就是红绿蓝三个通道交错地存储在一个平面内,在内存中是连续存储的。也就是说,转换后的图像数据全部保存在dst_data[0]指向的内存空间中。下面给出代码:

    复制代码
    //io_data.cpp
    int32_t write_packed_data_to_file2(uint8_t *data,int32_t linesize,int32_t width,int32_t height){
        for(int i=0;i){
            fwrite(data+i*linesize,1,width*3,output_file);
        }
    }
    复制代码

    四.释放资源

    void destroy_video_swscale(){
        av_frame_free(&input_frame);
        sws_freeContext(sws_ctx);
    }

      还有其他的文件打开和关闭以及将yuv图像读到AVFrame结构中的代码请看我之前的博客。

    五.main函数实现

    复制代码
    int main(){
        int result=0;
        const char *input_file_name="../input.yuv";
        const char *input_pic_size="1920x1080";
        const char *input_pix_fmt="YUV420P";
        const char *output_file_name="../output.rgb";
        const char *output_pic_size="640x480";
        const char *output_pix_fmt="RGB24";
        result= open_input_output_files(input_file_name,output_file_name);
        if(result<0){
            return -1;
        }
        result=init_video_swscale(input_pic_size,input_pix_fmt,output_pic_size,output_pix_fmt);
        if(result<0){
            return -1;
        }
        result=transforming(250);
        if(result<0){
            return -1;
        }
        destroy_video_swscale();
        close_input_output_files();
        return 0;
    }
    复制代码

      最后,可以用以下指令测试输出的output.rgb文件:

      ffplay -f rawvideo -video_size 640x480 -pixel_format rgb24 -i output.rgb

      

      

  • 相关阅读:
    我的安卓AOSP开发使用到的教程汇总【安卓12】
    选择篇(066)-下面代码的输出是什么?
    Linux中查找最大文件的命令是什么?
    【微处理器】基于FPGA的微处理器VHDL开发
    Linux中提高效率的环境配置
    计算机中进制相互转换(二进制、八进制、十进制和十六进制)
    使用 Flutter 和 Firebase 制作!计数器应用程序
    YUM源的几种常见方式
    反向营销,一本难念的经
    Day41 JMeter实战
  • 原文地址:https://www.cnblogs.com/luqman/p/swscale.html