JavaCV FFmpeg采集攝像頭YUV視頻數據


前陣子使用利用樹莓派搭建了一個視頻監控平台(傳送門),不過使用的是JavaCV封裝好的OpenCVFrameGrabberFFmpegFrameRecorder

其他關於JavaCV的文章,可以通過下面的鏈接查看:
JavaCV-開發系列文章匯總篇(https://www.cnblogs.com/itqn/p/14696221.html)

其實在javacpp項目集中有提供FFmpeg的JNI封裝,可以直接使用FFmpeg API的來處理音視頻數據,下面是一個簡單的案例,通過FFmpeg API采集攝像頭的YUV數據。

javacpp-ffmpeg依賴:

<dependency>
    <groupId>org.bytedeco.javacpp-presets</groupId>
    <artifactId>ffmpeg</artifactId>
    <version>${ffmpeg.version}</version>
</dependency>
1. 查找攝像頭設備

要采集攝像頭的YUV數據,首先得知道攝像頭的設備名稱,可以通過FFmpeg來查找攝像頭設備。

ffmpeg.exe -list_devices true -f dshow -i dummy  

在我的電腦上結果顯示如下:

其中 “Integrated Camera” 就是攝像頭的設備名稱。

2. 利用FFmpeg解碼

采集攝像頭數據即將攝像頭作為視頻流輸入,通過FFmpeg解碼獲取視頻幀,然后將視頻幀轉為YUV格式,最后將數據寫入文件即可。
下面是FFmpeg解碼的流程:

3. 開發視頻幀采集器

根據FFmpeg的解碼流程,實現視頻幀采集器大概需要經過以下幾個步驟:

FFmpeg初始化

首先需要使用av_register_all()這個函數完成編碼器和解碼器的初始化,只有初始化了編碼器和解碼器才能正常使用;另外要采集的是設備,所以還需要調用avdevice_register_all()完成初始化。

分配AVFormatContext

接着需要分配一個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;
}
4. 將視頻幀數據寫入文件

通過視頻解碼之后可以得到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();
5. 播放采集的YUV數據

采集的YUV數據可以通過YUV Player Deluxe,效果如下:

也可以通過ffplay來播放,命令如下

ffplay.exe -f rawvideo -video_size 1280x720 yuv420p.yuv

效果如下:

=========================================================
視頻幀采集器源碼可關注公眾號 “HiIT青年” 發送 “ffmpeg-yuv” 獲取。(如果沒有收到回復,可能是你之前取消過關注。)

HiIT青年
關注公眾號,閱讀更多文章。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM