ai说话人分离 | 基于语音大模型进行说话人拆分
0. 研究背景
我们在处理一段长音频时包含了多个人的声音,我们想要提取其中某个人的声音,那么我们该如何办呢?
如果你会音频处理软件,比如AU,那么你可以使用它来处理,但是这要人工处理几条音频还能接受,如果是处理成千上万条音频呢,我们就必须要借助计算机来处理了。那么本篇文章主要记录我开发的一款可以根据音频中不同的说话人的声音来切分音频片段的软件。
1. 软件开发详情
本软件使用python开发,最终通过pyinstaller打包成exe可执行程序。
1.1 统一音频编码
本软件中使用到了语音识别模型和声纹对比模型和vad模型以及标点符号预测模型,能够对传入的任意采样率的音频和通用格式的音频进行归一处理为pcm_s16le,单声道,16bit的wav格式的音频。
统一处理音频代码如下:
audio_bytes, _ = (
ffmpeg.input(audio, threads=0, hwaccel='cuda')
.output("-", format="wav", acodec="pcm_s16le", ac=1, ar=16000)
.run(cmd=["ffmpeg", "-nostdin"], capture_stdout=True, capture_stderr=True)
)
并且这里为了加速转码调用了cuda,也就是英伟达的显卡来转码,而不是CPU,这样转码的速度会提升不少。
注意这里需要你安装好ffmpeg,并且需要配置好环境变量,同时还需要你安装ffmpeg-python依赖。
1.2 模型加载
这里使用到了四个模型,都是来自于阿里modelscope中的开源模型,分别有支持热词的ASR模型speech_seaco_paraformer_large_asr_nat-zh-cn-16k-common-vocab8404-pytorch
,声纹对比模型speech_campplus_sv_zh-cn_16k-common
,vad模型speech_fsmn_vad_zh-cn-16k-common-pytorch
,以及标点符号预测模型punc_ct-transformer_zh-cn-common-vocab272727-pytorch
。
如何加载,代码如下:
home_directory = os.path.expanduser("~")
asr_model_path = os.path.join(home_directory, ".cache", "modelscope", "hub", "models", "iic", "speech_seaco_paraformer_large_asr_nat-zh-cn-16k-common-vocab8404-pytorch")
asr_model_revision = "v2.0.4"
vad_model_path = os.path.join(home_directory, ".cache", "modelscope", "hub", "models", "iic", "speech_fsmn_vad_zh-cn-16k-common-pytorch")
vad_model_revision = "v2.0.4"
punc_model_path = os.path.join(home_directory, ".cache", "modelscope", "hub", "models", "iic", "punc_ct-transformer_zh-cn-common-vocab272727-pytorch")
punc_model_revision = "v2.0.4"
spk_model_path = os.path.join(home_directory, ".cache", "modelscope", "hub", "models", "iic", "speech_campplus_sv_zh-cn_16k-common")
spk_model_revision = "v2.0.4"
ngpu = 1
device = "cuda"
ncpu = 4
# ASR 模型
model = AutoModel(model=asr_model_path,
model_revision=asr_model_revision,
vad_model=vad_model_path,
vad_model_revision=vad_model_revision,
punc_model=punc_model_path,
punc_model_revision=punc_model_revision,
spk_model=spk_model_path,
spk_model_revision=spk_model_revision,
ngpu=ngpu,
ncpu=ncpu,
device=device,
disable_pbar=True,
disable_log=True,
disable_update=True
)
res = model.generate(input=audio_bytes, batch_size_s=300, is_final=True, sentence_timestamp=True)
rec_result = res[0]
上面rec_result就是这四个模型综合返回的结果。我们如果获取里面的内容呢?可以看下面代码:
asr_result_text = rec_result['text']
if asr_result_text != '':
sentences = []
for sentence in rec_result["sentence_info"]:
start = to_date(sentence["start"])
end = to_date(sentence["end"])
if sentences and sentence["spk"] == sentences[-1]["spk"] and len(sentences[-1]["text"]) < int(split_number.get()):
sentences[-1]["text"] += "" + sentence["text"]
sentences[-1]["end"] = end
else:
sentences.append(
{"text": sentence["text"], "start": start, "end": end, "spk": sentence["spk"]}
)
1.3 音频拆分
我们可以拿到每个人讲的每句话的内容和时间戳,那么我们就能够根据时间戳来截取音频片段,通spk来把截取的音频片段保存到不同的目录中,从而得到了音频拆分的作用,具体代码如下:
# 剪切音频或视频片段
i = 0
for stn in sentences:
stn_txt = stn['text']
start = stn['start']
end = stn['end']
# tmp_start = to_milliseconds(start)
# tmp_end = to_milliseconds(end)
# duration = round((tmp_end - tmp_start) / 1000, 3)
spk = stn['spk']
# 根据文件名和 spk 创建目录
date = datetime.now().strftime("%Y-%m-%d")
final_save_path = os.path.join(save_path.get(), date, audio_name, str(spk))
os.makedirs(final_save_path, exist_ok=True)
# 获取音视频后缀
file_ext = os.path.splitext(audio)[-1]
final_save_file = os.path.join(final_save_path, str(i)+file_ext)
spk_txt_path = os.path.join(save_path.get(), date, audio_name)
spk_txt_file = os.path.join(spk_txt_path, f'spk{spk}.txt')
spk_txt_queue.put({'spk_txt_file': spk_txt_file, 'spk_txt': stn_txt, 'start': start, 'end': end})
i += 1
try:
if file_ext in support_audio_format:
(
ffmpeg.input(audio, threads=0, ss=start, to=end, hwaccel='cuda')
.output(final_save_file)
.run(cmd=["ffmpeg", "-nostdin"], overwrite_output=True, capture_stdout=True,
capture_stderr=True)
)
elif file_ext in support_video_format:
final_save_file = os.path.join(final_save_path, str(i)+'.mp4')
(
ffmpeg.input(audio, threads=0, ss=start, to=end, hwaccel='cuda')
.output(final_save_file, vcodec='libx264', crf=23, acodec='aac', ab='128k')
.run(cmd=["ffmpeg", "-nostdin"], overwrite_output=True, capture_stdout=True,
capture_stderr=True)
)
else:
print(f'{audio}不支持')
except ffmpeg.Error as e:
print(f"剪切音频发生错误,错误信息:{e}")
# 记录说话人和对应的音频片段,用于合并音频片段
if spk not in speaker_audios:
speaker_audios[spk] = [] # 列表中存储音频片段
speaker_audios[spk].append({'file': final_save_file, 'audio_name': audio_name})
ret = {"text": asr_result_text, "sentences": sentences}
print(f'{audio} 切分完成')
1.4 相同说话人合并音频
到此我们的核心功能就开发好了,此外,在后面我又添加了一个合并功能,把相同说话人讲的音频片段合并到一个音频文件中,具体代码如下:
def audio_concat_worker():
while True:
speaker_audios_tmp = audio_concat_queue.get()
for spk, audio_segments in speaker_audios_tmp.items():
# 合并每个说话人的音频片段
audio_name = audio_segments[0]['audio_name']
output_file = os.path.join(save_path.get(), datetime.now().strftime("%Y-%m-%d"), audio_name, f"{spk}.mp3")
os.makedirs(os.path.dirname(output_file), exist_ok=True)
inputs = [seg['file'] for seg in audio_segments]
concat_audio = AudioSegment.from_file(inputs[0])
for i in range(1, len(inputs)):
concat_audio = concat_audio + AudioSegment.from_file(inputs[i])
concat_audio.export(output_file, format="mp3")
print(f"已将 {spk} 的音频合并到 {output_file}")
audio_concat_queue.task_done()
到这里就开发完成了,具体效果可以看下面的视频演示。
2. 打包软件为可执行程序
使用python编写的软件需要在安装了python环境的电脑上跑,为了方便,这里使用pyinstaller打包为windows中可以直接双击运行的exe程序。使用下面命令打包。
pyinstaller --collect-data funasr app.py
上面命令中为啥要添加–collect-data funasr呢?
是因为发现在打包的时候无法把funasr打包进去,这个为啥,我也不清楚,即使通过这个参数指定要把funasr打包进去,也只是把version.txt文件打包进去,其它文件还是需要手动复制进去。
打包好的软件已经上传到我的网盘中,下面为网盘链接。
通过网盘分享的文件:模型文件.zip等3个文件
链接: https://pan.baidu.com/s/1yL5UPNgYTwUuaq3owhPDdQ?pwd=vy73 提取码: vy73
下载后把app.zip解压出来,然后把模型文件解压到你C盘中"用户"目录下的.cache/modelscope/hub/models/iic,如果不存在这个目录,那么自己手动创建,然后把ffmpeg.zip解压出来,把bin目录中的那三个exe文件放到app.exe目录中,最后双击app.exe即可启动了。第一次启动需要授权,可以根据提示进行授权。