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

2024-08-28 19:28

本文主要是介绍基于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 vyuv422 每两个y公用一组UV分量,yuyv(yuv422)一个像素大小:y+1/2(u)+1/2(v)=2byteyuv420p  存储最简单,先存所以的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 vint line_start = 0;int is_u = 1;int u_index = 0;int v_index = 0;// copy u, v per line. skip a line oncefor (i = 0; i < video_height; i += 2) {line_start = i * (video_width<<1);//每一行的起始位置,相当于:video_width*2for (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);
}

这篇关于基于FFMPEG读取摄像头图像编码为h264的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1115670

相关文章

如何通过海康威视设备网络SDK进行Java二次开发摄像头车牌识别详解

《如何通过海康威视设备网络SDK进行Java二次开发摄像头车牌识别详解》:本文主要介绍如何通过海康威视设备网络SDK进行Java二次开发摄像头车牌识别的相关资料,描述了如何使用海康威视设备网络SD... 目录前言开发流程问题和解决方案dll库加载不到的问题老旧版本sdk不兼容的问题关键实现流程总结前言作为

Java读取InfluxDB数据库的方法详解

《Java读取InfluxDB数据库的方法详解》本文介绍基于Java语言,读取InfluxDB数据库的方法,包括读取InfluxDB的所有数据库,以及指定数据库中的measurement、field、... 首先,创建一个Java项目,用于撰写代码。接下来,配置所需要的依赖;这里我们就选择可用于与Infl

C#读取本地网络配置信息全攻略分享

《C#读取本地网络配置信息全攻略分享》在当今数字化时代,网络已深度融入我们生活与工作的方方面面,对于软件开发而言,掌握本地计算机的网络配置信息显得尤为关键,而在C#编程的世界里,我们又该如何巧妙地读取... 目录一、引言二、C# 读取本地网络配置信息的基础准备2.1 引入关键命名空间2.2 理解核心类与方法

SpringBoot使用Apache POI库读取Excel文件的操作详解

《SpringBoot使用ApachePOI库读取Excel文件的操作详解》在日常开发中,我们经常需要处理Excel文件中的数据,无论是从数据库导入数据、处理数据报表,还是批量生成数据,都可能会遇到... 目录项目背景依赖导入读取Excel模板的实现代码实现代码解析ExcelDemoInfoDTO 数据传输

Python读取TIF文件的两种方法实现

《Python读取TIF文件的两种方法实现》本文主要介绍了Python读取TIF文件的两种方法实现,包括使用tifffile库和Pillow库逐帧读取TIFF文件,具有一定的参考价值,感兴趣的可以了解... 目录方法 1:使用 tifffile 逐帧读取安装 tifffile:逐帧读取代码:方法 2:使用

音视频入门基础:WAV专题(10)——FFmpeg源码中计算WAV音频文件每个packet的pts、dts的实现

一、引言 从文章《音视频入门基础:WAV专题(6)——通过FFprobe显示WAV音频文件每个数据包的信息》中我们可以知道,通过FFprobe命令可以打印WAV音频文件每个packet(也称为数据包或多媒体包)的信息,这些信息包含该packet的pts、dts: 打印出来的“pts”实际是AVPacket结构体中的成员变量pts,是以AVStream->time_base为单位的显

matlab读取NC文件(含group)

matlab读取NC文件(含group): NC文件数据结构: 代码: % 打开 NetCDF 文件filename = 'your_file.nc'; % 替换为你的文件名% 使用 netcdf.open 函数打开文件ncid = netcdf.open(filename, 'NC_NOWRITE');% 查看文件中的组% 假设我们想读取名为 "group1" 的组groupName

ffmpeg面向对象-待定

1.常用对象 rtsp拉流第一步都是avformat_open_input,其入参可以看下怎么用: AVFormatContext *fmt_ctx = NULL;result = avformat_open_input(&fmt_ctx, input_filename, NULL, NULL); 其中fmt_ctx 如何分配内存的?如下 int avformat_open_input(

argodb自定义函数读取hdfs文件的注意点,避免FileSystem已关闭异常

一、问题描述 一位同学反馈,他写的argo存过中调用了一个自定义函数,函数会加载hdfs上的一个文件,但有些节点会报FileSystem closed异常,同时有时任务会成功,有时会失败。 二、问题分析 argodb的计算引擎是基于spark的定制化引擎,对于自定义函数的调用跟hive on spark的是一致的。udf要通过反射生成实例,然后迭代调用evaluate。通过代码分析,udf在

下载/保存/读取 文件,并转成流输出

最近对文件的操作又熟悉了下;现在记载下来:学习在于 坚持!!!不以细小而不为。 实现的是:文件的下载、文件的保存到SD卡、文件的读取输出String 类型、最后是文件转换成流输出;一整套够用了; 重点: 1:   操作网络要记得开线程; 2:更新网络获取的数据 切记用Handler机制; 3:注意代码的可读性(这里面只是保存到SD卡,在项目中切记要对SD卡的有无做判断,然后再获取路径!)