From: Daniel Karbach Date: Sun, 22 Sep 2024 20:54:57 +0000 (+0200) Subject: basic font rendering X-Git-Url: https://git.localhorst.tv/?a=commitdiff_plain;h=5aa8dcf66c32b13e03d4609c097967a80f9e2831;p=ffmpeg-test.git basic font rendering --- diff --git a/Makefile b/Makefile index 48f4d5c..9554743 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ CPP_SRCS = $(shell find src -name \*.cpp) CPP_DEPS = $(shell find src -name \*.h) -LIBS = cairo libavformat libavcodec libavutil libswresample libswscale +LIBS = cairo freetype2 libavformat libavcodec libavutil libswresample libswscale main: $(CPP_SRCS) $(CPP_DEPS) clang++ -g $(shell pkg-config --cflags --libs $(LIBS)) $(CPP_SRCS) -o $@ diff --git a/src/app/Renderer.h b/src/app/Renderer.h new file mode 100644 index 0000000..41915a7 --- /dev/null +++ b/src/app/Renderer.h @@ -0,0 +1,64 @@ +#ifndef TEST_APP_RENDERER_H_ +#define TEST_APP_RENDERER_H_ + +#include +extern "C" { +#include "cairo.h" +} + +#include "../freetype/Face.h" +#include "../freetype/Library.h" +#include "../cairo/Context.h" +#include "../cairo/Face.h" +#include "../cairo/Surface.h" + +namespace app { + +class Renderer { + +public: + Renderer(uint8_t *plane, int linesize, int width, int height) + : ft() + , face(ft.NewFace("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 0)) + , font(face) + , surface(plane, linesize, CAIRO_FORMAT_ARGB32, width, height) + , ctx(surface.CreateContext()) + , width(width) + , height(height) { + } + ~Renderer() { + } + + Renderer(const Renderer &) = delete; + Renderer &operator =(const Renderer &) = delete; + +public: + void RenderVideoFrame(int64_t num, int64_t time_ms) { + ctx.SetSourceRGB(0, 0, 0); + ctx.Paint(); + + ctx.MoveTo(50, 50); + ctx.SetFontFace(font); + ctx.SetFontSize(16); + ctx.SetSourceRGB(1, 1, 1); + ctx.ShowText("Hello"); + + surface.Flush(); + } + + +private: + freetype::Library ft; + freetype::Face face; + cairo::Face font; + cairo::Surface surface; + cairo::Context ctx; + + int width; + int height; + +}; + +} + +#endif diff --git a/src/app/Stream.h b/src/app/Stream.h index 5da2a54..7ad9fde 100644 --- a/src/app/Stream.h +++ b/src/app/Stream.h @@ -13,6 +13,7 @@ extern "C" { } #include "../ffmpeg/Encoder.h" +#include "../ffmpeg/Network.h" #include "../ffmpeg/OutputContext.h" #include "../ffmpeg/Resampler.h" #include "../ffmpeg/Scaler.h" @@ -157,6 +158,7 @@ public: } private: + ffmpeg::Network net; ffmpeg::OutputContext output; ffmpeg::Encoder audio_encoder; ffmpeg::Encoder video_encoder; diff --git a/src/cairo/Context.h b/src/cairo/Context.h index 92cd24a..a652555 100644 --- a/src/cairo/Context.h +++ b/src/cairo/Context.h @@ -6,6 +6,7 @@ #include #include "Error.h" +#include "Face.h" namespace cairo { @@ -61,6 +62,10 @@ public: cairo_select_font_face(ctx, family, slant, weight); } + void SetFontFace(Face &face) { + cairo_set_font_face(ctx, face.GetFace()); + } + void SetFontSize(double size) { cairo_set_font_size(ctx, size); } diff --git a/src/cairo/Error.h b/src/cairo/Error.h index a0b869b..30e910e 100644 --- a/src/cairo/Error.h +++ b/src/cairo/Error.h @@ -7,7 +7,7 @@ #include namespace { -std::string make_message(const std::string &msg, cairo_status_t status) { +std::string cairo_make_message(const std::string &msg, cairo_status_t status) { return msg + ": " + std::string(cairo_status_to_string(status)); } } @@ -17,7 +17,7 @@ namespace cairo { class Error: public std::runtime_error { public: - Error(const std::string &msg, cairo_status_t status): std::runtime_error(make_message(msg, status)) { + Error(const std::string &msg, cairo_status_t status): std::runtime_error(cairo_make_message(msg, status)) { } ~Error() { } diff --git a/src/cairo/Face.h b/src/cairo/Face.h new file mode 100644 index 0000000..17551b6 --- /dev/null +++ b/src/cairo/Face.h @@ -0,0 +1,44 @@ +#ifndef TEST_CAIRO_FACE_H_ +#define TEST_CAIRO_FACE_H_ + +extern "C" { +#include +#include +} + +#include "../freetype/Face.h" + +namespace cairo { + +class Face { + +public: + explicit Face(const freetype::Face &face) + : font(cairo_ft_font_face_create_for_ft_face(face.GetFace(), 0)) + , face(face) { + } + ~Face() { + cairo_font_face_destroy(font); + } + + Face(const Face &) = delete; + Face &operator =(const Face &) = delete; + +public: + cairo_font_face_t *GetFace() { + return font; + } + + const cairo_font_face_t *GetFace() const { + return font; + } + +private: + cairo_font_face_t *font; + freetype::Face face; + +}; + +} + +#endif diff --git a/src/ffmpeg/Error.h b/src/ffmpeg/Error.h index bdcbdce..f768f72 100644 --- a/src/ffmpeg/Error.h +++ b/src/ffmpeg/Error.h @@ -9,7 +9,7 @@ extern "C" { } namespace { -std::string make_message(const std::string &msg, int code) { +std::string ffmpeg_make_message(const std::string &msg, int code) { char buf[128] = {0}; av_strerror(code, buf, sizeof(buf)); return msg + ": " + std::string(buf); @@ -21,7 +21,7 @@ namespace ffmpeg { class Error: public std::runtime_error { public: - Error(const std::string &msg, int code): std::runtime_error(make_message(msg, code)) { + Error(const std::string &msg, int code): std::runtime_error(ffmpeg_make_message(msg, code)) { } ~Error() { } diff --git a/src/ffmpeg/Network.h b/src/ffmpeg/Network.h new file mode 100644 index 0000000..5a29396 --- /dev/null +++ b/src/ffmpeg/Network.h @@ -0,0 +1,32 @@ +#ifndef TEST_FFMPEG_NETWORK_H_ +#define TEST_FFMPEG_NETWORK_H_ + +extern "C" { +#include +} + +#include "Error.h" + +namespace ffmpeg { + +class Network { + +public: + Network() { + int res = avformat_network_init(); + if (res != 0) { + throw Error("failed to initialize network", res); + } + } + ~Network() { + avformat_network_deinit(); + } + + Network(const Network &) = delete; + Network &operator =(const Network &) = delete; + +}; + +} + +#endif diff --git a/src/freetype/Error.h b/src/freetype/Error.h new file mode 100644 index 0000000..e8f7bac --- /dev/null +++ b/src/freetype/Error.h @@ -0,0 +1,33 @@ +#ifndef TEST_FREETYPE_ERROR_H_ +#define TEST_FREETYPE_ERROR_H_ + +#include +#include + +extern "C" { +#include +#include FT_FREETYPE_H +} + + +namespace { +std::string freetype_make_message(const std::string &msg, FT_Error status) { + return msg + ": " + std::string(FT_Error_String(status)); +} +} + +namespace freetype { + +class Error: public std::runtime_error { + +public: + Error(const std::string &msg, FT_Error status): std::runtime_error(freetype_make_message(msg, status)) { + } + ~Error() { + } + +}; + +} + +#endif diff --git a/src/freetype/Face.h b/src/freetype/Face.h new file mode 100644 index 0000000..064e4fb --- /dev/null +++ b/src/freetype/Face.h @@ -0,0 +1,47 @@ +#ifndef TEST_FREETYPE_FACE_H_ +#define TEST_FREETYPE_FACE_H_ + +extern "C" { +#include +#include FT_FREETYPE_H +} + +#include "Error.h" + +namespace freetype { + +class Face { + +public: + Face(FT_Library lib, const char *path, FT_Long index) { + FT_Error err = FT_New_Face(lib, path, index, &face); + if (err) { + throw Error("failed to open face", err); + } + } + ~Face() { + FT_Done_Face(face); + } + Face(const Face &other): face(other.face) { + FT_Reference_Face(face); + } + Face &operator =(const Face &other) { + FT_Reference_Face(other.face); + FT_Done_Face(face); + face = other.face; + return *this; + } + +public: + const FT_Face &GetFace() const { + return face; + } + +private: + FT_Face face; + +}; + +} + +#endif diff --git a/src/freetype/Library.h b/src/freetype/Library.h new file mode 100644 index 0000000..8c46be1 --- /dev/null +++ b/src/freetype/Library.h @@ -0,0 +1,42 @@ +#ifndef TEST_FREETYPE_LIBRARY_H_ +#define TEST_FREETYPE_LIBRARY_H_ + +extern "C" { +#include +#include FT_FREETYPE_H +} + +#include "Error.h" +#include "Face.h" + +namespace freetype { + +class Library { + +public: + Library() { + FT_Error err = FT_Init_FreeType(&lib); + if (err) { + throw Error("failed to initialize library", err); + } + } + ~Library() { + FT_Done_FreeType(lib); + } + + Library(const Library &) = delete; + Library &operator =(const Library &) = delete; + +public: + Face NewFace(const char *path, FT_Long index) { + return Face(lib, path, index); + } + +private: + FT_Library lib; + +}; + +} + +#endif diff --git a/src/main.cpp b/src/main.cpp index a77f4d0..a514c09 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,10 +1,7 @@ -#include "cairo.h" -#include "cairo/Context.h" #include #include #include #include -#include #include extern "C" { @@ -19,12 +16,9 @@ extern "C" { #include } +#include "app/Renderer.h" #include "app/Stream.h" -#include "cairo/Surface.h" - -#include "ffmpeg/Error.h" - namespace { @@ -34,11 +28,6 @@ void stop(int) { running = false; } -void RenderFrame(int64_t num, int64_t time_ms, cairo::Context &c) { - c.SetSourceRGB(0, 0, 0); - c.Paint(); -} - } @@ -48,11 +37,6 @@ int main(int argc, char**argv) { const int FPS = 60; const char *URL = "rtmp://localhost/localhorsttv"; - int res = avformat_network_init(); - if (res != 0) { - throw ffmpeg::Error("network init failed", res); - } - app::Stream stream(URL, WIDTH, HEIGHT, FPS); running = true; @@ -64,8 +48,7 @@ int main(int argc, char**argv) { int16_t *audio_plane = stream.GetAudioPlane(); const int audio_channels = stream.GetAudioChannels(); - cairo::Surface surface(plane, linesize, CAIRO_FORMAT_ARGB32, WIDTH, HEIGHT); - cairo::Context context(surface.CreateContext()); + app::Renderer renderer(plane, linesize, WIDTH, HEIGHT); stream.Start(); @@ -81,8 +64,7 @@ int main(int argc, char**argv) { if (stream.GetVideoFrameCounter() > 0 && difference < 0) { std::cout << (difference / 1000.0) << "s behind schedule, dropping frame" << std::endl; } else { - RenderFrame(stream.GetVideoFrameCounter(), target, context); - surface.Flush(); + renderer.RenderVideoFrame(stream.GetVideoFrameCounter(), target); } stream.PushVideoFrame();