[idd] network: enforce configurable video bitrate

This commit is contained in:
Geoffrey McRae
2026-10-01 16:27:48 +10:00
parent 6aea2496f0
commit 50177afe79
5 changed files with 154 additions and 68 deletions

View File

@@ -58,11 +58,12 @@ ITransport::OpenResult CNetworkTransport::Open()
m_settings = std::move(settings);
m_state = State::OPEN;
DEBUG_INFO("Network transport configured on [%ls]:%u for up to %u "
"clients (%ls authentication, %ls video)",
"clients (%ls authentication, %u Mbit/s, %ls video)",
m_settings.bindAddress.c_str(),
static_cast<unsigned>(m_settings.port),
static_cast<unsigned>(m_settings.maxClients),
m_settings.auth == LG_NET_AUTH_PASSWORD ? L"password" : L"none",
static_cast<unsigned>(m_settings.videoBitrateMbps),
m_settings.videoChroma ==
static_cast<LGNetVideoChromaSubsampling>(LG_NET_VIDEO_CHROMA_420) ?
L"4:2:0" : L"4:4:4");
@@ -110,7 +111,8 @@ bool CNetworkTransport::Initialize()
return false;
}
if ((m_config.services & TRANSPORT_SERVICE_FRAME) &&
!m_video.Start(m_settings.videoChroma))
!m_video.Start(
m_settings.videoBitrateMbps, m_settings.videoChroma))
{
DEBUG_ERROR("Failed to start the network video endpoint");
m_video.DetachSender();

View File

@@ -106,6 +106,16 @@ namespace
constexpr uint32_t PYROWAVE_MAX_8X8_BLOCKS = MAX_BLOCKS * 16U;
constexpr uint64_t MAX_PLANE_ELEMENTS = UINT64_C(1) <<
D3D12_REQ_BUFFER_RESOURCE_TEXEL_COUNT_2_TO_EXP;
constexpr uint32_t PYROWAVE_MIN_FRAME_BYTES = 20;
// Use capture cadence for an aggregate payload target. Limit idle credit to
// one second so an inactive source cannot create an unbounded burst.
constexpr uint64_t VIDEO_RATE_BYTES_PER_MBIT = 125000;
constexpr uint64_t VIDEO_RATE_NS_PER_SECOND = 1000000000ULL;
constexpr uint64_t VIDEO_RATE_DEFAULT_PERIOD_NS =
VIDEO_RATE_NS_PER_SECOND / 60U;
constexpr uint64_t VIDEO_RATE_MAX_PERIOD_NS =
VIDEO_RATE_NS_PER_SECOND;
constexpr LGNetVideoChromaSubsampling CHROMA_420 =
static_cast<LGNetVideoChromaSubsampling>(LG_NET_VIDEO_CHROMA_420);
@@ -1446,6 +1456,7 @@ void FinalizeMain()
uint64_t captureTime = 0;
uint64_t order = 0;
uint32_t sequence = 0;
uint32_t frameLimit = 0;
bool inFlight = false;
bool used = false;
};
@@ -1469,7 +1480,6 @@ void FinalizeMain()
uint32_t m_auxBase [4] = {};
uint32_t m_auxWords = 0;
uint32_t m_payloadWords = 0;
uint32_t m_frameLimit = 0;
uint32_t m_sequence = 0;
bool m_ready = false;
HANDLE m_wakeEvent = nullptr;
@@ -1792,10 +1802,16 @@ void FinalizeMain()
planes[component].height >> (localLevel + 1U);
const uint32_t bands =
level == static_cast<int>(PYROWAVE_LEVELS) - 1 ? 4U : 3U;
blocks += static_cast<uint64_t>((bandWidth + 31U) / 32U) *
((bandHeight + 31U) / 32U) * bands;
blocks8 += static_cast<uint64_t>((bandWidth + 7U) / 8U) *
((bandHeight + 7U) / 8U) * bands;
const uint64_t blocksX =
(static_cast<uint64_t>(bandWidth) + 31U) / 32U;
const uint64_t blocksY =
(static_cast<uint64_t>(bandHeight) + 31U) / 32U;
const uint64_t blocks8X =
(static_cast<uint64_t>(bandWidth) + 7U) / 8U;
const uint64_t blocks8Y =
(static_cast<uint64_t>(bandHeight) + 7U) / 8U;
blocks += blocksX * blocksY * bands;
blocks8 += blocks8X * blocks8Y * bands;
}
if (!blocks || blocks > MAX_BLOCKS || !blocks8 ||
blocks8 > PYROWAVE_MAX_8X8_BLOCKS)
@@ -1889,11 +1905,10 @@ void FinalizeMain()
}
bool Configure(const FrameCfg& cfg, LGNetVideoChromaSubsampling chroma,
uint32_t frameLimit, ID3D12Resource * source)
ID3D12Resource * source)
{
if (!source || !SupportedProfile(cfg.profile) || !cfg.width ||
!cfg.height || cfg.width > MAX_WIDTH || cfg.height > MAX_HEIGHT ||
frameLimit < 20U)
!cfg.height || cfg.width > MAX_WIDTH || cfg.height > MAX_HEIGHT)
return false;
ComPtr<ID3D12Device3> device;
@@ -1942,7 +1957,7 @@ void FinalizeMain()
metaBase + static_cast<uint64_t>(block8Capacity) * 2U;
const uint64_t rdoStateBase =
rdoOpsBase + static_cast<uint64_t>(blockCapacity) *
(PYROWAVE_QUANT_LEVELS - 1U);
(static_cast<uint64_t>(PYROWAVE_QUANT_LEVELS) - 1U);
const uint64_t auxWords = rdoStateBase +
PYROWAVE_RDO_WORDS + blockCapacity;
const uint64_t payloadWords =
@@ -1958,7 +1973,7 @@ void FinalizeMain()
static_cast<uint64_t>(alignedWidth) * alignedHeight * 2U + 8U;
const uint64_t packedCapacity = (std::min)(worstPacked,
(std::min)(targetPacked,
static_cast<uint64_t>(frameLimit & ~UINT32_C(3))));
static_cast<uint64_t>(MAX_FRAME_LENGTH)));
const uint64_t packetMetaBytes =
static_cast<uint64_t>(blockCapacity) *
PYROWAVE_PACKET_META_WORDS * sizeof(uint32_t);
@@ -1986,7 +2001,6 @@ void FinalizeMain()
m_auxBase[3] = static_cast<uint32_t>(rdoStateBase);
m_auxWords = static_cast<uint32_t>(auxWords);
m_payloadWords = static_cast<uint32_t>(payloadWords);
m_frameLimit = frameLimit;
std::copy(std::begin(planes), std::end(planes), std::begin(m_planes));
if (!m_computeQueue.Init(m_device.Get(),
@@ -2054,7 +2068,7 @@ void FinalizeMain()
}
bool RecordBands(ID3D12GraphicsCommandList * list, Pipeline pipeline,
uint32_t sequence)
uint32_t sequence, uint32_t packedLimit)
{
uint32_t blockBase = 0;
uint32_t blockBase8 = 0;
@@ -2095,7 +2109,7 @@ void FinalizeMain()
parameters.op1[1] = blockBase;
parameters.op1[2] = blockBase8;
parameters.op1[3] = pipeline == PIPELINE_QUANT ?
m_payloadWords : m_packedCapacity;
m_payloadWords : packedLimit;
parameters.values[0] = static_cast<float>(1U << quantBits);
parameters.values[1] = DistortionScale(
level, component, band);
@@ -2123,8 +2137,8 @@ void FinalizeMain()
void RecordTransforms(ID3D12GraphicsCommandList * list)
{
uint32_t activeWidth [3];
uint32_t activeHeight[3];
uint32_t activeWidth [3] = {};
uint32_t activeHeight[3] = {};
for (unsigned component = 0; component < 3; ++component)
{
activeWidth [component] = m_planes[component].width;
@@ -2174,7 +2188,8 @@ void FinalizeMain()
D3D12_RESOURCE_STATE_COMMON);
}
void RecordReadback(ID3D12GraphicsCommandList * list, Slot& slot)
void RecordReadback(
ID3D12GraphicsCommandList * list, Slot& slot) const
{
list->CopyBufferRegion(slot.readback.Get(), 0,
slot.packState.Get(), 0, PYROWAVE_STATE_BYTES);
@@ -2184,7 +2199,7 @@ void FinalizeMain()
static_cast<uint64_t>(m_blockCapacity) *
PYROWAVE_PACKET_META_WORDS * sizeof(uint32_t));
list->CopyBufferRegion(slot.readback.Get(), m_packedOffset,
slot.packed.Get(), 0, m_packedCapacity);
slot.packed.Get(), 0, slot.frameLimit);
}
static uint32_t LoadLE32(const uint8_t * data)
@@ -2203,6 +2218,7 @@ void FinalizeMain()
const uint32_t overflow = LoadLE32(readback + 8U);
if (overflow || !packetCount || packetCount > m_blockCapacity ||
packedBytes < 8U || packedBytes > m_packedCapacity ||
packedBytes > slot.frameLimit ||
packedBytes > MAX_FRAME_LENGTH)
return false;
@@ -2304,6 +2320,7 @@ void FinalizeMain()
slot.inFlight = false;
slot.captureTime = 0;
slot.order = 0;
slot.frameLimit = 0;
slot.frame = {};
slot.lease.Reset();
@@ -2357,14 +2374,20 @@ void FinalizeMain()
LGNetVideoChromaSubsampling chroma, uint32_t frameLimit)
{
if (!lease || !lease.Get() ||
lease->State() != D3D12_RESOURCE_STATE_COMMON)
lease->State() != D3D12_RESOURCE_STATE_COMMON ||
frameLimit < PYROWAVE_MIN_FRAME_BYTES)
return false;
ID3D12Resource * source = lease->Get();
if (!m_ready || !SameCfg(m_cfg, cfg) || m_chroma != chroma ||
m_frameLimit != frameLimit || !SameDevice(source))
if (!Configure(cfg, chroma, frameLimit, source))
!SameDevice(source))
if (!Configure(cfg, chroma, source))
return false;
const uint32_t packedLimit = (std::min)(
m_packedCapacity, frameLimit & ~UINT32_C(3));
if (packedLimit < PYROWAVE_MIN_FRAME_BYTES)
return false;
unsigned slotIndex = 0;
while (slotIndex < ENCODER_SLOT_COUNT && m_slots[slotIndex].inFlight)
++slotIndex;
@@ -2391,6 +2414,7 @@ void FinalizeMain()
slot.captureTime = lease->time.postStart;
slot.sequence = m_sequence;
slot.frameLimit = packedLimit;
CreateSourceDescriptor(slot, source);
ID3D12DescriptorHeap * heaps[] = { slot.descriptors.Get() };
@@ -2447,7 +2471,8 @@ void FinalizeMain()
UAVBarrier(computeList.Get(), slot.planes.Get());
RecordTransforms(computeList.Get());
if (!RecordBands(computeList.Get(), PIPELINE_QUANT, slot.sequence))
if (!RecordBands(computeList.Get(), PIPELINE_QUANT,
slot.sequence, m_packedCapacity))
{
copyCommand->Cancel();
computeCommand->Cancel();
@@ -2456,7 +2481,8 @@ void FinalizeMain()
UAVBarrier(computeList.Get(), slot.aux.Get());
UAVBarrier(computeList.Get(), slot.quantPayload.Get());
UAVBarrier(computeList.Get(), slot.packState.Get());
if (!RecordBands(computeList.Get(), PIPELINE_ANALYZE, slot.sequence))
if (!RecordBands(computeList.Get(), PIPELINE_ANALYZE,
slot.sequence, m_packedCapacity))
{
copyCommand->Cancel();
computeCommand->Cancel();
@@ -2469,14 +2495,15 @@ void FinalizeMain()
UAVBarrier(computeList.Get(), slot.aux.Get());
ShaderParameters resolve = {};
resolve.dim0[0] = (m_packedCapacity - 8U) / UINT32_C(4);
resolve.dim0[0] = (packedLimit - 8U) / UINT32_C(4);
resolve.dim0[1] = m_blockCapacity;
resolve.dim0[2] = m_blocksPerRDO;
Dispatch(computeList.Get(), PIPELINE_RESOLVE, resolve,
(m_blockCapacity + 63U) / 64U, 1);
UAVBarrier(computeList.Get(), slot.aux.Get());
if (!RecordBands(computeList.Get(), PIPELINE_PACK, slot.sequence))
if (!RecordBands(computeList.Get(), PIPELINE_PACK,
slot.sequence, packedLimit))
{
copyCommand->Cancel();
computeCommand->Cancel();
@@ -2610,7 +2637,6 @@ void FinalizeMain()
m_packedCapacity = 0;
m_auxWords = 0;
m_payloadWords = 0;
m_frameLimit = 0;
std::fill(std::begin(m_auxBase), std::end(m_auxBase), 0U);
std::fill(std::begin(m_planes), std::end(m_planes), Plane {});
}
@@ -2684,12 +2710,15 @@ struct CNetworkVideo::Impl
HANDLE m_workerThread = nullptr;
std::atomic<bool> m_workerStop = false;
INetworkVideoSender * m_sender = nullptr;
LGNetVideoChromaSubsampling m_videoChroma = CHROMA_444;
bool m_started = false;
bool m_stopped = false;
bool m_failed = false;
bool m_changing = false;
INetworkVideoSender * m_sender = nullptr;
uint64_t m_lastBudgetCaptureTimeNs = 0;
uint32_t m_videoBitrateMbps =
NETWORK_DEFAULT_VIDEO_BITRATE_MBPS;
LGNetVideoChromaSubsampling m_videoChroma = CHROMA_444;
bool m_started = false;
bool m_stopped = false;
bool m_failed = false;
bool m_changing = false;
FrameCfg m_activeCfg = {};
FrameCfg m_pendingCfg = {};
@@ -2778,7 +2807,7 @@ struct CNetworkVideo::Impl
return m_workerWake && m_workerThread;
}
void Wake()
void Wake() const
{
if (m_workerWake)
SetEvent(m_workerWake);
@@ -2812,6 +2841,25 @@ struct CNetworkVideo::Impl
return false;
}
uint32_t FrameLimitNL(uint64_t captureTimeNs)
{
uint64_t period = VIDEO_RATE_DEFAULT_PERIOD_NS;
if (captureTimeNs && m_lastBudgetCaptureTimeNs)
period = captureTimeNs > m_lastBudgetCaptureTimeNs ?
(std::min)(captureTimeNs - m_lastBudgetCaptureTimeNs,
VIDEO_RATE_MAX_PERIOD_NS) : 0;
if (captureTimeNs)
m_lastBudgetCaptureTimeNs = captureTimeNs;
const uint64_t bytes =
static_cast<uint64_t>(m_videoBitrateMbps) *
VIDEO_RATE_BYTES_PER_MBIT * period / VIDEO_RATE_NS_PER_SECOND;
const uint64_t limited = (std::max)(
static_cast<uint64_t>(PYROWAVE_MIN_FRAME_BYTES),
(std::min)(bytes, static_cast<uint64_t>(MAX_FRAME_LENGTH)));
return static_cast<uint32_t>(limited);
}
LGNetVideoChromaSubsampling ChromaNL() const
{
if (!m_active)
@@ -3573,7 +3621,9 @@ struct CNetworkVideo::Impl
}
if (!configured)
return true;
chroma = m_videoChroma;
frameLimit = (std::min)(
frameLimit, FrameLimitNL(pending.lease->time.postStart));
chroma = m_videoChroma;
}
if (m_encoder.Submit(std::move(pending.frame),
@@ -3803,17 +3853,22 @@ void CNetworkVideo::DetachSender()
m_impl->m_peers.clear();
}
bool CNetworkVideo::Start(LGNetVideoChromaSubsampling videoChroma)
bool CNetworkVideo::Start(uint32_t videoBitrateMbps,
LGNetVideoChromaSubsampling videoChroma)
{
if (!m_impl || !m_impl->WorkerReady() ||
!videoBitrateMbps ||
videoBitrateMbps > NETWORK_MAX_VIDEO_BITRATE_MBPS ||
(videoChroma != CHROMA_420 && videoChroma != CHROMA_444))
return false;
{
CSRWExclusiveLock lock(m_impl->m_lock);
if (m_impl->m_stopped || m_impl->m_failed)
return false;
m_impl->m_videoChroma = videoChroma;
m_impl->m_started = true;
m_impl->m_lastBudgetCaptureTimeNs = 0;
m_impl->m_videoBitrateMbps = videoBitrateMbps;
m_impl->m_videoChroma = videoChroma;
m_impl->m_started = true;
}
m_impl->Wake();
return true;
@@ -3828,12 +3883,13 @@ void CNetworkVideo::Stop()
CSRWExclusiveLock lock(m_impl->m_lock);
if (m_impl->m_stopped)
return;
m_impl->m_stopped = true;
m_impl->m_started = false;
m_impl->m_active = false;
m_impl->m_pending = false;
m_impl->m_changing = false;
m_impl->m_sendBytes = 0;
m_impl->m_stopped = true;
m_impl->m_started = false;
m_impl->m_active = false;
m_impl->m_pending = false;
m_impl->m_changing = false;
m_impl->m_sendBytes = 0;
m_impl->m_lastBudgetCaptureTimeNs = 0;
m_impl->m_frames.clear();
m_impl->m_sends.clear();
for (Impl::PeerState& peer : m_impl->m_peers)
@@ -3917,7 +3973,10 @@ void CNetworkVideo::PeerDisconnected(const NetworkVideoPeer& key)
if (peer != m_impl->m_peers.end())
m_impl->m_peers.erase(peer);
if (!m_impl->HasSubscribersNL())
{
m_impl->m_lastBudgetCaptureTimeNs = 0;
m_impl->m_frames.clear();
}
}
NetworkVideoPacketResult CNetworkVideo::HandlePacket(
@@ -3953,11 +4012,13 @@ NetworkVideoPacketResult CNetworkVideo::HandlePacket(
packet.payloadLength) != LG_NET_PARSE_OK ||
subscribe.maxWidth > MAX_WIDTH ||
subscribe.maxHeight > MAX_HEIGHT ||
subscribe.maxFrameLength < PYROWAVE_MIN_FRAME_BYTES ||
subscribe.maxFrameLength > MAX_FRAME_LENGTH ||
(subscribe.preferredCodec &&
subscribe.preferredCodec != LG_NET_VIDEO_CODEC_PYROWAVE))
return NetworkVideoPacketResult::INVALID;
const bool hadSubscribers = m_impl->HasSubscribersNL();
m_impl->DropPeerSendsNL(*peer);
peer->subscription = subscribe;
peer->subscribed = true;
@@ -3967,6 +4028,8 @@ NetworkVideoPacketResult CNetworkVideo::HandlePacket(
peer->congested = false;
peer->keyframePending = true;
peer->sendFailed = false;
if (!hadSubscribers)
m_impl->m_lastBudgetCaptureTimeNs = 0;
if (m_impl->m_active)
{
if (!m_impl->QueueConfigNL(
@@ -4001,6 +4064,11 @@ NetworkVideoPacketResult CNetworkVideo::HandlePacket(
peer->configured = false;
peer->componentEpoch = 0;
peer->keyframePending = false;
if (!m_impl->HasSubscribersNL())
{
m_impl->m_lastBudgetCaptureTimeNs = 0;
m_impl->m_frames.clear();
}
break;
}
@@ -4150,13 +4218,14 @@ void CNetworkVideo::Commit()
CSRWExclusiveLock lock(m_impl->m_lock);
if (!m_impl->m_pending || !m_impl->m_changing || m_impl->m_stopped)
return;
m_impl->m_activeCfg = m_impl->m_pendingCfg;
m_impl->m_pendingCfg = {};
m_impl->m_active = true;
m_impl->m_pending = false;
m_impl->m_failed = false;
m_impl->m_changing = false;
m_impl->m_configEpoch = Next64(m_impl->m_configEpoch);
m_impl->m_activeCfg = m_impl->m_pendingCfg;
m_impl->m_pendingCfg = {};
m_impl->m_active = true;
m_impl->m_pending = false;
m_impl->m_failed = false;
m_impl->m_changing = false;
m_impl->m_configEpoch = Next64(m_impl->m_configEpoch);
m_impl->m_lastBudgetCaptureTimeNs = 0;
m_impl->m_frames.clear();
for (Impl::PeerState& peer : m_impl->m_peers)
if (peer.subscribed)

View File

@@ -96,7 +96,8 @@ public:
bool AttachSender(INetworkVideoSender& sender);
void DetachSender();
bool Start(LGNetVideoChromaSubsampling videoChroma);
bool Start(uint32_t videoBitrateMbps,
LGNetVideoChromaSubsampling videoChroma);
void Stop();
void Process(uint64_t nowMs);

View File

@@ -29,12 +29,13 @@ namespace
{
enum Field : unsigned
{
FIELD_BIND = 1U << 0,
FIELD_PORT = 1U << 1,
FIELD_MAX_CLIENTS = 1U << 2,
FIELD_AUTH = 1U << 3,
FIELD_PASSWORD = 1U << 4,
FIELD_VIDEO_CHROMA = 1U << 5,
FIELD_BIND = 1U << 0,
FIELD_PORT = 1U << 1,
FIELD_MAX_CLIENTS = 1U << 2,
FIELD_AUTH = 1U << 3,
FIELD_PASSWORD = 1U << 4,
FIELD_VIDEO_CHROMA = 1U << 5,
FIELD_VIDEO_BITRATE = 1U << 6,
};
std::wstring Trim(const std::wstring& value)
@@ -152,6 +153,14 @@ namespace
else
return Fail(error, L"invalid video chroma subsampling");
}
else if (Equal(key, L"videoBitrateMbps"))
{
field = FIELD_VIDEO_BITRATE;
unsigned long parsed = 0;
if (!ParseUnsigned(value, NETWORK_MAX_VIDEO_BITRATE_MBPS, parsed))
return Fail(error, L"invalid video bitrate");
settings.videoBitrateMbps = static_cast<uint32_t>(parsed);
}
else
return Fail(error, L"unknown setting");

View File

@@ -20,30 +20,35 @@
#pragma once
#include <LGProtocol/NetworkProtocol.h>
#include <LGProtocol/NetworkServices.h>
#include <stdint.h>
#include <string>
static constexpr uint16_t NETWORK_DEFAULT_MAX_CLIENTS = 4;
static constexpr uint16_t NETWORK_MAX_CLIENTS = 32;
static constexpr LGNetVideoChromaSubsampling NETWORK_DEFAULT_VIDEO_CHROMA =
static constexpr uint16_t NETWORK_DEFAULT_MAX_CLIENTS = 4;
static constexpr uint16_t NETWORK_MAX_CLIENTS = 32;
static constexpr uint32_t NETWORK_DEFAULT_VIDEO_BITRATE_MBPS = 200;
static constexpr uint32_t NETWORK_MAX_VIDEO_BITRATE_MBPS = 100000;
static constexpr LGNetVideoChromaSubsampling NETWORK_DEFAULT_VIDEO_CHROMA =
static_cast<LGNetVideoChromaSubsampling>(LG_NET_VIDEO_CHROMA_444);
struct NetworkSettings
{
std::wstring bindAddress = L"*";
uint16_t port =
std::wstring bindAddress = L"*";
uint16_t port =
static_cast<uint16_t>(LG_NET_DEFAULT_PORT);
uint16_t maxClients = NETWORK_DEFAULT_MAX_CLIENTS;
LGNetAuthMode auth = LG_NET_AUTH_MODE_DEFAULT;
uint16_t maxClients = NETWORK_DEFAULT_MAX_CLIENTS;
LGNetAuthMode auth = LG_NET_AUTH_MODE_DEFAULT;
std::wstring password;
LGNetVideoChromaSubsampling videoChroma =
uint32_t videoBitrateMbps =
NETWORK_DEFAULT_VIDEO_BITRATE_MBPS;
LGNetVideoChromaSubsampling videoChroma =
NETWORK_DEFAULT_VIDEO_CHROMA;
};
// Settings use a semicolon-separated key=value list. Supported keys are
// bind, port, maxClients, auth, password, and videoChroma. videoChroma accepts
// bind, port, maxClients, auth, password, videoBitrateMbps, and videoChroma.
// videoBitrateMbps uses decimal Mbit/s and defaults to 200. videoChroma accepts
// 420 or 444 and defaults to 444. Password authentication requires a non-empty
// password. The default address requests a dual-stack wildcard listener from
// the eventual QUIC provider.