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.mediatool; 021 022import java.awt.image.BufferedImage; 023import java.util.concurrent.TimeUnit; 024 025import com.avpkit.core.IAudioSamples; 026import com.avpkit.core.ICodec; 027import com.avpkit.core.IContainer; 028import com.avpkit.core.IPixelFormat; 029import com.avpkit.core.IRational; 030import com.avpkit.core.IStream; 031import com.avpkit.core.IStreamCoder; 032import com.avpkit.core.IVideoPicture; 033 034/** 035 * An {@link IMediaCoder} that encodes and decodes media to an 036 * {@link IContainer}, and can optionally read data for encoding from other 037 * {@link IMediaGenerator} objects. 038 * 039 * <p> 040 * Here's some pseudo code that shows how to encode an audio file: 041 * </p> 042 * <pre> 043 * IMediaWriter writer = ToolFactory.makeWriter("output.mp3"); 044 * int sampleRate = 22050; 045 * int channels = 1; 046 * writer.addAudioStream(0, 0, sampleRate, channels); 047 * 048 * while(haveMoreAudio()) 049 * { 050 * short[] samples = getSourceSamples(); 051 * writer.encodeAudio(0, samples); 052 * } 053 * writer.close(); 054 * </pre> 055 * <p>And here's some pseudo code that shows how to encode a 056 * video image every 1 second: 057 * </p> 058 * <pre> 059 * IMediaWriter writer = ToolFactory.makeWriter("output.mp4"); 060 * writer.addVideoStream(0, 0, sampleRate, channels); 061 * 062 * long startTime = System.nanoTime(); 063 * while(haveMoreVideo()) 064 * { 065 * BufferedImage image = getImage(); 066 * writer.encodeAudio(0, image, 067 * System.nanoTime()-startTime, TimeUnit.NANOSECONDS); 068 * Thread.sleep(1000); 069 * } 070 * writer.close(); 071 * </pre> 072 * <p> 073 * 074 * An {@link IMediaWriter} is a simplified interface to the AVPKit library that 075 * opens up a media container, and allows media data to be written into it. 076 * 077 * </p> 078 * 079 * <p> 080 * The {@link IMediaWriter} class implements {@link IMediaListener}, and so it 081 * can be attached to any {@link IMediaGenerator} that generates raw media 082 * events (e.g. {@link IMediaReader}). If will query the input pipe for all of 083 * it's streams, and create the right output streams (and mappings) in the new 084 * file. 085 * </p> 086 * 087 * <p> 088 * 089 * Calls to {@link #encodeAudio(int, IAudioSamples)} and 090 * {@link #encodeVideo(int, IVideoPicture)} encode media into packets and write 091 * those packets to the specified container. 092 * 093 * </p> 094 * <p> 095 * 096 * If you are generating video from Java {@link BufferedImage} but you don't 097 * have an {@link IVideoPicture} object handy, don't sweat. You can use 098 * {@link #encodeVideo(int, BufferedImage, long, TimeUnit)} for that. 099 * 100 * </p> 101 * <p> 102 * 103 * If you are generating audio from in Java short arrays (16-bit audio) but 104 * don't have an {@link IAudioSamples} object handy, don't sweat. You can use 105 * {@link #encodeAudio(int, short[], long, TimeUnit)} for that. 106 * 107 * </p> 108 */ 109 110public interface IMediaWriter extends IMediaCoder, IMediaTool 111{ 112 113 /** 114 * Set late stream exception policy. When 115 * {@link #encodeAudio(int, IAudioSamples)} or 116 * {@link #encodeVideo(int, IVideoPicture)} is passed an unrecognized stream 117 * index after the the header has been written, either an exception is raised, 118 * or the media data is silently ignored. By default exceptions are raised, 119 * not masked. 120 * 121 * @param maskLateStreamExceptions true if late med 122 * 123 * @see #willMaskLateStreamExceptions 124 */ 125 126 public abstract void setMaskLateStreamExceptions( 127 boolean maskLateStreamExceptions); 128 129 /** 130 * Get the late stream exception policy. When 131 * {@link #encodeVideo(int, IVideoPicture)} or 132 * {@link #encodeAudio(int, IAudioSamples)}is passed an unrecognized stream 133 * index after the the header has been written, either an exception is raised, 134 * or the media data is silently ignored. By default exceptions are raised, 135 * not masked. 136 * 137 * @return true if late stream data raises exceptions 138 * 139 * @see #setMaskLateStreamExceptions 140 */ 141 142 public abstract boolean willMaskLateStreamExceptions(); 143 144 /** 145 * Set the force interleave option. 146 * 147 * <p> 148 * 149 * If false the media data will be left in the order in which it is presented 150 * to the IMediaWriter. 151 * 152 * </p> 153 * <p> 154 * 155 * If true {@link IMediaWriter} will buffer media data in time stamp order, 156 * and only write out data when it has at least one same time or later packet 157 * from all streams. 158 * 159 * <p> 160 * 161 * @param forceInterleave true if the IMediaWriter should force interleaving 162 * of media data 163 * 164 * @see #willForceInterleave 165 */ 166 167 public abstract void setForceInterleave(boolean forceInterleave); 168 169 /** 170 * Test if the {@link IMediaWriter} will forcibly interleave media data. The 171 * default value for this value is true. 172 * 173 * @return true if {@link IMediaWriter} interleaves media data. 174 * 175 * @see #setForceInterleave 176 */ 177 178 public abstract boolean willForceInterleave(); 179 180 /** 181 * Test if this {@link IMediaWriter} can write streams of this type. 182 * 183 * @param type the type of codec to be tested 184 * 185 * @return true if codec type is supported type 186 */ 187 188 public abstract boolean isSupportedCodecType(ICodec.Type type); 189 190 /** 191 * Get the default pixel type 192 * 193 * @return the default pixel type 194 */ 195 public abstract IPixelFormat.Type getDefaultPixelType(); 196 197 /** 198 * Get the default audio sample format 199 * 200 * @return the format 201 */ 202 public abstract IAudioSamples.Format getDefaultSampleFormat(); 203 204 /** 205 * Get the default time base we'll use on our encoders if one is not specified 206 * by the codec. 207 * 208 * @return the default time base 209 */ 210 public abstract IRational getDefaultTimebase(); 211 212 /** 213 * Add an audio stream with a codec guessed based on {@link #getUrl()} 214 * of this {@link IMediaWriter}. 215 * <p> 216 * The time base defaults to {@link #getDefaultTimebase()} and the audio 217 * format defaults to {@link #getDefaultSampleFormat()}. 218 * </p> 219 * 220 * @param inputIndex the index that will be passed to 221 * {@link #encodeAudio(int, IAudioSamples)} for this stream 222 * @param streamId a format-dependent id for this stream 223 * @param channelCount the number of audio channels for the stream 224 * @param sampleRate sample rate in Hz (samples per seconds), common values 225 * are 44100, 22050, 11025, etc. 226 * 227 * @return <0 on failure; otherwise returns the index of the new stream added 228 * by the writer. 229 * 230 * @throws IllegalArgumentException if inputIndex < 0, the stream id < 0, the 231 * codec is NULL or if the container is already open. 232 * @throws IllegalArgumentException if width or height are <= 0 233 * @throws UnsupportedOperationException if the given codec cannot be 234 * used for encoding. 235 * 236 * @see IContainer 237 * @see IStream 238 * @see IStreamCoder 239 * @see ICodec 240 * @see ICodec#guessEncodingCodec(com.avpkit.core.IContainerFormat, String, String, String, com.avpkit.core.ICodec.Type) 241 */ 242 public abstract int addAudioStream(int inputIndex, int streamId, 243 int channelCount, int sampleRate); 244 245 /** 246 * Add an audio stream that will later have data encoded with 247 * {@link #encodeAudio(int, IAudioSamples)}. 248 * <p> 249 * The time base defaults to {@link #getDefaultTimebase()} and the audio 250 * format defaults to {@link #getDefaultSampleFormat()}. 251 * </p> 252 * 253 * @param inputIndex the index that will be passed to 254 * {@link #encodeAudio(int, IAudioSamples)} for this stream 255 * @param streamId a format-dependent id for this stream 256 * @param codecId the codec id to used to encode data, to establish the codec see 257 * {@link ICodec} 258 * @param channelCount the number of audio channels for the stream 259 * @param sampleRate sample rate in Hz (samples per seconds), common values 260 * are 44100, 22050, 11025, etc. 261 * 262 * @return <0 on failure; otherwise returns the index of the new stream added 263 * by the writer. 264 * 265 * @throws IllegalArgumentException if inputIndex < 0, the stream id < 0, the 266 * codec is NULL or if the container is already open. 267 * @throws IllegalArgumentException if width or height are <= 0 268 * @throws UnsupportedOperationException if the given codec cannot be 269 * used for encoding. 270 * 271 * @see IContainer 272 * @see IStream 273 * @see IStreamCoder 274 * @see ICodec 275 */ 276 public abstract int addAudioStream(int inputIndex, int streamId, 277 ICodec.ID codecId, int channelCount, int sampleRate); 278 279 /** 280 * Add an audio stream that will later have data encoded with 281 * {@link #encodeAudio(int, IAudioSamples)}. 282 * <p> 283 * The time base defaults to {@link #getDefaultTimebase()} and the audio 284 * format defaults to {@link #getDefaultSampleFormat()}. 285 * </p> 286 * 287 * @param inputIndex the index that will be passed to 288 * {@link #encodeAudio(int, IAudioSamples)} for this stream 289 * @param streamId a format-dependent id for this stream 290 * @param codec the codec to used to encode data, to establish the codec see 291 * {@link ICodec} 292 * @param channelCount the number of audio channels for the stream 293 * @param sampleRate sample rate in Hz (samples per seconds), common values 294 * are 44100, 22050, 11025, etc. 295 * 296 * @return <0 on failure; otherwise returns the index of the new stream added 297 * by the writer. 298 * 299 * @throws IllegalArgumentException if inputIndex < 0, the stream id < 0, the 300 * codec is NULL or if the container is already open. 301 * @throws IllegalArgumentException if width or height are <= 0 302 * 303 * @see IContainer 304 * @see IStream 305 * @see IStreamCoder 306 * @see ICodec 307 */ 308 309 public abstract int addAudioStream(int inputIndex, int streamId, 310 ICodec codec, int channelCount, int sampleRate); 311 312 /** 313 * Add a video stream with a codec guessed based on {@link #getUrl()} 314 * of this {@link IMediaWriter}. 315 * 316 * <p> 317 * 318 * The time base defaults to {@link #getDefaultTimebase()} and the pixel 319 * format defaults to {@link #getDefaultPixelType()}. 320 * 321 * </p> 322 * 323 * @param inputIndex the index that will be passed to 324 * {@link #encodeVideo(int, IVideoPicture)} for this stream 325 * @param streamId a format-dependent id for this stream 326 * @param width width of video frames 327 * @param height height of video frames 328 * 329 * @throws IllegalArgumentException if inputIndex < 0, the stream id < 0, the 330 * codec is NULL or if the container is already open. 331 * @throws IllegalArgumentException if width or height are <= 0 332 * 333 * @return <0 on failure; otherwise returns the index of the new stream added 334 * by the writer. 335 * @see IContainer 336 * @see IStream 337 * @see IStreamCoder 338 * @see ICodec 339 * @see ICodec#guessEncodingCodec(com.avpkit.core.IContainerFormat, String, String, String, com.avpkit.core.ICodec.Type) 340 * @throws UnsupportedOperationException if the given codec cannot be 341 * used for encoding. 342 */ 343 344 public abstract int addVideoStream(int inputIndex, int streamId, int width, 345 int height); 346 347 /** 348 * Add a video stream that will later have data encoded with 349 * {@link #encodeVideo(int, IVideoPicture)}. 350 * 351 * <p> 352 * 353 * The time base defaults to {@link #getDefaultTimebase()} and the pixel 354 * format defaults to {@link #getDefaultPixelType()}. 355 * 356 * </p> 357 * 358 * @param inputIndex the index that will be passed to 359 * {@link #encodeVideo(int, IVideoPicture)} for this stream 360 * @param streamId a format-dependent id for this stream 361 * @param codecId the codec to used to encode data, to establish the codec see 362 * {@link ICodec} 363 * @param width width of video frames 364 * @param height height of video frames 365 * 366 * @throws IllegalArgumentException if inputIndex < 0, the stream id < 0, the 367 * codec is NULL or if the container is already open. 368 * @throws IllegalArgumentException if width or height are <= 0 369 * 370 * @return <0 on failure; otherwise returns the index of the new stream added 371 * by the writer. 372 * @see IContainer 373 * @see IStream 374 * @see IStreamCoder 375 * @see ICodec 376 * @throws UnsupportedOperationException if the given codec cannot be 377 * used for encoding. 378 */ 379 380 public abstract int addVideoStream(int inputIndex, int streamId, 381 ICodec.ID codecId, int width, int height); 382 383 /** 384 * Add a video stream that will later have data encoded with 385 * {@link #encodeVideo(int, IVideoPicture)}. 386 * 387 * <p> 388 * 389 * The time base defaults to {@link #getDefaultTimebase()} and the pixel 390 * format defaults to {@link #getDefaultPixelType()}. 391 * 392 * </p> 393 * 394 * @param inputIndex the index that will be passed to 395 * {@link #encodeVideo(int, IVideoPicture)} for this stream 396 * @param streamId a format-dependent id for this stream 397 * @param codec the codec to used to encode data, to establish the codec see 398 * {@link ICodec} 399 * @param width width of video frames 400 * @param height height of video frames 401 * 402 * @throws IllegalArgumentException if inputIndex < 0, the stream id < 0, the 403 * codec is NULL or if the container is already open. 404 * @throws IllegalArgumentException if width or height are <= 0 405 * 406 * @return <0 on failure; otherwise returns the index of the new stream added 407 * by the writer. 408 * @see IContainer 409 * @see IStream 410 * @see IStreamCoder 411 * @see ICodec 412 */ 413 414 public abstract int addVideoStream(int inputIndex, int streamId, 415 ICodec codec, int width, int height); 416 417 /** 418 * Add a video stream with a codec guessed based on {@link #getUrl()} of this 419 * {@link IMediaWriter}. 420 * 421 * <p> 422 * 423 * The time base defaults to {@link #getDefaultTimebase()} and the pixel 424 * format defaults to {@link #getDefaultPixelType()}. 425 * 426 * </p> 427 * 428 * @param inputIndex the index that will be passed to 429 * {@link #encodeVideo(int, IVideoPicture)} for this stream 430 * @param streamId a format-dependent id for this stream 431 * @param frameRate The frame rate if known or required by the output codec. 432 * See 433 * {@link #addVideoStream(int, int, com.avpkit.core.ICodec.ID, IRational, int, int)} 434 * . 435 * @param width width of video frames 436 * @param height height of video frames 437 * 438 * @throws IllegalArgumentException if inputIndex < 0, the stream id < 0, the 439 * codec is NULL or if the container is already open. 440 * @throws IllegalArgumentException if width or height are <= 0 441 * 442 * @return <0 on failure; otherwise returns the index of the new stream added 443 * by the writer. 444 * 445 * @see #addVideoStream(int, int, com.avpkit.core.ICodec.ID, IRational, 446 * int, int) 447 * @see IContainer 448 * @see IStream 449 * @see IStreamCoder 450 * @see ICodec 451 * @see ICodec#guessEncodingCodec(com.avpkit.core.IContainerFormat, String, 452 * String, String, com.avpkit.core.ICodec.Type) 453 * @throws UnsupportedOperationException if the given codec cannot be used for 454 * encoding. 455 */ 456 457 public abstract int addVideoStream(int inputIndex, int streamId, 458 IRational frameRate, int width, int height); 459 460 /** 461 * Add a video stream that will later have data encoded with 462 * {@link #encodeVideo(int, IVideoPicture)}. 463 * 464 * <p> 465 * 466 * The time base defaults to {@link #getDefaultTimebase()} and the pixel 467 * format defaults to {@link #getDefaultPixelType()}. 468 * 469 * </p> 470 * 471 * @param inputIndex the index that will be passed to 472 * {@link #encodeVideo(int, IVideoPicture)} for this stream 473 * @param streamId a format-dependent id for this stream 474 * @param codecId the codec to used to encode data, to establish the codec see 475 * {@link ICodec} 476 * @param frameRate The frame rate if known or required by the output codec. 477 * See 478 * {@link #addVideoStream(int, int, com.avpkit.core.ICodec.ID, IRational, int, int)} 479 * . 480 * @param width width of video frames 481 * @param height height of video frames 482 * 483 * @throws IllegalArgumentException if inputIndex < 0, the stream id < 0, the 484 * codec is NULL or if the container is already open. 485 * @throws IllegalArgumentException if width or height are <= 0 486 * 487 * @return <0 on failure; otherwise returns the index of the new stream added 488 * by the writer. 489 * @see #addVideoStream(int, int, com.avpkit.core.ICodec.ID, IRational, 490 * int, int) 491 * @see IContainer 492 * @see IStream 493 * @see IStreamCoder 494 * @see ICodec 495 * @throws UnsupportedOperationException if the given codec cannot be used for 496 * encoding. 497 */ 498 499 public abstract int addVideoStream(int inputIndex, int streamId, 500 ICodec.ID codecId, IRational frameRate, int width, int height); 501 502 /** 503 * Add a video stream that will later have data encoded with 504 * {@link #encodeVideo(int, IVideoPicture)}. 505 * 506 * <p> 507 * 508 * The time base defaults to {@link #getDefaultTimebase()} and the pixel 509 * format defaults to {@link #getDefaultPixelType()}. 510 * 511 * </p> 512 * 513 * @param inputIndex the index that will be passed to 514 * {@link #encodeVideo(int, IVideoPicture)} for this stream 515 * @param streamId a format-dependent id for this stream 516 * @param codec the codec to used to encode data, to establish the codec see 517 * {@link ICodec} 518 * @param frameRate if non null, then this is the frame-rate 519 * you will encode video at. Some codecs (e.g. MPEG2 video) 520 * require this to be fixed and will effectively ignore timestamps 521 * in favor of this. If you do not specify, we'll guess the 522 * highest frame-rate that can fit in the output container, and 523 * then try to honor the actual timestamps encoded. 524 * @param width width of video frames 525 * @param height height of video frames 526 * 527 * @throws IllegalArgumentException if inputIndex < 0, the stream id < 0, the 528 * codec is NULL or if the container is already open. 529 * @throws IllegalArgumentException if width or height are <= 0 530 * 531 * @return <0 on failure; otherwise returns the index of the new stream added 532 * by the writer. 533 * @see IContainer 534 * @see IStream 535 * @see IStreamCoder 536 * @see ICodec 537 */ 538 539 public abstract int addVideoStream(int inputIndex, int streamId, 540 ICodec codec, IRational frameRate, int width, int height); 541 542 /** 543 * Encodes audio from samples into the stream with the specified index. 544 * 545 * <p> 546 * 547 * Callers must ensure that {@link IAudioSamples#getTimeStamp()}, if specified 548 * is always monotonically increasing or an error will be returned. 549 * 550 * </p> 551 * 552 * @param streamIndex The stream index, as returned from 553 * {@link #addAudioStream(int, int, ICodec, int, int)}. 554 * @param samples A set of samples to add. 555 */ 556 public abstract void encodeAudio(int streamIndex, IAudioSamples samples); 557 558 /** 559 * Encoded audio from samples into the stream with the specified index. 560 * 561 * <p> 562 * 563 * {@link IMediaWriter} will assume that these samples are to played 564 * immediately after the last set of samples, or with the earliest time stamp 565 * in the container if they are the first samples. 566 * 567 * </p> 568 * 569 * @param streamIndex The stream index, as returned from 570 * {@link #addAudioStream(int, int, ICodec, int, int)}. 571 * @param samples A set of samples to add. 572 */ 573 public abstract void encodeAudio(int streamIndex, short[] samples); 574 575 /** 576 * Encoded audio from samples into the stream with the specified index. 577 * 578 * <p> 579 * 580 * If <code>timeUnit</code> is null, {@link IMediaWriter} will assume that 581 * these samples are to played immediately after the last set of samples, or 582 * with the earliest time stamp in the container if they are the first 583 * samples. 584 * 585 * </p> 586 * <p> 587 * 588 * Callers must ensure that <code>timeStamp</code>, if <code>timeUnit</code> 589 * is non-null, is always monotonically increasing or an runtime exception 590 * will be raised. 591 * 592 * </p> 593 * 594 * @param streamIndex The stream index, as returned from 595 * {@link #addAudioStream(int, int, ICodec, int, int)}. 596 * @param samples A set of samples to add. 597 * @param timeStamp The time stamp for this media. 598 * @param timeUnit The units of timeStamp, or null if you want 599 * {@link IMediaWriter} to assume these samples immediately precede any 600 * prior samples. 601 */ 602 public abstract void encodeAudio(int streamIndex, short[] samples, 603 long timeStamp, TimeUnit timeUnit); 604 605 /** 606 * Encodes video from the given picture into the stream with the specified 607 * index. 608 * 609 * <p> 610 * 611 * Callers must ensure that {@link IVideoPicture#getTimeStamp()}, if specified 612 * is always monotonically increasing or an {@link RuntimeException} will be 613 * raised. 614 * 615 * </p> 616 * 617 * @param streamIndex The stream index, as returned from 618 * {@link #addVideoStream(int, int, ICodec, int, int)}. 619 * @param picture A picture to encode 620 */ 621 public abstract void encodeVideo(int streamIndex, IVideoPicture picture); 622 623 /** 624 * Encodes video from the given picture into the stream with the specified 625 * index. 626 * 627 * <p> 628 * 629 * Callers must ensure that {@link IVideoPicture#getTimeStamp()}, if specified 630 * is always monotonically increasing or an {@link RuntimeException} will be 631 * raised. 632 * 633 * </p> 634 * 635 * @param streamIndex The stream index, as returned from 636 * {@link #addVideoStream(int, int, ICodec, int, int)}. 637 * @param image A {@link BufferedImage} to encode 638 * @param timeStamp The time stamp for this image 639 * @param timeUnit The time unit of timeStamp. Cannot be null. 640 */ 641 public abstract void encodeVideo(int streamIndex, BufferedImage image, 642 long timeStamp, TimeUnit timeUnit); 643 644 /** 645 * Flushes all encoders and writes their contents. 646 * 647 * <p> 648 * 649 * Callers should call this when they have finished encoding all audio and 650 * video to ensure that any cached data necessary for encoding was written. 651 * 652 * </p> 653 */ 654 public abstract void flush(); 655 656 /** 657 * Map an input stream index to an output stream index. 658 * 659 * @param inputStreamIndex the input stream index value 660 * 661 * @return the associated output stream index or null, if the input stream 662 * index has not been mapped to an output index. 663 */ 664 665 public abstract Integer getOutputStreamIndex(int inputStreamIndex); 666 667}