Allow recording audio in windows using wasapi
This commit is contained in:
parent
29d0839162
commit
cacbdbd668
@ -119,16 +119,24 @@ if (APPLE)
|
|||||||
set(JPEG_LIBRARY jpeglib)
|
set(JPEG_LIBRARY jpeglib)
|
||||||
else()
|
else()
|
||||||
find_package(JPEG REQUIRED)
|
find_package(JPEG REQUIRED)
|
||||||
find_library(TURBOJPEG_LIBRARY NAMES turbojpeg)
|
find_library(TURBOJPEG_LIBRARY NAMES turbojpeg libturbojpeg PATHS "${PROJECT_SOURCE_DIR}/dependencies/lib")
|
||||||
mark_as_advanced(TURBOJPEG_LIBRARY)
|
mark_as_advanced(TURBOJPEG_LIBRARY)
|
||||||
include_directories(${JPEG_INCLUDE_DIR})
|
include_directories(${JPEG_INCLUDE_DIR})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
include(FindPkgConfig)
|
if (UNIX AND NOT APPLE)
|
||||||
pkg_check_modules(PULSEAUDIO libpulse)
|
include(FindPkgConfig)
|
||||||
include_directories(${PULSEAUDIO_INCLUDEDIR})
|
pkg_check_modules(PULSEAUDIO libpulse)
|
||||||
|
include_directories(${PULSEAUDIO_INCLUDEDIR})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if (UNIX AND NOT APPLE)
|
||||||
|
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 vpxmt PATHS "${PROJECT_SOURCE_DIR}/dependencies/lib")
|
||||||
|
endif()
|
||||||
|
|
||||||
pkg_check_modules(VPX vpx)
|
|
||||||
include_directories(${VPX_INCLUDEDIR})
|
include_directories(${VPX_INCLUDEDIR})
|
||||||
|
|
||||||
if(NOT SERVER_ONLY AND NOT USE_GLES2)
|
if(NOT SERVER_ONLY AND NOT USE_GLES2)
|
||||||
@ -394,7 +402,6 @@ target_link_libraries(supertuxkart
|
|||||||
${OPENAL_LIBRARY}
|
${OPENAL_LIBRARY}
|
||||||
${FREETYPE_LIBRARIES}
|
${FREETYPE_LIBRARIES}
|
||||||
${JPEG_LIBRARIES}
|
${JPEG_LIBRARIES}
|
||||||
${PULSEAUDIO_LIBRARIES}
|
|
||||||
${TURBOJPEG_LIBRARY}
|
${TURBOJPEG_LIBRARY}
|
||||||
${VPX_LIBRARIES}
|
${VPX_LIBRARIES}
|
||||||
)
|
)
|
||||||
@ -408,7 +415,7 @@ if(NOT SERVER_ONLY)
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(UNIX AND NOT APPLE)
|
if(UNIX AND NOT APPLE)
|
||||||
target_link_libraries(supertuxkart ${X11_LIBRARIES} ${XRANDR_LIBRARIES})
|
target_link_libraries(supertuxkart ${X11_LIBRARIES} ${XRANDR_LIBRARIES} ${PULSEAUDIO_LIBRARIES})
|
||||||
if(USE_LIBBFD)
|
if(USE_LIBBFD)
|
||||||
target_link_libraries(supertuxkart ${LIBBFD_LIBRARIES})
|
target_link_libraries(supertuxkart ${LIBBFD_LIBRARIES})
|
||||||
endif()
|
endif()
|
||||||
|
@ -15,7 +15,7 @@ find_path(OGGVORBIS_VORBIS_INCLUDE_DIR NAMES vorbis/vorbisfile.h PATHS "${PROJEC
|
|||||||
find_library(OGGVORBIS_OGG_LIBRARY NAMES ogg Ogg libogg PATHS "${PROJECT_SOURCE_DIR}/dependencies/lib")
|
find_library(OGGVORBIS_OGG_LIBRARY NAMES ogg Ogg libogg PATHS "${PROJECT_SOURCE_DIR}/dependencies/lib")
|
||||||
find_library(OGGVORBIS_VORBIS_LIBRARY NAMES vorbis Vorbis libvorbis PATHS "${PROJECT_SOURCE_DIR}/dependencies/lib")
|
find_library(OGGVORBIS_VORBIS_LIBRARY NAMES vorbis Vorbis libvorbis PATHS "${PROJECT_SOURCE_DIR}/dependencies/lib")
|
||||||
find_library(OGGVORBIS_VORBISFILE_LIBRARY NAMES vorbisfile libvorbisfile PATHS "${PROJECT_SOURCE_DIR}/dependencies/lib")
|
find_library(OGGVORBIS_VORBISFILE_LIBRARY NAMES vorbisfile libvorbisfile PATHS "${PROJECT_SOURCE_DIR}/dependencies/lib")
|
||||||
find_library(OGGVORBIS_VORBISENC_LIBRARY NAMES vorbisenc)
|
find_library(OGGVORBIS_VORBISENC_LIBRARY NAMES vorbisenc libvorbisenc PATHS "${PROJECT_SOURCE_DIR}/dependencies/lib")
|
||||||
|
|
||||||
if (APPLE)
|
if (APPLE)
|
||||||
set(OGGVORBIS_OGG_INCLUDE_DIR "/Library/Frameworks/Ogg.framework/Headers/")
|
set(OGGVORBIS_OGG_INCLUDE_DIR "/Library/Frameworks/Ogg.framework/Headers/")
|
||||||
|
155
src/recorder/vorbis_encode.cpp
Normal file
155
src/recorder/vorbis_encode.cpp
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
// 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(SERVER_ONLY) || defined(USE_GLES2))
|
||||||
|
|
||||||
|
#include "recorder/vorbis_encode.hpp"
|
||||||
|
#include "utils/avi_writer.hpp"
|
||||||
|
#include "utils/log.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((AVIWriter::getRecordingTarget() + ".vb_data")
|
||||||
|
.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;
|
||||||
|
const unsigned channels = ved->m_channels;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
long i = 0;
|
||||||
|
if (audio_buf == NULL)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
float **buffer = vorbis_analysis_buffer(&vd, 1024);
|
||||||
|
if (ved->m_audio_type == VorbisEncoderData::AT_PCM)
|
||||||
|
{
|
||||||
|
for (i = 0; i < 1024; i++)
|
||||||
|
{
|
||||||
|
buffer[0][i] = ((audio_buf[i * 4 + 1] << 8) |
|
||||||
|
(0x00ff & (int)audio_buf[i * 4])) / 32768.0f;
|
||||||
|
buffer[1][i] = ((audio_buf[i * 4 + 3] << 8) |
|
||||||
|
(0x00ff & (int)audio_buf[i * 4 + 2])) / 32768.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
float* float_buf = reinterpret_cast<float*>(audio_buf);
|
||||||
|
for (unsigned j = 0; j < channels; j++)
|
||||||
|
{
|
||||||
|
for (i = 0; i < 1024; i++)
|
||||||
|
{
|
||||||
|
buffer[j][i] = float_buf[i * channels + j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vorbis_analysis_wrote(&vd, i);
|
||||||
|
}
|
||||||
|
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 / 44100. * 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;
|
||||||
|
|
||||||
|
} // vorbisEncode
|
||||||
|
}
|
||||||
|
#endif
|
45
src/recorder/vorbis_encode.hpp
Normal file
45
src/recorder/vorbis_encode.hpp
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
// 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(SERVER_ONLY) || defined(USE_GLES2))
|
||||||
|
|
||||||
|
#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
|
273
src/recorder/wasapi_record.cpp
Normal file
273
src/recorder/wasapi_record.cpp
Normal file
@ -0,0 +1,273 @@
|
|||||||
|
// 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(SERVER_ONLY) || defined(USE_GLES2)) && defined(WIN32)
|
||||||
|
|
||||||
|
#include "recorder/vorbis_encode.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>
|
||||||
|
|
||||||
|
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* audioRecord(void *obj)
|
||||||
|
{
|
||||||
|
VS::setThreadName("audioRecord");
|
||||||
|
if (!g_wasapi_data.m_loaded)
|
||||||
|
{
|
||||||
|
if (!g_wasapi_data.load())
|
||||||
|
{
|
||||||
|
Log::error("WasapiRecord", "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("WasapiRecord", "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("WasapiRecord", "Only 32bit float is"
|
||||||
|
" supported.");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log::error("WasapiRecord", "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("WasapiRecord", "Only 16bit PCM is supported.");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log::error("WasapiRecord", "Unsupported audio input format");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
HRESULT hr = g_wasapi_data.m_client->Start();
|
||||||
|
if (FAILED(hr))
|
||||||
|
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*> > pcm_data;
|
||||||
|
pthread_cond_t enc_request;
|
||||||
|
pthread_cond_init(&enc_request, NULL);
|
||||||
|
pthread_t vorbis_enc;
|
||||||
|
ved.m_data = &pcm_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_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);
|
||||||
|
pthread_cond_signal(&enc_request);
|
||||||
|
pcm_data.unlock();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
REFERENCE_TIME sleep_time = duration / 10000 / 2;
|
||||||
|
Sleep((uint32_t)sleep_time);
|
||||||
|
uint32_t packet_length;
|
||||||
|
hr = g_wasapi_data.m_capture_client->GetNextPacketSize(
|
||||||
|
&packet_length);
|
||||||
|
if (FAILED(hr))
|
||||||
|
return NULL;
|
||||||
|
while (packet_length != 0)
|
||||||
|
{
|
||||||
|
BYTE* data;
|
||||||
|
uint32_t frame_size;
|
||||||
|
DWORD flags;
|
||||||
|
hr = g_wasapi_data.m_capture_client->GetBuffer(&data,
|
||||||
|
&frame_size, &flags, NULL, NULL);
|
||||||
|
if (FAILED(hr))
|
||||||
|
return NULL;
|
||||||
|
const unsigned bytes = ved.m_channels *
|
||||||
|
(g_wasapi_data.m_wav_format->wBitsPerSample / 8) *
|
||||||
|
frame_size;
|
||||||
|
bool buf_full = readed + bytes > frag_size;
|
||||||
|
unsigned copy_size = buf_full ? frag_size - readed : bytes;
|
||||||
|
if (!(flags & AUDCLNT_BUFFERFLAGS_SILENT))
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
if (!(flags & AUDCLNT_BUFFERFLAGS_SILENT))
|
||||||
|
{
|
||||||
|
memcpy(each_pcm_buf, (uint8_t*)data + copy_size,
|
||||||
|
readed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
readed += (unsigned)bytes;
|
||||||
|
}
|
||||||
|
hr = g_wasapi_data.m_capture_client->ReleaseBuffer(frame_size);
|
||||||
|
if (FAILED(hr))
|
||||||
|
return NULL;
|
||||||
|
if (idle->getAtomic())
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
hr = g_wasapi_data.m_capture_client->GetNextPacketSize(
|
||||||
|
&frame_size);
|
||||||
|
if (FAILED(hr))
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pcm_data.lock();
|
||||||
|
pcm_data.getData().push_back(NULL);
|
||||||
|
pthread_cond_signal(&enc_request);
|
||||||
|
pcm_data.unlock();
|
||||||
|
pthread_join(vorbis_enc, NULL);
|
||||||
|
pthread_cond_destroy(&enc_request);
|
||||||
|
|
||||||
|
hr = g_wasapi_data.m_client->Stop();
|
||||||
|
if (FAILED(hr))
|
||||||
|
return NULL;
|
||||||
|
return NULL;
|
||||||
|
} // audioRecord
|
||||||
|
}
|
||||||
|
#endif
|
30
src/recorder/wasapi_record.hpp
Normal file
30
src/recorder/wasapi_record.hpp
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
// SuperTuxKart - a fun racing game with go-kart
|
||||||
|
// Copyright (C) 2017 SuperTuxKart-Team
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or
|
||||||
|
// modify it under the terms of the GNU General Public License
|
||||||
|
// as published by the Free Software Foundation; either version 3
|
||||||
|
// of the License, or (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program; if not, write to the Free Software
|
||||||
|
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||||
|
|
||||||
|
#if !(defined(SERVER_ONLY) || defined(USE_GLES2)) && defined(WIN32)
|
||||||
|
|
||||||
|
#ifndef HEADER_WASAPI_RECORD_HPP
|
||||||
|
#define HEADER_WASAPI_RECORD_HPP
|
||||||
|
|
||||||
|
namespace Recorder
|
||||||
|
{
|
||||||
|
void* audioRecord(void *obj);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
@ -27,6 +27,7 @@
|
|||||||
#include <mkvmuxer/mkvmuxer.h>
|
#include <mkvmuxer/mkvmuxer.h>
|
||||||
#include <mkvmuxer/mkvwriter.h>
|
#include <mkvmuxer/mkvwriter.h>
|
||||||
#include <mkvparser/mkvparser.h>
|
#include <mkvparser/mkvparser.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
#include <vpx/vpx_encoder.h>
|
#include <vpx/vpx_encoder.h>
|
||||||
|
|
||||||
namespace Recorder
|
namespace Recorder
|
||||||
@ -56,53 +57,63 @@ namespace Recorder
|
|||||||
Log::error("writeWebm", "Could not initialize muxer segment.");
|
Log::error("writeWebm", "Could not initialize muxer segment.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
uint64_t aud_track = muxer_segment.AddAudioTrack(44100, 2, 0);
|
|
||||||
if (!aud_track)
|
|
||||||
{
|
|
||||||
Log::error("writeWebm", "Could not add audio track.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
mkvmuxer::AudioTrack* const at = static_cast<mkvmuxer::AudioTrack*>(
|
|
||||||
muxer_segment.GetTrackByNumber(aud_track));
|
|
||||||
if (!at)
|
|
||||||
{
|
|
||||||
Log::error("writeWebm", "Could not get audio track.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
FILE* input = fopen(audio.c_str(), "rb");
|
|
||||||
uint32_t codec_private_size;
|
|
||||||
fread(&codec_private_size, 1, sizeof(uint32_t), input);
|
|
||||||
uint8_t* buf = (uint8_t*)malloc(1024 * 1024);
|
|
||||||
fread(buf, 1, codec_private_size, input);
|
|
||||||
if (!at->SetCodecPrivate(buf, codec_private_size))
|
|
||||||
{
|
|
||||||
Log::warn("writeWebm", "Could not add audio private data.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
std::list<mkvmuxer::Frame*> audio_frames;
|
std::list<mkvmuxer::Frame*> audio_frames;
|
||||||
while (fread(buf, 1, 12, input) == 12)
|
uint8_t* buf = (uint8_t*)malloc(1024 * 1024);
|
||||||
|
FILE* input = NULL;
|
||||||
|
struct stat st;
|
||||||
|
int result = stat(audio.c_str(), &st);
|
||||||
|
if (result == 0)
|
||||||
{
|
{
|
||||||
uint32_t frame_size;
|
input = fopen(audio.c_str(), "rb");
|
||||||
int64_t timestamp;
|
uint32_t sample_rate, channels;
|
||||||
memcpy(&frame_size, buf, sizeof(uint32_t));
|
fread(&sample_rate, 1, sizeof(uint32_t), input);
|
||||||
memcpy(×tamp, buf + sizeof(uint32_t), sizeof(int64_t));
|
fread(&channels, 1, sizeof(uint32_t), input);
|
||||||
fread(buf, 1, frame_size, input);
|
uint64_t aud_track = muxer_segment.AddAudioTrack(sample_rate,
|
||||||
mkvmuxer::Frame* audio_frame = new mkvmuxer::Frame();
|
channels, 0);
|
||||||
if (!audio_frame->Init(buf, frame_size))
|
if (!aud_track)
|
||||||
{
|
{
|
||||||
Log::error("writeWebm", "Failed to construct a frame.");
|
Log::error("writeWebm", "Could not add audio track.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
audio_frame->set_track_number(aud_track);
|
mkvmuxer::AudioTrack* const at = static_cast<mkvmuxer::AudioTrack*>
|
||||||
audio_frame->set_timestamp(timestamp);
|
(muxer_segment.GetTrackByNumber(aud_track));
|
||||||
audio_frame->set_is_key(true);
|
if (!at)
|
||||||
audio_frames.push_back(audio_frame);
|
{
|
||||||
|
Log::error("writeWebm", "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("writeWebm", "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("writeWebm", "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("writeWebm", "Failed to remove audio data file");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (remove(audio.c_str()) != 0)
|
|
||||||
{
|
|
||||||
Log::warn("writeWebm", "Failed to remove audio data file");
|
|
||||||
}
|
|
||||||
fclose(input);
|
|
||||||
uint64_t vid_track = muxer_segment.AddVideoTrack(
|
uint64_t vid_track = muxer_segment.AddVideoTrack(
|
||||||
irr_driver->getActualScreenSize().Width,
|
irr_driver->getActualScreenSize().Width,
|
||||||
irr_driver->getActualScreenSize().Height, 0);
|
irr_driver->getActualScreenSize().Height, 0);
|
||||||
@ -139,17 +150,26 @@ namespace Recorder
|
|||||||
muxer_frame.set_track_number(vid_track);
|
muxer_frame.set_track_number(vid_track);
|
||||||
muxer_frame.set_timestamp(timestamp);
|
muxer_frame.set_timestamp(timestamp);
|
||||||
muxer_frame.set_is_key((flag & VPX_FRAME_IS_KEY) != 0);
|
muxer_frame.set_is_key((flag & VPX_FRAME_IS_KEY) != 0);
|
||||||
mkvmuxer::Frame* cur_aud_frame = audio_frames.front();
|
mkvmuxer::Frame* cur_aud_frame =
|
||||||
while (cur_aud_frame->timestamp() < (uint64_t)timestamp)
|
audio_frames.empty() ? NULL : audio_frames.front();
|
||||||
|
if (cur_aud_frame != NULL)
|
||||||
{
|
{
|
||||||
if (!muxer_segment.AddGenericFrame(cur_aud_frame))
|
while (cur_aud_frame->timestamp() < (uint64_t)timestamp)
|
||||||
{
|
{
|
||||||
Log::error("writeWebm", "Could not add audio frame.");
|
if (!muxer_segment.AddGenericFrame(cur_aud_frame))
|
||||||
return;
|
{
|
||||||
|
Log::error("writeWebm", "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();
|
||||||
}
|
}
|
||||||
delete cur_aud_frame;
|
|
||||||
audio_frames.pop_front();
|
|
||||||
cur_aud_frame = audio_frames.front();
|
|
||||||
}
|
}
|
||||||
if (!muxer_segment.AddGenericFrame(&muxer_frame))
|
if (!muxer_segment.AddGenericFrame(&muxer_frame))
|
||||||
{
|
{
|
||||||
|
@ -17,6 +17,9 @@
|
|||||||
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||||
|
|
||||||
#if !(defined(SERVER_ONLY) || defined(USE_GLES2))
|
#if !(defined(SERVER_ONLY) || defined(USE_GLES2))
|
||||||
|
|
||||||
|
#ifndef HEADER_WEBM_WRITER_HPP
|
||||||
|
#define HEADER_WEBM_WRITER_HPP
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace Recorder
|
namespace Recorder
|
||||||
@ -24,3 +27,5 @@ namespace Recorder
|
|||||||
void writeWebm(const std::string& video, const std::string& audio);
|
void writeWebm(const std::string& video, const std::string& audio);
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
|
@ -27,7 +27,12 @@
|
|||||||
#include "utils/vs.hpp"
|
#include "utils/vs.hpp"
|
||||||
|
|
||||||
#include <ogg/ogg.h>
|
#include <ogg/ogg.h>
|
||||||
#include <pulse/pulseaudio.h>
|
#ifdef WIN32
|
||||||
|
#include "recorder/wasapi_record.hpp"
|
||||||
|
#else
|
||||||
|
#include <pulse/pulseaudio.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <turbojpeg.h>
|
#include <turbojpeg.h>
|
||||||
#include <vorbis/vorbisenc.h>
|
#include <vorbis/vorbisenc.h>
|
||||||
#include <vpx/vpx_encoder.h>
|
#include <vpx/vpx_encoder.h>
|
||||||
@ -405,6 +410,8 @@ void* AVIWriter::vorbisEncoder(void *obj)
|
|||||||
const uint32_t all = header.bytes + header_comm.bytes + header_code.bytes
|
const uint32_t all = header.bytes + header_comm.bytes + header_code.bytes
|
||||||
+ 3;
|
+ 3;
|
||||||
fwrite(&all, 1, sizeof(uint32_t), vb_data);
|
fwrite(&all, 1, sizeof(uint32_t), vb_data);
|
||||||
|
fwrite(&all, 1, sizeof(uint32_t), vb_data);
|
||||||
|
fwrite(&all, 1, sizeof(uint32_t), vb_data);
|
||||||
uint8_t size = 2;
|
uint8_t size = 2;
|
||||||
fwrite(&size, 1, sizeof(uint8_t), vb_data);
|
fwrite(&size, 1, sizeof(uint8_t), vb_data);
|
||||||
size = (uint8_t)header.bytes;
|
size = (uint8_t)header.bytes;
|
||||||
@ -477,6 +484,7 @@ void* AVIWriter::vorbisEncoder(void *obj)
|
|||||||
|
|
||||||
} // vorbisEncoder
|
} // vorbisEncoder
|
||||||
|
|
||||||
|
#ifndef WIN32
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
void serverInfoCallBack(pa_context* c, const pa_server_info* i, void* data)
|
void serverInfoCallBack(pa_context* c, const pa_server_info* i, void* data)
|
||||||
{
|
{
|
||||||
@ -614,6 +622,7 @@ void* AVIWriter::audioRecord(void *obj)
|
|||||||
return NULL;
|
return NULL;
|
||||||
} // audioRecord
|
} // audioRecord
|
||||||
|
|
||||||
|
#endif
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
int AVIWriter::getFrameCount(double rate)
|
int AVIWriter::getFrameCount(double rate)
|
||||||
{
|
{
|
||||||
@ -638,7 +647,12 @@ void AVIWriter::captureFrameBufferImage()
|
|||||||
if (m_idle.getAtomic())
|
if (m_idle.getAtomic())
|
||||||
{
|
{
|
||||||
m_idle.setAtomic(false);
|
m_idle.setAtomic(false);
|
||||||
|
#ifdef WIN32
|
||||||
|
pthread_create(&audio_thread, NULL, &Recorder::audioRecord, &m_idle);
|
||||||
|
|
||||||
|
#else
|
||||||
pthread_create(&audio_thread, NULL, &audioRecord, &m_idle);
|
pthread_create(&audio_thread, NULL, &audioRecord, &m_idle);
|
||||||
|
#endif
|
||||||
pthread_cond_init(vpx_ei.m_enc_request, NULL);
|
pthread_cond_init(vpx_ei.m_enc_request, NULL);
|
||||||
pthread_create(&vpx_enc_thread, NULL, &vpxEncoder, &vpx_ei);
|
pthread_create(&vpx_enc_thread, NULL, &vpxEncoder, &vpx_ei);
|
||||||
}
|
}
|
||||||
|
@ -255,6 +255,11 @@ public:
|
|||||||
m_recording_target.setAtomic(name);
|
m_recording_target.setAtomic(name);
|
||||||
}
|
}
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
static std::string getRecordingTarget()
|
||||||
|
{
|
||||||
|
return m_recording_target.getAtomic();
|
||||||
|
}
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
void captureFrameBufferImage();
|
void captureFrameBufferImage();
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
void resetFrameBufferImage();
|
void resetFrameBufferImage();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user