<?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
*/
namespace seekquarry\yioop\library\av_processing;
/**
* AacEncoder compresses a run of samples into AAC frames. Samples arrive a
* stretch at a time from whatever decoded them, in lengths that have nothing to
* do with the lengths AAC works in. So they are gathered here until there are
* enough for a frame, a frame is made, and the gathered samples are dropped as
* far as the next frame still needs. Nothing more than two frames' worth is
* held, so a recording of any length costs the same. Each frame covers twice as
* many samples as it advances by, because neighboring frames overlap and fade
* into each other. The first and last frames have silence on the side where
* there is no neighbor.
*/
class AacEncoder
{
/**
* HOP is how many samples a frame advances by.
*/
const HOP = 1024;
/**
* SPAN is how many samples a frame covers, which is twice what it advances.
*/
const SPAN = 2048;
/**
* SAMPLE_RATE is samples a second the sound arrives at.
*/
const SAMPLE_RATE = 48000;
/**
* waiting stores samples gathered but not yet covered by a finished frame.
* @var array
*/
public $waiting;
/**
* frames stores the finished frames of compressed sound, each
* covering the same fraction of a second, ready for the writer
* to put into a file.
* @var array
*/
public $frames;
/**
* budget stores how many bits a frame may take.
* @var int
*/
public $budget;
/**
* fade stores the curve each frame is faded in and out with, worked
* out once and used for every frame, since neighboring frames
* overlap and their fades must add to one
* @var array
*/
public $fade;
/**
* plan stores works out the tones of a frame.
* @var object
*/
public $plan;
/**
* __construct sets up an encoder at a given rate
*
* @param int $rate how many bits a second to aim for
*/
public function __construct($rate = 64000)
{
$this->budget = (int)($rate * self::HOP / self::SAMPLE_RATE);
$this->fade = AacBands::fadeFor(AacBands::LONG);
$this->plan = Mdct::forSize(self::HOP);
/* A frame covers samples before the ones it advances past, so
the run starts with silence for the first frame to fade in
from. */
$this->waiting = array_fill(0, self::HOP, 0.0);
$this->frames = [];
}
/**
* take takes another run of samples and makes whatever frames it completes
*
* @param array $samples the samples to take
*/
public function take($samples)
{
foreach ($samples as $sample) {
$this->waiting[] = $sample;
}
while (count($this->waiting) >= self::SPAN) {
$this->makeFrame();
$this->waiting = array_slice($this->waiting, self::HOP);
}
}
/**
* finish says there are no more samples, and makes a last frame to fade out
* into silence
*/
public function finish()
{
while (count($this->waiting) > self::HOP) {
$this->waiting[] = 0.0;
if (count($this->waiting) >= self::SPAN) {
$this->makeFrame();
$this->waiting = array_slice($this->waiting, self::HOP);
}
}
}
/**
* makeFrame makes one frame from the samples now waiting
*/
public function makeFrame()
{
$shaped = [];
for ($i = 0; $i < self::SPAN; $i++) {
$shaped[] = $this->waiting[$i] * $this->fade[$i];
}
$made = AacFrame::write($this->plan->forward($shaped),
$this->budget);
$this->frames[] = $made["bytes"];
}
}