JAVA版本使用FFmpeg实现视频截图

2021-10-30  本文已影响0人  何亮hook_8285

前言

使用javacpp中ffmpeg实现视频文件截图操作。

业务流程:

1.打开视频文件。

2.对视频文件解封装(解析视频协议信息,如编码器、宽高、音频采样率等)。

3.获取视频文件流信息,视频文件中包含视频流和音频流。

4.对视频解码,解码后可以得到解压缩的图像数据,此代码对图像缩放操作。

5.封装图像文件

6.打开编码器

7.将图像数据写入到磁盘。

maven依赖

<dependencies>
    <dependency>
        <groupId>org.bytedeco</groupId>
        <artifactId>javacv-platform</artifactId>
        <version>1.5.1</version>
        <exclusions>
            <exclusion>
                <groupId>org.bytedeco</groupId>
                <artifactId>*</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>org.bytedeco</groupId>
        <artifactId>ffmpeg-platform</artifactId>
        <version>4.1.3-1.5.1</version>
    </dependency>
    <dependency>
        <groupId>cn.hutool</groupId>
        <artifactId>hutool-all</artifactId>
        <version>5.5.2</version>
    </dependency>
</dependencies>

代码实现

package com.xl.ffmpeg;

import org.bytedeco.ffmpeg.avcodec.*;
import org.bytedeco.ffmpeg.avformat.*;
import org.bytedeco.ffmpeg.avutil.*;
import org.bytedeco.ffmpeg.swscale.*;
import org.bytedeco.javacpp.BytePointer;

import java.io.File;
import java.io.IOException;

import static org.bytedeco.ffmpeg.global.avcodec.*;
import static org.bytedeco.ffmpeg.global.avformat.*;
import static org.bytedeco.ffmpeg.global.avutil.*;
import static org.bytedeco.ffmpeg.global.swscale.*;


public class FFMPEGUtil {

    /**
     * 获取播放时间
     * @param videoDuration
     * @return
     */
    static String getPlayTime(long videoDuration){
        long duration = videoDuration + 5000;
        long hours, mins, secs, us;
        secs = duration / AV_TIME_BASE;
        us = duration % AV_TIME_BASE;
        mins = secs / 60;
        secs %= 60;
        hours = mins/ 60;
        mins %= 60;
        return hours+":"+mins+":"+secs+":"+ (100 * us) / AV_TIME_BASE;
    }


    /**
     * 获取视频信息
     * @param videoStream
     */
    static void getVideoInfo(AVStream videoStream){
        //宽度
        int width=videoStream.codecpar().width();
        //高度
        int height=videoStream.codecpar().height();
        //帧率
        double fps=av_q2d(videoStream.avg_frame_rate());
        System.out.println("width="+width+","+"height="+height+",fps="+fps);
    }


    public static void main(String[] args)throws Exception {

        //图片输出宽度
        int outWidth=1024;
        //图片输出高度
        int outHeight=768;
        //截取起始时间,单位秒
        int minute=30;
        int cutStartTime=minute*60;

        //解视频封装
        AVFormatContext avFormatContext=avformat_alloc_context();
        String url="鹿鼎记2:神龙教BD国粤双语中字.mp4";
        avformat_open_input(avFormatContext,url,null,null);
        avformat_find_stream_info(avFormatContext,(AVDictionary)null);


        //获取视频索引
        int videoStreamIndex=av_find_best_stream(avFormatContext,AVMEDIA_TYPE_VIDEO,-1,-1,(AVCodec)null,0);

        //获取视频流
        AVStream videoAvStream=avFormatContext.streams(videoStreamIndex);
        getVideoInfo(videoAvStream);
        System.out.println("total time:"+getPlayTime(avFormatContext.duration()));
        //查找对应视频解码器
        AVCodec videoCodec=avcodec_find_decoder(avFormatContext.streams(videoStreamIndex).codecpar().codec_id());
        AVCodecContext videoCodecCtx=avcodec_alloc_context3(videoCodec);
        avcodec_parameters_to_context(videoCodecCtx, avFormatContext.streams(videoStreamIndex).codecpar());
        avcodec_open2(videoCodecCtx,videoCodec,(AVDictionary)null);




        //初始化解码后视频桢对象
        AVFrame frame = av_frame_alloc();
        //初始化压缩包对象
        AVPacket pkt = av_packet_alloc();

        //初始化转换桢,此对象用于原始码率转换,并且转换每一帧的图片大小
        AVFrame pFrameRGB = av_frame_alloc();

        //初始化像素格式转换的上下文
        SwsContext videoSwsCtx = null;

        //---初始化像素转换的存储对象
        int numBytes = av_image_get_buffer_size(AV_PIX_FMT_YUVJ420P, outWidth,
                outHeight, 1);
        BytePointer buffer = new BytePointer(av_malloc(numBytes));
        av_image_fill_arrays(pFrameRGB.data(), pFrameRGB.linesize(),
                buffer, AV_PIX_FMT_YUVJ420P, outWidth,outHeight, 1);

        boolean flag=true;
        int index=0;

        //设置截取视频其实位置
        Long  pos= new Double(cutStartTime/av_q2d(videoAvStream.time_base())).longValue();
        av_seek_frame(avFormatContext,videoStreamIndex,pos,AVSEEK_FLAG_BACKWARD );


        while(flag){

            //截取10张图片就退出
            if(index==10){
                break;
            }

            //开始获取视频数据,将解复用的数据存储到AVPacket对象
            int re = av_read_frame(avFormatContext,pkt);

            //读取到最后就退出
            if(re != 0)
            {
                flag=false;
                break;
            }



            //视频流,使用视频解码器
            AVCodecContext temp = null;
            if(pkt.stream_index()==videoStreamIndex){
                temp=videoCodecCtx;

            }else { //音频流跳过
                av_packet_unref(pkt);
                continue;
            }

            //显示时间戳
            long pts = pkt.pts();
            //解码时间戳
            long dts = pkt.dts();
            //标志,其中最低为1表示该数据是一个关键帧
            int flags = pkt.flags();


            //数据的时长,以所属媒体流的时间基准为单位
            long duration = pkt.duration();
            //data的大小
            int size = pkt.size();

            //计算出这一桢在视频中的位置 播放时刻值:timestamp(单位秒)
            double now_time=(pts*av_q2d(videoAvStream.time_base()));
            //播放时长值:duration(单位秒)
            double playDuration=duration*av_q2d(videoAvStream.time_base());

            System.out.println("pts="+pts+",dts="+dts+",flags="+flags+",duration="+duration+",size="+size+",now_time="+now_time+",playDuration="+playDuration);

            //发送到线程中解码
            re = avcodec_send_packet(temp,pkt);
            //清理
            av_packet_unref(pkt);
            if(re != 0)
            {
                System.out.println("avcodec_send_packet failed!");
                continue;
            }

            while (true){

                //接收到解码一帧数据
                re = avcodec_receive_frame(temp,frame);
                if(re !=0)  break;
                //如果是视频帧
                if(temp == videoCodecCtx)
                {
                    double second=frame.pts()*av_q2d(videoAvStream.time_base());
                    if(second<=cutStartTime){
                        break;
                    }
                    System.out.println(second);
                    videoSwsCtx = sws_getCachedContext(videoSwsCtx,
                            frame.width(),
                            frame.height(),
                            frame.format(),
                            outWidth,
                            outHeight,
                            AV_PIX_FMT_YUVJ420P,
                            SWS_FAST_BILINEAR,
                            null,null, (double[]) null
                    );

                    sws_scale(videoSwsCtx,frame.data(),frame.linesize(),0,frame.height(),pFrameRGB.data(),pFrameRGB.linesize());
                    pFrameRGB.width(outWidth);
                    pFrameRGB.height(outHeight);
                    pFrameRGB.format(AV_PIX_FMT_YUVJ420P);
                    save_frame(pFrameRGB,index);
                    index++;
                    cutStartTime=cutStartTime+50;
                    pos= new Double(cutStartTime/av_q2d(videoAvStream.time_base())).longValue();
                    av_seek_frame(avFormatContext,videoStreamIndex,pos,AVSEEK_FLAG_BACKWARD );
                    break;
                }


            }



        }

        av_free(pFrameRGB);
        av_free(frame);
        sws_freeContext(videoSwsCtx);
        avcodec_free_context(videoCodecCtx);
        avformat_close_input(avFormatContext);
    }


    /**
     * 存储图片,音视频封装操作
     * @param pFrame
     * @param count
     * @throws IOException
     */
    static void save_frame(AVFrame pFrame,int count) throws IOException {

        int width=pFrame.width();
        int height=pFrame.height();

        AVFormatContext pFormatCtx = avformat_alloc_context();
        pFormatCtx.oformat(av_guess_format("mjpeg", null, null)) ;
        AVIOContext pb = new AVIOContext();
        String saveUrl=System.getProperty("user.dir")+File.separator+count+".jpeg";
        avio_open(pb, saveUrl, AVIO_FLAG_READ_WRITE);
        pFormatCtx.pb(pb);
        AVStream pAVStream = avformat_new_stream(pFormatCtx, null);

        AVCodec pCodec = avcodec_find_encoder(pFormatCtx.oformat().video_codec());


        AVCodecContext pCodeCtx  = avcodec_alloc_context3(pCodec);
        pCodeCtx.time_base().num(1);
        pCodeCtx.time_base().den(25);
        pCodeCtx.pix_fmt(AV_PIX_FMT_YUVJ420P);
        pCodeCtx.codec_type(AVMEDIA_TYPE_VIDEO);
        pCodeCtx.width(width);
        pCodeCtx.height(height);
        pCodeCtx.codec_id(pFormatCtx.oformat().video_codec());
        avcodec_open2(pCodeCtx, pCodec, (AVDictionary)null);

        avcodec_parameters_from_context(pAVStream.codecpar(), pCodeCtx);

        avformat_write_header(pFormatCtx,(AVDictionary)null);

        int y_size = width * height;
        AVPacket pkt=new AVPacket();
        av_new_packet(pkt, y_size);
        int ret=avcodec_send_frame(pCodeCtx, pFrame);
        if(ret<0){
            System.out.println("ret=="+ret);
        }
        avcodec_receive_packet(pCodeCtx, pkt);
        av_write_frame(pFormatCtx, pkt);
        av_packet_unref(pkt);
        av_write_trailer(pFormatCtx);

        avcodec_close(pCodeCtx);
        avio_close(pFormatCtx.pb());
        avformat_free_context(pFormatCtx);
    }

}

上一篇下一篇

猜你喜欢

热点阅读