mirror of
https://github.com/niellun/FastCarPlay.git
synced 2026-06-07 09:38:25 +02:00
Audio rework
This commit is contained in:
+6
-1
@@ -135,7 +135,7 @@
|
||||
# USB read pipeline tuning, number of libusb bulk transfers kept in flight and size of each USB transfer
|
||||
# Increase amount of async-usb-calls if you have issues with audio and video lagging behind
|
||||
#async-usb-calls = 16
|
||||
#usb-buffer-size = 2048
|
||||
#usb-buffer-size = 20480
|
||||
|
||||
# Number of buffered USB data slots queued for processing
|
||||
#usb-buffer = 64
|
||||
@@ -154,6 +154,11 @@
|
||||
# Reduction level from 1 (no reduction) to 0 (fully silenced)
|
||||
#audio-fade = 0.3
|
||||
|
||||
# Delay navidation audio in milliseconds.
|
||||
# Adjust value so navigation guidance do not start before main audio fade out.
|
||||
# If you experience audio artifacts with high values increase audio-buffer-size
|
||||
#audio-aux-delay = 200
|
||||
|
||||
# Basic number of audio sample frames in the output buffer that is scaled by sample rate.
|
||||
# This controls the size of the audio buffer used by the audio device.
|
||||
# The buffer time is ~ 1/10 buffer size in ms, depending on sample rate e.g. 2048 = ~200ms
|
||||
|
||||
+154
-157
@@ -6,32 +6,24 @@
|
||||
|
||||
// Add sample size (buffer size in samples) to ChannelConfig
|
||||
ChannelConfig PcmAudio::_configTable[] = {
|
||||
{8000, 1, 1}, // type = 3, ~256ms
|
||||
{48000, 2, 4}, // type = 4, ~170ms
|
||||
{16000, 1, 2}, // type = 5, ~256ms
|
||||
{24000, 1, 2}, // type = 6, ~170ms
|
||||
{16000, 2, 2}, // type = 7, ~256ms
|
||||
{8000, 1, 1}, // type = 3, ~256ms
|
||||
{48000, 2, 4}, // type = 4, ~170ms
|
||||
{16000, 1, 2}, // type = 5, ~256ms
|
||||
{24000, 1, 2}, // type = 6, ~170ms
|
||||
{16000, 2, 2}, // type = 7, ~256ms
|
||||
};
|
||||
|
||||
// Implementation
|
||||
PcmAudio::PcmAudio(const char *name)
|
||||
PcmAudio::PcmAudio(const char *name) : _name("default"),
|
||||
_fader(nullptr),
|
||||
_playing(false),
|
||||
_active(false),
|
||||
_fade(false),
|
||||
_config({0, 0, 0}),
|
||||
_volume(1),
|
||||
_fadedVolume(Settings::audioFade)
|
||||
{
|
||||
if (!name || strlen(name) < 1)
|
||||
{
|
||||
_name = "[Audio]";
|
||||
return;
|
||||
}
|
||||
_name = name;
|
||||
_fade = false;
|
||||
_faded = false;
|
||||
_volume = 1.0;
|
||||
_fadeVolume = Settings::audioFade;
|
||||
_config.channels = 0;
|
||||
_config.rate = 0;
|
||||
if (_fadeVolume < 0)
|
||||
_fadeVolume = 0;
|
||||
if (_fadeVolume > 1)
|
||||
_fadeVolume = 1;
|
||||
if (name && strlen(name) > 0)
|
||||
_name = name;
|
||||
}
|
||||
|
||||
PcmAudio::~PcmAudio()
|
||||
@@ -41,115 +33,6 @@ PcmAudio::~PcmAudio()
|
||||
_thread.join();
|
||||
}
|
||||
|
||||
void PcmAudio::fadecpy(uint8_t *target, uint8_t *source, size_t len)
|
||||
{
|
||||
if (!_fade && !_faded)
|
||||
{
|
||||
std::memcpy(target, source, len);
|
||||
return;
|
||||
}
|
||||
|
||||
int16_t *src = reinterpret_cast<int16_t *>(source);
|
||||
int16_t *dst = reinterpret_cast<int16_t *>(target);
|
||||
_faded = true;
|
||||
for (size_t i = 0; i < len / 2; i++)
|
||||
{
|
||||
if (_fade)
|
||||
{
|
||||
if (_volume - FADE_OUT_SPEED >= _fadeVolume)
|
||||
_volume = _volume - FADE_OUT_SPEED;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_volume + FADE_IN_SPEED <= 1)
|
||||
_volume = _volume + FADE_IN_SPEED;
|
||||
}
|
||||
dst[i] = src[i] * _volume;
|
||||
}
|
||||
_faded = _volume + FADE_IN_SPEED <= 1;
|
||||
}
|
||||
|
||||
void PcmAudio::callback(void *userdata, Uint8 *stream, int len)
|
||||
{
|
||||
PcmAudio *self = static_cast<PcmAudio *>(userdata);
|
||||
const Message *segment = self->_data->peek();
|
||||
if (segment && self->_offset == 0 && getConfig(segment->getInt(OFFSET_AUDIO_FORMAT)) != self->_config)
|
||||
{
|
||||
self->_paused = true;
|
||||
self->_cv.notify_one();
|
||||
}
|
||||
|
||||
if (self->_underflow)
|
||||
{
|
||||
int count = self->_data->count();
|
||||
if (count > self->_prefill)
|
||||
self->_underflow = false;
|
||||
else if (count == self->_lastCount)
|
||||
self->_underflowCount++;
|
||||
else
|
||||
self->_lastCount = count;
|
||||
}
|
||||
|
||||
while (len > 0)
|
||||
{
|
||||
if(segment == nullptr && !self->_underflow)
|
||||
{
|
||||
if (self->_fader)
|
||||
self->_fader->Fade(false);
|
||||
self->_underflowCount = 0;
|
||||
self->_underflow = true;
|
||||
}
|
||||
|
||||
if (self->_underflow)
|
||||
{
|
||||
std::fill_n(stream, len, 0);
|
||||
self->_faded = self->_fade;
|
||||
self->_volume = self->_faded ? Settings::audioFade : 1;
|
||||
if (self->_underflowCount < BUFFER_WAIT_COUNT)
|
||||
return;
|
||||
self->_data->clear();
|
||||
self->_paused = true;
|
||||
self->_cv.notify_one();
|
||||
return;
|
||||
}
|
||||
|
||||
int remain = segment->length() - self->_offset;
|
||||
uint8_t *data = segment->data() + self->_offset;
|
||||
|
||||
if (remain > len)
|
||||
{
|
||||
self->fadecpy(stream, data, len);
|
||||
self->_offset = self->_offset + len;
|
||||
return;
|
||||
}
|
||||
|
||||
self->fadecpy(stream, data, remain);
|
||||
len = len - remain;
|
||||
stream = stream + remain;
|
||||
self->_data->pop();
|
||||
self->_offset = 0;
|
||||
segment = self->_data->peek();
|
||||
if (segment && getConfig(segment->getInt(OFFSET_AUDIO_FORMAT)) != self->_config)
|
||||
{
|
||||
self->_paused = true;
|
||||
self->_cv.notify_one();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ChannelConfig PcmAudio::getConfig(int type)
|
||||
{
|
||||
if (type >= 3 && type <= 7)
|
||||
return _configTable[type - 3];
|
||||
// Default: 44100Hz, stereo, 4096 samples
|
||||
return {44100, 2, 4};
|
||||
}
|
||||
|
||||
void PcmAudio::Fade(bool enable)
|
||||
{
|
||||
_fade = enable;
|
||||
}
|
||||
|
||||
void PcmAudio::start(AtomicQueue<Message> *data, PcmAudio *fader)
|
||||
{
|
||||
if (_active)
|
||||
@@ -157,7 +40,7 @@ void PcmAudio::start(AtomicQueue<Message> *data, PcmAudio *fader)
|
||||
_fader = fader;
|
||||
_data = data;
|
||||
_active = true;
|
||||
_thread = std::thread(&PcmAudio::runner, this);
|
||||
_thread = std::thread(&PcmAudio::loop, this);
|
||||
}
|
||||
|
||||
void PcmAudio::stop()
|
||||
@@ -166,14 +49,138 @@ void PcmAudio::stop()
|
||||
return;
|
||||
_active = false;
|
||||
_data->notify();
|
||||
_cv.notify_all();
|
||||
}
|
||||
|
||||
void PcmAudio::runner()
|
||||
ChannelConfig PcmAudio::getConfig(const Message *msg)
|
||||
{
|
||||
uint8_t type = 0;
|
||||
if (msg)
|
||||
{
|
||||
type = msg->getInt(OFFSET_AUDIO_FORMAT);
|
||||
}
|
||||
|
||||
if (type >= 3 && type <= 7)
|
||||
return _configTable[type - 3];
|
||||
// Default: 44100Hz, stereo, 4096 samples
|
||||
return {44100, 2, 4};
|
||||
}
|
||||
|
||||
bool PcmAudio::isZero(const Message *msg)
|
||||
{
|
||||
const uint64_t *p = (const uint64_t *)msg->data();
|
||||
int n = msg->length() / 8;
|
||||
|
||||
for (int i = 0; i < n; ++i)
|
||||
{
|
||||
if (p[i] != 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void PcmAudio::fade(bool enable)
|
||||
{
|
||||
_fade.store(enable);
|
||||
if (!_playing)
|
||||
{
|
||||
_volume = enable ? Settings::audioFade : 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
void PcmAudio::fade(uint8_t *data, int32_t length)
|
||||
{
|
||||
bool fade = _fade.load();
|
||||
if (!fade && _volume >= 1)
|
||||
return;
|
||||
|
||||
int16_t *buf = reinterpret_cast<int16_t *>(data);
|
||||
for (size_t i = 0; i < length / 2; i++)
|
||||
{
|
||||
if (fade)
|
||||
{
|
||||
if (_volume - FADE_OUT_SPEED >= _fadedVolume)
|
||||
_volume = _volume - FADE_OUT_SPEED;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_volume + FADE_IN_SPEED <= 1)
|
||||
_volume = _volume + FADE_IN_SPEED;
|
||||
}
|
||||
if (_volume < 1)
|
||||
buf[i] = buf[i] * _volume;
|
||||
}
|
||||
}
|
||||
|
||||
void PcmAudio::play(SDL_AudioDeviceID device, ChannelConfig config, int32_t segmentSize)
|
||||
{
|
||||
uint8_t zeroSegments = 0;
|
||||
bool nonZero = false;
|
||||
int prefill = config.channels == 1 ? Settings::audioDelayCall : Settings::audioDelay;
|
||||
int segmentTimeMs = 1000.0 * segmentSize / (config.rate * config.channels * 2.0);
|
||||
|
||||
log_i("Prepare to play %s %dkHz %s prefill %d ~%dms chunk %d", _name.c_str(), config.rate, (config.channels == 2 ? "stereo" : "mono"), prefill, segmentTimeMs, segmentSize);
|
||||
|
||||
if (!_data->waitFor(_active, segmentTimeMs * (prefill + 1) * 3, prefill))
|
||||
{
|
||||
_data->clear();
|
||||
log_w("Not enough data to play %s %dkHz %s prefill %d ~%dms chunk %d", _name.c_str(), config.rate, (config.channels == 2 ? "stereo" : "mono"), prefill, segmentTimeMs, segmentSize);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_fader && !_fader->faded())
|
||||
{
|
||||
SDL_Delay(Settings::audioAuxDelay);
|
||||
}
|
||||
|
||||
while (_active)
|
||||
{
|
||||
std::unique_ptr<Message> segment = _data->pop();
|
||||
if (!segment)
|
||||
return;
|
||||
if (config != getConfig(segment.get()))
|
||||
return;
|
||||
|
||||
fade(segment->data(), segment->length());
|
||||
SDL_QueueAudio(device, segment->data(), segment->length());
|
||||
|
||||
if (!_playing)
|
||||
{
|
||||
log_d("Start playing %s %dkHz %s", _name.c_str(), config.rate, (config.channels == 2 ? "stereo" : "mono"));
|
||||
SDL_PauseAudioDevice(device, 0);
|
||||
_playing = true;
|
||||
}
|
||||
|
||||
if (_fader)
|
||||
{
|
||||
if (isZero(segment.get()))
|
||||
{
|
||||
if (nonZero && ++zeroSegments == FADE_ZERO_SEGMENTS)
|
||||
{
|
||||
log_d("Audio %s is zeroes, fade other channel in", _name.c_str());
|
||||
_fader->fade(false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
nonZero = true;
|
||||
zeroSegments = 0;
|
||||
_fader->fade(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (!_data->waitFor(_active, segmentTimeMs * 3))
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void PcmAudio::loop()
|
||||
{
|
||||
std::string threadName = "audio-" + _name;
|
||||
setThreadName(threadName.c_str());
|
||||
|
||||
log_d("Audio %s thread started", _name.c_str());
|
||||
|
||||
SDL_AudioDeviceID device = 0;
|
||||
SDL_AudioSpec spec;
|
||||
|
||||
@@ -183,12 +190,13 @@ void PcmAudio::runner()
|
||||
if (!segment)
|
||||
continue;
|
||||
|
||||
ChannelConfig config = getConfig(segment->getInt(OFFSET_AUDIO_FORMAT));
|
||||
ChannelConfig config = getConfig(segment);
|
||||
if (_config != config)
|
||||
{
|
||||
if (device != 0)
|
||||
{
|
||||
SDL_PauseAudioDevice(device, 1);
|
||||
SDL_ClearQueuedAudio(device);
|
||||
SDL_CloseAudioDevice(device);
|
||||
device = 0;
|
||||
}
|
||||
@@ -198,45 +206,34 @@ void PcmAudio::runner()
|
||||
spec.freq = config.rate;
|
||||
spec.format = AUDIO_S16SYS;
|
||||
spec.channels = config.channels;
|
||||
spec.samples = Settings::audioBuffer*config.scale;
|
||||
spec.callback = callback;
|
||||
spec.userdata = this;
|
||||
spec.samples = Settings::audioBuffer * config.scale;
|
||||
spec.callback = nullptr;
|
||||
spec.userdata = nullptr;
|
||||
|
||||
device = SDL_OpenAudioDevice(nullptr, 0, &spec, nullptr, 0);
|
||||
if (device == 0)
|
||||
{
|
||||
log_w("Failed to open audio %s > %s", _name.c_str(), SDL_GetError());
|
||||
log_w("Failed to open audio %s > %s", _name.c_str(), SDL_GetError());
|
||||
SDL_Delay(100);
|
||||
continue;
|
||||
}
|
||||
_config = config;
|
||||
}
|
||||
|
||||
_offset = 0;
|
||||
_paused = false;
|
||||
_underflow = true;
|
||||
_underflowCount = 0;
|
||||
_lastCount = 0;
|
||||
_prefill = spec.channels == 1 ? Settings::audioDelayCall : Settings::audioDelay;
|
||||
|
||||
SDL_PauseAudioDevice(device, 0);
|
||||
log_i("Start playing %s %dkHz %s", _name.c_str(), _config.rate, (_config.channels == 2 ? "stereo" : "mono"));
|
||||
if (_fader)
|
||||
_fader->Fade(true);
|
||||
|
||||
std::unique_lock<std::mutex> lock(_mtx);
|
||||
_cv.wait(lock, [&]
|
||||
{ return _paused.load() || !_active.load(); });
|
||||
|
||||
_fader->fade(true);
|
||||
play(device, config, segment->length());
|
||||
_playing = false;
|
||||
if (_fader)
|
||||
_fader->fade(false);
|
||||
SDL_PauseAudioDevice(device, 1);
|
||||
log_i("Stop playing %s", _name.c_str());
|
||||
if (_fader)
|
||||
_fader->Fade(false);
|
||||
}
|
||||
|
||||
if (device)
|
||||
{
|
||||
SDL_PauseAudioDevice(device, 1);
|
||||
SDL_ClearQueuedAudio(device);
|
||||
SDL_CloseAudioDevice(device);
|
||||
}
|
||||
}
|
||||
|
||||
+17
-26
@@ -12,7 +12,7 @@
|
||||
|
||||
#define FADE_IN_SPEED 0.00001
|
||||
#define FADE_OUT_SPEED 0.0001
|
||||
#define BUFFER_WAIT_COUNT 5
|
||||
#define FADE_ZERO_SEGMENTS 10
|
||||
|
||||
struct ChannelConfig
|
||||
{
|
||||
@@ -41,36 +41,27 @@ public:
|
||||
void start(AtomicQueue<Message> *data, PcmAudio *fader = nullptr);
|
||||
void stop();
|
||||
|
||||
void Fade(bool enble);
|
||||
|
||||
private:
|
||||
void runner();
|
||||
void loop(SDL_AudioDeviceID device);
|
||||
void fadecpy(uint8_t *target, uint8_t *source, size_t len);
|
||||
|
||||
static void callback(void *userdata, Uint8 *stream, int len);
|
||||
static ChannelConfig getConfig(const Message *msg);
|
||||
static ChannelConfig _configTable[];
|
||||
static ChannelConfig getConfig(int type);
|
||||
|
||||
AtomicQueue<Message> *_data;
|
||||
ChannelConfig _config;
|
||||
int _offset;
|
||||
PcmAudio *_fader;
|
||||
bool _fade;
|
||||
bool _faded;
|
||||
float _volume;
|
||||
float _fadeVolume;
|
||||
bool _underflow;
|
||||
int _underflowCount;
|
||||
int _lastCount;
|
||||
int _prefill;
|
||||
void fade(bool enble);
|
||||
void loop();
|
||||
void play(SDL_AudioDeviceID device, ChannelConfig config, int32_t segmentSize);
|
||||
bool isZero(const Message *msg);
|
||||
void fade(uint8_t *data, int32_t length);
|
||||
bool faded() const { return _volume <= _fadedVolume; }
|
||||
|
||||
std::thread _thread;
|
||||
std::mutex _mtx;
|
||||
std::condition_variable _cv;
|
||||
std::atomic<bool> _paused{false};
|
||||
std::atomic<bool> _active{false};
|
||||
std::string _name;
|
||||
PcmAudio *_fader;
|
||||
std::thread _thread;
|
||||
std::atomic<bool> _playing;
|
||||
std::atomic<bool> _active;
|
||||
std::atomic<bool> _fade;
|
||||
ChannelConfig _config;
|
||||
AtomicQueue<Message> *_data;
|
||||
float _volume;
|
||||
float _fadedVolume;
|
||||
};
|
||||
|
||||
#endif /* SRC_PCM_AUDIO */
|
||||
|
||||
+2
-1
@@ -47,13 +47,14 @@ public:
|
||||
static inline Setting<bool> alternativeRendering{"alternative-rendering", false};
|
||||
static inline Setting<bool> fastScale{"fast-render-scale", false};
|
||||
static inline Setting<int> usbQueue{"async-usb-calls", 16};
|
||||
static inline Setting<int> usbTransferSize{"usb-buffer-size", 2048};
|
||||
static inline Setting<int> usbTransferSize{"usb-buffer-size", 20480};
|
||||
static inline Setting<int> usbBuffer{"usb-buffer", 64};
|
||||
static inline Setting<int> videoQueue{"video-buffer-size", 64};
|
||||
static inline Setting<int> audioQueue{"audio-buffer-size", 64};
|
||||
static inline Setting<int> audioDelay{"audio-buffer-wait", 2};
|
||||
static inline Setting<int> audioDelayCall{"audio-buffer-wait-call", 6};
|
||||
static inline Setting<float> audioFade{"audio-fade", 0.3};
|
||||
static inline Setting<int> audioAuxDelay{"audio-aux-delay", 200};
|
||||
static inline Setting<int> audioBuffer{"audio-buffer-samples", 512};
|
||||
static inline Setting<std::string> audioDriver{"audio-driver", ""};
|
||||
static inline Setting<std::string> onConnect{"on-connect-script", ""};
|
||||
|
||||
Reference in New Issue
Block a user