<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>BeYoung</title>
    <link>https://lyapple2008.github.io/</link>
    <description>Recent content on BeYoung</description>
    <image>
      <title>BeYoung</title>
      <url>https://lyapple2008.github.io/%3Clink%20or%20path%20of%20image%20for%20opengraph,%20twitter-cards%3E</url>
      <link>https://lyapple2008.github.io/%3Clink%20or%20path%20of%20image%20for%20opengraph,%20twitter-cards%3E</link>
    </image>
    <generator>Hugo -- 0.147.9</generator>
    <language>zh</language>
    <copyright>See this site&amp;rsquo;s source code here, licensed under GPLv3 ·</copyright>
    <lastBuildDate>Fri, 31 Jul 2026 22:04:00 +0800</lastBuildDate>
    <atom:link href="https://lyapple2008.github.io/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>【置顶】2026</title>
      <link>https://lyapple2008.github.io/posts/2026-todo/</link>
      <pubDate>Sun, 22 Feb 2026 09:43:54 +0800</pubDate>
      <guid>https://lyapple2008.github.io/posts/2026-todo/</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;记录跟踪2026年要做的事情和正在做的事情。&lt;/p&gt;&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;目标&#34;&gt;目标&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;健康生活&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;input disabled=&#34;&#34; type=&#34;checkbox&#34;&gt; 体重减到60kg&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div id=&#34;weight-chart&#34; style=&#34;width: 100%; height: 300px;&#34;&gt;&lt;/div&gt;
&lt;script src=&#34;https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js&#34;&gt;&lt;/script&gt;
&lt;script&gt;
var chart = echarts.init(document.getElementById(&#39;weight-chart&#39;));
chart.setOption({
    tooltip: { trigger: &#39;axis&#39; },
    xAxis: { type: &#39;category&#39;, data: [&#39;1月&#39;, &#39;2月&#39;, &#39;3月&#39;, &#39;4月&#39;, &#39;5月&#39;, &#39;六月&#39;, &#39;七月&#39;] },
    yAxis: { type: &#39;value&#39;, min: 55, max: 70 },
    series: [{
        data: [65, 65.8, 65.6, 64.4, 63.7, 63.9, 63.6],
        type: &#39;line&#39;,
        smooth: true,
        areaStyle: { opacity: 0.3 },
        markLine: { data: [{ yAxis: 60, name: &#39;目标&#39; }], linestyle: { color: &#39;#52c41a&#39;, type: &#39;dashed&#39; } }
    }]
});
&lt;/script&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;探索可能&lt;/p&gt;</description>
      <content:encoded><![CDATA[<blockquote>
<p>记录跟踪2026年要做的事情和正在做的事情。</p></blockquote>
<hr>
<h2 id="目标">目标</h2>
<ul>
<li>
<p>健康生活</p>
<ul>
<li><input disabled="" type="checkbox"> 体重减到60kg</li>
</ul>
</li>
</ul>
<div id="weight-chart" style="width: 100%; height: 300px;"></div>
<script src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script>
<script>
var chart = echarts.init(document.getElementById('weight-chart'));
chart.setOption({
    tooltip: { trigger: 'axis' },
    xAxis: { type: 'category', data: ['1月', '2月', '3月', '4月', '5月', '六月', '七月'] },
    yAxis: { type: 'value', min: 55, max: 70 },
    series: [{
        data: [65, 65.8, 65.6, 64.4, 63.7, 63.9, 63.6],
        type: 'line',
        smooth: true,
        areaStyle: { opacity: 0.3 },
        markLine: { data: [{ yAxis: 60, name: '目标' }], linestyle: { color: '#52c41a', type: 'dashed' } }
    }]
});
</script>
<ul>
<li>
<p>探索可能</p>
<ul>
<li><input disabled="" type="checkbox"> 英语能力，熟练3篇演讲短文</li>
<li><input disabled="" type="checkbox"> 独立开发，目标完成4个独立项目 &ndash;&gt; 进度2/4</li>
<li><input disabled="" type="checkbox"> 公众号输出，目标输出文章10篇  &ndash;&gt; 进度8/10</li>
</ul>
</li>
</ul>
<h2 id="正在做的项目">正在做的项目</h2>
<ul>
<li>
<p><input checked="" disabled="" type="checkbox"> iOS双语字幕App</p>
</li>
<li>
<p><input checked="" disabled="" type="checkbox"> 拼了还拼小游戏外挂</p>
</li>
<li>
<p><input disabled="" type="checkbox"> asr from scratch</p>
</li>
<li>
<p><input disabled="" type="checkbox"> 虚拟主播</p>
</li>
</ul>
<h2 id="一些随想">一些随想</h2>
<blockquote>
<p>记录一些生活工作中一些小感受和小想法</p></blockquote>
<ul>
<li>2026.07.04</li>
</ul>
<blockquote>
<p>慢下来才能更快。</p></blockquote>
<ul>
<li>2026.05.24</li>
</ul>
<blockquote>
<p>You can outsource your thinking, but you can&rsquo;t outsource your understanding.<br>
思考可以外包，但理解不能外包<br>
虽然现在AI进展神速，但是工作/学习的底层逻辑没有变化，AI只是加速了知识的获取和帮忙个人思考，对事情的理解过程还是存在于个人的大脑，这个是AI没法替你完成的。</p></blockquote>
<ul>
<li>2026.03.29</li>
</ul>
<blockquote>
<p>每天记录和量化进度，本身就是一种负担，而且对于效率提高帮助不大，也不利于长期任务，与其每天盯着任务进度，
不如现在开始试下，里程碑式的工作方法，给自己一段时间设定一些目标，到期后来检查完成情况，中间如何只记录
据有里程碑意义的事件。</p></blockquote>
]]></content:encoded>
    </item>
    <item>
      <title>Speex 回声消除：从回声路径到 MDF/AUMDF</title>
      <link>https://lyapple2008.github.io/posts/202607/speex-echo-cancellation/</link>
      <pubDate>Fri, 31 Jul 2026 22:04:00 +0800</pubDate>
      <guid>https://lyapple2008.github.io/posts/202607/speex-echo-cancellation/</guid>
      <description>从声学回声模型、自适应滤波与 MDF/AUMDF 原理，深入到 SpeexDSP 1.2.1 mdf.c 工程实现的交互式中文导读。</description>
    </item>
    <item>
      <title>Token在燃烧？尝试本地部署？</title>
      <link>https://lyapple2008.github.io/posts/202607/2026-07-15-%E6%9C%AC%E5%9C%B0ai%E5%A4%A7%E6%A8%A1%E5%9E%8B%E6%96%B9%E6%A1%88/</link>
      <pubDate>Wed, 15 Jul 2026 22:52:35 +0800</pubDate>
      <guid>https://lyapple2008.github.io/posts/202607/2026-07-15-%E6%9C%AC%E5%9C%B0ai%E5%A4%A7%E6%A8%A1%E5%9E%8B%E6%96%B9%E6%A1%88/</guid>
      <description>&lt;p&gt;&lt;img alt=&#34;尝试本地部署？&#34; loading=&#34;lazy&#34; src=&#34;https://lyapple2008.github.io/images/2026-07/%E6%9C%AC%E5%9C%B0%E9%83%A8%E7%BD%B2.jpg&#34;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;如果把个人可以买到的设备限制在最多 128GB 统一内存，什么开源模型最适合 AI Coding？买 Mac Studio、AMD 大内存小主机、DGX Spark 或多显卡工作站，和直接调用在线 API 相比，哪一种更快、更便宜？&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><img alt="尝试本地部署？" loading="lazy" src="/images/2026-07/%E6%9C%AC%E5%9C%B0%E9%83%A8%E7%BD%B2.jpg"></p>
<blockquote>
<p>如果把个人可以买到的设备限制在最多 128GB 统一内存，什么开源模型最适合 AI Coding？买 Mac Studio、AMD 大内存小主机、DGX Spark 或多显卡工作站，和直接调用在线 API 相比，哪一种更快、更便宜？</p></blockquote>
<p>这篇文章只讨论 <strong>AI Coding Agent</strong>：模型需要读取代码仓库、调用终端、修改多个文件、执行测试，并根据报错继续迭代。普通聊天、知识问答和只补全下一行代码的 FIM 模型不在本文范围内。</p>
<p>为了让比较有实际采购意义，本文增加两个约束：</p>
<ol>
<li>本地设备的统一内存或可用于推理的总高速内存不超过 <strong>128GB</strong>；</li>
<li>所有本地方案运行同一个开源模型，在线方案也尽量调用同名模型，避免把模型能力差异误算成硬件差异。</li>
</ol>
<p>资料和价格更新至 <strong>2026 年 7 月 19 日</strong>。硬件价格按市场数量级估算，API 价格来自官方页面，实际采购价、汇率和服务价格可能变化。</p>
<h2 id="一先说结论">一、先说结论</h2>
<p>在 128GB 上限内，我选择 <strong>Qwen3-Coder-Next</strong> 作为本地 AI Coding 的比较样本。它不是所有维度都绝对最强，但在“开源许可、Coding Agent 能力、模型尺寸和本地推理效率”这几个条件同时成立时，是目前最有代表性的高端本地模型之一。</p>
<p>我的结论是：</p>
<ol>
<li><strong>只为降低 Token 费用，不值得专门购买本地硬件。</strong> 同名模型的 Qwen Cloud API 在短上下文下，重度个人使用约 ¥216/月；一台 ¥2万～¥5万元的本地设备通常需要很多年才能靠 API 费差价回本。</li>
<li><strong>已有 64GB 以上 Mac 或 48GB 显存工作站时，本地部署很有价值。</strong> Qwen3-Coder-Next 4bit 约需 46GB，模型完整装入后可以达到可交互的 Agent 速度。</li>
<li><strong>64GB 是能用，96GB～128GB 才是舒服。</strong> 64GB 在加载 4bit 权重后，留给操作系统、运行时和 KV Cache 的空间有限；96GB～128GB 更适合长上下文和并行工具任务。</li>
<li><strong>128GB 容量不等于高性能。</strong> Ryzen AI Max+ 395 和 DGX Spark 都能装入模型，但生成速度会受到约 256～273GB/s 内存带宽限制；专业显卡带宽更高，但价格也高得多。</li>
<li><strong>个人最合理的模式仍然是混合使用。</strong> 敏感代码、离线任务和高频小修改走本地；长上下文、紧急任务和复杂重构走 API。</li>
</ol>
<h2 id="二为什么选择-qwen3-coder-next">二、为什么选择 Qwen3-Coder-Next</h2>
<p><a href="https://huggingface.co/Qwen/Qwen3-Coder-Next">Qwen3-Coder-Next</a> 是专门面向 Coding Agent 和本地开发的 MoE 模型，采用 Apache-2.0 许可证。官方模型卡给出的核心参数如下：</p>
<ul>
<li>总参数量 80B，每个 Token 激活约 3B；</li>
<li>原生上下文 262,144 Token；</li>
<li>只支持非思考模式；</li>
<li>支持函数调用和 OpenAI 兼容服务；</li>
<li>可接入 Qwen Code、Claude Code、Codex、Cline、Kilo 等 Coding 工具；</li>
<li>支持 llama.cpp、MLX-LM、Ollama、SGLang 和 vLLM 等后端。</li>
</ul>
<p>公开 Coding 基准如下：</p>
<table>
  <thead>
      <tr>
          <th>基准</th>
          <th style="text-align: right">成绩</th>
          <th>主要考察内容</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>SWE-bench Verified</td>
          <td style="text-align: right">70.6</td>
          <td>修复真实 GitHub Issue</td>
      </tr>
      <tr>
          <td>SWE-bench Pro</td>
          <td style="text-align: right">44.3</td>
          <td>更困难、更抗数据污染的软件工程任务</td>
      </tr>
      <tr>
          <td>Terminal-Bench 2.0</td>
          <td style="text-align: right">36.2</td>
          <td>终端操作、工具调用和长链路 Agent 能力</td>
      </tr>
  </tbody>
</table>
<p>这些成绩会受到 Agent 框架、提示词、工具集和推理参数影响，不能理解为在任何 IDE 中都有固定成功率。不过它们至少说明，这个模型的目标不是生成几个函数，而是完成仓库级软件工程任务。</p>
<h3 id="为什么不选择更大的模型">为什么不选择更大的模型</h3>
<p>总参数更大的模型不一定适合本次硬件约束。例如 GLM-5.2 的最低实用量化也需要约 223GB～245GB 总内存，128GB 设备只能依赖大量 SSD 或远程卸载。它虽然能“启动”，但权重会频繁跨层级搬运，无法获得正常的 AI Coding 交互体验。</p>
<p>Qwen3-Coder-Next 的优势正好相反：总参数 80B，但每 Token 只激活约 3B。根据 <a href="https://unsloth.ai/docs/models/qwen3-coder-next">Unsloth 的本地运行说明</a>：</p>
<table>
  <thead>
      <tr>
          <th>量化精度</th>
          <th style="text-align: right">模型占用</th>
          <th style="text-align: right">适合的本地内存</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>3bit</td>
          <td style="text-align: right">低于 4bit，具体随量化格式变化</td>
          <td style="text-align: right">48GB 紧凑部署</td>
      </tr>
      <tr>
          <td>4bit</td>
          <td style="text-align: right">约 46GB</td>
          <td style="text-align: right">64GB～96GB</td>
      </tr>
      <tr>
          <td>8bit</td>
          <td style="text-align: right">约 85GB</td>
          <td style="text-align: right">96GB～128GB</td>
      </tr>
      <tr>
          <td>BF16</td>
          <td style="text-align: right">约 160GB，另需运行时空间</td>
          <td style="text-align: right">超出本文上限</td>
      </tr>
  </tbody>
</table>
<p>本文默认选择 <strong>4bit</strong>。它能在 64GB 设备上运行，又比 3bit 更稳；8bit 虽然更接近原始模型，但在 96GB 设备上只剩很少空间给 KV Cache，综合体验未必更好。</p>
<h2 id="三ai-coding-性能不能只看-tokens">三、AI Coding 性能不能只看 Token/s</h2>
<p>Coding Agent 的完整链路通常是：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">理解需求 -&gt; 搜索仓库 -&gt; 阅读文件 -&gt; 规划修改 -&gt; 编辑代码
</span></span><span class="line"><span class="cl">        -&gt; 执行命令 -&gt; 分析报错 -&gt; 再次修改 -&gt; 运行测试</span></span></code></pre></td></tr></table>
</div>
</div>
<p>因此需要同时比较四种性能：</p>
<ul>
<li><strong>任务成功率</strong>：能否最终通过测试，而不是只输出看起来正确的代码；</li>
<li><strong>首 Token 延迟</strong>：提交任务后多久开始响应；</li>
<li><strong>生成速度</strong>：模型持续输出时的 Token/s；</li>
<li><strong>总完成时间</strong>：包括读取上下文、工具调用、失败重试和人工纠错。</li>
</ul>
<p>本地部署最容易只关注生成速度，但 AI Coding 中经常更慢的是长提示词的 Prefill。一次输入 50K Token 的仓库上下文，即使后续输出很快，也可能在开始生成前等待很久。</p>
<p>另一个常见误区是把“支持 256K 上下文”理解成“应该默认开启 256K”。上下文越长，KV Cache、Prefill 时间和内存碎片越大。个人使用建议从 <strong>16K 或 32K</strong> 开始，让 Agent 通过代码搜索按需读取文件，再根据项目逐步提高。</p>
<h2 id="四本地部署方案对比">四、本地部署方案对比</h2>
<p>下面所有方案都以 Qwen3-Coder-Next 4bit、单用户 AI Coding 为基础。速度等级是综合容量、内存带宽、推理后端和是否发生卸载后的相对判断，不是厂商保证值。</p>
<table>
  <thead>
      <tr>
          <th>本地方案</th>
          <th style="text-align: right">可用内存</th>
          <th>模型状态</th>
          <th>交互性能</th>
          <th style="text-align: right">采购成本粗估</th>
          <th>核心瓶颈</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>64GB Mac Studio</td>
          <td style="text-align: right">64GB 统一内存</td>
          <td>4bit 可完整装入，余量较小</td>
          <td>中～高</td>
          <td style="text-align: right">¥2.5万～¥3.5万</td>
          <td>KV Cache 余量、Metal 后端、546GB/s 带宽</td>
      </tr>
      <tr>
          <td>96GB Mac Studio M3 Ultra</td>
          <td style="text-align: right">96GB 统一内存</td>
          <td>4bit 宽裕，8bit 紧张</td>
          <td>高</td>
          <td style="text-align: right">¥4.5万～¥6万元</td>
          <td>价格、无 CUDA、长上下文 Prefill</td>
      </tr>
      <tr>
          <td>Ryzen AI Max+ 395 128GB</td>
          <td style="text-align: right">128GB 统一内存</td>
          <td>4bit/8bit 均可装入</td>
          <td>中</td>
          <td style="text-align: right">¥2万～¥3.5万元</td>
          <td>LPDDR5x 带宽、ROCm/后端成熟度</td>
      </tr>
      <tr>
          <td>DGX Spark / GB10 128GB</td>
          <td style="text-align: right">128GB 一致性内存</td>
          <td>4bit/8bit 均可装入</td>
          <td>中</td>
          <td style="text-align: right">¥3.5万～¥4.5万元</td>
          <td>273GB/s 内存带宽、ARM64 兼容性</td>
      </tr>
      <tr>
          <td>双 RTX 3090 工作站</td>
          <td style="text-align: right">48GB 显存 + 系统内存</td>
          <td>4bit 极紧，宜 3bit 或少量卸载</td>
          <td>中～高</td>
          <td style="text-align: right">¥1.2万～¥2万元</td>
          <td>跨卡通信、显存余量、功耗散热</td>
      </tr>
      <tr>
          <td>RTX 5090 + 128GB RAM</td>
          <td style="text-align: right">32GB 显存 + 128GB RAM</td>
          <td>4bit 需 CPU/GPU 混合卸载</td>
          <td>中</td>
          <td style="text-align: right">¥2.5万～¥3.5万元</td>
          <td>PCIe 搬运、系统内存带宽</td>
      </tr>
      <tr>
          <td>RTX PRO 6000 Blackwell</td>
          <td style="text-align: right">96GB 显存</td>
          <td>4bit 很宽裕，8bit 可用</td>
          <td>很高</td>
          <td style="text-align: right">通常超过 ¥10万元</td>
          <td>采购价格、功耗、并发时 KV Cache</td>
      </tr>
  </tbody>
</table>
<p>Unsloth 给出的经验是：量化模型完整放入计算设备后，可期待 20+ Token/s；一旦模型不能完整装入而需要分层卸载，速度会明显下降。这个数字是模型级经验，不是对每一种硬件的实测承诺。</p>
<h3 id="1-64gb-mac-studio能用但要控制上下文">1. 64GB Mac Studio：能用，但要控制上下文</h3>
<p>Mac 的 CPU 和 GPU 共用统一内存，不存在传统 PC 上“系统有 64GB，但显卡只能用 24GB”的硬边界。Qwen3-Coder-Next 4bit 约 46GB，64GB Mac 可以完整加载，并通过 llama.cpp、MLX-LM、Ollama 或 LM Studio 提供本地 API。</p>
<p>它的主要瓶颈不是计算单元数量，而是 <strong>剩余内存和内存带宽</strong>：</p>
<ul>
<li>权重加载后只剩约 18GB，操作系统、运行时和 KV Cache 都要使用这部分空间；</li>
<li>上下文开得过大时容易触发内存压力和 Swap；</li>
<li>发生 SSD Swap 后，速度会从可交互迅速下降；</li>
<li>Metal 对桌面推理很成熟，但新模型的高级优化通常晚于 CUDA 后端。</li>
</ul>
<p>因此 64GB 更适合 4bit + 16K～32K 上下文，不适合追求 256K 满上下文。</p>
<h3 id="2-96gb-mac-studio-m3-ultramac-路线的性能型选择">2. 96GB Mac Studio M3 Ultra：Mac 路线的性能型选择</h3>
<p><a href="https://www.apple.com/mac-studio/specs/">Apple 的技术规格</a>显示，M3 Ultra 的统一内存带宽为 819GB/s。96GB 配置运行 46GB 的 4bit 模型时，可以给 KV Cache 和其他应用留下较大空间，比 64GB 配置更适合作为长期运行的本地 Coding 服务。</p>
<p>8bit 约需 85GB，看起来也能加载，但只剩约 11GB，容易被系统和长上下文吃完。实际使用更建议继续选择高质量 4bit，把容量留给上下文。</p>
<p>主要瓶颈是：</p>
<ul>
<li>没有 CUDA，无法使用只提供 CUDA Kernel 的最新优化；</li>
<li>单用户速度不错，但多人并发能力不如专业 GPU 服务；</li>
<li>长上下文仍受 KV Cache 和 Prefill 时间限制；</li>
<li>设备价格远高于调用同名 API 的费用。</li>
</ul>
<h3 id="3-ryzen-ai-max-395-128gb容量优先的-pc-小主机">3. Ryzen AI Max+ 395 128GB：容量优先的 PC 小主机</h3>
<p><a href="https://frame.work/desktop">Framework Desktop</a> 等设备可配置 128GB LPDDR5x-8000 统一内存，官方称最多可提供 96GB 图形可寻址内存，采用 256-bit 内存总线。Qwen3-Coder-Next 4bit 可以宽裕运行，8bit 也有机会完整装入。</p>
<p>这条路线的优点是容量大、体积小、功耗低，并且保留 Linux/Windows 环境。主要瓶颈是：</p>
<ul>
<li>统一内存带宽明显低于 M3 Ultra 和高端独立显卡；</li>
<li>ROCm、Vulkan、llama.cpp 对新架构的优化程度不完全一致；</li>
<li>“模型能装入”不代表 GPU 能以独显级速度完成推理；</li>
<li>Windows、Linux 和不同驱动版本的实际表现可能差异较大。</li>
</ul>
<p>它适合把“运行更大模型和更长上下文”放在第一位的人，不适合单纯追求最快 Token/s。</p>
<h3 id="4-dgx-spark128gb--cuda但不是大显存显卡的速度">4. DGX Spark：128GB + CUDA，但不是大显存显卡的速度</h3>
<p><a href="https://www.nvidia.com/en-us/products/workstations/dgx-spark/">NVIDIA DGX Spark</a> 使用 GB10 Grace Blackwell Superchip，提供 128GB 一致性内存和 NVIDIA AI 软件栈。它的价值是小体积、CUDA 环境和大容量可以同时获得，部署容器、NIM、PyTorch、SGLang 和 vLLM 类工具更接近 NVIDIA 服务器工作流。</p>
<p>它的核心瓶颈是约 <strong>273GB/s 内存带宽</strong>。这远低于 RTX 5090 和 RTX PRO 6000，因此不能因为名字中有“DGX”就预期获得数据中心 GPU 的生成速度。其他限制还包括 ARM64 环境的软件兼容、部分 Python Wheel 和第三方工具需要重新验证。</p>
<p>DGX Spark 更适合学习和验证 CUDA 部署栈，未必是单用户桌面 AI Coding 的最高性价比方案。</p>
<h3 id="5-双-rtx-3090最便宜的-48gb-cuda-路线">5. 双 RTX 3090：最便宜的 48GB CUDA 路线</h3>
<p>两张 RTX 3090 合计有 48GB 显存，接近 4bit 模型约 46GB 的大小。它的显存带宽高、CUDA 生态成熟，二手整机价格也低于新款大内存设备。</p>
<p>问题是两张显卡的显存不会自动变成一块无缝的 48GB：</p>
<ul>
<li>推理框架必须支持 Tensor Split 或 Tensor Parallel；</li>
<li>4bit 权重几乎占满显存，KV Cache 余量很小；</li>
<li>跨卡通信受 PCIe 或 NVLink 限制；</li>
<li>两张卡的额定功耗合计可达约 700W；</li>
<li>二手显卡还存在显存稳定性和维修历史风险。</li>
</ul>
<p>更现实的做法是使用 3bit，或者把少量层和 KV Cache 放到系统内存。代价是量化质量或速度下降。</p>
<h3 id="6-rtx-5090--128gb-系统内存显卡快卸载慢">6. RTX 5090 + 128GB 系统内存：显卡快，卸载慢</h3>
<p>RTX 5090 有 32GB 显存，无法完整装下约 46GB 的 4bit 模型。llama.cpp 可以把一部分层留在系统内存，从而让模型运行起来。</p>
<p>这条路线的瓶颈非常明确：CPU 中的权重要经过系统内存和 PCIe，不能享受显卡本地 GDDR7 的完整带宽。模型越多层卸载到 CPU，生成速度越接近系统内存方案，而不是 5090 的理论性能。</p>
<p>如果已经拥有 5090，这是一条新增成本很低的尝试路线；如果准备新购设备专门运行 Qwen3-Coder-Next，则不如直接选择能完整容纳模型的硬件。</p>
<h3 id="7-rtx-pro-6000-blackwell性能最好成本最差">7. RTX PRO 6000 Blackwell：性能最好，成本最差</h3>
<p>RTX PRO 6000 Blackwell 提供 96GB GDDR7 ECC 显存和约 1792GB/s 带宽。Qwen3-Coder-Next 4bit 可以完整装入，并为 KV Cache 和并发保留大量空间，是本文本地方案中性能最接近理想状态的一种。</p>
<p>它的主要瓶颈不是技术，而是价格：单卡价格已经超过大多数个人工作站。只有在多人共享、商业服务、模型微调、专业渲染等负载能够共同分摊成本时，才可能合理。</p>
<h2 id="五推理后端怎么选">五、推理后端怎么选</h2>
<table>
  <thead>
      <tr>
          <th>设备</th>
          <th>推荐后端</th>
          <th>原因</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Apple Silicon</td>
          <td>llama.cpp、MLX-LM、Ollama、LM Studio</td>
          <td>Metal 和统一内存支持成熟</td>
      </tr>
      <tr>
          <td>Ryzen AI Max</td>
          <td>llama.cpp、ROCm 后端</td>
          <td>能使用统一内存，需按系统验证兼容性</td>
      </tr>
      <tr>
          <td>单机 NVIDIA</td>
          <td>llama.cpp、SGLang、vLLM</td>
          <td>CUDA 生态完整，服务化工具丰富</td>
      </tr>
      <tr>
          <td>双 3090/多 GPU</td>
          <td>llama.cpp Tensor Split、SGLang/vLLM Tensor Parallel</td>
          <td>需要显式拆分模型和通信</td>
      </tr>
      <tr>
          <td>DGX Spark</td>
          <td>NVIDIA 容器、SGLang、vLLM、llama.cpp</td>
          <td>适合复用 CUDA 服务部署流程</td>
      </tr>
  </tbody>
</table>
<p>个人桌面部署建议先使用 llama.cpp 或封装它的 Ollama/LM Studio。它们对 GGUF 量化和 CPU/GPU 混合卸载支持最好。团队共享、需要并发时，再考虑 SGLang 或 vLLM。</p>
<p>无论使用哪一个后端，都应向 Coding 客户端暴露 OpenAI 兼容接口。这样可以把模型接入 OpenCode、Cline、Continue、Aider、Codex 或其他 Agent，而不必把工作流锁定在单一推理程序中。</p>
<h2 id="六同名模型在线-api-方案">六、同名模型在线 API 方案</h2>
<p><a href="https://www.qwencloud.com/models/qwen3-coder-next">Qwen Cloud 的 Qwen3-Coder-Next 页面</a>提供 OpenAI 兼容 API、函数调用、结构化输出和上下文缓存。官方公布的上下文上限约 262K，最大输入约 204.8K，最大输出约 65.5K。</p>
<p>API 按单次请求的输入长度分档计价：</p>
<table>
  <thead>
      <tr>
          <th>单次请求输入长度</th>
          <th style="text-align: right">输入价格</th>
          <th style="text-align: right">输出价格</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>不超过 32K</td>
          <td style="text-align: right">$0.3 / 百万 Token</td>
          <td style="text-align: right">$1.5 / 百万 Token</td>
      </tr>
      <tr>
          <td>32K～128K</td>
          <td style="text-align: right">$0.5 / 百万 Token</td>
          <td style="text-align: right">$2.5 / 百万 Token</td>
      </tr>
      <tr>
          <td>128K～256K</td>
          <td style="text-align: right">$0.8 / 百万 Token</td>
          <td style="text-align: right">$4 / 百万 Token</td>
      </tr>
  </tbody>
</table>
<p>在线 API 的优势：</p>
<ul>
<li>无硬件投入，开通即可使用；</li>
<li>不需要下载模型、处理驱动和量化兼容；</li>
<li>由服务端承担扩容、故障恢复和推理优化；</li>
<li>不需要为了适配 64GB 设备亲自选择量化和分配 KV Cache；服务端具体精度并未公开；</li>
<li>长上下文不会挤占本机内存。</li>
</ul>
<p>API 的性能瓶颈则是：</p>
<ul>
<li>首 Token 延迟包含公网往返、排队和长提示词 Prefill；</li>
<li>官方限制为 600 RPM、100 万 TPM，高并发时会触及配额；</li>
<li>请求超过 32K 和 128K 后，输入、输出单价都会提高；</li>
<li>速度和模型版本由服务商控制，无法固定推理 Kernel；</li>
<li>私有代码需要离开本机，必须评估数据协议、地区和合规要求。</li>
</ul>
<h2 id="七api-月度成本">七、API 月度成本</h2>
<p>为了避免只看“每百万 Token”却不知道一个月花多少，下面设置三种个人 AI Coding 用量：</p>
<table>
  <thead>
      <tr>
          <th>使用强度</th>
          <th style="text-align: right">月输入</th>
          <th style="text-align: right">月输出</th>
          <th>典型场景</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>轻度</td>
          <td style="text-align: right">10M</td>
          <td style="text-align: right">2M</td>
          <td>偶尔修 Bug、解释代码</td>
      </tr>
      <tr>
          <td>中度</td>
          <td style="text-align: right">30M</td>
          <td style="text-align: right">6M</td>
          <td>每个工作日使用 Agent</td>
      </tr>
      <tr>
          <td>重度</td>
          <td style="text-align: right">50M</td>
          <td style="text-align: right">10M</td>
          <td>多项目、高频仓库级任务</td>
      </tr>
  </tbody>
</table>
<p>按 1 美元约 7.2 元人民币计算，暂不计上下文缓存可能带来的进一步优惠：</p>
<table>
  <thead>
      <tr>
          <th>使用强度</th>
          <th style="text-align: right">请求通常 ≤32K</th>
          <th style="text-align: right">请求通常为 32K～128K</th>
          <th style="text-align: right">请求通常为 128K～256K</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>轻度</td>
          <td style="text-align: right">$6 / 约 ¥43</td>
          <td style="text-align: right">$10 / 约 ¥72</td>
          <td style="text-align: right">$16 / 约 ¥115</td>
      </tr>
      <tr>
          <td>中度</td>
          <td style="text-align: right">$18 / 约 ¥130</td>
          <td style="text-align: right">$30 / 约 ¥216</td>
          <td style="text-align: right">$48 / 约 ¥346</td>
      </tr>
      <tr>
          <td>重度</td>
          <td style="text-align: right">$30 / 约 ¥216</td>
          <td style="text-align: right">$50 / 约 ¥360</td>
          <td style="text-align: right">$80 / 约 ¥576</td>
      </tr>
  </tbody>
</table>
<p>这里的上下文档位是单次请求长度，不是月度 Token 总量。一个设计合理的 Coding Agent 会通过代码搜索、摘要和缓存尽量让请求留在 32K 内；如果每轮都把整个仓库重复发送到 128K 以上，不仅费用增加，首 Token 延迟也会变长。</p>
<h2 id="八本地硬件的真实月成本">八、本地硬件的真实月成本</h2>
<p>“本地 Token 免费”并不等于运行成本为零。把设备按 3 年折旧，并粗略加入每月 100 小时推理的电费，可以得到下面的数量级：</p>
<table>
  <thead>
      <tr>
          <th>设备</th>
          <th style="text-align: right">一次性投入粗估</th>
          <th style="text-align: right">三年折旧 + 电费/月</th>
          <th>相对 API 的成本结论</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>已有 64GB/48GB 设备</td>
          <td style="text-align: right">新增投入接近 0</td>
          <td style="text-align: right">约 ¥20～¥200</td>
          <td>最划算，适合直接尝试</td>
      </tr>
      <tr>
          <td>二手双 RTX 3090</td>
          <td style="text-align: right">¥1.2万～¥2万元</td>
          <td style="text-align: right">约 ¥450～¥850</td>
          <td>通常高于同名 API</td>
      </tr>
      <tr>
          <td>Ryzen AI Max 128GB</td>
          <td style="text-align: right">¥2万～¥3.5万元</td>
          <td style="text-align: right">约 ¥580～¥1050</td>
          <td>很难仅靠 Token 费回本</td>
      </tr>
      <tr>
          <td>64GB Mac Studio</td>
          <td style="text-align: right">¥2.5万～¥3.5万元</td>
          <td style="text-align: right">约 ¥720～¥1050</td>
          <td>购买理由应是隐私和日常生产力</td>
      </tr>
      <tr>
          <td>DGX Spark 128GB</td>
          <td style="text-align: right">¥3.5万～¥4.5万元</td>
          <td style="text-align: right">约 ¥1000～¥1350</td>
          <td>更适合 CUDA 研发和部署验证</td>
      </tr>
      <tr>
          <td>96GB Mac Studio</td>
          <td style="text-align: right">¥4.5万～¥6万元</td>
          <td style="text-align: right">约 ¥1280～¥1750</td>
          <td>单用户纯推理经济性较低</td>
      </tr>
      <tr>
          <td>RTX PRO 6000 工作站</td>
          <td style="text-align: right">通常超过 ¥10万元</td>
          <td style="text-align: right">通常超过 ¥2900</td>
          <td>需要团队或商业负载分摊</td>
      </tr>
  </tbody>
</table>
<p>假设重度使用的 API 费用为 ¥216～¥576/月：</p>
<ul>
<li>¥2万元设备的静态回本时间约为 2.9～7.7 年；</li>
<li>¥3万元设备约为 4.3～11.6 年；</li>
<li>¥5万元设备约为 7.2～19.3 年。</li>
</ul>
<p>这还没有计算 SSD、设备残值、维护时间、硬件故障和本地 4bit 量化的能力损失。只为了省 API 费购买新机器，经济上基本不成立。</p>
<h2 id="九性能瓶颈总表">九、性能瓶颈总表</h2>
<table>
  <thead>
      <tr>
          <th>方案</th>
          <th>首 Token</th>
          <th>生成速度</th>
          <th>长上下文</th>
          <th>任务质量</th>
          <th>最主要瓶颈</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>64GB Mac</td>
          <td>本地短上下文快</td>
          <td>可交互</td>
          <td>受内存余量限制</td>
          <td>4bit，接近但不等于原版</td>
          <td>KV Cache 和 Swap</td>
      </tr>
      <tr>
          <td>96GB M3 Ultra</td>
          <td>较快</td>
          <td>本地方案中较好</td>
          <td>4bit 下较宽裕</td>
          <td>4bit</td>
          <td>Metal 生态、Prefill</td>
      </tr>
      <tr>
          <td>Ryzen 128GB</td>
          <td>较快</td>
          <td>中等</td>
          <td>容量宽裕</td>
          <td>可选 4bit/8bit</td>
          <td>内存带宽和软件优化</td>
      </tr>
      <tr>
          <td>DGX Spark 128GB</td>
          <td>较快</td>
          <td>中等</td>
          <td>容量宽裕</td>
          <td>可选 4bit/8bit</td>
          <td>273GB/s 带宽、ARM64</td>
      </tr>
      <tr>
          <td>双 RTX 3090</td>
          <td>较快</td>
          <td>中～高</td>
          <td>显存余量很小</td>
          <td>3bit/4bit</td>
          <td>跨卡通信和容量</td>
      </tr>
      <tr>
          <td>5090 + CPU 卸载</td>
          <td>较快</td>
          <td>随卸载比例下降</td>
          <td>使用 RAM 可扩展</td>
          <td>4bit</td>
          <td>PCIe 和 DDR 带宽</td>
      </tr>
      <tr>
          <td>RTX PRO 6000</td>
          <td>快</td>
          <td>很高</td>
          <td>4bit 下宽裕</td>
          <td>4bit/8bit</td>
          <td>价格和并发 KV Cache</td>
      </tr>
      <tr>
          <td>Qwen Cloud API</td>
          <td>受网络和排队影响</td>
          <td>服务端通常稳定</td>
          <td>无本机容量压力</td>
          <td>精度与版本由服务商管理</td>
          <td>网络、限流、隐私、长上下文价格</td>
      </tr>
  </tbody>
</table>
<h2 id="十不同用户应该怎么选">十、不同用户应该怎么选</h2>
<table>
  <thead>
      <tr>
          <th>用户情况</th>
          <th>推荐方案</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>没有现成大内存硬件</td>
          <td>直接使用 Qwen3-Coder-Next API</td>
      </tr>
      <tr>
          <td>已有 64GB Apple Silicon</td>
          <td>本地 4bit，默认 16K～32K 上下文</td>
      </tr>
      <tr>
          <td>想买安静、省电、维护简单的主机</td>
          <td>64GB/96GB Mac Studio</td>
      </tr>
      <tr>
          <td>需要 128GB，偏好 Linux/Windows</td>
          <td>Ryzen AI Max+ 395 主机</td>
      </tr>
      <tr>
          <td>需要 128GB 和 CUDA 软件栈</td>
          <td>DGX Spark/GB10</td>
      </tr>
      <tr>
          <td>预算有限、能维护 Linux 和多显卡</td>
          <td>二手双 RTX 3090，优先 3bit 或混合卸载</td>
      </tr>
      <tr>
          <td>已有 RTX 5090</td>
          <td>增加系统内存，用 llama.cpp 混合卸载，不必立刻换设备</td>
      </tr>
      <tr>
          <td>小团队需要高并发</td>
          <td>96GB 专业显卡或云 GPU 服务</td>
      </tr>
      <tr>
          <td>代码绝对不能离开内网</td>
          <td>本地 4bit；性能成本让位于隐私要求</td>
      </tr>
      <tr>
          <td>个人追求综合性价比</td>
          <td>本地处理敏感和高频任务，困难任务调用 API</td>
      </tr>
  </tbody>
</table>
<h2 id="十一我的推荐配置">十一、我的推荐配置</h2>
<p>如果从零购买，我不会为了省 Token 费组建多卡服务器，而会在下面三种方案中选择：</p>
<h3 id="方案-a不买硬件直接-api">方案 A：不买硬件，直接 API</h3>
<p>适合大多数个人开发者。按实际用量付费，短上下文下即使重度使用也约 ¥216/月。省下的硬件预算可以购买很多年的 API，并且不需要维护模型和驱动。</p>
<h3 id="方案-b已有-64gb-设备本地-4bit--api-兜底">方案 B：已有 64GB 设备，本地 4bit + API 兜底</h3>
<p>这是性价比最高的本地路线。本地运行 Qwen3-Coder-Next 4bit，设置 16K～32K 上下文；同一个任务连续失败两次、上下文过长或需要更高成功率时，切换 Qwen Cloud API。</p>
<h3 id="方案-c隐私优先新购-96gb128gb-设备">方案 C：隐私优先，新购 96GB～128GB 设备</h3>
<p>Mac Studio 更安静省心，Ryzen AI Max 主机容量价格比更好，DGX Spark 的 CUDA 生态更完整。三者都应优先运行 4bit，而不是为了 8bit 把内存占满。额外容量应留给 KV Cache、IDE、编译和测试进程。</p>
<h2 id="总结">总结</h2>
<p>在统一内存不超过 128GB 的条件下，Qwen3-Coder-Next 是一个很合适的高端本地 AI Coding 样本：80B 总参数、3B 激活参数、4bit 约 46GB，既有较强的公开 Coding 成绩，也能在个人可以买到的设备上完整运行。</p>
<p>不同本地方案的本质差异可以压缩成三句话：</p>
<ul>
<li><strong>Mac Studio</strong> 用高带宽统一内存换取安静、简单，但受 Metal 生态和不可升级限制；</li>
<li><strong>Ryzen AI Max 与 DGX Spark</strong> 用 128GB 容量换取更大的模型空间，但性能瓶颈是内存带宽；</li>
<li><strong>NVIDIA 多卡或专业显卡</strong> 速度和生态最好，但瓶颈转向显存容量、跨卡通信和采购成本。</li>
</ul>
<p>而 API 的瓶颈不是本地内存，而是网络延迟、限流、隐私和长上下文价格。以目前同名模型的价格看，个人新购硬件几乎不可能单靠节省 Token 费用回本。</p>
<p>所以最终建议仍然是：</p>
<blockquote>
<p><strong>没有现成硬件就先用 API；已有 64GB 以上设备就部署本地 4bit；有隐私或离线刚需再购买 96GB～128GB 设备。不要为了“本地免费”忽略折旧、速度和任务成功率。</strong></p></blockquote>
<h2 id="参考资料">参考资料</h2>
<ul>
<li><a href="https://huggingface.co/Qwen/Qwen3-Coder-Next">Qwen3-Coder-Next 官方模型卡</a></li>
<li><a href="https://unsloth.ai/docs/models/qwen3-coder-next">Qwen3-Coder-Next 本地运行、量化与内存要求</a></li>
<li><a href="https://www.qwencloud.com/models/qwen3-coder-next">Qwen3-Coder-Next 官方 API 能力与价格</a></li>
<li><a href="https://github.com/QwenLM/Qwen3-Coder">Qwen3-Coder GitHub</a></li>
<li><a href="https://github.com/ggml-org/llama.cpp">llama.cpp</a></li>
<li><a href="https://github.com/vllm-project/vllm">vLLM</a></li>
<li><a href="https://github.com/sgl-project/sglang">SGLang</a></li>
<li><a href="https://www.apple.com/mac-studio/specs/">Apple Mac Studio 技术规格</a></li>
<li><a href="https://frame.work/desktop">Framework Desktop Ryzen AI Max</a></li>
<li><a href="https://www.nvidia.com/en-us/products/workstations/dgx-spark/">NVIDIA DGX Spark</a></li>
<li><a href="https://www.nvidia.com/en-us/products/workstations/professional-desktop-gpus/rtx-pro-6000/">NVIDIA RTX PRO 6000 Blackwell</a></li>
<li><a href="https://www.nvidia.com/en-us/geforce/graphics-cards/50-series/rtx-5090/">NVIDIA GeForce RTX 5090</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Mel Spectrogram 原理与实践</title>
      <link>https://lyapple2008.github.io/posts/202607/mel_spectrogram/</link>
      <pubDate>Sat, 04 Jul 2026 10:23:20 +0800</pubDate>
      <guid>https://lyapple2008.github.io/posts/202607/mel_spectrogram/</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Mel Spectrogram 是语音识别、音频分类、音乐信息检索里非常常见的输入特征。它做的事情可以简单理解为：把一段一维波形，变成一张“符合人耳听觉习惯”的时间-频率能量图。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<blockquote>
<p>Mel Spectrogram 是语音识别、音频分类、音乐信息检索里非常常见的输入特征。它做的事情可以简单理解为：把一段一维波形，变成一张“符合人耳听觉习惯”的时间-频率能量图。</p></blockquote>
<hr>
<h2 id="1-从一段声音开始">1. 从一段声音开始</h2>
<p>计算机拿到的音频通常是一串采样点：</p>
<p><img alt="waveform" loading="lazy" src="/images/2026-07/waveform.png"></p>
<p>这串数字描述了空气振动随时间变化的幅度。问题是，直接看波形很难回答这些问题：</p>
<ul>
<li>这段声音里有哪些频率？</li>
<li>某个频率是在什么时候出现的？</li>
<li>对人耳来说，哪些变化更重要？</li>
</ul>
<p>所以我们需要一种更适合分析声音的表示。</p>

<div class="mermaid">graph LR
    A[原始波形 waveform] --&gt; B[分帧 window]
    B --&gt; C[FFT]
    C --&gt; D[线性频谱 spectrogram]
    D --&gt; E[Mel 滤波器组]
    E --&gt; F[Mel Spectrogram]
    F --&gt; G[log 压缩]
    G --&gt; H[Log-Mel 特征]</div>
<hr>
<h2 id="2-为什么不能只做一次-fft">2. 为什么不能只做一次 FFT？</h2>
<p>FFT 可以告诉我们“一整段声音里有哪些频率”，但它会丢掉时间信息。</p>
<p>比如一段音频里先说 “a”，后说 “i”。如果直接对整段音频做 FFT，我们只能知道整段音频总体有哪些频率，不知道这些频率分别出现在什么时候。</p>
<p>语音是随时间变化的信号，所以更常用的是 STFT（Short-Time Fourier Transform，短时傅里叶变换）。</p>
<p>STFT 的做法是：</p>
<ol>
<li>把音频切成很多短帧，比如每帧 25ms。</li>
<li>相邻帧之间有重叠，比如每 10ms 移动一次。</li>
<li>对每一帧分别做 FFT。</li>
<li>把每一帧的频谱按时间排起来。</li>
</ol>
<p><img alt="幅度谱计算过程" loading="lazy" src="/images/2026-07/%E5%B9%85%E5%BA%A6%E8%B0%B1%E8%AE%A1%E7%AE%97%E8%BF%87%E7%A8%8B.png"></p>
<p>这张“横轴是时间，纵轴是频率，颜色表示能量”的图，就是 Spectrogram（声谱图）。</p>
<hr>
<h2 id="3-stft-到底在算什么">3. STFT 到底在算什么？</h2>
<p>假设输入音频是 $x[n]$，第 $m$ 帧的起点是 $mH$，其中 $H$ 是 hop length。每帧先乘一个窗函数 $w[n]$，再做 FFT：</p>
<p>$$
X(m, k) = \sum_{n=0}^{N-1} x[n + mH] \cdot w[n] \cdot e^{-j2\pi kn/N}
$$</p>
<p>其中：</p>
<table>
  <thead>
      <tr>
          <th>符号</th>
          <th>含义</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>$N$</td>
          <td>FFT 点数，也通常是窗口大小附近的值</td>
      </tr>
      <tr>
          <td>$H$</td>
          <td>帧移，决定相邻帧间隔</td>
      </tr>
      <tr>
          <td>$m$</td>
          <td>第几帧，也就是时间维索引</td>
      </tr>
      <tr>
          <td>$k$</td>
          <td>第几个频率 bin</td>
      </tr>
      <tr>
          <td>$w[n]$</td>
          <td>窗函数，常用 Hann window</td>
      </tr>
  </tbody>
</table>
<p>FFT 输出的是复数，实际做特征时通常取幅度谱或功率谱：</p>
<p>$$
P(m, k) = |X(m, k)|^2
$$</p>
<p>到这里得到的是普通线性频谱。它的频率刻度是线性的，比如 0Hz、100Hz、200Hz、300Hz &hellip;</p>
<hr>
<h2 id="4-为什么要用-mel-频率">4. 为什么要用 Mel 频率？</h2>
<p>人耳对频率的感知不是线性的。</p>
<p>我们对低频变化更敏感，对高频变化相对迟钝。比如：</p>
<ul>
<li>300Hz 到 400Hz 的变化，人耳容易感觉明显。</li>
<li>8000Hz 到 8100Hz 的变化，人耳通常没那么敏感。</li>
</ul>
<p>这就像看地图：市区街道很密，需要细看；荒野区域可以粗略一些。Mel 频率就是一种“听觉地图投影”：低频区域分辨率高，高频区域分辨率低。</p>
<p>常用的 Hz 到 Mel 转换公式是：</p>
<p>$$
m = 2595 \log_{10}(1 + \frac{f}{700})
$$</p>
<p>反过来：</p>
<p>$$
f = 700(10^{m/2595} - 1)
$$</p>
<p><img alt="frequency to mel frequency" loading="lazy" src="/images/2026-07/linear_fs_to_mel_fs.png"></p>
<p>大致效果如下：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Hz 线性刻度:
</span></span><span class="line"><span class="cl">0      1000      2000      3000      4000      5000      6000      7000      8000
</span></span><span class="line"><span class="cl">|--------|---------|---------|---------|---------|---------|---------|---------|
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Mel 滤波器中心频率:
</span></span><span class="line"><span class="cl">0  90 190 300 430 580  760  980  1250  1600  2050  2650  3500  5000       8000
</span></span><span class="line"><span class="cl">|--|--|---|---|----|----|----|----|-----|-----|-----|-----|------|----------|
</span></span><span class="line"><span class="cl">低频更密，高频更稀</span></span></code></pre></td></tr></table>
</div>
</div>
<hr>
<h2 id="5-mel-滤波器组是什么">5. Mel 滤波器组是什么？</h2>
<p>Mel Spectrogram 的核心不是一个神秘算法，而是一组三角形滤波器。</p>
<p>它们铺在线性频谱上，每个滤波器负责统计一小段频率范围内的能量。低频处滤波器更窄、更密；高频处滤波器更宽、更稀。</p>
<p><img alt="标准梅尔滤波器" loading="lazy" src="/images/2026-07/%E6%A0%87%E5%87%86%E6%A2%85%E5%B0%94%E6%BB%A4%E6%B3%A2%E5%99%A8.png"></p>
<p>如果线性功率谱是 $P(m, k)$，Mel 滤波器组是 $M(i, k)$，那么第 $i$ 个 Mel 频带的能量为：</p>
<p>$$
S(m, i) = \sum_k P(m, k) \cdot M(i, k)
$$</p>
<p>写成矩阵形式更直观：</p>
<p>$$
\text{MelPower} = \text{PowerSpectrogram} \times \text{MelFilterBank}^T
$$</p>
<p>最终通常再做 log 压缩：</p>
<p>$$
\text{LogMel}(m, i) = \log(S(m, i) + \epsilon)
$$</p>
<p>加上 $\epsilon$ 是为了避免 $\log(0)$。</p>
<h3 id="whisper-中的-mel-filter-bank">Whisper 中的 Mel filter bank</h3>
<p>Whisper 使用的 Mel filter bank 并不是在推理时临时手写生成的。OpenAI Whisper 里预先保存了 <code>mel_filters.npz</code>，其中 80 维版本的生成方式等价于：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">librosa</span><span class="o">.</span><span class="n">filters</span><span class="o">.</span><span class="n">mel</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">sr</span><span class="o">=</span><span class="mi">16000</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">n_fft</span><span class="o">=</span><span class="mi">400</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">n_mels</span><span class="o">=</span><span class="mi">80</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span></span></span></code></pre></td></tr></table>
</div>
</div>
<p>这里没有显式写出的默认参数也很重要：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">htk</span><span class="o">=</span><span class="kc">False</span>
</span></span><span class="line"><span class="cl"><span class="n">norm</span><span class="o">=</span><span class="s2">&#34;slaney&#34;</span></span></span></code></pre></td></tr></table>
</div>
</div>
<p><img alt="面积归一化的梅尔滤波器" loading="lazy" src="/images/2026-07/%E9%9D%A2%E7%A7%AF%E5%BD%92%E4%B8%80%E5%8C%96%E7%9A%84%E6%A2%85%E5%B0%94%E6%BB%A4%E6%B3%A2%E5%99%A8.png"></p>
<p>也就是说，Whisper 使用的是 Slaney 风格的 Mel 标尺，并且对 Mel filter 做了面积归一化。whisper.cpp 在模型转换时会把这组预计算好的 filter bank 写入 ggml 模型文件，运行时只是加载并使用这些系数。</p>
<p>面积归一化的含义是：三角滤波器不再追求每个峰值都等于 1，而是让每个滤波器覆盖区域的面积更接近一致。因为 Mel 滤波器在 Hz 轴上越往高频越宽，如果所有三角形峰值都固定为 1，高频滤波器会覆盖更多 FFT bin，自然累加出更大的能量。这会给高频 Mel band 带来额外偏置。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">不归一化：
</span></span><span class="line"><span class="cl">高频 filter 更宽 -&gt; 累加更多频点 -&gt; 输出偏大
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">面积归一化：
</span></span><span class="line"><span class="cl">高频 filter 更宽 -&gt; 峰值相应降低 -&gt; 各频带能量更可比</span></span></code></pre></td></tr></table>
</div>
</div>
<p>因此，<code>norm=&quot;slaney&quot;</code> 的好处不是让图形看起来更整齐，而是让 Mel 频带更接近“对一个频率区间的加权平均能量”，而不是简单“加权求和”。这能减少由于滤波器宽度不同造成的能量偏差，也保证推理时的输入特征分布和 Whisper 训练时保持一致。</p>
<hr>
<h2 id="6-为什么模型喜欢-log-mel">6. 为什么模型喜欢 Log-Mel？</h2>
<p>很多 ASR、音频分类模型并不直接吃原始波形，而是吃 Log-Mel 特征。原因主要有几个。</p>
<h3 id="61-更接近人耳听觉">6.1 更接近人耳听觉</h3>
<p>语音识别的目标不是还原波形，而是识别“人听到的内容”。Mel 刻度把频率轴变成更接近人耳感知的尺度，对语音任务很自然。</p>
<h3 id="62-降低输入维度">6.2 降低输入维度</h3>
<p>以 16kHz 音频、400 点 FFT 为例，线性频谱有 201 个频率 bin。经过 Mel 滤波器组后，常见配置只保留 80 个 Mel bins。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">线性功率谱:  201 维 / frame
</span></span><span class="line"><span class="cl">Mel 特征:     80 维 / frame</span></span></code></pre></td></tr></table>
</div>
</div>
<p>这相当于把不必要的频率细节做了一次听觉意义上的压缩。</p>
<h3 id="63-log-压缩更适合学习">6.3 log 压缩更适合学习</h3>
<p>声音能量的动态范围很大。一个很响的声音可能比很轻的声音能量大几百倍甚至几千倍。直接用线性能量，模型容易被大能量区域支配。</p>
<p>log 压缩可以把乘法比例变成加法差异：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">energy:  1      10      100      1000
</span></span><span class="line"><span class="cl">log:     0      2.3     4.6      6.9</span></span></code></pre></td></tr></table>
</div>
</div>
<p>这也更接近人耳对响度的感知方式。</p>
<hr>
<h2 id="7-常见参数怎么选">7. 常见参数怎么选？</h2>
<p>语音任务里最常见的配置：</p>
<table>
  <thead>
      <tr>
          <th>参数</th>
          <th>常见值</th>
          <th>说明</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>sample_rate</td>
          <td>16000</td>
          <td>ASR 常用 16kHz</td>
      </tr>
      <tr>
          <td>win_length</td>
          <td>400</td>
          <td>25ms，$16000 \times 0.025 = 400$</td>
      </tr>
      <tr>
          <td>hop_length</td>
          <td>160</td>
          <td>10ms，$16000 \times 0.01 = 160$</td>
      </tr>
      <tr>
          <td>n_fft</td>
          <td>512</td>
          <td>FFT 点数，通常不小于 win_length</td>
      </tr>
      <tr>
          <td>n_mels</td>
          <td>80</td>
          <td>Whisper、Paraformer 等常见取值</td>
      </tr>
      <tr>
          <td>f_min</td>
          <td>0</td>
          <td>最低频率</td>
      </tr>
      <tr>
          <td>f_max</td>
          <td>8000</td>
          <td>16kHz 采样时 Nyquist 频率</td>
      </tr>
  </tbody>
</table>
<p>帧长和帧移的意义：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">16kHz 音频:
</span></span><span class="line"><span class="cl">1 秒 = 16000 个采样点
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">win_length = 400  -&gt; 每帧 25ms
</span></span><span class="line"><span class="cl">hop_length = 160  -&gt; 每 10ms 产生一帧
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">所以 1 秒音频大约产生 100 帧特征。</span></span></code></pre></td></tr></table>
</div>
</div>
<p>如果最终特征维度是 80，那么 1 秒音频大约变成：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">100 frames x 80 mel bins</span></span></code></pre></td></tr></table>
</div>
</div>
<p>这已经很像一张小图片了。横轴是时间，纵轴是 Mel 频率，像素值是 log energy。</p>
<hr>
<h2 id="8-实践用-librosa-计算-mel-spectrogram">8. 实践：用 librosa 计算 Mel Spectrogram</h2>
<p>先安装依赖：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">pip install librosa matplotlib soundfile</span></span></code></pre></td></tr></table>
</div>
</div>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">show_mel_filters_by_librosa</span><span class="p">(</span><span class="n">n_fft</span><span class="p">,</span> <span class="n">n_mel</span><span class="p">,</span> <span class="n">sample_rate</span><span class="p">,</span> <span class="n">htk</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span> <span class="n">norm</span><span class="o">=</span><span class="s1">&#39;slaney&#39;</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="kn">import</span> <span class="nn">librosa</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">filters</span> <span class="o">=</span> <span class="n">librosa</span><span class="o">.</span><span class="n">filters</span><span class="o">.</span><span class="n">mel</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">sr</span><span class="o">=</span><span class="n">sample_rate</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">n_fft</span><span class="o">=</span><span class="n">n_fft</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">n_mels</span><span class="o">=</span><span class="n">n_mel</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">htk</span><span class="o">=</span><span class="n">htk</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">norm</span><span class="o">=</span><span class="n">norm</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">frequency_bins</span> <span class="o">=</span> <span class="n">librosa</span><span class="o">.</span><span class="n">fft_frequencies</span><span class="p">(</span><span class="n">sr</span><span class="o">=</span><span class="n">sample_rate</span><span class="p">,</span> <span class="n">n_fft</span><span class="o">=</span><span class="n">n_fft</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">fig</span><span class="p">,</span> <span class="n">axes</span> <span class="o">=</span> <span class="n">plt</span><span class="o">.</span><span class="n">subplots</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="n">figsize</span><span class="o">=</span><span class="p">(</span><span class="mi">12</span><span class="p">,</span> <span class="mi">8</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">n_mel</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="n">axes</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">.</span><span class="n">plot</span><span class="p">(</span><span class="n">frequency_bins</span><span class="p">,</span> <span class="n">filters</span><span class="p">[</span><span class="n">i</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">    <span class="n">axes</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">.</span><span class="n">set_title</span><span class="p">(</span><span class="s1">&#39;Mel Filter Bank by librosa&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">axes</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">.</span><span class="n">set_xlabel</span><span class="p">(</span><span class="s1">&#39;Frequency (Hz)&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">axes</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">.</span><span class="n">set_ylabel</span><span class="p">(</span><span class="s1">&#39;Weight&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">axes</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">.</span><span class="n">set_xlim</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">sample_rate</span> <span class="o">/</span> <span class="mi">2</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">axes</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">.</span><span class="n">grid</span><span class="p">(</span><span class="kc">True</span><span class="p">,</span> <span class="n">alpha</span><span class="o">=</span><span class="mf">0.3</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">img</span> <span class="o">=</span> <span class="n">axes</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="o">.</span><span class="n">imshow</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">filters</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">origin</span><span class="o">=</span><span class="s1">&#39;lower&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">aspect</span><span class="o">=</span><span class="s1">&#39;auto&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">extent</span><span class="o">=</span><span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="n">sample_rate</span> <span class="o">/</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">n_mel</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">axes</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="o">.</span><span class="n">set_title</span><span class="p">(</span><span class="s1">&#39;Mel Filter Bank Matrix by librosa&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">axes</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="o">.</span><span class="n">set_xlabel</span><span class="p">(</span><span class="s1">&#39;Frequency (Hz)&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">axes</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="o">.</span><span class="n">set_ylabel</span><span class="p">(</span><span class="s1">&#39;Mel filter index&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">fig</span><span class="o">.</span><span class="n">colorbar</span><span class="p">(</span><span class="n">img</span><span class="p">,</span> <span class="n">ax</span><span class="o">=</span><span class="n">axes</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="n">label</span><span class="o">=</span><span class="s1">&#39;Weight&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">plt</span><span class="o">.</span><span class="n">tight_layout</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">plt</span><span class="o">.</span><span class="n">show</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">filters</span></span></span></code></pre></td></tr></table>
</div>
</div>
<hr>
<h2 id="9-与-asr-前端的关系">9. 与 ASR 前端的关系</h2>
<p>在 ASR 系统里，Mel Spectrogram 通常是声学前端的第一步。</p>

<div class="mermaid">graph LR
    A[PCM / WAV] --&gt; B[Log-Mel / FBank]
    B --&gt; C[CMVN]
    C --&gt; D[LFR / stacking]
    D --&gt; E[Encoder]
    E --&gt; F[CTC / Attention / RNN-T]
    F --&gt; G[文本]</div>
<p>不同系统叫法可能略有差异：</p>
<table>
  <thead>
      <tr>
          <th>名称</th>
          <th>常见含义</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Mel Spectrogram</td>
          <td>Mel 频带能量图，通常可能还没取 log</td>
      </tr>
      <tr>
          <td>Log-Mel</td>
          <td>取 log 或 dB 后的 Mel 特征</td>
      </tr>
      <tr>
          <td>FBank</td>
          <td>Filter Bank 特征，在 ASR 中通常就是 Log-Mel filterbank</td>
      </tr>
      <tr>
          <td>MFCC</td>
          <td>在 Log-Mel 后再做 DCT 得到的倒谱特征</td>
      </tr>
  </tbody>
</table>
<p>现代深度学习 ASR 更常用 Log-Mel / FBank，而不是 MFCC。因为神经网络可以自己学习频带之间的组合关系，不一定需要 DCT 做进一步压缩。</p>
<hr>
<h2 id="10-常见坑">10. 常见坑</h2>
<h3 id="101-采样率要对齐">10.1 采样率要对齐</h3>
<p>模型训练用 16kHz，推理也应该用 16kHz。采样率不一致会导致频率轴整体错位。</p>
<h3 id="102-power-和-amplitude-不要混">10.2 power 和 amplitude 不要混</h3>
<p>幅度谱是：</p>
<p>$$
|X|
$$</p>
<p>功率谱是：</p>
<p>$$
|X|^2
$$</p>
<p><code>librosa.feature.melspectrogram(..., power=2.0)</code> 用的是功率谱。不同框架如果这里不一致，特征数值会明显不同。</p>
<h3 id="103-loglndb-的差异">10.3 log、ln、dB 的差异</h3>
<p>有些实现用自然对数：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">np</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="n">x</span> <span class="o">+</span> <span class="n">eps</span><span class="p">)</span></span></span></code></pre></td></tr></table>
</div>
</div>
<p>有些实现用 dB：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="mi">10</span> <span class="o">*</span> <span class="n">np</span><span class="o">.</span><span class="n">log10</span><span class="p">(</span><span class="n">x</span> <span class="o">+</span> <span class="n">eps</span><span class="p">)</span></span></span></code></pre></td></tr></table>
</div>
</div>
<p>它们表达的信息相近，但数值尺度不同。部署模型时要和训练阶段保持一致。</p>
<h3 id="104-shape-方向要确认">10.4 shape 方向要确认</h3>
<p><code>librosa</code> 常见输出是：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">(mel_bins, frames)</span></span></code></pre></td></tr></table>
</div>
</div>
<p>ASR runtime 常见输入是：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">(frames, mel_bins)</span></span></code></pre></td></tr></table>
</div>
</div>
<p>这里错了，模型不会报特别明显的错误，但识别结果会很离谱。</p>
<hr>
<h2 id="11-总结">11. 总结</h2>
<p>Mel Spectrogram 可以理解成三层变换：</p>
<ol>
<li><strong>STFT</strong>：把波形变成时间-频率图。</li>
<li><strong>Mel 滤波器组</strong>：把线性频率轴变成更接近人耳的 Mel 频率轴。</li>
<li><strong>log 压缩</strong>：把巨大能量范围压缩到更适合模型学习的尺度。</li>
</ol>
<p>最终得到的 Log-Mel 特征，是一张“人耳友好 + 模型友好”的声音图片。它既保留了语音随时间变化的频率结构，又比原始波形更稳定、更低维、更容易被神经网络使用。</p>
<hr>
<h2 id="参考">参考</h2>
<ul>
<li><a href="https://medium.com/@kunalkushwahatg/exploring-mel-spectrograms-a-powerful-feature-extraction-tool-for-audio-signals-3b68ff6fcf96">Exploring Mel Spectrograms: A Powerful Feature Extraction Tool for Audio Signals</a></li>
<li><a href="https://medium.com/@kunalkushwahatg/zooming-in-understanding-the-short-time-fourier-transform-27c58f6a08ee">Zooming In: Understanding the Short-Time Fourier Transform</a></li>
<li><a href="https://librosa.org/doc/main/generated/librosa.feature.melspectrogram.html">librosa.feature.melspectrogram</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>音频重采样：从原理到工程实现</title>
      <link>https://lyapple2008.github.io/posts/202604/2026-04-20-%E9%9F%B3%E9%A2%91%E9%87%8D%E9%87%87%E6%A0%B7/</link>
      <pubDate>Mon, 20 Apr 2026 15:02:03 +0800</pubDate>
      <guid>https://lyapple2008.github.io/posts/202604/2026-04-20-%E9%9F%B3%E9%A2%91%E9%87%8D%E9%87%87%E6%A0%B7/</guid>
      <description>&lt;p&gt;深入理解 sinc 卷积如何完成信号重建，探索从朴素算法到 SpeexDSP 的优化之路&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;第一部分重采样的原理与处理过程&#34;&gt;第一部分：重采样的原理与处理过程&lt;/h2&gt;
&lt;h3 id=&#34;目录&#34;&gt;目录&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&#34;https://lyapple2008.github.io/posts/202604/2026-04-20-%E9%9F%B3%E9%A2%91%E9%87%8D%E9%87%87%E6%A0%B7/#sampling-nature&#34;&gt;采样操作的本质：频谱复制&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://lyapple2008.github.io/posts/202604/2026-04-20-%E9%9F%B3%E9%A2%91%E9%87%8D%E9%87%87%E6%A0%B7/#reconstruction-condition&#34;&gt;重建的条件：无混叠&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://lyapple2008.github.io/posts/202604/2026-04-20-%E9%9F%B3%E9%A2%91%E9%87%8D%E9%87%87%E6%A0%B7/#ideal-lpf&#34;&gt;理想低通滤波器&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://lyapple2008.github.io/posts/202604/2026-04-20-%E9%9F%B3%E9%A2%91%E9%87%8D%E9%87%87%E6%A0%B7/#convolution-reconstruction&#34;&gt;卷积重建&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://lyapple2008.github.io/posts/202604/2026-04-20-%E9%9F%B3%E9%A2%91%E9%87%8D%E9%87%87%E6%A0%B7/#resampling-process&#34;&gt;重采样过程&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://lyapple2008.github.io/posts/202604/2026-04-20-%E9%9F%B3%E9%A2%91%E9%87%8D%E9%87%87%E6%A0%B7/#why-sinc&#34;&gt;为什么是 sinc 而不是其他函数&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3 id=&#34;11-采样操作的本质频谱复制&#34;&gt;1.1 采样操作的本质：频谱复制&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;核心结论 — Whittaker-Shannon 插值公式：&lt;/strong&gt;&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>深入理解 sinc 卷积如何完成信号重建，探索从朴素算法到 SpeexDSP 的优化之路</p>
<hr>
<h2 id="第一部分重采样的原理与处理过程">第一部分：重采样的原理与处理过程</h2>
<h3 id="目录">目录</h3>
<ol>
<li><a href="/posts/202604/2026-04-20-%E9%9F%B3%E9%A2%91%E9%87%8D%E9%87%87%E6%A0%B7/#sampling-nature">采样操作的本质：频谱复制</a></li>
<li><a href="/posts/202604/2026-04-20-%E9%9F%B3%E9%A2%91%E9%87%8D%E9%87%87%E6%A0%B7/#reconstruction-condition">重建的条件：无混叠</a></li>
<li><a href="/posts/202604/2026-04-20-%E9%9F%B3%E9%A2%91%E9%87%8D%E9%87%87%E6%A0%B7/#ideal-lpf">理想低通滤波器</a></li>
<li><a href="/posts/202604/2026-04-20-%E9%9F%B3%E9%A2%91%E9%87%8D%E9%87%87%E6%A0%B7/#convolution-reconstruction">卷积重建</a></li>
<li><a href="/posts/202604/2026-04-20-%E9%9F%B3%E9%A2%91%E9%87%8D%E9%87%87%E6%A0%B7/#resampling-process">重采样过程</a></li>
<li><a href="/posts/202604/2026-04-20-%E9%9F%B3%E9%A2%91%E9%87%8D%E9%87%87%E6%A0%B7/#why-sinc">为什么是 sinc 而不是其他函数</a></li>
</ol>
<hr>
<h3 id="11-采样操作的本质频谱复制">1.1 采样操作的本质：频谱复制</h3>
<blockquote>
<p><strong>核心结论 — Whittaker-Shannon 插值公式：</strong></p>
<p>$$x_c(t) = \sum_{n=-\infty}^{\infty} x[n] \cdot \text{sinc}\left(\frac{t - nT}{T}\right)$$</p>
<p>其中 $\text{sinc}(u) = \frac{\sin(\pi u)}{\pi u}$。
<strong>这意味着：任意时刻的连续值 = 离散样本与 sinc 函数的卷积。</strong></p></blockquote>
<h4 id="第一步采样定理的数学表达">第一步：采样定理的数学表达</h4>
<p>设连续信号 $x_c(t)$ 的傅里叶变换为 $X_c(j\Omega)$，带宽限制在 $|\Omega| \leq \Omega_0$（即 bandlimited）。</p>
<p><strong>采样</strong>（采样周期 $T$）等价于将 $x_c(t)$ 乘以冲激串：</p>
<p>$$x_s(t) = x_c(t) \cdot \sum_{n=-\infty}^{\infty} \delta(t - nT)$$</p>
<p>频域上，乘积变成卷积。冲激串的傅里叶变换仍是冲激串（周期为 $\Omega_s = 2\pi/T$）：</p>
<p>$$\sum_{n=-\infty}^{\infty} \delta(t - nT) \xrightarrow{\mathcal{F}} \frac{2\pi}{T}\sum_{k=-\infty}^{\infty} \delta(\Omega - k\Omega_s)$$</p>
<p>卷积结果：<strong>采样信号的频谱是原始频谱的无限复制</strong>：</p>
<p>$$X_s(j\Omega) = \frac{1}{T}\sum_{k=-\infty}^{\infty} X_c\left(j(\Omega - k\Omega_s)\right)$$</p>
<h4 id="直观理解为什么采样--频谱复制">直观理解：为什么采样 = 频谱复制</h4>
<p><strong>时域直觉：采样就是&quot;按快门&quot;</strong></p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">连续信号:  ∿∿∿∿∿∿∿∿∿∿∿∿∿∿∿∿∿∿∿∿  (无限多点)
</span></span><span class="line"><span class="cl">采样后:      ●     ●     ●     ●     (有限个点)
</span></span><span class="line"><span class="cl">           t=0   t=T  t=2T  t=3T</span></span></code></pre></td></tr></table>
</div>
</div>
<p>数学上，这个操作是信号乘以冲激串：$x_s(t) = x_c(t) \cdot \sum_n \delta(t - nT)$。冲激串就像一把&quot;梳子&quot;，只在整数倍 T 的位置&quot;抓住&quot;信号值，其余全部归零。</p>
<p><strong>频域发生了什么？</strong></p>
<p>时域相乘 → 频域卷积（傅里叶变换的基本性质）。而冲激串的傅里叶变换<strong>仍然是冲激串</strong>：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">时域:  ↑     ↑     ↑     ↑     (冲激串，周期 T)
</span></span><span class="line"><span class="cl">       ↓ 傅里叶变换 ↓
</span></span><span class="line"><span class="cl">频域:  ↑     ↑     ↑     ↑     (冲激串，周期 Ωs = 2π/T)</span></span></code></pre></td></tr></table>
</div>
</div>
<p><strong>卷积一个冲激串 = 把函数复制粘贴到每个冲激的位置。</strong> 好比一张照片（原始频谱）被复印无数份，每份放在一个相框里。</p>
<h4 id="复制品重叠与否决定混叠的关键">复制品重叠与否：决定混叠的关键</h4>
<p><strong>采样率足够高</strong>（Ωs &gt; 2Ω₀）—— 复制品之间有间隔，可以用低通滤波器完美提取原始频谱：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">     ╱╲       ╱╲       ╱╲       ╱╲
</span></span><span class="line"><span class="cl">    ╱  ╲     ╱  ╲     ╱  ╲     ╱  ╲
</span></span><span class="line"><span class="cl">   ╱    ╲   ╱    ╲   ╱    ╲   ╱    ╲
</span></span><span class="line"><span class="cl">──╱──────╲─╱──────╲─╱──────╲─╱──────╲──→ Ω
</span></span><span class="line"><span class="cl">              ↑
</span></span><span class="line"><span class="cl">         间隔存在，可完美重建</span></span></code></pre></td></tr></table>
</div>
</div>
<p><strong>采样率不够</strong>（Ωs &lt; 2Ω₀）—— 复制品重叠，原始频谱被破坏，这就是混叠：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">     ╱╲     ╱╲
</span></span><span class="line"><span class="cl">    ╱  ╲╱╲╱  ╲
</span></span><span class="line"><span class="cl">   ╱   ╳╳╳╳   ╲       ← 重叠！原始频谱被破坏
</span></span><span class="line"><span class="cl">──╱──╱──────╲──╲──→ Ω
</span></span><span class="line"><span class="cl">         ↑
</span></span><span class="line"><span class="cl">    高低频信息混在一起无法分离</span></span></code></pre></td></tr></table>
</div>
</div>
<h4 id="数字例子30hz-信号在-40hz-采样率下">数字例子：30Hz 信号在 40Hz 采样率下</h4>
<p>信号最高频率 30Hz，采样率 40Hz，Ωs = 40Hz：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">原始频谱: 0~30Hz 有能量
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">采样后频谱复制 (每 40Hz 一份):
</span></span><span class="line"><span class="cl">  复制品0:  [0 ~ 30Hz]       ← 原始
</span></span><span class="line"><span class="cl">  复制品1:  [40 ~ 70Hz]      ← 40+0 ~ 40+30
</span></span><span class="line"><span class="cl">  复制品-1: [-40 ~ -10Hz]    ← 折叠到正频率变成 10~40Hz</span></span></code></pre></td></tr></table>
</div>
</div>
<p>复制品-1 的负频率部分（-30~-10Hz）折叠到正频率变成 10<del>30Hz，与原始频谱的 10</del>30Hz <strong>重叠</strong>——30Hz 被&quot;误认&quot;为 10Hz，这就是混叠。</p>
<hr>
<h3 id="12-重建的条件无混叠">1.2 重建的条件：无混叠</h3>
<p>若 $\Omega_s \geq 2\Omega_0$（即采样频率高于奈奎斯特频率），则复制频谱<strong>互不重叠</strong>，可以通过一个理想低通滤波器提取出原始频谱。</p>
<p><strong>重建条件（采样定理）：</strong></p>
<p>$$\Omega_s = \frac{2\pi}{T} &gt; 2\Omega_0 \implies T &lt; \frac{\pi}{\Omega_0}$$</p>
<hr>
<h3 id="13-理想低通滤波器">1.3 理想低通滤波器</h3>
<p>要完美重建 $x_c(t)$，需要让 $x_s(t)$ 通过一个<strong>理想砖墙低通滤波器</strong> $H(j\Omega)$：</p>
<p>$$H(j\Omega) = \begin{cases} T, &amp; |\Omega| \leq \Omega_c \ 0, &amp; |\Omega| &gt; \Omega_c \end{cases}$$</p>
<p>其中 $\Omega_c$ 满足 $\Omega_0 \leq \Omega_c \leq \Omega_s - \Omega_0$（通常取 $\Omega_c = \Omega_s/2 = \pi/T$）。</p>
<p>该滤波器的<strong>单位冲激响应</strong>（即逆傅里叶变换）为：</p>
<p>$$h(t) = \mathcal{F}^{-1}{H(j\Omega)} = T \cdot \frac{\sin(\Omega_c t)}{\Omega_c t}$$</p>
<p>令 $\Omega_c = \pi/T$，则：</p>
<p>$$h(t) = T \cdot \frac{\sin(\pi t/T)}{\pi t/T} = \text{sinc}\left(\frac{t}{T}\right)$$</p>
<blockquote>
<p><strong>这就是 sinc 函数！</strong></p></blockquote>
<h4 id="sinc-同时完成重建与低通">sinc 同时完成重建与低通</h4>
<p>sinc 卷积在频域做了一件事：<strong>只保留目标频段，切除其余复制品</strong>。从不同角度看，它有两个名字：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">频域视角：
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">采样信号频谱:    |原始|  复制品  |  复制品  |  复制品
</span></span><span class="line"><span class="cl">                      ↑
</span></span><span class="line"><span class="cl">                 只提取这一块
</span></span><span class="line"><span class="cl">                 = 重建 = 低通滤波 = 去除复制品</span></span></code></pre></td></tr></table>
</div>
</div>
<table>
  <thead>
      <tr>
          <th>视角</th>
          <th>sinc 做了什么</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>重建</strong></td>
          <td>从离散样本恢复出连续信号</td>
      </tr>
      <tr>
          <td><strong>低通 / 防混叠</strong></td>
          <td>去除采样产生的频谱复制品</td>
      </tr>
  </tbody>
</table>
<p><strong>它们是同一个滤波操作的两种叫法。</strong> 重建 = 低通滤波 = sinc 卷积。</p>
<h4 id="sinc-如何确定截止频率">sinc 如何确定截止频率？</h4>
<p>sinc 函数不是&quot;带参数的滤波器&quot;，它<strong>就是</strong>理想低通滤波器的时域形态。截止频率由 sinc 的&quot;胖瘦&quot;决定：</p>
<p>**缩放规则：**sinc 在时域越&quot;窄&quot;，频域的矩形窗越&quot;宽&quot;（时频互逆）。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">sinc(t/T)          →  截止频率 = π/T = Ωs/2
</span></span><span class="line"><span class="cl">sinc(2Wt)          →  截止频率 = W    (角频率)
</span></span><span class="line"><span class="cl">sinc(2πf_c · t)    →  截止频率 = f_c  (Hz)</span></span></code></pre></td></tr></table>
</div>
</div>
<h4 id="重采样中截止频率的选择">重采样中截止频率的选择</h4>
<p>截止频率不是随便选的，它由<strong>目标采样率</strong>决定：<strong>sinc 的截止频率 = 目标采样率的 Nyquist 频率（$F_{\text{target}}/2$）。</strong></p>
<p>**上采样（插值）：**从 $F_s$ 上采样到 $M \cdot F_s$</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">原始 Nyquist = Fs/2
</span></span><span class="line"><span class="cl">新 Nyquist   = M·Fs/2
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">插零后频谱:   |原始|  镜像  |  镜像  |  镜像
</span></span><span class="line"><span class="cl">                     ↑
</span></span><span class="line"><span class="cl">              sinc 滤波器只保留这里
</span></span><span class="line"><span class="cl">              截止 = 新采样率的 Nyquist = M·Fs/2</span></span></code></pre></td></tr></table>
</div>
</div>
<p>**下采样（抽取）：**从 $F_s$ 下采样到 $F_s/M$</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">原始 Nyquist = Fs/2
</span></span><span class="line"><span class="cl">新 Nyquist   = Fs/(2M)
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">原始频谱:     |████████████████|
</span></span><span class="line"><span class="cl">                      ↑
</span></span><span class="line"><span class="cl">              只保留这部分，截止 = 新采样率的 Nyquist = Fs/(2M)
</span></span><span class="line"><span class="cl">              然后再抽取</span></span></code></pre></td></tr></table>
</div>
</div>
<hr>
<h3 id="14-卷积重建">1.4 卷积重建</h3>
<p>滤波器输出为输入与冲激响应的卷积：</p>
<p>$$x_c(t) = x_s(t) * h(t) = \left[x_c(t) \cdot \sum_{n}\delta(t-nT)\right] * \text{sinc}\left(\frac{t}{T}\right)$$</p>
<p>乘以冲激串后得到一系列移位冲激：</p>
<p>$$x_s(t) = \sum_{n=-\infty}^{\infty} x_c(nT) \cdot \delta(t - nT)$$</p>
<p>与 sinc 卷积：</p>
<p>$$x_c(t) = \sum_{n=-\infty}^{\infty} x_c(nT) \cdot \text{sinc}\left(\frac{t - nT}{T}\right)$$</p>
<blockquote>
<p><strong>核心公式的意义：</strong></p>
<ul>
<li>连续信号可以<strong>唯一地</strong>由其样本 ${x_c(nT)}$ 决定</li>
<li>每个样本乘以一个 sinc 函数，所有这些 sinc 函数叠加即重建连续信号</li>
<li>sinc 函数的峰值在 $t = nT$，在其他采样点为零（正交性）</li>
</ul></blockquote>
<hr>
<h3 id="15-重采样过程">1.5 重采样过程</h3>
<p>现在要将信号从采样率 $T_1$<strong>重采样</strong>到 $T_2$。</p>
<p><strong>整数倍上采样</strong>（$T_2 = T_1/N$，增加 $N$ 倍）：</p>
<ol>
<li>在样本间插入 $N-1$ 个零 → 频谱压缩 $N$ 倍</li>
<li>通过低通滤波器（增益 $N$）去除镜像频谱</li>
<li>该滤波器就是 sinc 函数</li>
</ol>
<p><strong>有理数比上/下采样</strong>（$T_2 = \frac{P}{Q}T_1$）：</p>
<ul>
<li>先上采样 $P$ 倍（插值）</li>
<li>再下采样 $Q$ 倍（抽取）</li>
<li>每一步都用 sinc 滤波</li>
</ul>
<p><strong>任意比值重采样：</strong></p>
<p>在目标时刻 $t = mT_2$ 直接计算：</p>
<p>$$x[m] = \sum_{n=-\infty}^{\infty} x_1[n] \cdot \text{sinc}\left(\frac{mT_2 - nT_1}{T_1}\right)$$</p>
<hr>
<h3 id="16-为什么是-sinc-而不是其他函数">1.6 为什么是 sinc 而不是其他函数？</h3>
<table>
  <thead>
      <tr>
          <th>函数</th>
          <th>来源</th>
          <th>性质</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>sinc</strong></td>
          <td>理想低通滤波器的逆 FT</td>
          <td>唯一满足完美重建（奈奎斯特条件）</td>
      </tr>
      <tr>
          <td>其他插值核（线性、立方等）</td>
          <td>窗化的 sinc 近似</td>
          <td>是 sinc 的截断/平滑版本，有误差</td>
      </tr>
  </tbody>
</table>
<blockquote>
<p><strong>sinc 是唯一的：<strong>在采样定理框架下，只有 sinc 能实现</strong>理论上完美</strong>的连续信号重建。任何其他核都是对 sinc 的近似，代价是高频失真（Gibbs 现象 / 振铃效应）。</p></blockquote>
<hr>
<h3 id="17-物理直觉">1.7 物理直觉</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">原始连续信号:  ~~~波形~~~
</span></span><span class="line"><span class="cl">采样点:          ●    ●    ●    ●
</span></span><span class="line"><span class="cl">                  ↖   ↗   ↖   ↗
</span></span><span class="line"><span class="cl">              每个点&#34;抓住&#34;一个sinc,叠加重建波形</span></span></code></pre></td></tr></table>
</div>
</div>
<p>sinc 函数是<strong>理想低通滤波器的时域响应</strong>。卷积即滤波，滤波即重建。</p>
<hr>
<h2 id="第二部分工程实现从朴素到优化">第二部分：工程实现：从朴素到优化</h2>
<p>以 SpeexDSP 重采样器为例，从最原始的 sinc 插值出发，逐步引入优化手段，理解每个优化解决什么问题。</p>
<h3 id="目录-1">目录</h3>
<ol>
<li><a href="/posts/202604/2026-04-20-%E9%9F%B3%E9%A2%91%E9%87%8D%E9%87%87%E6%A0%B7/#naive-implementation">最原始的实现：直接算 sinc</a></li>
<li><a href="/posts/202604/2026-04-20-%E9%9F%B3%E9%A2%91%E9%87%8D%E9%87%87%E6%A0%B7/#lookup-table">第一步优化：查表代替实时计算</a></li>
<li><a href="/posts/202604/2026-04-20-%E9%9F%B3%E9%A2%91%E9%87%8D%E9%87%87%E6%A0%B7/#polyphase">第二步优化：多相滤波</a></li>
<li><a href="/posts/202604/2026-04-20-%E9%9F%B3%E9%A2%91%E9%87%8D%E9%87%87%E6%A0%B7/#speex-hybrid">第三步优化：Speex 的混合策略</a></li>
<li><a href="/posts/202604/2026-04-20-%E9%9F%B3%E9%A2%91%E9%87%8D%E9%87%87%E6%A0%B7/#accumulator">第四步优化：分数位置推进</a></li>
<li><a href="/posts/202604/2026-04-20-%E9%9F%B3%E9%A2%91%E9%87%8D%E9%87%87%E6%A0%B7/#fixed-point">第五步优化：定点运算</a></li>
<li><a href="/posts/202604/2026-04-20-%E9%9F%B3%E9%A2%91%E9%87%8D%E9%87%87%E6%A0%B7/#summary">全景总结</a></li>
</ol>
<hr>
<h3 id="21-最原始的实现直接算-sinc">2.1 最原始的实现：直接算 sinc</h3>
<p>根据 Whittaker-Shannon 公式，要计算任意时刻 $t$ 的信号值：</p>
<p>$$x(t) = \sum_{n} x[n] \cdot \text{sinc}\left(\frac{t - nT}{T}\right)$$</p>
<p>最直觉的实现：<strong>对每个输出样本，遍历所有相关输入样本，实时计算 sinc 值。</strong></p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">numpy</span> <span class="k">as</span> <span class="nn">np</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">naive_resample</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">fs_in</span><span class="p">,</span> <span class="n">fs_out</span><span class="p">,</span> <span class="n">num_samples_out</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;最原始的重采样：直接算 sinc&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">output</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">zeros</span><span class="p">(</span><span class="n">num_samples_out</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">m</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">num_samples_out</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="n">t</span> <span class="o">=</span> <span class="n">m</span> <span class="o">/</span> <span class="n">fs_out</span>
</span></span><span class="line"><span class="cl">        <span class="n">pos</span> <span class="o">=</span> <span class="n">t</span> <span class="o">*</span> <span class="n">fs_in</span>  <span class="c1"># 在输入序列中的&#34;虚拟位置&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">n_center</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">np</span><span class="o">.</span><span class="n">floor</span><span class="p">(</span><span class="n">pos</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">        <span class="n">half_len</span> <span class="o">=</span> <span class="mi">16</span>  <span class="c1"># 只取 ±16 个点（截断 sinc）</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">s</span> <span class="o">=</span> <span class="mf">0.0</span>
</span></span><span class="line"><span class="cl">        <span class="k">for</span> <span class="n">n</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">n_center</span> <span class="o">-</span> <span class="n">half_len</span><span class="p">,</span> <span class="n">n_center</span> <span class="o">+</span> <span class="n">half_len</span> <span class="o">+</span> <span class="mi">1</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="mi">0</span> <span class="o">&lt;=</span> <span class="n">n</span> <span class="o">&lt;</span> <span class="nb">len</span><span class="p">(</span><span class="n">x</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">                <span class="n">sinc_val</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">sinc</span><span class="p">(</span><span class="n">pos</span> <span class="o">-</span> <span class="n">n</span><span class="p">)</span>  <span class="c1"># sin(πx)/(πx)</span>
</span></span><span class="line"><span class="cl">                <span class="n">s</span> <span class="o">+=</span> <span class="n">x</span><span class="p">[</span><span class="n">n</span><span class="p">]</span> <span class="o">*</span> <span class="n">sinc_val</span>
</span></span><span class="line"><span class="cl">        <span class="n">output</span><span class="p">[</span><span class="n">m</span><span class="p">]</span> <span class="o">=</span> <span class="n">s</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">output</span></span></span></code></pre></td></tr></table>
</div>
</div>
<h4 id="问题计算量太大">问题：计算量太大</h4>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">对每个输出样本:
</span></span><span class="line"><span class="cl">  → 遍历 2 × half_len 个输入样本
</span></span><span class="line"><span class="cl">  → 每个都要调用 np.sinc()（内部算 sin + 除法）
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">总计算量: num_out × 2 × half_len × (sin + 除法)
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">示例: 10 秒 48kHz 音频，half_len=32
</span></span><span class="line"><span class="cl">  480000 × 64 = 3072 万次 sin() 调用</span></span></code></pre></td></tr></table>
</div>
</div>
<blockquote>
<p><strong>核心瓶颈：</strong> sin() 是很贵的运算，每次都要算。</p></blockquote>
<hr>
<h3 id="22-第一步优化查表代替实时计算">2.2 第一步优化：查表代替实时计算</h3>
<p>sinc 函数是固定的，可以<strong>预先计算好一张表</strong>，运行时直接查表。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">优化前:  每次算 sin(πx)/(πx)     → 贵（三角函数 + 除法）
</span></span><span class="line"><span class="cl">优化后:  直接查表 sinc_table[i]   → 快（一次内存访问）</span></span></code></pre></td></tr></table>
</div>
</div>
<p><strong>新问题：精度不足。</strong> 输出位置通常是分数（比如 <code>pos = 3.7</code>），而 sinc 表只有固定间隔的值。需要用<strong>插值</strong>逼近任意分数位置的 sinc 值。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">目标: sinc(3.7)
</span></span><span class="line"><span class="cl">表里有: sinc(3.50), sinc(3.75)   ← 间隔 1/4
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">选项A: 线性插值   → sinc(3.7) ≈ 0.4×sinc(3.50) + 0.6×sinc(3.75)
</span></span><span class="line"><span class="cl">选项B: 三次插值   → 用相邻 4 个点做 cubic 插值（更准）
</span></span><span class="line"><span class="cl">选项C: 存更大的表  → oversample=16 或 32，直接取最近的（但费内存）</span></span></code></pre></td></tr></table>
</div>
</div>
<p>Speex 的 <strong>Interpolated 模式</strong>采用选项 B：存一张较稀疏的 sinc 表，用 Cubic 插值逼近任意位置。</p>
<hr>
<h3 id="23-第二步优化多相滤波--避免重复计算">2.3 第二步优化：多相滤波 — 避免重复计算</h3>
<p>上面的方法对每个输出样本独立做一次卷积。但如果输入输出采样率是<strong>整数比</strong>关系，可以利用<strong>周期性</strong>复用计算。</p>
<h4 id="关键观察">关键观察</h4>
<p>上采样 4 倍时，sinc 系数只有 4 种不同的偏移：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">输出样本 0: sinc 偏移 0/4 → 系数 [a0, a1, a2, a3]
</span></span><span class="line"><span class="cl">输出样本 1: sinc 偏移 1/4 → 系数 [b0, b1, b2, b3]
</span></span><span class="line"><span class="cl">输出样本 2: sinc 偏移 2/4 → 系数 [c0, c1, c2, c3]
</span></span><span class="line"><span class="cl">输出样本 3: sinc 偏移 3/4 → 系数 [d0, d1, d2, d3]
</span></span><span class="line"><span class="cl">输出样本 4: sinc 偏移 4/4 = 0/4 → 系数 [a0, a1, a2, a3]  ← 重复！</span></span></code></pre></td></tr></table>
</div>
</div>
<p><strong>只有 4 组不同的系数</strong>，不需要为每个输出样本重新查表。</p>
<h4 id="多相滤波器结构">多相滤波器结构</h4>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">输入 x[n] ───┬──→ [滤波器组 a] ──→ 输出 t=0/4
</span></span><span class="line"><span class="cl">             ├──→ [滤波器组 b] ──→ 输出 t=1/4
</span></span><span class="line"><span class="cl">             ├──→ [滤波器组 c] ──→ 输出 t=2/4
</span></span><span class="line"><span class="cl">             └──→ [滤波器组 d] ──→ 输出 t=3/4
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">4 组滤波器，每组只对同一个输入段计算一次</span></span></code></pre></td></tr></table>
</div>
</div>
<p><strong>计算量从 N×M 降到 N×M/P</strong>（P = 上采样倍数）。</p>
<hr>
<h3 id="24-第三步优化speex-的混合策略">2.4 第三步优化：Speex 的混合策略</h3>
<p>SpeexDSP 面对的是<strong>任意比例</strong>重采样（不是整数比），不能直接用纯多相。它的策略是根据参数自动选择查表方式。</p>
<h4 id="策略一direct-sinc-tableden_rate-较小时">策略一：Direct Sinc Table（den_rate 较小时）</h4>
<p>当分母 <code>den_rate</code> 不太大时，直接存 <code>filt_len × den_rate</code> 大小的 sinc 表，精确查找。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">den_rate = 160 (分母)
</span></span><span class="line"><span class="cl">filt_len = 64  (滤波器长度)
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">sinc 表大小 = 64 × 160 = 10240 个系数
</span></span><span class="line"><span class="cl">内存 ≈ 10240 × 2 bytes = 20KB
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">查找: sinc_table[j * den_rate + samp_frac_num]
</span></span><span class="line"><span class="cl">      ↑                ↑
</span></span><span class="line"><span class="cl">      第 j 个抽头      当前分数位置</span></span></code></pre></td></tr></table>
</div>
</div>
<p>**优点：**直接查表，零插值误差
**缺点：**内存随 den_rate 线性增长</p>
<h4 id="策略二interpolated-sinc-tableden_rate-较大时">策略二：Interpolated Sinc Table（den_rate 较大时）</h4>
<p>**核心矛盾：**Direct 模式要存 <code>filt_len × den_rate</code> 个系数。当 <code>den_rate = 48000</code> 时：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">filt_len=64, den_rate=48000 → 64 × 48000 = 307 万个系数 → 6MB 内存</span></span></code></pre></td></tr></table>
</div>
</div>
<p>太大了。能不能存一个小表，用插值来&quot;猜&quot;中间的值？</p>
<h4 id="sinc-表存的到底是什么">sinc 表存的到底是什么？</h4>
<p>每个输出样本需要的 sinc 系数，就是 sinc 函数在<strong>不同偏移位置</strong>的采样值。</p>
<p><strong>Direct 模式：为每个可能的分数位置都存一份</strong></p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">输出位置 0.000:  用 sinc 在 ..., -2.000, -1.000, 0.000, 1.000, ... 处的值
</span></span><span class="line"><span class="cl">输出位置 0.001:  用 sinc 在 ..., -1.999, -0.999, 0.001, 1.001, ... 处的值
</span></span><span class="line"><span class="cl">输出位置 0.002:  用 sinc 在 ..., -1.998, -0.998, 0.002, 1.002, ... 处的值
</span></span><span class="line"><span class="cl">...
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">den_rate=1000 时，需要为 1000 种不同的偏移各存 filt_len 个系数
</span></span><span class="line"><span class="cl">表大小 = filt_len × 1000</span></span></code></pre></td></tr></table>
</div>
</div>
<p><strong>Interpolated 模式：只存 oversample 份，用插值补中间</strong></p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">oversample=16 时，只存 16 种偏移的系数：
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">偏移 0/16:  sinc 在 0.000, 1.000, 2.000, ... 处的值
</span></span><span class="line"><span class="cl">偏移 1/16:  sinc 在 0.0625, 1.0625, 2.0625, ... 处的值
</span></span><span class="line"><span class="cl">偏移 2/16:  sinc 在 0.125, 1.125, 2.125, ... 处的值
</span></span><span class="line"><span class="cl">...
</span></span><span class="line"><span class="cl">偏移 15/16: sinc 在 0.9375, 1.9375, 2.9375, ... 处的值
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">表大小 = filt_len × 16</span></span></code></pre></td></tr></table>
</div>
</div>
<h4 id="完整的查找过程数字示例">完整的查找过程（数字示例）</h4>
<p>设定：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">信号: 48kHz → 44.1kHz 重采样
</span></span><span class="line"><span class="cl">filt_len = 8  (滤波器长度，取 8 个输入样本)
</span></span><span class="line"><span class="cl">oversample = 16
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">sinc 表大小 = 8 × 16 + 8 = 136 个系数</span></span></code></pre></td></tr></table>
</div>
</div>
<p><strong>第一步：确定在输入序列中的位置</strong></p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">输入位置 pos = m × (fs_in / fs_out) = 100 × (48000/44100) = 108.8480...
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">整数部分: n_center = 108
</span></span><span class="line"><span class="cl">小数部分: frac_input = 0.8480...</span></span></code></pre></td></tr></table>
</div>
</div>
<p><strong>第二步：将小数部分映射到 oversample 网格</strong></p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">oversample_index = frac_input × oversample = 0.8480 × 16 = 13.568
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">整数偏移: offset = 13        ← 在 sinc 表中的基准位置
</span></span><span class="line"><span class="cl">小数偏移: frac = 0.568       ← cubic 插值的输入</span></span></code></pre></td></tr></table>
</div>
</div>
<p><strong>第三步：对每个滤波器抽头，取 4 个相邻表值做 cubic 插值</strong></p>
<p><strong>第四步：所有抽头加权求和</strong></p>
<p>$$\mathrm{output}[m] = \sum_{j=0}^{7} \mathrm{sinc_value}[j] \times \mathrm{input}\big[n_{\mathrm{center}} + k_j\big]$$</p>
<h4 id="cubic-插值系数">Cubic 插值系数</h4>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="n">interp</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="o">-</span><span class="mf">0.16667</span><span class="o">*</span><span class="n">frac</span> <span class="o">+</span> <span class="mf">0.16667</span><span class="o">*</span><span class="n">frac</span><span class="o">*</span><span class="n">frac</span><span class="o">*</span><span class="n">frac</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="n">interp</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span>  <span class="n">frac</span> <span class="o">+</span> <span class="mf">0.5</span><span class="o">*</span><span class="n">frac</span><span class="o">*</span><span class="n">frac</span> <span class="o">-</span> <span class="mf">0.5</span><span class="o">*</span><span class="n">frac</span><span class="o">*</span><span class="n">frac</span><span class="o">*</span><span class="n">frac</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="n">interp</span><span class="p">[</span><span class="mi">3</span><span class="p">]</span> <span class="o">=</span> <span class="o">-</span><span class="mf">0.33333</span><span class="o">*</span><span class="n">frac</span> <span class="o">+</span> <span class="mf">0.5</span><span class="o">*</span><span class="n">frac</span><span class="o">*</span><span class="n">frac</span> <span class="o">-</span> <span class="mf">0.16667</span><span class="o">*</span><span class="n">frac</span><span class="o">*</span><span class="n">frac</span><span class="o">*</span><span class="n">frac</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="n">interp</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="o">=</span>  <span class="mf">1.f</span> <span class="o">-</span> <span class="n">interp</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">-</span> <span class="n">interp</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">-</span> <span class="n">interp</span><span class="p">[</span><span class="mi">3</span><span class="p">];</span></span></span></code></pre></td></tr></table>
</div>
</div>
<p>这是对 sinc 函数的 <strong>MMSE（最小均方误差）最优三次逼近</strong>。</p>
<p><strong>为什么用 Cubic 而不是线性？</strong></p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">线性插值:  在两个点之间拉直线 → 对 sinc 这种振荡形状误差大
</span></span><span class="line"><span class="cl">Cubic 插值: 用三次曲线拟合 → 高频失真更小，接近直接查表精度</span></span></code></pre></td></tr></table>
</div>
</div>
<h4 id="对比省了多少内存">对比：省了多少内存？</h4>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">场景: 48kHz → 44.1kHz, filt_len=64
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Direct (den_rate=48000):
</span></span><span class="line"><span class="cl">  表大小 = 64 × 48000 = 3,072,000 个系数
</span></span><span class="line"><span class="cl">  内存 = 3,072,000 × 2 bytes = 6 MB
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Interpolated (oversample=16):
</span></span><span class="line"><span class="cl">  表大小 = 64 × 16 + 8 = 1,032 个系数
</span></span><span class="line"><span class="cl">  内存 = 1,032 × 2 bytes = 2 KB
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">节省: 6MB → 2KB，缩小 3000 倍
</span></span><span class="line"><span class="cl">代价: cubic 插值引入微小误差（~0.01dB，人耳不可感知）</span></span></code></pre></td></tr></table>
</div>
</div>
<hr>
<h3 id="25-第四步优化分数位置推进--避免除法">2.5 第四步优化：分数位置推进 — 避免除法</h3>
<p>每个输出样本的分数位置怎么算？朴素做法：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// 朴素：每个样本都做一次除法
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="n">frac_pos</span> <span class="o">=</span> <span class="p">(</span><span class="n">m</span> <span class="o">*</span> <span class="n">fs_in</span><span class="p">)</span> <span class="o">%</span> <span class="n">fs_out</span> <span class="o">/</span> <span class="n">fs_out</span><span class="p">;</span></span></span></code></pre></td></tr></table>
</div>
</div>
<p><strong>除法很贵。</strong> Speex 用<strong>累加器</strong>避免除法：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// 预计算一次（整数除法，只在初始化时执行）
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="n">int_advance</span>  <span class="o">=</span> <span class="n">num_rate</span> <span class="o">/</span> <span class="n">den_rate</span><span class="p">;</span>     <span class="c1">// 整数部分
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="n">frac_advance</span> <span class="o">=</span> <span class="n">num_rate</span> <span class="o">%</span> <span class="n">den_rate</span><span class="p">;</span>     <span class="c1">// 分数累加步长
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="c1">// 每个输出样本（只有整数加法和比较）
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="n">last_sample</span>  <span class="o">+=</span> <span class="n">int_advance</span><span class="p">;</span>            <span class="c1">// 整数步进
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="n">samp_frac_num</span> <span class="o">+=</span> <span class="n">frac_advance</span><span class="p">;</span>          <span class="c1">// 分数累加
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="k">if</span> <span class="p">(</span><span class="n">samp_frac_num</span> <span class="o">&gt;=</span> <span class="n">den_rate</span><span class="p">)</span> <span class="p">{</span>        <span class="c1">// 进位
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>    <span class="n">samp_frac_num</span> <span class="o">-=</span> <span class="n">den_rate</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">last_sample</span><span class="o">++</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></td></tr></table>
</div>
</div>
<p><strong>整个过程只有整数加法和比较，零除法，零浮点运算。</strong></p>
<hr>
<h3 id="26-第五步优化定点运算">2.6 第五步优化：定点运算</h3>
<p>浮点运算在某些平台（嵌入式 DSP 芯片）很慢。Speex 支持 16-bit 定点模式：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// 浮点版
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="n">sum</span> <span class="o">+=</span> <span class="n">sinc_val</span> <span class="o">*</span> <span class="n">input_val</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// 定点版 (Q15 格式: 1 位符号 + 15 位小数)
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="n">sum</span> <span class="o">+=</span> <span class="nf">MULT16_16</span><span class="p">(</span><span class="n">sinc_val_q15</span><span class="p">,</span> <span class="n">input_val_q15</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="c1">// 其中 MULT16_16 = (int32_t)a * (int32_t)b &gt;&gt; 15
</span></span></span></code></pre></td></tr></table>
</div>
</div>
<p><strong>精度换速度：</strong> 16-bit 定点的阻带衰减约 90dB，对语音应用足够。</p>
<hr>
<h3 id="27-全景总结">2.7 全景总结</h3>
<h4 id="优化路径">优化路径</h4>
<table>
  <thead>
      <tr>
          <th>步骤</th>
          <th>优化内容</th>
          <th>解决的问题</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>0</td>
          <td>直接算 sinc</td>
          <td>瓶颈：sin() 太慢</td>
      </tr>
      <tr>
          <td>1</td>
          <td>sinc 查表 + 插值</td>
          <td>瓶颈：每个样本独立查表，重复计算</td>
      </tr>
      <tr>
          <td>2</td>
          <td>多相滤波（整数比时复用系数）</td>
          <td>瓶颈：任意比怎么办</td>
      </tr>
      <tr>
          <td>3</td>
          <td>Speex 混合策略</td>
          <td>瓶颈：每个样本做除法求分数位置</td>
      </tr>
      <tr>
          <td>4</td>
          <td>累加器推进（整数加法代替除法）</td>
          <td>瓶颈：浮点运算慢</td>
      </tr>
      <tr>
          <td>5</td>
          <td>定点运算（16-bit 定点乘加）</td>
          <td>最终：纯乘加，无除法，无 sin()，SIMD 友好</td>
      </tr>
  </tbody>
</table>
<h4 id="各步骤对比">各步骤对比</h4>
<table>
  <thead>
      <tr>
          <th>优化步骤</th>
          <th>解决的问题</th>
          <th>核心手段</th>
          <th>代价</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>查表</td>
          <td>sin() 太贵</td>
          <td>预计算 + 内存访问</td>
          <td>需要插值弥补精度</td>
      </tr>
      <tr>
          <td>Cubic 插值</td>
          <td>sinc 表太大</td>
          <td>小表 + 三次插值逼近</td>
          <td>微小的高频误差</td>
      </tr>
      <tr>
          <td>多相/累加器</td>
          <td>重复计算 + 除法</td>
          <td>周期性复用 + 整数累加</td>
          <td>仅适用于整数比</td>
      </tr>
      <tr>
          <td>混合策略</td>
          <td>任意比 + 内存/精度权衡</td>
          <td>自适应选择 Direct/Interpolated</td>
          <td>代码复杂度</td>
      </tr>
      <tr>
          <td>定点</td>
          <td>浮点太慢</td>
          <td>Q15 定点乘加</td>
          <td>精度有限（~90dB）</td>
      </tr>
  </tbody>
</table>
<h4 id="speex-质量等级与滤波器参数">Speex 质量等级与滤波器参数</h4>
<table>
  <thead>
      <tr>
          <th>等级</th>
          <th>filt_len</th>
          <th>Oversample</th>
          <th>阻带衰减</th>
          <th>适用场景</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Q0</td>
          <td>8</td>
          <td>4</td>
          <td>~60dB</td>
          <td>低功耗实时</td>
      </tr>
      <tr>
          <td>Q4</td>
          <td>64</td>
          <td>8</td>
          <td>~80dB</td>
          <td>一般语音</td>
      </tr>
      <tr>
          <td>Q5</td>
          <td>80</td>
          <td>16</td>
          <td>~100dB</td>
          <td>高质量语音</td>
      </tr>
      <tr>
          <td>Q10</td>
          <td>256</td>
          <td>32</td>
          <td>~100dB+</td>
          <td>音乐 / 离线处理</td>
      </tr>
  </tbody>
</table>
<p>filt_len 越长 → sinc 截断越接近理想 → 阻带衰减越好 → 计算量越大。</p>
<hr>
<h2 id="参考">参考</h2>
<ul>
<li><a href="https://ccrma.stanford.edu/~jos/resample/">Stanford CCRMA - Digital Audio Resampling</a></li>
<li>SpeexDSP 源码：<code>speex/libspeexdsp/resample.c</code></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Claude Code 是如何工作的</title>
      <link>https://lyapple2008.github.io/posts/202604/2026-04-02-claude-code%E6%98%AF%E5%A6%82%E4%BD%95%E5%B7%A5%E4%BD%9C%E7%9A%84/</link>
      <pubDate>Thu, 02 Apr 2026 09:58:19 +0800</pubDate>
      <guid>https://lyapple2008.github.io/posts/202604/2026-04-02-claude-code%E6%98%AF%E5%A6%82%E4%BD%95%E5%B7%A5%E4%BD%9C%E7%9A%84/</guid>
      <description>&lt;h1 id=&#34;从claude-code学习agent开发&#34;&gt;从Claude Code学习Agent开发&lt;/h1&gt;
&lt;p&gt;最近一直在重度使用 Claude Code，也一直被各种新名词轰炸：vibe coding、spec coding、agent、openclaw、harness 等等。刚好看到一个介绍 Claude Code 工程原理的教程，对Agent Engneer有了一些认识，记录总结一下。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h1 id="从claude-code学习agent开发">从Claude Code学习Agent开发</h1>
<p>最近一直在重度使用 Claude Code，也一直被各种新名词轰炸：vibe coding、spec coding、agent、openclaw、harness 等等。刚好看到一个介绍 Claude Code 工程原理的教程，对Agent Engneer有了一些认识，记录总结一下。</p>
<blockquote>
<p>教程地址：https://learn.shareai.run/en/<br>
项目仓库：https://github.com/shareAI-lab/learn-claude-code</p></blockquote>
<h2 id="课程介绍">课程介绍</h2>
<p>该课程包含12个递进式课程，从简单循环到隔离化的自治执行，每个课程添加一个 harness 机制，每个机制有一句格言，非常推荐大家去学习：</p>
<blockquote>
<p><strong>s01</strong>   <em>One loop &amp; Bash is all you need</em> — 一个工具 + 一个循环 = 一个 Agent</p>
<p><strong>s02</strong>   <em>加一个工具, 只加一个 handler</em> — 循环不用动, 新工具注册进 dispatch map 就行</p>
<p><strong>s03</strong>   <em>没有计划的 agent 走哪算哪</em> — 先列步骤再动手, 完成率翻倍</p>
<p><strong>s04</strong>   <em>大任务拆小, 每个小任务干净的上下文</em> — Subagent 用独立 messages[], 不污染主对话</p>
<p><strong>s05</strong>   <em>用到什么知识, 临时加载什么知识</em> — 通过 tool_result 注入, 不塞 system prompt</p>
<p><strong>s06</strong>   <em>上下文总会满, 要有办法腾地方</em> — 三层压缩策略, 换来无限会话</p>
<p><strong>s07</strong>   <em>大目标要拆成小任务, 排好序, 记在磁盘上</em> — 文件持久化的任务图, 为多 agent 协作打基础</p>
<p><strong>s08</strong>   <em>慢操作丢后台, agent 继续想下一步</em> — 后台线程跑命令, 完成后注入通知</p>
<p><strong>s09</strong>   <em>任务太大一个人干不完, 要能分给队友</em> — 持久化队友 + 异步邮箱</p>
<p><strong>s10</strong>   <em>队友之间要有统一的沟通规矩</em> — 一个 request-response 模式驱动所有协商</p>
<p><strong>s11</strong>   <em>队友自己看看板, 有活就认领</em> — 不需要领导逐个分配, 自组织</p>
<p><strong>s12</strong>   <em>各干各的目录, 互不干扰</em> — 任务管目标, worktree 管目录, 按 ID 绑定</p></blockquote>
<h2 id="什么是agentharness">什么是Agent/Harness</h2>
<h2 id="如何开发适用于自己工作流的agentharness">如何开发适用于自己工作流的Agent/Harness</h2>
<ol>
<li>流程是怎样的</li>
<li>需要哪些特定的知识</li>
<li>其中需要用到哪些工具，这些工具适用的场景和使用说明</li>
</ol>
<h2 id="总结">总结</h2>
<ol>
<li><strong>设计参考人类开发过程</strong> — 每个特性都是实际工作流会用的</li>
<li><strong>节约 Token，按需加载</strong> — skill 的两层 load 机制 + context 的压缩机制</li>
<li><strong>新名词殊途同归</strong> — prompt/harness/skill，皆是给 Agent 提供知识背景</li>
<li><strong>典范案例</strong> — Claude Code 是编码工作流与 AI 大模型结合的典范</li>
</ol>
<h3 id="claude-code-与-ai-模型的关系">Claude Code 与 AI 模型的关系</h3>
<ul>
<li><strong>Claude 模型</strong>：负责「思考」和「推理」，理解代码、分析任务、决定下一步</li>
<li><strong>Claude Code (agentic harness)</strong>：负责「赋能」和「执行」，提供工具接口、上下文管理、终端环境，让推理转化为实际操作</li>
</ul>
<blockquote>
<p>关系模式：Claude Code 相当于「外壳/操作系统」，Claude 模型是其中的「智能核心」。模型提出计划，Claude Code 通过工具和环境落实这些计划。</p></blockquote>
<hr>
<p>Agent 开发不过是将自身能力的一次实体化。如同那句话所说：大模型是放大器，自身能力决定了 Agent 的上限。这个时代对人的要求，反而更高了。只有人本身把工作流想清楚了，才有可以设计出高效合理的Agent流程。</p>
<p>当整个系统的短板是人的时候，再也没有借口可找。</p>
<p>参考：</p>
<ol>
<li><a href="https://github.com/shareAI-lab/learn-claude-code">github: learn-claude-code</a></li>
<li><a href="https://x.com/HiTw93/status/2032091246588518683">你不知道的 Claude Code：架构、治理与工程实践</a></li>
<li><a href="https://x.com/HiTw93/status/2034627967926825175">你不知道的 Agent：原理、架构与工程实践</a></li>
</ol>
]]></content:encoded>
    </item>
    <item>
      <title>一个程序员为了不看广告有多拼</title>
      <link>https://lyapple2008.github.io/posts/202603/2026-03-28-%E4%B8%80%E4%B8%AA%E7%A8%8B%E5%BA%8F%E5%91%98%E4%B8%BA%E4%BA%86%E4%B8%8D%E7%9C%8B%E5%B9%BF%E5%91%8A%E6%9C%89%E5%A4%9A%E6%8B%BC/</link>
      <pubDate>Sat, 28 Mar 2026 14:17:17 +0800</pubDate>
      <guid>https://lyapple2008.github.io/posts/202603/2026-03-28-%E4%B8%80%E4%B8%AA%E7%A8%8B%E5%BA%8F%E5%91%98%E4%B8%BA%E4%BA%86%E4%B8%8D%E7%9C%8B%E5%B9%BF%E5%91%8A%E6%9C%89%E5%A4%9A%E6%8B%BC/</guid>
      <description>&lt;h2 id=&#34;一缘起&#34;&gt;一、缘起&lt;/h2&gt;
&lt;p&gt;春节期间，为了打发时间，我迷上了一款拼图小游戏。游戏规则很简单——给你一张漂亮的图片，打乱成若干块，然后在时间限制内把它拼好。听起来很休闲对不对？&lt;strong&gt;too young too simple&lt;/strong&gt;。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h2 id="一缘起">一、缘起</h2>
<p>春节期间，为了打发时间，我迷上了一款拼图小游戏。游戏规则很简单——给你一张漂亮的图片，打乱成若干块，然后在时间限制内把它拼好。听起来很休闲对不对？<strong>too young too simple</strong>。</p>
<img src="/images/2026-03/微信拼图小游戏.PNG" height="400">
<p>问题在于：<strong>每次超时都要看30秒视频广告</strong>。人菜瘾又大的我，经常需要看广告才能过关。</p>
<p>终于在某次看了第17遍&quot;是兄弟就来砍我&quot;之后，我一拍桌子——<strong>不行，我要搞点事情</strong>。</p>
<blockquote>
<p>📢 友情提示：本文不涉及游戏外挂或修改游戏本身，只是研究如何用程序自动操作已知的拼图图片。</p></blockquote>
<h2 id="二动手干">二、动手干</h2>
<h3 id="21-拼图还原问题">2.1 拼图还原问题</h3>
<p><strong>拼图还原问题（Jigsaw Puzzle Reassembly Problem）</strong> 是计算机视觉和图像处理领域的一个重要研究课题。简单来说，就是给定一幅被打碎的图像，如何让计算机自动识别各个碎片之间的关系，并将它们重新拼合成完整的原始图像。</p>
<h4 id="问题分类">问题分类</h4>
<p>根据不同的碎片特征，拼图还原问题可以分为以下几类：</p>
<table>
  <thead>
      <tr>
          <th>类型</th>
          <th>碎片特征</th>
          <th>难度</th>
          <th>典型场景</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>Type 1</strong></td>
          <td>矩形碎片，排列规则，<strong>无旋转</strong></td>
          <td>⭐⭐</td>
          <td>网格切割的图片</td>
      </tr>
      <tr>
          <td><strong>Type 2</strong></td>
          <td>矩形碎片，排列规则，<strong>有旋转</strong></td>
          <td>⭐⭐⭐</td>
          <td>可旋转的拼图游戏</td>
      </tr>
      <tr>
          <td><strong>Type 3</strong></td>
          <td>传统锯齿边缘，<strong>无旋转</strong></td>
          <td>⭐⭐⭐⭐</td>
          <td>传统拼图</td>
      </tr>
      <tr>
          <td><strong>Type 4</strong></td>
          <td>传统锯齿边缘，<strong>有旋转</strong></td>
          <td>⭐⭐⭐⭐⭐</td>
          <td>复杂拼图、考古文物修复</td>
      </tr>
  </tbody>
</table>
<h4 id="本文面对的问题">本文面对的问题</h4>
<p>回顾本文面对的拼图问题：</p>
<ul>
<li>✅ <strong>矩形分块</strong>：每块都是规整的矩形，没有传统拼图的锯齿边缘</li>
<li>✅ <strong>网格排列</strong>：6×6、7×7、8×8 三种规格，整齐排列</li>
<li>✅ <strong>无旋转</strong>：所有碎片都是正向朝上，不涉及旋转匹配</li>
<li>❌ <strong>无参考图</strong>：原图被打碎后完全不可见，需要通过碎片自身特征推断位置</li>
</ul>
<p>这属于上表中的 <strong>Type 1</strong> 类型——矩形碎片、无旋转的规则拼图。虽然是相对简单的一类，但结合&quot;无参考图&quot;这一约束条件，仍然需要借助图像边缘匹配等计算机视觉技术来解决。</p>
<h3 id="22-实施方案">2.2 实施方案</h3>
<h4 id="221-提取拼图区域">2.2.1 提取拼图区域</h4>
<p><strong>输入</strong>：含 UI 的原始截图
<strong>输出</strong>：纯拼图区域图像 + bbox</p>
<p><strong>方法</strong>：基于背景颜色分割</p>
<p>从图像四周边缘采样获取背景色，对每个像素计算 RGB 欧氏距离。距离 &gt; 35 的像素标记为非背景，经形态学开闭运算去噪填洞后，查找最大连通域作为拼图区域。</p>
<hr>
<h4 id="222-检测拼图块边界">2.2.2 检测拼图块边界</h4>
<p><strong>输入</strong>：纯拼图区域图像
<strong>输出</strong>：M×N 个等大小 patch</p>
<p><strong>方法</strong>：基于轴向纹理信号检测分割线</p>
<p>灰度化后沿行/列方向计算标准差。拼图块内部纹理有变化→信号高，块间缝隙处纹理一致→信号低。通过搜索信号局部最小值定位每条分割线，最后归一化所有 patch 为统一尺寸。</p>
<hr>
<h4 id="223-遗传算法拼图">2.2.3 遗传算法拼图</h4>
<p><strong>输入</strong>：打乱的 M×N 个 patch
<strong>输出</strong>：还原后的 grid + 重建图像</p>
<p><strong>方法</strong>：进化算法最小化相邻块边缘差异</p>
<p>种群 200 个随机排列个体，以相邻块边缘像素差（MSE/Median/Percentile/Huber）的总和作为适应度。轮盘赌选择父代、交叉产生子代、精英保留 Top2，连续 10 代无提升则早停。</p>
<p><img alt="拼图还原示例" loading="lazy" src="/images/2026-03/jigsaw-recontruct-example.png"></p>
<h2 id="三最终效果展示">三、最终效果展示</h2>
<iframe src="//player.bilibili.com/player.html?bvid=BV1E2XDBBEN1" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true" width="100%" height="500"></iframe>
<h2 id="四总结">四、总结</h2>
<p>这个项目大概花了我<strong>四周的周末时间</strong>，从最初的&quot;看广告好烦&quot;到最后的&quot;这玩意儿居然还真能跑&quot;，过程颇为曲折。虽然全程vibe coding，但是大方向还是需要自己去研究清楚后，再给AI讲清楚后，才能有个好的结果。</p>
<p>目前这个效果离我最初设想的效果，一键启动通关，还差很远，还存在不少问题，不过也算基本达到当时的目的，用了这个后，玩这个拼图游戏再也没有看过广告了。也许后面有时间，再看看没有其它能够实现一键通关的方法，比如RL之类的，现在就暂时到这里吧，毕竟只是一个副本任务。</p>
<p>项目地址：<a href="https://github.com/lyapple2008/JigsawPuzzleReconstruction">JigsawPuzzleReconstruction</a></p>
<hr>
<p><strong>最后</strong>，如果你也被游戏广告折磨得不行，不妨试试这个思路——<strong>有时候最好的外挂，是用自己的技术绕过广告</strong>（而不是去黑游戏服务器）。</p>
<p>祝大家拼图愉快，<strong>永不看广告</strong>！</p>
]]></content:encoded>
    </item>
    <item>
      <title>基于iOS系统接口实现双语字幕App</title>
      <link>https://lyapple2008.github.io/posts/202603/2026-03-09-%E5%9F%BA%E4%BA%8Eios%E7%B3%BB%E7%BB%9F%E6%8E%A5%E5%8F%A3%E5%AE%9E%E7%8E%B0%E5%8F%8C%E8%AF%AD%E5%AD%97%E5%B9%95app/</link>
      <pubDate>Mon, 09 Mar 2026 18:24:47 +0800</pubDate>
      <guid>https://lyapple2008.github.io/posts/202603/2026-03-09-%E5%9F%BA%E4%BA%8Eios%E7%B3%BB%E7%BB%9F%E6%8E%A5%E5%8F%A3%E5%AE%9E%E7%8E%B0%E5%8F%8C%E8%AF%AD%E5%AD%97%E5%B9%95app/</guid>
      <description>&lt;p&gt;之前预告的 iOS 系统双语字幕 App，终于完成了。废话不多说，直接展示效果。目前的实现是通过 iOS 系统接口进行的，作为一个 baseline，后面也可以接入第三方开源方案。文末有项目链接，各位道友自取。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>之前预告的 iOS 系统双语字幕 App，终于完成了。废话不多说，直接展示效果。目前的实现是通过 iOS 系统接口进行的，作为一个 baseline，后面也可以接入第三方开源方案。文末有项目链接，各位道友自取。</p>
<h2 id="模块流程">模块流程</h2>

<div class="mermaid">graph LR
    A[捕获系统播放音频] --&gt; B[语音识别ASR]
    B --&gt; C[语言翻译]
    B --&gt; D[双语字幕]
    C --&gt; D</div>
<h2 id="效果展示">效果展示</h2>
<iframe width="100%" max-width="400" height="600" src="//player.bilibili.com/player.html?bvid=BV1irXGBtEnU&autoplay=0" frameborder="0" allowfullscreen></iframe>
<h2 id="一些感想">一些感想</h2>
<p>这也是第一个完全 AI Coding 的项目，有了 AI Coding 后有了一种自己无所不能的错觉。作为一个大龄程序员，已经放弃抵抗了，打不过咱就加入。</p>
<h3 id="ai-是放大器">AI 是放大器</h3>
<p>在做这个 Project 的过程中，对我来说最大的困难是 UI 部分。<a href="/posts/202601/2026-02-01-%E5%85%B3%E4%BA%8Eaicoding%E7%9A%84%E4%B8%80%E4%BA%9B%E6%84%9F%E6%83%B3%E5%92%8C%E5%90%8E%E7%BB%AD%E7%9A%84%E8%A7%84%E5%88%92/">关于 AICoding 的一些感想和后续的规划</a> 中提到的，AI 的能力约等于使用者自身的能力。最近听到&quot;AI 是放大器&quot;这个说法，这个比喻可真的是贴切形象了。假如 AI 可以放大 10 倍，如果应用在你熟悉的领域是 10 分，放大后就是 100 分；如果应用在你不熟悉的领域，比如 iOS 客户端开发，对我来说约等于 0 分，放大后也是约等于 0 分。那这个时候就看天吃饭了，AI 给什么就吃什么，就算有问题我也无法判断，更别说是纠正 AI 了。所以网上一堆零基础开发一个 App 上线并产生收入的，真的可能吗？难道我的使用姿势不对？</p>
<h3 id="狠狠地用起来">狠狠地用起来</h3>
<p>虽然对于自己不熟悉的领域，使用 AI 产生可用代码的偶然性挺大的，不过作为 AI 协作者，AI 在学习人类的代码，人类也可以学习 AI 的代码。在这个项目中，对于自己不熟悉的客户端 UI 部分的代码，我就会让 AI 给我讲解一遍，从语法到架构、为什么这么写。在提问 → 学习 → 再提问 → 再学习的不断迭代过程中，也在慢慢提高 AI 输出自己不熟悉代码的可控性和判断力。</p>
<p>现在是将想法落地最好的时代。以前你可能会卡在某个问题因为找不到有效的解答而不了了之，或者因为某个技术不懂而不能落地，但是现在你随时可以通过 AI 大模型得到你想要的答案，协助你实现自己的 idea。要像大神 Karpathy 一样，因为 token 没用完而感到焦虑，不停去跟 AI 交流、提问、讨论，落地想法。去尝试将 AI 嵌入到自己的工程流程中，这个我也还没找到方向，但是我觉得这个方向是没错的。狠狠地、使劲用起来吧。</p>
<h2 id="项目链接">项目链接</h2>
<p><a href="https://github.com/lyapple2008/DoubleSubtitleUseSystemAPI">DoubleSubtitleUseSystemAPI</a></p>
]]></content:encoded>
    </item>
    <item>
      <title>ASR任务初体验</title>
      <link>https://lyapple2008.github.io/posts/202602/2026-02-14-asr%E4%BB%BB%E5%8A%A1%E5%88%9D%E4%BD%93%E9%AA%8C/</link>
      <pubDate>Sat, 14 Feb 2026 17:59:07 +0800</pubDate>
      <guid>https://lyapple2008.github.io/posts/202602/2026-02-14-asr%E4%BB%BB%E5%8A%A1%E5%88%9D%E4%BD%93%E9%AA%8C/</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;这篇继续是开发日志，正在开发一款iOS端的实时双语字幕APP，由于需要用到语音识别，了解了下语音识别任务的现状和主流方案，为后面方案选择做准备。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<blockquote>
<p>这篇继续是开发日志，正在开发一款iOS端的实时双语字幕APP，由于需要用到语音识别，了解了下语音识别任务的现状和主流方案，为后面方案选择做准备。</p></blockquote>
<h3 id="模块流程">模块流程</h3>

<div class="mermaid">graph LR
    A[捕获系统播放音频] --&gt; B[语音识别ASR]
    B --&gt; C[语言翻译]
    B --&gt; D[双语字幕]
    C --&gt; D</div>
<h1 id="什么是asr任务">什么是ASR任务</h1>
<p>ASR（Automatic Speech Recognition，自动语音识别）任务，指的是将连续的音频信号转换为对应的文本序列的过程。这是一种典型的序列到序列（Sequence-to-Sequence）的转换任务：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">audio (连续信号)  →  text (离散 token)</span></span></code></pre></td></tr></table>
</div>
</div>
<p>从信号处理的角度来看，传统ASR需要解决以下核心问题：</p>
<ol>
<li><strong>声学建模</strong>：将音频特征映射到音素或字符</li>
<li><strong>语言建模</strong>：捕捉词汇之间的概率关系</li>
<li><strong>对齐问题</strong>：音频帧与输出token之间的对齐</li>
</ol>
<p>不过，在现代端到端深度学习方案中，这三个问题都被统一在一个神经网络中解决，不再需要独立的声学模型和语言模型。模型通过端到端训练自动学习如何从音频特征直接映射到文本输出。</p>
<h1 id="与降噪任务的区别">与降噪任务的区别</h1>
<p>在开发iOS双语字幕的过程中，我之前可能接触过降噪任务，这两者有本质区别：</p>
<table>
  <thead>
      <tr>
          <th>维度</th>
          <th>降噪任务</th>
          <th>ASR任务</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>输入输出</strong></td>
          <td>音频 → 音频</td>
          <td>音频 → 文本</td>
      </tr>
      <tr>
          <td><strong>任务类型</strong></td>
          <td>信号回归</td>
          <td>序列到序列</td>
      </tr>
      <tr>
          <td><strong>评估指标</strong></td>
          <td>SNR、PESQ</td>
          <td>WER、CER</td>
      </tr>
      <tr>
          <td><strong>难点</strong></td>
          <td>保留语音质量</td>
          <td>识别准确性</td>
      </tr>
      <tr>
          <td><strong>模型结构</strong></td>
          <td>Encoder</td>
          <td>Encoder-Decoder</td>
      </tr>
  </tbody>
</table>
<p>简单来说：</p>
<ul>
<li><strong>降噪任务</strong>：输入一段有噪声的音频，输出干净的音频（同类转换）</li>
<li><strong>ASR任务</strong>：输入音频，输出文字（跨模态转换）</li>
</ul>
<p>ASR的难点在于它需要&quot;理解&quot;音频内容并转换为语义符号，而不是简单地处理信号波形。同时，ASR任务的输入和输出也不像降噪任务那样是一一对应的关系，还需要处理对齐问题。</p>
<h1 id="目前主流的实现方案">目前主流的实现方案</h1>
<p>主流的ASR实现方案主要有三种：CTC、RNN-T和基于Attention的Seq2Seq。</p>
<h2 id="ctc-connectionist-temporal-classification">CTC (Connectionist Temporal Classification)</h2>
<p>CTC是一种经典的对齐方法，核心思想是<strong>不需要显式的对齐标签</strong>，而是通过&quot;blank&quot;机制自动学习对齐。</p>
<p>CTC引入了空白符（blank）和折叠机制：</p>
<ul>
<li>重复的字符会被折叠（如 &ldquo;aaabbb&rdquo; → &ldquo;ab&rdquo;）</li>
<li>blank符号不产生任何输出</li>
<li>通过动态规划计算所有可能路径的概率</li>
</ul>
<p><a href="https://distill.pub/2017/ctc/">Sequence Modeling With CTC</a> 详细原理可参考这篇文章。</p>
<p>训练阶段就是使目标token序列路径的概率最大，而推理阶段就是搜索概率最大的token路径并输出，这里通常有两种方法：</p>
<table>
  <thead>
      <tr>
          <th>方法</th>
          <th>描述</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>Greedy Search</strong></td>
          <td>每一步都选择概率最大的token输出</td>
      </tr>
      <tr>
          <td><strong>Beam Search</strong></td>
          <td>每一步保留top-N概率的token结果，输出token序列路径概率最大的结果</td>
      </tr>
  </tbody>
</table>
<h2 id="rnn-t-recurrent-neural-network-transducer">RNN-T (Recurrent Neural Network Transducer)</h2>
<p>RNN-T是CTC的扩展，引入了一个额外的预测网络（Prediction Network）来建模输出token之间的依赖关系。</p>
<p>RNN-T由三部分组成：</p>
<ol>
<li><strong>编码器（Encoder）</strong>：将音频特征转换为声学表征</li>
<li><strong>预测网络（Prediction Network）</strong>：基于已输出的token预测下一个token</li>
<li><strong>联合网络（Joint Network）</strong>：结合Encoder和Prediction的输出，预测下一个token</li>
</ol>
<p>RNN-T在输出时，不仅会输入当前帧音频数据，还会输入历史输出作为参考</p>
<h2 id="seq2seq-with-attention">Seq2Seq with Attention</h2>
<p>基于Attention机制的序列到序列模型是目前最流行的方案，被广泛用于Whisper、Paraformer等现代ASR系统。</p>
<h3 id="原理">原理</h3>
<p>经典结构：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">Encoder → Attention → Decoder → Output</span></span></code></pre></td></tr></table>
</div>
</div>
<ul>
<li><strong>Encoder</strong>：将音频特征编码为高维表征（通常使用Transformer或Conformer）</li>
<li><strong>Attention</strong>：让Decoder在生成每个token时&quot;关注&quot;输入的不同部分</li>
<li><strong>Decoder</strong>：自回归生成输出文本</li>
</ul>
<h3 id="优缺点">优缺点</h3>
<p><strong>优点</strong>：</p>
<ul>
<li>可以建模任意长度序列的依赖关系</li>
<li>识别准确率高</li>
<li>易于添加语言模型集成</li>
<li>适合大规模预训练</li>
</ul>
<p><strong>缺点</strong>：</p>
<ul>
<li>推理延迟较高（需要完整音频或较大chunk）</li>
<li>流式识别实现复杂</li>
<li>计算资源需求大</li>
</ul>
<h1 id="流式模型和非流式模型">流式模型和非流式模型</h1>
<p>流式（Streaming）和非流式（Offline）是ASR系统根据实时性要求的两种设计模式。</p>
<h2 id="什么是非流式模型">什么是非流式模型</h2>
<p>非流式模型（Offline ASR）需要<strong>等待完整音频输入后才能开始识别</strong>。</p>
<p>特点：</p>
<ul>
<li>输入：完整的音频文件或长音频段</li>
<li>延迟：较高，需要等待音频结束</li>
<li>准确率：通常较高，因为可以看到完整的上下文</li>
<li>适用场景：视频字幕生成、会议转录、录音文件处理</li>
</ul>
<h2 id="什么是流式模型">什么是流式模型</h2>
<p>流式模型（Streaming ASR）可以<strong>边接收音频输入边输出识别结果</strong>，实现实时识别。</p>
<p>特点：</p>
<ul>
<li>输入：连续的音频流（通常是短片段，如30ms一帧）</li>
<li>延迟：低，可以做到几百毫秒内输出</li>
<li>准确率：通常略低于非流式，因为只有历史和部分未来上下文</li>
<li>适用场景：实时语音对话、语音助手、直播字幕</li>
</ul>
<h2 id="技术实现差异">技术实现差异</h2>
<table>
  <thead>
      <tr>
          <th>维度</th>
          <th>流式模型</th>
          <th>非流式模型</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>上下文</strong></td>
          <td>有限上下文（lookahead）</td>
          <td>完整上下文</td>
      </tr>
      <tr>
          <td><strong>延迟</strong></td>
          <td>低（&lt;500ms）</td>
          <td>高</td>
      </tr>
      <tr>
          <td><strong>准确率</strong></td>
          <td>略低</td>
          <td>较高</td>
      </tr>
      <tr>
          <td><strong>模型复杂度</strong></td>
          <td>较高（需处理分段）</td>
          <td>较低</td>
      </tr>
      <tr>
          <td><strong>内存占用</strong></td>
          <td>较小</td>
          <td>较大</td>
      </tr>
  </tbody>
</table>
<p>流式模型通常需要特殊设计，如：</p>
<ul>
<li><strong>Chunked Attention</strong>：将音频分成小块处理</li>
<li><strong>CTC Prefix</strong>：使用CTC的前缀解码</li>
<li><strong>Lookahead</strong>：只考虑有限的未来帧</li>
</ul>
<hr>
<h1 id="自回归解码和非自回归解码">自回归解码和非自回归解码</h1>
<p>解码方式决定了模型如何生成输出文本。</p>
<h2 id="自回归解码-autoregressive-decoding">自回归解码 (Autoregressive Decoding)</h2>
<p>自回归解码是目前最主流的方式，特点是<strong>逐 token 生成，每个 token 的生成依赖之前所有生成的 token</strong>。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">输出: &#34;hello world&#34;
</span></span><span class="line"><span class="cl">生成过程:
</span></span><span class="line"><span class="cl">  1. 生成 &#34;h&#34;
</span></span><span class="line"><span class="cl">  2. 基于 &#34;h&#34; 生成 &#34;he&#34;
</span></span><span class="line"><span class="cl">  3. 基于 &#34;he&#34; 生成 &#34;hel&#34;
</span></span><span class="line"><span class="cl">  4. ...</span></span></code></pre></td></tr></table>
</div>
</div>
<p>特点：</p>
<ul>
<li><strong>优点</strong>：生成质量高，可以建模长期依赖</li>
<li><strong>缺点</strong>：串行生成，推理速度慢（O(n) 复杂度，n为输出长度）</li>
<li>典型模型：Transformer Decoder、RNN-T</li>
</ul>
<h2 id="非自回归解码-non-autoregressive-decoding">非自回归解码 (Non-autoregressive Decoding)</h2>
<p>非自回归解码是一种<strong>并行生成</strong>方式，一次性输出整个序列。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">输出: &#34;hello world&#34;
</span></span><span class="line"><span class="cl">生成过程:
</span></span><span class="line"><span class="cl">  1. 直接输出完整句子 &#34;hello world&#34;</span></span></code></pre></td></tr></table>
</div>
</div>
<p>特点：</p>
<ul>
<li><strong>优点</strong>：并行生成，推理速度快（O(1) 复杂度）</li>
<li><strong>缺点</strong>：难以建模输出token之间的依赖，生成质量可能较低</li>
<li>典型实现：CTC、FastCorrect、Mask-Predict</li>
</ul>
<h2 id="对比">对比</h2>
<table>
  <thead>
      <tr>
          <th>维度</th>
          <th>自回归解码</th>
          <th>非自回归解码</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>生成方式</strong></td>
          <td>串行，逐token</td>
          <td>并行，一次性输出</td>
      </tr>
      <tr>
          <td><strong>推理速度</strong></td>
          <td>慢</td>
          <td>快</td>
      </tr>
      <tr>
          <td><strong>生成质量</strong></td>
          <td>高</td>
          <td>略低</td>
      </tr>
      <tr>
          <td><strong>依赖关系</strong></td>
          <td>建模token间依赖</td>
          <td>假设条件独立</td>
      </tr>
      <tr>
          <td><strong>典型模型</strong></td>
          <td>RNN-T、Seq2Seq</td>
          <td>CTC、FastConformer</td>
      </tr>
  </tbody>
</table>
<h1 id="方案选择">方案选择</h1>
<p>OK，前面了解了这么多，都是为了后续实现最小原型产品，选择语音识别方案做准备，目标不是要训练一个SOTA模型，因此这里暂时只是粗浅的了解。</p>
<p>根据需求分析：</p>
<ul>
<li><strong>目标设备</strong>：iOS移动端（资源有限）</li>
<li><strong>语言支持</strong>：多语言</li>
<li><strong>实时性</strong>：实时输入音频，实时输出文字</li>
</ul>
<h2 id="选择优先级">选择优先级</h2>
<ol>
<li><strong>首先能跑</strong>：模型大小和计算量必须在移动端可承受范围内</li>
<li><strong>然后多语言</strong>：需要支持多种语言识别</li>
<li><strong>最后准确率</strong>：在功能可用后再优化性能</li>
</ol>
<h2 id="优先级一模型能在移动端跑起来">优先级一：模型能在移动端跑起来</h2>
<h3 id="参数量选择">参数量选择</h3>
<p>移动端资源有限，模型参数量直接决定了能否运行。这里暂时没有明确的数值界限，实际运行起来再看，后面也可以用量化技术缩小模型体积。</p>
<p><strong>量化技术</strong>：INT8量化可将模型体积缩小约4倍，准确率损失通常&lt;5%；INT4可进一步缩小到1/8，但准确率下降更明显。</p>
<h3 id="架构选择">架构选择</h3>
<table>
  <thead>
      <tr>
          <th>架构</th>
          <th>计算量</th>
          <th>内存占用</th>
          <th>移动端适用性</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>Transformer Encoder</strong></td>
          <td>中</td>
          <td>中</td>
          <td>★★★★☆</td>
      </tr>
      <tr>
          <td><strong>Conformer</strong></td>
          <td>中高</td>
          <td>中</td>
          <td>★★★★☆</td>
      </tr>
      <tr>
          <td><strong>RNN/LSTM</strong></td>
          <td>低</td>
          <td>低</td>
          <td>★★★☆☆</td>
      </tr>
  </tbody>
</table>
<p><strong>建议</strong>：选择Encoder-only或轻量级的Conformer结构，参数量控制在50M以内。</p>
<h2 id="优先级二支持多语言">优先级二：支持多语言</h2>
<p>确认模型能在移动端运行后，需要考虑多语言支持能力。</p>
<h3 id="不同模型架构的多语言能力">不同模型架构的多语言能力</h3>
<table>
  <thead>
      <tr>
          <th>模型架构</th>
          <th>多语言支持</th>
          <th>说明</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>端到端Seq2Seq</strong></td>
          <td>★★★★★</td>
          <td>训练时使用多语言数据，自然支持多语言</td>
      </tr>
      <tr>
          <td><strong>RNN-T</strong></td>
          <td>★★★☆☆</td>
          <td>可支持，但需要针对性训练多语言版本</td>
      </tr>
      <tr>
          <td><strong>CTC</strong></td>
          <td>★★☆☆☆</td>
          <td>通常针对单一语言，多语言版本较少</td>
      </tr>
  </tbody>
</table>
<h3 id="结论">结论</h3>
<p>需要支持多语言时，<strong>优先选择端到端Seq2Seq架构</strong>，这类模型的预训练版本通常已支持数十到上百种语言。</p>
<h2 id="优先级三实时性要求">优先级三：实时性要求</h2>
<p>实时字幕要求模型能够在接收音频的同时输出文字。</p>
<h3 id="流式-vs-非流式">流式 vs 非流式</h3>
<table>
  <thead>
      <tr>
          <th>类型</th>
          <th>延迟</th>
          <th>实现难度</th>
          <th>实时字幕适用性</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>流式模型</strong></td>
          <td>&lt;500ms</td>
          <td>高</td>
          <td>★★★★★</td>
      </tr>
      <tr>
          <td><strong>非流式模型</strong></td>
          <td>&gt;1s</td>
          <td>低</td>
          <td>★★☆☆☆</td>
      </tr>
  </tbody>
</table>
<p><strong>折中方案</strong>：使用非流式模型时，可通过&quot;分块处理&quot;策略模拟流式效果：</p>
<ul>
<li>将音频切分为固定长度的chunk（如1秒）</li>
<li>逐块识别并拼接结果</li>
<li>通过缓存历史上下文减少误差</li>
</ul>
<h3 id="解码方式">解码方式</h3>
<table>
  <thead>
      <tr>
          <th>解码方式</th>
          <th>延迟</th>
          <th>实现复杂度</th>
          <th>实时性</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>非自回归（Greedy）</strong></td>
          <td>低</td>
          <td>简单</td>
          <td>★★★★★</td>
      </tr>
      <tr>
          <td><strong>非自回归（Beam Search）</strong></td>
          <td>中</td>
          <td>中</td>
          <td>★★★★☆</td>
      </tr>
      <tr>
          <td><strong>自回归</strong></td>
          <td>高</td>
          <td>复杂</td>
          <td>★★☆☆☆</td>
      </tr>
  </tbody>
</table>
<p><strong>建议</strong>：实时场景优先选择<strong>非自回归解码</strong>（Greedy），延迟最低。</p>
<h2 id="总结">总结</h2>
<p><strong>核心理念</strong>：先完成端到端流程验证，再根据实际体验进行针对性优化。移动端ASR是一个迭代过程，不必追求一步到位。</p>
<p>后续我会记录在iOS端的具体实现过程，各位道友记得点赞追番哦。</p>
<p><img alt="各位道友记得一键三连" loading="lazy" src="/images/%E4%B8%80%E9%94%AE%E4%B8%89%E8%BF%9E.jpg"></p>
]]></content:encoded>
    </item>
    <item>
      <title>关于AICoding的一些感想和后续的规划</title>
      <link>https://lyapple2008.github.io/posts/202601/2026-02-01-%E5%85%B3%E4%BA%8Eaicoding%E7%9A%84%E4%B8%80%E4%BA%9B%E6%84%9F%E6%83%B3%E5%92%8C%E5%90%8E%E7%BB%AD%E7%9A%84%E8%A7%84%E5%88%92/</link>
      <pubDate>Sun, 01 Feb 2026 09:28:45 +0800</pubDate>
      <guid>https://lyapple2008.github.io/posts/202601/2026-02-01-%E5%85%B3%E4%BA%8Eaicoding%E7%9A%84%E4%B8%80%E4%BA%9B%E6%84%9F%E6%83%B3%E5%92%8C%E5%90%8E%E7%BB%AD%E7%9A%84%E8%A7%84%E5%88%92/</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;最近要说最热最火的话题，那一定是 AI，而其中 AI 应用场景 AI Coding 也是发展迅速，大有替代程序员的势头。最近几个月一直在使用 AI Coding，心态上经历了最初的「完了，失业要提前了」，到「稳了，还能苟几年」。下面就分享一些使用上的感受，以及面对 AI Coding 这股后浪，这个号后面的一些规划。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<blockquote>
<p>最近要说最热最火的话题，那一定是 AI，而其中 AI 应用场景 AI Coding 也是发展迅速，大有替代程序员的势头。最近几个月一直在使用 AI Coding，心态上经历了最初的「完了，失业要提前了」，到「稳了，还能苟几年」。下面就分享一些使用上的感受，以及面对 AI Coding 这股后浪，这个号后面的一些规划。</p></blockquote>
<p><img alt="程序员使用 AI 编码提升效率" loading="lazy" src="/images/2026-02-01/%E7%A8%8B%E5%BA%8F%E5%91%98%E4%BD%BF%E7%94%A8AI%E7%BC%96%E7%A0%81%E6%8F%90%E5%8D%87%E6%95%88%E7%8E%87.jpg"></p>
<h3 id="1-ai-coding-非常有帮助收费的优于免费的">1. AI Coding 非常有帮助，收费的优于免费的</h3>
<p>这几年 AI 发展迅速，早已不是当年的「人工智障」了，确实是可以作为生产力工具帮忙提升效率的。对于 AI Coding 来说，到底能提升多少效率，取决于多方面因素：比如 Coding 在日常开发中占的比重、使用 AI 的熟练程度、AI Coding 工具本身的性能等等。但不管怎么说，AI Coding 对于程序员来说都是有正向作用的，是值得花时间去用起来的。</p>
<p>我自己用得比较多的是 Cursor 跟 Claude Code。Cursor 是公司给配置的，Claude Code 是我自己订阅了 MiniMax 的 Coding Plan 配置的。在此之前也用过国内一些免费的 AI Coding 工具（比如 Trae 和 Qcoder），短暂的使用给我的感受就是没有付费的好用，一个问题反复折腾好几轮都没有跑通。</p>
<p>当然这些纯粹是个人拍脑袋的感受，使用时间不长，也没有很深入地去使用。网上也有一些使用 Trae 和 Qcoder 做出可用应用的案例。重要的是 AI Coding 确实能够提升开发效率的，大家都应该用起来。</p>
<h3 id="2-ai-coding-的能力约等于使用者自身">2. AI Coding 的能力约等于使用者自身</h3>
<p><img alt="AI Coding 能力边界" loading="lazy" src="/images/2026-02-01/1770474310208.jpg"></p>
<p>AI Coding 在目前这个阶段还是一个被动的工具，需要使用者去给它进行规划和任务指引，因此使用者的能力边界就约等于使用 AI Coding 后的能力边界。</p>
<p>程序开发是一个复杂的系统工程，每个需求拆分任务后就有很多步骤。就算 AI Coding 每一步的成功率为 95%，8 步之后最终能成功的概率也只剩下 66%。另外，AI Coding 获取的需求信息主要来源于使用者，使用者在与 AI 沟通描述时，这里又得损耗一些性能，导致成功率下降。不过好消息是，使用者可以在与 AI 互动过程中不断扩展自己的能力边界，把原来了解的领域变成熟悉领域，所以理论上 AI Coding 是没有边界的。</p>
<p>这里给我几点启发：</p>
<ol>
<li>生产环境只在自己熟悉的范围内使用，最多只触及到了解的区域，再往外扩展能得到什么成果就只能听天由命了，试验性的尝试性的项目不受这个限制。</li>
<li>给 AI 描述需求时，尽可能详细，就像你自己开发一样，想好整个代码的架构和目标，并记录成文档。文档不仅可以给 AI 描述需求，也可以随时给 AI 找回上下文，弥补 AI Coding 上下文有限的问题。</li>
<li>大龄程序员凭借多年积累的经验，辅以AI Coding弥补”编程体力不足“，又可以焕发第二春了。</li>
</ol>
<h3 id="3-ai-coding-还只是辅助编程使用者需要为其质量兜底">3. AI Coding 还只是辅助编程，使用者需要为其质量兜底</h3>
<p><img alt="AI 与程序员" loading="lazy" src="/images/2026-02-01/%E7%A8%8B%E5%BA%8F%E5%91%98%E8%A2%ABAI%E9%A9%BE%E9%A9%B6%E6%B1%BD%E8%BD%A6%E6%8B%A6%E4%BD%8F%E7%BD%9A%E6%AC%BE.png"></p>
<p>类似于汽车自动驾驶，AI Coding 现在也还只是 L2 水平，辅助编程阶段，出了事故还是得使用者来负责。所以对于生产环境使用 AI Coding 时，要对 AI Coding 生成的代码进行严格的 Review。</p>
<p>不过最近也看到另一种解法：随着 AI Coding 生成的代码量级快速上升，传统逐行代码 Review 的方式已经不太现实，而且效率不高。所以也有不 Review 只测试的方式来验收 AI Coding 生成的代码，在修改和生成代码的同时，让AI也生成对应的单元测试，人类负责把关单元测试的通过情况，可能也许也是一种可行的AI Coding协同方式。</p>
<h2 id="后续的规划">后续的规划</h2>
<p>随着 AI 的普及和渗透，获取知识、甚至获取技能都越来越轻而易举。坏的方面是，个人的知识和技能越来越贬值；乐观的方面是，个人可以利用的知识和技能也是越来越多，也越来越轻松。</p>
<p><img alt="Peter Steinberger github主页" loading="lazy" src="/images/2026-02-01/20260208-132545.jpg"></p>
<p>最近 OpenClaw 非常火，<a href="https://github.com/steipete">Peter Steinberger</a> 是其核心开发者。从其 GitHub 主页可以看到，在 Claude 爆火之前，他一直在做迭代各种各样的工具或者产品（具有完整定义功能的程序就算产品，不一定是像 WeChat/WhatsApp/TikTok 这样才算），OpenClaw的爆火不过是其多年程序迭代的一次厚积薄发。后面希望可以学习他这种方式，在做中学，在学中做，这也许是 AI 时代最快的进步方式。</p>
<p>后面将不再进行纯粹的单点知识分享，希望可以从打造一个真实可用的产品角度去分享打造的过程。这个产品只需要能提供完整可用的功能就行。</p>
<p>在这个知识和技能获取越来越便捷的时代，迭代速度也许才是制胜法宝。</p>
<hr>
<p>OK，以上就是一些个人浅显的、混乱的感想和 2026 年的 Flag，剩下的就交给时间和执行了，各位道友记得点赞追番一起加油吧！</p>
<p><img alt="请一键三连" loading="lazy" src="/images/2026-02-01/1770529335422.jpg"></p>
]]></content:encoded>
    </item>
  </channel>
</rss>
