<?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>音频 on Hacper&#39;s Blog</title>
    <link>https://hacperme.com/tags/%E9%9F%B3%E9%A2%91/</link>
    <description>Recent content in 音频 on Hacper&#39;s Blog</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>zh</language>
    <lastBuildDate>Fri, 17 Jul 2026 10:22:58 +0800</lastBuildDate>
    <atom:link href="https://hacperme.com/tags/%E9%9F%B3%E9%A2%91/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Sonic Wave：一个纯浏览器端的音频转换与压缩工具</title>
      <link>https://hacperme.com/posts/notes/2026/20260717_sonicwave/</link>
      <pubDate>Fri, 17 Jul 2026 10:22:58 +0800</pubDate>
      <guid>https://hacperme.com/posts/notes/2026/20260717_sonicwave/</guid>
      <description>介绍 Sonic Wave 的设计思路、核心实现、部署方式和使用方法。</description>
      <content:encoded><![CDATA[<p>最近我做了一个小工具项目：<a href="https://github.com/hacperme/SonicWave">Sonic Wave</a>。它的目标很直接：<strong>把常见音频转换、压缩、批量处理这件事，尽可能简单地放到浏览器里完成。</strong></p>
<p>在线体验地址：<a href="https://sonic-wave.hacperme.com/">https://sonic-wave.hacperme.com/</a></p>
<p>如果你平时经常遇到下面这些需求，那么这个工具可能正好适合你：</p>
<ul>
<li>把 WAV 转成 MP3 / M4A / OGG / FLAC</li>
<li>调整音频比特率、采样率、声道</li>
<li>批量处理多个音频文件</li>
<li>不想安装桌面软件，只想打开网页马上用</li>
<li>不希望把音频上传到第三方服务器</li>
</ul>
<p>这篇文章记录一下 Sonic Wave 的设计思路、核心实现和使用方法，也顺便给它打个广告（笑）。如果你正好有音频转码、音频压缩、批量格式转换的需求，欢迎试用和 star：</p>
<ul>
<li>GitHub：<a href="https://github.com/hacperme/SonicWave">https://github.com/hacperme/SonicWave</a></li>
<li>在线网站：<a href="https://sonic-wave.hacperme.com/">https://sonic-wave.hacperme.com/</a></li>
</ul>
<h2 id="项目想解决什么问题">项目想解决什么问题</h2>
<p>很多在线音频工具虽然方便，但通常有几个问题：</p>
<ol>
<li>需要把文件上传到服务器</li>
<li>对隐私敏感内容不友好</li>
<li>批量处理体验一般</li>
<li>页面广告多、速度不稳定</li>
<li>部署和迁移不方便</li>
</ol>
<p>Sonic Wave 的思路是反过来：<strong>服务器只负责把页面和静态资源发给浏览器，真正的音频处理全部在客户端完成。</strong></p>
<p>这样做的好处很明显：</p>
<ul>
<li><strong>隐私更好</strong>：音频文件不需要上传到后端</li>
<li><strong>部署更轻</strong>：后端不承担转码计算压力</li>
<li><strong>可移植性更强</strong>：一个静态前端 + 一个轻量服务端就能跑起来</li>
<li><strong>更适合自部署</strong>：个人网站、NAS、小服务器都可以挂起来</li>
</ul>
<h2 id="sonic-wave-能做什么">Sonic Wave 能做什么</h2>
<p>目前 Sonic Wave 主要支持这些能力：</p>
<ul>
<li>支持 <strong>MP3、WAV、AAC/M4A、OGG Vorbis、FLAC</strong> 格式转换</li>
<li>支持 <strong>批量选择多个音频文件</strong></li>
<li>支持设置：
<ul>
<li>输出格式</li>
<li>声道（单声道 / 立体声）</li>
<li>采样率</li>
<li>比特率</li>
</ul>
</li>
<li>支持批量处理完成后 <strong>打包 ZIP 下载</strong></li>
<li>支持音频基础信息解析，例如：
<ul>
<li>时长</li>
<li>采样率</li>
<li>声道</li>
<li>原始比特率</li>
</ul>
</li>
</ul>
<p>从页面交互上看，整个流程也比较直接：</p>
<ol>
<li>拖拽或选择音频文件</li>
<li>选择输出格式和参数</li>
<li>点击开始处理</li>
<li>逐个下载，或者打包下载全部结果</li>
</ol>
<h2 id="设计思路">设计思路</h2>
<p>Sonic Wave 的架构其实很克制，可以概括成一句话：</p>
<blockquote>
<p><strong>前端负责所有音频处理，后端只做静态资源分发和浏览器运行环境保障。</strong></p>
</blockquote>
<h3 id="前端部分ffmpegwasm-做核心处理">前端部分：FFmpeg.wasm 做核心处理</h3>
<p>项目前端是一个单页面应用，主要逻辑写在 <code>index.html</code> 中，核心依赖是本地化的 FFmpeg.wasm 资源：</p>
<ul>
<li><code>assets/ffmpeg.js</code></li>
<li><code>assets/ffmpeg-core.js</code></li>
<li><code>assets/ffmpeg-core.wasm</code></li>
<li><code>assets/util.js</code></li>
<li><code>assets/jszip.min.js</code></li>
</ul>
<p>页面初始化时会加载本地 FFmpeg 核心，而不是依赖外部 CDN。这样做有两个好处：</p>
<ul>
<li>部署时更可控，不依赖第三方资源可用性</li>
<li>更适合内网、NAS 或自建服务场景</li>
</ul>
<p>在转换时，前端会把用户选择的文件写入 FFmpeg 的虚拟文件系统，然后按选项拼出命令，例如：</p>
<ul>
<li><code>-ac</code> 控制声道</li>
<li><code>-ar</code> 控制采样率</li>
<li><code>-b:a</code> 控制音频比特率</li>
<li><code>-acodec</code> 选择对应输出编码器</li>
</ul>
<p>处理完成后，再从 WASM 文件系统中读取输出结果，转成浏览器可下载的 <code>Blob</code>。</p>
<h3 id="为什么强调纯浏览器端">为什么强调“纯浏览器端”</h3>
<p>因为这不是简单地在网页上加一个上传按钮，然后服务器上跑 ffmpeg。Sonic Wave 的关键点在于：</p>
<ul>
<li>音频文件在浏览器本地处理</li>
<li>服务端不接收待转换的音频内容</li>
<li>转换、压缩、打包下载都尽量在客户端完成</li>
</ul>
<p>对于隐私敏感的录音、会议音频、学习资料、个人素材，这一点很重要。</p>
<h2 id="一些实现细节">一些实现细节</h2>
<p>这个项目里有几处我觉得比较实用的设计。</p>
<h3 id="1-元数据解析先行">1. 元数据解析先行</h3>
<p>在真正转换前，Sonic Wave 会先尝试解析每个文件的基础信息。做法是调用 FFmpeg 的 <code>-i</code> 行为读取输入文件信息，再从日志里提取：</p>
<ul>
<li>Duration</li>
<li>Sample Rate</li>
<li>Channels</li>
<li>Bitrate</li>
</ul>
<p>这样用户在开始处理前，就能先看到文件的大概情况，避免“选完就转，但其实参数完全不清楚”的盲操作。</p>
<h3 id="2-批量处理时考虑了内存回收">2. 批量处理时考虑了内存回收</h3>
<p>浏览器里跑 FFmpeg.wasm，最现实的问题之一就是内存。</p>
<p>Sonic Wave 目前在批量处理时采用的是<strong>串行处理</strong>，并且每处理一批文件会主动重新初始化 FFmpeg 实例来释放内存。项目里还做了这些处理：</p>
<ul>
<li>每个文件处理完成后立即删除临时输入 / 输出文件</li>
<li>读取结果后立刻复制到独立内存，避免长期占用 WASM 内存</li>
<li>批量处理时带简单重试机制</li>
<li>对常见错误给出更友好的中文提示</li>
</ul>
<p>这类设计没有追求“同时并发处理一堆文件”，而是优先保证稳定性和可用性。对于浏览器端大文件转码来说，我觉得这是更实际的路线。</p>
<h3 id="3-文件大小限制更明确">3. 文件大小限制更明确</h3>
<p>为了避免浏览器直接被大文件拖垮，页面上限制了：</p>
<ul>
<li>单文件不超过 <strong>100MB</strong></li>
<li>总文件大小不超过 <strong>500MB</strong></li>
</ul>
<p>这不一定适合所有场景，但对于在线工具来说，这是一个比较务实的折中。</p>
<h3 id="4-支持批量结果打包下载">4. 支持批量结果打包下载</h3>
<p>如果一次转换了多个文件，Sonic Wave 会提供 <strong>ZIP 打包下载</strong>。这点对实际使用体验帮助很大，不然一个个点下载会很烦。</p>
<h3 id="5-对-webassembly-运行环境做了专门处理">5. 对 WebAssembly 运行环境做了专门处理</h3>
<p>为了让 FFmpeg.wasm 正常工作，服务端在响应里增加了：</p>
<ul>
<li><code>Cross-Origin-Opener-Policy: same-origin</code></li>
<li><code>Cross-Origin-Embedder-Policy: require-corp</code></li>
</ul>
<p>在线站点当前返回头里也能看到这两个配置。这类 Header 虽然不显眼，但对某些 WebAssembly / SharedArrayBuffer 相关能力是很关键的。</p>
<h2 id="后端为什么用-rust">后端为什么用 Rust</h2>
<p>Sonic Wave 的后端并不复杂，但我还是选了 Rust + Axum 来实现一个轻量静态文件服务。</p>
<p>它主要负责几件事：</p>
<ul>
<li>提供静态资源访问</li>
<li>注入 COOP / COEP 响应头</li>
<li>区分 HTML 和静态资源的缓存策略</li>
<li>读取配置文件和环境变量</li>
<li>提供更适合生产部署的运行方式</li>
</ul>
<p>项目里的配置优先级是：</p>
<blockquote>
<p><strong>环境变量 &gt; <code>config.toml</code> &gt; 默认值</strong></p>
</blockquote>
<p>默认配置大致如下：</p>
<ul>
<li>端口：<code>8089</code></li>
<li>静态目录：<code>.</code></li>
<li>HTML：<code>no-cache, must-revalidate</code></li>
<li>静态资源：<code>public, max-age=31536000, immutable</code></li>
</ul>
<p>这个设计很适合部署静态 Web 工具：</p>
<ul>
<li>HTML 不强缓存，更新能更快生效</li>
<li>JS / WASM / CSS 这类资源可以长期缓存，减轻带宽压力</li>
</ul>
<h2 id="docker-部署也做得比较轻">Docker 部署也做得比较轻</h2>
<p>这个项目除了可以直接运行二进制，还支持 Docker 部署。</p>
<p>Dockerfile 用的是<strong>多阶段构建</strong>：</p>
<ol>
<li>在 Rust 构建镜像里编译服务端</li>
<li>在精简 Alpine 镜像里只放最终产物</li>
<li>用非 root 用户运行</li>
<li>自带健康检查</li>
</ol>
<p>如果你只是想快速部署，最方便的方式就是直接用 <code>docker-compose.yml</code>。</p>
<h2 id="使用方法">使用方法</h2>
<h3 id="在线使用">在线使用</h3>
<p>最简单的方式就是直接打开：</p>
<p><a href="https://sonic-wave.hacperme.com/">https://sonic-wave.hacperme.com/</a></p>
<p>使用步骤：</p>
<ol>
<li>打开网页</li>
<li>拖拽音频文件，或点击上传</li>
<li>选择输出格式</li>
<li>按需调整比特率、采样率、声道</li>
<li>点击开始处理</li>
<li>下载转换结果</li>
</ol>
<p>适合这些场景：</p>
<ul>
<li>把录音转成更通用的 MP3</li>
<li>把音频体积压小，方便分享</li>
<li>批量整理不同格式的音频文件</li>
<li>快速导出适合手机、播放器或网页使用的格式</li>
</ul>
<h3 id="本地--服务器部署">本地 / 服务器部署</h3>
<h4 id="方式一docker-compose">方式一：Docker Compose</h4>
<p>docker-compose.yml 文件：</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></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">sonic-wave</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">hacper/sonic-wave:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s2">&#34;18099:8089&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">PORT=8089</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">STATIC_DIR=/app</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="c"># 可选：挂载自定义配置文件</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">./config.toml:/app/config.toml:ro</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">restart</span><span class="p">:</span><span class="w"> </span><span class="l">unless-stopped</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">sonic-wave</span><span class="w">
</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></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker-compose up -d
</span></span></code></pre></td></tr></table>
</div>
</div><p>默认会监听 <code>8089</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">http://localhost:8089
</span></span></code></pre></td></tr></table>
</div>
</div><h4 id="方式二docker-手动运行">方式二：Docker 手动运行</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></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker build -t sonic-wave .
</span></span><span class="line"><span class="cl">docker run -d -p 8089:8089 --name sonic-wave sonic-wave
</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></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">cargo build --release
</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></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">./target/release/sonic-wave
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="配置文件示例">配置文件示例</h3>
<p>可以通过 <code>config.toml</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><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-toml" data-lang="toml"><span class="line"><span class="cl"><span class="nx">port</span> <span class="p">=</span> <span class="mi">8089</span>
</span></span><span class="line"><span class="cl"><span class="nx">static_dir</span> <span class="p">=</span> <span class="s2">&#34;.&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">cache_control</span> <span class="p">=</span> <span class="s2">&#34;public, max-age=31536000, immutable&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">html_cache_control</span> <span class="p">=</span> <span class="s2">&#34;no-cache, must-revalidate&#34;</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></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nv">PORT</span><span class="o">=</span><span class="m">9000</span> <span class="nv">STATIC_DIR</span><span class="o">=</span>. ./target/release/sonic-wave
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="适合谁用">适合谁用</h2>
<p>我觉得 Sonic Wave 特别适合下面几类用户：</p>
<ul>
<li>想要一个<strong>自部署在线音频工具</strong>的人</li>
<li>希望文件<strong>不经过后端上传</strong>的人</li>
<li>需要<strong>批量转换音频</strong>的人</li>
<li>想在 NAS、轻量云服务器、个人站点里挂一个实用工具的人</li>
<li>想研究 <strong>FFmpeg.wasm + Rust 静态服务</strong> 组合的人</li>
</ul>
<h2 id="目前的一些边界">目前的一些边界</h2>
<p>当然，这个项目也不是没有边界。</p>
<p>比如浏览器端工具天然受这些因素影响：</p>
<ul>
<li>浏览器内存限制</li>
<li>设备性能差异</li>
<li>大文件长音频处理时间较长</li>
<li>某些编码格式在不同浏览器环境下体验不同</li>
</ul>
<p>所以 Sonic Wave 更适合：</p>
<ul>
<li>日常轻量到中等规模的音频处理</li>
<li>格式转换</li>
<li>批量整理</li>
<li>快速压缩和导出</li>
</ul>
<p>如果是超大体积、超长时长、专业级流水线处理，那还是本地原生 FFmpeg 更合适。</p>
<h2 id="为什么我还挺推荐你试试">为什么我还挺推荐你试试</h2>
<p>因为它确实是一个比较“顺手”的工具：</p>
<ul>
<li>打开就能用</li>
<li>不需要安装复杂软件</li>
<li>不需要把文件传给别人</li>
<li>对自部署很友好</li>
<li>技术栈也比较清晰，适合二次修改</li>
</ul>
<p>如果你平时正好会遇到：</p>
<ul>
<li>手机录音太大</li>
<li>音频格式不兼容</li>
<li>想批量转成 MP3 / M4A</li>
<li>想把一个实用小工具挂到自己的网站上</li>
</ul>
<p>那 Sonic Wave 值得收藏一下：</p>
<ul>
<li>在线体验：<a href="https://sonic-wave.hacperme.com/">https://sonic-wave.hacperme.com/</a></li>
<li>项目地址：<a href="https://github.com/hacperme/SonicWave">https://github.com/hacperme/SonicWave</a></li>
</ul>
<p>如果你用了之后觉得顺手，也欢迎给个 star，或者按自己的需求继续改造成更适合自己的版本。</p>
<h2 id="小结">小结</h2>
<p>Sonic Wave 不是一个追求“大而全”的音频平台，而是一个目标明确的小工具：</p>
<blockquote>
<p><strong>用尽量简单的架构，提供一个可在线使用、可自部署、重隐私、支持批量处理的音频转换工具。</strong></p>
</blockquote>
<p>我自己挺喜欢这类项目：功能具体、结构清晰、部署不重、拿来就能用。</p>
<p>如果你也喜欢这种风格，欢迎试试 Sonic Wave。</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
