/// <summary> /// Called to retry finding a sensor when the SensorConflict or /// NoAvailableSensors flags are set. /// </summary> public void TryResolveConflict() { using (var callbackLock = new CallbackLock(lockObject)) { TryFindAndStartKinect(callbackLock); } }
/// <summary> /// Gives up the current sensor if it has one. Stops watching for new sensors. /// </summary> public void Stop() { if (isStarted) { using (var callbackLock = new CallbackLock(lockObject)) { // Check again in case someone stopped us while // we were waiting on the lock. if (isStarted) { isStarted = false; KinectSensor.KinectSensors.StatusChanged -= KinectSensorsOnStatusChanged; SetSensorAndStatus(callbackLock, null, ChooserStatus.None); } } } }
private void KinectSensorsOnStatusChanged(object sender, StatusChangedEventArgs e) { if (e != null) { if ((e.Sensor == this.Kinect) || (this.Kinect == null)) { using (var callbackLock = new CallbackLock(lockObject)) { // Check again in case things changed while we were // waiting on the lock. if ((e.Sensor == this.Kinect) || (this.Kinect == null)) { // Something about our sensor changed or we don't have a sensor. TryFindAndStartKinect(callbackLock); } } } } }
/// <summary> /// Starts choosing a sensor. In the typical case, a sensor will /// be found and events will be fired before this function returns. /// </summary> public void Start() { if (!isStarted) { using (var callbackLock = new CallbackLock(lockObject)) { // Check again in case someone else started while // we were waiting on the lock. if (!isStarted) { isStarted = true; KinectSensor.KinectSensors.StatusChanged += KinectSensorsOnStatusChanged; TryFindAndStartKinect(callbackLock); } } } }
/// <summary> /// Helper to update our external status. /// </summary> /// <param name="callbackLock">Used to delay notifications to the client until after we exit the lock.</param> /// <param name="newKinect">The new sensor</param> /// <param name="newChooserStatus">The status we want to report to clients</param> private void SetSensorAndStatus(CallbackLock callbackLock, KinectSensor newKinect, ChooserStatus newChooserStatus) { KinectSensor oldKinect = Kinect; if (oldKinect != newKinect) { if (oldKinect != null) { oldKinect.Stop(); } Kinect = newKinect; callbackLock.LockExit += () => this.kinectChangedContextWrapper.Invoke(this, new KinectChangedEventArgs(oldKinect, newKinect)); callbackLock.LockExit += () => RaisePropertyChanged("Kinect"); } if (Status != newChooserStatus) { Status = newChooserStatus; callbackLock.LockExit += () => RaisePropertyChanged("Status"); } }
private void KinectSensorsOnStatusChanged(object sender, StatusChangedEventArgs e) { if (e != null) { if ((e.Sensor == this.Kinect) || (this.Kinect == null)) { using (var callbackLock = new CallbackLock(lockObject)) { // Check again in case things changed while we were // waiting on the lock. if ((e.Sensor == this.Kinect) || (this.Kinect == null)) { // Something about our sensor changed or we don't have a sensor. TryFindAndStartKinect(callbackLock); } } } } }
/// <summary> /// Called to retry finding a sensor when the SensorConflict or /// NoAvailableSensors flags are set. /// </summary> public void TryResolveConflict() { using (var callbackLock = new CallbackLock(lockObject)) { TryFindAndStartKinect(callbackLock); } }
/// <summary> /// Gives up the current sensor if it has one. Stops watching for new sensors. /// </summary> public void Stop() { if (isStarted) { using (var callbackLock = new CallbackLock(lockObject)) { // Check again in case someone stopped us while // we were waiting on the lock. if (isStarted) { isStarted = false; KinectSensor.KinectSensors.StatusChanged -= KinectSensorsOnStatusChanged; SetSensorAndStatus(callbackLock, null, ChooserStatus.None); } } } }
/// <summary> /// Starts choosing a sensor. In the typical case, a sensor will /// be found and events will be fired before this function returns. /// </summary> public void Start() { if (!isStarted) { using (var callbackLock = new CallbackLock(lockObject)) { // Check again in case someone else started while // we were waiting on the lock. if (!isStarted) { isStarted = true; KinectSensor.KinectSensors.StatusChanged += KinectSensorsOnStatusChanged; TryFindAndStartKinect(callbackLock); } } } }
/// <summary> /// Called when we don't have a sensor or possibly have the wrong sensor /// and we want to see if we can get one. /// </summary> private void TryFindAndStartKinect(CallbackLock callbackLock) { if (!isStarted) { // We aren't started so we don't need to be finding anything. Debug.Assert(Status == ChooserStatus.None, "isStarted and Status out of sync"); return; } if (Kinect != null && Kinect.Status == KinectStatus.Connected) { if (requiredConnectionId == null) { // we already have an appropriate sensor Debug.Assert(Status == ChooserStatus.SensorStarted, "Chooser in unexpected state"); return; } if (Kinect.DeviceConnectionId == requiredConnectionId) { // we already have the requested sensor Debug.Assert(Status == ChooserStatus.SensorStarted, "Chooser in unexpected state"); return; } } KinectSensor newSensor = null; ChooserStatus newStatus = 0; if (KinectSensor.KinectSensors.Count == 0) { newStatus = ChooserStatus.NoAvailableSensors; } else { foreach (KinectSensor sensor in KinectSensor.KinectSensors) { if (requiredConnectionId != null && sensor.DeviceConnectionId != requiredConnectionId) { // client has set a required connection Id and this // sensor does not have that Id newStatus |= ChooserStatus.NoAvailableSensors; continue; } if (sensor.Status != KinectStatus.Connected) { // Sensor is in some unusable state newStatus |= GetErrorStatusFromSensor(sensor); continue; } if (sensor.IsRunning) { // Sensor is already in use by this application newStatus |= ChooserStatus.NoAvailableSensors; continue; } // There is a potentially available sensor, try to start it try { sensor.Start(); } catch (IOException) { // some other app has this sensor. newStatus |= ChooserStatus.SensorConflict; continue; } catch (InvalidOperationException) { // TODO: In multi-proc scenarios, this is getting thrown at the start before we see IOException. Need to understand. // some other app has this sensor. newStatus |= ChooserStatus.SensorConflict; continue; } // Woo hoo, we have a started sensor. newStatus = ChooserStatus.SensorStarted; newSensor = sensor; break; } } SetSensorAndStatus(callbackLock, newSensor, newStatus); }
/// <summary> /// Resets all state to the initial state, with no users remembered as engaged or tracked. /// </summary> public void Reset() { using (var callbackLock = new CallbackLock(this.lockObject)) { this.activityMeter.Clear(); this.TrackedUserTrackingIds.Clear(); this.EngagedUserTrackingId = SharedConstants.InvalidUserTrackingId; this.SetPrimaryUserTrackingId(SharedConstants.InvalidUserTrackingId, callbackLock); this.UpdateUserStates(callbackLock); } }
/// <summary> /// Update user states exposed to clients, if necessary. /// </summary> /// <param name="callbackLock"> /// Lock used to delay all events until after we exit lock section. /// </param> private void UpdateUserStates(CallbackLock callbackLock) { this.userStatesAccumulator.Clear(); // Add states for tracked users foreach (var trackingId in this.TrackedUserTrackingIds) { this.userStatesAccumulator.Add(trackingId, TrackedStateName); } if (this.EngagedUserTrackingId != SharedConstants.InvalidUserTrackingId) { // Engaged state supersedes all other states this.userStatesAccumulator[this.EngagedUserTrackingId] = EngagedStateName; } if (this.HaveUserStatesChanged()) { var temporaryMap = this.publicUserStates; this.publicUserStates = this.userStatesAccumulator; this.userStatesAccumulator = temporaryMap; var userStatesToSend = GetStateMappingEntryArray(this.publicUserStates); callbackLock.LockExit += () => this.SendUserStateChanged( new UserStatesChangedEventMessage { category = EventCategory, eventType = UserStatesChangedEventType, userStates = userStatesToSend }); } }
/// <summary> /// Update the primary user being tracked. /// </summary> /// <param name="candidateUserInfo"> /// User information collection from which we will choose a primary user. /// </param> /// <param name="callbackLock"> /// Lock used to delay all events until after we exit lock section. /// </param> private void UpdatePrimaryUser(IEnumerable<UserInfo> candidateUserInfo, CallbackLock callbackLock) { int firstPrimaryUserCandidate = SharedConstants.InvalidUserTrackingId; bool currentPrimaryUserStillPrimary = false; bool engagedUserIsPrimary = false; var trackingIdsAvailable = new HashSet<int>(); foreach (var userInfo in candidateUserInfo) { if (userInfo.SkeletonTrackingId == SharedConstants.InvalidUserTrackingId) { continue; } trackingIdsAvailable.Add(userInfo.SkeletonTrackingId); foreach (var handPointer in userInfo.HandPointers) { if (handPointer.IsPrimaryForUser) { if (this.PrimaryUserTrackingId == userInfo.SkeletonTrackingId) { // If the current primary user still has an active hand, we should continue to consider them the primary user. currentPrimaryUserStillPrimary = true; } else if (SharedConstants.InvalidUserTrackingId == firstPrimaryUserCandidate) { // Else if this is the first user with an active hand, they are the alternative candidate for primary user. firstPrimaryUserCandidate = userInfo.SkeletonTrackingId; } if (this.EngagedUserTrackingId == userInfo.SkeletonTrackingId) { engagedUserIsPrimary = true; } } } } // If engaged user has a primary hand, always pick that user as primary user. // If current primary user still has a primary hand, let them remain primary. // Otherwise default to first primary user candidate seen. int primaryUserTrackingId = engagedUserIsPrimary ? this.EngagedUserTrackingId : (currentPrimaryUserStillPrimary ? this.PrimaryUserTrackingId : firstPrimaryUserCandidate); this.SetPrimaryUserTrackingId(primaryUserTrackingId, callbackLock); }
internal void SetPrimaryUserTrackingId(int newId, CallbackLock callbackLock) { int oldId = this.PrimaryUserTrackingId; this.PrimaryUserTrackingId = newId; if (oldId != newId) { callbackLock.LockExit += () => this.SendUserStateChanged( new UserTrackingIdChangedEventMessage { category = EventCategory, eventType = PrimaryUserChangedEventType, oldValue = oldId, newValue = newId }); } }
/// <summary> /// Called whenever the set of tracked users has changed. /// </summary> /// <param name="trackedUserInfo"> /// User information from which we'll update the set of tracked users and the primary user. /// </param> /// <param name="timestamp"> /// Interaction frame timestamp corresponding to given user information. /// </param> public void UpdateUserInformation(IEnumerable<UserInfo> trackedUserInfo, long timestamp) { bool foundEngagedUser = false; int firstTrackedUser = SharedConstants.InvalidUserTrackingId; using (var callbackLock = new CallbackLock(this.lockObject)) { this.previousTrackedUserTrackingIds.Clear(); var nextTrackedIds = this.previousTrackedUserTrackingIds; this.previousTrackedUserTrackingIds = this.TrackedUserTrackingIds; this.TrackedUserTrackingIds = nextTrackedIds; var trackedUserInfoArray = trackedUserInfo as UserInfo[] ?? trackedUserInfo.ToArray(); foreach (var userInfo in trackedUserInfoArray) { if (userInfo.SkeletonTrackingId == SharedConstants.InvalidUserTrackingId) { continue; } if (this.EngagedUserTrackingId == userInfo.SkeletonTrackingId) { this.TrackedUserTrackingIds.Add(userInfo.SkeletonTrackingId); foundEngagedUser = true; } else if (HasTrackedHands(userInfo) && (this.previousTrackedUserTrackingIds.Contains(userInfo.SkeletonTrackingId) || this.IsInactive(userInfo, timestamp))) { // Keep track of the non-engaged users we find that have at least one // tracked hand pointer and also either (1) were previously tracked or // (2) are not moving too much this.TrackedUserTrackingIds.Add(userInfo.SkeletonTrackingId); if (firstTrackedUser == SharedConstants.InvalidUserTrackingId) { // Consider the first non-engaged, stationary user as a candidate for engagement firstTrackedUser = userInfo.SkeletonTrackingId; } } } // If engaged user was not found in list of candidate users, engaged user has become invalid. if (!foundEngagedUser) { this.EngagedUserTrackingId = SharedConstants.InvalidUserTrackingId; } // Decide who should be the primary user, if anyone this.UpdatePrimaryUser(trackedUserInfoArray, callbackLock); // If there's a primary user, it is the preferred candidate for engagement. // Otherwise, the first tracked user seen is the preferred candidate. int candidateUserTrackingId = (this.PrimaryUserTrackingId != SharedConstants.InvalidUserTrackingId) ? this.PrimaryUserTrackingId : firstTrackedUser; // If there is a valid candidate user that is not already the engaged user if ((candidateUserTrackingId != SharedConstants.InvalidUserTrackingId) && (candidateUserTrackingId != this.EngagedUserTrackingId)) { // If there is currently no engaged user, or if candidate user is the // primary user controlling interactions while the currently engaged user // is not interacting if ((this.EngagedUserTrackingId == SharedConstants.InvalidUserTrackingId) || (candidateUserTrackingId == this.PrimaryUserTrackingId)) { this.PromoteCandidateToEngaged(candidateUserTrackingId); } } // Update user states as the very last action, to include results from updates // performed so far this.UpdateUserStates(callbackLock); } }
/// <summary> /// Helper to update our external status. /// </summary> /// <param name="callbackLock">Used to delay notifications to the client until after we exit the lock.</param> /// <param name="newKinect">The new sensor</param> /// <param name="newChooserStatus">The status we want to report to clients</param> private void SetSensorAndStatus(CallbackLock callbackLock, KinectSensor newKinect, ChooserStatus newChooserStatus) { KinectSensor oldKinect = Kinect; if (oldKinect != newKinect) { if (oldKinect != null) { oldKinect.Stop(); } Kinect = newKinect; callbackLock.LockExit += () => this.kinectChangedContextWrapper.Invoke(this, new KinectChangedEventArgs(oldKinect, newKinect)); callbackLock.LockExit += () => RaisePropertyChanged("Kinect"); } if (Status != newChooserStatus) { Status = newChooserStatus; callbackLock.LockExit += () => RaisePropertyChanged("Status"); } }
/// <summary> /// Called when we don't have a sensor or possibly have the wrong sensor /// and we want to see if we can get one. /// </summary> private void TryFindAndStartKinect(CallbackLock callbackLock) { if (!isStarted) { // We aren't started so we don't need to be finding anything. Debug.Assert(Status == ChooserStatus.None, "isStarted and Status out of sync"); return; } if (Kinect != null && Kinect.Status == KinectStatus.Connected) { if (requiredConnectionId == null) { // we already have an appropriate sensor Debug.Assert(Status == ChooserStatus.SensorStarted, "Chooser in unexpected state"); return; } if (Kinect.DeviceConnectionId == requiredConnectionId) { // we already have the requested sensor Debug.Assert(Status == ChooserStatus.SensorStarted, "Chooser in unexpected state"); return; } } KinectSensor newSensor = null; ChooserStatus newStatus = 0; if (KinectSensor.KinectSensors.Count == 0) { newStatus = ChooserStatus.NoAvailableSensors; } else { foreach (KinectSensor sensor in KinectSensor.KinectSensors) { if (requiredConnectionId != null && sensor.DeviceConnectionId != requiredConnectionId) { // client has set a required connection Id and this // sensor does not have that Id newStatus |= ChooserStatus.NoAvailableSensors; continue; } if (sensor.Status != KinectStatus.Connected) { // Sensor is in some unusable state newStatus |= GetErrorStatusFromSensor(sensor); continue; } if (sensor.IsRunning) { // Sensor is already in use by this application newStatus |= ChooserStatus.NoAvailableSensors; continue; } // There is a potentially available sensor, try to start it try { sensor.Start(); } catch (IOException) { // some other app has this sensor. newStatus |= ChooserStatus.SensorConflict; continue; } catch (InvalidOperationException) { // TODO: In multi-proc scenarios, this is getting thrown at the start before we see IOException. Need to understand. // some other app has this sensor. newStatus |= ChooserStatus.SensorConflict; continue; } // Woo hoo, we have a started sensor. newStatus = ChooserStatus.SensorStarted; newSensor = sensor; break; } } SetSensorAndStatus(callbackLock, newSensor, newStatus); }
/// <summary> /// Promote candidate user to be the engaged user. /// </summary> /// <param name="candidateTrackingId"> /// Tracking Id of user to be promoted to engaged user. /// If tracking Id does not match the Id of one of the currently tracked users, /// no action is taken. /// </param> /// <returns> /// True if specified candidate could be confirmed as the new engaged user, /// false otherwise. /// </returns> public bool PromoteCandidateToEngaged(int candidateTrackingId) { bool isConfirmed = false; if ((candidateTrackingId != SharedConstants.InvalidUserTrackingId) && this.TrackedUserTrackingIds.Contains(candidateTrackingId)) { using (var callbackLock = new CallbackLock(this.lockObject)) { this.EngagedUserTrackingId = candidateTrackingId; this.UpdateUserStates(callbackLock); } isConfirmed = true; } return isConfirmed; }