Tuning Wowza Streaming Engine for High-Concurrency CPU-Based Transcoding
Five checks for transcoding many concurrent streams to WebRTC on CPU-only Wowza Streaming Engine deployments, covering decoder implementation, codec pairing, resolution sizing, JVM and allocator tuning, and horizontal scaling.
What makes or breaks a high-concurrency WebRTC streaming workflow?
A containerized Wowza Streaming Engine instance can run on 4 vCPUs and 8 GB of RAM with no GPU attached. RTMP arrives from a camera fleet, and WebRTC carries the output to viewers who need sub-second latency. One stream works. Ten streams work. But somewhere past forty concurrent streams, you run out of memory. This is an illustrative example, sure, but it’s also a specific issue one of our customers recently faced.
The interesting part of that failure is how ordinary each contributing cause turned out to be. A decoder preset pointed at acceleration hardware the container did not have. An audio codec was valid for one delivery path, but invalid for the other. A source’s resolution was several times larger than anything the delivery target ever needed. A memory allocator ran on defaults tuned for a different pattern. Those elements compounded into a major issue, but the individual pieces easily went unnoticed.

Per-stream waste is invisible until you multiply it, and concurrency is the multiplier. A workflow that looks healthy in a smoke test can carry small inefficiencies that only become noticeable at scale. Below are five checks for anyone transcoding many concurrent streams on CPU-only infrastructure, and the use cases this workflow is best suited for.
When to use this workflow
This pattern shows up wherever many low-latency camera sources feed a small number of viewers each. Residential doorbell and intercom cameras. Body-worn cameras. Telehealth. Remote inspection and industrial monitoring feeds. The specifics differ, but the architecture holds across them all. They have a high source count, low viewer fan-out per source, a firm sub-second latency requirement, and a source codec or profile that doesn’t match what the delivery protocol needs. Wowza Streaming Engine transmuxes by default, and transcodes when the ingest stream’s audio codec, video codec, or video encoding profile differs from the WebRTC output.
Read the logs before changing anything
Configuration problems and capacity problems produce different evidence.
Capacity problems scale smoothly. CPU climbs with stream count, latency creeps up, and the degradation is proportional.
Configuration problems behave differently, though. They tend to leave a trail in the logs well before anything breaks, and when they break, they do so in a big way.
Two categories of log entry matter most for a CPU-only host: transcoding implementation and codec/container mismatches. Knowing which transcoding implementation each session actually used is important because a template can request accelerated decode and still run in software. In the packagers, codec and container mismatches register as warnings rather than errors (which can survive weeks of testing without being seen).
That usually narrows the problem to one of the five checks below.
Check one: classify the workload before tuning it
Before touching a template, confirm the instance sits in the right hardware class. Wowza publishes two recommended production tiers:
| Tier | CPU | RAM | Disk | Local network interface |
| Minimum production | Single quad core 3.00 GHz or better | 4 GB | HDD | 1 Gbps Ethernet |
| High-load production | 6 cores 3.00 GHz or better | 16-32 GB | SSD | 10 Gbps Ethernet |
Our customer had four vCPUs and 8 GB of RAM, which actually clears the minimum production recommendation and doubles the required memory. In this case, the container seemed fine to everyone who reviewed it. But dozens of concurrently transcoded WebRTC sessions? That’s a fundamentally high-load workload. The same container, which was a legitimate production shape, is now underpowered because it was designed for a different workload class. The customer needed to double their memory capacity at least, not to mention add more CPU resources.
Check two: stop paying for acceleration that is not there
Transcoder templates specify a decoding implementation, and the accelerated options assume hardware and drivers are present. When they are not, Wowza Streaming Engine falls back to the default software decoder, scaler, or encoder automatically.
Make the software path explicit instead of arriving at it by fallback. In Wowza Streaming Engine Manager, set the Decoding Preset implementation to Default. In the transcoder template XML:
<Decode>
<Video>
<Codec>H.264</Codec>
<Implementation>default</Implementation>
</Video>
</Decode>A template contains exactly one <Decode> block, so this is a single edit rather than a per-rendition one. The default built-in software codec is MainConcept for H.264 and H.265, and VPX for VP8 and VP9.
Check the encode and scale presets while the template is open. Both take the same implementation setting, and a partially configured template is the version of this problem that survives the longest, because the decoder looks correct and something downstream still requests hardware that does not exist.
Check three: match audio codecs to every delivery format served
This one produces no visible symptom at all, which is exactly why it’s dangerous. Codec requirements vary by protocol:
| Protocol Path | Audio Codecs Supported |
| RTMP ingest | AAC, AAC-LC, HE-AAC+ v1 and v2, MP3, Speex |
| WebRTC delivery | Opus (recommended), PCMU, PCMA |
| HLS delivery | AAC family, AC-3 and E-AC-3, MP3 |
RTMP ingest arrives as AAC, but WebRTC playback needs Opus. HLS needs AAC. Opus appears in neither the HLS nor the MPEG-DASH codec list. An application serving both WebRTC and HLS from one ingest, therefore, needs two audio renditions. Teams that build the WebRTC path first and add HLS later tend to discover this through a warning nobody reads.
For mono sources, set the default.scaleChannels decoding parameter to 2. This duplicates the mono stream across both channels, rather than leaving the resampler to guess. Configure the Opus rendition against the documented target of 48 kHz stereo, which is the maximum sample rate Opus supports:
<Audio>
<Codec>Opus</Codec>
<Bitrate>96000</Bitrate>
<Resample>
<Enable>true</Enable>
<SampleRate>48000</SampleRate>
<Channels>2</Channels>
</Resample>
</Audio>Check four: size resolution to the delivery target
Wowza recommends 720p at 30 fps for H.264 sources that are intended for WebRTC playback to ensure compatibility across clients. But more importantly, scaling costs CPU. CPU resources handle video scaling whenever hardware acceleration is unavailable. A 2304×1296 source scaled down for WebRTC delivery consumes meaningfully more decode and scale work per session than a 720p source does. One stream absorbs that. Fifty streams can’t.
Two related source settings sit in the same documentation and carry the same kind of hidden cost. The Baseline profile gives the broadest playback compatibility, and sources encoded with B-frames need those frames removed. The transcoder will do this if the encoder can’t. Fixing the source is cheaper than fixing it 50 times per second on the server.
Check five: tune the JVM and the memory allocator
Wowza Streaming Engine ships with Java Heap Size set to Development level by default, and production deployments need it changed to Production level. Servers hosting memory-intensive services or hitting out-of-memory errors should use a custom value between 3000 and 5000 MB with 4 GB or more of RAM, or 8000 MB with at least 16 GB. A container that comes straight out of a test environment, without additional memory, is a surefire way to take down the production server.
Garbage collection depends on your version. Generational ZGC is the default for Wowza Streaming Engine 4.8.28 and later and is fully supported on Java 21 and above. G1 is the default for 4.8.27 and earlier. Update the garbage collection settings if you’re rolling a 4.8.28 or later installation back to an earlier Java version so you don’t have startup errors. Wowza Streaming Engine supports Java 17 and Java 21, so a container that inherits a pinned JRE from its base image can lead to a mismatched deployment.
Then there is the allocator, and this is the setting most likely to be missing from a WebRTC deployment that keeps crashing after everything else looks right. Wowza documents that transcoding memory utilization can run higher than expected, potentially causing out-of-memory issues and server crashes. This happens most commonly when you frequently publish and unpublish WebRTC streams. Camera fleets reconnect constantly. Devices sleep, networks flap, sessions restart, and a steady-state load test that holds 50 streams open for an hour reproduces none of that churn. The fix on Linux is limiting MALLOC_ARENA_MAX, which defaults to eight times the processor core count. Wowza recommends starting at four times the core count and reducing further if memory pressure persists. On an eight-core server that means:
export MALLOC_ARENA_MAX=32Add the entry at the end of [install-dir]/bin/setenv.sh and restart. The value can go as low as 1, but performance may suffer.
Scale out rather than up
Without a GPU to offload to, transcoding capacity is bounded by cores, and adding cores to one instance runs into diminishing returns faster than adding instances does. Several moderately sized containers generally beat one oversized one.
Wowza Streaming Engine 4.11 introduced WHIP and WHEP signaling. Because it’s HTTP-based, it routes through existing load balancers, ingress controllers, and API gateways without sticky sessions or connection affinity concerns. Horizontal scaling stops hinging on WebSocket-aware routing.
Validate with a soak test, not a smoke test
Every failure described here is concurrency-dependent or churn-dependent. Neither shows up in a five-minute test.
Load testing an untuned server measures the misconfiguration rather than the capacity. Wowza’s own load testing guidance starts from the assumption that the instance is already tuned, pointing back to the performance tuning documentation as a prerequisite.
Run the test long enough to include reconnection churn, and monitor memory across the window rather than at the end of it. A week of monitored uptime under representative load is a reasonable bar before finalizing a configuration. Finally, test on a current release in staging. Performance and stability fixes ship regularly, and a scaling issue that survives careful tuning sometimes is already addressed upstream.
The checklist
- Confirm the instance matches the workload’s hardware tier.
- Set the decoder, scaler, and encoder implementations explicitly rather than relying on fallback.
- Give every delivery protocol an audio rendition it can actually package.
- Size source resolution to the delivery target, not to the camera’s maximum.
- Set heap for production, and limit MALLOC_ARENA_MAX on Linux WebRTC workloads.
- Soak test with churn before calling it done.
None of these are difficult. All of them are cheap to skip. But the bill arrives when you ramp up concurrency.
Frequently asked questions
Why does Wowza Streaming Engine use high CPU when transcoding without a GPU?
Software transcoding is CPU-bound by design, and CPU also handles video scaling when hardware acceleration is unavailable. A transcoder template requesting an accelerated implementation the server cannot provide, which falls back to software on every session, and a source resolution larger than the delivery target requires both can turn this into a worse issue than it needs to be.
How do I force software decoding in the Wowza Streaming Engine transcoder?
Set the decoding implementation to Default in the Decoding Preset in Wowza Streaming Engine Manager, or set <Implementation> to default inside the template’s <Decode> block. The built-in software decoder is MainConcept for H.264 and H.265 and VPX for VP8 and VP9. Apply the same setting to the scale and encode presets so no part of the pipeline requests hardware that is not present.
What audio codec does WebRTC playback require, and can the same rendition serve HLS?
WebRTC delivery from Wowza Streaming Engine supports Opus, which is recommended, along with PCMU and PCMA. HLS delivery through the Cupertino packager supports the AAC family, AC-3 and E-AC-3, and MP3. Opus is not among the supported HLS or MPEG-DASH audio codecs, so an application delivering both WebRTC and HLS needs separate audio renditions.
What resolution should I use for WebRTC playback?
Wowza documentation recommends 720p at 30 fps for H.264 sources for the broadest playback compatibility. At high-concurrency, there is an additional benefit, because scaling a much larger source consumes CPU per session that multiplies across every concurrent stream.
What hardware does Wowza Streaming Engine need for high-load transcoding?
The published high-load production recommendation is six CPU cores at 3.00 GHz or better, 16 to 32 GB of RAM, SATA SSD storage, and a 10 Gbps local network interface. The minimum production recommendation is a single quad core at 3.00 GHz or better with 4 GB of RAM, which suits lighter workloads than concurrent transcoding.
How many concurrent transcoded streams can one Wowza Streaming Engine instance handle?
There is no single number, because per-session cost depends on source resolution, codec path, whether transcoding or transmuxing is required, reconnection churn, and available cores. A useful way to approach it is measuring per-session cost on a tuned instance under representative load, then dividing available headroom by that figure rather than starting from a target stream count.
