mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-11-12 14:57:20 +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\CHDR16to10Effect.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\CControlHub.cpp" />
|
||||
<ClCompile Include="transport\CFrameHub.cpp" />
|
||||
@@ -127,6 +129,8 @@
|
||||
<ClInclude Include="postprocess\effect\CFormatEffect.h" />
|
||||
<ClInclude Include="postprocess\effect\CHDR16to10Effect.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\CControlHub.h" />
|
||||
<ClInclude Include="transport\CFrameHub.h" />
|
||||
@@ -139,6 +143,7 @@
|
||||
<ClInclude Include="transport\FrameProfile.h" />
|
||||
<ClInclude Include="transport\FrameIn.h" />
|
||||
<ClInclude Include="transport\IClipboardSource.h" />
|
||||
<ClInclude Include="transport\IAudioSource.h" />
|
||||
<ClInclude Include="transport\IControlSink.h" />
|
||||
<ClInclude Include="transport\IControlTransport.h" />
|
||||
<ClInclude Include="transport\IFrameSink.h" />
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
<Filter Include="Capture">
|
||||
<UniqueIdentifier>{202B79B2-5CCF-4AD4-B4C5-F5602419683D}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Audio">
|
||||
<UniqueIdentifier>{86750913-8C21-4F76-8E51-9737D4963E50}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="D3D">
|
||||
<UniqueIdentifier>{4B3C58D3-5F40-49AB-894E-B83EB44E6CF5}</UniqueIdentifier>
|
||||
</Filter>
|
||||
@@ -166,6 +169,15 @@
|
||||
<ClInclude Include="postprocess\effect\CRGB24Effect.h">
|
||||
<Filter>Post-processing\Effects</Filter>
|
||||
</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">
|
||||
<Filter>Transport</Filter>
|
||||
</ClInclude>
|
||||
@@ -378,6 +390,12 @@
|
||||
<ClCompile Include="postprocess\effect\CRGB24Effect.cpp">
|
||||
<Filter>Post-processing\Effects</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="audio\CLGAudioDevice.cpp">
|
||||
<Filter>Audio</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="transport\CAudioHub.cpp">
|
||||
<Filter>Transport</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="transport\CClipboardHub.cpp">
|
||||
<Filter>Transport</Filter>
|
||||
</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 "Seq.h"
|
||||
#include "transport/IClipboardSource.h"
|
||||
#include "transport/IAudioSource.h"
|
||||
#include "transport/ITexStage.h"
|
||||
|
||||
#include <Windows.h>
|
||||
@@ -489,9 +490,69 @@ bool CTransportManager::InitializeEntry(Entry& entry)
|
||||
if (entry.id == id && entry.epoch == epoch && entry.recoveryAttached)
|
||||
entry.syncPending = true;
|
||||
}
|
||||
AddAudioService(entry);
|
||||
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)
|
||||
{
|
||||
std::shared_ptr<ITransport> transport;
|
||||
@@ -665,6 +726,28 @@ bool CTransportManager::AddServices(Entry& entry)
|
||||
|
||||
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;
|
||||
uint32_t frameEpoch = 0;
|
||||
while (m_tex.TakeFailure(frameId, frameEpoch))
|
||||
@@ -840,6 +923,7 @@ void CTransportManager::RemoveServices(Entry& entry)
|
||||
bool controlAdded = false;
|
||||
bool inputAdded = false;
|
||||
bool clipboardAdded = false;
|
||||
bool audioAdded = false;
|
||||
{
|
||||
CSRWExclusiveLock entryLock(entry.lock);
|
||||
id = entry.id;
|
||||
@@ -849,16 +933,22 @@ void CTransportManager::RemoveServices(Entry& entry)
|
||||
controlAdded = entry.controlAdded;
|
||||
inputAdded = entry.inputAdded;
|
||||
clipboardAdded = entry.clipboardAdded;
|
||||
audioAdded = entry.audioAdded;
|
||||
entry.frameAdded = false;
|
||||
entry.frameLegacy = false;
|
||||
entry.texSink = nullptr;
|
||||
entry.controlAdded = false;
|
||||
entry.inputAdded = false;
|
||||
entry.clipboardAdded = false;
|
||||
entry.audioAdded = false;
|
||||
entry.audioAbsent = false;
|
||||
entry.audioRetryAt = 0;
|
||||
entry.frameRetryAt = 0;
|
||||
}
|
||||
|
||||
m_input.RevokeInteraction(id, epoch);
|
||||
if (audioAdded)
|
||||
m_audio.Unbind(id, epoch);
|
||||
m_tex.Drop(id, epoch);
|
||||
if (inputAdded)
|
||||
m_input.Unbind(id, epoch);
|
||||
@@ -905,6 +995,8 @@ void CTransportManager::RetryEntry(Entry& entry, uint64_t now,
|
||||
entry.inputAbsent = false;
|
||||
entry.clipboardFailed = false;
|
||||
entry.clipboardAbsent = false;
|
||||
entry.audioAbsent = false;
|
||||
entry.audioRetryAt = 0;
|
||||
entry.frameAbsent = false;
|
||||
entry.serviceRetryAt = 0;
|
||||
Seq::Inc(entry.epoch);
|
||||
@@ -1542,6 +1634,7 @@ ITransport::ProcessResult CTransportManager::Process(
|
||||
continue;
|
||||
|
||||
RetryEntry(entry, now, initialized, setup, alignment);
|
||||
AddAudioService(entry);
|
||||
|
||||
std::shared_ptr<ITransport> transport;
|
||||
BackendId id = 0;
|
||||
@@ -1685,6 +1778,7 @@ void CTransportManager::Stop()
|
||||
|
||||
m_input.Stop();
|
||||
m_clipboard.Stop();
|
||||
m_audio.Stop();
|
||||
|
||||
for (unsigned i = count; i > 0; --i)
|
||||
{
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
#include "Atomic.h"
|
||||
#include "CSRWLock.h"
|
||||
#include "transport/CAudioHub.h"
|
||||
#include "transport/CClipboardHub.h"
|
||||
#include "transport/CControlHub.h"
|
||||
#include "transport/CFrameHub.h"
|
||||
@@ -113,6 +114,9 @@ private:
|
||||
bool clipboardAdded = false;
|
||||
bool clipboardFailed = false;
|
||||
bool clipboardAbsent = false;
|
||||
bool audioAdded = false;
|
||||
bool audioAbsent = false;
|
||||
uint64_t audioRetryAt = 0;
|
||||
bool frameAdded = false;
|
||||
bool frameLegacy = false;
|
||||
ITexSink * texSink = nullptr;
|
||||
@@ -137,6 +141,7 @@ private:
|
||||
CTexHub m_tex;
|
||||
CInputHub m_input;
|
||||
CClipboardHub m_clipboard;
|
||||
CAudioHub m_audio;
|
||||
CRecoveryHub m_recovery;
|
||||
Entry * m_primary = nullptr;
|
||||
bool m_initialized = false;
|
||||
@@ -165,6 +170,7 @@ private:
|
||||
OpenResult OpenEntry(Entry& entry);
|
||||
bool InitializeEntry(Entry& entry);
|
||||
bool SetupEntry(Entry& entry, size_t alignment);
|
||||
void AddAudioService(Entry& entry);
|
||||
bool AddServices(Entry& entry);
|
||||
void HandleServiceFailures();
|
||||
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>
|
||||
|
||||
class IClipboardSource;
|
||||
class IAudioSource;
|
||||
class IControlSink;
|
||||
class IFrameSink;
|
||||
class IInputSource;
|
||||
@@ -174,7 +175,9 @@ public:
|
||||
}
|
||||
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 ITexSink * TexSink() { return nullptr; }
|
||||
virtual IControlSink * Control() { return nullptr; }
|
||||
|
||||
@@ -127,6 +127,8 @@ namespace
|
||||
service = TRANSPORT_SERVICE_INPUT;
|
||||
else if (Equal(name, L"clipboard"))
|
||||
service = TRANSPORT_SERVICE_CLIPBOARD;
|
||||
else if (Equal(name, L"audio"))
|
||||
service = TRANSPORT_SERVICE_AUDIO;
|
||||
else
|
||||
return false;
|
||||
|
||||
|
||||
@@ -32,9 +32,10 @@ enum TransportService : uint32_t
|
||||
TRANSPORT_SERVICE_CONTROL = 1U << 1,
|
||||
TRANSPORT_SERVICE_INPUT = 1U << 2,
|
||||
TRANSPORT_SERVICE_CLIPBOARD = 1U << 3,
|
||||
TRANSPORT_SERVICE_AUDIO = 1U << 4,
|
||||
TRANSPORT_SERVICE_ALL = TRANSPORT_SERVICE_FRAME |
|
||||
TRANSPORT_SERVICE_CONTROL | TRANSPORT_SERVICE_INPUT |
|
||||
TRANSPORT_SERVICE_CLIPBOARD,
|
||||
TRANSPORT_SERVICE_CLIPBOARD | TRANSPORT_SERVICE_AUDIO,
|
||||
};
|
||||
|
||||
struct TransportInstance
|
||||
|
||||
Reference in New Issue
Block a user