FFMPEG常用代码一

at 1年前  ca FFmpeg  pv 646  by touch  

描述

本文根据FFMEPG的API函数实现视频转换、音频提取、视频提取、音频视频裸流重封装、截图、YUV转JPEG、桌面抓图、动态生成M3U8、队列操作、时间基操作、windows下获取摄像头设备、windows下获取音频设备、windows下获取屏幕宽高

ffmpeg命令

#提取1秒钟25帧
ffmpeg -i 1.mp4 -s 1280*720 -vf fps=fps=25 -ss 00:00:00 -to 00:00:01 -pix_fmt rgb24  1.rgb
#提取yuv420p
ffmpeg -i 1.mp4 -s 800*400 -ss 00:00:02 -to 00:00:10 -pix_fmt yuv420p 1.yuv
#提取rgb文件
ffmpeg -i 1.mp4 -s 800*400 -pix_fmt rgba 1.rgb
#提取s16格式
ffmpeg -i 1.mp4 -ar 48000 -ac 2 -f s16le 48000_2_s16le.pcm
#提取flt格式
ffmpeg -i 1.aac -ar 48000 -ac 2 -f f32le 48000_2_f32le.pcm
#播放rgba
ffplay -pixel_format rgba -video_size 800*400 -framerate 5 -i 1.rgb
#播放yuv420
ffplay -pixel_format yuv420p -video_size 300*300 -framerate 5 -i 300_300.yuv
#播放pcm
ffplay -ar 48000 -ac 2 -f s16le 1.pcm
#播放bgra
ffplay -pixel_format bgra -video_size 1366*768 -framerate 15 -i deskTop.rgb

码流计算

分辨率

x轴的像素个数*y轴的像素个数

常见的宽高比例16:9或4:3

16:9分辨率:360P/720P/1K/2K

帧率

每秒钟采集/播放图像的个数

动画的帧率是25帧/s

常见的帧率:15帧/s 、30帧/s、60帧s

未编码视频的RGB码流
RGB=分辨率(宽*高)*3(Byte)*帧率(25帧) 例如:1280*720*3*25=69120000 约等于69M FFMPEG常用代码一 FFmpeg

AVFrame结构体

AVFrame是FFMPEG中一帧没压缩数据的结构体对象,以下代码是AVFrame结构体操作。

#include <iostream>


extern "C"{
    #include <libavformat/avformat.h>
};



int main()
{
    //创建avframe空间
    AVFrame *frame=av_frame_alloc();

    //宽度
    frame->width = 400;
    //高度
    frame->height = 300;
    //格式
    frame->format=AV_PIX_FMT_ARGB;

    //分配空间
    //av_frame_get_buffer 对齐方式默认32字节,yuv设置16字节
    int re=av_frame_get_buffer(frame,16);
    if(re != 0)
    {
        char buff[1024]={0};
        //打印错误
        av_strerror(re,buff,sizeof(buff));
        std::cout << buff << std::endl;
    }
    //rgba 只有第一个索引有值,如果是yuv,linesize的是3个索引值
    std::cout << frame->linesize[0] << std::endl;

    if(frame->buf[0]!=NULL)
    {
        std::cout << "frame ref count =" << av_buffer_get_ref_count(frame->buf[0]) << std::endl;
    }
    //初始化avframe空间
    AVFrame *frame2=av_frame_alloc();
    //将frame对象中的缓存数据拷贝frame2中
    av_frame_ref(frame2,frame);
    //将frame2的引用移除
    av_frame_unref(frame2);
    //将frame的引用移除
    av_frame_unref(frame);

    if(frame2->buf[0]!=NULL)
    {
        std::cout << "frame2 ref count = " << av_buffer_get_ref_count(frame2->buf[0]) << std::endl;
    }

    //销毁avframe
    av_frame_free(&frame);
    av_frame_free(&frame2);
    return 0;
}

帧率测试

#include <iostream>
#include <ctime>
#include <thread>


//自定义休眠,解决sleep_for不准确问题
void MySleep(unsigned int ms)
{
    clock_t beg=clock();
    for(int i=0;i<ms;i++)
    {
        std::this_thread::sleep_for(std::chrono::milliseconds(1));
        if((clock()-beg)/(CLOCKS_PER_SEC/1000)>=ms)
        {
            break;
        }
    }
}

int main()
{
    //开始时间
    clock_t beg=clock();
    //帧率
    int fps=0;
    for(;;)
    {
        fps++;
        clock_t  tmp=clock();
        //消息10毫秒,sleep函数休眠不精准
        std::this_thread::sleep_for(std::chrono::milliseconds(10));
//        MySleep(10);
        std::cout << clock() -tmp  << " " << std::flush ;
        //1秒钟开始统计,CLOCKS_PER_SEC CPU每秒跳数
        //间隔毫秒数
        if((clock()-beg)/(CLOCKS_PER_SEC/1000) >1000)
        {
            std::cout << "sleep for fps :" << fps << std::endl;
            break;
        }
    }
    return 0;
}

设置帧率

//一秒帧率
int fps=25;
//一秒钟除以25帧得到休眠毫秒数
int sleepMS=1000/25;
//设置休眠
MySleep(sleepMS);

码率计算公式

文件大小(bit) / 时长(秒) / 1024 = kbps每秒传输千位数
例如一个2M的视频,时长是20s

2M=2*1024*1024*8=16777216bit
码率=16777216/20/1024=819.2kbps

手机设置码率建议

通过上面的介绍,结合我做过的一些手机项目,我总结了一套设置码率的公式,分享给大家如下:项目计算公式

公式192X144320X240480X360640X4801280X7201920X1080
极低码率(宽X高X3)/430kb/s60kb/s120kps250kbps500kbps1mbps
低码率 (宽X高X3) /260kb/s120kb/s50kbps500kbps1mbps2mbps
中码率(宽X高X3)120kb/s250kb/s500kbps1mbps2mbps4mbps
高码率 (宽X高X3) X 2250kb/s500kb/s1mbps2mbps4mbps8mbps
极高码率(宽X高X3)X4500kb/s1mb/s2mbps4mbps8mbps16mbps

图像重采样-YUV转RGBA

#include <iostream>
#include <fstream>


typedef unsigned char Uint8;

extern "C"{
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
};

//heliang
int main()
{
    //源yuv宽度
    int yuvWidth=1364;
    //源yuv高度
    int yuvHeight=768;

    //yuv的linesize
    int yuv_linesize[3]={yuvWidth,yuvWidth/2,yuvWidth/2};

    //存储yuv数据
    Uint8 *yuv[3]={0};
    yuv[0]=new Uint8 [yuvWidth*yuvHeight];
    yuv[1]=new Uint8 [yuvWidth*yuvHeight/4];
    yuv[2]=new Uint8 [yuvWidth*yuvHeight/4];

    //打开yuv视频文件
    std::ifstream ifs;
    ifs.open("d:\\ds.yuv",std::ios::binary);

    //yuv重采样rgb
    SwsContext *yuvToRgbCtx= nullptr;
    //rgb宽度
    int rgbWidth=800;
    //rgb宽度
    int rgbHeight=600;

    //存储rgba数据
    Uint8 *rgbaOne=new Uint8[rgbWidth*rgbHeight*4];

    //输出rgba数据
    std::ofstream ofs;
    ofs.open("d:\\1.rgba",std::ios::binary);

    //rgb高度
    for(;;)
    {
        //读取yuv数据
        ifs.read((char*)yuv[0],yuvWidth*yuvHeight);
        ifs.read((char*)yuv[1],yuvWidth*yuvHeight/4);
        ifs.read((char*)yuv[2],yuvWidth*yuvHeight/4);
        if(ifs.gcount() ==0)break;


        //创建重采样的上下文
        yuvToRgbCtx=sws_getCachedContext(
                yuvToRgbCtx,  //重采样上下文
                yuvWidth, //源yuv宽度
                yuvHeight, //源yuv高度
                AV_PIX_FMT_YUV420P, //yuv存储格式
                rgbWidth, //转rgb宽度
                rgbHeight, //转rgb高度
                AV_PIX_FMT_RGBA, //rgba格式
                SWS_BILINEAR, //重采样算法,线性算法
                NULL, //源过滤,不使用
                NULL, //目标过滤,不使用
                0  //过滤参数,不使用
                );

        //重采样上下文创建失败
        if(!yuvToRgbCtx)
        {
            std::cerr << "sws_getCachedContext failed" << std::endl;
            break;
        }

        Uint8 *rgbData[1];
        rgbData[0]=rgbaOne;

        int rgbaLineSize[1];
        rgbaLineSize[0]=rgbWidth*4;

        //重采样
        int re=sws_scale(yuvToRgbCtx, //重采样上下文
                yuv, //yuv数据
                yuv_linesize, //yuv设置一行大小
                0, //设置y,不考虑,设置0
                yuvHeight, //设置yuv高度
                rgbData, //设置rgba的存储空间
                rgbaLineSize //rgba存储空间
        );

        std::cout << re << " " ;
        //写出rgb数据
        ofs.write((char *)rgbData[0],rgbWidth*rgbHeight*4);

    }

    ifs.close();
    ofs.close();
    //销毁数据
    delete yuv[0];
    delete yuv[1];
    delete yuv[2];
    delete rgbaOne;
    return 0;
}

图像重采样-RGBA转YUV

#include <iostream>
#include <fstream>


typedef unsigned char Uint8;

extern "C"{
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
};

//heliang
int main()
{
    //rgba宽度
    int rgbaWidth=800;
    //rgba高度
    int rgbaHeight=400;
    //rgba像素字节
    int rgbaPixSize=4;
    //存储rgba数据
    Uint8 *rgbaData=new Uint8[rgbaWidth*rgbaHeight*rgbaPixSize];
    std::ifstream ifs;
    ifs.open("d:\\1\\1.rgb",std::ios::binary);
    if(!ifs.is_open())
    {
        std::cout << "open rgb file is failed !" << std::endl;
        return 0;
    }

    SwsContext *rgbaToYuvCtx= NULL;

    int yuvWidth=300;
    int yuvHeight=300;

    Uint8 *yuv[3];
    yuv[0]=new Uint8[yuvWidth*yuvHeight];
    yuv[1]=new Uint8[yuvWidth*yuvHeight/4];
    yuv[2]=new Uint8[yuvWidth*yuvHeight/4];
    int yuvLineSize[3]={yuvWidth,yuvWidth/2,yuvHeight/2};
    std::ofstream ofs;
    ofs.open("d:\\300_300.yuv",std::ios::binary);

    for(;;)
    {
        //读取rgba数据
        ifs.read((char*)rgbaData,rgbaWidth*rgbaHeight*rgbaPixSize);
        if(ifs.gcount()==0)break;


        //初始化重采样上下文
        rgbaToYuvCtx=sws_getCachedContext(
                rgbaToYuvCtx, //上下文
                rgbaWidth, //源宽度
                rgbaHeight, //源高度
                AV_PIX_FMT_RGBA, //rgba存储格式
                yuvWidth, //目标宽度
                yuvHeight, //目标高度
                AV_PIX_FMT_YUV420P, //重采样格式,yuv420
                SWS_BILINEAR, //重采样算法,线性算法
                0, //过滤器参数,不使用
                0, //过滤器参数,不使用
                0 //过滤器参数,不使用
                );


        Uint8 *srcSlice[1];
        srcSlice[0]=rgbaData;
        int srcStride[1];
        srcStride[0]=rgbaWidth*rgbaPixSize;



        //重采样
        int reH=sws_scale(
                rgbaToYuvCtx, //重采样对象
                srcSlice, //rgba存储空间
                srcStride, //rgba 每行存储大小
                0, //y轴
                rgbaHeight, //高度
                yuv,   //设置yuv存储空间
                yuvLineSize //设置yuv每行存储大小
                );

        std::cout << reH << " ";

        ofs.write((char*)yuv[0],yuvWidth*yuvHeight);
        ofs.write((char*)yuv[1],yuvWidth*yuvHeight/4);
        ofs.write((char*)yuv[2],yuvWidth*yuvHeight/4);

    }

    ofs.close();
    ifs.close();
    delete yuv[0];
    delete yuv[1];
    delete yuv[2];
    delete rgbaData;
    return 0;
}

音频重采样-PCM

#include <iostream>
#include <fstream>

extern "C"{
#include <libavformat/avformat.h>
#include <libswresample/swresample.h>
#include <libavutil/opt.h>
};


//heliang
int main()
{

    //打印编解码器信息
    std::cout << avcodec_configuration() << std::endl;


    //重采样原来的采用存储格式
    AVSampleFormat audioOldFormat=AV_SAMPLE_FMT_S16;

    //重采样新的采用存储格式
    AVSampleFormat audioNewFormat=AV_SAMPLE_FMT_FLTP;

    //1.音频重采样上下文开辟空间
    SwrContext *audioSwrCtx=swr_alloc();

    //2.设置重采样上下文
    swr_alloc_set_opts(
            audioSwrCtx, //上下文
            AV_CH_LAYOUT_STEREO, //输出的layout, 如:5.1声道
            audioNewFormat, // 输出的采样格式
            44100,  //输出采样率,频率,单位:Hz
            AV_CH_LAYOUT_STEREO, //输⼊的layout
            audioOldFormat, //输⼊的采样格式
            48000,  // 输⼊的采样率,频率,单位:Hz
            0, //⽇志相关,不⽤管先,直接为0
            NULL
    );

    //3.初始化重采样上下文的参数
    swr_init(audioSwrCtx);

    //4.存储未重采样的数据
    AVFrame *audioOldframe=av_frame_alloc();
    //通道布局情况
    audioOldframe->channel_layout=AV_CH_LAYOUT_STEREO;
    //通道数
    audioOldframe->channels=av_get_channel_layout_nb_channels(audioOldframe->channel_layout);
    //采样格式
    audioOldframe->format=audioOldFormat;
    //采样率
    audioOldframe->sample_rate=48000;
    //nb_samples 必须设置
    //AAC的LC编码级:1024,ACC的HE编码级:2048,MP3:1152
    audioOldframe->nb_samples=1024;
    //packed:多个声道数据交错存放,frame中buffer会data[0],linesize[0] LRLR LRLR
    //planar 每个声道数据单独存放,frame中buffer会data[0],data[1],linesize[0]和 linesize[1]  LLLL RRRR
    /* 为frame分配buffer */
    int re = av_frame_get_buffer(audioOldframe, 0);
    if(re<0)
    {
        char buf[1024]={0};
        av_strerror(re,buf,sizeof(buf));
        std::cout << "av_frame_get_buffer failed error msg :" << buf << std::endl;
        return 0;
    }

    //存储重采样数据
    AVFrame  *audioNewframe=av_frame_alloc();

    //通道布局情况
    audioNewframe->channel_layout=AV_CH_LAYOUT_STEREO;
    //通道数
    audioNewframe->channels=av_get_channel_layout_nb_channels(audioOldframe->channel_layout);
    //采样格式
    audioNewframe->format=audioNewFormat;
    //采样率
    audioNewframe->sample_rate=44100;
    //nb_samples 必须设置
    //AAC的LC编码级:1024,ACC的HE编码级:2048,MP3:1152
    audioNewframe->nb_samples= av_rescale_rnd(audioOldframe->nb_samples, audioNewframe->sample_rate, audioOldframe->sample_rate, AV_ROUND_UP);
    re = av_frame_get_buffer(audioNewframe, 0);

    if(re<0)
    {
        char buf[1024]={0};
        av_strerror(re,buf,sizeof(buf));
        std::cout << "av_frame_get_buffer failed error msg :" << buf << std::endl;
        return 0;
    }


    //读取PCM数据,并且重采样
    std::ifstream ifs("d:\\48000_2_s16le.pcm",std::ios::binary);
    //获取未重采样帧的大小
    int audioOldFrameDataSize=audioOldframe->nb_samples*av_get_bytes_per_sample(AV_SAMPLE_FMT_S16)*audioOldframe->channels;

    std::ofstream ofs("d:\\41000_2_fltp.pcm",std::ios::binary);

    for(int i=0;;i++)
    {
        if(ifs.eof())break;
        ifs.read((char*)audioOldframe->data[0],audioOldFrameDataSize);

        //4.重采样音频
        int out_samples =swr_convert(audioSwrCtx,
                audioNewframe->data,
                audioNewframe->nb_samples,
                (const uint8_t**)audioOldframe->data,
                audioOldframe->nb_samples
                );

        //planar LRLRLR
        if(av_sample_fmt_is_planar(audioNewFormat)) {
            //获取重采样帧的大小
            int size=av_get_bytes_per_sample(audioNewFormat);
            for(int i = 0; i < out_samples; i++) {
                for(int c = 0; c < audioNewframe->channels; c++)
                ofs.write((char*)audioNewframe->data[c]+i*size,size);

            }
        }
        else {  // packed   LLLL RRRR
            //获取重采样帧的大小
            int audioNewFrameDataSize=audioNewframe->nb_samples*av_get_bytes_per_sample(audioNewFormat)*audioNewframe->channels;
            ofs.write((char*)audioNewframe->data[0], audioNewFrameDataSize);
        }

    }

    ofs.close();
    ifs.close();
    av_frame_free(&audioOldframe);
    av_frame_free(&audioNewframe);
    swr_close(audioSwrCtx);
    return 0;
}

视频编码器初始代码

#include <iostream>

extern "C"{
#include <libavcodec/avcodec.h>
};

//heliang
int main()
{
    //1.查到编码器 AV_CODEC_ID_HEVC h265
    //avcodec_find_decoder_by_name() 按名称获取编码器
    AVCodec *codec=avcodec_find_encoder(AV_CODEC_ID_H264);

    //2创建编码器上下文
    AVCodecContext *codecCtx=avcodec_alloc_context3(codec);

    //3.设置编码器上下文
    //视频宽度
    codecCtx->width=400;
    //视频高度
    codecCtx->height=300;
    //设置帧的时间单位,pts*time_base =播放时间秒
    codecCtx->time_base={1,25};
    //设置像素格式
    codecCtx->pix_fmt=AV_PIX_FMT_YUV420P;
    //编码线程数
    codecCtx->thread_count=16;


    //4 打开编码上器下文
    int re=avcodec_open2(codecCtx,codec,NULL);
    if(re!=0)
    {
      char buf[1024]={0};
      av_strerror(re,buf,sizeof(buf));
      std::cerr << buf << std::endl;
      return 0;
    }

    //释放上下文
    avcodec_free_context(&codecCtx);
    return 0;
}

视频编码器压缩数据

#include <iostream>
#include <fstream>


extern "C"{
#include <libavcodec/avcodec.h>
};

//heliang
int main()
{
    //1.查到编码器 AV_CODEC_ID_HEVC  AV_CODEC_ID_H264
    //avcodec_find_encoder_by_name() 按名称获取编码器
    AVCodec *codec=avcodec_find_encoder(AV_CODEC_ID_H264);

    //2创建编码器上下文
    AVCodecContext *codecCtx=avcodec_alloc_context3(codec);

    //3.设置编码器上下文
    //视频宽度
    codecCtx->width=400;
    //视频高度
    codecCtx->height=300;
    //设置帧的时间单位,pts*time_base =播放时间秒
    codecCtx->time_base={1,25};
    //设置像素格式
    codecCtx->pix_fmt=AV_PIX_FMT_YUV420P;
    //编码线程数
    codecCtx->thread_count=16;


    //4 打开编码上器下文
    int re=avcodec_open2(codecCtx,codec,NULL);
    if(re!=0)
    {
      char buf[1024]={0};
      av_strerror(re,buf,sizeof(buf));
      std::cerr << buf << std::endl;
      return 0;
    }

    //创建原始帧空间
    AVFrame *frame=av_frame_alloc();
    frame->width=codecCtx->width;
    frame->height=codecCtx->height;
    frame->format=codecCtx->pix_fmt;
    //设置buffer,并且设置对齐方式
    av_frame_get_buffer(frame,0);

    //初始化编码压缩包的空间
    AVPacket *packet=av_packet_alloc();

    //打包文件
    std::ofstream ofs;
    ofs.open("d:\\400_300.h264",std::ios::binary);


    //生成h264数据
    for(int i=0;i<220;i++)
    {
        //生成y数据
        for(int y=0;y<codecCtx->height;y++)
        {
            for(int x=0;x<codecCtx->width;x++)
            {
                frame->data[0][y*frame->linesize[0]+x]=x+y+i*3;
            }
        }

        //生成uv数据
        for(int y=0;y<codecCtx->height/2;y++)
        {
            for(int x=0;x<codecCtx->width/2;x++)
            {
                frame->data[1][y*frame->linesize[1]+x]=128+y+i*2;
                frame->data[2][y*frame->linesize[2]+x]=64+x+i*5;
            }
        }

        frame->pts= i ; //显示时间

        //发送未压缩帧到线程中压缩
        re=avcodec_send_frame(codecCtx,frame);
        if(re!=0)
        {
            break;
        }

        //返回多帧
        while(re >=0)
        {
            //接收压缩帧,一般前几次调用返回空(缓冲,编码未完成)
            //独立线程编码
            re=avcodec_receive_packet(codecCtx,packet);
            if(re == AVERROR(EAGAIN) || re == AVERROR_EOF)
                break;
            if(re <0)
            {
                char buf[1024]={0};
                av_strerror(re,buf,sizeof(buf));
                std::cerr << "avcodec_receive_packet error:" << buf << std::endl;
                break;
            }

            std::cout << packet->size << " " << std::flush;

            //写文件
            ofs.write((char*)packet->data,packet->size);
            //解引用
            av_packet_unref(packet);


        }


    }

    ofs.close();
    //释放帧
    av_frame_free(&frame);
    //释放压缩包
    av_packet_free(&packet);
    //释放上下文
    avcodec_free_context(&codecCtx);
    return 0;
}

视频-ABR比特率设置(不推荐)

//abr平均比特率(不推荐用,会生成脏码)
codecCtx->bit_rate=400000; //400KB

视频-CQP质量设置

#include <libavutil/opt.h>

//qp范围0-51,qp值越大像素越差,默认23
av_opt_set(codecCtx->priv_data,"qp","23",0);

视频-CBR比特率设置

#include <libavutil/opt.h>

//恒定比特率(CBR)由于MP4不支持NAL填充,因此输出文件必须为(MPEG-2 TS)
//400KB
int br=400000;
codecCtx->rc_min_rate=br;
codecCtx->rc_max_rate=br;
codecCtx->rc_buffer_size=br;
codecCtx->bit_rate=br;
av_opt_set(codecCtx->priv_data,"nal-hrd","cbr",0);


版权声明

本文仅代表作者观点,不代表码农殇立场。
本文系作者授权码农殇发表,未经许可,不得转载。

 

扫一扫在手机阅读、分享本文

已有0条评论
您是本站第12322名访客 今日有0篇新文章 当前在线 36 人