mirror of
https://github.com/claunia/cuetools.net.git
synced 2025-12-16 18:14:25 +00:00
2641 lines
104 KiB
C#
2641 lines
104 KiB
C#
/**
|
|
* CUETools.Flake: pure managed FLAC audio encoder
|
|
* Copyright (c) 2009 Grigory Chudov
|
|
* Based on Flake encoder, http://flake-enc.sourceforge.net/
|
|
* Copyright (c) 2006-2009 Justin Ruggles
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library 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
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
#define NOINTEROP
|
|
#define VARIANT1
|
|
|
|
using System;
|
|
using System.ComponentModel;
|
|
using System.Text;
|
|
using System.IO;
|
|
using System.Collections.Generic;
|
|
using System.Security.Cryptography;
|
|
#if INTEROP
|
|
using System.Runtime.InteropServices;
|
|
#endif
|
|
using CUETools.Codecs;
|
|
using Newtonsoft.Json;
|
|
|
|
namespace CUETools.Codecs.FLAKE
|
|
{
|
|
[JsonObject(MemberSerialization.OptIn)]
|
|
public class EncoderSettings : AudioEncoderSettings
|
|
{
|
|
public override string Extension => "flac";
|
|
|
|
public override string Name => "cuetools";
|
|
|
|
public override Type EncoderType => typeof(AudioEncoder);
|
|
|
|
public override int Priority => 4;
|
|
|
|
public override bool Lossless => true;
|
|
|
|
public EncoderSettings()
|
|
: base()
|
|
{
|
|
}
|
|
|
|
public override string GetSupportedModes(out string defaultMode)
|
|
{
|
|
defaultMode = "5";
|
|
return this.AllowNonSubset || (this.PCM != null && this.PCM.SampleRate > 48000) ? "0 1 2 3 4 5 6 7 8 9 10 11" : "0 1 2 3 4 5 6 7 8";
|
|
}
|
|
|
|
public bool IsSubset()
|
|
{
|
|
return (BlockSize == 0 || (BlockSize <= 16384 && (PCM.SampleRate > 48000 || BlockSize <= 4608)))
|
|
&& (PCM.SampleRate > 48000 || MaxLPCOrder <= 12)
|
|
&& MaxPartitionOrder <= 8
|
|
;
|
|
//The blocksize bits in the frame header must be 0001-1110. The blocksize must be <=16384; if the sample rate is <= 48000Hz, the blocksize must be <=4608.
|
|
//The sample rate bits in the frame header must be 0001-1110.
|
|
//The bits-per-sample bits in the frame header must be 001-111.
|
|
//If the sample rate is <= 48000Hz, the filter order in LPC subframes must be less than or equal to 12, i.e. the subframe type bits in the subframe header may not be 101100-111111.
|
|
//The Rice partition order in a Rice-coded residual section must be less than or equal to 8.
|
|
}
|
|
|
|
public void Validate()
|
|
{
|
|
if (EncoderModeIndex < 0)
|
|
throw new Exception("unsupported encoder mode");
|
|
SetDefaultValuesForMode();
|
|
if (Padding < 0)
|
|
throw new Exception("unsupported padding value " + Padding.ToString());
|
|
if (BlockSize != 0 && (BlockSize < 256 || BlockSize >= FlakeConstants.MAX_BLOCKSIZE))
|
|
throw new Exception("unsupported block size " + BlockSize.ToString());
|
|
if (MinLPCOrder > MaxLPCOrder || MaxLPCOrder > lpc.MAX_LPC_ORDER)
|
|
throw new Exception("invalid MaxLPCOrder " + MaxLPCOrder.ToString());
|
|
if (MinFixedOrder < 0 || MinFixedOrder > 4)
|
|
throw new Exception("invalid MinFixedOrder " + MinFixedOrder.ToString());
|
|
if (MaxFixedOrder < 0 || MaxFixedOrder > 4)
|
|
throw new Exception("invalid MaxFixedOrder " + MaxFixedOrder.ToString());
|
|
if (MinPartitionOrder < 0)
|
|
throw new Exception("invalid MinPartitionOrder " + MinPartitionOrder.ToString());
|
|
if (MinPartitionOrder > MaxPartitionOrder || MaxPartitionOrder > 8)
|
|
throw new Exception("invalid MaxPartitionOrder " + MaxPartitionOrder.ToString());
|
|
if (PredictionType == PredictionType.None)
|
|
throw new Exception("invalid PredictionType " + PredictionType.ToString());
|
|
if (PredictionType != PredictionType.Fixed)
|
|
{
|
|
if (WindowMethod == WindowMethod.Invalid)
|
|
throw new InvalidOperationException("invalid WindowMethod " + WindowMethod.ToString());
|
|
if (WindowFunctions == WindowFunction.None)
|
|
throw new InvalidOperationException("invalid WindowFunctions " + WindowFunctions.ToString());
|
|
if (EstimationDepth > 32 || EstimationDepth < 1)
|
|
throw new InvalidOperationException("invalid EstimationDepth " + EstimationDepth.ToString());
|
|
if (MinPrecisionSearch < 0 || MinPrecisionSearch >= lpc.MAX_LPC_PRECISIONS)
|
|
throw new Exception("unsupported MinPrecisionSearch value");
|
|
if (MaxPrecisionSearch < 0 || MaxPrecisionSearch >= lpc.MAX_LPC_PRECISIONS)
|
|
throw new Exception("unsupported MaxPrecisionSearch value");
|
|
if (MaxPrecisionSearch < MinPrecisionSearch)
|
|
throw new Exception("unsupported MaxPrecisionSearch value");
|
|
}
|
|
if (!AllowNonSubset && !IsSubset())
|
|
throw new Exception("the encoding parameters specified do not conform to the FLAC Subset");
|
|
}
|
|
|
|
[DefaultValue(-1)]
|
|
[DefaultValueForMode(2, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0)]
|
|
[Browsable(false)]
|
|
[DisplayName("MinFixedOrder")]
|
|
[SRDescription(typeof(Properties.Resources), "MinFixedOrderDescription")]
|
|
public int MinFixedOrder { get; set; }
|
|
|
|
[DefaultValue(-1)]
|
|
[DefaultValueForMode(2, 4, 4, 4, 2, 2, 4, 4, 4, 4, 4, 4)]
|
|
[Browsable(false)]
|
|
[DisplayName("MaxFixedOrder")]
|
|
[SRDescription(typeof(Properties.Resources), "MaxFixedOrderDescription")]
|
|
public int MaxFixedOrder { get; set; }
|
|
|
|
[DefaultValue(1)]
|
|
[Browsable(false)]
|
|
[DisplayName("MinLPCOrder")]
|
|
[SRDescription(typeof(Properties.Resources), "MinLPCOrderDescription")]
|
|
public int MinLPCOrder { get; set; }
|
|
|
|
[DefaultValue(-1)]
|
|
[DefaultValueForMode(8, 8, 8, 12, 12, 12, 12, 12, 12, 32, 32, 32)]
|
|
[Browsable(false)]
|
|
[DisplayName("MaxLPCOrder")]
|
|
[SRDescription(typeof(Properties.Resources), "MaxLPCOrderDescription")]
|
|
public int MaxLPCOrder { get; set; }
|
|
|
|
[DefaultValue(0)]
|
|
[DisplayName("MinPartitionOrder")]
|
|
[Browsable(false)]
|
|
[SRDescription(typeof(Properties.Resources), "MinPartitionOrderDescription")]
|
|
public int MinPartitionOrder { get; set; }
|
|
|
|
[DefaultValue(-1)]
|
|
[DefaultValueForMode(6, 6, 6, 6, 6, 6, 6, 6, 7, 6, 6, 8)]
|
|
[DisplayName("MaxPartitionOrder")]
|
|
[Browsable(false)]
|
|
[SRDescription(typeof(Properties.Resources), "MaxPartitionOrderDescription")]
|
|
public int MaxPartitionOrder { get; set; }
|
|
|
|
[DefaultValue(false)]
|
|
[DisplayName("Verify")]
|
|
[SRDescription(typeof(Properties.Resources), "DoVerifyDescription")]
|
|
[JsonProperty]
|
|
public bool DoVerify { get; set; }
|
|
|
|
[DefaultValue(true)]
|
|
[DisplayName("MD5")]
|
|
[SRDescription(typeof(Properties.Resources), "DoMD5Description")]
|
|
[JsonProperty]
|
|
public bool DoMD5 { get; set; }
|
|
|
|
[DefaultValue(false)]
|
|
[DisplayName("Allow Non-subset")]
|
|
[SRDescription(typeof(Properties.Resources), "AllowNonSubsetDescription")]
|
|
[JsonProperty]
|
|
public bool AllowNonSubset { get; set; }
|
|
|
|
[DefaultValue(StereoMethod.Invalid)]
|
|
[DefaultValueForMode(
|
|
/* 0 */ StereoMethod.Independent,
|
|
/* 1 */ StereoMethod.EstimateFixed,
|
|
/* 2 */ StereoMethod.Estimate,
|
|
/* 3 */ StereoMethod.Estimate,
|
|
/* 4 */ StereoMethod.Evaluate,
|
|
/* 5 */ StereoMethod.Evaluate,
|
|
/* 6 */ StereoMethod.Evaluate,
|
|
/* 7 */ StereoMethod.Evaluate,
|
|
/* 8 */ StereoMethod.Evaluate,
|
|
/* 9 */ StereoMethod.Evaluate,
|
|
/* 10 */ StereoMethod.Evaluate,
|
|
/* 11 */ StereoMethod.Evaluate)]
|
|
[Browsable(false)]
|
|
public StereoMethod StereoMethod { get; set; }
|
|
|
|
[DefaultValue(PredictionType.None)]
|
|
[DefaultValueForMode(
|
|
/* 0 */ PredictionType.Fixed,
|
|
/* 1 */ PredictionType.Fixed,
|
|
/* 2 */ PredictionType.Levinson,
|
|
/* 3 */ PredictionType.Levinson,
|
|
/* 4 */ PredictionType.Search,
|
|
/* 5 */ PredictionType.Search,
|
|
/* 6 */ PredictionType.Search,
|
|
/* 7 */ PredictionType.Search,
|
|
/* 8 */ PredictionType.Search,
|
|
/* 9 */ PredictionType.Levinson,
|
|
/* 10 */ PredictionType.Search,
|
|
/* 11 */ PredictionType.Search)]
|
|
[Browsable(false)]
|
|
public PredictionType PredictionType { get; set; }
|
|
|
|
[DefaultValue(WindowMethod.Invalid)]
|
|
[DefaultValueForMode(
|
|
/* 0 */ WindowMethod.Invalid,
|
|
/* 1 */ WindowMethod.Invalid,
|
|
/* 2 */ WindowMethod.Estimate,
|
|
/* 3 */ WindowMethod.Estimate,
|
|
/* 4 */ WindowMethod.Estimate,
|
|
/* 5 */ WindowMethod.EvaluateN,
|
|
/* 6 */ WindowMethod.EvaluateN,
|
|
/* 7 */ WindowMethod.EvaluateN,
|
|
/* 8 */ WindowMethod.EvaluateN,
|
|
/* 9 */ WindowMethod.EvaluateN,
|
|
/* 10 */ WindowMethod.EvaluateN,
|
|
/* 11 */ WindowMethod.EvaluateN)]
|
|
[Browsable(false)]
|
|
public WindowMethod WindowMethod { get; set; }
|
|
|
|
[DefaultValue(WindowFunction.None)]
|
|
[DefaultValueForMode(
|
|
/* 0 */ WindowFunction.None,
|
|
/* 1 */ WindowFunction.None,
|
|
/* 2 */ WindowFunction.Tukey3,
|
|
/* 3 */ WindowFunction.Tukey4,
|
|
/* 4 */ WindowFunction.Tukey4,
|
|
/* 5 */ WindowFunction.Tukey4 | WindowFunction.Tukey3,
|
|
/* 6 */ WindowFunction.Tukey4 | WindowFunction.Tukey3 | WindowFunction.Tukey,
|
|
/* 7 */ WindowFunction.Tukey4 | WindowFunction.Tukey3 | WindowFunction.Tukey2 | WindowFunction.Tukey,
|
|
/* 8 */ WindowFunction.Tukey4 | WindowFunction.Tukey3 | WindowFunction.Tukey2 | WindowFunction.Tukey,
|
|
/* 9 */ WindowFunction.Tukey3 | WindowFunction.Tukey2 | WindowFunction.Tukey,
|
|
/* 10 */ WindowFunction.Tukey3 | WindowFunction.Tukey2 | WindowFunction.Tukey,
|
|
/* 11 */ WindowFunction.Tukey3 | WindowFunction.Tukey2 | WindowFunction.Tukey)]
|
|
[Browsable(false)]
|
|
[DisplayName("WindowFunctions")]
|
|
[SRDescription(typeof(Properties.Resources), "WindowFunctionsDescription")]
|
|
public WindowFunction WindowFunctions { get; set; }
|
|
|
|
[DefaultValue(0)]
|
|
[DefaultValueForMode(0, 0, 1, 1, 1, 1, 1, 1, 3, 1, 1, 5)]
|
|
[Browsable(false)]
|
|
public int EstimationDepth { get; set; }
|
|
|
|
[DefaultValue(-1)]
|
|
[DefaultValueForMode(1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1)]
|
|
[Browsable(false)]
|
|
public int MinPrecisionSearch { get; set; }
|
|
|
|
[DefaultValue(-1)]
|
|
[DefaultValueForMode(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)]
|
|
[Browsable(false)]
|
|
public int MaxPrecisionSearch { get; set; }
|
|
|
|
[DefaultValue(0)]
|
|
[Browsable(false)]
|
|
public int TukeyParts { get; set; }
|
|
|
|
[DefaultValue(1.0)]
|
|
[Browsable(false)]
|
|
public double TukeyOverlap { get; set; }
|
|
|
|
[DefaultValue(1.0)]
|
|
[Browsable(false)]
|
|
public double TukeyP { get; set; }
|
|
|
|
[Browsable(false)]
|
|
public string[] Tags { get; set; }
|
|
}
|
|
|
|
[AudioEncoderClass(typeof(EncoderSettings))]
|
|
//[AudioEncoderClass("libFlake nonsub", "flac", true, "9 10 11", "9", 3, typeof(FlakeWriterSettings))]
|
|
public class AudioEncoder : IAudioDest
|
|
{
|
|
Stream _IO = null;
|
|
string _path;
|
|
long _position;
|
|
|
|
// number of audio channels
|
|
// set by user prior to calling flake_encode_init
|
|
// valid values are 1 to 8
|
|
int channels, ch_code;
|
|
|
|
// audio sample rate in Hz
|
|
// set by user prior to calling flake_encode_init
|
|
int sr_code0, sr_code1;
|
|
|
|
// sample size in bits
|
|
// set by user prior to calling flake_encode_init
|
|
// only 16-bit is currently supported
|
|
int bps_code;
|
|
|
|
// total stream samples
|
|
// set by user prior to calling flake_encode_init
|
|
// if 0, stream length is unknown
|
|
int sample_count = -1;
|
|
|
|
FlakeEncodeParams eparams;
|
|
|
|
// maximum frame size in bytes
|
|
// set by flake_encode_init
|
|
// this can be used to allocate memory for output
|
|
int max_frame_size;
|
|
|
|
byte[] frame_buffer = null;
|
|
|
|
int frame_count = 0;
|
|
|
|
long first_frame_offset = 0;
|
|
|
|
#if INTEROP
|
|
TimeSpan _userProcessorTime;
|
|
#endif
|
|
|
|
// header bytes
|
|
// allocated by flake_encode_init and freed by flake_encode_close
|
|
byte[] header;
|
|
|
|
int[] samplesBuffer;
|
|
int[] verifyBuffer;
|
|
int[] residualBuffer;
|
|
float[] windowBuffer;
|
|
double[] windowScale;
|
|
LpcWindowSection[, ,] windowSections;
|
|
|
|
WindowFunction[] windowType;
|
|
int samplesInBuffer = 0;
|
|
|
|
int m_blockSize = 0;
|
|
int _totalSize = 0;
|
|
int _windowsize = 0, _windowcount = 0;
|
|
|
|
Crc8 crc8;
|
|
MD5 md5;
|
|
|
|
FlacFrame frame;
|
|
AudioDecoder verify;
|
|
|
|
SeekPoint[] seek_table;
|
|
int seek_table_offset = -1;
|
|
|
|
bool inited = false;
|
|
|
|
public AudioEncoder(EncoderSettings settings, string path, Stream IO = null)
|
|
{
|
|
m_settings = settings.Clone() as EncoderSettings;
|
|
m_settings.Validate();
|
|
|
|
//if (Settings.PCM.BitsPerSample != 16)
|
|
// throw new Exception("Bits per sample must be 16.");
|
|
//if (Settings.PCM.ChannelCount != 2)
|
|
// throw new Exception("ChannelCount must be 2.");
|
|
|
|
channels = Settings.PCM.ChannelCount;
|
|
|
|
// flake_validate_params
|
|
|
|
_path = path;
|
|
_IO = IO;
|
|
|
|
samplesBuffer = new int[FlakeConstants.MAX_BLOCKSIZE * (channels == 2 ? 4 : channels)];
|
|
residualBuffer = new int[FlakeConstants.MAX_BLOCKSIZE * (channels == 2 ? 10 : channels + 1)];
|
|
windowBuffer = new float[FlakeConstants.MAX_BLOCKSIZE * 2 * lpc.MAX_LPC_WINDOWS];
|
|
windowScale = new double[lpc.MAX_LPC_WINDOWS];
|
|
windowType = new WindowFunction[lpc.MAX_LPC_WINDOWS];
|
|
windowSections = new LpcWindowSection[12, lpc.MAX_LPC_WINDOWS, lpc.MAX_LPC_SECTIONS];
|
|
|
|
eparams.flake_set_defaults(m_settings);
|
|
|
|
crc8 = new Crc8();
|
|
frame = new FlacFrame(channels * 2);
|
|
}
|
|
|
|
public int TotalSize
|
|
{
|
|
get
|
|
{
|
|
return _totalSize;
|
|
}
|
|
}
|
|
|
|
EncoderSettings m_settings;
|
|
|
|
public AudioEncoderSettings Settings => m_settings;
|
|
|
|
#if INTEROP
|
|
[DllImport("kernel32.dll")]
|
|
static extern bool GetThreadTimes(IntPtr hThread, out long lpCreationTime, out long lpExitTime, out long lpKernelTime, out long lpUserTime);
|
|
[DllImport("kernel32.dll")]
|
|
static extern IntPtr GetCurrentThread();
|
|
#endif
|
|
|
|
void DoClose()
|
|
{
|
|
if (inited)
|
|
{
|
|
while (samplesInBuffer > 0)
|
|
{
|
|
m_blockSize = samplesInBuffer;
|
|
output_frame();
|
|
}
|
|
|
|
if (_IO.CanSeek)
|
|
{
|
|
if (sample_count <= 0 && _position != 0)
|
|
{
|
|
BitWriter bitwriter = new BitWriter(header, 0, 4);
|
|
bitwriter.writebits(32, (int)_position);
|
|
bitwriter.flush();
|
|
_IO.Position = 22;
|
|
_IO.Write(header, 0, 4);
|
|
}
|
|
|
|
if (md5 != null)
|
|
{
|
|
md5.TransformFinalBlock(frame_buffer, 0, 0);
|
|
_IO.Position = 26;
|
|
_IO.Write(md5.Hash, 0, md5.Hash.Length);
|
|
}
|
|
|
|
if (seek_table != null)
|
|
{
|
|
_IO.Position = seek_table_offset;
|
|
int len = write_seekpoints(header, 0, 0);
|
|
_IO.Write(header, 4, len - 4);
|
|
}
|
|
}
|
|
_IO.Close();
|
|
inited = false;
|
|
}
|
|
|
|
#if INTEROP
|
|
long fake, KernelStart, UserStart;
|
|
GetThreadTimes(GetCurrentThread(), out fake, out fake, out KernelStart, out UserStart);
|
|
_userProcessorTime = new TimeSpan(UserStart);
|
|
#endif
|
|
}
|
|
|
|
public void Close()
|
|
{
|
|
DoClose();
|
|
if (sample_count > 0 && _position != sample_count)
|
|
throw new Exception(Properties.Resources.ExceptionSampleCount);
|
|
}
|
|
|
|
public void Delete()
|
|
{
|
|
if (inited)
|
|
{
|
|
_IO.Close();
|
|
inited = false;
|
|
}
|
|
|
|
if (_path != "")
|
|
File.Delete(_path);
|
|
}
|
|
|
|
public long Position
|
|
{
|
|
get
|
|
{
|
|
return _position;
|
|
}
|
|
}
|
|
|
|
public long FinalSampleCount
|
|
{
|
|
set { sample_count = (int)value; }
|
|
}
|
|
|
|
public OrderMethod OrderMethod
|
|
{
|
|
get { return eparams.order_method; }
|
|
set { eparams.order_method = value; }
|
|
}
|
|
|
|
public int DevelopmentMode
|
|
{
|
|
get { return eparams.development_mode; }
|
|
set { eparams.development_mode = value; }
|
|
}
|
|
|
|
public bool DoSeekTable
|
|
{
|
|
get { return eparams.do_seektable; }
|
|
set { eparams.do_seektable = value; }
|
|
}
|
|
|
|
public int VBRMode
|
|
{
|
|
get { return eparams.variable_block_size; }
|
|
set { eparams.variable_block_size = value; }
|
|
}
|
|
|
|
public TimeSpan UserProcessorTime
|
|
{
|
|
get
|
|
{
|
|
#if INTEROP
|
|
return _userProcessorTime;
|
|
#else
|
|
return new TimeSpan(0);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
unsafe int get_wasted_bits(int* signal, int samples)
|
|
{
|
|
int i, shift;
|
|
int x = 0;
|
|
|
|
for (i = 0; i < samples && 0 == (x & 1); i++)
|
|
x |= signal[i];
|
|
|
|
if (x == 0)
|
|
{
|
|
shift = 0;
|
|
}
|
|
else
|
|
{
|
|
for (shift = 0; 0 == (x & 1); shift++)
|
|
x >>= 1;
|
|
}
|
|
|
|
if (shift > 0)
|
|
{
|
|
for (i = 0; i < samples; i++)
|
|
signal[i] >>= shift;
|
|
}
|
|
|
|
return shift;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Copy channel-interleaved input samples into separate subframes
|
|
/// </summary>
|
|
/// <param name="samples"></param>
|
|
/// <param name="pos"></param>
|
|
/// <param name="block"></param>
|
|
unsafe void copy_samples(int[,] samples, int pos, int block)
|
|
{
|
|
fixed (int* fsamples = samplesBuffer, src = &samples[pos, 0])
|
|
{
|
|
if (channels == 2)
|
|
{
|
|
if (m_settings.StereoMethod == StereoMethod.Independent)
|
|
AudioSamples.Deinterlace(fsamples + samplesInBuffer, fsamples + FlakeConstants.MAX_BLOCKSIZE + samplesInBuffer, src, block);
|
|
else
|
|
{
|
|
int* left = fsamples + samplesInBuffer;
|
|
int* right = left + FlakeConstants.MAX_BLOCKSIZE;
|
|
int* leftM = right + FlakeConstants.MAX_BLOCKSIZE;
|
|
int* rightM = leftM + FlakeConstants.MAX_BLOCKSIZE;
|
|
for (int i = 0; i < block; i++)
|
|
{
|
|
int l = src[2 * i];
|
|
int r = src[2 * i + 1];
|
|
left[i] = l;
|
|
right[i] = r;
|
|
leftM[i] = (l + r) >> 1;
|
|
rightM[i] = l - r;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
for (int ch = 0; ch < channels; ch++)
|
|
{
|
|
int* psamples = fsamples + ch * FlakeConstants.MAX_BLOCKSIZE + samplesInBuffer;
|
|
for (int i = 0; i < block; i++)
|
|
psamples[i] = src[i * channels + ch];
|
|
}
|
|
}
|
|
samplesInBuffer += block;
|
|
}
|
|
|
|
//unsafe static void channel_decorrelation(int* leftS, int* rightS, int *leftM, int *rightM, int blocksize)
|
|
//{
|
|
// for (int i = 0; i < blocksize; i++)
|
|
// {
|
|
// leftM[i] = (leftS[i] + rightS[i]) >> 1;
|
|
// rightM[i] = leftS[i] - rightS[i];
|
|
// }
|
|
//}
|
|
|
|
unsafe void encode_residual_verbatim(int* res, int* smp, uint n)
|
|
{
|
|
AudioSamples.MemCpy(res, smp, (int) n);
|
|
}
|
|
|
|
unsafe static ulong encode_residual_fixed_partition(int* res, int* smp, int* end, int order, int* last_errors)
|
|
{
|
|
ulong sum = 0UL;
|
|
switch (order)
|
|
{
|
|
case 0:
|
|
{
|
|
while (smp < end)
|
|
{
|
|
int error = *(res++) = *(smp++);
|
|
sum += (uint)((error << 1) ^ (error >> 31));
|
|
}
|
|
break;
|
|
}
|
|
case 1:
|
|
{
|
|
int last_error_0 = last_errors[0];
|
|
while (smp < end)
|
|
{
|
|
int error, save;
|
|
error = *(smp++); save = error;
|
|
error -= last_error_0; *(res++) = error; last_error_0 = save;
|
|
sum += (uint)((error << 1) ^ (error >> 31));
|
|
}
|
|
last_errors[0] = last_error_0;
|
|
break;
|
|
}
|
|
case 2:
|
|
{
|
|
int last_error_0 = last_errors[0], last_error_1 = last_errors[1];
|
|
while (smp < end)
|
|
{
|
|
int error, save;
|
|
error = *(smp++); save = error;
|
|
error -= last_error_0; last_error_0 = save; save = error;
|
|
error -= last_error_1; *(res++) = error; last_error_1 = save;
|
|
sum += (uint)((error << 1) ^ (error >> 31));
|
|
}
|
|
last_errors[0] = last_error_0; last_errors[1] = last_error_1; ;
|
|
break;
|
|
}
|
|
case 3:
|
|
{
|
|
int last_error_0 = last_errors[0], last_error_1 = last_errors[1], last_error_2 = last_errors[2];
|
|
while (smp < end)
|
|
{
|
|
int error, save;
|
|
error = *(smp++); save = error;
|
|
error -= last_error_0; last_error_0 = save; save = error;
|
|
error -= last_error_1; last_error_1 = save; save = error;
|
|
error -= last_error_2; *(res++) = error; last_error_2 = save;
|
|
sum += (uint)((error << 1) ^ (error >> 31));
|
|
}
|
|
last_errors[0] = last_error_0; last_errors[1] = last_error_1; last_errors[2] = last_error_2;
|
|
break;
|
|
}
|
|
case 4:
|
|
{
|
|
int last_error_0 = last_errors[0], last_error_1 = last_errors[1], last_error_2 = last_errors[2], last_error_3 = last_errors[3];
|
|
while (smp < end)
|
|
{
|
|
int error, save;
|
|
error = *(smp++); save = error;
|
|
error -= last_error_0; last_error_0 = save; save = error;
|
|
error -= last_error_1; last_error_1 = save; save = error;
|
|
error -= last_error_2; last_error_2 = save; save = error;
|
|
error -= last_error_3; *(res++) = error; last_error_3 = save;
|
|
sum += (uint)((error << 1) ^ (error >> 31));
|
|
}
|
|
last_errors[0] = last_error_0; last_errors[1] = last_error_1; last_errors[2] = last_error_2; last_errors[3] = last_error_3;
|
|
break;
|
|
}
|
|
default:
|
|
throw new ArgumentOutOfRangeException();
|
|
}
|
|
return sum;
|
|
}
|
|
|
|
unsafe static void encode_residual_fixed(int* res, int* smp, int n, int order, ulong* sums, int pmax)
|
|
{
|
|
int* last_errors = stackalloc int[4];
|
|
int* end = smp + n;
|
|
int* seg_end = smp + (n >> pmax);
|
|
|
|
if (order > 4)
|
|
throw new ArgumentOutOfRangeException();
|
|
|
|
for (int i = 0; i < order; i++)
|
|
{
|
|
int* next_errors = stackalloc int[4];
|
|
next_errors[0] = *(res++) = *(smp++);
|
|
for (int j = 0; j < i; j++)
|
|
next_errors[j + 1] = next_errors[j] - last_errors[j];
|
|
for (int j = 0; j <= i; j++)
|
|
last_errors[j] = next_errors[j];
|
|
}
|
|
|
|
while (smp < end)
|
|
{
|
|
*(sums++) = encode_residual_fixed_partition(res, smp, seg_end, order, last_errors);
|
|
res += seg_end - smp;
|
|
smp = seg_end;
|
|
seg_end += n >> pmax;
|
|
}
|
|
}
|
|
|
|
#if XXX
|
|
unsafe static int encode_residual_fixed_estimate_best_order(int* res, int* smp, int n, int order)
|
|
{
|
|
int next_error_0, next_error_1, next_error_2, next_error_3, next_error_4;
|
|
int last_error_0, last_error_1, last_error_2, last_error_3;
|
|
int* end = smp + n;
|
|
ulong total_error_0 = 0, total_error_1 = 0, total_error_2 = 0, total_error_3 = 0, total_error_4 = 0;
|
|
|
|
if (order == 0)
|
|
{
|
|
AudioSamples.MemCpy(res, smp, n);
|
|
return 0;
|
|
}
|
|
|
|
next_error_0 = *(res++) = *(smp++);
|
|
last_error_0 = next_error_0;
|
|
|
|
if (order == 1)
|
|
{
|
|
while (smp < end)
|
|
{
|
|
next_error_0 = *(smp++);
|
|
next_error_1 = next_error_0 - last_error_0;
|
|
|
|
last_error_0 = next_error_0;
|
|
|
|
total_error_0 += (ulong)((next_error_0 << 1) ^ (next_error_0 >> 31));
|
|
total_error_1 += (ulong)((next_error_1 << 1) ^ (next_error_1 >> 31));
|
|
|
|
*(res++) = (int)next_error_1;
|
|
}
|
|
|
|
if ((total_error_0 < total_error_1))
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
next_error_0 = *(res++) = *(smp++);
|
|
next_error_1 = next_error_0 - last_error_0;
|
|
last_error_0 = next_error_0;
|
|
last_error_1 = next_error_1;
|
|
|
|
if (order == 2)
|
|
{
|
|
while (smp < end)
|
|
{
|
|
next_error_0 = *(smp++);
|
|
next_error_1 = next_error_0 - last_error_0;
|
|
next_error_2 = next_error_1 - last_error_1;
|
|
|
|
last_error_0 = next_error_0;
|
|
last_error_1 = next_error_1;
|
|
|
|
total_error_0 += (ulong)((next_error_0 << 1) ^ (next_error_0 >> 31));
|
|
total_error_1 += (ulong)((next_error_1 << 1) ^ (next_error_1 >> 31));
|
|
total_error_2 += (ulong)((next_error_2 << 1) ^ (next_error_2 >> 31));
|
|
|
|
*(res++) = (int)next_error_2;
|
|
}
|
|
|
|
if ((total_error_0 < total_error_1) & (total_error_0 < total_error_2))
|
|
return 0;
|
|
else if ((total_error_1 < total_error_2))
|
|
return 1;
|
|
return 2;
|
|
}
|
|
|
|
next_error_0 = *(res++) = *(smp++);
|
|
next_error_1 = next_error_0 - last_error_0;
|
|
next_error_2 = next_error_1 - last_error_1;
|
|
last_error_0 = next_error_0;
|
|
last_error_1 = next_error_1;
|
|
last_error_2 = next_error_2;
|
|
|
|
if (order == 3)
|
|
{
|
|
while (smp < end)
|
|
{
|
|
next_error_0 = *(smp++);
|
|
next_error_1 = next_error_0 - last_error_0;
|
|
next_error_2 = next_error_1 - last_error_1;
|
|
next_error_3 = next_error_2 - last_error_2;
|
|
|
|
last_error_0 = next_error_0;
|
|
last_error_1 = next_error_1;
|
|
last_error_2 = next_error_2;
|
|
|
|
total_error_0 += (ulong)((next_error_0 << 1) ^ (next_error_0 >> 31));
|
|
total_error_1 += (ulong)((next_error_1 << 1) ^ (next_error_1 >> 31));
|
|
total_error_2 += (ulong)((next_error_2 << 1) ^ (next_error_2 >> 31));
|
|
total_error_3 += (ulong)((next_error_3 << 1) ^ (next_error_3 >> 31));
|
|
|
|
*(res++) = (int)next_error_3;
|
|
}
|
|
|
|
if ((total_error_0 < total_error_1) & (total_error_0 < total_error_2) & (total_error_0 < total_error_3))
|
|
return 0;
|
|
else if ((total_error_1 < total_error_2) & (total_error_1 < total_error_3))
|
|
return 1;
|
|
else if ((total_error_2 < total_error_3))
|
|
return 2;
|
|
return 3;
|
|
}
|
|
|
|
next_error_0 = *(res++) = *(smp++);
|
|
next_error_1 = next_error_0 - last_error_0;
|
|
next_error_2 = next_error_1 - last_error_1;
|
|
next_error_3 = next_error_2 - last_error_2;
|
|
last_error_0 = next_error_0;
|
|
last_error_1 = next_error_1;
|
|
last_error_2 = next_error_2;
|
|
last_error_3 = next_error_3;
|
|
|
|
if (order == 4)
|
|
{
|
|
while (smp < end)
|
|
{
|
|
next_error_0 = *(smp++);
|
|
next_error_1 = next_error_0 - last_error_0;
|
|
next_error_2 = next_error_1 - last_error_1;
|
|
next_error_3 = next_error_2 - last_error_2;
|
|
next_error_4 = next_error_3 - last_error_3;
|
|
|
|
last_error_0 = next_error_0;
|
|
last_error_1 = next_error_1;
|
|
last_error_2 = next_error_2;
|
|
last_error_3 = next_error_3;
|
|
|
|
total_error_0 += (ulong)((next_error_0 << 1) ^ (next_error_0 >> 31));
|
|
total_error_1 += (ulong)((next_error_1 << 1) ^ (next_error_1 >> 31));
|
|
total_error_2 += (ulong)((next_error_2 << 1) ^ (next_error_2 >> 31));
|
|
total_error_3 += (ulong)((next_error_3 << 1) ^ (next_error_3 >> 31));
|
|
total_error_4 += (ulong)((next_error_4 << 1) ^ (next_error_4 >> 31));
|
|
|
|
*(res++) = (int)next_error_4;
|
|
}
|
|
|
|
if ((total_error_0 < total_error_1) & (total_error_0 < total_error_2) & (total_error_0 < total_error_3) & (total_error_0 < total_error_4))
|
|
return 0;
|
|
else if ((total_error_1 < total_error_2) & (total_error_1 < total_error_3) & (total_error_1 < total_error_4))
|
|
return 1;
|
|
else if ((total_error_2 < total_error_3) & (total_error_2 < total_error_4))
|
|
return 2;
|
|
else if (total_error_3 < total_error_4)
|
|
return 3;
|
|
return 4;
|
|
}
|
|
|
|
throw new ArgumentOutOfRangeException();
|
|
}
|
|
#endif
|
|
static unsafe uint calc_optimal_rice_params(int porder, int* parm, ulong* sums, uint n, uint pred_order, ref int method)
|
|
{
|
|
uint part = (1U << porder);
|
|
uint cnt = (n >> porder) - pred_order;
|
|
int maxK = method > 0 ? 30 : FlakeConstants.MAX_RICE_PARAM;
|
|
int k = cnt > 0 ? Math.Min(maxK, BitReader.log2i(sums[0] / cnt)) : 0;
|
|
int realMaxK0 = k;
|
|
ulong all_bits = cnt * ((uint)k + 1U) + (sums[0] >> k);
|
|
parm[0] = k;
|
|
cnt = (n >> porder);
|
|
int logcnt = BitReader.log2i(cnt);
|
|
if (cnt == 1 << logcnt)
|
|
{
|
|
for (uint i = 1; i < part; i++)
|
|
{
|
|
ulong s = sums[i];
|
|
ulong u = s >> logcnt;
|
|
k = u >> maxK != 0 ? maxK : BitReader.log2i((uint)u);
|
|
realMaxK0 = Math.Max(realMaxK0, k);
|
|
all_bits += ((uint)k << logcnt) + (s >> k);
|
|
parm[i] = k;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (uint i = 1; i < part; i++)
|
|
{
|
|
ulong s = sums[i];
|
|
ulong u = s / cnt;
|
|
k = u >> maxK != 0 ? maxK : BitReader.log2i((uint)u);
|
|
realMaxK0 = Math.Max(realMaxK0, k);
|
|
all_bits += cnt * (uint)k + (s >> k);
|
|
parm[i] = k;
|
|
}
|
|
}
|
|
all_bits += cnt * (part - 1U);
|
|
method = realMaxK0 > FlakeConstants.MAX_RICE_PARAM ? 1 : 0;
|
|
return (uint)all_bits + ((4U + (uint)method) * part);
|
|
}
|
|
|
|
static unsafe void calc_lower_sums(int pmin, int pmax, ulong* sums)
|
|
{
|
|
for (int i = pmax - 1; i >= pmin; i--)
|
|
{
|
|
for (int j = 0; j < (1 << i); j++)
|
|
{
|
|
sums[i * FlakeConstants.MAX_PARTITIONS + j] =
|
|
sums[(i + 1) * FlakeConstants.MAX_PARTITIONS + 2 * j] +
|
|
sums[(i + 1) * FlakeConstants.MAX_PARTITIONS + 2 * j + 1];
|
|
}
|
|
}
|
|
}
|
|
|
|
static unsafe uint calc_rice_params_sums(RiceContext rc, int pmin, int pmax, ulong* sums, uint n, uint pred_order, int bps)
|
|
{
|
|
int* parm = stackalloc int[(pmax + 1) * FlakeConstants.MAX_PARTITIONS];
|
|
//uint* bits = stackalloc uint[FlakeConstants.MAX_PARTITION_ORDER];
|
|
|
|
//assert(pmin >= 0 && pmin <= FlakeConstants.MAX_PARTITION_ORDER);
|
|
//assert(pmax >= 0 && pmax <= FlakeConstants.MAX_PARTITION_ORDER);
|
|
//assert(pmin <= pmax);
|
|
|
|
// sums for lower levels
|
|
calc_lower_sums(pmin, pmax, sums);
|
|
|
|
uint opt_bits = AudioSamples.UINT32_MAX;
|
|
int opt_porder = pmin;
|
|
int opt_method = 0;
|
|
for (int i = pmin; i <= pmax; i++)
|
|
{
|
|
int method = bps > 16 ? 1 : 0;
|
|
uint bits = calc_optimal_rice_params(i, parm + i * FlakeConstants.MAX_PARTITIONS, sums + i * FlakeConstants.MAX_PARTITIONS, n, pred_order, ref method);
|
|
if (bits <= opt_bits)
|
|
{
|
|
opt_bits = bits;
|
|
opt_porder = i;
|
|
opt_method = method;
|
|
}
|
|
}
|
|
|
|
rc.porder = opt_porder;
|
|
rc.coding_method = opt_method;
|
|
fixed (int* rparms = rc.rparams)
|
|
AudioSamples.MemCpy(rparms, parm + opt_porder * FlakeConstants.MAX_PARTITIONS, (1 << opt_porder));
|
|
|
|
return opt_bits;
|
|
}
|
|
|
|
static int get_max_p_order(int max_porder, int n, int order)
|
|
{
|
|
int porder = Math.Min(max_porder, BitReader.log2i(n ^ (n - 1)));
|
|
if (order > 0)
|
|
porder = Math.Min(porder, BitReader.log2i(n / order));
|
|
return porder;
|
|
}
|
|
|
|
// private static int[,] best_x = new int[14,8193];
|
|
private static int[][] good_x = new int[][] {
|
|
new int[] {}, // 0
|
|
new int[] { // 1
|
|
0x03,0x01,0x00,0x02
|
|
},
|
|
new int[] {// 2
|
|
0x01,0x07,0x06,0x02, 0x03,0x04,0x00,0x05
|
|
},
|
|
new int[] { // 3
|
|
0x0b,0x0f,0x0e,0x0d, 0x03,0x01,0x05,0x02
|
|
},
|
|
new int[] { //4
|
|
0x17,0x09,0x03,0x0a, 0x06,0x1d,0x1f,0x05, 0x1c,0x0d,0x07,0x0c,
|
|
},
|
|
new int[] { // 5
|
|
0x2b,0x3d,0x37,0x07, 0x11,0x15,0x36,0x3f,
|
|
},
|
|
new int[] { // 6
|
|
0x6b,0x15,0x7e,0x31, 0x07,0x1a,0x29,0x26, 0x5d,0x23,0x6f,0x19, 0x56,0x75
|
|
},
|
|
new int[] { // 7
|
|
0xdb,0xef,0xb5,0x47, 0xee,0x63,0x0b,0xfd, 0x31,0xbe,0xed,0x33, 0xff,0xfb,0xd6,0xbb
|
|
},
|
|
new int[] { // 8
|
|
0x1bb,0x1c7,0x069,0x087, 0x1fd,0x16e,0x095,0x1de, 0x066,0x071,0x055,0x09a,
|
|
},
|
|
new int[] { // 9
|
|
0x36b,0x3bd,0x097,0x0c3, 0x0e3,0x0b1,0x107,0x2de, 0x3ef,0x2fb,0x3d5,0x139
|
|
},
|
|
new int[] { // 10
|
|
//0x0e3,0x199,0x383,0x307, 0x1e3,0x01f,0x269,0x0f1, 0x266,0x03f,0x2cd,0x1c3, 0x19a,0x387,0x339,0x259,
|
|
0x6eb,0x187,0x77d,0x271, 0x195,0x259,0x5ae,0x169,
|
|
},
|
|
new int[] { // 11
|
|
0xddb,0xf77,0xb6d,0x587, 0x2c3,0x03b,0xef5,0x1e3, 0xdbe,
|
|
},
|
|
new int[] { // 12
|
|
0x1aeb,0x0587,0x0a71,0x1dbd, 0x0559,0x0aa5,0x0a2e,0x0d43, 0x05aa,0x00f3,0x0696,0x03c6,
|
|
},
|
|
new int[] { // 13
|
|
0x35d7,0x2f6f,0x0aa3,0x1569, 0x150f,0x3d79,0x0dc3,0x309f/*?*/,
|
|
},
|
|
new int[] { // 14
|
|
0x75d7,0x5f7b,0x6a8f,0x29a3,
|
|
},
|
|
new int[] { // 15
|
|
0xddd7,0xaaaf,0x55c3,0xf77b,
|
|
},
|
|
new int[] { // 16
|
|
0x1baeb,0x1efaf,0x1d5bf,0x1cff3,
|
|
},
|
|
new int[] { // 17
|
|
0x36dd7,0x3bb7b,0x3df6f,0x2d547,
|
|
},
|
|
new int[] { // 18
|
|
0x75dd7,0x6f77b,0x7aaaf,0x5ddd3,
|
|
},
|
|
new int[] { // 19
|
|
0xdddd7,0xf777b,0xd5547,0xb6ddb,
|
|
},
|
|
new int[] { // 20
|
|
0x1baeeb,0x1efbaf,0x1aaabf,0x17bbeb,
|
|
},
|
|
new int[] { // 21
|
|
0x376dd7,0x3ddf7b,0x2d550f,0x0aaaa3,
|
|
},
|
|
new int[] { // 22
|
|
0x6eddd7,0x77777b,0x5dcd4f,0x5d76f9,
|
|
},
|
|
new int[] { // 23
|
|
0xdeddd7,0xb5b6eb,0x55552b,0x2aaac3,
|
|
},
|
|
new int[] { // 24
|
|
0x1dddbb7,0x1b76eeb,0x17bbf5f,0x1eeaa9f,
|
|
},
|
|
new int[] { // 25
|
|
},
|
|
new int[] { // 26
|
|
},
|
|
new int[] { // 27
|
|
},
|
|
new int[] { // 28
|
|
},
|
|
new int[] { // 29
|
|
},
|
|
new int[] { // 30
|
|
},
|
|
};
|
|
|
|
unsafe void postprocess_coefs(FlacFrame frame, FlacSubframe sf, int ch)
|
|
{
|
|
if (eparams.development_mode < 0)
|
|
return;
|
|
if (sf.type != SubframeType.LPC || sf.order > 30)
|
|
return;
|
|
int orig_window = sf.window;
|
|
int orig_order = sf.order;
|
|
int orig_shift = sf.shift;
|
|
int orig_cbits = sf.cbits;
|
|
uint orig_size = sf.size;
|
|
var orig_coefs = stackalloc int[orig_order];
|
|
for (int i = 0; i < orig_order; i++) orig_coefs[i] = sf.coefs[i];
|
|
int orig_xx = -1;
|
|
int orig_seq = 0;
|
|
int maxxx = Math.Min(good_x[orig_order].Length, eparams.development_mode);
|
|
var pmax = get_max_p_order(m_settings.MaxPartitionOrder, frame.blocksize, orig_order);
|
|
var pmin = Math.Min(m_settings.MinPartitionOrder, pmax);
|
|
ulong* sums = stackalloc ulong[(pmax + 1) * FlakeConstants.MAX_PARTITIONS];
|
|
|
|
while (true)
|
|
{
|
|
var best_coefs = stackalloc int[orig_order];
|
|
int best_shift = orig_shift;
|
|
int best_cbits = orig_cbits;
|
|
uint best_size = orig_size;
|
|
int best_xx = -1;
|
|
for (int xx = -1; xx < maxxx; xx++)
|
|
{
|
|
int x = xx;
|
|
if (xx < 0)
|
|
{
|
|
if (orig_xx < 0 || maxxx < 1/*3*/)// || (orig_xx >> orig_order) != 0)
|
|
continue;
|
|
x = orig_xx;
|
|
orig_seq++;
|
|
}
|
|
else
|
|
{
|
|
orig_seq = 0;
|
|
if (orig_order < good_x.Length && good_x[orig_order] != null)
|
|
x = good_x[orig_order][xx];
|
|
}
|
|
|
|
frame.current.type = SubframeType.LPC;
|
|
frame.current.order = orig_order;
|
|
frame.current.window = orig_window;
|
|
frame.current.shift = orig_shift;
|
|
frame.current.cbits = orig_cbits;
|
|
|
|
if (((x >> orig_order) & 1) != 0)
|
|
{
|
|
frame.current.shift--;
|
|
frame.current.cbits--;
|
|
if (frame.current.shift < 0 || frame.current.cbits < 2)
|
|
continue;
|
|
}
|
|
|
|
ulong csum = 0;
|
|
int qmax = (1 << (frame.current.cbits - 1)) - 1;
|
|
for (int i = 0; i < frame.current.order; i++)
|
|
{
|
|
int shift = (x >> orig_order) & 1;
|
|
int increment = (x == 1 << orig_order) ? 0 : (((x >> i) & 1) << 1) - 1;
|
|
frame.current.coefs[i] = (orig_coefs[i] + (increment << orig_seq)) >> shift;
|
|
if (frame.current.coefs[i] < -(qmax + 1)) frame.current.coefs[i] = -(qmax + 1);
|
|
if (frame.current.coefs[i] > qmax) frame.current.coefs[i] = qmax;
|
|
csum += (ulong)Math.Abs(frame.current.coefs[i]);
|
|
}
|
|
|
|
fixed (int* coefs = frame.current.coefs)
|
|
{
|
|
if ((csum << frame.subframes[ch].obits) >= 1UL << 32)
|
|
lpc.encode_residual_long(frame.current.residual, frame.subframes[ch].samples, frame.blocksize, frame.current.order, coefs, frame.current.shift, sums + pmax * FlakeConstants.MAX_PARTITIONS, pmax);
|
|
else
|
|
lpc.encode_residual(frame.current.residual, frame.subframes[ch].samples, frame.blocksize, frame.current.order, coefs, frame.current.shift, sums + pmax * FlakeConstants.MAX_PARTITIONS, pmax);
|
|
}
|
|
|
|
var cur_size = calc_rice_params_sums(frame.current.rc, pmin, pmax, sums, (uint)frame.blocksize, (uint)frame.current.order, Settings.PCM.BitsPerSample);
|
|
frame.current.size = (uint)(frame.current.order * frame.subframes[ch].obits + 4 + 5 + frame.current.order * frame.current.cbits + 6 + (int)cur_size);
|
|
|
|
if (frame.current.size < best_size)
|
|
{
|
|
//var dif = best_size - frame.current.size;
|
|
for (int i = 0; i < frame.current.order; i++) best_coefs[i] = frame.current.coefs[i];
|
|
best_shift = frame.current.shift;
|
|
best_cbits = frame.current.cbits;
|
|
best_size = frame.current.size;
|
|
best_xx = x;
|
|
frame.ChooseBestSubframe(ch);
|
|
//if (dif > orig_order * 5)
|
|
// break;
|
|
}
|
|
|
|
if (xx < 0 && best_size < orig_size)
|
|
break;
|
|
}
|
|
|
|
if (best_size < orig_size)
|
|
{
|
|
//if (best_xx >= 0) best_x[order, best_xx]++;
|
|
//if (orig_size != 0x7FFFFFFF)
|
|
// System.Console.Write(string.Format(" {0}[{1:x}]", orig_size - best_size, best_xx));
|
|
for (int i = 0; i < orig_order; i++) orig_coefs[i] = best_coefs[i];
|
|
orig_shift = best_shift;
|
|
orig_cbits = best_cbits;
|
|
orig_size = best_size;
|
|
orig_xx = best_xx;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
//if (orig_size != 0x7FFFFFFF)
|
|
// System.Console.WriteLine();
|
|
|
|
//if (frame_count % 0x400 == 0)
|
|
//{
|
|
// for (int o = 0; o < best_x.GetLength(0); o++)
|
|
// {
|
|
// //for (int x = 0; x <= (1 << o); x++)
|
|
// // if (best_x[o, x] != 0)
|
|
// // System.Console.WriteLine(string.Format("{0:x2}\t{1:x4}\t{2}", o, x, best_x[o, x]));
|
|
// var s = new List<KeyValuePair<int, int>>();
|
|
// for (int x = 0; x < (1 << o); x++)
|
|
// if (best_x[o, x] != 0)
|
|
// s.Add(new KeyValuePair<int, int>(x, best_x[o, x]));
|
|
// s.Sort((x, y) => y.Value.CompareTo(x.Value));
|
|
// foreach (var x in s)
|
|
// System.Console.WriteLine(string.Format("{0:x2}\t{1:x4}\t{2}", o, x.Key, x.Value));
|
|
// int i = 0;
|
|
// foreach (var x in s)
|
|
// {
|
|
// System.Console.Write(string.Format(o <= 8 ? "0x{0:x2}," : "0x{0:x3},", x.Key));
|
|
// if ((++i) % 16 == 0)
|
|
// System.Console.WriteLine();
|
|
// }
|
|
// System.Console.WriteLine();
|
|
// }
|
|
//}
|
|
}
|
|
|
|
public static void SetCoefs(int order, int[] coefs)
|
|
{
|
|
good_x[order] = new int[coefs.Length];
|
|
for (int i = 0; i < coefs.Length; i++)
|
|
good_x[order][i] = coefs[i];
|
|
}
|
|
|
|
unsafe void encode_residual_lpc_sub(FlacFrame frame, float* lpcs, int iWindow, int order, int ch)
|
|
{
|
|
// select LPC precision based on block size
|
|
uint lpc_precision;
|
|
if (frame.blocksize <= 192) lpc_precision = 7U;
|
|
else if (frame.blocksize <= 384) lpc_precision = 8U;
|
|
else if (frame.blocksize <= 576) lpc_precision = 9U;
|
|
else if (frame.blocksize <= 1152) lpc_precision = 10U;
|
|
else if (frame.blocksize <= 2304) lpc_precision = 11U;
|
|
else if (frame.blocksize <= 4608) lpc_precision = 12U;
|
|
else if (frame.blocksize <= 8192) lpc_precision = 13U;
|
|
else if (frame.blocksize <= 16384) lpc_precision = 14U;
|
|
else lpc_precision = 15;
|
|
|
|
for (int i_precision = m_settings.MinPrecisionSearch; i_precision <= m_settings.MaxPrecisionSearch && lpc_precision + i_precision < 16; i_precision++)
|
|
// check if we already calculated with this order, window and precision
|
|
if ((frame.subframes[ch].lpc_ctx[iWindow].done_lpcs[i_precision] & (1U << (order - 1))) == 0)
|
|
{
|
|
frame.subframes[ch].lpc_ctx[iWindow].done_lpcs[i_precision] |= (1U << (order - 1));
|
|
|
|
uint cbits = lpc_precision + (uint)i_precision;
|
|
|
|
frame.current.type = SubframeType.LPC;
|
|
frame.current.order = order;
|
|
frame.current.window = iWindow;
|
|
frame.current.cbits = (int)cbits;
|
|
|
|
int pmax = get_max_p_order(m_settings.MaxPartitionOrder, frame.blocksize, frame.current.order);
|
|
int pmin = Math.Min(m_settings.MinPartitionOrder, pmax);
|
|
ulong* sums = stackalloc ulong[(pmax + 1) * FlakeConstants.MAX_PARTITIONS];
|
|
ulong csum = 0;
|
|
fixed (int* coefs = frame.current.coefs)
|
|
{
|
|
lpc.quantize_lpc_coefs(lpcs + (frame.current.order - 1) * lpc.MAX_LPC_ORDER,
|
|
frame.current.order, cbits, coefs, out frame.current.shift, 15, 0);
|
|
|
|
if (frame.current.shift < 0 || frame.current.shift > 15)
|
|
throw new Exception("negative shift");
|
|
|
|
for (int i = frame.current.order; i > 0; i--)
|
|
csum += (ulong)Math.Abs(coefs[i - 1]);
|
|
|
|
if ((csum << frame.subframes[ch].obits) >= 1UL << 32)
|
|
lpc.encode_residual_long(frame.current.residual, frame.subframes[ch].samples, frame.blocksize, frame.current.order, coefs, frame.current.shift, sums + pmax * FlakeConstants.MAX_PARTITIONS, pmax);
|
|
else
|
|
lpc.encode_residual(frame.current.residual, frame.subframes[ch].samples, frame.blocksize, frame.current.order, coefs, frame.current.shift, sums + pmax * FlakeConstants.MAX_PARTITIONS, pmax);
|
|
|
|
}
|
|
uint best_size = calc_rice_params_sums(frame.current.rc, pmin, pmax, sums, (uint)frame.blocksize, (uint)frame.current.order, Settings.PCM.BitsPerSample);
|
|
frame.current.size = (uint)(frame.current.order * frame.subframes[ch].obits + 4 + 5 + frame.current.order * (int)cbits + 6 + (int)best_size);
|
|
frame.ChooseBestSubframe(ch);
|
|
//if (frame.current.size >= frame.subframes[ch].best.size)
|
|
// postprocess_coefs(frame, frame.current, ch);
|
|
//else
|
|
//{
|
|
// frame.ChooseBestSubframe(ch);
|
|
// postprocess_coefs(frame, frame.subframes[ch].best, ch);
|
|
//}
|
|
}
|
|
}
|
|
|
|
unsafe void encode_residual_fixed_sub(FlacFrame frame, int order, int ch)
|
|
{
|
|
if ((frame.subframes[ch].done_fixed & (1U << order)) != 0)
|
|
return; // already calculated;
|
|
|
|
frame.current.order = order;
|
|
frame.current.type = SubframeType.Fixed;
|
|
|
|
#if XXX
|
|
int best_order = order;
|
|
if (frame.subframes[ch].done_fixed == 0)
|
|
{
|
|
best_order = encode_residual_fixed_estimate_best_order(frame.current.residual, frame.subframes[ch].samples, frame.blocksize, frame.current.order);
|
|
if (best_order != order)
|
|
{
|
|
//frame.subframes[ch].done_fixed |= (1U << order);
|
|
order = best_order;
|
|
frame.current.order = order;
|
|
encode_residual_fixed(frame.current.residual, frame.subframes[ch].samples, frame.blocksize, frame.current.order);
|
|
}
|
|
}
|
|
else
|
|
#endif
|
|
int pmax = get_max_p_order(m_settings.MaxPartitionOrder, frame.blocksize, frame.current.order);
|
|
int pmin = Math.Min(m_settings.MinPartitionOrder, pmax);
|
|
ulong* sums = stackalloc ulong[(pmax + 1) * FlakeConstants.MAX_PARTITIONS];
|
|
encode_residual_fixed(frame.current.residual, frame.subframes[ch].samples, frame.blocksize, frame.current.order, sums + pmax * FlakeConstants.MAX_PARTITIONS, pmax);
|
|
|
|
frame.current.size = (uint)(frame.current.order * frame.subframes[ch].obits) + 6
|
|
+ calc_rice_params_sums(frame.current.rc, pmin, pmax, sums, (uint)frame.blocksize, (uint)frame.current.order, Settings.PCM.BitsPerSample);
|
|
|
|
frame.subframes[ch].done_fixed |= (1U << order);
|
|
|
|
frame.ChooseBestSubframe(ch);
|
|
}
|
|
|
|
unsafe void fixed_compute_best_predictor(int* data, uint data_len, ulong* errors)//, float* residual_bits_per_sample)
|
|
{
|
|
long last_error_0 = data[-1];
|
|
long last_error_1 = data[-1] - data[-2];
|
|
long last_error_2 = last_error_1 - (data[-2] - data[-3]);
|
|
long last_error_3 = last_error_2 - (data[-2] - 2 * data[-3] + data[-4]);
|
|
ulong total_error_0 = 0, total_error_1 = 0, total_error_2 = 0, total_error_3 = 0, total_error_4 = 0;
|
|
|
|
#if VARIANT1
|
|
long error, save;
|
|
int* finish = data + data_len;
|
|
while (data < finish)
|
|
{
|
|
error = *(data++); total_error_0 += (ulong)((error << 1) ^ (error >> 63)); save = error;
|
|
error -= last_error_0; total_error_1 += (ulong)((error << 1) ^ (error >> 63)); last_error_0 = save; save = error;
|
|
error -= last_error_1; total_error_2 += (ulong)((error << 1) ^ (error >> 63)); last_error_1 = save; save = error;
|
|
error -= last_error_2; total_error_3 += (ulong)((error << 1) ^ (error >> 63)); last_error_2 = save; save = error;
|
|
error -= last_error_3; total_error_4 += (ulong)((error << 1) ^ (error >> 63)); last_error_3 = save;
|
|
}
|
|
#else
|
|
int* finish = data + data_len;
|
|
while (data < finish)
|
|
{
|
|
long next_error_0 = *(data++);
|
|
long next_error_1 = next_error_0 - last_error_0;
|
|
long next_error_2 = next_error_1 - last_error_1;
|
|
long next_error_3 = next_error_2 - last_error_2;
|
|
long next_error_4 = next_error_3 - last_error_3;
|
|
|
|
last_error_0 = next_error_0;
|
|
last_error_1 = next_error_1;
|
|
last_error_2 = next_error_2;
|
|
last_error_3 = next_error_3;
|
|
|
|
total_error_0 += (ulong)((last_error_0 << 1) ^ (last_error_0 >> 63));
|
|
total_error_1 += (ulong)((last_error_1 << 1) ^ (last_error_1 >> 63));
|
|
total_error_2 += (ulong)((last_error_2 << 1) ^ (last_error_2 >> 63));
|
|
total_error_3 += (ulong)((last_error_3 << 1) ^ (last_error_3 >> 63));
|
|
total_error_4 += (ulong)((next_error_4 << 1) ^ (next_error_4 >> 63));
|
|
}
|
|
#endif
|
|
|
|
errors[0] = total_error_0;
|
|
errors[1] = total_error_1;
|
|
errors[2] = total_error_2;
|
|
errors[3] = total_error_3;
|
|
errors[4] = total_error_4;
|
|
|
|
//residual_bits_per_sample[0] = (float)((total_error_0 > 0) ? log(M_LN2 * (FLAC__double)total_error_0 / (FLAC__double)data_len) / M_LN2 : 0.0);
|
|
//residual_bits_per_sample[1] = (float)((total_error_1 > 0) ? log(M_LN2 * (FLAC__double)total_error_1 / (FLAC__double)data_len) / M_LN2 : 0.0);
|
|
//residual_bits_per_sample[2] = (float)((total_error_2 > 0) ? log(M_LN2 * (FLAC__double)total_error_2 / (FLAC__double)data_len) / M_LN2 : 0.0);
|
|
//residual_bits_per_sample[3] = (float)((total_error_3 > 0) ? log(M_LN2 * (FLAC__double)total_error_3 / (FLAC__double)data_len) / M_LN2 : 0.0);
|
|
//residual_bits_per_sample[4] = (float)((total_error_4 > 0) ? log(M_LN2 * (FLAC__double)total_error_4 / (FLAC__double)data_len) / M_LN2 : 0.0);
|
|
}
|
|
|
|
unsafe int fixed_compute_best_predictor_order(ulong* error)
|
|
{
|
|
int order;
|
|
if ((error[0] < error[1]) & (error[0] < error[2]) & (error[0] < error[3]) & (error[0] < error[4]))
|
|
order = 0;
|
|
else if ((error[1] < error[2]) & (error[1] < error[3]) & (error[1] < error[4]))
|
|
order = 1;
|
|
else if ((error[2] < error[3]) & (error[2] < error[4]))
|
|
order = 2;
|
|
else if (error[3] < error[4])
|
|
order = 3;
|
|
else
|
|
order = 4;
|
|
return order;
|
|
}
|
|
|
|
unsafe void encode_residual(FlacFrame frame, int ch, PredictionType predict, OrderMethod omethod, int pass, int windows_mask)
|
|
{
|
|
int* smp = frame.subframes[ch].samples;
|
|
int i, n = frame.blocksize;
|
|
// save best.window, because we can overwrite it later with fixed frame
|
|
|
|
// CONSTANT
|
|
for (i = 1; i < n; i++)
|
|
{
|
|
if (smp[i] != smp[0]) break;
|
|
}
|
|
if (i == n)
|
|
{
|
|
frame.subframes[ch].best.type = SubframeType.Constant;
|
|
frame.subframes[ch].best.residual[0] = smp[0];
|
|
frame.subframes[ch].best.size = (uint)frame.subframes[ch].obits;
|
|
return;
|
|
}
|
|
|
|
// VERBATIM
|
|
frame.current.type = SubframeType.Verbatim;
|
|
frame.current.size = (uint)(frame.subframes[ch].obits * frame.blocksize);
|
|
frame.ChooseBestSubframe(ch);
|
|
|
|
if (n < 5 || predict == PredictionType.None)
|
|
return;
|
|
|
|
// LPC
|
|
if (n > m_settings.MaxLPCOrder &&
|
|
(predict == PredictionType.Levinson ||
|
|
predict == PredictionType.Search)
|
|
//predict == PredictionType.Search ||
|
|
//(pass == 2 && frame.subframes[ch].best.type == SubframeType.LPC))
|
|
)
|
|
{
|
|
float* lpcs = stackalloc float[lpc.MAX_LPC_ORDER * lpc.MAX_LPC_ORDER];
|
|
int min_order = m_settings.MinLPCOrder;
|
|
int max_order = m_settings.MaxLPCOrder;
|
|
|
|
for (int iWindow = 0; iWindow < _windowcount; iWindow++)
|
|
{
|
|
if ((windows_mask & (1 << iWindow)) == 0)
|
|
continue;
|
|
|
|
LpcContext lpc_ctx = frame.subframes[ch].lpc_ctx[iWindow];
|
|
fixed (LpcWindowSection* sections = &windowSections[frame.nSeg, iWindow, 0])
|
|
lpc_ctx.GetReflection(
|
|
frame.subframes[ch].sf, max_order, frame.blocksize,
|
|
smp, frame.window_buffer + iWindow * FlakeConstants.MAX_BLOCKSIZE * 2, sections);
|
|
lpc_ctx.ComputeLPC(lpcs);
|
|
|
|
//int frameSize = n;
|
|
//float* F = stackalloc float[frameSize];
|
|
//float* B = stackalloc float[frameSize];
|
|
//float* PE = stackalloc float[max_order + 1];
|
|
//float* arp = stackalloc float[max_order];
|
|
//float* rc = stackalloc float[max_order];
|
|
|
|
//for (int j = 0; j < frameSize; j++)
|
|
// F[j] = B[j] = smp[j];
|
|
|
|
//for (int K = 1; K <= max_order; K++)
|
|
//{
|
|
// // BURG:
|
|
// float denominator = 0.0f;
|
|
// //float denominator = F[K - 1] * F[K - 1] + B[frameSize - K] * B[frameSize - K];
|
|
// for (int j = 0; j < frameSize - K; j++)
|
|
// denominator += F[j + K] * F[j + K] + B[j] * B[j];
|
|
// denominator /= 2;
|
|
|
|
// // Estimate error
|
|
// PE[K - 1] = denominator / (frameSize - K);
|
|
|
|
// float reflectionCoeff = 0.0f;
|
|
// for (int j = 0; j < frameSize - K; j++)
|
|
// reflectionCoeff += F[j + K] * B[j];
|
|
// reflectionCoeff /= denominator;
|
|
// rc[K - 1] = arp[K - 1] = reflectionCoeff;
|
|
|
|
// // Levinson-Durbin
|
|
// for (int j = 0; j < (K - 1) >> 1; j++)
|
|
// {
|
|
// float arptmp = arp[j];
|
|
// arp[j] -= reflectionCoeff * arp[K - 2 - j];
|
|
// arp[K - 2 - j] -= reflectionCoeff * arptmp;
|
|
// }
|
|
// if (((K - 1) & 1) != 0)
|
|
// arp[(K - 1) >> 1] -= reflectionCoeff * arp[(K - 1) >> 1];
|
|
|
|
// for (int j = 0; j < frameSize - K; j++)
|
|
// {
|
|
// float f = F[j + K];
|
|
// float b = B[j];
|
|
// F[j + K] = f - reflectionCoeff * b;
|
|
// B[j] = b - reflectionCoeff * f;
|
|
// }
|
|
|
|
// for (int j = 0; j < K; j++)
|
|
// lpcs[(K - 1) * lpc.MAX_LPC_ORDER + j] = (float)arp[j];
|
|
//}
|
|
|
|
switch (omethod)
|
|
{
|
|
case OrderMethod.Akaike:
|
|
//lpc_ctx.SortOrdersAkaike(frame.blocksize, m_settings.EstimationDepth, max_order, 7.1, 0.0);
|
|
lpc_ctx.SortOrdersAkaike(frame.blocksize, m_settings.EstimationDepth, min_order, max_order, 4.5, 0);
|
|
break;
|
|
default:
|
|
throw new Exception("unknown order method");
|
|
}
|
|
|
|
for (i = 0; i < m_settings.EstimationDepth && i < max_order; i++)
|
|
encode_residual_lpc_sub(frame, lpcs, iWindow, lpc_ctx.best_orders[i], ch);
|
|
}
|
|
|
|
postprocess_coefs(frame, frame.subframes[ch].best, ch);
|
|
}
|
|
|
|
// FIXED
|
|
if (predict == PredictionType.Fixed ||
|
|
(predict == PredictionType.Search && pass != 1) ||
|
|
//predict == PredictionType.Search ||
|
|
//(pass == 2 && frame.subframes[ch].best.type == SubframeType.Fixed) ||
|
|
(n > m_settings.MaxFixedOrder && n <= m_settings.MaxLPCOrder))
|
|
{
|
|
int max_fixed_order = Math.Min(m_settings.MaxFixedOrder, 4);
|
|
int min_fixed_order = Math.Min(m_settings.MinFixedOrder, max_fixed_order);
|
|
|
|
if (min_fixed_order == 0 && max_fixed_order == 4)
|
|
{
|
|
fixed (ulong* fixed_errors = frame.subframes[ch].best_fixed)
|
|
{
|
|
if ((frame.subframes[ch].done_fixed & (1U << 5)) == 0)
|
|
{
|
|
fixed_compute_best_predictor(smp + 4, (uint)n - 4, fixed_errors);
|
|
frame.subframes[ch].done_fixed |= (1U << 5);
|
|
}
|
|
i = fixed_compute_best_predictor_order(fixed_errors);
|
|
encode_residual_fixed_sub(frame, i, ch);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (i = max_fixed_order; i >= min_fixed_order; i--)
|
|
encode_residual_fixed_sub(frame, i, ch);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
unsafe void output_frame_header(FlacFrame frame, BitWriter bitwriter)
|
|
{
|
|
bitwriter.writebits(15, 0x7FFC);
|
|
bitwriter.writebits(1, eparams.variable_block_size > 0 ? 1 : 0);
|
|
bitwriter.writebits(4, frame.bs_code0);
|
|
bitwriter.writebits(4, sr_code0);
|
|
if (frame.ch_mode == ChannelMode.NotStereo)
|
|
bitwriter.writebits(4, ch_code);
|
|
else
|
|
bitwriter.writebits(4, (int) frame.ch_mode);
|
|
bitwriter.writebits(3, bps_code);
|
|
bitwriter.writebits(1, 0);
|
|
bitwriter.write_utf8(frame_count);
|
|
|
|
// custom block size
|
|
if (frame.bs_code1 >= 0)
|
|
{
|
|
if (frame.bs_code1 < 256)
|
|
bitwriter.writebits(8, frame.bs_code1);
|
|
else
|
|
bitwriter.writebits(16, frame.bs_code1);
|
|
}
|
|
|
|
// custom sample rate
|
|
if (sr_code1 > 0)
|
|
{
|
|
if (sr_code1 < 256)
|
|
bitwriter.writebits(8, sr_code1);
|
|
else
|
|
bitwriter.writebits(16, sr_code1);
|
|
}
|
|
|
|
// CRC-8 of frame header
|
|
bitwriter.flush();
|
|
byte crc = crc8.ComputeChecksum(frame_buffer, 0, bitwriter.Length);
|
|
bitwriter.writebits(8, crc);
|
|
}
|
|
|
|
unsafe void output_residual(FlacFrame frame, BitWriter bitwriter, FlacSubframeInfo sub)
|
|
{
|
|
// rice-encoded block
|
|
bitwriter.writebits(2, sub.best.rc.coding_method);
|
|
|
|
// partition order
|
|
int porder = sub.best.rc.porder;
|
|
int psize = frame.blocksize >> porder;
|
|
//assert(porder >= 0);
|
|
bitwriter.writebits(4, porder);
|
|
int res_cnt = psize - sub.best.order;
|
|
|
|
int rice_len = 4 + sub.best.rc.coding_method;
|
|
// residual
|
|
int j = sub.best.order;
|
|
fixed (byte* fixbuf = &frame_buffer[0])
|
|
for (int p = 0; p < (1 << porder); p++)
|
|
{
|
|
int k = sub.best.rc.rparams[p];
|
|
bitwriter.writebits(rice_len, k);
|
|
if (p == 1) res_cnt = psize;
|
|
int cnt = Math.Min(res_cnt, frame.blocksize - j);
|
|
bitwriter.write_rice_block_signed(fixbuf, k, sub.best.residual + j, cnt);
|
|
j += cnt;
|
|
}
|
|
}
|
|
|
|
unsafe void
|
|
output_subframe_constant(FlacFrame frame, BitWriter bitwriter, FlacSubframeInfo sub)
|
|
{
|
|
bitwriter.writebits_signed(sub.obits, sub.best.residual[0]);
|
|
}
|
|
|
|
unsafe void
|
|
output_subframe_verbatim(FlacFrame frame, BitWriter bitwriter, FlacSubframeInfo sub)
|
|
{
|
|
int n = frame.blocksize;
|
|
for (int i = 0; i < n; i++)
|
|
bitwriter.writebits_signed(sub.obits, sub.samples[i]);
|
|
// Don't use residual here, because we don't copy samples to residual for verbatim frames.
|
|
}
|
|
|
|
unsafe void
|
|
output_subframe_fixed(FlacFrame frame, BitWriter bitwriter, FlacSubframeInfo sub)
|
|
{
|
|
// warm-up samples
|
|
for (int i = 0; i < sub.best.order; i++)
|
|
bitwriter.writebits_signed(sub.obits, sub.best.residual[i]);
|
|
|
|
// residual
|
|
output_residual(frame, bitwriter, sub);
|
|
}
|
|
|
|
unsafe void
|
|
output_subframe_lpc(FlacFrame frame, BitWriter bitwriter, FlacSubframeInfo sub)
|
|
{
|
|
// warm-up samples
|
|
for (int i = 0; i < sub.best.order; i++)
|
|
bitwriter.writebits_signed(sub.obits, sub.best.residual[i]);
|
|
|
|
// LPC coefficients
|
|
int cbits = 1;
|
|
for (int i = 0; i < sub.best.order; i++)
|
|
while (cbits < 16 && sub.best.coefs[i] != (sub.best.coefs[i] << (32 - cbits)) >> (32 - cbits))
|
|
cbits++;
|
|
bitwriter.writebits(4, cbits - 1);
|
|
bitwriter.writebits_signed(5, sub.best.shift);
|
|
for (int i = 0; i < sub.best.order; i++)
|
|
bitwriter.writebits_signed(cbits, sub.best.coefs[i]);
|
|
|
|
// residual
|
|
output_residual(frame, bitwriter, sub);
|
|
}
|
|
|
|
unsafe void output_subframes(FlacFrame frame, BitWriter bitwriter)
|
|
{
|
|
for (int ch = 0; ch < channels; ch++)
|
|
{
|
|
FlacSubframeInfo sub = frame.subframes[ch];
|
|
// subframe header
|
|
int type_code = (int) sub.best.type;
|
|
if (sub.best.type == SubframeType.Fixed)
|
|
type_code |= sub.best.order;
|
|
if (sub.best.type == SubframeType.LPC)
|
|
type_code |= sub.best.order - 1;
|
|
bitwriter.writebits(1, 0);
|
|
bitwriter.writebits(6, type_code);
|
|
bitwriter.writebits(1, sub.wbits != 0 ? 1 : 0);
|
|
if (sub.wbits > 0)
|
|
bitwriter.writebits((int)sub.wbits, 1);
|
|
|
|
// subframe
|
|
switch (sub.best.type)
|
|
{
|
|
case SubframeType.Constant:
|
|
output_subframe_constant(frame, bitwriter, sub);
|
|
break;
|
|
case SubframeType.Verbatim:
|
|
output_subframe_verbatim(frame, bitwriter, sub);
|
|
break;
|
|
case SubframeType.Fixed:
|
|
output_subframe_fixed(frame, bitwriter, sub);
|
|
break;
|
|
case SubframeType.LPC:
|
|
output_subframe_lpc(frame, bitwriter, sub);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void output_frame_footer(BitWriter bitwriter)
|
|
{
|
|
bitwriter.flush();
|
|
ushort crc = bitwriter.get_crc16();
|
|
bitwriter.writebits(16, crc);
|
|
bitwriter.flush();
|
|
}
|
|
|
|
unsafe void encode_residual_pass1(FlacFrame frame, int ch, int windows_mask)
|
|
{
|
|
int max_prediction_order = m_settings.MaxLPCOrder;
|
|
//int max_fixed_order = m_settings.MaxFixedOrder;
|
|
//int min_fixed_order = m_settings.MinFixedOrder;
|
|
int lpc_min_precision_search = m_settings.MinPrecisionSearch;
|
|
int lpc_max_precision_search = m_settings.MaxPrecisionSearch;
|
|
int max_partition_order = m_settings.MaxPartitionOrder;
|
|
int estimation_depth = m_settings.EstimationDepth;
|
|
var development_mode = eparams.development_mode;
|
|
//m_settings.MinFixedOrder = 2;
|
|
//m_settings.MaxFixedOrder = 2;
|
|
m_settings.MinPrecisionSearch = m_settings.MaxPrecisionSearch;
|
|
m_settings.MaxLPCOrder = Math.Min(m_settings.MaxLPCOrder, Math.Max(m_settings.MinLPCOrder, 8));
|
|
m_settings.EstimationDepth = 1;
|
|
eparams.development_mode = Math.Min(eparams.development_mode, -1);
|
|
encode_residual(frame, ch, m_settings.PredictionType, OrderMethod.Akaike, 1, windows_mask);
|
|
//m_settings.MinFixedOrder = min_fixed_order;
|
|
//m_settings.MaxFixedOrder = max_fixed_order;
|
|
m_settings.MaxLPCOrder = max_prediction_order;
|
|
m_settings.MinPrecisionSearch = lpc_min_precision_search;
|
|
m_settings.MaxPrecisionSearch = lpc_max_precision_search;
|
|
m_settings.MaxPartitionOrder = max_partition_order;
|
|
m_settings.EstimationDepth = estimation_depth;
|
|
eparams.development_mode = development_mode;
|
|
}
|
|
|
|
unsafe void encode_residual_pass2(FlacFrame frame, int ch)
|
|
{
|
|
encode_residual(frame, ch, m_settings.PredictionType, eparams.order_method, 2, estimate_best_windows(frame, ch));
|
|
}
|
|
|
|
unsafe int estimate_best_windows_akaike(FlacFrame frame, int ch, int count, bool onePerType)
|
|
{
|
|
int* windows_present = stackalloc int[_windowcount];
|
|
for (int i = 0; i < _windowcount; i++)
|
|
windows_present[i] = 0;
|
|
if (onePerType)
|
|
{
|
|
for (int i = 0; i < _windowcount; i++)
|
|
for (int j = 0; j < _windowcount; j++)
|
|
if (windowType[j] == windowType[i])
|
|
windows_present[j]++;
|
|
}
|
|
|
|
float* err = stackalloc float[lpc.MAX_LPC_ORDER];
|
|
for (int i = 0; i < _windowcount; i++)
|
|
{
|
|
LpcContext lpc_ctx = frame.subframes[ch].lpc_ctx[i];
|
|
if (onePerType && windows_present[i] <= count)
|
|
{
|
|
err[i] = 0;
|
|
continue;
|
|
}
|
|
int estimate_order = 4;
|
|
fixed (LpcWindowSection* sections = &windowSections[frame.nSeg, i, 0])
|
|
lpc_ctx.GetReflection(
|
|
frame.subframes[ch].sf, estimate_order, frame.blocksize,
|
|
frame.subframes[ch].samples, frame.window_buffer + i * FlakeConstants.MAX_BLOCKSIZE * 2, sections);
|
|
lpc_ctx.SortOrdersAkaike(frame.blocksize, 1, 1, estimate_order, 4.5, 0.0);
|
|
//err[i] = (float)(lpc_ctx.Akaike(frame.blocksize, lpc_ctx.best_orders[0], 4.5, 0.0));
|
|
//err[i] = (float)((frame.blocksize * lpc_ctx.prediction_error[lpc_ctx.best_orders[0] - 1] / windowScale[i]) + lpc_ctx.best_orders[0] * 4.5);
|
|
//err[i] = (float)((frame.blocksize * lpc_ctx.prediction_error[lpc_ctx.best_orders[0] - 1] / windowScale[i]) + lpc_ctx.best_orders[0] * frame.subframes[ch].obits);
|
|
|
|
// realistic
|
|
//err[i] = (float)(frame.blocksize * Math.Log(lpc_ctx.prediction_error[lpc_ctx.best_orders[0] - 1]) / Math.Log(2) / 2.5
|
|
//- windowScale[i] / 2 + lpc_ctx.best_orders[0] * frame.subframes[ch].obits / 2);
|
|
|
|
//err[i] = (float)(frame.blocksize * Math.Log(lpc_ctx.prediction_error[lpc_ctx.best_orders[0] - 1]) / Math.Log(2) / 2.5
|
|
//- frame.blocksize * Math.Log(lpc_ctx.autocorr_values[0]) / 2.1
|
|
//+ Math.Log(frame.blocksize) * lpc_ctx.best_orders[0] * 4.5 / 2.5 / Math.Log(2));
|
|
|
|
// Akaike
|
|
//err[i] = (float)(frame.blocksize * (Math.Log(lpc_ctx.prediction_error[lpc_ctx.best_orders[0] - 1])) + Math.Log(frame.blocksize) * lpc_ctx.best_orders[0] * 4.5);
|
|
|
|
//err[i] = (float)(lpc_ctx.Akaike(frame.blocksize, lpc_ctx.best_orders[0], 4.5, 0.0) - frame.blocksize * (frame.subframes[ch].obits + Math.Log(windowScale[i] / frame.blocksize) / 2));
|
|
|
|
// tested good
|
|
err[i] = (float)(lpc_ctx.Akaike(frame.blocksize, lpc_ctx.best_orders[0], 4.5, 0.0) - frame.blocksize * Math.Log(lpc_ctx.autocorr_values[0]) / 2);
|
|
}
|
|
int* best_windows = stackalloc int[lpc.MAX_LPC_ORDER];
|
|
for (int i = 0; i < _windowcount; i++)
|
|
best_windows[i] = i;
|
|
for (int i = 0; i < _windowcount; i++)
|
|
{
|
|
for (int j = i + 1; j < _windowcount; j++)
|
|
{
|
|
if (err[best_windows[i]] > err[best_windows[j]])
|
|
{
|
|
int tmp = best_windows[j];
|
|
best_windows[j] = best_windows[i];
|
|
best_windows[i] = tmp;
|
|
}
|
|
}
|
|
}
|
|
int window_mask = 0;
|
|
if (onePerType)
|
|
{
|
|
for (int i = 0; i < _windowcount; i++)
|
|
windows_present[i] = count;
|
|
for (int i = 0; i < _windowcount; i++)
|
|
{
|
|
int w = best_windows[i];
|
|
if (windows_present[w] > 0)
|
|
{
|
|
for (int j = 0; j < _windowcount; j++)
|
|
if (windowType[j] == windowType[w])
|
|
windows_present[j]--;
|
|
window_mask |= 1 << w;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (int i = 0; i < _windowcount && i < count; i++)
|
|
window_mask |= 1 << best_windows[i];
|
|
}
|
|
return window_mask;
|
|
}
|
|
|
|
unsafe int estimate_best_windows(FlacFrame frame, int ch)
|
|
{
|
|
if (_windowcount == 1 || m_settings.PredictionType == PredictionType.Fixed)
|
|
return 1;
|
|
switch (m_settings.WindowMethod)
|
|
{
|
|
case WindowMethod.Estimate:
|
|
return estimate_best_windows_akaike(frame, ch, 1, false);
|
|
case WindowMethod.Estimate2:
|
|
return estimate_best_windows_akaike(frame, ch, 2, false);
|
|
case WindowMethod.Estimate3:
|
|
return estimate_best_windows_akaike(frame, ch, 3, false);
|
|
case WindowMethod.EstimateN:
|
|
return estimate_best_windows_akaike(frame, ch, 1, true);
|
|
case WindowMethod.Evaluate2:
|
|
encode_residual_pass1(frame, ch, estimate_best_windows_akaike(frame, ch, 2, false));
|
|
return frame.subframes[ch].best.type == SubframeType.LPC ? 1 << frame.subframes[ch].best.window : 0;
|
|
case WindowMethod.Evaluate3:
|
|
encode_residual_pass1(frame, ch, estimate_best_windows_akaike(frame, ch, 3, false));
|
|
return frame.subframes[ch].best.type == SubframeType.LPC ? 1 << frame.subframes[ch].best.window : 0;
|
|
case WindowMethod.EvaluateN:
|
|
encode_residual_pass1(frame, ch, estimate_best_windows_akaike(frame, ch, 1, true));
|
|
#if XXX
|
|
if (frame.subframes[ch].best.type == SubframeType.LPC && frame.subframes[ch].best.order <= 4)
|
|
{
|
|
LpcContext lpc_ctx = frame.subframes[ch].lpc_ctx[frame.subframes[ch].best.window];
|
|
double err = lpc_ctx.prediction_error[frame.subframes[ch].best.order - 1] / lpc_ctx.autocorr_values[0];
|
|
double est = frame.blocksize * (frame.subframes[ch].obits * (1 - err));
|
|
double est1 = frame.blocksize * (frame.subframes[ch].obits * (err));
|
|
if (est < 0 || est1 < 0) return -1;
|
|
}
|
|
#endif
|
|
return frame.subframes[ch].best.type == SubframeType.LPC ? 1 << frame.subframes[ch].best.window : 0;
|
|
case WindowMethod.Evaluate2N:
|
|
encode_residual_pass1(frame, ch, estimate_best_windows_akaike(frame, ch, 2, true));
|
|
return frame.subframes[ch].best.type == SubframeType.LPC ? 1 << frame.subframes[ch].best.window : 0;
|
|
case WindowMethod.Evaluate3N:
|
|
encode_residual_pass1(frame, ch, estimate_best_windows_akaike(frame, ch, 3, true));
|
|
return frame.subframes[ch].best.type == SubframeType.LPC ? 1 << frame.subframes[ch].best.window : 0;
|
|
case WindowMethod.Evaluate:
|
|
encode_residual_pass1(frame, ch, -1);
|
|
return frame.subframes[ch].best.type == SubframeType.LPC ? 1 << frame.subframes[ch].best.window : 0;
|
|
case WindowMethod.Search:
|
|
return -1;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
unsafe void estimate_frame(FlacFrame frame, bool do_midside)
|
|
{
|
|
int subframes = do_midside ? channels * 2 : channels;
|
|
|
|
switch (m_settings.StereoMethod)
|
|
{
|
|
case StereoMethod.Estimate:
|
|
for (int ch = 0; ch < subframes; ch++)
|
|
{
|
|
LpcContext lpc_ctx = frame.subframes[ch].lpc_ctx[0];
|
|
int estimate_order = 4;
|
|
int iWindow = 0;
|
|
fixed (LpcWindowSection* sections = &windowSections[frame.nSeg, iWindow, 0])
|
|
lpc_ctx.GetReflection(
|
|
frame.subframes[ch].sf, estimate_order, frame.blocksize,
|
|
frame.subframes[ch].samples, frame.window_buffer + iWindow * FlakeConstants.MAX_BLOCKSIZE * 2, sections);
|
|
lpc_ctx.SortOrdersAkaike(frame.blocksize, 1, 1, estimate_order, 4.5, 0.0);
|
|
frame.subframes[ch].best.size
|
|
= (uint)Math.Max(0, frame.blocksize * (Math.Log(lpc_ctx.prediction_error[lpc_ctx.best_orders[0] - 1])) + Math.Log(frame.blocksize) * lpc_ctx.best_orders[0] * 4.5
|
|
//= (uint)Math.Max(0, lpc_ctx.Akaike(frame.blocksize, lpc_ctx.best_orders[0], 4.5, 0.0)
|
|
//* 2.0 / Math.Log(windowScale[0] / frame.blocksize)
|
|
+ 7.1 * frame.subframes[ch].obits * m_settings.MaxLPCOrder);
|
|
}
|
|
break;
|
|
case StereoMethod.EstimateFixed:
|
|
for (int ch = 0; ch < subframes; ch++)
|
|
{
|
|
fixed (ulong* fixed_errors = frame.subframes[ch].best_fixed)
|
|
{
|
|
if ((frame.subframes[ch].done_fixed & (1U << 5)) == 0)
|
|
{
|
|
fixed_compute_best_predictor(frame.subframes[ch].samples + 4, (uint)frame.blocksize - 4, fixed_errors);
|
|
frame.subframes[ch].done_fixed |= (1U << 5);
|
|
}
|
|
int best_order = fixed_compute_best_predictor_order(fixed_errors);
|
|
//residual_bits_per_sample[0] = (float)((total_error_0 > 0) ? log(M_LN2 * (FLAC__double)total_error_0 / (FLAC__double)data_len) / M_LN2 : 0.0);
|
|
frame.subframes[ch].best.size = (uint)fixed_errors[best_order];
|
|
}
|
|
}
|
|
break;
|
|
case StereoMethod.EstimateX:
|
|
for (int ch = 0; ch < subframes; ch++)
|
|
{
|
|
for (int iWindow = 0; iWindow < _windowcount; iWindow++)
|
|
{
|
|
LpcContext lpc_ctx = frame.subframes[ch].lpc_ctx[iWindow];
|
|
int estimate_order = 4;
|
|
fixed (LpcWindowSection* sections = &windowSections[frame.nSeg, iWindow, 0])
|
|
lpc_ctx.GetReflection(
|
|
frame.subframes[ch].sf, estimate_order, frame.blocksize,
|
|
frame.subframes[ch].samples, frame.window_buffer + iWindow * FlakeConstants.MAX_BLOCKSIZE * 2, sections);
|
|
lpc_ctx.SortOrdersAkaike(frame.blocksize, 1, 1, estimate_order, 4.5, 0.0);
|
|
uint estimate
|
|
= (uint)Math.Max(0, frame.blocksize * (Math.Log(lpc_ctx.prediction_error[lpc_ctx.best_orders[0] - 1])) + Math.Log(frame.blocksize) * lpc_ctx.best_orders[0] * 4.5
|
|
//= (uint)Math.Max(0, lpc_ctx.Akaike(frame.blocksize, lpc_ctx.best_orders[0], 4.5, 0.0)
|
|
//* 2.0 / Math.Log(windowScale[0] / frame.blocksize)
|
|
+ 7.1 * frame.subframes[ch].obits * m_settings.MaxLPCOrder);
|
|
if (iWindow == 0 || frame.subframes[ch].best.size > estimate)
|
|
frame.subframes[ch].best.size = estimate;
|
|
}
|
|
}
|
|
break;
|
|
case StereoMethod.Evaluate:
|
|
for (int ch = 0; ch < subframes; ch++)
|
|
encode_residual_pass1(frame, ch, 1);
|
|
break;
|
|
case StereoMethod.EvaluateX:
|
|
for (int ch = 0; ch < subframes; ch++)
|
|
encode_residual_pass1(frame, ch,
|
|
estimate_best_windows_akaike(frame, ch, 1, false));
|
|
break;
|
|
case StereoMethod.Search:
|
|
for (int ch = 0; ch < subframes; ch++)
|
|
encode_residual_pass2(frame, ch);
|
|
break;
|
|
}
|
|
}
|
|
|
|
unsafe uint measure_frame_size(FlacFrame frame, bool do_midside)
|
|
{
|
|
// crude estimation of header/footer size
|
|
uint total = (uint)(32 + ((BitReader.log2i(frame_count) + 4) / 5) * 8 + (eparams.variable_block_size != 0 ? 16 : 0) + 16);
|
|
|
|
if (do_midside)
|
|
{
|
|
uint bitsBest = AudioSamples.UINT32_MAX;
|
|
ChannelMode modeBest = ChannelMode.LeftRight;
|
|
|
|
if (bitsBest > frame.subframes[2].best.size + frame.subframes[3].best.size)
|
|
{
|
|
bitsBest = frame.subframes[2].best.size + frame.subframes[3].best.size;
|
|
modeBest = ChannelMode.MidSide;
|
|
}
|
|
if (bitsBest > frame.subframes[3].best.size + frame.subframes[1].best.size)
|
|
{
|
|
bitsBest = frame.subframes[3].best.size + frame.subframes[1].best.size;
|
|
modeBest = ChannelMode.RightSide;
|
|
}
|
|
if (bitsBest > frame.subframes[3].best.size + frame.subframes[0].best.size)
|
|
{
|
|
bitsBest = frame.subframes[3].best.size + frame.subframes[0].best.size;
|
|
modeBest = ChannelMode.LeftSide;
|
|
}
|
|
if (bitsBest > frame.subframes[0].best.size + frame.subframes[1].best.size)
|
|
{
|
|
bitsBest = frame.subframes[0].best.size + frame.subframes[1].best.size;
|
|
modeBest = ChannelMode.LeftRight;
|
|
}
|
|
frame.ch_mode = modeBest;
|
|
return total + bitsBest;
|
|
}
|
|
|
|
for (int ch = 0; ch < channels; ch++)
|
|
total += frame.subframes[ch].best.size;
|
|
return total;
|
|
}
|
|
|
|
unsafe void encode_estimated_frame(FlacFrame frame)
|
|
{
|
|
switch (m_settings.StereoMethod)
|
|
{
|
|
case StereoMethod.Estimate:
|
|
case StereoMethod.EstimateX:
|
|
case StereoMethod.EstimateFixed:
|
|
for (int ch = 0; ch < channels; ch++)
|
|
{
|
|
frame.subframes[ch].best.size = AudioSamples.UINT32_MAX;
|
|
encode_residual_pass2(frame, ch);
|
|
}
|
|
break;
|
|
case StereoMethod.Evaluate:
|
|
case StereoMethod.EvaluateX:
|
|
for (int ch = 0; ch < channels; ch++)
|
|
encode_residual_pass2(frame, ch);
|
|
break;
|
|
case StereoMethod.Search:
|
|
break;
|
|
}
|
|
}
|
|
|
|
unsafe delegate void window_function(float* window, int size);
|
|
|
|
unsafe void calculate_window(float* window, window_function func, WindowFunction flag)
|
|
{
|
|
if ((m_settings.WindowFunctions & flag) == 0 || _windowcount == lpc.MAX_LPC_WINDOWS)
|
|
return;
|
|
int sz = _windowsize;
|
|
float* pos1 = window + _windowcount * FlakeConstants.MAX_BLOCKSIZE * 2;
|
|
float* pos = pos1;
|
|
int nSeg = 0;
|
|
do
|
|
{
|
|
windowSections[nSeg, _windowcount, 0].setData(0, sz);
|
|
for (int j = 1; j < lpc.MAX_LPC_SECTIONS; j++)
|
|
windowSections[nSeg, _windowcount, j].setZero(sz, sz);
|
|
|
|
fixed (LpcWindowSection* sections = &windowSections[nSeg, _windowcount, 0])
|
|
func(pos, sz);
|
|
if ((sz & 1) != 0)
|
|
break;
|
|
nSeg++;
|
|
pos += sz;
|
|
sz >>= 1;
|
|
} while (sz >= 32);
|
|
double scale = 0.0;
|
|
for (int i = 0; i < _windowsize; i++)
|
|
scale += pos1[i] * pos1[i];
|
|
windowScale[_windowcount] = scale;
|
|
windowType[_windowcount] = flag;
|
|
_windowcount++;
|
|
}
|
|
|
|
class PunchoutTukeyVariant
|
|
{
|
|
public PunchoutTukeyVariant(
|
|
WindowFunction _type,
|
|
int _parts, double _overlap, double _p)
|
|
{
|
|
parts = _parts;
|
|
type = _type;
|
|
overlap = _overlap;
|
|
p = _p;
|
|
}
|
|
public WindowFunction type;
|
|
public int parts;
|
|
public double overlap;
|
|
public double p;
|
|
};
|
|
|
|
unsafe int encode_frame(out int size)
|
|
{
|
|
fixed (int* s = samplesBuffer, r = residualBuffer)
|
|
fixed (float* window = windowBuffer)
|
|
{
|
|
frame.InitSize(m_blockSize, eparams.variable_block_size != 0);
|
|
|
|
if (frame.blocksize != _windowsize && frame.blocksize > 4 && m_settings.PredictionType != PredictionType.Fixed)
|
|
{
|
|
_windowsize = frame.blocksize;
|
|
_windowcount = 0;
|
|
calculate_window(window, lpc.window_welch, WindowFunction.Welch);
|
|
calculate_window(window, lpc.window_flattop, WindowFunction.Flattop);
|
|
calculate_window(window, lpc.window_hann, WindowFunction.Hann);
|
|
calculate_window(window, lpc.window_bartlett, WindowFunction.Bartlett);
|
|
var tukeys = new PunchoutTukeyVariant[]
|
|
{
|
|
new PunchoutTukeyVariant(WindowFunction.Tukey4, 4, 0, 0.03),
|
|
new PunchoutTukeyVariant(WindowFunction.Tukey4A, 4, 0, 0.03),
|
|
new PunchoutTukeyVariant(WindowFunction.Tukey4B, 4, 0, 0.03),
|
|
new PunchoutTukeyVariant(WindowFunction.Tukey4X, 4, m_settings.TukeyOverlap, m_settings.TukeyP),
|
|
new PunchoutTukeyVariant(WindowFunction.Tukey3, 3, 1.0/3, 0.03),
|
|
new PunchoutTukeyVariant(WindowFunction.Tukey3A, 3, 1.0/3, 0.03),
|
|
new PunchoutTukeyVariant(WindowFunction.Tukey3B, 3, 1.0/3, 0.03),
|
|
new PunchoutTukeyVariant(WindowFunction.Tukey3X, 3, m_settings.TukeyOverlap, m_settings.TukeyP),
|
|
new PunchoutTukeyVariant(WindowFunction.Tukey2, 2, 0.25, 0.03),
|
|
new PunchoutTukeyVariant(WindowFunction.Tukey2A, 2, 0.25, 0.03),
|
|
new PunchoutTukeyVariant(WindowFunction.Tukey2B, 2, 0.25, 0.03),
|
|
new PunchoutTukeyVariant(WindowFunction.Tukey2X, 2, m_settings.TukeyOverlap, m_settings.TukeyP),
|
|
new PunchoutTukeyVariant(WindowFunction.Tukey, 1, 0.0, 0.03),
|
|
new PunchoutTukeyVariant(WindowFunction.Tukey1A, 1, 0.0, 0.03),
|
|
new PunchoutTukeyVariant(WindowFunction.Tukey1B, 1, 0.0, 0.03),
|
|
new PunchoutTukeyVariant(WindowFunction.Tukey1X, 1, m_settings.TukeyOverlap, m_settings.TukeyP),
|
|
};
|
|
|
|
foreach (var tukey in tukeys)
|
|
{
|
|
if (tukey.parts == 0 || (m_settings.WindowFunctions & tukey.type) == 0) continue;
|
|
if (tukey.parts == 1)
|
|
{
|
|
calculate_window(window, (w, wsz) =>
|
|
{
|
|
lpc.window_tukey(w, wsz, tukey.p);
|
|
}, tukey.type);
|
|
continue;
|
|
}
|
|
double overlap = tukey.overlap;
|
|
double overlap_units = overlap / (1.0 - overlap);
|
|
for (int m = 0; m < tukey.parts; m++)
|
|
calculate_window(window, (w, wsz) =>
|
|
{
|
|
lpc.window_punchout_tukey(w, wsz, tukey.p, tukey.p,
|
|
m / (tukey.parts + overlap_units),
|
|
(m + 1 + overlap_units) / (tukey.parts + overlap_units));
|
|
}, tukey.type);
|
|
}
|
|
|
|
if (_windowcount == 0)
|
|
throw new Exception("invalid windowfunction");
|
|
int nSeg = 0;
|
|
int sz = _windowsize;
|
|
float* window_segment = window;
|
|
do
|
|
{
|
|
fixed (LpcWindowSection* sections = &windowSections[nSeg, 0, 0])
|
|
LpcWindowSection.Detect(_windowcount, window_segment, FlakeConstants.MAX_BLOCKSIZE * 2, sz, Settings.PCM.BitsPerSample, sections);
|
|
if ((sz & 1) != 0)
|
|
break;
|
|
window_segment += sz;
|
|
nSeg++;
|
|
sz >>= 1;
|
|
} while (sz >= 32);
|
|
#if NONONO
|
|
using (TextWriter tx = File.CreateText(@"H:\ubuntu\flac\w.txt"))
|
|
{
|
|
#if !NONONO
|
|
int totaltotal = 0;
|
|
for (int i = 0; i < _windowcount; i++)
|
|
{
|
|
int total = 0;
|
|
for (int sec = 0; sec < lpc.MAX_LPC_SECTIONS; sec++)
|
|
if (windowSections[0, i, sec].m_type != LpcWindowSection.SectionType.Zero || windowSections[0, i, sec].m_start != windowSections[0, i, sec].m_end)
|
|
{
|
|
tx.WriteLine("{0}\t{1}\t{2}\t{3}", windowSections[0, i, sec].m_start, windowSections[0, i, sec].m_end, windowSections[0, i, sec].m_type, windowSections[0, i, sec].m_id);
|
|
if (windowSections[0, i, sec].m_type != LpcWindowSection.SectionType.One)
|
|
total += windowSections[0, i, sec].m_end - windowSections[0, i, sec].m_start;
|
|
}
|
|
totaltotal += total;
|
|
tx.WriteLine("{0} total window data", total);
|
|
}
|
|
tx.WriteLine("{0} grand total window data", totaltotal);
|
|
#endif
|
|
for (int x = 0; x < frame.blocksize; x++)
|
|
{
|
|
tx.Write("{0}", x);
|
|
for (int i = 0; i < _windowcount; i++)
|
|
tx.Write("\t{0}", window[i * FlakeConstants.MAX_BLOCKSIZE * 2 + x]);
|
|
tx.WriteLine();
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
if (channels != 2 || frame.blocksize <= 32 || m_settings.StereoMethod == StereoMethod.Independent)
|
|
{
|
|
frame.window_buffer = window;
|
|
frame.nSeg = 0;
|
|
frame.current.residual = r + channels * FlakeConstants.MAX_BLOCKSIZE;
|
|
frame.ch_mode = channels != 2 ? ChannelMode.NotStereo : ChannelMode.LeftRight;
|
|
for (int ch = 0; ch < channels; ch++)
|
|
frame.subframes[ch].Init(s + ch * FlakeConstants.MAX_BLOCKSIZE, r + ch * FlakeConstants.MAX_BLOCKSIZE,
|
|
Settings.PCM.BitsPerSample, get_wasted_bits(s + ch * FlakeConstants.MAX_BLOCKSIZE, frame.blocksize));
|
|
|
|
for (int ch = 0; ch < channels; ch++)
|
|
encode_residual_pass2(frame, ch);
|
|
}
|
|
else
|
|
{
|
|
//channel_decorrelation(s, s + FlakeConstants.MAX_BLOCKSIZE, s + 2 * FlakeConstants.MAX_BLOCKSIZE, s + 3 * FlakeConstants.MAX_BLOCKSIZE, frame.blocksize);
|
|
frame.window_buffer = window;
|
|
frame.nSeg = 0;
|
|
frame.current.residual = r + 4 * FlakeConstants.MAX_BLOCKSIZE;
|
|
for (int ch = 0; ch < 4; ch++)
|
|
frame.subframes[ch].Init(s + ch * FlakeConstants.MAX_BLOCKSIZE, r + ch * FlakeConstants.MAX_BLOCKSIZE,
|
|
Settings.PCM.BitsPerSample + (ch == 3 ? 1 : 0), get_wasted_bits(s + ch * FlakeConstants.MAX_BLOCKSIZE, frame.blocksize));
|
|
|
|
//for (int ch = 0; ch < 4; ch++)
|
|
// for (int iWindow = 0; iWindow < _windowcount; iWindow++)
|
|
// frame.subframes[ch].lpc_ctx[iWindow].GetReflection(32, frame.subframes[ch].samples, frame.blocksize, frame.window_buffer + iWindow * FlakeConstants.MAX_BLOCKSIZE * 2);
|
|
|
|
estimate_frame(frame, true);
|
|
uint fs = measure_frame_size(frame, true);
|
|
|
|
if (0 != eparams.variable_block_size)
|
|
{
|
|
FlacFrame frame2 = new FlacFrame(channels * 2);
|
|
FlacFrame frame3 = new FlacFrame(channels * 2);
|
|
int tumbler = 1;
|
|
while ((frame.blocksize & 1) == 0 && frame.blocksize >= 1024)
|
|
{
|
|
frame2.InitSize(frame.blocksize / 2, true);
|
|
frame2.window_buffer = frame.window_buffer + frame.blocksize;
|
|
frame2.nSeg = frame.nSeg + 1;
|
|
frame2.current.residual = r + tumbler * 5 * FlakeConstants.MAX_BLOCKSIZE;
|
|
for (int ch = 0; ch < 4; ch++)
|
|
frame2.subframes[ch].Init(frame.subframes[ch].samples, frame2.current.residual + (ch + 1) * frame2.blocksize,
|
|
frame.subframes[ch].obits + frame.subframes[ch].wbits, frame.subframes[ch].wbits);
|
|
estimate_frame(frame2, true);
|
|
//measure_frame_size(frame2, true);
|
|
//frame2.ChooseSubframes();
|
|
//encode_estimated_frame(frame2);
|
|
//uint fs2 = measure_frame_size(frame2, false);
|
|
uint fs2 = measure_frame_size(frame2, true);
|
|
uint fs3 = fs2;
|
|
if (eparams.variable_block_size == 2 || eparams.variable_block_size == 4)
|
|
{
|
|
frame3.InitSize(frame2.blocksize, true);
|
|
frame3.window_buffer = frame2.window_buffer;
|
|
frame3.nSeg = frame2.nSeg;
|
|
frame3.current.residual = frame2.current.residual + 5 * frame2.blocksize;
|
|
for (int ch = 0; ch < 4; ch++)
|
|
frame3.subframes[ch].Init(frame2.subframes[ch].samples + frame2.blocksize, frame3.current.residual + (ch + 1) * frame3.blocksize,
|
|
frame.subframes[ch].obits + frame.subframes[ch].wbits, frame.subframes[ch].wbits);
|
|
estimate_frame(frame3, true);
|
|
fs3 = measure_frame_size(frame3, true);
|
|
}
|
|
if (fs2 + fs3 > fs)
|
|
break;
|
|
FlacFrame tmp = frame;
|
|
frame = frame2;
|
|
frame2 = tmp;
|
|
fs = fs2;
|
|
if (eparams.variable_block_size <= 2)
|
|
break;
|
|
tumbler = 1 - tumbler;
|
|
}
|
|
}
|
|
|
|
frame.ChooseSubframes();
|
|
encode_estimated_frame(frame);
|
|
}
|
|
|
|
BitWriter bitwriter = new BitWriter(frame_buffer, 0, max_frame_size);
|
|
|
|
output_frame_header(frame, bitwriter);
|
|
output_subframes(frame, bitwriter);
|
|
output_frame_footer(bitwriter);
|
|
|
|
if (bitwriter.Length >= max_frame_size)
|
|
throw new Exception("buffer overflow");
|
|
|
|
if (frame_buffer != null)
|
|
{
|
|
if (eparams.variable_block_size > 0)
|
|
frame_count += frame.blocksize;
|
|
else
|
|
frame_count++;
|
|
}
|
|
size = frame.blocksize;
|
|
return bitwriter.Length;
|
|
}
|
|
}
|
|
|
|
unsafe int output_frame()
|
|
{
|
|
if (verify != null)
|
|
{
|
|
fixed (int* s = verifyBuffer, r = samplesBuffer)
|
|
for (int ch = 0; ch < channels; ch++)
|
|
AudioSamples.MemCpy(s + ch * FlakeConstants.MAX_BLOCKSIZE, r + ch * FlakeConstants.MAX_BLOCKSIZE, m_blockSize);
|
|
}
|
|
|
|
int fs, bs;
|
|
//if (0 != eparams.variable_block_size && 0 == (m_blockSize & 7) && m_blockSize >= 128)
|
|
// fs = encode_frame_vbs();
|
|
//else
|
|
fs = encode_frame(out bs);
|
|
|
|
if (seek_table != null && _IO.CanSeek)
|
|
{
|
|
for (int sp = 0; sp < seek_table.Length; sp++)
|
|
{
|
|
if (seek_table[sp].framesize != 0)
|
|
continue;
|
|
if (seek_table[sp].number > _position + bs)
|
|
break;
|
|
if (seek_table[sp].number >= _position)
|
|
{
|
|
seek_table[sp].number = _position;
|
|
seek_table[sp].offset = _IO.Position - first_frame_offset;
|
|
seek_table[sp].framesize = bs;
|
|
}
|
|
}
|
|
}
|
|
|
|
_position += bs;
|
|
_IO.Write(frame_buffer, 0, fs);
|
|
_totalSize += fs;
|
|
|
|
if (verify != null)
|
|
try
|
|
{
|
|
int decoded = verify.DecodeFrame(frame_buffer, 0, fs);
|
|
if (decoded != fs || verify.Remaining != bs)
|
|
throw new Exception(Properties.Resources.ExceptionValidationFailed);
|
|
fixed (int* s = verifyBuffer, r = verify.Samples)
|
|
{
|
|
for (int ch = 0; ch < channels; ch++)
|
|
if (AudioSamples.MemCmp(s + ch * FlakeConstants.MAX_BLOCKSIZE, r + ch * FlakeConstants.MAX_BLOCKSIZE, bs))
|
|
throw new Exception(Properties.Resources.ExceptionValidationFailed);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
//if (channels == 2)
|
|
//{
|
|
// var sw = new WAVWriter(string.Format("verify_{0}.wav", this.frame_count), new WAVWriterSettings(this.Settings.PCM));
|
|
// sw.FinalSampleCount = this.frame.blocksize;
|
|
// var ab = new AudioBuffer(this.Settings.PCM, this.frame.blocksize);
|
|
// ab.Prepare(this.frame.blocksize);
|
|
// fixed (int* abs = ab.Samples, s = verifyBuffer)
|
|
// AudioSamples.Interlace(abs, s, s + FlakeConstants.MAX_BLOCKSIZE, this.frame.blocksize);
|
|
// sw.Write(ab);
|
|
// sw.Close();
|
|
//} else
|
|
throw ex;
|
|
}
|
|
|
|
if (bs < m_blockSize)
|
|
{
|
|
for (int ch = 0; ch < (channels == 2 ? 4 : channels); ch++)
|
|
Buffer.BlockCopy(samplesBuffer, (bs + ch * FlakeConstants.MAX_BLOCKSIZE) * sizeof(int), samplesBuffer, ch * FlakeConstants.MAX_BLOCKSIZE * sizeof(int), (m_blockSize - bs) * sizeof(int));
|
|
//fixed (int* s = samplesBuffer)
|
|
// for (int ch = 0; ch < channels; ch++)
|
|
// AudioSamples.MemCpy(s + ch * FlakeConstants.MAX_BLOCKSIZE, s + bs + ch * FlakeConstants.MAX_BLOCKSIZE, m_blockSize - bs);
|
|
}
|
|
|
|
samplesInBuffer -= bs;
|
|
|
|
return bs;
|
|
}
|
|
|
|
public void Write(AudioBuffer buff)
|
|
{
|
|
if (!inited)
|
|
{
|
|
if (_IO == null)
|
|
_IO = new FileStream(_path, FileMode.Create, FileAccess.Write, FileShare.Read, 0x10000);
|
|
inited = true;
|
|
int header_size = flake_encode_init();
|
|
_IO.Write(header, 0, header_size);
|
|
if (_IO.CanSeek)
|
|
first_frame_offset = _IO.Position;
|
|
}
|
|
|
|
buff.Prepare(this);
|
|
|
|
int pos = 0;
|
|
while (pos < buff.Length)
|
|
{
|
|
int block = Math.Min(buff.Length - pos, m_blockSize - samplesInBuffer);
|
|
|
|
copy_samples(buff.Samples, pos, block);
|
|
|
|
pos += block;
|
|
|
|
while (samplesInBuffer >= m_blockSize)
|
|
output_frame();
|
|
}
|
|
|
|
if (md5 != null)
|
|
md5.TransformBlock(buff.Bytes, 0, buff.ByteLength, null, 0);
|
|
}
|
|
|
|
public string Path { get { return _path; } }
|
|
|
|
public static string Vendor
|
|
{
|
|
get
|
|
{
|
|
var version = typeof(AudioEncoder).Assembly.GetName().Version;
|
|
return vendor_string ?? "CUETools " + version.Major + "." + version.Minor + "." + version.Build;
|
|
}
|
|
set
|
|
{
|
|
vendor_string = value;
|
|
}
|
|
}
|
|
|
|
static string vendor_string = null;
|
|
|
|
int select_blocksize(int samplerate, int time_ms)
|
|
{
|
|
int blocksize = FlakeConstants.flac_blocksizes[1];
|
|
int target = (samplerate * time_ms) / 1000;
|
|
if (eparams.variable_block_size > 0)
|
|
{
|
|
blocksize = 1024;
|
|
while (target >= blocksize)
|
|
blocksize <<= 1;
|
|
return blocksize >> 1;
|
|
}
|
|
|
|
for (int i = 8; i < FlakeConstants.flac_blocksizes.Length - 1; i++)
|
|
if (target >= FlakeConstants.flac_blocksizes[i] && FlakeConstants.flac_blocksizes[i] > blocksize)
|
|
{
|
|
blocksize = FlakeConstants.flac_blocksizes[i];
|
|
}
|
|
return blocksize;
|
|
}
|
|
|
|
void write_streaminfo(byte[] header, int pos, int last)
|
|
{
|
|
Array.Clear(header, pos, 38);
|
|
BitWriter bitwriter = new BitWriter(header, pos, 38);
|
|
|
|
// metadata header
|
|
bitwriter.writebits(1, last);
|
|
bitwriter.writebits(7, (int)MetadataType.StreamInfo);
|
|
bitwriter.writebits(24, 34);
|
|
|
|
if (eparams.variable_block_size > 0)
|
|
bitwriter.writebits(16, 0);
|
|
else
|
|
bitwriter.writebits(16, m_blockSize);
|
|
|
|
bitwriter.writebits(16, m_blockSize);
|
|
bitwriter.writebits(24, 0);
|
|
bitwriter.writebits(24, max_frame_size);
|
|
bitwriter.writebits(20, Settings.PCM.SampleRate);
|
|
bitwriter.writebits(3, channels - 1);
|
|
bitwriter.writebits(5, Settings.PCM.BitsPerSample - 1);
|
|
|
|
// total samples
|
|
if (sample_count > 0)
|
|
{
|
|
bitwriter.writebits(4, 0);
|
|
bitwriter.writebits(32, sample_count);
|
|
}
|
|
else
|
|
{
|
|
bitwriter.writebits(4, 0);
|
|
bitwriter.writebits(32, 0);
|
|
}
|
|
bitwriter.flush();
|
|
}
|
|
|
|
/**
|
|
* Write vorbis comment metadata block to byte array.
|
|
* Just writes the vendor string for now.
|
|
*/
|
|
int write_vorbis_comment(byte[] comment, int pos, int len, int last)
|
|
{
|
|
BitWriter bitwriter = new BitWriter(comment, pos, len);
|
|
Encoding enc = new UTF8Encoding();
|
|
byte[] str = enc.GetBytes(Vendor);
|
|
|
|
// metadata header
|
|
bitwriter.writebits(1, last);
|
|
bitwriter.writebits(7, (int)MetadataType.VorbisComment);
|
|
int tagsLen = 0;
|
|
if (m_settings.Tags != null)
|
|
foreach (var t in m_settings.Tags)
|
|
tagsLen += 4 + enc.GetByteCount(t);
|
|
bitwriter.writebits(24, 8 + str.Length + tagsLen);
|
|
for (int i = 0; i < 4; i++)
|
|
bitwriter.writebits(8, (str.Length >> (i * 8)) & 0xff);
|
|
bitwriter.write(str);
|
|
int nTags = m_settings.Tags != null ? m_settings.Tags.Length : 0;
|
|
for (int i = 0; i < 4; i++)
|
|
bitwriter.writebits(8, (nTags >> (i * 8)) & 0xff);
|
|
if (m_settings.Tags != null)
|
|
foreach (var tag in m_settings.Tags)
|
|
{
|
|
str = enc.GetBytes(tag);
|
|
for (int i = 0; i < 4; i++)
|
|
bitwriter.writebits(8, (str.Length >> (i * 8)) & 0xff);
|
|
bitwriter.write(str);
|
|
}
|
|
bitwriter.flush();
|
|
return bitwriter.Length;
|
|
}
|
|
|
|
int write_seekpoints(byte[] header, int pos, int last)
|
|
{
|
|
seek_table_offset = pos + 4;
|
|
|
|
BitWriter bitwriter = new BitWriter(header, pos, 4 + 18 * seek_table.Length);
|
|
|
|
// metadata header
|
|
bitwriter.writebits(1, last);
|
|
bitwriter.writebits(7, (int)MetadataType.Seektable);
|
|
bitwriter.writebits(24, 18 * seek_table.Length);
|
|
for (int i = 0; i < seek_table.Length; i++)
|
|
{
|
|
bitwriter.writebits(FlakeConstants.FLAC__STREAM_METADATA_SEEKPOINT_SAMPLE_NUMBER_LEN, (ulong)seek_table[i].number);
|
|
bitwriter.writebits(FlakeConstants.FLAC__STREAM_METADATA_SEEKPOINT_STREAM_OFFSET_LEN, (ulong)seek_table[i].offset);
|
|
bitwriter.writebits(FlakeConstants.FLAC__STREAM_METADATA_SEEKPOINT_FRAME_SAMPLES_LEN, seek_table[i].framesize);
|
|
}
|
|
bitwriter.flush();
|
|
return 4 + 18 * seek_table.Length;
|
|
}
|
|
|
|
/**
|
|
* Write padding metadata block to byte array.
|
|
*/
|
|
int
|
|
write_padding(byte[] padding, int pos, int last, int padlen)
|
|
{
|
|
BitWriter bitwriter = new BitWriter(padding, pos, 4);
|
|
|
|
// metadata header
|
|
bitwriter.writebits(1, last);
|
|
bitwriter.writebits(7, (int)MetadataType.Padding);
|
|
bitwriter.writebits(24, padlen);
|
|
|
|
bitwriter.flush();
|
|
return padlen + 4;
|
|
}
|
|
|
|
int write_headers()
|
|
{
|
|
int header_size = 0;
|
|
int last = 0;
|
|
|
|
// stream marker
|
|
header[0] = 0x66;
|
|
header[1] = 0x4C;
|
|
header[2] = 0x61;
|
|
header[3] = 0x43;
|
|
header_size += 4;
|
|
|
|
// streaminfo
|
|
write_streaminfo(header, header_size, last);
|
|
header_size += 38;
|
|
|
|
// seek table
|
|
if (_IO.CanSeek && seek_table != null)
|
|
header_size += write_seekpoints(header, header_size, last);
|
|
|
|
// vorbis comments
|
|
if (m_settings.Padding == 0) last = 1;
|
|
header_size += write_vorbis_comment(header, header_size, header.Length - header_size, last);
|
|
|
|
// padding
|
|
if (m_settings.Padding > 0)
|
|
{
|
|
last = 1;
|
|
header_size += write_padding(header, header_size, last, m_settings.Padding);
|
|
}
|
|
|
|
return header_size;
|
|
}
|
|
|
|
int flake_encode_init()
|
|
{
|
|
int i, header_len;
|
|
|
|
//if(flake_validate_params(s) < 0)
|
|
|
|
ch_code = channels - 1;
|
|
|
|
// find samplerate in table
|
|
for (i = 1; i < 12; i++)
|
|
{
|
|
if (Settings.PCM.SampleRate == FlakeConstants.flac_samplerates[i])
|
|
{
|
|
sr_code0 = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// if not in table, samplerate is non-standard
|
|
if (i == 12)
|
|
throw new Exception("non-standard samplerate");
|
|
|
|
for (i = 1; i < 8; i++)
|
|
{
|
|
if (Settings.PCM.BitsPerSample == FlakeConstants.flac_bitdepths[i])
|
|
{
|
|
bps_code = i;
|
|
break;
|
|
}
|
|
}
|
|
if (i == 8)
|
|
throw new Exception("non-standard bps");
|
|
|
|
m_blockSize = m_settings.BlockSize != 0 ? m_settings.BlockSize :
|
|
select_blocksize(Settings.PCM.SampleRate, eparams.block_time_ms);
|
|
|
|
// set maximum encoded frame size (if larger, re-encodes in verbatim mode)
|
|
if (channels == 2)
|
|
max_frame_size = 16 + ((m_blockSize * (Settings.PCM.BitsPerSample + Settings.PCM.BitsPerSample + 1) + 7) >> 3);
|
|
else
|
|
max_frame_size = 16 + ((m_blockSize * channels * Settings.PCM.BitsPerSample + 7) >> 3);
|
|
|
|
if (_IO.CanSeek && eparams.do_seektable && sample_count > 0)
|
|
{
|
|
int seek_points_distance = Settings.PCM.SampleRate * 10;
|
|
int num_seek_points = 1 + sample_count / seek_points_distance; // 1 seek point per 10 seconds
|
|
if (sample_count % seek_points_distance == 0)
|
|
num_seek_points--;
|
|
seek_table = new SeekPoint[num_seek_points];
|
|
for (int sp = 0; sp < num_seek_points; sp++)
|
|
{
|
|
seek_table[sp].framesize = 0;
|
|
seek_table[sp].offset = 0;
|
|
seek_table[sp].number = sp * seek_points_distance;
|
|
}
|
|
}
|
|
|
|
// output header bytes
|
|
int tagsLen = 0;
|
|
Encoding enc = new UTF8Encoding();
|
|
if (m_settings.Tags != null)
|
|
foreach (var t in m_settings.Tags)
|
|
tagsLen += 4 + enc.GetByteCount(t);
|
|
header = new byte[m_settings.Padding + 1024 + (seek_table == null ? 0 : seek_table.Length * 18) + tagsLen];
|
|
header_len = write_headers();
|
|
|
|
// initialize CRC & MD5
|
|
if (_IO.CanSeek && m_settings.DoMD5)
|
|
md5 = new MD5CryptoServiceProvider();
|
|
|
|
if (m_settings.DoVerify)
|
|
{
|
|
verify = new AudioDecoder(Settings.PCM);
|
|
verifyBuffer = new int[FlakeConstants.MAX_BLOCKSIZE * channels];
|
|
}
|
|
|
|
frame_buffer = new byte[max_frame_size];
|
|
|
|
return header_len;
|
|
}
|
|
}
|
|
|
|
struct FlakeEncodeParams
|
|
{
|
|
// prediction order selection method
|
|
// set by user prior to calling flake_encode_init
|
|
// if set to less than 0, it is chosen based on compression.
|
|
// valid values are 0 to 5
|
|
// 0 = use maximum order only
|
|
// 1 = use estimation
|
|
// 2 = 2-level
|
|
// 3 = 4-level
|
|
// 4 = 8-level
|
|
// 5 = full search
|
|
// 6 = log search
|
|
public OrderMethod order_method;
|
|
|
|
// block time in milliseconds
|
|
// set by the user prior to calling flake_encode_init
|
|
// used to calculate block_size based on sample rate
|
|
// can also be changed by user before encoding a frame
|
|
public int block_time_ms;
|
|
|
|
// whether to use variable block sizes
|
|
// set by user prior to calling flake_encode_init
|
|
// 0 = fixed block size
|
|
// 1 = variable block size
|
|
public int variable_block_size;
|
|
|
|
public bool do_seektable;
|
|
|
|
public int development_mode;
|
|
|
|
public int flake_set_defaults(EncoderSettings settings)
|
|
{
|
|
order_method = OrderMethod.Akaike;
|
|
block_time_ms = 105;
|
|
variable_block_size = 0;
|
|
do_seektable = true;
|
|
development_mode = -1;
|
|
|
|
if (settings.EncoderModeIndex == 11)
|
|
variable_block_size = 4;
|
|
|
|
return 0;
|
|
}
|
|
}
|
|
}
|