如何让程序播放完后自动退出

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

我正在写一个小工具,它可以像 sox 一样播放 command/terminal 中的音频文件。我正在为 Windows.

使用 bass.dll 和 Golang syscall

这是我的代码,文件可以从评论中下载,仅运行 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()
}

有个大问题:

有没有办法让程序播放完后自动退出?

我认为你可以使用 golang 的 defer 关键字来在播放功能完成后触发退出。

您可以参考这里:A Tour of Go | Defer 或者在这里:Golang Blog | Defer

package main

import "fmt"

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

    fmt.Println("hello")
}

==========
$ go run main.go
hello
world

我强烈建议通过 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

Go 背后的整个想法是让并发和异步编程变得简单,它使用了多种语言结构(通道、goroutines)专门设计来帮助您处理这些情况。

例如,您可以使用一个频道来发信号:

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
}

一个常见的模式也是将通道传递给执行此工作的函数:

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

或者,如果您有多个例程,您将使用 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()
}

谢谢大家。可以用BASS_ChannelGetLengthBASS_ChannelGetPosition函数解决问题。

代码如下:

// +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()
}

您也可以在 https://gist.github.com/ycrao/e7d1df181f870091b4a6d298d6ea2770#file-bass_play-go-L81-L91 获得。