mirror of
https://github.com/KartKrewDev/RingRacers.git
synced 2025-10-30 08:01:28 +00:00
media: add libwebm container
This commit is contained in:
parent
b8015b4ad2
commit
60899133c1
7 changed files with 474 additions and 0 deletions
|
|
@ -1,5 +1,7 @@
|
|||
target_sources(SRB2SDL2 PRIVATE
|
||||
audio_encoder.hpp
|
||||
cfile.cpp
|
||||
cfile.hpp
|
||||
container.hpp
|
||||
encoder.hpp
|
||||
options.cpp
|
||||
|
|
@ -12,6 +14,10 @@ target_sources(SRB2SDL2 PRIVATE
|
|||
vp8.cpp
|
||||
vp8.hpp
|
||||
vpx_error.hpp
|
||||
webm.hpp
|
||||
webm_container.cpp
|
||||
webm_container.hpp
|
||||
webm_writer.hpp
|
||||
yuv420p.cpp
|
||||
yuv420p.hpp
|
||||
)
|
||||
|
|
|
|||
34
src/media/cfile.cpp
Normal file
34
src/media/cfile.cpp
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
// 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 <cerrno>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "cfile.hpp"
|
||||
|
||||
using namespace srb2::media;
|
||||
|
||||
CFile::CFile(const std::string file_name) : name_(file_name)
|
||||
{
|
||||
file_ = std::fopen(name(), "wb");
|
||||
|
||||
if (file_ == nullptr)
|
||||
{
|
||||
throw std::invalid_argument(fmt::format("{}: {}", name(), std::strerror(errno)));
|
||||
}
|
||||
}
|
||||
|
||||
CFile::~CFile()
|
||||
{
|
||||
std::fclose(file_);
|
||||
}
|
||||
36
src/media/cfile.hpp
Normal file
36
src/media/cfile.hpp
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
// 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_CFILE_HPP__
|
||||
#define __SRB2_MEDIA_CFILE_HPP__
|
||||
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
|
||||
namespace srb2::media
|
||||
{
|
||||
|
||||
class CFile
|
||||
{
|
||||
public:
|
||||
CFile(const std::string file_name);
|
||||
~CFile();
|
||||
|
||||
operator std::FILE*() const { return file_; }
|
||||
|
||||
const char* name() const { return name_.c_str(); }
|
||||
|
||||
private:
|
||||
std::string name_;
|
||||
std::FILE* file_;
|
||||
};
|
||||
|
||||
}; // namespace srb2::media
|
||||
|
||||
#endif // __SRB2_MEDIA_CFILE_HPP__
|
||||
26
src/media/webm.hpp
Normal file
26
src/media/webm.hpp
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
// 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_HPP__
|
||||
#define __SRB2_MEDIA_WEBM_HPP__
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <ratio>
|
||||
|
||||
namespace srb2::media::webm
|
||||
{
|
||||
|
||||
using track = uint64_t;
|
||||
using timestamp = uint64_t;
|
||||
using duration = std::chrono::duration<timestamp, std::nano>;
|
||||
|
||||
}; // namespace srb2::media::webm
|
||||
|
||||
#endif // __SRB2_MEDIA_WEBM_HPP__
|
||||
228
src/media/webm_container.cpp
Normal file
228
src/media/webm_container.cpp
Normal file
|
|
@ -0,0 +1,228 @@
|
|||
// 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 <cstdint>
|
||||
#include <memory>
|
||||
|
||||
#include "../cxxutil.hpp"
|
||||
#include "webm_vorbis.hpp"
|
||||
#include "webm_vp8.hpp"
|
||||
|
||||
using namespace srb2::media;
|
||||
|
||||
using time_unit_t = MediaEncoder::time_unit_t;
|
||||
|
||||
WebmContainer::WebmContainer(const Config cfg) : writer_(cfg.file_name), dtor_cb_(cfg.destructor_callback)
|
||||
{
|
||||
SRB2_ASSERT(segment_.Init(&writer_) == true);
|
||||
}
|
||||
|
||||
WebmContainer::~WebmContainer()
|
||||
{
|
||||
flush_queue();
|
||||
|
||||
if (!segment_.Finalize())
|
||||
{
|
||||
CONS_Alert(CONS_WARNING, "mkvmuxer::Segment::Finalize has failed\n");
|
||||
}
|
||||
|
||||
finalized_ = true;
|
||||
|
||||
if (dtor_cb_)
|
||||
{
|
||||
dtor_cb_(*this);
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<AudioEncoder> WebmContainer::make_audio_encoder(AudioEncoder::Config cfg)
|
||||
{
|
||||
const uint64_t tid = segment_.AddAudioTrack(cfg.sample_rate, cfg.channels, 0);
|
||||
|
||||
return std::make_unique<WebmVorbisEncoder>(*this, tid, cfg);
|
||||
}
|
||||
|
||||
std::unique_ptr<VideoEncoder> WebmContainer::make_video_encoder(VideoEncoder::Config cfg)
|
||||
{
|
||||
const uint64_t tid = segment_.AddVideoTrack(cfg.width, cfg.height, 0);
|
||||
|
||||
return std::make_unique<WebmVP8Encoder>(*this, tid, cfg);
|
||||
}
|
||||
|
||||
time_unit_t WebmContainer::duration() const
|
||||
{
|
||||
if (finalized_)
|
||||
{
|
||||
const auto& si = *segment_.segment_info();
|
||||
|
||||
return webm::duration(static_cast<uint64_t>(si.duration() * si.timecode_scale()));
|
||||
}
|
||||
|
||||
auto _ = queue_guard();
|
||||
|
||||
return webm::duration(latest_timestamp_);
|
||||
}
|
||||
|
||||
std::size_t WebmContainer::size() const
|
||||
{
|
||||
if (finalized_)
|
||||
{
|
||||
return writer_.Position();
|
||||
}
|
||||
|
||||
auto _ = queue_guard();
|
||||
|
||||
return writer_.Position() + queue_size_;
|
||||
}
|
||||
|
||||
std::size_t WebmContainer::track_size(webm::track trackid) const
|
||||
{
|
||||
auto _ = queue_guard();
|
||||
|
||||
return queue_.at(trackid).data_size;
|
||||
}
|
||||
|
||||
time_unit_t WebmContainer::track_duration(webm::track trackid) const
|
||||
{
|
||||
auto _ = queue_guard();
|
||||
|
||||
return webm::duration(queue_.at(trackid).flushed_timestamp);
|
||||
}
|
||||
|
||||
void WebmContainer::write_frame(
|
||||
tcb::span<const std::byte> buffer,
|
||||
webm::track trackid,
|
||||
webm::timestamp timestamp,
|
||||
bool is_key_frame
|
||||
)
|
||||
{
|
||||
SRB2_ASSERT(
|
||||
segment_.AddFrame(
|
||||
reinterpret_cast<const uint8_t*>(buffer.data()),
|
||||
buffer.size_bytes(),
|
||||
trackid,
|
||||
timestamp,
|
||||
is_key_frame
|
||||
) == true
|
||||
);
|
||||
|
||||
queue_[trackid].data_size += buffer.size_bytes();
|
||||
}
|
||||
|
||||
void WebmContainer::queue_frame(
|
||||
tcb::span<const std::byte> buffer,
|
||||
webm::track trackid,
|
||||
webm::timestamp timestamp,
|
||||
bool is_key_frame
|
||||
)
|
||||
{
|
||||
auto _ = queue_guard();
|
||||
|
||||
auto& q = queue_.at(trackid);
|
||||
|
||||
// If another track is behind this one, queue this
|
||||
// frame until the other track catches up.
|
||||
|
||||
if (flush_queue() < timestamp)
|
||||
{
|
||||
q.frames.emplace_back(buffer, timestamp, is_key_frame);
|
||||
queue_size_ += buffer.size_bytes();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Nothing is waiting; this frame can be written
|
||||
// immediately.
|
||||
|
||||
write_frame(buffer, trackid, timestamp, is_key_frame);
|
||||
q.flushed_timestamp = timestamp;
|
||||
}
|
||||
|
||||
q.queued_timestamp = timestamp;
|
||||
latest_timestamp_ = timestamp;
|
||||
}
|
||||
|
||||
webm::timestamp WebmContainer::flush_queue()
|
||||
{
|
||||
webm::timestamp goal = latest_timestamp_;
|
||||
|
||||
// Flush all tracks' queues, not beyond the end of the
|
||||
// shortest track.
|
||||
|
||||
for (const auto& [_, q] : queue_)
|
||||
{
|
||||
if (q.queued_timestamp < goal)
|
||||
{
|
||||
goal = q.queued_timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
webm::timestamp shortest;
|
||||
|
||||
do
|
||||
{
|
||||
shortest = goal;
|
||||
|
||||
for (const auto& [tid, q] : queue_)
|
||||
{
|
||||
const webm::timestamp flushed = flush_single_queue(tid, q.queued_timestamp);
|
||||
|
||||
if (flushed < shortest)
|
||||
{
|
||||
shortest = flushed;
|
||||
}
|
||||
}
|
||||
} while (shortest < goal);
|
||||
|
||||
return shortest;
|
||||
}
|
||||
|
||||
webm::timestamp WebmContainer::flush_single_queue(webm::track trackid, webm::timestamp flushed_timestamp)
|
||||
{
|
||||
webm::timestamp goal = flushed_timestamp;
|
||||
|
||||
// Find the lowest timestamp yet flushed from all other
|
||||
// tracks. We cannot write a frame beyond this timestamp
|
||||
// because PTS must only increase.
|
||||
|
||||
for (const auto& [tid, other] : queue_)
|
||||
{
|
||||
if (tid != trackid && other.flushed_timestamp < goal)
|
||||
{
|
||||
goal = other.flushed_timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
auto& q = queue_.at(trackid);
|
||||
auto it = q.frames.cbegin();
|
||||
|
||||
// Flush previously queued frames in this track.
|
||||
|
||||
for (; it != q.frames.cend(); ++it)
|
||||
{
|
||||
const auto& frame = *it;
|
||||
|
||||
if (frame.timestamp > goal)
|
||||
{
|
||||
q.flushed_timestamp = frame.timestamp;
|
||||
break;
|
||||
}
|
||||
|
||||
write_frame(frame.buffer, trackid, frame.timestamp, frame.is_key_frame);
|
||||
|
||||
queue_size_ -= frame.buffer.size();
|
||||
}
|
||||
|
||||
q.frames.erase(q.frames.cbegin(), it);
|
||||
|
||||
if (q.frames.empty())
|
||||
{
|
||||
q.flushed_timestamp = flushed_timestamp;
|
||||
}
|
||||
|
||||
return goal;
|
||||
}
|
||||
112
src/media/webm_container.hpp
Normal file
112
src/media/webm_container.hpp
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
// 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_CONTAINER_HPP__
|
||||
#define __SRB2_MEDIA_WEBM_CONTAINER_HPP__
|
||||
|
||||
#include <cstddef>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include <mkvmuxer/mkvmuxer.h>
|
||||
|
||||
#include "container.hpp"
|
||||
#include "webm.hpp"
|
||||
#include "webm_writer.hpp"
|
||||
|
||||
namespace srb2::media
|
||||
{
|
||||
|
||||
class WebmContainer : virtual public MediaContainer
|
||||
{
|
||||
public:
|
||||
WebmContainer(Config cfg);
|
||||
~WebmContainer();
|
||||
|
||||
virtual std::unique_ptr<AudioEncoder> make_audio_encoder(AudioEncoder::Config config) override final;
|
||||
virtual std::unique_ptr<VideoEncoder> make_video_encoder(VideoEncoder::Config config) override final;
|
||||
|
||||
virtual const char* name() const override final { return "WebM"; }
|
||||
virtual const char* file_name() const override final { return writer_.name(); }
|
||||
|
||||
virtual time_unit_t duration() const override final;
|
||||
virtual std::size_t size() const override final;
|
||||
|
||||
std::size_t track_size(webm::track trackid) const;
|
||||
time_unit_t track_duration(webm::track trackid) const;
|
||||
|
||||
template <typename T = mkvmuxer::Track>
|
||||
T* get_track(webm::track trackid) const
|
||||
{
|
||||
return reinterpret_cast<T*>(segment_.GetTrackByNumber(trackid));
|
||||
}
|
||||
|
||||
void init_queue(webm::track trackid) { queue_.try_emplace(trackid); }
|
||||
|
||||
// init_queue MUST be called before using this function.
|
||||
void queue_frame(
|
||||
tcb::span<const std::byte> buffer,
|
||||
webm::track trackid,
|
||||
webm::timestamp timestamp,
|
||||
bool is_key_frame
|
||||
);
|
||||
|
||||
auto queue_guard() const { return std::lock_guard(queue_mutex_); }
|
||||
|
||||
private:
|
||||
struct FrameQueue
|
||||
{
|
||||
struct Frame
|
||||
{
|
||||
std::vector<std::byte> buffer;
|
||||
webm::timestamp timestamp;
|
||||
bool is_key_frame;
|
||||
|
||||
Frame(tcb::span<const std::byte> buffer_, webm::timestamp timestamp_, bool is_key_frame_) :
|
||||
buffer(buffer_.begin(), buffer_.end()), timestamp(timestamp_), is_key_frame(is_key_frame_)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<Frame> frames;
|
||||
std::size_t data_size = 0;
|
||||
|
||||
webm::timestamp flushed_timestamp = 0;
|
||||
webm::timestamp queued_timestamp = 0;
|
||||
};
|
||||
|
||||
mkvmuxer::Segment segment_;
|
||||
WebmWriter writer_;
|
||||
|
||||
mutable std::recursive_mutex queue_mutex_;
|
||||
|
||||
std::unordered_map<webm::track, FrameQueue> queue_;
|
||||
|
||||
webm::timestamp latest_timestamp_ = 0;
|
||||
std::size_t queue_size_ = 0;
|
||||
|
||||
bool finalized_ = false;
|
||||
const dtor_cb_t dtor_cb_;
|
||||
|
||||
void write_frame(
|
||||
tcb::span<const std::byte> buffer,
|
||||
webm::track trackid,
|
||||
webm::timestamp timestamp,
|
||||
bool is_key_frame
|
||||
);
|
||||
|
||||
// Returns the largest timestamp that can be written.
|
||||
webm::timestamp flush_queue();
|
||||
webm::timestamp flush_single_queue(webm::track trackid, webm::timestamp flushed_timestamp);
|
||||
};
|
||||
|
||||
}; // namespace srb2::media
|
||||
|
||||
#endif // __SRB2_MEDIA_WEBM_CONTAINER_HPP__
|
||||
32
src/media/webm_writer.hpp
Normal file
32
src/media/webm_writer.hpp
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
// 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_WRITER_HPP__
|
||||
#define __SRB2_MEDIA_WEBM_WRITER_HPP__
|
||||
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
|
||||
#include <mkvmuxer/mkvwriter.h>
|
||||
|
||||
#include "cfile.hpp"
|
||||
|
||||
namespace srb2::media
|
||||
{
|
||||
|
||||
class WebmWriter : public CFile, public mkvmuxer::MkvWriter
|
||||
{
|
||||
public:
|
||||
WebmWriter(const std::string file_name) : CFile(file_name), MkvWriter(static_cast<std::FILE*>(*this)) {}
|
||||
~WebmWriter() { MkvWriter::Close(); }
|
||||
};
|
||||
|
||||
}; // namespace srb2::media
|
||||
|
||||
#endif // __SRB2_MEDIA_WEBM_WRITER_HPP__
|
||||
Loading…
Add table
Reference in a new issue