using System; using System.Text; using System.IO; using System.Collections.Generic; using System.Collections.Specialized; using System.Security.Cryptography; using System.Runtime.InteropServices; using CUETools.Codecs; namespace CUETools.Codecs.FLAKE { public class FlakeWriter : 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 sample_rate, sr_code0, sr_code1; // sample size in bits // set by user prior to calling flake_encode_init // only 16-bit is currently supported uint bits_per_sample; int bps_code; // total stream samples // set by user prior to calling flake_encode_init // if 0, stream length is unknown int sample_count; 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; uint lpc_precision; int frame_count = 0; TimeSpan _userProcessorTime; // header bytes // allocated by flake_encode_init and freed by flake_encode_close byte[] header; int[] samplesBuffer; int[] verifyBuffer; int[] residualBuffer; double[] windowBuffer; int samplesInBuffer = 0; int _compressionLevel = 5; int _blocksize = 0; int _totalSize = 0; int _windowsize = 0, _windowcount = 0; Crc8 crc8; Crc16 crc16; MD5 md5; FlakeReader verify; bool inited = false; public FlakeWriter(string path, int bitsPerSample, int channelCount, int sampleRate, Stream IO) { if (bitsPerSample != 16) throw new Exception("Bits per sample must be 16."); if (channelCount != 2) throw new Exception("ChannelCount must be 2."); channels = channelCount; sample_rate = sampleRate; bits_per_sample = (uint) bitsPerSample; // flake_validate_params _path = path; _IO = IO; //verify = new FlakeReader(channels, bits_per_sample); samplesBuffer = new int[Flake.MAX_BLOCKSIZE * (channels == 2 ? 4 : channels)]; residualBuffer = new int[Flake.MAX_BLOCKSIZE * (channels == 2 ? 4 : channels)]; windowBuffer = new double[Flake.MAX_BLOCKSIZE * lpc.MAX_LPC_WINDOWS]; if (verify != null) verifyBuffer = new int[Flake.MAX_BLOCKSIZE * channels]; eparams.flake_set_defaults(_compressionLevel); eparams.padding_size = 8192; crc8 = new Crc8(); crc16 = new Crc16(); md5 = new MD5CryptoServiceProvider(); } public int TotalSize { get { return _totalSize; } } public int PaddingLength { get { return eparams.padding_size; } set { eparams.padding_size = value; } } public int CompressionLevel { get { return _compressionLevel; } set { _compressionLevel = value; eparams.flake_set_defaults(_compressionLevel); } } [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(); void DoClose() { if (samplesInBuffer > 0) { eparams.block_size = samplesInBuffer; output_frame(); } long fake, KernelStart, UserStart; GetThreadTimes(GetCurrentThread(), out fake, out fake, out KernelStart, out UserStart); _userProcessorTime = new TimeSpan(UserStart); md5.TransformFinalBlock(frame_buffer, 0, 0); if (_IO.CanSeek) { _IO.Position = 26; _IO.Write(md5.Hash, 0, md5.Hash.Length); } _IO.Close(); } public void Close() { DoClose(); if (sample_count != 0 && _position != sample_count) throw new Exception("Samples written differs from the expected sample count."); } public void Delete() { DoClose(); File.Delete(_path); } public long Position { get { return _position; } } public long FinalSampleCount { set { sample_count = (int)value; } } public long BlockSize { set { _blocksize = (int)value; } } public OrderMethod OrderMethod { get { return eparams.order_method; } set { eparams.order_method = value; } } public PredictionType PredictionType { get { return eparams.prediction_type; } set { eparams.prediction_type = value; } } public StereoMethod StereoMethod { get { return eparams.stereo_method; } set { eparams.stereo_method = value; } } public WindowFunction WindowFunction { get { return eparams.window_function; } set { eparams.window_function = value; } } public int MinPredictionOrder { get { return eparams.prediction_type == PredictionType.Fixed ? eparams.min_fixed_order : eparams.min_prediction_order; } set { if (eparams.prediction_type == PredictionType.Fixed) { if (value < 0 || value > eparams.max_fixed_order) throw new Exception("invalid order"); eparams.min_fixed_order = value; return; } if (value < 0 || value > eparams.max_prediction_order) throw new Exception("invalid order"); eparams.min_prediction_order = value; } } public int MaxPredictionOrder { get { return eparams.prediction_type == PredictionType.Fixed ? eparams.max_fixed_order : eparams.max_prediction_order; } set { if (eparams.prediction_type == PredictionType.Fixed) { if (value > 4 || value < eparams.min_fixed_order) throw new Exception("invalid order"); eparams.max_fixed_order = value; return; } if (value > 32 || value < eparams.min_prediction_order) throw new Exception("invalid order"); eparams.max_prediction_order = value; } } public int MaxPartitionOrder { get { return eparams.max_partition_order; } set { if (value > 8 || value < eparams.min_partition_order) throw new Exception("invalid order"); eparams.max_partition_order = value; } } public TimeSpan UserProcessorTime { get { return _userProcessorTime; } } public int BitsPerSample { get { return 16; } } int encode_frame_vbs() { throw new Exception("vbs not supported"); } 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; } unsafe void init_frame(FlacFrame * frame) { //if (channels == 2) //max_frame_size = int i = 15; if (eparams.variable_block_size == 0) { for (i = 0; i < 15; i++) { if (eparams.block_size == Flake.flac_blocksizes[i]) { frame->blocksize = Flake.flac_blocksizes[i]; frame->bs_code0 = i; frame->bs_code1 = -1; break; } } } if (i == 15) { frame->blocksize = eparams.block_size; if (frame->blocksize <= 256) { frame->bs_code0 = 6; frame->bs_code1 = frame->blocksize - 1; } else { frame->bs_code0 = 7; frame->bs_code1 = frame->blocksize - 1; } } } /** * Copy channel-interleaved input samples into separate subframes */ unsafe void copy_samples(int[,] samples, int pos, int block) { fixed (int* fsamples = samplesBuffer) for (int ch = 0; ch < channels; ch++) { int* psamples = fsamples + ch * Flake.MAX_BLOCKSIZE + samplesInBuffer; for (int i = 0; i < block; i++) psamples[i] = samples[pos + i, ch]; } samplesInBuffer += block; } static uint rice_encode_count(uint sum, uint n, uint k) { return n*(k+1) + ((sum-(n>>1))>>(int)k); } //static unsafe uint find_optimal_rice_param(uint sum, uint n) //{ // uint* nbits = stackalloc uint[Flake.MAX_RICE_PARAM + 1]; // int k_opt = 0; // nbits[0] = UINT32_MAX; // for (int k = 0; k <= Flake.MAX_RICE_PARAM; k++) // { // nbits[k] = rice_encode_count(sum, n, (uint)k); // if (nbits[k] < nbits[k_opt]) // k_opt = k; // } // return (uint)k_opt; //} static unsafe int find_optimal_rice_param(uint sum, uint n, out uint nbits_best) { int k_opt = 0; uint a = n; uint b = sum - (n >> 1); uint nbits = a + b; for (int k = 1; k <= Flake.MAX_RICE_PARAM; k++) { a += n; b >>= 1; uint nbits_k = a + b; if (nbits_k < nbits) { k_opt = k; nbits = nbits_k; } } nbits_best = nbits; return k_opt; } unsafe uint calc_decorr_score(FlacFrame* frame, int ch, FlacSubframe* sub) { int* s = sub->samples; int n = frame->blocksize; ulong sum = 0; for (int i = 2; i < n; i++) sum += (ulong)Math.Abs(s[i] - 2 * s[i - 1] + s[i - 2]); uint nbits; find_optimal_rice_param((uint)(2 * sum), (uint)n, out nbits); return nbits; } unsafe void initialize_subframe(FlacFrame* frame, int ch, int *s, int * r, uint bps) { int w = get_wasted_bits(s, frame->blocksize); if (w > bps) throw new Exception("internal error"); frame->subframes[ch].samples = s; frame->subframes[ch].residual = r; frame->subframes[ch].obits = bps - (uint)w; frame->subframes[ch].wbits = (uint)w; frame->subframes[ch].type = SubframeType.Verbatim; frame->subframes[ch].size = UINT32_MAX; for (int iWindow = 0; iWindow < lpc.MAX_LPC_WINDOWS; iWindow++) frame->subframes[ch].done_lpcs[iWindow] = 0; frame->subframes[ch].done_fixed = 0; } 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) { Flake.memcpy(res, smp, (int) n); } unsafe void encode_residual_fixed(int* res, int* smp, int n, int order) { int i; int s0, s1, s2; switch (order) { case 0: Flake.memcpy(res, smp, n); return; case 1: *(res++) = s1 = *(smp++); for (i = n - 1; i > 0; i--) { s0 = *(smp++); *(res++) = s0 - s1; s1 = s0; } return; case 2: *(res++) = s2 = *(smp++); *(res++) = s1 = *(smp++); for (i = n - 2; i > 0; i--) { s0 = *(smp++); *(res++) = s0 - 2 * s1 + s2; s2 = s1; s1 = s0; } return; case 3: res[0] = smp[0]; res[1] = smp[1]; res[2] = smp[2]; for (i = 3; i < n; i++) { res[i] = smp[i] - 3 * smp[i - 1] + 3 * smp[i - 2] - smp[i - 3]; } return; case 4: res[0] = smp[0]; res[1] = smp[1]; res[2] = smp[2]; res[3] = smp[3]; for (i = 4; i < n; i++) { res[i] = smp[i] - 4 * smp[i - 1] + 6 * smp[i - 2] - 4 * smp[i - 3] + smp[i - 4]; } return; default: return; } } public const uint UINT32_MAX = 0xffffffff; static unsafe uint calc_optimal_rice_params(RiceContext* rc, int porder, uint* sums, uint n, uint pred_order) { uint part = (1U << porder); uint all_bits = 0; rc->rparams[0] = find_optimal_rice_param(sums[0], (n >> porder) - pred_order, out all_bits); uint cnt = (n >> porder); for (uint i = 1; i < part; i++) { uint nbits; rc->rparams[i] = find_optimal_rice_param(sums[i], cnt, out nbits); all_bits += nbits; } all_bits += (4 * part); rc->porder = porder; return all_bits; } static unsafe void calc_sums(int pmin, int pmax, uint* data, uint n, uint pred_order, uint* sums) { uint* res = &data[pred_order]; // sums for highest level int parts = (1 << pmax); uint cnt = (n >> pmax) - pred_order; for (int i = 0; i < parts; i++) { if (i == 1) cnt = (n >> pmax); if (i > 0) res = &data[i * cnt]; sums[pmax * Flake.MAX_PARTITIONS + i] = 0; for (int j = 0; j < cnt; j++) { sums[pmax * Flake.MAX_PARTITIONS + i] += res[j]; } } // sums for lower levels for (int i = pmax - 1; i >= pmin; i--) { parts = (1 << i); for (int j = 0; j < parts; j++) { sums[i * Flake.MAX_PARTITIONS + j] = sums[(i + 1) * Flake.MAX_PARTITIONS + 2 * j] + sums[(i + 1) * Flake.MAX_PARTITIONS + 2 * j + 1]; } } } static unsafe uint calc_rice_params(RiceContext* rc, int pmin, int pmax, int* data, uint n, uint pred_order) { RiceContext tmp_rc; uint* udata = stackalloc uint[(int)n]; uint* sums = stackalloc uint[(pmax + 1) * Flake.MAX_PARTITIONS]; //uint* bits = stackalloc uint[Flake.MAX_PARTITION_ORDER]; //assert(pmin >= 0 && pmin <= Flake.MAX_PARTITION_ORDER); //assert(pmax >= 0 && pmax <= Flake.MAX_PARTITION_ORDER); //assert(pmin <= pmax); for (uint i = 0; i < n; i++) udata[i] = (uint) ((2 * data[i]) ^ (data[i] >> 31)); calc_sums(pmin, pmax, udata, n, pred_order, sums); int opt_porder = pmin; uint opt_bits = UINT32_MAX; for (int i = pmin; i <= pmax; i++) { uint bits = calc_optimal_rice_params(&tmp_rc, i, sums + i * Flake.MAX_PARTITIONS, n, pred_order); if (bits <= opt_bits) { opt_porder = i; opt_bits = bits; *rc = tmp_rc; } } return opt_bits; } static int get_max_p_order(int max_porder, int n, int order) { int porder = Math.Min(max_porder, Flake.log2i(n ^ (n - 1))); if (order > 0) porder = Math.Min(porder, Flake.log2i(n / order)); return porder; } static unsafe uint calc_rice_params_fixed(RiceContext* rc, int pmin, int pmax, int* data, int n, int pred_order, uint bps) { pmin = get_max_p_order(pmin, n, pred_order); pmax = get_max_p_order(pmax, n, pred_order); uint bits = (uint)pred_order * bps + 6; bits += calc_rice_params(rc, pmin, pmax, data, (uint)n, (uint)pred_order); return bits; } static unsafe uint calc_rice_params_lpc(RiceContext* rc, int pmin, int pmax, int* data, int n, int pred_order, uint bps, uint precision) { pmin = get_max_p_order(pmin, n, pred_order); pmax = get_max_p_order(pmax, n, pred_order); uint bits = (uint)pred_order * bps + 4 + 5 + (uint)pred_order * precision + 6; bits += calc_rice_params(rc, pmin, pmax, data, (uint)n, (uint)pred_order); return bits; } unsafe void choose_best_subframe(FlacFrame* frame, int ch) { if (frame->current.size < frame->subframes[ch].size) { FlacSubframe tmp = frame->subframes[ch]; frame->subframes[ch] = frame->current; for (int iWindow = 0; iWindow < lpc.MAX_LPC_WINDOWS; iWindow++) frame->subframes[ch].done_lpcs[iWindow] = tmp.done_lpcs[iWindow]; frame->subframes[ch].done_fixed = tmp.done_fixed; frame->current = tmp; } } unsafe void encode_residual_lpc_sub(FlacFrame* frame, double* lpcs, int iWindow, int order, int ch) { if ((frame->subframes[ch].done_lpcs[iWindow] & (1U << (order - 1))) != 0) return; // already calculated; frame->subframes[ch].done_lpcs[iWindow] |= (1U << (order - 1)); frame->current.type = SubframeType.LPC; frame->current.order = order; frame->current.window = iWindow; lpc.quantize_lpc_coefs(lpcs + (frame->current.order - 1) * lpc.MAX_LPC_ORDER, frame->current.order, lpc_precision, frame->current.coefs, out frame->current.shift); lpc.encode_residual(frame->current.residual, frame->current.samples, frame->blocksize, frame->current.order, frame->current.coefs, frame->current.shift); frame->current.size = calc_rice_params_lpc(&frame->current.rc, eparams.min_partition_order, eparams.max_partition_order, frame->current.residual, frame->blocksize, frame->current.order, frame->current.obits, lpc_precision); choose_best_subframe(frame, 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; encode_residual_fixed(frame->current.residual, frame->current.samples, frame->blocksize, frame->current.order); frame->current.size = calc_rice_params_fixed(&frame->current.rc, eparams.min_partition_order, eparams.max_partition_order, frame->current.residual, frame->blocksize, frame->current.order, frame->current.obits); frame->subframes[ch].done_fixed |= (1U << order); choose_best_subframe(frame, ch); } unsafe void encode_residual(FlacFrame* frame, int ch, PredictionType predict, OrderMethod omethod) { int i; FlacSubframe* sub = frame->subframes + ch; int* smp = sub->samples; int n = frame->blocksize; frame->current.samples = sub->samples; frame->current.obits = sub->obits; frame->current.wbits = sub->wbits; // CONSTANT for (i = 1; i < n; i++) { if (smp[i] != smp[0]) break; } if (i == n) { sub->type = SubframeType.Constant; sub->residual[0] = smp[0]; sub->size = sub->obits; return; } // VERBATIM frame->current.type = SubframeType.Verbatim; frame->current.size = frame->current.obits * (uint)frame->blocksize; choose_best_subframe(frame, ch); if (n < 5 || predict == PredictionType.None) return; // FIXED if (predict == PredictionType.Fixed || predict == PredictionType.Search || (predict == PredictionType.Estimated && sub->type == SubframeType.Fixed) || n <= eparams.max_prediction_order) { int max_fixed_order = Math.Min(eparams.max_fixed_order, 4); int min_fixed_order = Math.Min(eparams.min_fixed_order, max_fixed_order); for (i = min_fixed_order; i <= max_fixed_order; i++) encode_residual_fixed_sub(frame, i, ch); } // LPC if (n > eparams.max_prediction_order && (predict == PredictionType.Levinson || predict == PredictionType.Search || (predict == PredictionType.Estimated && sub->type == SubframeType.LPC))) { double* lpcs = stackalloc double[lpc.MAX_LPC_ORDER * lpc.MAX_LPC_ORDER]; int min_order = eparams.min_prediction_order; int max_order = eparams.max_prediction_order; fixed (double* window = windowBuffer) for (int iWindow = 0; iWindow < _windowcount; iWindow++) { if (predict == PredictionType.Estimated && sub->window != iWindow) continue; int est_order = (int)lpc.calc_coefs(smp, (uint)n, (uint)max_order, omethod, lpcs, window + iWindow * _windowsize); switch (omethod) { case OrderMethod.Max: // always use maximum order encode_residual_lpc_sub(frame, lpcs, iWindow, max_order, ch); break; case OrderMethod.Estimate: // estimated order encode_residual_lpc_sub(frame, lpcs, iWindow, est_order, ch); break; case OrderMethod.Search: // brute-force optimal order search for (i = max_order; i > 0; i--) encode_residual_lpc_sub(frame, lpcs, iWindow, i, ch); break; case OrderMethod.EstSearch: // brute-force search starting from estimate for (i = est_order; i > 0; i--) encode_residual_lpc_sub(frame, lpcs, iWindow, i, ch); break; case OrderMethod.LogFast: // Try max, est, 32,16,8,4,2,1 encode_residual_lpc_sub(frame, lpcs, iWindow, max_order, ch); //encode_residual_lpc_sub(frame, lpcs, est_order, ch); for (i = lpc.MAX_LPC_ORDER; i > 0; i >>= 1) if (i < max_order) encode_residual_lpc_sub(frame, lpcs, iWindow, i, ch); break; case OrderMethod.LogSearch: // do LogFast first encode_residual_lpc_sub(frame, lpcs, iWindow, max_order, ch); //encode_residual_lpc_sub(frame, lpcs, est_order, ch); for (i = lpc.MAX_LPC_ORDER; i > 0; i >>= 1) if (i < max_order) encode_residual_lpc_sub(frame, lpcs, iWindow, i, ch); // if found a good order, try to search around it if (frame->subframes[ch].type == SubframeType.LPC) { // log search (written by Michael Niedermayer for FFmpeg) for (int step = lpc.MAX_LPC_ORDER; step > 0; step >>= 1) { int last = frame->subframes[ch].order; if (step <= (last + 1) / 2) for (i = last - step; i <= last + step; i += step) if (i >= min_order && i <= max_order) encode_residual_lpc_sub(frame, lpcs, iWindow, i, ch); } } break; default: throw new Exception("unknown ordermethod"); } } } } unsafe void output_frame_header(FlacFrame* frame, BitWriter bitwriter) { bitwriter.writebits(16, 0xFFF8); 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, FlacSubframe* sub) { // rice-encoded block bitwriter.writebits(2, 0); // partition order int porder = sub->rc.porder; int psize = frame->blocksize >> porder; //assert(porder >= 0); bitwriter.writebits(4, porder); int res_cnt = psize - sub->order; // residual int j = sub->order; for (int p = 0; p < (1 << porder); p++) { int k = sub->rc.rparams[p]; bitwriter.writebits(4, k); if (p == 1) res_cnt = psize; for (int i = 0; i < res_cnt && j < frame->blocksize; i++, j++) { bitwriter.write_rice_signed(k, sub->residual[j]); } } } unsafe void output_subframe_constant(FlacFrame* frame, BitWriter bitwriter, FlacSubframe* sub) { bitwriter.writebits_signed(sub->obits, sub->residual[0]); } unsafe void output_subframe_verbatim(FlacFrame* frame, BitWriter bitwriter, FlacSubframe* 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, FlacSubframe* sub) { // warm-up samples for (int i = 0; i < sub->order; i++) bitwriter.writebits_signed(sub->obits, sub->residual[i]); // residual output_residual(frame, bitwriter, sub); } unsafe void output_subframe_lpc(FlacFrame* frame, BitWriter bitwriter, FlacSubframe* sub) { // warm-up samples for (int i = 0; i < sub->order; i++) bitwriter.writebits_signed(sub->obits, sub->residual[i]); // LPC coefficients uint cbits = lpc_precision; bitwriter.writebits(4, cbits - 1); bitwriter.writebits_signed(5, sub->shift); for (int i = 0; i < sub->order; i++) bitwriter.writebits_signed(cbits, sub->coefs[i]); // residual output_residual(frame, bitwriter, sub); } unsafe void output_subframes(FlacFrame* frame, BitWriter bitwriter) { for (int ch = 0; ch < channels; ch++) { FlacSubframe* sub = frame->subframes + ch; // subframe header int type_code = (int) sub->type; if (sub->type == SubframeType.Fixed) type_code |= sub->order; if (sub->type == SubframeType.LPC) type_code |= sub->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->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 = crc16.ComputeChecksum(frame_buffer, 0, bitwriter.Length); bitwriter.writebits(16, crc); bitwriter.flush(); } unsafe void window_welch(double* window, int L) { int N = L - 1; double N2 = (double)N / 2.0; for (int n = 0; n <= N; n++) { double k = 1 / N2 - 1.0 - Math.Min(n, N-n); //double k = ((double)n - N2) / N2; window[n] = 1.0 - k * k; } } unsafe void window_rectangle(double* window, int L) { for (int n = 0; n < L; n++) window[n] = 1.0; } unsafe void window_flattop(double* window, int L) { int N = L - 1; for (int n = 0; n < L; n++) window[n] = 1.0 - 1.93 * Math.Cos(2.0 * Math.PI * n / N) + 1.29 * Math.Cos(4.0 * Math.PI * n / N) - 0.388 * Math.Cos(6.0 * Math.PI * n / N) + 0.0322 * Math.Cos(8.0 * Math.PI * n / N); } unsafe void window_tukey(double* window, int L) { window_rectangle(window, L); double p = 0.5; int Np = (int) (p / 2.0 * L) - 1; if (Np > 0) { for (int n = 0; n <= Np; n++) { window[n] = 0.5 - 0.5 * Math.Cos(Math.PI * n / Np); window[L - Np - 1 + n] = 0.5 - 0.5 * Math.Cos(Math.PI * (n + Np) / Np); } } } unsafe void window_hann(double* window, int L) { int N = L - 1; for (int n = 0; n < L; n++) window[n] = 0.5 - 0.5 * Math.Cos(2.0 * Math.PI * n / N); } unsafe int encode_frame() { FlacFrame* frame = stackalloc FlacFrame[1]; FlacSubframe* sf = stackalloc FlacSubframe[channels * 3]; fixed (int* s = samplesBuffer, r = residualBuffer) { frame->subframes = sf; init_frame(frame); int* current_residual = stackalloc int[frame->blocksize * 3]; bool alreadyEncoded = false; if (frame->blocksize != _windowsize && frame->blocksize > 4) fixed (double* window = windowBuffer) { _windowsize = frame->blocksize; _windowcount = 0; if ((eparams.window_function & WindowFunction.Welch) != 0) window_welch(window + (_windowcount++)*_windowsize, _windowsize); if ((eparams.window_function & WindowFunction.Tukey) != 0) window_tukey(window + (_windowcount++) * _windowsize, _windowsize); if ((eparams.window_function & WindowFunction.Hann) != 0) window_hann(window + (_windowcount++) * _windowsize, _windowsize); if ((eparams.window_function & WindowFunction.Flattop) != 0) window_flattop(window + (_windowcount++) * _windowsize, _windowsize); if (_windowcount == 0) throw new Exception("invalid windowfunction"); } if (channels != 2 || frame->blocksize <= 32 || eparams.stereo_method == StereoMethod.Independent) { frame->ch_mode = channels != 2 ? ChannelMode.NotStereo : ChannelMode.LeftRight; frame->current.residual = current_residual; for (int ch = 0; ch < channels; ch++) initialize_subframe(frame, ch, s + ch * Flake.MAX_BLOCKSIZE, r + ch * Flake.MAX_BLOCKSIZE, bits_per_sample); } else { FlacFrame* frameM = stackalloc FlacFrame[1]; FlacFrame* frameS = stackalloc FlacFrame[1]; init_frame(frameS); frameS->subframes = sf + channels; frameS->ch_mode = ChannelMode.LeftRight; frameS->current.residual = current_residual; init_frame(frameM); frameM->subframes = sf + channels * 2; frameM->ch_mode = ChannelMode.MidSide; frameM->current.residual = current_residual + frame->blocksize; channel_decorrelation(s, s + Flake.MAX_BLOCKSIZE, s + 2 * Flake.MAX_BLOCKSIZE, s + 3 * Flake.MAX_BLOCKSIZE, frame->blocksize); for (int ch = 0; ch < channels; ch++) { initialize_subframe(frameS, ch, s + ch * Flake.MAX_BLOCKSIZE, r + ch * Flake.MAX_BLOCKSIZE, bits_per_sample); initialize_subframe(frameM, ch, s + (channels + ch) * Flake.MAX_BLOCKSIZE, r + (channels + ch) * Flake.MAX_BLOCKSIZE, bits_per_sample + (uint)ch); } uint bitsBest = UINT32_MAX; ChannelMode modeBest = ChannelMode.LeftRight; if (eparams.stereo_method == StereoMethod.Estimate) { frameM->subframes[0].size = (uint)frame->blocksize * 32 + calc_decorr_score(frameM, 0, frameM->subframes + 0); frameM->subframes[1].size = (uint)frame->blocksize * 32 + calc_decorr_score(frameM, 1, frameM->subframes + 1); frameS->subframes[0].size = (uint)frame->blocksize * 32 + calc_decorr_score(frameS, 0, frameS->subframes + 0); frameS->subframes[1].size = (uint)frame->blocksize * 32 + calc_decorr_score(frameS, 1, frameS->subframes + 1); } else if (eparams.stereo_method == StereoMethod.Estimate2) { int max_prediction_order = eparams.max_prediction_order; int max_window = _windowcount; eparams.max_prediction_order /= 2; _windowcount = 1; encode_residual(frameM, 0, PredictionType.Levinson, OrderMethod.Estimate); encode_residual(frameM, 1, PredictionType.Levinson, OrderMethod.Estimate); encode_residual(frameS, 0, PredictionType.Levinson, OrderMethod.Estimate); encode_residual(frameS, 1, PredictionType.Levinson, OrderMethod.Estimate); _windowcount = max_window; eparams.max_prediction_order = max_prediction_order; } else if (eparams.stereo_method == StereoMethod.Estimate3) { int max_fixed_order = eparams.max_fixed_order; eparams.max_fixed_order = 2; encode_residual(frameM, 0, PredictionType.Fixed, OrderMethod.Estimate); encode_residual(frameM, 1, PredictionType.Fixed, OrderMethod.Estimate); encode_residual(frameS, 0, PredictionType.Fixed, OrderMethod.Estimate); encode_residual(frameS, 1, PredictionType.Fixed, OrderMethod.Estimate); eparams.max_fixed_order = max_fixed_order; } else if (eparams.stereo_method == StereoMethod.Estimate4) { int max_prediction_order = eparams.max_prediction_order; int max_fixed_order = eparams.max_fixed_order; int min_fixed_order = eparams.min_fixed_order; eparams.min_fixed_order = 2; eparams.max_fixed_order = 2; eparams.max_prediction_order = Math.Min(eparams.max_prediction_order, 8); encode_residual(frameM, 0, eparams.prediction_type, OrderMethod.Estimate); encode_residual(frameM, 1, eparams.prediction_type, OrderMethod.Estimate); encode_residual(frameS, 0, eparams.prediction_type, OrderMethod.Estimate); encode_residual(frameS, 1, eparams.prediction_type, OrderMethod.Estimate); eparams.min_fixed_order = min_fixed_order; eparams.max_fixed_order = max_fixed_order; eparams.max_prediction_order = max_prediction_order; } else if (eparams.stereo_method == StereoMethod.Estimate5) { int max_prediction_order = eparams.max_prediction_order; int max_fixed_order = eparams.max_fixed_order; int min_fixed_order = eparams.min_fixed_order; eparams.min_fixed_order = 2; eparams.max_fixed_order = 2; eparams.max_prediction_order = Math.Min(eparams.max_prediction_order, 12); encode_residual(frameM, 0, eparams.prediction_type, OrderMethod.Estimate); encode_residual(frameM, 1, eparams.prediction_type, OrderMethod.Estimate); encode_residual(frameS, 0, eparams.prediction_type, OrderMethod.Estimate); encode_residual(frameS, 1, eparams.prediction_type, OrderMethod.Estimate); eparams.min_fixed_order = min_fixed_order; eparams.max_fixed_order = max_fixed_order; eparams.max_prediction_order = max_prediction_order; } else // StereoMethod.Search { encode_residual(frameM, 0, eparams.prediction_type, eparams.order_method); encode_residual(frameM, 1, eparams.prediction_type, eparams.order_method); encode_residual(frameS, 0, eparams.prediction_type, eparams.order_method); encode_residual(frameS, 1, eparams.prediction_type, eparams.order_method); alreadyEncoded = true; } if (bitsBest > frameM->subframes[0].size + frameM->subframes[1].size) { bitsBest = frameM->subframes[0].size + frameM->subframes[1].size; modeBest = ChannelMode.MidSide; frame->subframes[0] = frameM->subframes[0]; frame->subframes[1] = frameM->subframes[1]; } if (bitsBest > frameM->subframes[1].size + frameS->subframes[1].size) { bitsBest = frameM->subframes[1].size + frameS->subframes[1].size; modeBest = ChannelMode.RightSide; frame->subframes[0] = frameM->subframes[1]; frame->subframes[1] = frameS->subframes[1]; } if (bitsBest > frameM->subframes[1].size + frameS->subframes[0].size) { bitsBest = frameM->subframes[1].size + frameS->subframes[0].size; modeBest = ChannelMode.LeftSide; frame->subframes[0] = frameS->subframes[0]; frame->subframes[1] = frameM->subframes[1]; } if (bitsBest > frameS->subframes[0].size + frameS->subframes[1].size) { bitsBest = frameS->subframes[0].size + frameS->subframes[1].size; modeBest = ChannelMode.LeftRight; frame->subframes[0] = frameS->subframes[0]; frame->subframes[1] = frameS->subframes[1]; } frame->ch_mode = modeBest; frame->current.residual = current_residual + 2 * frame->blocksize; if (eparams.stereo_method == StereoMethod.Estimate4 || eparams.stereo_method == StereoMethod.Estimate5) { encode_residual(frame, 0, PredictionType.Estimated, eparams.order_method); encode_residual(frame, 1, PredictionType.Estimated, eparams.order_method); alreadyEncoded = true; } } if (!alreadyEncoded) for (int ch = 0; ch < channels; ch++) encode_residual(frame, ch, eparams.prediction_type, eparams.order_method); BitWriter bitwriter = new BitWriter(frame_buffer, 0, max_frame_size); output_frame_header(frame, bitwriter); output_subframes(frame, bitwriter); output_frame_footer(bitwriter); if (frame_buffer != null) { if (eparams.variable_block_size > 0) frame_count += eparams.block_size; else frame_count++; } return bitwriter.Length; } } unsafe void output_frame() { if (verify != null) { fixed (int* s = verifyBuffer, r = samplesBuffer) for (int ch = 0; ch < channels; ch++) Flake.memcpy(s + ch * Flake.MAX_BLOCKSIZE, r + ch * Flake.MAX_BLOCKSIZE, eparams.block_size); } int fs; if (0 != eparams.variable_block_size && 0 == (eparams.block_size & 7) && eparams.block_size >= 128) fs = encode_frame_vbs(); else fs = encode_frame(); _position += eparams.block_size; _IO.Write(frame_buffer, 0, fs); _totalSize += fs; if (verify != null) { int decoded = verify.DecodeFrame(frame_buffer, 0, fs); if (decoded != fs || verify.Remaining != (ulong)eparams.block_size) throw new Exception("validation failed!"); fixed (int* s = verifyBuffer, r = verify.Samples) { for (int ch = 0; ch < channels; ch++) if (Flake.memcmp(s + ch * Flake.MAX_BLOCKSIZE, r +ch * Flake.MAX_BLOCKSIZE, decoded)) throw new Exception("validation failed!"); } } } public void Write(int[,] buff, int pos, int sampleCount) { if (!inited) { int header_size = flake_encode_init(); if (_IO == null) _IO = new FileStream(_path, FileMode.Create, FileAccess.Write, FileShare.Read); _IO.Write(header, 0, header_size); inited = true; } int len = sampleCount; while (len > 0) { int block = Math.Min(len, eparams.block_size - samplesInBuffer); copy_samples(buff, pos, block); AudioSamples.FLACSamplesToBytes(buff, pos, frame_buffer, 0, block, channels, (int)bits_per_sample); md5.TransformBlock(frame_buffer, 0, block * channels * ((int)bits_per_sample >> 3), null, 0); if (samplesInBuffer < eparams.block_size) return; output_frame(); samplesInBuffer = 0; len -= block; pos += block; } } public string Path { get { return _path; } } string vendor_string = "Flake#0.1"; int select_blocksize(int samplerate, int time_ms) { int blocksize = Flake.flac_blocksizes[1]; int target = (samplerate * time_ms) / 1000; for (int i = 0; i < Flake.flac_blocksizes.Length; i++) if (target >= Flake.flac_blocksizes[i] && Flake.flac_blocksizes[i] > blocksize) { blocksize = Flake.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.FLAC__METADATA_TYPE_STREAMINFO); bitwriter.writebits(24, 34); if (eparams.variable_block_size > 0) bitwriter.writebits(16, 0); else bitwriter.writebits(16, eparams.block_size); bitwriter.writebits(16, eparams.block_size); bitwriter.writebits(24, 0); bitwriter.writebits(24, max_frame_size); bitwriter.writebits(20, sample_rate); bitwriter.writebits(3, channels - 1); bitwriter.writebits(5, bits_per_sample - 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 last) { BitWriter bitwriter = new BitWriter(comment, pos, 4); Encoding enc = new ASCIIEncoding(); int vendor_len = enc.GetBytes(vendor_string, 0, vendor_string.Length, comment, pos + 8); // metadata header bitwriter.writebits(1, last); bitwriter.writebits(7, (int)MetadataType.FLAC__METADATA_TYPE_VORBIS_COMMENT); bitwriter.writebits(24, vendor_len + 8); comment[pos + 4] = (byte)(vendor_len & 0xFF); comment[pos + 5] = (byte)((vendor_len >> 8) & 0xFF); comment[pos + 6] = (byte)((vendor_len >> 16) & 0xFF); comment[pos + 7] = (byte)((vendor_len >> 24) & 0xFF); comment[pos + 8 + vendor_len] = 0; comment[pos + 9 + vendor_len] = 0; comment[pos + 10 + vendor_len] = 0; comment[pos + 11 + vendor_len] = 0; bitwriter.flush(); return vendor_len + 12; } /** * 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.FLAC__METADATA_TYPE_PADDING); bitwriter.writebits(24, padlen); 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; // vorbis comment if (eparams.padding_size == 0) last = 1; header_size += write_vorbis_comment(header, header_size, last); // padding if (eparams.padding_size > 0) { last = 1; header_size += write_padding(header, header_size, last, eparams.padding_size); } 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 = 4; i < 12; i++) { if (sample_rate == Flake.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 (bits_per_sample == Flake.flac_bitdepths[i]) { bps_code = i; break; } } if (i == 8) throw new Exception("non-standard bps"); // FIXME: For now, only 16-bit encoding is supported if (bits_per_sample != 16) throw new Exception("non-standard bps"); if (eparams.block_size == 0) if (_blocksize == 0) eparams.block_size = select_blocksize(sample_rate, eparams.block_time_ms); else eparams.block_size = _blocksize; // select LPC precision based on block size if (eparams.block_size <= 192) lpc_precision = 7U; else if (eparams.block_size <= 384) lpc_precision = 8U; else if (eparams.block_size <= 576) lpc_precision = 9U; else if (eparams.block_size <= 1152) lpc_precision = 10U; else if (eparams.block_size <= 2304) lpc_precision = 11U; else if (eparams.block_size <= 4608) lpc_precision = 12U; else if (eparams.block_size <= 8192) lpc_precision = 13U; else if (eparams.block_size <= 16384) lpc_precision = 14U; else lpc_precision = 15; // set maximum encoded frame size (if larger, re-encodes in verbatim mode) if (channels == 2) max_frame_size = 16 + ((eparams.block_size * (int)(bits_per_sample + bits_per_sample + 1) + 7) >> 3); else max_frame_size = 16 + ((eparams.block_size * channels * (int)bits_per_sample + 7) >> 3); // output header bytes header = new byte[eparams.padding_size + 1024]; header_len = write_headers(); // initialize CRC & MD5 //crc_init(); //md5_init(&ctx->md5ctx); frame_buffer = new byte[max_frame_size]; return header_len; } } struct FlakeEncodeParams { // compression quality // set by user prior to calling flake_encode_init // standard values are 0 to 8 // 0 is lower compression, faster encoding // 8 is higher compression, slower encoding // extended values 9 to 12 are slower and/or use // higher prediction orders public int compression; // 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; // stereo decorrelation 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 2 // 0 = independent L+R channels // 1 = mid-side encoding public StereoMethod stereo_method; // block size in samples // set by the user prior to calling flake_encode_init // if set to 0, a block size is chosen based on block_time_ms // can also be changed by user before encoding a frame public int block_size; // 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; // padding size in bytes // set by the user prior to calling flake_encode_init // if set to less than 0, defaults to 4096 public int padding_size; // maximum encoded frame size // this is set by flake_encode_init based on input audio format // it can be used by the user to allocate an output buffer public int max_frame_size; // minimum LPC order // set by user prior to calling flake_encode_init // if set to less than 0, it is chosen based on compression. // valid values are 1 to 32 public int min_prediction_order; // maximum LPC order // set by user prior to calling flake_encode_init // if set to less than 0, it is chosen based on compression. // valid values are 1 to 32 public int max_prediction_order; // minimum fixed prediction order // 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 4 public int min_fixed_order; // maximum fixed prediction order // 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 4 public int max_fixed_order; // type of linear prediction // set by user prior to calling flake_encode_init public PredictionType prediction_type; // minimum partition order // 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 8 public int min_partition_order; // maximum partition order // 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 8 public int max_partition_order; // 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 WindowFunction window_function; public int flake_set_defaults(int lvl) { compression = lvl; if ((lvl < 0 || lvl > 12) && (lvl != 99)) { return -1; } // default to level 5 params window_function = WindowFunction.Flattop | WindowFunction.Tukey; order_method = OrderMethod.Estimate; stereo_method = StereoMethod.Estimate; block_size = 0; block_time_ms = 105; prediction_type = PredictionType.Search; min_prediction_order = 1; max_prediction_order = 12; min_fixed_order = 0; max_fixed_order = 4; min_partition_order = 0; max_partition_order = 6; variable_block_size = 0; // differences from level 5 switch (lvl) { case 0: stereo_method = StereoMethod.Independent; block_time_ms = 27; prediction_type = PredictionType.Fixed; min_fixed_order = 2; max_fixed_order = 2; min_partition_order = 4; max_partition_order = 4; break; case 1: block_time_ms = 27; prediction_type = PredictionType.Fixed; min_fixed_order = 2; max_fixed_order = 3; min_partition_order = 2; max_partition_order = 2; break; case 2: block_time_ms = 27; prediction_type = PredictionType.Fixed; //prediction_type = PredictionType.Fixed; min_fixed_order = 2; max_fixed_order = 4; //min_partition_order = 0; //max_partition_order = 3; break; case 3: prediction_type = PredictionType.Levinson; window_function = WindowFunction.Welch; max_prediction_order = 8; break; case 4: prediction_type = PredictionType.Levinson; window_function = WindowFunction.Welch; break; case 5: prediction_type = PredictionType.Levinson; break; case 6: stereo_method = StereoMethod.Estimate4; break; case 7: order_method = OrderMethod.LogSearch; stereo_method = StereoMethod.Estimate4; break; case 8: order_method = OrderMethod.Search; stereo_method = StereoMethod.Estimate4; break; case 9: stereo_method = StereoMethod.Estimate4; max_prediction_order = 32; break; case 10: order_method = OrderMethod.LogFast; stereo_method = StereoMethod.Estimate5; max_prediction_order = 32; break; case 11: order_method = OrderMethod.LogSearch; stereo_method = StereoMethod.Estimate5; max_prediction_order = 32; max_partition_order = 8; break; case 99: order_method = OrderMethod.Search; block_time_ms = 186; max_prediction_order = 32; max_partition_order = 8; variable_block_size = 2; break; } return 0; } } }