diff --git a/src/devices/speaker/ffmpeg_speaker/CMakeLists.txt b/src/devices/speaker/ffmpeg_speaker/CMakeLists.txt index 29aee25e..68bd5bb4 100644 --- a/src/devices/speaker/ffmpeg_speaker/CMakeLists.txt +++ b/src/devices/speaker/ffmpeg_speaker/CMakeLists.txt @@ -6,4 +6,4 @@ find_package(ALSA REQUIRED) add_library(cmvr_es::device::ffmpeg_speaker ALIAS ffmpeg_speaker) -target_link_libraries(ffmpeg_speaker PRIVATE ALSA::ALSA) \ No newline at end of file +target_link_libraries(ffmpeg_speaker PRIVATE -lpulse-simple -lpulse) \ No newline at end of file diff --git a/src/devices/speaker/ffmpeg_speaker/include/ffmpeg_speaker.h b/src/devices/speaker/ffmpeg_speaker/include/ffmpeg_speaker.h index 735b1bb8..087526ff 100644 --- a/src/devices/speaker/ffmpeg_speaker/include/ffmpeg_speaker.h +++ b/src/devices/speaker/ffmpeg_speaker/include/ffmpeg_speaker.h @@ -6,10 +6,13 @@ #include #include #include -#include "../../abstract_speaker.h" +#include "speaker/abstract_speaker.h" #include +#include +#include +#include #include "ffmpeg_ptr.h" -#include "../../../../utils/base/include/os.h" +#include "utils/base/include/os.h" extern "C" { #include @@ -37,24 +40,36 @@ namespace cmvr::device { void getState(SpeakerState& state) override; void resetPlayState(); + bool initPulseDevice_(); + bool initAudioParams_(const std::string& audio_path); private: void decode_audio_(); void play_audio_(); std::string id_; - std::string alas_; - int channels_; - int sample_rate_; - int soft_resample_; - int latency_; - snd_pcm_t* pcm_handle_ = nullptr; + pa_simple* pulse_simple_ = nullptr; // 修改:PulseAudio 简单 API 句柄 + pa_sample_spec sample_spec_{}; // 新增:PulseAudio 采样规格 + int channels_ = 0; + int sample_rate_ = 0; + int soft_resample_ = 0; + int latency_ = 0; std::shared_ptr decode_thread_; std::shared_ptr play_thread_; std::mutex mtx_; std::string audio_path_; + // Boost 单生产者单消费者无锁队列 boost::lockfree::spsc_queue> audio_queue_; + + // 暂停相关的同步变量 + mutable std::mutex mtx_pause_; + std::condition_variable cv_pause_; + std::atomic is_paused_{false}; + + // 音频时钟同步 + std::atomic audio_pts_{0}; + std::chrono::time_point playback_start_time_; }; } diff --git a/src/devices/speaker/ffmpeg_speaker/src/ffmpeg_speaker.cpp b/src/devices/speaker/ffmpeg_speaker/src/ffmpeg_speaker.cpp index 390cba65..8e2ab015 100644 --- a/src/devices/speaker/ffmpeg_speaker/src/ffmpeg_speaker.cpp +++ b/src/devices/speaker/ffmpeg_speaker/src/ffmpeg_speaker.cpp @@ -13,11 +13,12 @@ ffmpegSpeaker::ffmpegSpeaker(const XmlNode& cfg) : AbstractSpeaker(cfg) { state_.is_paused = false; try { id_ = cfg_.getAttrString("id"); - alas_ = cfg_.getAttrString("alas"); - channels_ = std::clamp(cfg_.getAttrDefault("channels", 2), 1, 8); - sample_rate_ = cfg_.getAttrDefault("sampleRate", 44100); - soft_resample_ = cfg_.getAttrDefault("softResample", 1); - latency_ = cfg_.getAttrDefault("latency", 500000); + memset(&sample_spec_, 0, sizeof(sample_spec_)); + // alas_ = cfg_.getAttrString("alas"); + // channels_ = std::clamp(cfg_.getAttrDefault("channels", 2), 1, 8); + // sample_rate_ = cfg_.getAttrDefault("sampleRate", 44100); + // soft_resample_ = cfg_.getAttrDefault("softResample", 1); + // latency_ = cfg_.getAttrDefault("latency", 500000); state_.volume = clamp(cfg_.getAttrDefault("volume", 100), 0, 100); } catch (const exception& e) { @@ -26,7 +27,27 @@ ffmpegSpeaker::ffmpegSpeaker(const XmlNode& cfg) : AbstractSpeaker(cfg) { } ffmpegSpeaker::~ffmpegSpeaker() { - stop(); + { + std::lock_guard lock(mtx_); + state_.is_running = false; + state_.is_decoding = false; + state_.is_paused = false; + } + + cv_pause_.notify_all(); // 唤醒所有等待的线程 + + // 等待线程结束 + if (decode_thread_ && decode_thread_->joinable()) { + decode_thread_->join(); + decode_thread_.reset(); + } + + if (play_thread_ && play_thread_->joinable()) { + play_thread_->join(); + play_thread_.reset(); + } + + resetPlayState(); } void ffmpegSpeaker::init() { @@ -47,84 +68,46 @@ void ffmpegSpeaker::init() { } void ffmpegSpeaker::start() { - try { - lock_guard lock(mtx_); - if (!state_.is_initialized) { - LOG(ERROR) << "[ffmpegSpeaker] (start): device not initialized, id=" + id_; - throw runtime_error("[ffmpegSpeaker] (start): device not initialized, id=" + id_); - } - if (state_.is_running) { - LOG(WARNING) << "[ffmpegSpeaker] (start): device already started, id=" + id_; - throw runtime_error("[ffmpegSpeaker] (start): device already started, id=" + id_); - } - if (snd_pcm_open(&pcm_handle_, alas_.c_str(), SND_PCM_STREAM_PLAYBACK, 0) < 0) { - LOG(ERROR) << "[ffmpegSpeaker] (init): Failed to open ALSA device: " << alas_; - throw runtime_error("[ffmpegSpeaker] (init): Failed to open ALSA device: " + alas_); - } - if (snd_pcm_set_params(pcm_handle_, SND_PCM_FORMAT_S16_LE, SND_PCM_ACCESS_RW_INTERLEAVED, - channels_, sample_rate_, soft_resample_, latency_) < 0) { - LOG(ERROR) << "[ffmpegSpeaker] (init): Failed to set ALSA params, id=" << id_; - throw runtime_error("[ffmpegSpeaker] (init): Failed to set ALSA params, id=" + id_); - } - state_.is_running = false; - state_.is_paused = false; - state_.is_decoding = false; - LOG(INFO) << "[ffmpegSpeaker] (start): Success, id=" << id_; - } - catch (const exception& e) { - LOG(ERROR) <<"[ffmpegSpeaker] (start): Failed, id=" << id_ << ": " << e.what(); - throw runtime_error("[ffmpegSpeaker] (start): Failed, id=" + id_ + ": " + string(e.what())); - } + } + void ffmpegSpeaker::resetPlayState() { - if (pcm_handle_) { - snd_pcm_drain(pcm_handle_); - snd_pcm_close(pcm_handle_); - pcm_handle_ = nullptr; + if (pulse_simple_) { + pa_simple_flush(pulse_simple_, nullptr); + pa_simple_free(pulse_simple_); + pulse_simple_ = nullptr; } - state_.is_decoding = false; - state_.is_running = false; - state_.is_paused = false; + + // 清空队列 + AudioFrame frame; + while (audio_queue_.pop(frame)) { + // 清空所有帧 + } + + state_.is_initialized = false; + audio_path_.clear(); - - LOG(INFO) << "[ffmpegSpeaker] (resetPlayState): Success, id=" << id_; } void ffmpegSpeaker::stop() { - try { - //此处要注意可能和解码线程冲突引起死锁 - { - lock_guard lock(mtx_); - if (!state_.is_running) { - LOG(WARNING) << "[ffmpegSpeaker] (start): device already stoped, id=" + id_; - throw runtime_error("[ffmpegSpeaker] (start): device already stoped, id=" + id_); - } - state_.is_decoding = false; - state_.is_running = false; - state_.is_paused = false; - audio_path_.clear(); - } - if (decode_thread_->joinable()) { - decode_thread_->join(); - decode_thread_.reset(); - } - if (play_thread_->joinable()) { - play_thread_->join(); - play_thread_.reset(); - } - if (pcm_handle_) { - snd_pcm_drain(pcm_handle_); - snd_pcm_close(pcm_handle_); - pcm_handle_ = nullptr; - } - LOG(INFO) << "[ffmpegSpeaker] (stop): Success, id=" << id_; + + state_.is_running = false; + state_.is_decoding = false; + state_.is_paused = false; + // 等待线程结束 + if (decode_thread_ && decode_thread_->joinable()) { + decode_thread_->join(); + decode_thread_.reset(); } - catch (const exception& e) { - LOG(ERROR) <<"[ffmpegSpeaker] (stop): Failed, id=" << id_ << ": " << e.what(); - throw runtime_error("[ffmpegSpeaker] (stop): Failed, id=" + id_ + ": " + string(e.what())); + + if (play_thread_ && play_thread_->joinable()) { + play_thread_->join(); + play_thread_.reset(); } + + resetPlayState(); } void ffmpegSpeaker::pause() { @@ -169,42 +152,91 @@ void ffmpegSpeaker::resume() { } } -void ffmpegSpeaker::play(const string& audio_path) { +void ffmpegSpeaker::play(const std::string& audio_path) { + std::unique_lock lock(mtx_); + try { - lock_guard lock(mtx_); if (state_.is_running) { LOG(WARNING) << "[ffmpegSpeaker] (play): Speaker is playing " << audio_path_ << ", id=" << id_; throw runtime_error("[ffmpegSpeaker] (play): Speaker is playing " + audio_path_); } if (!pathExists(audio_path)) { - snd_pcm_drain(pcm_handle_); - snd_pcm_close(pcm_handle_); - LOG(ERROR) << "[ffmpegSpeaker] (play): audio path not exists, id=" << id_ << ", path=" << audio_path; - throw runtime_error("[ffmpegSpeaker] (play): audio path not exists, id=" + id_ + ", path=" + audio_path); + throw std::runtime_error("[ffmpegSpeaker] (play): Audio file not found: " + audio_path); + } + // 如果正在播放,先停止 + state_.is_running = false; + lock.unlock(); + + cv_pause_.notify_all(); + + if (decode_thread_ && decode_thread_->joinable()) { + decode_thread_->join(); + decode_thread_.reset(); + } + + if (play_thread_ && play_thread_->joinable()) { + play_thread_->join(); + play_thread_.reset(); + } + + resetPlayState(); + lock.lock(); + // 清空队列 + AudioFrame frame; + while (audio_queue_.pop(frame)) { + // 清空所有帧 } audio_path_ = audio_path; + + // 初始化音频参数 + if (!initAudioParams_(audio_path)) { + throw std::runtime_error("[ffmpegSpeaker] Failed to get audio parameters"); + } + + // 初始化 PulseAudio 设备 + if (!initPulseDevice_()) { + throw std::runtime_error("[ffmpegSpeaker] PulseAudio device initialization failed"); + } + + // 设置状态 state_.is_running = true; state_.is_decoding = true; - if (decode_thread_.get()) { - if (decode_thread_->joinable()) { - decode_thread_->join(); - decode_thread_.reset(); + state_.is_paused = false; + + // 启动解码线程 + decode_thread_ = std::make_shared([this]() { + try { + this->decode_audio_(); + } catch (const std::exception& e) { + std::cerr << "[ffmpegSpeaker] Decode thread exception: " << e.what() << std::endl; + std::lock_guard lock(mtx_); + state_.is_decoding = false; } - } - if (play_thread_.get()) { - if (play_thread_->joinable()) { - play_thread_->join(); - play_thread_.reset(); + }); + + // 等待解码线程开始(确保有数据) + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + // 启动播放线程 + play_thread_ = std::make_shared([this]() { + try { + this->play_audio_(); + } catch (const std::exception& e) { + std::cerr << "[ffmpegSpeaker] Play thread exception: " << e.what() << std::endl; + std::lock_guard lock(mtx_); + state_.is_running = false; } - } - decode_thread_ = make_shared(&ffmpegSpeaker::decode_audio_, this); - play_thread_ = make_shared(&ffmpegSpeaker::play_audio_, this); - LOG(INFO) << "[ffmpegSpeaker] (play): Success, id=" << id_; + }); + + std::cout << "[ffmpegSpeaker] Started playing: " << audio_path << std::endl; } - catch (const exception& e) { - LOG(ERROR) <<"[ffmpegSpeaker] (play): Failed, id=" << id_ << ": " << e.what(); - throw runtime_error("[ffmpegSpeaker] (play): Failed, id=" + id_ + ": " + string(e.what())); + catch (const std::exception& e) { + state_.is_running = false; + state_.is_decoding = false; + lock.unlock(); + resetPlayState(); + throw; } } @@ -216,6 +248,7 @@ void ffmpegSpeaker::setVolume(const int volume) { int ffmpegSpeaker::getVolume() const { return state_.volume; } + void ffmpegSpeaker::getState(SpeakerState& state) { state.is_decoding = state_.is_decoding; state.volume = state_.volume; @@ -227,139 +260,267 @@ void ffmpegSpeaker::getState(SpeakerState& state) { void ffmpegSpeaker::decode_audio_() { using namespace ffmpeg; + if (audio_path_.empty()) { - LOG(ERROR) << "[ffmpegSpeaker] (decode_audio_): audio_path is empty"; - { lock_guard lock(mtx_); state_.is_decoding = false; } - throw runtime_error("[ffmpegSpeaker] (decode_audio_): audio_path is empty"); + { + std::lock_guard lock(mtx_); + state_.is_decoding = false; + } + std::cerr << "[ffmpegSpeaker] (decode_audio_): audio_path is empty" << std::endl; + return; } + std::cout << "[ffmpegSpeaker] Decoding with: rate=" << sample_rate_ + << ", channels=" << channels_ << std::endl; + const AVFormatContextPtr fmt_ctx = make_format_context(audio_path_); if (!fmt_ctx) { - LOG(ERROR) << "[ffmpegSpeaker] (decode_audio_): Failed to open format context"; - { lock_guard lock(mtx_); state_.is_decoding = false; } - throw runtime_error("[ffmpegSpeaker] (decode_audio_): Failed to open format context"); + { + std::lock_guard lock(mtx_); + state_.is_decoding = false; + } + std::cerr << "[ffmpegSpeaker] (decode_audio_): Failed to open format context" << std::endl; + return; } AVCodec* decoder = nullptr; const int stream_index = av_find_best_stream(fmt_ctx.get(), AVMEDIA_TYPE_AUDIO, -1, -1, &decoder, 0); if (stream_index < 0) { - LOG(ERROR) << "[ffmpegSpeaker] (decode_audio_): No audio stream found in file"; - { lock_guard lock(mtx_); state_.is_decoding = false; } - throw runtime_error("[ffmpegSpeaker] (decode_audio_): No audio stream found in file"); + { + std::lock_guard lock(mtx_); + state_.is_decoding = false; + } + std::cerr << "[ffmpegSpeaker] (decode_audio_): No audio stream found in file" << std::endl; + return; } AVStream* audio_stream = fmt_ctx->streams[stream_index]; const AVCodecContextPtr codec_ctx = make_codec_context(audio_stream, decoder); if (!codec_ctx) { - LOG(ERROR) << "[ffmpegSpeaker] (decode_audio_): Failed to create codec context"; - { lock_guard lock(mtx_); state_.is_decoding = false; } - throw runtime_error("[ffmpegSpeaker] (decode_audio_): Failed to create codec context"); + { + std::lock_guard lock(mtx_); + state_.is_decoding = false; + } + std::cerr << "[ffmpegSpeaker] (decode_audio_): Failed to create codec context" << std::endl; + return; } const SwrContextPtr swr_ctx = make_swr_context(codec_ctx.get(), channels_, sample_rate_); if (!swr_ctx) { - LOG(ERROR) << "[ffmpegSpeaker] (decode_audio_): Failed to init swr context"; - { lock_guard lock(mtx_); state_.is_decoding = false; } - throw runtime_error("[ffmpegSpeaker] (decode_audio_): Failed to init swr context"); + { + std::lock_guard lock(mtx_); + state_.is_decoding = false; + } + std::cerr << "[ffmpegSpeaker] (decode_audio_): Failed to init swr context" << std::endl; + return; } const AVPacketPtr pkt(av_packet_alloc()); const AVFramePtr frame(av_frame_alloc()); - LOG(INFO) << "[ffmpegSpeaker] (decode_audio_): Start decoding " << audio_path_; - int64_t start_time = av_gettime(); // 播放开始时间 - while (state_.is_running && state_.is_decoding) { + // 解码循环 + while (true) { + { + std::lock_guard lock(mtx_); + if (!state_.is_running || !state_.is_decoding) { + break; + } + } - if (av_read_frame(fmt_ctx.get(), pkt.get()) < 0) break; + // 暂停处理 + { + std::unique_lock lock(mtx_pause_); + cv_pause_.wait(lock, [this]() { + return !state_.is_paused || !state_.is_running; + }); + if (!state_.is_running) break; + } + + // 控制队列大小,避免内存占用过高 + if (audio_queue_.write_available() < 5) { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + continue; + } + + if (av_read_frame(fmt_ctx.get(), pkt.get()) < 0) { + // 文件读取结束 + break; + } if (pkt->stream_index == stream_index) { - if (avcodec_send_packet(codec_ctx.get(), pkt.get()) < 0) continue; + if (avcodec_send_packet(codec_ctx.get(), pkt.get()) < 0) { + av_packet_unref(pkt.get()); + continue; + } - while (avcodec_receive_frame(codec_ctx.get(), frame.get()) == 0) { - while (state_.is_paused) + while (true) { { - av_usleep(10); - } - // 计算帧的播放时间(微秒) - int64_t frame_time = av_rescale_q(frame->pts, - fmt_ctx->streams[stream_index]->time_base, - AV_TIME_BASE_Q); - // 计算当前播放进度 - int64_t current_time = av_gettime() - start_time; - - // 如果解码太快,等待 - if (frame_time > current_time) { - int64_t sleep_time = frame_time - current_time; - //LOG(INFO) << "准备休眠: " << sleep_time << "us, is_running=" << state_.is_running; - av_usleep(sleep_time); - //LOG(INFO) << "休眠结束, is_running=" << state_.is_running; + std::lock_guard lock(mtx_); + if (!state_.is_running || !state_.is_decoding) { + av_packet_unref(pkt.get()); + goto end_decode; + } } + int ret = avcodec_receive_frame(codec_ctx.get(), frame.get()); + if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { + break; + } else if (ret < 0) { + break; + } + + // 计算输出样本数 int64_t max_samples64 = av_rescale_rnd( swr_get_delay(swr_ctx.get(), codec_ctx->sample_rate) + frame->nb_samples, sample_rate_, codec_ctx->sample_rate, AV_ROUND_UP); const int max_samples = static_cast(std::min(max_samples64, INT_MAX)); - auto buffer = make_shared>(max_samples * 2); // 2 channels + // 创建音频帧 + auto buffer = std::make_shared>(max_samples * channels_); uint8_t* out[] = { reinterpret_cast(buffer->data()), nullptr }; + // 重采样 const int out_samples = swr_convert( swr_ctx.get(), out, max_samples, const_cast(frame->data), frame->nb_samples); - int16_t* pcm_data = buffer->data(); + if (out_samples <= 0) { + continue; + } + + // 调整大小到实际样本数 + buffer->resize(out_samples * channels_); + + // 应用音量 float volume_scale; - { lock_guard lock(mtx_); volume_scale = static_cast(state_.volume) / 100.0f;} - const int sample_count = out_samples * 2; + { + std::lock_guard lock(mtx_); + volume_scale = static_cast(state_.volume) / 100.0f; + } + + int16_t* pcm_data = buffer->data(); + const int sample_count = buffer->size(); for (int i = 0; i < sample_count; ++i) { float scaled = static_cast(pcm_data[i]) * volume_scale; - pcm_data[i] = static_cast(clamp(scaled, -32768.f, 32767.f)); + pcm_data[i] = static_cast(std::clamp(scaled, -32768.f, 32767.f)); } - if (!audio_queue_.push(buffer)) { - LOG(WARNING) << "[ffmpegSpeaker] (decode_audio_): Audio queue full, drop frame"; + // 推入队列(使用非阻塞方式) + bool pushed = false; + int attempts = 0; + while (!pushed && attempts < 10) { + pushed = audio_queue_.push(buffer); + if (!pushed) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + attempts++; + } + } + + if (!pushed) { + std::cerr << "[ffmpegSpeaker] Failed to push audio frame to queue after 10 attempts" << std::endl; } } } av_packet_unref(pkt.get()); } - LOG(INFO) << "[ffmpegSpeaker] (decode_audio_): End of decoding"; - state_.is_decoding = false; + +end_decode: + { + std::lock_guard lock(mtx_); + state_.is_decoding = false; + } + std::cout << "[ffmpegSpeaker] Decoding finished" << std::endl; } void ffmpegSpeaker::play_audio_() { - while (state_.is_running) { - if (state_.is_paused) { - this_thread::sleep_for(chrono::milliseconds(500)); - continue; - } - AudioFrame frame; - if (audio_queue_.pop(frame)) { - const auto samples = frame->size() / 2; // stereo - if (snd_pcm_writei(pcm_handle_, frame->data(), samples) < 0) { - snd_pcm_prepare(pcm_handle_); // 恢复播放状态 - LOG(WARNING) << "[ffmpegSpeaker] ALSA buffer underrun, recover"; + int pa_error = 0; + + while (true) { + { + std::lock_guard lock(mtx_); + if (!state_.is_running) { + break; } } - else { - //音频数据队列为空且解码已经结束,退出 - if (!state_.is_decoding) { - if (pcm_handle_) { - // 停止播放,清空缓存,否则结束会有杂音 - snd_pcm_drop(pcm_handle_); - LOG(INFO) << "[ffmpegSpeaker] ALSA buffer cleared"; + + // 暂停处理 + if (state_.is_paused) { + std::unique_lock lock(mtx_pause_); + cv_pause_.wait(lock, [this]() { + return !state_.is_paused || !state_.is_running; + }); + if (!state_.is_running) break; + } + + AudioFrame frame; + + // 尝试获取音频帧 + if (!audio_queue_.pop(frame)) { + // 检查解码是否结束 + bool decoding_finished = false; + { + std::lock_guard lock(mtx_); + decoding_finished = !state_.is_decoding; + } + + if (decoding_finished && audio_queue_.empty()) { + break; // 解码结束且队列为空,播放完成 + } + + // 短暂等待后重试 + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + continue; + } + + // 使用 PulseAudio 播放 + if (pulse_simple_ && !frame->empty()) { + const size_t data_size = frame->size() * sizeof(int16_t); + int ret = pa_simple_write(pulse_simple_, frame->data(), data_size, &pa_error); + + if (ret < 0) { + std::cerr << "[ffmpegSpeaker] (play_audio_): PulseAudio write failed: " + << pa_strerror(pa_error) << std::endl; + + // 尝试重新连接 + resetPlayState(); + + // 稍等后重试初始化 + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + if (!initPulseDevice_()) { + std::cerr << "[ffmpegSpeaker] (play_audio_): Failed to reconnect" << std::endl; + { + std::lock_guard lock(mtx_); + state_.is_running = false; + } + break; + } + + // 重新写入当前帧 + ret = pa_simple_write(pulse_simple_, frame->data(), data_size, &pa_error); + if (ret < 0) { + std::lock_guard lock(mtx_); + state_.is_running = false; break; } } - this_thread::sleep_for(chrono::seconds(1)); // 空队列等待 } } - LOG(INFO) << "[ffmpegSpeaker] (play_audio_): Playback thread exiting"; - //state_.is_running = false; - resetPlayState(); + + // 播放结束,刷新缓冲区 + if (pulse_simple_) { + pa_simple_flush(pulse_simple_, &pa_error); + } + + { + std::lock_guard lock(mtx_); + state_.is_running = false; + } + + std::cout << "[ffmpegSpeaker] Playback finished" << std::endl; } void ffmpegSpeaker::updateParams(const std::pair& param) @@ -381,3 +542,87 @@ void ffmpegSpeaker::updateParams(const std::pair& para throw runtime_error(e.what()); } } + +bool ffmpegSpeaker::initPulseDevice_() { + // 验证参数 + if (sample_rate_ <= 0 || channels_ <= 0) { + std::cerr << "[ffmpegSpeaker] (initPulseDevice_): Invalid audio parameters: rate=" + << sample_rate_ << ", channels=" << channels_ << std::endl; + return false; + } + + // 配置采样规格 + sample_spec_.format = PA_SAMPLE_S16LE; // 16位有符号小端整数 + sample_spec_.rate = sample_rate_; // 采样率 + sample_spec_.channels = channels_; // 声道数 + + // 检查采样规格是否有效 + if (!pa_sample_spec_valid(&sample_spec_)) { + std::cerr << "[ffmpegSpeaker] (initPulseDevice_): Invalid sample specification: " + << "rate=" << sample_spec_.rate + << ", channels=" << sample_spec_.channels << std::endl; + return false; + } + + // 释放现有的连接 + if (pulse_simple_) { + pa_simple_free(pulse_simple_); + pulse_simple_ = nullptr; + } + + // 创建 PulseAudio 简单连接 + int pa_error = 0; + pulse_simple_ = pa_simple_new( + nullptr, // 使用默认服务器 + "ffmpegSpeaker", // 应用程序名称 + PA_STREAM_PLAYBACK, // 播放流 + nullptr, // 使用默认设备 + "Music", // 流描述 + &sample_spec_, // 采样规格 + nullptr, // 使用默认通道映射 + nullptr, // 缓冲区属性(使用默认) + &pa_error // 错误码 + ); + + if (!pulse_simple_) { + std::cerr << "[ffmpegSpeaker] (initPulseDevice_): Failed to create PulseAudio connection: " + << pa_strerror(pa_error) << std::endl; + return false; + } + + state_.is_initialized = true; + std::cout << "[ffmpegSpeaker] PulseAudio initialized: rate=" << sample_rate_ + << ", channels=" << channels_ << std::endl; + return true; +} + +bool ffmpegSpeaker::initAudioParams_(const std::string& audio_path) { + using namespace ffmpeg; + + auto fmt_ctx = make_format_context(audio_path); + if (!fmt_ctx) { + std::cerr << "[ffmpegSpeaker] Failed to open audio file: " << audio_path << std::endl; + return false; + } + + int stream_idx = av_find_best_stream(fmt_ctx.get(), AVMEDIA_TYPE_AUDIO, -1, -1, nullptr, 0); + if (stream_idx < 0) { + std::cerr << "[ffmpegSpeaker] No audio stream found" << std::endl; + return false; + } + + auto codec_par = fmt_ctx->streams[stream_idx]->codecpar; + channels_ = codec_par->channels; + sample_rate_ = codec_par->sample_rate; + + // 确保合理的参数 + if (channels_ <= 0) channels_ = 2; + if (sample_rate_ <= 0) sample_rate_ = 44100; + + // 检查并打印详细的音频参数 + std::cout << "[ffmpegSpeaker] Audio parameters: rate=" << sample_rate_ + << ", channels=" << channels_ + << ", format=" << av_get_sample_fmt_name((AVSampleFormat)codec_par->format) + << std::endl; + return true; +} diff --git a/src/service/src/grpc_speaker_service.cpp b/src/service/src/grpc_speaker_service.cpp index 57a1ba28..e9ae8d95 100644 --- a/src/service/src/grpc_speaker_service.cpp +++ b/src/service/src/grpc_speaker_service.cpp @@ -41,7 +41,7 @@ grpc::Status gRPCSpeakerServiceImpl::PlayAudio(grpc::ServerContext* context, string dev_id = request->header().device_id(); LOG(INFO) << "[gRPCSpeakerServiceImpl] (PlayAudio): id=" << dev_id; const auto dev = dmgr_.getDevice(dev_id); - dev->start(); + //dev->start(); dev->play(request->audio_path()); response->mutable_header()->set_success(true); setCurrentTimestamp(response->mutable_header()->mutable_timestamp());