rtsp: send interleaved frames in one write, stamp per frame, add G.711 - #42
rtsp: send interleaved frames in one write, stamp per frame, add G.711#42kasperiio wants to merge 1 commit into
Conversation
PR Summary by QodoOptimize RTSP frame delivery and add configurable G.711 audio
AI Description
Diagram
High-Level Assessment
Files changed (13)
|
Code Review by Qodo
1. Audio-only TCP never flushes
|
| struct connection_item_t *con; | ||
| list_upcast(trans, e); | ||
| MUST(con = trans->con, return FAILURE); | ||
| if (!con->trans[0].is_tcp || !con->tx_buf || !con->tx_len) return SUCCESS; |
There was a problem hiding this comment.
1. Audio-only tcp never flushes 🐞 Bug ≡ Correctness
__tcp_flush_each() checks the video track's TCP state even when flushing an audio transfer. Clients that SETUP only track 1 over interleaved TCP accumulate audio in tx_buf but never receive it.
Agent Prompt
## Issue description
Audio-only interleaved RTSP sessions never flush staged RTP because the flush callback always checks track 0.
## Issue Context
The callback already has the current `transfer_item_t`; use its associated track or remove the incorrect video-track gate while retaining the buffer checks and locking.
## Fix Focus Areas
- src/rtsp/rtp.c[63-73]
- src/rtsp/rtp.c[213-258]
- src/rtsp/rtp.c[552-575]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| else if (EQUALS(key, "audio_codec")) | ||
| strncpy(app_config.rtsp_audio_codec, value, sizeof(app_config.rtsp_audio_codec) - 1); |
There was a problem hiding this comment.
2. Codec switch corrupts sessions 🐞 Bug ≡ Correctness
The restart-only API setting immediately changes the codec branches read by aenc_thread(). Existing sessions negotiated for PT 8/8 kHz or PT 14/90 kHz consequently start receiving the other payload type and clock without a new DESCRIBE/SETUP.
Agent Prompt
## Issue description
Updating the restart-only codec setting changes active RTP output immediately and invalidates negotiated sessions.
## Issue Context
Keep the active RTSP codec immutable for the server lifetime, restart the RTSP/audio pipeline atomically, or reject live changes while sessions are active.
## Fix Focus Areas
- src/server.c[1223-1235]
- src/media.c[91-92]
- src/media.c[118-126]
- src/rtsp/rtp.c[552-601]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| if (!dec) dec = app_config.audio_srate / 8000 > 0 ? app_config.audio_srate / 8000 : 1; | ||
| for (unsigned int i = 0; i < samples; i++) { | ||
| acc += pcm[i]; | ||
| if (++accN < dec) continue; | ||
| alaw[fill++] = pcm_to_alaw((short)(acc / (int)dec)); |
There was a problem hiding this comment.
3. Pcma resampling runs fast 🐞 Bug ≡ Correctness
rtsp_pcma_feed() truncates audio_srate / 8000, so a supported rate such as 44.1 kHz emits 8,820 samples per second instead of 8,000. The resulting 160-byte packets represent about 18.1 ms while RTP timestamps and SDP declare an 8 kHz clock.
Agent Prompt
## Issue description
Integer-factor decimation does not produce 8 kHz PCMA for supported non-multiple input rates.
## Issue Context
Use a fractional phase accumulator or resampler that emits exactly 8,000 samples per second for every accepted capture rate, and derive RTP timestamp progression from emitted samples.
## Fix Focus Areas
- src/media.c[54-71]
- src/rtsp/rtp.c[552-575]
- src/app_config.c[457-462]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
|
|
||
| parse_bool(&ini, "rtsp", "enable", &app_config.rtsp_enable); | ||
| parse_int(&ini, "rtsp", "port", 0, USHRT_MAX, &app_config.rtsp_port); | ||
| parse_param_value(&ini, "rtsp", "audio_codec", app_config.rtsp_audio_codec); |
There was a problem hiding this comment.
4. Codec yaml overflows buffer 🐞 Bug ⛨ Security
The new 8-byte rtsp_audio_codec array is passed to the unbounded parse_param_value(). A YAML value longer than seven characters writes beyond the field and can corrupt adjacent application configuration during startup.
Agent Prompt
## Issue description
Parsing a long `rtsp.audio_codec` YAML value overflows the newly added eight-byte field.
## Issue Context
Introduce a size-aware parser or parse into a sufficiently sized temporary buffer, validate against exactly `pcma` and `mp3`, and only then copy into the configuration field.
## Fix Focus Areas
- src/app_config.c[435-437]
- src/app_config.h[60-63]
- src/hal/config.c[58-98]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| "{\"enable\":%s,\"enable_auth\":%s,\"port\":%d,\"auth_user\":\"%s\",\"audio_codec\":\"%s\"," | ||
| "\"note\":\"port and codec changes apply after restart\"}", | ||
| app_config.rtsp_enable ? "true" : "false", app_config.rtsp_enable_auth ? "true" : "false", | ||
| app_config.rtsp_port, app_config.rtsp_auth_user, app_config.rtsp_audio_codec); |
There was a problem hiding this comment.
5. Api emits invalid json 🐞 Bug ≡ Correctness
The new endpoints interpolate decoded usernames and codec values directly into quoted JSON strings without escaping them. Quotes, backslashes, or control characters therefore make the response invalid and cause the web UI's JSON.parse() to fail.
Agent Prompt
## Issue description
User-controlled RTSP and ONVIF values are emitted as raw JSON string contents.
## Issue Context
Serialize responses with a JSON encoder or correctly escape quotation marks, backslashes, and control characters before formatting both endpoint responses.
## Fix Focus Areas
- src/server.c[1219-1235]
- src/server.c[1251-1264]
- res/index.html[184-224]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
On a small SoC the RTSP sender spent more CPU on send() syscalls and on spinning over partial writes than on encoding. - Interleaved (TCP) packets are staged and written once per frame; a send() costs about 100 us here whatever its size. - The socket send buffer is raised to 512 KB, enough for one keyframe, so the sender no longer spins on EAGAIN for as long as the link takes to drain. - Partial writes wait in poll() instead of usleep(1000). - Timestamps are taken once per frame from the encoder's capture time. Stamping only the marker packet gave every earlier packet of a frame the previous frame's time, so receivers saw timestamps go backwards within a frame and players stalled and then raced to catch up. - G.711 A-law (payload type 8) as an alternative to MP3 for RTSP audio, which also lets the software MP3 encoder be skipped when nothing consumes MP3. - The MP4 muxer no longer builds moof/mdat when no HTTP client is connected, and resets its cached header when the stream configuration changes. - /api/rtsp and /api/onvif expose those settings, with the matching web UI. - bitbuf: copy with memcpy instead of a byte loop. Review fixes: - __tcp_flush_each() gated on track 0, so a client that set up only audio over interleaved TCP staged packets that were never flushed. - The RTSP audio codec is now latched when the server starts. Changing it through /api/rtsp used to switch the payload type and clock under sessions already negotiated for the other one; the API documented a restart but the media path read the value live. Both the API and the config parser now accept only pcma and mp3. - The 8 kHz resampling ratio is carried in 16.16 fixed point. Truncating srate / 8000 emitted 8820 samples a second at 44.1 kHz against an 8 kHz SDP and RTP clock, so G.711 ran fast and packets were 18.1 ms rather than 20. - G.711 timestamps advance by the samples actually sent rather than by millis(). - rtsp.audio_codec is parsed with a new bounded parse_param_value_n(); the unbounded parse_param_value() sprintf()s into the caller's buffer, which overflows the eight-byte field. Existing callers are unchanged. - /api/rtsp and /api/onvif escape the usernames and codec they echo back, which otherwise made the response unparseable for the web UI.
b626265 to
2585748
Compare
Adds src/hal/fh: a HAL for the Fullhan FH8852/FH8856 V100 generation (ARM1176 softfloat, kernel 3.0.8, SDK V1.2.0 "OSDRV" libraries libdsp/ libisp/libispcore/libvmm/libmipi/libadvapi/libacw_mpi). The SDK ships as binary-only shared objects without headers; the interface was recovered from the libraries and a vendor application that statically links the same SDK, and verified on an Asecam/Vatilon PB1 (FH8856 + GC4653). - fh_sys/fh_vpss/fh_venc/fh_isp/fh_aud: dlopen wrappers for the MPI subset - fh_snr_gc4653: userspace GC4653 driver (the ISP calls back into a sensor op table; registers go over /dev/i2c-0) - H.264 and H.265 over RTSP, MJPEG, JPEG snapshots, audio capture, OSD via the VPU graphic plane (ARGB1555 at sensor resolution) - anti-flicker via the AE flicker command; SmartIR image-gain day/night detection wired into night mode (no external light sensor needed) - the VPU exposes two scaler channels (main + one sub); the JPEG snapshot is taken from the MJPEG sub-stream when MJPEG is enabled, mirroring the vendor - fh_compat: getifaddrs() over SIOCGIFCONF; gpio.c resolves GPIO<n> vs gpio<n> sysfs node naming (fh kernels use uppercase) - server: do not crash on an OSD POST without a Content-Type header - platform detection via /proc/driver/chip; built only for ARMv6 targets Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Epx86cKNLr14TY4B41nq89 Includes the fixes from the upstream review of the three PRs this branch is split into (OpenIPC/divinus OpenIPC#41, OpenIPC#42, OpenIPC#43): stage-aware rollback on media init failure, the RTCP bounds check before indexing, validated audio API ranges, interleaved flush for audio-only sessions, a latched RTSP audio codec, exact 8 kHz G.711 resampling, bounded config parsing for rtsp.audio_codec and night_mode.lamp, JSON escaping in the new endpoints, OSD clipping and bitmap scaling, per-region OSD opacity, and checked snapshot reallocations.
Adds src/hal/fh: a HAL for the Fullhan FH8852/FH8856 V100 generation (ARM1176 softfloat, kernel 3.0.8, SDK V1.2.0 "OSDRV" libraries libdsp/ libisp/libispcore/libvmm/libmipi/libadvapi/libacw_mpi). The SDK ships as binary-only shared objects without headers; the interface was recovered from the libraries and a vendor application that statically links the same SDK, and verified on an Asecam/Vatilon PB1 (FH8856 + GC4653). - fh_sys/fh_vpss/fh_venc/fh_isp/fh_aud: dlopen wrappers for the MPI subset - fh_snr_gc4653: userspace GC4653 driver (the ISP calls back into a sensor op table; registers go over /dev/i2c-0) - H.264 and H.265 over RTSP, MJPEG, JPEG snapshots, audio capture, OSD via the VPU graphic plane (ARGB1555 at sensor resolution) - anti-flicker via the AE flicker command; SmartIR image-gain day/night detection wired into night mode (no external light sensor needed) - the VPU exposes two scaler channels (main + one sub); the JPEG snapshot is taken from the MJPEG sub-stream when MJPEG is enabled, mirroring the vendor - fh_compat: getifaddrs() over SIOCGIFCONF; gpio.c resolves GPIO<n> vs gpio<n> sysfs node naming (fh kernels use uppercase) - server: do not crash on an OSD POST without a Content-Type header - platform detection via /proc/driver/chip; built only for ARMv6 targets Claude-Session: https://claude.ai/code/session_01Epx86cKNLr14TY4B41nq89 Includes the fixes from the upstream review of the three PRs this branch is split into (OpenIPC/divinus OpenIPC#41, OpenIPC#42, OpenIPC#43): stage-aware rollback on media init failure, the RTCP bounds check before indexing, validated audio API ranges, interleaved flush for audio-only sessions, a latched RTSP audio codec, exact 8 kHz G.711 resampling, bounded config parsing for rtsp.audio_codec and night_mode.lamp, JSON escaping in the new endpoints, OSD clipping and bitmap scaling, per-region OSD opacity, and checked snapshot reallocations.
On a small SoC (ARM1176 at 600 MHz) the RTSP sender spent more CPU on
send()syscalls and on spinning over partial writes than the encoder spent encoding. In Frigate this showed as a stream that lagged and then fast-forwarded.send()costs roughly 100 µs here regardless of size, and a frame is many packets.EAGAINfor as long as the link took to drain — hundreds of ms of CPU per keyframe.SO_SNDBUFFORCEwith a fallback toSO_SNDBUF.poll()instead ofusleep(1000)on a partial write.rtsp.audio_codec. The SDP now advertises 8000 Hz for PT 0/8 rather than 90000. This also allows the software MP3 encoder — the single most expensive thing on this SoC, ~24 % of a core at 48 kHz — to be skipped entirely when nothing consumes MP3.moof/mdatwhen no HTTP client is connected (parameter sets are still cached, so a header is ready on connect), and resets its cached header when the stream configuration changes — without that a codec switch kept sending the old one./api/rtspand/api/onvifexpose those settings, with the matching web UI section.bitbuf:memcpyinstead of a byte-at-a-time loop.Testing
Built for
fh8856v100_lite(ARMv6). Runs as part of the combined branch this was split from, on three Fullhan FH8856 cameras — RTSP with both MP3 and G.711 audio, ONVIF, MP4 and MJPEG over HTTP. This branch on its own is build-tested, not separately run on hardware.Sits on top of the four bug fixes in the companion PR; independent of the HAL PR.