diff --git a/CUETools.Codecs.libFLAC/CUETools.Codecs.libFLAC.csproj b/CUETools.Codecs.libFLAC/CUETools.Codecs.libFLAC.csproj new file mode 100644 index 0000000..b638934 --- /dev/null +++ b/CUETools.Codecs.libFLAC/CUETools.Codecs.libFLAC.csproj @@ -0,0 +1,29 @@ + + + + net40;net20;netstandard2.0 + 2.1.6.0 + CUETools.Codecs.libFLAC + CUETools.Codecs.libFLAC + CUETools + A library for encoding flac using official encoder. + Copyright (c) 2008-2018 Grigory Chudov + Grigory Chudov + true + ..\bin\$(Configuration)\plugins + https://github.com/gchudov/cuetools.net + git + + + + + + False + + + + + + + + diff --git a/CUETools.Codecs.libFLAC/FLACDLL.cs b/CUETools.Codecs.libFLAC/FLACDLL.cs new file mode 100644 index 0000000..ca666ff --- /dev/null +++ b/CUETools.Codecs.libFLAC/FLACDLL.cs @@ -0,0 +1,166 @@ +using System; +using System.Runtime.InteropServices; + +namespace CUETools.Codecs.libFLAC +{ + internal unsafe static class FLACDLL + { + internal const string libFLACDll = "libFLAC_dynamic"; + internal const CallingConvention libFLACCallingConvention = CallingConvention.Cdecl; + internal const int FLAC__MAX_CHANNELS = 8; + + [DllImport("kernel32.dll")] + private static extern IntPtr LoadLibrary(string dllToLoad); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate FLAC__StreamDecoderReadStatus FLAC__StreamDecoderReadCallback(IntPtr decoder, byte* buffer, ref long bytes, void* client_data); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate FLAC__StreamDecoderSeekStatus FLAC__StreamDecoderSeekCallback(IntPtr decoder, long absolute_byte_offset, void* client_data); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate FLAC__StreamDecoderTellStatus FLAC__StreamDecoderTellCallback(IntPtr decoder, out long absolute_byte_offset, void* client_data); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate FLAC__StreamDecoderLengthStatus FLAC__StreamDecoderLengthCallback(IntPtr decoder, out long stream_length, void* client_data); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate int FLAC__StreamDecoderEofCallback(IntPtr decoder, void* client_data); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate FLAC__StreamDecoderWriteStatus FLAC__StreamDecoderWriteCallback(IntPtr decoder, FLAC__Frame* frame, int** buffer, void* client_data); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate void FLAC__StreamDecoderMetadataCallback(IntPtr decoder, FLAC__StreamMetadata* metadata, void* client_data); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate void FLAC__StreamDecoderErrorCallback(IntPtr decoder, FLAC__StreamDecoderErrorStatus status, void* client_data); + + [DllImport(libFLACDll, CallingConvention = libFLACCallingConvention)] + internal static extern IntPtr FLAC__stream_decoder_new(); + + [DllImport(libFLACDll, CallingConvention = libFLACCallingConvention)] + internal static extern int FLAC__stream_decoder_set_metadata_respond(IntPtr decoder, FLAC__MetadataType type); + + [DllImport(libFLACDll, CallingConvention = libFLACCallingConvention)] + internal static extern int FLAC__stream_decoder_process_until_end_of_metadata(IntPtr decoder); + + [DllImport(libFLACDll, CallingConvention = libFLACCallingConvention)] + internal static extern FLAC__StreamDecoderInitStatus FLAC__stream_decoder_init_stream( + IntPtr decoder, + FLAC__StreamDecoderReadCallback read_callback, + FLAC__StreamDecoderSeekCallback seek_callback, + FLAC__StreamDecoderTellCallback tell_callback, + FLAC__StreamDecoderLengthCallback length_callback, + FLAC__StreamDecoderEofCallback eof_callback, + FLAC__StreamDecoderWriteCallback write_callback, + FLAC__StreamDecoderMetadataCallback metadata_callback, + FLAC__StreamDecoderErrorCallback error_callback, + void* client_data + ); + + [DllImport(libFLACDll, CallingConvention = libFLACCallingConvention)] + internal static extern int FLAC__stream_decoder_finish(IntPtr decoder); + + [DllImport(libFLACDll, CallingConvention = libFLACCallingConvention)] + internal static extern int FLAC__stream_decoder_delete(IntPtr decoder); + + [DllImport(libFLACDll, CallingConvention = libFLACCallingConvention)] + internal static extern int FLAC__stream_decoder_seek_absolute(IntPtr decoder, long sample); + + [DllImport(libFLACDll, CallingConvention = libFLACCallingConvention)] + internal static extern FLAC__StreamDecoderState FLAC__stream_decoder_get_state(IntPtr decoder); + + [DllImport(libFLACDll, CallingConvention = libFLACCallingConvention)] + internal static extern int FLAC__stream_decoder_process_single(IntPtr decoder); + + + + [DllImport(libFLACDll, CallingConvention = libFLACCallingConvention)] + internal static extern IntPtr FLAC__stream_encoder_new(); + + [DllImport(libFLACDll, CallingConvention = libFLACCallingConvention)] + internal static extern int FLAC__stream_encoder_set_bits_per_sample(IntPtr encoder, uint value); + + [DllImport(libFLACDll, CallingConvention = libFLACCallingConvention)] + internal static extern int FLAC__stream_encoder_set_sample_rate(IntPtr encoder, uint value); + + [DllImport(libFLACDll, CallingConvention = libFLACCallingConvention)] + internal static extern int FLAC__stream_encoder_set_channels(IntPtr encoder, uint value); + + [DllImport(libFLACDll, CallingConvention = libFLACCallingConvention)] + internal static extern int FLAC__stream_encoder_finish(IntPtr encoder); + + [DllImport(libFLACDll, CallingConvention = libFLACCallingConvention)] + internal static extern int FLAC__stream_encoder_delete(IntPtr encoder); + + [DllImport(libFLACDll, CallingConvention = libFLACCallingConvention)] + internal static extern int FLAC__stream_encoder_process_interleaved(IntPtr encoder, int* buffer, int samples); + + [DllImport(libFLACDll, CallingConvention = libFLACCallingConvention)] + internal static extern FLAC__StreamEncoderState FLAC__stream_encoder_get_state(IntPtr encoder); + + [DllImport(libFLACDll, CallingConvention = libFLACCallingConvention)] + internal static extern void FLAC__stream_encoder_get_verify_decoder_error_stats(IntPtr encoder, out ulong absolute_sample, out uint frame_number, out uint channel, out uint sample, out int expected, out int got); + + [DllImport(libFLACDll, CallingConvention = libFLACCallingConvention)] + internal static extern FLAC__StreamMetadata* FLAC__metadata_object_new(FLAC__MetadataType type); + + [DllImport(libFLACDll, CallingConvention = libFLACCallingConvention)] + internal static extern int FLAC__metadata_object_seektable_template_append_spaced_points_by_samples(FLAC__StreamMetadata* metadata, int samples, long total_samples); + + [DllImport(libFLACDll, CallingConvention = libFLACCallingConvention)] + internal static extern int FLAC__metadata_object_seektable_template_sort(FLAC__StreamMetadata* metadata, int compact); + + [DllImport(libFLACDll, CallingConvention = libFLACCallingConvention)] + internal static extern int FLAC__stream_encoder_set_metadata(IntPtr encoder, FLAC__StreamMetadata** metadata, int num_blocks); + + [DllImport(libFLACDll, CallingConvention = libFLACCallingConvention)] + internal static extern int FLAC__stream_encoder_set_verify(IntPtr encoder, int value); + + [DllImport(libFLACDll, CallingConvention = libFLACCallingConvention)] + internal static extern int FLAC__stream_encoder_set_do_md5(IntPtr encoder, int value); + + [DllImport(libFLACDll, CallingConvention = libFLACCallingConvention)] + internal static extern int FLAC__stream_encoder_set_total_samples_estimate(IntPtr encoder, long value); + + [DllImport(libFLACDll, CallingConvention = libFLACCallingConvention)] + internal static extern int FLAC__stream_encoder_set_compression_level(IntPtr encoder, int value); + + [DllImport(libFLACDll, CallingConvention = libFLACCallingConvention)] + internal static extern int FLAC__stream_encoder_set_blocksize(IntPtr encoder, int value); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate FLAC__StreamEncoderWriteStatus FLAC__StreamEncoderWriteCallback(IntPtr encoder, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)] byte[] buffer, long bytes, int samples, int current_frame, void* client_data); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate FLAC__StreamEncoderSeekStatus FLAC__StreamEncoderSeekCallback(IntPtr encoder, long absolute_byte_offset, void* client_data); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate FLAC__StreamEncoderTellStatus FLAC__StreamEncoderTellCallback(IntPtr encoder, out long absolute_byte_offset, void* client_data); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate void FLAC__StreamEncoderMetadataCallback(IntPtr encoder, FLAC__StreamMetadata* metadata, void* client_data); + + [DllImport(libFLACDll, CallingConvention = libFLACCallingConvention)] + internal static extern FLAC__StreamEncoderInitStatus FLAC__stream_encoder_init_stream(IntPtr encoder, + FLAC__StreamEncoderWriteCallback write_callback, + FLAC__StreamEncoderSeekCallback seek_callback, + FLAC__StreamEncoderTellCallback tell_callback, + FLAC__StreamEncoderMetadataCallback metadata_callback, + void* client_data); + + static FLACDLL() + { + var myPath = new Uri(typeof(FLACDLL).Assembly.CodeBase).LocalPath; + var myFolder = System.IO.Path.GetDirectoryName(myPath); + var is64 = IntPtr.Size == 8; + var subfolder = is64 ? "plugins (x64)" : "plugins (win32)"; +#if NET40 + LoadLibrary(System.IO.Path.Combine(myFolder, "..", subfolder, libFLACDll + ".dll")); +#else + LoadLibrary(System.IO.Path.Combine(System.IO.Path.Combine(System.IO.Path.Combine(myFolder, ".."), subfolder), libFLACDll + ".dll")); +#endif + } + }; +} diff --git a/CUETools.Codecs.libFLAC/Reader.cs b/CUETools.Codecs.libFLAC/Reader.cs new file mode 100644 index 0000000..5364a3b --- /dev/null +++ b/CUETools.Codecs.libFLAC/Reader.cs @@ -0,0 +1,364 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; +using CUETools.Codecs; + +namespace CUETools.Codecs.libFLAC.Reader +{ + [AudioDecoderClass("libFLAC", "flac", 1)] + public unsafe class Reader : IAudioSource + { + public Reader(string path, Stream IO) + { + m_writeCallback = WriteCallback; + m_metadataCallback = MetadataCallback; + m_errorCallback = ErrorCallback; + m_readCallback = ReadCallback; + m_seekCallback = SeekCallback; + m_tellCallback = TellCallback; + m_lengthCallback = LengthCallback; + m_eofCallback = EofCallback; + + m_decoderActive = false; + + m_sampleOffset = 0; + m_sampleBuffer = null; + m_path = path; + m_bufferOffset = 0; + m_bufferLength = 0; + + m_stream = (IO != null) ? IO : new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read); + + m_decoder = FLACDLL.FLAC__stream_decoder_new(); + + if (0 == FLACDLL.FLAC__stream_decoder_set_metadata_respond(m_decoder, FLAC__MetadataType.FLAC__METADATA_TYPE_VORBIS_COMMENT)) + throw new Exception("unable to setup the decoder"); + + FLAC__StreamDecoderInitStatus st = FLACDLL.FLAC__stream_decoder_init_stream( + m_decoder, m_readCallback, + m_stream.CanSeek ? m_seekCallback : null, + m_stream.CanSeek ? m_tellCallback : null, + m_stream.CanSeek ? m_lengthCallback : null, + m_stream.CanSeek ? m_eofCallback : null, + m_writeCallback, m_metadataCallback, m_errorCallback, null); + + if (st != FLAC__StreamDecoderInitStatus.FLAC__STREAM_DECODER_INIT_STATUS_OK) + throw new Exception(string.Format("unable to initialize the decoder: {0}", st)); + + m_decoderActive = true; + + if (0 == FLACDLL.FLAC__stream_decoder_process_until_end_of_metadata(m_decoder)) + throw new Exception("unable to retrieve metadata"); + } + +#if SUPPORTMETADATA + bool UpdateTags (bool preserveTime) + { + Close (); + + FLAC__Metadata_Chain* chain = FLAC__metadata_chain_new (); + if (!chain) return false; + + IntPtr pathChars = Marshal::StringToHGlobalAnsi(_path); + int res = FLAC__metadata_chain_read (chain, (const char*)pathChars.ToPointer()); + Marshal::FreeHGlobal(pathChars); + if (!res) { + FLAC__metadata_chain_delete (chain); + return false; + } + FLAC__Metadata_Iterator* i = FLAC__metadata_iterator_new (); + FLAC__metadata_iterator_init (i, chain); + do { + FLAC__StreamMetadata* metadata = FLAC__metadata_iterator_get_block (i); + if (metadata->type == FLAC__METADATA_TYPE_VORBIS_COMMENT) + FLAC__metadata_iterator_delete_block (i, false); + } while (FLAC__metadata_iterator_next (i)); + + FLAC__StreamMetadata * vorbiscomment = FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT); + for (int tagno = 0; tagno <_tags->Count; tagno++) + { + String ^ tag_name = _tags->GetKey(tagno); + int tag_len = tag_name->Length; + char * tag = new char [tag_len + 1]; + IntPtr nameChars = Marshal::StringToHGlobalAnsi(tag_name); + memcpy (tag, (const char*)nameChars.ToPointer(), tag_len); + Marshal::FreeHGlobal(nameChars); + tag[tag_len] = 0; + + array^ tag_values = _tags->GetValues(tagno); + for (int valno = 0; valno < tag_values->Length; valno++) + { + UTF8Encoding^ enc = new UTF8Encoding(); + array^ value_array = enc->GetBytes (tag_values[valno]); + int value_len = value_array->Length; + char * value = new char [value_len + 1]; + Marshal::Copy (value_array, 0, (IntPtr) value, value_len); + value[value_len] = 0; + + FLAC__StreamMetadata_VorbisComment_Entry entry; + /* create and entry and append it */ + if(!FLAC__metadata_object_vorbiscomment_entry_from_name_value_pair(&entry, tag, value)) { + throw new Exception("Unable to add tags, must be valid utf8."); + } + if(!FLAC__metadata_object_vorbiscomment_append_comment(vorbiscomment, entry, /*copy=*/false)) { + throw new Exception("Unable to add tags."); + } + delete [] value; + } + delete [] tag; + } + + FLAC__metadata_iterator_insert_block_after (i, vorbiscomment); + FLAC__metadata_iterator_delete (i); + FLAC__metadata_chain_sort_padding (chain); + res = FLAC__metadata_chain_write (chain, true, preserveTime); + FLAC__metadata_chain_delete (chain); + return 0 != res; + } +#endif + + FLAC__StreamDecoderWriteStatus WriteCallback(IntPtr decoder, + FLAC__Frame* frame, int** buffer, void* client_data) + { + int sampleCount = frame->header.blocksize; + + if (m_bufferLength > 0) + throw new Exception("received unrequested samples"); + + if ((frame->header.bits_per_sample != m_pcm.BitsPerSample) || + (frame->header.channels != m_pcm.ChannelCount) || + (frame->header.sample_rate != m_pcm.SampleRate)) + throw new Exception("format changes within a file are not allowed"); + + if (m_bufferOffset != 0) + throw new Exception("internal buffer error"); + + if (m_sampleBuffer == null || m_sampleBuffer.Size < sampleCount) + m_sampleBuffer = new AudioBuffer(m_pcm, sampleCount); + m_sampleBuffer.Length = sampleCount; + + if (m_pcm.ChannelCount == 2) + m_sampleBuffer.Interlace(0, (int*)buffer[0], (int*)buffer[1], sampleCount); + else + { + int _channelCount = m_pcm.ChannelCount; + for (Int32 iChan = 0; iChan < _channelCount; iChan++) + { + fixed (int* pMyBuffer = &m_sampleBuffer.Samples[0, iChan]) + { + int* pMyBufferPtr = pMyBuffer; + int* pFLACBuffer = buffer[iChan]; + int* pFLACBufferEnd = pFLACBuffer + sampleCount; + while (pFLACBuffer < pFLACBufferEnd) + { + *pMyBufferPtr = *pFLACBuffer; + pMyBufferPtr += _channelCount; + pFLACBuffer++; + } + } + } + } + m_bufferLength = sampleCount; + m_sampleOffset += m_bufferLength; + return FLAC__StreamDecoderWriteStatus.FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; + } + + void MetadataCallback(IntPtr decoder, + FLAC__StreamMetadata *metadata, void *client_data) + { + if (metadata->type == FLAC__MetadataType.FLAC__METADATA_TYPE_STREAMINFO) + { + m_pcm = new AudioPCMConfig( + metadata->stream_info.bits_per_sample, + metadata->stream_info.channels, + metadata->stream_info.sample_rate, + (AudioPCMConfig.SpeakerConfig)0); + m_sampleCount = metadata->stream_info.total_samples; + } +#if SUPPORTMETADATA + if (metadata->type == FLAC__METADATA_TYPE_VORBIS_COMMENT) + { + for (int tagno = 0; tagno < metadata->vorbis_comment.num_comments; tagno ++) + { + char * field_name, * field_value; + if(!FLACDLL.FLAC__metadata_object_vorbiscomment_entry_to_name_value_pair(metadata->vorbis_comment.comments[tagno], &field_name, &field_value)) + throw new Exception("Unable to parse vorbis comment."); + string name = Marshal::PtrToStringAnsi ((IntPtr) field_name); + free (field_name); + array^ bvalue = new array((int) strlen (field_value)); + Marshal.Copy ((IntPtr) field_value, bvalue, 0, (int) strlen (field_value)); + free (field_value); + UTF8Encoding enc = new UTF8Encoding(); + string value = enc.GetString(bvalue); + _tags.Add(name, value); + } + } +#endif + } + + void ErrorCallback(IntPtr decoder, + FLAC__StreamDecoderErrorStatus status, void *client_data) + { + switch (status) + { + case FLAC__StreamDecoderErrorStatus.FLAC__STREAM_DECODER_ERROR_STATUS_LOST_SYNC: + throw new Exception("synchronization was lost"); + case FLAC__StreamDecoderErrorStatus.FLAC__STREAM_DECODER_ERROR_STATUS_BAD_HEADER: + throw new Exception("encountered a corrupted frame header"); + case FLAC__StreamDecoderErrorStatus.FLAC__STREAM_DECODER_ERROR_STATUS_FRAME_CRC_MISMATCH: + throw new Exception("frame CRC mismatch"); + default: + throw new Exception("an unknown error has occurred"); + } + } + + FLAC__StreamDecoderReadStatus ReadCallback(IntPtr decoder, byte* buffer, ref long bytes, void* client_data) + { + if (bytes <= 0 || bytes > int.MaxValue) + return FLAC__StreamDecoderReadStatus.FLAC__STREAM_DECODER_READ_STATUS_ABORT; /* abort to avoid a deadlock */ + + if (m_readBuffer == null || m_readBuffer.Length < bytes) + m_readBuffer = new byte[Math.Max(bytes, 0x4000)]; + + bytes = m_stream.Read(m_readBuffer, 0, (int)bytes); + //if(ferror(decoder->private_->file)) + //return FLAC__STREAM_DECODER_READ_STATUS_ABORT; + //else + if (bytes == 0) + return FLAC__StreamDecoderReadStatus.FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM; + + Marshal.Copy(m_readBuffer, 0, (IntPtr)buffer, (int)bytes); + return FLAC__StreamDecoderReadStatus.FLAC__STREAM_DECODER_READ_STATUS_CONTINUE; + } + + FLAC__StreamDecoderSeekStatus SeekCallback(IntPtr decoder, long absolute_byte_offset, void* client_data) + { + //if (!_IO.CanSeek) + // return FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED; + m_stream.Position = absolute_byte_offset; + //catch(Exception) { + // return FLAC__STREAM_DECODER_SEEK_STATUS_ERROR; + //} + return FLAC__StreamDecoderSeekStatus.FLAC__STREAM_DECODER_SEEK_STATUS_OK; + } + + FLAC__StreamDecoderTellStatus TellCallback(IntPtr decoder, out long absolute_byte_offset, void* client_data) + { + //if (!_IO.CanSeek) + // return FLAC__STREAM_DECODER_TELL_STATUS_UNSUPPORTED; + absolute_byte_offset = m_stream.Position; + // if (_IO.Position < 0) + // return FLAC__STREAM_DECODER_TELL_STATUS_ERROR; + return FLAC__StreamDecoderTellStatus.FLAC__STREAM_DECODER_TELL_STATUS_OK; + } + + FLAC__StreamDecoderLengthStatus LengthCallback(IntPtr decoder, out long stream_length, void* client_data) + { + //if (!_IO.CanSeek) + // return FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED; + // if (_IO.Length < 0) + // return FLAC__STREAM_DECODER_LENGTH_STATUS_ERROR; + stream_length = m_stream.Length; + return FLAC__StreamDecoderLengthStatus.FLAC__STREAM_DECODER_LENGTH_STATUS_OK; + } + + int EofCallback (IntPtr decoder, void *client_data) + { + return m_stream.Position == m_stream.Length ? 1 : 0; + } + + public AudioDecoderSettings Settings => null; + + public AudioPCMConfig PCM => m_pcm; + + public string Path => m_path; + + public long Length => m_sampleCount; + + private int SamplesInBuffer => m_bufferLength - m_bufferOffset; + + public long Position + { + get => m_sampleOffset - SamplesInBuffer; + + set + { + m_sampleOffset = value; + m_bufferOffset = 0; + m_bufferLength = 0; + if (0 == FLACDLL.FLAC__stream_decoder_seek_absolute(m_decoder, value)) + throw new Exception("unable to seek"); + } + } + + public long Remaining { get => m_sampleCount - Position; } + + public void Close() + { + if (m_decoderActive) + { + FLACDLL.FLAC__stream_decoder_finish(m_decoder); + FLACDLL.FLAC__stream_decoder_delete(m_decoder); + m_decoderActive = false; + } + if (m_stream != null) + { + m_stream.Close(); + m_stream = null; + } + } + + public int Read(AudioBuffer buff, int maxLength) + { + buff.Prepare(this, maxLength); + int buffOffset = 0; + int samplesNeeded = buff.Length; + + while (samplesNeeded != 0) + { + if (SamplesInBuffer == 0) + { + m_bufferOffset = 0; + m_bufferLength = 0; + do + { + if (FLACDLL.FLAC__stream_decoder_get_state(m_decoder) == FLAC__StreamDecoderState.FLAC__STREAM_DECODER_END_OF_STREAM) + { + buff.Length -= samplesNeeded; + return buff.Length; + } + if (0 == FLACDLL.FLAC__stream_decoder_process_single(m_decoder)) + throw new Exception(string.Format("an error occurred while decoding: {0}", FLACDLL.FLAC__stream_decoder_get_state(m_decoder))); + } while (m_bufferLength == 0); + } + int copyCount = Math.Min(samplesNeeded, SamplesInBuffer); + Array.Copy(m_sampleBuffer.Bytes, m_bufferOffset * m_pcm.BlockAlign, buff.Bytes, buffOffset * m_pcm.BlockAlign, copyCount * m_pcm.BlockAlign); + samplesNeeded -= copyCount; + buffOffset += copyCount; + m_bufferOffset += copyCount; + } + return buff.Length; + } + + AudioBuffer m_sampleBuffer; + byte[] m_readBuffer; + long m_sampleCount, m_sampleOffset; + int m_bufferOffset, m_bufferLength; + IntPtr m_decoder; + string m_path; + Stream m_stream; + bool m_decoderActive; + AudioPCMConfig m_pcm; + FLACDLL.FLAC__StreamDecoderReadCallback m_readCallback; + FLACDLL.FLAC__StreamDecoderSeekCallback m_seekCallback; + FLACDLL.FLAC__StreamDecoderTellCallback m_tellCallback; + FLACDLL.FLAC__StreamDecoderLengthCallback m_lengthCallback; + FLACDLL.FLAC__StreamDecoderEofCallback m_eofCallback; + FLACDLL.FLAC__StreamDecoderWriteCallback m_writeCallback; + FLACDLL.FLAC__StreamDecoderMetadataCallback m_metadataCallback; + FLACDLL.FLAC__StreamDecoderErrorCallback m_errorCallback; + } +} diff --git a/CUETools.Codecs.libFLAC/Writer.cs b/CUETools.Codecs.libFLAC/Writer.cs new file mode 100644 index 0000000..28db1a6 --- /dev/null +++ b/CUETools.Codecs.libFLAC/Writer.cs @@ -0,0 +1,251 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; +using CUETools.Codecs; + +namespace CUETools.Codecs.libFLAC.Writer +{ + public class WriterSettings : AudioEncoderSettings + { + public WriterSettings() + : base("0 1 2 3 4 5 6 7 8", "5") + { + } + + [DefaultValue(false)] + [DisplayName("Verify")] + [Description("Decode each frame and compare with original")] + public bool Verify { get; set; } + + [DefaultValue(true)] + [DisplayName("MD5")] + [Description("Calculate MD5 hash for audio stream")] + public bool MD5Sum { get; set; } + }; + + [AudioEncoderClass("libFLAC", "flac", true, 2, typeof(WriterSettings))] + public unsafe class Writer : IAudioDest + { + public Writer(string path, Stream output, WriterSettings settings) + { + m_path = path; + m_stream = output; + m_settings = settings; + m_streamGiven = output != null; + m_initialized = false; + m_finalSampleCount = 0; + m_samplesWritten = 0; + m_write_callback = StreamEncoderWriteCallback; + m_seek_callback = StreamEncoderSeekCallback; + m_tell_callback = StreamEncoderTellCallback; + + if (m_settings.PCM.BitsPerSample < 16 || m_settings.PCM.BitsPerSample > 24) + throw new Exception("bits per sample must be 16..24"); + + m_encoder = FLACDLL.FLAC__stream_encoder_new(); + + FLACDLL.FLAC__stream_encoder_set_bits_per_sample(m_encoder, (uint)m_settings.PCM.BitsPerSample); + FLACDLL.FLAC__stream_encoder_set_channels(m_encoder, (uint)m_settings.PCM.ChannelCount); + FLACDLL.FLAC__stream_encoder_set_sample_rate(m_encoder, (uint)m_settings.PCM.SampleRate); + } + + public Writer(string path, WriterSettings settings) + : this(path, null, settings) + { + } + + public AudioEncoderSettings Settings => m_settings; + + public string Path { get => m_path; } + + public long FinalSampleCount + { + get => m_finalSampleCount; + set + { + if (value < 0) + throw new Exception("invalid final sample count"); + if (m_initialized) + throw new Exception("final sample count cannot be changed after encoding begins"); + m_finalSampleCount = value; + } + } + + public void Close() + { + if (m_initialized) + { + FLACDLL.FLAC__stream_encoder_finish(m_encoder); + FLACDLL.FLAC__stream_encoder_delete(m_encoder); + m_encoder = IntPtr.Zero; + m_initialized = false; + } + if (m_stream != null) + { + m_stream.Close(); + m_stream = null; + } + if ((m_finalSampleCount != 0) && (m_samplesWritten != m_finalSampleCount)) + throw new Exception("samples written differs from the expected sample count"); + } + + public void Delete() + { + try + { + if (m_initialized) + { + FLACDLL.FLAC__stream_encoder_delete(m_encoder); + m_encoder = IntPtr.Zero; + m_initialized = false; + } + if (m_stream != null) + { + m_stream.Close(); + m_stream = null; + } + } + catch (Exception) + { + } + if (m_path != "") + File.Delete(m_path); + } + + public void Write(AudioBuffer sampleBuffer) + { + if (!m_initialized) Initialize(); + + sampleBuffer.Prepare(this); + + fixed (int* pSampleBuffer = &sampleBuffer.Samples[0, 0]) + { + if (0 == FLACDLL.FLAC__stream_encoder_process_interleaved(m_encoder, + pSampleBuffer, sampleBuffer.Length)) + { + var state = FLACDLL.FLAC__stream_encoder_get_state(m_encoder); + string status = state.ToString(); + if (state == FLAC__StreamEncoderState.FLAC__STREAM_ENCODER_VERIFY_MISMATCH_IN_AUDIO_DATA) + { + ulong absolute_sample; + uint frame_number; + uint channel; + uint sample; + int expected, got; + FLACDLL.FLAC__stream_encoder_get_verify_decoder_error_stats(m_encoder, out absolute_sample, out frame_number, out channel, out sample, out expected, out got); + status = status + String.Format("({0:x} instead of {1:x} @{2:x})", got, expected, absolute_sample); + } + throw new Exception("an error occurred while encoding: " + status); + } + } + + m_samplesWritten += sampleBuffer.Length; + } + + internal FLAC__StreamEncoderWriteStatus StreamEncoderWriteCallback(IntPtr encoder, byte[] buffer, long bytes, int samples, int current_frame, void* client_data) + { + try + { + m_stream.Write(buffer, 0, (int)bytes); + } + catch (Exception) + { + return FLAC__StreamEncoderWriteStatus.FLAC__STREAM_ENCODER_WRITE_STATUS_FATAL_ERROR; + } + return FLAC__StreamEncoderWriteStatus.FLAC__STREAM_ENCODER_WRITE_STATUS_OK; + } + + internal FLAC__StreamEncoderSeekStatus StreamEncoderSeekCallback(IntPtr encoder, long absolute_byte_offset, void* client_data) + { + if (!m_stream.CanSeek) return FLAC__StreamEncoderSeekStatus.FLAC__STREAM_ENCODER_SEEK_STATUS_UNSUPPORTED; + try + { + m_stream.Position = absolute_byte_offset; + } + catch (Exception) + { + return FLAC__StreamEncoderSeekStatus.FLAC__STREAM_ENCODER_SEEK_STATUS_ERROR; + } + return FLAC__StreamEncoderSeekStatus.FLAC__STREAM_ENCODER_SEEK_STATUS_OK; + } + + internal FLAC__StreamEncoderTellStatus StreamEncoderTellCallback(IntPtr encoder, out long absolute_byte_offset, void* client_data) + { + if (!m_stream.CanSeek) + { + absolute_byte_offset = -1; + return FLAC__StreamEncoderTellStatus.FLAC__STREAM_ENCODER_TELL_STATUS_UNSUPPORTED; + } + try + { + absolute_byte_offset = m_stream.Position; + } + catch (Exception) + { + absolute_byte_offset = -1; + return FLAC__StreamEncoderTellStatus.FLAC__STREAM_ENCODER_TELL_STATUS_ERROR; + } + return FLAC__StreamEncoderTellStatus.FLAC__STREAM_ENCODER_TELL_STATUS_OK; + } + + void Initialize() + { + if (m_stream == null) + m_stream = new FileStream(m_path, FileMode.Create, FileAccess.Write, FileShare.Read, 0x10000); + + var metadata = stackalloc FLAC__StreamMetadata*[4]; + int metadataCount = 0; + FLAC__StreamMetadata* padding, seektable, vorbiscomment; + + if (m_finalSampleCount != 0) + { + seektable = FLACDLL.FLAC__metadata_object_new(FLAC__MetadataType.FLAC__METADATA_TYPE_SEEKTABLE); + FLACDLL.FLAC__metadata_object_seektable_template_append_spaced_points_by_samples( + seektable, m_settings.PCM.SampleRate * 10, m_finalSampleCount); + FLACDLL.FLAC__metadata_object_seektable_template_sort(seektable, 1); + metadata[metadataCount++] = seektable; + } + + vorbiscomment = FLACDLL.FLAC__metadata_object_new(FLAC__MetadataType.FLAC__METADATA_TYPE_VORBIS_COMMENT); + metadata[metadataCount++] = vorbiscomment; + + if (m_settings.Padding != 0) + { + padding = FLACDLL.FLAC__metadata_object_new(FLAC__MetadataType.FLAC__METADATA_TYPE_PADDING); + padding->length = (uint)m_settings.Padding; + metadata[metadataCount++] = padding; + } + + FLACDLL.FLAC__stream_encoder_set_metadata(m_encoder, metadata, metadataCount); + FLACDLL.FLAC__stream_encoder_set_verify(m_encoder, m_settings.Verify ? 1 : 0); + FLACDLL.FLAC__stream_encoder_set_do_md5(m_encoder, m_settings.MD5Sum ? 1 : 0); + FLACDLL.FLAC__stream_encoder_set_compression_level(m_encoder, m_settings.EncoderModeIndex); + if (m_finalSampleCount != 0) + FLACDLL.FLAC__stream_encoder_set_total_samples_estimate(m_encoder, m_finalSampleCount); + if (m_settings.BlockSize > 0) + FLACDLL.FLAC__stream_encoder_set_blocksize(m_encoder, m_settings.BlockSize); + + FLAC__StreamEncoderInitStatus st = FLACDLL.FLAC__stream_encoder_init_stream( + m_encoder, m_write_callback, m_stream.CanSeek ? m_seek_callback : null, + m_stream.CanSeek ? m_tell_callback : null, null, null); + if (st != FLAC__StreamEncoderInitStatus.FLAC__STREAM_ENCODER_INIT_STATUS_OK) + throw new Exception(string.Format("unable to initialize the encoder: {0}", st)); + + m_initialized = true; + } + + WriterSettings m_settings; + Stream m_stream; + bool m_streamGiven; + IntPtr m_encoder; + bool m_initialized; + string m_path; + Int64 m_finalSampleCount, m_samplesWritten; + FLACDLL.FLAC__StreamEncoderWriteCallback m_write_callback; + FLACDLL.FLAC__StreamEncoderSeekCallback m_seek_callback; + FLACDLL.FLAC__StreamEncoderTellCallback m_tell_callback; + } +} diff --git a/CUETools.Codecs.libFLAC/libFLAC.cs b/CUETools.Codecs.libFLAC/libFLAC.cs new file mode 100644 index 0000000..8bebc4d --- /dev/null +++ b/CUETools.Codecs.libFLAC/libFLAC.cs @@ -0,0 +1,221 @@ +using System; +using System.Runtime.InteropServices; + +namespace CUETools.Codecs.libFLAC +{ + internal struct FLAC__FrameHeader + { + internal int blocksize; + internal int sample_rate; + internal int channels; + internal FLAC__ChannelAssignment channel_assignment; + internal int bits_per_sample; + internal FLAC__FrameNumberType number_type; + internal ulong sample_number; // can be uint frame_number depending on number_type + internal byte crc; + }; + + //internal struct FLAC__Subframe + //{ + // FLAC__SubframeType type; + // [FieldOffset(4)] + // FLAC__Subframe_Constant data_constant; + // [FieldOffset(4)] + // FLAC__Subframe_Fixed data_fixed; + // [FieldOffset(4)] + // FLAC__Subframe_LPC data_lpc; + // [FieldOffset(4)] + // FLAC__Subframe_Verbatim data_verbatim; + // uint wasted_bits; + //}; + + internal unsafe struct FLAC__Frame + { + internal FLAC__FrameHeader header; + //fixed FLAC__Subframe subframes[FLACDLL.FLAC__MAX_CHANNELS]; + //FLAC__FrameFooter footer; + }; + + [StructLayout(LayoutKind.Explicit), Serializable] + internal struct FLAC__StreamMetadata + { + [FieldOffset(0)] + internal FLAC__MetadataType type; + [FieldOffset(4)] + internal int is_last; + [FieldOffset(8)] + internal uint length; + [FieldOffset(16)] + internal FLAC__StreamMetadata_StreamInfo stream_info; + // [FieldOffset(16)] + // internal FLAC__StreamMetadata_Padding padding; + // [FieldOffset(16)] + // internal FLAC__StreamMetadata_Application application; + // [FieldOffset(16)] + // internal FLAC__StreamMetadata_SeekTable seek_table; + // [FieldOffset(16)] + // internal FLAC__StreamMetadata_VorbisComment vorbis_comment; + // [FieldOffset(16)] + // internal FLAC__StreamMetadata_CueSheet cue_sheet; + // [FieldOffset(16)] + // internal FLAC__StreamMetadata_Picture picture; + // [FieldOffset(16)] + // internal FLAC__StreamMetadata_Unknown unknown; + }; + + [StructLayout(LayoutKind.Sequential), Serializable] + internal unsafe struct FLAC__StreamMetadata_StreamInfo + { + internal int min_blocksize, max_blocksize; + internal int min_framesize, max_framesize; + internal int sample_rate; + internal int channels; + internal int bits_per_sample; + internal long total_samples; + internal fixed byte md5sum[16]; + }; + + internal enum FLAC__ChannelAssignment + { + FLAC__CHANNEL_ASSIGNMENT_INDEPENDENT = 0, + FLAC__CHANNEL_ASSIGNMENT_LEFT_SIDE = 1, + FLAC__CHANNEL_ASSIGNMENT_RIGHT_SIDE = 2, + FLAC__CHANNEL_ASSIGNMENT_MID_SIDE = 3 + }; + + internal enum FLAC__FrameNumberType + { + FLAC__FRAME_NUMBER_TYPE_FRAME_NUMBER, + FLAC__FRAME_NUMBER_TYPE_SAMPLE_NUMBER + }; + + internal enum FLAC__StreamDecoderInitStatus + { + FLAC__STREAM_DECODER_INIT_STATUS_OK = 0, + FLAC__STREAM_DECODER_INIT_STATUS_UNSUPPORTED_CONTAINER, + FLAC__STREAM_DECODER_INIT_STATUS_INVALID_CALLBACKS, + FLAC__STREAM_DECODER_INIT_STATUS_MEMORY_ALLOCATION_ERROR, + FLAC__STREAM_DECODER_INIT_STATUS_ERROR_OPENING_FILE, + FLAC__STREAM_DECODER_INIT_STATUS_ALREADY_INITIALIZED + }; + + internal enum FLAC__StreamDecoderWriteStatus + { + FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE, + FLAC__STREAM_DECODER_WRITE_STATUS_ABORT + }; + + internal enum FLAC__StreamDecoderReadStatus + { + FLAC__STREAM_DECODER_READ_STATUS_CONTINUE, + FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM, + FLAC__STREAM_DECODER_READ_STATUS_ABORT + }; + + internal enum FLAC__StreamDecoderSeekStatus + { + FLAC__STREAM_DECODER_SEEK_STATUS_OK, + FLAC__STREAM_DECODER_SEEK_STATUS_ERROR, + FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED + }; + + internal enum FLAC__StreamDecoderTellStatus + { + FLAC__STREAM_DECODER_TELL_STATUS_OK, + FLAC__STREAM_DECODER_TELL_STATUS_ERROR, + FLAC__STREAM_DECODER_TELL_STATUS_UNSUPPORTED + }; + + internal enum FLAC__StreamDecoderLengthStatus + { + FLAC__STREAM_DECODER_LENGTH_STATUS_OK, + FLAC__STREAM_DECODER_LENGTH_STATUS_ERROR, + FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED + }; + + internal enum FLAC__StreamDecoderErrorStatus + { + FLAC__STREAM_DECODER_ERROR_STATUS_LOST_SYNC, + FLAC__STREAM_DECODER_ERROR_STATUS_BAD_HEADER, + FLAC__STREAM_DECODER_ERROR_STATUS_FRAME_CRC_MISMATCH, + FLAC__STREAM_DECODER_ERROR_STATUS_UNPARSEABLE_STREAM + }; + + internal enum FLAC__StreamDecoderState + { + FLAC__STREAM_DECODER_SEARCH_FOR_METADATA = 0, + FLAC__STREAM_DECODER_READ_METADATA, + FLAC__STREAM_DECODER_SEARCH_FOR_FRAME_SYNC, + FLAC__STREAM_DECODER_READ_FRAME, + FLAC__STREAM_DECODER_END_OF_STREAM, + FLAC__STREAM_DECODER_OGG_ERROR, + FLAC__STREAM_DECODER_SEEK_ERROR, + FLAC__STREAM_DECODER_ABORTED, + FLAC__STREAM_DECODER_MEMORY_ALLOCATION_ERROR, + FLAC__STREAM_DECODER_UNINITIALIZED + }; + + internal enum FLAC__StreamEncoderState : int + { + FLAC__STREAM_ENCODER_OK = 0, + FLAC__STREAM_ENCODER_UNINITIALIZED, + FLAC__STREAM_ENCODER_OGG_ERROR, + FLAC__STREAM_ENCODER_VERIFY_DECODER_ERROR, + FLAC__STREAM_ENCODER_VERIFY_MISMATCH_IN_AUDIO_DATA, + FLAC__STREAM_ENCODER_CLIENT_ERROR, + FLAC__STREAM_ENCODER_IO_ERROR, + FLAC__STREAM_ENCODER_FRAMING_ERROR, + FLAC__STREAM_ENCODER_MEMORY_ALLOCATION_ERROR + }; + + internal enum FLAC__StreamEncoderInitStatus + { + FLAC__STREAM_ENCODER_INIT_STATUS_OK = 0, + FLAC__STREAM_ENCODER_INIT_STATUS_ENCODER_ERROR, + FLAC__STREAM_ENCODER_INIT_STATUS_UNSUPPORTED_CONTAINER, + FLAC__STREAM_ENCODER_INIT_STATUS_INVALID_CALLBACKS, + FLAC__STREAM_ENCODER_INIT_STATUS_INVALID_NUMBER_OF_CHANNELS, + FLAC__STREAM_ENCODER_INIT_STATUS_INVALID_BITS_PER_SAMPLE, + FLAC__STREAM_ENCODER_INIT_STATUS_INVALID_SAMPLE_RATE, + FLAC__STREAM_ENCODER_INIT_STATUS_INVALID_BLOCK_SIZE, + FLAC__STREAM_ENCODER_INIT_STATUS_INVALID_MAX_LPC_ORDER, + FLAC__STREAM_ENCODER_INIT_STATUS_INVALID_QLP_COEFF_PRECISION, + FLAC__STREAM_ENCODER_INIT_STATUS_BLOCK_SIZE_TOO_SMALL_FOR_LPC_ORDER, + FLAC__STREAM_ENCODER_INIT_STATUS_NOT_STREAMABLE, + FLAC__STREAM_ENCODER_INIT_STATUS_INVALID_METADATA, + FLAC__STREAM_ENCODER_INIT_STATUS_ALREADY_INITIALIZED + }; + + internal enum FLAC__MetadataType : int + { + FLAC__METADATA_TYPE_STREAMINFO = 0, + FLAC__METADATA_TYPE_PADDING = 1, + FLAC__METADATA_TYPE_APPLICATION = 2, + FLAC__METADATA_TYPE_SEEKTABLE = 3, + FLAC__METADATA_TYPE_VORBIS_COMMENT = 4, + FLAC__METADATA_TYPE_CUESHEET = 5, + FLAC__METADATA_TYPE_PICTURE = 6, + FLAC__METADATA_TYPE_UNDEFINED = 7, + FLAC__MAX_METADATA_TYPE = 126, + }; + + internal enum FLAC__StreamEncoderWriteStatus + { + FLAC__STREAM_ENCODER_WRITE_STATUS_OK = 0, + FLAC__STREAM_ENCODER_WRITE_STATUS_FATAL_ERROR + }; + + internal enum FLAC__StreamEncoderSeekStatus + { + FLAC__STREAM_ENCODER_SEEK_STATUS_OK, + FLAC__STREAM_ENCODER_SEEK_STATUS_ERROR, + FLAC__STREAM_ENCODER_SEEK_STATUS_UNSUPPORTED + }; + + internal enum FLAC__StreamEncoderTellStatus + { + FLAC__STREAM_ENCODER_TELL_STATUS_OK, + FLAC__STREAM_ENCODER_TELL_STATUS_ERROR, + FLAC__STREAM_ENCODER_TELL_STATUS_UNSUPPORTED + }; +} diff --git a/ThirdParty/Win32/libFLAC_dynamic.dll b/ThirdParty/Win32/libFLAC_dynamic.dll new file mode 100644 index 0000000..71c72df Binary files /dev/null and b/ThirdParty/Win32/libFLAC_dynamic.dll differ diff --git a/ThirdParty/x64/libFLAC_dynamic.dll b/ThirdParty/x64/libFLAC_dynamic.dll new file mode 100644 index 0000000..e859fa8 Binary files /dev/null and b/ThirdParty/x64/libFLAC_dynamic.dll differ