(3/3) Fix concurrency webcam windows (#684)

* Import wmcodecdsp and try to use their nv12 const

* Remove ifndef

* Enhance getCameraName function to retrieve friendly name from IPropertyBag, with fallback to display name. Update LDFLAGS in camera_windows.go to include oleaut32 library.

* Don't mistake label for friendly name

* Initialize COM for thread safety in Open and VideoRecord methods of camera_windows.go

* Refactor camera handling in camera_windows.go and camera_windows.cpp

- Introduced mutex for thread safety in camera struct to protect shared resources.
- Updated Open method to initialize done channel and manage camera state.
- Enhanced Close method to ensure proper cleanup and prevent double closing.
- Improved resolution listing logic in camera_windows.cpp by ensuring media types are freed correctly.
- Changed memory deletion from single to array deletion for camera properties.

* Fix issues post-rebase

* Refactor Close method

* Transfer golang buffer management to c to avoid gc

* Fix race condition in imageCallback by fixing unlock code location
This commit is contained in:
sean yu
2026-04-21 09:25:31 -04:00
committed by GitHub
parent 63c09cb1f4
commit 3d4fceab3d
2 changed files with 87 additions and 23 deletions
+1 -2
View File
@@ -253,7 +253,6 @@ int listResolution(camera* cam, const char** errstr)
ICaptureGraphBuilder2* captureGraph = nullptr; ICaptureGraphBuilder2* captureGraph = nullptr;
IAMStreamConfig* config = nullptr; IAMStreamConfig* config = nullptr;
IPin* src = nullptr; IPin* src = nullptr;
LPOLESTR name;
if (!selectCamera(cam, &moniker, errstr)) if (!selectCamera(cam, &moniker, errstr))
{ {
@@ -623,7 +622,7 @@ void freeCamera(camera* cam)
if (cam->props) if (cam->props)
{ {
delete cam->props; delete[] cam->props;
cam->props = nullptr; cam->props = nullptr;
} }
} }
+86 -21
View File
@@ -25,11 +25,17 @@ var (
) )
type camera struct { type camera struct {
name string name string
cam *C.camera // mu protects the fields under as per the mutex hat convention.
ch chan []byte mu sync.Mutex
buf []byte cam *C.camera
bufGo []byte closed bool
ch chan []byte
done chan struct{}
cbuf unsafe.Pointer // C.malloc'd buffer for DirectShow writes
bufLen int // byte length of cbuf
bufGo []byte
} }
func init() { func init() {
@@ -83,7 +89,20 @@ func DestroyObserver() error {
} }
func (c *camera) Open() error { func (c *camera) Open() error {
// COM is per-thread on Windows. Go goroutines can run on any OS thread,
// so ensure COM is initialized on the current one before making COM calls.
C.CoInitializeEx(nil, C.COINIT_MULTITHREADED)
c.mu.Lock()
defer c.mu.Unlock()
if c.cam != nil {
return fmt.Errorf("camera already open")
}
c.ch = make(chan []byte) c.ch = make(chan []byte)
c.done = make(chan struct{})
c.closed = false
c.cam = &C.camera{ c.cam = &C.camera{
name: C.CString(c.name), name: C.CString(c.name),
} }
@@ -91,6 +110,7 @@ func (c *camera) Open() error {
var errStr *C.char var errStr *C.char
if C.listResolution(c.cam, &errStr) != 0 { if C.listResolution(c.cam, &errStr) != 0 {
C.free(unsafe.Pointer(c.cam.name)) C.free(unsafe.Pointer(c.cam.name))
c.cam = nil
return fmt.Errorf("failed to open device: %s", C.GoString(errStr)) return fmt.Errorf("failed to open device: %s", C.GoString(errStr))
} }
@@ -101,36 +121,74 @@ func (c *camera) Open() error {
func imageCallback(cam uintptr) { func imageCallback(cam uintptr) {
callbacksMu.RLock() callbacksMu.RLock()
cb, ok := callbacks[uintptr(unsafe.Pointer(cam))] cb, ok := callbacks[uintptr(unsafe.Pointer(cam))]
callbacksMu.RUnlock()
if !ok { if !ok {
callbacksMu.RUnlock()
return return
} }
copy(cb.bufGo, unsafe.Slice((*byte)(cb.cbuf), cb.bufLen))
callbacksMu.RUnlock()
copy(cb.bufGo, cb.buf) select {
cb.ch <- cb.bufGo case cb.ch <- cb.bufGo:
case <-cb.done:
}
} }
func (c *camera) Close() error { func (c *camera) Close() error {
callbacksMu.Lock() c.mu.Lock()
key := uintptr(unsafe.Pointer(c.cam)) if c.closed {
if _, ok := callbacks[key]; ok { c.mu.Unlock()
delete(callbacks, key) return nil
} }
callbacksMu.Unlock() c.closed = true
close(c.ch) cam := c.cam
c.cam = nil
cbuf := c.cbuf
c.cbuf = nil
done := c.done
ch := c.ch
c.mu.Unlock()
if c.cam != nil { // Remove from callbacks map so no new imageCallback calls find this cam
C.free(unsafe.Pointer(c.cam.name)) callbacksMu.Lock()
C.freeCamera(c.cam) delete(callbacks, uintptr(unsafe.Pointer(cam)))
c.cam = nil callbacksMu.Unlock()
if done != nil {
close(done)
}
if cam != nil {
C.free(unsafe.Pointer(cam.name))
C.freeCamera(cam)
}
C.free(cbuf)
if ch != nil {
close(ch)
} }
return nil return nil
} }
func (c *camera) VideoRecord(p prop.Media) (video.Reader, error) { func (c *camera) VideoRecord(p prop.Media) (video.Reader, error) {
C.CoInitializeEx(nil, C.COINIT_MULTITHREADED)
c.mu.Lock()
defer c.mu.Unlock()
if c.cam == nil {
return nil, fmt.Errorf("camera not open")
}
nPix := p.Width * p.Height nPix := p.Width * p.Height
c.buf = make([]byte, nPix*2) bufSize := nPix * 2
c.bufGo = make([]byte, nPix*2) c.cbuf = C.malloc(C.size_t(bufSize))
if c.cbuf == nil {
return nil, fmt.Errorf("failed to allocate frame buffer")
}
c.bufLen = bufSize
c.bufGo = make([]byte, bufSize)
c.cam.width = C.int(p.Width) c.cam.width = C.int(p.Width)
c.cam.height = C.int(p.Height) c.cam.height = C.int(p.Height)
@@ -141,10 +199,12 @@ func (c *camera) VideoRecord(p prop.Media) (video.Reader, error) {
c.cam.fcc = fourccYUY2 c.cam.fcc = fourccYUY2
} }
c.cam.buf = C.size_t(uintptr(unsafe.Pointer(&c.buf[0]))) c.cam.buf = C.size_t(uintptr(c.cbuf))
var errStr *C.char var errStr *C.char
if C.openCamera(c.cam, &errStr) != 0 { if C.openCamera(c.cam, &errStr) != 0 {
C.free(c.cbuf)
c.cbuf = nil
return nil, fmt.Errorf("failed to open device: %s", C.GoString(errStr)) return nil, fmt.Errorf("failed to open device: %s", C.GoString(errStr))
} }
@@ -184,6 +244,11 @@ func (c *camera) VideoRecord(p prop.Media) (video.Reader, error) {
} }
func (c *camera) Properties() []prop.Media { func (c *camera) Properties() []prop.Media {
c.mu.Lock()
defer c.mu.Unlock()
if c.cam == nil {
return nil
}
properties := []prop.Media{} properties := []prop.Media{}
for i := 0; i < int(c.cam.numProps); i++ { for i := 0; i < int(c.cam.numProps); i++ {
p := C.getProp(c.cam, C.int(i)) p := C.getProp(c.cam, C.int(i))