Summary
On Samsung devices with Samsung's own SoC, publishing the default VP8 video
track results in zero frames reaching other participants. The call
connects, the camera opens, the track is published and stays published, and
the encoder is allocated — but it never starts, and no output buffer is ever
produced. Nothing fails, nothing throws: the publisher simply cannot be seen.
The Qualcomm build of the same phone model works.
Environment
io.livekit:livekit-android 2.26.1 (webrtc-sdk android-prefixed 144.7559.05)
- Devices: Galaxy S24+ (
e2s) and Galaxy S24 (e1s), both Exynos, Android 16 (API 36)
- Counter-example: Galaxy S26+ (
m2q, Qualcomm), Android 16 — works
- All three are physical devices in Firebase Test Lab, same APK, same test, same room
What we see
Publishing succeeds:
LK joined room <redacted>
LK setCameraEnabled(true) ok, publication TR_VC…
The encoder is then allocated and configured, and stops there:
I CCodec : allocate(c2.exynos.vp8.encoder)
I CCodec : Created component [c2.exynos.vp8.encoder]
I CCodec : [c2.exynos.vp8.encoder] state->set(ALLOCATED)
I CCodec : [c2.exynos.vp8.encoder] config->initialize() : 0
D CCodec : [c2.exynos.vp8.encoder] buffers are bound to CCodec for this session
There is no state->set(STARTING), no state->set(RUNNING), and no
CCodecBufferChannel input/output buffer line — compare an ordinary codec in
the same logcat, which goes ALLOCATED → STARTING → RUNNING with buffers.
The remote peer (a browser) receives 0 frames for the entire call.
On the receiving side we have measured the mirror image of this on a Galaxy
S26+ with Exynos (SM-S947B): subscribed VP8, packetsReceived 1483,
framesReceived 221, framesDecoded 0, decoderImplementation null.
The same chip family, both directions.
Why we think WebRTC picks that encoder at all
HardwareVideoEncoderFactory.isHardwareSupportedInCurrentSdkVp8() still only
accepts the OMX names:
return (name.startsWith(QCOM_PREFIX) && SDK_INT >= KITKAT)
|| (name.startsWith(EXYNOS_PREFIX) && SDK_INT >= M)
|| (name.startsWith(INTEL_PREFIX) && SDK_INT >= LOLLIPOP && enableIntelVp8Encoder);
No c2. prefix, and no MediaCodecInfo.isHardwareAccelerated() — unchanged
in upstream HEAD, while Android moved to Codec2 in 12. Our hypothesis is that
Samsung still exposes an OMX.Exynos.… alias for the Codec2 component, so a
check written for the OMX encoders of 2016 ends up selecting a codec it was
never validated against. We could not confirm the alias from logcat, so this
part is inference — the behaviour above is not.
Workaround
CustomVideoEncoderFactory already has exactly the right escape hatch, and
its default already distrusts hardware VP9:
private var forceSWCodecs: List<String> = listOf("VP9")
Passing listOf("VP8", "VP9") through LiveKitOverrides(videoEncoderFactory = …)
resolves it for us. (Note the factory has to be built lazily: its constructor
reaches into libwebrtc, which is only loaded while the room is built.)
Suggestion
Either add VP8 to that default on affected chipsets, or gate hardware VP8 on
something that reflects the Codec2 era (isHardwareAccelerated(), or a name
check that does not rely on OMX prefixes). At the very least it may be worth
documenting setForceSWCodecList as the remedy for "publisher is connected
but nobody can see them" on Samsung Exynos.
Happy to run any patch or extra logging on these exact devices — we have them
reproducible in Test Lab.
Summary
On Samsung devices with Samsung's own SoC, publishing the default VP8 video
track results in zero frames reaching other participants. The call
connects, the camera opens, the track is published and stays published, and
the encoder is allocated — but it never starts, and no output buffer is ever
produced. Nothing fails, nothing throws: the publisher simply cannot be seen.
The Qualcomm build of the same phone model works.
Environment
io.livekit:livekit-android2.26.1 (webrtc-sdkandroid-prefixed144.7559.05)e2s) and Galaxy S24 (e1s), both Exynos, Android 16 (API 36)m2q, Qualcomm), Android 16 — worksWhat we see
Publishing succeeds:
The encoder is then allocated and configured, and stops there:
There is no
state->set(STARTING), nostate->set(RUNNING), and noCCodecBufferChannelinput/output buffer line — compare an ordinary codec inthe same logcat, which goes
ALLOCATED → STARTING → RUNNINGwith buffers.The remote peer (a browser) receives 0 frames for the entire call.
On the receiving side we have measured the mirror image of this on a Galaxy
S26+ with Exynos (SM-S947B): subscribed VP8,
packetsReceived1483,framesReceived221,framesDecoded0,decoderImplementationnull.The same chip family, both directions.
Why we think WebRTC picks that encoder at all
HardwareVideoEncoderFactory.isHardwareSupportedInCurrentSdkVp8()still onlyaccepts the OMX names:
No
c2.prefix, and noMediaCodecInfo.isHardwareAccelerated()— unchangedin upstream HEAD, while Android moved to Codec2 in 12. Our hypothesis is that
Samsung still exposes an
OMX.Exynos.…alias for the Codec2 component, so acheck written for the OMX encoders of 2016 ends up selecting a codec it was
never validated against. We could not confirm the alias from logcat, so this
part is inference — the behaviour above is not.
Workaround
CustomVideoEncoderFactoryalready has exactly the right escape hatch, andits default already distrusts hardware VP9:
Passing
listOf("VP8", "VP9")throughLiveKitOverrides(videoEncoderFactory = …)resolves it for us. (Note the factory has to be built lazily: its constructor
reaches into libwebrtc, which is only loaded while the room is built.)
Suggestion
Either add VP8 to that default on affected chipsets, or gate hardware VP8 on
something that reflects the Codec2 era (
isHardwareAccelerated(), or a namecheck that does not rely on OMX prefixes). At the very least it may be worth
documenting
setForceSWCodecListas the remedy for "publisher is connectedbut nobody can see them" on Samsung Exynos.
Happy to run any patch or extra logging on these exact devices — we have them
reproducible in Test Lab.