mirror of
https://github.com/ddnet/ddnet.git
synced 2024-11-10 01:58:19 +00:00
initial work on a video recorder
This commit is contained in:
parent
ef32fc4bed
commit
c5c171a3cb
|
@ -30,6 +30,7 @@
|
|||
#include <engine/shared/config.h>
|
||||
#include <base/tl/threading.h>
|
||||
|
||||
#include "video.h"
|
||||
#include "graphics_threaded.h"
|
||||
#include "backend_sdl.h"
|
||||
|
||||
|
@ -2122,6 +2123,9 @@ CCommandProcessorFragment_SDL::CCommandProcessorFragment_SDL()
|
|||
|
||||
bool CCommandProcessorFragment_SDL::RunCommand(const CCommandBuffer::SCommand *pBaseCommand)
|
||||
{
|
||||
if (CVideo::Current())
|
||||
CVideo::Current()->nextFrame();
|
||||
|
||||
switch(pBaseCommand->m_Cmd)
|
||||
{
|
||||
case CCommandBuffer::CMD_SWAP: Cmd_Swap(static_cast<const CCommandBuffer::SCommand_Swap *>(pBaseCommand)); break;
|
||||
|
|
|
@ -69,6 +69,8 @@
|
|||
#include "updater.h"
|
||||
#include "client.h"
|
||||
|
||||
#include "video.h"
|
||||
|
||||
#include <zlib.h>
|
||||
|
||||
#include "SDL.h"
|
||||
|
@ -267,6 +269,8 @@ CClient::CClient() : m_DemoPlayer(&m_SnapshotDelta)
|
|||
for (int i = 0; i < RECORDER_MAX; i++)
|
||||
m_DemoRecorder[i] = CDemoRecorder(&m_SnapshotDelta);
|
||||
|
||||
m_pVideo = 0;
|
||||
|
||||
m_pEditor = 0;
|
||||
m_pInput = 0;
|
||||
m_pGraphics = 0;
|
||||
|
@ -3128,6 +3132,13 @@ void CClient::Run()
|
|||
m_LocalTime = (time_get()-m_LocalStartTime)/(float)time_freq();
|
||||
}
|
||||
|
||||
if (m_pVideo)
|
||||
{
|
||||
m_pVideo->stop();
|
||||
delete m_pVideo;
|
||||
m_pVideo = 0;
|
||||
}
|
||||
|
||||
#if defined(CONF_FAMILY_UNIX)
|
||||
m_Fifo.Shutdown();
|
||||
#endif
|
||||
|
@ -3274,6 +3285,29 @@ void CClient::Con_Screenshot(IConsole::IResult *pResult, void *pUserData)
|
|||
pSelf->Graphics()->TakeScreenshot(0);
|
||||
}
|
||||
|
||||
void CClient::Con_StartVideo(IConsole::IResult *pResult, void *pUserData)
|
||||
{
|
||||
CClient *pSelf = (CClient *)pUserData;
|
||||
|
||||
if (!pSelf->m_pVideo)
|
||||
{
|
||||
pSelf->m_pVideo = new CVideo(pSelf->Storage(), pSelf->m_pConsole, pSelf->Graphics()->ScreenWidth(), pSelf->Graphics()->ScreenHeight());
|
||||
pSelf->m_pVideo->start();
|
||||
}
|
||||
}
|
||||
|
||||
void CClient::Con_StopVideo(IConsole::IResult *pResult, void *pUserData)
|
||||
{
|
||||
CClient *pSelf = (CClient *)pUserData;
|
||||
|
||||
if (pSelf->m_pVideo)
|
||||
{
|
||||
pSelf->m_pVideo->stop();
|
||||
delete pSelf->m_pVideo;
|
||||
pSelf->m_pVideo = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void CClient::Con_Rcon(IConsole::IResult *pResult, void *pUserData)
|
||||
{
|
||||
CClient *pSelf = (CClient *)pUserData;
|
||||
|
@ -3750,6 +3784,8 @@ void CClient::RegisterCommands()
|
|||
m_pConsole->Register("disconnect", "", CFGFLAG_CLIENT, Con_Disconnect, this, "Disconnect from the server");
|
||||
m_pConsole->Register("ping", "", CFGFLAG_CLIENT, Con_Ping, this, "Ping the current server");
|
||||
m_pConsole->Register("screenshot", "", CFGFLAG_CLIENT, Con_Screenshot, this, "Take a screenshot");
|
||||
m_pConsole->Register("start_video", "", CFGFLAG_CLIENT, Con_StartVideo, this, "Start recording a video");
|
||||
m_pConsole->Register("stop_video", "", CFGFLAG_CLIENT, Con_StopVideo, this, "Stop recording a video");
|
||||
m_pConsole->Register("rcon", "r[rcon-command]", CFGFLAG_CLIENT, Con_Rcon, this, "Send specified command to rcon");
|
||||
m_pConsole->Register("rcon_auth", "s[password]", CFGFLAG_CLIENT, Con_RconAuth, this, "Authenticate to rcon");
|
||||
m_pConsole->Register("rcon_login", "s[username] r[password]", CFGFLAG_CLIENT, Con_RconLogin, this, "Authenticate to rcon with a username");
|
||||
|
|
|
@ -93,6 +93,8 @@ class CClient : public IClient, public CDemoPlayer::IListener
|
|||
class CFriends m_Friends;
|
||||
class CFriends m_Foes;
|
||||
|
||||
class CVideo* m_pVideo;
|
||||
|
||||
char m_aServerAddressStr[256];
|
||||
|
||||
unsigned m_SnapshotParts[2];
|
||||
|
@ -238,6 +240,8 @@ public:
|
|||
IStorage *Storage() { return m_pStorage; }
|
||||
IUpdater *Updater() { return m_pUpdater; }
|
||||
|
||||
class CVideo* Video() { return m_pVideo; }
|
||||
|
||||
CClient();
|
||||
|
||||
// ----- send functions -----
|
||||
|
@ -359,6 +363,8 @@ public:
|
|||
static void Con_Minimize(IConsole::IResult *pResult, void *pUserData);
|
||||
static void Con_Ping(IConsole::IResult *pResult, void *pUserData);
|
||||
static void Con_Screenshot(IConsole::IResult *pResult, void *pUserData);
|
||||
static void Con_StartVideo(IConsole::IResult *pResult, void *pUserData);
|
||||
static void Con_StopVideo(IConsole::IResult *pResult, void *pUserData);
|
||||
static void Con_Rcon(IConsole::IResult *pResult, void *pUserData);
|
||||
static void Con_RconAuth(IConsole::IResult *pResult, void *pUserData);
|
||||
static void Con_RconLogin(IConsole::IResult *pResult, void *pUserData);
|
||||
|
|
223
src/engine/client/video.cpp
Normal file
223
src/engine/client/video.cpp
Normal file
|
@ -0,0 +1,223 @@
|
|||
#include <engine/storage.h>
|
||||
#include <engine/console.h>
|
||||
|
||||
#include "video.h"
|
||||
|
||||
CVideo* CVideo::ms_pCurrentVideo = 0;
|
||||
|
||||
CVideo::CVideo(class IStorage* pStorage, class IConsole *pConsole, int width, int height) :
|
||||
m_pStorage(pStorage),
|
||||
m_pConsole(pConsole)
|
||||
{
|
||||
m_pPixels = 0;
|
||||
m_nframes = 0;
|
||||
|
||||
m_pContext = 0;
|
||||
m_pSws_context = 0;
|
||||
m_pRGB = 0;
|
||||
|
||||
m_Width = width;
|
||||
m_Height = height;
|
||||
|
||||
m_Recording = false;
|
||||
m_ProcessingFrame = false;
|
||||
|
||||
ms_pCurrentVideo = this;
|
||||
}
|
||||
|
||||
CVideo::~CVideo()
|
||||
{
|
||||
ms_pCurrentVideo = 0;
|
||||
}
|
||||
|
||||
void CVideo::start()
|
||||
{
|
||||
ffmpeg_encoder_start(AV_CODEC_ID_H264, 10, m_Width, m_Height);
|
||||
m_Recording = true;
|
||||
}
|
||||
|
||||
void CVideo::stop()
|
||||
{
|
||||
m_Recording = false;
|
||||
ffmpeg_encoder_finish();
|
||||
if (m_pRGB)
|
||||
free(m_pRGB);
|
||||
|
||||
if (m_pPixels)
|
||||
free(m_pPixels);
|
||||
}
|
||||
|
||||
void CVideo::nextFrame()
|
||||
{
|
||||
if (m_Recording)
|
||||
{
|
||||
m_ProcessingFrame = true;
|
||||
m_pFrame->pts = m_pContext->frame_number;
|
||||
dbg_msg("video_recorder", "frame: %d", m_pContext->frame_number);
|
||||
ffmpeg_encoder_glread_rgb();
|
||||
ffmpeg_encoder_encode_frame();
|
||||
m_nframes++;
|
||||
m_ProcessingFrame = false;
|
||||
}
|
||||
}
|
||||
|
||||
void CVideo::ffmpeg_encoder_set_frame_yuv_from_rgb(uint8_t *pRGB)
|
||||
{
|
||||
const int in_linesize[1] = { 3 * m_pContext->width };
|
||||
m_pSws_context = sws_getCachedContext(
|
||||
m_pSws_context,
|
||||
m_pContext->width, m_pContext->height, AV_PIX_FMT_RGB24,
|
||||
m_pContext->width, m_pContext->height, AV_PIX_FMT_YUV420P,
|
||||
0, 0, 0, 0
|
||||
);
|
||||
sws_scale(m_pSws_context, (const uint8_t * const *)&pRGB, in_linesize, 0,
|
||||
m_pContext->height, m_pFrame->data, m_pFrame->linesize);
|
||||
}
|
||||
|
||||
void CVideo::ffmpeg_encoder_start(int codec_id, int fps, int width, int height)
|
||||
{
|
||||
AVCodec *codec;
|
||||
int ret;
|
||||
avcodec_register_all();
|
||||
codec = avcodec_find_encoder((AVCodecID)codec_id);
|
||||
if (!codec)
|
||||
{
|
||||
dbg_msg("video_recorder", "Codec not found");
|
||||
exit(1);
|
||||
}
|
||||
m_pContext= avcodec_alloc_context3(codec);
|
||||
if (!m_pContext)
|
||||
{
|
||||
dbg_msg("video_recorder", "Could not allocate video codec context");
|
||||
exit(1);
|
||||
}
|
||||
m_pContext->bit_rate = 400000;
|
||||
m_pContext->width = width;
|
||||
m_pContext->height = height;
|
||||
m_pContext->time_base.num = 1;
|
||||
m_pContext->time_base.den = fps;
|
||||
m_pContext->gop_size = 10;
|
||||
m_pContext->max_b_frames = 1;
|
||||
m_pContext->pix_fmt = AV_PIX_FMT_YUV420P;
|
||||
if (codec_id == AV_CODEC_ID_H264)
|
||||
{
|
||||
av_opt_set(m_pContext->priv_data, "preset", "slow", 0);
|
||||
av_opt_set(m_pContext->priv_data, "crf", "22", 0);
|
||||
}
|
||||
if (avcodec_open2(m_pContext, codec, NULL) < 0)
|
||||
{
|
||||
dbg_msg("video_recorder", "Could not open codec");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
m_File = m_pStorage->OpenFile("tmp.mp4", IOFLAG_WRITE, IStorage::TYPE_SAVE);
|
||||
|
||||
if (!m_File)
|
||||
{
|
||||
dbg_msg("video_recorder", "Could not open %s", "tmp.mp4");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
m_pFrame = av_frame_alloc();
|
||||
if (!m_pFrame)
|
||||
{
|
||||
dbg_msg("video_recorder", "Could not allocate video frame");
|
||||
exit(1);
|
||||
}
|
||||
m_pFrame->format = m_pContext->pix_fmt;
|
||||
m_pFrame->width = m_pContext->width;
|
||||
m_pFrame->height = m_pContext->height;
|
||||
ret = av_image_alloc(m_pFrame->data, m_pFrame->linesize, m_pContext->width, m_pContext->height, m_pContext->pix_fmt, 24);
|
||||
if (ret < 0)
|
||||
{
|
||||
dbg_msg("video_recorder", "Could not allocate raw picture buffer");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
void CVideo::ffmpeg_encoder_finish()
|
||||
{
|
||||
while (m_ProcessingFrame)
|
||||
thread_sleep(10);
|
||||
|
||||
|
||||
//uint8_t endcode[] = { 0, 0, 1, 0xb7 };
|
||||
int ret_send, ret_recv = 0;
|
||||
|
||||
ret_send = avcodec_send_frame(m_pContext, 0);
|
||||
do
|
||||
{
|
||||
ret_recv = avcodec_receive_packet(m_pContext, &m_Packet);
|
||||
if (!ret_recv)
|
||||
{
|
||||
io_write(m_File, m_Packet.data, m_Packet.size);
|
||||
av_packet_unref(&m_Packet);
|
||||
}
|
||||
else
|
||||
break;
|
||||
} while (true);
|
||||
|
||||
if (ret_recv && ret_recv != AVERROR_EOF)
|
||||
{
|
||||
dbg_msg("video_recorder", "failed to finish recoding, error: %d", ret_recv);
|
||||
}
|
||||
|
||||
//io_write(m_File, endcode, sizeof(endcode));
|
||||
io_close(m_File);
|
||||
avcodec_close(m_pContext);
|
||||
av_free(m_pContext);
|
||||
av_freep(&m_pFrame->data[0]);
|
||||
av_frame_free(&m_pFrame);
|
||||
}
|
||||
|
||||
void CVideo::ffmpeg_encoder_encode_frame()
|
||||
{
|
||||
if (!m_Recording)
|
||||
return;
|
||||
|
||||
int ret_send, ret_recv = 0;
|
||||
|
||||
ffmpeg_encoder_set_frame_yuv_from_rgb(m_pRGB);
|
||||
av_init_packet(&m_Packet);
|
||||
m_Packet.data = NULL;
|
||||
m_Packet.size = 0;
|
||||
|
||||
ret_send = avcodec_send_frame(m_pContext, m_pFrame);
|
||||
do
|
||||
{
|
||||
ret_recv = avcodec_receive_packet(m_pContext, &m_Packet);
|
||||
if (!ret_recv)
|
||||
{
|
||||
io_write(m_File, m_Packet.data, m_Packet.size);
|
||||
av_packet_unref(&m_Packet);
|
||||
}
|
||||
else
|
||||
break;
|
||||
} while (true);
|
||||
|
||||
if (ret_recv && ret_recv != AVERROR(EAGAIN))
|
||||
{
|
||||
dbg_msg("video_recorder", "Error encoding frame, error: %d", ret_recv);
|
||||
}
|
||||
}
|
||||
|
||||
void CVideo::ffmpeg_encoder_glread_rgb()
|
||||
{
|
||||
size_t i, j, k, cur_gl, cur_rgb, nvals;
|
||||
const size_t format_nchannels = 3;
|
||||
nvals = format_nchannels * m_Width * m_Height;
|
||||
m_pPixels = (uint8_t *)realloc(m_pPixels, nvals * sizeof(GLubyte));
|
||||
m_pRGB = (uint8_t *)realloc(m_pRGB, nvals * sizeof(uint8_t));
|
||||
/* Get RGBA to align to 32 bits instead of just 24 for RGB. May be faster for FFmpeg. */
|
||||
glReadPixels(0, 0, m_Width, m_Height, GL_RGB, GL_UNSIGNED_BYTE, m_pPixels);
|
||||
for (i = 0; i < m_Height; i++)
|
||||
{
|
||||
for (j = 0; j < m_Width; j++)
|
||||
{
|
||||
cur_gl = format_nchannels * (m_Width * (m_Height - i - 1) + j);
|
||||
cur_rgb = format_nchannels * (m_Width * i + j);
|
||||
for (k = 0; k < format_nchannels; k++)
|
||||
m_pRGB[cur_rgb + k] = m_pPixels[cur_gl + k];
|
||||
}
|
||||
}
|
||||
}
|
75
src/engine/client/video.h
Normal file
75
src/engine/client/video.h
Normal file
|
@ -0,0 +1,75 @@
|
|||
#ifndef ENGINE_CLIENT_VIDEO_H
|
||||
#define ENGINE_CLIENT_VIDEO_H
|
||||
|
||||
#if defined(__ANDROID__)
|
||||
#define GL_GLEXT_PROTOTYPES
|
||||
#include <GLES/gl.h>
|
||||
#include <GLES/glext.h>
|
||||
#include <GL/glu.h>
|
||||
#define glOrtho glOrthof
|
||||
#else
|
||||
#include "SDL_opengl.h"
|
||||
|
||||
#if defined(CONF_PLATFORM_MACOSX)
|
||||
#include "OpenGL/glu.h"
|
||||
#else
|
||||
#include "GL/glu.h"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
extern "C"
|
||||
{
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavutil/imgutils.h>
|
||||
#include <libavutil/opt.h>
|
||||
#include <libswscale/swscale.h>
|
||||
};
|
||||
|
||||
#include <base/system.h>
|
||||
|
||||
enum Constants { SCREENSHOT_MAX_FILENAME = 256 };
|
||||
|
||||
class CVideo
|
||||
{
|
||||
public:
|
||||
CVideo(class IStorage* pStorage, class IConsole *pConsole, int width, int height);
|
||||
~CVideo();
|
||||
|
||||
void start();
|
||||
void stop();
|
||||
|
||||
void nextFrame();
|
||||
|
||||
static CVideo* Current() { return ms_pCurrentVideo; }
|
||||
|
||||
private:
|
||||
void ffmpeg_encoder_set_frame_yuv_from_rgb(uint8_t* pRGB);
|
||||
void ffmpeg_encoder_start(int codec_id, int fps, int width, int height);
|
||||
void ffmpeg_encoder_finish();
|
||||
void ffmpeg_encoder_encode_frame();
|
||||
void ffmpeg_encoder_glread_rgb();
|
||||
|
||||
class IStorage *m_pStorage;
|
||||
class IConsole *m_pConsole;
|
||||
|
||||
int m_Width;
|
||||
int m_Height;
|
||||
|
||||
bool m_Recording;
|
||||
bool m_ProcessingFrame;
|
||||
|
||||
GLubyte* m_pPixels;
|
||||
unsigned int m_nframes;
|
||||
|
||||
AVCodecContext* m_pContext;
|
||||
AVFrame* m_pFrame;
|
||||
AVPacket m_Packet;
|
||||
IOHANDLE m_File;
|
||||
struct SwsContext* m_pSws_context;
|
||||
uint8_t* m_pRGB;
|
||||
|
||||
static CVideo* ms_pCurrentVideo;
|
||||
};
|
||||
|
||||
|
||||
#endif
|
Loading…
Reference in a new issue