SACD_DST最小接入方案.md 11 KB

SACD DST 最小接入方案

1. 目标

只解决 TTMusic 当前的一个缺口:

  • SACD ISODST 压缩轨道无法提取

不做下面这些事:

  • 不重编 FFmpeg
  • 不改现有 ijkplayer 播放链路
  • 不把 audiodecoder.sacd 整个 Kodi 插件搬进项目
  • 不在第一版里做 SACD 元数据、封面、PCM 播放支持

第一版目标很单一:

  • LocalMusic 的 ISO 提取在遇到 DST 轨道时,也能输出 .dsf

2. 现状

TTMusic 当前已经有完整的 ArkTS ISO 提取链路:

现有能力已经覆盖:

  • 解析 SACD ISO 轨道列表
  • 按扇区读取 SACD 数据
  • 拆出音频 packet
  • 组装非 DST 的 DSD frame
  • 直接写出 .dsf
  • taskpool 进度回传

当前卡住的唯一原因是这里主动拦截了 DST:

并且在扇区处理逻辑里,DST 帧目前直接返回失败:

3. 结论

推荐的最小方案不是把 audiodecoder.sacd 整套搬进来,而是只抽它的 libdstdec

原因:

  • TTMusic 已经自己实现了 SACD ISO 的轨道解析、扇区拆包和 DSF 写出
  • audiodecoder.sacd 里真正解决 DST 的核心是 lib/libdstdec
  • src/sacd/*SACDAudio.cppSettings.cpp、Kodi addon 外壳大多是播放器插件包装,不是你当前缺的能力

也就是说,当前最小缺口不是 “不会读 SACD ISO”,而是 “已经拿到完整 DST frame 后,不会解成 DSD frame”。

4. 为什么不直接搬 audiodecoder.sacd

这个仓库确实有用,但直接整仓接入成本不合适。

强耦合点包括:

  • 顶层依赖 Kodi
  • src/SACDAudio.cpp 是 Kodi 音频解码器实例
  • src/sacd/sacd_media.* 依赖 kodi::vfs::CFile
  • src/sacd/sacd_reader.h 依赖 kodi::addon::AudioDecoderInfoTag
  • lib/libdsdpcm 是 DSD 转 PCM 播放用,不是抽轨必需

对 TTMusic 当前任务来说,真正要用的是:

  • lib/libdstdec

而不是:

  • Addon.cpp
  • SACDAudio.cpp
  • Settings.cpp
  • lib/libdsdpcm
  • lib/id3v2lib
  • Kodi 资源和插件清单

5. 推荐接入位置

推荐接到 entrylibentry.so,不要先塞进 ijkplayer_napi

理由:

  • 这条能力本质是“本地文件提取”,不是播放器渲染能力
  • entry 里已经有现成的 native bridge 模式
  • ArkTS 侧已经直接通过 libentry.so 调原生方法
  • 项目里已经存在 @Concurrent + libentry.so 的实际用例:

所以推荐目录:

  • entry/src/main/cpp/third_party/sacd/libdstdec/
  • entry/src/main/cpp/sacd_dst_decoder.h
  • entry/src/main/cpp/sacd_dst_decoder.cpp
  • entry/src/main/cpp/napi_init.cpp
  • entry/src/main/cpp/CMakeLists.txt
  • entry/src/main/cpp/types/libentry/Index.d.ts

6. 最小源码集合

6.1 第一版只拷这些

audiodecoder.sacd 拷贝:

  • lib/libdstdec/binding/dst_decoder_mt.cpp
  • lib/libdstdec/binding/dst_decoder_mt.h
  • lib/libdstdec/binding/semaphore.h
  • lib/libdstdec/decoder/decoder.cpp
  • lib/libdstdec/decoder/decoder.h
  • lib/libdstdec/ac.h
  • lib/libdstdec/common.h
  • lib/libdstdec/consts.h
  • lib/libdstdec/ct.h
  • lib/libdstdec/fh.h
  • lib/libdstdec/fr.h
  • lib/libdstdec/segment.h
  • lib/libdstdec/stream.h

6.2 第一版不要拷这些

  • src/SACDAudio.cpp
  • src/SACDAudio.h
  • src/Addon.cpp
  • src/Addon.h
  • src/Settings.cpp
  • src/Settings.h
  • src/sacd/sacd_core.*
  • src/sacd/id3_tagger.*
  • src/sacd/sacd_dsf.*
  • src/sacd/sacd_dsdiff.*
  • src/sacd/sacd_dsd.*
  • lib/libdsdpcm/*
  • lib/id3v2lib/*

6.3 要改掉的唯一外部耦合

libdstdec 当前最主要的外部耦合是日志:

  • common.h 里包含了 <kodi/General.h>

这一块建议改成 TTMusic 自己的日志适配:

  • 要么改成 hilog
  • 要么第一版直接做成 no-op 日志

换句话说,第一版不需要 Kodi 文件系统,不需要 Kodi 设置,不需要 Kodi AudioDecoder 类型。

7. 最小架构

7.1 保留现有 ArkTS 负责的部分

继续保留 ArkTS 现有能力:

  • SACD TOC 解析
  • ISO 扇区读取
  • packet/frame 组装
  • .dsf 写出
  • taskpool 进度更新

7.2 只新增一个原生能力

新增一个 “DST frame -> DSD frame” 的 native 解码器会话。

推荐原生接口:

export interface NativeModule {
  createSacdDstDecoder(channelCount: number, samplerate?: number, framerate?: number): number;
  decodeSacdDstFrame(decoderId: number, dstFrame: ArrayBuffer): ArrayBuffer;
  releaseSacdDstDecoder(decoderId: number): void;
}

默认参数建议:

  • samplerate = 2822400
  • framerate = 75

这里 75 不是拍脑袋,是 audiodecoder.sacdsacd_disc_t::get_framerate() 直接返回的值。

7.3 为什么要做成会话

不要做成每帧都重新初始化 decoder。

原因:

  • dst_decoder_t::init() 有初始化成本
  • 同一轨道连续解码时复用实例更稳
  • ArkTS 在提取单轨时天然就是顺序读帧

第一版直接用单线程 slot 即可:

  • dst_decoder_t(1)

这样每次 decode() 都能同步拿到当前帧输出,不需要额外做 flush 逻辑。

8. ArkTS 侧改法

8.1 只改 IsoArchiveTaskHelper.ets

第一版建议只改这个文件:

8.2 改造点

当前 extractSacdTrackEntry() 里这段需要调整:

  • 遇到 frameFormat === DST 时,不再直接返回失败

建议改成:

  1. 轨道开始前,按 channelCount 创建一个 DST decoder session
  2. processSacdSector() 继续负责把 DST packet 组装成完整 frame
  3. 一旦一个完整 DST frame 就绪,调用 native decodeSacdDstFrame()
  4. 拿到返回的 DSD frame 后,继续复用当前 writeSacdFrameToDsf()
  5. 轨道结束后释放 decoder

8.3 最小逻辑变化

当前逻辑里这段是阻断点:

if (assembler.dstEncoded) {
  return false;
}

第一版应改成:

  • assembler.dstEncoded === false:
    • 走现有 writeSacdFrameToDsf()
  • assembler.dstEncoded === true:
    • assembler.buffer.subarray(0, assembler.size) 送去 native 解码
    • 解码结果再写入 writeSacdFrameToDsf()

8.4 进度逻辑可以完全复用

因为扇区读取和写文件仍在 ArkTS,当前这些逻辑都不用重写:

  • advanceIsoExtractProgress()
  • reportIsoExtractProgress()
  • taskpool.Task.sendData()

这也是这个方案比“整套搬 C++ 抽轨器”更适合当前仓库的原因。

9. native 侧最小实现

9.1 sacd_dst_decoder.cpp

建议内部结构:

struct SacdDstDecoderSession {
    int64_t id;
    uint32_t channelCount;
    uint32_t samplerate;
    uint32_t framerate;
    size_t dsdFrameSize;
    std::unique_ptr<dst_decoder_t> decoder;
    std::vector<uint8_t> decodeBuffer;
};

9.2 三个导出方法

createSacdDstDecoder

职责:

  • 校验 channelCount
  • 创建 dst_decoder_t(1)
  • init(channelCount, 2822400, 75)
  • 分配 decodeBuffer
  • 返回 session id

decodeSacdDstFrame

输入:

  • decoderId
  • ArrayBuffer dstFrame

职责:

  • 从 NAPI 取出 ArrayBuffer
  • decoder->decode(dstBytes, dstSize, &outPtr, &outSize)
  • decodeBuffer 打包成新的 ArrayBuffer 返回

输出:

  • 一帧完整 DSD 数据

releaseSacdDstDecoder

职责:

  • 释放 session

9.3 CMakeLists.txt

entry/src/main/cpp/CMakeLists.txt 中:

  • entry 目标增加 sacd_dst_decoder.cpp
  • libdstdec 源码一并编进 entry
  • 增加 third_party/sacd/libdstdec 相关头文件目录
  • 继续链接 libace_napi.z.sohilog_ndk.z

第一版不需要:

  • WavPack
  • Iconv
  • Kodi

10. TypeScript 类型声明

entry/src/main/cpp/types/libentry/Index.d.ts 里追加:

createSacdDstDecoder(channelCount: number, samplerate?: number, framerate?: number): number;
decodeSacdDstFrame(decoderId: number, dstFrame: ArrayBuffer): ArrayBuffer;
releaseSacdDstDecoder(decoderId: number): void;

ArkTS 使用方式仍保持和 SMB bridge 一样:

import nativeBridge from 'libentry.so';

11. 推荐落地顺序

Step 1

先把 libdstdec 编进 libentry.so,做一个本地自测接口:

  • 输入固定的 DST frame
  • 输出 DSD frame

先确认:

  • 编译过
  • native 方法能从 ArkTS 调通
  • ArrayBuffer 往返正常

Step 2

IsoArchiveTaskHelper.ets 里只改 DST 分支:

  • DST 保持原样
  • DST 走 native 解码

Step 3

跑 3 组真机测试:

  • 2ch DST SACD ISO
  • MCH DST SACD ISO
  • 普通非 DST SACD ISO

确保:

  • DST 能提取
  • 非 DST 不回归
  • 混合选择时部分成功逻辑正常

Step 4

最后再考虑是否补:

  • 提取后的 track title / album 元数据
  • 封面
  • 更细粒度的错误码

这些都不应该挡住第一版上线。

12. Plan B

如果实际验证发现 TTMusic 当前 ArkTS 的 DST frame 组装和 sacd_disc_t::read_frame() 在某些碟片上不兼容,再退回到第二方案:

  • 再搬 src/sacd/scarletbook.*
  • 再搬 src/sacd/sacd_disc.*
  • 自己改造 sacd_media.*
  • 让 native 负责 “ISO -> DST/DSD frame”

但这应该是 Plan B,不是第一步。

理由很简单:

  • 接入量更大
  • Kodi 耦合更多
  • 调试成本更高
  • 你当前仓库已经有一套可工作的 ArkTS 解析链路,没必要先推倒重来

13. 风险与边界

13.1 许可证

audiodecoder.sacd 顶层是 GPL-2.0 / GPL-2.0-or-later 路线,接入前必须先明确你项目能否接受对应分发约束。

libdstdec/binding/dst_decoder_mt.* 文件头本身写的是 LGPL-2.1 or later,但整仓来源仍然需要你按第三方合规去核。

这个问题不是技术问题,是分发问题。

13.2 日志适配

libdstdec/common.h 当前带 kodi::Log,需要改成本地日志。

13.3 首版不做元数据

第一版目标只保证:

  • 能提取
  • 生成 .dsf
  • 不回归非 DST

不保证:

  • SACD 文本信息完整保留
  • 专辑/曲目名从 SACD TOC 自动灌进 DSF tag

这些可以后续再做。

14. 最终建议

对 TTMusic 来说,最小、最稳、最符合当前代码结构的方案是:

  1. 不接 audiodecoder.sacd 整套
  2. 只抽 libdstdec
  3. 继续复用现有 ArkTS 的 SACD 扇区解析和 DSF writer
  4. DST frame -> DSD frame 这一步补到 libentry.so

如果你下一步要我继续做,实现顺序建议是:

  1. 先把 libdstdec 接进 entry/src/main/cpp
  2. 先把 libentry.so 的 3 个 native API 做出来
  3. 再把 IsoArchiveTaskHelper.etsDST 分支接上去