<?php
/**
* SeekQuarry/Yioop --
* Open Source Pure PHP Search Engine, Crawler, and Indexer
*
* Copyright (C) 2009 - 2026 Chris Pollett chris@pollett.org
*
* LICENSE:
*
* 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 3 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, see <https://www.gnu.org/licenses/>.
*
* END LICENSE
*
* @author Chris Pollett chris@pollett.org
* @license https://www.gnu.org/licenses/ GPL3
* @link https://www.seekquarry.com/
* @copyright 2009 - 2026
* @filesource
*
* This class reads a string of bytes a bit at a time, the most significant
* bit first. That is how these formats write their settings.
*/
namespace seekquarry\yioop\library\av_processing;
/**
* BitReader walks a string of bytes one bit at a time, taking the highest
* bit of a byte first.
*/
class BitReader
{
/**
* $bytes stores the bytes this reader was given. It walks them and never
* changes them, so several readers may work over the same bytes at once.
* @var string
*/
public string $bytes;
/**
* $position stores how many bits the reader has already passed, counting
* from the start of the bytes. Reading a field moves it forward by the
* width of that field, and skipBits moves it forward without handing
* anything back, which is how a decoder steps over a field it does not
* need.
* @var int
*/
public int $position = 0;
/**
* $bit_count stores how many bits the bytes hold altogether, which is eight
* times their length. A decoder asks how many are left before reading a
* field, so that a file cut short is reported rather than read past its
* end.
* @var int
*/
public int $bit_count;
/**
* __construct a reader is made over the bytes a caller wants to read.
* Nothing is read until a field is asked for.
*
* @param string $data The bytes to read through.
*/
public function __construct(string $data)
{
$this->bytes = $data;
$this->bit_count = strlen($data) * 8;
}
/**
* bitsLeft a caller asks how many bits are left before reading a field
* whose width it knows, so that a file which ends early is reported rather
* than read past its end.
*
* @return int How many bits remain unread.
*/
public function bitsLeft(): int
{
return $this->bit_count - $this->position;
}
/**
* readBit one bit is read and the reader moves on by one. Formats write
* their flags as single bits, so this is the smallest thing a decoder asks
* for. Reading past the end throws, since a decoder that has run off the
* end of a file has lost its place.
*
* @return int The bit read, zero or one.
*/
public function readBit(): int
{
if ($this->position >= $this->bit_count) {
throw new VideoException('bitstream overrun');
}
$byte = ord($this->bytes[$this->position >> 3]);
$bit = ($byte >> (7 - ($this->position & 7))) & 1;
$this->position++;
return $bit;
}
/**
* readBits a field of a known width is read as one number, its highest bit
* first. Most of what a format's settings hold is written this way: a width
* in sixteen bits, a flag in one, a code in five.
*
* @param int $count How many bits the field takes.
* @return int The number those bits spell out.
*/
public function readBits(int $count): int
{
$value = 0;
for ($i = 0; $i < $count; $i++) {
$value = ($value << 1) | $this->readBit();
}
return $value;
}
/**
* lookAtBits the next bits are read without the reader moving, so a caller
* can look at what comes next and decide how to read it. Bits past the end
* of the bytes come back as zero rather than throwing, since looking ahead
* at the end of a file is not itself an error.
*
* @param int $count How many bits to look at.
* @return int The number those bits spell out.
*/
public function lookAtBits(int $count): int
{
$value = 0;
$position = $this->position;
for ($i = 0; $i < $count; $i++, $position++) {
$bit = 0;
if ($position < $this->bit_count) {
$whole = ord($this->bytes[$position >> 3]);
$bit = ($whole >> (7 - ($position & 7))) & 1;
}
$value = ($value << 1) | $bit;
}
return $value;
}
/**
* skipBits the reader moves forward without anything being read. A decoder
* uses this for a field whose width the format fixes but whose value it
* does not need, which is quicker than reading it and throwing the number
* away.
*
* @param int $count How many bits to pass over.
*/
public function skipBits(int $count): void
{
$this->position += $count;
}
}