mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-11-13 15:27:21 +00:00
[idd] audio: add the mediated device connector
Open the optional audio broker from a passive worker and move bounded playback and capture packets through a transport-neutral audio hub. Attach audio sources after transport initialization without making audio driver availability part of display transport readiness.
This commit is contained in:
@@ -66,6 +66,8 @@
|
|||||||
<ClCompile Include="postprocess\effect\CFormatEffect.cpp" />
|
<ClCompile Include="postprocess\effect\CFormatEffect.cpp" />
|
||||||
<ClCompile Include="postprocess\effect\CHDR16to10Effect.cpp" />
|
<ClCompile Include="postprocess\effect\CHDR16to10Effect.cpp" />
|
||||||
<ClCompile Include="postprocess\effect\CRGB24Effect.cpp" />
|
<ClCompile Include="postprocess\effect\CRGB24Effect.cpp" />
|
||||||
|
<ClCompile Include="audio\CLGAudioDevice.cpp" />
|
||||||
|
<ClCompile Include="transport\CAudioHub.cpp" />
|
||||||
<ClCompile Include="transport\CClipboardHub.cpp" />
|
<ClCompile Include="transport\CClipboardHub.cpp" />
|
||||||
<ClCompile Include="transport\CControlHub.cpp" />
|
<ClCompile Include="transport\CControlHub.cpp" />
|
||||||
<ClCompile Include="transport\CFrameHub.cpp" />
|
<ClCompile Include="transport\CFrameHub.cpp" />
|
||||||
@@ -127,6 +129,8 @@
|
|||||||
<ClInclude Include="postprocess\effect\CFormatEffect.h" />
|
<ClInclude Include="postprocess\effect\CFormatEffect.h" />
|
||||||
<ClInclude Include="postprocess\effect\CHDR16to10Effect.h" />
|
<ClInclude Include="postprocess\effect\CHDR16to10Effect.h" />
|
||||||
<ClInclude Include="postprocess\effect\CRGB24Effect.h" />
|
<ClInclude Include="postprocess\effect\CRGB24Effect.h" />
|
||||||
|
<ClInclude Include="audio\CLGAudioDevice.h" />
|
||||||
|
<ClInclude Include="transport\CAudioHub.h" />
|
||||||
<ClInclude Include="transport\CClipboardHub.h" />
|
<ClInclude Include="transport\CClipboardHub.h" />
|
||||||
<ClInclude Include="transport\CControlHub.h" />
|
<ClInclude Include="transport\CControlHub.h" />
|
||||||
<ClInclude Include="transport\CFrameHub.h" />
|
<ClInclude Include="transport\CFrameHub.h" />
|
||||||
@@ -139,6 +143,7 @@
|
|||||||
<ClInclude Include="transport\FrameProfile.h" />
|
<ClInclude Include="transport\FrameProfile.h" />
|
||||||
<ClInclude Include="transport\FrameIn.h" />
|
<ClInclude Include="transport\FrameIn.h" />
|
||||||
<ClInclude Include="transport\IClipboardSource.h" />
|
<ClInclude Include="transport\IClipboardSource.h" />
|
||||||
|
<ClInclude Include="transport\IAudioSource.h" />
|
||||||
<ClInclude Include="transport\IControlSink.h" />
|
<ClInclude Include="transport\IControlSink.h" />
|
||||||
<ClInclude Include="transport\IControlTransport.h" />
|
<ClInclude Include="transport\IControlTransport.h" />
|
||||||
<ClInclude Include="transport\IFrameSink.h" />
|
<ClInclude Include="transport\IFrameSink.h" />
|
||||||
|
|||||||
@@ -17,6 +17,9 @@
|
|||||||
<Filter Include="Capture">
|
<Filter Include="Capture">
|
||||||
<UniqueIdentifier>{202B79B2-5CCF-4AD4-B4C5-F5602419683D}</UniqueIdentifier>
|
<UniqueIdentifier>{202B79B2-5CCF-4AD4-B4C5-F5602419683D}</UniqueIdentifier>
|
||||||
</Filter>
|
</Filter>
|
||||||
|
<Filter Include="Audio">
|
||||||
|
<UniqueIdentifier>{86750913-8C21-4F76-8E51-9737D4963E50}</UniqueIdentifier>
|
||||||
|
</Filter>
|
||||||
<Filter Include="D3D">
|
<Filter Include="D3D">
|
||||||
<UniqueIdentifier>{4B3C58D3-5F40-49AB-894E-B83EB44E6CF5}</UniqueIdentifier>
|
<UniqueIdentifier>{4B3C58D3-5F40-49AB-894E-B83EB44E6CF5}</UniqueIdentifier>
|
||||||
</Filter>
|
</Filter>
|
||||||
@@ -166,6 +169,15 @@
|
|||||||
<ClInclude Include="postprocess\effect\CRGB24Effect.h">
|
<ClInclude Include="postprocess\effect\CRGB24Effect.h">
|
||||||
<Filter>Post-processing\Effects</Filter>
|
<Filter>Post-processing\Effects</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="audio\CLGAudioDevice.h">
|
||||||
|
<Filter>Audio</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="transport\CAudioHub.h">
|
||||||
|
<Filter>Transport</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="transport\IAudioSource.h">
|
||||||
|
<Filter>Transport</Filter>
|
||||||
|
</ClInclude>
|
||||||
<ClInclude Include="transport\CClipboardHub.h">
|
<ClInclude Include="transport\CClipboardHub.h">
|
||||||
<Filter>Transport</Filter>
|
<Filter>Transport</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
@@ -378,6 +390,12 @@
|
|||||||
<ClCompile Include="postprocess\effect\CRGB24Effect.cpp">
|
<ClCompile Include="postprocess\effect\CRGB24Effect.cpp">
|
||||||
<Filter>Post-processing\Effects</Filter>
|
<Filter>Post-processing\Effects</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="audio\CLGAudioDevice.cpp">
|
||||||
|
<Filter>Audio</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="transport\CAudioHub.cpp">
|
||||||
|
<Filter>Transport</Filter>
|
||||||
|
</ClCompile>
|
||||||
<ClCompile Include="transport\CClipboardHub.cpp">
|
<ClCompile Include="transport\CClipboardHub.cpp">
|
||||||
<Filter>Transport</Filter>
|
<Filter>Transport</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
|||||||
1432
idd/LGIdd/audio/CLGAudioDevice.cpp
Normal file
1432
idd/LGIdd/audio/CLGAudioDevice.cpp
Normal file
File diff suppressed because it is too large
Load Diff
94
idd/LGIdd/audio/CLGAudioDevice.h
Normal file
94
idd/LGIdd/audio/CLGAudioDevice.h
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
/**
|
||||||
|
* Looking Glass
|
||||||
|
* Copyright © 2017-2026 The Looking Glass Authors
|
||||||
|
* https://looking-glass.io
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License as published by the Free
|
||||||
|
* Software Foundation; either version 2 of the License, or (at your option)
|
||||||
|
* any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
||||||
|
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "transport/IAudioSource.h"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
struct LGAudioDeviceSnapshot
|
||||||
|
{
|
||||||
|
AudioEndpointStatus status;
|
||||||
|
bool open = false;
|
||||||
|
bool bound = false;
|
||||||
|
bool activeRequested = false;
|
||||||
|
bool deactivating = false;
|
||||||
|
uint64_t renderPackets = 0;
|
||||||
|
uint64_t renderBytes = 0;
|
||||||
|
uint64_t capturePackets = 0;
|
||||||
|
uint64_t captureBytes = 0;
|
||||||
|
uint64_t reconnects = 0;
|
||||||
|
uint32_t lastError = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ILGAudioDeviceEvents
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~ILGAudioDeviceEvents() = default;
|
||||||
|
|
||||||
|
virtual void LGAudioState(const AudioEndpointStatus& state) = 0;
|
||||||
|
virtual AudioResult LGAudioPlayback(const AudioPacket& packet,
|
||||||
|
const uint8_t * data, size_t size) = 0;
|
||||||
|
virtual void LGAudioCaptureReady() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ILGAudioDevice
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~ILGAudioDevice() = default;
|
||||||
|
|
||||||
|
// Methods are thread-safe and nonblocking except Stop. Stop is a callback-
|
||||||
|
// quiescence barrier; after it returns no event callback can still run.
|
||||||
|
virtual bool Start(ILGAudioDeviceEvents& events) = 0;
|
||||||
|
virtual void Stop() = 0;
|
||||||
|
virtual LGAudioDeviceSnapshot Snapshot() const = 0;
|
||||||
|
virtual AudioResult SetActive(bool active) = 0;
|
||||||
|
virtual AudioResult SendCapture(const AudioPacket& packet,
|
||||||
|
const uint8_t * data, size_t size) = 0;
|
||||||
|
virtual AudioResult SetClockFeedback(
|
||||||
|
const AudioClockFeedback& feedback) = 0;
|
||||||
|
virtual void PlaybackReceiveReady() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CLGAudioDevice final : public ILGAudioDevice
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
class CState;
|
||||||
|
std::unique_ptr<CState> m_state;
|
||||||
|
|
||||||
|
public:
|
||||||
|
CLGAudioDevice();
|
||||||
|
~CLGAudioDevice();
|
||||||
|
|
||||||
|
CLGAudioDevice(const CLGAudioDevice&) = delete;
|
||||||
|
CLGAudioDevice& operator=(const CLGAudioDevice&) = delete;
|
||||||
|
|
||||||
|
bool Start(ILGAudioDeviceEvents& events) override;
|
||||||
|
void Stop() override;
|
||||||
|
|
||||||
|
LGAudioDeviceSnapshot Snapshot() const override;
|
||||||
|
AudioResult SetActive(bool active) override;
|
||||||
|
AudioResult SendCapture(const AudioPacket& packet,
|
||||||
|
const uint8_t * data, size_t size) override;
|
||||||
|
AudioResult SetClockFeedback(
|
||||||
|
const AudioClockFeedback& feedback) override;
|
||||||
|
void PlaybackReceiveReady() override;
|
||||||
|
};
|
||||||
386
idd/LGIdd/transport/CAudioHub.cpp
Normal file
386
idd/LGIdd/transport/CAudioHub.cpp
Normal file
@@ -0,0 +1,386 @@
|
|||||||
|
/**
|
||||||
|
* Looking Glass
|
||||||
|
* Copyright © 2017-2026 The Looking Glass Authors
|
||||||
|
* https://looking-glass.io
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License as published by the Free
|
||||||
|
* Software Foundation; either version 2 of the License, or (at your option)
|
||||||
|
* any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
||||||
|
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "transport/CAudioHub.h"
|
||||||
|
|
||||||
|
#include <new>
|
||||||
|
|
||||||
|
CAudioHub::CAudioHub() :
|
||||||
|
m_device(new (std::nothrow) CLGAudioDevice())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
CAudioHub::CAudioHub(std::unique_ptr<ILGAudioDevice> device) :
|
||||||
|
m_device(std::move(device))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
CAudioHub::~CAudioHub()
|
||||||
|
{
|
||||||
|
Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CAudioHub::StartDevice()
|
||||||
|
{
|
||||||
|
{
|
||||||
|
CSRWSharedLock lock(m_lock);
|
||||||
|
if (m_stopped || !m_device)
|
||||||
|
return false;
|
||||||
|
if (m_deviceStarted)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_device->Start(*this))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
CSRWExclusiveLock lock(m_lock);
|
||||||
|
if (m_stopped)
|
||||||
|
{
|
||||||
|
lock.Unlock();
|
||||||
|
m_device->Stop();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
m_deviceStarted = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CAudioHub::Start()
|
||||||
|
{
|
||||||
|
CSRWExclusiveLock lifecycleLock(m_lifecycleLock);
|
||||||
|
return StartDevice();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CAudioHub::MarkFailed(IAudioSource * source)
|
||||||
|
{
|
||||||
|
CSRWExclusiveLock lock(m_lock);
|
||||||
|
if (!source || m_source != source || (!m_active && !m_reserved))
|
||||||
|
return false;
|
||||||
|
const bool changed = !m_failed;
|
||||||
|
m_failed = true;
|
||||||
|
m_failurePending = true;
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CAudioHub::Bind(
|
||||||
|
BackendId backend, uint32_t epoch, IAudioSource& source)
|
||||||
|
{
|
||||||
|
CSRWExclusiveLock lifecycleLock(m_lifecycleLock);
|
||||||
|
if (!backend || !epoch || !StartDevice())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
{
|
||||||
|
CSRWExclusiveLock lock(m_lock);
|
||||||
|
if (m_stopped || m_source || m_active || m_reserved ||
|
||||||
|
m_deactivating)
|
||||||
|
return false;
|
||||||
|
m_source = &source;
|
||||||
|
m_backend = backend;
|
||||||
|
m_epoch = epoch;
|
||||||
|
m_active = true;
|
||||||
|
m_reserved = true;
|
||||||
|
m_failed = false;
|
||||||
|
m_failurePending = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
CSRWExclusiveLock callbackLock(m_callbackLock);
|
||||||
|
const bool started = source.Start(*this);
|
||||||
|
bool attached = false;
|
||||||
|
{
|
||||||
|
CSRWExclusiveLock lock(m_lock);
|
||||||
|
if (started && m_source == &source && m_backend == backend &&
|
||||||
|
m_epoch == epoch && m_active && m_reserved && !m_failed &&
|
||||||
|
!m_stopped)
|
||||||
|
{
|
||||||
|
m_running = true;
|
||||||
|
m_reserved = false;
|
||||||
|
attached = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_active = false;
|
||||||
|
m_running = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!attached)
|
||||||
|
{
|
||||||
|
source.Stop();
|
||||||
|
CSRWExclusiveLock lock(m_lock);
|
||||||
|
if (m_source == &source && m_backend == backend && m_epoch == epoch)
|
||||||
|
{
|
||||||
|
m_source = nullptr;
|
||||||
|
m_backend = 0;
|
||||||
|
m_epoch = 0;
|
||||||
|
m_active = false;
|
||||||
|
m_running = false;
|
||||||
|
m_reserved = false;
|
||||||
|
m_failed = false;
|
||||||
|
m_failurePending = false;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
source.AudioState(m_device->Snapshot().status);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CAudioHub::Unbind(BackendId backend, uint32_t epoch)
|
||||||
|
{
|
||||||
|
CSRWExclusiveLock lifecycleLock(m_lifecycleLock);
|
||||||
|
IAudioSource * source = nullptr;
|
||||||
|
bool stop = false;
|
||||||
|
{
|
||||||
|
CSRWExclusiveLock lock(m_lock);
|
||||||
|
if ((!m_active && !m_failed && !m_reserved) ||
|
||||||
|
m_backend != backend || m_epoch != epoch || !m_source)
|
||||||
|
return;
|
||||||
|
source = m_source;
|
||||||
|
stop = m_running || m_reserved;
|
||||||
|
m_active = false;
|
||||||
|
m_running = false;
|
||||||
|
m_reserved = true;
|
||||||
|
m_failed = false;
|
||||||
|
m_failurePending = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Serialize the request and barrier publication with state callbacks so
|
||||||
|
// a fast inactive callback cannot pass before the barrier is visible.
|
||||||
|
CSRWExclusiveLock callbackLock(m_callbackLock);
|
||||||
|
// Start deactivation before asking the source to quiesce, then repeat it
|
||||||
|
// after Stop so a target call already in flight cannot reactivate the
|
||||||
|
// device on behalf of the departing source.
|
||||||
|
m_device->SetActive(false);
|
||||||
|
if (stop)
|
||||||
|
source->Stop();
|
||||||
|
const LGAudioDeviceSnapshot before = m_device->Snapshot();
|
||||||
|
const AudioResult deactivate = m_device->SetActive(false);
|
||||||
|
const LGAudioDeviceSnapshot after = m_device->Snapshot();
|
||||||
|
const bool pending = deactivate == AudioResult::ACCEPTED &&
|
||||||
|
(before.status.active || before.activeRequested ||
|
||||||
|
after.status.active || after.activeRequested ||
|
||||||
|
after.deactivating);
|
||||||
|
{
|
||||||
|
CSRWExclusiveLock lock(m_lock);
|
||||||
|
m_deactivating = pending;
|
||||||
|
m_deactivationGeneration = pending ?
|
||||||
|
before.status.generation : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CSRWExclusiveLock lock(m_lock);
|
||||||
|
if (m_source == source && m_backend == backend && m_epoch == epoch)
|
||||||
|
{
|
||||||
|
m_source = nullptr;
|
||||||
|
m_backend = 0;
|
||||||
|
m_epoch = 0;
|
||||||
|
m_active = false;
|
||||||
|
m_running = false;
|
||||||
|
m_reserved = false;
|
||||||
|
m_failed = false;
|
||||||
|
m_failurePending = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CAudioHub::TakeFailure(SourceKey& source)
|
||||||
|
{
|
||||||
|
CSRWSharedLock lifecycleLock(m_lifecycleLock);
|
||||||
|
CSRWExclusiveLock lock(m_lock);
|
||||||
|
if (!m_failurePending)
|
||||||
|
return false;
|
||||||
|
source = {};
|
||||||
|
source.backend = m_backend;
|
||||||
|
source.epoch = m_epoch;
|
||||||
|
m_failurePending = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
LGAudioDeviceSnapshot CAudioHub::Snapshot() const
|
||||||
|
{
|
||||||
|
CSRWSharedLock lock(const_cast<CSRWLock&>(m_lock));
|
||||||
|
return m_device ? m_device->Snapshot() : LGAudioDeviceSnapshot {};
|
||||||
|
}
|
||||||
|
|
||||||
|
void CAudioHub::Stop()
|
||||||
|
{
|
||||||
|
CSRWExclusiveLock lifecycleLock(m_lifecycleLock);
|
||||||
|
IAudioSource * source = nullptr;
|
||||||
|
bool stopSource = false;
|
||||||
|
bool stopDevice = false;
|
||||||
|
{
|
||||||
|
CSRWExclusiveLock lock(m_lock);
|
||||||
|
if (m_stopped)
|
||||||
|
return;
|
||||||
|
m_stopped = true;
|
||||||
|
source = m_source;
|
||||||
|
stopSource = source && (m_running || m_reserved);
|
||||||
|
stopDevice = m_deviceStarted;
|
||||||
|
m_active = false;
|
||||||
|
m_running = false;
|
||||||
|
m_reserved = source != nullptr;
|
||||||
|
m_failed = false;
|
||||||
|
m_failurePending = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_device)
|
||||||
|
m_device->SetActive(false);
|
||||||
|
{
|
||||||
|
CSRWExclusiveLock callbackLock(m_callbackLock);
|
||||||
|
if (stopSource)
|
||||||
|
source->Stop();
|
||||||
|
}
|
||||||
|
if (stopDevice)
|
||||||
|
m_device->Stop();
|
||||||
|
|
||||||
|
CSRWExclusiveLock lock(m_lock);
|
||||||
|
m_source = nullptr;
|
||||||
|
m_backend = 0;
|
||||||
|
m_epoch = 0;
|
||||||
|
m_deviceStarted = false;
|
||||||
|
m_active = false;
|
||||||
|
m_running = false;
|
||||||
|
m_reserved = false;
|
||||||
|
m_failed = false;
|
||||||
|
m_failurePending = false;
|
||||||
|
m_deactivating = false;
|
||||||
|
m_deactivationGeneration = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioResult CAudioHub::SetAudioActive(bool active)
|
||||||
|
{
|
||||||
|
IAudioSource * source = nullptr;
|
||||||
|
{
|
||||||
|
CSRWSharedLock lock(m_lock);
|
||||||
|
if (m_stopped || !m_active || !m_running || m_failed ||
|
||||||
|
!m_deviceStarted)
|
||||||
|
return AudioResult::UNAVAILABLE;
|
||||||
|
source = m_source;
|
||||||
|
}
|
||||||
|
const AudioResult result = m_device->SetActive(active);
|
||||||
|
if (result == AudioResult::FAILED)
|
||||||
|
MarkFailed(source);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioResult CAudioHub::SendCapture(const AudioPacket& packet,
|
||||||
|
const uint8_t * data, size_t size)
|
||||||
|
{
|
||||||
|
IAudioSource * source = nullptr;
|
||||||
|
{
|
||||||
|
CSRWSharedLock lock(m_lock);
|
||||||
|
if (m_stopped || !m_active || !m_running || m_failed ||
|
||||||
|
!m_deviceStarted)
|
||||||
|
return AudioResult::UNAVAILABLE;
|
||||||
|
source = m_source;
|
||||||
|
}
|
||||||
|
const AudioResult result = m_device->SendCapture(packet, data, size);
|
||||||
|
if (result == AudioResult::FAILED)
|
||||||
|
MarkFailed(source);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioResult CAudioHub::SetClockFeedback(
|
||||||
|
const AudioClockFeedback& feedback)
|
||||||
|
{
|
||||||
|
IAudioSource * source = nullptr;
|
||||||
|
{
|
||||||
|
CSRWSharedLock lock(m_lock);
|
||||||
|
if (m_stopped || !m_active || !m_running || m_failed ||
|
||||||
|
!m_deviceStarted)
|
||||||
|
return AudioResult::UNAVAILABLE;
|
||||||
|
source = m_source;
|
||||||
|
}
|
||||||
|
const AudioResult result = m_device->SetClockFeedback(feedback);
|
||||||
|
if (result == AudioResult::FAILED)
|
||||||
|
MarkFailed(source);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CAudioHub::PlaybackReceiveReady()
|
||||||
|
{
|
||||||
|
{
|
||||||
|
CSRWSharedLock lock(m_lock);
|
||||||
|
if (m_stopped || !m_active || !m_running || m_failed ||
|
||||||
|
!m_deviceStarted)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_device->PlaybackReceiveReady();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CAudioHub::AudioFailed()
|
||||||
|
{
|
||||||
|
IAudioSource * source = nullptr;
|
||||||
|
{
|
||||||
|
CSRWSharedLock lock(m_lock);
|
||||||
|
source = m_source;
|
||||||
|
}
|
||||||
|
if (source)
|
||||||
|
MarkFailed(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CAudioHub::LGAudioState(const AudioEndpointStatus& state)
|
||||||
|
{
|
||||||
|
CSRWExclusiveLock callbackLock(m_callbackLock);
|
||||||
|
IAudioSource * source = nullptr;
|
||||||
|
{
|
||||||
|
CSRWExclusiveLock lock(m_lock);
|
||||||
|
if (m_deactivating && !state.active &&
|
||||||
|
state.generation != m_deactivationGeneration)
|
||||||
|
{
|
||||||
|
m_deactivating = false;
|
||||||
|
m_deactivationGeneration = 0;
|
||||||
|
}
|
||||||
|
if (!m_stopped && m_active && m_running && !m_failed)
|
||||||
|
source = m_source;
|
||||||
|
}
|
||||||
|
if (source)
|
||||||
|
source->AudioState(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioResult CAudioHub::LGAudioPlayback(const AudioPacket& packet,
|
||||||
|
const uint8_t * data, size_t size)
|
||||||
|
{
|
||||||
|
CSRWExclusiveLock callbackLock(m_callbackLock);
|
||||||
|
IAudioSource * source = nullptr;
|
||||||
|
{
|
||||||
|
CSRWSharedLock lock(m_lock);
|
||||||
|
if (!m_stopped && m_active && m_running && !m_failed)
|
||||||
|
source = m_source;
|
||||||
|
}
|
||||||
|
if (!source)
|
||||||
|
return AudioResult::UNAVAILABLE;
|
||||||
|
const AudioResult result = source->SendPlayback(packet, data, size);
|
||||||
|
if (result == AudioResult::FAILED)
|
||||||
|
MarkFailed(source);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CAudioHub::LGAudioCaptureReady()
|
||||||
|
{
|
||||||
|
CSRWExclusiveLock callbackLock(m_callbackLock);
|
||||||
|
IAudioSource * source = nullptr;
|
||||||
|
{
|
||||||
|
CSRWSharedLock lock(m_lock);
|
||||||
|
if (!m_stopped && m_active && m_running && !m_failed)
|
||||||
|
source = m_source;
|
||||||
|
}
|
||||||
|
if (source)
|
||||||
|
source->CaptureReceiveReady();
|
||||||
|
}
|
||||||
83
idd/LGIdd/transport/CAudioHub.h
Normal file
83
idd/LGIdd/transport/CAudioHub.h
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
/**
|
||||||
|
* Looking Glass
|
||||||
|
* Copyright © 2017-2026 The Looking Glass Authors
|
||||||
|
* https://looking-glass.io
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License as published by the Free
|
||||||
|
* Software Foundation; either version 2 of the License, or (at your option)
|
||||||
|
* any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
||||||
|
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CSRWLock.h"
|
||||||
|
#include "audio/CLGAudioDevice.h"
|
||||||
|
#include "transport/IAudioSource.h"
|
||||||
|
#include "transport/ITransport.h"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
class CAudioHub final : public IAudioTarget,
|
||||||
|
private ILGAudioDeviceEvents
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
CSRWLock m_lifecycleLock;
|
||||||
|
CSRWLock m_callbackLock;
|
||||||
|
CSRWLock m_lock;
|
||||||
|
std::unique_ptr<ILGAudioDevice> m_device;
|
||||||
|
IAudioSource * m_source = nullptr;
|
||||||
|
BackendId m_backend = 0;
|
||||||
|
uint32_t m_epoch = 0;
|
||||||
|
bool m_deviceStarted = false;
|
||||||
|
bool m_active = false;
|
||||||
|
bool m_running = false;
|
||||||
|
bool m_reserved = false;
|
||||||
|
bool m_failed = false;
|
||||||
|
bool m_failurePending = false;
|
||||||
|
bool m_deactivating = false;
|
||||||
|
uint64_t m_deactivationGeneration = 0;
|
||||||
|
bool m_stopped = false;
|
||||||
|
|
||||||
|
bool StartDevice();
|
||||||
|
bool MarkFailed(IAudioSource * source);
|
||||||
|
|
||||||
|
void LGAudioState(const AudioEndpointStatus& state) override;
|
||||||
|
AudioResult LGAudioPlayback(const AudioPacket& packet,
|
||||||
|
const uint8_t * data, size_t size) override;
|
||||||
|
void LGAudioCaptureReady() override;
|
||||||
|
|
||||||
|
public:
|
||||||
|
CAudioHub();
|
||||||
|
explicit CAudioHub(std::unique_ptr<ILGAudioDevice> device);
|
||||||
|
~CAudioHub() override;
|
||||||
|
|
||||||
|
CAudioHub(const CAudioHub&) = delete;
|
||||||
|
CAudioHub& operator=(const CAudioHub&) = delete;
|
||||||
|
|
||||||
|
// Start only creates the passive connector worker. Broker absence or an
|
||||||
|
// incompatible/busy broker is reported through state and is not failure.
|
||||||
|
bool Start();
|
||||||
|
bool Bind(BackendId backend, uint32_t epoch, IAudioSource& source);
|
||||||
|
void Unbind(BackendId backend, uint32_t epoch);
|
||||||
|
bool TakeFailure(SourceKey& source);
|
||||||
|
LGAudioDeviceSnapshot Snapshot() const;
|
||||||
|
void Stop();
|
||||||
|
|
||||||
|
AudioResult SetAudioActive(bool active) override;
|
||||||
|
AudioResult SendCapture(const AudioPacket& packet,
|
||||||
|
const uint8_t * data, size_t size) override;
|
||||||
|
AudioResult SetClockFeedback(
|
||||||
|
const AudioClockFeedback& feedback) override;
|
||||||
|
void PlaybackReceiveReady() override;
|
||||||
|
void AudioFailed() override;
|
||||||
|
};
|
||||||
@@ -26,6 +26,7 @@
|
|||||||
#include "ipc/CPipeServer.h"
|
#include "ipc/CPipeServer.h"
|
||||||
#include "Seq.h"
|
#include "Seq.h"
|
||||||
#include "transport/IClipboardSource.h"
|
#include "transport/IClipboardSource.h"
|
||||||
|
#include "transport/IAudioSource.h"
|
||||||
#include "transport/ITexStage.h"
|
#include "transport/ITexStage.h"
|
||||||
|
|
||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
@@ -489,9 +490,69 @@ bool CTransportManager::InitializeEntry(Entry& entry)
|
|||||||
if (entry.id == id && entry.epoch == epoch && entry.recoveryAttached)
|
if (entry.id == id && entry.epoch == epoch && entry.recoveryAttached)
|
||||||
entry.syncPending = true;
|
entry.syncPending = true;
|
||||||
}
|
}
|
||||||
|
AddAudioService(entry);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CTransportManager::AddAudioService(Entry& entry)
|
||||||
|
{
|
||||||
|
std::shared_ptr<ITransport> transport;
|
||||||
|
BackendId id = 0;
|
||||||
|
uint32_t epoch = 0;
|
||||||
|
uint32_t services = 0;
|
||||||
|
bool audioAdded = false;
|
||||||
|
bool audioAbsent = false;
|
||||||
|
uint64_t retryAt = 0;
|
||||||
|
State state = State::CLOSED;
|
||||||
|
{
|
||||||
|
CSRWSharedLock entryLock(entry.lock);
|
||||||
|
transport = entry.transport;
|
||||||
|
id = entry.id;
|
||||||
|
epoch = entry.epoch;
|
||||||
|
services = entry.config.services;
|
||||||
|
audioAdded = entry.audioAdded;
|
||||||
|
audioAbsent = entry.audioAbsent;
|
||||||
|
retryAt = entry.audioRetryAt;
|
||||||
|
state = entry.state;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state != State::INITIALIZED && state != State::READY)
|
||||||
|
return;
|
||||||
|
if (!(services & TRANSPORT_SERVICE_AUDIO))
|
||||||
|
{
|
||||||
|
CSRWExclusiveLock entryLock(entry.lock);
|
||||||
|
entry.audioAbsent = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!transport || audioAdded || audioAbsent ||
|
||||||
|
GetTickCount64() < retryAt)
|
||||||
|
return;
|
||||||
|
|
||||||
|
IAudioSource * audio = transport->Audio();
|
||||||
|
if (!audio)
|
||||||
|
{
|
||||||
|
CSRWExclusiveLock entryLock(entry.lock);
|
||||||
|
if (entry.transport == transport && entry.epoch == epoch)
|
||||||
|
entry.audioAbsent = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_audio.Bind(id, epoch, *audio))
|
||||||
|
{
|
||||||
|
CSRWExclusiveLock entryLock(entry.lock);
|
||||||
|
if (entry.transport == transport && entry.epoch == epoch)
|
||||||
|
{
|
||||||
|
entry.audioAdded = true;
|
||||||
|
entry.audioRetryAt = 0;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CSRWExclusiveLock entryLock(entry.lock);
|
||||||
|
if (entry.transport == transport && entry.epoch == epoch)
|
||||||
|
entry.audioRetryAt = GetTickCount64() + SERVICE_RETRY_DELAY_MS;
|
||||||
|
}
|
||||||
|
|
||||||
bool CTransportManager::AddServices(Entry& entry)
|
bool CTransportManager::AddServices(Entry& entry)
|
||||||
{
|
{
|
||||||
std::shared_ptr<ITransport> transport;
|
std::shared_ptr<ITransport> transport;
|
||||||
@@ -665,6 +726,28 @@ bool CTransportManager::AddServices(Entry& entry)
|
|||||||
|
|
||||||
void CTransportManager::HandleServiceFailures()
|
void CTransportManager::HandleServiceFailures()
|
||||||
{
|
{
|
||||||
|
SourceKey audioSource;
|
||||||
|
while (m_audio.TakeFailure(audioSource))
|
||||||
|
{
|
||||||
|
Entry * entries[FRAME_MAX_SINKS] = {};
|
||||||
|
const unsigned count = Entries(entries);
|
||||||
|
for (unsigned i = 0; i < count; ++i)
|
||||||
|
{
|
||||||
|
Entry& entry = *entries[i];
|
||||||
|
{
|
||||||
|
CSRWExclusiveLock entryLock(entry.lock);
|
||||||
|
if (entry.id != audioSource.backend ||
|
||||||
|
entry.epoch != audioSource.epoch || !entry.audioAdded)
|
||||||
|
continue;
|
||||||
|
entry.audioAdded = false;
|
||||||
|
entry.audioRetryAt =
|
||||||
|
GetTickCount64() + SERVICE_RETRY_DELAY_MS;
|
||||||
|
}
|
||||||
|
m_audio.Unbind(audioSource.backend, audioSource.epoch);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
BackendId frameId = 0;
|
BackendId frameId = 0;
|
||||||
uint32_t frameEpoch = 0;
|
uint32_t frameEpoch = 0;
|
||||||
while (m_tex.TakeFailure(frameId, frameEpoch))
|
while (m_tex.TakeFailure(frameId, frameEpoch))
|
||||||
@@ -840,6 +923,7 @@ void CTransportManager::RemoveServices(Entry& entry)
|
|||||||
bool controlAdded = false;
|
bool controlAdded = false;
|
||||||
bool inputAdded = false;
|
bool inputAdded = false;
|
||||||
bool clipboardAdded = false;
|
bool clipboardAdded = false;
|
||||||
|
bool audioAdded = false;
|
||||||
{
|
{
|
||||||
CSRWExclusiveLock entryLock(entry.lock);
|
CSRWExclusiveLock entryLock(entry.lock);
|
||||||
id = entry.id;
|
id = entry.id;
|
||||||
@@ -849,16 +933,22 @@ void CTransportManager::RemoveServices(Entry& entry)
|
|||||||
controlAdded = entry.controlAdded;
|
controlAdded = entry.controlAdded;
|
||||||
inputAdded = entry.inputAdded;
|
inputAdded = entry.inputAdded;
|
||||||
clipboardAdded = entry.clipboardAdded;
|
clipboardAdded = entry.clipboardAdded;
|
||||||
|
audioAdded = entry.audioAdded;
|
||||||
entry.frameAdded = false;
|
entry.frameAdded = false;
|
||||||
entry.frameLegacy = false;
|
entry.frameLegacy = false;
|
||||||
entry.texSink = nullptr;
|
entry.texSink = nullptr;
|
||||||
entry.controlAdded = false;
|
entry.controlAdded = false;
|
||||||
entry.inputAdded = false;
|
entry.inputAdded = false;
|
||||||
entry.clipboardAdded = false;
|
entry.clipboardAdded = false;
|
||||||
|
entry.audioAdded = false;
|
||||||
|
entry.audioAbsent = false;
|
||||||
|
entry.audioRetryAt = 0;
|
||||||
entry.frameRetryAt = 0;
|
entry.frameRetryAt = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_input.RevokeInteraction(id, epoch);
|
m_input.RevokeInteraction(id, epoch);
|
||||||
|
if (audioAdded)
|
||||||
|
m_audio.Unbind(id, epoch);
|
||||||
m_tex.Drop(id, epoch);
|
m_tex.Drop(id, epoch);
|
||||||
if (inputAdded)
|
if (inputAdded)
|
||||||
m_input.Unbind(id, epoch);
|
m_input.Unbind(id, epoch);
|
||||||
@@ -905,6 +995,8 @@ void CTransportManager::RetryEntry(Entry& entry, uint64_t now,
|
|||||||
entry.inputAbsent = false;
|
entry.inputAbsent = false;
|
||||||
entry.clipboardFailed = false;
|
entry.clipboardFailed = false;
|
||||||
entry.clipboardAbsent = false;
|
entry.clipboardAbsent = false;
|
||||||
|
entry.audioAbsent = false;
|
||||||
|
entry.audioRetryAt = 0;
|
||||||
entry.frameAbsent = false;
|
entry.frameAbsent = false;
|
||||||
entry.serviceRetryAt = 0;
|
entry.serviceRetryAt = 0;
|
||||||
Seq::Inc(entry.epoch);
|
Seq::Inc(entry.epoch);
|
||||||
@@ -1542,6 +1634,7 @@ ITransport::ProcessResult CTransportManager::Process(
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
RetryEntry(entry, now, initialized, setup, alignment);
|
RetryEntry(entry, now, initialized, setup, alignment);
|
||||||
|
AddAudioService(entry);
|
||||||
|
|
||||||
std::shared_ptr<ITransport> transport;
|
std::shared_ptr<ITransport> transport;
|
||||||
BackendId id = 0;
|
BackendId id = 0;
|
||||||
@@ -1685,6 +1778,7 @@ void CTransportManager::Stop()
|
|||||||
|
|
||||||
m_input.Stop();
|
m_input.Stop();
|
||||||
m_clipboard.Stop();
|
m_clipboard.Stop();
|
||||||
|
m_audio.Stop();
|
||||||
|
|
||||||
for (unsigned i = count; i > 0; --i)
|
for (unsigned i = count; i > 0; --i)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
|
|
||||||
#include "Atomic.h"
|
#include "Atomic.h"
|
||||||
#include "CSRWLock.h"
|
#include "CSRWLock.h"
|
||||||
|
#include "transport/CAudioHub.h"
|
||||||
#include "transport/CClipboardHub.h"
|
#include "transport/CClipboardHub.h"
|
||||||
#include "transport/CControlHub.h"
|
#include "transport/CControlHub.h"
|
||||||
#include "transport/CFrameHub.h"
|
#include "transport/CFrameHub.h"
|
||||||
@@ -113,6 +114,9 @@ private:
|
|||||||
bool clipboardAdded = false;
|
bool clipboardAdded = false;
|
||||||
bool clipboardFailed = false;
|
bool clipboardFailed = false;
|
||||||
bool clipboardAbsent = false;
|
bool clipboardAbsent = false;
|
||||||
|
bool audioAdded = false;
|
||||||
|
bool audioAbsent = false;
|
||||||
|
uint64_t audioRetryAt = 0;
|
||||||
bool frameAdded = false;
|
bool frameAdded = false;
|
||||||
bool frameLegacy = false;
|
bool frameLegacy = false;
|
||||||
ITexSink * texSink = nullptr;
|
ITexSink * texSink = nullptr;
|
||||||
@@ -137,6 +141,7 @@ private:
|
|||||||
CTexHub m_tex;
|
CTexHub m_tex;
|
||||||
CInputHub m_input;
|
CInputHub m_input;
|
||||||
CClipboardHub m_clipboard;
|
CClipboardHub m_clipboard;
|
||||||
|
CAudioHub m_audio;
|
||||||
CRecoveryHub m_recovery;
|
CRecoveryHub m_recovery;
|
||||||
Entry * m_primary = nullptr;
|
Entry * m_primary = nullptr;
|
||||||
bool m_initialized = false;
|
bool m_initialized = false;
|
||||||
@@ -165,6 +170,7 @@ private:
|
|||||||
OpenResult OpenEntry(Entry& entry);
|
OpenResult OpenEntry(Entry& entry);
|
||||||
bool InitializeEntry(Entry& entry);
|
bool InitializeEntry(Entry& entry);
|
||||||
bool SetupEntry(Entry& entry, size_t alignment);
|
bool SetupEntry(Entry& entry, size_t alignment);
|
||||||
|
void AddAudioService(Entry& entry);
|
||||||
bool AddServices(Entry& entry);
|
bool AddServices(Entry& entry);
|
||||||
void HandleServiceFailures();
|
void HandleServiceFailures();
|
||||||
void RetryEntry(Entry& entry, uint64_t now, bool initialized,
|
void RetryEntry(Entry& entry, uint64_t now, bool initialized,
|
||||||
|
|||||||
175
idd/LGIdd/transport/IAudioSource.h
Normal file
175
idd/LGIdd/transport/IAudioSource.h
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
/**
|
||||||
|
* Looking Glass
|
||||||
|
* Copyright © 2017-2026 The Looking Glass Authors
|
||||||
|
* https://looking-glass.io
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License as published by the Free
|
||||||
|
* Software Foundation; either version 2 of the License, or (at your option)
|
||||||
|
* any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
||||||
|
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
static constexpr uint32_t AUDIO_MAX_CHANNELS = 18;
|
||||||
|
static constexpr uint32_t AUDIO_STREAM_MAX_CHANNELS = 8;
|
||||||
|
static constexpr uint32_t AUDIO_MAX_FRAMES_PER_PACKET = 2048;
|
||||||
|
static constexpr uint32_t AUDIO_MAX_BYTES_PER_SAMPLE = 3;
|
||||||
|
static constexpr uint32_t AUDIO_MAX_PCM_BYTES =
|
||||||
|
AUDIO_MAX_FRAMES_PER_PACKET * AUDIO_STREAM_MAX_CHANNELS *
|
||||||
|
AUDIO_MAX_BYTES_PER_SAMPLE;
|
||||||
|
static constexpr uint32_t AUDIO_CHANNEL_POSITION_MASK = 0x0003ffffU;
|
||||||
|
|
||||||
|
enum AudioCapability : uint32_t
|
||||||
|
{
|
||||||
|
AUDIO_CAP_PLAYBACK = 1U << 0,
|
||||||
|
AUDIO_CAP_CAPTURE = 1U << 1,
|
||||||
|
AUDIO_CAP_CLOCK_FEEDBACK = 1U << 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum AudioPacketFlag : uint32_t
|
||||||
|
{
|
||||||
|
AUDIO_PACKET_DISCONTINUITY = 1U << 0,
|
||||||
|
AUDIO_PACKET_SILENT = 1U << 1,
|
||||||
|
AUDIO_PACKET_CLOCK_VALID = 1U << 2,
|
||||||
|
AUDIO_PACKET_CLOCK_STABLE = 1U << 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum AudioClockFlag : uint32_t
|
||||||
|
{
|
||||||
|
AUDIO_CLOCK_VALID = 1U << 0,
|
||||||
|
AUDIO_CLOCK_STABLE = 1U << 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class AudioStreamState : uint32_t
|
||||||
|
{
|
||||||
|
UNAVAILABLE,
|
||||||
|
STOPPED,
|
||||||
|
ACQUIRED,
|
||||||
|
PAUSED,
|
||||||
|
RUNNING,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class AudioResult
|
||||||
|
{
|
||||||
|
ACCEPTED,
|
||||||
|
BUSY,
|
||||||
|
UNAVAILABLE,
|
||||||
|
STALE,
|
||||||
|
REJECTED,
|
||||||
|
FAILED,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AudioFormat
|
||||||
|
{
|
||||||
|
uint32_t sampleRate = 0;
|
||||||
|
uint32_t channelMask = 0;
|
||||||
|
uint16_t channels = 0;
|
||||||
|
uint16_t containerBits = 0;
|
||||||
|
uint16_t validBits = 0;
|
||||||
|
uint16_t blockAlign = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AudioStreamStatus
|
||||||
|
{
|
||||||
|
uint64_t generation = 0;
|
||||||
|
AudioStreamState state = AudioStreamState::UNAVAILABLE;
|
||||||
|
bool formatValid = false;
|
||||||
|
AudioFormat format;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AudioEndpointStatus
|
||||||
|
{
|
||||||
|
uint64_t generation = 1;
|
||||||
|
uint32_t capabilities = 0;
|
||||||
|
bool available = false;
|
||||||
|
bool active = false;
|
||||||
|
AudioStreamStatus playback;
|
||||||
|
AudioStreamStatus capture;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AudioPacket
|
||||||
|
{
|
||||||
|
uint64_t endpointGeneration = 0;
|
||||||
|
uint64_t streamGeneration = 0;
|
||||||
|
uint32_t flags = 0;
|
||||||
|
uint32_t frames = 0;
|
||||||
|
uint64_t position = 0;
|
||||||
|
int64_t timeNs = 0;
|
||||||
|
uint64_t rateQ32 = 0;
|
||||||
|
AudioFormat format;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AudioClockFeedback
|
||||||
|
{
|
||||||
|
uint64_t endpointGeneration = 0;
|
||||||
|
uint64_t playbackGeneration = 0;
|
||||||
|
uint64_t position = 0;
|
||||||
|
int64_t timeNs = 0;
|
||||||
|
uint64_t targetRateQ32 = 0;
|
||||||
|
uint32_t flags = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class IAudioTarget
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~IAudioTarget() = default;
|
||||||
|
|
||||||
|
// Methods are thread-safe and nonblocking. Packet generations and format
|
||||||
|
// must exactly match the latest AudioState. A packet contains 1..2048
|
||||||
|
// frames and only the declared AudioPacketFlag bits. Non-silent payload
|
||||||
|
// size is frames * blockAlign; a silent packet has size zero and is
|
||||||
|
// expanded to zero PCM by the target. CLOCK_STABLE requires CLOCK_VALID;
|
||||||
|
// position, timeNs, and rateQ32 are meaningful only when CLOCK_VALID.
|
||||||
|
|
||||||
|
// Activation is asynchronous. AudioState reports when the local endpoint
|
||||||
|
// is actually bound. Deactivation restores the driver's local loopback.
|
||||||
|
// A new activation returns BUSY until a preceding deactivation has reached
|
||||||
|
// an inactive AudioState, preventing PCM from crossing owner handoffs.
|
||||||
|
virtual AudioResult SetAudioActive(bool active) = 0;
|
||||||
|
|
||||||
|
// ACCEPTED consumes a copied packet. BUSY consumes nothing, and the source
|
||||||
|
// retries the exact packet after CaptureReceiveReady.
|
||||||
|
virtual AudioResult SendCapture(const AudioPacket& packet,
|
||||||
|
const uint8_t * data, size_t size) = 0;
|
||||||
|
virtual AudioResult SetClockFeedback(
|
||||||
|
const AudioClockFeedback& feedback) = 0;
|
||||||
|
|
||||||
|
// The source calls this after SendPlayback returned BUSY and it can accept
|
||||||
|
// an exact retry of the retained playback packet.
|
||||||
|
virtual void PlaybackReceiveReady() = 0;
|
||||||
|
virtual void AudioFailed() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class IAudioSource
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~IAudioSource() = default;
|
||||||
|
|
||||||
|
// Methods are thread-safe and nonblocking except Stop. Start attaches the
|
||||||
|
// target. The source must wait for its initial AudioState callback before
|
||||||
|
// making target calls. Stop is a callback-quiescence barrier and leaves no
|
||||||
|
// work which can access target.
|
||||||
|
virtual bool Start(IAudioTarget& target) = 0;
|
||||||
|
virtual void Stop() = 0;
|
||||||
|
|
||||||
|
// All callbacks into a source are serialized. BUSY consumes neither the
|
||||||
|
// packet nor its borrowed payload; the hub retains and retries both after
|
||||||
|
// PlaybackReceiveReady.
|
||||||
|
virtual void AudioState(const AudioEndpointStatus& state) = 0;
|
||||||
|
virtual AudioResult SendPlayback(const AudioPacket& packet,
|
||||||
|
const uint8_t * data, size_t size) = 0;
|
||||||
|
virtual void CaptureReceiveReady() = 0;
|
||||||
|
};
|
||||||
@@ -30,6 +30,7 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
class IClipboardSource;
|
class IClipboardSource;
|
||||||
|
class IAudioSource;
|
||||||
class IControlSink;
|
class IControlSink;
|
||||||
class IFrameSink;
|
class IFrameSink;
|
||||||
class IInputSource;
|
class IInputSource;
|
||||||
@@ -174,7 +175,9 @@ public:
|
|||||||
}
|
}
|
||||||
virtual DirectFrameBufferMemory GetDirectMemory() const = 0;
|
virtual DirectFrameBufferMemory GetDirectMemory() const = 0;
|
||||||
|
|
||||||
// Component pointers are fixed after Setup and remain valid until Stop.
|
// Audio is fixed after Initialize. The remaining component pointers are
|
||||||
|
// fixed after Setup. All remain valid until Stop.
|
||||||
|
virtual IAudioSource * Audio() { return nullptr; }
|
||||||
virtual IFrameSink * FrameSink() { return nullptr; }
|
virtual IFrameSink * FrameSink() { return nullptr; }
|
||||||
virtual ITexSink * TexSink() { return nullptr; }
|
virtual ITexSink * TexSink() { return nullptr; }
|
||||||
virtual IControlSink * Control() { return nullptr; }
|
virtual IControlSink * Control() { return nullptr; }
|
||||||
|
|||||||
@@ -127,6 +127,8 @@ namespace
|
|||||||
service = TRANSPORT_SERVICE_INPUT;
|
service = TRANSPORT_SERVICE_INPUT;
|
||||||
else if (Equal(name, L"clipboard"))
|
else if (Equal(name, L"clipboard"))
|
||||||
service = TRANSPORT_SERVICE_CLIPBOARD;
|
service = TRANSPORT_SERVICE_CLIPBOARD;
|
||||||
|
else if (Equal(name, L"audio"))
|
||||||
|
service = TRANSPORT_SERVICE_AUDIO;
|
||||||
else
|
else
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
|||||||
@@ -32,9 +32,10 @@ enum TransportService : uint32_t
|
|||||||
TRANSPORT_SERVICE_CONTROL = 1U << 1,
|
TRANSPORT_SERVICE_CONTROL = 1U << 1,
|
||||||
TRANSPORT_SERVICE_INPUT = 1U << 2,
|
TRANSPORT_SERVICE_INPUT = 1U << 2,
|
||||||
TRANSPORT_SERVICE_CLIPBOARD = 1U << 3,
|
TRANSPORT_SERVICE_CLIPBOARD = 1U << 3,
|
||||||
|
TRANSPORT_SERVICE_AUDIO = 1U << 4,
|
||||||
TRANSPORT_SERVICE_ALL = TRANSPORT_SERVICE_FRAME |
|
TRANSPORT_SERVICE_ALL = TRANSPORT_SERVICE_FRAME |
|
||||||
TRANSPORT_SERVICE_CONTROL | TRANSPORT_SERVICE_INPUT |
|
TRANSPORT_SERVICE_CONTROL | TRANSPORT_SERVICE_INPUT |
|
||||||
TRANSPORT_SERVICE_CLIPBOARD,
|
TRANSPORT_SERVICE_CLIPBOARD | TRANSPORT_SERVICE_AUDIO,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct TransportInstance
|
struct TransportInstance
|
||||||
|
|||||||
Reference in New Issue
Block a user