Skip to content
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

Cleanup various parts of the library #150

Merged
merged 24 commits into from
Feb 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
44e6fc7
Remove `streamLivestreamVideo`
longnguyen2004 Feb 3, 2025
b678a6e
Stop exposing `(Audio|Video)Stream`
longnguyen2004 Feb 3, 2025
2693b96
Move `setProtocols` call back into `BaseMediaConnection`
longnguyen2004 Feb 3, 2025
1a44e3f
Remove problematic `streamOptions`, move force Chacha20 options to `S…
longnguyen2004 Feb 4, 2025
35af8a8
Remove the `forceChacha20Encryption` option in `playStream`
longnguyen2004 Feb 4, 2025
4dfe2d4
Expose new API as top level exports
longnguyen2004 Feb 4, 2025
82e8051
Pass SSRC through constructor
longnguyen2004 Feb 4, 2025
87e4468
Move `rtcpSenderReportEnabled` option to Streamer, make RTCP interval…
longnguyen2004 Feb 4, 2025
ddff8ba
Fix README
longnguyen2004 Feb 4, 2025
6804f35
Remove old basic example
longnguyen2004 Feb 4, 2025
d679b46
Add `noTranscoding` option to README
longnguyen2004 Feb 4, 2025
37a1b2f
Remove `custom-stream-copy-codec` example
longnguyen2004 Feb 4, 2025
4eb3787
Update `puppeteer-stream` example
longnguyen2004 Feb 4, 2025
9bb33ba
Add newlines to fix IDE docs
longnguyen2004 Feb 4, 2025
b3a3eb3
Document `playStream` options, specify where to change streamer options
longnguyen2004 Feb 4, 2025
933faec
Add optional specifier to `playStream` options
longnguyen2004 Feb 4, 2025
2b20a38
Move `sendOpcode` call outside of promise creation
longnguyen2004 Feb 4, 2025
be8f806
Fix incorrect logic
longnguyen2004 Feb 5, 2025
7f2c3e6
Revert "Move `sendOpcode` call outside of promise creation"
longnguyen2004 Feb 5, 2025
e63d1e6
How did this happen...
longnguyen2004 Feb 5, 2025
43db32a
Update streamer options
longnguyen2004 Feb 5, 2025
37297c7
Add performance tips
longnguyen2004 Feb 5, 2025
3c9ac09
Remove leftover `srInterval` override
longnguyen2004 Feb 5, 2025
4c610f3
Use `crypto` global instead of importing `node:crypto`
longnguyen2004 Feb 6, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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