1
0
Fork 0
mirror of https://github.com/diamondburned/arikawa.git synced 2024-11-27 17:23:00 +00:00

Voice: Allow custom frame parameters; removed StopSpeaking

This comimt adds a method into the UDP connection to control the
internal frequency ticker which controls the speed of playback. For more
information, refer to (*voice/udp.Connection).ResetFrequency().

StopSpeaking is removed because it no longer works with variable
parameters. The functionality of that method was also arguably useless,
as it only sends silent frames.
This commit is contained in:
diamondburned 2020-11-17 14:43:00 -08:00
parent f4750292eb
commit 6bdac16c2a
2 changed files with 54 additions and 40 deletions

View file

@ -21,8 +21,6 @@ import (
const Protocol = "xsalsa20_poly1305"
var OpusSilence = [...]byte{0xF8, 0xFF, 0xFE}
// ErrAlreadyConnecting is returned when the session is already connecting.
var ErrAlreadyConnecting = errors.New("already connecting")
@ -239,8 +237,7 @@ func (s *Session) reconnectCtx(ctx context.Context) (err error) {
return nil
}
// Speaking tells Discord we're speaking. This calls
// (*voicegateway.Gateway).Speaking(). This method should not be called
// Speaking tells Discord we're speaking. This method should not be called
// concurrently.
func (s *Session) Speaking(flag voicegateway.SpeakingFlag) error {
s.mut.RLock()
@ -250,22 +247,6 @@ func (s *Session) Speaking(flag voicegateway.SpeakingFlag) error {
return gateway.Speaking(flag)
}
// StopSpeaking sends 5 frames of silence over the UDP connection. Since the UDP
// connection itself is not concurrently safe, this method should not be called
// as such.
func (s *Session) StopSpeaking() error {
udp := s.VoiceUDPConn()
// Send 5 frames of silence.
for i := 0; i < 5; i++ {
if _, err := udp.Write(OpusSilence[:]); err != nil {
return errors.Wrapf(err, "failed to send frame %d", i)
}
}
return nil
}
// UseContext tells the UDP voice connection to write with the given mutex.
func (s *Session) UseContext(ctx context.Context) error {
s.mut.Lock()

View file

@ -10,7 +10,6 @@ import (
"github.com/pkg/errors"
"golang.org/x/crypto/nacl/secretbox"
"golang.org/x/time/rate"
)
// Dialer is the default dialer that this package uses for all its dialing.
@ -27,9 +26,12 @@ type Connection struct {
conn net.Conn
ssrc uint32
frequency rate.Limiter
packet [12]byte
secret [32]byte
// frequency rate.Limiter
frequency *time.Ticker
timeIncr uint32
packet [12]byte
secret [32]byte
sequence uint16
timestamp uint32
@ -87,15 +89,44 @@ func DialConnectionCtx(ctx context.Context, addr string, ssrc uint32) (*Connecti
return &Connection{
GatewayIP: string(ip),
GatewayPort: port,
// 50 sends per second, 960 samples each at 48kHz
frequency: *rate.NewLimiter(rate.Every(20*time.Millisecond), 1),
context: context.Background(),
packet: packet,
ssrc: ssrc,
conn: conn,
frequency: time.NewTicker(20 * time.Millisecond),
timeIncr: 960,
context: context.Background(),
packet: packet,
ssrc: ssrc,
conn: conn,
}, nil
}
// ResetFrequency resets the internal frequency ticker as well as the timestamp
// incremental number. For more information, refer to
// https://tools.ietf.org/html/rfc7587#section-4.2.
//
// frameDuration controls the Opus frame duration used by the UDP connection to
// control the frequency of packets sent over. 20ms is the default by libopus.
//
// timestampIncr is the timestamp to increment for each Opus packet. This should
// be consistent with th given frameDuration. For the right combination, refer
// to the Valid Parameters section below.
//
// Valid Parameters
//
// The following table lists the recommended parameters for these variables.
//
// +---------+-----+-----+------+------+
// | Mode | 10 | 20 | 40 | 60 |
// +---------+-----+-----+------+------+
// | ts incr | 480 | 960 | 1920 | 2880 |
// +---------+-----+-----+------+------+
//
// Note that audio mode is omitted, as it is not recommended. For the full
// table, refer to the IETF RFC7587 section 4.2 link above.
func (c *Connection) ResetFrequency(frameDuration time.Duration, timeIncr uint32) {
c.frequency.Stop()
c.frequency = time.NewTicker(frameDuration)
c.timeIncr = timeIncr
}
// UseSecret uses the given secret. This method is not thread-safe, so it should
// only be used right after initialization.
func (c *Connection) UseSecret(secret [32]byte) {
@ -123,6 +154,7 @@ func (c *Connection) useContext(ctx context.Context) error {
}
func (c *Connection) Close() error {
c.frequency.Stop()
return c.conn.Close()
}
@ -135,12 +167,10 @@ func (c *Connection) Write(b []byte) (int, error) {
// given context. It ignores the context inside the connection, but will restore
// the deadline after this call is done.
func (c *Connection) WriteCtx(ctx context.Context, b []byte) (int, error) {
if deadline, ok := ctx.Deadline(); ok {
ctx := c.context
defer c.useContext(ctx) // restore after we're done
oldCtx := c.context
c.conn.SetWriteDeadline(deadline)
}
c.useContext(ctx)
defer c.useContext(oldCtx)
return c.write(b)
}
@ -151,16 +181,19 @@ func (c *Connection) write(b []byte) (int, error) {
c.sequence++
binary.BigEndian.PutUint32(c.packet[4:8], c.timestamp)
c.timestamp += 960 // Samples
c.timestamp += c.timeIncr
copy(c.nonce[:], c.packet[:])
if err := c.frequency.Wait(c.context); err != nil {
return 0, errors.Wrap(err, "failed to wait for frequency tick")
}
toSend := secretbox.Seal(c.packet[:], b, &c.nonce, &c.secret)
select {
case <-c.frequency.C:
case <-c.context.Done():
return 0, c.context.Err()
}
n, err := c.conn.Write(toSend)
if err != nil {
return n, errors.Wrap(err, "failed to write to UDP connection")