First working jukebox using Subsonic API

This commit is contained in:
emeric
2026-02-17 23:16:52 +01:00
parent c338521843
commit 3963969cec
39 changed files with 1464 additions and 277 deletions
+1 -52
View File
@@ -19,7 +19,6 @@
#include "AudioFile.hpp"
#include <array>
#include <cstdio>
#include <unordered_map>
@@ -27,7 +26,6 @@ extern "C"
{
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/log.h>
}
#include "core/ILogger.hpp"
@@ -174,54 +172,6 @@ namespace lms::audio::ffmpeg
return std::nullopt;
}
}
core::LiteralString avLogLevelToStr(int level)
{
switch (level)
{
case AV_LOG_TRACE:
return "trace";
case AV_LOG_DEBUG:
return "debug";
case AV_LOG_VERBOSE:
return "verbose";
case AV_LOG_INFO:
return "info";
case AV_LOG_WARNING:
return "warning";
case AV_LOG_ERROR:
return "error";
case AV_LOG_FATAL:
return "fatal";
case AV_LOG_PANIC:
return "panic";
default:
return "unknown";
}
}
void avLogCallback(void*, int level, const char* fmt, va_list vl)
{
if (!core::Service<core::logging::ILogger>::get()->isSeverityActive(core::logging::Severity::DEBUG))
return;
if (level > AV_LOG_WARNING)
return;
std::array<char, 256> buffer{ 0 };
std::vsnprintf(buffer.data(), buffer.size(), fmt, vl);
LMS_LOG(AUDIO, DEBUG, "FFmpeg [" << avLogLevelToStr(level) << "] " << buffer.data());
}
class AvInitializer
{
public:
AvInitializer()
{
::av_log_set_callback(avLogCallback);
}
};
} // namespace
AudioFile::AudioFile(const std::filesystem::path& p)
@@ -229,8 +179,7 @@ namespace lms::audio::ffmpeg
{
LMS_SCOPED_TRACE_DETAILED("MetaData", "FFmpegParseFile");
// TODO move this
static AvInitializer init;
utils::init();
int error{ avformat_open_input(&_context, _p.c_str(), nullptr, nullptr) };
if (error < 0)
+21 -3
View File
@@ -41,9 +41,9 @@ extern "C"
namespace lms::audio
{
std::unique_ptr<IPcmDecoder> createPcmDecoder(const std::filesystem::path& filePath, const PcmParameters& parameters)
std::unique_ptr<IPcmDecoder> createPcmDecoder(const std::filesystem::path& filePath, std::chrono::microseconds offset, const PcmParameters& parameters)
{
return std::make_unique<ffmpeg::PcmDecoder>(filePath, parameters);
return std::make_unique<ffmpeg::PcmDecoder>(filePath, offset, parameters);
}
} // namespace lms::audio
@@ -69,12 +69,15 @@ namespace lms::audio::ffmpeg
}
} // namespace
PcmDecoder::PcmDecoder(const std::filesystem::path& filePath, const PcmParameters& parameters)
PcmDecoder::PcmDecoder(const std::filesystem::path& filePath, std::chrono::microseconds offset, const PcmParameters& parameters)
: _parameters{ parameters }
{
if (_parameters.channelCount > AV_NUM_DATA_POINTERS)
throw Exception("Channel count exceeds maximum supported channels");
utils::init();
// TODO: use AudioFile wrapper?
{
::AVFormatContext* context{};
int error{ ::avformat_open_input(&context, filePath.c_str(), nullptr, nullptr) };
@@ -109,6 +112,21 @@ namespace lms::audio::ffmpeg
throw FFmpegException{ "Cannot find best audio stream in '" + filePath.string() + "'", _inputStreamIndex };
}
if (offset.count() > 0)
{
const AVStream* stream{ _context->streams[_inputStreamIndex] };
using OffsetPeriod = decltype(offset)::period;
constexpr AVRational offsetTimebase{ static_cast<int>(OffsetPeriod::num), static_cast<int>(OffsetPeriod::den) };
const int64_t targetTimestamp{ static_cast<int64_t>(av_rescale_q(offset.count(), offsetTimebase, stream->time_base)) };
const int seekError{ ::av_seek_frame(_context.get(), _inputStreamIndex, targetTimestamp, AVSEEK_FLAG_BACKWARD) };
if (seekError < 0)
{
LMS_LOG(AUDIO, WARNING, "Failed to seek to offset: " << utils::averrorToString(seekError));
}
}
_decoderContext = AVCodecContextPtr{ ::avcodec_alloc_context3(decoder) };
if (!_decoderContext)
throw Exception{ "Cannot allocate decoder context" };
+2 -2
View File
@@ -28,14 +28,14 @@ namespace lms::audio::ffmpeg
class PcmDecoder : public IPcmDecoder
{
public:
PcmDecoder(const std::filesystem::path& filePath, const PcmParameters& parameters);
PcmDecoder(const std::filesystem::path& filePath, std::chrono::microseconds offset, const PcmParameters& parameters);
~PcmDecoder() override;
PcmDecoder(const PcmDecoder&) = delete;
PcmDecoder& operator=(const PcmDecoder&) = delete;
private:
const PcmParameters& getParameters() const;
const PcmParameters& getParameters() const override;
std::size_t readSamples(std::span<WritableBuffer> outputChannelBuffers) override;
bool finished() const override;
+68
View File
@@ -18,14 +18,77 @@
*/
#include "Utils.hpp"
#include "core/String.hpp"
#include <array>
extern "C"
{
#include <libavutil/error.h>
#include <libavutil/log.h>
}
#include "core/ILogger.hpp"
#include "core/LiteralString.hpp"
namespace lms::audio::ffmpeg::utils
{
namespace
{
core::LiteralString avLogLevelToStr(int level)
{
switch (level)
{
case AV_LOG_TRACE:
return "trace";
case AV_LOG_DEBUG:
return "debug";
case AV_LOG_VERBOSE:
return "verbose";
case AV_LOG_INFO:
return "info";
case AV_LOG_WARNING:
return "warning";
case AV_LOG_ERROR:
return "error";
case AV_LOG_FATAL:
return "fatal";
case AV_LOG_PANIC:
return "panic";
default:
return "unknown";
}
}
void avLogCallback(void*, int level, const char* fmt, va_list vl)
{
if (!core::Service<core::logging::ILogger>::get()->isSeverityActive(core::logging::Severity::DEBUG))
return;
if (level > AV_LOG_WARNING)
return;
std::array<char, 256> buffer{ 0 };
if (std::vsnprintf(buffer.data(), buffer.size(), fmt, vl) > 0)
{
std::string_view str{ buffer.data() };
str = core::stringUtils::stringTrimEnd(str, " \t\r\n");
// TODO translate levels?
LMS_LOG(AUDIO, DEBUG, "[FFmpeg] [" << avLogLevelToStr(level) << "] " << str);
}
}
class AvInitializer
{
public:
AvInitializer()
{
::av_log_set_callback(avLogCallback);
}
};
} // namespace
std::string averrorToString(int error)
{
std::array<char, 128> buf{ 0 };
@@ -62,4 +125,9 @@ namespace lms::audio::ffmpeg::utils
};
return fileExtensions;
}
void init()
{
static AvInitializer init;
}
} // namespace lms::audio::ffmpeg::utils
+2
View File
@@ -27,4 +27,6 @@ namespace lms::audio::ffmpeg::utils
std::string averrorToString(int error);
std::span<const std::filesystem::path> getSupportedExtensions();
void init();
} // namespace lms::audio::ffmpeg::utils