サウンドカードからの乱数。

多くの人がこれまで乱数に興味を持っています。 ほとんどすべてのコンピューターに組み込まれている「ハードウェアジェネレーター」 、つまりサウンドカードを使用して、真に乱数を取得する実験を共有したいと思います

素材を準備する際に、古いCコードをPythonで書き直したので、このopusは、標準ctypesライブラリを使用してPythonからWindows DLLを使用する例でもあります。

記事の最後に、2枚のRealtekおよびAudigy 2サウンドカードから取得したデータを比較し、統計的ランダムテストの結果を示します。

UPD UFOが食べたコードのゼロの欠落を修正しました。

退屈な理論の紹介


ほとんどすべてのプログラミング言語は、いわゆる疑似乱数を生成するためのいくつかの機能を提供します。 PSHは単語の数学的な意味でランダムではなく、いくつかのよく知られたアルゴリズムによって取得され、アルゴリズムの初期パラメーターを知っているため、結果のシーケンスは常に再び繰り返されます。 多くのタスクで、高品質のPSCジェネレーターは真の乱数を完全に置き換えることができます。 コンピューターゲーム、プロセスモデリング、モンテカルロ統合、遺伝的アルゴリズム...優れたPSHで十分なタスクのリストは、長期間継続できます。

一方、得られたシーケンスの真のランダム性が重要である問題の限られたサークルがあります。 主な例は暗号化です。 本当に乱数を得ることに人々が常に興味を持っていたのは暗号化の文脈です。 最も単純な例は、絶対暗号強度が証明されている唯一の暗号です。Vernam暗号(英語のワンタイムパッド )は、キーの秘密メッセージと同じ長さの真に乱数のシーケンスを必要とします。 暗号強度を確保するために、キーの生成に使用されるランダムデータ(Vernam暗号、AES、RSAなど)を再利用しないでください。 これは、信頼できる乱数のソースを見つけるという問題につながります。

サウンドカードには、コンピューターの他のほとんどのコンポーネントとは異なり、デジタル部分だけでなくアナログ部分もあります。

サウンドカードの線形入力でオーディオ信号をデジタル化する(プリミティブな)プロセスを考えます。
  1. 最初に、音に関する情報を伝える何らかのソースからの電気信号があります
  2. 信号はサウンドカードのアナログ部分に入り、そこで増幅されてADC(アナログデジタルコンバーター)のダイナミックレンジに一致します。
  3. 信号は、特定の解像度とサンプリング周波数でADCによってデジタル化され、オーディオカードのデジタル部分に入力されます。この部分からプログラムで取得できます。

パラグラフ2に興味があります。 ご存知のように、アナログ電気信号には必然的にノイズが含まれます。ノイズ成分はいくつかのカテゴリに大別できます。

干渉とパワーのランダム性が重要なポイントである場合、3番目のタイプのノイズは純粋に量子であり、 真にランダムです。

実際問題は、このノイズがサウンドカードのデジタル部分にどれだけ浸透するかということです。 経験によれば、それは浸透しています。デジタル化して保存できます。

16ビット記録モードでは、各サンプルの最下位ビットのみがランダム情報を伝送します.24ビットモードでは、いくつかの最下位ビットが、常に1つの最下位ビットのみを取ることが最も信頼できます。 これをさらに行う方法と、Windows用のPythonプログラムの例を説明します。

Pythonに興味がない人:プログラムの説明の後の最後の結果と結論の分析。


Windowsでの録音


Windowsでサウンドを録音する最も簡単な方法は、 winmm.dllライブラリのWaveform Audioインターフェイスを使用することです。 Pythonでサウンドを操作するための標準ライブラリはないため、通常のDLLへのインターフェイスを提供するctypesライブラリを使用します

sysライブラリ(標準出力へのアクセス用)とtimesleep関数へのアクセス用)をインポートします。 また、 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 = WAVEFORMATEXWAVE_FORMAT_PCM
自己nSamplesPerSec =サンプル
自己wBitsPerSample =ビット
自己nChannels =チャンネル
自己nBlockAlign = self nChannels * selfwBitsPerSample / 8
自己nAvgBytesPerSec = self nBlockAlign * selfnSamplesPerSec
自己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__ selfWaveformat
自己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 0wavBuf。dwBytesRecorded2 ]
#24ビットの場合:前の行の2の終わりを3に置き換えます
範囲 = 0len ビット2 )の iのバイアス= [ビット[ i ] ビット[ i ]の 場合 =ビット[ i + 1 ] ]
bytes = [ chr reduce lambda v、b:v << 1 | b、bias [ i- 8 :i ]0 i for range 8len 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 0wavBuf。dwBytesRecorded2 ]

データは2バイト(24ビットの場合は3バイト)でパックされるため、2のステップで配列を調べます(範囲(0、wavBuf.dwBytesRecorded、2))。ペアの最下位バイトの最下位ビットを選択します: ord(wavBuf.lpData [i]) &1 。 結果は、各サンプルの最下位ビットのビットのリストです。

小さな数学的な余談:

ランダムデータは異なる分布を持つ場合があります。 特定の番号の発生頻度。 たとえば、ビット0000100000000のシーケンスがあり、ユニットの位置がランダムに変化する場合、これもランダムシーケンスですが、ゼロが発生する確率がユニットよりもはるかに高いことは明らかです。 最も便利ないわゆる ゼロと1の発生確率が等しい「均一分布」 。 均一な分布に還元する手順は、バイアスなしと呼ばれます。 最も簡単な方法は置換です:10-> 1、01-> 0、11-> discard、00-> discard

バイアスを解除すると、次の行が実行されます
範囲 = 0len ビット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 8len 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 プログラムはいくつかのパラメーターをカウントします。


ランダム性の基準は本質的に統計的であり、シーケンス自体ではなく、ソースに適用されることに注意してください。 したがって、信頼できる結果を得るには、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.php
http://csrc.nist.gov/groups/ST/toolkit/rng/documentation_software.html

Audigy 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).

Source: https://habr.com/ru/post/J62237/


All Articles