From d883df00473a964434c04aa8b78a9a1cacd94d95 Mon Sep 17 00:00:00 2001 From: Robert Ennis Date: Wed, 3 Apr 2024 02:17:38 +0200 Subject: [PATCH] - initial audio stream implementation w/ playback example - audiovideostream wrapper --- examples/make_gaze_overlay_video.py | 29 ++-- examples/plot_and_play_audio.ipynb | 130 ++++++++++++++++++ .../neon_recording/neon_recording.py | 6 +- .../neon_recording/stream/__init__.py | 2 +- .../stream/av_stream/__init__.py | 5 + .../stream/av_stream/audio_stream.py | 117 ++++++++++++++++ .../stream/av_stream/audio_video_stream.py | 34 +++++ .../stream/av_stream/av_load.py | 33 +++++ .../stream/{ => av_stream}/video_stream.py | 69 +++------- .../neon_recording/stream/gaze_stream.py | 1 - .../neon_recording/stream/imu/__init__.py | 4 +- .../neon_recording/stream/imu/imu_stream.py | 2 - 12 files changed, 364 insertions(+), 68 deletions(-) create mode 100644 examples/plot_and_play_audio.ipynb create mode 100644 src/pupil_labs/neon_recording/stream/av_stream/__init__.py create mode 100644 src/pupil_labs/neon_recording/stream/av_stream/audio_stream.py create mode 100644 src/pupil_labs/neon_recording/stream/av_stream/audio_video_stream.py create mode 100644 src/pupil_labs/neon_recording/stream/av_stream/av_load.py rename src/pupil_labs/neon_recording/stream/{ => av_stream}/video_stream.py (62%) diff --git a/examples/make_gaze_overlay_video.py b/examples/make_gaze_overlay_video.py index eef2f2f..4824e19 100644 --- a/examples/make_gaze_overlay_video.py +++ b/examples/make_gaze_overlay_video.py @@ -11,6 +11,8 @@ gaze = rec.gaze eye = rec.eye scene = rec.scene +scene_video = scene.video_stream +scene_audio = scene.audio_stream imu = rec.imu @@ -47,10 +49,12 @@ def convert_neon_pts_to_video_pts(neon_pts, neon_time_base, video_time_base): fps = 65535 container = plv.open("video.mp4", mode="w") -stream = container.add_stream("mpeg4", rate=fps) -stream.width = scene.width -stream.height = scene.height -stream.pix_fmt = "yuv420p" +out_video_stream = container.add_stream("mpeg4", rate=fps) +out_video_stream.width = scene.width +out_video_stream.height = scene.height +out_video_stream.pix_fmt = "yuv420p" + +out_audio_stream = container.add_stream("aac", rate=scene_audio.sample_rate) neon_time_base = scene.data[0].time_base video_time_base = Fraction(1, fps) @@ -105,9 +109,14 @@ def convert_neon_pts_to_video_pts(neon_pts, neon_time_base, video_time_base): pts_offset = 0 video_pts = 0 reached_video_start = False -for gaze_datum, eye_frame, scene_frame, imu_datum in zip( - gaze.sample(my_ts), eye.sample(my_ts), scene.sample(my_ts), imu.sample(my_ts) -): +combined_data = zip( + gaze.sample(my_ts), + eye.sample(my_ts), + scene_video.sample(my_ts), + imu.sample(my_ts), + scene_audio.sample(my_ts), +) +for gaze_datum, eye_frame, scene_frame, imu_datum, audio_sample in combined_data: scene_image = ( scene_frame.cv2 if scene_frame is not None @@ -163,7 +172,7 @@ def convert_neon_pts_to_video_pts(neon_pts, neon_time_base, video_time_base): frame.pts = pts_offset + video_pts frame.time_base = video_time_base - for packet in stream.encode(frame): + for packet in out_video_stream.encode(frame): container.mux(packet) if scene_frame is not None: @@ -175,8 +184,8 @@ def convert_neon_pts_to_video_pts(neon_pts, neon_time_base, video_time_base): pts_offset += avg_video_pts_size try: - # Flush stream - for packet in stream.encode(): + # Flush out_video_stream + for packet in out_video_stream.encode(): container.mux(packet) finally: # Close the file diff --git a/examples/plot_and_play_audio.ipynb b/examples/plot_and_play_audio.ipynb new file mode 100644 index 0000000..d196b09 --- /dev/null +++ b/examples/plot_and_play_audio.ipynb @@ -0,0 +1,130 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[2m2024-04-02T23:10:54.320855Z\u001b[0m [\u001b[32m\u001b[1minfo \u001b[0m] \u001b[1mNeonRecording: package loaded.\u001b[0m \u001b[36mfunc_name\u001b[0m=\u001b[35m\u001b[0m\n", + "\u001b[2m2024-04-02T23:10:54.836957Z\u001b[0m [\u001b[32m\u001b[1minfo \u001b[0m] \u001b[1mNeonRecording: Loading recording from ../tests/test_data/2024-01-25_22-19-10_test-f96b6e36/\u001b[0m \u001b[36mfunc_name\u001b[0m=\u001b[35m__init__\u001b[0m\n", + "\u001b[2m2024-04-02T23:10:54.853169Z\u001b[0m [\u001b[32m\u001b[1minfo \u001b[0m] \u001b[1mNeonRecording: Loading recording info\u001b[0m \u001b[36mfunc_name\u001b[0m=\u001b[35m__init__\u001b[0m\n", + "\u001b[2m2024-04-02T23:10:54.853732Z\u001b[0m [\u001b[32m\u001b[1minfo \u001b[0m] \u001b[1mNeonRecording: Loading wearer \u001b[0m \u001b[36mfunc_name\u001b[0m=\u001b[35m__init__\u001b[0m\n", + "\u001b[2m2024-04-02T23:10:54.854329Z\u001b[0m [\u001b[32m\u001b[1minfo \u001b[0m] \u001b[1mNeonRecording: Loading events \u001b[0m \u001b[36mfunc_name\u001b[0m=\u001b[35m__init__\u001b[0m\n", + "\u001b[2m2024-04-02T23:10:54.855061Z\u001b[0m [\u001b[32m\u001b[1minfo \u001b[0m] \u001b[1mNeonRecording: Loading data streams\u001b[0m \u001b[36mfunc_name\u001b[0m=\u001b[35m__init__\u001b[0m\n", + "\u001b[2m2024-04-02T23:10:54.855293Z\u001b[0m [\u001b[32m\u001b[1minfo \u001b[0m] \u001b[1mNeonRecording: Loading gaze data\u001b[0m \u001b[36mfunc_name\u001b[0m=\u001b[35m_load\u001b[0m\n", + "\u001b[2m2024-04-02T23:10:54.856980Z\u001b[0m [\u001b[32m\u001b[1minfo \u001b[0m] \u001b[1mNeonRecording: Loading IMU data\u001b[0m \u001b[36mfunc_name\u001b[0m=\u001b[35m_load\u001b[0m\n", + "\u001b[2m2024-04-02T23:10:54.896690Z\u001b[0m [\u001b[32m\u001b[1minfo \u001b[0m] \u001b[1mNeonRecording: Loading video: Neon Sensor Module v1 ps1.\u001b[0m \u001b[36mfunc_name\u001b[0m=\u001b[35m_load\u001b[0m\n", + "\u001b[2m2024-04-02T23:10:54.905216Z\u001b[0m [\u001b[32m\u001b[1minfo \u001b[0m] \u001b[1mNeonRecording: Loading audio-video: Neon Scene Camera v1 ps1.\u001b[0m \u001b[36mfunc_name\u001b[0m=\u001b[35m_load\u001b[0m\n", + "\u001b[2m2024-04-02T23:10:55.492774Z\u001b[0m [\u001b[32m\u001b[1minfo \u001b[0m] \u001b[1mNeonRecording: Finished loading recording.\u001b[0m \u001b[36mfunc_name\u001b[0m=\u001b[35m__init__\u001b[0m\n" + ] + } + ], + "source": [ + "import pupil_labs.neon_recording as nr\n", + "import matplotlib.pyplot as plt\n", + "\n", + "rec = nr.load(... rec_dir ...)" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [], + "source": [ + "audio = rec.streams[\"scene\"].audio_stream" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plt.subplot(2, 1, 1)\n", + "plt.title(\"Original audio signal\")\n", + "plt.plot(audio[\"ts_rel\"], audio[\"sample\"])\n", + "plt.grid()\n", + "plt.tight_layout()" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + " \n", + " " + ], + "text/plain": [ + "" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from IPython.display import Audio\n", + "Audio(audio[\"sample\"], rate=audio.sample_rate)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import av\n", + "import numpy as np\n", + "\n", + "samps = audio[\"sample\"].astype(np.float32)[:, np.newaxis].T\n", + "aframe = av.audio.frame.AudioFrame.from_ndarray(samps, format=\"flt\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.8" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/src/pupil_labs/neon_recording/neon_recording.py b/src/pupil_labs/neon_recording/neon_recording.py index b6462a4..405fde2 100644 --- a/src/pupil_labs/neon_recording/neon_recording.py +++ b/src/pupil_labs/neon_recording/neon_recording.py @@ -1,14 +1,12 @@ import json import pathlib -import numpy as np - from . import structlog from .calib import Calibration, parse_calib_bin from .stream.gaze_stream import GazeStream from .stream.imu import IMUStream from .stream.stream import Stream -from .stream.video_stream import VideoStream +from .stream.av_stream import AudioVideoStream, VideoStream from .time_utils import load_and_convert_tstamps, ns_to_s log = structlog.get_logger(__name__) @@ -183,8 +181,8 @@ def __init__(self, rec_dir_in: pathlib.Path | str): self._streams = { "gaze": GazeStream("gaze", self), "imu": IMUStream("imu", self), - "scene": VideoStream("scene", "Neon Scene Camera v1 ps1", self), "eye": VideoStream("eye", "Neon Sensor Module v1 ps1", self), + "scene": AudioVideoStream("scene", "Neon Scene Camera v1 ps1", self), } log.info("NeonRecording: Finished loading recording.") diff --git a/src/pupil_labs/neon_recording/stream/__init__.py b/src/pupil_labs/neon_recording/stream/__init__.py index 01ef138..1201730 100644 --- a/src/pupil_labs/neon_recording/stream/__init__.py +++ b/src/pupil_labs/neon_recording/stream/__init__.py @@ -1,3 +1,3 @@ -from .stream import Stream # noqa: F401 +from .stream import Stream __all__ = ["Stream"] diff --git a/src/pupil_labs/neon_recording/stream/av_stream/__init__.py b/src/pupil_labs/neon_recording/stream/av_stream/__init__.py new file mode 100644 index 0000000..569ac37 --- /dev/null +++ b/src/pupil_labs/neon_recording/stream/av_stream/__init__.py @@ -0,0 +1,5 @@ +from .audio_stream import AudioStream +from .video_stream import VideoStream +from .audio_video_stream import AudioVideoStream + +__all__ = ["AudioStream", "VideoStream", "AudioVideoStream"] diff --git a/src/pupil_labs/neon_recording/stream/av_stream/audio_stream.py b/src/pupil_labs/neon_recording/stream/av_stream/audio_stream.py new file mode 100644 index 0000000..7e5dd0b --- /dev/null +++ b/src/pupil_labs/neon_recording/stream/av_stream/audio_stream.py @@ -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", "