Topologi Perangkat

API DeviceTopology memberi klien kontrol atas berbagai fungsi internal adaptor audio yang tidak dapat mereka akses melalui MMDevice API, WASAPI, atau EndpointVolume API.

Seperti yang dijelaskan sebelumnya, MMDevice API, WASAPI, dan EndpointVolume API menyajikan mikrofon, speaker, headphone, dan perangkat input dan output audio lainnya kepada klien sebagai perangkat titik akhir audio. Model perangkat titik akhir memberi klien akses mudah ke kontrol volume dan bisukan di perangkat audio. Klien yang hanya memerlukan kontrol sederhana ini dapat menghindari melintasi topologi internal perangkat keras dalam adaptor audio.

Di Windows Vista, mesin audio secara otomatis mengonfigurasi topologi perangkat audio untuk digunakan oleh aplikasi audio. Dengan demikian, aplikasi jarang, jika pernah, perlu menggunakan DEVICETopology API untuk tujuan ini. Misalnya, asumsikan bahwa adaptor audio berisi multiplexer input yang dapat mengambil aliran dari input baris atau mikrofon, tetapi itu tidak dapat mengambil aliran dari kedua perangkat titik akhir secara bersamaan. Asumsikan bahwa pengguna telah mengaktifkan aplikasi mode eksklusif untuk mendahului penggunaan perangkat titik akhir audio oleh aplikasi mode bersama, seperti yang dijelaskan dalam Mode Eksklusif Aliran. Jika aplikasi mode bersama merekam aliran dari input baris pada saat aplikasi mode eksklusif mulai merekam aliran dari mikrofon, mesin audio secara otomatis mengalihkan multiplexer dari input baris ke mikrofon. Sebaliknya, di versi Windows sebelumnya, termasuk Windows XP, aplikasi mode eksklusif dalam contoh ini akan menggunakan fungsi mixerXxx di API multimedia Windows untuk melintasi topologi perangkat adaptor, menemukan multiplexer, dan mengonfigurasi multiplexer untuk memilih input mikrofon. Di Windows Vista, langkah-langkah ini tidak lagi diperlukan.

Namun, beberapa klien mungkin memerlukan kontrol eksplisit atas jenis kontrol perangkat keras audio yang tidak dapat diakses melalui MMDevice API, WASAPI, atau EndpointVolume API. Untuk klien ini, DEVICETopology API menyediakan kemampuan untuk melintasi topologi perangkat adaptor untuk menemukan dan mengelola kontrol audio di perangkat. Aplikasi yang menggunakan DEVICETopology API harus dirancang dengan hati-hati untuk menghindari mengganggu kebijakan audio Windows dan mengganggu konfigurasi internal perangkat audio yang dibagikan dengan aplikasi lain. Untuk informasi selengkapnya tentang kebijakan audio Windows, lihat Komponen Audio Mode Pengguna.

API DeviceTopology menyediakan antarmuka untuk menemukan dan mengelola jenis kontrol audio berikut dalam topologi perangkat:

  • Kontrol perolehan otomatis
  • Kontrol Bass
  • Pemilih input (multiplexer)
  • Kontrol kenyaringan
  • Kontrol midrange
  • Kontrol bisukan
  • Pemilih output (demultiplexer)
  • Meter puncak
  • Kontrol treble
  • Kontrol volume

Selain itu, API DeviceTopology memungkinkan klien untuk mengkueri perangkat adaptor untuk informasi tentang format aliran yang didukungnya. File header Devicetopology.h mendefinisikan antarmuka dalam API DeviceTopology.

Diagram berikut menunjukkan contoh beberapa topologi perangkat yang terhubung untuk bagian adaptor PCI yang mengambil audio dari mikrofon, input baris, dan pemutar CD.

example of four connected device topologies

Diagram sebelumnya menunjukkan jalur data yang mengarah dari input analog ke bus sistem. Masing-masing perangkat berikut direpresentasikan sebagai objek topologi perangkat dengan antarmuka IDeviceTopology:

  • Perangkat tangkapan gelombang
  • Input perangkat multiplexer
  • Perangkat titik akhir A
  • Perangkat titik akhir B

Perhatikan bahwa diagram topologi menggabungkan perangkat adaptor (tangkapan gelombang dan perangkat multiplexer input) dengan perangkat titik akhir. Melalui koneksi antara perangkat, data audio meneruskan dari satu perangkat ke perangkat berikutnya. Di setiap sisi koneksi adalah konektor (berlabel Con dalam diagram) tempat data masuk atau meninggalkan perangkat.

Di tepi kiri diagram, sinyal dari jack input baris dan mikrofon memasuki perangkat titik akhir.

Di dalam perangkat penangkapan gelombang dan perangkat multiplexer input adalah fungsi pemrosesan aliran, yang, dalam terminologi API DeviceTopology, disebut subunit. Jenis subunit berikut muncul dalam diagram sebelumnya:

  • Kontrol volume (berlabel Vol)
  • Kontrol bisukan (berlabel Matikan Suara)
  • Multiplexer (atau pemilih input; BERlabel MUX)
  • Konverter Analog-ke-digital (berlabel ADC)

Pengaturan dalam subunit volume, bisu, dan multiplexer dapat dikontrol oleh klien, dan API DeviceTopology menyediakan antarmuka kontrol kepada klien untuk mengontrolnya. Dalam contoh ini, subunit ADC tidak memiliki pengaturan kontrol. Dengan demikian, API DeviceTopology tidak menyediakan antarmuka kontrol untuk ADC.

Dalam terminologi API DeviceTopology, konektor dan subunit termasuk dalam kategori umum yang sama—bagian. Semua bagian, terlepas dari apakah itu konektor atau subunit, menyediakan serangkaian fungsi umum. API DeviceTopology mengimplementasikan antarmuka IPart untuk mewakili fungsi generik yang umum untuk konektor dan subunit. API mengimplementasikan antarmuka I Koneksi or dan ISubunit untuk mewakili aspek konektor dan subunit tertentu.

DEVICETopology API membangun topologi perangkat tangkapan gelombang dan memasukkan perangkat multiplexer dari filter kernel-streaming (KS) yang diekspos driver audio ke sistem operasi untuk mewakili perangkat ini. (Driver adaptor audio mengimplementasikan Antarmuka IMiniportWaveXxx dan IMiniportTopology untuk mewakili bagian yang bergantung pada perangkat keras dari filter ini; untuk informasi selengkapnya tentang antarmuka ini dan tentang filter KS, lihat dokumentasi Windows DDK.)

API DeviceTopology membangun topologi sepele untuk mewakili perangkat titik akhir A dan B dalam diagram sebelumnya. Topologi perangkat perangkat titik akhir terdiri dari satu konektor. Topologi ini hanyalah tempat penampung untuk perangkat titik akhir dan tidak berisi subunit untuk memproses data audio. Bahkan, perangkat adaptor berisi semua subunit yang digunakan aplikasi klien untuk mengontrol pemrosesan audio. Topologi perangkat perangkat titik akhir berfungsi terutama sebagai titik awal untuk menjelajahi topologi perangkat perangkat adaptor.

Koneksi internal antara dua bagian dalam topologi perangkat disebut tautan. API DeviceTopology menyediakan metode untuk melintasi tautan dari satu bagian ke bagian berikutnya dalam topologi perangkat. API juga menyediakan metode untuk melintas koneksi antara topologi perangkat.

Untuk memulai eksplorasi sekumpulan topologi perangkat yang terhubung, aplikasi klien mengaktifkan antarmuka IDeviceTopology dari perangkat titik akhir audio. Konektor di perangkat titik akhir tersambung baik ke konektor dalam adaptor audio atau ke jaringan. Jika titik akhir terhubung ke perangkat pada adaptor audio, maka metode dalam API DeviceTopology memungkinkan aplikasi untuk melangkah melintasi koneksi dari titik akhir ke adaptor dengan mendapatkan referensi ke antarmuka IDeviceTopology dari perangkat adaptor di sisi lain koneksi. Jaringan, di sisi lain, tidak memiliki topologi perangkat. Koneksi jaringan menyalurkan aliran audio ke klien yang mengakses sistem dari jarak jauh.

API DeviceTopology menyediakan akses hanya ke topologi perangkat keras dalam adaptor audio. Perangkat eksternal di tepi kiri diagram dan komponen perangkat lunak di tepi kanan berada di luar cakupan API. Garis putus-putus di kedua sisi diagram mewakili batas API DeviceTopology. Klien dapat menggunakan API untuk menjelajahi jalur data yang membentang dari jack input ke bus sistem, tetapi API tidak dapat menembus di luar batas-batas ini.

Setiap konektor dalam diagram sebelumnya memiliki jenis koneksi terkait yang menunjukkan jenis koneksi yang dilakukan konektor. Dengan demikian, konektor di dua sisi koneksi selalu memiliki jenis koneksi yang identik. Jenis koneksi ditunjukkan oleh nilai enumerasi Koneksi orType—Physical_External, Physical_Internal, Software_Fixed, Software_IO, atau Jaringan. Koneksi antara perangkat multiplexer input dan perangkat titik akhir A dan B berjenis Physical_External, yang berarti bahwa koneksi mewakili koneksi fisik ke perangkat eksternal (dengan kata lain, jack audio yang dapat diakses pengguna). Koneksi ke sinyal analog dari pemutar CD internal berjenis Physical_Internal, yang menunjukkan koneksi fisik ke perangkat tambahan yang dipasang di dalam sasis sistem. Koneksi antara perangkat tangkapan gelombang dan perangkat multiplexer input berjenis Software_Fixed, yang menunjukkan koneksi permanen yang diperbaiki dan tidak dapat dikonfigurasi di bawah kontrol perangkat lunak. Akhirnya, koneksi ke bus sistem di sisi kanan diagram berjenis Software_IO, yang menunjukkan bahwa I/O data untuk koneksi diimplementasikan oleh mesin DMA di bawah kontrol perangkat lunak. (Diagram tidak menyertakan contoh jenis koneksi Jaringan.)

Klien mulai melintas jalur data di perangkat titik akhir. Pertama, klien mendapatkan antarmuka IMMDevice yang mewakili perangkat titik akhir, seperti yang dijelaskan dalam Menghitung Perangkat Audio. Untuk mendapatkan antarmuka IDeviceTopology untuk perangkat titik akhir, klien memanggil metode IMMDevice::Activate dengan parameter iid diatur ke REFIID IID_IDeviceTopology.

Dalam contoh dalam diagram sebelumnya, perangkat multiplexer input berisi semua kontrol perangkat keras (volume, bisu, dan multiplexer) untuk aliran pengambilan dari jack input baris dan mikrofon. Contoh kode berikut menunjukkan cara mendapatkan antarmuka IDeviceTopology untuk perangkat multiplexer input dari antarmuka IMMDevice untuk perangkat titik akhir untuk input baris atau mikrofon:

//-----------------------------------------------------------
// The input argument to this function is a pointer to the
// IMMDevice interface of an endpoint device. The function
// outputs a pointer (counted reference) to the
// IDeviceTopology interface of the adapter device that
// connects to the endpoint device.
//-----------------------------------------------------------
#define EXIT_ON_ERROR(hres)  \
              if (FAILED(hres)) { goto Exit; }
#define SAFE_RELEASE(punk)  \
              if ((punk) != NULL)  \
                { (punk)->Release(); (punk) = NULL; }

const IID IID_IDeviceTopology = __uuidof(IDeviceTopology);
const IID IID_IPart = __uuidof(IPart);

HRESULT GetHardwareDeviceTopology(
            IMMDevice *pEndptDev,
            IDeviceTopology **ppDevTopo)
{
    HRESULT hr = S_OK;
    IDeviceTopology *pDevTopoEndpt = NULL;
    IConnector *pConnEndpt = NULL;
    IConnector *pConnHWDev = NULL;
    IPart *pPartConn = NULL;

    // Get the endpoint device's IDeviceTopology interface.

    hr = pEndptDev->Activate(
                      IID_IDeviceTopology, CLSCTX_ALL,
                      NULL, (void**)&pDevTopoEndpt);
    EXIT_ON_ERROR(hr)

    // The device topology for an endpoint device always
    // contains just one connector (connector number 0).

    hr = pDevTopoEndpt->GetConnector(0, &pConnEndpt);
    EXIT_ON_ERROR(hr)

    // Use the connector in the endpoint device to get the
    // connector in the adapter device.

    hr = pConnEndpt->GetConnectedTo(&pConnHWDev);
    EXIT_ON_ERROR(hr)

    // Query the connector in the adapter device for
    // its IPart interface.

    hr = pConnHWDev->QueryInterface(
                         IID_IPart, (void**)&pPartConn);
    EXIT_ON_ERROR(hr)

    // Use the connector's IPart interface to get the
    // IDeviceTopology interface for the adapter device.

    hr = pPartConn->GetTopologyObject(ppDevTopo);

Exit:
    SAFE_RELEASE(pDevTopoEndpt)
    SAFE_RELEASE(pConnEndpt)
    SAFE_RELEASE(pConnHWDev)
    SAFE_RELEASE(pPartConn)

    return hr;
}

Fungsi GetHardwareDeviceTopology dalam contoh kode sebelumnya melakukan langkah-langkah berikut untuk mendapatkan antarmuka IDeviceTopology untuk perangkat multiplekser input:

  1. Panggil metode IMMDevice::Activate untuk mendapatkan antarmuka IDeviceTopology untuk perangkat titik akhir.
  2. Dengan antarmuka IDeviceTopology yang diperoleh pada langkah sebelumnya, panggil metode IDeviceTopology::Get Koneksi or untuk mendapatkan antarmuka I Koneksi or dari konektor tunggal (nomor konektor 0) di perangkat titik akhir.
  3. Dengan antarmuka I Koneksi or yang diperoleh pada langkah sebelumnya, panggil metode I Koneksi or::Get Koneksi edTo untuk mendapatkan antarmuka I Koneksi or konektor di perangkat multiplekser input.
  4. Kueri antarmuka I Koneksi or yang diperoleh di langkah sebelumnya untuk antarmuka IPart-nya.
  5. Dengan antarmuka IPart yang diperoleh pada langkah sebelumnya, panggil metode IPart::GetTopologyObject untuk mendapatkan antarmuka IDeviceTopology untuk perangkat multiplexer input.

Sebelum pengguna dapat merekam dari mikrofon dalam diagram sebelumnya, aplikasi klien harus memastikan bahwa multiplexer memilih input mikrofon. Contoh kode berikut menunjukkan bagaimana klien dapat melintasi jalur data dari mikrofon hingga menemukan multiplexer, yang kemudian diprogram untuk memilih input mikrofon:

//-----------------------------------------------------------
// The input argument to this function is a pointer to the
// IMMDevice interface for a capture endpoint device. The
// function traverses the data path that extends from the
// endpoint device to the system bus (for example, PCI)
// or external bus (USB). If the function discovers a MUX
// (input selector) in the path, it selects the MUX input
// that connects to the stream from the endpoint device.
//-----------------------------------------------------------
#define EXIT_ON_ERROR(hres)  \
              if (FAILED(hres)) { goto Exit; }
#define SAFE_RELEASE(punk)  \
              if ((punk) != NULL)  \
                { (punk)->Release(); (punk) = NULL; }

const IID IID_IDeviceTopology = __uuidof(IDeviceTopology);
const IID IID_IPart = __uuidof(IPart);
const IID IID_IConnector = __uuidof(IConnector);
const IID IID_IAudioInputSelector = __uuidof(IAudioInputSelector);

HRESULT SelectCaptureDevice(IMMDevice *pEndptDev)
{
    HRESULT hr = S_OK;
    DataFlow flow;
    IDeviceTopology *pDeviceTopology = NULL;
    IConnector *pConnFrom = NULL;
    IConnector *pConnTo = NULL;
    IPart *pPartPrev = NULL;
    IPart *pPartNext = NULL;
    IAudioInputSelector *pSelector = NULL;

    if (pEndptDev == NULL)
    {
        EXIT_ON_ERROR(hr = E_POINTER)
    }

    // Get the endpoint device's IDeviceTopology interface.
    hr = pEndptDev->Activate(
                      IID_IDeviceTopology, CLSCTX_ALL, NULL,
                      (void**)&pDeviceTopology);
    EXIT_ON_ERROR(hr)

    // The device topology for an endpoint device always
    // contains just one connector (connector number 0).
    hr = pDeviceTopology->GetConnector(0, &pConnFrom);
    SAFE_RELEASE(pDeviceTopology)
    EXIT_ON_ERROR(hr)

    // Make sure that this is a capture device.
    hr = pConnFrom->GetDataFlow(&flow);
    EXIT_ON_ERROR(hr)

    if (flow != Out)
    {
        // Error -- this is a rendering device.
        EXIT_ON_ERROR(hr = AUDCLNT_E_WRONG_ENDPOINT_TYPE)
    }

    // Outer loop: Each iteration traverses the data path
    // through a device topology starting at the input
    // connector and ending at the output connector.
    while (TRUE)
    {
        BOOL bConnected;
        hr = pConnFrom->IsConnected(&bConnected);
        EXIT_ON_ERROR(hr)

        // Does this connector connect to another device?
        if (bConnected == FALSE)
        {
            // This is the end of the data path that
            // stretches from the endpoint device to the
            // system bus or external bus. Verify that
            // the connection type is Software_IO.
            ConnectorType  connType;
            hr = pConnFrom->GetType(&connType);
            EXIT_ON_ERROR(hr)

            if (connType == Software_IO)
            {
                break;  // finished
            }
            EXIT_ON_ERROR(hr = E_FAIL)
        }

        // Get the connector in the next device topology,
        // which lies on the other side of the connection.
        hr = pConnFrom->GetConnectedTo(&pConnTo);
        EXIT_ON_ERROR(hr)
        SAFE_RELEASE(pConnFrom)

        // Get the connector's IPart interface.
        hr = pConnTo->QueryInterface(
                        IID_IPart, (void**)&pPartPrev);
        EXIT_ON_ERROR(hr)
        SAFE_RELEASE(pConnTo)

        // Inner loop: Each iteration traverses one link in a
        // device topology and looks for input multiplexers.
        while (TRUE)
        {
            PartType parttype;
            UINT localId;
            IPartsList *pParts;

            // Follow downstream link to next part.
            hr = pPartPrev->EnumPartsOutgoing(&pParts);
            EXIT_ON_ERROR(hr)

            hr = pParts->GetPart(0, &pPartNext);
            pParts->Release();
            EXIT_ON_ERROR(hr)

            hr = pPartNext->GetPartType(&parttype);
            EXIT_ON_ERROR(hr)

            if (parttype == Connector)
            {
                // We've reached the output connector that
                // lies at the end of this device topology.
                hr = pPartNext->QueryInterface(
                                  IID_IConnector,
                                  (void**)&pConnFrom);
                EXIT_ON_ERROR(hr)

                SAFE_RELEASE(pPartPrev)
                SAFE_RELEASE(pPartNext)
                break;
            }

            // Failure of the following call means only that
            // the part is not a MUX (input selector).
            hr = pPartNext->Activate(
                              CLSCTX_ALL,
                              IID_IAudioInputSelector,
                              (void**)&pSelector);
            if (hr == S_OK)
            {
                // We found a MUX (input selector), so select
                // the input from our endpoint device.
                hr = pPartPrev->GetLocalId(&localId);
                EXIT_ON_ERROR(hr)

                hr = pSelector->SetSelection(localId, NULL);
                EXIT_ON_ERROR(hr)

                SAFE_RELEASE(pSelector)
            }

            SAFE_RELEASE(pPartPrev)
            pPartPrev = pPartNext;
            pPartNext = NULL;
        }
    }

Exit:
    SAFE_RELEASE(pConnFrom)
    SAFE_RELEASE(pConnTo)
    SAFE_RELEASE(pPartPrev)
    SAFE_RELEASE(pPartNext)
    SAFE_RELEASE(pSelector)
    return hr;
}

API DeviceTopology mengimplementasikan antarmuka IAudioInputSelector untuk merangkum multiplexer, seperti yang ada dalam diagram sebelumnya. (An Antarmuka IAudioOutputSelector merangkum demultiplexer.) Dalam contoh kode sebelumnya, perulangan dalam fungsi SelectCaptureDevice meminta setiap subunit yang ditemukannya untuk menemukan apakah subunit adalah multiplexer. Jika subunit adalah multiplexer, maka fungsi memanggil metode IAudioInputSelector::SetSelection untuk memilih input yang terhubung ke aliran dari perangkat titik akhir.

Dalam contoh kode sebelumnya, setiap iterasi perulangan luar melintasi satu topologi perangkat. Saat melintasi topologi perangkat dalam diagram sebelumnya, iterasi pertama melintasi perangkat multiplexer input dan iterasi kedua melintasi perangkat tangkapan gelombang. Fungsi akan berakhir ketika mencapai konektor di tepi kanan diagram. Penghentian terjadi ketika fungsi mendeteksi konektor dengan jenis koneksi Software_IO. Jenis koneksi ini mengidentifikasi titik di mana perangkat adaptor terhubung ke bus sistem.

Panggilan ke metode IPart::GetPartType dalam contoh kode sebelumnya mendapatkan nilai enumerasi IPartType yang menunjukkan apakah bagian saat ini adalah konektor atau subunit pemrosesan audio.

Perulangan dalam dalam contoh kode sebelumnya langkah-langkah di seluruh tautan dari satu bagian ke bagian berikutnya dengan memanggil metode IPart::EnumPartsOutgoing. (Ada juga IPart::Metode EnumPartsIncoming untuk melangkah ke arah yang berlawanan.) Metode ini mengambil objek IPartsList yang berisi daftar semua bagian keluar. Namun, bagian mana pun yang diharapkan oleh fungsi SelectCaptureDevice dalam perangkat tangkapan akan selalu memiliki tepat satu bagian keluar. Dengan demikian, panggilan berikutnya ke IPartsList::GetPart selalu meminta bagian pertama dalam daftar, bagian nomor 0, karena fungsi mengasumsikan bahwa ini adalah satu-satunya bagian dalam daftar.

Jika fungsi SelectCaptureDevice menemukan topologi yang asumsinya tidak valid, fungsi mungkin gagal mengonfigurasi perangkat dengan benar. Untuk menghindari kegagalan seperti itu, versi fungsi yang lebih tujuan umum mungkin melakukan hal berikut:

  • Panggil metode IPartsList::GetCount untuk menentukan jumlah bagian keluar.
  • Untuk setiap bagian keluar, panggil IPartsList::GetPart untuk mulai melintasi jalur data yang mengarah dari bagian tersebut.

Beberapa, tetapi belum tentu semua, bagian memiliki kontrol perangkat keras terkait yang dapat diatur atau didapatkan klien. Bagian tertentu mungkin memiliki kontrol perangkat keras nol, satu, atau lebih. Kontrol perangkat keras diwakili oleh sepasang antarmuka berikut:

  • Antarmuka kontrol generik, IControlInterface, yang memiliki metode yang umum untuk semua kontrol perangkat keras.
  • Antarmuka khusus fungsi (misalnya, IAudioVolumeLevel) yang mengekspos parameter kontrol untuk jenis kontrol perangkat keras tertentu (misalnya, kontrol volume).

Untuk menghitung kontrol perangkat keras untuk suatu bagian, klien terlebih dahulu memanggil metode IPart::GetControlInterfaceCount untuk menentukan jumlah kontrol perangkat keras yang terkait dengan bagian tersebut. Selanjutnya, klien melakukan serangkaian panggilan ke metode IPart::GetControlInterface untuk mendapatkan antarmuka IControlInterface untuk setiap kontrol perangkat keras. Terakhir, klien mendapatkan antarmuka khusus fungsi untuk setiap kontrol perangkat keras dengan memanggil metode IControlInterface::GetIID untuk mendapatkan ID antarmuka. Klien memanggil metode IPart::Activate dengan ID ini untuk mendapatkan antarmuka khusus fungsi.

Bagian yang merupakan konektor mungkin mendukung salah satu antarmuka kontrol khusus fungsi berikut:

Bagian yang merupakan subunit mungkin mendukung satu atau beberapa antarmuka kontrol khusus fungsi berikut:

Bagian mendukung antarmuka IDeviceSpecificProperty hanya jika kontrol perangkat keras yang mendasarinya memiliki nilai kontrol khusus perangkat dan kontrol tidak dapat diwakili secara memadai oleh antarmuka khusus fungsi lainnya dalam daftar sebelumnya. Biasanya, properti khusus perangkat hanya berguna untuk klien yang dapat menyimpulkan arti nilai properti dari informasi seperti jenis bagian, subjenis bagian, dan nama bagian. Klien dapat memperoleh informasi ini dengan memanggil metode IPart::GetPartType, IPart::GetSubType, dan IPart::GetName.

Panduan Pemrograman