143 lines
4.4 KiB
Python
143 lines
4.4 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
"""
|
|
|
|
"""
|
|
import cv2 as cv
|
|
import queue
|
|
import sounddevice as sd
|
|
import time
|
|
from .lasp_atomic import Atomic
|
|
from threading import Thread, Condition
|
|
import h5py
|
|
|
|
|
|
class Playback:
|
|
"""
|
|
Play back a single channel from a
|
|
"""
|
|
|
|
def __init__(self, fn1, channel=0, video=False, verbose=True):
|
|
"""
|
|
Initialize a Playback class for playing back audio
|
|
|
|
Args:
|
|
fn1: Filename of the measurement file
|
|
channel: channel index to play back
|
|
video: if True and video is available in the measurement file,
|
|
video will also be shown
|
|
verbose: print out status messages to stdout
|
|
"""
|
|
ext = '.h5'
|
|
if fn1[-3:] != ext:
|
|
fn = fn1 + ext
|
|
else:
|
|
fn = fn1
|
|
|
|
print('Filename: ', fn)
|
|
self._fn = fn
|
|
|
|
self.channel = channel
|
|
self._video = video
|
|
self._aframectr = Atomic(0)
|
|
self._running = Atomic(False)
|
|
self._running_cond = Condition()
|
|
if video:
|
|
self._video_queue = queue.Queue()
|
|
|
|
with h5py.File(fn, 'r') as f:
|
|
self.samplerate = f.attrs['samplerate']
|
|
self.nchannels = f.attrs['nchannels']
|
|
self.blocksize = f.attrs['blocksize']
|
|
self.nblocks = f['audio'].shape[0]
|
|
if verbose:
|
|
print('Sample rate: ', self.samplerate)
|
|
print('Number of audio frames: ', self.nblocks*self.blocksize)
|
|
print('Recording time: ', self.nblocks
|
|
* self.blocksize/self.samplerate)
|
|
|
|
if video:
|
|
try:
|
|
f['video']
|
|
self._video_frame_positions = f['video_frame_positions'][:]
|
|
except AttributeError:
|
|
print('No video available in measurement file.'
|
|
'Disabling video')
|
|
self._video = False
|
|
|
|
@property
|
|
def T(self):
|
|
"""
|
|
Returns
|
|
the lenght of the measurement in seconds
|
|
"""
|
|
return self._nblocks*self._blocksize/self._samplerate
|
|
|
|
def start(self):
|
|
"""
|
|
Start the playback
|
|
"""
|
|
with h5py.File(self._fn, 'r') as f:
|
|
self._ad = f['audio']
|
|
dtype = self._ad.dtype
|
|
dtype_str = str(dtype)
|
|
stream = sd.OutputStream(samplerate=self.samplerate,
|
|
blocksize=self.blocksize,
|
|
channels=1,
|
|
dtype=dtype_str,
|
|
callback=self.audio_callback)
|
|
|
|
self._running <<= True
|
|
if self._video:
|
|
self._vd = f['video']
|
|
videothread = Thread(target=self.video_thread_fcn)
|
|
videothread.start()
|
|
|
|
with stream:
|
|
try:
|
|
with self._running_cond:
|
|
while self._running:
|
|
self._running_cond.wait()
|
|
except KeyboardInterrupt:
|
|
print('Keyboard interrupt. Quit playback')
|
|
|
|
if self._video:
|
|
videothread.join()
|
|
|
|
def audio_callback(self, outdata, frames, time, status):
|
|
"""
|
|
|
|
"""
|
|
aframectr = self._aframectr()
|
|
if aframectr < self.nblocks:
|
|
outdata[:, 0] = self._ad[aframectr, :, self.channel]
|
|
self._aframectr += 1
|
|
else:
|
|
self._running <<= False
|
|
with self._running_cond:
|
|
self._running_cond.notify()
|
|
|
|
def video_thread_fcn(self):
|
|
frame_ctr = 0
|
|
nframes = self._vd.shape[0]
|
|
video_frame_positions = self._video_frame_positions
|
|
assert video_frame_positions.shape[0] == nframes
|
|
|
|
while self._running and frame_ctr < nframes:
|
|
frame = self._vd[frame_ctr]
|
|
|
|
# Find corresponding audio frame
|
|
corsp_aframe = video_frame_positions[frame_ctr]
|
|
|
|
while self._aframectr() <= corsp_aframe:
|
|
print('Sleep video...')
|
|
time.sleep(self.blocksize/self.samplerate/2)
|
|
|
|
cv.imshow("Video output. Press 'q' to quit", frame)
|
|
if cv.waitKey(1) & 0xFF == ord('q'):
|
|
self._running <<= False
|
|
|
|
frame_ctr += 1
|
|
print('Ending video playback thread')
|
|
cv.destroyAllWindows()
|