Beispiel #1
0
 def _send_keyframes(self):
     if self._keyframe_timer is None:
         self._keyframe_timer = ExponentialTimer()
         self.notification_center.add_observer(self, sender=self._keyframe_timer)
     self._keyframe_timer.start(0.5, immediate=True, iterations=5)
Beispiel #2
0
 def _send_keyframes(self):
     if self._keyframe_timer is None:
         self._keyframe_timer = ExponentialTimer()
         self.notification_center.add_observer(self, sender=self._keyframe_timer)
     self._keyframe_timer.start(0.5, immediate=True, iterations=5)
Beispiel #3
0
class VideoStream(RTPStream):
    implements(IVideoProducer)

    type = 'video'
    priority = 1

    def __init__(self):
        super(VideoStream, self).__init__()

        from sipsimple.application import SIPApplication
        self.device = SIPApplication.video_device
        self.video_mixer = SIPApplication.video_mixer
        self._keyframe_timer = None

    @property
    def producer(self):
        return self._transport.remote_video if self._transport else None

    @classmethod
    def new_from_sdp(cls, session, remote_sdp, stream_index):
        stream = super(VideoStream, cls).new_from_sdp(session, remote_sdp, stream_index)
        if stream.device.producer is None:
            raise InvalidStreamError("no video support available")
        #  todo -check this later, commentingout for h264 issues for now
        #if not stream.validate_update(remote_sdp, stream_index):
        #    raise InvalidStreamError("no valid SDP")
        return stream

    def initialize(self, session, direction):
        super(VideoStream, self).initialize(session, direction)
        self.notification_center.add_observer(self, name='VideoDeviceDidChangeCamera')

    def start(self, local_sdp, remote_sdp, stream_index):
        with self._lock:
            if self.state != "INITIALIZED":
                raise RuntimeError("VideoStream.start() may only be called in the INITIALIZED state")
            settings = SIPSimpleSettings()
            self._transport.start(local_sdp, remote_sdp, stream_index, timeout=settings.rtp.timeout)
            self._transport.local_video.producer = self.device.producer
            self._save_remote_sdp_rtp_info(remote_sdp, stream_index)
            self._check_hold(self._transport.direction, True)
            if self._try_ice and self._ice_state == "NULL":
                self.state = 'WAIT_ICE'
            else:
                self._send_keyframes()
                self.state = 'ESTABLISHED'
                self.notification_center.post_notification('MediaStreamDidStart', sender=self)

    def validate_update(self, remote_sdp, stream_index):
        with self._lock:
            remote_media = remote_sdp.media[stream_index]
            if 'H264' in remote_media.codec_list:
                rtpmap = next(attr for attr in remote_media.attributes if attr.name=='rtpmap' and 'h264' in attr.value.lower())
                payload_type = rtpmap.value.partition(' ')[0]
                has_profile_level_id = any('profile-level-id' in attr.value.lower() for attr in remote_media.attributes if attr.name=='fmtp' and attr.value.startswith(payload_type + ' '))
                if not has_profile_level_id:
                    return False
            return True

    def update(self, local_sdp, remote_sdp, stream_index):
        with self._lock:
            new_direction = local_sdp.media[stream_index].direction
            self._check_hold(new_direction, False)
            self._transport.update_direction(new_direction)
            self._save_remote_sdp_rtp_info(remote_sdp, stream_index)
            self._transport.update_sdp(local_sdp, remote_sdp, stream_index)
            self._hold_request = None

    def deactivate(self):
        with self._lock:
            self.notification_center.discard_observer(self, name='VideoDeviceDidChangeCamera')

    def end(self):
        with self._lock:
            if self.state == "ENDED" or self._done:
                return
            self._done = True
            if not self._initialized:
                self.state = "ENDED"
                self.notification_center.post_notification('MediaStreamDidNotInitialize', sender=self, data=NotificationData(reason='Interrupted'))
                return
            if self._keyframe_timer is not None:
                self._keyframe_timer.stop()
                self.notification_center.remove_observer(self, sender=self._keyframe_timer)
            self._keyframe_timer = None
            self.notification_center.post_notification('MediaStreamWillEnd', sender=self)
            if self._transport is not None:
                self.notification_center.remove_observer(self, sender=self._transport)
                self.notification_center.remove_observer(self, sender=self._rtp_transport)
                call_in_thread('device-io', self._transport.stop)
                self._transport = None
                self._rtp_transport = None
            self.state = "ENDED"
            self.notification_center.post_notification('MediaStreamDidEnd', sender=self, data=NotificationData(error=self._failure_reason))
            self.session = None

    def reset(self, stream_index):
        pass

    def _NH_RTPTransportICENegotiationDidSucceed(self, notification):
        with self._lock:
            if self.state == "WAIT_ICE":
                self._send_keyframes()
        super(VideoStream, self)._NH_RTPTransportICENegotiationDidSucceed(notification)

    def _NH_RTPTransportICENegotiationDidFail(self, notification):
        with self._lock:
            if self.state == "WAIT_ICE":
                self._send_keyframes()
        super(VideoStream, self)._NH_RTPTransportICENegotiationDidFail(notification)

    def _NH_RTPVideoTransportDidTimeout(self, notification):
        self.notification_center.post_notification('RTPStreamDidTimeout', sender=self)

    def _NH_RTPVideoTransportRemoteFormatDidChange(self, notification):
        self.notification_center.post_notification('VideoStreamRemoteFormatDidChange', sender=self, data=notification.data)

    def _NH_RTPVideoTransportReceivedKeyFrame(self, notification):
        self.notification_center.post_notification('VideoStreamReceivedKeyFrame', sender=self, data=notification.data)

    def _NH_RTPVideoTransportMissedKeyFrame(self, notification):
        self._transport.request_keyframe()
        self.notification_center.post_notification('VideoStreamMissedKeyFrame', sender=self, data=notification.data)

    def _NH_RTPVideoTransportRequestedKeyFrame(self, notification):
        self._transport.send_keyframe()
        self.notification_center.post_notification('VideoStreamRequestedKeyFrame', sender=self, data=notification.data)

    def _NH_VideoDeviceDidChangeCamera(self, notification):
        new_camera = notification.data.new_camera
        if self._transport is not None and self._transport.local_video is not None:
            self._transport.local_video.producer = new_camera

    def _NH_ExponentialTimerDidTimeout(self, notification):
        if self._transport is not None:
            self._transport.send_keyframe()

    def _create_transport(self, rtp_transport, remote_sdp=None, stream_index=None):
        settings = SIPSimpleSettings()
        codecs = list(self.session.account.rtp.video_codec_list or settings.rtp.video_codec_list)
        return VideoTransport(rtp_transport, self.video_mixer, remote_sdp=remote_sdp, sdp_index=stream_index or 0, codecs=codecs)

    def _check_hold(self, direction, is_initial):
        was_on_hold_by_local = self.on_hold_by_local
        was_on_hold_by_remote = self.on_hold_by_remote
        self.direction = direction
        inactive = self.direction == "inactive"
        self.on_hold_by_local = was_on_hold_by_local if inactive else direction == "sendonly"
        self.on_hold_by_remote = "send" not in direction
        if self.on_hold_by_local or self.on_hold_by_remote:
            self._pause()
        elif not self.on_hold_by_local and not self.on_hold_by_remote and (was_on_hold_by_local or was_on_hold_by_remote):
            self._resume()
        if not was_on_hold_by_local and self.on_hold_by_local:
            self.notification_center.post_notification('RTPStreamDidChangeHoldState', sender=self, data=NotificationData(originator="local", on_hold=True))
        if was_on_hold_by_local and not self.on_hold_by_local:
            self.notification_center.post_notification('RTPStreamDidChangeHoldState', sender=self, data=NotificationData(originator="local", on_hold=False))
        if not was_on_hold_by_remote and self.on_hold_by_remote:
            self.notification_center.post_notification('RTPStreamDidChangeHoldState', sender=self, data=NotificationData(originator="remote", on_hold=True))
        if was_on_hold_by_remote and not self.on_hold_by_remote:
            self.notification_center.post_notification('RTPStreamDidChangeHoldState', sender=self, data=NotificationData(originator="remote", on_hold=False))

    @run_in_twisted_thread
    def _send_keyframes(self):
        if self._keyframe_timer is None:
            self._keyframe_timer = ExponentialTimer()
            self.notification_center.add_observer(self, sender=self._keyframe_timer)
        self._keyframe_timer.start(0.5, immediate=True, iterations=5)

    def _pause(self):
        self._transport.pause()

    def _resume(self):
        self._transport.resume()
        self._send_keyframes()
        self._transport.request_keyframe()
Beispiel #4
0
class VideoStream(RTPStream):
    implements(IVideoProducer)

    type = 'video'
    priority = 1

    def __init__(self):
        super(VideoStream, self).__init__()

        from sipsimple.application import SIPApplication
        self.device = SIPApplication.video_device
        self._keyframe_timer = None

    @property
    def producer(self):
        return self._transport.remote_video if self._transport else None

    @classmethod
    def new_from_sdp(cls, session, remote_sdp, stream_index):
        stream = super(VideoStream, cls).new_from_sdp(session, remote_sdp, stream_index)
        if stream.device.producer is None:
            raise InvalidStreamError("no video support available")
        if not stream.validate_update(remote_sdp, stream_index):
            raise InvalidStreamError("no valid SDP")
        return stream

    def initialize(self, session, direction):
        super(VideoStream, self).initialize(session, direction)
        self.notification_center.add_observer(self, name='VideoDeviceDidChangeCamera')

    def start(self, local_sdp, remote_sdp, stream_index):
        with self._lock:
            if self.state != "INITIALIZED":
                raise RuntimeError("VideoStream.start() may only be called in the INITIALIZED state")
            settings = SIPSimpleSettings()
            self._transport.start(local_sdp, remote_sdp, stream_index, timeout=settings.rtp.timeout)
            self._transport.local_video.producer = self.device.producer
            self._save_remote_sdp_rtp_info(remote_sdp, stream_index)
            self._check_hold(self._transport.direction, True)
            if self._try_ice and self._ice_state == "NULL":
                self.state = 'WAIT_ICE'
            else:
                self._send_keyframes()
                self.state = 'ESTABLISHED'
                self.notification_center.post_notification('MediaStreamDidStart', sender=self)

    def validate_update(self, remote_sdp, stream_index):
        with self._lock:
            remote_media = remote_sdp.media[stream_index]
            if 'H264' in remote_media.codec_list:
                rtpmap = next(attr for attr in remote_media.attributes if attr.name=='rtpmap' and 'h264' in attr.value.lower())
                payload_type = rtpmap.value.partition(' ')[0]
                has_profile_level_id = any('profile-level-id' in attr.value.lower() for attr in remote_media.attributes if attr.name=='fmtp' and attr.value.startswith(payload_type + ' '))
                if not has_profile_level_id:
                    return False
            return True

    def update(self, local_sdp, remote_sdp, stream_index):
        with self._lock:
            new_direction = local_sdp.media[stream_index].direction
            self._check_hold(new_direction, False)
            self._transport.update_direction(new_direction)
            self._save_remote_sdp_rtp_info(remote_sdp, stream_index)
            self._transport.update_sdp(local_sdp, remote_sdp, stream_index)
            self._hold_request = None

    def deactivate(self):
        with self._lock:
            self.notification_center.discard_observer(self, name='VideoDeviceDidChangeCamera')

    def end(self):
        with self._lock:
            if self.state == "ENDED" or self._done:
                return
            self._done = True
            if not self._initialized:
                self.state = "ENDED"
                self.notification_center.post_notification('MediaStreamDidNotInitialize', sender=self, data=NotificationData(reason='Interrupted'))
                return
            if self._keyframe_timer is not None:
                self._keyframe_timer.stop()
                self.notification_center.remove_observer(self, sender=self._keyframe_timer)
            self._keyframe_timer = None
            self.notification_center.post_notification('MediaStreamWillEnd', sender=self)
            if self._transport is not None:
                self.notification_center.remove_observer(self, sender=self._transport)
                self.notification_center.remove_observer(self, sender=self._rtp_transport)
                call_in_thread('device-io', self._transport.stop)
                self._transport = None
                self._rtp_transport = None
            self.state = "ENDED"
            self.notification_center.post_notification('MediaStreamDidEnd', sender=self, data=NotificationData(error=self._failure_reason))
            self.session = None

    def reset(self, stream_index):
        pass

    def _NH_RTPTransportICENegotiationDidSucceed(self, notification):
        with self._lock:
            if self.state == "WAIT_ICE":
                self._send_keyframes()
        super(VideoStream, self)._NH_RTPTransportICENegotiationDidSucceed(notification)

    def _NH_RTPTransportICENegotiationDidFail(self, notification):
        with self._lock:
            if self.state == "WAIT_ICE":
                self._send_keyframes()
        super(VideoStream, self)._NH_RTPTransportICENegotiationDidFail(notification)

    def _NH_RTPVideoTransportDidTimeout(self, notification):
        self.notification_center.post_notification('RTPStreamDidTimeout', sender=self)

    def _NH_RTPVideoTransportRemoteFormatDidChange(self, notification):
        self.notification_center.post_notification('VideoStreamRemoteFormatDidChange', sender=self, data=notification.data)

    def _NH_RTPVideoTransportReceivedKeyFrame(self, notification):
        self.notification_center.post_notification('VideoStreamReceivedKeyFrame', sender=self, data=notification.data)

    def _NH_RTPVideoTransportMissedKeyFrame(self, notification):
        self._transport.request_keyframe()
        self.notification_center.post_notification('VideoStreamMissedKeyFrame', sender=self, data=notification.data)

    def _NH_RTPVideoTransportRequestedKeyFrame(self, notification):
        self._transport.send_keyframe()
        self.notification_center.post_notification('VideoStreamRequestedKeyFrame', sender=self, data=notification.data)

    def _NH_VideoDeviceDidChangeCamera(self, notification):
        new_camera = notification.data.new_camera
        if self._transport is not None and self._transport.local_video is not None:
            self._transport.local_video.producer = new_camera

    def _NH_ExponentialTimerDidTimeout(self, notification):
        if self._transport is not None:
            self._transport.send_keyframe()

    def _create_transport(self, rtp_transport, remote_sdp=None, stream_index=None):
        settings = SIPSimpleSettings()
        codecs = list(self.session.account.rtp.video_codec_list or settings.rtp.video_codec_list)
        return VideoTransport(rtp_transport, remote_sdp=remote_sdp, sdp_index=stream_index or 0, codecs=codecs)

    def _check_hold(self, direction, is_initial):
        was_on_hold_by_local = self.on_hold_by_local
        was_on_hold_by_remote = self.on_hold_by_remote
        self.direction = direction
        inactive = self.direction == "inactive"
        self.on_hold_by_local = was_on_hold_by_local if inactive else direction == "sendonly"
        self.on_hold_by_remote = "send" not in direction
        if self.on_hold_by_local or self.on_hold_by_remote:
            self._pause()
        elif not self.on_hold_by_local and not self.on_hold_by_remote and (was_on_hold_by_local or was_on_hold_by_remote):
            self._resume()
        if not was_on_hold_by_local and self.on_hold_by_local:
            self.notification_center.post_notification('RTPStreamDidChangeHoldState', sender=self, data=NotificationData(originator="local", on_hold=True))
        if was_on_hold_by_local and not self.on_hold_by_local:
            self.notification_center.post_notification('RTPStreamDidChangeHoldState', sender=self, data=NotificationData(originator="local", on_hold=False))
        if not was_on_hold_by_remote and self.on_hold_by_remote:
            self.notification_center.post_notification('RTPStreamDidChangeHoldState', sender=self, data=NotificationData(originator="remote", on_hold=True))
        if was_on_hold_by_remote and not self.on_hold_by_remote:
            self.notification_center.post_notification('RTPStreamDidChangeHoldState', sender=self, data=NotificationData(originator="remote", on_hold=False))

    @run_in_twisted_thread
    def _send_keyframes(self):
        if self._keyframe_timer is None:
            self._keyframe_timer = ExponentialTimer()
            self.notification_center.add_observer(self, sender=self._keyframe_timer)
        self._keyframe_timer.start(0.5, immediate=True, iterations=5)

    def _pause(self):
        self._transport.pause()

    def _resume(self):
        self._transport.resume()
        self._send_keyframes()
        self._transport.request_keyframe()