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.demos;
021
022import org.slf4j.Logger;
023import org.slf4j.LoggerFactory;
024
025import java.awt.image.BufferedImage;
026
027import com.avpkit.mediatool.IMediaViewer;
028import com.avpkit.mediatool.IMediaWriter;
029import com.avpkit.mediatool.ToolFactory;
030import com.avpkit.core.IAudioSamples;
031
032import static java.util.concurrent.TimeUnit.SECONDS;
033import static java.util.concurrent.TimeUnit.MILLISECONDS;
034import static com.avpkit.core.Global.DEFAULT_TIME_UNIT;
035
036/**
037 * Generate audio and video frames and use the {@link IMediaWriter} to
038 * encode that media and write it out to a file.
039 *
040 * @author trebor
041 * @author aclarke
042 */
043
044public class GenerateAudioAndVideo
045{
046  // the log
047
048  private static final Logger log = LoggerFactory.getLogger(
049    GenerateAudioAndVideo.class);
050  { log.trace("<init>"); }
051
052  /**
053   * Create and display a number of bouncing balls on the 
054   */
055
056  public static void main(String[] args)
057  {
058    // the number of balls to bounce around
059
060    final int ballCount = 2;
061
062    // total duration of the media
063
064    final long duration = DEFAULT_TIME_UNIT.convert(60, SECONDS);
065
066    // video parameters
067
068    final int videoStreamIndex = 0;
069    final int videoStreamId = 0;
070    final long frameRate = DEFAULT_TIME_UNIT.convert(15, MILLISECONDS);
071    final int width = 320;
072    final int height = 200;
073    
074    // audio parameters
075
076    final int audioStreamIndex = 1;
077    final int audioStreamId = 0;
078    final int channelCount = 1;
079    final int sampleRate = 44100; // Hz
080    final int sampleCount = 1000;
081
082    // the clock time of the next frame
083
084    long nextFrameTime = 0;
085
086    // the total number of audio samples
087
088    long totalSampleCount = 0;
089
090    // create a media writer and specify the output file
091
092    final IMediaWriter writer = ToolFactory.makeWriter("myballs.mov");
093
094    // add a viewer so we can see the media as it is created
095
096    writer.addListener(ToolFactory.makeViewer(
097        IMediaViewer.Mode.AUDIO_VIDEO, true, 
098        javax.swing.WindowConstants.EXIT_ON_CLOSE));
099
100    // add the video stream
101
102    writer.addVideoStream(videoStreamIndex, videoStreamId,
103        width, height);
104
105    // add the audio stream
106
107    writer.addAudioStream(audioStreamIndex, audioStreamId,
108        channelCount, sampleRate);
109
110    // create some balls to show on the screen
111
112    Balls balls = new MovingBalls(ballCount, width, height, sampleCount);
113
114    // loop through clock time, which starts at zero and increases based
115    // on the total number of samples created thus far
116
117    for (long clock = 0; clock < duration; clock = IAudioSamples
118           .samplesToDefaultPts(totalSampleCount, sampleRate))
119    {
120      // while the clock time exceeds the time of the next video frame,
121      // get and encode the next video frame
122
123      while (clock >= nextFrameTime)
124      {
125        BufferedImage frame = balls.getVideoFrame(frameRate);
126        writer.encodeVideo(videoStreamIndex, frame, nextFrameTime, 
127          DEFAULT_TIME_UNIT);
128        nextFrameTime += frameRate;
129      }
130
131      // compute and encode the audio for the balls
132
133      short[] samples = balls.getAudioFrame(sampleRate);
134      writer.encodeAudio(audioStreamIndex, samples, clock, 
135        DEFAULT_TIME_UNIT);
136      totalSampleCount += sampleCount;
137    }
138
139    // manually close the writer
140    
141    writer.close();
142  }  
143}