/* * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #ifndef MODULES_AUDIO_DEVICE_WIN_CORE_AUDIO_UTILITY_WIN_H_ #define MODULES_AUDIO_DEVICE_WIN_CORE_AUDIO_UTILITY_WIN_H_ #include #include #include #include #include #include #include #include #include #include "absl/strings/string_view.h" #include "api/units/time_delta.h" #include "modules/audio_device/audio_device_name.h" #include "modules/audio_device/include/audio_device_defines.h" #include "rtc_base/logging.h" #include "rtc_base/string_utils.h" #pragma comment(lib, "Avrt.lib") namespace webrtc { namespace webrtc_win { // Utility class which registers a thread with MMCSS in the constructor and // deregisters MMCSS in the destructor. The task name is given by `task_name`. // The Multimedia Class Scheduler service (MMCSS) enables multimedia // applications to ensure that their time-sensitive processing receives // prioritized access to CPU resources without denying CPU resources to // lower-priority applications. class ScopedMMCSSRegistration { public: const char* PriorityClassToString(DWORD priority_class) { switch (priority_class) { case ABOVE_NORMAL_PRIORITY_CLASS: return "ABOVE_NORMAL"; case BELOW_NORMAL_PRIORITY_CLASS: return "BELOW_NORMAL"; case HIGH_PRIORITY_CLASS: return "HIGH"; case IDLE_PRIORITY_CLASS: return "IDLE"; case NORMAL_PRIORITY_CLASS: return "NORMAL"; case REALTIME_PRIORITY_CLASS: return "REALTIME"; default: return "INVALID"; } } const char* PriorityToString(int priority) { switch (priority) { case THREAD_PRIORITY_ABOVE_NORMAL: return "ABOVE_NORMAL"; case THREAD_PRIORITY_BELOW_NORMAL: return "BELOW_NORMAL"; case THREAD_PRIORITY_HIGHEST: return "HIGHEST"; case THREAD_PRIORITY_IDLE: return "IDLE"; case THREAD_PRIORITY_LOWEST: return "LOWEST"; case THREAD_PRIORITY_NORMAL: return "NORMAL"; case THREAD_PRIORITY_TIME_CRITICAL: return "TIME_CRITICAL"; default: // Can happen in combination with REALTIME_PRIORITY_CLASS. return "INVALID"; } } explicit ScopedMMCSSRegistration(const wchar_t* task_name) { RTC_DLOG(LS_INFO) << "ScopedMMCSSRegistration: " << rtc::ToUtf8(task_name); // Register the calling thread with MMCSS for the supplied `task_name`. DWORD mmcss_task_index = 0; mmcss_handle_ = AvSetMmThreadCharacteristicsW(task_name, &mmcss_task_index); if (mmcss_handle_ == nullptr) { RTC_LOG(LS_ERROR) << "Failed to enable MMCSS on this thread: " << GetLastError(); } else { const DWORD priority_class = GetPriorityClass(GetCurrentProcess()); const int priority = GetThreadPriority(GetCurrentThread()); RTC_DLOG(LS_INFO) << "priority class: " << PriorityClassToString(priority_class) << "(" << priority_class << ")"; RTC_DLOG(LS_INFO) << "priority: " << PriorityToString(priority) << "(" << priority << ")"; } } ~ScopedMMCSSRegistration() { if (Succeeded()) { // Deregister with MMCSS. RTC_DLOG(LS_INFO) << "~ScopedMMCSSRegistration"; AvRevertMmThreadCharacteristics(mmcss_handle_); } } ScopedMMCSSRegistration(const ScopedMMCSSRegistration&) = delete; ScopedMMCSSRegistration& operator=(const ScopedMMCSSRegistration&) = delete; bool Succeeded() const { return mmcss_handle_ != nullptr; } private: HANDLE mmcss_handle_ = nullptr; }; // A PROPVARIANT that is automatically initialized and cleared upon respective // construction and destruction of this class. class ScopedPropVariant { public: ScopedPropVariant() { PropVariantInit(&pv_); } ~ScopedPropVariant() { Reset(); } ScopedPropVariant(const ScopedPropVariant&) = delete; ScopedPropVariant& operator=(const ScopedPropVariant&) = delete; bool operator==(const ScopedPropVariant&) const = delete; bool operator!=(const ScopedPropVariant&) const = delete; // Returns a pointer to the underlying PROPVARIANT for use as an out param in // a function call. PROPVARIANT* Receive() { RTC_DCHECK_EQ(pv_.vt, VT_EMPTY); return &pv_; } // Clears the instance to prepare it for re-use (e.g., via Receive). void Reset() { if (pv_.vt != VT_EMPTY) { HRESULT result = PropVariantClear(&pv_); RTC_DCHECK_EQ(result, S_OK); } } const PROPVARIANT& get() const { return pv_; } const PROPVARIANT* ptr() const { return &pv_; } private: PROPVARIANT pv_; }; // Simple scoped memory releaser class for COM allocated memory. template class ScopedCoMem { public: ScopedCoMem() : mem_ptr_(nullptr) {} ~ScopedCoMem() { Reset(nullptr); } ScopedCoMem(const ScopedCoMem&) = delete; ScopedCoMem& operator=(const ScopedCoMem&) = delete; T** operator&() { // NOLINT RTC_DCHECK(mem_ptr_ == nullptr); // To catch memory leaks. return &mem_ptr_; } operator T*() { return mem_ptr_; } T* operator->() { RTC_DCHECK(mem_ptr_ != nullptr); return mem_ptr_; } const T* operator->() const { RTC_DCHECK(mem_ptr_ != nullptr); return mem_ptr_; } explicit operator bool() const { return mem_ptr_; } friend bool operator==(const ScopedCoMem& lhs, std::nullptr_t) { return lhs.Get() == nullptr; } friend bool operator==(std::nullptr_t, const ScopedCoMem& rhs) { return rhs.Get() == nullptr; } friend bool operator!=(const ScopedCoMem& lhs, std::nullptr_t) { return lhs.Get() != nullptr; } friend bool operator!=(std::nullptr_t, const ScopedCoMem& rhs) { return rhs.Get() != nullptr; } void Reset(T* ptr) { if (mem_ptr_) CoTaskMemFree(mem_ptr_); mem_ptr_ = ptr; } T* Get() const { return mem_ptr_; } private: T* mem_ptr_; }; // A HANDLE that is automatically initialized and closed upon respective // construction and destruction of this class. class ScopedHandle { public: ScopedHandle() : handle_(nullptr) {} explicit ScopedHandle(HANDLE h) : handle_(nullptr) { Set(h); } ~ScopedHandle() { Close(); } ScopedHandle& operator=(const ScopedHandle&) = delete; bool operator==(const ScopedHandle&) const = delete; bool operator!=(const ScopedHandle&) const = delete; // Use this instead of comparing to INVALID_HANDLE_VALUE. bool IsValid() const { return handle_ != nullptr; } void Set(HANDLE new_handle) { Close(); // Windows is inconsistent about invalid handles. // See https://blogs.msdn.microsoft.com/oldnewthing/20040302-00/?p=40443 // for details. if (new_handle != INVALID_HANDLE_VALUE) { handle_ = new_handle; } } HANDLE Get() const { return handle_; } operator HANDLE() const { return handle_; } void Close() { if (handle_) { if (!::CloseHandle(handle_)) { RTC_DCHECK_NOTREACHED(); } handle_ = nullptr; } } private: HANDLE handle_; }; // Utility methods for the Core Audio API on Windows. // Always ensure that Core Audio is supported before using these methods. // Use webrtc_win::core_audio_utility::IsSupported() for this purpose. // Also, all methods must be called on a valid COM thread. This can be done // by using the ScopedCOMInitializer helper class. // These methods are based on media::CoreAudioUtil in Chrome. namespace core_audio_utility { // Helper class which automates casting between WAVEFORMATEX and // WAVEFORMATEXTENSIBLE raw pointers using implicit constructors and // operator overloading. Note that, no memory is allocated by this utility // structure. It only serves as a handle (or a wrapper) of the structure // provided to it at construction. class WaveFormatWrapper { public: WaveFormatWrapper(WAVEFORMATEXTENSIBLE* p) : ptr_(reinterpret_cast(p)) {} WaveFormatWrapper(WAVEFORMATEX* p) : ptr_(p) {} ~WaveFormatWrapper() = default; operator WAVEFORMATEX*() const { return ptr_; } WAVEFORMATEX* operator->() const { return ptr_; } WAVEFORMATEX* get() const { return ptr_; } WAVEFORMATEXTENSIBLE* GetExtensible() const; bool IsExtensible() const; bool IsPcm() const; bool IsFloat() const; size_t size() const; private: WAVEFORMATEX* ptr_; }; // Returns true if Windows Core Audio is supported. // Always verify that this method returns true before using any of the // other methods in this class. bool IsSupported(); // Returns true if Multimedia Class Scheduler service (MMCSS) is supported. // The MMCSS enables multimedia applications to ensure that their time-sensitive // processing receives prioritized access to CPU resources without denying CPU // resources to lower-priority applications. bool IsMMCSSSupported(); // The MMDevice API lets clients discover the audio endpoint devices in the // system and determine which devices are suitable for the application to use. // Header file Mmdeviceapi.h defines the interfaces in the MMDevice API. // Number of active audio devices in the specified data flow direction. // Set `data_flow` to eAll to retrieve the total number of active audio // devices. int NumberOfActiveDevices(EDataFlow data_flow); // Returns 1, 2, or 3 depending on what version of IAudioClient the platform // supports. // Example: IAudioClient2 is supported on Windows 8 and higher => 2 is returned. uint32_t GetAudioClientVersion(); // Creates an IMMDeviceEnumerator interface which provides methods for // enumerating audio endpoint devices. // TODO(henrika): IMMDeviceEnumerator::RegisterEndpointNotificationCallback. Microsoft::WRL::ComPtr CreateDeviceEnumerator(); // These functions return the unique device id of the default or // communications input/output device, or an empty string if no such device // exists or if the device has been disabled. std::string GetDefaultInputDeviceID(); std::string GetDefaultOutputDeviceID(); std::string GetCommunicationsInputDeviceID(); std::string GetCommunicationsOutputDeviceID(); // Creates an IMMDevice interface corresponding to the unique device id in // `device_id`, or by data-flow direction and role if `device_id` is set to // AudioDeviceName::kDefaultDeviceId. Microsoft::WRL::ComPtr CreateDevice(absl::string_view device_id, EDataFlow data_flow, ERole role); // Returns the unique ID and user-friendly name of a given endpoint device. // Example: "{0.0.1.00000000}.{8db6020f-18e3-4f25-b6f5-7726c9122574}", and // "Microphone (Realtek High Definition Audio)". webrtc::AudioDeviceName GetDeviceName(IMMDevice* device); // Gets the user-friendly name of the endpoint device which is represented // by a unique id in `device_id`, or by data-flow direction and role if // `device_id` is set to AudioDeviceName::kDefaultDeviceId. std::string GetFriendlyName(absl::string_view device_id, EDataFlow data_flow, ERole role); // Query if the audio device is a rendering device or a capture device. EDataFlow GetDataFlow(IMMDevice* device); // Enumerates all input devices and adds the names (friendly name and unique // device id) to the list in `device_names`. bool GetInputDeviceNames(webrtc::AudioDeviceNames* device_names); // Enumerates all output devices and adds the names (friendly name and unique // device id) to the list in `device_names`. bool GetOutputDeviceNames(webrtc::AudioDeviceNames* device_names); // The Windows Audio Session API (WASAPI) enables client applications to // manage the flow of audio data between the application and an audio endpoint // device. Header files Audioclient.h and Audiopolicy.h define the WASAPI // interfaces. // Creates an IAudioSessionManager2 interface for the specified `device`. // This interface provides access to e.g. the IAudioSessionEnumerator Microsoft::WRL::ComPtr CreateSessionManager2( IMMDevice* device); // Creates an IAudioSessionEnumerator interface for the specified `device`. // The client can use the interface to enumerate audio sessions on the audio // device Microsoft::WRL::ComPtr CreateSessionEnumerator( IMMDevice* device); // Number of active audio sessions for the given `device`. Expired or inactive // sessions are not included. int NumberOfActiveSessions(IMMDevice* device); // Creates an IAudioClient instance for a specific device or the default // device specified by data-flow direction and role. Microsoft::WRL::ComPtr CreateClient(absl::string_view device_id, EDataFlow data_flow, ERole role); Microsoft::WRL::ComPtr CreateClient2(absl::string_view device_id, EDataFlow data_flow, ERole role); Microsoft::WRL::ComPtr CreateClient3(absl::string_view device_id, EDataFlow data_flow, ERole role); // Sets the AudioCategory_Communications category. Should be called before // GetSharedModeMixFormat() and IsFormatSupported(). The `client` argument must // be an IAudioClient2 or IAudioClient3 interface pointer, hence only supported // on Windows 8 and above. // TODO(henrika): evaluate effect (if any). HRESULT SetClientProperties(IAudioClient2* client); // Returns the buffer size limits of the hardware audio engine in // 100-nanosecond units given a specified `format`. Does not require prior // audio stream initialization. The `client` argument must be an IAudioClient2 // or IAudioClient3 interface pointer, hence only supported on Windows 8 and // above. // TODO(henrika): always fails with AUDCLNT_E_OFFLOAD_MODE_ONLY. HRESULT GetBufferSizeLimits(IAudioClient2* client, const WAVEFORMATEXTENSIBLE* format, REFERENCE_TIME* min_buffer_duration, REFERENCE_TIME* max_buffer_duration); // Get the mix format that the audio engine uses internally for processing // of shared-mode streams. The client can call this method before calling // IAudioClient::Initialize. When creating a shared-mode stream for an audio // endpoint device, the Initialize method always accepts the stream format // obtained by this method. HRESULT GetSharedModeMixFormat(IAudioClient* client, WAVEFORMATEXTENSIBLE* format); // Returns true if the specified `client` supports the format in `format` // for the given `share_mode` (shared or exclusive). The client can call this // method before calling IAudioClient::Initialize. bool IsFormatSupported(IAudioClient* client, AUDCLNT_SHAREMODE share_mode, const WAVEFORMATEXTENSIBLE* format); // For a shared-mode stream, the audio engine periodically processes the // data in the endpoint buffer at the period obtained in `device_period`. // For an exclusive mode stream, `device_period` corresponds to the minimum // time interval between successive processing by the endpoint device. // This period plus the stream latency between the buffer and endpoint device // represents the minimum possible latency that an audio application can // achieve. The time in `device_period` is expressed in 100-nanosecond units. HRESULT GetDevicePeriod(IAudioClient* client, AUDCLNT_SHAREMODE share_mode, REFERENCE_TIME* device_period); // Returns the range of periodicities supported by the engine for the specified // stream `format`. The periodicity of the engine is the rate at which the // engine wakes an event-driven audio client to transfer audio data to or from // the engine. Can be used for low-latency support on some devices. // The `client` argument must be an IAudioClient3 interface pointer, hence only // supported on Windows 10 and above. HRESULT GetSharedModeEnginePeriod(IAudioClient3* client3, const WAVEFORMATEXTENSIBLE* format, uint32_t* default_period_in_frames, uint32_t* fundamental_period_in_frames, uint32_t* min_period_in_frames, uint32_t* max_period_in_frames); // Get the preferred audio parameters for the given `client` corresponding to // the stream format that the audio engine uses for its internal processing of // shared-mode streams. The acquired values should only be utilized for shared // mode streamed since there are no preferred settings for an exclusive mode // stream. HRESULT GetPreferredAudioParameters(IAudioClient* client, webrtc::AudioParameters* params); // As above but override the preferred sample rate and use `sample_rate` // instead. Intended mainly for testing purposes and in combination with rate // conversion. HRESULT GetPreferredAudioParameters(IAudioClient* client, webrtc::AudioParameters* params, uint32_t sample_rate); // After activating an IAudioClient interface on an audio endpoint device, // the client must initialize it once, and only once, to initialize the audio // stream between the client and the device. In shared mode, the client // connects indirectly through the audio engine which does the mixing. // If a valid event is provided in `event_handle`, the client will be // initialized for event-driven buffer handling. If `event_handle` is set to // nullptr, event-driven buffer handling is not utilized. To achieve the // minimum stream latency between the client application and audio endpoint // device, set `buffer_duration` to 0. A client has the option of requesting a // buffer size that is larger than what is strictly necessary to make timing // glitches rare or nonexistent. Increasing the buffer size does not necessarily // increase the stream latency. Each unit of reference time is 100 nanoseconds. // The `auto_convert_pcm` parameter can be used for testing purposes to ensure // that the sample rate of the client side does not have to match the audio // engine mix format. If `auto_convert_pcm` is set to true, a rate converter // will be inserted to convert between the sample rate in `format` and the // preferred rate given by GetPreferredAudioParameters(). // The output parameter `endpoint_buffer_size` contains the size of the // endpoint buffer and it is expressed as the number of audio frames the // buffer can hold. HRESULT SharedModeInitialize(IAudioClient* client, const WAVEFORMATEXTENSIBLE* format, HANDLE event_handle, REFERENCE_TIME buffer_duration, bool auto_convert_pcm, uint32_t* endpoint_buffer_size); // Works as SharedModeInitialize() but adds support for using smaller engine // periods than the default period. // The `client` argument must be an IAudioClient3 interface pointer, hence only // supported on Windows 10 and above. // TODO(henrika): can probably be merged into SharedModeInitialize() to avoid // duplicating code. Keeping as separate method for now until decided if we // need low-latency support. HRESULT SharedModeInitializeLowLatency(IAudioClient3* client, const WAVEFORMATEXTENSIBLE* format, HANDLE event_handle, uint32_t period_in_frames, bool auto_convert_pcm, uint32_t* endpoint_buffer_size); // Creates an IAudioRenderClient client for an existing IAudioClient given by // `client`. The IAudioRenderClient interface enables a client to write // output data to a rendering endpoint buffer. The methods in this interface // manage the movement of data packets that contain audio-rendering data. Microsoft::WRL::ComPtr CreateRenderClient( IAudioClient* client); // Creates an IAudioCaptureClient client for an existing IAudioClient given by // `client`. The IAudioCaptureClient interface enables a client to read // input data from a capture endpoint buffer. The methods in this interface // manage the movement of data packets that contain capture data. Microsoft::WRL::ComPtr CreateCaptureClient( IAudioClient* client); // Creates an IAudioClock interface for an existing IAudioClient given by // `client`. The IAudioClock interface enables a client to monitor a stream's // data rate and the current position in the stream. Microsoft::WRL::ComPtr CreateAudioClock(IAudioClient* client); // Creates an AudioSessionControl interface for an existing IAudioClient given // by `client`. The IAudioControl interface enables a client to configure the // control parameters for an audio session and to monitor events in the session. Microsoft::WRL::ComPtr CreateAudioSessionControl( IAudioClient* client); // Creates an ISimpleAudioVolume interface for an existing IAudioClient given by // `client`. This interface enables a client to control the master volume level // of an active audio session. Microsoft::WRL::ComPtr CreateSimpleAudioVolume( IAudioClient* client); // Fills up the endpoint rendering buffer with silence for an existing // IAudioClient given by `client` and a corresponding IAudioRenderClient // given by `render_client`. bool FillRenderEndpointBufferWithSilence(IAudioClient* client, IAudioRenderClient* render_client); // Prints/logs all fields of the format structure in `format`. // Also supports extended versions (WAVEFORMATEXTENSIBLE). std::string WaveFormatToString(WaveFormatWrapper format); // Converts Windows internal REFERENCE_TIME (100 nanosecond units) into // generic webrtc::TimeDelta which then can be converted to any time unit. webrtc::TimeDelta ReferenceTimeToTimeDelta(REFERENCE_TIME time); // Converts size expressed in number of audio frames, `num_frames`, into // milliseconds given a specified `sample_rate`. double FramesToMilliseconds(uint32_t num_frames, uint16_t sample_rate); // Converts a COM error into a human-readable string. std::string ErrorToString(const _com_error& error); } // namespace core_audio_utility } // namespace webrtc_win } // namespace webrtc #endif // MODULES_AUDIO_DEVICE_WIN_CORE_AUDIO_UTILITY_WIN_H_