ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

图像视频编码和FFmpeg(3)-----用FFmpeg进行图像格式转换和AVFrame简介_luotuo44的专栏-程序员资料_avframe格式转换

2021-11-04 18:35:06  阅读:181  来源: 互联网

标签:src 图像格式 转换 FFmpeg linesize frame AVFrame data dst


上一篇介绍了YUV格式,并给出了一个YUYV422转RGB24的例子。其实,FFmpeg有一个函数专门进行图像格式转换的。本文就介绍怎么用FFmpeg转换,因为在转换时还要用到AVFrame这个结构体,所以这里也会介绍AVFrame。在FFmpeg中,AVFrame是一个比较重要的结构体。

        AVFrame,顾名思义,这个结构体应该是保存视频帧的信息的。像一帧图像也是可以保存在AVFrame结构中。事实上,我们可以直接从一个YUV文件中,把一张YUV图像数据读到AVFrame中。本文后面的例子也是这样做的。

        为了弄懂AVFrame是怎么存放一张YUV图像的(当然AVFrame可以存放其他格式图像的),现在先看一下AVFrame结构体的主要成员。

 

typedef struct AVFrame
{
#define AV_NUM_DATA_POINTERS 8
    uint8_t * 	data [AV_NUM_DATA_POINTERS]; //指向图像数据

    int linesize [AV_NUM_DATA_POINTERS]; //行的长度

    int width; //图像的宽
    int height; //图像的高
    int format;  //图像格式
	 ……
}AVFrame;

 

        注意到data成员是一个指针数组。其指向的内容就是图像的实际数据。

        可以用av_frame_alloc(void)函数来分配一个AVFrame结构体。这个函数只是分配AVFrame结构体,但data指向的内存并没有分配,需要我们指定。这个内存的大小就是一张特定格式图像所需的大小,如前一篇博文中说到的,对于YUYV422格式,所需的大小是width * height * 2。所以AVFrame结构体的整个初始化过程如下:

 

AVFrame* frame = av_frame_alloc();

//这里FFmpeg会帮我们计算这个格式的图片,需要多少字节来存储
//相当于前一篇博文例子中的width * height * 2
int bytes_num = avpicture_get_size(AV_PIX_FMT_YUV420P, width, height); //AV_PIX_FMT_YUV420P是FFmpeg定义的标明YUV420P图像格式的宏定义

//申请空间来存放图片数据。包含源数据和目标数据
uint8_t* buff = (uint8_t*)av_malloc(bytes_num);

//前面的av_frame_alloc函数,只是为这个AVFrame结构体分配了内存,
//而该类型的指针指向的内存还没分配。这里把av_malloc得到的内存和AVFrame关联起来。
//当然,其还会设置AVFrame的其他成员
avpicture_fill((AVPicture*)frame, buff, AV_PIX_FMT_ YUV420P,width, height);
<iframe allowfullscreen="allowfullscreen" data-google-container-id="a!2" data-google-query-id="CI6I3a68_vMCFYi5vAod624KmQ" data-load-complete="true" data-mce-fragment="1" frameborder="0" height="280" id="aswift_1" marginheight="0" marginwidth="0" name="aswift_1" sandbox="allow-forms allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-top-navigation-by-user-activation" scrolling="no" src="https://googleads.g.doubleclick.net/pagead/ads?gdpr=0&client=ca-pub-8705509105161355&output=html&h=280&adk=2231597620&adf=3624322323&pi=t.aa~a.2320284503~i.19~rp.4&w=740&fwrn=4&fwrnh=100&lmt=1636020787&num_ads=1&rafmt=1&armr=3&sem=mc&pwprc=3717380529&psa=1&ad_type=text_image&format=740x280&url=http%3A%2F%2Fwww.4k8k.xyz%2Farticle%2Fluotuo44%2F26486877&flash=0&fwr=0&pra=3&rh=185&rw=740&rpe=1&resp_fmts=3&wgl=1&fa=27&adsid=ChEI8ImOjAYQvqXu04DO7JjMARI9AFh_avcvf6CVpqiB9gjYPFx_RCB8CSZmuk4hWh-QOw1oH3SIKF5xAPyOX1rzNxcubxzBCoJSfADU0Ew10w&dt=1636020787668&bpp=3&bdt=1065&idt=-M&shv=r20211101&mjsv=m202111020101&ptt=9&saldr=aa&abxe=1&cookie=ID%3Dbb47066b209591f4-2239ef8f94ce0050%3AT%3D1634199490%3ART%3D1636020785%3AS%3DALNI_MZ0dKhcqVj2gZuM3By-SoV_8kpiPQ&prev_fmts=0x0&nras=2&correlator=7904063770189&frm=20&pv=1&ga_vid=638895556.1634199491&ga_sid=1636020787&ga_hid=1313816354&ga_fc=1&u_tz=480&u_his=1&u_h=1080&u_w=1920&u_ah=1040&u_aw=1920&u_cd=24&adx=387&ady=1267&biw=1903&bih=888&scr_x=0&scr_y=0&eid=31062937%2C31063294%2C31063354%2C31063412&oid=2&pvsid=2016631947340685&pem=19&ref=https%3A%2F%2Fwww.google.com%2F&eae=0&fc=1408&brdim=0%2C0%2C0%2C0%2C1920%2C0%2C1920%2C1040%2C1920%2C888&vis=1&rsz=%7C%7Cs%7C&abl=NS&fu=128&bc=23&jar=2021-11-04-10&ifi=2&uci=a!2&btvi=1&fsb=1&xpc=klcMCIwwQH&p=http%3A//www.4k8k.xyz&dtd=110" width="740"></iframe>

 

        看到这里,可能有些读者会疑问:data成员是一个指针数组(即数组里面的每一个元素都是一个指针),一个buff怎么够用(多对一的关系)。其实,这就是FFmpeg设计的一个巧妙之处。还记得前一篇博文说到的 图像物理存储有 planar和packed两种模式吗?这个data指针数组就是为了planar设计的。对于planar模式的YUV。data[0]指向Y分量的开始位置、data[1]指向U分量的开始位置、data[2]指向V分量的开始位置。对于packed模式YUV,data[0]指向数据的开始位置,而data[1]和data[2]都为NULL。

<iframe allowfullscreen="allowfullscreen" data-google-container-id="a!3" data-google-query-id="CKm63a68_vMCFZFGvAodAjYPUg" data-load-complete="true" data-mce-fragment="1" frameborder="0" height="280" id="aswift_2" marginheight="0" marginwidth="0" name="aswift_2" sandbox="allow-forms allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-top-navigation-by-user-activation" scrolling="no" src="https://googleads.g.doubleclick.net/pagead/ads?gdpr=0&client=ca-pub-8705509105161355&output=html&h=280&adk=2231597620&adf=1736798824&pi=t.aa~a.2320284503~i.25~rp.4&w=740&fwrn=4&fwrnh=100&lmt=1636020787&num_ads=1&rafmt=1&armr=3&sem=mc&pwprc=3717380529&psa=1&ad_type=text_image&format=740x280&url=http%3A%2F%2Fwww.4k8k.xyz%2Farticle%2Fluotuo44%2F26486877&flash=0&fwr=0&pra=3&rh=185&rw=740&rpe=1&resp_fmts=3&wgl=1&fa=27&adsid=ChEI8ImOjAYQvqXu04DO7JjMARI9AFh_avcvf6CVpqiB9gjYPFx_RCB8CSZmuk4hWh-QOw1oH3SIKF5xAPyOX1rzNxcubxzBCoJSfADU0Ew10w&dt=1636020787668&bpp=1&bdt=1064&idt=-M&shv=r20211101&mjsv=m202111020101&ptt=9&saldr=aa&abxe=1&cookie=ID%3Dbb47066b209591f4-2239ef8f94ce0050%3AT%3D1634199490%3ART%3D1636020785%3AS%3DALNI_MZ0dKhcqVj2gZuM3By-SoV_8kpiPQ&prev_fmts=0x0%2C740x280&nras=3&correlator=7904063770189&frm=20&pv=1&ga_vid=638895556.1634199491&ga_sid=1636020787&ga_hid=1313816354&ga_fc=1&u_tz=480&u_his=1&u_h=1080&u_w=1920&u_ah=1040&u_aw=1920&u_cd=24&adx=387&ady=1747&biw=1903&bih=888&scr_x=0&scr_y=0&eid=31062937%2C31063294%2C31063354%2C31063412&oid=2&pvsid=2016631947340685&pem=19&ref=https%3A%2F%2Fwww.google.com%2F&eae=0&fc=1408&brdim=0%2C0%2C0%2C0%2C1920%2C0%2C1920%2C1040%2C1920%2C888&vis=1&rsz=%7C%7Cs%7C&abl=NS&fu=128&bc=23&jar=2021-11-04-10&ifi=3&uci=a!3&btvi=2&fsb=1&xpc=dRDyrDz9qG&p=http%3A//www.4k8k.xyz&dtd=116" width="740"></iframe>

        在上面的代码中,运行avpicture_fill后,data[0]将指向buff的开始位置,即data[0]等于buff。data[1]指向buff数组的某一个位置(该位置为U分量的开始处),data[2]也指向buff数组某一个位置(该位置为V分量的开始处)。

        有些网友说到,对于planar模式,需要分开读取和写的。其实,无论是planar还是packed模式,在用acpicture_fill函数处理后,都可以用下面的方法把一张图像的数据读取到AVFrame中,而不需要分别读data[0]、data[1]、data[2]。因为对于图像文件来说,如果是plannar模式的图像格式,其存储必然是先存完一张图像所有的所有Y、紧接着再存一张图像的所有U、紧接着存一张图像的所有V。这刚好和data数组的三个指针的对应的。

 

fread(frame->data[0], 1, bytes_num, fin);
        同样对于写图像也是如此。无需分data[0]、data[1]、data[2]。

 

        扯了这么多,还没说FFmpeg是怎么转换图像格式的。现在来说一下。

        FFmpeg定义了一个结构体SwsContext,它记录进行图像格式转换时,源图像和目标图像的格式、大小分别是什么。然后用sws_scale函数直接转换即可。过程如下:

 

SwsContext* sws_ctx = sws_getContext(src_width, src_height,
                                     AV_PIX_FMT_YUV420P,
                                     dst_width, dst_height,
                                     AV_PIX_FMT_YUYV422,
                                     SWS_BICUBIC,
                                     NULL,
                                     NULL,
                                     NULL);

sws_scale(sws_ctx, src_frame->data, src_frame->linesize,
          0, height, //源图像的高
          dst_frame->data, dst_frame->linesize);

 

        下面给出完整的转换例子。该例子将YUV420P转换成YUYV422,并写入一个文件中。

 

#ifdef __cplusplus
 #define __STDC_CONSTANT_MACROS
 #ifdef _STDINT_H
  #undef _STDINT_H
 #endif
 # include <stdint.h>
#endif

extern "C"
{
#include<libavcodec/avcodec.h>
#include<libavformat/avformat.h>
#include<libavutil/log.h>
#include<libswscale/swscale.h>
}

#include<stdio.h>

#include <windows.h> //for saveAsBitmap

bool saveAsBitmap(AVFrame *pFrameRGB, int width, int height, int iFrame)
{
      FILE *pFile = NULL;
      BITMAPFILEHEADER bmpheader;
      BITMAPINFO bmpinfo;

      char fileName[32];
      int bpp = 24;

      // open file
      sprintf(fileName, "frame%d.bmp", iFrame);
      pFile = fopen(fileName, "wb");
      if (!pFile)
            return false;

      bmpheader.bfType = ('M' <<8)|'B';
      bmpheader.bfReserved1 = 0;
      bmpheader.bfReserved2 = 0;
      bmpheader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
      bmpheader.bfSize = bmpheader.bfOffBits + width*height*bpp/8;

      bmpinfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
      bmpinfo.bmiHeader.biWidth = width;
      bmpinfo.bmiHeader.biHeight = -height; //reverse the image
      bmpinfo.bmiHeader.biPlanes = 1;
      bmpinfo.bmiHeader.biBitCount = bpp;
      bmpinfo.bmiHeader.biCompression = BI_RGB;
      bmpinfo.bmiHeader.biSizeImage = 0;
      bmpinfo.bmiHeader.biXPelsPerMeter = 100;
      bmpinfo.bmiHeader.biYPelsPerMeter = 100;
      bmpinfo.bmiHeader.biClrUsed = 0;
      bmpinfo.bmiHeader.biClrImportant = 0;

      fwrite(&bmpheader, sizeof(BITMAPFILEHEADER), 1, pFile);
      fwrite(&bmpinfo.bmiHeader, sizeof(BITMAPINFOHEADER), 1, pFile);
      uint8_t *buffer = pFrameRGB->data[0];
      for (int h=0; h<height; h++)
      {
            for (int w=0; w<width; w++)
            {
                  fwrite(buffer+2, 1, 1, pFile);
                  fwrite(buffer+1, 1, 1, pFile);
                  fwrite(buffer, 1, 1, pFile);

                  buffer += 3;
            }
      }
      fclose(pFile);

      return true;
}

int main(int argc, char** argv)
{
    const char* filename = argc > 1 ? argv[1] : "flower_cif.yuv";

    FILE* fin = fopen(filename, "rb");
    if( fin == NULL )
    {
        printf("can't open the file\n");
        return -1;
    }

    int width = 352;
    int height = 288;

    AVPixelFormat src_fmt = AV_PIX_FMT_YUV420P;
    AVPixelFormat dst_fmt = AV_PIX_FMT_YUYV422;


    AVFrame* src_frame = av_frame_alloc();
    AVFrame* dst_frame = av_frame_alloc();
    if( src_frame == NULL || dst_frame == NULL )
    {
        printf("av_frame_alloc fail\n");
        return -1;
    }

    //这里FFmpeg会帮我们计算这个格式的图片,需要多少字节来存储
    //相当于前面例子中的width * height * 2
    int src_bytes_num = avpicture_get_size(src_fmt,
                                           width, height);
    int dst_bytes_num = avpicture_get_size(dst_fmt,
                                           width, height);

    //申请空间来存放图片数据。包含源数据和目标数据
    uint8_t* src_buff = (uint8_t*)av_malloc(src_bytes_num);
    uint8_t* dst_buff = (uint8_t*)av_malloc(dst_bytes_num);

    //前面的av_frame_alloc函数,只是为这个AVFrame结构体分配了内存,
    //而该类型的指针指向的内存还没分配。这里把av_malloc得到的内存和AVFrame关联起来。
    //当然,其还会设置AVFrame的其他成员
    avpicture_fill((AVPicture*)src_frame, src_buff, src_fmt,
                   width, height);

    avpicture_fill((AVPicture*)dst_frame, dst_buff, dst_fmt,
                   width, height);


    //这里主要说明linesize这个成员的含义。不想看可以忽略
    //YUV格式中有一个很重要的等量关系,那就是有多少个像素就有多少个y。
    //linesize正如其名,一条线(即一行)的大小。对于yuv420p。data[0]存放的是y,对应地linesize[0]就
    //指明一行有多少个y。对于352*288的图像,一行有352个像素。根据刚才的等量关系。那么linesize[0]就
    //应该为352.即一行有352个y。对于linesize[1],因为data[1]存放的是u。而一行352个像素在yuv420p格式中,
    //其只需352/2,即176个。所以linesize[1]的大小为176。同理linesize[2]也为176。

    //而对于yuyv422格式。data[0]这一行要负责存放y、u、v这三个分量。而y:u:v = 2:1:1的关系。根据前面所说的
    //等量关系,y等于352(相对于352*288大小的图像来说),u和v都等于352/2 。所以u+v等于352。所以linesize[0]
    //等于352*2.
    printf("%d %d %d\n", src_frame->linesize[0],
            src_frame->linesize[1], src_frame->linesize[2]);
    printf("%d %d %d \n", dst_frame->linesize[0],
            dst_frame->linesize[1], dst_frame->linesize[2]);


    //对转换进行配置。这里要设置转换源的大小、格式和转换目标的大小、格式
    //设置后,下面就可以直接使用sws_scale函数,进行转换
    SwsContext* sws_ctx = sws_getContext(width, height,
                                         src_fmt,
                                         width, height,
                                         dst_fmt,
                                         SWS_BICUBIC,
                                         //SWS_BILINEAR,
                                         NULL,
                                         NULL,
                                         NULL);

    if( sws_ctx == NULL)
    {
        printf("sws_getContext fail ");
        return -1;
    }


    FILE* fout = fopen("yuyv422.yuv", "wb");
    int count = 0;

    while( 1 )
    {
        int ret = fread(src_frame->data[0], 1, src_bytes_num, fin);
        if( ret != src_bytes_num )
        {
            printf("don't read enough data %d\n", ret);
            break;
        }

        sws_scale(sws_ctx, src_frame->data, src_frame->linesize,
                  0, height,
                  dst_frame->data, dst_frame->linesize);


        ret = fwrite(dst_frame->data[0], 1, dst_bytes_num, fout);
        if( ret != dst_bytes_num )
            printf("don't write enough data %d \n", ret);


        //如果要保存为BMP格式,要把目标图像的格式设置为RGB24。
        //只需把前面的AVPixelFormat dst_fmt = AV_PIX_FMT_YUYV422;
        //改成AVPixelFormat dst_fmt = AV_PIX_FMT_RGB24;即可
        saveAsBitmap(dst_frame, width, height, count++);
    }


    av_free(src_frame);
    av_free(dst_frame);
    av_free(src_buff);
    av_free(dst_buff);

    sws_freeContext(sws_ctx);


    fclose(fin);
    fclose(fout);

    return 0;
}

        例子中,还可以把图像保存成bmp图片。那个函数来自:http://blog.csdn.net/ajaxhe/article/details/7340508

      例子中用到的YUV420P格式的文件,可以到这里下载。

参考:

 

https://blog.csdn.net/luotuo44/article/details/26486877

标签:src,图像格式,转换,FFmpeg,linesize,frame,AVFrame,data,dst
来源: https://www.cnblogs.com/lidabo/p/15509517.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有