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 javax.imageio.ImageIO;
023
024import java.io.File;
025
026import java.awt.image.BufferedImage;
027
028import com.avpkit.mediatool.IMediaReader;
029import com.avpkit.mediatool.MediaListenerAdapter;
030import com.avpkit.mediatool.ToolFactory;
031import com.avpkit.mediatool.event.IVideoPictureEvent;
032import com.avpkit.core.Global;
033
034/**
035 * Using {@link IMediaReader}, takes a media container, finds the first video stream, decodes that
036 * stream, and then writes video frames out to a PNG image file every 5
037 * seconds, based on the video presentation timestamps.
038 *
039 * @author aclarke
040 * @author trebor
041 */
042
043public class DecodeAndCaptureFrames extends MediaListenerAdapter
044{
045  /** 
046   * The number of seconds between frames.
047   */
048
049  public static final double SECONDS_BETWEEN_FRAMES = 5;
050
051  /** 
052   * The number of micro-seconds between frames. 
053   */
054
055  public static final long MICRO_SECONDS_BETWEEN_FRAMES = 
056    (long)(Global.DEFAULT_PTS_PER_SECOND * SECONDS_BETWEEN_FRAMES);
057  
058  /** Time of last frame write. */
059  
060  private static long mLastPtsWrite = Global.NO_PTS;
061
062  /**
063   * The video stream index, used to ensure we display frames from one
064   * and only one video stream from the media container.
065   */
066
067  private int mVideoStreamIndex = -1;
068
069  /**
070   * Takes a media container (file) as the first argument, opens it and
071   *  writes some of it's video frames to PNG image files in the
072   *  temporary directory.
073   *  
074   * @param args must contain one string which represents a filename
075   */
076
077  public static void main(String[] args)
078  {
079    if (args.length <= 0)
080      throw new IllegalArgumentException(
081        "must pass in a filename as the first argument");
082    
083    // create a new mr. decode and capture frames
084    
085    new DecodeAndCaptureFrames(args[0]);
086  }
087
088  /** Construct a DecodeAndCaptureFrames which reads and captures
089   * frames from a video file.
090   * 
091   * @param filename the name of the media file to read
092   */
093
094  public DecodeAndCaptureFrames(String filename)
095  {
096    // create a media reader for processing video
097
098    IMediaReader reader = ToolFactory.makeReader(filename);
099    
100    // stipulate that we want BufferedImages created in BGR 24bit color space
101    reader.setBufferedImageTypeToGenerate(BufferedImage.TYPE_3BYTE_BGR);
102
103    
104    // note that DecodeAndCaptureFrames is derived from
105    // MediaReader.ListenerAdapter and thus may be added as a listener
106    // to the MediaReader. DecodeAndCaptureFrames implements
107    // onVideoPicture().
108
109    reader.addListener(this);
110
111    // read out the contents of the media file, note that nothing else
112    // happens here.  action happens in the onVideoPicture() method
113    // which is called when complete video pictures are extracted from
114    // the media source
115
116    while (reader.readPacket() == null)
117      do {} while(false);
118  }
119
120  /** 
121   * Called after a video frame has been decoded from a media stream.
122   * Optionally a BufferedImage version of the frame may be passed
123   * if the calling {@link IMediaReader} instance was configured to
124   * create BufferedImages.
125   * 
126   * This method blocks, so return quickly.
127   */
128
129  public void onVideoPicture(IVideoPictureEvent event)
130  {
131    try
132    {
133      // if the stream index does not match the selected stream index,
134      // then have a closer look
135      
136      if (event.getStreamIndex() != mVideoStreamIndex)
137      {
138        // if the selected video stream id is not yet set, go ahead an
139        // select this lucky video stream
140        
141        if (-1 == mVideoStreamIndex)
142          mVideoStreamIndex = event.getStreamIndex();
143        
144        // otherwise return, no need to show frames from this video stream
145        
146        else
147          return;
148      }
149      
150      // if uninitialized, backdate mLastPtsWrite so we get the very
151      // first frame
152
153      if (mLastPtsWrite == Global.NO_PTS)
154        mLastPtsWrite = event.getTimeStamp() - MICRO_SECONDS_BETWEEN_FRAMES;
155
156      // if it's time to write the next frame
157
158      if (event.getTimeStamp() - mLastPtsWrite >= MICRO_SECONDS_BETWEEN_FRAMES)
159      {
160        // Make a temporary file name
161
162        File file = File.createTempFile("frame", ".png");
163
164        // write out PNG
165
166        ImageIO.write(event.getImage(), "png", file);
167
168        // indicate file written
169
170        double seconds = ((double)event.getTimeStamp())
171          / Global.DEFAULT_PTS_PER_SECOND;
172        System.out.printf("at elapsed time of %6.3f seconds wrote: %s\n",
173          seconds, file);
174        
175        // update last write time
176        
177        mLastPtsWrite += MICRO_SECONDS_BETWEEN_FRAMES;
178      }
179    }
180    catch (Exception e)
181    {
182      e.printStackTrace();
183    }
184  }
185}