• Welcome to ASR. There are many reviews of audio hardware and expert members to help answer your questions. Click here to have your audio equipment measured for free!

Building an LG-DAC - Optical output volume control made simple for LG TVs

Joined
Sep 3, 2023
Messages
14
Likes
19
Hello everyone! This is a write up of my experience trying to get better sound from my LG OLED TV, while keeping the setup as simple* as possible (*yeah, right… keep reading).

The main issue I was trying to solve was using the LG remote to control the volume of an external DAC.

One alternative that I tried with limited success was an HDMI ARC capable DAC (I tested the SMSL PS100 because it’s cheap). While it worked, I had two problems with that solution. First, I randomly got no sound from it, as if the device wouldn’t turn on from the CEC message and remained silent until rebooted. Second, while the LG remote could control the volume, the volume changes were too coarse and the level indicator was just a +/- icon on the TV, no 0-100 level or any reference at all about how loud it was.
So after a bit of research I found this website (https://github.com/hifiberry/hifiberry-dsp/blob/master/doc/reverse-engineering-lg.md) that explains how the LG Sound Sync feature works on LG TVs. Essentially, the SPDIF specification includes some control bits which, while not standardized, LG uses to transmit volume information so that a compatible LG soundbar can adjust the volume with an optical connection.

This has the main benefit of working at all times because there are no CEC messages that can get lost, and the volume indicator on the TV shows the 0-100 numbers I wanted. So far so good. This sounded like a good solution. The main caveat: it only works with LG soundbars and I wanted to use passive speakers with a separate amp, not an LG soundbar.

Then I stumbled across this GitHub repository (https://github.com/elehobica/pico_spdif_rx) which explains how to use a Raspberry Pi Pico (the smallest version, not the linux mini computer but a much simpler device) to create a DAC that has an SPDIF input and can output I2S signals to a compatible DAC. So my first thought was why not use this to receive the SPDIF signal from the LG TV, parse the SPDIF control bits and adjust the volume before outputting to the I2S DAC? Well, long story short, it works!

So here’s what works and what doesn’t:
  • Limited to stereo only, since LG Sound Sync is active only when outputting PCM and not a Dolby bitstream.
  • This alters the PCM data, so it’s essentially digital volume control. Can this be modified to use the downline DAC’s volume control? Probably but I haven’t tried.
  • Since it changes the PCM data, we could in theory output SPDIF after volume adjustment and connect ANY DAC instead of the $2 PCM5102A one I used.
  • I have no idea what the SINAD, jitter and other parameters are but for my use case, it sounds pretty good.
  • To do: avoid outputting noise then fed a Dolby or DTS bitstream.
  • To do: only adjust the volume when the LG Sound Sync feature is turned on. If not, set the volume to 100%.
So what did I do? It’s divided in three parts: Hardware, software, and configuration.

Hardware:

The pico_spdif_rx page shows how to connect everything. I used normal dupont cables to connect both the DAC to the Raspberry Pi Pico and the SPDIF receiver module to the Raspberry as a temporary** solution (**there’s nothing more permanent than a temporary solution…).

After connecting both the SPDIF receiver and the DAC, the only other connection necessary was the USB cable to power up the device.

Software:

The pico_spdif_rx exposes the SPDIF control bits and there’s one “issue” in the repository where this same topic was discussed.

So I adapted the code and came up with a working solution that uses the LG Sound Sync information to adjust the volume in a non-linear way so that the lowest steps in the scale from 0-100 are more fine-grained.

I also modified the number of SPDIF packets to keep in memory so that the delay was minimized, while also avoiding buffer underruns. This was necessary because I wanted to use this not only to watch TV but also to play games.

Configuration:

Go into the LG TV and select Optical cable as the sound output, and then under Advanced Settings enable LG Sound Sync. Not that this disables bitstream output of compressed formats and the TV will always output stereo PCM.

That’s it! Connect the optical cable to the Frankenstein of a device, the DAC's 3.5mm output to an amp or powered speakers and you’re good to go!

Here are all the changes to the code that I had to do to make this work. I’m not an expert writing C code so any tomatoes feedback is welcome.

In the main.cpp file, in the decode() function, just before the give_audio_buffer(ap, ab); part, I added this to the “else” statement:


C:
else {

        // I2S frequency adjustment (feedback from SPDIF_RX fffo_count)
        // note that this scheme could increase I2S clock jitter
        // (using pio_sm_set_clkdiv should have already include jitter unless dividing by integer)
        if (fifo_count <= SPDIF_RX_FIFO_SIZE / 2 - SPDIF_BLOCK_SIZE) {
            set_offset_clkdiv(CLKDIV_SLOW);
            //printf("<");
        } else if (fifo_count <= SPDIF_RX_FIFO_SIZE / 2 + SPDIF_BLOCK_SIZE) {
            set_offset_clkdiv(CLKDIV_NORM);
            //printf("-");
        } else {
            set_offset_clkdiv(CLKDIV_FAST);
            //printf(">");
        }

        if (ab->sample_count > fifo_count / 2) {
            ab->sample_count = fifo_count / 2;
        }
        uint32_t total_count = ab->sample_count * 2;
        int i = 0;
        uint32_t read_count = 0;
        uint32_t* buff;
        uint16_t c_bits;
        spdif_rx_get_c_bits(&c_bits, sizeof(c_bits), 7); // to get byte 7, 8
        volume = (c_bits & 0x07F0) >> 4; // 0 ~ 100 // here we get the volume as indicated by the LG TV
        mute = (c_bits & 0x0800) != 0; // here we get the mute flag

        printf("Volume: %d\n", volume);
        while (read_count < total_count) {
            uint32_t get_count = spdif_rx_read_fifo(&buff, total_count - read_count);
            for (int j = 0; j < get_count / 2; j++) { // here the volume is adjusted using a square function to get more granular steps in the lower end of the scale
                samples[i*2+0] = (int32_t) ((buff[j*2+0] & 0x0ffffff0) << 4) / 100 * volume / 100 * volume * !mute + DAC_ZERO;
                samples[i*2+1] = (int32_t) ((buff[j*2+1] & 0x0ffffff0) << 4) / 100 * volume / 100 * volume * !mute + DAC_ZERO;
                i++;
            }
            read_count += get_count;
        }
    }

In the spdif_rx.h file, I set the NUM_BLOCKS to 4. I got crackling sound when set to 2, with 4 I see no increased latency and no crackling.

Hope this helps someone!
 
Nice! I recently spent a couple of hours fighting with my father-in-laws new LG OLED and LG sound bar (he'd bought the OLED on my recommendation, so no pressure!). He couldn't get any of the TV remotes to control the soundbar volume, only the little sound bar remote worked and sometimes the volume would change unexpectedly (often on a programme change when the sound signal might switch from stereo to Dolby 5.1 or vice versa)

It turned out he'd connected the soundbar to the TV with both eARC HDMI and optical connections and this was confusing the hell out of the TV. I turned everything off, removed the optical connection, disconnected all other HDMI sources, switched it back on and let the TV find the sound bar again on the eARC connection. Once this was working, I was able reconnect the other HDMI sources and the volume has been working correctly ever since.
 
Nice! I recently spent a couple of hours fighting with my father-in-laws new LG OLED and LG sound bar (he'd bought the OLED on my recommendation, so no pressure!). He couldn't get any of the TV remotes to control the soundbar volume, only the little sound bar remote worked and sometimes the volume would change unexpectedly (often on a programme change when the sound signal might switch from stereo to Dolby 5.1 or vice versa)

It turned out he'd connected the soundbar to the TV with both eARC HDMI and optical connections and this was confusing the hell out of the TV. I turned everything off, removed the optical connection, disconnected all other HDMI sources, switched it back on and let the TV find the sound bar again on the eARC connection. Once this was working, I was able reconnect the other HDMI sources and the volume has been working correctly ever since.
Good point! Connecting an eARC compatible device automatically changes the sound output to eARC and Sound Sync is only available for optical output. With eARC you probably got the +/- icon indicating that a volume change was issued, but not the 0-100 indicator as with optical + Sound Sync.
 
Good point! Connecting an eARC compatible device automatically changes the sound output to eARC and Sound Sync is only available for optical output. With eARC you probably got the +/- icon indicating that a volume change was issued, but not the 0-100 indicator as with optical + Sound Sync.
I'm pretty sure I got the 0-100 volume display with just the eARC set up correctly, but i'm nowhere near the TV to check. Maybe I need to treat myself to a new OLED, but the Samsung 'Quantum Dot' KS7000 I bought in 2017 is still working fine, so I can't justify it yet :(
 
This is awesome, I’m really glad I came across this thread! I’ve been dealing with the same issue for months now. I even tried using a DAC with HDMI ARC support, but ran into the same problems you mentioned: unreliable behavior and coarse volume steps. I was just about to cave and buy an expensive AV receiver for my TV setup, but this seems like a more flexible and rewarding DIY alternative. I already picked up a Pico H and would love to try building this myself. This would actually be my first time building a custom hardware project like this, so if you’d be willing to share the list of components you used, it would really help me replicate your setup. Any additional tips or insight would also be greatly appreciated!
 
Hi od_aqua! I bought everything from AliExpress.
Apart from the Raspberry Pi Pico itself, you’ll need at least a DAC, a TOSLINK receiver and some dupont wires.
Search for “I2S PCM5102A” for the DAC, “DLR1160 receiver” for the TOSLINK receiver, and “dupont wires” for a bunch of wires with female connectors.
My Pi didn’t have the header soldered so I did it myself (barely). The same with the DAC, it came with a single row of 5 pins I had to solder to the board.
Everything was connected with the dupont wires and connectors. Even the DLR1160 receiver can be connected this way if you remove the plastic housing from the dupont connector from a wire, push the metal “pin” so it leaves less room inside the connector and then attach the plastic housing to the wire again. Far from perfect but it works as a quick way of testing if there’s anything wrong.
By far the most difficult thing was setting up the VSCode environment and compiling the project. Had many problems with the pico-SDK and pico-extras not being found when compiling. Let me know if you run into those issues so I can help you! If you want I can send you the compiled file so you only need to drag and drop into the Pico to install it.
 
Hi od_aqua! I bought everything from AliExpress.
Apart from the Raspberry Pi Pico itself, you’ll need at least a DAC, a TOSLINK receiver and some dupont wires.
Search for “I2S PCM5102A” for the DAC, “DLR1160 receiver” for the TOSLINK receiver, and “dupont wires” for a bunch of wires with female connectors.
My Pi didn’t have the header soldered so I did it myself (barely). The same with the DAC, it came with a single row of 5 pins I had to solder to the board.
Everything was connected with the dupont wires and connectors. Even the DLR1160 receiver can be connected this way if you remove the plastic housing from the dupont connector from a wire, push the metal “pin” so it leaves less room inside the connector and then attach the plastic housing to the wire again. Far from perfect but it works as a quick way of testing if there’s anything wrong.
By far the most difficult thing was setting up the VSCode environment and compiling the project. Had many problems with the pico-SDK and pico-extras not being found when compiling. Let me know if you run into those issues so I can help you! If you want I can send you the compiled file so you only need to drag and drop into the Pico to install it.
Thanks, this is super helpful! I’m going to order the parts ASAP. I was thinking it might be a fun exercise to try compiling the code myself, but there’s a good chance I get frustrated and just bail, so having a precompiled copy would be amazing. I really appreciate the offer! I’ll report back once I start piecing everything together, probably in the next week or two.
 
So I finally received all my parts and just finished putting everything together and it works phenomenally! While my current setup is still a bit janky and would definitely benefit from soldered connections and a proper enclosure, as my first real hardware prototyping project, I’m really proud of how it turned out. Huge thanks again for all your insight and the code modifications, they were very helpful.
You were absolutely right about the hardest part being the environment setup. I probably spent around two hours just installing all the required tools and dependencies to get the project to compile. One of the most time-consuming hurdles was getting CMake to find the pico-extras folder properly, but reading through the pico_spdif_rx repo more thoroughly helped me sort out the configuration.
Another compilation issue I ran into was that the mute variable referenced in the volume/mute patch wasn’t defined anywhere. I ended up replacing all instances of mute with mute_flag, which was defined in the original main.cpp, and everything compiled cleanly after that. I am curious, in your build, did you declare a separate mute variable, rename mute_flag to mute, or did your version of main.cpp already use mute?
After having the device running for a couple of days, I did run into one intermittent issue where, occasionally, when turning the TV on, the PIO seems to misinterpret the soft edges of the SPDIF data stream and locks onto a “fake” preamble. This causes it to sync with the wrong cycle, resulting in a glitchy repeating output until I unplug the Pico or trigger the mute logic. This only happens at startup when there’s no valid signal yet, and now I definitely see the value of adding the 74HC04 inverter to clean up the edges. That’ll be the next addition to my setup.
Thanks again for all your help, not just for the code, but also for making this project approachable for someone whose new to this stuff!
 
Last edited:
Nice! Good job od_aqua! I believe you may be right about the mute flag. Completely forgot that I ran into the same problem with the mute flag and had to correctly declare it as a variable.
I do run into some garbage data when it powers on or off, so instead of plugging it into a TV USB port I used an old cellphone charger to provide constant 5V power. It does seem more like a typical pop sound when powering on or off a power amp or cheap DAC though.
The only time I ran into some glitches was when setting the buffer too low, but apart from that it’s been perfect.

Let me know if you run into any other issue!
 
Back
Top Bottom