Video: Added "Video Recorder" to record AVI files

This commit is contained in:
Souryo 2016-12-29 21:19:13 -05:00
parent 1c5a44c20e
commit 0f5c7fe78b
35 changed files with 1701 additions and 68 deletions

98
Core/AviRecorder.cpp Normal file
View file

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

37
Core/AviRecorder.h Normal file
View file

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

View file

@ -406,6 +406,7 @@
<ClInclude Include="ArkanoidController.h" />
<ClInclude Include="AutoRomTest.h" />
<ClInclude Include="AutoSaveManager.h" />
<ClInclude Include="AviRecorder.h" />
<ClInclude Include="Ax5705.h" />
<ClInclude Include="Bandai74161_7432.h" />
<ClInclude Include="BandaiFcg.h" />
@ -745,6 +746,7 @@
<ClCompile Include="ArkanoidController.cpp" />
<ClCompile Include="AutoRomTest.cpp" />
<ClCompile Include="AutoSaveManager.cpp" />
<ClCompile Include="AviRecorder.cpp" />
<ClCompile Include="BaseControlDevice.cpp" />
<ClCompile Include="BaseMapper.cpp" />
<ClCompile Include="BisqwitNtscFilter.cpp" />

View file

@ -1108,6 +1108,9 @@
<ClInclude Include="BisqwitNtscFilter.h">
<Filter>VideoDecoder</Filter>
</ClInclude>
<ClInclude Include="AviRecorder.h">
<Filter>Misc</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="stdafx.cpp">
@ -1299,5 +1302,8 @@
<ClCompile Include="BisqwitNtscFilter.cpp">
<Filter>VideoDecoder</Filter>
</ClCompile>
<ClCompile Include="AviRecorder.cpp">
<Filter>Misc</Filter>
</ClCompile>
</ItemGroup>
</Project>

View file

@ -20,6 +20,7 @@ std::unordered_map<string, string> 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<string, string> 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<string, string> 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<string, string> 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<string, string> MessageManager::_jaResources = {
{ "ScreenshotSaved", u8"スクリーンショット" },
{ "SoundRecorder", u8"サウンドレコーダー" },
{ "Test", u8"テスト" },
{ "VideoRecorder", u8"動画レコーダー" },
{ "ApplyingIps", u8"パッチファイルを適用しました: %1" },
{ "CheatApplied", u8"チートコード%1個を有効にしました。" },
@ -189,6 +196,8 @@ std::unordered_map<string, string> 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<string, string> 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<string, string> 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<string, string> 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<string, string> 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<string, string> 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<string, string> 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<string, string> 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<string, string> 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." },

View file

@ -2,6 +2,7 @@
#include "SoundMixer.h"
#include "APU.h"
#include "CPU.h"
#include "VideoDecoder.h"
IAudioDevice* SoundMixer::AudioDevice = nullptr;
unique_ptr<WaveRecorder> 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

View file

@ -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 = _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<AviRecorder> 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 = _aviRecorder;
if(aviRecorder) {
aviRecorder->AddSound(soundBuffer, sampleCount, sampleRate);
}
}
void VideoDecoder::StopRecording()
{
_aviRecorder.reset();
}
bool VideoDecoder::IsRecording()
{
return _aviRecorder != nullptr && _aviRecorder->IsRecording();
}

View file

@ -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<thread> _decodeThread;
shared_ptr<AviRecorder> _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();
};

View file

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

View file

@ -4,6 +4,7 @@
<Message ID="FilterAll">All Files (*.*)|*.*</Message>
<Message ID="FilterMovie">Movie files (*.mmo)|*.mmo|All Files (*.*)|*.*</Message>
<Message ID="FilterWave">Wave files (*.wav)|*.wav|All Files (*.*)|*.*</Message>
<Message ID="FilterAvi">Avi files (*.avi)|*.avi|All Files (*.*)|*.*</Message>
<Message ID="FilterPalette">Palette Files (*.pal)|*.pal|All Files (*.*)|*.*</Message>
<Message ID="FilterRom">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 (*.*)|*.*</Message>
<Message ID="FilterRomIps">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 (*.*)|*.*</Message>

View file

@ -71,6 +71,9 @@
<Control ID="mnuSoundRecorder">Grabar sonido</Control>
<Control ID="mnuWaveRecord">Grabar...</Control>
<Control ID="mnuWaveStop">Detener</Control>
<Control ID="mnuVideoRecorder">Video Recorder</Control>
<Control ID="mnuAviRecord">Grabar...</Control>
<Control ID="mnuAviStop">Detener</Control>
<Control ID="mnuCheats">Trucos</Control>
<Control ID="mnuTests">Tests</Control>
<Control ID="mnuTestRun">Iniciar...</Control>
@ -477,11 +480,19 @@
<Control ID="btnOK">Ok</Control>
<Control ID="lblSearch">Buscar</Control>
</Form>
<Form ID="frmRecordAvi" Title="Video Recorder">
<Control ID="lblAviFile">Save to:</Control>
<Control ID="chkUseCompression">Use ZMBV compression</Control>
<Control ID="btnBrowse">Browse...</Control>
<Control ID="btnOK">OK</Control>
<Control ID="btnCancel">Cancelar</Control>
</Form>
</Forms>
<Messages>
<Message ID="FilterAll">Todos los tipos de archivo (*.*)|*.*</Message>
<Message ID="FilterMovie">Videos (*.mmo)|*.mmo|Todos los archivos (*.*)|*.*</Message>
<Message ID="FilterWave">Archivos wave (*.wav)|*.wav|Todos los archivos (*.*)|*.*</Message>
<Message ID="FilterAvi">Archivos avi (*.avi)|*.avi|Todos los archivos (*.*)|*.*</Message>
<Message ID="FilterPalette">Archivos pal (*.pal)|*.pal|Todos los archivos (*.*)|*.*</Message>
<Message ID="FilterRom">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 (*.*)|*.*</Message>
<Message ID="FilterRomIps">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 (*.*)|*.*</Message>

View file

@ -71,6 +71,9 @@
<Control ID="mnuSoundRecorder">Enregistreur audio</Control>
<Control ID="mnuWaveRecord">Enregistrer...</Control>
<Control ID="mnuWaveStop">Arrêter</Control>
<Control ID="mnuVideoRecorder">Enregistreur vidéo</Control>
<Control ID="mnuAviRecord">Enregistrer...</Control>
<Control ID="mnuAviStop">Arrêter</Control>
<Control ID="mnuCheats">Codes</Control>
<Control ID="mnuTests">Tests</Control>
<Control ID="mnuTestRun">Run...</Control>
@ -490,11 +493,19 @@
<Control ID="btnOK">OK</Control>
<Control ID="btnCancel">Annuler</Control>
</Form>
<Form ID="frmRecordAvi" Title="Enregistreur vidéo">
<Control ID="lblAviFile">Enregistrer sous:</Control>
<Control ID="chkUseCompression">Utiliser la compression ZMBV</Control>
<Control ID="btnBrowse">Parcourir...</Control>
<Control ID="btnOK">OK</Control>
<Control ID="btnCancel">Annuler</Control>
</Form>
</Forms>
<Messages>
<Message ID="FilterAll">Tous les fichiers (*.*)|*.*</Message>
<Message ID="FilterMovie">Films (*.mmo)|*.mmo|Tous les fichiers (*.*)|*.*</Message>
<Message ID="FilterWave">Fichiers wave (*.wav)|*.wav|Tous les fichiers (*.*)|*.*</Message>
<Message ID="FilterAvi">Fichiers avi (*.avi)|*.avi|Tous les fichiers (*.*)|*.*</Message>
<Message ID="FilterPalette">Fichier de palette (*.pal)|*.pal|Tous les fichiers (*.*)|*.*</Message>
<Message ID="FilterRom">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 (*.*)|*.*</Message>
<Message ID="FilterRomIps">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 (*.*)|*.*</Message>

View file

@ -71,6 +71,9 @@
<Control ID="mnuSoundRecorder">サウンドレコーダー</Control>
<Control ID="mnuWaveRecord">録音</Control>
<Control ID="mnuWaveStop">停止</Control>
<Control ID="mnuVideoRecorder">動画レコーダー</Control>
<Control ID="mnuAviRecord">録画</Control>
<Control ID="mnuAviStop">停止</Control>
<Control ID="mnuCheats">チートコード</Control>
<Control ID="mnuTests">テスト</Control>
<Control ID="mnuTestRun">Run...</Control>
@ -472,11 +475,19 @@
<Control ID="btnOK">OK</Control>
<Control ID="btnCancel">キャンセル</Control>
</Form>
<Form ID="frmRecordAvi" Title="動画レコーダー">
<Control ID="lblAviFile">保存先:</Control>
<Control ID="chkUseCompression">ZMBV圧縮を利用する</Control>
<Control ID="btnBrowse">参照...</Control>
<Control ID="btnOK">OK</Control>
<Control ID="btnCancel">キャンセル</Control>
</Form>
</Forms>
<Messages>
<Message ID="FilterAll">すべてのファイル (*.*)|*.*</Message>
<Message ID="FilterMovie">動画 (*.mmo)|*.mmo|すべてのファイル (*.*)|*.*</Message>
<Message ID="FilterWave">WAVファイル (*.wav)|*.wav|すべてのファイル (*.*)|*.*</Message>
<Message ID="FilterAvi">AVIファイル (*.avi)|*.avi|すべてのファイル (*.*)|*.*</Message>
<Message ID="FilterPalette">パレットファイル (*.pal)|*.pal|すべてのファイル (*.*)|*.*</Message>
<Message ID="FilterRom">対応するすべてのファイル (*.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|すべてのファイル (*.*)|*.*</Message>
<Message ID="FilterRomIps">対応するすべてのファイル (*.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|すべてのファイル (*.*)|*.*</Message>

View file

@ -68,6 +68,9 @@
<Control ID="mnuRecordFromStart">Do início do jogo</Control>
<Control ID="mnuRecordFromNow">Do estado atual</Control>
<Control ID="mnuStopMovie">Parar</Control>
<Control ID="mnuVideoRecorder">Video Recorder</Control>
<Control ID="mnuAviRecord">Gravar...</Control>
<Control ID="mnuAviStop">Parar</Control>
<Control ID="mnuSoundRecorder">Gravar som</Control>
<Control ID="mnuWaveRecord">Gravar...</Control>
<Control ID="mnuWaveStop">Parar</Control>
@ -477,11 +480,19 @@
<Control ID="btnOK">Ok</Control>
<Control ID="lblSearch">Buscar</Control>
</Form>
<Form ID="frmRecordAvi" Title="Video Recorder">
<Control ID="lblAviFile">Save to:</Control>
<Control ID="chkUseCompression">Use ZMBV compression</Control>
<Control ID="btnBrowse">Browse...</Control>
<Control ID="btnOK">OK</Control>
<Control ID="btnCancel">Cancelar</Control>
</Form>
</Forms>
<Messages>
<Message ID="FilterAll">Todos os tipos de arquivo (*.*)|*.*</Message>
<Message ID="FilterMovie">Vídeos (*.mmo)|*.mmo|Todos os arquivos (*.*)|*.*</Message>
<Message ID="FilterWave">Arquivos wave (*.wav)|*.wav|Todos os arquivos (*.*)|*.*</Message>
<Message ID="FilterAvi">Arquivos avi (*.avi)|*.avi|Todos os arquivos (*.*)|*.*</Message>
<Message ID="FilterPalette">Arquivos pal (*.pal)|*.pal|Todos os arquivos (*.*)|*.*</Message>
<Message ID="FilterRom">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 (*.*)|*.*</Message>
<Message ID="FilterRomIps">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 (*.*)|*.*</Message>

View file

@ -71,6 +71,9 @@
<Control ID="mnuSoundRecorder">Запись звука</Control>
<Control ID="mnuWaveRecord">Начать</Control>
<Control ID="mnuWaveStop">Остановить</Control>
<Control ID="mnuVideoRecorder">Video Recorder</Control>
<Control ID="mnuAviRecord">Начать</Control>
<Control ID="mnuAviStop">Остановить</Control>
<Control ID="mnuCheats">Читы</Control>
<Control ID="mnuTests">Тесты</Control>
<Control ID="mnuTestRun">Запустить...</Control>
@ -481,11 +484,19 @@
<Control ID="btnOK">ОК</Control>
<Control ID="btnCancel">Отмена</Control>
</Form>
<Form ID="frmRecordAvi" Title="Video Recorder">
<Control ID="lblAviFile">Save to:</Control>
<Control ID="chkUseCompression">Use ZMBV compression</Control>
<Control ID="btnBrowse">Browse...</Control>
<Control ID="btnOK">OK</Control>
<Control ID="btnCancel">Отмена</Control>
</Form>
</Forms>
<Messages>
<Message ID="FilterAll">Все файлы (*.*)|*.*</Message>
<Message ID="FilterMovie">Записи (*.mmo)|*.mmo|All Files (*.*)|*.*</Message>
<Message ID="FilterWave">Wave файлы (*.wav)|*.wav|All Files (*.*)|*.*</Message>
<Message ID="FilterAvi">Avi файлы (*.avi)|*.avi|All Files (*.*)|*.*</Message>
<Message ID="FilterPalette">Файлы палитры (*.pal)|*.pal|All Files (*.*)|*.*</Message>
<Message ID="FilterRom">Все поддерживаемые форматы (*.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 (*.*)|*.*</Message>
<Message ID="FilterRomIps">Все поддерживаемые форматы (*.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 (*.*)|*.*</Message>

View file

@ -71,6 +71,9 @@
<Control ID="mnuSoundRecorder">Запис звуку</Control>
<Control ID="mnuWaveRecord">Почати</Control>
<Control ID="mnuWaveStop">Зупинити</Control>
<Control ID="mnuVideoRecorder">Video Recorder</Control>
<Control ID="mnuAviRecord">Почати</Control>
<Control ID="mnuAviStop">Зупинити</Control>
<Control ID="mnuCheats">Чити</Control>
<Control ID="mnuTests">Тести</Control>
<Control ID="mnuTestRun">Запустити...</Control>
@ -194,7 +197,7 @@
<Control ID="tpgSet2">Контролер #2</Control>
<Control ID="tpgSet3">Контролер #3</Control>
<Control ID="tpgSet4">Контролер #4</Control>
<Control ID="btnSelectPreset">Пресети</Control>
<Control ID="mnuKeyboard">Клавіатура</Control>
<Control ID="mnuWasdLayout">WSAD розкладка</Control>
@ -210,7 +213,7 @@
<Control ID="mnuSnes30Controller">SNES30 геймпад</Control>
<Control ID="mnuSnes30Layout1">Контролер #1</Control>
<Control ID="mnuSnes30Layout2">Контролер #2</Control>
<Control ID="btnClear">Очистити</Control>
<Control ID="btnOK">OK</Control>
<Control ID="btnCancel">Відміна</Control>
@ -254,7 +257,7 @@
<Control ID="trkYFilterLength">Y Filter (Horizontal Blur)</Control>
<Control ID="trkIFilterLength">I Filter (Horizontal Bleed)</Control>
<Control ID="trkQFilterLength">Q Filter (Horizontal Bleed)</Control>
<Control ID="grpScanlines">Scanlines</Control>
<Control ID="trkScanlines">Scanlines</Control>
<Control ID="btnResetPictureSettings">Скидання</Control>
@ -268,7 +271,7 @@
<Control ID="chkDisableSprites">Відключити sprites</Control>
<Control ID="chkForceBackgroundFirstColumn">Force background display in first column</Control>
<Control ID="chkForceSpritesFirstColumn">Force sprite display in first column</Control>
<Control ID="btnOK">OK</Control>
<Control ID="btnCancel">Відміна</Control>
</Form>
@ -474,17 +477,25 @@
</Form>
<Form ID="frmGetKey" Title="Налаштування клавіш...">
<Control ID="lblSetKeyMessage">Натисніть будь-яку клавішу на клавіатурі або контролері.</Control>...">
</Form>
<Form ID="frmPlayerProfile" Title="Профіль">
<Control ID="lblName">Iм'я :</Control>
<Control ID="btnOK">ОК</Control>
<Control ID="btnCancel">Вiдмiна</Control>
</Form>
</Form>
<Form ID="frmPlayerProfile" Title="Профіль">
<Control ID="lblName">Iм'я :</Control>
<Control ID="btnOK">ОК</Control>
<Control ID="btnCancel">Вiдмiна</Control>
</Form>
<Form ID="frmRecordAvi" Title="Video Recorder">
<Control ID="lblAviFile">Save to:</Control>
<Control ID="chkUseCompression">Use ZMBV compression</Control>
<Control ID="btnBrowse">Browse...</Control>
<Control ID="btnOK">OK</Control>
<Control ID="btnCancel">Вiдмiна</Control>
</Form>
</Forms>
<Messages>
<Message ID="FilterAll">Всi файли (*.*)|*.*</Message>
<Message ID="FilterMovie">Записи (*.mmo)|*.mmo|All Files (*.*)|*.*</Message>
<Message ID="FilterWave">Wave файли (*.wav)|*.wav|All Files (*.*)|*.*</Message>
<Message ID="FilterAvi">Avi файли (*.avi)|*.avi|All Files (*.*)|*.*</Message>
<Message ID="FilterPalette">Файли палiтр (*.pal)|*.pal|All Files (*.*)|*.*</Message>
<Message ID="FilterRom">Всі підтримувані формати (*.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 (*.*)|*.*</Message>
<Message ID="FilterRomIps">Всі підтримувані формати (*.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 (*.*)|*.*</Message>

View file

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

View file

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

131
GUI.NET/Forms/frmRecordAvi.Designer.cs generated Normal file
View file

@ -0,0 +1,131 @@
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.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;
}
}

View file

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

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

@ -562,6 +562,12 @@
<Compile Include="Forms\frmLogWindow.Designer.cs">
<DependentUpon>frmLogWindow.cs</DependentUpon>
</Compile>
<Compile Include="Forms\frmRecordAvi.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Forms\frmRecordAvi.Designer.cs">
<DependentUpon>frmRecordAvi.cs</DependentUpon>
</Compile>
<Compile Include="Forms\frmSelectRom.cs">
<SubType>Form</SubType>
</Compile>
@ -773,6 +779,9 @@
<EmbeddedResource Include="Forms\frmMain.resx">
<DependentUpon>frmMain.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Forms\frmRecordAvi.resx">
<DependentUpon>frmRecordAvi.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Forms\frmSelectRom.resx">
<DependentUpon>frmSelectRom.cs</DependentUpon>
</EmbeddedResource>
@ -882,6 +891,7 @@
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="Icon.ico" />
<None Include="Resources\VideoRecorder.png" />
<None Include="Resources\Donate.png" />
<None Include="Resources\Dice.png" />
<None Include="Resources\Warning.png" />

View file

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

View file

@ -470,6 +470,16 @@ namespace Mesen.GUI.Properties {
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap VideoRecorder {
get {
object obj = ResourceManager.GetObject("VideoRecorder", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>

View file

@ -244,4 +244,7 @@
<data name="Donate" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\Donate.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="VideoRecorder" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\VideoRecorder.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
</root>

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 B

View file

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

270
Utilities/AviWriter.cpp Normal file
View file

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

57
Utilities/AviWriter.h Normal file
View file

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

10
Utilities/BaseCodec.h Normal file
View file

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

50
Utilities/RawCodec.h Normal file
View file

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

View file

@ -322,6 +322,7 @@
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="ArchiveReader.h" />
<ClInclude Include="AviWriter.h" />
<ClInclude Include="blip_buf.h" />
<ClInclude Include="CRC32.h" />
<ClInclude Include="FolderUtilities.h" />
@ -337,8 +338,10 @@
<ClInclude Include="nes_ntsc.h" />
<ClInclude Include="nes_ntsc_config.h" />
<ClInclude Include="nes_ntsc_impl.h" />
<ClInclude Include="BaseCodec.h" />
<ClInclude Include="PlatformUtilities.h" />
<ClInclude Include="PNGHelper.h" />
<ClInclude Include="RawCodec.h" />
<ClInclude Include="Scale2x\scale2x.h" />
<ClInclude Include="Scale2x\scale3x.h" />
<ClInclude Include="Scale2x\scalebit.h" />
@ -354,9 +357,11 @@
<ClInclude Include="xBRZ\xbrz.h" />
<ClInclude Include="ZipReader.h" />
<ClInclude Include="ZipWriter.h" />
<ClInclude Include="ZmbvCodec.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="ArchiveReader.cpp" />
<ClCompile Include="AviWriter.cpp" />
<ClCompile Include="blip_buf.cpp" />
<ClCompile Include="CRC32.cpp" />
<ClCompile Include="FolderUtilities.cpp" />
@ -496,6 +501,7 @@
</ClCompile>
<ClCompile Include="ZipReader.cpp" />
<ClCompile Include="ZipWriter.cpp" />
<ClCompile Include="ZmbvCodec.cpp" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">

View file

@ -21,6 +21,9 @@
<Filter Include="KreedSaiEagle">
<UniqueIdentifier>{8e159744-fb91-4e16-aa82-8d8703ba2762}</UniqueIdentifier>
</Filter>
<Filter Include="Avi">
<UniqueIdentifier>{8b0e23bf-1bd9-4cc1-8046-784fd01e8688}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="stdafx.h">
@ -122,6 +125,18 @@
<ClInclude Include="HexUtilities.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="ZmbvCodec.h">
<Filter>Avi</Filter>
</ClInclude>
<ClInclude Include="AviWriter.h">
<Filter>Avi</Filter>
</ClInclude>
<ClInclude Include="BaseCodec.h">
<Filter>Avi</Filter>
</ClInclude>
<ClInclude Include="RawCodec.h">
<Filter>Avi</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="stdafx.cpp">
@ -220,5 +235,11 @@
<ClCompile Include="HexUtilities.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="ZmbvCodec.cpp">
<Filter>Avi</Filter>
</ClCompile>
<ClCompile Include="AviWriter.cpp">
<Filter>Avi</Filter>
</ClCompile>
</ItemGroup>
</Project>

410
Utilities/ZmbvCodec.cpp Normal file
View file

@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#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<yblocks;y++) {
for (x=0;x<xblocks;x++) {
blocks[i].start=((y*blockheight)+MAX_VECTOR)*pitch+
(x*blockwidth)+MAX_VECTOR;
if (xleft && x==(xblocks-1)) {
blocks[i].dx=xleft;
} else {
blocks[i].dx=blockwidth;
}
if (yleft && y==(yblocks-1)) {
blocks[i].dy=yleft;
} else {
blocks[i].dy=blockheight;
}
i++;
}
}
memset(buf1,0,bufsize);
memset(buf2,0,bufsize);
memset(work,0,bufsize);
oldframe=buf1;
newframe=buf2;
format = _format;
_bufSize = NeededSize(width, height, format);
_buf = new uint8_t[_bufSize];
return true;
}
void ZmbvCodec::CreateVectorTable(void) {
int x,y,s;
VectorCount=1;
VectorTable[0].x=VectorTable[0].y=0;
for (s=1;s<=10;s++) {
for (y=0-s;y<=0+s;y++) for (x=0-s;x<=0+s;x++) {
if (abs(x)==s || abs(y)==s) {
VectorTable[VectorCount].x=x;
VectorTable[VectorCount].y=y;
VectorCount++;
}
}
}
}
template<class P>
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;y<block->dy;y+=4) {
for (int x=0;x<block->dx;x+=4) {
int test=0-((pold[x]-pnew[x])&0x00ffffff);
ret-=(test>>31);
}
pold+=pitch*4;
pnew+=pitch*4;
}
return ret;
}
template<class P>
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;y<block->dy;y++) {
for (int x=0;x<block->dx;x++) {
int test=0-((pold[x]-pnew[x])&0x00ffffff);
ret-=(test>>31);
}
pold+=pitch;
pnew+=pitch;
}
return ret;
}
template<class P>
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;y<block->dy;y++) {
for (int x=0;x<block->dx;x++) {
*((P*)&work[workUsed])=pnew[x] ^ pold[x];
workUsed+=sizeof(P);
}
pold+=pitch;
pnew+=pitch;
}
}
template<class P>
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<blockcount;b++) {
FrameBlock * block=&blocks[b];
int bestvx = 0;
int bestvy = 0;
int bestchange=CompareBlock<P>(0,0, block);
int possibles=64;
for (int v=0;v<VectorCount && possibles;v++) {
if (bestchange<4) break;
int vx = VectorTable[v].x;
int vy = VectorTable[v].y;
if (PossibleBlock<P>(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<P>(vx,vy, block);
if (testchange<bestchange) {
bestchange=testchange;
bestvx = vx;
bestvy = vy;
}
}
}
vectors[b*2+0]=(bestvx << 1);
vectors[b*2+1]=(bestvy << 1);
if (bestchange) {
vectors[b*2+0]|=1;
AddXorBlock<P>(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<palsize;i++) {
work[workUsed++] = palette[i*4+0];
work[workUsed++] = palette[i*4+1];
work[workUsed++] = palette[i*4+2];
}
}
/* Restart deflate */
deflateReset(&zstream);
} else {
if (palsize && pal && memcmp(pal, palette, palsize * 4)) {
*firstByte |= Mask_DeltaPalette;
for(i=0;i<palsize;i++) {
work[workUsed++]=palette[i*4+0] ^ pal[i*4+0];
work[workUsed++]=palette[i*4+1] ^ pal[i*4+1];
work[workUsed++]=palette[i*4+2] ^ pal[i*4+2];
}
memcpy(&palette,pal, palsize * 4);
}
}
return true;
}
void ZmbvCodec::CompressLines(int lineCount, void *lineData[])
{
int linePitch = pitch * pixelsize;
int lineWidth = width * pixelsize;
int i = 0;
unsigned char *destStart = newframe + pixelsize*(MAX_VECTOR+(compressInfo.linesDone+MAX_VECTOR)*pitch);
while ( i < lineCount && (compressInfo.linesDone < height)) {
memcpy(destStart, lineData[i], lineWidth );
destStart += linePitch;
i++; compressInfo.linesDone++;
}
}
int ZmbvCodec::FinishCompressFrame(uint8_t** compressedData)
{
unsigned char firstByte = *compressInfo.writeBuf;
if (firstByte & Mask_KeyFrame) {
int i;
/* Add the full frame data */
unsigned char * readFrame = newframe + pixelsize*(MAX_VECTOR+MAX_VECTOR*pitch);
for (i=0;i<height;i++) {
memcpy(&work[workUsed], readFrame, width*pixelsize);
readFrame += pitch*pixelsize;
workUsed += width*pixelsize;
}
} else {
/* Add the delta frame data */
switch (format) {
case ZMBV_FORMAT_8BPP:
AddXorFrame<char>();
break;
case ZMBV_FORMAT_15BPP:
case ZMBV_FORMAT_16BPP:
AddXorFrame<short>();
break;
case ZMBV_FORMAT_32BPP:
AddXorFrame<long>();
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";
}

115
Utilities/ZmbvCodec.h Normal file
View file

@ -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<class P> void AddXorFrame(void);
template<class P> INLINE int PossibleBlock(int vx,int vy,FrameBlock * block);
template<class P> INLINE int CompareBlock(int vx,int vy,FrameBlock * block);
template<class P> 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;
};