音訊質量評估及音訊處理常用功能

zailushang發表於2020-08-07

背景

最近新上的一款機器人,有視訊通話功能,傳送端通過音視訊SDK傳輸到接收端,需要對音視訊做一些質量評估。本篇僅包含:音訊處理常用功能,以及音訊的質量評估。
各種專業知識和公式,是真心看不懂。只是對google結果的一次彙總,作為筆記記錄。

1、常用的質量評估演算法

(1)python-pesq(PESQ)

2001年2月,ITU-T推出了P.862 標準《窄帶電話網路端到端語音質量和話音編解碼器質量的客觀評價方法》,推薦使用語音質量感知評價PESQ演算法,該建議是基於輸入-輸出方式的典型演算法,效果良好。

PESQ演算法需要帶噪的衰減訊號和一個原始的參考訊號。開始時將兩個待比較的語音訊號經過電平調整、輸入濾波器濾波、時間對準和補償、聽覺變換之後, 分別提取兩路訊號的引數, 綜合其時頻特性, 得到PESQ分數, 最終將這個分數對映到主觀平均意見分(MOS)。PESQ得分範圍在-0.5--4.5之間。得分越高表示語音質量越好。
程式碼實現:

def get_pesq(clean_wav, denoised_wav):
"""
計算兩個音訊的pesq,要求取樣率為16000或8000,且8000只支援窄帶。
PESQ就是用經過處理後的語音檔案(語音壓縮、重構等)與原始語音進行比較。PESQ得分範圍在-0.5--4.5之間。得分越高表示語音質量越好。
git: https://github.com/vBaiCai/python-pesq
:param clean_wav: 原始檔案
:param denoised_wav: 待評估檔案
:return: score
"""

ref, sr0 = sf.read(clean_wav)
deg, sr1 = sf.read(denoised_wav)

# 檢查取樣率是否達標
if sr0 == sr1 and (sr0 == 16000 or sr0 == 8000):
logger.info("ref_audio/deg_audio音訊取樣率為: %s/%s" % (str(sr0), str(sr1)))
else:
logger.error("音訊取樣率必須為16000或窄帶8000。ref_audio/deg_audio音訊取樣率為: %s/%s" % (str(sr0), str(sr1)))
return False

# 檢查兩個音訊檔案長度,幀數相差不大於10
if abs(len(ref) - len(deg)) > 10:
logger.error("ref_wav/deg_wav兩個音訊長度不一致: %d/%d" % (len(ref), len(deg)))
return False

score = pesq(ref, deg, sr0)
logger.success("PESQ演算法計算的MOS值為:%s" % str(score))

return score

(2)訊雜比(Signal-to-Noise Ratio,SNR)

SNR一直是衡量針對寬頻噪聲失真的語音增強算的常規方法。但要計算訊雜比必需知道純淨語音訊號,但在實際應用中這是不可能的。因此,SNR主要用於純淨語音訊號和噪聲訊號都是己知的演算法的模擬中。 訊雜比計算整個時間軸上的語音訊號與噪聲訊號的平均功率之比。

(3)分段訊雜比(SegSNR)

由於語音訊號是一種緩慢變化的短時平穩訊號,因而在不同時間段上的訊雜比也應不一樣。為了改善訊雜比的問題,可以採用分段訊雜比。

(4)對數似然比測度(LLR)

阪倉距離測度是通過語音訊號的線性預測分析來實現的。ISD基於兩組線性預測引數(分別從原純淨語音和處理過的語音的同步幀得到)之間的差異。LLR可以看成一種阪倉距離(Itakura Distance,IS),但IS距離需要考慮模型增益。而LLR不考慮模型增益引起的幅度位移,更重視整體譜包絡的相似度。

(5)對數譜距離(LSD)

對數譜距離的定義

(6)可短時客觀可懂(STOI)

0-1範圍,值越大,可懂度越高。

程式碼實現:

def get_stoi(ref_wav, deg_wav):
"""
計算語音的STOI值,範圍0~1,值越大,可懂度越高.
注意:兩個音訊長度一致,且需要是單聲道
:param ref_wav:
:param deg_wav:
:return:
"""

import soundfile as sf
from pystoi import stoi

clean, fs = sf.read(ref_wav)
denoised, fs = sf.read(deg_wav)

# 檢查是否為單聲道
import wave
with wave.open(ref_wav, 'rb') as reg_wav_obj:
reg_wav_channels = reg_wav_obj.getnchannels()
if reg_wav_channels > 1:
logger.error("音訊不是單聲道,聲道數為:%d,音訊: %s" % (reg_wav_channels, ref_wav))
return False

with wave.open(deg_wav, 'rb') as deg_wav_obj:
deg_wav_channels = deg_wav_obj.getnchannels()
if deg_wav_channels > 1:
logger.error("音訊不是單聲道,聲道數為:%d,音訊: %s" % (deg_wav_channels, deg_wav))
return False

# 檢查兩個音訊檔案長度,幀數相差不大於10
if abs(len(clean) - len(denoised)) > 10:
logger.error("ref_wav/deg_wav兩個音訊長度不一致: %d/%d" % (len(clean), len(denoised)))
return False

# Clean and den should have the same length, and be 1D
d = stoi(clean, denoised, fs, extended=False)

return d

(7)加權譜傾斜測度(WSS)

WSS值越小說明扭曲越少,越小越好,範圍

(8)感知客觀語音質量評估(POLQA)

POLQA (感知客觀語音質量評估),是一個技術升級,它能夠覆蓋最新的語音編碼和網路傳輸技術,對於3G,4G/LTE和VoIP網路有了更高的準確度。POLQA是PESQ的繼承者(ITU-T P.862建議書)。POLQA避免了當前P.862型號的弱點,並且擴充套件到處理更高頻寬的音訊訊號。進一步的改進針對具有許多延遲變化的稱為訊號和訊號的時間的處理。與P.862類似,POLQA支援普通電話頻段(300-3400 Hz)的測量,但此外它還具有第二種操作模式,用於評估寬頻和超寬頻語音訊號中的HD-Voice(50-14000)赫茲)。POLQA還針對由具有嘴和耳模擬器的人造頭部在聲學上記錄的語音訊號的評估。

ITU-T的全系列參考目標語音質量測量系列始於1997年的P.861(PSQM),2001年被P.862(PESQ)取代.P.862 後來補充了P.862.1的建議。(PESQ得分到MOS量表的對映),P.862.2(寬頻測量)和P.862.3(應用指南)。自2011年以來P.863(POLQA)生效。ITU-T第12研究組於2011年11月同意了P.863的另外兩個實施者指南。除了上面列出的完整參考方法外,ITU-T的客觀語音質量測量標準清單還包括P.563(無參考演算法)

POLQA,類似於P.862 PESQ,是一種全參考(FR)演算法,可對與原始訊號相關的降級或處理過的語音訊號進行評級。它將參考訊號(講話者側)的每個樣本與劣化訊號(收聽者側)的每個相應樣本進行比較。兩個訊號之間的感知差異被評為差異。感知心理聲學模型基於類似的人類感知模型,如MP3或AAC。基本上,在應用掩蔽函式之後,在頻域(在臨界頻帶中)分析訊號。兩個訊號表示之間的未遮蔽差異將被計為失真。最後,語音檔案中累積的失真被對映到MOS測試中通常的1到5質量等級。

POLQA是全參考演算法,並且在對應的參考和測試訊號的摘錄的時間對準之後逐個樣本地分析語音訊號。POLQA可用於為網路提供端到端(E2E)質量評估,或表徵各個網路元件。

POLQA結果主要是模型平均意見得分(MOS),涵蓋從1(差)到5(優秀)的範圍。

2、音訊處理常用功能

(1)子程式執行cmd

下面的方法會呼叫此方法,所以多一個無關方法.

def subprocess_cmd(cmd, method_name):
"""
子程式執行cmd
:param cmd:
:param method_name:
:return:
"""

process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
start_time = time.time()
while True:
retcode = process.poll()
if retcode is None:
time.sleep(1)
else:
out, err = process.communicate()
if retcode == 1:
logger.error("執行失敗,輸出:\n %s" % str(err, encoding='utf-8'))
elif retcode == 0:
end_time = time.time()
logger.success("%s執行結束,耗時: %d 秒, cmd: %s" % (method_name, int(end_time - start_time), cmd))
break

(2)視訊提取音訊

def get_aac_audio(input_file: str, output_file: str):
"""從視訊檔案中獲取音訊
:param input_file: 視訊檔案,如input.mp4
:param output_file: 音訊檔案,如output.aac
:return:
"""

if not os.path.exists(input_file):
logger.error("檔案不存在,請檢查檔案: %s" % input_file)

if os.path.exists(output_file) and os.path.isfile(output_file):
os.remove(output_file)

cmd = "ffmpeg -i %s -vn -c:a copy %s" % (input_file, output_file)
subprocess_cmd(cmd, "get_audio")

return output_file

(3)降噪處理音訊

def optimize_audio(input_file, output_file):
"""
對音訊進行降噪處理,隔離可聽見的聲音。將低通濾波器與高通濾波器結合使用。
過濾掉200hz及以下的內容,然後過濾掉3000hz及以上的內容,可以很好地保持可用的語音音訊。
:param input_file: 原始檔案
:param output_file: 處理後檔案
:return:
"""

if not os.path.exists(input_file):
logger.error("檔案不存在,請檢查檔案: %s" % input_file)

if os.path.isfile(output_file) and os.path.exists(output_file):
os.remove(output_file)

cmd = 'ffmpeg -i %s -af "highpass=f=200, lowpass=f=3000" %s' % (input_file, output_file)
subprocess_cmd(cmd, "handle_audio")

return output_file

(4)各類音訊格式轉換

def conversions_format_audio(input_file: str, output_file: str):
"""
音訊格式轉換。
參考:https://linuxconfig.org/ffmpeg-audio-format-conversions
:param input_file:
:param output_file:
:return:
"""

to_mp3_cmd = "ffmpeg -i %s -acodec libmp3lame %s" % (input_file, output_file)
to_ogg_cmd = "ffmpeg -i %s -acodec libvorbis %s" % (input_file, output_file)
to_aac_cmd = "ffmpeg -i %s %s" % (input_file, output_file)
to_ac3_cmd = "ffmpeg -i %s -acodec ac3 %s" % (input_file, output_file)
to_wav_cmd = "ffmpeg -i %s %s" % (input_file, output_file)

cmd = ""

if os.path.exists(output_file) and os.path.isfile(output_file):
os.remove(output_file)

# wav ->
if input_file.endswith(".wav"):
if output_file.endswith(".mp3"):
cmd = to_mp3_cmd
elif output_file.endswith(".ogg"):
cmd = to_ogg_cmd
elif output_file.endswith(".aac"):
cmd = to_aac_cmd
elif output_file.endswith(".ac3"):
cmd = to_ac3_cmd

# ogg ->
elif input_file.endswith(".ogg"):
if output_file.endswith(".mp3"):
cmd = to_mp3_cmd
elif output_file.endswith(".wav"):
cmd = to_wav_cmd
elif output_file.endswith(".aac"):
cmd = to_aac_cmd
elif output_file.endswith(".ac3"):
cmd = to_ac3_cmd

# ac3 ->
elif input_file.endswith(".ac3"):
if output_file.endswith(".mp3"):
cmd = to_mp3_cmd
elif output_file.endswith(".wav"):
cmd = to_wav_cmd
elif output_file.endswith(".aac"):
cmd = to_aac_cmd
elif output_file.endswith(".ogg"):
cmd = to_ogg_cmd

# aac ->
elif input_file.endswith(".aac"):
if output_file.endswith(".mp3"):
cmd = to_mp3_cmd
elif output_file.endswith(".wav"):
cmd = to_wav_cmd
elif output_file.endswith(".ac3"):
cmd = to_ac3_cmd
elif output_file.endswith(".ogg"):
cmd = to_ogg_cmd

if cmd:
logger.info(cmd)
subprocess_cmd(cmd, "conversions_format_audio")
else:
logger.error("input_file檔案格式不對: %s" % input_file)

(5)音訊原始檔案pcm轉wav

如果不理解下面的名次,看本文最後的名詞解釋.

def pcm2wav(pcm_file):
"""
音訊原始檔案轉wav
:param pcm_file:
:return:
"""

import wave

with open(pcm_file, 'rb') as pcmfile:
pcmdata = pcmfile.read()

with wave.open(pcm_file + '.wav', 'wb') as wavfile:
# nchannels(聲道數量)
# sampwidth(取樣位數, Bit Depth一樣)
# framerate(取樣率)
# nframes(幀數)
# comptype(壓縮型別)
# compname(壓縮名)
# wavfile.setparams((1, 2, 16000, 0, 'NONE', 'NONE'))
wavfile.setnchannels(1)
wavfile.setsampwidth(2)
wavfile.setframerate(16000)
wavfile.writeframes(pcmdata)

(6)立體聲轉單聲道

def stereo2mono(input_file, output_file):
"""
立體聲 轉 單聲道。
-ac 1 設定聲道數為1
-ar 48000 設定取樣率為48000Hz
參考:https://blog.csdn.net/yu540135101/article/details/101025249
:param input_file: 立體聲原始音訊
:param output_file: 取樣率48000Hz的單聲道
:return:
"""

cmd = "ffmpeg -i %s -ac 1 -ar 16000 -y %s" % (input_file, output_file)
subprocess_cmd(cmd, "stereo2mono")

return output_file

3、名詞解釋

  • 取樣頻率(Sample Rate):也稱取樣率, 是指錄音裝置在單位時間內對聲音訊號的取樣數或樣本數, 單位為Hz(赫茲), 取樣頻率越高能表現的頻率範圍就越大。
    一些常用音訊取樣率如下:
    8kHz - 電話所用取樣率
    22.05kHz - 無線電廣播所用取樣率
    44.1kHz - 音訊CD, 也常用於 MPEG-1 音訊(VCD, SVCD, MP3)所用取樣率
    48kHz - miniDV、數字電視、DVD、DAT、電影和專業音訊所用的數字聲音所用取樣率

  • 取樣位數(Bit Depth, Sample Format, Sample Size, Sample Width), 也稱位深度, 是指採集卡在採集和播放聲音檔案時所使用數字聲音訊號的二進位制位數, 或者說是每個取樣樣本所包含的位數, 通常有8 bit、16 bit。

  • 聲道數(Channel), 是指採集卡在採集時使用聲道數, 分為單聲道(Mono)和雙聲道/立體聲(Stereo)

  • 位元率(Bit Rate), 也稱位率, 指每秒傳送的位元(bit)數, 單位為bps(Bit Per Second), 位元率越高, 傳送資料速度越快. 聲音中的位元率是指將模擬聲音訊號轉換成數字聲音訊號後, 單位時間內的二進位制資料量。
    其計算公式為: 位元率 = 取樣頻率 * 取樣位數 * 聲道數

4、參考資料

語音質量評估
語音質量評價方法

相關文章