#region CPL License
/*
Nuclex Framework
Copyright (C) 2002-2009 Nuclex Development Labs
This library is free software; you can redistribute it and/or
modify it under the terms of the IBM Common Public License as
published by the IBM Corporation; either version 1.0 of the
License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
IBM Common Public License for more details.
You should have received a copy of the IBM Common Public
License along with this library
*/
#endregion
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
namespace Nuclex.Audio.Formats.Flac {
#if ENABLE_PINVOKE_FLAC_DECODER
/// Errors that can occur when a FLAC file is decoded
public enum StreamDecoderError {
///
/// An error in the stream caused the decoder to lose synchronization.
///
LostSync,
/// The decoder encountered a corrupted frame header.
BadHeader,
/// The frame's data did not match the CRC in the footer.
FrameCrcMismatch,
/// The decoder encountered reserved fields in use in the stream.
UnparseableStream
};
/// Adapter that wraps a managed stream into a FLAC stream
public abstract class FlacStream {
/// Initializes a new FLAC stream adapter
protected FlacStream() {
this.ReadCallbackDelegate = new UnsafeNativeMethods.FLAC__StreamDecoderReadCallback(
readCallback
);
this.SeekCallbackDelegate = new UnsafeNativeMethods.FLAC__StreamDecoderSeekCallback(
seekCallback
);
this.TellCallbackDelegate = new UnsafeNativeMethods.FLAC__StreamDecoderTellCallback(
tellCallback
);
this.LengthCallbackDelegate = new UnsafeNativeMethods.FLAC__StreamDecoderLengthCallback(
lengthCallback
);
this.EofCallbackDelegate = new UnsafeNativeMethods.FLAC__StreamDecoderEofCallback(
eofCallback
);
this.WriteCallbackDelegate = new UnsafeNativeMethods.FLAC__StreamDecoderWriteCallback(
writeCallback
);
this.MetadataCallbackDelegate = new UnsafeNativeMethods.FLAC__StreamDecoderMetadataCallback(
metadataCallback
);
this.ErrorCallbackDelegate = new UnsafeNativeMethods.FLAC__StreamDecoderErrorCallback(
errorCallback
);
}
///
/// Reads data from the stream at the current position of the file pointer
///
/// Buffer to store the read bytes in
/// Number of bytes to read at most
///
/// The number of bytes that were actually read and stored in the buffer
///
protected abstract int Read(byte[] buffer, int byteCount);
/// Moves the file pointer within the stream
/// New offset to move the file pointer to
protected abstract void Seek(long offset);
/// Retrieves the current position of the file pointer in the stream
/// The file pointer's current position
protected abstract long Position { get; }
/// Total length of the stream in bytes
protected abstract long Length { get; }
/// Determines whether the end of the stream has been reached
protected abstract bool IsEndOfStreamReached { get; }
/// Writes decoded data into the stream
/// Informations about the decoded frame
/// Buffers containing the decoded data of each channel
protected abstract void Write(int frame, byte[][] buffers);
/// Processes decoded metadata informations
/// Decoded metadata
protected abstract void ProcessMetadata(int metadata);
/// Informs the stream that an error has occured decoding the stream
/// Error that has occured
protected abstract void HandleError(StreamDecoderError error);
///
/// Reads data from the stream at the current position of the file pointer
///
/// FLAC stream decoder issuing the read request
/// Buffer to store the read bytes in
///
/// Number of bytes to read at most and, upon return, contains the number
/// of bytes that were actually read
///
/// Not used
/// Whether the read request was completed successfully
private UnsafeNativeMethods.FLAC__StreamDecoderReadStatus readCallback(
IntPtr decoder,
IntPtr bufferHandle,
ref int byteCount,
IntPtr clientData
) {
try {
byte[] buffer = new byte[byteCount];
byteCount = Read(buffer, byteCount);
Marshal.Copy(buffer, 0, bufferHandle, byteCount);
}
catch(EndOfStreamException) {
return UnsafeNativeMethods.FLAC__StreamDecoderReadStatus.
FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM;
}
catch(Exception) {
return UnsafeNativeMethods.FLAC__StreamDecoderReadStatus.
FLAC__STREAM_DECODER_READ_STATUS_ABORT;
}
return UnsafeNativeMethods.FLAC__StreamDecoderReadStatus.
FLAC__STREAM_DECODER_READ_STATUS_CONTINUE;
}
/// Moves the file pointer within the stream
/// FLAC stream decoder issuing the seek request
/// New offset to move the file pointer to
/// Not used
/// Whether the file pointer was successfully moved
private UnsafeNativeMethods.FLAC__StreamDecoderSeekStatus seekCallback(
IntPtr decoder,
UInt64 absolute_byte_offset,
IntPtr client_data
) {
try {
Seek((long)absolute_byte_offset);
}
catch(NotSupportedException) {
return UnsafeNativeMethods.FLAC__StreamDecoderSeekStatus.
FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED;
}
catch(Exception) {
return UnsafeNativeMethods.FLAC__StreamDecoderSeekStatus.
FLAC__STREAM_DECODER_SEEK_STATUS_ERROR;
}
return UnsafeNativeMethods.FLAC__StreamDecoderSeekStatus.
FLAC__STREAM_DECODER_SEEK_STATUS_OK;
}
/// Returns the current position of the file pointer within the stream
/// FLAC stream decoder issuing the seek request
///
/// Output parameter that will receive the current file pointer position
///
/// Not used
/// Whether the file pointer position was successfully determined
private UnsafeNativeMethods.FLAC__StreamDecoderTellStatus tellCallback(
IntPtr decoder,
ref UInt64 absolute_byte_offset,
IntPtr client_data
) {
try {
absolute_byte_offset = (UInt64)Position;
}
catch(NotSupportedException) {
return UnsafeNativeMethods.FLAC__StreamDecoderTellStatus.
FLAC__STREAM_DECODER_TELL_STATUS_UNSUPPORTED;
}
catch(Exception) {
return UnsafeNativeMethods.FLAC__StreamDecoderTellStatus.
FLAC__STREAM_DECODER_TELL_STATUS_ERROR;
}
return UnsafeNativeMethods.FLAC__StreamDecoderTellStatus.
FLAC__STREAM_DECODER_TELL_STATUS_OK;
}
/// Returns the length of the stream
/// FLAC stream decoder issuing the length request
///
/// Output parameter that will receive the stream's length
///
/// Not used
/// Whether the length of the stream was successfully determined
private UnsafeNativeMethods.FLAC__StreamDecoderLengthStatus lengthCallback(
IntPtr decoder,
ref UInt64 stream_length,
IntPtr client_data
) {
try {
stream_length = (UInt64)Length;
}
catch(NotSupportedException) {
return UnsafeNativeMethods.FLAC__StreamDecoderLengthStatus.
FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED;
}
catch(Exception) {
return UnsafeNativeMethods.FLAC__StreamDecoderLengthStatus.
FLAC__STREAM_DECODER_LENGTH_STATUS_ERROR;
}
return UnsafeNativeMethods.FLAC__StreamDecoderLengthStatus.
FLAC__STREAM_DECODER_LENGTH_STATUS_OK;
}
/// Determines whether the end of the stream has been reached
/// FLAC stream decoder issuing the eof determination request
/// Not used
/// True when the end of the stream was reached, false otherwise
private int eofCallback(
IntPtr decoder,
IntPtr client_data
) {
try {
return IsEndOfStreamReached ? 1 : 0;
}
catch(Exception exception) {
Trace.TraceError(
"FLAC stream adapter received an exception from IsEndOfStreamReached:\n{0}",
exception
);
}
return 1;
}
/// Writes data into the stream
/// FLAC stream decoder issuing the write request
/// A description of the decoded frame
/// Pointers to the decoded data of the individual channels
/// Not used
///
/// Whether the decoded data has been written into the stream successfully
///
private UnsafeNativeMethods.FLAC__StreamDecoderWriteStatus writeCallback(
IntPtr decoder,
IntPtr frameHandle,
IntPtr[] buffer,
IntPtr client_data
) {
try {
Frame frame = FrameMarshalHelper.MarshalFrame(frameHandle);
Write(0, null); // TODO
}
catch(Exception) {
return UnsafeNativeMethods.FLAC__StreamDecoderWriteStatus.
FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
}
return UnsafeNativeMethods.FLAC__StreamDecoderWriteStatus.
FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
}
/// Processes a decoded metadata block
///
/// FLAC stream decoder issuing the metadata processing request
///
/// Pointer to the decoded metadata block
/// Not used
private void metadataCallback(
IntPtr decoder,
IntPtr metadata,
IntPtr client_data
) {
try {
ProcessMetadata(123); // TODO
}
catch(Exception exception) {
Trace.TraceError(
"FLAC stream adapter received an exception from ProcessMetadata():\n{0}",
exception
);
}
}
/// Responds to an error that has occured during decoding
/// FLAC stream decoder issuing the error notification
/// Error that has been encountered by the decoder
/// Not used
private void errorCallback(
IntPtr decoder,
UnsafeNativeMethods.FLAC__StreamDecoderErrorStatus status,
IntPtr client_data
) {
try {
HandleError(streamDecoderErrorFromFlacStreamDecoderErrorStatus(status));
}
catch(Exception exception) {
Trace.TraceError(
"FLAC stream adapter received an exception from HandleError():\n{0}",
exception
);
}
}
///
/// Converts a FLAC stream decoder error status into a value from the
/// StreamDecoderErrors enumeration
///
///
/// FLAC stream decoder error status that will be converted
///
/// The equivalent entry in the StreamDecoderErrors enumeration
private static StreamDecoderError streamDecoderErrorFromFlacStreamDecoderErrorStatus(
UnsafeNativeMethods.FLAC__StreamDecoderErrorStatus errorStatus
) {
switch(errorStatus) {
case UnsafeNativeMethods.FLAC__StreamDecoderErrorStatus.
FLAC__STREAM_DECODER_ERROR_STATUS_BAD_HEADER: {
return StreamDecoderError.BadHeader;
}
case UnsafeNativeMethods.FLAC__StreamDecoderErrorStatus.
FLAC__STREAM_DECODER_ERROR_STATUS_FRAME_CRC_MISMATCH: {
return StreamDecoderError.FrameCrcMismatch;
}
case UnsafeNativeMethods.FLAC__StreamDecoderErrorStatus.
FLAC__STREAM_DECODER_ERROR_STATUS_LOST_SYNC: {
return StreamDecoderError.LostSync;
}
case UnsafeNativeMethods.FLAC__StreamDecoderErrorStatus.
FLAC__STREAM_DECODER_ERROR_STATUS_UNPARSEABLE_STREAM: {
return StreamDecoderError.UnparseableStream;
}
default: {
throw new ArgumentException("Invalid error status");
}
}
}
// These delegates are required!
//
// When a delegate is passed to native code, the .NET runtime generates a small thunk of
// executable code tailored for the delegate instance. This thunk performs the call back
// into the method the delegate is pointing to. It also counts as a reference for the GC
// not to collect the object whose method the delegate points to.
//
// Now if native code stores the adress of this generated thunk for later use, the GC has
// no way of knowing about it and will see the delegate as a candidate for garbage
// collection. Should a collection happen and the native code tries to call the function
// pointer, the result is most likely an access violation.
//
// Thus, the FlacStream keeps its delegates alive as long as it is alive itself by
// referencing them here. The FlacStream then is prevented from becoming a candidate for
// garbage collection by a GCHandle that's stored as the 'client_data' pointer by
// unmanaged code.
//
// Yep, P/Invoke isn't always as clean and straightforward as you might think :)
/// Delegate for the readCallback() method
internal UnsafeNativeMethods.FLAC__StreamDecoderReadCallback ReadCallbackDelegate;
/// Delegate for the seekCallback() method
internal UnsafeNativeMethods.FLAC__StreamDecoderSeekCallback SeekCallbackDelegate;
/// Delegate for the tellCallback() method
internal UnsafeNativeMethods.FLAC__StreamDecoderTellCallback TellCallbackDelegate;
/// Delegate for the lengthCallback() method
internal UnsafeNativeMethods.FLAC__StreamDecoderLengthCallback LengthCallbackDelegate;
/// Delegate for the eofCallback() method
internal UnsafeNativeMethods.FLAC__StreamDecoderEofCallback EofCallbackDelegate;
/// Delegate for the writeCallback() method
internal UnsafeNativeMethods.FLAC__StreamDecoderWriteCallback WriteCallbackDelegate;
/// Delegate for the metadataCallback() method
internal UnsafeNativeMethods.FLAC__StreamDecoderMetadataCallback MetadataCallbackDelegate;
/// Delegate for the errorCallback() method
internal UnsafeNativeMethods.FLAC__StreamDecoderErrorCallback ErrorCallbackDelegate;
}
#endif // ENABLE_PINVOKE_FLAC_DECODER
} // namespace Nuclex.Audio.Formats.Flac