]> git.localhorst.tv Git - ffmpeg-test.git/commitdiff
animation
authorDaniel Karbach <daniel.karbach@localhorst.tv>
Wed, 2 Oct 2024 15:15:28 +0000 (17:15 +0200)
committerDaniel Karbach <daniel.karbach@localhorst.tv>
Wed, 2 Oct 2024 15:15:28 +0000 (17:15 +0200)
src/app/Clock.h [new file with mode: 0644]
src/app/Message.h
src/app/Mixer.h [new file with mode: 0644]
src/app/Renderer.h
src/app/Stream.h
src/main.cpp

diff --git a/src/app/Clock.h b/src/app/Clock.h
new file mode 100644 (file)
index 0000000..81b092f
--- /dev/null
@@ -0,0 +1,73 @@
+#ifndef TEST_APP_CLOCK_H_
+#define TEST_APP_CLOCK_H_
+
+extern "C" {
+#include <libavutil/mathematics.h>
+#include <libavutil/rational.h>
+}
+
+namespace app {
+
+class Clock {
+
+public:
+       Clock()
+       : timebase{1, 1000}, counter(0) {
+       }
+       explicit Clock(AVRational timebase)
+       : timebase(timebase), counter(0) {
+       }
+
+public:
+       void Advance() {
+               ++counter;
+       }
+
+       void Advance(int n) {
+               counter += n;
+       }
+
+       Clock Difference(const Clock &other) const {
+               Clock result(*this);
+               int64_t rescaled = av_rescale_q(other.counter, other.timebase, timebase);
+               result.counter -= rescaled;
+               return result;
+       }
+
+       double Interpolate(double from, double to, int64_t from_ms, int64_t to_ms) const {
+               double t = double(GetMS() - from_ms) / double(to_ms - from_ms);
+               return from + t * (to - from);
+       }
+
+       double InterpolateClamp(double from, double to, int64_t from_ms, int64_t to_ms) const {
+               int64_t ms = GetMS();
+               if (ms < from_ms) return from;
+               if (ms > to_ms) return to;
+               return Interpolate(from, to, from_ms, to_ms);
+       }
+
+       void Reset() {
+               counter = 0;
+       }
+
+       Clock Snapshot() const {
+               return *this;
+       }
+
+       int64_t GetCounter() const {
+               return counter;
+       }
+
+       int64_t GetMS() const {
+               return av_rescale_q(counter, timebase, AVRational{1, 1000});
+       }
+
+private:
+       AVRational timebase;
+       int64_t counter;
+
+};
+
+}
+
+#endif
index 30fbc81d965640a3c5914d79dd293974df4785d2..a84d8a7c4de4fa9ad0b7855798d59710b0e85db8 100644 (file)
@@ -3,6 +3,7 @@
 
 #include <string>
 
+#include "Clock.h"
 #include "../cairo/Context.h"
 #include "../gfx/ColorRGB.h"
 #include "../gfx/Position.h"
@@ -46,6 +47,14 @@ public:
                channel_layout.SetWidth(inner_width);
        }
 
+       void SetBirth(const Clock &clock) {
+               birth = clock;
+       }
+
+       const Clock &GetBirth() const {
+               return birth;
+       }
+
        void SetText(const std::string &t) {
                text_layout.SetText(t);
        }
@@ -58,6 +67,10 @@ public:
                pos = p;
        }
 
+       const gfx::Position &GetPosition() const {
+               return pos;
+       }
+
        double GetHeight() const {
                return size.h;
        }
@@ -93,6 +106,7 @@ private:
        gfx::ColorRGB text_color;
        gfx::ColorRGB channel_color;
 
+       Clock birth;
        gfx::Position pos;
        gfx::Size size;
        gfx::Spacing padding;
diff --git a/src/app/Mixer.h b/src/app/Mixer.h
new file mode 100644 (file)
index 0000000..dbe0508
--- /dev/null
@@ -0,0 +1,41 @@
+#ifndef TEST_APP_MIXER_H_
+#define TEST_APP_MIXER_H_
+
+#include <cstdint>
+
+#include "Clock.h"
+
+namespace app {
+
+class Mixer {
+
+public:
+       Mixer(const Clock *clock, int16_t *plane, int channels, int frame_size)
+       : clock(clock), plane(plane), channels(channels), frame_size(frame_size) {
+       }
+       ~Mixer() {
+       }
+
+       Mixer(const Mixer &) = delete;
+       Mixer &operator =(const Mixer &) = delete;
+
+public:
+       void RenderAudioFrame() {
+               for (int i = 0; i < frame_size; ++i) {
+                       for (int j = 0; j < channels; ++j) {
+                               plane[i * channels + j] = 0;
+                       }
+               }
+       }
+
+private:
+       const Clock *clock;
+       int16_t *plane;
+       int channels;
+       int frame_size;
+
+};
+
+}
+
+#endif
index a74c7c8d02b229319e05338c4dd3775f8f800747..b7b2e7e370d786f06a712730ab1513759e7fc442 100644 (file)
@@ -8,6 +8,7 @@ extern "C" {
 #include "cairo.h"
 }
 
+#include "Clock.h"
 #include "Message.h"
 #include "../cairo/Context.h"
 #include "../cairo/Surface.h"
@@ -17,8 +18,9 @@ namespace app {
 class Renderer {
 
 public:
-       Renderer(uint8_t *plane, int linesize, int width, int height)
-       : text_font("DejaVu Sans 32px")
+       Renderer(const Clock *clock, uint8_t *plane, int linesize, int width, int height)
+       : clock(clock)
+       , text_font("DejaVu Sans 32px")
        , channel_font("DejaVu Sans 24px")
        , surface(plane, linesize, CAIRO_FORMAT_ARGB32, width, height)
        , ctx(surface.CreateContext())
@@ -33,7 +35,21 @@ public:
        Renderer &operator =(const Renderer &) = delete;
 
 public:
-       void RenderVideoFrame(int64_t num, int64_t time_ms) {
+       void Update() {
+               gfx::Position pos({ 50, 50 });
+               for (Message &msg : msgs) {
+                       double distance = msg.GetHeight() + 10.0;
+                       Clock lifetime = clock->Difference(msg.GetBirth());
+                       pos.y -= lifetime.InterpolateClamp(distance + 50, 0.0, 0, 600);
+                       msg.SetPosition(pos);
+                       pos.y = std::max(pos.y + distance, 50.0);
+               }
+               if (msgs.size() > 1 && msgs.back().GetPosition().y > height) {
+                       msgs.pop_back();
+               }
+       }
+
+       void RenderVideoFrame() {
                ctx.SetSourceRGB(0, 0, 0);
                ctx.Paint();
 
@@ -52,18 +68,12 @@ public:
                msg.SetWidth(width / 2.0);
                msg.SetText(text);
                msg.SetChannel(channel);
+               msg.SetBirth(clock->Snapshot());
                msg.Update();
-               if (msgs.size() > 3) {
-                       msgs.pop_back();
-               }
-               gfx::Position pos({ 50, 50 });
-               for (Message &msg : msgs) {
-                       msg.SetPosition(pos);
-                       pos.y += msg.GetHeight() + 10.0;
-               }
        }
 
 private:
+       const Clock *clock;
        pango::Font text_font;
        pango::Font channel_font;
        cairo::Surface surface;
index 7ad9fde73676d69e0a03b6144e432e932335ad35..4f53425f671c38690ad47e41478db09056a2394b 100644 (file)
@@ -12,6 +12,7 @@ extern "C" {
 #include <libavutil/samplefmt.h>
 }
 
+#include "Clock.h"
 #include "../ffmpeg/Encoder.h"
 #include "../ffmpeg/Network.h"
 #include "../ffmpeg/OutputContext.h"
@@ -34,6 +35,7 @@ public:
                audio_encoder.InferSampleFormat();
                audio_encoder.SetBitRate(160 * 1000);
                audio_encoder.Open();
+               audio_clock = Clock(audio_encoder.GetTimeBase());
 
                video_encoder.SetSize(width, height);
                video_encoder.InferPixelFormat();
@@ -46,6 +48,7 @@ public:
                        video_encoder.SetGlobalHeader();
                }
                video_encoder.Open();
+               video_clock = Clock(video_encoder.GetTimeBase());
 
                video_stream = output.CreateVideoStream(video_encoder);
                audio_stream = output.CreateAudioStream(audio_encoder);
@@ -85,8 +88,8 @@ public:
                output.Open();
                output.WriteHeader();
                start = std::chrono::high_resolution_clock::now();
-               video_frame_counter = 0;
-               audio_frame_counter = 0;
+               video_clock.Reset();
+               audio_clock.Reset();
        }
 
        void Finish() {
@@ -107,12 +110,12 @@ public:
                output.WriteTrailer();
        }
 
-       int64_t GetVideoFrameCounter() const {
-               return video_frame_counter;
+       const Clock &GetAudioClock() const {
+               return audio_clock;
        }
 
-       int64_t AudioElapsedMS() const {
-               return av_rescale_q(audio_frame_counter, audio_encoder.GetTimeBase(), AVRational{1, 1000});
+       const Clock &GetVideoClock() const {
+               return video_clock;
        }
 
        int64_t TimeElapsedMS() const {
@@ -120,24 +123,20 @@ public:
                return std::chrono::duration_cast<std::chrono::milliseconds>(now - start).count();
        }
 
-       int64_t VideoElapsedMS() const {
-               return av_rescale_q(video_frame_counter, video_encoder.GetTimeBase(), AVRational{1, 1000});
-       }
-
        void PrepareAudioFrame() {
                audio_input_frame.MakeWritable();
        }
 
        void PushAudioFrame() {
                resampler.Convert(audio_encoder, audio_input_frame, audio_output_frame);
-               audio_output_frame.SetPresentationTimestamp(audio_frame_counter);
+               audio_output_frame.SetPresentationTimestamp(audio_clock.GetCounter());
                audio_encoder.SendFrame(audio_output_frame);
                while (audio_encoder.ReceivePacket(audio_packet)) {
                        audio_packet.Rescale(audio_encoder, audio_stream);
                        audio_packet.SetStream(audio_stream);
                        output.WriteInterleavedPacket(audio_packet);
                }
-               audio_frame_counter += audio_encoder.GetFrameSize();
+               audio_clock.Advance(audio_encoder.GetFrameSize());
        }
 
        void PrepareVideoFrame() {
@@ -147,9 +146,9 @@ public:
 
        void PushVideoFrame() {
                scaler.ScaleFrame(video_input_frame, video_output_frame);
-               video_output_frame.SetPresentationTimestamp(video_frame_counter);
+               video_output_frame.SetPresentationTimestamp(video_clock.GetCounter());
                video_encoder.SendFrame(video_output_frame);
-               ++video_frame_counter;
+               video_clock.Advance();
                while (video_encoder.ReceivePacket(video_packet)) {
                        video_packet.Rescale(video_encoder, video_stream);
                        video_packet.SetStream(video_stream);
@@ -174,8 +173,8 @@ private:
        ffmpeg::Packet audio_packet;
 
        std::chrono::time_point<std::chrono::system_clock> start;
-       int64_t video_frame_counter;
-       int64_t audio_frame_counter;
+       Clock audio_clock;
+       Clock video_clock;
 
 };
 
index 862628577d6a55601caff3450cc61ab2ebe3814a..b06aa407cd70eac2b5c98b773da3468224e12c42 100644 (file)
@@ -17,6 +17,7 @@ extern "C" {
 #include <libavutil/timestamp.h>
 }
 
+#include "app/Mixer.h"
 #include "app/Renderer.h"
 #include "app/Stream.h"
 #include "uv/Loop.h"
@@ -70,8 +71,10 @@ int main(int argc, char**argv) {
 
        int16_t *audio_plane = stream.GetAudioPlane();
        const int audio_channels = stream.GetAudioChannels();
+       const int audio_frame_size = stream.GetAudioFrameSize();
 
-       app::Renderer renderer(plane, linesize, WIDTH, HEIGHT);
+       app::Renderer renderer(&stream.GetVideoClock(), plane, linesize, WIDTH, HEIGHT);
+       app::Mixer mixer(&stream.GetAudioClock(), audio_plane, audio_channels, audio_frame_size);
 
        wsconn.Subscribe("ChatBotLog", &ws_handler, &renderer);
 
@@ -82,27 +85,24 @@ int main(int argc, char**argv) {
        while (running) {
                loop.TryStep();
 
-               const int64_t target = stream.VideoElapsedMS();
+               const int64_t target = stream.GetVideoClock().GetMS();
                const int64_t elapsed = stream.TimeElapsedMS();
                const int64_t difference = target - elapsed;
 
                stream.PrepareVideoFrame();
 
-               if (stream.GetVideoFrameCounter() > 0 && difference < 0) {
+               if (target > 0 && difference < 0) {
                        std::cout << (difference / -1000.0) << "s behind schedule, dropping frame" << std::endl;
                } else {
-                       renderer.RenderVideoFrame(stream.GetVideoFrameCounter(), target);
+                       renderer.Update();
+                       renderer.RenderVideoFrame();
                }
 
                stream.PushVideoFrame();
 
-               while (stream.AudioElapsedMS() < target) {
+               while (stream.GetAudioClock().GetMS() < target) {
                        stream.PrepareAudioFrame();
-                       for (int i = 0; i < stream.GetAudioFrameSize(); ++i) {
-                               for (int j = 0; j < audio_channels; ++j) {
-                                       audio_plane[i * audio_channels + j] = 0;
-                               }
-                       }
+                       mixer.RenderAudioFrame();
                        stream.PushAudioFrame();
                }