2014-07-03 12 views
5

Tôi đang nghiên cứu về cách tạo video mp4 từ hình ảnh trong Java. Sau một vài ngày nghiên cứu, tôi biết rằng JCodec có thể làm điều đó (http://jcodec.org/). Đây là cuộc biểu tình tôi tìm thấy trên Android make animated video from list of images (tôi chỉ thay đổi các đầu vào và liên kết đầu ra):Làm thế nào để tạo một video mp4 từ hình ảnh trong Java bằng cách sử dụng thư viện JCodec?

private SeekableByteChannel ch; 
private Picture toEncode; 
private RgbToYuv420 transform; 
private H264Encoder encoder; 
private ArrayList<ByteBuffer> spsList; 
private ArrayList<ByteBuffer> ppsList; 
private CompressedTrack outTrack; 
private ByteBuffer _out; 
private int frameNo; 
private MP4Muxer muxer; 

public SequenceImagesEncoder(File out) throws IOException { 
    this.ch = NIOUtils.writableFileChannel(out); 

    // Transform to convert between RGB and YUV 
    transform = new RgbToYuv420(0, 0); 

    // Muxer that will store the encoded frames 
    muxer = new MP4Muxer(ch, Brand.MP4); 

    // Add video track to muxer 
    outTrack = muxer.addTrackForCompressed(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(); 

    // Encoder extra data (SPS, PPS) to be stored in a special place of 
    // MP4 
    spsList = new ArrayList<ByteBuffer>(); 
    ppsList = new ArrayList<ByteBuffer>(); 

} 

public void encodeImage(BufferedImage bi) throws IOException { 
    if (toEncode == null) { 
     toEncode = Picture.create(bi.getWidth(), bi.getHeight(), ColorSpace.YUV420); 
    } 

    // Perform conversion 
    for (int i = 0; i < 3; i++) 
     Arrays.fill(toEncode.getData()[i], 0); 
    transform.transform(AWTUtil.fromBufferedImage(bi), toEncode); 

    // Encode image into H.264 frame, the result is stored in '_out' buffer 
    _out.clear(); 
    ByteBuffer result = encoder.encodeFrame(_out, toEncode); 

    // Based on the frame above form correct MP4 packet 
    spsList.clear(); 
    ppsList.clear(); 
    H264Utils.encodeMOVPacket(result, spsList, ppsList); 

    // Add packet to video track 
    outTrack.addFrame(new MP4Packet(result, frameNo, 25, 1, frameNo, true, null, frameNo, 0)); 

    frameNo++; 
} 

public void finish() throws IOException { 
    // Push saved SPS/PPS to a special storage in MP4 
    outTrack.addSampleEntry(H264Utils.createMOVSampleEntry(spsList, ppsList)); 

    // Write MP4 header and finalize recording 
    muxer.writeHeader(); 
    NIOUtils.closeQuietly(ch); 
} 

public static void main(String[] args) throws IOException { 
    SequenceImagesEncoder encoder = new SequenceImagesEncoder(new File("D:/workspace/JCodecMakeMP4/out.mp4")); 
    for (int i = 1; i < 100; i++) { 
     BufferedImage bi = ImageIO.read(new File(String.format("D:/workspace/JCodecMakeMP4/bin/frame" + i + ".jpeg", i))); 
     encoder.encodeImage(bi); 
    } 
    encoder.finish(); 
} 

Khi tôi sử dụng jcodec-0.1.0.jar, các NIOUtils lớp không có một chức năng: writableFileChannel (file file).

Khi tôi sử dụng jcodec-0.1.3.jar, mọi thứ có vẻ ổn, nhưng tôi gỡ lỗi mã, nó dẫn đến "Nguồn không tìm thấy" khi tôi chuyển sang dòng: muxer = new MP4Muxer (ch, Brand. MP4);

Có ai biết cách khắc phục không.

Cảm ơn bạn trước!

+0

[jcodec -? Có ai nhìn thấy tài liệu trên thư viện này] (http://stackoverflow.com/questions/10969423/jcodec-has -anyone-seen-documentation-on-this-library) có thể giúp (xem mã trong câu trả lời) – DavidPostill

+0

Tôi cũng tìm thấy mã trên webside này: http://stackoverflow.com/questions/10284708/android-make-animated -video-from-list-of-images Khi tôi cố gắng sử dụng jcodec-0.1.0.jar, các chức năng: NIOUtils.writableFileChannel (out); không xuất hiện. Tôi sử dụng jcodec-0.1.3.jar mọi thứ có vẻ ổn nhưng khi tôi chạy mã, nó gây ra lỗi: "Nguồn không tìm thấy" khi đến dòng mã: muxer = new MP4Muxer (ch, Brand.MP4); – LuongTruong

+0

Nếu bạn cần thêm trợ giúp thì bước tiếp theo của bạn có thể là cung cấp mã của bạn [Cách tạo một ví dụ Tối thiểu, Hoàn chỉnh và Có thể xác minh] (http://stackoverflow.com/help/mcve) – DavidPostill

Trả lời

9

Nó chỉ là chia sẻ kinh nghiệm của tôi. Tôi sử dụng JpegImagesToMovie để giải quyết như vấn đề của bạn.

Để tham khảo thêm JpegImagesToMovie.

Mẫu Chương trình

public static void makeVideo(String fileName) throws MalformedURLException { 
    Vector<String> imgLst = get images path list. 

    JpegImagesToMovie imageToMovie = new JpegImagesToMovie(); 
    MediaLocator oml; 
    if ((oml = imageToMovie.createMediaLocator(fileName)) == null) { 
     System.err.println("Cannot build media locator from: " + fileName); 
     System.exit(0); 
    } 
    int interval = 50; 
    imageToMovie.doIt(screenWidth, screenHeight, (1000/interval), imgLst, oml); 

} 

JpegImagesToMovie.java

/* 
* @(#)JpegImagesToMovie.java 1.3 01/03/13 
* 
* Copyright (c) 1999-2001 Sun Microsystems, Inc. All Rights Reserved. 
* 
* Sun grants you ("Licensee") a non-exclusive, royalty free, license to use, 
* modify and redistribute this software in source and binary code form, 
* provided that i) this copyright notice and license appear on all copies of 
* the software; and ii) Licensee does not utilize the software in a manner 
* which is disparaging to Sun. 
* 
* This software is provided "AS IS," without a warranty of any kind. ALL 
* EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY 
* IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR 
* NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE 
* LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING 
* OR DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS 
* LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, 
* INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER 
* CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF 
* OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE 
* POSSIBILITY OF SUCH DAMAGES. 
* 
* This software is not designed or intended for use in on-line control of 
* aircraft, air traffic, aircraft navigation or aircraft communications; or in 
* the design, construction, operation or maintenance of any nuclear 
* facility. Licensee represents and warrants that it will not use or 
* redistribute the Software for such purposes. 
*/ 

import java.awt.Dimension; 
import java.io.File; 
import java.io.IOException; 
import java.io.RandomAccessFile; 
import java.net.MalformedURLException; 
import java.util.Vector; 

import javax.media.Buffer; 
import javax.media.ConfigureCompleteEvent; 
import javax.media.ControllerEvent; 
import javax.media.ControllerListener; 
import javax.media.DataSink; 
import javax.media.EndOfMediaEvent; 
import javax.media.Format; 
import javax.media.Manager; 
import javax.media.MediaLocator; 
import javax.media.PrefetchCompleteEvent; 
import javax.media.Processor; 
import javax.media.RealizeCompleteEvent; 
import javax.media.ResourceUnavailableEvent; 
import javax.media.Time; 
import javax.media.control.TrackControl; 
import javax.media.datasink.DataSinkErrorEvent; 
import javax.media.datasink.DataSinkEvent; 
import javax.media.datasink.DataSinkListener; 
import javax.media.datasink.EndOfStreamEvent; 
import javax.media.format.VideoFormat; 
import javax.media.protocol.ContentDescriptor; 
import javax.media.protocol.DataSource; 
import javax.media.protocol.FileTypeDescriptor; 
import javax.media.protocol.PullBufferDataSource; 
import javax.media.protocol.PullBufferStream; 

/** 
* This program takes a list of JPEG image files and convert them into a 
* QuickTime movie. 
*/ 
public class JpegImagesToMovie implements ControllerListener, DataSinkListener { 

    public boolean doIt(int width, int height, int frameRate, Vector inFiles, 
      MediaLocator outML) throws MalformedURLException { 
     ImageDataSource ids = new ImageDataSource(width, height, frameRate, 
       inFiles); 

     Processor p; 

     try { 
      //System.err 
      //  .println("- create processor for the image datasource ..."); 
      p = Manager.createProcessor(ids); 
     } catch (Exception e) { 
      System.err 
        .println("Yikes! Cannot create a processor from the data source."); 
      return false; 
     } 

     p.addControllerListener(this); 

     // Put the Processor into configured state so we can set 
     // some processing options on the processor. 
     p.configure(); 
     if (!waitForState(p, p.Configured)) { 
      System.err.println("Failed to configure the processor."); 
      return false; 
     } 

     // Set the output content descriptor to QuickTime. 
     p.setContentDescriptor(new ContentDescriptor(
       FileTypeDescriptor.QUICKTIME)); 

     // Query for the processor for supported formats. 
     // Then set it on the processor. 
     TrackControl tcs[] = p.getTrackControls(); 
     Format f[] = tcs[0].getSupportedFormats(); 
     if (f == null || f.length <= 0) { 
      System.err.println("The mux does not support the input format: " 
        + tcs[0].getFormat()); 
      return false; 
     } 

     tcs[0].setFormat(f[0]); 

     //System.err.println("Setting the track format to: " + f[0]); 

     // We are done with programming the processor. Let's just 
     // realize it. 
     p.realize(); 
     if (!waitForState(p, p.Realized)) { 
      System.err.println("Failed to realize the processor."); 
      return false; 
     } 

     // Now, we'll need to create a DataSink. 
     DataSink dsink; 
     if ((dsink = createDataSink(p, outML)) == null) { 
      System.err 
        .println("Failed to create a DataSink for the given output MediaLocator: " 
          + outML); 
      return false; 
     } 

     dsink.addDataSinkListener(this); 
     fileDone = false; 

     System.out.println("Generating the video : "+outML.getURL().toString()); 

     // OK, we can now start the actual transcoding. 
     try { 
      p.start(); 
      dsink.start(); 
     } catch (IOException e) { 
      System.err.println("IO error during processing"); 
      return false; 
     } 

     // Wait for EndOfStream event. 
     waitForFileDone(); 

     // Cleanup. 
     try { 
      dsink.close(); 
     } catch (Exception e) { 
     } 
     p.removeControllerListener(this); 

     System.out.println("Video creation completed!!!!!"); 
     return true; 
    } 

    /** 
    * Create the DataSink. 
    */ 
    DataSink createDataSink(Processor p, MediaLocator outML) { 

     DataSource ds; 

     if ((ds = p.getDataOutput()) == null) { 
      System.err 
        .println("Something is really wrong: the processor does not have an output DataSource"); 
      return null; 
     } 

     DataSink dsink; 

     try { 
      //System.err.println("- create DataSink for: " + outML); 
      dsink = Manager.createDataSink(ds, outML); 
      dsink.open(); 
     } catch (Exception e) { 
      System.err.println("Cannot create the DataSink: " + e); 
      return null; 
     } 

     return dsink; 
    } 

    Object waitSync = new Object(); 
    boolean stateTransitionOK = true; 

    /** 
    * Block until the processor has transitioned to the given state. Return 
    * false if the transition failed. 
    */ 
    boolean waitForState(Processor p, int state) { 
     synchronized (waitSync) { 
      try { 
       while (p.getState() < state && stateTransitionOK) 
        waitSync.wait(); 
      } catch (Exception e) { 
      } 
     } 
     return stateTransitionOK; 
    } 

    /** 
    * Controller Listener. 
    */ 
    public void controllerUpdate(ControllerEvent evt) { 

     if (evt instanceof ConfigureCompleteEvent 
       || evt instanceof RealizeCompleteEvent 
       || evt instanceof PrefetchCompleteEvent) { 
      synchronized (waitSync) { 
       stateTransitionOK = true; 
       waitSync.notifyAll(); 
      } 
     } else if (evt instanceof ResourceUnavailableEvent) { 
      synchronized (waitSync) { 
       stateTransitionOK = false; 
       waitSync.notifyAll(); 
      } 
     } else if (evt instanceof EndOfMediaEvent) { 
      evt.getSourceController().stop(); 
      evt.getSourceController().close(); 
     } 
    } 

    Object waitFileSync = new Object(); 
    boolean fileDone = false; 
    boolean fileSuccess = true; 

    /** 
    * Block until file writing is done. 
    */ 
    boolean waitForFileDone() { 
     synchronized (waitFileSync) { 
      try { 
       while (!fileDone) 
        waitFileSync.wait(); 
      } catch (Exception e) { 
      } 
     } 
     return fileSuccess; 
    } 

    /** 
    * Event handler for the file writer. 
    */ 
    public void dataSinkUpdate(DataSinkEvent evt) { 

     if (evt instanceof EndOfStreamEvent) { 
      synchronized (waitFileSync) { 
       fileDone = true; 
       waitFileSync.notifyAll(); 
      } 
     } else if (evt instanceof DataSinkErrorEvent) { 
      synchronized (waitFileSync) { 
       fileDone = true; 
       fileSuccess = false; 
       waitFileSync.notifyAll(); 
      } 
     } 
    } 

    /*public static void main(String args[]) { 

     if (args.length == 0) 
      prUsage(); 

     // Parse the arguments. 
     int i = 0; 
     int width = -1, height = -1, frameRate = 1; 
     Vector inputFiles = new Vector(); 
     String outputURL = null; 

     while (i < args.length) { 

      if (args[i].equals("-w")) { 
       i++; 
       if (i >= args.length) 
        prUsage(); 
       width = new Integer(args[i]).intValue(); 
      } else if (args[i].equals("-h")) { 
       i++; 
       if (i >= args.length) 
        prUsage(); 
       height = new Integer(args[i]).intValue(); 
      } else if (args[i].equals("-f")) { 
       i++; 
       if (i >= args.length) 
        prUsage(); 
       frameRate = new Integer(args[i]).intValue(); 
      } else if (args[i].equals("-o")) { 
       i++; 
       if (i >= args.length) 
        prUsage(); 
       outputURL = args[i]; 
      } else { 
       inputFiles.addElement(args[i]); 
      } 
      i++; 
     } 

     if (outputURL == null || inputFiles.size() == 0) 
      prUsage(); 

     // Check for output file extension. 
     if (!outputURL.endsWith(".mov") && !outputURL.endsWith(".MOV")) { 
      System.err 
        .println("The output file extension should end with a .mov extension"); 
      prUsage(); 
     } 

     if (width < 0 || height < 0) { 
      System.err.println("Please specify the correct image size."); 
      prUsage(); 
     } 

     // Check the frame rate. 
     if (frameRate < 1) 
      frameRate = 1; 

     // Generate the output media locators. 
     MediaLocator oml; 

     if ((oml = createMediaLocator(outputURL)) == null) { 
      System.err.println("Cannot build media locator from: " + outputURL); 
      System.exit(0); 
     } 

     JpegImagesToMovie imageToMovie = new JpegImagesToMovie(); 
     imageToMovie.doIt(width, height, frameRate, inputFiles, oml); 

     System.exit(0); 
    }*/ 

    static void prUsage() { 
     System.err 
       .println("Usage: java JpegImagesToMovie -w <width> -h <height> -f <frame rate> -o <output URL> <input JPEG file 1> <input JPEG file 2> ..."); 
     System.exit(-1); 
    } 

    /** 
    * Create a media locator from the given string. 
    */ 
    static MediaLocator createMediaLocator(String url) { 

     MediaLocator ml; 

     if (url.indexOf(":") > 0 && (ml = new MediaLocator(url)) != null) 
      return ml; 

     if (url.startsWith(File.separator)) { 
      if ((ml = new MediaLocator("file:" + url)) != null) 
       return ml; 
     } else { 
      String file = "file:" + System.getProperty("user.dir") 
        + File.separator + url; 
      if ((ml = new MediaLocator(file)) != null) 
       return ml; 
     } 

     return null; 
    } 

    // ///////////////////////////////////////////// 
    // 
    // Inner classes. 
    // ///////////////////////////////////////////// 

    /** 
    * A DataSource to read from a list of JPEG image files and turn that into a 
    * stream of JMF buffers. The DataSource is not seekable or positionable. 
    */ 
    class ImageDataSource extends PullBufferDataSource { 

     ImageSourceStream streams[]; 

     ImageDataSource(int width, int height, int frameRate, Vector images) { 
      streams = new ImageSourceStream[1]; 
      streams[0] = new ImageSourceStream(width, height, frameRate, images); 
     } 

     public void setLocator(MediaLocator source) { 
     } 

     public MediaLocator getLocator() { 
      return null; 
     } 

     /** 
     * Content type is of RAW since we are sending buffers of video frames 
     * without a container format. 
     */ 
     public String getContentType() { 
      return ContentDescriptor.RAW; 
     } 

     public void connect() { 
     } 

     public void disconnect() { 
     } 

     public void start() { 
     } 

     public void stop() { 
     } 

     /** 
     * Return the ImageSourceStreams. 
     */ 
     public PullBufferStream[] getStreams() { 
      return streams; 
     } 

     /** 
     * We could have derived the duration from the number of frames and 
     * frame rate. But for the purpose of this program, it's not necessary. 
     */ 
     public Time getDuration() { 
      return DURATION_UNKNOWN; 
     } 

     public Object[] getControls() { 
      return new Object[0]; 
     } 

     public Object getControl(String type) { 
      return null; 
     } 
    } 

    /** 
    * The source stream to go along with ImageDataSource. 
    */ 
    class ImageSourceStream implements PullBufferStream { 

     Vector images; 
     int width, height; 
     VideoFormat format; 

     int nextImage = 0; // index of the next image to be read. 
     boolean ended = false; 

     public ImageSourceStream(int width, int height, int frameRate, 
       Vector images) { 
      this.width = width; 
      this.height = height; 
      this.images = images; 

      format = new VideoFormat(VideoFormat.JPEG, new Dimension(width, 
        height), Format.NOT_SPECIFIED, Format.byteArray, 
        (float) frameRate); 
     } 

     /** 
     * We should never need to block assuming data are read from files. 
     */ 
     public boolean willReadBlock() { 
      return false; 
     } 

     /** 
     * This is called from the Processor to read a frame worth of video 
     * data. 
     */ 
     public void read(Buffer buf) throws IOException { 

      // Check if we've finished all the frames. 
      if (nextImage >= images.size()) { 
       // We are done. Set EndOfMedia. 
       //System.err.println("Done reading all images."); 
       buf.setEOM(true); 
       buf.setOffset(0); 
       buf.setLength(0); 
       ended = true; 
       return; 
      } 

      String imageFile = (String) images.elementAt(nextImage); 
      nextImage++; 

      //System.err.println(" - reading image file: " + imageFile); 

      // Open a random access file for the next image. 
      RandomAccessFile raFile; 
      raFile = new RandomAccessFile(imageFile, "r"); 

      byte data[] = null; 

      // Check the input buffer type & size. 

      if (buf.getData() instanceof byte[]) 
       data = (byte[]) buf.getData(); 

      // Check to see the given buffer is big enough for the frame. 
      if (data == null || data.length < raFile.length()) { 
       data = new byte[(int) raFile.length()]; 
       buf.setData(data); 
      } 

      // Read the entire JPEG image from the file. 
      raFile.readFully(data, 0, (int) raFile.length()); 

      //System.err.println(" read " + raFile.length() + " bytes."); 

      buf.setOffset(0); 
      buf.setLength((int) raFile.length()); 
      buf.setFormat(format); 
      buf.setFlags(buf.getFlags() | buf.FLAG_KEY_FRAME); 

      // Close the random access file. 
      raFile.close(); 
     } 

     /** 
     * Return the format of each video frame. That will be JPEG. 
     */ 
     public Format getFormat() { 
      return format; 
     } 

     public ContentDescriptor getContentDescriptor() { 
      return new ContentDescriptor(ContentDescriptor.RAW); 
     } 

     public long getContentLength() { 
      return 0; 
     } 

     public boolean endOfStream() { 
      return ended; 
     } 

     public Object[] getControls() { 
      return new Object[0]; 
     } 

     public Object getControl(String type) { 
      return null; 
     } 
    } 
} 
+0

Tôi tự hỏi nếu chúng ta có thể tạo một video mp4 từ hình ảnh thông qua mã mẫu của bạn? Bởi vì tôi đang làm việc với định dạng mp4. – LuongTruong

+0

Tôi thử nghiệm với định dạng '.mov'. Nhưng, tôi nghĩ bạn có thể .. – CycDemo

+1

@ user3675966 Nó là ok cho định dạng 'mp4'. Chỉ cần sử dụng tên tệp như 'xxx.mp4'. – CycDemo

Các vấn đề liên quan