FFmpeg编程FFmpeg中级开发

Posted 贺二公子

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了FFmpeg编程FFmpeg中级开发相关的知识,希望对你有一定的参考价值。

原味地址:https://www.cnblogs.com/ssyfj/p/14722272.html


文章目录


一:H264解码处理

(一)解码步骤

1.引入解码头文件

#include <libavcodec/avcodec.h>

2.常用数据结构

  • AVCodec编码器结构体:所使用的编码器类型,(H264/H265,音频/视频)
  • AVCodecContext编码器上下文:串联各个API,形成API链条,每个API都需要我们把上下文作为参数传入该API。内部也保存了编解码器信息,可以供其调用
  • AVFrame解码后的帧:未压缩的帧(未编码)

3.结构体内存的分配和释放

4.解码步骤

  • avcodec_find_decoder通过id查找解码器,当然也可以通过名字by_name查找到编解码器(如下面的编码);两种方式各有好处
  • avcodec_decode_video2编解码一般使用外部编解码库,比如libx264;

注意:avcodec_decode_video2与后面的avcodec_decode_audio4函数解码:是指从packet中解析出来一帧一帧数据,并不涉及数据格式的转换,如果要进行格式的转换,需要设置重采样方法等等!!

(二)编程实战(YUV视频流转RGB图像)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libavutil/log.h>
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>    //ibswscale是一个主要用于处理图片像素数据的类库。可以完成图片像素格式的转换,图片的拉伸等工作

#define WORD uint16_t
#define DWORD uint32_t
#define LONG int32_t

//https://www.cnblogs.com/lzlsky/archive/2012/08/16/2641698.html
typedef struct tagBITMAPFILEHEADER 
  WORD  bfType;            //位图类别,根据不同的操作,系统而不同,在Windows中,此字段的值总为‘BM’
  DWORD bfSize;            //BMP图像文件的大小
  WORD  bfReserved1;    //保留,为0
  WORD  bfReserved2;    //保留,为0
  DWORD bfOffBits;        //BMP图像数据的地址
 BITMAPFILEHEADER, *PBITMAPFILEHEADER;

typedef struct tagBITMAPINFOHEADER 
  DWORD biSize;            //包含的是这个结构体的大小(包括颜色表)
  LONG  biWidth;        //是图片的长
  LONG  biHeight;        //是图片的宽
  WORD  biPlanes;        //是目标绘图设备包含的层数,必须设置为1
  WORD  biBitCount;        //是图像的位数,例如24位,8位等
  DWORD biCompression;    //压缩方式,0表示不压缩,1表示RLE8压缩,2表示RLE4压缩,3表示每个像素值由指定的掩码决定
  DWORD biSizeImage;    //BMP图像数据大小,必须是4的倍数,图像数据大小不是4的倍数时用0填充补足
  LONG  biXPelsPerMeter;//水平分辨率,单位像素/m
  LONG  biYPelsPerMeter;//垂直分辨率,单位像素/m
  DWORD biClrUsed;        //BMP图像使用的颜色,0表示使用全部颜色,对于256色位图来说,此值为100h=256
  DWORD biClrImportant;    //重要的颜色数,此值为0时所有颜色都重要,对于使用调色板的BMP图像来说,当显卡不能够显示所有颜色时,此值将辅助驱动程序显示颜色
 BITMAPINFOHEADER, *PBITMAPINFOHEADER;

void saveBMP(struct SwsContext* img_convert_cxt,AVFrame* frame,char* filename)
    //1.先进行转换,YUV420=>RGB24
    int w = frame->width;
    int h = frame->height;

    int numBytes = avpicture_get_size(AV_PIX_FMT_RGB24,w,h);    //计算字节数量
    uint8_t* buffer = (uint8_t*)av_malloc(numBytes*sizeof(uint8_t));    //分配空间

    AVFrame* pFrameRGB = av_frame_alloc();
    //avpicture_fill函数将ptr指向的数据填充到picture内,但并没有拷贝,只是将picture结构内的data指针指向了ptr的数据。
    avpicture_fill((AVPicture*)pFrameRGB,buffer,AV_PIX_FMT_RGB24,w,h);
    //真正用来做转换的函数:https://www.cnblogs.com/yongdaimi/p/10715830.html
    sws_scale(img_convert_cxt,frame->data,frame->linesize,    //当前处理区域的每个通道数据指针,每个通道行字节数
        0,h,    //参数int srcSliceY, int srcSliceH,定义在输入图像上处理区域,srcSliceY是起始位置,srcSliceH是处理多少行。如果srcSliceY=0,srcSliceH=height,表示一次性处理完整个图像。
        pFrameRGB->data,pFrameRGB->linesize);    //定义输出图像信息(输出的每个通道数据指针,每个通道行字节数)

    //上面将数据转换完成,下面初始化结构体,写入BMP图片数据,到文件中去
    //---构造BITMAPINFOHEADER信息首部(第二部分,先有文件头,再有信息头,再之后是数据)
    BITMAPINFOHEADER header;

    header.biSize = sizeof(BITMAPINFOHEADER);
    header.biWidth = w;
    /*
    如果该值是一个正数,说明Btimap是Bottom up DIB,起始点是左下角,也就是从图像的最下面一行扫描,位图数组中得到的第一行数据实际是图形的最下面的一行。图像是倒向的;
    如果该值是一个负数,则说明图像是TopDown DIB,起始点是左上角,图像从最上面一行扫描,图像正向的。
    大多数的BMP文件都是倒向的位图,也就是时,高度值是一个正数。(注:当高度值是一个负数时(正向图像),图像将不能被压缩(也就是说biCompression成员将不能是BI_RLE8或BI_RLE4)
    */
    header.biHeight = h*(-1);            //biHeight字段的正负号指定DIB图像的绘制方向,负数表示为正向,不被压缩
    header.biPlanes = 1;                //必须为1
    header.biBitCount = 24;                //RBG位深24
    header.biCompression = 0;            //不压缩
    header.biSizeImage = 0;                //其中 biSizeImage 如果不为 0 这代表位图中实际的像素数据字节数;同时如果为0,位图像素数据的字节数也可以通过 biWidth biHeight biBitCount 计算得到。
    header.biXPelsPerMeter = 0;            //设置分辨率 像素/米
    header.biYPelsPerMeter = 0;    
    header.biClrUsed = 0;                //使用全部颜色
    header.biClrImportant = 0;            //全都重要

    //---BITMAPFILEHEADER文件头(第一部分)
    BITMAPFILEHEADER bmpFileHeader;
    bmpFileHeader.bfType = 0x4d42;    //"BM"
    bmpFileHeader.bfSize = sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER)+numBytes;
    bmpFileHeader.bfReserved1 = 0;
    bmpFileHeader.bfReserved2 = 0;
    bmpFileHeader.bfOffBits = sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER);

    FILE* fp = fopen(filename,"wb");
    //由于linux上4字节对齐,而信息头大小为54字节,第一部分14字节,第二部分40字节,所以会将第一部分补齐为16自己,直接用sizeof,打开图片时就会遇到premature end-of-file encountered错误  
    //下面的方式可以防止对齐,避免对齐导致出错
    fwrite(&bmpFileHeader,8,1,fp);    //先把bfType、bfSize、bfReserved1写入
    fwrite(&bmpFileHeader.bfReserved2,sizeof(bmpFileHeader.bfReserved2),1,fp);
    fwrite(&bmpFileHeader.bfOffBits,sizeof(bmpFileHeader.bfOffBits),1,fp);

    fwrite(&header,sizeof(BITMAPINFOHEADER),1,fp);
    
    //-----------进行色彩矫正,RGB-->GBR才能变为原本的色彩
    for(int i=0;i<numBytes-3;i+=3)
        uint8_t temp = pFrameRGB->data[0][i];
        pFrameRGB->data[0][i] = pFrameRGB->data[0][i+1];
        pFrameRGB->data[0][i+1] = temp;

        temp = pFrameRGB->data[0][i+1];
        pFrameRGB->data[0][i+1] = pFrameRGB->data[0][i+2];
        pFrameRGB->data[0][i+2] = temp;
    

    fwrite(pFrameRGB->data[0],1,numBytes,fp);    //对于RGB只需要一个数组,YUV需要3个
    fclose(fp);

    //释放资源
    av_freep(&pFrameRGB[0]);
    av_freep(pFrameRGB);


int decode_write_frame(const char* out_filename,AVCodecContext* avctx,struct SwsContext* img_convert_cxt,
                        AVFrame* frame,int* frame_count,AVPacket* packet,int last)
    int len,got_frame;
    char buf[1024];

    //进行解码操作-----------------
    //作用是解码一帧视频数据。输入一个压缩编码的结构体AVPacket,输出一个解码后的结构体AVFrame。
    len = avcodec_decode_video2(avctx,frame,&got_frame,packet);    //got_frame该值为0表明没有图像可以解码,否则表明有图像可以解码;
    if(len<0)
        av_log(NULL,AV_LOG_ERROR,"Fail to decode video frame %d\\n",*frame_count);
        return len;
    

    if(got_frame)    //一个包中可能有1个或者多个帧,一般视频包中包含1帧;这里我们只获取1帧,进行处理,其他的依旧保留在packet结构体中,后面进行修改
        if(last)
            av_log(NULL,AV_LOG_INFO,"----Get last frame");    //一般最后一帧可能会做特殊处理
        av_log(NULL,AV_LOG_INFO,"Get frame count %3d,Saving frame %3d\\n",got_frame,*frame_count);
        snprintf(buf,sizeof(buf),"%s-%d.bmp",out_filename,*frame_count);    //图像命名

        //保存图像
        saveBMP(img_convert_cxt,frame,buf);
        (*frame_count)++;
    

    //avcodec_decode_video2只获取了包中的一帧,然而包中可能还有其他帧,所以这里进行处理,进行结构体数据修改
    if(packet->data)
        packet->size -= len;    //大小减去1帧大小
        packet->data += len;    //数据下移,跳过已经处理的数据
    
    return 0;


void decode_video(char* in_filename,char* out_filename)
    int ret,cnt=500;    //cnt长视频输出数量

    AVFormatContext* fmt_cxt = NULL;

    AVCodec* codec = NULL;
    AVCodecContext* c = NULL;

    struct SwsContext* img_convert_cxt = NULL;    //图像处理上下文

    int stream_idx;
    AVStream* st = NULL;

    int frameCnt = 0;    //记录解码的帧数量
    AVFrame* frame = NULL;

    AVPacket packet;

    ret = avformat_open_input(&fmt_cxt,in_filename,NULL,NULL);
    if(ret<0)
        av_log(NULL,AV_LOG_ERROR,"Can`t open input file:%s\\n",in_filename);
        return;
    

    ret = avformat_find_stream_info(fmt_cxt,0); //获取输入流的详细信息
    if(ret<0)
        av_log(NULL,AV_LOG_ERROR,"Could not find stream information\\n");
        goto __AVFORMAT;
    
    
    //媒体文件句柄 / 流类型 / 请求的流编号(-1则自动去找) / 相关流索引号(比如音频对应的视频流索引号),不指定则-1 / 如果非空,则返回所选流的解码器(指针获取) / flag当前未定义
    ret = av_find_best_stream(fmt_cxt,AVMEDIA_TYPE_VIDEO,-1,-1,NULL,0);
    if(ret<0)
        av_log(NULL,AV_LOG_ERROR,"Can`t find the best stream\\n");
        goto __AVFORMAT;
    

    //获取流并打印流的信息
    stream_idx = ret;
    st = fmt_cxt->streams[stream_idx];

    av_dump_format(fmt_cxt,stream_idx,in_filename,0);

    //查找解码器-----------------
    codec = avcodec_find_decoder(st->codecpar->codec_id);    //根据id查找解码器,id信息存放在输入文件视频流中
    if(!codec)
        av_log(NULL,AV_LOG_ERROR,"Can`t find the decoder[%s] for input file:%s\\n",av_get_media_type_string(AVMEDIA_TYPE_VIDEO),in_filename);
        goto __AVFORMAT;
    

    //打开解码器之前,先分配上下文内存-----------------
    c = avcodec_alloc_context3(NULL);
    if(!c)
        av_log(NULL,AV_LOG_ERROR,"Failed to copy %s codec parameters to decoder context\\n",av_get_media_type_string(AVMEDIA_TYPE_VIDEO));
        goto __AVFORMAT;
    

    //将解码器的参数进行设置:将输入流的参数直接拷贝即可-----------------
    ret = avcodec_parameters_to_context(c,st->codecpar);
    if(ret<0)
        av_log(NULL,AV_LOG_ERROR,"Cannot initialize the conversion context\\n");
        goto __ACCODEC;
    

    //创建图片转换上下文(在上面参数拷贝后面)-----------------
    //源图像宽、高、像素格式;目标图像宽、高、像素格式;以及图像拉伸使用的算法
    img_convert_cxt = sws_getContext(c->width,c->height,c->pix_fmt,
                                    c->width,c->height,AV_PIX_FMT_BGR24,
                                    SWS_BICUBIC,NULL,NULL,NULL);    //后面为源、目的图像过滤器,和参数
    if(img_convert_cxt==NULL)
        av_log(NULL,AV_LOG_ERROR,"Can`t open the codec\\n");
        goto __ACCODEC;
    

    //打开解码器-----------------
    ret = avcodec_open2(c,codec,NULL);
    if(ret<0)
        av_log(NULL,AV_LOG_ERROR,"Can`t open the codec\\n");
        goto __SWSCXT;
    

    frame = av_frame_alloc();
    if(!frame)
        av_log(NULL,AV_LOG_ERROR,"Can`t alloc the frame for video\\n");
        goto __SWSCXT;
    

    //开始读取数据
    av_init_packet(&packet);    //初始化数据包
    while(av_read_frame(fmt_cxt,&packet)>=0&&(--cnt)>=0)
        if(packet.stream_index == stream_idx)
            if(decode_write_frame(out_filename,c,img_convert_cxt,
                        frame,&frameCnt,&packet,0)<0)
                av_log(NULL,AV_LOG_ERROR,"Failed to decode frame in decode_write_frame\\n");
                goto __PACKET;
            

            av_packet_unref(&packet);    //注意:减少引用不会释放空间,因为本函数还在引用这个packet结构体;
            //所以我们在解码函数中剩余的其他帧数据,还是保留在packet中的,后面根据av_read_frame继续向内部添加数据
        
    
    //处理packet中剩余的帧
    packet.data = NULL;
    packet.size = 0;
    decode_write_frame(out_filename,c,img_convert_cxt,
                        frame,&frameCnt,&packet,1);

//下面开始处理在堆上创建的内存空间    
__PACKET:
    av_frame_free(&frame);
    av_packet_unref(&packet);    //减少引用,使得自己释放空间

__SWSCXT:
    sws_freeContext(img_convert_cxt);

__ACCODEC:
    avcodec_free_context(&c);

__AVFORMAT:
    avformat_close_input(&fmt_cxt);
    return;


int main(int argc,char* argv[])
    av_register_all();
    av_log_set_level(AV_LOG_DEBUG);

    if(argc < 3)
        av_log(NULL,AV_LOG_ERROR,"The Count of Parameter must be than 2\\n");
        return -1;
    

    decode_video(argv[1],argv[2]);
    return 0;

gcc 02_ffmpeg_dec.c -o fd -I /usr/local/ffmpeg/include/ -L /usr/local/ffmpeg/lib/ -lavutil -lavformat -lavcodec -lavdevice -lswscale
./fd gfxm.mp4 gfxm

二:H264编码处理

(一)编码流程

  • avcodec_open2在解码和编码过程不同(在设置参数上):
    • 解码:因为输入文件中输入流本身已经设置好了参数,解码时只需要把这些参数拷贝即可,不需要手动设置
    • 编码:需要我们手动设置,比如分辨率…

(二)编码实战:(YUV编码为H264)FFmpeg学习(五)H264结构

#include <stdio.h>
#include <libavutil/log.h>
#include <libavcodec/avcodec.h>
#include <libavdevice/avdevice.h>
#include <libavformat/avformat.h>

#define V_WIDTH 640
#define V_HEIGHT 480

AVFormatContext* open_dev()
    char* devicename = "/dev/video0";    //设备文件描述符
    char errors[1024];
    int ret;

    AVFormatContext* fmt_ctx=NULL;    //格式上下文获取-----av_read_frame获取packet
    AVDictionary* options=NULL;
    AVInputFormat *iformat=NULL;
    AVPacket packet;    //包结构

    //获取输入(采集)格式
    iformat = av_find_input_format("video4linux2");    //驱动,用来录制视频
    //设置参数 ffmpeg -f video4linux2 -pixel_format yuyv422 -video_size 640*480  -framerate 15 -i /dev/video0 out.yuv
    av_dict_set(&options,"video_size","640*480",0);
    av_dict_set(&options,"framerate","30",0);
    av_dict_set(&optionsFFMPEG:两个单声道到一个立体声文件,其持续时间是这两个单声道文件中最长的

音视频处理 ffmpeg中级开发 视频转图片

FFMPEG:生成输入文件时间最长的7.1声道音频文件

FFmpegffmpeg 命令查询二 ( 比特流过滤器 | 可用协议 | 过滤器 | 像素格式 | 标准声道布局 | 音频采样格式 | 颜色名称 )

《FFmpeg代码技巧》系列(中级)-总览

FFmpeg for ios架构:中级