Skip to content

Commit 8be340d

Browse files
Julusianniklaspandersson
authored andcommitted
wip: inspired by #16
1 parent 08ee1e7 commit 8be340d

File tree

5 files changed

+199
-14
lines changed

5 files changed

+199
-14
lines changed

src/modules/ffmpeg/CMakeLists.txt

+2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ set(SOURCES
1111
consumer/ffmpeg_consumer.cpp
1212
consumer/ffmpeg_consumer.h
1313

14+
util/audio_resampler.cpp
15+
util/audio_resampler.h
1416
util/av_util.cpp
1517
util/av_util.h
1618
util/av_assert.h
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#include "audio_resampler.h"
2+
#include "av_assert.h"
3+
4+
extern "C" {
5+
#include <libavutil/samplefmt.h>
6+
#include <libswresample/swresample.h>
7+
}
8+
9+
namespace caspar::ffmpeg {
10+
11+
AudioResampler::AudioResampler(int64_t sample_rate, AVSampleFormat in_sample_fmt)
12+
: ctx(std::shared_ptr<SwrContext>(swr_alloc_set_opts(nullptr,
13+
AV_CH_LAYOUT_7POINT1,
14+
AV_SAMPLE_FMT_S32,
15+
sample_rate,
16+
AV_CH_LAYOUT_7POINT1,
17+
in_sample_fmt,
18+
sample_rate,
19+
0,
20+
nullptr),
21+
[](SwrContext* ptr) { swr_free(&ptr); }))
22+
{
23+
if (!ctx)
24+
FF_RET(AVERROR(ENOMEM), "swr_alloc_set_opts");
25+
26+
FF_RET(swr_init(ctx.get()), "swr_init");
27+
}
28+
29+
caspar::array<int32_t> AudioResampler::convert(int frames, const void** src)
30+
{
31+
auto result = caspar::array<int32_t>(frames * 8 * sizeof(int32_t));
32+
auto ptr = result.data();
33+
auto ret = swr_convert(ctx.get(), (uint8_t**)&ptr, frames, reinterpret_cast<const uint8_t**>(src), frames);
34+
35+
return result;
36+
}
37+
38+
}; // namespace caspar::ffmpeg
+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#include <common/array.h>
2+
#include <memory>
3+
4+
extern "C" {
5+
#include <libavutil/samplefmt.h>
6+
}
7+
8+
struct SwrContext;
9+
10+
namespace caspar::ffmpeg {
11+
12+
class AudioResampler
13+
{
14+
std::shared_ptr<SwrContext> ctx;
15+
16+
public:
17+
AudioResampler(int64_t sample_rate, AVSampleFormat in_sample_fmt);
18+
19+
AudioResampler(const AudioResampler&) = delete;
20+
AudioResampler& operator=(const AudioResampler&) = delete;
21+
22+
caspar::array<int32_t> convert(int frames, const void** src);
23+
};
24+
25+
}; // namespace caspar::ffmpeg

src/modules/html/CMakeLists.txt

+4-2
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,12 @@ casparcg_add_module_project(html
2020
)
2121

2222
target_include_directories(html PRIVATE
23+
..
2324
../..
2425
${CEF_INCLUDE_PATH}
25-
)
26-
target_link_libraries(html ${CEF_LIB})
26+
${FFMPEG_INCLUDE_PATH}
27+
)
28+
target_link_libraries(html ffmpeg ${CEF_LIB})
2729

2830
set_target_properties(html PROPERTIES FOLDER modules)
2931
source_group(sources\\producer producer/*)

src/modules/html/producer/html_producer.cpp

+130-12
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,76 @@
6161
#include <queue>
6262
#include <utility>
6363

64+
#include <ffmpeg/util/audio_resampler.h>
65+
66+
#include "../html.h"
6467
#include "../util.h"
6568

6669
namespace caspar { namespace html {
6770

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+
68130
class html_client
69131
: public CefClient
70132
, public CefRenderHandler
133+
, public CefAudioHandler
71134
, public CefLifeSpanHandler
72135
, public CefLoadHandler
73136
, public CefDisplayHandler
@@ -86,11 +149,14 @@ class html_client
86149
bool gpu_enabled_;
87150
tbb::concurrent_queue<std::wstring> javascript_before_load_;
88151
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_;
90154
mutable std::mutex frames_mutex_;
91155
const size_t frames_max_size_ = 4;
92156
std::atomic<bool> closing_;
93157

158+
std::unique_ptr<ffmpeg::AudioResampler> audioResampler_;
159+
94160
core::draw_frame last_frame_;
95161
std::int_least64_t last_frame_time_;
96162

@@ -168,15 +234,15 @@ class html_client
168234

169235
// Check if the sole buffered frame is too young to have a partner field generated (with a tolerance)
170236
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;
172238

173239
if (follows_gap_in_frames && front_frame_is_too_young) {
174240
return false;
175241
}
176242
}
177243

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);
180246
frames_.pop();
181247

182248
graph_->set_value("buffered-frames", (double)frames_.size() / frames_max_size_);
@@ -247,12 +313,7 @@ class html_client
247313
}
248314

249315
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+
256317

257318
void GetViewRect(CefRefPtr<CefBrowser> browser, CefRect& rect) override
258319
{
@@ -304,7 +365,13 @@ class html_client
304365
{
305366
std::lock_guard<std::mutex> lock(frames_mutex_);
306367

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));
308375
while (frames_.size() > 4) {
309376
frames_.pop();
310377
graph_->set_tag(diagnostics::tag_severity::WARNING, "dropped-frame");
@@ -355,6 +422,8 @@ class html_client
355422

356423
CefRefPtr<CefRenderHandler> GetRenderHandler() override { return this; }
357424

425+
CefRefPtr<CefAudioHandler> GetAudioHandler() override { return this; }
426+
358427
CefRefPtr<CefLifeSpanHandler> GetLifeSpanHandler() override { return this; }
359428

360429
CefRefPtr<CefLoadHandler> GetLoadHandler() override { return this; }
@@ -380,7 +449,7 @@ class html_client
380449

381450
{
382451
std::lock_guard<std::mutex> lock(frames_mutex_);
383-
frames_.push(std::make_pair(now(), core::draw_frame::empty()));
452+
frames_.push(presentation_frame());
384453
}
385454

386455
{
@@ -401,6 +470,55 @@ class html_client
401470
return false;
402471
}
403472

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+
404522
void do_execute_javascript(const std::wstring& javascript)
405523
{
406524
html::begin_invoke([=] {

0 commit comments

Comments
 (0)