FFMPEG常用代码一
at 2年前 ca FFmpeg pv 1469 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);
版权声明
本文仅代表作者观点,不代表码农殇立场。
本文系作者授权码农殇发表,未经许可,不得转载。