First working jukebox using Subsonic API
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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" };
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user