mirror of
https://github.com/pion/mediadevices.git
synced 2026-04-22 15:57:27 +08:00
(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:
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
Reference in New Issue
Block a user