Game audio on a MIB2

The frontend on this Volkswagen MIB2 could draw games and accept a controller, but it had no sound. QNX on the unit has an audio service, an amplifier, and a factory source manager between a native process and the speakers; the path that makes an ordinary media source audible is therefore much longer than a device write suggests. Sending PCM to a plausible device was easy enough to try. Making it play through the right route without leaving the dashboard in an awkward source state took a day of small experiments.

The useful boundary was simple: each probe was a short-lived process, used an existing entertainment route, and left factory processes alone, while its receipt recorded source state before the tone, the ready callback, every accepted write, restoration of the transmitter state, and the source state after the process exited. Accepted bytes only describe one part of the path to audible output, so source selection stayed in the test.

The first sound was in the wrong place

The first probe sent a short tone to a mono lane while Radio was playing. All three sends were accepted. The tone was audible, and Radio kept playing under it without stopping, fading, or pausing.

That proved the process could reach an audio lane. It also established what that lane was for: an overlay mixed with the existing entertainment source. Game music there would sit on top of Radio at the level selected by the factory mixer. It suited alerts. A game needs to replace its source cleanly.

The stereo lane looked more promising. A left-to-right tone streamed at real time, yet it was silent. A control flag made the transmitter consume the stream, and the speakers still stayed quiet. The bytes had a destination; the amplifier was playing a different logical source.

Following the source manager

The next probe asked the factory source manager for the entertainment route used by media playback, waited for its ready callback, enabled the stereo lane, then played the same tone. The existing Radio source stopped. The route reported ready after 812 milliseconds, and 384,000 bytes of stereo audio were accepted in 1,876 milliseconds; its receipt also showed the expected source change and a clean end to the short-lived process, which was enough evidence to treat the speaker result as a routed-audio result rather than a stray mix.

The tone came through the speakers. That is the narrow result that mattered: a native process can supply interleaved 48 kHz stereo to the dashboard speakers when the factory entertainment route is active.

Measured audio-path milestones

Releasing the route exposed a sharp edge. The source manager stopped it and did not resume Radio. The volume knob remained locked until selecting Media and then Radio on the HMI. I changed the game-run close path to request the source that had been active before launch, so it does not leave an empty entertainment state behind.

A game host, not a tone generator

The SNES host was the first emulator wired to the new sink. It converts the core's samples into the stereo format and writes in small chunks while the game runs. The route opened in 2,248 milliseconds in a repeated run, with its fade complete at 1,012 milliseconds. The host delivered 1,842,001 input frames as 2,759,569 output frames across 5,749 chunks. There were no dropped chunks and no failed sends.

The first game-audio run was audible, then the timing made the next problem visible. Some sends waited for the transmitter's render period. In one run, about half of them waited three milliseconds or more during the first twenty seconds, then the waits almost disappeared. The audio service uses an adaptive sample-rate converter, so the curve fits a rate estimate settling after a poorly primed ring buffer; its algorithm and target fill remain an offline reverse-engineering question. I had tried a rate adjustment during the first run. The better repeated result came without it, so that adjustment was removed.

Audio dips in Yoshi's Island had another cause. It reached 58 frames per second against a 59.9 frame target, with 79 frame overruns in about 85 seconds. The sink reported no drops or failed sends, while its five-second windows delivered 461, 469, and 472 chunks where real time needed 500, which drains the 250 millisecond cushion even though the service accepts every chunk that reaches it. The Super FX emulation was falling behind and starving the audio cushion. Super Mario World held 60 frames per second and stayed clean.

The first frontend run, and a different bottleneck

The audio host went into the ES-DE frontend and a Super Nintendo game played through the dashboard. On exit, the saved source returned and the volume knob worked again. The game itself opened its sink at 2,137 milliseconds, completed the route fade at 1,015 milliseconds, sent 1,896 chunks without drops or failures, and exited after 21 seconds through a held HOME input.

Starting the library took 91,706 milliseconds. The two timings in the lower half of the figure should not be compared as though they are one problem: the audio path had completed in about two seconds, while startup was spending its time walking the games card. Super Nintendo took 38.8 seconds, Game Boy 17.3, and Game Boy Colour 17.4. Those were the three systems with bulk ROMs and covers. The frontend was still calling stat once for each cover lookup on a FAT directory, where each call cost about 23 milliseconds, so a card layout that looks ordinary in a file browser becomes punishing when thousands of lookups are issued serially during every return to the game library.

That discovery changes the next task. The audio path is established for the SNES host, subject to the slower Super FX titles. The library needs to trust the directory index it has already built instead of checking every cover again. The remaining hosts can take the same audio sink after that startup fault is removed.