| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373 |
- using System.Windows.Forms;
- using ScreenRecorder;
- var app = new RecorderForm();
- Application.Run(app);
- public class RecorderForm : Form
- {
- private readonly Button _btnStart;
- private readonly Button _btnStop;
- private readonly Label _lblStatus;
- private readonly Label _lblTimer;
- private readonly Label _lblFps;
- private readonly Label _lblQuality;
- private readonly TrackBar _trackFps;
- private readonly ComboBox _cmbQuality;
- private readonly Label _lblFpsVal;
- private readonly CheckBox _chkHideCursor;
- private readonly System.Windows.Forms.Timer _timer;
- private readonly ProgressBar _progressBar;
- private readonly Label _lblProgress;
- private CaptureService? _capture;
- private DateTime _startTime;
- private string _outputDir = Environment.GetFolderPath(Environment.SpecialFolder.MyVideos);
- private string? _lastVideoPath;
- private static string? FindFfmpeg()
- {
- var local = Path.Combine(AppContext.BaseDirectory, "ffmpeg.exe");
- if (File.Exists(local)) return local;
- foreach (var dir in (Environment.GetEnvironmentVariable("PATH") ?? "").Split(';'))
- {
- var p = Path.Combine(dir.Trim(), "ffmpeg.exe");
- if (File.Exists(p)) return p;
- }
- return null;
- }
- // Value: 0=无损H265, 1-100=MJPEG quality, 10/18=H264有损
- private static readonly (string Label, int Value, string FfmpegArgs)[] QualityOptions =
- [
- ("无损 (H.265 CRF 0, yuv444p)", 0, "-c:v libx265 -preset fast -crf 0 -pix_fmt yuv444p -tag:v hvc1"),
- ("极高画质 (H.264 CRF 10)", 10, "-c:v libx264 -preset fast -crf 10 -pix_fmt yuv420p"),
- ("高画质 (H.264 CRF 18)", 18, "-c:v libx264 -preset fast -crf 18 -pix_fmt yuv420p"),
- ("中画质 MJPEG 100", 100, ""),
- ("低画质 MJPEG 80", 80, ""),
- ("极低画质 MJPEG 50", 50, ""),
- ];
- public RecorderForm()
- {
- Text = "屏幕录制";
- Size = new System.Drawing.Size(360, 340);
- FormBorderStyle = FormBorderStyle.FixedSingle;
- MaximizeBox = false;
- StartPosition = FormStartPosition.CenterScreen;
- _lblStatus = new Label
- {
- Text = "就绪",
- Location = new System.Drawing.Point(20, 12),
- Size = new System.Drawing.Size(320, 22),
- Font = new System.Drawing.Font("微软雅黑", 10)
- };
- _lblTimer = new Label
- {
- Text = "00:00:00",
- Location = new System.Drawing.Point(20, 36),
- Size = new System.Drawing.Size(320, 26),
- Font = new System.Drawing.Font("Consolas", 15),
- ForeColor = System.Drawing.Color.DarkBlue
- };
- _lblFps = new Label
- {
- Text = "帧率 (fps):",
- Location = new System.Drawing.Point(20, 74),
- Size = new System.Drawing.Size(80, 20),
- Font = new System.Drawing.Font("微软雅黑", 9)
- };
- _trackFps = new TrackBar
- {
- Minimum = 1, Maximum = 60, Value = 30,
- TickFrequency = 5, SmallChange = 1, LargeChange = 5,
- Location = new System.Drawing.Point(100, 67),
- Size = new System.Drawing.Size(190, 40)
- };
- _lblFpsVal = new Label
- {
- Text = "30",
- Location = new System.Drawing.Point(295, 74),
- Size = new System.Drawing.Size(40, 20),
- Font = new System.Drawing.Font("Consolas", 10),
- ForeColor = System.Drawing.Color.DarkBlue
- };
- _trackFps.ValueChanged += (_, _) => _lblFpsVal.Text = _trackFps.Value.ToString();
- _lblQuality = new Label
- {
- Text = "画质:",
- Location = new System.Drawing.Point(20, 120),
- Size = new System.Drawing.Size(80, 20),
- Font = new System.Drawing.Font("微软雅黑", 9)
- };
- _cmbQuality = new ComboBox
- {
- Location = new System.Drawing.Point(100, 117),
- Size = new System.Drawing.Size(230, 26),
- DropDownStyle = ComboBoxStyle.DropDownList,
- Font = new System.Drawing.Font("微软雅黑", 9)
- };
- foreach (var (label, _, _) in QualityOptions)
- _cmbQuality.Items.Add(label);
- _cmbQuality.SelectedIndex = 0;
- _chkHideCursor = new CheckBox
- {
- Text = "隐藏鼠标光标",
- Location = new System.Drawing.Point(20, 155),
- Size = new System.Drawing.Size(150, 22),
- Font = new System.Drawing.Font("微软雅黑", 9),
- Checked = false
- };
- _btnStart = new Button
- {
- Text = "开始录制",
- Location = new System.Drawing.Point(20, 185),
- Size = new System.Drawing.Size(140, 38),
- BackColor = System.Drawing.Color.ForestGreen,
- ForeColor = System.Drawing.Color.White,
- FlatStyle = FlatStyle.Flat
- };
- _btnStart.Click += BtnStart_Click;
- _btnStop = new Button
- {
- Text = "停止录制",
- Location = new System.Drawing.Point(185, 185),
- Size = new System.Drawing.Size(140, 38),
- BackColor = System.Drawing.Color.Crimson,
- ForeColor = System.Drawing.Color.White,
- FlatStyle = FlatStyle.Flat,
- Enabled = false
- };
- _btnStop.Click += BtnStop_Click;
- _progressBar = new ProgressBar
- {
- Location = new System.Drawing.Point(20, 235),
- Size = new System.Drawing.Size(305, 16),
- Style = ProgressBarStyle.Continuous,
- Minimum = 0, Maximum = 100, Value = 0,
- Visible = false
- };
- _lblProgress = new Label
- {
- Location = new System.Drawing.Point(20, 254),
- Size = new System.Drawing.Size(305, 18),
- Font = new System.Drawing.Font("Consolas", 8),
- ForeColor = System.Drawing.Color.DimGray,
- Text = "",
- Visible = false
- };
- _timer = new System.Windows.Forms.Timer { Interval = 1000 };
- _timer.Tick += (_, _) => _lblTimer.Text = (DateTime.Now - _startTime).ToString(@"hh\:mm\:ss");
- Controls.AddRange(new Control[]
- {
- _lblStatus, _lblTimer,
- _lblFps, _trackFps, _lblFpsVal,
- _lblQuality, _cmbQuality,
- _chkHideCursor,
- _btnStart, _btnStop,
- _progressBar,
- _lblProgress
- });
- }
- private void BtnStart_Click(object? sender, EventArgs e)
- {
- var (_, qualityValue, _) = QualityOptions[_cmbQuality.SelectedIndex];
- var timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
- var videoPath = Path.Combine(_outputDir, $"recording_{timestamp}.avi");
- _lastVideoPath = videoPath;
- // H.264 模式先录无压缩 AVI,之后 ffmpeg 转码
- // MJPEG 模式直接录 MJPEG AVI
- int captureQuality = qualityValue <= 18 ? 101 : qualityValue; // 101 = 无压缩
- _capture = new CaptureService(fps: _trackFps.Value, quality: captureQuality, hideCursor: _chkHideCursor.Checked);
- _capture.Start(videoPath);
- _startTime = DateTime.Now;
- _timer.Start();
- _trackFps.Enabled = false;
- _cmbQuality.Enabled = false;
- _chkHideCursor.Enabled = false;
- _lblStatus.Text = $"录制中 → {Path.GetFileName(videoPath)}";
- _btnStart.Enabled = false;
- _btnStop.Enabled = true;
- }
- private void BtnStop_Click(object? sender, EventArgs e)
- {
- _timer.Stop();
- _capture?.Stop();
- _capture = null;
- _lblTimer.Text = "00:00:00";
- _btnStop.Enabled = false;
- var aviPath = _lastVideoPath;
- var (_, qualityValue, ffmpegArgs) = QualityOptions[_cmbQuality.SelectedIndex];
- bool needFfmpeg = qualityValue <= 18;
- if (aviPath != null && File.Exists(aviPath) && needFfmpeg)
- {
- var ffmpeg = FindFfmpeg();
- if (ffmpeg != null)
- {
- // 先获取视频总时长(秒),用于计算进度百分比
- double totalSeconds = GetAviDuration(ffmpeg, aviPath);
- _lblStatus.Text = "转码中,请稍候...";
- _progressBar.Value = 0;
- _progressBar.Visible = true;
- _lblProgress.Text = "";
- _lblProgress.Visible = true;
- var mp4Path = Path.ChangeExtension(aviPath, ".mp4");
- Task.Run(() =>
- {
- try
- {
- var psi = new System.Diagnostics.ProcessStartInfo
- {
- FileName = ffmpeg,
- Arguments = $"-y -i \"{aviPath}\" {ffmpegArgs} -progress pipe:1 \"{mp4Path}\"",
- CreateNoWindow = true,
- UseShellExecute = false,
- RedirectStandardOutput = true,
- RedirectStandardError = true
- };
- var proc = System.Diagnostics.Process.Start(psi)!;
- proc.ErrorDataReceived += (_, _) => { }; // 丢弃 stderr 避免阻塞
- proc.BeginErrorReadLine();
- // 逐行读取 stdout 进度
- string? line;
- while ((line = proc.StandardOutput.ReadLine()) != null)
- {
- // 格式: out_time_us=1234567
- if (line.StartsWith("out_time_us=") &&
- long.TryParse(line.AsSpan("out_time_us=".Length), out long us) &&
- totalSeconds > 0)
- {
- double done = us / 1_000_000.0;
- int pct = (int)Math.Min(100, done / totalSeconds * 100);
- string timeStr = TimeSpan.FromSeconds(done).ToString(@"hh\:mm\:ss");
- string totalStr = TimeSpan.FromSeconds(totalSeconds).ToString(@"hh\:mm\:ss");
- Invoke(() =>
- {
- _progressBar.Value = pct;
- _lblProgress.Text = $"{timeStr} / {totalStr} ({pct}%)";
- });
- }
- }
- proc.WaitForExit();
- bool ok = proc.ExitCode == 0 && File.Exists(mp4Path);
- if (ok) File.Delete(aviPath);
- Invoke(() =>
- {
- _progressBar.Visible = false;
- _lblProgress.Visible = false;
- _trackFps.Enabled = true;
- _cmbQuality.Enabled = true;
- _chkHideCursor.Enabled = true;
- _btnStart.Enabled = true;
- if (ok)
- {
- _lblStatus.Text = $"已保存至 {_outputDir}";
- MessageBox.Show($"录制完成:\n{mp4Path}", "完成",
- MessageBoxButtons.OK, MessageBoxIcon.Information);
- }
- else
- {
- _lblStatus.Text = "转码失败,AVI 已保留";
- MessageBox.Show($"转码失败,AVI 保留在:\n{aviPath}", "提示",
- MessageBoxButtons.OK, MessageBoxIcon.Warning);
- }
- });
- }
- catch (Exception ex)
- {
- Invoke(() =>
- {
- _progressBar.Visible = false;
- _lblProgress.Visible = false;
- _trackFps.Enabled = true;
- _cmbQuality.Enabled = true;
- _chkHideCursor.Enabled = true;
- _btnStart.Enabled = true;
- _lblStatus.Text = "转码出错";
- MessageBox.Show($"转码出错:{ex.Message}", "错误",
- MessageBoxButtons.OK, MessageBoxIcon.Error);
- });
- }
- });
- return;
- }
- else
- {
- MessageBox.Show(
- $"未找到 ffmpeg,无法转码。\nAVI 保留在:\n{aviPath}",
- "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
- }
- }
- else if (aviPath != null)
- {
- _lblStatus.Text = $"已保存至 {_outputDir}";
- MessageBox.Show($"录制完成:\n{aviPath}", "完成",
- MessageBoxButtons.OK, MessageBoxIcon.Information);
- }
- _trackFps.Enabled = true;
- _cmbQuality.Enabled = true;
- _chkHideCursor.Enabled = true;
- _btnStart.Enabled = true;
- }
- // 用 ffprobe 思路:让 ffmpeg 读文件头获取时长
- private static double GetAviDuration(string ffmpeg, string path)
- {
- try
- {
- var psi = new System.Diagnostics.ProcessStartInfo
- {
- FileName = ffmpeg,
- Arguments = $"-i \"{path}\"",
- CreateNoWindow = true,
- UseShellExecute = false,
- RedirectStandardError = true
- };
- var proc = System.Diagnostics.Process.Start(psi)!;
- string err = proc.StandardError.ReadToEnd();
- proc.WaitForExit();
- // 解析 "Duration: HH:MM:SS.xx"
- var m = System.Text.RegularExpressions.Regex.Match(err,
- @"Duration:\s*(\d+):(\d+):(\d+\.\d+)");
- if (m.Success)
- {
- double h = double.Parse(m.Groups[1].Value);
- double min = double.Parse(m.Groups[2].Value);
- double sec = double.Parse(m.Groups[3].Value);
- return h * 3600 + min * 60 + sec;
- }
- }
- catch { }
- return 0;
- }
- protected override void OnFormClosing(FormClosingEventArgs e)
- {
- _capture?.Dispose();
- base.OnFormClosing(e);
- }
- }
|