feat: add hardware accelerated main-stream HD previews
This commit is contained in:
+27
-13
@@ -1,4 +1,4 @@
|
||||
"""On-demand H.264/AAC browser preview from a local MediaMTX substream."""
|
||||
"""On-demand H.264/AAC previews; main-stream HD uses Intel VAAPI."""
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
@@ -12,11 +12,13 @@ from urllib.parse import quote
|
||||
|
||||
def main():
|
||||
name = os.environ.get('MTX_PATH', '')
|
||||
if not re.fullmatch(r'cam_[a-f0-9]{16}_compat', name):
|
||||
match = re.fullmatch(r'(cam_[a-f0-9]{16})_(compat|hd)', name)
|
||||
if not match:
|
||||
raise SystemExit('Invalid preview path')
|
||||
data = Path(sys.argv[1]).resolve()
|
||||
config = json.loads((data / 'mediamtx.yml').read_text(encoding='utf8'))
|
||||
original = name.removesuffix('_compat') + '_sub'
|
||||
hd = match[2] == 'hd'
|
||||
original = match[1] + ('_main' if hd else '_sub')
|
||||
if config['paths'].get(original, {}).get('source', 'publisher') == 'publisher':
|
||||
raise SystemExit('Camera is disabled')
|
||||
secret = next(u['pass'] for u in config['authInternalUsers'] if u['user'] == 'vision')
|
||||
@@ -24,17 +26,29 @@ def main():
|
||||
executable = shutil.which('ffmpeg')
|
||||
if not executable:
|
||||
raise SystemExit('Install FFmpeg to enable compatible preview')
|
||||
# A bounded 768x432 / 10 fps preview keeps six lenses practical on a small
|
||||
# computer. Original main/sub streams and recording retain their own codecs.
|
||||
# HD reads the actual main stream. Decode, resize and encode on the GPU;
|
||||
# never silently start six high-resolution software decoders on a small CPU.
|
||||
acceleration = []
|
||||
if hd:
|
||||
device = os.environ.get('VISION_VAAPI_DEVICE', '/dev/dri/renderD128')
|
||||
if not os.access(device, os.R_OK | os.W_OK):
|
||||
raise SystemExit('HD preview requires an accessible VAAPI render device')
|
||||
acceleration = ['-hwaccel', 'vaapi', '-hwaccel_device', device,
|
||||
'-hwaccel_output_format', 'vaapi', '-extra_hw_frames', '8']
|
||||
video = ['-vf', 'fps=15,scale_vaapi=w=1920:h=1080:format=nv12:force_original_aspect_ratio=decrease:force_divisible_by=2',
|
||||
'-c:v', 'h264_vaapi', '-profile:v', 'main', '-level:v', '4.1',
|
||||
'-rc_mode', 'VBR', '-b:v', '3500k', '-maxrate', '5000k',
|
||||
'-bufsize', '10000k', '-g', '30', '-bf', '0']
|
||||
else:
|
||||
video = ['-vf', 'fps=10,scale=768:432:force_original_aspect_ratio=decrease:force_divisible_by=2',
|
||||
'-c:v', 'libx264', '-threads', '1', '-preset', 'veryfast',
|
||||
'-tune', 'zerolatency', '-profile:v', 'baseline', '-level:v', '3.1',
|
||||
'-pix_fmt', 'yuv420p', '-crf', '26', '-maxrate', '1100k',
|
||||
'-bufsize', '2200k', '-g', '20', '-keyint_min', '20', '-sc_threshold', '0']
|
||||
command = [executable, '-hide_banner', '-loglevel', 'error', '-nostdin',
|
||||
'-threads', '1', '-filter_threads', '1', '-rtsp_transport', 'tcp',
|
||||
'-timeout', '15000000', '-i', base + original,
|
||||
'-map', '0:v:0', '-map', '0:a:0?', '-sn', '-dn',
|
||||
'-vf', 'fps=10,scale=768:432:force_original_aspect_ratio=decrease:force_divisible_by=2',
|
||||
'-c:v', 'libx264', '-threads', '1', '-preset', 'veryfast',
|
||||
'-tune', 'zerolatency', '-profile:v', 'baseline', '-level:v', '3.1',
|
||||
'-pix_fmt', 'yuv420p', '-crf', '26', '-maxrate', '1100k',
|
||||
'-bufsize', '2200k', '-g', '20', '-keyint_min', '20', '-sc_threshold', '0',
|
||||
'-threads', '1', '-filter_threads', '1'] + acceleration + [
|
||||
'-rtsp_transport', 'tcp', '-timeout', '15000000', '-i', base + original,
|
||||
'-map', '0:v:0', '-map', '0:a:0?', '-sn', '-dn'] + video + [
|
||||
'-c:a', 'aac', '-ar', '48000', '-ac', '1', '-b:a', '48k',
|
||||
'-f', 'rtsp', '-rtsp_transport', 'tcp', base + name]
|
||||
process = subprocess.Popen(command, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL,
|
||||
|
||||
Reference in New Issue
Block a user