1 // Copyright (C) 2015 Kazuhiro Fujieda <fujieda@users.osdn.me>
3 // This program is part of BurageSnap.
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.
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.
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/>.
19 using System.Collections;
20 using System.Collections.Generic;
22 using System.Drawing.Imaging;
24 using System.Runtime.InteropServices;
25 using System.Threading;
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; }
40 public Recorder(Config config)
47 SaveFrame(CaptureFrame(true));
52 if (_config.RingBuffer == 0)
53 SaveFrame(CaptureFrame(true));
56 _ringBuffer.Size = _config.RingBuffer;
57 AddFrame(CaptureFrame(true));
60 _timeProc = TimerCallback; // avoid to be collected by GC
61 _timerId = timeSetEvent(_config.Interval == 0 ? 1u : (uint)_config.Interval, 0, _timeProc, ref dummy, 1);
67 timeKillEvent(_timerId);
68 if (_config.RingBuffer != 0)
72 private void AddFrame(Frame frame)
74 _ringBuffer.Add(frame);
77 private void SaveRingBuffer()
79 foreach (var frame in _ringBuffer)
84 [DllImport("winmm.dll")]
85 private static extern uint timeSetEvent(uint delay, uint resolution, TimeProc timeProc,
86 ref uint user, uint eventType);
88 private delegate void TimeProc(uint timerId, uint msg, ref uint user, ref uint rsv1, uint rsv2);
90 [DllImport("winmm.dll")]
91 private static extern uint timeKillEvent(uint timerId);
93 private void TimerCallback(uint timerId, uint msg, ref uint user, ref uint rsv1, uint rsv2)
95 if (!Monitor.TryEnter(_lockObj))
97 var frame = CaptureFrame();
100 timeKillEvent(timerId);
103 if (_config.RingBuffer == 0)
107 Monitor.Exit(_lockObj);
110 private Frame CaptureFrame(bool initial = false)
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};
120 private void SaveFrame(Frame frame)
124 var dir = Path.Combine(_config.Folder, frame.Time.ToString(DateFormat));
127 Directory.CreateDirectory(dir);
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);
140 private class RingBuffer : IEnumerable<Frame>
142 private readonly Frame[] _buffer = new Frame[128];
143 private const int Mask = 128 - 1;
144 private int _top, _bottom;
146 public int Size { private get; set; }
148 public void Add(Frame frame)
150 var n = _bottom - _top;
155 if (_buffer[_top] != null)
157 _buffer[_top].Dispose();
158 _buffer[_top] = null;
160 _top = (_top + 1) & Mask;
162 _buffer[_bottom] = frame;
163 _bottom = (_bottom + 1) & Mask;
166 public IEnumerator<Frame> GetEnumerator()
168 for (var i = _top; i != _bottom; i = (i + 1) % Mask)
169 yield return _buffer[i];
172 IEnumerator IEnumerable.GetEnumerator()
174 return GetEnumerator();
179 for (var i = _top; i != _bottom; i = (i + 1) % Mask)
181 _buffer[i]?.Dispose();
188 private class Frame : IDisposable
190 public DateTime Time { get; set; }
191 public Bitmap Bitmap { get; set; }
193 public void Dispose()