數據集基礎信息
數據集地址:ASVspoof 2019_LA數據集
數據集描述:
ASVspoof 2019語料庫的LA數據集中的語音數據全部是基於VCTK語料庫得到的,VCTK語料庫采集了包含46名男性和61名女性在內的107名說話人的真實語音,所有的真實語音均采用了相同的錄音配置,且沒有信道和背景噪聲的干擾。LA數據集中的真實語音均直接選取自VCTK語料庫,而數據集中欺詐語音則均是由這些真實語音使用不同的語音合成和語音轉換技術得到的,所有語音數據的采樣率均為16kHz。
數據集中的數據被分成了三個子集, 分別為訓練集 (Train)、 開發集(Development)和測試集(Evaluation)。 其中,訓練集和開發集中的欺詐語音均來源於6種相同的語音合成和語音轉換技術,這6種技術作為已知的攻擊類型,可以用來對合成語音檢測系統進行訓練和調整,而測試集中欺詐語音的生成方法包含了2種上述的已知攻擊和11種與6種已知攻擊不同的語音合成和語音轉換技術,這11種技術作為系統面臨的未知攻擊類型。
ASVspoof 2019_LA數據集描述及圖表均來自論文【劉暢. 關於自動說話人確認系統的反語音合成欺騙攻擊的研究[D].天津大學,2020.DOI:10.27356/d.cnki.gtjdu.2020.002592.】
數據集處理
該數據集的音頻文件均為flac格式,且沒有文本標注。基於自身研究需要,需要語音文本來對提取音素。現有的音頻處理接口基本均支持wav格式音頻,為了研究方便,需要對該數據集進行以下處理:
1.格式轉換。flac格式轉wav格式
2.語音識別。由語音得到文本
格式轉換
flac格式是wav格式的一種無損壓縮格式,它通過編碼壓縮的方式,可以達到節省空間的目的。此外,FLAC格式具有較好的抗損性,其壓縮后每一幀都是不相關的,在壓縮或傳輸過程中出
現損壞時,受影響的內容只有特定的損壞幀,而不會影響前后幀的內容。將flac格式轉換為wav格式基本不會對丟失原始音頻的信息。
使用pydub庫實現對數據集的批量轉換,轉換后的文件存放在wav目錄中,實現效果如下:
from pydub import AudioSegment
import os
def trans_flac_to_wav(outputdir,filepath,hz):
"""
flac格式轉為wav格式
"""
audio = AudioSegment.from_file(filepath)
isExists = os.path.exists(outputdir) # 檢查目錄是否存在
if not isExists:
os.mkdir(outputdir) # 不存在,新建目錄
filename = os.path.basename(filepath).split('.')[0]
audio.export(outputdir + filename +"."+str(hz), format=str(hz))
path=r"/Users/zhangxiao/Downloads/LA/ASVspoof2019_LA_train/flac/"
for fileName in os.listdir(path):
if os.path.splitext(fileName)[1] == '.flac':
savedir = "/Users/zhangxiao/Downloads/LA/ASVspoof2019_LA_train/wav/"
trans_flac_to_wav(savedir, path+fileName, "wav")
語音識別
數據集的語料是英文語料,語音識別使用了百度的API和科大訊飛的API進行了對比,兩種API均可以查閱官方文檔來實現。百度API的調用較科大訊飛API更簡單,在10個文件的識別結果來看,科大訊飛API的識別效果好於百度API的識別效果,我選擇使用科大訊飛的語音識別API來對數據集進行識別。
百度短語音識別API:https://cloud.baidu.com/doc/SPEECH/s/Vk38lxily
科大訊飛語音識別API:https://www.xfyun.cn/doc/asr/voicedictation/API.html#接口說明
import websocket
import datetime
import hashlib
import base64
import hmac
import json
from urllib.parse import urlencode
import time
import ssl
from wsgiref.handlers import format_date_time
from datetime import datetime
from time import mktime
import _thread as thread
import os
STATUS_FIRST_FRAME = 0 # 第一幀的標識
STATUS_CONTINUE_FRAME = 1 # 中間幀標識
STATUS_LAST_FRAME = 2 # 最后一幀的標識
class Ws_Param(object):
# 初始化
def __init__(self, APPID, APIKey, APISecret, AudioFile):
self.APPID = APPID
self.APIKey = APIKey
self.APISecret = APISecret
self.AudioFile = AudioFile
# 公共參數(common)
self.CommonArgs = {"app_id": self.APPID}
# 業務參數(business),更多個性化參數可在官網查看,zh_cn,en_us
self.BusinessArgs = {"domain": "iat", "language": "en_us", "accent": "mandarin", "vinfo":1,"vad_eos":10000}
# 生成url
def create_url(self):
url = 'wss://ws-api.xfyun.cn/v2/iat'
# 生成RFC1123格式的時間戳
now = datetime.now()
date = format_date_time(mktime(now.timetuple()))
# 拼接字符串
signature_origin = "host: " + "ws-api.xfyun.cn" + "\n"
signature_origin += "date: " + date + "\n"
signature_origin += "GET " + "/v2/iat " + "HTTP/1.1"
# 進行hmac-sha256進行加密
signature_sha = hmac.new(self.APISecret.encode('utf-8'), signature_origin.encode('utf-8'),
digestmod=hashlib.sha256).digest()
signature_sha = base64.b64encode(signature_sha).decode(encoding='utf-8')
authorization_origin = "api_key=\"%s\", algorithm=\"%s\", headers=\"%s\", signature=\"%s\"" % (
self.APIKey, "hmac-sha256", "host date request-line", signature_sha)
authorization = base64.b64encode(authorization_origin.encode('utf-8')).decode(encoding='utf-8')
# 將請求的鑒權參數組合為字典
v = {
"authorization": authorization,
"date": date,
"host": "ws-api.xfyun.cn"
}
# 拼接鑒權參數,生成url
url = url + '?' + urlencode(v)
# print("date: ",date)
# print("v: ",v)
# 此處打印出建立連接時候的url,參考本demo的時候可取消上方打印的注釋,比對相同參數時生成的url與自己代碼生成的url是否一致
# print('websocket url :', url)
return url
# 收到websocket消息的處理
def on_message(ws, message):
try:
code = json.loads(message)["code"]
sid = json.loads(message)["sid"]
if code != 0:
errMsg = json.loads(message)["message"]
print("sid:%s call error:%s code is:%s" % (sid, errMsg, code))
else:
data = json.loads(message)["data"]["result"]["ws"]
# print(json.loads(message))
result = ""
for i in data:
for w in i["cw"]:
result += w["w"]
############文本寫入#################
resultName = "spoof_result.txt"
with open(resultName,'a') as f: #設置文件對象
if(result == "." or result =="?"):
f.write(result)
f.write("\n")
else:
f.write(fileName + ' ')
f.write(result) #將結果寫入文件中
# print("sid:%s call success!,data is:%s" % (sid, json.dumps(data, ensure_ascii=False)))
except Exception as e:
print("receive msg,but parse exception:", e)
# 收到websocket錯誤的處理
def on_error(ws, error):
print("### error:", error)
# 收到websocket關閉的處理
def on_close(ws, a1, a2):
print("### closed ###")
# 收到websocket連接建立的處理
def on_open(ws):
def run(*args):
frameSize = 16000 # 每一幀的音頻大小
intervel = 0.04 # 發送音頻間隔(單位:s)
status = STATUS_FIRST_FRAME # 音頻的狀態信息,標識音頻是第一幀,還是中間幀、最后一幀
with open(wsParam.AudioFile, "rb") as fp:
while True:
buf = fp.read(frameSize)
# 文件結束
if not buf:
status = STATUS_LAST_FRAME
# 第一幀處理
# 發送第一幀音頻,帶business 參數
# appid 必須帶上,只需第一幀發送
if status == STATUS_FIRST_FRAME:
d = {"common": wsParam.CommonArgs,
"business": wsParam.BusinessArgs,
"data": {"status": 0, "format": "audio/L16;rate=16000",
"audio": str(base64.b64encode(buf), 'utf-8'),
"encoding": "raw"}}
d = json.dumps(d)
ws.send(d)
status = STATUS_CONTINUE_FRAME
# 中間幀處理
elif status == STATUS_CONTINUE_FRAME:
d = {"data": {"status": 1, "format": "audio/L16;rate=16000",
"audio": str(base64.b64encode(buf), 'utf-8'),
"encoding": "raw"}}
ws.send(json.dumps(d))
# 最后一幀處理
elif status == STATUS_LAST_FRAME:
d = {"data": {"status": 2, "format": "audio/L16;rate=16000",
"audio": str(base64.b64encode(buf), 'utf-8'),
"encoding": "raw"}}
ws.send(json.dumps(d))
time.sleep(1)
break
# 模擬音頻采樣間隔
time.sleep(intervel)
ws.close()
thread.start_new_thread(run, ())
if __name__ == "__main__":
# 測試時候在此處正確填寫相關信息即可運行
path = "/Users/zhangxiao/mfa_data/my_corpus/audio_wav/LA_spoof/"
for fileName in os.listdir(path):
if os.path.splitext(fileName)[1] == '.wav':
audio_file = path + fileName
time1 = datetime.now()
wsParam = Ws_Param(APPID='XXXX', APISecret='XXXXXXXXXXXXXXXXXXX',
APIKey='XXXXXXXXXXXXXXXXXXX',
AudioFile=audio_file)
websocket.enableTrace(False)
wsUrl = wsParam.create_url()
ws = websocket.WebSocketApp(wsUrl, on_message=on_message, on_error=on_error, on_close=on_close)
ws.on_open = on_open
ws.run_forever(sslopt={"cert_reqs": ssl.CERT_NONE})
time2 = datetime.now()
# print(time2-time1)