zoukankan      html  css  js  c++  java
  • JavaCV FFmpeg采集摄像头YUV视频数据

    前阵子使用利用树莓派搭建了一个视频监控平台(传送门),不过使用的是JavaCV封装好的OpenCVFrameGrabberFFmpegFrameRecorder
    其实在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青年
    关注公众号,阅读更多文章。

  • 相关阅读:
    Security and Cryptography in Python
    Security and Cryptography in Python
    Security and Cryptography in Python
    Security and Cryptography in Python
    Security and Cryptography in Python
    Security and Cryptography in Python
    基于分布式锁解决定时任务重复问题
    基于Redis的Setnx实现分布式锁
    基于数据库悲观锁的分布式锁
    使用锁解决电商中的超卖
  • 原文地址:https://www.cnblogs.com/itqn/p/13789079.html
Copyright © 2011-2022 走看看