基于FFMPEG读取摄像头图像编码为h264

1.调用ffmpeg命令采集摄像头图像

c 复制代码
$ 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();

  • 编码流程图如下:

示例代码:

c 复制代码
#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);
}
相关推荐
韩曙亮25 分钟前
【FFmpeg】FFmpeg 内存结构 ③ ( AVPacket 函数简介 | av_packet_ref 函数 | av_packet_clone 函数 )
ffmpeg·音视频·avpacket·av_packet_clone·av_packet_ref·ffmpeg内存结构
oushaojun25 小时前
ubuntu中使用ffmpeg和nginx推流rtmp视频
nginx·ubuntu·ffmpeg·rtmp
莫固执,朋友5 小时前
网络抓包工具tcpdump 在海思平台上的编译使用
网络·ffmpeg·音视频·tcpdump
lxkj_20246 小时前
修改ffmpeg实现https-flv内容加密
网络协议·https·ffmpeg
cuijiecheng20186 小时前
音视频入门基础:MPEG2-TS专题(6)——FFmpeg源码中,获取MPEG2-TS传输流每个transport packet长度的实现
ffmpeg·音视频
VisionX Lab11 小时前
数据脱敏工具:基于 FFmpeg 的视频批量裁剪
python·ffmpeg·音视频
柳鲲鹏1 天前
全网首发:Ubuntu编译跨平台嵌入式支持ffmpeg的OpenCV
linux·ubuntu·ffmpeg
冰山一脚20131 天前
ffplay音频SDL播放处理
ffmpeg
cuijiecheng20181 天前
音视频入门基础:MPEG2-TS专题(7)——FFmpeg源码中,读取出一个transport packet数据的实现
ffmpeg·音视频
Maxwellhang2 天前
【java-ffmpeg】java 内存测试和集成
java·ffmpeg·jni