ChayaSar 发表于 2025-11-25 18:43:13

咨询LHDCV5解码算法

我正在制作ESP32蓝牙播放器,根据Github开源的解码器已经在ESP32上成功跑通了AAC、aptX、LDAC,然后我试图在ESP32上实现LHDCV5,但是注意到目前似乎没人开源过LHDCV5的解码器,我只找到了BES的SDK(包含LHDCV5静态库“.a”文件及对应的api头文件)、AOSP的LHDCV5解码器(缺lhdcv5_util_dec.c文件)、小米手机内置的LHDCV5动态库“.so”文件。
因为我不懂逆向,同时注意到AOSP和ESP32的bluedroid架构几乎一样,且其依赖的外部库只是缺一个源文件,所以我从AOSP移植LHDCV5解码器到ESP-IDF,目前这部分工作已经完成,我将他们开源在Github的liblhdc-collections/tree/main/ESP-IDF/bluedroid;它们包含下列文件:
└─bluedroid
        ├─api
        │└─include
        │          esp_a2dp_api.h
        │
        ├─external
        │└─liblhdcv5dec
        │      │CMakeLists.txt
        │      │release_note
        │      │
        │      ├─inc
        │      │      lhdcv5BT_dec.h
        │      │
        │      ├─include
        │      │      lhdcv5_util_dec.h
        │      │
        │      └─src
        │            lhdcv5BT_dec.c
        │            lhdcv5_util_dec.c
        │
        └─stack
                ├─a2dp
                │      a2dp_vendor.c
                │      a2dp_vendor_lhdcv5.c
                │      a2dp_vendor_lhdcv5_decoder.c
                │
                └─include
                        └─stack
                                        a2dp_vendor.h
                                        a2dp_vendor_lhdcv5.h
                                        a2dp_vendor_lhdcv5_constants.h
                                        a2dp_vendor_lhdcv5_decoder.h
                                        a2dp_vendor_lhdc_constants.h

因为AOSP需要使用liblhdcv5外部库,所以移植到IDF的LHDCV5解码器也是,因此我也同步移植了AOSP的LHDCV5外部库,但是正如前面所说,这个外部库缺乏lhdcv5_util_dec.c的实现,因此我依据lhdcv5_util_dec.h中声明的函数和使用的变量逆推了一个lhdcv5_util_dec.c,但是其中的关键部分——LHDCV5解码算法我无法得知,我搜索不到任何与LHDC编码算法相关的文档/代码。

所以我在lhdcv5_util_dec.c的LHDCV5解码函数“int32_t lhdcv5_util_dec_process(uint8_t * pOutBuf, uint8_t * pInput, uint32_t InLen, uint32_t *OutLen)”中调用了正弦发生器进行伪解码,实际输出的是正弦发生器产生的标准音。对于这个liblhdcv5dec,我也将其开源在Github:liblhdcv5dec,完整内容可以在这个Github仓库看到,其中的伪解码函数如下:
int32_t lhdcv5_util_dec_process(uint8_t * pOutBuf, uint8_t * pInput, uint32_t InLen, uint32_t *OutLen)
{
    if (!decoder_initialized)
      return LHDCV5_UTIL_DEC_ERROR_NO_INIT;
    if (!pInput || !pOutBuf || !OutLen)
      return LHDCV5_UTIL_DEC_ERROR_PARAM;

    // 计算样本大小和帧样本数 - 使用输出位深
    uint32_t sample_bytes = (g_output_bit_per_sample + 7) / 8;
    uint32_t frame_samples = 256;// LHDC V5典型每声道样本数
    uint32_t channels = (g_channel_type == LHDC_OUTPUT_STEREO) ? 2 : 1;
    *OutLen = frame_samples * sample_bytes * channels;

    // 生成32位正弦波PCM数据,然后根据需要转换
    int32_t *out_32 = (int32_t *)pOutBuf;
    int16_t *out_16 = (int16_t *)pOutBuf;
    uint8_t *out_24 = pOutBuf;// 24位特殊处理,存储为3字节

    for (uint32_t i = 0; i < frame_samples; i++) {
      // 生成左声道样本(始终生成32位样本)
      int32_t sample_32 = generate_sine_sample(g_sampleRate, 32);
      
      // 根据输出位深转换
      switch (g_output_bit_per_sample) {
            case 16:
                // 从32位转换到16位
                int16_t sample_16 = (int16_t)(sample_32 >> 16);
                out_16 = sample_16;// 左声道
                if (channels == 2) {
                  // 右声道使用稍作修改的样本,制造立体声效果
                  out_16 = (int16_t)(sample_16 * 0.9f); // 右声道
                }
                break;
               
            case 24:
                // 从32位转换到24位
                int32_t sample_24 = sample_32 >> 8; // 右移8位得到24位

                // 正确处理有符号24位样本
                int32_t clamped_sample = sample_24;
                // 确保样本在24位范围内
                if (clamped_sample > 8388607) clamped_sample = 8388607;
                if (clamped_sample < -8388608) clamped_sample = -8388608;
               
                // 转换为无符号偏移量(+8388608)以便正确存储符号位
                uint32_t u_sample = clamped_sample + 8388608;
               
                out_24 = u_sample & 0xFF;         // 低8位
                out_24 = (u_sample >> 8) & 0xFF;// 中8位
                out_24 = (u_sample >> 16) & 0xFF; // 高8位
               
                if (channels == 2) {
                  int32_t right_sample = (int32_t)(sample_24 * 0.9f);
                  int32_t clamped_right = right_sample;
                  if (clamped_right > 8388607) clamped_right = 8388607;
                  if (clamped_right < -8388608) clamped_right = -8388608;
                  
                  uint32_t u_right = clamped_right + 8388608;
                  out_24 = u_right & 0xFF;
                  out_24 = (u_right >> 8) & 0xFF;
                  out_24 = (u_right >> 16) & 0xFF;
                }
                break;
               
            case 32:
                out_32 = sample_32;// 左声道
                if (channels == 2) {
                  out_32 = (int32_t)(sample_32 * 0.9f);// 右声道
                }
                break;
      }
    }

    LOG_DEBUG("%s: Generated sine wave frame - %u samples, %u bits, %u channels, %u bytes",
             __func__, frame_samples, g_bitPerSample, channels, *OutLen);
    return LHDCV5_UTIL_DEC_SUCCESS;
}
目前在ESP32上已经可以运行伪解码的LHDCV5了,小米14连接ESP32后,以LHDCV5协商,手机播放音乐时,ESP32上听到的是正弦发生器的标准音。我想知道我该如何获取LHDCV5的解码算法源码?官方肯定是不行了,早段时间发过邮件没理我。注意到论坛的大神@O11111和@O2C14已经在这方面有进展,非常佩服,也注意到有人说还有别的大神也实现了,但我不知道怎么联系,我是新来的小白一个,不知大神们可否与我交流一下,我挺想在ESP32等设备上实现真正的LHDCV5解码,可以有偿(不是太贵都能接受),不白拿资料:lolGithub的sprlightning可联系到我,还有我Q是3397935446:lol






jsjj 发表于 2025-11-25 22:51:41

虽然没有资料,但是给你大大的赞。

ChayaSar 发表于 2025-11-25 23:54:53

jsjj 发表于 2025-11-25 22:51
虽然没有资料,但是给你大大的赞。

哈哈感谢:lol

ChayaSar 发表于 7 天前

不知道什么情况,上面写的地址超链接没生效,这里直接把开源地址发出来吧:
1)移植到IDF的liblhdcv5dec源码(伪解码版本):https://github.com/sprlightning/liblhdcv5dec
2)与liblhdcv5dec配套的LHDCV5解码器源码(IDF bluedroid):https://github.com/sprlightning/liblhdc-collections/tree/main/ESP-IDF/bluedroid
3)我收集的所有LHDC方面的资料,包括BES2600IHC、BES2700IHC(含静态库),和Android方面(含64位动态库),也集成了我在使用的IDF内容:https://github.com/sprlightning/liblhdc-collections

O11111 发表于 7 天前

是的,我们开发了 LHDCV5 编码器和解码器,但从未公开发布。我只在 Linux 系统下使用打过补丁的 Pipewire 版本进行了测试。我们计划最终发布,但目前存在一些限制,例如不支持 192 kHz(不过反正也没什么用)或无损编码。

ChayaSar 发表于 7 天前

O11111 发表于 2025-11-26 01:13
是的,我们开发了 LHDCV5 编码器和解码器,但从未公开发布。我只在 Linux 系统下使用打过补丁的 Pipewire...

感谢大神回复,我对你和O2C14搞定LHDCV5的编解码器算法感到非常钦佩。因为你们计划最终发布,那我就等等,并关注你们的动态:lol
我认为复现LHDCV5的编解码算法应该需要比较好的通信理论基础,我确定我这方面能力不行,可能起不到什么帮助;不过对于LHDCV5的测试我是很期待的,我有支持LHDCV5的XIAOMI14手机和漫步者花再halo space耳机,以及移植codecs的能力,如果大神们有这方面的需求可以找我:lol
此外我有一个疑问,你们已经实现了LHDCV5在低采样率下的编解码,为什么高采样率还不支持?是受限于处理器计算速度?还是什么别的限制?不同采样率情况下编解码算法会有较大差异吗?原谅我的奇怪问题,因为我这方面不是很懂,但是我确实比较好奇~

晋123456 发表于 7 天前

不错不错学习一下:)

O11111 发表于 7 天前

ChayaSar 发表于 2025-11-26 04:42
感谢大神回复,我对你和O2C14搞定LHDCV5的编解码器算法感到非常钦佩。因为你们计划最终发布,那我就等等 ...

最主要的是需要花费大量时间和一些 C 语言知识。现代逆向工程工具让这个过程变得简单得多,甚至不需要懂汇编语言(尽管懂汇编会有帮助)。

目前,96 kHz 的采样率可以正常工作。192 kHz 的采样率需要额外的工作,我不太想做。而且它本身也没什么用,因为没有任何设备支持。它主要涉及一些常量和表格,算法是一样的。另一方面,无损模式则截然不同,而且当时没有任何设备支持它,所以就被跳过了。

此外,LHDCV5 包含大量的浮点运算,所以你需要一个浮点运算单元(FPU)。

ChayaSar 发表于 7 天前

O11111 发表于 2025-11-26 18:46
最主要的是需要花费大量时间和一些 C 语言知识。现代逆向工程工具让这个过程变得简单得多,甚至不需要懂 ...

感谢大神解惑:lol

ChayaSar 发表于 5 天前

我试了试把BES2700IHC的lhdcv5 a文件(ARM架构)载入IDA,它给我解析出来19个o文件,大致分为:
- 内层lhdc_log;
- 内层svlice_v5系列;
- 内层lhdc_v5系列;
- 外层lhdcv5_util_dec,有配套api头文件;

我用IDA自动反汇编了几个文件,它们的关联性让我很满意,这使我产生出了一个计划:依次逆向19个o文件,合并其中重复的函数,那也许会得到所有完整函数的伪代码。然后按照这些伪代码的逻辑,再复现出所有函数的源码。不过这也许会花很多的时间。

我注意到这些a文件似乎比so文件更完整,更利于分析。lhdcv5 a文件在上面的GitHub开源地址已提供,有同样想法的朋友可以试试:lol





页: [1]
查看完整版本: 咨询LHDCV5解码算法