Allow muxing mjpeg with vorbis audio with mkv

Not sure if playable in with all players
This commit is contained in:
Benau 2017-04-04 16:12:09 +08:00
parent b0d0a0739e
commit b7c709c709
11 changed files with 189 additions and 797 deletions

View File

@ -442,7 +442,7 @@ endif()
if(BUILD_RECORDER)
target_link_libraries(supertuxkart webm ${TURBOJPEG_LIBRARY} ${VPX_LIBRARIES})
if(BUILD_RECORDER_WITH_SOUND)
if(BUILD_RECORDER_WITH_SOUND AND UNIX)
if(BUILD_PULSE_WO_DL)
target_link_libraries(supertuxkart ${PULSEAUDIO_LIBRARIES})
else()

View File

@ -4151,11 +4151,11 @@ bool Segment::WriteFramesLessThan(uint64_t timestamp) {
}
bool Segment::DocTypeIsWebm() const {
const int kNumCodecIds = 9;
const int kNumCodecIds = 8;
// TODO(vigneshv): Tweak .clang-format.
const char* kWebmCodecIds[kNumCodecIds] = {
Tracks::kOpusCodecId, Tracks::kVorbisCodecId,
Tracks::kOpusCodecId, //Tracks::kVorbisCodecId,
Tracks::kVp8CodecId, Tracks::kVp9CodecId,
Tracks::kVp10CodecId, Tracks::kWebVttCaptionsId,
Tracks::kWebVttDescriptionsId, Tracks::kWebVttMetadataId,
@ -4165,20 +4165,14 @@ bool Segment::DocTypeIsWebm() const {
for (int track_index = 0; track_index < num_tracks; ++track_index) {
const Track* const track = tracks_.GetTrackByIndex(track_index);
const std::string codec_id = track->codec_id();
bool id_is_webm = false;
for (int id_index = 0; id_index < kNumCodecIds; ++id_index) {
if (codec_id == kWebmCodecIds[id_index]) {
id_is_webm = true;
break;
return true;
}
}
}
if (!id_is_webm)
return false;
}
return true;
}
} // namespace mkvmuxer

View File

@ -0,0 +1,87 @@
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2017 SuperTuxKart-Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 3
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#ifdef ENABLE_RECORDER
#include "config/user_config.hpp"
#include "graphics/irr_driver.hpp"
#include "recorder/recorder_common.hpp"
#include "utils/log.hpp"
#include "utils/synchronised.hpp"
#include "utils/vs.hpp"
#include <turbojpeg.h>
namespace Recorder
{
// ------------------------------------------------------------------------
void* jpgWriter(void *obj)
{
VS::setThreadName("jpgWriter");
FILE* jpg_writer = fopen((getRecordingName() + ".video").c_str(), "wb");
if (jpg_writer == NULL)
{
Log::error("jpgWriter", "Failed to open file for writing");
return NULL;
}
ThreadData* td = (ThreadData*)obj;
Synchronised<std::list<std::tuple<uint8_t*, unsigned, int> > >*
jpg_data = (Synchronised<std::list<std::tuple<uint8_t*,
unsigned, int> > >*)td->m_data;
pthread_cond_t* cond_request = td->m_request;
const unsigned width = irr_driver->getActualScreenSize().Width;
const unsigned height = irr_driver->getActualScreenSize().Height;
int64_t frames_encoded = 0;
while (true)
{
jpg_data->lock();
bool waiting = jpg_data->getData().empty();
while (waiting)
{
pthread_cond_wait(cond_request, jpg_data->getMutex());
waiting = jpg_data->getData().empty();
}
auto& p = jpg_data->getData().front();
uint8_t* jpg = std::get<0>(p);
uint32_t jpg_size = std::get<1>(p);
int frame_count = std::get<2>(p);
if (jpg == NULL)
{
jpg_data->getData().clear();
jpg_data->unlock();
break;
}
jpg_data->getData().pop_front();
jpg_data->unlock();
while (frame_count != 0)
{
fwrite(&jpg_size, 1, sizeof(uint32_t), jpg_writer);
fwrite(&frames_encoded, 1, sizeof(int64_t), jpg_writer);
fwrite(&jpg_size, 1, sizeof(uint32_t), jpg_writer);
fwrite(jpg, 1, jpg_size, jpg_writer);
frame_count--;
frames_encoded++;
}
tjFree(jpg);
}
fclose(jpg_writer);
return NULL;
} // jpgWriter
}
#endif

View File

@ -0,0 +1,30 @@
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2017 SuperTuxKart-Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 3
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#ifdef ENABLE_RECORDER
#ifndef HEADER_JPG_WRITER_HPP
#define HEADER_JPG_WRITER_HPP
namespace Recorder
{
void* jpgWriter(void *obj);
};
#endif
#endif

View File

@ -17,9 +17,9 @@
#ifdef ENABLE_RECORDER
#include "recorder/webm_writer.hpp"
#include "config/user_config.hpp"
#include "graphics/irr_driver.hpp"
#include "recorder/recorder_common.hpp"
#include "utils/log.hpp"
#include "utils/string_utils.hpp"
@ -33,7 +33,7 @@
namespace Recorder
{
// ------------------------------------------------------------------------
void writeWebm(const std::string& video, const std::string& audio)
void writeMKV(const std::string& video, const std::string& audio)
{
time_t rawtime;
time(&rawtime);
@ -44,17 +44,19 @@ namespace Recorder
timeInfo->tm_mday, timeInfo->tm_hour,
timeInfo->tm_min, timeInfo->tm_sec);
std::string no_ext = StringUtils::removeExtension(video);
std::string webm_name = no_ext + "-" + time_buffer + ".webm";
VideoFormat vf = (VideoFormat)(int)UserConfigParams::m_record_format;
std::string file_name = no_ext + "-" + time_buffer +
(vf == VF_VP8 || vf == VF_VP9 ? ".webm" : ".mkv");
mkvmuxer::MkvWriter writer;
if (!writer.Open(webm_name.c_str()))
if (!writer.Open(file_name.c_str()))
{
Log::error("writeWebm", "Error while opening output file.");
Log::error("writeMKV", "Error while opening output file.");
return;
}
mkvmuxer::Segment muxer_segment;
if (!muxer_segment.Init(&writer))
{
Log::error("writeWebm", "Could not initialize muxer segment.");
Log::error("writeMKV", "Could not initialize muxer segment.");
return;
}
std::list<mkvmuxer::Frame*> audio_frames;
@ -72,14 +74,14 @@ namespace Recorder
channels, 0);
if (!aud_track)
{
Log::error("writeWebm", "Could not add audio track.");
Log::error("writeMKV", "Could not add audio track.");
return;
}
mkvmuxer::AudioTrack* const at = static_cast<mkvmuxer::AudioTrack*>
(muxer_segment.GetTrackByNumber(aud_track));
if (!at)
{
Log::error("writeWebm", "Could not get audio track.");
Log::error("writeMKV", "Could not get audio track.");
return;
}
uint32_t codec_private_size;
@ -87,7 +89,7 @@ namespace Recorder
fread(buf, 1, codec_private_size, input);
if (!at->SetCodecPrivate(buf, codec_private_size))
{
Log::warn("writeWebm", "Could not add audio private data.");
Log::warn("writeMKV", "Could not add audio private data.");
return;
}
while (fread(buf, 1, 12, input) == 12)
@ -100,7 +102,7 @@ namespace Recorder
mkvmuxer::Frame* audio_frame = new mkvmuxer::Frame();
if (!audio_frame->Init(buf, frame_size))
{
Log::error("writeWebm", "Failed to construct a frame.");
Log::error("writeMKV", "Failed to construct a frame.");
return;
}
audio_frame->set_track_number(aud_track);
@ -111,7 +113,7 @@ namespace Recorder
fclose(input);
if (remove(audio.c_str()) != 0)
{
Log::warn("writeWebm", "Failed to remove audio data file");
Log::warn("writeMKV", "Failed to remove audio data file");
}
}
uint64_t vid_track = muxer_segment.AddVideoTrack(
@ -119,17 +121,32 @@ namespace Recorder
irr_driver->getActualScreenSize().Height, 0);
if (!vid_track)
{
Log::error("writeWebm", "Could not add video track.");
Log::error("writeMKV", "Could not add video track.");
return;
}
mkvmuxer::VideoTrack* const vt = static_cast<mkvmuxer::VideoTrack*>(
muxer_segment.GetTrackByNumber(vid_track));
if (!vt)
{
Log::error("writeWebm", "Could not get video track.");
Log::error("writeMKV", "Could not get video track.");
return;
}
vt->set_frame_rate(UserConfigParams::m_record_fps);
switch (vf)
{
case VF_VP8:
vt->set_codec_id("V_VP8");
break;
case VF_VP9:
vt->set_codec_id("V_VP9");
break;
case VF_MJPEG:
vt->set_codec_id("V_MJPEG");
break;
case VF_H264:
vt->set_codec_id("V_MPEG4/ISO/AVC");
break;
}
input = fopen(video.c_str(), "rb");
while (fread(buf, 1, 16, input) == 16)
{
@ -144,12 +161,19 @@ namespace Recorder
mkvmuxer::Frame muxer_frame;
if (!muxer_frame.Init(buf, frame_size))
{
Log::error("writeWebm", "Failed to construct a frame.");
Log::error("writeMKV", "Failed to construct a frame.");
return;
}
muxer_frame.set_track_number(vid_track);
muxer_frame.set_timestamp(timestamp);
if (vf == VF_VP8 || vf == VF_VP9)
{
muxer_frame.set_is_key((flag & VPX_FRAME_IS_KEY) != 0);
}
else
{
muxer_frame.set_is_key(true);
}
mkvmuxer::Frame* cur_aud_frame =
audio_frames.empty() ? NULL : audio_frames.front();
if (cur_aud_frame != NULL)
@ -158,7 +182,7 @@ namespace Recorder
{
if (!muxer_segment.AddGenericFrame(cur_aud_frame))
{
Log::error("writeWebm", "Could not add audio frame.");
Log::error("writeMKV", "Could not add audio frame.");
return;
}
delete cur_aud_frame;
@ -173,7 +197,7 @@ namespace Recorder
}
if (!muxer_segment.AddGenericFrame(&muxer_frame))
{
Log::error("writeWebm", "Could not add video frame.");
Log::error("writeMKV", "Could not add video frame.");
return;
}
}
@ -185,14 +209,14 @@ namespace Recorder
}
if (remove(video.c_str()) != 0)
{
Log::warn("writeWebm", "Failed to remove video data file");
Log::warn("writeMKV", "Failed to remove video data file");
}
if (!muxer_segment.Finalize())
{
Log::error("writeWebm", "Finalization of segment failed.");
Log::error("writeMKV", "Finalization of segment failed.");
return;
}
writer.Close();
} // writeWebm
} // writeMKV
};
#endif

View File

@ -18,13 +18,13 @@
#ifdef ENABLE_RECORDER
#ifndef HEADER_WEBM_WRITER_HPP
#define HEADER_WEBM_WRITER_HPP
#ifndef HEADER_MKV_WRITER_HPP
#define HEADER_MKV_WRITER_HPP
#include <string>
namespace Recorder
{
void writeWebm(const std::string& video, const std::string& audio);
void writeMKV(const std::string& video, const std::string& audio);
};
#endif

View File

@ -22,10 +22,11 @@
#include "graphics/irr_driver.hpp"
#include "graphics/gl_headers.hpp"
#include "guiengine/message_queue.hpp"
#include "recorder/jpg_writer.hpp"
#include "recorder/mkv_writer.hpp"
#include "recorder/pulseaudio_recorder.hpp"
#include "recorder/wasapi_recorder.hpp"
#include "recorder/vpx_encoder.hpp"
#include "recorder/webm_writer.hpp"
#include "recorder/wasapi_recorder.hpp"
#include "utils/synchronised.hpp"
#include "utils/translation.hpp"
#include "utils/vs.hpp"
@ -108,7 +109,7 @@ namespace Recorder
g_jpg_list.unlock();
pthread_join(*g_video_thread.getAtomic(), NULL);
g_video_thread.setAtomic(NULL);
Recorder::writeWebm(g_recording_name + ".video",
Recorder::writeMKV(g_recording_name + ".video",
g_recording_name + ".audio");
if (g_destroy)
{
@ -281,8 +282,21 @@ namespace Recorder
pthread_create(&g_audio_thread, NULL, &Recorder::audioRecorder,
&g_idle);
g_video_thread.setAtomic(new pthread_t());
pthread_create(g_video_thread.getAtomic(), NULL, &Recorder::vpxEncoder,
&g_jpg_thread_data);
VideoFormat vf = (VideoFormat)(int)UserConfigParams::m_record_format;
switch (vf)
{
case VF_VP8:
case VF_VP9:
pthread_create(g_video_thread.getAtomic(), NULL,
&Recorder::vpxEncoder, &g_jpg_thread_data);
break;
case VF_MJPEG:
pthread_create(g_video_thread.getAtomic(), NULL,
&Recorder::jpgWriter, &g_jpg_thread_data);
break;
case VF_H264:
break;
}
} // prepareCapture
// ------------------------------------------------------------------------
int getFrameCount(double rate)

View File

@ -17,7 +17,6 @@
#ifdef ENABLE_RECORDER
#include "recorder/vpx_encoder.hpp"
#include "config/user_config.hpp"
#include "graphics/irr_driver.hpp"
#include "recorder/recorder_common.hpp"
@ -110,7 +109,7 @@ namespace Recorder
FILE* vpx_data = fopen((getRecordingName() + ".video").c_str(), "wb");
if (vpx_data == NULL)
{
Log::error("vorbisEncoder", "Failed to encode ogg file");
Log::error("vpxEncoder", "Failed to open file for writing");
return NULL;
}
ThreadData* td = (ThreadData*)obj;

View File

@ -21,10 +21,6 @@
#ifndef HEADER_VPX_ENCODER_HPP
#define HEADER_VPX_ENCODER_HPP
#include "utils/no_copy.hpp"
#include <pthread.h>
namespace Recorder
{
void* vpxEncoder(void *obj);

View File

@ -1,487 +0,0 @@
//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2015 Dawid Gan
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 3
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#if !(defined(SERVER_ONLY) || defined(USE_GLES2))
#include "utils/avi_writer.hpp"
#include "config/user_config.hpp"
#include "graphics/irr_driver.hpp"
#include "guiengine/message_queue.hpp"
#include "recorder/pulseaudio_recorder.hpp"
#include "recorder/vorbis_encoder.hpp"
#include "recorder/vpx_encoder.hpp"
#include "recorder/wasapi_recorder.hpp"
#include "recorder/webm_writer.hpp"
#include "utils/translation.hpp"
#include "utils/vs.hpp"
#include <turbojpeg.h>
#include <vpx/vpx_encoder.h>
#include <vpx/vp8cx.h>
#include <jpeglib.h>
#include <cstring>
Synchronised<std::string> AVIWriter::m_recording_target("");
// ----------------------------------------------------------------------------
AVIWriter::AVIWriter() : m_idle(true)
{
resetFrameBufferImage();
resetCaptureFormat();
m_file = NULL;
m_last_junk_chunk = 0;
m_end_of_header = 0;
m_movi_start = 0;
m_stream_bytes = 0;
m_total_frames = 0;
m_chunk_fcc = 0;
m_width = irr_driver->getActualScreenSize().Width;
m_height = irr_driver->getActualScreenSize().Height;
glGenBuffers(3, m_pbo);
for (int i = 0; i < 3; i++)
{
glBindBuffer(GL_PIXEL_PACK_BUFFER, m_pbo[i]);
glBufferData(GL_PIXEL_PACK_BUFFER, m_width * m_height * 4, NULL,
GL_STREAM_READ);
}
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
pthread_cond_init(&m_cond_request, NULL);
} // AVIWriter
// ----------------------------------------------------------------------------
AVIWriter::~AVIWriter()
{
glDeleteBuffers(3, m_pbo);
addFrameBufferImage(NULL, 0);
if (!waitForReadyToDeleted(2.0f))
Log::info("AVIWriter", "AVIWriter not stopping, exiting anyway.");
pthread_cond_destroy(&m_cond_request);
} // ~AVIWriter
// ----------------------------------------------------------------------------
void AVIWriter::resetFrameBufferImage()
{
m_pbo_use = 0;
m_accumulated_time = 0.0f;
} // resetFrameBufferImage
// ----------------------------------------------------------------------------
void AVIWriter::resetCaptureFormat()
{
m_img_quality = UserConfigParams::m_recorder_jpg_quality;
m_msec_per_frame = unsigned(1000 / UserConfigParams::m_record_fps);
m_avi_format = AVI_FORMAT_JPG;
} // resetCaptureFormat
// ----------------------------------------------------------------------------
int AVIWriter::getFrameCount(double rate)
{
const double frame_rate = 1. / double(UserConfigParams::m_record_fps);
m_accumulated_time += rate;
if (m_accumulated_time < frame_rate)
{
return 0;
}
int frame_count = 0;
while (m_accumulated_time >= frame_rate)
{
frame_count++;
m_accumulated_time = m_accumulated_time - frame_rate;
}
return frame_count;
} // getFrameCount
// ----------------------------------------------------------------------------
void AVIWriter::captureFrameBufferImage()
{
} // captureFrameBufferImage
// ----------------------------------------------------------------------------
bool AVIWriter::addJUNKChunk(std::string str, unsigned int min_size)
{
int size = str.size() < min_size ? min_size : str.size() + 1;
size = (size + 1) & 0xfffffffe;
CHUNK chunk;
chunk.fcc = FOURCC('J', 'U', 'N', 'K');
chunk.cb = size;
char* buffer = (char*)calloc(size, 1);
strcpy(buffer, str.c_str());
int num = fwrite(&chunk, 1, sizeof(chunk), m_file);
if (num != sizeof(chunk))
goto error;
num = fwrite(buffer, 1, size * sizeof(char), m_file);
free(buffer);
if (num != size)
goto error;
m_last_junk_chunk = ftell(m_file);
if (m_last_junk_chunk < 0)
goto error;
return true;
error:
closeFile(true/*delete_file*/);
return false;
} // addJUNKChunk
// ----------------------------------------------------------------------------
AVIErrCode AVIWriter::addImage(unsigned char* buffer, int buf_size)
{
if (m_file == NULL)
goto error;
int num; num = ftell(m_file);
if (num < 0)
goto error;
if (m_total_frames >= (unsigned)MAX_FRAMES)
goto size_limit;
CHUNK chunk;
chunk.fcc = m_chunk_fcc;
chunk.cb = buf_size;
m_index_table[m_total_frames].Offset = num;
m_index_table[m_total_frames].Length = chunk.cb;
m_index_table[m_total_frames].fcc = chunk.fcc;
num = fwrite(&chunk, 1, sizeof(chunk), m_file);
if (num != sizeof(chunk))
goto error;
num = fwrite(buffer, 1, buf_size, m_file);
if (num != buf_size)
goto error;
int fill_size; fill_size = (sizeof(chunk) + buf_size) & 0x00000001;
if (fill_size > 0)
{
uint32_t filler = 0;
num = fwrite(&filler, 1, fill_size, m_file);
if (num != fill_size)
goto error;
}
m_stream_bytes += sizeof(chunk) + buf_size + fill_size;
m_total_frames++;
num = ftell(m_file);
if (num < 0)
goto error;
if (((num - m_last_junk_chunk) > 20000) && (!addJUNKChunk("", 1)))
goto error;
// check if we reached the file size limit
if (num >= MAX_FILE_SIZE)
goto size_limit;
return AVI_SUCCESS;
error:
closeFile(true/*delete_file*/);
return AVI_IO_ERR;
size_limit:
MessageQueue::add(MessageQueue::MT_GENERIC,
_("Video exceeded size limit, starting a new one."));
closeFile();
return AVI_SIZE_LIMIT_ERR;
} // addImage
// ----------------------------------------------------------------------------
bool AVIWriter::closeFile(bool delete_file, bool exiting)
{
if (m_file == NULL)
return false;
if (delete_file)
goto error;
// add the index
int idx_start; idx_start = ftell(m_file);
if (idx_start < 0)
goto error;
CHUNK chunk;
chunk.fcc = FOURCC('i', 'd', 'x', '1');
chunk.cb = sizeof(AVIINDEXENTRY) * m_total_frames;
int num; num = fwrite(&chunk, 1, sizeof(chunk), m_file);
if (num != sizeof(chunk))
goto error;
for (unsigned int i = 0; i < m_total_frames; i++)
{
AVIINDEXENTRY Index;
Index.ckid = m_index_table[i].fcc;
Index.dwFlags = AVIIF_KEYFRAME;
Index.dwChunkOffset = m_index_table[i].Offset;
Index.dwChunkLength = m_index_table[i].Length;
num = fwrite(&Index, 1, sizeof(Index), m_file);
if (num != sizeof(Index))
goto error;
}
// update the header
if (m_total_frames > 0 && m_msec_per_frame > 0)
{
num = fseek(m_file, 0, SEEK_END);
if (num < 0)
goto error;
int size; size = ftell(m_file);
if (size < 0)
goto error;
num = fseek(m_file, 0, SEEK_SET);
if (num < 0)
goto error;
m_avi_hdr.riff.cb = size - sizeof(m_avi_hdr.riff);
m_avi_hdr.avih.dwMaxBytesPerSec = (uint32_t)
(((m_stream_bytes / m_total_frames) * m_format_hdr.strh.dwRate) /
m_msec_per_frame + 0.5f);
m_avi_hdr.avih.dwTotalFrames = m_total_frames;
num = fwrite(&m_avi_hdr, 1, sizeof(m_avi_hdr), m_file);
if (num != sizeof(m_avi_hdr))
goto error;
m_format_hdr.strh.dwLength = m_total_frames;
num = fwrite(&m_format_hdr, 1, sizeof(m_format_hdr), m_file);
if (num != sizeof(m_format_hdr))
goto error;
}
// update the movi section
m_movi_chunk.cb = idx_start - m_movi_start;
num = fseek(m_file, m_movi_start - sizeof(m_movi_chunk), SEEK_SET);
if (num < 0)
goto error;
num = fwrite(&m_movi_chunk, 1, sizeof(m_movi_chunk), m_file);
if (num != sizeof(m_movi_chunk))
goto error;
fclose(m_file);
m_file = NULL;
if (!exiting)
{
MessageQueue::add(MessageQueue::MT_GENERIC,
_("Video saved in \"%s\".", m_filename.c_str()));
}
return true;
error:
if (!exiting)
{
MessageQueue::add(MessageQueue::MT_ERROR,
_("Error when saving video."));
}
fclose(m_file);
remove(m_filename.c_str());
m_file = NULL;
return false;
} // closeFile
// ----------------------------------------------------------------------------
bool AVIWriter::createFile()
{
time_t rawtime;
time(&rawtime);
tm* timeInfo = localtime(&rawtime);
char time_buffer[256];
sprintf(time_buffer, "%i.%02i.%02i_%02i.%02i.%02i",
timeInfo->tm_year + 1900, timeInfo->tm_mon + 1,
timeInfo->tm_mday, timeInfo->tm_hour,
timeInfo->tm_min, timeInfo->tm_sec);
m_filename = m_recording_target.getAtomic() + "-" + time_buffer + ".avi";
m_stream_bytes = 0;
m_total_frames = 0;
m_movi_start = 0;
m_last_junk_chunk = 0;
BitmapInfoHeader bitmap_hdr;
bitmap_hdr.biSize = sizeof(BitmapInfoHeader);
bitmap_hdr.biWidth = m_width;
bitmap_hdr.biHeight = m_height;
bitmap_hdr.biPlanes = 1;
bitmap_hdr.biBitCount = 24;
bitmap_hdr.biCompression = 0;
bitmap_hdr.biSizeImage = (m_width * m_height * 3 * bitmap_hdr.biPlanes);
bitmap_hdr.biXPelsPerMeter = 0;
bitmap_hdr.biYPelsPerMeter = 0;
bitmap_hdr.biClrUsed = 0;
bitmap_hdr.biClrImportant = 0;
memset(&m_avi_hdr, '\0', sizeof(m_avi_hdr));
m_avi_hdr.riff.fcc = FOURCC('R', 'I', 'F', 'F');
m_avi_hdr.riff.cb = 0; // update when finished (size of the file - 8)
m_avi_hdr.avi = FOURCC('A', 'V', 'I', ' ');
m_avi_hdr.list1.fcc = FOURCC('L', 'I', 'S', 'T');
m_avi_hdr.list1.cb = 0;
m_avi_hdr.hdrl = FOURCC('h', 'd', 'r', 'l');
m_avi_hdr.avihhdr.fcc = FOURCC('a', 'v', 'i', 'h');
m_avi_hdr.avihhdr.cb = sizeof(m_avi_hdr.avih);
m_avi_hdr.avih.dwMicroSecPerFrame = m_msec_per_frame * 1000;
m_avi_hdr.avih.dwMaxBytesPerSec = 0; // update when finished
m_avi_hdr.avih.dwPaddingGranularity = 0;
m_avi_hdr.avih.dwFlags = AVIF_WASCAPTUREFILE | AVIF_HASINDEX;
m_avi_hdr.avih.dwTotalFrames = 0; // update when finished
m_avi_hdr.avih.dwInitialFrames = 0;
m_avi_hdr.avih.dwStreams = 1; // 1 = video, 2 = video and audio
m_avi_hdr.avih.dwSuggestedBufferSize = 0; // can be just 0
m_avi_hdr.avih.dwWidth = m_width;
m_avi_hdr.avih.dwHeight = m_height;
m_format_hdr.list.fcc = FOURCC('L', 'I', 'S', 'T');
m_format_hdr.list.cb = (sizeof(m_format_hdr) - 8) +
sizeof(BitmapInfoHeader);
m_format_hdr.strl = FOURCC('s', 't', 'r', 'l');
m_format_hdr.strhhdr.fcc = FOURCC('s', 't', 'r', 'h');
m_format_hdr.strhhdr.cb = sizeof(m_format_hdr.strh);
m_format_hdr.strh.fccType = FOURCC('v', 'i', 'd', 's');
m_format_hdr.strh.fccHandler = CC_DIB;
m_format_hdr.strh.dwFlags = 0;
m_format_hdr.strh.wPriority = 0;
m_format_hdr.strh.wLanguage = 0;
m_format_hdr.strh.dwInitialFrames = 0;
m_format_hdr.strh.dwScale = m_msec_per_frame;
m_format_hdr.strh.dwRate = 1000;
m_format_hdr.strh.dwStart = 0;
m_format_hdr.strh.dwLength = 0; // update when finished
m_format_hdr.strh.dwSuggestedBufferSize = 0; // can be just 0
m_format_hdr.strh.dwQuality = m_img_quality * 100;
m_format_hdr.strh.dwSampleSize = 0;
m_format_hdr.strh.Left = 0;
m_format_hdr.strh.Top = 0;
m_format_hdr.strh.Right = m_avi_hdr.avih.dwWidth;
m_format_hdr.strh.Bottom = m_avi_hdr.avih.dwHeight;
m_format_hdr.strfhdr.fcc = FOURCC('s', 't', 'r', 'f');
m_format_hdr.strfhdr.cb = sizeof(BitmapInfoHeader);
// Format specific changes
if (m_avi_format == AVI_FORMAT_JPG)
{
m_format_hdr.strh.fccHandler = CC_MJPG;
bitmap_hdr.biCompression = FOURCC('M', 'J', 'P', 'G');
m_chunk_fcc = FOURCC('0', '0', 'd', 'c');
}
else if (m_avi_format == AVI_FORMAT_BMP)
{
bitmap_hdr.biHeight = -m_height;
bitmap_hdr.biCompression = 0;
m_chunk_fcc = FOURCC('0', '0', 'd', 'b');
}
const uint32_t fcc_movi = FOURCC('m', 'o', 'v', 'i');
m_file = fopen(m_filename.c_str(), "wb");
if (m_file == NULL)
return false;
int num = fwrite(&m_avi_hdr, 1, sizeof(m_avi_hdr), m_file);
if (num != sizeof(m_avi_hdr))
goto error;
num = fwrite(&m_format_hdr, 1, sizeof(m_format_hdr), m_file);
if (num != sizeof(m_format_hdr))
goto error;
num = fwrite(&bitmap_hdr, 1, sizeof(BitmapInfoHeader), m_file);
if (num != sizeof(BitmapInfoHeader))
goto error;
m_end_of_header = ftell(m_file);
if (m_end_of_header < 0)
goto error;
if (!addJUNKChunk("", 2840))
goto error;
m_avi_hdr.list1.cb = m_end_of_header - sizeof(m_avi_hdr.riff) -
sizeof(m_avi_hdr.avi) - sizeof(m_avi_hdr.list1);
m_movi_chunk.fcc = FOURCC('L', 'I', 'S', 'T');
m_movi_chunk.cb = 0; // update when finished
num = fwrite(&m_movi_chunk, 1, sizeof(m_movi_chunk), m_file);
if (num != sizeof(m_movi_chunk))
goto error;
m_movi_start = ftell(m_file);
if (m_movi_start < 0)
goto error;
num = fwrite(&fcc_movi, 1, sizeof(fcc_movi), m_file);
if (num != sizeof(fcc_movi))
goto error;
return true;
error:
closeFile(true/*delete_file*/);
return false;
} // createFile
// ----------------------------------------------------------------------------
int AVIWriter::bmpToJpg(unsigned char* image_data, unsigned char* image_output,
unsigned long buf_length)
{
struct jpeg_compress_struct cinfo;
struct jpeg_error_mgr jerr;
cinfo.err = jpeg_std_error(&jerr);
jpeg_create_compress(&cinfo);
cinfo.image_width = m_width;
cinfo.image_height = m_height;
cinfo.input_components = 3;
cinfo.in_color_space = JCS_RGB;
jpeg_set_defaults(&cinfo);
jpeg_set_quality(&cinfo, m_img_quality, true);
jpeg_mem_dest(&cinfo, &image_output, &buf_length);
jpeg_start_compress(&cinfo, true);
JSAMPROW jrow[1];
while (cinfo.next_scanline < cinfo.image_height)
{
jrow[0] = &image_data[cinfo.next_scanline * m_width * 3];
jpeg_write_scanlines(&cinfo, jrow, 1);
}
jpeg_finish_compress(&cinfo);
jpeg_destroy_compress(&cinfo);
return buf_length;
} // bmpToJpg
#endif

View File

@ -1,265 +0,0 @@
//
// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2015 Dawid Gan
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 3
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#if !(defined(SERVER_ONLY) || defined(USE_GLES2))
#include "graphics/gl_headers.hpp"
#include "utils/can_be_deleted.hpp"
#include "utils/no_copy.hpp"
#include "utils/singleton.hpp"
#include "utils/synchronised.hpp"
#include <chrono>
#include <string>
#include <list>
#include <pthread.h>
#define FOURCC(a,b,c,d) ((uint32_t) (((d)<<24) | ((c)<<16) | ((b)<<8) | (a)))
const uint32_t CC_MJPG = FOURCC('m', 'j', 'p', 'g');
const uint32_t CC_DIB = FOURCC('\0', '\0', '\0', '\0');
const uint32_t CC_VIDS = FOURCC('v', 'i', 'd', 's');
const uint32_t AVIF_HASINDEX = 0x00000010;
const uint32_t AVIF_MUSTUSEINDEX = 0x00000020;
const uint32_t AVIF_ISINTERLEAVED = 0x00000100;
const uint32_t AVIF_TRUSTCKTYPE = 0x00000800;
const uint32_t AVIF_WASCAPTUREFILE = 0x00010000;
const uint32_t AVIF_COPYRIGHTED = 0x00020000;
const uint32_t AVISF_DISABLED = 0x00000001;
const uint32_t AVISF_VIDEO_PALCHANGES = 0x00010000;
const uint32_t AVIIF_LIST = 0x00000001;
const uint32_t AVIIF_KEYFRAME = 0x00000010;
const uint32_t AVIIF_FIRSTPART = 0x00000020;
const uint32_t AVIIF_LASTPART = 0x00000040;
const uint32_t AVIIF_MIDPART = 0x00000060;
const uint32_t AVIIF_NOTIME = 0x00000100;
const uint32_t AVIIF_COMPUSE = 0x0FFF0000;
enum AVIFormat
{
AVI_FORMAT_BMP,
AVI_FORMAT_JPG
};
enum AVIErrCode
{
AVI_SUCCESS,
AVI_SIZE_LIMIT_ERR,
AVI_IO_ERR
};
const int MAX_FRAMES = 1000000;
const int MAX_FILE_SIZE = 2000000000;
struct MainAVIHeader
{
uint32_t dwMicroSecPerFrame;
uint32_t dwMaxBytesPerSec;
uint32_t dwPaddingGranularity;
uint32_t dwFlags;
uint32_t dwTotalFrames;
uint32_t dwInitialFrames;
uint32_t dwStreams;
uint32_t dwSuggestedBufferSize;
uint32_t dwWidth;
uint32_t dwHeight;
uint32_t dwReserved[4];
};
struct AVIStreamHeader
{
uint32_t fccType;
uint32_t fccHandler;
uint32_t dwFlags;
uint16_t wPriority;
uint16_t wLanguage;
uint32_t dwInitialFrames;
uint32_t dwScale;
uint32_t dwRate;
uint32_t dwStart;
uint32_t dwLength;
uint32_t dwSuggestedBufferSize;
uint32_t dwQuality;
uint32_t dwSampleSize;
uint16_t Left;
uint16_t Top;
uint16_t Right;
uint16_t Bottom;
};
struct BitmapInfoHeader
{
uint32_t biSize;
uint32_t biWidth;
uint32_t biHeight;
uint16_t biPlanes;
uint16_t biBitCount;
uint32_t biCompression;
uint32_t biSizeImage;
uint32_t biXPelsPerMeter;
uint32_t biYPelsPerMeter;
uint32_t biClrUsed;
uint32_t biClrImportant;
};
struct AVIINDEXENTRY
{
uint32_t ckid;
uint32_t dwFlags;
uint32_t dwChunkOffset;
uint32_t dwChunkLength;
};
struct CHUNK
{
uint32_t fcc;
uint32_t cb;
};
struct AVIHeader
{
CHUNK riff;
uint32_t avi;
CHUNK list1;
uint32_t hdrl;
CHUNK avihhdr;
MainAVIHeader avih;
};
struct FormatHeader
{
CHUNK list;
uint32_t strl;
CHUNK strhhdr;
AVIStreamHeader strh;
CHUNK strfhdr;
};
struct IndexTable
{
uint32_t Offset;
uint32_t Length;
uint32_t fcc;
};
class AVIWriter : public CanBeDeleted, public NoCopy,
public Singleton<AVIWriter>
{
private:
FILE* m_file;
static Synchronised<std::string> m_recording_target;
std::string m_filename;
int m_last_junk_chunk, m_end_of_header, m_movi_start, m_img_quality,
m_width, m_height;
unsigned int m_msec_per_frame, m_stream_bytes, m_total_frames, m_pbo_use;
double m_accumulated_time;
AVIFormat m_avi_format;
AVIHeader m_avi_hdr;
CHUNK m_movi_chunk;
FormatHeader m_format_hdr;
IndexTable m_index_table[MAX_FRAMES];
uint32_t m_chunk_fcc;
Synchronised<std::list<std::pair<uint8_t*, int> > > m_fbi_queue;
Synchronised<bool> m_idle;
pthread_t m_record_thread;
pthread_cond_t m_cond_request;
GLuint m_pbo[3];
std::chrono::high_resolution_clock::time_point m_framerate_timer;
// ------------------------------------------------------------------------
int bmpToJpg(unsigned char* image_data, unsigned char* image_output,
unsigned long buf_length);
// ------------------------------------------------------------------------
AVIErrCode addImage(unsigned char* buffer, int size);
// ------------------------------------------------------------------------
bool closeFile(bool delete_file = false, bool exiting = false);
// ------------------------------------------------------------------------
bool createFile();
// ------------------------------------------------------------------------
bool addJUNKChunk(std::string str, unsigned int min_size);
// ------------------------------------------------------------------------
void addFrameBufferImage(uint8_t* fbi, int frame_count)
{
m_fbi_queue.lock();
m_fbi_queue.getData().emplace_back(fbi, frame_count);
pthread_cond_signal(&m_cond_request);
m_fbi_queue.unlock();
}
// ------------------------------------------------------------------------
int getFrameCount(double rate);
// ------------------------------------------------------------------------
void cleanAllFrameBufferImages()
{
m_fbi_queue.lock();
for (auto& p : m_fbi_queue.getData())
delete [] p.first;
m_fbi_queue.getData().clear();
m_fbi_queue.unlock();
}
// ------------------------------------------------------------------------
bool isIdle() const { return m_idle.getAtomic(); }
public:
// ------------------------------------------------------------------------
AVIWriter();
// ------------------------------------------------------------------------
~AVIWriter();
// ------------------------------------------------------------------------
static void setRecordingTarget(const std::string& name)
{
m_recording_target.setAtomic(name);
}
// ------------------------------------------------------------------------
static std::string getRecordingTarget()
{
return m_recording_target.getAtomic();
}
// ------------------------------------------------------------------------
void captureFrameBufferImage();
// ------------------------------------------------------------------------
void resetFrameBufferImage();
// ------------------------------------------------------------------------
void resetCaptureFormat();
// ------------------------------------------------------------------------
void stopRecording() { addFrameBufferImage(NULL, -1); }
};
#endif