Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

output device selection? #54

Open
0xDiddi opened this issue Jan 16, 2019 · 15 comments
Open

output device selection? #54

0xDiddi opened this issue Jan 16, 2019 · 15 comments

Comments

@0xDiddi
Copy link

0xDiddi commented Jan 16, 2019

What would be involved in enabling support for selecting different audio devices?

@hajimehoshi
Copy link
Member

Makes sense.

In some environments like browsers, there is no way to choose audio devices.

@0xDiddi
Copy link
Author

0xDiddi commented Jan 17, 2019

I have looked around the underlying windows library and it seems pretty straight forward

  • waveOutOpen takes a device id. This is currently set to WAVE_MAPPER which selects a device with "the appropriate capabilities".
  • there is waveOutGetNumDevs which returns the number of available audio devices
  • I'm not sure if the device id is just its index or if there is some mapping. I have however seen no function that would facilitate such a mapping.
  • there is waveOutGetDevCaps which, for a given device id, returns its capabilities and its name, which would allow for "search by name" functionality

I'll give this a shot and see how it goes.


Another question is that of the API. I suppose this functionality looks roughly similar on other platforms that support multiple devices, but the question is in what way this should be exposed.

Some thoughts:

  • The current behaviour should remain the default, no matter what, as to not break dependent projects.
  • I'd suggest a GetDevices() function that returns a slice of device structs. These would roughly be a translation of the WAVEOUTCAPS capability struct, containing the devices name, id, and potentially other relevant information
  • A SetDevice() function that takes either the id or the device struct, and does the necessary api calls

@0xDiddi
Copy link
Author

0xDiddi commented Jan 17, 2019

I realized a small oversight in my previous idea of a SetDevice() function: at least on windows, the device must be specified when first opening the device, and can't (afaict) be changed later on.

This means we'd need another variant for newDriver and subsequently also for NewPlayer that takes the device selection into account.

Alternatively it would be an option to re-create the driver every time the device is changed. This sounds like kind of a bad idea, but then again device selection would usually be a one-time setup call so it might not be all that bad.

@hajimehoshi
Copy link
Member

I am now working on introducing 'context' #47 to create multiple players. Perhaps, we should use the term 'device' instead of 'context'.

Alternatively it would be an option to re-create the driver every time the device is changed.

I tend to agree. What is an actual use case when the user wants to change the device?

@Asday
Copy link

Asday commented Jan 17, 2019

Occasionally, Path of Exile gets confused and tries to play its sound out of my screen through HDMI. I then have to go to its settings pane and flick it back to my DAC.

EDIT: Also back when I had multiple DACs, applications would generally open and use my default device as specified in the Windows settings, and I'd need to go into their settings (if possible) and switch them to the DAC I wanted them on. These DACs were then connected to a mixer, which sent some of them to one output, and all of them to the other, which let me do things like stream while listening to music, without the music being played on stream.

@0xDiddi
Copy link
Author

0xDiddi commented Jan 19, 2019

which let me do things like stream while listening to music, without the music being played on stream.

My use case is kinda similar: I have my speakers as the default device, and then I have a virtual audio cable which I use in combination with a rigged VLC player as a sort of soundboard in VOIP apps.

I was looking to program a custom soundboard app which would of course need to send its output to the virtual audio cable and not my speakers.

My program would likely be a console application, so the configuration would be either a config file or command line option, but either way it would be a one-time setup at startup. Given that go hasn't found it's way into all that many ui applications, I'd guess that'd be the same for most other apps as well.


Perhaps, we should use the term 'device' instead of 'context'.

That seems to me like the more descriptive term. Given what you said in #47, at that point we could also add another parameter to the constructor in addition to channels, size and sample rate that would specify the physical device.

This constructor could then also internally check if an object for that device has already been created and throw an error if necessary.

@0xDiddi
Copy link
Author

0xDiddi commented Feb 12, 2019

So I finally found the time to experiment around a bit and found the following:

  • waveOutGetNumDevs() works fine (returns the correct number of devices)
    • The device id used by the API is a number between 0 and the value returned by waveOutGetNumDevs()
  • regarding waveOutGetDevCaps
    • there are two variants: waveOutGetDevCapsA and waveOutGetDevCapsW. After looking into the header file, the W variant is for wide characters in the device name. I'd assume that sticking to 8 bits width with the A variant should be sufficient then.
    • calling both with either width returns a mmresult of 11, which means invalid arguments, but I have no idea where the error is

My current code looks like this:

var caps *waveoutcaps
var id int = 5 // actually a parameter

r, _, e := procWaveOutGetDevCaps.Call(uintptr(id), uintptr(unsafe.Pointer(caps)), unsafe.Sizeof(waveoutcaps{}))

@hajimehoshi
Copy link
Member

  • there are two variants: waveOutGetDevCapsA and waveOutGetDevCapsW. After looking into the header file, the W variant is for wide characters in the device name. I'd assume that sticking to 8 bits width with the A variant should be sufficient then.

This might be out of topic, but W is always preferable since this accepts UTF-16 (which can be easily converted from UTF-8) while A accepts local dependent code.

@0xDiddi
Copy link
Author

0xDiddi commented Apr 9, 2019

So today I finally got around to testing a bit more.

(I'm using wide variants of the functions)

My go code still returns code 11, still unsure where exactly the error is.

I managed to get it to work however in pure C. Interestingly: at first I got result code 11 here as well.
After switching form sizeof(LPWAVEOUTCAPSW) to sizeof(WAVEOUTCAPSW) as the third parameter it worked flawlessly. This makes sense, as I want to have the size of the struct and not the pointer.

That leaves me with even more questions as before, as now (as far as I can tell) my non-working go code is the exact equivalent of well-working C code.

Here is a gist with my current non-working go code and the working c code.

@0xDiddi
Copy link
Author

0xDiddi commented Apr 12, 2019

var caps *waveoutcaps

I was apparently thinking the struct would initialize itself. Which of course it wasn't, and so it failed.

With that out of the way, I should have a working prototype for device selection on windows shortly.

@jessemillar
Copy link

@0xDiddi Any progress on your prototype?

@0xDiddi
Copy link
Author

0xDiddi commented Jul 7, 2020

University got pretty busy for me last year and I forgot about this. I'll have a look on what the exact status is probably within the week or next weekend.

I think I got at least name-based device selection for playback working, not sure how far I was in presenting a list of the available devices.

@0xDiddi
Copy link
Author

0xDiddi commented Jul 12, 2020

It seems like I messed up here somewhat. I reset my pc a few months ago, and while I backed up my own go projects, I ignored all "dependencies", thinking I could simply pull them again, not thinking about this fork. I also didn't have anything committed yet since it was only half-baked, so no hope there either.

I also don't think I will be able to put a lot of time, if any, towards this for another month or two, but I'll be happy to help with questions if anyone else wants to tackle it in the meantime.

@jessemillar
Copy link

@0xDiddi Ah. Sad to hear but completely understandable. Thanks for checking!

@raffaeletani
Copy link

I did some exploring on @0xDiddi s proposal from the gist and got the listing of output devices to work

func waveOutGetDeviceCaps(id int) (*waveoutcaps, error) {
	var caps = new(waveoutcaps) //see https://github.com/ebitengine/oto/issues/54#issuecomment-482648636

	r, _, e := procWaveOutGetDevCaps.Call(uintptr(id), uintptr(unsafe.Pointer(caps)), unsafe.Sizeof(waveoutcaps{}))
	if e.(windows.Errno) != 0 {
		return nil, &winmmError{
			fname: "waveOutGetDeviceCaps",
			errno: e.(windows.Errno),
		}
	}

	if mmresult(r) != mmsyserrNoerror {
		return nil, &winmmError{
			fname:    "waveOutGetDeviceCaps",
			mmresult: mmresult(r),
		}
	}

	return caps, nil
}

Usage:

for idx := 0; idx < waveOutDeviceCount(); idx++ {
  device, err := waveOutGetDeviceCaps(idx)
  if err != nil {
	  fmt.Println(err)
  }
  fmt.Println(windows.UTF16ToString(device.szPname[:]))
}

About:

  • I'm not sure if the device id is just its index or if there is some mapping. I have however seen no function that would facilitate such a mapping.

i tested this with multiple different output device configurations and as it seems, it's just an index.
So we could easily store the devices name, look up the corresponding index and set that index in waveOutOpen

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants