Merge remote-tracking branch 'origin/libopenglrecorder'
This commit is contained in:
commit
fd07df4251
@ -32,12 +32,8 @@ addons:
|
||||
- libjpeg-dev
|
||||
- libogg-dev
|
||||
- libopenal-dev
|
||||
- libpulse-dev
|
||||
- libpng-dev
|
||||
- libturbojpeg
|
||||
- libvorbis-dev
|
||||
- libvorbisenc2
|
||||
- libvpx-dev
|
||||
- libxrandr-dev
|
||||
- mesa-common-dev
|
||||
- pkg-config
|
||||
@ -57,7 +53,7 @@ before_script:
|
||||
script:
|
||||
- mkdir "build"
|
||||
- cd "build"
|
||||
- cmake .. -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DSERVER_ONLY=$SERVER_ONLY -DCHECK_ASSETS=off -DDISABLE_VPX=on
|
||||
- cmake .. -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DSERVER_ONLY=$SERVER_ONLY -DCHECK_ASSETS=off -DBUILD_RECORDER=off
|
||||
- make VERBOSE=1 -j $THREADS
|
||||
|
||||
notifications:
|
||||
|
@ -24,10 +24,6 @@ option(ENABLE_NETWORK_MULTIPLAYER "Enable network multiplayer. This will replace
|
||||
|
||||
CMAKE_DEPENDENT_OPTION(BUILD_RECORDER "Build opengl recorder" ON
|
||||
"NOT SERVER_ONLY;NOT USE_GLES2;NOT APPLE" OFF)
|
||||
CMAKE_DEPENDENT_OPTION(BUILD_RECORDER_WITH_SOUND "Build opengl recorder with sound" ON
|
||||
BUILD_RECORDER OFF)
|
||||
CMAKE_DEPENDENT_OPTION(BUILD_PULSE_WO_DL "If pulseaudio in your distro / system is optional, turn this off to load pulse with libdl"
|
||||
ON "BUILD_RECORDER_WITH_SOUND;UNIX" OFF)
|
||||
|
||||
if (UNIX AND NOT APPLE)
|
||||
option(USE_GLES2 "Use OpenGL ES2 renderer" OFF)
|
||||
@ -88,12 +84,6 @@ endif()
|
||||
|
||||
if(BUILD_RECORDER)
|
||||
add_definitions(-DENABLE_RECORDER)
|
||||
if(BUILD_RECORDER_WITH_SOUND)
|
||||
add_definitions(-DENABLE_REC_SOUND)
|
||||
if(BUILD_PULSE_WO_DL)
|
||||
add_definitions(-DENABLE_PULSE_WO_DL)
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Build the Bullet physics library
|
||||
@ -144,23 +134,15 @@ else()
|
||||
include_directories(${JPEG_INCLUDE_DIR})
|
||||
endif()
|
||||
|
||||
if(BUILD_RECORDER)
|
||||
find_library(TURBOJPEG_LIBRARY NAMES turbojpeg libturbojpeg PATHS "${PROJECT_SOURCE_DIR}/dependencies/lib")
|
||||
mark_as_advanced(TURBOJPEG_LIBRARY)
|
||||
if(UNIX)
|
||||
include(FindPkgConfig)
|
||||
pkg_check_modules(VPX vpx)
|
||||
else()
|
||||
find_path(VPX_INCLUDEDIR NAMES vpx/vpx_codec.h PATHS "${PROJECT_SOURCE_DIR}/dependencies/include")
|
||||
find_library(VPX_LIBRARIES NAMES libvpx PATHS "${PROJECT_SOURCE_DIR}/dependencies/lib")
|
||||
if (BUILD_RECORDER)
|
||||
find_library(OPENGLRECORDER_LIBRARY NAMES openglrecorder libopenglrecorder PATHS "${PROJECT_SOURCE_DIR}/dependencies/lib")
|
||||
find_path(OPENGLRECORDER_INCLUDEDIR NAMES openglrecorder.h PATHS "${PROJECT_SOURCE_DIR}/dependencies/include")
|
||||
if (NOT OPENGLRECORDER_LIBRARY OR NOT OPENGLRECORDER_INCLUDEDIR)
|
||||
message(FATAL_ERROR "libopenglrecorder not found. "
|
||||
"Either install libopenglrecorder or disable ingame recorder with -DBUILD_RECORDER=0")
|
||||
endif()
|
||||
include_directories(${VPX_INCLUDEDIR})
|
||||
if(BUILD_RECORDER_WITH_SOUND AND UNIX)
|
||||
pkg_check_modules(PULSEAUDIO libpulse)
|
||||
include_directories(${PULSEAUDIO_INCLUDEDIR})
|
||||
endif()
|
||||
add_subdirectory("${PROJECT_SOURCE_DIR}/lib/libwebm")
|
||||
include_directories("${PROJECT_SOURCE_DIR}/lib/libwebm")
|
||||
include_directories(${OPENGLRECORDER_INCLUDEDIR})
|
||||
mark_as_advanced(OPENGLRECORDER_LIBRARY OPENGLRECORDER_INCLUDEDIR)
|
||||
endif()
|
||||
|
||||
if(NOT SERVER_ONLY AND NOT USE_GLES2)
|
||||
@ -447,14 +429,7 @@ if(UNIX AND NOT APPLE)
|
||||
endif()
|
||||
|
||||
if(BUILD_RECORDER)
|
||||
target_link_libraries(supertuxkart webm ${TURBOJPEG_LIBRARY} ${VPX_LIBRARIES})
|
||||
if(BUILD_RECORDER_WITH_SOUND AND UNIX)
|
||||
if(BUILD_PULSE_WO_DL)
|
||||
target_link_libraries(supertuxkart ${PULSEAUDIO_LIBRARIES})
|
||||
else()
|
||||
target_link_libraries(supertuxkart dl)
|
||||
endif()
|
||||
endif()
|
||||
target_link_libraries(supertuxkart ${OPENGLRECORDER_LIBRARY})
|
||||
endif()
|
||||
|
||||
# FreeBSD does not search in /usr/local/lib, but at least Freetype is installed there :(
|
||||
|
@ -60,6 +60,12 @@ libcurl4-gnutls-dev libfreetype6-dev libfribidi-dev libgl1-mesa-dev \
|
||||
libjpeg-dev libogg-dev libopenal-dev libpng-dev libvorbis-dev libxrandr-dev \
|
||||
mesa-common-dev pkg-config zlib1g-dev
|
||||
```
|
||||
### In-game recorder
|
||||
|
||||
In order to build the in-game recorder for STK, you have to install
|
||||
libopenglrecorder from your distribution or compile it yourself, get it [here](https://github.com/Benau/libopenglrecorder):
|
||||
Compilation instruction is explained there, if you don't need such feature,
|
||||
pass `-DBUILD_RECORDER=off` to cmake.
|
||||
|
||||
### Compiling
|
||||
|
||||
|
@ -1,10 +0,0 @@
|
||||
cmake_minimum_required(VERSION 2.6)
|
||||
if (UNIX OR MINGW)
|
||||
add_definitions(-std=gnu++0x -O3)
|
||||
endif()
|
||||
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
|
||||
add_library(webm STATIC
|
||||
mkvmuxer/mkvmuxer.cc
|
||||
mkvmuxer/mkvmuxerutil.cc
|
||||
mkvmuxer/mkvwriter.cc
|
||||
)
|
@ -1,30 +0,0 @@
|
||||
Copyright (c) 2010, Google Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in
|
||||
the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
|
||||
* Neither the name of Google nor the names of its contributors may
|
||||
be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
@ -1,192 +0,0 @@
|
||||
// Copyright (c) 2012 The WebM project authors. All Rights Reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style license
|
||||
// that can be found in the LICENSE file in the root of the source
|
||||
// tree. An additional intellectual property rights grant can be found
|
||||
// in the file PATENTS. All contributing project authors may
|
||||
// be found in the AUTHORS file in the root of the source tree.
|
||||
|
||||
#ifndef COMMON_WEBMIDS_H_
|
||||
#define COMMON_WEBMIDS_H_
|
||||
|
||||
namespace libwebm {
|
||||
|
||||
enum MkvId {
|
||||
kMkvEBML = 0x1A45DFA3,
|
||||
kMkvEBMLVersion = 0x4286,
|
||||
kMkvEBMLReadVersion = 0x42F7,
|
||||
kMkvEBMLMaxIDLength = 0x42F2,
|
||||
kMkvEBMLMaxSizeLength = 0x42F3,
|
||||
kMkvDocType = 0x4282,
|
||||
kMkvDocTypeVersion = 0x4287,
|
||||
kMkvDocTypeReadVersion = 0x4285,
|
||||
kMkvVoid = 0xEC,
|
||||
kMkvSignatureSlot = 0x1B538667,
|
||||
kMkvSignatureAlgo = 0x7E8A,
|
||||
kMkvSignatureHash = 0x7E9A,
|
||||
kMkvSignaturePublicKey = 0x7EA5,
|
||||
kMkvSignature = 0x7EB5,
|
||||
kMkvSignatureElements = 0x7E5B,
|
||||
kMkvSignatureElementList = 0x7E7B,
|
||||
kMkvSignedElement = 0x6532,
|
||||
// segment
|
||||
kMkvSegment = 0x18538067,
|
||||
// Meta Seek Information
|
||||
kMkvSeekHead = 0x114D9B74,
|
||||
kMkvSeek = 0x4DBB,
|
||||
kMkvSeekID = 0x53AB,
|
||||
kMkvSeekPosition = 0x53AC,
|
||||
// Segment Information
|
||||
kMkvInfo = 0x1549A966,
|
||||
kMkvTimecodeScale = 0x2AD7B1,
|
||||
kMkvDuration = 0x4489,
|
||||
kMkvDateUTC = 0x4461,
|
||||
kMkvTitle = 0x7BA9,
|
||||
kMkvMuxingApp = 0x4D80,
|
||||
kMkvWritingApp = 0x5741,
|
||||
// Cluster
|
||||
kMkvCluster = 0x1F43B675,
|
||||
kMkvTimecode = 0xE7,
|
||||
kMkvPrevSize = 0xAB,
|
||||
kMkvBlockGroup = 0xA0,
|
||||
kMkvBlock = 0xA1,
|
||||
kMkvBlockDuration = 0x9B,
|
||||
kMkvReferenceBlock = 0xFB,
|
||||
kMkvLaceNumber = 0xCC,
|
||||
kMkvSimpleBlock = 0xA3,
|
||||
kMkvBlockAdditions = 0x75A1,
|
||||
kMkvBlockMore = 0xA6,
|
||||
kMkvBlockAddID = 0xEE,
|
||||
kMkvBlockAdditional = 0xA5,
|
||||
kMkvDiscardPadding = 0x75A2,
|
||||
// Track
|
||||
kMkvTracks = 0x1654AE6B,
|
||||
kMkvTrackEntry = 0xAE,
|
||||
kMkvTrackNumber = 0xD7,
|
||||
kMkvTrackUID = 0x73C5,
|
||||
kMkvTrackType = 0x83,
|
||||
kMkvFlagEnabled = 0xB9,
|
||||
kMkvFlagDefault = 0x88,
|
||||
kMkvFlagForced = 0x55AA,
|
||||
kMkvFlagLacing = 0x9C,
|
||||
kMkvDefaultDuration = 0x23E383,
|
||||
kMkvMaxBlockAdditionID = 0x55EE,
|
||||
kMkvName = 0x536E,
|
||||
kMkvLanguage = 0x22B59C,
|
||||
kMkvCodecID = 0x86,
|
||||
kMkvCodecPrivate = 0x63A2,
|
||||
kMkvCodecName = 0x258688,
|
||||
kMkvCodecDelay = 0x56AA,
|
||||
kMkvSeekPreRoll = 0x56BB,
|
||||
// video
|
||||
kMkvVideo = 0xE0,
|
||||
kMkvFlagInterlaced = 0x9A,
|
||||
kMkvStereoMode = 0x53B8,
|
||||
kMkvAlphaMode = 0x53C0,
|
||||
kMkvPixelWidth = 0xB0,
|
||||
kMkvPixelHeight = 0xBA,
|
||||
kMkvPixelCropBottom = 0x54AA,
|
||||
kMkvPixelCropTop = 0x54BB,
|
||||
kMkvPixelCropLeft = 0x54CC,
|
||||
kMkvPixelCropRight = 0x54DD,
|
||||
kMkvDisplayWidth = 0x54B0,
|
||||
kMkvDisplayHeight = 0x54BA,
|
||||
kMkvDisplayUnit = 0x54B2,
|
||||
kMkvAspectRatioType = 0x54B3,
|
||||
kMkvFrameRate = 0x2383E3,
|
||||
// end video
|
||||
// colour
|
||||
kMkvColour = 0x55B0,
|
||||
kMkvMatrixCoefficients = 0x55B1,
|
||||
kMkvBitsPerChannel = 0x55B2,
|
||||
kMkvChromaSubsamplingHorz = 0x55B3,
|
||||
kMkvChromaSubsamplingVert = 0x55B4,
|
||||
kMkvCbSubsamplingHorz = 0x55B5,
|
||||
kMkvCbSubsamplingVert = 0x55B6,
|
||||
kMkvChromaSitingHorz = 0x55B7,
|
||||
kMkvChromaSitingVert = 0x55B8,
|
||||
kMkvRange = 0x55B9,
|
||||
kMkvTransferCharacteristics = 0x55BA,
|
||||
kMkvPrimaries = 0x55BB,
|
||||
kMkvMaxCLL = 0x55BC,
|
||||
kMkvMaxFALL = 0x55BD,
|
||||
// mastering metadata
|
||||
kMkvMasteringMetadata = 0x55D0,
|
||||
kMkvPrimaryRChromaticityX = 0x55D1,
|
||||
kMkvPrimaryRChromaticityY = 0x55D2,
|
||||
kMkvPrimaryGChromaticityX = 0x55D3,
|
||||
kMkvPrimaryGChromaticityY = 0x55D4,
|
||||
kMkvPrimaryBChromaticityX = 0x55D5,
|
||||
kMkvPrimaryBChromaticityY = 0x55D6,
|
||||
kMkvWhitePointChromaticityX = 0x55D7,
|
||||
kMkvWhitePointChromaticityY = 0x55D8,
|
||||
kMkvLuminanceMax = 0x55D9,
|
||||
kMkvLuminanceMin = 0x55DA,
|
||||
// end mastering metadata
|
||||
// end colour
|
||||
// projection
|
||||
kMkvProjection = 0x7670,
|
||||
kMkvProjectionType = 0x7671,
|
||||
kMkvProjectionPrivate = 0x7672,
|
||||
kMkvProjectionPoseYaw = 0x7673,
|
||||
kMkvProjectionPosePitch = 0x7674,
|
||||
kMkvProjectionPoseRoll = 0x7675,
|
||||
// end projection
|
||||
// audio
|
||||
kMkvAudio = 0xE1,
|
||||
kMkvSamplingFrequency = 0xB5,
|
||||
kMkvOutputSamplingFrequency = 0x78B5,
|
||||
kMkvChannels = 0x9F,
|
||||
kMkvBitDepth = 0x6264,
|
||||
// end audio
|
||||
// ContentEncodings
|
||||
kMkvContentEncodings = 0x6D80,
|
||||
kMkvContentEncoding = 0x6240,
|
||||
kMkvContentEncodingOrder = 0x5031,
|
||||
kMkvContentEncodingScope = 0x5032,
|
||||
kMkvContentEncodingType = 0x5033,
|
||||
kMkvContentCompression = 0x5034,
|
||||
kMkvContentCompAlgo = 0x4254,
|
||||
kMkvContentCompSettings = 0x4255,
|
||||
kMkvContentEncryption = 0x5035,
|
||||
kMkvContentEncAlgo = 0x47E1,
|
||||
kMkvContentEncKeyID = 0x47E2,
|
||||
kMkvContentSignature = 0x47E3,
|
||||
kMkvContentSigKeyID = 0x47E4,
|
||||
kMkvContentSigAlgo = 0x47E5,
|
||||
kMkvContentSigHashAlgo = 0x47E6,
|
||||
kMkvContentEncAESSettings = 0x47E7,
|
||||
kMkvAESSettingsCipherMode = 0x47E8,
|
||||
kMkvAESSettingsCipherInitData = 0x47E9,
|
||||
// end ContentEncodings
|
||||
// Cueing Data
|
||||
kMkvCues = 0x1C53BB6B,
|
||||
kMkvCuePoint = 0xBB,
|
||||
kMkvCueTime = 0xB3,
|
||||
kMkvCueTrackPositions = 0xB7,
|
||||
kMkvCueTrack = 0xF7,
|
||||
kMkvCueClusterPosition = 0xF1,
|
||||
kMkvCueBlockNumber = 0x5378,
|
||||
// Chapters
|
||||
kMkvChapters = 0x1043A770,
|
||||
kMkvEditionEntry = 0x45B9,
|
||||
kMkvChapterAtom = 0xB6,
|
||||
kMkvChapterUID = 0x73C4,
|
||||
kMkvChapterStringUID = 0x5654,
|
||||
kMkvChapterTimeStart = 0x91,
|
||||
kMkvChapterTimeEnd = 0x92,
|
||||
kMkvChapterDisplay = 0x80,
|
||||
kMkvChapString = 0x85,
|
||||
kMkvChapLanguage = 0x437C,
|
||||
kMkvChapCountry = 0x437E,
|
||||
// Tags
|
||||
kMkvTags = 0x1254C367,
|
||||
kMkvTag = 0x7373,
|
||||
kMkvSimpleTag = 0x67C8,
|
||||
kMkvTagName = 0x45A3,
|
||||
kMkvTagString = 0x4487
|
||||
};
|
||||
|
||||
} // namespace libwebm
|
||||
|
||||
#endif // COMMON_WEBMIDS_H_
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,28 +0,0 @@
|
||||
// Copyright (c) 2012 The WebM project authors. All Rights Reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style license
|
||||
// that can be found in the LICENSE file in the root of the source
|
||||
// tree. An additional intellectual property rights grant can be found
|
||||
// in the file PATENTS. All contributing project authors may
|
||||
// be found in the AUTHORS file in the root of the source tree.
|
||||
|
||||
#ifndef MKVMUXER_MKVMUXERTYPES_H_
|
||||
#define MKVMUXER_MKVMUXERTYPES_H_
|
||||
|
||||
namespace mkvmuxer {
|
||||
typedef unsigned char uint8;
|
||||
typedef short int16;
|
||||
typedef int int32;
|
||||
typedef unsigned int uint32;
|
||||
typedef long long int64;
|
||||
typedef unsigned long long uint64;
|
||||
} // namespace mkvmuxer
|
||||
|
||||
// Copied from Chromium basictypes.h
|
||||
// A macro to disallow the copy constructor and operator= functions
|
||||
// This should be used in the private: declarations for a class
|
||||
#define LIBWEBM_DISALLOW_COPY_AND_ASSIGN(TypeName) \
|
||||
TypeName(const TypeName&); \
|
||||
void operator=(const TypeName&)
|
||||
|
||||
#endif // MKVMUXER_MKVMUXERTYPES_HPP_
|
@ -1,743 +0,0 @@
|
||||
// Copyright (c) 2012 The WebM project authors. All Rights Reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style license
|
||||
// that can be found in the LICENSE file in the root of the source
|
||||
// tree. An additional intellectual property rights grant can be found
|
||||
// in the file PATENTS. All contributing project authors may
|
||||
// be found in the AUTHORS file in the root of the source tree.
|
||||
|
||||
#include "mkvmuxer/mkvmuxerutil.h"
|
||||
|
||||
#ifdef __ANDROID__
|
||||
#include <fcntl.h>
|
||||
#endif
|
||||
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <ctime>
|
||||
#include <new>
|
||||
|
||||
#include "common/webmids.h"
|
||||
#include "mkvmuxer/mkvmuxer.h"
|
||||
#include "mkvmuxer/mkvwriter.h"
|
||||
|
||||
namespace mkvmuxer {
|
||||
|
||||
namespace {
|
||||
|
||||
// Date elements are always 8 octets in size.
|
||||
const int kDateElementSize = 8;
|
||||
|
||||
uint64 WriteBlock(IMkvWriter* writer, const Frame* const frame, int64 timecode,
|
||||
uint64 timecode_scale) {
|
||||
uint64 block_additional_elem_size = 0;
|
||||
uint64 block_addid_elem_size = 0;
|
||||
uint64 block_more_payload_size = 0;
|
||||
uint64 block_more_elem_size = 0;
|
||||
uint64 block_additions_payload_size = 0;
|
||||
uint64 block_additions_elem_size = 0;
|
||||
if (frame->additional()) {
|
||||
block_additional_elem_size =
|
||||
EbmlElementSize(libwebm::kMkvBlockAdditional, frame->additional(),
|
||||
frame->additional_length());
|
||||
block_addid_elem_size = EbmlElementSize(
|
||||
libwebm::kMkvBlockAddID, static_cast<uint64>(frame->add_id()));
|
||||
|
||||
block_more_payload_size =
|
||||
block_addid_elem_size + block_additional_elem_size;
|
||||
block_more_elem_size =
|
||||
EbmlMasterElementSize(libwebm::kMkvBlockMore, block_more_payload_size) +
|
||||
block_more_payload_size;
|
||||
block_additions_payload_size = block_more_elem_size;
|
||||
block_additions_elem_size =
|
||||
EbmlMasterElementSize(libwebm::kMkvBlockAdditions,
|
||||
block_additions_payload_size) +
|
||||
block_additions_payload_size;
|
||||
}
|
||||
|
||||
uint64 discard_padding_elem_size = 0;
|
||||
if (frame->discard_padding() != 0) {
|
||||
discard_padding_elem_size =
|
||||
EbmlElementSize(libwebm::kMkvDiscardPadding,
|
||||
static_cast<int64>(frame->discard_padding()));
|
||||
}
|
||||
|
||||
const uint64 reference_block_timestamp =
|
||||
frame->reference_block_timestamp() / timecode_scale;
|
||||
uint64 reference_block_elem_size = 0;
|
||||
if (!frame->is_key()) {
|
||||
reference_block_elem_size =
|
||||
EbmlElementSize(libwebm::kMkvReferenceBlock, reference_block_timestamp);
|
||||
}
|
||||
|
||||
const uint64 duration = frame->duration() / timecode_scale;
|
||||
uint64 block_duration_elem_size = 0;
|
||||
if (duration > 0)
|
||||
block_duration_elem_size =
|
||||
EbmlElementSize(libwebm::kMkvBlockDuration, duration);
|
||||
|
||||
const uint64 block_payload_size = 4 + frame->length();
|
||||
const uint64 block_elem_size =
|
||||
EbmlMasterElementSize(libwebm::kMkvBlock, block_payload_size) +
|
||||
block_payload_size;
|
||||
|
||||
const uint64 block_group_payload_size =
|
||||
block_elem_size + block_additions_elem_size + block_duration_elem_size +
|
||||
discard_padding_elem_size + reference_block_elem_size;
|
||||
|
||||
if (!WriteEbmlMasterElement(writer, libwebm::kMkvBlockGroup,
|
||||
block_group_payload_size)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!WriteEbmlMasterElement(writer, libwebm::kMkvBlock, block_payload_size))
|
||||
return 0;
|
||||
|
||||
if (WriteUInt(writer, frame->track_number()))
|
||||
return 0;
|
||||
|
||||
if (SerializeInt(writer, timecode, 2))
|
||||
return 0;
|
||||
|
||||
// For a Block, flags is always 0.
|
||||
if (SerializeInt(writer, 0, 1))
|
||||
return 0;
|
||||
|
||||
if (writer->Write(frame->frame(), static_cast<uint32>(frame->length())))
|
||||
return 0;
|
||||
|
||||
if (frame->additional()) {
|
||||
if (!WriteEbmlMasterElement(writer, libwebm::kMkvBlockAdditions,
|
||||
block_additions_payload_size)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!WriteEbmlMasterElement(writer, libwebm::kMkvBlockMore,
|
||||
block_more_payload_size))
|
||||
return 0;
|
||||
|
||||
if (!WriteEbmlElement(writer, libwebm::kMkvBlockAddID,
|
||||
static_cast<uint64>(frame->add_id())))
|
||||
return 0;
|
||||
|
||||
if (!WriteEbmlElement(writer, libwebm::kMkvBlockAdditional,
|
||||
frame->additional(), frame->additional_length())) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (frame->discard_padding() != 0 &&
|
||||
!WriteEbmlElement(writer, libwebm::kMkvDiscardPadding,
|
||||
static_cast<int64>(frame->discard_padding()))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!frame->is_key() &&
|
||||
!WriteEbmlElement(writer, libwebm::kMkvReferenceBlock,
|
||||
reference_block_timestamp)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (duration > 0 &&
|
||||
!WriteEbmlElement(writer, libwebm::kMkvBlockDuration, duration)) {
|
||||
return false;
|
||||
}
|
||||
return EbmlMasterElementSize(libwebm::kMkvBlockGroup,
|
||||
block_group_payload_size) +
|
||||
block_group_payload_size;
|
||||
}
|
||||
|
||||
uint64 WriteSimpleBlock(IMkvWriter* writer, const Frame* const frame,
|
||||
int64 timecode) {
|
||||
if (WriteID(writer, libwebm::kMkvSimpleBlock))
|
||||
return 0;
|
||||
|
||||
const int32 size = static_cast<int32>(frame->length()) + 4;
|
||||
if (WriteUInt(writer, size))
|
||||
return 0;
|
||||
|
||||
if (WriteUInt(writer, static_cast<uint64>(frame->track_number())))
|
||||
return 0;
|
||||
|
||||
if (SerializeInt(writer, timecode, 2))
|
||||
return 0;
|
||||
|
||||
uint64 flags = 0;
|
||||
if (frame->is_key())
|
||||
flags |= 0x80;
|
||||
|
||||
if (SerializeInt(writer, flags, 1))
|
||||
return 0;
|
||||
|
||||
if (writer->Write(frame->frame(), static_cast<uint32>(frame->length())))
|
||||
return 0;
|
||||
|
||||
return GetUIntSize(libwebm::kMkvSimpleBlock) + GetCodedUIntSize(size) + 4 +
|
||||
frame->length();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int32 GetCodedUIntSize(uint64 value) {
|
||||
if (value < 0x000000000000007FULL)
|
||||
return 1;
|
||||
else if (value < 0x0000000000003FFFULL)
|
||||
return 2;
|
||||
else if (value < 0x00000000001FFFFFULL)
|
||||
return 3;
|
||||
else if (value < 0x000000000FFFFFFFULL)
|
||||
return 4;
|
||||
else if (value < 0x00000007FFFFFFFFULL)
|
||||
return 5;
|
||||
else if (value < 0x000003FFFFFFFFFFULL)
|
||||
return 6;
|
||||
else if (value < 0x0001FFFFFFFFFFFFULL)
|
||||
return 7;
|
||||
return 8;
|
||||
}
|
||||
|
||||
int32 GetUIntSize(uint64 value) {
|
||||
if (value < 0x0000000000000100ULL)
|
||||
return 1;
|
||||
else if (value < 0x0000000000010000ULL)
|
||||
return 2;
|
||||
else if (value < 0x0000000001000000ULL)
|
||||
return 3;
|
||||
else if (value < 0x0000000100000000ULL)
|
||||
return 4;
|
||||
else if (value < 0x0000010000000000ULL)
|
||||
return 5;
|
||||
else if (value < 0x0001000000000000ULL)
|
||||
return 6;
|
||||
else if (value < 0x0100000000000000ULL)
|
||||
return 7;
|
||||
return 8;
|
||||
}
|
||||
|
||||
int32 GetIntSize(int64 value) {
|
||||
// Doubling the requested value ensures positive values with their high bit
|
||||
// set are written with 0-padding to avoid flipping the signedness.
|
||||
const uint64 v = (value < 0) ? value ^ -1LL : value;
|
||||
return GetUIntSize(2 * v);
|
||||
}
|
||||
|
||||
uint64 EbmlMasterElementSize(uint64 type, uint64 value) {
|
||||
// Size of EBML ID
|
||||
int32 ebml_size = GetUIntSize(type);
|
||||
|
||||
// Datasize
|
||||
ebml_size += GetCodedUIntSize(value);
|
||||
|
||||
return ebml_size;
|
||||
}
|
||||
|
||||
uint64 EbmlElementSize(uint64 type, int64 value) {
|
||||
// Size of EBML ID
|
||||
int32 ebml_size = GetUIntSize(type);
|
||||
|
||||
// Datasize
|
||||
ebml_size += GetIntSize(value);
|
||||
|
||||
// Size of Datasize
|
||||
ebml_size++;
|
||||
|
||||
return ebml_size;
|
||||
}
|
||||
|
||||
uint64 EbmlElementSize(uint64 type, uint64 value) {
|
||||
return EbmlElementSize(type, value, 0);
|
||||
}
|
||||
|
||||
uint64 EbmlElementSize(uint64 type, uint64 value, uint64 fixed_size) {
|
||||
// Size of EBML ID
|
||||
uint64 ebml_size = GetUIntSize(type);
|
||||
|
||||
// Datasize
|
||||
ebml_size += (fixed_size > 0) ? fixed_size : GetUIntSize(value);
|
||||
|
||||
// Size of Datasize
|
||||
ebml_size++;
|
||||
|
||||
return ebml_size;
|
||||
}
|
||||
|
||||
uint64 EbmlElementSize(uint64 type, float /* value */) {
|
||||
// Size of EBML ID
|
||||
uint64 ebml_size = GetUIntSize(type);
|
||||
|
||||
// Datasize
|
||||
ebml_size += sizeof(float);
|
||||
|
||||
// Size of Datasize
|
||||
ebml_size++;
|
||||
|
||||
return ebml_size;
|
||||
}
|
||||
|
||||
uint64 EbmlElementSize(uint64 type, const char* value) {
|
||||
if (!value)
|
||||
return 0;
|
||||
|
||||
// Size of EBML ID
|
||||
uint64 ebml_size = GetUIntSize(type);
|
||||
|
||||
// Datasize
|
||||
ebml_size += strlen(value);
|
||||
|
||||
// Size of Datasize
|
||||
ebml_size += GetCodedUIntSize(strlen(value));
|
||||
|
||||
return ebml_size;
|
||||
}
|
||||
|
||||
uint64 EbmlElementSize(uint64 type, const uint8* value, uint64 size) {
|
||||
if (!value)
|
||||
return 0;
|
||||
|
||||
// Size of EBML ID
|
||||
uint64 ebml_size = GetUIntSize(type);
|
||||
|
||||
// Datasize
|
||||
ebml_size += size;
|
||||
|
||||
// Size of Datasize
|
||||
ebml_size += GetCodedUIntSize(size);
|
||||
|
||||
return ebml_size;
|
||||
}
|
||||
|
||||
uint64 EbmlDateElementSize(uint64 type) {
|
||||
// Size of EBML ID
|
||||
uint64 ebml_size = GetUIntSize(type);
|
||||
|
||||
// Datasize
|
||||
ebml_size += kDateElementSize;
|
||||
|
||||
// Size of Datasize
|
||||
ebml_size++;
|
||||
|
||||
return ebml_size;
|
||||
}
|
||||
|
||||
int32 SerializeInt(IMkvWriter* writer, int64 value, int32 size) {
|
||||
if (!writer || size < 1 || size > 8)
|
||||
return -1;
|
||||
|
||||
for (int32 i = 1; i <= size; ++i) {
|
||||
const int32 byte_count = size - i;
|
||||
const int32 bit_count = byte_count * 8;
|
||||
|
||||
const int64 bb = value >> bit_count;
|
||||
const uint8 b = static_cast<uint8>(bb);
|
||||
|
||||
const int32 status = writer->Write(&b, 1);
|
||||
|
||||
if (status < 0)
|
||||
return status;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32 SerializeFloat(IMkvWriter* writer, float f) {
|
||||
if (!writer)
|
||||
return -1;
|
||||
|
||||
assert(sizeof(uint32) == sizeof(float));
|
||||
// This union is merely used to avoid a reinterpret_cast from float& to
|
||||
// uint32& which will result in violation of strict aliasing.
|
||||
union U32 {
|
||||
uint32 u32;
|
||||
float f;
|
||||
} value;
|
||||
value.f = f;
|
||||
|
||||
for (int32 i = 1; i <= 4; ++i) {
|
||||
const int32 byte_count = 4 - i;
|
||||
const int32 bit_count = byte_count * 8;
|
||||
|
||||
const uint8 byte = static_cast<uint8>(value.u32 >> bit_count);
|
||||
|
||||
const int32 status = writer->Write(&byte, 1);
|
||||
|
||||
if (status < 0)
|
||||
return status;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32 WriteUInt(IMkvWriter* writer, uint64 value) {
|
||||
if (!writer)
|
||||
return -1;
|
||||
|
||||
int32 size = GetCodedUIntSize(value);
|
||||
|
||||
return WriteUIntSize(writer, value, size);
|
||||
}
|
||||
|
||||
int32 WriteUIntSize(IMkvWriter* writer, uint64 value, int32 size) {
|
||||
if (!writer || size < 0 || size > 8)
|
||||
return -1;
|
||||
|
||||
if (size > 0) {
|
||||
const uint64 bit = 1LL << (size * 7);
|
||||
|
||||
if (value > (bit - 2))
|
||||
return -1;
|
||||
|
||||
value |= bit;
|
||||
} else {
|
||||
size = 1;
|
||||
int64 bit;
|
||||
|
||||
for (;;) {
|
||||
bit = 1LL << (size * 7);
|
||||
const uint64 max = bit - 2;
|
||||
|
||||
if (value <= max)
|
||||
break;
|
||||
|
||||
++size;
|
||||
}
|
||||
|
||||
if (size > 8)
|
||||
return false;
|
||||
|
||||
value |= bit;
|
||||
}
|
||||
|
||||
return SerializeInt(writer, value, size);
|
||||
}
|
||||
|
||||
int32 WriteID(IMkvWriter* writer, uint64 type) {
|
||||
if (!writer)
|
||||
return -1;
|
||||
|
||||
writer->ElementStartNotify(type, writer->Position());
|
||||
|
||||
const int32 size = GetUIntSize(type);
|
||||
|
||||
return SerializeInt(writer, type, size);
|
||||
}
|
||||
|
||||
bool WriteEbmlMasterElement(IMkvWriter* writer, uint64 type, uint64 size) {
|
||||
if (!writer)
|
||||
return false;
|
||||
|
||||
if (WriteID(writer, type))
|
||||
return false;
|
||||
|
||||
if (WriteUInt(writer, size))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WriteEbmlElement(IMkvWriter* writer, uint64 type, uint64 value) {
|
||||
return WriteEbmlElement(writer, type, value, 0);
|
||||
}
|
||||
|
||||
bool WriteEbmlElement(IMkvWriter* writer, uint64 type, uint64 value,
|
||||
uint64 fixed_size) {
|
||||
if (!writer)
|
||||
return false;
|
||||
|
||||
if (WriteID(writer, type))
|
||||
return false;
|
||||
|
||||
uint64 size = GetUIntSize(value);
|
||||
if (fixed_size > 0) {
|
||||
if (size > fixed_size)
|
||||
return false;
|
||||
size = fixed_size;
|
||||
}
|
||||
if (WriteUInt(writer, size))
|
||||
return false;
|
||||
|
||||
if (SerializeInt(writer, value, static_cast<int32>(size)))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WriteEbmlElement(IMkvWriter* writer, uint64 type, int64 value) {
|
||||
if (!writer)
|
||||
return false;
|
||||
|
||||
if (WriteID(writer, type))
|
||||
return 0;
|
||||
|
||||
const uint64 size = GetIntSize(value);
|
||||
if (WriteUInt(writer, size))
|
||||
return false;
|
||||
|
||||
if (SerializeInt(writer, value, static_cast<int32>(size)))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WriteEbmlElement(IMkvWriter* writer, uint64 type, float value) {
|
||||
if (!writer)
|
||||
return false;
|
||||
|
||||
if (WriteID(writer, type))
|
||||
return false;
|
||||
|
||||
if (WriteUInt(writer, 4))
|
||||
return false;
|
||||
|
||||
if (SerializeFloat(writer, value))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WriteEbmlElement(IMkvWriter* writer, uint64 type, const char* value) {
|
||||
if (!writer || !value)
|
||||
return false;
|
||||
|
||||
if (WriteID(writer, type))
|
||||
return false;
|
||||
|
||||
const uint64 length = strlen(value);
|
||||
if (WriteUInt(writer, length))
|
||||
return false;
|
||||
|
||||
if (writer->Write(value, static_cast<const uint32>(length)))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WriteEbmlElement(IMkvWriter* writer, uint64 type, const uint8* value,
|
||||
uint64 size) {
|
||||
if (!writer || !value || size < 1)
|
||||
return false;
|
||||
|
||||
if (WriteID(writer, type))
|
||||
return false;
|
||||
|
||||
if (WriteUInt(writer, size))
|
||||
return false;
|
||||
|
||||
if (writer->Write(value, static_cast<uint32>(size)))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WriteEbmlDateElement(IMkvWriter* writer, uint64 type, int64 value) {
|
||||
if (!writer)
|
||||
return false;
|
||||
|
||||
if (WriteID(writer, type))
|
||||
return false;
|
||||
|
||||
if (WriteUInt(writer, kDateElementSize))
|
||||
return false;
|
||||
|
||||
if (SerializeInt(writer, value, kDateElementSize))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
uint64 WriteFrame(IMkvWriter* writer, const Frame* const frame,
|
||||
Cluster* cluster) {
|
||||
if (!writer || !frame || !frame->IsValid() || !cluster ||
|
||||
!cluster->timecode_scale())
|
||||
return 0;
|
||||
|
||||
// Technically the timecode for a block can be less than the
|
||||
// timecode for the cluster itself (remember that block timecode
|
||||
// is a signed, 16-bit integer). However, as a simplification we
|
||||
// only permit non-negative cluster-relative timecodes for blocks.
|
||||
const int64 relative_timecode = cluster->GetRelativeTimecode(
|
||||
frame->timestamp() / cluster->timecode_scale());
|
||||
if (relative_timecode < 0 || relative_timecode > kMaxBlockTimecode)
|
||||
return 0;
|
||||
|
||||
return frame->CanBeSimpleBlock() ?
|
||||
WriteSimpleBlock(writer, frame, relative_timecode) :
|
||||
WriteBlock(writer, frame, relative_timecode,
|
||||
cluster->timecode_scale());
|
||||
}
|
||||
|
||||
uint64 WriteVoidElement(IMkvWriter* writer, uint64 size) {
|
||||
if (!writer)
|
||||
return false;
|
||||
|
||||
// Subtract one for the void ID and the coded size.
|
||||
uint64 void_entry_size = size - 1 - GetCodedUIntSize(size - 1);
|
||||
uint64 void_size = EbmlMasterElementSize(libwebm::kMkvVoid, void_entry_size) +
|
||||
void_entry_size;
|
||||
|
||||
if (void_size != size)
|
||||
return 0;
|
||||
|
||||
const int64 payload_position = writer->Position();
|
||||
if (payload_position < 0)
|
||||
return 0;
|
||||
|
||||
if (WriteID(writer, libwebm::kMkvVoid))
|
||||
return 0;
|
||||
|
||||
if (WriteUInt(writer, void_entry_size))
|
||||
return 0;
|
||||
|
||||
const uint8 value = 0;
|
||||
for (int32 i = 0; i < static_cast<int32>(void_entry_size); ++i) {
|
||||
if (writer->Write(&value, 1))
|
||||
return 0;
|
||||
}
|
||||
|
||||
const int64 stop_position = writer->Position();
|
||||
if (stop_position < 0 ||
|
||||
stop_position - payload_position != static_cast<int64>(void_size))
|
||||
return 0;
|
||||
|
||||
return void_size;
|
||||
}
|
||||
|
||||
void GetVersion(int32* major, int32* minor, int32* build, int32* revision) {
|
||||
*major = 0;
|
||||
*minor = 2;
|
||||
*build = 1;
|
||||
*revision = 0;
|
||||
}
|
||||
|
||||
uint64 MakeUID(unsigned int* seed) {
|
||||
uint64 uid = 0;
|
||||
|
||||
#ifdef __MINGW32__
|
||||
srand(*seed);
|
||||
#endif
|
||||
|
||||
for (int i = 0; i < 7; ++i) { // avoid problems with 8-byte values
|
||||
uid <<= 8;
|
||||
|
||||
// TODO(fgalligan): Move random number generation to platform specific code.
|
||||
#ifdef _MSC_VER
|
||||
(void)seed;
|
||||
const int32 nn = rand();
|
||||
#elif __ANDROID__
|
||||
(void)seed;
|
||||
int32 temp_num = 1;
|
||||
int fd = open("/dev/urandom", O_RDONLY);
|
||||
if (fd != -1) {
|
||||
read(fd, &temp_num, sizeof(temp_num));
|
||||
close(fd);
|
||||
}
|
||||
const int32 nn = temp_num;
|
||||
#elif defined __MINGW32__
|
||||
const int32 nn = rand();
|
||||
#else
|
||||
const int32 nn = rand_r(seed);
|
||||
#endif
|
||||
const int32 n = 0xFF & (nn >> 4); // throw away low-order bits
|
||||
|
||||
uid |= n;
|
||||
}
|
||||
|
||||
return uid;
|
||||
}
|
||||
|
||||
bool IsMatrixCoefficientsValueValid(uint64_t value) {
|
||||
switch (value) {
|
||||
case mkvmuxer::Colour::kGbr:
|
||||
case mkvmuxer::Colour::kBt709:
|
||||
case mkvmuxer::Colour::kUnspecifiedMc:
|
||||
case mkvmuxer::Colour::kReserved:
|
||||
case mkvmuxer::Colour::kFcc:
|
||||
case mkvmuxer::Colour::kBt470bg:
|
||||
case mkvmuxer::Colour::kSmpte170MMc:
|
||||
case mkvmuxer::Colour::kSmpte240MMc:
|
||||
case mkvmuxer::Colour::kYcocg:
|
||||
case mkvmuxer::Colour::kBt2020NonConstantLuminance:
|
||||
case mkvmuxer::Colour::kBt2020ConstantLuminance:
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsChromaSitingHorzValueValid(uint64_t value) {
|
||||
switch (value) {
|
||||
case mkvmuxer::Colour::kUnspecifiedCsh:
|
||||
case mkvmuxer::Colour::kLeftCollocated:
|
||||
case mkvmuxer::Colour::kHalfCsh:
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsChromaSitingVertValueValid(uint64_t value) {
|
||||
switch (value) {
|
||||
case mkvmuxer::Colour::kUnspecifiedCsv:
|
||||
case mkvmuxer::Colour::kTopCollocated:
|
||||
case mkvmuxer::Colour::kHalfCsv:
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsColourRangeValueValid(uint64_t value) {
|
||||
switch (value) {
|
||||
case mkvmuxer::Colour::kUnspecifiedCr:
|
||||
case mkvmuxer::Colour::kBroadcastRange:
|
||||
case mkvmuxer::Colour::kFullRange:
|
||||
case mkvmuxer::Colour::kMcTcDefined:
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsTransferCharacteristicsValueValid(uint64_t value) {
|
||||
switch (value) {
|
||||
case mkvmuxer::Colour::kIturBt709Tc:
|
||||
case mkvmuxer::Colour::kUnspecifiedTc:
|
||||
case mkvmuxer::Colour::kReservedTc:
|
||||
case mkvmuxer::Colour::kGamma22Curve:
|
||||
case mkvmuxer::Colour::kGamma28Curve:
|
||||
case mkvmuxer::Colour::kSmpte170MTc:
|
||||
case mkvmuxer::Colour::kSmpte240MTc:
|
||||
case mkvmuxer::Colour::kLinear:
|
||||
case mkvmuxer::Colour::kLog:
|
||||
case mkvmuxer::Colour::kLogSqrt:
|
||||
case mkvmuxer::Colour::kIec6196624:
|
||||
case mkvmuxer::Colour::kIturBt1361ExtendedColourGamut:
|
||||
case mkvmuxer::Colour::kIec6196621:
|
||||
case mkvmuxer::Colour::kIturBt202010bit:
|
||||
case mkvmuxer::Colour::kIturBt202012bit:
|
||||
case mkvmuxer::Colour::kSmpteSt2084:
|
||||
case mkvmuxer::Colour::kSmpteSt4281Tc:
|
||||
case mkvmuxer::Colour::kAribStdB67Hlg:
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsPrimariesValueValid(uint64_t value) {
|
||||
switch (value) {
|
||||
case mkvmuxer::Colour::kReservedP0:
|
||||
case mkvmuxer::Colour::kIturBt709P:
|
||||
case mkvmuxer::Colour::kUnspecifiedP:
|
||||
case mkvmuxer::Colour::kReservedP3:
|
||||
case mkvmuxer::Colour::kIturBt470M:
|
||||
case mkvmuxer::Colour::kIturBt470Bg:
|
||||
case mkvmuxer::Colour::kSmpte170MP:
|
||||
case mkvmuxer::Colour::kSmpte240MP:
|
||||
case mkvmuxer::Colour::kFilm:
|
||||
case mkvmuxer::Colour::kIturBt2020:
|
||||
case mkvmuxer::Colour::kSmpteSt4281P:
|
||||
case mkvmuxer::Colour::kJedecP22Phosphors:
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace mkvmuxer
|
@ -1,112 +0,0 @@
|
||||
// Copyright (c) 2012 The WebM project authors. All Rights Reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style license
|
||||
// that can be found in the LICENSE file in the root of the source
|
||||
// tree. An additional intellectual property rights grant can be found
|
||||
// in the file PATENTS. All contributing project authors may
|
||||
// be found in the AUTHORS file in the root of the source tree.
|
||||
#ifndef MKVMUXER_MKVMUXERUTIL_H_
|
||||
#define MKVMUXER_MKVMUXERUTIL_H_
|
||||
|
||||
#include "mkvmuxertypes.h"
|
||||
|
||||
#include "stdint.h"
|
||||
|
||||
namespace mkvmuxer {
|
||||
class Cluster;
|
||||
class Frame;
|
||||
class IMkvWriter;
|
||||
|
||||
// TODO(tomfinegan): mkvmuxer:: integer types continue to be used here because
|
||||
// changing them causes pain for downstream projects. It would be nice if a
|
||||
// solution that allows removal of the mkvmuxer:: integer types while avoiding
|
||||
// pain for downstream users of libwebm. Considering that mkvmuxerutil.{cc,h}
|
||||
// are really, for the great majority of cases, EBML size calculation and writer
|
||||
// functions, perhaps a more EBML focused utility would be the way to go as a
|
||||
// first step.
|
||||
|
||||
const uint64 kEbmlUnknownValue = 0x01FFFFFFFFFFFFFFULL;
|
||||
const int64 kMaxBlockTimecode = 0x07FFFLL;
|
||||
|
||||
// Writes out |value| in Big Endian order. Returns 0 on success.
|
||||
int32 SerializeInt(IMkvWriter* writer, int64 value, int32 size);
|
||||
|
||||
// Returns the size in bytes of the element.
|
||||
int32 GetUIntSize(uint64 value);
|
||||
int32 GetIntSize(int64 value);
|
||||
int32 GetCodedUIntSize(uint64 value);
|
||||
uint64 EbmlMasterElementSize(uint64 type, uint64 value);
|
||||
uint64 EbmlElementSize(uint64 type, int64 value);
|
||||
uint64 EbmlElementSize(uint64 type, uint64 value);
|
||||
uint64 EbmlElementSize(uint64 type, float value);
|
||||
uint64 EbmlElementSize(uint64 type, const char* value);
|
||||
uint64 EbmlElementSize(uint64 type, const uint8* value, uint64 size);
|
||||
uint64 EbmlDateElementSize(uint64 type);
|
||||
|
||||
// Returns the size in bytes of the element assuming that the element was
|
||||
// written using |fixed_size| bytes. If |fixed_size| is set to zero, then it
|
||||
// computes the necessary number of bytes based on |value|.
|
||||
uint64 EbmlElementSize(uint64 type, uint64 value, uint64 fixed_size);
|
||||
|
||||
// Creates an EBML coded number from |value| and writes it out. The size of
|
||||
// the coded number is determined by the value of |value|. |value| must not
|
||||
// be in a coded form. Returns 0 on success.
|
||||
int32 WriteUInt(IMkvWriter* writer, uint64 value);
|
||||
|
||||
// Creates an EBML coded number from |value| and writes it out. The size of
|
||||
// the coded number is determined by the value of |size|. |value| must not
|
||||
// be in a coded form. Returns 0 on success.
|
||||
int32 WriteUIntSize(IMkvWriter* writer, uint64 value, int32 size);
|
||||
|
||||
// Output an Mkv master element. Returns true if the element was written.
|
||||
bool WriteEbmlMasterElement(IMkvWriter* writer, uint64 value, uint64 size);
|
||||
|
||||
// Outputs an Mkv ID, calls |IMkvWriter::ElementStartNotify|, and passes the
|
||||
// ID to |SerializeInt|. Returns 0 on success.
|
||||
int32 WriteID(IMkvWriter* writer, uint64 type);
|
||||
|
||||
// Output an Mkv non-master element. Returns true if the element was written.
|
||||
bool WriteEbmlElement(IMkvWriter* writer, uint64 type, uint64 value);
|
||||
bool WriteEbmlElement(IMkvWriter* writer, uint64 type, int64 value);
|
||||
bool WriteEbmlElement(IMkvWriter* writer, uint64 type, float value);
|
||||
bool WriteEbmlElement(IMkvWriter* writer, uint64 type, const char* value);
|
||||
bool WriteEbmlElement(IMkvWriter* writer, uint64 type, const uint8* value,
|
||||
uint64 size);
|
||||
bool WriteEbmlDateElement(IMkvWriter* writer, uint64 type, int64 value);
|
||||
|
||||
// Output an Mkv non-master element using fixed size. The element will be
|
||||
// written out using exactly |fixed_size| bytes. If |fixed_size| is set to zero
|
||||
// then it computes the necessary number of bytes based on |value|. Returns true
|
||||
// if the element was written.
|
||||
bool WriteEbmlElement(IMkvWriter* writer, uint64 type, uint64 value,
|
||||
uint64 fixed_size);
|
||||
|
||||
// Output a Mkv Frame. It decides the correct element to write (Block vs
|
||||
// SimpleBlock) based on the parameters of the Frame.
|
||||
uint64 WriteFrame(IMkvWriter* writer, const Frame* const frame,
|
||||
Cluster* cluster);
|
||||
|
||||
// Output a void element. |size| must be the entire size in bytes that will be
|
||||
// void. The function will calculate the size of the void header and subtract
|
||||
// it from |size|.
|
||||
uint64 WriteVoidElement(IMkvWriter* writer, uint64 size);
|
||||
|
||||
// Returns the version number of the muxer in |major|, |minor|, |build|,
|
||||
// and |revision|.
|
||||
void GetVersion(int32* major, int32* minor, int32* build, int32* revision);
|
||||
|
||||
// Returns a random number to be used for UID, using |seed| to seed
|
||||
// the random-number generator (see POSIX rand_r() for semantics).
|
||||
uint64 MakeUID(unsigned int* seed);
|
||||
|
||||
// Colour field validation helpers. All return true when |value| is valid.
|
||||
bool IsMatrixCoefficientsValueValid(uint64_t value);
|
||||
bool IsChromaSitingHorzValueValid(uint64_t value);
|
||||
bool IsChromaSitingVertValueValid(uint64_t value);
|
||||
bool IsColourRangeValueValid(uint64_t value);
|
||||
bool IsTransferCharacteristicsValueValid(uint64_t value);
|
||||
bool IsPrimariesValueValid(uint64_t value);
|
||||
|
||||
} // namespace mkvmuxer
|
||||
|
||||
#endif // MKVMUXER_MKVMUXERUTIL_H_
|
@ -1,90 +0,0 @@
|
||||
// Copyright (c) 2012 The WebM project authors. All Rights Reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style license
|
||||
// that can be found in the LICENSE file in the root of the source
|
||||
// tree. An additional intellectual property rights grant can be found
|
||||
// in the file PATENTS. All contributing project authors may
|
||||
// be found in the AUTHORS file in the root of the source tree.
|
||||
|
||||
#include "mkvmuxer/mkvwriter.h"
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#include <share.h> // for _SH_DENYWR
|
||||
#endif
|
||||
|
||||
namespace mkvmuxer {
|
||||
|
||||
MkvWriter::MkvWriter() : file_(NULL), writer_owns_file_(true) {}
|
||||
|
||||
MkvWriter::MkvWriter(FILE* fp) : file_(fp), writer_owns_file_(false) {}
|
||||
|
||||
MkvWriter::~MkvWriter() { Close(); }
|
||||
|
||||
int32 MkvWriter::Write(const void* buffer, uint32 length) {
|
||||
if (!file_)
|
||||
return -1;
|
||||
|
||||
if (length == 0)
|
||||
return 0;
|
||||
|
||||
if (buffer == NULL)
|
||||
return -1;
|
||||
|
||||
const size_t bytes_written = fwrite(buffer, 1, length, file_);
|
||||
|
||||
return (bytes_written == length) ? 0 : -1;
|
||||
}
|
||||
|
||||
bool MkvWriter::Open(const char* filename) {
|
||||
if (filename == NULL)
|
||||
return false;
|
||||
|
||||
if (file_)
|
||||
return false;
|
||||
|
||||
#ifdef _MSC_VER
|
||||
file_ = _fsopen(filename, "wb", _SH_DENYWR);
|
||||
#else
|
||||
file_ = fopen(filename, "wb");
|
||||
#endif
|
||||
if (file_ == NULL)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void MkvWriter::Close() {
|
||||
if (file_ && writer_owns_file_) {
|
||||
fclose(file_);
|
||||
}
|
||||
file_ = NULL;
|
||||
}
|
||||
|
||||
int64 MkvWriter::Position() const {
|
||||
if (!file_)
|
||||
return 0;
|
||||
|
||||
#ifdef _MSC_VER
|
||||
return _ftelli64(file_);
|
||||
#else
|
||||
return ftell(file_);
|
||||
#endif
|
||||
}
|
||||
|
||||
int32 MkvWriter::Position(int64 position) {
|
||||
if (!file_)
|
||||
return -1;
|
||||
|
||||
#ifdef _MSC_VER
|
||||
return _fseeki64(file_, position, SEEK_SET);
|
||||
#else
|
||||
return fseeko(file_, static_cast<off_t>(position), SEEK_SET);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool MkvWriter::Seekable() const { return true; }
|
||||
|
||||
void MkvWriter::ElementStartNotify(uint64, int64) {}
|
||||
|
||||
} // namespace mkvmuxer
|
@ -1,51 +0,0 @@
|
||||
// Copyright (c) 2012 The WebM project authors. All Rights Reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style license
|
||||
// that can be found in the LICENSE file in the root of the source
|
||||
// tree. An additional intellectual property rights grant can be found
|
||||
// in the file PATENTS. All contributing project authors may
|
||||
// be found in the AUTHORS file in the root of the source tree.
|
||||
|
||||
#ifndef MKVMUXER_MKVWRITER_H_
|
||||
#define MKVMUXER_MKVWRITER_H_
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "mkvmuxer/mkvmuxer.h"
|
||||
#include "mkvmuxer/mkvmuxertypes.h"
|
||||
|
||||
namespace mkvmuxer {
|
||||
|
||||
// Default implementation of the IMkvWriter interface on Windows.
|
||||
class MkvWriter : public IMkvWriter {
|
||||
public:
|
||||
MkvWriter();
|
||||
explicit MkvWriter(FILE* fp);
|
||||
virtual ~MkvWriter();
|
||||
|
||||
// IMkvWriter interface
|
||||
virtual int64 Position() const;
|
||||
virtual int32 Position(int64 position);
|
||||
virtual bool Seekable() const;
|
||||
virtual int32 Write(const void* buffer, uint32 length);
|
||||
virtual void ElementStartNotify(uint64 element_id, int64 position);
|
||||
|
||||
// Creates and opens a file for writing. |filename| is the name of the file
|
||||
// to open. This function will overwrite the contents of |filename|. Returns
|
||||
// true on success.
|
||||
bool Open(const char* filename);
|
||||
|
||||
// Closes an opened file.
|
||||
void Close();
|
||||
|
||||
private:
|
||||
// File handle to output file.
|
||||
FILE* file_;
|
||||
bool writer_owns_file_;
|
||||
|
||||
LIBWEBM_DISALLOW_COPY_AND_ASSIGN(MkvWriter);
|
||||
};
|
||||
|
||||
} // namespace mkvmuxer
|
||||
|
||||
#endif // MKVMUXER_MKVWRITER_H_
|
File diff suppressed because it is too large
Load Diff
@ -57,7 +57,6 @@
|
||||
#include "modes/profile_world.hpp"
|
||||
#include "modes/world.hpp"
|
||||
#include "physics/physics.hpp"
|
||||
#include "recorder/recorder_common.hpp"
|
||||
#include "scriptengine/property_animator.hpp"
|
||||
#include "states_screens/dialogs/confirm_resolution_dialog.hpp"
|
||||
#include "states_screens/state_manager.hpp"
|
||||
@ -69,6 +68,11 @@
|
||||
|
||||
#include <irrlicht.h>
|
||||
|
||||
#ifdef ENABLE_RECORDER
|
||||
#include <chrono>
|
||||
#include <openglrecorder.h>
|
||||
#endif
|
||||
|
||||
/* Build-time check that the Irrlicht we're building against works for us.
|
||||
* Should help prevent distros building against an incompatible library.
|
||||
*/
|
||||
@ -156,6 +160,9 @@ IrrDriver::IrrDriver()
|
||||
*/
|
||||
IrrDriver::~IrrDriver()
|
||||
{
|
||||
#ifdef ENABLE_RECORDER
|
||||
ogrDestroy();
|
||||
#endif
|
||||
assert(m_device != NULL);
|
||||
m_device->drop();
|
||||
m_device = NULL;
|
||||
@ -169,9 +176,6 @@ IrrDriver::~IrrDriver()
|
||||
#endif
|
||||
delete m_wind;
|
||||
delete m_renderer;
|
||||
#ifdef ENABLE_RECORDER
|
||||
Recorder::destroyRecorder();
|
||||
#endif
|
||||
} // ~IrrDriver
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
@ -603,6 +607,54 @@ void IrrDriver::initDevice()
|
||||
sml->drop();
|
||||
|
||||
m_actual_screen_size = m_video_driver->getCurrentRenderTargetSize();
|
||||
|
||||
#ifdef ENABLE_RECORDER
|
||||
RecorderConfig cfg;
|
||||
cfg.m_triple_buffering = 1;
|
||||
cfg.m_record_audio = 1;
|
||||
cfg.m_width = m_actual_screen_size.Width;
|
||||
cfg.m_height = m_actual_screen_size.Height;
|
||||
int vf = UserConfigParams::m_record_format;
|
||||
cfg.m_video_format = (VideoFormat)vf;
|
||||
cfg.m_audio_format = OGR_AF_VORBIS;
|
||||
cfg.m_audio_bitrate = 112000;
|
||||
cfg.m_video_bitrate = UserConfigParams::m_vp_bitrate;
|
||||
cfg.m_record_fps = UserConfigParams::m_record_fps;
|
||||
cfg.m_record_jpg_quality = UserConfigParams::m_recorder_jpg_quality;
|
||||
if (ogrInitConfig(&cfg) == 0)
|
||||
{
|
||||
Log::error("irr_driver",
|
||||
"RecorderConfig is invalid, use the default one.");
|
||||
}
|
||||
|
||||
ogrRegReadPixelsFunction([]
|
||||
(int x, int y, int w, int h, unsigned int f, unsigned int t, void* d)
|
||||
{ glReadPixels(x, y, w, h, f, t, d); });
|
||||
|
||||
ogrRegPBOFunctions([](int n, unsigned int* b) { glGenBuffers(n, b); },
|
||||
[](unsigned int t, unsigned int b) { glBindBuffer(t, b); },
|
||||
[](unsigned int t, ptrdiff_t s, const void* d, unsigned int u)
|
||||
{ glBufferData(t, s, d, u); },
|
||||
[](int n, const unsigned int* b) { glDeleteBuffers(n, b); },
|
||||
[](unsigned int t, unsigned int a) { return glMapBuffer(t, a); },
|
||||
[](unsigned int t) { return glUnmapBuffer(t); });
|
||||
|
||||
ogrRegGeneralCallback(OGR_CBT_START_RECORDING,
|
||||
[] (void* user_data) { MessageQueue::add
|
||||
(MessageQueue::MT_GENERIC, _("Video recording started.")); }, NULL);
|
||||
ogrRegStringCallback(OGR_CBT_ERROR_RECORDING,
|
||||
[](const char* s, void* user_data)
|
||||
{ Log::error("openglrecorder", "%s", s); }, NULL);
|
||||
ogrRegStringCallback(OGR_CBT_SAVED_RECORDING,
|
||||
[] (const char* s, void* user_data) { MessageQueue::add
|
||||
(MessageQueue::MT_GENERIC, _("Video saved in \"%s\".", s));
|
||||
}, NULL);
|
||||
ogrRegIntCallback(OGR_CBT_PROGRESS_RECORDING,
|
||||
[] (const int i, void* user_data)
|
||||
{ MessageQueue::showProgressBar(i, _("Encoding progress:")); }, NULL);
|
||||
|
||||
#endif
|
||||
|
||||
#ifndef SERVER_ONLY
|
||||
if(CVS->isGLSL())
|
||||
m_renderer = new ShaderBasedRenderer();
|
||||
@ -927,7 +979,7 @@ void IrrDriver::applyResolutionSettings()
|
||||
VAOManager::getInstance()->kill();
|
||||
STKTexManager::getInstance()->kill();
|
||||
#ifdef ENABLE_RECORDER
|
||||
Recorder::destroyRecorder();
|
||||
ogrDestroy();
|
||||
m_recording = false;
|
||||
#endif
|
||||
// initDevice will drop the current device.
|
||||
@ -1896,7 +1948,11 @@ void IrrDriver::update(float dt)
|
||||
// printRenderStats();
|
||||
#ifdef ENABLE_RECORDER
|
||||
if (m_recording)
|
||||
Recorder::captureFrameBufferImage();
|
||||
{
|
||||
PROFILER_PUSH_CPU_MARKER("- Recording", 0x0, 0x50, 0x40);
|
||||
ogrCapture();
|
||||
PROFILER_POP_CPU_MARKER();
|
||||
}
|
||||
#endif
|
||||
} // update
|
||||
|
||||
@ -1909,25 +1965,28 @@ void IrrDriver::setRecording(bool val)
|
||||
Log::warn("irr_driver", "PBO extension missing, can't record video.");
|
||||
return;
|
||||
}
|
||||
if (m_recording == val)
|
||||
if (val == (ogrCapturing() == 1))
|
||||
return;
|
||||
m_recording = val;
|
||||
if (val == true)
|
||||
{
|
||||
if (!Recorder::isRecording())
|
||||
return;
|
||||
m_recording = val;
|
||||
std::string track_name = World::getWorld() != NULL ?
|
||||
race_manager->getTrackName() : "menu";
|
||||
Recorder::setRecordingName(file_manager->getScreenshotDir() +
|
||||
track_name);
|
||||
Recorder::prepareCapture();
|
||||
MessageQueue::add(MessageQueue::MT_GENERIC,
|
||||
_("Video recording started."));
|
||||
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);
|
||||
ogrSetSavedName((file_manager->getScreenshotDir() +
|
||||
track_name + "_" + time_buffer).c_str());
|
||||
ogrPrepareCapture();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_recording = val;
|
||||
Recorder::stopRecording();
|
||||
ogrStopCapture();
|
||||
}
|
||||
#endif
|
||||
} // setRecording
|
||||
|
@ -22,6 +22,7 @@
|
||||
|
||||
#include "graphics/irr_driver.hpp"
|
||||
#include "guiengine/engine.hpp"
|
||||
#include "guiengine/scalable_font.hpp"
|
||||
#include "guiengine/skin.hpp"
|
||||
#include "utils/synchronised.hpp"
|
||||
#include "utils/translation.hpp"
|
||||
@ -30,23 +31,66 @@
|
||||
#include "IGUIEnvironment.h"
|
||||
#include "IGUIStaticText.h"
|
||||
|
||||
#include <atomic>
|
||||
|
||||
using namespace GUIEngine;
|
||||
|
||||
namespace MessageQueue
|
||||
{
|
||||
// ============================================================================
|
||||
/** The area which the message is drawn. */
|
||||
core::recti g_area;
|
||||
/** The label widget used to show the current message. */
|
||||
SkinWidgetContainer* g_container = NULL;
|
||||
|
||||
// ============================================================================
|
||||
/** A small helper class to store and sort messages to be displayed. */
|
||||
/** A base class for any messages. */
|
||||
class Message
|
||||
{
|
||||
protected:
|
||||
/** The message. */
|
||||
core::stringw m_message;
|
||||
|
||||
/** If this < 0, remove this message from queue. */
|
||||
float m_display_timer;
|
||||
|
||||
/** The area which the message is drawn. */
|
||||
core::recti m_area;
|
||||
|
||||
private:
|
||||
/** Tell if this message has been initialized. */
|
||||
bool m_inited;
|
||||
|
||||
public:
|
||||
Message(float timer) : m_display_timer(timer), m_inited(false) {}
|
||||
// ------------------------------------------------------------------------
|
||||
virtual ~Message() {}
|
||||
// ------------------------------------------------------------------------
|
||||
virtual MessageQueue::MessageType getMessageType() const = 0;
|
||||
// ------------------------------------------------------------------------
|
||||
virtual void init() = 0;
|
||||
// ------------------------------------------------------------------------
|
||||
virtual void draw(float dt)
|
||||
{
|
||||
if (!m_inited)
|
||||
{
|
||||
m_inited = true;
|
||||
init();
|
||||
}
|
||||
m_display_timer -= dt;
|
||||
}
|
||||
// ------------------------------------------------------------------------
|
||||
bool canBeRemoved() const { return m_display_timer < 0.0f; }
|
||||
// ------------------------------------------------------------------------
|
||||
virtual void remove() {}
|
||||
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
/** A small helper class to store and sort text messages to be displayed. */
|
||||
class TextMessage : public Message
|
||||
{
|
||||
private:
|
||||
/** The type of the message. */
|
||||
MessageQueue::MessageType m_message_type;
|
||||
/** The message. */
|
||||
core::stringw m_message;
|
||||
|
||||
/** The render type of the message: either achievement-message::neutral
|
||||
* or friend-message::neutral. */
|
||||
@ -56,42 +100,38 @@ private:
|
||||
gui::IGUIStaticText* m_text;
|
||||
|
||||
public:
|
||||
Message(MessageQueue::MessageType mt, const core::stringw &message)
|
||||
TextMessage(MessageQueue::MessageType mt, const core::stringw &message) :
|
||||
Message(5.0f)
|
||||
{
|
||||
m_message_type = mt;
|
||||
m_message = message;
|
||||
m_text = NULL;
|
||||
if(mt==MessageQueue::MT_ACHIEVEMENT)
|
||||
assert(mt != MessageQueue::MT_PROGRESS);
|
||||
if (mt == MessageQueue::MT_ACHIEVEMENT)
|
||||
m_render_type = "achievement-message::neutral";
|
||||
else if (mt==MessageQueue::MT_ERROR)
|
||||
else if (mt == MessageQueue::MT_ERROR)
|
||||
m_render_type = "error-message::neutral";
|
||||
else if (mt==MessageQueue::MT_GENERIC)
|
||||
else if (mt == MessageQueue::MT_GENERIC)
|
||||
m_render_type = "generic-message::neutral";
|
||||
else
|
||||
m_render_type = "friend-message::neutral";
|
||||
} // Message
|
||||
// ------------------------------------------------------------------------
|
||||
~Message()
|
||||
~TextMessage()
|
||||
{
|
||||
assert(m_text != NULL);
|
||||
m_text->drop();
|
||||
}
|
||||
/** Returns the message. */
|
||||
const core::stringw & getMessage() const { return m_message; }
|
||||
// ------------------------------------------------------------------------
|
||||
/** Returns the type of the message (achievement or friend). */
|
||||
MessageQueue::MessageType getMessageType() const { return m_message_type; }
|
||||
// ------------------------------------------------------------------------
|
||||
/** Returns the render type: either achievement-message::neutral or
|
||||
* friend-message::neutral (see skin for details). */
|
||||
const std::string &getRenderType() const
|
||||
{
|
||||
return m_render_type;
|
||||
}
|
||||
/** Returns the type of the message.*/
|
||||
virtual MessageQueue::MessageType getMessageType() const
|
||||
{ return m_message_type; }
|
||||
// ------------------------------------------------------------------------
|
||||
/** Init the message text, do linebreak as required. */
|
||||
void init()
|
||||
virtual void init()
|
||||
{
|
||||
if (m_text)
|
||||
m_text->drop();
|
||||
const GUIEngine::BoxRenderParams &brp =
|
||||
GUIEngine::getSkin()->getBoxRenderParams(m_render_type);
|
||||
const unsigned width = irr_driver->getActualScreenSize().Width;
|
||||
@ -107,21 +147,25 @@ public:
|
||||
dim.Width += brp.m_left_border + brp.m_right_border;
|
||||
int x = (width - dim.Width) / 2;
|
||||
int y = height - int(1.5f * dim.Height);
|
||||
g_area = irr::core::recti(x, y, x + dim.Width, y + dim.Height);
|
||||
m_text->setRelativePosition(g_area);
|
||||
m_area = irr::core::recti(x, y, x + dim.Width, y + dim.Height);
|
||||
m_text->setRelativePosition(m_area);
|
||||
m_text->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_CENTER);
|
||||
m_text->grab();
|
||||
m_text->remove();
|
||||
}
|
||||
// ------------------------------------------------------------------------
|
||||
/** Draw the message. */
|
||||
void draw()
|
||||
virtual void draw(float dt)
|
||||
{
|
||||
Message::draw(dt);
|
||||
GUIEngine::getSkin()->drawMessage(g_container, m_area, m_render_type);
|
||||
assert(m_text != NULL);
|
||||
m_text->draw();
|
||||
}
|
||||
// ------------------------------------------------------------------------
|
||||
virtual void remove() { delete this; }
|
||||
|
||||
}; // class Message
|
||||
}; // class TextMessage
|
||||
|
||||
// ============================================================================
|
||||
/** A function class to compare messages, required for priority_queue. */
|
||||
@ -142,30 +186,87 @@ public:
|
||||
Synchronised<std::priority_queue<Message*, std::vector<Message*>,
|
||||
CompareMessages> > g_all_messages;
|
||||
|
||||
/** How long the current message has been displayed. The special value
|
||||
* -1 indicates that a new message was added when the queue was empty. */
|
||||
float g_current_display_time = -1.0f;
|
||||
|
||||
/** How long the current message should be displaed. */
|
||||
float g_max_display_time = 5.0f;
|
||||
|
||||
/** The label widget used to show the current message. */
|
||||
SkinWidgetContainer *g_container = NULL;
|
||||
// ============================================================================
|
||||
/** Add any message to the message queue.
|
||||
* \param message Any message.
|
||||
*/
|
||||
void privateAdd(Message* m)
|
||||
{
|
||||
g_all_messages.lock();
|
||||
g_all_messages.getData().push(m);
|
||||
g_all_messages.unlock();
|
||||
} // privateAdd
|
||||
|
||||
// ============================================================================
|
||||
|
||||
void createLabel(Message *message)
|
||||
/** A class which display a progress bar in game, only one can be displayed. */
|
||||
class ProgressBarMessage : public Message
|
||||
{
|
||||
if(!g_container)
|
||||
g_container = new SkinWidgetContainer();
|
||||
private:
|
||||
std::atomic_int_fast8_t m_progress_value;
|
||||
|
||||
g_current_display_time = 0.0f;
|
||||
// Maybe make this time dependent on message length as well?
|
||||
g_max_display_time = 5.0f;
|
||||
message->init();
|
||||
} // createLabel
|
||||
bool m_showing;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
SkinWidgetContainer m_swc;
|
||||
public:
|
||||
ProgressBarMessage() :
|
||||
Message(9999999.9f)
|
||||
{
|
||||
m_progress_value.store(0);
|
||||
m_showing = false;
|
||||
} // ProgressBarMessage
|
||||
// ------------------------------------------------------------------------
|
||||
~ProgressBarMessage() {}
|
||||
// ------------------------------------------------------------------------
|
||||
/** Returns the type of the message.*/
|
||||
virtual MessageQueue::MessageType getMessageType() const
|
||||
{ return MT_PROGRESS; }
|
||||
// ------------------------------------------------------------------------
|
||||
virtual void init()
|
||||
{
|
||||
const unsigned width = irr_driver->getActualScreenSize().Width;
|
||||
const unsigned height = irr_driver->getActualScreenSize().Height;
|
||||
core::dimension2du dim(width * 0.75f, height * 0.05f);
|
||||
int x = (width - dim.Width) / 2;
|
||||
int y = height - int(1.5f * dim.Height);
|
||||
m_area = irr::core::recti(x, y, x + dim.Width,
|
||||
y + dim.Height);
|
||||
}
|
||||
// ------------------------------------------------------------------------
|
||||
virtual void draw(float dt)
|
||||
{
|
||||
Message::draw(dt);
|
||||
m_display_timer = 9999999.9f;
|
||||
GUIEngine::getSkin()->drawProgressBarInScreen(&m_swc, m_area,
|
||||
m_progress_value.load());
|
||||
video::SColor color(255, 0, 0, 0);
|
||||
GUIEngine::getFont()->draw(m_message, m_area, color, true, true);
|
||||
if (m_progress_value.load() >= 100)
|
||||
{
|
||||
m_display_timer = -1.0f;
|
||||
m_showing = false;
|
||||
}
|
||||
}
|
||||
// ------------------------------------------------------------------------
|
||||
void setProgress(int progress, const wchar_t* msg)
|
||||
{
|
||||
if (progress < 0)
|
||||
return;
|
||||
if (progress > 100)
|
||||
progress = 100;
|
||||
m_progress_value.store((int_fast8_t)progress);
|
||||
if (!m_showing && progress == 0)
|
||||
{
|
||||
m_showing = true;
|
||||
m_message = msg;
|
||||
privateAdd(this);
|
||||
}
|
||||
}
|
||||
}; // class ProgressBarMessage
|
||||
|
||||
// ============================================================================
|
||||
/** One instance of progress bar. */
|
||||
ProgressBarMessage g_progress_bar_msg;
|
||||
// ============================================================================
|
||||
/** Called when the screen resolution is changed to compute the new
|
||||
* position of the message. */
|
||||
void updatePosition()
|
||||
@ -177,28 +278,19 @@ void updatePosition()
|
||||
g_all_messages.unlock();
|
||||
return;
|
||||
}
|
||||
Message *last = g_all_messages.getData().top();
|
||||
createLabel(last);
|
||||
g_all_messages.getData().top()->init();
|
||||
g_all_messages.unlock();
|
||||
} // updatePosition
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
/** Adds a message to the message queue.
|
||||
/** Adds a Text message to the message queue.
|
||||
* \param mt The MessageType of the message.
|
||||
* \param message The actual message.
|
||||
*/
|
||||
void add(MessageType mt, const irr::core::stringw &message)
|
||||
{
|
||||
Message *m = new Message(mt, message);
|
||||
g_all_messages.lock();
|
||||
if (g_all_messages.getData().empty())
|
||||
{
|
||||
// Indicate that there is a new message, which should
|
||||
// which needs a new label etc. to be computed.
|
||||
g_current_display_time =-1.0f;
|
||||
}
|
||||
g_all_messages.getData().push(m);
|
||||
g_all_messages.unlock();
|
||||
Message *m = new TextMessage(mt, message);
|
||||
privateAdd(m);
|
||||
} // add
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
@ -210,40 +302,41 @@ void add(MessageType mt, const irr::core::stringw &message)
|
||||
*/
|
||||
void update(float dt)
|
||||
{
|
||||
if (!g_container)
|
||||
g_container = new SkinWidgetContainer();
|
||||
|
||||
g_all_messages.lock();
|
||||
bool empty = g_all_messages.getData().empty();
|
||||
g_all_messages.unlock();
|
||||
if (empty) return;
|
||||
|
||||
g_all_messages.lock();
|
||||
g_current_display_time += dt;
|
||||
if (g_current_display_time > g_max_display_time)
|
||||
if (empty)
|
||||
{
|
||||
g_all_messages.unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
Message* current = g_all_messages.getData().top();
|
||||
current->draw(dt);
|
||||
|
||||
if (current->canBeRemoved())
|
||||
{
|
||||
Message *last = g_all_messages.getData().top();
|
||||
g_all_messages.getData().pop();
|
||||
delete last;
|
||||
if (g_all_messages.getData().empty())
|
||||
{
|
||||
g_all_messages.unlock();
|
||||
return;
|
||||
}
|
||||
g_current_display_time = -1.0f;
|
||||
}
|
||||
|
||||
Message *current = g_all_messages.getData().top();
|
||||
// Create new data for the display.
|
||||
if (g_current_display_time < 0)
|
||||
{
|
||||
createLabel(current);
|
||||
current->remove();
|
||||
}
|
||||
g_all_messages.unlock();
|
||||
|
||||
GUIEngine::getSkin()->drawMessage(g_container, g_area,
|
||||
current->getRenderType());
|
||||
current->draw();
|
||||
|
||||
} // update
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
/** The time a user firstly call this function with a zero progress, a progress
|
||||
* bar will display in the game, the time the value of progress become 100,
|
||||
* the progress bar will disappear. So make sure only 1 progress bar is being
|
||||
* used each time.
|
||||
* \param progress Progress from 0 to 100.
|
||||
*/
|
||||
void showProgressBar(int progress, const wchar_t* msg)
|
||||
{
|
||||
g_progress_bar_msg.setProgress(progress, msg);
|
||||
} // showProgressBar
|
||||
|
||||
} // namespace GUIEngine
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
@ -34,9 +34,17 @@ namespace MessageQueue
|
||||
* different look. This type is used to sort the messages, so it is
|
||||
* important that messages that need to be shown as early as possible
|
||||
* will be listed last (i.e. have highest priority). */
|
||||
enum MessageType { MT_FRIEND, MT_ACHIEVEMENT, MT_ERROR, MT_GENERIC};
|
||||
enum MessageType
|
||||
{
|
||||
MT_FRIEND,
|
||||
MT_ACHIEVEMENT,
|
||||
MT_ERROR,
|
||||
MT_GENERIC,
|
||||
MT_PROGRESS
|
||||
};
|
||||
|
||||
void add(MessageType mt, const core::stringw &message);
|
||||
void showProgressBar(int progress, const wchar_t* msg);
|
||||
void updatePosition();
|
||||
void update(float dt);
|
||||
|
||||
|
@ -816,29 +816,25 @@ void Skin::drawProgress(Widget* w, const core::recti &rect,
|
||||
else
|
||||
{
|
||||
ProgressBarWidget * progress = (ProgressBarWidget*)w;
|
||||
drawBoxFromStretchableTexture(w, rect,
|
||||
SkinConfig::m_render_params["progress::neutral"],
|
||||
w->m_deactivated);
|
||||
//the " - 10" is a dirty hack to avoid to have the right arrow
|
||||
// before the left one
|
||||
//FIXME
|
||||
core::recti rect2 = rect;
|
||||
rect2.LowerRightCorner.X -= (rect.getWidth() - 10)
|
||||
- progress->getValue()*rect.getWidth()/100;
|
||||
|
||||
drawBoxFromStretchableTexture(w, rect2,
|
||||
SkinConfig::m_render_params["progress::fill"],
|
||||
w->m_deactivated);
|
||||
#if 0
|
||||
draw2DImage(
|
||||
SkinConfig::m_render_params["progress::fill"].getImage(),
|
||||
sized_rect, core::recti(0,0,progress->m_w, progress->m_h),
|
||||
0 /* no clipping */, colors, true);
|
||||
#endif
|
||||
|
||||
drawProgressBarInScreen(w, rect, progress->getValue(),
|
||||
w->m_deactivated);
|
||||
}
|
||||
} // drawProgress
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
void Skin::drawProgressBarInScreen(SkinWidgetContainer* swc,
|
||||
const core::rect< s32 > &rect, int progress,
|
||||
bool deactivated)
|
||||
{
|
||||
drawBoxFromStretchableTexture(swc, rect,
|
||||
SkinConfig::m_render_params["progress::neutral"], deactivated);
|
||||
core::recti rect2 = rect;
|
||||
rect2.LowerRightCorner.X -= (rect.getWidth())
|
||||
- progress * rect.getWidth() / 100;
|
||||
drawBoxFromStretchableTexture(swc, rect2,
|
||||
SkinConfig::m_render_params["progress::fill"], deactivated);
|
||||
} // drawProgress
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
/**
|
||||
* @param focused whether this element is focus by the master player (focus
|
||||
|
@ -336,6 +336,9 @@ namespace GUIEngine
|
||||
void drawBgImage();
|
||||
void drawBGFadeColor();
|
||||
void drawBadgeOn(const Widget* widget, const core::rect<s32>& rect);
|
||||
void drawProgressBarInScreen(SkinWidgetContainer* swc,
|
||||
const core::rect< s32 > &rect,
|
||||
int progress, bool deactivated = false);
|
||||
|
||||
// irrlicht's callbacks
|
||||
virtual void draw2DRectangle (gui::IGUIElement *element,
|
||||
|
@ -1,84 +0,0 @@
|
||||
// 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;
|
||||
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
|
@ -1,30 +0,0 @@
|
||||
|
||||
// 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
|
@ -1,223 +0,0 @@
|
||||
// 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/string_utils.hpp"
|
||||
|
||||
#include <list>
|
||||
#include <mkvmuxer/mkvmuxer.h>
|
||||
#include <mkvmuxer/mkvwriter.h>
|
||||
#include <mkvparser/mkvparser.h>
|
||||
#include <sys/stat.h>
|
||||
#include <vpx/vpx_encoder.h>
|
||||
|
||||
namespace Recorder
|
||||
{
|
||||
// ------------------------------------------------------------------------
|
||||
std::string writeMKV(const std::string& video, const std::string& audio)
|
||||
{
|
||||
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);
|
||||
std::string no_ext = StringUtils::removeExtension(video);
|
||||
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(file_name.c_str()))
|
||||
{
|
||||
Log::error("writeMKV", "Error while opening output file.");
|
||||
return "";
|
||||
}
|
||||
mkvmuxer::Segment muxer_segment;
|
||||
if (!muxer_segment.Init(&writer))
|
||||
{
|
||||
Log::error("writeMKV", "Could not initialize muxer segment.");
|
||||
return "";
|
||||
}
|
||||
std::list<mkvmuxer::Frame*> audio_frames;
|
||||
uint8_t* buf = (uint8_t*)malloc(1024 * 1024);
|
||||
FILE* input = NULL;
|
||||
struct stat st;
|
||||
int result = stat(audio.c_str(), &st);
|
||||
if (result == 0)
|
||||
{
|
||||
input = fopen(audio.c_str(), "rb");
|
||||
uint32_t sample_rate, channels;
|
||||
fread(&sample_rate, 1, sizeof(uint32_t), input);
|
||||
fread(&channels, 1, sizeof(uint32_t), input);
|
||||
uint64_t aud_track = muxer_segment.AddAudioTrack(sample_rate,
|
||||
channels, 0);
|
||||
if (!aud_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("writeMKV", "Could not get audio track.");
|
||||
return "";
|
||||
}
|
||||
uint32_t codec_private_size;
|
||||
fread(&codec_private_size, 1, sizeof(uint32_t), input);
|
||||
fread(buf, 1, codec_private_size, input);
|
||||
if (!at->SetCodecPrivate(buf, codec_private_size))
|
||||
{
|
||||
Log::warn("writeMKV", "Could not add audio private data.");
|
||||
return "";
|
||||
}
|
||||
while (fread(buf, 1, 12, input) == 12)
|
||||
{
|
||||
uint32_t frame_size;
|
||||
int64_t timestamp;
|
||||
memcpy(&frame_size, buf, sizeof(uint32_t));
|
||||
memcpy(×tamp, buf + sizeof(uint32_t), sizeof(int64_t));
|
||||
fread(buf, 1, frame_size, input);
|
||||
mkvmuxer::Frame* audio_frame = new mkvmuxer::Frame();
|
||||
if (!audio_frame->Init(buf, frame_size))
|
||||
{
|
||||
Log::error("writeMKV", "Failed to construct a frame.");
|
||||
return "";
|
||||
}
|
||||
audio_frame->set_track_number(aud_track);
|
||||
audio_frame->set_timestamp(timestamp);
|
||||
audio_frame->set_is_key(true);
|
||||
audio_frames.push_back(audio_frame);
|
||||
}
|
||||
fclose(input);
|
||||
if (remove(audio.c_str()) != 0)
|
||||
{
|
||||
Log::warn("writeMKV", "Failed to remove audio data file");
|
||||
}
|
||||
}
|
||||
uint64_t vid_track = muxer_segment.AddVideoTrack(
|
||||
irr_driver->getActualScreenSize().Width,
|
||||
irr_driver->getActualScreenSize().Height, 0);
|
||||
if (!vid_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("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)
|
||||
{
|
||||
uint32_t frame_size, flag;
|
||||
int64_t timestamp;
|
||||
memcpy(&frame_size, buf, sizeof(uint32_t));
|
||||
memcpy(×tamp, buf + sizeof(uint32_t), sizeof(int64_t));
|
||||
memcpy(&flag, buf + sizeof(uint32_t) + sizeof(int64_t),
|
||||
sizeof(uint32_t));
|
||||
timestamp *= 1000000000ll / UserConfigParams::m_record_fps;
|
||||
fread(buf, 1, frame_size, input);
|
||||
mkvmuxer::Frame muxer_frame;
|
||||
if (!muxer_frame.Init(buf, frame_size))
|
||||
{
|
||||
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)
|
||||
{
|
||||
while (cur_aud_frame->timestamp() < (uint64_t)timestamp)
|
||||
{
|
||||
if (!muxer_segment.AddGenericFrame(cur_aud_frame))
|
||||
{
|
||||
Log::error("writeMKV", "Could not add audio frame.");
|
||||
return "";
|
||||
}
|
||||
delete cur_aud_frame;
|
||||
audio_frames.pop_front();
|
||||
if (audio_frames.empty())
|
||||
{
|
||||
cur_aud_frame = NULL;
|
||||
break;
|
||||
}
|
||||
cur_aud_frame = audio_frames.front();
|
||||
}
|
||||
}
|
||||
if (!muxer_segment.AddGenericFrame(&muxer_frame))
|
||||
{
|
||||
Log::error("writeMKV", "Could not add video frame.");
|
||||
return "";
|
||||
}
|
||||
}
|
||||
free(buf);
|
||||
fclose(input);
|
||||
for (mkvmuxer::Frame* aud_frame : audio_frames)
|
||||
{
|
||||
delete aud_frame;
|
||||
}
|
||||
if (remove(video.c_str()) != 0)
|
||||
{
|
||||
Log::warn("writeMKV", "Failed to remove video data file");
|
||||
}
|
||||
if (!muxer_segment.Finalize())
|
||||
{
|
||||
Log::error("writeMKV", "Finalization of segment failed.");
|
||||
return "";
|
||||
}
|
||||
writer.Close();
|
||||
return file_name;
|
||||
} // writeMKV
|
||||
};
|
||||
#endif
|
@ -1,31 +0,0 @@
|
||||
|
||||
// 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_MKV_WRITER_HPP
|
||||
#define HEADER_MKV_WRITER_HPP
|
||||
#include <string>
|
||||
|
||||
namespace Recorder
|
||||
{
|
||||
std::string writeMKV(const std::string& video, const std::string& audio);
|
||||
};
|
||||
#endif
|
||||
|
||||
#endif
|
@ -1,567 +0,0 @@
|
||||
// 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.
|
||||
|
||||
#if defined(ENABLE_REC_SOUND) && !defined(WIN32)
|
||||
|
||||
#include "recorder/vorbis_encoder.hpp"
|
||||
#include "utils/synchronised.hpp"
|
||||
#include "utils/log.hpp"
|
||||
#include "utils/vs.hpp"
|
||||
|
||||
#include <cstring>
|
||||
#include <list>
|
||||
#include <pulse/pulseaudio.h>
|
||||
#include <string>
|
||||
|
||||
#ifndef ENABLE_PULSE_WO_DL
|
||||
#include <dlfcn.h>
|
||||
#endif
|
||||
|
||||
namespace Recorder
|
||||
{
|
||||
// ========================================================================
|
||||
void serverInfoCallBack(pa_context* c, const pa_server_info* i, void* data)
|
||||
{
|
||||
*(std::string*)data = i->default_sink_name;
|
||||
} // serverInfoCallBack
|
||||
// ========================================================================
|
||||
struct PulseAudioData
|
||||
{
|
||||
bool m_loaded;
|
||||
pa_mainloop* m_loop;
|
||||
pa_context* m_context;
|
||||
pa_stream* m_stream;
|
||||
pa_sample_spec m_sample_spec;
|
||||
std::string m_default_sink;
|
||||
#ifndef ENABLE_PULSE_WO_DL
|
||||
void* m_dl_handle;
|
||||
|
||||
typedef pa_stream* (*pa_stream_new_t)(pa_context*, const char*,
|
||||
const pa_sample_spec*, const pa_channel_map*);
|
||||
pa_stream_new_t pa_stream_new;
|
||||
|
||||
typedef int (*pa_stream_connect_record_t)(pa_stream*, const char*,
|
||||
const pa_buffer_attr*, pa_stream_flags_t);
|
||||
pa_stream_connect_record_t pa_stream_connect_record;
|
||||
|
||||
typedef pa_stream_state_t (*pa_stream_get_state_t)(pa_stream*);
|
||||
pa_stream_get_state_t pa_stream_get_state;
|
||||
|
||||
typedef size_t (*pa_stream_readable_size_t)(pa_stream*);
|
||||
pa_stream_readable_size_t pa_stream_readable_size;
|
||||
|
||||
typedef int (*pa_stream_peek_t)(pa_stream*, const void**, size_t*);
|
||||
pa_stream_peek_t pa_stream_peek;
|
||||
|
||||
typedef int (*pa_stream_drop_t)(pa_stream*);
|
||||
pa_stream_drop_t pa_stream_drop;
|
||||
|
||||
typedef int (*pa_stream_disconnect_t)(pa_stream*);
|
||||
pa_stream_disconnect_t pa_stream_disconnect;
|
||||
|
||||
typedef void (*pa_stream_unref_t)(pa_stream*);
|
||||
pa_stream_unref_t pa_stream_unref;
|
||||
|
||||
typedef pa_mainloop* (*pa_mainloop_new_t)(void);
|
||||
pa_mainloop_new_t pa_mainloop_new;
|
||||
|
||||
typedef pa_mainloop_api* (*pa_mainloop_get_api_t)(pa_mainloop*);
|
||||
pa_mainloop_get_api_t pa_mainloop_get_api;
|
||||
|
||||
typedef pa_context* (*pa_context_new_t)(pa_mainloop_api*, const char*);
|
||||
pa_context_new_t pa_context_new;
|
||||
|
||||
typedef int (*pa_context_connect_t)(pa_context*, const char*,
|
||||
pa_context_flags_t, const pa_spawn_api*);
|
||||
pa_context_connect_t pa_context_connect;
|
||||
|
||||
typedef int (*pa_mainloop_iterate_t)(pa_mainloop*, int, int*);
|
||||
pa_mainloop_iterate_t pa_mainloop_iterate;
|
||||
|
||||
typedef pa_context_state_t (*pa_context_get_state_t)(pa_context*);
|
||||
pa_context_get_state_t pa_context_get_state;
|
||||
|
||||
typedef pa_operation* (*pa_context_get_server_info_t)(pa_context*,
|
||||
pa_server_info_cb_t, void*);
|
||||
pa_context_get_server_info_t pa_context_get_server_info;
|
||||
|
||||
typedef pa_operation_state_t (*pa_operation_get_state_t)
|
||||
(pa_operation*);
|
||||
pa_operation_get_state_t pa_operation_get_state;
|
||||
|
||||
typedef void (*pa_operation_unref_t)(pa_operation*);
|
||||
pa_operation_unref_t pa_operation_unref;
|
||||
|
||||
typedef void (*pa_context_disconnect_t)(pa_context*);
|
||||
pa_context_disconnect_t pa_context_disconnect;
|
||||
|
||||
typedef void (*pa_context_unref_t)(pa_context*);
|
||||
pa_context_unref_t pa_context_unref;
|
||||
|
||||
typedef void (*pa_mainloop_free_t)(pa_mainloop*);
|
||||
pa_mainloop_free_t pa_mainloop_free;
|
||||
#endif
|
||||
// --------------------------------------------------------------------
|
||||
PulseAudioData()
|
||||
{
|
||||
m_loaded = false;
|
||||
m_loop = NULL;
|
||||
m_context = NULL;
|
||||
m_stream = NULL;
|
||||
#ifndef ENABLE_PULSE_WO_DL
|
||||
m_dl_handle = NULL;
|
||||
pa_stream_new = NULL;
|
||||
pa_stream_connect_record = NULL;
|
||||
pa_stream_get_state = NULL;
|
||||
pa_stream_readable_size = NULL;
|
||||
pa_stream_peek = NULL;
|
||||
pa_stream_drop = NULL;
|
||||
pa_stream_disconnect = NULL;
|
||||
pa_stream_unref = NULL;
|
||||
pa_mainloop_new = NULL;
|
||||
pa_mainloop_get_api = NULL;
|
||||
pa_context_new = NULL;
|
||||
pa_context_connect = NULL;
|
||||
pa_mainloop_iterate = NULL;
|
||||
pa_context_get_state = NULL;
|
||||
pa_context_get_server_info = NULL;
|
||||
pa_operation_get_state = NULL;
|
||||
pa_operation_unref = NULL;
|
||||
pa_context_disconnect = NULL;
|
||||
pa_context_unref = NULL;
|
||||
pa_mainloop_free = NULL;
|
||||
#endif
|
||||
} // PulseAudioData
|
||||
// --------------------------------------------------------------------
|
||||
#ifndef ENABLE_PULSE_WO_DL
|
||||
bool loadPulseAudioLibrary()
|
||||
{
|
||||
m_dl_handle = dlopen("libpulse.so", RTLD_LAZY);
|
||||
if (m_dl_handle == NULL)
|
||||
{
|
||||
Log::error("PulseAudioRecorder", "Failed to open PulseAudio"
|
||||
" library");
|
||||
return false;
|
||||
}
|
||||
pa_stream_new = (pa_stream_new_t)dlsym(m_dl_handle,
|
||||
"pa_stream_new");
|
||||
if (pa_stream_new == NULL)
|
||||
{
|
||||
Log::error("PulseAudioRecorder", "Cannot load function"
|
||||
" 'pa_stream_new'");
|
||||
return false;
|
||||
}
|
||||
pa_stream_connect_record = (pa_stream_connect_record_t)dlsym
|
||||
(m_dl_handle, "pa_stream_connect_record");
|
||||
if (pa_stream_connect_record == NULL)
|
||||
{
|
||||
Log::error("PulseAudioRecorder", "Cannot load function"
|
||||
" 'pa_stream_connect_record'");
|
||||
return false;
|
||||
}
|
||||
pa_stream_get_state = (pa_stream_get_state_t)dlsym(m_dl_handle,
|
||||
"pa_stream_get_state");
|
||||
if (pa_stream_get_state == NULL)
|
||||
{
|
||||
Log::error("PulseAudioRecorder", "Cannot load function"
|
||||
" 'pa_stream_get_state'");
|
||||
return false;
|
||||
}
|
||||
pa_stream_readable_size = (pa_stream_readable_size_t)dlsym
|
||||
(m_dl_handle, "pa_stream_readable_size");
|
||||
if (pa_stream_readable_size == NULL)
|
||||
{
|
||||
Log::error("PulseAudioRecorder", "Cannot load function"
|
||||
" 'pa_stream_readable_size'");
|
||||
return false;
|
||||
}
|
||||
pa_stream_peek = (pa_stream_peek_t)dlsym(m_dl_handle,
|
||||
"pa_stream_peek");
|
||||
if (pa_stream_peek == NULL)
|
||||
{
|
||||
Log::error("PulseAudioRecorder", "Cannot load function"
|
||||
" 'pa_stream_peek'");
|
||||
return false;
|
||||
}
|
||||
pa_stream_drop = (pa_stream_drop_t)dlsym(m_dl_handle,
|
||||
"pa_stream_drop");
|
||||
if (pa_stream_drop == NULL)
|
||||
{
|
||||
Log::error("PulseAudioRecorder", "Cannot load function"
|
||||
" 'pa_stream_drop'");
|
||||
return false;
|
||||
}
|
||||
pa_stream_disconnect = (pa_stream_disconnect_t)dlsym(m_dl_handle,
|
||||
"pa_stream_disconnect");
|
||||
if (pa_stream_disconnect == NULL)
|
||||
{
|
||||
Log::error("PulseAudioRecorder", "Cannot load function"
|
||||
" 'pa_stream_disconnect'");
|
||||
return false;
|
||||
}
|
||||
pa_stream_unref = (pa_stream_unref_t)dlsym(m_dl_handle,
|
||||
"pa_stream_unref");
|
||||
if (pa_stream_unref == NULL)
|
||||
{
|
||||
Log::error("PulseAudioRecorder", "Cannot load function"
|
||||
" 'pa_stream_unref'");
|
||||
return false;
|
||||
}
|
||||
pa_mainloop_new = (pa_mainloop_new_t)dlsym(m_dl_handle,
|
||||
"pa_mainloop_new");
|
||||
if (pa_mainloop_new == NULL)
|
||||
{
|
||||
Log::error("PulseAudioRecorder", "Cannot load function"
|
||||
" 'pa_mainloop_new'");
|
||||
return false;
|
||||
}
|
||||
pa_mainloop_get_api = (pa_mainloop_get_api_t)dlsym(m_dl_handle,
|
||||
"pa_mainloop_get_api");
|
||||
if (pa_mainloop_get_api == NULL)
|
||||
{
|
||||
Log::error("PulseAudioRecorder", "Cannot load function"
|
||||
" 'pa_mainloop_get_api'");
|
||||
return false;
|
||||
}
|
||||
pa_context_new = (pa_context_new_t)dlsym(m_dl_handle,
|
||||
"pa_context_new");
|
||||
if (pa_context_new == NULL)
|
||||
{
|
||||
Log::error("PulseAudioRecorder", "Cannot load function"
|
||||
" 'pa_context_new'");
|
||||
return false;
|
||||
}
|
||||
pa_context_connect = (pa_context_connect_t)dlsym(m_dl_handle,
|
||||
"pa_context_connect");
|
||||
if (pa_context_connect == NULL)
|
||||
{
|
||||
Log::error("PulseAudioRecorder", "Cannot load function"
|
||||
" 'pa_context_connect'");
|
||||
return false;
|
||||
}
|
||||
pa_mainloop_iterate = (pa_mainloop_iterate_t)dlsym(m_dl_handle,
|
||||
"pa_mainloop_iterate");
|
||||
if (pa_mainloop_iterate == NULL)
|
||||
{
|
||||
Log::error("PulseAudioRecorder", "Cannot load function"
|
||||
" 'pa_mainloop_iterate'");
|
||||
return false;
|
||||
}
|
||||
pa_context_get_state = (pa_context_get_state_t)dlsym(m_dl_handle,
|
||||
"pa_context_get_state");
|
||||
if (pa_context_get_state == NULL)
|
||||
{
|
||||
Log::error("PulseAudioRecorder", "Cannot load function"
|
||||
" 'pa_context_get_state'");
|
||||
return false;
|
||||
}
|
||||
pa_context_get_server_info = (pa_context_get_server_info_t)dlsym
|
||||
(m_dl_handle, "pa_context_get_server_info");
|
||||
if (pa_context_get_server_info == NULL)
|
||||
{
|
||||
Log::error("PulseAudioRecorder", "Cannot load function"
|
||||
" 'pa_context_get_server_info'");
|
||||
return false;
|
||||
}
|
||||
pa_operation_get_state = (pa_operation_get_state_t)dlsym
|
||||
(m_dl_handle, "pa_operation_get_state");
|
||||
if (pa_operation_get_state == NULL)
|
||||
{
|
||||
Log::error("PulseAudioRecorder", "Cannot load function"
|
||||
" 'pa_operation_get_state'");
|
||||
return false;
|
||||
}
|
||||
pa_operation_unref = (pa_operation_unref_t)dlsym(m_dl_handle,
|
||||
"pa_operation_unref");
|
||||
if (pa_operation_unref == NULL)
|
||||
{
|
||||
Log::error("PulseAudioRecorder", "Cannot load function"
|
||||
" 'pa_operation_unref'");
|
||||
return false;
|
||||
}
|
||||
pa_context_disconnect = (pa_context_disconnect_t)dlsym(m_dl_handle,
|
||||
"pa_context_disconnect");
|
||||
if (pa_context_disconnect == NULL)
|
||||
{
|
||||
Log::error("PulseAudioRecorder", "Cannot load function"
|
||||
" 'pa_context_disconnect'");
|
||||
return false;
|
||||
}
|
||||
pa_context_unref = (pa_context_unref_t)dlsym(m_dl_handle,
|
||||
"pa_context_unref");
|
||||
if (pa_context_unref == NULL)
|
||||
{
|
||||
Log::error("PulseAudioRecorder", "Cannot load function"
|
||||
" 'pa_context_unref'");
|
||||
return false;
|
||||
}
|
||||
pa_mainloop_free = (pa_mainloop_free_t)dlsym(m_dl_handle,
|
||||
"pa_mainloop_free");
|
||||
if (pa_mainloop_free == NULL)
|
||||
{
|
||||
Log::error("PulseAudioRecorder", "Cannot load function"
|
||||
" 'pa_mainloop_free'");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} // loadPulseAudioLibrary
|
||||
#endif
|
||||
// --------------------------------------------------------------------
|
||||
bool load()
|
||||
{
|
||||
#ifndef ENABLE_PULSE_WO_DL
|
||||
if (!loadPulseAudioLibrary())
|
||||
{
|
||||
if (m_dl_handle != NULL)
|
||||
{
|
||||
dlclose(m_dl_handle);
|
||||
m_dl_handle = NULL;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
m_loop = pa_mainloop_new();
|
||||
if (m_loop == NULL)
|
||||
{
|
||||
Log::error("PulseAudioRecorder", "Failed to create mainloop");
|
||||
return false;
|
||||
}
|
||||
m_context = pa_context_new(pa_mainloop_get_api(m_loop),
|
||||
"audioRecord");
|
||||
if (m_context == NULL)
|
||||
{
|
||||
Log::error("PulseAudioRecorder", "Failed to create context");
|
||||
return false;
|
||||
}
|
||||
pa_context_connect(m_context, NULL, PA_CONTEXT_NOAUTOSPAWN , NULL);
|
||||
while (true)
|
||||
{
|
||||
while (pa_mainloop_iterate(m_loop, 0, NULL) > 0);
|
||||
pa_context_state_t state = pa_context_get_state(m_context);
|
||||
if (state == PA_CONTEXT_READY)
|
||||
break;
|
||||
if (!PA_CONTEXT_IS_GOOD(state))
|
||||
{
|
||||
Log::error("PulseAudioRecorder", "Failed to connect to"
|
||||
" context");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
pa_operation* pa_op = pa_context_get_server_info(m_context,
|
||||
serverInfoCallBack, &m_default_sink);
|
||||
enum pa_operation_state op_state;
|
||||
while ((op_state =
|
||||
pa_operation_get_state(pa_op)) == PA_OPERATION_RUNNING)
|
||||
pa_mainloop_iterate(m_loop, 0, NULL);
|
||||
pa_operation_unref(pa_op);
|
||||
if (m_default_sink.empty())
|
||||
{
|
||||
Log::error("PulseAudioRecorder", "Failed to get default sink");
|
||||
return false;
|
||||
}
|
||||
m_default_sink += ".monitor";
|
||||
m_sample_spec.format = PA_SAMPLE_S16LE;
|
||||
m_sample_spec.rate = 44100;
|
||||
m_sample_spec.channels = 2;
|
||||
|
||||
m_loaded = true;
|
||||
return true;
|
||||
} // load
|
||||
// --------------------------------------------------------------------
|
||||
void configAudioType(Recorder::VorbisEncoderData* ved)
|
||||
{
|
||||
ved->m_sample_rate = m_sample_spec.rate;
|
||||
ved->m_channels = m_sample_spec.channels;
|
||||
ved->m_audio_type = Recorder::VorbisEncoderData::AT_PCM;
|
||||
} // configAudioType
|
||||
// --------------------------------------------------------------------
|
||||
inline void mainLoopIterate()
|
||||
{
|
||||
while (pa_mainloop_iterate(m_loop, 0, NULL) > 0);
|
||||
} // mainLoopIterate
|
||||
// --------------------------------------------------------------------
|
||||
bool createRecordStream()
|
||||
{
|
||||
assert(m_stream == NULL);
|
||||
m_stream = pa_stream_new(m_context, "input", &m_sample_spec, NULL);
|
||||
if (m_stream == NULL)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
pa_buffer_attr buf_attr;
|
||||
const unsigned frag_size = 1024 * m_sample_spec.channels *
|
||||
sizeof(int16_t);
|
||||
buf_attr.fragsize = frag_size;
|
||||
const unsigned max_uint = -1;
|
||||
buf_attr.maxlength = max_uint;
|
||||
buf_attr.minreq = max_uint;
|
||||
buf_attr.prebuf = max_uint;
|
||||
buf_attr.tlength = max_uint;
|
||||
pa_stream_connect_record(m_stream, m_default_sink.c_str(),
|
||||
&buf_attr, (pa_stream_flags_t)(PA_STREAM_ADJUST_LATENCY));
|
||||
while (true)
|
||||
{
|
||||
mainLoopIterate();
|
||||
pa_stream_state_t state = pa_stream_get_state(m_stream);
|
||||
if (state == PA_STREAM_READY)
|
||||
break;
|
||||
if (!PA_STREAM_IS_GOOD(state))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} // createRecordStream
|
||||
// --------------------------------------------------------------------
|
||||
void removeRecordStream()
|
||||
{
|
||||
assert(m_stream != NULL);
|
||||
pa_stream_disconnect(m_stream);
|
||||
pa_stream_unref(m_stream);
|
||||
m_stream = NULL;
|
||||
} // removeRecordStream
|
||||
// --------------------------------------------------------------------
|
||||
inline size_t getReadableSize()
|
||||
{
|
||||
assert(m_stream != NULL);
|
||||
return pa_stream_readable_size(m_stream);
|
||||
} // removeRecordStream
|
||||
// --------------------------------------------------------------------
|
||||
inline void peekStream(const void** data, size_t* bytes)
|
||||
{
|
||||
assert(m_stream != NULL);
|
||||
pa_stream_peek(m_stream, data, bytes);
|
||||
} // peekStream
|
||||
// --------------------------------------------------------------------
|
||||
inline void dropStream()
|
||||
{
|
||||
assert(m_stream != NULL);
|
||||
pa_stream_drop(m_stream);
|
||||
} // dropStream
|
||||
// --------------------------------------------------------------------
|
||||
~PulseAudioData()
|
||||
{
|
||||
if (m_loaded)
|
||||
{
|
||||
if (m_context != NULL)
|
||||
{
|
||||
pa_context_disconnect(m_context);
|
||||
pa_context_unref(m_context);
|
||||
}
|
||||
if (m_loop != NULL)
|
||||
{
|
||||
pa_mainloop_free(m_loop);
|
||||
}
|
||||
#ifndef ENABLE_PULSE_WO_DL
|
||||
if (m_dl_handle != NULL)
|
||||
{
|
||||
dlclose(m_dl_handle);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
} // ~PulseAudioData
|
||||
};
|
||||
// ========================================================================
|
||||
PulseAudioData g_pa_data;
|
||||
// ========================================================================
|
||||
void* audioRecorder(void *obj)
|
||||
{
|
||||
VS::setThreadName("audioRecorder");
|
||||
if (!g_pa_data.m_loaded)
|
||||
{
|
||||
if (!g_pa_data.load())
|
||||
{
|
||||
Log::error("PulseAudioRecord", "Cannot pulseaudio data");
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (g_pa_data.createRecordStream() == false)
|
||||
{
|
||||
Log::error("PulseAudioRecorder", "Failed to create stream");
|
||||
if (g_pa_data.m_stream != NULL)
|
||||
{
|
||||
g_pa_data.removeRecordStream();
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
Synchronised<bool>* idle = (Synchronised<bool>*)obj;
|
||||
Synchronised<std::list<int8_t*> > pcm_data;
|
||||
pthread_cond_t enc_request;
|
||||
pthread_cond_init(&enc_request, NULL);
|
||||
pthread_t vorbis_enc;
|
||||
|
||||
Recorder::VorbisEncoderData ved;
|
||||
g_pa_data.configAudioType(&ved);
|
||||
ved.m_data = &pcm_data;
|
||||
ved.m_enc_request = &enc_request;
|
||||
const unsigned frag_size = 1024 * g_pa_data.m_sample_spec.channels *
|
||||
sizeof(int16_t);
|
||||
pthread_create(&vorbis_enc, NULL, &Recorder::vorbisEncoder, &ved);
|
||||
int8_t* each_pcm_buf = new int8_t[frag_size]();
|
||||
unsigned readed = 0;
|
||||
while (true)
|
||||
{
|
||||
if (idle->getAtomic())
|
||||
{
|
||||
pcm_data.lock();
|
||||
pcm_data.getData().push_back(each_pcm_buf);
|
||||
pcm_data.getData().push_back(NULL);
|
||||
pthread_cond_signal(&enc_request);
|
||||
pcm_data.unlock();
|
||||
break;
|
||||
}
|
||||
g_pa_data.mainLoopIterate();
|
||||
const void* data;
|
||||
size_t bytes;
|
||||
size_t readable = g_pa_data.getReadableSize();
|
||||
if (readable == 0)
|
||||
continue;
|
||||
g_pa_data.peekStream(&data, &bytes);
|
||||
if (data == NULL)
|
||||
{
|
||||
if (bytes > 0)
|
||||
g_pa_data.dropStream();
|
||||
continue;
|
||||
}
|
||||
bool buf_full = readed + (unsigned)bytes > frag_size;
|
||||
unsigned copy_size = buf_full ?
|
||||
frag_size - readed : (unsigned)bytes;
|
||||
memcpy(each_pcm_buf + readed, data, copy_size);
|
||||
if (buf_full)
|
||||
{
|
||||
pcm_data.lock();
|
||||
pcm_data.getData().push_back(each_pcm_buf);
|
||||
pthread_cond_signal(&enc_request);
|
||||
pcm_data.unlock();
|
||||
each_pcm_buf = new int8_t[frag_size]();
|
||||
readed = (unsigned)bytes - copy_size;
|
||||
memcpy(each_pcm_buf, (uint8_t*)data + copy_size, readed);
|
||||
}
|
||||
else
|
||||
{
|
||||
readed += (unsigned)bytes;
|
||||
}
|
||||
g_pa_data.dropStream();
|
||||
}
|
||||
pthread_join(vorbis_enc, NULL);
|
||||
pthread_cond_destroy(&enc_request);
|
||||
g_pa_data.removeRecordStream();
|
||||
return NULL;
|
||||
} // audioRecorder
|
||||
}
|
||||
#endif
|
@ -1,34 +0,0 @@
|
||||
// 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.
|
||||
|
||||
#ifndef WIN32
|
||||
|
||||
#ifndef HEADER_PULSEAUDIO_RECORD_HPP
|
||||
#define HEADER_PULSEAUDIO_RECORD_HPP
|
||||
|
||||
namespace Recorder
|
||||
{
|
||||
#ifdef ENABLE_REC_SOUND
|
||||
void* audioRecorder(void *obj);
|
||||
#else
|
||||
inline void* audioRecorder(void *obj) { return NULL; }
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
@ -1,401 +0,0 @@
|
||||
// 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 "recorder/recorder_common.hpp"
|
||||
#include "config/user_config.hpp"
|
||||
#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/vpx_encoder.hpp"
|
||||
#include "recorder/wasapi_recorder.hpp"
|
||||
#include "utils/synchronised.hpp"
|
||||
#include "utils/translation.hpp"
|
||||
#include "utils/vs.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <cassert>
|
||||
#include <turbojpeg.h>
|
||||
|
||||
namespace Recorder
|
||||
{
|
||||
// ========================================================================
|
||||
tjhandle g_compress_handle;
|
||||
// ========================================================================
|
||||
Synchronised<std::list<std::tuple<uint8_t*, unsigned, int> > > g_jpg_list;
|
||||
// ========================================================================
|
||||
pthread_cond_t g_jpg_request;
|
||||
// ========================================================================
|
||||
ThreadData g_jpg_thread_data;
|
||||
// ========================================================================
|
||||
int bmpToJPG(uint8_t* raw, unsigned width, unsigned height,
|
||||
uint8_t** jpeg_buffer, unsigned long* jpeg_size)
|
||||
{
|
||||
int ret = 0;
|
||||
#ifdef TJFLAG_FASTDCT
|
||||
ret = tjCompress2(g_compress_handle, raw, width, 0, height, TJPF_BGR,
|
||||
jpeg_buffer, jpeg_size, TJSAMP_420,
|
||||
UserConfigParams::m_recorder_jpg_quality, TJFLAG_FASTDCT);
|
||||
#else
|
||||
ret = tjCompress2(g_compress_handle, raw, width, 0, height, TJPF_BGR,
|
||||
jpeg_buffer, jpeg_size, TJSAMP_420,
|
||||
UserConfigParams::m_recorder_jpg_quality, 0);
|
||||
#endif
|
||||
if (ret != 0)
|
||||
{
|
||||
char* err = tjGetErrorStr();
|
||||
Log::error("RecorderCommon", "Jpeg encode error: %s.", err);
|
||||
return ret;
|
||||
}
|
||||
return ret;
|
||||
} // bmpToJPG
|
||||
// ========================================================================
|
||||
pthread_t g_audio_thread;
|
||||
// ========================================================================
|
||||
Synchronised<pthread_t*> g_video_thread(NULL);
|
||||
// ========================================================================
|
||||
Synchronised<bool> g_idle(true);
|
||||
// ========================================================================
|
||||
bool g_destroy;
|
||||
// ========================================================================
|
||||
std::string g_recording_name;
|
||||
// ========================================================================
|
||||
Synchronised<bool> g_display_progress(false);
|
||||
// ========================================================================
|
||||
void* fbiConversion(void* obj)
|
||||
{
|
||||
VS::setThreadName("fbiConversion");
|
||||
ThreadData* td = (ThreadData*)obj;
|
||||
Synchronised<std::list<std::pair<uint8_t*, int> > >* fbi_queue =
|
||||
(Synchronised<std::list<std::pair<uint8_t*, int> > >*)td->m_data;
|
||||
pthread_cond_t* cond_request = td->m_request;
|
||||
while (true)
|
||||
{
|
||||
fbi_queue->lock();
|
||||
bool waiting = fbi_queue->getData().empty();
|
||||
while (waiting)
|
||||
{
|
||||
pthread_cond_wait(cond_request, fbi_queue->getMutex());
|
||||
waiting = fbi_queue->getData().empty();
|
||||
}
|
||||
auto& p = fbi_queue->getData().front();
|
||||
uint8_t* fbi = p.first;
|
||||
int frame_count = p.second;
|
||||
if (frame_count == -1)
|
||||
{
|
||||
fbi_queue->getData().clear();
|
||||
fbi_queue->unlock();
|
||||
g_idle.setAtomic(true);
|
||||
pthread_join(g_audio_thread, NULL);
|
||||
g_jpg_list.lock();
|
||||
if (!g_destroy && g_jpg_list.getData().size() > 100)
|
||||
{
|
||||
MessageQueue::add(MessageQueue::MT_GENERIC,
|
||||
_("Please wait while encoding is finished."));
|
||||
}
|
||||
g_jpg_list.getData().emplace_back((uint8_t*)NULL, 0, 0);
|
||||
pthread_cond_signal(&g_jpg_request);
|
||||
g_jpg_list.unlock();
|
||||
g_display_progress.setAtomic(true);
|
||||
pthread_join(*g_video_thread.getData(), NULL);
|
||||
delete g_video_thread.getData();
|
||||
g_video_thread.setAtomic(NULL);
|
||||
std::string f = Recorder::writeMKV(g_recording_name + ".video",
|
||||
g_recording_name + ".audio");
|
||||
g_display_progress.setAtomic(false);
|
||||
if (g_destroy)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
if (f.empty())
|
||||
{
|
||||
MessageQueue::add(MessageQueue::MT_ERROR,
|
||||
_("Error when saving video."));
|
||||
}
|
||||
else
|
||||
{
|
||||
MessageQueue::add(MessageQueue::MT_GENERIC,
|
||||
_("Video saved in \"%s\".", f.c_str()));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
else if (fbi == NULL)
|
||||
{
|
||||
fbi_queue->getData().clear();
|
||||
fbi_queue->unlock();
|
||||
if (g_destroy)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
const bool too_slow = fbi_queue->getData().size() > 50;
|
||||
if (too_slow)
|
||||
{
|
||||
MessageQueue::add(MessageQueue::MT_ERROR,
|
||||
_("Encoding is too slow, dropping frames."));
|
||||
delete [] fbi;
|
||||
fbi_queue->getData().pop_front();
|
||||
for (auto& p : fbi_queue->getData())
|
||||
delete [] p.first;
|
||||
fbi_queue->getData().clear();
|
||||
fbi_queue->unlock();
|
||||
continue;
|
||||
}
|
||||
fbi_queue->getData().pop_front();
|
||||
fbi_queue->unlock();
|
||||
uint8_t* orig_fbi = fbi;
|
||||
const unsigned width = irr_driver->getActualScreenSize().Width;
|
||||
const unsigned height = irr_driver->getActualScreenSize().Height;
|
||||
const unsigned area = width * height;
|
||||
int size = area * 4;
|
||||
int dest = size - 3;
|
||||
int src = size - 4;
|
||||
int copied = 0;
|
||||
while (true)
|
||||
{
|
||||
if (copied++ > 1)
|
||||
memcpy(fbi + dest, fbi + src, 3);
|
||||
else
|
||||
memmove(fbi + dest, fbi + src, 3);
|
||||
if (src == 0)
|
||||
break;
|
||||
dest -= 3;
|
||||
src -= 4;
|
||||
}
|
||||
fbi = fbi + area;
|
||||
const int pitch = width * 3;
|
||||
uint8_t* p2 = fbi + (height - 1) * pitch;
|
||||
uint8_t* tmp_buf = new uint8_t[pitch];
|
||||
for (unsigned i = 0; i < height; i += 2)
|
||||
{
|
||||
memcpy(tmp_buf, fbi, pitch);
|
||||
memcpy(fbi, p2, pitch);
|
||||
memcpy(p2, tmp_buf, pitch);
|
||||
fbi += pitch;
|
||||
p2 -= pitch;
|
||||
}
|
||||
delete [] tmp_buf;
|
||||
uint8_t* jpg = NULL;
|
||||
unsigned long jpg_size = 0;
|
||||
bmpToJPG(orig_fbi + area, width, height, &jpg, &jpg_size);
|
||||
delete[] orig_fbi;
|
||||
g_jpg_list.lock();
|
||||
g_jpg_list.getData().emplace_back(jpg, jpg_size, frame_count);
|
||||
pthread_cond_signal(&g_jpg_request);
|
||||
g_jpg_list.unlock();
|
||||
}
|
||||
return NULL;
|
||||
} // fbiConversion
|
||||
// ========================================================================
|
||||
struct CommonData : public NoCopy
|
||||
{
|
||||
GLuint m_pbo[3];
|
||||
unsigned m_pbo_use;
|
||||
bool m_loaded;
|
||||
Synchronised<std::list<std::pair<uint8_t*, int> > > m_fbi_queue;
|
||||
pthread_cond_t m_fbi_request;
|
||||
pthread_t m_fbi_thread;
|
||||
ThreadData m_common_thread_data;
|
||||
// --------------------------------------------------------------------
|
||||
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_fbi_request);
|
||||
m_fbi_queue.unlock();
|
||||
} // addFrameBufferImage
|
||||
// --------------------------------------------------------------------
|
||||
CommonData()
|
||||
{
|
||||
m_loaded = false;
|
||||
m_pbo_use = 0;
|
||||
g_compress_handle = tjInitCompress();
|
||||
} // CommonData
|
||||
// --------------------------------------------------------------------
|
||||
~CommonData()
|
||||
{
|
||||
destroy();
|
||||
tjDestroy(g_compress_handle);
|
||||
} // ~CommonData
|
||||
// --------------------------------------------------------------------
|
||||
void destroy()
|
||||
{
|
||||
if (m_loaded)
|
||||
{
|
||||
glDeleteBuffers(3, m_pbo);
|
||||
addFrameBufferImage(NULL, 0);
|
||||
pthread_join(m_fbi_thread, NULL);
|
||||
pthread_cond_destroy(&m_fbi_request);
|
||||
pthread_cond_destroy(&g_jpg_request);
|
||||
g_destroy = false;
|
||||
}
|
||||
m_loaded = false;
|
||||
} // destroy
|
||||
// --------------------------------------------------------------------
|
||||
void load()
|
||||
{
|
||||
if (m_loaded) return;
|
||||
m_loaded = true;
|
||||
glGenBuffers(3, m_pbo);
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, m_pbo[i]);
|
||||
glBufferData(GL_PIXEL_PACK_BUFFER,
|
||||
irr_driver->getActualScreenSize().Width *
|
||||
irr_driver->getActualScreenSize().Height * 4, NULL,
|
||||
GL_STREAM_READ);
|
||||
}
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
|
||||
pthread_cond_init(&m_fbi_request, NULL);
|
||||
pthread_cond_init(&g_jpg_request, NULL);
|
||||
m_common_thread_data.m_data = &m_fbi_queue;
|
||||
m_common_thread_data.m_request = &m_fbi_request;
|
||||
g_jpg_thread_data.m_data = &g_jpg_list;
|
||||
g_jpg_thread_data.m_request = &g_jpg_request;
|
||||
pthread_create(&m_fbi_thread, NULL, &fbiConversion,
|
||||
&m_common_thread_data);
|
||||
} // load
|
||||
};
|
||||
// ========================================================================
|
||||
std::chrono::high_resolution_clock::time_point g_framerate_timer;
|
||||
// ========================================================================
|
||||
double g_accumulated_time;
|
||||
// ========================================================================
|
||||
CommonData g_common_data;
|
||||
// ========================================================================
|
||||
void setRecordingName(const std::string& name)
|
||||
{
|
||||
g_recording_name = name;
|
||||
} // setRecordingName
|
||||
// ------------------------------------------------------------------------
|
||||
const std::string& getRecordingName()
|
||||
{
|
||||
return g_recording_name;
|
||||
} // getRecordingName
|
||||
// ------------------------------------------------------------------------
|
||||
void prepareCapture()
|
||||
{
|
||||
g_common_data.load();
|
||||
g_common_data.m_pbo_use = 0;
|
||||
g_accumulated_time = 0.;
|
||||
assert(g_idle.getAtomic() && g_video_thread.getAtomic() == NULL);
|
||||
g_idle.setAtomic(false);
|
||||
pthread_create(&g_audio_thread, NULL, &Recorder::audioRecorder,
|
||||
&g_idle);
|
||||
g_video_thread.setAtomic(new pthread_t());
|
||||
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)
|
||||
{
|
||||
const double frame_rate = 1. / double(UserConfigParams::m_record_fps);
|
||||
g_accumulated_time += rate;
|
||||
if (g_accumulated_time < frame_rate)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
int frame_count = 0;
|
||||
while (g_accumulated_time >= frame_rate)
|
||||
{
|
||||
frame_count++;
|
||||
g_accumulated_time = g_accumulated_time - frame_rate;
|
||||
}
|
||||
return frame_count;
|
||||
} // getFrameCount
|
||||
// ------------------------------------------------------------------------
|
||||
void captureFrameBufferImage()
|
||||
{
|
||||
assert(g_common_data.m_loaded);
|
||||
int pbo_read = -1;
|
||||
if (g_common_data.m_pbo_use > 3 && g_common_data.m_pbo_use % 3 == 0)
|
||||
g_common_data.m_pbo_use = 3;
|
||||
auto rate = std::chrono::high_resolution_clock::now() -
|
||||
g_framerate_timer;
|
||||
g_framerate_timer = std::chrono::high_resolution_clock::now();
|
||||
glReadBuffer(GL_BACK);
|
||||
const unsigned width = irr_driver->getActualScreenSize().Width;
|
||||
const unsigned height = irr_driver->getActualScreenSize().Height;
|
||||
if (g_common_data.m_pbo_use >= 3)
|
||||
{
|
||||
int frame_count = getFrameCount(std::chrono::duration_cast
|
||||
<std::chrono::duration<double> >(rate).count());
|
||||
if (frame_count != 0)
|
||||
{
|
||||
pbo_read = g_common_data.m_pbo_use % 3;
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER,
|
||||
g_common_data.m_pbo[pbo_read]);
|
||||
void* ptr = glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY);
|
||||
const unsigned size = width * height * 4;
|
||||
uint8_t* fbi = new uint8_t[size];
|
||||
memcpy(fbi, ptr, size);
|
||||
glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
|
||||
g_common_data.addFrameBufferImage(fbi, frame_count);
|
||||
}
|
||||
}
|
||||
int pbo_use = g_common_data.m_pbo_use++ % 3;
|
||||
assert(pbo_read == -1 || pbo_use == pbo_read);
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, g_common_data.m_pbo[pbo_use]);
|
||||
glReadPixels(0, 0, width, height, GL_BGRA, GL_UNSIGNED_BYTE, NULL);
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
|
||||
} // captureFrameBufferImage
|
||||
// ------------------------------------------------------------------------
|
||||
void stopRecording()
|
||||
{
|
||||
if (!isRecording())
|
||||
{
|
||||
g_common_data.addFrameBufferImage(NULL, -1);
|
||||
}
|
||||
} // stopRecording
|
||||
// ------------------------------------------------------------------------
|
||||
bool isRecording()
|
||||
{
|
||||
return g_video_thread.getAtomic() == NULL;
|
||||
} // isRecording
|
||||
// ------------------------------------------------------------------------
|
||||
void destroyRecorder()
|
||||
{
|
||||
g_destroy = true;
|
||||
stopRecording();
|
||||
g_common_data.destroy();
|
||||
} // destroyRecorder
|
||||
// ------------------------------------------------------------------------
|
||||
bool displayProgress()
|
||||
{
|
||||
return g_display_progress.getAtomic();
|
||||
} // displayProgress
|
||||
|
||||
}
|
||||
#endif
|
@ -1,60 +0,0 @@
|
||||
|
||||
// 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_RECORDER_COMMON_HPP
|
||||
#define HEADER_RECORDER_COMMON_HPP
|
||||
|
||||
#include "utils/no_copy.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <list>
|
||||
#include <pthread.h>
|
||||
|
||||
namespace Recorder
|
||||
{
|
||||
// ------------------------------------------------------------------------
|
||||
enum VideoFormat { VF_VP8, VF_VP9, VF_MJPEG, VF_H264 };
|
||||
// ------------------------------------------------------------------------
|
||||
struct ThreadData : public NoCopy
|
||||
{
|
||||
void* m_data;
|
||||
pthread_cond_t* m_request;
|
||||
};
|
||||
// ------------------------------------------------------------------------
|
||||
void setRecordingName(const std::string& name);
|
||||
// ------------------------------------------------------------------------
|
||||
const std::string& getRecordingName();
|
||||
// ------------------------------------------------------------------------
|
||||
void prepareCapture();
|
||||
// ------------------------------------------------------------------------
|
||||
void captureFrameBufferImage();
|
||||
// ------------------------------------------------------------------------
|
||||
void stopRecording();
|
||||
// ------------------------------------------------------------------------
|
||||
bool isRecording();
|
||||
// ------------------------------------------------------------------------
|
||||
void destroyRecorder();
|
||||
// ------------------------------------------------------------------------
|
||||
bool displayProgress();
|
||||
|
||||
};
|
||||
#endif
|
||||
|
||||
#endif
|
@ -1,159 +0,0 @@
|
||||
// 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 "recorder/vorbis_encoder.hpp"
|
||||
#include "recorder/recorder_common.hpp"
|
||||
#include "utils/log.hpp"
|
||||
#include "utils/synchronised.hpp"
|
||||
#include "utils/vs.hpp"
|
||||
|
||||
#include <ogg/ogg.h>
|
||||
#include <vorbis/vorbisenc.h>
|
||||
|
||||
namespace Recorder
|
||||
{
|
||||
void* vorbisEncoder(void *obj)
|
||||
{
|
||||
VS::setThreadName("vorbisEncoder");
|
||||
VorbisEncoderData* ved = (VorbisEncoderData*)obj;
|
||||
vorbis_info vi;
|
||||
vorbis_dsp_state vd;
|
||||
vorbis_block vb;
|
||||
vorbis_info_init(&vi);
|
||||
vorbis_encode_init(&vi, ved->m_channels, ved->m_sample_rate, -1,
|
||||
112000, -1);
|
||||
vorbis_analysis_init(&vd, &vi);
|
||||
vorbis_block_init(&vd, &vb);
|
||||
vorbis_comment vc;
|
||||
vorbis_comment_init(&vc);
|
||||
vorbis_comment_add_tag(&vc, "ENCODER", "STK vorbis encoder");
|
||||
ogg_packet header;
|
||||
ogg_packet header_comm;
|
||||
ogg_packet header_code;
|
||||
vorbis_analysis_headerout(&vd, &vc, &header, &header_comm,
|
||||
&header_code);
|
||||
if (header.bytes > 255 || header_comm.bytes > 255)
|
||||
{
|
||||
Log::error("vorbisEncoder", "Header is too long.");
|
||||
return NULL;
|
||||
}
|
||||
FILE* vb_data = fopen((getRecordingName() + ".audio").c_str(), "wb");
|
||||
if (vb_data == NULL)
|
||||
{
|
||||
Log::error("vorbisEncoder", "Failed to open file for encoding"
|
||||
" vorbis.");
|
||||
return NULL;
|
||||
}
|
||||
fwrite(&ved->m_sample_rate, 1, sizeof(uint32_t), vb_data);
|
||||
fwrite(&ved->m_channels, 1, sizeof(uint32_t), vb_data);
|
||||
const uint32_t all = header.bytes + header_comm.bytes +
|
||||
header_code.bytes + 3;
|
||||
fwrite(&all, 1, sizeof(uint32_t), vb_data);
|
||||
uint8_t size = 2;
|
||||
fwrite(&size, 1, sizeof(uint8_t), vb_data);
|
||||
size = (uint8_t)header.bytes;
|
||||
fwrite(&size, 1, sizeof(uint8_t), vb_data);
|
||||
size = (uint8_t)header_comm.bytes;
|
||||
fwrite(&size, 1, sizeof(uint8_t), vb_data);
|
||||
fwrite(header.packet, 1, header.bytes, vb_data);
|
||||
fwrite(header_comm.packet, 1, header_comm.bytes, vb_data);
|
||||
fwrite(header_code.packet, 1, header_code.bytes, vb_data);
|
||||
Synchronised<std::list<int8_t*> >* audio_data =
|
||||
(Synchronised<std::list<int8_t*> >*)ved->m_data;
|
||||
pthread_cond_t* cond_request = ved->m_enc_request;
|
||||
ogg_packet op;
|
||||
int64_t last_timestamp = 0;
|
||||
bool eos = false;
|
||||
while (eos == false)
|
||||
{
|
||||
audio_data->lock();
|
||||
bool waiting = audio_data->getData().empty();
|
||||
while (waiting)
|
||||
{
|
||||
pthread_cond_wait(cond_request, audio_data->getMutex());
|
||||
waiting = audio_data->getData().empty();
|
||||
}
|
||||
int8_t* audio_buf = audio_data->getData().front();
|
||||
audio_data->getData().pop_front();
|
||||
audio_data->unlock();
|
||||
if (audio_buf == NULL)
|
||||
{
|
||||
vorbis_analysis_wrote(&vd, 0);
|
||||
eos = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
float **buffer = vorbis_analysis_buffer(&vd, 1024);
|
||||
const unsigned channels = ved->m_channels;
|
||||
if (ved->m_audio_type == VorbisEncoderData::AT_PCM)
|
||||
{
|
||||
for (unsigned j = 0; j < channels; j++)
|
||||
{
|
||||
for (unsigned i = 0; i < 1024; i++)
|
||||
{
|
||||
int8_t* each_channel =
|
||||
&audio_buf[i * channels * 2 + j * 2];
|
||||
buffer[j][i] = float((each_channel[1] << 8) |
|
||||
(0x00ff & (int)each_channel[0])) / 32768.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (unsigned j = 0; j < channels; j++)
|
||||
{
|
||||
for (unsigned i = 0; i < 1024; i++)
|
||||
{
|
||||
float* fbuf = reinterpret_cast<float*>(audio_buf);
|
||||
buffer[j][i] = fbuf[i * channels + j];
|
||||
}
|
||||
}
|
||||
}
|
||||
vorbis_analysis_wrote(&vd, 1024);
|
||||
}
|
||||
while (vorbis_analysis_blockout(&vd, &vb) == 1)
|
||||
{
|
||||
vorbis_analysis(&vb, NULL);
|
||||
vorbis_bitrate_addblock(&vb);
|
||||
while (vorbis_bitrate_flushpacket(&vd, &op))
|
||||
{
|
||||
if (op.granulepos > 0)
|
||||
{
|
||||
uint32_t frame_size = (uint32_t)op.bytes;
|
||||
fwrite(&frame_size, 1, sizeof(uint32_t), vb_data);
|
||||
fwrite(&last_timestamp, 1, sizeof(int64_t), vb_data);
|
||||
fwrite(op.packet, 1, frame_size, vb_data);
|
||||
double s = (double)op.granulepos /
|
||||
(double)ved->m_sample_rate * 1000000000.;
|
||||
last_timestamp = (int64_t)s;
|
||||
}
|
||||
}
|
||||
}
|
||||
delete[] audio_buf;
|
||||
}
|
||||
vorbis_block_clear(&vb);
|
||||
vorbis_dsp_clear(&vd);
|
||||
vorbis_comment_clear(&vc);
|
||||
vorbis_info_clear(&vi);
|
||||
fclose(vb_data);
|
||||
return NULL;
|
||||
|
||||
} // vorbisEncoder
|
||||
}
|
||||
#endif
|
@ -1,45 +0,0 @@
|
||||
// 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_VORBIS_ENCODE_HPP
|
||||
#define HEADER_VORBIS_ENCODE_HPP
|
||||
|
||||
#include "utils/no_copy.hpp"
|
||||
#include "utils/types.hpp"
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
namespace Recorder
|
||||
{
|
||||
struct VorbisEncoderData : public NoCopy
|
||||
{
|
||||
enum AudioType { AT_FLOAT, AT_PCM };
|
||||
void* m_data;
|
||||
pthread_cond_t* m_enc_request;
|
||||
uint32_t m_sample_rate;
|
||||
uint32_t m_channels;
|
||||
AudioType m_audio_type;
|
||||
};
|
||||
|
||||
void* vorbisEncoder(void *obj);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
@ -1,232 +0,0 @@
|
||||
// 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.
|
||||
|
||||
#if defined(ENABLE_RECORDER) && !defined(NO_VPX)
|
||||
|
||||
#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 <chrono>
|
||||
#include <turbojpeg.h>
|
||||
#include <vpx/vpx_encoder.h>
|
||||
#include <vpx/vp8cx.h>
|
||||
|
||||
namespace Recorder
|
||||
{
|
||||
// ========================================================================
|
||||
struct JPGDecoder
|
||||
{
|
||||
tjhandle m_handle;
|
||||
// --------------------------------------------------------------------
|
||||
JPGDecoder()
|
||||
{
|
||||
m_handle = tjInitDecompress();
|
||||
} // JPGDecoder
|
||||
// --------------------------------------------------------------------
|
||||
~JPGDecoder()
|
||||
{
|
||||
tjDestroy(m_handle);
|
||||
} // ~JPGDecoder
|
||||
// --------------------------------------------------------------------
|
||||
int yuvConversion(uint8_t* jpeg_buffer, unsigned jpeg_size,
|
||||
uint8_t** yuv_buffer, unsigned* yuv_size)
|
||||
{
|
||||
int width, height;
|
||||
TJSAMP subsample;
|
||||
int ret = 0;
|
||||
ret = tjDecompressHeader2(m_handle, jpeg_buffer, jpeg_size, &width,
|
||||
&height, (int*)&subsample);
|
||||
if (ret != 0)
|
||||
{
|
||||
char* err = tjGetErrorStr();
|
||||
Log::error("vpxEncoder", "Jpeg decode error: %s.", err);
|
||||
return ret;
|
||||
}
|
||||
*yuv_size = tjBufSizeYUV(width, height, subsample);
|
||||
*yuv_buffer = new uint8_t[*yuv_size];
|
||||
ret = tjDecompressToYUV(m_handle, jpeg_buffer, jpeg_size,
|
||||
*yuv_buffer, 0);
|
||||
if (ret != 0)
|
||||
{
|
||||
char* err = tjGetErrorStr();
|
||||
Log::error("vpxEncoder", "YUV conversion error: %s.", err);
|
||||
return ret;
|
||||
}
|
||||
return ret;
|
||||
} // yuvConversion
|
||||
};
|
||||
// ========================================================================
|
||||
JPGDecoder g_jpg_decoder;
|
||||
// ========================================================================
|
||||
int vpxEncodeFrame(vpx_codec_ctx_t *codec, vpx_image_t *img,
|
||||
int frame_index, FILE *out)
|
||||
{
|
||||
int got_pkts = 0;
|
||||
vpx_codec_iter_t iter = NULL;
|
||||
const vpx_codec_cx_pkt_t *pkt = NULL;
|
||||
const vpx_codec_err_t res = vpx_codec_encode(codec, img, frame_index,
|
||||
1, 0, VPX_DL_REALTIME);
|
||||
if (res != VPX_CODEC_OK)
|
||||
{
|
||||
Log::error("vpxEncoder", "Failed to encode frame");
|
||||
return -1;
|
||||
}
|
||||
while ((pkt = vpx_codec_get_cx_data(codec, &iter)) != NULL)
|
||||
{
|
||||
got_pkts = 1;
|
||||
if (pkt->kind == VPX_CODEC_CX_FRAME_PKT)
|
||||
{
|
||||
fwrite(&pkt->data.frame.sz, 1, sizeof(uint32_t), out);
|
||||
fwrite(&pkt->data.frame.pts, 1, sizeof(int64_t), out);
|
||||
fwrite(&pkt->data.frame.flags, 1,
|
||||
sizeof(vpx_codec_frame_flags_t), out);
|
||||
fwrite(pkt->data.frame.buf, 1, pkt->data.frame.sz, out);
|
||||
}
|
||||
}
|
||||
return got_pkts;
|
||||
} // vpxEncodeFrame
|
||||
// ------------------------------------------------------------------------
|
||||
void* vpxEncoder(void *obj)
|
||||
{
|
||||
VS::setThreadName("vpxEncoder");
|
||||
FILE* vpx_data = fopen((getRecordingName() + ".video").c_str(), "wb");
|
||||
if (vpx_data == NULL)
|
||||
{
|
||||
Log::error("vpxEncoder", "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;
|
||||
|
||||
vpx_codec_ctx_t codec;
|
||||
vpx_codec_enc_cfg_t cfg;
|
||||
vpx_codec_iface_t* codec_if = NULL;
|
||||
VideoFormat vf = (VideoFormat)(int)UserConfigParams::m_record_format;
|
||||
switch (vf)
|
||||
{
|
||||
case VF_VP8:
|
||||
codec_if = vpx_codec_vp8_cx();
|
||||
break;
|
||||
case VF_VP9:
|
||||
codec_if = vpx_codec_vp9_cx();
|
||||
break;
|
||||
case VF_MJPEG:
|
||||
case VF_H264:
|
||||
assert(false);
|
||||
break;
|
||||
}
|
||||
vpx_codec_err_t res = vpx_codec_enc_config_default(codec_if, &cfg, 0);
|
||||
if (res > 0)
|
||||
{
|
||||
Log::error("vpxEncoder", "Failed to get default codec config.");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const unsigned width = irr_driver->getActualScreenSize().Width;
|
||||
const unsigned height = irr_driver->getActualScreenSize().Height;
|
||||
int frames_encoded = 0;
|
||||
cfg.g_w = width;
|
||||
cfg.g_h = height;
|
||||
cfg.g_timebase.num = 1;
|
||||
cfg.g_timebase.den = UserConfigParams::m_record_fps;
|
||||
int end_usage = UserConfigParams::m_vp_end_usage;
|
||||
cfg.rc_end_usage = (vpx_rc_mode)end_usage;
|
||||
cfg.rc_target_bitrate = UserConfigParams::m_vp_bitrate;
|
||||
|
||||
if (vpx_codec_enc_init(&codec, codec_if, &cfg, 0) > 0)
|
||||
{
|
||||
Log::error("vpxEncoder", "Failed to initialize encoder");
|
||||
fclose(vpx_data);
|
||||
return NULL;
|
||||
}
|
||||
std::chrono::high_resolution_clock::time_point tp;
|
||||
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();
|
||||
}
|
||||
|
||||
if (displayProgress())
|
||||
{
|
||||
auto rate = std::chrono::high_resolution_clock::now() -
|
||||
tp;
|
||||
double t = std::chrono::duration_cast<std::chrono::
|
||||
duration<double> >(rate).count();
|
||||
if (t > 3.)
|
||||
{
|
||||
tp = std::chrono::high_resolution_clock::now();
|
||||
Log::info("vpxEncoder", "%d frames remaining.",
|
||||
jpg_data->getData().size());
|
||||
}
|
||||
}
|
||||
auto& p = jpg_data->getData().front();
|
||||
uint8_t* jpg = std::get<0>(p);
|
||||
unsigned 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();
|
||||
uint8_t* yuv = NULL;
|
||||
unsigned yuv_size;
|
||||
int ret = g_jpg_decoder.yuvConversion(jpg, jpg_size, &yuv,
|
||||
&yuv_size);
|
||||
if (ret < 0)
|
||||
{
|
||||
delete [] yuv;
|
||||
tjFree(jpg);
|
||||
continue;
|
||||
}
|
||||
assert(yuv_size != 0);
|
||||
tjFree(jpg);
|
||||
vpx_image_t each_frame;
|
||||
vpx_img_wrap(&each_frame, VPX_IMG_FMT_I420, width, height, 1, yuv);
|
||||
while (frame_count != 0)
|
||||
{
|
||||
vpxEncodeFrame(&codec, &each_frame, frames_encoded++, vpx_data);
|
||||
frame_count--;
|
||||
}
|
||||
delete [] yuv;
|
||||
}
|
||||
|
||||
while (vpxEncodeFrame(&codec, NULL, -1, vpx_data));
|
||||
if (vpx_codec_destroy(&codec))
|
||||
{
|
||||
Log::error("vpxEncoder", "Failed to destroy codec.");
|
||||
return NULL;
|
||||
}
|
||||
fclose(vpx_data);
|
||||
return NULL;
|
||||
|
||||
} // vpxEncoder
|
||||
}
|
||||
#endif
|
@ -1,34 +0,0 @@
|
||||
|
||||
// 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_VPX_ENCODER_HPP
|
||||
#define HEADER_VPX_ENCODER_HPP
|
||||
|
||||
namespace Recorder
|
||||
{
|
||||
#ifdef NO_VPX
|
||||
inline void* vpxEncoder(void *obj) { return NULL; }
|
||||
#else
|
||||
void* vpxEncoder(void *obj);
|
||||
#endif
|
||||
};
|
||||
#endif
|
||||
|
||||
#endif
|
@ -1,309 +0,0 @@
|
||||
// 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.
|
||||
|
||||
#if defined(ENABLE_REC_SOUND) && defined(WIN32)
|
||||
|
||||
#include "recorder/vorbis_encoder.hpp"
|
||||
#include "utils/synchronised.hpp"
|
||||
#include "utils/log.hpp"
|
||||
#include "utils/vs.hpp"
|
||||
|
||||
#include <list>
|
||||
|
||||
#include <audioclient.h>
|
||||
#include <mmsystem.h>
|
||||
#include <mmreg.h>
|
||||
#include <mmdeviceapi.h>
|
||||
#include <windows.h>
|
||||
|
||||
#if defined (__MINGW32__) || defined(__CYGWIN__)
|
||||
#include "utils/types.hpp"
|
||||
inline GUID uuidFromString(const char* s)
|
||||
{
|
||||
unsigned long p0;
|
||||
unsigned int p1, p2, p3, p4, p5, p6, p7, p8, p9, p10;
|
||||
sscanf(s, "%08lX-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X",
|
||||
&p0, &p1, &p2, &p3, &p4, &p5, &p6, &p7, &p8, &p9, &p10);
|
||||
GUID g = { p0, (uint16_t)p1, (uint16_t)p2, { (uint8_t)p3, (uint8_t)p4,
|
||||
(uint8_t)p5, (uint8_t)p6, (uint8_t)p7, (uint8_t)p8, (uint8_t)p9,
|
||||
(uint8_t)p10 }};
|
||||
return g;
|
||||
}
|
||||
#undef KSDATAFORMAT_SUBTYPE_PCM
|
||||
#define KSDATAFORMAT_SUBTYPE_PCM \
|
||||
uuidFromString("00000001-0000-0010-8000-00aa00389b71")
|
||||
#undef KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
|
||||
#define KSDATAFORMAT_SUBTYPE_IEEE_FLOAT \
|
||||
uuidFromString("00000003-0000-0010-8000-00aa00389b71")
|
||||
#endif
|
||||
|
||||
namespace Recorder
|
||||
{
|
||||
// ========================================================================
|
||||
const REFERENCE_TIME REFTIMES_PER_SEC = 10000000;
|
||||
// ========================================================================
|
||||
struct WasapiData
|
||||
{
|
||||
bool m_loaded;
|
||||
IMMDeviceEnumerator* m_dev_enum;
|
||||
IMMDevice* m_dev;
|
||||
IAudioClient* m_client;
|
||||
IAudioCaptureClient* m_capture_client;
|
||||
WAVEFORMATEX* m_wav_format;
|
||||
uint32_t m_buffer_size;
|
||||
// --------------------------------------------------------------------
|
||||
WasapiData()
|
||||
{
|
||||
m_loaded = false;
|
||||
m_dev_enum = NULL;
|
||||
m_dev = NULL;
|
||||
m_client = NULL;
|
||||
m_capture_client = NULL;
|
||||
m_wav_format = NULL;
|
||||
} // WasapiData
|
||||
// --------------------------------------------------------------------
|
||||
bool load()
|
||||
{
|
||||
HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL,
|
||||
CLSCTX_ALL, __uuidof(IMMDeviceEnumerator),
|
||||
(void**)&m_dev_enum);
|
||||
if (FAILED(hr))
|
||||
return false;
|
||||
|
||||
hr = m_dev_enum->GetDefaultAudioEndpoint(eRender, eConsole,
|
||||
&m_dev);
|
||||
if (FAILED(hr))
|
||||
return false;
|
||||
|
||||
hr = m_dev->Activate(__uuidof(IAudioClient), CLSCTX_ALL, NULL,
|
||||
(void**)&m_client);
|
||||
if (FAILED(hr))
|
||||
return false;
|
||||
|
||||
hr = m_client->GetMixFormat(&m_wav_format);
|
||||
if (FAILED(hr))
|
||||
return false;
|
||||
|
||||
hr = m_client->Initialize(AUDCLNT_SHAREMODE_SHARED,
|
||||
AUDCLNT_STREAMFLAGS_LOOPBACK, REFTIMES_PER_SEC, 0,
|
||||
m_wav_format, NULL);
|
||||
if (FAILED(hr))
|
||||
return false;
|
||||
|
||||
hr = m_client->GetBufferSize(&m_buffer_size);
|
||||
if (FAILED(hr))
|
||||
return false;
|
||||
|
||||
hr = m_client->GetService(__uuidof(IAudioCaptureClient),
|
||||
(void**)&m_capture_client);
|
||||
if (FAILED(hr))
|
||||
return false;
|
||||
|
||||
m_loaded = true;
|
||||
return true;
|
||||
} // load
|
||||
// --------------------------------------------------------------------
|
||||
~WasapiData()
|
||||
{
|
||||
if (m_loaded)
|
||||
{
|
||||
CoTaskMemFree(m_wav_format);
|
||||
if (m_dev_enum)
|
||||
m_dev_enum->Release();
|
||||
if (m_dev)
|
||||
m_dev->Release();
|
||||
if (m_client)
|
||||
m_client->Release();
|
||||
if (m_capture_client)
|
||||
m_capture_client->Release();
|
||||
}
|
||||
} // ~WasapiData
|
||||
};
|
||||
// ========================================================================
|
||||
WasapiData g_wasapi_data;
|
||||
// ========================================================================
|
||||
void* audioRecorder(void *obj)
|
||||
{
|
||||
VS::setThreadName("audioRecorder");
|
||||
if (!g_wasapi_data.m_loaded)
|
||||
{
|
||||
if (!g_wasapi_data.load())
|
||||
{
|
||||
Log::error("WasapiRecorder", "Failed to load wasapi data");
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
VorbisEncoderData ved = {};
|
||||
if (g_wasapi_data.m_wav_format->wFormatTag == WAVE_FORMAT_EXTENSIBLE)
|
||||
{
|
||||
WAVEFORMATEXTENSIBLE* wav_for_ext =
|
||||
(WAVEFORMATEXTENSIBLE*)g_wasapi_data.m_wav_format;
|
||||
ved.m_channels = wav_for_ext->Format.nChannels;
|
||||
ved.m_sample_rate = wav_for_ext->Format.nSamplesPerSec;
|
||||
if (IsEqualGUID(KSDATAFORMAT_SUBTYPE_PCM, wav_for_ext->SubFormat))
|
||||
{
|
||||
ved.m_audio_type = VorbisEncoderData::AT_PCM;
|
||||
if (wav_for_ext->Format.wBitsPerSample != 16)
|
||||
{
|
||||
Log::error("WasapiRecorder", "Only 16bit PCM is"
|
||||
" supported.");
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
else if (IsEqualGUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, wav_for_ext
|
||||
->SubFormat))
|
||||
{
|
||||
ved.m_audio_type = VorbisEncoderData::AT_FLOAT;
|
||||
if (wav_for_ext->Format.wBitsPerSample != 32)
|
||||
{
|
||||
Log::error("WasapiRecorder", "Only 32bit float is"
|
||||
" supported.");
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Log::error("WasapiRecorder", "Unsupported audio input"
|
||||
" format.");
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
else if (g_wasapi_data.m_wav_format->wFormatTag == WAVE_FORMAT_PCM)
|
||||
{
|
||||
ved.m_channels = g_wasapi_data.m_wav_format->nChannels;
|
||||
ved.m_sample_rate = g_wasapi_data.m_wav_format->nSamplesPerSec;
|
||||
ved.m_audio_type = VorbisEncoderData::AT_PCM;
|
||||
if (g_wasapi_data.m_wav_format->wBitsPerSample != 16)
|
||||
{
|
||||
Log::error("WasapiRecorder", "Only 16bit PCM is supported.");
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Log::error("WasapiRecorder", "Unsupported audio input format");
|
||||
return NULL;
|
||||
}
|
||||
if (ved.m_sample_rate > 48000)
|
||||
{
|
||||
Log::error("WasapiRecorder", "Only support maximum 48000hz sample "
|
||||
"rate audio.");
|
||||
return NULL;
|
||||
}
|
||||
HRESULT hr = g_wasapi_data.m_client->Reset();
|
||||
if (FAILED(hr))
|
||||
{
|
||||
Log::error("WasapiRecorder", "Failed to reset recorder");
|
||||
return NULL;
|
||||
}
|
||||
hr = g_wasapi_data.m_client->Start();
|
||||
if (FAILED(hr))
|
||||
{
|
||||
Log::error("WasapiRecorder", "Failed to start recorder");
|
||||
return NULL;
|
||||
}
|
||||
REFERENCE_TIME duration = REFTIMES_PER_SEC *
|
||||
g_wasapi_data.m_buffer_size / g_wasapi_data.m_wav_format
|
||||
->nSamplesPerSec;
|
||||
|
||||
Synchronised<bool>* idle = (Synchronised<bool>*)obj;
|
||||
Synchronised<std::list<int8_t*> > audio_data;
|
||||
pthread_cond_t enc_request;
|
||||
pthread_cond_init(&enc_request, NULL);
|
||||
pthread_t vorbis_enc;
|
||||
ved.m_data = &audio_data;
|
||||
ved.m_enc_request = &enc_request;
|
||||
pthread_create(&vorbis_enc, NULL, &Recorder::vorbisEncoder, &ved);
|
||||
const unsigned frag_size = 1024 * ved.m_channels *
|
||||
(g_wasapi_data.m_wav_format->wBitsPerSample / 8);
|
||||
int8_t* each_audio_buf = new int8_t[frag_size]();
|
||||
unsigned readed = 0;
|
||||
while (true)
|
||||
{
|
||||
if (idle->getAtomic())
|
||||
{
|
||||
audio_data.lock();
|
||||
audio_data.getData().push_back(each_audio_buf);
|
||||
audio_data.getData().push_back(NULL);
|
||||
pthread_cond_signal(&enc_request);
|
||||
audio_data.unlock();
|
||||
break;
|
||||
}
|
||||
uint32_t packet_length = 0;
|
||||
hr = g_wasapi_data.m_capture_client->GetNextPacketSize(
|
||||
&packet_length);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
Log::warn("WasapiRecorder", "Failed to get next packet size");
|
||||
}
|
||||
if (packet_length == 0)
|
||||
{
|
||||
REFERENCE_TIME sleep_time = duration / 10000 / 2;
|
||||
Sleep((uint32_t)sleep_time);
|
||||
continue;
|
||||
}
|
||||
BYTE* data;
|
||||
DWORD flags;
|
||||
hr = g_wasapi_data.m_capture_client->GetBuffer(&data,
|
||||
&packet_length, &flags, NULL, NULL);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
Log::warn("WasapiRecorder", "Failed to get buffer");
|
||||
}
|
||||
const unsigned bytes = ved.m_channels * (g_wasapi_data.m_wav_format
|
||||
->wBitsPerSample / 8) * packet_length;
|
||||
bool buf_full = readed + bytes > frag_size;
|
||||
unsigned copy_size = buf_full ? frag_size - readed : bytes;
|
||||
if (!(flags & AUDCLNT_BUFFERFLAGS_SILENT))
|
||||
{
|
||||
memcpy(each_audio_buf + readed, data, copy_size);
|
||||
}
|
||||
if (buf_full)
|
||||
{
|
||||
audio_data.lock();
|
||||
audio_data.getData().push_back(each_audio_buf);
|
||||
pthread_cond_signal(&enc_request);
|
||||
audio_data.unlock();
|
||||
each_audio_buf = new int8_t[frag_size]();
|
||||
readed = bytes - copy_size;
|
||||
if (!(flags & AUDCLNT_BUFFERFLAGS_SILENT))
|
||||
{
|
||||
memcpy(each_audio_buf, (uint8_t*)data + copy_size, readed);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
readed += bytes;
|
||||
}
|
||||
hr = g_wasapi_data.m_capture_client->ReleaseBuffer(packet_length);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
Log::warn("WasapiRecorder", "Failed to release buffer");
|
||||
}
|
||||
}
|
||||
hr = g_wasapi_data.m_client->Stop();
|
||||
if (FAILED(hr))
|
||||
{
|
||||
Log::warn("WasapiRecorder", "Failed to stop recorder");
|
||||
}
|
||||
pthread_join(vorbis_enc, NULL);
|
||||
pthread_cond_destroy(&enc_request);
|
||||
|
||||
return NULL;
|
||||
} // audioRecorder
|
||||
}
|
||||
#endif
|
@ -1,34 +0,0 @@
|
||||
// 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 WIN32
|
||||
|
||||
#ifndef HEADER_WASAPI_RECORD_HPP
|
||||
#define HEADER_WASAPI_RECORD_HPP
|
||||
|
||||
namespace Recorder
|
||||
{
|
||||
#ifdef ENABLE_REC_SOUND
|
||||
void* audioRecorder(void *obj);
|
||||
#else
|
||||
inline void* audioRecorder(void *obj) { return NULL; }
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue
Block a user