这篇文章主要是对音频数据进行处理的一些初步探索和尝试。这篇文章并不会讲述这些模型的结构和原理。目的是使用这几个模型工具从音频数据中提取出纯净的语音数据用于训练,这里进行了尝试,并展示了结果,可以让大家对这些工具有一个直观的认识,主要尝试的模型包括
WebRTCVAD
,FunASR
,YAMNet
。
在工作中遇到了一个需求,就是想要训练某个特别音色的TTS模型,用于输出语音。虽然cosyvoice
等其他语音模型都提供了音色克隆,但是某些情况下仍然不能满足,所以想要进行训练来满足需求。但是数据从哪里来呢?我初步想到的一个方法就是从现有的影视剧作品中提取音频数据进行训练(商业模型的话需要考虑版权问题哟😊,我这里只是进行技术探索~)。
初步方案
对一段音频进行处理达到可以进行训练的标准,需要质量非常的高,并且标注不同的说话人音色。我这里的初步方案是:
- 对长音频进行分段。识别出不同的说话人。对片段进行杂音去除,或者对片段进行筛选,筛选出没有杂音的片段。最后得到音频、文本、音色(或者角色标注)的数据。
静音分割
直接看代码吧这里不再赘述。
from pydub import AudioSegmentfrom pydub.silence import split_on_silence# 加载音频audio = AudioSegment.from_file("input.wav", format="wav")# 按静音切分(参数需根据音频调整)chunks = split_on_silence( audio, min_silence_len=500, # 最小静音时长(毫秒) silence_thresh=-40, # 静音阈值(dBFS,低于此值视为静音) keep_silence=300 # 每段前后保留的静音时长(毫秒))# 保存分段for i, chunk in enumerate(chunks): chunk.export(f"chunk_{i}.wav", format="wav")
WebRTC VAD
WebRTC VAD(Voice Activity Detection)
也是Google 研发的,是 WebRTC
项目中的核心模块,可用于实时检测音频流中的语音活动。WebRTC VAD
可以进行4种检测模式:0(Normal):通用场景,平衡了灵敏度与误检。1(Low Bitrate):进行了低带宽优化。2(Aggressive)3(Very Aggressive):适合高噪环境,减少漏检但增加了误检的情况。
代码
import numpy as npfrom pydub import AudioSegmentimport webrtcvaddef strict_vad_detection(audio_path, segment_length_ms=500, vad_aggressiveness=1): # 1. 加载音频并转换为16kHz单声道16-bit PCM audio = AudioSegment.from_file(audio_path) audio = audio.set_frame_rate(16000).set_channels(1) samples = np.array(audio.get_array_of_samples(), dtype=np.int16) # 2. 初始化VAD vad = webrtcvad.Vad(vad_aggressiveness) frame_duration_ms = 30 # WebRTCVAD要求10ms/20ms/30ms frame_size = int(16000 * frame_duration_ms / 1000) # 每帧采样数 segments = [] for start_ms in range(0, len(audio), segment_length_ms): end_ms = start_ms + segment_length_ms segment_samples = samples[start_ms*16 : end_ms*16] # 16=16000Hz/1000ms # 3. 逐帧检测,只要有一帧不是人声,整个片段标记为噪声 is_pure_speech = True for i in range(0, len(segment_samples), frame_size): frame = segment_samples[i:i+frame_size] if len(frame) < frame_size: continue # 跳过不完整的最后一帧 frame_bytes = frame.tobytes() if not vad.is_speech(frame_bytes, sample_rate=16000): is_pure_speech = False break # 发现噪声,立即终止判断 segments.append({ "start_ms": start_ms, "end_ms": end_ms, "is_speech": is_pure_speech, # True=纯净人声,False=噪声/音乐 "audio": audio[start_ms:end_ms] }) return segmentssegments = strict_vad_detection("output/chunk_0.wav", segment_length_ms=500)for seg in segments: label = "人声" if seg["is_speech"] else "噪声" print(f"{seg['start_ms']}-{seg['end_ms']}ms: {label}") seg["audio"].export(f"segment_{seg['start_ms']}_{label}.wav", format="wav")
结论
想要通过这种方式检测其中是否包含噪音其实特别的不准确,其实WebRTC VAD
的核心目的是区分语音与非语音或者说静音或噪声,并不是专门识别噪音的类型的。它基于高斯混合模型的检测逻辑,通过子带能量分析判断语音活动,但是噪声与语音通常在频谱上有大量的重叠,最终导致误判~。
FunASR
FunASR(Functional Automatic Speech Recognition)
是由阿里巴巴达摩院开源的多功能语音识别工具包,他能够进行ASR
、VAD
、多人说话检测
。其中VAD
基于FSMN-VAD
模型,精准检测语音的起始和结束。最让我心动的是可以进行多人说话检测,这不就省去了对音色进行标注的麻烦了嘛~~
代码
from funasr import AutoModelfrom pydub import AudioSegmentimport jsonimport os# Initialize model with relative pathsmodel = AutoModel(model="models/speech_paraformer-large-vad-punc_asr_nat-zh-cn-16k-common-vocab8404-pytorch", vad_model="models/speech_fsmn_vad_zh-cn-16k-common-pytorch", punc_model="models/punc_ct-transformer_zh-cn-common-vocab272727-pytorch", spk_model="models/speech_campplus_sv_zh-cn_16k-common")def split_audio_by_sentences(audio_path, output_folder, asr_result): audio = AudioSegment.from_file(audio_path) os.makedirs(output_folder, exist_ok=True) for i, sentence in enumerate(asr_result[0]['sentence_info']): print(sentence) start_ms = sentence['start'] # 开始时间(毫秒) end_ms = sentence['end'] # 结束时间(毫秒) text = sentence['text'].strip().replace(' ', '_')[:50] # 清理文本作为文件名 # 提取音频片段 segment = audio[start_ms:end_ms] # 生成输出文件名 output_path = os.path.join(output_folder, f"sentence_{i+1}_{start_ms}-{end_ms}_{text}.wav") # 保存音频片段 segment.export(output_path, format="wav") print(f"已保存: {output_path} ({len(segment)/1000:.2f}秒)")if __name__ == "__main__": input_file = "01最爱玩跳泥坑游戏.mp3" asr_result = model.generate(input=input_file, batch_size_s=300, hotword='魔搭') split_audio_by_sentences( audio_path=input_file, output_folder="output/funasr_out", asr_result=asr_result )
输出举例
{ "text": "猪妈妈和猪爸爸也穿着他们的靴子。", "start": 245710, "end": 248575, "timestamp": [ [245710, 245950], [246030, 246170], [246170, 246290], [246290, 246430], [246430, 246630], [246630, 246830], [246830, 247070], [247090, 247330], [247370, 247470], [247470, 247670], [247670, 247830], [247830, 247950], [247950, 248050], [248050, 248250], [248250, 248575] ], "spk": 2}
结论
这个工具非常好用,识别的还算准确,对识别结果稍作处理即可应用。后续的工作是,两个片段中的speaker 不能直接对应起来,这方面还需要自己进行匹配。
YAMNet
YAMNet(Yet Another Music Recognition Network)
是由谷歌开发的预训练深度学习模型,专门为音频事件分类设计。它基于MobileNetV1的深度可分离卷积架构,在AudioSet数据集(包含大规模音频-视频数据)上训练,它能够识别521种声音类别(如环境音、音乐、人声等)。512中声音呐,检测一段音频中是否包含杂音应该足够了吧~~
代码
import tensorflow as tfimport tensorflow_hub as hubimport numpy as npimport osimport librosafrom collections import defaultdictdef load_yamnet_model(): """Load and cache the YAMNet model to avoid repeated downloads""" model = hub.load('https://tfhub.dev/google/yamnet/1') # Load class names once class_map_path = model.class_map_path().numpy() with tf.io.gfile.GFile(class_map_path, 'r') as f: class_names = [line.strip() for line in f] return model, class_names# Cache the model and class names at module levelYAMNET_MODEL, CLASS_NAMES = load_yamnet_model()def analyze_audio(waveform, model, class_names): """Run YAMNet model and process results""" scores, _, _ = model(waveform) mean_scores = np.mean(scores.numpy(), axis=0) top_k_indices = np.argsort(mean_scores)[-5:][::-1] # Top-5 predictions return { class_names[i].split('/')[-1].lower(): mean_scores[i] for i in top_k_indices }def detect_non_speech_yamnet(file_path): """Detect non-speech elements in audio file using YAMNet""" try: # Load audio with librosa (mono, 16kHz) waveform, _ = librosa.load(file_path, sr=16000, mono=True) waveform = tf.convert_to_tensor(waveform, dtype=tf.float32) # Analyze audio and get top predictions return analyze_audio(waveform, YAMNET_MODEL, CLASS_NAMES) except Exception as e: print(f"Error processing {file_path}: {str(e)}") return Nonedef process_directory(directory_path, extension=".wav"): """Process all audio files in a directory and return statistics""" stats = defaultdict(float) for filename in os.listdir(directory_path): if not filename.endswith(extension): continue filepath = os.path.join(directory_path, filename) try: result = detect_non_speech_yamnet(filepath) if result: for key, value in result.items(): stats[key] += value except Exception as e: print(f"Fatal error processing {filename}: {str(e)}") return sorted(stats.items(), key=lambda x: x[1], reverse=True)if __name__ == "__main__": # Example usage audio_dir = "output/peggy" statistics = process_directory(audio_dir) print("\nAudio Classification Statistics:") for key, value in statistics: print(f"{key}: {value:.4f}")
输出样例
index,mid,display_name: 6.081626892089844032n05,whale vocalization: 1.020454406738281209x0r,speech: 0.32054355740547180155w,blues: 0.2389743030071258502yds9,purr: 0.199967876076698307qyrcz,plop: 0.14755500853061676dd00136,whimper (dog): 0.14755499362945557016622,tubular bells: 0.1403512209653854407pt_g0,pulse: 0.1246077418327331503q5t,harpsichord: 0.096752308309078220dwsp,"marimba, xylophone": 0.09039849787950516dd00013,children playing: 0.082002848386764530j45pbj,mallet percussion: 0.073054611682891850261r1,babbling: 0.0693439766764640801h8n0,conversation: 0.0417588986456394207phhsh,rumble: 0.030374335125088690239kh,cowbell: 0.0246097389608621607qcx4z,tearing: 0.0195935145020484920ytgt,"child speech, kid speaking": 0.018814150243997574
结论
对于输出的结果,就我自己的观察,并不是特别的准确,但是存在一定的输出模式的规律,后面如果想用这个模型,应该进行后续的处理,比如:
- 筛选,观察一些符合自己要求的模式。进行聚类分析。标注部分数据对模型进行微调等等。
总结
本篇文章介绍了几种现有的模型工具用于处理语音的数据,包括WebRTCVAD
,FunASR
,YAMNet
。这些工具很好,但并不是开箱即用的,可能需要根据自己想要实现的功能不同进行不同程度的后处理或者进行模型的预训练。