61
61
#include < queue>
62
62
#include < utility>
63
63
64
+ #include < ffmpeg/util/audio_resampler.h>
65
+
64
66
#include " ../html.h"
65
67
66
68
namespace caspar { namespace html {
67
69
70
+ inline std::int_least64_t now ()
71
+ {
72
+ return std::chrono::duration_cast<std::chrono::milliseconds>(
73
+ std::chrono::high_resolution_clock::now ().time_since_epoch ())
74
+ .count ();
75
+ }
76
+
77
+ struct presentation_frame
78
+ {
79
+ std::int_least64_t timestamp = now();
80
+ core::draw_frame frame = core::draw_frame::empty();
81
+ bool has_video = false ;
82
+ bool has_audio = false ;
83
+
84
+ explicit presentation_frame ()
85
+ {
86
+ }
87
+
88
+ presentation_frame (presentation_frame&& other)
89
+ noexcept : timestamp(other.timestamp),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)
97
+ {
98
+ timestamp = rhs.timestamp ;
99
+ frame = std::move (rhs.frame );
100
+ return *this ;
101
+ }
102
+
103
+ ~presentation_frame () {}
104
+
105
+ void add_audio (core::mutable_frame audio) {
106
+ if (has_audio) return ;
107
+ has_audio = true ;
108
+
109
+ if (frame) {
110
+ frame = core::draw_frame::over (frame, core::draw_frame (std::move (audio)));
111
+ } else {
112
+ frame = core::draw_frame (std::move (audio));
113
+ }
114
+ }
115
+
116
+ void add_video (core::draw_frame video) {
117
+ if (has_video) return ;
118
+ has_video = true ;
119
+
120
+ if (frame) {
121
+ frame = core::draw_frame::over (frame, std::move (video));
122
+ } else {
123
+ frame = std::move (video);
124
+ }
125
+ }
126
+ };
127
+
128
+
68
129
class html_client
69
130
: public CefClient
70
131
, public CefRenderHandler
132
+ , public CefAudioHandler
71
133
, public CefLifeSpanHandler
72
134
, public CefLoadHandler
73
135
, public CefDisplayHandler
@@ -86,11 +148,14 @@ class html_client
86
148
bool gpu_enabled_;
87
149
tbb::concurrent_queue<std::wstring> javascript_before_load_;
88
150
std::atomic<bool > loaded_;
89
- std::queue<std::pair<std::int_least64_t , core::draw_frame>> frames_;
151
+ std::queue<presentation_frame> frames_;
152
+ core::draw_frame last_generated_frame_;
90
153
mutable std::mutex frames_mutex_;
91
154
const size_t frames_max_size_ = 4 ;
92
155
std::atomic<bool > closing_;
93
156
157
+ std::unique_ptr<ffmpeg::AudioResampler> audioResampler_;
158
+
94
159
core::draw_frame last_frame_;
95
160
std::int_least64_t last_frame_time_;
96
161
@@ -168,15 +233,15 @@ class html_client
168
233
169
234
// Check if the sole buffered frame is too young to have a partner field generated (with a tolerance)
170
235
auto time_per_frame = (1000 * 1.5 ) / format_desc_.fps ;
171
- auto front_frame_is_too_young = (now_time - frames_.front ().first ) < time_per_frame;
236
+ auto front_frame_is_too_young = (now_time - frames_.front ().timestamp ) < time_per_frame;
172
237
173
238
if (follows_gap_in_frames && front_frame_is_too_young) {
174
239
return false ;
175
240
}
176
241
}
177
242
178
- last_frame_time_ = frames_.front ().first ;
179
- last_frame_ = std::move (frames_.front ().second );
243
+ last_frame_time_ = frames_.front ().timestamp ;
244
+ last_frame_ = std::move (frames_.front ().frame );
180
245
frames_.pop ();
181
246
182
247
graph_->set_value (" buffered-frames" , (double )frames_.size () / frames_max_size_);
@@ -246,12 +311,7 @@ class html_client
246
311
}
247
312
248
313
private:
249
- std::int_least64_t now ()
250
- {
251
- return std::chrono::duration_cast<std::chrono::milliseconds>(
252
- std::chrono::high_resolution_clock::now ().time_since_epoch ())
253
- .count ();
254
- }
314
+
255
315
256
316
void GetViewRect (CefRefPtr<CefBrowser> browser, CefRect& rect) override
257
317
{
@@ -303,7 +363,13 @@ class html_client
303
363
{
304
364
std::lock_guard<std::mutex> lock (frames_mutex_);
305
365
306
- frames_.push (std::make_pair (now (), core::draw_frame (std::move (frame))));
366
+ core::draw_frame new_frame = core::draw_frame (std::move (frame));
367
+ last_generated_frame_ = new_frame;
368
+
369
+ presentation_frame wrapped_frame;
370
+ wrapped_frame.add_video (std::move (new_frame));
371
+
372
+ frames_.push (std::move (wrapped_frame));
307
373
while (frames_.size () > 4 ) {
308
374
frames_.pop ();
309
375
graph_->set_tag (diagnostics::tag_severity::WARNING, " dropped-frame" );
@@ -354,6 +420,8 @@ class html_client
354
420
355
421
CefRefPtr<CefRenderHandler> GetRenderHandler () override { return this ; }
356
422
423
+ CefRefPtr<CefAudioHandler> GetAudioHandler () override { return this ; }
424
+
357
425
CefRefPtr<CefLifeSpanHandler> GetLifeSpanHandler () override { return this ; }
358
426
359
427
CefRefPtr<CefLoadHandler> GetLoadHandler () override { return this ; }
@@ -379,7 +447,7 @@ class html_client
379
447
380
448
{
381
449
std::lock_guard<std::mutex> lock (frames_mutex_);
382
- frames_.push (std::make_pair ( now (), core::draw_frame::empty () ));
450
+ frames_.push (presentation_frame ( ));
383
451
}
384
452
385
453
{
@@ -400,6 +468,55 @@ class html_client
400
468
return false ;
401
469
}
402
470
471
+ bool GetAudioParameters (CefRefPtr<CefBrowser> browser, CefAudioParameters& params) override
472
+ {
473
+ params.channel_layout = CEF_CHANNEL_LAYOUT_7_1;
474
+ params.sample_rate = format_desc_.audio_sample_rate ;
475
+ params.frames_per_buffer = format_desc_.audio_cadence [0 ];
476
+ return format_desc_.audio_cadence .size () == 1 ; // TODO - handle 59.94
477
+ }
478
+
479
+ void OnAudioStreamStarted (CefRefPtr<CefBrowser> browser, const CefAudioParameters& params, int channels) override
480
+ {
481
+ audioResampler_ = std::make_unique<ffmpeg::AudioResampler>(params.sample_rate , AV_SAMPLE_FMT_FLTP);
482
+ }
483
+ void OnAudioStreamPacket (CefRefPtr<CefBrowser> browser, const float ** data, int samples, int64_t pts) override
484
+ {
485
+ if (!audioResampler_) return ;
486
+
487
+ auto audio = audioResampler_->convert (samples, reinterpret_cast <const void **>(data));
488
+ auto audio_frame = core::mutable_frame (this , {}, std::move (audio), core::pixel_format_desc ());
489
+
490
+ {
491
+ std::lock_guard<std::mutex> lock (frames_mutex_);
492
+ if (frames_.empty ()) {
493
+ presentation_frame wrapped_frame;
494
+
495
+ wrapped_frame.add_audio (std::move (audio_frame));
496
+ if (last_generated_frame_) {
497
+ wrapped_frame.add_video (last_generated_frame_);
498
+ }
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;
506
+ wrapped_frame.add_audio (std::move (audio_frame));
507
+ frames_.push (std::move (wrapped_frame));
508
+ }
509
+ }
510
+
511
+ }
512
+ }
513
+ void OnAudioStreamStopped (CefRefPtr<CefBrowser> browser) override { audioResampler_ = nullptr ; }
514
+ void OnAudioStreamError (CefRefPtr<CefBrowser> browser, const CefString& message) override
515
+ {
516
+ CASPAR_LOG (info) << " [html_producer] OnAudioStreamError: \" " << message.ToString () << " \" " ;
517
+ audioResampler_ = nullptr ;
518
+ }
519
+
403
520
void do_execute_javascript (const std::wstring& javascript)
404
521
{
405
522
html::begin_invoke ([=] {
0 commit comments