Video: AVI recording

This commit is contained in:
Sour 2019-03-15 12:48:34 -04:00
parent 4525d7328b
commit 033469ff01
23 changed files with 778 additions and 41 deletions

117
Core/AviRecorder.cpp Normal file
View file

@ -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 = 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;
}

45
Core/AviRecorder.h Normal file
View file

@ -0,0 +1,45 @@
#pragma once
#include "stdafx.h"
#include <thread>
#include "../Utilities/AutoResetEvent.h"
#include "../Utilities/AviWriter.h"
#include "../Utilities/SimpleLock.h"
class Console;
class AviRecorder
{
private:
std::thread _aviWriterThread;
unique_ptr<AviWriter> _aviWriter;
shared_ptr<Console> _console;
string _outputFile;
SimpleLock _lock;
AutoResetEvent _waitFrame;
atomic<bool> _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> 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();
};

View file

@ -43,6 +43,7 @@
</ProjectConfiguration>
</ItemGroup>
<ItemGroup>
<ClInclude Include="AviRecorder.h" />
<ClInclude Include="BaseCartridge.h" />
<ClInclude Include="BaseControlDevice.h" />
<ClInclude Include="BaseRenderer.h" />
@ -126,6 +127,7 @@
<ClInclude Include="VideoRenderer.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="AviRecorder.cpp" />
<ClCompile Include="BaseCartridge.cpp" />
<ClCompile Include="BaseControlDevice.cpp" />
<ClCompile Include="BaseRenderer.cpp" />

View file

@ -242,6 +242,9 @@
<ClInclude Include="RewindManager.h">
<Filter>Misc</Filter>
</ClInclude>
<ClInclude Include="AviRecorder.h">
<Filter>Misc</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="stdafx.cpp" />
@ -390,6 +393,9 @@
<ClCompile Include="RewindManager.cpp">
<Filter>Misc</Filter>
</ClCompile>
<ClCompile Include="AviRecorder.cpp">
<Filter>Misc</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<Filter Include="SNES">

View file

@ -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> 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> 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();
}
}

View file

@ -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)

View file

@ -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> console)
{
@ -58,11 +59,10 @@ void VideoRenderer::RenderThread()
void VideoRenderer::UpdateFrame(void *frameBuffer, uint32_t width, uint32_t height)
{
//TODO
/*shared_ptr<AviRecorder> aviRecorder = _aviRecorder;
shared_ptr<AviRecorder> 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<AviRecorder> 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();
}
*/

View file

@ -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<bool> _stopFlag;
//TODO
//shared_ptr<AviRecorder> _aviRecorder;
shared_ptr<AviRecorder> _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();
};

View file

@ -457,6 +457,7 @@
<ClCompile Include="EmuApiWrapper.cpp" />
<ClCompile Include="DebugApiWrapper.cpp" />
<ClCompile Include="InputApiWrapper.cpp" />
<ClCompile Include="RecordApiWrapper.cpp" />
<ClCompile Include="stdafx.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>

View file

@ -37,5 +37,8 @@
<ClCompile Include="InputApiWrapper.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="RecordApiWrapper.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
</Project>

View file

@ -0,0 +1,13 @@
#include "stdafx.h"
#include "../Core/Console.h"
#include "../Core/VideoRenderer.h"
extern shared_ptr<Console> _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(); }
}

View file

@ -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,
}
}

View file

@ -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()

View file

@ -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;
}
}
}

239
UI/Forms/Tools/frmRecordAvi.designer.cs generated Normal file
View file

@ -0,0 +1,239 @@
namespace Mesen.GUI.Forms
{
partial class frmRecordAvi
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if(disposing && (components != null)) {
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
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;
}
}

View file

@ -0,0 +1,123 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<metadata name="toolTip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
</metadata>
</root>

View file

@ -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;
}
}

View file

@ -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();
}
}
}

29
UI/Interop/RecordApi.cs Normal file
View file

@ -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();*/
}
}

View file

@ -209,6 +209,7 @@
</ItemGroup>
<ItemGroup>
<Compile Include="Config\AudioConfig.cs" />
<Compile Include="Config\AviRecordConfig.cs" />
<Compile Include="Config\BaseConfig.cs" />
<Compile Include="Config\ConfigAttributes.cs" />
<Compile Include="Config\Configuration.cs" />
@ -557,10 +558,10 @@
<Compile Include="Forms\frmFullscreenRenderer.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Forms\frmLogWindow.cs">
<Compile Include="Forms\Tools\frmLogWindow.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Forms\frmLogWindow.designer.cs">
<Compile Include="Forms\Tools\frmLogWindow.designer.cs">
<DependentUpon>frmLogWindow.cs</DependentUpon>
</Compile>
<Compile Include="Forms\frmMain.cs">
@ -575,6 +576,13 @@
<Compile Include="Forms\frmSelectRom.designer.cs">
<DependentUpon>frmSelectRom.cs</DependentUpon>
</Compile>
<Compile Include="Forms\Tools\frmRecordAvi.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Forms\Tools\frmRecordAvi.designer.cs">
<DependentUpon>frmRecordAvi.cs</DependentUpon>
</Compile>
<Compile Include="Interop\RecordApi.cs" />
<Compile Include="Updates\frmDownloadProgress.cs">
<SubType>Form</SubType>
</Compile>
@ -744,7 +752,7 @@
<EmbeddedResource Include="Forms\frmAbout.resx">
<DependentUpon>frmAbout.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Forms\frmLogWindow.resx">
<EmbeddedResource Include="Forms\Tools\frmLogWindow.resx">
<DependentUpon>frmLogWindow.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Forms\frmMain.resx">
@ -753,6 +761,9 @@
<EmbeddedResource Include="Forms\frmSelectRom.resx">
<DependentUpon>frmSelectRom.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Forms\Tools\frmRecordAvi.resx">
<DependentUpon>frmRecordAvi.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Updates\frmDownloadProgress.resx">
<DependentUpon>frmDownloadProgress.cs</DependentUpon>
</EmbeddedResource>