|
| 1 | +package bot |
| 2 | + |
| 3 | +import ( |
| 4 | + "log" |
| 5 | + |
| 6 | + "VLX_ChatBridge/internal/core/audio" |
| 7 | + "github.com/disgoorg/disgo/voice" |
| 8 | + "github.com/disgoorg/snowflake/v2" |
| 9 | + "gopkg.in/hraban/opus.v2" |
| 10 | + "encoding/binary" |
| 11 | + "bytes" |
| 12 | +) |
| 13 | + |
| 14 | +type DiscordOpusReceiver struct { |
| 15 | + decoder *opus.Decoder |
| 16 | + configDiscordOut bool |
| 17 | + excludedUsers map[string]struct{} |
| 18 | +} |
| 19 | + |
| 20 | +func NewDiscordOpusReceiver(discordOutEnabled bool, excludedUsersList []string) *DiscordOpusReceiver { |
| 21 | + // Discord sends 48kHz, 2 channels |
| 22 | + decoder, err := opus.NewDecoder(48000, 2) |
| 23 | + excludedMap := make(map[string]struct{}) |
| 24 | + for _, id := range excludedUsersList { |
| 25 | + excludedMap[id] = struct{}{} |
| 26 | + } |
| 27 | + |
| 28 | + if err != nil { |
| 29 | + log.Printf("[AudioBridge] Failed to create Opus decoder: %v", err) |
| 30 | + return &DiscordOpusReceiver{configDiscordOut: discordOutEnabled, excludedUsers: excludedMap} |
| 31 | + } |
| 32 | + return &DiscordOpusReceiver{ |
| 33 | + decoder: decoder, |
| 34 | + configDiscordOut: discordOutEnabled, |
| 35 | + excludedUsers: excludedMap, |
| 36 | + } |
| 37 | +} |
| 38 | + |
| 39 | +func (r *DiscordOpusReceiver) ReceiveOpusFrame(userID snowflake.ID, packet *voice.Packet) error { |
| 40 | + if !r.configDiscordOut { |
| 41 | + return nil |
| 42 | + } |
| 43 | + |
| 44 | + if _, excluded := r.excludedUsers[userID.String()]; excluded { |
| 45 | + return nil |
| 46 | + } |
| 47 | + |
| 48 | + if r.decoder == nil { |
| 49 | + return nil |
| 50 | + } |
| 51 | + |
| 52 | + // Opus packets from Discord are typically 20ms at 48kHz stereo = 960 samples per channel = 1920 samples total. |
| 53 | + // 1920 int16 samples * 2 bytes/sample = 3840 bytes. |
| 54 | + // Allocate a slice large enough. |
| 55 | + pcm := make([]int16, 1920) |
| 56 | + |
| 57 | + n, err := r.decoder.Decode(packet.Opus, pcm) |
| 58 | + if err != nil { |
| 59 | + return err |
| 60 | + } |
| 61 | + |
| 62 | + // n is the number of samples per channel. For stereo, total samples = n * 2 |
| 63 | + totalSamples := n * 2 |
| 64 | + |
| 65 | + // Convert int16 PCM to byte slice (little endian) |
| 66 | + buf := new(bytes.Buffer) |
| 67 | + // We can use binary.Write |
| 68 | + err = binary.Write(buf, binary.LittleEndian, pcm[:totalSamples]) |
| 69 | + if err != nil { |
| 70 | + return err |
| 71 | + } |
| 72 | + |
| 73 | + audio.PCMChannel <- audio.StreamData{ |
| 74 | + ID: "discord_" + userID.String(), |
| 75 | + Data: buf.Bytes(), |
| 76 | + } |
| 77 | + |
| 78 | + return nil |
| 79 | +} |
| 80 | + |
| 81 | +func (r *DiscordOpusReceiver) CleanupUser(userID snowflake.ID) { |
| 82 | + // No user specific state right now. |
| 83 | +} |
| 84 | + |
| 85 | +func (r *DiscordOpusReceiver) Close() { |
| 86 | + // Cleanup if necessary |
| 87 | +} |
0 commit comments