如何使用libswscale庫將YUV420P格式的影象序列轉換為RGB24格式輸出?

2023-07-04 12:01:04

一.視訊格式轉換初始化

  將視訊中的影象幀按照一定比例縮放或指定寬高進行放大和縮小是視訊編輯中最為常見的操作之一,這裡我們將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<frame_cnt;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<height;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