/** * Copyright 2014 Comcast Cable Communications Management, LLC * * This file is part of CATS. * * CATS is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * CATS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with CATS. If not, see <http://www.gnu.org/licenses/>. */ package com.comcast.cats.video.service; import java.awt.Dimension; import java.awt.image.BufferedImage; import java.io.IOException; import java.net.MalformedURLException; import java.net.URI; import java.net.URL; import java.util.Collection; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.imageio.ImageIO; import org.apache.log4j.Logger; import com.comcast.cats.event.CatsEventDispatcher; import com.comcast.cats.event.VideoEvent; /** * Video Service implementation. Contains video service controls. * * @author JTyrre001 * */ public class VideoController implements Runnable { public static final String VIDEO_CGI_PATH = "/axis-cgi/mjpg/video.cgi"; public static final String IMAGE_CGI_PATH = "/axis-cgi/jpg/image.cgi"; /** * Source object used when sending video events. */ public final Object source; /** * The regex used to extract the camera #. */ public static final Pattern CAMERA_PATTERN = Pattern.compile( ".*camera=([1-9]+).*" ); /** * Default streaming rate in frames per second. */ private static final int DEFAULT_STREAM_FPS = 10; private int imageSequence = 0; /** * Holds the current frames per second streaming frame rate. */ private int streamfps = DEFAULT_STREAM_FPS; private AxisVideoSize VIDEO_DEFAULT_RESOLUTION = AxisVideoSize.AXIS_4CIF; private AxisVideoSize currentVideoResolution = VIDEO_DEFAULT_RESOLUTION; /** * Instantiate AxisServerConnection. */ private final AxisServerConnection axis; /** * is Streaming flag. */ private boolean isStreaming = false; /** * Locator to video device. */ private final URI videoLocator; /** * CatsEventDispatcher object. */ private CatsEventDispatcher dispatcher; /** * Camera number defaults to 1; */ private Integer camera = 1; /** * default logger. */ private static final Logger logger = Logger.getLogger( VideoController.class ); protected void parseVideoLocator( URI videoLocator ) { String query = videoLocator.getQuery(); if ( query == null ) { return; } final Matcher m = CAMERA_PATTERN.matcher( query ); if ( m.find() ) { camera = Integer.parseInt( m.group( 1 ) ); } } /** * Constructor. * * @param parent * parent * @param url * axis server url * @throws MalformedURLException */ public VideoController( final CatsEventDispatcher dispatcher, final URI videoLocator ) throws MalformedURLException { if ( videoLocator == null ) { logger.error( "VideoLocator is Null throwing IllegalArgumentException" ); throw new IllegalArgumentException( "VideoLocator is null which is not allowed" ); } if ( dispatcher == null ) { logger.error( "No CatsEventDispatcher so throwing IllegalArgumentException" ); throw new IllegalArgumentException( "CatsEventDispatcher is null which is not allowed" ); } this.videoLocator = videoLocator; this.source = this; parseVideoLocator( videoLocator ); axis = new AxisServerConnection( generateVideoURL() ); this.dispatcher = dispatcher; } /** * Constructor. * * @param parent * parent * @param url * axis server url * @param source * source object for all events generated by this class. * @throws MalformedURLException */ public VideoController( final CatsEventDispatcher dispatcher, final URI videoLocator, Object source ) throws MalformedURLException { if ( videoLocator == null ) { logger.error( "VideoLocator is Null throwing IllegalArgumentException" ); throw new IllegalArgumentException( "VideoLocator is null which is not allowed" ); } if ( dispatcher == null ) { logger.error( "No CatsEventDispatcher so throwing IllegalArgumentException" ); throw new IllegalArgumentException( "CatsEventDispatcher is null which is not allowed" ); } this.videoLocator = videoLocator; this.source = source; parseVideoLocator( videoLocator ); axis = new AxisServerConnection( generateVideoURL() ); this.dispatcher = dispatcher; } private String generateVideoURL() { return generateURL( VIDEO_CGI_PATH ); } private String generateImageURL() { return generateURL( IMAGE_CGI_PATH ); } private String generateImageURL( AxisVideoSize videoSize ) { String tempUrl = generateBaseURL( IMAGE_CGI_PATH ); tempUrl += getVideoResolutionString( videoSize ); return tempUrl; } private String generateBaseURL( String path ) { String tempUrl = "http://" + videoLocator.getHost(); if ( videoLocator.getPort() > 0 ) { tempUrl += ":" + videoLocator.getPort(); } tempUrl += path; tempUrl += "?camera=" + getCamera(); return tempUrl; } private String generateURL( String path ) { String tempUrl = generateBaseURL( path ); tempUrl += getCurrentVideoResolutionString(); tempUrl += "&fps=" + getFrameRate(); logger.debug( "Generated URL " + tempUrl ); return tempUrl; } public Integer getCamera() { return camera; } /** * Returns Axis server URL. * * @return URL of Axis Server device */ public String getURL() { return generateVideoURL(); } public URI getVideoLocator() { return videoLocator; } /** * Returns streaming video status of true or false. * * @return axis.isConnected() connected status. Presumes connected, * therefore streaming. */ public boolean isStreaming() { return isStreaming; } /** * Returns connection to Axis Server status of true or false. * * @return axis.isConnected() connected status. Presumes connected, * therefore streaming. */ public boolean isConnected() { return axis.isConnected(); } /** * Connects to the Axis server device. * * @throws MalformedURLException */ public void connect() throws MalformedURLException { disconnectFromAxisDevice(); while ( isStreaming ) { // wait till last thread returns try { Thread.sleep( 10 ); } catch ( InterruptedException e ) { e.printStackTrace(); } } axis.setUrl( getURL() ); axis.connect(); Thread thread = new Thread( this ); thread.start(); } /** * Disconnects from the Axis Server device. */ public void disconnectFromAxisDevice() { if ( axis.isConnected() ) { axis.disconnect(); } } private BufferedImage readImage( AxisVideoSize axisSize ) { String imageLocation = generateImageURL( axisSize ); try { logger.info( "Retrieve Image[" + imageLocation + "]" ); URL imageURL = new URL( imageLocation ); return ImageIO.read( imageURL ); } catch ( MalformedURLException e1 ) { logger.error( "Error creating URL for image location = " + imageLocation ); } catch ( IOException e ) { logger.error( "Error retrieving Image from = " + imageLocation ); } return null; } /** * Returns a buffered image. * * @return BufferedImage */ public BufferedImage getImage() { if ( isStreaming ) { /** * Streaming is enabled, so try and pull image from axis server. */ logger.info( "Streaming video, pull image out of stream." ); BufferedImage image; try { image = axis.getImage(); } catch ( IOException e ) { logger.debug( "Excpetion while redaing image " + e.getMessage() ); image = null; } return image; } logger.debug( "Reading image with current video resolution" ); return readImage( currentVideoResolution ); } public BufferedImage getImage( Dimension dim ) { if ( null == dim ) { throw new IllegalArgumentException( "Dimension must not be null" ); } AxisVideoSize axisSize = AxisVideoSize.getAxisFormatFromDimension( dim ); if ( null == axisSize ) { logger.warn( "Dimension provided is invalid Axis Video Size " ); return null; } return readImage( axisSize ); } /** * Sets the frames per second read rate. * * @param fps * frames per second read rate */ public void setFrameRate( int fps ) { streamfps = ( fps <= 0 || fps > 30 ) ? DEFAULT_STREAM_FPS : fps; if ( isConnected() ) { try { connect();// reconnect. } catch ( MalformedURLException e ) { e.printStackTrace(); } } } /** * Gets the frames per second read rate. * * @return streamfps frames per second read rate */ public int getFrameRate() { return streamfps; } /** * Gets and returns an ArrayList (table) of available video resolutions * sizes. First column is resolution id: D1, 4CIF, CIF etc. Second column is * resolution: 720 x 480 etc. * * @return resList available video size dimension list */ public Collection< String > getAvailableVideoSizes() { return AxisServerConnection.getAvailableVideoResolutions(); } /** * Get the Axis URL string resolution based on currently selected * resolution. * * @return Axis camera string resolution parameters. */ private String getCurrentVideoResolutionString() { logger.debug( "Getting Video Dimension " + currentVideoResolution.getAxisResolution() ); return getVideoResolutionString( currentVideoResolution ); } private String getVideoResolutionString( AxisVideoSize axisSize ) { String res = "&resolution=" + axisSize.getAxisResolution(); // if(currentVideoResolution.isSquareResoultion()) { // res += "&squarepixel=1"; // } if ( axisSize.isSquareResoultion() ) { res += "&squarepixel=1"; } return res; } /** * Sets the height/width dimension of the user specified screen size. * * @param dm * Dimension of selected screen resolution */ public void setVideoDimension( final Dimension dm ) { currentVideoResolution = AxisVideoSize.getAxisFormatFromDimension( dm ); if ( currentVideoResolution == null ) { currentVideoResolution = VIDEO_DEFAULT_RESOLUTION; } logger.debug( "Set Video Dimension to " + currentVideoResolution + " Requested : " + dm ); } /** * Gets and return the screen dimension set by the user client. * * @return dim client/user selected screen dimension. */ public Dimension getVideoDimension() { return currentVideoResolution.getDimension(); } /** * Thread to read frames out from the stream. (non-Javadoc) * * @see java.lang.Runnable#run() */ @Override public void run() { readStream(); } /** * Reads and extracts JPEGS frames from the stream at a specified rate * (frames per second). */ private void readStream() { try { while ( axis.isConnected() ) { isStreaming = true; BufferedImage image = axis.getImage(); VideoEvent videoEvent = new VideoEvent( imageSequence++, null, source, image ); dispatcher.sendCatsEvent( videoEvent ); } if ( logger.isTraceEnabled() ) { logger.trace( "Axis is no longer connected so VideoController is no longer streaming." ); } isStreaming = false; } catch ( Exception e ) { e.printStackTrace(); isStreaming = false; logger.trace( "Exception occured so VideoController is no longer streaming." + e.getMessage() ); logger.debug( "Try Reconnecting " ); try { connect(); } catch ( MalformedURLException e1 ) { logger.debug( "Incorrect URL " + e1.getMessage() ); } } } }