From c4c344e88f3ff8e6d19b09fb0a7a5ec7dd22d180 Mon Sep 17 00:00:00 2001 From: chudov Date: Mon, 20 Sep 2010 05:32:05 +0000 Subject: [PATCH] opencl flac encoder --- .../CUETools.Codecs.FLACCL.csproj | 91 + CUETools.Codecs.FLACCL/FLACCLWriter.cs | 2473 +++++++++++++++++ CUETools.Codecs.FLACCL/OpenCLNet.dll | Bin 0 -> 148480 bytes CUETools.Codecs.FLACCL/OpenCLNet.pdb | Bin 0 -> 394752 bytes .../Properties/AssemblyInfo.cs | 35 + .../Properties/Resources.Designer.cs | 117 + .../Properties/Resources.resx | 138 + .../Properties/Resources.ru-RU.resx | 138 + CUETools.Codecs.FLACCL/flac.cl | 1275 +++++++++ 9 files changed, 4267 insertions(+) create mode 100644 CUETools.Codecs.FLACCL/CUETools.Codecs.FLACCL.csproj create mode 100644 CUETools.Codecs.FLACCL/FLACCLWriter.cs create mode 100644 CUETools.Codecs.FLACCL/OpenCLNet.dll create mode 100644 CUETools.Codecs.FLACCL/OpenCLNet.pdb create mode 100644 CUETools.Codecs.FLACCL/Properties/AssemblyInfo.cs create mode 100644 CUETools.Codecs.FLACCL/Properties/Resources.Designer.cs create mode 100644 CUETools.Codecs.FLACCL/Properties/Resources.resx create mode 100644 CUETools.Codecs.FLACCL/Properties/Resources.ru-RU.resx create mode 100644 CUETools.Codecs.FLACCL/flac.cl diff --git a/CUETools.Codecs.FLACCL/CUETools.Codecs.FLACCL.csproj b/CUETools.Codecs.FLACCL/CUETools.Codecs.FLACCL.csproj new file mode 100644 index 0000000..1282a58 --- /dev/null +++ b/CUETools.Codecs.FLACCL/CUETools.Codecs.FLACCL.csproj @@ -0,0 +1,91 @@ + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {DFE55765-564C-4B8F-993B-A94C4D1C212E} + Library + Properties + CUETools.Codecs.FLACCL + CUETools.Codecs.FLACCL + + + 2.0 + + + + + true + full + false + ..\bin\Debug\plugins\ + DEBUG;TRACE + prompt + 4 + true + + + pdbonly + true + ..\bin\Release\plugins\ + TRACE + prompt + 4 + true + + + + False + OpenCLNet.dll + + + + + + + + + + True + True + Resources.resx + + + + + {082D6B9E-326E-4D15-9798-EDAE9EDE70A6} + CUETools.Codecs.FLAKE + False + + + {6458A13A-30EF-45A9-9D58-E5031B17BEE2} + CUETools.Codecs + False + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + + + + + + \ No newline at end of file diff --git a/CUETools.Codecs.FLACCL/FLACCLWriter.cs b/CUETools.Codecs.FLACCL/FLACCLWriter.cs new file mode 100644 index 0000000..65b5cf2 --- /dev/null +++ b/CUETools.Codecs.FLACCL/FLACCLWriter.cs @@ -0,0 +1,2473 @@ +/** + * CUETools.FLACCL: FLAC audio encoder using CUDA + * Copyright (c) 2009 Gregory S. Chudov + * + * 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 + */ + +using System; +using System.ComponentModel; +using System.Collections.Generic; +using System.IO; +using System.Security.Cryptography; +using System.Threading; +using System.Text; +using System.Runtime.InteropServices; +using CUETools.Codecs; +using CUETools.Codecs.FLAKE; +using OpenCLNet; + +namespace CUETools.Codecs.FLACCL +{ + public class FLACCLWriterSettings + { + public FLACCLWriterSettings() { DoVerify = false; GPUOnly = false; DoMD5 = true; } + + [DefaultValue(false)] + [DisplayName("Verify")] + [SRDescription(typeof(Properties.Resources), "DoVerifyDescription")] + public bool DoVerify { get; set; } + + [DefaultValue(true)] + [DisplayName("MD5")] + [SRDescription(typeof(Properties.Resources), "DoMD5Description")] + public bool DoMD5 { get; set; } + + [DefaultValue(true)] + [SRDescription(typeof(Properties.Resources), "DescriptionGPUOnly")] + public bool GPUOnly { get; set; } + + int cpu_threads = 1; + [DefaultValue(1)] + [SRDescription(typeof(Properties.Resources), "DescriptionCPUThreads")] + public int CPUThreads + { + get + { + return cpu_threads; + } + set + { + if (value < 0 || value > 16) + throw new Exception("CPUThreads must be between 0..16"); + cpu_threads = value; + } + } + } + + [AudioEncoderClass("FLACCL", "flac", true, "0 1 2 3 4 5 6 7 8 9 10 11", "8", 2, typeof(FLACCLWriterSettings))] + //[AudioEncoderClass("FLACCL nonsub", "flac", true, "9 10 11", "9", 1, typeof(FLACCLWriterSettings))] + public class FLACCLWriter : IAudioDest + { + Stream _IO = null; + string _path; + long _position; + + // number of audio channels + // valid values are 1 to 8 + int channels, ch_code; + + // audio sample rate in Hz + int sample_rate, sr_code0, sr_code1; + + // sample size in bits + // only 16-bit is currently supported + uint bits_per_sample; + int bps_code; + + // total stream samples + // if 0, stream length is unknown + int sample_count = -1; + + FlakeEncodeParams eparams; + + // maximum frame size in bytes + // this can be used to allocate memory for output + int max_frame_size; + + int frame_count = 0; + int frame_pos = 0; + + long first_frame_offset = 0; + + TimeSpan _userProcessorTime; + + // header bytes + // allocated by flake_encode_init and freed by flake_encode_close + byte[] header; + + float[] windowBuffer; + int samplesInBuffer = 0; + int max_frames = 0; + + int _compressionLevel = 7; + int _blocksize = 0; + int _totalSize = 0; + int _windowsize = 0, _windowcount = 0; + + Crc8 crc8; + Crc16 crc16; + MD5 md5; + + SeekPoint[] seek_table; + int seek_table_offset = -1; + + bool inited = false; + + OpenCLManager OCLMan; + Context openCLContext; + Program openCLProgram; + + FLACCLTask task1; + FLACCLTask task2; + FLACCLTask[] cpu_tasks; + int oldest_cpu_task = 0; + + Mem cudaWindow; + + AudioPCMConfig _pcm; + + public const int MAX_BLOCKSIZE = 4096 * 16; + internal const int maxFrames = 128; + internal const int maxAutocorParts = (MAX_BLOCKSIZE + 255) / 256; + + public FLACCLWriter(string path, Stream IO, AudioPCMConfig pcm) + { + _pcm = pcm; + + if (pcm.BitsPerSample != 16) + throw new Exception("Bits per sample must be 16."); + if (pcm.ChannelCount != 2) + throw new Exception("ChannelCount must be 2."); + + channels = pcm.ChannelCount; + sample_rate = pcm.SampleRate; + bits_per_sample = (uint) pcm.BitsPerSample; + + // flake_validate_params + + _path = path; + _IO = IO; + + windowBuffer = new float[FLACCLWriter.MAX_BLOCKSIZE * lpc.MAX_LPC_WINDOWS]; + + eparams.flake_set_defaults(_compressionLevel, !_settings.GPUOnly); + eparams.padding_size = 8192; + + crc8 = new Crc8(); + crc16 = new Crc16(); + } + + public FLACCLWriter(string path, AudioPCMConfig pcm) + : this(path, null, pcm) + { + } + + public int TotalSize + { + get + { + return _totalSize; + } + } + + public long Padding + { + get + { + return eparams.padding_size; + } + set + { + eparams.padding_size = value; + } + } + + public int CompressionLevel + { + get + { + return _compressionLevel; + } + set + { + if (value < 0 || value > 11) + throw new Exception("unsupported compression level"); + _compressionLevel = value; + eparams.flake_set_defaults(_compressionLevel, !_settings.GPUOnly); + } + } + + FLACCLWriterSettings _settings = new FLACCLWriterSettings(); + + public object Settings + { + get + { + return _settings; + } + set + { + if (value as FLACCLWriterSettings == null) + throw new Exception("Unsupported options " + value); + _settings = value as FLACCLWriterSettings; + eparams.flake_set_defaults(_compressionLevel, !_settings.GPUOnly); + } + } + + //[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 (inited) + { + int nFrames = samplesInBuffer / eparams.block_size; + if (nFrames > 0) + do_output_frames(nFrames); + if (samplesInBuffer > 0) + { + eparams.block_size = samplesInBuffer; + do_output_frames(1); + } + if (task2.frameCount > 0) + { + if (cpu_tasks != null) + { + for (int i = 0; i < cpu_tasks.Length; i++) + { + wait_for_cpu_task(); + FLACCLTask task = cpu_tasks[oldest_cpu_task]; + oldest_cpu_task = (oldest_cpu_task + 1) % cpu_tasks.Length; + if (task.frameCount > 0) + { + write_result(task); + task.frameCount = 0; + } + } + } + task2.openCLCQ.Finish(); // cuda.SynchronizeStream(task2.stream); + process_result(task2); + write_result(task2); + task2.frameCount = 0; + } + + 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(new byte[] { 0 }, 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(); + + cudaWindow.Dispose(); + task1.Dispose(); + task2.Dispose(); + if (cpu_tasks != null) + foreach (FLACCLTask task in cpu_tasks) + task.Dispose(); + openCLProgram.Dispose(); + openCLContext.Dispose(); + inited = false; + } + } + + public void Close() + { + DoClose(); + if (sample_count > 0 && _position != sample_count) + throw new Exception(string.Format("Samples written differs from the expected sample count. Expected {0}, got {1}.", sample_count, _position)); + } + + public void Delete() + { + if (inited) + { + _IO.Close(); + cudaWindow.Dispose(); + task1.Dispose(); + task2.Dispose(); + if (cpu_tasks != null) + foreach (FLACCLTask task in cpu_tasks) + task.Dispose(); + openCLProgram.Dispose(); + openCLContext.Dispose(); + inited = false; + } + + if (_path != "") + File.Delete(_path); + } + + public long Position + { + get + { + return _position; + } + } + + public long FinalSampleCount + { + set { sample_count = (int)value; } + } + + public long BlockSize + { + set { + if (value < 256 || value > MAX_BLOCKSIZE ) + throw new Exception("unsupported BlockSize value"); + _blocksize = (int)value; + } + get { return _blocksize == 0 ? eparams.block_size : _blocksize; } + } + + public StereoMethod StereoMethod + { + get { return eparams.do_midside ? StereoMethod.Search : StereoMethod.Independent; } + set { eparams.do_midside = value != StereoMethod.Independent; } + } + + public int MinPrecisionSearch + { + get { return eparams.lpc_min_precision_search; } + set + { + if (value < 0 || value > eparams.lpc_max_precision_search) + throw new Exception("unsupported MinPrecisionSearch value"); + eparams.lpc_min_precision_search = value; + } + } + + public int MaxPrecisionSearch + { + get { return eparams.lpc_max_precision_search; } + set + { + if (value < eparams.lpc_min_precision_search || value >= lpc.MAX_LPC_PRECISIONS) + throw new Exception("unsupported MaxPrecisionSearch value"); + eparams.lpc_max_precision_search = value; + } + } + + public WindowFunction WindowFunction + { + get { return eparams.window_function; } + set { eparams.window_function = 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 int OrdersPerWindow + { + get + { + return eparams.orders_per_window; + } + set + { + if (value < 0 || value > 32) + throw new Exception("invalid OrdersPerWindow " + value.ToString()); + eparams.orders_per_window = value; + } + } + + public int MinLPCOrder + { + get + { + return eparams.min_prediction_order; + } + set + { + if (value < 1 || value > eparams.max_prediction_order) + throw new Exception("invalid MinLPCOrder " + value.ToString()); + eparams.min_prediction_order = value; + } + } + + public int MaxLPCOrder + { + get + { + return eparams.max_prediction_order; + } + set + { + if (value > lpc.MAX_LPC_ORDER || value < eparams.min_prediction_order) + throw new Exception("invalid MaxLPCOrder " + value.ToString()); + eparams.max_prediction_order = value; + } + } + + public int MinFixedOrder + { + get + { + return eparams.min_fixed_order; + } + set + { + if (value < 0 || value > eparams.max_fixed_order) + throw new Exception("invalid MinFixedOrder " + value.ToString()); + eparams.min_fixed_order = value; + } + } + + public int MaxFixedOrder + { + get + { + return eparams.max_fixed_order; + } + set + { + if (value > 4 || value < eparams.min_fixed_order) + throw new Exception("invalid MaxFixedOrder " + value.ToString()); + eparams.max_fixed_order = value; + } + } + + public int MinPartitionOrder + { + get { return eparams.min_partition_order; } + set + { + if (value < 0 || value > eparams.max_partition_order) + throw new Exception("invalid MinPartitionOrder " + value.ToString()); + eparams.min_partition_order = value; + } + } + + public int MaxPartitionOrder + { + get { return eparams.max_partition_order; } + set + { + if (value > 8 || value < eparams.min_partition_order) + throw new Exception("invalid MaxPartitionOrder " + value.ToString()); + eparams.max_partition_order = value; + } + } + + public TimeSpan UserProcessorTime + { + get { return _userProcessorTime; } + } + + public AudioPCMConfig PCM + { + get { return _pcm; } + } + + unsafe void encode_residual_fixed(int* res, int* smp, int n, int order) + { + int i; + int s0, s1, s2; + switch (order) + { + case 0: + AudioSamples.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; + } + } + + static unsafe uint calc_optimal_rice_params(int porder, int* parm, uint* sums, uint n, uint pred_order) + { + uint part = (1U << porder); + uint cnt = (n >> porder) - pred_order; + int k = cnt > 0 ? Math.Min(Flake.MAX_RICE_PARAM, BitReader.log2i(sums[0] / cnt)) : 0; + uint all_bits = cnt * ((uint)k + 1U) + (sums[0] >> k); + parm[0] = k; + cnt = (n >> porder); + for (uint i = 1; i < part; i++) + { + k = Math.Min(Flake.MAX_RICE_PARAM, BitReader.log2i(sums[i] / cnt)); + all_bits += cnt * ((uint)k + 1U) + (sums[i] >> k); + parm[i] = k; + } + return all_bits + (4 * part); + } + + static unsafe void calc_lower_sums(int pmin, int pmax, uint* sums) + { + for (int i = pmax - 1; i >= pmin; i--) + { + for (int j = 0; j < (1 << i); 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 void calc_sums(int pmin, int pmax, uint* data, uint n, uint pred_order, uint* sums) + { + int parts = (1 << pmax); + uint* res = data + pred_order; + uint cnt = (n >> pmax) - pred_order; + uint sum = 0; + for (uint j = cnt; j > 0; j--) + sum += *(res++); + sums[0] = sum; + cnt = (n >> pmax); + for (int i = 1; i < parts; i++) + { + sum = 0; + for (uint j = cnt; j > 0; j--) + sum += *(res++); + sums[i] = sum; + } + } + + /// + /// Special case when (n >> pmax) == 18 + /// + /// + /// + /// + /// + /// + /// + static unsafe void calc_sums18(int pmin, int pmax, uint* data, uint n, uint pred_order, uint* sums) + { + int parts = (1 << pmax); + uint* res = data + pred_order; + uint cnt = 18 - pred_order; + uint sum = 0; + for (uint j = cnt; j > 0; j--) + sum += *(res++); + sums[0] = sum; + for (int i = 1; i < parts; i++) + { + sums[i] = + *(res++) + *(res++) + *(res++) + *(res++) + + *(res++) + *(res++) + *(res++) + *(res++) + + *(res++) + *(res++) + *(res++) + *(res++) + + *(res++) + *(res++) + *(res++) + *(res++) + + *(res++) + *(res++); + } + } + + /// + /// Special case when (n >> pmax) == 18 + /// + /// + /// + /// + /// + /// + /// + static unsafe void calc_sums16(int pmin, int pmax, uint* data, uint n, uint pred_order, uint* sums) + { + int parts = (1 << pmax); + uint* res = data + pred_order; + uint cnt = 16 - pred_order; + uint sum = 0; + for (uint j = cnt; j > 0; j--) + sum += *(res++); + sums[0] = sum; + for (int i = 1; i < parts; i++) + { + sums[i] = + *(res++) + *(res++) + *(res++) + *(res++) + + *(res++) + *(res++) + *(res++) + *(res++) + + *(res++) + *(res++) + *(res++) + *(res++) + + *(res++) + *(res++) + *(res++) + *(res++); + } + } + + static unsafe uint calc_rice_params(RiceContext rc, int pmin, int pmax, int* data, uint n, uint pred_order) + { + uint* udata = stackalloc uint[(int)n]; + uint* sums = stackalloc uint[(pmax + 1) * Flake.MAX_PARTITIONS]; + int* parm = stackalloc int[(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)((data[i] << 1) ^ (data[i] >> 31)); + + // sums for highest level + if ((n >> pmax) == 18) + calc_sums18(pmin, pmax, udata, n, pred_order, sums + pmax * Flake.MAX_PARTITIONS); + else if ((n >> pmax) == 16) + calc_sums16(pmin, pmax, udata, n, pred_order, sums + pmax * Flake.MAX_PARTITIONS); + else + calc_sums(pmin, pmax, udata, n, pred_order, sums + pmax * Flake.MAX_PARTITIONS); + // sums for lower levels + calc_lower_sums(pmin, pmax, sums); + + uint opt_bits = AudioSamples.UINT32_MAX; + int opt_porder = pmin; + for (int i = pmin; i <= pmax; i++) + { + uint bits = calc_optimal_rice_params(i, parm + i * Flake.MAX_PARTITIONS, sums + i * Flake.MAX_PARTITIONS, n, pred_order); + if (bits <= opt_bits) + { + opt_bits = bits; + opt_porder = i; + } + } + + rc.porder = opt_porder; + fixed (int* rparms = rc.rparams) + AudioSamples.MemCpy(rparms, parm + opt_porder * Flake.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; + } + + unsafe void output_frame_header(FlacFrame frame) + { + frame.writer.writebits(15, 0x7FFC); + frame.writer.writebits(1, eparams.variable_block_size > 0 ? 1 : 0); + frame.writer.writebits(4, frame.bs_code0); + frame.writer.writebits(4, sr_code0); + if (frame.ch_mode == ChannelMode.NotStereo) + frame.writer.writebits(4, ch_code); + else + frame.writer.writebits(4, (int)frame.ch_mode); + frame.writer.writebits(3, bps_code); + frame.writer.writebits(1, 0); + frame.writer.write_utf8(frame.frame_number); + + // custom block size + if (frame.bs_code1 >= 0) + { + if (frame.bs_code1 < 256) + frame.writer.writebits(8, frame.bs_code1); + else + frame.writer.writebits(16, frame.bs_code1); + } + + // custom sample rate + if (sr_code1 > 0) + { + if (sr_code1 < 256) + frame.writer.writebits(8, sr_code1); + else + frame.writer.writebits(16, sr_code1); + } + + // CRC-8 of frame header + frame.writer.flush(); + byte crc = crc8.ComputeChecksum(frame.writer.Buffer, frame.writer_offset, frame.writer.Length - frame.writer_offset); + frame.writer.writebits(8, crc); + } + + unsafe void output_residual(FlacFrame frame, FlacSubframeInfo sub) + { + // rice-encoded block + frame.writer.writebits(2, 0); + + // partition order + int porder = sub.best.rc.porder; + int psize = frame.blocksize >> porder; + //assert(porder >= 0); + frame.writer.writebits(4, porder); + int res_cnt = psize - sub.best.order; + + // residual + int j = sub.best.order; + fixed (byte* fixbuf = frame.writer.Buffer) + for (int p = 0; p < (1 << porder); p++) + { + int k = sub.best.rc.rparams[p]; + frame.writer.writebits(4, k); + if (p == 1) res_cnt = psize; + int cnt = Math.Min(res_cnt, frame.blocksize - j); + frame.writer.write_rice_block_signed(fixbuf, k, sub.best.residual + j, cnt); + j += cnt; + } + } + + unsafe void + output_subframe_constant(FlacFrame frame, FlacSubframeInfo sub) + { + frame.writer.writebits_signed(sub.obits, sub.samples[0]); + } + + unsafe void + output_subframe_verbatim(FlacFrame frame, FlacSubframeInfo sub) + { + int n = frame.blocksize; + for (int i = 0; i < n; i++) + frame.writer.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, FlacSubframeInfo sub) + { + // warm-up samples + for (int i = 0; i < sub.best.order; i++) + frame.writer.writebits_signed(sub.obits, sub.samples[i]); + + // residual + output_residual(frame, sub); + } + + unsafe void + output_subframe_lpc(FlacFrame frame, FlacSubframeInfo sub) + { + // warm-up samples + for (int i = 0; i < sub.best.order; i++) + frame.writer.writebits_signed(sub.obits, sub.samples[i]); + + // LPC coefficients + frame.writer.writebits(4, sub.best.cbits - 1); + frame.writer.writebits_signed(5, sub.best.shift); + for (int i = 0; i < sub.best.order; i++) + frame.writer.writebits_signed(sub.best.cbits, sub.best.coefs[i]); + + // residual + output_residual(frame, sub); + } + + unsafe void output_subframes(FlacFrame frame) + { + 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; + frame.writer.writebits(1, 0); + frame.writer.writebits(6, type_code); + frame.writer.writebits(1, sub.wbits != 0 ? 1 : 0); + if (sub.wbits > 0) + frame.writer.writebits((int)sub.wbits, 1); + + //if (frame_writer.Length >= frame_buffer.Length) + // throw new Exception("buffer overflow"); + + // subframe + switch (sub.best.type) + { + case SubframeType.Constant: + output_subframe_constant(frame, sub); + break; + case SubframeType.Verbatim: + output_subframe_verbatim(frame, sub); + break; + case SubframeType.Fixed: + output_subframe_fixed(frame, sub); + break; + case SubframeType.LPC: + output_subframe_lpc(frame, sub); + break; + } + //if (frame_writer.Length >= frame_buffer.Length) + // throw new Exception("buffer overflow"); + } + } + + void output_frame_footer(FlacFrame frame) + { + frame.writer.flush(); + ushort crc = crc16.ComputeChecksum(frame.writer.Buffer, frame.writer_offset, frame.writer.Length - frame.writer_offset); + frame.writer.writebits(16, crc); + frame.writer.flush(); + } + + unsafe delegate void window_function(float* window, int size); + + unsafe void calculate_window(float* window, window_function func, WindowFunction flag) + { + if ((eparams.window_function & flag) == 0 || _windowcount == lpc.MAX_LPC_WINDOWS) + return; + + func(window + _windowcount * _windowsize, _windowsize); + //int sz = _windowsize; + //float* pos = window + _windowcount * FLACCLWriter.MAX_BLOCKSIZE * 2; + //do + //{ + // func(pos, sz); + // if ((sz & 1) != 0) + // break; + // pos += sz; + // sz >>= 1; + //} while (sz >= 32); + _windowcount++; + } + + unsafe void initializeSubframeTasks(int blocksize, int channelsCount, int nFrames, FLACCLTask task) + { + task.nResidualTasks = 0; + task.nTasksPerWindow = Math.Min(32, eparams.orders_per_window); + task.nResidualTasksPerChannel = _windowcount * task.nTasksPerWindow + 1 + (eparams.do_constant ? 1 : 0) + eparams.max_fixed_order - eparams.min_fixed_order; + if (task.nResidualTasksPerChannel >= 4) + task.nResidualTasksPerChannel = (task.nResidualTasksPerChannel + 7) & ~7; + task.nAutocorTasksPerChannel = _windowcount; + for (int iFrame = 0; iFrame < nFrames; iFrame++) + { + for (int ch = 0; ch < channelsCount; ch++) + { + for (int iWindow = 0; iWindow < _windowcount; iWindow++) + { + // LPC tasks + for (int order = 0; order < task.nTasksPerWindow; order++) + { + task.ResidualTasks[task.nResidualTasks].type = (int)SubframeType.LPC; + task.ResidualTasks[task.nResidualTasks].channel = ch; + task.ResidualTasks[task.nResidualTasks].obits = (int)bits_per_sample + (channels == 2 && ch == 3 ? 1 : 0); + task.ResidualTasks[task.nResidualTasks].abits = task.ResidualTasks[task.nResidualTasks].obits; + task.ResidualTasks[task.nResidualTasks].blocksize = blocksize; + task.ResidualTasks[task.nResidualTasks].residualOrder = order + 1; + task.ResidualTasks[task.nResidualTasks].samplesOffs = ch * FLACCLWriter.MAX_BLOCKSIZE + iFrame * blocksize; + task.ResidualTasks[task.nResidualTasks].residualOffs = task.ResidualTasks[task.nResidualTasks].samplesOffs; + task.nResidualTasks++; + } + } + // Constant frames + if (eparams.do_constant) + { + task.ResidualTasks[task.nResidualTasks].type = (int)SubframeType.Constant; + task.ResidualTasks[task.nResidualTasks].channel = ch; + task.ResidualTasks[task.nResidualTasks].obits = (int)bits_per_sample + (channels == 2 && ch == 3 ? 1 : 0); + task.ResidualTasks[task.nResidualTasks].abits = task.ResidualTasks[task.nResidualTasks].obits; + task.ResidualTasks[task.nResidualTasks].blocksize = blocksize; + task.ResidualTasks[task.nResidualTasks].samplesOffs = ch * FLACCLWriter.MAX_BLOCKSIZE + iFrame * blocksize; + task.ResidualTasks[task.nResidualTasks].residualOffs = task.ResidualTasks[task.nResidualTasks].samplesOffs; + task.ResidualTasks[task.nResidualTasks].residualOrder = 1; + task.ResidualTasks[task.nResidualTasks].shift = 0; + task.ResidualTasks[task.nResidualTasks].coefs[0] = 1; + task.nResidualTasks++; + } + // Fixed prediction + for (int order = eparams.min_fixed_order; order <= eparams.max_fixed_order; order++) + { + task.ResidualTasks[task.nResidualTasks].type = (int)SubframeType.Fixed; + task.ResidualTasks[task.nResidualTasks].channel = ch; + task.ResidualTasks[task.nResidualTasks].obits = (int)bits_per_sample + (channels == 2 && ch == 3 ? 1 : 0); + task.ResidualTasks[task.nResidualTasks].abits = task.ResidualTasks[task.nResidualTasks].obits; + task.ResidualTasks[task.nResidualTasks].blocksize = blocksize; + task.ResidualTasks[task.nResidualTasks].residualOrder = order; + task.ResidualTasks[task.nResidualTasks].samplesOffs = ch * FLACCLWriter.MAX_BLOCKSIZE + iFrame * blocksize; + task.ResidualTasks[task.nResidualTasks].residualOffs = task.ResidualTasks[task.nResidualTasks].samplesOffs; + task.ResidualTasks[task.nResidualTasks].shift = 0; + switch (order) + { + case 0: + break; + case 1: + task.ResidualTasks[task.nResidualTasks].coefs[0] = 1; + break; + case 2: + task.ResidualTasks[task.nResidualTasks].coefs[1] = 2; + task.ResidualTasks[task.nResidualTasks].coefs[0] = -1; + break; + case 3: + task.ResidualTasks[task.nResidualTasks].coefs[2] = 3; + task.ResidualTasks[task.nResidualTasks].coefs[1] = -3; + task.ResidualTasks[task.nResidualTasks].coefs[0] = 1; + break; + case 4: + task.ResidualTasks[task.nResidualTasks].coefs[3] = 4; + task.ResidualTasks[task.nResidualTasks].coefs[2] = -6; + task.ResidualTasks[task.nResidualTasks].coefs[1] = 4; + task.ResidualTasks[task.nResidualTasks].coefs[0] = -1; + break; + } + task.nResidualTasks++; + } + // Filler + while ((task.nResidualTasks % task.nResidualTasksPerChannel) != 0) + { + task.ResidualTasks[task.nResidualTasks].type = (int)SubframeType.Verbatim; + task.ResidualTasks[task.nResidualTasks].channel = ch; + task.ResidualTasks[task.nResidualTasks].obits = (int)bits_per_sample + (channels == 2 && ch == 3 ? 1 : 0); + task.ResidualTasks[task.nResidualTasks].abits = task.ResidualTasks[task.nResidualTasks].obits; + task.ResidualTasks[task.nResidualTasks].blocksize = blocksize; + task.ResidualTasks[task.nResidualTasks].residualOrder = 0; + task.ResidualTasks[task.nResidualTasks].samplesOffs = ch * FLACCLWriter.MAX_BLOCKSIZE + iFrame * blocksize; + task.ResidualTasks[task.nResidualTasks].residualOffs = task.ResidualTasks[task.nResidualTasks].samplesOffs; + task.ResidualTasks[task.nResidualTasks].shift = 0; + task.nResidualTasks++; + } + } + } + if (sizeof(FLACCLSubframeTask) * task.nResidualTasks > task.residualTasksLen) + throw new Exception("oops"); + task.openCLCQ.EnqueueWriteBuffer(task.cudaResidualTasks, true, 0, sizeof(FLACCLSubframeTask) * task.nResidualTasks, task.residualTasksPtr.AddrOfPinnedObject()); + task.openCLCQ.EnqueueBarrier(); + + task.frameSize = blocksize; + } + + unsafe void encode_residual(FLACCLTask task) + { + bool unpacked = false; + unpack_samples(task, Math.Min(32, task.frameSize)); + for (int ch = 0; ch < channels; ch++) + { + switch (task.frame.subframes[ch].best.type) + { + case SubframeType.Constant: + break; + case SubframeType.Verbatim: + if (!unpacked) unpack_samples(task, task.frameSize); unpacked = true; + break; + case SubframeType.Fixed: + // if (!_settings.GPUOnly) + { + if (!unpacked) unpack_samples(task, task.frameSize); unpacked = true; + encode_residual_fixed(task.frame.subframes[ch].best.residual, task.frame.subframes[ch].samples, + task.frame.blocksize, task.frame.subframes[ch].best.order); + + int pmin = get_max_p_order(eparams.min_partition_order, task.frame.blocksize, task.frame.subframes[ch].best.order); + int pmax = get_max_p_order(eparams.max_partition_order, task.frame.blocksize, task.frame.subframes[ch].best.order); + uint bits = (uint)(task.frame.subframes[ch].best.order * task.frame.subframes[ch].obits) + 6; + task.frame.subframes[ch].best.size = bits + calc_rice_params(task.frame.subframes[ch].best.rc, pmin, pmax, task.frame.subframes[ch].best.residual, (uint)task.frame.blocksize, (uint)task.frame.subframes[ch].best.order); + } + break; + case SubframeType.LPC: + fixed (int* coefs = task.frame.subframes[ch].best.coefs) + { + ulong csum = 0; + for (int i = task.frame.subframes[ch].best.order; i > 0; i--) + csum += (ulong)Math.Abs(coefs[i - 1]); + // if ((csum << task.frame.subframes[ch].obits) >= 1UL << 32 || !_settings.GPUOnly) + { + if (!unpacked) unpack_samples(task, task.frameSize); unpacked = true; + if ((csum << task.frame.subframes[ch].obits) >= 1UL << 32) + lpc.encode_residual_long(task.frame.subframes[ch].best.residual, task.frame.subframes[ch].samples, task.frame.blocksize, task.frame.subframes[ch].best.order, coefs, task.frame.subframes[ch].best.shift); + else + lpc.encode_residual(task.frame.subframes[ch].best.residual, task.frame.subframes[ch].samples, task.frame.blocksize, task.frame.subframes[ch].best.order, coefs, task.frame.subframes[ch].best.shift); + int pmin = get_max_p_order(eparams.min_partition_order, task.frame.blocksize, task.frame.subframes[ch].best.order); + int pmax = get_max_p_order(eparams.max_partition_order, task.frame.blocksize, task.frame.subframes[ch].best.order); + uint bits = (uint)(task.frame.subframes[ch].best.order * task.frame.subframes[ch].obits) + 4 + 5 + (uint)task.frame.subframes[ch].best.order * (uint)task.frame.subframes[ch].best.cbits + 6; + //uint oldsize = task.frame.subframes[ch].best.size; + task.frame.subframes[ch].best.size = bits + calc_rice_params(task.frame.subframes[ch].best.rc, pmin, pmax, task.frame.subframes[ch].best.residual, (uint)task.frame.blocksize, (uint)task.frame.subframes[ch].best.order); + //if (task.frame.subframes[ch].best.size > task.frame.subframes[ch].obits * (uint)task.frame.blocksize && + // oldsize <= task.frame.subframes[ch].obits * (uint)task.frame.blocksize) + // throw new Exception("oops"); + } + } + break; + } + if (task.frame.subframes[ch].best.size > task.frame.subframes[ch].obits * task.frame.blocksize) + { +#if DEBUG + throw new Exception("larger than verbatim"); +#endif + task.frame.subframes[ch].best.type = SubframeType.Verbatim; + task.frame.subframes[ch].best.size = (uint)(task.frame.subframes[ch].obits * task.frame.blocksize); + if (!unpacked) unpack_samples(task, task.frameSize); unpacked = true; + } + } + } + + unsafe void select_best_methods(FlacFrame frame, int channelsCount, int iFrame, FLACCLTask task) + { + if (channelsCount == 4 && channels == 2) + { + if (task.BestResidualTasks[iFrame * 2].channel == 0 && task.BestResidualTasks[iFrame * 2 + 1].channel == 1) + frame.ch_mode = ChannelMode.LeftRight; + else if (task.BestResidualTasks[iFrame * 2].channel == 0 && task.BestResidualTasks[iFrame * 2 + 1].channel == 3) + frame.ch_mode = ChannelMode.LeftSide; + else if (task.BestResidualTasks[iFrame * 2].channel == 3 && task.BestResidualTasks[iFrame * 2 + 1].channel == 1) + frame.ch_mode = ChannelMode.RightSide; + else if (task.BestResidualTasks[iFrame * 2].channel == 2 && task.BestResidualTasks[iFrame * 2 + 1].channel == 3) + frame.ch_mode = ChannelMode.MidSide; + else + throw new Exception("internal error: invalid stereo mode"); + frame.SwapSubframes(0, task.BestResidualTasks[iFrame * 2].channel); + frame.SwapSubframes(1, task.BestResidualTasks[iFrame * 2 + 1].channel); + } + else + frame.ch_mode = channels != 2 ? ChannelMode.NotStereo : ChannelMode.LeftRight; + + for (int ch = 0; ch < channels; ch++) + { + int index = ch + iFrame * channels; + frame.subframes[ch].best.residual = ((int*)task.residualBufferPtr.AddrOfPinnedObject()) + task.BestResidualTasks[index].residualOffs; + frame.subframes[ch].best.type = SubframeType.Verbatim; + frame.subframes[ch].best.size = (uint)(frame.subframes[ch].obits * frame.blocksize); + frame.subframes[ch].wbits = 0; + + if (task.BestResidualTasks[index].size < 0) + throw new Exception("internal error"); + if (frame.blocksize > Math.Max(4, eparams.max_prediction_order) && frame.subframes[ch].best.size > task.BestResidualTasks[index].size) + { + frame.subframes[ch].best.type = (SubframeType)task.BestResidualTasks[index].type; + frame.subframes[ch].best.size = (uint)task.BestResidualTasks[index].size; + frame.subframes[ch].best.order = task.BestResidualTasks[index].residualOrder; + frame.subframes[ch].best.cbits = task.BestResidualTasks[index].cbits; + frame.subframes[ch].best.shift = task.BestResidualTasks[index].shift; + frame.subframes[ch].obits -= task.BestResidualTasks[index].wbits; + frame.subframes[ch].wbits = task.BestResidualTasks[index].wbits; + frame.subframes[ch].best.rc.porder = task.BestResidualTasks[index].porder; + for (int i = 0; i < task.BestResidualTasks[index].residualOrder; i++) + frame.subframes[ch].best.coefs[i] = task.BestResidualTasks[index].coefs[task.BestResidualTasks[index].residualOrder - 1 - i]; + //if (_settings.GPUOnly && (frame.subframes[ch].best.type == SubframeType.Fixed || frame.subframes[ch].best.type == SubframeType.LPC)) + //{ + // int* riceParams = ((int*)task.bestRiceParamsPtr.AddrOfPinnedObject()) + (index << task.max_porder); + // fixed (int* dstParams = frame.subframes[ch].best.rc.rparams) + // AudioSamples.MemCpy(dstParams, riceParams, (1 << frame.subframes[ch].best.rc.porder)); + // //for (int i = 0; i < (1 << frame.subframes[ch].best.rc.porder); i++) + // // frame.subframes[ch].best.rc.rparams[i] = riceParams[i]; + //} + } + } + } + + unsafe void estimate_residual(FLACCLTask task, int channelsCount) + { + if (task.frameSize <= 4) + return; + + //int autocorPartSize = (2 * 256 - eparams.max_prediction_order) & ~15; + int autocorPartSize = 32 * 7; + int autocorPartCount = (task.frameSize + autocorPartSize - 1) / autocorPartSize; + if (autocorPartCount > maxAutocorParts) + throw new Exception("internal error"); + + int max_porder = get_max_p_order(eparams.max_partition_order, task.frameSize, eparams.max_prediction_order); + int calcPartitionPartSize = task.frameSize >> max_porder; + while (calcPartitionPartSize < 16 && max_porder > 0) + { + calcPartitionPartSize <<= 1; + max_porder--; + } + int calcPartitionPartCount = (calcPartitionPartSize >= 128) ? 1 : (256 / calcPartitionPartSize); + + if (channels != 2) throw new Exception("channels != 2"); // need to Enqueue cudaChannelDecorr for each channel + Kernel cudaChannelDecorr = channels == 2 ? (channelsCount == 4 ? task.cudaStereoDecorr : task.cudaChannelDecorr2) : null;// task.cudaChannelDecorr; + //Kernel cudaCalcPartition = calcPartitionPartSize >= 128 ? task.cudaCalcLargePartition : calcPartitionPartSize == 16 && task.frameSize >= 256 ? task.cudaCalcPartition16 : task.cudaCalcPartition; + + cudaChannelDecorr.SetArg(0, task.cudaSamples); + cudaChannelDecorr.SetArg(1, task.cudaSamplesBytes); + cudaChannelDecorr.SetArg(2, (uint)MAX_BLOCKSIZE); + + task.cudaComputeLPC.SetArg(0, task.cudaResidualTasks); + task.cudaComputeLPC.SetArg(1, (uint)task.nResidualTasksPerChannel); + task.cudaComputeLPC.SetArg(2, task.cudaAutocorOutput); + task.cudaComputeLPC.SetArg(3, (uint)eparams.max_prediction_order); + task.cudaComputeLPC.SetArg(4, task.cudaLPCData); + task.cudaComputeLPC.SetArg(5, (uint)_windowcount); + task.cudaComputeLPC.SetArg(6, (uint)autocorPartCount); + + //task.cudaComputeLPCLattice.SetArg(0, task.cudaResidualTasks); + //task.cudaComputeLPCLattice.SetArg(1, (uint)task.nResidualTasksPerChannel); + //task.cudaComputeLPCLattice.SetArg(2, task.cudaSamples); + //task.cudaComputeLPCLattice.SetArg(3, (uint)_windowcount); + //task.cudaComputeLPCLattice.SetArg(4, (uint)eparams.max_prediction_order); + //task.cudaComputeLPCLattice.SetArg(5, task.cudaLPCData); + //cuda.SetFunctionBlockShape(task.cudaComputeLPCLattice, 256, 1, 1); + + task.cudaQuantizeLPC.SetArg(0, task.cudaResidualTasks); + task.cudaQuantizeLPC.SetArg(1, (uint)task.nResidualTasksPerChannel); + task.cudaQuantizeLPC.SetArg(2, (uint)task.nTasksPerWindow); + task.cudaQuantizeLPC.SetArg(3, task.cudaLPCData); + task.cudaQuantizeLPC.SetArg(4, (uint)eparams.max_prediction_order); + task.cudaQuantizeLPC.SetArg(5, (uint)eparams.lpc_min_precision_search); + task.cudaQuantizeLPC.SetArg(6, (uint)(eparams.lpc_max_precision_search - eparams.lpc_min_precision_search)); + + task.cudaCopyBestMethod.SetArg(0, task.cudaBestResidualTasks); + task.cudaCopyBestMethod.SetArg(1, task.cudaResidualTasks); + task.cudaCopyBestMethod.SetArg(2, (uint)task.nResidualTasksPerChannel); + + task.cudaCopyBestMethodStereo.SetArg(0, task.cudaBestResidualTasks); + task.cudaCopyBestMethodStereo.SetArg(1, task.cudaResidualTasks); + task.cudaCopyBestMethodStereo.SetArg(2, (uint)task.nResidualTasksPerChannel); + + //task.cudaEncodeResidual.SetArg(0, task.cudaResidual); + //task.cudaEncodeResidual.SetArg(1, task.cudaSamples); + //task.cudaEncodeResidual.SetArg(2, task.cudaBestResidualTasks); + //cuda.SetFunctionBlockShape(task.cudaEncodeResidual, residualPartSize, 1, 1); + + //cudaCalcPartition.SetArg(0, task.cudaPartitions); + //cudaCalcPartition.SetArg(1, task.cudaResidual); + //cudaCalcPartition.SetArg(2, task.cudaSamples); + //cudaCalcPartition.SetArg(3, task.cudaBestResidualTasks); + //cudaCalcPartition.SetArg(4, (uint)max_porder); + //cudaCalcPartition.SetArg(5, (uint)calcPartitionPartSize); + //cudaCalcPartition.SetArg(6, (uint)calcPartitionPartCount); + //cuda.SetFunctionBlockShape(cudaCalcPartition, 16, 16, 1); + + //task.cudaSumPartition.SetArg(0, task.cudaPartitions); + //task.cudaSumPartition.SetArg(1, (uint)max_porder); + //cuda.SetFunctionBlockShape(task.cudaSumPartition, Math.Max(32, 1 << (max_porder - 1)), 1, 1); + + //task.cudaFindRiceParameter.SetArg(0, task.cudaRiceParams); + //task.cudaFindRiceParameter.SetArg(1, task.cudaPartitions); + //task.cudaFindRiceParameter.SetArg(2, (uint)max_porder); + //cuda.SetFunctionBlockShape(task.cudaFindRiceParameter, 32, 8, 1); + + //task.cudaFindPartitionOrder.SetArg(0, task.cudaBestRiceParams); + //task.cudaFindPartitionOrder.SetArg(1, task.cudaBestResidualTasks); + //task.cudaFindPartitionOrder.SetArg(2, task.cudaRiceParams); + //task.cudaFindPartitionOrder.SetArg(3, (uint)max_porder); + //cuda.SetFunctionBlockShape(task.cudaFindPartitionOrder, 256, 1, 1); + + + // issue work to the GPU + task.openCLCQ.EnqueueBarrier(); + task.openCLCQ.EnqueueNDRangeKernel(cudaChannelDecorr, 1, null, new int[] { task.frameCount * task.frameSize }, null ); + //task.openCLCQ.EnqueueNDRangeKernel(cudaChannelDecorr, 1, null, new int[] { 64 * 128 }, new int[] { 128 }); + //cuda.SetFunctionBlockShape(cudaChannelDecorr, 256, 1, 1); + //cuda.LaunchAsync(cudaChannelDecorr, (task.frameCount * task.frameSize + 255) / 256, channels == 2 ? 1 : channels, task.stream); + + if (eparams.do_wasted) + { + task.openCLCQ.EnqueueBarrier(); + task.EnqueueFindWasted(channelsCount); + } + + // geometry??? + task.openCLCQ.EnqueueBarrier(); + task.EnqueueComputeAutocor(autocorPartCount, channelsCount, cudaWindow, eparams.max_prediction_order); + + //float* autoc = stackalloc float[1024]; + //task.openCLCQ.EnqueueBarrier(); + //task.openCLCQ.EnqueueReadBuffer(task.cudaAutocorOutput, true, 0, sizeof(float) * 1024, (IntPtr)autoc); + + task.openCLCQ.EnqueueBarrier(); + task.openCLCQ.EnqueueNDRangeKernel(task.cudaComputeLPC, 2, null, new int[] { task.nAutocorTasksPerChannel * 32, channelsCount * task.frameCount }, new int[] { 32, 1 }); + //cuda.SetFunctionBlockShape(task.cudaComputeLPC, 32, 1, 1); + + //float* lpcs = stackalloc float[1024]; + //task.openCLCQ.EnqueueBarrier(); + //task.openCLCQ.EnqueueReadBuffer(task.cudaLPCData, true, 0, sizeof(float) * 1024, (IntPtr)lpcs); + + task.openCLCQ.EnqueueBarrier(); + task.openCLCQ.EnqueueNDRangeKernel(task.cudaQuantizeLPC, 2, null, new int[] { task.nAutocorTasksPerChannel * 32, channelsCount * task.frameCount * 4 }, new int[] { 32, 4 }); + //cuda.SetFunctionBlockShape(task.cudaQuantizeLPC, 32, 4, 1); + + task.openCLCQ.EnqueueBarrier(); + task.EnqueueEstimateResidual(channelsCount, eparams.max_prediction_order); + + //int* rr = stackalloc int[1024]; + //task.openCLCQ.EnqueueBarrier(); + //task.openCLCQ.EnqueueReadBuffer(task.cudaResidualOutput, true, 0, sizeof(int) * 1024, (IntPtr)rr); + + task.openCLCQ.EnqueueBarrier(); + task.EnqueueChooseBestMethod(channelsCount); + + task.openCLCQ.EnqueueBarrier(); + if (channels == 2 && channelsCount == 4) + task.openCLCQ.EnqueueNDRangeKernel(task.cudaCopyBestMethodStereo, 2, null, new int[] { 64, task.frameCount }, new int[] { 64, 1 }); + //cuda.SetFunctionBlockShape(task.cudaCopyBestMethodStereo, 64, 1, 1); + else + task.openCLCQ.EnqueueNDRangeKernel(task.cudaCopyBestMethod, 2, null, new int[] { 64, channels * task.frameCount }, new int[] { 64, 1 }); + //cuda.SetFunctionBlockShape(task.cudaCopyBestMethod, 64, 1, 1); + //if (_settings.GPUOnly) + //{ + // int bsz = calcPartitionPartCount * calcPartitionPartSize; + // if (cudaCalcPartition.Pointer == task.cudaCalcLargePartition.Pointer) + // cuda.LaunchAsync(task.cudaEncodeResidual, residualPartCount, channels * task.frameCount, task.stream); + // cuda.LaunchAsync(cudaCalcPartition, (task.frameSize + bsz - 1) / bsz, channels * task.frameCount, task.stream); + // if (max_porder > 0) + // cuda.LaunchAsync(task.cudaSumPartition, Flake.MAX_RICE_PARAM + 1, channels * task.frameCount, task.stream); + // cuda.LaunchAsync(task.cudaFindRiceParameter, ((2 << max_porder) + 31) / 32, channels * task.frameCount, task.stream); + // //if (max_porder > 0) // need to run even if max_porder==0 just to calculate the final frame size + // cuda.LaunchAsync(task.cudaFindPartitionOrder, 1, channels * task.frameCount, task.stream); + // cuda.CopyDeviceToHostAsync(task.cudaResidual, task.residualBufferPtr, (uint)(sizeof(int) * MAX_BLOCKSIZE * channels), task.stream); + // cuda.CopyDeviceToHostAsync(task.cudaBestRiceParams, task.bestRiceParamsPtr, (uint)(sizeof(int) * (1 << max_porder) * channels * task.frameCount), task.stream); + // task.max_porder = max_porder; + //} + task.openCLCQ.EnqueueBarrier(); + task.openCLCQ.EnqueueReadBuffer(task.cudaBestResidualTasks, false, 0, sizeof(FLACCLSubframeTask) * channels * task.frameCount, task.bestResidualTasksPtr.AddrOfPinnedObject()); + //task.openCLCQ.EnqueueBarrier(); + //task.openCLCQ.EnqueueReadBuffer(task.cudaResidualTasks, true, 0, sizeof(FLACCLSubframeTask) * task.nResidualTasks, task.residualTasksPtr.AddrOfPinnedObject()); + //task.openCLCQ.EnqueueBarrier(); + } + + /// + /// Copy channel-interleaved input samples into separate subframes + /// + /// + /// + unsafe void unpack_samples(FLACCLTask task, int count) + { + int iFrame = task.frame.frame_number; + short* src = ((short*)task.samplesBytesPtr.AddrOfPinnedObject()) + iFrame * channels * task.frameSize; + + switch (task.frame.ch_mode) + { + case ChannelMode.NotStereo: + for (int ch = 0; ch < channels; ch++) + { + int* s = task.frame.subframes[ch].samples; + int wbits = (int)task.frame.subframes[ch].wbits; + for (int i = 0; i < count; i++) + s[i] = src[i * channels + ch] >>= wbits; + } + break; + case ChannelMode.LeftRight: + { + int* left = task.frame.subframes[0].samples; + int* right = task.frame.subframes[1].samples; + int lwbits = (int)task.frame.subframes[0].wbits; + int rwbits = (int)task.frame.subframes[1].wbits; + for (int i = 0; i < count; i++) + { + int l = *(src++); + int r = *(src++); + left[i] = l >> lwbits; + right[i] = r >> rwbits; + } + break; + } + case ChannelMode.LeftSide: + { + int* left = task.frame.subframes[0].samples; + int* right = task.frame.subframes[1].samples; + int lwbits = (int)task.frame.subframes[0].wbits; + int rwbits = (int)task.frame.subframes[1].wbits; + for (int i = 0; i < count; i++) + { + int l = *(src++); + int r = *(src++); + left[i] = l >> lwbits; + right[i] = (l - r) >> rwbits; + } + break; + } + case ChannelMode.RightSide: + { + int* left = task.frame.subframes[0].samples; + int* right = task.frame.subframes[1].samples; + int lwbits = (int)task.frame.subframes[0].wbits; + int rwbits = (int)task.frame.subframes[1].wbits; + for (int i = 0; i < count; i++) + { + int l = *(src++); + int r = *(src++); + left[i] = (l - r) >> lwbits; + right[i] = r >> rwbits; + } + break; + } + case ChannelMode.MidSide: + { + int* left = task.frame.subframes[0].samples; + int* right = task.frame.subframes[1].samples; + int lwbits = (int)task.frame.subframes[0].wbits; + int rwbits = (int)task.frame.subframes[1].wbits; + for (int i = 0; i < count; i++) + { + int l = *(src++); + int r = *(src++); + left[i] = (l + r) >> (1 + lwbits); + right[i] = (l - r) >> rwbits; + } + break; + } + } + } + + unsafe int encode_frame(bool doMidside, int channelCount, int iFrame, FLACCLTask task, int current_frame_number) + { + task.frame.InitSize(task.frameSize, eparams.variable_block_size != 0); + task.frame.frame_number = iFrame; + task.frame.ch_mode = ChannelMode.NotStereo; + + fixed (int* smp = task.samplesBuffer) + { + for (int ch = 0; ch < channelCount; ch++) + task.frame.subframes[ch].Init( + smp + ch * FLACCLWriter.MAX_BLOCKSIZE + iFrame * task.frameSize, + ((int*)task.residualBufferPtr.AddrOfPinnedObject()) + ch * FLACCLWriter.MAX_BLOCKSIZE + iFrame * task.frameSize, + _pcm.BitsPerSample + (doMidside && ch == 3 ? 1 : 0), 0); + + select_best_methods(task.frame, channelCount, iFrame, task); + //unpack_samples(task); + encode_residual(task); + + //task.frame.writer.Reset(); + task.frame.frame_number = current_frame_number; + task.frame.writer_offset = task.frame.writer.Length; + + output_frame_header(task.frame); + output_subframes(task.frame); + output_frame_footer(task.frame); + if (task.frame.writer.Length - task.frame.writer_offset >= max_frame_size) + throw new Exception("buffer overflow"); + + return task.frame.writer.Length - task.frame.writer_offset; + } + } + + unsafe void send_to_GPU(FLACCLTask task, int nFrames, int blocksize) + { + bool doMidside = channels == 2 && eparams.do_midside; + int channelsCount = doMidside ? 2 * channels : channels; + if (blocksize != task.frameSize) + task.nResidualTasks = 0; + task.frameCount = nFrames; + task.frameSize = blocksize; + task.frameNumber = eparams.variable_block_size > 0 ? frame_pos : frame_count; + task.framePos = frame_pos; + frame_count += nFrames; + frame_pos += nFrames * blocksize; + task.openCLCQ.EnqueueWriteBuffer(task.cudaSamplesBytes, false, 0, sizeof(short) * channels * blocksize * nFrames, task.samplesBytesPtr.AddrOfPinnedObject()); + task.openCLCQ.EnqueueBarrier(); + } + + unsafe void run_GPU_task(FLACCLTask task) + { + bool doMidside = channels == 2 && eparams.do_midside; + int channelsCount = doMidside ? 2 * channels : channels; + + if (task.frameSize != _windowsize && task.frameSize > 4) + fixed (float* window = windowBuffer) + { + _windowsize = task.frameSize; + _windowcount = 0; + calculate_window(window, lpc.window_welch, WindowFunction.Welch); + calculate_window(window, lpc.window_flattop, WindowFunction.Flattop); + calculate_window(window, lpc.window_tukey, WindowFunction.Tukey); + calculate_window(window, lpc.window_hann, WindowFunction.Hann); + calculate_window(window, lpc.window_bartlett, WindowFunction.Bartlett); + if (_windowcount == 0) + throw new Exception("invalid windowfunction"); + task.openCLCQ.EnqueueWriteBuffer(cudaWindow, true, 0, sizeof(float) * windowBuffer.Length, (IntPtr)window); + task.openCLCQ.EnqueueBarrier(); + } + if (task.nResidualTasks == 0) + initializeSubframeTasks(task.frameSize, channelsCount, max_frames, task); + + estimate_residual(task, channelsCount); + } + + unsafe void process_result(FLACCLTask task) + { + bool doMidside = channels == 2 && eparams.do_midside; + int channelCount = doMidside ? 2 * channels : channels; + + long iSample = 0; + long iByte = 0; + task.frame.writer.Reset(); + task.frame.writer_offset = 0; + for (int iFrame = 0; iFrame < task.frameCount; iFrame++) + { + //if (0 != eparams.variable_block_size && 0 == (task.blocksize & 7) && task.blocksize >= 128) + // fs = encode_frame_vbs(); + //else + int fn = task.frameNumber + (eparams.variable_block_size > 0 ? (int)iSample : iFrame); + int fs = encode_frame(doMidside, channelCount, iFrame, task, fn); + + if (task.verify != null) + { + int decoded = task.verify.DecodeFrame(task.frame.writer.Buffer, task.frame.writer_offset, fs); + if (decoded != fs || task.verify.Remaining != task.frameSize) + throw new Exception("validation failed! frame size mismatch"); + fixed (int* r = task.verify.Samples) + { + for (int ch = 0; ch < channels; ch++) + { + short* res = ((short*)task.samplesBytesPtr.AddrOfPinnedObject()) + iFrame * channels * task.frameSize + ch; + int* smp = r + ch * Flake.MAX_BLOCKSIZE; + for (int i = task.frameSize; i > 0; i--) + { + //if (AudioSamples.MemCmp(s + iFrame * task.frameSize + ch * FLACCLWriter.MAX_BLOCKSIZE, r + ch * Flake.MAX_BLOCKSIZE, task.frameSize)) + if (*res != *(smp++)) + throw new Exception(string.Format("validation failed! iFrame={0}, ch={1}", iFrame, ch)); + res += channels; + } + } + } + } + + 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 >= task.framePos + iSample + task.frameSize) + break; + if (seek_table[sp].number >= task.framePos + iSample) + { + seek_table[sp].number = task.framePos + iSample; + seek_table[sp].offset = iByte; + seek_table[sp].framesize = task.frameSize; + } + } + } + + //Array.Copy(task.frame.buffer, 0, task.outputBuffer, iByte, fs); + + iSample += task.frameSize; + iByte += fs; + } + task.outputSize = (int)iByte; + if (iByte != task.frame.writer.Length) + throw new Exception("invalid length"); + } + + unsafe void write_result(FLACCLTask task) + { + int iSample = task.frameSize * task.frameCount; + + if (seek_table != null && _IO.CanSeek) + for (int sp = 0; sp < seek_table.Length; sp++) + { + if (seek_table[sp].number >= task.framePos + iSample) + break; + if (seek_table[sp].number >= task.framePos) + seek_table[sp].offset += _IO.Position - first_frame_offset; + } + _IO.Write(task.outputBuffer, 0, task.outputSize); + _position += iSample; + _totalSize += task.outputSize; + } + + public unsafe void InitTasks() + { + bool doMidside = channels == 2 && eparams.do_midside; + int channelCount = doMidside ? 2 * channels : channels; + + if (!inited) + { + if (OpenCL.NumberOfPlatforms < 1) + throw new Exception("no opencl platforms found"); + + OCLMan = new OpenCLManager(); + // Attempt to save binaries after compilation, as well as load precompiled binaries + // to avoid compilation. Usually you'll want this to be true. + OCLMan.AttemptUseBinaries = false; // true; + // Attempt to compile sources. This should probably be true for almost all projects. + // Setting it to false means that when you attempt to compile "mysource.cl", it will + // only scan the precompiled binary directory for a binary corresponding to a source + // with that name. There's a further restriction that the compiled binary also has to + // use the same Defines and BuildOptions + OCLMan.AttemptUseSource = true; + // Binary and source paths + // This is where we store our sources and where compiled binaries are placed + //OCLMan.BinaryPath = @"OpenCL\bin"; + //OCLMan.SourcePath = @"OpenCL\src"; + // If true, RequireImageSupport will filter out any devices without image support + // In this project we don't need image support though, so we set it to false + OCLMan.RequireImageSupport = false; + // The Defines string gets prepended to any and all sources that are compiled + // and serve as a convenient way to pass configuration information to the compilation process + OCLMan.Defines = "#define MAX_ORDER " + eparams.max_prediction_order.ToString(); + // The BuildOptions string is passed directly to clBuild and can be used to do debug builds etc + OCLMan.BuildOptions = ""; + + OCLMan.CreateDefaultContext(0, DeviceType.GPU); + + openCLContext = OCLMan.Context; + //try + //{ + // openCLProgram = OCLMan.CompileFile("flac.cl"); + //} + //catch (OpenCLBuildException ex) + //{ + // string buildLog = ex.BuildLogs[0]; + // throw ex; + //} + using (Stream kernel = GetType().Assembly.GetManifestResourceStream(GetType(), "flac.cl")) + using (StreamReader sr = new StreamReader(kernel)) + { + try + { + openCLProgram = OCLMan.CompileSource(sr.ReadToEnd()); ; + } + catch (OpenCLBuildException ex) + { + string buildLog = ex.BuildLogs[0]; + throw ex; + } + } +#if TTTTKJHSKJH + var openCLPlatform = OpenCL.GetPlatform(0); + openCLContext = openCLPlatform.CreateDefaultContext(); + using (Stream kernel = GetType().Assembly.GetManifestResourceStream(GetType(), "flac.cl")) + using (StreamReader sr = new StreamReader(kernel)) + openCLProgram = openCLContext.CreateProgramWithSource(sr.ReadToEnd()); + try + { + openCLProgram.Build(); + } + catch (OpenCLException) + { + string buildLog = openCLProgram.GetBuildLog(openCLProgram.Devices[0]); + throw; + } +#endif + + if (_IO == null) + _IO = new FileStream(_path, FileMode.Create, FileAccess.Write, FileShare.Read); + int header_size = flake_encode_init(); + _IO.Write(header, 0, header_size); + if (_IO.CanSeek) + first_frame_offset = _IO.Position; + + task1 = new FLACCLTask(openCLProgram, channelCount, channels, bits_per_sample, max_frame_size, _settings.DoVerify); + task2 = new FLACCLTask(openCLProgram, channelCount, channels, bits_per_sample, max_frame_size, _settings.DoVerify); + if (_settings.CPUThreads > 0) + { + cpu_tasks = new FLACCLTask[_settings.CPUThreads]; + for (int i = 0; i < cpu_tasks.Length; i++) + cpu_tasks[i] = new FLACCLTask(openCLProgram, channelCount, channels, bits_per_sample, max_frame_size, _settings.DoVerify); + } + cudaWindow = openCLProgram.Context.CreateBuffer(MemFlags.READ_ONLY, sizeof(float) * FLACCLWriter.MAX_BLOCKSIZE * 2 * lpc.MAX_LPC_WINDOWS); + + inited = true; + } + } + + public unsafe void Write(AudioBuffer buff) + { + InitTasks(); + buff.Prepare(this); + int pos = 0; + while (pos < buff.Length) + { + int block = Math.Min(buff.Length - pos, eparams.block_size * max_frames - samplesInBuffer); + + fixed (byte* buf = buff.Bytes) + AudioSamples.MemCpy(((byte*)task1.samplesBytesPtr.AddrOfPinnedObject()) + samplesInBuffer * _pcm.BlockAlign, buf + pos * _pcm.BlockAlign, block * _pcm.BlockAlign); + + samplesInBuffer += block; + pos += block; + + int nFrames = samplesInBuffer / eparams.block_size; + if (nFrames >= max_frames) + do_output_frames(nFrames); + } + if (md5 != null) + md5.TransformBlock(buff.Bytes, 0, buff.ByteLength, null, 0); + } + + public void wait_for_cpu_task() + { + FLACCLTask task = cpu_tasks[oldest_cpu_task]; + if (task.workThread == null) + return; + lock (task) + { + while (!task.done && task.exception == null) + Monitor.Wait(task); + if (task.exception != null) + throw task.exception; + } + } + + public void cpu_task_thread(object param) + { + FLACCLTask task = param as FLACCLTask; + try + { + while (true) + { + lock (task) + { + while (task.done && !task.exit) + Monitor.Wait(task); + if (task.exit) + return; + } + process_result(task); + lock (task) + { + task.done = true; + Monitor.Pulse(task); + } + } + } + catch (Exception ex) + { + lock (task) + { + task.exception = ex; + Monitor.Pulse(task); + } + } + } + + public void start_cpu_task() + { + FLACCLTask task = cpu_tasks[oldest_cpu_task]; + if (task.workThread == null) + { + task.done = false; + task.exit = false; + task.workThread = new Thread(cpu_task_thread); + task.workThread.IsBackground = true; + //task.workThread.Priority = ThreadPriority.BelowNormal; + task.workThread.Start(task); + } + else + { + lock (task) + { + task.done = false; + Monitor.Pulse(task); + } + } + } + + public unsafe void do_output_frames(int nFrames) + { + send_to_GPU(task1, nFrames, eparams.block_size); + if (task2.frameCount > 0) + task2.openCLCQ.Finish(); + run_GPU_task(task1); + if (task2.frameCount > 0) + { + if (cpu_tasks != null) + { + wait_for_cpu_task(); + + FLACCLTask ttmp = cpu_tasks[oldest_cpu_task]; + cpu_tasks[oldest_cpu_task] = task2; + task2 = ttmp; + + start_cpu_task(); + + oldest_cpu_task = (oldest_cpu_task + 1) % cpu_tasks.Length; + + if (task2.frameCount > 0) + write_result(task2); + } + else + { + process_result(task2); + write_result(task2); + } + } + int bs = eparams.block_size * nFrames; + samplesInBuffer -= bs; + if (samplesInBuffer > 0) + AudioSamples.MemCpy( + ((byte*)task2.samplesBytesPtr.AddrOfPinnedObject()), + ((byte*)task1.samplesBytesPtr.AddrOfPinnedObject()) + bs * _pcm.BlockAlign, + samplesInBuffer * _pcm.BlockAlign); + FLACCLTask tmp = task1; + task1 = task2; + task2 = tmp; + task1.frameCount = 0; + } + + public string Path { get { return _path; } } + + public static readonly string vendor_string = "FLACCL#.91"; + + int select_blocksize(int samplerate, int time_ms) + { + int blocksize = Flake.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 = 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.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.VorbisComment); + 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; + } + + 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.writebits64(Flake.FLAC__STREAM_METADATA_SEEKPOINT_SAMPLE_NUMBER_LEN, (ulong)seek_table[i].number); + bitwriter.writebits64(Flake.FLAC__STREAM_METADATA_SEEKPOINT_STREAM_OFFSET_LEN, (ulong)seek_table[i].offset); + bitwriter.writebits(Flake.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, long padlen) + { + BitWriter bitwriter = new BitWriter(padding, pos, 4); + + // metadata header + bitwriter.writebits(1, last); + bitwriter.writebits(7, (int)MetadataType.Padding); + bitwriter.writebits(24, (int)padlen); + + return (int)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 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 (_blocksize == 0) + { + if (eparams.block_size == 0) + eparams.block_size = select_blocksize(sample_rate, eparams.block_time_ms); + _blocksize = eparams.block_size; + } + else + eparams.block_size = _blocksize; + + max_frames = Math.Min(maxFrames, FLACCLWriter.MAX_BLOCKSIZE / eparams.block_size); + + // 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); + + if (_IO.CanSeek && eparams.do_seektable && sample_count > 0) + { + int seek_points_distance = sample_rate * 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 + header = new byte[eparams.padding_size + 1024 + (seek_table == null ? 0 : seek_table.Length * 18)]; + header_len = write_headers(); + + // initialize CRC & MD5 + if (_IO.CanSeek && _settings.DoMD5) + md5 = new MD5CryptoServiceProvider(); + + 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; + + // 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 bool do_midside; + + // 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 long padding_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; + + public int orders_per_window; + + // 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; + + // 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; + + // whether to try various lpc_precisions + // 0 - use only one precision + // 1 - try two precisions + public int lpc_max_precision_search; + + public int lpc_min_precision_search; + + public bool do_wasted; + + public bool do_constant; + + public WindowFunction window_function; + + public bool do_seektable; + + public int flake_set_defaults(int lvl, bool encode_on_cpu) + { + compression = lvl; + + if ((lvl < 0 || lvl > 12) && (lvl != 99)) + { + return -1; + } + + // default to level 5 params + window_function = WindowFunction.Flattop | WindowFunction.Tukey; + do_midside = true; + block_size = 0; + block_time_ms = 100; + min_fixed_order = 0; + max_fixed_order = 4; + min_prediction_order = 1; + max_prediction_order = 12; + min_partition_order = 0; + max_partition_order = 6; + variable_block_size = 0; + lpc_min_precision_search = 0; + lpc_max_precision_search = 0; + do_seektable = true; + do_wasted = true; + do_constant = true; + + // differences from level 7 + switch (lvl) + { + case 0: + do_constant = false; + do_wasted = false; + do_midside = false; + orders_per_window = 1; + max_partition_order = 4; + max_prediction_order = 7; + min_fixed_order = 2; + max_fixed_order = 2; + break; + case 1: + do_wasted = false; + do_midside = false; + window_function = WindowFunction.Bartlett; + orders_per_window = 1; + max_prediction_order = 12; + max_partition_order = 4; + break; + case 2: + do_constant = false; + window_function = WindowFunction.Bartlett; + min_fixed_order = 3; + max_fixed_order = 2; + orders_per_window = 1; + max_prediction_order = 7; + max_partition_order = 4; + break; + case 3: + window_function = WindowFunction.Bartlett; + min_fixed_order = 2; + max_fixed_order = 2; + orders_per_window = 6; + max_prediction_order = 7; + max_partition_order = 4; + break; + case 4: + min_fixed_order = 2; + max_fixed_order = 2; + orders_per_window = 3; + max_prediction_order = 8; + max_partition_order = 4; + break; + case 5: + do_constant = false; + min_fixed_order = 2; + max_fixed_order = 2; + orders_per_window = 1; + break; + case 6: + min_fixed_order = 2; + max_fixed_order = 2; + orders_per_window = 3; + break; + case 7: + min_fixed_order = 2; + max_fixed_order = 2; + orders_per_window = 7; + break; + case 8: + orders_per_window = 12; + break; + case 9: + min_fixed_order = 2; + max_fixed_order = 2; + orders_per_window = 3; + max_prediction_order = 32; + break; + case 10: + min_fixed_order = 2; + max_fixed_order = 2; + orders_per_window = 7; + max_prediction_order = 32; + break; + case 11: + min_fixed_order = 2; + max_fixed_order = 2; + orders_per_window = 11; + max_prediction_order = 32; + break; + } + + if (!encode_on_cpu) + max_partition_order = 8; + + return 0; + } + } + + unsafe struct FLACCLSubframeTask + { + public int residualOrder; + public int samplesOffs; + public int shift; + public int cbits; + public int size; + public int type; + public int obits; + public int blocksize; + public int best_index; + public int channel; + public int residualOffs; + public int wbits; + public int abits; + public int porder; + public fixed int reserved[2]; + public fixed int coefs[32]; + }; + + internal class FLACCLTask + { + Program openCLProgram; + public CommandQueue openCLCQ; + public Kernel cudaStereoDecorr; + //public Kernel cudaChannelDecorr; + public Kernel cudaChannelDecorr2; + public Kernel cudaFindWastedBits; + public Kernel cudaComputeAutocor; + public Kernel cudaComputeLPC; + //public Kernel cudaComputeLPCLattice; + public Kernel cudaQuantizeLPC; + public Kernel cudaEstimateResidual; + public Kernel cudaChooseBestMethod; + public Kernel cudaCopyBestMethod; + public Kernel cudaCopyBestMethodStereo; + //public Kernel cudaEncodeResidual; + //public Kernel cudaCalcPartition; + //public Kernel cudaCalcPartition16; + //public Kernel cudaCalcLargePartition; + //public Kernel cudaSumPartition; + //public Kernel cudaFindRiceParameter; + //public Kernel cudaFindPartitionOrder; + public Mem cudaSamplesBytes; + public Mem cudaSamples; + public Mem cudaLPCData; + public Mem cudaResidual; + public Mem cudaPartitions; + public Mem cudaRiceParams; + public Mem cudaBestRiceParams; + public Mem cudaAutocorOutput; + public Mem cudaResidualTasks; + public Mem cudaResidualOutput; + public Mem cudaBestResidualTasks; + public GCHandle samplesBytesPtr; + public GCHandle residualBufferPtr; + public GCHandle bestRiceParamsPtr; + public GCHandle residualTasksPtr; + public GCHandle bestResidualTasksPtr; + public int[] samplesBuffer; + public byte[] outputBuffer; + public int outputSize = 0; + public int frameSize = 0; + public int frameCount = 0; + public int frameNumber = 0; + public int framePos = 0; + public FlacFrame frame; + public int residualTasksLen; + public int bestResidualTasksLen; + public int samplesBufferLen; + public int nResidualTasks = 0; + public int nResidualTasksPerChannel = 0; + public int nTasksPerWindow = 0; + public int nAutocorTasksPerChannel = 0; + //public int max_porder = 0; + + public FlakeReader verify; + + public Thread workThread = null; + public Exception exception = null; + public bool done = false; + public bool exit = false; + + unsafe public FLACCLTask(Program _openCLProgram, int channelCount, int channels, uint bits_per_sample, int max_frame_size, bool do_verify) + { + openCLProgram = _openCLProgram; + Device[] openCLDevices = openCLProgram.Context.Platform.QueryDevices(DeviceType.GPU); + openCLCQ = openCLProgram.Context.CreateCommandQueue(openCLDevices[0], CommandQueueProperties.PROFILING_ENABLE); + + residualTasksLen = sizeof(FLACCLSubframeTask) * channelCount * (lpc.MAX_LPC_ORDER * lpc.MAX_LPC_WINDOWS + 8) * FLACCLWriter.maxFrames; + bestResidualTasksLen = sizeof(FLACCLSubframeTask) * channelCount * FLACCLWriter.maxFrames; + samplesBufferLen = sizeof(int) * FLACCLWriter.MAX_BLOCKSIZE * channelCount; + int partitionsLen = sizeof(int) * (30 << 8) * channelCount * FLACCLWriter.maxFrames; + int riceParamsLen = sizeof(int) * (4 << 8) * channelCount * FLACCLWriter.maxFrames; + int lpcDataLen = sizeof(float) * 32 * 33 * lpc.MAX_LPC_WINDOWS * channelCount * FLACCLWriter.maxFrames; + + cudaSamplesBytes = openCLProgram.Context.CreateBuffer(MemFlags.READ_ONLY | MemFlags.ALLOC_HOST_PTR, (uint)samplesBufferLen / 2); + cudaSamples = openCLProgram.Context.CreateBuffer(MemFlags.READ_WRITE, samplesBufferLen); + cudaResidual = openCLProgram.Context.CreateBuffer(MemFlags.READ_WRITE | MemFlags.ALLOC_HOST_PTR, samplesBufferLen); + cudaLPCData = openCLProgram.Context.CreateBuffer(MemFlags.READ_WRITE, lpcDataLen); + cudaPartitions = openCLProgram.Context.CreateBuffer(MemFlags.READ_WRITE, partitionsLen); + cudaRiceParams = openCLProgram.Context.CreateBuffer(MemFlags.READ_WRITE, riceParamsLen); + cudaBestRiceParams = openCLProgram.Context.CreateBuffer(MemFlags.READ_WRITE | MemFlags.ALLOC_HOST_PTR, riceParamsLen / 4); + cudaAutocorOutput = openCLProgram.Context.CreateBuffer(MemFlags.READ_WRITE, sizeof(float) * channelCount * lpc.MAX_LPC_WINDOWS * (lpc.MAX_LPC_ORDER + 1) * (FLACCLWriter.maxAutocorParts + FLACCLWriter.maxFrames)); + cudaResidualTasks = openCLProgram.Context.CreateBuffer(MemFlags.READ_WRITE | MemFlags.ALLOC_HOST_PTR, residualTasksLen); + cudaBestResidualTasks = openCLProgram.Context.CreateBuffer(MemFlags.READ_WRITE | MemFlags.ALLOC_HOST_PTR, bestResidualTasksLen); + cudaResidualOutput = openCLProgram.Context.CreateBuffer(MemFlags.READ_WRITE, sizeof(int) * channelCount * (lpc.MAX_LPC_WINDOWS * lpc.MAX_LPC_ORDER + 8) * 64 /*FLACCLWriter.maxResidualParts*/ * FLACCLWriter.maxFrames); + + samplesBytesPtr = GCHandle.Alloc(new byte[samplesBufferLen / 2], GCHandleType.Pinned); + residualBufferPtr = GCHandle.Alloc(new byte[samplesBufferLen], GCHandleType.Pinned); + bestRiceParamsPtr = GCHandle.Alloc(new byte[riceParamsLen / 4], GCHandleType.Pinned); + residualTasksPtr = GCHandle.Alloc(new byte[residualTasksLen], GCHandleType.Pinned); + bestResidualTasksPtr = GCHandle.Alloc(new byte[bestResidualTasksLen], GCHandleType.Pinned); + + cudaComputeAutocor = openCLProgram.CreateKernel("cudaComputeAutocor"); + cudaStereoDecorr = openCLProgram.CreateKernel("cudaStereoDecorr"); + //cudaChannelDecorr = openCLProgram.CreateKernel("cudaChannelDecorr"); + cudaChannelDecorr2 = openCLProgram.CreateKernel("cudaChannelDecorr2"); + cudaFindWastedBits = openCLProgram.CreateKernel("cudaFindWastedBits"); + cudaComputeLPC = openCLProgram.CreateKernel("cudaComputeLPC"); + cudaQuantizeLPC = openCLProgram.CreateKernel("cudaQuantizeLPC"); + //cudaComputeLPCLattice = openCLProgram.CreateKernel("cudaComputeLPCLattice"); + cudaEstimateResidual = openCLProgram.CreateKernel("cudaEstimateResidual"); + cudaChooseBestMethod = openCLProgram.CreateKernel("cudaChooseBestMethod"); + cudaCopyBestMethod = openCLProgram.CreateKernel("cudaCopyBestMethod"); + cudaCopyBestMethodStereo = openCLProgram.CreateKernel("cudaCopyBestMethodStereo"); + //cudaEncodeResidual = openCLProgram.CreateKernel("cudaEncodeResidual"); + //cudaCalcPartition = openCLProgram.CreateKernel("cudaCalcPartition"); + //cudaCalcPartition16 = openCLProgram.CreateKernel("cudaCalcPartition16"); + //cudaCalcLargePartition = openCLProgram.CreateKernel("cudaCalcLargePartition"); + //cudaSumPartition = openCLProgram.CreateKernel("cudaSumPartition"); + //cudaFindRiceParameter = openCLProgram.CreateKernel("cudaFindRiceParameter"); + //cudaFindPartitionOrder = openCLProgram.CreateKernel("cudaFindPartitionOrder"); + + samplesBuffer = new int[FLACCLWriter.MAX_BLOCKSIZE * channelCount]; + outputBuffer = new byte[max_frame_size * FLACCLWriter.maxFrames + 1]; + frame = new FlacFrame(channelCount); + frame.writer = new BitWriter(outputBuffer, 0, outputBuffer.Length); + + if (do_verify) + { + verify = new FlakeReader(new AudioPCMConfig((int)bits_per_sample, channels, 44100)); + verify.DoCRC = false; + } + } + + public void Dispose() + { + if (workThread != null) + { + lock (this) + { + exit = true; + Monitor.Pulse(this); + } + workThread.Join(); + workThread = null; + } + + cudaComputeAutocor.Dispose(); + cudaStereoDecorr.Dispose(); + //cudaChannelDecorr.Dispose(); + cudaChannelDecorr2.Dispose(); + cudaFindWastedBits.Dispose(); + cudaComputeLPC.Dispose(); + cudaQuantizeLPC.Dispose(); + //cudaComputeLPCLattice.Dispose(); + cudaEstimateResidual.Dispose(); + cudaChooseBestMethod.Dispose(); + cudaCopyBestMethod.Dispose(); + cudaCopyBestMethodStereo.Dispose(); + //cudaEncodeResidual.Dispose(); + //cudaCalcPartition.Dispose(); + //cudaCalcPartition16.Dispose(); + //cudaCalcLargePartition.Dispose(); + //cudaSumPartition.Dispose(); + //cudaFindRiceParameter.Dispose(); + //cudaFindPartitionOrder.Dispose(); + + cudaSamples.Dispose(); + cudaSamplesBytes.Dispose(); + cudaLPCData.Dispose(); + cudaResidual.Dispose(); + cudaPartitions.Dispose(); + cudaAutocorOutput.Dispose(); + cudaResidualTasks.Dispose(); + cudaResidualOutput.Dispose(); + cudaBestResidualTasks.Dispose(); + + samplesBytesPtr.Free(); + residualBufferPtr.Free(); + bestRiceParamsPtr.Free(); + residualTasksPtr.Free(); + bestResidualTasksPtr.Free(); + + openCLCQ.Dispose(); + } + + public void EnqueueFindWasted(int channelsCount) + { + cudaFindWastedBits.SetArg(0, cudaResidualTasks); + cudaFindWastedBits.SetArg(1, cudaSamples); + cudaFindWastedBits.SetArg(2, nResidualTasksPerChannel); + + int workX = 128; // 256 + int grpX = frameCount * channelsCount; + //openCLCQ.EnqueueNDRangeKernel(cudaFindWastedBits, 1, null, new int[] { 128 }, new int[] { 128 }); + //openCLCQ.EnqueueNDRangeKernel(cudaFindWastedBits, 1, null, new int[] { 128 }, null); + openCLCQ.EnqueueNDRangeKernel(cudaFindWastedBits, 1, null, new int[] { grpX * workX }, new int[] { workX }); + + //openCLCQ.EnqueueNDRangeKernel(cudaFindWastedBits, 1, null, new int[] { 256 * 128 }, new int[] { 128 }); + //openCLCQ.EnqueueNDRangeKernel(cudaFindWastedBits, 1, null, new int[] { grpX * workX }, null); + //cuda.SetFunctionBlockShape(task.cudaFindWastedBits, 256, 1, 1); + //cuda.LaunchAsync(task.cudaFindWastedBits, channelsCount * task.frameCount, 1, task.stream); + } + + public void EnqueueComputeAutocor(int autocorPartCount, int channelsCount, Mem cudaWindow, int max_prediction_order) + { + cudaComputeAutocor.SetArg(0, cudaAutocorOutput); + cudaComputeAutocor.SetArg(1, cudaSamples); + cudaComputeAutocor.SetArg(2, cudaWindow); + cudaComputeAutocor.SetArg(3, cudaResidualTasks); + cudaComputeAutocor.SetArg(4, max_prediction_order); + cudaComputeAutocor.SetArg(5, (uint)nAutocorTasksPerChannel - 1); + cudaComputeAutocor.SetArg(6, (uint)nResidualTasksPerChannel); + int workX = autocorPartCount; + int workY = nAutocorTasksPerChannel * channelsCount * frameCount; + int ws = 32; + int wy = 4; + openCLCQ.EnqueueNDRangeKernel(cudaComputeAutocor, 2, null, new int[] { workX * ws, workY * wy }, new int[] { ws, wy }); + //openCLCQ.EnqueueNDRangeKernel(cudaComputeAutocor, 3, null, new int[] { workX * ws, workY, max_prediction_order + 1 }, new int[] { ws, 1, 1 }); + + //cuda.SetFunctionBlockShape(task.cudaComputeAutocor, 32, 8, 1); + //cuda.LaunchAsync(task.cudaComputeAutocor, autocorPartCount, task.nAutocorTasksPerChannel * channelsCount * task.frameCount, task.stream); + } + + public void EnqueueEstimateResidual(int channelsCount, int max_prediction_order) + { + cudaEstimateResidual.SetArg(0, cudaResidualOutput); + cudaEstimateResidual.SetArg(1, cudaSamples); + cudaEstimateResidual.SetArg(2, cudaResidualTasks); + + int threads_x = 128; + int workX = threads_x; + int workY = nResidualTasksPerChannel * channelsCount * frameCount; + + openCLCQ.EnqueueNDRangeKernel(cudaEstimateResidual, 2, null, new int[] { workX, workY }, new int[] { threads_x, 1 }); + } + + public void EnqueueChooseBestMethod(int channelsCount) + { + cudaChooseBestMethod.SetArg(0, cudaResidualTasks); + cudaChooseBestMethod.SetArg(1, cudaResidualOutput); + cudaChooseBestMethod.SetArg(2, (uint)nResidualTasksPerChannel); + + int threadsY = 4; + + openCLCQ.EnqueueNDRangeKernel(cudaChooseBestMethod, 2, null, new int[] { 32, channelsCount * frameCount * threadsY }, new int[] { 32, threadsY }); + //cuda.SetFunctionBlockShape(task.cudaChooseBestMethod, 32, 8, 1); + } + + public unsafe FLACCLSubframeTask* ResidualTasks + { + get + { + return (FLACCLSubframeTask*)residualTasksPtr.AddrOfPinnedObject(); + } + } + + public unsafe FLACCLSubframeTask* BestResidualTasks + { + get + { + return (FLACCLSubframeTask*)bestResidualTasksPtr.AddrOfPinnedObject(); + } + } + } +} diff --git a/CUETools.Codecs.FLACCL/OpenCLNet.dll b/CUETools.Codecs.FLACCL/OpenCLNet.dll new file mode 100644 index 0000000000000000000000000000000000000000..b6bf0cc0d49e5bf6748822bcde9ca09af668fd7f GIT binary patch literal 148480 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 R3=9TVL&?#jZlzWT006sw+{pj{ literal 0 HcmV?d00001 diff --git a/CUETools.Codecs.FLACCL/OpenCLNet.pdb b/CUETools.Codecs.FLACCL/OpenCLNet.pdb new file mode 100644 index 0000000000000000000000000000000000000000..e3f941b95dc1032f6de21d397825523ba2bd5057 GIT binary patch literal 394752 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 VyA4=^u)@~)3L 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 literal 0 HcmV?d00001 diff --git a/CUETools.Codecs.FLACCL/Properties/AssemblyInfo.cs b/CUETools.Codecs.FLACCL/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..83e2da4 --- /dev/null +++ b/CUETools.Codecs.FLACCL/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("CUETools.Codecs.FLACCL")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("CUETools.Codecs.FLACCL")] +[assembly: AssemblyCopyright("Copyright © 2009-2010 Gregory S. Chudov")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("b28ffece-6c89-426b-b227-e647b435cc3d")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Revision and Build Numbers +// by using the '*' as shown below: +[assembly: AssemblyVersion("2.0.9.0")] +[assembly: AssemblyFileVersion("2.0.9.0")] diff --git a/CUETools.Codecs.FLACCL/Properties/Resources.Designer.cs b/CUETools.Codecs.FLACCL/Properties/Resources.Designer.cs new file mode 100644 index 0000000..7b3d40a --- /dev/null +++ b/CUETools.Codecs.FLACCL/Properties/Resources.Designer.cs @@ -0,0 +1,117 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:2.0.50727.4200 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace CUETools.Codecs.FLACCL.Properties { + using System; + + + ///

+ /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "2.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("CUETools.Codecs.FLACCL.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Use additional CPU threads. + /// + internal static string DescriptionCPUThreads { + get { + return ResourceManager.GetString("DescriptionCPUThreads", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Use GPU on all stages. + /// + internal static string DescriptionGPUOnly { + get { + return ResourceManager.GetString("DescriptionGPUOnly", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Calculate MD5 hash for audio stream. + /// + internal static string DoMD5Description { + get { + return ResourceManager.GetString("DoMD5Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Decode each frame and compare with original. + /// + internal static string DoVerifyDescription { + get { + return ResourceManager.GetString("DoVerifyDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Samples written differs from the expected sample count. + /// + internal static string ExceptionSampleCount { + get { + return ResourceManager.GetString("ExceptionSampleCount", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Validation failed. + /// + internal static string ExceptionValidationFailed { + get { + return ResourceManager.GetString("ExceptionValidationFailed", resourceCulture); + } + } + } +} diff --git a/CUETools.Codecs.FLACCL/Properties/Resources.resx b/CUETools.Codecs.FLACCL/Properties/Resources.resx new file mode 100644 index 0000000..1579dd9 --- /dev/null +++ b/CUETools.Codecs.FLACCL/Properties/Resources.resx @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Use additional CPU threads + + + Use GPU on all stages + + + Calculate MD5 hash for audio stream + + + Decode each frame and compare with original + + + Samples written differs from the expected sample count + + + Validation failed + + \ No newline at end of file diff --git a/CUETools.Codecs.FLACCL/Properties/Resources.ru-RU.resx b/CUETools.Codecs.FLACCL/Properties/Resources.ru-RU.resx new file mode 100644 index 0000000..5f87a8e --- /dev/null +++ b/CUETools.Codecs.FLACCL/Properties/Resources.ru-RU.resx @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Использовать дополнительные потоки + + + Использовать GPU на всех стадиях + + + Вычислять MD5-хеш аудиопотока + + + Декодировать каждый блок и сравнивать с оригиналом + + + Количество записанных сэмплов отличается от ожидавшегося + + + Ошибка верификации + + \ No newline at end of file diff --git a/CUETools.Codecs.FLACCL/flac.cl b/CUETools.Codecs.FLACCL/flac.cl new file mode 100644 index 0000000..6c30b50 --- /dev/null +++ b/CUETools.Codecs.FLACCL/flac.cl @@ -0,0 +1,1275 @@ +/** + * CUETools.FLACCL: FLAC audio encoder using OpenCL + * Copyright (c) 2009 Gregory S. Chudov + * + * 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 + */ + +#ifndef _FLACCL_KERNEL_H_ +#define _FLACCL_KERNEL_H_ + +typedef enum +{ + Constant = 0, + Verbatim = 1, + Fixed = 8, + LPC = 32 +} SubframeType; + +typedef struct +{ + int residualOrder; // <= 32 + int samplesOffs; + int shift; + int cbits; + int size; + int type; + int obits; + int blocksize; + int best_index; + int channel; + int residualOffs; + int wbits; + int abits; + int porder; + int reserved[2]; +} FLACCLSubframeData; + +typedef struct +{ + FLACCLSubframeData data; + union + { + int coefs[32]; // fixme: should be short? + int4 coefs4[8]; + }; +} FLACCLSubframeTask; + +__kernel void cudaStereoDecorr( + __global int *samples, + __global short2 *src, + int offset +) +{ + int pos = get_global_id(0); + if (pos < offset) + { + short2 s = src[pos]; + samples[pos] = s.x; + samples[1 * offset + pos] = s.y; + samples[2 * offset + pos] = (s.x + s.y) >> 1; + samples[3 * offset + pos] = s.x - s.y; + } +} + +__kernel void cudaChannelDecorr2( + __global int *samples, + __global short2 *src, + int offset +) +{ + int pos = get_global_id(0); + if (pos < offset) + { + short2 s = src[pos]; + samples[pos] = s.x; + samples[1 * offset + pos] = s.y; + } +} + +//__kernel void cudaChannelDecorr( +// int *samples, +// short *src, +// int offset +//) +//{ +// int pos = get_global_id(0); +// if (pos < offset) +// samples[get_group_id(1) * offset + pos] = src[pos * get_num_groups(1) + get_group_id(1)]; +//} + +#define __ffs(a) (32 - clz(a & (-a))) +//#define __ffs(a) (33 - clz(~a & (a - 1))) + +__kernel __attribute__((reqd_work_group_size(128, 1, 1))) +void cudaFindWastedBits( + __global FLACCLSubframeTask *tasks, + __global int *samples, + int tasksPerChannel +) +{ + __local volatile int wbits[128]; + __local volatile int abits[128]; + __local FLACCLSubframeData task; + + int tid = get_local_id(0); + if (tid < sizeof(task) / sizeof(int)) + ((__local int*)&task)[tid] = ((__global int*)(&tasks[get_group_id(0) * tasksPerChannel].data))[tid]; + barrier(CLK_LOCAL_MEM_FENCE); + + int w = 0, a = 0; + for (int pos = 0; pos < task.blocksize; pos += get_local_size(0)) + { + int smp = pos + tid < task.blocksize ? samples[task.samplesOffs + pos + tid] : 0; + w |= smp; + a |= smp ^ (smp >> 31); + } + wbits[tid] = w; + abits[tid] = a; + barrier(CLK_LOCAL_MEM_FENCE); + //atom_or(shared.wbits, shared.wbits[tid]); + //atom_or(shared.abits, shared.abits[tid]); + //SUM256(shared.wbits, tid, |=); + //SUM256(shared.abits, tid, |=); + //SUM128(wbits, tid, |=); + //SUM128(abits, tid, |=); + + for (int s = get_local_size(0) / 2; s > 0; s >>= 1) + { + if (tid < s) + { + wbits[tid] |= wbits[tid + s]; + abits[tid] |= abits[tid + s]; + } + barrier(CLK_LOCAL_MEM_FENCE); + } + + if (tid == 0) + task.wbits = max(0,__ffs(wbits[0]) - 1); + if (tid == 0) + task.abits = 32 - clz(abits[0]) - task.wbits; + // if (tid == 0) + //task.wbits = get_num_groups(0); + // if (tid == 0) + //task.abits = get_local_size(0); + + barrier(CLK_LOCAL_MEM_FENCE); + + if (tid < tasksPerChannel) + tasks[get_group_id(0) * tasksPerChannel + tid].data.wbits = task.wbits; + if (tid < tasksPerChannel) + tasks[get_group_id(0) * tasksPerChannel + tid].data.abits = task.abits; +} + +//__kernel __attribute__((reqd_work_group_size(32, 4, 1))) +//void cudaComputeAutocor( +// __global float *output, +// __global const int *samples, +// __global const float *window, +// __global FLACCLSubframeTask *tasks, +// const int max_order, // should be <= 32 +// const int windowCount, // windows (log2: 0,1) +// const int taskCount // tasks per block +//) +//{ +// __local struct { +// float data[256]; +// volatile float product[128]; +// FLACCLSubframeData task; +// volatile int dataPos; +// volatile int dataLen; +// } shared; +// const int tid = get_local_id(0) + get_local_id(1) * 32; +// // fetch task data +// if (tid < sizeof(shared.task) / sizeof(int)) +// ((__local int*)&shared.task)[tid] = ((__global int*)(tasks + taskCount * (get_group_id(1) >> windowCount)))[tid]; +// if (tid == 0) +// { +// shared.dataPos = get_group_id(0) * 7 * 32; +// shared.dataLen = min(shared.task.blocksize - shared.dataPos, 7 * 32 + max_order); +// } +// barrier(CLK_LOCAL_MEM_FENCE); +// +// // fetch samples +// shared.data[tid] = tid < shared.dataLen ? samples[tid] * window[tid]: 0.0f; +// int tid2 = tid + 128; +// shared.data[tid2] = tid2 < shared.dataLen ? samples[tid2] * window[tid2]: 0.0f; +// barrier(CLK_LOCAL_MEM_FENCE); +// +// for (int lag = 0; lag <= max_order; lag ++) +// { +// if (lag <= 12) +// shared.product[tid] = 0.0f; +// barrier(CLK_LOCAL_MEM_FENCE); +// } +// barrier(CLK_LOCAL_MEM_FENCE); +// if (tid <= max_order) +// output[(get_group_id(0) + get_group_id(1) * get_num_groups(0)) * (max_order + 1) + tid] = shared.product[tid]; +//} + +__kernel __attribute__((reqd_work_group_size(32, 4, 1))) +void cudaComputeAutocor( + __global float *output, + __global const int *samples, + __global const float *window, + __global FLACCLSubframeTask *tasks, + const int max_order, // should be <= 32 + const int windowCount, // windows (log2: 0,1) + const int taskCount // tasks per block +) +{ + __local struct { + float data[256]; + volatile float product[128]; + FLACCLSubframeData task; + volatile float result[33]; + volatile int dataPos; + volatile int dataLen; + volatile int windowOffs; + volatile int samplesOffs; + //volatile int resultOffs; + } shared; + const int tid = get_local_id(0) + get_local_id(1) * 32; + // fetch task data + if (tid < sizeof(shared.task) / sizeof(int)) + ((__local int*)&shared.task)[tid] = ((__global int*)(tasks + taskCount * (get_group_id(1) >> windowCount)))[tid]; + if (tid == 0) + { + shared.dataPos = get_group_id(0) * 7 * 32; + shared.windowOffs = (get_group_id(1) & ((1 << windowCount)-1)) * shared.task.blocksize + shared.dataPos; + shared.samplesOffs = shared.task.samplesOffs + shared.dataPos; + shared.dataLen = min(shared.task.blocksize - shared.dataPos, 7 * 32 + max_order); + } + //if (tid == 32) + //shared.resultOffs = __mul24(get_group_id(0) + __mul24(get_group_id(1), get_num_groups(0)), max_order + 1); + barrier(CLK_LOCAL_MEM_FENCE); + + // fetch samples + shared.data[tid] = tid < shared.dataLen ? samples[shared.samplesOffs + tid] * window[shared.windowOffs + tid]: 0.0f; + int tid2 = tid + 128; + shared.data[tid2] = tid2 < shared.dataLen ? samples[shared.samplesOffs + tid2] * window[shared.windowOffs + tid2]: 0.0f; + barrier(CLK_LOCAL_MEM_FENCE); + + const int ptr = get_local_id(0) * 7; + //if (get_local_id(1) == 0) for (int lag = 0; lag <= max_order; lag ++) + //for (int lag = get_local_id(1); lag <= max_order; lag += get_local_size(1)) + for (int lag0 = 0; lag0 <= max_order; lag0 += get_local_size(1)) + { + ////const int productLen = min(shared.task.blocksize - get_group_id(0) * partSize - lag, partSize); + const int lag = lag0 + get_local_id(1); + const int ptr2 = ptr + lag; + shared.product[tid] = + shared.data[ptr + 0] * shared.data[ptr2 + 0] + + shared.data[ptr + 1] * shared.data[ptr2 + 1] + + shared.data[ptr + 2] * shared.data[ptr2 + 2] + + shared.data[ptr + 3] * shared.data[ptr2 + 3] + + shared.data[ptr + 4] * shared.data[ptr2 + 4] + + shared.data[ptr + 5] * shared.data[ptr2 + 5] + + shared.data[ptr + 6] * shared.data[ptr2 + 6]; + barrier(CLK_LOCAL_MEM_FENCE); + for (int l = 16; l > 1; l >>= 1) + { + if (get_local_id(0) < l) + shared.product[tid] = shared.product[tid] + shared.product[tid + l]; + barrier(CLK_LOCAL_MEM_FENCE); + } + // return results + if (get_local_id(0) == 0 && lag <= max_order) + shared.result[lag] = shared.product[tid] + shared.product[tid + 1]; + barrier(CLK_LOCAL_MEM_FENCE); + } + if (tid <= max_order) + output[(get_group_id(0) + get_group_id(1) * get_num_groups(0)) * (max_order + 1) + tid] = shared.result[tid]; + //output[(get_group_id(0) + get_group_id(1) * get_num_groups(0)) * (max_order + 1) + tid] = shared.product[tid]; + //output[(get_group_id(0) + get_group_id(1) * get_num_groups(0)) * (max_order + 1) + tid] = shared.windowOffs; +} + +__kernel __attribute__((reqd_work_group_size(32, 1, 1))) +void cudaComputeLPC( + __global FLACCLSubframeTask *tasks, + int taskCount, // tasks per block + __global float*autoc, + int max_order, // should be <= 32 + __global float *lpcs, + int windowCount, + int partCount +) +{ + __local struct { + FLACCLSubframeData task; + volatile float parts[32]; + volatile float ldr[32]; + volatile float gen1[32]; + volatile float error[32]; + volatile float autoc[33]; + volatile int lpcOffs; + volatile int autocOffs; + } shared; + const int tid = get_local_id(0);// + get_local_id(1) * 32; + + // fetch task data + if (tid < sizeof(shared.task) / sizeof(int)) + ((__local int*)&shared.task)[tid] = ((__global int*)(tasks + get_group_id(1) * taskCount))[tid]; + if (tid == 0) + { + shared.lpcOffs = (get_group_id(0) + get_group_id(1) * windowCount) * (max_order + 1) * 32; + shared.autocOffs = (get_group_id(0) + get_group_id(1) * get_num_groups(0)) * (max_order + 1) * partCount; + } + barrier(CLK_LOCAL_MEM_FENCE); + + // add up autocorrelation parts + + // for (int order = get_local_id(0); order <= max_order; order += 32) + // { + //float sum = 0.0f; + //for (int pos = 0; pos < partCount; pos++) + // sum += autoc[shared.autocOffs + pos * (max_order + 1) + order]; + //shared.autoc[order] = sum; + // } + + for (int order = 0; order <= max_order; order ++) + { + float part = 0.0f; + for (int pos = get_local_id(0); pos < partCount; pos += get_local_size(0)) + part += autoc[shared.autocOffs + pos * (max_order + 1) + order]; + shared.parts[tid] = part; + barrier(CLK_LOCAL_MEM_FENCE); + for (int l = get_local_size(0) / 2; l > 1; l >>= 1) + { + if (get_local_id(0) < l) + shared.parts[tid] += shared.parts[tid + l]; + barrier(CLK_LOCAL_MEM_FENCE); + } + if (get_local_id(0) == 0) + shared.autoc[order] = shared.parts[tid] + shared.parts[tid + 1]; + } + barrier(CLK_LOCAL_MEM_FENCE); + + // Compute LPC using Schur and Levinson-Durbin recursion + float gen0 = shared.gen1[get_local_id(0)] = shared.autoc[get_local_id(0)+1]; + shared.ldr[get_local_id(0)] = 0.0f; + float error = shared.autoc[0]; + barrier(CLK_LOCAL_MEM_FENCE); + for (int order = 0; order < max_order; order++) + { + // Schur recursion + float reff = -shared.gen1[0] / error; + error += shared.gen1[0] * reff; // Equivalent to error *= (1 - reff * reff); + float gen1; + if (get_local_id(0) < max_order - 1 - order) + { + gen1 = shared.gen1[get_local_id(0) + 1] + reff * gen0; + gen0 += shared.gen1[get_local_id(0) + 1] * reff; + } + barrier(CLK_LOCAL_MEM_FENCE); + if (get_local_id(0) < max_order - 1 - order) + shared.gen1[get_local_id(0)] = gen1; + + // Store prediction error + if (get_local_id(0) == 0) + shared.error[order] = error; + + // Levinson-Durbin recursion + float ldr = + select(0.0f, reff * shared.ldr[order - 1 - get_local_id(0)], get_local_id(0) < order) + + select(0.0f, reff, get_local_id(0) == order); + barrier(CLK_LOCAL_MEM_FENCE); + shared.ldr[get_local_id(0)] += ldr; + barrier(CLK_LOCAL_MEM_FENCE); + + // Output coeffs + if (get_local_id(0) <= order) + lpcs[shared.lpcOffs + order * 32 + get_local_id(0)] = -shared.ldr[order - get_local_id(0)]; + } + barrier(CLK_LOCAL_MEM_FENCE); + // Output prediction error estimates + if (get_local_id(0) < max_order) + lpcs[shared.lpcOffs + max_order * 32 + get_local_id(0)] = shared.error[get_local_id(0)]; +} + +//__kernel void cudaComputeLPCLattice( +// FLACCLSubframeTask *tasks, +// const int taskCount, // tasks per block +// const int *samples, +// const int windowCount, +// const int max_order, // should be <= 12 +// float*lpcs +//) +//{ +// __local struct { +// volatile FLACCLSubframeData task; +// volatile float F[512]; +// volatile float arp[32]; +// volatile float tmp[256]; +// volatile float error[32]; +// volatile int lpcOffs; +// } shared; +// +// // fetch task data +// if (get_local_id(0) < sizeof(shared.task) / sizeof(int)) +// ((int*)&shared.task)[get_local_id(0)] = ((int*)(tasks + taskCount * get_group_id(1)))[get_local_id(0)]; +// if (get_local_id(0) == 0) +// shared.lpcOffs = __mul24(__mul24(get_group_id(1) + 1, windowCount) - 1, max_order + 1) * 32; +// barrier(CLK_LOCAL_MEM_FENCE); +// +// // F = samples; B = samples +// float s1 = get_local_id(0) < shared.task.blocksize ? (samples[shared.task.samplesOffs + get_local_id(0)]) / 32768.0f : 0.0f; +// float s2 = get_local_id(0) + 256 < shared.task.blocksize ? (samples[shared.task.samplesOffs + get_local_id(0) + 256]) / 32768.0f : 0.0f; +// shared.F[get_local_id(0)] = s1; +// shared.F[get_local_id(0) + 256] = s2; +// barrier(CLK_LOCAL_MEM_FENCE); +// +// shared.tmp[get_local_id(0)] = FSQR(s1) + FSQR(s2); +// barrier(CLK_LOCAL_MEM_FENCE); +// SUM256(shared.tmp, get_local_id(0), +=); +// barrier(CLK_LOCAL_MEM_FENCE); +// float DEN = shared.tmp[0]; +// barrier(CLK_LOCAL_MEM_FENCE); +// +// for (int order = 0; order < max_order; order++) +// { +// // reff = F(order+1:frameSize) * B(1:frameSize-order)' / DEN +// int idxF = get_local_id(0) + order + 1; +// int idxF2 = idxF + 256; +// +// shared.tmp[get_local_id(0)] = idxF < shared.task.blocksize ? shared.F[idxF] * s1 : 0.0f; +// shared.tmp[get_local_id(0)] += idxF2 < shared.task.blocksize ? shared.F[idxF2] * s2 : 0.0f; +// barrier(CLK_LOCAL_MEM_FENCE); +// SUM256(shared.tmp, get_local_id(0), +=); +// barrier(CLK_LOCAL_MEM_FENCE); +// float reff = shared.tmp[0] / DEN; +// barrier(CLK_LOCAL_MEM_FENCE); +// +// // arp(order) = rc(order) = reff +// if (get_local_id(0) == 0) +// shared.arp[order] = reff; +// //shared.rc[order - 1] = shared.lpc[order - 1][order - 1] = reff; +// +// // Levinson-Durbin recursion +// // arp(1:order-1) = arp(1:order-1) - reff * arp(order-1:-1:1) +// if (get_local_id(0) < order) +// shared.arp[get_local_id(0)] = shared.arp[get_local_id(0)] - reff * shared.arp[order - 1 - get_local_id(0)]; +// +// // Output coeffs +// if (get_local_id(0) <= order) +// lpcs[shared.lpcOffs + order * 32 + get_local_id(0)] = shared.arp[order - get_local_id(0)]; +// +// // F1 = F(order+1:frameSize) - reff * B(1:frameSize-order) +// // B(1:frameSize-order) = B(1:frameSize-order) - reff * F(order+1:frameSize) +// // F(order+1:frameSize) = F1 +// if (idxF < shared.task.blocksize) +// { +// float f1 = shared.F[idxF]; +// shared.F[idxF] -= reff * s1; +// s1 -= reff * f1; +// } +// if (idxF2 < shared.task.blocksize) +// { +// float f2 = shared.F[idxF2]; +// shared.F[idxF2] -= reff * s2; +// s2 -= reff * f2; +// } +// +// // DEN = F(order+1:frameSize) * F(order+1:frameSize)' + B(1:frameSize-order) * B(1:frameSize-order)' (BURG) +// shared.tmp[get_local_id(0)] = (idxF + 1 < shared.task.blocksize ? FSQR(shared.F[idxF]) + FSQR(s1) : 0); +// shared.tmp[get_local_id(0)] += (idxF2 + 1 < shared.task.blocksize ? FSQR(shared.F[idxF2]) + FSQR(s2) : 0); +// barrier(CLK_LOCAL_MEM_FENCE); +// SUM256(shared.tmp, get_local_id(0), +=); +// barrier(CLK_LOCAL_MEM_FENCE); +// DEN = shared.tmp[0] / 2; +// // shared.PE[order-1] = shared.tmp[0] / 2 / (frameSize - order + 1); +// if (get_local_id(0) == 0) +// shared.error[order] = DEN / (shared.task.blocksize - order); +// barrier(CLK_LOCAL_MEM_FENCE); +// } +// +// // Output prediction error estimates +// if (get_local_id(0) < max_order) +// lpcs[shared.lpcOffs + max_order * 32 + get_local_id(0)] = shared.error[get_local_id(0)]; +//} + +__kernel __attribute__((reqd_work_group_size(32, 4, 1))) +void cudaQuantizeLPC( + __global FLACCLSubframeTask *tasks, + int taskCount, // tasks per block + int taskCountLPC, // tasks per set of coeffs (<= 32) + __global float*lpcs, + int max_order, // should be <= 32 + int minprecision, + int precisions +) +{ + __local struct { + FLACCLSubframeData task; + volatile int tmpi[128]; + volatile int index[64]; + volatile float error[64]; + volatile int lpcOffs; + } shared; + + const int tid = get_local_id(0) + get_local_id(1) * 32; + + // fetch task data + if (tid < sizeof(shared.task) / sizeof(int)) + ((__local int*)&shared.task)[tid] = ((__global int*)(tasks + get_group_id(1) * taskCount))[tid]; + if (tid == 0) + shared.lpcOffs = (get_group_id(0) + get_group_id(1) * get_num_groups(0)) * (max_order + 1) * 32; + barrier(CLK_LOCAL_MEM_FENCE); + + if (get_local_id(1) == 0) + { + shared.index[get_local_id(0)] = min(max_order - 1, get_local_id(0)); + shared.error[get_local_id(0)] = shared.task.blocksize * 64 + get_local_id(0); + shared.index[32 + get_local_id(0)] = min(max_order - 1, get_local_id(0)); + shared.error[32 + get_local_id(0)] = shared.task.blocksize * 64 + get_local_id(0); + + // Select best orders based on Akaike's Criteria + + // Load prediction error estimates + if (get_local_id(0) < max_order) + shared.error[get_local_id(0)] = shared.task.blocksize * log(lpcs[shared.lpcOffs + max_order * 32 + get_local_id(0)]) + get_local_id(0) * 5.12f * log(shared.task.blocksize); + //shared.error[get_local_id(0)] = shared.task.blocksize * log(lpcs[shared.lpcOffs + max_order * 32 + get_local_id(0)]) + get_local_id(0) * 0.30f * (shared.task.abits + 1) * log(shared.task.blocksize); + } + + barrier(CLK_LOCAL_MEM_FENCE); + + // Sort using bitonic sort + for(int size = 2; size < 64; size <<= 1){ + //Bitonic merge + int ddd = (get_local_id(0) & (size / 2)) == 0; + for(int stride = size / 2; stride > 0; stride >>= 1){ + int pos = 2 * get_local_id(0) - (get_local_id(0) & (stride - 1)); + float e0, e1; + int i0, i1; + if (get_local_id(1) == 0) + { + e0 = shared.error[pos]; + e1 = shared.error[pos + stride]; + i0 = shared.index[pos]; + i1 = shared.index[pos + stride]; + } + barrier(CLK_LOCAL_MEM_FENCE); + if ((e0 >= e1) == ddd && get_local_id(1) == 0) + { + shared.error[pos] = e1; + shared.error[pos + stride] = e0; + shared.index[pos] = i1; + shared.index[pos + stride] = i0; + } + barrier(CLK_LOCAL_MEM_FENCE); + } + } + + //ddd == dir for the last bitonic merge step + { + for(int stride = 32; stride > 0; stride >>= 1){ + //barrier(CLK_LOCAL_MEM_FENCE); + int pos = 2 * get_local_id(0) - (get_local_id(0) & (stride - 1)); + float e0, e1; + int i0, i1; + if (get_local_id(1) == 0) + { + e0 = shared.error[pos]; + e1 = shared.error[pos + stride]; + i0 = shared.index[pos]; + i1 = shared.index[pos + stride]; + } + barrier(CLK_LOCAL_MEM_FENCE); + if (e0 >= e1 && get_local_id(1) == 0) + { + shared.error[pos] = e1; + shared.error[pos + stride] = e0; + shared.index[pos] = i1; + shared.index[pos + stride] = i0; + } + barrier(CLK_LOCAL_MEM_FENCE); + } + } + + // Quantization + for (int ii = 0; ii < taskCountLPC; ii += get_local_size(1)) + { + int i = ii + get_local_id(1); + int order = shared.index[i >> precisions]; + float lpc = get_local_id(0) <= order ? lpcs[shared.lpcOffs + order * 32 + get_local_id(0)] : 0.0f; + // get 15 bits of each coeff + int coef = convert_int_rte(lpc * (1 << 15)); + // remove sign bits + shared.tmpi[tid] = coef ^ (coef >> 31); + barrier(CLK_LOCAL_MEM_FENCE); + // OR reduction + for (int l = get_local_size(0) / 2; l > 1; l >>= 1) + { + if (get_local_id(0) < l) + shared.tmpi[tid] |= shared.tmpi[tid + l]; + barrier(CLK_LOCAL_MEM_FENCE); + } + //SUM32(shared.tmpi,tid,|=); + // choose precision + //int cbits = max(3, min(10, 5 + (shared.task.abits >> 1))); // - convert_int_rte(shared.PE[order - 1]) + int cbits = max(3, min(min(13 - minprecision + (i - ((i >> precisions) << precisions)) - (shared.task.blocksize <= 2304) - (shared.task.blocksize <= 1152) - (shared.task.blocksize <= 576), shared.task.abits), clz(order) + 1 - shared.task.abits)); + // calculate shift based on precision and number of leading zeroes in coeffs + int shift = max(0,min(15, clz(shared.tmpi[get_local_id(1) * 32] | shared.tmpi[get_local_id(1) * 32 + 1]) - 18 + cbits)); + + //cbits = 13; + //shift = 15; + + //if (shared.task.abits + 32 - clz(order) < shift + //int shift = max(0,min(15, (shared.task.abits >> 2) - 14 + clz(shared.tmpi[get_local_id(0) & ~31]) + ((32 - clz(order))>>1))); + // quantize coeffs with given shift + coef = convert_int_rte(clamp(lpc * (1 << shift), -1 << (cbits - 1), 1 << (cbits - 1))); + // error correction + //shared.tmp[get_local_id(0)] = (get_local_id(0) != 0) * (shared.arp[get_local_id(0) - 1]*(1 << shared.task.shift) - shared.task.coefs[get_local_id(0) - 1]); + //shared.task.coefs[get_local_id(0)] = max(-(1 << (shared.task.cbits - 1)), min((1 << (shared.task.cbits - 1))-1, convert_int_rte((shared.arp[get_local_id(0)]) * (1 << shared.task.shift) + shared.tmp[get_local_id(0)]))); + // remove sign bits + shared.tmpi[tid] = coef ^ (coef >> 31); + barrier(CLK_LOCAL_MEM_FENCE); + // OR reduction + for (int l = get_local_size(0) / 2; l > 1; l >>= 1) + { + if (get_local_id(0) < l) + shared.tmpi[tid] |= shared.tmpi[tid + l]; + barrier(CLK_LOCAL_MEM_FENCE); + } + //SUM32(shared.tmpi,tid,|=); + // calculate actual number of bits (+1 for sign) + cbits = 1 + 32 - clz(shared.tmpi[get_local_id(1) * 32] | shared.tmpi[get_local_id(1) * 32 + 1]); + + // output shift, cbits and output coeffs + if (i < taskCountLPC) + { + int taskNo = get_group_id(1) * taskCount + get_group_id(0) * taskCountLPC + i; + if (get_local_id(0) == 0) + tasks[taskNo].data.shift = shift; + if (get_local_id(0) == 0) + tasks[taskNo].data.cbits = cbits; + if (get_local_id(0) == 0) + tasks[taskNo].data.residualOrder = order + 1; + if (get_local_id(0) <= order) + tasks[taskNo].coefs[get_local_id(0)] = coef; + } + } +} + +__kernel __attribute__(( vec_type_hint (int4))) +void cudaEstimateResidual( + __global int*output, + __global int*samples, + __global FLACCLSubframeTask *tasks + ) +{ + __local float data[128 * 2]; + __local int residual[128]; + __local FLACCLSubframeTask task; + __local float4 coefsf4[8]; + + const int tid = get_local_id(0); + if (tid < sizeof(task)/sizeof(int)) + ((__local int*)&task)[tid] = ((__global int*)(&tasks[get_group_id(1)]))[tid]; + barrier(CLK_GLOBAL_MEM_FENCE); + + int ro = task.data.residualOrder; + int bs = task.data.blocksize; + float res = 0; + + if (tid < 32) + ((__local float *)&coefsf4[0])[tid] = select(0.0f, ((float)task.coefs[tid]) / (1 << task.data.shift), tid < ro); + data[tid] = tid < bs ? (float)(samples[task.data.samplesOffs + tid] >> task.data.wbits) : 0.0f; + for (int pos = 0; pos < bs; pos += get_local_size(0)) + { + // fetch samples + float nextData = pos + tid + get_local_size(0) < bs ? (float)(samples[task.data.samplesOffs + pos + tid + get_local_size(0)] >> task.data.wbits) : 0.0f; + data[tid + get_local_size(0)] = nextData; + barrier(CLK_LOCAL_MEM_FENCE); + + // compute residual + __local float4 * dptr = (__local float4 *)&data[tid]; + float sumf = data[tid + ro] - + ( dot(dptr[0], coefsf4[0]) + + dot(dptr[1], coefsf4[1]) +#if MAX_ORDER > 8 + + dot(dptr[2], coefsf4[2]) +#if MAX_ORDER > 12 + + dot(dptr[3], coefsf4[3]) +#if MAX_ORDER > 16 + + dot(dptr[4], coefsf4[4]) + + dot(dptr[5], coefsf4[5]) + + dot(dptr[6], coefsf4[6]) + + dot(dptr[7], coefsf4[7]) +#endif +#endif +#endif + ); + //residual[tid] = sum; + + res += select(0.0f, min(fabs(sumf), (float)0x7fffff), pos + tid + ro < bs); + barrier(CLK_LOCAL_MEM_FENCE); + + //int k = min(33 - clz(sum), 14); + //res += select(0, 1 + k, pos + tid + ro < bs); + + //sum = residual[tid] + residual[tid + 1] + residual[tid + 2] + residual[tid + 3] + // + residual[tid + 4] + residual[tid + 5] + residual[tid + 6] + residual[tid + 7]; + //int k = clamp(29 - clz(sum), 0, 14); + //res += select(0, 8 * (k + 1) + (sum >> k), pos + tid + ro < bs && !(tid & 7)); + + data[tid] = nextData; + } + + int residualLen = (bs - ro) / get_local_size(0) + select(0, 1, tid < (bs - ro) % get_local_size(0)); + int k = clamp(convert_int_rtn(log2((res + 0.000001f) / (residualLen + 0.000001f))), 0, 14); + residual[tid] = residualLen * (k + 1) + (convert_int_rtz(res) >> k); + + barrier(CLK_LOCAL_MEM_FENCE); + for (int l = get_local_size(0) / 2; l > 0; l >>= 1) + { + if (tid < l) + residual[tid] += residual[tid + l]; + barrier(CLK_LOCAL_MEM_FENCE); + } + if (tid == 0) + output[get_group_id(1)] = residual[0]; +} + +__kernel void cudaChooseBestMethod( + __global FLACCLSubframeTask *tasks, + __global int *residual, + int taskCount + ) +{ + __local struct { + volatile int index[128]; + volatile int length[256]; + volatile FLACCLSubframeTask task[8]; + } shared; + const int tid = get_local_id(0) + get_local_id(1) * 32; + + shared.length[tid] = 0x7fffffff; + shared.index[tid] = tid; + for (int task = 0; task < taskCount; task += get_local_size(1)) + if (task + get_local_id(1) < taskCount) + { + // fetch task data + ((__local int*)&shared.task[get_local_id(1)])[get_local_id(0)] = + ((__global int*)(tasks + task + get_local_id(1) + taskCount * get_group_id(1)))[get_local_id(0)]; + + barrier(CLK_LOCAL_MEM_FENCE); + + if (get_local_id(0) == 0) + { + // fetch part sum + int partLen = residual[task + get_local_id(1) + taskCount * get_group_id(1)]; + //// calculate part size + //int residualLen = shared.task[get_local_id(1)].data.blocksize - shared.task[get_local_id(1)].data.residualOrder; + //residualLen = residualLen * (shared.task[get_local_id(1)].data.type != Constant || psum != 0); + //// calculate rice parameter + //int k = max(0, min(14, convert_int_rtz(log2((psum + 0.000001f) / (residualLen + 0.000001f) + 0.5f)))); + //// calculate part bit length + //int partLen = residualLen * (k + 1) + (psum >> k); + + int obits = shared.task[get_local_id(1)].data.obits - shared.task[get_local_id(1)].data.wbits; + shared.length[task + get_local_id(1)] = + min(obits * shared.task[get_local_id(1)].data.blocksize, + shared.task[get_local_id(1)].data.type == Fixed ? shared.task[get_local_id(1)].data.residualOrder * obits + 6 + (4 * 1/2) + partLen : + shared.task[get_local_id(1)].data.type == LPC ? shared.task[get_local_id(1)].data.residualOrder * obits + 4 + 5 + shared.task[get_local_id(1)].data.residualOrder * shared.task[get_local_id(1)].data.cbits + 6 + (4 * 1/2)/* << porder */ + partLen : + shared.task[get_local_id(1)].data.type == Constant ? obits * (1 + shared.task[get_local_id(1)].data.blocksize * (partLen != 0)) : + obits * shared.task[get_local_id(1)].data.blocksize); + } + } + //shared.index[get_local_id(0)] = get_local_id(0); + //shared.length[get_local_id(0)] = (get_local_id(0) < taskCount) ? tasks[get_local_id(0) + taskCount * get_group_id(1)].size : 0x7fffffff; + + barrier(CLK_LOCAL_MEM_FENCE); + if (tid < taskCount) + tasks[tid + taskCount * get_group_id(1)].data.size = shared.length[tid]; + + int l1 = shared.length[tid]; + for (int sh = 8; sh > 0; sh --) + { + if (tid + (1 << sh) < get_local_size(0) * get_local_size(1)) + { + int l2 = shared.length[tid + (1 << sh)]; + shared.index[tid] = shared.index[tid + ((l2 < l1) << sh)]; + shared.length[tid] = l1 = min(l1, l2); + } + barrier(CLK_LOCAL_MEM_FENCE); + } + if (tid == 0) + tasks[taskCount * get_group_id(1)].data.best_index = taskCount * get_group_id(1) + shared.index[shared.length[1] < shared.length[0]]; +} + +__kernel void cudaCopyBestMethod( + __global FLACCLSubframeTask *tasks_out, + __global FLACCLSubframeTask *tasks, + int count + ) +{ + __local int best_index; + if (get_local_id(0) == 0) + best_index = tasks[count * get_group_id(1)].data.best_index; + barrier(CLK_LOCAL_MEM_FENCE); + if (get_local_id(0) < sizeof(FLACCLSubframeTask)/sizeof(int)) + ((__global int*)(tasks_out + get_group_id(1)))[get_local_id(0)] = ((__global int*)(tasks + best_index))[get_local_id(0)]; +} + +__kernel void cudaCopyBestMethodStereo( + __global FLACCLSubframeTask *tasks_out, + __global FLACCLSubframeTask *tasks, + int count + ) +{ + __local struct { + int best_index[4]; + int best_size[4]; + int lr_index[2]; + } shared; + if (get_local_id(0) < 4) + shared.best_index[get_local_id(0)] = tasks[count * (get_group_id(1) * 4 + get_local_id(0))].data.best_index; + barrier(CLK_LOCAL_MEM_FENCE); + if (get_local_id(0) < 4) + shared.best_size[get_local_id(0)] = tasks[shared.best_index[get_local_id(0)]].data.size; + barrier(CLK_LOCAL_MEM_FENCE); + if (get_local_id(0) == 0) + { + int bitsBest = shared.best_size[2] + shared.best_size[3]; // MidSide + shared.lr_index[0] = shared.best_index[2]; + shared.lr_index[1] = shared.best_index[3]; + if (bitsBest > shared.best_size[3] + shared.best_size[1]) // RightSide + { + bitsBest = shared.best_size[3] + shared.best_size[1]; + shared.lr_index[0] = shared.best_index[3]; + shared.lr_index[1] = shared.best_index[1]; + } + if (bitsBest > shared.best_size[0] + shared.best_size[3]) // LeftSide + { + bitsBest = shared.best_size[0] + shared.best_size[3]; + shared.lr_index[0] = shared.best_index[0]; + shared.lr_index[1] = shared.best_index[3]; + } + if (bitsBest > shared.best_size[0] + shared.best_size[1]) // LeftRight + { + bitsBest = shared.best_size[0] + shared.best_size[1]; + shared.lr_index[0] = shared.best_index[0]; + shared.lr_index[1] = shared.best_index[1]; + } + } + barrier(CLK_LOCAL_MEM_FENCE); + if (get_local_id(0) < sizeof(FLACCLSubframeTask)/sizeof(int)) + ((__global int*)(tasks_out + 2 * get_group_id(1)))[get_local_id(0)] = ((__global int*)(tasks + shared.lr_index[0]))[get_local_id(0)]; + if (get_local_id(0) == 0) + tasks_out[2 * get_group_id(1)].data.residualOffs = tasks[shared.best_index[0]].data.residualOffs; + if (get_local_id(0) < sizeof(FLACCLSubframeTask)/sizeof(int)) + ((__global int*)(tasks_out + 2 * get_group_id(1) + 1))[get_local_id(0)] = ((__global int*)(tasks + shared.lr_index[1]))[get_local_id(0)]; + if (get_local_id(0) == 0) + tasks_out[2 * get_group_id(1) + 1].data.residualOffs = tasks[shared.best_index[1]].data.residualOffs; +} + +//__kernel void cudaEncodeResidual( +// int*output, +// int*samples, +// FLACCLSubframeTask *tasks +// ) +//{ +// __local struct { +// int data[256 + 32]; +// FLACCLSubframeTask task; +// } shared; +// const int tid = get_local_id(0); +// if (get_local_id(0) < sizeof(shared.task) / sizeof(int)) +// ((int*)&shared.task)[get_local_id(0)] = ((int*)(&tasks[get_group_id(1)]))[get_local_id(0)]; +// barrier(CLK_LOCAL_MEM_FENCE); +// const int partSize = get_local_size(0); +// const int pos = get_group_id(0) * partSize; +// const int dataLen = min(shared.task.data.blocksize - pos, partSize + shared.task.data.residualOrder); +// +// // fetch samples +// shared.data[tid] = tid < dataLen ? samples[shared.task.data.samplesOffs + pos + tid] >> shared.task.data.wbits : 0; +// if (tid < 32) shared.data[tid + partSize] = tid + partSize < dataLen ? samples[shared.task.data.samplesOffs + pos + tid + partSize] >> shared.task.data.wbits : 0; +// const int residualLen = max(0,min(shared.task.data.blocksize - pos - shared.task.data.residualOrder, partSize)); +// +// barrier(CLK_LOCAL_MEM_FENCE); +// // compute residual +// int sum = 0; +// for (int c = 0; c < shared.task.data.residualOrder; c++) +// sum += __mul24(shared.data[tid + c], shared.task.coefs[c]); +// barrier(CLK_LOCAL_MEM_FENCE); +// shared.data[tid + shared.task.data.residualOrder] -= (sum >> shared.task.data.shift); +// barrier(CLK_LOCAL_MEM_FENCE); +// if (tid >= shared.task.data.residualOrder && tid < residualLen + shared.task.data.residualOrder) +// output[shared.task.data.residualOffs + pos + tid] = shared.data[tid]; +// if (tid + 256 < residualLen + shared.task.data.residualOrder) +// output[shared.task.data.residualOffs + pos + tid + 256] = shared.data[tid + 256]; +//} +// +//__kernel void cudaCalcPartition( +// int* partition_lengths, +// int* residual, +// int* samples, +// FLACCLSubframeTask *tasks, +// int max_porder, // <= 8 +// int psize, // == (shared.task.data.blocksize >> max_porder), < 256 +// int parts_per_block // == 256 / psize, > 0, <= 16 +// ) +//{ +// __local struct { +// int data[256+32]; +// FLACCLSubframeTask task; +// } shared; +// const int tid = get_local_id(0) + (get_local_id(1) << 4); +// if (tid < sizeof(shared.task) / sizeof(int)) +// ((int*)&shared.task)[tid] = ((int*)(&tasks[get_group_id(1)]))[tid]; +// barrier(CLK_LOCAL_MEM_FENCE); +// +// const int parts = min(parts_per_block, (1 << max_porder) - get_group_id(0) * parts_per_block); +// const int offs = get_group_id(0) * psize * parts_per_block + tid; +// +// // fetch samples +// if (tid < 32) shared.data[tid] = min(offs, tid + shared.task.data.residualOrder) >= 32 ? samples[shared.task.data.samplesOffs + offs - 32] >> shared.task.data.wbits : 0; +// shared.data[32 + tid] = tid < parts * psize ? samples[shared.task.data.samplesOffs + offs] >> shared.task.data.wbits : 0; +// barrier(CLK_LOCAL_MEM_FENCE); +// +// // compute residual +// int s = 0; +// for (int c = -shared.task.data.residualOrder; c < 0; c++) +// s += __mul24(shared.data[32 + tid + c], shared.task.coefs[shared.task.data.residualOrder + c]); +// s = shared.data[32 + tid] - (s >> shared.task.data.shift); +// +// if (offs >= shared.task.data.residualOrder && tid < parts * psize) +// residual[shared.task.data.residualOffs + offs] = s; +// else +// s = 0; +// +// // convert to unsigned +// s = min(0xfffff, (s << 1) ^ (s >> 31)); +// +// //barrier(CLK_LOCAL_MEM_FENCE); +// //shared.data[tid] = s; +// //barrier(CLK_LOCAL_MEM_FENCE); +// +// //shared.data[tid] = (shared.data[tid] & (0x0000ffff << (tid & 16))) | (((shared.data[tid ^ 16] & (0x0000ffff << (tid & 16))) << (~tid & 16)) >> (tid & 16)); +// //shared.data[tid] = (shared.data[tid] & (0x00ff00ff << (tid & 8))) | (((shared.data[tid ^ 8] & (0x00ff00ff << (tid & 8))) << (~tid & 8)) >> (tid & 8)); +// //shared.data[tid] = (shared.data[tid] & (0x0f0f0f0f << (tid & 4))) | (((shared.data[tid ^ 4] & (0x0f0f0f0f << (tid & 4))) << (~tid & 4)) >> (tid & 4)); +// //shared.data[tid] = (shared.data[tid] & (0x33333333 << (tid & 2))) | (((shared.data[tid ^ 2] & (0x33333333 << (tid & 2))) << (~tid & 2)) >> (tid & 2)); +// //shared.data[tid] = (shared.data[tid] & (0x55555555 << (tid & 1))) | (((shared.data[tid ^ 1] & (0x55555555 << (tid & 1))) << (~tid & 1)) >> (tid & 1)); +// //shared.data[tid] = __popc(shared.data[tid]); +// +// barrier(CLK_LOCAL_MEM_FENCE); +// shared.data[tid + (tid / psize)] = s; +// //shared.data[tid] = s; +// barrier(CLK_LOCAL_MEM_FENCE); +// +// s = (psize - shared.task.data.residualOrder * (get_local_id(0) + get_group_id(0) == 0)) * (get_local_id(1) + 1); +// int dpos = __mul24(get_local_id(0), psize + 1); +// //int dpos = __mul24(get_local_id(0), psize); +// // calc number of unary bits for part get_local_id(0) with rice paramater get_local_id(1) +//#pragma unroll 0 +// for (int i = 0; i < psize; i++) +// s += shared.data[dpos + i] >> get_local_id(1); +// +// // output length +// const int pos = (15 << (max_porder + 1)) * get_group_id(1) + (get_local_id(1) << (max_porder + 1)); +// if (get_local_id(1) <= 14 && get_local_id(0) < parts) +// partition_lengths[pos + get_group_id(0) * parts_per_block + get_local_id(0)] = s; +//} +// +//__kernel void cudaCalcPartition16( +// int* partition_lengths, +// int* residual, +// int* samples, +// FLACCLSubframeTask *tasks, +// int max_porder, // <= 8 +// int psize, // == 16 +// int parts_per_block // == 16 +// ) +//{ +// __local struct { +// int data[256+32]; +// FLACCLSubframeTask task; +// } shared; +// const int tid = get_local_id(0) + (get_local_id(1) << 4); +// if (tid < sizeof(shared.task) / sizeof(int)) +// ((int*)&shared.task)[tid] = ((int*)(&tasks[get_group_id(1)]))[tid]; +// barrier(CLK_LOCAL_MEM_FENCE); +// +// const int offs = (get_group_id(0) << 8) + tid; +// +// // fetch samples +// if (tid < 32) shared.data[tid] = min(offs, tid + shared.task.data.residualOrder) >= 32 ? samples[shared.task.data.samplesOffs + offs - 32] >> shared.task.data.wbits : 0; +// shared.data[32 + tid] = samples[shared.task.data.samplesOffs + offs] >> shared.task.data.wbits; +// // if (tid < 32 && tid >= shared.task.data.residualOrder) +// //shared.task.coefs[tid] = 0; +// barrier(CLK_LOCAL_MEM_FENCE); +// +// // compute residual +// int s = 0; +// for (int c = -shared.task.data.residualOrder; c < 0; c++) +// s += __mul24(shared.data[32 + tid + c], shared.task.coefs[shared.task.data.residualOrder + c]); +// // int spos = 32 + tid - shared.task.data.residualOrder; +// // int s= +// //__mul24(shared.data[spos + 0], shared.task.coefs[0]) + __mul24(shared.data[spos + 1], shared.task.coefs[1]) + +// //__mul24(shared.data[spos + 2], shared.task.coefs[2]) + __mul24(shared.data[spos + 3], shared.task.coefs[3]) + +// //__mul24(shared.data[spos + 4], shared.task.coefs[4]) + __mul24(shared.data[spos + 5], shared.task.coefs[5]) + +// //__mul24(shared.data[spos + 6], shared.task.coefs[6]) + __mul24(shared.data[spos + 7], shared.task.coefs[7]) + +// //__mul24(shared.data[spos + 8], shared.task.coefs[8]) + __mul24(shared.data[spos + 9], shared.task.coefs[9]) + +// //__mul24(shared.data[spos + 10], shared.task.coefs[10]) + __mul24(shared.data[spos + 11], shared.task.coefs[11]) + +// //__mul24(shared.data[spos + 12], shared.task.coefs[12]) + __mul24(shared.data[spos + 13], shared.task.coefs[13]) + +// //__mul24(shared.data[spos + 14], shared.task.coefs[14]) + __mul24(shared.data[spos + 15], shared.task.coefs[15]); +// s = shared.data[32 + tid] - (s >> shared.task.data.shift); +// +// if (get_group_id(0) != 0 || tid >= shared.task.data.residualOrder) +// residual[shared.task.data.residualOffs + (get_group_id(0) << 8) + tid] = s; +// else +// s = 0; +// +// // convert to unsigned +// s = min(0xfffff, (s << 1) ^ (s >> 31)); +// barrier(CLK_LOCAL_MEM_FENCE); +// shared.data[tid + get_local_id(1)] = s; +// barrier(CLK_LOCAL_MEM_FENCE); +// +// // calc number of unary bits for part get_local_id(0) with rice paramater get_local_id(1) +// int dpos = __mul24(get_local_id(0), 17); +// int sum = +// (shared.data[dpos + 0] >> get_local_id(1)) + (shared.data[dpos + 1] >> get_local_id(1)) + +// (shared.data[dpos + 2] >> get_local_id(1)) + (shared.data[dpos + 3] >> get_local_id(1)) + +// (shared.data[dpos + 4] >> get_local_id(1)) + (shared.data[dpos + 5] >> get_local_id(1)) + +// (shared.data[dpos + 6] >> get_local_id(1)) + (shared.data[dpos + 7] >> get_local_id(1)) + +// (shared.data[dpos + 8] >> get_local_id(1)) + (shared.data[dpos + 9] >> get_local_id(1)) + +// (shared.data[dpos + 10] >> get_local_id(1)) + (shared.data[dpos + 11] >> get_local_id(1)) + +// (shared.data[dpos + 12] >> get_local_id(1)) + (shared.data[dpos + 13] >> get_local_id(1)) + +// (shared.data[dpos + 14] >> get_local_id(1)) + (shared.data[dpos + 15] >> get_local_id(1)); +// +// // output length +// const int pos = ((15 * get_group_id(1) + get_local_id(1)) << (max_porder + 1)) + (get_group_id(0) << 4) + get_local_id(0); +// if (get_local_id(1) <= 14) +// partition_lengths[pos] = sum + (16 - shared.task.data.residualOrder * (get_local_id(0) + get_group_id(0) == 0)) * (get_local_id(1) + 1); +//} +// +//__kernel void cudaCalcLargePartition( +// int* partition_lengths, +// int* residual, +// int* samples, +// FLACCLSubframeTask *tasks, +// int max_porder, // <= 8 +// int psize, // == >= 128 +// int parts_per_block // == 1 +// ) +//{ +// __local struct { +// int data[256]; +// volatile int length[256]; +// FLACCLSubframeTask task; +// } shared; +// const int tid = get_local_id(0) + (get_local_id(1) << 4); +// if (tid < sizeof(shared.task) / sizeof(int)) +// ((int*)&shared.task)[tid] = ((int*)(&tasks[get_group_id(1)]))[tid]; +// barrier(CLK_LOCAL_MEM_FENCE); +// +// int sum = 0; +// for (int pos = 0; pos < psize; pos += 256) +// { +// // fetch residual +// int offs = get_group_id(0) * psize + pos + tid; +// int s = (offs >= shared.task.data.residualOrder && pos + tid < psize) ? residual[shared.task.data.residualOffs + offs] : 0; +// // convert to unsigned +// shared.data[tid] = min(0xfffff, (s << 1) ^ (s >> 31)); +// barrier(CLK_LOCAL_MEM_FENCE); +// +// // calc number of unary bits for each residual sample with each rice paramater +//#pragma unroll 0 +// for (int i = get_local_id(0); i < min(psize,256); i += 16) +// // for sample (i + get_local_id(0)) with this rice paramater (get_local_id(1)) +// sum += shared.data[i] >> get_local_id(1); +// barrier(CLK_LOCAL_MEM_FENCE); +// } +// shared.length[tid] = min(0xfffff,sum); +// SUM16(shared.length,tid,+=); +// +// // output length +// const int pos = (15 << (max_porder + 1)) * get_group_id(1) + (get_local_id(1) << (max_porder + 1)); +// if (get_local_id(1) <= 14 && get_local_id(0) == 0) +// partition_lengths[pos + get_group_id(0)] = min(0xfffff,shared.length[tid]) + (psize - shared.task.data.residualOrder * (get_group_id(0) == 0)) * (get_local_id(1) + 1); +//} +// +//// Sums partition lengths for a certain k == get_group_id(0) +//// Requires 128 threads +//__kernel void cudaSumPartition( +// int* partition_lengths, +// int max_porder +// ) +//{ +// __local struct { +// volatile int data[512+32]; // max_porder <= 8, data length <= 1 << 9. +// } shared; +// +// const int pos = (15 << (max_porder + 1)) * get_group_id(1) + (get_group_id(0) << (max_porder + 1)); +// +// // fetch partition lengths +// shared.data[get_local_id(0)] = get_local_id(0) < (1 << max_porder) ? partition_lengths[pos + get_local_id(0)] : 0; +// shared.data[get_local_size(0) + get_local_id(0)] = get_local_size(0) + get_local_id(0) < (1 << max_porder) ? partition_lengths[pos + get_local_size(0) + get_local_id(0)] : 0; +// barrier(CLK_LOCAL_MEM_FENCE); +// +// int in_pos = (get_local_id(0) << 1); +// int out_pos = (1 << max_porder) + get_local_id(0); +// int bs; +// for (bs = 1 << (max_porder - 1); bs > 32; bs >>= 1) +// { +// if (get_local_id(0) < bs) shared.data[out_pos] = shared.data[in_pos] + shared.data[in_pos + 1]; +// in_pos += bs << 1; +// out_pos += bs; +// barrier(CLK_LOCAL_MEM_FENCE); +// } +// if (get_local_id(0) < 32) +// for (; bs > 0; bs >>= 1) +// { +// shared.data[out_pos] = shared.data[in_pos] + shared.data[in_pos + 1]; +// in_pos += bs << 1; +// out_pos += bs; +// } +// barrier(CLK_LOCAL_MEM_FENCE); +// if (get_local_id(0) < (1 << max_porder)) +// partition_lengths[pos + (1 << max_porder) + get_local_id(0)] = shared.data[(1 << max_porder) + get_local_id(0)]; +// if (get_local_size(0) + get_local_id(0) < (1 << max_porder)) +// partition_lengths[pos + (1 << max_porder) + get_local_size(0) + get_local_id(0)] = shared.data[(1 << max_porder) + get_local_size(0) + get_local_id(0)]; +//} +// +//// Finds optimal rice parameter for up to 16 partitions at a time. +//// Requires 16x16 threads +//__kernel void cudaFindRiceParameter( +// int* rice_parameters, +// int* partition_lengths, +// int max_porder +// ) +//{ +// __local struct { +// volatile int length[256]; +// volatile int index[256]; +// } shared; +// const int tid = get_local_id(0) + (get_local_id(1) << 5); +// const int parts = min(32, 2 << max_porder); +// const int pos = (15 << (max_porder + 1)) * get_group_id(1) + (get_local_id(1) << (max_porder + 1)); +// +// // read length for 32 partitions +// int l1 = (get_local_id(0) < parts) ? partition_lengths[pos + get_group_id(0) * 32 + get_local_id(0)] : 0xffffff; +// int l2 = (get_local_id(1) + 8 <= 14 && get_local_id(0) < parts) ? partition_lengths[pos + (8 << (max_porder + 1)) + get_group_id(0) * 32 + get_local_id(0)] : 0xffffff; +// // find best rice parameter +// shared.index[tid] = get_local_id(1) + ((l2 < l1) << 3); +// shared.length[tid] = l1 = min(l1, l2); +// barrier(CLK_LOCAL_MEM_FENCE); +//#pragma unroll 3 +// for (int sh = 7; sh >= 5; sh --) +// { +// if (tid < (1 << sh)) +// { +// l2 = shared.length[tid + (1 << sh)]; +// shared.index[tid] = shared.index[tid + ((l2 < l1) << sh)]; +// shared.length[tid] = l1 = min(l1, l2); +// } +// barrier(CLK_LOCAL_MEM_FENCE); +// } +// if (tid < parts) +// { +// // output rice parameter +// rice_parameters[(get_group_id(1) << (max_porder + 2)) + get_group_id(0) * parts + tid] = shared.index[tid]; +// // output length +// rice_parameters[(get_group_id(1) << (max_porder + 2)) + (1 << (max_porder + 1)) + get_group_id(0) * parts + tid] = shared.length[tid]; +// } +//} +// +//__kernel void cudaFindPartitionOrder( +// int* best_rice_parameters, +// FLACCLSubframeTask *tasks, +// int* rice_parameters, +// int max_porder +// ) +//{ +// __local struct { +// int data[512]; +// volatile int tmp[256]; +// int length[32]; +// int index[32]; +// //char4 ch[64]; +// FLACCLSubframeTask task; +// } shared; +// const int pos = (get_group_id(1) << (max_porder + 2)) + (2 << max_porder); +// if (get_local_id(0) < sizeof(shared.task) / sizeof(int)) +// ((int*)&shared.task)[get_local_id(0)] = ((int*)(&tasks[get_group_id(1)]))[get_local_id(0)]; +// // fetch partition lengths +// shared.data[get_local_id(0)] = get_local_id(0) < (2 << max_porder) ? rice_parameters[pos + get_local_id(0)] : 0; +// shared.data[get_local_id(0) + 256] = get_local_id(0) + 256 < (2 << max_porder) ? rice_parameters[pos + 256 + get_local_id(0)] : 0; +// barrier(CLK_LOCAL_MEM_FENCE); +// +// for (int porder = max_porder; porder >= 0; porder--) +// { +// shared.tmp[get_local_id(0)] = (get_local_id(0) < (1 << porder)) * shared.data[(2 << max_porder) - (2 << porder) + get_local_id(0)]; +// barrier(CLK_LOCAL_MEM_FENCE); +// SUM256(shared.tmp, get_local_id(0), +=); +// if (get_local_id(0) == 0) +// shared.length[porder] = shared.tmp[0] + (4 << porder); +// barrier(CLK_LOCAL_MEM_FENCE); +// } +// +// if (get_local_id(0) < 32) +// { +// shared.index[get_local_id(0)] = get_local_id(0); +// if (get_local_id(0) > max_porder) +// shared.length[get_local_id(0)] = 0xfffffff; +// int l1 = shared.length[get_local_id(0)]; +// #pragma unroll 4 +// for (int sh = 3; sh >= 0; sh --) +// { +// int l2 = shared.length[get_local_id(0) + (1 << sh)]; +// shared.index[get_local_id(0)] = shared.index[get_local_id(0) + ((l2 < l1) << sh)]; +// shared.length[get_local_id(0)] = l1 = min(l1, l2); +// } +// if (get_local_id(0) == 0) +// tasks[get_group_id(1)].data.porder = shared.index[0]; +// if (get_local_id(0) == 0) +// { +// int obits = shared.task.data.obits - shared.task.data.wbits; +// tasks[get_group_id(1)].data.size = +// shared.task.data.type == Fixed ? shared.task.data.residualOrder * obits + 6 + l1 : +// shared.task.data.type == LPC ? shared.task.data.residualOrder * obits + 6 + l1 + 4 + 5 + shared.task.data.residualOrder * shared.task.data.cbits : +// shared.task.data.type == Constant ? obits : obits * shared.task.data.blocksize; +// } +// } +// barrier(CLK_LOCAL_MEM_FENCE); +// int porder = shared.index[0]; +// if (get_local_id(0) < (1 << porder)) +// best_rice_parameters[(get_group_id(1) << max_porder) + get_local_id(0)] = rice_parameters[pos - (2 << porder) + get_local_id(0)]; +// // FIXME: should be bytes? +// // if (get_local_id(0) < (1 << porder)) +// //shared.tmp[get_local_id(0)] = rice_parameters[pos - (2 << porder) + get_local_id(0)]; +// // barrier(CLK_LOCAL_MEM_FENCE); +// // if (get_local_id(0) < max(1, (1 << porder) >> 2)) +// // { +// //char4 ch; +// //ch.x = shared.tmp[(get_local_id(0) << 2)]; +// //ch.y = shared.tmp[(get_local_id(0) << 2) + 1]; +// //ch.z = shared.tmp[(get_local_id(0) << 2) + 2]; +// //ch.w = shared.tmp[(get_local_id(0) << 2) + 3]; +// //shared.ch[get_local_id(0)] = ch +// // } +// // barrier(CLK_LOCAL_MEM_FENCE); +// // if (get_local_id(0) < max(1, (1 << porder) >> 2)) +// //best_rice_parameters[(get_group_id(1) << max_porder) + get_local_id(0)] = shared.ch[get_local_id(0)]; +//} +// +//#endif +// +//#if 0 +// if (get_local_id(0) < order) +// { +// for (int i = 0; i < order; i++) +// if (get_local_id(0) >= i) +// sum[get_local_id(0) - i] += coefs[get_local_id(0)] * sample[order - i - 1]; +// fot (int i = order; i < blocksize; i++) +// { +// if (!get_local_id(0)) sample[order + i] = s = residual[order + i] + (sum[order + i] >> shift); +// sum[get_local_id(0) + i + 1] += coefs[get_local_id(0)] * s; +// } +// } +//#endif +#endif