基于javacv的视频截图、转码、提取音频

目标

将所有格式的视频转码为H5能播放的mp4格式,也可以截取任意一帧图片

全量依赖

<dependency>
    <groupId>org.bytedeco</groupId>
    <artifactId>javacv-platform</artifactId>
    <version>1.5.3</version>
</dependency>

最小依赖

直接引入javacv-platform依赖,打包后大约有800MB,以下是支持linux-x86_64和windows-x86_64平台的转码和截图功能的最小依赖配置

<properties>
	<javacv.version>1.5.3</javacv.version>
	<javacv.linux-x86_64>linux-x86_64</javacv.linux-x86_64>
	<javacv.windows-x86_64>windows-x86_64</javacv.windows-x86_64>
</properties>

<dependencies>
	<dependency>
	    <groupId>org.bytedeco</groupId>
	    <artifactId>javacv</artifactId>
	    <version>${javacv.version}</version>
	    <exclusions>
	    	<exclusion>
	    		<groupId>org.bytedeco</groupId>
	    		<artifactId>flycapture</artifactId>
	    	</exclusion>
	    	<exclusion>
	    		<groupId>org.bytedeco</groupId>
	    		<artifactId>libdc1394</artifactId>
	    	</exclusion>
	    	<exclusion>
	    		<groupId>org.bytedeco</groupId>
	    		<artifactId>libfreenect</artifactId>
	    	</exclusion>
	    	<exclusion>
	    		<groupId>org.bytedeco</groupId>
	    		<artifactId>libfreenect2</artifactId>
	    	</exclusion>
	    	<exclusion>
	    		<groupId>org.bytedeco</groupId>
	    		<artifactId>librealsense</artifactId>
	    	</exclusion>
	    	<exclusion>
	    		<groupId>org.bytedeco</groupId>
	    		<artifactId>librealsense2</artifactId>
	    	</exclusion>
	    	<exclusion>
	    		<groupId>org.bytedeco</groupId>
	    		<artifactId>videoinput</artifactId>
	    	</exclusion>
	    	<exclusion>
	    		<groupId>org.bytedeco</groupId>
	    		<artifactId>artoolkitplus</artifactId>
	    	</exclusion>
	    	<exclusion>
	    		<groupId>org.bytedeco</groupId>
	    		<artifactId>flandmark</artifactId>
	    	</exclusion>
	    	<exclusion>
	    		<groupId>org.bytedeco</groupId>
	    		<artifactId>leptonica</artifactId>
	    	</exclusion>
	    	<exclusion>
	    		<groupId>org.bytedeco</groupId>
	    		<artifactId>tesseract</artifactId>
	    	</exclusion>
	    </exclusions>
	</dependency>
	<dependency>
	    <groupId>org.bytedeco</groupId>
	    <artifactId>ffmpeg</artifactId>
	    <version>4.2.2-${javacv.version}</version>
	    <classifier>${javacv.linux-x86_64}</classifier>
	</dependency>
	<dependency>
	    <groupId>org.bytedeco</groupId>
	    <artifactId>ffmpeg</artifactId>
	    <version>4.2.2-${javacv.version}</version>
	    <classifier>${javacv.windows-x86_64}</classifier>
	</dependency>
	<dependency>
	    <groupId>org.bytedeco</groupId>
	    <artifactId>javacpp</artifactId>
	    <version>${javacv.version}</version>
	    <classifier>${javacv.linux-x86_64}</classifier>
	</dependency>
	<dependency>
	    <groupId>org.bytedeco</groupId>
	    <artifactId>javacpp</artifactId>
	    <version>${javacv.version}</version>
	    <classifier>${javacv.windows-x86_64}</classifier>
	</dependency>
	<dependency>
	    <groupId>org.bytedeco</groupId>
	    <artifactId>openblas</artifactId>
	    <version>0.3.9-${javacv.version}</version>
	    <classifier>${javacv.linux-x86_64}</classifier>
	</dependency>
	<dependency>
	    <groupId>org.bytedeco</groupId>
	    <artifactId>openblas</artifactId>
	    <version>0.3.9-${javacv.version}</version>
	    <classifier>${javacv.windows-x86_64}</classifier>
	</dependency>
	<dependency>
	    <groupId>org.bytedeco</groupId>
	    <artifactId>opencv</artifactId>
	    <version>4.3.0-${javacv.version}</version>
	    <classifier>${javacv.linux-x86_64}</classifier>
	</dependency>
	<dependency>
	    <groupId>org.bytedeco</groupId>
	    <artifactId>opencv</artifactId>
	    <version>4.3.0-${javacv.version}</version>
	    <classifier>${javacv.windows-x86_64}</classifier>
	</dependency>
</dependencies>

截图配置类

TargetShot.java

package com.videotest.video;

public class TargetShot {

	private String targetParentPath;//截图保存目录,如果不设置,则与源文件目录相同
	
	private String prefix = "";//截图文件名前缀
	
	private String suffix = "_shot";//截图文件简单名后缀
	
	private String extName = "jpg";//截图文件扩展名
	
	private int frameNum = 1;//截取视频第几帧

	public String getTargetParentPath() {
		return targetParentPath;
	}

	public TargetShot setTargetParentPath(String targetParentPath) {
		this.targetParentPath = targetParentPath;
		return this;
	}

	public String getPrefix() {
		return prefix;
	}

	public TargetShot setPrefix(String prefix) {
		this.prefix = prefix;
		return this;
	}

	public String getSuffix() {
		return suffix;
	}

	public TargetShot setSuffix(String suffix) {
		this.suffix = suffix;
		return this;
	}

	public String getExtName() {
		return extName;
	}

	public TargetShot setExtName(String extName) {
		this.extName = extName;
		return this;
	}

	public int getFrameNum() {
		return frameNum;
	}

	public TargetShot setFrameNum(int frameNum) {
		if (frameNum < 1) {
			frameNum = 1;
		}
		this.frameNum = frameNum;
		return this;
	}

	@Override
	public String toString() {
		return "TargetShot [targetParentPath=" + targetParentPath + ", prefix=" + prefix + ", suffix=" + suffix
				+ ", extName=" + extName + ", frameNum=" + frameNum + "]";
	}
	
}

转码配置类

TargetVideo.class

package com.videotest.video;

public class TargetVideo {

	private String targetParentPath;//转码后的文件保存目录,如果不设置,则与源文件目录相同
	
	private String prefix = "";//转码后的文件名前缀
	
	private String suffix = "_recode";//转码后的文件简单名后缀
	
	private boolean closeSound;//转码后的文件是否删除音轨,默认不删除

	public String getTargetParentPath() {
		return targetParentPath;
	}

	public TargetVideo setTargetParentPath(String targetParentPath) {
		this.targetParentPath = targetParentPath;
		return this;
	}

	public String getPrefix() {
		return prefix;
	}

	public TargetVideo setPrefix(String prefix) {
		this.prefix = prefix;
		return this;
	}

	public String getSuffix() {
		return suffix;
	}

	public TargetVideo setSuffix(String suffix) {
		this.suffix = suffix;
		return this;
	}

	public boolean isCloseSound() {
		return closeSound;
	}

	public TargetVideo setCloseSound(boolean closeSound) {
		this.closeSound = closeSound;
		return this;
	}

	@Override
	public String toString() {
		return "TargetVideo [targetParentPath=" + targetParentPath + ", prefix=" + prefix + ", suffix=" + suffix
				+ ", closeSound=" + closeSound + "]";
	}
	
}

提取音频配置类

TargetSound.class

package com.videotest.video;

public class TargetSound {

	private String targetParentPath;//截图保存目录,如果不设置,则与源文件目录相同
	
	private String prefix = "";//截图文件名前缀
	
	private String suffix = "_sound";//截图文件简单名后缀
	
	private String extName = "mp3";//截图文件扩展名

	public String getTargetParentPath() {
		return targetParentPath;
	}

	public TargetSound setTargetParentPath(String targetParentPath) {
		this.targetParentPath = targetParentPath;
		return this;
	}

	public String getPrefix() {
		return prefix;
	}

	public TargetSound setPrefix(String prefix) {
		this.prefix = prefix;
		return this;
	}

	public String getSuffix() {
		return suffix;
	}

	public TargetSound setSuffix(String suffix) {
		this.suffix = suffix;
		return this;
	}

	public String getExtName() {
		return extName;
	}

	public TargetSound setExtName(String extName) {
		this.extName = extName;
		return this;
	}

	@Override
	public String toString() {
		return "TargetSound [targetParentPath=" + targetParentPath + ", prefix=" + prefix + ", suffix=" + suffix
				+ ", extName=" + extName + "]";
	}
	
}

源视频处理结果类

VideoInfo.class

package com.videotest.video;

public class VideoInfo<T> {
	
	private T id;//数据唯一标识

	private boolean sourceExists;//源文件是否存在
	
	private boolean sourceIsFile;//源文件是否文件
	
	private String sourceAbsolutePath;//sourceParentPath + "/" + sourceName
	
	private String sourceParentPath = "";//源文件目录
	
	private String sourceName = ""; //sourceSimpleName + ["." + extName]
	
	private String sourceSimpleName = "";
	
	private String sourceExtName = "";//扩展名

	/**
	 * @See {org.bytedeco.ffmpeg.global.avcodec}
	 */
	private int sourceVideoCodec;//视频编码格式
	
	/**
	 * @See {org.bytedeco.ffmpeg.global.avcodec}
	 */
	private int sourceAudioCodec;//音频编码格式
	
	private String sourceFormat = "";//封装格式
	
	private boolean shotExists;//处理结果是否存在截图
	
	private String targetShotPath;//截图绝对路径
	
	private String targetShotName;//截图文件名
	
	private boolean videoExists;//处理结果是否存在转码后的视频
	
	private String targetVideoPath;//转码后的视频绝对路径
	
	private String targetVideoName;//转码后的视频文件名
	
	private boolean soundExists;//处理结果是否存在音频
	
	private String targetSoundPath;//提取的音频绝对路径
	
	private String targetSoundName;//提取的音频文件名
	
	public VideoInfo(T id) {
		this.id = id;
	}

	public T getId() {
		return id;
	}

	void setId(T id) {
		this.id = id;
	}

	public boolean isSourceExists() {
		return sourceExists;
	}

	void setSourceExists(boolean sourceExists) {
		this.sourceExists = sourceExists;
	}

	public boolean isSourceIsFile() {
		return sourceIsFile;
	}

	void setSourceIsFile(boolean sourceIsFile) {
		this.sourceIsFile = sourceIsFile;
	}

	public String getSourceAbsolutePath() {
		return sourceAbsolutePath;
	}

	void setSourceAbsolutePath(String sourceAbsolutePath) {
		this.sourceAbsolutePath = sourceAbsolutePath;
	}

	public String getSourceParentPath() {
		return sourceParentPath;
	}

	void setSourceParentPath(String sourceParentPath) {
		this.sourceParentPath = sourceParentPath;
	}

	public String getSourceName() {
		return sourceName;
	}

	void setSourceName(String sourceName) {
		this.sourceName = sourceName;
	}

	public String getSourceSimpleName() {
		return sourceSimpleName;
	}

	void setSourceSimpleName(String sourceSimpleName) {
		this.sourceSimpleName = sourceSimpleName;
	}

	public String getSourceExtName() {
		return sourceExtName;
	}

	void setSourceExtName(String sourceExtName) {
		this.sourceExtName = sourceExtName;
	}

	public int getSourceVideoCodec() {
		return sourceVideoCodec;
	}

	void setSourceVideoCodec(int sourceVideoCodec) {
		this.sourceVideoCodec = sourceVideoCodec;
	}

	public int getSourceAudioCodec() {
		return sourceAudioCodec;
	}

	void setSourceAudioCodec(int sourceAudioCodec) {
		this.sourceAudioCodec = sourceAudioCodec;
	}

	public String getSourceFormat() {
		return sourceFormat;
	}

	void setSourceFormat(String sourceFormat) {
		this.sourceFormat = sourceFormat;
	}

	public boolean getShotExists() {
		return shotExists;
	}

	void setShotExists(boolean shotExists) {
		this.shotExists = shotExists;
	}

	public String getTargetShotPath() {
		return targetShotPath;
	}

	void setTargetShotPath(String targetShotPath) {
		this.targetShotPath = targetShotPath;
	}

	public String getTargetShotName() {
		return targetShotName;
	}

	void setTargetShotName(String targetShotName) {
		this.targetShotName = targetShotName;
	}

	public boolean getVideoExists() {
		return videoExists;
	}

	void setVideoExists(boolean videoExists) {
		this.videoExists = videoExists;
	}

	public String getTargetVideoPath() {
		return targetVideoPath;
	}

	void setTargetVideoPath(String targetVideoPath) {
		this.targetVideoPath = targetVideoPath;
	}

	public String getTargetVideoName() {
		return targetVideoName;
	}

	void setTargetVideoName(String targetVideoName) {
		this.targetVideoName = targetVideoName;
	}

	public boolean isSoundExists() {
		return soundExists;
	}

	void setSoundExists(boolean soundExists) {
		this.soundExists = soundExists;
	}

	public String getTargetSoundPath() {
		return targetSoundPath;
	}

	void setTargetSoundPath(String targetSoundPath) {
		this.targetSoundPath = targetSoundPath;
	}

	public String getTargetSoundName() {
		return targetSoundName;
	}

	void setTargetSoundName(String targetSoundName) {
		this.targetSoundName = targetSoundName;
	}

	@Override
	public String toString() {
		return "VideoInfo [id=" + id + ", sourceExists=" + sourceExists + ", sourceIsFile=" + sourceIsFile
				+ ", sourceAbsolutePath=" + sourceAbsolutePath + ", sourceParentPath=" + sourceParentPath
				+ ", sourceName=" + sourceName + ", sourceSimpleName=" + sourceSimpleName + ", sourceExtName="
				+ sourceExtName + ", sourceVideoCodec=" + sourceVideoCodec + ", sourceAudioCodec=" + sourceAudioCodec
				+ ", sourceFormat=" + sourceFormat + ", shotExists=" + shotExists + ", targetShotPath=" + targetShotPath
				+ ", targetShotName=" + targetShotName + ", videoExists=" + videoExists + ", targetVideoPath="
				+ targetVideoPath + ", targetVideoName=" + targetVideoName + ", soundExists=" + soundExists
				+ ", targetSoundPath=" + targetSoundPath + ", targetSoundName=" + targetSoundName + "]";
	}

}

核心类

Video.class

package com.videotest.video;

import java.awt.image.BufferedImage;
import java.io.File;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;

import javax.imageio.ImageIO;

import org.bytedeco.ffmpeg.global.avcodec;
import org.bytedeco.ffmpeg.global.avutil;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.FFmpegFrameRecorder;
import org.bytedeco.javacv.FFmpegLogCallback;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.Java2DFrameConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public final class Video<T> {
	private static final Logger logger = LoggerFactory.getLogger(Video.class);
	
	private static final String DEFAULT_VIDEO_EXTNAME = "mp4";
	
	private static final String DEFAULT_FORMAT = "mov,mp4,m4a,3gp,3g2,mj2";
	
	private VideoInfo<T> videoInfo;
	
	private File sourceFile;
	
	private TargetShot targetShot;
	
	private TargetVideo targetVideo;
	
	private TargetSound targetSound;

	private Video (T id) {
		this.videoInfo = new VideoInfo<T>(id);
	}
	
	public static <T> Video<T> getInstance(T id) {
		return new Video<T>(id);
	}
	
	public Video<T> sourceFile (String sourceAbsolutePath) {
		return this.sourceFile(new File(sourceAbsolutePath));
	}
	
	public Video<T> sourceFile (String sourceParentPath, String sourceName) {
		return this.sourceFile(new File(sourceParentPath, sourceName));
	}
	
	public Video<T> sourceFile (File sourceFile) {
		this.sourceFile = sourceFile;
		return this;
	}
	
	/**
	 * 开启截图功能
	 * @return
	 */
	public Video<T> enableShot () {
		this.targetShot = new TargetShot();
		return this;
	}
	
	public Video<T> enableShot (TargetShot targetShot) {
		this.targetShot = targetShot;
		return this;
	}
	
	/**
	 * 开启转码功能
	 * @return
	 */
	public Video<T> enableVideo () {
		this.targetVideo = new TargetVideo();
		return this;
	}
	
	public Video<T> enableVideo (TargetVideo targetVideo) {
		this.targetVideo = targetVideo;
		return this;
	}
	
	/**
	 * 开启提取音频功能
	 * @return
	 */
	public Video<T> enableSound () {
		this.targetSound = new TargetSound();
		return this;
	}
	
	public Video<T> enableSound (TargetSound targetSound) {
		this.targetSound = targetSound;
		return this;
	}
	
	/**
	 * 开始转码截图
	 * @return
	 */
	public VideoInfo<T> start () {
		avutil.av_log_set_level(avutil.AV_LOG_INFO);
		FFmpegLogCallback.set();
		VideoInfo<T> videoInfo = this.videoInfo;
		File sourceFile = this.sourceFile;
		TargetVideo targetVideo = this.targetVideo;
		TargetShot targetShot = this.targetShot;
		TargetSound targetSound = this.targetSound;
		FFmpegFrameGrabber frameGrabber = null;
		try {
			videoInfo.setSourceExists(sourceFile.exists());
			videoInfo.setSourceIsFile(sourceFile.isFile());
			videoInfo.setSourceAbsolutePath(sourceFile.getAbsolutePath().replace("\\", "/"));
			videoInfo.setSourceParentPath(sourceFile.getParent().replace("\\", "/"));
			if (targetVideo != null && targetVideo.getTargetParentPath() == null) {
				targetVideo.setTargetParentPath(videoInfo.getSourceParentPath());
			}
			if (targetShot != null && targetShot.getTargetParentPath() == null) {
				targetShot.setTargetParentPath(videoInfo.getSourceParentPath());
			}
			if (targetSound != null && targetSound.getTargetParentPath() == null) {
				targetSound.setTargetParentPath(videoInfo.getSourceParentPath());
			}
			videoInfo.setSourceName(sourceFile.getName());
			if (!sourceFile.isFile()) {
				return videoInfo;
			}
			int dotLastIndex = videoInfo.getSourceName().lastIndexOf(".");
			if (dotLastIndex > 0 && dotLastIndex < videoInfo.getSourceName().length() - 1) {
				videoInfo.setSourceSimpleName(videoInfo.getSourceName().substring(0, dotLastIndex));
				videoInfo.setSourceExtName(videoInfo.getSourceName().substring(dotLastIndex + 1));
			} else {
				videoInfo.setSourceSimpleName(videoInfo.getSourceName());
			}
			frameGrabber = FFmpegFrameGrabber.createDefault(sourceFile);
			frameGrabber.start();
			videoInfo.setSourceVideoCodec(frameGrabber.getVideoCodec());
			videoInfo.setSourceAudioCodec(frameGrabber.getAudioCodec());
			videoInfo.setSourceFormat(frameGrabber.getFormat());
			if (!this.isVideo()) {
				throw new org.bytedeco.javacv.FrameGrabber.Exception("[" + videoInfo.getSourceAbsolutePath() + "] 不是视频文件");
			}
			if (targetShot != null) {
				this.shot(frameGrabber);
			}
		} catch (Exception e) {
			logger.warn(sourceFile.getAbsolutePath() + " -> error", e);
		} finally {
			if (frameGrabber != null) {
				try {
					frameGrabber.close();
				} catch (org.bytedeco.javacv.FrameGrabber.Exception e) {
					logger.warn("frameGrabber.close异常", e);
				}
				frameGrabber = null;
			}
		}
		if (targetVideo != null) {
			this.convert2Mp4();
		}
		if (targetSound != null && this.hasAudio()) {
			this.extractSound();
		}
		return videoInfo;
	}

	private void shot (FFmpegFrameGrabber frameGrabber) throws Exception {
		TargetShot targetShot = this.targetShot;
		VideoInfo<T> videoInfo = this.videoInfo;
		Frame frame = null;
		int i = 0;
        int fps = (int) frameGrabber.getFrameRate();
        int lengthInFrames = frameGrabber.getLengthInFrames();
        int frameNum = targetShot.getFrameNum();
        BufferedImage buffer = null;
        while (i < lengthInFrames) {
            frame = frameGrabber.grabImage();
            if (frameNum <= 1
            		|| i >= fps && i % (fps * frameNum) == 0
            		|| i == (lengthInFrames - 1) / fps * fps) {
                Java2DFrameConverter converter = new Java2DFrameConverter();
                buffer = converter.getBufferedImage(frame);
                File file = new File(targetShot.getTargetParentPath(), targetShot.getPrefix() + videoInfo.getSourceSimpleName() + targetShot.getSuffix() + "." + targetShot.getExtName());
                Files.deleteIfExists(file.toPath());
                ImageIO.write(buffer, targetShot.getExtName(), file);
                videoInfo.setShotExists(true);
                videoInfo.setTargetShotPath(file.getAbsolutePath().replace("\\", "/"));
                videoInfo.setTargetShotName(file.getName());
                logger.info(videoInfo.getTargetShotPath() + " -> 截图完成");
                break;
            }
            i++;
        }
        frameGrabber.stop();
	}
	
	private void convert2Mp4 () {
		VideoInfo<T> videoInfo = this.videoInfo;
		TargetVideo targetVideo = this.targetVideo;
		File sourceFile = this.sourceFile;
		FFmpegFrameGrabber frameGrabber = null;
		FFmpegFrameRecorder recorder = null;
		try {
			File videoOutFile = new File(targetVideo.getTargetParentPath());
			if (videoOutFile.isFile()) {
				throw new FileAlreadyExistsException("[" + targetVideo.getTargetParentPath() + "] 是文件并且已存在");
			}
			if (!videoOutFile.exists()) {
				videoOutFile.mkdirs();
			}
			File file = new File(targetVideo.getTargetParentPath(), targetVideo.getPrefix() + videoInfo.getSourceSimpleName() + targetVideo.getSuffix() + "." + DEFAULT_VIDEO_EXTNAME);
			String videoOutFilePath = file.getAbsolutePath().replace("\\", "/");
			if (this.canPlayInH5IgnoreExtName() && (!targetVideo.isCloseSound() || !this.hasAudio())) {
				Files.copy(new File(videoInfo.getSourceParentPath(), videoInfo.getSourceName()).toPath(), new File(videoOutFilePath).toPath(), StandardCopyOption.REPLACE_EXISTING);
			} else {
				frameGrabber = FFmpegFrameGrabber.createDefault(sourceFile);
				frameGrabber.start();
				Frame captured_frame = null;
				recorder = new FFmpegFrameRecorder(videoOutFilePath, frameGrabber.getImageWidth(), frameGrabber.getImageHeight(), frameGrabber.getAudioChannels());
				recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
				recorder.setFormat(DEFAULT_VIDEO_EXTNAME);
				recorder.setFrameRate(frameGrabber.getFrameRate());
				int bitrate = Long.valueOf(frameGrabber.getFormatContext().bit_rate()).intValue();
				if (bitrate > 0) {
					int videoBitrate = frameGrabber.getVideoBitrate();
					int audioBitrate = frameGrabber.getAudioBitrate();
					if (audioBitrate < 1) {
						recorder.setVideoBitrate(bitrate);
					} else if (videoBitrate < 1) {
						recorder.setAudioBitrate(bitrate);
					} else {
						double rate = (videoBitrate + audioBitrate) * 1.0 / bitrate;
						if (rate > 0.95 && rate < 1.05) {
							recorder.setVideoBitrate(videoBitrate);
							recorder.setAudioBitrate(audioBitrate);
						} else {
							recorder.setVideoBitrate(Math.round(bitrate * 1.0f * videoBitrate / (videoBitrate + audioBitrate)));
							recorder.setAudioBitrate(Math.round(bitrate * 1.0f * audioBitrate / (videoBitrate + audioBitrate)));
						}
					}
				}
				recorder.setSampleRate(frameGrabber.getSampleRate());
				recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC);
				recorder.start();
				if (targetVideo.isCloseSound()) {
					while (true) {
						captured_frame = frameGrabber.grabImage();
						if (captured_frame == null) {
							logger.info(videoOutFilePath + " 转码完成");
							break;
						}
						if (frameGrabber.getTimestamp() > -1) {
							recorder.setTimestamp(frameGrabber.getTimestamp());
						}
						recorder.record(captured_frame);
					}
				} else {
					while (true) {
						captured_frame = frameGrabber.grabFrame();
						if (captured_frame == null) {
							logger.info(videoOutFilePath + " 转码完成");
							break;
						}
						if (frameGrabber.getTimestamp() > -1) {
							recorder.setTimestamp(frameGrabber.getTimestamp());
						}
						recorder.record(captured_frame);
					}
				}
			}
			videoInfo.setVideoExists(true);
			videoInfo.setTargetVideoPath(videoOutFilePath);
			videoInfo.setTargetVideoName(file.getName());
		} catch (Exception e) {
			logger.warn(sourceFile.getAbsolutePath() + " -> 转码 error", e);
		} finally {
			if (recorder != null) {
				try {
					recorder.close();
				} catch (Exception e) {
					logger.warn("recorder.close异常", e);
				}
			}
			if (frameGrabber != null) {
				try {
					frameGrabber.close();
				} catch (org.bytedeco.javacv.FrameGrabber.Exception e) {
					logger.warn("frameGrabber.close异常", e);
				}
			}
		}
	}
	
	private void extractSound() {
		VideoInfo<T> videoInfo = this.videoInfo;
		TargetSound targetSound = this.targetSound;
		File sourceFile = this.sourceFile;
		FFmpegFrameGrabber frameGrabber = null;
		FFmpegFrameRecorder recorder = null;
		try {
			File soundOutFile = new File(targetSound.getTargetParentPath());
			if (soundOutFile.isFile()) {
				throw new FileAlreadyExistsException("[" + targetSound.getTargetParentPath() + "] 是文件并且已存在");
			}
			if (!soundOutFile.exists()) {
				soundOutFile.mkdirs();
			}
			File file = new File(targetSound.getTargetParentPath(), targetSound.getPrefix() + videoInfo.getSourceSimpleName() + targetSound.getSuffix() + "." + targetSound.getExtName());
			String soundOutFilePath = file.getAbsolutePath().replace("\\", "/");
			
			frameGrabber = FFmpegFrameGrabber.createDefault(sourceFile);
			frameGrabber.start();
			Frame captured_frame = null;
			recorder = new FFmpegFrameRecorder(soundOutFilePath, frameGrabber.getAudioChannels());
			recorder.setFormat(targetSound.getExtName());
			recorder.setSampleRate(frameGrabber.getSampleRate());
			int bitrate = Long.valueOf(frameGrabber.getFormatContext().bit_rate()).intValue();
			if (bitrate > 0) {
				int videoBitrate = frameGrabber.getVideoBitrate();
				int audioBitrate = frameGrabber.getAudioBitrate();
				if (audioBitrate > 0) {
					if (videoBitrate < 1) {
						recorder.setAudioBitrate(bitrate);
					} else {
						double rate = (videoBitrate + audioBitrate) * 1.0 / bitrate;
						if (rate > 0.95 && rate < 1.05) {
							recorder.setAudioBitrate(audioBitrate);
						} else {
							recorder.setAudioBitrate(Math.round(bitrate * 1.0f * audioBitrate / (videoBitrate + audioBitrate)));
						}
					}
				}
			}
			recorder.start();
			while (true) {
				captured_frame = frameGrabber.grabSamples();
				if (captured_frame == null) {
					logger.info(soundOutFilePath + " 音频提取完成");
					break;
				}
				if (frameGrabber.getTimestamp() > -1) {
					recorder.setTimestamp(frameGrabber.getTimestamp());
				}
				recorder.recordSamples(captured_frame.samples);
			}
			videoInfo.setTargetSoundPath(soundOutFilePath);
			videoInfo.setTargetSoundName(file.getName());
			videoInfo.setSoundExists(true);
		} catch (Exception e) {
			logger.warn(sourceFile.getAbsolutePath() + " -> 音频提取 error", e);
		} finally {
			if (recorder != null) {
				try {
					recorder.close();
				} catch (Exception e) {
					logger.warn("recorder.close异常", e);
				}
			}
			if (frameGrabber != null) {
				try {
					frameGrabber.close();
				} catch (org.bytedeco.javacv.FrameGrabber.Exception e) {
					logger.warn("frameGrabber.close异常", e);
				}
			}
		}
	}
	
	private boolean isVideo () {
		return this.videoInfo.getSourceVideoCodec() != avcodec.AV_CODEC_ID_NONE;
	}
	
	private boolean hasAudio () {
		return this.videoInfo.getSourceAudioCodec() != avcodec.AV_CODEC_ID_NONE;
	}
	
	private boolean canPlayInH5IgnoreExtName () {
		if (!this.isVideo()) {
			return false;
		}
		VideoInfo<T> videoInfo = this.videoInfo;
		if (this.hasAudio() && videoInfo.getSourceAudioCodec() != avcodec.AV_CODEC_ID_AAC) {
			return false;
		}
		if (videoInfo.getSourceVideoCodec() != avcodec.AV_CODEC_ID_H264) {
			return false;
		}
		if (!DEFAULT_FORMAT.equals(videoInfo.getSourceFormat())) {
			return false;
		}
		return true;
	}
	
}

怎么使用

package com.videotest;

import java.io.File;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.videotest.video.TargetShot;
import com.videotest.video.TargetSound;
import com.videotest.video.TargetVideo;
import com.videotest.video.Video;
import com.videotest.video.VideoInfo;


public class Test {
	private static final Logger logger = LoggerFactory.getLogger(Test.class);

	public static void main(String[] args) throws JsonProcessingException {
		
		File file = new File("D:/data");
		File[] listFiles = file.listFiles();
		for (File file2 : listFiles) {
			VideoInfo<Long> videoInfo = Video
				.getInstance(4622L)
//				.getInstance("234567765")
//				.sourceFile("D:/案例视频/测试android", "同业36-安卓.mp4")
//				.sourceFile("D:/1607589556364qq.mp4")
				.sourceFile(file2)
				.enableShot(null)
//				.enableShot(new TargetShot())
//				.enableShot(new TargetShot().setExtName("png").setPrefix("tmp_").setTargetParentPath("D:/data"))
				.enableVideo(null)
//				.enableVideo(new TargetVideo())
//				.enableVideo(new TargetVideo().setCloseSound(true)) //关闭音频
				.enableSound()
//				.enableSound(new TargetSound().setExtName("mp3").setPrefix("tmp_").setSuffix("").setTargetParentPath("D:/data"))
				.start();
			String writeValueAsString = new ObjectMapper().writeValueAsString(videoInfo);
			logger.info(writeValueAsString);
		}
	}
}

  • 3
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值