• 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
30
Likes
30
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.
 
  • Like
Reactions: EJ3
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!
 
This is awesome, Could some one share a picture of their "temporary" setup. I am interested in setting this up, my use case would be

lg tv --- (optical)--- > pico ---- (either optical or coaxial) ---> DAC accepting digital input. Is this doable?
 
It should be possible. Either getting the pico to output SPDIF directly or through an I2S to optical/coaxial converter. This way you’re essentially using the pico as a digital preamp before feeding any commercial DAC.
 
I'd like to build myself this adaptor. I cannot find a main.cpp file to edit.
Would you happen to have an already compiled binary for the Pico in.uf2 format ? Or could you point me at the right file to edit before compiling ?

I tried to look here but couldn't find the code you mention:
 
Yes, it’s directly connected. No resistors and no capacitors are necessary for TOSLINK according to that repository and it works fine
 
Anybody else noticed volume goes up when activating sound link? Actually I think it's the other way around and volume is reduced when sound link is off. With activated sound link it's as "loud" as my other sources (I mix them using motu 8d)
 
I finally managed to build it. I really struggled with the compilation environment after trying in Windows and Linux. Finally got it working in Linux using the docker mentioned in the project's GitHub readme. I also had to edit the mute_flag.
If someone wants it I can share the ready-to-flash uf2 file for a standard Pi Pico as I have it now.

Thanks a lot for this project nullpointerninja ! I got a WiiM Ultra+Vibelink combo but the Ultra introduces a bit of lag which is not ideal for rhythm games. So I think I'll sell the WiiM Ultra and use this Pico DAC for connectivity to my TV + ESP32 Squeezelite for audio streaming.

I seem to have occasionally some crackling in the sound although it's rare. I'll try compiling another firmware with NUM_BLOCKS to 5 or 6, it is currently at 4. My TV is an LG G5.

Also, it'd like to use the DAC in my WiiM Vibelink amp instead of the PCM5102a in the future, in theory I could buy this board and just connect it in place of the PCM5102a, right ?
 
Last edited:
Nice to hear you got it running @snk4ever!
In theory you could connect to that board but I never got to try it. The other option would be getting SPDIF output directly from the Pi but that’s way above my current understanding of how this all works.
 
LG Soundsync DAC - Imgur.jpg
LG Soundsync DAC - Imgur.png


Photos of the working quick-and-dirty assembly, with volume control on the TV. The volume control with this is much more reliable and natural feeling than with HDMI ARC.

And my plan for a combined device to replace the WiiM Ultra. I want to use two physical rocker switches to route the I2S signal from 2 inputs (SqueezeLite and this Pico project) to 2 outputs: headphones jack with the PCM DAC or the Amp through SPDIF.

LG Soundsync DAC - Imgur(1).png


Links to firmware with different NUM_BLOCK values, nb5 works well for my TV:
NB4
NB5
NB8
 
Last edited:
Reading more about the WM8805 board to output SPDIF, I think it would work with Squeezelite ESP32 that exposes a MCLK but not with the pico_spdif_rx that seems to only expose 3-wire I2S (no MCLK).

I guess I'll do it all on analog for now with PCM5102a and monitor the DSPi project on this forum that may be able to do the I2S to SPDIF conversion someday with added benefit of adding an EQ in the chain.
 
Back
Top Bottom