namespace OpenVIII.AV { using FFmpeg.AutoGen; using Microsoft.Xna.Framework.Audio; using Microsoft.Xna.Framework.Graphics; using NAudio.Wave; using NAudio.Wave.SampleProviders; using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; /// /// Ffcc is a front end for processing Audio and Video using ffmpeg.autogen /// /// /// Code bits mostly converted to c# from c++ It uses bits of code from FFmpeg examples, Aforge, /// FFmpeg.autogen, stackoverflow /// public abstract partial class Ffcc : IDisposable { /// /// Opens filename and init class. /// protected static T Load(string filename, AVMediaType mediatype = AVMediaType.AVMEDIA_TYPE_AUDIO, FfccMode mode = FfccMode.STATE_MACH, int loopstart = -1) where T : Ffcc, new() { T r = new T(); r.Init(filename, mediatype, mode, loopstart); if (mode == FfccMode.PROCESS_ALL) r.Dispose(false); return r; } #region Fields /// /// If you have sound skipping increase this number and it'll go away. might decrease sync or /// increase memory load The goal is to keep the dynamicsoundeffectinterface fed. If it plays /// the audio before you give it more, then you get sound skips. /// /// The goal buffer count. /// /// Will want to be as low as possible without sound skipping. 91.875 is 1 second of audio at /// 44100 hz @ 15 fps; 99.9001 is 1 second of audio at 48000 hz @ 15 fps; /// private const int GoalBufferCount = 100; /// /// NextAsync sleeps when filling buffer. If set too high buffer will empty before filling it again. /// private const int NextAsyncSleep = 10; /// /// on exception this is turned to true and will force fallback to naudio when monogame isn't working. /// private static bool useNaudio = false; private object DynamicSoundEffectLock = new object(); private readonly unsafe AVDictionary* _dict; private unsafe AVIOContext* _avio_ctx; private unsafe byte* _avio_ctx_buffer; private int _avio_ctx_buffer_size; private byte[] _convertedData; //private byte* _convertedData; private MemoryStream _decodedMemoryStream; private bool _frameSkip = true; //private IntPtr _intPtr; private int _loopstart; /// /// buffered wave provider used to handle audio samples /// /// private CancellationToken cancellationToken; #if _WINDOWS private BufferedWaveProvider bufferedWaveProvider; /// /// Wave out for naudio only works in windows. /// /// private DirectSoundOut nAudioOut; /// /// Directsound requires VolumeSampleProvider to change volume. /// private VolumeSampleProvider volumeSampleProvider; /// /// Required by naudio to pan the sound. /// private PanningSampleProvider panningSampleProvider; #endif private avio_alloc_context_read_packet rf; private CancellationTokenSource sourceToken; private bool stopped = false; private Task task; private FfccVaribleGroup _decoder = new FfccVaribleGroup(); private DynamicSoundEffectInstance _dynamicSound; private int _loopend; private int _looplength; #endregion Fields #region Destructors ~Ffcc() { Dispose(false); } #endregion Destructors #region Enums //public FileStream DecodeFileStream { get => _decodeFileStream; set => _decodeFileStream = value; } public enum FfccMode { /// /// Processes entire file at once and does something with output /// PROCESS_ALL, /// /// State machine, functions in this call update to update current state. And update /// decides what to do next. /// STATE_MACH, /// /// Not Init some error happened that prevented the class from working /// NOTINIT } public enum FfccState { OPEN, /// /// Waiting for request for next frame /// WAITING, /// /// Readall the data /// READALL, /// /// Done reading file nothing more can be done /// DONE, /// /// Don't change state just pass ret value. /// NULL, /// /// Get packet of data containing frames /// READONE, /// /// Missing DLL required to function /// NODLL, /// /// Gets stream and Codec /// GETCODEC, /// /// Prepares Scaler for Video stream /// PREPARE_SWS, /// /// Start Reading /// READ, /// /// Prepares Resampler for Audio stream /// PREPARE_SWR } #endregion Enums #region Properties /// /// Are you ahead of target frame /// /// true if ahead public bool Ahead { get { if (Decoder.StreamIndex != -1 && Mode == FfccMode.STATE_MACH) { if (MediaType == AVMediaType.AVMEDIA_TYPE_AUDIO) { if (DynamicSound != null) { return DynamicSound.PendingBufferCount > GoalBufferCount; } #if _WINDOWS else if (useNaudio) { return bufferedWaveProvider.BufferedDuration.TotalSeconds > bufferedWaveProvider.BufferDuration.TotalSeconds - 1; } #endif } else if (timer.IsRunning) { return CurrentFrameNum > ExpectedFrame; } } return true; } } /// /// Are you behind target frame /// /// true if behind public bool Behind => !Ahead && !Current; /// /// Are you on target frame /// /// true if correct frame public unsafe bool Current { get { if (Decoder.StreamIndex != -1 && Mode == FfccMode.STATE_MACH) { if (MediaType == AVMediaType.AVMEDIA_TYPE_AUDIO) { if (DynamicSound != null) { return DynamicSound.PendingBufferCount == GoalBufferCount; } #if _WINDOWS else if (useNaudio) { return bufferedWaveProvider.BufferedDuration.TotalSeconds == bufferedWaveProvider.BufferDuration.TotalSeconds - 1; } #endif else { die($"{Decoder.CodecContext->sample_rate} is currently unsupported"); } } else if (timer.IsRunning) { return CurrentFrameNum == ExpectedFrame; } } return false; } } /// /// Path and filename of file. /// public string DecodedFileName { get; private set; } /// /// Dynamic Sound Effect Interface for class allows control out of class. Mode must be in STATE_MACH /// public DynamicSoundEffectInstance DynamicSound { get => _dynamicSound; private set { lock (DynamicSoundEffectLock) _dynamicSound = value; } } /// /// True if file is open. /// public bool FileOpened { get; private set; } /// /// returns Frames per second or if that is 0. it will return the Time_Base ratio. This is /// the fundamental unit of time (in seconds) in terms of which frame timestamps are /// represented. In many cases the audio files time base is the same as the sample rate. /// example 1/44100. video files audio stream might be 1/100 or 1/1000. these can make for /// large durrations. /// public unsafe double FPS { get { double r = FPSvideo; if (r != 0) { return r; } else { if (MediaType == AVMediaType.AVMEDIA_TYPE_AUDIO && Decoder.CodecContext != null && Decoder.CodecContext->framerate.den != 0) { return Decoder.CodecContext->framerate.num / (double)Decoder.CodecContext->framerate.den; } else if (Decoder.Stream != null && Decoder.Stream->time_base.den != 0) { return Decoder.Stream->time_base.num / (double)Decoder.Stream->time_base.den; // returns the time_base } } return 0; } } /// /// When getting video frames if behind it goes to next frame. disabled for debugging purposes. /// public bool FrameSkip { get => MediaType == AVMediaType.AVMEDIA_TYPE_VIDEO ? _frameSkip : false; set => _frameSkip = value; } /// /// Is the class disposed of. If true calling Dispose() does nothing. /// public bool IsDisposed { get; private set; } = false; /// /// Sample count that loop starts from. /// public int LOOPSTART { get => _loopstart; set => _loopstart = value; } /// /// Current media type being processed. /// public AVMediaType MediaType { get; private set; } /// /// Metadata container for tags. /// public Dictionary Metadata { get; private set; } = new Dictionary(); /// /// SoundEffect for class allows control out of class. Mode must be in PROCESS_ALL /// public SoundEffect SoundEffect { get; private set; } /// /// SoundEffectInterface for class. allows for more control than just playing the above soundeffect. /// public SoundEffectInstance SoundEffectInstance { get; private set; } /// /// Stopwatch tracks the time audio has played so video can sync or loops can be looped. /// public Stopwatch timer { get; } = new Stopwatch(); /// /// if there is no stream it returns false. only checked when trying to process audio /// private bool AudioEnabled => Decoder.StreamIndex >= 0; //private byte* ConvertedData { get => _convertedData; set => _convertedData = value; } private byte[] ConvertedData { get => _convertedData; set => _convertedData = value; } /// /// Current frame number /// /// Current frame number or -1 on error public unsafe int CurrentFrameNum => Decoder.CodecContext != null ? Decoder.CodecContext->frame_number : -1; /// /// MemoryStream of Audio after decoding and resamping to compatable format. /// private MemoryStream DecodedMemoryStream { get => _decodedMemoryStream; set => _decodedMemoryStream = value; } /// /// Holder of varibles for Decoder /// protected FfccVaribleGroup Decoder { get => _decoder; set => _decoder = value; } /// /// Based on timer and FPS determines what the current frame is. /// /// Expected Frame Number private int ExpectedFrame => timer.IsRunning ? (int)Math.Round(timer.ElapsedMilliseconds * (FPS / 1000)) : 0; /// /// FPS of the video stream. /// private unsafe double FPSvideo { get { Return = ffmpeg.av_find_best_stream(Decoder.Format, AVMediaType.AVMEDIA_TYPE_VIDEO, -1, -1, null, 0); if (Return < 0) { return 0; } else if (Decoder.Format->streams[Return]->codec->framerate.den > 0) { return (double)Decoder.Format->streams[Return]->codec->framerate.num / Decoder.Format->streams[Return]->codec->framerate.den; } return 0; } } /// /// Mode that ffcc is running in. /// private FfccMode Mode { get; set; } /// /// Resample Context /// private unsafe SwrContext* ResampleContext { get; set; } /// /// Frame used by resampler /// private unsafe AVFrame* ResampleFrame { get; set; } /// /// Most ffmpeg functions return an integer. If the value is less than 0 it is an error /// usually. Sometimes data is passed and then it will be greater than 0. /// private int Return { get; set; } /// /// SWS Context /// private unsafe SwsContext* ScalerContext { get; set; } /// /// State ffcc is in. /// private FfccState State { get; set; } #endregion Properties #region Methods /// /// Dispose of all leaky varibles. /// public void Dispose() => Dispose(true); /// /// Attempts to get 1 frame of Video, or refill Audio buffer. /// /// Returns -1 if missing stream or returns AVERROR or returns 0 if no problem. public int Next() { //if stream doesn't exist or stream is done, end if (Decoder.StreamIndex == -1 || State == FfccState.DONE) { return -1; } // read next frame(s) else { return Update(FfccState.READONE); } } /// /// Pause or Resume timer. WIP /// public void Pause() { if (Decoder.StreamIndex > -1) { if (Mode == FfccMode.STATE_MACH) { if (!timer.IsRunning) { timer.Stop(); } else { timer.Start(); } } } } /// /// Start playing Sound or Start FPS timer for Video /// /// /// Volume, ranging from 0.0 (silence) to 1.0 (full volume). Volume during playback is scaled /// by SoundEffect.MasterVolume. /// /// /// Pitch adjustment, ranging from -1.0 (down an octave) to 0.0 (no change) to 1.0 (up an octave). /// /// /// Panning, ranging from -1.0 (left speaker) to 0.0 (centered), 1.0 (right speaker). /// public void Play(float volume = 1.0f, float pitch = 0.0f, float pan = 0.0f) // there are some videos without sound meh. { if (Decoder != null && Decoder.StreamIndex > -1) { if (!timer.IsRunning && Mode == FfccMode.STATE_MACH && MediaType == AVMediaType.AVMEDIA_TYPE_VIDEO) { timer.Start(); } if (!useNaudio) { if (DynamicSound != null && !DynamicSound.IsDisposed && AudioEnabled) { lock (DynamicSoundEffectLock) { DynamicSound.Volume = volume; DynamicSound.Pitch = pitch; DynamicSound.Pan = pan; DynamicSound.Play(); } } if (SoundEffect != null && !SoundEffect.IsDisposed && AudioEnabled) { SoundEffectInstance.Volume = volume; SoundEffectInstance.Pitch = pitch; SoundEffectInstance.Pan = pan; try { SoundEffectInstance.Play(); } catch (Exception e) { if (e.GetType().Name == "SharpDXException") { Mode = FfccMode.NOTINIT; State = FfccState.NODLL; SoundEffectInstance = null; SoundEffect = null; // if it gets here I can't extract the sound from the SoundEffect but // I can turn on nAudio and next sound will work useNaudio = true; } else e.Rethrow(); } } } #if _WINDOWS else if (bufferedWaveProvider != null && nAudioOut != null) { volumeSampleProvider.Volume = volume; if (panningSampleProvider != null) // panning requires mono sound so it's null if it wasn't 1 channel. panningSampleProvider.Pan = pan; // i'll leave out pitch unless it's needed. there is a provider for it but sounds // like it might do more than we need. nAudioOut.Play(); } #endif } } /// /// Same as Play but with a task. Thread is terminated on Stop() or Dispose(). /// public void PlayInTask(float volume = 1.0f, float pitch = 0.0f, float pan = 0.0f) { if (MediaType == AVMediaType.AVMEDIA_TYPE_AUDIO) { if (sourceToken == null) sourceToken = new CancellationTokenSource(); if (cancellationToken == null) cancellationToken = sourceToken.Token; Play(volume, pitch, pan); task = new Task(NextinTask); task.Start(); } else Play(volume, pitch, pan); } /// /// Stop playing Sound or Stop the FPS timer for Video , and Dispose of Varibles /// public async void Stop() { if (stopped) return; if (timer.IsRunning) { timer.Stop(); timer.Reset(); } if (!useNaudio) { if (DynamicSound != null && !DynamicSound.IsDisposed) { lock (DynamicSoundEffectLock) { if (AudioEnabled) { try { DynamicSound?.Stop(); } catch (NullReferenceException) { } } DynamicSound.Dispose(); } DynamicSound = null; } if (SoundEffectInstance != null && !SoundEffectInstance.IsDisposed) { if (AudioEnabled) { SoundEffectInstance.Stop(); } SoundEffectInstance.Dispose(); } if (SoundEffect != null && !SoundEffect.IsDisposed) { SoundEffect.Dispose(); } } #if _WINDOWS else if (bufferedWaveProvider != null && nAudioOut != null) { nAudioOut.Stop(); try { nAudioOut.Dispose(); bufferedWaveProvider.ClearBuffer(); } catch (InvalidOperationException) { // naudio can't be disposed of if not in original thread. } } #endif if (task != null) { sourceToken.Cancel(); await task; } } /// /// Converts Frame to Texture with correct colorspace /// /// Texture2D public unsafe Texture2D Texture2D() { lock (Decoder) { Texture2D frameTex = new Texture2D(Memory.spriteBatch.GraphicsDevice, Decoder.CodecContext->width, Decoder.CodecContext->height, false, SurfaceFormat.Color); const int bpp = 4; byte[] texBuffer = new byte[Decoder.CodecContext->width * Decoder.CodecContext->height * bpp]; fixed (byte* ptr = &texBuffer[0]) { byte*[] srcData = { ptr, null, null, null }; int[] srcLinesize = { Decoder.CodecContext->width * bpp, 0, 0, 0 }; // convert video frame to the RGB data ffmpeg.sws_scale(ScalerContext, Decoder.Frame->data, Decoder.Frame->linesize, 0, Decoder.CodecContext->height, srcData, srcLinesize); } frameTex.SetData(texBuffer); return frameTex; } } protected virtual void Dispose(bool disposing) { if (Decoder != null) { lock (Decoder) { dis(); } } else dis(); void dis() { if (disposing) { Stop(); } if (!IsDisposed) { if (task != null) { if (task.IsCompleted) task.Dispose(); else Memory.LeftOverTask.Add(task); task = null; } State = FfccState.DONE; Mode = FfccMode.NOTINIT; // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below. // TODO: set large fields to null. if (DecodedMemoryStream != null) { DecodedMemoryStream.Dispose(); } if (ConvertedData != null) { //Marshal.FreeHGlobal((IntPtr)ConvertedData); } //if (_intPtr != null) //{ // Marshal.FreeHGlobal(_intPtr); //} unsafe { ffmpeg.sws_freeContext(ScalerContext); if (ResampleContext != null) { ffmpeg.swr_close(ResampleContext); SwrContext* pResampleContext = ResampleContext; ffmpeg.swr_free(&pResampleContext); } ffmpeg.av_frame_unref(ResampleFrame); ffmpeg.av_free(ResampleFrame); if (_avio_ctx != null) { //ffmpeg.avio_close(avio_ctx); //CTD ffmpeg.av_free(_avio_ctx); } } //if (avio_ctx_buffer != null) // ffmpeg.av_freep(avio_ctx_buffer); //throws exception // set to true to prevent multiple disposings IsDisposed = true; //GC.Collect(); // donno if this really does much. was trying to make sure the memory i'm watching is what is really there. } else { if (nAudioOut != null && useNaudio) { nAudioOut.Dispose(); nAudioOut = null; bufferedWaveProvider.ClearBuffer(); } if (sourceToken != null) { sourceToken.Dispose(); sourceToken = null; } if (_decoder != null) { _decoder.Dispose(); _decoder = null; } } } } /// /// Flush the Decoder context and packet /// /// Decoder Codec Context /// Decoder Packet /// 0 on success, less than 0 on error private static unsafe int DecodeFlush(ref AVCodecContext* avctx, ref AVPacket avpkt) { avpkt.data = null; avpkt.size = 0; fixed (AVPacket* tmpPacket = &avpkt) { return ffmpeg.avcodec_send_packet(avctx, tmpPacket); } } /// /// throw new exception /// /// string of message private static void die(string v) => throw new Exception(v.Trim('\0')); /// /// For reading data from a memory pointer as if it's a file. /// /// incoming data /// outgoing data /// outgoing buffer size /// Total bytes read, or EOF private static unsafe int Read_packet(void* opaque, byte* buf, int buf_size) { BufferData* bd = (BufferData*)opaque; return bd->Read(buf, buf_size); } /// /// Converts FFMPEG error codes into a string. /// private unsafe string AvError(int ret) { ulong errbuff_size = 256; byte[] errbuff = new byte[errbuff_size]; fixed (byte* ptr = &errbuff[0]) { ffmpeg.av_strerror(ret, ptr, errbuff_size); } return System.Text.Encoding.UTF8.GetString(errbuff).Trim('\0'); } /// /// Throws exception if Ret is less than 0 /// private int CheckReturn() { switch (Return) { case ffmpeg.AVERROR_OUTPUT_CHANGED: die($"The swr_context output ch_layout, sample_rate, sample_fmt must match outframe! {Return} - {AvError(Return)}"); break; case ffmpeg.AVERROR_INPUT_CHANGED: die($"The swr_context input ch_layout, sample_rate, sample_fmt must match inframe! {Return} - {AvError(Return)}"); break; default: if (Return < 0) { die($"{Return} - {AvError(Return)}"); } break; } return Return; } /// /// Decode the next frame. /// /// Current Decoded Frame /// false if EOF, or true if grabbed frame private unsafe bool Decode(out AVFrame frame) { do { //need this extra receive frame for when decoding audio with >1 frame per packet Return = ffmpeg.avcodec_receive_frame(Decoder.CodecContext, Decoder.Frame); if (Return == ffmpeg.AVERROR(ffmpeg.EAGAIN)) { do { do { //make sure packet is unref before getting a new one. ffmpeg.av_packet_unref(Decoder.Packet); Return = ffmpeg.av_read_frame(Decoder.Format, Decoder.Packet); if (Return == ffmpeg.AVERROR_EOF) { goto EOF; } else { CheckReturn(); } } //check for correct stream. while (Decoder.Packet->stream_index != Decoder.StreamIndex); Return = ffmpeg.avcodec_send_packet(Decoder.CodecContext, Decoder.Packet); ffmpeg.av_packet_unref(Decoder.Packet); CheckReturn(); Return = ffmpeg.avcodec_receive_frame(Decoder.CodecContext, Decoder.Frame); } while (Return == ffmpeg.AVERROR(ffmpeg.EAGAIN)); CheckReturn(); } else if (Return == ffmpeg.AVERROR_EOF) { goto EOF; } else { CheckReturn(); } } //check for frameskip, if enabled check if behind. while (FrameSkip && Behind); frame = *Decoder.Frame; return true; //end of file, check for loop and end. //TODO: add LOOPEND and LOOPLENGTH support https://github.com/FFT-Hackers/vgmstream/commit/b332e5cf5cc9e3469cbbab226836083d8377ce61 EOF: Loop(); frame = *Decoder.Frame; return false; } /// /// reads the tags from metadata /// /// metadata from format or stream private unsafe void GetTags(ref AVDictionary* metadata) { AVDictionaryEntry* tag = null; while ((tag = ffmpeg.av_dict_get(metadata, "", tag, ffmpeg.AV_DICT_IGNORE_SUFFIX)) != null) { string key = ""; string val = ""; for (int i = 0; tag->value[i] != 0; i++) { val += (char)tag->value[i]; } for (int i = 0; tag->key[i] != 0; i++) { key += (char)tag->key[i]; } Metadata[key.Trim().ToUpper()] = val; Debug.WriteLine($"{key} = {val}"); if (key.Trim().IndexOf("LOOPSTART", StringComparison.OrdinalIgnoreCase) >= 0) { if (!int.TryParse(val, out _loopstart)) { _loopstart = 0; Debug.WriteLine($"Failed Parse {key} = {val}"); } } else if (key.Trim().IndexOf("LOOPEND", StringComparison.OrdinalIgnoreCase) >= 0) { if (int.TryParse(val, out _loopend)) _looplength = _loopend - _loopstart; else Debug.WriteLine($"Failed Parse {key} = {val}"); } else if (key.Trim().IndexOf("LOOPLENGTH", StringComparison.OrdinalIgnoreCase) >= 0) { if (int.TryParse(val, out _looplength)) _loopend = _loopend + _loopstart; else Debug.WriteLine($"Failed Parse {key} = {val}"); } } } /// /// Opens filename and init class. /// protected void Init(string filename, AVMediaType mediatype = AVMediaType.AVMEDIA_TYPE_AUDIO, FfccMode mode = FfccMode.STATE_MACH, int loopstart = -1) { ffmpeg.av_log_set_level(ffmpeg.AV_LOG_PANIC); LOOPSTART = loopstart; State = FfccState.OPEN; Mode = mode; DecodedFileName = filename; MediaType = mediatype; Return = -1; ConvertedData = null; Update(); } private unsafe bool initNaudio() { #if _WINDOWS if (!Extended.IsLinux) { bufferedWaveProvider = new BufferedWaveProvider( new WaveFormat(ResampleFrame->sample_rate, ResampleFrame->channels)) { DiscardOnBufferOverflow = false }; volumeSampleProvider = new VolumeSampleProvider(bufferedWaveProvider.ToSampleProvider()); nAudioOut = new DirectSoundOut(); if (ResampleFrame->channels == 1) { panningSampleProvider = new PanningSampleProvider(volumeSampleProvider); nAudioOut.Init(panningSampleProvider); } else nAudioOut.Init(volumeSampleProvider); useNaudio = true; return true; } #endif return false; } /// /// Sets up AVFormatContext to be able from the memory buffer. /// protected unsafe void LoadFromRAM(BufferData* bd) { _avio_ctx = null; _avio_ctx_buffer_size = 4096; int ret = 0; //_bufferData = new Buffer_Data //{ // Header = buffer, // HeaderSize = buffer_size //}; _avio_ctx_buffer = (byte*)ffmpeg.av_malloc((ulong)_avio_ctx_buffer_size); if (_avio_ctx_buffer == null) { ret = ffmpeg.AVERROR(ffmpeg.ENOMEM); return; } rf = new avio_alloc_context_read_packet(Read_packet); _avio_ctx = ffmpeg.avio_alloc_context(_avio_ctx_buffer, _avio_ctx_buffer_size, 0, bd, rf, null, null); if (_avio_ctx == null) { ret = ffmpeg.AVERROR(ffmpeg.ENOMEM); } Decoder._format->pb = _avio_ctx; Open(); } /// /// Load sound from Memorystream into a SoundEFFect /// /// Memory Stream containing sound data private unsafe void LoadSoundFromStream(ref MemoryStream decodedStream) { if (DecodedMemoryStream.Length > 0 && MediaType == AVMediaType.AVMEDIA_TYPE_AUDIO) { if (!useNaudio) { if (SoundEffect == null) { SoundEffect = new SoundEffect(decodedStream.GetBuffer(), 0, (int)decodedStream.Length, ResampleFrame->sample_rate, (AudioChannels)ResampleFrame->channels, 0, 0); SoundEffectInstance = SoundEffect.CreateInstance(); if (LOOPSTART >= 0) { SoundEffectInstance.IsLooped = true; } //doesn't throw an exception till you goto play it. } } else RecorderOnDataAvailable(this, new WaveInEventArgs(decodedStream.GetBuffer(), (int)decodedStream.Length)); } } /// /// Copies byte[] data to a Pointer. So it can be used with ffmpeg. /// /// incoming data /// size of data //private void LoadFromRAM(byte[] data, int length) //{ // _intPtr = Marshal.AllocHGlobal(length); // Marshal.Copy(data, 0, _intPtr, length); // LoadFromRAM(_intPtr, length); //} /// /// Add sound from byte[] to a DynamicSoundEFFectInstance, it can play while you give it more data. /// /// sound data private unsafe void LoadSoundFromStream(ref byte[] buffer, int start, ref int length) { if (length > 0 && MediaType == AVMediaType.AVMEDIA_TYPE_AUDIO) { if (!useNaudio) { if (DynamicSound == null) { //create instance here to set sample_rate and channels dynamicly DynamicSound = new DynamicSoundEffectInstance(ResampleFrame->sample_rate, (AudioChannels)ResampleFrame->channels); } try { lock (DynamicSoundEffectLock) DynamicSound.SubmitBuffer(buffer, start, length); } catch (ArgumentException) { //got error saying buffer was too small. makes no sense. } catch (Exception e) { if (e.GetType().Name == "SharpDXException") { //DynamicSound.Dispose(); DynamicSound = null; if (!initNaudio()) { Mode = FfccMode.NOTINIT; State = FfccState.NODLL; } else { LoadSoundFromStream(ref buffer, start, ref length); } } else e.Rethrow(); } } else RecorderOnDataAvailable(this, new WaveInEventArgs(start > 0 ? buffer.Skip(start).ToArray() : buffer, length - start)); } } /// /// Load sound from memory stream by default. /// private void LoadSoundFromStream() => LoadSoundFromStream(ref _decodedMemoryStream); /// /// If looping seek back to LOOPSTART /// private unsafe void Loop() { if (LOOPSTART >= 0 && Mode == FfccMode.STATE_MACH) { int min = LOOPSTART - 1000; if (min < 0) { min = 0; } long max = Decoder.Stream->duration; if (max <= LOOPSTART) { max = LOOPSTART + 1000; } //I didn't realize this didn't change the framenumber to 0. it just appends the LOOPSTART pos to the current stream. //So it is possible this could overflow when it's looped long enough to bring the currentframenum to max value. Return = ffmpeg.avformat_seek_file(Decoder.Format, Decoder.StreamIndex, min, LOOPSTART, max, 0); CheckReturn(); State = FfccState.WAITING; } } /// /// For use in threads runs Next till done. To keep audio buffer fed. Or really good timing /// on video frames. /// private int NextinTask() { try { while (Mode == FfccMode.STATE_MACH && !cancellationToken.IsCancellationRequested && State != FfccState.DONE && !IsDisposed) { lock (Decoder) //make the main thread wait if it accesses this class. { while (!IsDisposed && !Ahead) { if (Next() < 0) break; } } if (!useNaudio) Thread.Sleep(NextAsyncSleep); //delay checks } } //catch (ThreadAbortException) //{ // disposeAll = true;//stop playing //} finally { Dispose(cancellationToken.IsCancellationRequested); // dispose of everything except audio encase it's still playing. } #if _WINDOWS if (useNaudio) { while (!cancellationToken.IsCancellationRequested && nAudioOut != null && nAudioOut.PlaybackState != PlaybackState.Stopped) Thread.Sleep(NextAsyncSleep); //try //{ // if (nAudioOut != null) // nAudioOut.Dispose(); // bufferedWaveProvider.ClearBuffer(); //} //catch (InvalidOperationException) ////{ // if (nAudioOut != null) // Memory.MainThreadOnlyActions.Enqueue(nAudioOut.Dispose); // Memory.MainThreadOnlyActions.Enqueue(bufferedWaveProvider.ClearBuffer); // //doesn't like threads... //} } #endif return 0; } /// /// Opens filename and assigns FormatContext. /// private unsafe int Open() { if (!FileOpened) { fixed (AVFormatContext** tmp = &Decoder._format) { Return = ffmpeg.avformat_open_input(tmp, DecodedFileName, null, null); CheckReturn(); } Return = ffmpeg.avformat_find_stream_info(Decoder.Format, null); CheckReturn(); GetTags(ref Decoder.Format->metadata); FileOpened = true; } return (FileOpened) ? 0 : -1; } /// /// Finds the codec for the chosen stream /// private unsafe void PrepareCodec() { // find & open codec fixed (AVCodec** tmp = &Decoder._codec) { Return = ffmpeg.av_find_best_stream(Decoder.Format, MediaType, -1, -1, tmp, 0); } if (Return == ffmpeg.AVERROR_STREAM_NOT_FOUND && MediaType == AVMediaType.AVMEDIA_TYPE_AUDIO) { //Don't fail if no audiostream just be done. State = FfccState.DONE; Mode = FfccMode.NOTINIT; return; } else { CheckReturn(); } Decoder.StreamIndex = Return; GetTags(ref Decoder.Stream->metadata); Decoder.CodecContext = ffmpeg.avcodec_alloc_context3(Decoder.Codec); if (Decoder.CodecContext == null) { die("Could not allocate codec context"); } Return = ffmpeg.avcodec_parameters_to_context(Decoder.CodecContext, Decoder.Stream->codecpar); CheckReturn(); fixed (AVDictionary** tmp = &_dict) { Return = ffmpeg.av_dict_set(tmp, "strict", "+experimental", 0); CheckReturn(); Return = ffmpeg.avcodec_open2(Decoder.CodecContext, Decoder.Codec, tmp); CheckReturn(); } if (MediaType == AVMediaType.AVMEDIA_TYPE_AUDIO) { if (Decoder.CodecContext->channel_layout == 0) { if (Decoder.CodecContext->channels == 2) { Decoder.CodecContext->channel_layout = ffmpeg.AV_CH_LAYOUT_STEREO; } else if (Decoder.CodecContext->channels == 1) { Decoder.CodecContext->channel_layout = ffmpeg.AV_CH_LAYOUT_MONO; } else { die("must set custom channel layout, is not stereo or mono"); } } } } private void PrepareProcess() { using (DecodedMemoryStream = new MemoryStream()) { Process(); LoadSoundFromStream(); } } /// /// PrepareResampler /// private unsafe void PrepareResampler() { if (MediaType != AVMediaType.AVMEDIA_TYPE_AUDIO) { return; } //resampler ResampleFrame = ffmpeg.av_frame_alloc(); if (ResampleFrame == null) { die("Error allocating the frame\n"); } ResampleContext = ffmpeg.swr_alloc(); ffmpeg.av_opt_set_channel_layout(ResampleContext, "in_channel_layout", (long)Decoder.CodecContext->channel_layout, 0); ffmpeg.av_opt_set_int(ResampleContext, "in_sample_rate", Decoder.CodecContext->sample_rate, 0); ffmpeg.av_opt_set_sample_fmt(ResampleContext, "in_sample_fmt", Decoder.CodecContext->sample_fmt, 0); ffmpeg.av_opt_set_channel_layout(ResampleContext, "out_channel_layout", (long)Decoder.CodecContext->channel_layout, 0); ffmpeg.av_opt_set_sample_fmt(ResampleContext, "out_sample_fmt", AVSampleFormat.AV_SAMPLE_FMT_S16, 0); ffmpeg.av_opt_set_int(ResampleContext, "out_sample_rate", Decoder.CodecContext->sample_rate, 0); Return = ffmpeg.swr_init(ResampleContext); if (Return < 0) { die("swr_init"); } Decoder.Frame->format = (int)Decoder.CodecContext->sample_fmt; Decoder.Frame->channel_layout = Decoder.CodecContext->channel_layout; Decoder.Frame->channels = Decoder.CodecContext->channels; Decoder.Frame->sample_rate = Decoder.CodecContext->sample_rate; ResampleFrame->nb_samples = Decoder.CodecContext->frame_size; if (ResampleFrame->nb_samples == 0) { ResampleFrame->nb_samples = 32; //32, or 64, ADPCM require 32. } ResampleFrame->format = (int)AVSampleFormat.AV_SAMPLE_FMT_S16; ResampleFrame->channel_layout = Decoder.CodecContext->channel_layout; ResampleFrame->channels = Decoder.CodecContext->channels; ResampleFrame->sample_rate = Decoder.CodecContext->sample_rate; int convertedFrameBufferSize = ffmpeg.av_samples_get_buffer_size(null, ResampleFrame->channels, ResampleFrame->nb_samples, (AVSampleFormat)ResampleFrame->format, 0); //ConvertedData = (byte*)Marshal.AllocHGlobal(convertedFrameBufferSize); ConvertedData = new byte[convertedFrameBufferSize]; if (useNaudio) initNaudio(); } /// /// Setup scaler for drawing frames to bitmap. /// private unsafe void PrepareScaler() { if (MediaType != AVMediaType.AVMEDIA_TYPE_VIDEO) { return; } ScalerContext = ffmpeg.sws_getContext( Decoder.CodecContext->width, Decoder.CodecContext->height, Decoder.CodecContext->pix_fmt, Decoder.CodecContext->width, Decoder.CodecContext->height, AVPixelFormat.AV_PIX_FMT_RGBA, ffmpeg.SWS_ACCURATE_RND, null, null, null); Return = ffmpeg.sws_init_context(ScalerContext, null, null); CheckReturn(); } /// /// Decodes, Resamples, Encodes /// /// https://stackoverflow.com/questions/32051847/c-ffmpeg-distorted-sound-when-converting-audio?rq=1#_=_ private unsafe void Process() { while (Decode(out AVFrame _DecodedFrame)) { if (MediaType == AVMediaType.AVMEDIA_TYPE_VIDEO) { if (Mode == FfccMode.STATE_MACH) { State = FfccState.WAITING; break; } // do something with video here. else if (Mode == FfccMode.PROCESS_ALL) { } } else if (MediaType == AVMediaType.AVMEDIA_TYPE_AUDIO) { Resample(ref _DecodedFrame); if (Mode == FfccMode.STATE_MACH && !Behind) //still behind stay here. { State = FfccState.WAITING; break; } } } if (State != FfccState.WAITING) { State = FfccState.DONE; timer.Stop(); timer.Reset(); DecodeFlush(ref Decoder._codecContext, ref *Decoder.Packet); //calling this twice was causing issues. } } /// /// /// /// /// private void RecorderOnDataAvailable(object sender, WaveInEventArgs waveInEventArgs) { #if _WINDOWS if (bufferedWaveProvider == null) initNaudio(); if (useNaudio) bufferedWaveProvider.AddSamples(waveInEventArgs.Buffer, 0, waveInEventArgs.BytesRecorded); #endif } /// /// Resample current frame, Save output data /// /// Current Decoded Frame private unsafe void Resample(ref AVFrame frame) { // Convert int outSamples = 0; fixed (byte** tmp = (byte*[])frame.data) { outSamples = ffmpeg.swr_convert(ResampleContext, null, 0, tmp, frame.nb_samples); } if (outSamples < 0) { die("Could not convert"); } for (; ; ) { outSamples = ffmpeg.swr_get_out_samples(ResampleContext, 0); // 32 was nb_samples but if too big would just not decode anything if (outSamples < 32 * ResampleFrame->channels) { break; } //fixed (byte** tmp = &_convertedData) fixed (byte* tmp = &_convertedData[0]) { outSamples = ffmpeg.swr_convert(ResampleContext, &tmp, ResampleFrame->nb_samples, null, 0); } int buffer_size = ffmpeg.av_samples_get_buffer_size(null, ResampleFrame->channels, ResampleFrame->nb_samples, (AVSampleFormat)ResampleFrame->format, 0); // write to buffer WritetoMs(ref _convertedData, 0, ref buffer_size); } } /// /// Run code depending on state /// /// Change the state to this /// return this /// ret private int Update(FfccState state = FfccState.NULL, int ret = 0) { if (Mode == FfccMode.NOTINIT) { die("Class not Init"); } if (state == FfccState.NODLL) { return -1; } if (state != FfccState.NULL) { State = state; } do { switch (State) { case FfccState.OPEN: State = FfccState.GETCODEC; if (0 < Open()) { die("Failed to Open"); } break; case FfccState.GETCODEC: State = FfccState.PREPARE_SWR; PrepareCodec(); break; case FfccState.PREPARE_SWR: State = FfccState.PREPARE_SWS; PrepareResampler(); break; case FfccState.PREPARE_SWS: State = FfccState.READ; PrepareScaler(); break; case FfccState.READ://Enters waiting state unless we want to process all now. State = Mode == FfccMode.PROCESS_ALL ? FfccState.READALL : FfccState.READONE; //READONE here makes it grab one video frame and precaches audio to it's limit. //WAITING here makes it wait till GetFrame() is called. //READALL just processes the whole audio stream (memoryleaky), not ment for video //Currently video will just saves all the frames to bmp in temp folder on READALL. break; case FfccState.READALL: State = FfccState.DONE; PrepareProcess(); break; case FfccState.READONE: PrepareProcess(); switch (State) { case FfccState.WAITING: ret = 0; break; default: ret = -1; break; } break; default: State = FfccState.DONE; break; } } while (!((Mode == FfccMode.PROCESS_ALL && State == FfccState.DONE) || (State == FfccState.DONE || State == FfccState.WAITING))); return ret; } private int WritetoMs(ref byte[] output, int start, ref int length) { if (Mode == FfccMode.STATE_MACH) { LoadSoundFromStream(ref output, start, ref length); } else { DecodedMemoryStream.Write(output, start, length); } return length - start; } /// /// Write to Memory Stream. /// /// Byte pointer to output buffer array /// Start from typically 0 /// Total bytes to read. /// bytes wrote private unsafe int WritetoMs(ref byte* output, int start, ref int length) { if (Mode == FfccMode.STATE_MACH) { byte[] arr = new byte[length]; Marshal.Copy((IntPtr)output, arr, 0, length); LoadSoundFromStream(ref arr, start, ref length); return length; } else { //memory leaky? seems when i used this method alot of memory wouldn't get disposed, might be only with large sounds long c_len = DecodedMemoryStream.Length; for (int i = start; i < length; i++) { DecodedMemoryStream.WriteByte(output[i]); } if (DecodedMemoryStream.Length - c_len != length) { die("not all data wrote"); } return (int)(DecodedMemoryStream.Length - c_len); } } #endregion Methods } }