001: package org.apollo.jmf.test;
002:
003: /*
004: * @(#)JpegImagesToMovie.java 1.3 01/03/13
005: *
006: * Copyright (c) 1999-2001 Sun Microsystems, Inc. All Rights Reserved.
007: *
008: * Sun grants you ("Licensee") a non-exclusive, royalty free, license to use,
009: * modify and redistribute this software in source and binary code form,
010: * provided that i) this copyright notice and license appear on all copies of
011: * the software; and ii) Licensee does not utilize the software in a manner
012: * which is disparaging to Sun.
013: *
014: * This software is provided "AS IS," without a warranty of any kind. ALL
015: * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY
016: * IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR
017: * NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE
018: * LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING
019: * OR DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS
020: * LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT,
021: * INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER
022: * CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF
023: * OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
024: * POSSIBILITY OF SUCH DAMAGES.
025: *
026: * This software is not designed or intended for use in on-line control of
027: * aircraft, air traffic, aircraft navigation or aircraft communications; or in
028: * the design, construction, operation or maintenance of any nuclear
029: * facility. Licensee represents and warrants that it will not use or
030: * redistribute the Software for such purposes.
031: */
032:
033: import java.io.*;
034: import java.util.*;
035: import java.awt.Dimension;
036:
037: import javax.media.*;
038: import javax.media.control.*;
039: import javax.media.protocol.*;
040: import javax.media.protocol.DataSource;
041: import javax.media.datasink.*;
042: import javax.media.format.VideoFormat;
043:
044: /**
045: * This program takes a list of JPEG image files and convert them into
046: * a QuickTime movie.
047: */
048: public class JpegImagesToMovie implements ControllerListener,
049: DataSinkListener {
050:
051: public boolean doIt(int width, int height, int frameRate,
052: Vector inFiles, MediaLocator outML) {
053: ImageDataSource ids = new ImageDataSource(width, height,
054: frameRate, inFiles);
055:
056: Processor p;
057:
058: try {
059: System.err
060: .println("- create processor for the image datasource ...");
061: p = Manager.createProcessor(ids);
062: } catch (Exception e) {
063: System.err
064: .println("Yikes! Cannot create a processor from the data source.");
065: return false;
066: }
067:
068: p.addControllerListener(this );
069:
070: // Put the Processor into configured state so we can set
071: // some processing options on the processor.
072: p.configure();
073: if (!waitForState(p, p.Configured)) {
074: System.err.println("Failed to configure the processor.");
075: return false;
076: }
077:
078: // Set the output content descriptor to QuickTime.
079: p.setContentDescriptor(new ContentDescriptor(
080: FileTypeDescriptor.QUICKTIME));
081:
082: // Query for the processor for supported formats.
083: // Then set it on the processor.
084: TrackControl tcs[] = p.getTrackControls();
085: Format f[] = tcs[0].getSupportedFormats();
086: if (f == null || f.length <= 0) {
087: System.err
088: .println("The mux does not support the input format: "
089: + tcs[0].getFormat());
090: return false;
091: }
092:
093: tcs[0].setFormat(f[0]);
094:
095: System.err.println("Setting the track format to: " + f[0]);
096:
097: // We are done with programming the processor. Let's just
098: // realize it.
099: p.realize();
100: if (!waitForState(p, p.Realized)) {
101: System.err.println("Failed to realize the processor.");
102: return false;
103: }
104:
105: // Now, we'll need to create a DataSink.
106: DataSink dsink;
107: if ((dsink = createDataSink(p, outML)) == null) {
108: System.err
109: .println("Failed to create a DataSink for the given output MediaLocator: "
110: + outML);
111: return false;
112: }
113:
114: dsink.addDataSinkListener(this );
115: fileDone = false;
116:
117: System.err.println("start processing...");
118:
119: // OK, we can now start the actual transcoding.
120: try {
121: p.start();
122: dsink.start();
123: } catch (IOException e) {
124: System.err.println("IO error during processing");
125: return false;
126: }
127:
128: // Wait for EndOfStream event.
129: waitForFileDone();
130:
131: // Cleanup.
132: try {
133: dsink.close();
134: } catch (Exception e) {
135: }
136: p.removeControllerListener(this );
137:
138: System.err.println("...done processing.");
139:
140: return true;
141: }
142:
143: /**
144: * Create the DataSink.
145: */
146: DataSink createDataSink(Processor p, MediaLocator outML) {
147:
148: DataSource ds;
149:
150: if ((ds = p.getDataOutput()) == null) {
151: System.err
152: .println("Something is really wrong: the processor does not have an output DataSource");
153: return null;
154: }
155:
156: DataSink dsink;
157:
158: try {
159: System.err.println("- create DataSink for: " + outML);
160: dsink = Manager.createDataSink(ds, outML);
161: dsink.open();
162: } catch (Exception e) {
163: System.err.println("Cannot create the DataSink: " + e);
164: return null;
165: }
166:
167: return dsink;
168: }
169:
170: Object waitSync = new Object();
171: boolean stateTransitionOK = true;
172:
173: /**
174: * Block until the processor has transitioned to the given state.
175: * Return false if the transition failed.
176: */
177: boolean waitForState(Processor p, int state) {
178: synchronized (waitSync) {
179: try {
180: while (p.getState() < state && stateTransitionOK)
181: waitSync.wait();
182: } catch (Exception e) {
183: }
184: }
185: return stateTransitionOK;
186: }
187:
188: /**
189: * Controller Listener.
190: */
191: public void controllerUpdate(ControllerEvent evt) {
192:
193: if (evt instanceof ConfigureCompleteEvent
194: || evt instanceof RealizeCompleteEvent
195: || evt instanceof PrefetchCompleteEvent) {
196: synchronized (waitSync) {
197: stateTransitionOK = true;
198: waitSync.notifyAll();
199: }
200: } else if (evt instanceof ResourceUnavailableEvent) {
201: synchronized (waitSync) {
202: stateTransitionOK = false;
203: waitSync.notifyAll();
204: }
205: } else if (evt instanceof EndOfMediaEvent) {
206: evt.getSourceController().stop();
207: evt.getSourceController().close();
208: }
209: }
210:
211: Object waitFileSync = new Object();
212: boolean fileDone = false;
213: boolean fileSuccess = true;
214:
215: /**
216: * Block until file writing is done.
217: */
218: boolean waitForFileDone() {
219: synchronized (waitFileSync) {
220: try {
221: while (!fileDone)
222: waitFileSync.wait();
223: } catch (Exception e) {
224: }
225: }
226: return fileSuccess;
227: }
228:
229: /**
230: * Event handler for the file writer.
231: */
232: public void dataSinkUpdate(DataSinkEvent evt) {
233:
234: if (evt instanceof EndOfStreamEvent) {
235: synchronized (waitFileSync) {
236: fileDone = true;
237: waitFileSync.notifyAll();
238: }
239: } else if (evt instanceof DataSinkErrorEvent) {
240: synchronized (waitFileSync) {
241: fileDone = true;
242: fileSuccess = false;
243: waitFileSync.notifyAll();
244: }
245: }
246: }
247:
248: public static void main(String args[]) {
249:
250: if (args.length == 0)
251: prUsage();
252:
253: // Parse the arguments.
254: int i = 0;
255: int width = -1, height = -1, frameRate = 1;
256: Vector inputFiles = new Vector();
257: String outputURL = null;
258:
259: while (i < args.length) {
260:
261: if (args[i].equals("-w")) {
262: i++;
263: if (i >= args.length)
264: prUsage();
265: width = new Integer(args[i]).intValue();
266: } else if (args[i].equals("-h")) {
267: i++;
268: if (i >= args.length)
269: prUsage();
270: height = new Integer(args[i]).intValue();
271: } else if (args[i].equals("-f")) {
272: i++;
273: if (i >= args.length)
274: prUsage();
275: frameRate = new Integer(args[i]).intValue();
276: } else if (args[i].equals("-o")) {
277: i++;
278: if (i >= args.length)
279: prUsage();
280: outputURL = args[i];
281: } else {
282: for (int j = 0; j < 120; j++) {
283: inputFiles.addElement(args[i]);
284: }
285: }
286: i++;
287: }
288:
289: if (outputURL == null || inputFiles.size() == 0)
290: prUsage();
291:
292: // Check for output file extension.
293: if (!outputURL.endsWith(".mov") && !outputURL.endsWith(".MOV")) {
294: System.err
295: .println("The output file extension should end with a .mov extension");
296: prUsage();
297: }
298:
299: if (width < 0 || height < 0) {
300: System.err
301: .println("Please specify the correct image size.");
302: prUsage();
303: }
304:
305: // Check the frame rate.
306: if (frameRate < 1)
307: frameRate = 1;
308:
309: // Generate the output media locators.
310: MediaLocator oml;
311:
312: if ((oml = createMediaLocator(outputURL)) == null) {
313: System.err.println("Cannot build media locator from: "
314: + outputURL);
315: System.exit(0);
316: }
317:
318: JpegImagesToMovie imageToMovie = new JpegImagesToMovie();
319: imageToMovie.doIt(width, height, frameRate, inputFiles, oml);
320:
321: System.exit(0);
322: }
323:
324: static void prUsage() {
325: System.err
326: .println("Usage: java JpegImagesToMovie -w <width> -h <height> -f <frame rate> -o <output URL> <input JPEG file 1> <input JPEG file 2> ...");
327: System.exit(-1);
328: }
329:
330: /**
331: * Create a media locator from the given string.
332: */
333: static MediaLocator createMediaLocator(String url) {
334:
335: MediaLocator ml;
336:
337: if (url.indexOf(":") > 0
338: && (ml = new MediaLocator(url)) != null)
339: return ml;
340:
341: if (url.startsWith(File.separator)) {
342: if ((ml = new MediaLocator("file:" + url)) != null)
343: return ml;
344: } else {
345: String file = "file:" + System.getProperty("user.dir")
346: + File.separator + url;
347: if ((ml = new MediaLocator(file)) != null)
348: return ml;
349: }
350:
351: return null;
352: }
353:
354: ///////////////////////////////////////////////
355: //
356: // Inner classes.
357: ///////////////////////////////////////////////
358:
359: /**
360: * A DataSource to read from a list of JPEG image files and
361: * turn that into a stream of JMF buffers.
362: * The DataSource is not seekable or positionable.
363: */
364: class ImageDataSource extends PullBufferDataSource {
365:
366: ImageSourceStream streams[];
367:
368: ImageDataSource(int width, int height, int frameRate,
369: Vector images) {
370: streams = new ImageSourceStream[1];
371: streams[0] = new ImageSourceStream(width, height,
372: frameRate, images);
373: }
374:
375: public void setLocator(MediaLocator source) {
376: }
377:
378: public MediaLocator getLocator() {
379: return null;
380: }
381:
382: /**
383: * Content type is of RAW since we are sending buffers of video
384: * frames without a container format.
385: */
386: public String getContentType() {
387: return ContentDescriptor.RAW;
388: }
389:
390: public void connect() {
391: }
392:
393: public void disconnect() {
394: }
395:
396: public void start() {
397: }
398:
399: public void stop() {
400: }
401:
402: /**
403: * Return the ImageSourceStreams.
404: */
405: public PullBufferStream[] getStreams() {
406: return streams;
407: }
408:
409: /**
410: * We could have derived the duration from the number of
411: * frames and frame rate. But for the purpose of this program,
412: * it's not necessary.
413: */
414: public Time getDuration() {
415: return DURATION_UNKNOWN;
416: }
417:
418: public Object[] getControls() {
419: return new Object[0];
420: }
421:
422: public Object getControl(String type) {
423: return null;
424: }
425: }
426:
427: /**
428: * The source stream to go along with ImageDataSource.
429: */
430: class ImageSourceStream implements PullBufferStream {
431:
432: Vector images;
433: int width, height;
434: VideoFormat format;
435:
436: int nextImage = 0; // index of the next image to be read.
437: boolean ended = false;
438:
439: public ImageSourceStream(int width, int height, int frameRate,
440: Vector images) {
441: this .width = width;
442: this .height = height;
443: this .images = images;
444:
445: format = new VideoFormat(VideoFormat.JPEG, new Dimension(
446: width, height), Format.NOT_SPECIFIED,
447: Format.byteArray, (float) frameRate);
448: }
449:
450: /**
451: * We should never need to block assuming data are read from files.
452: */
453: public boolean willReadBlock() {
454: return false;
455: }
456:
457: /**
458: * This is called from the Processor to read a frame worth
459: * of video data.
460: */
461: public void read(Buffer buf) throws IOException {
462:
463: // Check if we've finished all the frames.
464: if (nextImage >= images.size()) {
465: // We are done. Set EndOfMedia.
466: System.err.println("Done reading all images.");
467: buf.setEOM(true);
468: buf.setOffset(0);
469: buf.setLength(0);
470: ended = true;
471: return;
472: }
473:
474: String imageFile = (String) images.elementAt(nextImage);
475: nextImage++;
476:
477: System.err.println(" - reading image file: " + imageFile);
478:
479: // Open a random access file for the next image.
480: RandomAccessFile raFile;
481: raFile = new RandomAccessFile(imageFile, "r");
482:
483: byte data[] = null;
484:
485: // Check the input buffer type & size.
486:
487: if (buf.getData() instanceof byte[])
488: data = (byte[]) buf.getData();
489:
490: // Check to see the given buffer is big enough for the frame.
491: if (data == null || data.length < raFile.length()) {
492: data = new byte[(int) raFile.length()];
493: buf.setData(data);
494: }
495:
496: // Read the entire JPEG image from the file.
497: raFile.readFully(data, 0, (int) raFile.length());
498:
499: System.err.println(" read " + raFile.length()
500: + " bytes.");
501:
502: buf.setOffset(0);
503: buf.setLength((int) raFile.length());
504: buf.setFormat(format);
505: buf.setFlags(buf.getFlags() | buf.FLAG_KEY_FRAME);
506:
507: // Close the random access file.
508: raFile.close();
509: }
510:
511: /**
512: * Return the format of each video frame. That will be JPEG.
513: */
514: public Format getFormat() {
515: return format;
516: }
517:
518: public ContentDescriptor getContentDescriptor() {
519: return new ContentDescriptor(ContentDescriptor.RAW);
520: }
521:
522: public long getContentLength() {
523: return 0;
524: }
525:
526: public boolean endOfStream() {
527: return ended;
528: }
529:
530: public Object[] getControls() {
531: return new Object[0];
532: }
533:
534: public Object getControl(String type) {
535: return null;
536: }
537: }
538: }
|