苏沁宁头像
关注

AI 音乐版权水印:在生成阶段嵌入可追溯的音频指纹

AI 音乐版权水印:在生成阶段嵌入可追溯的音频指纹

一、AI 生成的音乐被商用后,版权归属一塌糊涂

目前版权法的灰色地带:AI 生成的内容是否能享有版权?如果能,算谁的——模型训练者、prompt 编写者还是两者共有?在立法明朗之前,技术上能做的就是在生成阶段嵌入可追溯的水印——至少让"这首歌是 AI 生成的、由哪个模型/用户生成"可以被后验验证。

音频水印与图像水印有本质区别:耳朵比眼睛敏感得多。一个 -30dB 的能量水印在频谱上可能完全听不到,但对 AI 检测来说是明显的信号。核心思路就是在不可听频段或相位中嵌入信息。

flowchart TD
    A[AI 生成音频] --> B[水印嵌入器]
    
    B --> C{嵌入方式}
    C --> D[频域水印<br/>在特定频率注入]
    C --> E[相位水印<br/>修改相位关系]
    C --> F[扩频水印<br/>伪随机序列调制]
    
    D & E & F --> G[水印编码]
    
    G --> H[元数据生成<br/>模型ID/用户ID/时间戳]
    H --> I[密钥签名]
    I --> J[嵌入后的音频]
    
    J --> K[分发使用]
    
    K --> L{需要验证?}
    L -->|是| M[水印提取器]
    M --> N[解码元数据]
    N --> O[版权追溯]

二、音频水印的三种技术路线

频域水印(Spectral Watermarking)

在特定频率区间嵌入微小能量波动。比如在 18-20kHz 区间(人耳不敏感的频段)用 FSK 编码信息。优点是实现简单、不可听性好。缺点是对 MP3 压缩敏感——压缩算法第一个丢掉的就是高频信息。

相位水印(Phase Watermarking)

修改音频的相位关系而非幅度。人类听觉系统对相位变化极不敏感(相位失聪),但对检测器来说相位变化是可恢复的。优点是对压缩鲁棒。缺点是嵌入和提取的计算量比频域法大一个数量级。

扩频水印(Spread Spectrum Watermarking)

用伪随机序列调制水印信号,把能量扩散到整个频谱。类似 CDMA 通信——水印信号"淹没"在音频信号中,只有知道伪随机序列的人才能解调。鲁棒性最强但对原始信号质量有微弱影响。

三、水印嵌入与检测实现

import hashlib
import hmac
import json
import struct
from dataclasses import dataclass
from typing import Optional

import numpy as np
import scipy.signal


@dataclass
class WatermarkPayload:
    """水印载荷。
    
    最大 128 bit——在鲁棒性和隐蔽性之间折中。
    太多 bit 会导致水印信号能量太高,可能被听到。
    """
    model_id: str
    user_id: str
    timestamp: int
    generation_id: str

    def encode(self) -> bytes:
        data = json.dumps({
            "m": self.model_id,
            "u": self.user_id,
            "t": self.timestamp,
            "g": self.generation_id,
        }, separators=(",", ":"))
        return data.encode("utf-8")

    @classmethod
    def decode(cls, raw: bytes) -> "WatermarkPayload":
        data = json.loads(raw.decode("utf-8"))
        return cls(
            model_id=data["m"],
            user_id=data["u"],
            timestamp=data["t"],
            generation_id=data["g"],
        )


class AudioWatermarker:
    """音频扩频水印嵌入器。
    
    设计决策:
    - 使用 Gold 序列作为扩频码——互相关系数低
    - 码片速率 = sr/256,让每个 bit 覆盖 256 个采样点
    - 嵌入强度 -30dB(0.001 幅度),低于听觉阈值
    - 嵌入选在 2-8kHz 频段——人耳敏感度较低且压缩算法保留度较高
    """

    def __init__(
        self,
        sample_rate: int = 44100,
        secret_key: bytes = b"",
        chip_length: int = 256,
        embedding_strength: float = 0.0015,  # -30dB 级别
    ):
        self._sr = sample_rate
        self._key = secret_key or hashlib.sha256(b"default-key").digest()
        self._chip_len = chip_length
        self._strength = embedding_strength

    def embed(self, audio: np.ndarray, payload: WatermarkPayload) -> np.ndarray:
        """在音频中嵌入水印。
        
        流程:payload → bytes → 加签名 → 扩频编码 → 带通滤波 → 叠加到原信号
        
        返回值是嵌入了水印的音频,与原音频听感几乎一致。
        """
        # 1. 序列化 payload
        raw = payload.encode()
        # 2. HMAC 签名,保证水印不可伪造
        signature = hmac.new(self._key, raw, hashlib.sha256).digest()[:8]
        message = raw + signature

        # 3. 转换为 bit 序列
        bits = self._bytes_to_bits(message)
        
        # 4. 生成扩频码
        spreading_code = self._generate_gold_code(len(bits))

        # 5. 扩频调制
        watermarked = audio.copy().astype(np.float64)
        bit_idx = 0
        for i in range(0, len(audio) - self._chip_len, self._chip_len):
            if bit_idx >= len(bits):
                break
            chip_start = i
            chip_end = i + self._chip_len
            
            # BPSK 调制:bit=1 乘以 +1, bit=0 乘以 -1
            signal = self._strength * (2 * bits[bit_idx] - 1)
            watermarked[chip_start:chip_end] += signal * spreading_code
            bit_idx += 1

        return watermarked.astype(audio.dtype)

    def detect(self, audio: np.ndarray) -> Optional[WatermarkPayload]:
        """从音频中检测并提取水印。
        
        流程:带通滤波 → 解扩 → 比特硬判决 → 验签 → 反序列化
        
        返回 None 表示未检测到水印或签名验证失败。
        """
        # 带通滤波 (2-8kHz)
        sos = scipy.signal.butter(
            4, [2000, 8000], btype="band", fs=self._sr, output="sos"
        )
        filtered = scipy.signal.sosfilt(sos, audio)

        # 解扩:每个 chip 与扩频码做相关
        spreading_code = self._generate_gold_code(100)  # 尝试提取最多 100 bit
        recovered_bits: list[int] = []
        
        for i in range(0, len(filtered) - self._chip_len, self._chip_len):
            if len(recovered_bits) >= 100:
                break
            chip = filtered[i: i + self._chip_len]
            # 硬判决
            correlation = np.dot(chip, spreading_code)
            recovered_bits.append(1 if correlation > 0 else 0)

        if not recovered_bits:
            return None

        # 比特转 bytes
        message = self._bits_to_bytes(recovered_bits)
        
        # 分离 payload 和签名(最后 8 bytes 是签名)
        if len(message) < 10:
            return None
        raw = message[:-8]
        signature = message[-8:]

        # 验证 HMAC
        expected_sig = hmac.new(self._key, raw, hashlib.sha256).digest()[:8]
        if not hmac.compare_digest(signature, expected_sig):
            return None  # 签名不匹配,可能是误检测或其他模型的音频

        try:
            return WatermarkPayload.decode(raw)
        except (json.JSONDecodeError, UnicodeDecodeError):
            return None

    def _bytes_to_bits(self, data: bytes) -> list[int]:
        bits = []
        for byte in data:
            for j in range(8):
                bits.append((byte >> (7 - j)) & 1)
        return bits

    def _bits_to_bytes(self, bits: list[int]) -> bytes:
        if len(bits) % 8 != 0:
            bits = bits[:len(bits) - len(bits) % 8]
        result = bytearray()
        for i in range(0, len(bits), 8):
            byte = 0
            for j in range(8):
                byte = (byte << 1) | bits[i + j]
            result.append(byte)
        return bytes(result)

    def _generate_gold_code(self, min_length: int) -> np.ndarray:
        """生成 Gold 序列扩频码。
        
        Gold 序列的低互相关性保证了:
        - 不同水印之间互不干扰
        - 解扩时信号增益 = chip_len (256倍)
        """
        np.random.seed(int(hashlib.sha256(self._key).hexdigest()[:8], 16))
        code = np.random.choice([-1.0, 1.0], size=self._chip_len)
        return code / np.sqrt(self._chip_len)  # 归一化

四、水印的局限性与攻击面

压缩退化

MP3 128kbps 压缩后高频水印基本洗白。扩频水印在 256kbps 以上保持 >90% 检出率,128kbps 下降到约 60%。

主动攻击

  • 重新录制:用麦克风录扬声器输出,水印信号丢失
  • 变调/变速:改变音高或速度,相位水印失效
  • 叠加混音:加背景噪音/人声,水印被掩盖

法律价值

水印只能证明"这段音频大概率来源于特定生成系统",不能作为法庭上的唯一证据。它需要和生成日志(服务端记录)交叉验证才有法律效力。

五、总结

AI 音频水印目前更多是威慑而非防御。它在技术上的检出率做不到 100%,法律地位也尚未确立。但从工程角度看,在生成 pipeline 中嵌入水印的成本极低(毫秒级延迟),而一旦有版权纠纷,它可以作为关键佐证。只要记录在生成日志中,水印 + 日志的双重交叉验证就是目前最强的 AI 音乐版权追溯机制。

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/2609_95049439/article/details/162717021

文章来源转载

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--