update ffmpeg_speaker

This commit is contained in:
linbo 2025-12-08 15:57:22 +08:00
parent f47a93c21f
commit e4c96e689b
4 changed files with 433 additions and 173 deletions

View File

@ -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)
target_link_libraries(ffmpeg_speaker PRIVATE -lpulse-simple -lpulse)

View File

@ -6,10 +6,13 @@
#include <thread>
#include <mutex>
#include <condition_variable>
#include "../../abstract_speaker.h"
#include "speaker/abstract_speaker.h"
#include <boost/lockfree/spsc_queue.hpp>
#include <pulse/simple.h>
#include <pulse/error.h>
#include <pulse/pulseaudio.h>
#include "ffmpeg_ptr.h"
#include "../../../../utils/base/include/os.h"
#include "utils/base/include/os.h"
extern "C" {
#include <alsa/asoundlib.h>
@ -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<std::thread> decode_thread_;
std::shared_ptr<std::thread> play_thread_;
std::mutex mtx_;
std::string audio_path_;
// Boost 单生产者单消费者无锁队列
boost::lockfree::spsc_queue<AudioFrame, boost::lockfree::capacity<AUDIO_QUEUE_CAPACITY>> audio_queue_;
// 暂停相关的同步变量
mutable std::mutex mtx_pause_;
std::condition_variable cv_pause_;
std::atomic<bool> is_paused_{false};
// 音频时钟同步
std::atomic<int64_t> audio_pts_{0};
std::chrono::time_point<std::chrono::steady_clock> playback_start_time_;
};
}

View File

@ -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<std::mutex> 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<std::mutex> 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<std::thread>([this]() {
try {
this->decode_audio_();
} catch (const std::exception& e) {
std::cerr << "[ffmpegSpeaker] Decode thread exception: " << e.what() << std::endl;
std::lock_guard<std::mutex> 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<std::thread>([this]() {
try {
this->play_audio_();
} catch (const std::exception& e) {
std::cerr << "[ffmpegSpeaker] Play thread exception: " << e.what() << std::endl;
std::lock_guard<std::mutex> lock(mtx_);
state_.is_running = false;
}
}
decode_thread_ = make_shared<thread>(&ffmpegSpeaker::decode_audio_, this);
play_thread_ = make_shared<thread>(&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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> lock(mtx_);
if (!state_.is_running || !state_.is_decoding) {
break;
}
}
if (av_read_frame(fmt_ctx.get(), pkt.get()) < 0) break;
// 暂停处理
{
std::unique_lock<std::mutex> 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<std::mutex> 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<int>(std::min<int64_t>(max_samples64, INT_MAX));
auto buffer = make_shared<vector<int16_t>>(max_samples * 2); // 2 channels
// 创建音频帧
auto buffer = std::make_shared<std::vector<int16_t>>(max_samples * channels_);
uint8_t* out[] = { reinterpret_cast<uint8_t*>(buffer->data()), nullptr };
// 重采样
const int out_samples = swr_convert(
swr_ctx.get(),
out, max_samples,
const_cast<const uint8_t**>(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<float>(state_.volume) / 100.0f;}
const int sample_count = out_samples * 2;
{
std::lock_guard<std::mutex> lock(mtx_);
volume_scale = static_cast<float>(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<float>(pcm_data[i]) * volume_scale;
pcm_data[i] = static_cast<int16_t>(clamp(scaled, -32768.f, 32767.f));
pcm_data[i] = static_cast<int16_t>(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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> lock(mtx_);
state_.is_running = false;
}
std::cout << "[ffmpegSpeaker] Playback finished" << std::endl;
}
void ffmpegSpeaker::updateParams(const std::pair<std::string, std::string>& param)
@ -381,3 +542,87 @@ void ffmpegSpeaker::updateParams(const std::pair<std::string, std::string>& 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;
}

View File

@ -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<AbstractSpeaker>(dev_id);
dev->start();
//dev->start();
dev->play(request->audio_path());
response->mutable_header()->set_success(true);
setCurrentTimestamp(response->mutable_header()->mutable_timestamp());