/// <summary>
        /// Decodes an audio stream through FFmpeg from an encoded file stream.
        /// Accepts an optional file name hint to help FFmpeg determine the format of
        /// the encoded data.
        /// </summary>
        /// <param name="stream">the stream to decode</param>
        /// <param name="fileName">optional file name hint for FFmpeg</param>
        public FFmpegSourceStream(Stream stream, string fileName)
        {
            sourceStream = stream;
            reader       = new FFmpegReader(stream, FFmpeg.Type.Audio, fileName);

            if (reader.AudioOutputConfig.length == long.MinValue)
            {
                /*
                 * length == FFmpeg AV_NOPTS_VALUE
                 *
                 * This means that for the opened file/format, there is no length/PTS data
                 * available, which also makes seeking more or less impossible.
                 *
                 * As a workaround, an index could be created to map the frames to the file
                 * position, and then seek by file position. The index could be created by
                 * linearly reading through the file (decoding not necessary), and creating
                 * a mapping of AVPacket.pos to the frame time.
                 */
                throw new FileNotSeekableException();
            }

            properties = new AudioProperties(
                reader.AudioOutputConfig.format.channels,
                reader.AudioOutputConfig.format.sample_rate,
                reader.AudioOutputConfig.format.sample_size * 8,
                reader.AudioOutputConfig.format.sample_size == 4 ? AudioFormat.IEEE : AudioFormat.LPCM);

            readerPosition = 0;
            sourceBuffer   = new byte[reader.AudioOutputConfig.frame_size *
                                      reader.AudioOutputConfig.format.channels *
                                      reader.AudioOutputConfig.format.sample_size];
            sourceBufferPosition = 0;
            sourceBufferLength   = -1; // -1 means buffer empty, >= 0 means valid buffer data

            // determine first PTS to handle cases where it is > 0
            try {
                Position = 0;
            }
            catch (InvalidOperationException) {
                readerFirstPTS = readerPosition;
                readerPosition = 0;
                Console.WriteLine("first PTS = " + readerFirstPTS);
            }

            seekIndexCreated = false;
        }
        /// <summary>
        /// Creates a Wave format proxy file in the same directory and with the same name as the specified file,
        /// if no storage directory is specified (i.e. if it is null). If a storage directory is specified, the proxy
        /// file will be stored in the specified directory with a hashed file name to avoid name collisions and
        /// file overwrites. The story directory option is convenient for the usage of temporary or working directories.
        /// </summary>
        /// <param name="fileInfo">the file for which a proxy file should be created</param>
        /// <param name="storageDirectory">optional directory where the proxy file will be stored, can be null</param>
        /// <returns>the FileInfo of the proxy file</returns>
        public static FileInfo CreateWaveProxy(FileInfo fileInfo, DirectoryInfo storageDirectory)
        {
            FileInfo outputFileInfo;

            if (storageDirectory == null)
            {
                // Without a storage directory, store the proxy file beside the original file
                outputFileInfo = new FileInfo(fileInfo.FullName + ".ffproxy.wav");
            }
            else
            {
                // With a storage directory specified, store the proxy file with a hashed name
                // (to avoid name collision / overwrites) in the target directory (e.g. a temp or working directory)
                using (var sha256 = SHA256.Create()) {
                    byte[] hash       = sha256.ComputeHash(Encoding.Unicode.GetBytes(fileInfo.FullName));
                    string hashString = BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
                    outputFileInfo = new FileInfo(Path.Combine(storageDirectory.FullName, hashString + ".ffproxy.wav"));
                }
            }


            if (outputFileInfo.Exists)
            {
                Console.WriteLine("Proxy already existing, using " + outputFileInfo.Name);
                return(outputFileInfo);
            }

            var reader = new FFmpegReader(fileInfo, FFmpeg.Type.Audio);

            var writer = new MemoryWriterStream(new AudioProperties(
                                                    reader.AudioOutputConfig.format.channels,
                                                    reader.AudioOutputConfig.format.sample_rate,
                                                    reader.AudioOutputConfig.format.sample_size * 8,
                                                    reader.AudioOutputConfig.format.sample_size == 4 ? AudioFormat.IEEE : AudioFormat.LPCM));

            int output_buffer_size = reader.AudioOutputConfig.frame_size * writer.SampleBlockSize;

            byte[] output_buffer = new byte[output_buffer_size];

            int  samplesRead;
            long timestamp;

            FFmpeg.Type type;

            // sequentially read samples from decoder and write it to wav file
            while ((samplesRead = reader.ReadFrame(out timestamp, output_buffer, output_buffer_size, out type)) > 0)
            {
                int bytesRead = samplesRead * writer.SampleBlockSize;
                writer.Write(output_buffer, 0, bytesRead);
            }

            reader.Dispose();

            writer.Position = 0;

            AudioStreamFactory.WriteToFile(writer, outputFileInfo.FullName);

            writer.Close();

            return(outputFileInfo);
        }