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 { 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; } [DisplayName("Version")] [Description("Library version")] public string Version => FLACDLL.GetVersion; }; [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; } }