Linux上のPCスピーカー用の.wavコンソールプレーヤー

私は長い間、ノートやモノラルの着信音だけでなく、PCスピーカー用のプレーヤーを書きたいと思っていました。 しかし、それが関連していたとき(DOS-永遠に!)私には知識も能力も考えもありませんでした。 その後、Windows DDKを介してアクセスできず、QBASIC SOUNDのスタイルで静かにきしみ続けました。 はい、そして、サウンドデバイスとしてのPCスピーカーの関連性はゼロになり、誇り高いスピーカーはブザーとブザーに変わりました。 ただし、以前のように、PCのどこからも消えることはなく(途中ですべてのドライブよりも長持ちしていました)、起動時にエラーを報告してエラ​​ーを報告しました。 それでは、現代のユーザー空間のハードウェアおよびソフトウェア環境で、PCスピーカーでポリフォニックメロディーまたは音声を再生することは可能ですか? もちろんできます-CとLinuxがこれを助けてくれます。

帽子と眼鏡をかけ男に捧げ、全員を特定の方向に送ります (著者に知られていませんが、すべてがDOSBoxでうまく機能します )。

プログラマーの観点から見たスピーカーとは何ですか? このデバイスには2つの状態があります。オンとオフ-これにより、音を出す膜(おそらく、他のアクティブな要素)を制御します。 生物学者によると、人間の聴覚の閾値は約22KHz上および20Hz下であるため、非常に迅速に状態を切り替える必要があります。 通常、スピーカーはインターバルタイマーによって制御されますが、この方法では、既成のソフトウェア(タイマーに直接アクセスする必要はまったくありません)とユーザーインターフェイスがある特定の周波数と持続時間のサウンドのみを再生できます。 たとえば、Linuxコンソールの場合、「To」というメモは最初のオクターブであり、1秒間続きます。



echo -en "\e[10;263;11;1000]\a" echo -en "\ec"
      
      





2番目のechoコマンドは、持続時間と周波数の設定をデフォルト状態に戻します。

スピーカーコントロールにアクセスするには、0ビットと最初のビットで0x61(16進数の61)ポートと直接通信する必要があります。 ゼロビット:スピーカーのタイマーへのバインドを制御します-1の場合、タイマーによって制御されます。 最初のビット:スピーカーの状態を切り替えます-1オン、0オフ。 これが私たちが使用する方法です。

さて、どのようにプレーするかを理解するために、何をプレーするかを決めなければなりません。 再生には、.WAVファイルを使用します。 .WAVファイルのデータ形式はさまざまですが、通常はパルスコード変調のデータを含むファイルを指します。 これは、ADCから特定の周波数(サンプリング周波数)で取得され、各チャネルに対してファイルにそのまま書き込まれる一連の値です。 音の観点からは、これは特定の時点での音量、またはスピーカーの観点からは静止点に対する膜の位置です。 一度にADCから取得した最大値は、ビットストリーム(ビットレート)の値を設定します。 特定の時間におけるすべてのチャネルのデータレコードのセットはサンプルです。 音について言えば、 コテルニコフの定理を確実に覚えておく必要があり、これらのデータに基づいて、元の波を(高いサンプリング周波数とビットストリームで)損失なく復元できると言わなければなりません。 しかし、私たちの場合、これは機能しません。なぜなら、スピーカーには音量の概念がないか、むしろ存在しますが、変更することはできません-音量全体(オン)またはそうでない(オフ)のどちらかです。 ADCボリュームサンプリングの精度も有限であるため、「ウォームチューブサウンド」の値が明確になります。

記録されたデータは、正および負の値(横軸を横切る波の動き)の形式で表示されますが、わずかに異なる形式で表示されます:最大値から半分(ゼロ)が取得されます。 データがシングルバイトのサンプルで表される場合、ゼロは256/2 = 128になります。256は1バイトで表すことができる最大数、または256 = 255(0xFF、数の最大値)+ 1です。したがって、記録に使用する場合2バイト、次に(65535 + 1)/ 2 =32768。この数値より大きい値は正であり、小さい値から大きい値に成長し、この数より小さい値は負であり、大きい値から小さい値に減少します。 たとえば、シングルバイト値の通常の数値に変換します。



 245(ADC値)=> 117 = 245-128
  93(ADC値)=> -35 = 93-128


上記のように、スピーカーのオンとオフのみを切り替えることができます。つまり、受信データから方形波を形成します。1は値がゼロより大きく、その他の場合は0です。 スピーカーの再生方法の詳細については、 こちらをご覧ください 。この記事は、考えられた実装の出発点として役立ちました。

計画の実行を開始します。 1つのチャネルのみが再生されるように予約する必要があります。分析用の最大データサイズはチャネルごとに4バイトです(スピーカーの場合、可能な限り最大の品質を得るには1ビットで十分であるため、この値は32倍冗長です)。 最初から、2つの大きな問題がありました。

まず、ポートへのアクセス-DOSですべてが簡単だったのと同じように、Windowsでも私と同じ程度にすべてが困難でした。 ただし、Linuxはポートに直接アクセスするための非常に素晴らしい機会を提供します。ルートパスワードを知るか、他の方法でスーパーユーザー権限を取得するか、作成するプロセスのCAP_SYS_RAWIO権限を取得するだけです。 この機能はiopermと呼ばれ、 0〜0x3FFの範囲のすべてのポートへのアクセスを開くことができます。 もっと欲しいのですが、別の呼び出し-ioplを使用します。これは役に立たないでしょう。

許可後、入出力ポートへの直接アクセスは、アセンブラーでのインライン挿入を記述するマクロを使用して実行されます。 バイト(b)、ワード(w)、ダブルワード(l)、行(s)を書き込み(out)および読み取り(in)でき、操作後にポーズ(_p)を使用できます。 MANページにはほとんど情報が含まれておらず、これを行うべきではないので怖いので、ヘッダーファイルのソースファイルを確認することをお勧めします。 _pでマクロを使用する場合、0x80ポートへのアクセスを開く必要もあります。これは、このポートにデータバイトを出力することによって遅延が実行されるためです。

プログラムで-最初に、ポートへのアクセスを初期化し、プログラムの実行後の回復のために、すでに0x61ポートにあった値を保存します。



 #define SPKPORT 0x61 static unsigned char old61 = 0; unsigned char out61 = 0; if (!ioperm(SPKPORT,1,1)){ //   0x61  old61 = inb(SPKPORT); // out61 = old61 & 0xFE; //   outb(old61,SPKPORT); //,       }
      
      





最後に、元の状態に戻ります。



 outb(old61,SPKPORT); //   ioperm(SPKPORT,1,0); //      0x61 
      
      





2番目の問題は時間です。 Linuxはマルチタスクマルチユーザー環境であり、リソース(特にプロセッサ)の独占的な継続的な所有に頼ることはできません。 オリジナルにできるだけ近い(PCスピーカーであるため)サウンドを再生するには、厳密に設定された間隔でデータを送信する必要があります。この間隔からのずれはサウンドを即座に歪ませます。 間隔がリズミカル(同じ)であるが、より長いまたは短い場合、再生される音はそれぞれ元の音よりも低くまたは高くなります。 間隔が毎回異なる場合、音は単に認識されません。 これはすべて、最小サンプリング周波数が8000 Hzであり、1/8000〜125マイクロ秒以下の最長間隔を指定せざるを得ないという事実によって複雑になります。 22KHzでは、これは45マイクロ秒間隔です。 MANによると、このような遅延は、 usleepまたはnanosleepを使用している場合に発生する可能性があります。 しかし、最初に、それらを行う必要があった場所:



 char wavdata[0x10000]; //   unsigned int *curdata; //      unsigned int bufsize; //    unsigned int cursampleraw; //    unsigned int datamask; //        short int onechannelinc; //     =   unsigned int samplezero; //   for (i = 0;i < bufsize;i += onechannelinc){ curdata = (void*)(wavdata+i); //     cursampleraw = *curdata&datamask; //    if (cursampleraw > samplezero){ //    out61 |= 0x2; // ,  1  = 1 }else{ out61 &= 0xFD; // ,  1  = 0 } outb(out61,SPKPORT); //   //  ,     }
      
      





これは実際にはプログラム全体であり、他のすべてはファイルと予備データ分析から読み取っています。

では、遅延はどうですか? usleepnanosleepを使用しても結果は得られなかった、または結果が得られたが、ポーズ値は10マイクロ秒未満でした。 ポーズが長くなると、音は修復不可能に壊れ、ポイントはピッチではなく、ポーズが維持されないという事実、リズムはありませんでした。つまり、サイクルの各パッセージは異なる持続時間を持ちました。 プロセスの優先順位が低すぎると考えて、 niceを使用します。 しかし、ユーティリティもソフトウェアの呼び出しも問題を解決しませんでした。 スケジューラポリシーを変更してみてください:



 struct sched_param schedio; sched_getparam(0,&schedio); //   schedio.sched_priority = sched_get_priority_max(SCHED_FIFO); //   sched_setscheduler(0,SCHED_FIFO,&schedio); //  SCHED_FIFO
      
      





正しい使用方法はわかりませんが、この形式では役に立ちませんでした(このコードはプログラム内でコメント化されたままです 。それでも役立つ場合は、ビジネスに戻すことができます)。 このすべては、このページの推奨事項に従って試行されました。 usleepの代わりに空のサイクルを試してみただけで、結果が得られました。音楽だけでなくスピーチも聞こえました。

すべてがうまくいきました。 これは希望を呼び起こしましたが、他のマシンに転送された場合の悪い結果を示唆しました。 最後のリンクでは、ポートへの出力が約1マイクロ秒の遅延を与えるという事実についていくつかの段落がありましたが、私は懐疑的でしたが、遅延のあるポートへの出力用のoutb_pマクロは同じ原理によって導かれました。 メインループに一時停止が追加されました。



 short int pause; for (i = 0;i < bufsize;i += onechannelinc){ curdata = (void*)(wavdata+i); cursampleraw = *curdata&datamask; if (cursampleraw > samplezero){ out61 |= 0x2; }else{ out61 &= 0xFD; } for (k=0;k<pause;k++)outb(out61,SPKPORT); //    }
      
      





-その後、プログラムは稼働中のサーバーでテストされましたが、予想外に何も壊れず、すべてが機能しました。 再生中のファイルからの読み取りの問題(読み取りサイクル間のフェード)もテストされたコンピューターでは発生しませんでした。ファイルは64KBのバッファーで読み取られ、目立った歪みはありません。 メインループに無関係なコードが存在しても、このコードがシステムコールでない限り、音質に影響はありません。 その結果、逆説的ではないので、コンピューターが強力であればあるほど、スピーカーの音は良くなるという結論に達しました。 電力を削減する方向に進むと、マルチタスクシステムのある時点ですべてが壊れますが、シングルタスクに切り替えることで、再び結果が得られます。

正直なところ、一度にDOS用のこのプログラムの2つの通常のバージョンがあったので、私は肯定的な結果を確信していませんでした。 まず、すべての割り込みを禁止し(すべての排他的な所有者のままです)、上記のようにコードを向かい合わせに実行し、無限ループでクロックサイクルごとに遅延をカウントします。 次に、タイマーを最大周波数(最小間隔は約1マイクロ秒)に設定し、0x8割り込み(IRQ0)を取得し、計算された間隔でスピーカーデータを提供します。これにより、誰もこのプロセスに干渉しなくなります。 これらのオプションはどちらもLinuxのユーザー空間環境では機能しませんが、すべてが非常に奇妙になったことを嬉しく思います。

これで、残りのコードの内容に関する数行です。 基本的に、これは.WAVファイルヘッダーの解析であり、 この説明を見つけたので 、すべてのフィールドをソートしてチェックしましたが、インターネットからダウンロードした最初のファイルは間違った形式であることが判明しました。 次に、 このドキュメントに目を向けて分析を簡素化し、新しいデータを考慮して、インターネットから2番目のファイルをダウンロードしましたが、 ファクトチェーンの前に2バイトのフィールドがないため困惑しました。任意のファイル。

.WAVヘッダーの分析には、 sizeofコンストラクトを使用したデータ型のチェックが含まれます。使用されるintデータ型は4バイト、 short intは2バイト、 charは1バイトであると想定されます。 この形式では、64ビットシステム用にコンパイルできます。 そうでない場合、チェックはパスしないはずで、プログラムはサポートされていない.WAV形式に関するエラーをスローします。

また、コードにはプロセスを視覚化する試みがありましたが、それを音と組み合わせると、クローキングとパチパチ音だけが聞こえるので、グラフを見ることができますが、音はありません。 ちなみに、 usleepを遅延として使用すると、再生がどれほど遅いかを評価できます(または視覚と音声の感覚を比較できます)。

これは、コンパイルされた言語のLinux専用の最初のプログラムです。したがって、Borland Cからconio.hを探している人にとっては、ここにはありませんが、すべてがはるかに優れています。読み取りモードの1つ)、コンソール画面のサイズの取得、ただし、ここではioctl (man console_ioctl)を使用してデバイスに直接アクセスする必要があります:



 struct winsize scrsize; ioctl(STDOUT_FILENO,TIOCGWINSZ,&scrsize); //scrsize.ws_col -    //scrsize.ws_row -   
      
      





プログラムはここで入手できます: playwav.zip-いくつかの.WAVファイルもアーカイブに含まれています。 64ビットシステムでのテストを含め、簡単にコンパイルできます。

 gcc playwav64.c -o playwav64 chmod +x playwav64
      
      





3つのパラメーターで実行できます。最初のパラメーターは再生するファイルです。



 sudo ./playwav64 file.wav
      
      





2番目は時間の乗数です。2番目のパラメーターが数値でない場合、大きい場合は音が小さくなり、デフォルト値は650,000です。



 sudo ./playwav64 file.wav 500000
      
      





3番目-任意の値は、画面にグラフを表示する必要があることをプログラムに伝えます(音なし)。



 ./playwav64 file.wav sw
      
      





ルート権限のみで鳴ります。 KDEでも動作しますが、Xで実行しない方が良いです。 SSHセッションで実行する場合、サウンドは接続先の物理マシン上にあります。

私は可能な限りすべてのコンピューターでそれをチェックしました(どこでも動作しました)、おそらくコードをめちゃくちゃにしたので、申し訳ありません。



All Articles