前阵子使用利用树莓派搭建了一个视频监控平台(传送门),不过使用的是JavaCV封装好的OpenCVFrameGrabber
和FFmpegFrameRecorder
。
其实在javacpp
项目集中有提供FFmpeg的JNI封装,能够直接使用FFmpeg API的来处理音视频数据,下面是一个简单的案例,经过FFmpeg API采集摄像头的YUV数据。html
javacpp-ffmpeg依赖:java
<dependency> <groupId>org.bytedeco.javacpp-presets</groupId> <artifactId>ffmpeg</artifactId> <version>${ffmpeg.version}</version> </dependency>
要采集摄像头的YUV数据,首先得知道摄像头的设备名称,能够经过FFmpeg来查找摄像头设备。shell
ffmpeg.exe -list_devices true -f dshow -i dummy
在个人电脑上结果显示以下:
ide
其中 “Integrated Camera” 就是摄像头的设备名称。函数
采集摄像头数据即将摄像头做为视频流输入,经过FFmpeg解码获取视频帧,而后将视频帧转为YUV格式,最后将数据写入文件便可。
下面是FFmpeg解码的流程:编码
根据FFmpeg的解码流程,实现视频帧采集器大概须要通过如下几个步骤:code
FFmpeg初始化orm
首先须要使用av_register_all()
这个函数完成编码器和解码器的初始化,只有初始化了编码器和解码器才能正常使用;另外要采集的是设备,因此还须要调用avdevice_register_all()
完成初始化。视频
分配AVFormatContextxml
接着须要分配一个AVFormatContext,能够经过avformat_alloc_context()
来分配AVFormatContext。
pFormatCtx = avformat_alloc_context();
打开视频流
经过avformat_open_input()
来打开视频流,这里须要注意的是input format要指定为dshow
,能够经过av_find_input_format("dshow")
获取AVInputFormat对象。
ret = avformat_open_input(pFormatCtx, String.format("video=%s", input), av_find_input_format("dshow"), (AVDictionary) null);
查找视频流
须要注意的是,查找视频流以前须要调用avformat_find_stream_info()
,下面是查找视频流的代码:
ret = avformat_find_stream_info(pFormatCtx, (AVDictionary) null); for (int i = 0; i < pFormatCtx.nb_streams(); i++) { if (pFormatCtx.streams(i).codec().codec_type() == AVMEDIA_TYPE_VIDEO) { videoIdx = i; break; } }
打开解码器
能够经过视频流来查找解码器,而后打开解码器,对视频流进行解码,Java代码以下:
pCodecCtx = pFormatCtx.streams(videoIdx).codec(); pCodec = avcodec_find_decoder(pCodecCtx.codec_id()); if (pCodec == null) { throw new FFmpegException("没有找到合适的解码器:" + pCodecCtx.codec_id()); } // 打开解码器 ret = avcodec_open2(pCodecCtx, pCodec, (AVDictionary) null); if (ret != 0) { throw new FFmpegException(ret, "avcodec_open2 解码器打开失败"); }
采集视频帧
最后就是采集视频帧了,这里须要注意的是采集摄像头的视频流解码获得的不必定是YUV格式的视频帧,因此须要对视频帧进行转化一下(videoConverter.scale(pFrame))。
public AVFrame grab() throws FFmpegException { if (av_read_frame(pFormatCtx, pkt) >= 0 && pkt.stream_index() == videoIdx) { ret = avcodec_decode_video2(pCodecCtx, pFrame, got, pkt); if (ret < 0) { throw new FFmpegException(ret, "avcodec_decode_video2 解码失败"); } if (got[0] != 0) { return videoConverter.scale(pFrame); } av_packet_unref(pkt); } return null; }
经过视频解码以后能够获得YUV格式的视频帧,只须要将视频帧的数据写入文件就能够完成整个摄像头YUV数据的采集流程,RGB数据是存在AVFrame.data[0]中,而YUV格式的数据分三个地方存储,Y数据存在AVFrame.data[0],U数据存在AVFrame.data[1],V数据存在AVFrame.data[2],其中U、V的数量是Y的1/4。
因此只须要根据YUV存储的位置和容量取出数据便可:
int fps = 25; Yuv420PGrabber g = new Yuv420PGrabber(); g.open("Integrated Camera"); byte[] y = new byte[g.getVideoWidth() * g.getVideoHeight()]; byte[] u = new byte[g.getVideoWidth() * g.getVideoHeight() / 4]; byte[] v = new byte[g.getVideoWidth() * g.getVideoHeight() / 4]; // 1280x720 OutputStream fos = new FileOutputStream("yuv420p.yuv"); for (int i = 0; i < 200; i ++) { AVFrame avFrame = g.grab(); avFrame.data(0).get(y); avFrame.data(1).get(u); avFrame.data(2).get(v); fos.write(y); fos.write(u); fos.write(v); Thread.sleep(1000 / fps); } fos.flush(); fos.close(); g.close();
采集的YUV数据能够经过YUV Player Deluxe,效果以下:
也能够经过ffplay来播放,命令以下
ffplay.exe -f rawvideo -video_size 1280x720 yuv420p.yuv
效果以下:
=========================================================
视频帧采集器源码可关注公众号 “HiIT青年” 发送 “ffmpeg-yuv” 获取。
关注公众号,阅读更多文章。