-
Notifications
You must be signed in to change notification settings - Fork 5
/
NDIRender.cs
189 lines (168 loc) · 7.45 KB
/
NDIRender.cs
1
2
3
4
5
6
7
8
9
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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
using NewTek.NDI;
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Threading;
using System.Threading.Tasks;
namespace NDI_SubTitle
{
public class NDIRender
{
public const int Alpha_Max = 255;
public const int Alpha_Min = 255;
private static readonly object syncLock = new object();
private readonly CancellationToken cancellationToken;
private readonly VideoFrame videoFrame;
private Bitmap bmp;
private Graphics graphics;
private Font font;
private SolidBrush defaultBrush;
private static Point Point_Sub1 = new Point(90, 20);
private static Point Point_Sub2 = new Point(90, 90);
private SubTitle program;
private SubTitle fading;
private bool isFading;
private int Fading_X;
private int delta_X;
private RenderConfig Config;
public NDIRender(CancellationToken cancellationToken, RenderConfig config, FontFamily fontFamily)
{
this.cancellationToken = cancellationToken;
this.Config = config;
// We are going to create a 1920x180 frame at 50p, progressive (default).
this.videoFrame = new VideoFrame(config.Width, config.Height, config.aspectRatio, config.frameRateNumerator, config.frameRateDenominator);
bmp = new Bitmap(videoFrame.Width, videoFrame.Height, videoFrame.Stride,
System.Drawing.Imaging.PixelFormat.Format32bppArgb, videoFrame.BufferPtr);
graphics = Graphics.FromImage(bmp);
graphics.SmoothingMode = SmoothingMode.HighQuality;
// NDI Renderer
program = SubTitle.Empty;
fading = SubTitle.Empty;
// Style
defaultBrush = new SolidBrush(config.Default_Color);
font = new Font(fontFamily, config.fontSize);
// Fade Setting
isFading = false;
// For delta_X, We plus delta_X into alpha
// Fade Effect will last for 500ms
// So every frame it will change 255/(50frames/2steps) = 10
delta_X = Alpha_Max / (config.frameRateNumerator / config.frameRateDenominator) * 2;
// If Fading_X < Alpha_Max / 2 ,it's the former part (from program to empty)
// If Fading_X >= Alpha_Max / 2 ,it's the latter part (from empty to fading )
// If Fading_X >= Alpha_Max ,it means fade ends (set program as fading)
Fading_X = 0;
}
public void Cut(SubTitle sub)
{
lock (syncLock)
{
if (isFading)
return;
program = sub;
}
}
public void Fade(SubTitle sub)
{
lock (syncLock)
{
if (isFading)
return;
fading = sub.Clone();
fading.alpha = Alpha_Min;
isFading = true;
Fading_X = 0; //Reset Fading_X
}
}
public void ChangeFont(FontFamily fontFamily)
{
lock (syncLock)
font = new Font(fontFamily, Config.fontSize);
}
private void DrawFrame()
{
lock (syncLock)
{
graphics.Clear(Color.Transparent);
if (!isFading)
{ // As Normal
graphics.DrawString(program.First_Sub, font, defaultBrush, Point_Sub1);
graphics.DrawString(program.Second_Sub, font, defaultBrush, Point_Sub2);
}
else
{ // BUG !!!!
// FADING
if (Fading_X >= Alpha_Max)
{
Fading_X = 255;
isFading = false; //end fade
program = fading;
}
if (Fading_X < Alpha_Max / 2) //Alpha_Max/2 = 127.5
{
// from program to empty
// It's 0 -> 127 , but alpha should be 255 -> 0
// So we use 255 - Fading_X * 2
int alpha = Alpha_Max - Fading_X * 2;
if (alpha < 0)
alpha = 0;
var brush = new SolidBrush(Color.FromArgb(alpha, Color.White));
graphics.DrawString(program.First_Sub, font, brush, Point_Sub1);
graphics.DrawString(program.Second_Sub, font, brush, Point_Sub2);
}
else if (Fading_X >= Alpha_Max / 2)
{
// from empty to fading
// It's 128 -> 255,but alpha should be 0 -> 255
// So we use Fading_X * 2 - 255
int alpha = Fading_X * 2 - Alpha_Max;
if (alpha > 255)
alpha = 255;
var brush = new SolidBrush(Color.FromArgb(alpha, Color.White));
graphics.DrawString(fading.First_Sub, font, brush, Point_Sub1);
graphics.DrawString(fading.Second_Sub, font, brush, Point_Sub2);
}
Fading_X += delta_X;
}
}
}
public async Task<int> Run()
{
// Note that some of these using statements are sharing the same scope and
// will be disposed together simply because I dislike deeply nested scopes.
// You can manually handle .Dispose() for longer lived objects or use any pattern you prefer.
// When creating the sender use the Managed NDIlib Send example as the failover for this sender
// Therefore if you run both examples and then close this one it demonstrates failover in action
string failoverName = string.Format("{0} (NDIlib Send Example)", System.Net.Dns.GetHostName());
// this will show up as a source named "Example" with all other settings at their defaults
using (Sender sendInstance = new Sender("Example", true, false, null, failoverName))
{
// We will send 10000 frames of video.
while (!cancellationToken.IsCancellationRequested)
{
// are we connected to anyone?
if (sendInstance.GetConnections(100) < 1)
{
// no point rendering
Console.WriteLine("No current connections, so no rendering needed.");
// Wait a bit, otherwise our limited example will end before you can connect to it
System.Threading.Thread.Sleep(50);
}
else
{
// We now submit the frame. Note that this call will be clocked so that we end up submitting at exactly 29.97fps.
DrawFrame();
sendInstance.Send(videoFrame);
}
} // using sendInstance
sendInstance.Dispose();
return 0;
} // Main
}
internal void onChanged(RenderConfig config)
{
lock (syncLock)
font = new Font(font.FontFamily, config.fontSize);
delta_X = Alpha_Max / ((Config.frameRateNumerator / Config.frameRateDenominator) * config.Fade_Time / 1000);
}
}
}