通过ffmpeg取摄像头原始数据
windows下打开的设备名“HD webcam”,通过设备管理器查看;linux下一般插入USB设备后,会在生成设备节点/dev/video0,如果没有的话通过lsusb查看,可以看到有挂载信息(usb摄像头驱动vid、pid),此时可能是uvc驱动的问题,有的裁剪版操作系统就会出现这种,比如openwrt。
准备工作:
USB摄像头:“HD webcam”,输入格式MJPEG。
解码器:AV_CODEC_ID_MJPEG
解码后的格式:AV_PIX_FMT_NV12 (可以用yuv420p等,只是我的intel集显带有的h264_qsv硬编码器支持NV12,所以使用这种格式)
环境:windows/linux
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libavdevice/avdevice.h>
#include <libavutil/time.h>
#include <libswresample/swresample.h>
#include <libavformat/avformat.h>
#include <libavutil/mathematics.h>
#include <libavutil/opt.h>
#include <libavutil/imgutils.h>
#include <libavfilter/avfilter.h>
#include <libavfilter/buffersink.h>
#include <libavfilter/buffersrc.h>
void showDshowDeviceOption(char *devName){
AVFormatContext *pFormatCtx = avformat_alloc_context();
AVDictionary* options = NULL;
av_dict_set(&options,"list_options","true",0);
AVInputFormat *iformat = av_find_input_format("dshow");
avformat_open_input(&pFormatCtx,devName,iformat,&options);
avformat_free_context(pFormatCtx);
}
int main(){
int ret;
AVCodecContext *pCodecCtx; //解码器上下文
AVCodec *pCodec; //解码器
AVFormatContext *ifmtCtx;//摄像头输入上下文
AVDictionary *opt = NULL;//配置参数
//初始化开始
avdevice_register_all();
av_register_all();//这个函数在最新版已经弃用,在windows下可以注释掉,但是在linux下会有问题
ifmtCtx= avformat_alloc_context();
AVInputFormat *ifmt=av_find_input_format("dshow"); //windows下dshow
//AVInputFormat *ifmt=av_find_input_format("video4linux2"); //linux
if(ifmt==NULL)
{
printf("输入格式错误.\n");
return -1;
}
av_dict_set_int(&opt, "rtbufsize", 2*1024*1024, 0); //为摄像头图像采集分配的内存,太小会丢失,太大会导致延时。可以设置为128*1024*1024
av_dict_set(&opt,"start_time_realtime",0,0); //直播减少延时的,但是测试没啥效果
av_dict_set(&opt,"video_size","1280x720",0); //设置分辨率,要看是否支持。windows可通过showDshowDeviceOption("dshow")查看。
av_dict_set(&opt,"framerate","30",0);
if(avformat_open_input(&ifmtCtx,"video=HD Camera",ifmt,&opt)!=0) //
{
printf("无法打开输入流.\n");
return -2;
}
if(avformat_find_stream_info(ifmtCtx,NULL)<0)
{
printf("找不到流信息.\n");
return -3;
}
pCodecCtx = avcodec_alloc_context3(NULL);
if (pCodecCtx == NULL)
{
printf("Could not allocate AVCodecContext\n");
return -4;
}
avcodec_parameters_to_context(pCodecCtx, ifmtCtx->streams[0]->codecpar);//由于一些结构体弃用(AVCodecContext *codec;),选择使用codecpar,当前只有视频流,因此streams[0]就为视频流,省略遍历过程。
pCodec=avcodec_find_decoder(pCodecCtx->codec_id); //这里就会得到MJPEG解码器
if(pCodec==NULL)
{
printf("找不到编解码器。\n");
return -5;
}
if(avcodec_open2(pCodecCtx, pCodec,NULL)<0)
{
printf("无法打开编解码器。\n");
return -6;
}
//初始化工作已经完成,接下来取原始图像并解码。
AVPacket *inPacket = av_packet_alloc(); //摄像头取到的数据,待解码,先分配空间。
AVFrame *pFrame = av_frame_alloc(); //分配空间存储解码后的信息
while(1){
if(av_read_frame(ifmtCtx, inPacket)>=0)//取摄像头数据,以阻塞的形式,帧率为30的话大概33ms返回一次。
{
if(inPacket->stream_index==0)
{
ret = avcodec_send_packet(pCodecCtx, inPacket);
if (ret < 0) {
fprintf(stderr, "Error sending a packet for decoding\n");
return -7;
}
while (ret >= 0) {
ret = avcodec_receive_frame(pCodecCtx, pFrame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
break;
else if (ret < 0) {
fprintf(stderr, "Error during decoding\n");
return -8;
}
printf("format:%d width:%d height:%d size:%d\n",pFrame->format,pFrame->width,pFrame->height,pFrame->pkt_size);
/*pFrame就是解码后的数据,此时可以存入ringbuf,另外一个线程做h264编码
此时对解码后的数据做处理,比如加水印、格式转换(如:YUV420p转NV12)*/
}
av_packet_unref(inPacket);//必须要解引用,否则会内存泄露
}
}
}
//当前为不退出程序,故不对分配的内存进行释放。
return 0;
}
编译完成,执行程序:
说明:
1.format:13,是默认的解码后的格式,也就是nv12
2.width、height:像素宽高
3.size:解码后每一帧的数据大小
4.具体数据保存在AVFrame 的uint8_t *data[AV_NUM_DATA_POINTERS];字段中,与yuv结构有关系
接下来会对解码后的图像做水印处理并硬编码为h264.