001/** 002 * Copyright (c) 2011, The University of Southampton and the individual contributors. 003 * All rights reserved. 004 * 005 * Redistribution and use in source and binary forms, with or without modification, 006 * are permitted provided that the following conditions are met: 007 * 008 * * Redistributions of source code must retain the above copyright notice, 009 * this list of conditions and the following disclaimer. 010 * 011 * * Redistributions in binary form must reproduce the above copyright notice, 012 * this list of conditions and the following disclaimer in the documentation 013 * and/or other materials provided with the distribution. 014 * 015 * * Neither the name of the University of Southampton nor the names of its 016 * contributors may be used to endorse or promote products derived from this 017 * software without specific prior written permission. 018 * 019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 022 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 023 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 026 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 028 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 029 */ 030/** 031 * 032 */ 033package org.openimaj.video.xuggle; 034 035import static com.xuggle.xuggler.Global.DEFAULT_TIME_UNIT; 036import static java.util.concurrent.TimeUnit.MILLISECONDS; 037 038import java.awt.image.BufferedImage; 039 040import org.openimaj.image.ImageUtilities; 041import org.openimaj.image.MBFImage; 042import org.openimaj.video.VideoWriter; 043 044import com.xuggle.mediatool.IMediaWriter; 045import com.xuggle.mediatool.ToolFactory; 046 047/** 048 * An implementation of the video writer class that uses the Xuggler 049 * video API to write videos. Note that Xuggler will resize any images 050 * to fit within the given output size during its write process. 051 * 052 * @author David Dupplaw (dpd@ecs.soton.ac.uk) 053 * @created 27 Jul 2011 054 * 055 */ 056public class XuggleVideoWriter extends VideoWriter<MBFImage> 057{ 058 /** The media writer we'll use to write the video */ 059 private IMediaWriter writer = null; 060 061 /** The filename to write the video to */ 062 private String filename = "output.mp4"; 063 064 /** Keep track of the timecode of the next frame */ 065 private long nextFrameTime = 0; 066 067 /** This is the time between each frame in milliseconds */ 068 private long interFrameTime = 400; 069 070 /** 071 * Default constructor that takes the frame size and frame rate 072 * of the resulting video. 073 * 074 * @param filename The filename to write the video to 075 * @param width The width of the video frame in pixels 076 * @param height The height of the video frame in pixels 077 * @param frameRate The frame rate of the resulting video 078 */ 079 public XuggleVideoWriter( String filename, int width, 080 int height, double frameRate ) 081 { 082 super( width, height, frameRate ); 083 084 this.filename = filename; 085 this.initialise(); 086 087 long sd = (long)(1000/frameRate); 088 this.interFrameTime = DEFAULT_TIME_UNIT.convert(sd, MILLISECONDS); 089 } 090 091 /** 092 * Initialise the writer 093 */ 094 public void initialise() 095 { 096 if( writer == null ) 097 { 098 // First, let's make a IMediaWriter to write the file. 099 this.writer = ToolFactory.makeWriter( filename ); 100 101 // We tell it we're going to add one video stream, with id 0, 102 // at position 0 103 this.writer.addVideoStream(0, 0, width, height); 104 105 // Reset the next frame's timecode 106 this.nextFrameTime = 0; 107 } 108 } 109 110 /** 111 * {@inheritDoc} 112 * @see org.openimaj.video.processor.VideoProcessor#processingComplete() 113 */ 114 @Override 115 public void processingComplete() 116 { 117 this.close(); 118 } 119 120 /** 121 * Close the video stream 122 */ 123 @Override 124 public void close() 125 { 126 this.writer.close(); 127 } 128 129 /** 130 * {@inheritDoc} 131 * @see org.openimaj.video.VideoWriter#addFrame(org.openimaj.image.Image) 132 */ 133 @Override 134 public void addFrame( MBFImage frame ) 135 { 136 // Create a buffered image for Xuggler 137 BufferedImage f = ImageUtilities.createBufferedImage( frame ); 138 f = convertToType( f, BufferedImage.TYPE_3BYTE_BGR ); 139 140 // Encode the image to the video stream 141 this.writer.encodeVideo( 0, f, nextFrameTime, DEFAULT_TIME_UNIT ); 142 143 // Set up the next timecode 144 this.nextFrameTime += this.interFrameTime; 145 } 146 147 /** 148 * Convert a {@link BufferedImage} of any type, to {@link BufferedImage} of 149 * a specified type. If the source image is the same type as the target 150 * type, then original image is returned, otherwise new image of the correct 151 * type is created and the content of the source image is copied into the 152 * new image. 153 * 154 * @param sourceImage the image to be converted 155 * @param targetType the desired BufferedImage type 156 * 157 * @return a BufferedImage of the specified target type. 158 */ 159 public static BufferedImage convertToType( BufferedImage sourceImage, 160 int targetType ) 161 { 162 BufferedImage image; 163 164 // if the source image is already the target type, return the source image 165 if( sourceImage.getType() == targetType ) 166 image = sourceImage; 167 // otherwise create a new image of the target type and draw the new image 168 else 169 { 170 image = new BufferedImage( sourceImage.getWidth(), 171 sourceImage.getHeight(), targetType ); 172 image.getGraphics().drawImage( sourceImage, 0, 0, null ); 173 } 174 175 return image; 176 } 177 178 @Override 179 public void reset() 180 { 181 // Cannot reset the Xuggle video writer. 182 } 183}