Mac Bug: Static for sound

Started by psiegel, May 16, 2004, 12:55:20

Previous topic - Next topic

psiegel

My Mac tester reports that when he runs the game now, he gets blasted with loud static instead of our intro music.  I can post some code tidbits on how I use OpenAL, but I thought I'd better query first what the state of the sound code is in the Mac port.  Should this work, or is it known that the sound code is not yet complete on Mac?

princec

Sound works perfectly on the Mac but you've forgotten that it's the other way round Endian-wise :)

Cas :)

psiegel

So, assuming I have an array of bytes that represent the PCM sound data, and am about to call AL10.alBufferData(), here's what I do:

byte[] pcm = getPCMBytes();
ByteBuffer data = ByteBuffer.allocateDirect(pcm.length).order(ByteOrder.nativeOrder));
data.put(pcm);


Is there some clever generic way to fix this that it will work on both Mac and Windows/Linux?  Or will I have to detect that I am running on a Mac, and invert each individual byte before adding it to the ByteBuffer?

Paul

psiegel

Now that I've written it out, perhaps it's just a matter of calling the order method after the ByteBuffer has been populated?  As in:

byte[] pcm = getPCMBytes();
ByteBuffer data = ByteBuffer.allocateDirect(pcm.length);
data.put(pcm);
data.rewind();
data = data.order(ByteOrder.nativeOrder());


That works fine in Windows anyway.  Does that seem correct to you, Cas?

Paul

princec

You can't go altering the byte order after you've already shoved the data in in the wrong order! That'll only affect subsequent Java-side buffer reads - OpenAL will still get it in the wrong order.

Cas :)

psiegel

So I'll have to just manually go through the byte array and flip the high and low words?  I'm not so scared to do it, I just think that it's pretty poor that I'll have to detect what OS I'm running and write specialized code for Mac only.   Wish there was a platform-independant way to do it.

Paul

Matzon

How are you loading your sound?
You might want to use:
org.lwjgl.test.openal.WaveData

princec

You don't need to know what platform you are running on; you need to know what endianness your data was saved in. Typically you'll be saving it in Intel byte order, so when you read it in on PPC it's backward. This is completely cross-platform; you are dealing here with a platform interchange problem which has nothing to do with Java! Fortunately the byte-order methods in Buffers can make your life a little simpler in this respect.

Cas :)

psiegel

Ok, so I know what the endianess of my data is.  I also know that I have a byte buffer in the correct native OS order.  Is it therefore a matter of checking what order ByteOrder.nativeOrder() returns, and flipping the order of the bytes before I add them if it returns an order opposite what my file format uses?  That at least makes me feel better than detecting that what OS I'm running on.  

So assuming I know that my data is the opposite endianess of the ByteBuffer I'm trying to add it to, is there some way to set up a ByteBuffer to automatically flip data that is put into it?  Or is this simply a manual process I'll have to write myself?  Sorry if I sound nieve, but it's really the nio packages that I don't have a great handle on.  

Thanks for the help!

Paul

cfmdobbie

This is probably the section of WaveData.java that is of interest to you:

private static ByteBuffer convertAudioBytes(byte[] audio_bytes, boolean two_bytes_data) {
		ByteBuffer dest = ByteBuffer.allocateDirect(audio_bytes.length);
		dest.order(ByteOrder.nativeOrder());
		ByteBuffer src = ByteBuffer.wrap(audio_bytes);
		src.order(ByteOrder.LITTLE_ENDIAN);
		if (two_bytes_data) {
			ShortBuffer dest_short = dest.asShortBuffer();
			ShortBuffer src_short = src.asShortBuffer();
			while (src_short.hasRemaining())
				dest_short.put(src_short.get());
		} else {
			while (src.hasRemaining())
				dest.put(src.get());
		}
		dest.rewind();
		return dest;
	}
ellomynameis Charlie Dobbie.

psiegel

Excellent, that is very helpful.  Now that I've encountered this, I'm wondering if this is likely to be a problem in other areas as well.  I'm specifically thinking of image data.  Do you think it's necessary to do this process to pixel data put into an OpenGL texture as well, or is OpenGL in some way different from OpenAL?

Thanks again!

Paul

psiegel

Though I'm curious now, why would wrapping the data in a little endian byte buffer be any different from simply directly accessing the original data?  I mean, assuming that you know that audio_data is in little endian order, wouldn't the following produce the exact same results?

ByteBuffer dest = ByteBuffer.allocateDirect(audio_bytes.length);
dest.order(ByteOrder.nativeOrder()); 
for (int i=0;i<audio_data.length;i++) {
  dest.put(audio_data[i]);
}


I don't see what wrapping the original data in a ByteBuffer is going to do, if you're just going to call src.get(), which returns a primitive byte anyway.

Paul

princec

Ah, but notice the calls are being made on a ShortBuffer and that's where bytes might get swapped round. And yes, OpenGL has the same issue, obfuscated by the various GL_RGB, GL_RGBA, GL_BGRA_EXT, etc. formats but essentially the same deal as with sound data.

Cas :)

psiegel

True, but assuming two_bytes_data was false, the extra ByteBuffer would be superfluous, right?  And then the only difference between this code and the code that I originally posted would be the fact that I attempted to use the bulk put instead of putting one byte at a time.  And according to the javadoc, the two should be equivalent, excepting that the bulk put may be more efficient.

Which brings us back to square 1.  :)

Paul

psiegel

Ok, I think I may have muddled my way through this.  Tell me if this makes sense.  Endianness doesn't matter for individual bytes, only when multiple bytes are used to create a single larger primitive value (eg. an integer).  As I looked through the WaveData.java file, I see that the two_bytes_data field is set when the bit depth of the audio data is 16.  So if my sound was sampled in 8 bit, I probably wouldn't be experiencing this problem.  However, since I'm in 16 bit, I must swap every other byte, or use this ShortBuffer wrapper solution you use in WaveData.

One might say that your own method might be made more efficient thus:
private static ByteBuffer convertAudioBytes(byte[] audio_bytes, boolean two_bytes_data) {
    ByteBuffer dest = ByteBuffer.allocateDirect(audio_bytes.length);
    dest.order(ByteOrder.nativeOrder());
    if (two_bytes_data) {
       ByteBuffer src = ByteBuffer.wrap(audio_bytes);
       src.order(ByteOrder.LITTLE_ENDIAN);
       ShortBuffer dest_short = dest.asShortBuffer();
       ShortBuffer src_short = src.asShortBuffer();
       while (src_short.hasRemaining())
          dest_short.put(src_short.get());
    } else {
       dest.put(audio_bytes);
    }
    dest.rewind();
    return dest;
 }

Since clearly when each sample is only a single byte, there can be no endianness problem.

The final odd point I couldn't figure out was why I didn't seem to have problems with my image files.  However, I've noticed that I create all my OpenGL textures using GL_UNSIGNED_BYTE as the data type.  If I had been using GL_UNSIGNED_SHORT or GL_UNSIGNED_INT, I would likely see the problem arise.

Does this analysis ring true for anyone else?

Paul