
移动开发智能硬件音视频【免费下载链接】DiPlayIndependent CarPlay receiver for compatible Android head units. Wired and wireless public preview.项目地址https://gitcode.com/gh_mirrors/di/DiPlay点击查看免费下载本文以 DiPlay 仓库中随附的 SpeexDSP vendoring 说明shared/src/main/jni/speexdsp/README.md为主体梳理 DiPlay 如何从 SpeexDSP 1.2.1 中裁剪出未修改的回声消除子集、以何种编译标志构建为libspeex_echo.so以及该原生库如何通过 JNI 桥接与 Kotlin 封装接入 CarPlay 通话麦克风链路。读完本篇你将掌握一个第三方 DSP 库子集化引入的完整落地链路文件清单与许可证保留、Android.mk构建配置、JNI 状态机参数、以及软件 AEC 与平台 AEC 的互斥协调策略。1. 引入的是哪个子集SpeexDSP 1.2.1 的 AEC 核心链路shared/src/main/jni/speexdsp/README.md 的核心声明只有几句话但每一条都有明确的工程含义引入的是SpeexDSP 1.2.1 的声学回声消除器mdf.c、预处理器preprocess.c及其 FFT / 滤波器组支持且未经修改unmodifiedinclude/speex/speexdsp_config_types.h替代了原本由 autotools configure 流程生成的头文件——这是脱离上游构建系统的必要改动点也是整个 vendoring 中唯一允许出现的非上游头文件以浮点模式 KISS FFT-DFLOATING_POINT -DUSE_KISS_FFT由相邻目录的 Android.mk 构建许可证为 COPYING 中的 BSD 风格三条款许可证。仓库中实际存在的 vendored 文件目录 shared/src/main/jni/speexdsp/与这条声明一一对应类别文件作用核心算法mdf.c约 1279 行AEC 核心多频域multi-domain filter自适应回声消除核心算法preprocess.c约 1215 行预处理器残余回声抑制、降噪、AGC、VAD、去混响后三项在 DiPlay 中被显式关闭FFT 支持fftwrap.c/fftwrap.hFFT 封装层在 KISS FFT 与内置实现之间切换FFT 支持kiss_fft.c/kiss_fft.h/kiss_fftr.c/kiss_fftr.h/_kiss_fft_guts.hMark Borgerding 的 KISS FFT 实数/复数变换实现滤波器组filterbank.c/filterbank.h预处理器使用的分析/合成滤波器组平台适配arch.h/fixed_generic.h/pseudofloat.h/math_approx.h/os_support.h数据类型、定点/浮点抽象、数学近似公开头文件include/speex/speex_echo.h/speex_preprocess.h/speexdsp_types.hSpeexEchoState、SpeexPreprocessState等 API 声明配置头文件include/speex/speexdsp_config_types.h替代 configure 生成的类型定义头关于未修改这一声明的可验证程度docs/VALIDATION.md 记录道SpeexDSP 1.2.1 的 20 个 vendored 文件与官方上游 blob 逐一对齐并保留了 BSD 许可证。许可证方面的完整保留情况见 docs/THIRD_PARTY_NOTICES.md它说明 DiPlay 引入的是 Xiph.org Foundation 的 SpeexDSP 子集编译进libspeex_echo.so供可选的Call echo cancellation实验性功能使用组件源码头文件保留了各自附加声明包括 KISS FFT 对 Mark Borgerding 的署名完整版权声明保留在 COPYING 中。2. 构建Android.mk中的模块与编译标志README 声称该子集由../Android.mk以浮点 KISS FFT 构建shared/src/main/jni/Android.mk 给出了具体落地。整个Android.mk构建三个共享库xcertplay_i2c、local_hotspot_radio与本主题无关与 Speex 相关的段落是include $(CLEAR_VARS) LOCAL_MODULE : speex_echo LOCAL_SRC_FILES : speex_echo_jni.c \ speexdsp/mdf.c speexdsp/preprocess.c speexdsp/fftwrap.c \ speexdsp/kiss_fft.c speexdsp/kiss_fftr.c speexdsp/filterbank.c LOCAL_C_INCLUDES : $(LOCAL_PATH)/speexdsp $(LOCAL_PATH)/speexdsp/include LOCAL_CFLAGS : -O2 -DFLOATING_POINT -DUSE_KISS_FFT -DEXPORT -Wno-unused-parameter LOCAL_LDLIBS : -lm include $(BUILD_SHARED_LIBRARY)逐项解读参与编译的 C 源码只有 7 个JNI 桥接文件speex_echo_jni.c加上 AEC 依赖闭包mdf.c、preprocess.c、fftwrap.c、kiss_fft.c、kiss_fftr.c、filterbank.c。目录里的arch.h、os_support.h、pseudofloat.h等是纯头文件被#include拉入而不单独参与编译-DFLOATING_POINT选择浮点运算路径spx_word32_t等类型在 speexdsp_config_types.h /pseudofloat.h中落到浮点实现-DUSE_KISS_FFT让fftwrap.c走 KISS FFT 后端这也是需要kiss_fft.c、kiss_fftr.c入列的原因-DEXPORT把上游的EXPORT导出宏置空避免在非上游构建环境下引入__declspec等平台相关的修饰-O2优化等级-lm链接数学库浮点 FFT 与滤波器组需要sinf、cosf等。shared/src/main/jni/Application.mk 只含两行APP_PLATFORM : android-25、APP_STL : none。APP_STL : none值得注意——它意味着这套 C 代码不链接 C 标准库与纯 C 算法 JNI 桥的形态一致也减少了与宿主应用的 STL 版本耦合。产物是libspeex_echo.so按 ABI 编入 Debug APKarm64-v8a、armeabi-v7a、x86_64见 docs/VALIDATION.md。3. JNI 桥接层speex_echo_jni.c中的状态机与参数shared/src/main/jni/speex_echo_jni.c 是唯一的 JNI 入口文件暴露三个方法nativeCreate、nativeProcess、nativeDestroy。其内部用一个轻量结构体把 AEC 与预处理器两个状态绑定在一起typedef struct { SpeexEchoState *echo; SpeexPreprocessState *preprocess; int frame; } Canceller;3.1 创建参数校验与预处理器配置nativeCreate(frame, filter, rate, suppress, suppressActive)的关键逻辑入参校验frame 0 || filter frame || rate 0时返回 0即尾长滤波器长度不允许小于帧长——这在频域 AEC 中意味着滤波器至少要有覆盖一帧的频率分辨率初始化两个状态speex_echo_state_init(frame, filter)与speex_preprocess_state_init(frame, rate)任一失败则销毁另一半并返回 0通过 ctl 接口配置其中有一组刻意的关闭注释解释了原因——// Platform noise suppression and gain already run on the recorder; only remove residual echo here. value 0; speex_preprocess_ctl(c-preprocess, SPEEX_PREPROCESS_SET_DENOISE, value); speex_preprocess_ctl(c-preprocess, SPEEX_PREPROCESS_SET_AGC, value); speex_preprocess_ctl(c-preprocess, SPEEX_PREPROCESS_SET_VAD, value); speex_preprocess_ctl(c-preprocess, SPEEX_PREPROCESS_SET_DEREVERB, value); speex_preprocess_ctl(c-preprocess, SPEEX_PREPROCESS_SET_ECHO_STATE, c-echo); value suppress; speex_preprocess_ctl(c-preprocess, SPEEX_PREPROCESS_SET_ECHO_SUPPRESS, value); value suppressActive; speex_preprocess_ctl(c-preprocess, SPEEX_PREPROCESS_SET_ECHO_SUPPRESS_ACTIVE, value);从这段配置可以看出 DiPlay 对 Speex 的定位降噪、AGC、VAD、去混响全部交给 Android 录音器的平台效果NoiseSuppressor等Speex 预处理器只承担 AEC 收敛之后仍可能残留的残余回声抑制SPEEX_PREPROCESS_SET_ECHO_STATE把SpeexEchoState注入预处理器使其能读取 AEC 滤波器状态来计算残余回声量。两个抑制阈值由调用方传入下一节给出 DiPlay 实际取值ECHO_SUPPRESS是远端在说话时的抑制深度ECHO_SUPPRESS_ACTIVE是检测到本端活动语音时的抑制深度。3.2 处理nativeProcess的边界与内存语义nativeProcess(handle, mic, reference, out)逐帧执行三个jshortArray长度都必须不小于c-frame否则返回JNI_FALSE短数组防御被 JNI 冒烟测试专门覆盖用GetShortArrayElements取三个数组指针后依次调用speex_echo_cancellation(c-echo, m, r, o)用参考信号r从麦克风m中消回声结果写o与speex_preprocess_run(c-preprocess, o)对残余回声做二次抑制释放策略体现了成功才提交的语义out在成功时以0复制回 Java 数组释放输入数组与失败路径一律JNI_ABORT——处理失败时 Java 侧帧原样保留这与 Kotlin 封装中false 即不动该帧的契约对应。4. Kotlin 封装SpeexEchoCanceller的帧格式与参数推导shared/src/main/java/com/shilapi/xcertplay/media/SpeexEchoCanceller.kt 以类注释交代了引入动机Apple 期望配件端自己消除回声而把第三方通话当音乐播放的车机不会对这些音频运行自己的 AEC否则远端会听到自己——这正是 DiPlay 需要软件 AEC 的根本原因。封装层的要点可用性是懒加载且可降级的available由System.loadLibrary(speex_echo)是否成功决定失败仅记一条 warning 并返回 false通话链路照常走平台效果尾长到滤波器长度的换算在工厂方法create(frameSamples, sampleRate, tailMillis)中完成val filter maxOf(frameSamples, sampleRate * tailMillis / 1000)即滤波器长度至少覆盖tailMillis毫秒对应的采样数。DiPlay 的tailMillis来自 MicrophoneUplink.kt 的常量ECHO_TAIL_MILLIS 250与 docs/BYD_NAVIGATION.md 中Call echo cancellation 使用 250 ms tail的描述一致抑制深度常量ECHO_SUPPRESS_DB -40、ECHO_SUPPRESS_ACTIVE_DB -15即远端说话时残余回声压 40 dB、本端检测到活动语音时压 15 dB——两者都传给nativeCreate最终落到 3.1 节的SPEEX_PREPROCESS_SET_ECHO_SUPPRESS(_ACTIVE)帧格式转换在 Kotlin 侧完成process(frame: ByteArray, reference: ShortArray)把 PCM16LE 的ByteArray逐样本转成ShortArray送 JNI结果再原地写回frame任何长度不足、native 失败或库不可用都返回 false 且该帧原样通过保证回声处理失败不会丢帧。5. 接入通话麦克风与平台 AEC 的互斥协调SpeexEchoCanceller不是独立工作的它在 MicrophoneUplink.kt 中的接入策略是这套 vendored 库最有工程含量的部分只在电话录音路径启用当config.audioType telephony时调用createEchoCanceller()Siri 走语音识别采集路径不经过这里硬性前提——单声道且采样率匹配createEchoCanceller()要求config.channels 1且config.sampleRate与参考信号采样率一致否则跳过并记日志microphone echo canceller skipped rate... channels...通话退回平台效果docs/BYD_NAVIGATION.md 明确这一约束It needs mono capture at the downlink sample rate不与平台 AEC 叠加voiceEffects()在 Speex 已创建时会显式AcousticEchoCanceler.create(sessionId)并setEnabled(false)注释写明原因——VOICE_COMMUNICATION源默认可能开启系统 AEC宁可显式禁用也不堆叠两个消除器若禁用失败vendor ROM 上效果对象不可控则反过来关闭 Speex 状态、退回平台路径运行期失败自动回退采集循环中canceller.process(...)返回 false 或抛RuntimeException/LinkageError时调用restorePlatformEchoCancellation()并把canceller置空——软件 AEC 的任何故障模式最终都收敛到恢复平台路径这一安全态。6. 参考信号EchoReference的扬声器时钟锚定AEC 的第二个输入是远端通话音频在车机扬声器上实际播出的信号。shared/src/main/java/com/shilapi/xcertplay/media/EchoReference.kt 用一个 2 秒容量的环形缓冲capacityMillis 2_000解决时间对齐问题其类注释给出了原理通话渲染器在向AudioTrack交付每个 PCM 块之后追加该块内容以及此刻扬声器前方仍排队的采样数pendingFrames。这就锚定了扬声器时钟使麦克风能索取它刚捕获的那段时间窗口内实际播放的采样。具体机制append(...)把多声道 PCM 折叠为单声道写入环形缓冲并用written - pendingFrames计算锚点锚点采样序号 锚点时刻纳秒构成扬声器播放的时钟原点read(out, endNs)用anchorSample (endNs - anchorNs) * sampleRate / 1e9推算endNs时刻扬声器正在播哪个采样向前取out.size个采样填入输出超出环形缓冲或尚未锚定的部分以静音填充调用侧在 MicrophoneUplink.kt 中以clock.frameEndNs(...) ECHO_REFERENCE_LEAD_NS30 ms提前量读取参考信号注释解释了原因——稍微超前取使采集时钟漏掉的扬声器延迟仍落在 250 ms 尾长覆盖范围内保证直达声始终处于 AEC 滤波器可见区间。7. 功能定位、默认状态与验证边界综合仓库文档这个 vendored 组件在产品层面的定位可以完整描述为实验性、默认关闭docs/COMPATIBILITY.md 与 docs/INSTALL.md 均说明Call echo cancellation (experimental)位于Settings → Advanced → Video and audio默认关闭且在下次连接时生效不重连当前会话效果边界文档反复强调source/JNI tests do not establish acoustic quality on a car——仓库能证明的是构建、生命周期与边界行为声学质量需要逐车机实测可复现的 JNI 冒烟验证docs/VALIDATION.md 记录了经检查的 JNI 冒烟运行在 8/16/24/48 kHz 下完成了 400 次 create/process/destroy 循环、共 20,400 帧处理含 1,200 次短数组与非法创建/空句柄路径。它验证生命周期与边界而非声学质量——这与 3.2 节中短数组返回 false 的实现互相印证。8. 小结一份可复用的算法库子集化引入范式回到本文主体 shared/src/main/jni/speexdsp/README.md 的九行声明它在 DiPlay 中实际展开为一条完整证据链裁剪只保留 AEC 依赖闭包6 个 .c 必要头文件speexdsp_config_types.h手工替代 configure 产物其余保持与上游 SpeexDSP 1.2.1 逐字节一致构建Android.mk 用-DFLOATING_POINT -DUSE_KISS_FFT -DEXPORT固定构建形态Application.mk 以APP_STL : none避免 STL 耦合产物libspeex_echo.so按 ABI 分发桥接speex_echo_jni.c 把SpeexEchoState与SpeexPreprocessState绑定为单一句柄显式关闭降噪/AGC/VAD/去混响只保留 AEC 残余回声抑制-40 dB / -15 dB失败时 Java 帧零损伤集成Kotlin 层推导 250 ms 尾长对应的滤波器长度EchoReference用pendingFrames锚定扬声器时钟并对齐参考信号与平台 AEC 严格互斥、故障即回退合规与验证BSD 三条款全文保留于 COPYINGdocs/THIRD_PARTY_NOTICES.md 完成署名上游一致性断言与 JNI 冒烟数据见 docs/VALIDATION.md。对需要在 Android NDK 侧引入第三方 DSP 算法库的读者这份最小 README 上游一致性校验 构建标志注释 桥接层参数契约 文档化验证边界的组合比单看任何一份源码都更接近可复制的工程范式。赞分享移动开发智能硬件音视频【免费下载链接】DiPlayIndependent CarPlay receiver for compatible Android head units. Wired and wireless public preview.项目地址https://gitcode.com/gh_mirrors/di/DiPlay点击查看免费下载相关推荐从入门到精通利用ndk-samples掌握JNI编程的完整指南从入门到精通利用ndk samples掌握JNI编程的完整指南 ndk samples是Android NDK开发的官方示例集合包含了从基础到高级的各类JN示例工程移动开发如何引入 Font AwesomeCDN 图标集成实战指南如何引入 Font AwesomeCDN 图标集成实战指南 Font Awesome 是一套提供 2000 免费图标的 SVG、字体与 CSS 图标库。本文前端UI组件3步解决英雄联盟回放兼容性问题ROFL-Player完整指南3步解决英雄联盟回放兼容性问题ROFL Player完整指南 还在为英雄联盟旧版本回放无法播放而烦恼吗ROFL Player是一款专为英雄联盟玩家设计的回放桌面应用上一篇Noto字体使用指南三招告别豆腐块一套开源字体免费覆盖全球150种文字下一篇网盘直链下载助手免费完整指南8大网盘直链解析与批量下载实战教程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考