<?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
*
* VorbisHeader reads what a Vorbis stream says about itself before any
* sound is read.
*/
namespace seekquarry\yioop\library\av_processing;
/**
* VorbisHeader reads the three descriptions a Vorbis stream begins
* with. The first says how the sound was recorded, the second carries
* the titles and names a person typed, and the third holds every table
* the stream is written with.
*
* Vorbis differs from the other formats this folder reads in where its
* tables come from. AAC and MP3 take theirs from their standards, the
* same in every file, while a Vorbis file carries its own, chosen by
* the encoder for the sound at hand. A decoder therefore reads them out
* of the file before it can read a single value.
*
* @author Chris Pollett
*/
class VorbisHeader
{
/**
* IDENTIFICATION is the number the first description gives itself,
* the one saying how the sound was recorded.
* @var int
*/
const IDENTIFICATION = 1;
/**
* NAMES is the number the second description gives itself, the one
* carrying titles and names.
* @var int
*/
const NAMES = 3;
/**
* TABLES is the number the third description gives itself, the one
* holding every table the stream is written with.
* @var int
*/
const TABLES = 5;
/**
* MARK is the word every description carries after its number, so a
* reader can tell a Vorbis description from anything else.
* @var string
*/
const MARK = "vorbis";
/**
* $rate stores how many samples a second the sound was recorded at.
* @var int
*/
public $rate = 0;
/**
* $channels stores how many channels the sound carries.
* @var int
*/
public $channels = 1;
/**
* $short_block stores how many samples the shorter of the two
* stretches covers. A stream writes a short stretch where the sound
* changes quickly, so that a change is not smeared across a long
* one.
* @var int
*/
public $short_block = 0;
/**
* $long_block stores how many samples the longer stretch covers,
* which is what most of a recording is written in.
* @var int
*/
public $long_block = 0;
/**
* $lowest_rate stores the fewest bits a second the encoder aimed
* at, or zero where it named none.
* @var int
*/
public $lowest_rate = 0;
/**
* $usual_rate stores the bits a second the encoder aimed at, or
* zero where it named none.
* @var int
*/
public $usual_rate = 0;
/**
* $highest_rate stores the most bits a second the encoder aimed at,
* or zero where it named none.
* @var int
*/
public $highest_rate = 0;
/**
* fromString reads the first description of a Vorbis stream, which
* says how the sound was recorded. A caller reads it before
* anything else, since the rate and the channel count settle how
* every later packet is read.
*
* @param string $data The first packet of the stream.
* @return VorbisHeader What the description says.
*/
public static function fromString($data)
{
if (strlen($data) < 30) {
throw new \RuntimeException("this Vorbis description is too "
. "short to be read");
}
if (ord($data[0]) !== self::IDENTIFICATION
|| substr($data, 1, 6) !== self::MARK) {
throw new \RuntimeException("this packet is not the "
. "description a Vorbis stream begins with");
}
$said = new self();
$said->channels = max(1, ord($data[11]));
$said->rate = unpack("V", substr($data, 12, 4))[1];
$said->highest_rate = self::signedRate(substr($data, 16, 4));
$said->usual_rate = self::signedRate(substr($data, 20, 4));
$said->lowest_rate = self::signedRate(substr($data, 24, 4));
$sizes = ord($data[28]);
$said->short_block = 1 << ($sizes & 0x0F);
$said->long_block = 1 << (($sizes >> 4) & 0x0F);
return $said;
}
/**
* signedRate reads one of the three bit rates a description may
* carry. A rate the encoder did not name is written as a number
* below zero, and this hands back zero for those, so a caller need
* not tell the two apart.
*
* @param string $held The four bytes the description stored.
* @return int The rate in bits a second, or zero where none was
* named.
*/
public static function signedRate($held)
{
$value = unpack("V", $held)[1];
if ($value >= 2147483648) {
$value -= 4294967296;
}
return ($value > 0) ? $value : 0;
}
/**
* describe says what a Vorbis stream holds, without decoding any
* sound. A caller prints this when somebody asks what is inside a
* file.
*
* @param array $packets The packets of the stream, in order.
* @return array What the stream says about itself, keyed by
* container, codec, rate, channels and seconds.
*/
public static function describe($packets)
{
if ($packets === []) {
throw new \RuntimeException("this file carries no Vorbis "
. "sound");
}
$said = self::fromString($packets[0]);
return ["codec" => "vorbis", "rate" => $said->rate,
"channels" => $said->channels,
"short_block" => $said->short_block,
"long_block" => $said->long_block];
}
}