Skip to content

Commit

Permalink
Cleanup various parts of the library (#150)
Browse files Browse the repository at this point in the history
* Remove `streamLivestreamVideo`

* Stop exposing `(Audio|Video)Stream`

* Move `setProtocols` call back into `BaseMediaConnection`

User shouldn't be calling this anyway

* Remove problematic `streamOptions`, move force Chacha20 options to `Streamer`

Moving the Chacha20 options to the Streamer class is probably a good move, since it's unlikely that anyone would want 2 separate encryption methods for the 2 streams

* Remove the `forceChacha20Encryption` option in `playStream`

* Expose new API as top level exports

* Pass SSRC through constructor

* Move `rtcpSenderReportEnabled` option to Streamer, make RTCP interval time-based

* Fix README

* Remove old basic example

* Add `noTranscoding` option to README

* Remove `custom-stream-copy-codec` example

The functionality is now built in to the library

* Update `puppeteer-stream` example

Untested, but I don't see why it shouldn't work

* Add newlines to fix IDE docs

* Document `playStream` options, specify where to change streamer options

* Add optional specifier to `playStream` options

* Move `sendOpcode` call outside of promise creation

* Fix incorrect logic

* Revert "Move `sendOpcode` call outside of promise creation"

Due to some unknown causes, the event ends up firing before the handler is attached

* How did this happen...

* Update streamer options

* Add performance tips

* Remove leftover `srInterval` override

* Use `crypto` global instead of importing `node:crypto`
  • Loading branch information
longnguyen2004 authored Feb 6, 2025
1 parent 4bb49e8 commit 9537628
Show file tree
Hide file tree
Showing 30 changed files with 467 additions and 1,170 deletions.
81 changes: 81 additions & 0 deletions PERFORMANCE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Performance related tweaks

## Transport encryption methods

On CPUs without AES acceleration (very old x86 CPUs, certain ARM SoCs on single board computers, certain VMs that don't expose AES acceleration capability), the default encryption method (AES-256-GCM) might not be fast enough to handle high frame-rate + high bitrate streams.

In such cases, you can enable the `forceChacha20Encryption` option on the `Streamer` instance (`streamer.opts.forceChacha20Encryption = true`) before starting a stream, to force the use of the faster Chacha20-Poly1305 encryption method. For even higher performance, also install the optional [`sodium-native`](https://www.npmjs.com/package/sodium-native) package to use the faster native version instead of the WASM version.

Below are some benchmark results of the two encryption methods in various circumstances, for reference purposes only. All benchmarks are performed on a Ryzen 5 5600H.

<details>
<summary>AES-256-GCM, with AES acceleration</summary>

```
PS C:\> openssl speed -elapsed -aead -evp aes-256-gcm
You have chosen to measure elapsed time instead of user CPU time.
Doing AES-256-GCM ops for 3s on 2 size blocks: 19046296 AES-256-GCM ops in 3.00s
Doing AES-256-GCM ops for 3s on 31 size blocks: 15299030 AES-256-GCM ops in 3.00s
Doing AES-256-GCM ops for 3s on 136 size blocks: 13580376 AES-256-GCM ops in 3.00s
Doing AES-256-GCM ops for 3s on 1024 size blocks: 7691855 AES-256-GCM ops in 3.00s
Doing AES-256-GCM ops for 3s on 8192 size blocks: 1648811 AES-256-GCM ops in 3.00s
Doing AES-256-GCM ops for 3s on 16384 size blocks: 863115 AES-256-GCM ops in 3.00s
version: 3.4.0
built on: Tue Oct 22 23:27:41 2024 UTC
options: bn(64,64)
compiler: cl /Z7 /Fdossl_static.pdb /Gs0 /GF /Gy /MD /W3 /wd4090 /nologo /O2 -DL_ENDIAN -DOPENSSL_PIC -D"OPENSSL_BUILDING_OPENSSL" -D"OPENSSL_SYS_WIN32" -D"WIN32_LEAN_AND_MEAN" -D"UNICODE" -D"_UNICODE" -D"_CRT_SECURE_NO_DEPRECATE" -D"_WINSOCK_DEPRECATED_NO_WARNINGS" -D"NDEBUG" -D_WINSOCK_DEPRECATED_NO_WARNINGS -D_WIN32_WINNT=0x0502
CPUINFO: OPENSSL_ia32cap=0xfed8320b078bffff:0x400684219c97a9
The 'numbers' are in 1000s of bytes per second processed.
type 2 bytes 31 bytes 136 bytes 1024 bytes 8192 bytes 16384 bytes
AES-256-GCM 12693.30k 158089.98k 615233.56k 2625486.51k 4500852.95k 4712187.99k
```

</details>

<details>
<summary>AES-256-GCM, without AES acceleration</summary>

```
PS C:\> openssl speed -elapsed -aead -evp aes-256-gcm
You have chosen to measure elapsed time instead of user CPU time.
Doing AES-256-GCM ops for 3s on 2 size blocks: 6947831 AES-256-GCM ops in 3.00s
Doing AES-256-GCM ops for 3s on 31 size blocks: 4875037 AES-256-GCM ops in 3.00s
Doing AES-256-GCM ops for 3s on 136 size blocks: 3132696 AES-256-GCM ops in 3.00s
Doing AES-256-GCM ops for 3s on 1024 size blocks: 821006 AES-256-GCM ops in 3.00s
Doing AES-256-GCM ops for 3s on 8192 size blocks: 113769 AES-256-GCM ops in 3.00s
Doing AES-256-GCM ops for 3s on 16384 size blocks: 57074 AES-256-GCM ops in 3.00s
version: 3.4.0
built on: Tue Oct 22 23:27:41 2024 UTC
options: bn(64,64)
compiler: cl /Z7 /Fdossl_static.pdb /Gs0 /GF /Gy /MD /W3 /wd4090 /nologo /O2 -DL_ENDIAN -DOPENSSL_PIC -D"OPENSSL_BUILDING_OPENSSL" -D"OPENSSL_SYS_WIN32" -D"WIN32_LEAN_AND_MEAN" -D"UNICODE" -D"_UNICODE" -D"_CRT_SECURE_NO_DEPRECATE" -D"_WINSOCK_DEPRECATED_NO_WARNINGS" -D"NDEBUG" -D_WINSOCK_DEPRECATED_NO_WARNINGS -D_WIN32_WINNT=0x0502
CPUINFO: OPENSSL_ia32cap=0xfcd83209078bffff:0x0 env:~0x200000200000000
The 'numbers' are in 1000s of bytes per second processed.
type 2 bytes 31 bytes 136 bytes 1024 bytes 8192 bytes 16384 bytes
AES-256-GCM 4630.34k 50358.60k 142015.55k 280143.33k 310561.70k 311596.27k
```

</details>

<details>
<summary>Chacha20-Poly1305</summary>

```
PS C:\> openssl speed -elapsed -aead -evp chacha20-poly1305
You have chosen to measure elapsed time instead of user CPU time.
Doing ChaCha20-Poly1305 ops for 3s on 2 size blocks: 8312139 ChaCha20-Poly1305 ops in 3.00s
Doing ChaCha20-Poly1305 ops for 3s on 31 size blocks: 7801222 ChaCha20-Poly1305 ops in 3.00s
Doing ChaCha20-Poly1305 ops for 3s on 136 size blocks: 5436377 ChaCha20-Poly1305 ops in 3.00s
Doing ChaCha20-Poly1305 ops for 3s on 1024 size blocks: 4182141 ChaCha20-Poly1305 ops in 3.00s
Doing ChaCha20-Poly1305 ops for 3s on 8192 size blocks: 903567 ChaCha20-Poly1305 ops in 3.00s
Doing ChaCha20-Poly1305 ops for 3s on 16384 size blocks: 472556 ChaCha20-Poly1305 ops in 3.00s
version: 3.4.0
built on: Tue Oct 22 23:27:41 2024 UTC
options: bn(64,64)
compiler: cl /Z7 /Fdossl_static.pdb /Gs0 /GF /Gy /MD /W3 /wd4090 /nologo /O2 -DL_ENDIAN -DOPENSSL_PIC -D"OPENSSL_BUILDING_OPENSSL" -D"OPENSSL_SYS_WIN32" -D"WIN32_LEAN_AND_MEAN" -D"UNICODE" -D"_UNICODE" -D"_CRT_SECURE_NO_DEPRECATE" -D"_WINSOCK_DEPRECATED_NO_WARNINGS" -D"NDEBUG" -D_WINSOCK_DEPRECATED_NO_WARNINGS -D_WIN32_WINNT=0x0502
CPUINFO: OPENSSL_ia32cap=0xfed8320b078bffff:0x400684219c97a9
The 'numbers' are in 1000s of bytes per second processed.
type 2 bytes 31 bytes 136 bytes 1024 bytes 8192 bytes 16384 bytes
ChaCha20-Poly1305 5539.58k 80585.77k 246284.90k 1427504.13k 2465696.49k 2580785.83k
```

</details>
188 changes: 146 additions & 42 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Discord self-bot video

Fork: [Discord-video-experiment](https://github.com/mrjvs/Discord-video-experiment)

> [!CAUTION]
Expand All @@ -9,34 +10,42 @@ This project implements the custom Discord UDP protocol for sending media. Since
For better stability it is recommended to use WebRTC protocol instead since Discord is forced to adhere to spec, which means that the non-signaling portion of the code is guaranteed to work.

## Features
- Playing video & audio in a voice channel (`Go Live`, or webcam video)

- Playing video & audio in a voice channel (`Go Live`, or webcam video)

## Implementation

What I implemented and what I did not.

#### Video codecs
- [X] VP8
- [ ] VP9
- [X] H.264
- [X] H.265
- [ ] AV1
### Video codecs

- [X] VP8
- [ ] VP9
- [X] H.264
- [X] H.265
- [ ] AV1

### Packet types

#### Packet types
- [X] RTP (sending of realtime data)
- [ ] RTX (retransmission)
- [X] RTP (sending of realtime data)
- [ ] RTX (retransmission)

#### Connection types
- [X] Regular Voice Connection
- [X] Go live
### Connection types

### Encryption
- [X] Transport Encryption
- [ ] https://github.com/dank074/Discord-video-stream/issues/102
- [X] Regular Voice Connection
- [X] Go live

#### Extras
- [X] Figure out rtp header extensions (discord specific) (discord seems to use one-byte RTP header extension https://www.rfc-editor.org/rfc/rfc8285.html#section-4.2)
### Encryption

- [X] Transport Encryption
- [ ] [End-to-end Encryption](https://github.com/dank074/Discord-video-stream/issues/102)

### Extras

- [X] Figure out rtp header extensions (discord specific) (discord seems to use one-byte RTP header extension https://www.rfc-editor.org/rfc/rfc8285.html#section-4.2)

Extensions supported by Discord (taken from the webrtc sdp exchange)

```
"a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level"
"a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time"
Expand All @@ -51,19 +60,24 @@ Extensions supported by Discord (taken from the webrtc sdp exchange)
"a=extmap:13 urn:3gpp:video-orientation"
"a=extmap:14 urn:ietf:params:rtp-hdrext:toffset"
```

## Requirements

Ffmpeg is required for the usage of this package. If you are on linux you can easily install ffmpeg from your distribution's package manager.

If you are on Windows, you can download it from the official ffmpeg website: https://ffmpeg.org/download.html

## Usage

Install the package, alongside its peer-dependency discord.js-selfbot-v13:

```
npm install @dank074/discord-video-stream@latest
npm install discord.js-selfbot-v13@latest
```

Create a new Streamer, and pass it a selfbot Client

```typescript
import { Client } from "discord.js-selfbot-v13";
import { Streamer } from '@dank074/discord-video-stream';
Expand All @@ -74,6 +88,7 @@ await streamer.client.login('TOKEN HERE');
```

Make client join a voice channel and create a stream:

```typescript
await streamer.joinVoice("GUILD ID HERE", "CHANNEL ID HERE");

Expand All @@ -83,29 +98,47 @@ const udp = await streamer.createStream({
```

Start sending media over the udp connection:

```typescript
udp.mediaConnection.setSpeaking(true);
udp.mediaConnection.setVideoStatus(true);
import { prepareStream, playStream, Util } from "@dank074/discord-video-stream"
try {
const cancellableCommand = await streamLivestreamVideo("DIRECT VIDEO URL OR READABLE STREAM HERE", udp);
const { command, output } = prepareStream("DIRECT VIDEO URL OR READABLE STREAM HERE", {
// Specify either width or height for aspect ratio aware scaling
// Specify both for stretched output
height: 1080,

// Force frame rate, or leave blank to use source frame rate
frameRate: 30,
bitrateVideo: 5000,
bitrateVideoMax: 7500,
videoCodec: Utils.normalizeVideoCodec("H264" /* or H265, VP9 */),
h26xPreset: "veryfast" // or superfast, ultrafast, ...
});
command.on("error", (err, stdout, stderr) => {
// Handle ffmpeg errors here
});

const result = await cancellableCommand;
console.log("Finished playing video " + res);
await playStream(output, streamer, {
type: "go-live" // use "camera" for camera stream
});

console.log("Finished playing video");
} catch (e) {
if (command.isCanceled) {
// Handle the cancelation here
console.log('Ffmpeg command was cancelled');
} else {
console.log(e);
}
} finally {
udp.mediaConnection.setSpeaking(false);
udp.mediaConnection.setVideoStatus(false);
console.log(e);
}
```

## Stream options available
## Encoder options available

```typescript
/**
* Disable transcoding of the video stream. If specified, all video related
* options have no effects
*
* Only use this if your video stream is Discord streaming friendly, otherwise
* you'll get a glitchy output
*/
noTranscoding?: boolean;
/**
* Video output width
*/
Expand All @@ -119,10 +152,21 @@ height?: number;
*/
fps?: number;
/**
* Video output bitrate in kbps
* Video average bitrate in kbps
*/
bitrateKbps?: number;
maxBitrateKbps?: number;
bitrateVideo?: number;
/**
* Video max bitrate in kbps
*/
bitrateVideoMax?: number;
/**
* Audio bitrate in kbps
*/
bitrateAudio?: number;
/**
* Enable audio output
*/
includeAudio?: boolean;
/**
* Enables hardware accelerated video decoding. Enabling this option might result in an exception
* being thrown by Ffmpeg process if your system does not support hardware acceleration
Expand All @@ -132,11 +176,6 @@ hardwareAcceleratedDecoding?: boolean;
* Output video codec. **Only** supports H264, H265, and VP8 currently
*/
videoCodec?: SupportedVideoCodec;
/**
* Enables sending RTCP sender reports. Helps the receiver synchronize the audio/video frames, except in some weird
* cases which is why you can disable it
*/
rtcpSenderReportEnabled?: boolean;
/**
* Encoding preset for H264 or H265. The faster it is, the lower the quality
*/
Expand All @@ -146,13 +185,73 @@ h26xPreset?: 'ultrafast' | 'superfast' | 'veryfast' | 'faster' | 'fast' | 'mediu
* Might create lag in video output in some rare cases
*/
minimizeLatency?: boolean;
```

## `playStream` options available

```typescript
/**
* Set stream type as "Go Live" or camera stream
*/
type?: "go-live" | "camera",

/**
* Override video width sent to Discord.
*
* DO NOT SPECIFY UNLESS YOU KNOW WHAT YOU'RE DOING!
*/
width?: number,

/**
* Override video height sent to Discord.
*
* DO NOT SPECIFY UNLESS YOU KNOW WHAT YOU'RE DOING!
*/
height?: number,

/**
* Override video frame rate sent to Discord.
*
* DO NOT SPECIFY UNLESS YOU KNOW WHAT YOU'RE DOING!
*/
frameRate?: number,

/**
* Same as ffmpeg's `readrate_initial_burst` command line flag
*
* See https://ffmpeg.org/ffmpeg.html#:~:text=%2Dreadrate_initial_burst
*/
readrateInitialBurst?: number,
```

## Streamer options available

These control internal operations of the library, and can be changed through the `opts` property on the `Streamer` class. You probably shouldn't change it without a good reason

```typescript
/**
* Enables sending RTCP sender reports. Helps the receiver synchronize the
* audio/video frames, except in some weird cases which is why you can disable it
*/
rtcpSenderReportEnabled?: boolean;
/**
* ChaCha20-Poly1305 Encryption is faster than AES-256-GCM, except when using AES-NI
*/
forceChacha20Encryption?: boolean;
/**
* Custom headers for HTTP requests
*/
customHeaders?: Record<string, string>
```

## Performance tips

See [this page](./PERFORMANCE.md) for some tips on improving performance

## Running example

`examples/basic/src/config.json`:

```json
"token": "SELF TOKEN HERE",
"acceptedAuthors": ["USER_ID_HERE"],
Expand All @@ -162,23 +261,28 @@ forceChacha20Encryption?: boolean;
2. Generate js files with ```npm run build```
3. Start program with: ```npm run start```
4. Join a voice channel
5. Start streaming with commands:
5. Start streaming with commands:

for go-live

```
$play-live <Direct video link>
```

or for cam

```
$play-cam <Direct video link>
```

for example:

```
$play-live http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4
```

## FAQS

- Can I stream on existing voice connection (CAM) and in a go-live connection simultaneously?

Yes, just send the media packets over both udp connections. The voice gateway expects you to signal when a user turns on their camera, so make sure you signal using `client.signalVideo(guildId, channelId, true)` before you start sending cam media packets.
Expand Down
3 changes: 0 additions & 3 deletions examples/basic-new-api/README.md

This file was deleted.

Loading

0 comments on commit 9537628

Please sign in to comment.