initial work on a video recorder

This commit is contained in:
H-M-H 2016-08-27 17:51:23 +02:00 committed by sirius
parent ef32fc4bed
commit c5c171a3cb
5 changed files with 344 additions and 0 deletions

View file

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

View file

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

View file

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