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}