Exemple #1
0
 def _setup_audio_vis(self):
     self.audio_timeline = None
     self.audio_viz_trans = Audio_Viz_Transform(self.g_pool.rec_dir)
     self.audio_viz_data = None
     self.log_scale = False
     self.xlim = (self.g_pool.timestamps[0], self.g_pool.timestamps[-1])
     self.ylim = (0, 210)
Exemple #2
0
    def cache_audio_data(self):
        if self.get_audio_data:
            if self.audio_viz_trans is None:
                try:
                    self.audio_viz_trans = Audio_Viz_Transform(self.g_pool.rec_dir)
                except FileNotFoundError:
                    self.get_audio_data = False
                    return False

            a_levels, finished = self.audio_viz_trans.get_data()
            if a_levels is not None:
                self.cache["audio_level"] = a_levels
            if a_levels is None or finished:
                self.get_audio_data = False
            return True
        else:
            return False
Exemple #3
0
    def cache_audio_data(self):
        if self.get_audio_data:
            if self.audio_viz_trans is None:
                try:
                    self.audio_viz_trans = Audio_Viz_Transform(self.g_pool.rec_dir)
                except FileNotFoundError:
                    self.get_audio_data = False
                    return False

            a_levels, finished = self.audio_viz_trans.get_data()
            if a_levels is not None:
                self.cache["audio_level"] = a_levels
            if a_levels is None or finished:
                self.get_audio_data = False
            return True
        else:
            return False
Exemple #4
0
    def __init__(self, g_pool):
        super().__init__(g_pool)

        self.play = False
        self.pa_stream = None
        self.audio_sync = 0.0
        self.audio_delay = 0.0
        self.audio_container = None
        self.audio_stream = None
        self.next_audio_frame = None
        self.audio_start_pts = 0
        self.check_ts_consistency = False
        self.req_audio_volume = 1.0
        self.current_audio_volume = 1.0
        self.req_buffer_size_secs = 0.5
        self.audio_viz_trans = None
        audio_file = os.path.join(self.g_pool.rec_dir, "audio.mp4")
        if os.path.isfile(audio_file):
            self.audio_container = av.open(str(audio_file))
            try:
                self.audio_stream = next(
                    s for s in self.audio_container.streams if s.type == "audio"
                )
                logger.debug("loaded audiostream: %s" % self.audio_stream)
            except StopIteration:
                self.audio_stream = None
                logger.debug("No audiostream found in media container")
        else:
            return
        if self.audio_stream is not None:
            self.audio_bytes_fifo = []
            audiots_path = os.path.splitext(audio_file)[0] + "_timestamps.npy"
            try:
                self.audio_timestamps = np.load(audiots_path)
            except IOError:
                self.audio_timestamps = None
                logger.warning("Could not load audio timestamps")
            self.next_audio_frame = self._next_audio_frame()
            self.audio_resampler = av.audio.resampler.AudioResampler(
                format=self.audio_stream.format.packed,
                layout=self.audio_stream.layout,
                rate=self.audio_stream.rate,
            )
            self.audio_paused = False
            af0, af1 = next(self.next_audio_frame), next(self.next_audio_frame)
            # Check pts

            self.audio_pts_rate = af0.samples  # af1.pts - af0.pts
            self.audio_start_pts = 0
            logger.debug(
                "audio_pts_rate = {} start_pts = {}".format(
                    self.audio_pts_rate, self.audio_start_pts
                )
            )

            if self.check_ts_consistency:
                print("**** Checking stream")
                for i, af in enumerate(self.next_audio_frame):
                    fnum = i + 2
                    if af.samples != af0.samples:
                        print("fnum {} samples = {}".format(fnum, af.samples))
                    if af.pts != self.audio_idx_to_pts(fnum):
                        print(
                            "af.pts = {} fnum = {} idx2pts = {}".format(
                                af.pts, fnum, self.audio_idx_to_pts(fnum)
                            )
                        )
                    if (
                        self.audio_timestamps[fnum]
                        != self.audio_timestamps[0]
                        + af.pts * self.audio_stream.time_base
                    ):
                        print(
                            "ts[0] + af.pts = {} fnum = {} timestamp = {}".format(
                                self.audio_timestamps[0]
                                + af.pts * self.audio_stream.time_base,
                                fnum,
                                self.audio_timestamps[fnum],
                            )
                        )
                print("**** Done")
            self.seek_to_audio_frame(0)

            logger.debug(
                "Audio file format {} chans {} rate {} framesize {}".format(
                    self.audio_stream.format.name,
                    self.audio_stream.channels,
                    self.audio_stream.rate,
                    self.audio_stream.frame_size,
                )
            )
            self.audio_start_time = 0
            self.audio_measured_latency = -1.0
            self.last_dac_time = 0
            self.filter_graph = None
            self.filter_graph_list = None
            try:
                self.pa = pa.PyAudio()
                self.pa_stream = self.pa.open(
                    format=self.pa.get_format_from_width(
                        self.audio_stream.format.bytes
                    ),
                    channels=self.audio_stream.channels,
                    rate=self.audio_stream.rate,
                    frames_per_buffer=self.audio_stream.frame_size,
                    stream_callback=self.audio_callback,
                    output=True,
                    start=False,
                )
                logger.debug(
                    "Audio output latency: {}".format(
                        self.pa_stream.get_output_latency()
                    )
                )
                self.audio_sync = self.pa_stream.get_output_latency()
                self.audio_reported_latency = self.pa_stream.get_output_latency()

            except ValueError:
                self.pa_stream = None

            self.audio_timeline = None

            self.audio_viz_trans = Audio_Viz_Transform(self.g_pool.rec_dir)
            self.audio_viz_data = None
            self.log_scale = False
            self.xlim = (self.g_pool.timestamps[0], self.g_pool.timestamps[-1])
            self.ylim = (0, 210)
Exemple #5
0
class Audio_Playback(System_Plugin_Base):
    """Calibrate using a marker on your screen
    We use a ring detector that moves across the screen to 9 sites
    Points are collected at sites not between
    """

    icon_chr = chr(0xE050)
    icon_font = "pupil_icons"

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

        self.play = False
        self.pa_stream = None
        self.audio_sync = 0.0
        self.audio_delay = 0.0
        self.audio_container = None
        self.audio_stream = None
        self.next_audio_frame = None
        self.audio_start_pts = 0
        self.check_ts_consistency = False
        self.req_audio_volume = 1.0
        self.current_audio_volume = 1.0
        self.req_buffer_size_secs = 0.5
        self.audio_viz_trans = None
        audio_file = os.path.join(self.g_pool.rec_dir, "audio.mp4")
        if os.path.isfile(audio_file):
            self.audio_container = av.open(str(audio_file))
            try:
                self.audio_stream = next(
                    s for s in self.audio_container.streams if s.type == "audio"
                )
                logger.debug("loaded audiostream: %s" % self.audio_stream)
            except StopIteration:
                self.audio_stream = None
                logger.debug("No audiostream found in media container")
        else:
            return
        if self.audio_stream is not None:
            self.audio_bytes_fifo = []
            audiots_path = os.path.splitext(audio_file)[0] + "_timestamps.npy"
            try:
                self.audio_timestamps = np.load(audiots_path)
            except IOError:
                self.audio_timestamps = None
                logger.warning("Could not load audio timestamps")
            self.next_audio_frame = self._next_audio_frame()
            self.audio_resampler = av.audio.resampler.AudioResampler(
                format=self.audio_stream.format.packed,
                layout=self.audio_stream.layout,
                rate=self.audio_stream.rate,
            )
            self.audio_paused = False
            af0, af1 = next(self.next_audio_frame), next(self.next_audio_frame)
            # Check pts

            self.audio_pts_rate = af0.samples  # af1.pts - af0.pts
            self.audio_start_pts = 0
            logger.debug(
                "audio_pts_rate = {} start_pts = {}".format(
                    self.audio_pts_rate, self.audio_start_pts
                )
            )

            if self.check_ts_consistency:
                print("**** Checking stream")
                for i, af in enumerate(self.next_audio_frame):
                    fnum = i + 2
                    if af.samples != af0.samples:
                        print("fnum {} samples = {}".format(fnum, af.samples))
                    if af.pts != self.audio_idx_to_pts(fnum):
                        print(
                            "af.pts = {} fnum = {} idx2pts = {}".format(
                                af.pts, fnum, self.audio_idx_to_pts(fnum)
                            )
                        )
                    if (
                        self.audio_timestamps[fnum]
                        != self.audio_timestamps[0]
                        + af.pts * self.audio_stream.time_base
                    ):
                        print(
                            "ts[0] + af.pts = {} fnum = {} timestamp = {}".format(
                                self.audio_timestamps[0]
                                + af.pts * self.audio_stream.time_base,
                                fnum,
                                self.audio_timestamps[fnum],
                            )
                        )
                print("**** Done")
            self.seek_to_audio_frame(0)

            logger.debug(
                "Audio file format {} chans {} rate {} framesize {}".format(
                    self.audio_stream.format.name,
                    self.audio_stream.channels,
                    self.audio_stream.rate,
                    self.audio_stream.frame_size,
                )
            )
            self.audio_start_time = 0
            self.audio_measured_latency = -1.0
            self.last_dac_time = 0
            self.filter_graph = None
            self.filter_graph_list = None
            try:
                self.pa = pa.PyAudio()
                self.pa_stream = self.pa.open(
                    format=self.pa.get_format_from_width(
                        self.audio_stream.format.bytes
                    ),
                    channels=self.audio_stream.channels,
                    rate=self.audio_stream.rate,
                    frames_per_buffer=self.audio_stream.frame_size,
                    stream_callback=self.audio_callback,
                    output=True,
                    start=False,
                )
                logger.debug(
                    "Audio output latency: {}".format(
                        self.pa_stream.get_output_latency()
                    )
                )
                self.audio_sync = self.pa_stream.get_output_latency()
                self.audio_reported_latency = self.pa_stream.get_output_latency()

            except ValueError:
                self.pa_stream = None

            self.audio_timeline = None

            self.audio_viz_trans = Audio_Viz_Transform(self.g_pool.rec_dir)
            self.audio_viz_data = None
            self.log_scale = False
            self.xlim = (self.g_pool.timestamps[0], self.g_pool.timestamps[-1])
            self.ylim = (0, 210)

    def init_ui(self):
        if self.pa_stream is None:
            return
        self.add_menu()
        self.menu_icon.order = 0.01
        self.menu.label = "Audio Playback"

        def set_volume(val):
            # print("set_volume!")
            self.req_audio_volume = val

        self.menu.append(
            ui.Slider(
                "req_audio_volume",
                self,
                # setter=set_volume,
                step=0.05,
                min=0.0,
                max=1.0,
                label="Volume",
            )
        )
        self.menu.append(
            ui.Slider(
                "req_buffer_size_secs",
                self,
                # setter=set_volume,
                step=0.05,
                min=0.0,
                max=1.0,
                label="Buffer size (s)",
            )
        )

        self.audio_timeline = ui.Timeline("Audio level", self.draw_audio, None)
        self.audio_timeline.content_height *= 2
        self.g_pool.user_timelines.append(self.audio_timeline)
        self.menu.append(ui.Switch("log_scale", self, label="Log scale"))

    def sec_to_frames(self, sec):
        return int(np.ceil(sec * self.audio_stream.rate / self.audio_stream.frame_size))

    def frames_to_sec(self, frames):
        return frames * self.audio_stream.frame_size / self.audio_stream.rate

    def buffer_len_secs(self):
        return self.frames_to_sec(len(self.audio_bytes_fifo))

    def audio_callback(self, in_data, frame_count, time_info, status):
        cb_to_adc_time = time_info["output_buffer_dac_time"] - time_info["current_time"]
        start_to_cb_time = monotonic() - self.audio_start_time
        if self.audio_measured_latency < 0:
            self.audio_measured_latency = start_to_cb_time + cb_to_adc_time
            lat_diff = self.audio_reported_latency - self.audio_measured_latency
            self.audio_sync -= lat_diff
            self.g_pool.seek_control.time_slew = self.audio_sync

            logger.debug("Measured latency = {}".format(self.audio_measured_latency))
        self.last_dac_time = time_info["output_buffer_dac_time"]
        if not self.play:
            self.audio_paused = True
            logger.debug("audio cb abort 1")
            return (None, pa.paAbort)
        try:
            samples, ts = self.audio_bytes_fifo.pop(0)
            desync = abs(
                self.g_pool.seek_control.current_playback_time + cb_to_adc_time - ts
            )
            if desync > 0.4:
                logger.debug("*** Audio desync detected: {}".format(desync))
                self.audio_paused = True
                return (None, pa.paAbort)
            return (samples, pa.paContinue)
        except IndexError:
            self.audio_paused = True
            logger.debug("audio cb abort 2")
            return (None, pa.paAbort)

    def get_audio_sync(self):
        # Audio has been started without delay
        if self.audio_measured_latency > 0:
            lat_diff = self.pa_stream.get_output_latency() - self.audio_measured_latency
            return self.audio_sync - lat_diff
        else:
            return self.audio_sync

    def _next_audio_frame(self):
        for packet in self.audio_container.demux(self.audio_stream):
            for frame in packet.decode():
                if frame:
                    yield frame

    def audio_idx_to_pts(self, idx):
        return idx * self.audio_pts_rate

    def seek_to_audio_frame(self, seek_pos):
        try:
            self.audio_stream.seek(
                self.audio_start_pts + self.audio_idx_to_pts(seek_pos)
            )
        except av.AVError as e:
            raise FileSeekError()
        else:
            self.next_audio_frame = self._next_audio_frame()
            self.audio_bytes_fifo.clear()

    def seek_to_frame(self, frame_idx):
        if self.audio_stream is not None:
            audio_idx = bisect(self.audio_timestamps, self.timestamps[frame_idx])
            self.seek_to_audio_frame(audio_idx)

    def on_notify(self, notification):
        if notification["subject"] == "seek_control.was_seeking":
            if self.pa_stream is not None and not self.pa_stream.is_stopped():
                self.pa_stream.stop_stream()
                self.play = False

    def recent_events(self, events):
        if self.audio_viz_trans is not None:
            self.audio_viz_data, finished = self.audio_viz_trans.get_data(
                log_scale=self.log_scale
            )
            if not finished:
                self.audio_timeline.refresh()

        if self.pa_stream is not None and not self.pa_stream.is_stopped():
            # print("req_volume = {}".format(self.req_audio_volume))
            if not self.audio_paused and not self.pa_stream.is_active():
                logger.info("Reopening audio stream...")
                try:
                    self.pa_stream = self.pa.open(
                        format=self.pa.get_format_from_width(
                            self.audio_stream.format.bytes
                        ),
                        channels=self.audio_stream.channels,
                        rate=self.audio_stream.rate,
                        frames_per_buffer=self.audio_stream.frame_size,
                        stream_callback=self.audio_callback,
                        output=True,
                        start=False,
                    )
                    logger.debug(
                        "Audio output latency: {}".format(
                            self.pa_stream.get_output_latency()
                        )
                    )
                    self.audio_sync = self.pa_stream.get_output_latency()
                    self.audio_reported_latency = self.pa_stream.get_output_latency()

                except ValueError:
                    self.pa_stream = None
        start_stream = False

        if (
            self.g_pool.seek_control.playback_speed == 1.0
            and self.pa_stream is not None
        ):
            self.play = True

            # if self.filter_graph_list is None or self.req_audio_volume != self.current_audio_volume:
            #    self.current_audio_volume = self.req_audio_volume
            #    print("Setting volume {} ".format(self.current_audio_volume))
            #    self.pa_stream.stop_stream()

            #    self.filter_graph = av.filter.Graph()
            #    self.filter_graph_list = []
            #    self.filter_graph_list.append(self.filter_graph.add_buffer(template=self.audio_stream))
            #    args = "volume={}:precision=float".format(self.current_audio_volume)
            #    print("args = {}".format(args))
            #    self.filter_graph_list.append(
            #        self.filter_graph.add("volume", args))
            #    self.filter_graph_list[-2].link_to(self.filter_graph_list[-1])
            #    self.filter_graph_list.append(self.filter_graph.add("abuffersink"))
            #    self.filter_graph_list[-2].link_to(self.filter_graph_list[-1])
            #    self.filter_graph.configure()
            if (
                self.pa_stream.is_stopped() or self.audio_paused
            ) and self.audio_delay <= 0.001:
                start_stream = True
                pbt = self.g_pool.seek_control.current_playback_time
                frame_idx = self.g_pool.seek_control.ts_idx_from_playback_time(pbt)
                audio_idx = bisect(
                    self.audio_timestamps, self.g_pool.timestamps[frame_idx]
                )
                self.seek_to_audio_frame(audio_idx)

            if self.filter_graph_list is None:
                self.current_audio_volume = self.req_audio_volume
                print("Setting volume {} ".format(self.current_audio_volume))
                # if self.filter_graph is not None:
                #    self.filter_graph.close()
                #    self.filter_graph = None
                self.filter_graph = av.filter.Graph()
                self.filter_graph_list = []
                self.filter_graph_list.append(
                    self.filter_graph.add_buffer(template=self.audio_stream)
                )
                args = "volume={}:precision=float".format(self.current_audio_volume)
                print("args = {}".format(args))
                self.volume_filter = self.filter_graph.add("volume", args)
                self.filter_graph_list.append(self.volume_filter)
                self.filter_graph_list[-2].link_to(self.filter_graph_list[-1])
                self.filter_graph_list.append(
                    self.filter_graph.add(
                        "aresample",
                        "osf={}".format(self.audio_stream.format.packed.name),
                    )
                )
                self.filter_graph_list[-2].link_to(self.filter_graph_list[-1])
                self.filter_graph_list.append(self.filter_graph.add("abuffersink"))
                self.filter_graph_list[-2].link_to(self.filter_graph_list[-1])
                self.filter_graph.configure()
            elif self.req_audio_volume != self.current_audio_volume:
                self.current_audio_volume = self.req_audio_volume
                args = "{}".format(self.current_audio_volume)
                self.volume_filter.cmd("volume", args)

            frames_to_fetch = self.sec_to_frames(
                max(0, self.req_buffer_size_secs - self.buffer_len_secs())
            )
            if frames_to_fetch > 0:
                frames_chunk = itertools.islice(self.next_audio_frame, frames_to_fetch)
                for audio_frame_p in frames_chunk:
                    pts = audio_frame_p.pts
                    audio_frame_p.pts = None
                    audio_frame_f = None
                    if self.filter_graph_list is not None:
                        self.filter_graph_list[0].push(audio_frame_p)
                        audio_frame = self.filter_graph_list[-1].pull()
                    else:
                        # print("No filter graph!")
                        audio_frame = self.audio_resampler.resample(audio_frame_p)
                    audio_frame.pts = pts
                    self.audio_bytes_fifo.append(
                        (
                            bytes(audio_frame.planes[0]),
                            self.audio_timestamps[0]
                            + audio_frame.pts * self.audio_stream.time_base,
                        )
                    )
            # print("Frames in buffer {}".format(len(self.audio_bytes_fifo)))
            if start_stream:
                rt_delay = (
                    self.audio_timestamps[audio_idx]
                    - self.g_pool.seek_control.current_playback_time
                )
                adj_delay = rt_delay - self.pa_stream.get_output_latency()
                self.audio_delay = 0
                self.audio_sync = 0
                if adj_delay > 0:
                    self.audio_delay = adj_delay
                    self.audio_sync = 0
                else:
                    self.audio_sync = -adj_delay

                logger.debug(
                    "Audio sync = {} rt_delay = {} adj_delay = {}".format(
                        self.audio_sync, rt_delay, adj_delay
                    )
                )
                self.g_pool.seek_control.time_slew = self.audio_sync
                self.pa_stream.stop_stream()
                self.audio_measured_latency = -1
                if self.audio_delay < 0.001:
                    self.audio_start_time = monotonic()
                    self.pa_stream.start_stream()
                else:

                    def delayed_audio_start():
                        if self.pa_stream.is_stopped():
                            self.audio_start_time = monotonic()
                            self.pa_stream.start_stream()
                            self.audio_delay = 0
                            logger.debug("Started delayed audio")
                        self.audio_timer.cancel()

                    self.audio_timer = Timer(self.audio_delay, delayed_audio_start)
                    self.audio_timer.start()

                self.audio_paused = False

        else:
            if self.pa_stream is not None and not self.pa_stream.is_stopped():
                self.pa_stream.stop_stream()
            self.play = False

    def cache_audio_data(self):
        if self.get_audio_data:
            if self.audio_viz_trans is None:
                try:
                    self.audio_viz_trans = Audio_Viz_Transform(self.g_pool.rec_dir)
                except FileNotFoundError:
                    self.get_audio_data = False
                    return False

            a_levels, finished = self.audio_viz_trans.get_data()
            if a_levels is not None:
                self.cache["audio_level"] = a_levels
            if a_levels is None or finished:
                self.get_audio_data = False
            return True
        else:
            return False

    def draw_audio(self, width, height, scale):
        with gl_utils.Coord_System(*self.xlim, *self.ylim):
            draw_bars_buffer(self.audio_viz_data, color=viz_color)
Exemple #6
0
class Audio_Playback(System_Plugin_Base):
    """Calibrate using a marker on your screen
    We use a ring detector that moves across the screen to 9 sites
    Points are collected at sites not between
    """

    icon_chr = chr(0xE050)
    icon_font = "pupil_icons"

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

        self.play = False
        self.pa_stream = None
        self.audio_sync = 0.0
        self.audio_delay = 0.0
        self.audio_timer = None
        self.audio_frame_iterator = None
        self.audio_start_pts = 0

        # debug flag. Only set if timestamp consistency should be checked
        self.should_check_ts_consistency = False

        self.req_audio_volume = 1.0
        self.current_audio_volume = 1.0
        self.req_buffer_size_secs = 0.5
        self.audio_viz_trans = None
        self.audio_bytes_fifo = collections.deque()

        try:
            self.audio_all = load_audio(self.g_pool.rec_dir)
        except NoAudioLoadedError:
            return

        self.calculate_audio_bounds()

        self.filter_graph = None
        self.filter_graph_list = None
        self.pa = pa.PyAudio()

        self._setup_input_audio_part(0)

        self._setup_output_audio()
        self._setup_audio_vis()

    def check_audio_part_setup(self):
        part_idx = self.audio_part_idx_from_playbacktime()
        if part_idx > -1 and part_idx != self.current_audio_part_idx:
            self._setup_input_audio_part(part_idx)

    def calculate_audio_bounds(self):
        audio_part_boundaries = (audio.timestamps[[0, -1]] for audio in self.audio_all)
        audio_part_boundaries = itertools.chain.from_iterable(audio_part_boundaries)
        self.audio_bounds = np.fromiter(audio_part_boundaries, dtype=float)

    def audio_part_idx_from_playbacktime(self):
        pbt = self.g_pool.seek_control.current_playback_time
        bound_idx = bisect(self.audio_bounds, pbt)
        if bound_idx % 2 == 0:
            return -1  # pbt is between audio parts
        else:
            part_idx = bound_idx // 2
            return part_idx

    def _setup_input_audio_part(self, part_idx):
        self.current_audio_part_idx = part_idx
        self.audio = self.audio_all[part_idx]
        self.audio_bytes_fifo.clear()

        self.audio_frame_iterator = self.get_audio_frame_iterator()
        self.audio_resampler = av.audio.resampler.AudioResampler(
            format=self.audio.stream.format.packed,
            layout=self.audio.stream.layout,
            rate=self.audio.stream.rate,
        )
        self.audio_paused = False

        self.audio.stream.seek(0)
        first_frame = next(self.audio_frame_iterator)
        self.audio_pts_rate = first_frame.samples
        self.audio_start_pts = first_frame.pts

        logger.debug(
            "audio_pts_rate = {} start_pts = {}".format(
                self.audio_pts_rate, self.audio_start_pts
            )
        )
        self.check_ts_consistency(reference_frame=first_frame)
        self.seek_to_audio_frame(0)

        logger.debug(
            "Audio file format {} chans {} rate {} framesize {}".format(
                self.audio.stream.format.name,
                self.audio.stream.channels,
                self.audio.stream.rate,
                self.audio.stream.frame_size,
            )
        )
        self.audio_start_time = 0
        self.audio_measured_latency = -1.0
        self.last_dac_time = 0

    def _setup_output_audio(self):
        try:
            self.pa_stream = self.pa.open(
                format=self.pa.get_format_from_width(self.audio.stream.format.bytes),
                channels=self.audio.stream.channels,
                rate=self.audio.stream.rate,
                frames_per_buffer=self.audio.stream.frame_size,
                stream_callback=self.audio_callback,
                output=True,
                start=False,
            )
            logger.debug(
                "Audio output latency: {}".format(self.pa_stream.get_output_latency())
            )
            self.audio_sync = self.pa_stream.get_output_latency()
            self.audio_reported_latency = self.pa_stream.get_output_latency()

        except ValueError:
            self.pa_stream = None

    def _setup_audio_vis(self):
        self.audio_timeline = None
        self.audio_viz_trans = Audio_Viz_Transform(self.g_pool.rec_dir)
        self.audio_viz_data = None
        self.log_scale = False
        self.xlim = (self.g_pool.timestamps[0], self.g_pool.timestamps[-1])
        self.ylim = (0, 210)

    def _setup_filter_graph(self):
        """Graph: buffer -> volume filter -> resample -> buffersink"""
        self.current_audio_volume = self.req_audio_volume
        logger.debug("Setting volume {} ".format(self.current_audio_volume))
        self.filter_graph = av.filter.Graph()
        self.filter_graph_list = []
        self.filter_graph_list.append(
            self.filter_graph.add_buffer(template=self.audio.stream)
        )
        args = "volume={}:precision=float".format(self.current_audio_volume)
        logger.debug("args = {}".format(args))
        self.volume_filter = self.filter_graph.add("volume", args)
        self.filter_graph_list.append(self.volume_filter)
        self.filter_graph_list[-2].link_to(self.filter_graph_list[-1])
        self.filter_graph_list.append(
            self.filter_graph.add(
                "aresample",
                "osf={}".format(self.audio.stream.format.packed.name),
            )
        )
        self.filter_graph_list[-2].link_to(self.filter_graph_list[-1])
        self.filter_graph_list.append(self.filter_graph.add("abuffersink"))
        self.filter_graph_list[-2].link_to(self.filter_graph_list[-1])
        self.filter_graph.configure()

    def sec_to_frames(self, sec):
        return int(np.ceil(sec * self.audio.stream.rate / self.audio.stream.frame_size))

    def frames_to_sec(self, frames):
        return frames * self.audio.stream.frame_size / self.audio.stream.rate

    def buffer_len_secs(self):
        return self.frames_to_sec(len(self.audio_bytes_fifo))

    def audio_callback(self, in_data, frame_count, time_info, status):
        cb_to_adc_time = time_info["output_buffer_dac_time"] - time_info["current_time"]
        start_to_cb_time = monotonic() - self.audio_start_time
        if self.audio_measured_latency < 0:
            self.audio_measured_latency = start_to_cb_time + cb_to_adc_time
            lat_diff = self.audio_reported_latency - self.audio_measured_latency
            self.audio_sync -= lat_diff
            self.g_pool.seek_control.time_slew = self.audio_sync

            logger.debug("Measured latency = {}".format(self.audio_measured_latency))
        self.last_dac_time = time_info["output_buffer_dac_time"]
        if not self.play:
            self.audio_paused = True
            logger.debug("audio cb complete 1")
            return (None, pa.paAbort)
        try:
            samples, ts = self.audio_bytes_fifo.popleft()
            desync = abs(
                self.g_pool.seek_control.current_playback_time + cb_to_adc_time - ts
            )
            if desync > 0.4:
                logger.debug("*** Audio desync detected: {}".format(desync))
                self.audio_paused = True
                return (None, pa.paAbort)
            return (samples, pa.paContinue)
        except IndexError:
            self.audio_paused = True
            logger.debug("audio cb abort 2")
            return (None, pa.paAbort)

    def get_audio_sync(self):
        # Audio has been started without delay
        if self.audio_measured_latency > 0:
            lat_diff = self.pa_stream.get_output_latency() - self.audio_measured_latency
            return self.audio_sync - lat_diff
        else:
            return self.audio_sync

    def get_audio_frame_iterator(self):
        for packet in self.audio.container.demux(self.audio.stream):
            for frame in packet.decode():
                if frame:
                    yield frame

    def audio_idx_to_pts(self, idx):
        return idx * self.audio_pts_rate

    def seek_to_audio_frame(self, seek_pos):
        try:
            self.audio.stream.seek(
                self.audio_start_pts + self.audio_idx_to_pts(seek_pos)
            )
        except av.AVError:
            raise FileSeekError()
        else:
            self.audio_frame_iterator = self.get_audio_frame_iterator()
            self.audio_bytes_fifo.clear()

    def seek_to_frame(self, frame_idx):
        if self.audio.stream is not None:
            audio_idx = bisect(self.audio.timestamps, self.timestamps[frame_idx])
            self.seek_to_audio_frame(audio_idx)

    def on_notify(self, notification):
        if notification["subject"] == "seek_control.was_seeking":
            if self.pa_stream is not None and not self.pa_stream.is_stopped():
                self.pa_stream.stop_stream()
                self.play = False

    def recent_events(self, events):
        self.update_audio_viz()
        self.setup_pyaudio_output_if_necessary()
        if self.pa_stream is None:
            self.play = False
            return
        if self.g_pool.seek_control.playback_speed != 1.0:
            if not self.pa_stream.is_stopped():
                self.pa_stream.stop_stream()
            self.play = False
            return

        self.check_audio_part_setup()
        start_stream = False
        self.play = True
        is_stream_paused = self.pa_stream.is_stopped() or self.audio_paused
        is_audio_delay_low_enough = self.audio_delay <= 0.001
        if is_stream_paused and is_audio_delay_low_enough:
            ts_audio_start = self.calc_audio_start_ts()
            if ts_audio_start is not None:
                start_stream = True

        self.adjust_audio_volume_filter_if_necessary()
        self.fill_audio_queue()

        if start_stream:
            self.calculate_delays(ts_audio_start)
            self.start_audio()

    def calc_audio_start_ts(self):
        pbt = self.g_pool.seek_control.current_playback_time
        audio_start, audio_end = self.audio.timestamps[[0, -1]]

        if audio_start <= pbt <= audio_end:
            audio_idx = bisect(self.audio.timestamps, pbt)
            self.seek_to_audio_frame(audio_idx)
            return self.audio.timestamps[audio_idx]

    def update_audio_viz(self):
        if self.audio_viz_trans is not None:
            self.audio_viz_data, finished = self.audio_viz_trans.get_data(
                log_scale=self.log_scale
            )
            if not finished:
                self.audio_timeline.refresh()

    def setup_pyaudio_output_if_necessary(self):
        if self.pa_stream is not None and not self.pa_stream.is_stopped():
            if not self.audio_paused and not self.pa_stream.is_active():
                logger.debug("Reopening audio stream...")
                self._setup_output_audio()

    def calculate_delays(self, ts_audio_start):
        real_time_delay = (
            ts_audio_start - self.g_pool.seek_control.current_playback_time
        )
        adjusted_delay = real_time_delay - self.pa_stream.get_output_latency()
        self.audio_delay = 0
        self.audio_sync = 0
        if adjusted_delay > 0:
            self.audio_delay = adjusted_delay
            self.audio_sync = 0
        else:
            self.audio_sync = -adjusted_delay

        logger.debug(
            "Audio sync = {} rt_delay = {} adj_delay = {}".format(
                self.audio_sync, real_time_delay, adjusted_delay
            )
        )
        self.g_pool.seek_control.time_slew = self.audio_sync
        self.pa_stream.stop_stream()
        self.audio_measured_latency = -1

    def start_audio(self):
        if self.audio_delay < 0.001:
            self.audio_start_time = monotonic()
            self.pa_stream.start_stream()
        else:

            def delayed_audio_start():
                if self.pa_stream.is_stopped():
                    self.audio_start_time = monotonic()
                    self.pa_stream.start_stream()
                    self.audio_delay = 0
                    logger.debug("Started delayed audio")
                self.audio_timer.cancel()
                self.audio_timer = None

            logger.debug("Starting delayed audio timer")
            self.audio_timer = Timer(self.audio_delay, delayed_audio_start)
            self.audio_timer.start()

        self.audio_paused = False

    def adjust_audio_volume_filter_if_necessary(self):
        if self.filter_graph_list is None:
            self._setup_filter_graph()
        elif self.req_audio_volume != self.current_audio_volume:
            self.current_audio_volume = self.req_audio_volume
            args = "{}".format(self.current_audio_volume)
            self.volume_filter.cmd("volume", args)

    def fill_audio_queue(self):
        frames_to_fetch = self.sec_to_frames(
            max(0, self.req_buffer_size_secs - self.buffer_len_secs())
        )
        if frames_to_fetch <= 0:
            return
        frames_chunk = itertools.islice(self.audio_frame_iterator, frames_to_fetch)
        for audio_frame_p in frames_chunk:
            pts = audio_frame_p.pts
            audio_frame_p.pts = None
            if self.filter_graph_list is not None:
                self.filter_graph_list[0].push(audio_frame_p)
                audio_frame = self.filter_graph_list[-1].pull()
            else:
                audio_frame = self.audio_resampler.resample(audio_frame_p)
            audio_frame.pts = pts
            audio_buffer = bytes(audio_frame.planes[0])

            audio_part_start_ts = self.audio.timestamps[0]
            audio_part_progress = audio_frame.pts * self.audio.stream.time_base
            audio_playback_time = audio_part_start_ts + audio_part_progress
            self.audio_bytes_fifo.append((audio_buffer, audio_playback_time))

    def draw_audio(self, width, height, scale):
        if self.audio_viz_data is None:
            return
        with gl_utils.Coord_System(*self.xlim, *self.ylim):
            pyglui_utils.draw_bars_buffer(self.audio_viz_data, color=viz_color)

    def init_ui(self):
        if self.pa_stream is None:
            return
        self.add_menu()
        self.menu_icon.order = 0.01
        self.menu.label = "Audio Playback"

        def set_volume(val):
            self.req_audio_volume = val

        self.menu.append(
            ui.Slider(
                "req_audio_volume",
                self,
                step=0.05,
                min=0.0,
                max=1.0,
                label="Volume",
            )
        )
        self.menu.append(
            ui.Slider(
                "req_buffer_size_secs",
                self,
                step=0.05,
                min=0.0,
                max=1.0,
                label="Buffer size (s)",
            )
        )

        self.audio_timeline = ui.Timeline("Audio level", self.draw_audio, None)
        self.audio_timeline.content_height *= 2
        self.g_pool.user_timelines.append(self.audio_timeline)
        self.menu.append(ui.Switch("log_scale", self, label="Log scale"))

    def cleanup(self):
        if self.audio_timer is not None:
            self.audio_timer.cancel()
            self.audio_timer = None

    def check_ts_consistency(self, reference_frame):
        if self.should_check_ts_consistency:
            print("**** Checking stream")
            for i, af in enumerate(self.audio_frame_iterator):
                fnum = i + 1
                if af.samples != reference_frame.samples:
                    print("fnum {} samples = {}".format(fnum, af.samples))
                if af.pts != self.audio_idx_to_pts(fnum):
                    print(
                        "af.pts = {} fnum = {} idx2pts = {}".format(
                            af.pts, fnum, self.audio_idx_to_pts(fnum)
                        )
                    )
                if (
                    self.audio.timestamps[fnum]
                    != self.audio.timestamps[0] + af.pts * self.audio.stream.time_base
                ):
                    print(
                        "ts[0] + af.pts = {} fnum = {} timestamp = {}".format(
                            self.audio.timestamps[0]
                            + af.pts * self.audio.stream.time_base,
                            fnum,
                            self.audio.timestamps[fnum],
                        )
                    )
            print("**** Done")
    def __init__(self, g_pool):
        super().__init__(g_pool)

        self.play = False
        self.pa_stream = None
        self.audio_sync = 0.0
        self.audio_delay = 0.0
        self.next_audio_frame = None
        self.audio_start_pts = 0
        self.check_ts_consistency = False
        self.req_audio_volume = 1.0
        self.current_audio_volume = 1.0
        self.req_buffer_size_secs = 0.5
        self.audio_viz_trans = None

        try:
            self.audio = load_audio(self.g_pool.rec_dir)
        except NoAudioLoadedError:
            return

        self.audio_bytes_fifo = []
        self.next_audio_frame = self._next_audio_frame()
        self.audio_resampler = av.audio.resampler.AudioResampler(
            format=self.audio.stream.format.packed,
            layout=self.audio.stream.layout,
            rate=self.audio.stream.rate,
        )
        self.audio_paused = False
        af0, af1 = next(self.next_audio_frame), next(self.next_audio_frame)
        # Check pts

        self.audio_pts_rate = af0.samples  # af1.pts - af0.pts
        self.audio_start_pts = 0
        logger.debug("audio_pts_rate = {} start_pts = {}".format(
            self.audio_pts_rate, self.audio_start_pts))

        if self.check_ts_consistency:
            print("**** Checking stream")
            for i, af in enumerate(self.next_audio_frame):
                fnum = i + 2
                if af.samples != af0.samples:
                    print("fnum {} samples = {}".format(fnum, af.samples))
                if af.pts != self.audio_idx_to_pts(fnum):
                    print("af.pts = {} fnum = {} idx2pts = {}".format(
                        af.pts, fnum, self.audio_idx_to_pts(fnum)))
                if (self.audio.timestamps[fnum] != self.audio.timestamps[0] +
                        af.pts * self.audio.stream.time_base):
                    print(
                        "ts[0] + af.pts = {} fnum = {} timestamp = {}".format(
                            self.audio.timestamps[0] +
                            af.pts * self.audio.stream.time_base,
                            fnum,
                            self.audio.timestamps[fnum],
                        ))
            print("**** Done")
        self.seek_to_audio_frame(0)

        logger.debug(
            "Audio file format {} chans {} rate {} framesize {}".format(
                self.audio.stream.format.name,
                self.audio.stream.channels,
                self.audio.stream.rate,
                self.audio.stream.frame_size,
            ))
        self.audio_start_time = 0
        self.audio_measured_latency = -1.0
        self.last_dac_time = 0
        self.filter_graph = None
        self.filter_graph_list = None
        try:
            self.pa = pa.PyAudio()
            self.pa_stream = self.pa.open(
                format=self.pa.get_format_from_width(
                    self.audio.stream.format.bytes),
                channels=self.audio.stream.channels,
                rate=self.audio.stream.rate,
                frames_per_buffer=self.audio.stream.frame_size,
                stream_callback=self.audio_callback,
                output=True,
                start=False,
            )
            logger.debug("Audio output latency: {}".format(
                self.pa_stream.get_output_latency()))
            self.audio_sync = self.pa_stream.get_output_latency()
            self.audio_reported_latency = self.pa_stream.get_output_latency()

        except ValueError:
            self.pa_stream = None

        self.audio_timeline = None

        self.audio_viz_trans = Audio_Viz_Transform(self.g_pool.rec_dir)
        self.audio_viz_data = None
        self.log_scale = False
        self.xlim = (self.g_pool.timestamps[0], self.g_pool.timestamps[-1])
        self.ylim = (0, 210)
Exemple #8
0
    def __init__(self, g_pool):
        super().__init__(g_pool)

        self.play = False
        self.pa_stream = None
        self.audio_sync = 0.0
        self.audio_delay = 0.0
        self.next_audio_frame = None
        self.audio_start_pts = 0
        self.check_ts_consistency = False
        self.req_audio_volume = 1.0
        self.current_audio_volume = 1.0
        self.req_buffer_size_secs = 0.5
        self.audio_viz_trans = None

        try:
            self.audio = load_audio(self.g_pool.rec_dir)
        except NoAudioLoadedError:
            return

        self.audio_bytes_fifo = []
        self.next_audio_frame = self._next_audio_frame()
        self.audio_resampler = av.audio.resampler.AudioResampler(
            format=self.audio.stream.format.packed,
            layout=self.audio.stream.layout,
            rate=self.audio.stream.rate,
        )
        self.audio_paused = False
        af0, af1 = next(self.next_audio_frame), next(self.next_audio_frame)
        # Check pts

        self.audio_pts_rate = af0.samples  # af1.pts - af0.pts
        self.audio_start_pts = 0
        logger.debug(
            "audio_pts_rate = {} start_pts = {}".format(
                self.audio_pts_rate, self.audio_start_pts
            )
        )

        if self.check_ts_consistency:
            print("**** Checking stream")
            for i, af in enumerate(self.next_audio_frame):
                fnum = i + 2
                if af.samples != af0.samples:
                    print("fnum {} samples = {}".format(fnum, af.samples))
                if af.pts != self.audio_idx_to_pts(fnum):
                    print(
                        "af.pts = {} fnum = {} idx2pts = {}".format(
                            af.pts, fnum, self.audio_idx_to_pts(fnum)
                        )
                    )
                if (
                    self.audio.timestamps[fnum]
                    != self.audio.timestamps[0] + af.pts * self.audio.stream.time_base
                ):
                    print(
                        "ts[0] + af.pts = {} fnum = {} timestamp = {}".format(
                            self.audio.timestamps[0]
                            + af.pts * self.audio.stream.time_base,
                            fnum,
                            self.audio.timestamps[fnum],
                        )
                    )
            print("**** Done")
        self.seek_to_audio_frame(0)

        logger.debug(
            "Audio file format {} chans {} rate {} framesize {}".format(
                self.audio.stream.format.name,
                self.audio.stream.channels,
                self.audio.stream.rate,
                self.audio.stream.frame_size,
            )
        )
        self.audio_start_time = 0
        self.audio_measured_latency = -1.0
        self.last_dac_time = 0
        self.filter_graph = None
        self.filter_graph_list = None
        try:
            self.pa = pa.PyAudio()
            self.pa_stream = self.pa.open(
                format=self.pa.get_format_from_width(self.audio.stream.format.bytes),
                channels=self.audio.stream.channels,
                rate=self.audio.stream.rate,
                frames_per_buffer=self.audio.stream.frame_size,
                stream_callback=self.audio_callback,
                output=True,
                start=False,
            )
            logger.debug(
                "Audio output latency: {}".format(self.pa_stream.get_output_latency())
            )
            self.audio_sync = self.pa_stream.get_output_latency()
            self.audio_reported_latency = self.pa_stream.get_output_latency()

        except ValueError:
            self.pa_stream = None

        self.audio_timeline = None

        self.audio_viz_trans = Audio_Viz_Transform(self.g_pool.rec_dir)
        self.audio_viz_data = None
        self.log_scale = False
        self.xlim = (self.g_pool.timestamps[0], self.g_pool.timestamps[-1])
        self.ylim = (0, 210)
Exemple #9
0
class Audio_Playback(System_Plugin_Base):
    """Calibrate using a marker on your screen
    We use a ring detector that moves across the screen to 9 sites
    Points are collected at sites not between
    """

    icon_chr = chr(0xE050)
    icon_font = "pupil_icons"

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

        self.play = False
        self.pa_stream = None
        self.audio_sync = 0.0
        self.audio_delay = 0.0
        self.next_audio_frame = None
        self.audio_start_pts = 0
        self.check_ts_consistency = False
        self.req_audio_volume = 1.0
        self.current_audio_volume = 1.0
        self.req_buffer_size_secs = 0.5
        self.audio_viz_trans = None

        try:
            self.audio = load_audio(self.g_pool.rec_dir)
        except NoAudioLoadedError:
            return

        self.audio_bytes_fifo = []
        self.next_audio_frame = self._next_audio_frame()
        self.audio_resampler = av.audio.resampler.AudioResampler(
            format=self.audio.stream.format.packed,
            layout=self.audio.stream.layout,
            rate=self.audio.stream.rate,
        )
        self.audio_paused = False
        af0, af1 = next(self.next_audio_frame), next(self.next_audio_frame)
        # Check pts

        self.audio_pts_rate = af0.samples  # af1.pts - af0.pts
        self.audio_start_pts = 0
        logger.debug(
            "audio_pts_rate = {} start_pts = {}".format(
                self.audio_pts_rate, self.audio_start_pts
            )
        )

        if self.check_ts_consistency:
            print("**** Checking stream")
            for i, af in enumerate(self.next_audio_frame):
                fnum = i + 2
                if af.samples != af0.samples:
                    print("fnum {} samples = {}".format(fnum, af.samples))
                if af.pts != self.audio_idx_to_pts(fnum):
                    print(
                        "af.pts = {} fnum = {} idx2pts = {}".format(
                            af.pts, fnum, self.audio_idx_to_pts(fnum)
                        )
                    )
                if (
                    self.audio.timestamps[fnum]
                    != self.audio.timestamps[0] + af.pts * self.audio.stream.time_base
                ):
                    print(
                        "ts[0] + af.pts = {} fnum = {} timestamp = {}".format(
                            self.audio.timestamps[0]
                            + af.pts * self.audio.stream.time_base,
                            fnum,
                            self.audio.timestamps[fnum],
                        )
                    )
            print("**** Done")
        self.seek_to_audio_frame(0)

        logger.debug(
            "Audio file format {} chans {} rate {} framesize {}".format(
                self.audio.stream.format.name,
                self.audio.stream.channels,
                self.audio.stream.rate,
                self.audio.stream.frame_size,
            )
        )
        self.audio_start_time = 0
        self.audio_measured_latency = -1.0
        self.last_dac_time = 0
        self.filter_graph = None
        self.filter_graph_list = None
        try:
            self.pa = pa.PyAudio()
            self.pa_stream = self.pa.open(
                format=self.pa.get_format_from_width(self.audio.stream.format.bytes),
                channels=self.audio.stream.channels,
                rate=self.audio.stream.rate,
                frames_per_buffer=self.audio.stream.frame_size,
                stream_callback=self.audio_callback,
                output=True,
                start=False,
            )
            logger.debug(
                "Audio output latency: {}".format(self.pa_stream.get_output_latency())
            )
            self.audio_sync = self.pa_stream.get_output_latency()
            self.audio_reported_latency = self.pa_stream.get_output_latency()

        except ValueError:
            self.pa_stream = None

        self.audio_timeline = None

        self.audio_viz_trans = Audio_Viz_Transform(self.g_pool.rec_dir)
        self.audio_viz_data = None
        self.log_scale = False
        self.xlim = (self.g_pool.timestamps[0], self.g_pool.timestamps[-1])
        self.ylim = (0, 210)

    def init_ui(self):
        if self.pa_stream is None:
            return
        self.add_menu()
        self.menu_icon.order = 0.01
        self.menu.label = "Audio Playback"

        def set_volume(val):
            # print("set_volume!")
            self.req_audio_volume = val

        self.menu.append(
            ui.Slider(
                "req_audio_volume",
                self,
                # setter=set_volume,
                step=0.05,
                min=0.0,
                max=1.0,
                label="Volume",
            )
        )
        self.menu.append(
            ui.Slider(
                "req_buffer_size_secs",
                self,
                # setter=set_volume,
                step=0.05,
                min=0.0,
                max=1.0,
                label="Buffer size (s)",
            )
        )

        self.audio_timeline = ui.Timeline("Audio level", self.draw_audio, None)
        self.audio_timeline.content_height *= 2
        self.g_pool.user_timelines.append(self.audio_timeline)
        self.menu.append(ui.Switch("log_scale", self, label="Log scale"))

    def sec_to_frames(self, sec):
        return int(np.ceil(sec * self.audio.stream.rate / self.audio.stream.frame_size))

    def frames_to_sec(self, frames):
        return frames * self.audio.stream.frame_size / self.audio.stream.rate

    def buffer_len_secs(self):
        return self.frames_to_sec(len(self.audio_bytes_fifo))

    def audio_callback(self, in_data, frame_count, time_info, status):
        cb_to_adc_time = time_info["output_buffer_dac_time"] - time_info["current_time"]
        start_to_cb_time = monotonic() - self.audio_start_time
        if self.audio_measured_latency < 0:
            self.audio_measured_latency = start_to_cb_time + cb_to_adc_time
            lat_diff = self.audio_reported_latency - self.audio_measured_latency
            self.audio_sync -= lat_diff
            self.g_pool.seek_control.time_slew = self.audio_sync

            logger.debug("Measured latency = {}".format(self.audio_measured_latency))
        self.last_dac_time = time_info["output_buffer_dac_time"]
        if not self.play:
            self.audio_paused = True
            logger.debug("audio cb abort 1")
            return (None, pa.paAbort)
        try:
            samples, ts = self.audio_bytes_fifo.pop(0)
            desync = abs(
                self.g_pool.seek_control.current_playback_time + cb_to_adc_time - ts
            )
            if desync > 0.4:
                logger.debug("*** Audio desync detected: {}".format(desync))
                self.audio_paused = True
                return (None, pa.paAbort)
            return (samples, pa.paContinue)
        except IndexError:
            self.audio_paused = True
            logger.debug("audio cb abort 2")
            return (None, pa.paAbort)

    def get_audio_sync(self):
        # Audio has been started without delay
        if self.audio_measured_latency > 0:
            lat_diff = self.pa_stream.get_output_latency() - self.audio_measured_latency
            return self.audio_sync - lat_diff
        else:
            return self.audio_sync

    def _next_audio_frame(self):
        for packet in self.audio.container.demux(self.audio.stream):
            for frame in packet.decode():
                if frame:
                    yield frame

    def audio_idx_to_pts(self, idx):
        return idx * self.audio_pts_rate

    def seek_to_audio_frame(self, seek_pos):
        try:
            self.audio.stream.seek(
                self.audio_start_pts + self.audio_idx_to_pts(seek_pos)
            )
        except av.AVError as e:
            raise FileSeekError()
        else:
            self.next_audio_frame = self._next_audio_frame()
            self.audio_bytes_fifo.clear()

    def seek_to_frame(self, frame_idx):
        if self.audio.stream is not None:
            audio_idx = bisect(self.audio.timestamps, self.timestamps[frame_idx])
            self.seek_to_audio_frame(audio_idx)

    def on_notify(self, notification):
        if notification["subject"] == "seek_control.was_seeking":
            if self.pa_stream is not None and not self.pa_stream.is_stopped():
                self.pa_stream.stop_stream()
                self.play = False

    def recent_events(self, events):
        if self.audio_viz_trans is not None:
            self.audio_viz_data, finished = self.audio_viz_trans.get_data(
                log_scale=self.log_scale
            )
            if not finished:
                self.audio_timeline.refresh()

        if self.pa_stream is not None and not self.pa_stream.is_stopped():
            # print("req_volume = {}".format(self.req_audio_volume))
            if not self.audio_paused and not self.pa_stream.is_active():
                logger.info("Reopening audio stream...")
                try:
                    self.pa_stream = self.pa.open(
                        format=self.pa.get_format_from_width(
                            self.audio.stream.format.bytes
                        ),
                        channels=self.audio.stream.channels,
                        rate=self.audio.stream.rate,
                        frames_per_buffer=self.audio.stream.frame_size,
                        stream_callback=self.audio_callback,
                        output=True,
                        start=False,
                    )
                    logger.debug(
                        "Audio output latency: {}".format(
                            self.pa_stream.get_output_latency()
                        )
                    )
                    self.audio_sync = self.pa_stream.get_output_latency()
                    self.audio_reported_latency = self.pa_stream.get_output_latency()

                except ValueError:
                    self.pa_stream = None
        start_stream = False

        if (
            self.g_pool.seek_control.playback_speed == 1.0
            and self.pa_stream is not None
        ):
            self.play = True

            # if self.filter_graph_list is None or self.req_audio_volume != self.current_audio_volume:
            #    self.current_audio_volume = self.req_audio_volume
            #    print("Setting volume {} ".format(self.current_audio_volume))
            #    self.pa_stream.stop_stream()

            #    self.filter_graph = av.filter.Graph()
            #    self.filter_graph_list = []
            #    self.filter_graph_list.append(self.filter_graph.add_buffer(template=self.audio.stream))
            #    args = "volume={}:precision=float".format(self.current_audio_volume)
            #    print("args = {}".format(args))
            #    self.filter_graph_list.append(
            #        self.filter_graph.add("volume", args))
            #    self.filter_graph_list[-2].link_to(self.filter_graph_list[-1])
            #    self.filter_graph_list.append(self.filter_graph.add("abuffersink"))
            #    self.filter_graph_list[-2].link_to(self.filter_graph_list[-1])
            #    self.filter_graph.configure()
            if (
                self.pa_stream.is_stopped() or self.audio_paused
            ) and self.audio_delay <= 0.001:
                start_stream = True
                pbt = self.g_pool.seek_control.current_playback_time
                frame_idx = self.g_pool.seek_control.ts_idx_from_playback_time(pbt)
                audio_idx = bisect(
                    self.audio.timestamps, self.g_pool.timestamps[frame_idx]
                )
                if audio_idx < len(self.audio.timestamps):
                    self.seek_to_audio_frame(audio_idx)
                else:
                    start_stream = False

            if self.filter_graph_list is None:
                self.current_audio_volume = self.req_audio_volume
                print("Setting volume {} ".format(self.current_audio_volume))
                # if self.filter_graph is not None:
                #    self.filter_graph.close()
                #    self.filter_graph = None
                self.filter_graph = av.filter.Graph()
                self.filter_graph_list = []
                self.filter_graph_list.append(
                    self.filter_graph.add_buffer(template=self.audio.stream)
                )
                args = "volume={}:precision=float".format(self.current_audio_volume)
                print("args = {}".format(args))
                self.volume_filter = self.filter_graph.add("volume", args)
                self.filter_graph_list.append(self.volume_filter)
                self.filter_graph_list[-2].link_to(self.filter_graph_list[-1])
                self.filter_graph_list.append(
                    self.filter_graph.add(
                        "aresample",
                        "osf={}".format(self.audio.stream.format.packed.name),
                    )
                )
                self.filter_graph_list[-2].link_to(self.filter_graph_list[-1])
                self.filter_graph_list.append(self.filter_graph.add("abuffersink"))
                self.filter_graph_list[-2].link_to(self.filter_graph_list[-1])
                self.filter_graph.configure()
            elif self.req_audio_volume != self.current_audio_volume:
                self.current_audio_volume = self.req_audio_volume
                args = "{}".format(self.current_audio_volume)
                self.volume_filter.cmd("volume", args)

            frames_to_fetch = self.sec_to_frames(
                max(0, self.req_buffer_size_secs - self.buffer_len_secs())
            )
            if frames_to_fetch > 0:
                frames_chunk = itertools.islice(self.next_audio_frame, frames_to_fetch)
                for audio_frame_p in frames_chunk:
                    pts = audio_frame_p.pts
                    audio_frame_p.pts = None
                    audio_frame_f = None
                    if self.filter_graph_list is not None:
                        self.filter_graph_list[0].push(audio_frame_p)
                        audio_frame = self.filter_graph_list[-1].pull()
                    else:
                        # print("No filter graph!")
                        audio_frame = self.audio_resampler.resample(audio_frame_p)
                    audio_frame.pts = pts
                    self.audio_bytes_fifo.append(
                        (
                            bytes(audio_frame.planes[0]),
                            self.audio.timestamps[0]
                            + audio_frame.pts * self.audio.stream.time_base,
                        )
                    )
            # print("Frames in buffer {}".format(len(self.audio_bytes_fifo)))
            if start_stream:
                rt_delay = (
                    self.audio.timestamps[audio_idx]
                    - self.g_pool.seek_control.current_playback_time
                )
                adj_delay = rt_delay - self.pa_stream.get_output_latency()
                self.audio_delay = 0
                self.audio_sync = 0
                if adj_delay > 0:
                    self.audio_delay = adj_delay
                    self.audio_sync = 0
                else:
                    self.audio_sync = -adj_delay

                logger.debug(
                    "Audio sync = {} rt_delay = {} adj_delay = {}".format(
                        self.audio_sync, rt_delay, adj_delay
                    )
                )
                self.g_pool.seek_control.time_slew = self.audio_sync
                self.pa_stream.stop_stream()
                self.audio_measured_latency = -1
                if self.audio_delay < 0.001:
                    self.audio_start_time = monotonic()
                    self.pa_stream.start_stream()
                else:

                    def delayed_audio_start():
                        if self.pa_stream.is_stopped():
                            self.audio_start_time = monotonic()
                            self.pa_stream.start_stream()
                            self.audio_delay = 0
                            logger.debug("Started delayed audio")
                        self.audio_timer.cancel()

                    self.audio_timer = Timer(self.audio_delay, delayed_audio_start)
                    self.audio_timer.start()

                self.audio_paused = False

        else:
            if self.pa_stream is not None and not self.pa_stream.is_stopped():
                self.pa_stream.stop_stream()
            self.play = False

    def cache_audio_data(self):
        if self.get_audio_data:
            if self.audio_viz_trans is None:
                try:
                    self.audio_viz_trans = Audio_Viz_Transform(self.g_pool.rec_dir)
                except FileNotFoundError:
                    self.get_audio_data = False
                    return False

            a_levels, finished = self.audio_viz_trans.get_data()
            if a_levels is not None:
                self.cache["audio_level"] = a_levels
            if a_levels is None or finished:
                self.get_audio_data = False
            return True
        else:
            return False

    def draw_audio(self, width, height, scale):
        with gl_utils.Coord_System(*self.xlim, *self.ylim):
            draw_bars_buffer(self.audio_viz_data, color=viz_color)