无法将一系列 JPEG 图像编码为 MPEG 视频
Unable to encode series of JPEG images to MPEG video
我需要将 JPEG 图像转换为 MPEG 视频,并且能够轻松设置每帧显示的持续时间。
我已经尝试过 JMF、Xuggler 和 JCodec,但都有各自的问题。
对于 JMF、Xuggler、JCode 或什至 JavaCV(尚未尝试过)得到很好解释的任何解决方案,我将不胜感激。
以下是我的编码尝试。
我的测试菜单和主菜单,用于测试 Xuggler 方法或 JCodec:
package com.conuretech.video_assembler;
import com.xuggle.xuggler.IContainerFormat;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
*
* @author ANaim
*/
public class TestVideo {
public static void main (String [] args)
{
if (args[0].equalsIgnoreCase("1"))
{
System.out.println("Testing Xuggler ...");
Iterator<IContainerFormat> iterator = IContainerFormat.getInstalledOutputFormats().iterator();
while (iterator.hasNext())
{
System.out.println(iterator.next().toString());
}
System.out.println("Testing xuggler...");
//C:\Users\Public\Pictures\Sample_Pictures_2.jpg
XugglerJpegImagesToMpegVideo newjpegImagesToMpegVideo = new XugglerJpegImagesToMpegVideo();
newjpegImagesToMpegVideo.setFrameRate(25);
newjpegImagesToMpegVideo.setOutputFilenamePath("C:/Users/Public/Pictures/Sample_Pictures_2/video.mpg");
List<String> jpegImages = new ArrayList<String>();
jpegImages.add("C:/Users/Public/Pictures/Sample_Pictures_2/1.jpg");
jpegImages.add("C:/Users/Public/Pictures/Sample_Pictures_2/2.jpg");
jpegImages.add("C:/Users/Public/Pictures/Sample_Pictures_2/3.jpg");
jpegImages.add("C:/Users/Public/Pictures/Sample_Pictures_2/4.jpg");
jpegImages.add("C:/Users/Public/Pictures/Sample_Pictures_2/5.jpg");
jpegImages.add("C:/Users/Public/Pictures/Sample_Pictures_2/6.jpg");
jpegImages.add("C:/Users/Public/Pictures/Sample_Pictures_2/7.jpg");
jpegImages.add("C:/Users/Public/Pictures/Sample_Pictures_2/8.jpg");
newjpegImagesToMpegVideo.setJpegFilePathList(jpegImages);
newjpegImagesToMpegVideo.convertJpegFramesToMpegVideo();
}
else if (args[0].equalsIgnoreCase("2"))
{
System.out.println("Testing JCodec...");
jCodecJpegImagesToMpegVideo newJCodecJpegImagesToMpegVideo = new jCodecJpegImagesToMpegVideo();
List<String> jpegImages = new ArrayList<String>();
jpegImages.add("C:/Users/Public/Pictures/Sample_Pictures_2/1.jpg");
jpegImages.add("C:/Users/Public/Pictures/Sample_Pictures_2/2.jpg");
jpegImages.add("C:/Users/Public/Pictures/Sample_Pictures_2/3.jpg");
jpegImages.add("C:/Users/Public/Pictures/Sample_Pictures_2/4.jpg");
jpegImages.add("C:/Users/Public/Pictures/Sample_Pictures_2/5.jpg");
jpegImages.add("C:/Users/Public/Pictures/Sample_Pictures_2/6.jpg");
jpegImages.add("C:/Users/Public/Pictures/Sample_Pictures_2/7.jpg");
jpegImages.add("C:/Users/Public/Pictures/Sample_Pictures_2/8.jpg");
newJCodecJpegImagesToMpegVideo.setJpegFilePathList(jpegImages);
newJCodecJpegImagesToMpegVideo.convertJpegFramesToMpegVideo();
}
else
{
}
System.exit(0);
}
}
我的pom.xml(包括 Xuggler 和 JCodec 依赖项):
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.conuretech</groupId>
<artifactId>Video_Assembler</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Video_Assembler</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!--<mainClass>com.conuretech.video_assembler.TestVideo</mainClass>-->
<mainClass>com.conuretech.video_assembler.MainApp</mainClass>
</properties>
<organization>
<!-- Used as the 'Vendor' for JNLP generation -->
<name>conuretech</name>
</organization>
<repositories>
<repository>
<id>xuggle repo</id>
<url>http://xuggle.googlecode.com/svn/trunk/repo/share/java/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.jcodec</groupId>
<artifactId>jcodec</artifactId>
<version>0.1.9</version>
</dependency>
<dependency>
<groupId>xuggle</groupId>
<artifactId>xuggle-xuggler</artifactId>
<version>5.4</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.6.4</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>commons-cli</groupId>
<artifactId>commons-cli</artifactId>
<version>1.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.0.0</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.0.0</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>xuggle</groupId>
<artifactId>xuggle-utils</artifactId>
<version>1.22</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.6</version>
<executions>
<execution>
<id>unpack-dependencies</id>
<phase>package</phase>
<goals>
<goal>unpack-dependencies</goal>
</goals>
<configuration>
<excludeScope>system</excludeScope>
<excludeGroupIds>junit,org.mockito,org.hamcrest</excludeGroupIds>
<outputDirectory>${project.build.directory}/classes</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.2.1</version>
<executions>
<execution>
<id>unpack-dependencies</id>
<phase>package</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>${java.home}/../bin/javafxpackager</executable>
<arguments>
<argument>-createjar</argument>
<argument>-nocss2bin</argument>
<argument>-appclass</argument>
<argument>${mainClass}</argument>
<argument>-srcdir</argument>
<argument>${project.build.directory}/classes</argument>
<argument>-outdir</argument>
<argument>${project.build.directory}</argument>
<argument>-outfile</argument>
<argument>${project.build.finalName}.jar</argument>
</arguments>
</configuration>
</execution>
<execution>
<id>default-cli</id>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>${java.home}/bin/java</executable>
<commandlineArgs>${runfx.args}</commandlineArgs>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
<compilerArguments>
<bootclasspath>${sun.boot.class.path}${path.separator}${java.home}/lib/jfxrt.jar</bootclasspath>
</compilerArguments>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.16</version>
<configuration>
<additionalClasspathElements>
<additionalClasspathElement>${java.home}/lib/jfxrt.jar</additionalClasspathElement>
</additionalClasspathElements>
</configuration>
</plugin>
</plugins>
</build>
</project>
JMF 尝试 – 根据他们的数据格式站点,他们不支持 MPEG“写入”或仅编码“读取”解码,请参阅他们的格式 (http://www.oracle.com/technetwork/java/javase/formats-138492.html ).
Xuggler 尝试 (XugglerJpegImagesToMpegVideo.java) –
package com.conuretech.video_assembler;
import com.xuggle.mediatool.IMediaWriter;
import com.xuggle.mediatool.ToolFactory;
import com.xuggle.xuggler.IContainer;
import com.xuggle.xuggler.IContainerFormat;
import java.util.ArrayList;
import java.util.List;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
/**
*
* @author ANaim
*/
public class XugglerJpegImagesToMpegVideo {
//how many frames will be displayed per minute
private int frameRate = 1;
//total number of frames to be assembled into a video file
private int numberOfFrames = 0;
//path to output mpeg video
private String outputFilenamePath = "";
//list of JPEG pictures to be converted
private List<String> jpegFilePathList = new ArrayList<String>();
private List<BufferedImage> jpegFileList = new ArrayList<BufferedImage>();
public void convertJpegFramesToMpegVideo() {
System.out.println("convertJpegFramesToMpegVideo");
// BufferedImage to store JPEG data from file
BufferedImage img = null;
IContainer container = IContainer.make();
IMediaWriter writer = null;
long startTime = 0;
//ISSUE - container.open() fails to open
if (container.open(getOutputFilenamePath(), IContainer.Type.WRITE, null) > 0)
{
writer = ToolFactory.makeWriter(getOutputFilenamePath(),container);
this.setNumberOfFrames(getJpegFilePathList().size());
}
else
{
throw new RuntimeException("class jpegImagesToMpegVideo,convertJpegFramesToMpegVideo(), failed to open");
}
File imgFile = null;
//loop to load data from paths to BufferedImage objects
for (int i = 0; i < jpegFilePathList.size(); i++)
{
imgFile = new File(getJpegFilePathList().get(i));
if (imgFile.exists())
{
//System.out.println("convertJpegFramesToMpegVideo, file path: "+imgFile.getPath()+" exists");
try
{
img = ImageIO.read(imgFile);
jpegFileList.add(img);
}
catch (IOException e) {
e.printStackTrace();
}
}
else
{
System.out.println("convertJpegFramesToMpegVideo, file path: "+imgFile.getPath()+" does not exist!");
}
}//end for to load path to images
long nextEncodeTime = 0L;
startTime = System.nanoTime();
//for loop to encode each BufferedImage
for (int i = 0; i < jpegFileList.size(); i++) {
System.out.println("convertJpegFramesToMpegVideo encode counter: "+i);
img = jpegFileList.get(i);
nextEncodeTime = System.nanoTime() - startTime;
//encode
writer.encodeVideo(0, img,nextEncodeTime, TimeUnit.NANOSECONDS);
try {
/*Duration of each image - sleep 1000 millisecs (1 sec)
in order to shift outcome of the next nextEncodeTime = System.nanoTime() - startTime calcultation
by 1 second in order to have each frame displayed for 1 sec*/
Thread.sleep(1000);
} catch (InterruptedException ex) {
Logger.getLogger(XugglerJpegImagesToMpegVideo.class.getName()).log(Level.SEVERE, null, ex);
}
}//end loop
// after encoding all BufferedImages close
writer.close();
}//end
public int getFrameRate() {
return frameRate;
}
public void setFrameRate(int frameRate) {
this.frameRate = frameRate;
}
public int getNumberOfFrames() {
return numberOfFrames;
}
public void setNumberOfFrames(int numberOfFrames) {
this.numberOfFrames = numberOfFrames;
}
public String getOutputFilenamePath() {
return outputFilenamePath;
}
public void setOutputFilenamePath(String outputFilenamePath) {
this.outputFilenamePath = outputFilenamePath;
}
public List<String> getJpegFilePathList() {
return jpegFilePathList;
}
public void setJpegFilePathList(List<String> jpegFilePathList) {
this.jpegFilePathList = jpegFilePathList;
}
}
问题-
容器无法打开,这里是确切的片段:
//ISSUE - container.open() fails to open
if (container.open(getOutputFilenamePath(), IContainer.Type.WRITE, null) > 0)
{
writer = ToolFactory.makeWriter(getOutputFilenamePath(),container);
this.setNumberOfFrames(getJpegFilePathList().size());
}
JCodec 尝试(jCodecJpegImagesToMpegVideo.java):
对于 jCodec,我有 2 种方法,首先使用内置 org.jcodec.api.SequenceEncoder,然后根据其他在线帖子创建我自己的 SequenceEncoder class,称为 MySequenceEncoder,因为 org.jcodec.api.SequenceEncoder 没有任何设置每帧持续时间的方法。
package com.conuretech.video_assembler;
import java.awt.image.BufferedImage;
import java.awt.image.DataBuffer;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.imageio.ImageIO;
import org.jcodec.api.SequenceEncoder;
import org.jcodec.codecs.h264.H264Encoder;
import org.jcodec.common.model.ColorSpace;
import org.jcodec.common.model.Picture;
/**
*
* @author ANaim
*/
public class jCodecJpegImagesToMpegVideo {
//list of JPEG pictures to be converted
private List<String> jpegFilePathList = new ArrayList<String>();
private List<BufferedImage> jpegFileList = new ArrayList<BufferedImage>();
public void convertJpegFramesToMpegVideo() {
System.out.println("convertJpegFramesToMpegVideo");
//TODO: make dimensions dynanmic
BufferedImage img = null;
File imgFile = null;
for (int i = 0; i < jpegFilePathList.size(); i++)
{
imgFile = new File(getJpegFilePathList().get(i));
if (imgFile.exists())
{
//System.out.println("convertJpegFramesToMpegVideo, file path: "+imgFile.getPath()+" exists");
try
{
img = ImageIO.read(imgFile);
jpegFileList.add(img);
}
catch (IOException e) {
e.printStackTrace();
}
}
else
{
System.out.println("convertJpegFramesToMpegVideo, file path: "+imgFile.getPath()+" does not exist!");
}
}//end for to load path to images
File outVideoFile = new File("C:/Users/Public/Pictures/Sample_Pictures_2/video.mp4");
//Class that encodes images to a video
try
{
/*Attempt # 1 using org.jcodec.api.SequenceEncoder - SequenceEncoder class does not have method to specify duration,fps,etc..
SequenceEncoder newSequenceEncoder = new SequenceEncoder(outVideoFile);*/
/*Attempt # 2 - MySequenceEncoder is based on JCodec SequenceEncoder class src from GitHub,
were on can have finer access regarding duration & fps via " outTrack.addFrame(new MP4Packet(result,25,1,1,frameNo,true,null,1,0));"
however there is no official documentation describing how to set outTrack.addFrame(new MP4Packet(result,25,1,1,frameNo,true,null,1,0));*/
MySequenceEncoder newSequenceEncoder = new MySequenceEncoder(outVideoFile);
//H264 (aka mpeg) encoder
H264Encoder encoder = new H264Encoder();
//JCode class that holds media data before final processing
Picture toEncode = null;
//for loop to convert images to mpeg video
System.out.println("SupportedColorSpaces: "+encoder.getSupportedColorSpaces()[0]);
for (int i=0; i<jpegFileList.size(); i++)
{
img = jpegFileList.get(i);
toEncode = makeFrame(img, encoder.getSupportedColorSpaces()[0]);
//encode
newSequenceEncoder.encodeNativeFrame(toEncode);
}//end loop
//end encoding close sequence
newSequenceEncoder.finish();
}
catch (IOException exc)
{
exc.printStackTrace();
}
}//end
//I took this code from Whosebug because JCodecs AWTUtil was deprecated and this method converts BufferedImage to Picture class
private Picture makeFrame(BufferedImage bi, ColorSpace cs)
{
DataBuffer imgdata = bi.getRaster().getDataBuffer();
int[] ypix = new int[imgdata.getSize()];
int[] upix = new int[ imgdata.getSize() >> 2 ];
int[] vpix = new int[ imgdata.getSize() >> 2 ];
int ipx = 0, uvoff = 0;
for (int h = 0; h < bi.getHeight(); h++) {
for (int w = 0; w < bi.getWidth(); w++) {
int elem = imgdata.getElem(ipx);
int r = 0x0ff & (elem >>> 16);
int g = 0x0ff & (elem >>> 8);
int b = 0x0ff & elem;
ypix[ipx] = ((66 * r + 129 * g + 25 * b) >> 8) + 16;
if ((0 != w % 2) && (0 != h % 2)) {
upix[uvoff] = (( -38 * r + -74 * g + 112 * b) >> 8) + 128;
vpix[uvoff] = (( 112 * r + -94 * g + -18 * b) >> 8) + 128;
uvoff++;
}
ipx++;
}
}
int[][] pix = { ypix, upix, vpix, null };
return new Picture(bi.getWidth(), bi.getHeight(), pix, cs);
}
public List<String> getJpegFilePathList() {
return jpegFilePathList;
}
public void setJpegFilePathList(List<String> jpegFilePathList) {
this.jpegFilePathList = jpegFilePathList;
}
public List<BufferedImage> getJpegFileList() {
return jpegFileList;
}
public void setJpegFileList(List<BufferedImage> jpegFileList) {
this.jpegFileList = jpegFileList;
}
}//end class
这是我的序列编码器(MySequenceEncoder.java),我从 JCodec 的 github:
中获取源代码
package com.conuretech.video_assembler;
import org.jcodec.api.SequenceEncoder;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import org.jcodec.codecs.h264.H264Encoder;
import org.jcodec.codecs.h264.H264Utils;
import org.jcodec.common.NIOUtils;
import org.jcodec.common.SeekableByteChannel;
import org.jcodec.common.model.ColorSpace;
import org.jcodec.common.model.Picture;
import org.jcodec.containers.mp4.Brand;
import org.jcodec.containers.mp4.MP4Packet;
import org.jcodec.containers.mp4.TrackType;
import org.jcodec.containers.mp4.muxer.FramesMP4MuxerTrack;
import org.jcodec.containers.mp4.muxer.MP4Muxer;
import org.jcodec.scale.ColorUtil;
import org.jcodec.scale.Transform;
public class MySequenceEncoder {
/**
* This class is part of JCodec ( www.jcodec.org ) This software is distributed
* under FreeBSD License
*
* @author The JCodec project
*
*/
private SeekableByteChannel ch;
private Picture toEncode;
private Transform transform;
private H264Encoder encoder;
private ArrayList<ByteBuffer> spsList;
private ArrayList<ByteBuffer> ppsList;
private FramesMP4MuxerTrack outTrack;
private ByteBuffer _out;
private int frameNo;
private MP4Muxer muxer;
public MySequenceEncoder(File out) throws IOException {
this.ch = NIOUtils.writableFileChannel(out);
// Muxer that will store the encoded frames
muxer = new MP4Muxer(ch, Brand.MP4);
// Add video track to muxer
outTrack = muxer.addTrack(TrackType.VIDEO, 25);
// Allocate a buffer big enough to hold output frames
_out = ByteBuffer.allocate(1920 * 1080 * 6);
// Create an instance of encoder
encoder = new H264Encoder();
// Transform to convert between RGB and YUV
transform = ColorUtil.getTransform(ColorSpace.RGB, encoder.getSupportedColorSpaces()[0]);
// Encoder extra data ( SPS, PPS ) to be stored in a special place of
// MP4
spsList = new ArrayList<ByteBuffer>();
ppsList = new ArrayList<ByteBuffer>();
}
public void encodeNativeFrame(Picture pic) throws IOException {
if (toEncode == null) {
toEncode = Picture.create(pic.getWidth(), pic.getHeight(), encoder.getSupportedColorSpaces()[0]);
}
// Perform conversion
transform.transform(pic, toEncode);
// Encode image into H.264 frame, the result is stored in '_out' buffer
_out.clear();
ByteBuffer result = encoder.encodeFrame(toEncode, _out);
// Based on the frame above form correct MP4 packet
spsList.clear();
ppsList.clear();
H264Utils.wipePS(result, spsList, ppsList);
H264Utils.encodeMOVPacket(result);
//ISSUE - Im not sure what values to supply to MP4Packet() in order to control the duration of each frame to 1 sec
outTrack.addFrame(new MP4Packet(result,25,1,1,frameNo,true,null,1,0));
frameNo++;
}
public void finish() throws IOException {
// Push saved SPS/PPS to a special storage in MP4
outTrack.addSampleEntry(H264Utils.createMOVSampleEntry(spsList, ppsList, 4));
// Write MP4 header and finalize recording
muxer.writeHeader();
NIOUtils.closeQuietly(ch);
}
}//end class
问题-不管是用SequenceEncoder.java还是MySequenceEncoder.java制作出来的视频都播放不了,也没有错误,视频打开了,但是什么都没有显示。
我也无法确定要传递给哪些参数是正确的:
outTrack.addFrame(new MP4Packet(result,25,1,1,frameNo,true,null,1,0));
再次提供有效的 JMF、Xuggler、JCodec 或 JavaCV 解决方案,以及完整的说明和依赖信息,我们将不胜感激。
提前谢谢大家。
我终于找到了一个可以使用 Xuggler 库将 JPEG 图像转换为 MPEG 扩展名“.mp4”视频的工作解决方案:
这是我修改后的 "XugglerJpegImagesToMpegVideo.java" :
package com.conuretech.video_assembler;
import com.xuggle.mediatool.IMediaWriter;
import com.xuggle.mediatool.ToolFactory;
import com.xuggle.xuggler.IContainer;
import java.util.ArrayList;
import java.util.List;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import javax.imageio.ImageIO;
import com.xuggle.xuggler.ICodec;
import com.xuggle.xuggler.IRational;
/**
*
* @author Aryan Naim
* This class converts JPEG images to MPEG (.mp4) extension video using Xuggler library
*/
public class XugglerJpegImagesToMpegVideo {
//fps = how many frames will be displayed per sec, default 24
private int frameRate = 24;
//number of millisecs each frame will be displayed for the viewer , default 1000 millisecs
private long durationPerFrame = 1000;
//path to output mpeg video
private String outputFilenamePath = "";
//list of JPEG pictures to be converted
private List<String> jpegFilePathList = new ArrayList<String>();
//list of actual images in memory to be iterated through & encoded by Xuggler
private List<BufferedImage> jpegFileList = new ArrayList<BufferedImage>();
/* VERY IMPORTANT regarding setDurationPerFrame(), I noticed that fpr 24 fps ,
the expected duration fell short 15% or 150 millisec per 1 second,
therefore I adjusted this by adding 15% to the user specified duration,
For example 3000 millisec duration will be adjusted to 3450 millisecs.
This adjustment was even more severe for lower fps*/
public void setDurationPerFrame(long durationPerFrame) {
this.durationPerFrame = new Double(Math.ceil(durationPerFrame * 1.15)).longValue();
}
public long getDurationPerFrame() {
return durationPerFrame;
}
/*
Actual method that converts JPEG images to MPEG (.mp4) extension video using Xuggler library
*/
public void convertJpegFramesToMpegVideo() {
System.out.println("convertJpegFramesToMpegVideo, start...");
// BufferedImage to store JPEG data from file
BufferedImage img = null;
IContainer container = IContainer.make();
IMediaWriter writer = null;
long nextEncodeTime = getDurationPerFrame();
writer = ToolFactory.makeWriter(getOutputFilenamePath(), container);
//the framerate is set at 24 fps , could be adjusted
writer.addVideoStream(0, 0, ICodec.ID.CODEC_ID_MPEG4,IRational.make(frameRate),1024,768);
File imgFile = null;
//loop to load data from paths to BufferedImage objects
for (int i = 0; i < jpegFilePathList.size(); i++) {
imgFile = new File(getJpegFilePathList().get(i));
if (imgFile.exists()) {
try {
img = ImageIO.read(imgFile);
jpegFileList.add(img);
} catch (IOException e) {
e.printStackTrace();
}
} else {
System.out.println("convertJpegFramesToMpegVideo, file path: " + imgFile.getPath() + " does not exist!");
}
}//end for to load path to images
//for loop to encode each BufferedImage
for (int i = 0; i <jpegFileList.size(); i++) {
System.out.println("convertJpegFramesToMpegVideo encoded frame counter: " + i);
img = jpegFileList.get(i);
writer.encodeVideo(0, img, nextEncodeTime, TimeUnit.MILLISECONDS);
nextEncodeTime = nextEncodeTime + getDurationPerFrame();
}//end loop
// after encoding all BufferedImages close
writer.close();
System.out.println("convertJpegFramesToMpegVideo, end!");
}//end
public int getFrameRate() {
return frameRate;
}
public void setFrameRate(int frameRate) {
this.frameRate = frameRate;
}
public String getOutputFilenamePath() {
return outputFilenamePath;
}
public void setOutputFilenamePath(String outputFilenamePath) {
this.outputFilenamePath = outputFilenamePath;
}
public List<String> getJpegFilePathList() {
return jpegFilePathList;
}
public void setJpegFilePathList(List<String> jpegFilePathList) {
this.jpegFilePathList = jpegFilePathList;
}
public List<BufferedImage> getJpegFileList() {
return jpegFileList;
}
public void setJpegFileList(List<BufferedImage> jpegFileList) {
this.jpegFileList = jpegFileList;
}
}//end class
这是实例化 XugglerJpegImagesToMpegVideo.java 并设置 JPEG 文件路径、fps 和帧持续时间等参数的主要 class:
package com.conuretech.video_assembler;
import com.xuggle.xuggler.IContainerFormat;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
*
* @author ANaim
*/
public class TestVideo {
public static void main (String [] args)
{
if (args[0].equalsIgnoreCase("1"))
{
System.out.println("Testing xuggler...");
XugglerJpegImagesToMpegVideo newjpegImagesToMpegVideo = new XugglerJpegImagesToMpegVideo();
newjpegImagesToMpegVideo.setFrameRate(24);
//duration in millisecs, for example 2 secs will be 2000
newjpegImagesToMpegVideo.setDurationPerFrame(2000);
newjpegImagesToMpegVideo.setOutputFilenamePath("C:/Users/Public/Pictures/Sample_Pictures_2/video.mp4");
List<String> jpegImages = new ArrayList<String>();
jpegImages.add("C:/Users/Public/Pictures/Sample_Pictures_2/1.jpg");
jpegImages.add("C:/Users/Public/Pictures/Sample_Pictures_2/2.jpg");
jpegImages.add("C:/Users/Public/Pictures/Sample_Pictures_2/3.jpg");
jpegImages.add("C:/Users/Public/Pictures/Sample_Pictures_2/4.jpg");
jpegImages.add("C:/Users/Public/Pictures/Sample_Pictures_2/5.jpg");
jpegImages.add("C:/Users/Public/Pictures/Sample_Pictures_2/6.jpg");
jpegImages.add("C:/Users/Public/Pictures/Sample_Pictures_2/7.jpg");
jpegImages.add("C:/Users/Public/Pictures/Sample_Pictures_2/8.jpg");
newjpegImagesToMpegVideo.setJpegFilePathList(jpegImages);
newjpegImagesToMpegVideo.convertJpegFramesToMpegVideo();
}
else
{
}
System.exit(0);
}
}//end main
这创建了一个 16 秒的 MPEG 视频,每帧持续 2 秒。
这就是所有人!
我需要将 JPEG 图像转换为 MPEG 视频,并且能够轻松设置每帧显示的持续时间。
我已经尝试过 JMF、Xuggler 和 JCodec,但都有各自的问题。
对于 JMF、Xuggler、JCode 或什至 JavaCV(尚未尝试过)得到很好解释的任何解决方案,我将不胜感激。
以下是我的编码尝试。
我的测试菜单和主菜单,用于测试 Xuggler 方法或 JCodec:
package com.conuretech.video_assembler;
import com.xuggle.xuggler.IContainerFormat;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
*
* @author ANaim
*/
public class TestVideo {
public static void main (String [] args)
{
if (args[0].equalsIgnoreCase("1"))
{
System.out.println("Testing Xuggler ...");
Iterator<IContainerFormat> iterator = IContainerFormat.getInstalledOutputFormats().iterator();
while (iterator.hasNext())
{
System.out.println(iterator.next().toString());
}
System.out.println("Testing xuggler...");
//C:\Users\Public\Pictures\Sample_Pictures_2.jpg
XugglerJpegImagesToMpegVideo newjpegImagesToMpegVideo = new XugglerJpegImagesToMpegVideo();
newjpegImagesToMpegVideo.setFrameRate(25);
newjpegImagesToMpegVideo.setOutputFilenamePath("C:/Users/Public/Pictures/Sample_Pictures_2/video.mpg");
List<String> jpegImages = new ArrayList<String>();
jpegImages.add("C:/Users/Public/Pictures/Sample_Pictures_2/1.jpg");
jpegImages.add("C:/Users/Public/Pictures/Sample_Pictures_2/2.jpg");
jpegImages.add("C:/Users/Public/Pictures/Sample_Pictures_2/3.jpg");
jpegImages.add("C:/Users/Public/Pictures/Sample_Pictures_2/4.jpg");
jpegImages.add("C:/Users/Public/Pictures/Sample_Pictures_2/5.jpg");
jpegImages.add("C:/Users/Public/Pictures/Sample_Pictures_2/6.jpg");
jpegImages.add("C:/Users/Public/Pictures/Sample_Pictures_2/7.jpg");
jpegImages.add("C:/Users/Public/Pictures/Sample_Pictures_2/8.jpg");
newjpegImagesToMpegVideo.setJpegFilePathList(jpegImages);
newjpegImagesToMpegVideo.convertJpegFramesToMpegVideo();
}
else if (args[0].equalsIgnoreCase("2"))
{
System.out.println("Testing JCodec...");
jCodecJpegImagesToMpegVideo newJCodecJpegImagesToMpegVideo = new jCodecJpegImagesToMpegVideo();
List<String> jpegImages = new ArrayList<String>();
jpegImages.add("C:/Users/Public/Pictures/Sample_Pictures_2/1.jpg");
jpegImages.add("C:/Users/Public/Pictures/Sample_Pictures_2/2.jpg");
jpegImages.add("C:/Users/Public/Pictures/Sample_Pictures_2/3.jpg");
jpegImages.add("C:/Users/Public/Pictures/Sample_Pictures_2/4.jpg");
jpegImages.add("C:/Users/Public/Pictures/Sample_Pictures_2/5.jpg");
jpegImages.add("C:/Users/Public/Pictures/Sample_Pictures_2/6.jpg");
jpegImages.add("C:/Users/Public/Pictures/Sample_Pictures_2/7.jpg");
jpegImages.add("C:/Users/Public/Pictures/Sample_Pictures_2/8.jpg");
newJCodecJpegImagesToMpegVideo.setJpegFilePathList(jpegImages);
newJCodecJpegImagesToMpegVideo.convertJpegFramesToMpegVideo();
}
else
{
}
System.exit(0);
}
}
我的pom.xml(包括 Xuggler 和 JCodec 依赖项):
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.conuretech</groupId>
<artifactId>Video_Assembler</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Video_Assembler</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!--<mainClass>com.conuretech.video_assembler.TestVideo</mainClass>-->
<mainClass>com.conuretech.video_assembler.MainApp</mainClass>
</properties>
<organization>
<!-- Used as the 'Vendor' for JNLP generation -->
<name>conuretech</name>
</organization>
<repositories>
<repository>
<id>xuggle repo</id>
<url>http://xuggle.googlecode.com/svn/trunk/repo/share/java/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.jcodec</groupId>
<artifactId>jcodec</artifactId>
<version>0.1.9</version>
</dependency>
<dependency>
<groupId>xuggle</groupId>
<artifactId>xuggle-xuggler</artifactId>
<version>5.4</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.6.4</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>commons-cli</groupId>
<artifactId>commons-cli</artifactId>
<version>1.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.0.0</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.0.0</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>xuggle</groupId>
<artifactId>xuggle-utils</artifactId>
<version>1.22</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.6</version>
<executions>
<execution>
<id>unpack-dependencies</id>
<phase>package</phase>
<goals>
<goal>unpack-dependencies</goal>
</goals>
<configuration>
<excludeScope>system</excludeScope>
<excludeGroupIds>junit,org.mockito,org.hamcrest</excludeGroupIds>
<outputDirectory>${project.build.directory}/classes</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.2.1</version>
<executions>
<execution>
<id>unpack-dependencies</id>
<phase>package</phase>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>${java.home}/../bin/javafxpackager</executable>
<arguments>
<argument>-createjar</argument>
<argument>-nocss2bin</argument>
<argument>-appclass</argument>
<argument>${mainClass}</argument>
<argument>-srcdir</argument>
<argument>${project.build.directory}/classes</argument>
<argument>-outdir</argument>
<argument>${project.build.directory}</argument>
<argument>-outfile</argument>
<argument>${project.build.finalName}.jar</argument>
</arguments>
</configuration>
</execution>
<execution>
<id>default-cli</id>
<goals>
<goal>exec</goal>
</goals>
<configuration>
<executable>${java.home}/bin/java</executable>
<commandlineArgs>${runfx.args}</commandlineArgs>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
<compilerArguments>
<bootclasspath>${sun.boot.class.path}${path.separator}${java.home}/lib/jfxrt.jar</bootclasspath>
</compilerArguments>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.16</version>
<configuration>
<additionalClasspathElements>
<additionalClasspathElement>${java.home}/lib/jfxrt.jar</additionalClasspathElement>
</additionalClasspathElements>
</configuration>
</plugin>
</plugins>
</build>
</project>
JMF 尝试 – 根据他们的数据格式站点,他们不支持 MPEG“写入”或仅编码“读取”解码,请参阅他们的格式 (http://www.oracle.com/technetwork/java/javase/formats-138492.html ).
Xuggler 尝试 (XugglerJpegImagesToMpegVideo.java) –
package com.conuretech.video_assembler;
import com.xuggle.mediatool.IMediaWriter;
import com.xuggle.mediatool.ToolFactory;
import com.xuggle.xuggler.IContainer;
import com.xuggle.xuggler.IContainerFormat;
import java.util.ArrayList;
import java.util.List;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
/**
*
* @author ANaim
*/
public class XugglerJpegImagesToMpegVideo {
//how many frames will be displayed per minute
private int frameRate = 1;
//total number of frames to be assembled into a video file
private int numberOfFrames = 0;
//path to output mpeg video
private String outputFilenamePath = "";
//list of JPEG pictures to be converted
private List<String> jpegFilePathList = new ArrayList<String>();
private List<BufferedImage> jpegFileList = new ArrayList<BufferedImage>();
public void convertJpegFramesToMpegVideo() {
System.out.println("convertJpegFramesToMpegVideo");
// BufferedImage to store JPEG data from file
BufferedImage img = null;
IContainer container = IContainer.make();
IMediaWriter writer = null;
long startTime = 0;
//ISSUE - container.open() fails to open
if (container.open(getOutputFilenamePath(), IContainer.Type.WRITE, null) > 0)
{
writer = ToolFactory.makeWriter(getOutputFilenamePath(),container);
this.setNumberOfFrames(getJpegFilePathList().size());
}
else
{
throw new RuntimeException("class jpegImagesToMpegVideo,convertJpegFramesToMpegVideo(), failed to open");
}
File imgFile = null;
//loop to load data from paths to BufferedImage objects
for (int i = 0; i < jpegFilePathList.size(); i++)
{
imgFile = new File(getJpegFilePathList().get(i));
if (imgFile.exists())
{
//System.out.println("convertJpegFramesToMpegVideo, file path: "+imgFile.getPath()+" exists");
try
{
img = ImageIO.read(imgFile);
jpegFileList.add(img);
}
catch (IOException e) {
e.printStackTrace();
}
}
else
{
System.out.println("convertJpegFramesToMpegVideo, file path: "+imgFile.getPath()+" does not exist!");
}
}//end for to load path to images
long nextEncodeTime = 0L;
startTime = System.nanoTime();
//for loop to encode each BufferedImage
for (int i = 0; i < jpegFileList.size(); i++) {
System.out.println("convertJpegFramesToMpegVideo encode counter: "+i);
img = jpegFileList.get(i);
nextEncodeTime = System.nanoTime() - startTime;
//encode
writer.encodeVideo(0, img,nextEncodeTime, TimeUnit.NANOSECONDS);
try {
/*Duration of each image - sleep 1000 millisecs (1 sec)
in order to shift outcome of the next nextEncodeTime = System.nanoTime() - startTime calcultation
by 1 second in order to have each frame displayed for 1 sec*/
Thread.sleep(1000);
} catch (InterruptedException ex) {
Logger.getLogger(XugglerJpegImagesToMpegVideo.class.getName()).log(Level.SEVERE, null, ex);
}
}//end loop
// after encoding all BufferedImages close
writer.close();
}//end
public int getFrameRate() {
return frameRate;
}
public void setFrameRate(int frameRate) {
this.frameRate = frameRate;
}
public int getNumberOfFrames() {
return numberOfFrames;
}
public void setNumberOfFrames(int numberOfFrames) {
this.numberOfFrames = numberOfFrames;
}
public String getOutputFilenamePath() {
return outputFilenamePath;
}
public void setOutputFilenamePath(String outputFilenamePath) {
this.outputFilenamePath = outputFilenamePath;
}
public List<String> getJpegFilePathList() {
return jpegFilePathList;
}
public void setJpegFilePathList(List<String> jpegFilePathList) {
this.jpegFilePathList = jpegFilePathList;
}
}
问题- 容器无法打开,这里是确切的片段:
//ISSUE - container.open() fails to open
if (container.open(getOutputFilenamePath(), IContainer.Type.WRITE, null) > 0)
{
writer = ToolFactory.makeWriter(getOutputFilenamePath(),container);
this.setNumberOfFrames(getJpegFilePathList().size());
}
JCodec 尝试(jCodecJpegImagesToMpegVideo.java):
对于 jCodec,我有 2 种方法,首先使用内置 org.jcodec.api.SequenceEncoder,然后根据其他在线帖子创建我自己的 SequenceEncoder class,称为 MySequenceEncoder,因为 org.jcodec.api.SequenceEncoder 没有任何设置每帧持续时间的方法。
package com.conuretech.video_assembler;
import java.awt.image.BufferedImage;
import java.awt.image.DataBuffer;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.imageio.ImageIO;
import org.jcodec.api.SequenceEncoder;
import org.jcodec.codecs.h264.H264Encoder;
import org.jcodec.common.model.ColorSpace;
import org.jcodec.common.model.Picture;
/**
*
* @author ANaim
*/
public class jCodecJpegImagesToMpegVideo {
//list of JPEG pictures to be converted
private List<String> jpegFilePathList = new ArrayList<String>();
private List<BufferedImage> jpegFileList = new ArrayList<BufferedImage>();
public void convertJpegFramesToMpegVideo() {
System.out.println("convertJpegFramesToMpegVideo");
//TODO: make dimensions dynanmic
BufferedImage img = null;
File imgFile = null;
for (int i = 0; i < jpegFilePathList.size(); i++)
{
imgFile = new File(getJpegFilePathList().get(i));
if (imgFile.exists())
{
//System.out.println("convertJpegFramesToMpegVideo, file path: "+imgFile.getPath()+" exists");
try
{
img = ImageIO.read(imgFile);
jpegFileList.add(img);
}
catch (IOException e) {
e.printStackTrace();
}
}
else
{
System.out.println("convertJpegFramesToMpegVideo, file path: "+imgFile.getPath()+" does not exist!");
}
}//end for to load path to images
File outVideoFile = new File("C:/Users/Public/Pictures/Sample_Pictures_2/video.mp4");
//Class that encodes images to a video
try
{
/*Attempt # 1 using org.jcodec.api.SequenceEncoder - SequenceEncoder class does not have method to specify duration,fps,etc..
SequenceEncoder newSequenceEncoder = new SequenceEncoder(outVideoFile);*/
/*Attempt # 2 - MySequenceEncoder is based on JCodec SequenceEncoder class src from GitHub,
were on can have finer access regarding duration & fps via " outTrack.addFrame(new MP4Packet(result,25,1,1,frameNo,true,null,1,0));"
however there is no official documentation describing how to set outTrack.addFrame(new MP4Packet(result,25,1,1,frameNo,true,null,1,0));*/
MySequenceEncoder newSequenceEncoder = new MySequenceEncoder(outVideoFile);
//H264 (aka mpeg) encoder
H264Encoder encoder = new H264Encoder();
//JCode class that holds media data before final processing
Picture toEncode = null;
//for loop to convert images to mpeg video
System.out.println("SupportedColorSpaces: "+encoder.getSupportedColorSpaces()[0]);
for (int i=0; i<jpegFileList.size(); i++)
{
img = jpegFileList.get(i);
toEncode = makeFrame(img, encoder.getSupportedColorSpaces()[0]);
//encode
newSequenceEncoder.encodeNativeFrame(toEncode);
}//end loop
//end encoding close sequence
newSequenceEncoder.finish();
}
catch (IOException exc)
{
exc.printStackTrace();
}
}//end
//I took this code from Whosebug because JCodecs AWTUtil was deprecated and this method converts BufferedImage to Picture class
private Picture makeFrame(BufferedImage bi, ColorSpace cs)
{
DataBuffer imgdata = bi.getRaster().getDataBuffer();
int[] ypix = new int[imgdata.getSize()];
int[] upix = new int[ imgdata.getSize() >> 2 ];
int[] vpix = new int[ imgdata.getSize() >> 2 ];
int ipx = 0, uvoff = 0;
for (int h = 0; h < bi.getHeight(); h++) {
for (int w = 0; w < bi.getWidth(); w++) {
int elem = imgdata.getElem(ipx);
int r = 0x0ff & (elem >>> 16);
int g = 0x0ff & (elem >>> 8);
int b = 0x0ff & elem;
ypix[ipx] = ((66 * r + 129 * g + 25 * b) >> 8) + 16;
if ((0 != w % 2) && (0 != h % 2)) {
upix[uvoff] = (( -38 * r + -74 * g + 112 * b) >> 8) + 128;
vpix[uvoff] = (( 112 * r + -94 * g + -18 * b) >> 8) + 128;
uvoff++;
}
ipx++;
}
}
int[][] pix = { ypix, upix, vpix, null };
return new Picture(bi.getWidth(), bi.getHeight(), pix, cs);
}
public List<String> getJpegFilePathList() {
return jpegFilePathList;
}
public void setJpegFilePathList(List<String> jpegFilePathList) {
this.jpegFilePathList = jpegFilePathList;
}
public List<BufferedImage> getJpegFileList() {
return jpegFileList;
}
public void setJpegFileList(List<BufferedImage> jpegFileList) {
this.jpegFileList = jpegFileList;
}
}//end class
这是我的序列编码器(MySequenceEncoder.java),我从 JCodec 的 github:
中获取源代码package com.conuretech.video_assembler;
import org.jcodec.api.SequenceEncoder;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import org.jcodec.codecs.h264.H264Encoder;
import org.jcodec.codecs.h264.H264Utils;
import org.jcodec.common.NIOUtils;
import org.jcodec.common.SeekableByteChannel;
import org.jcodec.common.model.ColorSpace;
import org.jcodec.common.model.Picture;
import org.jcodec.containers.mp4.Brand;
import org.jcodec.containers.mp4.MP4Packet;
import org.jcodec.containers.mp4.TrackType;
import org.jcodec.containers.mp4.muxer.FramesMP4MuxerTrack;
import org.jcodec.containers.mp4.muxer.MP4Muxer;
import org.jcodec.scale.ColorUtil;
import org.jcodec.scale.Transform;
public class MySequenceEncoder {
/**
* This class is part of JCodec ( www.jcodec.org ) This software is distributed
* under FreeBSD License
*
* @author The JCodec project
*
*/
private SeekableByteChannel ch;
private Picture toEncode;
private Transform transform;
private H264Encoder encoder;
private ArrayList<ByteBuffer> spsList;
private ArrayList<ByteBuffer> ppsList;
private FramesMP4MuxerTrack outTrack;
private ByteBuffer _out;
private int frameNo;
private MP4Muxer muxer;
public MySequenceEncoder(File out) throws IOException {
this.ch = NIOUtils.writableFileChannel(out);
// Muxer that will store the encoded frames
muxer = new MP4Muxer(ch, Brand.MP4);
// Add video track to muxer
outTrack = muxer.addTrack(TrackType.VIDEO, 25);
// Allocate a buffer big enough to hold output frames
_out = ByteBuffer.allocate(1920 * 1080 * 6);
// Create an instance of encoder
encoder = new H264Encoder();
// Transform to convert between RGB and YUV
transform = ColorUtil.getTransform(ColorSpace.RGB, encoder.getSupportedColorSpaces()[0]);
// Encoder extra data ( SPS, PPS ) to be stored in a special place of
// MP4
spsList = new ArrayList<ByteBuffer>();
ppsList = new ArrayList<ByteBuffer>();
}
public void encodeNativeFrame(Picture pic) throws IOException {
if (toEncode == null) {
toEncode = Picture.create(pic.getWidth(), pic.getHeight(), encoder.getSupportedColorSpaces()[0]);
}
// Perform conversion
transform.transform(pic, toEncode);
// Encode image into H.264 frame, the result is stored in '_out' buffer
_out.clear();
ByteBuffer result = encoder.encodeFrame(toEncode, _out);
// Based on the frame above form correct MP4 packet
spsList.clear();
ppsList.clear();
H264Utils.wipePS(result, spsList, ppsList);
H264Utils.encodeMOVPacket(result);
//ISSUE - Im not sure what values to supply to MP4Packet() in order to control the duration of each frame to 1 sec
outTrack.addFrame(new MP4Packet(result,25,1,1,frameNo,true,null,1,0));
frameNo++;
}
public void finish() throws IOException {
// Push saved SPS/PPS to a special storage in MP4
outTrack.addSampleEntry(H264Utils.createMOVSampleEntry(spsList, ppsList, 4));
// Write MP4 header and finalize recording
muxer.writeHeader();
NIOUtils.closeQuietly(ch);
}
}//end class
问题-不管是用SequenceEncoder.java还是MySequenceEncoder.java制作出来的视频都播放不了,也没有错误,视频打开了,但是什么都没有显示。
我也无法确定要传递给哪些参数是正确的:
outTrack.addFrame(new MP4Packet(result,25,1,1,frameNo,true,null,1,0));
再次提供有效的 JMF、Xuggler、JCodec 或 JavaCV 解决方案,以及完整的说明和依赖信息,我们将不胜感激。
提前谢谢大家。
我终于找到了一个可以使用 Xuggler 库将 JPEG 图像转换为 MPEG 扩展名“.mp4”视频的工作解决方案:
这是我修改后的 "XugglerJpegImagesToMpegVideo.java" :
package com.conuretech.video_assembler;
import com.xuggle.mediatool.IMediaWriter;
import com.xuggle.mediatool.ToolFactory;
import com.xuggle.xuggler.IContainer;
import java.util.ArrayList;
import java.util.List;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import javax.imageio.ImageIO;
import com.xuggle.xuggler.ICodec;
import com.xuggle.xuggler.IRational;
/**
*
* @author Aryan Naim
* This class converts JPEG images to MPEG (.mp4) extension video using Xuggler library
*/
public class XugglerJpegImagesToMpegVideo {
//fps = how many frames will be displayed per sec, default 24
private int frameRate = 24;
//number of millisecs each frame will be displayed for the viewer , default 1000 millisecs
private long durationPerFrame = 1000;
//path to output mpeg video
private String outputFilenamePath = "";
//list of JPEG pictures to be converted
private List<String> jpegFilePathList = new ArrayList<String>();
//list of actual images in memory to be iterated through & encoded by Xuggler
private List<BufferedImage> jpegFileList = new ArrayList<BufferedImage>();
/* VERY IMPORTANT regarding setDurationPerFrame(), I noticed that fpr 24 fps ,
the expected duration fell short 15% or 150 millisec per 1 second,
therefore I adjusted this by adding 15% to the user specified duration,
For example 3000 millisec duration will be adjusted to 3450 millisecs.
This adjustment was even more severe for lower fps*/
public void setDurationPerFrame(long durationPerFrame) {
this.durationPerFrame = new Double(Math.ceil(durationPerFrame * 1.15)).longValue();
}
public long getDurationPerFrame() {
return durationPerFrame;
}
/*
Actual method that converts JPEG images to MPEG (.mp4) extension video using Xuggler library
*/
public void convertJpegFramesToMpegVideo() {
System.out.println("convertJpegFramesToMpegVideo, start...");
// BufferedImage to store JPEG data from file
BufferedImage img = null;
IContainer container = IContainer.make();
IMediaWriter writer = null;
long nextEncodeTime = getDurationPerFrame();
writer = ToolFactory.makeWriter(getOutputFilenamePath(), container);
//the framerate is set at 24 fps , could be adjusted
writer.addVideoStream(0, 0, ICodec.ID.CODEC_ID_MPEG4,IRational.make(frameRate),1024,768);
File imgFile = null;
//loop to load data from paths to BufferedImage objects
for (int i = 0; i < jpegFilePathList.size(); i++) {
imgFile = new File(getJpegFilePathList().get(i));
if (imgFile.exists()) {
try {
img = ImageIO.read(imgFile);
jpegFileList.add(img);
} catch (IOException e) {
e.printStackTrace();
}
} else {
System.out.println("convertJpegFramesToMpegVideo, file path: " + imgFile.getPath() + " does not exist!");
}
}//end for to load path to images
//for loop to encode each BufferedImage
for (int i = 0; i <jpegFileList.size(); i++) {
System.out.println("convertJpegFramesToMpegVideo encoded frame counter: " + i);
img = jpegFileList.get(i);
writer.encodeVideo(0, img, nextEncodeTime, TimeUnit.MILLISECONDS);
nextEncodeTime = nextEncodeTime + getDurationPerFrame();
}//end loop
// after encoding all BufferedImages close
writer.close();
System.out.println("convertJpegFramesToMpegVideo, end!");
}//end
public int getFrameRate() {
return frameRate;
}
public void setFrameRate(int frameRate) {
this.frameRate = frameRate;
}
public String getOutputFilenamePath() {
return outputFilenamePath;
}
public void setOutputFilenamePath(String outputFilenamePath) {
this.outputFilenamePath = outputFilenamePath;
}
public List<String> getJpegFilePathList() {
return jpegFilePathList;
}
public void setJpegFilePathList(List<String> jpegFilePathList) {
this.jpegFilePathList = jpegFilePathList;
}
public List<BufferedImage> getJpegFileList() {
return jpegFileList;
}
public void setJpegFileList(List<BufferedImage> jpegFileList) {
this.jpegFileList = jpegFileList;
}
}//end class
这是实例化 XugglerJpegImagesToMpegVideo.java 并设置 JPEG 文件路径、fps 和帧持续时间等参数的主要 class:
package com.conuretech.video_assembler;
import com.xuggle.xuggler.IContainerFormat;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
*
* @author ANaim
*/
public class TestVideo {
public static void main (String [] args)
{
if (args[0].equalsIgnoreCase("1"))
{
System.out.println("Testing xuggler...");
XugglerJpegImagesToMpegVideo newjpegImagesToMpegVideo = new XugglerJpegImagesToMpegVideo();
newjpegImagesToMpegVideo.setFrameRate(24);
//duration in millisecs, for example 2 secs will be 2000
newjpegImagesToMpegVideo.setDurationPerFrame(2000);
newjpegImagesToMpegVideo.setOutputFilenamePath("C:/Users/Public/Pictures/Sample_Pictures_2/video.mp4");
List<String> jpegImages = new ArrayList<String>();
jpegImages.add("C:/Users/Public/Pictures/Sample_Pictures_2/1.jpg");
jpegImages.add("C:/Users/Public/Pictures/Sample_Pictures_2/2.jpg");
jpegImages.add("C:/Users/Public/Pictures/Sample_Pictures_2/3.jpg");
jpegImages.add("C:/Users/Public/Pictures/Sample_Pictures_2/4.jpg");
jpegImages.add("C:/Users/Public/Pictures/Sample_Pictures_2/5.jpg");
jpegImages.add("C:/Users/Public/Pictures/Sample_Pictures_2/6.jpg");
jpegImages.add("C:/Users/Public/Pictures/Sample_Pictures_2/7.jpg");
jpegImages.add("C:/Users/Public/Pictures/Sample_Pictures_2/8.jpg");
newjpegImagesToMpegVideo.setJpegFilePathList(jpegImages);
newjpegImagesToMpegVideo.convertJpegFramesToMpegVideo();
}
else
{
}
System.exit(0);
}
}//end main
这创建了一个 16 秒的 MPEG 视频,每帧持续 2 秒。
这就是所有人!