Video Playback

From TSG Doc
Jump to navigation Jump to search

When using video in your experiment, especially when presenting time-critical stimuli, special care should be taken to optimize the video and audio settings on multiple levels (hardware, OS, script), as many things can go wrong along the way.

This page outlines some best practices; however, we advise to always consult a TSG member if you plan to run a video experiment in the labs.

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

Example demonstrating how to play a video with audio:

 1from psychopy import logging, prefs
 2prefs.hardware['audioLib'] = ['PTB']
 3prefs.hardware['audioLatencyMode'] = 2
 4
 5from psychopy import visual, core, event
 6from psychopy.hardware import keyboard
 7
 8# File paths for video and audio
 9video_file = "tick_rhythm_combined_30min.mp4"
10
11win = visual.Window(size=(1024, 768), fullscr=False, color=(0, 0, 0))
12
13video = visual.VlcMovieStim(
14    win, filename=video_file,
15    autoStart= False
16)
17
18kb = keyboard.Keyboard()
19
20# Play the video
21win.flip()
22core.wait(3.0)
23video.play()
24video_start_time = core.getTime()
25
26# Main loop for video playback
27while video.status != visual.FINISHED:
28    # Draw the current video frame
29    video.draw()
30    win.flip()
31
32    keys = kb.getKeys(['q'], waitRelease=True)
33    if 'q' in keys:
34        break
35
36win.close()
37core.quit()

Example demonstrating how to play a video with audio disconnected:

 1from psychopy import logging, prefs
 2from psychopy import visual, core, sound, event
 3import time
 4
 5prefs.hardware['audioLib'] = ['PTB']
 6prefs.hardware['audioLatencyMode'] = 2
 7
 8# File paths for video and audio
 9video_file = "tick_rhythm_30min.mp4"
10audio_file = "tick_rhythm_30min.wav"
11
12win = visual.Window(size=(1280, 720), fullscr=False, color=(0, 0, 0), units="pix")
13
14video = visual.VlcMovieStim(
15    win, filename=video_file,
16    size=None,  # Use the native video size
17    pos=[0, 0], 
18    flipVert=False,
19    flipHoriz=False,
20    loop=False,
21    autoStart=False,
22    noAudio=True,
23    volume=100,
24    name='myMovie'
25)
26
27# Load the audio
28audio = sound.Sound(audio_file, -1)
29
30# Synchronize audio and video playback
31win.flip()
32time.sleep(5)
33 
34audio.play()
35time.sleep(0.04)
36video.play()
37video_start_time = core.getTime()
38
39while video.status != visual.FINISHED:
40    # Draw the current video frame
41    video.draw()
42    win.flip()
43
44    # Check for keypress to quit
45    if "q" in event.getKeys():
46        audio.stop()
47        break
48
49# Close the PsychoPy window
50win.close()
51core.quit()

Example demonstrating if video and audio encoding are correct:

  1import subprocess
  2import json
  3
  4file_path = "tick_rhythm_combined_1min.mp4"
  5
  6def check_video_file(file_path):
  7    try:
  8        # Run ffprobe to get file metadata in JSON format
  9        result = subprocess.run(
 10            [
 11                "ffprobe",
 12                "-v", "error",
 13                "-show_streams",
 14                "-show_format",
 15                "-print_format", "json",
 16                file_path
 17            ],
 18            stdout=subprocess.PIPE,
 19            stderr=subprocess.PIPE,
 20            text=True
 21        )
 22        metadata = json.loads(result.stdout)
 23    except Exception as e:
 24        print(f"Error running ffprobe: {e}")
 25        return
 26    
 27    # Check for video stream
 28    video_stream = next((stream for stream in metadata['streams'] if stream['codec_type'] == 'video'), None)
 29    if video_stream:
 30        # Check video codec
 31        video_codec = video_stream.get('codec_name')
 32        if video_codec == 'h264':
 33            print("Video codec: H.264")
 34        else:
 35            print(f"ERROR: Video codec is NOT H.264 (Found: {video_codec})")
 36
 37        # Extract and report frame rate
 38        if 'r_frame_rate' in video_stream:
 39            raw_frame_rate = video_stream['r_frame_rate']
 40            calculated_frame_rate = eval(raw_frame_rate)  # Convert string like "30/1" to float
 41            print(f"Frame rate: {calculated_frame_rate:.2f} FPS (raw: {raw_frame_rate})")
 42        else:
 43            print("ERROR: Could not determine raw frame rate from metadata.")
 44
 45        # Check for constant frame rate
 46        if video_stream.get('avg_frame_rate'):
 47            avg_frame_rate = eval(video_stream['avg_frame_rate'])
 48            if abs(avg_frame_rate - calculated_frame_rate) < 0.01:
 49                print("Frame rate: Constant")
 50            else:
 51                print(f"ERROR: Frame rate is NOT constant (avg_frame_rate: {avg_frame_rate:.2f} FPS)")
 52        else:
 53            print("ERROR: Could not determine average frame rate consistency.")
 54        
 55        # Check for frame drops
 56        try:
 57            frame_info_result = subprocess.run(
 58                [
 59                    "ffprobe",
 60                    "-v", "error",
 61                    "-select_streams", "v:0",
 62                    "-show_entries", "frame=pkt_pts_time",
 63                    "-of", "csv=p=0",
 64                    file_path
 65                ],
 66                stdout=subprocess.PIPE,
 67                stderr=subprocess.PIPE,
 68                text=True
 69            )
 70            # Filter out empty or invalid lines
 71            frame_times = [
 72                float(line.strip()) for line in frame_info_result.stdout.splitlines()
 73                if line.strip()  # Exclude empty lines
 74            ]
 75            expected_interval = 1.0 / calculated_frame_rate  # Expected time between frames
 76            frame_drops = [
 77                i for i, (t1, t2) in enumerate(zip(frame_times, frame_times[1:]))
 78                if abs(t2 - t1 - expected_interval) > 0.01  # Tolerance for irregularity
 79            ]
 80            if frame_drops:
 81                print(f"ERROR: Detected frame drops at frames: {frame_drops}")
 82            else:
 83                print("No frame drops detected.")
 84        except Exception as e:
 85            print(f"Error analyzing frames for drops: {e}")
 86    else:
 87        print("ERROR: No video stream found")
 88    
 89    # Check for audio stream
 90    audio_stream = next((stream for stream in metadata['streams'] if stream['codec_type'] == 'audio'), None)
 91    if audio_stream:
 92        # Check audio codec
 93        audio_codec = audio_stream.get('codec_name')
 94        if audio_codec == 'pcm_s16le':
 95            print("Audio codec: WAV (PCM)")
 96        else:
 97            print(f"ERROR: Audio codec is NOT WAV (PCM) (Found: {audio_codec})")
 98        
 99        # Check sample rate
100        sample_rate = audio_stream.get('sample_rate')
101        if sample_rate == "44100":
102            print("Audio sample rate: 44.1 kHz")
103        else:
104            print(f"ERROR: Audio sample rate is NOT 44.1 kHz (Found: {sample_rate} Hz)")
105    else:
106        print("ERROR: No audio stream found")
107    
108    # Check synchronization
109    if video_stream and audio_stream:
110        video_start_pts = float(video_stream.get('start_time', 0))
111        audio_start_pts = float(audio_stream.get('start_time', 0))
112        if abs(video_start_pts - audio_start_pts) < 0.01:  # Tolerance for synchronization
113            print("Video and audio are synchronized.")
114        else:
115            print(f"ERROR: Video and audio are NOT synchronized. Start difference: {abs(video_start_pts - audio_start_pts):.3f} seconds")
116    else:
117        print("ERROR: Could not determine synchronization (missing video or audio streams).")
118
119# Example usage
120if __name__ == "__main__":
121    check_video_file(file_path)

Example demonstrating how to disconnect audio from video:

 1import os
 2import subprocess
 3
 4input_file = 'tick_rhythm_combined_1min.mp4'
 5
 6directory = os.path.dirname(input_file)
 7base_name = os.path.splitext(os.path.basename(input_file))[0]
 8
 9output_video = os.path.join(directory, f"{base_name}_video_only.mp4")
10output_audio = os.path.join(directory, f"{base_name}_audio_only.wav")
11
12subprocess.run(['ffmpeg', '-i', input_file, '-an', output_video])
13
14subprocess.run(['ffmpeg', '-i', input_file, '-vn', '-acodec', 'pcm_s16le', '-ar', '48000', output_audio])
15
16print(f"Video saved to: {output_video}")
17print(f"Audio saved to: {output_audio}")

Example demonstrating how to combine audio and video:

 1import os
 2import subprocess
 3
 4# --- Inputs
 5video_file = 'tick_rhythm_combined_1min_video_only.mp4'   # Your video-only file
 6audio_file = 'mic_segment.wav'                            # Your trimmed audio
 7output_file = 'final_synced_output.mp4'                   # Output file name
 8
 9# --- FFmpeg command to combine
10subprocess.run([
11    'ffmpeg',
12    '-i', video_file,
13    '-i', audio_file,
14    '-c:v', 'copy',               # Copy video stream as-is
15    '-c:a', 'aac',                # Encode audio with AAC (widely compatible)
16    '-shortest',                 # Trim to the shortest stream (prevents overhang)
17    output_file
18])
19
20print(f"Synchronized video saved to: {output_file}")

Video encoding

When recording video for stimulus material or as input for your experiment, please: Use a high-quality camera, with settings appropriate for your application (e.g., frame rate, resolution). Use a high-quality recorder or capture device, capable of recording at 1080p (1920×1080) and 60fps or higher. 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. The TSG recomends to use the facecam for high quality video recording.

Video Settings

We recommend using the following settings:

File format .mp4 (H.264 codec(libx264))
Frame rate 60 fps (frames per second)
Resolution 1920×1080 (Full HD) or match your experiment's display settings
Bitrate 10-20 Mbps for Full HD video
Constant Frame Rate (CFR) enforce a constant frame rate

Windows Settings

Windows 10 has a habit of automatically enabling video enhancements or unnecessary processing features, which can interfere with smooth playback. Therefore, please make sure these are disabled:

right click background → Display settings → Graphics Settings. If available, disable "Hardware-accelerated GPU scheduling" for critical timing experiments.

For specific applications (e.g., PsychoPy), under "Graphics Performance Preference," set them to "High Performance" to ensure they use the dedicated GPU.

Python

Example demonstrating how to record a video with a facecam:

 1#!/usr/bin/env python3.10
 2# -*- coding: utf-8 -*-
 3
 4import datetime
 5import cv2
 6import ctypes
 7import ffmpegcv
 8
 9#set sleep to 1ms accuracy
10winmm = ctypes.WinDLL('winmm')
11winmm.timeBeginPeriod(1)
12
13def configure_webcam(cam_id, width=1920, height=1080, fps=60):
14    cap = cv2.VideoCapture(cam_id, cv2.CAP_DSHOW)
15    if not cap.isOpened():
16        print(f"Error: Couldn't open webcam {cam_id}.")
17        return None
18
19    # Try to set each property
20    cap.set(cv2.CAP_PROP_FRAME_WIDTH, width)
21    cap.set(cv2.CAP_PROP_FRAME_HEIGHT, height)
22    cap.set(cv2.CAP_PROP_FPS, fps)
23
24    # Read back the values
25    actual_width = cap.get(cv2.CAP_PROP_FRAME_WIDTH)
26    actual_height = cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
27    actual_fps = cap.get(cv2.CAP_PROP_FPS)
28
29    print(f"Resolution set to: {actual_width}x{actual_height}")
30    print(f"FPS set to: {actual_fps}")
31
32    return cap
33
34def getWebcamData():
35    global frame_width
36    global frame_height
37
38    print("opening webcam...")
39    camera = configure_webcam(1, frame_width, frame_height)
40    time_stamp = datetime.datetime.now().strftime('%Y-%m-%d %H-%M-%S')
41    file_name = time_stamp +'_output.avi'
42    video_writer = ffmpegcv.VideoWriter(file_name, 'h264', fps=freq)
43    
44    while True:
45        grabbed = camera.grab()
46        if grabbed:
47            grabbed, frame = camera.retrieve()
48            
49            video_writer.write(frame)  # Write the video to the file system
50            
51            frame = cv2.resize(frame, (int(frame_width/4),int(frame_height/4)))
52            cv2.imshow("Frame", frame)  # show the frame to our screen
53        
54        if cv2.waitKey(1) & 0xFF == ord('q'):
55            break
56
57freq = 60
58frame_width = 1920 
59frame_height = 1080
60
61getWebcamData()
62
63cv2.destroyAllWindows()

Audio encoding

Audio Settings

We recommend using the following settings for audio:

Codec lossless or high-quality codecs
PCM (WAV) uncompressed
Sample Rate 48 kHz

Set your audio for low-latency, high-accuracy playback with ffmpeg:

   ffmpeg -i input.wav -ar 48000 -ac 2 -sample_fmt s16 output_fixed.wav

   Explanation:
   -ar 48000 → Set sample rate to 48000 Hz (standard for ASIO/Windows audio, matches most soundcards)
   -ac 2 → Set 2 channels (stereo)
   -sample_fmt s16 → Use 16-bit signed integer samples

Windows Settings

Windows 10 Settings to check

sound → Playback → right-click → Properties → Advanced Tab:

   - Set Default Format to 48000 Hz, 16 bit, Studio Quality.

   - Disable sound enhancements.

   - In the same properties window, go to Enhancements tab → Disable all enhancements.

   - Exclusive Mode:

   - In the same Advanced tab.

   - Allow applications to take exclusive control of this device → CHECKED

   - Give exclusive mode applications priority → CHECKED

Python

Example demonstrating how to check your os settings, audio file and play your audio:

 1#!/usr/bin/env python3.10
 2
 3import psychopy
 4print(psychopy.__version__)
 5import sys
 6print(sys.version)
 7
 8import keyboard
 9from psychopy import prefs
10from psychopy import visual, core, event
11
12from psychopy.sound import backend_ptb
13# 0: No special settings (default, not optimized)
14# 1: Try low-latency but allow some delay
15# 2: Aggressive low-latency
16# 3: Exclusive mode, lowest latency but may not work on all systems
17backend_ptb.SoundPTB.latencyMode = 2
18
19prefs.hardware['audioLib'] = ['PTB']
20prefs.hardware['audioDriver'] = ['ASIO']
21prefs.hardware['audioDevice'] = ['ASIO4ALL v2']
22from psychopy import sound
23
24# --- OS-level audio device sample rate ---
25default_output = sd.query_devices(kind='output')
26print("\nDefault output device info (OS level):")
27print(f"  Name: {default_output['name']}")
28print(f"  Default Sample Rate: {default_output['default_samplerate']} Hz")
29print(f"  Max Output Channels: {default_output['max_output_channels']}")
30
31# Confirm the audio library and output settings
32print(f"Using {sound.audioLib} for sound playback.")
33print(f"Audio library options: {prefs.hardware['audioLib']}")
34print(f"Audio driver: {prefs.hardware.get('audioDriver', 'Default')}")
35print(f"Audio device: {prefs.hardware.get('audioDevice', 'Default')}")
36
37audio_file = 'tick_rhythm_5min.wav'
38
39print("Creating sound...")
40wave_file = sound.Sound(audio_file)
41
42print("Playing sound...")
43wave_file.play()
44
45while not keyboard.is_pressed('q'):
46    pass
47
48# Clean up
49print("Exiting...")
50win.close()
51core.quit()

FFmpeg

Synchronization

Ensure the audio and video streams have consistent timestamps:

FFmpeg Options:

       -fflags +genpts: Generates accurate presentation timestamps (PTS) for the video.

       -async 1: Synchronizes audio and video when they drift.

       -map 0:v:0 and -map 0:a:0: Explicitly map video and audio streams to avoid accidental mismatches.

Recommended FFmpeg Command

Here’s a command that encodes video and audio while maintaining high time accuracy:

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.
	-preset slow: Optimize for quality and compression efficiency.
	-crf 18: Adjusts quality (lower = better; range: 0–51).
	-vsync cfr: Enforces constant frame rate.
	-c:a pcm_s16le: Encodes audio in uncompressed WAV format.
	-ar 48000: Sets audio sample rate to 48.0 kHz.
	-fflags +genpts: Ensures accurate timestamps.
	-async 1: Synchronizes audio and video streams.

Enumeration

- Ensure Low Latency: If you're processing video/audio in real time, use low-latency settings (e.g., -tune zerolatency for H.264).

- Avoid Resampling: If possible, use the original frame rate and sample rate to avoid timing mismatches.

- Testing: Always test playback on different devices or players to confirm synchronization.

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