From 5a92dc8b84458a214981722d500d876294dda213 Mon Sep 17 00:00:00 2001 From: chudov Date: Tue, 4 Nov 2008 01:14:25 +0000 Subject: [PATCH] test version of ALAC decoder, trying to clean up audio interfaces --- ALACDotNet/ALACDotNet.cs | 1370 ++++++++++++++++++ ALACDotNet/ALACDotNet.csproj | 103 ++ ALACDotNet/Properties/AssemblyInfo.cs | 35 + AudioCodecsDotNet/AudioCodecsDotNet.cs | 613 ++++++++ AudioCodecsDotNet/AudioCodecsDotNet.csproj | 95 ++ AudioCodecsDotNet/Properties/AssemblyInfo.cs | 35 + 6 files changed, 2251 insertions(+) create mode 100644 ALACDotNet/ALACDotNet.cs create mode 100644 ALACDotNet/ALACDotNet.csproj create mode 100644 ALACDotNet/Properties/AssemblyInfo.cs create mode 100644 AudioCodecsDotNet/AudioCodecsDotNet.cs create mode 100644 AudioCodecsDotNet/AudioCodecsDotNet.csproj create mode 100644 AudioCodecsDotNet/Properties/AssemblyInfo.cs diff --git a/ALACDotNet/ALACDotNet.cs b/ALACDotNet/ALACDotNet.cs new file mode 100644 index 0000000..20ec066 --- /dev/null +++ b/ALACDotNet/ALACDotNet.cs @@ -0,0 +1,1370 @@ +using System; +using System.Text; +using System.IO; +using System.Collections.Generic; +using System.Collections.Specialized; +using AudioCodecsDotNet; + +//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 ALACDotNet +{ + public class ALACReader : IAudioSource + { + public ALACReader(string path) + { + _path = path; + m_spIO = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read); + _buff = new byte[4]; + qtmovie_read(); + if (!_formatRead || _bitsPerSample != 16 || _channelCount != 2 || _sampleRate != 44100) + throw new Exception("Invalid ALAC file."); + _saved_mdat_pos = m_spIO.Position; + } + + public uint Read(byte[] buff, uint sampleCount) + { + if (_predicterror_buffer_a == null) + { + setinfo_max_samples_per_frame = 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, 38); + uint setinfo_86 = read_uint32(_codecData, 42); + uint setinfo_8a_rate = read_uint32(_codecData, 44); + + _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; + _samplesBuffer = new byte[setinfo_max_samples_per_frame*4]; + _framesBuffer = new byte[65536]; + } + + ulong offset = 0; + + while (_samplesInBuffer < sampleCount) + { + if (_samplesInBuffer > 0) + { + Array.Copy(_samplesBuffer, (long) _samplesBufferOffset * 4, buff, (long)offset * 4, (long)_samplesInBuffer * 4); + sampleCount -= (uint) _samplesInBuffer; + offset += _samplesInBuffer; + _samplesInBuffer = 0; + _samplesBufferOffset = 0; + } + + ulong sampleDuration; + uint sampleSize; + get_sample_info(_iSample, out sampleDuration, out sampleSize); + m_spIO.Read(_framesBuffer, 0, (int) sampleSize); + decodeFrame(sampleDuration, sampleSize); + if (sampleDuration != _samplesInBuffer) + throw new Exception("sample count mismatch"); + _samplesInBuffer -= _samplesBufferOffset; + _sampleOffset += _samplesInBuffer; + _iSample++; + } + + Array.Copy(_samplesBuffer, (long) _samplesBufferOffset * 4, buff, (long)offset * 4, (long)sampleCount * 4); + _samplesInBuffer -= sampleCount; + _samplesBufferOffset += sampleCount; + if (_samplesInBuffer == 0) + _samplesBufferOffset = 0; + return (uint) offset + sampleCount; + } + + public void Close() + { + m_spIO.Close(); + } + + public ulong Length + { + get + { + return _sampleCount; + } + } + + public ulong Remaining + { + get + { + return _sampleCount - _sampleOffset + _samplesInBuffer; + } + } + + public ulong Position + { + get + { + return _sampleOffset - _samplesInBuffer; + } + set + { + _sampleOffset = value; + _samplesInBuffer = 0; + _samplesBufferOffset = 0; + + _iSample = 0; + ulong durOffs = 0; + ulong sampleDuration; + long fileOffs = 0; + uint sampleSize; + do + { + if (durOffs == value) + { + m_spIO.Position = _saved_mdat_pos + fileOffs; + return; + } + get_sample_info(_iSample, out sampleDuration, out sampleSize); + durOffs += sampleDuration; + fileOffs += sampleSize; + _iSample++; + } while (durOffs <= value); + m_spIO.Position = _saved_mdat_pos + fileOffs - sampleSize; + _samplesBufferOffset = sampleDuration - durOffs + value; + _iSample--; + } + } + + public int BitsPerSample + { + get + { + return _bitsPerSample; + } + } + + public int ChannelCount + { + get + { + return _channelCount; + } + } + + public int SampleRate + { + get + { + return _sampleRate; + } + } + + public NameValueCollection Tags + { + get + { + return _tags; + } + set + { + _tags = value; + } + } + + public string Path + { + get + { + return _path; + } + } + + private void get_sample_info(ulong iSample, out ulong sampleDuration, out uint sampleSize) + { + // if (iSample >= _sample_byte_size.Length) + uint duration_index_accum = 0; + uint 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]; + duration_cur_index ++; + } + sampleDuration = _time_to_sample_duration[duration_cur_index]; + sampleSize = _sample_byte_size[iSample]; + } + + 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() + { + if (m_spIO.Read(_buff, 0, 4) != 4) + throw new Exception("Decoding failed."); + return read_uint32 (_buff, 0); + } + + private ushort read_uint16(byte[] buff, int pos) + { + return (ushort)((buff[pos] << 8) + buff[pos + 1]); + } + + private ushort stream_read_uint16() + { + if (m_spIO.Read(_buff, 0, 2) != 2) + throw new Exception("Decoding failed."); + return read_uint16 (_buff, 0); + } + + private byte read_uint8(byte[] buff, int pos) + { + return buff[pos]; + } + + private byte stream_read_uint8() + { + if (m_spIO.Read(_buff, 0, 1) != 1) + throw new Exception("Decoding failed."); + return read_uint8 (_buff, 0); + } + + private void stream_skip (UInt32 skip) + { + m_spIO.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_16(byte* buff, int pos, int bits) + { + uint result = (((uint)buff[pos]) << 16) | (((uint)buff[pos + 1]) << 8) | ((uint)buff[pos + 2]); + result <<= _bitaccumulator; + result &= 0x00ffffff; + result >>= 24 - bits; + 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 static uint SIGN_EXTENDED32(uint val, int bits) + { + return ((val << (32 - bits)) >> (32 - bits)); + } + + 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_zeros(uint input, int first) + { + int zeroes = 1; + int check = first; + while (check > 0) + { + uint shifted_input = input >> check; + if (shifted_input == 0) + zeroes += check; + else + input = shifted_input; + check >>= 1; + } + return zeroes - (int)input; + } + + private static int count_leading_zeros(uint input) + { + int zeroes = 1; + int check = 16; + while (check > 0) + { + uint shifted_input = input >> check; + if (shifted_input == 0) + zeroes += check; + else + input = shifted_input; + check >>= 1; + } + return zeroes - (int)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 */ + for (int i = 0; i < pr->predictor_coef_num; i++) + { + pr->predictor_coef_table[i] = (short)readbits(_framesBuffer, ref pos, 16); + } + } + } + + private unsafe int decode_scalar(byte * buff, ref int pos, int k, int limit, int readsamplesize) + { + int x = 0; + //while (x <= 8 && readbit(_framesBuffer, ref pos) != 0) + // x++; + uint next = peekbits_16(buff, pos, 9); + for (x = 0; (next & 0x100) != 0; x++) + next <<= 1; + skipbits(ref pos, x >= 9 ? 9 : x + 1); + if (x > 8) /* RICE THRESHOLD */ + return (int) readbits(buff, ref pos, readsamplesize); + 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(uint 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 = 0; + int x_modified; + int final_val; + + x = decode_scalar(buff, ref pos, 31 - count_leading_zeros((history >> 9) + 3), rice_kmodifier, readsamplesize); + + x_modified = sign_modifier + x; + final_val = (x_modified + 1) / 2; + if ((x_modified & 1) != 0) final_val *= -1; + + output_buffer[output_count] = final_val; + + sign_modifier = 0; + + /* now update the history */ + history = (uint)(history + (x_modified * rice_historymult) + - ((history * rice_historymult) >> 9)); + + if (x_modified > 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_zeros(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 int sign_only(int v) + { + return v == 0 ? 0 : v > 0 ? 1 : -1; + } + + private unsafe void predictor_decompress_fir_adapt(uint output_size, ref predictor_t predictor_info, ref int[] error_buffer, ref int[] buffer_out, int readsamplesize) + { + int i; + + /* first sample always copies */ + buffer_out[0] = error_buffer[0]; + + if (predictor_info.predictor_coef_num == 0) + { + if (output_size <= 1) + return; + for (i = 1; i < output_size; i++) + buffer_out[i] = error_buffer[i]; + return; + } + + if (predictor_info.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 - 1; i++) + { + int prev_value; + int error_value; + + prev_value = buffer_out[i]; + error_value = error_buffer[i + 1]; + buffer_out[i + 1] = + extend_sign32((prev_value + error_value), readsamplesize); + } + return; + } + + /* read warm-up samples */ + if (predictor_info.predictor_coef_num > 0) + for (i = 0; i < predictor_info.predictor_coef_num; i++) + { + int val; + + val = buffer_out[i] + error_buffer[i + 1]; + val = extend_sign32(val, readsamplesize); + buffer_out[i + 1] = val; + } + + //#if 0 + // /* 4 and 8 are very common cases (the only ones i've seen). these + // * should be unrolled and optimized + // */ + // if (predictor_coef_num == 4) { + // /* FIXME: optimized general case */ + // return; + // } + + // if (predictor_coef_table == 8) { + // /* FIXME: optimized general case */ + // return; + // } + //#endif + + /* general case */ + if (predictor_info.predictor_coef_num > 0) + fixed (predictor_t* pr = &predictor_info) + fixed (int * buf_out = &buffer_out[0], buf_err = &error_buffer[0]) + { + int pos = 0; + + for (i = (int) predictor_info.predictor_coef_num + 1; i < output_size; i++) + { + int j; + int sum = 0; + int outval; + int error_val = buf_err[i]; + + for (j = 0; j < predictor_info.predictor_coef_num; j++) + { + sum += (buf_out[pos + predictor_info.predictor_coef_num - j] - buf_out[pos]) * + pr->predictor_coef_table[j]; + } + + outval = (1 << (predictor_info.prediction_quantitization - 1)) + sum; + outval = outval >> predictor_info.prediction_quantitization; + outval = outval + buf_out[pos] + error_val; + outval = extend_sign32(outval, readsamplesize); + + buf_out[pos + pr->predictor_coef_num + 1] = outval; + + if (error_val > 0) + { + int predictor_num = pr->predictor_coef_num - 1; + + while (predictor_num >= 0 && error_val > 0) + { + int val = buf_out[pos] - buf_out[pos + predictor_info.predictor_coef_num - predictor_num]; + short sign = (short) sign_only(val); + + pr->predictor_coef_table[predictor_num] -= sign; + + val *= sign; /* absolute value */ + + error_val -= ((val >> predictor_info.prediction_quantitization) * + (predictor_info.predictor_coef_num - predictor_num)); + + predictor_num--; + } + } + else if (error_val < 0) + { + int predictor_num = predictor_info.predictor_coef_num - 1; + + while (predictor_num >= 0 && error_val < 0) + { + int val = buf_out[pos] - buf_out[pos + predictor_info.predictor_coef_num - predictor_num]; + short sign = (short) sign_only(- val); + + pr->predictor_coef_table[predictor_num] -= sign; + + val *= sign; /* neg value */ + + error_val -= ((val >> predictor_info.prediction_quantitization) * + (predictor_info.predictor_coef_num - predictor_num)); + + predictor_num--; + } + } + + pos++; + } + } + } + + private unsafe void deinterlace_16(uint numsamples, byte interlacing_shift, byte interlacing_leftweight) + { + int i; + + if (numsamples <= 0) + return; + + fixed (int* buf_a = &_outputsamples_buffer_a[0], buf_b = _outputsamples_buffer_b) + fixed (byte * buf_s = &_samplesBuffer[0]) + { + /* weighted interlacing */ + if (interlacing_leftweight != 0) + { + for (i = 0; i < numsamples; i++) + { + int a = buf_a[i]; + int b = buf_b[i]; + + a -= (b * interlacing_leftweight) >> interlacing_shift; + b += a; + + buf_s[i * 4] = (byte)(b & 0xff); + buf_s[i * 4 + 1] = (byte)((b >> 8) & 0xff); + buf_s[i * 4 + 2] = (byte)(a & 0xff); + buf_s[i * 4 + 3] = (byte)((a >> 8) & 0xff); + } + return; + } + + /* otherwise basic interlacing took place */ + for (i = 0; i < numsamples; i++) + { + int a = buf_a[i]; + int b = buf_b[i]; + buf_s[i * 4] = (byte)(a & 0xff); + buf_s[i * 4 + 1] = (byte)((a >> 8) & 0xff); + buf_s[i * 4 + 2] = (byte)(b & 0xff); + buf_s[i * 4 + 3] = (byte)((b >> 8) & 0xff); + } + } + } + + private void decodeFrame(ulong sampleDuration, uint sampleSize) + { + _bitaccumulator = 0; + int pos = 0; + + int channels = (int) readbits(_framesBuffer, ref pos, 3); + if (channels != 1) + throw new Exception("Not stereo"); + + byte interlacing_shift; + byte interlacing_leftweight; + 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 */ + uint outputSamples = hassize ? readbits(_framesBuffer, ref pos, 32) : setinfo_max_samples_per_frame; + + int readsamplesize = _bitsPerSample - (wasted_bytes * 8) + channels; + 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 */ + if (_bitsPerSample != 16) + throw new Exception("Not 16 bit"); + for (int i = 0; i < outputSamples; i++) + { + _outputsamples_buffer_a[i] = extend_sign32((int)readbits(_framesBuffer, ref pos, _bitsPerSample), _bitsPerSample); + _outputsamples_buffer_b[i] = extend_sign32((int)readbits(_framesBuffer, ref pos, _bitsPerSample), _bitsPerSample); + } + /* wasted_bytes = 0; */ + interlacing_shift = 0; + interlacing_leftweight = 0; + } + + if (_bitsPerSample != 16) + throw new Exception("Not 16 bit"); + + deinterlace_16(outputSamples, interlacing_shift, interlacing_leftweight); + + _samplesInBuffer = outputSamples; + } + + private void skip_chunk(UInt32 chunk_len) + { + stream_skip(chunk_len - 8); /* FIXME WRONG */ + } + + /* 'mvhd' movie header atom */ + private void read_chunk_mvhd(UInt32 chunk_len) + { + UInt32 size_remaining = chunk_len - 8; /* FIXME WRONG */ + + /* 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); + } + } + + /* 'udta' user data.. contains tag info */ + private void read_chunk_udta(UInt32 chunk_len) + { + /* don't need anything from here atm, skip */ + skip_chunk(chunk_len); + } + + private void read_chunk_tkhd(UInt32 chunk_len) + { + /* don't need anything from here atm, skip */ + skip_chunk(chunk_len); + } + + private void read_chunk_mdhd(UInt32 chunk_len) + { + /* don't need anything from here atm, skip */ + skip_chunk(chunk_len); + } + + /* 'iods' */ + private void read_chunk_iods(UInt32 chunk_len) + { + /* don't need anything from here atm, skip */ + skip_chunk(chunk_len); + } + + private void read_chunk_edts(UInt32 chunk_len) + { + /* don't need anything from here atm, skip */ + skip_chunk(chunk_len); + } + + private void read_chunk_elst(UInt32 chunk_len) + { + /* don't need anything from here atm, skip */ + skip_chunk(chunk_len); + } + + private void read_chunk_stsd(UInt32 chunk_len) + { + uint i; + UInt32 numentries; + UInt32 size_remaining = chunk_len - 8; /* FIXME WRONG */ + + /* 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; + + _channelCount = (int)stream_read_uint16(); + + _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?? */ + _sampleRate = stream_read_uint16(); + entry_remaining -= 2; + + /* skip 2 */ + stream_skip(2); + entry_remaining -= 2; + + /* 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'; + m_spIO.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 read_chunk_stts(UInt32 chunk_len) + { + uint i; + UInt32 numentries; + UInt32 size_remaining = chunk_len - 8; /* FIXME WRONG */ + + /* 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 UInt32[numentries]; + _time_to_sample_duration = new UInt32[numentries]; + + for (i = 0; i < numentries; i++) + { + _time_to_sample_count[i] = stream_read_uint32(); + _time_to_sample_duration[i] = stream_read_uint32(); + size_remaining -= 8; + } + + if (size_remaining > 0) + { + throw new Exception("ehm, size remianing?"); + // stream_skip(size_remaining); + } + } + + private void read_chunk_stsz(UInt32 chunk_len) + { + uint i; + UInt32 numentries; + UInt32 size_remaining = chunk_len - 8; /* FIXME WRONG */ + + /* 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 uint[numentries]; + + for (i = 0; i < numentries; i++) + { + _sample_byte_size[i] = stream_read_uint32(); + size_remaining -= 4; + } + + if (size_remaining > 0) + { + throw new Exception("ehm, size remianing?\n"); + //stream_skip(qtmovie->stream, size_remaining); + } + } + + private void read_chunk_stbl(UInt32 chunk_len) + { + UInt32 size_remaining = chunk_len - 8; /* FIXME WRONG */ + while (size_remaining > 0) + { + UInt32 sub_chunk_len = stream_read_uint32(); + if (sub_chunk_len <= 1 || sub_chunk_len > size_remaining) + throw new Exception("strange size for chunk inside stbl."); + + UInt32 sub_chunk_id = stream_read_uint32(); + switch (sub_chunk_id) + { + case FCC_STSD: + read_chunk_stsd(sub_chunk_len); + break; + case FCC_STTS: + read_chunk_stts(sub_chunk_len); + break; + case FCC_STSZ: + read_chunk_stsz(sub_chunk_len); + break; + case FCC_STSC: + case FCC_STCO: + /* skip these, no indexing for us! */ + stream_skip(sub_chunk_len - 8); + break; + default: + throw new Exception(String.Format("(trak) unknown chunk id: {0}.", sub_chunk_id)); + } + size_remaining -= sub_chunk_len; + } + } + + private void read_chunk_minf(UInt32 chunk_len) + { + UInt32 dinf_size, stbl_size; + UInt32 size_remaining = chunk_len - 8; /* FIXME WRONG */ + + /**** SOUND HEADER CHUNK ****/ + if (stream_read_uint32() != 16) + throw new Exception("unexpected size in media info\n"); + if (stream_read_uint32() != FCC_SMHD) + throw new Exception("not a sound header! can't handle this.\n"); + /* now skip the rest */ + stream_skip(16 - 8); + size_remaining -= 16; + /****/ + + /**** DINF CHUNK ****/ + dinf_size = stream_read_uint32(); + if (stream_read_uint32() != FCC_DINF) + throw new Exception("expected dinf, didn't get it."); + /* skip it */ + stream_skip(dinf_size - 8); + size_remaining -= dinf_size; + /****/ + + /**** SAMPLE TABLE ****/ + stbl_size = stream_read_uint32(); + if (stream_read_uint32() != FCC_STBL) + throw new Exception("expected stbl, didn't get it."); + read_chunk_stbl(stbl_size); + size_remaining -= stbl_size; + + if (size_remaining > 0) + { + throw new Exception("oops\n"); + //stream_skip(size_remaining); + } + } + + /* media handler inside mdia */ + private void read_chunk_hdlr(UInt32 chunk_len) + { + UInt32 comptype, compsubtype; + UInt32 size_remaining = chunk_len - 8; /* FIXME WRONG */ + + /* 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 */ + UInt32 strlen = stream_read_uint8(); + byte[] str = new byte[strlen]; + m_spIO.Read(str, 0, (int)strlen); + size_remaining -= 1 + strlen; + + if (size_remaining > 0) + stream_skip(size_remaining); + } + + + private void read_chunk_mdia(UInt32 chunk_len) + { + UInt32 size_remaining = chunk_len - 8; /* FIXME WRONG */ + while (size_remaining > 0) + { + UInt32 sub_chunk_len = stream_read_uint32(); + if (sub_chunk_len <= 1 || sub_chunk_len > size_remaining) + throw new Exception("strange size for chunk inside mdia."); + + UInt32 sub_chunk_id = stream_read_uint32(); + switch (sub_chunk_id) + { + case FCC_MDHD: + read_chunk_mdhd(sub_chunk_len); + break; + case FCC_HDLR: + read_chunk_hdlr(sub_chunk_len); + break; + case FCC_MINF: + read_chunk_minf(sub_chunk_len); + break; + default: + throw new Exception(String.Format("(mdia) unknown chunk id: {0}.", sub_chunk_id)); + } + + size_remaining -= sub_chunk_len; + } + } + + /* 'trak' - a movie track - contains other atoms */ + private void read_chunk_trak(UInt32 chunk_len) + { + UInt32 size_remaining = chunk_len - 8; /* FIXME WRONG */ + while (size_remaining > 0) + { + UInt32 sub_chunk_len = stream_read_uint32(); ; + if (sub_chunk_len <= 1 || sub_chunk_len > size_remaining) + throw new Exception("strange size for chunk inside trak."); + + UInt32 sub_chunk_id = stream_read_uint32(); + switch (sub_chunk_id) + { + case FCC_TKHD: + read_chunk_tkhd(sub_chunk_len); + break; + case FCC_MDIA: + read_chunk_mdia(sub_chunk_len); + break; + case FCC_EDTS: + read_chunk_edts(sub_chunk_len); + break; + default: + throw new Exception(String.Format("(trak) unknown chunk id: {0}.", sub_chunk_id)); + } + + size_remaining -= sub_chunk_len; + } + } + + 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 = m_spIO.Position; + stream_skip(size_remaining); + } + //#if 0 + // qtmovie->res->mdat = malloc(size_remaining); + + // stream_read(qtmovie->stream, size_remaining, qtmovie->res->mdat); + //#endif + } + + private void read_chunk_moov(UInt32 chunk_len) + { + UInt32 size_remaining = chunk_len - 8; /* FIXME WRONG */ + while (size_remaining > 0) + { + UInt32 sub_chunk_len = stream_read_uint32(); + if (sub_chunk_len <= 1 || sub_chunk_len > size_remaining) + throw new Exception("strange size for chunk inside moov."); + UInt32 sub_chunk_id = stream_read_uint32(); + switch (sub_chunk_id) + { + case FCC_MVHD: + read_chunk_mvhd(sub_chunk_len); + break; + case FCC_TRAK: + read_chunk_trak(sub_chunk_len); + break; + case FCC_UDTA: + read_chunk_udta(sub_chunk_len); + break; + case FCC_ELST: + read_chunk_elst(sub_chunk_len); + break; + case FCC_IODS: + read_chunk_iods(sub_chunk_len); + break; + default: + throw new Exception(String.Format("(moov) unknown chunk id: {0}.", sub_chunk_id)); + } + size_remaining -= sub_chunk_len; + } + } + + private void qtmovie_read() + { + bool found_moov = false; + bool found_mdat = false; + 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: + read_chunk_moov(chunk_len); + if (found_mdat) + { + m_spIO.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; + FileStream m_spIO; + + byte[] _codecData; + uint[] _time_to_sample_count, _time_to_sample_duration, _sample_byte_size; + long _saved_mdat_pos; + bool _formatRead; + int _bitaccumulator; + uint 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; + ulong _samplesInBuffer; + byte[] _samplesBuffer; + byte[] _framesBuffer; + byte[] _buff; + int _sampleRate; + int _channelCount; + int _bitsPerSample; + ulong _sampleCount; + ulong _sampleOffset; + ulong _samplesBufferOffset; + ulong _iSample; + + unsafe struct predictor_t + { + public fixed short predictor_coef_table[32]; + 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_MVHD = ('m' << 24) + ('v' << 16) + ('h' << 8) + ('d' << 0); + const UInt32 FCC_TRAK = ('t' << 24) + ('r' << 16) + ('a' << 8) + ('k' << 0); + const UInt32 FCC_UDTA = ('u' << 24) + ('d' << 16) + ('t' << 8) + ('a' << 0); + const UInt32 FCC_ELST = ('e' << 24) + ('l' << 16) + ('s' << 8) + ('t' << 0); + const UInt32 FCC_IODS = ('i' << 24) + ('o' << 16) + ('d' << 8) + ('s' << 0); + const UInt32 FCC_TKHD = ('t' << 24) + ('k' << 16) + ('h' << 8) + ('d' << 0); + const UInt32 FCC_MDIA = ('m' << 24) + ('d' << 16) + ('i' << 8) + ('a' << 0); + const UInt32 FCC_EDTS = ('e' << 24) + ('d' << 16) + ('t' << 8) + ('s' << 0); + const UInt32 FCC_MDHD = ('m' << 24) + ('d' << 16) + ('h' << 8) + ('d' << 0); + const UInt32 FCC_HDLR = ('h' << 24) + ('d' << 16) + ('l' << 8) + ('r' << 0); + const UInt32 FCC_MINF = ('m' << 24) + ('i' << 16) + ('n' << 8) + ('f' << 0); + const UInt32 FCC_SMHD = ('s' << 24) + ('m' << 16) + ('h' << 8) + ('d' << 0); + const UInt32 FCC_DINF = ('d' << 24) + ('i' << 16) + ('n' << 8) + ('f' << 0); + const UInt32 FCC_STBL = ('s' << 24) + ('t' << 16) + ('b' << 8) + ('l' << 0); + const UInt32 FCC_STSD = ('s' << 24) + ('t' << 16) + ('s' << 8) + ('d' << 0); + const UInt32 FCC_STTS = ('s' << 24) + ('t' << 16) + ('t' << 8) + ('s' << 0); + const UInt32 FCC_STSZ = ('s' << 24) + ('t' << 16) + ('s' << 8) + ('z' << 0); + const UInt32 FCC_STSC = ('s' << 24) + ('t' << 16) + ('s' << 8) + ('c' << 0); + const UInt32 FCC_STCO = ('s' << 24) + ('t' << 16) + ('c' << 8) + ('o' << 0); + const UInt32 FCC_ALAC = ('a' << 24) + ('l' << 16) + ('a' << 8) + ('c' << 0); + } +} diff --git a/ALACDotNet/ALACDotNet.csproj b/ALACDotNet/ALACDotNet.csproj new file mode 100644 index 0000000..750817b --- /dev/null +++ b/ALACDotNet/ALACDotNet.csproj @@ -0,0 +1,103 @@ + + + Debug + AnyCPU + 8.0.50727 + 2.0 + {F2EC7193-D5E5-4252-9803-5CEB407E910F} + Library + Properties + ALACDotNet + ALACDotNet + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + true + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + true + + + true + bin\x64\Debug\ + DEBUG;TRACE + full + x64 + C:\Program Files (x86)\Microsoft Visual Studio 8\Team Tools\Static Analysis Tools\FxCop\\rules + true + GlobalSuppressions.cs + prompt + true + + + bin\x64\Release\ + TRACE + true + pdbonly + x64 + C:\Program Files (x86)\Microsoft Visual Studio 8\Team Tools\Static Analysis Tools\FxCop\\rules + true + GlobalSuppressions.cs + prompt + true + + + true + bin\x86\Debug\ + DEBUG;TRACE + full + x86 + C:\Program Files (x86)\Microsoft Visual Studio 8\Team Tools\Static Analysis Tools\FxCop\\rules + true + GlobalSuppressions.cs + prompt + true + + + bin\x86\Release\ + TRACE + true + pdbonly + x86 + C:\Program Files (x86)\Microsoft Visual Studio 8\Team Tools\Static Analysis Tools\FxCop\\rules + true + GlobalSuppressions.cs + prompt + true + + + + + + + + + + + + + {6458A13A-30EF-45A9-9D58-E5031B17BEE2} + AudioCodecsDotNet + + + + + \ No newline at end of file diff --git a/ALACDotNet/Properties/AssemblyInfo.cs b/ALACDotNet/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..81c3221 --- /dev/null +++ b/ALACDotNet/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("ALACDotNet")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft")] +[assembly: AssemblyProduct("ALACDotNet")] +[assembly: AssemblyCopyright("Copyright © Microsoft 2008")] +[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("d7752598-7548-4bcd-bb23-040fb6cc7673")] + +// 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("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/AudioCodecsDotNet/AudioCodecsDotNet.cs b/AudioCodecsDotNet/AudioCodecsDotNet.cs new file mode 100644 index 0000000..46b4520 --- /dev/null +++ b/AudioCodecsDotNet/AudioCodecsDotNet.cs @@ -0,0 +1,613 @@ +using System; +using System.IO; +using System.Text; +using System.Collections.Generic; +using System.Collections.Specialized; + +namespace AudioCodecsDotNet +{ + public interface IAudioSource + { + uint Read(byte[] buff, uint sampleCount); + ulong Length { get; } + ulong Position { get; set; } + NameValueCollection Tags { get; set; } + ulong Remaining { get; } + void Close(); + int BitsPerSample { get; } + int ChannelCount { get; } + int SampleRate { get; } + string Path { get; } + } + + public interface IAudioDest + { + void Write(int[,] buff, uint sampleCount); + bool SetTags(NameValueCollection tags); + void Close(); + long FinalSampleCount { set; } + string Path { get; } + } + + public class AudioCodecsDotNet + { + public static unsafe void FLACSamplesToBytes_16(int[,] inSamples, uint inSampleOffset, + byte[] outSamples, uint outByteOffset, uint sampleCount, int channelCount) + { + uint loopCount = sampleCount * (uint)channelCount; + + if ((inSamples.GetLength(0) - inSampleOffset < sampleCount) || + (outSamples.Length - outByteOffset < loopCount * 2)) + { + throw new IndexOutOfRangeException(); + } + + fixed (int* pInSamplesFixed = &inSamples[inSampleOffset, 0]) + { + fixed (byte* pOutSamplesFixed = &outSamples[outByteOffset]) + { + int* pInSamples = pInSamplesFixed; + short* pOutSamples = (short*)pOutSamplesFixed; + + for (int i = 0; i < loopCount; i++) + { + *(pOutSamples++) = (short)*(pInSamples++); + } + } + } + } + + public static unsafe void FLACSamplesToBytes_24(int[,] inSamples, uint inSampleOffset, + byte[] outSamples, uint outByteOffset, uint sampleCount, int channelCount) + { + uint loopCount = sampleCount * (uint)channelCount; + + if ((inSamples.GetLength(0) - inSampleOffset < sampleCount) || + (outSamples.Length - outByteOffset < loopCount * 3)) + { + throw new IndexOutOfRangeException(); + } + + fixed (int* pInSamplesFixed = &inSamples[inSampleOffset, 0]) + { + fixed (byte* pOutSamplesFixed = &outSamples[outByteOffset]) + { + int* pInSamples = pInSamplesFixed; + byte* pOutSamples = pOutSamplesFixed; + + for (int i = 0; i < loopCount; i++) + { + uint sample_out = (uint)*(pInSamples++); + *(pOutSamples++) = (byte)(sample_out & 0xFF); + sample_out >>= 8; + *(pOutSamples++) = (byte)(sample_out & 0xFF); + sample_out >>= 8; + *(pOutSamples++) = (byte)(sample_out & 0xFF); + } + } + } + } + + public static unsafe void FLACSamplesToBytes(int[,] inSamples, uint inSampleOffset, + byte[] outSamples, uint outByteOffset, uint sampleCount, int channelCount, int bitsPerSample) + { + if (bitsPerSample == 16) + AudioCodecsDotNet.FLACSamplesToBytes_16(inSamples, inSampleOffset, outSamples, outByteOffset, sampleCount, channelCount); + else if (bitsPerSample == 24) + AudioCodecsDotNet.FLACSamplesToBytes_24(inSamples, inSampleOffset, outSamples, outByteOffset, sampleCount, channelCount); + else + throw new Exception("Unsupported bitsPerSample value"); + } + + public static unsafe void BytesToFLACSamples_16(byte[] inSamples, int inByteOffset, + int[,] outSamples, int outSampleOffset, uint sampleCount, int channelCount) + { + uint loopCount = sampleCount * (uint)channelCount; + + if ((inSamples.Length - inByteOffset < loopCount * 2) || + (outSamples.GetLength(0) - outSampleOffset < sampleCount)) + { + throw new IndexOutOfRangeException(); + } + + fixed (byte* pInSamplesFixed = &inSamples[inByteOffset]) + { + fixed (int* pOutSamplesFixed = &outSamples[outSampleOffset, 0]) + { + short* pInSamples = (short*)pInSamplesFixed; + int* pOutSamples = pOutSamplesFixed; + + for (int i = 0; i < loopCount; i++) + { + *(pOutSamples++) = (int)*(pInSamples++); + } + } + } + } + } + + public class DummyWriter : IAudioDest + { + public DummyWriter(string path, int bitsPerSample, int channelCount, int sampleRate) + { + } + + public bool SetTags(NameValueCollection tags) + { + return false; + } + + public void Close() + { + } + + public long FinalSampleCount + { + set + { + } + } + + public void Write(int[,] buff, uint sampleCount) + { + } + + public string Path { get { return null; } } + } + + public class SilenceGenerator : IAudioSource + { + private ulong _sampleOffset, _sampleCount; + + public SilenceGenerator(uint sampleCount) + { + _sampleOffset = 0; + _sampleCount = sampleCount; + } + + public ulong Length + { + get + { + return _sampleCount; + } + } + + public ulong Remaining + { + get + { + return _sampleCount - _sampleOffset; + } + } + + public ulong Position + { + get + { + return _sampleOffset; + } + set + { + _sampleOffset = value; + } + } + + public int BitsPerSample + { + get + { + return 16; + } + } + + public int ChannelCount + { + get + { + return 2; + } + } + + public int SampleRate + { + get + { + return 44100; + } + } + + public NameValueCollection Tags + { + get + { + return new NameValueCollection(); + } + set + { + } + } + + public uint Read(byte[] buff, uint sampleCount) + { + uint samplesRemaining, byteCount, i; + + samplesRemaining = (uint)(_sampleCount - _sampleOffset); + if (sampleCount > samplesRemaining) + { + sampleCount = samplesRemaining; + } + + byteCount = sampleCount * 2 * 2; + for (i = 0; i < byteCount; i++) + { + buff[i] = 0; + } + + _sampleOffset += sampleCount; + + return sampleCount; + } + + public void Close() + { + } + + public string Path { get { return null; } } + } + + public class WAVReader : IAudioSource + { + FileStream _fs; + BinaryReader _br; + ulong _dataOffset, _dataLen; + ulong _samplePos, _sampleLen; + int _bitsPerSample, _channelCount, _sampleRate, _blockAlign; + bool _largeFile; + string _path; + + public WAVReader(string path) + { + _path = path; + //_fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read); + _fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 0x10000, FileOptions.SequentialScan); + _br = new BinaryReader(_fs); + + ParseHeaders(); + + _sampleLen = _dataLen / (uint)_blockAlign; + Position = 0; + } + + public void Close() + { + _br.Close(); + + _br = null; + _fs = null; + } + + private void ParseHeaders() + { + const long maxFileSize = 0x7FFFFFFEL; + const uint fccRIFF = 0x46464952; + const uint fccWAVE = 0x45564157; + const uint fccFormat = 0x20746D66; + const uint fccData = 0x61746164; + + uint lenRIFF; + long fileEnd; + bool foundFormat, foundData; + + if (_br.ReadUInt32() != fccRIFF) + { + throw new Exception("Not a valid RIFF file."); + } + + lenRIFF = _br.ReadUInt32(); + fileEnd = (long)lenRIFF + 8; + + if (_br.ReadUInt32() != fccWAVE) + { + throw new Exception("Not a valid WAVE file."); + } + + _largeFile = false; + foundFormat = false; + foundData = false; + + while (_fs.Position < fileEnd) + { + uint ckID, ckSize, ckSizePadded; + long ckEnd; + + ckID = _br.ReadUInt32(); + ckSize = _br.ReadUInt32(); + ckSizePadded = (ckSize + 1U) & ~1U; + ckEnd = _fs.Position + (long)ckSizePadded; + + if (ckID == fccFormat) + { + foundFormat = true; + + if (_br.ReadUInt16() != 1) + { + throw new Exception("WAVE must be PCM format."); + } + _channelCount = _br.ReadInt16(); + _sampleRate = _br.ReadInt32(); + _br.ReadInt32(); + _blockAlign = _br.ReadInt16(); + _bitsPerSample = _br.ReadInt16(); + } + else if (ckID == fccData) + { + foundData = true; + + _dataOffset = (ulong)_fs.Position; + if (_fs.Length <= maxFileSize) + { + _dataLen = ckSize; + } + else + { + _largeFile = true; + _dataLen = ((ulong)_fs.Length) - _dataOffset; + } + } + + if ((foundFormat & foundData) || _largeFile) + { + break; + } + + _fs.Seek(ckEnd, SeekOrigin.Begin); + } + + if ((foundFormat & foundData) == false) + { + throw new Exception("Format or data chunk not found."); + } + + if (_channelCount <= 0) + { + throw new Exception("Channel count is invalid."); + } + if (_sampleRate <= 0) + { + throw new Exception("Sample rate is invalid."); + } + if (_blockAlign != (_channelCount * ((_bitsPerSample + 7) / 8))) + { + throw new Exception("Block align is invalid."); + } + if ((_bitsPerSample <= 0) || (_bitsPerSample > 32)) + { + throw new Exception("Bits per sample is invalid."); + } + } + + public ulong Position + { + get + { + return _samplePos; + } + set + { + ulong seekPos; + + if (value > _sampleLen) + { + _samplePos = _sampleLen; + } + else + { + _samplePos = value; + } + + seekPos = _dataOffset + (_samplePos * (uint)_blockAlign); + _fs.Seek((long)seekPos, SeekOrigin.Begin); + } + } + + public ulong Length + { + get + { + return _sampleLen; + } + } + + public ulong Remaining + { + get + { + return _sampleLen - _samplePos; + } + } + + public int ChannelCount + { + get + { + return _channelCount; + } + } + + public int SampleRate + { + get + { + return _sampleRate; + } + } + + public int BitsPerSample + { + get + { + return _bitsPerSample; + } + } + + public int BlockAlign + { + get + { + return _blockAlign; + } + } + + public NameValueCollection Tags + { + get + { + return new NameValueCollection(); + } + set + { + } + } + + public void GetTags(out List names, out List values) + { + names = new List(); + values = new List(); + } + + public uint Read(byte[] buff, uint sampleCount) + { + if (sampleCount > Remaining) + sampleCount = (uint)Remaining; + + uint byteCount = sampleCount * (uint)_blockAlign; + + if (sampleCount != 0) + { + if (_fs.Read(buff, 0, (int)byteCount) != byteCount) + { + throw new Exception("Incomplete file read."); + } + _samplePos += sampleCount; + } + + return sampleCount; + } + + public string Path { get { return _path; } } + } + + public class WAVWriter : IAudioDest + { + FileStream _fs; + BinaryWriter _bw; + int _bitsPerSample, _channelCount, _sampleRate, _blockAlign; + long _sampleLen; + string _path; + private byte[] _sampleBuffer; + + public WAVWriter(string path, int bitsPerSample, int channelCount, int sampleRate) + { + _path = path; + _bitsPerSample = bitsPerSample; + _channelCount = channelCount; + _sampleRate = sampleRate; + _blockAlign = _channelCount * ((_bitsPerSample + 7) / 8); + + _fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read); + _bw = new BinaryWriter(_fs); + + WriteHeaders(); + } + + public bool SetTags(NameValueCollection tags) + { + return false; + } + + private void WriteHeaders() + { + const uint fccRIFF = 0x46464952; + const uint fccWAVE = 0x45564157; + const uint fccFormat = 0x20746D66; + const uint fccData = 0x61746164; + + _bw.Write(fccRIFF); + _bw.Write((uint)0); + _bw.Write(fccWAVE); + + _bw.Write(fccFormat); + _bw.Write((uint)16); + _bw.Write((ushort)1); + _bw.Write((ushort)_channelCount); + _bw.Write((uint)_sampleRate); + _bw.Write((uint)(_sampleRate * _blockAlign)); + _bw.Write((ushort)_blockAlign); + _bw.Write((ushort)_bitsPerSample); + + _bw.Write(fccData); + _bw.Write((uint)0); + } + + public void Close() + { + const long maxFileSize = 0x7FFFFFFEL; + long dataLen, dataLenPadded; + + dataLen = _sampleLen * _blockAlign; + + if ((dataLen & 1) == 1) + { + _bw.Write((byte)0); + } + + if ((dataLen + 44) > maxFileSize) + { + dataLen = ((maxFileSize - 44) / _blockAlign) * _blockAlign; + } + + dataLenPadded = ((dataLen & 1) == 1) ? (dataLen + 1) : dataLen; + + _bw.Seek(4, SeekOrigin.Begin); + _bw.Write((uint)(dataLenPadded + 36)); + + _bw.Seek(40, SeekOrigin.Begin); + _bw.Write((uint)dataLen); + + _bw.Close(); + + _bw = null; + _fs = null; + } + + public long Position + { + get + { + return _sampleLen; + } + } + + public long FinalSampleCount + { + set + { + } + } + + public void Write(int[,] buff, uint sampleCount) + { + if (sampleCount == 0) + return; + if (_sampleBuffer == null || _sampleBuffer.Length < sampleCount * _channelCount) + _sampleBuffer = new byte[sampleCount * _blockAlign]; + AudioCodecsDotNet.FLACSamplesToBytes(buff, 0, _sampleBuffer, 0, + sampleCount, _channelCount, _bitsPerSample); + _fs.Write(_sampleBuffer, 0, (int)sampleCount * _blockAlign); + _sampleLen += sampleCount; + } + + public string Path { get { return _path; } } + } +} diff --git a/AudioCodecsDotNet/AudioCodecsDotNet.csproj b/AudioCodecsDotNet/AudioCodecsDotNet.csproj new file mode 100644 index 0000000..9edac8d --- /dev/null +++ b/AudioCodecsDotNet/AudioCodecsDotNet.csproj @@ -0,0 +1,95 @@ + + + Debug + AnyCPU + 8.0.50727 + 2.0 + {6458A13A-30EF-45A9-9D58-E5031B17BEE2} + Library + Properties + AudioCodecsDotNet + AudioCodecsDotNet + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + true + ..\bin\x64\Debug\ + DEBUG;TRACE + full + x64 + C:\Program Files (x86)\Microsoft Visual Studio 8\Team Tools\Static Analysis Tools\FxCop\\rules + true + GlobalSuppressions.cs + prompt + true + + + ..\bin\x64\Release\ + TRACE + true + pdbonly + x64 + C:\Program Files (x86)\Microsoft Visual Studio 8\Team Tools\Static Analysis Tools\FxCop\\rules + true + GlobalSuppressions.cs + prompt + true + + + true + ..\bin\win32\Debug\ + DEBUG;TRACE + full + x86 + C:\Program Files (x86)\Microsoft Visual Studio 8\Team Tools\Static Analysis Tools\FxCop\\rules + true + GlobalSuppressions.cs + prompt + true + + + ..\bin\win32\Release\ + TRACE + true + pdbonly + x86 + C:\Program Files (x86)\Microsoft Visual Studio 8\Team Tools\Static Analysis Tools\FxCop\\rules + true + GlobalSuppressions.cs + prompt + true + + + + + + + + + + + + + \ No newline at end of file diff --git a/AudioCodecsDotNet/Properties/AssemblyInfo.cs b/AudioCodecsDotNet/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..a4d3f2d --- /dev/null +++ b/AudioCodecsDotNet/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("AudioCodecsDotNet")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft")] +[assembly: AssemblyProduct("AudioCodecsDotNet")] +[assembly: AssemblyCopyright("Copyright © Microsoft 2008")] +[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("eee100ad-1381-47f6-b31f-eb1af1d47aef")] + +// 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("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")]