Merge pull request #8302 from Robyt3/Video-Refactoring

Video recorder: improve error handling and log messages, fix crashes, refactoring
This commit is contained in:
Dennis Felsing 2024-05-04 14:59:06 +00:00 committed by GitHub
commit 2f22447d44
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 390 additions and 305 deletions

View file

@ -3301,9 +3301,14 @@ void CClient::StartVideo(const char *pFilename, bool WithTimestamp)
Graphics()->WaitForIdle(); Graphics()->WaitForIdle();
// pause the sound device while creating the video instance // pause the sound device while creating the video instance
Sound()->PauseAudioDevice(); Sound()->PauseAudioDevice();
new CVideo((CGraphics_Threaded *)m_pGraphics, Sound(), Storage(), Graphics()->ScreenWidth(), Graphics()->ScreenHeight(), aFilename); new CVideo(Graphics(), Sound(), Storage(), Graphics()->ScreenWidth(), Graphics()->ScreenHeight(), aFilename);
Sound()->UnpauseAudioDevice(); Sound()->UnpauseAudioDevice();
IVideo::Current()->Start(); if(!IVideo::Current()->Start())
{
log_error("videorecorder", "Failed to start recording to '%s'", aFilename);
m_DemoPlayer.Stop("Failed to start video recording. See local console for details.");
return;
}
if(m_DemoPlayer.Info()->m_Info.m_Paused) if(m_DemoPlayer.Info()->m_Info.m_Paused)
{ {
IVideo::Current()->Pause(true); IVideo::Current()->Pause(true);

View file

@ -131,7 +131,9 @@ public:
void StopVoice(CVoiceHandle Voice) override REQUIRES(!m_SoundLock); void StopVoice(CVoiceHandle Voice) override REQUIRES(!m_SoundLock);
bool IsPlaying(int SampleId) override REQUIRES(!m_SoundLock); bool IsPlaying(int SampleId) override REQUIRES(!m_SoundLock);
int MixingRate() const override { return m_MixingRate; }
void Mix(short *pFinalOut, unsigned Frames) override REQUIRES(!m_SoundLock); void Mix(short *pFinalOut, unsigned Frames) override REQUIRES(!m_SoundLock);
void PauseAudioDevice() override; void PauseAudioDevice() override;
void UnpauseAudioDevice() override; void UnpauseAudioDevice() override;
}; };

File diff suppressed because it is too large Load diff

View file

@ -15,39 +15,38 @@ extern "C" {
#include <mutex> #include <mutex>
#include <thread> #include <thread>
#include <vector> #include <vector>
#define ALEN 2048
class CGraphics_Threaded; class IGraphics;
class ISound; class ISound;
class IStorage; class IStorage;
extern CLock g_WriteLock; extern CLock g_WriteLock;
// a wrapper around a single output AVStream // a wrapper around a single output AVStream
struct OutputStream class COutputStream
{ {
AVStream *pSt = nullptr; public:
AVCodecContext *pEnc = nullptr; AVStream *m_pStream = nullptr;
AVCodecContext *m_pCodecContext = nullptr;
/* pts of the next frame that will be generated */ /* pts of the next frame that will be generated */
int64_t NextPts = 0;
int64_t m_SamplesCount = 0; int64_t m_SamplesCount = 0;
int64_t m_SamplesFrameCount = 0; int64_t m_SamplesFrameCount = 0;
std::vector<AVFrame *> m_vpFrames; std::vector<AVFrame *> m_vpFrames;
std::vector<AVFrame *> m_vpTmpFrames; std::vector<AVFrame *> m_vpTmpFrames;
std::vector<struct SwsContext *> m_vpSwsCtxs; std::vector<struct SwsContext *> m_vpSwsContexts;
std::vector<struct SwrContext *> m_vpSwrCtxs; std::vector<struct SwrContext *> m_vpSwrContexts;
}; };
class CVideo : public IVideo class CVideo : public IVideo
{ {
public: public:
CVideo(CGraphics_Threaded *pGraphics, ISound *pSound, IStorage *pStorage, int Width, int Height, const char *pName); CVideo(IGraphics *pGraphics, ISound *pSound, IStorage *pStorage, int Width, int Height, const char *pName);
~CVideo(); ~CVideo();
void Start() override REQUIRES(!g_WriteLock); bool Start() override REQUIRES(!g_WriteLock);
void Stop() override; void Stop() override;
void Pause(bool Pause) override; void Pause(bool Pause) override;
bool IsRecording() override { return m_Recording; } bool IsRecording() override { return m_Recording; }
@ -60,12 +59,12 @@ public:
static IVideo *Current() { return IVideo::ms_pCurrentVideo; } static IVideo *Current() { return IVideo::ms_pCurrentVideo; }
static void Init() { av_log_set_level(AV_LOG_DEBUG); } static void Init();
private: private:
void RunVideoThread(size_t ParentThreadIndex, size_t ThreadIndex) REQUIRES(!g_WriteLock); void RunVideoThread(size_t ParentThreadIndex, size_t ThreadIndex) REQUIRES(!g_WriteLock);
void FillVideoFrame(size_t ThreadIndex) REQUIRES(!g_WriteLock); void FillVideoFrame(size_t ThreadIndex) REQUIRES(!g_WriteLock);
void ReadRGBFromGL(size_t ThreadIndex); void UpdateVideoBufferFromGraphics(size_t ThreadIndex);
void RunAudioThread(size_t ParentThreadIndex, size_t ThreadIndex) REQUIRES(!g_WriteLock); void RunAudioThread(size_t ParentThreadIndex, size_t ThreadIndex) REQUIRES(!g_WriteLock);
void FillAudioFrame(size_t ThreadIndex); void FillAudioFrame(size_t ThreadIndex);
@ -75,27 +74,26 @@ private:
AVFrame *AllocPicture(enum AVPixelFormat PixFmt, int Width, int Height); AVFrame *AllocPicture(enum AVPixelFormat PixFmt, int Width, int Height);
AVFrame *AllocAudioFrame(enum AVSampleFormat SampleFmt, uint64_t ChannelLayout, int SampleRate, int NbSamples); AVFrame *AllocAudioFrame(enum AVSampleFormat SampleFmt, uint64_t ChannelLayout, int SampleRate, int NbSamples);
void WriteFrame(OutputStream *pStream, size_t ThreadIndex) REQUIRES(g_WriteLock); void WriteFrame(COutputStream *pStream, size_t ThreadIndex) REQUIRES(g_WriteLock);
void FinishFrames(OutputStream *pStream); void FinishFrames(COutputStream *pStream);
void CloseStream(OutputStream *pStream); void CloseStream(COutputStream *pStream);
bool AddStream(OutputStream *pStream, AVFormatContext *pOC, const AVCodec **ppCodec, enum AVCodecID CodecId) const; bool AddStream(COutputStream *pStream, AVFormatContext *pFormatContext, const AVCodec **ppCodec, enum AVCodecID CodecId) const;
CGraphics_Threaded *m_pGraphics; IGraphics *m_pGraphics;
IStorage *m_pStorage; IStorage *m_pStorage;
ISound *m_pSound; ISound *m_pSound;
int m_Width; int m_Width;
int m_Height; int m_Height;
char m_aName[256]; char m_aName[256];
//FILE *m_dbgfile; uint64_t m_VideoFrameIndex = 0;
uint64_t m_VSeq = 0; uint64_t m_AudioFrameIndex = 0;
uint64_t m_ASeq = 0;
uint64_t m_Vframe;
int m_FPS; int m_FPS;
bool m_Started; bool m_Started;
bool m_Stopped;
bool m_Recording; bool m_Recording;
size_t m_VideoThreads = 2; size_t m_VideoThreads = 2;
@ -103,8 +101,9 @@ private:
size_t m_AudioThreads = 2; size_t m_AudioThreads = 2;
size_t m_CurAudioThreadIndex = 0; size_t m_CurAudioThreadIndex = 0;
struct SVideoRecorderThread class CVideoRecorderThread
{ {
public:
std::thread m_Thread; std::thread m_Thread;
std::mutex m_Mutex; std::mutex m_Mutex;
std::condition_variable m_Cond; std::condition_variable m_Cond;
@ -118,10 +117,11 @@ private:
uint64_t m_VideoFrameToFill = 0; uint64_t m_VideoFrameToFill = 0;
}; };
std::vector<std::unique_ptr<SVideoRecorderThread>> m_vVideoThreads; std::vector<std::unique_ptr<CVideoRecorderThread>> m_vpVideoThreads;
struct SAudioRecorderThread class CAudioRecorderThread
{ {
public:
std::thread m_Thread; std::thread m_Thread;
std::mutex m_Mutex; std::mutex m_Mutex;
std::condition_variable m_Cond; std::condition_variable m_Cond;
@ -136,22 +136,28 @@ private:
int64_t m_SampleCountStart = 0; int64_t m_SampleCountStart = 0;
}; };
std::vector<std::unique_ptr<SAudioRecorderThread>> m_vAudioThreads; std::vector<std::unique_ptr<CAudioRecorderThread>> m_vpAudioThreads;
std::atomic<int32_t> m_ProcessingVideoFrame; std::atomic<int32_t> m_ProcessingVideoFrame;
std::atomic<int32_t> m_ProcessingAudioFrame; std::atomic<int32_t> m_ProcessingAudioFrame;
bool m_HasAudio; bool m_HasAudio;
struct SVideoSoundBuffer class CVideoBuffer
{ {
int16_t m_aBuffer[ALEN * 2]; public:
std::vector<uint8_t> m_vBuffer;
}; };
std::vector<SVideoSoundBuffer> m_vBuffer; std::vector<CVideoBuffer> m_vVideoBuffers;
std::vector<std::vector<uint8_t>> m_vPixelHelper; class CAudioBuffer
{
public:
int16_t m_aBuffer[4096];
};
std::vector<CAudioBuffer> m_vAudioBuffers;
OutputStream m_VideoStream; COutputStream m_VideoStream;
OutputStream m_AudioStream; COutputStream m_AudioStream;
const AVCodec *m_pVideoCodec; const AVCodec *m_pVideoCodec;
const AVCodec *m_pAudioCodec; const AVCodec *m_pAudioCodec;

View file

@ -91,6 +91,7 @@ public:
virtual int SetPos(int WantedTick) = 0; virtual int SetPos(int WantedTick) = 0;
virtual void Pause() = 0; virtual void Pause() = 0;
virtual void Unpause() = 0; virtual void Unpause() = 0;
virtual const char *ErrorMessage() const = 0;
virtual bool IsPlaying() const = 0; virtual bool IsPlaying() const = 0;
virtual const CInfo *BaseInfo() const = 0; virtual const CInfo *BaseInfo() const = 0;
virtual void GetDemoName(char *pBuffer, size_t BufferSize) const = 0; virtual void GetDemoName(char *pBuffer, size_t BufferSize) const = 0;

View file

@ -167,8 +167,8 @@ public:
const CInfo *BaseInfo() const override { return &m_Info.m_Info; } const CInfo *BaseInfo() const override { return &m_Info.m_Info; }
void GetDemoName(char *pBuffer, size_t BufferSize) const override; void GetDemoName(char *pBuffer, size_t BufferSize) const override;
bool GetDemoInfo(class IStorage *pStorage, class IConsole *pConsole, const char *pFilename, int StorageType, CDemoHeader *pDemoHeader, CTimelineMarkers *pTimelineMarkers, CMapInfo *pMapInfo, IOHANDLE *pFile = nullptr, char *pErrorMessage = nullptr, size_t ErrorMessageSize = 0) const override; bool GetDemoInfo(class IStorage *pStorage, class IConsole *pConsole, const char *pFilename, int StorageType, CDemoHeader *pDemoHeader, CTimelineMarkers *pTimelineMarkers, CMapInfo *pMapInfo, IOHANDLE *pFile = nullptr, char *pErrorMessage = nullptr, size_t ErrorMessageSize = 0) const override;
const char *Filename() { return m_aFilename; } const char *Filename() const { return m_aFilename; }
const char *ErrorMessage() { return m_aErrorMessage; } const char *ErrorMessage() const override { return m_aErrorMessage; }
int Update(bool RealTime = true); int Update(bool RealTime = true);

View file

@ -12,7 +12,7 @@ class IVideo
public: public:
virtual ~IVideo(){}; virtual ~IVideo(){};
virtual void Start() = 0; virtual bool Start() = 0;
virtual void Stop() = 0; virtual void Stop() = 0;
virtual void Pause(bool Pause) = 0; virtual void Pause(bool Pause) = 0;
virtual bool IsRecording() = 0; virtual bool IsRecording() = 0;

View file

@ -92,7 +92,9 @@ public:
virtual void StopVoice(CVoiceHandle Voice) = 0; virtual void StopVoice(CVoiceHandle Voice) = 0;
virtual bool IsPlaying(int SampleId) = 0; virtual bool IsPlaying(int SampleId) = 0;
virtual int MixingRate() const = 0;
virtual void Mix(short *pFinalOut, unsigned Frames) = 0; virtual void Mix(short *pFinalOut, unsigned Frames) = 0;
// useful for thread synchronization // useful for thread synchronization
virtual void PauseAudioDevice() = 0; virtual void PauseAudioDevice() = 0;
virtual void UnpauseAudioDevice() = 0; virtual void UnpauseAudioDevice() = 0;

View file

@ -1962,8 +1962,6 @@ void CMenus::PopupConfirmDemoReplaceVideo()
str_format(aBuf, sizeof(aBuf), "%s/%s.demo", m_aCurrentDemoFolder, m_aCurrentDemoSelectionName); str_format(aBuf, sizeof(aBuf), "%s/%s.demo", m_aCurrentDemoFolder, m_aCurrentDemoSelectionName);
char aVideoName[IO_MAX_PATH_LENGTH]; char aVideoName[IO_MAX_PATH_LENGTH];
str_copy(aVideoName, m_DemoRenderInput.GetString()); str_copy(aVideoName, m_DemoRenderInput.GetString());
if(!str_endswith(aVideoName, ".mp4"))
str_append(aVideoName, ".mp4");
const char *pError = Client()->DemoPlayer_Render(aBuf, m_DemolistStorageType, aVideoName, m_Speed, m_StartPaused); const char *pError = Client()->DemoPlayer_Render(aBuf, m_DemolistStorageType, aVideoName, m_Speed, m_StartPaused);
m_Speed = 4; m_Speed = 4;
m_StartPaused = false; m_StartPaused = false;

View file

@ -1085,7 +1085,14 @@ void CMenus::RenderDemoBrowserList(CUIRect ListView, bool &WasListboxItemActivat
#if defined(CONF_VIDEORECORDER) #if defined(CONF_VIDEORECORDER)
if(!m_DemoRenderInput.IsEmpty()) if(!m_DemoRenderInput.IsEmpty())
{ {
m_Popup = POPUP_RENDER_DONE; if(DemoPlayer()->ErrorMessage()[0] == '\0')
{
m_Popup = POPUP_RENDER_DONE;
}
else
{
m_DemoRenderInput.Clear();
}
} }
#endif #endif