kinect/codes/Azure-Kinect-Sensor-SDK/tools/k4aviewer/k4amicrophone.cpp

298 lines
9.5 KiB
C++
Raw Normal View History

2024-03-06 18:05:53 +00:00
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
// Associated header
//
#include "k4amicrophone.h"
// System headers
//
#include <algorithm>
// Library headers
//
// Project headers
//
#include "k4amicrophonelistener.h"
#include "k4aviewererrormanager.h"
using namespace k4aviewer;
K4AMicrophone::K4AMicrophone(std::shared_ptr<SoundIoDevice> device) : m_device(std::move(device)) {}
void K4AMicrophone::SetFailed(const int errorCode)
{
// We can't stop the stream because this function can be called from the reader thread,
// and you're not allowed to call soundio_destroy_instream from the reader thread.
// Instead, we set the failed state and destroy the instream the next time someone tries
// to use it. K4AMicrophoneListeners know to check this and drop their references to the
// K4AMicrophone if the K4AMicrophone has failed.
//
m_statusCode = errorCode;
m_started = false;
// The next time all our listeners try to pull frames and realize that the mic is dead,
// we'll lose the last reference to the microphone and it'll get deleted. We can't actually
// stop the sound stream on the callback thread, though (libsoundio doesn't allow the callback
// thread to call soundio_instream_destroy), so we can't stop the callbacks from happening.
// To work around this, we have to null out the pointer that libsoundio has our callback can
// know that the stream is dead and we get don't segfault.
//
m_inStream->userdata = nullptr;
}
int K4AMicrophone::Start()
{
m_inStream.reset(soundio_instream_create(m_device.get()));
if (!m_inStream)
{
return SoundIoErrorNoMem;
}
m_inStream->format = SoundIoFormatFloat32LE;
m_inStream->sample_rate = K4AMicrophoneSampleRate;
m_inStream->layout = *soundio_channel_layout_get_builtin(SoundIoChannelLayoutId7Point0);
m_inStream->software_latency = 0.2;
m_inStream->userdata = this;
m_inStream->read_callback = K4AMicrophone::ReadCallback;
m_inStream->overflow_callback = K4AMicrophone::OverflowCallback;
m_inStream->error_callback = K4AMicrophone::ErrorCallback;
int result = soundio_instream_open(m_inStream.get());
if (result != SoundIoErrorNone)
{
m_inStream.reset();
return result;
}
result = soundio_instream_start(m_inStream.get());
if (result != SoundIoErrorNone)
{
return result;
}
m_started = true;
return result;
}
void K4AMicrophone::Stop()
{
m_started = false;
m_inStream.reset();
std::lock_guard<std::mutex> lock(m_listenersMutex);
m_listeners.clear();
}
std::shared_ptr<K4AMicrophoneListener> K4AMicrophone::CreateListener()
{
if (!m_inStream)
{
return nullptr;
}
constexpr int bufferPaddingRatio = 3;
const auto bufferSize = static_cast<size_t>(bufferPaddingRatio * m_inStream->software_latency *
m_inStream->sample_rate * m_inStream->bytes_per_frame);
auto result = std::shared_ptr<K4AMicrophoneListener>(new K4AMicrophoneListener(shared_from_this(), bufferSize));
if (!result->m_buffer)
{
// OOM
//
result.reset();
return nullptr;
}
std::weak_ptr<K4AMicrophoneListener> newListenerWeakPtr = result;
std::lock_guard<std::mutex> lock(m_listenersMutex);
m_listeners.emplace_back(std::move(newListenerWeakPtr));
return result;
}
void K4AMicrophone::ReadCallback(SoundIoInStream *inStream, const int frameCountMin, const int frameCountMax)
{
if (!inStream->userdata)
{
return;
}
auto instance = reinterpret_cast<K4AMicrophone *>(inStream->userdata);
int maxFramesToWrite = 0;
// Grab references to all the listeners and figure out how many frames we're going to read
//
struct ListenerInfo
{
std::shared_ptr<K4AMicrophoneListener> Listener = nullptr;
int FramesToWrite = 0;
int FramesWritten = 0;
char *WritePtr = nullptr;
};
std::vector<ListenerInfo> listenerInfo;
// We don't need the lock on the listeners for the whole function, so create a scope that just
// lasts as long as we need the lock
{
std::lock_guard<std::mutex> lock(instance->m_listenersMutex);
listenerInfo.reserve(instance->m_listeners.size());
bool expiredReferencesFound = false;
for (std::weak_ptr<K4AMicrophoneListener> &wpListener : instance->m_listeners)
{
std::shared_ptr<K4AMicrophoneListener> spListener = wpListener.lock();
if (spListener)
{
const int bufferFreeBytes = soundio_ring_buffer_free_count(spListener->m_buffer.get());
const int bufferFreeFrames = bufferFreeBytes / instance->m_inStream->bytes_per_frame;
const int totalFramesToWrite = std::min(bufferFreeFrames, frameCountMax);
ListenerInfo newListener;
newListener.Listener = std::move(spListener);
newListener.FramesToWrite = totalFramesToWrite;
newListener.FramesWritten = 0;
newListener.WritePtr = soundio_ring_buffer_write_ptr(newListener.Listener->m_buffer.get());
listenerInfo.emplace_back(std::move(newListener));
maxFramesToWrite = std::max(totalFramesToWrite, maxFramesToWrite);
}
else
{
expiredReferencesFound = true;
}
}
// Clean up listeners that have been destroyed
//
if (expiredReferencesFound)
{
const auto isExpired = [](const std::weak_ptr<K4AMicrophoneListener> &wp) { return wp.expired(); };
instance->m_listeners.erase(std::remove_if(instance->m_listeners.begin(),
instance->m_listeners.end(),
isExpired),
instance->m_listeners.end());
}
}
if (frameCountMin > maxFramesToWrite)
{
// Everyone is out of buffer space, which means something has gone badly wrong.
//
ErrorCallback(inStream, SoundIoErrorStreaming);
return;
}
// Actually read audio data
//
int remainingFramesToWrite = maxFramesToWrite;
int maxFramesWritten = 0;
while (true)
{
int readFrameCount = remainingFramesToWrite;
SoundIoChannelArea *areas;
int err = soundio_instream_begin_read(instance->m_inStream.get(), &areas, &readFrameCount);
if (err != SoundIoErrorNone)
{
ErrorCallback(inStream, err);
return;
}
if (readFrameCount == 0)
{
break;
}
// Distribute audio data to each listener
//
for (ListenerInfo &listener : listenerInfo)
{
const int framesToWriteForListener = std::min(readFrameCount, listener.FramesToWrite);
if (framesToWriteForListener < readFrameCount)
{
// This listener has run out of space in its buffer and is going to lose data.
//
listener.Listener->m_overflowed = true;
}
if (areas == nullptr)
{
// There is a hole in the buffer; we need to fill it with silence.
// This can happen if the microphone is muted by the OS.
//
memset(listener.WritePtr,
0,
static_cast<size_t>(readFrameCount * instance->m_inStream->bytes_per_frame));
}
else
{
for (int frame = 0; frame < readFrameCount; ++frame)
{
for (int channel = 0; channel < instance->m_inStream->layout.channel_count; channel++)
{
memcpy(listener.WritePtr,
areas[channel].ptr,
static_cast<size_t>(instance->m_inStream->bytes_per_sample));
areas[channel].ptr += areas[channel].step;
listener.WritePtr += instance->m_inStream->bytes_per_sample;
}
}
}
listener.FramesToWrite -= framesToWriteForListener;
listener.FramesWritten += framesToWriteForListener;
maxFramesWritten = std::max(listener.FramesWritten, maxFramesWritten);
}
err = soundio_instream_end_read(instance->m_inStream.get());
if (err != SoundIoErrorNone)
{
ErrorCallback(inStream, err);
return;
}
remainingFramesToWrite -= readFrameCount;
if (remainingFramesToWrite <= 0)
{
break;
}
}
for (ListenerInfo &listener : listenerInfo)
{
const int bytesWritten = listener.FramesWritten * instance->m_inStream->bytes_per_frame;
soundio_ring_buffer_advance_write_ptr(listener.Listener->m_buffer.get(), bytesWritten);
// This listener fell behind and lost some data; notify it that this happened
//
if (listener.FramesWritten < maxFramesWritten)
{
listener.Listener->m_overflowed = true;
}
}
}
void K4AMicrophone::ErrorCallback(SoundIoInStream *inStream, const int errorCode)
{
auto instance = reinterpret_cast<K4AMicrophone *>(inStream->userdata);
instance->SetFailed(errorCode);
}
void K4AMicrophone::OverflowCallback(SoundIoInStream *inStream)
{
ErrorCallback(inStream, SoundIoErrorStreaming);
}