Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[LG Sound Sync] Document and implement muting #34

Merged
merged 3 commits into from
Aug 18, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 23 additions & 2 deletions doc/reverse-engineering-lg.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,18 +87,39 @@ Looks like this changes some status bits - cool :-)
0xf62: 00 00 00 00 00 00 00 00 00 00 02 A0 00 00 00 13 5F 04 8A 60 02 00 8C 04
```

## Update: Data from a LG OLED55C9 (with volume 53%)
### Corresponding status when connected to a LG OLED55C9

```
0xf61: 00 00 00 00 00 00 00 00 00 00 02 B0 00 00 00 03 5F 04 8A 60 02 00 0C 04
0xf62: 00 00 00 00 00 00 00 00 00 00 02 B0 00 00 00 03 5F 04 8A 60 02 00 0C 04
```

## Muting

Pressing the mute button on the remote toggles two bits:

```
0xf61: 00 00 00 00 00 00 00 00 00 00 04 E0 00 00 00 05 0F 04 8A 60 02 00 0C 04
| |
0xf61: 00 00 00 00 00 00 00 00 00 00 0C E0 00 00 00 0D 0F 04 8A 60 02 00 0C 04
```

More visible at the bit level:

```
4: 0100 5: 0101
| | | |
C: 1100 D: 1101
```

(Data collected using a LG OLED55C9 with volume set to 80%.)

## Conclusions

It seems the volume information is encoded multiple times.

It might be the easiest way to use byte 16.5 (half of byte 16 and 17) that gives the volume as 0-100.
It might be the easiest way to use byte 16.5 (half of byte 16 and 17) that gives the volume as 0-100 when unmuted.
When muted, the first bit in that byte is set.

Checking bytes 17.5/19 for 0xF048A seems to indicate that Sound Sync is active.

Expand Down
31 changes: 17 additions & 14 deletions hifiberrydsp/lg/soundsync.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@

from threading import Thread

from hifiberrydsp.hardware.adau145x import Adau145x
from hifiberrydsp.filtering.volume import percent2amplification
from hifiberrydsp import datatools

try:
from hifiberrydsp.hardware.adau145x import Adau145x
from hifiberrydsp.hardware.spi import SpiHandler
# depends on spidev and is not required to run tests
except:
Expand Down Expand Up @@ -86,29 +86,32 @@ def is_spdif_active(self):

def try_read_volume(self):
spdif_status_register = 0xf617
return self.parse_volume_from_status(self.spi.read(spdif_status_register, 6))
return self.parse_volume_from_status(self.spi.read(spdif_status_register, 5))

# Volume ~~~~~
# 0: 00f048a..$ This is what the SPDIF status registers look like with different volume levels set.
# 1: 01f048a..$
# 2: 02f048a..$ We check for f048a (SIGNATURE_VALUE) to see if LG Sound Sync is enabled.
# 3: 03f048a..$
# 100: 64f048a..$ The byte to the left is the volume we want to extract.
# ~~
SIGNATURE_SHIFT = 2 * 4
# 0: 00f048a$ This is what the SPDIF status registers look like with different volume levels set.
# 1: 01f048a$
# 2: 02f048a$ We check for f048a (SIGNATURE_VALUE) to see if LG Sound Sync is enabled.
# 3: 03f048a$
# 100: 64f048a$ The byte to the left is the volume we want to extract.
# ~~ The first bit is set to 1 when muted.
SIGNATURE_MASK = 0xfffff
SIGNATURE_VALUE = 0xf048a
VOLUME_SHIFT = 7 * 4
VOLUME_MASK = 0xff
SHIFT = 5 * 4
MUTE_MASK = 0b10000000
VOLUME_MASK = 0b01111111

@staticmethod
def parse_volume_from_status(data):
bits = int.from_bytes(data, byteorder="big")

if bits >> SoundSync.SIGNATURE_SHIFT & SoundSync.SIGNATURE_MASK == SoundSync.SIGNATURE_VALUE:
return bits >> SoundSync.VOLUME_SHIFT & SoundSync.VOLUME_MASK
if bits & SoundSync.SIGNATURE_MASK != SoundSync.SIGNATURE_VALUE:
return None

return None
if bits >> SoundSync.SHIFT & SoundSync.MUTE_MASK:
return 0

return bits >> SoundSync.SHIFT & SoundSync.VOLUME_MASK

def write_volume(self, volume):
assert 0 <= volume <= 100
Expand Down
18 changes: 8 additions & 10 deletions hifiberrydsp/lg/test_soundsync.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,20 @@

class TestSoundSync(TestCase):
def test_parse_volume_with_missing_sound_sync_signature(self):
assert SoundSync.parse_volume_from_status(b'\xff\xff\xff\xff\xff\xff') is None

def test_parse_out_of_range_volume(self):
# The maximum value is 100, but we don't enforce that in the parser itself,
# but rather in later validation.
assert SoundSync.parse_volume_from_status(b'\x00\x1f\xff\x04\x8a\x62') == 255
assert SoundSync.parse_volume_from_status(b'\xff\xff\xff\xff\xff') is None

def test_parse_volume_of_0(self):
assert SoundSync.parse_volume_from_status(b'\x00\x10\x0f\x04\x8a\x62') == 0
assert SoundSync.parse_volume_from_status(b'\x00\x10\x0f\x04\x8a') == 0

def test_parse_volume_of_50(self):
assert SoundSync.parse_volume_from_status(b'\x00\x13\x2f\x04\x8a\x62') == 50
assert SoundSync.parse_volume_from_status(b'\x00\x13\x2f\x04\x8a') == 50

def test_parse_volume_of_100(self):
assert SoundSync.parse_volume_from_status(b'\x00\x16\x4f\x04\x8a\x62') == 100
assert SoundSync.parse_volume_from_status(b'\x00\x16\x4f\x04\x8a') == 100

def test_parse_volume_of_100_on_another_tv(self):
# Data observed on a LG OLED55C9
assert SoundSync.parse_volume_from_status(b'\x00\x06\x4f\x04\x8a\x60') == 100
assert SoundSync.parse_volume_from_status(b'\x00\x06\x4f\x04\x8a') == 100

def test_parse_volume_of_muted_tv(self):
assert SoundSync.parse_volume_from_status(b'\x00\x0d\x0f\x04\x8a') == 0