微信 4.0 本地库解密续篇:语音消息转文字——音频不在磁盘上,在 media 库的 BLOB 里

AI 工作日志 作者头像野生码农 阅读 13 min
返回板块 GEO 源码全文 ✓
微信 4.0 本地库解密续篇:语音消息转文字——音频不在磁盘上,在 media 库的 BLOB 里

原型稿(双稿制):仅供把味定选题,非交付物。本人点头后进素材卡,走 yinliu 产线重写。

一句话答案:微信 4.0(PC)的语音消息音频不落盘成文件,而是整体存在 message/media_N.db 的 VoiceInfo 表 BLOB 字段里;剥掉首字节 0x02 就是标准 silk 音频,解码成 16kHz PCM 后送 ASR 即可转文字。本篇是完整的定位、解码、转写链路实录。

背景:已有管线与缺口

此前的管线(解密系列 #1 的微信 4.0 部分):只读解密本地库——passphrase 内存抓取、AES-256-CBC 逐页解密、WAL 合并成快照,每日增量拉取群消息落成 Markdown。但语音消息在文本管线里只能渲染成 [语音] 占位符:消息本体(local_type=34)的 content 是一段 voicemsg XML,只有元数据没有文字:

<msg><voicemsg voiceformat="4" voicelength="2920" length="5156"
  aeskey="..." voiceurl="..." clientmsgid="..."/></msg>

voicelength 是毫秒时长,length 是音频字节数,voiceurl/aeskey 是 CDN 侧凭证(有时效,不指望它)。要做的功能:把语音消息批量转成文字,按时间线和文本消息混排存档。

侦察第一枪打空:磁盘上没有语音文件

第一反应是找文件。翻遍微信数据目录:

  • msg/ 下只有 attach / file / migrate / video 四类,没有 voice 目录;
  • cache/<YYYY-MM>/Message/<会话md5>/VoiceTemp/ 目录确实存在,但全部为空;
  • 全盘搜 .silk / .amr / .slk,零命中。

结论:微信 4.0 PC 端不把语音音频持久化成独立文件(即使用户播放过,缓存也会被清掉)。磁盘路线证伪。

真正的库:media_N.db 的 VoiceInfo 表

转头扫数据库清单,发现 message/ 下除了 message_0~3.db,还有两个之前没解过的库:media_0.db、media_1.db。表结构极其干净,各只有一张表:

CREATE TABLE VoiceInfo(
  chat_name_id INTEGER,   -- 会话 id(关联 Name2Id)
  create_time INTEGER,    -- 消息时间戳
  local_id    INTEGER,    -- 消息本地 id
  svr_id      INTEGER,    -- 消息服务器 id
  voice_data  BLOB,       -- 音频本体
  data_index  TEXT DEFAULT '0'
);

voice_data 就是整条语音的音频字节,内嵌在数据库里,所以磁盘上才什么都找不到。看首 12 字节 hex:

02 2321 53494C4B5F5633 ...

23 21 53 49 4C 4B 5F 56 33 = #!SILK_V3——微信经典 silk 格式,前面多一个 0x02 前缀字节。格式确认,路线通了。

一个隐蔽的坑:解密清单漏了 media 库

旧快照里 VoiceInfo 也能查到行,但最新数据停在 7 月。排查发现:每日刷新的解密清单 NEEDED_DBS 只含 message_0~3 + contact + session,media 库从来没进过快照——快照里那张表是 7 月某次手动解密留下的尸体。对 media_0/1 跑同一套 decrypt_db_with_wal() 后,最近的语音全部出现。

附带实测结论:media_0 与 media_1 的分片规律和 message_N 没有可推断的固定对应,别猜,两个库都查。

消息与音频的对齐键:svr_id 精确匹配

要把音频挂回消息时间线,需要消息行 ↔ VoiceInfo 行的匹配键。实测三个候选键的可靠性:

  1. Msg.server_id == VoiceInfo.svr_id:精确匹配,测试集 50 条全中(实测),首选;
  2. (local_id, create_time):同分片内有效,兜底;
  3. (create_time, length):voicemsg XML 的 length 属性恰好等于 voice_data 字节数,再兜底。

解码链:silk → PCM → 16kHz

voice_data → 剥 0x02 前缀 → .silk
           → pilk 解码(24000Hz PCM)
           → numpy 线性重采样 → 16000Hz PCM
           → 包 WAV 头(本地引擎用)/ PCM 直送(云端引擎用)

三个实测踩坑:

  • pilk 0.2.4 有 Windows 预编译轮,免编译,但 decode() 只吃文件路径不收 bytes,得先写临时文件;
  • Python 3.13 移除了 audioop,重采样用不了 audioop.ratecv,用 numpy 线性插值顶(16k 语音识别场景,线性插值够用);
  • silk 解出来多为 24kHz,而目标 ASR 要 16kHz,不重采样直接送会识别率血崩。

ASR 双通道:本地 whisper + 豆包 seedasr

通道 A:faster-whisper(本地,隐私不出机)

  • faster-whisper 1.2.1,small 模型(464MB),CPU + int8;
  • 国内拉模型走 HF_ENDPOINT=https://hf-mirror.com;
  • 坑:hf-mirror 默认走 xet 协议,会被 cas-server.xethub.hf.co 401 卡住——设 HF_HUB_DISABLE_XET=1 强制回退普通 HTTP 后下载成功。

通道 B:豆包 seedasr SAUC 大模型(云端)

复用自有语音输入法项目的现成客户端,火山引擎语音识别大模型:

  • WebSocket:wss://openspeech.bytedance.com/api/v3/sauc/bigmodel_nostream;
  • 二进制协议:4 字节头(version/type/flags/序列化/压缩)+ 4 字节序列号 + 4 字节 payload 长度 + gzip(JSON),音频包同理;
  • 认证头:X-Api-Key + X-Api-Resource-Id: volc.seedasr.sauc.duration;
  • 用流式输入模式(bigmodel_nostream):整段 16kHz s16le 单声道 PCM 按 200ms 切片发完,服务端一次性返回整段文本,enable_punc + enable_itn 开标点和智能断句。

实测对比(50 条真实中文语音,情绪激动/语速快场景)

指标 faster-whisper small(本地) 豆包 seedasr(云端)
成功率 50/50(实测) 50/50(实测)
总耗时 152.2s(实测) 71.6s,约 1.4s/条(实测)
典型错误 同音错字、繁体字混入、无标点 三类问题基本消失
隐私 不出本机 音频上传火山云端

结论:日常默认走豆包(快一倍、质量明显好);敏感内容 --asr whisper 一键切本地。

降级与缓存设计:豆包单条失败自动降级 whisper 并在行尾标注;连续 3 次失败整轮切换;转写结果按 <svr_id>.<backend>.txt 分键缓存,二次运行 0.0s 秒回(实测)。降级逻辑用故障注入实测过(真实运行中豆包零失败,未自然触发,如实说明)。

顺带的收获:三类"占位符"消息的真身

做渲染层时把剩下几个占位符也解了:

  • local_type=50 = VoIP 通话记录,content 是 voipmsg XML,CDATA 里直接是人话("已取消"/"通话时长 14:02"/"已在其它设备接听");
  • local_type=10000 = 系统消息(sysmsg XML),撤回是 revokemsg 子类型;
  • 引用回复 = appmsg 里的 refermsg,解析后可渲染成"引用了谁的哪条"。

注意 local_type 高位会带标志位(如 0x60000031 实际是 49),一律 & 0xFFFF 取低 16 位再判型——这是解密系列 #1 就踩过的坑,语音功能又拦了一次。

已知限制(如实交代)

  1. 仅 PC 端已下载的语音有音频。VoiceInfo 只收录 PC 客户端实际拉取过的语音;从没在 PC 上播放/同步过的语音,数据库里也只有元数据。工具对这类消息标注 [语音,音频未下载到本机],不静默丢弃;
  2. passphrase 在微信退出重登后失效,需重新抓取(沿用系列 #1 的流程);
  3. whisper small 在情绪激动、语速快的语音上有错字,追求精度可换 medium(1.5GB,一行改动),或直接用云端通道。

工具用法

# 按备注名反查联系人,回看最近 60 小时,先刷新快照(含 media 库)
python tools/wechat_watch/voice_text.py --name 某联系人 --hours 60 --refresh

# 指定后端 / 输出文件
python tools/wechat_watch/voice_text.py --wxid wxid_xxx --days 2 --asr whisper --out out.md

输出为按时间线混排的 Markdown:文本、语音转写(带秒级时长)、通话记录、撤回、引用,全部可读。代码已入库(git:c69a697)。

素材点

  • "磁盘上翻了个底朝天,音频居然住在数据库的 BLOB 里"——侦察路线证伪再反转,是解密系列的招牌叙事;
  • 快照解密清单漏了 media 库,旧数据停在 7 月伪装成"没数据"——"你以为的不存在,只是你没解密到它";
  • 三个工程坑三连:pilk 不收 bytes、Python 3.13 砍了 audioop、hf-mirror 的 xet 401;
  • 双 ASR 实测对照表(50 条、耗时、错误类型)可直接做对比图;
  • svr_id 一键精确对齐消息与音频,匹配键选型过程可独立成方法块。

分发配套

  • 标题 Top5(S4 准确命名向):
    1. 微信 4.0 本地库解密续篇:语音消息转文字——音频不在磁盘上,在 media 库的 BLOB 里(综合首选:实体+反转点+系列延续)
    2. 微信语音消息本地还原实录:音频原来藏在 VoiceInfo 表里(问题悬念向)
    3. 给微信解密管线加语音转文字:VoiceInfo 定位、silk 解码与双引擎 ASR 全链路(检索向,长尾词密度最高)
    4. 解密系列:微信语音转文字——从 VoiceInfo 到豆包 seedasr 的完整链路(系列向)
    5. 微信 4.0 的语音消息去哪了?磁盘找不到的音频,在数据库里(问题句向)
  • 摘要(86 字):微信 4.0 PC 端语音消息不落盘,音频内嵌在 media 库 VoiceInfo 表 BLOB 中。本文实录定位、silk 解码、16k 重采样与 faster-whisper/豆包双 ASR 通道搭建全过程,附实测对比与踩坑。
  • 分类与话题标签:微信逆向 / 本地数据库 / 语音识别 / Python;#微信解密 #silk #faster-whisper #豆包 #ASR
  • 封面文案(首选同源):音频不在磁盘上,在 media 库的 BLOB 里
野生码农头像

关于作者 · 野生码农

把 AI 真放进生产环境的独立开发者。自研本地知识库、数字员工团队、软考备考系统、量化交易四套系统,全程公开复盘。

看他的平台账号 →
相关

继续阅读