My LG CX shipped without DTS, a surround-sound audio codec competing with Dolby Digital. A month of firmware archaeology and assembly work later, it was sending DTS 5.1 to an Onkyo receiver over HDMI ARC.
Update, 27 September 2026: The working setup is now packaged as a build-it-yourself kit and an on-TV activator. Fresh-boot activation and DTS playback have worked on the CX, and a clean source clone reproduces all eight audio assets byte-for-byte.
The work spanned the full audio stack, down to the DSP itself, the TV’s dedicated audio processor. We reconstructed DTS support inside the CX’s own DSP firmware and repaired the media parsing, host libraries, and kernel connections around it. That combination ran in memory on the television, delivering DTS over ARC to the receiver.
The starting point was a gap in LG’s product history. The 2019 C9 supported DTS. The 2020 CX did not. The 2023 C3 and 2024 C4 supported it again. While their firmware gave us working DTS implementations to compare with the CX, this was no trivial affair.
I wanted to obtain those firmware images, compare their implementations, and reconstruct the missing pieces for the CX.
One removal is visible in the published source. The code that recognises DTS audio tracks inside MKV files is present in upstream GStreamer 1.14.4, but deleted in the change labelled “LG changes for OLED CX”. The public source mirror records an author date of 26 December 2019, before the CX launched, and an import date in 2022.
I used Claude and Codex for the investigation and implementation. Claude did much of the root and host-pipeline work. Codex handled firmware acquisition and DSP reconstruction, then also worked on the final host-side repair.
Choosing reference firmware
We compared firmware from the C9, C3 and C4 to work out which parts of their DTS support could be adapted to the CX. Each model served a different purpose:
Different parts of the stack needed different references. The C9 was close to the CX’s userspace audio stack, but its DSP came from an older configuration and toolchain lineage. The C3 was a better DSP reference, even though its newer host audio stack had changed substantially.
That wasn’t our initial conclusion. Claude had ruled out the newer TVs as DSP donors on instruction-set grounds. Codex’s later comparison found that the C3 was actually a closer architectural match to the CX than the C9. Cross-checking that assumption opened up the reference we ended up using.
The C3 and CX shared Cadence Tensilica HiFi3 instructions, the same toolchain generation and nearly identical DSP startup code. That made it possible to reuse compiled DTS code, relocating it and adapting its calls to the CX’s firmware. The hardware communication and memory layout still differed, so a shared processor architecture didn’t make the complete firmware interchangeable.
Finding and collecting firmware
To get the files, I worked backwards from the TV’s update mechanism. Claude refused to brute-force the firmware CDNs, so I had it write up what we knew about the updater and the tools that emulated LG’s update service, and handed that brief to Codex instead. This was my first time using it.
The brief treated consumer downloads as unavailable and the old update service as dead. Codex first found an official LG download for the CX’s 04.64.00 firmware and extracted its filesystem. That gave Claude the updater executable to examine, and I passed the recovered protocol details back to Codex to continue the search.
The updater showed us how the TV found its firmware: it asked LG’s update service what was available, then downloaded the image from the address in the response. Using the recovered request format, with base64-encoded XML, Codex got back the CX’s firmware name, size and a complete download URL. Our earlier requests had returned empty image fields, but the service still worked. LG chooses which update to offer; this wasn’t a way to retrieve any historical release on demand.
For older releases, we found another useful distinction: a public support-page link could stop working while the firmware itself remained on LG’s content delivery network (CDN). Recovering an exact historical URL could therefore get us a file that appeared to have disappeared.
The search expanded through regional listings, preserved firmware indexes and community archives. One sweep added eight C9 and CX images; the C4–C6 collection reached roughly 35 GB of downloads and extracted packages. Some promising URLs returned only HTML denial pages, so we validated the downloads and kept model and region identities, hashes and signature checks alongside the files.
For the C3, the release mattered as much as the model. Recent C3 and C4 firmware shared a newer software branch, compiled for their different hardware, with audio modules such as IAMF that the CX didn’t need. The C3 firmware we initially collected was a later update, not the version it had shipped with.
Codex recovered the C3’s launch firmware, 03.11.10.01, from a community archive and verified its hashes and LG signatures. Its DSP image had the CX’s decoder set plus DTS, without additions from later updates such as IAMF, making it a better reference for the reconstruction.
Developer Mode and root access
LG provides an official Developer Mode app for people building apps for webOS, the operating system on the TV. It lets developers connect a computer to the television, install their own apps, and test them on the real hardware.
That connection also gave us a shell: a text-based way to run commands on the TV from a computer. The shell ran as a user literally named prisoner, which turned out to be an accurate description of its permissions. We could investigate from there, without having permission to replace the running media libraries or modify the live audio system. For that, we needed root: the administrator-level access normally reserved for the system itself.
The root investigation eventually found a flaw in the video encoder driver’s memory-mapping path. It allowed physical-memory access from the developer environment, which we used to change the test process’s credentials. The proof was a process running as root, creating a root-owned file where it had previously been denied.
Root access had to be re-established after each boot. The firmware changes were temporary too. The approach was to place replacement libraries in RAM and bind-mount them over the original files, patch the live kernel in memory, and load a rebuilt DSP image through the runtime audio-loading mechanism. The system firmware partitions stayed untouched. A reboot returned those components to stock.
The media framework’s plugin registry, a cache of available playback components, was an exception: it lived on flash and was regenerated during deployment. The firmware changes themselves remained in memory.
The ability to recover by rebooting made repeated experiments practical. It did not prevent crashes, hangs, or occasions when normal audio stopped working. Recovery and reliable deployment became substantial parts of the project.
Restoring the audio stack
LG uses GStreamer to assemble the software pipeline that reads a media file and routes its audio for playback. Restoring DTS meant repairing several stages of that pipeline, as well as the lower-level software controlling the DSP.
An MKV file first had to be recognised as containing DTS. The stream needed a parser. GStreamer’s selection machinery had to accept the codec and construct the right decoder element. LG’s audio library needed to translate that into a kernel request. The kernel needed to select an implementation in the DSP. Finally, the encoded audio needed a working route to ARC.
We organised the restoration into eight layers:
This is an inventory of the affected components, not a claim that every audio byte passes through every element in that order. The fake decoder, for one, is deployed on every audio stream as a transient stand-in and then swapped out for a real decoder.
One early inventory counted 42 changed bytes across several host files and the kernel. That was an intermediate state: it excluded the added parser and rebuilt DSP, and later host repairs increased the total.
The module lookup was a small but necessary repair. The kernel still had a lookup entry for DRA, a codec whose host-side path was disabled on this platform. We repurposed its codec comparison and changed the module name from dec_dra to dec_dts, so the DTS request could resolve to the rebuilt DSP module. That was a six-byte patch in live kernel memory; it disappeared on reboot.
Existing DTS names and constants helped identify relevant code, but did not establish that support was still implemented. A configuration file could rank a decoder that no longer existed. An enum could name a codec without any executable implementation behind it. Finding the word DTS in a binary was a starting point for investigation.
Rebuilding the DSP firmware
The television’s audio DSP runs its own firmware. Giving GStreamer permission to accept DTS does not create the missing implementation in that firmware.
For LG, omitting DTS could have been as simple as turning off a codec option when compiling the DSP firmware. We don’t have their build configuration, so the exact mechanism is an assumption. But a build flag can leave the implementation out of the binary entirely. With only the shipped firmware, there was no equivalent switch for us to turn back on: we had to reconstruct the missing code and reconnect it to the rest of the stack.
We explored whole-image replacements, but newer images differed in their platform communication, interrupt arrangements, memory assumptions, and assigned DSP roles. A smaller auxiliary DSP image could fit in the available slot while lacking other functionality the CX needed.
The eventual approach kept the CX’s own DSP firmware as the base. Codex isolated the C3’s DTS code and the data it depended on, fitted them into the CX image, and rebound their connections to the CX’s native framework, allocator, and platform services.
Restoring DTS meant sculpting it into the CX’s existing DSP firmware. It involved working directly with assembly and linked binary structures: selecting the pieces, changing their placement, rewriting references, and adding the bridges needed to make them operate in our generation’s firmware.
The existing DTS algorithm was retained from the donor. The work was in reconstructing its surroundings and connections accurately enough that the CX could run it. That included much more than copying a contiguous block of code.
The donor code expected functions and data at particular memory addresses. Moving it into the CX firmware meant finding and rewriting those references so it would use the right locations. Instructions that load values, stored function addresses, and calls into existing CX functions all needed attention. The tables that initialise the firmware and register its codecs also had to agree with the new layout.
The reconstruction checks included 20,024 literal-load instruction rewrites, 2,912 address-value mappings, and 98 imported call sites. These counts describe one verified stage of the reconstruction.
Even reading the instructions correctly was a problem. The TV’s audio DSP uses Xtensa, a processor architecture that can be customised with extra instructions. A disassembler translates machine code back into assembly. A generic one could interpret specialised HiFi3 instructions as plausible but completely wrong branches and calls. That would create imaginary dependencies and corrupt a relocation plan.
Codex built an Xtensa version of objdump, the disassembler from GNU Binutils, and loaded a public Cannon Lake configuration from the Sound Open Firmware project. This configuration overlay describes the processor’s instructions and registers, giving the tool the information it needs to read the specialised HiFi3 code correctly. Codex checked its output against independently linked copies of the DTS code in the reference images. The analysis covered 2,603 vendor functions, then explicitly recovered the switch-table targets that a simpler control-flow walk would have missed.
Memory budgeting also had to separate several different limits. The size of the firmware file, the space needed after startup copied it into DSP RAM, and the heap needed during playback were different quantities. A zero-filled region was not automatically spare space. A higher address in the newer firmware did not automatically imply that the CX lacked that much memory.
We also discovered that my own TV used a different hardware revision from the CX image we had started with. The donor analysis remained useful, but the addresses and startup changes had to be worked out again for the correct revision.
Booting the rebuilt image and processing DTS required further fixes.
Fixing DSP function calls
An early candidate hung while registering DTS with the DSP’s audio framework, the step that makes the codec available for use. The problem was in our assembly bridge: it overwrote information the processor needed to return from the function, so execution entered the CX’s registration code but couldn’t get back.
Xtensa’s windowed calling convention sets rules for how functions share the processor’s registers and preserve that return information. Our bridge broke those rules by reusing a register to hold the destination address.
Codex replaced that scheme with one stub for each of the 98 imported call sites counted above. Each stub was a short assembly bridge that preserved the register state needed to return correctly. The next candidate completed registration and booted. We verified registration in the live registry state.
This is the assembly rewriting I mean when I describe sculpting the DSP. It required understanding what the instructions did in their actual calling convention, then constructing and checking new instruction sequences.
With the host patches and DSP running together, DTS reached the new module. It opened, connected and started, but crashed while processing its first buffer.
Testing and mistaken conclusions
Testing on the TV meant checking both what a result showed and whether the intended changes were actually running. We got both wrong at times.
A log message we took as confirmation of DTS passthrough also appeared during MP3 and AC3 playback, so it proved nothing about DTS reaching the receiver. Another test seemed to produce far fewer errors, but had simply run for less time. Comparing errors per second showed essentially no improvement.
The most expensive of those came from a single pipeline dump. It showed working AC3 passthrough as the decoder proxy, the fake decoder and the Matroska demuxer, with no sign of LG’s real audio decoder, while DTS showed those three plus the decoder and chose to decode rather than pass through. The obvious reading was that the real decoder was what forced DTS down the wrong path. Both halves of that were wrong. The fake decoder appears on every audio stream regardless, so its presence proves nothing, and the dump had simply missed libraries, most likely by sampling at start-up rather than during steady playback. The real decoder was in the working AC3 path all along, and several conclusions that rested on its absence had to be thrown out.
Even establishing a clean test was troublesome. A reboot command could fail inside the restricted shell, leaving the previous kernel running. That shell could also see a different set of mounted files from the playback process, making a correctly deployed library appear absent. We added checks for uptime, the replacement libraries and the DSP image actually loaded in memory, including which library the playback process was using.
Those checks didn’t cover everything. Standby could leave the audio stack in a broken state while the patches still appeared to be in place, and a buffering log client could lose its evidence when the TV crashed. Decoder activity helped us follow playback inside the TV, but confirming working passthrough still required checking the receiver and listening to the audio.
Getting DTS over ARC
For ARC playback, the receiver would decode the audio. The TV needed to supply the compressed DTS stream, rather than PCM, the decoded audio samples. DTS-HD soundtracks can include a backwards-compatible DTS core, which is the part we wanted to extract and send over ordinary ARC.
Getting the DSP to supply that stream took several attempts. Early versions either decoded to PCM or selected the encoded output with a sample rate of zero. One attempted fix never ran during playback. After finding the active code path, we got the DSP to extract the DTS core and report the correct 48 kHz sample rate. We also fixed a buffer lock in our own output guard that had been stalling playback.
The DSP could now produce usable DTS, but the receiver still wasn’t getting it reliably. The TV’s main audio software would briefly select passthrough, then switch the output back to PCM. That moved the investigation back out of the DSP and into the CX’s audio library, liblxa.
Comparing it with the C9 version, Codex found three decisions that routed DTS back to PCM: an initial eligibility check, a general output selector and an ARC-specific selector. Our earlier repairs had left those decisions in place. The final audio-library patch allowed DTS through when ARC passthrough was selected, while keeping PCM available when explicitly requested.
Offline tests simulated 244,016 cases, with additional checks for corruption and output-selection rules. For the playback test, only the audio library changed. The rebuilt DSP firmware, kernel patches and other plugins stayed in place. We relaunched Plex without rebooting the TV or reloading the DSP, so we could test the effect of that final repair.
Playing The Sopranos confirmed that programme audio worked, with the Onkyo reporting DTS 5.1. Its commentary track was two-channel DTS, confirmed in the TV’s logs, and came through as DTS 2.0.
Plain DTS 5.1 sources came out as 5.1. The DTS-HD MA 7.1 source came out as DTS-ES Matrix 6.1, because its 5.1-compatible core carries matrix flags for the back-surround channel and the receiver acted on them. That difference is useful evidence in itself: the receiver could only switch decoding modes per source if the core’s own metadata was reaching it intact. Had the TV been re-encoding or reconstructing the stream, every track would have arrived as undifferentiated 5.1.
The mode changes are supporting evidence rather than proof: the matrix flags weren’t inspected directly, and re-encoding can preserve or regenerate metadata.
Packaging it all up
I wanted to keep the firmware changes temporary while R&D continued. So I built a small app that could reapply them from the TV after a reboot. It bundles the rebuilt DSP image, patched libraries and activation tools, checks compatibility and verifies the result. There’s no computer or firmware download needed for activation, and a reboot returns the firmware components to stock.
Developer Mode is still needed to install it, but routine activation no longer needs a separate manual root step. A separate worker obtains temporary root for the hardware checks and activation; the app’s long-running service stays unprivileged and exposes no general-purpose root-command interface. Checking status also uses temporary root, though it leaves the audio stack unchanged.
Installing the app writes Developer Mode storage, and Developer Mode expiry can remove it. The audio-library binds, kernel patches and DSP registration still disappear on a real reboot. The app provides neither automatic startup nor permanent root; standby does not count as a verified reboot.
Packaging it for someone else also meant making the components reproducible rather than redistributable, so that no proprietary code ships in the repository. The build kit takes the exact CX 04.64.00 and C3 03.11.10 firmware packages, reconstructs seven firmware-derived assets and compiles the parser from upstream GStreamer source. A clean clone rebuilt and imported all eight outputs, matching the working files byte-for-byte. The build replays the reconstruction we worked out; it doesn’t require repeating the reverse engineering or obtaining private prebuilt files.
The initial build and installation need a computer, with LG’s matching SDK, the extractor and its keys, and the specified GStreamer source for the build. The build guide identifies the exact USB firmware files; an update-service image with the same version won’t substitute. Users supply those inputs themselves, including the historical C3 release that LG no longer provides at its original download link. Neither image is flashed onto the CX, and no C3 television is needed.
The repository contains source, app build and installation instructions and recovery guidance, with no firmware binaries or ready-made app download. The supported target remains OLED55CX6LA / O20B0 on firmware 04.64.00. Kernel patch addresses are specific to that build, so other CX variants and newer firmware aren’t qualified.
Results
DTS core passthrough works from Plex on my LG CX over HDMI ARC. The Onkyo received and decoded DTS 5.1 with correct programme audio, and the DTS 2.0 commentary track worked too. I tested both manual deployment and the TV app after fresh boots, and DTS playback worked.
The captured test window recorded 16 encoded-output configurations and 7 PCM configurations. The native encoded-audio renderer, the component feeding compressed audio to the output, was connected 16 times; PCM renderer connections appeared around playback transitions and reconfiguration, with encoded output returning for DTS playback. ARC output ran at 48 kHz with eARC disabled. This result covers DTS core, including from DTS-HD sources, rather than lossless DTS-HD MA output.
Long-duration stability and a complete AC3/PCM regression check remain unfinished. The live app tests used the original packaged build; building the app from the clean public repository, installing it and confirming fresh-boot playback is still to come. Setting up the SDK from scratch on a new machine also remains untested: the successful rebuild used an existing installation. The validation notes track those remaining checks.
The app source, firmware build tools and instructions are available on GitHub. DTS decoding through the TV’s own speakers is a separate unfinished branch and isn’t included in this release.