Challenge
did I hear that correctly?
我这个听对了吗?
题目提供一个 WAV 音频文件,要求找出隐藏的密码。
Solution
先确认 WAV 的基本参数:
$ soxi 2.wavInput File : '2.wav'Channels : 2Sample Rate : 22050Precision : 16-bitDuration : 00:00:10.00 = 220500 samples ~ 750 CDDA sectorsBit Rate : 706kSample Encoding: 16-bit Signed Integer PCM这是 22050 Hz、16-bit、双声道、10 秒的 PCM 音频。直接播放只能听到噪声;反向播放、交换声道也没有可懂内容。频谱分析显示,异常的时间变化集中在约 1.2–1.6 kHz,说明载荷不在普通听感或 WAV 元数据里,而是画在时频平面上。
命令行直接渲染频谱:
$ sox 2.wav -n spectrogram -o spec.png -z 90为了让字符更清楚,限制频率范围并提高时频分辨率。下面的脚本使用 NFFT=1024、HOP=64,将 0–2000 Hz 的频谱保存为灰度图:
#!/usr/bin/env python3"""Render the spectrogram band that carries the password.
Usage: uv run --with numpy --with pillow python render_spec.py 2.wav spec.png"""import sysimport wave
import numpy as npfrom PIL import Image
NFFT, HOP = 1024, 64FMAX = 2000DYNAMIC_RANGE = 70.0
def load_mono(path): with wave.open(path, "rb") as w: sample_rate = w.getframerate()30 collapsed lines
channels = w.getnchannels() frames = w.readframes(w.getnframes()) pcm = np.frombuffer(frames, dtype=np.int16).astype(np.float64) return sample_rate, pcm.reshape(-1, channels).mean(axis=1)
def render(path): sample_rate, samples = load_mono(path) window = np.hanning(NFFT) freqs = np.fft.rfftfreq(NFFT, 1.0 / sample_rate) keep = int(np.searchsorted(freqs, FMAX)) frame_count = 1 + (len(samples) - NFFT) // HOP magnitude = np.empty((keep, frame_count))
for index in range(frame_count): start = index * HOP frame = samples[start:start + NFFT] * window magnitude[:, index] = np.abs(np.fft.rfft(frame))[:keep]
db = 20.0 * np.log10(magnitude + 1e-9) db -= db.max() image = np.clip((db + DYNAMIC_RANGE) / DYNAMIC_RANGE, 0.0, 1.0) image = (image[::-1] * 255).astype(np.uint8) return image
def main(): source = sys.argv[1] if len(sys.argv) > 1 else "2.wav" target = sys.argv[2] if len(sys.argv) > 2 else "spec.png" image = Image.fromarray(render(source), "L") image = image.resize((image.width, image.height * 4), Image.NEAREST) image.save(target) print("wrote %s (%d x %d, 0-%d Hz)" % (target, image.width, image.height, FMAX))
if __name__ == "__main__": main()$ uv run --with numpy --with pillow python render_spec.py 2.wav spec.pngwrote spec.png (3430 x 372, 0-2000 Hz)jb298abc9qb2