cgocgolibsndfile

How can I open a sound file loaded in memory with libsndfile?


To understand this, I want to tell the following scenario: I designed a game and finished it. I encrypted the audio data with AES. I will then decrypt these encrypted files while the game is opening. These files are encoded in bytes in memory. For example I want to create buffers for OpenAL.

NOTE: no encryption functions here. Because my example would be complicated and difficult.

Here is a simple sketch of what I want to do with CGo and Go:

package main

// #cgo windows  CFLAGS:  -DGO_WINDOWS -I.
// #cgo windows  LDFLAGS: -L. -lsndfile-1
/*
    #include <stdio.h>
    #include <stdlib.h>
    #include <fcntl.h>
    #include <math.h>
    #include <string.h>
#include <errno.h>

    #include "sndfile.h"

    typedef struct {
        sf_count_t offset, length ;
        unsigned char data [16 * 1024] ;
    } VIO_DATA;

    static sf_count_t
    vfget_filelen(void *user_data) {
    VIO_DATA *vf = (VIO_DATA *) user_data;
    return vf->length;
}

    static sf_count_t
    vfseek(sf_count_t offset, int whence, void *user_data) {
        VIO_DATA *vf = (VIO_DATA *) user_data ;

    switch (whence) {
        case SEEK_SET :
            vf->offset = offset ;
            break ;

        case SEEK_CUR :
            vf->offset = vf->offset + offset ;
            break ;

        case SEEK_END :
            vf->offset = vf->length + offset ;
            break ;
        default :
            break ;
        };

    return vf->offset;
}

    static sf_count_t
    vfread (void *ptr, sf_count_t count, void *user_data) {
    VIO_DATA *vf = (VIO_DATA *) user_data ;
    if (vf->offset + count > vf->length)
        count = vf->length - vf->offset ;

    memcpy (ptr, vf->data + vf->offset, count) ;
    vf->offset += count ;

    return count ;
}

    static sf_count_t
    vfwrite (const void *ptr, sf_count_t count, void *user_data) {
        VIO_DATA *vf = (VIO_DATA *) user_data ;
    if(vf->offset >= sizeof(vf->data))
        return 0;

        if (vf->offset + count > sizeof(vf->data))
            count = sizeof (vf->data) - vf->offset;

        memcpy(vf->data + vf->offset, ptr, (size_t) count);
    vf->offset += count;

    if(vf->offset > vf->length)
        vf->length = vf->offset ;

    return count;
}

    static sf_count_t
    vftell(void *user_data) {
        VIO_DATA *vf = (VIO_DATA *) user_data ;
        return vf->offset;
    }



    void voidTest(void *data) {
        printf("value in c: %s \n", (char*)data);
        static VIO_DATA vio_data ;
//      static short fdata [256];

        SF_VIRTUAL_IO vio ;
        SNDFILE * file ;
        SF_INFO sfinfo ;

        vio.get_filelen = vfget_filelen;
        vio.seek = vfseek ;
        vio.read = vfread;
        vio.write = vfwrite;
        vio.tell = vftell;

        vio_data.offset = 0;
        vio_data.length = 0;

        memset (&sfinfo, 0, sizeof (sfinfo));
        sfinfo.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16;


if((file = sf_open_virtual (&vio, SFM_READ, &sfinfo, &vio_data)) == NULL) {
            printf("Sndfile error: %s\n", sf_strerror(file));
            exit(1);
//      return;
        }
        // !As an example I want to know the number of channels etc.
        printf("Channel: %i\n", sfinfo.channels);
        sf_close(file);
        }
*/
import "C"

import (
        "io/ioutil"
        "fmt"
    "unsafe"
)

func main() {

    byt, err := ioutil.ReadFile("birds22.wav")
    if err != nil {
        fmt.Println(err)
        return
        }
    var v unsafe.Pointer
    v = (unsafe.Pointer)(&byt[0])
    C.voidTest(v)
        // fmt.Println("byte:", string(byt))
    fmt.Println("No crash: test successfuly")
}

Project folder:

My build command: go build .

I am not an experienced C developer. That's why I work with CGo. But I will try to adapt C codes to CGO, which explains how to do this.

The sources I use and the codes I try to adapt: libsndfile virtualio test gosndfile virtualio test


Solution

  • Here is a solution. If you want to use it, you must put the libsndfile and openal 'libraries in the correct location.
    Tested with Go 15.2 64 bit.
    I am a Windows user.
    The file is read from virtual sources, the information is printed (channel, frames, seekable etc.) and played with OpenAL. And the issue on GitHub. Reading can be helpful. #16

    main go code:

    package main
    
    // #cgo CFLAGS: -Wall -O3 -Iinclude
    // #cgo LDFLAGS: -O3 -L. -llibsndfile-1 -lopenal32
    /*
        #include "extras.h"
    
        #include "sndfile.h"
        #include "stdlib.h"
    
        #include "AL/al.h"
        #include "AL/alc.h"
        #include "AL/alext.h"
    */
    import "C"
    
    import (
        "fmt"
        "unsafe"
        "io/ioutil"
        "bytes"
        "time"
        "os"
        "./oal"
    )
    
    type MyData struct {
        MyBytes     *bytes.Reader
        Count       int64
    }
    
    
    func main() {
    fullFileByte, err := ioutil.ReadFile(os.Args[1]); errHandler(err)
        reader := bytes.NewReader(fullFileByte)
    
    
        // file info (Channels, frames, seekable etc...)
        var myInfo Info
    
        data := &MyData{MyBytes: reader, Count: 0}
    
    
        getLen :=  func() int64 {
            l := data.MyBytes.Len()
            println("Lenght:", l)
            return int64(l)
            }
    
        vRead := func(o []byte) int64 {
            //println("Read:", data.Count)
            i, _ := data.MyBytes.Read(o) // ; errHandler(err)
            data.Count += int64(i)
            return int64(i)
        }
    
    seek := func(offset int64, whence int) int64 {
            println("Seek:", data.Count)
            goWhence := whence
            data.Count, _ = data.MyBytes.Seek(offset, goWhence) // ; errHandler(err)
            return data.Count
        }
    
    tell := func() int64 {
            println("Tell: ", data.Count)
            return data.Count
        }
    
    
    globVB.GetFileLen = getLen
        globVB.Read = vRead
        globVB.Seek = seek
    globVB.Tell = tell
    
    
        f := OpenVirtual(ModeRead, &myInfo)
    
        fmt.Println("Channel:", myInfo.Channels, "\nFrames:", myInfo.Frames, "\nFormat:", myInfo.Format, "\nSections:", myInfo.Sections, "\nSample Rate:", myInfo.Samplerate, "\nSeekable:", myInfo.Seekable)
    
        s := Source{}
    
        s.Create(uint32(C.CreateVirtualBuffer(f.SFile, *myInfo.fromGoToCInfo())))
            s.Play()
    
        for {
    
            time.Sleep(500 * time.Millisecond)
            }
    }
    
    
    func OpenVirtual(mode FMode, info* Info) File { // File
    
    
    We're tricking the libsndfile. It's actually unnecessary code.
        var vb *C.VirtualCallbacks
    
    
        var file File
    
        // Go → C
        cInfo := info.fromGoToCInfo()
    
        cVirtualIO := C.NewVirtualIO()
    
    
    
        file.SFile = C.sf_open_virtual(cVirtualIO, C.int(mode), cInfo, (unsafe.Pointer)(vb))
    
        if file.SFile == nil {
            panic(C.GoString(C.sf_strerror(file.SFile)))
        }
        *info = fromCToGo(cInfo)
        return file
    }
    
    
    
    type File struct {
        SFile*      C.SNDFILE
    }
    
    
    
    type Info struct {
        Frames      int64
        Samplerate      int
        Channels        int
        Format      int
        Sections        int
        Seekable        int
    }
    
    func (s Info) fromGoToCInfo() *C.SF_INFO {
        val := new(C.SF_INFO)
        val.frames = C.sf_count_t(s.Frames)
            val.samplerate = C.int(s.Samplerate)
        val.channels = C.int(s.Channels)
        val.format = C.int(s.Format)
        val.sections = C.int(s.Sections)
        val.seekable = C.int(s.Seekable)
        return val
    }
    
    
    type SFile C.SNDFILE
    
    
    
    func fromCToGo(info* C.SF_INFO) Info {
        val := Info{}
        val.Frames = int64(info.frames)
        val.Samplerate = int(info.samplerate)
        val.Channels = int(info.channels)
        val.Format = int(info.format)
        val.Sections = int(info.sections)
        val.Seekable = int(info.seekable)
        return val
    }
    
    
    // File modes: read, write and readwrite
    type FMode int
    
    const (
        ModeRead        FMode = C.SFM_READ
        ModeWrite       FMode = C.SFM_WRITE
        ModeReadWrite       FMode = C.SFM_RDWR
    )
    
    func errHandler(e error) {
    if e != nil {
            panic(e)
    }
    }
    
    
    func init() {
        device, err := oal.OpenDevice("")
        errHandler(err)
        ctx, err := oal.CreateContext(device, nil)
        errHandler(err)
        oal.MakeContextCurrent(ctx)
    }
    
    
    
    type TGetFileLen func() int64
    
    type TVioSeek func(offset int64, whence int) int64
    
    type TVioRead func(o []byte) int64
    
    type TVioWrite func( ptr unsafe.Pointer, count int64, user_data unsafe.Pointer)
    
    type TVioTell func() int64
    
    
    type SVirtualIO struct {
        GetFileLen      TGetFileLen
        Seek        TVioSeek
        Read        TVioRead
        Write       TVioWrite
        Tell        TVioTell
        // Data     interface{}
    }
    
    var globVB  SVirtualIO
    
    //export goVirtualRead
    func goVirtualRead(buffPtr unsafe.Pointer, count int64, data unsafe.Pointer) int64 {
        byteBuff := (*[1 << 31]byte) (buffPtr)[0:count]
    
        return globVB.Read(byteBuff)
        }
    
    
    //export goGetLen
    func goGetLen(userData unsafe.Pointer) int64 {
    
        return globVB.GetFileLen()
    }
    
    //export goVirtualSeek
    func goVirtualSeek(offset int64,  whence int, userData unsafe.Pointer) int64 {
    
        return globVB.Seek(offset, whence)
    }
    
    //export goVirtualTell
    func goVirtualTell(userData unsafe.Pointer) int64 {
        
        return globVB.Tell()
    }
    
    type Source struct {
        Source      C.ALuint
        Buffer      C.ALuint
    }
    
    
    func (s Source) Delete() {
        C.alDeleteSources(1, &s.Source)
        C.alDeleteBuffers(1, &s.Buffer)
    }
    
    func (s* Source) Create(b uint32) {
            var source C.ALuint
        var buffer C.ALuint = C.ALuint(b)
        source = 0
        C.alGenSources(1, &source)
    
        C.alSourcei(source, C.AL_BUFFER, C.ALint(buffer))
    
        s.Source = source
        s.Buffer = buffer
    }
    
    func (s Source) Play() {
            C.alSourcePlay(s.Source)
    }
    

    extras.c:

    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include "extras.h"
    #include "_cgo_export.h"
    
        #include "sndfile.h"
    
    #include "AL/al.h"
    #include "AL/alc.h"
    
        sf_count_t
        virtualRead(void *ptr, sf_count_t count, void *userData) {
            return goVirtualRead(ptr, count, userData);
        }
    
    
        sf_count_t
        virtualGetFileLen(void *udata) {
            return goGetLen(udata);
        }
    
    
        sf_count_t
    virtualSeek(sf_count_t offset, int whence, void *user_data) {
        return goVirtualSeek(offset, whence, user_data);
    }
    
    sf_count_t
        virtualTell(void *userData) {
            return goVirtualTell(userData);
    }
    
    SF_VIRTUAL_IO*
    NewVirtualIO() {
        static SF_VIRTUAL_IO sndVirtualIO;
    
        sndVirtualIO.read = virtualRead;
    
        sndVirtualIO.get_filelen  = virtualGetFileLen;
        sndVirtualIO.seek = virtualSeek;
        sndVirtualIO.tell = virtualTell;
    
    //  sndVirtualIO.write= virtualWrite;
        return &sndVirtualIO;
    }
    
    
    
    ALuint
    CreateVirtualBuffer(SNDFILE *file, SF_INFO info) {
        ALenum err, format;
        ALuint buffer;
        SNDFILE *sndfile;
        SF_INFO sfinfo;
        sfinfo = info;
    
        short *membuf;
        sf_count_t num_frames;
        ALsizei num_bytes;
    
        sndfile = file;
        if(!sndfile)  {
            return 0;
        }
        if(sfinfo.channels == 1)
            format = AL_FORMAT_MONO16;
        else if(sfinfo.channels == 2)
            format = AL_FORMAT_STEREO16;
        else {
            sf_close(sndfile);
            return 0;
        }
    
        membuf = malloc((size_t)(sfinfo.frames * sfinfo.channels) * sizeof(short));
    
        num_frames = sf_readf_short(sndfile, membuf, sfinfo.frames);
        if(num_frames < 1)
        {
            free(membuf);
            sf_close(sndfile);
            return 0;
        }
        num_bytes = (ALsizei)(num_frames * sfinfo.channels) * (ALsizei)sizeof(short);
    
        buffer = 0;
        alGenBuffers(1, &buffer);
        alBufferData(buffer, format, membuf, num_bytes, sfinfo.samplerate);
    
        free(membuf);
        sf_close(sndfile);
    
        err = alGetError();
        if(err != AL_NO_ERROR) {
            if(buffer && alIsBuffer(buffer))
                alDeleteBuffers(1, &buffer);
            return 0;
        }
    
        return buffer;
    }
    
    

    extras.h (header file):

    #ifndef EXTRAS_H
    #define EXTRAS_H
    
    
    #include "sndfile.h"
    
    #include "AL/al.h"
    #include "AL/alc.h"
    
    // **redundant code. Because NULL is not accepted. :)
    
    typedef sf_count_t (*goreadfunc)(void* sf_count_t, void*);
    
    struct VirtualCallbacks {
        goreadfunc      vRead;
        sf_vio_get_filelen      vGetFileLen;
        sf_vio_seek     vSeek;
        sf_vio_write        vWrite;
    
    };
    
    typedef struct VirtualCallbacks VirtualCallbacks;
    
    
    
    sf_count_t goVirtualRead(void *ptr, sf_count_t count, void *user_data);
    
    
    SF_VIRTUAL_IO*
    NewVirtualIO(void);
    
    ALuint
    CreateVirtualBuffer(SNDFILE *file, SF_INFO info);
    
    #endif