Allow muxing mjpeg with vorbis audio with mkv
Not sure if playable in with all players
This commit is contained in:
parent
b0d0a0739e
commit
b7c709c709
@ -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()
|
||||
|
@ -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
|
||||
|
87
src/recorder/jpg_writer.cpp
Normal file
87
src/recorder/jpg_writer.cpp
Normal 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
|
30
src/recorder/jpg_writer.hpp
Normal file
30
src/recorder/jpg_writer.hpp
Normal 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
|
@ -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
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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
|
@ -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
|
Loading…
Reference in New Issue
Block a user