media: add libvorbis encoder

This commit is contained in:
James R 2023-02-12 02:07:45 -08:00
parent e9f5a75d4a
commit 650264ea86
4 changed files with 249 additions and 0 deletions

View file

@ -6,4 +6,7 @@ target_sources(SRB2SDL2 PRIVATE
options.hpp
video_encoder.hpp
video_frame.hpp
vorbis.cpp
vorbis.hpp
vorbis_error.hpp
)

138
src/media/vorbis.cpp Normal file
View file

@ -0,0 +1,138 @@
// RING RACERS
//-----------------------------------------------------------------------------
// Copyright (C) 2023 by James Robert Roman
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#include <chrono>
#include <cstddef>
#include <stdexcept>
#include <fmt/format.h>
#include <vorbis/vorbisenc.h>
#include "../cxxutil.hpp"
#include "vorbis.hpp"
#include "vorbis_error.hpp"
using namespace srb2::media;
// clang-format off
const Options VorbisEncoder::options_("vorbis", {
{"quality", Options::range<float>("0", -0.1f, 1.f)},
{"max_bitrate", Options::range_min<int>("-1", -1)},
{"nominal_bitrate", Options::range_min<int>("-1", -1)},
{"min_bitrate", Options::range_min<int>("-1", -1)},
});
// clang-format on
VorbisEncoder::VorbisEncoder(Config cfg)
{
const long max_bitrate = options_.get<int>("max_bitrate");
const long nominal_bitrate = options_.get<int>("nominal_bitrate");
const long min_bitrate = options_.get<int>("min_bitrate");
vorbis_info_init(&vi_);
if (max_bitrate != -1 || nominal_bitrate != -1 || min_bitrate != -1)
{
// managed bitrate mode
VorbisError error =
vorbis_encode_init(&vi_, cfg.channels, cfg.sample_rate, max_bitrate, nominal_bitrate, min_bitrate);
if (error != 0)
{
throw std::invalid_argument(fmt::format(
"vorbis_encode_init: {}, max_bitrate={}, nominal_bitrate={}, min_bitrate={}",
error,
max_bitrate,
nominal_bitrate,
min_bitrate
));
}
}
else
{
// variable bitrate mode
const float quality = options_.get<float>("quality");
VorbisError error = vorbis_encode_init_vbr(&vi_, cfg.channels, cfg.sample_rate, quality);
if (error != 0)
{
throw std::invalid_argument(fmt::format("vorbis_encode_init: {}, quality={}", error, quality));
}
}
SRB2_ASSERT(vorbis_analysis_init(&vd_, &vi_) == 0);
SRB2_ASSERT(vorbis_block_init(&vd_, &vb_) == 0);
}
VorbisEncoder::~VorbisEncoder()
{
vorbis_block_clear(&vb_);
vorbis_dsp_clear(&vd_);
vorbis_info_clear(&vi_);
}
VorbisEncoder::headers_t VorbisEncoder::generate_headers()
{
headers_t op;
vorbis_comment vc;
vorbis_comment_init(&vc);
VorbisError error = vorbis_analysis_headerout(&vd_, &vc, &op[0], &op[1], &op[2]);
if (error != 0)
{
throw std::invalid_argument(fmt::format("vorbis_analysis_headerout: {}", error));
}
vorbis_comment_clear(&vc);
return op;
}
void VorbisEncoder::analyse(sample_buffer_t in)
{
const int ch = channels();
const std::size_t n = in.size() / ch;
float** fv = vorbis_analysis_buffer(&vd_, n);
for (std::size_t i = 0; i < n; ++i)
{
auto s = in.subspan(i * ch, ch);
fv[0][i] = s[0];
fv[1][i] = s[1];
}
// automatically handles end of stream if n = 0
SRB2_ASSERT(vorbis_analysis_wrote(&vd_, n) == 0);
while (vorbis_analysis_blockout(&vd_, &vb_) > 0)
{
SRB2_ASSERT(vorbis_analysis(&vb_, nullptr) == 0);
SRB2_ASSERT(vorbis_bitrate_addblock(&vb_) == 0);
ogg_packet op;
while (vorbis_bitrate_flushpacket(&vd_, &op) > 0)
{
write_packet(&op);
}
}
}
void VorbisEncoder::write_packet(ogg_packet* op)
{
using T = const std::byte;
tcb::span<T> p(reinterpret_cast<T*>(op->packet), static_cast<std::size_t>(op->bytes));
write_frame(p, std::chrono::duration<float>(vorbis_granule_time(&vd_, op->granulepos)), true);
}

54
src/media/vorbis.hpp Normal file
View file

@ -0,0 +1,54 @@
// RING RACERS
//-----------------------------------------------------------------------------
// Copyright (C) 2023 by James Robert Roman
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#ifndef __SRB2_MEDIA_VORBIS_HPP__
#define __SRB2_MEDIA_VORBIS_HPP__
#include <array>
#include <vorbis/codec.h>
#include "audio_encoder.hpp"
#include "options.hpp"
namespace srb2::media
{
class VorbisEncoder : public AudioEncoder
{
public:
static const Options options_;
VorbisEncoder(Config config);
~VorbisEncoder();
virtual void encode(sample_buffer_t samples) override final { analyse(samples); }
virtual void flush() override final { analyse(); }
virtual const char* name() const override final { return "Vorbis"; }
virtual int channels() const override final { return vi_.channels; }
virtual int sample_rate() const override final { return vi_.rate; }
protected:
using headers_t = std::array<ogg_packet, 3>;
headers_t generate_headers();
private:
vorbis_info vi_;
vorbis_dsp_state vd_;
vorbis_block vb_;
void analyse(sample_buffer_t samples = {});
void write_packet(ogg_packet* op);
};
}; // namespace srb2::media
#endif // __SRB2_MEDIA_VORBIS_HPP__

View file

@ -0,0 +1,54 @@
// RING RACERS
//-----------------------------------------------------------------------------
// Copyright (C) 2023 by James Robert Roman
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#ifndef __SRB2_MEDIA_VORBIS_ERROR_HPP__
#define __SRB2_MEDIA_VORBIS_ERROR_HPP__
#include <string>
#include <fmt/format.h>
#include <vorbis/codec.h>
class VorbisError
{
public:
VorbisError(int error) : error_(error) {}
operator int() const { return error_; }
std::string name() const
{
switch (error_)
{
case OV_EFAULT:
return "Internal error (OV_EFAULT)";
case OV_EINVAL:
return "Invalid settings (OV_EINVAL)";
case OV_EIMPL:
return "Invalid settings (OV_EIMPL)";
default:
return fmt::format("error {}", error_);
}
}
private:
int error_;
};
template <>
struct fmt::formatter<VorbisError> : formatter<std::string>
{
template <typename FormatContext>
auto format(const VorbisError& error, FormatContext& ctx) const
{
return formatter<std::string>::format(error.name(), ctx);
}
};
#endif // __SRB2_MEDIA_VORBIS_ERROR_HPP__