# Added basic video recording.

* Included FFmpeg library for video encoding. (LGPL, dynamically
linked.)
 * Added 'record' button to cinematics - plays the selected track at a
fixed framerate, and saves the output to a video file (preferably
MPEG4). Slightly lacking in reliability and features (fixed at 640x480,
no choice of codec, probably crashes horribly if any problems occur
while encoding, etc).
 * Moved icon bitmaps out of mods directory. Added 'record' icon.

This was SVN commit r4676.
This commit is contained in:
Ykkrosh 2006-12-05 02:35:00 +00:00
parent 6ed4ad6519
commit 2c71c22045
18 changed files with 733 additions and 16 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 822 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 822 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 822 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 822 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 822 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 822 B

View File

@ -62,6 +62,10 @@ extern_lib_defs = {
win_names = { "ddraw", "dsound" },
dbg_suffix = "",
},
ffmpeg = {
win_names = { "avcodec-51", "avformat-51", "avutil-49" },
dbg_suffix = "",
},
libjpg = {
win_names = { "jpeg-6b" },
unix_names = { "jpeg" },
@ -95,8 +99,9 @@ extern_lib_defs = {
tinsert(package.includepaths, libraries_dir.."wxwidgets/include/msvc")
tinsert(package.includepaths, libraries_dir.."wxwidgets/include")
tinsert(package.libpaths, libraries_dir.."wxwidgets/lib/vc_lib")
package.config["Debug" ].links = { "wxmsw26ud_gl" }
package.config["Release"].links = { "wxmsw26u_gl" }
tinsert(package.config["Debug" ].links, "wxmsw26ud_gl")
tinsert(package.config["Testing"].links, "wxmsw26ud_gl")
tinsert(package.config["Release"].links, "wxmsw26u_gl")
else
tinsert(package.buildoptions, "`wx-config --cxxflags`")
tinsert(package.linkoptions, "`wx-config --libs std,gl,ogl,media`")

View File

@ -477,7 +477,7 @@ local function setup_atlas_package(package_name, target_type, rel_source_dirs, r
tinsert(package.defines, "_UNICODE")
-- Link to required libraries
package.links = { "winmm", "comctl32", "rpcrt4" }
package.links = { "winmm", "comctl32", "rpcrt4", "delayimp" }
-- required to use WinMain() on Windows, otherwise will default to main()
tinsert(package.buildflags, "no-main")
@ -525,6 +525,7 @@ function setup_atlas_packages()
"CustomControls/Windows",
"FileConverter",
"General",
"General/VideoRecorder",
"Misc",
"ScenarioEditor",
"ScenarioEditor/Sections/Common",
@ -543,8 +544,9 @@ function setup_atlas_packages()
},{ -- extern_libs
"boost",
"devil",
"xerces",
"wxwidgets"
"ffmpeg",
"wxwidgets",
"xerces"
},{ -- extra_params
pch = 1,
extra_links = { "AtlasObject", "DatafileIO" },

View File

@ -247,7 +247,7 @@ void WriteBigScreenshot(const char* extension, int tiles)
g_Renderer.Resize(tile_w, tile_h);
SViewPort vp = { 0, 0, tile_w, tile_h };
g_Game->GetView()->GetCamera()->SetViewPort(&vp);
g_Game->GetView()->GetCamera()->SetProjection (CGameView::defaultNear, CGameView::defaultFar, CGameView::defaultFOV);
g_Game->GetView()->GetCamera()->SetProjection(CGameView::defaultNear, CGameView::defaultFar, CGameView::defaultFOV);
}
// Temporarily move everything onto the front buffer, so the user can
@ -290,7 +290,7 @@ void WriteBigScreenshot(const char* extension, int tiles)
g_Renderer.Resize(g_xres, g_yres);
SViewPort vp = { 0, 0, g_xres, g_yres };
g_Game->GetView()->GetCamera()->SetViewPort(&vp);
g_Game->GetView()->GetCamera()->SetProjection (1, 5000, DEGTORAD(20));
g_Game->GetView()->GetCamera()->SetProjection(CGameView::defaultNear, CGameView::defaultFar, CGameView::defaultFOV);
g_Game->GetView()->GetCamera()->SetProjectionTile(1, 0, 0);
}

View File

@ -0,0 +1,391 @@
#include "stdafx.h"
/*
Code originally taken from ffmpeg's output_example.c
This is all rather hacked together and unreliable. In particular, I should:
* Change the fprintf/exit error handling so that it works in Atlas.
* Send all the logging output to wx too.
* Support variable bitrate (set qscale to 1 (best) .. 31 (worst), sameq)
* See if other codecs (particularly lossless ones) could be made to work.
(Currently it half assumes that it's passed a .mp4 filename.)
* See if other compression parameters would give better quality/speed/etc.
* Make the frame size variable.
* Tidy everything up a bit.
Please complain if I forget to do those things.
*/
#include "FFmpeg.h"
#ifdef _MSC_VER
# pragma warning(disable: 4100 4505 4510 4610)
#endif
#include "ffmpeg/avformat.h"
#include "ffmpeg/swscale.h"
struct VideoEncoderImpl
{
int framerate;
int bitrate;
float duration;
AVStream *video_st;
AVFormatContext *oc;
AVOutputFormat *fmt;
AVFrame *picture, *tmp_picture;
uint8_t *video_outbuf;
int frame_count, video_outbuf_size;
VideoEncoderImpl()
{
video_st = NULL;
oc = NULL;
fmt = NULL;
picture = NULL;
tmp_picture = NULL;
video_outbuf = NULL;
frame_count = 0;
video_outbuf_size = 0;
}
AVStream *add_video_stream(AVFormatContext *oc, int codec_id)
{
AVCodecContext *c;
AVStream *st;
st = av_new_stream(oc, 0);
if (!st) {
fprintf(stderr, "Could not alloc stream\n");
exit(1);
}
c = st->codec;
c->codec_id = (CodecID)codec_id;
c->codec_type = CODEC_TYPE_VIDEO;
c->bit_rate = bitrate*1000;
c->width = 640;
c->height = 480;
c->time_base.den = framerate;
c->time_base.num = 1;
c->pix_fmt = PIX_FMT_YUV420P;
// TODO: these compression parameters aren't necessarily any good
c->mb_decision = FF_MB_DECISION_RD;
c->gop_size = 250;
c->max_b_frames = 2;
c->b_frame_strategy = 1;
c->dia_size = 4;
c->me_cmp = c->me_sub_cmp= FF_CMP_SAD;
c->mb_cmp= FF_CMP_SSE;
c->last_predictor_count = 3;
c->flags |= CODEC_FLAG_4MV | CODEC_FLAG_QP_RD;
if(!strcmp(oc->oformat->name, "mp4") || !strcmp(oc->oformat->name, "mov") || !strcmp(oc->oformat->name, "3gp"))
c->flags |= CODEC_FLAG_GLOBAL_HEADER;
return st;
}
AVFrame *alloc_picture(int pix_fmt, int width, int height)
{
AVFrame *picture;
uint8_t *picture_buf;
int size;
picture = avcodec_alloc_frame();
if (!picture)
return NULL;
size = avpicture_get_size(pix_fmt, width, height);
picture_buf = (uint8_t*)av_malloc(size);
if (!picture_buf) {
av_free(picture);
return NULL;
}
avpicture_fill((AVPicture *)picture, picture_buf,
pix_fmt, width, height);
return picture;
}
void open_video(AVFormatContext *oc, AVStream *st)
{
AVCodec *codec;
AVCodecContext *c;
c = st->codec;
/* find the video encoder */
codec = avcodec_find_encoder(c->codec_id);
if (!codec) {
fprintf(stderr, "codec not found\n");
exit(1);
}
/* open the codec */
if (avcodec_open(c, codec) < 0) {
fprintf(stderr, "could not open codec\n");
exit(1);
}
video_outbuf = NULL;
if (!(oc->oformat->flags & AVFMT_RAWPICTURE)) {
/* allocate output buffer */
/* XXX: API change will be done */
/* buffers passed into lav* can be allocated any way you prefer,
as long as they're aligned enough for the architecture, and
they're freed appropriately (such as using av_free for buffers
allocated with av_malloc) */
video_outbuf_size = bitrate*1000/framerate * 8;
video_outbuf = (uint8_t*)av_malloc(video_outbuf_size);
}
/* allocate the encoded raw picture */
picture = alloc_picture(c->pix_fmt, c->width, c->height);
if (!picture) {
fprintf(stderr, "Could not allocate picture\n");
exit(1);
}
/* if the output format is not YUV420P, then a temporary YUV420P
picture is needed too. It is then converted to the required
output format */
tmp_picture = NULL;
if (c->pix_fmt != PIX_FMT_RGB24) {
tmp_picture = alloc_picture(PIX_FMT_RGB24, c->width, c->height);
if (!tmp_picture) {
fprintf(stderr, "Could not allocate temporary picture\n");
exit(1);
}
}
}
void copy_rgb_image(AVFrame *pict, int width, int height, const unsigned char* buffer)
{
for(int y=0;y<height;y++) {
memcpy(&pict->data[0][y * pict->linesize[0]], buffer+y*width*3, width*3);
}
}
void write_video_frame(AVFormatContext *oc, AVStream *st, const unsigned char* buffer)
{
AVCodecContext *c;
static struct SwsContext *img_convert_ctx;
int out_size, ret;
c = st->codec;
if (!buffer || frame_count >= (int)(duration*framerate)) {
/* no more frame to compress. The codec has a latency of a few
frames if using B frames, so we get the last frames by
passing the same picture again */
} else {
if (c->pix_fmt != PIX_FMT_RGB24) {
/* as we only generate a YUV420P picture, we must convert it
to the codec pixel format if needed */
if (img_convert_ctx == NULL) {
img_convert_ctx = sws_getContext(c->width, c->height,
PIX_FMT_RGB24,
c->width, c->height,
c->pix_fmt,
SWS_BICUBIC, NULL, NULL, NULL);
if (img_convert_ctx == NULL) {
fprintf(stderr, "Cannot initialize the conversion context\n");
exit(1);
}
}
copy_rgb_image(tmp_picture, c->width, c->height, buffer);
sws_scale(img_convert_ctx, tmp_picture->data, tmp_picture->linesize,
0, c->height, picture->data, picture->linesize);
} else {
copy_rgb_image(picture, c->width, c->height, buffer);
}
}
if (oc->oformat->flags & AVFMT_RAWPICTURE) {
/* raw video case. The API will change slightly in the near
futur for that */
AVPacket pkt;
av_init_packet(&pkt);
pkt.flags |= PKT_FLAG_KEY;
pkt.stream_index= st->index;
pkt.data= (uint8_t *)picture;
pkt.size= sizeof(AVPicture);
ret = av_write_frame(oc, &pkt);
} else {
/* encode the image */
out_size = avcodec_encode_video(c, video_outbuf, video_outbuf_size, picture);
/* if zero size, it means the image was buffered */
if (out_size > 0) {
AVPacket pkt;
av_init_packet(&pkt);
pkt.pts= av_rescale_q(c->coded_frame->pts, c->time_base, st->time_base);
if(c->coded_frame->key_frame)
pkt.flags |= PKT_FLAG_KEY;
pkt.stream_index= st->index;
pkt.data= video_outbuf;
pkt.size= out_size;
/* write the compressed frame in the media file */
ret = av_write_frame(oc, &pkt);
} else {
ret = 0;
}
}
if (ret != 0) {
fprintf(stderr, "Error while writing video frame\n");
exit(1);
}
frame_count++;
}
void close_video(AVFormatContext *oc, AVStream *st)
{
avcodec_close(st->codec);
av_free(picture->data[0]);
av_free(picture);
if (tmp_picture) {
av_free(tmp_picture->data[0]);
av_free(tmp_picture);
}
av_free(video_outbuf);
}
};
//////////////////////////////////////////////////////////////////////////
void log(void* v, int i, const char* format, va_list ap)
{
char buf[512];
vsnprintf(buf, sizeof(buf), format, ap);
buf[sizeof(buf)-1] = '\0';
wxLogDebug(L"[%d] %hs", i, buf);
}
VideoEncoder::VideoEncoder(const wxString& filenameStr, int framerate, int bitrate, float duration)
: m(new VideoEncoderImpl)
{
wxCharBuffer filename = filenameStr.ToAscii();
m->framerate = framerate;
m->bitrate = bitrate;
m->duration = duration;
/* initialize libavcodec, and register all codecs and formats */
av_register_all();
av_log_set_callback(&log);
/* auto detect the output format from the name. default is mpeg. */
m->fmt = guess_format(NULL, filename, NULL);
if (!m->fmt) {
printf("Could not deduce output format from file extension: using MPEG.\n");
m->fmt = guess_format("mpeg", NULL, NULL);
}
if (!m->fmt) {
fprintf(stderr, "Could not find suitable output format\n");
exit(1);
}
/* allocate the output media context */
m->oc = av_alloc_format_context();
if (!m->oc) {
fprintf(stderr, "Memory error\n");
exit(1);
}
m->oc->oformat = m->fmt;
strncpy(m->oc->filename, filename, sizeof(m->oc->filename));
m->oc->filename[sizeof(m->oc->filename) - 1] = '\0';
/* add the audio and video streams using the default format codecs
and initialize the codecs */
m->video_st = NULL;
if (m->fmt->video_codec != CODEC_ID_NONE) {
m->video_st = m->add_video_stream(m->oc, m->fmt->video_codec);
}
/* set the output parameters (must be done even if no
parameters). */
if (av_set_parameters(m->oc, NULL) < 0) {
fprintf(stderr, "Invalid output format parameters\n");
exit(1);
}
dump_format(m->oc, 0, filename, 1);
/* now that all the parameters are set, we can open the audio and
video codecs and allocate the necessary encode buffers */
if (m->video_st)
m->open_video(m->oc, m->video_st);
/* open the output file, if needed */
if (!(m->fmt->flags & AVFMT_NOFILE)) {
if (url_fopen(&m->oc->pb, filename, URL_WRONLY) < 0) {
fprintf(stderr, "Could not open '%s'\n", filename);
exit(1);
}
}
/* write the stream header, if any */
av_write_header(m->oc);
}
void VideoEncoder::Frame(const unsigned char* buffer)
{
double video_pts = (double)m->video_st->pts.val * m->video_st->time_base.num / m->video_st->time_base.den;
if (video_pts >= m->duration)
return;
m->write_video_frame(m->oc, m->video_st, buffer);
}
VideoEncoder::~VideoEncoder()
{
for(;;)
{
double video_pts = (double)m->video_st->pts.val * m->video_st->time_base.num / m->video_st->time_base.den;
if (video_pts >= m->duration)
break;
m->write_video_frame(m->oc, m->video_st, NULL);
}
/* close each codec */
if (m->video_st)
m->close_video(m->oc, m->video_st);
/* write the trailer, if any */
av_write_trailer(m->oc);
/* free the streams */
for(int i = 0; i < m->oc->nb_streams; i++) {
av_freep(&m->oc->streams[i]->codec);
av_freep(&m->oc->streams[i]);
}
if (!(m->fmt->flags & AVFMT_NOFILE)) {
/* close the output file */
url_fclose(&m->oc->pb);
}
/* free the stream */
av_free(m->oc);
delete m;
}

View File

@ -0,0 +1,17 @@
#ifndef FFMPEG_H__
#define FFMPEG_H__
struct VideoEncoderImpl;
class VideoEncoder
{
public:
VideoEncoder(const wxString& filename, int framerate, int bitrate, float duration);
void Frame(const unsigned char* buffer);
~VideoEncoder();
private:
VideoEncoderImpl* m;
};
#endif // FFMPEG_H__

View File

@ -0,0 +1,187 @@
#include "stdafx.h"
#include "VideoRecorder.h"
#include "FFmpeg.h"
#include "GameInterface/Messages.h"
using namespace AtlasMessage;
class RecorderDialog : public wxDialog
{
enum
{
ID_FILENAME,
ID_FILECHOOSE,
ID_FRAMERATE,
ID_BITRATE,
ID_DURATION,
ID_FILESIZE
};
public:
RecorderDialog(wxWindow* parent, float duration)
: wxDialog(parent, wxID_ANY, (wxString)_("Video recording options")),
m_Duration(duration)
{
wxArrayString framerates;
framerates.Add(_T("10"));
framerates.Add(_T("15"));
framerates.Add(_T("25"));
framerates.Add(_T("30"));
framerates.Add(_T("60"));
wxString framerateDefault = _T("25");
wxArrayString bitrates;
bitrates.Add(_T("500"));
bitrates.Add(_T("800"));
bitrates.Add(_T("1200"));
bitrates.Add(_T("1600"));
bitrates.Add(_T("3200"));
wxString bitrateDefault = _T("1200");
wxSizer* filenameSizer = new wxBoxSizer(wxHORIZONTAL);
filenameSizer->Add(new wxTextCtrl(this, ID_FILENAME, _T("")), wxSizerFlags().Proportion(1));
filenameSizer->Add(new wxButton(this, ID_FILECHOOSE, _("Browse...")));
wxGridSizer* gridSizer = new wxGridSizer(4);
gridSizer->Add(new wxStaticText(this, -1, _("Framerate (fps)")), wxSizerFlags().Right().Border(wxRIGHT, 4));
gridSizer->Add(new wxComboBox(this, ID_FRAMERATE, framerateDefault, wxDefaultPosition, wxDefaultSize, framerates), wxSizerFlags().Proportion(1).Expand());
// TODO: use a wxValidator to only allow digits
gridSizer->Add(new wxStaticText(this, -1, _("Duration (seconds):")), wxSizerFlags().Right().Border(wxLEFT, 4));
gridSizer->Add(new wxStaticText(this, ID_DURATION, wxString::Format(_T("%.1f"), m_Duration)), wxSizerFlags().Expand().Border(wxLEFT, 4));
gridSizer->Add(new wxStaticText(this, -1, _("Bitrate (Kbit/s)")), wxSizerFlags().Right().Border(wxRIGHT, 4));
gridSizer->Add(new wxComboBox(this, ID_BITRATE, bitrateDefault, wxDefaultPosition, wxDefaultSize, bitrates), wxSizerFlags().Proportion(1).Expand());
gridSizer->Add(new wxStaticText(this, -1, _("Estimated file size:")), wxSizerFlags().Right().Border(wxRIGHT, 4));
gridSizer->Add(new wxStaticText(this, ID_FILESIZE, L"???"), wxSizerFlags().Expand().Border(wxLEFT, 4));
RecalculateSizes_();
wxSizer* gridSizerBox = new wxStaticBoxSizer(wxVERTICAL, this, _("Compression options"));
gridSizerBox->Add(gridSizer, wxSizerFlags().Border(wxALL, 10));
wxStdDialogButtonSizer* buttonSizer = new wxStdDialogButtonSizer();
wxButton* okButton = new wxButton(this, wxID_OK, _("Save"));
okButton->SetDefault();
buttonSizer->AddButton(okButton);
buttonSizer->AddButton(new wxButton(this, wxID_CANCEL, _("Cancel")));
buttonSizer->Realize();
wxSizer* sizer = new wxBoxSizer(wxVERTICAL);
sizer->Add(filenameSizer, wxSizerFlags().Border(wxALL, 10).Expand());
sizer->Add(gridSizerBox, wxSizerFlags().Border(wxALL, 10));
sizer->Add(buttonSizer, wxSizerFlags().Border(wxALL, 10).Centre());
SetSizer(sizer);
sizer->SetSizeHints(this);
}
// Outputs:
wxString m_Filename;
unsigned long m_Framerate;
unsigned long m_Bitrate;
protected:
void OnOK(wxCommandEvent& event)
{
wxTextCtrl* filenameWin = wxDynamicCast(FindWindow(ID_FILENAME), wxTextCtrl);
wxComboBox* framerateWin = wxDynamicCast(FindWindow(ID_FRAMERATE), wxComboBox);
wxComboBox* bitrateWin = wxDynamicCast(FindWindow(ID_BITRATE), wxComboBox);
wxCHECK(filenameWin && framerateWin && bitrateWin, );
m_Filename = filenameWin->GetValue();
if (m_Filename.IsEmpty())
{
wxLogError(_("No filename specified."));
return;
}
if (!framerateWin->GetValue().ToULong(&m_Framerate) ||
!bitrateWin->GetValue().ToULong(&m_Bitrate))
{
wxLogError(_("Invalid framerate/bitrate."));
return;
}
wxDialog::OnOK(event);
}
private:
void RecalculateSizes(wxCommandEvent& WXUNUSED(event)) { RecalculateSizes_(); }
void RecalculateSizes_()
{
wxComboBox* framerateWin = wxDynamicCast(FindWindow(ID_FRAMERATE), wxComboBox);
wxComboBox* bitrateWin = wxDynamicCast(FindWindow(ID_BITRATE), wxComboBox);
wxStaticText* filesizeWin = wxDynamicCast(FindWindow(ID_FILESIZE), wxStaticText);
wxCHECK(framerateWin && bitrateWin && filesizeWin, );
unsigned long framerate = 0, bitrate = 0;
if (!framerateWin->GetValue().ToULong(&framerate) || !bitrateWin->GetValue().ToULong(&bitrate))
return;
int size = m_Duration * bitrate * 1000/8;
wxString sizeStr;
if (size < 1024*1024) sizeStr = wxString::Format(_T("~%.0f KiB"), (float)size/1024);
else if (size < 1024*1024*100) sizeStr = wxString::Format(_T("~%.1f MiB"), (float)size/(1024*1024));
else if (size < 1024*1024*1024) sizeStr = wxString::Format(_T("~%.0f MiB"), (float)size/(1024*1024));
else sizeStr = wxString::Format(_T("~%.2f GiB"), (float)size/(1024*1024*1024));
filesizeWin->SetTitle(sizeStr);
}
void FileBrowse(wxCommandEvent& WXUNUSED(event))
{
wxTextCtrl* filenameWin = wxDynamicCast(FindWindow(ID_FILENAME), wxTextCtrl);
wxCHECK(filenameWin, );
wxFileDialog dlg (this, wxFileSelectorPromptStr, filenameWin->GetValue(), filenameWin->GetValue(), _("MP4 files (*.mp4)|*.mp4"), wxSAVE);
if (dlg.ShowModal() != wxID_OK)
return;
filenameWin->SetValue(dlg.GetPath());
}
float m_Duration;
DECLARE_EVENT_TABLE();
};
BEGIN_EVENT_TABLE(RecorderDialog, wxDialog)
EVT_COMBOBOX(ID_FRAMERATE, RecorderDialog::RecalculateSizes)
EVT_COMBOBOX(ID_BITRATE, RecorderDialog::RecalculateSizes)
EVT_TEXT(ID_FRAMERATE, RecorderDialog::RecalculateSizes)
EVT_TEXT(ID_BITRATE, RecorderDialog::RecalculateSizes)
EVT_BUTTON(ID_FILECHOOSE, RecorderDialog::FileBrowse)
EVT_BUTTON(wxID_OK, RecorderDialog::OnOK)
END_EVENT_TABLE()
void callback(const sCinemaRecordCB* data, void* cbdata)
{
VideoEncoder* enc = (VideoEncoder*)cbdata;
enc->Frame(data->buffer);
}
void VideoRecorder::RecordCinematic(wxWindow* window, const wxString& trackName, float duration)
{
RecorderDialog dlg(window, duration);
if (dlg.ShowModal() != wxID_OK)
return;
wxStopWatch sw;
VideoEncoder venc (dlg.m_Filename, dlg.m_Framerate, dlg.m_Bitrate, duration);
qCinemaRecord qry(trackName.c_str(), dlg.m_Framerate, duration, Callback<sCinemaRecordCB>(&callback, (void*)&venc));
qry.Post();
wxLogMessage(_("Finished recording (took %.1f seconds)\n"), sw.Time()/1000.f);
}

View File

@ -0,0 +1,10 @@
#ifndef VIDEORECORDER_H__
#define VIDEORECORDER_H__
class VideoRecorder
{
public:
static void RecordCinematic(wxWindow* window, const wxString& trackName, float duration);
};
#endif // VIDEORECORDER_H__

View File

@ -9,6 +9,8 @@
#include "ScenarioEditor/Tools/Common/Tools.h"
#include "HighResTimer/HighResTimer.h"
#include "General/VideoRecorder/VideoRecorder.h"
#include "wx/spinctrl.h"
#include "wx/filename.h"
#include "wx/wfstream.h"
@ -23,7 +25,7 @@ struct eCinemaButton
{
enum {
previous/*, rewind, reverse*/, stop,
play, pause, /*forward,*/ next };
play, pause, /*forward,*/ next, record };
};
float CinemaTextFloat(wxTextCtrl&, size_t, float, float, float);
@ -925,6 +927,17 @@ public:
m_Parent->m_Playing = true;
}
void OnRecord(wxCommandEvent& WXUNUSED(event))
{
if ( m_Parent->m_SelectedTrack < 0 )
return;
m_Parent->m_SliderBox->m_Timer.Stop();
m_Parent->m_SliderBox->PrepareTimers();
VideoRecorder::RecordCinematic(this,
m_Parent->m_Tracks[m_Parent->m_SelectedTrack].name.c_str(),
m_Parent->m_Tracks[m_Parent->m_SelectedTrack].duration);
}
void OnPause(wxCommandEvent& WXUNUSED(event))
{
if ( m_Parent->m_SelectedTrack < 0 )
@ -980,6 +993,7 @@ BEGIN_EVENT_TABLE(CinemaButtonBox, wxPanel)
EVT_BUTTON(eCinemaButton::pause, CinemaButtonBox::OnPause)
// EVT_BUTTON(eCinemaButton::forward, CinemaButtonBox::OnForward)
EVT_BUTTON(eCinemaButton::next, CinemaButtonBox::OnNext)
EVT_BUTTON(eCinemaButton::record, CinemaButtonBox::OnRecord)
END_EVENT_TABLE()
//////////////////////////////////////////////////////////////////////////
@ -1126,7 +1140,7 @@ wxImage CinematicSidebar::LoadIcon(const wxString& filename)
wxImage img (1, 1, true);
// Load the icon
wxFileName iconPath (_T("mods/official/art/textures/ui/session/icons/single/atlas/"));
wxFileName iconPath (_T("tools/atlas/buttons/"));
iconPath.MakeAbsolute(Datafile::GetDataDirectory());
iconPath.SetFullName(filename);
wxFileInputStream fstr (iconPath.GetFullPath());
@ -1162,6 +1176,9 @@ void CinematicSidebar::LoadIcons()
wxBitmapButton* next = new wxBitmapButton(m_IconSizer,
eCinemaButton::next, LoadIcon( _T("next_s.bmp") ));
m_IconSizer->Add(next);
wxBitmapButton* record = new wxBitmapButton(m_IconSizer,
eCinemaButton::record, LoadIcon( _T("record_s.bmp") ));
m_IconSizer->Add(record);
}
void CinematicSidebar::AddTrack(float x, float y, float z,
std::wstring& name, int count)

View File

@ -94,13 +94,6 @@ private:
CinemaInfoBox* m_InfoBox; // ^same^
CinematicBottomBar* m_CinemaBottomBar;
struct eCinemaButton
{
enum {
previous/*, rewind, reverse*/, stop,
play, pause, /*forward,*/ next };
};
wxImage LoadIcon(const wxString& filename);
void LoadIcons();
};

View File

@ -6,6 +6,13 @@
#include "ps/GameSetup/Config.h"
#include "ps/Util.h"
#include "ps/Game.h"
#include "renderer/Renderer.h"
#include "ps/GameSetup/GameSetup.h"
#include "../GameLoop.h"
extern void (*Atlas_GLSwapBuffers)(void* context);
namespace AtlasMessage {
MESSAGEHANDLER(MessageTrace)
@ -19,6 +26,57 @@ MESSAGEHANDLER(Screenshot)
WriteBigScreenshot("bmp", msg->tiles);
}
QUERYHANDLER(CinemaRecord)
{
CCinemaManager* manager = g_Game->GetView()->GetCinema();
manager->SetCurrentTrack(*msg->track, false, false, false);
const int w = 640, h = 480;
{
g_Renderer.Resize(w, h);
SViewPort vp = { 0, 0, w, h };
g_Game->GetView()->GetCamera()->SetViewPort(&vp);
g_Game->GetView()->GetCamera()->SetProjection(CGameView::defaultNear, CGameView::defaultFar, CGameView::defaultFOV);
}
unsigned char* img = new unsigned char [w*h*3];
int num_frames = msg->framerate * msg->duration;
for (int frame = 0; frame < num_frames; ++frame)
{
manager->MoveToPointAbsolute((float)frame/msg->framerate);
Render();
Atlas_GLSwapBuffers((void*)g_GameLoop->glContext);
glReadPixels(0, 0, w, h, GL_RGB, GL_UNSIGNED_BYTE, img);
// Swap the rows around, else the image will be upside down
char temp[w*3];
for (int y = 0; y < h/2; ++y)
{
memcpy2(temp, &img[y*w*3], w*3);
memcpy2(&img[y*w*3], &img[(h-1-y)*w*3], w*3);
memcpy2(&img[(h-1-y)*w*3], temp, w*3);
}
// Call the user-supplied function with this data, so they can
// store it as a video
sCinemaRecordCB cbdata = { img };
msg->cb.Call(cbdata);
}
delete[] img;
// Restore viewport
{
g_Renderer.Resize(g_xres, g_yres);
SViewPort vp = { 0, 0, g_xres, g_yres };
g_Game->GetView()->GetCamera()->SetViewPort(&vp);
g_Game->GetView()->GetCamera()->SetProjection(CGameView::defaultNear, CGameView::defaultFar, CGameView::defaultFOV);
}
}
QUERYHANDLER(Ping)
{
UNUSED2(msg);

View File

@ -80,6 +80,20 @@ MESSAGE(Screenshot,
((int, tiles)) // the final image will be (640*tiles)x(480*tiles)
);
struct sCinemaRecordCB
{
unsigned char* buffer;
};
SHAREABLE_STRUCT(sCinemaRecordCB);
QUERY(CinemaRecord,
((std::wstring, track))
((int, framerate))
((float, duration))
((Callback<sCinemaRecordCB>, cb))
,
);
//////////////////////////////////////////////////////////////////////////
MESSAGE(Brush,

View File

@ -268,6 +268,29 @@ public:
}
};
// Shareable callbacks:
// (TODO - this is probably not really safely shareable, due to unspecified calling conventions)
template<typename T> struct Callback
{
Callback(void (*cb) (const T*, void*), void* cbdata) : cb(cb), cbdata(cbdata) {}
void (*cb) (const T*, void*);
void* cbdata;
};
template<typename T> class Shareable<Callback<T> >
{
ASSERT_TYPE_IS_SHAREABLE(T);
public:
Shareable(Callback<T> cb) : cb(cb) {}
Callback<T> cb;
void Call(const T& data) const
{
cb.cb(&data, cb.cbdata);
}
};
}
#ifdef SHAREABLE_USED_NOMMGR