OSDN Git Service

Refactor FormMain into some classes
[buragesnap/BurageSnap.git] / BurageSnap / Recorder.cs
1 // Copyright (C) 2015 Kazuhiro Fujieda <fujieda@users.osdn.me>
2 //
3 // This program is part of BurageSnap.
4 //
5 // BurageSnap is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, see <http://www.gnu.org/licenses/>.
17
18 using System;
19 using System.Collections;
20 using System.Collections.Generic;
21 using System.Drawing;
22 using System.Drawing.Imaging;
23 using System.IO;
24 using System.Runtime.InteropServices;
25 using System.Threading;
26
27 namespace BurageSnap
28 {
29     public class Recorder
30     {
31         public const string DateFormat = "yyyy-MM-dd";
32         private readonly Config _config;
33         private readonly Capture _screenCapture = new Capture();
34         private readonly RingBuffer _ringBuffer = new RingBuffer();
35         private uint _timerId;
36         private TimeProc _timeProc;
37         private readonly object _lockObj = new object();
38         public Action<DateTime> ReportCaptureTime { private get; set; }
39
40         public Recorder(Config config)
41         {
42             _config = config;
43         }
44
45         public void OneShot()
46         {
47             SaveFrame(CaptureFrame(true));
48         }
49
50         public void Start()
51         {
52             if (_config.RingBuffer == 0)
53                 SaveFrame(CaptureFrame(true));
54             else
55             {
56                 _ringBuffer.Size = _config.RingBuffer;
57                 AddFrame(CaptureFrame(true));
58             }
59             var dummy = 0u;
60             _timeProc = TimerCallback; // avoid to be collected by GC
61             _timerId = timeSetEvent(_config.Interval == 0 ? 1u : (uint)_config.Interval, 0, _timeProc, ref dummy, 1);
62         }
63
64         public void Stop()
65         {
66             if (_timerId != 0)
67                 timeKillEvent(_timerId);
68             if (_config.RingBuffer != 0)
69                 SaveRingBuffer();
70         }
71
72         private void AddFrame(Frame frame)
73         {
74             _ringBuffer.Add(frame);
75         }
76
77         private void SaveRingBuffer()
78         {
79             foreach (var frame in _ringBuffer)
80                 SaveFrame(frame);
81             _ringBuffer.Clear();
82         }
83
84         [DllImport("winmm.dll")]
85         private static extern uint timeSetEvent(uint delay, uint resolution, TimeProc timeProc,
86             ref uint user, uint eventType);
87
88         private delegate void TimeProc(uint timerId, uint msg, ref uint user, ref uint rsv1, uint rsv2);
89
90         [DllImport("winmm.dll")]
91         private static extern uint timeKillEvent(uint timerId);
92
93         private void TimerCallback(uint timerId, uint msg, ref uint user, ref uint rsv1, uint rsv2)
94         {
95             if (!Monitor.TryEnter(_lockObj))
96                 return;
97             var frame = CaptureFrame();
98             if (frame == null)
99             {
100                 timeKillEvent(timerId);
101                 return;
102             }
103             if (_config.RingBuffer == 0)
104                 SaveFrame(frame);
105             else
106                 AddFrame(frame);
107             Monitor.Exit(_lockObj);
108         }
109
110         private Frame CaptureFrame(bool initial = false)
111         {
112             var bmp = initial
113                 ? _screenCapture.CaptureGameScreen(_config.TitleHistory[0])
114                 : _screenCapture.CaptureGameScreen();
115             var now = DateTime.Now;
116             ReportCaptureTime(now);
117             return new Frame {Time = now, Bitmap = bmp};
118         }
119
120         private void SaveFrame(Frame frame)
121         {
122             if (frame == null)
123                 return;
124             var dir = Path.Combine(_config.Folder, frame.Time.ToString(DateFormat));
125             try
126             {
127                 Directory.CreateDirectory(dir);
128             }
129             catch
130             {
131                 return;
132             }
133             var path = Path.Combine(dir, frame.Time.ToString("yyyy-MM-dd HH-mm-ss.fff") +
134                                          (_config.Format == OutputFormat.Jpg ? ".jpg" : ".png"));
135             using (var fs = File.OpenWrite(path))
136                 frame.Bitmap.Save(fs, _config.Format == OutputFormat.Jpg ? ImageFormat.Jpeg : ImageFormat.Png);
137             frame.Dispose();
138         }
139
140         private class RingBuffer : IEnumerable<Frame>
141         {
142             private readonly Frame[] _buffer = new Frame[128];
143             private const int Mask = 128 - 1;
144             private int _top, _bottom;
145
146             public int Size { private get; set; }
147
148             public void Add(Frame frame)
149             {
150                 var n = _bottom - _top;
151                 if (n < 0)
152                     n += _buffer.Length;
153                 if (n >= Size)
154                 {
155                     if (_buffer[_top] != null)
156                     {
157                         _buffer[_top].Dispose();
158                         _buffer[_top] = null;
159                     }
160                     _top = (_top + 1) & Mask;
161                 }
162                 _buffer[_bottom] = frame;
163                 _bottom = (_bottom + 1) & Mask;
164             }
165
166             public IEnumerator<Frame> GetEnumerator()
167             {
168                 for (var i = _top; i != _bottom; i = (i + 1) % Mask)
169                     yield return _buffer[i];
170             }
171
172             IEnumerator IEnumerable.GetEnumerator()
173             {
174                 return GetEnumerator();
175             }
176
177             public void Clear()
178             {
179                 for (var i = _top; i != _bottom; i = (i + 1) % Mask)
180                 {
181                     _buffer[i]?.Dispose();
182                     _buffer[i] = null;
183                 }
184                 _top = _bottom = 0;
185             }
186         }
187
188         private class Frame : IDisposable
189         {
190             public DateTime Time { get; set; }
191             public Bitmap Bitmap { get; set; }
192
193             public void Dispose()
194             {
195                 Bitmap.Dispose();
196             }
197         }
198     }
199 }