基于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);
}
  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
以下是使用CUDA将摄像头读取的数据编码成H264流的示例代码,使用了NVIDIA Video Codec SDK和OpenCV库: ```cpp #include <iostream> #include <opencv2/opencv.hpp> #include "NvEncoder/NvEncoderCuda.h" using namespace std; using namespace cv; int main(int argc, char* argv[]) { // 从摄像头读取数据 VideoCapture cap(0); if (!cap.isOpened()) { cout << "Failed to open camera!" << endl; return -1; } // 设置编码器参数 int nWidth = 640, nHeight = 480; int nBitrate = 1000000; int nFps = 30; int nGopSize = 30; int nMaxConcurrentSessions = 1; int nCodec = NV_ENC_H264; std::string sPreset = "hq"; std::string sProfile = "high"; NvEncoderInitParam encodeParams = { 0 }; encodeParams.width = nWidth; encodeParams.height = nHeight; encodeParams.bitrate = nBitrate; encodeParams.fps = nFps; encodeParams.gopSize = nGopSize; encodeParams.codec = nCodec; encodeParams.preset = const_cast<char*>(sPreset.c_str()); encodeParams.profile = const_cast<char*>(sProfile.c_str()); encodeParams.maxConcurrentSessions = nMaxConcurrentSessions; NvEncoderCuda enc(encodeParams); // 分配编码器缓冲区 int nFrameSize = enc.GetFrameSize(); uint8_t* pFrame = new uint8_t[nFrameSize]; uint8_t* pBitstream = new uint8_t[nFrameSize]; // 编码并输出h264流 Mat frame; while (true) { // 读取一帧图像 cap >> frame; if (frame.empty()) { break; } // 将帧数据复制到CUDA缓冲区 uint8_t* dpFrame = NULL; int nPitch = 0; enc.GetDeviceFrameBuffer(&dpFrame, &nPitch); cudaMemcpy2D(dpFrame, nPitch, frame.data, frame.step, nWidth * 3, nHeight, cudaMemcpyHostToDevice); // 编码一帧图像 int nBytes = 0; enc.EncodeFrame(pFrame, &nBytes, dpFrame); // 将编码后的数据复制回主机内存 cudaMemcpy(pBitstream, enc.GetBitstreamBuffer(), nBytes, cudaMemcpyDeviceToHost); // 输出h264流 fwrite(pBitstream, sizeof(uint8_t), nBytes, stdout); } // 释放资源 enc.DestroyEncoder(); delete[] pFrame; delete[] pBitstream; return 0; } ``` 在编译时需要链接NVIDIA Video Codec SDK和OpenCV库。执行程序后,它将从摄像头读取数据并将其编码为H264流输出到标准输出。你可以将输出重定向到文件中,例如: ``` ./encode | ffmpeg -i - output.mp4 ``` 这将从标准输入读取H264流,并将其转换为MP4文件。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

IT_阿水

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值