我需要将jpeg图像转换为mpeg视频,并能够轻松设置每帧显示的持续时间。
我试过jmf、xuggler和jcodec,但都有问题。
我将非常感谢为jmf、xuggler、jcode甚至javacv(我没有尝试过这个)提供的任何解决方案。
下面是我的编码尝试。
我的测试菜单&main,用于测试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\1.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);
}
}
my 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,我有两种方法:首先使用内置的org.jcodec.api.sequenceencoder,然后基于其他在线帖子创建我自己的sequenceencoder类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 Stackoverflow 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解决方案,以及完整的指令和依赖信息将非常受欢迎。
提前谢谢大家。
1条答案
按热度按时间pvcm50d11#
我终于找到了一个可行的解决方案,使用xuggler库将jpeg图像转换为mpeg扩展名“.mp4”视频:
这是我修改过的“xugglerjpegimagestompegvideo.java”:
下面是示例化xuglerjpegimagestompegvideo.java并设置jpeg文件路径、fps和帧持续时间等参数的主类:
这创建了一个16秒的mpeg视频,每帧持续2秒。
全是这些人!