Skip to content

Fix audio, RTSP, OSD and config bugs in Divinus - #73

Open
difanz wants to merge 11 commits into
OpenIPC:masterfrom
difanz:generic-fixes
Open

difanz wants to merge 11 commits into
OpenIPC:masterfrom
difanz:generic-fixes

Conversation

@difanz

@difanz difanz commented Nov 8, 2026

Copy link
Copy Markdown
Contributor

Bug fixes in shared code, found while bringing up the V4L2 backend (#72).

Audio

  • media: set audioOn before starting the audio threads. The capture and encoding threads loop only while audioOn is set, but it was set only after both threads had been created. A thread scheduled before that returned at once, and audio stayed silent until the next restart.
  • media: follow sample rate changes when resampling G.711. The resampling step was cached in a static. After /api/audio changed the rate, RTSP G.711 was resampled at the old ratio and played at the wrong speed. The step calculation also overflowed at rates of 65536 Hz and above.
  • media: refresh the MP4 audio settings after /api/audio changes. The MP4 muxer kept describing the old audio track after a rate, bitrate or enable change. The settings are now reapplied under mp4Mtx, and an IDR is requested so the header is rebuilt.

RTSP

  • rtsp: terminate error responses and echo CSeq. PAUSE, RECORD and error responses were a bare status line, with no CSeq and no blank line ending the headers, so clients waited for the rest of the response. OPTIONS no longer advertises PAUSE, which isn't supported.
  • rtsp: drop stalled TCP clients instead of blocking all streams. Interleaved RTP writes retried EAGAIN without limit, so one client that stopped reading blocked every other client. A client is now dropped after 2 s without progress. A failed send no longer stops delivery to the clients after it.

HTTP / OSD

  • server: reject malformed OSD uploads instead of crashing. Any HTTP client could crash the streamer with a multipart POST to /api/osd that has a missing boundary, a missing header terminator or closing boundary, or an over-long boundary. A failed fopen() was also passed to fwrite(). Such requests now get a 400 or 500.
  • server: show uploaded OSD images. Uploaded PNGs were saved but never displayed, because the region kept its previous image path.
  • region: fix PNG decoding error handling. Errors were checked through the buffer pointer instead of the return code, so a corrupt image was converted from uninitialized memory. Early errors freed uninitialized pointers, and the decoder context leaked on success.
  • text: clip glyphs to the OSD canvas. With an outline set, glyphs at the edges of a line were written before the start and past the end of the canvas buffer, corrupting the heap.

Config

  • config: terminate the executable path before taking its directory. dirname() ran on the readlink() result before it was NUL-terminated.
  • config: leave room for the list terminators. A fourth entry in web_whitelist or stream.dest pushed the list terminator past the array. Behaviour change: these lists now hold at most three entries, so a fourth one is ignored instead of overflowing.

Testing

Built for ARM (musl). On FH8626V100 hardware, with the V4L2 backend on top:

  • Audio starts reliably.
  • After /api/audio?srate=16000, the MP4 audio track reports 16 kHz.
  • A PNG uploaded to /api/osd/1 appears in the stream.
  • Malformed multipart uploads get a 400, and the process stays up.
  • RTSP H.264 and PCMA streaming is unaffected.

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Fix audio, RTSP, OSD, and configuration reliability

🐞 Bug fix 🕐 40+ Minutes

Grey Divider

AI Description

• Prevent audio startup races and keep G.711 and MP4 settings aligned with runtime changes.
• Complete RTSP error responses and disconnect stalled TCP clients without stopping delivery to
 others.
• Reject malformed OSD uploads, safely render images and text, and prevent configuration buffer
 overruns.
Diagram

graph TD
  CFG["Config parser"] --> API["HTTP API"] --> MEDIA["Media pipeline"] --> MP4["MP4 muxer"]
  MEDIA --> RTP["RTP delivery"]
  RTSP["RTSP control"] --> RTP
  API --> OSD["OSD renderer"]
  CFG --> MEDIA
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Per-client nonblocking RTP queues
  • ➕ Isolates slow clients without a potential two-second wait in the shared sender.
  • ➖ Requires queue limits, backpressure policy, and more complex session cleanup.
2. Length-bounded multipart parser
  • ➕ Provides a more systematic way to validate binary uploads and multipart framing.
  • ➖ Broadens the change beyond the immediate malformed-request fixes.

Recommendation: The targeted fixes suit this bug-fix PR. Consider per-client queues if stalled-client delays remain significant, and a length-bounded parser if the OSD upload endpoint needs broader multipart support.

Files changed (8) +119 / -76

Bug fix (8) +119 / -76
app_config.cPrevent executable-path and list-terminator overruns +5/-4

Prevent executable-path and list-terminator overruns

• Terminates the readlink result before passing it to dirname during configuration lookup and restore. Reserves a terminator slot in the web whitelist and stream destination arrays, limiting each to three entries.

src/app_config.c

media.cStabilize audio startup and refresh stream audio settings +22/-13

Stabilize audio startup and refresh stream audio settings

• Sets audioOn before creating audio threads and clears it on thread-creation failure. Recalculates the G.711 resampling step from the current sample rate without the previous Q16 shift overflow. Adds a mutex-protected MP4 configuration function used during startup and runtime changes.

src/media.c

media.hExpose MP4 configuration refresh +1/-0

Expose MP4 configuration refresh

• Declares media_mp4_config so HTTP audio settings changes can refresh the MP4 muxer.

src/media.h

region.cHandle PNG decoder errors and release resources +3/-2

Handle PNG decoder errors and release resources

• Initializes image buffers before error handling and checks the PNG decoder return code. Frees the decoder context after successful conversion as well as on failure.

src/region.c

rtp.cBound interleaved TCP sends and continue past failed clients +37/-36

Bound interleaved TCP sends and continue past failed clients

• Replaces indefinite EAGAIN retries with a send helper that shuts down clients after repeated polls without progress. Both staged and direct TCP delivery continue iterating over other clients after a send failure.

src/rtsp/rtp.c

rtsp.cSend complete RTSP error responses +10/-4

Send complete RTSP error responses

• Adds CSeq and the terminating blank line to PAUSE, RECORD, and generic error responses. Removes unsupported PAUSE from the OPTIONS advertised methods.

src/rtsp/rtsp.c

server.cApply audio changes to MP4 and validate OSD uploads +33/-10

Apply audio changes to MP4 and validate OSD uploads

• Refreshes MP4 audio settings and requests an IDR after runtime audio changes. Rejects malformed multipart uploads and file-open failures, then records the uploaded image path so the OSD can display it.

src/server.c

text.cClip glyph compositing to the OSD canvas +8/-7

Clip glyph compositing to the OSD canvas

• Checks each glyph row and column against canvas bounds before accessing destination pixels, preventing out-of-bounds writes from outlines or negative bearings.

src/text.c

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Nov 8, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (3) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Live video breaks after audio changes 🐞 Bug ≡ Correctness
Description
media_mp4_config() invalidates the global MP4 header, but the /api/audio path leaves each
connected client's header_sent flag set. When the new header is generated, existing /video.mp4
clients skip it and continue receiving fragments under their old audio initialization metadata.
Code

src/server.c[R910-912]

+            if (app_config.mp4_enable) {
+                media_mp4_config();
+                request_idr();
Evidence
The new API call resets the cached muxer header, while the live-output path sends a header only to
clients whose persistent header_sent flag is false.

src/server.c[907-913]
src/media.c[742-748]
src/fmt/mp4.c[65-79]
src/server.c[275-300]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Existing MP4 clients retain an initialization header that no longer describes fragments after `/api/audio` changes the muxer configuration.
## Fix Focus Areas
- src/server.c[910-913]
- src/server.c[275-291]
- src/media.c[742-748]
## Recommended Fix
When audio metadata changes, end affected live MP4 connections so clients reconnect and receive a matching initialization header. Coordinate the change with MP4 output under the existing locks.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Active recordings mix audio formats 🐞 Bug ≡ Correctness
Description
media_mp4_config() resets global MP4 metadata and timing without rotating an active recording or
resetting its recordState. If /api/audio changes audio settings during recording, subsequent
fragments are appended to a file with the old initialization header and continuing fragment state.
Code

src/server.c[R910-912]

+            if (app_config.mp4_enable) {
+                media_mp4_config();
+                request_idr();
Evidence
The new configuration call clears global header and parameter-set state, but active recordings only
reset their header and fragment state when a file is closed and reopened; their fragment path
otherwise keeps using the existing state.

src/server.c[907-913]
src/fmt/mp4.c[65-79]
src/record.c[91-132]
src/record.c[194-219]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
An active recording continues in the same file after its muxer configuration changes, leaving its existing header inconsistent with later fragments.
## Fix Focus Areas
- src/server.c[910-913]
- src/media.c[742-748]
- src/record.c[91-132]
## Recommended Fix
Coordinate audio reconfiguration with the recorder: finish the current segment before changing MP4 settings, then open a new segment with fresh recording state and the rebuilt header.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

3. Audio startup failures leak resources 🐞 Bug ☼ Reliability
Description
media_audio_enable() clears audioOn when either pthread_create() fails, but continues startup
and returns the earlier HAL result. On a partial thread-creation failure, media_audio_disable()
then exits immediately because audioOn is false, leaving initialized audio resources and any
successfully created thread unjoined.
Code

src/media.c[R621-624]

+                        &aencPid, &thread_attr, (void *(*)(void *))aenc_thread, NULL)) {
+            audioOn = 0;
          HAL_ERROR("media", "Starting the audio encoding thread failed!\n");
+        }
Evidence
The changed failure branches set audioOn to zero without stopping startup; disable uses that flag
to skip all joins and cleanup, and enable returns ret rather than the thread-creation result.

src/media.c[591-630]
src/media.c[505-537]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A thread-creation failure marks audio off without cleaning up resources or reliably reporting startup failure.
## Fix Focus Areas
- src/media.c[591-630]
- src/media.c[505-537]
## Recommended Fix
Track which audio threads and resources started successfully. On either creation failure, stop and join only created threads, release initialized resources and platform audio state, and return a failure code rather than continuing startup.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can pick a display mode instead of tweaking every Display option by hand

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

@difanz

difanz commented Nov 8, 2026

Copy link
Copy Markdown
Contributor Author

None of these are regressions from this PR:

  • Connected /video.mp4 clients: before this change, every client got a header describing the old audio track after /api/audio. Now only clients that were already connected keep the stale one. /api/mp4 reconfiguration has the same behaviour today. Handling it properly means disconnecting MP4 clients on reconfiguration (players don't accept a second init segment mid-stream), which is better as a separate change covering both endpoints.
  • Active recordings: same situation. Recordings were already left running across both audio and /api/mp4 changes.
  • Thread-creation failure: HAL_ERROR returns EXIT_FAILURE, so the failure is reported. The missing unwind on a failed pthread_create predates this PR; this commit only moves where audioOn is set so the threads don't exit at startup.

difanz added 11 commits November 8, 2026 15:21
The capture and encoding threads loop while audioOn is set, but it was
only set after both threads had been created. A thread scheduled before
that returned at once and audio stayed silent until the next restart.
The decimation step was computed once and kept in a static, so after
/api/audio changed the sample rate RTSP G.711 audio was resampled at the
old ratio and played at the wrong speed. The Q16 step also overflowed for
sample rates of 65536 Hz and above. Compute it per call without overflow.
The PAUSE, RECORD and error responses were a bare status line without
CSeq or the empty line ending the headers, so a client that paused kept
waiting for the rest of the response. OPTIONS also advertised PAUSE,
which is not supported. Send complete responses and drop PAUSE from the
Public header.
Interleaved RTP writes retried EAGAIN without limit, so a client that
kept its connection open but stopped reading blocked the sending thread
and every other client with it. A failed send also aborted delivery to
the clients after it in the list.

Give up on a client after 2 s without progress and shut its socket down,
which ends the session through the normal EOF path, and keep sending to
the remaining clients.
A multipart POST to /api/osd without a boundary parameter, without the
part header terminator or without the closing boundary dereferenced NULL
or wrote with a bogus length, and a failed fopen() was passed to
fwrite(). A boundary longer than 127 bytes also overflowed the char
length passed to memstr() and read past the request. Any HTTP client
could crash the streamer this way. Accept RFC 2046 boundaries only and
answer 400 or 500 instead.
An image uploaded to /api/osd was stored as /tmp/osdN.png or .bmp, but
the region thread loads osds[N].img and only falls back to
/tmp/osdN.bmp when that is empty. PNG uploads were therefore never shown,
and neither was any upload to a region that already named an image.
Point the region at the stored file.
Decode failures were tested through the buffer pointer instead of the
returned error, so a corrupt image was converted from uninitialized
memory. Errors raised before the buffers were allocated freed
uninitialized pointers, and the decoder context leaked on success.
The canvas leaves room for negative left bearings only, while outlined
text is also drawn up to the outline thickness above, below and to
either side of each glyph. With an outline set, the first glyph of a
line was written in front of the canvas buffer and the last ones past
its end, corrupting the heap. Clip every glyph to the canvas.
dirname() ran on the readlink() result before it was NUL-terminated, so
it scanned uninitialized stack bytes and could derive the configuration
path next to the executable from the wrong directory.
The web whitelist and stream destination lists are read up to an empty
entry, and the parser stored up to all four entries before the caller
wrote that terminator. A fourth entry therefore put the terminator past
the array and its readers went on into the following settings. Parse at
most three entries.
/api/audio restarts audio with a new rate, bitrate or enable state, but
the MP4 muxer kept describing the old audio track. Reapply the MP4
configuration there and request an IDR so the header is rebuilt, and
take mp4Mtx while doing so since the video thread muxes concurrently.
@difanz difanz mentioned this pull request Nov 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant