private static ChannelType[] GetChannelTypes(HcaInfo hca) { int channelsPerTrack = hca.ChannelCount / hca.TrackCount; if (hca.StereoBandCount == 0 || channelsPerTrack == 1) { return(new ChannelType[8]); } switch (channelsPerTrack) { case 2: return(new[] { StereoPrimary, StereoSecondary }); case 3: return(new[] { StereoPrimary, StereoSecondary, Discrete }); case 4 when hca.ChannelConfig != 0: return(new[] { StereoPrimary, StereoSecondary, Discrete, Discrete }); case 4 when hca.ChannelConfig == 0: return(new[] { StereoPrimary, StereoSecondary, StereoPrimary, StereoSecondary }); case 5 when hca.ChannelConfig > 2: return(new[] { StereoPrimary, StereoSecondary, Discrete, Discrete, Discrete }); case 5 when hca.ChannelConfig <= 2: return(new[] { StereoPrimary, StereoSecondary, Discrete, StereoPrimary, StereoSecondary }); case 6: return(new[] { StereoPrimary, StereoSecondary, Discrete, Discrete, StereoPrimary, StereoSecondary }); case 7: return(new[] { StereoPrimary, StereoSecondary, Discrete, Discrete, StereoPrimary, StereoSecondary, Discrete }); case 8: return(new[] { StereoPrimary, StereoSecondary, Discrete, Discrete, StereoPrimary, StereoSecondary, StereoPrimary, StereoSecondary }); default: return(new ChannelType[channelsPerTrack]); } }
public static void Crypt(HcaInfo hca, byte[][] audio, CriHcaKey key, bool doDecrypt) { for (int frame = 0; frame < hca.FrameCount; frame++) { CryptFrame(hca, audio[frame], key, doDecrypt); } }
public static void DecryptFrame(HcaInfo hca, byte[] audio, CriHcaKey key) { for (int b = 0; b < hca.FrameSize; b++) { audio[b] = key.DecryptionTable[audio[b]]; } }
/// <summary> /// Initializes this <see cref="CriHcaEncoder"/>. Any preexisting state is reset, and the encoder /// will be ready to accept PCM audio via <see cref="Encode"/>. /// </summary> /// <param name="config">The configuration to be used when creating the HCA file.</param> public void Initialize(CriHcaParameters config) { if (config.ChannelCount > 8) { throw new ArgumentOutOfRangeException(nameof(config.ChannelCount), "HCA channel count must be 8 or below"); } CutoffFrequency = config.SampleRate / 2; Quality = config.Quality; PostSamples = 128; Hca = new HcaInfo { ChannelCount = config.ChannelCount, TrackCount = 1, SampleCount = config.SampleCount, SampleRate = config.SampleRate, MinResolution = 1, MaxResolution = 15, InsertedSamples = SamplesPerSubFrame }; Bitrate = CalculateBitrate(Hca, Quality, config.Bitrate, config.LimitBitrate); CalculateBandCounts(Hca, Bitrate, CutoffFrequency); Hca.CalculateHfrValues(); SetChannelConfiguration(Hca); int inputSampleCount = Hca.SampleCount; if (config.Looping) { Hca.Looping = true; Hca.SampleCount = Math.Min(config.LoopEnd, config.SampleCount); Hca.InsertedSamples += GetNextMultiple(config.LoopStart, SamplesPerFrame) - config.LoopStart; CalculateLoopInfo(Hca, config.LoopStart, config.LoopEnd); inputSampleCount = Math.Min(GetNextMultiple(Hca.SampleCount, SamplesPerSubFrame), config.SampleCount); inputSampleCount += SamplesPerSubFrame * 2; PostSamples = inputSampleCount - Hca.SampleCount; } CalculateHeaderSize(Hca); int totalSamples = inputSampleCount + Hca.InsertedSamples; Hca.FrameCount = totalSamples.DivideByRoundUp(SamplesPerFrame); Hca.AppendedSamples = Hca.FrameCount * SamplesPerFrame - Hca.InsertedSamples - inputSampleCount; Frame = new CriHcaFrame(Hca); Channels = Frame.Channels; PcmBuffer = CreateJaggedArray <short[][]>(Hca.ChannelCount, SamplesPerFrame); PostAudio = CreateJaggedArray <short[][]>(Hca.ChannelCount, PostSamples); HcaOutputBuffer = new Queue <byte[]>(); BufferPreSamples = Hca.InsertedSamples - 128; }
public static void CryptFrame(HcaInfo hca, byte[] audio, CriHcaKey key, bool doDecrypt) { byte[] substitutionTable = doDecrypt ? key.DecryptionTable : key.EncryptionTable; for (int b = 0; b < hca.FrameSize - 2; b++) { audio[b] = substitutionTable[audio[b]]; } ushort crc = Crc.Compute(audio, hca.FrameSize - 2); audio[hca.FrameSize - 2] = (byte)(crc >> 8); audio[hca.FrameSize - 1] = (byte)crc; }
public static CriHcaKey FindKey(HcaInfo hca, byte[][] audio) { var frame = new CriHcaFrame(hca); var buffer = new byte[hca.FrameSize]; foreach (CriHcaKey key in Keys) { if (TestKey(frame, audio, key, buffer)) { return(key); } } return(null); }
private static void SetChannelConfiguration(HcaInfo hca, int channelConfig = -1) { int channelsPerTrack = hca.ChannelCount / hca.TrackCount; if (channelConfig == -1) { channelConfig = CriHcaTables.DefaultChannelMapping[channelsPerTrack]; } if (CriHcaTables.ValidChannelMappings[channelsPerTrack - 1][channelConfig] != 1) { throw new ArgumentOutOfRangeException(nameof(channelConfig), "Channel mapping is not valid."); } hca.ChannelConfig = channelConfig; }
private static void CalculateLoopInfo(HcaInfo hca, int loopStart, int loopEnd) { loopStart += hca.InsertedSamples; loopEnd += hca.InsertedSamples; hca.LoopStartFrame = loopStart / SamplesPerFrame; hca.PreLoopSamples = loopStart % SamplesPerFrame; hca.LoopEndFrame = loopEnd / SamplesPerFrame; hca.PostLoopSamples = SamplesPerFrame - loopEnd % SamplesPerFrame; if (hca.PostLoopSamples == SamplesPerFrame) { hca.LoopEndFrame--; hca.PostLoopSamples = 0; } }
private static void CalculateHfrScale(CriHcaFrame frame) { HcaInfo hca = frame.Hca; if (hca.HfrGroupCount == 0) { return; } int hfrStartBand = hca.StereoBandCount + hca.BaseBandCount; int hfrBandCount = Math.Min(hca.HfrBandCount, hca.TotalBandCount - hca.HfrBandCount); foreach (CriHcaChannel channel in frame.Channels) { if (channel.Type == ChannelType.StereoSecondary) { continue; } double[] groupSpectra = channel.HfrGroupAverageSpectra; for (int group = 0, band = 0; group < hca.HfrGroupCount; group++) { double sum = 0.0; int count = 0; for (int i = 0; i < hca.BandsPerHfrGroup && band < hfrBandCount; band++, i++) { for (int subframe = 0; subframe < SubframesPerFrame; subframe++) { sum += Math.Abs(channel.ScaledSpectra[hfrStartBand - band - 1][subframe]); } count += SubframesPerFrame; } double averageSpectra = sum / count; if (averageSpectra > 0.0) { groupSpectra[group] *= Math.Min(1.0 / averageSpectra, Math.Sqrt(2)); } channel.HfrScales[group] = FindScaleFactor(groupSpectra[group]); } } }
public static short[][] Decode(HcaInfo hca, byte[][] audio, CriHcaParameters config = null) { config?.Progress?.SetTotal(hca.FrameCount); var pcmOut = Helpers.CreateJaggedArray <short[][]>(hca.ChannelCount, hca.SampleCount); var pcmBuffer = Helpers.CreateJaggedArray <short[][]>(hca.ChannelCount, SamplesPerFrame); var frame = new CriHcaFrame(hca); for (int i = 0; i < hca.FrameCount; i++) { DecodeFrame(audio[i], frame, pcmBuffer); CopyPcmToOutput(pcmBuffer, pcmOut, hca, i); //CopyBuffer(pcmBuffer, pcmOut, hca.InsertedSamples, i); config?.Progress?.ReportAdd(1); } return(pcmOut); }
private static void CopyPcmToOutput(short[][] pcmIn, short[][] pcmOut, HcaInfo hca, int frame) { int currentSample = frame * SamplesPerFrame - hca.InsertedSamples; int remainingSamples = Math.Min(hca.SampleCount - currentSample, hca.SampleCount); int srcStart = Helpers.Clamp(0 - currentSample, 0, SamplesPerFrame); int destStart = Math.Max(currentSample, 0); int length = Math.Min(SamplesPerFrame - srcStart, remainingSamples); if (length <= 0) { return; } for (int c = 0; c < pcmOut.Length; c++) { Array.Copy(pcmIn[c], srcStart, pcmOut[c], destStart, length); } }
public CriHcaFrame(HcaInfo hca) { Hca = hca; ChannelType[] channelTypes = GetChannelTypes(hca); Channels = new CriHcaChannel[hca.ChannelCount]; for (int i = 0; i < Channels.Length; i++) { Channels[i] = new CriHcaChannel { Type = channelTypes[i], CodedScaleFactorCount = channelTypes[i] == StereoSecondary ? hca.BaseBandCount : hca.BaseBandCount + hca.StereoBandCount }; } AthCurve = hca.UseAthCurve ? ScaleAthCurve(hca.SampleRate) : new byte[SamplesPerSubFrame]; }
private static void CalculateHeaderSize(HcaInfo hca) { const int baseHeaderSize = 96; const int baseHeaderAlignment = 32; const int loopFrameAlignment = 2048; hca.HeaderSize = GetNextMultiple(baseHeaderSize + hca.CommentLength, baseHeaderAlignment); if (hca.Looping) { int loopFrameOffset = hca.HeaderSize + hca.FrameSize * hca.LoopStartFrame; int paddingBytes = GetNextMultiple(loopFrameOffset, loopFrameAlignment) - loopFrameOffset; int paddingFrames = paddingBytes / hca.FrameSize; hca.InsertedSamples += paddingFrames * SamplesPerFrame; hca.LoopStartFrame += paddingFrames; hca.LoopEndFrame += paddingFrames; hca.HeaderSize += paddingBytes % hca.FrameSize; } }
private static void CalculateBandCounts(HcaInfo hca, int bitrate, int cutoffFreq) { hca.FrameSize = bitrate * 1024 / hca.SampleRate / 8; int numGroups = 0; int pcmBitrate = hca.SampleRate * hca.ChannelCount * 16; int hfrRatio; // HFR is used at bitrates below (pcmBitrate / hfrRatio) int cutoffRatio; // The cutoff frequency is lowered at bitrates below (pcmBitrate / cutoffRatio) if (hca.ChannelCount <= 1 || pcmBitrate / bitrate <= 6) { hfrRatio = 6; cutoffRatio = 12; } else { hfrRatio = 8; cutoffRatio = 16; } if (bitrate < pcmBitrate / cutoffRatio) { cutoffFreq = Math.Min(cutoffFreq, cutoffRatio * bitrate / (32 * hca.ChannelCount)); } int totalBandCount = (int)Math.Round(cutoffFreq * 256.0 / hca.SampleRate); int hfrStartBand = (int)Math.Min(totalBandCount, Math.Round((hfrRatio * bitrate * 128.0) / pcmBitrate)); int stereoStartBand = hfrRatio == 6 ? hfrStartBand : (hfrStartBand + 1) / 2; int hfrBandCount = totalBandCount - hfrStartBand; int bandsPerGroup = hfrBandCount.DivideByRoundUp(8); if (bandsPerGroup > 0) { numGroups = hfrBandCount.DivideByRoundUp(bandsPerGroup); } hca.TotalBandCount = totalBandCount; hca.BaseBandCount = stereoStartBand; hca.StereoBandCount = hfrStartBand - stereoStartBand; hca.HfrGroupCount = numGroups; hca.BandsPerHfrGroup = bandsPerGroup; }
private int CalculateBitrate(HcaInfo hca, CriHcaQuality quality, int bitrate, bool limitBitrate) { int pcmBitrate = Hca.SampleRate * Hca.ChannelCount * 16; int maxBitrate = pcmBitrate / 4; int minBitrate = 0; int compressionRatio = 6; switch (quality) { case CriHcaQuality.Highest: compressionRatio = 4; break; case CriHcaQuality.High: compressionRatio = 6; break; case CriHcaQuality.Middle: compressionRatio = 8; break; case CriHcaQuality.Low: compressionRatio = hca.ChannelCount == 1 ? 10 : 12; break; case CriHcaQuality.Lowest: compressionRatio = hca.ChannelCount == 1 ? 12 : 16; break; } bitrate = bitrate != 0 ? bitrate : pcmBitrate / compressionRatio; if (limitBitrate) { minBitrate = Math.Min( hca.ChannelCount == 1 ? 42666 : 32000 * hca.ChannelCount, pcmBitrate / 6); } return(Clamp(bitrate, minBitrate, maxBitrate)); }
private static void ReconstructHighFrequency(CriHcaFrame frame) { HcaInfo hca = frame.Hca; if (hca.HfrGroupCount == 0) { return; } // The last spectral coefficient should always be 0; int totalBandCount = Math.Min(hca.TotalBandCount, 127); int hfrStartBand = hca.BaseBandCount + hca.StereoBandCount; int hfrBandCount = Math.Min(hca.HfrBandCount, totalBandCount - hca.HfrBandCount); foreach (CriHcaChannel channel in frame.Channels) { if (channel.Type == ChannelType.StereoSecondary) { continue; } for (int group = 0, band = 0; group < hca.HfrGroupCount; group++) { for (int i = 0; i < hca.BandsPerHfrGroup && band < hfrBandCount; band++, i++) { int highBand = hfrStartBand + band; int lowBand = hfrStartBand - band - 1; int index = channel.HfrScales[group] - channel.ScaleFactors[lowBand] + 64; for (int sf = 0; sf < SubframesPerFrame; sf++) { channel.Spectra[sf][highBand] = ScaleConversionTable[index] * channel.Spectra[sf][lowBand]; } } } } }
private static void CalculateHfrGroupAverages(CriHcaFrame frame) { HcaInfo hca = frame.Hca; if (hca.HfrGroupCount == 0) { return; } int hfrStartBand = hca.StereoBandCount + hca.BaseBandCount; foreach (CriHcaChannel channel in frame.Channels) { if (channel.Type == ChannelType.StereoSecondary) { continue; } for (int group = 0, band = hfrStartBand; group < hca.HfrGroupCount; group++) { double sum = 0.0; int count = 0; for (int i = 0; i < hca.BandsPerHfrGroup && band < SamplesPerSubFrame; band++, i++) { for (int subframe = 0; subframe < SubframesPerFrame; subframe++) { sum += Math.Abs(channel.Spectra[subframe][band]); } count += SubframesPerFrame; } channel.HfrGroupAverageSpectra[group] = sum / count; } } }