Skip to content

Commit 6d9cd85

Browse files
committed
Add hidden transmuxing performance measurments
1 parent 09266f1 commit 6d9cd85

4 files changed

Lines changed: 207 additions & 6 deletions

File tree

src/ts-main/api.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -350,14 +350,24 @@ export default class WaspHlsPlayer extends EventEmitter<WaspHlsPlayerEvents> {
350350
if (this.__worker__ === null) {
351351
throw new Error("The Player is not initialized or is disposed.");
352352
}
353-
for (const [key, value] of Object.entries(overwrite)) {
353+
for (const [key, value] of Object.entries(
354+
overwrite as Record<string, unknown>,
355+
)) {
354356
if (value !== undefined) {
355-
this.__config__[key as keyof WaspHlsPlayerConfig] = value;
357+
if (key in this.__config__) {
358+
const typedKey = key as keyof WaspHlsPlayerConfig;
359+
(
360+
this.__config__ as Record<
361+
keyof WaspHlsPlayerConfig,
362+
WaspHlsPlayerConfig[keyof WaspHlsPlayerConfig]
363+
>
364+
)[typedKey] = value as WaspHlsPlayerConfig[keyof WaspHlsPlayerConfig];
365+
}
356366
}
357367
}
358368
postMessageToWorker(this.__worker__, {
359369
type: MainMessageType.UpdateConfig,
360-
value: this.__config__,
370+
value: overwrite,
361371
});
362372
}
363373

src/ts-worker/bindings.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ import {
5555
getTrackFragmentDecodeTime,
5656
} from "./isobmff-utils.js";
5757
import postMessageToMain from "./postMessage.js";
58+
import { recordTransmuxProfile } from "./transmux-profiling.js";
5859
import { getTransmuxedType, createTransmuxer } from "./transmux.js";
5960
import { formatErrMessage, shouldTransmux } from "./utils.js";
6061

@@ -783,11 +784,12 @@ export function addSourceBuffer(
783784
if (shouldTransmux(typ)) {
784785
mimeType = getTransmuxedType(typ, mediaType);
785786
}
787+
const transmuxer = mimeType === typ ? null : createTransmuxer();
786788
const sourceBufferId = nextSourceBufferId;
787789
sourceBuffers.push({
788790
lastInitTimescale: undefined,
789791
id: sourceBufferId,
790-
transmuxer: mimeType === typ ? null : createTransmuxer(),
792+
transmuxer,
791793
sourceBuffer: null,
792794
mediaType,
793795
});
@@ -830,13 +832,14 @@ export function addSourceBuffer(
830832
mimeType = getTransmuxedType(typ, mediaType);
831833
}
832834
const sourceBuffer = mediaSource.addSourceBuffer(mimeType);
835+
const transmuxer = mimeType === typ ? null : createTransmuxer();
833836
const sourceBufferId = nextSourceBufferId;
834837
const queuedSourceBuffer = new QueuedSourceBuffer(sourceBuffer);
835838
sourceBuffers.push({
836839
lastInitTimescale: undefined,
837840
id: sourceBufferId,
838841
sourceBuffer: queuedSourceBuffer,
839-
transmuxer: mimeType === typ ? null : createTransmuxer(),
842+
transmuxer,
840843
mediaType,
841844
});
842845
contentInfo.mediaSourceObj.nextSourceBufferId++;
@@ -912,9 +915,18 @@ export function appendBuffer(
912915
const sourceBufferObj = mediaSourceObj.sourceBuffers[sourceBufferObjIdx];
913916
if (sourceBufferObj.transmuxer !== null) {
914917
try {
918+
const inputBytes = segment.byteLength;
919+
const startTime = timerFn();
915920
const transmuxedData =
916921
sourceBufferObj.transmuxer.transmuxSegment(segment);
922+
const durationMs = timerFn() - startTime;
917923
if (transmuxedData !== null) {
924+
recordTransmuxProfile({
925+
durationMs,
926+
inputBytes,
927+
outputBytes: transmuxedData.byteLength,
928+
mediaType: sourceBufferObj.mediaType,
929+
});
918930
segment = transmuxedData;
919931
} else {
920932
return AppendBufferResult.error(

src/ts-worker/globals.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ import type {
77
} from "../ts-common/types.ts";
88
import type Transmuxer from "../ts-transmux/index.ts";
99
import { Dispatcher, type InitOutput, type MediaType } from "../wasm/index.js";
10+
import {
11+
type HiddenTransmuxProfilingConfig,
12+
resetTransmuxProfiling,
13+
updateTransmuxProfilingConfig,
14+
} from "./transmux-profiling.ts";
1015

1116
export interface WorkerInitializationOptions {
1217
hasMseInWorker: boolean;
@@ -39,6 +44,7 @@ class PlayerInstance {
3944
) {
4045
const dispatcher = new Dispatcher(opts.initialBandwidth);
4146
updateDispatcherConfig(dispatcher, config);
47+
resetTransmuxProfiling();
4248
this._instanceInfo = {
4349
wasm,
4450
dispatcher,
@@ -52,6 +58,7 @@ class PlayerInstance {
5258
this._instanceInfo?.dispatcher.free();
5359
jsMemoryResources.freeEverything();
5460
requestsStore.freeEverything();
61+
resetTransmuxProfiling();
5562
}
5663

5764
public changeContent(content: ContentInfo) {
@@ -63,6 +70,7 @@ class PlayerInstance {
6370
}
6471
jsMemoryResources.freeEverything();
6572
requestsStore.freeEverything();
73+
resetTransmuxProfiling();
6674
this._instanceInfo.content = content;
6775
}
6876

@@ -159,8 +167,9 @@ const I32_MAX_VALUE = 2147483647;
159167

160168
export function updateDispatcherConfig(
161169
dispatcher: Dispatcher,
162-
config: Partial<WaspHlsPlayerConfig>,
170+
config: Partial<WaspHlsPlayerConfig> & HiddenTransmuxProfilingConfig,
163171
): void {
172+
updateTransmuxProfilingConfig(config);
164173
if (config.bufferGoal !== undefined) {
165174
dispatcher.set_buffer_goal(config.bufferGoal);
166175
}
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
import logger, { LoggerLevel } from "../ts-common/logger";
2+
import { MediaType } from "../wasm/wasp_hls";
3+
4+
export interface HiddenTransmuxProfilingConfig {
5+
transmuxProfiling?: boolean;
6+
transmuxProfilingSampleSize?: number;
7+
transmuxProfilingSlowThreshold?: number;
8+
}
9+
10+
interface TransmuxProfilingConfig {
11+
enabled: boolean;
12+
sampleSize: number;
13+
slowThreshold: number;
14+
}
15+
16+
interface TransmuxProfilingTotals {
17+
count: number;
18+
totalDurationMs: number;
19+
maxDurationMs: number;
20+
totalInputBytes: number;
21+
totalOutputBytes: number;
22+
}
23+
24+
const DEFAULT_CONFIG: TransmuxProfilingConfig = {
25+
enabled: false,
26+
sampleSize: 20,
27+
slowThreshold: 12,
28+
};
29+
30+
const currentConfig: TransmuxProfilingConfig = { ...DEFAULT_CONFIG };
31+
let totals = createEmptyTotals();
32+
33+
function createEmptyTotals(): TransmuxProfilingTotals {
34+
return {
35+
count: 0,
36+
totalDurationMs: 0,
37+
maxDurationMs: 0,
38+
totalInputBytes: 0,
39+
totalOutputBytes: 0,
40+
};
41+
}
42+
43+
function clampPositiveInteger(value: number, fallback: number): number {
44+
if (!Number.isFinite(value) || value < 1) {
45+
return fallback;
46+
}
47+
return Math.floor(value);
48+
}
49+
50+
function clampNonNegativeNumber(value: number, fallback: number): number {
51+
if (!Number.isFinite(value) || value < 0) {
52+
return fallback;
53+
}
54+
return value;
55+
}
56+
57+
function formatBytesPerMs(bytes: number, durationMs: number): string {
58+
if (durationMs <= 0) {
59+
return "n/a";
60+
}
61+
return (bytes / durationMs).toFixed(1);
62+
}
63+
64+
function mediaTypeToString(mediaType: MediaType): string {
65+
switch (mediaType) {
66+
case MediaType.Audio:
67+
return "audio";
68+
case MediaType.Video:
69+
return "video";
70+
default:
71+
return "unknown";
72+
}
73+
}
74+
75+
export function updateTransmuxProfilingConfig(
76+
config: HiddenTransmuxProfilingConfig,
77+
): void {
78+
const previousEnabled = currentConfig.enabled;
79+
if (config.transmuxProfiling !== undefined) {
80+
currentConfig.enabled = config.transmuxProfiling;
81+
}
82+
if (config.transmuxProfilingSampleSize !== undefined) {
83+
currentConfig.sampleSize = clampPositiveInteger(
84+
config.transmuxProfilingSampleSize,
85+
DEFAULT_CONFIG.sampleSize,
86+
);
87+
}
88+
if (config.transmuxProfilingSlowThreshold !== undefined) {
89+
currentConfig.slowThreshold = clampNonNegativeNumber(
90+
config.transmuxProfilingSlowThreshold,
91+
DEFAULT_CONFIG.slowThreshold,
92+
);
93+
}
94+
95+
if (currentConfig.enabled !== previousEnabled || !currentConfig.enabled) {
96+
resetTransmuxProfiling();
97+
}
98+
99+
if (
100+
currentConfig.enabled &&
101+
!previousEnabled &&
102+
logger.hasLevel(LoggerLevel.Info)
103+
) {
104+
logger.info(
105+
"[transmux-profile] enabled sampleSize=",
106+
currentConfig.sampleSize,
107+
"slowThresholdMs=",
108+
currentConfig.slowThreshold,
109+
);
110+
}
111+
}
112+
113+
export function resetTransmuxProfiling(): void {
114+
totals = createEmptyTotals();
115+
}
116+
117+
export function recordTransmuxProfile(args: {
118+
durationMs: number;
119+
inputBytes: number;
120+
outputBytes: number;
121+
mediaType: MediaType;
122+
}): void {
123+
if (!currentConfig.enabled) {
124+
return;
125+
}
126+
127+
totals.count += 1;
128+
totals.totalDurationMs += args.durationMs;
129+
totals.maxDurationMs = Math.max(totals.maxDurationMs, args.durationMs);
130+
totals.totalInputBytes += args.inputBytes;
131+
totals.totalOutputBytes += args.outputBytes;
132+
133+
if (
134+
args.durationMs >= currentConfig.slowThreshold &&
135+
logger.hasLevel(LoggerLevel.Info)
136+
) {
137+
logger.info(
138+
"[transmux-profile] slow-segment mediaType=",
139+
mediaTypeToString(args.mediaType),
140+
"durationMs=",
141+
args.durationMs.toFixed(2),
142+
"inputBytes=",
143+
args.inputBytes,
144+
"outputBytes=",
145+
args.outputBytes,
146+
);
147+
}
148+
149+
if (
150+
totals.count % currentConfig.sampleSize === 0 &&
151+
logger.hasLevel(LoggerLevel.Info)
152+
) {
153+
logger.info(
154+
"[transmux-profile] summary segments=",
155+
totals.count,
156+
"avgMs=",
157+
(totals.totalDurationMs / totals.count).toFixed(2),
158+
"maxMs=",
159+
totals.maxDurationMs.toFixed(2),
160+
"avgInputBytes=",
161+
Math.round(totals.totalInputBytes / totals.count),
162+
"avgOutputBytes=",
163+
Math.round(totals.totalOutputBytes / totals.count),
164+
"avgInputBytesPerMs=",
165+
formatBytesPerMs(totals.totalInputBytes, totals.totalDurationMs),
166+
"avgOutputBytesPerMs=",
167+
formatBytesPerMs(totals.totalOutputBytes, totals.totalDurationMs),
168+
);
169+
}
170+
}

0 commit comments

Comments
 (0)