Video Playback: Difference between revisions
Jump to navigation
Jump to search
| (22 intermediate revisions by 2 users not shown) | |||
| Line 4: | Line 4: | ||
==Video playback== | ==Video playback== | ||
Note that the Lab Computer displays are typically set to 1920×1080 at 120Hz. We found that this is sufficient for most applications. There are possibilities to go higher. Later in this wiki we will explain how to build audio and video. We will start with playing video, both with and without audio. | |||
=== Python === | === Python psychopy 2024.2.4=== | ||
Example demonstrating how to play a video with audio: | Example demonstrating how to play a video with audio: | ||
<syntaxhighlight lang="python" line> | <syntaxhighlight lang="python" line> | ||
| Line 20: | Line 21: | ||
win = visual.Window(size=(1024, 768), fullscr=False, color=(0, 0, 0)) | win = visual.Window(size=(1024, 768), fullscr=False, color=(0, 0, 0)) | ||
movie = visual.MovieStim( | |||
win, filename=video_file, | win, filename=video_file, | ||
autoStart= False | autoStart= False | ||
| Line 34: | Line 35: | ||
# Main loop for video playback | # Main loop for video playback | ||
while | while not movie.isFinished: | ||
# Draw the current video frame | # Draw the current video frame | ||
movie.draw() | |||
win.flip() | win.flip() | ||
| Line 42: | Line 43: | ||
if 'q' in keys: | if 'q' in keys: | ||
break | break | ||
movie.stop() # stop playback | |||
del movie | |||
gc.collect() | |||
win.close() | win.close() | ||
| Line 47: | Line 51: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Example demonstrating how to play a video with audio | Example demonstrating how to play a video with audio disconnected: | ||
<syntaxhighlight lang="python" line> | <syntaxhighlight lang="python" line> | ||
from psychopy import logging, prefs | from psychopy import logging, prefs | ||
| Line 62: | Line 66: | ||
win = visual.Window(size=(1280, 720), fullscr=False, color=(0, 0, 0), units="pix") | win = visual.Window(size=(1280, 720), fullscr=False, color=(0, 0, 0), units="pix") | ||
movie = visual.MovieStim( | |||
win, filename=video_file, | win, filename=video_file, | ||
size=None, # Use the native video size | size=None, # Use the native video size | ||
| Line 84: | Line 88: | ||
audio.play() | audio.play() | ||
time.sleep(0.04) | time.sleep(0.04) | ||
movie.play() | |||
movie_start_time = core.getTime() | |||
while | while not movie.isFinished: | ||
# Draw the current video frame | # Draw the current video frame | ||
movie.draw() | |||
win.flip() | win.flip() | ||
| Line 96: | Line 100: | ||
audio.stop() | audio.stop() | ||
break | break | ||
movie.stop() # stop playback | |||
del movie | |||
gc.collect() | |||
# Close the PsychoPy window | # Close the PsychoPy window | ||
win.close() | win.close() | ||
core.quit() | core.quit() | ||
</syntaxhighlight> | |||
Example demonstrating if video and audio encoding are correct: | |||
<syntaxhighlight lang="python" line> | |||
import subprocess | |||
import json | |||
file_path = "tick_rhythm_combined_1min.mp4" | |||
def check_video_file(file_path): | |||
try: | |||
# Run ffprobe to get file metadata in JSON format | |||
result = subprocess.run( | |||
[ | |||
"ffprobe", | |||
"-v", "error", | |||
"-show_streams", | |||
"-show_format", | |||
"-print_format", "json", | |||
file_path | |||
], | |||
stdout=subprocess.PIPE, | |||
stderr=subprocess.PIPE, | |||
text=True | |||
) | |||
metadata = json.loads(result.stdout) | |||
except Exception as e: | |||
print(f"Error running ffprobe: {e}") | |||
return | |||
# Check for video stream | |||
video_stream = next((stream for stream in metadata['streams'] if stream['codec_type'] == 'video'), None) | |||
if video_stream: | |||
# Check video codec | |||
video_codec = video_stream.get('codec_name') | |||
if video_codec == 'h264': | |||
print("Video codec: H.264") | |||
else: | |||
print(f"ERROR: Video codec is NOT H.264 (Found: {video_codec})") | |||
# Extract and report frame rate | |||
if 'r_frame_rate' in video_stream: | |||
raw_frame_rate = video_stream['r_frame_rate'] | |||
calculated_frame_rate = eval(raw_frame_rate) # Convert string like "30/1" to float | |||
print(f"Frame rate: {calculated_frame_rate:.2f} FPS (raw: {raw_frame_rate})") | |||
else: | |||
print("ERROR: Could not determine raw frame rate from metadata.") | |||
# Check for constant frame rate | |||
if video_stream.get('avg_frame_rate'): | |||
avg_frame_rate = eval(video_stream['avg_frame_rate']) | |||
if abs(avg_frame_rate - calculated_frame_rate) < 0.01: | |||
print("Frame rate: Constant") | |||
else: | |||
print(f"ERROR: Frame rate is NOT constant (avg_frame_rate: {avg_frame_rate:.2f} FPS)") | |||
else: | |||
print("ERROR: Could not determine average frame rate consistency.") | |||
# Check for frame drops | |||
try: | |||
frame_info_result = subprocess.run( | |||
[ | |||
"ffprobe", | |||
"-v", "error", | |||
"-select_streams", "v:0", | |||
"-show_entries", "frame=pkt_pts_time", | |||
"-of", "csv=p=0", | |||
file_path | |||
], | |||
stdout=subprocess.PIPE, | |||
stderr=subprocess.PIPE, | |||
text=True | |||
) | |||
# Filter out empty or invalid lines | |||
frame_times = [ | |||
float(line.strip()) for line in frame_info_result.stdout.splitlines() | |||
if line.strip() # Exclude empty lines | |||
] | |||
expected_interval = 1.0 / calculated_frame_rate # Expected time between frames | |||
frame_drops = [ | |||
i for i, (t1, t2) in enumerate(zip(frame_times, frame_times[1:])) | |||
if abs(t2 - t1 - expected_interval) > 0.01 # Tolerance for irregularity | |||
] | |||
if frame_drops: | |||
print(f"ERROR: Detected frame drops at frames: {frame_drops}") | |||
else: | |||
print("No frame drops detected.") | |||
except Exception as e: | |||
print(f"Error analyzing frames for drops: {e}") | |||
else: | |||
print("ERROR: No video stream found") | |||
# Check for audio stream | |||
audio_stream = next((stream for stream in metadata['streams'] if stream['codec_type'] == 'audio'), None) | |||
if audio_stream: | |||
# Check audio codec | |||
audio_codec = audio_stream.get('codec_name') | |||
if audio_codec == 'pcm_s16le': | |||
print("Audio codec: WAV (PCM)") | |||
else: | |||
print(f"ERROR: Audio codec is NOT WAV (PCM) (Found: {audio_codec})") | |||
# Check sample rate | |||
sample_rate = audio_stream.get('sample_rate') | |||
if sample_rate == "44100": | |||
print("Audio sample rate: 44.1 kHz") | |||
else: | |||
print(f"ERROR: Audio sample rate is NOT 44.1 kHz (Found: {sample_rate} Hz)") | |||
else: | |||
print("ERROR: No audio stream found") | |||
# Check synchronization | |||
if video_stream and audio_stream: | |||
video_start_pts = float(video_stream.get('start_time', 0)) | |||
audio_start_pts = float(audio_stream.get('start_time', 0)) | |||
if abs(video_start_pts - audio_start_pts) < 0.01: # Tolerance for synchronization | |||
print("Video and audio are synchronized.") | |||
else: | |||
print(f"ERROR: Video and audio are NOT synchronized. Start difference: {abs(video_start_pts - audio_start_pts):.3f} seconds") | |||
else: | |||
print("ERROR: Could not determine synchronization (missing video or audio streams).") | |||
# Example usage | |||
if __name__ == "__main__": | |||
check_video_file(file_path) | |||
</syntaxhighlight> | </syntaxhighlight> | ||
| Line 118: | Line 250: | ||
subprocess.run(['ffmpeg', '-i', input_file, '-an', output_video]) | subprocess.run(['ffmpeg', '-i', input_file, '-an', output_video]) | ||
subprocess.run(['ffmpeg', '-i', input_file, '-vn', '-acodec', 'pcm_s16le', '-ar', ' | subprocess.run(['ffmpeg', '-i', input_file, '-vn', '-acodec', 'pcm_s16le', '-ar', '48000', output_audio]) | ||
print(f"Video saved to: {output_video}") | print(f"Video saved to: {output_video}") | ||
| Line 155: | Line 287: | ||
Stabilize the camera and avoid automatic exposure, white balance, or focus during recording to prevent inconsistencies. | Stabilize the camera and avoid automatic exposure, white balance, or focus during recording to prevent inconsistencies. | ||
Record in a controlled environment with consistent lighting and minimal background distractions. | Record in a controlled environment with consistent lighting and minimal background distractions. | ||
The TSG recomends to use the [[Elgato Facecam]] for high quality video recording. | |||
===Video Settings=== | ===Video Settings=== | ||
| Line 162: | Line 294: | ||
|- | |- | ||
!File format | !File format | ||
|.mp4 (H.264 codec(libx264)) | |.mp4 (H.264 [[media:Openh264-1.8.0-win64_.zip | codec(libx264)]]) | ||
|- | |- | ||
!Frame rate | !Frame rate | ||
| Line 302: | Line 434: | ||
=== Python === | === Python === | ||
Example demonstrating how to check and play your audio: | Example demonstrating how to check your os settings, audio file and play your audio: | ||
<syntaxhighlight lang="python" line> | <syntaxhighlight lang="python" line> | ||
#!/usr/bin/env python3.10 | #!/usr/bin/env python3.10 | ||
| Line 359: | Line 491: | ||
==FFmpeg== | ==FFmpeg== | ||
{{See also|FFmpeg}} | |||
===Synchronization=== | ===Synchronization=== | ||
Ensure the audio and video streams have consistent timestamps: | Ensure the audio and video streams have consistent timestamps: | ||
| Line 375: | Line 508: | ||
Here’s a command that encodes video and audio while maintaining high time accuracy: | Here’s a command that encodes video and audio while maintaining high time accuracy: | ||
<pre> | <pre> | ||
ffmpeg -i input.mp4 -c:v libx264 -preset slow -crf 18 -vsync cfr -g 30 -c:a pcm_s16le -ar | ffmpeg -i input.mp4 -c:v libx264 -preset slow -crf 18 -vsync cfr -g 30 -c:a pcm_s16le -ar 48000 -fflags +genpts -async 1 output.mp4 | ||
-c:v libx264: Encode video using H.264. | -c:v libx264: Encode video using H.264. | ||
-preset slow: Optimize for quality and compression efficiency. | -preset slow: Optimize for quality and compression efficiency. | ||
| Line 386: | Line 519: | ||
</pre> | </pre> | ||
=== | ===Enumeration=== | ||
- '''Ensure Low Latency''': If you're processing video/audio in real time, use low-latency settings (e.g., -tune zerolatency for H.264). | - '''Ensure Low Latency''': If you're processing video/audio in real time, use low-latency settings (e.g., -tune zerolatency for H.264). | ||
| Line 392: | Line 525: | ||
- '''Testing''': Always test playback on different devices or players to confirm synchronization. | - '''Testing''': Always test playback on different devices or players to confirm synchronization. | ||
==Editing== | ==Editing== | ||
Alternatively, you can use '''Shotcut''', a simple open-source editor, available here: https://shotcut.org/ | |||
Another one is DaVinci Resolve for editing and converting video files. DaVinci Resolve is a free, professional-grade editing program, available here: https://www.blackmagicdesign.com/products/davinciresolve | |||
for | |||