From 033469ff01a891e3d507cc91db1618a8c0825f6f Mon Sep 17 00:00:00 2001 From: Sour Date: Fri, 15 Mar 2019 12:48:34 -0400 Subject: [PATCH] Video: AVI recording --- Core/AviRecorder.cpp | 117 +++++++++ Core/AviRecorder.h | 45 ++++ Core/Core.vcxproj | 2 + Core/Core.vcxproj.filters | 6 + Core/SoundMixer.cpp | 53 ++-- Core/SoundResampler.cpp | 3 +- Core/VideoRenderer.cpp | 13 +- Core/VideoRenderer.h | 12 +- InteropDLL/InteropDLL.vcxproj | 1 + InteropDLL/InteropDLL.vcxproj.filters | 3 + InteropDLL/RecordApiWrapper.cpp | 13 + UI/Config/AviRecordConfig.cs | 21 ++ UI/Config/Configuration.cs | 2 + UI/Forms/{ => Tools}/frmLogWindow.cs | 0 UI/Forms/{ => Tools}/frmLogWindow.designer.cs | 0 UI/Forms/{ => Tools}/frmLogWindow.resx | 0 UI/Forms/Tools/frmRecordAvi.cs | 57 +++++ UI/Forms/Tools/frmRecordAvi.designer.cs | 239 ++++++++++++++++++ UI/Forms/Tools/frmRecordAvi.resx | 123 +++++++++ UI/Forms/frmMain.Designer.cs | 42 +++ UI/Forms/frmMain.cs | 21 ++ UI/Interop/RecordApi.cs | 29 +++ UI/UI.csproj | 17 +- 23 files changed, 778 insertions(+), 41 deletions(-) create mode 100644 Core/AviRecorder.cpp create mode 100644 Core/AviRecorder.h create mode 100644 InteropDLL/RecordApiWrapper.cpp create mode 100644 UI/Config/AviRecordConfig.cs rename UI/Forms/{ => Tools}/frmLogWindow.cs (100%) rename UI/Forms/{ => Tools}/frmLogWindow.designer.cs (100%) rename UI/Forms/{ => Tools}/frmLogWindow.resx (100%) create mode 100644 UI/Forms/Tools/frmRecordAvi.cs create mode 100644 UI/Forms/Tools/frmRecordAvi.designer.cs create mode 100644 UI/Forms/Tools/frmRecordAvi.resx create mode 100644 UI/Interop/RecordApi.cs diff --git a/Core/AviRecorder.cpp b/Core/AviRecorder.cpp new file mode 100644 index 0000000..d53af27 --- /dev/null +++ b/Core/AviRecorder.cpp @@ -0,0 +1,117 @@ +#include "stdafx.h" +#include "AviRecorder.h" +#include "MessageManager.h" +#include "Console.h" +#include "EmuSettings.h" + +AviRecorder::AviRecorder(shared_ptr console) +{ + _console = console; + _recording = false; + _stopFlag = false; + _frameBuffer = nullptr; + _frameBufferLength = 0; + _sampleRate = 0; +} + +AviRecorder::~AviRecorder() +{ + if(_recording) { + StopRecording(); + } + + if(_frameBuffer) { + delete[] _frameBuffer; + _frameBuffer = nullptr; + } +} + +uint32_t AviRecorder::GetFps() +{ + if(_console->GetRegion() == ConsoleRegion::Ntsc) { + return _console->GetSettings()->GetVideoConfig().IntegerFpsMode ? 60000000 : 60098812; + } else { + return _console->GetSettings()->GetVideoConfig().IntegerFpsMode ? 50000000 : 50006978; + } +} + +bool AviRecorder::StartRecording(string filename, VideoCodec codec, uint32_t width, uint32_t height, uint32_t bpp, uint32_t audioSampleRate, uint32_t compressionLevel) +{ + if(!_recording) { + _outputFile = filename; + _sampleRate = audioSampleRate; + _width = width; + _height = height; + _fps = GetFps(); + _frameBufferLength = height * width * bpp; + _frameBuffer = new uint8_t[_frameBufferLength]; + + _aviWriter.reset(new AviWriter()); + if(!_aviWriter->StartWrite(filename, codec, width, height, bpp, _fps, audioSampleRate, compressionLevel)) { + _aviWriter.reset(); + return false; + } + + _aviWriterThread = std::thread([=]() { + while(!_stopFlag) { + _waitFrame.Wait(); + if(_stopFlag) { + break; + } + + auto lock = _lock.AcquireSafe(); + _aviWriter->AddFrame(_frameBuffer); + } + }); + + MessageManager::DisplayMessage("VideoRecorder", "VideoRecorderStarted", _outputFile); + _recording = true; + } + return true; +} + +void AviRecorder::StopRecording() +{ + if(_recording) { + _recording = false; + + _stopFlag = true; + _waitFrame.Signal(); + _aviWriterThread.join(); + + _aviWriter->EndWrite(); + _aviWriter.reset(); + + MessageManager::DisplayMessage("VideoRecorder", "VideoRecorderStopped", _outputFile); + } +} + +void AviRecorder::AddFrame(void* frameBuffer, uint32_t width, uint32_t height) +{ + if(_recording) { + if(_width != width || _height != height || _fps != GetFps()) { + StopRecording(); + } else { + auto lock = _lock.AcquireSafe(); + memcpy(_frameBuffer, frameBuffer, _frameBufferLength); + _waitFrame.Signal(); + } + } +} + +void AviRecorder::AddSound(int16_t* soundBuffer, uint32_t sampleCount, uint32_t sampleRate) +{ + if(_recording) { + if(_sampleRate != sampleRate) { + auto lock = _lock.AcquireSafe(); + StopRecording(); + } else { + _aviWriter->AddSound(soundBuffer, sampleCount); + } + } +} + +bool AviRecorder::IsRecording() +{ + return _recording; +} \ No newline at end of file diff --git a/Core/AviRecorder.h b/Core/AviRecorder.h new file mode 100644 index 0000000..8677991 --- /dev/null +++ b/Core/AviRecorder.h @@ -0,0 +1,45 @@ +#pragma once +#include "stdafx.h" +#include +#include "../Utilities/AutoResetEvent.h" +#include "../Utilities/AviWriter.h" +#include "../Utilities/SimpleLock.h" + +class Console; + +class AviRecorder +{ +private: + std::thread _aviWriterThread; + + unique_ptr _aviWriter; + shared_ptr _console; + + string _outputFile; + SimpleLock _lock; + AutoResetEvent _waitFrame; + + atomic _stopFlag; + bool _recording; + uint8_t* _frameBuffer; + uint32_t _frameBufferLength; + uint32_t _sampleRate; + + uint32_t _fps; + uint32_t _width; + uint32_t _height; + + uint32_t GetFps(); + +public: + AviRecorder(shared_ptr console); + virtual ~AviRecorder(); + + bool StartRecording(string filename, VideoCodec codec, uint32_t width, uint32_t height, uint32_t bpp, uint32_t audioSampleRate, uint32_t compressionLevel); + void StopRecording(); + + void AddFrame(void* frameBuffer, uint32_t width, uint32_t height); + void AddSound(int16_t* soundBuffer, uint32_t sampleCount, uint32_t sampleRate); + + bool IsRecording(); +}; \ No newline at end of file diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index 7cba715..eb5ccb6 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -43,6 +43,7 @@ + @@ -126,6 +127,7 @@ + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index 6bad133..a1b4f8c 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -242,6 +242,9 @@ Misc + + Misc + @@ -390,6 +393,9 @@ Misc + + Misc + diff --git a/Core/SoundMixer.cpp b/Core/SoundMixer.cpp index 3c5c65a..f480c29 100644 --- a/Core/SoundMixer.cpp +++ b/Core/SoundMixer.cpp @@ -4,6 +4,7 @@ #include "EmuSettings.h" #include "SoundResampler.h" #include "RewindManager.h" +#include "VideoRenderer.h" #include "../Utilities/Equalizer.h" #include "../Utilities/blip_buf.h" @@ -47,35 +48,45 @@ void SoundMixer::StopAudio(bool clearBuffer) void SoundMixer::PlayAudioBuffer(int16_t* samples, uint32_t sampleCount) { - if(_audioDevice) { - AudioConfig cfg = _console->GetSettings()->GetAudioConfig(); + AudioConfig cfg = _console->GetSettings()->GetAudioConfig(); - if(!cfg.EnableAudio) { - _audioDevice->Stop(); - return; + if(cfg.EnableEqualizer) { + ProcessEqualizer(samples, sampleCount); + } + + if(cfg.MasterVolume != 25) { + //Apply volume if not using the default value + for(uint32_t i = 0; i < sampleCount * 2; i++) { + samples[i] = (int32_t)samples[i] * (int32_t)cfg.MasterVolume / 25; + } + } + + shared_ptr rewindManager = _console->GetRewindManager(); + if(rewindManager && rewindManager->SendAudio(samples, (uint32_t)sampleCount)) { + int16_t *out = nullptr; + uint32_t count = 0; + if(cfg.SampleRate == SoundResampler::SpcSampleRate && cfg.DisableDynamicSampleRate) { + out = samples; + count = sampleCount; + } else { + count = _resampler->Resample(samples, sampleCount, cfg.SampleRate, _sampleBuffer); + out = _sampleBuffer; } - if(cfg.EnableEqualizer) { - ProcessEqualizer(samples, sampleCount); + bool isRecording = _console->GetVideoRenderer()->IsRecording() /* TODO || _waveRecorder*/; + if(isRecording) { + _console->GetVideoRenderer()->AddRecordingSound(out, count, cfg.SampleRate); } - if(cfg.MasterVolume != 25) { - //Apply volume if not using the default value - for(uint32_t i = 0; i < sampleCount * 2; i++) { - samples[i] = (int32_t)samples[i] * (int32_t)cfg.MasterVolume / 25; + if(_audioDevice) { + if(!cfg.EnableAudio) { + _audioDevice->Stop(); + return; } - } - shared_ptr rewindManager = _console->GetRewindManager(); - if(rewindManager && rewindManager->SendAudio(samples, (uint32_t)sampleCount)) { - if(cfg.SampleRate == SoundResampler::SpcSampleRate && cfg.DisableDynamicSampleRate) { - _audioDevice->PlayBuffer(samples, sampleCount, cfg.SampleRate, true); - } else { - uint32_t resampledCount = _resampler->Resample(samples, sampleCount, cfg.SampleRate, _sampleBuffer); - _audioDevice->PlayBuffer(_sampleBuffer, resampledCount, cfg.SampleRate, true); - } + _audioDevice->PlayBuffer(out, count, cfg.SampleRate, true); + _audioDevice->ProcessEndOfFrame(); } - _audioDevice->ProcessEndOfFrame(); } } diff --git a/Core/SoundResampler.cpp b/Core/SoundResampler.cpp index 207238d..facfbc6 100644 --- a/Core/SoundResampler.cpp +++ b/Core/SoundResampler.cpp @@ -3,6 +3,7 @@ #include "Console.h" #include "EmuSettings.h" #include "SoundMixer.h" +#include "VideoRenderer.h" #include "../Utilities/blip_buf.h" SoundResampler::SoundResampler(Console *console) @@ -26,7 +27,7 @@ double SoundResampler::GetRateAdjustment() double SoundResampler::GetTargetRateAdjustment() { AudioConfig cfg = _console->GetSettings()->GetAudioConfig(); - bool isRecording = false; //TODO _waveRecorder || _console->GetVideoRenderer()->IsRecording(); + bool isRecording = _console->GetVideoRenderer()->IsRecording() /* TODO || _waveRecorder */; if(!isRecording && !cfg.DisableDynamicSampleRate) { //Don't deviate from selected sample rate while recording //TODO: Have 2 output streams (one for recording, one for the speakers) diff --git a/Core/VideoRenderer.cpp b/Core/VideoRenderer.cpp index 900a801..7d716d4 100644 --- a/Core/VideoRenderer.cpp +++ b/Core/VideoRenderer.cpp @@ -1,9 +1,10 @@ #include "stdafx.h" #include "IRenderingDevice.h" #include "VideoRenderer.h" -//#include "AviRecorder.h" +#include "AviRecorder.h" #include "VideoDecoder.h" #include "Console.h" +#include "EmuSettings.h" VideoRenderer::VideoRenderer(shared_ptr console) { @@ -58,11 +59,10 @@ void VideoRenderer::RenderThread() void VideoRenderer::UpdateFrame(void *frameBuffer, uint32_t width, uint32_t height) { - //TODO - /*shared_ptr aviRecorder = _aviRecorder; + shared_ptr aviRecorder = _aviRecorder; if(aviRecorder) { aviRecorder->AddFrame(frameBuffer, width, height); - }*/ + } if(_renderer) { _renderer->UpdateFrame(frameBuffer, width, height); @@ -84,14 +84,12 @@ void VideoRenderer::UnregisterRenderingDevice(IRenderingDevice *renderer) } } -//TODO -/* void VideoRenderer::StartRecording(string filename, VideoCodec codec, uint32_t compressionLevel) { shared_ptr recorder(new AviRecorder(_console)); FrameInfo frameInfo = _console->GetVideoDecoder()->GetFrameInfo(); - if(recorder->StartRecording(filename, codec, frameInfo.Width, frameInfo.Height, frameInfo.BitsPerPixel, _console->GetSettings()->GetSampleRate(), compressionLevel)) { + if(recorder->StartRecording(filename, codec, frameInfo.Width, frameInfo.Height, 4, _console->GetSettings()->GetAudioConfig().SampleRate, compressionLevel)) { _aviRecorder = recorder; } } @@ -113,4 +111,3 @@ bool VideoRenderer::IsRecording() { return _aviRecorder != nullptr && _aviRecorder->IsRecording(); } -*/ \ No newline at end of file diff --git a/Core/VideoRenderer.h b/Core/VideoRenderer.h index 9d6683f..98c377d 100644 --- a/Core/VideoRenderer.h +++ b/Core/VideoRenderer.h @@ -6,9 +6,8 @@ class IRenderingDevice; class Console; -//TODO -//class AviRecorder; -//enum class VideoCodec; +class AviRecorder; +enum class VideoCodec; class VideoRenderer { @@ -20,8 +19,7 @@ private: IRenderingDevice* _renderer = nullptr; atomic _stopFlag; - //TODO - //shared_ptr _aviRecorder; + shared_ptr _aviRecorder; void RenderThread(); @@ -36,10 +34,8 @@ public: void RegisterRenderingDevice(IRenderingDevice *renderer); void UnregisterRenderingDevice(IRenderingDevice *renderer); - //TODO - /* void StartRecording(string filename, VideoCodec codec, uint32_t compressionLevel); void AddRecordingSound(int16_t* soundBuffer, uint32_t sampleCount, uint32_t sampleRate); void StopRecording(); - bool IsRecording();*/ + bool IsRecording(); }; \ No newline at end of file diff --git a/InteropDLL/InteropDLL.vcxproj b/InteropDLL/InteropDLL.vcxproj index dfd901a..065cc03 100644 --- a/InteropDLL/InteropDLL.vcxproj +++ b/InteropDLL/InteropDLL.vcxproj @@ -457,6 +457,7 @@ + Create Create diff --git a/InteropDLL/InteropDLL.vcxproj.filters b/InteropDLL/InteropDLL.vcxproj.filters index 6111d12..fde6db4 100644 --- a/InteropDLL/InteropDLL.vcxproj.filters +++ b/InteropDLL/InteropDLL.vcxproj.filters @@ -37,5 +37,8 @@ Source Files + + Source Files + \ No newline at end of file diff --git a/InteropDLL/RecordApiWrapper.cpp b/InteropDLL/RecordApiWrapper.cpp new file mode 100644 index 0000000..911eba9 --- /dev/null +++ b/InteropDLL/RecordApiWrapper.cpp @@ -0,0 +1,13 @@ +#include "stdafx.h" +#include "../Core/Console.h" +#include "../Core/VideoRenderer.h" + +extern shared_ptr _console; +enum class VideoCodec; + +extern "C" +{ + DllExport void __stdcall AviRecord(char* filename, VideoCodec codec, uint32_t compressionLevel) { _console->GetVideoRenderer()->StartRecording(filename, codec, compressionLevel); } + DllExport void __stdcall AviStop() { _console->GetVideoRenderer()->StopRecording(); } + DllExport bool __stdcall AviIsRecording() { return _console->GetVideoRenderer()->IsRecording(); } +} \ No newline at end of file diff --git a/UI/Config/AviRecordConfig.cs b/UI/Config/AviRecordConfig.cs new file mode 100644 index 0000000..1fa58c4 --- /dev/null +++ b/UI/Config/AviRecordConfig.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Mesen.GUI.Config +{ + public class AviRecordConfig + { + public VideoCodec Codec = VideoCodec.CSCD; + public UInt32 CompressionLevel = 6; + } + + public enum VideoCodec + { + None = 0, + ZMBV = 1, + CSCD = 2, + } +} diff --git a/UI/Config/Configuration.cs b/UI/Config/Configuration.cs index e318a5b..a45ad7f 100644 --- a/UI/Config/Configuration.cs +++ b/UI/Config/Configuration.cs @@ -22,6 +22,7 @@ namespace Mesen.GUI.Config public PreferencesConfig Preferences; public DebugInfo Debug; public RecentItems RecentFiles; + public AviRecordConfig AviRecord; public Point WindowLocation; public Size WindowSize; public bool NeedInputReinit = true; @@ -35,6 +36,7 @@ namespace Mesen.GUI.Config Input = new InputConfig(); Emulation = new EmulationConfig(); Preferences = new PreferencesConfig(); + AviRecord = new AviRecordConfig(); } ~Configuration() diff --git a/UI/Forms/frmLogWindow.cs b/UI/Forms/Tools/frmLogWindow.cs similarity index 100% rename from UI/Forms/frmLogWindow.cs rename to UI/Forms/Tools/frmLogWindow.cs diff --git a/UI/Forms/frmLogWindow.designer.cs b/UI/Forms/Tools/frmLogWindow.designer.cs similarity index 100% rename from UI/Forms/frmLogWindow.designer.cs rename to UI/Forms/Tools/frmLogWindow.designer.cs diff --git a/UI/Forms/frmLogWindow.resx b/UI/Forms/Tools/frmLogWindow.resx similarity index 100% rename from UI/Forms/frmLogWindow.resx rename to UI/Forms/Tools/frmLogWindow.resx diff --git a/UI/Forms/Tools/frmRecordAvi.cs b/UI/Forms/Tools/frmRecordAvi.cs new file mode 100644 index 0000000..902bc96 --- /dev/null +++ b/UI/Forms/Tools/frmRecordAvi.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +using Mesen.GUI.Config; + +namespace Mesen.GUI.Forms +{ + public partial class frmRecordAvi : BaseConfigForm + { + public frmRecordAvi() + { + InitializeComponent(); + + Entity = ConfigManager.Config.AviRecord; + AddBinding(nameof(AviRecordConfig.Codec), cboVideoCodec); + AddBinding(nameof(AviRecordConfig.CompressionLevel), trkCompressionLevel); + } + + public string Filename { get; internal set; } + + protected override bool ValidateInput() + { + return !string.IsNullOrWhiteSpace(txtFilename.Text); + } + + protected override void OnFormClosed(FormClosedEventArgs e) + { + base.OnFormClosed(e); + + this.Filename = txtFilename.Text; + } + + private void btnBrowse_Click(object sender, EventArgs e) + { + SaveFileDialog sfd = new SaveFileDialog(); + sfd.SetFilter(ResourceHelper.GetMessage("FilterAvi")); + sfd.InitialDirectory = ConfigManager.AviFolder; + //TODO + //sfd.FileName = InteropEmu.GetRomInfo().GetRomName() + ".avi"; + if(sfd.ShowDialog() == System.Windows.Forms.DialogResult.OK) { + txtFilename.Text = sfd.FileName; + } + } + + private void cboVideoCodec_SelectedIndexChanged(object sender, EventArgs e) + { + lblCompressionLevel.Visible = cboVideoCodec.SelectedIndex > 0; + tlpCompressionLevel.Visible = cboVideoCodec.SelectedIndex > 0; + } + } +} diff --git a/UI/Forms/Tools/frmRecordAvi.designer.cs b/UI/Forms/Tools/frmRecordAvi.designer.cs new file mode 100644 index 0000000..4395f14 --- /dev/null +++ b/UI/Forms/Tools/frmRecordAvi.designer.cs @@ -0,0 +1,239 @@ +namespace Mesen.GUI.Forms +{ + partial class frmRecordAvi + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if(disposing && (components != null)) { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel(); + this.lblCodec = new System.Windows.Forms.Label(); + this.lblAviFile = new System.Windows.Forms.Label(); + this.txtFilename = new System.Windows.Forms.TextBox(); + this.btnBrowse = new System.Windows.Forms.Button(); + this.cboVideoCodec = new System.Windows.Forms.ComboBox(); + this.lblCompressionLevel = new System.Windows.Forms.Label(); + this.lblLowCompression = new System.Windows.Forms.Label(); + this.panel1 = new System.Windows.Forms.Panel(); + this.trkCompressionLevel = new System.Windows.Forms.TrackBar(); + this.lblHighCompression = new System.Windows.Forms.Label(); + this.tlpCompressionLevel = new System.Windows.Forms.TableLayoutPanel(); + this.tableLayoutPanel1.SuspendLayout(); + this.panel1.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.trkCompressionLevel)).BeginInit(); + this.tlpCompressionLevel.SuspendLayout(); + this.SuspendLayout(); + // + // baseConfigPanel + // + this.baseConfigPanel.Location = new System.Drawing.Point(0, 99); + this.baseConfigPanel.Size = new System.Drawing.Size(397, 29); + // + // tableLayoutPanel1 + // + this.tableLayoutPanel1.ColumnCount = 3; + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel1.Controls.Add(this.lblCodec, 0, 1); + this.tableLayoutPanel1.Controls.Add(this.lblAviFile, 0, 0); + this.tableLayoutPanel1.Controls.Add(this.txtFilename, 1, 0); + this.tableLayoutPanel1.Controls.Add(this.btnBrowse, 2, 0); + this.tableLayoutPanel1.Controls.Add(this.cboVideoCodec, 1, 1); + this.tableLayoutPanel1.Controls.Add(this.lblCompressionLevel, 0, 2); + this.tableLayoutPanel1.Controls.Add(this.tlpCompressionLevel, 1, 2); + this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill; + this.tableLayoutPanel1.Location = new System.Drawing.Point(0, 0); + this.tableLayoutPanel1.Name = "tableLayoutPanel1"; + this.tableLayoutPanel1.RowCount = 4; + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayoutPanel1.Size = new System.Drawing.Size(397, 128); + this.tableLayoutPanel1.TabIndex = 0; + // + // lblCodec + // + this.lblCodec.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.lblCodec.AutoSize = true; + this.lblCodec.Location = new System.Drawing.Point(3, 36); + this.lblCodec.Name = "lblCodec"; + this.lblCodec.Size = new System.Drawing.Size(71, 13); + this.lblCodec.TabIndex = 5; + this.lblCodec.Text = "Video Codec:"; + // + // lblAviFile + // + this.lblAviFile.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.lblAviFile.AutoSize = true; + this.lblAviFile.Location = new System.Drawing.Point(3, 8); + this.lblAviFile.Name = "lblAviFile"; + this.lblAviFile.Size = new System.Drawing.Size(47, 13); + this.lblAviFile.TabIndex = 0; + this.lblAviFile.Text = "Save to:"; + // + // txtFilename + // + this.txtFilename.Dock = System.Windows.Forms.DockStyle.Fill; + this.txtFilename.Location = new System.Drawing.Point(108, 3); + this.txtFilename.Name = "txtFilename"; + this.txtFilename.ReadOnly = true; + this.txtFilename.Size = new System.Drawing.Size(205, 20); + this.txtFilename.TabIndex = 1; + // + // btnBrowse + // + this.btnBrowse.Location = new System.Drawing.Point(319, 3); + this.btnBrowse.Name = "btnBrowse"; + this.btnBrowse.Size = new System.Drawing.Size(75, 23); + this.btnBrowse.TabIndex = 2; + this.btnBrowse.Text = "Browse..."; + this.btnBrowse.UseVisualStyleBackColor = true; + this.btnBrowse.Click += new System.EventHandler(this.btnBrowse_Click); + // + // cboVideoCodec + // + this.cboVideoCodec.Dock = System.Windows.Forms.DockStyle.Fill; + this.cboVideoCodec.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.cboVideoCodec.FormattingEnabled = true; + this.cboVideoCodec.Location = new System.Drawing.Point(108, 32); + this.cboVideoCodec.Name = "cboVideoCodec"; + this.cboVideoCodec.Size = new System.Drawing.Size(205, 21); + this.cboVideoCodec.TabIndex = 4; + this.cboVideoCodec.SelectedIndexChanged += new System.EventHandler(this.cboVideoCodec_SelectedIndexChanged); + // + // lblCompressionLevel + // + this.lblCompressionLevel.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.lblCompressionLevel.AutoSize = true; + this.lblCompressionLevel.Location = new System.Drawing.Point(3, 67); + this.lblCompressionLevel.Name = "lblCompressionLevel"; + this.lblCompressionLevel.Size = new System.Drawing.Size(99, 13); + this.lblCompressionLevel.TabIndex = 6; + this.lblCompressionLevel.Text = "Compression Level:"; + // + // lblLowCompression + // + this.lblLowCompression.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.lblLowCompression.AutoSize = true; + this.lblLowCompression.Location = new System.Drawing.Point(3, 4); + this.lblLowCompression.Margin = new System.Windows.Forms.Padding(3, 0, 0, 0); + this.lblLowCompression.Name = "lblLowCompression"; + this.lblLowCompression.Size = new System.Drawing.Size(30, 26); + this.lblLowCompression.TabIndex = 9; + this.lblLowCompression.Text = "low\r\n(fast)"; + this.lblLowCompression.TextAlign = System.Drawing.ContentAlignment.TopCenter; + // + // panel1 + // + this.panel1.Controls.Add(this.trkCompressionLevel); + this.panel1.Dock = System.Windows.Forms.DockStyle.Fill; + this.panel1.Location = new System.Drawing.Point(36, 3); + this.panel1.Name = "panel1"; + this.panel1.Size = new System.Drawing.Size(138, 29); + this.panel1.TabIndex = 9; + // + // trkCompressionLevel + // + this.trkCompressionLevel.Dock = System.Windows.Forms.DockStyle.Fill; + this.trkCompressionLevel.Location = new System.Drawing.Point(0, 0); + this.trkCompressionLevel.Maximum = 9; + this.trkCompressionLevel.Minimum = 1; + this.trkCompressionLevel.Name = "trkCompressionLevel"; + this.trkCompressionLevel.Size = new System.Drawing.Size(138, 29); + this.trkCompressionLevel.TabIndex = 7; + this.trkCompressionLevel.Value = 1; + // + // lblHighCompression + // + this.lblHighCompression.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.lblHighCompression.AutoSize = true; + this.lblHighCompression.Location = new System.Drawing.Point(177, 4); + this.lblHighCompression.Margin = new System.Windows.Forms.Padding(0); + this.lblHighCompression.Name = "lblHighCompression"; + this.lblHighCompression.Size = new System.Drawing.Size(34, 26); + this.lblHighCompression.TabIndex = 10; + this.lblHighCompression.Text = "high\r\n(slow)"; + this.lblHighCompression.TextAlign = System.Drawing.ContentAlignment.TopCenter; + // + // tlpCompressionLevel + // + this.tlpCompressionLevel.ColumnCount = 3; + this.tlpCompressionLevel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tlpCompressionLevel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tlpCompressionLevel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tlpCompressionLevel.Controls.Add(this.lblHighCompression, 2, 0); + this.tlpCompressionLevel.Controls.Add(this.panel1, 1, 0); + this.tlpCompressionLevel.Controls.Add(this.lblLowCompression, 0, 0); + this.tlpCompressionLevel.Dock = System.Windows.Forms.DockStyle.Fill; + this.tlpCompressionLevel.Location = new System.Drawing.Point(105, 56); + this.tlpCompressionLevel.Margin = new System.Windows.Forms.Padding(0, 0, 0, 0); + this.tlpCompressionLevel.Name = "tlpCompressionLevel"; + this.tlpCompressionLevel.RowCount = 1; + this.tlpCompressionLevel.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tlpCompressionLevel.Size = new System.Drawing.Size(211, 35); + this.tlpCompressionLevel.TabIndex = 9; + // + // frmRecordAvi + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(397, 128); + this.Controls.Add(this.tableLayoutPanel1); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "frmRecordAvi"; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "Video Recording Options"; + this.Controls.SetChildIndex(this.tableLayoutPanel1, 0); + this.Controls.SetChildIndex(this.baseConfigPanel, 0); + this.tableLayoutPanel1.ResumeLayout(false); + this.tableLayoutPanel1.PerformLayout(); + this.panel1.ResumeLayout(false); + this.panel1.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.trkCompressionLevel)).EndInit(); + this.tlpCompressionLevel.ResumeLayout(false); + this.tlpCompressionLevel.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1; + private System.Windows.Forms.Label lblAviFile; + private System.Windows.Forms.TextBox txtFilename; + private System.Windows.Forms.Button btnBrowse; + private System.Windows.Forms.ComboBox cboVideoCodec; + private System.Windows.Forms.Label lblCodec; + private System.Windows.Forms.Label lblCompressionLevel; + private System.Windows.Forms.Label lblLowCompression; + private System.Windows.Forms.Panel panel1; + private System.Windows.Forms.TrackBar trkCompressionLevel; + private System.Windows.Forms.Label lblHighCompression; + private System.Windows.Forms.TableLayoutPanel tlpCompressionLevel; + } +} \ No newline at end of file diff --git a/UI/Forms/Tools/frmRecordAvi.resx b/UI/Forms/Tools/frmRecordAvi.resx new file mode 100644 index 0000000..8766f29 --- /dev/null +++ b/UI/Forms/Tools/frmRecordAvi.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + \ No newline at end of file diff --git a/UI/Forms/frmMain.Designer.cs b/UI/Forms/frmMain.Designer.cs index dc64a14..f6900ee 100644 --- a/UI/Forms/frmMain.Designer.cs +++ b/UI/Forms/frmMain.Designer.cs @@ -111,6 +111,10 @@ this.toolStripMenuItem3 = new System.Windows.Forms.ToolStripSeparator(); this.mnuPreferences = new System.Windows.Forms.ToolStripMenuItem(); this.toolsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuVideoRecorder = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuAviRecord = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuAviStop = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripMenuItem11 = new System.Windows.Forms.ToolStripSeparator(); this.mnuLogWindow = new System.Windows.Forms.ToolStripMenuItem(); this.toolStripMenuItem7 = new System.Windows.Forms.ToolStripSeparator(); this.mnuTakeScreenshot = new System.Windows.Forms.ToolStripMenuItem(); @@ -747,12 +751,46 @@ // toolsToolStripMenuItem // this.toolsToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.mnuVideoRecorder, + this.toolStripMenuItem11, this.mnuLogWindow, this.toolStripMenuItem7, this.mnuTakeScreenshot}); this.toolsToolStripMenuItem.Name = "toolsToolStripMenuItem"; this.toolsToolStripMenuItem.Size = new System.Drawing.Size(47, 20); this.toolsToolStripMenuItem.Text = "Tools"; + this.toolsToolStripMenuItem.DropDownOpening += new System.EventHandler(this.toolsToolStripMenuItem_DropDownOpening); + // + // mnuVideoRecorder + // + this.mnuVideoRecorder.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.mnuAviRecord, + this.mnuAviStop}); + this.mnuVideoRecorder.Image = global::Mesen.GUI.Properties.Resources.VideoRecorder; + this.mnuVideoRecorder.Name = "mnuVideoRecorder"; + this.mnuVideoRecorder.Size = new System.Drawing.Size(159, 22); + this.mnuVideoRecorder.Text = "Video Recorder"; + // + // mnuAviRecord + // + this.mnuAviRecord.Image = global::Mesen.GUI.Properties.Resources.Record; + this.mnuAviRecord.Name = "mnuAviRecord"; + this.mnuAviRecord.Size = new System.Drawing.Size(155, 22); + this.mnuAviRecord.Text = "Record..."; + this.mnuAviRecord.Click += new System.EventHandler(this.mnuAviRecord_Click); + // + // mnuAviStop + // + this.mnuAviStop.Image = global::Mesen.GUI.Properties.Resources.MediaStop; + this.mnuAviStop.Name = "mnuAviStop"; + this.mnuAviStop.Size = new System.Drawing.Size(155, 22); + this.mnuAviStop.Text = "Stop Recording"; + this.mnuAviStop.Click += new System.EventHandler(this.mnuAviStop_Click); + // + // toolStripMenuItem11 + // + this.toolStripMenuItem11.Name = "toolStripMenuItem11"; + this.toolStripMenuItem11.Size = new System.Drawing.Size(156, 6); // // mnuLogWindow // @@ -1015,5 +1053,9 @@ private System.Windows.Forms.ToolStripMenuItem mnuRegionNtsc; private System.Windows.Forms.ToolStripMenuItem mnuRegionPal; private Controls.ctrlRecentGames ctrlRecentGames; + private System.Windows.Forms.ToolStripMenuItem mnuVideoRecorder; + private System.Windows.Forms.ToolStripMenuItem mnuAviRecord; + private System.Windows.Forms.ToolStripMenuItem mnuAviStop; + private System.Windows.Forms.ToolStripSeparator toolStripMenuItem11; } } \ No newline at end of file diff --git a/UI/Forms/frmMain.cs b/UI/Forms/frmMain.cs index 0d789ca..fae49fe 100644 --- a/UI/Forms/frmMain.cs +++ b/UI/Forms/frmMain.cs @@ -431,5 +431,26 @@ namespace Mesen.GUI.Forms mnuRegionNtsc.Checked = ConfigManager.Config.Emulation.Region == ConsoleRegion.Ntsc; mnuRegionPal.Checked = ConfigManager.Config.Emulation.Region == ConsoleRegion.Pal; } + + private void mnuAviRecord_Click(object sender, EventArgs e) + { + using(frmRecordAvi frm = new frmRecordAvi()) { + if(frm.ShowDialog(mnuVideoRecorder, this) == DialogResult.OK) { + RecordApi.AviRecord(frm.Filename, ConfigManager.Config.AviRecord.Codec, ConfigManager.Config.AviRecord.CompressionLevel); + } + } + } + + private void mnuAviStop_Click(object sender, EventArgs e) + { + RecordApi.AviStop(); + } + + private void toolsToolStripMenuItem_DropDownOpening(object sender, EventArgs e) + { + mnuVideoRecorder.Enabled = EmuRunner.IsRunning(); + mnuAviRecord.Enabled = EmuRunner.IsRunning() && !RecordApi.AviIsRecording(); + mnuAviStop.Enabled = EmuRunner.IsRunning() && RecordApi.AviIsRecording(); + } } } diff --git a/UI/Interop/RecordApi.cs b/UI/Interop/RecordApi.cs new file mode 100644 index 0000000..6f38ff6 --- /dev/null +++ b/UI/Interop/RecordApi.cs @@ -0,0 +1,29 @@ +using Mesen.GUI.Config; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace Mesen.GUI +{ + class RecordApi + { + private const string DllPath = "MesenSCore.dll"; + + [DllImport(DllPath)] public static extern void AviRecord([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))]string filename, VideoCodec codec, UInt32 compressionLevel); + [DllImport(DllPath)] public static extern void AviStop(); + [DllImport(DllPath)] [return: MarshalAs(UnmanagedType.I1)] public static extern bool AviIsRecording(); + + /*[DllImport(DllPath)] public static extern void WaveRecord([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(UTF8Marshaler))]string filename); + [DllImport(DllPath)] public static extern void WaveStop(); + [DllImport(DllPath)] [return: MarshalAs(UnmanagedType.I1)] public static extern bool WaveIsRecording(); + + [DllImport(DllPath)] public static extern void MoviePlay([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(UTF8Marshaler))]string filename); + [DllImport(DllPath)] public static extern void MovieRecord(ref RecordMovieOptions options); + [DllImport(DllPath)] public static extern void MovieStop(); + [DllImport(DllPath)] [return: MarshalAs(UnmanagedType.I1)] public static extern bool MoviePlaying(); + [DllImport(DllPath)] [return: MarshalAs(UnmanagedType.I1)] public static extern bool MovieRecording();*/ + } +} diff --git a/UI/UI.csproj b/UI/UI.csproj index 90d8a55..cea8d1d 100644 --- a/UI/UI.csproj +++ b/UI/UI.csproj @@ -209,6 +209,7 @@ + @@ -557,10 +558,10 @@ Form - + Form - + frmLogWindow.cs @@ -575,6 +576,13 @@ frmSelectRom.cs + + Form + + + frmRecordAvi.cs + + Form @@ -744,7 +752,7 @@ frmAbout.cs - + frmLogWindow.cs @@ -753,6 +761,9 @@ frmSelectRom.cs + + frmRecordAvi.cs + frmDownloadProgress.cs