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.core.demos; 021 022import java.awt.image.BufferedImage; 023 024import com.avpkit.core.ICodec; 025import com.avpkit.core.IContainer; 026import com.avpkit.core.IContainerFormat; 027import com.avpkit.core.IError; 028import com.avpkit.core.IMetaData; 029import com.avpkit.core.IPacket; 030import com.avpkit.core.IPixelFormat; 031import com.avpkit.core.IStream; 032import com.avpkit.core.IStreamCoder; 033import com.avpkit.core.IVideoPicture; 034import com.avpkit.core.IVideoResampler; 035import com.avpkit.core.Utils; 036 037/** 038 * Takes a FFMPEG device driver name (ex: "video4linux2"), and a device name (ex: /dev/video0), and displays video 039 * from that device. For example, a web camera. 040 * <p> 041 * For example, to play the default camera on these operating systems: 042 * <ul> 043 * <li>Microsoft Windows:<pre>java -cp %XUGGLE_HOME%\share\java\jars\avpkit-core.jar com.avpkit.core.demos.DisplayWebcamVideo vfwcap 0</pre></li> 044 * <li>Linux:<pre>java -cp $XUGGLE_HOME/share/java/jars/avpkit-core.jar com.avpkit.core.demos.DisplayWebcamVideo video4linux2 /dev/video0</pre></li> 045 * </ul> 046 * </p> 047 * @author aclarke 048 * 049 */ 050public class DisplayWebcamVideo 051{ 052 /** 053 * Takes a FFMPEG webcam driver name, and a device name, opens the 054 * webcam, and displays its video in a Swing window. 055 * <p> 056 * Examples of device formats are: 057 * </p> 058 * <table border="1"> 059 * <thead> 060 * <tr> 061 * <td>OS</td> 062 * <td>Driver Name</td> 063 * <td>Sample Device Name</td> 064 * </tr> 065 * </thead> 066 * <tbody> 067 * <tr> 068 * <td>Windows</td> 069 * <td>vfwcap</td> 070 * <td>0</td> 071 * </tr> 072 * <tr> 073 * <td>Linux</td> 074 * <td>video4linux2</td> 075 * <td>/dev/video0</td> 076 * </tr> 077 * </tbody> 078 * </table> 079 * 080 * <p> 081 * Webcam support is very limited; you can't query what devices are 082 * available, nor can you query what their capabilities are without 083 * actually opening the device. Sorry, but that's how FFMPEG rolls. 084 * </p> 085 * 086 * @param args Must contain two strings: a FFMPEG driver name and a device name 087 * (which is dependent on the FFMPEG driver). 088 */ 089 @SuppressWarnings("deprecation") 090 public static void main(String[] args) 091 { 092 if (args.length != 2) 093 throw new IllegalArgumentException("must pass in driver and device name"); 094 095 String driverName = args[0]; 096 String deviceName= args[1]; 097 098 // Let's make sure that we can actually convert video pixel formats. 099 if (!IVideoResampler.isSupported(IVideoResampler.Feature.FEATURE_COLORSPACECONVERSION)) 100 throw new RuntimeException("you must install the GPL version of AVPKit (with IVideoResampler support) for this demo to work"); 101 102 // Create a AVPKit container object 103 IContainer container = IContainer.make(); 104 105 // Tell AVPKit about the device format 106 IContainerFormat format = IContainerFormat.make(); 107 if (format.setInputFormat(driverName) < 0) 108 throw new IllegalArgumentException("couldn't open webcam device: " + driverName); 109 110 // devices, unlike most files, need to have parameters set in order 111 // for AVPKit to know how to configure them, for a webcam, these 112 // parameters make sense 113 114 IMetaData params = IMetaData.make(); 115 116 params.setValue("framerate", "30/1"); 117 params.setValue("video_size", "320x240"); 118 119 // Open up the container 120 int retval = container.open(deviceName, IContainer.Type.READ, format, 121 false, true, params, null); 122 if (retval < 0) 123 { 124 // This little trick converts the non friendly integer return value into 125 // a slightly more friendly object to get a human-readable error name 126 IError error = IError.make(retval); 127 throw new IllegalArgumentException("could not open file: " + deviceName + "; Error: " + error.getDescription()); 128 } 129 130 // query how many streams the call to open found 131 int numStreams = container.getNumStreams(); 132 133 // and iterate through the streams to find the first video stream 134 int videoStreamId = -1; 135 IStreamCoder videoCoder = null; 136 for(int i = 0; i < numStreams; i++) 137 { 138 // Find the stream object 139 IStream stream = container.getStream(i); 140 // Get the pre-configured decoder that can decode this stream; 141 IStreamCoder coder = stream.getStreamCoder(); 142 143 if (coder.getCodecType() == ICodec.Type.CODEC_TYPE_VIDEO) 144 { 145 videoStreamId = i; 146 videoCoder = coder; 147 break; 148 } 149 } 150 if (videoStreamId == -1) 151 throw new RuntimeException("could not find video stream in container: "+deviceName); 152 153 /* 154 * Now we have found the video stream in this file. Let's open up our decoder so it can 155 * do work. 156 */ 157 if (videoCoder.open() < 0) 158 throw new RuntimeException("could not open video decoder for container: "+deviceName); 159 160 IVideoResampler resampler = null; 161 if (videoCoder.getPixelType() != IPixelFormat.Type.BGR24) 162 { 163 // if this stream is not in BGR24, we're going to need to 164 // convert it. The VideoResampler does that for us. 165 resampler = IVideoResampler.make(videoCoder.getWidth(), videoCoder.getHeight(), IPixelFormat.Type.BGR24, 166 videoCoder.getWidth(), videoCoder.getHeight(), videoCoder.getPixelType()); 167 if (resampler == null) 168 throw new RuntimeException("could not create color space resampler for: " + deviceName); 169 } 170 /* 171 * And once we have that, we draw a window on screen 172 */ 173 openJavaWindow(); 174 175 /* 176 * Now, we start walking through the container looking at each packet. 177 */ 178 IPacket packet = IPacket.make(); 179 while(container.readNextPacket(packet) >= 0) 180 { 181 /* 182 * Now we have a packet, let's see if it belongs to our video stream 183 */ 184 if (packet.getStreamIndex() == videoStreamId) 185 { 186 /* 187 * We allocate a new picture to get the data out of AVPKit 188 */ 189 IVideoPicture picture = IVideoPicture.make(videoCoder.getPixelType(), 190 videoCoder.getWidth(), videoCoder.getHeight()); 191 192 int offset = 0; 193 while(offset < packet.getSize()) 194 { 195 /* 196 * Now, we decode the video, checking for any errors. 197 * 198 */ 199 int bytesDecoded = videoCoder.decodeVideo(picture, packet, offset); 200 if (bytesDecoded < 0) 201 throw new RuntimeException("got error decoding video in: " + deviceName); 202 offset += bytesDecoded; 203 204 /* 205 * Some decoders will consume data in a packet, but will not be able to construct 206 * a full video picture yet. Therefore you should always check if you 207 * got a complete picture from the decoder 208 */ 209 if (picture.isComplete()) 210 { 211 IVideoPicture newPic = picture; 212 /* 213 * If the resampler is not null, that means we didn't get the video in BGR24 format and 214 * need to convert it into BGR24 format. 215 */ 216 if (resampler != null) 217 { 218 // we must resample 219 newPic = IVideoPicture.make(resampler.getOutputPixelFormat(), picture.getWidth(), picture.getHeight()); 220 if (resampler.resample(newPic, picture) < 0) 221 throw new RuntimeException("could not resample video from: " + deviceName); 222 } 223 if (newPic.getPixelType() != IPixelFormat.Type.BGR24) 224 throw new RuntimeException("could not decode video as BGR 24 bit data in: " + deviceName); 225 226 // Convert the BGR24 to an Java buffered image 227 BufferedImage javaImage = Utils.videoPictureToImage(newPic); 228 229 // and display it on the Java Swing window 230 updateJavaWindow(javaImage); 231 } 232 } 233 } 234 else 235 { 236 /* 237 * This packet isn't part of our video stream, so we just silently drop it. 238 */ 239 do {} while(false); 240 } 241 242 } 243 /* 244 * Technically since we're exiting anyway, these will be cleaned up by 245 * the garbage collector... but because we're nice people and want 246 * to be invited places for Christmas, we're going to show how to clean up. 247 */ 248 if (videoCoder != null) 249 { 250 videoCoder.close(); 251 videoCoder = null; 252 } 253 if (container !=null) 254 { 255 container.close(); 256 container = null; 257 } 258 closeJavaWindow(); 259 260 } 261 262 /** 263 * The window we'll draw the video on. 264 * 265 */ 266 private static VideoImage mScreen = null; 267 268 private static void updateJavaWindow(BufferedImage javaImage) 269 { 270 mScreen.setImage(javaImage); 271 } 272 273 /** 274 * Opens a Swing window on screen. 275 */ 276 private static void openJavaWindow() 277 { 278 mScreen = new VideoImage(); 279 } 280 281 /** 282 * Forces the swing thread to terminate; I'm sure there is a right 283 * way to do this in swing, but this works too. 284 */ 285 private static void closeJavaWindow() 286 { 287 System.exit(0); 288 } 289 290}