Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions src/Beutl.Extensions.MediaFoundation/Encoding/MFEncoderInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using Beutl.Media.Encoding;

#if MF_BUILD_IN
namespace Beutl.Embedding.MediaFoundation.Encoding;
#else
namespace Beutl.Extensions.MediaFoundation.Encoding;
#endif

public class MFEncoderInfo : IEncoderInfo
{
public string Name => "Media Foundation Encoder";

public MediaWriter? Create(string file, VideoEncoderSettings videoConfig, AudioEncoderSettings audioConfig)
{
if (videoConfig is not MFVideoEncoderSettings mfVideoConfig)
return null;

return new MFWriter(file, mfVideoConfig, audioConfig);
}

public IEnumerable<string> SupportExtensions()
{
yield return ".mp4";
yield return ".mov";
yield return ".m4v";
yield return ".avi";
yield return ".wmv";
yield return ".sami";
yield return ".smi";
yield return ".adts";
yield return ".asf";
yield return ".3gp";
yield return ".3gp2";
yield return ".3gpp";
}

public VideoEncoderSettings DefaultVideoConfig() => new MFVideoEncoderSettings();

public AudioEncoderSettings DefaultAudioConfig() => new AudioEncoderSettings();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using Beutl.Extensibility;
using Beutl.Media.Encoding;

#if MF_BUILD_IN
namespace Beutl.Embedding.MediaFoundation.Encoding;
#else
namespace Beutl.Extensions.MediaFoundation.Encoding;
#endif

[Export]
public class MFEncodingExtension : EncodingExtension
{
public override string Name => "Media Foundation Encoder";

public override string DisplayName => "Media Foundation Encoder";

public override IEncoderInfo GetEncoderInfo() => new MFEncoderInfo();

public override void Load()
{
if (OperatingSystem.IsWindows())
{
EncoderRegistry.Register(GetEncoderInfo());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using Beutl.Media.Encoding;

#if MF_BUILD_IN
namespace Beutl.Embedding.MediaFoundation.Encoding;
#else
namespace Beutl.Extensions.MediaFoundation.Encoding;
#endif

public class MFVideoEncoderSettings : VideoEncoderSettings
{
public MFVideoFormat Format { get; set; }
}
117 changes: 117 additions & 0 deletions src/Beutl.Extensions.MediaFoundation/Encoding/MFVideoFormat.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
using SharpDX.MediaFoundation;

#if MF_BUILD_IN
namespace Beutl.Embedding.MediaFoundation.Encoding;
#else
namespace Beutl.Extensions.MediaFoundation.Encoding;
#endif

// MFVideoFormatとVideoFormatGuidsを相互に変換するクラス
public static class MFVideoFormatExtension
{
// MFVideoFormatからVideoFormatGuidsに変換する
public static Guid ToVideoFormatGuid(this MFVideoFormat format)
{
return format switch
{
MFVideoFormat.Wmv1 => VideoFormatGuids.Wmv1,
MFVideoFormat.Wmv2 => VideoFormatGuids.Wmv2,
MFVideoFormat.Wmv3 => VideoFormatGuids.Wmv3,
MFVideoFormat.Dvc => VideoFormatGuids.Dvc,
MFVideoFormat.Dv50 => VideoFormatGuids.Dv50,
MFVideoFormat.Dv25 => VideoFormatGuids.Dv25,
MFVideoFormat.H263 => VideoFormatGuids.H263,
MFVideoFormat.H264 => VideoFormatGuids.H264,
MFVideoFormat.H265 => VideoFormatGuids.H265,
MFVideoFormat.Hevc => VideoFormatGuids.Hevc,
MFVideoFormat.HevcEs => VideoFormatGuids.HevcEs,
MFVideoFormat.Vp80 => VideoFormatGuids.Vp80,
MFVideoFormat.Vp90 => VideoFormatGuids.Vp90,
MFVideoFormat.MultisampledS2 => VideoFormatGuids.MultisampledS2,
MFVideoFormat.M4S2 => VideoFormatGuids.M4S2,
MFVideoFormat.Wvc1 => VideoFormatGuids.Wvc1,
MFVideoFormat.P010 => VideoFormatGuids.P010,
MFVideoFormat.AI44 => VideoFormatGuids.AI44,
MFVideoFormat.Dvh1 => VideoFormatGuids.Dvh1,
MFVideoFormat.Dvhd => VideoFormatGuids.Dvhd,
MFVideoFormat.MultisampledS1 => VideoFormatGuids.MultisampledS1,
MFVideoFormat.Mp43 => VideoFormatGuids.Mp43,
MFVideoFormat.Mp4s => VideoFormatGuids.Mp4s,
MFVideoFormat.Mp4v => VideoFormatGuids.Mp4v,
MFVideoFormat.Mpg1 => VideoFormatGuids.Mpg1,
MFVideoFormat.Mjpg => VideoFormatGuids.Mjpg,
MFVideoFormat.Dvsl => VideoFormatGuids.Dvsl,
MFVideoFormat.YUY2 => VideoFormatGuids.YUY2,
MFVideoFormat.Yv12 => VideoFormatGuids.Yv12,
MFVideoFormat.P016 => VideoFormatGuids.P016,
MFVideoFormat.P210 => VideoFormatGuids.P210,
MFVideoFormat.P216 => VideoFormatGuids.P216,
MFVideoFormat.I420 => VideoFormatGuids.I420,
MFVideoFormat.Dvsd => VideoFormatGuids.Dvsd,
MFVideoFormat.Y42T => VideoFormatGuids.Y42T,
MFVideoFormat.NV12 => VideoFormatGuids.NV12,
MFVideoFormat.NV11 => VideoFormatGuids.NV11,
MFVideoFormat.Y210 => VideoFormatGuids.Y210,
MFVideoFormat.Y216 => VideoFormatGuids.Y216,
MFVideoFormat.Y410 => VideoFormatGuids.Y410,
MFVideoFormat.Y416 => VideoFormatGuids.Y416,
MFVideoFormat.Y41P => VideoFormatGuids.Y41P,
MFVideoFormat.Y41T => VideoFormatGuids.Y41T,
MFVideoFormat.Yvu9 => VideoFormatGuids.Yvu9,
MFVideoFormat.Yvyu => VideoFormatGuids.Yvyu,
MFVideoFormat.Iyuv => VideoFormatGuids.Iyuv,
_ => throw new ArgumentOutOfRangeException(nameof(format), format, null)
};
}
}

public enum MFVideoFormat
{
// SharpDX.MediaFoundation.VideoFormatGuidsから作成
Wmv1,
Wmv2,
Wmv3,
Dvc,
Dv50,
Dv25,
H263,
H264,
H265,
Hevc,
HevcEs,
Vp80,
Vp90,
MultisampledS2,
M4S2,
Wvc1,
P010,
AI44,
Dvh1,
Dvhd,
MultisampledS1,
Mp43,
Mp4s,
Mp4v,
Mpg1,
Mjpg,
Dvsl,
YUY2,
Yv12,
P016,
P210,
P216,
I420,
Dvsd,
Y42T,
NV12,
NV11,
Y210,
Y216,
Y410,
Y416,
Y41P,
Y41T,
Yvu9,
Yvyu,
Iyuv
}
114 changes: 114 additions & 0 deletions src/Beutl.Extensions.MediaFoundation/Encoding/MFWriter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
using Beutl.Media;
using Beutl.Media.Encoding;
using Beutl.Media.Music;
using Beutl.Media.Pixel;
using SharpDX;
using SharpDX.Direct3D9;
using SharpDX.MediaFoundation;
using SharpDX.Multimedia;
using SharpDX.Win32;

#if MF_BUILD_IN
namespace Beutl.Embedding.MediaFoundation.Encoding;
#else
namespace Beutl.Extensions.MediaFoundation.Encoding;
#endif

// MediaFoundationを使用して、Bitmapから動画を作成するクラス
public unsafe class MFWriter : MediaWriter
{
private SinkWriter _sinkWriter;
private int _videoStreamIndex;

public MFWriter(string file, MFVideoEncoderSettings videoConfig, AudioEncoderSettings audioConfig)
: base(videoConfig, audioConfig)
{
// sinkwriterを初期化
_sinkWriter = MediaFactory.CreateSinkWriterFromURL(file, null, null);
_videoStreamIndex = ConfigureVideoEncoder(videoConfig);

_sinkWriter.BeginWriting();
}

// IMFMediaTypeを作成
private static MediaType CreateMediaTypeFromSubtype(Guid subtype, int width, int height, double rate)
{
var mediaType = new MediaType();
mediaType.Set(MediaTypeAttributeKeys.MajorType, MediaTypeGuids.Video);
mediaType.Set(MediaTypeAttributeKeys.Subtype, subtype);
mediaType.Set(MediaTypeAttributeKeys.InterlaceMode, (int)VideoInterlaceMode.Progressive);
mediaType.Set(MediaTypeAttributeKeys.FrameSize, ((long)width << 32) | (uint)height);
mediaType.Set(MediaTypeAttributeKeys.FrameRate, ((long)(int)(rate * 10000000) << 32 | 10000000));
return mediaType;
}

// ConfigureVideoEncoder
private int ConfigureVideoEncoder(MFVideoEncoderSettings videoConfig)
{
using var outputType = CreateMediaTypeFromSubtype(
videoConfig.Format.ToVideoFormatGuid(),
videoConfig.DestinationSize.Width,
videoConfig.DestinationSize.Height,
videoConfig.FrameRate.ToDouble());
outputType.Set(MediaTypeAttributeKeys.AvgBitrate, videoConfig.Bitrate);
_sinkWriter.AddStream(outputType, out int streamIndex);

// InputType
using var inputType = CreateMediaTypeFromSubtype(
VideoFormatGuids.Argb32,
videoConfig.SourceSize.Width,
videoConfig.SourceSize.Height,
videoConfig.FrameRate.ToDouble());
_sinkWriter.SetInputMediaType(streamIndex, inputType, null);

return streamIndex;
}

public override long NumberOfFrames { get; }

public override long NumberOfSamples { get; }

public override bool AddVideo(IBitmap image)
{
bool requireDispose = false;

if (image is not Bitmap<Bgra8888>)
{
image = image.Convert<Bgra8888>();
requireDispose = true;
}

try
{
using var buffer = MediaFactory.CreateMemoryBuffer(image.ByteCount);
IntPtr ptr = buffer.Lock(out _, out _);
Buffer.MemoryCopy((void*)image.Data, (void*)ptr, image.ByteCount, image.ByteCount);
buffer.Unlock();
buffer.CurrentLength = image.ByteCount;

using var sample = MediaFactory.CreateSample();
sample.AddBuffer(buffer);

_sinkWriter.WriteSample(_videoStreamIndex, sample);

return true;
}
finally
{
if (requireDispose)
image.Dispose();
}
}

public override bool AddAudio(IPcm sound)
{
return false;
}

protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
_sinkWriter.Finalize();
_sinkWriter.Dispose();
}
}