diff --git a/website/package.json b/website/package.json index bdfb4fa61..4d3ff5397 100644 --- a/website/package.json +++ b/website/package.json @@ -22,7 +22,7 @@ "@docusaurus/preset-classic": "2.4.1", "@foxglove/eslint-plugin": "1.0.1", "@foxglove/rostime": "1.1.2", - "@foxglove/schemas": "1.6.2", + "@foxglove/schemas": "github:foxglove/schemas#fbb9ccfd8d31ac6c3aa18ce7b9959eb0be9bed7c", "@foxglove/tsconfig": "2.0.0", "@mcap/core": "workspace:*", "@mdx-js/react": "1.6.22", diff --git a/website/src/components/McapRecordingDemo/Recorder.ts b/website/src/components/McapRecordingDemo/Recorder.ts index 058ae603c..c72217239 100644 --- a/website/src/components/McapRecordingDemo/Recorder.ts +++ b/website/src/components/McapRecordingDemo/Recorder.ts @@ -1,8 +1,11 @@ -import { Time, fromNanoSec } from "@foxglove/rostime"; +// cspell:word millis + +import { Time, fromMillis, fromNanoSec } from "@foxglove/rostime"; import { PoseInFrame, CompressedImage, CompressedVideo, + CompressedAudio, } from "@foxglove/schemas"; import { foxgloveMessageSchemas } from "@foxglove/schemas/internal"; import zstd from "@foxglove/wasm-zstd"; @@ -11,6 +14,7 @@ import { EventEmitter } from "eventemitter3"; import Queue from "promise-queue"; import { ProtobufChannelInfo, addProtobufChannel } from "./addProtobufChannel"; +import { CompressedAudioData } from "./audioCapture"; import { CompressedVideoFrame } from "./videoCapture"; export type ProtobufObject = { @@ -60,6 +64,8 @@ export class Recorder extends EventEmitter { #vp9ChannelSeq = 0; #av1Channel?: ProtobufChannelInfo; #av1ChannelSeq = 0; + #opusChannel?: ProtobufChannelInfo; + #opusChannelSeq = 0; #blobParts: Uint8Array[] = []; bytesWritten = 0n; @@ -112,6 +118,8 @@ export class Recorder extends EventEmitter { this.#h265ChannelSeq = 0; this.#av1Channel = undefined; this.#av1ChannelSeq = 0; + this.#opusChannel = undefined; + this.#opusChannelSeq = 0; } #time(): bigint { @@ -277,6 +285,47 @@ export class Recorder extends EventEmitter { }); } + async addAudioData(data: CompressedAudioData): Promise { + void this.#queue.add(async () => { + if (!this.#writer) { + return; + } + let channel: ProtobufChannelInfo; + let sequence: number; + switch (data.format) { + case "opus": + channel = this.#opusChannel ??= await addProtobufChannel( + this.#writer, + "microphone_opus", + foxgloveMessageSchemas.CompressedAudio, + ); + sequence = this.#opusChannelSeq++; + break; + } + const { id, rootType } = channel; + const msg: ProtobufObject = { + timestamp: toProtobufTime(fromMillis(data.timestamp)), + data: data.data, + format: data.format, + type: data.type, + sample_rate: data.sampleRate, + number_of_channels: data.numberOfChannels, + }; + const encodedMsg = rootType.encode(msg).finish(); + data.release(); + const now = this.#time(); + await this.#writer.addMessage({ + sequence, + channelId: id, + logTime: now, + publishTime: now, + data: encodedMsg, + }); + this.messageCount++; + this.#emit(); + }); + } + async closeAndRestart(): Promise { return await this.#queue.add(async () => { await this.#writer?.end(); diff --git a/website/src/components/McapRecordingDemo/audioCapture.ts b/website/src/components/McapRecordingDemo/audioCapture.ts new file mode 100644 index 000000000..e53ca5164 --- /dev/null +++ b/website/src/components/McapRecordingDemo/audioCapture.ts @@ -0,0 +1,13 @@ +type CompressedAudioFormat = "opus"; +type CompressedAudioType = "key" | "delta"; +export type CompressedAudioData = { + format: CompressedAudioFormat; + type: CompressedAudioType; + timestamp: number; + data: Uint8Array; + sampleRate: number; + numberOfChannels: number; + + /** Call this function to release the buffer so it can be reused for new frames */ + release: () => void; +}; diff --git a/yarn.lock b/yarn.lock index 31ed8b2b4..51e27d0c9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3056,6 +3056,16 @@ __metadata: languageName: node linkType: hard +"@foxglove/schemas@github:foxglove/schemas#fbb9ccfd8d31ac6c3aa18ce7b9959eb0be9bed7c": + version: 1.6.7 + resolution: "@foxglove/schemas@https://github.com/foxglove/schemas.git#commit=fbb9ccfd8d31ac6c3aa18ce7b9959eb0be9bed7c" + dependencies: + "@foxglove/rosmsg-msgs-common": "npm:^3.0.0" + tslib: "npm:^2.5.0" + checksum: 10c0/5153e9973b5d16ae92bdd6d4c1837518adc425f78a33985cf3f364027895c792d136aa6ac9f7233d44670b4be72bd4c3946aec3c7099514054af88eaf8ffde1b + languageName: node + linkType: hard + "@foxglove/schemas@npm:1.3.0": version: 1.3.0 resolution: "@foxglove/schemas@npm:1.3.0" @@ -3066,7 +3076,7 @@ __metadata: languageName: node linkType: hard -"@foxglove/schemas@npm:1.6.2, @foxglove/schemas@npm:^1.0.0": +"@foxglove/schemas@npm:^1.0.0": version: 1.6.2 resolution: "@foxglove/schemas@npm:1.6.2" dependencies: @@ -15610,7 +15620,7 @@ __metadata: "@docusaurus/preset-classic": "npm:2.4.1" "@foxglove/eslint-plugin": "npm:1.0.1" "@foxglove/rostime": "npm:1.1.2" - "@foxglove/schemas": "npm:1.6.2" + "@foxglove/schemas": "github:foxglove/schemas#fbb9ccfd8d31ac6c3aa18ce7b9959eb0be9bed7c" "@foxglove/tsconfig": "npm:2.0.0" "@mcap/core": "workspace:*" "@mdx-js/react": "npm:1.6.22"