多くの人がこれまで乱数に興味を持っています。 ほとんどすべてのコンピューターに組み込まれている
「ハードウェアジェネレーター」 、つまり
サウンドカードを使用して、真に乱数を取得する実験を共有したいと思い
ます 。
素材を準備する際に、古いCコードをPythonで書き直したので、このopusは、標準
ctypesライブラリを使用してPythonから
Windows DLLを使用する例でもあります。
記事の最後に、2枚の
Realtekおよび
Audigy 2サウンドカードから取得したデータを比較し、統計的ランダムテストの結果を示します。
UPD UFOが食べたコードのゼロの欠落を修正しました。
退屈な理論の紹介
ほとんどすべてのプログラミング言語は、いわゆる
疑似乱数を生成するためのいくつかの機能を提供します。 PSHは単語の数学的な意味でランダムではなく、いくつかのよく知られたアルゴリズムによって取得され、アルゴリズムの初期パラメーターを知っているため、結果のシーケンスは常に再び繰り返されます。 多くのタスクで、高品質のPSCジェネレーターは真の乱数を完全に置き換えることができます。 コンピューターゲーム、プロセスモデリング、モンテカルロ統合、遺伝的アルゴリズム...優れたPSHで十分なタスクのリストは、長期間継続できます。
一方、得られたシーケンスの真のランダム性が重要である問題の限られたサークルがあります。 主な例は暗号化です。
本当に乱数を得ることに人々が常に興味を持っていたのは暗号化の文脈です。 最も単純な例は、絶対暗号強度が証明されている唯一の暗号です。Vernam暗号(英語の
ワンタイムパッド )は、キーの秘密メッセージと同じ長さの真に乱数のシーケンスを必要とします。 暗号強度を確保するために、キーの生成に使用されるランダムデータ(Vernam暗号、AES、RSAなど)を再利用しないでください。 これは、信頼できる乱数のソースを見つけるという問題につながります。
サウンドカードには、コンピューターの他のほとんどのコンポーネントとは異なり、デジタル部分だけでなくアナログ部分もあります。
サウンドカードの線形入力でオーディオ信号をデジタル化する(プリミティブな)プロセスを考えます。
- 最初に、音に関する情報を伝える何らかのソースからの電気信号があります
- 信号はサウンドカードのアナログ部分に入り、そこで増幅されてADC(アナログデジタルコンバーター)のダイナミックレンジに一致します。
- 信号は、特定の解像度とサンプリング周波数でADCによってデジタル化され、オーディオカードのデジタル部分に入力されます。この部分からプログラムで取得できます。
パラグラフ2に興味があります。 ご存知のように、アナログ電気信号には必然的にノイズが含まれます。ノイズ成分はいくつかのカテゴリに大別できます。
- 無線干渉および近隣のデバイスおよび無線からの干渉(シールドが不十分なアンプが無線をキャッチする方法を聞いたことがありますか?)
- 電源干渉(原則として、無線干渉に起因する場合もあります)
- 回路部品のランダム電子運動の熱雑音
干渉とパワーのランダム性が重要なポイントである場合、3番目のタイプのノイズは純粋に量子であり、
真にランダムです。
実際問題は、このノイズがサウンドカードのデジタル部分にどれだけ浸透するかということです。 経験によれば、それは浸透しています。デジタル化して保存できます。
16ビット記録モードでは、各サンプルの最下位ビットのみがランダム情報を伝送します.24ビットモードでは、いくつかの最下位ビットが、常に1つの最下位ビットのみを取ることが最も信頼できます。 これをさらに行う方法と、Windows用のPythonプログラムの例を説明します。
Pythonに興味がない人:プログラムの説明の後の最後の結果と結論の分析。
Windowsでの録音
Windowsでサウンドを録音する最も簡単な方法は、
winmm.dllライブラリの
Waveform Audioインターフェイスを使用することです。 Pythonでサウンドを操作するための標準ライブラリはないため、通常の
DLLへのインターフェイスを提供する
ctypesライブラリを使用し
ます 。
sysライブラリ(標準出力へのアクセス用)と
time (
sleep関数へのアクセス用)をインポートします。 また、
ctypesからすべての名前をインポートします。
インポートシステム
輸入 時間
ctypes インポート から *
整数(通常はエラーコード)を返すC関数の場合、Pythonではエラーコードをチェックする関数を指定できます。 これを使用して、プログラムに最小限のエラー制御を追加します。C関数がエラーを返す場合、MMSYSERR_NOERRORはPython例外をスローし、コンソールは問題がどこにあるかを正確に確認します。
次に、リストから
winmm.dllライブラリ(Pythonオブジェクトwindll.winmmが
ctypesからインポートされます)の各関数に対して、現在の
vars()コンテキストで変数を作成するループがあります。 .waveInOpen)。 また、戻り値の型を「制御」関数MMSYSERR_NOERRORに割り当てます。
def MMSYSERR_NOERROR (値) :
値なら ! = 0 :
raise Exception ( "winmm.dll関数の実行中のエラー" 、値)
戻り値
[ "waveInOpen" 、 "waveInPrepareHeader"の funcnameの場合、
"waveInAddBuffer" 、 "waveInStart" 、
"waveInStop" 、 "waveInReset" 、
"waveInUnprepareHeader" 、 "waveInClose" ] :
vars ( ) [ funcname ] = windll。 winmm [ funcname ]
vars ( ) [ funcname ] restype = MMSYSERR_NOERROR
Windows Audioで作業するために必要なC構造を定義します。 構造クラスは、
ctypesからインポートされた
構造クラスを継承する必要があり、構造要素の名前とCタイプをリストする
_fields_フィールドを含む必要があります。
ctypesからCタイプのクラスをインポートしました。それらの名前は、c_int、c_uintなどを表しています。
最初のWAVEFORMATEX構造には、オーディオデータ形式の情報が含まれています。 パラメーターがない場合、コンストラクターはほとんどのサウンドカードの典型的な値である
16ビット48 kHzモノラルの構造を作成します。
2番目のWAVEHDRは、オーディオデータのバッファーを記述します。 パラメーターとして、コンストラクターはWAVEFORMATEX型のオブジェクトを必要とし、この形式の1秒の音声を保存できるバッファーを割り当てます。 C文字の配列は、
create_string_buffer関数によって作成されます。
クラス WAVEFORMATEX (構造体) :
WAVE_FORMAT_PCM = 1
_fields_ = [ ( "wFormatTag" 、c_ushort ) 、
( "nChannels" 、c_ushort ) 、
( "nSamplesPerSec" 、c_uint ) 、
( "nAvgBytesPerSec" 、c_uint ) 、
( "nBlockAlign" 、c_ushort ) 、
( "wBitsPerSample" 、c_ushort ) 、
( "cbSize" 、c_ushort ) ]
def __init__ ( self 、samples = 48000 、bits = 16 、channels = 1 ) :
自己 。 wFormatTag = WAVEFORMATEX 。 WAVE_FORMAT_PCM
自己 。 nSamplesPerSec =サンプル
自己 。 wBitsPerSample =ビット
自己 。 nChannels =チャンネル
自己 。 nBlockAlign = self nChannels * self 。 wBitsPerSample / 8
自己 。 nAvgBytesPerSec = self nBlockAlign * self 。 nSamplesPerSec
自己 。 cbSize = 0
クラス WAVEHDR (構造体) :
_fields_ = [ ( "lpData" 、POINTER ( c_char ) ) 、
( "dwBufferLength" 、c_uint ) 、
( "dwBytesRecorded" 、c_uint ) 、
( "dwUser" 、c_uint ) 、 #ユーザーデータdwordまたはポインター
( "dwFlags" 、c_uint ) 、
( "dwLoops" 、c_uint ) 、
( "lpNext" 、c_uint ) 、 #予約済みのポインター
( "reserved" 、c_uint ) ] #予約済みのポインター
def __init__ ( self 、 Waveformat ) :
自己 。 dwBufferLength =波形。 nAvgBytesPerSec
自己 。 lpData = create_string_buffer ( ' \ 0 00' * self。dwBufferLength )
自己 。 dwFlags = 0
次に、waveFormatオブジェクトを作成します。 オーディオデータ用の3つのバッファ。
残念ながら、ほとんどのドライバーでは、
winmm.dll (かなり古いインターフェイスの可能性があります)では、オーディオカードがサポートしていても、16ビットをより正確にデジタル化できません。 私は1枚のカードだけを知っています:SB Live24bit、それはできました。 現在は手元にありませんが、DirectXまたはASIOでのみ24ビットを書き込むAudigy 2 Notebookがあります。 したがって、今日の例は16ビット用に設計されています(24ビットで変更する必要がある場所は、カードがこれをサポートしている場合にコメントでマークされています)。
waveFormat = WAVEFORMATEX (サンプル= 48000 、ビット= 16 )
waveBufferArray = [ 範囲 ( 3 )の iのWAVEHDR ( waveFormat ) ]
次の関数はプログラムの主要な関数であり、
winmm.dllがバッファの1つを満たしたときにWindowsによって呼び出されます。
これはCコールバックであるため、最初にクラスを作成する必要があります。 これは
ctypesのWINFUNCTYPE関数、引数:戻り値の型、引数の型によって行われます。 この関数は何も返すべきではないため、MSDNによると、最初の引数は
None 、残りは
Noneです。 引数POINTER(c_uint)に注意してください-これはユーザーデータへのポインタです。この例では単なる数字ですが、たとえば、データを書き込む場所や必要なデータ量を示すクラスなど、何でもかまいません。 POINTER(WAVEHDR)は、データバッファーへのポインターです。
uMsgパラメーターは呼び出しの理由を示します
。MM_WIM_DATAに興味があります-オーディオデータが利用可能です。
WRITECALLBACK = WINFUNCTYPE ( None 、c_uint、c_uint、POINTER ( c_uint ) 、POINTER ( WAVEHDR ) 、c_uint )
def pythonWriteCallBack ( HandleWaveIn、uMsg、dwInstance、dwParam1、dwParam2 ) :
MM_WIM_CLOSE = 0x3BF
MM_WIM_DATA = 0x3C0
MM_WIM_OPEN = 0x3BE
uMsg == MM_WIM_OPENの場合 :
"Open handle =" 、HandleWaveInを印刷します
elif uMsg == MM_WIM_CLOSE:
"Close handle =" 、HandleWaveInを印刷し ます
elif uMsg == MM_WIM_DATA:
#print "Data handle ="、HandleWaveIn
wavBuf = dwParam1。 内容
wavBufの場合 。 dwBytesRecorded > 0 :
bits = [ ord ( wavBuf。lpData [ i ] ) & 1 for i in range ( 0 、 wavBuf。dwBytesRecorded 、 2 ) ]
#24ビットの場合:前の行の2の終わりを3に置き換えます
範囲 = 0 、 len (ビット) 、 2 )の iのバイアス= [ビット[ i ] 、ビット[ i ]の 場合 ! =ビット[ i + 1 ] ]
bytes = [ chr ( reduce ( lambda v、b:v << 1 | b、bias [ i- 8 :i ] 、 0 ) ) i for range ( 8 、 len ( bias ) 、 8 ) ]
rndstr = '' 。 結合 ( バイト )
#print bytes、
sys 標準 書き込み ( rndstr )
wavBufの場合 。 dwBytesRecorded == wavBuf。 dwBufferLength :
waveInAddBuffer ( HandleWaveIn、dwParam1、sizeof ( waveBuf ) )
その他 :
「1つのバッファの解放」 、dwInstance [ 0 ]を出力します
dwInstance [ 0 ] -= 1
その他 :
「不明なメッセージ」を発生させる
キーポイント:
bits = [ ord ( wavBuf。lpData [ i ] ) & 1 for i in range ( 0 、 wavBuf。dwBytesRecorded 、 2 ) ]
データは2バイト(24ビットの場合は3バイト)でパックされるため、2のステップで配列を
調べます(範囲(0、wavBuf.dwBytesRecorded、2))。ペアの最下位バイトの最下位ビットを選択します:
ord(wavBuf.lpData [i]) &1 。 結果は、各サンプルの最下位ビットの
ビットのリストです。
小さな数学的な余談:
ランダムデータは異なる分布を持つ場合があります。 特定の番号の発生頻度。 たとえば、ビット0000100000000のシーケンスがあり、ユニットの位置がランダムに変化する場合、これもランダムシーケンスですが、ゼロが発生する確率がユニットよりもはるかに高いことは明らかです。 最も便利ないわゆる ゼロと1の発生確率が等しい
「均一分布」 。 均一な分布に還元する手順は、バイアスなしと呼ばれます。 最も簡単な方法は置換です:10-> 1、01-> 0、11-> discard、00-> discard
バイアスを解除すると、次の行が実行されます
範囲 = 0 、 len (ビット) 、 2 )の iのバイアス= [ビット[ i ] 、ビット[ i ]の 場合 ! =ビット[ i + 1 ] ]
ステップ2を実行します:
range(0、len(bits)、2) 、同じペアを投げ
ます :
bits [i]!= Bits [i + 1] 、残りから最初に取る:ビット[i]。
最後に、この式はビットをバイトに収集し、すべてを文字列にマージします
bytes = [ chr ( reduce ( lambda v、b:v << 1 | b、bias [ i- 8 :i ] 、 0 ) ) i for range ( 8 、 len ( bias ) 、 8 ) ]
8ビットごとに、
reduce関数が呼び出されます
(ラムダv、b:v << 1 | b、バイアス[i-8:i]、0) 。 ここでは、関数型プログラミング要素、匿名関数(一時的にFと呼びましょう)
lambda v、b:v << 1 | bを使用します。次のようなreduce関数によって呼び出されます。バイアス[i-7])、...、バイアス[i-1])-
chr関数によって文字に変換されるバイトが判明します。
バイトのリストは
stdoutに書き込まれる文字列に変換されます。これは、このバイナリデータの方が出力をファイルにリダイレクトする方がよいためです。 コンソールに書き込む場合、コンソールへのバイナリ出力時にWindowsがCtrl + Cを適切にキャッチしないため
、 「
print bytes 」を使用してこれを行うことをお勧めします。
関数の最後には、バッファが完全に満たされているかどうかを確認するチェックがあります。 そうである場合、システムに戻してwaveInAddBuffer関数で埋めます。 そうでない場合、これはデータ出力が停止している(デバイスが閉じている)ことを意味し、占有バッファのカウンターを1つ減らします(カウンターはユーザーデータに保存されます)。
pythonWriteCallBack関数からWRITECALLBACKクラスのC関数のインスタンスを作成します。
次に、いくつかの便利な定数を定義した後、最初にWAVE_FORMAT_QUERYパラメーターでフォーマットがサポートされていることを確認し、次にCALLBACK_FUNCTIONパラメーターで、WAVE_MAPPERデバイスを開きます(サウンドカード番号を直接設定できます。ユーザーデータ(この例では、ExitFlagの数)。
byref関数を使用して、ポインターがPythonからC関数にどのように渡されるかに注目して
ください 。 また、
byref(ExitFlag)で「ユーザーデータ」へのポインターを渡しました
。Windowsは、イベントごとに
dwInstanceの形式でコールバックに渡します(たとえば、バッファーを埋める)。
次に、作成された各バッファーに対してwaveInPrepareHeaderを呼び出し、waveInAddBufferを使用して
winmm.dllを渡します。 最後に、waveInStart(HandleWaveIn)を呼び出すと、音声の入力を開始するように指示されます。 サイクル
time.sleep(1)で待っている終わり。
キーの組み合わせCtrl + C(Python
KeyboardInterrupt )をインターセプトして、プログラムを終了します。
writeCallBack = WRITECALLBACK ( pythonWriteCallBack )
試してください :
ExitFlag = c_uint ( 3 )
HandleWaveIn = c_uint ( 0 )
WAVE_MAPPER = c_int ( -1 )
WAVE_FORMAT_QUERY = c_int ( 1 )
CALLBACK_FUNCTION = c_int ( 0x30000 )
waveInOpen ( 0 、WAVE_MAPPER、byref ( waveFormat ) 、 0、0 、WAVE_FORMAT_QUERY )
waveInOpen ( byref ( HandleWaveIn ) 、WAVE_MAPPER、byref ( waveFormat ) 、writeCallBack、byref ( ExitFlag ) 、CALLBACK_FUNCTION )
waveBufferArrayのwaveBufの場合:
waveInPrepareHeader ( HandleWaveIn、byref ( waveBuf ) 、sizeof ( waveBuf ) )
waveInAddBuffer ( HandleWaveIn、byref ( waveBuf ) 、sizeof ( waveBuf ) )
waveInStart ( HandleWaveIn )
一方 1
時間 。 寝る ( 1 )
KeyboardInterrupt を除く :
waveInReset ( HandleWaveIn )
一方 1
時間 。 寝る ( 1 )
ExitFlagの場合 。 値 == 0 :
破る
waveBufferArrayのwaveBufの場合:
waveInUnprepareHeader ( HandleWaveIn、byref ( waveBuf ) 、sizeof ( waveBuf ) )
waveInClose ( HandleWaveIn )
行末の自動変換を無効にするには、「-
u 」パラメーターを使用してスクリプトを呼び出す必要があります。
c:\...\python.exe -u .py > .rnd
結論
外部信号なしで録音することができ、「無音」だけで、下位ビットはランダムな値を取得します。 外部信号(ソース)なしで録音すると、オーディオカードの回路に自然に存在するノイズの特性が得られます。 別のオプションとして、何らかの信号を記録すると、最下位ビットのデジタル化エラーにノイズが現れる場合があります。
常にデジタル化エラーがありますが、一方で、「ゼロ」ノイズはオーディオカードの物理デバイスに大きく依存しており、一部のモデルでは表示されない場合があります。 「ゼロ」ノイズの分析結果については、以下で説明します。
実験的に得られた録音のための最適なミキサー設定:録音チャンネルを選択します(再生ではありません!)
Line-In 、チャンネルの音量を最大にし、他のチャンネルをすべてオフにします。
多くのオーディオカードがマイクからの信号を「改善」し、さまざまなデジタルフィルターを適用しようとしているため、マイクチャネルは乱数の品質が最悪です。 テストされたオーディオカードの1つ(つまり
Realtek )で、マイクチャネルはミッドレンジの受信に適さない出力を生成しました。
Audigy 2では 、Mic Boost + 20DBAマイクゲインをオンにすると、ランダムなコンポーネントも削除されました。
いくつかのプログラムを使用して、受信した乱数の品質をテストできます。 最も使いやすいent(
http://www.fourmilab.ch/random/random.zipからダウンロード)。 コンソールから実行
> type data.rnd | ent.exe
data.rndがランダムデータを含むバイナリファイルである場合(プログラム内の余分な印刷をコメントアウトすることを忘れないでください。統計を少し損なう可能性があります)。 テストに最適なファイルサイズは約です。 500KB プログラムはいくつかのパラメーターをカウントします。
- エントロピー(ランダムデータの場合:8-バイトのビット数になりやすい)
- 算術平均(ランダムデータの場合:127.5になる傾向があります)
- モンテカルロPi番号(500kBデータの場合、誤差は約0.1%です)
- カイ二乗(理想的には10〜90%の範囲にあります)
- 係数(0に近いランダムデータの場合)
ランダム性の基準は本質的に統計的であり、シーケンス自体ではなく、ソースに適用されることに注意してください。 したがって、信頼できる結果を得るには、1つのソースのサンプルに対して一連のテストを実施する必要があります。 たとえば、500KBの20個。 大多数がテストに合格した場合(約90%)、ソースは一定の確率でランダムです。
残念ながら、統計には100%の基準はありません。 一定の確率で、サウンドカードがこの記事を生成できます(実際、これが私が得た方法です-冗談です)。
また、NIST(米国規格協会)のより洗練されたソフトウェアパッケージを使用して、両方のサウンドカードの「ゼロ」出力のランダム性をテストしました。 両方のカードは良質の乱数を示しました。
興味深いことに、
Realtekの内蔵オーディオは、明らかにADCの品質が低くノイズが高いため、分布の均一性がわずかに向上しています。
Audigy 2はRealtekでサポートされていない24ビットモードでは競合しません(いずれにしても、DirectXが必要であり、PythonのDirectXは別の話です)。
他のテストパッケージへのリンク:
http://stat.fsu.edu/pub/diehard/http://www.phy.duke.edu/~rgb/General/dieharder.phphttp://csrc.nist.gov/groups/ST/toolkit/rng/documentation_software.htmlAudigy 2ライン入力から無音を記録することにより取得したファイルでの
entプログラム出力の例:
Entropy = 7.999609 bits per byte.
Optimum compression would reduce the size
of this 500000 byte file by 0 percent.
Chi square distribution for 500000 samples is 270.78, and randomly
would exceed this value 23.75 percent of the times.
Arithmetic mean value of data bytes is 127.5890 (127.5 = random).
Monte Carlo value for Pi is 3.139644559 (error 0.06 percent).
Serial correlation coefficient is 0.001109 (totally uncorrelated = 0.0).