hex007 / freej2me

A free J2ME emulator with libretro, awt and sdl2 frontends.
Other
469 stars 72 forks source link

FreeJ2ME is unable to play certain audio media types, even known ones. #149

Closed AShiningRay closed 8 months ago

AShiningRay commented 2 years ago

This issue is related to PR #143, although i don't think that one by itself will solve the problems listed here, or else it would be too big.

Many NET Lizard games such as Shadow of Egypt 3D (that uses a custom 3D engine not dependent on m3g or MascotCapsule V3, so it renders fine) try to play "audio/X-wav" files, but FreeJ2ME fails to do so. That's because it's setup to only play "audio/x-wav" in a case sensitive way. Also, a few Gameloft games such as Assassin's Creed Revelations and Motocross Trial Extreme try to play "audio/wav" files, an due to only having type checks for "audio/x-wav", FreeJ2ME also fails to play them, despite those also being wav files.

However, allowing FreeJ2ME to play "audio/wav" files from Gameloft games exposed an interesting behavior: Those aren't encoded in the standard PCM format, instead using ADPCM to achieve better space efficiency for the samples. This is also a problem since FreeJ2ME doesn't support ADPCM as of yet.

There's also the possibility that we'll face other encoding formats for wav as well, so adding a try-catch when trying to load data on the wavPlayer in order to throw info about problems in the loading procedure might be a good idea.

Besides wav, the midiPlayer also has some minor issues that need to be dealt with, such as games that constantly call the start() method, with one of those being Star Warriors (fan-translation of a chinese port of Raiden for J2ME). Adding some checks for isRunning() and tickPosition like wavPlayer has in some instances does solve those problems.

AShiningRay commented 1 year ago

Finally made some more progress on this issue, this time on IMA ADPCM decoding. Been really short on time lately but managed to work on that for a bit while i still struggle to port the retroarch core to win32.

So far i've managed to introduce a way of reading the wav files' headers to check whether it's IMA ADPCM or not, and even got to the initial stages of decoding. However, i'm not sure how to keep going on the decoding itself. Tried to implement it based on multiple sources cs.columbia and MultimediaWiki being some of those, but so far, no good results.

Games that depend on that format don't crash of fail to load those files anymore, and they do play something when it's time to play those streams (though i had to resort to using SourceDataLine for that since InputStream or AudioInputStream simply refuse to play those due to the, for now, incomplete header and there's not much hope that those will play any better), but the decoded data is really far from what the streams should sound like, and at this point it's very likely that i'm messing things up somewhere.

Currently it's sitting at the HEAD of my fork's dev-clean branch, so if anyone with more experience or a better understanding of how media decoding and etc works on Java could shed a light, i'd really appreciate it.

recompileorg commented 1 year ago

My plan with adpcm was to convert the stream to pcm (well, create a new stream anyway) so we could treat it like an ordinary wav. ADPCM is allegedly 'simple' to decode but it's surprisingly hard to find good documentation on the decompression these days. I think I have a few docs lying around from when I last looked at it, I'll try to remember to post them here if I find them.

AShiningRay commented 1 year ago

That's basically what i'm doing (well, trying to) as well. The plan is to start by marking the beginning of the stream, reading the first 44 bytes that comprise the RIFF header to find out if the AudioFormat is IMA ADPCM (that would be format 0x0011, or 17 as int based on some preliminary testing), resetting back to that beginning and then decoding the stream if it is, indeed, IMA ADPCM.

Ideally what i intend to do is decode everything after the first 44 bytes from IMA to signed mono PCM_16LE (trying to convert the header is not a good idea anyway) on memory through byteArrays to rule out any additional latency by using storage, convert that byteArray into a ByteArrayInputStream, build a new header for it to describe what kind of format this new stream has, as well as sample data and file size (which is another tricky part in itself), then building an InputStream from that data.

Although building an AudioInputStream by passing the data and the audio format is also another approach and likely a better one since this one at least tried to play the decoded stream but complained that the Frame Size was > 1 or something (the decoded mono PCM_16LE stream has a frame size of 2 if i'm not mistaken). Using either InputStream or AudioInputStream is ideal since we could reuse basically the whole wavPlayer code for playback control and such instead of relying on if-else logic to handle SourceDataLines for ADPCM.

AShiningRay commented 1 year ago

Updating this one after a while: finally managed to do some good progress on this one. Now not only are the decoded streams much closer to the expected output, they also run through the same steps a supported wav file would, which means no more SourceDataLines!

Here's an image from Motocross - Trial Extreme (known to not even boot due to the lack of adpcm support on the compatibility list) reaching the menu with wav header debugging enabled:

image

Still, there are a few issues with the implementation as it is: The audio, while now certainly reflective of the source files, is still VERY low quality, and for some reason the new header is being built with bitsPerSample as 10 instead of 16 although i'm not sure if that would actually cause any issues. Not sure why the header is behaving like that either, all the debug code around it points to bitsPerSample being 16, and the bitwise logic appears to be correct as well, or else this would also happen on other header entries.

AShiningRay commented 1 year ago

Another update: Audio is now far better and closely resembles what it should sound like, it was just a matter of adding a "simplest ever" low-pass filter to attenuate the high range of decoded samples.

Given that this small header mismatch doesn't seem to cause any issues, i'll be opening a pull request for this implementation.

Edit: To prevent this issue from getting too big in the future, i'll set the PR to close it because AMR and MP3 audio types make more sense as separate issues.

AShiningRay commented 8 months ago

Yet another update: That "low pass filter" is not needed anymore.

Decoding works as expected no matter the stream's size, it's rather lightweight, and the header creation method allows us to dump audio if needed. For more info, look here.