javacpp-FFmpeg系列之1:視頻拉流解碼成YUVJ420P,並保存爲jpg圖片

javacpp-ffmpeg系列:java

javacpp-FFmpeg系列之1:視頻拉流解碼成YUVJ420P,並保存爲jpg圖片c++

javacpp-FFmpeg系列之2:通用拉流解碼器,支持視頻拉流解碼並轉換爲YUV、BGR24或RGB24等圖像像素數據app

javacpp-FFmpeg系列之3: 圖像數據轉換(BGR與BufferdImage互轉,RGB與BufferdImage互轉)maven

前言

本篇文章算是javacv系列的後續,javacv算是做者在ffmpeg基礎上封裝了一層,咱們算是站在別人的肩膀上,儘管javacv還有不少不足的地方,這個暫且不談。固然此次寫的這篇算是迴歸底層實現了,用別人封裝好的可能兩三行就能夠搞定的東西,此次恰恰想不開去參照了c++的實現,其中拉流部分參考了部分javacpp官方的demo(沒有文檔,等你來填233),解碼和圖片部分參考了c++的實現。ide

 

1、說明以及依賴

本篇文章主要是爲了視頻截圖功能,本篇的代碼能夠複用到其餘好比本地文件的截圖,或者做爲後臺截圖服務的實現庫,也很穩定。測試

使用了javacpp1.4.1版本做爲java調用c++的方式,ffmpeg使用了3.4.2版本ui

maven方式:this

<properties>編碼

<!-- javacpp當前版本 -->
        <javacpp.version>1.4.1</javacpp.version>
        <!-- ffmpeg版本 -->
        <ffmpeg.version>3.4.2</ffmpeg.version>url

</properties>

            <dependency>
                <groupId>org.bytedeco</groupId>
                <artifactId>javacv-platform</artifactId>
                <version>${javacpp.version}</version>
            </dependency>
            <dependency>
                <groupId>org.bytedeco</groupId>
                <artifactId>javacpp</artifactId>
                <version>${javacpp.version}</version>
            </dependency>
            <dependency>
                <groupId>org.bytedeco.javacpp-presets</groupId>
                <artifactId>ffmpeg-platform</artifactId>
                <version>${ffmpeg.version}-${javacpp.version}</version>
            </dependency>

2、實現功能

一、ffmpeg拉流(視頻源能夠是文件和流媒體(rtsp/rtmp/hls/flv等等))

二、將視頻幀解碼爲yuvj420p圖像數據

三、將yuvj402p圖像數據保存爲jpg圖片

3、實現代碼

/**
     * 打開視頻流或者視頻文件,並解碼視頻幀爲YUVJ420P數據
     *
     * @param url -視頻源地址
     * @param out_file 截圖文件保存地址
     * @author eguid
     * @throws IOException
     */
    private int openVideo(String url,String out_file) throws IOException {
        AVFormatContext pFormatCtx = new AVFormatContext(null);
        int i, videoStream;
        AVCodecContext pCodecCtx = null;
        AVCodec pCodec = null;
        AVFrame pFrame = null;
        AVPacket packet = new AVPacket();
        int[] frameFinished = new int[1];
        AVDictionary optionsDict = null;

        AVFrame pFrameRGB = null;
        int numBytes;
        BytePointer buffer = null;
        SwsContext sws_ctx = null;

        // Open video file

        if (avformat_open_input(pFormatCtx, url, null, null) != 0) {
            return -1; // Couldn't open file
        }

        // Retrieve stream information
        if (avformat_find_stream_info(pFormatCtx, (PointerPointer<Pointer>) null) < 0) {
            return -1;// Couldn't find stream information
        }

        av_dump_format(pFormatCtx, 0, url, 0);// Dump information about file onto standard error

        // Find the first video stream
        videoStream = -1;
        for (i = 0; i < pFormatCtx.nb_streams(); i++) {
            if (pFormatCtx.streams(i).codec().codec_type() == AVMEDIA_TYPE_VIDEO) {
                videoStream = i;
                break;
            }
        }
        if (videoStream == -1) {
            return -1; // Didn't find a video stream
        }

        // Get a pointer to the codec context for the video stream
        pCodecCtx = pFormatCtx.streams(videoStream).codec();

        // Find the decoder for the video stream
        pCodec = avcodec_find_decoder(pCodecCtx.codec_id());
        if (pCodec == null) {
            System.err.println("Unsupported codec!");
            return -1; // Codec not found
        }
        // Open codec
        if (avcodec_open2(pCodecCtx, pCodec, optionsDict) < 0) {
            return -1; // Could not open codec
        }

        pFrame = av_frame_alloc();// Allocate video frame

        // Allocate an AVFrame structure
        pFrameRGB = av_frame_alloc();
        if (pFrameRGB == null) {
            return -1;
        }
        int width = pCodecCtx.width(), height = pCodecCtx.height();
        pFrameRGB.width(width);
        pFrameRGB.height(height);
        pFrameRGB.format(AV_PIX_FMT_YUVJ420P);
        // Determine required buffer size and allocate buffer
        numBytes = avpicture_get_size(AV_PIX_FMT_YUVJ420P, width, height);

        buffer = new BytePointer(av_malloc(numBytes));

        sws_ctx = sws_getContext(pCodecCtx.width(), pCodecCtx.height(), pCodecCtx.pix_fmt(), pCodecCtx.width(),
                pCodecCtx.height(), AV_PIX_FMT_YUVJ420P, SWS_BICUBIC, null, null, (DoublePointer) null);

        // Assign appropriate parts of buffer to image planes in pFrameRGB
        // Note that pFrameRGB is an AVFrame, but AVFrame is a superset
        // of AVPicture
        avpicture_fill(new AVPicture(pFrameRGB), buffer, AV_PIX_FMT_YUVJ420P, pCodecCtx.width(), pCodecCtx.height());

        // Read frames and save first five frames to disk
        
        int ret=-1;
        while (av_read_frame(pFormatCtx, packet) >= 0) {
            if (packet.stream_index() == videoStream) {// Is this a packet from the video stream?
                avcodec_decode_video2(pCodecCtx, pFrame, frameFinished, packet);// Decode video frame

                // Did we get a video frame?
                if (frameFinished != null) {
                    // 轉換圖像格式,將解壓出來的YUV420P的圖像轉換爲YUVJ420P的圖像
                    sws_scale(sws_ctx, pFrame.data(), pFrame.linesize(), 0, pCodecCtx.height(), pFrameRGB.data(),
                            pFrameRGB.linesize());
                }

                if (frameFinished[0] != 0 && !pFrame.isNull()) {
                    // Convert the image from its native format to YUVJ420P
                    sws_scale(sws_ctx, pFrame.data(), pFrame.linesize(), 0, pCodecCtx.height(), pFrameRGB.data(),pFrameRGB.linesize());
                    if((ret=saveImg(pFrameRGB,out_file))>=0) {
                        break;
                    }
                }
            }

        }
        av_free_packet(packet);// Free the packet that was allocated by av_read_frame
        // Free the RGB image
        av_free(buffer);

        av_free(pFrameRGB);

        av_free(pFrame);// Free the YUV frame

        avcodec_close(pCodecCtx);// Close the codec

        avformat_close_input(pFormatCtx);// Close the video file

        return ret;
    }

    /**
     * 把YUVJ420P數據編碼保存成jpg圖片
     *
     * @param pFrame -圖像幀
     * @param out_file -截圖文件保存地址
     * @author eguid
     * @return
     */
    private int saveImg(AVFrame pFrame, String out_file) {
        AVPacket pkt = null;
        AVStream pAVStream = null;
        AVCodec codec = null;
        int ret = -1;

        int width = pFrame.width(), height = pFrame.height();
        // 分配AVFormatContext對象
        AVFormatContext pFormatCtx = avformat_alloc_context();
        // 設置輸出文件格式
        pFormatCtx.oformat(av_guess_format("mjpeg", null, null));
        if (pFormatCtx.oformat() == null) {
            return -1;
        }
        try {
            // 建立並初始化一個和該url相關的AVIOContext
            AVIOContext pb = new AVIOContext();
            if (avio_open(pb, out_file, AVIO_FLAG_READ_WRITE) < 0) {// dont open file
                return -1;
            }
            pFormatCtx.pb(pb);
            // 構建一個新stream
            pAVStream = avformat_new_stream(pFormatCtx, codec);
            if (pAVStream == null) {
                return -1;
            }
            int codec_id = pFormatCtx.oformat().video_codec();
            // 設置該stream的信息
            // AVCodecContext pCodecCtx = pAVStream.codec();
            AVCodecContext pCodecCtx = pAVStream.codec();
            pCodecCtx.codec_id(codec_id);
            pCodecCtx.codec_type(AVMEDIA_TYPE_VIDEO);
            pCodecCtx.pix_fmt(AV_PIX_FMT_YUVJ420P);
            pCodecCtx.width(width);
            pCodecCtx.height(height);
            pCodecCtx.time_base().num(1);
            pCodecCtx.time_base().den(25);

            // Begin Output some information
            av_dump_format(pFormatCtx, 0, out_file, 1);
            // End Output some information

            // 查找解碼器
            AVCodec pCodec = avcodec_find_encoder(codec_id);
            if (pCodec == null) {// codec not found
                return -1;
            }
            // 設置pCodecCtx的解碼器爲pCodec
            if (avcodec_open2(pCodecCtx, pCodec, (PointerPointer<Pointer>) null) < 0) {
                System.err.println("Could not open codec.");
                return -1;
            }

            // Write Header
            avformat_write_header(pFormatCtx, (PointerPointer<Pointer>) null);

            // 給AVPacket分配足夠大的空間
            pkt = new AVPacket();
            if (av_new_packet(pkt, width * height * 3) < 0) {
                return -1;
            }
            int[] got_picture = { 0 };
            // encode
            if (avcodec_encode_video2(pCodecCtx, pkt, pFrame, got_picture) >= 0) {
                // flush
                if ((ret = av_write_frame(pFormatCtx, pkt)) >= 0) {
                    // Write Trailer
                    if (av_write_trailer(pFormatCtx) >= 0) {
                        System.err.println("Encode Successful.");
                    }
                }
            }
            return ret;
            // 結束時銷燬
        } finally {
            if (pkt != null) {
                av_free_packet(pkt);
            }
            if (pAVStream != null) {
                avcodec_close(pAVStream.codec());
            }
            if (pFormatCtx != null) {
                avio_close(pFormatCtx.pb());
                avformat_free_context(pFormatCtx);
            }
        }
    }

 

4、測試

使用了這個地址測試了截圖效果:http://live.hkstv.hk.lxdns.com/live/hks/playlist.m3u8"

還不錯

相關文章
相關標籤/搜索