consisten one behind
This commit is contained in:
@@ -97,13 +97,13 @@ def receive_record_relay_and_detect(stream_url, output_path, relay_url,
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Scene detection: CUDA decode (GPU) → select filter (CPU, lightweight)
|
# Scene detection: CUDA decode (GPU) → select filter (CPU, lightweight)
|
||||||
# → showinfo → MJPEG piped to stdout (avoids image2 muxer one-frame buffer)
|
# → showinfo → MJPEG piped to stdout
|
||||||
select_expr = f"gt(scene,{scene_threshold})"
|
select_expr = f"gt(scene,{scene_threshold})"
|
||||||
scene_stream = stream.filter("select", select_expr).filter("showinfo")
|
scene_stream = stream.filter("select", select_expr).filter("showinfo")
|
||||||
scene_out = ffmpeg.output(
|
scene_out = ffmpeg.output(
|
||||||
scene_stream, "pipe:1",
|
scene_stream, "pipe:1",
|
||||||
f="image2pipe", vcodec="mjpeg",
|
f="image2pipe", vcodec="mjpeg",
|
||||||
vsync="vfr", flush_packets=1, **{"q:v": "2"},
|
flush_packets=1, **{"q:v": "2", "fps_mode": "passthrough"},
|
||||||
)
|
)
|
||||||
|
|
||||||
return ffmpeg.merge_outputs(file_out, relay_out, scene_out).global_args(*GLOBAL_ARGS)
|
return ffmpeg.merge_outputs(file_out, relay_out, scene_out).global_args(*GLOBAL_ARGS)
|
||||||
|
|||||||
@@ -10,10 +10,8 @@ Architecture:
|
|||||||
|
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
from queue import Queue, Empty
|
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
|
|
||||||
from cht.config import (
|
from cht.config import (
|
||||||
@@ -244,10 +242,9 @@ class StreamManager:
|
|||||||
- stderr: parses showinfo lines, queues pts_time values
|
- stderr: parses showinfo lines, queues pts_time values
|
||||||
- stdout: reads JPEG frames from pipe, pairs with queued timestamps,
|
- stdout: reads JPEG frames from pipe, pairs with queued timestamps,
|
||||||
writes files to disk, fires callbacks immediately
|
writes files to disk, fires callbacks immediately
|
||||||
|
|
||||||
showinfo fires before the JPEG encoder, so timestamps are always
|
|
||||||
queued before the corresponding JPEG data arrives on stdout.
|
|
||||||
"""
|
"""
|
||||||
|
from queue import Queue, Empty
|
||||||
|
import os
|
||||||
ts_queue = Queue()
|
ts_queue = Queue()
|
||||||
|
|
||||||
def _read_stderr():
|
def _read_stderr():
|
||||||
@@ -269,13 +266,10 @@ class StreamManager:
|
|||||||
buf = b""
|
buf = b""
|
||||||
raw_fd = proc.stdout.fileno()
|
raw_fd = proc.stdout.fileno()
|
||||||
while True:
|
while True:
|
||||||
# os.read on the raw fd returns as soon as ANY data is available
|
|
||||||
# (no Python buffered-IO blocking waiting to fill a buffer)
|
|
||||||
chunk = os.read(raw_fd, 65536)
|
chunk = os.read(raw_fd, 65536)
|
||||||
if not chunk:
|
if not chunk:
|
||||||
break
|
break
|
||||||
buf += chunk
|
buf += chunk
|
||||||
# Split JPEG frames by SOI (0xFFD8) and EOI (0xFFD9) markers
|
|
||||||
while True:
|
while True:
|
||||||
soi = buf.find(b"\xff\xd8")
|
soi = buf.find(b"\xff\xd8")
|
||||||
if soi < 0:
|
if soi < 0:
|
||||||
@@ -283,12 +277,11 @@ class StreamManager:
|
|||||||
break
|
break
|
||||||
eoi = buf.find(b"\xff\xd9", soi + 2)
|
eoi = buf.find(b"\xff\xd9", soi + 2)
|
||||||
if eoi < 0:
|
if eoi < 0:
|
||||||
buf = buf[soi:] # keep from SOI, need more data
|
buf = buf[soi:]
|
||||||
break
|
break
|
||||||
jpeg_data = buf[soi:eoi + 2]
|
jpeg_data = buf[soi:eoi + 2]
|
||||||
buf = buf[eoi + 2:]
|
buf = buf[eoi + 2:]
|
||||||
|
|
||||||
# Get timestamp (showinfo fires before encode, so it's queued)
|
|
||||||
try:
|
try:
|
||||||
pts_time = ts_queue.get(timeout=2.0)
|
pts_time = ts_queue.get(timeout=2.0)
|
||||||
except Empty:
|
except Empty:
|
||||||
|
|||||||
Reference in New Issue
Block a user