Repository navigation
Conversation
63eac29 to
4f3491f
Compare
MP4 blank screen: malformed avcC fixed (commit 93253e8)Symptom: saved MP4 had the correct duration but decoded to a blank screen, while the same ring saved as MKV played fine. Root cause:
MP4 players take the SPS from the stream header (avcC); with a zero SPS count they get none, so every sample fails to decode -> blank screen, while the container metadata (duration, size) is still correct. MKV is unaffected because it stores the Annex B SPS/PPS in-band/in CodecPrivate. The record now follows the ISO/IEC 14496-15 avcC layout |
MP4 save now fails with "non monotonically increasing dts" — fixed (commit 4d40e92)Root cause (separate from the avcC bug): ring video packets are stamped with the wall-clock time of the frame they were drained from, and a hardware encoder can emit several packets for one frame (or flush two or more at once) — so consecutive ring packets share a timestamp. The MP4 muxer requires strictly increasing dts and fails the whole save:
Verified with a standalone lavf harness: two consecutive H.264 packets with equal dts → On keeping the Annex B → AVCC conversion for MP4: yes, it is still needed.
|
Audio never opened: AAC encoder missing
|
Audio resampler never initialized — fixed (commit 886a710)The AAC encoder now opens (the So every Fix: call Verified the exact case from the log (48 kHz mono → 48 kHz mono, packed FLT in/out) in a standalone harness: |
MKV audio/duration: Matroska needs millisecond timestamps — fixed (commit e49393d)"MKV: no audio, plays 30 s then freezes on the last frame for >20 min" traced to a Matroska-specific timestamp issue, reproduced with a standalone harness muxing real H.264+AAC packets through the same The matroska muxer writes packet timestamps into the file verbatim. Its
MP4 was unaffected because the mov muxer rescales from the stream timebase itself (hence MP4 audio worked). Fix in
Harness verification: old behavior → MKV Duration element |
|
Follow-up to the encoder-format audit (nvenc/qsv/amf/videotoolbox/v4l2m2m wrappers reviewed against the ffmpeg 8.1 sources): no new encoder-specific conversion code is needed - every candidate emits its extradata and packets in the same form. This commit adds one log line at mux time: the first 4 bytes of the first video ring packet, printed next to the existing extradata hex. If a driver ever reported one H.264 form (AVCC extradata) while emitting the other (Annex B packets), the MP4 save would otherwise be a silent blank file; this makes the mismatch immediately visible in the log. Log-only, no behavior change. (aedba6c) |
|
Added h264_mf (the OS-native Media Foundation H.264 encoder) to the Windows candidate list, after h264_amf and before mpeg4. It is compiled into the vendored LGPL build (verified in avcodec-63.dll) and present on essentially all Windows 8+: when none of the GPU vendors' encoders (qsv/nvenc/amf) is available, the session now falls back to real, universally playable H.264 instead of MPEG-4 Part 2. Device-backed like the hardware wrappers, so a runtime failure follows the existing leak-and-poison path; rotation/poison/fallback logic needed no changes. The mux-time log lines report its extradata/packet form on first use. (9508c51, rebased onto dc8129c) |
9508c51 to
927370c
Compare
|
This is GPL safe right? We're using the LGPL part of ffmpeg because we're only including headers and their pre-compiled binaries? |
|
I believe so. I specifically used a LGPL build of ffmpeg. So, I think it should be LGPL? |
…HISTORY_FFMPEG).
Replaces the JPEG-frame ring with an in-memory ring of encoded H.264 video + AAC
audio packets when the new CMake option PA_STREAM_HISTORY_FFMPEG is enabled.
Nothing is written to storage until save() is called, at which point the ring
is muxed into an MP4 (or MKV for .mkv filenames) via libavformat.
- New StreamHistoryTracker_Ring (Recording/StreamHistoryTracker_Ring.{h,cpp}):
- Video: dedicated encoder thread with the same bounded-queue / drop-oldest
scheme as the JPEG tracker. Hardware H.264 encoders preferred
(qsv/nvenc/amf on Windows, videotoolbox on macOS, v4l2m2m on Linux),
libx264 veryfast as fallback, no B-frames, keyframe every 2 s, NV12 fast
path with a Qt RGB888 fallback for other pixel formats.
- Audio: resampled to 48 kHz (swresample) and encoded as AAC inline in
on_samples(); deinterleaves the DUAL_44100 input, which the session
declares as mono but the pipeline delivers as interleaved stereo.
- The ring is evicted by time window (HISTORY_SECONDS) plus a ~90 MB cap.
- save() snapshots the ring (shared packets, no deep copy), rewinds to the
last keyframe, and muxes H.264+AAC. Encoder init / capture failures log
and disable the affected stream instead of crashing.
- StreamHistorySession picks the ring tracker when PA_STREAM_HISTORY_FFMPEG is
defined (same #if shape as the existing Qt version check).
- CMake: option(PA_STREAM_HISTORY_FFMPEG OFF) - OFF leaves every existing
build untouched. When ON: Windows links the ffmpeg import libs from
3rdPartyBinaries/ + headers from 3rdParty/ffmpeg/ (mirroring the OpenCV
precedent; DLLs placed in 3rdPartyBinaries/ are auto-deployed by the
existing THIRD_PARTY_DLLS glob), macOS/Linux use pkg-config
(avcodec/avformat/avutil/swscale/swresample, requires ffmpeg >= 5.1).
Missing ffmpeg fails at configure time with an explicit FATAL_ERROR.
Note: the design discussion called the audio codec Opus; AAC was chosen
instead because the save target is 'Video.mp4' (Discord attachments) and
H.264+AAC MP4 plays everywhere, while Opus-in-MP4 has spotty player support.
- save() rewound the video to the LAST keyframe, so saving a 30 s window returned only the ~2 s since the newest keyframe. Rewind to the FIRST keyframe in the ring instead (loses at most one keyframe interval from the head of the window). - Video packet PTS were computed as us * time_base.den / time_base.num, which for the 1/1e6 timebase multiplies timestamps by 1e6, producing unplayable files. Use av_rescale_q from a 1/1000000 timebase. - Muxed packets never set packet->stream_index, so real libavformat would route every packet (video and audio) into stream 0. - swr_alloc_set_opts2() was called with 9 args including &swr as the first parameter; the ffmpeg >= 5.1 API takes 8 (out channel layout first, no SwrContext**). This did not compile against real ffmpeg headers. - avcodec_send_frame() EAGAIN (encoder input queue full - realistic with hardware encoders) was treated as a hard failure without draining, which clogs the encoder permanently and logs every frame after. Drain one output packet and retry the send. - Video encoder failure paths wrote video_enabled without the ring lock (raced with on_frame reading it under the lock). - Link the ffmpeg libraries PRIVATE on POSIX (matches the PRIVATE compile definition and the Windows side). - Include libavutil/error.h and libavutil/rational.h explicitly.
…encoder. The project is LGPL-only, so the encoder candidate list no longer relies on the GPL libx264. The final candidate is now 'mpeg4' (MPEG-4 Part 2, built into libavcodec in LGPL builds), with the libx264-specific preset/tune/bframes options removed along with the now-unneeded <cstring> include. The CMake comment now points at the LGPL shared ffmpeg builds (gyan.dev '...-lgpl-shared' / BtbN 'win64-lgpl-shared').
…g 5.1-8.x - Save bug: the AAC encoder was opened without AV_CODEC_FLAG_GLOBAL_HEADER, so it emitted ADTS frames with no AudioSpecificConfig extradata and mux_to_file() failed to set up the audio stream - every save that included audio failed. Set the flag (both MP4 and MKV need it). - MP4 output: pass movflags=+faststart to the mp4 muxer so the moov atom (file index) is written at the front of the file instead of at the end. MKV saves are unchanged. - Portability: jw's build fixes targeted only the ffmpeg 8.0 API surface (void av_channel_layout_default + av_channel_layout_check, avcodec_get_supported_config, swr_alloc_set_opts2 taking a SwrContext**), which does not compile against the ffmpeg that mac/linux package managers provide. Guard each version-sensitive call on the library version macros so the tracker builds against ffmpeg 5.1 through 8.x (AVCodec.pix_fmts for pre-7.0, the old swr_alloc_set_opts2 signature for pre-8.0, the int-returning av_channel_layout_default for pre-8.0). - Fix an AVFrame leak in on_samples() (early return without av_frame_free) and stop retrying a permanently broken audio path by disabling audio instead. - Restore <cstring> (still needed for memcpy) and add <libavutil/version.h> for the guard macros.
… detection * StreamHistoryTracker_Ring.cpp: ffmpeg's public API headers do not carry their own extern "C" guards, so from C++ every ffmpeg declaration was C++ name-mangled and none of the symbols resolved against the ffmpeg shared libraries (Windows: LNK2019 unresolved externals at the link stage after a full ~31 min build; mac/linux: undefined symbols). Wrap all ffmpeg includes in an extern "C" block. * Restore the version-conditional include: ffmpeg 8.0 (libavutil 61+) renamed err.h to error.h, but Ubuntu 24.04's apt ships ffmpeg 6.1 (libavutil 58) which only has err.h - the unconditional error.h include fails to compile there. * Re-apply the C4458-safe parameter/local names in mux_to_file() and save() (video/audio/have_audio/video_start hide Impl members, which is a /W4 warning that becomes an error under /WX on Windows). * Restore the #ifndef EAGAIN fallback for MSVC. * CMakeLists (POSIX): pkg-config module names differ between platforms - Homebrew ships avcodec.pc etc. while Debian/Ubuntu ship libavcodec.pc etc. - so the previous 'pkg_check_modules(FFMPEG REQUIRED avcodec ...)' failed configure on Ubuntu, and the 'libavcodec>=59' version check failed configure on macOS (brew has no libavcodec.pc). Try both namings, check the minimum version with whichever was found, and add target_link_directories (like the OpenCV/Tesseract/DPP blocks above) so -lavcodec etc. are found under /opt/homebrew/lib on macOS.
…al API The CI Ubuntu runner's ffmpeg (7.1-era) already has the 'new' API signatures (void av_channel_layout_default, int swr_alloc_set_opts2(SwrContext **ps, ...)) while its libavutil/libswresample major numbers are still below the 8.0 ones the #if guards expected, so the old-API branches were selected against new-API headers and failed to compile. - av_channel_layout_default / swr_alloc_set_opts2: select the API generation at compile time from the actual declarations (std::is_same on the function-pointer types, C language linkage). The generation-specific calls are routed through function templates so they are only instantiated when their branch is taken - names/expressions in a discarded if-constexpr branch are still looked up and checked, which is why the old 8-argument swr_alloc_set_opts2 call (missing the AVDictionary** that real ffmpeg 5.1-7.0 takes) also failed against the real headers. - error.h vs err.h: pick with __has_include instead of a version macro. - avcodec_get_supported_config stays a preprocessor gate (LIBAVCODEC_VERSION_MAJOR >= 61): the symbol is not declared at all in older headers, so the code must be removed before name lookup, not merely branch-discarded. The threshold is confirmed by this CI (its libavcodec compiles the new branch fine) and by ffmpeg 6.1 (libavcodec 60, which has AVCodec.pix_fmts instead). - CMake: log which pkg-config module set / version was found. Verified: syntax-checks against the committed ffmpeg 8.0 headers, and full behavioral harness runs against both an ffmpeg 5.1-7.0 stub set and a 7.1-runner-model stub set (new signatures, old version numbers).
…ffmpeg 7.x Per jw: we won't support the old ffmpeg API, so remove the compile-time detection of the API generation in StreamHistoryTracker_Ring.cpp and use the current signatures directly. - error.h/err.h: just include <libavutil/error.h>. The __has_include fallback to err.h was dead either way - error.h exists in every ffmpeg release, and ffmpeg 8.x removed err.h by merging it into error.h. - Removed kChannelLayoutDefaultReturnsVoid / kSwrAllocSetOpts2TakesContextPtr and the old-API template shims; call av_channel_layout_default() (void, validated with av_channel_layout_check()) and swr_alloc_set_opts2() taking a SwrContext** directly. These signatures exist in ffmpeg 7.1+ (the pre-7.1 releases pass an AVDictionary** to swr_alloc_set_opts2 and get an int back from av_channel_layout_default). - CMake: enforce the floor for real. macOS/Linux: avcodec>=61 (libavcodec 61 = ffmpeg 7.x; previously avcodec>=59 / 'ffmpeg 5.1'). Windows: new check parsing LIBAVCODEC_VERSION_MAJOR out of the vendored 3rdParty/ffmpeg/ headers (previously there was no version check at all). The vendored 8.x headers pass.
The ring's unguarded ffmpeg API usage (channel layout API: av_channel_layout_default/check/copy/uninit, AVCodecContext.ch_layout, AVFrame.ch_layout; swr_alloc_set_opts2; everything else is pre-5.x API) is available in ffmpeg 5.1 with unchanged signatures, so 5.1 is the minimum supported version. libavcodec's major version cannot express that floor as a number (4.x and 5.x are both libavcodec 58), so the CMake floor check now detects the 5.1 API generation in the headers (vendored tree on Windows, pkg-config include dirs elsewhere) instead of comparing LIBAVCODEC_VERSION_MAJOR against 61. The pix_fmts/avcodec_get_supported_config split in StreamHistoryTracker_Ring.cpp is untouched: it is an API-generation gate for 7.0+ headers, and with 5.1-7.0 headers the AVCodec.pix_fmts path (still present in those releases) compiles instead.
…ntime Hardware encoders (h264_amf & co.) can open successfully and still fail on every frame - e.g. h264_amf whose D3D11 device cannot create textures (SubmitInput() failed with error 18 / AMF_DIRECTX_FAILED, CreateTexture2D -> 0x887A0005 DXGI_ERROR_GPU_NOT_FOUND). Previously that logged 'Video encode failed' on every frame forever and the saved file had no video. After 3 consecutive hard avcodec_send_frame() failures the current candidate is abandoned and init_video_encoder() retries starting from the next candidate (typically the CPU mpeg4 encoder), so the recording still gets video and the error spam stops. Candidate rotation is remembered (failed candidates are not retried), and the scaler is rebuilt when the new encoder prefers a different pixel format.
On some AMD iGPUs (observed: Ryzen 5 5625U), a hardware H.264 encoder can open successfully and then fail on every frame because its GPU device cannot allocate textures (h264_amf: CreateTexture2D fails with DXGI_ERROR_GPU_NOT_FOUND). Releasing such a context with avcodec_free_context() crashes the AMD display driver with an access violation in atidxx64.dll, taking the whole process down - observed both at the encoder fallback point and in the tracker destructor. Therefore, when a hardware encoder fails at runtime (or at open), its context is now deliberately leaked (a bounded, one-time leak) instead of freed, and the encoder is remembered as poisoned for the rest of the process so that later sessions skip it instead of re-initializing it. Fallback to the next candidate now happens on the first hard avcodec_send_frame() failure instead of after three, to minimize the number of calls into the broken driver. The CPU mpeg4 fallback is still released normally.
…on open failures Two fixes for the encoder fallback path: - mpeg4 failed to open with 'Invalid argument' on ffmpeg 8.x: since 7.0, AVCodec.pix_fmts is gone and the supported formats must come from avcodec_get_supported_config(); when that query yields no list, ctx->pix_fmt was left at AV_PIX_FMT_NONE, which mpeg4 (and likely other encoders) reject at open time. The chosen input pixel format now always falls back to YUV420P - supported by every encoder in the candidate list - when no list is available, and the selected format is logged when the encoder opens. - Encoders that merely fail to open (e.g. h264_qsv/h264_nvenc on a machine without that hardware) are now released normally instead of being leaked and poisoned for the whole process: the driver was never exercised through a context that failed at open. Only a context that failed at runtime (after pushing frames into a possibly broken GPU driver) is leaked, as before.
Two encoder-setup fixes, both observed on an AMD Ryzen 5 5625U iGPU (ffmpeg 8.1): - mpeg4 failed to open with EINVAL: 'timebase 1/1000000 not supported by MPEG 4 standard, the maximum admitted value for the timebase denominator is 65535' (the 16-bit time_incr_length of MPEG-4 Part 2). The video timebase is now 1/1000 (milliseconds) for all candidates; the hardware H.264 wrappers accept any timebase, and the muxer derives packet timestamps from the ring's microsecond WallClock timestamps anyway. - h264_amf failed at the first SubmitInput: the AMD driver logged 'unexpected frame rate' and removed the D3D11 device (AMF_NO_DEVICE). The frame rate is now set explicitly (ctx->framerate = target_fps/1), which the AMF wrapper passes to the driver; it also feeds rate control on the other hardware encoders.
Saving the video crashed in avformat_write_header() (access violation in avformat-63.dll) on an AMD iGPU with the h264_amf encoder. The muxer ran on the caller's thread while the video worker and the audio pipeline thread were still encoding into the same AVCodecContexts, and ffmpeg is not thread-safe on a live context: the hardware encoders populate their extradata lazily (sometimes only after the first encoded packet, and possibly mid-stream), so the cross-thread avcodec_parameters_from_context() in mux_to_file() could capture a torn state (e.g. a nonzero extradata_size with a null extradata pointer), which is what made the mp4 muxer walk off a null pointer. The muxer now works exclusively from codec-parameter snapshots (video_params/audio_params + video_time_base) that are captured on each encoder's own thread - the video worker after every encoded packet, the audio pipeline thread after every encoded AAC frame - under the ring lock, and are refreshed whenever the encoder's extradata size changes. A new encoder invalidates the stale video snapshot. If no snapshot (or no extradata) exists yet, the save fails with the existing 'missing encoder state' error instead of crashing. Also logs the video extradata size and first four bytes at mux time to make any future extradata-format issue (AVCC vs Annex B) visible in the log.
The h264_amf wrapper emits its H.264 stream in Annex B form (SPS/PPS extradata and packets with 00 00 00 01 start codes), even with AV_CODEC_FLAG_GLOBAL_HEADER. The MP4 muxer in the vendored ffmpeg 8.x (avformat-63) crashes with an access violation inside avformat_write_header() when it is given H.264 in that form (older ffmpeg, e.g. 6.x, tolerated it). Now that h264_amf is the first encoder that actually works on AMD iGPUs, saving the video hit this crash deterministically. Since MP4 (ISO BMFF) canonically stores H.264 as AVCC - an avcC configuration record in the stream header plus NAL units with 4-byte big-endian length prefixes - mux_to_file() now converts the stream to that form when the video is H.264, the target is MP4, and the extradata is Annex B: it builds the avcC record from the SPS/PPS NAL units in the extradata snapshot and rewrites each ring packet with NAL length prefixes at write time. Matroska output is left in Annex B, and encoders that already produce AVCC (nvenc, qsv) are untouched. Also fixes a regression introduced by the parameter-snapshot commit: the 'missing encoder state' check required extradata for every video codec, but MPEG-4 Part 2 (the CPU fallback) has no extradata at all; the requirement now applies to H.264 only (AAC audio still requires its AudioSpecificConfig).
avformat_write_header() does not open format->url: the lavf docs require the caller to provide an opened AVIOContext (format->pb), and the muxer's write_header() dereferences s->pb unconditionally. With pb still NULL the MP4 muxer faults in mov_write_header (NULL+0x88 read, avformat-63.dll). Open the file with avio_open2() just before writing the header and close it on the failure path (the success path already closed it via avio_closep).
The avcC configuration record built by h264_avcc_from_annexb_extradata() was malformed: it wrote a stray 5th byte (sps[4], the first byte of the SPS RBSP) before the reserved/lengthSizeMinusOne byte, and used 0xE0 (numOfSequenceParameterSets = 0) instead of 0xE1 (= 1). Players that take the SPS from the stream header therefore found none: the saved MP4 had the right duration and container structure, but decoded to a blank screen, while MKV (which keeps the Annex B SPS/PPS in-band) played fine. The record now matches the ISO/IEC 14496-15 avcC layout 01 <profile> <compat> <level> FF E1 <spsLen16> <SPS> 01 <ppsLen16> <PPS>, byte-for-byte the same layout ffmpeg's own mov muxer writes from the same SPS/PPS (verified by muxing and decoding the result).
Ring video packets are stamped with the wall-clock time of the frame they were drained from, and a hardware encoder can emit several packets for one frame (or flush two or more at once), so consecutive ring packets can share a timestamp. The MP4 muxer rejects that: [mp4] Application provided invalid, non monotonically increasing dts to muxer in stream 0: 596 >= 596 and the whole save failed. mux_to_file() now clamps a non-increasing video dts to last+1 (1 ms at the 1/1000 video timebase), and skips empty ring packets instead of feeding an empty sample to the muxer (which would also fail the Annex B -> AVCC conversion). Verified with a standalone lavf harness: writing two consecutive H.264 packets with the same dts fails with AVERROR(EINVAL) exactly like the reported error, and the clamped sequence muxes and decodes cleanly.
…erleave into planes
init_audio_encoder() never set actx->sample_fmt, so avcodec_open2() failed
with EINVAL ("Invalid audio sample format: -1") and audio was disabled on
every platform - the log showed 'Failed to open AAC encoder. Disabling
audio.' even though everything else in the configuration was valid.
The native AAC encoder only accepts AV_SAMPLE_FMT_FLTP, so:
- set actx->sample_fmt = AV_SAMPLE_FMT_FLTP (and frame_size = 1024) before
avcodec_open2(), and
- on_samples() now builds each 1024-sample encode frame as FLTP by
de-interleaving the accumulated packed resampler output into the frame's
per-channel planes instead of memcpy'ing it as one packed plane.
Also split the open-failure logging so the channel-layout step and
avcodec_open2() failures are reported separately, with the ffmpeg error
string for the latter.
Verified with a standalone harness that replicates the full pipeline
(resample 44.1/48 kHz -> packed FLT -> de-interleave -> FLTP frames ->
AAC encode with AV_CODEC_FLAG_GLOBAL_HEADER): the encoder opens, produces
packets, and exposes the 5-byte AudioSpecificConfig extradata that the
MP4/MKV muxers need.
make_audio_resampler() only called swr_alloc_set_opts2(), which allocates the SwrContext and fills in the options but does NOT initialize it; the context must be passed through swr_init() before swr_convert() will work. On every audio callback swr_convert() therefore logged 'Context has not been initialized' and returned an error, so no audio samples were ever accumulated and saved videos had a video track but no audio track - even after the AAC encoder itself was fixed to open (sample_fmt/FLTP). Call swr_init() after allocation and free the context if it fails; the existing 'Failed to create audio resampler' error path now reports real initialization failures too.
…writes The Matroska muxer writes packet timestamps into the file verbatim: its write_header() rewrites every stream timebase to 1/1000 (the native TimestampScale is milliseconds) but does not rescale the dts/pts the application submits. The audio stream was submitted with a 1/48000 timebase and sample-count timestamps, so every audio block landed 48x too late in the file timeline (a 30 s clip's audio blocks spanned 0..24 min) and the segment Duration element became ~24 min for a 30 s clip. Symptom: the MKV played its 30 s of video with no audible audio (each audio block carried only 21 ms of sound, spaced 1.024 s apart on the timeline), then sat on the last frame for the remaining ~19 minutes the player believed the file still had. MP4 was unaffected because the mov muxer rescales from the stream timebase itself. Fix in mux_to_file(): - for .mkv, use a 1/1000 stream timebase for both video and audio and rescale the packet dts into it (video: us -> ms; audio: samples -> ms), while .mp4 keeps the encoder's video timebase and the 1/48000 audio timebase it already handled correctly; - add a strictly-increasing dts guard to the audio packets, mirroring the video one; - submit the video and audio packets interleaved in timestamp order instead of writing the whole video stream and then the whole audio stream, which laid the Matroska clusters out of time order (all audio data physically after all of the video). Verified with a standalone harness that muxes real H.264 + AAC packets through the same avformat calls: with sample-count audio dts the MKV Duration element came out 1441813 ms (24 min) for a 30 s clip, with the new millisecond stamping the duration is ~30 s and the audio packets decode at 0..30 s; the MP4 path is unchanged.
… the last poisoned_encoder held a single name, so if two different hardware encoders hard-failed in one process, the second overwrote the first and a later session would re-initialize the first (leaked, possibly still broken) encoder. A set keeps the poison for every encoder that failed.
Only av_dict_* is used here, which is declared in dict.h; both avcodec.h and avformat.h include dict.h directly, so opt.h was redundant. pixdesc.h stays (needed by the Windows build).
Every known hardware encoder emits the extradata and the packets in the same form (Annex B), and the MP4 conversion is driven by the extradata form. If a driver ever reported one form while emitting the other, the MP4 save would be a silent blank file. Logging the first 4 bytes of the first ring packet next to the extradata makes any such mismatch immediately visible (NVENC's SPS/PPS payload, in particular, is driver-produced and unverified).
…dows candidates h264_mf is the OS-native Media Foundation H.264 encoder (typically the built-in Microsoft encoder). It ships in the vendored LGPL build (verified in avcodec-63.dll) and is present on essentially all Windows 8+. When none of the GPU vendors' encoders (qsv/nvenc/amf) is available, it now yields real, universally playable H.264 instead of dropping to MPEG-4 Part 2. It joins the candidate list after h264_amf and before mpeg4. Like the hardware wrappers it is device-backed, so a runtime failure follows the same leak-and-poison path as abandon_video_encoder() (only the mpeg4 CPU fallback is released normally); the existing rotation, poison, and fallback logic all handle it without changes. The mux-time log lines (extradata hex + first packet bytes) report its H.264 form on first use.
927370c to
dc747ed
Compare
Implements the in-memory ffmpeg stream-history ring discussed in #Stream (option 1): video + audio are encoded in the background into an in-memory ring, and nothing touches storage until
save()is called, when the ring is muxed into a single MP4/MKV file.Toggle (compile-time)
.cppisn't even compiled;StreamHistorySession.cppkeeps the old#ifshape).3rdParty/ffmpeg/(the ffmpeginclude/tree) + import libsavcodec/lib avformat/lib avutil/lib swscale/lib swresample/libin3rdPartyBinaries/(mirrors the OpenCV import-lib precedent). The existingTHIRD_PARTY_DLLSglob auto-deploys any ffmpeg DLLs placed in3rdPartyBinaries/.pkg-config(avcodec avformat avutil swscale swresample); Ubuntu:sudo apt install libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev, macOS:brew install ffmpeg.FATAL_ERRORat configure time with an explicit message (ffmpeg ≥ 5.1 / libavcodec 59+ required for the channel-layout API).What the tracker does (
Recording/StreamHistoryTracker_Ring.{h,cpp})AsyncTaskonunlimited_normal()). Replaces JPEG with the ffmpeg C API: hardware H.264 encoders first (h264_qsv/h264_nvenc/h264_amfon Windows,h264_videotoolboxon macOS,h264_v4l2m2mon Linux) withlibx264 veryfastfallback; no B-frames, forced keyframe every 2 s, NV12 fast path straight intosws_scalewith a QttoImage()RGB888 fallback for other pixel formats. Honors the existingStreamHistoryOptionsettings (RESOLUTION / VIDEO_FPS / ENCODING_MODE / VIDEO_QUALITY / VIDEO_BITRATE).on_samples→ swresample to 48 kHz → nativeaacencoder, 1024-sample frames, inline (sub-millisecond). Includes a deinterleave forDUAL_44100, which the session declares as 1ch but the pipeline actually delivers as interleaved stereo.(WallClock, encoded packet)deques evicted by theHISTORY_SECONDSwindow plus a hard ~90 MB byte cap (≈23 min at the default 512 kbps). Zero I/O until prompted.save(filename)— snapshot the ring (shared packets, no deep copy), rewind to the first keyframe in the ring (oldest decodable start; loses at most one ~2 s keyframe interval from the head of the window), trim audio to the same start point, mux to MP4 (or MKV when the filename ends.mkv) withlibavformatusing copy-only semantics.ErrorReportssaves toVideo.mp4, so MP4 is the default target.One deviation from the discussion worth calling out: the plan said Opus, but I used AAC — the save target is
Video.mp4(Discord attachment) and H.264+AAC MP4 plays everywhere, whereas Opus-in-MP4 has spotty player support. The ring design (compressed audio packets, 48 kHz, ~20 ms frames) is otherwise as discussed; swapping to libopus is a ~30-line change if you'd rather have it.Failure behavior
Encoder init, scaler, resampler, or open failures log and disable just that stream (video or audio) — the bot never crashes, matching the existing tracker's failure style.
save()with an empty ring logs and returnsfalse.Verification
No ffmpeg/Qt toolchain in this sandbox, so this is not a full production build. What was done instead:
StreamHistoryTracker_Ring.cpp(unmodified, straight from the repo) with g++ 13-std=c++23 -Wall -Wextraagainst signature-faithful ffmpeg ≥5.1 API stubs + a minimal QtQVideoFrame/QImagemodel, and ran it behaviorally: full video+audio pipeline → MP4 mux (packet counts, keyframe cadence, µs timestamps, A/V alignment),HISTORY_SECONDSeviction,DUAL_44100deinterleave/resample (44100 → 48000, exact frame counts), empty-ring save,.mkvmuxer selection, and x264veryfast/zerolatency/bframes=0options. All pass, repeated runs included.send_framehitsEAGAINuntil drained), which exercises the EAGAIN/retry path.The stub review caught and fixed (commit
7cd8981), against real ffmpeg 5.1 semantics:save()rewound to the last keyframe → a 30 s window saved only the final ~2 s. Now rewinds to the first keyframe.us * den / numon a 1/1e6 timebase) → unplayable files. Nowav_rescale_qfrom 1/1000000.stream_index→ reallibavformatwould route audio into the video stream.swr_alloc_set_opts2(&swr, ...)— 9-arg hybrid that does not exist in ffmpeg ≥5.1 (8 params, out layout first); would not compile against real headers.avcodec_send_frameEAGAIN (full encoder input queue — realistic on QSV/NVENC/AMF) permanently clogged the encoder instead of draining + retrying.video_enabledwrites racingon_frame; ffmpeg libs linkedPRIVATEon POSIX; explicitlibavutil/error.h/rational.hincludes.Please still do a real build with the toggle ON before relying on it — in particular the hardware-encoder paths (QSV/NVENC/AMF) are the part most likely to need a first-pass tweak.
Files
SerialPrograms/Source/CommonFramework/Recording/StreamHistoryTracker_Ring.{h,cpp}— new trackerSerialPrograms/Source/CommonFramework/Recording/StreamHistorySession.cpp— 4-line#ifselectionSerialPrograms/CMakeLists.txt— option, per-platform link blocks, compile definitionSerialPrograms/cmake/SourceFiles.cmake— new header in the list; new.cppappended only when the option is ON