Skip to content

Commit

Permalink
Add example for subscribe receive audio frames (#260)
Browse files Browse the repository at this point in the history
* Add example for subscribe receive audio frames

* update package

* update example steps

* update package.json to use workspace dependency

* going back to using releases

* writing wav files

* update pnpm-lock.yaml

---------

Co-authored-by: lukasIO <[email protected]>
  • Loading branch information
davidzhao and lukasIO committed Aug 25, 2024
1 parent 26206ec commit 5345ab9
Show file tree
Hide file tree
Showing 7 changed files with 524 additions and 8 deletions.
2 changes: 1 addition & 1 deletion examples/publish-wav/.env.example
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# 1. Copy this file and rename it to .env.local
# 1. Copy this file and rename it to .env
# 2. Update the enviroment variables below.

LIVEKIT_API_KEY=devkey
Expand Down
7 changes: 4 additions & 3 deletions examples/publish-wav/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@
"keywords": [],
"license": "Apache-2.0",
"dependencies": {
"@livekit/rtc-node": "workspace:*",
"@livekit/rtc-node": "^0.7.0",
"dotenv": "^16.4.5",
"livekit-server-sdk": "workspace:*"
"livekit-server-sdk": "^2.6.1"
},
"devDependencies": {
"@types/node": "^20.10.4"
"@types/node": "^20.10.4",
"tsx": "^4.7.1"
}
}
6 changes: 6 additions & 0 deletions examples/receive-audio/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# 1. Copy this file and rename it to .env
# 2. Update the enviroment variables below.

LIVEKIT_API_KEY=mykey
LIVEKIT_API_SECRET=mysecret
LIVEKIT_URL=wss://myproject.livekit.cloud
10 changes: 10 additions & 0 deletions examples/receive-audio/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Receive audio example

This example demonstrates receiving the first audio track published in a room and writing that audio data to a wav file.

To run the example:

- Copy .env.example to .env and fill in the values
- Run `pnpm install` in the root folder of this repo
- Run `tsx index.ts` in this folder
- From another client, join the room `test-room` and publish an audio track
125 changes: 125 additions & 0 deletions examples/receive-audio/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import { AudioStream, Room, RoomEvent, TrackKind } from '@livekit/rtc-node';
import type { AudioFrame } from '@livekit/rtc-node/src';
import { Buffer } from 'buffer';
import { config } from 'dotenv';
import * as fs from 'fs';
import { AccessToken } from 'livekit-server-sdk';

config();

// Constants for WAV file
const BITS_PER_SAMPLE = 16;
const WAV_FILE = 'output.wav';

function writeWavHeader(writer: fs.WriteStream, frame: AudioFrame) {
const header = Buffer.alloc(44);
const byteRate = (frame.sampleRate * frame.channels * BITS_PER_SAMPLE) / 8;
const blockAlign = (frame.channels * BITS_PER_SAMPLE) / 8;

writer = fs.createWriteStream(WAV_FILE);
// Write the RIFF header
header.write('RIFF', 0); // ChunkID
header.writeUInt32LE(0, 4); // ChunkSize placeholder
header.write('WAVE', 8); // Format

// Write the fmt subchunk
header.write('fmt ', 12); // Subchunk1ID
header.writeUInt32LE(16, 16); // Subchunk1Size (PCM)
header.writeUInt16LE(1, 20); // AudioFormat (PCM = 1)
header.writeUInt16LE(frame.channels, 22); // NumChannels
header.writeUInt32LE(frame.sampleRate, 24); // SampleRate
header.writeUInt32LE(byteRate, 28); // ByteRate
header.writeUInt16LE(blockAlign, 32); // BlockAlign
header.writeUInt16LE(16, 34); // BitsPerSample

// Write the data subchunk
header.write('data', 36); // Subchunk2ID
header.writeUInt32LE(0, 40); // Subchunk2Size placeholder

// Write the header to the stream
writer.write(header);
}

function updateWavHeader(path: string) {
// Update the size of the audio data in the header
const stats = fs.statSync(path);
const fileSize = stats.size;

const chunkSize = fileSize - 8;
const subchunk2Size = fileSize - 44;
const header = Buffer.alloc(8);
header.writeUInt32LE(chunkSize, 0);
header.writeUInt32LE(subchunk2Size, 4);

// Reopen the file for updating the header
const fd = fs.openSync(path, 'r+');
fs.writeSync(fd, header, 0, 4, 4); // Update ChunkSize
fs.writeSync(fd, header, 4, 4, 40); // Update Subchunk2Size
fs.closeSync(fd);
}

// create access token from API credentials
const token = new AccessToken(process.env.LIVEKIT_API_KEY, process.env.LIVEKIT_API_SECRET, {
identity: 'example-participant',
});
token.addGrant({
room: 'test-room',
roomJoin: true,
roomCreate: true,
canPublish: true,
canPublishData: true,
});
const jwt = await token.toJwt();

// set up room
const room = new Room();

let trackToProcess: string | null = null;
let writer: fs.WriteStream | null = null;

room.on(RoomEvent.TrackSubscribed, (track, publication, participant) => {
console.log('subscribed to track', track.sid, publication, participant.identity);
if (track.kind === TrackKind.KIND_AUDIO) {
const stream = new AudioStream(track);
trackToProcess = track.sid;

stream.on('frameReceived', (ev) => {
if (!trackToProcess) {
return;
}

if (writer == null) {
// create file on first frame
// also guard when track is unsubscribed
writer = fs.createWriteStream('output.wav');
writeWavHeader(writer, ev.frame);
}

if (writer) {
const buf = Buffer.from(ev.frame.data.buffer);
writer.write(buf);
}
});
}
});

const finishedPromise = new Promise<void>((resolve) => {
room.on(RoomEvent.TrackUnsubscribed, (_, publication, participant) => {
console.log('unsubscribed from track', publication.sid, participant.identity);
if (publication.sid === trackToProcess) {
trackToProcess = null;
if (writer) {
writer.close();
// update header
updateWavHeader(WAV_FILE);
}
resolve();
}
});
});

await room.connect(process.env.LIVEKIT_URL, jwt, { autoSubscribe: true, dynacast: true });
console.log('connected to room', room);

// stay in the room until publisher leaves
await finishedPromise;
22 changes: 22 additions & 0 deletions examples/receive-audio/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"name": "example-receive-audio",
"author": "LiveKit",
"private": "true",
"description": "",
"type": "module",
"main": "index.ts",
"scripts": {
"lint": "eslint -f unix \"**/*.ts\""
},
"keywords": [],
"license": "Apache-2.0",
"dependencies": {
"@livekit/rtc-node": "^0.7.0",
"dotenv": "^16.4.5",
"livekit-server-sdk": "^2.6.1"
},
"devDependencies": {
"@types/node": "^20.10.4",
"tsx": "^4.7.1"
}
}
Loading

0 comments on commit 5345ab9

Please sign in to comment.