Example #1
0
 def _create_conf(self):
     fd, conf_path = tempfile.mkstemp(suffix='.conf')
     conf = "start  {}\nend {}\ninterface {}\nnotify_file dumpleases\noption subnet {}\noption lease {}\n".format(
         Settings().peeraddress,
         Settings().peeraddress, self.interface,
         Settings().netmask,
         Settings().timeout)
     with open(conf_path, 'w') as c:
         c.write(conf)
     return conf_path
Example #2
0
 def __init__(self,  player, logger='picast'):
     super(RtspSink, self).__init__(name='rtsp-server-0', daemon=True)
     self.config = Settings()
     self.logger = getLogger(logger)
     self.player = player
     self.watchdog = 0
     self.csnum = 0
     self.daemon = True
     self.video = RasberryPiVideo()
     self.wfd_parameters = self.config.get_wfd_parameters()
     self.wfd_video_formats = self.video.get_wfd_video_formats()
Example #3
0
 def __init__(self, interface: str, logger='picast'):
     """Constructor accept an interface to listen."""
     self.config = Settings()
     self.dhcpd = None
     self.interface = interface
     self.logger = getLogger(logger)
     self.conf_path = self._create_conf()
Example #4
0
def main(arg: Optional[Any] = None):
    parser = argparse.ArgumentParser(
        prog='picast',
        description='picast',
        formatter_class=argparse.RawTextHelpFormatter,
        add_help=True)
    parser.add_argument("--debug",
                        action="store_true",
                        help="Verbose debug output")
    parser.add_argument("-c", "--config", help="Specify configuration file.")

    args = parser.parse_args(arg)
    if args.config:
        config_file = args.config
        if os.path.exists(config_file) and os.path.isfile(config_file):
            pass
        else:
            config_file = None
    else:
        config_file = None

    # ------------------- start of configurations
    # it should call first: load configurations from ini files
    config = Settings(config=config_file)
    package_dir = os.path.join(os.path.dirname(__file__))
    if config.logging_config:
        logging_ini = config.logging_config
        if not os.path.exists(logging_ini):
            if os.path.exists(os.path.join(package_dir, logging_ini)):
                logging_ini = os.path.join(package_dir, logging_ini)
    else:
        logging_ini = os.path.join(package_dir, "logging.ini")
    LoggingConfig.fileConfig(logging_ini)
    logger = getLogger(config.logger)
    if args.debug:
        logger.setLevel("DEBUG")

    if config.player == "gst":
        player = GstPlayer()  # type: Optional[Union[GstPlayer, VlcPlayer]]
    elif config.player == "vlc":
        player = VlcPlayer()
    else:
        player = None
        logger.fatal("FATAL: Unknown player name option!: {}".format(
            config.player))
        exit(1)
    # ------------------- end of configurations

    wifip2p = WifiP2PServer()
    rtspsink = RtspSink(player)

    wifip2p.start()
    rtspsink.start()

    rtspsink.join()
    wifip2p.join()
Example #5
0
 def __init__(self):
     self.config = Settings()
     self.native = 0x06
     self.preferred = 0
     self.profile = 0x01
     self.level = 0x10
     with open(os.path.join(os.path.dirname(__file__), 'resolutions.json'),
               'r') as j:
         self.resolutions = json.load(j)[0]
     self._get_display_resolutions()
Example #6
0
def test_config_override():
    fd, temp_path = tempfile.mkstemp()
    with open(temp_path, "w") as f:
        f.write("[player]\n")
        f.write("name=nop\n")
    Settings()._config = None # Clean singletone state
    assert Settings().device_name == 'picast'
    assert Settings().player == 'vlc'
    Settings()._config = None # Clena singletone state
    assert Settings(config=temp_path).device_name == 'picast'
    assert Settings(config=temp_path).player == 'nop'
    Settings()._config = None # Clena singletone state
    os.unlink(temp_path)
Example #7
0
def test_config_device_name():
    assert Settings().device_name == 'picast'
Example #8
0
def test_config_rtp_port():
    assert Settings().rtp_port == 1028
Example #9
0
def test_config_rtsp_port():
    assert Settings().rtsp_port == 7236
Example #10
0
class RtspSink(threading.Thread):

    def __init__(self,  player, logger='picast'):
        super(RtspSink, self).__init__(name='rtsp-server-0', daemon=True)
        self.config = Settings()
        self.logger = getLogger(logger)
        self.player = player
        self.watchdog = 0
        self.csnum = 0
        self.daemon = True
        self.video = RasberryPiVideo()
        self.wfd_parameters = self.config.get_wfd_parameters()
        self.wfd_video_formats = self.video.get_wfd_video_formats()

    def get_rtsp_headers(self) -> Dict[str, Optional[str]]:
        headers = self.read_headers()
        results = {}
        firstline = headers[0]
        regex = re.compile(r"RTSP/1.0 ([0-9]+) (\w+([ ]\w)*)")
        if firstline.startswith('RTSP/1.0'):
            m = regex.match(firstline)
            if m:
                status, reason = m.group(1, 2)
                cmd = None
                url = None
                resp = "{} {}".format(status, reason)  # type: Optional[str]
            else:
                raise ValueError
        else:
            cmd, url, version = firstline.split(' ')
            if version != 'RTSP/1.0':
                raise ValueError
            resp = None
        results['cmd'] = cmd
        results['url'] = url
        results['resp'] = resp
        for h in headers[1:]:
            pos = h.find(':')
            if not pos:
                break
            key = h[:pos]
            val = h[pos + 2:]
            results[key] = val
        return results

    def read_headers(self) -> List[str]:
        headers = []

        line = self.sock.readline()
        while not line:
            line = self.sock.readline()

        while line:
            headers.append(line.decode('UTF-8'))
            line = self.sock.readline()
        self.logger.debug("<< {}".format(headers))
        return headers

    def read_body(self, headers) -> bytes:
        length = headers.get('Content-Length', None)
        if length is None:
            return b''
        length = int(length)
        return self.sock.read(length)

    @staticmethod
    def _rtsp_response_header(cmd: Optional[str] = None, url: Optional[str] = None, res: Optional[str] = None,
                              seq: Optional[str] = None, others: Optional[List[Tuple[str, str]]] = None) -> str:
        if cmd is not None:
            msg = "{0:s} {1:s} RTSP/1.0".format(cmd, url)
        else:
            msg = "RTSP/1.0"
        if res is not None:
            msg += ' {0}\r\nCSeq: {1}\r\n'.format(res, seq)
        else:
            msg += '\r\nCSeq: {}\r\n'.format(seq)
        if others is not None:
            for k, v in others:
                msg += '{}: {}\r\n'.format(k, v)
        msg += '\r\n'
        return msg

    @staticmethod
    def _parse_transport_header(data: str) -> Tuple[bool, str, str]:
        """ Parse Transport header value such as "Transport: RTP/AVP/UDP;unicast;client_port=1028;server_port=5000"
        """
        udp = True
        client_port = '0'
        server_port = '0'
        paramlist = data.split(';')
        for p in paramlist:
            if p.startswith('RTP'):
                rtp, avp, prot = p.split('/')
                if prot == 'UDP':
                    udp = True
                elif prot == 'TCP':
                    udp = False
                else:
                    raise ValueError
            elif p.startswith('unicast'):
                pass
            elif p.startswith('client_port'):
                _, client_port = p.split('=')
            elif p.startswith('server_port'):
                _, server_port = p.split('=')
            else:
                continue
        return udp, client_port, server_port

    def rtsp_m1(self) -> bool:
        headers = self.get_rtsp_headers()
        if headers['cmd'] != 'OPTIONS':
            return False
        s_data = self._rtsp_response_header(seq=headers['CSeq'], res="200 OK",
                                            others=[("Public", "org.wfa.wfd1.0, SET_PARAMETER, GET_PARAMETER")])
        self.logger.debug("<-{}".format(s_data))
        self.sock.write(s_data.encode('ASCII'))
        return True

    def rtsp_m2(self) -> bool:
        self.csnum = 100
        s_data = self._rtsp_response_header(seq=str(self.csnum), cmd="OPTIONS",
                                            url="*", others=[('Require', 'org.wfa.wfd1.0')])
        self.logger.debug("<-{}".format(s_data))
        self.sock.write(s_data.encode('ASCII'))
        headers = self.get_rtsp_headers()
        if headers['CSeq'] != '100' or headers['resp'] != "200 OK":
            return False
        return True

    def rtsp_m3(self) -> bool:
        headers = self.get_rtsp_headers()
        if headers['cmd'] != 'GET_PARAMETER' or headers['url'] != 'rtsp://localhost/wfd1.0':
            return False
        body = self.read_body(headers)
        msg = ''
        for req in body.decode('UTF-8').split('\r\n'):
            if req == '':
                continue
            elif req == 'wfd_client_rtp_ports':
                msg += "wfd_client_rtp_ports: RTP/AVP/UDP;unicast {} 0 mode=play\r\n".format(self.config.rtp_port)
            elif req == 'wfd_video_formats':
                msg += 'wfd_video_formats: {}\r\n'.format(self.wfd_video_formats)
            elif req in self.wfd_parameters:
                msg += '{}: {}\r\n'.format(req, self.wfd_parameters[req])
            else:
                msg += '{}: none\r\n'.format(req)

        m3resp = self._rtsp_response_header(seq=headers['CSeq'], res="200 OK",
                                            others=[('Content-Type', 'text/parameters'),
                                                    ('Content-Length', str(len(msg)))
                                                    ])
        m3resp += msg
        self.logger.debug("<-{}".format(m3resp))
        self.sock.write(m3resp.encode('ASCII'))
        return True

    def rtsp_m4(self) -> bool:
        headers = self.get_rtsp_headers()
        if headers['cmd'] != "SET_PARAMETER" or headers['url'] != "rtsp://localhost/wfd1.0":
            return False
        self.read_body(headers)
        # FIXME: parse body here to retrieve video mode and set actual mode.
        s_data = self._rtsp_response_header(res="200 OK", seq=headers['CSeq'])
        self.logger.debug("<-{}".format(s_data))
        self.sock.write(s_data.encode('ASCII'))
        return True

    def rtsp_m5(self) -> bool:
        headers = self.get_rtsp_headers()
        self.read_body(headers)
        if headers['cmd'] != 'SET_PARAMETER':
            self.logger.debug("M5: got other than SET_PARAMETER request.")
            s_data = self._rtsp_response_header(res="400 Bad Requests", seq=headers['CSeq'])
            self.logger.debug("<-{}".format(s_data))
            self.sock.write(s_data.encode('ASCII'))
            return False
        # FIXME: analyze body to have  'wfd_trigger_method: SETUP'
        s_data = self._rtsp_response_header(res="200 OK", seq=headers['CSeq'])
        self.logger.debug("<-{}".format(s_data))
        self.sock.write(s_data.encode('ASCII'))
        return True

    def rtsp_m6(self) -> Tuple[Optional[str], Optional[str]]:
        self.csnum += 1
        sessionid = None
        server_port = None
        m6req = self._rtsp_response_header(cmd="SETUP",
                                           url="rtsp://{0:s}/wfd1.0/streamid=0".format(self.config.peeraddress),
                                           seq=str(self.csnum),
                                           others=[
                                               ('Transport',
                                                'RTP/AVP/UDP;unicast;client_port={0:d}'.format(self.config.rtp_port))
                                           ])
        self.logger.debug("<-{}".format(m6req))
        self.sock.write(m6req.encode('ASCII'))

        headers = self.get_rtsp_headers()
        if headers['CSeq'] is not None and headers['CSeq'] != str(self.csnum):
            raise ValueError('Unmatch sequence number: {}'.format(headers['CSeq']))
        if 'Transport' in headers:
            assert headers['Transport'] is not None
            udp, client_port, server_port = self._parse_transport_header(headers['Transport'])
            self.logger.debug("server port {}".format(server_port))
        if 'Session' in headers:
            assert headers['Session'] is not None
            sessionid = headers['Session'].split(';')[0]
        return sessionid, server_port

    def rtsp_m7(self, sessionid: str) -> bool:
        self.csnum += 1
        m7req = self._rtsp_response_header(cmd='PLAY',
                                           url='rtsp://{0:s}/wfd1.0/streamid=0'.format(self.config.peeraddress),
                                           seq=str(self.csnum),
                                           others=[('Session', sessionid)])
        self.logger.debug("<-{}".format(m7req))
        self.sock.write(m7req.encode('ASCII'))
        headers = self.get_rtsp_headers()
        if headers['resp'] != "200 OK" or headers['CSeq'] != str(self.csnum):
            return False
        return True

    def negotiate(self) -> bool:
        self.logger.debug("---- Start negotiation ----")
        while True:
            if not self.rtsp_m1():
                break
            if not self.rtsp_m2():
                break
            if not self.rtsp_m3():
                break
            if not self.rtsp_m4():
                break
            if not self.rtsp_m5():
                break
            sessionid, server_port = self.rtsp_m6()
            if sessionid is None:
                break
            if not self.rtsp_m7(sessionid):
                break
            self.logger.info("---- Negotiation successful ----")
            return True
        self.logger.info("---- Negotiation failed ----")
        return False

    def is_keep_alive(self, headers):
        return headers['cmd'] == "GET_PARAMETER" and headers['url'] == "rtsp://localhost/wfd1.0"

    def is_parameter_change(self, headers):
        return headers['cmd'] == "SET_PARAMETER"

    def keep_alive(self, headers):
        self.read_body(headers)
        resp_msg = self._rtsp_response_header(seq=headers['CSeq'], res="200 OK")
        self.sock.write(resp_msg.encode('ASCII'))
        return

    def parameter_change(self, headers):
        body = self.read_body(headers)
        lines = body.decode('UTF-8').splitlines()
        if 'wfd_trigger_method: TEARDOWN' in lines:
            self.logger.debug("Got TEARDOWN request.")
            self.response_teardown(headers)
            self.teardown = True
        return

    def response_teardown(self, headers):
        resp_msg = self._rtsp_response_header(seq=headers['CSeq'], res="200 OK")
        self.sock.write(resp_msg.encode('ASCII'))
        m5_msg = self._rtsp_response_header(seq=str(self.csnum), cmd="TEARDOWN", url="rtsp://localhost/wfd1.0")
        self.sock.write(m5_msg.encode('ASCII'))

    def is_response(self, headers):
        return headers['resp'] == "200 OK"

    def play(self) -> None:
        self.player.start()
        self.teardown = False
        self.watchdog = 0
        self.sock.settimeout(10)

        while True:
            try:
                headers = self.get_rtsp_headers()
            except socket.error as e:
                err = e.args[0]
                if err == errno.EAGAIN or err == errno.EWOULDBLOCK or err == errno.EALREADY or err == errno.EINPROGRESS:
                    sleep(0.1)
                    continue
                elif err == errno.ETIMEDOUT:
                    self.watchdog += 1
                    if self.watchdog > self.config.max_timeout:
                        break
                else:
                    self.logger.debug("Got unexpected socket error {}".format(e.args[0]))
                    break
            else:
                self.watchdog = 0
                if self.is_keep_alive(headers):
                    self.keep_alive(headers)
                elif self.is_parameter_change(headers):
                    self.parameter_change(headers)
                    if self.teardown:
                        self.player.stop()
                elif self.is_response(headers):
                    if self.teardown:
                        break
                else:
                    # ignore all other commands now...
                    continue

    def run(self):
        sd = ServiceDiscovery()
        sd.register()
        while True:
            self.sock = RTSPTransport(self.config.peeraddress, self.config.rtsp_port)
            if self.negotiate():
                self.play()
            self.sock.close()
            sleep(1)
Example #11
0
def test_config_group_name():
    assert Settings().group_name == 'persistent'
Example #12
0
def test_config_player_log_file():
    assert Settings().player_log_file == '/var/tmp/player.log'
Example #13
0
 def __init__(self, logger='picast'):
     self.config = Settings()
     self.logger = getLogger(logger)
     Gst.init(None)
Example #14
0
def test_config_pin():
    assert Settings().pin == '12345678'
Example #15
0
def test_config_device_type():
    assert Settings().device_type == '7-0050F204-4'
Example #16
0
def test_config_gst_decoder():
    assert Settings().gst_decoder == 'omxh264dec'
Example #17
0
def test_config_max_timeout():
    assert Settings().max_timeout == '10'
Example #18
0
def test_config_wps_mode():
    assert Settings().wps_mode == 'pin'
Example #19
0
def test_config_recreate_group():
    assert Settings().recreate_group == False
Example #20
0
def test_config_logger():
    assert Settings().logger == 'picast'
Example #21
0
def test_config_player():
    assert Settings().player == 'vlc'
Example #22
0
def test_config_logging_config():
    assert Settings().logging_config == 'logging.ini'
Example #23
0
def test_config_player_custom_args():
    assert Settings().player_custom_args == []
Example #24
0
def test_config_myaddress():
    assert Settings().myaddress == '192.168.173.1'
Example #25
0
def test_config_peeraddress():
    assert Settings().peeraddress == '192.168.173.80'
Example #26
0
def test_config_netmask():
    assert Settings().netmask == '255.255.255.0'
Example #27
0
 def __init__(self):
     self.config = Settings()
     self.logger = getLogger(self.config.logger)
     self.zc = zeroconf.Zeroconf()
Example #28
0
 def __init__(self, R2=False, logger='picast'):
     super(WifiP2PServer, self).__init__(name='wifi-p2p-0', daemon=False)
     self.config = Settings()
     self.logger = getLogger(logger)
     self.set_p2p_interface(R2)