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.io.File;
026import java.nio.ShortBuffer;
027
028import java.awt.Color;
029import java.awt.Graphics2D;
030import java.awt.geom.Rectangle2D;
031import java.awt.image.BufferedImage;
032
033import com.avpkit.mediatool.IMediaTool;
034import com.avpkit.mediatool.ToolFactory;
035import com.avpkit.mediatool.IMediaReader;
036import com.avpkit.mediatool.IMediaWriter;
037import com.avpkit.mediatool.MediaToolAdapter;
038import com.avpkit.mediatool.event.IAudioSamplesEvent;
039import com.avpkit.mediatool.event.IVideoPictureEvent;
040
041/**
042 * Read and modify audio and video frames and use the {@link
043 * IMediaWriter} to encode that media and write it out to a file.
044 *
045 * @author trebor
046 * @author aclarke
047 */
048public class ModifyAudioAndVideo
049{
050  // the log
051
052  private static final Logger log = LoggerFactory.getLogger(
053    ModifyAudioAndVideo.class);
054  { log.trace("<init>"); }
055
056  /**
057   * Create and display a number of bouncing balls on the 
058   */
059
060  public static void main(String[] args)
061  {
062    if (args.length < 2)
063    {
064      System.out.println(
065        "Usage: ModifyAudioAndVideo <inputFileName> <outputFileName>");
066      System.exit(-1);
067    }
068
069    File inputFile = new File(args[0]);
070    if (!inputFile.exists())
071    {
072      System.out.println("Input file does not exist: " + inputFile);
073      System.exit(-1);
074    }
075
076    File outputFile = new File(args[1]);
077
078    // create a media reader and configure it to generate BufferImages
079
080    IMediaReader reader = ToolFactory.makeReader(inputFile.toString());
081    reader.setBufferedImageTypeToGenerate(BufferedImage.TYPE_3BYTE_BGR);
082
083    // create a writer and configure it's parameters from the reader
084    
085    IMediaWriter writer = ToolFactory.makeWriter(outputFile.toString(), reader);
086
087    // create a tool which paints video time stamp into frame
088
089    IMediaTool addTimeStamp = new TimeStampTool();
090
091    // create a tool which reduces audio volume to 1/10th original
092
093    IMediaTool reduceVolume = new VolumeAdjustTool(0.1);
094
095    // create a tool chain:
096    //   reader -> addTimeStamp -> reduceVolume -> writer
097
098    reader.addListener(addTimeStamp);
099    addTimeStamp.addListener(reduceVolume);
100    reduceVolume.addListener(writer);
101
102    // add a viewer to the writer, to see media modified media
103    
104    writer.addListener(ToolFactory.makeViewer());
105
106    // read and decode packets from the source file and
107    // then encode and write out data to the output file
108    
109    while (reader.readPacket() == null)
110      do {} while(false);
111  }  
112
113  /** 
114   * Create a tool which adds a time stamp to a video image.
115   */
116
117  static class TimeStampTool extends MediaToolAdapter
118  {
119    /** {@inheritDoc} */
120
121    @Override
122      public void onVideoPicture(IVideoPictureEvent event)
123    {
124      // get the graphics for the image
125
126      Graphics2D g = event.getImage().createGraphics();
127
128      // establish the timestamp and how much space it will take
129
130      String timeStampStr = event.getPicture().getFormattedTimeStamp();
131      Rectangle2D bounds = g.getFont().getStringBounds(timeStampStr,
132        g.getFontRenderContext());
133      
134      // compute the amount to inset the time stamp and translate the
135      // image to that position
136
137      double inset = bounds.getHeight() / 2;
138      g.translate(inset, event.getImage().getHeight() - inset);
139      
140      // draw a white background and black timestamp text
141
142      g.setColor(Color.WHITE);
143      g.fill(bounds);
144      g.setColor(Color.BLACK);
145      g.drawString(timeStampStr, 0, 0);
146      
147      // call parent which will pass the video onto next tool in chain
148
149      super.onVideoPicture(event);
150    }
151  }
152
153  /** 
154   * Create a tool which adjusts the volume of audio by some constant factor.
155   */
156
157  static class VolumeAdjustTool extends MediaToolAdapter
158  {
159    // the amount to adjust the volume by
160
161    private double mVolume;
162    
163    /** 
164     * Construct a volume adjustor.
165     * 
166     * @param volume the volume muliplier, values between 0 and 1 are
167     *        recommended.
168     */
169
170    public VolumeAdjustTool(double volume)
171    {
172      mVolume = volume;
173    }
174
175    /** {@inheritDoc} */
176
177    @Override
178      public void onAudioSamples(IAudioSamplesEvent event)
179    {
180      // get the raw audio byes and adjust it's value 
181      
182      ShortBuffer buffer = event.getAudioSamples().getByteBuffer().asShortBuffer();
183      for (int i = 0; i < buffer.limit(); ++i)
184        buffer.put(i, (short)(buffer.get(i) * mVolume));
185      
186      // call parent which will pass the audio onto next tool in chain
187
188      super.onAudioSamples(event);
189    }
190  }
191}