Nagram/TMessagesProj/jni/libtgvoip3/os/linux/AudioOutputPulse.cpp

217 lines
6.6 KiB
C++
Raw Normal View History

2020-04-24 09:21:58 +00:00
//
// libtgvoip is free and unencumbered public domain software.
// For more information, see http://unlicense.org or the UNLICENSE file
// you should have received with this source code distribution.
//
#include "../../logging.h"
#include "../../VoIPController.h"
#include "AudioOutputPulse.h"
#include "AudioPulse.h"
#include "PulseFunctions.h"
#include <dlfcn.h>
#include <unistd.h>
#if !defined(__GLIBC__)
#include <libgen.h>
#endif
#include <cassert>
#include <cstring>
#define BUFFER_SIZE 960
#define CHECK_ERROR(res, msg) \
if (res != 0) \
{ \
LOGE(msg " failed: %s", pa_strerror(res)); \
m_failed = true; \
return; \
}
using namespace tgvoip;
using namespace tgvoip::audio;
AudioOutputPulse::AudioOutputPulse(pa_context* context, pa_threaded_mainloop* mainloop, std::string devID)
: m_mainloop(mainloop)
, m_context(context)
{
pa_threaded_mainloop_lock(mainloop);
m_stream = CreateAndInitStream();
pa_threaded_mainloop_unlock(mainloop);
SetCurrentDevice(std::move(devID));
}
AudioOutputPulse::~AudioOutputPulse()
{
if (m_stream)
{
pa_stream_disconnect(m_stream);
pa_stream_unref(m_stream);
}
}
pa_stream* AudioOutputPulse::CreateAndInitStream()
{
pa_sample_spec sampleSpec
{
.format = PA_SAMPLE_S16LE,
.rate = 48000,
.channels = 1
};
pa_proplist* proplist = pa_proplist_new();
pa_proplist_sets(proplist, PA_PROP_FILTER_APPLY, ""); // according to PA sources, this disables any possible filters
pa_stream* stream = pa_stream_new_with_proplist(m_context, "libtgvoip playback", &sampleSpec, nullptr, proplist);
pa_proplist_free(proplist);
if (stream == nullptr)
{
LOGE("Error initializing PulseAudio (pa_stream_new)");
m_failed = true;
return nullptr;
}
pa_stream_set_state_callback(stream, AudioOutputPulse::StreamStateCallback, this);
pa_stream_set_write_callback(stream, AudioOutputPulse::StreamWriteCallback, this);
return stream;
}
void AudioOutputPulse::Start()
{
if (m_failed || m_isPlaying)
return;
m_isPlaying = true;
pa_threaded_mainloop_lock(m_mainloop);
pa_operation_unref(pa_stream_cork(m_stream, 0, nullptr, nullptr));
pa_threaded_mainloop_unlock(m_mainloop);
}
void AudioOutputPulse::Stop()
{
if (!m_isPlaying)
return;
m_isPlaying = false;
pa_threaded_mainloop_lock(m_mainloop);
pa_operation_unref(pa_stream_cork(m_stream, 1, nullptr, nullptr));
pa_threaded_mainloop_unlock(m_mainloop);
}
bool AudioOutputPulse::IsPlaying()
{
return m_isPlaying;
}
void AudioOutputPulse::SetCurrentDevice(std::string devID)
{
pa_threaded_mainloop_lock(m_mainloop);
m_currentDevice = std::move(devID);
if (m_isPlaying && m_isConnected)
{
pa_stream_disconnect(m_stream);
pa_stream_unref(m_stream);
m_isConnected = false;
m_stream = CreateAndInitStream();
}
pa_buffer_attr bufferAttr =
{
.maxlength = std::numeric_limits<std::uint32_t>::max(),
.tlength = 960 * 2,
.prebuf = std::numeric_limits<std::uint32_t>::max(),
.minreq = std::numeric_limits<std::uint32_t>::max(),
.fragsize = std::numeric_limits<std::uint32_t>::max()
};
int streamFlags = PA_STREAM_START_CORKED | PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_ADJUST_LATENCY;
int err = pa_stream_connect_playback(m_stream, devID == "default" ? nullptr : devID.c_str(), &bufferAttr, static_cast<pa_stream_flags_t>(streamFlags), nullptr, nullptr);
if (err != 0 && devID != "default")
{
SetCurrentDevice("default");
return;
}
CHECK_ERROR(err, "pa_stream_connect_playback");
while (true)
{
pa_stream_state_t streamState = pa_stream_get_state(m_stream);
if (!PA_STREAM_IS_GOOD(streamState))
{
LOGE("Error connecting to audio device '%s'", devID.c_str());
m_failed = true;
return;
}
if (streamState == PA_STREAM_READY)
break;
pa_threaded_mainloop_wait(m_mainloop);
}
m_isConnected = true;
if (m_isPlaying)
{
pa_operation_unref(pa_stream_cork(m_stream, 0, nullptr, nullptr));
}
pa_threaded_mainloop_unlock(m_mainloop);
}
bool AudioOutputPulse::EnumerateDevices(std::vector<AudioOutputDevice>& devs)
{
return AudioPulse::DoOneOperation([&](pa_context* ctx)
{
return pa_context_get_sink_info_list(
ctx, [](pa_context* ctx, const pa_sink_info* info, int eol, void* userdata)
{
if (eol > 0)
return;
std::vector<AudioOutputDevice>* devs = reinterpret_cast<std::vector<AudioOutputDevice>*>(userdata);
AudioOutputDevice dev;
dev.id = std::string(info->name);
dev.displayName = std::string(info->description);
devs->emplace_back(dev);
},
&devs);
});
}
void AudioOutputPulse::StreamStateCallback(pa_stream* s, void* arg)
{
AudioOutputPulse* self = reinterpret_cast<AudioOutputPulse*>(arg);
pa_threaded_mainloop_signal(self->m_mainloop, 0);
}
void AudioOutputPulse::StreamWriteCallback(pa_stream* stream, std::size_t requestedBytes, void* userdata)
{
(reinterpret_cast<AudioOutputPulse*>(userdata))->StreamWriteCallback(stream, requestedBytes);
}
void AudioOutputPulse::StreamWriteCallback(pa_stream* stream, std::size_t requestedBytes)
{
if (requestedBytes > sizeof(m_remainingData))
{
requestedBytes = 960 * 2; // force buffer size to 20ms. This probably wrecks the jitter buffer, but still better than crashing
}
pa_usec_t latency;
if (pa_stream_get_latency(stream, &latency, nullptr) == 0)
{
m_estimatedDelay = static_cast<std::int32_t>(latency / 100);
}
while (requestedBytes > m_remainingDataSize)
{
if (m_isPlaying)
{
InvokeCallback(m_remainingData + m_remainingDataSize, 960 * 2);
m_remainingDataSize += 960 * 2;
}
else
{
std::memset(m_remainingData + m_remainingDataSize, 0, requestedBytes - m_remainingDataSize);
m_remainingDataSize = requestedBytes;
}
}
int err = pa_stream_write(stream, m_remainingData, requestedBytes, nullptr, 0, PA_SEEK_RELATIVE);
CHECK_ERROR(err, "pa_stream_write");
m_remainingDataSize -= requestedBytes;
if (m_remainingDataSize > 0)
memmove(m_remainingData, m_remainingData + requestedBytes, m_remainingDataSize);
}