55import com .google .common .eventbus .EventBus ;
66import com .google .common .eventbus .Subscribe ;
77import com .thoughtworks .xstream .annotations .XStreamAlias ;
8- import edu .wpi .grip .core .OutputSocket ;
9- import edu .wpi .grip .core .SocketHint ;
10- import edu .wpi .grip .core .SocketHints ;
11- import edu .wpi .grip .core .Source ;
12- import edu .wpi .grip .core .events .UnexpectedThrowableEvent ;
8+ import edu .wpi .grip .core .*;
139import edu .wpi .grip .core .events .SourceRemovedEvent ;
10+ import edu .wpi .grip .core .events .StartedStoppedEvent ;
11+ import edu .wpi .grip .core .events .UnexpectedThrowableEvent ;
1412import org .bytedeco .javacpp .opencv_core .Mat ;
1513import org .bytedeco .javacv .*;
1614
2725 * Provides a way to generate a constantly updated {@link Mat} from a camera
2826 */
2927@ XStreamAlias (value = "grip:Camera" )
30- public class CameraSource extends Source {
28+ public final class CameraSource extends Source implements StartStoppable {
3129
3230 private final static String DEVICE_NUMBER_PROPERTY = "deviceNumber" ;
3331 private final static String ADDRESS_PROPERTY = "address" ;
@@ -42,7 +40,7 @@ public class CameraSource extends Source {
4240 private OutputSocket <Mat > frameOutputSocket ;
4341 private OutputSocket <Number > frameRateOutputSocket ;
4442 private Optional <Thread > frameThread ;
45- private Optional < FrameGrabber > grabber ;
43+ private FrameGrabber grabber ;
4644
4745 /**
4846 * Creates a camera source that can be used as an input to a pipeline
@@ -72,7 +70,6 @@ public CameraSource(EventBus eventBus, String address) throws IOException {
7270 * Used for serialization
7371 */
7472 public CameraSource () {
75- this .grabber = Optional .empty ();
7673 this .frameThread = Optional .empty ();
7774 }
7875
@@ -81,9 +78,7 @@ private void initialize(EventBus eventBus, FrameGrabber frameGrabber, String nam
8178 this .name = name ;
8279 this .frameOutputSocket = new OutputSocket <>(eventBus , imageOutputHint );
8380 this .frameRateOutputSocket = new OutputSocket <>(eventBus , frameRateOutputHint );
84-
85- this .eventBus .register (this );
86- this .startVideo (frameGrabber );
81+ this .grabber = frameGrabber ;
8782 }
8883
8984 @ Override
@@ -127,109 +122,119 @@ public void createFromProperties(EventBus eventBus, Properties properties) throw
127122 }
128123
129124 /**
130- * Starts the video capture from the
131- *
132- * @param grabber A JavaCV {@link FrameGrabber} instance to capture from
125+ * Starts the video capture from this frame grabber.
133126 */
134- private synchronized void startVideo ( FrameGrabber grabber ) throws IOException {
127+ public void start ( ) throws IOException , IllegalStateException {
135128 final OpenCVFrameConverter .ToMat convertToMat = new OpenCVFrameConverter .ToMat ();
136- if (this .frameThread .isPresent ()) {
137- throw new IllegalStateException ("The video retrieval thread has already been started." );
138- }
139- if (this .grabber .isPresent ()) {
140- throw new IllegalStateException ("The Frame Grabber has already been started." );
141- }
142- try {
143- grabber .start ();
144- } catch (FrameGrabber .Exception e ) {
145- throw new IOException ("A problem occurred trying to start the frame grabber for " + this .name , e );
146- }
129+ synchronized (this ) {
130+ if (this .frameThread .isPresent ()) {
131+ throw new IllegalStateException ("The video retrieval thread has already been started." );
132+ }
133+ try {
134+ grabber .start ();
135+ } catch (FrameGrabber .Exception e ) {
136+ throw new IOException ("A problem occurred trying to start the frame grabber for " + this .name , e );
137+ }
147138
148- // Store the grabber only once it has been started in the case that there is an exception.
149- this .grabber = Optional .of (grabber );
139+ final Thread frameExecutor = new Thread (() -> {
140+ long lastFrame = System .currentTimeMillis ();
141+ while (!Thread .interrupted ()) {
142+ final Frame videoFrame ;
143+ try {
144+ videoFrame = grabber .grab ();
145+ } catch (FrameGrabber .Exception e ) {
146+ throw new IllegalStateException ("Failed to grab image" , e );
147+ }
150148
151- final Thread frameExecutor = new Thread (() -> {
152- long lastFrame = System .currentTimeMillis ();
153- while (!Thread .interrupted ()) {
154- final Frame videoFrame ;
155- try {
156- videoFrame = grabber .grab ();
157- } catch (FrameGrabber .Exception e ) {
158- throw new IllegalStateException ("Failed to grab image" , e );
159- }
160- final Mat frameMat = convertToMat .convert (videoFrame );
149+ final Mat frameMat = convertToMat .convert (videoFrame );
161150
162- if (frameMat == null || frameMat .isNull ()) {
163- throw new IllegalStateException ("The camera returned a null frame Mat" );
151+ if (frameMat == null || frameMat .isNull ()) {
152+ throw new IllegalStateException ("The camera returned a null frame Mat" );
153+ }
154+
155+ frameMat .copyTo (frameOutputSocket .getValue ().get ());
156+ frameOutputSocket .setValue (frameOutputSocket .getValue ().get ());
157+ long thisMoment = System .currentTimeMillis ();
158+ frameRateOutputSocket .setValue (1000 / (thisMoment - lastFrame ));
159+ lastFrame = thisMoment ;
164160 }
161+ }, "Camera" );
165162
166- frameMat .copyTo (frameOutputSocket .getValue ().get ());
167- frameOutputSocket .setValue (frameOutputSocket .getValue ().get ());
168- long thisMoment = System .currentTimeMillis ();
169- frameRateOutputSocket .setValue (1000 / (thisMoment - lastFrame ));
170- lastFrame = thisMoment ;
171- }
172- }, "Camera" );
173- frameExecutor .setUncaughtExceptionHandler (
174- (thread , exception ) -> {
175- eventBus .post (new UnexpectedThrowableEvent (exception , "Webcam Frame Grabber Thread crashed with uncaught exception" ));
176- try {
177- stopVideo ();
178- } catch (TimeoutException e ) {
179- eventBus .post (new UnexpectedThrowableEvent (e , "Webcam Frame Grabber could not be stopped!" ));
163+ frameExecutor .setUncaughtExceptionHandler (
164+ (thread , exception ) -> {
165+ // TODO: This should use the ExceptionWitness once that has a UI component added for it
166+ eventBus .post (new UnexpectedThrowableEvent (exception , "Camera Frame Grabber Thread crashed with uncaught exception" ));
167+ try {
168+ stop ();
169+ } catch (TimeoutException e ) {
170+ // TODO: This should use the ExceptionWitness once that has a UI component added for it
171+ eventBus .post (new UnexpectedThrowableEvent (e , "Camera Frame Grabber could not be stopped!" ));
172+ }
180173 }
181- }
182- );
183- frameExecutor .setDaemon (true );
184- frameExecutor .start ();
185- frameThread = Optional .of (frameExecutor );
174+ );
175+ frameExecutor .setDaemon (true );
176+ frameExecutor .start ();
177+ this .frameThread = Optional .of (frameExecutor );
178+ // This should only be posted now that it is running
179+ eventBus .post (new StartedStoppedEvent (this ));
180+ }
186181 }
187182
188183 /**
189- * Stops the video feed from updating the output socket.
184+ * Stops this source.
185+ * This will stop the source publishing new socket values after this method returns.
190186 *
191- * @throws TimeoutException If the thread running the Webcam fails to join this one after a timeout.
187+ * @return The source that was stopped
188+ * @throws TimeoutException if the thread running the source fails to stop.
189+ * @throws IOException If there is a problem stopping the Source
192190 */
193- private void stopVideo () throws TimeoutException {
194- if (frameThread .isPresent ()) {
195- final Thread ex = frameThread .get ();
196- ex .interrupt ();
197- try {
198- ex .join (TimeUnit .SECONDS .toMillis (2 ));
199- if (ex .isAlive ()) {
200- throw new TimeoutException ("Unable to terminate video feed from Web Camera" );
201- }
202- } catch (InterruptedException e ) {
203- Thread .currentThread ().interrupt ();
204- //TODO: Move this into a logging framework
205- System .out .println ("Caught Exception:" );
206- e .printStackTrace ();
207- } finally {
208- frameThread = Optional .empty ();
209- // This will always run even if a timeout exception occurs
191+ public final void stop () throws TimeoutException , IllegalStateException {
192+ synchronized (this ) {
193+ if (frameThread .isPresent ()) {
194+ final Thread ex = frameThread .get ();
195+ ex .interrupt ();
210196 try {
211- grabber .ifPresent (grabber -> {
212- try {
213- grabber .stop ();
214- } catch (FrameGrabber .Exception e ) {
215- throw new IllegalStateException ("A problem occurred trying to stop the frame grabber" , e );
216- }
217- });
197+ ex .join (TimeUnit .SECONDS .toMillis (500 ));
198+ if (ex .isAlive ()) {
199+ throw new TimeoutException ("Unable to terminate video feed from Web Camera" );
200+ }
201+ // This should only be removed if the thread is successfully killed off
202+ frameThread = Optional .empty ();
203+ } catch (InterruptedException e ) {
204+ Thread .currentThread ().interrupt ();
205+ //TODO: Move this into a logging framework
206+ System .err .println ("Caught Exception:" );
207+ e .printStackTrace ();
218208 } finally {
219- // This will always run even if we fail to stop the grabber
220- grabber = Optional .empty ();
209+ // This will always run even if a timeout exception occurs
210+ try {
211+ // Calling this multiple times will have no effect
212+ grabber .stop ();
213+ } catch (FrameGrabber .Exception e ) {
214+ throw new IllegalStateException ("A problem occurred trying to stop the frame grabber" , e );
215+ }
221216 }
217+ } else {
218+ throw new IllegalStateException ("Tried to stop a Webcam that is already stopped." );
222219 }
223- } else {
224- throw new IllegalStateException ("Tried to stop a Webcam that is already stopped." );
225220 }
221+ eventBus .post (new StartedStoppedEvent (this ));
222+ frameRateOutputSocket .setValue (0 );
223+ }
224+
225+ @ Override
226+ public synchronized boolean isStarted () {
227+ return this .frameThread .isPresent () && this .frameThread .get ().isAlive ();
226228 }
227229
228230 @ Subscribe
229231 public void onSourceRemovedEvent (SourceRemovedEvent event ) throws TimeoutException {
230232 if (event .getSource () == this ) {
231- this .stopVideo ();
232- this .eventBus .unregister (this );
233+ try {
234+ if (this .isStarted ()) this .stop ();
235+ } finally {
236+ this .eventBus .unregister (this );
237+ }
233238 }
234239 }
235240
0 commit comments