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;
79
+ core::draw_frame frame;
80
+
81
+ explicit presentation_frame (core::draw_frame frame = {}, std::int_least64_t ts = now()) noexcept
82
+ : timestamp(ts)
83
+ , frame(std::move(frame))
84
+ {
85
+ }
86
+
87
+ presentation_frame (presentation_frame&& other) noexcept
88
+ : timestamp(other.timestamp)
89
+ , frame(std::move(other.frame))
90
+ {
91
+ }
92
+
93
+ presentation_frame (const presentation_frame&) = delete ;
94
+ presentation_frame& operator =(const presentation_frame&) = delete ;
95
+
96
+ presentation_frame& operator =(presentation_frame&& rhs) noexcept
97
+ {
98
+ timestamp = rhs.timestamp ;
99
+ frame = std::move (rhs.frame );
100
+ return *this ;
101
+ }
102
+
103
+ ~presentation_frame () {}
104
+ };
105
+
67
106
class html_client
68
107
: public CefClient
69
108
, public CefRenderHandler
109
+ , public CefAudioHandler
70
110
, public CefLifeSpanHandler
71
111
, public CefLoadHandler
72
112
, public CefDisplayHandler
@@ -80,16 +120,21 @@ class html_client
80
120
caspar::timer paint_timer_;
81
121
caspar::timer test_timer_;
82
122
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_;
92
-
123
+ spl::shared_ptr<core::frame_factory> frame_factory_;
124
+ core::video_format_desc format_desc_;
125
+ bool gpu_enabled_;
126
+ tbb::concurrent_queue<std::wstring> javascript_before_load_;
127
+ std::atomic<bool > loaded_;
128
+ std::queue<presentation_frame> frames_;
129
+ std::queue<presentation_frame> audio_frames_;
130
+ mutable std::mutex frames_mutex_;
131
+ mutable std::mutex audio_frames_mutex_;
132
+ const size_t frames_max_size_ = 4 ;
133
+ std::atomic<bool > closing_;
134
+
135
+ std::unique_ptr<ffmpeg::AudioResampler> audioResampler_;
136
+
137
+ core::draw_frame last_video_frame_;
93
138
core::draw_frame last_frame_;
94
139
std::int_least64_t last_frame_time_;
95
140
@@ -146,8 +191,21 @@ class html_client
146
191
147
192
bool try_pop (const core::video_field field)
148
193
{
194
+ bool result = false ;
149
195
std::lock_guard<std::mutex> lock (frames_mutex_);
150
196
197
+ core::draw_frame audio_frame;
198
+ uint64_t audio_frame_timestamp = 0 ;
199
+
200
+ {
201
+ std::lock_guard<std::mutex> audio_lock (audio_frames_mutex_);
202
+ if (!audio_frames_.empty ()) {
203
+ audio_frame_timestamp = audio_frames_.front ().timestamp ;
204
+ audio_frame = core::draw_frame (std::move (audio_frames_.front ().frame ));
205
+ audio_frames_.pop ();
206
+ }
207
+ }
208
+
151
209
if (!frames_.empty ()) {
152
210
/*
153
211
* CEF in gpu-enabled mode only sends frames when something changes, and interlaced channels
@@ -167,35 +225,43 @@ class html_client
167
225
168
226
// Check if the sole buffered frame is too young to have a partner field generated (with a tolerance)
169
227
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;
228
+ auto front_frame_is_too_young = (now_time - frames_.front ().timestamp ) < time_per_frame;
171
229
172
230
if (follows_gap_in_frames && front_frame_is_too_young) {
173
231
return false ;
174
232
}
175
233
}
176
234
177
- last_frame_time_ = frames_.front ().first ;
178
- last_frame_ = std::move (frames_.front ().second );
235
+ last_frame_time_ = frames_.front ().timestamp ;
236
+ last_video_frame_ = std::move (frames_.front ().frame );
237
+ last_frame_ = last_video_frame_;
179
238
frames_.pop ();
180
239
181
240
graph_->set_value (" buffered-frames" , (double )frames_.size () / frames_max_size_);
182
241
183
- return true ;
242
+ result = true ;
184
243
}
185
244
186
- return false ;
245
+ if (audio_frame) {
246
+ last_frame_time_ = audio_frame_timestamp;
247
+ last_frame_ = core::draw_frame::over (last_video_frame_, audio_frame);
248
+ result = true ;
249
+ }
250
+
251
+ return result;
187
252
}
188
253
189
254
core::draw_frame receive (const core::video_field field)
190
255
{
191
256
if (!try_pop (field)) {
192
257
graph_->set_tag (diagnostics::tag_severity::SILENT, " late-frame" );
258
+ return core::draw_frame::still (last_frame_);
259
+ } else {
260
+ return last_frame_;
193
261
}
194
-
195
- return last_frame_;
196
262
}
197
263
198
- core::draw_frame last_frame () const { return last_frame_; }
264
+ core::draw_frame last_frame () const { return core::draw_frame::still ( last_frame_) ; }
199
265
200
266
bool is_ready () const
201
267
{
@@ -245,13 +311,6 @@ class html_client
245
311
}
246
312
247
313
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
314
void GetViewRect (CefRefPtr<CefBrowser> browser, CefRect& rect) override
256
315
{
257
316
CASPAR_ASSERT (CefCurrentlyOn (TID_UI));
@@ -302,8 +361,10 @@ class html_client
302
361
{
303
362
std::lock_guard<std::mutex> lock (frames_mutex_);
304
363
305
- frames_.push (std::make_pair (now (), core::draw_frame (std::move (frame))));
306
- while (frames_.size () > 4 ) {
364
+ core::draw_frame new_frame = core::draw_frame (std::move (frame));
365
+
366
+ frames_.push (presentation_frame (std::move (new_frame)));
367
+ while (frames_.size () > frames_max_size_) {
307
368
frames_.pop ();
308
369
graph_->set_tag (diagnostics::tag_severity::WARNING, " dropped-frame" );
309
370
}
@@ -353,6 +414,8 @@ class html_client
353
414
354
415
CefRefPtr<CefRenderHandler> GetRenderHandler () override { return this ; }
355
416
417
+ CefRefPtr<CefAudioHandler> GetAudioHandler () override { return this ; }
418
+
356
419
CefRefPtr<CefLifeSpanHandler> GetLifeSpanHandler () override { return this ; }
357
420
358
421
CefRefPtr<CefLoadHandler> GetLoadHandler () override { return this ; }
@@ -378,7 +441,7 @@ class html_client
378
441
379
442
{
380
443
std::lock_guard<std::mutex> lock (frames_mutex_);
381
- frames_.push (std::make_pair ( now (), core::draw_frame::empty () ));
444
+ frames_.push (presentation_frame ( ));
382
445
}
383
446
384
447
{
@@ -399,6 +462,41 @@ class html_client
399
462
return false ;
400
463
}
401
464
465
+ bool GetAudioParameters (CefRefPtr<CefBrowser> browser, CefAudioParameters& params) override
466
+ {
467
+ params.channel_layout = CEF_CHANNEL_LAYOUT_7_1;
468
+ params.sample_rate = format_desc_.audio_sample_rate ;
469
+ params.frames_per_buffer = format_desc_.audio_cadence [0 ];
470
+ return format_desc_.audio_cadence .size () == 1 ; // TODO - handle 59.94
471
+ }
472
+
473
+ void OnAudioStreamStarted (CefRefPtr<CefBrowser> browser, const CefAudioParameters& params, int channels) override
474
+ {
475
+ audioResampler_ = std::make_unique<ffmpeg::AudioResampler>(params.sample_rate , AV_SAMPLE_FMT_FLTP);
476
+ }
477
+ void OnAudioStreamPacket (CefRefPtr<CefBrowser> browser, const float ** data, int samples, int64_t pts) override
478
+ {
479
+ if (!audioResampler_)
480
+ return ;
481
+
482
+ auto audio = audioResampler_->convert (samples, reinterpret_cast <const void **>(data));
483
+ auto audio_frame = core::mutable_frame (this , {}, std::move (audio), core::pixel_format_desc ());
484
+
485
+ {
486
+ std::lock_guard<std::mutex> lock (audio_frames_mutex_);
487
+ while (audio_frames_.size () >= frames_max_size_) {
488
+ audio_frames_.pop ();
489
+ }
490
+ audio_frames_.push (presentation_frame (core::draw_frame (std::move (audio_frame))));
491
+ }
492
+ }
493
+ void OnAudioStreamStopped (CefRefPtr<CefBrowser> browser) override { audioResampler_ = nullptr ; }
494
+ void OnAudioStreamError (CefRefPtr<CefBrowser> browser, const CefString& message) override
495
+ {
496
+ CASPAR_LOG (info) << " [html_producer] OnAudioStreamError: \" " << message.ToString () << " \" " ;
497
+ audioResampler_ = nullptr ;
498
+ }
499
+
402
500
void do_execute_javascript (const std::wstring& javascript)
403
501
{
404
502
html::begin_invoke ([=] {
0 commit comments