-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- initial audio stream implementation w/ playback example
- audiovideostream wrapper
- Loading branch information
Showing
12 changed files
with
364 additions
and
68 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,3 @@ | ||
from .stream import Stream # noqa: F401 | ||
from .stream import Stream | ||
|
||
__all__ = ["Stream"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
from .audio_stream import AudioStream | ||
from .video_stream import VideoStream | ||
from .audio_video_stream import AudioVideoStream | ||
|
||
__all__ = ["AudioStream", "VideoStream", "AudioVideoStream"] |
117 changes: 117 additions & 0 deletions
117
src/pupil_labs/neon_recording/stream/av_stream/audio_stream.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
import numpy as np | ||
|
||
from ... import structlog | ||
from .av_load import _load_av_container | ||
from ..stream import Stream | ||
|
||
log = structlog.get_logger(__name__) | ||
|
||
|
||
def _convert_audio_data_to_recarray(audio_data, ts, ts_rel): | ||
log.debug("NeonRecording: Converting audio data to recarray format.") | ||
|
||
if audio_data.shape[0] != len(ts): | ||
log.error("NeonRecording: Length mismatch - audio_data and ts.") | ||
raise ValueError("audio_data and ts must have the same length") | ||
if len(ts) != len(ts_rel): | ||
log.error("NeonRecording: Length mismatch - ts and ts_rel.") | ||
raise ValueError("ts and ts_rel must have the same length") | ||
|
||
out = np.recarray( | ||
audio_data.shape[0], | ||
dtype=[("sample", "<f8"), ("ts", "<f8"), ("ts_rel", "<f8")], | ||
) | ||
out.sample = audio_data[:] | ||
out.ts = ts.astype(np.float64) | ||
out.ts_rel = ts_rel.astype(np.float64) | ||
|
||
return out | ||
|
||
|
||
class AudioStream(Stream): | ||
def __init__(self, name, file_name, recording, container=None, video_ts=None): | ||
super().__init__(name, recording) | ||
self._file_name = file_name | ||
self._backing_container = container | ||
self._video_ts = video_ts | ||
self._sample_rate = None | ||
self._n_samples = None | ||
|
||
self._load() | ||
|
||
@property | ||
def ts_rel(self): | ||
return self._ts_rel | ||
|
||
@property | ||
def sample_rate(self): | ||
return self._sample_rate | ||
|
||
@property | ||
def n_samples(self): | ||
return self._n_samples | ||
|
||
def _load(self): | ||
# if a backing_container is supplied, then a ts array is usually also supplied | ||
if self._backing_container is None: | ||
log.info(f"NeonRecording: Loading audio from: {self._file_name}.") | ||
self._backing_container, self._video_ts = _load_av_container( | ||
self._recording._rec_dir, self._file_name | ||
) | ||
|
||
self._sample_rate = self._backing_container.streams.audio[0].sample_rate | ||
self._n_frames = self._backing_container.streams.audio[0].frames | ||
self._samples_per_frame = self._backing_container.streams.audio[0].frames[0].samples | ||
|
||
ac = 0 | ||
audio_data = np.zeros( | ||
shape=self._samples_per_frame * (self._n_frames - 1), dtype=np.float64 | ||
) | ||
sample_start_times = np.zeros(shape=(self._n_frames - 1), dtype=np.float64) | ||
for sc, sample in enumerate(self._backing_container.streams.audio[0].frames): | ||
sample_start_times[sc] = sample.time | ||
|
||
for val in sample.to_ndarray()[0]: | ||
audio_data[ac] = val | ||
ac += 1 | ||
|
||
|
||
ts_c = 0 | ||
tdiffs = np.diff(sample_start_times) | ||
tdiffs = np.concatenate((tdiffs, [np.mean(tdiffs)])) | ||
ts_rel = np.zeros(audio_data.shape) | ||
for tc, start_time in enumerate(sample_start_times): | ||
for t in range(self._samples_per_frame): | ||
ts_rel[ts_c] = start_time + tdiffs[tc] * t / self._samples_per_frame | ||
ts_c += 1 | ||
|
||
|
||
self._ts_rel = ts_rel | ||
self._ts = self._ts_rel + self._video_ts[0] | ||
|
||
audio_data = _convert_audio_data_to_recarray(audio_data, self._ts, ts_rel) | ||
|
||
self._backing_data = audio_data | ||
self._data = audio_data[:] | ||
|
||
def _sample_linear_interp(self, sorted_ts): | ||
pass | ||
|
||
samples = self._data.sample | ||
|
||
interp_data = np.zeros( | ||
len(sorted_ts), | ||
dtype=[("sample", "<f8"), ("ts", "<f8"), ("ts_rel", "<f8")], | ||
).view(np.recarray) | ||
interp_data.sample = np.interp(sorted_ts, self._ts, samples, left=np.nan, right=np.nan) | ||
interp_data.ts = sorted_ts | ||
interp_data.ts_rel = np.interp( | ||
sorted_ts, self._ts, self._ts_rel, left=np.nan, right=np.nan | ||
) | ||
|
||
for d in interp_data: | ||
if not np.isnan(d.x): | ||
yield d | ||
else: | ||
yield None | ||
|
34 changes: 34 additions & 0 deletions
34
src/pupil_labs/neon_recording/stream/av_stream/audio_video_stream.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
from ... import structlog | ||
from .audio_stream import AudioStream | ||
from .video_stream import VideoStream | ||
from .av_load import _load_av_container | ||
|
||
log = structlog.get_logger(__name__) | ||
|
||
class AudioVideoStream(): | ||
def __init__(self, name, file_name, recording): | ||
self.name = name | ||
self._recording = recording | ||
self._file_name = file_name | ||
self._video_stream = None | ||
self._audio_stream = None | ||
|
||
self._load() | ||
|
||
@property | ||
def video_stream(self): | ||
return self._video_stream | ||
|
||
@property | ||
def audio_stream(self): | ||
return self._audio_stream | ||
|
||
def _load(self): | ||
log.info(f"NeonRecording: Loading audio-video: {self._file_name}.") | ||
|
||
container, video_ts = _load_av_container(self._recording._rec_dir, self._file_name) | ||
|
||
self._video_stream = VideoStream( | ||
"video", self._file_name, self._recording, container=container, ts=video_ts | ||
) | ||
self._audio_stream = AudioStream("audio", self._file_name, self._recording, container=container, video_ts=video_ts) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import pathlib | ||
|
||
import pupil_labs.video as plv | ||
|
||
from ... import structlog | ||
from ...time_utils import load_and_convert_tstamps | ||
|
||
log = structlog.get_logger(__name__) | ||
|
||
|
||
def _load_av_container(rec_dir: pathlib.Path, file_name: pathlib.Path | str): | ||
log.debug( | ||
f"NeonRecording: Loading video and associated timestamps: {file_name}." | ||
) | ||
|
||
if not (rec_dir / (file_name + ".mp4")).exists(): | ||
raise FileNotFoundError( | ||
f"File not found: {rec_dir / (file_name + '.mp4')}. Please double check the recording download." | ||
) | ||
|
||
container = plv.open(rec_dir / (file_name + ".mp4")) | ||
|
||
# use hardware ts | ||
# ts = load_and_convert_tstamps(rec_dir / (file_name + '.time_aux')) | ||
try: | ||
video_ts = load_and_convert_tstamps( | ||
rec_dir / (file_name + ".time") | ||
) | ||
except Exception as e: | ||
log.exception(f"Error loading timestamps: {e}") | ||
raise | ||
|
||
return container, video_ts |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,4 @@ | ||
import pathlib | ||
from typing import Optional | ||
|
||
import numpy as np | ||
|
||
|
Oops, something went wrong.