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("Error: %s\n", exception.getMessage()); 123 * } 124 * } 125 * </pre> 126 * 127 * <p> 128 * 129 * Pass "--help" 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}