当前位置: 首页 > article >正文

基于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);
}

http://www.kler.cn/a/287340.html

相关文章:

  • 20241116解决在WIN11和ubuntu20.04通过samba共享时出现局域网千兆带宽拉满的情况
  • 第十六届蓝桥杯模拟赛(第一期)-c++/c
  • 【网页设计】CSS3 进阶(动画篇)
  • Go语言基本类型转换
  • 如何合理设计一套springcloud+springboot项目中的各个微服务模块之间的继承关系的最优方案
  • Java连接MySQL(测试build path功能)
  • 【Datawhale X 李宏毅苹果书 AI夏令营】《深度学习详解》Task2 打卡
  • 【Web开发工具】基于Windows系统下的WebStorm安装教程
  • 告警管理大师:深入解析Alertmanager的配置与实战应用
  • esp32 中断最简验证程序
  • css的“id选择器“命名问题
  • pytest自动化测试执行环境切换的两种解决方案
  • 深度学习系列74:语音中的mel谱
  • 通用文字识别API如何通过Java进行调用?(一)
  • Python知识点:如何使用Appium进行移动应用测试
  • Linux基础(包括centos7安装、linux基础命令、vi编辑器)
  • three.js 开发粒子系统
  • RCU概念
  • 【QNX+Android虚拟化方案】101 - Android GVM 虚拟网络 Virt-Net 配置
  • 数学基础 -- 线性代数之酉矩阵
  • git笔记 -- 日志搜索的方法
  • JAVA HttpUrlConnection 使用 GZIP 编码压缩
  • 学习如何更好向GPT提问
  • 从跟跑到领跑:AIGC时代国产游戏的崛起与展望
  • SpringCloud乐尚代驾学习笔记:项目概述(一)
  • prometheus download all