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码流
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
手机设置码率建议
通过上面的介绍,结合我做过的一些手机项目,我总结了一套设置码率的公式,分享给大家如下:项目计算公式
公式 | 192X144 | 320X240 | 480X360 | 640X480 | 1280X720 | 1920X1080 |
---|---|---|---|---|---|---|
极低码率(宽X高X3)/4 | 30kb/s | 60kb/s | 120kps | 250kbps | 500kbps | 1mbps |
低码率 (宽X高X3) /2 | 60kb/s | 120kb/s | 50kbps | 500kbps | 1mbps | 2mbps |
中码率(宽X高X3) | 120kb/s | 250kb/s | 500kbps | 1mbps | 2mbps | 4mbps |
高码率 (宽X高X3) X 2 | 250kb/s | 500kb/s | 1mbps | 2mbps | 4mbps | 8mbps |
极高码率(宽X高X3)X4 | 500kb/s | 1mb/s | 2mbps | 4mbps | 8mbps | 16mbps |
图像重采样-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条评论