11using System . Reactive . Subjects ;
2+ using System . Threading . Channels ;
3+ using Beutl . Configuration ;
24using Beutl . Graphics . Rendering ;
5+ using Beutl . Logging ;
36using Beutl . Media ;
47using Beutl . Media . Pixel ;
58using Beutl . ProjectSystem ;
9+ using Microsoft . Extensions . Logging ;
610
711namespace Beutl . Models ;
812
9- public class FrameProviderImpl ( Scene scene , Rational rate , SceneRenderer renderer , Subject < TimeSpan > progress )
10- : IFrameProvider
13+ public sealed class FrameProviderImpl : IFrameProvider , IDisposable
1114{
12- public long FrameCount => ( long ) ( scene . Duration . TotalSeconds * rate . ToDouble ( ) ) ;
15+ private readonly ILogger _logger = Log . CreateLogger < FrameProviderImpl > ( ) ;
16+ private readonly Scene _scene ;
17+ private readonly Rational _rate ;
18+ private readonly SceneRenderer _renderer ;
19+ private readonly Subject < TimeSpan > _progress ;
20+ private readonly Channel < ( long Frame , Bitmap < Bgra8888 > Bitmap ) > _channel ;
21+ private readonly CancellationTokenSource _cts = new ( ) ;
22+ private readonly Task _producerTask ;
23+ private bool _disposed ;
1324
14- public Rational FrameRate => rate ;
25+ public FrameProviderImpl ( Scene scene , Rational rate , SceneRenderer renderer , Subject < TimeSpan > progress )
26+ {
27+ _scene = scene ;
28+ _rate = rate ;
29+ _renderer = renderer ;
30+ _progress = progress ;
31+
32+ int bufferSize = Preferences . Default . Get ( "Output.FrameBufferSize" , 100 ) ;
33+ _channel = Channel . CreateBounded < ( long Frame , Bitmap < Bgra8888 > Bitmap ) > (
34+ new BoundedChannelOptions ( bufferSize )
35+ {
36+ FullMode = BoundedChannelFullMode . Wait ,
37+ SingleReader = true ,
38+ SingleWriter = true ,
39+ } ) ;
40+
41+ _producerTask = Task . Run ( RenderFramesAsync , _cts . Token ) ;
42+ }
43+
44+ public long FrameCount => ( long ) ( _scene . Duration . TotalSeconds * _rate . ToDouble ( ) ) ;
45+
46+ public Rational FrameRate => _rate ;
1547
1648 private Bitmap < Bgra8888 > RenderCore ( TimeSpan time )
1749 {
1850 int retry = 0 ;
1951 Retry :
20- if ( renderer . Render ( time + scene . Start ) )
52+ if ( _renderer . Render ( time + _scene . Start ) )
2153 {
22- return renderer . Snapshot ( ) ;
54+ return _renderer . Snapshot ( ) ;
2355 }
2456
2557 if ( retry > 3 )
@@ -29,25 +61,96 @@ private Bitmap<Bgra8888> RenderCore(TimeSpan time)
2961 goto Retry ;
3062 }
3163
32- public async ValueTask < Bitmap < Bgra8888 > > RenderFrame ( long frame )
64+ private async ValueTask < Bitmap < Bgra8888 > > RenderFrameCore ( long frame , CancellationToken cancellationToken )
3365 {
3466 // rate.Numerator, rate.Denominatorを使ってできるだけ正確に
3567 // (frame / (rate.Numerator / rate.Denominator)) * TimeSpan.TicksPerSecond
36- var time = TimeSpan . FromTicks ( frame * rate . Denominator * TimeSpan . TicksPerSecond / rate . Numerator ) ;
68+ var time = TimeSpan . FromTicks ( frame * _rate . Denominator * TimeSpan . TicksPerSecond / _rate . Numerator ) ;
69+
70+ if ( RenderThread . Dispatcher . CheckAccess ( ) )
71+ {
72+ return RenderCore ( time ) ;
73+ }
74+ else
75+ {
76+ return await RenderThread . Dispatcher . InvokeAsync ( ( ) => RenderCore ( time ) , ct : cancellationToken ) ;
77+ }
78+ }
79+
80+ private async Task RenderFramesAsync ( )
81+ {
3782 try
3883 {
39- if ( RenderThread . Dispatcher . CheckAccess ( ) )
84+ for ( long frame = 0 ; frame < FrameCount && ! _cts . Token . IsCancellationRequested ; frame ++ )
4085 {
41- return RenderCore ( time ) ;
86+ var bitmap = await RenderFrameCore ( frame , _cts . Token ) ;
87+ await _channel . Writer . WriteAsync ( ( frame , bitmap ) , _cts . Token ) ;
4288 }
43- else
89+ }
90+ catch ( OperationCanceledException )
91+ {
92+ // Ignore cancellation
93+ }
94+ catch ( Exception ex )
95+ {
96+ _logger . LogError ( ex , "An error occurred while rendering frames." ) ;
97+ _channel . Writer . TryComplete ( ex ) ;
98+ return ;
99+ }
100+
101+ _logger . LogDebug ( "Frame rendering completed." ) ;
102+ _channel . Writer . TryComplete ( ) ;
103+ }
104+
105+ public async ValueTask < Bitmap < Bgra8888 > > RenderFrame ( long frame )
106+ {
107+ ObjectDisposedException . ThrowIf ( _disposed , this ) ;
108+
109+ var time = TimeSpan . FromTicks ( frame * _rate . Denominator * TimeSpan . TicksPerSecond / _rate . Numerator ) ;
110+ _progress . OnNext ( time ) ;
111+
112+ while ( await _channel . Reader . WaitToReadAsync ( _cts . Token ) )
113+ {
114+ if ( _channel . Reader . TryRead ( out var item ) )
44115 {
45- return await RenderThread . Dispatcher . InvokeAsync ( ( ) => RenderCore ( time ) ) ;
116+ if ( item . Frame == frame )
117+ {
118+ return item . Bitmap ;
119+ }
120+
121+ item . Bitmap . Dispose ( ) ;
122+ _logger . LogWarning ( "The frame is misaligned. Requested frame: {RequestedFrame}, Received frame: {ReceivedFrame}" , frame , item . Frame ) ;
123+ return await RenderFrameCore ( frame , _cts . Token ) ;
46124 }
47125 }
48- finally
126+
127+ _logger . LogWarning ( "The frame could not be read from the channel. Frame: {Frame}" , frame ) ;
128+ return await RenderFrameCore ( frame , _cts . Token ) ;
129+ }
130+
131+ public void Dispose ( )
132+ {
133+ if ( _disposed ) return ;
134+ _disposed = true ;
135+ _cts . Cancel ( ) ;
136+
137+ if ( ! _producerTask . IsCompleted )
138+ {
139+ try
140+ {
141+ _producerTask . Wait ( ) ;
142+ }
143+ catch
144+ {
145+ // ignore
146+ }
147+ }
148+
149+ while ( _channel . Reader . TryRead ( out var item ) )
49150 {
50- progress . OnNext ( time ) ;
151+ item . Bitmap . Dispose ( ) ;
51152 }
153+
154+ _cts . Dispose ( ) ;
52155 }
53156}
0 commit comments