pilk
v0.2.4
python silk codec binding 支援微信語音編解碼
pilk: python + silk
關聯項目: weixin-wxposed-silk-voice
pip install pilk
SILK是一種語音編碼格式,由Skype公司研發,網路上可找到的最新版本是2012 發布的。
SILK原始程式碼已上傳至Release , 包含規格文檔
Tencent系語音支援來自silk-v3-decoder
Release 中也包含silk-v3-decoder 重編譯的x64-win版本,支援中文,原始碼
此處Tencent系語音,僅以微信語音為例
b'#!SILK_V3'
開始,以b'xFFxFF'
結束,中間為語音數據b'x02'
,去除了結尾的b'xFFxFF'
,中間不變已下統稱為語音文件
語音資料分為很多個獨立frame ,每個frame開頭兩位元組儲存剩餘frame資料的大小,每個frame預設儲存20ms的音訊數據
據此可寫出取得語音檔案持續時間(duration) 的函數(此函數pilk中已包含)
def get_duration ( silk_path : str , frame_ms : int = 20 ) -> int :
"""获取 silk 文件持续时间,单位:ms"""
with open ( silk_path , 'rb' ) as silk :
tencent = False
if silk . read ( 1 ) == b' x02 ' :
tencent = True
silk . seek ( 0 )
if tencent :
silk . seek ( 10 )
else :
silk . seek ( 9 )
i = 0
while True :
size = silk . read ( 2 )
if len ( size ) != 2 :
break
i += 1
size = size [ 0 ] + size [ 1 ] * 16
silk . seek ( silk . tell () + size )
return i * frame_ms
根據SILK格式規範, frame_ms可為20, 40, 60, 80, 100
詳情請在IDE 中查看API 文件註釋
在使用pilk之前,你還需要清楚音訊檔案mp3, aac, m4a, flac, wav, ...
與語音檔案之間的轉換是藉助PCM raw data完成的
具體轉換關係:音訊檔案⇔ PCM ⇔ 語音文件
音(視)頻文件➜ PCM
借助ffmpeg,你當然需要先有ffmpeg
ffmpeg -y -i <音(视)频输入文件> -vn -ar <采样率> -ac 1 -f s16le < PCM输出文件>
-y
: 可加可不加,表示<PCM輸出檔> 已存在時不詢問,直接覆蓋-i
: 沒啥好說的,固定的,後接<音(視)頻輸入檔>-vn
: 表示不處理視訊數據,建議添加,雖然不加也不會處理視訊數據(視訊數據不存在轉PCM的說法),但可能會列印警告-ar
: 設定取樣率,可選的值是[8000, 12000, 16000, 24000, 32000, 44100, 48000], 這裡你可以直接理解為聲音品質-ac
: 設定聲道數,在這裡必須為1 ,這是由SILK決定的-f
: 表示強制轉換為指定的格式,一般來說必須為s16le , 表示16-bit short integer Little-Endian data
ffmpeg -y -i mv.mp4 -vn -ar 44100 -ac 1 -f s16le mv.pcm
ffmpeg -y -i music.mp3 -ar 44100 -ac 1 -f s16le music.pcm
PCM ➜ 音訊文件
ffmpeg -y -f s16le -ar <采样率> -ac <声道数> -i < PCM输入文件> <音频输出文件>
-f
: 這裡必須為s16le
, 同樣也是由SILK決定的-ar
: 同上-ac
: 意義同上,值隨意<音频输出文件>
: 副檔名要準確,沒有指定格式時, ffmpeg會根據給定的輸出檔副檔名來判斷需要輸出的格式ffmpeg -y -f s16le -ar 16000 -i test.pcm test.mp3
ffmpeg 也可以使用python ffmpeg binding 替換,推薦PyAV 大家自行研究,這裡不再囉嗦。
講完了音訊檔⇔ PCM,接下來就是用pilk進行PCM ⇔ 語音檔互轉
import pilk
# pcm_rate 参数必须和 使用 ffmpeg 转 音频 到 PCM 文件时,使用的 `-ar` 参数一致
# pcm_rate 参数必须和 使用 ffmpeg 转 音频 到 PCM 文件时,使用的 `-ar` 参数一致
# pcm_rate 参数必须和 使用 ffmpeg 转 音频 到 PCM 文件时,使用的 `-ar` 参数一致
duration = pilk . encode ( "test.pcm" , "test.silk" , pcm_rate = 44100 , tencent = True )
print ( "语音时间为:" , duration )
import pilk
# pcm_rate 参数必须和 使用 ffmpeg 转 音频 到 PCM 文件时,使用的 `-ar` 参数一致
duration = pilk . decode ( "test.silk" , "test.pcm" )
print ( "语音时间为:" , duration )
使用pudub 依賴ffmpeg
import os , pilk
from pydub import AudioSegment
def convert_to_silk ( media_path : str ) -> str :
"""将输入的媒体文件转出为 silk, 并返回silk路径"""
media = AudioSegment . from_file ( media_path )
pcm_path = os . path . basename ( media_path )
pcm_path = os . path . splitext ( pcm_path )[ 0 ]
silk_path = pcm_path + '.silk'
pcm_path += '.pcm'
media . export ( pcm_path , 's16le' , parameters = [ '-ar' , str ( media . frame_rate ), '-ac' , '1' ]). close ()
pilk . encode ( pcm_path , silk_path , pcm_rate = media . frame_rate , tencent = True )
return silk_path
使用pyav推薦
import os
import av
import pilk
def to_pcm ( in_path : str ) -> tuple [ str , int ]:
"""任意媒体文件转 pcm"""
out_path = os . path . splitext ( in_path )[ 0 ] + '.pcm'
with av . open ( in_path ) as in_container :
in_stream = in_container . streams . audio [ 0 ]
sample_rate = in_stream . codec_context . sample_rate
with av . open ( out_path , 'w' , 's16le' ) as out_container :
out_stream = out_container . add_stream (
'pcm_s16le' ,
rate = sample_rate ,
layout = 'mono'
)
try :
for frame in in_container . decode ( in_stream ):
frame . pts = None
for packet in out_stream . encode ( frame ):
out_container . mux ( packet )
except :
pass
return out_path , sample_rate
def convert_to_silk ( media_path : str ) -> str :
"""任意媒体文件转 silk, 返回silk路径"""
pcm_path , sample_rate = to_pcm ( media_path )
silk_path = os . path . splitext ( pcm_path )[ 0 ] + '.silk'
pilk . encode ( pcm_path , silk_path , pcm_rate = sample_rate , tencent = True )
os . remove ( pcm_path )
return silk_path