7.72 — three reproducible crash signatures, with dump analysis (LAVVideo AV on freed input sample, ProgDvbEngine+0xE93D9

#1
Hello,

I run ProgDVB Professional 7.72 on two machines as a 24/7 IPTV + recording setup. Both crash regularly. I analysed the crash dumps and can give exact fault locations, faulting instructions, register state and call stacks. I hope this saves you time.

There are three distinct signatures, not one.

═══ ENVIRONMENT ═══

Code: Select all

                 Machine A                      Machine B
OS               Windows 11 Pro 26200           Windows 10 Pro 22H2 19045
CPU              AMD Ryzen 7 PRO 4750U 8C/16T   Intel Core i7-4770 4C/8T
RAM              31 GB                          20 GB
GPU              AMD Radeon (Vega) 31.0.21921   Intel HD 4600 20.19.15.4531
.NET             4.8.09221                      4.8.09037
ProgDVB          7.72.0.0 portable              7.72.0.0 portable
Renderer         EVR                            EVR
Bundled LAV      0.80.0, 1294520 bytes, SHA256 10B54672...049327DA (identical)

ProgDvbNet.exe   timestamp 6A40348F
ProgDvbEngine.DLL timestamp 6A17E841
Both machines run two ProgDVB instances at once, playing HLS/IPTV sources over the internet, with timeshift enabled (MaxTimeShift=8) and continuous recording. Some source streams are unstable and disconnect often — this appears to matter, see TRIGGER CONDITIONS below.

═══ SIGNATURE 1 — LAVVideo.ax+0xF116D — access violation reading a freed input sample ═══

Two full dumps, byte-identical code path in both.

Code: Select all

Exception : c0000005 ACCESS_VIOLATION, READ
Module    : LAVVideo.ax (0.80.0) +0xF116D
Faulting instruction, disassembled from the dump's own memory image:

Code: Select all

vmovdqu  ymm0, ymmword ptr [rdx]           <-- AV here
vmovdqu  ymm5, ymmword ptr [rdx+r8-0x20]
cmp      r8, 0x100
jbe      ...
mov      r9, rcx
and      r9, 0x1f                          ; align destination to 32
This is the AVX2 memcpy fast path, i.e. memcpy(dst = RCX, src = RDX, len = R8):

Code: Select all

Dump  Date              dst (RCX)        src (RDX)        len (R8)  src page
 #1   2026-08-26 20:31  0x133362C6B40    0x1332F5BE3C0     6633     NOT MAPPED
 #2   2026-08-30 16:47  0x1E3B35F0040    0x1E3C9A1AD6E    36198     NOT MAPPED
The source pointer is the problem. In both dumps that address is absent from the dump's memory ranges entirely — the pages were decommitted, not merely heap-freed. The pointer is not NULL and looks like a plausible heap address, so this is a dangling pointer, not a missing NULL check. The length differs per crash, consistent with a compressed frame payload.

Call stack, identical in both dumps, innermost first:

Code: Select all

LAVVideo.ax+0x3364          <- memcpy
LAVVideo.ax+0xA80F4
LAVVideo.ax+0x3222
LAVVideo.ax+0x19324
LAVVideo.ax+0x3130
LAVVideo.ax+0x1DDD
combase.dll+0x78C7          <- COM boundary (dump #1)
LAVVideo.ax+0x1705D
LAVVideo.ax+0x1F916
ProgDvbEngine.DLL+0x12D192  <-- ProgDVB delivering the sample
LAVVideo.ax+0xA2B71
LAVVideo.ax+0xA8A8E
LAVVideo.ax+0xA2D22
ProgDvbEngine.DLL+0x132F32
ProgDvbEngine.DLL+0x13281C  <-- ProgDVB worker thread entry
kernel32!BaseThreadInitThunk
ntdll!RtlUserThreadStart
I located the exact source line. LAV Filters is open source and this is CDecAvcodec::ParsePacket() in decoder/LAVVideo/decoders/avcodec.cpp:

Code: Select all

// re-allocate with padding, if needed
if (m_bInputPadded == false && buflen > 0)
{
    BYTE *pBuf = (BYTE *)av_fast_realloc(m_pFFBuffer, &m_nFFBufferSize,
                                         buflen + AV_INPUT_BUFFER_PADDING_SIZE);
    if (!pBuf) return E_FAIL;
    m_pFFBuffer = pBuf;

    memcpy(m_pFFBuffer, buffer, buflen);     // <-- THE CRASH IS HERE
    memset(m_pFFBuffer + buflen, 0, AV_INPUT_BUFFER_PADDING_SIZE);
    pDataBuffer = m_pFFBuffer;
}
The registers map onto it exactly: m_pFFBuffer = RCX (LAV's own buffer, valid), buffer = RDX (your IMediaSample data pointer, unmapped), buflen = R8.

m_bInputPadded is set at avcodec.cpp:582 as m_bInputPadded = (dwDecFlags & LAV_VIDEO_DEC_FLAG_LAVSPLITTER), and that flag is only set when the upstream filter is LAV Splitter. With ProgDVB upstream it is false, so LAV takes this defensive path: it allocates its own padded buffer and copies your sample data into it before touching anything else.

This means LAV Video is not doing anything wrong. That memcpy is the first read of the sample data. So the window is not "LAV held the pointer too long" — the sample's memory was already released before or during the Receive() call that delivered it.

At the moment of the crash, 102 of 103 threads were parked in ntdll waits — the releasing thread had already returned, which is what you would expect from a lifetime race rather than true simultaneous access.

Also note the pages are unmapped, not merely heap-freed. A CRT/heap free would normally leave the page readable and produce corrupt video rather than an AV. That points at a buffer allocated with VirtualAlloc and released with VirtualFree, or an allocator Decommit().

Suggested fix. Because the very first read of the sample faults, the problem is on the delivery side, not in the decoder. Worth checking in this order:
  1. IMemAllocator::Decommit() must not release the buffer memory until every outstanding sample has been returned. If ProgDVB's allocator VirtualFrees or decommits its arena on Decommit without waiting for outstanding IMediaSample references, this is exactly the observed failure.
  2. Ordering of flush/stop vs. streaming: ensure BeginFlush / EndFlush / Stop cannot run the teardown path concurrently with an in-flight Receive() on the streaming thread.
  3. Do not deliver a sample whose allocator has already been decommitted — on a source disconnect the streaming thread may still have one sample in hand.
  4. If buffers are recycled rather than freed, the same symptom appears when a buffer is recycled one sample too early.
This reproduces on two machines with different CPU vendors, different GPU vendors and different Windows versions, so it is not driver-specific.

═══ SIGNATURE 2 — ProgDvbEngine.DLL+0xE93D9 — most frequent (machine B) ═══

Code: Select all

Exception : c0000005 ACCESS_VIOLATION
Module    : ProgDvbEngine.DLL +0xE93D9
WER bucket: 6534305e3843e894376fa3657d11bb7e15e4ae1 / 2442a3b1
Seven occurrences in 90 days, always the identical offset:

Code: Select all

2026-08-11 03:41:34    2026-08-13 02:14:30    2026-08-30 15:06:31
2026-08-11 14:02:20    2026-08-26 03:16:45    2026-08-30 22:29:28
2026-08-11 21:29:23
Same offset every time = a single deterministic code path, not memory corruption.

I could not analyse this one: no crash dump is produced on that machine, and the address is inside the protected region of the DLL. RVA 0xE93D9 lies outside the raw .text range (0x1000-0xB7200) of the on-disk image, so it only exists after the runtime unpacks — static analysis returns nothing there. A dump would resolve it immediately, as it did for signature 1.

Question: what is the supported way to make ProgDVB write a crash dump? On machine A there is a C:\PDVB\_crashdumps\ folder with ProgDvbNet.exe.<pid>.dmp files (~78 MB), but machine B has no such folder and produces none. If creating the folder is enough, please say so and I will capture one and send it. Otherwise I can enable Windows WER LocalDumps.

Two of the seven coincided (within 1 second) with a recording ending; the other five happened while that instance was only playing, so it is not recording-specific.

═══ SIGNATURE 3 — ucrtbase.dll+0xA527E — fast-fail from GDI+ / System.Drawing ═══

Code: Select all

Date        : 2026-08-22 15:47
Instruction : int 0x29        (__fastfail)
RCX         : 7               (FAST_FAIL_FATAL_APP_EXIT)
R8          : 0x7FFFFFFFFFFFFFFC

ucrtbase.dll+0xA527E
ucrtbase.dll+0xB2FA8
System.Drawing.ni.dll+0x11310
System.Drawing.ni.dll+0x6E7AA
ucrtbase.dll+0x6E70D
GdiPlus.dll+0xFA42
System.Drawing.ni.dll+0x676B5
clr.dll+0x7E7630
System.Drawing.ni.dll+0x67620
System.Drawing.ni.dll+0x6E39E
System.Drawing.ni.dll+0x124C0
Entirely in the managed UI layer, unrelated to decoding. Looks like an invalid parameter reaching a GDI+ call — a disposed or concurrently-used Graphics/Bitmap on the OSD/skin drawing path would produce this. Note R8 = 0x7FFFFFFFFFFFFFFC, which looks like a bad size/offset.

═══ TRIGGER CONDITIONS ═══

I analysed 128 recordings over 22 days on machine B by measuring actual written bitrate. Sources that disconnect frequently correlate strongly with both stalls and crashes:

Code: Select all

Source group                  healthy   stalled (<0.5 Mbps for hours)
Direct, stable HLS sources       77        0
Unstable provider sources         5        7
"Stalled" means the recording file stays open for hours while almost no data is written — e.g. 725 minutes producing 359 MB (0.07 Mbps). The existing 15-second no-data watchdog does not catch this (see below), and from the UI a trickling stream looks identical to a working one.

Every disconnect triggers a stream teardown/rebuild, and every teardown is another chance to hit the race in signature 1. Normal reconnects recover in 3-4 seconds; a crash costs about two hours because nothing restarts the application.

═══ ON THE EXISTING STREAM WATCHDOG ═══

I want to be accurate here: ProgDVB already has this recovery, and it works. On machine A I see it fire and recover correctly:

Code: Select all

09:41:45.292 - [101]TransponderManager::RefreshChannel ! 15s chunk=1 net=1 10000ms error0
09:41:45.294 - Do RefreshChannel
Three times today, roughly every two hours, each time recovering in a few seconds.

The gap is that the trigger appears to be absence of data for 15 s, and the failure mode above is not an absence — it is a trickle. A source delivering 0.07 Mbps never stops sending for 15 seconds, so the timer keeps resetting while the picture is unusable and the recording grows at 1/100th of the expected rate. The watchdog is presence-based; the failure is rate-based.

Request: could the refresh trigger also consider throughput — e.g. "if the incoming bitrate stays below X% of the nominal rate for N seconds, refresh the channel"? That would close the remaining case.

Question: ProgTV.win.ini contains RefreshLevelNetwork=0, which I cannot find documented anywhere. What are the valid values and what does each level change? If a higher level already makes recovery more aggressive, I would like to try that before asking you for a code change.

One more small request: auto-restart after a crash, or at least resuming an interrupted recording. A crash currently costs about two hours of recording because nothing brings the application back.

═══ WHAT I RULED OUT ═══
  • Disk space — all record/timeshift paths point to a volume with 3.3 TB free.
  • Hardware acceleration — irrelevant to signature 1 by construction: the fault happens in the input copy, before any decoding, so the decode backend cannot matter.
  • CPU starvation — one machine was CPU-limited by a power policy. Fixing it (1.6 GHz -> 3.2 GHz, ProgDVB CPU use 107% of a core -> 16%) removed stuttering but did not change the crash signatures.
  • Memory pressure — 31 GB / 20 GB, both machines had over 12 GB free at crash time.
  • Drivers — different OS versions, CPU vendors and GPU vendors produce the same signatures.
═══ WHAT I CAN PROVIDE ═══
  • Three full crash dumps (~78 MB each) for signatures 1 and 3.
  • Report.wer files for all eight crashes on machine B.
  • A dump for signature 2 as soon as you tell me how to make ProgDVB produce one.
I am happy to test a debug build. Thank you for ProgDVB — it has run this setup for years, and the stall/crash behaviour is the only thing standing in the way.