mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-11-13 07:17:28 +00:00
342 lines
12 KiB
C
342 lines
12 KiB
C
/**
|
|
* 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
|
|
*/
|
|
|
|
/*
|
|
* The packet layout and block ordering validated here are adapted from the
|
|
* PyroWave bitstream specification and reference parser at commit
|
|
* 89f7e47d4abbf650c91fae766728af866c5e32a0.
|
|
*
|
|
* Copyright (c) 2025 Hans-Kristian Arntzen
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
* copy of this software and associated documentation files (the "Software"),
|
|
* to deal in the Software without restriction, including without limitation
|
|
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
* and/or sell copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
* DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
|
|
#include "pyrowave.h"
|
|
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
|
|
#define NETWORK_PYROWAVE_HEADER_SIZE 8U
|
|
#define NETWORK_PYROWAVE_DECOMPOSITION_LEVELS 5U
|
|
#define NETWORK_PYROWAVE_ALIGNMENT \
|
|
(1U << NETWORK_PYROWAVE_DECOMPOSITION_LEVELS)
|
|
#define NETWORK_PYROWAVE_MINIMUM_SIZE \
|
|
(4U << NETWORK_PYROWAVE_DECOMPOSITION_LEVELS)
|
|
#define NETWORK_PYROWAVE_SEQUENCE_MASK 7U
|
|
|
|
static uint32_t networkPyroWaveReadU32(const uint8_t * data)
|
|
{
|
|
return (uint32_t)data[0] |
|
|
(uint32_t)data[1] << 8 |
|
|
(uint32_t)data[2] << 16 |
|
|
(uint32_t)data[3] << 24;
|
|
}
|
|
|
|
static uint16_t networkPyroWaveReadU16(const uint8_t * data)
|
|
{
|
|
return (uint16_t)((uint16_t)data[0] | (uint16_t)data[1] << 8);
|
|
}
|
|
|
|
static unsigned networkPyroWaveBitCount(uint32_t value)
|
|
{
|
|
unsigned count = 0;
|
|
while (value)
|
|
{
|
|
count += value & 1U;
|
|
value >>= 1;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
static bool networkPyroWavePacketValid(
|
|
const uint8_t * packet, size_t packetSize, uint16_t ballot)
|
|
{
|
|
const unsigned groups = networkPyroWaveBitCount(ballot);
|
|
const size_t controlBase = NETWORK_PYROWAVE_HEADER_SIZE;
|
|
const size_t scaleBase = controlBase + groups * sizeof(uint16_t);
|
|
const size_t planeBase = scaleBase + groups;
|
|
if (planeBase > packetSize)
|
|
return false;
|
|
|
|
size_t planeOffset = planeBase;
|
|
unsigned signs = 0;
|
|
for (unsigned group = 0; group < 16U; ++group)
|
|
{
|
|
if (!(ballot & (UINT32_C(1) << group)))
|
|
continue;
|
|
|
|
const unsigned compact = networkPyroWaveBitCount(
|
|
ballot & ((UINT32_C(1) << group) - 1U));
|
|
const uint16_t control = networkPyroWaveReadU16(
|
|
packet + controlBase + compact * sizeof(uint16_t));
|
|
const uint8_t qBits = packet[scaleBase + compact] & 15U;
|
|
|
|
for (unsigned subblock = 0; subblock < 8U; ++subblock)
|
|
{
|
|
const unsigned planes = qBits +
|
|
((control >> (2U * subblock)) & 3U);
|
|
if (planes > packetSize - planeOffset)
|
|
return false;
|
|
|
|
uint8_t nonzero = 0;
|
|
for (unsigned plane = 0; plane < planes; ++plane)
|
|
nonzero |= packet[planeOffset++];
|
|
signs += networkPyroWaveBitCount(nonzero);
|
|
}
|
|
}
|
|
|
|
const size_t signBytes = (signs + 7U) / 8U;
|
|
if (signBytes > packetSize - planeOffset)
|
|
return false;
|
|
|
|
/* payload_words rounds the packet to a u32 boundary. No other trailing
|
|
* payload is defined by version 1. */
|
|
return packetSize - planeOffset - signBytes < sizeof(uint32_t);
|
|
}
|
|
|
|
static uint32_t networkPyroWaveAlign(uint32_t value, uint32_t alignment)
|
|
{
|
|
return (value + alignment - 1U) & ~(alignment - 1U);
|
|
}
|
|
|
|
static bool networkPyroWaveBlockCapacity(
|
|
const LGNetVideoStreamConfig * config, uint32_t * capacity)
|
|
{
|
|
if (!config || !capacity)
|
|
return false;
|
|
|
|
uint32_t width = networkPyroWaveAlign(
|
|
config->width, NETWORK_PYROWAVE_ALIGNMENT);
|
|
uint32_t height = networkPyroWaveAlign(
|
|
config->height, NETWORK_PYROWAVE_ALIGNMENT);
|
|
if (width < NETWORK_PYROWAVE_MINIMUM_SIZE)
|
|
width = NETWORK_PYROWAVE_MINIMUM_SIZE;
|
|
if (height < NETWORK_PYROWAVE_MINIMUM_SIZE)
|
|
height = NETWORK_PYROWAVE_MINIMUM_SIZE;
|
|
|
|
uint64_t blocks = 0;
|
|
for (int level = NETWORK_PYROWAVE_DECOMPOSITION_LEVELS - 1;
|
|
level >= 0; --level)
|
|
{
|
|
const uint32_t levelWidth = width >> (level + 1);
|
|
const uint32_t levelHeight = height >> (level + 1);
|
|
const uint32_t blocksWide = (levelWidth + 31U) / 32U;
|
|
const uint32_t blocksHigh = (levelHeight + 31U) / 32U;
|
|
const uint32_t bands =
|
|
level == NETWORK_PYROWAVE_DECOMPOSITION_LEVELS - 1 ? 4U : 3U;
|
|
uint32_t components = 3U;
|
|
if (!level && config->chromaSubsampling == LG_NET_VIDEO_CHROMA_420)
|
|
components = 1U;
|
|
|
|
blocks += (uint64_t)blocksWide * blocksHigh * bands * components;
|
|
}
|
|
|
|
if (!blocks || blocks > LG_NET_VIDEO_MAX_BLOCKS)
|
|
return false;
|
|
|
|
*capacity = (uint32_t)blocks;
|
|
return true;
|
|
}
|
|
|
|
bool networkPyroWaveProfileValid(const LGNetVideoStreamConfig * config)
|
|
{
|
|
if (!config || !lgNetVideoStreamConfigValid(config) ||
|
|
config->codec != LG_NET_VIDEO_CODEC_PYROWAVE ||
|
|
config->codecVersion < LG_NET_VIDEO_PYROWAVE_CODEC_VERSION_MIN ||
|
|
config->codecVersion > LG_NET_VIDEO_PYROWAVE_CODEC_VERSION_MAX)
|
|
return false;
|
|
|
|
if (config->flags & LG_NET_VIDEO_STREAM_RESERVED_ALPHA)
|
|
return false;
|
|
|
|
const bool hdr =
|
|
(config->flags & LG_NET_VIDEO_STREAM_HDR) != 0;
|
|
if (hdr ?
|
|
(config->colorPrimaries != LG_NET_COLOR_PRIMARIES_BT2020 ||
|
|
config->colorTransfer != LG_NET_COLOR_TRANSFER_PQ ||
|
|
config->colorMatrix != LG_NET_COLOR_MATRIX_BT2020) :
|
|
(config->colorPrimaries != LG_NET_COLOR_PRIMARIES_BT709 ||
|
|
config->colorTransfer != LG_NET_COLOR_TRANSFER_BT709 ||
|
|
config->colorMatrix != LG_NET_COLOR_MATRIX_BT709))
|
|
return false;
|
|
|
|
uint32_t capacity;
|
|
return networkPyroWaveBlockCapacity(config, &capacity);
|
|
}
|
|
|
|
static bool networkPyroWaveSequenceProfile(
|
|
const LGNetVideoStreamConfig * config, uint32_t word)
|
|
{
|
|
const uint32_t chroma = (word >> 26) & 1U;
|
|
const uint32_t primaries = (word >> 27) & 1U;
|
|
const uint32_t transfer = (word >> 28) & 1U;
|
|
const uint32_t transform = (word >> 29) & 1U;
|
|
const uint32_t range = (word >> 30) & 1U;
|
|
const uint32_t wireChroma =
|
|
config->chromaSubsampling == LG_NET_VIDEO_CHROMA_444 ? 1U : 0U;
|
|
const uint32_t wirePrimaries =
|
|
config->colorPrimaries == LG_NET_COLOR_PRIMARIES_BT2020 ? 1U : 0U;
|
|
const uint32_t wireTransfer =
|
|
config->colorTransfer == LG_NET_COLOR_TRANSFER_PQ ? 1U : 0U;
|
|
const uint32_t wireTransform =
|
|
config->colorMatrix == LG_NET_COLOR_MATRIX_BT2020 ? 1U : 0U;
|
|
const uint32_t wireRange =
|
|
config->colorRange == LG_NET_COLOR_RANGE_LIMITED ? 1U : 0U;
|
|
|
|
return chroma == wireChroma &&
|
|
primaries == wirePrimaries &&
|
|
transfer == wireTransfer &&
|
|
transform == wireTransform &&
|
|
range == wireRange;
|
|
}
|
|
|
|
NetworkPyroWaveResult networkPyroWaveParse(
|
|
const LGNetVideoStreamConfig * config, const void * data, size_t size,
|
|
uint32_t expectedBlocks, uint32_t * offsets, uint32_t offsetCount,
|
|
NetworkPyroWaveLayout * layout)
|
|
{
|
|
if (!networkPyroWaveProfileValid(config))
|
|
return NETWORK_PYROWAVE_UNSUPPORTED;
|
|
if (!data || size < NETWORK_PYROWAVE_HEADER_SIZE ||
|
|
size > config->maxFrameLength || !expectedBlocks || !offsets || !layout)
|
|
return NETWORK_PYROWAVE_INVALID;
|
|
|
|
uint32_t capacity;
|
|
if (!networkPyroWaveBlockCapacity(config, &capacity) ||
|
|
expectedBlocks > capacity || offsetCount != capacity)
|
|
return NETWORK_PYROWAVE_INVALID;
|
|
|
|
memset(offsets, 0xff, (size_t)capacity * sizeof(*offsets));
|
|
*layout = (NetworkPyroWaveLayout){0};
|
|
|
|
const uint8_t * bytes = data;
|
|
size_t cursor = 0;
|
|
uint32_t uniqueBlocks = 0;
|
|
uint32_t declaredBlocks = 0;
|
|
uint32_t headerFirst = 0;
|
|
uint32_t headerSecond = 0;
|
|
uint8_t frameSequence = 0;
|
|
bool haveSequence = false;
|
|
bool haveHeader = false;
|
|
bool haveClear = false;
|
|
bool valid = true;
|
|
|
|
while (valid && size - cursor >= NETWORK_PYROWAVE_HEADER_SIZE)
|
|
{
|
|
const uint32_t first = networkPyroWaveReadU32(bytes + cursor);
|
|
const uint32_t second = networkPyroWaveReadU32(
|
|
bytes + cursor + sizeof(uint32_t));
|
|
const uint8_t packetSequence =
|
|
(uint8_t)((first >> 28) & NETWORK_PYROWAVE_SEQUENCE_MASK);
|
|
const bool extended = (first >> 31) != 0;
|
|
|
|
if (haveSequence && packetSequence != frameSequence)
|
|
{
|
|
valid = false;
|
|
break;
|
|
}
|
|
haveSequence = true;
|
|
frameSequence = packetSequence;
|
|
|
|
if (extended)
|
|
{
|
|
const uint32_t code =
|
|
(second >> LG_NET_VIDEO_PYROWAVE_MODE_SHIFT) &
|
|
LG_NET_VIDEO_PYROWAVE_MODE_MASK;
|
|
const uint32_t width = (first & UINT32_C(0x00003FFF)) + 1U;
|
|
const uint32_t height = ((first >> 14) & UINT32_C(0x00003FFF)) + 1U;
|
|
const uint32_t blocks = second & UINT32_C(0x00FFFFFF);
|
|
if ((code != LG_NET_VIDEO_PYROWAVE_MODE_FULL &&
|
|
code != LG_NET_VIDEO_PYROWAVE_MODE_DELTA) ||
|
|
width != config->width || height != config->height ||
|
|
!blocks || blocks > capacity ||
|
|
!networkPyroWaveSequenceProfile(config, second) ||
|
|
(haveHeader &&
|
|
(first != headerFirst || second != headerSecond)))
|
|
{
|
|
valid = false;
|
|
break;
|
|
}
|
|
|
|
declaredBlocks = blocks;
|
|
headerFirst = first;
|
|
headerSecond = second;
|
|
haveHeader = true;
|
|
cursor += NETWORK_PYROWAVE_HEADER_SIZE;
|
|
continue;
|
|
}
|
|
|
|
const uint32_t ballot = first & UINT32_C(0x0000FFFF);
|
|
const uint32_t payloadWords = (first >> 16) & UINT32_C(0x00000FFF);
|
|
const uint32_t blockIndex = second >> 8;
|
|
const size_t packetSize = (size_t)payloadWords * sizeof(uint32_t);
|
|
const bool clear = ballot == 0;
|
|
if (payloadWords < 2U || packetSize > size - cursor ||
|
|
blockIndex >= capacity || offsets[blockIndex] != UINT32_MAX ||
|
|
(clear ? payloadWords != LG_NET_VIDEO_PYROWAVE_CLEAR_PACKET_WORDS :
|
|
!networkPyroWavePacketValid(
|
|
bytes + cursor, packetSize, (uint16_t)ballot)))
|
|
{
|
|
valid = false;
|
|
break;
|
|
}
|
|
|
|
offsets[blockIndex] = (uint32_t)(cursor / sizeof(uint32_t));
|
|
if (clear)
|
|
haveClear = true;
|
|
++uniqueBlocks;
|
|
cursor += packetSize;
|
|
}
|
|
|
|
valid = valid && cursor == size && haveHeader && haveSequence &&
|
|
declaredBlocks == uniqueBlocks && expectedBlocks == uniqueBlocks &&
|
|
(!haveClear ||
|
|
((headerSecond >> LG_NET_VIDEO_PYROWAVE_MODE_SHIFT) &
|
|
LG_NET_VIDEO_PYROWAVE_MODE_MASK) ==
|
|
LG_NET_VIDEO_PYROWAVE_MODE_DELTA);
|
|
if (!valid)
|
|
return NETWORK_PYROWAVE_INVALID;
|
|
|
|
layout->sequence = frameSequence;
|
|
layout->chromaLeft = (headerSecond >> 31) != 0;
|
|
layout->delta =
|
|
((headerSecond >> LG_NET_VIDEO_PYROWAVE_MODE_SHIFT) &
|
|
LG_NET_VIDEO_PYROWAVE_MODE_MASK) ==
|
|
LG_NET_VIDEO_PYROWAVE_MODE_DELTA;
|
|
return NETWORK_PYROWAVE_OK;
|
|
}
|