stk-code_catmod/src/recorder/wasapi_recorder.cpp
2017-04-02 10:28:46 +08:00

283 lines
10 KiB
C++

// 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_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>
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;
}
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