/ src / library / av_processing / BitReader.php
<?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;
    }
}
X