1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
|
package pulse
//#include "dde-pulse.h"
import "C"
import (
"fmt"
"runtime"
"unsafe"
)
// ONLY C file and below function can use C.pa_threaded_mainloop_lock/unlock
type safeDoCtx struct {
fn func()
loop *C.pa_threaded_mainloop
}
var pendingSafeDo = make(chan safeDoCtx)
func startHandleSafeDo() {
// Move all safeDo fn to here to avoid creating too many OS Thread.
// Because GOMAXPROC only apply to go-runtime.
for c := range pendingSafeDo {
if c.fn != nil {
runtime.LockOSThread()
C.pa_threaded_mainloop_lock(c.loop)
c.fn()
C.pa_threaded_mainloop_unlock(c.loop)
runtime.UnlockOSThread()
}
}
}
func init() {
// TODO: encapsulate the logic to Context and adding Start/Stop logic.
go startHandleSafeDo()
}
func freeContext(ctx *Context) {
runtime.LockOSThread()
C.pa_threaded_mainloop_lock(ctx.loop)
// The pa_context_unref must be protected.
// This operation will cancel all pending operations which will touch mainloop object.
C.pa_context_unref(ctx.ctx)
C.pa_threaded_mainloop_unlock(ctx.loop)
// There no need to call pa_threaded_mainloop_stop.
C.pa_threaded_mainloop_free(ctx.loop)
runtime.UnlockOSThread()
ctx.loop = nil
ctx.ctx = nil
}
// safeDo invoke an function with lock
func (c *Context) safeDo(fn func()) {
pendingSafeDo <- safeDoCtx{fn, c.loop}
}
func (c *Context) AddEventChan(ch chan<- *Event) {
c.mu.Lock()
c.eventChanList = append(c.eventChanList, ch)
c.mu.Unlock()
}
func (c *Context) RemoveEventChan(ch chan<- *Event) {
c.mu.Lock()
var newList []chan<- *Event
for _, ch0 := range c.eventChanList {
if ch0 != ch {
newList = append(newList, ch0)
}
}
c.eventChanList = newList
c.mu.Unlock()
}
func (c *Context) AddStateChan(ch chan<- int) {
c.mu.Lock()
c.stateChanList = append(c.stateChanList, ch)
c.mu.Unlock()
}
func (c *Context) RemoveStateChan(ch chan<- int) {
c.mu.Lock()
var newList []chan<- int
for _, ch0 := range c.stateChanList {
if ch0 != ch {
newList = append(newList, ch0)
}
}
c.stateChanList = newList
c.mu.Unlock()
}
// ALL of below functions are invoked from pulse's mainloop thread.
// So
// 1. Don't hold go-runtime lock in current thread.
// 2. Don't hold pa_threaded_mainloop in current thread.
//
// NOTE:
// In pa_threaded_mainloop, _prepare_ and _dispatch_ is running
// under lock. Only phrase _poll_ is release the lock. So callback
// must not try to hold lock.
//export go_handle_changed
func go_handle_changed(facility int, event_type int, idx uint32) {
event := &Event{
Facility: facility,
Type: event_type,
Index: idx,
}
ctx := GetContext()
ctx.mu.Lock()
for _, eventChan := range ctx.eventChanList {
select {
case eventChan <- event:
default:
}
}
ctx.mu.Unlock()
}
//export go_handle_state_changed
func go_handle_state_changed(state int) {
ctx := GetContext()
ctx.mu.Lock()
for _, stateChan := range ctx.stateChanList {
select {
case stateChan <- state:
default:
}
}
ctx.mu.Unlock()
}
//export go_update_volume_meter
func go_update_volume_meter(source_index uint32, sink_index uint32, v float64) {
sourceMeterLock.RLock()
cb, ok := sourceMeterCBs[source_index]
sourceMeterLock.RUnlock()
if ok && cb != nil {
cb(v)
}
}
//export go_receive_some_info
func go_receive_some_info(cookie int64, infoType int, info unsafe.Pointer, eol int) {
ck := fetchCookie(cookie)
if ck == nil {
if eol == 0 {
// Sometimes pulseaudio will send eol=1 to pa_context_get_*_by_index/name.
fmt.Println("Warning: recieve_some_info with nil cookie", cookie, infoType, info)
}
return
}
switch {
case eol == 1:
ck.EndOfList()
case eol == 0:
// 1. the Feed will be blocked until the ck creator received.
// 2. the creator of ck may be invoked under PendingCallback
// so this must not be moved to PendingCallback.
paInfo := NewPaInfo(info, infoType)
ck.Feed(paInfo)
case eol < 0:
ck.Failed()
}
}
|