Python シルク コーデック バインディングは WeChat 音声コーデックをサポートします
ピルク:パイソン+シルク
関連プロジェクト: weixin-wxused-silk-voice
pip install pilk
SILKはSkypeによって開発された音声コーディング形式で、オンラインで利用できる最新バージョンは 2012 年にリリースされました。
仕様書も含めたSILKオリジナルコードをReleaseにアップロードしました
Tencent の音声サポートは Silk-v3-decoder から提供されます
このリリースには、再コンパイルされたx64-winバージョンの Silk-v3-decoder も含まれており、中国語のソース コードをサポートしています
Tencent はここでは音声を指しており、WeChat の音声のみを例に挙げています。
b'#!SILK_V3'
で始まり、 b'xFFxFF'
で終わり、途中に音声データがあります。b'x02'
を挿入し、末尾のb'xFFxFF'
を削除し、中間部分は変更しないままにします。総称して音声ファイルと呼びます
音声データは多くの独立したフレームに分割され、各フレームの最初の 2 バイトには残りのフレームデータのサイズが格納されます。デフォルトでは、各フレームに20 ミリ秒のオーディオ データが格納されます。
これに基づいて、音声ファイルの長さを取得する関数を書くことができます (この関数は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 生データの助けを借りて完了することを知っておく必要がありますmp3, aac, m4a, flac, wav, ...
具体的な変換関係:音声ファイル ⇔ PCM ⇔ 音声ファイル
オーディオ (ビデオ) ファイル ➜ PCM
ffmpeg を使用する場合は、もちろん最初に ffmpeg が必要です
ffmpeg -y -i <音(视)频输入文件> -vn -ar <采样率> -ac 1 -f s16le < PCM输出文件>
-y
: 追加するかどうかを指定できます。<PCM 出力ファイル> が既に存在するかどうかを尋ねず、直接上書きすることを示します。-i
: 何も言うことはありません。修正され、その後に <オーディオ (ビデオ) 入力ファイル> が続きます。-vn
: ビデオデータを処理しないことを示します。追加しないとビデオデータは処理されませんが、警告が出力される場合があります。-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 バインディングに置き換えることもできます。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 )
ffmpeg に依存するには pudub を使用する
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