perf(call): H.264 + contentHint + 30fps default for screen-share
The real cost in the "frame by frame" stutter wasn't the custom capture path — it was LiveKit re-encoding via VP9 software with L3T3_KEY SVC (three spatial × three temporal layers, all CPU). Switching the per-publish codec to H.264 lets Chromium's hardware encoder take over on Windows and sidesteps the SVC mode entirely (H.264 has no SVC). Also pushes `contentHint = 'detail'` on the track — setScreenShareEnabled does this internally, the manual publishTrack paths had been missing it, which changes how the encoder allocates its frame budget for static UI content. Auto preset default framerate 60 → 30. 60fps desktop share burns three full-res encodes per frame at sizes up to 4K; 30 is what getDisplayMedia practically delivers anyway. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1310,9 +1310,17 @@ export function CallProvider({ children }: { children: ReactNode }) {
|
||||
stream.getTracks().forEach((t) => t.stop());
|
||||
throw new Error('no video track from chromeMediaSource');
|
||||
}
|
||||
// H.264 lets Chromium's hardware encoder take over on Windows
|
||||
// (Intel QuickSync / NVENC / AMD VCE) — VP9 is software-only in
|
||||
// Chromium's WebRTC path and collapses under the L3T3_KEY
|
||||
// scalability mode we set globally for camera. `contentHint` is
|
||||
// what setScreenShareEnabled sets internally; pushing it by hand
|
||||
// here tells the encoder to weight sharpness over smoothness,
|
||||
// which is right for UI/text-heavy screen content.
|
||||
videoMst.contentHint = 'detail';
|
||||
const videoPub = await lp.publishTrack(videoMst, {
|
||||
source: LkTrack.Source.ScreenShare,
|
||||
videoCodec: 'vp9',
|
||||
videoCodec: 'h264',
|
||||
});
|
||||
const audioRes = await startWasapiAudio(LkTrack);
|
||||
videoMst.addEventListener('ended', () => {
|
||||
@@ -1358,9 +1366,10 @@ export function CallProvider({ children }: { children: ReactNode }) {
|
||||
nativeCaptureRef.current = null;
|
||||
throw new NativeCaptureUnavailable('canvas stream produced no video track');
|
||||
}
|
||||
videoMst.contentHint = 'detail';
|
||||
const videoPub = await lp.publishTrack(videoMst, {
|
||||
source: LkTrack.Source.ScreenShare,
|
||||
videoCodec: 'vp9',
|
||||
videoCodec: 'h264',
|
||||
});
|
||||
const audioRes = await startWasapiAudio(LkTrack);
|
||||
videoMst.addEventListener('ended', () => {
|
||||
|
||||
@@ -46,7 +46,7 @@ export interface PresetParams {
|
||||
}
|
||||
|
||||
const PRESET_PARAMS: Record<ScreenSharePreset, PresetParams> = {
|
||||
auto: { dims: null, framerate: 60, bitrateKbps: 8000, label: 'Auto (Original)' },
|
||||
auto: { dims: null, framerate: 30, bitrateKbps: 8000, label: 'Auto (Original)' },
|
||||
'720p30': {
|
||||
dims: { width: 1280, height: 720 },
|
||||
framerate: 30,
|
||||
|
||||
Reference in New Issue
Block a user