From 654f97fa72e2e301ab32f2626ea8ca26c540a278 Mon Sep 17 00:00:00 2001 From: James R Date: Sun, 12 Feb 2023 02:15:23 -0800 Subject: [PATCH] media: add WebM Vorbis and VP8 encoders --- src/media/CMakeLists.txt | 4 ++ src/media/webm_encoder.hpp | 51 ++++++++++++++++++++++ src/media/webm_vorbis.hpp | 59 +++++++++++++++++++++++++ src/media/webm_vorbis_lace.cpp | 79 ++++++++++++++++++++++++++++++++++ src/media/webm_vp8.hpp | 44 +++++++++++++++++++ 5 files changed, 237 insertions(+) create mode 100644 src/media/webm_encoder.hpp create mode 100644 src/media/webm_vorbis.hpp create mode 100644 src/media/webm_vorbis_lace.cpp create mode 100644 src/media/webm_vp8.hpp diff --git a/src/media/CMakeLists.txt b/src/media/CMakeLists.txt index 5025a7550..8282d847f 100644 --- a/src/media/CMakeLists.txt +++ b/src/media/CMakeLists.txt @@ -15,8 +15,12 @@ target_sources(SRB2SDL2 PRIVATE vp8.hpp vpx_error.hpp webm.hpp + webm_encoder.hpp webm_container.cpp webm_container.hpp + webm_vorbis.hpp + webm_vorbis_lace.cpp + webm_vp8.hpp webm_writer.hpp yuv420p.cpp yuv420p.hpp diff --git a/src/media/webm_encoder.hpp b/src/media/webm_encoder.hpp new file mode 100644 index 000000000..197886dba --- /dev/null +++ b/src/media/webm_encoder.hpp @@ -0,0 +1,51 @@ +// 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_WEBM_ENCODER_HPP__ +#define __SRB2_MEDIA_WEBM_ENCODER_HPP__ + +#include + +#include "encoder.hpp" +#include "webm_container.hpp" + +namespace srb2::media +{ + +template +class WebmEncoder : virtual public MediaEncoder +{ +public: + WebmEncoder(WebmContainer& container, webm::track trackid) : container_(container), trackid_(trackid) + { + container_.init_queue(trackid_); + } + +protected: + WebmContainer& container_; + webm::track trackid_; + + std::size_t size() const { return container_.track_size(trackid_); } + time_unit_t duration() const { return container_.track_duration(trackid_); } + + static T* get_track(const WebmContainer& container, webm::track trackid) { return container.get_track(trackid); } + + T* track() const { return get_track(container_, trackid_); } + + virtual void write_frame(frame_buffer_t p, time_unit_t ts, bool is_key_frame) override final + { + const auto ts_nano = std::chrono::duration_cast(ts); + + container_.queue_frame(p, trackid_, ts_nano.count(), is_key_frame); + } +}; + +}; // namespace srb2::media + +#endif // __SRB2_MEDIA_WEBM_ENCODER_HPP__ diff --git a/src/media/webm_vorbis.hpp b/src/media/webm_vorbis.hpp new file mode 100644 index 000000000..5e3825ceb --- /dev/null +++ b/src/media/webm_vorbis.hpp @@ -0,0 +1,59 @@ +// 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_WEBM_VORBIS_HPP__ +#define __SRB2_MEDIA_WEBM_VORBIS_HPP__ + +#include +#include +#include +#include + +#include "../cxxutil.hpp" +#include "vorbis.hpp" +#include "webm_encoder.hpp" + +namespace srb2::media +{ + +class WebmVorbisEncoder : public WebmEncoder, public VorbisEncoder +{ +public: + WebmVorbisEncoder(WebmContainer& container, webm::track trackid, AudioEncoder::Config cfg) : + WebmEncoder(container, trackid), VorbisEncoder(cfg) + { + // write Vorbis extra data + + const auto p = make_vorbis_private_data(); + + SRB2_ASSERT(track()->SetCodecPrivate(reinterpret_cast(p.data()), p.size()) == true); + } + + virtual BitRate estimated_bit_rate() const override final + { + auto _ = container_.queue_guard(); + + const std::chrono::duration t = duration(); + + if (t <= t.zero()) + { + return {}; + } + + using namespace std::chrono_literals; + return {static_cast((size() * 8) / t.count()), 1s}; + } + +private: + std::vector make_vorbis_private_data(); +}; + +}; // namespace srb2::media + +#endif // __SRB2_MEDIA_WEBM_VORBIS_HPP__ diff --git a/src/media/webm_vorbis_lace.cpp b/src/media/webm_vorbis_lace.cpp new file mode 100644 index 000000000..506d3f763 --- /dev/null +++ b/src/media/webm_vorbis_lace.cpp @@ -0,0 +1,79 @@ +// 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 +#include +#include +#include + +#include + +#include "webm_vorbis.hpp" + +// https://www.matroska.org/technical/notes.html#xiph-lacing +// https://www.matroska.org/technical/codec_specs.html#a_vorbis + +using namespace srb2::media; + +static std::size_t lace_length(const ogg_packet& op) +{ + return (op.bytes / 255) + 1; +} + +static void lace(std::vector& v, const ogg_packet& op) +{ + // The lacing size is encoded in at least one byte. If + // the value is 255, add the value of the next byte in + // sequence. This ends with a byte that is less than 255. + + std::fill_n(std::back_inserter(v), lace_length(op) - 1, std::byte {255}); + + const unsigned char n = (op.bytes % 255); + v.emplace_back(std::byte {n}); +} + +std::vector WebmVorbisEncoder::make_vorbis_private_data() +{ + const headers_t packets = generate_headers(); + + std::vector v; + + // There are three Vorbis header packets. The lacing for + // these packets in Matroska does not count the final + // packet. + + // clang-format off + v.reserve( + 1 + + lace_length(packets[0]) + + lace_length(packets[1]) + + packets[0].bytes + + packets[1].bytes + + packets[2].bytes); + // clang-format on + + // The first byte is the number of packets. Once again, + // the last packet is not counted. + v.emplace_back(std::byte {2}); + + // Then the laced sizes for each packet. + lace(v, packets[0]); + lace(v, packets[1]); + + // Then each packet's data. The last packet's data + // actually is written here. + for (auto op : packets) + { + tcb::span p(reinterpret_cast(op.packet), op.bytes); + + std::copy(p.begin(), p.end(), std::back_inserter(v)); + } + + return v; +} diff --git a/src/media/webm_vp8.hpp b/src/media/webm_vp8.hpp new file mode 100644 index 000000000..44e951146 --- /dev/null +++ b/src/media/webm_vp8.hpp @@ -0,0 +1,44 @@ +// 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_WEBM_VP8_HPP__ +#define __SRB2_MEDIA_WEBM_VP8_HPP__ + +#include "vp8.hpp" +#include "webm_encoder.hpp" + +namespace srb2::media +{ + +class WebmVP8Encoder : public WebmEncoder, public VP8Encoder +{ +public: + WebmVP8Encoder(WebmContainer& container, webm::track trackid, VideoEncoder::Config cfg) : + WebmEncoder(container, trackid), VP8Encoder(cfg) + { + } + + virtual BitRate estimated_bit_rate() const override final + { + auto _ = container_.queue_guard(); + + const int frames = frame_count().frames; + + if (frames <= 0) + { + return {}; + } + + return {(size() * 8) / frames, std::chrono::duration(1.f / frame_rate())}; + } +}; + +}; // namespace srb2::media + +#endif // __SRB2_MEDIA_WEBM_VP8_HPP__