nullpointerninja
Member
- 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:
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 anytomatoes 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:
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!
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%.
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
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!