Skip to content

Commit 21a08d8

Browse files
committed
feat: test playahead buffer test
Signed-off-by: deltag0 <ioan.1931@gmail.com>
1 parent cd8d165 commit 21a08d8

7 files changed

Lines changed: 163 additions & 5 deletions

File tree

src/lib/image/MovieFFMpeg/MovieFFMpeg.cpp

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include <TwkUtil/PathConform.h>
2121
#include <TwkUtil/File.h>
2222
#include <TwkUtil/sgcHop.h>
23+
#include <stream/StreamPreloadPool.h>
2324
#include <boost/thread/lock_algorithms.hpp>
2425
#include <boost/thread/mutex.hpp>
2526
#include <cstddef>
@@ -66,6 +67,7 @@ extern "C"
6667

6768
static ENVVAR_BOOL(evUseUploadedMovieForStreaming, "RV_SHOTGRID_USE_UPLOADED_MOVIE_FOR_STREAMING", false);
6869
static ENVVAR_STRING(evStreamCachePath, "RV_STREAM_CACHE_PATH", "/tmp");
70+
static ENVVAR_FLOAT(evPlayheadPrefetchSeconds, "RV_STREAM_PLAYHEAD_PREFETCH_SECONDS", 5.0f);
6971

7072
namespace TwkMovie
7173
{
@@ -1337,6 +1339,36 @@ namespace TwkMovie
13371339
}
13381340
}
13391341

1342+
void MovieFFMpegReader::prefetchAtFrame(int frame)
1343+
{
1344+
const float windowSeconds = evPlayheadPrefetchSeconds.getValue();
1345+
if (!TwkUtil::pathIsURL(m_filename) || windowSeconds <= 0.0f || m_info.fps <= 0.0f)
1346+
return;
1347+
1348+
const double frameSeconds = std::max(0.0, double(frame - m_info.start) / double(m_info.fps));
1349+
const int64_t window = static_cast<int64_t>(frameSeconds / windowSeconds);
1350+
if (m_lastPrefetchWindow.exchange(window) == window)
1351+
return;
1352+
1353+
std::string url = m_filename;
1354+
if (evUseUploadedMovieForStreaming.getValue())
1355+
boost::replace_all(url, "#.mp4", "");
1356+
url = "shared:" + url;
1357+
1358+
StreamerPool::Options options;
1359+
options.emplace_back("cache_dir", evStreamCachePath.getValue());
1360+
options.emplace_back("seekable", "1");
1361+
options.emplace_back("reconnect", "1");
1362+
options.emplace_back("multiple_requests", "1");
1363+
for (const auto& [name, value] : m_request.parameters)
1364+
{
1365+
if (name == "cookies" || name == "headers")
1366+
options.emplace_back(name, value);
1367+
}
1368+
1369+
StreamerPool::getPool().enqueueWindow(url, options, frameSeconds, windowSeconds);
1370+
}
1371+
13401372
bool MovieFFMpegReader::openAVFormat()
13411373
{
13421374
const bool filepathIsURL = TwkUtil::pathIsURL(m_filename);
@@ -2647,7 +2679,7 @@ namespace TwkMovie
26472679
AVCodecContext* videoCodecContext = track->avCodecContext;
26482680

26492681
// Tell RV to restrict caching to one thread
2650-
bool slowTrackRandomAccess = (codecHasSlowAccess(videoCodecContext->codec->name) || TwkUtil::pathIsURL(m_filename));
2682+
bool slowTrackRandomAccess = codecHasSlowAccess(videoCodecContext->codec->name);
26512683
slowRandomAccess = slowTrackRandomAccess || slowRandomAccess;
26522684

26532685
// Make sure the orientation/rotation matches for each track

src/lib/image/MovieFFMpeg/MovieFFMpeg/MovieFFMpeg.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include <TwkMovie/MovieReader.h>
1111
#include <TwkMovie/MovieWriter.h>
1212
#include <TwkMovie/MovieIO.h>
13+
#include <atomic>
1314
#include <stdint.h>
1415
extern "C"
1516
{
@@ -158,6 +159,7 @@ namespace TwkMovie
158159
// Used for streamed media only to establish connections at the launch of RV for movie reader clones
159160
//
160161
void warmOpen() override;
162+
void prefetchAtFrame(int frame) override;
161163

162164
virtual bool canConvertAudioChannels() const;
163165
void close();
@@ -279,6 +281,7 @@ namespace TwkMovie
279281
bool m_multiTrackAudio;
280282
AudioState* m_audioState;
281283
bool m_cloning{false};
284+
std::atomic<int64_t> m_lastPrefetchWindow{-1};
282285
bool m_mustReadFirstFrame{false};
283286
AVPixelFormat m_pxlFormatOnOpen{AV_PIX_FMT_NONE};
284287

src/lib/image/TwkMovie/MovieReader.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ namespace TwkMovie
3838

3939
void MovieReader::warmOpen() {}
4040

41+
void MovieReader::prefetchAtFrame(int) {}
42+
4143
Movie* MovieReader::clone() const { return 0; }
4244

4345
float MovieReader::scanProgress() const { return 1.0; }

src/lib/image/TwkMovie/TwkMovie/MovieReader.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,8 @@ namespace TwkMovie
132132

133133
virtual void warmOpen();
134134

135+
virtual void prefetchAtFrame(int frame);
136+
135137
///
136138
/// if an additional scanning pass is required after open this
137139
/// should return true from needsScan() while that is the case.

src/lib/image/stream/StreamPreloadPool.cpp

Lines changed: 107 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,15 @@
1111

1212
#include <TwkUtil/EnvVar.h>
1313

14+
#include <algorithm>
15+
1416
extern "C"
1517
{
18+
#include <libavformat/avformat.h>
1619
#include <libavformat/avio.h>
1720
#include <libavutil/dict.h>
1821
#include <libavutil/error.h>
22+
#include <libavutil/mathematics.h>
1923
}
2024

2125
// 32 KB
@@ -72,6 +76,35 @@ void StreamerPool::enqueue(const std::string& url, const Options& options)
7276
m_wake.notify_all();
7377
}
7478

79+
void StreamerPool::enqueueWindow(const std::string& url, const Options& options, double startSeconds, double durationSeconds)
80+
{
81+
{
82+
std::lock_guard<std::mutex> lock(m_mutex);
83+
84+
if (m_stopped)
85+
{
86+
return;
87+
}
88+
89+
m_queue.erase(std::remove_if(m_queue.begin(), m_queue.end(), [](const Job& job) { return job.window; }), m_queue.end());
90+
91+
Job job;
92+
job.url = url;
93+
job.options = options;
94+
job.window = true;
95+
job.startSeconds = startSeconds;
96+
job.durationSeconds = durationSeconds;
97+
m_queue.push_front(job);
98+
99+
if (!m_scheduler.joinable())
100+
{
101+
m_scheduler = std::thread(&StreamerPool::schedulerLoop, this);
102+
}
103+
}
104+
105+
m_wake.notify_all();
106+
}
107+
75108
void StreamerPool::schedulerLoop()
76109
{
77110
for (;;)
@@ -82,18 +115,32 @@ void StreamerPool::schedulerLoop()
82115
[this]
83116
{
84117
reapFinished();
85-
return m_stopped || (!m_queue.empty() && m_activeWorkers.size() < m_maxThreads);
118+
return m_stopped
119+
|| (!m_queue.empty()
120+
&& (m_queue.front().window ? m_activeWindowWorkers == 0 : m_activeWorkers.size() < m_maxThreads));
86121
});
87122

88123
if (m_stopped)
89124
{
90125
return;
91126
}
92127

93-
while (!m_queue.empty() && m_activeWorkers.size() < m_maxThreads)
128+
while (!m_queue.empty())
94129
{
130+
if (m_queue.front().window)
131+
{
132+
if (m_activeWindowWorkers != 0)
133+
break;
134+
}
135+
else if (m_activeWorkers.size() >= m_maxThreads)
136+
{
137+
break;
138+
}
139+
95140
Job job = m_queue.front();
96141
m_queue.pop_front();
142+
if (job.window)
143+
++m_activeWindowWorkers;
97144
m_activeWorkers.emplace_back(&StreamerPool::workerFunc, this, job);
98145
}
99146
}
@@ -120,10 +167,15 @@ void StreamerPool::reapFinished()
120167

121168
void StreamerPool::workerFunc(Job job)
122169
{
123-
download(job);
170+
if (job.window)
171+
downloadWindow(job);
172+
else
173+
download(job);
124174

125175
{
126176
std::lock_guard<std::mutex> lock(m_mutex);
177+
if (job.window)
178+
--m_activeWindowWorkers;
127179
m_finished.insert(std::this_thread::get_id());
128180
}
129181

@@ -175,6 +227,58 @@ void StreamerPool::download(const Job& job)
175227
avio_closep(&context);
176228
}
177229

230+
void StreamerPool::downloadWindow(const Job& job)
231+
{
232+
AVDictionary* options = nullptr;
233+
for (const auto& [key, value] : job.options)
234+
{
235+
av_dict_set(&options, key.c_str(), value.c_str(), 0);
236+
}
237+
238+
AVFormatContext* context = avformat_alloc_context();
239+
if (context == nullptr)
240+
{
241+
av_dict_free(&options);
242+
return;
243+
}
244+
245+
context->interrupt_callback.callback = &StreamerPool::interruptCallback;
246+
context->interrupt_callback.opaque = this;
247+
248+
if (avformat_open_input(&context, job.url.c_str(), nullptr, &options) < 0)
249+
{
250+
av_dict_free(&options);
251+
avformat_free_context(context);
252+
return;
253+
}
254+
av_dict_free(&options);
255+
256+
if (avformat_find_stream_info(context, nullptr) >= 0)
257+
{
258+
const int64_t mediaStart = context->start_time == AV_NOPTS_VALUE ? 0 : context->start_time;
259+
const int64_t start = mediaStart + static_cast<int64_t>(job.startSeconds * AV_TIME_BASE);
260+
const int64_t end = start + static_cast<int64_t>(job.durationSeconds * AV_TIME_BASE);
261+
262+
if (avformat_seek_file(context, -1, INT64_MIN, start, INT64_MAX, AVSEEK_FLAG_BACKWARD) >= 0)
263+
{
264+
AVPacket* packet = av_packet_alloc();
265+
while (packet != nullptr && !m_abort.load() && av_read_frame(context, packet) >= 0)
266+
{
267+
const int64_t timestamp = packet->pts != AV_NOPTS_VALUE ? packet->pts : packet->dts;
268+
const int64_t packetTime = timestamp == AV_NOPTS_VALUE
269+
? start
270+
: av_rescale_q(timestamp, context->streams[packet->stream_index]->time_base, AV_TIME_BASE_Q);
271+
av_packet_unref(packet);
272+
if (packetTime >= end)
273+
break;
274+
}
275+
av_packet_free(&packet);
276+
}
277+
}
278+
279+
avformat_close_input(&context);
280+
}
281+
178282
void StreamerPool::shutdown()
179283
{
180284
{

src/lib/image/stream/stream/StreamPreloadPool.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ class StreamerPool
4848
// Wakes
4949
void enqueue(const std::string& url, const Options& options);
5050

51+
// Prefetch a short demuxed window without decoding it. Window jobs use
52+
// one reserved worker and supersede older queued window jobs.
53+
void enqueueWindow(const std::string& url, const Options& options, double startSeconds, double durationSeconds);
54+
5155
//
5256
// Interrupt every in flight download, drop whatever is still queued
5357
// and join the workers.
@@ -63,6 +67,9 @@ class StreamerPool
6367
{
6468
std::string url;
6569
Options options;
70+
bool window = false;
71+
double startSeconds = 0.0;
72+
double durationSeconds = 0.0;
6673
};
6774

6875
//
@@ -76,6 +83,7 @@ class StreamerPool
7683
// Handles FFMPEG API calls and fully downloads the raw media of the file
7784
//
7885
void download(const Job& job);
86+
void downloadWindow(const Job& job);
7987

8088
//
8189
// Checks which workers have finished, waits for the threads to finish for sure,
@@ -102,6 +110,7 @@ class StreamerPool
102110
std::thread m_scheduler;
103111
std::list<std::thread> m_activeWorkers;
104112
std::set<std::thread::id> m_finished;
113+
size_t m_activeWindowWorkers = 0;
105114

106115
std::deque<Job> m_queue; // pending jobs if threads maxed out
107116
bool m_stopped; // scheduler finished

src/lib/ip/IPBaseNodes/FileSourceIPNode.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@
5858
}
5959

6060
static ENVVAR_BOOL(evIgnoreAudio, "RV_IGNORE_AUDIO", false);
61-
static ENVVAR_INT(evWarmCloneThreads, "RV_WARM_CLONE_THREADS", 4);
61+
static ENVVAR_INT(evWarmCloneThreads, "RV_WARM_CLONE_THREADS", 2);
6262
static ENVVAR_BOOL(evDebugCookies, "RV_DEBUG_FFMPEG_COOKIES", false);
6363
static ENVVAR_BOOL(evDebugHeaders, "RV_DEBUG_FFMPEG_HEADERS", false);
6464

@@ -1066,6 +1066,12 @@ namespace IPCore
10661066

10671067
PROFILE_SAMPLE(profile, ioEnd);
10681068

1069+
if (context.thread & DisplayThread)
1070+
{
1071+
if (MovieReader* reader = dynamic_cast<MovieReader*>(mov))
1072+
reader->prefetchAtFrame(request.frame);
1073+
}
1074+
10691075
if (failed || empty)
10701076
{
10711077
Movie::IdentifierVector ids;

0 commit comments

Comments
 (0)