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 ()
84
+ {
85
+ }
86
+
87
+ presentation_frame (presentation_frame&& other)
88
+ noexcept : timestamp(other.timestamp),frame(std::move(other.frame))
89
+ {
90
+ }
91
+
92
+ presentation_frame (const presentation_frame&) = delete;
93
+ presentation_frame& operator =(const presentation_frame&) = delete ;
94
+
95
+ presentation_frame& operator =(presentation_frame&& rhs)
96
+ {
97
+ timestamp = rhs.timestamp ;
98
+ frame = std::move (rhs.frame );
99
+ return *this ;
100
+ }
101
+
102
+ ~presentation_frame () {}
103
+
104
+ void add_audio (core::mutable_frame audio) {
105
+ if (has_audio) return ;
106
+ has_audio = true ;
107
+
108
+ if (frame) {
109
+ frame = core::draw_frame::over (frame, core::draw_frame (std::move (audio)));
110
+ } else {
111
+ frame = core::draw_frame (std::move (audio));
112
+ }
113
+ }
114
+
115
+ void add_video (core::draw_frame video) {
116
+ if (has_video) return ;
117
+ has_video = true ;
118
+
119
+ if (frame) {
120
+ frame = core::draw_frame::over (frame, std::move (video));
121
+ } else {
122
+ frame = std::move (video);
123
+ }
124
+ }
125
+ };
126
+
127
+
67
128
class html_client
68
129
: public CefClient
69
130
, public CefRenderHandler
131
+ , public CefAudioHandler
70
132
, public CefLifeSpanHandler
71
133
, public CefLoadHandler
72
134
, public CefDisplayHandler
@@ -85,11 +147,14 @@ class html_client
85
147
bool gpu_enabled_;
86
148
tbb::concurrent_queue<std::wstring> javascript_before_load_;
87
149
std::atomic<bool > loaded_;
88
- std::queue<std::pair<std::int_least64_t , core::draw_frame>> frames_;
150
+ std::queue<presentation_frame> frames_;
151
+ core::draw_frame last_generated_frame_;
89
152
mutable std::mutex frames_mutex_;
90
153
const size_t frames_max_size_ = 4 ;
91
154
std::atomic<bool > closing_;
92
155
156
+ std::unique_ptr<ffmpeg::AudioResampler> audioResampler_;
157
+
93
158
core::draw_frame last_frame_;
94
159
std::int_least64_t last_frame_time_;
95
160
@@ -167,15 +232,15 @@ class html_client
167
232
168
233
// Check if the sole buffered frame is too young to have a partner field generated (with a tolerance)
169
234
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;
235
+ auto front_frame_is_too_young = (now_time - frames_.front ().timestamp ) < time_per_frame;
171
236
172
237
if (follows_gap_in_frames && front_frame_is_too_young) {
173
238
return false ;
174
239
}
175
240
}
176
241
177
- last_frame_time_ = frames_.front ().first ;
178
- last_frame_ = std::move (frames_.front ().second );
242
+ last_frame_time_ = frames_.front ().timestamp ;
243
+ last_frame_ = std::move (frames_.front ().frame );
179
244
frames_.pop ();
180
245
181
246
graph_->set_value (" buffered-frames" , (double )frames_.size () / frames_max_size_);
@@ -245,12 +310,7 @@ class html_client
245
310
}
246
311
247
312
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
- }
313
+
254
314
255
315
void GetViewRect (CefRefPtr<CefBrowser> browser, CefRect& rect) override
256
316
{
@@ -302,7 +362,13 @@ class html_client
302
362
{
303
363
std::lock_guard<std::mutex> lock (frames_mutex_);
304
364
305
- frames_.push (std::make_pair (now (), core::draw_frame (std::move (frame))));
365
+ core::draw_frame new_frame = core::draw_frame (std::move (frame));
366
+ last_generated_frame_ = new_frame;
367
+
368
+ presentation_frame wrapped_frame;
369
+ wrapped_frame.add_video (std::move (new_frame));
370
+
371
+ frames_.push (std::move (wrapped_frame));
306
372
while (frames_.size () > 4 ) {
307
373
frames_.pop ();
308
374
graph_->set_tag (diagnostics::tag_severity::WARNING, " dropped-frame" );
@@ -353,6 +419,8 @@ class html_client
353
419
354
420
CefRefPtr<CefRenderHandler> GetRenderHandler () override { return this ; }
355
421
422
+ CefRefPtr<CefAudioHandler> GetAudioHandler () override { return this ; }
423
+
356
424
CefRefPtr<CefLifeSpanHandler> GetLifeSpanHandler () override { return this ; }
357
425
358
426
CefRefPtr<CefLoadHandler> GetLoadHandler () override { return this ; }
@@ -378,7 +446,7 @@ class html_client
378
446
379
447
{
380
448
std::lock_guard<std::mutex> lock (frames_mutex_);
381
- frames_.push (std::make_pair ( now (), core::draw_frame::empty () ));
449
+ frames_.push (presentation_frame ( ));
382
450
}
383
451
384
452
{
@@ -399,6 +467,55 @@ class html_client
399
467
return false ;
400
468
}
401
469
470
+ bool GetAudioParameters (CefRefPtr<CefBrowser> browser, CefAudioParameters& params) override
471
+ {
472
+ params.channel_layout = CEF_CHANNEL_LAYOUT_7_1;
473
+ params.sample_rate = format_desc_.audio_sample_rate ;
474
+ params.frames_per_buffer = format_desc_.audio_cadence [0 ];
475
+ return format_desc_.audio_cadence .size () == 1 ; // TODO - handle 59.94
476
+ }
477
+
478
+ void OnAudioStreamStarted (CefRefPtr<CefBrowser> browser, const CefAudioParameters& params, int channels) override
479
+ {
480
+ audioResampler_ = std::make_unique<ffmpeg::AudioResampler>(params.sample_rate , AV_SAMPLE_FMT_FLTP);
481
+ }
482
+ void OnAudioStreamPacket (CefRefPtr<CefBrowser> browser, const float ** data, int samples, int64_t pts) override
483
+ {
484
+ if (!audioResampler_) return ;
485
+
486
+ auto audio = audioResampler_->convert (samples, reinterpret_cast <const void **>(data));
487
+ auto audio_frame = core::mutable_frame (this , {}, std::move (audio), core::pixel_format_desc ());
488
+
489
+ {
490
+ std::lock_guard<std::mutex> lock (frames_mutex_);
491
+ if (frames_.empty ()) {
492
+ presentation_frame wrapped_frame;
493
+
494
+ wrapped_frame.add_audio (std::move (audio_frame));
495
+ if (last_generated_frame_) {
496
+ wrapped_frame.add_video (last_generated_frame_);
497
+ }
498
+
499
+ frames_.push (std::move (wrapped_frame));
500
+ } else {
501
+ if (!frames_.back ().has_audio ) {
502
+ frames_.back ().add_audio (std::move (audio_frame));
503
+ } else {
504
+ presentation_frame wrapped_frame;
505
+ wrapped_frame.add_audio (std::move (audio_frame));
506
+ frames_.push (std::move (wrapped_frame));
507
+ }
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