前陣子使用利用樹莓派搭建了一個視頻監控平臺(傳送門),不過使用的是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」 獲取。
關注公衆號,閱讀更多文章。