diff --git a/CMakeLists.txt b/CMakeLists.txt index 9a9250b53..63a4de331 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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() diff --git a/lib/libwebm/mkvmuxer/mkvmuxer.cc b/lib/libwebm/mkvmuxer/mkvmuxer.cc index c400eb777..953ef0a88 100644 --- a/lib/libwebm/mkvmuxer/mkvmuxer.cc +++ b/lib/libwebm/mkvmuxer/mkvmuxer.cc @@ -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; + return false; } } // namespace mkvmuxer diff --git a/src/recorder/jpg_writer.cpp b/src/recorder/jpg_writer.cpp new file mode 100644 index 000000000..7e91af25c --- /dev/null +++ b/src/recorder/jpg_writer.cpp @@ -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 + +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 > >* + jpg_data = (Synchronised > >*)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 diff --git a/src/recorder/jpg_writer.hpp b/src/recorder/jpg_writer.hpp new file mode 100644 index 000000000..25fef6b57 --- /dev/null +++ b/src/recorder/jpg_writer.hpp @@ -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 diff --git a/src/recorder/webm_writer.cpp b/src/recorder/mkv_writer.cpp similarity index 75% rename from src/recorder/webm_writer.cpp rename to src/recorder/mkv_writer.cpp index 474b262bf..97a7858e9 100644 --- a/src/recorder/webm_writer.cpp +++ b/src/recorder/mkv_writer.cpp @@ -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 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 (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( 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); - muxer_frame.set_is_key((flag & VPX_FRAME_IS_KEY) != 0); + 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 diff --git a/src/recorder/webm_writer.hpp b/src/recorder/mkv_writer.hpp similarity index 87% rename from src/recorder/webm_writer.hpp rename to src/recorder/mkv_writer.hpp index 7108e3b2b..1868ec2bc 100644 --- a/src/recorder/webm_writer.hpp +++ b/src/recorder/mkv_writer.hpp @@ -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 namespace Recorder { - void writeWebm(const std::string& video, const std::string& audio); + void writeMKV(const std::string& video, const std::string& audio); }; #endif diff --git a/src/recorder/recorder_common.cpp b/src/recorder/recorder_common.cpp index 7cd80b00f..7fbc93e0b 100644 --- a/src/recorder/recorder_common.cpp +++ b/src/recorder/recorder_common.cpp @@ -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) diff --git a/src/recorder/vpx_encoder.cpp b/src/recorder/vpx_encoder.cpp index 3d8cc390e..dbe944228 100644 --- a/src/recorder/vpx_encoder.cpp +++ b/src/recorder/vpx_encoder.cpp @@ -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; diff --git a/src/recorder/vpx_encoder.hpp b/src/recorder/vpx_encoder.hpp index 74235c961..4fefc8c33 100644 --- a/src/recorder/vpx_encoder.hpp +++ b/src/recorder/vpx_encoder.hpp @@ -21,10 +21,6 @@ #ifndef HEADER_VPX_ENCODER_HPP #define HEADER_VPX_ENCODER_HPP -#include "utils/no_copy.hpp" - -#include - namespace Recorder { void* vpxEncoder(void *obj); diff --git a/src/utils/avi_writer.cpp b/src/utils/avi_writer.cpp deleted file mode 100644 index a2e9e8602..000000000 --- a/src/utils/avi_writer.cpp +++ /dev/null @@ -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 -#include -#include - -#include -#include - -Synchronised 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 diff --git a/src/utils/avi_writer.hpp b/src/utils/avi_writer.hpp deleted file mode 100644 index 240eed089..000000000 --- a/src/utils/avi_writer.hpp +++ /dev/null @@ -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 -#include -#include - -#include - -#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 -{ -private: - FILE* m_file; - - static Synchronised 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 > > m_fbi_queue; - - Synchronised 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