AVI: Added options to record system/input HUDs in AVIs

This commit is contained in:
Sour 2023-02-15 00:28:39 -05:00
parent d255cf3db0
commit 6f99de0135
16 changed files with 233 additions and 140 deletions

View file

@ -25,11 +25,11 @@ void DebugHud::ClearScreen()
_commands.clear();
}
void DebugHud::Draw(uint32_t* argbBuffer, FrameInfo frameInfo, OverscanDimensions overscan, uint32_t frameNumber, bool autoScale)
void DebugHud::Draw(uint32_t* argbBuffer, FrameInfo frameInfo, OverscanDimensions overscan, uint32_t frameNumber, bool autoScale, float forcedScale)
{
auto lock = _commandLock.AcquireSafe();
for(unique_ptr<DrawCommand> &command : _commands) {
command->Draw(argbBuffer, frameInfo, overscan, frameNumber, autoScale);
command->Draw(argbBuffer, frameInfo, overscan, frameNumber, autoScale, forcedScale);
}
_commands.erase(std::remove_if(_commands.begin(), _commands.end(), [](const unique_ptr<DrawCommand>& c) { return c->Expired(); }), _commands.end());
_commandCount = (uint32_t)_commands.size();

View file

@ -18,7 +18,7 @@ public:
bool HasCommands() { return _commandCount > 0; }
void Draw(uint32_t* argbBuffer, FrameInfo frameInfo, OverscanDimensions overscan, uint32_t frameNumber, bool autoScale);
void Draw(uint32_t* argbBuffer, FrameInfo frameInfo, OverscanDimensions overscan, uint32_t frameNumber, bool autoScale, float forcedScale = 0);
void ClearScreen();
void DrawPixel(int x, int y, int color, int frameCount, int startFrame = -1);

View file

@ -106,7 +106,7 @@ public:
{
}
void Draw(uint32_t* argbBuffer, FrameInfo frameInfo, OverscanDimensions &overscan, uint32_t frameNumber, bool autoScale)
void Draw(uint32_t* argbBuffer, FrameInfo frameInfo, OverscanDimensions &overscan, uint32_t frameNumber, bool autoScale, float forcedScale = 0)
{
if(_startFrame < 0) {
//When no start frame was specified, start on the next drawn frame
@ -118,7 +118,10 @@ public:
_frameInfo = frameInfo;
_overscan = overscan;
if(autoScale && !_disableAutoScale) {
if(forcedScale != 0) {
_xScale = forcedScale;
_yScale = forcedScale;
} else if(autoScale && !_disableAutoScale) {
//TODOv2 review
float scale = _frameInfo.Width + _overscan.Left + _overscan.Right > 256 ? (_frameInfo.Width + _overscan.Left + _overscan.Right) / 256.0f : 1;
_yScale = _frameInfo.Height + _overscan.Top + _overscan.Bottom > 240 ? (int)scale : 1;

View file

@ -7,10 +7,9 @@
#include "Shared/Video/DrawStringCommand.h"
#include "Shared/Interfaces/IMessageManager.h"
SystemHud::SystemHud(Emulator* emu, DebugHud* hud)
SystemHud::SystemHud(Emulator* emu)
{
_emu = emu;
_hud = hud;
MessageManager::RegisterMessageManager(this);
}
@ -19,39 +18,37 @@ SystemHud::~SystemHud()
MessageManager::UnregisterMessageManager(this);
}
void SystemHud::Draw(uint32_t width, uint32_t height)
void SystemHud::Draw(DebugHud* hud, uint32_t width, uint32_t height) const
{
_screenWidth = width;
_screenHeight = height;
DrawCounters();
DrawMessages();
DrawCounters(hud, width);
DrawMessages(hud, width, height);
if(_emu->IsRunning()) {
EmuSettings* settings = _emu->GetSettings();
bool showMovieIcons = settings->GetPreferences().ShowMovieIcons;
int xOffset = 0;
if(_emu->IsPaused()) {
DrawPauseIcon();
DrawPauseIcon(hud);
} else if(showMovieIcons && _emu->GetMovieManager()->Playing()) {
DrawPlayIcon();
DrawPlayIcon(hud);
xOffset += 12;
} else if(showMovieIcons && _emu->GetMovieManager()->Recording()) {
DrawRecordIcon();
DrawRecordIcon(hud);
xOffset += 12;
}
bool showTurboRewindIcons = settings->GetPreferences().ShowTurboRewindIcons;
if(!_emu->IsPaused() && showTurboRewindIcons) {
if(settings->CheckFlag(EmulationFlags::Rewind)) {
DrawTurboRewindIcon(true, xOffset);
DrawTurboRewindIcon(hud, true, xOffset);
} else if(settings->CheckFlag(EmulationFlags::Turbo)) {
DrawTurboRewindIcon(false, xOffset);
DrawTurboRewindIcon(hud, false, xOffset);
}
}
}
}
void SystemHud::DrawMessage(MessageInfo &msg, int& lastHeight)
void SystemHud::DrawMessage(DebugHud* hud, MessageInfo &msg, uint32_t screenWidth, uint32_t screenHeight, int& lastHeight) const
{
//Get opacity for fade in/out effect
uint8_t opacity = (uint8_t)(msg.GetOpacity() * 255);
@ -59,54 +56,34 @@ void SystemHud::DrawMessage(MessageInfo &msg, int& lastHeight)
string text = "[" + msg.GetTitle() + "] " + msg.GetMessage();
int maxWidth = _screenWidth - textLeftMargin;
int maxWidth = screenWidth - textLeftMargin;
TextSize size = DrawStringCommand::MeasureString(text, maxWidth);
lastHeight += size.Y;
DrawString(text, textLeftMargin, _screenHeight - lastHeight, opacity);
DrawString(hud, screenWidth, text, textLeftMargin, screenHeight - lastHeight, opacity);
}
void SystemHud::DrawString(string text, int x, int y, uint8_t opacity)
void SystemHud::DrawString(DebugHud* hud, uint32_t screenWidth, string text, int x, int y, uint8_t opacity) const
{
int maxWidth = _screenWidth - x;
int maxWidth = screenWidth - x;
opacity = 255 - opacity;
for(int i = -1; i <= 1; i++) {
for(int j = -1; j <= 1; j++) {
_hud->DrawString(x + i, y + j, text, 0 | (opacity << 24), 0xFF000000, 1, -1, maxWidth);
hud->DrawString(x + i, y + j, text, 0 | (opacity << 24), 0xFF000000, 1, -1, maxWidth);
}
}
_hud->DrawString(x, y, text, 0xFFFFFF | (opacity << 24), 0xFF000000, 1, -1, maxWidth);
hud->DrawString(x, y, text, 0xFFFFFF | (opacity << 24), 0xFF000000, 1, -1, maxWidth);
}
void SystemHud::ShowFpsCounter(int lineNumber)
void SystemHud::ShowFpsCounter(DebugHud* hud, uint32_t screenWidth, int lineNumber) const
{
int yPos = 10 + 10 * lineNumber;
if(_fpsTimer.GetElapsedMS() > 1000) {
//Update fps every sec
uint32_t frameCount = _emu->GetFrameCount();
if(_lastFrameCount > frameCount) {
_currentFPS = 0;
} else {
_currentFPS = (int)(std::round((double)(frameCount - _lastFrameCount) / (_fpsTimer.GetElapsedMS() / 1000)));
_currentRenderedFPS = (int)(std::round((double)(_renderedFrameCount - _lastRenderedFrameCount) / (_fpsTimer.GetElapsedMS() / 1000)));
}
_lastFrameCount = frameCount;
_lastRenderedFrameCount = _renderedFrameCount;
_fpsTimer.Reset();
}
if(_currentFPS > 5000) {
_currentFPS = 0;
}
if(_currentRenderedFPS > 5000) {
_currentRenderedFPS = 0;
}
string fpsString = string("FPS: ") + std::to_string(_currentFPS); // +" / " + std::to_string(_currentRenderedFPS);
uint32_t length = DrawStringCommand::MeasureString(fpsString).X;
DrawString(fpsString, _screenWidth - 8 - length, yPos);
DrawString(hud, screenWidth, fpsString, screenWidth - 8 - length, yPos);
}
void SystemHud::ShowGameTimer(int lineNumber)
void SystemHud::ShowGameTimer(DebugHud* hud, uint32_t screenWidth, int lineNumber) const
{
int yPos = 10 + 10 * lineNumber;
uint32_t frameCount = _emu->GetFrameCount();
@ -122,48 +99,47 @@ void SystemHud::ShowGameTimer(int lineNumber)
string text = ss.str();
uint32_t length = DrawStringCommand::MeasureString(text).X;
DrawString(ss.str(), _screenWidth - 8 - length, yPos);
DrawString(hud, screenWidth, ss.str(), screenWidth - 8 - length, yPos);
}
void SystemHud::ShowFrameCounter(int lineNumber)
void SystemHud::ShowFrameCounter(DebugHud* hud, uint32_t screenWidth, int lineNumber) const
{
int yPos = 10 + 10 * lineNumber;
uint32_t frameCount = _emu->GetFrameCount();
string frameCounter = MessageManager::Localize("Frame") + ": " + std::to_string(frameCount);
uint32_t length = DrawStringCommand::MeasureString(frameCounter).X;
DrawString(frameCounter, _screenWidth - 8 - length, yPos);
DrawString(hud, screenWidth, frameCounter, screenWidth - 8 - length, yPos);
}
void SystemHud::ShowLagCounter(int lineNumber)
void SystemHud::ShowLagCounter(DebugHud* hud, uint32_t screenWidth, int lineNumber) const
{
int yPos = 10 + 10 * lineNumber;
uint32_t count = _emu->GetLagCounter();
string lagCounter = MessageManager::Localize("Lag") + ": " + std::to_string(count);
uint32_t length = DrawStringCommand::MeasureString(lagCounter).X;
DrawString(lagCounter, _screenWidth - 8 - length, yPos);
DrawString(hud, screenWidth, lagCounter, screenWidth - 8 - length, yPos);
}
void SystemHud::DrawCounters()
void SystemHud::DrawCounters(DebugHud* hud, uint32_t screenWidth) const
{
int lineNumber = 0;
PreferencesConfig cfg = _emu->GetSettings()->GetPreferences();
if(_emu->IsRunning()) {
if(cfg.ShowFps) {
ShowFpsCounter(lineNumber++);
ShowFpsCounter(hud, screenWidth, lineNumber++);
}
if(cfg.ShowGameTimer) {
ShowGameTimer(lineNumber++);
ShowGameTimer(hud, screenWidth, lineNumber++);
}
if(cfg.ShowFrameCounter) {
ShowFrameCounter(lineNumber++);
ShowFrameCounter(hud, screenWidth, lineNumber++);
}
if(cfg.ShowLagCounter) {
ShowLagCounter(lineNumber++);
ShowLagCounter(hud, screenWidth, lineNumber++);
}
_renderedFrameCount++;
}
}
@ -173,17 +149,13 @@ void SystemHud::DisplayMessage(string title, string message)
_messages.push_front(std::make_unique<MessageInfo>(title, message, 3000));
}
void SystemHud::DrawMessages()
void SystemHud::DrawMessages(DebugHud* hud, uint32_t screenWidth, uint32_t screenHeight) const
{
auto lock = _msgLock.AcquireSafe();
_messages.remove_if([](unique_ptr<MessageInfo>& msg) { return msg->IsExpired(); });
int counter = 0;
int lastHeight = 3;
for(unique_ptr<MessageInfo>& msg : _messages) {
for(auto& msg : _messages) {
if(counter < 4) {
DrawMessage(*msg.get(), lastHeight);
DrawMessage(hud, *msg.get(), screenWidth, screenHeight, lastHeight);
} else {
break;
}
@ -191,29 +163,29 @@ void SystemHud::DrawMessages()
}
}
void SystemHud::DrawBar(int x, int y, int width, int height)
void SystemHud::DrawBar(DebugHud* hud, int x, int y, int width, int height) const
{
_hud->DrawRectangle(x, y, width, height, 0xFFFFFF, true, 1);
_hud->DrawLine(x, y + 1, x + width, y + 1, 0x4FBECE, 1);
_hud->DrawLine(x+1, y, x+1, y + height, 0x4FBECE, 1);
hud->DrawRectangle(x, y, width, height, 0xFFFFFF, true, 1);
hud->DrawLine(x, y + 1, x + width, y + 1, 0x4FBECE, 1);
hud->DrawLine(x+1, y, x+1, y + height, 0x4FBECE, 1);
_hud->DrawLine(x + width - 1, y, x + width - 1, y + height, 0xCC9E22, 1);
_hud->DrawLine(x, y + height - 1, x + width, y + height - 1, 0xCC9E22, 1);
hud->DrawLine(x + width - 1, y, x + width - 1, y + height, 0xCC9E22, 1);
hud->DrawLine(x, y + height - 1, x + width, y + height - 1, 0xCC9E22, 1);
_hud->DrawLine(x, y, x + width, y, 0x303030, 1);
_hud->DrawLine(x, y, x, y + height, 0x303030, 1);
hud->DrawLine(x, y, x + width, y, 0x303030, 1);
hud->DrawLine(x, y, x, y + height, 0x303030, 1);
_hud->DrawLine(x + width, y, x + width, y + height, 0x303030, 1);
_hud->DrawLine(x, y + height, x + width, y + height, 0x303030, 1);
hud->DrawLine(x + width, y, x + width, y + height, 0x303030, 1);
hud->DrawLine(x, y + height, x + width, y + height, 0x303030, 1);
}
void SystemHud::DrawPauseIcon()
void SystemHud::DrawPauseIcon(DebugHud* hud) const
{
DrawBar(10, 7, 5, 12);
DrawBar(17, 7, 5, 12);
DrawBar(hud, 10, 7, 5, 12);
DrawBar(hud, 17, 7, 5, 12);
}
void SystemHud::DrawPlayIcon()
void SystemHud::DrawPlayIcon(DebugHud* hud) const
{
int x = 12;
int y = 12;
@ -225,37 +197,37 @@ void SystemHud::DrawPlayIcon()
for(int i = 0; i < width; i++) {
int left = x + i * 2;
int top = y + i;
_hud->DrawLine(left, top - 1, left, y + height - i + 1, borderColor, 1);
_hud->DrawLine(left + 1, top - 1, left + 1, y + height - i + 1, borderColor, 1);
hud->DrawLine(left, top - 1, left, y + height - i + 1, borderColor, 1);
hud->DrawLine(left + 1, top - 1, left + 1, y + height - i + 1, borderColor, 1);
if(i > 0) {
_hud->DrawLine(left, top, left, y + height - i, color, 1);
hud->DrawLine(left, top, left, y + height - i, color, 1);
}
if(i < width - 1) {
_hud->DrawLine(left + 1, top, left + 1, y + height - i, color, 1);
hud->DrawLine(left + 1, top, left + 1, y + height - i, color, 1);
}
}
}
void SystemHud::DrawRecordIcon()
void SystemHud::DrawRecordIcon(DebugHud* hud) const
{
int x = 12;
int y = 11;
int borderColor = 0x00000;
int color = 0xFF0000;
_hud->DrawRectangle(x + 3, y, 4, 10, borderColor, true, 1);
_hud->DrawRectangle(x, y + 3, 10, 4, borderColor, true, 1);
_hud->DrawRectangle(x + 2, y + 1, 6, 8, borderColor, true, 1);
_hud->DrawRectangle(x + 1, y + 2, 8, 6, borderColor, true, 1);
hud->DrawRectangle(x + 3, y, 4, 10, borderColor, true, 1);
hud->DrawRectangle(x, y + 3, 10, 4, borderColor, true, 1);
hud->DrawRectangle(x + 2, y + 1, 6, 8, borderColor, true, 1);
hud->DrawRectangle(x + 1, y + 2, 8, 6, borderColor, true, 1);
_hud->DrawRectangle(x + 3, y + 1, 4, 8, color, true, 1);
_hud->DrawRectangle(x + 2, y + 2, 6, 6, color, true, 1);
_hud->DrawRectangle(x + 1, y + 3, 8, 4, color, true, 1);
hud->DrawRectangle(x + 3, y + 1, 4, 8, color, true, 1);
hud->DrawRectangle(x + 2, y + 2, 6, 6, color, true, 1);
hud->DrawRectangle(x + 1, y + 3, 8, 4, color, true, 1);
}
void SystemHud::DrawTurboRewindIcon(bool forRewind, int xOffset)
void SystemHud::DrawTurboRewindIcon(DebugHud* hud, bool forRewind, int xOffset) const
{
int x = 12 + xOffset;
int y = 12;
@ -285,18 +257,51 @@ void SystemHud::DrawTurboRewindIcon(bool forRewind, int xOffset)
for(int i = 0; i < width; i++) {
int left = x + i*sign * 2;
int top = y + i * 2;
_hud->DrawLine(left, top - 2, left, y + height - i*2 + 2, borderColor, 1);
_hud->DrawLine(left + 1 * sign, top - 1, left + 1 * sign, y + height - i*2 + 1, borderColor, 1);
hud->DrawLine(left, top - 2, left, y + height - i*2 + 2, borderColor, 1);
hud->DrawLine(left + 1 * sign, top - 1, left + 1 * sign, y + height - i*2 + 1, borderColor, 1);
if(i > 0) {
_hud->DrawLine(left, top - 1, left, y + height + 1 - i*2, color, 1);
hud->DrawLine(left, top - 1, left, y + height + 1 - i*2, color, 1);
}
if(i < width - 1) {
_hud->DrawLine(left + 1 * sign, top, left + 1 * sign, y + height - i*2, color, 1);
hud->DrawLine(left + 1 * sign, top, left + 1 * sign, y + height - i*2, color, 1);
}
}
x += 6;
}
}
void SystemHud::UpdateHud()
{
{
auto lock = _msgLock.AcquireSafe();
_messages.remove_if([](unique_ptr<MessageInfo>& msg) { return msg->IsExpired(); });
}
if(_emu->IsRunning()) {
if(_fpsTimer.GetElapsedMS() > 1000) {
//Update fps every sec
uint32_t frameCount = _emu->GetFrameCount();
if(_lastFrameCount > frameCount) {
_currentFPS = 0;
} else {
_currentFPS = (int)(std::round((double)(frameCount - _lastFrameCount) / (_fpsTimer.GetElapsedMS() / 1000)));
_currentRenderedFPS = (int)(std::round((double)(_renderedFrameCount - _lastRenderedFrameCount) / (_fpsTimer.GetElapsedMS() / 1000)));
}
_lastFrameCount = frameCount;
_lastRenderedFrameCount = _renderedFrameCount;
_fpsTimer.Reset();
}
if(_currentFPS > 5000) {
_currentFPS = 0;
}
if(_currentRenderedFPS > 5000) {
_currentRenderedFPS = 0;
}
_renderedFrameCount++;
}
}

View file

@ -14,7 +14,6 @@ class SystemHud final : public IMessageManager
{
private:
Emulator* _emu = nullptr;
DebugHud* _hud = nullptr;
SimpleLock _msgLock;
list<unique_ptr<MessageInfo>> _messages;
@ -27,31 +26,29 @@ private:
uint32_t _currentRenderedFPS = 0;
uint32_t _renderedFrameCount = 0;
uint32_t _screenWidth = 0;
uint32_t _screenHeight = 0;
void DrawMessages();
void DrawBar(int x, int y, int width, int height);
void DrawPauseIcon();
void DrawPlayIcon();
void DrawRecordIcon();
void DrawTurboRewindIcon(bool forRewind, int xOffset);
void DrawMessage(MessageInfo& msg, int& lastHeight);
void DrawString(string msg, int x, int y, uint8_t opacity = 255);
void DrawMessages(DebugHud* hud, uint32_t screenWidth, uint32_t screenHeight) const;
void DrawBar(DebugHud* hud, int x, int y, int width, int height) const;
void DrawPauseIcon(DebugHud* hud) const;
void DrawPlayIcon(DebugHud* hud) const;
void DrawRecordIcon(DebugHud* hud) const;
void DrawTurboRewindIcon(DebugHud* hud, bool forRewind, int xOffset) const;
void DrawMessage(DebugHud* hud, MessageInfo& msg, uint32_t screenWidth, uint32_t screenHeight, int& lastHeight) const;
void DrawString(DebugHud* hud, uint32_t screenWidth, string msg, int x, int y, uint8_t opacity = 255) const;
void DisplayMessage(string title, string message) override;
void ShowFpsCounter(int lineNumber);
void ShowFrameCounter(int lineNumber);
void ShowLagCounter(int lineNumber);
void ShowGameTimer(int lineNumber);
void ShowFpsCounter(DebugHud* hud, uint32_t screenWidth, int lineNumber) const;
void ShowFrameCounter(DebugHud* hud, uint32_t screenWidth, int lineNumber) const;
void ShowLagCounter(DebugHud* hud, uint32_t screenWidth, int lineNumber) const;
void ShowGameTimer(DebugHud* hud, uint32_t screenWidth, int lineNumber) const;
void DrawCounters();
void DrawCounters(DebugHud* hud, uint32_t screenWidth) const;
public:
SystemHud(Emulator* emu, DebugHud* hud);
SystemHud(Emulator* emu);
~SystemHud();
void Draw(uint32_t width, uint32_t height);
void Draw(DebugHud* hud, uint32_t width, uint32_t height) const;
void UpdateHud();
};

View file

@ -18,7 +18,7 @@ VideoRenderer::VideoRenderer(Emulator* emu)
_stopFlag = false;
_rendererHud.reset(new DebugHud());
_systemHud.reset(new SystemHud(_emu, _rendererHud.get()));
_systemHud.reset(new SystemHud(_emu));
_inputHud.reset(new InputHud(emu, _rendererHud.get()));
}
@ -90,7 +90,10 @@ void VideoRenderer::RenderThread()
_emuHudSurface.Clear();
_inputHud->DrawControllers(size, frame.InputData);
_systemHud->Draw(size.Width, size.Height);
{
auto lock = _hudLock.AcquireSafe();
_systemHud->Draw(_rendererHud.get(), size.Width, size.Height);
}
_rendererHud->Draw(_emuHudSurface.Buffer, size, {}, 0, false);
DrawScriptHud(frame);
@ -133,13 +136,13 @@ std::pair<FrameInfo, OverscanDimensions> VideoRenderer::GetScriptHudSize()
void VideoRenderer::UpdateFrame(RenderedFrame& frame)
{
shared_ptr<IVideoRecorder> recorder = _recorder.lock();
if(recorder) {
if(!recorder->AddFrame(frame.FrameBuffer, frame.Width, frame.Height, _emu->GetFps())) {
StopRecording();
}
{
auto lock = _hudLock.AcquireSafe();
_systemHud->UpdateHud();
}
ProcessAviRecording(frame);
{
auto lock = _frameLock.AcquireSafe();
_lastFrame = frame;
@ -172,15 +175,58 @@ void VideoRenderer::UnregisterRenderingDevice(IRenderingDevice *renderer)
}
}
void VideoRenderer::StartRecording(string filename, VideoCodec codec, uint32_t compressionLevel)
void VideoRenderer::ProcessAviRecording(RenderedFrame& frame)
{
shared_ptr<IVideoRecorder> recorder = _recorder.lock();
if(recorder) {
if(_recorderOptions.RecordInputHud || _recorderOptions.RecordSystemHud) {
//Calculate the scale needed for the HUD elements
FrameInfo originalSize = _emu->GetVideoDecoder()->GetBaseFrameInfo(true);
double scale = (double)frame.Height / originalSize.Height;
FrameInfo scaledFrameSize = { (uint32_t)(frame.Width / scale), (uint32_t)(frame.Height / scale) };
//Update the surface to match the frame's size
_aviRecorderSurface.UpdateSize(frame.Width, frame.Height);
//Copy the game screen
memcpy(_aviRecorderSurface.Buffer, frame.FrameBuffer, frame.Width * frame.Height * sizeof(uint32_t));
//Draw the system/input HUDs
DebugHud hud;
InputHud inputHud(_emu, &hud);
if(_recorderOptions.RecordSystemHud) {
_systemHud->Draw(&hud, scaledFrameSize.Width, scaledFrameSize.Height);
}
if(_recorderOptions.RecordInputHud) {
inputHud.DrawControllers(scaledFrameSize, frame.InputData);
}
FrameInfo frameSize = { frame.Width, frame.Height };
hud.Draw((uint32_t*)_aviRecorderSurface.Buffer, frameSize, {}, frame.FrameNumber, false, scale);
//Record the final result
if(!recorder->AddFrame(_aviRecorderSurface.Buffer, frame.Width, frame.Height, _emu->GetFps())) {
StopRecording();
}
} else {
//Only record the game screen
if(!recorder->AddFrame(frame.FrameBuffer, frame.Width, frame.Height, _emu->GetFps())) {
StopRecording();
}
}
}
}
void VideoRenderer::StartRecording(string filename, RecordAviOptions options)
{
_recorderOptions = options;
FrameInfo frameInfo = _emu->GetVideoDecoder()->GetFrameInfo();
shared_ptr<IVideoRecorder> recorder;
if(codec == VideoCodec::GIF) {
if(options.Codec == VideoCodec::GIF) {
recorder.reset(new GifRecorder());
} else {
recorder.reset(new AviRecorder(codec, compressionLevel));
recorder.reset(new AviRecorder(options.Codec, options.CompressionLevel));
}
if(recorder->StartRecording(filename, frameInfo.Width, frameInfo.Height, 4, _emu->GetSettings()->GetAudioConfig().SampleRate, _emu->GetFps())) {
@ -205,6 +251,7 @@ void VideoRenderer::StopRecording()
if(recorder) {
MessageManager::DisplayMessage("VideoRecorder", "VideoRecorderStopped", recorder->GetOutputFile());
}
_aviRecorderSurface.UpdateSize(0, 0);
_recorder.reset();
}

View file

@ -17,6 +17,14 @@ class InputHud;
class IVideoRecorder;
enum class VideoCodec;
struct RecordAviOptions
{
VideoCodec Codec;
uint32_t CompressionLevel;
bool RecordSystemHud;
bool RecordInputHud;
};
class VideoRenderer
{
private:
@ -34,7 +42,11 @@ private:
unique_ptr<DebugHud> _rendererHud;
unique_ptr<SystemHud> _systemHud;
unique_ptr<InputHud> _inputHud;
SimpleLock _hudLock;
RenderSurfaceInfo _aviRecorderSurface = {};
RecordAviOptions _recorderOptions = {};
RenderSurfaceInfo _emuHudSurface = {};
RenderSurfaceInfo _scriptHudSurface = {};
bool _needScriptHudClear = false;
@ -48,6 +60,8 @@ private:
void RenderThread();
void DrawScriptHud(RenderedFrame& frame);
void ProcessAviRecording(RenderedFrame& frame);
public:
VideoRenderer(Emulator* emu);
@ -67,7 +81,7 @@ public:
void RegisterRenderingDevice(IRenderingDevice *renderer);
void UnregisterRenderingDevice(IRenderingDevice *renderer);
void StartRecording(string filename, VideoCodec codec, uint32_t compressionLevel);
void StartRecording(string filename, RecordAviOptions options);
void AddRecordingSound(int16_t* soundBuffer, uint32_t sampleCount, uint32_t sampleRate);
void StopRecording();
bool IsRecording();

View file

@ -5,11 +5,10 @@
#include "Core/Shared/Movies/MovieManager.h"
extern unique_ptr<Emulator> _emu;
enum class VideoCodec;
extern "C"
{
DllExport void __stdcall AviRecord(char* filename, VideoCodec codec, uint32_t compressionLevel) { _emu->GetVideoRenderer()->StartRecording(filename, codec, compressionLevel); }
DllExport void __stdcall AviRecord(char* filename, RecordAviOptions options) { _emu->GetVideoRenderer()->StartRecording(filename, options); }
DllExport void __stdcall AviStop() { _emu->GetVideoRenderer()->StopRecording(); }
DllExport bool __stdcall AviIsRecording() { return _emu->GetVideoRenderer()->IsRecording(); }

View file

@ -11,6 +11,8 @@ namespace Mesen.Config
{
[Reactive] public VideoCodec Codec { get; set; } = VideoCodec.CSCD;
[Reactive] public UInt32 CompressionLevel { get; set; } = 6;
[Reactive] public bool RecordSystemHud { get; set; } = false;
[Reactive] public bool RecordInputHud { get; set; } = false;
}
public enum VideoCodec

View file

@ -12,7 +12,7 @@ namespace Mesen.Interop
{
private const string DllPath = EmuApi.DllName;
[DllImport(DllPath)] public static extern void AviRecord([MarshalAs(UnmanagedType.LPUTF8Str)]string filename, VideoCodec codec, UInt32 compressionLevel);
[DllImport(DllPath)] public static extern void AviRecord([MarshalAs(UnmanagedType.LPUTF8Str)]string filename, RecordAviOptions options);
[DllImport(DllPath)] public static extern void AviStop();
[DllImport(DllPath)] [return: MarshalAs(UnmanagedType.I1)] public static extern bool AviIsRecording();
@ -68,4 +68,13 @@ namespace Mesen.Interop
public RecordMovieFrom RecordFrom;
}
public struct RecordAviOptions
{
public VideoCodec Codec;
public UInt32 CompressionLevel;
[MarshalAs(UnmanagedType.I1)] public bool RecordSystemHud;
[MarshalAs(UnmanagedType.I1)] public bool RecordInputHud;
};
}

View file

@ -640,6 +640,10 @@
<Control ID="lblCompressionLevel">Compression Level:</Control>
<Control ID="lblLowCompression">low&#13;(fast)</Control>
<Control ID="lblHighCompression">high&#13;(slow)</Control>
<Control ID="lblRecordSystemHud">Record system HUD (game timer, on-screen messages, etc.)</Control>
<Control ID="lblRecordInputHud">Record input HUD</Control>
<Control ID="btnBrowse">Browse...</Control>
<Control ID="btnOK">OK</Control>
<Control ID="btnCancel">Cancel</Control>

View file

@ -168,7 +168,12 @@ namespace Mesen.Utilities
RecordApi.AviStop();
} else {
string filename = GetOutputFilename(ConfigManager.AviFolder, ConfigManager.Config.VideoRecord.Codec == VideoCodec.GIF ? ".gif" : ".avi");
RecordApi.AviRecord(filename, ConfigManager.Config.VideoRecord.Codec, ConfigManager.Config.VideoRecord.CompressionLevel);
RecordApi.AviRecord(filename, new RecordAviOptions() {
Codec = ConfigManager.Config.VideoRecord.Codec,
CompressionLevel = ConfigManager.Config.VideoRecord.CompressionLevel,
RecordSystemHud = ConfigManager.Config.VideoRecord.RecordSystemHud,
RecordInputHud = ConfigManager.Config.VideoRecord.RecordInputHud
});
}
}

View file

@ -8,9 +8,9 @@
xmlns:vm="using:Mesen.ViewModels"
xmlns:l="using:Mesen.Localization"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="500" d:DesignHeight="130"
mc:Ignorable="d"
x:Class="Mesen.Windows.VideoRecordWindow"
Width="500" Height="130"
Width="500" Height="170"
x:DataType="vm:VideoRecordConfigViewModel"
Title="{l:Translate wndTitle}"
>
@ -20,7 +20,7 @@
<Button Width="70" HorizontalContentAlignment="Center" IsCancel="True" Click="Cancel_OnClick" Content="{l:Translate btnCancel}" />
</StackPanel>
<Grid ColumnDefinitions="Auto,1*,Auto" RowDefinitions="Auto,Auto,Auto">
<Grid ColumnDefinitions="Auto,1*,Auto" RowDefinitions="Auto,Auto,Auto,Auto,Auto">
<TextBlock Text="{l:Translate lblAviFile}" />
<TextBox Grid.Column="1" IsReadOnly="True" Text="{CompiledBinding SavePath}" />
<Button Grid.Column="2" Content="{l:Translate btnBrowse}" Click="OnBrowseClick" />
@ -46,6 +46,9 @@
/>
<TextBlock Grid.Column="2" Text="{l:Translate lblHighCompression}" />
</Grid>
<CheckBox Grid.Row="3" Grid.ColumnSpan="3" Content="{l:Translate lblRecordSystemHud}" IsChecked="{CompiledBinding Config.RecordSystemHud}" />
<CheckBox Grid.Row="4" Grid.ColumnSpan="3" Content="{l:Translate lblRecordInputHud}" IsChecked="{CompiledBinding Config.RecordInputHud}" />
</Grid>
</DockPanel>
</Window>

View file

@ -45,7 +45,12 @@ namespace Mesen.Windows
VideoRecordConfigViewModel model = (VideoRecordConfigViewModel)DataContext!;
model.SaveConfig();
RecordApi.AviRecord(model.SavePath, model.Config.Codec, model.Config.CompressionLevel);
RecordApi.AviRecord(model.SavePath, new RecordAviOptions() {
Codec = model.Config.Codec,
CompressionLevel = model.Config.CompressionLevel,
RecordSystemHud = model.Config.RecordSystemHud,
RecordInputHud = model.Config.RecordInputHud
});
}
private void Cancel_OnClick(object sender, RoutedEventArgs e)

View file

@ -16,14 +16,14 @@ void Timer::Reset()
_start = high_resolution_clock::now();
}
double Timer::GetElapsedMS()
double Timer::GetElapsedMS() const
{
high_resolution_clock::time_point end = high_resolution_clock::now();
duration<double> span = duration_cast<duration<double>>(end - _start);
return span.count() * 1000.0;
}
void Timer::WaitUntil(double targetMillisecond)
void Timer::WaitUntil(double targetMillisecond) const
{
if(targetMillisecond > 0) {
double elapsedTime = GetElapsedMS();

View file

@ -11,6 +11,6 @@ class Timer
public:
Timer();
void Reset();
double GetElapsedMS();
void WaitUntil(double targetMillisecond);
double GetElapsedMS() const;
void WaitUntil(double targetMillisecond) const;
};