using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace Nuclex.Audio.Formats.Flac {
#if ENABLE_PINVOKE_FLAC_DECODER
/// FLAC stream decoder
public class StreamDecoder : IDisposable {
/// Initializes a new FLAC stream decoder
public StreamDecoder() {
this.decoderHandle = UnsafeNativeMethods.FLAC__stream_decoder_new();
if(this.decoderHandle == IntPtr.Zero) {
throw new OutOfMemoryException("Could not allocate memory for FLAC stream decoder");
}
}
/// Finalizer that will be called by the GC.
~StreamDecoder() {
Dispose(false); // false == not being called by user code
}
/// Initializes the FLAC stream decoder
/// Stream that will be decoded
public void InitStream(FlacStream stream) {
// Explanation for the GCHandle stuff: The only reference to the FlacStream instance
// will very likely remain in unmanaged code. Since the garbage collector cannot
// see this reference, given no other references to it exist, the FlacStream instance
// would then become a candidate for garbage collection. The GCHandle avoids this.
// It will ensure the FlacStream instance is kept alive until it the FLAC stream decoder
// is destroyed (at which point we are responsible for invoking its Free() method)
GCHandle streamHandle = GCHandle.Alloc(stream, GCHandleType.Normal);
try {
int result = UnsafeNativeMethods.FLAC__stream_decoder_init_stream(
this.decoderHandle,
stream.ReadCallbackDelegate,
stream.SeekCallbackDelegate,
stream.TellCallbackDelegate,
stream.LengthCallbackDelegate,
stream.EofCallbackDelegate,
stream.WriteCallbackDelegate,
stream.MetadataCallbackDelegate,
stream.ErrorCallbackDelegate,
GCHandle.ToIntPtr(streamHandle)
);
int goodResult = (int)UnsafeNativeMethods.FLAC__StreamDecoderInitStatus.
FLAC__STREAM_DECODER_INIT_STATUS_OK;
if(result != goodResult) {
// TODO: Transform the error code into an appropriate exception
throw new Exception("FLAC__stream_decoder_init_stream() has failed");
}
}
catch(Exception) {
streamHandle.Free();
throw;
}
}
/// Decodes data until the end of the stream has been reached
public void ProcessUntilEndOfStream() {
int result = UnsafeNativeMethods.FLAC__stream_decoder_process_until_end_of_stream(
this.decoderHandle
);
if(result == 0) {
throw new Exception("Stream processing has been aborted because of an error");
}
}
/// The "MD5 signature checking" flag
public bool EnableMd5Checking {
get {
int result = UnsafeNativeMethods.FLAC__stream_decoder_get_md5_checking(
this.decoderHandle
);
return (result != 0);
}
set {
int result = UnsafeNativeMethods.FLAC__stream_decoder_set_md5_checking(
this.decoderHandle, value ? 1 : 0
);
if(result == 0) {
throw new InvalidOperationException("FLAC stream decoder is already initialized");
}
}
}
/// Immediately releases all resources owned by the object.
public void Dispose() {
Dispose(true); // true == called by user code
// We are finalized already, tell the GC that it doesn't
// need to call the finalizer anymore
GC.SuppressFinalize(this);
}
/// Immediately releases all resources owned by the object.
///
/// If true, the object is being disposed explicitely and can still access
/// the other managed objects it is referencing.
///
protected virtual void Dispose(bool calledByUser) {
if(calledByUser) {
// Perform finalization of managed objects here
}
if(this.decoderHandle != IntPtr.Zero) {
UnsafeNativeMethods.FLAC__stream_decoder_delete(this.decoderHandle);
}
}
///
/// Handle of the native FLAC stream decoder being wrapped by the instance
///
private IntPtr decoderHandle;
}
#endif // ENABLE_PINVOKE_FLAC_DECODER
} // namespace Nuclex.Audio.Formats.Flac