001/*******************************************************************************
002 * Copyright (c) 2024, 2026, Olivier Ayache.  All rights reserved.
003 *
004 * This file is part of AVPKit.
005 *
006 * AVPKit is free software: you can redistribute it and/or modify
007 * it under the terms of the GNU Lesser General Public License as published by
008 * the Free Software Foundation, either version 3 of the License, or
009 * (at your option) any later version.
010 *
011 * AVPKit is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
014 * GNU Lesser General Public License for more details.
015 *
016 * You should have received a copy of the GNU Lesser General Public License
017 * along with AVPKit.  If not, see <http://www.gnu.org/licenses/>.
018 *******************************************************************************/
019
020package com.avpkit.core;
021
022import java.util.List;
023
024import org.apache.commons.cli.*;
025import org.slf4j.Logger;
026import org.slf4j.LoggerFactory;
027
028import com.avpkit.core.Global;
029import com.avpkit.core.IAudioResampler;
030import com.avpkit.core.IAudioSamples;
031import com.avpkit.core.IAudioSamples.Format;
032import com.avpkit.core.ICodec;
033import com.avpkit.core.IContainer;
034import com.avpkit.core.IContainerFormat;
035import com.avpkit.core.IVideoPicture;
036import com.avpkit.core.IPacket;
037import com.avpkit.core.IRational;
038import com.avpkit.core.IStream;
039import com.avpkit.core.IStreamCoder;
040import com.avpkit.core.IVideoResampler;
041
042import com.avpkit.core.io.URLProtocolManager;
043
044/**
045 * An example class that shows how to use the AVPKit library to open, decode,
046 * re-sample, encode and write media files.
047 * 
048 * <p>
049 * 
050 * This class is called by the {@link AVPKit} class to do all the heavy
051 * lifting. It is meant as a Demonstration class and implements a small subset
052 * of the functionality that the (much more full-featured) <code>ffmpeg</code>
053 * command line tool implements. It is really meant to show people how the
054 * AVPKit library is used from java.
055 * 
056 * </p>
057 * <p>
058 * 
059 * Read <a href="{@docRoot}/src-html/com/avpkit/core/Converter.html">the
060 * Converter.java source</a> for a good example of a class exercising this
061 * library.
062 * 
063 * </p>
064 * <p>
065 * 
066 * <strong>It is not our intent to replicate all features in the
067 * <code>ffmpeg</code> command line tool in this class.</strong>
068 * 
069 * </p>
070 * <p>
071 * 
072 * If you are just trying to convert pre-existing media files from one format to
073 * another with a batch-processing program we strongly recommend you use the
074 * <code>ffmpeg</code> command-line tool to do it. Look, here's even the <a
075 * href="http://ffmpeg.org/ffmpeg-doc.html">documentation</a> for that program.
076 * 
077 * </p>
078 * <p>
079 * 
080 * If on the other hand you need to programatically decide when and how you do
081 * video processing, or process only parts of files, or do transcoding live
082 * within a Java server without calling out to another process, then by all
083 * means use AVPKit and use this class as an example of how to do conversion.
084 * But please recognize you will likely need to implement code specific to your
085 * application. <strong>This class is no substitute for actually understanding
086 * the how to use the AVPKit API within your specific use-case</strong>
087 * 
088 * </p>
089 * <p>
090 * 
091 * And if you haven't gotten the impression, please stop asking us to support
092 * <code>ffmpeg</code> options like "-re" in this class. This class is only
093 * meant as a teaching-aide for the underlying AVPKit API.
094 * 
095 * </p>
096 * <p>
097 * 
098 * Instead implement your own Java class based off of this that does real-time
099 * playback yourself. Really. Please. We'd appreciate it very much.
100 * 
101 * </p>
102 * <p>
103 * Now, all that said, here's how to create a main class that uses this
104 * Converter class:
105 * </p>
106 * 
107 * <pre>
108 * public static void main(String[] args)
109 * {
110 *   Converter converter = new Converter();
111 *   try
112 *   {
113 *     // first define options
114 *     Options options = converter.defineOptions();
115 *     // And then parse them.
116 *     CommandLine cmdLine = converter.parseOptions(options, args);
117 *     // Finally, run the converter.
118 *     converter.run(cmdLine);
119 *   }
120 *   catch (Exception exception)
121 *   {
122 *     System.err.printf(&quot;Error: %s\n&quot;, exception.getMessage());
123 *   }
124 * }
125 * </pre>
126 * 
127 * <p>
128 * 
129 * Pass &quot;--help&quot to your main class as the argument to see the list of
130 * accepted options.
131 * 
132 * </p>
133 * 
134 * @author aclarke
135 * 
136 */
137public class Converter
138{
139  static
140  {
141    // this forces the FFMPEG io library to be loaded which means we can bypass
142    // FFMPEG's file io if needed
143    URLProtocolManager.getManager();
144  }
145
146  /**
147   * Create a new Converter object.
148   */
149  public Converter()
150  {
151      
152  }
153
154  private final Logger log = LoggerFactory.getLogger(this.getClass());
155
156  /**
157   * A container we'll use to read data from.
158   */
159  private IContainer mIContainer = null;
160  /**
161   * A container we'll use to write data from.
162   */
163  private IContainer mOContainer = null;
164
165  /**
166   * A set of {@link IStream} values for each stream in the input
167   * {@link IContainer}.
168   */
169  private IStream[] mIStreams = null;
170  /**
171   * A set of {@link IStreamCoder} objects we'll use to decode audio and video.
172   */
173  private IStreamCoder[] mICoders = null;
174
175  /**
176   * A set of {@link IStream} objects for each stream we'll output to the output
177   * {@link IContainer}.
178   */
179  private IStream[] mOStreams = null;
180  /**
181   * A set of {@link IStreamCoder} objects we'll use to encode audio and video.
182   */
183  private IStreamCoder[] mOCoders = null;
184
185  /**
186   * A set of {@link IVideoPicture} objects that we'll use to hold decoded video
187   * data.
188   */
189  private IVideoPicture[] mIVideoPictures = null;
190  /**
191   * A set of {@link IVideoPicture} objects we'll use to hold
192   * potentially-resampled video data before we encode it.
193   */
194  private IVideoPicture[] mOVideoPictures = null;
195
196  /**
197   * A set of {@link IAudioSamples} objects we'll use to hold decoded audio
198   * data.
199   */
200  private IAudioSamples[] mISamples = null;
201  /**
202   * A set of {@link IAudioSamples} objects we'll use to hold
203   * potentially-resampled audio data before we encode it.
204   */
205  private IAudioSamples[] mOSamples = null;
206
207  /**
208   * A set of {@link IAudioResampler} objects (one for each stream) we'll use to
209   * resample audio if needed.
210   */
211  private IAudioResampler[] mASamplers = null;
212  /**
213   * A set of {@link IVideoResampler} objects (one for each stream) we'll use to
214   * resample video if needed.
215   */
216  private IVideoResampler[] mVSamplers = null;
217
218  /**
219   * Should we convert audio
220   */
221  private boolean mHasAudio = true;
222  /**
223   * Should we convert video
224   */
225  private boolean mHasVideo = true;
226
227  /**
228   * Should we force an interleaving of the output
229   */
230  private final boolean mForceInterleave = true;
231
232  /**
233   * Should we attempt to encode 'in real time'
234   */
235  private boolean mRealTimeEncoder;
236
237  private Long mStartClockTime;
238  private Long mStartStreamTime;
239
240  /**
241   * Define all the command line options this program can take.
242   * 
243   * @return The set of accepted options.
244   */
245  public Options defineOptions()
246  {
247    Options options = new Options();
248
249    Option help = new Option("help", "print this message");
250
251    /*
252     * Output container options.
253     */
254    OptionBuilder.withArgName("container-format");
255    OptionBuilder.hasArg(true);
256    OptionBuilder
257        .withDescription("output container format to use (e.g. \"mov\")");
258    Option containerFormat = OptionBuilder.create("containerformat");
259
260    OptionBuilder.withArgName("icontainer-format");
261    OptionBuilder.hasArg(true);
262    OptionBuilder
263        .withDescription("input container format to use (e.g. \"mov\")");
264    Option icontainerFormat = OptionBuilder.create("icontainerformat");    
265    
266    OptionBuilder.withArgName("file");
267    OptionBuilder.hasArg(true);
268    OptionBuilder
269        .withDescription("container option presets file");
270    Option cpreset = OptionBuilder.create("cpreset");
271
272    /*
273     * Audio options
274     */
275    OptionBuilder.hasArg(false);
276    OptionBuilder.withDescription("no audio");
277    Option ano = OptionBuilder.create("ano");
278
279    OptionBuilder.withArgName("file");
280    OptionBuilder.hasArg(true);
281    OptionBuilder
282        .withDescription("audio option presets file");
283    Option apreset = OptionBuilder.create("apreset");
284
285    OptionBuilder.withArgName("codec");
286    OptionBuilder.hasArg(true);
287    OptionBuilder
288        .withDescription("audio codec to encode with (e.g. \"libmp3lame\")");
289    Option acodec = OptionBuilder.create("acodec");
290
291    OptionBuilder.withArgName("icodec");
292    OptionBuilder.hasArg(true);
293    OptionBuilder
294        .withDescription("input audio codec  (e.g. \"libmp3lame\")");
295    Option iacodec = OptionBuilder.create("iacodec");    
296    
297    OptionBuilder.withArgName("sample-rate");
298    OptionBuilder.hasArg(true);
299    OptionBuilder
300        .withDescription("audio sample rate to (up/down) encode with (in hz) (e.g. \"22050\")");
301    Option asamplerate = OptionBuilder.create("asamplerate");
302
303    OptionBuilder.withArgName("isample-rate");
304    OptionBuilder.hasArg(true);
305    OptionBuilder
306        .withDescription("input audio sample rate to (up/down) encode with (in hz) (e.g. \"22050\")");
307    Option iasamplerate = OptionBuilder.create("iasamplerate");    
308    
309    OptionBuilder.withArgName("channels");
310    OptionBuilder.hasArg(true);
311    OptionBuilder
312        .withDescription("number of audio channels (1 or 2) to encode with (e.g. \"2\")");
313    Option achannels = OptionBuilder.create("achannels");
314
315    OptionBuilder.withArgName("ichannels");
316    OptionBuilder.hasArg(true);
317    OptionBuilder
318        .withDescription("input number of audio channels (1 or 2)");
319    Option iachannels = OptionBuilder.create("iachannels");    
320    
321    OptionBuilder.withArgName("abit-rate");
322    OptionBuilder.hasArg(true);
323    OptionBuilder
324        .withDescription("bit rate to encode audio with (in bps) (e.g. \"60000\")");
325    Option abitrate = OptionBuilder.create("abitrate");
326
327    OptionBuilder.withArgName("stream");
328    OptionBuilder.hasArg(true);
329    OptionBuilder
330        .withDescription("if multiple audio streams of a given type, this is the stream you want to output");
331    Option astream = OptionBuilder.create("astream");
332
333    OptionBuilder.withArgName("quality");
334    OptionBuilder.hasArg(true);
335    OptionBuilder
336        .withDescription("quality setting to use for audio.  0 means same as source; higher numbers are (perversely) lower quality.  Defaults to 0.");
337    Option aquality = OptionBuilder.create("aquality");
338
339    /*
340     * Video options
341     */
342    OptionBuilder.hasArg(false);
343    OptionBuilder.withDescription("no video");
344    Option vno = OptionBuilder.create("vno");
345
346    OptionBuilder.withArgName("file");
347    OptionBuilder.hasArg(true);
348    OptionBuilder
349        .withDescription("video option presets file");
350    Option vpreset = OptionBuilder.create("vpreset");
351
352    OptionBuilder.withArgName("codec");
353    OptionBuilder.hasArg(true);
354    OptionBuilder
355        .withDescription("video codec to encode with (e.g. \"mpeg4\")");
356    Option vcodec = OptionBuilder.create("vcodec");
357
358    OptionBuilder.withArgName("factor");
359    OptionBuilder.hasArg(true);
360    OptionBuilder
361        .withDescription("scaling factor to scale output video by (e.g. \"0.75\")");
362    Option vscaleFactor = OptionBuilder.create("vscalefactor");
363
364    OptionBuilder.withArgName("vbitrate");
365    OptionBuilder.hasArg(true);
366    OptionBuilder
367        .withDescription("bit rate to encode video with (in bps) (e.g. \"60000\")");
368    Option vbitrate = OptionBuilder.create("vbitrate");
369
370    OptionBuilder.withArgName("vbitratetolerance");
371    OptionBuilder.hasArg(true);
372    OptionBuilder
373        .withDescription("bit rate tolerance the bitstream is allowed to diverge from the reference (in bits) (e.g. \"1200000\")");
374    Option vbitratetolerance = OptionBuilder.create("vbitratetolerance");
375
376    OptionBuilder.withArgName("stream");
377    OptionBuilder.hasArg(true);
378    OptionBuilder
379        .withDescription("if multiple video streams of a given type, this is the stream you want to output");
380    Option vstream = OptionBuilder.create("vstream");
381
382    OptionBuilder.withArgName("quality");
383    OptionBuilder.hasArg(true);
384    OptionBuilder
385        .withDescription("quality setting to use for video.  0 means same as source; higher numbers are (perversely) lower quality.  Defaults to 0.  If set, then bitrate flags are ignored.");
386    Option vquality = OptionBuilder.create("vquality");
387    
388    OptionBuilder.withArgName("realtime");
389    OptionBuilder.hasArg(false);
390    OptionBuilder.withDescription("attempt to encode frames at the realtime rate -- i.e. it encodes when the picture should play");
391    Option realtime = OptionBuilder.create("realtime");
392
393    options.addOption(help);
394    options.addOption(containerFormat);
395    options.addOption(cpreset);
396    options.addOption(ano);
397    options.addOption(apreset);
398    options.addOption(acodec);
399    options.addOption(asamplerate);
400    options.addOption(achannels);
401    options.addOption(abitrate);
402    options.addOption(astream);
403    options.addOption(aquality);
404    options.addOption(vno);
405    options.addOption(vpreset);
406    options.addOption(vcodec);
407    options.addOption(vscaleFactor);
408    options.addOption(vbitrate);
409    options.addOption(vbitratetolerance);
410    options.addOption(vstream);
411    options.addOption(vquality);
412
413    options.addOption(icontainerFormat);
414    options.addOption(iacodec);
415    options.addOption(iachannels);
416    options.addOption(iasamplerate);
417
418    options.addOption(realtime);
419    
420    return options;
421  }
422
423  /**
424   * Given a set of arguments passed into this object, return back a parsed
425   * command line.
426   * 
427   * @param opt
428   *          A set of options as defined by {@link #defineOptions()}.
429   * @param args
430   *          A set of command line arguments passed into this class.
431   * @return A parsed command line.
432   * @throws ParseException
433   *           If there is an error in the command line.
434   */
435  public CommandLine parseOptions(Options opt, String[] args)
436      throws ParseException
437  {
438    CommandLine cmdLine = null;
439
440    CommandLineParser parser = new GnuParser();
441
442    cmdLine = parser.parse(opt, args);
443
444    if (cmdLine.hasOption("help"))
445    {
446      HelpFormatter help = new HelpFormatter();
447      help.printHelp("AVPKit [options] input_url output_url", opt);
448      System.exit(1);
449    }
450    // Make sure we have only two left over args
451    if (cmdLine.getArgs().length != 2)
452      throw new ParseException("missing input or output url");
453
454    return cmdLine;
455  }
456
457  /**
458   * Get an integer value from a command line argument.
459   * 
460   * @param cmdLine
461   *          A parsed command line (as returned from
462   *          {@link #parseOptions(Options, String[])}
463   * @param key
464   *          The key for an option you want.
465   * @param defaultVal
466   *          The default value you want set if the key is not present in
467   *          cmdLine.
468   * @return The value for the key in the cmdLine, or defaultVal if it's not
469   *         there.
470   */
471  private int getIntOptionValue(CommandLine cmdLine, String key, int defaultVal)
472  {
473    int retval = defaultVal;
474    String optValue = cmdLine.getOptionValue(key);
475
476    if (optValue != null)
477    {
478      try
479      {
480        retval = Integer.parseInt(optValue);
481      }
482      catch (Exception ex)
483      {
484        log
485            .warn(
486                "Option \"{}\" value \"{}\" cannot be converted to integer; using {} instead",
487                new Object[]
488                {
489                    key, optValue, defaultVal
490                });
491      }
492    }
493    return retval;
494  }
495
496  /**
497   * Get a double value from a command line argument.
498   * 
499   * @param cmdLine
500   *          A parsed command line (as returned from
501   *          {@link #parseOptions(Options, String[])}
502   * @param key
503   *          The key for an option you want.
504   * @param defaultVal
505   *          The default value you want set if the key is not present in
506   *          cmdLine.
507   * @return The value for the key in the cmdLine, or defaultVal if it's not
508   *         there.
509   */
510  private double getDoubleOptionValue(CommandLine cmdLine, String key,
511      double defaultVal)
512  {
513    double retval = defaultVal;
514    String optValue = cmdLine.getOptionValue(key);
515
516    if (optValue != null)
517    {
518      try
519      {
520        retval = Double.parseDouble(optValue);
521      }
522      catch (Exception ex)
523      {
524        log
525            .warn(
526                "Option \"{}\" value \"{}\" cannot be converted to double; using {} instead",
527                new Object[]
528                {
529                    key, optValue, defaultVal
530                });
531      }
532    }
533    return retval;
534  }
535
536  /**
537   * Open an initialize all AVPKit objects needed to encode and decode a video
538   * file.
539   * 
540   * @param cmdLine
541   *          A command line (as returned from
542   *          {@link #parseOptions(Options, String[])}) that specifies what
543   *          files we want to process and how to process them.
544   * @return Number of streams in the input file, or <= 0 on error.
545   */
546
547  int setupStreams(CommandLine cmdLine)
548  {
549    String inputURL = cmdLine.getArgs()[0];
550    String outputURL = cmdLine.getArgs()[1];
551
552    mHasAudio = !cmdLine.hasOption("ano");
553    mHasVideo = !cmdLine.hasOption("vno");
554
555    mRealTimeEncoder = cmdLine.hasOption("realtime");
556    
557    String acodec = cmdLine.getOptionValue("acodec");
558    String vcodec = cmdLine.getOptionValue("vcodec");
559    String containerFormat = cmdLine.getOptionValue("containerformat");
560    int astream = getIntOptionValue(cmdLine, "astream", -1);
561    int aquality = getIntOptionValue(cmdLine, "aquality", 0);
562
563    int sampleRate = getIntOptionValue(cmdLine, "asamplerate", 0);
564    int channels = getIntOptionValue(cmdLine, "achannels", 0);
565    int abitrate = getIntOptionValue(cmdLine, "abitrate", 0);
566    int vbitrate = getIntOptionValue(cmdLine, "vbitrate", 0);
567    int vbitratetolerance = getIntOptionValue(cmdLine, "vbitratetolerance", 0);
568    int vquality = getIntOptionValue(cmdLine, "vquality", -1);
569    int vstream = getIntOptionValue(cmdLine, "vstream", -1);
570    double vscaleFactor = getDoubleOptionValue(cmdLine, "vscalefactor", 1.0);
571
572    String icontainerFormat = cmdLine.getOptionValue("icontainerformat");    
573    String iacodec = cmdLine.getOptionValue("iacodec");
574    int isampleRate = getIntOptionValue(cmdLine, "iasamplerate", 0);
575    int ichannels = getIntOptionValue(cmdLine, "iachannels", 0);
576    
577    // Should have everything now!
578    int retval = 0;
579
580    /**
581     * Create one container for input, and one for output.
582     */
583    mIContainer = IContainer.make();
584    mOContainer = IContainer.make();
585    
586    String cpreset = cmdLine.getOptionValue("cpreset");
587    if (cpreset != null)
588      Configuration.configure(cpreset, mOContainer);
589    
590    IContainerFormat iFmt = null;
591    IContainerFormat oFmt = null;
592
593    
594    // override input format
595    if (icontainerFormat != null)
596    {
597      iFmt = IContainerFormat.make();
598     
599      /**
600       * Try to find an output format based on what the user specified, or
601       * failing that, based on the outputURL (e.g. if it ends in .flv, we'll
602       * guess FLV).
603       */
604      retval = iFmt.setInputFormat(icontainerFormat);
605      if (retval < 0)
606        throw new RuntimeException("could not find input container format: "
607            + icontainerFormat);
608    }    
609    
610    // override the input codec
611    if (iacodec != null)
612    {
613      ICodec codec = null;
614      /**
615       * Looks like they did specify one; let's look it up by name.
616       */
617      codec = ICodec.findDecodingCodecByName(iacodec);
618      if (codec == null || codec.getType() != ICodec.Type.CODEC_TYPE_AUDIO)
619        throw new RuntimeException("could not find decoder: " + iacodec);
620      /**
621       * Now, tell the output stream coder that it's to use that codec.
622       */
623      mIContainer.setForcedAudioCodec(codec.getID());
624    }
625    
626
627    /**
628     * Open the input container for Reading.
629     */
630    IMetaData parameters = IMetaData.make();
631    
632    if (isampleRate > 0)
633      parameters.setValue("sample_rate", ""+isampleRate);
634
635    if (ichannels > 0)
636      parameters.setValue("channels", ""+ichannels);
637    
638    IMetaData rejectParameters = IMetaData.make();
639
640    retval = mIContainer.open(inputURL, IContainer.Type.READ, iFmt, false, true, 
641        parameters, rejectParameters);
642    if (retval < 0)
643      throw new RuntimeException("could not open url: " + inputURL);
644    if (rejectParameters.getNumKeys() > 0)
645      throw new RuntimeException("some parameters were rejected: " + rejectParameters);
646    /**
647     * If the user EXPLICITLY asked for a output container format, we'll try to
648     * honor their request here.
649     */
650    if (containerFormat != null)
651    {
652      oFmt = IContainerFormat.make();
653      /**
654       * Try to find an output format based on what the user specified, or
655       * failing that, based on the outputURL (e.g. if it ends in .flv, we'll
656       * guess FLV).
657       */
658      retval = oFmt.setOutputFormat(containerFormat, outputURL, null);
659      if (retval < 0)
660        throw new RuntimeException("could not find output container format: "
661            + containerFormat);
662    }
663       
664    /**
665     * Open the output container for writing. If oFmt is null, we are telling
666     * AVPKit to guess the output container format based on the outputURL.
667     */
668    retval = mOContainer.open(outputURL, IContainer.Type.WRITE, oFmt);
669    if (retval < 0)
670      throw new RuntimeException("could not open output url: " + outputURL);
671
672    /**
673     * Find out how many streams are there in the input container? For example,
674     * most FLV files will have 2 -- 1 audio stream and 1 video stream.
675     */
676    int numStreams = mIContainer.getNumStreams();
677    if (numStreams <= 0)
678      throw new RuntimeException("not streams in input url: " + inputURL);
679
680    /**
681     * Here we create IStream, IStreamCoders and other objects for each input
682     * stream.
683     * 
684     * We make parallel objects for each output stream as well.
685     */
686    mIStreams = new IStream[numStreams];
687    mICoders = new IStreamCoder[numStreams];
688    mOStreams = new IStream[numStreams];
689    mOCoders = new IStreamCoder[numStreams];
690    mASamplers = new IAudioResampler[numStreams];
691    mVSamplers = new IVideoResampler[numStreams];
692    mIVideoPictures = new IVideoPicture[numStreams];
693    mOVideoPictures = new IVideoPicture[numStreams];
694    mISamples = new IAudioSamples[numStreams];
695    mOSamples = new IAudioSamples[numStreams];
696
697    /**
698     * Now let's go through the input streams one by one and explicitly set up
699     * our contexts.
700     */
701    for (int i = 0; i < numStreams; i++)
702    {
703      /**
704       * Get the IStream for this input stream.
705       */
706      IStream is = mIContainer.getStream(i);
707      /**
708       * And get the input stream coder. AVPKit will set up all sorts of
709       * defaults on this StreamCoder for you (such as the audio sample rate)
710       * when you open it.
711       * 
712       * You can create IStreamCoders yourself using
713       * IStreamCoder#make(IStreamCoder.Direction), but then you have to set all
714       * parameters yourself.
715       */
716      IStreamCoder ic = is.getStreamCoder();
717
718      /**
719       * Find out what Codec AVPKit guessed the input stream was encoded with.
720       */
721      ICodec.Type cType = ic.getCodecType();
722
723      mIStreams[i] = is;
724      mICoders[i] = ic;
725      mOStreams[i] = null;
726      mOCoders[i] = null;
727      mASamplers[i] = null;
728      mVSamplers[i] = null;
729      mIVideoPictures[i] = null;
730      mOVideoPictures[i] = null;
731      mISamples[i] = null;
732      mOSamples[i] = null;
733
734      if (cType == ICodec.Type.CODEC_TYPE_AUDIO && mHasAudio
735          && (astream == -1 || astream == i))
736      {
737         /**
738         * First, did the user specify an audio codec?
739         */
740        ICodec codec = null;
741        if (acodec != null)
742        {
743          /**
744           * Looks like they did specify one; let's look it up by name.
745           */
746          codec = ICodec.findEncodingCodecByName(acodec);
747          if (codec == null || codec.getType() != cType)
748            throw new RuntimeException("could not find encoder: " + acodec);
749 
750        }
751        else
752        {
753          /**
754           * Looks like the user didn't specify an output coder for audio.
755           * 
756           * So we ask AVPKit to guess an appropriate output coded based on the
757           * URL, container format, and that it's audio.
758           */
759          codec = ICodec.guessEncodingCodec(oFmt, null, outputURL, null,
760              cType);
761          if (codec == null)
762            throw new RuntimeException("could not guess " + cType
763                + " encoder for: " + outputURL);
764        }
765        /**
766         * So it looks like this stream as an audio stream. Now we add an audio
767         * stream to the output container that we will use to encode our
768         * resampled audio.
769         */
770        IStream os = mOContainer.addNewStream(codec);
771
772        /**
773         * And we ask the IStream for an appropriately configured IStreamCoder
774         * for output.
775         * 
776         * Unfortunately you still need to specify a lot of things for
777         * outputting (because we can't really guess what you want to encode
778         * as).
779         */
780        IStreamCoder oc = os.getStreamCoder();
781
782        mOStreams[i] = os;
783        mOCoders[i] = oc;
784
785       /**
786         * Now let's see if the codec can support the input sample format; if not
787         * we pick the last sample format the codec supports.
788         */
789        Format preferredFormat = ic.getSampleFormat();
790        
791        List<Format> formats = codec.getSupportedAudioSampleFormats();
792        for(Format format : formats) {
793          oc.setSampleFormat(format);
794          if (format == preferredFormat)
795            break;
796        }
797
798        final String apreset = cmdLine.getOptionValue("apreset");
799        if (apreset != null)
800          Configuration.configure(apreset, oc);
801        
802        /**
803         * In general a IStreamCoder encoding audio needs to know: 1) A ICodec
804         * to use. 2) The sample rate and number of channels of the audio. Most
805         * everything else can be defaulted.
806         */
807
808        /**
809         * If the user didn't specify a sample rate to encode as, then just use
810         * the same sample rate as the input.
811         */
812        if (sampleRate == 0)
813          sampleRate = ic.getSampleRate();
814        oc.setSampleRate(sampleRate);
815        /**
816         * If the user didn't specify a bit rate to encode as, then just use the
817         * same bit as the input.
818         */
819        if (abitrate == 0)
820          abitrate = ic.getBitRate();
821        if (abitrate == 0)
822          // some containers don't give a bit-rate
823          abitrate = 64000;
824        oc.setBitRate(abitrate);
825        
826        /**
827         * If the user didn't specify the number of channels to encode audio as,
828         * just assume we're keeping the same number of channels.
829         */
830        if (channels == 0)
831          channels = ic.getChannels();
832        oc.setChannels(channels);
833
834        /**
835         * And set the quality (which defaults to 0, or highest, if the user
836         * doesn't tell us one).
837         */
838        oc.setGlobalQuality(aquality);
839
840        /**
841         * Now check if our output channels or sample rate differ from our input
842         * channels or sample rate.
843         * 
844         * If they do, we're going to need to resample the input audio to be in
845         * the right format to output.
846         */
847        if (oc.getChannels() != ic.getChannels()
848            || oc.getSampleRate() != ic.getSampleRate()
849            || oc.getSampleFormat() != ic.getSampleFormat())
850        {
851          /**
852           * Create an audio resampler to do that job.
853           */
854          mASamplers[i] = IAudioResampler.make(oc.getChannels(), ic
855              .getChannels(), oc.getSampleRate(), ic.getSampleRate(),
856              IAudioSamples.Format.FMT_S16, IAudioSamples.Format.FMT_S16);
857          if (mASamplers[i] == null)
858          {
859            throw new RuntimeException(
860                "could not open audio resampler for stream: " + i);
861          }
862        }
863        else
864        {
865          mASamplers[i] = null;
866        }
867        /**
868         * Finally, create some buffers for the input and output audio
869         * themselves.
870         * 
871         * We'll use these repeated during the #run(CommandLine) method.
872         */
873        mISamples[i] = IAudioSamples.make(1024, ic.getChannels(), ic.getSampleFormat());
874        mOSamples[i] = IAudioSamples.make(1024, oc.getChannels(), oc.getSampleFormat());
875      }
876      else if (cType == ICodec.Type.CODEC_TYPE_VIDEO && mHasVideo
877          && (vstream == -1 || vstream == i))
878      {
879        /**
880         * If you're reading these commends, this does the same thing as the
881         * above branch, only for video. I'm going to assume you read those
882         * comments and will only document something substantially different
883         * here.
884         */
885        ICodec codec = null;
886        if (vcodec != null)
887        {
888          codec = ICodec.findEncodingCodecByName(vcodec);
889          if (codec == null || codec.getType() != cType)
890            throw new RuntimeException("could not find encoder: " + vcodec);
891        }
892        else
893        {
894          codec = ICodec.guessEncodingCodec(oFmt, null, outputURL, null,
895              cType);
896          if (codec == null)
897            throw new RuntimeException("could not guess " + cType
898                + " encoder for: " + outputURL);
899
900        }
901        final IStream os = mOContainer.addNewStream(codec);
902        final IStreamCoder oc = os.getStreamCoder();
903
904        mOStreams[i] = os;
905        mOCoders[i] = oc;
906
907
908        // Set options AFTER selecting codec
909        final String vpreset = cmdLine.getOptionValue("vpreset");
910        if (vpreset != null)
911          Configuration.configure(vpreset, oc);
912        
913        /**
914         * In general a IStreamCoder encoding video needs to know: 1) A ICodec
915         * to use. 2) The Width and Height of the Video 3) The pixel format
916         * (e.g. IPixelFormat.Type#YUV420P) of the video data. Most everything
917         * else can be defaulted.
918         */
919        if (vbitrate == 0)
920          vbitrate = ic.getBitRate();
921        if (vbitrate == 0)
922          vbitrate = 250000;
923        oc.setBitRate(vbitrate);
924        if (vbitratetolerance > 0)
925          oc.setBitRateTolerance(vbitratetolerance);
926
927        int oWidth = ic.getWidth();
928        int oHeight = ic.getHeight();
929
930        if (oHeight <= 0 || oWidth <= 0)
931          throw new RuntimeException("could not find width or height in url: "
932              + inputURL);
933
934        /**
935         * For this program we don't allow the user to specify the pixel format
936         * type; we force the output to be the same as the input.
937         */
938        oc.setPixelType(ic.getPixelType());
939
940        if (vscaleFactor != 1.0)
941        {
942          /**
943           * In this case, it looks like the output video requires rescaling, so
944           * we create a IVideoResampler to do that dirty work.
945           */
946          oWidth = (int) (oWidth * vscaleFactor);
947          oHeight = (int) (oHeight * vscaleFactor);
948
949          mVSamplers[i] = IVideoResampler
950              .make(oWidth, oHeight, oc.getPixelType(), ic.getWidth(), ic
951                  .getHeight(), ic.getPixelType());
952          if (mVSamplers[i] == null)
953          {
954            throw new RuntimeException(
955                "This version of AVPKit does not support video resampling "
956                    + i);
957          }
958        }
959        else
960        {
961          mVSamplers[i] = null;
962        }
963        oc.setHeight(oHeight);
964        oc.setWidth(oWidth);
965
966        if (vquality >= 0)
967        {
968          oc.setFlag(IStreamCoder.Flags.FLAG_QSCALE, true);
969          oc.setGlobalQuality(vquality);
970        }
971
972        /**
973         * TimeBases are important, especially for Video. In general Audio
974         * encoders will assume that any new audio happens IMMEDIATELY after any
975         * prior audio finishes playing. But for video, we need to make sure
976         * it's being output at the right rate.
977         * 
978         * In this case we make sure we set the same time base as the input, and
979         * then we don't change the time stamps of any IVideoPictures.
980         * 
981         * But take my word that time stamps are tricky, and this only touches
982         * the envelope. The good news is, it's easier in AVPKit than some
983         * other systems.
984         */
985        IRational num = null;
986        num = ic.getFrameRate();
987        oc.setFrameRate(num);
988        oc.setTimeBase(IRational.make(num.getDenominator(), num
989                .getNumerator()));
990        num = null;
991
992        /**
993         * And allocate buffers for us to store decoded and resample video
994         * pictures.
995         */
996        mIVideoPictures[i] = IVideoPicture.make(ic.getPixelType(), ic
997            .getWidth(), ic.getHeight());
998        mOVideoPictures[i] = IVideoPicture.make(oc.getPixelType(), oc
999            .getWidth(), oc.getHeight());
1000      }
1001      else
1002      {
1003        log.warn("Ignoring input stream {} of type {}", i, cType);
1004      }
1005
1006      /**
1007       * Now, once you've set up all the parameters on the StreamCoder, you must
1008       * open() them so they can do work.
1009       * 
1010       * They will return an error if not configured correctly, so we check for
1011       * that here.
1012       */
1013      if (mOCoders[i] != null)
1014      {
1015        // some codecs require experimental mode to be set, and so we set it here.
1016        retval = mOCoders[i].setStandardsCompliance(IStreamCoder.CodecStandardsCompliance.COMPLIANCE_EXPERIMENTAL);
1017        if (retval < 0)
1018          throw new RuntimeException ("could not set compliance mode to experimental");
1019        
1020        retval = mOCoders[i].open(null, null);
1021        if (retval < 0)
1022          throw new RuntimeException(
1023              "could not open output encoder for stream: " + i);
1024        retval = mICoders[i].open(null, null);
1025        if (retval < 0)
1026          throw new RuntimeException(
1027              "could not open input decoder for stream: " + i);
1028      }
1029    }
1030
1031    /**
1032     * Pretty much every output container format has a header they need written,
1033     * so we do that here.
1034     * 
1035     * You must configure your output IStreams correctly before writing a
1036     * header, and few formats deal nicely with key parameters changing (e.g.
1037     * video width) after a header is written.
1038     */
1039    retval = mOContainer.writeHeader();
1040    if (retval < 0)
1041      throw new RuntimeException("Could not write header for: " + outputURL);
1042
1043    /**
1044     * That's it with setup; we're good to begin!
1045     */
1046    return numStreams;
1047  }
1048
1049  /**
1050   * Close and release all resources we used to run this program.
1051   */
1052  void closeStreams()
1053  {
1054    int numStreams = 0;
1055    int i = 0;
1056
1057    numStreams = mIContainer.getNumStreams();
1058    /**
1059     * Some video coders (e.g. MP3) will often "read-ahead" in a stream and keep
1060     * extra data around to get efficient compression. But they need some way to
1061     * know they're never going to get more data. The convention for that case
1062     * is to pass null for the IMediaData (e.g. IAudioSamples or IVideoPicture)
1063     * in encodeAudio(...) or encodeVideo(...) once before closing the coder.
1064     * 
1065     * In that case, the IStreamCoder will flush all data.
1066     */
1067    for (i = 0; i < numStreams; i++)
1068    {
1069      if (mOCoders[i] != null)
1070      {
1071        IPacket oPacket = IPacket.make();
1072        do {
1073          if (mOCoders[i].getCodecType() == ICodec.Type.CODEC_TYPE_AUDIO)
1074            mOCoders[i].encodeAudio(oPacket, null, 0);
1075          else
1076            mOCoders[i].encodeVideo(oPacket, null, 0);
1077          if (oPacket.isComplete())
1078            mOContainer.writePacket(oPacket, mForceInterleave);
1079        } while (oPacket.isComplete());
1080      }
1081    }
1082    /**
1083     * Some container formats require a trailer to be written to avoid a corrupt
1084     * files.
1085     * 
1086     * Others, such as the FLV container muxer, will take a writeTrailer() call
1087     * to tell it to seek() back to the start of the output file and write the
1088     * (now known) duration into the Meta Data.
1089     * 
1090     * So trailers are required. In general if a format is a streaming format,
1091     * then the writeTrailer() will never seek backwards.
1092     * 
1093     * Make sure you don't close your codecs before you write your trailer, or
1094     * we'll complain loudly and not actually write a trailer.
1095     */
1096    int retval = mOContainer.writeTrailer();
1097    if (retval < 0)
1098      throw new RuntimeException("Could not write trailer to output file");
1099
1100    /**
1101     * We do a nice clean-up here to show you how you should do it.
1102     * 
1103     * That said, AVPKit goes to great pains to clean up after you if you
1104     * forget to release things. But still, you should be a good boy or giral
1105     * and clean up yourself.
1106     */
1107    for (i = 0; i < numStreams; i++)
1108    {
1109      if (mOCoders[i] != null)
1110      {
1111        /**
1112         * And close the input coder to tell AVPKit it can release all native
1113         * memory.
1114         */
1115        mOCoders[i].close();
1116      }
1117      mOCoders[i] = null;
1118      if (mICoders[i] != null)
1119        /**
1120         * Close the input coder to tell AVPKit it can release all native
1121         * memory.
1122         */
1123        mICoders[i].close();
1124      mICoders[i] = null;
1125    }
1126
1127    /**
1128     * Tell AVPKit it can close the output file, write all data, and free all
1129     * relevant memory.
1130     */
1131    mOContainer.close();
1132    /**
1133     * And do the same with the input file.
1134     */
1135    mIContainer.close();
1136
1137    /**
1138     * Technically setting everything to null here doesn't do anything but tell
1139     * Java it can collect the memory it used.
1140     * 
1141     * The interesting thing to note here is that if you forget to close() a
1142     * AVPKit object, but also loose all references to it from Java, you won't
1143     * leak the native memory. Instead, we'll clean up after you, but we'll
1144     * complain LOUDLY in your logs, so you really don't want to do that.
1145     */
1146    mOContainer = null;
1147    mIContainer = null;
1148    mISamples = null;
1149    mOSamples = null;
1150    mIVideoPictures = null;
1151    mOVideoPictures = null;
1152    mOCoders = null;
1153    mICoders = null;
1154    mASamplers = null;
1155    mVSamplers = null;
1156  }
1157
1158  /**
1159   * Allow child class to override this method to alter the audio frame before
1160   * it is rencoded and written. In this implementation the audio frame is
1161   * passed through unmodified.
1162   * 
1163   * @param audioFrame
1164   *          the source audio frame to be modified
1165   * 
1166   * @return the modified audio frame
1167   */
1168
1169  protected IAudioSamples alterAudioFrame(IAudioSamples audioFrame)
1170  {
1171    return audioFrame;
1172  }
1173
1174  /**
1175   * Allow child class to override this method to alter the video frame before
1176   * it is rencoded and written. In this implementation the video frame is
1177   * passed through unmodified.
1178   * 
1179   * @param videoFrame
1180   *          the source video frame to be modified
1181   * 
1182   * @return the modified video frame
1183   */
1184
1185  protected IVideoPicture alterVideoFrame(IVideoPicture videoFrame)
1186  {
1187    return videoFrame;
1188  }
1189
1190  /**
1191   * Takes a given command line and decodes the input file, and encodes with new
1192   * parameters to the output file.
1193   * 
1194   * @param cmdLine
1195   *          A command line returned from
1196   *          {@link #parseOptions(Options, String[])}.
1197   */
1198  public void run(CommandLine cmdLine)
1199  {
1200    /**
1201     * Setup all our input and outputs
1202     */
1203    setupStreams(cmdLine);
1204
1205    /**
1206     * Create packet buffers for reading data from and writing data to the
1207     * conatiners.
1208     */
1209    IPacket iPacket = IPacket.make();
1210    IPacket oPacket = IPacket.make();
1211
1212    /**
1213     * Keep some "pointers' we'll use for the audio we're working with.
1214     */
1215    IAudioSamples inSamples = null;
1216    IAudioSamples outSamples = null;
1217    IAudioSamples reSamples = null;
1218
1219    int retval = 0;
1220
1221    /**
1222     * And keep some convenience pointers for the specific stream we're working
1223     * on for a packet.
1224     */
1225    IStreamCoder ic = null;
1226    IStreamCoder oc = null;
1227    IAudioResampler as = null;
1228    IVideoResampler vs = null;
1229    IVideoPicture inFrame = null;
1230    IVideoPicture reFrame = null;
1231
1232    /**
1233     * Now, we've already opened the files in #setupStreams(CommandLine). We
1234     * just keep reading packets from it until the IContainer returns <0
1235     */
1236    while (mIContainer.readNextPacket(iPacket) == 0)
1237    {
1238      /**
1239       * Find out which stream this packet belongs to.
1240       */
1241      int i = iPacket.getStreamIndex();
1242      int offset = 0;
1243
1244      /**
1245       * Find out if this stream has a starting timestamp
1246       */
1247      IStream stream = mIContainer.getStream(i);
1248      long tsOffset = 0;
1249      if (stream.getStartTime() != Global.NO_PTS && stream.getStartTime() > 0
1250          && stream.getTimeBase() != null)
1251      {
1252        IRational defTimeBase = IRational.make(1,
1253            (int) Global.DEFAULT_PTS_PER_SECOND);
1254        tsOffset = defTimeBase.rescale(stream.getStartTime(), stream
1255            .getTimeBase());
1256      }
1257      /**
1258       * And look up the appropriate objects that are working on that stream.
1259       */
1260      ic = mICoders[i];
1261      oc = mOCoders[i];
1262      as = mASamplers[i];
1263      vs = mVSamplers[i];
1264      inFrame = mIVideoPictures[i];
1265      reFrame = mOVideoPictures[i];
1266      inSamples = mISamples[i];
1267      reSamples = mOSamples[i];
1268
1269      if (oc == null)
1270        // we didn't set up this coder; ignore the packet
1271        continue;
1272
1273      /**
1274       * Find out if the stream is audio or video.
1275       */
1276      ICodec.Type cType = ic.getCodecType();
1277
1278      if (cType == ICodec.Type.CODEC_TYPE_AUDIO && mHasAudio)
1279      {
1280        /**
1281         * Decoding audio works by taking the data in the packet, and eating
1282         * chunks from it to create decoded raw data.
1283         * 
1284         * However, there may be more data in a packet than is needed to get one
1285         * set of samples (or less), so you need to iterate through the byts to
1286         * get that data.
1287         * 
1288         * The following loop is the standard way of doing that.
1289         */
1290        while (offset < iPacket.getSize())
1291        {
1292          retval = ic.decodeAudio(inSamples, iPacket, offset);
1293          if (retval <= 0)
1294            throw new RuntimeException("could not decode audio.  stream: " + i);
1295          
1296          if (inSamples.getTimeStamp() != Global.NO_PTS)
1297            inSamples.setTimeStamp(inSamples.getTimeStamp() - tsOffset);
1298
1299          log.trace("packet:{}; samples:{}; offset:{}", new Object[]
1300          {
1301              iPacket, inSamples, tsOffset
1302          });
1303
1304          /**
1305           * If not an error, the decodeAudio returns the number of bytes it
1306           * consumed. We use that so the next time around the loop we get new
1307           * data.
1308           */
1309          offset += retval;
1310          int numSamplesConsumed = 0;
1311          /**
1312           * If as is not null then we know a resample was needed, so we do that
1313           * resample now.
1314           */
1315          if (as != null && inSamples.getNumSamples() > 0)
1316          {
1317            retval = as.resample(reSamples, inSamples, inSamples
1318                .getNumSamples());
1319
1320            outSamples = reSamples;
1321          }
1322          else
1323          {
1324            outSamples = inSamples;
1325          }
1326
1327          /**
1328           * Include call a hook to derivied classes to allow them to alter the
1329           * audio frame.
1330           */
1331
1332          outSamples = alterAudioFrame(outSamples);
1333
1334          /**
1335           * Now that we've resampled, it's time to encode the audio.
1336           * 
1337           * This workflow is similar to decoding; you may have more, less or
1338           * just enough audio samples available to encode a packet. But you
1339           * must iterate through.
1340           * 
1341           * Unfortunately (don't ask why) there is a slight difference between
1342           * encodeAudio and decodeAudio; encodeAudio returns the number of
1343           * samples consumed, NOT the number of bytes. This can be confusing,
1344           * and we encourage you to read the IAudioSamples documentation to
1345           * find out what the difference is.
1346           * 
1347           * But in any case, the following loop encodes the samples we have
1348           * into packets.
1349           */
1350          while (numSamplesConsumed < outSamples.getNumSamples())
1351          {
1352            retval = oc.encodeAudio(oPacket, outSamples, numSamplesConsumed);
1353            if (retval <= 0)
1354              throw new RuntimeException("Could not encode any audio: "
1355                  + retval);
1356            /**
1357             * Increment the number of samples consumed, so that the next time
1358             * through this loop we encode new audio
1359             */
1360            numSamplesConsumed += retval;
1361            log.trace("out packet:{}; samples:{}; offset:{}", new Object[]{
1362                oPacket, outSamples, tsOffset
1363            });
1364
1365            writePacket(oPacket);
1366          }
1367        }
1368
1369      }
1370      else if (cType == ICodec.Type.CODEC_TYPE_VIDEO && mHasVideo)
1371      {
1372        /**
1373         * This encoding workflow is pretty much the same as the for the audio
1374         * above.
1375         * 
1376         * The only major delta is that encodeVideo() will always consume one
1377         * frame (whereas encodeAudio() might only consume some samples in an
1378         * IAudioSamples buffer); it might not be able to output a packet yet,
1379         * but you can assume that you it consumes the entire frame.
1380         */
1381        IVideoPicture outFrame = null;
1382        while (offset < iPacket.getSize())
1383        {
1384          retval = ic.decodeVideo(inFrame, iPacket, offset);
1385          if (retval <= 0)
1386            throw new RuntimeException("could not decode any video.  stream: "
1387                + i);
1388
1389          log.trace("decoded vid ts: {}; pkts ts: {}", inFrame.getTimeStamp(),
1390              iPacket.getTimeStamp());
1391          if (inFrame.getTimeStamp() != Global.NO_PTS)
1392            inFrame.setTimeStamp(inFrame.getTimeStamp() - tsOffset);
1393
1394          offset += retval;
1395          if (inFrame.isComplete())
1396          {
1397            if (vs != null)
1398            {
1399              retval = vs.resample(reFrame, inFrame);
1400              if (retval < 0)
1401                throw new RuntimeException("could not resample video");
1402              outFrame = reFrame;
1403            }
1404            else
1405            {
1406              outFrame = inFrame;
1407            }
1408
1409            /**
1410             * Include call a hook to derivied classes to allow them to alter
1411             * the audio frame.
1412             */
1413
1414            outFrame = alterVideoFrame(outFrame);
1415
1416            outFrame.setQuality(0);
1417            retval = oc.encodeVideo(oPacket, outFrame, 0);
1418            if (retval < 0)
1419              throw new RuntimeException("could not encode video");
1420            writePacket(oPacket);
1421          }
1422        }
1423      }
1424      else
1425      {
1426        /**
1427         * Just to be complete; there are other types of data that can show up
1428         * in streams (e.g. SUB TITLE).
1429         * 
1430         * Right now we don't support decoding and encoding that data, but youc
1431         * could still decide to write out the packets if you wanted.
1432         */
1433        log.trace("ignoring packet of type: {}", cType);
1434      }
1435
1436    }
1437
1438    // and cleanup.
1439    closeStreams();
1440  }
1441
1442  private void writePacket(IPacket oPacket)
1443  {
1444    int retval;
1445    if (oPacket.isComplete())
1446    {
1447      if (mRealTimeEncoder)
1448      {
1449        delayForRealTime(oPacket);
1450      }
1451      /**
1452       * If we got a complete packet out of the encoder, then go ahead
1453       * and write it to the container.
1454       */
1455      retval = mOContainer.writePacket(oPacket, mForceInterleave);
1456      if (retval < 0)
1457        throw new RuntimeException("could not write output packet");
1458    }
1459  }
1460
1461  /**
1462   * WARNING for those who want to copy this method and think it'll stream
1463   * for them -- it won't.  It doesn't interleave packets from non-interleaved
1464   * containers, so instead it'll write chunky data.  But it's useful if you
1465   * have previously interleaved data that you want to write out slowly to
1466   * a file, or, a socket.
1467   * @param oPacket the packet about to be written.
1468   */
1469  private void delayForRealTime(IPacket oPacket)
1470  {
1471    // convert packet timestamp to microseconds
1472    final IRational timeBase = oPacket.getTimeBase();
1473    if (timeBase == null || timeBase.getNumerator() == 0 ||
1474        timeBase.getDenominator() == 0)
1475      return;
1476    long dts = oPacket.getDts();
1477    if (dts == Global.NO_PTS)
1478      return;
1479    
1480    final long currStreamTime = IRational.rescale(dts,
1481        1,
1482        1000000,
1483        timeBase.getNumerator(),
1484        timeBase.getDenominator(),
1485        IRational.Rounding.ROUND_NEAR_INF);
1486    if (mStartStreamTime == null)
1487      mStartStreamTime = currStreamTime;
1488
1489    // convert now to microseconds
1490    final long currClockTime = System.nanoTime()/1000;
1491    if (mStartClockTime == null)
1492      mStartClockTime = currClockTime;
1493    
1494    final long currClockDelta  = currClockTime - mStartClockTime;
1495    if (currClockDelta < 0)
1496      return;
1497    final long currStreamDelta = currStreamTime - mStartStreamTime;
1498    if (currStreamDelta < 0)
1499      return;
1500    final long streamToClockDeltaMilliseconds = (currStreamDelta - currClockDelta)/1000;
1501    if (streamToClockDeltaMilliseconds <= 0)
1502      return;
1503    try
1504    {
1505      Thread.sleep(streamToClockDeltaMilliseconds);
1506    }
1507    catch (InterruptedException e)
1508    {
1509    }
1510  }
1511
1512  /**
1513   * 
1514   * A simple test of core, this program takes an input file, and outputs it
1515   * as an output file.
1516   * 
1517   * @param args
1518   *          The command line args passed to this program.
1519   */
1520  
1521  public static void main(String[] args)
1522  {
1523    Converter converter = new Converter();
1524
1525    try
1526    {
1527      // first define options
1528      Options options = converter.defineOptions();
1529      // And then parse them.
1530      CommandLine cmdLine = converter.parseOptions(options, args);
1531      // Finally, run the converter.
1532      converter.run(cmdLine);
1533    }
1534    catch (Exception exception)
1535    {
1536      System.err.printf("Error: %s\n", exception.getMessage());
1537    }
1538  }
1539
1540}