non-ascii device IDs in alcCaptureOpenDevice()

Started by kinoko, August 16, 2012, 07:07:46

Previous topic - Next topic

kinoko

OS : Win7, 64bit
Java: JDK 1.7.0_06
LWJGL : 2.8.4

I am trying to open a non-default record device with a non-ascii devicename.

// -- My code
// -- the default rec device is  "マイク (S/PDIF) (Sound Blaster X-Fi Xtreme Audio)"
defaultRecDeviceName = ALC10.alcGetString(null, ALC11.ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER);
ALC11.alcCaptureOpenDevice(defaultRecDeviceName , 
			 		16000, AL10.AL_FORMAT_MONO16, buffSz);

// --  Resulted Exception:
Exception in thread "main" java.lang.RuntimeException: java.nio.charset.UnmappableCharacterException: Input length = 1
	at org.lwjgl.MemoryUtil.encode(MemoryUtil.java:285)
	at org.lwjgl.MemoryUtil.encode(MemoryUtil.java:246)
	at org.lwjgl.MemoryUtil.encodeASCII(MemoryUtil.java:205)
	at org.lwjgl.openal.ALC11.alcCaptureOpenDevice(ALC11.java:95)

// -- org.lwjgl.MemoryUtil#encodeASCII
#94	public static ALCdevice alcCaptureOpenDevice(String devicename, int frequency, int format, int buffersize) {
#95		ByteBuffer buffer = MemoryUtil.encodeASCII(devicename);


Well, encodeASCII fails because of the Japanese katakana. So, while ALC10.alcGetString does nothing with string encoding, alcCaptureOpenDevice tries do encode the received Java String to ASCII - using org.lwjgl.MemoryUtil.encodeASCII.

//-- org.lwjgl.openal.ALC10.class
public static String alcGetString(ALCdevice device, int pname) {
	String result;
	result = nalcGetString(getDevice(device), pname);
	Util.checkALCError(device);
	return result;
}



The native code holds on to a non-ascii string, but lwjgl encodes everything to ascii.
I guess I can't do anything about it in my code. Can I?
Any ideas?


Matzon

not really sure here ... we might take a hint from the code page of the platform we're running on, but the openal specs do NOT specify a character encoding. At least I haven't been able to locate it.

Some old discussion here: http://comments.gmane.org/gmane.comp.lib.openal.devel/90

Quote
For 1.0 and 1.1, it's ASCII, and saying otherwise breaks legacy
apps...but we should fix this to be Unicode for Whatever Version Comes Next.

--ryan.

Quote
Until someone hands back a string that needs more than 8 bits for a
character, then existing apps break.

--ryan.

::)  ::)

kinoko

Okay, let me answer my question. This worked for me:

defaultRecDeviceName = ALC10.alcGetString(null, ALC11.ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER);
String defaultRecDeviceNameASCII = new String(defaultRecDeviceName.getBytes(Charset.forName("Shift-JIS")), Charset.forName("ASCII"));
currentRecDev = ALC11.alcCaptureOpenDevice(defaultRecDeviceNameASCII, 16000, AL10.AL_FORMAT_MONO16, buffSz);


Actually, Charset.forName("Shift-JIS")  was just a guess (given I run Japanese Win 7), I guess it depends on the manufacturer/driver's choice of encoding. (It has nothing to do with Charset.defaultCharset())


kinoko

Matzon,

Thanks you for your answer! And the link!
In the meanwile I found a solution. Unoftunately it works only in my case (+ on some Shift-JIS encoded devices).

I am trying to figure out a better way to keep out Java's automatic encoding of Strings. Also I was wondering if it's possible to have another alcGetString-like function that returns a byte(or char) array..., also a corresponding alcCaptureOpenDevice that accepts byte arrays.

Just for curiousity, I have been always wondering why devices ids are not implemented as integers? Strings get pretty messy, especially if  JNI is involved (memory leaks, etc).




spasi

We could change encodeASCII to encodeUTF8 in alcCaptureOpenDevice, it should work fine with both ASCII and unicode device names.

Matzon

aye, I think that would be safe to do ... however, it does seem that the OAL api is character encoding neutral - so the fact that we're doing ANY conversion, COULD be problematic.

somewhere down the line, a device may export its device name with unicode-32, just for the heck of it, and then we'd be in the same problem again. But for now, I think utf8 is a safe bet.

kinoko

Wouldn't be just easier to use byte arrays in the API and let the developers play around with encodings to their heart's content?
I might be ignorant here, but displaying polluted characters (e.g., as '? ? ? ') is not a real disaster, unlike failing to set a device. ::)

In the meanwhile I checked, Java uses UTF-16 internally.
:-\

kinoko


[Matzon Reply #5]
> the fact that we're doing ANY conversion, COULD be problematic.

Exactly. But as early as when native byte (or alchar?) arrays are converted to Java Strings (at JNI level) there is an assumption about the encoding. JNI provides GetStringUTFChars or GetStringChars with utf-8 and Unicode encodings - respectively.

Having a look at  LWJGL's implementation... (tracking it down from alcGetString, which provides the default record device):

#234 common_tools.c
jstring NewStringNativeWithLength(JNIEnv *env, const char *str, int length) {       
// ... 
   jcls_str = (*env)->FindClass(env,"java/lang/String");   
...
   jmethod_str = (*env)->GetMethodID(env,jcls_str, "<init>", "([B)V");
...
    result = (jstring)(*env)->NewObject(env,jcls_str, jmethod_str, bytes);
//...


The Java string is created with default c'tor, that is, it "constructs a new String by decoding the specified array of bytes using the platform's default charset." (from Java's String doc)...   So, there is the native char/byte array of unknown encoding, converted by a platform specific encoding. On the way 'back' (ie when opening the device from the app code) the same string gets converted to ASCII by org.lwjgl.MemoryUtil.encodeASCII... well.. good luck with that.

So, again, how about using  byte array arguments? Along the lines of:

byte[] ALC10.alcGetStringAsByteArray(...)
alcCaptureOpenDevice(byte [], ...) 

// as for JNI...  something like...
static jbyteArray  JNICALL Java_org_lwjgl_openal_ALC10_alcGetStringAsByteArray (JNIEnv *env, jclass clazz, jlong deviceaddress, jint token) {
const char* alcString = (const char*) alcGetString((ALCdevice*)((intptr_t)deviceaddress), (ALenum) token);  
jbyteArray jb=(*env)->NewByteArray(env, strlen(alcString));
   (*env)->SetByteArrayRegion(env, jb, 0, 
	strlen(alcString), (jbyte *)m);
...





spasi

Please try the next build. I changed alcGetString and alcOpenDevice to use UTF8 decoding/encoding. If the problem remains, I can expose methods that return/accept ByteBuffers (not byte arrays), but I'll wait for your reply first.

kinoko

Spasi, thanks for that!  :)
I see it's in the trunk now. Shall I try to compile it myself or can I grab some binaries somewhere?