基于FFMPEG读取摄像头图像编码为h264
1.调用ffmpeg命令采集摄像头图像
$ ffmpeg -f v4l2 -framerate 30 -video_size 1280*720 -i /dev/video0 -c:v libx264 -preset veryfast -f h264 output.h264
-f v4l2: 指定输入设备采用Video4Linux2框架。
-framerate 30: 设置帧率为30。
-video_size 1280720: 设置视频分辨率为1280720
-i /dev/video0: 指定输入设备文件路径。
-c:v libx264: 指定使用H.264编码。
-preset veryfast: 选择快速编码预设。
-f h264: 输出格式为H.264帧。
Output.h264: 输出文件。
2 调用ffmpeg库实现摄像头采集并编码为h264
- ffmpeg 采集摄像头图像,编码为H264格式步骤:
1.注册设备avdevice_register_all();
2.查找摄像头框架格式av_find_input_format(“video4Linux2”);
3.设置摄像头参数options:图像尺寸(video_size)、帧率(framerate)、图像格式(input_format),av_dict_set();
4.打开输入文件,获取输入上下文指针avformat_open_input();
5.获取摄像头图像流信息avformat_find_stream_info;
6.查找摄像头中的视频流av_find_best_stream;
7.根据编码格式,获取解码器avcodec_find_decoder_by_name(“libx264”);
8.分配编码器上下文指针avcodec_alloc_context3();
9.设置图像编码参数:图像尺寸、帧率framebate、time_base、gop_size、pix_fmt,将编码器关联到AVDocodecCotext指针;
10.创建输出文件fopen
11.创建视频帧av_frame_alloc();
12.设置frame参数:宽度、高度、图像格式;
13.为frame中data和buf分配空间:av_frame_get_buffer();
14.分配packet包,用于存放h264编码后的数据;
15.从摄像头中读取采集的数据av_read_frame();
17.判断是否为视频流,将packet中的yuv422数据转换为yuv420p格式,并保存到frame中;
18.将frame中的流数据进行h264格式编码encodec_video();
- 编码流程图如下:
示例代码:
#include <stdio.h>
#include <libavutil/avutil.h>
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavdevice/avdevice.h>
#include <libavutil/imgutils.h>
#include <unistd.h>
#include <signal.h>
#define VIDEO_DEV "/dev/video0"
static int video_width;
static int video_height;
int camera_flag=0;
void YUYV422_toYuv420p(AVFrame *frame,AVPacket *pkt){
/*
yuv422 存储格式为 y u y v y u y v
y u y v y u y v
yuv422 每两个y公用一组UV分量,yuyv(yuv422)一个像素大小:y+1/2(u)+1/2(v)=2byte
yuv420p 存储最简单,先存所以的y,再存u,最后v,yuv420p 每4个Y共用一组UV分量
所以先把422所有的y存在一起,再提奇数行的u ,偶数行舍弃。提完u后,再提v,v也是偶数行不提取。
*/
int i = 0;
int yuv422_length=video_width*video_height*2;//yuv422图像大小
int y_index = 0;
// 取出Y分量数据
for (i = 0; i < yuv422_length; i += 2) {
frame->data[0][y_index] = pkt->data[i];
y_index++;
}
// copy u and v
int line_start = 0;
int is_u = 1;
int u_index = 0;
int v_index = 0;
// copy u, v per line. skip a line once
for (i = 0; i < video_height; i += 2)
{
line_start = i * (video_width<<1);//每一行的起始位置,相当于:video_width*2
for (int j = line_start + 1; j < line_start + (video_width<<1); j += 4)
{
frame->data[1][u_index++]=pkt->data[j];
frame->data[2][v_index++]=pkt->data[j+2];
}
}
}
//编码视频格式
int encodec_video(FILE *fp,AVCodecContext *ctx,AVFrame *frame,AVPacket *pkt){
int ret=0;
//将数据帧传入编码器进行编码,该函数仅编码数据,并不会写入
ret=avcodec_send_frame(ctx,frame);
if(ret){
av_log(ctx,AV_LOG_ERROR,"编码视频帧失败ret=%s\n",av_err2str(ret));
return -1;
}
//从编码器中读取编码好的数据帧
while((ret=avcodec_receive_packet(ctx,pkt))>=0){
if(ret==AVERROR(EAGAIN) || ret==AVERROR_EOF)//数据帧不可用或者没有新的数据帧
{
av_packet_unref(pkt);//减少引用次数
break;
}
else if(ret==AVERROR(EINVAL)){//没有正确打开编码器
av_packet_unref(pkt);//减少引用次数
return -1;
}
//将编码好的数据写入到文件
fwrite(pkt->data,pkt->size,1,fp);
av_packet_unref(pkt);//减少引用次数
}
return 0;
}
//采集摄像头数据,将摄像头数据进行h264编码
//摄像头初始化
void *video_CollectImage(void *arg)
{
//1.注册设备
avdevice_register_all();
const AVInputFormat *ifmt=NULL;//输入格式
AVFormatContext *pfmtctx=NULL;//输入上下文
AVDictionary *options=NULL;//其它参数
const AVCodec *ocodec=NULL;
AVCodecContext *icodecCtx=NULL;//解码器上下文指针
AVPacket *opkt=NULL;
AVFrame *iframe=NULL;
FILE *fp=NULL;
int ret=0;
int idx=-1;//视频流下标
//2.查找输入格式
ifmt=av_find_input_format("video4linux2");
if(ifmt==NULL){
av_log(NULL,AV_LOG_ERROR,"video4linux2格式信息获取失败\n");
return (void *)-1;
}
av_dict_set(&options,"video_size","1280*720",0);//设置图像大小
av_dict_set(&options,"framerate","30",0);//帧率
av_dict_set(&options,"input_format","yuv420p",0);//图像格式
ret=avformat_open_input(&pfmtctx,VIDEO_DEV,ifmt,&options);
if(ret<0){
av_log(NULL,AV_LOG_ERROR,"打开输入文件,设置输入上下文指针失败,ret=%s\n",av_err2str(ret));
return 0;
}
//通过读取数据包,获取流信息
avformat_find_stream_info(pfmtctx,NULL);
av_dump_format(pfmtctx, 0, VIDEO_DEV, 0);
//3.寻找视频流
idx=av_find_best_stream(pfmtctx,AVMEDIA_TYPE_VIDEO, -1,-1,NULL, 0);
if(idx<0){
av_log(&pfmtctx,AV_LOG_ERROR,"获取视频流失败ret=%s\n",av_err2str(idx));
goto _fil;
}
av_log(pfmtctx,AV_LOG_INFO,"idx=%d\n",idx);
video_width=pfmtctx->streams[idx]->codecpar->width;
video_height=pfmtctx->streams[idx]->codecpar->height;
//1.根据名字获取注册的编码器
ocodec=avcodec_find_encoder_by_name("libx264");
if(!ocodec){
av_log(NULL, AV_LOG_ERROR, "libx264 获取编码器失败\n");
goto _fil;
}
av_log(pfmtctx,AV_LOG_INFO,"libx264格式:%d\n",ocodec->id);
//5.分配AVCodecContext上下文指针
icodecCtx=avcodec_alloc_context3(ocodec);
if(icodecCtx==NULL){
av_log(pfmtctx,AV_LOG_ERROR,"分配上下文指针失败\n");
goto _fil;
}
//设置图像尺寸
icodecCtx->width=video_width;
icodecCtx->height=video_height;
icodecCtx->bit_rate=1500000;//码率
icodecCtx->time_base=(AVRational){1,25};//时间基准
icodecCtx->framerate=(AVRational){25,1};//帧率
icodecCtx->gop_size=10;//一组图像的是数量
icodecCtx->max_b_frames=2;//B帧数量
icodecCtx->pix_fmt=AV_PIX_FMT_YUV420P;//图像格式
if(ocodec->id==AV_CODEC_ID_H264)//编码流格式
{
/*
设置私有属性信息
int av_opt_set(void *obj, const char *name, const char *val, int search_flags);
obj: 需要设置选项的对象。
name: 要设置的选项名称。
val: 设置的选项值。
search_flags: 搜索标志,通常为0。
*/
av_opt_set(icodecCtx->priv_data,"preset","slow", 0);
}
//关联编码器上下文
ret=avcodec_open2(icodecCtx,ocodec, NULL);
if(ret<0){
av_log(icodecCtx,AV_LOG_ERROR,"关联编码器上下文件指针失败ret=%s\n",av_err2str(ret));
goto _fil;
}
fp=fopen("camera.h264","w+b");
if(fp==NULL){
av_log(icodecCtx,AV_LOG_ERROR,"文件创建失败\n");
goto _fil;
}
//创建视频帧
iframe=av_frame_alloc();
if(iframe==NULL){
av_log(icodecCtx,AV_LOG_ERROR,"创建视频帧frame失败\n");
goto _fil;
}
iframe->width=video_width;
iframe->height=video_height;
iframe->format=icodecCtx->pix_fmt;
ret=av_frame_get_buffer(iframe, 0);
if(ret<0){
av_log(icodecCtx,AV_LOG_ERROR,"分别frame buffer缓冲区失败,ret=%s\n",av_err2str(ret));
goto _fil;
}
//7.创建数据包
AVPacket ipkt;
opkt=av_packet_alloc();
if(!opkt){
av_log(icodecCtx,AV_LOG_ERROR,"分配packet失败\n");
goto _fil;
}
int i=0;
av_log(NULL,AV_LOG_INFO,"开始读取数据包\n");
camera_flag=1;
//读取数据包
while(av_read_frame(pfmtctx, &ipkt)>=0 && camera_flag==1)
{
if(ipkt.stream_index == idx)//判断是否为视频帧
{
av_log(pfmtctx,AV_LOG_INFO,"pts=%ld\n",ipkt.pts);
YUYV422_toYuv420p(iframe,&ipkt);//格式转换
iframe->pts=av_rescale_q_rnd(ipkt.dts,pfmtctx->streams[idx]->time_base ,icodecCtx->time_base,AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
av_log(pfmtctx,AV_LOG_INFO,"pts:%ld\n",iframe->pts);
encodec_video(fp,icodecCtx,iframe,opkt);
if(ret<0){
goto _fil;
}
}
av_packet_unref(&ipkt);//减少引用次数
}
encodec_video(fp,icodecCtx,iframe,opkt);
fclose(fp);
av_log(pfmtctx,AV_LOG_INFO,"数据采集完成\n");
_fil:
if(pfmtctx){
avformat_close_input(&pfmtctx);//释放上下文指针
pfmtctx=NULL;
}
av_log(NULL,AV_LOG_INFO,"上下文指针释放成功\n");
avcodec_free_context(&icodecCtx);
av_frame_free(&iframe);
av_packet_free(&opkt);
}
void sig_work(int sig)
{
if(sig==SIGINT){
camera_flag=0;
}
}
int main(int argc,char **argv)
{
signal(SIGINT,sig_work);
av_log_set_level(AV_LOG_DEBUG);
video_CollectImage(NULL);
}