From 0f5c7fe78b107df9a34163af6855f2bd1fda748a Mon Sep 17 00:00:00 2001 From: Souryo Date: Thu, 29 Dec 2016 21:19:13 -0500 Subject: [PATCH] Video: Added "Video Recorder" to record AVI files --- Core/AviRecorder.cpp | 98 ++++++ Core/AviRecorder.h | 37 ++ Core/Core.vcxproj | 2 + Core/Core.vcxproj.filters | 6 + Core/MessageManager.cpp | 22 ++ Core/SoundMixer.cpp | 62 ++-- Core/VideoDecoder.cpp | 46 ++- Core/VideoDecoder.h | 8 + GUI.NET/Config/ConfigManager.cs | 20 +- GUI.NET/Dependencies/resources.en.xml | 1 + GUI.NET/Dependencies/resources.es.xml | 11 + GUI.NET/Dependencies/resources.fr.xml | 11 + GUI.NET/Dependencies/resources.ja.xml | 11 + GUI.NET/Dependencies/resources.pt.xml | 11 + GUI.NET/Dependencies/resources.ru.xml | 11 + GUI.NET/Dependencies/resources.uk.xml | 31 +- GUI.NET/Forms/frmMain.Designer.cs | 83 +++-- GUI.NET/Forms/frmMain.cs | 19 +- GUI.NET/Forms/frmRecordAvi.Designer.cs | 131 ++++++++ GUI.NET/Forms/frmRecordAvi.cs | 48 +++ GUI.NET/Forms/frmRecordAvi.resx | 123 +++++++ GUI.NET/GUI.NET.csproj | 10 + GUI.NET/InteropEmu.cs | 10 + GUI.NET/Properties/Resources.Designer.cs | 10 + GUI.NET/Properties/Resources.resx | 3 + GUI.NET/Resources/VideoRecorder.png | Bin 0 -> 233 bytes InteropDLL/ConsoleWrapper.cpp | 5 + Utilities/AviWriter.cpp | 270 +++++++++++++++ Utilities/AviWriter.h | 57 ++++ Utilities/BaseCodec.h | 10 + Utilities/RawCodec.h | 50 +++ Utilities/Utilities.vcxproj | 6 + Utilities/Utilities.vcxproj.filters | 21 ++ Utilities/ZmbvCodec.cpp | 410 +++++++++++++++++++++++ Utilities/ZmbvCodec.h | 115 +++++++ 35 files changed, 1701 insertions(+), 68 deletions(-) create mode 100644 Core/AviRecorder.cpp create mode 100644 Core/AviRecorder.h create mode 100644 GUI.NET/Forms/frmRecordAvi.Designer.cs create mode 100644 GUI.NET/Forms/frmRecordAvi.cs create mode 100644 GUI.NET/Forms/frmRecordAvi.resx create mode 100644 GUI.NET/Resources/VideoRecorder.png create mode 100644 Utilities/AviWriter.cpp create mode 100644 Utilities/AviWriter.h create mode 100644 Utilities/BaseCodec.h create mode 100644 Utilities/RawCodec.h create mode 100644 Utilities/ZmbvCodec.cpp create mode 100644 Utilities/ZmbvCodec.h diff --git a/Core/AviRecorder.cpp b/Core/AviRecorder.cpp new file mode 100644 index 00000000..9e79598d --- /dev/null +++ b/Core/AviRecorder.cpp @@ -0,0 +1,98 @@ +#include "stdafx.h" +#include "AviRecorder.h" +#include "MessageManager.h" + +AviRecorder::AviRecorder() +{ + _recording = false; + _stopFlag = false; + _frameBuffer = nullptr; + _frameBufferLength = 0; + _sampleRate = 0; +} + +AviRecorder::~AviRecorder() +{ + if(_recording) { + StopRecording(); + } + + if(_frameBuffer) { + delete[] _frameBuffer; + _frameBuffer = nullptr; + } +} + +bool AviRecorder::StartRecording(string filename, VideoCodec codec, uint32_t width, uint32_t height, uint32_t bpp, uint32_t fps, uint32_t audioSampleRate) +{ + if(!_recording) { + _outputFile = filename; + _sampleRate = audioSampleRate; + _frameBufferLength = height * width * bpp; + _frameBuffer = new uint8_t[_frameBufferLength]; + + _aviWriter.reset(new AviWriter()); + if(!_aviWriter->StartWrite(filename, codec, width, height, bpp, fps, audioSampleRate)) { + _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) +{ + if(_recording) { + 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 00000000..a82017a4 --- /dev/null +++ b/Core/AviRecorder.h @@ -0,0 +1,37 @@ +#pragma once +#include "stdafx.h" +#include +#include "../Utilities/AutoResetEvent.h" +#include "../Utilities/AviWriter.h" +#include "../Utilities/SimpleLock.h" + +class AviRecorder +{ +private: + std::thread _aviWriterThread; + + unique_ptr _aviWriter; + + string _outputFile; + SimpleLock _lock; + AutoResetEvent _waitFrame; + + atomic _stopFlag; + bool _recording; + uint8_t* _frameBuffer; + uint32_t _frameBufferLength; + uint32_t _sampleRate; + +public: + AviRecorder(); + virtual ~AviRecorder(); + + bool StartRecording(string filename, VideoCodec codec, uint32_t width, uint32_t height, uint32_t bpp, uint32_t fps, uint32_t audioSampleRate); + void StopRecording(); + + void AddFrame(void* frameBuffer); + void AddSound(int16_t* soundBuffer, uint32_t sampleCount, uint32_t sampleRate); + void SendAudio(); + + bool IsRecording(); +}; \ No newline at end of file diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index 20552399..59eb2e4e 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -406,6 +406,7 @@ + @@ -745,6 +746,7 @@ + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index fed13ccd..9bbb920a 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -1108,6 +1108,9 @@ VideoDecoder + + Misc + @@ -1299,5 +1302,8 @@ VideoDecoder + + Misc + \ No newline at end of file diff --git a/Core/MessageManager.cpp b/Core/MessageManager.cpp index aa0d3bdd..1aefe2c4 100644 --- a/Core/MessageManager.cpp +++ b/Core/MessageManager.cpp @@ -20,6 +20,7 @@ std::unordered_map MessageManager::_enResources = { { "ScreenshotSaved", u8"Screenshot Saved" }, { "SoundRecorder", u8"Sound Recorder" }, { "Test", u8"Test" }, + { "VideoRecorder", u8"Video Recorder" }, { "ApplyingIps", u8"Applying patch: %1" }, { "CheatApplied", u8"1 cheat applied." }, @@ -61,6 +62,8 @@ std::unordered_map MessageManager::_enResources = { { "SoundRecorderStopped", u8"Recording saved to: %1" }, { "TestFileSavedTo", u8"Test file saved to: %1" }, { "UnsupportedMapper", u8"Unsupported mapper (%1), cannot load game." }, + { "VideoRecorderStarted", u8"Recording to: %1" }, + { "VideoRecorderStopped", u8"Recording saved to: %1" }, { "GoogleDrive", u8"Google Drive" }, { "SynchronizationStarted", u8"Synchronization started." }, @@ -84,6 +87,7 @@ std::unordered_map MessageManager::_frResources = { { "ScreenshotSaved", u8"Capture d'écran" }, { "SoundRecorder", u8"Enregistreur audio" }, { "Test", u8"Test" }, + { "VideoRecorder", u8"Enregistreur vidéo" }, { "ApplyingIps", u8"Fichier IPS appliqué : %1" }, { "CheatApplied", u8"%1 code activé." }, @@ -125,6 +129,8 @@ std::unordered_map MessageManager::_frResources = { { "SoundRecorderStopped", u8"Enregistrement audio sauvegardé : %1" }, { "TestFileSavedTo", u8"Test sauvegardé : %1" }, { "UnsupportedMapper", u8"Ce mapper (%1) n'est pas encore supporté - le jeu ne peut pas être démarré." }, + { "VideoRecorderStarted", u8"En cours d'enregistrement : %1" }, + { "VideoRecorderStopped", u8"Enregistrement audio sauvegardé : %1" }, { "GoogleDrive", u8"Google Drive" }, { "SynchronizationStarted", u8"Synchronisation en cours." }, @@ -148,6 +154,7 @@ std::unordered_map MessageManager::_jaResources = { { "ScreenshotSaved", u8"スクリーンショット" }, { "SoundRecorder", u8"サウンドレコーダー" }, { "Test", u8"テスト" }, + { "VideoRecorder", u8"動画レコーダー" }, { "ApplyingIps", u8"パッチファイルを適用しました: %1" }, { "CheatApplied", u8"チートコード%1個を有効にしました。" }, @@ -189,6 +196,8 @@ std::unordered_map MessageManager::_jaResources = { { "SoundRecorderStopped", u8"録音を終了しました: %1" }, { "TestFileSavedTo", u8"Test file saved to: %1" }, { "UnsupportedMapper", u8"このMapper (%1)を使うゲームはロードできません。" }, + { "VideoRecorderStarted", u8"%1に録画しています。" }, + { "VideoRecorderStopped", u8"録画を終了しました: %1" }, { "GoogleDrive", u8"Google Drive" }, { "SynchronizationStarted", u8"同期中。" }, @@ -212,6 +221,7 @@ std::unordered_map MessageManager::_ruResources = { { "ScreenshotSaved", u8"Скриншот сохранён" }, { "SoundRecorder", u8"Запись звука" }, { "Test", u8"Тест" }, + { "VideoRecorder", u8"Video Recorder" }, { "ApplyingIps", u8"Применён патч: %1" }, { "CheatApplied", u8"1 Чит применён." }, @@ -253,6 +263,8 @@ std::unordered_map MessageManager::_ruResources = { { "SoundRecorderStopped", u8"Запись сохранена: %1" }, { "TestFileSavedTo", u8"Тест сохранён: %1" }, { "UnsupportedMapper", u8"Неподдерживаемый mapper (%1), игра не загружена." }, + { "VideoRecorderStarted", u8"Запись начата to: %1" }, + { "VideoRecorderStopped", u8"Запись сохранена: %1" }, { "GoogleDrive", u8"Google Drive" }, { "SynchronizationStarted", u8"Синхронизация начата." }, @@ -276,6 +288,7 @@ std::unordered_map MessageManager::_esResources = { { "ScreenshotSaved", u8"Captura Guardada" }, { "SoundRecorder", u8"Grabadora de Sonido" }, { "Test", u8"Test" }, + { "VideoRecorder", u8"Video Recorder" }, { "ApplyingIps", u8"Aplicando parche: %1" }, { "CheatApplied", u8"1 truco aplicado." }, @@ -317,6 +330,9 @@ std::unordered_map MessageManager::_esResources = { { "SoundRecorderStopped", u8"Grabación guardada en: %1" }, { "TestFileSavedTo", u8"Archivo test guardado en: %1" }, { "UnsupportedMapper", u8"Mapa (%1) no soportado, no se puede cargar el juego." }, + { "VideoRecorderStarted", u8"Grabando en: %1" }, + { "VideoRecorderStopped", u8"Grabación guardada en: %1" }, + { "GoogleDrive", u8"Google Drive" }, { "SynchronizationStarted", u8"Sincronización iniciada." }, @@ -340,6 +356,7 @@ std::unordered_map MessageManager::_ukResources = { { "ScreenshotSaved", u8"Скріншот збережений" }, { "SoundRecorder", u8"Запис звуку" }, { "Test", u8"Тест" }, + { "VideoRecorder", u8"Video Recorder" }, { "ApplyingIps", u8"Застосовано патч: %1" }, { "CheatApplied", u8"1 Чiт застосований." }, @@ -381,6 +398,8 @@ std::unordered_map MessageManager::_ukResources = { { "SoundRecorderStopped", u8"Запис збережена: %1" }, { "TestFileSavedTo", u8"Тест збережений: %1" }, { "UnsupportedMapper", u8"Непідтримуваний mapper (%1), гра не завантажена." }, + { "VideoRecorderStarted", u8"Запис розпочато to: %1" }, + { "VideoRecorderStopped", u8"Запис збережена: %1" }, { "Google Диск", u8"Google Drive" }, { "SynchronizationStarted", u8"Синхронізацію розпочато." }, @@ -404,6 +423,7 @@ std::unordered_map MessageManager::_ptResources = { { "ScreenshotSaved", u8"Screenshot salva" }, { "SoundRecorder", u8"Gravador de Som" }, { "Test", u8"Teste" }, + { "VideoRecorder", u8"Video Recorder" }, { "ApplyingIps", u8"Aplicando patch: %1" }, { "CheatApplied", u8"1 cheat aplicado." }, @@ -445,6 +465,8 @@ std::unordered_map MessageManager::_ptResources = { { "SoundRecorderStopped", u8"Gravação salva em: %1" }, { "TestFileSavedTo", u8"Arquivo teste salvo em: %1" }, { "UnsupportedMapper", u8"Mapa (%1) não suportado, não se pode carregar o jogo." }, + { "VideoRecorderStarted", u8"Gravando em: %1" }, + { "VideoRecorderStopped", u8"Gravação salva em: %1" }, { "GoogleDrive", u8"Google Drive" }, { "SynchronizationStarted", u8"Sincronização iniciada." }, diff --git a/Core/SoundMixer.cpp b/Core/SoundMixer.cpp index 94af2754..b0dd670f 100644 --- a/Core/SoundMixer.cpp +++ b/Core/SoundMixer.cpp @@ -2,6 +2,7 @@ #include "SoundMixer.h" #include "APU.h" #include "CPU.h" +#include "VideoDecoder.h" IAudioDevice* SoundMixer::AudioDevice = nullptr; unique_ptr SoundMixer::_waveRecorder; @@ -85,41 +86,44 @@ void SoundMixer::PlayAudioBuffer(uint32_t time) size_t sampleCount = blip_read_samples(_blipBufLeft, _outputBuffer, SoundMixer::MaxSamplesPerFrame, 1); blip_read_samples(_blipBufRight, _outputBuffer + 1, SoundMixer::MaxSamplesPerFrame, 1); + //Apply low pass filter/volume reduction when in background (based on options) + if(!VideoDecoder::GetInstance()->IsRecording() && !_waveRecorder && !EmulationSettings::CheckFlag(EmulationFlags::NsfPlayerEnabled) && EmulationSettings::CheckFlag(EmulationFlags::InBackground)) { + if(EmulationSettings::CheckFlag(EmulationFlags::MuteSoundInBackground)) { + _lowPassFilter.ApplyFilter(_outputBuffer, sampleCount, 0, 0); + } else if(EmulationSettings::CheckFlag(EmulationFlags::ReduceSoundInBackground)) { + _lowPassFilter.ApplyFilter(_outputBuffer, sampleCount, 6, 0.75); + } + } + + if(EmulationSettings::GetReverbStrength() > 0) { + _reverbFilter.ApplyFilter(_outputBuffer, sampleCount, _sampleRate, EmulationSettings::GetReverbStrength(), EmulationSettings::GetReverbDelay()); + } else { + _reverbFilter.ResetFilter(); + } + + switch(EmulationSettings::GetStereoFilter()) { + case StereoFilter::Delay: _stereoDelay.ApplyFilter(_outputBuffer, sampleCount, _sampleRate); break; + case StereoFilter::Panning: _stereoPanning.ApplyFilter(_outputBuffer, sampleCount); break; + } + + if(EmulationSettings::GetCrossFeedRatio() > 0) { + _crossFeedFilter.ApplyFilter(_outputBuffer, sampleCount, EmulationSettings::GetCrossFeedRatio()); + } + if(SoundMixer::AudioDevice && !EmulationSettings::IsPaused()) { - //Apply low pass filter/volume reduction when in background (based on options) - if(!_waveRecorder && !EmulationSettings::CheckFlag(EmulationFlags::NsfPlayerEnabled) && EmulationSettings::CheckFlag(EmulationFlags::InBackground)) { - if(EmulationSettings::CheckFlag(EmulationFlags::MuteSoundInBackground)) { - _lowPassFilter.ApplyFilter(_outputBuffer, sampleCount, 0, 0); - } else if(EmulationSettings::CheckFlag(EmulationFlags::ReduceSoundInBackground)) { - _lowPassFilter.ApplyFilter(_outputBuffer, sampleCount, 6, 0.75); - } - } - - if(EmulationSettings::GetReverbStrength() > 0) { - _reverbFilter.ApplyFilter(_outputBuffer, sampleCount, _sampleRate, EmulationSettings::GetReverbStrength(), EmulationSettings::GetReverbDelay()); - } else { - _reverbFilter.ResetFilter(); - } - - switch(EmulationSettings::GetStereoFilter()) { - case StereoFilter::Delay: _stereoDelay.ApplyFilter(_outputBuffer, sampleCount, _sampleRate); break; - case StereoFilter::Panning: _stereoPanning.ApplyFilter(_outputBuffer, sampleCount); break; - } - - if(EmulationSettings::GetCrossFeedRatio() > 0) { - _crossFeedFilter.ApplyFilter(_outputBuffer, sampleCount, EmulationSettings::GetCrossFeedRatio()); - } - SoundMixer::AudioDevice->PlayBuffer(_outputBuffer, (uint32_t)sampleCount, _sampleRate, true); + } + + if(_waveRecorder) { + auto lock = _waveRecorderLock.AcquireSafe(); if(_waveRecorder) { - auto lock = _waveRecorderLock.AcquireSafe(); - if(_waveRecorder) { - if(!_waveRecorder->WriteSamples(_outputBuffer, (uint32_t)sampleCount, _sampleRate, true)) { - _waveRecorder.reset(); - } + if(!_waveRecorder->WriteSamples(_outputBuffer, (uint32_t)sampleCount, _sampleRate, true)) { + _waveRecorder.reset(); } } } + + VideoDecoder::GetInstance()->AddRecordingSound(_outputBuffer, (uint32_t)sampleCount, _sampleRate); if(EmulationSettings::GetSampleRate() != _sampleRate) { //Update sample rate for next frame if setting changed diff --git a/Core/VideoDecoder.cpp b/Core/VideoDecoder.cpp index 7c11b21b..350748fd 100644 --- a/Core/VideoDecoder.cpp +++ b/Core/VideoDecoder.cpp @@ -1,4 +1,5 @@ #include "stdafx.h" +#include "AviRecorder.h" #include "IRenderingDevice.h" #include "VideoDecoder.h" #include "EmulationSettings.h" @@ -90,6 +91,10 @@ void VideoDecoder::UpdateVideoFilter() case VideoFilterType::HdPack: _videoFilter.reset(new HdVideoFilter()); break; } + + if(_aviRecorder) { + StopRecording(); + } } } @@ -102,13 +107,18 @@ void VideoDecoder::DecodeFrame() } _videoFilter->SendFrame(_ppuOutputBuffer); + shared_ptr aviRecorder = _aviRecorder; + if(aviRecorder) { + aviRecorder->AddFrame(_videoFilter->GetOutputBuffer()); + } + FrameInfo frameInfo = _videoFilter->GetFrameInfo(); if(_previousScale != EmulationSettings::GetVideoScale() || frameInfo.Height != _previousFrameInfo.Height || frameInfo.Width != _previousFrameInfo.Width) { MessageManager::SendNotification(ConsoleNotificationType::ResolutionChanged); } _previousScale = EmulationSettings::GetVideoScale(); _previousFrameInfo = frameInfo; - + _frameChanged = false; VideoRenderer::GetInstance()->UpdateFrame(_videoFilter->GetOutputBuffer(), frameInfo.Width, frameInfo.Height); @@ -126,7 +136,7 @@ void VideoDecoder::DecodeThread() //This thread will decode the PPU's output (color ID to RGB, intensify r/g/b and produce a HD version of the frame if needed) while(!_stopFlag.load()) { //DecodeFrame returns the final ARGB frame we want to display in the emulator window - if(!_frameChanged) { + while(!_frameChanged) { _waitForFrame.Wait(); if(_stopFlag.load()) { return; @@ -151,7 +161,7 @@ void VideoDecoder::UpdateFrame(void *ppuOutputBuffer, HdPpuPixelInfo *hdPixelInf } //At this point, we are sure that the decode thread is no longer busy } - + _hdScreenTiles = hdPixelInfo; _ppuOutputBuffer = (uint16_t*)ppuOutputBuffer; _frameChanged = true; @@ -202,4 +212,34 @@ void VideoDecoder::TakeScreenshot() if(_videoFilter) { _videoFilter->TakeScreenshot(); } +} + +void VideoDecoder::StartRecording(string filename, VideoCodec codec) +{ + if(_videoFilter) { + shared_ptr recorder(new AviRecorder()); + + FrameInfo frameInfo = _videoFilter->GetFrameInfo(); + if(recorder->StartRecording(filename, codec, frameInfo.Width, frameInfo.Height, frameInfo.BitsPerPixel, 60098814, EmulationSettings::GetSampleRate())) { + _aviRecorder = recorder; + } + } +} + +void VideoDecoder::AddRecordingSound(int16_t* soundBuffer, uint32_t sampleCount, uint32_t sampleRate) +{ + shared_ptr aviRecorder = _aviRecorder; + if(aviRecorder) { + aviRecorder->AddSound(soundBuffer, sampleCount, sampleRate); + } +} + +void VideoDecoder::StopRecording() +{ + _aviRecorder.reset(); +} + +bool VideoDecoder::IsRecording() +{ + return _aviRecorder != nullptr && _aviRecorder->IsRecording(); } \ No newline at end of file diff --git a/Core/VideoDecoder.h b/Core/VideoDecoder.h index 914a342b..ce49aa05 100644 --- a/Core/VideoDecoder.h +++ b/Core/VideoDecoder.h @@ -5,10 +5,12 @@ using std::thread; #include "../Utilities/SimpleLock.h" #include "../Utilities/AutoResetEvent.h" +#include "../Utilities/AviWriter.h" #include "EmulationSettings.h" #include "HdNesPack.h" #include "FrameInfo.h" +class AviRecorder; class BaseVideoFilter; class IRenderingDevice; @@ -28,6 +30,7 @@ private: HdPpuPixelInfo *_hdScreenTiles = nullptr; unique_ptr _decodeThread; + shared_ptr _aviRecorder; AutoResetEvent _waitForFrame; @@ -66,4 +69,9 @@ public: bool IsRunning(); void StartThread(); void StopThread(); + + void StartRecording(string filename, VideoCodec codec); + void AddRecordingSound(int16_t* soundBuffer, uint32_t sampleCount, uint32_t sampleRate); + void StopRecording(); + bool IsRecording(); }; \ No newline at end of file diff --git a/GUI.NET/Config/ConfigManager.cs b/GUI.NET/Config/ConfigManager.cs index a835054a..7451ee3e 100644 --- a/GUI.NET/Config/ConfigManager.cs +++ b/GUI.NET/Config/ConfigManager.cs @@ -83,11 +83,23 @@ namespace Mesen.GUI.Config { get { - string waveFoler = Path.Combine(ConfigManager.HomeFolder, "Wave"); - if(!Directory.Exists(waveFoler)) { - Directory.CreateDirectory(waveFoler); + string waveFolder = Path.Combine(ConfigManager.HomeFolder, "Wave"); + if(!Directory.Exists(waveFolder)) { + Directory.CreateDirectory(waveFolder); } - return waveFoler; + return waveFolder; + } + } + + public static string AviFolder + { + get + { + string aviFolder = Path.Combine(ConfigManager.HomeFolder, "Avi"); + if(!Directory.Exists(aviFolder)) { + Directory.CreateDirectory(aviFolder); + } + return aviFolder; } } diff --git a/GUI.NET/Dependencies/resources.en.xml b/GUI.NET/Dependencies/resources.en.xml index aaaebe66..0a96add7 100644 --- a/GUI.NET/Dependencies/resources.en.xml +++ b/GUI.NET/Dependencies/resources.en.xml @@ -4,6 +4,7 @@ All Files (*.*)|*.* Movie files (*.mmo)|*.mmo|All Files (*.*)|*.* Wave files (*.wav)|*.wav|All Files (*.*)|*.* + Avi files (*.avi)|*.avi|All Files (*.*)|*.* Palette Files (*.pal)|*.pal|All Files (*.*)|*.* All supported formats (*.nes, *.zip, *.7z, *.nsf, *.nsfe, *.fds, *.unf)|*.NES;*.ZIP;*.7z;*.FDS;*.NSF;*.NSFE;*.UNF|NES Roms (*.nes, *.unf)|*.NES;*.UNF|Famicom Disk System Roms (*.fds)|*.FDS|NSF files (*.nsf, *.nsfe)|*.nsf;*.nsfe|ZIP Archives (*.zip)|*.ZIP|7-Zip Archives (*.7z)|*.7z|All (*.*)|*.* All supported formats (*.nes, *.zip, *.7z, *.fds, *.nsf, *.nsfe, *.unf, *.ips)|*.NES;*.ZIP;*.7z;*.IPS;*.FDS;*.NSF;*.NSFE;*.UNF|NES Roms (*.nes, *.unf)|*.NES;*.UNF|Famicom Disk System Roms (*.fds)|*.FDS|NSF files (*.nsf, *.nsfe)|*.nsf;*.nsfe|ZIP Archives (*.zip)|*.ZIP|7-Zip Archives (*.7z)|*.7z|IPS Patches (*.ips)|*.IPS|All (*.*)|*.* diff --git a/GUI.NET/Dependencies/resources.es.xml b/GUI.NET/Dependencies/resources.es.xml index 86aac2ce..8ece2bca 100644 --- a/GUI.NET/Dependencies/resources.es.xml +++ b/GUI.NET/Dependencies/resources.es.xml @@ -71,6 +71,9 @@ Grabar sonido Grabar... Detener + Video Recorder + Grabar... + Detener Trucos Tests Iniciar... @@ -477,11 +480,19 @@ Ok Buscar +
+ Save to: + Use ZMBV compression + Browse... + OK + Cancelar +
Todos los tipos de archivo (*.*)|*.* Videos (*.mmo)|*.mmo|Todos los archivos (*.*)|*.* Archivos wave (*.wav)|*.wav|Todos los archivos (*.*)|*.* + Archivos avi (*.avi)|*.avi|Todos los archivos (*.*)|*.* Archivos pal (*.pal)|*.pal|Todos los archivos (*.*)|*.* Todos los formatos soportados (*.nes, *.zip, *.7z, *.fds, *.nsf, *.nsfe, *.unf)|*.NES;*.ZIP;*.7z;*.FDS;*.NSF;*.NSFE;*.UNF|Roms de NES (*.nes, *.unf)|*.NES;*.UNF|Roms de Famicom Disk System (*.fds)|*.FDS|Archivos NSF (*.nsf, *.nsfe)|*.NSF;*.NSFE|Archivos ZIP (*.zip)|*.ZIP|Archivos 7-Zip (*.7z)|*.7z|Todos los archivos (*.*)|*.* Todos los formatos soportados (*.nes, *.zip, *.7z, *.fds, *.nsf, *.nsfe, *.unf, *.ips)|*.NES;*.ZIP;*.7z;*.IPS;*.FDS;*.NSF;*.NSFE;*.UNF|Roms de NES(*.nes, *.unf)|*.NES;*.UNF|Roms de Famicom Disk System (*.fds)|*.FDS|Archivos NSF (*.nsf, *.nsfe)|*.NSF;*.NSFE|Archivos ZIP (*.zip)|*.ZIP|Archivos 7-Zip (*.7z)|*.7z|Archivos IPS (*.ips)|*.IPS|Todos los archivos (*.*)|*.* diff --git a/GUI.NET/Dependencies/resources.fr.xml b/GUI.NET/Dependencies/resources.fr.xml index a631b435..541c9f47 100644 --- a/GUI.NET/Dependencies/resources.fr.xml +++ b/GUI.NET/Dependencies/resources.fr.xml @@ -71,6 +71,9 @@ Enregistreur audio Enregistrer... Arrêter + Enregistreur vidéo + Enregistrer... + Arrêter Codes Tests Run... @@ -490,11 +493,19 @@ OK Annuler +
+ Enregistrer sous: + Utiliser la compression ZMBV + Parcourir... + OK + Annuler +
Tous les fichiers (*.*)|*.* Films (*.mmo)|*.mmo|Tous les fichiers (*.*)|*.* Fichiers wave (*.wav)|*.wav|Tous les fichiers (*.*)|*.* + Fichiers avi (*.avi)|*.avi|Tous les fichiers (*.*)|*.* Fichier de palette (*.pal)|*.pal|Tous les fichiers (*.*)|*.* Tous les formats supportés (*.nes, *.zip, *.7z, *.fds, *.nsf, *.nsfe, *.unf)|*.NES;*.ZIP;*.7z;*.FDS;*.NSF;*.NSFE;*.UNF|Roms de NES (*.nes, *.unf)|*.NES;*.UNF|Roms du Famicom Disk System (*.fds)|*.FDS|Fichiers NSF (*.nsf, *.nsfe)|*.NSF;*.NSFE|Fichiers ZIP (*.zip)|*.ZIP|Fichiers 7-Zip (*.7z)|*.7z|Tous les fichiers (*.*)|*.* Tous les formats supportés (*.nes, *.zip, *.7z, *.fds, *.nsf, *.nsfe, *.unf, *.ips)|*.NES;*.ZIP;*.7z;*.IPS;*.FDS;*.NSF;*.NSFE;*.UNF|Roms de NES (*.nes, *.unf)|*.NES;*.UNF|Roms du Famicom Disk System (*.fds)|*.FDS|Fichiers NSF (*.nsf, *.nsfe)|*.NSF;*.NSFE|Fichiers ZIP (*.zip)|*.ZIP|Fichiers 7-Zip (*.7z)|*.7z|Fichiers IPS (*.ips)|*.IPS|Tous les fichiers (*.*)|*.* diff --git a/GUI.NET/Dependencies/resources.ja.xml b/GUI.NET/Dependencies/resources.ja.xml index 9951bcea..2f2ab8c6 100644 --- a/GUI.NET/Dependencies/resources.ja.xml +++ b/GUI.NET/Dependencies/resources.ja.xml @@ -71,6 +71,9 @@ サウンドレコーダー 録音 停止 + 動画レコーダー + 録画 + 停止 チートコード テスト Run... @@ -472,11 +475,19 @@ OK キャンセル +
+ 保存先: + ZMBV圧縮を利用する + 参照... + OK + キャンセル +
すべてのファイル (*.*)|*.* 動画 (*.mmo)|*.mmo|すべてのファイル (*.*)|*.* WAVファイル (*.wav)|*.wav|すべてのファイル (*.*)|*.* + AVIファイル (*.avi)|*.avi|すべてのファイル (*.*)|*.* パレットファイル (*.pal)|*.pal|すべてのファイル (*.*)|*.* 対応するすべてのファイル (*.nes, *.zip, *.7z, *.fds, *.nsf, *.nsfe, *.unf)|*.NES;*.ZIP;*.FDS;*.7z;*.NSF;*.NSFE;*.UNF|ファミコンゲーム (*.nes, *.unf)|*.NES;*.UNF|ファミコンディスクシステムのゲーム (*.fds)|*.FDS|NSFファイル (*.nsf, *.nsfe)|*.NSF;*.NSFE|ZIPファイル (*.zip)|*.ZIP|7-Zipファイル (*.7z)|*.7z|すべてのファイル (*.*)|*.* 対応するすべてのファイル (*.nes, *.zip, *.7z, *.fds, *.nsf, *.nsfe, *.unf, *.ips)|*.NES;*.ZIP;*.7z;*.IPS;*.FDS;*.NSF;*.NSFE;*.UNF|ファミコンゲーム (*.nes, *.unf)|*.NES;*.UNF|ファミコンディスクシステムのゲーム (*.fds)|*.FDS|NSFファイル (*.nsf, *.nsfe)|*.NSF;*.NSFE|ZIPファイル (*.zip)|*.ZIP|7-Zipファイル (*.7z)|*.7z|IPSファイル (*.ips)|*.IPS|すべてのファイル (*.*)|*.* diff --git a/GUI.NET/Dependencies/resources.pt.xml b/GUI.NET/Dependencies/resources.pt.xml index a12c547b..73554258 100644 --- a/GUI.NET/Dependencies/resources.pt.xml +++ b/GUI.NET/Dependencies/resources.pt.xml @@ -68,6 +68,9 @@ Do início do jogo Do estado atual Parar + Video Recorder + Gravar... + Parar Gravar som Gravar... Parar @@ -477,11 +480,19 @@ Ok Buscar +
+ Save to: + Use ZMBV compression + Browse... + OK + Cancelar +
Todos os tipos de arquivo (*.*)|*.* Vídeos (*.mmo)|*.mmo|Todos os arquivos (*.*)|*.* Arquivos wave (*.wav)|*.wav|Todos os arquivos (*.*)|*.* + Arquivos avi (*.avi)|*.avi|Todos os arquivos (*.*)|*.* Arquivos pal (*.pal)|*.pal|Todos os arquivos (*.*)|*.* Todos os formatos suportados (*.nes, *.zip, *.7z, *.fds, *.nsf, *.nsfe, *.unf)|*.NES;*.ZIP;*.7z;*.FDS;*.NSF;*.NSFE;*.UNF|Roms de NES (*.nes, *.unf)|*.NES;*.UNF|Roms de Famicom Disk System (*.fds)|*.FDS|Arquivos NSF (*.nsf, *.nsfe)|*.NSF;*.NSFE|Arquivos ZIP (*.zip)|*.ZIP|Arquivos 7-Zip (*.7z)|*.7z|Todos os arquivos (*.*)|*.* Todos os formatos suportados (*.nes, *.zip, *.7z, *.fds, *.nsf, *.nsfe, *.unf, *.ips)|*.NES;*.ZIP;*.7z;*.IPS;*.FDS;*.NSF;*.NSFE;*.UNF|Roms de NES(*.nes, *.unf)|*.NES;*.UNF|Roms de Famicom Disk System (*.fds)|*.FDS|Arquivos NSF (*.nsf, *.nsfe)|*.NSF;*.NSFE|Arquivos ZIP (*.zip)|*.ZIP|Arquivos 7-Zip (*.7z)|*.7z|Arquivos IPS (*.ips)|*.IPS|Todos os arquivos (*.*)|*.* diff --git a/GUI.NET/Dependencies/resources.ru.xml b/GUI.NET/Dependencies/resources.ru.xml index 8864aac9..3c44ed58 100644 --- a/GUI.NET/Dependencies/resources.ru.xml +++ b/GUI.NET/Dependencies/resources.ru.xml @@ -71,6 +71,9 @@ Запись звука Начать Остановить + Video Recorder + Начать + Остановить Читы Тесты Запустить... @@ -481,11 +484,19 @@ ОК Отмена +
+ Save to: + Use ZMBV compression + Browse... + OK + Отмена +
Все файлы (*.*)|*.* Записи (*.mmo)|*.mmo|All Files (*.*)|*.* Wave файлы (*.wav)|*.wav|All Files (*.*)|*.* + Avi файлы (*.avi)|*.avi|All Files (*.*)|*.* Файлы палитры (*.pal)|*.pal|All Files (*.*)|*.* Все поддерживаемые форматы (*.nes, *.zip, *.7z, *.nsf, *.nsfe, *.fds, *.unf)|*.NES;*.ZIP;*.7z;*.FDS;*.NSF;*.NSFE;*.UNF|NES Roms (*.nes, *.unf)|*.NES;*.UNF|Famicom Disk System Roms (*.fds)|*.FDS|NSF files (*.nsf, *.nsfe)|*.nsf;*.nsfe|ZIP Archives (*.zip)|*.ZIP|7-Zip Archives (*.7z)|*.7z|All (*.*)|*.* Все поддерживаемые форматы (*.nes, *.zip, *.7z, *.fds, *.nsf, *.nsfe, *.unf, *.ips)|*.NES;*.ZIP;*.7z;*.IPS;*.FDS;*.NSF;*.NSFE;*.UNF|NES Roms (*.nes, *.unf)|*.NES;*.UNF|Famicom Disk System Roms (*.fds)|*.FDS|NSF files (*.nsf, *.nsfe)|*.nsf;*.nsfe|ZIP Archives (*.zip)|*.ZIP|7-Zip Archives (*.7z)|*.7z|IPS Patches (*.ips)|*.IPS|All (*.*)|*.* diff --git a/GUI.NET/Dependencies/resources.uk.xml b/GUI.NET/Dependencies/resources.uk.xml index af8bd160..0d70c5de 100644 --- a/GUI.NET/Dependencies/resources.uk.xml +++ b/GUI.NET/Dependencies/resources.uk.xml @@ -71,6 +71,9 @@ Запис звуку Почати Зупинити + Video Recorder + Почати + Зупинити Чити Тести Запустити... @@ -194,7 +197,7 @@ Контролер #2 Контролер #3 Контролер #4 - + Пресети Клавіатура WSAD розкладка @@ -210,7 +213,7 @@ SNES30 геймпад Контролер #1 Контролер #2 - + Очистити OK Відміна @@ -254,7 +257,7 @@ Y Filter (Horizontal Blur) I Filter (Horizontal Bleed) Q Filter (Horizontal Bleed) - + Scanlines Scanlines Скидання @@ -268,7 +271,7 @@ Відключити sprites Force background display in first column Force sprite display in first column - + OK Відміна @@ -474,17 +477,25 @@
Натисніть будь-яку клавішу на клавіатурі або контролері...."> -
-
- Iм'я : - ОК - Вiдмiна -
+ +
+ Iм'я : + ОК + Вiдмiна +
+
+ Save to: + Use ZMBV compression + Browse... + OK + Вiдмiна +
Всi файли (*.*)|*.* Записи (*.mmo)|*.mmo|All Files (*.*)|*.* Wave файли (*.wav)|*.wav|All Files (*.*)|*.* + Avi файли (*.avi)|*.avi|All Files (*.*)|*.* Файли палiтр (*.pal)|*.pal|All Files (*.*)|*.* Всі підтримувані формати (*.nes, *.zip, *.7z, *.nsf, *.nsfe, *.fds, *.unf)|*.NES;*.ZIP;*.7z;*.FDS;*.NSF;*.NSFE;*.UNF|NES Roms (*.nes, *.unf)|*.NES;*.UNF|Famicom Disk System Roms (*.fds)|*.FDS|NSF files (*.nsf, *.nsfe)|*.nsf;*.nsfe|ZIP Archives (*.zip)|*.ZIP|7-Zip Archives (*.7z)|*.7z|All (*.*)|*.* Всі підтримувані формати (*.nes, *.zip, *.7z, *.fds, *.nsf, *.nsfe, *.unf, *.ips)|*.NES;*.ZIP;*.7z;*.IPS;*.FDS;*.NSF;*.NSFE;*.UNF|NES Roms (*.nes, *.unf)|*.NES;*.UNF|Famicom Disk System Roms (*.fds)|*.FDS|NSF files (*.nsf, *.nsfe)|*.nsf;*.nsfe|ZIP Archives (*.zip)|*.ZIP|7-Zip Archives (*.7z)|*.7z|IPS Patches (*.ips)|*.IPS|All (*.*)|*.* diff --git a/GUI.NET/Forms/frmMain.Designer.cs b/GUI.NET/Forms/frmMain.Designer.cs index a2730ce2..28401459 100644 --- a/GUI.NET/Forms/frmMain.Designer.cs +++ b/GUI.NET/Forms/frmMain.Designer.cs @@ -145,10 +145,14 @@ namespace Mesen.GUI.Forms this.mnuRecordFromStart = new System.Windows.Forms.ToolStripMenuItem(); this.mnuRecordFromNow = new System.Windows.Forms.ToolStripMenuItem(); this.mnuStopMovie = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuCheats = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripMenuItem22 = new System.Windows.Forms.ToolStripSeparator(); + this.mnuVideoRecorder = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuAviRecord = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuAviStop = new System.Windows.Forms.ToolStripMenuItem(); this.mnuSoundRecorder = new System.Windows.Forms.ToolStripMenuItem(); this.mnuWaveRecord = new System.Windows.Forms.ToolStripMenuItem(); this.mnuWaveStop = new System.Windows.Forms.ToolStripMenuItem(); - this.mnuCheats = new System.Windows.Forms.ToolStripMenuItem(); this.toolStripMenuItem12 = new System.Windows.Forms.ToolStripSeparator(); this.mnuTests = new System.Windows.Forms.ToolStripMenuItem(); this.mnuTestRun = new System.Windows.Forms.ToolStripMenuItem(); @@ -453,7 +457,7 @@ namespace Mesen.GUI.Forms this.mnuShowFPS}); this.mnuEmulationSpeed.Image = global::Mesen.GUI.Properties.Resources.Speed; this.mnuEmulationSpeed.Name = "mnuEmulationSpeed"; - this.mnuEmulationSpeed.Size = new System.Drawing.Size(152, 22); + this.mnuEmulationSpeed.Size = new System.Drawing.Size(135, 22); this.mnuEmulationSpeed.Text = "Speed"; this.mnuEmulationSpeed.DropDownOpening += new System.EventHandler(this.mnuEmulationSpeed_DropDownOpening); // @@ -555,7 +559,7 @@ namespace Mesen.GUI.Forms this.mnuFullscreen}); this.mnuVideoScale.Image = global::Mesen.GUI.Properties.Resources.Fullscreen; this.mnuVideoScale.Name = "mnuVideoScale"; - this.mnuVideoScale.Size = new System.Drawing.Size(152, 22); + this.mnuVideoScale.Size = new System.Drawing.Size(135, 22); this.mnuVideoScale.Text = "Video Size"; // // mnuScale1x @@ -667,7 +671,7 @@ namespace Mesen.GUI.Forms this.toolStripMenuItem19, this.mnuBilinearInterpolation}); this.mnuVideoFilter.Name = "mnuVideoFilter"; - this.mnuVideoFilter.Size = new System.Drawing.Size(152, 22); + this.mnuVideoFilter.Size = new System.Drawing.Size(135, 22); this.mnuVideoFilter.Text = "Video Filter"; // // mnuNoneFilter @@ -876,7 +880,7 @@ namespace Mesen.GUI.Forms this.mnuRegionDendy}); this.mnuRegion.Image = global::Mesen.GUI.Properties.Resources.Globe; this.mnuRegion.Name = "mnuRegion"; - this.mnuRegion.Size = new System.Drawing.Size(152, 22); + this.mnuRegion.Size = new System.Drawing.Size(135, 22); this.mnuRegion.Text = "Region"; // // mnuRegionAuto @@ -910,13 +914,13 @@ namespace Mesen.GUI.Forms // toolStripMenuItem10 // this.toolStripMenuItem10.Name = "toolStripMenuItem10"; - this.toolStripMenuItem10.Size = new System.Drawing.Size(149, 6); + this.toolStripMenuItem10.Size = new System.Drawing.Size(132, 6); // // mnuAudioConfig // this.mnuAudioConfig.Image = global::Mesen.GUI.Properties.Resources.Audio; this.mnuAudioConfig.Name = "mnuAudioConfig"; - this.mnuAudioConfig.Size = new System.Drawing.Size(152, 22); + this.mnuAudioConfig.Size = new System.Drawing.Size(135, 22); this.mnuAudioConfig.Text = "Audio"; this.mnuAudioConfig.Click += new System.EventHandler(this.mnuAudioConfig_Click); // @@ -924,7 +928,7 @@ namespace Mesen.GUI.Forms // this.mnuInput.Image = global::Mesen.GUI.Properties.Resources.Controller; this.mnuInput.Name = "mnuInput"; - this.mnuInput.Size = new System.Drawing.Size(152, 22); + this.mnuInput.Size = new System.Drawing.Size(135, 22); this.mnuInput.Text = "Input"; this.mnuInput.Click += new System.EventHandler(this.mnuInput_Click); // @@ -932,7 +936,7 @@ namespace Mesen.GUI.Forms // this.mnuVideoConfig.Image = global::Mesen.GUI.Properties.Resources.Video; this.mnuVideoConfig.Name = "mnuVideoConfig"; - this.mnuVideoConfig.Size = new System.Drawing.Size(152, 22); + this.mnuVideoConfig.Size = new System.Drawing.Size(135, 22); this.mnuVideoConfig.Text = "Video"; this.mnuVideoConfig.Click += new System.EventHandler(this.mnuVideoConfig_Click); // @@ -940,20 +944,20 @@ namespace Mesen.GUI.Forms // this.mnuEmulationConfig.Image = global::Mesen.GUI.Properties.Resources.DipSwitches; this.mnuEmulationConfig.Name = "mnuEmulationConfig"; - this.mnuEmulationConfig.Size = new System.Drawing.Size(152, 22); + this.mnuEmulationConfig.Size = new System.Drawing.Size(135, 22); this.mnuEmulationConfig.Text = "Emulation"; this.mnuEmulationConfig.Click += new System.EventHandler(this.mnuEmulationConfig_Click); // // toolStripMenuItem11 // this.toolStripMenuItem11.Name = "toolStripMenuItem11"; - this.toolStripMenuItem11.Size = new System.Drawing.Size(149, 6); + this.toolStripMenuItem11.Size = new System.Drawing.Size(132, 6); // // mnuPreferences // this.mnuPreferences.Image = global::Mesen.GUI.Properties.Resources.Cog; this.mnuPreferences.Name = "mnuPreferences"; - this.mnuPreferences.Size = new System.Drawing.Size(152, 22); + this.mnuPreferences.Size = new System.Drawing.Size(135, 22); this.mnuPreferences.Text = "Preferences"; this.mnuPreferences.Click += new System.EventHandler(this.mnuPreferences_Click); // @@ -962,8 +966,10 @@ namespace Mesen.GUI.Forms this.mnuTools.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { this.mnuNetPlay, this.mnuMovies, - this.mnuSoundRecorder, this.mnuCheats, + this.toolStripMenuItem22, + this.mnuSoundRecorder, + this.mnuVideoRecorder, this.toolStripMenuItem12, this.mnuTests, this.mnuDebugger, @@ -1127,6 +1133,45 @@ namespace Mesen.GUI.Forms this.mnuStopMovie.Text = "Stop"; this.mnuStopMovie.Click += new System.EventHandler(this.mnuStopMovie_Click); // + // mnuCheats + // + this.mnuCheats.Image = global::Mesen.GUI.Properties.Resources.CheatCode; + this.mnuCheats.Name = "mnuCheats"; + this.mnuCheats.Size = new System.Drawing.Size(231, 22); + this.mnuCheats.Text = "Cheats"; + this.mnuCheats.Click += new System.EventHandler(this.mnuCheats_Click); + // + // toolStripMenuItem22 + // + this.toolStripMenuItem22.Name = "toolStripMenuItem22"; + this.toolStripMenuItem22.Size = new System.Drawing.Size(228, 6); + // + // 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(231, 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.Stop; + 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); + // // mnuSoundRecorder // this.mnuSoundRecorder.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { @@ -1153,14 +1198,6 @@ namespace Mesen.GUI.Forms this.mnuWaveStop.Text = "Stop Recording"; this.mnuWaveStop.Click += new System.EventHandler(this.mnuWaveStop_Click); // - // mnuCheats - // - this.mnuCheats.Image = global::Mesen.GUI.Properties.Resources.CheatCode; - this.mnuCheats.Name = "mnuCheats"; - this.mnuCheats.Size = new System.Drawing.Size(231, 22); - this.mnuCheats.Text = "Cheats"; - this.mnuCheats.Click += new System.EventHandler(this.mnuCheats_Click); - // // toolStripMenuItem12 // this.toolStripMenuItem12.Name = "toolStripMenuItem12"; @@ -1496,6 +1533,10 @@ namespace Mesen.GUI.Forms private System.Windows.Forms.ToolStripMenuItem mnuNtscBisqwitHalfFilter; private System.Windows.Forms.ToolStripMenuItem mnuNtscBisqwitFullFilter; private System.Windows.Forms.ToolStripMenuItem mnuNtscBisqwitQuarterFilter; + private System.Windows.Forms.ToolStripSeparator toolStripMenuItem22; + private System.Windows.Forms.ToolStripMenuItem mnuVideoRecorder; + private System.Windows.Forms.ToolStripMenuItem mnuAviRecord; + private System.Windows.Forms.ToolStripMenuItem mnuAviStop; } } diff --git a/GUI.NET/Forms/frmMain.cs b/GUI.NET/Forms/frmMain.cs index cc8841e0..9542c515 100644 --- a/GUI.NET/Forms/frmMain.cs +++ b/GUI.NET/Forms/frmMain.cs @@ -619,6 +619,10 @@ namespace Mesen.GUI.Forms mnuWaveRecord.Enabled = _emuThread != null && !waveRecording; mnuWaveStop.Enabled = _emuThread != null && waveRecording; + bool aviRecording = InteropEmu.AviIsRecording(); + mnuAviRecord.Enabled = _emuThread != null && !aviRecording; + mnuAviStop.Enabled = _emuThread != null && aviRecording; + bool testRecording = InteropEmu.RomTestRecording(); mnuTestRun.Enabled = !netPlay && !moviePlaying && !movieRecording; mnuTestStopRecording.Enabled = _emuThread != null && testRecording; @@ -934,7 +938,6 @@ namespace Mesen.GUI.Forms RecordMovie(false); } - private void mnuWaveRecord_Click(object sender, EventArgs e) { SaveFileDialog sfd = new SaveFileDialog(); @@ -951,6 +954,20 @@ namespace Mesen.GUI.Forms InteropEmu.WaveStop(); } + private void mnuAviRecord_Click(object sender, EventArgs e) + { + using(frmRecordAvi frm = new frmRecordAvi()) { + if(frm.ShowDialog(mnuVideoRecorder) == DialogResult.OK) { + InteropEmu.AviRecord(frm.Filename, frm.UseCompression ? VideoCodec.ZMBV : VideoCodec.None); + } + } + } + + private void mnuAviStop_Click(object sender, EventArgs e) + { + InteropEmu.AviStop(); + } + private void mnuTestRun_Click(object sender, EventArgs e) { OpenFileDialog ofd = new OpenFileDialog(); diff --git a/GUI.NET/Forms/frmRecordAvi.Designer.cs b/GUI.NET/Forms/frmRecordAvi.Designer.cs new file mode 100644 index 00000000..e6bd6a8b --- /dev/null +++ b/GUI.NET/Forms/frmRecordAvi.Designer.cs @@ -0,0 +1,131 @@ +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.lblAviFile = new System.Windows.Forms.Label(); + this.txtFilename = new System.Windows.Forms.TextBox(); + this.btnBrowse = new System.Windows.Forms.Button(); + this.chkUseCompression = new System.Windows.Forms.CheckBox(); + this.tableLayoutPanel1.SuspendLayout(); + this.SuspendLayout(); + // + // baseConfigPanel + // + this.baseConfigPanel.Location = new System.Drawing.Point(0, 66); + this.baseConfigPanel.Size = new System.Drawing.Size(375, 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.lblAviFile, 0, 0); + this.tableLayoutPanel1.Controls.Add(this.txtFilename, 1, 0); + this.tableLayoutPanel1.Controls.Add(this.btnBrowse, 2, 0); + this.tableLayoutPanel1.Controls.Add(this.chkUseCompression, 0, 1); + this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill; + this.tableLayoutPanel1.Location = new System.Drawing.Point(0, 0); + this.tableLayoutPanel1.Name = "tableLayoutPanel1"; + this.tableLayoutPanel1.RowCount = 3; + 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.Absolute, 20F)); + this.tableLayoutPanel1.Size = new System.Drawing.Size(375, 95); + this.tableLayoutPanel1.TabIndex = 0; + // + // 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(56, 3); + this.txtFilename.Name = "txtFilename"; + this.txtFilename.ReadOnly = true; + this.txtFilename.Size = new System.Drawing.Size(235, 20); + this.txtFilename.TabIndex = 1; + // + // btnBrowse + // + this.btnBrowse.Location = new System.Drawing.Point(297, 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); + // + // chkUseCompression + // + this.chkUseCompression.AutoSize = true; + this.chkUseCompression.Checked = true; + this.chkUseCompression.CheckState = System.Windows.Forms.CheckState.Checked; + this.tableLayoutPanel1.SetColumnSpan(this.chkUseCompression, 3); + this.chkUseCompression.Location = new System.Drawing.Point(3, 32); + this.chkUseCompression.Name = "chkUseCompression"; + this.chkUseCompression.Size = new System.Drawing.Size(140, 17); + this.chkUseCompression.TabIndex = 3; + this.chkUseCompression.Text = "Use ZMBV compression"; + this.chkUseCompression.UseVisualStyleBackColor = true; + // + // frmRecordAvi + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(375, 95); + this.Controls.Add(this.tableLayoutPanel1); + 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.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.CheckBox chkUseCompression; + } +} \ No newline at end of file diff --git a/GUI.NET/Forms/frmRecordAvi.cs b/GUI.NET/Forms/frmRecordAvi.cs new file mode 100644 index 00000000..4f5e434e --- /dev/null +++ b/GUI.NET/Forms/frmRecordAvi.cs @@ -0,0 +1,48 @@ +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(); + } + + public string Filename { get; internal set; } + public bool UseCompression { 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; + this.UseCompression = chkUseCompression.Checked; + } + + private void btnBrowse_Click(object sender, EventArgs e) + { + SaveFileDialog sfd = new SaveFileDialog(); + sfd.SetFilter(ResourceHelper.GetMessage("FilterAvi")); + sfd.InitialDirectory = ConfigManager.AviFolder; + sfd.FileName = InteropEmu.GetRomInfo().GetRomName() + ".avi"; + if(sfd.ShowDialog() == System.Windows.Forms.DialogResult.OK) { + txtFilename.Text = sfd.FileName; + } + } + } +} diff --git a/GUI.NET/Forms/frmRecordAvi.resx b/GUI.NET/Forms/frmRecordAvi.resx new file mode 100644 index 00000000..8766f298 --- /dev/null +++ b/GUI.NET/Forms/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/GUI.NET/GUI.NET.csproj b/GUI.NET/GUI.NET.csproj index eb0d28f5..5bb81167 100644 --- a/GUI.NET/GUI.NET.csproj +++ b/GUI.NET/GUI.NET.csproj @@ -562,6 +562,12 @@ frmLogWindow.cs + + Form + + + frmRecordAvi.cs + Form @@ -773,6 +779,9 @@ frmMain.cs + + frmRecordAvi.cs + frmSelectRom.cs @@ -882,6 +891,7 @@ Always + diff --git a/GUI.NET/InteropEmu.cs b/GUI.NET/InteropEmu.cs index 665df515..d3f7fbd9 100644 --- a/GUI.NET/InteropEmu.cs +++ b/GUI.NET/InteropEmu.cs @@ -91,6 +91,10 @@ namespace Mesen.GUI [DllImport(DLLPath)] [return: MarshalAs(UnmanagedType.I1)] public static extern bool MoviePlaying(); [DllImport(DLLPath)] [return: MarshalAs(UnmanagedType.I1)] public static extern bool MovieRecording(); + [DllImport(DLLPath)] public static extern void AviRecord([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(UTF8Marshaler))]string filename, VideoCodec codec); + [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(); @@ -1084,6 +1088,12 @@ namespace Mesen.GUI Sunsoft5B = 10 } + public enum VideoCodec + { + None = 0, + ZMBV = 1, + } + public enum VideoFilterType { None = 0, diff --git a/GUI.NET/Properties/Resources.Designer.cs b/GUI.NET/Properties/Resources.Designer.cs index 68d45474..9056bc1a 100644 --- a/GUI.NET/Properties/Resources.Designer.cs +++ b/GUI.NET/Properties/Resources.Designer.cs @@ -470,6 +470,16 @@ namespace Mesen.GUI.Properties { } } + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap VideoRecorder { + get { + object obj = ResourceManager.GetObject("VideoRecorder", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + /// /// Looks up a localized resource of type System.Drawing.Bitmap. /// diff --git a/GUI.NET/Properties/Resources.resx b/GUI.NET/Properties/Resources.resx index 2779013c..31b9233b 100644 --- a/GUI.NET/Properties/Resources.resx +++ b/GUI.NET/Properties/Resources.resx @@ -244,4 +244,7 @@ ..\Resources\Donate.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + ..\Resources\VideoRecorder.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + \ No newline at end of file diff --git a/GUI.NET/Resources/VideoRecorder.png b/GUI.NET/Resources/VideoRecorder.png new file mode 100644 index 0000000000000000000000000000000000000000..d2144c5cd541078a3fe5f1e2dcad1958d3a6f753 GIT binary patch literal 233 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`EX7WqAsj$Z!;#Vf4nJ za0`PlBg3pY5dQ4 Z#9r1Sm3Vw-v^>yR44$rjF6*2UngIMmRNMdn literal 0 HcmV?d00001 diff --git a/InteropDLL/ConsoleWrapper.cpp b/InteropDLL/ConsoleWrapper.cpp index 2c92db00..6e12c91e 100644 --- a/InteropDLL/ConsoleWrapper.cpp +++ b/InteropDLL/ConsoleWrapper.cpp @@ -17,6 +17,7 @@ #include "../Core/NsfMapper.h" #include "../Core/IRenderingDevice.h" #include "../Core/IAudioDevice.h" +#include "../Utilities/AviWriter.h" #ifdef WIN32 #include "../Windows/Renderer.h" @@ -299,6 +300,10 @@ namespace InteropEmu { DllExport bool __stdcall MoviePlaying() { return Movie::Playing(); } DllExport bool __stdcall MovieRecording() { return Movie::Recording(); } + DllExport void __stdcall AviRecord(char* filename, VideoCodec codec) { VideoDecoder::GetInstance()->StartRecording(filename, codec); } + DllExport void __stdcall AviStop() { VideoDecoder::GetInstance()->StopRecording(); } + DllExport bool __stdcall AviIsRecording() { return VideoDecoder::GetInstance()->IsRecording(); } + DllExport void __stdcall WaveRecord(char* filename) { SoundMixer::StartRecording(filename); } DllExport void __stdcall WaveStop() { SoundMixer::StopRecording(); } DllExport bool __stdcall WaveIsRecording() { return SoundMixer::IsRecording(); } diff --git a/Utilities/AviWriter.cpp b/Utilities/AviWriter.cpp new file mode 100644 index 00000000..3485e07c --- /dev/null +++ b/Utilities/AviWriter.cpp @@ -0,0 +1,270 @@ +// This file is a part of Mesen +// It is a heavily modified version of the hardware.h/cpp file found in DOSBox's code. + +/* +* Copyright (C) 2002-2011 The DOSBox Team +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include "stdafx.h" +#include +#include "AviWriter.h" +#include "BaseCodec.h" +#include "RawCodec.h" +#include "ZmbvCodec.h" + +void AviWriter::WriteAviChunk(const char *tag, uint32_t size, void *data, uint32_t flags) +{ + uint8_t chunk[8] = { (uint8_t)tag[0], (uint8_t)tag[1], (uint8_t)tag[2], (uint8_t)tag[3] }; + host_writed(&chunk[4], size); + _file.write((char*)chunk, 8); + + uint32_t writesize = (size + 1)&~1; + _file.write((char*)data, writesize); + + uint32_t pos = _written + 4; + _written += writesize + 8; + + _aviIndex.push_back(tag[0]); + _aviIndex.push_back(tag[1]); + _aviIndex.push_back(tag[2]); + _aviIndex.push_back(tag[3]); + _aviIndex.insert(_aviIndex.end(), 12, 0); + host_writed(_aviIndex.data() + _aviIndex.size() - 12, flags); + host_writed(_aviIndex.data() + _aviIndex.size() - 8, pos); + host_writed(_aviIndex.data() + _aviIndex.size() - 4, size); +} + +void AviWriter::host_writew(uint8_t* buffer, uint16_t value) +{ + buffer[0] = value & 0xFF; + buffer[1] = value >> 8; +} + +void AviWriter::host_writed(uint8_t* buffer, uint32_t value) +{ + buffer[0] = value; + buffer[1] = value >> 8; + buffer[2] = value >> 16; + buffer[3] = value >> 24; +} + +bool AviWriter::StartWrite(string filename, VideoCodec codec, uint32_t width, uint32_t height, uint32_t bpp, uint32_t fps, uint32_t audioSampleRate) +{ + _codecType = codec; + _file.open(filename, std::ios::out | std::ios::binary); + if(!_file) { + return false; + } + + if(_codecType == VideoCodec::ZMBV) { + _codec.reset(new ZmbvCodec()); + } else { + _codec.reset(new RawCodec()); + } + + if(!_codec->SetupCompress(width, height)) { + return false; + } + + _frameBuffer = new uint8_t[width*height*bpp]; + + _aviIndex.clear(); + _aviIndex.insert(_aviIndex.end(), 8, 0); + + _width = width; + _height = height; + _bpp = bpp; + _fps = fps; + + _audiorate = audioSampleRate; + + for(int i = 0; i < AviWriter::AviHeaderSize; i++) { + _file.put(0); + } + _frames = 0; + _written = 0; + _audioPos = 0; + _audiowritten = 0; + + return true; +} + +void AviWriter::EndWrite() +{ + /* Close the video */ + uint8_t avi_header[AviWriter::AviHeaderSize]; + uint32_t main_list; + uint32_t header_pos = 0; +#define AVIOUT4(_S_) memcpy(&avi_header[header_pos],_S_,4);header_pos+=4; +#define AVIOUTw(_S_) host_writew(&avi_header[header_pos], _S_);header_pos+=2; +#define AVIOUTd(_S_) host_writed(&avi_header[header_pos], _S_);header_pos+=4; + /* Try and write an avi header */ + AVIOUT4("RIFF"); // Riff header + AVIOUTd(AviWriter::AviHeaderSize + _written - 8 + (uint32_t)_aviIndex.size()); + AVIOUT4("AVI "); + AVIOUT4("LIST"); // List header + main_list = header_pos; + AVIOUTd(0); // TODO size of list + AVIOUT4("hdrl"); + + AVIOUT4("avih"); + AVIOUTd(56); /* # of bytes to follow */ + AVIOUTd((uint32_t)(1000000 / _fps)); /* Microseconds per frame */ + AVIOUTd(0); + AVIOUTd(0); /* PaddingGranularity (whatever that might be) */ + AVIOUTd(0x110); /* Flags,0x10 has index, 0x100 interleaved */ + AVIOUTd(_frames); /* TotalFrames */ + AVIOUTd(0); /* InitialFrames */ + AVIOUTd(2); /* Stream count */ + AVIOUTd(0); /* SuggestedBufferSize */ + AVIOUTd(_width); /* Width */ + AVIOUTd(_height); /* Height */ + AVIOUTd(0); /* TimeScale: Unit used to measure time */ + AVIOUTd(0); /* DataRate: Data rate of playback */ + AVIOUTd(0); /* StartTime: Starting time of AVI data */ + AVIOUTd(0); /* DataLength: Size of AVI data chunk */ + + /* Video stream list */ + AVIOUT4("LIST"); + AVIOUTd(4 + 8 + 56 + 8 + 40); /* Size of the list */ + AVIOUT4("strl"); + /* video stream header */ + AVIOUT4("strh"); + AVIOUTd(56); /* # of bytes to follow */ + AVIOUT4("vids"); /* Type */ + AVIOUT4(_codec->GetFourCC()); /* Handler */ + AVIOUTd(0); /* Flags */ + AVIOUTd(0); /* Reserved, MS says: wPriority, wLanguage */ + AVIOUTd(0); /* InitialFrames */ + AVIOUTd(1000000); /* Scale */ + AVIOUTd(_fps); /* Rate: Rate/Scale == samples/second */ + AVIOUTd(0); /* Start */ + AVIOUTd(_frames); /* Length */ + AVIOUTd(0); /* SuggestedBufferSize */ + AVIOUTd(~0); /* Quality */ + AVIOUTd(0); /* SampleSize */ + AVIOUTd(0); /* Frame */ + AVIOUTd(0); /* Frame */ + /* The video stream format */ + AVIOUT4("strf"); + AVIOUTd(40); /* # of bytes to follow */ + AVIOUTd(40); /* Size */ + AVIOUTd(_width); /* Width */ + AVIOUTd(_height); /* Height */ + // OUTSHRT(1); OUTSHRT(24); /* Planes, Count */ + AVIOUTw(1); //number of planes + AVIOUTw(24); //bits for colors + AVIOUT4(_codec->GetFourCC()); /* Compression */ + AVIOUTd(_width * _height * 4); /* SizeImage (in bytes?) */ + AVIOUTd(0); /* XPelsPerMeter */ + AVIOUTd(0); /* YPelsPerMeter */ + AVIOUTd(0); /* ClrUsed: Number of colors used */ + AVIOUTd(0); /* ClrImportant: Number of colors important */ + + /* Audio stream list */ + AVIOUT4("LIST"); + AVIOUTd(4 + 8 + 56 + 8 + 16); /* Length of list in bytes */ + AVIOUT4("strl"); + /* The audio stream header */ + AVIOUT4("strh"); + AVIOUTd(56); /* # of bytes to follow */ + AVIOUT4("auds"); + AVIOUTd(0); /* Format (Optionally) */ + AVIOUTd(0); /* Flags */ + AVIOUTd(0); /* Reserved, MS says: wPriority, wLanguage */ + AVIOUTd(0); /* InitialFrames */ + AVIOUTd(4); /* Scale */ + AVIOUTd(_audiorate * 4); /* Rate, actual rate is scale/rate */ + AVIOUTd(0); /* Start */ + if(!_audiorate) + _audiorate = 1; + AVIOUTd(_audiowritten / 4); /* Length */ + AVIOUTd(0); /* SuggestedBufferSize */ + AVIOUTd(~0); /* Quality */ + AVIOUTd(4); /* SampleSize */ + AVIOUTd(0); /* Frame */ + AVIOUTd(0); /* Frame */ + /* The audio stream format */ + AVIOUT4("strf"); + AVIOUTd(16); /* # of bytes to follow */ + AVIOUTw(1); /* Format, WAVE_ZMBV_FORMAT_PCM */ + AVIOUTw(2); /* Number of channels */ + AVIOUTd(_audiorate); /* SamplesPerSec */ + AVIOUTd(_audiorate * 4); /* AvgBytesPerSec*/ + AVIOUTw(4); /* BlockAlign */ + AVIOUTw(16); /* BitsPerSample */ + int nmain = header_pos - main_list - 4; + /* Finish stream list, i.e. put number of bytes in the list to proper pos */ + + int njunk = AviWriter::AviHeaderSize - 8 - 12 - header_pos; + AVIOUT4("JUNK"); + AVIOUTd(njunk); + /* Fix the size of the main list */ + header_pos = main_list; + AVIOUTd(nmain); + header_pos = AviWriter::AviHeaderSize - 12; + AVIOUT4("LIST"); + AVIOUTd(_written + 4); /* Length of list in bytes */ + AVIOUT4("movi"); + /* First add the index table to the end */ + memcpy(_aviIndex.data(), "idx1", 4); + host_writed(_aviIndex.data() + 4, (uint32_t)_aviIndex.size() - 8); + + _file.write((char*)_aviIndex.data(), _aviIndex.size()); + _file.seekp(std::ios::beg); + _file.write((char*)avi_header, AviWriter::AviHeaderSize); + _file.close(); +} + +void AviWriter::AddFrame(uint8_t *frameData) +{ + if(!_file) { + return; + } + + bool isKeyFrame = (_frames % 120 == 0) ? 1 : 0; + + uint8_t* compressedData = nullptr; + int written = _codec->CompressFrame(isKeyFrame, frameData, &compressedData); + if(written < 0) { + return; + } + + if(_codecType == VideoCodec::None) { + isKeyFrame = true; + } + WriteAviChunk(_codecType == VideoCodec::None ? "00db" : "00dc", written, compressedData, isKeyFrame ? 0x10 : 0); + _frames++; + + if(_audioPos) { + auto lock = _audioLock.AcquireSafe(); + WriteAviChunk("01wb", _audioPos, _audiobuf, 0); + _audiowritten += _audioPos; + _audioPos = 0; + } +} + +void AviWriter::AddSound(int16_t *data, uint32_t sampleCount) +{ + if(!_file) { + return; + } + + auto lock = _audioLock.AcquireSafe(); + memcpy(_audiobuf+_audioPos/2, data, sampleCount * 4); + _audioPos += sampleCount * 4; +} \ No newline at end of file diff --git a/Utilities/AviWriter.h b/Utilities/AviWriter.h new file mode 100644 index 00000000..7501e515 --- /dev/null +++ b/Utilities/AviWriter.h @@ -0,0 +1,57 @@ +// This file is a part of Mesen +// It is a heavily modified version of the hardware.h/cpp file found in DOSBox's code. + +#pragma once +#include "stdafx.h" +#include "SimpleLock.h" +#include "BaseCodec.h" + +enum class VideoCodec +{ + None = 0, + ZMBV = 1, +}; + +class AviWriter +{ +private: + static constexpr int WaveBufferSize = 16 * 1024; + static constexpr int AviHeaderSize = 500; + + std::unique_ptr _codec; + ofstream _file; + + VideoCodec _codecType; + + int16_t _audiobuf[WaveBufferSize]; + uint32_t _audioPos = 0; + uint32_t _audiorate = 0; + uint32_t _audiowritten = 0; + + uint32_t _frames = 0; + uint32_t _width = 0; + uint32_t _height = 0; + uint32_t _bpp = 0; + uint32_t _written = 0; + uint32_t _fps = 0; + + uint8_t* _frameBuffer = nullptr; + + int _bufSize = 0; + void *_buf = nullptr; + vector _aviIndex; + + SimpleLock _audioLock; + +private: + void host_writew(uint8_t* buffer, uint16_t value); + void host_writed(uint8_t* buffer, uint32_t value); + void WriteAviChunk(const char * tag, uint32_t size, void * data, uint32_t flags); + +public: + void AddFrame(uint8_t* frameData); + void AddSound(int16_t * data, uint32_t sampleCount); + + bool StartWrite(string filename, VideoCodec codec, uint32_t width, uint32_t height, uint32_t bpp, uint32_t fps, uint32_t audioSampleRate); + void EndWrite(); +}; \ No newline at end of file diff --git a/Utilities/BaseCodec.h b/Utilities/BaseCodec.h new file mode 100644 index 00000000..cc1ea691 --- /dev/null +++ b/Utilities/BaseCodec.h @@ -0,0 +1,10 @@ +#pragma once +#include "stdafx.h" + +class BaseCodec +{ +public: + virtual bool SetupCompress(int width, int height) = 0; + virtual int CompressFrame(bool isKeyFrame, uint8_t *frameData, uint8_t** compressedData) = 0; + virtual const char* GetFourCC() = 0; +}; \ No newline at end of file diff --git a/Utilities/RawCodec.h b/Utilities/RawCodec.h new file mode 100644 index 00000000..3631c487 --- /dev/null +++ b/Utilities/RawCodec.h @@ -0,0 +1,50 @@ +#pragma once +#include "stdafx.h" +#include "BaseCodec.h" + +class RawCodec : public BaseCodec +{ +private: + int _width = 0; + int _height = 0; + uint32_t _bufferSize = 0; + uint8_t* _buffer = nullptr; + +public: + virtual bool SetupCompress(int width, int height) override + { + _height = height; + _width = width; + + _bufferSize = width * height * 3; + _buffer = new uint8_t[(_bufferSize + 1) & ~1]; + memset(_buffer, 0, (_bufferSize + 1) & ~1); + + return true; + } + + virtual int CompressFrame(bool isKeyFrame, uint8_t *frameData, uint8_t** compressedData) override + { + *compressedData = _buffer; + + //Convert raw frame to BMP/DIB format (row order is reversed) + uint8_t* buffer = _buffer; + frameData += (_height - 1) * _width * 4; + for(int y = 0; y < _height; y++) { + for(int x = 0; x < _width; x++) { + buffer[0] = frameData[0]; + buffer[1] = frameData[1]; + buffer[2] = frameData[2]; + frameData += 4; + buffer += 3; + } + frameData -= _width * 2 * 4; + } + return _bufferSize; + } + + virtual const char* GetFourCC() override + { + return "\0\0\0\0"; + } +}; \ No newline at end of file diff --git a/Utilities/Utilities.vcxproj b/Utilities/Utilities.vcxproj index ceafd99d..5feaaeba 100644 --- a/Utilities/Utilities.vcxproj +++ b/Utilities/Utilities.vcxproj @@ -322,6 +322,7 @@ + @@ -337,8 +338,10 @@ + + @@ -354,9 +357,11 @@ + + @@ -496,6 +501,7 @@ + diff --git a/Utilities/Utilities.vcxproj.filters b/Utilities/Utilities.vcxproj.filters index ff4c24bf..1433f4f0 100644 --- a/Utilities/Utilities.vcxproj.filters +++ b/Utilities/Utilities.vcxproj.filters @@ -21,6 +21,9 @@ {8e159744-fb91-4e16-aa82-8d8703ba2762} + + {8b0e23bf-1bd9-4cc1-8046-784fd01e8688} + @@ -122,6 +125,18 @@ Header Files + + Avi + + + Avi + + + Avi + + + Avi + @@ -220,5 +235,11 @@ Source Files + + Avi + + + Avi + \ No newline at end of file diff --git a/Utilities/ZmbvCodec.cpp b/Utilities/ZmbvCodec.cpp new file mode 100644 index 00000000..cb40232e --- /dev/null +++ b/Utilities/ZmbvCodec.cpp @@ -0,0 +1,410 @@ +// This file is a part of Mesen +// It is a heavily modified version of the zmbv.h/cpp file found in DOSBox's code. + +/* + * Copyright (C) 2002-2011 The DOSBox Team + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "stdafx.h" +#include +#include +#include +#include + +#include "miniz.h" +#include "ZmbvCodec.h" + +#define DBZV_VERSION_HIGH 0 +#define DBZV_VERSION_LOW 1 + +#define COMPRESSION_NONE 0 +#define COMPRESSION_ZLIB 1 + +#define MAX_VECTOR 16 + +#define Mask_KeyFrame 0x01 +#define Mask_DeltaPalette 0x02 + +int ZmbvCodec::NeededSize( int _width, int _height, zmbv_format_t _format) { + int f; + switch (_format) { + case ZMBV_FORMAT_8BPP:f = 1;break; + case ZMBV_FORMAT_15BPP:f = 2;break; + case ZMBV_FORMAT_16BPP:f = 2;break; + case ZMBV_FORMAT_32BPP:f = 4;break; + default: + return -1; + } + f = f*_width*_height + 2*(1+(_width/8)) * (1+(_height/8))+1024; + return f + f/1000; +} + +bool ZmbvCodec::SetupBuffers(zmbv_format_t _format, int blockwidth, int blockheight) { + FreeBuffers(); + palsize = 0; + switch (_format) { + case ZMBV_FORMAT_8BPP: + pixelsize = 1; + palsize = 256; + break; + case ZMBV_FORMAT_15BPP: + pixelsize = 2; + break; + case ZMBV_FORMAT_16BPP: + pixelsize = 2; + break; + case ZMBV_FORMAT_32BPP: + pixelsize = 4; + break; + default: + return false; + }; + bufsize = (height+2*MAX_VECTOR)*pitch*pixelsize+2048; + + buf1 = new unsigned char[bufsize]; + buf2 = new unsigned char[bufsize]; + work = new unsigned char[bufsize]; + + int xblocks = (width/blockwidth); + int xleft = width % blockwidth; + if (xleft) xblocks++; + int yblocks = (height/blockheight); + int yleft = height % blockheight; + if (yleft) yblocks++; + blockcount=yblocks*xblocks; + blocks=new FrameBlock[blockcount]; + + if (!buf1 || !buf2 || !work || !blocks) { + FreeBuffers(); + return false; + } + int y,x,i; + i=0; + for (y=0;y +INLINE int ZmbvCodec::PossibleBlock(int vx,int vy,FrameBlock * block) { + int ret=0; + P * pold=((P*)oldframe)+block->start+(vy*pitch)+vx; + P * pnew=((P*)newframe)+block->start;; + for (int y=0;ydy;y+=4) { + for (int x=0;xdx;x+=4) { + int test=0-((pold[x]-pnew[x])&0x00ffffff); + ret-=(test>>31); + } + pold+=pitch*4; + pnew+=pitch*4; + } + return ret; +} + +template +INLINE int ZmbvCodec::CompareBlock(int vx,int vy,FrameBlock * block) { + int ret=0; + P * pold=((P*)oldframe)+block->start+(vy*pitch)+vx; + P * pnew=((P*)newframe)+block->start;; + for (int y=0;ydy;y++) { + for (int x=0;xdx;x++) { + int test=0-((pold[x]-pnew[x])&0x00ffffff); + ret-=(test>>31); + } + pold+=pitch; + pnew+=pitch; + } + return ret; +} + +template +INLINE void ZmbvCodec::AddXorBlock(int vx,int vy,FrameBlock * block) { + P * pold=((P*)oldframe)+block->start+(vy*pitch)+vx; + P * pnew=((P*)newframe)+block->start; + for (int y=0;ydy;y++) { + for (int x=0;xdx;x++) { + *((P*)&work[workUsed])=pnew[x] ^ pold[x]; + workUsed+=sizeof(P); + } + pold+=pitch; + pnew+=pitch; + } +} + +template +void ZmbvCodec::AddXorFrame(void) { + int written=0; + int lastvector=0; + signed char * vectors=(signed char*)&work[workUsed]; + /* Align the following xor data on 4 byte boundary*/ + workUsed=(workUsed + blockcount*2 +3) & ~3; + int totalx=0; + int totaly=0; + for (int b=0;b(0,0, block); + int possibles=64; + for (int v=0;v(vx, vy, block) < 4) { + possibles--; +// if (!possibles) Msg("Ran out of possibles, at %d of %d best %d\n",v,VectorCount,bestchange); + int testchange=CompareBlock

(vx,vy, block); + if (testchange(bestvx, bestvy, block); + } + } +} + +bool ZmbvCodec::SetupCompress( int _width, int _height ) { + width = _width; + height = _height; + pitch = _width + 2*MAX_VECTOR; + format = ZMBV_FORMAT_NONE; + if (deflateInit (&zstream, 4) != Z_OK) + return false; + + return true; +} + +bool ZmbvCodec::PrepareCompressFrame(int flags, zmbv_format_t _format, char * pal) +{ + int i; + unsigned char *firstByte; + + if (_format != format) { + if (!SetupBuffers( _format, 16, 16)) + return false; + flags|=1; //Force a keyframe + } + /* replace oldframe with new frame */ + unsigned char *copyFrame = newframe; + newframe = oldframe; + oldframe = copyFrame; + + compressInfo.linesDone = 0; + compressInfo.writeSize = _bufSize; + compressInfo.writeDone = 1; + compressInfo.writeBuf = (unsigned char *)_buf; + /* Set a pointer to the first byte which will contain info about this frame */ + firstByte = compressInfo.writeBuf; + *firstByte = 0; + //Reset the work buffer + workUsed = 0;workPos = 0; + if (flags & 1) { + /* Make a keyframe */ + *firstByte |= Mask_KeyFrame; + KeyframeHeader * header = (KeyframeHeader *)(compressInfo.writeBuf + compressInfo.writeDone); + header->high_version = DBZV_VERSION_HIGH; + header->low_version = DBZV_VERSION_LOW; + header->compression = COMPRESSION_ZLIB; + header->format = format; + header->blockwidth = 16; + header->blockheight = 16; + compressInfo.writeDone += sizeof(KeyframeHeader); + /* Copy the new frame directly over */ + if (palsize) { + if (pal) + memcpy(&palette, pal, sizeof(palette)); + else + memset(&palette,0, sizeof(palette)); + /* keyframes get the full palette */ + for (i=0;i(); + break; + case ZMBV_FORMAT_15BPP: + case ZMBV_FORMAT_16BPP: + AddXorFrame(); + break; + case ZMBV_FORMAT_32BPP: + AddXorFrame(); + break; + } + } + /* Create the actual frame with compression */ + zstream.next_in = (Bytef *)work; + zstream.avail_in = workUsed; + zstream.total_in = 0; + + zstream.next_out = (Bytef *)(compressInfo.writeBuf + compressInfo.writeDone); + zstream.avail_out = compressInfo.writeSize - compressInfo.writeDone; + zstream.total_out = 0; + int res = deflate(&zstream, Z_SYNC_FLUSH); + + *compressedData = _buf; + + return compressInfo.writeDone + zstream.total_out; +} + +void ZmbvCodec::FreeBuffers() +{ + if (blocks) { + delete[] blocks; + blocks= nullptr; + } + if (buf1) { + delete[] buf1; + buf1= nullptr; + } + if (buf2) { + delete[] buf2; + buf2= nullptr; + } + if (work) { + delete[] work; + work= nullptr; + } + if(_buf) { + delete[] _buf; + _buf = nullptr; + } +} + +ZmbvCodec::ZmbvCodec() +{ + CreateVectorTable(); + blocks = nullptr; + buf1 = nullptr; + buf2 = nullptr; + work = nullptr; + memset( &zstream, 0, sizeof(zstream)); +} + +int ZmbvCodec::CompressFrame(bool isKeyFrame, uint8_t *frameData, uint8_t** compressedData) +{ + if(!PrepareCompressFrame(isKeyFrame ? 1 : 0, ZMBV_FORMAT_32BPP, nullptr)) { + return -1; + } + + for(int i = 0; i < height; i++) { + void * rowPointer = frameData + i*width*4; + CompressLines(1, &rowPointer); + } + + return FinishCompressFrame(compressedData); +} + +const char* ZmbvCodec::GetFourCC() +{ + return "ZMBV"; +} \ No newline at end of file diff --git a/Utilities/ZmbvCodec.h b/Utilities/ZmbvCodec.h new file mode 100644 index 00000000..4fcdc8e8 --- /dev/null +++ b/Utilities/ZmbvCodec.h @@ -0,0 +1,115 @@ +// This file is a part of Mesen +// It is a heavily modified version of the zmbv.h/cpp file found in DOSBox's code. + +/* + * Copyright (C) 2002-2011 The DOSBox Team + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#pragma once + +#include "BaseCodec.h" +#include "miniz.h" + +#ifdef _MSC_VER +#define INLINE __forceinline +#else +#define INLINE inline +#endif + +typedef enum { + ZMBV_FORMAT_NONE = 0x00, + ZMBV_FORMAT_1BPP = 0x01, + ZMBV_FORMAT_2BPP = 0x02, + ZMBV_FORMAT_4BPP = 0x03, + ZMBV_FORMAT_8BPP = 0x04, + ZMBV_FORMAT_15BPP = 0x05, + ZMBV_FORMAT_16BPP = 0x06, + ZMBV_FORMAT_24BPP = 0x07, + ZMBV_FORMAT_32BPP = 0x08 +} zmbv_format_t; + +class ZmbvCodec : public BaseCodec +{ +private: + struct FrameBlock { + int start; + int dx,dy; + }; + struct CodecVector { + int x,y; + int slot; + }; + struct KeyframeHeader { + unsigned char high_version; + unsigned char low_version; + unsigned char compression; + unsigned char format; + unsigned char blockwidth,blockheight; + }; + + struct { + int linesDone; + int writeSize; + int writeDone; + unsigned char *writeBuf; + } compressInfo; + + CodecVector VectorTable[512]; + int VectorCount; + + unsigned char *oldframe, *newframe; + unsigned char *buf1, *buf2, *work; + int bufsize; + + int blockcount; + FrameBlock * blocks; + + int workUsed, workPos; + + int palsize; + char palette[256*4]; + int height, width, pitch; + zmbv_format_t format; + int pixelsize; + + uint8_t* _buf; + uint32_t _bufSize; + + z_stream zstream; + + // methods + void FreeBuffers(void); + void CreateVectorTable(void); + bool SetupBuffers(zmbv_format_t format, int blockwidth, int blockheight); + + template void AddXorFrame(void); + template INLINE int PossibleBlock(int vx,int vy,FrameBlock * block); + template INLINE int CompareBlock(int vx,int vy,FrameBlock * block); + template INLINE void AddXorBlock(int vx,int vy,FrameBlock * block); + + int NeededSize(int _width, int _height, zmbv_format_t _format); + + void CompressLines(int lineCount, void *lineData[]); + bool PrepareCompressFrame(int flags, zmbv_format_t _format, char * pal); + int FinishCompressFrame(uint8_t** compressedData); + +public: + ZmbvCodec(); + bool SetupCompress(int _width, int _height) override; + int CompressFrame(bool isKeyFrame, uint8_t *frameData, uint8_t** compressedData) override; + const char* GetFourCC() override; +};