From 3535ecd5da933fe38e1c7daa5afe15ae8f16151b Mon Sep 17 00:00:00 2001 From: jo_kil Date: Tue, 26 Dec 2023 22:24:33 +0100 Subject: [PATCH] fixed "memory" leak which copied sound in memory for every instance playing --- .../audioplayer/discordplayer/vcon/vcon.go | 2 +- EsefexApi/audioprocessing/gopusencoder.go | 4 +- EsefexApi/audioprocessing/pcmutil/readpcm.go | 23 +++++++ EsefexApi/audioprocessing/pcmutil/types.go | 4 ++ .../audioprocessing/s16leReferenceReader.go | 61 +++++++++++++++++++ .../s16leReferenceReader_test.go | 29 +++++++++ EsefexApi/main.go | 3 +- EsefexApi/sounddb/apimockdb/apimockdb.go | 2 +- EsefexApi/sounddb/db.go | 2 +- EsefexApi/sounddb/dbcache/dbcache.go | 6 +- EsefexApi/sounddb/filedb/filedb_test.go | 2 +- EsefexApi/sounddb/filedb/getsoundpcm.go | 4 +- 12 files changed, 130 insertions(+), 12 deletions(-) create mode 100644 EsefexApi/audioprocessing/pcmutil/readpcm.go create mode 100644 EsefexApi/audioprocessing/pcmutil/types.go create mode 100644 EsefexApi/audioprocessing/s16leReferenceReader.go create mode 100644 EsefexApi/audioprocessing/s16leReferenceReader_test.go diff --git a/EsefexApi/audioplayer/discordplayer/vcon/vcon.go b/EsefexApi/audioplayer/discordplayer/vcon/vcon.go index 29e5262..be55be8 100644 --- a/EsefexApi/audioplayer/discordplayer/vcon/vcon.go +++ b/EsefexApi/audioplayer/discordplayer/vcon/vcon.go @@ -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: diff --git a/EsefexApi/audioprocessing/gopusencoder.go b/EsefexApi/audioprocessing/gopusencoder.go index 90a674d..b0b9ff7 100644 --- a/EsefexApi/audioprocessing/gopusencoder.go +++ b/EsefexApi/audioprocessing/gopusencoder.go @@ -1,7 +1,7 @@ package audioprocessing import ( - "encoding/binary" + "esefexapi/audioprocessing/pcmutil" "io" "log" @@ -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) diff --git a/EsefexApi/audioprocessing/pcmutil/readpcm.go b/EsefexApi/audioprocessing/pcmutil/readpcm.go new file mode 100644 index 0000000..66dc445 --- /dev/null +++ b/EsefexApi/audioprocessing/pcmutil/readpcm.go @@ -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 +} diff --git a/EsefexApi/audioprocessing/pcmutil/types.go b/EsefexApi/audioprocessing/pcmutil/types.go new file mode 100644 index 0000000..e91e417 --- /dev/null +++ b/EsefexApi/audioprocessing/pcmutil/types.go @@ -0,0 +1,4 @@ +package pcmutil + +type PcmSample int16 +type Pcm []PcmSample diff --git a/EsefexApi/audioprocessing/s16leReferenceReader.go b/EsefexApi/audioprocessing/s16leReferenceReader.go new file mode 100644 index 0000000..6593392 --- /dev/null +++ b/EsefexApi/audioprocessing/s16leReferenceReader.go @@ -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 +} diff --git a/EsefexApi/audioprocessing/s16leReferenceReader_test.go b/EsefexApi/audioprocessing/s16leReferenceReader_test.go new file mode 100644 index 0000000..c1ce020 --- /dev/null +++ b/EsefexApi/audioprocessing/s16leReferenceReader_test.go @@ -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) +} diff --git a/EsefexApi/main.go b/EsefexApi/main.go index 978b78c..6e48fc4 100644 --- a/EsefexApi/main.go +++ b/EsefexApi/main.go @@ -8,6 +8,7 @@ import ( "esefexapi/sounddb/dbcache" "esefexapi/sounddb/filedb" "esefexapi/util" + "os" "log" @@ -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 { diff --git a/EsefexApi/sounddb/apimockdb/apimockdb.go b/EsefexApi/sounddb/apimockdb/apimockdb.go index 3406b48..c63df7c 100644 --- a/EsefexApi/sounddb/apimockdb/apimockdb.go +++ b/EsefexApi/sounddb/apimockdb/apimockdb.go @@ -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") } diff --git a/EsefexApi/sounddb/db.go b/EsefexApi/sounddb/db.go index b7b6cfe..31fafa0 100644 --- a/EsefexApi/sounddb/db.go +++ b/EsefexApi/sounddb/db.go @@ -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) diff --git a/EsefexApi/sounddb/dbcache/dbcache.go b/EsefexApi/sounddb/dbcache/dbcache.go index 8ee1305..06d0b21 100644 --- a/EsefexApi/sounddb/dbcache/dbcache.go +++ b/EsefexApi/sounddb/dbcache/dbcache.go @@ -16,7 +16,7 @@ type DBCache struct { } type CachedSound struct { - Data []int16 + Data *[]int16 Meta sounddb.SoundMeta } @@ -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, @@ -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() diff --git a/EsefexApi/sounddb/filedb/filedb_test.go b/EsefexApi/sounddb/filedb/filedb_test.go index 9d0eec7..84d2817 100644 --- a/EsefexApi/sounddb/filedb/filedb_test.go +++ b/EsefexApi/sounddb/filedb/filedb_test.go @@ -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() diff --git a/EsefexApi/sounddb/filedb/getsoundpcm.go b/EsefexApi/sounddb/filedb/getsoundpcm.go index 92e6cdd..53bdb1b 100644 --- a/EsefexApi/sounddb/filedb/getsoundpcm.go +++ b/EsefexApi/sounddb/filedb/getsoundpcm.go @@ -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 { @@ -28,5 +28,5 @@ func (f *FileDB) GetSoundPcm(uid sounddb.SoundUID) ([]int16, error) { return nil, err } - return pcm, nil + return &pcm, nil }