def receiver(self): """ Connect to the RTSP server and receive the selected video (see :attr:`video`). """ VTLOG.info("Starting GStreamer receiver...") self.pipeline = parse_launch( 'rtspsrc name=source ! tee name=t ! queue ! ' + self.depay + self.__add + ' ! filesink name=sink1 t. ! queue \ ! decodebin ! videorate skip-to-first=True ! video/x-raw-yuv,framerate=' + self.conf['framerate'] + '/1 ! filesink name=sink2') source = self.pipeline.get_by_name('source') sink1 = self.pipeline.get_by_name('sink1') sink2 = self.pipeline.get_by_name('sink2') source.props.location = self.url source.props.protocols = self.conf['protocols'] location = self.conf['tempdir'] + self.conf['num'] + '.' + self.conf[ 'codec'] self.files['received'].append(location) sink1.props.location = location location = self.conf['tempdir'] + self.conf['num'] + '.yuv' self.files['received'].append(location) sink2.props.location = location pad = sink2.get_pad("sink") pad.connect("notify::caps", self.__notifyCaps) self.__play() VTLOG.info("GStreamer receiver stopped")
def __play(self): """ Attach event handler, set state to *playing* and run the loop (see :attr:`loop`). """ self.pipeline.get_bus().add_watch(self.__events) self.pipeline.set_state(STATE_PLAYING) self.loop = MainLoop() self.loop.run() VTLOG.debug("GStreamer: Loop stopped")
def __ping(self): """ Ping to server (4 echoes). """ from scapy.all import IP, ICMP, send from time import sleep sleep(0.5) VTLOG.info("Pinging...") for i in range(0, 4): send(IP(dst=self.conf['ip'])/ICMP(seq=i), verbose=False) sleep(0.5)
def __saveMeasures(self, measures): """ Save measures to disc (with standard module :mod:`pickle`). :param list measures: List of measures. """ VTLOG.info("Saving measures...") from pickle import dump for measure in measures: f = open(self.conf['tempdir'] + self.conf['num'] + '_' + measure['name'] + '.pkl', "wb") dump(measure, f) f.close()
def run(self, q): """ Start packet sniffing and save the capture. :param Queue q: Queue to communicate with the main thread. """ VTLOG.info("Starting sniffer...") expr = 'host ' + self.conf['ip'] cap = self.sniff(iface=self.conf['iface'], filter=expr) location = self.conf['tempdir'] + self.conf['num'] + '.cap' wrpcap(location, cap) q.put(location) VTLOG.info("Sniffer stopped")
def run(self): """ Run registered measures. For each measure in :attr:`measures`, this method calls :meth:`Measure.calculate`. :returns: The list of measures. :rtype: list """ measures = [] for measure in self.measures: VTLOG.info("- Measuring: " + measure.data['name']) try: measures.append(measure.calculate()) except Exception, e: VTLOG.error(e)
def getBSm(self, measures): """ Get bit-stream measures. :param measures: Selected bit-stream measures. :type measures: string or list :returns: Calculated bit-stream measures. :rtype: list """ VTLOG.info("----------getBSm-----------") measures = BSmeter(measures, self.codecdata).run() VTLOG.info("---------------------------") return measures
def getQoSm(self, measures): """ Get QoS measures. :param measures: Selected QoS measures. :type measures: string or list :returns: Calculated QoS measures. :rtype: list """ VTLOG.info("----------getQoSm----------") measures = QoSmeter(measures, self.packetdata).run() VTLOG.info("---------------------------") return measures
def onCloseWindow(self, event): """ Show a dialog to verify exit. """ # dialog to verify exit (including menuExit) dlg = wx.MessageDialog(self, "Do you want to exit?", "Exit", wx.YES_NO | wx.ICON_QUESTION) result = dlg.ShowModal() dlg.Destroy() if result == wx.ID_YES: try: self.pipeline.set_state(STATE_NULL) except: pass VTLOG.removeHandler(self.hdlr) self.Destroy() # frame
def __init__(self, selected, data): """ **On init:** Register selected bit-stream measures. :param selected: Selected bit-stream measures. :type selected: string or list :param tuple data: Collected bit-stream parameters. """ Meter.__init__(self) VTLOG.info("Starting BSmeter...") if 'streameye' in selected: self.measures.append(StreamEye(data)) if 'refstreameye' in selected: self.measures.append(RefStreamEye(data)) if 'gop' in selected: self.measures.append(GOP(data)) if 'iflr' in selected: self.measures.append(IFrameLossRate(data))
def __loadData(self, videodata, size, codec): """ Load raw video data and coded video data. :param videodata: (see :attr:`VideoTester.gstreamer.RTSPclient.files`) :returns: Coded video data object (see :class:`VideoTester.video.YUVvideo`) and raw video data object (see :class:`VideoTester.video.CodedVideo`). :rtype: tuple """ VTLOG.info("Loading videos...") from VideoTester.video import YUVvideo, CodedVideo codecdata = {} rawdata = {} for x in videodata.keys(): if x != 'original': codecdata[x] = CodedVideo(videodata[x][0], codec) rawdata[x] = YUVvideo(videodata[x][1], size) VTLOG.info("+++") return codecdata, rawdata
def extract(p): """ Extract information from a UDP packet. :param Packet p: UDP packet. """ ptype = ord(str(p[UDP].payload)[1]) & 0x7F #Delete RTP marker p[UDP].decode_payload_as(RTP) if ptype == self.ptype: #Avoid duplicates while running on loopback interface if p[RTP].sequence not in self.sequences: self.lengths.append(p.len) self.times.append(p.time) self.sequences.append(p[RTP].sequence + self.__add) self.timestamps.append(p[RTP].timestamp) VTLOG.debug("UDP/RTP packet found. Sequence: " + str(p[RTP].sequence)) if p[RTP].sequence == 65535: self.__add += 65536
def __init__(self, file, gui=False): """ **On init:** Some initialization code. :param file: Path to a configuration file (string) or parsed configuration file (dictionary). :type file: string or dictionary :param boolean gui: True if :class:`Client` is called from GUI. False otherwise. :raises: Bad configuration file or path. .. warning:: If ``gui == True``, `file` MUST be a dictionary. Otherwise, `file` MUST be a string. """ VT.__init__(self) from os.path import exists from VideoTester.config import TEMP, makeDir if gui: self.conf = file else: try: #: Dictionary of configuration options. self.conf = dict(self.parseConf(file, "client")) except: VTLOG.error("Bad configuration file or path") exit() #: Path to the selected video. self.video = '/'.join([self.path, dict(self.videos)[self.conf['video']]]) self.conf['tempdir'] = TEMP + self.conf['video'] + '_' + self.conf['codec'] + '_' + self.conf['bitrate'] + '_' + self.conf['framerate'] + '_' + self.conf['protocols'] + '/' makeDir(self.conf['tempdir']) i , j = 0, True while j and i < 100: if i < 10: num = '0' + str(i) else: num = str(i) i = i + 1 j = exists(self.conf['tempdir'] + num + '.yuv') if j: VTLOG.error("The TEMP directory is full") exit() #: Numerical prefix for temporary files. self.conf['num'] = num
def __init__(self, selected, data): """ **On init:** Register selected video quality measures. :param selected: Selected video quality measures. :type selected: string or list :param tuple data: Collected QoS + bit-stream + video parameters. """ Meter.__init__(self) VTLOG.info("Starting VQmeter...") if 'psnr' in selected: self.measures.append(PSNR(data)) if 'ssim' in selected: self.measures.append(SSIM(data)) if 'g1070' in selected: self.measures.append(G1070(data)) if 'psnrtomos' in selected: self.measures.append(PSNRtoMOS(data)) if 'miv' in selected: self.measures.append(MIV(data))
def __events(self, bus, msg): """ Event handler. :param bus: Gstreamer bus object. :param msg: Gstreamer message object. :returns: True. :rtype: boolean """ t = msg.type if t == MESSAGE_EOS: self.pipeline.set_state(STATE_PAUSED) sleep(0.5) self.pipeline.set_state(STATE_READY) self.pipeline.set_state(STATE_NULL) VTLOG.debug("GStreamer: MESSAGE_EOS received") self.loop.quit() elif t == MESSAGE_ERROR: self.pipeline.set_state(STATE_PAUSED) sleep(0.5) self.pipeline.set_state(STATE_READY) self.pipeline.set_state(STATE_NULL) e, d = msg.parse_error() VTLOG.error("GStreamer: MESSAGE_ERROR received") VTLOG.error(e) self.loop.quit() return True
def __init__(self): """ **On init:** Parse the `video` section. .. warning:: This section MUST be present in the default configuration file (see :const:`VideoTester.config.CONF`) and MUST contain the same videos at the client and the server. :raises: Bad configuration file or path. """ from VideoTester.config import CONF try: #: List of ``(id, name)`` pairs for each available video. self.videos = self.parseConf(CONF, "video") if self.videos[0][0] != 'path': raise #: Path to the video directory. self.path = self.videos[0][1] self.videos.pop(0) except: VTLOG.error("Bad '" + CONF + "' file or path") exit()
def __parseUDP(self): """ Parse RTP over UDP session. """ def extract(p): """ Extract information from a UDP packet. :param Packet p: UDP packet. """ ptype = ord(str(p[UDP].payload)[1]) & 0x7F #Delete RTP marker p[UDP].decode_payload_as(RTP) if ptype == self.ptype: #Avoid duplicates while running on loopback interface if p[RTP].sequence not in self.sequences: self.lengths.append(p.len) self.times.append(p.time) self.sequences.append(p[RTP].sequence + self.__add) self.timestamps.append(p[RTP].timestamp) VTLOG.debug("UDP/RTP packet found. Sequence: " + str(p[RTP].sequence)) if p[RTP].sequence == 65535: self.__add += 65536 play = False for p in self.cap: if p.haslayer(IP): if (str(p).find("PAUSE") != -1) and play: VTLOG.debug("PAUSE found!") break if not play: play = self.__prepare(p) elif play and (p[IP].src == self.conf['ip']) and ( p.haslayer(UDP)) and (str(p).find("GStreamer") == -1): if (p.sport == self.sport) and (p.dport == self.dport): extract(p) bubbleSort(self.sequences, self.times, self.timestamps) VTLOG.debug("Sequence list sorted")
def stop(self, bitrate, framerate): """ Stop an RTSP server with a given bitrate and framerate (if no remaining clients) or remove a client (if remaining clients). :param bitrate: The bitrate (in kbps). :type bitrate: string or integer :param framerate: The framerate (in fps). :type framerate: string or integer :returns: True. :rtype: boolean """ key = str(bitrate) + ' kbps - ' + str(framerate) + ' fps' self.servers[key]['clients'] = self.servers[key]['clients'] - 1 if self.servers[key]['clients'] == 0: self.servers[key]['server'].terminate() self.servers[key]['server'].join() VTLOG.info(key + " server: last client disconnected and server stopped") self.servers.pop(key) else: VTLOG.info(key + " server: client disconnected. Remaining: " + str(self.servers[key]['clients'])) return True
def reference(self): """ Make the reference videos. :returns: Paths to video files (see :attr:`files`) and video size (see :attr:`size`). :rtype: tuple """ VTLOG.info("Making reference...") self.pipeline = parse_launch( 'filesrc name=source ! decodebin ! videorate ! video/x-raw-yuv,framerate=' + self.conf['framerate'] + '/1 ! filesink name=sink1') source = self.pipeline.get_by_name('source') sink1 = self.pipeline.get_by_name('sink1') location = self.video self.files['original'].append(location) source.props.location = location location = self.conf['tempdir'] + self.conf['num'] + '_ref_original.yuv' self.files['original'].append(location) sink1.props.location = location self.__play() self.pipeline = parse_launch('filesrc name=source ! decodebin ! videorate ! video/x-raw-yuv,framerate=' + self.conf['framerate'] + '/1 ! ' + self.encoder + ' bitrate=' + self.bitrate \ + ' ! tee name=t ! queue' + self.__add + ' ! filesink name=sink2 t. ! queue ! decodebin ! filesink name=sink3') source = self.pipeline.get_by_name('source') sink2 = self.pipeline.get_by_name('sink2') sink3 = self.pipeline.get_by_name('sink3') location = self.video source.props.location = location location = self.conf['tempdir'] + self.conf[ 'num'] + '_ref.' + self.conf['codec'] self.files['coded'].append(location) sink2.props.location = location location = self.conf['tempdir'] + self.conf['num'] + '_ref.yuv' self.files['coded'].append(location) sink3.props.location = location self.__play() VTLOG.info("Reference made") return self.files, self.size
def __init__(self, selected, data): """ **On init:** Register selected QoS measures. :param selected: Selected QoS measures. :type selected: string or list :param tuple data: Collected QoS parameters. """ Meter.__init__(self) VTLOG.info("Starting QoSmeter...") if 'latency' in selected: self.measures.append(Latency(data)) if 'delta' in selected: self.measures.append(Delta(data)) if 'jitter' in selected: self.measures.append(Jitter(data)) if 'skew' in selected: self.measures.append(Skew(data)) if 'bandwidth' in selected: self.measures.append(Bandwidth(data)) if 'plr' in selected: self.measures.append(PacketLossRate(data)) if 'pld' in selected: self.measures.append(PacketLossDist(data))
def extract(p): """ Extract many RTSP packets from a TCP stream recursively. :param Packet p: TCP stream. """ fin = False a = p[RTSPi].length b = p[RTSPi].payload c = str(b)[0:a] loss = c.find('PACKETLOSS') if loss == -1: #No loss: look inside then ptype = ord(str( p[RTSPi].payload)[1]) & 0x7F #Delete RTP marker if ptype == self.ptype: aux = str(p).split('ENDOFPACKET') p[RTSPi].decode_payload_as(RTP) #Avoid duplicates while running on loopback interface if p[RTP].sequence not in self.sequences: self.lengths.append(int(aux[2])) self.times.append(float(aux[1]) / 1000000) self.sequences.append(p[RTP].sequence + self.__add) self.timestamps.append(p[RTP].timestamp) VTLOG.debug("TCP/RTP packet found. Sequence: " + str(p[RTP].sequence)) if p[RTP].sequence == 65535: self.__add += 65536 else: #Avoid PACKETLOSS a = loss + len('PACKETLOSS') VTLOG.debug("PACKETLOSS!") p = RTSPi(str(b)[a:len(b)]) ptype = ord(str(p[RTSPi].payload)[1]) & 0x7F #Let's find the next RTSP packet while not fin and not ((p[RTSPi].magic == 0x24) and (p[RTSPi].channel == 0x00) and (ptype == self.ptype)): stream = str(p) if stream.find('PACKETLOSS') == 0: #Avoid PACKETLOSS stream = stream[len('PACKETLOSS'):len(stream)] VTLOG.debug("PACKETLOSS!") else: #Find next packet stream = stream[1:len(stream)] if len(stream) > 5: p = RTSPi(stream) ptype = ord(str(p[RTSPi].payload)[1]) & 0x7F else: #Yep! We're done! fin = True if not fin: extract(p)
def parsePkts(self): """ Parse packets and extract :attr:`lengths`, :attr:`times`, :attr:`sequences`, :attr:`timestamps` and :attr:`ping`. :returns: :attr:`lengths`, :attr:`times`, :attr:`sequences`, :attr:`timestamps` and :attr:`ping`. :rtype: tuple """ VTLOG.info("Starting packet parser...") if self.conf['protocols'] == "tcp": self.__parseTCP() else: self.__parseUDP() self.__normalize() a = str(self.sequences[-1] - len(self.sequences) + 1) b = str(len(self.sequences)) VTLOG.debug(b + " RTP packets received, " + a + " losses") VTLOG.info("Packet parser stopped") return self.lengths, self.times, self.sequences, self.timestamps, self.ping
def sniff(count=0, prn=None, lfilter=None, *arg, **karg): """ ``scapy.sendrecv.sniff()`` Scapy function modification. It stops when an RTSP *TEARDOWN* packet is found. :param integer count: Number of packets to capture (0 means infinite). :param prn: Function to apply to each packet. If something is returned, it is displayed. :param lfilter: Python function applied to each packet to determine if any further action is requireds. :returns: A list of packets. :rtype: list """ c = 0 L2socket = conf.L2listen s = L2socket(type=ETH_P_ALL, *arg, **karg) lst = [] remain = None VTLOG.debug("Sniffer: loop started. Sniffing...") while 1: sel = select([s], [], [], remain) if s in sel[0]: p = s.recv(MTU) if p is None: break #This line fixes the lack of timing accuracy p.time = time() if lfilter and not lfilter(p): continue lst.append(p) aux = str(p) #Look for a TEARDOWN packet to stop the loop if (aux.find("TEARDOWN") != -1) and (aux.find("Public:") == -1): VTLOG.debug("TEARDOWN found!") break c += 1 if prn: r = prn(p) if r is not None: print r if count > 0 and c >= count: break s.close() VTLOG.debug("Sniffer: loop terminated") return PacketList(lst, "Sniffed")
def run(self, bitrate, framerate): """ Run a subprocess for an RTSP server with a given bitrate and framerate (if not running) or add a client (if running). :param bitrate: The bitrate (in kbps). :type bitrate: string or integer :param framerate: The framerate (in fps). :type framerate: string or integer :returns: The RTSP server port. :rtype: integer :raises OSError: An error ocurred while running subprocess. """ from multiprocessing import Process from VideoTester.gstreamer import RTSPserver key = str(bitrate) + ' kbps - ' + str(framerate) + ' fps' if key in self.servers: self.servers[key]['clients'] = self.servers[key]['clients'] + 1 else: self.servers[key] = dict() while not self.__freePort(): self.port = self.port + 1 self.servers[key]['server'] = Process(target=RTSPserver(self.port, bitrate, framerate, self.path, self.videos).run) try: self.servers[key]['server'].start() except e: VTLOG.error(e) self.servers[key]['server'].terminate() self.servers[key]['server'].join() exit() self.servers[key]['port'] = self.port self.servers[key]['clients'] = 1 VTLOG.info("RTSP Server running!") VTLOG.info("PID: " + str(self.servers[key]['server'].pid) + ", " + key + " server, connected clients: " + str(self.servers[key]['clients'])) return self.servers[key]['port']
def __init__(self, *args, **kwds): VT.__init__(self) # begin wxGlade: VTframe.__init__ kwds["style"] = wx.DEFAULT_FRAME_STYLE wx.Frame.__init__(self, *args, **kwds) self.SetIcon(getVTIcon()) # Menu Bar self.vtmenubar = wx.MenuBar() wxglade_tmp_menu = wx.Menu() self.m_files = wxglade_tmp_menu.Append(wx.ID_OPEN, "&Open files...", "Select Pickle files to plot") wxglade_tmp_menu.AppendSeparator() self.m_exit = wxglade_tmp_menu.Append(wx.ID_EXIT, "E&xit", "Exit program") self.vtmenubar.Append(wxglade_tmp_menu, "&File") wxglade_tmp_menu = wx.Menu() self.m_run = wxglade_tmp_menu.Append(wx.NewId(), "&Run...", "Run test") self.vtmenubar.Append(wxglade_tmp_menu, "R&un") wxglade_tmp_menu = wx.Menu() self.m_about = wxglade_tmp_menu.Append(wx.ID_ABOUT, "&About", "About this program") self.vtmenubar.Append(wxglade_tmp_menu, "&Help") self.SetMenuBar(self.vtmenubar) # Menu Bar end self.vtstatusbar = self.CreateStatusBar(1, 0) self.tabs = wx.Notebook(self, -1, style=0) self.options_tab = wx.Panel(self.tabs, -1) self.video_label = wx.StaticText(self.options_tab, -1, "Choose a video:") self.video = wx.Choice(self.options_tab, -1, choices=[x[0] for x in self.videos]) self.codec_label = wx.StaticText(self.options_tab, -1, "Choose a codec:") self.codec = wx.Choice(self.options_tab, -1, choices=["h263", "h264", "mpeg4", "theora"]) self.bitrate_label = wx.StaticText(self.options_tab, -1, "Select the bitrate:") self.bitrate = wx.Slider(self.options_tab, -1, 128, 64, 1024, style=wx.SL_HORIZONTAL|wx.SL_LABELS) self.framerate_label = wx.StaticText(self.options_tab, -1, "Select the framerate:") self.framerate = wx.Slider(self.options_tab, -1, 25, 1, 100, style=wx.SL_HORIZONTAL|wx.SL_LABELS) self.sizer_2_staticbox = wx.StaticBox(self.options_tab, -1, "Video options:") self.iface_label = wx.StaticText(self.options_tab, -1, "Interface:") self.iface = wx.TextCtrl(self.options_tab, -1, "eth0") self.ip_label = wx.StaticText(self.options_tab, -1, "Server IP:") self.ip = wx.TextCtrl(self.options_tab, -1, "192.168.229.131") self.port_label = wx.StaticText(self.options_tab, -1, "Server port:") self.port = wx.TextCtrl(self.options_tab, -1, "8000") self.protocols = wx.RadioBox(self.options_tab, -1, "Protocol:", choices=["UDP-unicast", "TCP", "UDP-multicast"], majorDimension=3, style=wx.RA_SPECIFY_COLS) self.sizer_3_staticbox = wx.StaticBox(self.options_tab, -1, "Net options:") self.latency = wx.CheckBox(self.options_tab, -1, "Latency") self.delta = wx.CheckBox(self.options_tab, -1, "Delta") self.jitter = wx.CheckBox(self.options_tab, -1, "Jitter") self.skew = wx.CheckBox(self.options_tab, -1, "Skew") self.bandwidth = wx.CheckBox(self.options_tab, -1, "Bandwidth") self.plr = wx.CheckBox(self.options_tab, -1, "Packet Loss Rate") self.pld = wx.CheckBox(self.options_tab, -1, "Packet Loss Distribution") self.sizer_15_staticbox = wx.StaticBox(self.options_tab, -1, "QoS measures:") self.seye = wx.CheckBox(self.options_tab, -1, "Stream Eye") self.refseye = wx.CheckBox(self.options_tab, -1, "refStream Eye") self.gop = wx.CheckBox(self.options_tab, -1, "GOP size") self.iflr = wx.CheckBox(self.options_tab, -1, "I Frame Loss Rate") self.sizer_16_staticbox = wx.StaticBox(self.options_tab, -1, "BitStream measures:") self.psnr = wx.CheckBox(self.options_tab, -1, "PSNR") self.ssim = wx.CheckBox(self.options_tab, -1, "SSIM") self.g1070 = wx.CheckBox(self.options_tab, -1, "G.1070") self.psnrtomos = wx.CheckBox(self.options_tab, -1, "PSNRtoMOS") self.miv = wx.CheckBox(self.options_tab, -1, "MIV") self.sizer_5_staticbox = wx.StaticBox(self.options_tab, -1, "Video quality measures:") self.log_tab = wx.Panel(self.tabs, -1) self.log = wx.TextCtrl(self.log_tab, -1, "Log messages:\n----------------\n", style=wx.TE_MULTILINE|wx.TE_READONLY) self.results_tab = PlotNotebook(self.tabs) self.video_tab = wx.Panel(self.tabs, -1) self.label_2 = wx.StaticText(self.video_tab, -1, "Play videos:") self.play_video = wx.Button(self.video_tab, -1, "Play", name="playvideo") self.__set_properties() self.__do_layout() self.Bind(wx.EVT_MENU, self.onOpen, self.m_files) self.Bind(wx.EVT_MENU, self.onExit, self.m_exit) self.Bind(wx.EVT_MENU, self.onRun, self.m_run) self.Bind(wx.EVT_MENU, self.onAbout, self.m_about) self.Bind(wx.EVT_CLOSE, self.onCloseWindow) self.play_video.Bind(wx.EVT_BUTTON, self.onPlay) # Logging self.log_text = '' def update(x): self.log_text += x+'\n' formatter = logging.Formatter("[%(asctime)s VTClient] %(levelname)s : %(message)s") self.hdlr = FuncLog(update) self.hdlr.setLevel(logging.DEBUG) self.hdlr.setFormatter(formatter) VTLOG.addHandler(self.hdlr) self.Bind(wx.EVT_IDLE, self.onIdle)
def __prepare(self, p): """ Pre-process capture file. This method parses RTSP information and extracts :attr:`ping`, :attr:`ptype` and :attr:`clock`. :returns: True when a RTSP *PLAY* packet is found. :rtype: boolean """ if p.haslayer(ICMP): self.ping[p[ICMP].seq][p[ICMP].type] = p.time elif str(p).find("Content-Type: application/sdp") != -1: lines = str(p[TCP].payload).splitlines() for line in lines: if line.find("m=video") != -1: fields = line.split(" ") self.ptype = int(fields[-1]) VTLOG.debug("Payload type found! Value: " + str(self.ptype)) for line in lines: if line.find("rtpmap:" + str(self.ptype)) != -1: fields = line.split("/") self.clock = int(fields[-1]) VTLOG.debug("Clock rate found! Value: " + str(self.clock)) elif (str(p).find("Transport: RTP") != -1) and (str(p).find('mode="PLAY"') != -1): if str(p).find("RTP/AVP/TCP") != -1: self.sport = int(p.sport) VTLOG.debug("Source port found! Value: " + str(self.sport)) self.dport = int(p.dport) VTLOG.debug("Destination port found! Value: " + str(self.dport)) else: fields = str(p[TCP].payload).split(";") for field in fields: if field.find("server_port=") != -1: self.sport = int(field[12:field.index('-')]) VTLOG.debug("Source port found! Value: " + str(self.sport)) elif field.find("client_port=") != -1: self.dport = int(field[12:field.index('-')]) VTLOG.debug("Destination port found! Value: " + str(self.dport)) elif field.find("port=") != -1: self.sport = int(field[5:field.index('-')]) self.dport = self.sport VTLOG.debug("Source/destination port found! Value: " + str(self.sport)) elif (str(p).find("PLAY") != -1) and (str(p).find("Public:") == -1): self.play = True VTLOG.debug("PLAY found!") if self.play and self.sport and self.dport: play = True else: play = False return play
def __parseTCP(self): """ Parse RTP over TCP session. """ def extract(p): """ Extract many RTSP packets from a TCP stream recursively. :param Packet p: TCP stream. """ fin = False a = p[RTSPi].length b = p[RTSPi].payload c = str(b)[0:a] loss = c.find('PACKETLOSS') if loss == -1: #No loss: look inside then ptype = ord(str( p[RTSPi].payload)[1]) & 0x7F #Delete RTP marker if ptype == self.ptype: aux = str(p).split('ENDOFPACKET') p[RTSPi].decode_payload_as(RTP) #Avoid duplicates while running on loopback interface if p[RTP].sequence not in self.sequences: self.lengths.append(int(aux[2])) self.times.append(float(aux[1]) / 1000000) self.sequences.append(p[RTP].sequence + self.__add) self.timestamps.append(p[RTP].timestamp) VTLOG.debug("TCP/RTP packet found. Sequence: " + str(p[RTP].sequence)) if p[RTP].sequence == 65535: self.__add += 65536 else: #Avoid PACKETLOSS a = loss + len('PACKETLOSS') VTLOG.debug("PACKETLOSS!") p = RTSPi(str(b)[a:len(b)]) ptype = ord(str(p[RTSPi].payload)[1]) & 0x7F #Let's find the next RTSP packet while not fin and not ((p[RTSPi].magic == 0x24) and (p[RTSPi].channel == 0x00) and (ptype == self.ptype)): stream = str(p) if stream.find('PACKETLOSS') == 0: #Avoid PACKETLOSS stream = stream[len('PACKETLOSS'):len(stream)] VTLOG.debug("PACKETLOSS!") else: #Find next packet stream = stream[1:len(stream)] if len(stream) > 5: p = RTSPi(stream) ptype = ord(str(p[RTSPi].payload)[1]) & 0x7F else: #Yep! We're done! fin = True if not fin: extract(p) def fillGaps(seqlist, lenlist): """ Locate packet losses. :param list seqlist: List of RTP sequence numbers. :param list lenlist: List of packet lengths. :returns: List of losses (0 -> no loss, 1 -> loss). :rtype: list """ fill = [0 for i in range(0, len(seqlist))] for i in range(0, len(seqlist) - 1): if seqlist[i] + lenlist[i] < seqlist[i + 1]: fill[i] = 1 return fill play = False packetlist = [] seqlist = [] lenlist = [] for p in self.cap: if p.haslayer(IP): if (str(p).find("PAUSE") != -1) and play: VTLOG.debug("PAUSE found!") break if not play: play = self.__prepare(p) #Packets from server, with TCP layer. Avoid ACK's. Avoid RTSP packets elif play and (p[IP].src == self.conf['ip']) and p.haslayer(TCP) and ( len(p) > 66) and (str(p).find("RTSP/1.0") == -1): if (p.sport == self.sport) and (p.dport == self.dport): packetlist.append(p) seqlist.append(p[TCP].seq) lenlist.append(len(p[TCP].payload)) VTLOG.debug("TCP packet appended. Sequence: " + str(p[TCP].seq)) bubbleSort(seqlist, packetlist, lenlist) VTLOG.debug("Sequence list sorted") #Locate packet losses fill = fillGaps(seqlist, lenlist) stream = '' for i, p in enumerate(packetlist): stream = ''.join([stream, str(p[TCP].payload)]) #Mark ENDOFPACKET and save time and length stream = ''.join([stream, 'ENDOFPACKET']) stream = ''.join([stream, str(int(p.time * 1000000))]) stream = ''.join([stream, 'ENDOFPACKET']) stream = ''.join([stream, str(p.len)]) stream = ''.join([stream, 'ENDOFPACKET']) if fill[i]: #Mark PACKETLOSS VTLOG.debug("PACKETLOSS!") stream = ''.join([stream, 'PACKETLOSS']) VTLOG.debug("TCP payloads assembled") stream = RTSPi(stream) extract(stream)
def run(self): """ Run the client and perform all the operations: * Connect to the server. * Receive video while sniffing packets. * Close connection. * Process data and extract information. * Run meters. :returns: A list of measures (see :attr:`VideoTester.measures.core.Meter.measures`) and the path to the temporary directory plus files prefix: ``<path-to-tempdir>/<prefix>``. :rtype: tuple """ VTLOG.info("Client running!") VTLOG.info("XMLRPC Server at " + self.conf['ip'] + ':' + self.conf['port']) VTLOG.info("Evaluating: " + self.conf['video'] + " + " + self.conf['codec'] + " at " + self.conf['bitrate'] + " kbps and " + self.conf['framerate'] + " fps under " + self.conf['protocols']) from xmlrpclib import ServerProxy from scapy.all import rdpcap from multiprocessing import Process, Queue from VideoTester.gstreamer import RTSPclient from VideoTester.sniffer import Sniffer from VideoTester.measures.qos import QoSmeter from VideoTester.measures.bs import BSmeter from VideoTester.measures.vq import VQmeter try: server = ServerProxy('http://' + self.conf['ip'] + ':' + self.conf['port']) self.conf['rtspport'] = str(server.run(self.conf['bitrate'], self.conf['framerate'])) except: VTLOG.error("Bad IP or port") exit() sniffer = Sniffer(self.conf) rtspclient = RTSPclient(self.conf, self.video) q = Queue() child = Process(target=sniffer.run, args=(q,)) try: child.start() self.__ping() rtspclient.receiver() sniffer.cap = rdpcap(q.get()) child.join() except KeyboardInterrupt: VTLOG.warning("Keyboard interrupt!") server.stop(self.conf['bitrate'], self.conf['framerate']) child.terminate() child.join() exit() server.stop(self.conf['bitrate'], self.conf['framerate']) videodata, size = rtspclient.reference() conf = {'codec':self.conf['codec'], 'bitrate':float(self.conf['bitrate']), 'framerate':float(self.conf['framerate']), 'size':size} packetdata = sniffer.parsePkts() codecdata, rawdata = self.__loadData(videodata, size, self.conf['codec']) qosm = QoSmeter(self.conf['qos'], packetdata).run() bsm = BSmeter(self.conf['bs'], codecdata).run() vqm = VQmeter(self.conf['vq'], (conf, rawdata, codecdata, packetdata)).run() self.__saveMeasures(qosm + bsm + vqm) VTLOG.info("Client stopped!") return qosm + bsm + vqm, self.conf['tempdir'] + self.conf['num']