How can I make the program automatically exit after audio played over

318 views Asked by At

I'm writing a small tool, it can play audio file in the command/terminal like sox. I'm using bass.dll and Golang syscall for Windows.

Here is my code, files can downloaded from comments, only run on Windows X64.

bass.go on github gist

package main

import (
    "fmt"
    "syscall"
    "time"
    "unsafe"
)

/*
基于 [bass.dll](http://us2.un4seen.com/files/bass24.zip)
和 [Golang syscall](https://github.com/golang/go/wiki/WindowsDLLs) 
实现的命令行版播放器。
*/

type BassLib struct {
    libBass syscall.Handle
    init   uintptr
    free   uintptr
    streamCreateFile uintptr
    channelPlay uintptr
    channelPause uintptr
    channelStop uintptr
}

func (bass *BassLib) LoadBass(bassDllPath string) bool {
    bass.libBass, _ = syscall.LoadLibrary(bassDllPath)
    if bass.libBass == 0 {
        fmt.Println("load library result")
        fmt.Println(bass.libBass)
        return false
    }
    bass.init, _ = syscall.GetProcAddress(bass.libBass, "BASS_Init")
    // BASS_init(device, freq, flags, win, clsid)
    // see http://www.un4seen.com/doc/#bass/BASS_Init.html
    device := 1
    syscall.Syscall6(bass.init, 5, uintptr(device), uintptr(44100), uintptr(0), uintptr(0), uintptr(0), 0)
    return true
}


func StrPtr(s string) uintptr {
    // return uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(s)))
    p, _ := syscall.UTF16PtrFromString(s)
    return uintptr(unsafe.Pointer(p))
}

func (bass *BassLib) PlayFile(filePath string) {
    bass.streamCreateFile, _ = syscall.GetProcAddress(bass.libBass, "BASS_StreamCreateFile")
    // hstream = BASS_StreamCreateFile(mem=0, &file, offset=0, length=0, flags=(A_IsUnicode ? 0x80000000 : 0x40000))
    // see http://www.un4seen.com/doc/#bass/BASS_StreamCreateFile.html
    var bassUnicode uint32 = 0x80000000
    hstream, _, _ := syscall.Syscall6(bass.streamCreateFile, 5,  uintptr(0), StrPtr(filePath), uintptr(0), uintptr(0), uintptr(bassUnicode), 0)
    // bassErrorGetCode, _ := syscall.GetProcAddress(bass.libBass, "BASS_ErrorGetCode")
    // errCode, _, _ := syscall.Syscall(uintptr(bassErrorGetCode), 0, 0, 0, 0)
    // fmt.Println(errCode)
    fmt.Println("hstream")
    fmt.Println(hstream)
    bass.channelPlay, _ = syscall.GetProcAddress(bass.libBass, "BASS_ChannelPlay")
    // BASS_ChannelPlay(hstream)
    // see http://www.un4seen.com/doc/#bass/BASS_ChannelPlay.html
    ret, _, _ := syscall.Syscall(bass.channelPlay, 2, hstream, uintptr(0), 0)
    bassErrorGetCode, _ := syscall.GetProcAddress(bass.libBass, "BASS_ErrorGetCode")
    errCode, _, _ := syscall.Syscall(bassErrorGetCode, 0, 0, 0, 0)
    fmt.Println(errCode)
    fmt.Println(ret)
    // sleep to wait playing mp3 file
    time.Sleep(time.Second * 10)
    // bass.channelPause, _ = syscall.GetProcAddress(bass.libBass, "BASS_ChannelPause")
    // bass.channelStop, _ = syscall.GetProcAddress(bass.libBass, "BASS_ChannelStop")
    // return true
}

func (bass *BassLib) UnLoad() {
    if bass.libBass != 0 {
        bass.free, _ = syscall.GetProcAddress(bass.libBass, "BASS_Free")
        syscall.Syscall(bass.free, 0, 0, 0, 0)
        // BASS_Free()
        // see http://www.un4seen.com/doc/#bass/BASS_Free.html
        syscall.FreeLibrary(bass.libBass)
    }
}

func main() {
    bass := &BassLib{}
    bass.LoadBass("C:\\workspace\\play\\bass.dll")
    bass.PlayFile("C:\\workspace\\play\\sample.mp3")
    bass.UnLoad()
}

There is a big problem:

  • if time.Sleep code (bass.go line 68) not added , no sound played with quickly quit out.
  • When I added time.Sleep(time.Second * 10) code, maybe the audio duration more than 10 seconds.

Is there any possibility that make the program automatically exit after audio played over?

3

There are 3 answers

0
raoyc On BEST ANSWER

Thanks everyone. Can solve the problem with BASS_ChannelGetLength and BASS_ChannelGetPosition functions.

Here is the code:

// +build windows

package main

import (
    "fmt"
    "syscall"
    "time"
    "unsafe"
)

/*
基于 [bass.dll](http://us2.un4seen.com/files/bass24.zip)
和 [Golang syscall](https://github.com/golang/go/wiki/WindowsDLLs)
实现的命令行版播放器。
*/

type (
    BASSErrorGetCode int32
)

const (
    BassUnicode uint32 = 0x80000000 // BASS_UNICODE
    BassSampleFloat uint32 = 256 // BASS_SAMPLE_FLOAT
    BassPosByte uint64 = 0  // BASS_POS_BYTE
)

type BassLib struct {
    libBass syscall.Handle
    init   uintptr
    free   uintptr
    streamCreateFile uintptr
    channelPlay uintptr
    channelPause uintptr
    channelStop uintptr
    channelGetLength uintptr
    channelGetPosition uintptr
    channelBytes2Seconds uintptr
}

func (bass *BassLib) LoadBass(bassDllPath string) bool {
    bass.libBass, _ = syscall.LoadLibrary(bassDllPath)
    if bass.libBass == 0 {
        fmt.Println("Load `bass.dll` library failed!")
        errCode := bass.GetBassErrorGetCode()
        fmt.Println("Bass_Init failed!")
        fmt.Println(errCode)
        return false
    }
    bass.init, _ = syscall.GetProcAddress(bass.libBass, "BASS_Init")
    // BASS_Init(device, freq, flags, win, clsid)
    // see http://www.un4seen.com/doc/#bass/BASS_Init.html
    device := 1
    r, _, _ := syscall.Syscall6(bass.init, 5, uintptr(device), uintptr(44100), uintptr(0), uintptr(0), uintptr(0), 0)
    // var ret = *(* int)(unsafe.Pointer(&r))
    if r == 0 {
        errCode := bass.GetBassErrorGetCode()
        fmt.Println("Bass_Init failed!")
        fmt.Println(errCode)
        return false
    }
    return true
}


func StrPtr(s string) uintptr {
    p, _ := syscall.UTF16PtrFromString(s)
    return uintptr(unsafe.Pointer(p))
}

func (bass *BassLib) PlayFile(filePath string) {
    bass.streamCreateFile, _ = syscall.GetProcAddress(bass.libBass, "BASS_StreamCreateFile")
    // hStream = BASS_StreamCreateFile(mem=0, &file, offset=0, length=0, flags=(A_IsUnicode ? 0x80000000 : 0x40000))
    // see http://www.un4seen.com/doc/#bass/BASS_StreamCreateFile.html
    hStream, _, _ := syscall.Syscall6(bass.streamCreateFile, 5,  uintptr(0), StrPtr(filePath), uintptr(0), uintptr(0), uintptr(BassUnicode|BassSampleFloat), 0)
    bass.channelPlay, _ = syscall.GetProcAddress(bass.libBass, "BASS_ChannelPlay")
    // BASS_ChannelPlay(hStream)
    // see http://www.un4seen.com/doc/#bass/BASS_ChannelPlay.html
    r, _, _ := syscall.Syscall(bass.channelPlay, 2, hStream, uintptr(0), 0)
    if r == 1 {
        totalDuration := bass.GetAudioByteLength(hStream)
        // currentPos := bass.GetAudioCurrentBytePosition(hStream)
        fmt.Println(totalDuration)
        // fmt.Println(currentPos)
        time.Sleep(time.Second*1)
        for {
            currentPos := bass.GetAudioCurrentBytePosition(hStream)
            if currentPos >= totalDuration {
                break
            }
        }
    } else {
        errCode := bass.GetBassErrorGetCode()
        fmt.Println("Bass_ChannelPlay failed!")
        fmt.Println(errCode)
    }
}

func (bass *BassLib) GetBassErrorGetCode() BASSErrorGetCode {
    bassErrorGetCode, _ := syscall.GetProcAddress(bass.libBass, "BASS_ErrorGetCode")
    // BASS_ErrorGetCode()
    // BASS_OK              BASSErrorGetCode = 0    // all is OK
    // BASS_ERROR_MEM       BASSErrorGetCode = 1    // memory error
    // ...
    // see http://www.un4seen.com/doc/#bass/BASS_ErrorGetCode.html
    errCode, _, _ := syscall.Syscall(bassErrorGetCode, 0, 0, 0, 0)
    var iErrCode = *(*BASSErrorGetCode)(unsafe.Pointer(&errCode))
    return iErrCode
}

func (bass *BassLib) GetAudioByteLength(handle uintptr) uintptr {
    // (QWORD) BASS_ChannelGetLength(handle=hStream, mode)
    // see http://www.un4seen.com/doc/#bass/BASS_ChannelGetLength.html
    bass.channelGetLength, _ = syscall.GetProcAddress(bass.libBass, "BASS_ChannelGetLength")
    len, _, _ := syscall.Syscall(bass.channelGetLength, 2, handle,  uintptr(BassPosByte), 0)
    return len
}


func (bass *BassLib) GetAudioCurrentBytePosition(handle uintptr) uintptr {
    // BASS_ChannelGetPosition(handle=hStream, mode)
    // see http://www.un4seen.com/doc/#bass/BASS_ChannelGetPosition.html
    bass.channelGetPosition, _ = syscall.GetProcAddress(bass.libBass, "BASS_ChannelGetPosition")
    pos, _, _ := syscall.Syscall(bass.channelGetPosition, 2, handle,  uintptr(BassPosByte), 0)
    return pos
}

func (bass *BassLib) GetChannelBytes2Seconds(handle uintptr, pos uintptr) uintptr {
    // BASS_ChannelBytes2Seconds(handle=hStream, pos)
    // see http://www.un4seen.com/doc/#bass/BASS_ChannelBytes2Seconds.html
    // bass.channelBytes2Seconds, _ = syscall.GetProcAddress(bass.libBass, "BASS_ChannelBytes2Seconds")
    len, _, _ := syscall.Syscall(bass.channelBytes2Seconds, 2, handle, pos, 0)
    return len
}




func (bass *BassLib) UnLoad() {
    if bass.libBass != 0 {
        bass.free, _ = syscall.GetProcAddress(bass.libBass, "BASS_Free")
        syscall.Syscall(bass.free, 0, 0, 0, 0)
        // BASS_Free()
        // see http://www.un4seen.com/doc/#bass/BASS_Free.html
        syscall.FreeLibrary(bass.libBass)
    }
}

func main() {
    bass := &BassLib{}
    bass.LoadBass("C:\\workspace\\play\\bass.dll")
    bass.PlayFile("C:\\workspace\\play\\sample.mp3")
    bass.UnLoad()
}

Also you can get at https://gist.github.com/ycrao/e7d1df181f870091b4a6d298d6ea2770#file-bass_play-go-L81-L91 .

1
Tho Quach On

I think you can use defer keyword of golang to trigger an exit when play function have done.

You can refer here: A Tour of Go | Defer Or here: Golang Blog | Defer

package main

import "fmt"

func main() {
    defer fmt.Println("world")

    fmt.Println("hello")
}

==========
$ go run main.go
hello
world
1
vgru On

I would strongly recommend going through Effective Go on the golang.org website (it's not a long read, I am sure you can go through all the ideas in a single day), paying special attention to the concurrency section.

The whole idea behind Go is to make concurrency and asynchronous programming easy, and it uses several language constructs (channels, goroutines) especially designed to help you handle these cases.

For example, you can use a channel to signal:

func main() {    
    
    // end signal
    finished := make(chan bool)
    
    // create and run a goroutine
    go func() {
       // do your bass stuff here
       ...
       
       // send a signal
       finished <- true           
    }()
    
    // wait
    <-finished
}

A common pattern is also to pass the channel to the function doing the job:

func main() {    
    
    // end signal
    finished := make(chan bool)
    
    // PlayFile is responsible for
    // signalling 'finished' when done
    go PlayFile(someFile, finished);
    
    // wait
    <-finished
}

Or, if you have multiple routines, you will use a WaitGroup:

func main() {

    // create the waitgroup
    var wg sync.WaitGroup

    // number of semaphores
    wg.Add(1)

    go func() {
       // notify WaitGroup when done
       // (the 'defer' keyword means
       // this call will be executed before
       // returning from the method)
       defer wg.Done()
       
       // do your bass stuff here
       ...
    }()

    wg.Wait()
}