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