Two edge-cluster regressions surfaced when validating an RTMP
origin/edge setup. Each is a small, surgical fix in its own commit.
- **HTTP-FLV play on edge always 404'd.**
`SrsHttpStreamServer::assemble()` registered the dynamic matcher only
when the mux cast was `NULL` (inverted guard), so the matcher was never
wired up. On edge the FLV mount is created lazily by the dynamic
matcher, so every HTTP-FLV client got 404. Invert the guard to register
when the mux is valid, mirroring the destructor.
(`trunk/src/app/srs_app_http_stream.cpp`)
- **RTMP players that join an edge stream after the first player fail to
decode.** After v7.0.94 (#4513) stopped creating `SrsOriginHub` on edge,
the `hub_active` gate in `SrsLiveSource::consumer_dumps()` always
evaluated false on edge. That gate guards the dump of cached
`onMetaData` + AVC sequence header + AAC sequence header + GOP cache to
a new consumer. Result: the first player attaches before the edge-pull
starts and gets headers via the live fan-out, but every subsequent
player gets coded payload with no codec config and ffmpeg aborts with
`dimensions not set` / `Could not write header`. Fall back to the meta
cache state when `hub_` is `NULL`, so the dump path runs once the
edge-pull has populated the cache.
(`trunk/src/app/srs_app_rtmp_source.cpp`)
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Fix a goroutine leak on the WHEP path: the backend→client reader was
being spawned on every inbound client packet (STUN keepalives + RTCP
feedback), leaking tens of thousands of goroutines under steady-state
load. Now spawned exactly once per connection via `sync.Once` on both
the RTC and SRT proxies. Listener and reader receive buffers are also
reused across iterations.
- Make the legacy SRS `/rtc/v1/play/` and `/rtc/v1/publish/` APIs work
end-to-end through the proxy. Those endpoints wrap the SDP in a JSON
envelope (`{"sdp":"v=0\r\n..."}` where `\r\n` is the literal 2-byte JSON
escape, not real CRLF), so ICE parsing previously absorbed the rest of
the body into the ufrag. Added `unwrapSDPEnvelope` for ICE extraction
and tightened `ParseIceUfragPwd`'s value class to stop at `\`. The bytes
forwarded to the client and the in-body candidate-port rewrite still
operate on the raw envelope.
- Enable `net/http/pprof` endpoints when `GO_PPROF` is set (blank import
in `internal/debug/pprof.go`) and add `docs/perf/proxy-whep.md` walking
through CPU/alloc/heap/goroutine/trace collection and `pprof -base`
before/after diffs for the WHEP workload (1 publisher + N players).
- Tighten `SRTHandshakePacket.UnmarshalBinary` to
`bytes.Clone(ExtraData)` so decoded handshakes kept on the connection
(`handshake0`, `handshake2`) stay valid once the receive buffer is
reused.
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
"Free" represents the new era of open source development empowered by AI. Both freedom and free — the AI agent is not just free labor, it is like a copy of myself, or even tens of copies, all deeply understanding this project, how to deliver high-quality code, and how to serve the community. AI handles all the dirty work — the boring tasks, the documentation, the coding — and often does it better than I could, with ten times the power. We built an AI robot for the community to answer questions and help users learn this project, and we used AI to almost entirely rewrite the SRS Proxy server — its structure, its workflow — so that AI agents can comprehensively manage and maintain it. With AI I have power, and I have choice: no longer waiting for other developers to contribute, I am free to manage this project, freed from the labor of maintaining it alone. This is a fantastic, amazing new era for building and sustaining open source projects and communities.
- Refactor the Go proxy for dependency injection: every proxy server,
the bootstrap, the signal handler, the load balancers, and AMF0 now accept
functional-option seams (factories/closures) so tests can inject fakes
without binding real sockets, talking to real Redis, or racing on
package globals.
- Drop the package-global `lb.SrsLoadBalancer`. The bootstrap creates
the LB locally and threads it through every proxy server constructor. Two old
global indirections in `internal/signal` and `internal/rtmp/amf0` are
likewise replaced by per-instance fields.
- Rename `internal/server` → `internal/proxy` and rename the `lb` public
surface for clarity: `SRSLoadBalancer` is split into `OriginService` /
`HLSService` / `RTCService` and recomposed as `OriginLoadBalancer`;
`SRSServer` → `OriginServer`; all proxy server types gain a `Proxy`
qualifier (e.g. `RTMPServer` → `RTMPProxyServer`).
- Extract the Redis client behind a new `internal/redisclient` package
with a minimal `RedisClient` interface and a counterfeiter fake.
- Add counterfeiter fakes (`proxyfakes`, `lbfakes`, `redisclientfakes`)
and ~7.5k lines of unit tests covering bootstrap, memory + Redis LBs, all
five proxy servers, the signal handler, and AMF0.
- Add two new E2E flows — `proxy-e2e-srt-test.sh` (SRT publish through
proxy, verify SRT/RTMP/HTTP-FLV/HLS playback) and `proxy-e2e-whip-test.sh`
(WHIP publish, verify RTMP/HTTP-FLV/HLS via origin `rtc_to_rtmp`) — plus
`setup-ffmpeg-with-whip.sh`, a macOS builder for an ffmpeg with
openssl-DTLS WHIP and SRT support that the two scripts auto-invoke when needed.
- Workspace reorg: move `memory/` and `skills/` to the repo root so all
agent tools (Claude / Codex / Kiro / OpenClaw) share one source of truth via
symlinks. Sync `docs/proxy/proxy-load-balancer.md` and
`memory/srs-codebase-map.md` with the new names.
No protocol, log, HTTP API, or wire-format changes. Refactor only — all
externally observable proxy behavior is unchanged.
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: chatgpt-codex-connector[bot] <199175422+chatgpt-codex-connector[bot]@users.noreply.github.com>
Modernizes several `internal/*` packages under the Go proxy, replaces
third-party forks with standard-library primitives, and brings the
test suite from near-zero to high coverage across the touched packages.
Package changes
- **`internal/errors`** — Rewrites the `pkg/errors` fork as a thin
wrapper
over stdlib `errors`. A single `withStack` struct captures stack
traces via `runtime.Callers`; `fmt.Errorf("%w", ...)` handles all
message wrapping. Restores `errors.Is`/`As`/`Unwrap` chain traversal
(silently broken in the fork) and deletes ~190 lines of stack/frame
formatting. `Is`, `As`, `Unwrap`, and `Join` are re-exported so
callers need a single import.
- **`internal/logger`** — Swaps stdlib `log.Logger` for `log/slog` JSON
handlers with UTC timestamps and custom level labels (`verb`, `debug`,
`warn`, `error`). Hides `withContextID` (no external callers).
- **`internal/sync`** — Converts `Map[K, V]` from a concrete struct to
an interface with a `NewMap` constructor for testability.
- **`internal/signal`** — Adds `signalNotify` / `osExit` indirections so
`InstallSignals` and `InstallForceQuit` can be exercised without real
OS signals or process termination.
- **`internal/utils`** — Drops deprecated `io/ioutil` and the stdlib
`errors` alias (the internal `errors` package re-exports what's
needed).
- **`internal/version`** — No code changes; fully covered by new tests.
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- **Refactor `internal/env` for testability.** Route every
`os`/filesystem
call in `env.go` through swappable package-level function variables
(`getEnv`, `setEnv`, `lookupEnv`, `openFile`). Split `parseEnvFile` into
a
thin file-opening wrapper plus a pure `parseEnvReader(io.Reader)` so the
parser can be tested directly without touching disk.
- **Hermetic tests, 96.9% coverage.** Rewrite `internal/env/env_test.go`
to
install in-memory fakes via `withFakeEnv` / `withFakeOpen` helpers that
swap the package vars and restore them on `t.Cleanup`. Tests no longer
mutate real process env or write temp `.env` files, removing a source of
flakiness under parallel test execution. New cases cover
`NewEnvironment`,
`setEnvDefault`, `loadEnvFile` error paths, and edge cases in the
parser.
- **Counterfeiter-based fake generation.** Add `counterfeiter` as a Go
tool
dependency, a `//go:generate` directive for the `Environment` interface
(`internal/env/gen.go`), and commit the generated
`internal/env/envfakes/fake_environment.go` so downstream packages can
test against a spec-faithful fake instead of hand-rolling stubs. Expose
the step as `make generate`.
- **Tooling.** `scripts/proxy-utest.sh` gains a `--coverage` / `-c` flag
that runs `go test -coverprofile=...` across `./cmd/...` and
`./internal/...` and prints per-function coverage via `go tool cover
-func`. The `srs-develop` skill doc is updated to include the
regenerate-fakes step and the new coverage flag.
- **Go version.** Bump `go.mod` to Go 1.25 (required for the `go tool`
directive used to pin the counterfeiter CLI as a tool dep).
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
- Move build output from `./srs-proxy` to `bin/srs-proxy` following Go
project conventions, updating Makefile, .gitignore, and all
documentation references
- Replace third-party `godotenv` dependency with a custom `.env` parser
that supports comments, `export` prefix, quoted values, escape
sequences, and inline comments — with full unit tests
- Remove `ignore-worklog.md` and update `README.md` with skill-based AI
prompts
- Bump copyright year from 2025 to 2026 across all source files
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
- Extract proxy bootstrap implementation from bootstrap.go into
internal/bootstrap/proxy.go, keeping only the Bootstrap interface in the
shared file. This prepares for origin/edge servers
to have their own bootstrap implementations.
- Rename NewBootstrap() → NewProxyBootstrap() to follow the explicit
factory naming convention.
- Rebrand signature from SRSProxy to SRSX and update logger context key
accordingly.
- Add srs-develop skill with task router, module routing workflow, proxy
unit test script, and RTMP E2E test script.
- Remove st-develop skill (superseded by srs-develop).
- Add srs-support eval #21 for HLS AnnexB decode error scenario.
Test plan
- go build ./cmd/proxy/... compiles successfully
- go test ./cmd/... ./internal/... passes
- E2E RTMP proxy test (proxy-e2e-test.sh) passes
- Verify proxy starts and logs SRSX-Proxy/<version> started
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Restructure the OpenClaw workspace so all SRS project directories are
accessible via symlinks from `.openclaw/`, eliminating the need for
parent traversal or absolute paths. All AI tools (OpenClaw, Claude Code,
Codex, Kiro) now see the same relative paths from the workspace root.
**Workspace restructuring**
- Add symlinks in `.openclaw/` for `trunk/`, `cmd/`, `internal/`,
`cmake/`, `docs/`, `objs/`, and a self-referential `.openclaw` link
- Add root-level `memory` symlink pointing to `.openclaw/memory`
- Simplify `TOOLS.md` working directory rules: everything is relative
from CWD
- Update `.gitignore` patterns for `personal*`, `support*`,
`srs-consults*` directories
**New codebase map (`memory/srs-codebase-map.md`)**
- Comprehensive map of the entire SRS codebase: C++ media server modules
(`core/`, `kernel/`, `protocol/`, `app/`), State Threads, Go next-gen
server (`cmd/` + `internal/`), documentation, and testing structure
- Enables AI to reason about which files are relevant to a question
instead of blind grepping
- Added "Codebase map first" rule to `MEMORY.md`: always load the map
before searching code
**Skill updates**
The `srs-support` has been reorganized into a three-phase workflow
consisting of Setup, Load Knowledge, and Answer by Topic. It now
features a tiered approach to knowledge integration, with the codebase
map being incorporated as the third layer.
- `st-develop`: Simplified setup, added codebase map reference
For both skills, the dynamic resolution logic for `SRS_ROOT` has been
eliminated. Now, all paths are relative.
**Documentation rewrite (`getting-started-ai.md`)**
- Replaced Augment Code / GitHub Copilot / PR review content with
current AI tooling: SRS Robot (Telegram/Discord), Claude Code, Codex,
Kiro, and OpenClaw
- Added sections on skills and the knowledge base philosophy
**Cleanup**
- Removed `docs/ideas.md`, `docs/youtube/` transcripts, and
`proxy/README.md`
- Removed "Ideas Capture" and "YouTube Channel Content" sections from
`MEMORY.md`
- Fixed origin cluster doc build command (`cd srs && make`)
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
- Rename workspace directory from openclaw/ to .openclaw/ (hidden)
- Update all symlinks in .claude/, .codex/, .kiro/ to point to ../.openclaw/
- Add memory symlinks for .claude/, .codex/, .kiro/
- Replace .codex/CODEX.md regular file with symlink to AGENTS.md
- Remove .codex/AGENTS.md symlink (replaced by CODEX.md symlink)
- Update internal paths in MEMORY.md, TOOLS.md
- Remove kb-review and srs-learn skills
- Update srs-support and st-develop skills: unified SRS_ROOT resolution, relative knowledge base paths
Replace vendor-specific config (.augment-guidelines, .augmentignore) with
a unified approach: .claude/, .codex/, and .kiro/ directories all symlink
to the canonical persona files in openclaw/ (SOUL.md, USER.md, MEMORY.md,
IDENTITY.md, TOOLS.md, AGENTS.md).
All artificial intelligence programming entities, including Claude Code, Codex,
and Kiro, possess commonality. the same identity, memory, and conventions
without file duplication.
- Remove .augment-guidelines and .augmentignore (Augment AI)
- Add .claude/ with settings.local.json hook to auto-load persona files
- Add .codex/ with config.toml and CODEX.md instruction entrypoint
- Rename .agents/skills to .codex/skills
- Add .kiro/steering/ with persona symlinks
- Document ACP working directory convention in TOOLS.md
- Update openclaw/.gitignore for .pi and extensions directories
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Reorganize the SRS (Simple Realtime Server) repository to
follow a conventional Go project structure, setting the stage for a
progressive transition from a C++ project to a Go project. The proxy,
which was once contained within its own `proxy/` subdirectory, will now
be converted into the initial Go module located at the root of the
repository, serving as a template for subsequent Go modules.
- **Go module at repo root:** `go.mod` moved to repo root, module
renamed from `proxy` to `srsx`. The repo is now a proper Go project with
`cmd/` and `internal/` at the top level.
- **Elevation of Proxy Code:** Move the proxy code from
`proxy/cmd/proxy-go/` to `cmd/proxy/`, and from `proxy/internal/` to
`internal/`. The proxy serves as the inaugural application; subsequent
modules (for instance, `cmd/origin`) will mimic this arrangement.
- **Documentation Restructured:** Transfer the documentation from
`proxy/docs/` to `docs/proxy/`, revise the main README to endorse
OpenClaw as the preferred AI tool, and update `proxy/README.md` to point
to the new documentation locations.
- **Build and config:** `Makefile` moved to root, `PROXY_STATIC_FILES`
default path corrected for the new layout, `.gitignore` consolidated.
- **Cleanup:** removed standalone `proxy/LICENSE` (repo-level license
applies), all internal imports updated to `srsx/internal/...`.
- **OpenClaw workspace:** added community bot info, git workflow
conventions, and support group behavior guidance.
This restructuring was performed by OpenClaw orchestrating Claude Code
and Codex via ACP.
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: chatgpt-codex-connector[bot] <199175422+chatgpt-codex-connector[bot]@users.noreply.github.com>
Rewrite srs-support SKILL.md with selective knowledge loading and structured
answering-by-topic sections. Add 15 eval test cases covering protocols, codecs,
scaling, comparisons, deployment, and access control.
Correct latency numbers in srs-overview.md: HLS is 10-30s in practice (not
3-5s), add concrete ranges for SRT (~500ms-1s), WebRTC (~50-400ms), and
HTTP-FLV (~1-3s). Add VLC player-side buffering warnings throughout.
Expand knowledge base entries: Security section now covers referer, IP
allow/deny, and HTTP callback auth (no built-in user management). HTTP Callback
corrected to v0.9. Edge Cluster clarified as viewer scaling with new version
planned. Windows section explains the ST + SRT C++ exception handling blocker.
Add SRS Community Bot section to MEMORY.md with Telegram/Discord links.
Update AGENTS.md to answer SRS support questions directly when mentioned.
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
- Add a comprehensive ST knowledge base document:
- openclaw/memory/srs-coroutines.md
- Add ST-focused developer skill:
- openclaw/skills/st-develop/SKILL.md
- openclaw/skills/st-develop/scripts/verify.sh
- Add KB workflow skills that support ST documentation quality and
learning:
- openclaw/skills/kb-review/SKILL.md
- openclaw/skills/srs-learn/SKILL.md
- Update openclaw/skills/srs-support/SKILL.md to use dynamic SRS_ROOT
path resolution, improving portability for KB/source
loading.
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: chatgpt-codex-connector[bot] <199175422+chatgpt-codex-connector[bot]@users.noreply.github.com>
Move the SRS proxy server code from the standalone repository
https://github.com/ossrs/proxy into the proxy/ directory of the
main SRS repo. Also update build instructions in origin-cluster.md.
# HLS/DASH: Fix dispose() to cleanup files after unpublish
## Summary
Fixes a bug where HLS/DASH files are not deleted after the configured
`hls_dispose`/`dash_dispose` timeout.
## Problem
When a stream is unpublished:
1. `on_unpublish()` is called and sets `enabled_ = false`
2. After the dispose timeout, `cycle()` calls `dispose()`
3. `dispose()` immediately returns due to `if (!enabled_)` check at line
2722 (HLS) and line 891 (DASH)
4. `controller_->dispose()` is never called
5. Files remain on disk indefinitely
**Observed behavior**:
- Stream stopped at 11:32:42
- `dispose()` called at 11:33:14 (after 30s timeout)
- Log shows "hls cycle to dispose hls" but no "gracefully dispose hls"
message
- Files remain on disk
## Root Cause
Commit 550760f2d introduced an early return in `dispose()` when
`!enabled_`, which prevents file cleanup after `on_unpublish()` has
already been called and set `enabled_` to false.
## Solution
Reorder the logic in `dispose()` to:
1. Check if dispose is enabled (hls_dispose/dash_dispose > 0) first
2. Call `on_unpublish()` only if `enabled_` is still true (prevents
duplicate calls)
3. Always call `controller_->dispose()` to cleanup files when dispose
timeout occurs
This ensures files are properly cleaned up while still preventing
duplicate `on_unpublish()` calls.
## Changes Made
- **trunk/src/app/srs_app_hls.cpp** (lines 2718-2734): Reordered
dispose() logic
- **trunk/src/app/srs_app_dash.cpp** (lines 887-902): Reordered
dispose() logic
- **trunk/doc/CHANGELOG.md**: Added v7.0.137 entry
## Testing Recommendation
To verify the fix:
1. Start RTMP stream to `/live/test`:
```bash
ffmpeg -re -i test.mp4 -c copy -f flv rtmp://localhost:1935/live/test
```
2. Wait for HLS segments to be created:
```bash
ls -la /path/to/hls/live/test/
```
3. Stop the stream (Ctrl+C)
4. Wait for `hls_dispose` timeout (default 120s, or 30s with your
config):
```bash
# Watch logs for "hls cycle to dispose hls" and "gracefully dispose hls"
tail -f srs.log
```
5. Verify files are deleted:
```bash
ls -la /path/to/hls/live/test/
# Should be empty or directory removed
```
**Expected results**:
- Before fix: Files remain on disk
- After fix: Files are deleted, logs show "gracefully dispose hls"
## Impact
- **Risk**: Low - minimal logic change, only reordering of checks
- **Breaking changes**: None
- **Performance**: No impact
- **Compatibility**: Fixes existing bug, improves expected behavior
## Checklist
- [x] Code follows project style
- [x] Both HLS and DASH are fixed
- [x] CHANGELOG updated
- [x] Tested locally (recommended before merge)
- [x] No breaking changes
## Related Issues
- Regression introduced in: 550760f2d
- Related to: #865 (hls_dispose feature)
---------
Co-authored-by: Jacob Su <suzp1984@gmail.com>
based on @HeeJoon-Kim's patch, try to fix#4594
Fix audio-only HLS fMP4 streams causing players to skip segments.
The bug was in segment_close() which always used video_dts_ (0 for
audio-only) to calculate the final sample duration, causing unsigned
integer overflow and corrupted m4s files.
The fix passes max(audio_dts_, video_dts_) from the controller to
segment_close(), ensuring correct duration calculation for both
audio-only and video streams.
---------
Co-authored-by: OSSRS-AI <winlinam@gmail.com>
When recording HEVC streams to MP4, DVR fails with error "doesn't
support
hvcC change" (ERROR_MP4_HVCC_CHANGE).
The root cause is in video_avc_demux(): the SrsVideoFrame object is
reused
across frames, and its initialize() method does not reset
avc_packet_type.
When a sequence header is processed, avc_packet_type is set to 0
(SrsVideoAvcFrameTraitSequenceHeader). When the next video info frame
arrives (which only appears in HEVC streams), the function returns early
without assigning video->avc_packet_type, so it retains the value 0 from
the previous sequence header frame.
When DVR processes this video info frame, it checks avc_packet_type and
incorrectly identifies it as a sequence header. Since the real HEVC
sequence
header was already recorded, DVR returns the "hvcC change" error.
The fix assigns video->avc_packet_type = packet_type before returning
early for VideoInfoFrame. After the fix, avc_packet_type is correctly
set
to the actual packet type (1 or 3 for coded frames), so DVR correctly
identifies it as NOT a sequence header.
---------
Co-authored-by: OSSRS-AI <winlinam@gmail.com>
When SRT publisher disconnects, player hangs indefinitely instead of
exiting after the configured peer_idle_timeout. This is because the
consumer wait() never checks if the publisher is still connected.
After fix, player waits for peer_idle_timeout (default 10s) then exits
gracefully when no packets arrive and publisher has disconnected.
We have discovered that some IP cameras send two publish packets in a
row.
The first packet is flash publish `publish('xxx')`
The second packet is FMLE publish `FCPublish('xxx|@setDataFrame()`
It seems that this is not processed correctly on the SRS side. In fact,
the stream is simply deinitialized, and republish is simply not
supported in this case.
As a fix, I suggest simply ignoring the FMLE publish packet after the
flash publish.
<img width="720" alt="screen"
src="https://github.com/user-attachments/assets/2db806ab-71b9-4e7b-bcf9-c16ea12df671"
/>
**Problem**: Newly created sources (RTMP/SRT/RTC/RTSP) were being
immediately marked as "dead" and deleted by the cleanup timer before
publishers could connect, causing "new live source, dead=1" errors.
**Root Cause**: All source constructors initialized `stream_die_at_ =
0`, causing `stream_is_dead()` to return `true` immediately since
current time was always greater than `0 + 3 seconds`.
**Solution**: Changed all four source constructors to initialize
`stream_die_at_ = srs_time_now_cached()`, giving newly created sources a
proper 3-second grace period before cleanup.
Fix issue #4570 by supporting optional `msid` attribute in WebRTC SDP
negotiation, enabling compatibility with libdatachannel and other
clients that don't include msid information.
SRS failed to negotiate WebRTC connections from libdatachannel clients
because:
- libdatachannel SDP lacks `a=ssrc:XX msid:stream_id track_id`
attributes
- SRS required msid information to create track descriptions
- According to RFC 8830, the msid attribute and its appdata (track_id)
are **optional**
If diligently look at the SDP generated by libdatachannel:
```
a=ssrc:42 cname:video-send
a=ssrc:43 cname:audio-send
```
It's deliberately missing the `a=ssrc:XX msid:stream_id track_id` line,
comparing that with this one:
```
a=ssrc:42 cname:video-send
a=ssrc:42 msid:stream_id video_track_id
a=ssrc:43 cname:audio-send
a=ssrc:43 msid:stream_id audio_track_id
```
In such a situation, to keep compatible with libdatachannel, if no msid
line in sdp, SRS comprehensively and consistently uses:
* app/stream as stream_id, such as live/livestream
* type=video|audio, cname, and ssrc as track_id, such as
track-video-video-send-43
Fix log flooding issue when processing SRT streams containing SCTE-35
PIDs or other unrecognized stream types.
The `SrsSrtFormat::on_srt_packet()` method continuously parses TS
packets throughout the entire stream lifetime. The TS parser logs
warnings for every unrecognized stream type (like SCTE-35) in the PMT,
causing log flooding.
However, `SrsFormat` is only used to detect audio/video codec
information. Once both codecs are detected, there's no need to continue
parsing TS packets.
Note: This fix mitigates the problem - there will still be some warning
logs during the initial codec detection phase (typically 5-10 seconds),
but the continuous log flooding after codec detection is completely
eliminated.
This PR adds G.711 (PCMU/PCMA) audio codec support for WebRTC in SRS,
enabling relay-only streaming of G.711 audio between WebRTC clients via
WHIP/WHEP. G.711 is a widely-used, royalty-free audio codec with
excellent compatibility across VoIP systems, IP cameras, and legacy
telephony equipment.
Fixes#4075
Many IP cameras, VoIP systems, and IoT devices use G.711 (PCMU/PCMA) as
their default audio codec. Previously, SRS only supported Opus for
WebRTC audio, requiring transcoding or rejecting G.711 streams entirely.
This PR enables direct relay of G.711 audio streams in WebRTC, similar
to how VP9/AV1 video codecs are supported.
Enhanced WHIP/WHEP players with URL-based codec selection:
```
# Audio codec only
http://localhost:8080/players/whip.html?acodec=pcmuhttp://localhost:8080/players/whip.html?acodec=pcma
# Video + audio codecs
http://localhost:8080/players/whip.html?vcodec=vp9&acodec=pcmuhttp://localhost:8080/players/whep.html?vcodec=h264&acodec=pcma
# Backward compatible (codec = vcodec)
http://localhost:8080/players/whip.html?codec=vp9
```
Testing
```bash
# Build and run unit tests
cd trunk
make utest -j && ./objs/srs_utest
# Test with WHIP player
# 1. Start SRS server
./objs/srs -c conf/rtc.conf
# 2. Open WHIP publisher with PCMU audio
http://localhost:8080/players/whip.html?acodec=pcmu
# 3. Open WHEP player to receive stream
http://localhost:8080/players/whep.html
```
## Related Issues
- Fixes#4075 - WebRTC G.711A Audio Codec Support
- Related to #4548 - VP9 codec support (similar relay-only pattern)
This PR adds separate audio and video frame counting to the HTTP API
(`/api/v1/streams/`) for better stream observability. The API now
reports three frame fields:
- `frames` - Total frames (video + audio)
- `video_frames` - Video frames/packets only
- `audio_frames` - Audio frames/packets only
This enhancement provides better visibility into stream composition and
helps detect issues with CBR/VBR streams, audio/video sync problems, and
codec-specific behavior.
**Before:**
```json
{
"streams": [
{
"frames": 0, // video frames.
}
]
}
```
**After:**
```json
{
"streams": [
{
"frames": 6912, // video frames.
"audio_frames": 5678, // audio frames.
"video_frames": 1234, // video frames.
}
]
}
```
Frame Counting Strategy
- All protocols report frames every N frames to balance accuracy and
performance
- Frames are counted at the protocol-specific message/packet level:
- RTMP: Counts RTMP messages (video/audio)
- WebRTC: Counts RTP packets (video/audio)
- SRT: Counts MPEG-TS messages (H.264/HEVC/AAC)