-
Notifications
You must be signed in to change notification settings - Fork 172
Add audio encoder reconfiguration #1362
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -66,7 +66,6 @@ export class Encoder { | |
| this.groupDuration = props?.groupDuration ?? (100 as Time.Milli); // Default is a group every 100ms | ||
|
|
||
| this.#signals.run(this.#runSource.bind(this)); | ||
| this.#signals.run(this.#runConfig.bind(this)); | ||
| this.#signals.run(this.#runGain.bind(this)); | ||
| this.#signals.run(this.#runCatalog.bind(this)); | ||
| } | ||
|
|
@@ -102,14 +101,29 @@ export class Encoder { | |
| await context.audioWorklet.addModule(CaptureWorklet); | ||
| if (context.state === "closed") return; | ||
|
|
||
| const channelCount = settings.channelCount ?? root.channelCount; | ||
| const worklet = new AudioWorkletNode(context, "capture", { | ||
| numberOfInputs: 1, | ||
| numberOfOutputs: 0, | ||
| channelCount: settings.channelCount ?? root.channelCount, | ||
| channelCount, | ||
| }); | ||
|
|
||
| effect.set(this.#worklet, worklet); | ||
|
|
||
| // The information about channels count can be unreliable on different platforms (Apple's safari). | ||
| // Try to get the first audio frame and only then create the configuration. | ||
| worklet.port.onmessage = ({ data }: { data: Capture.AudioFrame }) => { | ||
| const channelCount = data.channels.length; | ||
| if (!channelCount) return; | ||
|
|
||
| this.#config.set(this.#createConfig(worklet, channelCount)); | ||
| worklet.port.onmessage = null; | ||
| }; | ||
| effect.cleanup(() => { | ||
| worklet.port.onmessage = null; | ||
| this.#config.set(undefined); | ||
| }); | ||
|
Comment on lines
+122
to
+125
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Clear the derived catalog when the config is reset. This cleanup drops 🩹 Suggested fix`#runCatalog`(effect: Effect): void {
const config = effect.get(this.#config);
- if (!config) return;
+ if (!config) {
+ effect.set(this.#catalog, undefined);
+ return;
+ }
const catalog: Catalog.Audio = {
renditions: { [Encoder.TRACK]: config },
};🤖 Prompt for AI Agents
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you make sure #runCatalog uses this #config signal?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
`#runCatalog`(effect: Effect): void {
const config = effect.get(this.#config);
if (!config) {
effect.set(this.#catalog, undefined);
return;
}
const catalog: Catalog.Audio = {
renditions: { [Encoder.TRACK]: config },
};
effect.set(this.#catalog, catalog);
}Now when 🧠 Learnings used |
||
|
|
||
| gain.connect(worklet); | ||
| effect.cleanup(() => worklet.disconnect()); | ||
|
|
||
|
|
@@ -118,23 +132,17 @@ export class Encoder { | |
| }); | ||
| } | ||
|
|
||
| #runConfig(effect: Effect): void { | ||
| const values = effect.getAll([this.source, this.#worklet]); | ||
| if (!values) return; | ||
| const [_source, worklet] = values; | ||
|
|
||
| const config = { | ||
| #createConfig(worklet: AudioWorkletNode, channelCount: number): Catalog.AudioConfig { | ||
| return { | ||
| codec: "opus", | ||
| sampleRate: Catalog.u53(worklet.context.sampleRate), | ||
| numberOfChannels: Catalog.u53(worklet.channelCount), | ||
| bitrate: Catalog.u53(worklet.channelCount * 32_000), | ||
| numberOfChannels: Catalog.u53(channelCount), | ||
| bitrate: Catalog.u53(channelCount * 32_000), | ||
| container: { kind: "legacy" } as const, | ||
| // TODO parse the actual frame duration instead of assuming 20ms. | ||
| // Opus supports 2.5–60ms but 20ms is the real-time default. | ||
| jitter: Catalog.u53(20), | ||
| }; | ||
|
|
||
| effect.set(this.#config, config); | ||
| } | ||
|
|
||
| #runGain(effect: Effect): void { | ||
|
|
@@ -153,9 +161,9 @@ export class Encoder { | |
| } | ||
|
|
||
| serve(track: Moq.Track, effect: Effect): void { | ||
| const values = effect.getAll([this.enabled, this.#worklet, this.#config]); | ||
| const values = effect.getAll([this.enabled, this.#worklet]); | ||
| if (!values) return; | ||
| const [_, worklet, config] = values; | ||
| const [_, worklet] = values; | ||
|
|
||
| effect.set(this.active, true, false); | ||
|
|
||
|
|
@@ -190,11 +198,26 @@ export class Encoder { | |
| }); | ||
| effect.cleanup(() => encoder.close()); | ||
|
|
||
| console.debug("encoding audio", config); | ||
| encoder.configure(config); | ||
| let config = this.#config.peek(); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Peek is racey. If we really need the config, we should break early if |
||
| if (config) { | ||
| console.debug("encoding audio", config); | ||
| encoder.configure(config); | ||
| } | ||
|
|
||
| worklet.port.onmessage = ({ data }: { data: Capture.AudioFrame }) => { | ||
| const channels = data.channels.slice(0, worklet.channelCount); | ||
| const channelCount = data.channels.length; | ||
| if (!channelCount) return; | ||
|
|
||
| if (!config || channelCount !== config.numberOfChannels) { | ||
| config = this.#createConfig(worklet, channelCount); | ||
| this.#config.set(config); | ||
| lastKeyframe = undefined; | ||
|
|
||
| console.debug("encoding audio", config); | ||
| encoder.configure(config); | ||
| } | ||
|
|
||
| const channels = data.channels; | ||
| const joinedLength = channels.reduce((a, b) => a + b.length, 0); | ||
| const joined = new Float32Array(joinedLength); | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a little spooky setting
onmessagein two places.You shuld use
It'll unregister the event on cleanup too.