letmaik / pyvirtualcam

🎥 Send frames to a virtual camera from Python
GNU General Public License v2.0
471 stars 50 forks source link

Cannot open installed OBS camera when running from a service #65

Closed agi1512 closed 3 years ago

agi1512 commented 3 years ago

Decription Using pywin32==301 and auto-py-to-exe==2.9.0 to install pyvirtualcam as Windows service, I'm getting following error: 'obs' backend: virtual camera output could not be started

To Reproduce

def main():
    try:
        with pyvirtualcam.Camera(width=1280, height=720, fps=20, backend='obs') as cam:
            print(f'Using virtual camera: {cam.device}\n')
            frame = np.zeros((cam.height, cam.width, 3), np.uint8)  # RGB
            while True:
                frame[:] = cam.frames_sent % 255  # grayscale animation
                cam.send(frame)
                cam.sleep_until_next_frame()
    except Exception as e:
        with open(r"C:\ProgramData\error.txt", 'a') as f:
            f.write(str(e)+'\n')

class Service(win32serviceutil.ServiceFramework):
    _svc_name_ = "TEST_VID0"
    _svc_display_name_ = "test video zeros 0"

    def __init__(self, *args):
        # win32serviceutil.ServiceFramework.__init__(self, *args)
        super().__init__(*args)
        self.hWaitStop = win32event.CreateEvent(None, 0, 0, None)
        socket.setdefaulttimeout(5)
        self.stop_requested = False

    def SvcStop(self):
        self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
        win32event.SetEvent(self.hWaitStop)
        self.ReportServiceStatus(win32service.SERVICE_STOPPED)
        self.stop_requested = True

    def SvcDoRun(self):
        self.ReportServiceStatus(win32service.SERVICE_RUNNING)
        self.main()

    def main(self):
        main()

if __name__ == '__main__':
    if len(sys.argv) == 1:
        servicemanager.Initialize()
        servicemanager.PrepareToHostSingle(Service)
        servicemanager.StartServiceCtrlDispatcher()
    else:
        win32serviceutil.HandleCommandLine(Service)

NOTE: It's not first time when i'm using pywin32 to install python programs as a service, it definetely works, provided code is a default code to create any service which main loop lies in main() function. To delete service use file.exe remove. My guess is that system services somehow cannot access camera from where they are started, but i have no clue why and thats why i ask for help, any comment on this would be helpful.

letmaik commented 3 years ago

I'm not exactly sure why it fails at this stage already, but even if it worked, I doubt it is possible to then connect to this camera from a client. The reason is that there is a split between local (logged in users) and global (kernel, services, etc.) namespace when it comes to shared memory (which is what the OBS virtual camera uses), see also https://docs.microsoft.com/en-us/windows/win32/termserv/kernel-object-namespaces. I think services always create in the global namespace, but logged in users always under the local one, except if they override this behavior. Since in the context of OBS it doesn't make sense that the virtual camera data comes from somewhere that is not the user session (like a service), the OBS camera device would never try to access the global namespace. I have a feeling this is a dead end. You should see the same issue with the Unity Capture backend, but it may be worth a try.

agi1512 commented 3 years ago

Thing that bothers me most is that opencv can read from camera while being a windows service, but we can't send image back, this is sad ;(

I tried with Unity backend, and it's is even more disturbing. In logs i see that there were frames sent, I print out "frame sent" in while loop, but image on preview is not changing. Almost same situation, standalone version works, service version dont, but this time there's no error. Anyway thank you for quick reply, I guess I'll have to stick to autolaunch script and no-console mode while compiling exe.

letmaik commented 3 years ago

Thanks for trying out Unity Capture as well. I'll close this issue since there isn't anything I can do here, but if you or someone else finds a better solution, please comment.