高度な検索
Developer Connection
Member Login ログイン | ご入会 ADC連絡先

Technote 1124

New Sound Input Driver Features


目次

変更の背景

変更点

変更点へのスマートな対処方法

サンプルコード

その他の注意とコメント

要約
の TECHNOTE では、Mac OS 8.1 とともに導入された、新しい PCI サウンド入力ドライバの外部の新機能と変更点を説明します。このドキュメントは、主にサウンド入力、場合によってはサウンド出力 (再生) を行うアプリケーションのデベロッパを対象としています。サウンド入力ドライバを作成しているデベロッパも、この TECHNOTE に一通り目を通して、こうした変更点を自分のドライバに反映させるべきか検討してください。


変更の背景

デベロッパが今まで求めてきたのは、より速く、より柔軟でより有益な機能を持つサウンド入力ドライバです。新しい PCI サウンド入力ドライバは、この要求に応えるための最初のステップです。

要求の 1 つは、録音をはじめてからアプリケーションが録音データの最初のビットを取得するまでの待ち時間を短くすることでした。

また、割り込みバッファを 2 の整数乗にして、高速フーリエ変換などのアルゴリズムをもっと効率よく行いたいという要求もありました。

ユーザの側はシンプルなサウンド入力を要求していました。既存ドライバでは、CD を聞いたり、コンピュータのボリュームを調整するなどの簡単なオーディオ関連の作業でさえ、Macintosh を設定するのが難しすぎました。アップルのサポート部員は、オーディオ CD を聞くことができないというユーザの電話を受けることがよくありました。この問題も、新しいサウンド入力ドライバによって対応しました。

最後の変更点は、サウンド入力ドライバのインタフェース (siOptionsDialog) の削除で、これは技術的な面からの要請です。これは、サウンド入力ドライバを標準ドライバの制限 (ModalDialog を呼ばないなど) により厳密にしたがわせるためです。

注意:
Sound Manager のどの機能が使用可能かを調べるために、Mac OS のバージョンのチェックに頼ってはいけません。これらの変更を普遍的なものとしてソースコードをアップデートしてください。サウンド入力ドライバの変更は Mac OS 8.1 を対象に行われましたが、こうした変更は Sound Manager システム機能拡張のアップデートを通じて、Mac OS の以前のバージョンにも適用される可能性があります。


変更点

待ち時間の短縮

もっとも多い要求の 1 つは、サウンド入力と出力 (再生) の割り込み待ち時間の短縮でした。この問題は、ハードウェア割り込みバッファのサイズを小さくすることで対応しました。仮想メモリ (VM) がオフの場合は、以前は 1056 サンプルでしたが、今は 512 サンプルに減らしました。VM がオンの場合は 4224 サンプルから 4096 サンプルにしました。

512 という数値が選ばれた理由は 2 つあります。バッファのサイズを約 50% 小さくすることで、割り込み待ち時間も 50% 短くなりました。これでサウンド割り込みはおおよそ 22 ミリ秒ごとではなく、11 ミリ秒ごとに起こるようになりました (44.1KHz サウンドの場合)。これで、アプリケーションはより少ない待ち時間で録音データを受け取れるようになり、音声の再生も早く始めることができるようになりました。

バッファサイズを 512 サンプルに削減した 2 つめの理由は、512 が 2 の整数乗であることで、録音した音声をリアルタイムで分析しようとしているデベロッパにとって、これは重要なことです。この種の分析 (つまり、高速フーリエ変換) に使用するアルゴリズムでは、しばしば 2 の整数乗のバッファが求められます。サウンドドライバのバッファサイズをこのように調整することで、デベロッパは割り込みコールバック内で複雑なデータのバッファリングを行う必要がなくなります。siHardwareFormat セレクタを使って、再生用ハードウェアのバッファサイズを調べ、siDeviceBufferInfo で入力用ハードウェアのバッファサイズを調べてください。

オプションダイアログの削除

siOptionsDialog セレクタが PCI サウンド入力ドライバから削除されました。削除されたのは、次の 2 つの理由からです。

  1. ドライバはユーザインタフェースを持つべきではありません (実際、ネイティブデバイスドライバ ('ndrv') は ModalDialog の呼び出しができません)。
  2. ダイアログは Macintosh のモデルによって一貫していません。

デベロッパのほうが siOptionsDialog よりもすぐれたインタフェースを提供できるはずだと感じたからです。デベロッパは、自分自身で作成することも、QuickTime の SequenceGrabber ダイアログを使うこともできます。

入力ソース選択の新しい方法

デベロッパはサウンド入力ソースを選択する統一的な方法を求めていました。これは、siOSTypeInputSource および siOSTypeInputAvailable セレクタによって達成されました。デベロッパは、入力ソースをこれまでのように名前 (システムによって違ったり、ローカライズされてしまうことがあります) ではなく、標準の OSType によって選択できるようになりました。

よりシンプルなサウンドモデル

Macintosh のサウンドモデルをシンプルにすることに貢献したもうひとつの変更点は、これまで別々だったヘッドフォンと内部スピーカの音量調節を 1 つのコントロールに統合したことです。ヘッドフォンの音量は内部スピーカの音量とは別に設定することができなくなりました (ユーザは、どのボリュームスライダを使って適切な音量レベルに調節するのか、混乱することが多かったからです)。また、ヘッドフォン装着時に内部スピーカの無音状態を調節することもできなくなりました。


変更点へのスマートな対処方法

待ち時間の短縮

ドライバの入力バッファの変更は何も問題を起こさないはずです。大多数のアプリケーションは変更があったことに気づかないでしょう。少数の、割り込みバッファサイズに依存するアプリケーションは、siDeviceBufferInfo セレクタで SPBGetDeviceInfo を呼び出して、割り込みバッファのサイズを取得しているはずです。これを正しく行っていれば、何も変更せずに新しいバッファサイズで動作するはずです。

オプションダイアログの削除

siOptionsDialog セレクタが削除されたため、アプリケーションデベロッパはサウンド入力ドライバの録音状態を適切に設定するためのインタフェースを作成しなければなりません。デベロッパはもはや siOptionsDialog を呼び出したり、他のソフトウェアがサウンド入力ドライバを設定してくれることに依存することができなくなりました。

サウンド入力ドライバの適切な設定方法については、さまざまな混乱があったようです。次の簡単な規則を心に留めて、アプリケーションを設計してください。

  1. サウンド入力ドライバを閉じた後も設定が残ると考えてはいけません。
  2. 他のプログラムがサウンド入力ソースを代わりに設定してくれると考えてはいけません。
  3. 他のプログラムが再生パラメータ (playthrough state) を設定してくれると考えてはいけません。
  4. サウンド入力ドライバの状態を仮定してはいけません。

この簡単な規則から、ユーザがモニタ&サウンドコントロールパネルまたはサウンドコントロールパネルですでにサウンド入力ソースを設定していると考えてはいけないことは明白です。最初の 3 つのルールを破ってしまうことになるからです。

サウンド入力ドライバを設定する正しい方法は、自分で作成したインタフェースか、QuickTime の SequenceGrabber インタフェースをユーザに提示することです。インタフェースで最低限必要なのは、ユーザが希望のサウンド入力ソースと再生パラメータを選択できることです。入力ソースと再生パラメータ (サンプルレート、サンプルサイズ、チャネル数など) が設定されたら、入力ドライバを閉じてはいけません。ドライバを開き直したときに、ユーザがあなたのプログラムを使って設定したままになっているという保証がないからです。入力ドライバを閉じる必要がある場合は、開き直したときに元の指定通りに設定し直してください。

入力ソース選択の新しい方法

アプリケーションの作成者が簡単に正しい入力ソースを選択できるように、新しい PCI サウンド入力ドライバには 2 つの新しいセレクタが追加され、OSType で入力ソースが選択できるようになりました。

 * siOSTypeInputSource = FOUR_CHAR_CODE('inpt')
   * siOSTypeInputAvailable = FOUR_CHAR_CODE('inav')

siOSTypeInputSource セレクタを使うと、入力ソース番号でなく OSType を渡すことで、入力ソースを設定することができます。これは、外部マイクなど、同じ入力ソースでも機種によって全く違うソース番号になっているという現実を考えると、非常に便利です。siOSTypeInputSource セレクタに指定できる入力ソースの定数は次のとおりです (Universal Headers 3.1 から)。

enum {
    kNoSource               = FOUR_CHAR_CODE('none'),
    kCDSource               = FOUR_CHAR_CODE('cd  '),
    kExtMicSource           = FOUR_CHAR_CODE('emic'),
    kRCAInSource            = FOUR_CHAR_CODE('irca'),
    kTVFMTunerSource        = FOUR_CHAR_CODE('tvfm'),
    kDAVInSource            = FOUR_CHAR_CODE('idav'),
    kIntMicSource           = FOUR_CHAR_CODE('imic'),
    kMediaBaySource         = FOUR_CHAR_CODE('mbay'),
    kModemSource            = FOUR_CHAR_CODE('modm'),
    kZoomVideoSource        = FOUR_CHAR_CODE('zvpc')
};


siOSTypeInputSource セレクタは SPBSetDeviceInfoSPBGetDeviceInfo の両方で使用できますが、siOSTypeInputAvailable は、SPBGetDeviceInfo でしか使えません。後者は現在の Macintosh で使用できる OSType セレクタをリストで返すからです。

siOSTypeInputSource および siOSTypeInputAvailable セレクタの使用法の詳細については、下記のサンプルコードと「Q&A SND12」を参照してください。

これらのセレクタは、あらかじめ Sound Manager や Sound Input Manager のバージョンチェックを行わずに使えます。セレクタを使おうとしてエラーが返ってきた場合は、そのサウンド入力ドライバは新しいセレクタをサポートしていないことがわかるので、その場合は従来の方法でサウンド入力ソースを設定する必要があります (ダイアログを表示してユーザに入力ソースを選択してもらうなど)。

よりシンプルなサウンドモデル

ヘッドフォンと内部スピーカの個別の音量調節をできなくしたのは、家庭用ステレオなどの他のオーディオハードウェアに近いインタフェースをユーザに提供するためです。このオーディオモデルでは、ヘッドフォンをつなぐと、メインユニットのスピーカはオフになり、音量調節つまみをまわすと今度はヘッドフォンの音量が変化します。これは、ワンボタンマウスのようにシンプルな概念です。音量調節つまみが 1 つしかなければ、どれを使うのか迷うことはありません。内部スピーカの音量に作用する API 呼び出しは常にヘッドフォンの音量をも設定し、その反対も同じです。音量を別々に調整する方法はありません。その機能は削除されました。

このサウンドモデルは、ヘッドフォンと内部スピーカを同時にしか音量調節できない Macintosh や、ヘッドフォンが接続されると内部スピーカの接続が切れる Macintosh にも該当します。

サンプルコード

次のサンプルコードは、siOSTypeInputAvailable から返された OSType 配列へのアクセス方法を示すものです。

OSErr GetSoundInputSourceNames (long siRefNum, Handle *sourceNames) {
    OSErr                   err;
    long                    offset;
    short                   numNames;
    int                     i;
    char                    sourceName[255];
    // siInputSourceNames は次の構造体を返します
    //  struct {
    //      short   numNames;
    //      PString names[numNames];
    //  };
    if (sourceNames != nil) {
        err = SPBGetDeviceInfo (siRefNum, siInputSourceNames, sourceNames);
    }
#if DEBUG
    if (err == noErr) {
        printf ("\nThe sound input source names are:\n");
        numNames = (*(short**)sourceNames)[0];

        offset = 3;
        for (i = 0; i < numNames; i++) {
            BlockMoveData (&((char*)(*sourceNames))[offset],
            sourceName, (*(char**)sourceNames)[offset-1]);
            sourceName[(*(char**)sourceNames)[offset-1]] = 0;
            printf ("  %s\n", sourceName);
            offset += (*(char**)sourceNames)[offset-1] + 1;
        }
    }
#endif
    return err;
}


次のサンプルコードは、サウンド入力オプションダイアログを作成するための基本的なテクニックを示したものです。まず、サウンド入力ソース名のリストを取得し、そのリストからメニュー (この場合はポップアップメニュー) を作成し、そのメニューをダイアログに挿入して、そのダイアログを表示します。そのダイアログボックスが消えたところで、ユーザがキャンセルしていなければ、GetControlValue を使ってユーザがどのメニュー項目を選択したかを調べ、siInputSource セレクタでその値を渡して、サウンド入力ドライバにその入力ソースを使用させます。メニュー内の値をマッピングし直す必要はありません。サウンド入力ドライバは、siInputSource が入力ソースを選択する時と同じ順序で名前のリストを返すからです。

#define kSourceNamesMenu        3
#define kPlayThruCheckBox       4
OSErr    DoSoundInputConfig (long soundInputDevice) {
    OSErr                   err                 = noErr;
    Rect                    box;
    Handle                  sourceNames;
    MenuHandle              namesMenu;
    ControlHandle           control;
    DialogPtr               optionsDialog;
    long                    offset,
                            i;
    short                   type,
                            inputSource,
                            itemHit,
                            playThruState;
    Boolean                 done                = false;

    err = GetSoundInputSourceNames (soundInputDevice, &sourceNames);

    namesMenu = NewMenu (kNamesMenu, nil);

    if (namesMenu != nil) {
        offset = sizeof (short);    // 個数のフィールドは飛ばす
        for (i = 0; i < (*(short**)sourceNames)[0]; i++) {
            AppendMenu (namesMenu, &((unsigned char*)(*sourceNames))[offset]);
            offset += (*(char**)sourceNames)[offset] + 1;
        }

        InsertMenu (namesMenu, hierMenu);

        optionsDialog = GetNewDialog (kOptionsDialog, nil, (WindowPtr)-1L);

        GetDialogItem (optionsDialog, kSourceNamesMenu, &type, &
                      (Handle)control, &box);
        err = SPBGetDeviceInfo (soundInputDevice, siInputSource, &inputSource);
        SetControlValue (control, inputSource);

        GetDialogItem (optionsDialog, kPlayThruCheckBox, &type, &
                      (Handle)control, &box);
        err = SPBGetDeviceInfo (soundInputDevice, siPlayThruOnOff, &playThruState);
        if (playThruState 1)
            playThruState = 1;
        SetControlValue (control, playThruState);

        SetDialogDefaultItem (optionsDialog, ok);
        ShowWindow (optionsDialog);

        while (done == false) {
            ModalDialog (nil, &itemHit);

            switch (itemHit) {
                case ok:
                    err = noErr;
                    done = true;
                    break;
                case cancel:
                    err = userCanceledErr;
                    done = true;
                    break;
                case kPlayThruCheckBox:
                    type = chkCtrl;
                    GetDialogItem (optionsDialog, kPlayThruCheckBox, &
                                   type, &(Handle)control, &box);
                    SetControlValue (control, !GetControlValue (control));
                    break;
            }
        }

        if (err == noErr) {
            GetDialogItem (optionsDialog, kSourceNamesMenu, &
                           type, &(Handle)control, &box);
            inputSource = GetControlValue (control);
            err = SPBSetDeviceInfo (soundInputDevice, siInputSource, &inputSource);

            if (err == noErr) {
                GetDialogItem (optionsDialog, kPlayThruCheckBox, &
                               type, &(Handle)control, &box);
                playThruState = GetControlValue (control);
                if (playThruState == 1)
                    playThruState = 7;
                err = SPBSetDeviceInfo (soundInputDevice, siPlayThruOnOff,
                                        &playThruState);
            }
        }

        DisposeDialog (optionsDialog);
        DisposeMenu (namesMenu);
    }

    return (err);
}


その他の注意とコメント

いつも述べることですが、QuickTime はオーディオ (再生と録音) をより簡単に扱うためにあるものです。アプリケーションで必要とする録音がごく簡単なものである場合は、QuickTime を使って録音するのがよいでしょう。QuickTime には、1 度の簡単な呼び出しでアクセスできる標準ユーザインタフェース、SGSettingsDialog が用意されています。また、QuickTime を使えば、アプリケーションが動作するハードウェアのことを意識せずに、簡単に音声を録音することができます。

要約

あなたのアプリケーションがすでにサウンド入力ドライバの設定ダイアログをユーザに提供しているのであれば、今回の PCI サウンド入力ドライバの変更が、あなたやユーザに苦痛をもたらすことはありません。しかし、アプリケーションが他のソフトウェアに依存してサウンド入力ドライバの設定を行っている場合、今こそすべてのサウンド設定を自分で正しく処理するようにアプリケーションをアップデートする時です。

参考文献
TECHNOTE 1108: 「よく知られていなかったサウンドの機能」
TECHNOTE 1048: Some Sound Advice: Getting the Most Out of the Sound Manager
Inside Macintosh: Sound
Inside Macintosh: QuickTime