Zingabopp / OBSControl

A Beat Saber mod to control and monitor OBS in-game.
Other
42 stars 4 forks source link

[Feature] Make OBSControl set Audio Devices #14

Closed Gordin closed 3 years ago

Gordin commented 3 years ago

Sometimes the configuration of my Audio Devices changes (Sometimes Windows decides that one of my monitors is supposed to output Audio instead of my USB Sound card, or the Oculus software re-adds the Headsets Audio to the Windows audio devices, and Windows will switch to it). This can sometimes lead to OBS forgetting which audio device it should record from. I've recorded videos multiple times only to find out afterwards that my videos don't actually have any sound because of this :/

To prevent this from happening I wrote a small script that will set the correct audio devices with the OBS Websocket plugin with the SetSourceSettings command. It would be awesome if something like this could be added to OBSControl as an option (even if just available through editing the JSON file).

Here is (part of) a python example of setting my Desktop Audio to my USB sound card and my Mic to the Windows default. Available (configured) audio inputs can be listed with the GetSpecialSources command and GetSourceSettings can be used to get the uuid of the currently selected device. Unfortunately it seems to me that the OBS Websocket API does not expose the human-readable names for the audio devices :( The device uuid I'm setting in the example shows up at Speakers (USB Audio Device) in OBS but GetSourceSettings only shows the uuid. Right now I'm using the soundcard python library to get a mapping between the human-readable names, I'm guessing OBSControl would need some C# library to get the names as well.

await ws.call('SetSourceSettings', {
    'sourceName': 'Desktop Audio',
    'sourceSettings': {
        'device_id': '{0.0.0.00000000}.{345c83bc-7a33-477b-8c93-31d7fc22e6f9}'
    }
})
await ws.call('SetSourceSettings', {
    'sourceName': 'Mic/Aux',
    'sourceSettings': {
        'device_id': 'default'
    }
})

Would you be willing to add something like this to OBSControl? If you don't have time but would accept a PR I might try to add this myself, I have zero experience with writing Beat Saber plugins though...

Zingabopp commented 3 years ago

I was actually trying to do something similar because of Valve's decision to use DisplayPort for the audio instead of USB (video driver updates can change the Index audio's device ID). I wanted to validate that OBS had a valid audio device set. Unfortunately, it seems you can't get a list of available audio output devices in Unity (they only have an API for input devices). I might try looking for a library I can ILMerge into OBSControl to deal with listing devices, but it might be awhile. If you want, you can start a PR with a class that can get a list of audio devices and add the option to the config. I can deal with integrating it into the mod later (and you could have your own build that sets it in OBS inside OBSController.OnConnect(object, EventArgs))

Gordin commented 3 years ago

Ah, so I'm not the only one with that problem ^^ If I find time I might look into implementing something then.

In case anyone else with the same problem sees this, for now I'm using this script I wrote: https://github.com/Gordin/obs-audio-fixer It's not really ready for use if you are unfamiliar with python (no README yet), but it works. It connects to OBS via WebSockets and sets the Audio devices according to the config. It will also list out all possible devices on the system, they should be the same as the ones selectable in OBS. The human-readable names can be used in the config at the top of the file.

Sample output:

Available audio output devices:
Headphones (2- Rift S)
Digital Audio (S/PDIF) (High Definition Audio Device)
Speakers (USB Audio Device)
Acer CB241H (NVIDIA High Definition Audio)

Available audio input devices:
Microphone (USB Audio Device)
Headset Microphone (Rift S)

Default Audio  Input (for this script) set to 'Headset Microphone (Rift S)'
Default Audio Output (for this script) set to 'Speakers (USB Audio Device)'

OBS WebSocket connection established.

Desktop Audio is set to 'Speakers (USB Audio Device)'
Mic/Aux is set to 'Headset Microphone (Rift S)'
Mic/Aux 2 is set to a device-id unknown to Windows: '{0.0.1.00000000}.{c82a60d9-22a9-4e22-a46a-041b7fe568ad}'

Setting Input devices
Setting OBS source Mic/Aux to Windows device Headset Microphone (Rift S)
Setting OBS source Mic/Aux 2 to Windows device Headset Microphone (Rift S)

Setting Output devices
Setting OBS source Desktop Audio to Windows device Speakers (USB Audio Device)

OBS WebSocket disconnected.                                                                 
Gordin commented 3 years ago

I've been playing around with the project and I found the "NAudio" NuGet package https://www.nuget.org/packages/NAudio/ It has functions that will list the GUID and human-readable names for audio devices very easily, I can list my devices at least in the debugger. I have no idea about the C#/Windows/NuGet/Beat Saber Mods infrastructure though, so I have no idea if adding a package to the project could be a problem or not (apart from maybe the size of the Mod DLL). Can I just add NuGet packages for stuff that I need to the project and the resulting plugin will work on other machines?

Zingabopp commented 3 years ago

Almost, but not quite. I'm using ILRepack to merge my Nugets into the OBSControl.dll. I can confirm the NAudio Nuget works with this. You can add the Nuget and add a line to ILRepack.targets: <InputAssemblies Include="NAudio.dll" /> below <InputAssemblies Include="WebSocket4Net.dll" /> image

Gordin commented 3 years ago

Ah, I'll try that :) Is there an easy way to test the OBS Websocket stuff in a test (or any other way that doesn't require to have BeatSaber running) with a real OBS instance? Right now I have this: devenv_U8PMifGzs3 It connects to OBS fine, but on the .GetSpecialSources() call it just dies. Doesn't even stop at an exception, it just quits the debugging interface and I have this in the log devenv_8lXEbw4ORP

Zingabopp commented 3 years ago

OBSControl is working off this branch of obs-websocket-dotnet, there's a TestClient project using Windows Forms that you can use to test things.

Gordin commented 3 years ago

Thanks the TestClient from from-websocket-dotnet helped ^^ Also, working in Visual Studio is kinda horrible, but I'm getting somewhere... I dropped NAudio for CSCore (which is what NAudio is using internally anyway), because NAudio has a couple other dependencies and would crash when trying to get the devices Beat Saber. OculusScreenshot1601155060 OculusScreenshot1601155076

Gordin commented 3 years ago

Got it working :) So far, OBSControl fetches the system devices, OBS devices and OBS sources and it can set any source to an audio device based on its "friendlyName". I still have to read/write the config file, add one dropdown for each OBS source, and make sure all devices are set when a recording starts. I think your code should have examples for everything I need ^^ ezgif-7-47201f6b587c

Zingabopp commented 3 years ago

Nice, this should give me more motivation to finish the refactor I'm working on too.

Gordin commented 3 years ago

Nice ^.^ When I'm done I'll have to refactor my stuff as well. Right now I have a million Logger.log.Debug statements in there... Usually I work with debuggers instead of printing everything, but I couldn't figure out how to run Beat Saber with a debugger attached to the Plugin. It also doesn't help that Exceptions don't seem to go into the log unless you catch them and log them yourself. Right now my process is write code => build => Start Beat Saber =>Quit Beat Saber => Read the Log file => repeat, but this time with more logs... Is there anything I can improve about this? Is there any way to attach a debugger or is this just not possible for Beat Saber plugins? Or is there a way to reload a plugin DLL without having to completely restart Beat Saber? Both of these would speed things up a lot, but I couldn't find any information about this.

Gordin commented 3 years ago

Last commit from my PR now sets the configured devices from the config when Beat Saber starts :) Also, all devices now have shortened names, so they fit in the dropdown menus now... It's not ready for merge, but you could try it out. Here is a preview (watch with sound, obviously...) https://www.youtube.com/watch?v=pxZlsZMMlnE I still need to make it so it sets the configured devices every time before a song starts and not just when Beat Saber starts, and probably add some code to handle when devices from the config file aren't connected when the Plugin tries to set them. It should already work, but I haven't' tested it, so there are probably some Null-Reference-Exceptions somewhere...

Gordin commented 3 years ago

Oh, could you tell me what the problem is with the builds by GitHub Actions? It fails when trying to do a wget ... bsfiles.zip from a FILES_URL variable that is empty and I have no idea what that is supposed to be...

Zingabopp commented 3 years ago

The zip is a clone of my Beat Saber game directory with all the DLLs stripped of their contents. Some modders used to include them in the repo for CI, but Beat Game would prefer we didn't do that. In my repo, I have a secret with a link to zip that the Action downloads and extracts, but I guess the secret doesn't work for PRs.

Gordin commented 3 years ago

@Zingabopp you can take a look at the branch now if you like. The Menus now remember all devices that were in the list across restarts and show you when a device is not available or a source is disabled in OBS. It also now sets the devices every time a song starts

Gordin commented 3 years ago

Also, I'm not that proud of the code, I tried my best not to copy&paste stuff, but having 6 possible sources makes it really hard, because as far as I can tell, everything I want to use in the bsml file has to be a separate variable :/ If You could put something like value=~someArray["desktop-1"].value in the bsml file I could reduce the code a lot but from the documentation that isn't how it works, so there is a lot of Desktop1..., Desktop2.... , Mic1..., Mic2..., Mic3..., Mic4... in the code...

Zingabopp commented 3 years ago

Added in v2.0.0

Gordin commented 3 years ago

btw, have you tested this again in the latest versions? It hasn't been working for me since at least 0.13.4. I'm going to look into it... (It just shows me "default" for all devices)

Zingabopp commented 3 years ago

I haven't tested it lately. There is a bug where if an output and input device have the same name it breaks (duplicate key in the dictionary).

Gordin commented 3 years ago

Ah, I see what the problem is. For something called Speakers (Some Name) it tries to use Speakers as short name, when it should be using Some Name... I remember there was some weird naming differences between Inputs and Outputs, for outputs the part in ( ) is the actual name, and for inputs the part before the ( ) is the name. Looking into it...

Gordin commented 3 years ago

Actually, that convention seems to have changed, which is why it is using the wrong part of the name. No idea if this is a change in some Windows 10 update or some library >_>

Gordin commented 3 years ago

Nevermind, looks like both versions don't work if you have a certain combination of devices... Guess I'll have to rewrite this to use UUIDs internally instead of the short names...