mirror of
https://github.com/ZLMediaKit/ZLMediaKit.git
synced 2026-07-29 21:38:22 +08:00
Add async init SDL audio device for player to prevent blocking main poller(#4773)
This commit is contained in:
@@ -18,13 +18,21 @@ using namespace toolkit;
|
|||||||
INSTANCE_IMP(SDLAudioDevice);
|
INSTANCE_IMP(SDLAudioDevice);
|
||||||
|
|
||||||
SDLAudioDevice::~SDLAudioDevice() {
|
SDLAudioDevice::~SDLAudioDevice() {
|
||||||
if (_device) {
|
if (_init_thread.joinable()) {
|
||||||
SDL_CloseAudioDevice(_device);
|
_init_thread.join();
|
||||||
|
}
|
||||||
|
auto dev = _device.load();
|
||||||
|
if (dev) {
|
||||||
|
SDL_CloseAudioDevice(dev);
|
||||||
_device = 0;
|
_device = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SDLAudioDevice::SDLAudioDevice() {
|
SDLAudioDevice::SDLAudioDevice() {
|
||||||
|
// 将 SDL_OpenAudioDevice 放到后台线程异步执行, 防止主线程(SDL事件循环)和 event poller 线程阻塞
|
||||||
|
// 在Windows下没声卡/设备异常时,SDL_OpenAudioDevice()会阻塞
|
||||||
|
// 在 Linux(PulseAudio/ALSA) 和 macOS(CoreAudio) 上通常秒回,几乎无开销
|
||||||
|
_init_thread = std::thread([this]() {
|
||||||
SDL_AudioSpec wanted_spec;
|
SDL_AudioSpec wanted_spec;
|
||||||
wanted_spec.freq = DEFAULT_SAMPLERATE;
|
wanted_spec.freq = DEFAULT_SAMPLERATE;
|
||||||
wanted_spec.format = DEFAULT_FORMAT;
|
wanted_spec.format = DEFAULT_FORMAT;
|
||||||
@@ -33,32 +41,43 @@ SDLAudioDevice::SDLAudioDevice() {
|
|||||||
wanted_spec.samples = DEFAULT_SAMPLES;
|
wanted_spec.samples = DEFAULT_SAMPLES;
|
||||||
wanted_spec.userdata = this;
|
wanted_spec.userdata = this;
|
||||||
wanted_spec.callback = [](void *userdata, Uint8 *stream, int len) {
|
wanted_spec.callback = [](void *userdata, Uint8 *stream, int len) {
|
||||||
SDLAudioDevice *_this = (SDLAudioDevice *) userdata;
|
SDLAudioDevice *_this = (SDLAudioDevice *)userdata;
|
||||||
_this->onReqPCM((char *) stream, len);
|
_this->onReqPCM((char *)stream, len);
|
||||||
};
|
};
|
||||||
|
|
||||||
_device = SDL_OpenAudioDevice(NULL, 0, &wanted_spec, &_audio_config, 0);
|
SDL_AudioSpec audio_config;
|
||||||
if (_device <= 0)
|
auto dev = SDL_OpenAudioDevice(NULL, 0, &wanted_spec, &audio_config, 0);
|
||||||
_device = SDL_OpenAudioDevice(NULL, 0, &wanted_spec, &_audio_config, SDL_AUDIO_ALLOW_ANY_CHANGE);
|
if (dev <= 0)
|
||||||
if (_device <= 0) {
|
dev = SDL_OpenAudioDevice(NULL, 0, &wanted_spec, &audio_config, SDL_AUDIO_ALLOW_ANY_CHANGE);
|
||||||
throw std::runtime_error("SDL_OpenAudioDevice failed");
|
if (dev <= 0) {
|
||||||
|
WarnL << "SDL_OpenAudioDevice failed, audio output disabled: " << SDL_GetError();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
InfoL << "actual audioSpec, " << "freq:" << _audio_config.freq
|
InfoL << "actual audioSpec, " << "freq:" << audio_config.freq
|
||||||
<< ", format:" << hex << _audio_config.format << dec
|
<< ", format:" << hex << audio_config.format << dec
|
||||||
<< ", channels:" << (int) _audio_config.channels
|
<< ", channels:" << (int)audio_config.channels
|
||||||
<< ", samples:" << _audio_config.samples
|
<< ", samples:" << audio_config.samples
|
||||||
<< ", pcm size:" << _audio_config.size;
|
<< ", pcm size:" << audio_config.size;
|
||||||
|
|
||||||
_play_buf.reset(new char[_audio_config.size], [](char *ptr) {
|
// 先设置 _audio_config 和 _play_buf,再设置 _device (release)
|
||||||
|
// 这样 addChannel 读到 _device != 0 时,_audio_config 和 _play_buf 已就绪
|
||||||
|
_audio_config = audio_config;
|
||||||
|
_play_buf.reset(new char[audio_config.size], [](char *ptr) {
|
||||||
delete[] ptr;
|
delete[] ptr;
|
||||||
});
|
});
|
||||||
|
_device.store(dev, std::memory_order_release);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void SDLAudioDevice::addChannel(AudioSRC *chn) {
|
void SDLAudioDevice::addChannel(AudioSRC *chn) {
|
||||||
|
auto dev = _device.load(std::memory_order_acquire);
|
||||||
|
if (dev == 0) {
|
||||||
|
throw std::runtime_error("SDL audio device not available (not ready or no audio device)");
|
||||||
|
}
|
||||||
lock_guard<recursive_mutex> lck(_channel_mtx);
|
lock_guard<recursive_mutex> lck(_channel_mtx);
|
||||||
if (_channels.empty()) {
|
if (_channels.empty()) {
|
||||||
SDL_PauseAudioDevice(_device, false);
|
SDL_PauseAudioDevice(dev, false);
|
||||||
}
|
}
|
||||||
chn->setOutputAudioConfig(_audio_config);
|
chn->setOutputAudioConfig(_audio_config);
|
||||||
_channels.emplace(chn);
|
_channels.emplace(chn);
|
||||||
@@ -67,8 +86,9 @@ void SDLAudioDevice::addChannel(AudioSRC *chn) {
|
|||||||
void SDLAudioDevice::delChannel(AudioSRC *chn) {
|
void SDLAudioDevice::delChannel(AudioSRC *chn) {
|
||||||
lock_guard<recursive_mutex> lck(_channel_mtx);
|
lock_guard<recursive_mutex> lck(_channel_mtx);
|
||||||
_channels.erase(chn);
|
_channels.erase(chn);
|
||||||
if (_channels.empty()) {
|
auto dev = _device.load();
|
||||||
SDL_PauseAudioDevice(_device, true);
|
if (_channels.empty() && dev) {
|
||||||
|
SDL_PauseAudioDevice(dev, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,8 @@
|
|||||||
|
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <atomic>
|
||||||
|
#include <thread>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
|
|
||||||
@@ -40,11 +42,12 @@ private:
|
|||||||
void onReqPCM(char *stream, int len);
|
void onReqPCM(char *stream, int len);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
SDL_AudioDeviceID _device;
|
std::atomic<SDL_AudioDeviceID> _device{ 0 };
|
||||||
std::shared_ptr<char> _play_buf;
|
std::shared_ptr<char> _play_buf;
|
||||||
SDL_AudioSpec _audio_config;
|
SDL_AudioSpec _audio_config;
|
||||||
std::recursive_mutex _channel_mtx;
|
std::recursive_mutex _channel_mtx;
|
||||||
std::unordered_set<AudioSRC *> _channels;
|
std::unordered_set<AudioSRC *> _channels;
|
||||||
|
std::thread _init_thread;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /* SDLAUDIOMIXER_SDLAUDIODEVICE_H_ */
|
#endif /* SDLAUDIOMIXER_SDLAUDIODEVICE_H_ */
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ int main(int argc, char *argv[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (audioTrack) {
|
if (audioTrack) {
|
||||||
|
try {
|
||||||
auto decoder = std::make_shared<FFmpegDecoder>(audioTrack);
|
auto decoder = std::make_shared<FFmpegDecoder>(audioTrack);
|
||||||
auto audio_player = std::make_shared<AudioPlayer>();
|
auto audio_player = std::make_shared<AudioPlayer>();
|
||||||
// FFmpeg解码时已经统一转换为16位整型pcm
|
// FFmpeg解码时已经统一转换为16位整型pcm
|
||||||
@@ -101,7 +102,7 @@ int main(int argc, char *argv[]) {
|
|||||||
|
|
||||||
decoder->setOnDecode([audio_player, swr](const FFmpegFrame::Ptr &frame) mutable {
|
decoder->setOnDecode([audio_player, swr](const FFmpegFrame::Ptr &frame) mutable {
|
||||||
if (!swr) {
|
if (!swr) {
|
||||||
# if LIBAVCODEC_VERSION_INT >= FF_CODEC_VER_7_1
|
#if LIBAVCODEC_VERSION_INT >= FF_CODEC_VER_7_1
|
||||||
swr = std::make_shared<FFmpegSwr>(AV_SAMPLE_FMT_S16, &(frame->get()->ch_layout), frame->get()->sample_rate);
|
swr = std::make_shared<FFmpegSwr>(AV_SAMPLE_FMT_S16, &(frame->get()->ch_layout), frame->get()->sample_rate);
|
||||||
#else
|
#else
|
||||||
swr = std::make_shared<FFmpegSwr>(AV_SAMPLE_FMT_S16, frame->get()->channels, frame->get()->channel_layout, frame->get()->sample_rate);
|
swr = std::make_shared<FFmpegSwr>(AV_SAMPLE_FMT_S16, frame->get()->channels, frame->get()->channel_layout, frame->get()->sample_rate);
|
||||||
@@ -112,18 +113,32 @@ int main(int argc, char *argv[]) {
|
|||||||
audio_player->playPCM((const char *)(pcm->get()->data[0]), MIN(len, frame->get()->linesize[0]));
|
audio_player->playPCM((const char *)(pcm->get()->data[0]), MIN(len, frame->get()->linesize[0]));
|
||||||
});
|
});
|
||||||
audioTrack->addDelegate([decoder](const Frame::Ptr &frame) { return decoder->inputFrame(frame, false, true); });
|
audioTrack->addDelegate([decoder](const Frame::Ptr &frame) { return decoder->inputFrame(frame, false, true); });
|
||||||
|
} catch (const std::exception &ex) {
|
||||||
|
WarnL << "audio output disabled: " << ex.what();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
player->setOnShutdown([](const SockException &ex) { WarnL << "play shutdown: " << ex.what(); });
|
player->setOnShutdown([](const SockException &ex) { WarnL << "play shutdown: " << ex.what(); });
|
||||||
// 不等待track ready再回调播放成功事件,这样可以加快秒开速度
|
// 不等待track ready再回调播放成功事件,这样可以加快秒开速度
|
||||||
(*player)[Client::kWaitTrackReady] = false;
|
(*player)[Client::kWaitTrackReady] = false;
|
||||||
|
(*player)[Client::kPlayTrack] = 0;
|
||||||
if (argc > 2) {
|
if (argc > 2) {
|
||||||
(*player)[Client::kRtpType] = atoi(argv[2]);
|
(*player)[Client::kRtpType] = atoi(argv[2]);
|
||||||
}
|
}
|
||||||
if (argc > 3) {
|
if (argc > 3) {
|
||||||
(*player)[Client::kPlayTrack] = atoi(argv[3]);
|
(*player)[Client::kPlayTrack] = atoi(argv[3]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 提前触发 SDL 音频设备异步初始化(后台线程)
|
||||||
|
// 某些平台在无音频设备时 SDL_OpenAudioDevice 会长时间阻塞
|
||||||
|
// (如 Windows WASAPI 约 16 秒),提前启动确保 play 回调时已就绪
|
||||||
|
try {
|
||||||
|
SDLAudioDevice::Instance();
|
||||||
|
} catch (const std::exception &ex) {
|
||||||
|
WarnL << "SDL audio device pre-init failed: " << ex.what();
|
||||||
|
}
|
||||||
|
|
||||||
player->play(argv[1]);
|
player->play(argv[1]);
|
||||||
SDLDisplayerHelper::Instance().runLoop();
|
SDLDisplayerHelper::Instance().runLoop();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user