├── .editorconfig ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE.md └── stale.yml ├── .gitignore ├── Analyzers.ruleset ├── LICENSE ├── README.md ├── Support ├── arch-michelob-2.0.png ├── arch-michelob-2.0.pptx ├── ffme.png ├── ffmeplay.png ├── nugetdoc.md └── readme.txt ├── Unosquare.FFME.MediaElement ├── Common │ ├── DataFrameReceivedEventArgs.cs │ ├── FrameDecodedEventArgs.cs │ ├── InputFormatEventArgs.cs │ ├── MediaFailedEventArgs.cs │ ├── MediaInitializingEventArgs.cs │ ├── MediaLogMessageEventArgs.cs │ ├── MediaOpenedEventArgs.cs │ ├── MediaOpeningEventArgs.cs │ ├── MediaStateChangedEventArgs.cs │ ├── PacketReadEventArgs.cs │ ├── PositionChangedEventArgs.cs │ ├── SubtitleDecodedEventArgs.cs │ └── ViewModelBase.cs ├── Constants.cs ├── MediaElement.Events.cs ├── MediaElement.Properties.cs ├── MediaElement.cs ├── Platform │ ├── ClassProxy.cs │ ├── IGuiContext.cs │ ├── IPropertyProxy.cs │ ├── MediaConnector.cs │ ├── PropertyMapper.cs │ └── PropertyProxy.cs ├── Unosquare.FFME.MediaElement.projitems └── Unosquare.FFME.MediaElement.shproj ├── Unosquare.FFME.Windows.Sample ├── App.Icons.xaml ├── App.Styles.xaml ├── App.xaml ├── App.xaml.cs ├── AppCommands.cs ├── Controls │ ├── ControllerPanelControl.xaml │ ├── ControllerPanelControl.xaml.cs │ ├── PlaylistPanelControl.xaml │ ├── PlaylistPanelControl.xaml.cs │ ├── PropertiesPanelControl.xaml │ └── PropertiesPanelControl.xaml.cs ├── Foundation │ ├── CustomPlaylistEntry.cs │ ├── CustomPlaylistEntryCollection.cs │ ├── DeferredAction.cs │ ├── DelegateCommand.cs │ ├── FileInputStream.cs │ ├── ReactiveExtensions.cs │ ├── ThumbnailGenerator.cs │ ├── TransportStreamRecorder.cs │ ├── ValueConverters.cs │ └── WindowStatus.cs ├── GlobalSuppressions.cs ├── MainWindow.MediaEvents.cs ├── MainWindow.MediaRendering.cs ├── MainWindow.xaml ├── MainWindow.xaml.cs ├── Properties │ └── launchSettings.json ├── Unosquare.FFME.Windows.Sample.csproj ├── ViewModels │ ├── AttachedViewModel.cs │ ├── ControllerViewModel.cs │ ├── PlaylistViewModel.cs │ └── RootViewModel.cs └── ffmpeg.ico ├── Unosquare.FFME.Windows ├── Common │ ├── AudioDeviceInfo.cs │ ├── BitmapDataBuffer.cs │ ├── LegacyAudioException.cs │ ├── LegacyAudioResult.cs │ ├── RendererOptions.cs │ ├── RenderingAudioEventArgs.cs │ ├── RenderingEventArgs.cs │ ├── RenderingSubtitlesEventArgs.cs │ ├── RenderingVideoEventArgs.cs │ └── VideoRendererImageType.cs ├── GlobalSuppressions.cs ├── Library.cs ├── MediaElement.Events.cs ├── MediaElement.Properties.cs ├── MediaElement.cs ├── MediaElement.png ├── Platform │ ├── GuiContext.cs │ ├── GuiContextType.cs │ ├── MediaConnector.cs │ └── SoundTouch.cs ├── Properties │ ├── Resources.Designer.cs │ ├── Resources.resx │ └── launchSettings.json ├── Rendering │ ├── AudioRenderer.cs │ ├── ClosedCaptionsBuffer.cs │ ├── ClosedCaptionsCell.cs │ ├── ClosedCaptionsCellState.cs │ ├── ClosedCaptionsControl.cs │ ├── ElementHostBase.cs │ ├── ImageHost.cs │ ├── InteropVideoRenderer.cs │ ├── SubtitleRenderer.cs │ ├── SubtitlesControl.cs │ ├── VideoRenderer.cs │ ├── VideoRendererBase.cs │ └── Wave │ │ ├── DirectSoundDeviceData.cs │ │ ├── DirectSoundPlayer.cs │ │ ├── IWavePlayer.cs │ │ ├── IWaveProvider.cs │ │ ├── LegacyAudioDeviceData.cs │ │ ├── LegacyAudioPlayer.cs │ │ ├── MmTime.cs │ │ ├── PlaybackState.cs │ │ ├── SupportedWaveFormat.cs │ │ ├── WaveFormat.cs │ │ ├── WaveHeader.cs │ │ ├── WaveHeaderFlags.cs │ │ ├── WaveInterop.cs │ │ ├── WaveOutBuffer.cs │ │ └── WaveOutSupport.cs ├── Resources │ └── ffme-preview.png ├── Subtitles.cs └── Unosquare.FFME.Windows.csproj ├── Unosquare.FFME.sln ├── Unosquare.FFME ├── ClosedCaptions │ ├── CaptionsChannel.cs │ ├── CaptionsColor.cs │ ├── CaptionsCommand.cs │ ├── CaptionsPacketType.cs │ ├── CaptionsStyle.cs │ ├── CaptionsXdsClass.cs │ └── ClosedCaptionPacket.cs ├── Commands │ ├── CommandManager.Direct.cs │ ├── CommandManager.Enums.cs │ ├── CommandManager.Priority.cs │ ├── CommandManager.Seek.cs │ └── CommandManager.cs ├── Common │ ├── ContainerConfiguration.cs │ ├── DataFrame.cs │ ├── DecoderOptions.cs │ ├── DemuxerGlobalOptions.cs │ ├── HardwareDeviceInfo.cs │ ├── IMediaEngineState.cs │ ├── IMediaInputStream.cs │ ├── MediaContainerException.cs │ ├── MediaInfo.cs │ ├── MediaLogMessageType.cs │ ├── MediaOptions.cs │ ├── MediaPlaybackState.cs │ ├── MediaType.cs │ ├── OptionMetadata.cs │ ├── VideoResolutionDivider.cs │ ├── VideoSeekIndex.cs │ └── VideoSeekIndexEntry.cs ├── Constants.cs ├── Container │ ├── AudioBlock.cs │ ├── AudioComponent.cs │ ├── AudioFrame.cs │ ├── DataComponentSet.cs │ ├── HardwareAccelerator.cs │ ├── MediaBlock.cs │ ├── MediaBlockBuffer.cs │ ├── MediaComponent.cs │ ├── MediaComponentSet.cs │ ├── MediaContainer.cs │ ├── MediaFrame.cs │ ├── MediaPacket.cs │ ├── PacketBufferState.cs │ ├── PacketQueue.cs │ ├── PacketQueueOp.cs │ ├── SubtitleBlock.cs │ ├── SubtitleComponent.cs │ ├── SubtitleFrame.cs │ ├── VideoBlock.cs │ ├── VideoComponent.cs │ └── VideoFrame.cs ├── Diagnostics │ ├── Aspects.cs │ ├── Benchmark.cs │ ├── BenchmarkResult.cs │ ├── ILoggingHandler.cs │ ├── ILoggingSource.cs │ ├── Logging.cs │ ├── LoggingMessage.cs │ └── RC.cs ├── Engine │ ├── BlockRenderingWorker.cs │ ├── FrameDecodingWorker.cs │ ├── IMediaWorker.cs │ ├── MediaEngine.Connector.cs │ ├── MediaEngine.Controller.cs │ ├── MediaEngine.Workers.cs │ ├── MediaEngine.cs │ ├── MediaEngineState.cs │ ├── MediaWorkerSet.cs │ ├── MediaWorkerType.cs │ ├── PacketReadingWorker.cs │ └── TimingController.cs ├── FFmpeg │ ├── FFAudioParams.cs │ ├── FFBPrint.cs │ ├── FFDictionary.cs │ ├── FFDictionaryEntry.cs │ └── FFInterop.cs ├── Library.cs ├── Platform │ ├── IMediaConnector.cs │ └── IMediaRenderer.cs ├── Playlists │ ├── PlaylistEntry.cs │ ├── PlaylistEntryAttributeDictionary.cs │ ├── PlaylistEntryCollection.cs │ └── PlaylistExtensions.cs ├── Primitives │ ├── AtomicBoolean.cs │ ├── AtomicDateTime.cs │ ├── AtomicDouble.cs │ ├── AtomicInteger.cs │ ├── AtomicLong.cs │ ├── AtomicTimeSpan.cs │ ├── AtomicTypeBase.cs │ ├── CircularBuffer.cs │ ├── ISyncLocker.cs │ ├── IWorker.cs │ ├── IntervalWorkerBase.cs │ ├── MediaTypeDictionary.cs │ ├── RealtimeClock.cs │ ├── StepTimer.cs │ ├── SyncLockerFactory.cs │ ├── VerticalSyncContext.cs │ ├── WorkerBase.cs │ └── WorkerState.cs ├── Unosquare.FFME.projitems ├── Unosquare.FFME.shproj ├── Utilities.Audio.cs ├── Utilities.Media.cs ├── Utilities.Time.cs └── Utilities.cs ├── appveyor.yml ├── docfx.json └── toc.yml /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.cs] 4 | indent_style = space 5 | indent_size = 4 6 | 7 | # CA2000: Dispose objects before losing scope 8 | dotnet_diagnostic.CA2000.severity = suggestion 9 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set the default behavior, in case people don't have core.autocrlf set. 2 | * text=auto 3 | 4 | # Explicitly declare text files you want to always be normalized and converted 5 | # to native line endings on checkout. 6 | *.c text 7 | *.h text 8 | 9 | # Declare files that will always have CRLF line endings on checkout. 10 | *.sln text eol=crlf 11 | *.cs text eol=crlf 12 | *.xml text eol=crlf 13 | *.xaml text eol=crlf 14 | 15 | # Denote all files that are truly binary and should not be modified. 16 | *.png binary 17 | *.jpg binary 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Issue Title (*change this title!*) 2 | 3 | *Please enter a general description for the issue. Delete sections that are not relevant and provide additional sections if necessary.* 4 | 5 | ## Issue Categories 6 | 7 | - [ ] Bug 8 | - [ ] Feature Request 9 | - [ ] Question 10 | - [ ] Not sure 11 | 12 | ## Version Information 13 | 14 | - [ ] NuGet Package *Enter Version Number Here* 15 | - [ ] Build From *Master* branch, Commit *Enter commit id. Example: 704c482* 16 | - [ ] Build from *Branch Name*, Commit *Enter commit id. Example: 704c482* 17 | 18 | ## Steps to Reproduce 19 | 20 | 1. Step 1 21 | 2. Step 2 22 | 3. Step 3 23 | 24 | ## Expected Results 25 | 26 | - Result 1 27 | - Result 2 28 | 29 | ## Sample Code 30 | 31 | ### XAML 32 | ```xml 33 | 34 | 35 | ``` 36 | 37 | ### C# 38 | ```csharp 39 | System.Console.WriteLine("Change Me Please!"); 40 | ``` 41 | 42 | ### Command line ffplay 43 | ```batch 44 | ffplay "hello.mp4" 45 | ``` 46 | ## Delete This Section 47 | - Please consider a donation here: https://www.paypal.me/mariodivece/50usd 48 | - I work on this project for fun and on my free time. 49 | - Provide as much information as possible and any sample files 50 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 60 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: false 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - pinned 8 | - security 9 | # Label to use when marking an issue as stale 10 | staleLabel: wontfix 11 | # Comment to post when marking an issue as stale. Set to `false` to disable 12 | markComment: > 13 | This issue has been automatically marked as stale because it has not had 14 | recent activity. Thank you for your contributions. 15 | # Comment to post when closing a stale issue. Set to `false` to disable 16 | closeComment: false 17 | -------------------------------------------------------------------------------- /Support/arch-michelob-2.0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unosquare/ffmediaelement/72f2c791dfbb74234b8976ef5ae43f2dc46f678e/Support/arch-michelob-2.0.png -------------------------------------------------------------------------------- /Support/arch-michelob-2.0.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unosquare/ffmediaelement/72f2c791dfbb74234b8976ef5ae43f2dc46f678e/Support/arch-michelob-2.0.pptx -------------------------------------------------------------------------------- /Support/ffme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unosquare/ffmediaelement/72f2c791dfbb74234b8976ef5ae43f2dc46f678e/Support/ffme.png -------------------------------------------------------------------------------- /Support/ffmeplay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unosquare/ffmediaelement/72f2c791dfbb74234b8976ef5ae43f2dc46f678e/Support/ffmeplay.png -------------------------------------------------------------------------------- /Support/nugetdoc.md: -------------------------------------------------------------------------------- 1 | Here is a quick guide on how to get started. 2 | 1. Open Visual Studio (v2019 recommended), and create a new WPF Application. Target Framework must be .Net 5.0 or above. 3 | 2. Install the NuGet Package from your Package Manager Console: `PM> Install-Package FFME.Windows` 4 | 3. You need FFmpeg **shared** binaries (64 or 32 bit, depending on your app's target architecture). Build your own or download a compatible build from [FFmpeg Windows Downloads](https://ffmpeg.org/download.html). 5 | 4. Your FFmpeg build should have a `bin` folder with 3 exe files and some dll files. Copy all those files to a folder such as `c:\ffmpeg` 6 | 5. Within you application's startup code (`Main` method), set `Unosquare.FFME.Library.FFmpegDirectory = @"c:\ffmpeg";`. 7 | 6. Use the FFME `MediaElement` control as any other WPF control. 8 | For example: In your `MainForm.xaml`, add the namespace: `xmlns:ffme="clr-namespace:Unosquare.FFME;assembly=ffme.win"` and then add the FFME control your window's XAML: `` 9 | 7. To play files or streams, simply call the asynchronous `Open` method: `await Media.Open(new Uri(@"c:\your-file-here"));`. 10 | -------------------------------------------------------------------------------- /Support/readme.txt: -------------------------------------------------------------------------------- 1 | How to use FFME 2 | 3 | In order to use the FFME MediaElement control, you will need to setup a folder with FFmpeg binaries and point to it from your application code. 4 | Here are the steps: 5 | 6 | 1. You can build your own FFmpeg **shared** binaries or download a compatible build from the FFmpeg Windows Downloads page: (https://ffmpeg.org/download.html). 7 | 2. Your FFmpeg build (see the bin folder) should have 3 exe files and a number of dll files and must match your app's architecture (32-bit or 64-bit). Copy all of them to a folder such as (c:\ffmpeg) 8 | 3. Within you application's startup code (Main method), set Unosquare.FFME.Library.FFmpegDirectory = @"path to ffmpeg binaries from the previous step";. 9 | 4. Use the FFME MediaElement control as any other WPF control! 10 | For example: In your MainForm.xaml, add the namespace: xmlns:ffme="clr-namespace:Unosquare.FFME;assembly=ffme.win" 11 | And then add the FFME control your window: 12 | To play files or streams, call the asyncrhonous Open method: await Media.Open(new Uri(@"c:\your-file-here")); 13 | 14 | Happy coding! 15 | *Mario, Unosquare and the FFME contributors.* -------------------------------------------------------------------------------- /Unosquare.FFME.MediaElement/Common/DataFrameReceivedEventArgs.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Common 2 | { 3 | using System; 4 | 5 | /// 6 | /// Event arguments corresponding to the reading of data (non-media) frames. 7 | /// 8 | public sealed class DataFrameReceivedEventArgs : EventArgs 9 | { 10 | /// 11 | /// Initializes a new instance of the class. 12 | /// 13 | /// The data frame. 14 | /// The stream. 15 | internal DataFrameReceivedEventArgs(DataFrame dataFrame, StreamInfo stream) 16 | { 17 | Frame = dataFrame; 18 | Stream = stream; 19 | } 20 | 21 | /// 22 | /// Contains the data frame. 23 | /// 24 | public DataFrame Frame { get; } 25 | 26 | /// 27 | /// Gets the associated stream information. 28 | /// 29 | public StreamInfo Stream { get; } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Unosquare.FFME.MediaElement/Common/FrameDecodedEventArgs.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Common 2 | { 3 | using FFmpeg.AutoGen; 4 | 5 | /// 6 | /// Event arguments corresponding to the audio or video frame decoded events. Useful for capturing streams. 7 | /// 8 | public sealed unsafe class FrameDecodedEventArgs : InputFormatEventArgs 9 | { 10 | /// 11 | /// Initializes a new instance of the class. 12 | /// 13 | /// The audio or video frame pointer. 14 | /// The input format context. 15 | internal FrameDecodedEventArgs(AVFrame* frame, AVFormatContext* context) 16 | : base(context) 17 | { 18 | Frame = frame; 19 | } 20 | 21 | /// 22 | /// Gets the pointer to the audio or video frame that was decoded. 23 | /// 24 | public AVFrame* Frame { get; } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Unosquare.FFME.MediaElement/Common/InputFormatEventArgs.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Common 2 | { 3 | using FFmpeg.AutoGen; 4 | using System; 5 | 6 | /// 7 | /// Generic Input format event arguments. Useful for capturing streams. 8 | /// 9 | public abstract unsafe class InputFormatEventArgs : EventArgs 10 | { 11 | /// 12 | /// Initializes a new instance of the class. 13 | /// 14 | /// The input format context. 15 | protected InputFormatEventArgs(AVFormatContext* context) 16 | { 17 | InputContext = context; 18 | } 19 | 20 | /// 21 | /// Gets a pointer to the unmanaged input format context. 22 | /// 23 | public AVFormatContext* InputContext { get; } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Unosquare.FFME.MediaElement/Common/MediaFailedEventArgs.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Common 2 | { 3 | using System; 4 | 5 | /// 6 | /// The Media failed event arguments. 7 | /// 8 | public sealed class MediaFailedEventArgs : EventArgs 9 | { 10 | /// 11 | /// Initializes a new instance of the class. 12 | /// 13 | /// The exception. 14 | internal MediaFailedEventArgs(Exception errorException) 15 | { 16 | ErrorException = errorException; 17 | } 18 | 19 | /// 20 | /// Gets the error exception. 21 | /// 22 | public Exception ErrorException { get; } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Unosquare.FFME.MediaElement/Common/MediaInitializingEventArgs.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Common 2 | { 3 | using System; 4 | using System.Windows; 5 | 6 | /// 7 | /// Represents the event arguments of the MediaInitializing routed event. 8 | /// 9 | /// 10 | public class MediaInitializingEventArgs : EventArgs 11 | { 12 | /// 13 | /// Initializes a new instance of the class. 14 | /// 15 | /// The container configuration options. 16 | /// The URL. 17 | internal MediaInitializingEventArgs(ContainerConfiguration config, string mediaSource) 18 | { 19 | Configuration = config; 20 | MediaSource = mediaSource; 21 | } 22 | 23 | /// 24 | /// Set or change the container configuration options before the media is opened. 25 | /// 26 | public ContainerConfiguration Configuration { get; } 27 | 28 | /// 29 | /// Gets the URL. 30 | /// 31 | public string MediaSource { get; } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Unosquare.FFME.MediaElement/Common/MediaLogMessageEventArgs.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Common 2 | { 3 | using Diagnostics; 4 | using System; 5 | 6 | /// 7 | /// Contains the Message Logged Event Arguments. 8 | /// 9 | /// 10 | public class MediaLogMessageEventArgs : EventArgs 11 | { 12 | /// 13 | /// Initializes a new instance of the class. 14 | /// 15 | /// The message. 16 | internal MediaLogMessageEventArgs(LoggingMessage message) 17 | { 18 | TimestampUtc = message.TimestampUtc; 19 | MessageType = message.MessageType; 20 | Message = message.Message; 21 | AspectName = message.AspectName; 22 | } 23 | 24 | /// 25 | /// Gets the timestamp. 26 | /// 27 | public DateTime TimestampUtc { get; } 28 | 29 | /// 30 | /// Gets the type of the message. 31 | /// 32 | public MediaLogMessageType MessageType { get; } 33 | 34 | /// 35 | /// Gets the contents of the message. 36 | /// 37 | public string Message { get; } 38 | 39 | /// 40 | /// Gets the aspect or feature that sent the logged message. 41 | /// May or may not be available. 42 | /// 43 | public string AspectName { get; } 44 | 45 | /// 46 | public override string ToString() => 47 | $"[{TimestampUtc.Minute:00}:{TimestampUtc.Second:00}.{TimestampUtc.Millisecond:000} " + 48 | $"| {GetTypePrefix()} | {AspectName,-20}] {Message}"; 49 | 50 | /// 51 | /// Gets the type prefix. 52 | /// 53 | /// A 3-letter abbreviation. 54 | private string GetTypePrefix() 55 | { 56 | switch (MessageType) 57 | { 58 | case MediaLogMessageType.Debug: 59 | return "DBG"; 60 | case MediaLogMessageType.Error: 61 | return "ERR"; 62 | case MediaLogMessageType.Info: 63 | return "INF"; 64 | case MediaLogMessageType.None: 65 | return "NON"; 66 | case MediaLogMessageType.Trace: 67 | return "TRC"; 68 | case MediaLogMessageType.Warning: 69 | return "WRN"; 70 | default: 71 | return "INV"; 72 | } 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Unosquare.FFME.MediaElement/Common/MediaOpenedEventArgs.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Common 2 | { 3 | using Common; 4 | using System; 5 | 6 | /// 7 | /// Represents the event arguments of the or 8 | /// routed events. 9 | /// 10 | /// 11 | public class MediaOpenedEventArgs : EventArgs 12 | { 13 | /// 14 | /// Initializes a new instance of the class. 15 | /// 16 | /// The input information. 17 | internal MediaOpenedEventArgs(MediaInfo info) 18 | { 19 | Info = info; 20 | } 21 | 22 | /// 23 | /// Provides internal details of the media, including its component streams. 24 | /// Typically, options are set based on what this information contains. 25 | /// 26 | public MediaInfo Info { get; } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Unosquare.FFME.MediaElement/Common/MediaOpeningEventArgs.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Common 2 | { 3 | using System; 4 | 5 | /// 6 | /// Represents the event arguments of the 7 | /// or routed events. 8 | /// 9 | /// 10 | public class MediaOpeningEventArgs : EventArgs 11 | { 12 | /// 13 | /// Initializes a new instance of the class. 14 | /// 15 | /// The options. 16 | /// The input information. 17 | internal MediaOpeningEventArgs(MediaOptions options, MediaInfo info) 18 | { 19 | Options = options; 20 | Info = info; 21 | } 22 | 23 | /// 24 | /// Set or change the options before the media is opened. 25 | /// 26 | public MediaOptions Options { get; } 27 | 28 | /// 29 | /// Provides internal details of the media, including its component streams. 30 | /// Typically, options are set based on what this information contains. 31 | /// 32 | public MediaInfo Info { get; } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Unosquare.FFME.MediaElement/Common/MediaStateChangedEventArgs.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Common 2 | { 3 | using Common; 4 | using System; 5 | 6 | /// 7 | /// Contains the media state changed event args. 8 | /// 9 | /// 10 | public class MediaStateChangedEventArgs : EventArgs 11 | { 12 | /// 13 | /// Initializes a new instance of the class. 14 | /// 15 | /// State of the previous. 16 | /// The new state. 17 | internal MediaStateChangedEventArgs(MediaPlaybackState oldState, MediaPlaybackState newState) 18 | { 19 | OldMediaState = oldState; 20 | MediaState = newState; 21 | } 22 | 23 | /// 24 | /// Gets the current media state. 25 | /// 26 | public MediaPlaybackState MediaState { get; } 27 | 28 | /// 29 | /// Gets the position. 30 | /// 31 | public MediaPlaybackState OldMediaState { get; } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Unosquare.FFME.MediaElement/Common/PacketReadEventArgs.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Common 2 | { 3 | using FFmpeg.AutoGen; 4 | 5 | /// 6 | /// Event arguments corresponding to the packet reading event. Useful for capturing streams. 7 | /// 8 | public sealed unsafe class PacketReadEventArgs : InputFormatEventArgs 9 | { 10 | /// 11 | /// Initializes a new instance of the class. 12 | /// 13 | /// The packet pointer. 14 | /// The input format context. 15 | internal PacketReadEventArgs(AVPacket* packet, AVFormatContext* context) 16 | : base(context) 17 | { 18 | Packet = packet; 19 | } 20 | 21 | /// 22 | /// Gets the pointer to the packet that was read. 23 | /// 24 | public AVPacket* Packet { get; } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Unosquare.FFME.MediaElement/Common/PositionChangedEventArgs.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Common 2 | { 3 | using System; 4 | 5 | /// 6 | /// Contains the position changed routed event args. 7 | /// 8 | /// 9 | public class PositionChangedEventArgs : EventArgs 10 | { 11 | /// 12 | /// Initializes a new instance of the class. 13 | /// 14 | /// State of the engine. 15 | /// The old position. 16 | /// The new position. 17 | internal PositionChangedEventArgs(IMediaEngineState engineState, TimeSpan oldPosition, TimeSpan newPosition) 18 | { 19 | Position = newPosition; 20 | OldPosition = oldPosition; 21 | EngineState = engineState; 22 | } 23 | 24 | /// 25 | /// Gets the current position. 26 | /// 27 | public TimeSpan Position { get; } 28 | 29 | /// 30 | /// Gets the old position. 31 | /// 32 | public TimeSpan OldPosition { get; } 33 | 34 | /// 35 | /// Provides access to the underlying media engine state. 36 | /// 37 | public IMediaEngineState EngineState { get; } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Unosquare.FFME.MediaElement/Common/SubtitleDecodedEventArgs.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Common 2 | { 3 | using FFmpeg.AutoGen; 4 | 5 | /// 6 | /// Event arguments corresponding to the subtitle decoded event. Useful for capturing streams. 7 | /// 8 | public sealed unsafe class SubtitleDecodedEventArgs : InputFormatEventArgs 9 | { 10 | /// 11 | /// Initializes a new instance of the class. 12 | /// 13 | /// The subtitle pointer. 14 | /// The input format context. 15 | internal SubtitleDecodedEventArgs(AVSubtitle* subtitle, AVFormatContext* context) 16 | : base(context) 17 | { 18 | Subtitle = subtitle; 19 | } 20 | 21 | /// 22 | /// Gets the pointer to subtitle that was decoded. 23 | /// 24 | public AVSubtitle* Subtitle { get; } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Unosquare.FFME.MediaElement/Constants.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME 2 | { 3 | using System; 4 | 5 | internal static partial class Constants 6 | { 7 | /// 8 | /// Gets the period at which media state properties are updated. 9 | /// 10 | public static TimeSpan PropertyUpdatesInterval { get; } = TimeSpan.FromMilliseconds(30); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Unosquare.FFME.MediaElement/Platform/IGuiContext.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Platform 2 | { 3 | using System; 4 | using System.Runtime.CompilerServices; 5 | 6 | /// 7 | /// Provides platform-specific methods sensible to a UI context. 8 | /// 9 | internal interface IGuiContext 10 | { 11 | /// 12 | /// Gets the type of the context. 13 | /// 14 | GuiContextType Type { get; } 15 | 16 | /// 17 | /// Invokes a task on the GUI thread with the possibility of awaiting it. 18 | /// 19 | /// The callback. 20 | /// The awaitable task. 21 | ConfiguredTaskAwaitable InvokeAsync(Action callback); 22 | 23 | /// 24 | /// Invokes a task on the GUI thread and does not await it. 25 | /// 26 | /// The callback. 27 | void EnqueueInvoke(Action callback); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Unosquare.FFME.MediaElement/Platform/IPropertyProxy.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Platform 2 | { 3 | using System; 4 | 5 | /// 6 | /// Defines methods and properties for a proxy related to a property 7 | /// containing getter and setter delegates along with property metadata. 8 | /// 9 | internal interface IPropertyProxy 10 | { 11 | /// 12 | /// Gets the property name. 13 | /// 14 | string Name { get; } 15 | 16 | /// 17 | /// Gets the property type. 18 | /// 19 | Type Type { get; } 20 | 21 | /// 22 | /// Gets a value indicating whether this property has a getter. 23 | /// 24 | bool CanRead { get; } 25 | 26 | /// 27 | /// Gets a value indicating whether this property has a setter. 28 | /// 29 | bool CanWrite { get; } 30 | 31 | /// 32 | /// Calls the getter method for the property on the specified instance. 33 | /// 34 | /// The instance. 35 | /// The value of the property. 36 | object GetValue(object instance); 37 | 38 | /// 39 | /// Calls the setter method for the property on the specified instance. 40 | /// 41 | /// The instance. 42 | /// The value. 43 | void SetValue(object instance, object value); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Unosquare.FFME.MediaElement/Platform/PropertyProxy.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable CA1812 2 | namespace Unosquare.FFME.Platform 3 | { 4 | using System; 5 | using System.Reflection; 6 | using System.Runtime.CompilerServices; 7 | 8 | /// 9 | /// Defines property metadata and delegate methods to get and set property values. 10 | /// 11 | /// The type of the object. 12 | /// The type of the value. 13 | internal sealed class PropertyProxy : IPropertyProxy 14 | where TClass : class 15 | { 16 | private readonly Func Getter; 17 | private readonly Action Setter; 18 | 19 | /// 20 | /// Initializes a new instance of the class. 21 | /// 22 | /// The property. 23 | public PropertyProxy(PropertyInfo property) 24 | { 25 | Name = property.Name; 26 | Type = property.PropertyType; 27 | 28 | var getterInfo = property.GetGetMethod(false); 29 | if (getterInfo != null) 30 | { 31 | CanRead = true; 32 | Getter = (Func)Delegate.CreateDelegate(typeof(Func), getterInfo); 33 | } 34 | 35 | var setterInfo = property.GetSetMethod(false); 36 | if (setterInfo != null) 37 | { 38 | CanWrite = true; 39 | Setter = (Action)Delegate.CreateDelegate(typeof(Action), setterInfo); 40 | } 41 | } 42 | 43 | /// 44 | public string Name { get; } 45 | 46 | /// 47 | public Type Type { get; } 48 | 49 | /// 50 | public bool CanRead { get; } 51 | 52 | /// 53 | public bool CanWrite { get; } 54 | 55 | /// 56 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 57 | object IPropertyProxy.GetValue(object instance) => 58 | Getter(instance as TClass); 59 | 60 | /// 61 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 62 | void IPropertyProxy.SetValue(object instance, object value) => 63 | Setter(instance as TClass, (TProperty)value); 64 | } 65 | } 66 | #pragma warning restore CA1812 -------------------------------------------------------------------------------- /Unosquare.FFME.MediaElement/Unosquare.FFME.MediaElement.projitems: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | $(MSBuildAllProjects);$(MSBuildThisFileFullPath) 5 | true 6 | 4f84c6a1-ba3a-4a1e-84ee-7a7aa214f317 7 | 8 | 9 | Unosquare.FFME 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /Unosquare.FFME.MediaElement/Unosquare.FFME.MediaElement.shproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 4f84c6a1-ba3a-4a1e-84ee-7a7aa214f317 5 | 14.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Unosquare.FFME.Windows.Sample/App.xaml: -------------------------------------------------------------------------------- 1 |  6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /Unosquare.FFME.Windows.Sample/Controls/ControllerPanelControl.xaml.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Windows.Sample.Controls 2 | { 3 | /// 4 | /// Interaction logic for ControllerPanelControl.xaml. 5 | /// 6 | public partial class ControllerPanelControl 7 | { 8 | /// 9 | /// Initializes a new instance of the class. 10 | /// 11 | public ControllerPanelControl() => InitializeComponent(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Unosquare.FFME.Windows.Sample/Controls/PlaylistPanelControl.xaml.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Windows.Sample.Controls 2 | { 3 | using Foundation; 4 | using System; 5 | using System.Windows; 6 | using System.Windows.Controls.Primitives; 7 | using System.Windows.Input; 8 | using ViewModels; 9 | 10 | /// 11 | /// Interaction logic for PlaylistPanelControl.xaml. 12 | /// 13 | public partial class PlaylistPanelControl 14 | { 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | public PlaylistPanelControl() 19 | { 20 | InitializeComponent(); 21 | 22 | // Prevent binding to the events 23 | if (App.IsInDesignMode) 24 | return; 25 | 26 | // Bind the Enter key to the command 27 | OpenFileTextBox.KeyDown += async (s, e) => 28 | { 29 | if (e.Key != Key.Enter) return; 30 | await App.ViewModel.Commands.OpenCommand.ExecuteAsync(OpenFileTextBox.Text); 31 | e.Handled = true; 32 | }; 33 | 34 | SearchTextBox.IsEnabledChanged += (s, e) => 35 | { 36 | if ((bool)e.OldValue == false && (bool)e.NewValue) 37 | FocusSearchBox(); 38 | 39 | if ((bool)e.OldValue && (bool)e.NewValue == false) 40 | FocusFileBox(); 41 | }; 42 | 43 | IsVisibleChanged += (s, e) => 44 | { 45 | if (SearchTextBox.IsEnabled) 46 | FocusSearchBox(); 47 | else 48 | FocusFileBox(); 49 | }; 50 | } 51 | 52 | #region Properties 53 | 54 | /// 55 | /// A proxy, strongly-typed property to the underlying DataContext. 56 | /// 57 | public RootViewModel ViewModel => DataContext as RootViewModel; 58 | 59 | #endregion 60 | 61 | private static void FocusTextBox(TextBoxBase textBox) 62 | { 63 | DeferredAction.Create(context => 64 | { 65 | if (textBox == null || Application.Current == null || Application.Current.MainWindow == null) 66 | return; 67 | 68 | textBox.Focus(); 69 | textBox.SelectAll(); 70 | FocusManager.SetFocusedElement(Application.Current.MainWindow, textBox); 71 | Keyboard.Focus(textBox); 72 | 73 | if (textBox.IsVisible == false || textBox.IsKeyboardFocused) 74 | context?.Dispose(); 75 | else 76 | context?.Defer(TimeSpan.FromSeconds(0.25)); 77 | }).Defer(TimeSpan.FromSeconds(0.25)); 78 | } 79 | 80 | /// 81 | /// Focuses the search box. 82 | /// 83 | private void FocusSearchBox() 84 | { 85 | FocusTextBox(SearchTextBox); 86 | } 87 | 88 | /// 89 | /// Focuses the file box. 90 | /// 91 | private void FocusFileBox() 92 | { 93 | FocusTextBox(OpenFileTextBox); 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /Unosquare.FFME.Windows.Sample/Controls/PropertiesPanelControl.xaml.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Windows.Sample.Controls 2 | { 3 | /// 4 | /// Interaction logic for MediaPropertyPanelControl.xaml. 5 | /// 6 | public partial class PropertiesPanelControl 7 | { 8 | /// 9 | /// Initializes a new instance of the class. 10 | /// 11 | public PropertiesPanelControl() 12 | { 13 | InitializeComponent(); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Unosquare.FFME.Windows.Sample/Foundation/CustomPlaylistEntry.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Windows.Sample.Foundation 2 | { 3 | using Playlists; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Globalization; 7 | using System.Runtime.CompilerServices; 8 | 9 | /// 10 | /// A custom playlist entry with notification properties backed by Attributes. 11 | /// 12 | /// 13 | public sealed class CustomPlaylistEntry : PlaylistEntry 14 | { 15 | private static readonly Dictionary PropertyMap = new Dictionary 16 | { 17 | { nameof(Thumbnail), "ffme-thumbnail" }, 18 | { nameof(Format), "info-format" }, 19 | { nameof(LastOpenedUtc), "ffme-lastopened" } 20 | }; 21 | 22 | /// 23 | /// Gets or sets the thumbnail. 24 | /// 25 | public string Thumbnail 26 | { 27 | get => GetMappedAttributeValue(); 28 | set => SetMappedAttributeValue(value); 29 | } 30 | 31 | /// 32 | /// Gets or sets the format. 33 | /// 34 | public string Format 35 | { 36 | get => GetMappedAttributeValue(); 37 | set => SetMappedAttributeValue(value); 38 | } 39 | 40 | /// 41 | /// Gets or sets the last opened UTC. 42 | /// 43 | public DateTime? LastOpenedUtc 44 | { 45 | get 46 | { 47 | var currentValue = GetMappedAttributeValue(); 48 | if (string.IsNullOrWhiteSpace(currentValue)) 49 | return default; 50 | 51 | return long.TryParse(currentValue, out var binaryValue) ? 52 | DateTime.FromBinary(binaryValue) : 53 | default(DateTime?); 54 | } 55 | set 56 | { 57 | if (value == null) 58 | { 59 | SetMappedAttributeValue(null); 60 | return; 61 | } 62 | 63 | var binaryValue = value.Value.ToBinary().ToString(CultureInfo.InvariantCulture); 64 | SetMappedAttributeValue(binaryValue); 65 | } 66 | } 67 | 68 | private string GetMappedAttributeValue([CallerMemberName] string propertyName = null) => 69 | Attributes.GetEntryValue(PropertyMap[propertyName ?? throw new ArgumentNullException(nameof(propertyName))]); 70 | 71 | private void SetMappedAttributeValue(string value, [CallerMemberName] string propertyName = null) 72 | { 73 | if (Attributes.SetEntryValue(PropertyMap[propertyName ?? throw new ArgumentNullException(nameof(propertyName))], value)) 74 | OnPropertyChanged(propertyName); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Unosquare.FFME.Windows.Sample/Foundation/DeferredAction.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Windows.Sample.Foundation 2 | { 3 | using System; 4 | using System.Threading; 5 | using System.Windows; 6 | 7 | /// 8 | /// Represents a timer which performs an action on the UI thread when time elapses. Rescheduling is supported. 9 | /// Original code from here: https://www.codeproject.com/Articles/32426/Deferring-ListCollectionView-filter-updates-for-a address. 10 | /// 11 | public sealed class DeferredAction : IDisposable 12 | { 13 | private readonly Timer DeferTimer; 14 | private bool IsDisposed; 15 | 16 | /// 17 | /// Initializes a new instance of the class. 18 | /// 19 | /// The action. 20 | private DeferredAction(Action action) 21 | { 22 | DeferTimer = new Timer(s => Application.Current?.Dispatcher?.Invoke(() => action(this))); 23 | } 24 | 25 | /// 26 | /// Creates a new DeferredAction. 27 | /// 28 | /// 29 | /// The action that will be deferred. It is not performed until after is called. 30 | /// 31 | /// The Deferred Action. 32 | public static DeferredAction Create(Action action) 33 | { 34 | if (action == null) 35 | throw new ArgumentNullException(nameof(action)); 36 | 37 | return new DeferredAction(action); 38 | } 39 | 40 | /// 41 | /// Defers performing the action until after time elapses. Repeated calls will reschedule the action 42 | /// if it has not already been performed. 43 | /// 44 | /// 45 | /// The amount of time to wait before performing the action. 46 | /// 47 | public void Defer(TimeSpan delay) 48 | { 49 | // Fire action when time elapses (with no subsequent calls). 50 | DeferTimer.Change(delay, Timeout.InfiniteTimeSpan); 51 | } 52 | 53 | #region IDisposable Implementation 54 | 55 | /// 56 | public void Dispose() 57 | { 58 | if (IsDisposed) return; 59 | IsDisposed = true; 60 | DeferTimer.Dispose(); 61 | } 62 | 63 | #endregion 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Unosquare.FFME.Windows.Sample/Foundation/FileInputStream.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Windows.Sample.Foundation; 2 | 3 | using Common; 4 | using FFmpeg.AutoGen; 5 | using System; 6 | using System.IO; 7 | using System.Runtime.InteropServices; 8 | 9 | /// 10 | /// 11 | /// Provides an example of a very simple custom input stream. 12 | /// 13 | /// 14 | public sealed unsafe class FileInputStream : IMediaInputStream 15 | { 16 | private readonly FileStream BackingStream; 17 | private readonly object ReadLock = new(); 18 | private readonly byte[] ReadBuffer; 19 | 20 | /// 21 | /// Initializes a new instance of the class. 22 | /// 23 | /// The path. 24 | public FileInputStream(string path) 25 | { 26 | var fullPath = Path.GetFullPath(path); 27 | BackingStream = File.OpenRead(fullPath); 28 | var uri = new Uri(fullPath); 29 | StreamUri = new Uri(uri.ToString().ReplaceOrdinal("file://", Scheme)); 30 | CanSeek = true; 31 | ReadBuffer = new byte[ReadBufferLength]; 32 | } 33 | 34 | /// 35 | /// The custom file scheme (URL prefix) including the :// sequence. 36 | /// 37 | public static string Scheme => "customfile://"; 38 | 39 | /// 40 | public Uri StreamUri { get; } 41 | 42 | /// 43 | public bool CanSeek { get; } 44 | 45 | /// 46 | public int ReadBufferLength => 1024 * 16; 47 | 48 | /// 49 | public InputStreamInitializing OnInitializing { get; } 50 | 51 | /// 52 | public InputStreamInitialized OnInitialized { get; } 53 | 54 | /// 55 | public void Dispose() 56 | { 57 | BackingStream?.Dispose(); 58 | } 59 | 60 | /// 61 | /// Reads from the underlying stream and writes up to bytes 62 | /// to the . Returns the number of bytes that were written. 63 | /// 64 | /// The opaque. 65 | /// The target buffer. 66 | /// Length of the target buffer. 67 | /// 68 | /// The number of bytes that have been read. 69 | /// 70 | public int Read(void* opaque, byte* targetBuffer, int targetBufferLength) 71 | { 72 | lock (ReadLock) 73 | { 74 | try 75 | { 76 | var readCount = BackingStream.Read(ReadBuffer, 0, ReadBuffer.Length); 77 | if (readCount > 0) 78 | Marshal.Copy(ReadBuffer, 0, (IntPtr)targetBuffer, readCount); 79 | else if (readCount == 0) 80 | return ffmpeg.AVERROR_EOF; 81 | 82 | return readCount; 83 | } 84 | catch (Exception) 85 | { 86 | return ffmpeg.AVERROR_EOF; 87 | } 88 | } 89 | } 90 | 91 | /// 92 | public long Seek(void* opaque, long offset, int whence) 93 | { 94 | lock (ReadLock) 95 | { 96 | try 97 | { 98 | return whence == ffmpeg.AVSEEK_SIZE ? 99 | BackingStream.Length : BackingStream.Seek(offset, SeekOrigin.Begin); 100 | } 101 | catch 102 | { 103 | return ffmpeg.AVERROR_EOF; 104 | } 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /Unosquare.FFME.Windows.Sample/Foundation/ReactiveExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Windows.Sample.Foundation 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.ComponentModel; 6 | 7 | /// 8 | /// A very simple set of extensions to more easily handle UI state changes based on 9 | /// notification properties. The main idea is to bind to the PropertyChanged event 10 | /// for a publisher only one and add a set of callbacks with matching property names 11 | /// when the publisher raises the event. 12 | /// 13 | internal static class ReactiveExtensions 14 | { 15 | /// 16 | /// Contains a list of subscriptions Subscriptions[Publisher][PropertyName].List of subscriber-action pairs. 17 | /// 18 | private static readonly Dictionary Subscriptions 19 | = new Dictionary(); 20 | 21 | private static readonly object SyncLock = new object(); 22 | 23 | /// 24 | /// Specifies a callback when properties change. 25 | /// 26 | /// The publisher. 27 | /// The callback. 28 | /// The property names. 29 | public static void WhenChanged(this INotifyPropertyChanged publisher, Action callback, params string[] propertyNames) 30 | { 31 | var bindPropertyChanged = false; 32 | 33 | lock (SyncLock) 34 | { 35 | // Create the subscription set for the publisher if it does not exist. 36 | if (Subscriptions.ContainsKey(publisher) == false) 37 | { 38 | Subscriptions[publisher] = new SubscriptionSet(); 39 | 40 | // if it did not exist before, we need to bind to the 41 | // PropertyChanged event of the publisher. 42 | bindPropertyChanged = true; 43 | } 44 | 45 | foreach (var propertyName in propertyNames) 46 | { 47 | // Create the set of callback references for the publisher's property if it does not exist. 48 | if (Subscriptions[publisher].ContainsKey(propertyName) == false) 49 | Subscriptions[publisher][propertyName] = new CallbackList(); 50 | 51 | // Add the callback for the publisher's property changed 52 | Subscriptions[publisher][propertyName].Add(callback); 53 | } 54 | } 55 | 56 | // Make an initial call 57 | callback(); 58 | 59 | // No need to bind to the PropertyChanged event if we are already bound to it. 60 | if (bindPropertyChanged == false) 61 | return; 62 | 63 | // Finally, bind to property changed 64 | publisher.PropertyChanged += (s, e) => 65 | { 66 | CallbackList propertyCallbacks = null; 67 | 68 | lock (SyncLock) 69 | { 70 | // we don't need to perform any action if there are no subscriptions to 71 | // this property name. 72 | if (Subscriptions[publisher].ContainsKey(e.PropertyName) == false) 73 | return; 74 | 75 | // Get the list of alive subscriptions for this property name 76 | propertyCallbacks = Subscriptions[publisher][e.PropertyName]; 77 | } 78 | 79 | // Call the subscription's callbacks 80 | foreach (var propertyCallback in propertyCallbacks) 81 | { 82 | // if the subscription is alive, invoke the matching action 83 | propertyCallback.Invoke(); 84 | } 85 | }; 86 | } 87 | 88 | internal sealed class SubscriptionSet : Dictionary { } 89 | 90 | internal sealed class CallbackList : List 91 | { 92 | public CallbackList() 93 | : base(32) 94 | { 95 | // placeholder 96 | } 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /Unosquare.FFME.Windows.Sample/Foundation/WindowStatus.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Windows.Sample.Foundation 2 | { 3 | using System; 4 | using System.Runtime.InteropServices; 5 | using System.Windows; 6 | 7 | /// 8 | /// Represents the general state of a Window. 9 | /// 10 | public sealed class WindowStatus 11 | { 12 | /// 13 | /// Gets or sets the state of the window. 14 | /// 15 | /// 16 | /// The state of the window. 17 | /// 18 | public WindowState WindowState { get; set; } 19 | 20 | /// 21 | /// Gets or sets the top. 22 | /// 23 | /// 24 | /// The top. 25 | /// 26 | public double Top { get; set; } 27 | 28 | /// 29 | /// Gets or sets the left. 30 | /// 31 | /// 32 | /// The left. 33 | /// 34 | public double Left { get; set; } 35 | 36 | /// 37 | /// Gets or sets the window style. 38 | /// 39 | /// 40 | /// The window style. 41 | /// 42 | public WindowStyle WindowStyle { get; set; } 43 | 44 | /// 45 | /// Gets or sets a value indicating whether this is topmost. 46 | /// 47 | /// 48 | /// true if topmost; otherwise, false. 49 | /// 50 | public bool Topmost { get; set; } 51 | 52 | /// 53 | /// Gets or sets the resize mode. 54 | /// 55 | /// 56 | /// The resize mode. 57 | /// 58 | public ResizeMode ResizeMode { get; set; } 59 | 60 | /// 61 | /// Disables the display timeout. 62 | /// 63 | public static void DisableDisplayTimeout() 64 | { 65 | NativeMethods.SetThreadExecutionState( 66 | NativeMethods.EXECUTION_STATE.ES_DISPLAY_REQUIRED | NativeMethods.EXECUTION_STATE.ES_CONTINUOUS); 67 | } 68 | 69 | /// 70 | /// Allows the display timeout. 71 | /// 72 | public static void EnableDisplayTimeout() 73 | { 74 | NativeMethods.SetThreadExecutionState( 75 | NativeMethods.EXECUTION_STATE.ES_CONTINUOUS); 76 | } 77 | 78 | /// 79 | /// Captures the specified window object. 80 | /// 81 | /// The window state object. 82 | public void Capture(Window w) 83 | { 84 | if (w == null) throw new ArgumentNullException(nameof(w)); 85 | 86 | WindowState = w.WindowState; 87 | Top = w.Top; 88 | Left = w.Left; 89 | WindowStyle = w.WindowStyle; 90 | Topmost = w.Topmost; 91 | ResizeMode = w.ResizeMode; 92 | } 93 | 94 | /// 95 | /// Applies the specified window state. 96 | /// 97 | /// The w. 98 | public void Apply(Window w) 99 | { 100 | if (w == null) throw new ArgumentNullException(nameof(w)); 101 | 102 | w.WindowState = WindowState; 103 | w.Top = Top; 104 | w.Left = Left; 105 | w.WindowStyle = WindowStyle; 106 | w.Topmost = Topmost; 107 | w.ResizeMode = ResizeMode; 108 | } 109 | 110 | /// 111 | /// Provides access to disable or enable screen timeout. Original idea taken from: 112 | /// http://www.blackwasp.co.uk/DisableScreensaver.aspx address. 113 | /// 114 | private static class NativeMethods 115 | { 116 | [Flags] 117 | public enum EXECUTION_STATE : uint 118 | { 119 | ES_AWAYMODE_REQUIRED = 0x00000040, 120 | ES_CONTINUOUS = 0x80000000, 121 | ES_DISPLAY_REQUIRED = 0x00000002, 122 | ES_SYSTEM_REQUIRED = 0x00000001 123 | } 124 | 125 | [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] 126 | public static extern EXECUTION_STATE SetThreadExecutionState(EXECUTION_STATE esFlags); 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /Unosquare.FFME.Windows.Sample/GlobalSuppressions.cs: -------------------------------------------------------------------------------- 1 | // This file is used by Code Analysis to maintain SuppressMessage 2 | // attributes that are applied to this project. 3 | // Project-level suppressions either have no target or are given 4 | // a specific target and scoped to a namespace, type, member, etc. 5 | 6 | using System.Diagnostics.CodeAnalysis; 7 | 8 | [assembly: SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1010:Opening square brackets should be spaced correctly", Justification = "Better formatting", Scope = "namespaceanddescendants", Target = "~N:Unosquare.FFME")] 9 | [assembly: SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1010:Opening square brackets should be spaced correctly", Justification = "Better formatting", Scope = "member", Target = "~F:Unosquare.FFME.Windows.Sample.MainWindow.TogglePlayPauseKeys")] 10 | -------------------------------------------------------------------------------- /Unosquare.FFME.Windows.Sample/MainWindow.xaml: -------------------------------------------------------------------------------- 1 |  11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 35 | 36 | 40 | 41 | 45 | 46 | 47 | 50 | 51 | 54 | 55 | 58 | 59 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /Unosquare.FFME.Windows.Sample/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "Unosquare.FFME.Windows.Sample": { 4 | "commandName": "Project", 5 | "nativeDebugging": false 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /Unosquare.FFME.Windows.Sample/ViewModels/AttachedViewModel.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Windows.Sample.ViewModels 2 | { 3 | using Common; 4 | 5 | /// 6 | /// A base class for Root VM-attached view models. 7 | /// 8 | /// 9 | public abstract class AttachedViewModel : ViewModelBase 10 | { 11 | /// 12 | /// Initializes a new instance of the class. 13 | /// 14 | /// The root. 15 | protected AttachedViewModel(RootViewModel root) 16 | { 17 | Root = root; 18 | } 19 | 20 | /// 21 | /// Gets the root VM this object belongs to. 22 | /// 23 | public RootViewModel Root { get; } 24 | 25 | /// 26 | /// Called by the root ViewModel when the application is loaded and fully available. 27 | /// 28 | internal virtual void OnApplicationLoaded() 29 | { 30 | // placeholder 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Unosquare.FFME.Windows.Sample/ffmpeg.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unosquare/ffmediaelement/72f2c791dfbb74234b8976ef5ae43f2dc46f678e/Unosquare.FFME.Windows.Sample/ffmpeg.ico -------------------------------------------------------------------------------- /Unosquare.FFME.Windows/Common/AudioDeviceInfo.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Common 2 | { 3 | using System; 4 | 5 | /// 6 | /// Represents a device identifier. 7 | /// 8 | /// The type of the device identifier. 9 | public class AudioDeviceInfo 10 | where T : struct 11 | { 12 | /// 13 | /// Initializes a new instance of the class. 14 | /// 15 | /// The device identifier. 16 | /// The name. 17 | /// The provider. 18 | /// if set to true [is default]. 19 | /// The tag. 20 | internal AudioDeviceInfo(T deviceId, string name, string provider, bool isDefault, string tag) 21 | { 22 | DeviceId = deviceId; 23 | Name = name; 24 | Provider = provider; 25 | Tag = tag; 26 | IsDefault = isDefault; 27 | } 28 | 29 | /// 30 | /// Gets the device identifier. 31 | /// 32 | public T DeviceId { get; } 33 | 34 | /// 35 | /// Gets the name. 36 | /// 37 | public string Name { get; } 38 | 39 | /// 40 | /// Gets the provider. 41 | /// 42 | public string Provider { get; } 43 | 44 | /// 45 | /// Gets the tag. 46 | /// 47 | public string Tag { get; } 48 | 49 | /// 50 | /// Gets a value indicating whether this device is the default. 51 | /// 52 | public bool IsDefault { get; } 53 | 54 | /// 55 | /// Returns a that represents this instance. 56 | /// 57 | /// 58 | /// A that represents this instance. 59 | /// 60 | public override string ToString() 61 | { 62 | return $"{Provider}: {Name}"; 63 | } 64 | } 65 | 66 | /// 67 | /// Represents information about a legacy WinMM audio device. 68 | /// 69 | public sealed class LegacyAudioDeviceInfo : AudioDeviceInfo 70 | { 71 | /// 72 | /// Initializes a new instance of the class. 73 | /// 74 | /// The device identifier. 75 | /// The name. 76 | /// The provider. 77 | /// if set to true [is default]. 78 | /// The tag. 79 | internal LegacyAudioDeviceInfo(int deviceId, string name, string provider, bool isDefault, string tag) 80 | : base(deviceId, name, provider, isDefault, tag) 81 | { 82 | // placeholder 83 | } 84 | } 85 | 86 | /// 87 | /// Represents information about a DirectSound audio device. 88 | /// 89 | public sealed class DirectSoundDeviceInfo : AudioDeviceInfo 90 | { 91 | /// 92 | /// Initializes a new instance of the class. 93 | /// 94 | /// The device identifier. 95 | /// The name. 96 | /// The provider. 97 | /// if set to true [is default]. 98 | /// The tag. 99 | internal DirectSoundDeviceInfo(Guid deviceId, string name, string provider, bool isDefault, string tag) 100 | : base(deviceId, name, provider, isDefault, tag) 101 | { 102 | // placeholder 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /Unosquare.FFME.Windows/Common/LegacyAudioException.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Common; 2 | 3 | using System; 4 | 5 | /// 6 | /// 7 | /// An exception representing an error in Windows Multimedia Audio. 8 | /// 9 | [Serializable] 10 | public sealed class LegacyAudioException : MediaContainerException 11 | { 12 | /// 13 | /// Initializes a new instance of the class. 14 | /// 15 | /// The result returned by the Windows API call. 16 | /// The name of the Windows API that failed. 17 | public LegacyAudioException(LegacyAudioResult result, string functionName) 18 | : base(ErrorMessage(result, functionName)) 19 | { 20 | Result = result; 21 | FunctionName = functionName; 22 | } 23 | 24 | /// 25 | /// Initializes a new instance of the class. 26 | /// 27 | public LegacyAudioException() 28 | : this(LegacyAudioResult.UnspecifiedError, $"{nameof(LegacyAudioException)}.ctor()") 29 | { 30 | // placeholder 31 | } 32 | 33 | /// 34 | /// Initializes a new instance of the class. 35 | /// 36 | /// The error message. 37 | public LegacyAudioException(string message) 38 | : this(LegacyAudioResult.UnspecifiedError, $"{nameof(LegacyAudioException)}.ctor(): {message}") 39 | { 40 | } 41 | 42 | /// 43 | /// Initializes a new instance of the class. 44 | /// 45 | /// The error message. 46 | /// The inner exception. 47 | public LegacyAudioException(string message, Exception innerException) 48 | : base(message, innerException) 49 | { 50 | Result = LegacyAudioResult.UnspecifiedError; 51 | FunctionName = $"{nameof(LegacyAudioException)}.ctor()"; 52 | } 53 | 54 | /// 55 | /// Gets the name of the function that failed. 56 | /// 57 | public string FunctionName { get; } 58 | 59 | /// 60 | /// Gets the Windows API result code. 61 | /// 62 | public LegacyAudioResult Result { get; } 63 | 64 | /// 65 | /// Helper function to automatically raise an exception on failure. 66 | /// 67 | /// The result of the API call. 68 | /// The API function name. 69 | internal static void Try(LegacyAudioResult result, string function) 70 | { 71 | if (result != LegacyAudioResult.NoError) 72 | throw new LegacyAudioException(result, function); 73 | } 74 | 75 | /// 76 | /// Creates an error message base don an error result. 77 | /// 78 | /// The result. 79 | /// The function. 80 | /// A descriptive error message. 81 | private static string ErrorMessage(LegacyAudioResult result, string function) => $"{result} calling {function}"; 82 | } 83 | -------------------------------------------------------------------------------- /Unosquare.FFME.Windows/Common/LegacyAudioResult.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Common 2 | { 3 | /// 4 | /// Windows multimedia error codes from mmsystem.h. 5 | /// 6 | public enum LegacyAudioResult 7 | { 8 | /// no error. 9 | NoError = 0, 10 | 11 | /// unspecified error. 12 | UnspecifiedError = 1, 13 | 14 | /// device ID out of range. 15 | BadDeviceId = 2, 16 | 17 | /// driver failed enable. 18 | NotEnabled = 3, 19 | 20 | /// device already allocated. 21 | AlreadyAllocated = 4, 22 | 23 | /// device handle is invalid. 24 | InvalidHandle = 5, 25 | 26 | /// no device driver present. 27 | NoDriver = 6, 28 | 29 | /// memory allocation error. 30 | MemoryAllocationError = 7, 31 | 32 | /// function isn't supported. 33 | NotSupported = 8, 34 | 35 | /// error value out of range. 36 | BadErrorNumber = 9, 37 | 38 | /// invalid flag passed. 39 | InvalidFlag = 10, 40 | 41 | /// invalid parameter passed. 42 | InvalidParameter = 11, 43 | 44 | /// handle being used simultaneously on another thread (eg callback). 45 | HandleBusy = 12, 46 | 47 | /// specified alias not found. 48 | InvalidAlias = 13, 49 | 50 | /// bad registry database. 51 | BadRegistryDatabase = 14, 52 | 53 | /// registry key not found. 54 | RegistryKeyNotFound = 15, 55 | 56 | /// registry read error. 57 | RegistryReadError = 16, 58 | 59 | /// registry write error. 60 | RegistryWriteError = 17, 61 | 62 | /// registry delete error. 63 | RegistryDeleteError = 18, 64 | 65 | /// registry value not found. 66 | RegistryValueNotFound = 19, 67 | 68 | /// driver does not call DriverCallback. 69 | NoDriverCallback = 20, 70 | 71 | /// more data to be returned. 72 | MoreData = 21, 73 | 74 | /// unsupported wave format. 75 | WaveBadFormat = 32, 76 | 77 | /// still something playing. 78 | WaveStillPlaying = 33, 79 | 80 | /// header not prepared. 81 | WaveHeaderUnprepared = 34, 82 | 83 | /// device is synchronous. 84 | WaveSync = 35, 85 | 86 | // ACM error codes, found in msacm.h 87 | 88 | /// Conversion not possible. 89 | AcmNotPossible = 512, 90 | 91 | /// Busy. 92 | AcmBusy = 513, 93 | 94 | /// Header Unprepared. 95 | AcmHeaderUnprepared = 514, 96 | 97 | /// Cancelled. 98 | AcmCancelled = 515, 99 | 100 | // Mixer error codes, found in mmresult.h 101 | 102 | /// invalid line. 103 | MixerInvalidLine = 1024, 104 | 105 | /// invalid control. 106 | MixerInvalidControl = 1025, 107 | 108 | /// invalid value. 109 | MixerInvalidValue = 1026 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /Unosquare.FFME.Windows/Common/RendererOptions.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Common 2 | { 3 | /// 4 | /// Provides access to various internal media renderer options. 5 | /// 6 | public sealed class RendererOptions 7 | { 8 | /// 9 | /// By default, the audio renderer will skip or wait for samples to 10 | /// synchronize to video. 11 | /// 12 | public bool AudioDisableSync { get; set; } 13 | 14 | /// 15 | /// Gets or sets the DirectSound device identifier. It is the default playback device by default. 16 | /// Only valid if is set to false which is the default. 17 | /// 18 | public DirectSoundDeviceInfo DirectSoundDevice { get; set; } = Library.DefaultDirectSoundDevice; 19 | 20 | /// 21 | /// Gets or sets the wave device identifier. -1 is the default playback device. 22 | /// Only valid if is set to true. 23 | /// 24 | public LegacyAudioDeviceInfo LegacyAudioDevice { get; set; } = Library.DefaultLegacyAudioDevice; 25 | 26 | /// 27 | /// Gets or sets a value indicating whether the legacy MME (WinMM) should be used 28 | /// as an audio output device as opposed to DirectSound. This defaults to false. 29 | /// 30 | public bool UseLegacyAudioOut { get; set; } 31 | 32 | /// 33 | /// Gets or sets the frame refresh rate limit for the video renderer. 34 | /// Defaults to 0 and means no limit. Units are in frames per second. 35 | /// 36 | public int VideoRefreshRateLimit { get; set; } 37 | 38 | /// 39 | /// Gets or sets which image type is used for the video renderer. 40 | /// Use WriteableBitmap for tear-free scenarios, or use the InteropBitmap for 41 | /// faster, lower CPU usage. InteropBitmap might introduce some tearing. 42 | /// 43 | public VideoRendererImageType VideoImageType { get; set; } = VideoRendererImageType.WriteableBitmap; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Unosquare.FFME.Windows/Common/RenderingAudioEventArgs.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Common 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | 6 | /// 7 | /// Provides the audio samples rendering payload as event arguments. 8 | /// 9 | /// 10 | public sealed class RenderingAudioEventArgs : RenderingEventArgs 11 | { 12 | /// 13 | /// Initializes a new instance of the class. 14 | /// 15 | /// The buffer. 16 | /// The length. 17 | /// The latency between the buffer position and the real-time playback clock. 18 | /// The engine. 19 | /// The stream. 20 | /// The start time. 21 | /// The duration. 22 | /// The clock. 23 | /// The unadjusted PTS of the frame in stream Time Base units. 24 | internal RenderingAudioEventArgs( 25 | byte[] buffer, int length, TimeSpan latency, IMediaEngineState engineState, StreamInfo stream, TimeSpan startTime, TimeSpan duration, TimeSpan clock, long pts) 26 | : base(engineState, stream, startTime, duration, clock, pts) 27 | { 28 | Buffer = buffer; 29 | BufferLength = length; 30 | SampleRate = Constants.AudioSampleRate; 31 | ChannelCount = Constants.AudioChannelCount; 32 | BitsPerSample = Constants.AudioBitsPerSample; 33 | Latency = latency; 34 | } 35 | 36 | /// 37 | /// Gets the latency between the audio buffer position and the real-time playback clock. 38 | /// 39 | public TimeSpan Latency { get; } 40 | 41 | /// 42 | /// Gets a the raw data buffer going into the audio device. 43 | /// Samples are provided in PCM 16-bit signed, interleaved stereo. 44 | /// 45 | public IReadOnlyList Buffer { get; } 46 | 47 | /// 48 | /// Gets the length in bytes of the samples buffer. 49 | /// 50 | public int BufferLength { get; } 51 | 52 | /// 53 | /// Gets the number of samples in 1 second. 54 | /// 55 | public int SampleRate { get; } 56 | 57 | /// 58 | /// Gets the number of channels. 59 | /// 60 | public int ChannelCount { get; } 61 | 62 | /// 63 | /// Gets the number of bits per sample. 64 | /// 65 | public int BitsPerSample { get; } 66 | 67 | /// 68 | /// Gets the number of samples in the buffer for all channels. 69 | /// 70 | public int Samples => BufferLength / (BitsPerSample / 8); 71 | 72 | /// 73 | /// Gets the number of samples in the buffer per channel. 74 | /// 75 | public int SamplesPerChannel => Samples / ChannelCount; 76 | 77 | /// 78 | /// Gets a the raw data buffer going into the audio device. 79 | /// Samples are provided in PCM 16-bit signed, interleaved stereo. 80 | /// 81 | /// The buffer data as an array. 82 | public byte[] GetBufferData() => Buffer as byte[]; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Unosquare.FFME.Windows/Common/RenderingEventArgs.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Common 2 | { 3 | using System; 4 | 5 | /// 6 | /// A base class to represent media block 7 | /// rendering event arguments. 8 | /// 9 | /// 10 | public abstract class RenderingEventArgs : EventArgs 11 | { 12 | /// 13 | /// Initializes a new instance of the class. 14 | /// 15 | /// The media engine state. 16 | /// The stream. 17 | /// The position. 18 | /// The duration. 19 | /// The clock. 20 | /// The original unadjusted PTS of the frame. 21 | protected RenderingEventArgs(IMediaEngineState engineState, StreamInfo stream, TimeSpan startTime, TimeSpan duration, TimeSpan clock, long pts) 22 | { 23 | EngineState = engineState; 24 | StartTime = startTime; 25 | Duration = duration; 26 | Clock = clock; 27 | Stream = stream; 28 | PresentationTime = pts; 29 | } 30 | 31 | /// 32 | /// Provides access to the underlying media engine state. 33 | /// 34 | public IMediaEngineState EngineState { get; } 35 | 36 | /// 37 | /// Provides Stream Information coming from the media container. 38 | /// 39 | public StreamInfo Stream { get; } 40 | 41 | /// 42 | /// Gets the clock position at which the media 43 | /// was called for rendering. 44 | /// 45 | public TimeSpan Clock { get; } 46 | 47 | /// 48 | /// Gets the starting time at which this media 49 | /// has to be presented. 50 | /// 51 | public TimeSpan StartTime { get; } 52 | 53 | /// 54 | /// Gets how long this media has to be presented. 55 | /// 56 | public TimeSpan Duration { get; } 57 | 58 | /// 59 | /// Gets the unadjusted, original presentation timestamp (PTS) of the frame in 60 | /// units. May return 61 | /// when invialid, not applicable (as in continuous audio rendering), or unavailable. 62 | /// 63 | public long PresentationTime { get; } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Unosquare.FFME.Windows/Common/RenderingSubtitlesEventArgs.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Common 2 | { 3 | using FFmpeg.AutoGen; 4 | using System; 5 | using System.Collections.Generic; 6 | 7 | /// 8 | /// Provides the subtitles rendering payload as event arguments. 9 | /// 10 | /// 11 | public sealed class RenderingSubtitlesEventArgs : RenderingEventArgs 12 | { 13 | /// 14 | /// Initializes a new instance of the class. 15 | /// 16 | /// The text. 17 | /// The original text. 18 | /// The format. 19 | /// The engine. 20 | /// The stream. 21 | /// The start time. 22 | /// The duration. 23 | /// The clock. 24 | /// The unadjusted PTS of the frame given in stream Time Base units. 25 | internal RenderingSubtitlesEventArgs( 26 | IList text, 27 | IList originalText, 28 | AVSubtitleType format, 29 | IMediaEngineState engineState, 30 | StreamInfo stream, 31 | TimeSpan startTime, 32 | TimeSpan duration, 33 | TimeSpan clock, 34 | long pts) 35 | : base(engineState, stream, startTime, duration, clock, pts) 36 | { 37 | Text = text; 38 | Format = format; 39 | OriginalText = originalText; 40 | } 41 | 42 | /// 43 | /// Gets the text stripped out of ASS or SRT formatting. 44 | /// This is what the default subtitle renderer will display 45 | /// on the screen. 46 | /// 47 | public IList Text { get; } 48 | 49 | /// 50 | /// Gets the text as originally decoded including 51 | /// all markup and formatting. 52 | /// 53 | public IList OriginalText { get; } 54 | 55 | /// 56 | /// Gets the type of subtitle format the original 57 | /// subtitle text is in. 58 | /// 59 | public AVSubtitleType Format { get; } 60 | 61 | /// 62 | /// When set to true, clears the current subtitle and 63 | /// prevents the subtitle block from being rendered. 64 | /// 65 | public bool Cancel { get; set; } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Unosquare.FFME.Windows/Common/RenderingVideoEventArgs.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Common 2 | { 3 | using ClosedCaptions; 4 | using FFmpeg.AutoGen; 5 | using System; 6 | using System.Collections.Generic; 7 | 8 | /// 9 | /// The video rendering event arguments. 10 | /// 11 | /// 12 | public sealed class RenderingVideoEventArgs : RenderingEventArgs 13 | { 14 | /// 15 | /// Initializes a new instance of the class. 16 | /// 17 | /// The bitmap. 18 | /// The closed captions. 19 | /// The smtpe time code. 20 | /// The picture number. 21 | /// The origination picture type. 22 | /// The engine. 23 | /// The stream. 24 | /// The start time. 25 | /// The duration. 26 | /// The clock. 27 | /// The unadjusted, original presentation timestamp (PTS) of the frame. 28 | internal RenderingVideoEventArgs( 29 | BitmapDataBuffer bitmap, 30 | IReadOnlyList closedCaptions, 31 | string smtpeTimeCode, 32 | long pictureNumber, 33 | AVPictureType pictureType, 34 | IMediaEngineState engineState, 35 | StreamInfo stream, 36 | TimeSpan startTime, 37 | TimeSpan duration, 38 | TimeSpan clock, 39 | long pts) 40 | : base(engineState, stream, startTime, duration, clock, pts) 41 | { 42 | PictureNumber = pictureNumber; 43 | Bitmap = bitmap; 44 | SmtpeTimeCode = smtpeTimeCode; 45 | ClosedCaptions = closedCaptions; 46 | PictureType = pictureType; 47 | } 48 | 49 | /// 50 | /// Gets the writable bitmap filled with the video frame pixels. 51 | /// Feel free to capture or change this buffer. 52 | /// 53 | public BitmapDataBuffer Bitmap { get; } 54 | 55 | /// 56 | /// Gets the closed caption decoded packets. 57 | /// 58 | public IReadOnlyList ClosedCaptions { get; } 59 | 60 | /// 61 | /// Gets the display picture number (frame number). 62 | /// If not set by the decoder, this attempts to obtain it by dividing the start time by the 63 | /// frame duration. 64 | /// 65 | public long PictureNumber { get; } 66 | 67 | /// 68 | /// Gets the SMTPE time code. 69 | /// 70 | public string SmtpeTimeCode { get; } 71 | 72 | /// 73 | /// Gets the picture type. 74 | /// 75 | public AVPictureType PictureType { get; } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Unosquare.FFME.Windows/Common/VideoRendererImageType.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Common 2 | { 3 | /// 4 | /// Enumerates the different Video renderer image types. 5 | /// 6 | public enum VideoRendererImageType 7 | { 8 | /// 9 | /// Uses a tear-free WriteableBitmap. 10 | /// 11 | WriteableBitmap, 12 | 13 | /// 14 | /// Uses the faster, non tear-free InteropBitmap. 15 | /// 16 | InteropBitmap 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Unosquare.FFME.Windows/GlobalSuppressions.cs: -------------------------------------------------------------------------------- 1 | // This file is used by Code Analysis to maintain SuppressMessage 2 | // attributes that are applied to this project. 3 | // Project-level suppressions either have no target or are given 4 | // a specific target and scoped to a namespace, type, member, etc. 5 | 6 | using System.Diagnostics.CodeAnalysis; 7 | 8 | [assembly: SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1010:Opening square brackets should be spaced correctly", Justification = "Better formatting", Scope = "namespaceanddescendants", Target = "~N:Unosquare.FFME")] 9 | [assembly: SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1010:Opening square brackets should be spaced correctly", Justification = "Better formatting", Scope = "namespaceanddescendants", Target = "~N:FFmpeg")] -------------------------------------------------------------------------------- /Unosquare.FFME.Windows/Library.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME 2 | { 3 | using Common; 4 | using Rendering.Wave; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.ComponentModel; 8 | using System.Windows; 9 | 10 | public static partial class Library 11 | { 12 | /// 13 | /// Gets or sets a value indicating whether the video visualization control 14 | /// creates its own dispatcher thread to handle rendering of video frames. 15 | /// This is an experimental feature and it is useful when creating video walls. 16 | /// For example if you want to display multiple videos at a time and don't want to 17 | /// use time from the main UI thread. This feature is only valid if we are in 18 | /// a WPF context. 19 | /// 20 | public static bool EnableWpfMultiThreadedVideo { get; set; } 21 | 22 | /// 23 | /// The default DirectSound device. 24 | /// 25 | public static DirectSoundDeviceInfo DefaultDirectSoundDevice { get; } = new DirectSoundDeviceInfo( 26 | DirectSoundPlayer.DefaultPlaybackDeviceId, nameof(DefaultDirectSoundDevice), nameof(DirectSoundPlayer), true, Guid.Empty.ToString()); 27 | 28 | /// 29 | /// The default Windows Multimeda Extensions Legacy Audio Device. 30 | /// 31 | public static LegacyAudioDeviceInfo DefaultLegacyAudioDevice { get; } = new LegacyAudioDeviceInfo( 32 | -1, nameof(DefaultLegacyAudioDevice), nameof(LegacyAudioPlayer), true, Guid.Empty.ToString()); 33 | 34 | /// 35 | /// Determines if the control library is currently in design-time mode (as opposed to run-time). 36 | /// 37 | internal static bool IsInDesignMode => 38 | (bool)DesignerProperties.IsInDesignModeProperty.GetMetadata(typeof(DependencyObject)).DefaultValue; 39 | 40 | /// 41 | /// Enumerates the DirectSound devices. 42 | /// 43 | /// The available DirectSound devices. 44 | public static IEnumerable EnumerateDirectSoundDevices() 45 | { 46 | var devices = DirectSoundPlayer.EnumerateDevices(); 47 | var result = new List(16) { DefaultDirectSoundDevice }; 48 | 49 | foreach (var device in devices) 50 | { 51 | result.Add(new DirectSoundDeviceInfo( 52 | device.Guid, device.Description, nameof(DirectSoundPlayer), false, device.ModuleName)); 53 | } 54 | 55 | return result; 56 | } 57 | 58 | /// 59 | /// Enumerates the (Legacy) Windows Multimedia Extensions devices. 60 | /// 61 | /// The available MME devices. 62 | public static IEnumerable EnumerateLegacyAudioDevices() 63 | { 64 | var devices = LegacyAudioPlayer.EnumerateDevices(); 65 | var result = new List(16) { DefaultLegacyAudioDevice }; 66 | 67 | for (var deviceId = 0; deviceId < devices.Count; deviceId++) 68 | { 69 | var device = devices[deviceId]; 70 | result.Add(new LegacyAudioDeviceInfo( 71 | deviceId, device.ProductName, nameof(LegacyAudioPlayer), false, device.ProductGuid.ToString())); 72 | } 73 | 74 | return result; 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Unosquare.FFME.Windows/MediaElement.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unosquare/ffmediaelement/72f2c791dfbb74234b8976ef5ae43f2dc46f678e/Unosquare.FFME.Windows/MediaElement.png -------------------------------------------------------------------------------- /Unosquare.FFME.Windows/Platform/GuiContextType.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Platform 2 | { 3 | /// 4 | /// Enumerates GUI Context Types. 5 | /// 6 | internal enum GuiContextType 7 | { 8 | /// 9 | /// An invalid GUI context (console applications). 10 | /// 11 | None, 12 | 13 | /// 14 | /// A WPF GUI context (i.e. has dispatcher and is not Windows Forms). 15 | /// 16 | WPF, 17 | 18 | /// 19 | /// A Windows Forms GUI Context. 20 | /// 21 | WinForms 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Unosquare.FFME.Windows/Platform/MediaConnector.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Platform 2 | { 3 | using Common; 4 | using Engine; 5 | using Rendering; 6 | using System; 7 | 8 | internal partial class MediaConnector 9 | { 10 | /// 11 | public IMediaRenderer CreateRenderer(MediaType mediaType, MediaEngine mediaCore) 12 | { 13 | switch (mediaType) 14 | { 15 | case MediaType.Audio: 16 | return new AudioRenderer(mediaCore); 17 | case MediaType.Video: 18 | return ((mediaCore.Parent as MediaElement)?.RendererOptions.VideoImageType ?? VideoRendererImageType.WriteableBitmap) switch 19 | { 20 | VideoRendererImageType.WriteableBitmap => new VideoRenderer(mediaCore), 21 | VideoRendererImageType.InteropBitmap => new InteropVideoRenderer(mediaCore), 22 | _ => new VideoRenderer(mediaCore), 23 | }; 24 | case MediaType.Subtitle: 25 | return new SubtitleRenderer(mediaCore); 26 | default: 27 | throw new NotSupportedException($"No suitable renderer for Media Type '{mediaType}'"); 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Unosquare.FFME.Windows/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace Unosquare.FFME.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Unosquare.FFME.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | 63 | /// 64 | /// Looks up a localized resource of type System.Drawing.Bitmap. 65 | /// 66 | internal static System.Drawing.Bitmap FFmpegMediaElementBackground { 67 | get { 68 | object obj = ResourceManager.GetObject("FFmpegMediaElementBackground", resourceCulture); 69 | return ((System.Drawing.Bitmap)(obj)); 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Unosquare.FFME.Windows/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "Unosquare.FFME.Windows": { 4 | "commandName": "Project", 5 | "nativeDebugging": false 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /Unosquare.FFME.Windows/Rendering/ClosedCaptionsCell.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Rendering 2 | { 3 | /// 4 | /// Represents a grid cell state containing a Display and a back-buffer 5 | /// of a character and its properties. 6 | /// 7 | internal sealed class ClosedCaptionsCell 8 | { 9 | /// 10 | /// Initializes a new instance of the class. 11 | /// 12 | /// Index of the row. 13 | /// Index of the column. 14 | public ClosedCaptionsCell(int rowIndex, int columnIndex) 15 | { 16 | RowIndex = rowIndex; 17 | ColumnIndex = columnIndex; 18 | } 19 | 20 | /// 21 | /// Gets the index of the row this cell belongs to. 22 | /// 23 | public int RowIndex { get; } 24 | 25 | /// 26 | /// Gets the index of the column this cell belongs to. 27 | /// 28 | public int ColumnIndex { get; } 29 | 30 | /// 31 | /// Gets or sets the character. 32 | /// 33 | public ClosedCaptionsCellState Display { get; } = new ClosedCaptionsCellState(); 34 | 35 | /// 36 | /// Gets or sets the buffered character. 37 | /// 38 | public ClosedCaptionsCellState Buffer { get; } = new ClosedCaptionsCellState(); 39 | 40 | /// 41 | /// Copies the buffer content on to the display content 42 | /// and clears the buffer content. 43 | /// 44 | public void DisplayBuffer() 45 | { 46 | Display.CopyFrom(Buffer); 47 | Buffer.Clear(); 48 | } 49 | 50 | /// 51 | /// Resets the entire state and contents of this cell. 52 | /// 53 | public void Reset() 54 | { 55 | Display.Clear(); 56 | Buffer.Clear(); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Unosquare.FFME.Windows/Rendering/ClosedCaptionsCellState.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Rendering 2 | { 3 | using System.Globalization; 4 | using System.Windows.Media; 5 | 6 | /// 7 | /// Contains single-character text and its attributes. 8 | /// 9 | internal sealed class ClosedCaptionsCellState 10 | { 11 | /// 12 | /// Gets the character as a string. 13 | /// 14 | public string Text => Character == default 15 | ? string.Empty 16 | : Character.ToString(CultureInfo.InvariantCulture); 17 | 18 | /// 19 | /// Gets or sets the character. 20 | /// 21 | public char Character { get; set; } 22 | 23 | /// 24 | /// Gets or sets the opacity (from 0.0 to 1.0 opaque). 25 | /// 26 | public double Opacity { get; set; } = 0.80; 27 | 28 | /// 29 | /// Gets or sets the foreground text color. 30 | /// 31 | public Brush Foreground { get; set; } = Brushes.White; 32 | 33 | /// 34 | /// Gets or sets the background color. 35 | /// 36 | public Brush Background { get; set; } = Brushes.Black; 37 | 38 | /// 39 | /// Gets or sets a value indicating whether this instance is underline. 40 | /// 41 | public bool IsUnderlined { get; set; } 42 | 43 | /// 44 | /// Gets or sets a value indicating whether this instance is italics. 45 | /// 46 | public bool IsItalics { get; set; } 47 | 48 | /// 49 | /// Copies text and attributes from another cell state content. 50 | /// 51 | /// The cell. 52 | public void CopyFrom(ClosedCaptionsCellState cell) 53 | { 54 | Character = cell.Character; 55 | Opacity = cell.Opacity; 56 | Foreground = cell.Foreground; 57 | Background = cell.Background; 58 | IsUnderlined = cell.IsUnderlined; 59 | IsItalics = cell.IsItalics; 60 | } 61 | 62 | /// 63 | /// Clears the text and its attributes. 64 | /// 65 | public void Clear() 66 | { 67 | Character = default; 68 | Opacity = 0.80; 69 | Foreground = Brushes.White; 70 | Background = Brushes.Black; 71 | IsUnderlined = default; 72 | IsItalics = default; 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Unosquare.FFME.Windows/Rendering/ImageHost.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Rendering 2 | { 3 | using System.Windows; 4 | using System.Windows.Controls; 5 | using System.Windows.Media; 6 | 7 | /// 8 | /// Implements an Image control that is hosted on its own independent dispatcher 9 | /// but maintains composability with the main UI. 10 | /// 11 | internal sealed class ImageHost : ElementHostBase 12 | { 13 | /// 14 | /// Initializes a new instance of the class. 15 | /// 16 | public ImageHost() 17 | : base(true) 18 | { 19 | // placeholder 20 | } 21 | 22 | public ImageHost(bool hasOwnDispatcher) 23 | : base(hasOwnDispatcher) 24 | { 25 | // placeholder 26 | } 27 | 28 | /// 29 | /// Gets or sets the source. 30 | /// 31 | public ImageSource Source 32 | { 33 | get => GetElementProperty(Image.SourceProperty); 34 | set => SetElementProperty(Image.SourceProperty, value); 35 | } 36 | 37 | /// 38 | /// Gets or sets the stretch. 39 | /// 40 | public Stretch Stretch 41 | { 42 | get => GetElementProperty(Image.StretchProperty); 43 | set => SetElementProperty(Image.StretchProperty, value); 44 | } 45 | 46 | /// 47 | /// Gets or sets the stretch direction. 48 | /// 49 | public StretchDirection StretchDirection 50 | { 51 | get => GetElementProperty(Image.StretchDirectionProperty); 52 | set => SetElementProperty(Image.StretchDirectionProperty, value); 53 | } 54 | 55 | /// 56 | protected override Image CreateHostedElement() 57 | { 58 | var control = new Image(); 59 | control.BeginInit(); 60 | control.HorizontalAlignment = HorizontalAlignment.Stretch; 61 | control.VerticalAlignment = VerticalAlignment.Stretch; 62 | control.EndInit(); 63 | return control; 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Unosquare.FFME.Windows/Rendering/Wave/DirectSoundDeviceData.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Rendering.Wave 2 | { 3 | using System; 4 | 5 | /// 6 | /// Class for enumerating DirectSound devices. 7 | /// 8 | internal sealed class DirectSoundDeviceData 9 | { 10 | /// 11 | /// The device identifier. 12 | /// 13 | public Guid Guid { get; internal set; } 14 | 15 | /// 16 | /// Device description. 17 | /// 18 | public string Description { get; internal set; } 19 | 20 | /// 21 | /// Device module name. 22 | /// 23 | public string ModuleName { get; internal set; } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Unosquare.FFME.Windows/Rendering/Wave/IWavePlayer.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Rendering.Wave 2 | { 3 | using System; 4 | 5 | /// 6 | /// Represents the interface to a device that can play a Wave data. 7 | /// 8 | internal interface IWavePlayer : IDisposable 9 | { 10 | /// 11 | /// Current playback state. 12 | /// 13 | PlaybackState PlaybackState { get; } 14 | 15 | /// 16 | /// Gets a value indicating whether the audio playback is running. 17 | /// 18 | bool IsRunning { get; } 19 | 20 | /// 21 | /// Gets or sets the desired latency in milliseconds 22 | /// Should be set before a call to Init. 23 | /// 24 | int DesiredLatency { get; } 25 | 26 | /// 27 | /// Gets the renderer that owns this wave player. 28 | /// 29 | AudioRenderer Renderer { get; } 30 | 31 | /// 32 | /// Begin playback. 33 | /// 34 | void Start(); 35 | 36 | /// 37 | /// Clears the internal audio data with silence data. 38 | /// 39 | void Clear(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Unosquare.FFME.Windows/Rendering/Wave/IWaveProvider.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Rendering.Wave 2 | { 3 | /// 4 | /// Generic interface for all WaveProviders. 5 | /// 6 | internal interface IWaveProvider 7 | { 8 | /// 9 | /// Gets the WaveFormat of this WaveProvider. 10 | /// 11 | WaveFormat WaveFormat { get; } 12 | 13 | /// 14 | /// Fill the specified buffer with wave data. 15 | /// 16 | /// The buffer to fill of wave data. 17 | /// Offset into buffer. 18 | /// The number of bytes to read. 19 | /// 20 | /// the number of bytes written to the buffer. 21 | /// 22 | int Read(byte[] buffer, int offset, int count); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Unosquare.FFME.Windows/Rendering/Wave/MmTime.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Rendering.Wave 2 | { 3 | using System; 4 | using System.Runtime.InteropServices; 5 | 6 | /// 7 | /// http://msdn.microsoft.com/en-us/library/dd757347(v=VS.85).aspx. 8 | /// 9 | [StructLayout(LayoutKind.Explicit)] 10 | internal struct MmTime : IEquatable 11 | { 12 | public const int TimeMs = 0x0001; 13 | public const int TimeSamples = 0x0002; 14 | public const int TimeBytes = 0x0004; 15 | 16 | [FieldOffset(0)] 17 | public uint Type; 18 | 19 | [FieldOffset(4)] 20 | public uint Ms; 21 | 22 | [FieldOffset(4)] 23 | public uint Sample; 24 | 25 | [FieldOffset(4)] 26 | public uint CB; 27 | 28 | [FieldOffset(4)] 29 | public uint Ticks; 30 | 31 | [FieldOffset(4)] 32 | public byte SmpteHour; 33 | 34 | [FieldOffset(5)] 35 | public byte SmpteMin; 36 | 37 | [FieldOffset(6)] 38 | public byte SmpteSec; 39 | 40 | [FieldOffset(7)] 41 | public byte SmpteFrame; 42 | 43 | [FieldOffset(8)] 44 | public byte SmpteFps; 45 | 46 | [FieldOffset(9)] 47 | public byte SmpteDummy; 48 | 49 | [FieldOffset(10)] 50 | public byte SmptePad0; 51 | 52 | [FieldOffset(11)] 53 | public byte SmptePad1; 54 | 55 | [FieldOffset(4)] 56 | public uint MidiSongPtrPos; 57 | 58 | /// 59 | public bool Equals(MmTime other) 60 | { 61 | return Type == other.Type && 62 | Ms == other.Ms && 63 | Sample == other.Sample && 64 | CB == other.CB && 65 | Ticks == other.Ticks; 66 | } 67 | 68 | /// 69 | public override bool Equals(object obj) => 70 | obj is MmTime && Equals((MmTime)obj); 71 | 72 | /// 73 | public override int GetHashCode() => 74 | throw new NotSupportedException($"{nameof(MmTime)} does not support hashing."); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Unosquare.FFME.Windows/Rendering/Wave/PlaybackState.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Rendering.Wave 2 | { 3 | /// 4 | /// Enumerates the various wave output playback states. 5 | /// 6 | internal enum PlaybackState 7 | { 8 | /// 9 | /// Stopped. 10 | /// 11 | Stopped, 12 | 13 | /// 14 | /// Playing. 15 | /// 16 | Playing 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Unosquare.FFME.Windows/Rendering/Wave/SupportedWaveFormat.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Rendering.Wave 2 | { 3 | using System; 4 | 5 | /// 6 | /// Supported wave formats for WaveOutCapabilities. 7 | /// 8 | [Flags] 9 | internal enum SupportedWaveFormat 10 | { 11 | /// 12 | /// 11.025 kHz, Mono, 8-bit. 13 | /// 14 | WAVE_FORMAT_1M08 = 0x00000001, 15 | 16 | /// 17 | /// 11.025 kHz, Stereo, 8-bit. 18 | /// 19 | WAVE_FORMAT_1S08 = 0x00000002, 20 | 21 | /// 22 | /// 11.025 kHz, Mono, 16-bit. 23 | /// 24 | WAVE_FORMAT_1M16 = 0x00000004, 25 | 26 | /// 27 | /// 11.025 kHz, Stereo, 16-bit. 28 | /// 29 | WAVE_FORMAT_1S16 = 0x00000008, 30 | 31 | /// 32 | /// 22.05 kHz, Mono, 8-bit. 33 | /// 34 | WAVE_FORMAT_2M08 = 0x00000010, 35 | 36 | /// 37 | /// 22.05 kHz, Stereo, 8-bit. 38 | /// 39 | WAVE_FORMAT_2S08 = 0x00000020, 40 | 41 | /// 42 | /// 22.05 kHz, Mono, 16-bit. 43 | /// 44 | WAVE_FORMAT_2M16 = 0x00000040, 45 | 46 | /// 47 | /// 22.05 kHz, Stereo, 16-bit. 48 | /// 49 | WAVE_FORMAT_2S16 = 0x00000080, 50 | 51 | /// 52 | /// 44.1 kHz, Mono, 8-bit. 53 | /// 54 | WAVE_FORMAT_44M08 = 0x00000100, 55 | 56 | /// 57 | /// 44.1 kHz, Stereo, 8-bit. 58 | /// 59 | WAVE_FORMAT_44S08 = 0x00000200, 60 | 61 | /// 62 | /// 44.1 kHz, Mono, 16-bit. 63 | /// 64 | WAVE_FORMAT_44M16 = 0x00000400, 65 | 66 | /// 67 | /// 44.1 kHz, Stereo, 16-bit. 68 | /// 69 | WAVE_FORMAT_44S16 = 0x00000800, 70 | 71 | /// 72 | /// 48 kHz, Mono, 8-bit. 73 | /// 74 | WAVE_FORMAT_48M08 = 0x00001000, 75 | 76 | /// 77 | /// 48 kHz, Stereo, 8-bit. 78 | /// 79 | WAVE_FORMAT_48S08 = 0x00002000, 80 | 81 | /// 82 | /// 48 kHz, Mono, 16-bit. 83 | /// 84 | WAVE_FORMAT_48M16 = 0x00004000, 85 | 86 | /// 87 | /// 48 kHz, Stereo, 16-bit. 88 | /// 89 | WAVE_FORMAT_48S16 = 0x00008000, 90 | 91 | /// 92 | /// 96 kHz, Mono, 8-bit. 93 | /// 94 | WAVE_FORMAT_96M08 = 0x00010000, 95 | 96 | /// 97 | /// 96 kHz, Stereo, 8-bit. 98 | /// 99 | WAVE_FORMAT_96S08 = 0x00020000, 100 | 101 | /// 102 | /// 96 kHz, Mono, 16-bit. 103 | /// 104 | WAVE_FORMAT_96M16 = 0x00040000, 105 | 106 | /// 107 | /// 96 kHz, Stereo, 16-bit. 108 | /// 109 | WAVE_FORMAT_96S16 = 0x00080000 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /Unosquare.FFME.Windows/Rendering/Wave/WaveHeader.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable SA1401 // Fields must be private 2 | namespace Unosquare.FFME.Rendering.Wave 3 | { 4 | using System; 5 | using System.Runtime.InteropServices; 6 | 7 | /// 8 | /// WaveHeader interop structure (WAVEHDR) 9 | /// http://msdn.microsoft.com/en-us/library/dd743837%28VS.85%29.aspx. 10 | /// 11 | [StructLayout(LayoutKind.Sequential)] 12 | internal class WaveHeader 13 | { 14 | /// pointer to locked data buffer (lpData). 15 | public IntPtr DataBuffer; 16 | 17 | /// length of data buffer (dwBufferLength). 18 | public int BufferLength; 19 | 20 | /// used for input only (dwBytesRecorded). 21 | public int BytesRecorded; 22 | 23 | /// for client's use (dwUser). 24 | public IntPtr UserData; 25 | 26 | /// assorted flags (dwFlags). 27 | public WaveHeaderFlags Flags; 28 | 29 | /// loop control counter (dwLoops). 30 | public int Loops; 31 | 32 | /// PWaveHdr, reserved for driver (lpNext). 33 | public IntPtr Next; 34 | 35 | /// reserved for driver. 36 | public IntPtr Reserved; 37 | } 38 | } 39 | #pragma warning restore SA1401 // Fields must be private -------------------------------------------------------------------------------- /Unosquare.FFME.Windows/Rendering/Wave/WaveHeaderFlags.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Rendering.Wave 2 | { 3 | using System; 4 | 5 | /// 6 | /// Wave Header Flags enumeration. 7 | /// 8 | [Flags] 9 | internal enum WaveHeaderFlags 10 | { 11 | /// 12 | /// This buffer is the first buffer in a loop. This flag is used only with output buffers. 13 | /// 14 | BeginLoop = 0x00000004, 15 | 16 | /// 17 | /// Set by the device driver to indicate that it is finished with the buffer and is returning it to the application. 18 | /// 19 | Done = 0x00000001, 20 | 21 | /// 22 | /// This buffer is the last buffer in a loop. This flag is used only with output buffers. 23 | /// 24 | EndLoop = 0x00000008, 25 | 26 | /// 27 | /// Set by Windows to indicate that the buffer is queued for playback. 28 | /// 29 | InQueue = 0x00000010, 30 | 31 | /// 32 | /// Set by Windows to indicate that the buffer has been prepared with the waveInPrepareHeader or waveOutPrepareHeader function. 33 | /// 34 | Prepared = 0x00000002 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Unosquare.FFME.Windows/Rendering/Wave/WaveOutBuffer.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Rendering.Wave 2 | { 3 | using Primitives; 4 | using System; 5 | using System.Runtime.InteropServices; 6 | 7 | /// 8 | /// 9 | /// A buffer of Wave samples for streaming to a Wave Output device. 10 | /// 11 | internal sealed class WaveOutBuffer : IDisposable 12 | { 13 | private readonly AtomicBoolean m_IsDisposed = new AtomicBoolean(false); 14 | private readonly WaveHeader header; 15 | private readonly byte[] Buffer; 16 | private readonly IWaveProvider WaveStream; 17 | 18 | // Structs 19 | private GCHandle BufferHandle; 20 | private GCHandle HeaderHandle; // we need to pin the header structure 21 | private IntPtr DeviceHandle; 22 | 23 | /// 24 | /// Initializes a new instance of the class. 25 | /// 26 | /// WaveOut device to write to. 27 | /// Buffer size in bytes. 28 | /// Stream to provide more data. 29 | public WaveOutBuffer(IntPtr deviceHandle, int bufferSize, IWaveProvider waveStream) 30 | { 31 | BufferSize = bufferSize; 32 | Buffer = new byte[BufferSize]; 33 | DeviceHandle = deviceHandle; 34 | WaveStream = waveStream; 35 | 36 | BufferHandle = GCHandle.Alloc(Buffer, GCHandleType.Pinned); 37 | header = new WaveHeader(); 38 | HeaderHandle = GCHandle.Alloc(header, GCHandleType.Pinned); 39 | 40 | header.DataBuffer = BufferHandle.AddrOfPinnedObject(); 41 | header.BufferLength = bufferSize; 42 | header.Loops = 1; 43 | header.UserData = IntPtr.Zero; 44 | 45 | WaveInterop.AllocateHeader(DeviceHandle, header); 46 | } 47 | 48 | /// 49 | /// Whether the header's in queue flag is set. 50 | /// 51 | public bool IsQueued => header.Flags.HasFlag(WaveHeaderFlags.InQueue); 52 | 53 | /// 54 | /// The buffer size in bytes. 55 | /// 56 | public int BufferSize { get; } 57 | 58 | /// 59 | public void Dispose() 60 | { 61 | if (m_IsDisposed.Value) return; 62 | m_IsDisposed.Value = true; 63 | 64 | // Release the wave header 65 | WaveInterop.ReleaseHeader(DeviceHandle, header); 66 | 67 | // Unpin The header 68 | if (HeaderHandle.IsAllocated) 69 | HeaderHandle.Free(); 70 | 71 | // Unpin the buffer 72 | if (BufferHandle.IsAllocated) 73 | BufferHandle.Free(); 74 | 75 | // Reset the struct fields 76 | HeaderHandle = default; 77 | BufferHandle = default; 78 | DeviceHandle = IntPtr.Zero; 79 | } 80 | 81 | /// 82 | /// Clears the internal buffer data. 83 | /// 84 | public void Clear() 85 | { 86 | if (Buffer != null) 87 | Array.Clear(Buffer, 0, Buffer.Length); 88 | } 89 | 90 | /// 91 | /// this is called by the Wave callback and should be used to refill the buffer. 92 | /// This calls the method on the stream. 93 | /// 94 | /// True when bytes were written. False if no bytes were written. 95 | internal bool ReadWaveStream() 96 | { 97 | var readCount = WaveStream.Read(Buffer, 0, Buffer.Length); 98 | if (readCount <= 0) return false; 99 | 100 | if (readCount < Buffer.Length) 101 | Array.Clear(Buffer, readCount, Buffer.Length - readCount); 102 | 103 | WaveInterop.WriteAudioData(DeviceHandle, header); 104 | return true; 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /Unosquare.FFME.Windows/Rendering/Wave/WaveOutSupport.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Rendering.Wave 2 | { 3 | using System; 4 | 5 | /// 6 | /// Flags indicating what features this WaveOut device supports. 7 | /// 8 | [Flags] 9 | internal enum WaveOutSupport 10 | { 11 | /// supports pitch control. 12 | Pitch = 0x0001, 13 | 14 | /// supports playback rate control. 15 | PlaybackRate = 0x0002, 16 | 17 | /// supports volume control (WAVECAPS_VOLUME). 18 | Volume = 0x0004, 19 | 20 | /// supports separate left-right volume control. 21 | LRVolume = 0x0008, 22 | 23 | /// Sync. 24 | Sync = 0x0010, 25 | 26 | /// Sample-Accurate. 27 | SampleAccurate = 0x0020 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Unosquare.FFME.Windows/Resources/ffme-preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unosquare/ffmediaelement/72f2c791dfbb74234b8976ef5ae43f2dc46f678e/Unosquare.FFME.Windows/Resources/ffme-preview.png -------------------------------------------------------------------------------- /Unosquare.FFME/ClosedCaptions/CaptionsChannel.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.ClosedCaptions 2 | { 3 | /// 4 | /// Enumerates the 4 different CC channels. 5 | /// 6 | public enum CaptionsChannel 7 | { 8 | /// 9 | /// No channel specified -- use previous. 10 | /// 11 | CCP = 0, 12 | 13 | /// 14 | /// Field 1, Channel 1. 15 | /// 16 | CC1 = 1, 17 | 18 | /// 19 | /// Field 1, Channel 2. 20 | /// 21 | CC2 = 2, 22 | 23 | /// 24 | /// Field 2, Channel 1. 25 | /// 26 | CC3 = 3, 27 | 28 | /// 29 | /// Field 2, Channel 2. 30 | /// 31 | CC4 = 4 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Unosquare.FFME/ClosedCaptions/CaptionsColor.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.ClosedCaptions 2 | { 3 | /// 4 | /// Enumerates the different Closed-Captioning Colors. 5 | /// 6 | public enum CaptionsColor 7 | { 8 | /// 9 | /// No color. 10 | /// 11 | None = 0x00, 12 | 13 | /// 14 | /// The white color. 15 | /// 16 | White = 0x20, 17 | 18 | /// 19 | /// The white transparent color. 20 | /// 21 | WhiteTransparent = 0x21, 22 | 23 | /// 24 | /// The green color. 25 | /// 26 | Green = 0x22, 27 | 28 | /// 29 | /// The green transparent color. 30 | /// 31 | GreenTransparent = 0x23, 32 | 33 | /// 34 | /// The blue color. 35 | /// 36 | Blue = 0x24, 37 | 38 | /// 39 | /// The blue transparent color. 40 | /// 41 | BlueTransparent = 0x25, 42 | 43 | /// 44 | /// The cyan color. 45 | /// 46 | Cyan = 0x26, 47 | 48 | /// 49 | /// The cyan transparent color. 50 | /// 51 | CyanTransparent = 0x27, 52 | 53 | /// 54 | /// The red color. 55 | /// 56 | Red = 0x28, 57 | 58 | /// 59 | /// The red transparent color. 60 | /// 61 | RedTransparent = 0x29, 62 | 63 | /// 64 | /// The yellow color. 65 | /// 66 | Yellow = 0x2A, 67 | 68 | /// 69 | /// The yellow transparent color. 70 | /// 71 | YellowTransparent = 0x2B, 72 | 73 | /// 74 | /// The magenta color. 75 | /// 76 | Magenta = 0x2C, 77 | 78 | /// 79 | /// The magenta transparent color. 80 | /// 81 | MagentaTransparent = 0x2D, 82 | 83 | /// 84 | /// The white italics color. 85 | /// 86 | WhiteItalics = 0x2E, 87 | 88 | /// 89 | /// The white italics transparent color. 90 | /// 91 | WhiteItalicsTransparent = 0x2F, 92 | 93 | /// 94 | /// The background transparent color. 95 | /// 96 | BackgroundTransparent = 0x2D00, 97 | 98 | /// 99 | /// The foreground black color. 100 | /// 101 | ForegroundBlack = 0x2E00, 102 | 103 | /// 104 | /// The foreground black underline color. 105 | /// 106 | ForegroundBlackUnderline = 0x2F00 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /Unosquare.FFME/ClosedCaptions/CaptionsCommand.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.ClosedCaptions 2 | { 3 | /// 4 | /// Enumerates the Closed-Captioning misc commands. 5 | /// 6 | public enum CaptionsCommand 7 | { 8 | /// 9 | /// No command. 10 | /// 11 | None = 0, 12 | 13 | /// 14 | /// The resume command. 15 | /// 16 | Resume = 0x20, 17 | 18 | /// 19 | /// The backspace command. 20 | /// 21 | Backspace = 0x21, 22 | 23 | /// 24 | /// The alarm off command. 25 | /// 26 | AlarmOff = 0x22, 27 | 28 | /// 29 | /// The alarm on command. 30 | /// 31 | AlarmOn = 0x23, 32 | 33 | /// 34 | /// The clear line command 35 | /// Delete to end of Row (DER). 36 | /// 37 | ClearLine = 0x24, 38 | 39 | /// 40 | /// The roll up 2 command. 41 | /// 42 | RollUp2 = 0x25, 43 | 44 | /// 45 | /// The roll up 3 command. 46 | /// 47 | RollUp3 = 0x26, 48 | 49 | /// 50 | /// The roll up 4 command. 51 | /// 52 | RollUp4 = 0x27, 53 | 54 | /// 55 | /// The start caption command. 56 | /// 57 | StartCaption = 0x29, 58 | 59 | /// 60 | /// The start non caption command. 61 | /// 62 | StartNonCaption = 0x2A, 63 | 64 | /// 65 | /// The resume non caption command. 66 | /// 67 | ResumeNonCaption = 0x2B, 68 | 69 | /// 70 | /// The clear screen command. 71 | /// 72 | ClearScreen = 0x2C, 73 | 74 | /// 75 | /// The new line command. 76 | /// 77 | NewLine = 0x2D, 78 | 79 | /// 80 | /// The clear buffer command. 81 | /// 82 | ClearBuffer = 0x2E, 83 | 84 | /// 85 | /// The end caption command. 86 | /// 87 | EndCaption = 0x2F 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Unosquare.FFME/ClosedCaptions/CaptionsPacketType.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.ClosedCaptions 2 | { 3 | /// 4 | /// Defines Closed-Captioning Packet types. 5 | /// 6 | public enum CaptionsPacketType 7 | { 8 | /// 9 | /// The unrecognized packet type. 10 | /// 11 | Unrecognized, 12 | 13 | /// 14 | /// The null pad packet type. 15 | /// 16 | NullPad, 17 | 18 | /// 19 | /// The XDS class packet type. 20 | /// 21 | XdsClass, 22 | 23 | /// 24 | /// The misc command packet type. 25 | /// 26 | Command, 27 | 28 | /// 29 | /// The text packet type. 30 | /// 31 | Text, 32 | 33 | /// 34 | /// The mid row packet type. 35 | /// 36 | MidRow, 37 | 38 | /// 39 | /// The preamble packet type. 40 | /// 41 | Preamble, 42 | 43 | /// 44 | /// The color packet type. 45 | /// 46 | Color, 47 | 48 | /// 49 | /// The charset packet type. 50 | /// 51 | PrivateCharset, 52 | 53 | /// 54 | /// The tabs packet type 55 | /// Section B.4 Tab Offsets. 56 | /// 57 | Tabs 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Unosquare.FFME/ClosedCaptions/CaptionsXdsClass.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.ClosedCaptions 2 | { 3 | /// 4 | /// Defines Closed-Captioning XDS Packet Classes. 5 | /// 6 | public enum CaptionsXdsClass 7 | { 8 | /// 9 | /// The none XDS Class. 10 | /// 11 | None = 0x00, 12 | 13 | /// 14 | /// The current start XDS Class. 15 | /// 16 | CurrentStart = 0x01, 17 | 18 | /// 19 | /// The current continue XDS Class. 20 | /// 21 | CurrentContinue = 0x02, 22 | 23 | /// 24 | /// The future start XDS Class. 25 | /// 26 | FutureStart = 0x03, 27 | 28 | /// 29 | /// The future continue XDS Class. 30 | /// 31 | FutureContinue = 0x04, 32 | 33 | /// 34 | /// The channel start XDS Class. 35 | /// 36 | ChannelStart = 0x05, 37 | 38 | /// 39 | /// The channel continue XDS Class. 40 | /// 41 | ChannelContinue = 0x06, 42 | 43 | /// 44 | /// The misc start XDS Class. 45 | /// 46 | MiscStart = 0x07, 47 | 48 | /// 49 | /// The misc continue XDS Class. 50 | /// 51 | MiscContinue = 0x08, 52 | 53 | /// 54 | /// The public service start XDS Class. 55 | /// 56 | PublicServiceStart = 0x09, 57 | 58 | /// 59 | /// The public service continue XDS Class. 60 | /// 61 | PublicServiceContinue = 0x0A, 62 | 63 | /// 64 | /// The reserved start XDS Class. 65 | /// 66 | ReservedStart = 0x0B, 67 | 68 | /// 69 | /// The reserved continue XDS Class. 70 | /// 71 | ReservedContinue = 0x0C, 72 | 73 | /// 74 | /// The private start XDS Class. 75 | /// 76 | PrivateStart = 0x0D, 77 | 78 | /// 79 | /// The private continue XDS Class. 80 | /// 81 | PrivateContinue = 0x0E, 82 | 83 | /// 84 | /// The end all XDS Class. 85 | /// 86 | EndAll = 0x0F 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Unosquare.FFME/Commands/CommandManager.Enums.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Commands 2 | { 3 | internal partial class CommandManager 4 | { 5 | /// 6 | /// Enumerates the different seek modes for the seek command. 7 | /// 8 | public enum SeekMode 9 | { 10 | /// Normal seek mode. 11 | Normal, 12 | 13 | /// Stop seek mode. 14 | Stop, 15 | 16 | /// Frame step forward. 17 | StepForward, 18 | 19 | /// Frame step backward. 20 | StepBackward 21 | } 22 | 23 | /// 24 | /// Enumerates the different direct command types. 25 | /// 26 | private enum DirectCommandType 27 | { 28 | /// 29 | /// No command Type. 30 | /// 31 | None, 32 | 33 | /// 34 | /// The open command. 35 | /// 36 | Open, 37 | 38 | /// 39 | /// The close command. 40 | /// 41 | Close, 42 | 43 | /// 44 | /// The change command. 45 | /// 46 | Change 47 | } 48 | 49 | /// 50 | /// The priority command types. 51 | /// 52 | private enum PriorityCommandType 53 | { 54 | /// 55 | /// The none command. 56 | /// 57 | None, 58 | 59 | /// 60 | /// The play command. 61 | /// 62 | Play, 63 | 64 | /// 65 | /// The pause command. 66 | /// 67 | Pause, 68 | 69 | /// 70 | /// The stop command. 71 | /// 72 | Stop 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Unosquare.FFME/Common/ContainerConfiguration.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Common 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | 6 | /// 7 | /// Represents a set of options that are used to initialize a media container before opening the stream. 8 | /// This includes both, demuxer and decoder options. 9 | /// 10 | public sealed class ContainerConfiguration 11 | { 12 | /// 13 | /// The scan all PMTS private option name. 14 | /// 15 | internal const string ScanAllPmts = "scan_all_pmts"; 16 | 17 | /// 18 | /// Initializes a new instance of the class. 19 | /// 20 | internal ContainerConfiguration() 21 | { 22 | // placeholder 23 | } 24 | 25 | /// 26 | /// Gets or sets the forced input format. If let null or empty, 27 | /// the input format will be selected automatically. 28 | /// 29 | public string ForcedInputFormat { get; set; } 30 | 31 | /// 32 | /// Gets the protocol prefix. 33 | /// Typically async for local files and empty for other types. 34 | /// 35 | public string ProtocolPrefix { get; set; } 36 | 37 | /// 38 | /// Gets or sets the amount of time to wait for a an open or read 39 | /// operation to complete before it times out. It is 30 seconds by default. 40 | /// 41 | public TimeSpan ReadTimeout { get; set; } = TimeSpan.FromSeconds(30); 42 | 43 | /// 44 | /// Contains global options for the demuxer. For additional info 45 | /// please see: https://ffmpeg.org/ffmpeg-formats.html#Format-Options. 46 | /// 47 | public DemuxerGlobalOptions GlobalOptions { get; } = new(); 48 | 49 | /// 50 | /// Contains private demuxer options. For additional info 51 | /// please see: https://ffmpeg.org/ffmpeg-all.html#Demuxers. 52 | /// 53 | public Dictionary PrivateOptions { get; } = 54 | new(512, StringComparer.InvariantCultureIgnoreCase); 55 | 56 | /// 57 | /// Gets or sets a method to be called when reading the input stream has timed out. 58 | /// 59 | internal Action ReadTimeoutCallback { get; set; } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Unosquare.FFME/Common/HardwareDeviceInfo.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Common 2 | { 3 | using FFmpeg.AutoGen; 4 | 5 | /// 6 | /// Represents a hardware configuration pair of device and pixel format. 7 | /// 8 | public sealed unsafe class HardwareDeviceInfo 9 | { 10 | /// 11 | /// Initializes a new instance of the class. 12 | /// 13 | /// The source configuration. 14 | internal HardwareDeviceInfo(AVCodecHWConfig* config) 15 | { 16 | DeviceType = config->device_type; 17 | PixelFormat = config->pix_fmt; 18 | DeviceTypeName = ffmpeg.av_hwdevice_get_type_name(DeviceType); 19 | PixelFormatName = ffmpeg.av_get_pix_fmt_name(PixelFormat); 20 | } 21 | 22 | /// 23 | /// Gets the type of hardware device. 24 | /// 25 | public AVHWDeviceType DeviceType { get; } 26 | 27 | /// 28 | /// Gets the name of the device type. 29 | /// 30 | public string DeviceTypeName { get; } 31 | 32 | /// 33 | /// Gets the hardware output pixel format. 34 | /// 35 | public AVPixelFormat PixelFormat { get; } 36 | 37 | /// 38 | /// Gets the name of the pixel format. 39 | /// 40 | public string PixelFormatName { get; } 41 | 42 | /// 43 | /// Returns a that represents this instance. 44 | /// 45 | /// 46 | /// A that represents this instance. 47 | /// 48 | public override string ToString() 49 | { 50 | return $"Device {DeviceTypeName}: {PixelFormatName}"; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Unosquare.FFME/Common/IMediaInputStream.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Common 2 | { 3 | using System; 4 | using FFmpeg.AutoGen; 5 | 6 | /// 7 | /// A callback that gets called before the media container's input context gets initialized for the . 8 | /// 9 | /// The container configuration. 10 | /// The media source URL. 11 | public unsafe delegate void InputStreamInitializing(ContainerConfiguration containerConfig, string mediaSource); 12 | 13 | /// 14 | /// A callback that gets called when the media container's input format context has been initialized for the . 15 | /// 16 | /// The unmanaged input format pointer. 17 | /// The unmanaged format context. 18 | /// The detected media info. 19 | public unsafe delegate void InputStreamInitialized(AVInputFormat* inputFormat, AVFormatContext* formatContext, MediaInfo mediaInfo); 20 | 21 | /// 22 | /// Defines the properties and methods necessary for implementing a 23 | /// custom media input stream. 24 | /// 25 | public unsafe interface IMediaInputStream : IDisposable 26 | { 27 | /// 28 | /// Gets the stream URI. This is just a pseudo URI to identify the stream. 29 | /// 30 | Uri StreamUri { get; } 31 | 32 | /// 33 | /// Gets a value indicating whether this stream is seekable. 34 | /// 35 | bool CanSeek { get; } 36 | 37 | /// 38 | /// Gets the length in bytes of the read buffer that will be allocated. 39 | /// Something like 4096 is recommended. 40 | /// 41 | int ReadBufferLength { get; } 42 | 43 | /// 44 | /// Gets the callback that gets called before the media container's input context gets initialized for the . 45 | /// 46 | InputStreamInitializing OnInitializing { get; } 47 | 48 | /// 49 | /// Gets the callback that gets called when the media container's input format context has been initialized for the . 50 | /// 51 | InputStreamInitialized OnInitialized { get; } 52 | 53 | /// 54 | /// Reads from the underlying stream and writes up to bytes 55 | /// to the . Returns the number of bytes that were written. 56 | /// 57 | /// An FFmpeg provided opaque reference. 58 | /// The target buffer. 59 | /// Length of the target buffer. 60 | /// The number of bytes that have been read. 61 | int Read(void* opaque, byte* targetBuffer, int targetBufferLength); 62 | 63 | /// 64 | /// Seeks to the specified offset. The offset can be in byte position or in time units. 65 | /// This is specified by the whence parameter which is one of the AVSEEK prefixed constants. 66 | /// 67 | /// The opaque. 68 | /// The offset. 69 | /// The whence. 70 | /// The position in bytes or time scale that has been read. 71 | long Seek(void* opaque, long offset, int whence); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Unosquare.FFME/Common/MediaContainerException.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Common 2 | { 3 | using System; 4 | 5 | /// 6 | /// 7 | /// A Media Container Exception. 8 | /// 9 | [Serializable] 10 | public class MediaContainerException : Exception 11 | { 12 | // TODO: Add error code property and enumerate error codes. 13 | 14 | /// 15 | /// Initializes a new instance of the class. 16 | /// 17 | /// The message that describes the error. 18 | public MediaContainerException(string message) 19 | : base(message) 20 | { 21 | // placeholder 22 | } 23 | 24 | /// 25 | /// Initializes a new instance of the class. 26 | /// 27 | public MediaContainerException() 28 | : base("Unidentified media container exception") 29 | { 30 | } 31 | 32 | /// 33 | /// Initializes a new instance of the class. 34 | /// 35 | /// The message. 36 | /// The inner exception. 37 | public MediaContainerException(string message, Exception innerException) 38 | : base(message, innerException) 39 | { 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Unosquare.FFME/Common/MediaLogMessageType.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Common 2 | { 3 | /// 4 | /// Defines the different log message types received by the log handler. 5 | /// 6 | public enum MediaLogMessageType 7 | { 8 | /// 9 | /// The none message type. 10 | /// 11 | None = 0, 12 | 13 | /// 14 | /// The information message type. 15 | /// 16 | Info = 1, 17 | 18 | /// 19 | /// The debug message type. 20 | /// 21 | Debug = 2, 22 | 23 | /// 24 | /// The trace message type. 25 | /// 26 | Trace = 4, 27 | 28 | /// 29 | /// The error message type. 30 | /// 31 | Error = 8, 32 | 33 | /// 34 | /// The warning message type. 35 | /// 36 | Warning = 16 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Unosquare.FFME/Common/MediaPlaybackState.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Common 2 | { 3 | /// 4 | /// Media States compatible with MediaState enumeration. 5 | /// 6 | public enum MediaPlaybackState 7 | { 8 | /// 9 | /// The manual status. 10 | /// 11 | Manual = 0, 12 | 13 | /// 14 | /// The play status. 15 | /// 16 | Play = 1, 17 | 18 | /// 19 | /// The close status. 20 | /// 21 | Close = 2, 22 | 23 | /// 24 | /// The pause status. 25 | /// 26 | Pause = 3, 27 | 28 | /// 29 | /// The stop status. 30 | /// 31 | Stop = 4 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Unosquare.FFME/Common/MediaType.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Common 2 | { 3 | using FFmpeg.AutoGen; 4 | 5 | /// 6 | /// Enumerates the different Media Types compatible with AVMEDIATYPE_* constants 7 | /// defined by FFmpeg. 8 | /// 9 | public enum MediaType 10 | { 11 | /// 12 | /// Represents an un-existing media type (-1). 13 | /// 14 | None = AVMediaType.AVMEDIA_TYPE_UNKNOWN, 15 | 16 | /// 17 | /// The video media type (0). 18 | /// 19 | Video = AVMediaType.AVMEDIA_TYPE_VIDEO, 20 | 21 | /// 22 | /// The audio media type (1). 23 | /// 24 | Audio = AVMediaType.AVMEDIA_TYPE_AUDIO, 25 | 26 | /// 27 | /// The subtitle media type (3). 28 | /// 29 | Subtitle = AVMediaType.AVMEDIA_TYPE_SUBTITLE 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Unosquare.FFME/Common/VideoResolutionDivider.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Common 2 | { 3 | /// 4 | /// Enumerates the different low resolution divider indices. 5 | /// 6 | public enum VideoResolutionDivider 7 | { 8 | /// 9 | /// Represents no resolution reduction. 10 | /// 11 | Full = 0, 12 | 13 | /// 14 | /// Represents 1/2 resolution. 15 | /// 16 | Half = 1, 17 | 18 | /// 19 | /// Represents 1/4 resolution. 20 | /// 21 | Quarter = 2, 22 | 23 | /// 24 | /// Represents 1/8 resolution. 25 | /// 26 | Eighth = 3 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Unosquare.FFME/Container/AudioBlock.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Container 2 | { 3 | using Common; 4 | 5 | /// 6 | /// A scaled, pre-allocated audio frame container. 7 | /// The buffer is in 16-bit signed, interleaved sample data. 8 | /// 9 | internal sealed class AudioBlock : MediaBlock 10 | { 11 | /// 12 | /// Initializes a new instance of the class. 13 | /// 14 | internal AudioBlock() 15 | : base(MediaType.Audio) 16 | { 17 | // placeholder 18 | } 19 | 20 | #region Properties 21 | 22 | /// 23 | /// Gets the sample rate. 24 | /// 25 | public int SampleRate { get; internal set; } 26 | 27 | /// 28 | /// Gets the channel count. 29 | /// 30 | public int ChannelCount { get; internal set; } 31 | 32 | /// 33 | /// Gets the available samples per channel. 34 | /// 35 | public int SamplesPerChannel { get; internal set; } 36 | 37 | /// 38 | /// Gets the length of the samples buffer. This might differ from the 39 | /// property after scaling but must always be less than or equal to it. 40 | /// 41 | /// 42 | /// The length of the samples buffer. 43 | /// 44 | public int SamplesBufferLength { get; internal set; } 45 | 46 | #endregion 47 | 48 | #region Methods 49 | 50 | /// 51 | protected override void Deallocate() 52 | { 53 | base.Deallocate(); 54 | SampleRate = default; 55 | ChannelCount = default; 56 | SamplesPerChannel = default; 57 | SamplesBufferLength = 0; 58 | } 59 | 60 | #endregion 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Unosquare.FFME/Container/AudioFrame.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Container; 2 | 3 | using Common; 4 | using FFmpeg.AutoGen; 5 | using System; 6 | 7 | /// 8 | /// 9 | /// Represents a wrapper from an unmanaged FFmpeg audio frame. 10 | /// 11 | /// 12 | /// 13 | internal sealed unsafe class AudioFrame : MediaFrame 14 | { 15 | #region Private Members 16 | 17 | private readonly object DisposeLock = new(); 18 | private bool IsDisposed; 19 | 20 | #endregion 21 | 22 | #region Constructor 23 | 24 | /// 25 | /// Initializes a new instance of the class. 26 | /// 27 | /// The frame. 28 | /// The component. 29 | internal AudioFrame(AVFrame* frame, MediaComponent component) 30 | : base(frame, component, MediaType.Audio) 31 | { 32 | // Compute the start time. 33 | frame->pts = frame->best_effort_timestamp; 34 | HasValidStartTime = frame->pts != ffmpeg.AV_NOPTS_VALUE; 35 | StartTime = frame->pts == ffmpeg.AV_NOPTS_VALUE ? 36 | TimeSpan.FromTicks(0) : 37 | TimeSpan.FromTicks(frame->pts.ToTimeSpan(StreamTimeBase).Ticks); 38 | 39 | // Compute the audio frame duration 40 | Duration = frame->duration > 0 ? 41 | frame->duration.ToTimeSpan(StreamTimeBase) : 42 | TimeSpan.FromTicks(Convert.ToInt64(TimeSpan.TicksPerMillisecond * 1000d * frame->nb_samples / frame->sample_rate)); 43 | 44 | // Compute the audio frame end time 45 | EndTime = TimeSpan.FromTicks(StartTime.Ticks + Duration.Ticks); 46 | } 47 | 48 | #endregion 49 | 50 | #region Properties 51 | 52 | /// 53 | /// Gets the pointer to the unmanaged frame. 54 | /// 55 | internal AVFrame* Pointer => (AVFrame*)InternalPointer; 56 | 57 | #endregion 58 | 59 | #region IDisposable Support 60 | 61 | /// 62 | public override void Dispose() 63 | { 64 | lock (DisposeLock) 65 | { 66 | if (IsDisposed) 67 | return; 68 | 69 | if (InternalPointer != IntPtr.Zero) 70 | ReleaseAVFrame(Pointer); 71 | 72 | InternalPointer = IntPtr.Zero; 73 | IsDisposed = true; 74 | } 75 | } 76 | 77 | #endregion 78 | } 79 | -------------------------------------------------------------------------------- /Unosquare.FFME/Container/DataComponentSet.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Container 2 | { 3 | using Common; 4 | 5 | /// 6 | /// Represents the component set for non-media packets. 7 | /// Data packets sent to this component is not queued and decoding of 8 | /// data frames is up to the user. 9 | /// 10 | internal sealed class DataComponentSet 11 | { 12 | private readonly object SyncLock = new object(); 13 | public delegate void OnDataPacketReceivedDelegate(MediaPacket dataPacket, StreamInfo stream); 14 | 15 | /// 16 | /// Gets or sets a method that gets called when a data packet is received from the input stream. 17 | /// 18 | public OnDataPacketReceivedDelegate OnDataPacketReceived { get; set; } 19 | 20 | /// 21 | /// Tries to handle processing of a data packet. If the packet is in fact a data packet, it is 22 | /// automatically disposed after executing the appropriate callbacks and returns true. 23 | /// If the packet is not a data packet, this method returns false and does not dispose of the 24 | /// packet so that the media component set tries to handle it. 25 | /// 26 | /// The container. 27 | /// The packet. 28 | /// Returns false if the packet is not a data packet. 29 | public bool TryHandleDataPacket(MediaContainer container, MediaPacket packet) 30 | { 31 | lock (SyncLock) 32 | { 33 | // ensure packet and container are not null 34 | if (packet == null || container == null) 35 | return false; 36 | 37 | // Get the associated stream 38 | var stream = container.MediaInfo.Streams.ContainsKey(packet.StreamIndex) 39 | ? container.MediaInfo.Streams[packet.StreamIndex] 40 | : null; 41 | 42 | // Ensure the stream is in fact a data stream 43 | if (stream == null || !stream.IsNonMedia) 44 | return false; 45 | 46 | try 47 | { 48 | // Execute the packet handling callback 49 | OnDataPacketReceived?.Invoke(packet, stream); 50 | } 51 | catch 52 | { 53 | // Ignore 54 | } 55 | finally 56 | { 57 | // always dispose of the packet 58 | packet.Dispose(); 59 | } 60 | 61 | // Signal that the packet has been handled as a data packet 62 | return true; 63 | } 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Unosquare.FFME/Container/PacketBufferState.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Container 2 | { 3 | using System; 4 | 5 | /// 6 | /// A value type that representing the packet buffer state. 7 | /// 8 | internal struct PacketBufferState : IEquatable 9 | { 10 | /// 11 | /// The length in bytes of the packet buffer. 12 | /// 13 | public long Length; 14 | 15 | /// 16 | /// The number of packets in the packet buffer. 17 | /// 18 | public int Count; 19 | 20 | /// 21 | /// The minimum number of packets so is set to true. 22 | /// 23 | public int CountThreshold; 24 | 25 | /// 26 | /// Whether the packet buffer has enough packets. 27 | /// 28 | public bool HasEnoughPackets; 29 | 30 | /// 31 | /// The duration of the packets. An invalid value will return . 32 | /// 33 | public TimeSpan Duration; 34 | 35 | /// 36 | public bool Equals(PacketBufferState other) => 37 | Length == other.Length && 38 | Count == other.Count && 39 | CountThreshold == other.CountThreshold && 40 | HasEnoughPackets == other.HasEnoughPackets; 41 | 42 | /// 43 | public override bool Equals(object obj) => 44 | obj is PacketBufferState state && Equals(state); 45 | 46 | /// 47 | public override int GetHashCode() => 48 | throw new NotSupportedException($"{nameof(PacketBufferState)} does not support hashing."); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Unosquare.FFME/Container/PacketQueueOp.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Container 2 | { 3 | /// 4 | /// Defines the multiple packet queue operations. 5 | /// 6 | internal enum PacketQueueOp 7 | { 8 | /// 9 | /// The packet queue was cleared. 10 | /// 11 | Clear = 0, 12 | 13 | /// 14 | /// The packet queue queued a packet. 15 | /// 16 | Queued = 1, 17 | 18 | /// 19 | /// The packet queue dequeued a packet. 20 | /// 21 | Dequeued = 2 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Unosquare.FFME/Container/SubtitleBlock.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Container 2 | { 3 | using Common; 4 | using FFmpeg.AutoGen; 5 | using System.Collections.Generic; 6 | 7 | /// 8 | /// A subtitle frame container. Simply contains text lines. 9 | /// 10 | internal sealed class SubtitleBlock : MediaBlock 11 | { 12 | /// 13 | /// Initializes a new instance of the class. 14 | /// 15 | internal SubtitleBlock() 16 | : base(MediaType.Subtitle) 17 | { 18 | // placeholder 19 | } 20 | 21 | #region Properties 22 | 23 | /// 24 | /// Gets the lines of text for this subtitle frame with all formatting stripped out. 25 | /// 26 | public IList Text { get; } = new List(16); 27 | 28 | /// 29 | /// Gets the original text in SRT or ASS format. 30 | /// 31 | public IList OriginalText { get; } = new List(16); 32 | 33 | /// 34 | /// Gets the type of the original text. 35 | /// Returns None when it's a bitmap or when it's None. 36 | /// 37 | public AVSubtitleType OriginalTextType { get; internal set; } 38 | 39 | #endregion 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Unosquare.FFME/Container/SubtitleFrame.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Container 2 | { 3 | using FFmpeg.AutoGen; 4 | using System; 5 | using System.Collections.Generic; 6 | 7 | /// 8 | /// Represents a wrapper for an unmanaged Subtitle frame. 9 | /// TODO: Only text (ASS and SRT) subtitles are supported currently. 10 | /// There is no support to bitmap subtitles. 11 | /// 12 | /// 13 | internal sealed unsafe class SubtitleFrame : MediaFrame 14 | { 15 | #region Private Members 16 | 17 | private readonly object DisposeLock = new object(); 18 | private bool IsDisposed; 19 | 20 | #endregion 21 | 22 | #region Constructors 23 | 24 | /// 25 | /// Initializes a new instance of the class. 26 | /// 27 | /// The frame. 28 | /// The component. 29 | internal SubtitleFrame(AVSubtitle* frame, MediaComponent component) 30 | : base(frame, component) 31 | { 32 | // Extract timing information (pts for Subtitles is always in AV_TIME_BASE units) 33 | HasValidStartTime = frame->pts != ffmpeg.AV_NOPTS_VALUE; 34 | var timeOffset = frame->pts.ToTimeSpan(ffmpeg.AV_TIME_BASE); 35 | 36 | // start_display_time and end_display_time are relative to timeOffset 37 | StartTime = TimeSpan.FromMilliseconds(timeOffset.TotalMilliseconds + frame->start_display_time); 38 | EndTime = TimeSpan.FromMilliseconds(timeOffset.TotalMilliseconds + frame->end_display_time); 39 | Duration = TimeSpan.FromMilliseconds(frame->end_display_time - frame->start_display_time); 40 | 41 | // Extract text strings 42 | TextType = AVSubtitleType.SUBTITLE_NONE; 43 | 44 | for (var i = 0; i < frame->num_rects; i++) 45 | { 46 | var rect = frame->rects[i]; 47 | 48 | if (rect->type == AVSubtitleType.SUBTITLE_TEXT) 49 | { 50 | if (rect->text == null) continue; 51 | Text.Add(Utilities.PtrToStringUTF8(rect->text)); 52 | TextType = AVSubtitleType.SUBTITLE_TEXT; 53 | break; 54 | } 55 | 56 | if (rect->type == AVSubtitleType.SUBTITLE_ASS) 57 | { 58 | if (rect->ass == null) continue; 59 | Text.Add(Utilities.PtrToStringUTF8(rect->ass)); 60 | TextType = AVSubtitleType.SUBTITLE_ASS; 61 | break; 62 | } 63 | 64 | TextType = rect->type; 65 | } 66 | } 67 | 68 | #endregion 69 | 70 | #region Properties 71 | 72 | /// 73 | /// Gets lines of text that the subtitle frame contains. 74 | /// 75 | public IList Text { get; } = new List(16); 76 | 77 | /// 78 | /// Gets the type of the text. 79 | /// 80 | /// 81 | /// The type of the text. 82 | /// 83 | public AVSubtitleType TextType { get; } 84 | 85 | /// 86 | /// Gets the pointer to the unmanaged subtitle struct. 87 | /// 88 | internal AVSubtitle* Pointer => (AVSubtitle*)InternalPointer; 89 | 90 | #endregion 91 | 92 | #region Static Methods 93 | 94 | /// 95 | public override void Dispose() 96 | { 97 | lock (DisposeLock) 98 | { 99 | if (IsDisposed) 100 | return; 101 | 102 | if (InternalPointer != IntPtr.Zero) 103 | ReleaseAVSubtitle(Pointer); 104 | 105 | InternalPointer = IntPtr.Zero; 106 | IsDisposed = true; 107 | } 108 | } 109 | 110 | #endregion 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /Unosquare.FFME/Diagnostics/Aspects.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Diagnostics 2 | { 3 | /// 4 | /// Provides constants for logging aspect identifiers. 5 | /// 6 | internal static class Aspects 7 | { 8 | public static string None => "Log.Text"; 9 | 10 | public static string FFmpegLog => "FFmpeg.Log"; 11 | 12 | public static string EngineCommand => "Engine.Commands"; 13 | 14 | public static string ReadingWorker => "Engine.Reading"; 15 | 16 | public static string DecodingWorker => "Engine.Decoding"; 17 | 18 | public static string RenderingWorker => "Engine.Rendering"; 19 | 20 | public static string Connector => "Engine.Connector"; 21 | 22 | public static string Container => "Container"; 23 | 24 | public static string Timing => "Timing"; 25 | 26 | public static string Component => "Container.Component"; 27 | 28 | public static string ReferenceCounter => "ReferenceCounter"; 29 | 30 | public static string VideoRenderer => "Element.Video"; 31 | 32 | public static string AudioRenderer => "Element.Audio"; 33 | 34 | public static string SubtitleRenderer => "Element.Subtitle"; 35 | 36 | public static string Events => "Element.Events"; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Unosquare.FFME/Diagnostics/BenchmarkResult.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Diagnostics 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | 7 | /// 8 | /// Contains benchmark summary data. 9 | /// 10 | internal sealed class BenchmarkResult 11 | { 12 | /// 13 | /// Initializes a new instance of the class. 14 | /// 15 | /// The identifier. 16 | /// The measures. 17 | internal BenchmarkResult(string identifier, List measures) 18 | { 19 | Identifier = identifier; 20 | Count = measures.Count; 21 | Average = measures.Average(t => t.TotalMilliseconds); 22 | Min = measures.Min(t => t.TotalMilliseconds); 23 | Max = measures.Max(t => t.TotalMilliseconds); 24 | } 25 | 26 | /// 27 | /// Gets the benchmark identifier. 28 | /// 29 | public string Identifier { get; } 30 | 31 | /// 32 | /// Gets the measure count. 33 | /// 34 | public int Count { get; } 35 | 36 | /// 37 | /// Gets the average time in milliseconds. 38 | /// 39 | public double Average { get; } 40 | 41 | /// 42 | /// Gets the minimum time in milliseconds. 43 | /// 44 | public double Min { get; } 45 | 46 | /// 47 | /// Gets the maximum time in milliseconds. 48 | /// 49 | public double Max { get; } 50 | 51 | /// 52 | public override string ToString() 53 | { 54 | return $"BID: {Identifier,-30} | CNT: {Count,6} | " + 55 | $"AVG: {Average,8:0.000} ms. | " + 56 | $"MAX: {Max,8:0.000} ms. | " + 57 | $"MIN: {Min,8:0.000} ms. | "; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Unosquare.FFME/Diagnostics/ILoggingHandler.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Diagnostics 2 | { 3 | /// 4 | /// Defines interface methods for logging message handlers. 5 | /// 6 | internal interface ILoggingHandler 7 | { 8 | /// 9 | /// Handles a log message. 10 | /// 11 | /// The message object containing the data. 12 | void HandleLogMessage(LoggingMessage message); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Unosquare.FFME/Diagnostics/ILoggingSource.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Diagnostics 2 | { 3 | /// 4 | /// Defines interface members for a class that 5 | /// defines a logging message handler . 6 | /// 7 | internal interface ILoggingSource 8 | { 9 | /// 10 | /// Gets the logging handler. 11 | /// 12 | ILoggingHandler LoggingHandler { get; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Unosquare.FFME/Diagnostics/LoggingMessage.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Diagnostics 2 | { 3 | using Common; 4 | using System; 5 | 6 | /// 7 | /// Represents the contents of a logging message that was sent to the log manager. 8 | /// 9 | internal sealed class LoggingMessage 10 | { 11 | /// 12 | /// Initializes a new instance of the class. 13 | /// 14 | /// The object that shall handle the message when it is output by the queue. 15 | /// Type of the message. 16 | /// The message text. 17 | /// Name of the code aspect the message came from. 18 | internal LoggingMessage(ILoggingHandler loggingHandler, MediaLogMessageType messageType, string messageText, string aspectName) 19 | { 20 | MessageType = messageType; 21 | Message = messageText; 22 | TimestampUtc = DateTime.UtcNow; 23 | Handler = loggingHandler; 24 | AspectName = aspectName; 25 | } 26 | 27 | /// 28 | /// Gets the object that shall handle the message when it is output by the queue. 29 | /// 30 | public ILoggingHandler Handler { get; } 31 | 32 | /// 33 | /// Gets the timestamp. 34 | /// 35 | public DateTime TimestampUtc { get; } 36 | 37 | /// 38 | /// Gets the type of the message. 39 | /// 40 | public MediaLogMessageType MessageType { get; } 41 | 42 | /// 43 | /// Gets the contents of the message. 44 | /// 45 | public string Message { get; } 46 | 47 | /// 48 | /// Gets the aspect or feature that sent the logged message. 49 | /// May or may not be available. 50 | /// 51 | public string AspectName { get; } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Unosquare.FFME/Engine/IMediaWorker.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Engine 2 | { 3 | using Primitives; 4 | 5 | /// 6 | /// Represents a worker API owned by a . 7 | /// 8 | /// 9 | internal interface IMediaWorker : IWorker 10 | { 11 | /// 12 | /// Gets the media core this worker belongs to. 13 | /// 14 | MediaEngine MediaCore { get; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Unosquare.FFME/Engine/MediaWorkerType.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Engine 2 | { 3 | /// 4 | /// Defines the different worker types. 5 | /// 6 | internal enum MediaWorkerType 7 | { 8 | /// 9 | /// The packet reading worker. 10 | /// 11 | Read, 12 | 13 | /// 14 | /// The frame decoding worker. 15 | /// 16 | Decode, 17 | 18 | /// 19 | /// The block rendering worker. 20 | /// 21 | Render 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Unosquare.FFME/Engine/PacketReadingWorker.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Engine 2 | { 3 | using Common; 4 | using Container; 5 | using Diagnostics; 6 | using Primitives; 7 | using System; 8 | using System.Threading; 9 | 10 | /// 11 | /// Implement packet reading worker logic. 12 | /// 13 | /// 14 | internal sealed class PacketReadingWorker : IntervalWorkerBase, IMediaWorker, ILoggingSource 15 | { 16 | public PacketReadingWorker(MediaEngine mediaCore) 17 | : base(nameof(PacketReadingWorker)) 18 | { 19 | MediaCore = mediaCore; 20 | Container = mediaCore.Container; 21 | 22 | // Enable data frame processing as a connector callback (i.e. hanlde non-media frames) 23 | Container.Data.OnDataPacketReceived = (dataPacket, stream) => 24 | { 25 | try 26 | { 27 | var dataFrame = new DataFrame(dataPacket, stream, MediaCore); 28 | MediaCore.Connector?.OnDataFrameReceived(dataFrame, stream); 29 | } 30 | catch 31 | { 32 | // ignore 33 | } 34 | }; 35 | 36 | // Packet Buffer Notification Callbacks 37 | Container.Components.OnPacketQueueChanged = (op, packet, mediaType, state) => 38 | { 39 | MediaCore.State.UpdateBufferingStats(state.Length, state.Count, state.CountThreshold, state.Duration); 40 | 41 | if (op != PacketQueueOp.Queued) 42 | return; 43 | 44 | unsafe 45 | { 46 | MediaCore.Connector?.OnPacketRead(packet.Pointer, Container.InputContext); 47 | } 48 | }; 49 | } 50 | 51 | /// 52 | public MediaEngine MediaCore { get; } 53 | 54 | /// 55 | ILoggingHandler ILoggingSource.LoggingHandler => MediaCore; 56 | 57 | /// 58 | /// Gets the Media Engine's container. 59 | /// 60 | private MediaContainer Container { get; } 61 | 62 | /// 63 | protected override void ExecuteCycleLogic(CancellationToken ct) 64 | { 65 | while (MediaCore.ShouldReadMorePackets) 66 | { 67 | if (Container.IsReadAborted || Container.IsAtEndOfStream || ct.IsCancellationRequested || 68 | WorkerState != WantedWorkerState) 69 | { 70 | break; 71 | } 72 | 73 | try { Container.Read(); } 74 | catch (MediaContainerException) { /* ignore */ } 75 | } 76 | } 77 | 78 | /// 79 | protected override void OnCycleException(Exception ex) => 80 | this.LogError(Aspects.ReadingWorker, "Worker Cycle exception thrown", ex); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Unosquare.FFME/FFmpeg/FFBPrint.cs: -------------------------------------------------------------------------------- 1 | namespace FFmpeg; 2 | 3 | using FFmpeg.AutoGen; 4 | using System; 5 | using System.Runtime.InteropServices; 6 | using Unosquare.FFME; 7 | 8 | internal unsafe sealed class FFBPrint : IDisposable 9 | { 10 | private static readonly nint ReservedFieldOffset = sizeof(nint) + (3 * sizeof(uint)); 11 | 12 | public FFBPrint() 13 | { 14 | Update(AllocateAutoAVBPrint()); 15 | } 16 | 17 | public AVBPrint* Target { get; private set; } 18 | 19 | public string Contents 20 | { 21 | get 22 | { 23 | if (Target is null) 24 | return string.Empty; 25 | 26 | var bpStruct = Marshal.PtrToStructure(new IntPtr(Target)); 27 | return Utilities.PtrToStringUTF8(bpStruct.str) ?? string.Empty; 28 | } 29 | } 30 | 31 | public unsafe void Dispose() 32 | { 33 | var bpStruct = Marshal.PtrToStructure((nint)Target); 34 | 35 | var isAllocated = Target + ReservedFieldOffset != bpStruct.str; 36 | 37 | if (isAllocated) 38 | ffmpeg.av_freep(&bpStruct.str); 39 | 40 | var target = Target; 41 | ffmpeg.av_freep(&target); 42 | Update(null); 43 | } 44 | 45 | private static unsafe AVBPrint* AllocateAutoAVBPrint() 46 | { 47 | // https://ffmpeg.org/doxygen/1.0/bprint_8h-source.html 48 | const int StructurePadding = 1024; 49 | var bpStructAddress = ffmpeg.av_mallocz(StructurePadding); 50 | var bStruct = default(AVBPrintExtended); 51 | 52 | bStruct.len = 0; 53 | bStruct.size = 1; 54 | bStruct.size_max = uint.MaxValue - 1; 55 | bStruct.reserved_internal_buffer = 0; 56 | 57 | // point at the address of the reserved_internal_buffer 58 | bStruct.str = (byte*)((nint)bpStructAddress + ReservedFieldOffset); 59 | 60 | Marshal.StructureToPtr(bStruct, (nint)bpStructAddress, true); 61 | return (AVBPrint*)bpStructAddress; 62 | } 63 | 64 | private unsafe void Update(AVBPrint* target) 65 | { 66 | Target = target; 67 | } 68 | 69 | #pragma warning disable SA1307 // Accessible fields should begin with upper-case letter 70 | #pragma warning disable SA1310 // Field names should not contain underscore 71 | [StructLayout(LayoutKind.Sequential)] 72 | private struct AVBPrintExtended 73 | { 74 | public byte* str; 75 | public uint len; 76 | public uint size; 77 | public uint size_max; 78 | public byte reserved_internal_buffer; 79 | } 80 | #pragma warning restore SA1310 // Field names should not contain underscore 81 | #pragma warning restore SA1307 // Accessible fields should begin with upper-case letter 82 | } -------------------------------------------------------------------------------- /Unosquare.FFME/FFmpeg/FFDictionaryEntry.cs: -------------------------------------------------------------------------------- 1 | namespace FFmpeg.AutoGen 2 | { 3 | using System; 4 | using Unosquare.FFME; 5 | 6 | /// 7 | /// An AVDictionaryEntry wrapper. 8 | /// 9 | internal unsafe class FFDictionaryEntry 10 | { 11 | // This pointer is generated in unmanaged code. 12 | private readonly IntPtr m_Pointer; 13 | 14 | /// 15 | /// Initializes a new instance of the class. 16 | /// 17 | /// The entry pointer. 18 | public FFDictionaryEntry(AVDictionaryEntry* entryPointer) 19 | { 20 | m_Pointer = new IntPtr(entryPointer); 21 | } 22 | 23 | /// 24 | /// Gets the unmanaged pointer. 25 | /// 26 | public AVDictionaryEntry* Pointer => (AVDictionaryEntry*)m_Pointer; 27 | 28 | /// 29 | /// Gets the key. 30 | /// 31 | public string Key => m_Pointer != IntPtr.Zero ? Utilities.PtrToStringUTF8(Pointer->key) : null; 32 | 33 | /// 34 | /// Gets the value. 35 | /// 36 | public string Value => m_Pointer != IntPtr.Zero ? Utilities.PtrToStringUTF8(Pointer->value) : null; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Unosquare.FFME/Platform/IMediaRenderer.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Platform 2 | { 3 | using Container; 4 | using Engine; 5 | using System; 6 | 7 | /// 8 | /// Provides a unified API for media rendering classes. 9 | /// 10 | internal interface IMediaRenderer 11 | { 12 | /// 13 | /// Gets the parent media engine. 14 | /// 15 | MediaEngine MediaCore { get; } 16 | 17 | /// 18 | /// Waits for the renderer to be ready to render. 19 | /// This is called only once before all Render calls are made. 20 | /// 21 | void OnStarting(); 22 | 23 | /// 24 | /// Executed when the Play method is called on the parent Media Engine. 25 | /// 26 | void OnPlay(); 27 | 28 | /// 29 | /// Executed when the Pause method is called on the parent Media Engine. 30 | /// 31 | void OnPause(); 32 | 33 | /// 34 | /// Executed when the Stop method is called on the parent Media Engine. 35 | /// 36 | void OnStop(); 37 | 38 | /// 39 | /// Executed when the Close method is called on the parent Media Engine. 40 | /// Release all resources when this call is received. 41 | /// 42 | void OnClose(); 43 | 44 | /// 45 | /// Executed after a Seek operation is performed on the parent Media Engine. 46 | /// 47 | void OnSeek(); 48 | 49 | /// 50 | /// Called when a media block is due rendering. 51 | /// This needs to return immediately so the calling thread is not disturbed. 52 | /// 53 | /// The media block. 54 | /// The clock position. 55 | void Render(MediaBlock mediaBlock, TimeSpan clockPosition); 56 | 57 | /// 58 | /// Called on every block rendering clock cycle just in case some update operation needs to be performed. 59 | /// This needs to return immediately so the calling thread is not disturbed. 60 | /// 61 | /// The clock position. 62 | void Update(TimeSpan clockPosition); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Unosquare.FFME/Playlists/PlaylistEntry.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Playlists 2 | { 3 | using System; 4 | using System.ComponentModel; 5 | using System.Runtime.CompilerServices; 6 | 7 | /// 8 | /// Represents a generic playlist entry. 9 | /// 10 | public class PlaylistEntry : INotifyPropertyChanged 11 | { 12 | private string m_MediaSource; 13 | private string m_Title; 14 | private TimeSpan m_Duration; 15 | 16 | /// 17 | /// Occurs when a property value changes. 18 | /// 19 | public event PropertyChangedEventHandler PropertyChanged; 20 | 21 | /// 22 | /// Gets or sets the media source URL. 23 | /// 24 | public string MediaSource 25 | { 26 | get => m_MediaSource; 27 | set => SetProperty(ref m_MediaSource, value); 28 | } 29 | 30 | /// 31 | /// Gets or sets the title. 32 | /// 33 | public string Title 34 | { 35 | get => m_Title; 36 | set => SetProperty(ref m_Title, value); 37 | } 38 | 39 | /// 40 | /// Gets or sets the duration. 41 | /// 42 | public TimeSpan Duration 43 | { 44 | get => m_Duration; 45 | set => SetProperty(ref m_Duration, value); 46 | } 47 | 48 | /// 49 | /// Gets the extended attributes. 50 | /// 51 | public PlaylistEntryAttributeDictionary Attributes { get; } = new PlaylistEntryAttributeDictionary(); 52 | 53 | /// 54 | /// Checks if a property already matches a desired value. Sets the property and 55 | /// notifies listeners only when necessary. 56 | /// 57 | /// Type of the property. 58 | /// Reference to a property with both getter and setter. 59 | /// Desired value for the property. 60 | /// Name of the property used to notify listeners. This 61 | /// value is optional and can be provided automatically when invoked from compilers that 62 | /// support CallerMemberName. 63 | /// True if the value was changed, false if the existing value matched the 64 | /// desired value. 65 | protected virtual bool SetProperty(ref T storage, T value, [CallerMemberName] string propertyName = null) 66 | { 67 | if (Equals(storage, value)) return false; 68 | storage = value; 69 | OnPropertyChanged(propertyName); 70 | return true; 71 | } 72 | 73 | /// 74 | /// Notifies listeners that a property value has changed. 75 | /// 76 | /// Name of the property used to notify listeners. This 77 | /// value is optional and can be provided automatically when invoked from compilers 78 | /// that support . 79 | protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) 80 | { 81 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Unosquare.FFME/Playlists/PlaylistEntryAttributeDictionary.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Playlists 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Web; 6 | 7 | /// 8 | /// Represents a dictionary of attributes (key-value pairs). 9 | /// 10 | [Serializable] 11 | public class PlaylistEntryAttributeDictionary : Dictionary 12 | { 13 | /// 14 | /// Initializes a new instance of the class. 15 | /// 16 | public PlaylistEntryAttributeDictionary() 17 | : base(16) 18 | { 19 | // placeholder 20 | } 21 | 22 | /// 23 | /// Returns a that represents this instance. 24 | /// 25 | /// 26 | /// A that represents this instance. 27 | /// 28 | public override string ToString() 29 | { 30 | var attributes = new List(Count); 31 | foreach (var kvp in this) 32 | { 33 | if (string.IsNullOrWhiteSpace(kvp.Key)) 34 | continue; 35 | 36 | var value = string.IsNullOrWhiteSpace(kvp.Value) ? string.Empty : kvp.Value; 37 | attributes.Add($"{HttpUtility.UrlEncode(kvp.Key)}=\"{HttpUtility.UrlEncode(value)}\""); 38 | } 39 | 40 | return string.Join(" ", attributes); 41 | } 42 | 43 | /// 44 | /// Gets the entry value safely. 45 | /// 46 | /// The entry key. 47 | /// The entry value or null. 48 | public string GetEntryValue(string entryKey) 49 | { 50 | return ContainsKey(entryKey) ? this[entryKey] : null; 51 | } 52 | 53 | /// 54 | /// Sets the entry value and returns true if the value changes. 55 | /// 56 | /// The entry key. 57 | /// The value. 58 | /// True if the property changed, false otherwise. 59 | public bool SetEntryValue(string entryKey, string value) 60 | { 61 | var existingValue = GetEntryValue(entryKey); 62 | this[entryKey] = value; 63 | if (existingValue == null) return true; 64 | return Equals(existingValue, value) == false; 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Unosquare.FFME/Primitives/AtomicBoolean.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Primitives 2 | { 3 | /// 4 | /// Fast, atomic boolean combining interlocked to write value and volatile to read values 5 | /// Idea taken from Memory model and .NET operations in article: 6 | /// http://igoro.com/archive/volatile-keyword-in-c-memory-model-explained/. 7 | /// 8 | internal sealed class AtomicBoolean : AtomicTypeBase 9 | { 10 | /// 11 | /// Initializes a new instance of the class. 12 | /// 13 | /// if set to true [initial value]. 14 | public AtomicBoolean(bool initialValue) 15 | : base(initialValue ? 1 : 0) 16 | { 17 | // placeholder 18 | } 19 | 20 | /// 21 | /// Initializes a new instance of the class. 22 | /// 23 | public AtomicBoolean() 24 | : base(0) 25 | { 26 | // placeholder 27 | } 28 | 29 | /// 30 | protected override bool FromLong(long backingValue) => backingValue != 0; 31 | 32 | /// 33 | protected override long ToLong(bool value) => value ? 1 : 0; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Unosquare.FFME/Primitives/AtomicDateTime.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Primitives 2 | { 3 | using System; 4 | 5 | /// 6 | /// Defines an atomic DateTime. 7 | /// 8 | internal sealed class AtomicDateTime : AtomicTypeBase 9 | { 10 | /// 11 | /// Initializes a new instance of the class. 12 | /// 13 | /// The initial value. 14 | public AtomicDateTime(DateTime initialValue) 15 | : base(initialValue.Ticks) 16 | { 17 | // placeholder 18 | } 19 | 20 | /// 21 | protected override DateTime FromLong(long backingValue) => new DateTime(backingValue); 22 | 23 | /// 24 | protected override long ToLong(DateTime value) => value.Ticks; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Unosquare.FFME/Primitives/AtomicDouble.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Primitives 2 | { 3 | using System; 4 | 5 | /// 6 | /// Fast, atomic double combining interlocked to write value and volatile to read values 7 | /// Idea taken from Memory model and .NET operations in article: 8 | /// http://igoro.com/archive/volatile-keyword-in-c-memory-model-explained/. 9 | /// 10 | internal sealed class AtomicDouble : AtomicTypeBase 11 | { 12 | /// 13 | /// Initializes a new instance of the class. 14 | /// 15 | /// if set to true [initial value]. 16 | public AtomicDouble(double initialValue) 17 | : base(BitConverter.DoubleToInt64Bits(initialValue)) 18 | { 19 | // placeholder 20 | } 21 | 22 | /// 23 | /// Initializes a new instance of the class. 24 | /// 25 | public AtomicDouble() 26 | : base(BitConverter.DoubleToInt64Bits(0)) 27 | { 28 | // placeholder 29 | } 30 | 31 | /// 32 | protected override double FromLong(long backingValue) => BitConverter.Int64BitsToDouble(backingValue); 33 | 34 | /// 35 | protected override long ToLong(double value) => BitConverter.DoubleToInt64Bits(value); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Unosquare.FFME/Primitives/AtomicInteger.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Primitives 2 | { 3 | using System; 4 | 5 | /// 6 | /// Represents an atomically readable or writable integer. 7 | /// 8 | internal sealed class AtomicInteger : AtomicTypeBase 9 | { 10 | /// 11 | /// Initializes a new instance of the class. 12 | /// 13 | /// if set to true [initial value]. 14 | public AtomicInteger(int initialValue) 15 | : base(Convert.ToInt64(initialValue)) 16 | { 17 | // placeholder 18 | } 19 | 20 | /// 21 | /// Initializes a new instance of the class. 22 | /// 23 | public AtomicInteger() 24 | : base(0) 25 | { 26 | // placeholder 27 | } 28 | 29 | /// 30 | protected override int FromLong(long backingValue) => Convert.ToInt32(backingValue); 31 | 32 | /// 33 | protected override long ToLong(int value) => Convert.ToInt64(value); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Unosquare.FFME/Primitives/AtomicLong.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Primitives 2 | { 3 | /// 4 | /// Fast, atomic long combining interlocked to write value and volatile to read values 5 | /// Idea taken from Memory model and .NET operations in article: 6 | /// http://igoro.com/archive/volatile-keyword-in-c-memory-model-explained/. 7 | /// 8 | internal sealed class AtomicLong : AtomicTypeBase 9 | { 10 | /// 11 | /// Initializes a new instance of the class. 12 | /// 13 | /// if set to true [initial value]. 14 | public AtomicLong(long initialValue) 15 | : base(initialValue) 16 | { 17 | // placeholder 18 | } 19 | 20 | /// 21 | /// Initializes a new instance of the class. 22 | /// 23 | public AtomicLong() 24 | : base(0) 25 | { 26 | // placeholder 27 | } 28 | 29 | /// 30 | protected override long FromLong(long backingValue) => backingValue; 31 | 32 | /// 33 | protected override long ToLong(long value) => value; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Unosquare.FFME/Primitives/AtomicTimeSpan.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Primitives 2 | { 3 | using System; 4 | 5 | /// 6 | /// Represents an atomic TimeSpan type. 7 | /// 8 | internal sealed class AtomicTimeSpan : AtomicTypeBase 9 | { 10 | /// 11 | /// Initializes a new instance of the class. 12 | /// 13 | /// The initial value. 14 | public AtomicTimeSpan(TimeSpan initialValue) 15 | : base(initialValue.Ticks) 16 | { 17 | // placeholder 18 | } 19 | 20 | /// 21 | protected override TimeSpan FromLong(long backingValue) => TimeSpan.FromTicks(backingValue); 22 | 23 | /// 24 | protected override long ToLong(TimeSpan value) => value.Ticks; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Unosquare.FFME/Primitives/ISyncLocker.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Primitives 2 | { 3 | using System; 4 | 5 | /// 6 | /// Defines a generic interface for synchronized locking mechanisms. 7 | /// 8 | internal interface ISyncLocker : IDisposable 9 | { 10 | /// 11 | /// Gets a value indicating whether this instance is disposed. 12 | /// 13 | bool IsDisposed { get; } 14 | 15 | /// 16 | /// Acquires a writer lock. 17 | /// The lock is released when the returned locking object is disposed. 18 | /// 19 | /// A disposable locking object. 20 | IDisposable AcquireWriterLock(); 21 | 22 | /// 23 | /// Tries to acquire a writer lock with a timeout. 24 | /// 25 | /// The timeout milliseconds. 26 | /// The locker. 27 | /// True if the lock was acquired. 28 | bool TryAcquireWriterLock(int timeoutMilliseconds, out IDisposable locker); 29 | 30 | /// 31 | /// Tries to acquire a writer lock with a default timeout. 32 | /// 33 | /// The locker. 34 | /// /// True if the lock was acquired. 35 | bool TryAcquireWriterLock(out IDisposable locker); 36 | 37 | /// 38 | /// Acquires a reader lock. 39 | /// The lock is released when the returned locking object is disposed. 40 | /// 41 | /// A disposable locking object. 42 | IDisposable AcquireReaderLock(); 43 | 44 | /// 45 | /// Tries to acquire a reader lock with a timeout. 46 | /// 47 | /// The timeout milliseconds. 48 | /// The locker. 49 | /// True if the lock was acquired. 50 | bool TryAcquireReaderLock(int timeoutMilliseconds, out IDisposable locker); 51 | 52 | /// 53 | /// Tries to acquire a reader lock with a default timeout. 54 | /// 55 | /// The locker. 56 | /// True if the lock was acquired. 57 | bool TryAcquireReaderLock(out IDisposable locker); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Unosquare.FFME/Primitives/IWorker.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Primitives 2 | { 3 | using System; 4 | using System.Threading.Tasks; 5 | 6 | /// 7 | /// Defines a standard API to control background application workers. 8 | /// 9 | /// 10 | internal interface IWorker : IDisposable 11 | { 12 | /// 13 | /// Gets the current state of the worker. 14 | /// 15 | WorkerState WorkerState { get; } 16 | 17 | /// 18 | /// Gets a value indicating whether this instance is disposed. 19 | /// 20 | /// 21 | /// true if this instance is disposed; otherwise, false. 22 | /// 23 | bool IsDisposed { get; } 24 | 25 | /// 26 | /// Gets the name identifier of this worker. 27 | /// 28 | string Name { get; } 29 | 30 | /// 31 | /// Starts execution of worker cycles. 32 | /// 33 | /// The awaitable task. 34 | Task StartAsync(); 35 | 36 | /// 37 | /// Pauses execution of worker cycles. 38 | /// 39 | /// The awaitable task. 40 | Task PauseAsync(); 41 | 42 | /// 43 | /// Resumes execution of worker cycles. 44 | /// 45 | /// The awaitable task. 46 | Task ResumeAsync(); 47 | 48 | /// 49 | /// Permanently stops execution of worker cycles. 50 | /// An interrupt is always sent to the worker. If you wish to stop 51 | /// the worker without interrupting then call the 52 | /// method, await it, and finally call the method. 53 | /// 54 | /// The awaitable task. 55 | Task StopAsync(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Unosquare.FFME/Primitives/IntervalWorkerBase.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Primitives 2 | { 3 | /// 4 | /// A base class for implementing interval workers. 5 | /// 6 | internal abstract class IntervalWorkerBase : WorkerBase 7 | { 8 | private readonly StepTimer QuantumTimer; 9 | 10 | /// 11 | /// Initializes a new instance of the class. 12 | /// 13 | /// The name. 14 | protected IntervalWorkerBase(string name) 15 | : base(name) 16 | { 17 | QuantumTimer = new StepTimer(OnQuantumTicked); 18 | } 19 | 20 | /// 21 | protected override void Dispose(bool alsoManaged) 22 | { 23 | base.Dispose(alsoManaged); 24 | QuantumTimer.Dispose(); 25 | } 26 | 27 | /// 28 | /// Called when every quantum of time occurs. 29 | /// 30 | private void OnQuantumTicked() 31 | { 32 | if (!TryBeginCycle()) 33 | return; 34 | 35 | ExecuteCyle(); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Unosquare.FFME/Primitives/MediaTypeDictionary.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Primitives 2 | { 3 | using Common; 4 | using System; 5 | using System.Collections.Generic; 6 | 7 | /// 8 | /// Represents a very simple dictionary for MediaType keys. 9 | /// 10 | /// The type of the value. 11 | [Serializable] 12 | internal sealed class MediaTypeDictionary 13 | : Dictionary 14 | { 15 | /// 16 | /// Initializes a new instance of the class. 17 | /// 18 | public MediaTypeDictionary() 19 | : base(Enum.GetValues(typeof(MediaType)).Length) 20 | { 21 | // placeholder 22 | } 23 | 24 | /// 25 | /// Gets or sets the item with the specified key. 26 | /// return the default value of the value type when the key does not exist. 27 | /// 28 | /// The key. 29 | /// The item. 30 | public new TValue this[MediaType key] 31 | { 32 | get => ContainsKey(key) == false ? default : base[key]; 33 | internal set => base[key] = value; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Unosquare.FFME/Primitives/WorkerState.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME.Primitives 2 | { 3 | /// 4 | /// Enumerates the different states in which a worker can be. 5 | /// 6 | internal enum WorkerState 7 | { 8 | /// 9 | /// The worker has been created and it is ready to start. 10 | /// 11 | Created, 12 | 13 | /// 14 | /// The worker is running it cycle logic. 15 | /// 16 | Running, 17 | 18 | /// 19 | /// The worker is running its delay logic. 20 | /// 21 | Waiting, 22 | 23 | /// 24 | /// The worker is in the paused or suspended state. 25 | /// 26 | Paused, 27 | 28 | /// 29 | /// The worker is stopped and ready for disposal. 30 | /// 31 | Stopped 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Unosquare.FFME/Unosquare.FFME.shproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | b14a1cd8-e4e8-44e1-805a-66d0fafc6263 5 | 14.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Unosquare.FFME/Utilities.Audio.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME 2 | { 3 | using System; 4 | using System.Runtime.CompilerServices; 5 | 6 | public static partial class Utilities 7 | { 8 | /// 9 | /// Gets the audio sample amplitude (absolute value of the sample). 10 | /// 11 | /// The buffer. 12 | /// The offset. 13 | /// The sample amplitude. 14 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 15 | public static short GetAudioSampleAmplitude(this byte[] buffer, int offset) 16 | { 17 | var value = buffer.GetAudioSample(offset); 18 | return value == short.MinValue ? short.MaxValue : Math.Abs(value); 19 | } 20 | 21 | /// 22 | /// Gets the audio sample level for 0 to 1. 23 | /// 24 | /// The buffer. 25 | /// The offset. 26 | /// The amplitude level. 27 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 28 | public static double GetAudioSampleLevel(this byte[] buffer, int offset) 29 | { 30 | return buffer.GetAudioSampleAmplitude(offset) / Convert.ToDouble(short.MaxValue); 31 | } 32 | 33 | /// 34 | /// Puts a short value in the target buffer as bytes. 35 | /// 36 | /// The target. 37 | /// The offset. 38 | /// The value. 39 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 40 | internal static void PutAudioSample(this byte[] buffer, int offset, short value) 41 | { 42 | if (BitConverter.IsLittleEndian) 43 | { 44 | buffer[offset] = (byte)(value & 0x00ff); // set the LSB 45 | buffer[offset + 1] = (byte)(value >> 8); // set the MSB 46 | return; 47 | } 48 | 49 | buffer[offset] = (byte)(value >> 8); // set the MSB 50 | buffer[offset + 1] = (byte)(value & 0x00ff); // set the LSB 51 | } 52 | 53 | /// 54 | /// Gets the a signed 16 bit integer at the given offset. 55 | /// 56 | /// The buffer. 57 | /// The offset. 58 | /// The signed integer. 59 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 60 | internal static short GetAudioSample(this byte[] buffer, int offset) => 61 | BitConverter.ToInt16(buffer, offset); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Unosquare.FFME/Utilities.cs: -------------------------------------------------------------------------------- 1 | namespace Unosquare.FFME; 2 | 3 | using System; 4 | using System.Runtime.CompilerServices; 5 | using System.Text; 6 | 7 | /// 8 | /// Provides various helpers and extension methods. 9 | /// 10 | public static partial class Utilities 11 | { 12 | /// 13 | /// Converts the given value to a value that is of the given multiple. 14 | /// 15 | /// The value. 16 | /// The multiple. 17 | /// The value snapped to the given multiple. 18 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 19 | public static double ToMultipleOf(this double value, double multiple) 20 | { 21 | var factor = Convert.ToInt32(value / multiple); 22 | return factor * multiple; 23 | } 24 | 25 | /// 26 | /// Converts a byte pointer to a UTF8 encoded string. 27 | /// 28 | /// The pointer to the starting character. 29 | /// The string. 30 | public static unsafe string PtrToStringUTF8(byte* stringAddress) 31 | { 32 | if (stringAddress == null) return null; 33 | if (*stringAddress == 0) return string.Empty; 34 | 35 | var byteLength = 0; 36 | while (true) 37 | { 38 | if (stringAddress[byteLength] == 0) 39 | break; 40 | 41 | byteLength++; 42 | } 43 | 44 | var stringBuffer = stackalloc byte[byteLength]; 45 | Buffer.MemoryCopy(stringAddress, stringBuffer, byteLength, byteLength); 46 | return Encoding.UTF8.GetString(stringBuffer, byteLength); 47 | } 48 | 49 | /// 50 | /// A cross-platform implementation of string.Replace. 51 | /// 52 | /// The string to search. 53 | /// The string to find. 54 | /// The string to replace. 55 | /// The string with the replacement. 56 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 57 | public static string ReplaceOrdinal(this string source, string find, string replace) => 58 | #if NETCOREAPP 59 | source?.Replace(find, replace, StringComparison.Ordinal); 60 | #else 61 | source?.Replace(find, replace); 62 | #endif 63 | 64 | /// 65 | /// Determines if the string contains the search term in ordinal (binary) comparison. 66 | /// 67 | /// The string to search. 68 | /// The search term. 69 | /// Thether the search term is contained in the string. 70 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 71 | public static bool ContainsOrdinal(this string source, string find) => 72 | source != null && source.IndexOf(find, StringComparison.Ordinal) > -1; 73 | } 74 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: '7.0.361.{build}' 2 | image: Visual Studio 2019 Preview 3 | configuration: 4 | - Release 5 | platform: Any CPU 6 | notifications: 7 | - provider: Slack 8 | auth_token: 9 | secure: Q+xg4/yU5OR9BVF14cw4yZ+3qlhMeYDsAhUQyOIszmF1mHvq44tIvQpWByBJCd/cgUIZk3SwBpk4hh1MrkQIk6rnaOZ2LNBTev4zrq36oXk= 10 | channel: '#builds' 11 | environment: 12 | # Don't report back to the mothership 13 | DOTNET_CLI_TELEMETRY_OPTOUT: 1 14 | COVERALLS_REPO_TOKEN: 15 | secure: dVpAqavd3jP7LMW+oswLBPfihwiGDZUtWDQWJadMO1Gj61SYegxNsGTZOT2BYN6+ 16 | op_build_user: "Geo Perez" 17 | op_build_user_email: "geovanni.perez@gmail.com" 18 | access_token: 19 | secure: HzWdswNyfQbQ0vLk9IQyO+Ei9mxoPYp9rvv6HPhtC9J/Fm7EHRzyV953pbPRXI9I 20 | before_build: 21 | - nuget restore -verbosity quiet 22 | - ps: | 23 | if(-Not $env:APPVEYOR_PULL_REQUEST_TITLE) 24 | { 25 | git checkout $env:APPVEYOR_REPO_BRANCH -q 26 | cinst docfx -y --no-progress 27 | } 28 | build_script: 29 | - cd Unosquare.FFME.Windows.Sample 30 | - msbuild /p:Configuration=Release /verbosity:quiet 31 | - cd .. 32 | after_build: 33 | - ps: | 34 | if(-Not $env:APPVEYOR_PULL_REQUEST_TITLE) 35 | { 36 | git config --global credential.helper store 37 | Add-Content "$env:USERPROFILE\.git-credentials" "https://$($env:access_token):x-oauth-basic@github.com`n" 38 | git config --global core.autocrlf false 39 | git config --global user.email $env:op_build_user_email 40 | git config --global user.name $env:op_build_user 41 | git clone https://github.com/unosquare/ffmediaelement.git -b gh-pages origin_site -q 42 | git clone -b documentation https://github.com/unosquare/best-practices.git -q 43 | docfx docfx.json --logLevel Error 44 | Copy-Item origin_site/.git _site -recurse 45 | CD _site 46 | Copy-Item README.html index.html -force 47 | git add -A 2>&1 48 | git commit -m "Documentation update" -q 49 | git push origin gh-pages -q 50 | CD .. 51 | } 52 | -------------------------------------------------------------------------------- /docfx.json: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": [ 3 | { 4 | "src": [ 5 | { 6 | "files": [ "Unosquare.FFME.Windows/**/*.cs" ], 7 | "exclude": [ "**/bin/**", "**/obj/**" ] 8 | } 9 | ], 10 | "dest": "obj/api" 11 | } 12 | ], 13 | "build": { 14 | "xrefService": "https://xref.docs.microsoft.com/query?uid={uid}", 15 | "template": [ 16 | "best-practices/templates/default" 17 | ], 18 | "content": [ 19 | { 20 | "files": [ 21 | "**/*.yml" 22 | ], 23 | "cwd": "obj/api", 24 | "dest": "api" 25 | }, 26 | { 27 | "files": [ 28 | "*.md", 29 | "toc.yml" 30 | ] 31 | } 32 | ], 33 | "resource": [ 34 | { 35 | "files": [ "best-practices/resources/**", "ffme.png"] 36 | } 37 | ], 38 | "globalMetadata": { 39 | "_appTitle": "FFME: WPF MediaElement replacement based on FFmpeg", 40 | "_enableSearch": true, 41 | "_docLogo": "ffme.png", 42 | "_appLogoPath": "best-practices/resources/images/logo.png" 43 | }, 44 | "dest": "_site" 45 | } 46 | } -------------------------------------------------------------------------------- /toc.yml: -------------------------------------------------------------------------------- 1 | - name: FFME Documentation 2 | href: api/Unosquare.FFME.html 3 | --------------------------------------------------------------------------------