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}