当前位置: 首页 > news >正文

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即可启动了。第一次启动需要授权,可以根据提示进行授权。

相关文章:

  • 【精华】为什么class在前端开发中不常用?
  • 【云原生之kubernetes实战】在k8s环境中高效部署Vikunja任务管理工具(含数据库配置)
  • 【C++】Rusage(一)
  • 广义线性模型下的数据分析(R语言)
  • 当JMeter遇见AI:性能测试进入智能时代(附实战案例)
  • 虚拟仿真无线路由器5G和2.4G发射信号辐射对比(虚拟仿真得出最小安全距离,与国际标准要求一致)
  • 百度 API 教程 006:使用BMapGL.Marker3D绘制带高度的点纹理贴图
  • 市场加速下跌,但监管「坚冰」正在消融
  • 【练习】【贪心】力扣1005. K 次取反后最大化的数组和
  • 可以用于promise面试的例子--其1
  • 基于单片机的机床切屑运输系统设计
  • cv2.solvePnP 报错 求相机位姿
  • 车载电源管理新标杆NCV8460ADR2G 在汽车电子负载开关中的应用
  • 删除idea recent projects 记录
  • springboot项目部署脚本
  • c++11新特性 chrono库
  • yolov8 目标追踪 (源码 +效果图)
  • JS中let和var变量区别
  • LeetCode刷题 -- 29. 两数相除
  • 8、HTTP/1.0和HTTP/1.1的区别【高频】
  • 珠海网站建设找哪家/游戏推广代理平台
  • html5网站建设微信运营公司织梦模板/搜索引擎在线
  • 浙江省建设工程质量协会网站/腾讯云建站
  • b2b网站的特点/怎么建网站卖东西
  • 中国建设银行网站登录不上/网络推广营销网
  • 平面设计教程视频全集免费/网络优化的流程