class PerformanceMetric: def __init__(self): self.switches = {"VIDEO": Trace("seconds", "bps"), "AUDIO": Trace("seconds", "bps")} self.buffer_levels = {"VIDEO": BufferLevelMetric(), "AUDIO": BufferLevelMetric()} self.bps_history = Trace("seconds", "bps") self.bps_history.append(0, 0) self.buffer_levels["VIDEO"].append(0, 0) self.buffer_levels["AUDIO"].append(0, 0) self.switches["VIDEO"].append(0, 0) self.switches["AUDIO"].append(0, 0) @property def underrun_count(self): return self.buffer_levels["VIDEO"].underrun_count + self.buffer_levels["AUDIO"].underrun_count def min_buffer_level(self): return min(self.buffer_levels["VIDEO"].current_value, self.buffer_levels["AUDIO"].current_value) """ So far, use reciproc of underruns to give a score. 1.0 perfect score """ def score(self): return 1.0 / (self.underrun_count + 1) def print_stats(self): print("Score: %.4f" % self.score()) print("Underruns: %d" % self.underrun_count) print self.switches["VIDEO"]
class PerformanceMetric: def __init__(self): self.switches = {"VIDEO": Trace("seconds", "bps"), "AUDIO": Trace("seconds", "bps")} self.buffer_levels = {"VIDEO": BufferLevelMetric(), "AUDIO": BufferLevelMetric()} self.bps_history = Trace("seconds", "bps") self.bps_history.append(0, 0) self.buffer_levels["VIDEO"].append(0, 0) self.buffer_levels["AUDIO"].append(0, 0) self.switches["VIDEO"].append(0, 0) self.switches["AUDIO"].append(0, 0) @property def underrun_count(self): return self.buffer_levels["VIDEO"].underrun_count + self.buffer_levels["AUDIO"].underrun_count def min_buffer_level(self): return min(self.buffer_levels["VIDEO"].current_value, self.buffer_levels["AUDIO"].current_value) """ So far, use reciproc of underruns to give a score. 1.0 perfect score """ def score(self): return 1.0 / (self.underrun_count + 1) def print_stats(self): print("Score: %.4f" % self.score()) print("Underruns: %d" % self.underrun_count) print self.switches["VIDEO"]
class BufferLevelMetric(Trace): def __init__(self): Trace.__init__(self, "seconds", "seconds") self._level = 0.0 # in seconds self._underruns = Trace("seconds", "underrun duration (seconds)") @property def underrun_count(self): return self._underruns.count def increase_by(self, absolute_time, level_increase): val = self.current_value if val is None: val = 0.0 self.append(absolute_time, val + level_increase) def decrease_by(self, absolute_time, level_decrease): val = self.current_value if val is None: val = 0.0 val -= level_decrease if val <= 0.0: # record an underrun [time, duration_of_underrun] self._underruns.append(absolute_time, abs(val)) # clamp level to 0.0 val = 0.0 self.append(absolute_time, val) @property def level(self): """ Alias for current_value or current_y_value :return: current_y_value """ return self.current_value def __unicode__(self): val = self.current_value if val is not None: return "BufferLevel(t=%.2fs): %.2fs" % (self.current_x_value, self.current_value) else: return "BufferLevel(t=0): 0" def __str__(self): return self.__unicode__()
class BufferLevelMetric(Trace): def __init__(self): Trace.__init__(self, "seconds", "seconds") self._level = 0.0 # in seconds self._underruns = Trace("seconds", "underrun duration (seconds)") @property def underrun_count(self): return self._underruns.count def increase_by(self, absolute_time, level_increase): val = self.current_value if val is None: val = 0.0 self.append(absolute_time, val + level_increase) def decrease_by(self, absolute_time, level_decrease): val = self.current_value if val is None: val = 0.0 val -= level_decrease if val <= 0.0: # record an underrun [time, duration_of_underrun] self._underruns.append(absolute_time, abs(val)) # clamp level to 0.0 val = 0.0 self.append(absolute_time, val) @property def level(self): """ Alias for current_value or current_y_value :return: current_y_value """ return self.current_value def __unicode__(self): val = self.current_value if val is not None: return "BufferLevel(t=%.2fs): %.2fs" % (self.current_x_value, self.current_value) else: return "BufferLevel(t=0): 0" def __str__(self): return self.__unicode__()
class CastLabsAdaptation(Adaptation): MA_SIZE = 5 def __init__(self, bitrates): Adaptation.__init__(self, bitrates) self.max_seconds = 50.0 self.level_low_seconds = 10.0 # critical buffer level. Fill as fast as possible until level_high_seconds reached if below this value self.level_high_seconds = 30.0 # stable buffer level. Try to maintain current bitrate or improve it self.bps_history = Trace("time", "bps") self.bitrate_selections = {"AUDIO": Trace("time", "Audio-Bitrate selections"), "VIDEO": Trace("time", "Video-Bitrate selections")} self.sim_state = None # self.bitrate_selections["VIDEO"].append(-1, 0) # self.bitrate_selections["AUDIO"].append(-1, 0) self.my_bps = Trace("seconds", "bps") self.ma4_filter = alg.IterativeMovingAverage(4) self.ma10_filter = alg.IterativeMovingAverage(10) self.ma50_filter = alg.IterativeMovingAverage(80) self. last_index = 0 def current_buffer_level(self, type_str): return self.simulator.buffer_level(type_str) def min_buffer_level(self): return self.sim_state.metric.min_buffer_level() def clamp(self, val, range): return min(max(range[0], val), range[1]) def next_bitrate(self, type_str): index = 0 avg = 0 clamp_range = [0, len(self.bitrates[type_str]) - 1] if self.bps_history.length != 0: avg = self.ma50_filter(self.bps_history.current_value) index = self.clamp(bisect.bisect(self.bitrates[type_str], avg) - 1, clamp_range) self.last_index = index self.my_bps.append(self.sim_state.t, avg) bps = self.bitrates[type_str][index] return bps def is_buffering(self): if self.sim_state is None: return False else: return self.min_buffer_level() < 3.0 def evaluate(self, next_segment_type, seg_choices, state): """ Updates internal performance statistics :param state: current state of the player simulator including buffer levels and http statistics :type state: dict of states :param next_segment_type: Type of the next segment, "AUDIO" or "VIDEO" :type next_segment_type: string :return: Next segment that should be downloaded """ self.sim_state = state if state.http is not None: self.bps_history.append(state.t, state.http.bps) bps = self.next_bitrate(next_segment_type) if bps != self.bitrate_selections[next_segment_type].current_value: if next_segment_type == "VIDEO" and self.my_bps.length > 0: print "SWITCH @ t=%.2f: %d -> %d [Avg: %.2f / idx: %d]" % (self.sim_state.t, self.bitrate_selections[next_segment_type].current_value, bps, self.my_bps.current_value, self.last_index) print self.bitrates[next_segment_type][self.last_index] self.bitrate_selections[next_segment_type].append(state.t, bps) print self.bitrate_selections[next_segment_type].x_data print self.bitrate_selections[next_segment_type].y_data return Segment.find_segment_for_bitrate(seg_choices, bps)