Files
cuetools.net/CUETools.Codecs.ALAC/ALACDotNet.cs
Wolfgang Stöggl 66b533c284 Bump copyright year to 2020
The copyright year was last time updated in 2018. There is some cleanup
involved in this commit and the next copyright year update is supposed
to be simpler (i.e. substitute "-2020").

- Substitute occurrences of "-2018" with "-2020" using:
  git grep -I -l -e '-2018' -- ':(exclude)*.bak' | xargs \
  sed -b -i -e 's/-2018/-2020/g'

- Update special cases:
  CUEPlayer
    git grep -I -l -e 'Grigory Chudov 2010' -- | xargs \
    sed -b -i -e 's/Grigory Chudov 2010/2010-2020 Grigory Chudov/g'
  CUERipper
    git grep -I -l -e '2008-2009' -- | xargs \
    sed -b -i -e 's/2008-2009/2008-2020/g'
  CUETools, CUETools.FLACCL.cmd
    git grep -I -l -e '2008-2010' -- ':(exclude)*FlaCuda*' | xargs \
    sed -b -i -e 's/2008-2010/2008-2020/g'
    git grep -I -l -e '2010-2013' -- | xargs \
    sed -b -i -e 's/2010-2013/2010-2020/g'
  CUETools.ChaptersToCue
    git grep -I -l -e 'Grigory Chudov 2017' -- | xargs \
    sed -b -i -e 's/Grigory Chudov 2017/2017-2020 Grigory Chudov/g'
  CUETools.CTDB.EACPlugin
    git grep -I -l -e 'Grigory Chudov 2012' -- | xargs \
    sed -b -i -e 's/Grigory Chudov 2012/2012-2020 Grigory Chudov/g'
    git grep -I -l -e '2011-12' -- | xargs \
    sed -b -i -e 's/2011-12/2011-2020/g'
  CUETools.Codecs.FLACCL
    git grep -I -l -e '2009-2010' -- ':(exclude)*FlaCuda*' | xargs \
    sed -b -i -e 's/2009-2010/2009-2020/g'
  CUETools.eac3ui (BluTools)
    git grep -I -l -e '©  2018' -- | xargs \
    sed -b -i -e 's/©  2018/© 2018-2020 Grigory Chudov/g'
  CUETools.Flake
    git grep -I -l -e ' 2009-2014 Gr' -- | xargs \
    sed -b -i -e 's/ 2009-2014 Gr/ 2009-2020 Gr/g'
  CUETools.Processor
    git grep -I -l -e ' 2008-2013  Gr' -- | xargs \
    sed -b -i -e 's/ 2008-2013  Gr/ 2008-2020 Gr/g'
  CUETools.Ripper.Console
    git grep -I -l -e ' 2008-10 Gr' -- | xargs \
    sed -b -i -e 's/ 2008-10 Gr/ 2008-2020 Gr/g'
  CUETools.Ripper.Console, CUETools.Ripper.SCSI
    git grep -I -l -e ' 2008-13 Gr' -- | xargs \
    sed -b -i -e 's/ 2008-13 Gr/ 2008-2020 Gr/g'

  Single year entries: 2008, 2009, 2010, 2011, 2017, 2018
    git grep -I -l -e ' 2008 Gr' -- | xargs \
    sed -b -i -e 's/ 2008 Gr/ 2008-2020 Gr/g'
    git grep -I -l -e ' 2009 Gr' -- ':(exclude)*FlaCuda*' | xargs \
    sed -b -i -e 's/ 2009 Gr/ 2009-2020 Gr/g'
    git grep -I -l -e ' 2010 Gr' -- | xargs \
    sed -b -i -e 's/ 2010 Gr/ 2010-2020 Gr/g'
    git grep -I -l -e ' 2011 Gr' -- | xargs \
    sed -b -i -e 's/ 2011 Gr/ 2011-2020 Gr/g'
    git grep -I -l -e ' 2017 Gr' -- | xargs \
    sed -b -i -e 's/ 2017 Gr/ 2017-2020 Gr/g'
    git grep -I -l -e ' 2018 Gr' -- | xargs \
    sed -b -i -e 's/ 2018 Gr/ 2018-2020 Gr/g'

  Fix typo in copyright year of CUETools.Codecs.WMA/AudioDecoder.cs:
    Copyright (c) 20139 Grigory Chudov
    git grep -I -lw -e '20139' -- | xargs \
    sed -b -i -e 's/20139/2013-2020/g'
2020-02-04 13:35:53 +01:00

1310 lines
42 KiB
C#
Raw Blame History

using System;
using System.Text;
using System.IO;
using System.Collections.Generic;
using System.Collections.Specialized;
using CUETools.Codecs;
using System.ComponentModel;
using Newtonsoft.Json;
//Copyright (c) 2008-2020 Grigory Chudov.
//This library is based on ALAC decoder by David Hammerton.
//See http://crazney.net/programs/itunes/alac.html for details.
//Copyright (c) 2004 David Hammerton.
//Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is furnished
// to do so, subject to the following conditions:
//The above copyright notice and this permission notice shall be included in all
//copies or substantial portions of the Software.
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
namespace CUETools.Codecs.ALAC
{
[JsonObject(MemberSerialization.OptIn)]
public class DecoderSettings : IAudioDecoderSettings
{
#region IAudioDecoderSettings implementation
[Browsable(false)]
public string Extension => "m4a";
[Browsable(false)]
public string Name => "cuetools";
[Browsable(false)]
public Type DecoderType => typeof(AudioDecoder);
[Browsable(false)]
public int Priority => 2;
public IAudioDecoderSettings Clone()
{
return MemberwiseClone() as IAudioDecoderSettings;
}
#endregion
public DecoderSettings()
{
this.Init();
}
}
public class AudioDecoder : IAudioSource
{
public AudioDecoder(DecoderSettings settings, string path, Stream IO = null)
{
m_settings = settings;
_path = path;
_IO = IO ?? new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
_buff = new byte[512];
_tags = new NameValueCollection();
qtmovie_read();
if (!_formatRead || pcm.BitsPerSample != 16 || pcm.ChannelCount != 2 || pcm.SampleRate != 44100)
throw new Exception("Invalid ALAC file.");
_saved_mdat_pos = _IO.Position;
calculate_length();
}
public AudioDecoder(AudioPCMConfig _pcm, int rice_historymult, int rice_initialhistory, int rice_kmodifier, int blocksize)
{
pcm = _pcm;
setinfo_max_samples_per_frame = blocksize;
setinfo_rice_historymult = (byte)rice_historymult;
setinfo_rice_initialhistory = (byte)rice_initialhistory;
setinfo_rice_kmodifier = (byte)rice_kmodifier;
_predicterror_buffer_a = new int[setinfo_max_samples_per_frame];
_predicterror_buffer_b = new int[setinfo_max_samples_per_frame];
_outputsamples_buffer_a = new int[setinfo_max_samples_per_frame];
_outputsamples_buffer_b = new int[setinfo_max_samples_per_frame];
_framesBuffer = new byte[65536];
}
private DecoderSettings m_settings;
public IAudioDecoderSettings Settings => m_settings;
private void InitTables()
{
if (_predicterror_buffer_a != null)
return;
setinfo_max_samples_per_frame = (int)read_uint32(_codecData, 24);
byte setinfo_7a = read_uint8(_codecData, 28);
byte setinfo_sample_size = read_uint8(_codecData, 29);
setinfo_rice_historymult = read_uint8(_codecData, 30);
setinfo_rice_initialhistory = read_uint8(_codecData, 31);
setinfo_rice_kmodifier = read_uint8(_codecData, 32);
byte setinfo_7f = read_uint8(_codecData, 33);
ushort setinfo_80 = read_uint16(_codecData, 34);
uint setinfo_82 = read_uint32(_codecData, 36); // maxframesize
uint setinfo_86 = read_uint32(_codecData, 40); // bitrate
uint setinfo_8a_rate = read_uint32(_codecData, 44); // samplerate
_predicterror_buffer_a = new int[setinfo_max_samples_per_frame];
_predicterror_buffer_b = new int[setinfo_max_samples_per_frame];
_outputsamples_buffer_a = new int[setinfo_max_samples_per_frame];
_outputsamples_buffer_b = new int[setinfo_max_samples_per_frame];
_samplesInBuffer = 0;
_framesBuffer = new byte[65536];
}
public int Read(AudioBuffer buff, int maxLength)
{
InitTables();
buff.Prepare(this, maxLength);
int offset = 0;
int sampleCount = buff.Length;
while (_samplesInBuffer < sampleCount)
{
if (_samplesInBuffer > 0)
{
deinterlace(buff.Samples, offset, _samplesInBuffer);
sampleCount -= _samplesInBuffer;
offset += _samplesInBuffer;
_samplesInBuffer = 0;
_samplesBufferOffset = 0;
}
int sampleDuration;
int sampleSize;
if (_iSample >= _sample_byte_size.Length)
return buff.Length = offset;
get_sample_info(_iSample, out sampleDuration, out sampleSize);
_IO.Read(_framesBuffer, 0, sampleSize);
decodeFrame(sampleSize);
if (sampleDuration != _samplesInBuffer)
throw new Exception("sample count mismatch");
_samplesInBuffer -= _samplesBufferOffset;
_sampleOffset += _samplesInBuffer;
_iSample++;
}
deinterlace(buff.Samples, offset, sampleCount);
_samplesInBuffer -= sampleCount;
_samplesBufferOffset += sampleCount;
if (_samplesInBuffer == 0)
_samplesBufferOffset = 0;
return buff.Length = offset + sampleCount;
}
public void Close()
{
_IO.Close();
}
public TimeSpan Duration => Length < 0 ? TimeSpan.Zero : TimeSpan.FromSeconds((double)Length / PCM.SampleRate);
public long Length
{
get
{
return _sampleCount;
}
}
public long Remaining
{
get
{
return _sampleCount - _sampleOffset + _samplesInBuffer;
}
}
public long Position
{
get
{
return _sampleOffset - _samplesInBuffer;
}
set
{
_sampleOffset = value;
_samplesInBuffer = 0;
_samplesBufferOffset = 0;
_iSample = 0;
long durOffs = 0;
int sampleDuration;
long fileOffs = 0;
int sampleSize;
do
{
if (durOffs == value)
{
_IO.Position = _saved_mdat_pos + fileOffs;
return;
}
if ((int)_iSample >= _sample_byte_size.Length)
throw new Exception("seeking past end of stream");
get_sample_info(_iSample, out sampleDuration, out sampleSize);
durOffs += sampleDuration;
fileOffs += sampleSize;
_iSample++;
} while (durOffs <= value);
_IO.Position = _saved_mdat_pos + fileOffs - sampleSize;
_samplesBufferOffset = (int) (value + sampleDuration - durOffs);
_iSample--;
}
}
public AudioPCMConfig PCM { get { return pcm; } }
public string Path
{
get
{
return _path;
}
}
private void get_sample_info(long iSample, out int sampleDuration, out int sampleSize)
{
// if (iSample >= _sample_byte_size.Length)
int duration_index_accum = 0;
int duration_cur_index = 0;
while (_time_to_sample_count[duration_cur_index] + duration_index_accum <= iSample)
{
duration_index_accum += _time_to_sample_count[duration_cur_index];
if (duration_cur_index == _time_to_sample_count.Length - 1)
throw new Exception("seeking past end of stream");
duration_cur_index ++;
}
sampleDuration = _time_to_sample_duration[duration_cur_index];
sampleSize = _sample_byte_size[iSample];
}
private void calculate_length()
{
_sampleCount = 0;
uint duration_cur_index = 0;
for (duration_cur_index = 0; duration_cur_index < _time_to_sample_count.Length; duration_cur_index++)
_sampleCount += _time_to_sample_count[duration_cur_index] * _time_to_sample_duration[duration_cur_index];
// try a work around for ffdshow-generated buggy files
if (_time_to_sample_count.Length == 1 && _IO.CanSeek)
{
int sample_count_0 = _time_to_sample_count[0] - 1;
int sample_duration_0 = _time_to_sample_duration[0];
Position = sample_count_0 * sample_duration_0;
int sampleDuration;
int sampleSize;
if ((int)_iSample < _sample_byte_size.Length)
{
get_sample_info(_iSample, out sampleDuration, out sampleSize);
InitTables();
_IO.Read(_framesBuffer, 0, (int)sampleSize);
decodeFrame(sampleSize);
if (_samplesInBuffer < sampleDuration)
{
_time_to_sample_duration = new int[2] { sample_duration_0, _samplesInBuffer };
_time_to_sample_count = new int[2] { sample_count_0, 1 };
_sampleCount -= sampleDuration - _samplesInBuffer;
}
}
Position = 0;
}
}
private byte [] stream_read_bytes(int len)
{
if (len > 512)
throw new Exception("Decoding failed.");
if (_IO.Read(_buff, 0, len) != len)
throw new Exception("Decoding failed.");
return _buff;
}
private uint read_uint32(byte [] buff, int pos)
{
return (uint)((buff[pos] << 24) + (buff[pos+1] << 16) + (buff[pos+2] << 8) + (buff[pos+3] << 0));
}
private uint stream_read_uint32()
{
return read_uint32(stream_read_bytes(4), 0);
}
private ushort read_uint16(byte[] buff, int pos)
{
return (ushort)((buff[pos] << 8) + buff[pos + 1]);
}
private ushort stream_read_uint16()
{
return read_uint16(stream_read_bytes(2), 0);
}
private byte read_uint8(byte[] buff, int pos)
{
return buff[pos];
}
private byte stream_read_uint8()
{
return stream_read_bytes(1)[0];
}
private void stream_skip (UInt32 skip)
{
_IO.Position += skip;
}
/* supports reading 1 to 24 bits, in big endian format */
private uint readbits_24 (byte []buff, ref int pos, int bits)
{
uint result = (((uint)buff[pos]) << 24) | (((uint)buff[pos + 1]) << 16) | (((uint)buff[pos + 2]) << 8) | ((uint)buff[pos + 3]);
result <<= _bitaccumulator;
result >>= 32 - bits;
int new_accumulator = (_bitaccumulator + bits);
pos += (new_accumulator >> 3);
_bitaccumulator = (new_accumulator & 7);
return result;
}
/* supports reading 1 to 24 bits, in big endian format */
private unsafe uint readbits_24(byte* buff, ref int pos, int bits)
{
uint result = (((uint)buff[pos]) << 24) | (((uint)buff[pos + 1]) << 16) | (((uint)buff[pos + 2]) << 8) | ((uint)buff[pos + 3]);
result <<= _bitaccumulator;
result >>= 32 - bits;
int new_accumulator = (_bitaccumulator + bits);
pos += (new_accumulator >> 3);
_bitaccumulator = (new_accumulator & 7);
return result;
}
/* supports reading 1 to 16 bits, in big endian format */
private unsafe uint peekbits_9(byte* buff, int pos)
{
uint result = (((uint)buff[pos]) << 8) | (((uint)buff[pos + 1]));
result <<= _bitaccumulator;
result &= 0x0000ffff;
result >>= 7;
return result;
}
/* supports reading 1 to 16 bits, in big endian format */
private void skipbits(ref int pos, int bits)
{
int new_accumulator = (_bitaccumulator + bits);
pos += (new_accumulator >> 3);
_bitaccumulator = (new_accumulator & 7);
}
/* supports reading 1 to 32 bits, in big endian format */
private uint readbits(byte[] buff, ref int pos, int bits)
{
if (bits <= 24)
return readbits_24(buff, ref pos, bits);
ulong result = (((ulong)buff[pos]) << 32) | (((ulong)buff[pos + 1]) << 24) | (((ulong)buff[pos + 2]) << 16) | (((ulong)buff[pos + 3]) << 8) | ((ulong)buff[pos + 4]);
result <<= _bitaccumulator;
result &= 0x00ffffffffff;
result >>= 40 - bits;
int new_accumulator = (_bitaccumulator + bits);
pos += (new_accumulator >> 3);
_bitaccumulator = (new_accumulator & 7);
return (uint)result;
}
/* supports reading 1 to 32 bits, in big endian format */
private unsafe uint readbits(byte * buff, ref int pos, int bits)
{
if (bits <= 24)
return readbits_24(buff, ref pos, bits);
ulong result = (((ulong)buff[pos]) << 32) | (((ulong)buff[pos + 1]) << 24) | (((ulong)buff[pos + 2]) << 16) | (((ulong)buff[pos + 3]) << 8) | ((ulong)buff[pos + 4]);
result <<= _bitaccumulator;
result &= 0x00ffffffffff;
result >>= 40 - bits;
int new_accumulator = (_bitaccumulator + bits);
pos += (new_accumulator >> 3);
_bitaccumulator = (new_accumulator & 7);
return (uint)result;
}
/* reads a single bit */
private uint readbit(byte[] buff, ref int pos)
{
int new_accumulator;
uint result = buff[pos];
result <<= _bitaccumulator;
result = result >> 7 & 1;
new_accumulator = (_bitaccumulator + 1);
pos += (new_accumulator / 8);
_bitaccumulator = (new_accumulator % 8);
return result;
}
private void unreadbits(ref int pos, int bits)
{
int new_accumulator = (_bitaccumulator - bits);
pos += (new_accumulator >> 3);
_bitaccumulator = (new_accumulator & 7);
if (_bitaccumulator < 0)
_bitaccumulator *= -1;
}
private static int count_leading_zeroes(uint input)
{
int zeroes = 0;
uint shifted_input = input >> 16;
if (shifted_input == 0)
zeroes += 16;
else
input = shifted_input;
shifted_input = input >> 8;
if (shifted_input == 0)
zeroes += 8;
else
input = shifted_input;
return zeroes + BitReader.byte_to_unary_table[input];
}
private unsafe void readPredictor(ref int pos, ref predictor_t predictor_info)
{
fixed (predictor_t* pr = &predictor_info)
{
pr->prediction_type = (int)readbits(_framesBuffer, ref pos, 4);
pr->prediction_quantitization = (int)readbits(_framesBuffer, ref pos, 4);
pr->ricemodifier = (int)readbits(_framesBuffer, ref pos, 3);
pr->predictor_coef_num = (int)readbits(_framesBuffer, ref pos, 5);
/* read the predictor table */
pr->predictor_coef_table_sum = 0;
for (int i = pr->predictor_coef_num - 1; i >= 0; i--)
{
pr->predictor_coef_table[i] = (short)readbits(_framesBuffer, ref pos, 16);
pr->predictor_coef_table_sum += pr->predictor_coef_table[i];
}
}
}
private unsafe int decode_scalar(byte * buff, ref int pos, int k, int limit, int readsamplesize)
{
uint next = peekbits_9(buff, pos);
int x = (next >> 8 == 0) ? 0 :
1 + BitReader.byte_to_unary_table[(~next) & 0xff];
if (x == 9) /* RICE THRESHOLD 9 bits */
{
skipbits(ref pos, 9);
return (int)readbits(buff, ref pos, readsamplesize);
}
skipbits(ref pos, x + 1);
if (k >= limit)
k = limit;
if (k == 1)
return x;
int extrabits = (int) readbits(buff, ref pos, k);
x = (x << k) - x; // /* multiply x by 2^k - 1, as part of their strange algorithm */
if (extrabits > 1)
return x + extrabits - 1;
unreadbits(ref pos, 1);
return x;
}
private unsafe void basterdised_rice_decompress(int output_size, ref int pos, ref predictor_t predictor_info, ref int[] predicterror_buffer, int readsamplesize)
{
fixed (predictor_t* pr = &predictor_info)
fixed (int* output_buffer = &predicterror_buffer[0])
fixed (byte* buff = &_framesBuffer[0])
{
uint history = setinfo_rice_initialhistory;
int rice_kmodifier = setinfo_rice_kmodifier;
int rice_historymult = pr->ricemodifier * setinfo_rice_historymult / 4;
int sign_modifier = 0;
for (int output_count = 0; output_count < output_size; output_count++)
{
int x = sign_modifier + decode_scalar(buff, ref pos, 31 - count_leading_zeroes((history >> 9) + 3), rice_kmodifier, readsamplesize);
output_buffer[output_count] = (x >> 1) ^ - (x & 1);
sign_modifier = 0;
/* now update the history */
history = (uint)(history + (x * rice_historymult)
- ((history * rice_historymult) >> 9));
if (x > 0xffff)
history = 0xffff;
/* special case: there may be compressed blocks of 0 */
if ((history < 128) && (output_count + 1 < output_size))
{
int k = 7 - (31 - count_leading_zeroes(history)) + (((int)history + 16) >> 6);
int block_size = decode_scalar(buff, ref pos, k, rice_kmodifier, 16);
if (block_size > 0)
{
if (output_count + 1 + block_size > output_size)
throw new Exception("buffer overflow: " + output_size.ToString() + " vs " + (output_count + 1 + block_size).ToString());
//block_size = (int) output_size - output_count - 1;
for (int p = 0; p < block_size; p++)
output_buffer[output_count + 1 + p] = 0;
output_count += block_size;
}
sign_modifier = block_size > 0xffff ? 0 : 1;
history = 0;
}
}
}
}
private static int extend_sign32(int val, int bits)
{
return (val << (32 - bits)) >> (32 - bits);
}
private static short sign_only(int v)
{
return (short)(1 - ((v >> 30) & 2));
}
private unsafe void predictor_decompress_fir_adapt(int output_size, ref predictor_t predictor_info, ref int[] error_buffer, ref int[] buffer_out, int readsamplesize)
{
int i;
fixed (predictor_t* pr = &predictor_info)
fixed (int* buf_out = &buffer_out[0], buf_err = &error_buffer[0])
{
if (pr->predictor_coef_num == 0)
{
for (i = 0; i < output_size; i++)
buf_out[i] = buf_err[i];
return;
}
int sample = 0;
if (pr->predictor_coef_num == 0x1f)
{ /* 11111 - max value of predictor_coef_num */
/* second-best case scenario for fir decompression,
* error describes a small difference from the previous sample only
*/
if (output_size <= 1)
return;
for (i = 0; i < output_size; i++)
{
sample = extend_sign32(sample + buf_err[i], readsamplesize);
buf_out[i] = sample;
}
return;
}
if (output_size <= predictor_info.predictor_coef_num || pr->predictor_coef_num < 0)
throw new Exception("invalid output size");
/* read warm-up samples */
for (i = 0; i <= predictor_info.predictor_coef_num; i++)
{
sample = extend_sign32(sample + buf_err[i], readsamplesize);
buf_out[i] = sample;
}
/* general case */
int* buf_pos = buf_out;
int predictor_coef_table_sum = pr->predictor_coef_table_sum;
for (i = (int)pr->predictor_coef_num + 1; i < output_size; i++)
{
int j;
int sum = 0;
int outval;
int error_val = buf_err[i];
int sample_val = *(buf_pos++);
for (j = 0; j < pr->predictor_coef_num; j++)
sum += buf_pos[j] * pr->predictor_coef_table[j];
sum -= predictor_coef_table_sum * sample_val;
outval = (1 << (pr->prediction_quantitization - 1)) + sum;
outval >>= pr->prediction_quantitization;
outval += sample_val + error_val;
buf_pos[pr->predictor_coef_num] = extend_sign32(outval, readsamplesize);
if (error_val != 0)
{
short error_sign = sign_only(error_val);
for (j = 0; j < pr->predictor_coef_num; j++)
{
int val = sample_val - buf_pos[j];
if (val == 0)
continue;
short sign = sign_only(error_sign * val);
pr->predictor_coef_table[j] -= sign;
predictor_coef_table_sum -= sign;
val *= sign; /* absolute value with same sign as error */
error_val -= (val >> pr->prediction_quantitization) * (j + 1);
if (error_val * error_sign <= 0)
break;
}
}
}
pr->predictor_coef_table_sum = predictor_coef_table_sum;
}
}
internal unsafe void deinterlace(int[,] samplesBuffer, int offset, int sampleCount)
{
if (sampleCount <= 0 || sampleCount > _samplesInBuffer)
return;
int i;
fixed (int* buf_a = &_outputsamples_buffer_a[_samplesBufferOffset], buf_b = &_outputsamples_buffer_b[_samplesBufferOffset])
fixed (int* buf_s = &samplesBuffer[offset, 0])
{
/* weighted interlacing */
if (_interlacing_leftweight != 0)
{
for (i = 0; i < sampleCount; i++)
{
int midright = buf_a[i];
int diff = buf_b[i];
midright -= (diff * _interlacing_leftweight) >> _interlacing_shift;
buf_s[i * 2] = midright + diff;
buf_s[i * 2 + 1] = midright;
#if DEBUG
if (buf_s[i * 2] >= (1 << pcm.BitsPerSample) || buf_s[i * 2] < -(1 << pcm.BitsPerSample) ||
buf_s[i * 2 + 1] >= (1 << pcm.BitsPerSample) || buf_s[i * 2 + 1] < -(1 << pcm.BitsPerSample)
)
throw new Exception("overflow in ALAC decoder");
#endif
}
return;
}
/* otherwise basic interlacing took place */
AudioSamples.Interlace(buf_s, buf_a, buf_b, sampleCount);
}
}
internal int DecodeFrame(byte[] buffer, int pos, int len)
{
Array.Copy(buffer, pos, _framesBuffer, 0, len);
decodeFrame(len);
return len; // pos
}
private void decodeFrame(int sampleSize)
{
_bitaccumulator = 0;
int pos = 0;
int channels = (int) readbits(_framesBuffer, ref pos, 3);
if (channels != 1)
throw new Exception("Not stereo");
readbits(_framesBuffer, ref pos, 4);
readbits(_framesBuffer, ref pos, 12); /* unknown, skip 12 bits */
bool hassize = 0 != readbits(_framesBuffer, ref pos, 1); /* the output sample size is stored soon */
int wasted_bytes = (int) readbits(_framesBuffer, ref pos, 2); /* unknown ? */
bool isnotcompressed = 0 != readbits(_framesBuffer, ref pos, 1); /* whether the frame is compressed */
int outputSamples = hassize ? (int)readbits(_framesBuffer, ref pos, 32) : setinfo_max_samples_per_frame;
int readsamplesize = pcm.BitsPerSample - (wasted_bytes * 8) + pcm.ChannelCount - 1;
if (!isnotcompressed)
{
/* compressed */
_interlacing_shift = (byte)readbits(_framesBuffer, ref pos, 8);
_interlacing_leftweight = (byte)readbits(_framesBuffer, ref pos, 8);
if (wasted_bytes != 0)
throw new Exception("FIXME: unimplemented, unhandling of wasted_bytes");
readPredictor(ref pos, ref predictor_info_a);
readPredictor(ref pos, ref predictor_info_b);
basterdised_rice_decompress(outputSamples, ref pos, ref predictor_info_a, ref _predicterror_buffer_a, readsamplesize);
if (predictor_info_a.prediction_type == 0)
predictor_decompress_fir_adapt(outputSamples, ref predictor_info_a, ref _predicterror_buffer_a, ref _outputsamples_buffer_a, readsamplesize);
else
throw new Exception("FIXME: unhandled prediction type.");
basterdised_rice_decompress(outputSamples, ref pos, ref predictor_info_b, ref _predicterror_buffer_b, readsamplesize);
if (predictor_info_b.prediction_type == 0)
predictor_decompress_fir_adapt(outputSamples, ref predictor_info_b, ref _predicterror_buffer_b, ref _outputsamples_buffer_b, readsamplesize);
else
throw new Exception("FIXME: unhandled prediction type.");
}
else
{
/* not compressed, easy case */
int bps = pcm.BitsPerSample;
for (int i = 0; i < outputSamples; i++)
{
_outputsamples_buffer_a[i] = extend_sign32((int)readbits(_framesBuffer, ref pos, bps), bps);
_outputsamples_buffer_b[i] = extend_sign32((int)readbits(_framesBuffer, ref pos, bps), bps);
}
/* wasted_bytes = 0; */
_interlacing_shift = 0;
_interlacing_leftweight = 0;
}
if (readbits(_framesBuffer, ref pos, 3) != 7)
throw new Exception("Invalid frame.");
_samplesInBuffer = outputSamples;
}
/* 'mvhd' movie header atom */
private void qtmovie_read_chunk_mvhd(string path, uint length, object param)
{
UInt32 size_remaining = length;
/* version */
stream_read_uint8();
size_remaining -= 1;
stream_read_uint8();
stream_read_uint8();
stream_read_uint8();
size_remaining -= 3;
stream_read_uint32(); /* creation time */
size_remaining -= 4;
stream_read_uint32(); /* modification time */
size_remaining -= 4;
stream_read_uint32(); /* time scale */
size_remaining -= 4;
_sampleCount = stream_read_uint32(); /* duration */
size_remaining -= 4;
stream_read_uint32(); /* preferred scale */
size_remaining -= 4;
stream_read_uint16(); /* preferred volume */
size_remaining -= 2;
stream_skip(10); /* reserved */
size_remaining -= 10;
stream_skip(36); /* display matrix */
size_remaining -= 36;
stream_read_uint32(); size_remaining -= 4; /* preview time */
stream_read_uint32(); size_remaining -= 4; /* preview duration */
stream_read_uint32(); size_remaining -= 4; /* poster time */
stream_read_uint32(); size_remaining -= 4; /* selection time */
stream_read_uint32(); size_remaining -= 4; /* selection duration */
stream_read_uint32(); size_remaining -= 4; /* current time */
stream_read_uint32(); size_remaining -= 4; /* next track ID */
if (size_remaining > 0)
{
throw new Exception("ehm, size remianing?");
// stream_skip(size_remaining);
}
}
private void qtmovie_read_chunk_stsd(string path, uint length, object param)
{
uint i;
UInt32 numentries;
UInt32 size_remaining = length;
/* version */
stream_read_uint8();
size_remaining -= 1;
/* flags */
stream_read_uint8();
stream_read_uint8();
stream_read_uint8();
size_remaining -= 3;
numentries = stream_read_uint32();
size_remaining -= 4;
if (numentries != 1)
throw new Exception("only expecting one entry in sample description atom!");
for (i = 0; i < numentries; i++)
{
UInt32 entry_size;
UInt16 version;
UInt32 entry_remaining;
entry_size = stream_read_uint32();
UInt32 format = stream_read_uint32();
entry_remaining = entry_size;
entry_remaining -= 8;
/* sound info: */
stream_skip(6); /* reserved */
entry_remaining -= 6;
version = stream_read_uint16();
if (version != 1)
throw new Exception("unknown version!?");
entry_remaining -= 2;
/* revision level */
stream_read_uint16();
/* vendor */
stream_read_uint32();
entry_remaining -= 6;
/* EH?? spec doesn't say theres an extra 16 bits here.. but there is! */
stream_read_uint16();
entry_remaining -= 2;
int _channelCount = (int)stream_read_uint16();
int _bitsPerSample = stream_read_uint16();
entry_remaining -= 4;
/* compression id */
stream_read_uint16();
/* packet size */
stream_read_uint16();
entry_remaining -= 4;
/* sample rate - 32bit fixed point = 16bit?? */
int _sampleRate = stream_read_uint16();
entry_remaining -= 2;
/* skip 2 */
stream_skip(2);
entry_remaining -= 2;
pcm = new AudioPCMConfig(_bitsPerSample, _channelCount, _sampleRate);
/* remaining is codec data */
//#if 0
// qtmovie->res->codecdata_len = stream_read_uint32();
// if (qtmovie->res->codecdata_len != entry_remaining)
// fprintf(stderr, "perhaps not? %i vs %i\n",
// qtmovie->res->codecdata_len, entry_remaining);
// entry_remaining -= 4;
// stream_read_uint32(); /* 'alac' */
// entry_remaining -= 4;
// qtmovie->res->codecdata = malloc(qtmovie->res->codecdata_len - 8);
// stream_read(qtmovie->stream,
// entry_remaining,
// qtmovie->res->codecdata);
// entry_remaining = 0;
//#else
/* 12 = audio format atom, 8 = padding */
uint _codecDataLen = entry_remaining + 12 + 8;
_codecData = new byte[_codecDataLen];
/* audio format atom */
_codecData[0] = 0; _codecData[1] = 0; _codecData[2] = 0; _codecData[3] = 0x0C;
_codecData[4] = (byte)'f'; _codecData[5] = (byte)'r'; _codecData[6] = (byte)'m'; _codecData[7] = (byte)'a';
_codecData[8] = (byte)'a'; _codecData[9] = (byte)'l'; _codecData[10] = (byte)'a'; _codecData[11] = (byte)'c';
_IO.Read(_codecData, 12, (int)entry_remaining);
entry_remaining -= entry_remaining;
//#endif
if (entry_remaining > 0)
stream_skip(entry_remaining);
_formatRead = true;
if (format != FCC_ALAC)
throw new Exception("Expecting ALAC data format");
}
}
private void qtmovie_read_chunk_stts(string path, uint length, object param)
{
uint i;
UInt32 numentries;
UInt32 size_remaining = length;
/* version */
stream_read_uint8();
size_remaining -= 1;
/* flags */
stream_read_uint8();
stream_read_uint8();
stream_read_uint8();
size_remaining -= 3;
numentries = stream_read_uint32();
size_remaining -= 4;
_time_to_sample_count = new int[numentries];
_time_to_sample_duration = new int[numentries];
for (i = 0; i < numentries; i++)
{
_time_to_sample_count[i] = (int)stream_read_uint32();
_time_to_sample_duration[i] = (int)stream_read_uint32();
size_remaining -= 8;
}
if (size_remaining > 0)
{
throw new Exception("ehm, size remianing?");
// stream_skip(size_remaining);
}
}
private void qtmovie_read_chunk_stsz(string path, uint length, object param)
{
uint i;
UInt32 numentries;
UInt32 size_remaining = length;
/* version */
stream_read_uint8();
size_remaining -= 1;
/* flags */
stream_read_uint8();
stream_read_uint8();
stream_read_uint8();
size_remaining -= 3;
/* default sample size */
if (stream_read_uint32() != 0)
{
throw new Exception("i was expecting variable samples sizes\n");
//stream_read_uint32();
//size_remaining -= 4;
//return;
}
size_remaining -= 4;
numentries = stream_read_uint32();
size_remaining -= 4;
_sample_byte_size = new int[numentries];
for (i = 0; i < numentries; i++)
{
_sample_byte_size[i] = (int)stream_read_uint32();
size_remaining -= 4;
}
if (size_remaining > 0)
{
throw new Exception("ehm, size remianing?\n");
//stream_skip(qtmovie->stream, size_remaining);
}
}
/* media handler inside mdia */
private void qtmovie_read_chunk_hdlr(string path, uint length, object param)
{
UInt32 comptype, compsubtype;
UInt32 size_remaining = length;
/* version */
stream_read_uint8();
size_remaining -= 1;
/* flags */
stream_read_uint8();
stream_read_uint8();
stream_read_uint8();
size_remaining -= 3;
/* component type */
comptype = stream_read_uint32();
compsubtype = stream_read_uint32();
size_remaining -= 8;
/* component manufacturer */
stream_read_uint32();
size_remaining -= 4;
/* flags */
stream_read_uint32();
stream_read_uint32();
size_remaining -= 8;
/* name */
// Had do disable: some files have 'SoundHandler' here without preceding length;
//UInt32 strlen = stream_read_uint8();
//byte[] str = new byte[strlen];
//_IO.Read(str, 0, (int)strlen);
//size_remaining -= 1 + strlen;
if (size_remaining > 0)
stream_skip(size_remaining);
}
private void read_chunk_ftyp(UInt32 chunk_len)
{
UInt32 type = stream_read_uint32();
if (type != FCC_M4A)
throw new Exception("not M4A file.");
UInt32 minor_ver = stream_read_uint32();
stream_skip(chunk_len - 16);
//UInt32 size_remaining = chunk_len - 16; /* FIXME: can't hardcode 16, size may be 64bit */
/* compatible brands */
//while (size_remaining > 0)
//{
// /* unused */
// /*fourcc_t cbrand =*/
// stream_read_uint32();
// size_remaining -= 4;
//}
}
private void read_chunk_mdat(UInt32 chunk_len, bool skip_mdat)
{
UInt32 size_remaining = chunk_len - 8; /* FIXME WRONG */
if (size_remaining == 0)
return;
if (skip_mdat)
{
_saved_mdat_pos = _IO.Position;
stream_skip(size_remaining);
}
//#if 0
// qtmovie->res->mdat = malloc(size_remaining);
// stream_read(qtmovie->stream, size_remaining, qtmovie->res->mdat);
//#endif
}
private delegate void qtmovie_read_atom (string path, uint length, object param);
private void qtmovie_read_meta_name(string path, uint length, object param)
{
uint language = stream_read_uint32();
_meta_name = new ASCIIEncoding().GetString(stream_read_bytes((int)length - 4), 0, (int)length - 4);
}
private void qtmovie_read_meta_mean(string path, uint length, object param)
{
uint language = stream_read_uint32();
_meta_mean = new ASCIIEncoding().GetString(stream_read_bytes((int)length - 4), 0, (int)length - 4);
}
private void qtmovie_read_meta_data(string path, uint length, object param)
{
uint tag_format = stream_read_uint32();
uint language = stream_read_uint32();
int str_size = (int)length - 8;
if (str_size <= 0) return;
if (tag_format != 1) throw new Exception(path + ": not a string");
_meta_data = new UTF8Encoding().GetString(stream_read_bytes(str_size), 0, str_size);
}
private void qtmovie_read_meta_freeform(string path, uint length, object param)
{
_meta_data = _meta_name = _meta_mean = null;
qtmovie_read_lst(path, length, param);
if (_meta_data == null || _meta_name == null || _meta_mean == null)
throw new Exception(path + " doesn't contain data, name or mean");
_tags.Add(_meta_name, _meta_data);
}
private void qtmovie_read_meta_string(string path, uint length, object param)
{
_meta_data = null;
qtmovie_read_lst(path, length, param);
if (_meta_data == null)
throw new Exception(path + " doesn't contain data");
_tags.Add((string)param, _meta_data);
}
private void qtmovie_read_meta_binary(string path, uint length, object param)
{
uint tag_format = stream_read_uint32();
uint language = stream_read_uint32();
int str_size = (int)length - 8;
if (str_size <= 0) return;
if (tag_format != 0) throw new Exception(path + " not a binary");
byte[] value = new byte[str_size];
if (_IO.Read(value, 0, str_size) != str_size)
throw new Exception("Decoding failed.");
if (path.EndsWith(".trkn.data"))
{
if (str_size >= 4)
_tags.Add("TRACKNUMBER", value[3].ToString());
if (str_size >= 6)
_tags.Add("TOTALTRACKS", value[5].ToString());
}
else if (path.EndsWith(".disk.data"))
{
if (str_size >= 4)
_tags.Add("DISCNUMBER", value[3].ToString());
if (str_size >= 6)
_tags.Add("TOTALDISCS", value[5].ToString());
}
}
private void qtmovie_read_nul(string path, uint length, object param)
{
stream_skip(length);
}
private void qtmovie_read_lst(string path, uint length, object param)
{
uint size_remaining = length;
if (param != null && param is uint)
{
size_remaining -= (uint) param;
stream_skip((uint)param);
}
StringBuilder chunk_path = new StringBuilder(path);
chunk_path.Append('.');
while (size_remaining > 0)
{
uint sub_chunk_len = stream_read_uint32();
if (sub_chunk_len <= 1 || sub_chunk_len > size_remaining)
throw new Exception("strange size for chunk inside "+path+".");
stream_read_bytes(4);
for (int c = 0; c < 4; c++)
chunk_path.Append((char)_buff[c]);
string chunk_path_str = chunk_path.ToString();
qtmovie_read_atom handler;
if (_qtmovie_parsers.TryGetValue(chunk_path_str, out handler))
handler(chunk_path_str, sub_chunk_len - 8, _qtmovie_parser_params[chunk_path_str]);
else
stream_skip(sub_chunk_len - 8);
chunk_path.Length -= 4;
size_remaining -= sub_chunk_len;
}
}
private void qtmovie_add_any_parser (string path, qtmovie_read_atom handler, object param)
{
_qtmovie_parsers.Add(path, handler);
_qtmovie_parser_params.Add(path, param);
}
private void qtmovie_add_lst_parser(string path, object param)
{
qtmovie_add_any_parser(path, new qtmovie_read_atom(qtmovie_read_lst), param);
}
private void qtmovie_add_nul_parser(string path)
{
qtmovie_add_any_parser(path, new qtmovie_read_atom(qtmovie_read_nul), null);
}
private void qtmovie_add_tag_parser(string path, string flacName)
{
qtmovie_add_any_parser(path, new qtmovie_read_atom(qtmovie_read_meta_string), flacName);
qtmovie_add_any_parser(path + ".data", new qtmovie_read_atom(qtmovie_read_meta_data), null);
}
private void qtmovie_add_tag_parser(string path)
{
qtmovie_add_lst_parser(path, null);
qtmovie_add_any_parser(path + ".data", new qtmovie_read_atom(qtmovie_read_meta_binary), null);
}
private void qtmovie_read()
{
bool found_moov = false;
bool found_mdat = false;
_qtmovie_parsers = new Dictionary<string, qtmovie_read_atom>();
_qtmovie_parser_params = new Dictionary<string, object>();
qtmovie_add_lst_parser("top.moov", null);
qtmovie_add_any_parser("top.moov.mvhd", new qtmovie_read_atom(qtmovie_read_chunk_mvhd), null);
qtmovie_add_nul_parser("top.moov.elst");
qtmovie_add_nul_parser("top.moov.iods");
qtmovie_add_lst_parser("top.moov.trak", null); /* 'trak' - a movie track */
qtmovie_add_nul_parser("top.moov.trak.tkhd");
qtmovie_add_nul_parser("top.moov.trak.edts");
qtmovie_add_lst_parser("top.moov.trak.mdia", null);
qtmovie_add_any_parser("top.moov.trak.mdia.hdlr", new qtmovie_read_atom(qtmovie_read_chunk_hdlr), null);
qtmovie_add_lst_parser("top.moov.trak.mdia.minf", null);
qtmovie_add_nul_parser("top.moov.trak.mdia.minf.smhd"); // required, unused
qtmovie_add_nul_parser("top.moov.trak.mdia.minf.dinf"); // required, unused
qtmovie_add_lst_parser("top.moov.trak.mdia.minf.stbl", null); // SAMPLE TABLE, required
qtmovie_add_any_parser("top.moov.trak.mdia.minf.stbl.stsd", new qtmovie_read_atom(qtmovie_read_chunk_stsd), null); // _codecData
qtmovie_add_any_parser("top.moov.trak.mdia.minf.stbl.stts", new qtmovie_read_atom(qtmovie_read_chunk_stts), null); // _time_to_sample_*
qtmovie_add_any_parser("top.moov.trak.mdia.minf.stbl.stsz", new qtmovie_read_atom(qtmovie_read_chunk_stsz), null); // _sample_byte_size
qtmovie_add_nul_parser("top.moov.trak.mdia.minf.stbl.stsc"); /* skip these, no indexing for us! */
qtmovie_add_nul_parser("top.moov.trak.mdia.minf.stbl.stco"); /* skip these, no indexing for us! */
qtmovie_add_nul_parser("top.moov.udta");
//qtmovie_add_lst_parser("top.moov.udta", null);
//qtmovie_add_lst_parser("top.moov.udta.meta", (uint)4);
//qtmovie_add_lst_parser("top.moov.udta.meta.ilst", null);
//qtmovie_add_tag_parser("top.moov.udta.meta.ilst.<2E>nam", "TITLE");
//qtmovie_add_tag_parser("top.moov.udta.meta.ilst.<2E>ART", "ARTIST");
//qtmovie_add_tag_parser("top.moov.udta.meta.ilst.<2E>wrt", "COMPOSER");
//qtmovie_add_tag_parser("top.moov.udta.meta.ilst.<2E>alb", "ALBUM");
//qtmovie_add_tag_parser("top.moov.udta.meta.ilst.<2E>day", "DATE");
//qtmovie_add_tag_parser("top.moov.udta.meta.ilst.<2E>gen", "GENRE");
//qtmovie_add_tag_parser("top.moov.udta.meta.ilst.disk");
//qtmovie_add_tag_parser("top.moov.udta.meta.ilst.trkn");
//qtmovie_add_any_parser("top.moov.udta.meta.ilst.----", new qtmovie_read_atom(qtmovie_read_meta_freeform), null);
//qtmovie_add_any_parser("top.moov.udta.meta.ilst.----.mean", new qtmovie_read_atom(qtmovie_read_meta_mean), null);
//qtmovie_add_any_parser("top.moov.udta.meta.ilst.----.name", new qtmovie_read_atom(qtmovie_read_meta_name), null);
//qtmovie_add_any_parser("top.moov.udta.meta.ilst.----.data", new qtmovie_read_atom(qtmovie_read_meta_data), null);
while (true)
{
UInt32 chunk_len = stream_read_uint32();
if (chunk_len == 1)
throw new Exception("need 64bit support.");
UInt32 chunk_id = stream_read_uint32();
switch (chunk_id)
{
case FCC_FTYP:
read_chunk_ftyp(chunk_len);
break;
case FCC_MOOV:
qtmovie_read_lst("top.moov", chunk_len - 8, null);
if (found_mdat)
{
_IO.Position = _saved_mdat_pos;
return;
}
found_moov = true;
break;
/* if we hit mdat before we've found moov, record the position
* and move on. We can then come back to mdat later.
* This presumes the stream supports seeking backwards.
*/
case FCC_MDAT:
read_chunk_mdat(chunk_len, !found_moov);
if (found_moov)
return;
found_mdat = true;
break;
/* these following atoms can be skipped !!!! */
case FCC_FREE:
stream_skip(chunk_len - 8); /* FIXME not 8 */
break;
default:
throw new Exception(String.Format("(top) unknown chunk id: {0}.", chunk_id));
}
}
}
string _path;
Stream _IO;
byte[] _codecData;
int[] _time_to_sample_count, _time_to_sample_duration, _sample_byte_size;
long _saved_mdat_pos;
bool _formatRead;
int _bitaccumulator;
int setinfo_max_samples_per_frame;
byte setinfo_rice_initialhistory;
byte setinfo_rice_kmodifier;
byte setinfo_rice_historymult;
int[] _predicterror_buffer_a,
_predicterror_buffer_b,
_outputsamples_buffer_a,
_outputsamples_buffer_b;
predictor_t predictor_info_a;
predictor_t predictor_info_b;
NameValueCollection _tags;
int _samplesInBuffer, _samplesBufferOffset;
byte[] _framesBuffer;
byte[] _buff;
byte _interlacing_shift;
byte _interlacing_leftweight;
AudioPCMConfig pcm;
long _sampleCount;
long _sampleOffset;
long _iSample;
Dictionary<string, qtmovie_read_atom> _qtmovie_parsers;
Dictionary<string, object> _qtmovie_parser_params;
string _meta_data, _meta_name, _meta_mean;
unsafe struct predictor_t
{
public fixed short predictor_coef_table[32];
public int predictor_coef_table_sum;
public int predictor_coef_num;
public int prediction_type;
public int prediction_quantitization;
public int ricemodifier;
}
const UInt32 FCC_FTYP = ('f' << 24) + ('t' << 16) + ('y' << 8) + ('p' << 0);
const UInt32 FCC_MOOV = ('m' << 24) + ('o' << 16) + ('o' << 8) + ('v' << 0);
const UInt32 FCC_MDAT = ('m' << 24) + ('d' << 16) + ('a' << 8) + ('t' << 0);
const UInt32 FCC_FREE = ('f' << 24) + ('r' << 16) + ('e' << 8) + ('e' << 0);
const UInt32 FCC_M4A = ('M' << 24) + ('4' << 16) + ('A' << 8) + (' ' << 0);
const UInt32 FCC_ALAC = ('a' << 24) + ('l' << 16) + ('a' << 8) + ('c' << 0);
}
}