Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit 269d162

Browse files
authored
Implement more robust broadcast chunk header detection (#10006)
1 parent e9d7232 commit 269d162

File tree

2 files changed

+43
-13
lines changed

2 files changed

+43
-13
lines changed

src/voice-broadcast/audio/VoiceBroadcastRecorder.ts

+29-9
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17+
import { isEqual } from "lodash";
1718
import { Optional } from "matrix-events-sdk";
19+
import { logger } from "matrix-js-sdk/src/logger";
1820
import { TypedEventEmitter } from "matrix-js-sdk/src/models/typed-event-emitter";
1921

2022
import { getChunkLength } from "..";
@@ -38,6 +40,12 @@ export interface ChunkRecordedPayload {
3840
length: number;
3941
}
4042

43+
// char sequence of "OpusHead"
44+
const OpusHead = [79, 112, 117, 115, 72, 101, 97, 100];
45+
46+
// char sequence of "OpusTags"
47+
const OpusTags = [79, 112, 117, 115, 84, 97, 103, 115];
48+
4149
/**
4250
* This class provides the function to seamlessly record fixed length chunks.
4351
* Subscribe with on(VoiceBroadcastRecordingEvents.ChunkRecorded, (payload: ChunkRecordedPayload) => {})
@@ -47,11 +55,11 @@ export class VoiceBroadcastRecorder
4755
extends TypedEventEmitter<VoiceBroadcastRecorderEvent, EventMap>
4856
implements IDestroyable
4957
{
50-
private headers = new Uint8Array(0);
58+
private opusHead?: Uint8Array;
59+
private opusTags?: Uint8Array;
5160
private chunkBuffer = new Uint8Array(0);
5261
// position of the previous chunk in seconds
5362
private previousChunkEndTimePosition = 0;
54-
private pagesFromRecorderCount = 0;
5563
// current chunk length in seconds
5664
private currentChunkLength = 0;
5765

@@ -73,7 +81,7 @@ export class VoiceBroadcastRecorder
7381
public async stop(): Promise<Optional<ChunkRecordedPayload>> {
7482
try {
7583
await this.voiceRecording.stop();
76-
} catch {
84+
} catch (e) {
7785
// Ignore if the recording raises any error.
7886
}
7987

@@ -82,7 +90,6 @@ export class VoiceBroadcastRecorder
8290
const chunk = this.extractChunk();
8391
this.currentChunkLength = 0;
8492
this.previousChunkEndTimePosition = 0;
85-
this.headers = new Uint8Array(0);
8693
return chunk;
8794
}
8895

@@ -103,11 +110,19 @@ export class VoiceBroadcastRecorder
103110

104111
private onDataAvailable = (data: ArrayBuffer): void => {
105112
const dataArray = new Uint8Array(data);
106-
this.pagesFromRecorderCount++;
107113

108-
if (this.pagesFromRecorderCount <= 2) {
109-
// first two pages contain the headers
110-
this.headers = concat(this.headers, dataArray);
114+
// extract the part, that contains the header type info
115+
const headerType = Array.from(dataArray.slice(28, 36));
116+
117+
if (isEqual(OpusHead, headerType)) {
118+
// data seems to be an "OpusHead" header
119+
this.opusHead = dataArray;
120+
return;
121+
}
122+
123+
if (isEqual(OpusTags, headerType)) {
124+
// data seems to be an "OpusTags" header
125+
this.opusTags = dataArray;
111126
return;
112127
}
113128

@@ -134,9 +149,14 @@ export class VoiceBroadcastRecorder
134149
return null;
135150
}
136151

152+
if (!this.opusHead || !this.opusTags) {
153+
logger.warn("Broadcast chunk cannot be extracted. OpusHead or OpusTags is missing.");
154+
return null;
155+
}
156+
137157
const currentRecorderTime = this.voiceRecording.recorderSeconds;
138158
const payload: ChunkRecordedPayload = {
139-
buffer: concat(this.headers, this.chunkBuffer),
159+
buffer: concat(this.opusHead!, this.opusTags!, this.chunkBuffer),
140160
length: this.getCurrentChunkLength(),
141161
};
142162
this.chunkBuffer = new Uint8Array(0);

test/voice-broadcast/audio/VoiceBroadcastRecorder-test.ts

+14-4
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,10 @@ describe("VoiceBroadcastRecorder", () => {
6767

6868
describe("instance", () => {
6969
const chunkLength = 30;
70-
const headers1 = new Uint8Array([1, 2]);
71-
const headers2 = new Uint8Array([3, 4]);
70+
// 0... OpusHead
71+
const headers1 = new Uint8Array([...Array(28).fill(0), 79, 112, 117, 115, 72, 101, 97, 100]);
72+
// 0... OpusTags
73+
const headers2 = new Uint8Array([...Array(28).fill(0), 79, 112, 117, 115, 84, 97, 103, 115]);
7274
const chunk1 = new Uint8Array([5, 6]);
7375
const chunk2a = new Uint8Array([7, 8]);
7476
const chunk2b = new Uint8Array([9, 10]);
@@ -79,12 +81,16 @@ describe("VoiceBroadcastRecorder", () => {
7981
let onChunkRecorded: (chunk: ChunkRecordedPayload) => void;
8082

8183
const simulateFirstChunk = (): void => {
84+
// send headers in wrong order and multiple times to test robustness for that
85+
voiceRecording.onDataAvailable(headers2);
86+
voiceRecording.onDataAvailable(headers1);
8287
voiceRecording.onDataAvailable(headers1);
8388
voiceRecording.onDataAvailable(headers2);
8489
// set recorder seconds to something greater than the test chunk length of 30
8590
// @ts-ignore
8691
voiceRecording.recorderSeconds = 42;
8792
voiceRecording.onDataAvailable(chunk1);
93+
voiceRecording.onDataAvailable(headers1);
8894
};
8995

9096
const expectOnFirstChunkRecorded = (): void => {
@@ -155,15 +161,15 @@ describe("VoiceBroadcastRecorder", () => {
155161
expect(voiceBroadcastRecorder.contentType).toBe(contentType);
156162
});
157163

158-
describe("when the first page from recorder has been received", () => {
164+
describe("when the first header from recorder has been received", () => {
159165
beforeEach(() => {
160166
voiceRecording.onDataAvailable(headers1);
161167
});
162168

163169
itShouldNotEmitAChunkRecordedEvent();
164170
});
165171

166-
describe("when a second page from recorder has been received", () => {
172+
describe("when the second header from recorder has been received", () => {
167173
beforeEach(() => {
168174
voiceRecording.onDataAvailable(headers1);
169175
voiceRecording.onDataAvailable(headers2);
@@ -229,6 +235,10 @@ describe("VoiceBroadcastRecorder", () => {
229235

230236
// simulate a second chunk
231237
voiceRecording.onDataAvailable(chunk2a);
238+
239+
// send headers again to test robustness for that
240+
voiceRecording.onDataAvailable(headers2);
241+
232242
// add another 30 seconds for the next chunk
233243
// @ts-ignore
234244
voiceRecording.recorderSeconds = 72;

0 commit comments

Comments
 (0)