/**
* Copyright (C) 2013 Colorado School of Mines
*
* This file is part of the Interface Software Development Kit (SDK).
*
* The InterfaceSDK 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.
*
* The InterfaceSDK 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 the InterfaceSDK. If not, see <http://www.gnu.org/licenses/>.
*/
package edu.mines.acmX.exhibit.input_services.hardware.drivers.MicrosoftSDK;
import com.sun.jna.platform.win32.COM.COMException;
import com.sun.jna.platform.win32.Kernel32;
import com.sun.jna.platform.win32.WinBase;
import com.sun.jna.platform.win32.WinDef.*;
import com.sun.jna.platform.win32.WinNT.*;
import com.sun.jna.ptr.IntByReference;
import edu.mines.acmX.exhibit.input_services.events.EventManager;
import edu.mines.acmX.exhibit.input_services.events.EventType;
import edu.mines.acmX.exhibit.input_services.events.InputReceiver;
import edu.mines.acmX.exhibit.input_services.hardware.devicedata.DepthImageInterface;
import edu.mines.acmX.exhibit.input_services.hardware.devicedata.GestureTrackerInterface;
import edu.mines.acmX.exhibit.input_services.hardware.devicedata.HandTrackerInterface;
import edu.mines.acmX.exhibit.input_services.hardware.devicedata.RGBImageInterface;
import edu.mines.acmX.exhibit.input_services.hardware.drivers.DriverException;
import edu.mines.acmX.exhibit.input_services.hardware.drivers.DriverInterface;
import edu.mines.acmX.exhibit.input_services.hardware.drivers.InvalidConfigurationFileException;
import java.nio.ByteBuffer;
import java.nio.ShortBuffer;
import java.util.ArrayList;
import java.util.List;
import static com.sun.jna.platform.win32.W32Errors.FAILED;
import static com.sun.jna.platform.win32.W32Errors.HRESULT_CODE;
/**
* Kinect driver that provides depth and rgb image functionality. Uses
* Microsoft's SDK for communication to the kinect device.
*
* @author Matt Wesemann
*
*/
public class KinectSDKDriver implements DriverInterface,
DepthImageInterface, RGBImageInterface, HandTrackerInterface, GestureTrackerInterface {
private boolean loaded;
private KinectDevice device;
private HANDLE colorStream;
private HANDLE depthStream;
private HANDLE nextColorImageFrame;
private HANDLE nextDepthImageFrame;
private HANDLE nextSkeletonFrame;
private HANDLE nextInteractionFrame;
private INuiInteractionStream interactionStream;
private INuiInteractionClient interactionClient;
private GestureTracker gestureTracker;
public KinectSDKDriver(){
loaded = false;
device = null;
colorStream = null;
depthStream = null;
nextColorImageFrame = null;
nextDepthImageFrame = null;
nextSkeletonFrame = null;
nextInteractionFrame = null;
interactionStream = null;
interactionClient = null;
gestureTracker = new GestureTracker();
}
@Override
public boolean isAvailable() {
if(!loaded) {
try{
load();
} catch (Throwable t){
// logger do something
return false;
}
}
return device != null;
}
@Override
public void destroy() {
if(loaded){
loaded = false;
if(interactionStream != null){
interactionStream.Disable();
interactionStream.Release();
interactionStream = null;
}
if(interactionClient != null){
interactionClient.Release();
interactionClient = null;
}
if(colorStream != null){
Kernel32.INSTANCE.CloseHandle(colorStream);
colorStream = null;
}
if(depthStream != null){
Kernel32.INSTANCE.CloseHandle(depthStream);
depthStream = null;
}
if(nextColorImageFrame != null){
Kernel32.INSTANCE.CloseHandle(nextColorImageFrame);
nextColorImageFrame = null;
}
if(nextDepthImageFrame != null){
Kernel32.INSTANCE.CloseHandle(nextDepthImageFrame);
nextDepthImageFrame = null;
}
if(nextSkeletonFrame != null){
Kernel32.INSTANCE.CloseHandle(nextSkeletonFrame);
nextSkeletonFrame = null;
}
if(nextInteractionFrame != null){
Kernel32.INSTANCE.CloseHandle(nextInteractionFrame);
nextInteractionFrame = null;
}
if(device != null){
checkRC(device.NuiSkeletonTrackingDisable());
device.NuiShutdown();
device.Release();
device = null;
}
// Remove all receivers connected to this driver
EventManager.getInstance().removeReceivers(EventType.HAND_CREATED);
EventManager.getInstance().removeReceivers(EventType.HAND_UPDATED);
EventManager.getInstance().removeReceivers(EventType.HAND_DESTROYED);
}
}
@Override
public void load() throws InvalidConfigurationFileException, DriverException {
if(loaded)
destroy(); // reset driver
loaded = true;
IntByReference pcount = new IntByReference(0);
checkRC(KinectLibrary.INSTANCE.NuiGetSensorCount(pcount));
if(pcount.getValue() == 0){
throw new DriverException("No connected devices");
}
KinectDevice.ByReference newDevice = new KinectDevice.ByReference();
checkRC(KinectLibrary.INSTANCE.NuiCreateSensorByIndex(0, newDevice));
device = newDevice.getDevice();
checkRC(device.NuiInitialize(new DWORD(KinectLibrary.NUI_INITIALIZE_FLAG_USES_COLOR |
KinectLibrary.NUI_INITIALIZE_FLAG_USES_SKELETON |
KinectLibrary.NUI_INITIALIZE_FLAG_USES_DEPTH)));
nextColorImageFrame = Kernel32.INSTANCE.CreateEvent(null, true, false, null);
nextDepthImageFrame = Kernel32.INSTANCE.CreateEvent(null, true, false, null);
nextSkeletonFrame = Kernel32.INSTANCE.CreateEvent(null, true, false, null);
nextInteractionFrame = Kernel32.INSTANCE.CreateEvent(null, true, false, null);
HANDLEByReference handle= new HANDLEByReference();
checkRC(device.NuiImageStreamOpen(new JnaEnumWrapper<>(NUI_IMAGE_TYPE.NUI_IMAGE_TYPE_COLOR),
new JnaEnumWrapper<>(NUI_IMAGE_RESOLUTION.NUI_IMAGE_RESOLUTION_640x480),
new DWORD(0),
new DWORD(2),
nextColorImageFrame,
handle));
colorStream = handle.getValue();
checkRC(device.NuiImageStreamOpen(new JnaEnumWrapper<>(NUI_IMAGE_TYPE.NUI_IMAGE_TYPE_DEPTH),
new JnaEnumWrapper<>(NUI_IMAGE_RESOLUTION.NUI_IMAGE_RESOLUTION_640x480),
new DWORD(0),
new DWORD(2),
nextDepthImageFrame,
handle));
depthStream = handle.getValue();
checkRC(device.NuiSkeletonTrackingEnable(nextSkeletonFrame, new DWORD(0)));
interactionClient = new INuiInteractionClient();
INuiInteractionStream.ByReference newStream = new INuiInteractionStream.ByReference();
checkRC(KinectToolkitLibrary.INSTANCE.NuiCreateInteractionStream(device, interactionClient, newStream));
interactionStream = newStream.getStream();
checkRC(interactionStream.Enable(nextInteractionFrame));
new Thread(new BackgroundThread()).start();
}
@Override
public boolean loaded() {
return loaded;
}
private void processSkeleton(){
NUI_SKELETON_FRAME skeletonFrame = new NUI_SKELETON_FRAME();
checkRC(device.NuiSkeletonGetNextFrame(new DWORD(Kernel32.INFINITE), skeletonFrame));
checkRC(device.NuiTransformSmooth(skeletonFrame, new NUI_TRANSFORM_SMOOTH_PARAMETERS()));
Vector4 vect = new Vector4();
checkRC(device.NuiAccelerometerGetCurrentReading(vect));
checkRC(interactionStream.ProcessSkeleton(new UINT(6),
skeletonFrame.SkeletonData(),
vect, skeletonFrame.liTimeStamp().getValue()));
}
private void processDepth(){
NUI_IMAGE_FRAME imageFrame = new NUI_IMAGE_FRAME();
checkRC(device.NuiImageStreamGetNextFrame(depthStream, new DWORD(0), imageFrame));
BOOLByReference isNearMode = new BOOLByReference();
INuiFrameTexture.ByReference newTexture = new INuiFrameTexture.ByReference();
checkRC(device.NuiImageFrameGetDepthImagePixelFrameTexture(depthStream, imageFrame, isNearMode, newTexture));
INuiFrameTexture frameTexture = newTexture.getTexture();
NUI_LOCKED_RECT lockedRect = new NUI_LOCKED_RECT();
checkRC(frameTexture.LockRect(new UINT(0), lockedRect, null, new DWORD(0)));
if(lockedRect.Pitch == 0)
throw new RuntimeException("Kinect didn't give us data");
byte[] bytes = lockedRect.getBytes();
checkRC(frameTexture.UnlockRect(0));
checkRC(interactionStream.ProcessDepth(new UINT(bytes.length), bytes, imageFrame.liTimeStamp.getValue()));
frameTexture.Release();
checkRC(device.NuiImageStreamReleaseFrame(depthStream, imageFrame));
}
private void processInteraction(){
NUI_INTERACTION_FRAME interactionFrame = new NUI_INTERACTION_FRAME();
HRESULT hr = interactionStream.GetNextFrame(new DWORD(0), interactionFrame);
if(FAILED(hr)){
// this happens when we did not process the data in the 1/30 of second required by the sdk
// because we are in java we can sometimes run slow enough to hit this boundary
// we can safely ignore this and as our code gets faster it will happen less
if(hr.intValue() == KinectLibrary.E_NUI_FRAME_NO_DATA){
return;
}
checkRC(hr);
}
// get a list of all skeletons and hands with information
List<Integer> ids = new ArrayList<>();
List<NUI_HAND_TYPE> types = new ArrayList<>();
for(NUI_USER_INFO info : interactionFrame.UserInfos){
if(info.HandPointerInfos[0].State.intValue() != 0) {
ids.add(info.SkeletonTrackingId.intValue());
types.add(info.HandPointerInfos[0].HandType.value);
gestureTracker.update(device, interactionFrame.TimeStamp.getValue(),
info.HandPointerInfos[0].RawX,
info.HandPointerInfos[0].RawY,
info.HandPointerInfos[0].RawZ,
info.SkeletonTrackingId.intValue(),
info.HandPointerInfos[0].HandType.value);
}
if(info.HandPointerInfos[1].State.intValue() != 0) {
ids.add(info.SkeletonTrackingId.intValue());
types.add(info.HandPointerInfos[1].HandType.value);
gestureTracker.update(device, interactionFrame.TimeStamp.getValue(),
info.HandPointerInfos[1].RawX,
info.HandPointerInfos[1].RawY,
info.HandPointerInfos[1].RawZ,
info.SkeletonTrackingId.intValue(),
info.HandPointerInfos[1].HandType.value);
}
}
gestureTracker.findDestroyed(ids, types);
}
// wait for new data for all streams
// also look for new hands for hand tracking
class BackgroundThread implements Runnable {
@Override
public void run() {
while(loaded) {
HANDLE[] handles = {nextSkeletonFrame, nextDepthImageFrame, nextInteractionFrame};
Kernel32.INSTANCE.WaitForMultipleObjects(handles.length, handles, false, Kernel32.INFINITE);
if (WinBase.WAIT_OBJECT_0 == Kernel32.INSTANCE.WaitForSingleObject(nextSkeletonFrame, 0))
try {
processSkeleton();
} catch (Throwable t) {
System.out.println("Error in processSkeleton");
}
if (WinBase.WAIT_OBJECT_0 == Kernel32.INSTANCE.WaitForSingleObject(nextDepthImageFrame, 0))
try {
processDepth();
}catch (Throwable t) {
System.out.println("Error in processDepth");
}
if (WinBase.WAIT_OBJECT_0 == Kernel32.INSTANCE.WaitForSingleObject(nextInteractionFrame, 0))
try {
processInteraction();
} catch (Throwable t) {
System.out.println("Error in processInteraction");
}
}
}
}
@Override
public void updateDriver() {
}
// bugbug
@Override
public int getHandTrackingWidth() {
return 640;
}
// bugbug
@Override
public int getHandTrackingHeight() {
return 480;
}
@Override
public void registerHandCreated(InputReceiver r) {
EventManager.getInstance().registerReceiver(EventType.HAND_CREATED, r);
}
@Override
public void registerHandUpdated(InputReceiver r) {
EventManager.getInstance().registerReceiver(EventType.HAND_UPDATED, r);
}
@Override
public void registerHandDestroyed(InputReceiver r) {
EventManager.getInstance().registerReceiver(EventType.HAND_DESTROYED, r);
}
@Override
public void clearAllHands() {
gestureTracker.clearAllHands();
}
@Override
public ByteBuffer getVisualData() {
Kernel32.INSTANCE.WaitForSingleObject(nextColorImageFrame, Kernel32.INFINITE);
NUI_IMAGE_FRAME imageFrame = new NUI_IMAGE_FRAME();
checkRC(device.NuiImageStreamGetNextFrame(colorStream, new DWORD(0), imageFrame));
NUI_LOCKED_RECT lockedRect = new NUI_LOCKED_RECT();
checkRC(imageFrame.pFrameTexture.LockRect(new UINT(0), lockedRect, null, new DWORD(0)));
if(lockedRect.Pitch == 0)
throw new RuntimeException("Kinect didn't give us data");
ByteBuffer buf = ByteBuffer.wrap(lockedRect.getBytes());
checkRC(imageFrame.pFrameTexture.UnlockRect(0));
// Release the frame
checkRC(device.NuiImageStreamReleaseFrame(colorStream, imageFrame));
return buf;
}
// bugbug
@Override
public int getRGBImageWidth() {
return 640;
}
//bugbug
@Override
public int getRGBImageHeight() {
return 480;
}
@Override
public ShortBuffer getDepthImageData() {
return null;
}
// bugbug
@Override
public int getDepthImageWidth() {
return 640;
}
// bugbug
@Override
public int getDepthImageHeight() {
return 480;
}
public void registerGestureRecognized(InputReceiver r) {
EventManager.getInstance().registerReceiver(EventType.GESTURE_RECOGNIZED, r);
}
public static void checkRC(HRESULT hr) {
if (FAILED(hr)) {
throw new COMException(hr.toString());
}
}
}