]> git.sesse.net Git - nageru/blob - nageru/cef_encoder_adapter.cpp
Add CEF support to Kaeru.
[nageru] / nageru / cef_encoder_adapter.cpp
1 #include "cef_encoder_adapter.h"
2
3 #include <bmusb/bmusb.h>
4 #include <chrono>
5 #include <mutex>
6 #include <optional>
7 #include <stddef.h>
8 #include <stdint.h>
9
10 extern "C" {
11 #include <libavutil/rational.h>
12 #include <libswscale/swscale.h>
13 }
14
15 #include "flags.h"
16 #include "quittable_sleeper.h"
17
18 using namespace bmusb;
19 using namespace std;
20 using namespace std::chrono;
21
22 class FFmpegCapture;
23
24 // In kaeru.cpp. (This is a bit of a hack, but in the interest of a cleaner split.)
25 void video_frame_callback(FFmpegCapture *video, X264Encoder *x264_encoder, AudioEncoder *audio_encoder,
26                           int64_t video_pts, AVRational video_timebase,
27                           int64_t audio_pts, AVRational audio_timebase,
28                           uint16_t timecode,
29                           FrameAllocator::Frame video_frame, size_t video_offset, VideoFormat video_format,
30                           FrameAllocator::Frame audio_frame, size_t audio_offset, AudioFormat audio_format);
31
32 void CEFEncoderAdapter::video_frame_callback(uint16_t timecode,
33                                              FrameAllocator::Frame video_frame, size_t video_offset, VideoFormat video_format,
34                                              FrameAllocator::Frame audio_frame, size_t audio_offset, AudioFormat audio_format)
35 {
36         lock_guard lock(mu);
37         if (first_frame) {
38                 start = steady_clock::now();
39                 first_frame = false;
40         }
41
42         last_video_format = video_format;
43         last_audio_format = audio_format;
44
45         steady_clock::time_point now = steady_clock::now();
46         int64_t video_pts = duration_cast<microseconds>(now - start).count();
47         AVRational video_timebase{ 1, 1000000 };
48
49         FrameAllocator::Frame nv12_video_frame;
50         if (video_frame.len > 0) {
51                 last_frame = now;
52
53                 // Do the actual conversion, replacing the frame.
54                 const uint8_t *src_pic_data[4] = { video_frame.data + video_offset, nullptr, nullptr, nullptr };
55                 int src_linesizes[4] = { global_flags.width * 4, 0, 0, 0 };
56
57                 uint8_t *pic_data[4] = { nv12_data.get(), nv12_data.get() + global_flags.width * global_flags.height, nullptr, nullptr };
58                 int linesizes[4] = { global_flags.width, global_flags.width, 0, 0 };
59
60                 sws_scale(sws_ctx.get(), src_pic_data, src_linesizes, 0, video_format.height, pic_data, linesizes);
61
62                 nv12_video_frame.data = nv12_data.get();
63                 nv12_video_frame.len = nv12_video_frame.size = global_flags.width * global_flags.height * 2;
64                 if (video_frame.owner) {
65                         video_frame.owner->release_frame(video_frame);
66                 }
67         } else {
68                 nv12_video_frame = video_frame;
69                 // Will be released by video_frame_callback(), so don't do that here.
70         }
71
72         // NOTE: We don't have CEF audio yet.
73
74         ::video_frame_callback(nullptr, x264_encoder, audio_encoder,
75                                video_pts, video_timebase,
76                                /*audio_pts=*/0, AVRational{ 1, 1 },
77                                timecode,
78                                nv12_video_frame, /*video_offset=*/0, video_format,
79                                audio_frame, audio_offset, audio_format);
80 }
81
82 // Enforce at least 15 fps by duplicating frames. This feels suboptimal
83 // on the face of it, but so many things in x264 (in particular lookahead
84 // and keyframe interval) work on the number of frames, so not having
85 // frames for a while will mess up the latency pretty badly, especially
86 // when clients do the initial join on the stream.
87 //
88 // Returns early (without any duplication) if should_quit becomes active.
89 void CEFEncoderAdapter::duplicate_frame_if_needed(QuittableSleeper *should_quit)
90 {
91         constexpr duration max_frame_interval = milliseconds(1000 / 15);
92
93         optional<steady_clock::time_point> last_frame_now = get_last_frame();
94         if (!last_frame_now) {
95                 // No initial frame yet, so nothing to duplicate. Just wait a bit.
96                 should_quit->sleep_for(max_frame_interval);
97                 return;
98         }
99         steady_clock::time_point next_inserted_frame = *last_frame_now + max_frame_interval;
100         if (!should_quit->sleep_until(next_inserted_frame)) {
101                 // Asked to quit.
102                 return;
103         }
104         lock_guard lock(mu);
105         if (*last_frame_now != last_frame) {
106                 // A new frame came while we were waiting, so we don't need one just yet.
107                 return;
108         }
109
110         // No new frame came before the deadline, so add a duplicate. The data from last
111         // conversion is still in the buffer, so we can just use that.
112         steady_clock::time_point now = steady_clock::now();
113         int64_t video_pts = duration_cast<microseconds>(now - start).count();
114
115         last_frame = now;
116         FrameAllocator::Frame nv12_video_frame;
117         nv12_video_frame.data = nv12_data.get();
118         nv12_video_frame.len = nv12_video_frame.size = global_flags.width * global_flags.height * 2;
119         ::video_frame_callback(nullptr, x264_encoder, audio_encoder,
120                                video_pts, video_timebase,
121                                /*audio_pts=*/0, AVRational{ 1, 1 },
122                                /*timecode=*/0,
123                                nv12_video_frame, /*video_offset=*/0, last_video_format,
124                                /*audio_frame=*/{}, /*audio_offset=*/0, last_audio_format);
125 }
126
127 optional<steady_clock::time_point> CEFEncoderAdapter::get_last_frame() const {
128         lock_guard lock(mu);
129         if (first_frame) {
130                 return nullopt;
131         } else {
132                 return last_frame;
133         }
134 }