60
60
#include < queue>
61
61
#include < utility>
62
62
63
+ #include < ffmpeg/util/audio_resampler.h>
64
+
63
65
#include " ../html.h"
64
66
65
67
namespace caspar { namespace html {
66
68
69
+ inline std::int_least64_t now ()
70
+ {
71
+ return std::chrono::duration_cast<std::chrono::milliseconds>(
72
+ std::chrono::high_resolution_clock::now ().time_since_epoch ())
73
+ .count ();
74
+ }
75
+
76
+ struct presentation_frame
77
+ {
78
+ std::int_least64_t timestamp = now();
79
+ core::draw_frame frame = core::draw_frame::empty();
80
+ bool has_video = false ;
81
+ bool has_audio = false ;
82
+
83
+ explicit presentation_frame (core::draw_frame video = {})
84
+ {
85
+ if (video) {
86
+ frame = std::move (video);
87
+ has_video = true ;
88
+ }
89
+ }
90
+
91
+ presentation_frame (presentation_frame&& other) noexcept
92
+ : timestamp(other.timestamp)
93
+ , frame(std::move(other.frame))
94
+ {
95
+ }
96
+
97
+ presentation_frame (const presentation_frame&) = delete;
98
+ presentation_frame& operator =(const presentation_frame&) = delete ;
99
+
100
+ presentation_frame& operator =(presentation_frame&& rhs)
101
+ {
102
+ timestamp = rhs.timestamp ;
103
+ frame = std::move (rhs.frame );
104
+ return *this ;
105
+ }
106
+
107
+ ~presentation_frame () {}
108
+
109
+ void add_audio (core::mutable_frame audio)
110
+ {
111
+ if (has_audio)
112
+ return ;
113
+ has_audio = true ;
114
+
115
+ if (frame) {
116
+ frame = core::draw_frame::over (frame, core::draw_frame (std::move (audio)));
117
+ } else {
118
+ frame = core::draw_frame (std::move (audio));
119
+ }
120
+ }
121
+
122
+ void add_video (core::draw_frame video)
123
+ {
124
+ if (has_video)
125
+ return ;
126
+ has_video = true ;
127
+
128
+ if (frame) {
129
+ frame = core::draw_frame::over (frame, std::move (video));
130
+ } else {
131
+ frame = std::move (video);
132
+ }
133
+ }
134
+ };
135
+
67
136
class html_client
68
137
: public CefClient
69
138
, public CefRenderHandler
139
+ , public CefAudioHandler
70
140
, public CefLifeSpanHandler
71
141
, public CefLoadHandler
72
142
, public CefDisplayHandler
@@ -80,15 +150,18 @@ class html_client
80
150
caspar::timer paint_timer_;
81
151
caspar::timer test_timer_;
82
152
83
- spl::shared_ptr<core::frame_factory> frame_factory_;
84
- core::video_format_desc format_desc_;
85
- bool gpu_enabled_;
86
- tbb::concurrent_queue<std::wstring> javascript_before_load_;
87
- std::atomic<bool > loaded_;
88
- std::queue<std::pair<std::int_least64_t , core::draw_frame>> frames_;
89
- mutable std::mutex frames_mutex_;
90
- const size_t frames_max_size_ = 4 ;
91
- std::atomic<bool > closing_;
153
+ spl::shared_ptr<core::frame_factory> frame_factory_;
154
+ core::video_format_desc format_desc_;
155
+ bool gpu_enabled_;
156
+ tbb::concurrent_queue<std::wstring> javascript_before_load_;
157
+ std::atomic<bool > loaded_;
158
+ std::queue<presentation_frame> frames_;
159
+ core::draw_frame last_generated_frame_;
160
+ mutable std::mutex frames_mutex_;
161
+ const size_t frames_max_size_ = 4 ;
162
+ std::atomic<bool > closing_;
163
+
164
+ std::unique_ptr<ffmpeg::AudioResampler> audioResampler_;
92
165
93
166
core::draw_frame last_frame_;
94
167
std::int_least64_t last_frame_time_;
@@ -167,15 +240,15 @@ class html_client
167
240
168
241
// Check if the sole buffered frame is too young to have a partner field generated (with a tolerance)
169
242
auto time_per_frame = (1000 * 1.5 ) / format_desc_.fps ;
170
- auto front_frame_is_too_young = (now_time - frames_.front ().first ) < time_per_frame;
243
+ auto front_frame_is_too_young = (now_time - frames_.front ().timestamp ) < time_per_frame;
171
244
172
245
if (follows_gap_in_frames && front_frame_is_too_young) {
173
246
return false ;
174
247
}
175
248
}
176
249
177
- last_frame_time_ = frames_.front ().first ;
178
- last_frame_ = std::move (frames_.front ().second );
250
+ last_frame_time_ = frames_.front ().timestamp ;
251
+ last_frame_ = std::move (frames_.front ().frame );
179
252
frames_.pop ();
180
253
181
254
graph_->set_value (" buffered-frames" , (double )frames_.size () / frames_max_size_);
@@ -190,12 +263,13 @@ class html_client
190
263
{
191
264
if (!try_pop (field)) {
192
265
graph_->set_tag (diagnostics::tag_severity::SILENT, " late-frame" );
266
+ return core::draw_frame::still (last_frame_);
267
+ } else {
268
+ return last_frame_;
193
269
}
194
-
195
- return last_frame_;
196
270
}
197
271
198
- core::draw_frame last_frame () const { return last_frame_; }
272
+ core::draw_frame last_frame () const { return core::draw_frame::still ( last_frame_) ; }
199
273
200
274
bool is_ready () const
201
275
{
@@ -245,13 +319,6 @@ class html_client
245
319
}
246
320
247
321
private:
248
- std::int_least64_t now ()
249
- {
250
- return std::chrono::duration_cast<std::chrono::milliseconds>(
251
- std::chrono::high_resolution_clock::now ().time_since_epoch ())
252
- .count ();
253
- }
254
-
255
322
void GetViewRect (CefRefPtr<CefBrowser> browser, CefRect& rect) override
256
323
{
257
324
CASPAR_ASSERT (CefCurrentlyOn (TID_UI));
@@ -302,7 +369,10 @@ class html_client
302
369
{
303
370
std::lock_guard<std::mutex> lock (frames_mutex_);
304
371
305
- frames_.push (std::make_pair (now (), core::draw_frame (std::move (frame))));
372
+ core::draw_frame new_frame = core::draw_frame (std::move (frame));
373
+ last_generated_frame_ = new_frame;
374
+
375
+ frames_.push (presentation_frame (std::move (new_frame)));
306
376
while (frames_.size () > 4 ) {
307
377
frames_.pop ();
308
378
graph_->set_tag (diagnostics::tag_severity::WARNING, " dropped-frame" );
@@ -353,6 +423,8 @@ class html_client
353
423
354
424
CefRefPtr<CefRenderHandler> GetRenderHandler () override { return this ; }
355
425
426
+ CefRefPtr<CefAudioHandler> GetAudioHandler () override { return this ; }
427
+
356
428
CefRefPtr<CefLifeSpanHandler> GetLifeSpanHandler () override { return this ; }
357
429
358
430
CefRefPtr<CefLoadHandler> GetLoadHandler () override { return this ; }
@@ -378,7 +450,7 @@ class html_client
378
450
379
451
{
380
452
std::lock_guard<std::mutex> lock (frames_mutex_);
381
- frames_.push (std::make_pair ( now (), core::draw_frame::empty () ));
453
+ frames_.push (presentation_frame ( ));
382
454
}
383
455
384
456
{
@@ -399,6 +471,51 @@ class html_client
399
471
return false ;
400
472
}
401
473
474
+ bool GetAudioParameters (CefRefPtr<CefBrowser> browser, CefAudioParameters& params) override
475
+ {
476
+ params.channel_layout = CEF_CHANNEL_LAYOUT_7_1;
477
+ params.sample_rate = format_desc_.audio_sample_rate ;
478
+ params.frames_per_buffer = format_desc_.audio_cadence [0 ];
479
+ return format_desc_.audio_cadence .size () == 1 ; // TODO - handle 59.94
480
+ }
481
+
482
+ void OnAudioStreamStarted (CefRefPtr<CefBrowser> browser, const CefAudioParameters& params, int channels) override
483
+ {
484
+ audioResampler_ = std::make_unique<ffmpeg::AudioResampler>(params.sample_rate , AV_SAMPLE_FMT_FLTP);
485
+ }
486
+ void OnAudioStreamPacket (CefRefPtr<CefBrowser> browser, const float ** data, int samples, int64_t pts) override
487
+ {
488
+ if (!audioResampler_)
489
+ return ;
490
+
491
+ auto audio = audioResampler_->convert (samples, reinterpret_cast <const void **>(data));
492
+ auto audio_frame = core::mutable_frame (this , {}, std::move (audio), core::pixel_format_desc ());
493
+
494
+ {
495
+ std::lock_guard<std::mutex> lock (frames_mutex_);
496
+ if (frames_.empty ()) {
497
+ presentation_frame wrapped_frame (last_generated_frame_);
498
+ wrapped_frame.add_audio (std::move (audio_frame));
499
+
500
+ frames_.push (std::move (wrapped_frame));
501
+ } else {
502
+ if (!frames_.back ().has_audio ) {
503
+ frames_.back ().add_audio (std::move (audio_frame));
504
+ } else {
505
+ presentation_frame wrapped_frame (last_generated_frame_);
506
+ wrapped_frame.add_audio (std::move (audio_frame));
507
+ frames_.push (std::move (wrapped_frame));
508
+ }
509
+ }
510
+ }
511
+ }
512
+ void OnAudioStreamStopped (CefRefPtr<CefBrowser> browser) override { audioResampler_ = nullptr ; }
513
+ void OnAudioStreamError (CefRefPtr<CefBrowser> browser, const CefString& message) override
514
+ {
515
+ CASPAR_LOG (info) << " [html_producer] OnAudioStreamError: \" " << message.ToString () << " \" " ;
516
+ audioResampler_ = nullptr ;
517
+ }
518
+
402
519
void do_execute_javascript (const std::wstring& javascript)
403
520
{
404
521
html::begin_invoke ([=] {
0 commit comments