934 lines
29 KiB
C++
934 lines
29 KiB
C++
#include "common/base/logging/logger.h"
|
|
//
|
|
// Created by xtkuang on 2025/6/9.
|
|
//
|
|
|
|
#include <filesystem>
|
|
#include <algorithm>
|
|
#include <climits>
|
|
#include <cstring>
|
|
|
|
#include "../include/ffmpeg_speaker.h"
|
|
|
|
using namespace std;
|
|
using namespace cmvr::device;
|
|
|
|
ffmpegSpeaker::ffmpegSpeaker(const config::FFMpegSpeakerConfig& cfg):config_(cfg)
|
|
{
|
|
state_.is_initialized = false;
|
|
state_.is_running = false;
|
|
state_.is_paused = false;
|
|
try {
|
|
id_ = config_.id();
|
|
memset(&sample_spec_, 0, sizeof(sample_spec_));
|
|
state_.volume = 100;
|
|
}
|
|
catch (const exception& e) {
|
|
CMVR_LOG(ERROR) << "[ffmpegSpeaker] ([ffmpegSpeaker]): Failed to parse config: " << e.what();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
ffmpegSpeaker::~ffmpegSpeaker() {
|
|
is_stopping_ = true;
|
|
{
|
|
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();
|
|
}
|
|
|
|
bool ffmpegSpeaker::init() {
|
|
try {
|
|
lock_guard lock(mtx_);
|
|
state_.is_running = false;
|
|
state_.is_paused = false;
|
|
state_.is_decoding = false;
|
|
av_log_set_level(AV_LOG_QUIET);
|
|
avformat_network_init();
|
|
state_.is_initialized = true;
|
|
CMVR_LOG(INFO) << "[ffmpegSpeaker] (init): Success, id=" << id_;
|
|
return true;
|
|
}
|
|
catch (const exception& e) {
|
|
CMVR_LOG(ERROR) <<"[ffmpegSpeaker] (init): Failed, id=" << id_ << ": " << e.what();
|
|
state_.is_initialized = false;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool ffmpegSpeaker::start() {
|
|
return true;
|
|
}
|
|
|
|
void ffmpegSpeaker::resetPlayState()
|
|
{
|
|
releaseStreamDecoder_();
|
|
|
|
if (pulse_simple_) {
|
|
pa_simple_flush(pulse_simple_, nullptr);
|
|
pa_simple_free(pulse_simple_);
|
|
pulse_simple_ = nullptr;
|
|
}
|
|
|
|
// 清空队列
|
|
AudioFrame frame;
|
|
while (audio_queue_.pop(frame)) {
|
|
// 清空所有帧
|
|
}
|
|
|
|
state_.is_initialized = false;
|
|
is_streaming_input_ = false;
|
|
|
|
audio_path_.clear();
|
|
CMVR_LOG(INFO) << "[ffmpegSpeaker] (resetPlayState): Success, id=" << id_;
|
|
}
|
|
|
|
bool ffmpegSpeaker::stop() {
|
|
std::lock_guard<std::mutex> stop_lock(stop_mtx_);
|
|
is_stopping_ = true;
|
|
|
|
{
|
|
lock_guard lock(mtx_);
|
|
state_.is_running = false;
|
|
state_.is_decoding = false;
|
|
state_.is_paused = false;
|
|
is_streaming_input_ = 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();
|
|
is_stopping_ = false;
|
|
return true;
|
|
}
|
|
|
|
void ffmpegSpeaker::pause() {
|
|
try {
|
|
lock_guard lock(mtx_);
|
|
if (!state_.is_running || !state_.is_initialized) {
|
|
CMVR_LOG(ERROR) << "[ffmpegSpeaker] (pause): Speaker not running";
|
|
return;
|
|
}
|
|
|
|
if (state_.is_paused) {
|
|
CMVR_LOG(WARNING) << "[ffmpegSpeaker] (pause): Speaker already paused";
|
|
return;
|
|
}
|
|
state_.is_paused = true;
|
|
CMVR_LOG(INFO) << "[ffmpegSpeaker] (pause): Success, id=" << id_;
|
|
}
|
|
catch (const exception& e) {
|
|
CMVR_LOG(ERROR) <<"[ffmpegSpeaker] (pause): Failed, id=" << id_ << ": " << e.what();
|
|
}
|
|
}
|
|
|
|
void ffmpegSpeaker::resume() {
|
|
try {
|
|
lock_guard lock(mtx_);
|
|
if (!state_.is_running || !state_.is_initialized) {
|
|
CMVR_LOG(ERROR) << "[ffmpegSpeaker] (pause): Speaker not running";
|
|
return;
|
|
}
|
|
|
|
if (!state_.is_paused) {
|
|
CMVR_LOG(WARNING) << "[ffmpegSpeaker] (pause): Speaker is not paused";
|
|
return;
|
|
}
|
|
state_.is_paused = false;
|
|
cv_pause_.notify_all();
|
|
CMVR_LOG(INFO) << "[ffmpegSpeaker] (resume): Success, id=" << id_;
|
|
}
|
|
catch (const exception& e) {
|
|
CMVR_LOG(ERROR) <<"[ffmpegSpeaker] (resume): Failed, id=" << id_ << ": " << e.what();
|
|
}
|
|
}
|
|
|
|
void ffmpegSpeaker::play(const std::string& audio_path) {
|
|
std::unique_lock<std::mutex> lock(mtx_);
|
|
|
|
try {
|
|
if (state_.is_running) {
|
|
CMVR_LOG(WARNING) << "[ffmpegSpeaker] (play): Speaker is playing " << audio_path_ << ", id=" << id_;
|
|
return;
|
|
}
|
|
if (!std::filesystem::exists(audio_path)) {
|
|
CMVR_LOG(ERROR) << "[ffmpegSpeaker] (play): Audio file not found: " << audio_path;
|
|
return;
|
|
}
|
|
// 如果正在播放,先停止
|
|
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)) {
|
|
CMVR_LOG(ERROR) << "[ffmpegSpeaker] Failed to get audio parameters";
|
|
return;
|
|
}
|
|
|
|
// 初始化 PulseAudio 设备
|
|
if (!initPulseDevice_()) {
|
|
CMVR_LOG(ERROR) << "[ffmpegSpeaker] PulseAudio device initialization failed";
|
|
return;
|
|
}
|
|
|
|
// 设置状态
|
|
state_.is_running = true;
|
|
state_.is_decoding = true;
|
|
state_.is_paused = false;
|
|
|
|
// 启动解码线程
|
|
decode_thread_ = std::make_shared<std::thread>([this]() {
|
|
try {
|
|
this->decode_audio_();
|
|
} catch (const std::exception& e) {
|
|
CMVR_LOG(ERROR) << "[ffmpegSpeaker] Decode thread exception: " << e.what();
|
|
std::lock_guard<std::mutex> lock(mtx_);
|
|
state_.is_decoding = false;
|
|
}
|
|
});
|
|
|
|
// 等待解码线程开始(确保有数据)
|
|
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) {
|
|
CMVR_LOG(ERROR) << "[ffmpegSpeaker] Play thread exception: " << e.what();
|
|
std::lock_guard<std::mutex> lock(mtx_);
|
|
state_.is_running = false;
|
|
}
|
|
});
|
|
|
|
CMVR_LOG(INFO) << "[ffmpegSpeaker] Started playing: " << audio_path;
|
|
}
|
|
catch (const std::exception& e) {
|
|
CMVR_LOG(ERROR) << "[ffmpegSpeaker] (play): Failed, id=" << id_ << ": " << e.what();
|
|
state_.is_running = false;
|
|
state_.is_decoding = false;
|
|
lock.unlock();
|
|
resetPlayState();
|
|
}
|
|
}
|
|
|
|
void ffmpegSpeaker::setVolume(const int volume) {
|
|
lock_guard lock(mtx_);
|
|
state_.volume = clamp(volume, 0, 100);
|
|
}
|
|
|
|
int ffmpegSpeaker::getVolume() const {
|
|
return state_.volume;
|
|
}
|
|
|
|
void ffmpegSpeaker::getState(SpeakerState& state) {
|
|
state.is_decoding = state_.is_decoding;
|
|
state.volume = state_.volume;
|
|
state.is_initialized = state_.is_initialized;
|
|
state.is_running = state_.is_running;
|
|
state.is_paused = state_.is_paused;
|
|
}
|
|
|
|
|
|
void ffmpegSpeaker::decode_audio_() {
|
|
using namespace ffmpeg;
|
|
|
|
if (audio_path_.empty()) {
|
|
{
|
|
std::lock_guard<std::mutex> lock(mtx_);
|
|
state_.is_decoding = false;
|
|
}
|
|
CMVR_LOG(ERROR) << "[ffmpegSpeaker] (decode_audio_): audio_path is empty";
|
|
return;
|
|
}
|
|
|
|
CMVR_LOG(INFO) << "[ffmpegSpeaker] Decoding with: rate=" << sample_rate_
|
|
<< ", channels=" << channels_;
|
|
|
|
const AVFormatContextPtr fmt_ctx = make_format_context(audio_path_);
|
|
if (!fmt_ctx) {
|
|
{
|
|
std::lock_guard<std::mutex> lock(mtx_);
|
|
state_.is_decoding = false;
|
|
}
|
|
CMVR_LOG(ERROR) << "[ffmpegSpeaker] (decode_audio_): Failed to open format context";
|
|
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) {
|
|
{
|
|
std::lock_guard<std::mutex> lock(mtx_);
|
|
state_.is_decoding = false;
|
|
}
|
|
CMVR_LOG(ERROR) << "[ffmpegSpeaker] (decode_audio_): No audio stream found in file";
|
|
return;
|
|
}
|
|
|
|
AVStream* audio_stream = fmt_ctx->streams[stream_index];
|
|
const AVCodecContextPtr codec_ctx = make_codec_context(audio_stream, decoder);
|
|
if (!codec_ctx) {
|
|
{
|
|
std::lock_guard<std::mutex> lock(mtx_);
|
|
state_.is_decoding = false;
|
|
}
|
|
CMVR_LOG(ERROR) << "[ffmpegSpeaker] (decode_audio_): Failed to create codec context";
|
|
return;
|
|
}
|
|
|
|
const SwrContextPtr swr_ctx = make_swr_context(codec_ctx.get(), channels_, sample_rate_);
|
|
if (!swr_ctx) {
|
|
{
|
|
std::lock_guard<std::mutex> lock(mtx_);
|
|
state_.is_decoding = false;
|
|
}
|
|
CMVR_LOG(ERROR) << "[ffmpegSpeaker] (decode_audio_): Failed to init swr context";
|
|
return;
|
|
}
|
|
|
|
const AVPacketPtr pkt(av_packet_alloc());
|
|
const AVFramePtr frame(av_frame_alloc());
|
|
|
|
// 解码循环
|
|
while (true) {
|
|
{
|
|
std::lock_guard<std::mutex> lock(mtx_);
|
|
if (!state_.is_running || !state_.is_decoding) {
|
|
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) {
|
|
av_packet_unref(pkt.get());
|
|
continue;
|
|
}
|
|
|
|
while (true) {
|
|
{
|
|
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 = 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);
|
|
|
|
if (out_samples <= 0) {
|
|
continue;
|
|
}
|
|
|
|
// 调整大小到实际样本数
|
|
buffer->resize(out_samples * channels_);
|
|
|
|
// 应用音量
|
|
float volume_scale;
|
|
{
|
|
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>(std::clamp(scaled, -32768.f, 32767.f));
|
|
}
|
|
|
|
// 推入队列(使用非阻塞方式)
|
|
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) {
|
|
CMVR_LOG(ERROR) << "[ffmpegSpeaker] Failed to push audio frame to queue after 10 attempts";
|
|
}
|
|
}
|
|
}
|
|
av_packet_unref(pkt.get());
|
|
}
|
|
|
|
end_decode:
|
|
{
|
|
std::lock_guard<std::mutex> lock(mtx_);
|
|
state_.is_decoding = false;
|
|
}
|
|
CMVR_LOG(INFO) << "[ffmpegSpeaker] Decoding finished";
|
|
}
|
|
|
|
void ffmpegSpeaker::play_audio_() {
|
|
int pa_error = 0;
|
|
|
|
while (true) {
|
|
{
|
|
std::lock_guard<std::mutex> lock(mtx_);
|
|
if (!state_.is_running) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// 暂停处理
|
|
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) {
|
|
CMVR_LOG(ERROR) << "[ffmpegSpeaker] (play_audio_): PulseAudio write failed: "
|
|
<< pa_strerror(pa_error);
|
|
|
|
// 尝试重新连接
|
|
resetPlayState();
|
|
|
|
// 稍等后重试初始化
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
|
|
|
if (!initPulseDevice_()) {
|
|
CMVR_LOG(ERROR) << "[ffmpegSpeaker] (play_audio_): Failed to reconnect";
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 播放结束,刷新缓冲区
|
|
if (pulse_simple_) {
|
|
pa_simple_flush(pulse_simple_, &pa_error);
|
|
}
|
|
|
|
{
|
|
std::lock_guard<std::mutex> lock(mtx_);
|
|
state_.is_running = false;
|
|
}
|
|
|
|
CMVR_LOG(INFO) << "[ffmpegSpeaker] Playback finished";
|
|
}
|
|
|
|
bool ffmpegSpeaker::pushAudioFrame(const AudioStreamFrameData& frame_data)
|
|
{
|
|
if (is_stopping_) {
|
|
return false;
|
|
}
|
|
|
|
if (frame_data.data.empty()) {
|
|
return true;
|
|
}
|
|
|
|
bool need_start = false;
|
|
bool need_restart = false;
|
|
{
|
|
std::lock_guard<std::mutex> lock(mtx_);
|
|
need_start = !state_.is_running;
|
|
need_restart = state_.is_running && is_streaming_input_ &&
|
|
(frame_data.sample_rate != sample_rate_ || frame_data.channels != channels_);
|
|
|
|
if (state_.is_running && !is_streaming_input_) {
|
|
CMVR_LOG(ERROR) << "[ffmpegSpeaker] cannot stream audio while file playback is active";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (need_restart) {
|
|
stopStreaming();
|
|
need_start = true;
|
|
}
|
|
|
|
if (need_start && !startStreamingPlayback_(frame_data)) {
|
|
return false;
|
|
}
|
|
|
|
if (frame_data.format == AudioStreamFormat::PCM ||
|
|
frame_data.codec == "pcm_s16le" ||
|
|
frame_data.codec == "pcm") {
|
|
return pushPcmFrame_(frame_data);
|
|
}
|
|
return decodeStreamFrame_(frame_data);
|
|
}
|
|
|
|
void ffmpegSpeaker::stopStreaming()
|
|
{
|
|
std::lock_guard<std::mutex> stop_lock(stop_mtx_);
|
|
is_stopping_ = true;
|
|
{
|
|
std::lock_guard<std::mutex> lock(mtx_);
|
|
if (!is_streaming_input_) {
|
|
is_stopping_ = false;
|
|
return;
|
|
}
|
|
state_.is_decoding = false;
|
|
state_.is_paused = false;
|
|
is_streaming_input_ = false;
|
|
}
|
|
|
|
cv_pause_.notify_all();
|
|
|
|
if (play_thread_ && play_thread_->joinable()) {
|
|
play_thread_->join();
|
|
play_thread_.reset();
|
|
}
|
|
|
|
resetPlayState();
|
|
is_stopping_ = false;
|
|
}
|
|
|
|
bool ffmpegSpeaker::startStreamingPlayback_(const AudioStreamFrameData& frame_data)
|
|
{
|
|
std::lock_guard<std::mutex> lock(mtx_);
|
|
|
|
sample_rate_ = frame_data.sample_rate > 0 ? frame_data.sample_rate : 44100;
|
|
channels_ = frame_data.channels > 0 ? frame_data.channels : 2;
|
|
|
|
AudioFrame queued_frame;
|
|
while (audio_queue_.pop(queued_frame)) {
|
|
}
|
|
|
|
if (!initPulseDevice_()) {
|
|
CMVR_LOG(ERROR) << "[ffmpegSpeaker] PulseAudio device initialization failed";
|
|
return false;
|
|
}
|
|
|
|
state_.is_running = true;
|
|
state_.is_decoding = true;
|
|
state_.is_paused = false;
|
|
is_streaming_input_ = true;
|
|
|
|
play_thread_ = std::make_shared<std::thread>([this]() {
|
|
try {
|
|
this->play_audio_();
|
|
} catch (const std::exception& e) {
|
|
CMVR_LOG(ERROR) << "[ffmpegSpeaker] Stream play thread exception: " << e.what();
|
|
std::lock_guard<std::mutex> lock(mtx_);
|
|
state_.is_running = false;
|
|
state_.is_decoding = false;
|
|
}
|
|
});
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ffmpegSpeaker::pushPcmFrame_(const AudioStreamFrameData& frame_data)
|
|
{
|
|
const size_t sample_count = frame_data.data.size() / sizeof(int16_t);
|
|
if (sample_count == 0) {
|
|
return true;
|
|
}
|
|
|
|
auto buffer = std::make_shared<std::vector<int16_t>>(sample_count);
|
|
std::memcpy(buffer->data(), frame_data.data.data(), sample_count * sizeof(int16_t));
|
|
|
|
float volume_scale = 1.0f;
|
|
{
|
|
std::lock_guard<std::mutex> lock(mtx_);
|
|
volume_scale = static_cast<float>(state_.volume) / 100.0f;
|
|
}
|
|
|
|
for (int16_t& sample : *buffer) {
|
|
const float scaled = static_cast<float>(sample) * volume_scale;
|
|
sample = static_cast<int16_t>(std::clamp(scaled, -32768.f, 32767.f));
|
|
}
|
|
|
|
while (true) {
|
|
{
|
|
std::lock_guard<std::mutex> lock(mtx_);
|
|
if (!state_.is_running || is_stopping_) {
|
|
return false;
|
|
}
|
|
}
|
|
if (audio_queue_.push(buffer)) {
|
|
break;
|
|
}
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool ffmpegSpeaker::decodeStreamFrame_(const AudioStreamFrameData& frame_data)
|
|
{
|
|
std::lock_guard<std::mutex> decode_lock(stream_decode_mtx_);
|
|
if (!stream_decoder_ctx_ ||
|
|
stream_codec_ != frame_data.codec ||
|
|
stream_input_sample_rate_ != frame_data.sample_rate ||
|
|
stream_input_channels_ != frame_data.channels) {
|
|
releaseStreamDecoderUnlocked_();
|
|
if (!initStreamDecoder_(frame_data)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
av_packet_unref(stream_decode_packet_);
|
|
if (av_new_packet(stream_decode_packet_, static_cast<int>(frame_data.data.size())) < 0) {
|
|
return false;
|
|
}
|
|
std::memcpy(stream_decode_packet_->data, frame_data.data.data(), frame_data.data.size());
|
|
stream_decode_packet_->pts = frame_data.pts;
|
|
|
|
int ret = avcodec_send_packet(stream_decoder_ctx_, stream_decode_packet_);
|
|
av_packet_unref(stream_decode_packet_);
|
|
if (ret < 0) {
|
|
return false;
|
|
}
|
|
|
|
while ((ret = avcodec_receive_frame(stream_decoder_ctx_, stream_decode_frame_)) == 0) {
|
|
if (!stream_swr_ctx_) {
|
|
stream_swr_ctx_ = swr_alloc_set_opts(nullptr,
|
|
av_get_default_channel_layout(channels_),
|
|
AV_SAMPLE_FMT_S16,
|
|
sample_rate_,
|
|
av_get_default_channel_layout(stream_decode_frame_->channels),
|
|
static_cast<AVSampleFormat>(stream_decode_frame_->format),
|
|
stream_decode_frame_->sample_rate,
|
|
0,
|
|
nullptr);
|
|
if (!stream_swr_ctx_ || swr_init(stream_swr_ctx_) < 0) {
|
|
CMVR_LOG(ERROR) << "[ffmpegSpeaker] Failed to init stream resampler";
|
|
av_frame_unref(stream_decode_frame_);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
const int64_t max_samples64 = av_rescale_rnd(
|
|
swr_get_delay(stream_swr_ctx_, stream_decode_frame_->sample_rate) + stream_decode_frame_->nb_samples,
|
|
sample_rate_,
|
|
stream_decode_frame_->sample_rate,
|
|
AV_ROUND_UP);
|
|
const int max_samples = static_cast<int>(std::min<int64_t>(max_samples64, INT_MAX));
|
|
|
|
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(stream_swr_ctx_,
|
|
out,
|
|
max_samples,
|
|
const_cast<const uint8_t**>(stream_decode_frame_->data),
|
|
stream_decode_frame_->nb_samples);
|
|
av_frame_unref(stream_decode_frame_);
|
|
if (out_samples <= 0) {
|
|
continue;
|
|
}
|
|
|
|
buffer->resize(out_samples * channels_);
|
|
|
|
float volume_scale = 1.0f;
|
|
{
|
|
std::lock_guard<std::mutex> lock(mtx_);
|
|
volume_scale = static_cast<float>(state_.volume) / 100.0f;
|
|
}
|
|
for (int16_t& sample : *buffer) {
|
|
const float scaled = static_cast<float>(sample) * volume_scale;
|
|
sample = static_cast<int16_t>(std::clamp(scaled, -32768.f, 32767.f));
|
|
}
|
|
|
|
while (true) {
|
|
{
|
|
std::lock_guard<std::mutex> lock(mtx_);
|
|
if (!state_.is_running || is_stopping_) {
|
|
return false;
|
|
}
|
|
}
|
|
if (audio_queue_.push(buffer)) {
|
|
break;
|
|
}
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
}
|
|
}
|
|
|
|
return ret == AVERROR(EAGAIN) || ret == AVERROR_EOF;
|
|
}
|
|
|
|
bool ffmpegSpeaker::initStreamDecoder_(const AudioStreamFrameData& frame_data)
|
|
{
|
|
const AVCodec* decoder = nullptr;
|
|
if (!frame_data.codec.empty()) {
|
|
decoder = avcodec_find_decoder_by_name(frame_data.codec.c_str());
|
|
}
|
|
if (!decoder) {
|
|
AVCodecID codec_id = AV_CODEC_ID_NONE;
|
|
if (frame_data.format == AudioStreamFormat::AAC) {
|
|
codec_id = AV_CODEC_ID_AAC;
|
|
} else if (frame_data.format == AudioStreamFormat::MP3) {
|
|
codec_id = AV_CODEC_ID_MP3;
|
|
}
|
|
if (codec_id != AV_CODEC_ID_NONE) {
|
|
decoder = avcodec_find_decoder(codec_id);
|
|
}
|
|
}
|
|
if (!decoder) {
|
|
CMVR_LOG(ERROR) << "[ffmpegSpeaker] Unsupported stream audio codec: " << frame_data.codec;
|
|
return false;
|
|
}
|
|
|
|
stream_decoder_ctx_ = avcodec_alloc_context3(decoder);
|
|
if (!stream_decoder_ctx_) {
|
|
return false;
|
|
}
|
|
stream_decoder_ctx_->sample_rate = frame_data.sample_rate > 0 ? frame_data.sample_rate : sample_rate_;
|
|
stream_decoder_ctx_->channels = frame_data.channels > 0 ? frame_data.channels : channels_;
|
|
stream_decoder_ctx_->channel_layout =
|
|
stream_decoder_ctx_->channels == 1 ? AV_CH_LAYOUT_MONO : AV_CH_LAYOUT_STEREO;
|
|
|
|
if (avcodec_open2(stream_decoder_ctx_, decoder, nullptr) < 0) {
|
|
releaseStreamDecoderUnlocked_();
|
|
return false;
|
|
}
|
|
|
|
stream_decode_frame_ = av_frame_alloc();
|
|
stream_decode_packet_ = av_packet_alloc();
|
|
if (!stream_decode_frame_ || !stream_decode_packet_) {
|
|
releaseStreamDecoderUnlocked_();
|
|
return false;
|
|
}
|
|
|
|
stream_codec_ = frame_data.codec;
|
|
stream_input_sample_rate_ = frame_data.sample_rate;
|
|
stream_input_channels_ = frame_data.channels;
|
|
return true;
|
|
}
|
|
|
|
void ffmpegSpeaker::releaseStreamDecoder_()
|
|
{
|
|
std::lock_guard<std::mutex> decode_lock(stream_decode_mtx_);
|
|
releaseStreamDecoderUnlocked_();
|
|
}
|
|
|
|
void ffmpegSpeaker::releaseStreamDecoderUnlocked_()
|
|
{
|
|
if (stream_swr_ctx_) {
|
|
swr_free(&stream_swr_ctx_);
|
|
}
|
|
if (stream_decode_frame_) {
|
|
av_frame_free(&stream_decode_frame_);
|
|
}
|
|
if (stream_decode_packet_) {
|
|
av_packet_free(&stream_decode_packet_);
|
|
}
|
|
if (stream_decoder_ctx_) {
|
|
avcodec_free_context(&stream_decoder_ctx_);
|
|
}
|
|
stream_codec_.clear();
|
|
stream_input_sample_rate_ = 0;
|
|
stream_input_channels_ = 0;
|
|
}
|
|
|
|
bool ffmpegSpeaker::initPulseDevice_() {
|
|
// 验证参数
|
|
if (sample_rate_ <= 0 || channels_ <= 0) {
|
|
CMVR_LOG(ERROR) << "[ffmpegSpeaker] (initPulseDevice_): Invalid audio parameters: rate="
|
|
<< sample_rate_ << ", channels=" << channels_;
|
|
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_)) {
|
|
CMVR_LOG(ERROR) << "[ffmpegSpeaker] (initPulseDevice_): Invalid sample specification: "
|
|
<< "rate=" << sample_spec_.rate
|
|
<< ", channels=" << sample_spec_.channels;
|
|
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_) {
|
|
CMVR_LOG(ERROR) << "[ffmpegSpeaker] (initPulseDevice_): Failed to create PulseAudio connection: "
|
|
<< pa_strerror(pa_error);
|
|
return false;
|
|
}
|
|
|
|
state_.is_initialized = true;
|
|
CMVR_LOG(INFO) << "[ffmpegSpeaker] PulseAudio initialized: rate=" << sample_rate_
|
|
<< ", channels=" << channels_;
|
|
return true;
|
|
}
|
|
|
|
bool ffmpegSpeaker::initAudioParams_(const std::string& audio_path) {
|
|
using namespace ffmpeg;
|
|
|
|
auto fmt_ctx = make_format_context(audio_path);
|
|
if (!fmt_ctx) {
|
|
CMVR_LOG(ERROR) << "[ffmpegSpeaker] Failed to open audio file: " << audio_path;
|
|
return false;
|
|
}
|
|
|
|
int stream_idx = av_find_best_stream(fmt_ctx.get(), AVMEDIA_TYPE_AUDIO, -1, -1, nullptr, 0);
|
|
if (stream_idx < 0) {
|
|
CMVR_LOG(ERROR) << "[ffmpegSpeaker] No audio stream found";
|
|
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;
|
|
|
|
// 检查并打印详细的音频参数
|
|
CMVR_LOG(INFO) << "[ffmpegSpeaker] Audio parameters: rate=" << sample_rate_
|
|
<< ", channels=" << channels_
|
|
<< ", format=" << av_get_sample_fmt_name((AVSampleFormat)codec_par->format)
|
|
;
|
|
return true;
|
|
}
|