Skip to content

Commit

Permalink
Merge pull request #8 from EsefexBot/dev-api
Browse files Browse the repository at this point in the history
Fixed "memory" leak which copied sound in memory for every instance playing
  • Loading branch information
jokil123 authored Dec 26, 2023
2 parents be6e59b + 3535ecd commit 8076d88
Show file tree
Hide file tree
Showing 12 changed files with 130 additions and 12 deletions.
2 changes: 1 addition & 1 deletion EsefexApi/audioplayer/discordplayer/vcon/vcon.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ func (a *VCon) Run() {
continue
}

s := audioprocessing.NewS16leCacheReaderFromPCM(pcm)
s := audioprocessing.NewS16leReferenceReaderFromRef(pcm)
a.mixer.AddSource(s)
log.Println("Added sound to mixer")
case <-a.stop:
Expand Down
4 changes: 2 additions & 2 deletions EsefexApi/audioprocessing/gopusencoder.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package audioprocessing

import (
"encoding/binary"
"esefexapi/audioprocessing/pcmutil"
"io"
"log"

Expand Down Expand Up @@ -32,7 +32,7 @@ func NewGopusEncoder(s io.Reader) (*GopusEncoder, error) {
func (e *GopusEncoder) EncodeNext() ([]byte, error) {
pcm := make([]int16, 960*2)

err := binary.Read(*e.source, binary.LittleEndian, &pcm)
_, err := pcmutil.ReadPCM(*e.source, &pcm)
// Read from the source
if err != nil && err != io.EOF {
log.Printf("Error reading from source: %s\n", err)
Expand Down
23 changes: 23 additions & 0 deletions EsefexApi/audioprocessing/pcmutil/readpcm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package pcmutil

import (
"encoding/binary"
"io"
)

// ReadPCM reads PCM data from r and stores it in buf.
// buf must be a slice of int16s.
// ReadPCM returns the number of bytes read and an error if any.
// ReadPCM assumes that the data is in little endian.
// if the io.Reader gives an EOF before the buffer is filled, ReadPCM returns io.EOF and the number of bytes read.
// unlike binary.Read, ReadPCM will still write to buf even if the io.Reader gives an EOF before the buffer is filled.
func ReadPCM(r io.Reader, buf *[]int16) (int, error) {
for i := range *buf {
err := binary.Read(r, binary.LittleEndian, &(*buf)[i])
if err != nil {
return i, err
}
}

return len(*buf), nil
}
4 changes: 4 additions & 0 deletions EsefexApi/audioprocessing/pcmutil/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package pcmutil

type PcmSample int16
type Pcm []PcmSample
61 changes: 61 additions & 0 deletions EsefexApi/audioprocessing/s16leReferenceReader.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package audioprocessing

import (
"io"
"log"
)

type S16leReferenceReader struct {
data *[]int16
cursor int
}

func (s *S16leReferenceReader) Read(p []byte) (n int, err error) {
if s.cursor >= len(*s.data)*2 {
log.Println("EOF")
return 0, io.EOF
}

n = 0

for i := range p {
b, err := s.getByte(s.cursor)
if err != nil {
break
}

p[i] = b
s.cursor++
n++
}

return n, nil
}

func (s *S16leReferenceReader) getByte(i int) (byte, error) {
if i >= len(*s.data)*2 {
return 0, io.EOF
}

if i%2 == 0 {
return byte((*s.data)[i/2] & 0xff), nil
} else {
return byte((*s.data)[i/2] >> 8), nil
}
}

func (s *S16leReferenceReader) Load(data *[]int16) {
s.data = data
s.cursor = 0
}

func NewS16leReferenceReader() *S16leReferenceReader {
return &S16leReferenceReader{}
}

func NewS16leReferenceReaderFromRef(pcm *[]int16) *S16leReferenceReader {
reader := &S16leReferenceReader{}
reader.Load(pcm)

return reader
}
29 changes: 29 additions & 0 deletions EsefexApi/audioprocessing/s16leReferenceReader_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package audioprocessing

import (
"esefexapi/audioprocessing/pcmutil"
"log"
"testing"

"github.com/stretchr/testify/assert"
)

func TestS16leReferenceReader(t *testing.T) {
data := []int16{1, 2, 3, 4, 5, 6}

reader := NewS16leReferenceReaderFromRef(&data)

assert.Equal(t, data, *reader.data)

// assert.Equal(t, byte(1), reader.getByte(0))
// assert.Equal(t, byte(0), reader.getByte(1))
// assert.Equal(t, byte(2), reader.getByte(2))
// assert.Equal(t, byte(0), reader.getByte(3))

int16buf := make([]int16, len(data))
n, err := pcmutil.ReadPCM(reader, &int16buf)
assert.Nil(t, err)
log.Printf("n: %d", n)

assert.Equal(t, data, int16buf)
}
3 changes: 2 additions & 1 deletion EsefexApi/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"esefexapi/sounddb/dbcache"
"esefexapi/sounddb/filedb"
"esefexapi/util"
"os"

"log"

Expand All @@ -20,7 +21,7 @@ func init() {
}

func main() {
log.Println("Starting Esefex API")
log.Printf("Starting Esefex API with PID: %d", os.Getpid())

cfg, err := config.LoadConfig("config.toml")
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion EsefexApi/sounddb/apimockdb/apimockdb.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ func (*ApiMockDB) GetSoundMeta(uid sounddb.SoundUID) (sounddb.SoundMeta, error)
}

// GetSoundPcm implements sounddb.ISoundDB.
func (*ApiMockDB) GetSoundPcm(uid sounddb.SoundUID) ([]int16, error) {
func (*ApiMockDB) GetSoundPcm(uid sounddb.SoundUID) (*[]int16, error) {
panic("unimplemented")
}

Expand Down
2 changes: 1 addition & 1 deletion EsefexApi/sounddb/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ type ISoundDB interface {
AddSound(serverID string, name string, icon Icon, pcm []int16) (SoundUID, error)
DeleteSound(uid SoundUID) error
GetSoundMeta(uid SoundUID) (SoundMeta, error)
GetSoundPcm(uid SoundUID) ([]int16, error)
GetSoundPcm(uid SoundUID) (*[]int16, error)
GetSoundUIDs(serverID string) ([]SoundUID, error)
GetServerIDs() ([]string, error)
SoundExists(uid SoundUID) (bool, error)
Expand Down
6 changes: 3 additions & 3 deletions EsefexApi/sounddb/dbcache/dbcache.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ type DBCache struct {
}

type CachedSound struct {
Data []int16
Data *[]int16
Meta sounddb.SoundMeta
}

Expand All @@ -41,7 +41,7 @@ func (c *DBCache) AddSound(serverID string, name string, icon sounddb.Icon, pcm
}

c.sounds[uid] = &CachedSound{
Data: pcm,
Data: &pcm,
Meta: sounddb.SoundMeta{
SoundID: uid.SoundID,
ServerID: serverID,
Expand Down Expand Up @@ -106,7 +106,7 @@ func (c *DBCache) GetSoundMeta(uid sounddb.SoundUID) (sounddb.SoundMeta, error)
}

// GetSoundPcm implements db.SoundDB.
func (c *DBCache) GetSoundPcm(uid sounddb.SoundUID) ([]int16, error) {
func (c *DBCache) GetSoundPcm(uid sounddb.SoundUID) (*[]int16, error) {
c.rw.RLock()
defer c.rw.RUnlock()

Expand Down
2 changes: 1 addition & 1 deletion EsefexApi/sounddb/filedb/filedb_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func TestFileDB(t *testing.T) {
// Test that we can get the sound pcm
soundPcm2, err := db.GetSoundPcm(uid)
assert.Nil(t, err)
assert.Equal(t, soundPcm, soundPcm2)
assert.Equal(t, &soundPcm, soundPcm2)

// Test that we can get the server ids
ids, err := db.GetServerIDs()
Expand Down
4 changes: 2 additions & 2 deletions EsefexApi/sounddb/filedb/getsoundpcm.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
)

// GetSoundPcm implements sounddb.SoundDB.
func (f *FileDB) GetSoundPcm(uid sounddb.SoundUID) ([]int16, error) {
func (f *FileDB) GetSoundPcm(uid sounddb.SoundUID) (*[]int16, error) {
path := fmt.Sprintf("%s/%s/%s_sound.s16le", f.location, uid.ServerID, uid.SoundID)
sf, err := os.Open(path)
if err != nil {
Expand All @@ -28,5 +28,5 @@ func (f *FileDB) GetSoundPcm(uid sounddb.SoundUID) ([]int16, error) {
return nil, err
}

return pcm, nil
return &pcm, nil
}

0 comments on commit 8076d88

Please sign in to comment.