Inertia's picture

OpenAL Device Choices

I've recently bought a Creative Labs Soundblaster X-Fi Platinum Fatal1ty Champion Series (TM)(C)(R) card (hope I did not forget any buzzwords) and getting the impression that it finally works as it should, but AudioContext is giving me some troubles porting the old Alut code. (I want to confirm that OpenTK's X-Ram class works as advertised by me :P)

The card did not ship with a driver CD, I've installed latest drivers and oalinst right from creative labs website.

My impression is that AudioContext uses the wrong tokens to select it's device, here's c&p from some AlcGetString queries.

Mind the "Detected by OpenTK" is simply what AudioContext.AvailableDevices returns. Also I have nothing connected to the SPDIF Out, so it's correctly recognizing Speakers to use for output.

Using Creative Labs driver (result identical for x32 and x64):

Quote:

Detected by OpenTK:
Generic Software
Returned by AlcGetString.DefaultDeviceSpecifier:
Generic Software
Returned by AlcGetString.DefaultAllDevicesSpecifier:
Generic Software on Speakers (Creative SB X-Fi)
Returned by AlcGetStringList.DeviceSpecifier:
Generic Software
Returned by AlcGetStringList.AllDevicesSpecifier:
Generic Software on Speakers (Creative SB X-Fi)
Generic Software on SPDIF Out (Creative SB X-Fi)

using OpenAL Soft drivers (copied to app dir, result identical for x32 and x64)

Quote:

Detected by OpenTK:
DirectSound Software
Wave File Writer
Returned by AlcGetString.DefaultDeviceSpecifier:
DirectSound Software
Returned by AlcGetString.DefaultAllDevicesSpecifier:
DirectSound Software on Speakers (Creative SB X-Fi)
Returned by AlcGetStringList.DeviceSpecifier:
DirectSound Software
Wave File Writer
Returned by AlcGetStringList.AllDevicesSpecifier:
DirectSound Software on Speakers (Creative SB X-Fi)
DirectSound Software on SPDIF Out (Creative SB X-Fi)
Wave File Writer

------------------------------------------------

However when I run the EfxEnumerateWin32.exe from the OpenAL SDK I get these device choices:

Quote:

Enumerate EFX Application

Select OpenAL Device:
1. SB X-Fi Audio [E800](DEFAULT)
2. Generic Software

Running EfxEnumerateWin64.exe gives:

Quote:

Enumerate EFX Application

Select OpenAL Device:
1. Generic Software(DEFAULT)

---------------------------------------------

Running openal-info.exe (with OpenAL Soft in it's dir) returns

Available playback devices:
    DirectSound Software on Speakers (Creative SB X-Fi)
    DirectSound Software on SPDIF Out (Creative SB X-Fi)
    Wave File Writer
Available capture devices:
    WaveIn on Line-In 2/Mic 2 (Creative SB X-
    WaveIn on Digital-In (Creative SB X-Fi)
    WaveIn on S/PDIF-In (Creative SB X-Fi)
    WaveIn on Auxiliary (Creative SB X-Fi)
    WaveIn on Microphone (Creative SB X-Fi)
    WaveIn on Auxiliary 2 (Creative SB X-Fi)
    WaveIn on Line-In (Creative SB X-Fi)
Default device: DirectSound Software
Default capture device: WaveIn on Line-In 2/Mic 2 (Creative SB X-
ALC version: 1.1
ALC extensions:
    ALC_ENUMERATE_ALL_EXT, ALC_ENUMERATION_EXT, ALC_EXT_CAPTURE, ALC_EXT_EFX
OpenAL vendor string: OpenAL Community
OpenAL renderer string: OpenAL Soft
OpenAL version string: 1.1 ALSOFT 1.8.466
OpenAL extensions:
    AL_EXTX_buffer_sub_data, AL_EXT_EXPONENT_DISTANCE, AL_EXT_FLOAT32,
    AL_EXT_IMA4, AL_EXT_LINEAR_DISTANCE, AL_EXT_MCFORMATS, AL_EXT_OFFSET,
    AL_EXTX_source_distance_model, AL_LOKI_quadriphonic
EFX version: 1.0
Max auxiliary sends: 2
Available filters:
    Low-pass
Available effects:
    EAX Reverb
    Reverb
    Echo

Running openal-info.exe without OpenAL Soft returns:

Available playback devices:
    SB X-Fi Audio [E800]
    Generic Software on Speakers (Creative SB X-Fi)
    Generic Software on SPDIF Out (Creative SB X-Fi)
Available capture devices:
    Line-In 2/Mic 2 (Creative SB X-
    Digital-In (Creative SB X-Fi)
    S/PDIF-In (Creative SB X-Fi)
    Auxiliary (Creative SB X-Fi)
    Microphone (Creative SB X-Fi)
    Auxiliary 2 (Creative SB X-Fi)
    Line-In (Creative SB X-Fi)
Default device: SB X-Fi Audio [E800]
Default capture device: Line-In 2/Mic 2 (Creative SB X-
ALC version: 1.1
ALC extensions:
    None
OpenAL vendor string: Creative Labs Inc.
OpenAL renderer string: SB X-Fi Audio [E800]
OpenAL version string: OpenAL version 1.1
OpenAL extensions:
    EAX
EAX1.0
EAX2.0
EAX3.0
EAX4.0
EAX5.0
 
EFX version: 1.0
Max auxiliary sends: 2
Available filters:
    Low-pass
Available effects:
    EAX Reverb
    Reverb
    Chorus
    Distortion
    Echo
    Flanger
    Frequency Shifter
    Vocal Morpher
    Pitch Shifter
    Ring Modulator
    Autowah
    Compressor
    Equalizer

openal-info is most likely an x86 app (came with the OpenAL Soft x86 driver), could it be that my x64 driver is simply broken?

I kinda miss the "Generic Hardware" device, which is probably the "SB X-Fi Audio [E800]". (mind the device name is identical to "OpenAL renderer string: SB X-Fi Audio [E800]" returned by openal-info)

Either way I think AudioContext should use AlcGetString.DefaultAllDevicesSpecifier to select the default device. With both drivers it selects the most appropriate device.

Edit: Could it be that the problem is related to either a) the disabled onboard sound or b) the graphic card's HDMI out? Another solution might be http://icculus.org/alextreg/wiki/ALC_ENUMERATE_ALL_EXT currently AudioContext uses ALC_ENUMERATE_EXT only.


Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
Inertia's picture

Do you intend to work on this? I'm already working on it, but since you wrote it you know your way around better than me and I'll gladly hand it over ;-)

btw. X-Ram works fine, it reports 66MB total RAM although it should be 64MB (free RAM reports 64MB), but I think this has something to do with all X-Fi cards having 2MB onboard RAM, probably for streaming the mixed output or something like that. No reason for concern imho.

the Fiddler's picture

Feel free to tackle the issue. I'm studying for exams, so my OpenTK time is strictly limited.

Inertia's picture

Good luck with that, but I hope you will review changes because I'm getting creative here ...

  • Refactored AvailableDevices property into AvailablePlaybackDevices.
  • Added AvailableRecordingDevices property.
  • Added a public readonly-property GetDeviceName. (Good for Apps which allow the user to select a device from a list, or apps which simply request the default device, but would like to know afterwards what it's called)
  • Added a public readonly-property GetAlcError. (Alc.GetError takes an IntPtr as parameter, which is bad for the wrapper's design)
the Fiddler's picture

Suggestion: commit the code to a new branch (branches/openal-next or something). Both easier to review and more fine-grained commits this way (instead of a single, large code dump in the end).

IIRC, the design-guidelines suggest to avoid 'Get' in properties, i.e. DeviceName instead of GetDeviceName (the latter is fine if it is a function, however).

Are you planning to add capture functionality? Do capture contexts share functionality with plain contexts or do they use completely different APIs? (never used OpenAL capture before. Are they even called "capture contexts"?)

Inertia's picture

[branch]
k, but the changes are really minor (besides the refactoring of AvailableDevices).

[get]
How would you name GetAlcError? AlcError is the name of an already used enum. It does internally call Alc.GetError, so maybe simply "Error" or "FirstError"?

[capture]
IIRC the OpenAL spec likes to call playback "render" and recording "capture".
Recording does not use a context, you can only open/close the device, start/stop recording and capture the samples. No AL commands and only few Alc commands work with the recording device, i.e. you need a playback device first to query available recording devices.
I can wrap the associated commands up, but I don't think it would be a good design to include any form of threads/mainloop into the class.

It might look like this in the end:

RecordingDevice rec = new RecordingDevice(); // open
rec.StartCapture();
Thread.Sleep(500);
rec.StopCapture();
byte[] samples = rec.CaptureSamples( rec.SampleCount );
rec.Dispose(); // close
the Fiddler's picture

[branch]
Just makes reviewing easier (there is an rss feed of new commits you can subscribe to).

[get]
I think error checking is better served by a function: context.CheckErrors() or similar to this (but see below).

[error checking]
Which conditions generate errors? Is it possible to perform all error checking inside the AudioContext and throw exceptions instead?

[capture]
The code sample you posted looks just about perfect. No threading/whatnot, just a thin wrapper over Alc.

Neither AudioContext or RecordingDevice are strictly necessary (in the sense of GraphicsContext, which *is* necessary). You could get the same results with plain Alc, only with more code for error checking). Besides, the symmetry between graphics/audio is nice.

Edit: if you actually need a playback device to create a recording device, maybe you could pass it as a (non-optional) parameter to the RecordingDevice constructor? This would enforce correct order of construction:

AudioContext context = new AudioContext();
RecordingDevice device = new RecordingDevice(context);
Inertia's picture

[error checking]
The system is the same as for GL, pretty much any function can generate InvalidOperation/-Enum/OutOfMemory etc.
The "automatic GL.GetError" for debug builds could be applied to AL in the same way, assuming the functions have some internal import and a public interface where to place the geterror calls.

There are also some special cases that require handling by the application, for example the user may intentionally or accidentially unplug the microphone or put it on mute. At least I do prefer testing for this condition manually rather than creating a huge try{} to encase all code where some AudioDeviceUnpluggedException could be thrown.

[capture]
There is no need for an AudioContext instance in order to use the capture extension, but AudioContext's static constructor must be called to create the dummy device/context and query available devices from the driver. This may have not been clear in the recording-example application I posted a couple of weeks ago, since it uses an AudioContext for playback anyway. But in theory (I cannot think of any good usage example for it) you could write an application that only captures microphone input but has no AudioContext to play anything back.

the Fiddler's picture

[error checking]
Automatic AL error checking will require a great amount of work (and possibly some kind of code generator). I'd prefer to postpone that until we can find a way to implement GL/AL/CL error checking in a unified way. However, AudioContext and/or RecordingDevice should probably check for ALC errors occuring in their code.

I don't think OpenAL 1.1 supports dynamic enumeration of devices, i.e. the list of available recording or playback devices won't change once OpenAL is initialized. I really have no idea how OpenAL will react upon removal of a device. For example, Windows Vista will reroute audio to another device automatically, but I'm not sure if OpenAL can detect that.

Have you tried disconnecting the microphone while capture is running? Does it result in an error or does the application continue as if nothing happened?

[capture]
My mistake, I assumed recording devices needed a playback context. Since they don't, maybe it would make sense to enumerate recording devices inside the RecordingDevice constructor instead of the AudioContext one?

Inertia's picture

How about the name "AudioRecorder" instead of "RecordingDevice"? Then we'd have AudioContext for playback and AudioRecorder for recording. Maybe even refactor AudioContext to AudioPlayback?

[error checking]
How about some bool ThrowOnError? I'd prefer that over forcing exceptions to be default behaviour.

[unplugged]
IIRC Alc.CaptureStart will generate an Alc Error if no microphone is connected, but opening the device worked. Haven't tried unplug it during capturing and currently the mic is not working (did I already say that installing the card is a bitch and that I miss the the days where you had to RTFM, jumper the card's IRQ and it worked after that, instead of Plug&Pray?)

[enumeration]
From the isolation-perspective that would certainly be the best choice, but from an efficiency-perspective it is horrible: 2 dummy contexts would be created and destroyed, devices must be protected from both static constructors accessing the device at the same time and also only a fraction of OpenTK applications do use Audio, even less will use the recording capabilities.

I'd rather add a get-property to the AudioRecorder that pulls the information from AudioContext's properties. Like I said before I cannot think of any good usage example that would want to use recording capabilities but no playback.

the Fiddler's picture

[AudioRecorder vs RecordingDevice]
AudioRecorder is much better than RecordingDevice. The documentation refers to it as 'capture', so AudioCapture is another good option.

No point in renaming AudioContext, since it agrees with the documentation (alcCreateContext). It's also nicely symmetrical with GraphicsContext.

[error checking]
I can see this working for automatic AL error checking (just like you can turn off GL exceptions), but how would this work inside e.g. AudioContext? For example if alcOpenDevice fails in the AudioContext constructor, you don't have any option other than throwing an exception. If you don't throw, the AudioContext will be constructed successfully but remain invalid - which is all kinds of wrong.

[unplugged]
Isn't this a PCIe card? How can it be so difficult to install? :/

The ideal solution would be to have a DeviceUnplugged event that you can subscribe to. Unfortunately, OpenAL doesn't offer any notification mechanism, so the only way to discover an unplugged device is to try and use it - and by that time it's already too late for a notification.

[enumeration]
You are right, no point in creating two dummy contexts (I don't even want to think what this would do to buggy implementations).

Arguably, we could add a common, non-public class accessed by both AudioContext and AudioRecorder/Capture/whatever.

static class DeviceEnumerator
{
    static readonly List<string> playback_devices = new List<string>();
    static readonly List<string> recording_devices = new List<string>();
 
    static DeviceEnumerator()
    {
        // Create dummy context and query available devices.
        // The lists don't change once OpenAL is initialized, so we can query only once.
    }
 
    static IList<string> PlaybackDevices { get { return playback_devices.AsReadonly(); } }
 
    static IList<string> RecordingDevices { get { return recording_devices.AsReadonly(); } }
}