AI 音视频转录背后的技术链路,从信号处理到结构化笔记的完整实现

AI 音视频转录背后的技术链路,从信号处理到结构化笔记的完整实现

我上周整理了一个三小时的 AI 技术直播,用的工具自动生成了图文笔记和思维导图。我盯着那份笔记看了半天,好奇这背后的技术链路是怎么跑通的。

不是简单地把语音变成文字,而是从声波到结构化知识,中间隔着信号处理、语音识别、自然语言理解、知识图谱好几座山。

音频先变成数字,这一步就有讲究

声音是空气振动,麦克风把振动变成模拟电信号,ADC 芯片再采样成数字。采样率是关键参数,CD 音质 44.1kHz,语音电话 8kHz。采样率越高,高频细节越多,但数据量也越大。

拿到数字音频后,预处理直接影响后续准确率。降噪、去混响、音量归一化,这些操作在安静环境下差别不大,但在咖啡厅录的播客里,预处理能把识别率从 70% 拉到 90% 以上。

我用 FFmpeg 做过一个实验,同一段带噪音频,先做高通滤波去噪再识别,WER(词错误率)降了差不多 8 个百分点。

python 复制代码
import subprocess

def preprocess_audio(input_path, output_path):
    # 高通滤波 + 降噪 + 音量归一化
    cmd = [
        'ffmpeg', '-i', input_path,
        '-af', 'highpass=f=200,afftdn=nf=-25,volume=1.5',
        '-ar', '16000',  # 统一采样率到 16kHz
        '-ac', '1',      # 单声道
        output_path
    ]
    subprocess.run(cmd, check=True)

# 使用
preprocess_audio('noisy_podcast.mp3', 'cleaned.wav')

从声波到特征向量

预处理完的信号还是时域波形,直接扔给神经网络效果不好。需要特征提取,把时域转成频域表示。

最经典的做法是梅尔频率倒谱系数(MFCC)。模拟人耳听觉特性,对低频敏感、对高频迟钝。一段音频切成 20-40 毫秒的帧,每帧提取 13-40 维特征,变成特征向量。

python 复制代码
import librosa
import numpy as np

def extract_mfcc(audio_path, n_mfcc=40):
    y, sr = librosa.load(audio_path, sr=16000)
    
    # 预加重,增强高频
    y = librosa.effects.preemphasis(y)
    
    # 提取 MFCC
    mfccs = librosa.feature.mfcc(
        y=y, sr=sr, n_mfcc=n_mfcc,
        n_fft=512, hop_length=160
    )
    
    # 添加一阶和二阶差分,捕捉动态特征
    delta = librosa.feature.delta(mfccs)
    delta2 = librosa.feature.delta(mfccs, order=2)
    
    return np.vstack([mfccs, delta, delta2])

# 使用
features = extract_mfcc('cleaned.wav')
# 输出 shape: (120, 时间帧数),40维MFCC + 40维一阶差分 + 40维二阶差分

但现在更流行的做法是让神经网络自己学特征。wav2vec 2.0、HuBERT 这些模型直接拿原始波形或梅尔频谱图当输入,效果比手工设计的 MFCC 好一截。

声学模型,从 HMM 到 Transformer

特征向量怎么变成文字?靠声学模型。

早期用隐马尔可夫模型(HMM),把语音看作状态序列,每个状态对应音素。HMM 需要大量人工设计,建模能力有限,噪声场景下基本崩盘。

2012 年后 DNN-HMM 混合系统成了主流,深度神经网络替代高斯混合模型,负责把特征映射到音素概率。

再往后,端到端模型彻底改变游戏规则。Listen-Attend-Spell、Transformer、Conformer 直接从音频学到文字,不需要中间音素表示。

python 复制代码
import torch
import torch.nn as nn

class ConformerBlock(nn.Module):
    def __init__(self, dim, num_heads, d_ff, kernel_size=31):
        super().__init__()
        # 前馈网络
        self.ffn1 = nn.Sequential(
            nn.Linear(dim, d_ff),
            nn.SiLU(),
            nn.Dropout(0.1),
            nn.Linear(d_ff, dim)
        )
        
        # 多头自注意力
        self.attention = nn.MultiheadAttention(dim, num_heads, batch_first=True)
        self.norm1 = nn.LayerNorm(dim)
        
        # 卷积模块
        self.conv = nn.Sequential(
            nn.LayerNorm(dim),
            nn.Conv1d(dim, dim, kernel_size, padding=kernel_size//2, groups=dim),
            nn.BatchNorm1d(dim),
            nn.SiLU(),
            nn.Conv1d(dim, dim, 1),
            nn.BatchNorm1d(dim),
            nn.SiLU()
        )
        
        # 第二个前馈网络
        self.ffn2 = nn.Sequential(
            nn.Linear(dim, d_ff),
            nn.SiLU(),
            nn.Dropout(0.1),
            nn.Linear(d_ff, dim)
        )
        self.norm2 = nn.LayerNorm(dim)
    
    def forward(self, x):
        # 前馈 + 残差
        x = x + 0.5 * self.ffn1(x)
        
        # 注意力 + 残差
        attn_out, _ = self.attention(x, x, x)
        x = self.norm1(x + attn_out)
        
        # 卷积 + 残差
        conv_out = self.conv(x.transpose(1, 2)).transpose(1, 2)
        x = x + conv_out
        
        # 第二个前馈 + 残差
        x = x + 0.5 * self.ffn2(x)
        x = self.norm2(x)
        
        return x

Conformer 把 CNN 的局部特征提取和 Transformer 的全局注意力结合,在 LibriSpeech 上 WER 干到了 2% 以下。

语言模型,让句子通顺

声学模型输出单个字的概率,但语音识别是猜句子,不是猜字。语言模型给通顺句子打高分,不通顺的打低分。

传统 n-gram 统计模型看前面几个字预测下一个。现在大语言模型接棒,GPT、BERT 对上下文理解更深,能纠正声学模型的错误。

python 复制代码
from transformers import BertTokenizer, BertForMaskedLM
import torch

class RescoringLM:
    def __init__(self):
        self.tokenizer = BertTokenizer.from_pretrained('bert-base-chinese')
        self.model = BertForMaskedLM.from_pretrained('bert-base-chinese')
        self.model.eval()
    
    def score(self, text):
        # 计算句子的困惑度
        inputs = self.tokenizer(text, return_tensors='pt')
        with torch.no_grad():
            outputs = self.model(**inputs)
            logits = outputs.logits
        
        # 计算交叉熵损失作为分数
        labels = inputs['input_ids']
        loss = torch.nn.functional.cross_entropy(
            logits.view(-1, logits.size(-1)),
            labels.view(-1),
            reduction='mean'
        )
        return -loss.item()  # 分数越高越好
    
    def rescore(self, candidates):
        # 对多个候选句子重新打分
        scores = [(c, self.score(c)) for c in candidates]
        return sorted(scores, key=lambda x: x[1], reverse=True)

# 使用
lm = RescoringLM()
candidates = ["深度学习是机器学习的分支", "深度学习是机器学习的分子"]
best = lm.rescore(candidates)
# 输出: [('深度学习是机器学习的分支', -2.3), ('深度学习是机器学习的分子', -5.1)]

说话人识别,多人场景的噩梦

会议录音、播客访谈、课堂讨论,多个说话人。单纯转文字不够,得知道哪句话是谁说的。

说话人识别分两个层面。一是分割,把音频切成不同说话人的片段。二是聚类,把同一个人的片段归到一起。

python 复制代码
from speechbrain.pretrained import EncoderClassifier
import torch
import numpy as np
from sklearn.cluster import AgglomerativeClustering

class SpeakerDiarizer:
    def __init__(self):
        # 加载预训练的说话人嵌入模型
        self.encoder = EncoderClassifier.from_hparams(
            source="speechbrain/ecapa-tdnn"
        )
    
    def extract_embeddings(self, audio_path, segments):
        # 提取每个语音片段的嵌入向量
        embeddings = []
        for start, end in segments:
            # 切分音频
            waveform, sr = torchaudio.load(audio_path)
            start_sample = int(start * sr)
            end_sample = int(end * sr)
            segment = waveform[:, start_sample:end_sample]
            
            # 提取嵌入
            emb = self.encoder.encode_batch(segment)
            embeddings.append(emb.squeeze().numpy())
        
        return np.array(embeddings)
    
    def cluster(self, embeddings, num_speakers=None):
        # 层次聚类
        if num_speakers:
            clusterer = AgglomerativeClustering(n_clusters=num_speakers)
        else:
            # 自动决定聚类数量
            clusterer = AgglomerativeClustering(
                n_clusters=None,
                distance_threshold=0.5
            )
        
        labels = clusterer.fit_predict(embeddings)
        return labels

# 使用
diarizer = SpeakerDiarizer()
segments = [(0, 10), (10, 25), (25, 40)]  # 语音片段时间戳
embeddings = diarizer.extract_embeddings('meeting.wav', segments)
speaker_labels = diarizer.cluster(embeddings)
# 输出: [0, 1, 0] 表示第1、3段是说话人A,第2段是说话人B

实际工程中这个环节最容易出问题。两个人声音相似、有人说话断断续续、背景噪音干扰,都会导致聚类错误。我试过一个三小时会议录音,最后发现两个说话人被误判成一个人,手动修正花了半小时。

从语音到笔记,最后一公里

语音识别把音频变成文字,但用户要的是笔记,不是文字稿。

这需要 NLP 技术介入。文本摘要提取核心观点,命名实体识别找关键人名地名,关系抽取梳理逻辑结构。思维导图生成需要理解文本层级关系,AI 播客需要把文字改写成对话体。

python 复制代码
from transformers import pipeline

class NoteGenerator:
    def __init__(self):
        self.summarizer = pipeline("summarization", model="facebook/bart-large-cnn")
        self.ner = pipeline("ner", model="dslim/bert-base-NER")
        self.classifier = pipeline("zero-shot-classification")
    
    def generate_summary(self, text, max_length=150):
        # 生成摘要
        summary = self.summarizer(
            text, 
            max_length=max_length,
            min_length=30,
            do_sample=False
        )
        return summary[0]['summary_text']
    
    def extract_entities(self, text):
        # 提取命名实体
        entities = self.ner(text)
        return {
            'persons': [e['word'] for e in entities if e['entity'] == 'B-PER'],
            'organizations': [e['word'] for e in entities if e['entity'] == 'B-ORG'],
            'locations': [e['word'] for e in entities if e['entity'] == 'B-LOC']
        }
    
    def classify_structure(self, text):
        # 零样本分类,判断文本结构
        candidate_labels = ['背景介绍', '核心观点', '案例分析', '总结结论']
        result = self.classifier(text, candidate_labels)
        return result['labels'][0]  # 返回最可能的类别

# 使用
generator = NoteGenerator()
text = "深度学习是机器学习的一个分支,它基于神经网络架构..."
summary = generator.generate_summary(text)
entities = generator.extract_entities(text)
structure = generator.classify_structure(text)

现在的做法是先让大语言模型做理解和总结,再按需生成不同格式。比如把技术演讲转成笔记,模型先提取「问题-方案-实验-结论」的结构,再填充每个部分的关键细节。

实际落地的坑

长音频的内存和计算是挑战。两小时视频按 10 毫秒一帧处理,特征序列长度超过 72 万。直接扔给 Transformer,显存扛不住。工程上一般用滑动窗口或层级处理,先切成段落再分别识别。

专业术语识别也是老大难。医学、法律、金融领域有大量专有名词,通用模型没见过,识别出来全是谐音字。解决办法是用领域语料做微调,或者运行时动态注入热词。

多语言混合更麻烦。中文演讲里突然冒出英文术语,模型经常切换不过来。现在的做法是用语言识别模块先判断语种,再路由到对应识别模型,但切换点判断本身不太准。

这类工具的工程实践

市面上做音视频转笔记的产品,底层架构大同小异。前端负责音视频解析和上传,后端做语音识别、NLP 处理、结果生成。

比如我平时用的 Ai 好记 ,就是基于这套思路。

它把 B 站、抖音、小宇宙这些平台的链接直接解析,后端做语音提取和识别,然后生成图文笔记、思维导图、精华速览。

多人对话场景下能区分说话人,带时间戳标记。

专业领域比如医学、金融,用专门语料库训练过,无字幕视频也能识别得比较准。

跟纯语音识别工具比,这类产品多了 NLP 层和生成层。不是只给文字稿,而是直接给结构化笔记。对学习效率的提升,我觉得还是挺明显的。

FAQ

Q,语音识别准确率能做到多少?

A,安静环境下普通话可以达到 95% 以上,带口音或噪声场景会下降。专业术语密集的领域需要专门优化。

Q,长视频怎么处理?

A,一般分段处理再合并。技术上用滑动窗口或层级注意力,工程上限制单次处理时长。

Q,多语言视频能识别吗?

A,可以,但需要语言识别模块先判断语种。中英混合场景切换准确率还在提升。

Q,说话人识别准确率怎么样?

A,两人差异明显时不错,声音相似或噪音大时容易出错。目前还是辅助功能,不能完全替代人工核对。