* Instead of using some bogus latency for the VideoProducer,
compute the latency from the buffer count. It should be the duration of in-flight buffers (i.e. number of buffers minus the one currently showing). * Compute the wakeUp time based on the buffer latency used above. * Make the "wasCached" mechanism work again, i.e. don't send the buffer to the consumer if the contents did not change. This removes the need to cache frames in the ProxyVideoSupplier and thus one more memcpy() (5ms on my Q6600 for full-HD content). * Remove the weird forceSendingBuffer override and the setting of the header starttime to 0. Now seeking clips works also when the playback is paused. All in all, playback is more efficient now, and the chances of dropping frames are much less. Something is still fishy, though, since VLC, even though seemingly using slightly more CPU, drops frames more seldomly than MediaPlayer. Audio/Video sync seems to be better, though, since the VideoProducer is using a more accurate latency now. git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@38662 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
parent
6eac6bdea9
commit
f7eb8be930
@ -51,6 +51,10 @@ VideoProducer::VideoProducer(BMediaAddOn* addon, const char* name,
|
|||||||
fUsedBufferGroup(NULL),
|
fUsedBufferGroup(NULL),
|
||||||
fThread(-1),
|
fThread(-1),
|
||||||
fFrameSync(-1),
|
fFrameSync(-1),
|
||||||
|
fFrame(0),
|
||||||
|
fFrameBase(0),
|
||||||
|
fPerformanceTimeBase(0),
|
||||||
|
fBufferLatency(0),
|
||||||
fRunning(false),
|
fRunning(false),
|
||||||
fConnected(false),
|
fConnected(false),
|
||||||
fEnabled(false),
|
fEnabled(false),
|
||||||
@ -422,9 +426,6 @@ VideoProducer::PrepareToConnect(const media_source& source,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#define NODE_LATENCY 20000
|
|
||||||
|
|
||||||
|
|
||||||
void
|
void
|
||||||
VideoProducer::Connect(status_t error, const media_source& source,
|
VideoProducer::Connect(status_t error, const media_source& source,
|
||||||
const media_destination& destination, const media_format& format,
|
const media_destination& destination, const media_format& format,
|
||||||
@ -454,29 +455,25 @@ VideoProducer::Connect(status_t error, const media_source& source,
|
|||||||
|
|
||||||
fOutput.destination = destination;
|
fOutput.destination = destination;
|
||||||
strcpy(_name, fOutput.name);
|
strcpy(_name, fOutput.name);
|
||||||
|
fConnectedFormat = format.u.raw_video;
|
||||||
|
fBufferDuration = 20000;
|
||||||
|
|
||||||
if (fOutput.format.u.raw_video.field_rate != 0.0f) {
|
if (fConnectedFormat.field_rate != 0.0f) {
|
||||||
fPerformanceTimeBase = fPerformanceTimeBase
|
fPerformanceTimeBase = fPerformanceTimeBase
|
||||||
+ (bigtime_t)((fFrame - fFrameBase)
|
+ (bigtime_t)((fFrame - fFrameBase)
|
||||||
* 1000000 / fOutput.format.u.raw_video.field_rate);
|
* 1000000LL / fConnectedFormat.field_rate);
|
||||||
fFrameBase = fFrame;
|
fFrameBase = fFrame;
|
||||||
|
fBufferDuration = 1000000LL / fConnectedFormat.field_rate;
|
||||||
}
|
}
|
||||||
|
|
||||||
fConnectedFormat = format.u.raw_video;
|
|
||||||
if (fConnectedFormat.display.bytes_per_row == 0) {
|
if (fConnectedFormat.display.bytes_per_row == 0) {
|
||||||
ERROR("Connect() - connected format still has BPR wildcard!\n");
|
ERROR("Connect() - connected format still has BPR wildcard!\n");
|
||||||
fConnectedFormat.display.bytes_per_row
|
fConnectedFormat.display.bytes_per_row
|
||||||
= 4 * fConnectedFormat.display.line_width;
|
= 4 * fConnectedFormat.display.line_width;
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the latency
|
|
||||||
bigtime_t latency = 0;
|
|
||||||
media_node_id tsID = 0;
|
|
||||||
FindLatencyFor(fOutput.destination, &latency, &tsID);
|
|
||||||
SetEventLatency(latency + NODE_LATENCY);
|
|
||||||
|
|
||||||
// Create the buffer group
|
// Create the buffer group
|
||||||
if (!fUsedBufferGroup) {
|
if (fUsedBufferGroup == NULL) {
|
||||||
fBufferGroup = new BBufferGroup(fConnectedFormat.display.bytes_per_row
|
fBufferGroup = new BBufferGroup(fConnectedFormat.display.bytes_per_row
|
||||||
* fConnectedFormat.display.line_count, BUFFER_COUNT);
|
* fConnectedFormat.display.line_count, BUFFER_COUNT);
|
||||||
status_t err = fBufferGroup->InitCheck();
|
status_t err = fBufferGroup->InitCheck();
|
||||||
@ -489,6 +486,20 @@ VideoProducer::Connect(status_t error, const media_source& source,
|
|||||||
fUsedBufferGroup = fBufferGroup;
|
fUsedBufferGroup = fBufferGroup;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// get the latency
|
||||||
|
fBufferLatency = (BUFFER_COUNT - 1) * fBufferDuration;
|
||||||
|
|
||||||
|
int32 bufferCount;
|
||||||
|
if (fUsedBufferGroup->CountBuffers(&bufferCount) == B_OK) {
|
||||||
|
// recompute the latency
|
||||||
|
fBufferLatency = (bufferCount - 1) * fBufferDuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
bigtime_t latency = 0;
|
||||||
|
media_node_id tsID = 0;
|
||||||
|
FindLatencyFor(fOutput.destination, &latency, &tsID);
|
||||||
|
SetEventLatency(latency + fBufferLatency);
|
||||||
|
|
||||||
fConnected = true;
|
fConnected = true;
|
||||||
fEnabled = true;
|
fEnabled = true;
|
||||||
|
|
||||||
@ -665,8 +676,6 @@ int32
|
|||||||
VideoProducer::_FrameGeneratorThread()
|
VideoProducer::_FrameGeneratorThread()
|
||||||
{
|
{
|
||||||
bool forceSendingBuffer = true;
|
bool forceSendingBuffer = true;
|
||||||
bigtime_t lastFrameSentAt = 0;
|
|
||||||
int64 lastPlaylistFrame = 0;
|
|
||||||
int32 droppedFrames = 0;
|
int32 droppedFrames = 0;
|
||||||
const int32 kMaxDroppedFrames = 15;
|
const int32 kMaxDroppedFrames = 15;
|
||||||
bool running = true;
|
bool running = true;
|
||||||
@ -680,7 +689,6 @@ VideoProducer::_FrameGeneratorThread()
|
|||||||
bigtime_t nextPerformanceTime = 0;
|
bigtime_t nextPerformanceTime = 0;
|
||||||
bigtime_t waitUntil = 0;
|
bigtime_t waitUntil = 0;
|
||||||
bigtime_t nextWaitUntil = 0;
|
bigtime_t nextWaitUntil = 0;
|
||||||
bigtime_t maxRenderTime = 0;
|
|
||||||
int32 playingDirection = 0;
|
int32 playingDirection = 0;
|
||||||
int32 playingMode = 0;
|
int32 playingMode = 0;
|
||||||
int64 playlistFrame = 0;
|
int64 playlistFrame = 0;
|
||||||
@ -691,14 +699,11 @@ VideoProducer::_FrameGeneratorThread()
|
|||||||
// get the times for the current and the next frame
|
// get the times for the current and the next frame
|
||||||
performanceTime = fManager->TimeForFrame(fFrame);
|
performanceTime = fManager->TimeForFrame(fFrame);
|
||||||
nextPerformanceTime = fManager->TimeForFrame(fFrame + 1);
|
nextPerformanceTime = fManager->TimeForFrame(fFrame + 1);
|
||||||
maxRenderTime = min_c(bigtime_t(40000),
|
|
||||||
max_c(fSupplier->ProcessingLatency(), maxRenderTime));
|
|
||||||
playingMode = fManager->PlayModeAtFrame(fFrame);
|
playingMode = fManager->PlayModeAtFrame(fFrame);
|
||||||
|
|
||||||
waitUntil = TimeSource()->RealTimeFor(fPerformanceTimeBase
|
waitUntil = TimeSource()->RealTimeFor(fPerformanceTimeBase
|
||||||
+ performanceTime, 0) - maxRenderTime;
|
+ performanceTime, fBufferLatency);
|
||||||
nextWaitUntil = TimeSource()->RealTimeFor(fPerformanceTimeBase
|
nextWaitUntil = TimeSource()->RealTimeFor(fPerformanceTimeBase
|
||||||
+ nextPerformanceTime, 0) - maxRenderTime;
|
+ nextPerformanceTime, fBufferLatency);
|
||||||
// get playing direction and playlist frame for the current
|
// get playing direction and playlist frame for the current
|
||||||
// frame
|
// frame
|
||||||
bool newPlayingState;
|
bool newPlayingState;
|
||||||
@ -707,10 +712,6 @@ VideoProducer::_FrameGeneratorThread()
|
|||||||
TRACE("_FrameGeneratorThread: performance time: %Ld, "
|
TRACE("_FrameGeneratorThread: performance time: %Ld, "
|
||||||
"playlist frame: %lld\n", performanceTime, playlistFrame);
|
"playlist frame: %lld\n", performanceTime, playlistFrame);
|
||||||
forceSendingBuffer |= newPlayingState;
|
forceSendingBuffer |= newPlayingState;
|
||||||
if (lastPlaylistFrame != playlistFrame) {
|
|
||||||
forceSendingBuffer = true;
|
|
||||||
lastPlaylistFrame = playlistFrame;
|
|
||||||
}
|
|
||||||
fManager->SetCurrentVideoTime(nextPerformanceTime);
|
fManager->SetCurrentVideoTime(nextPerformanceTime);
|
||||||
fManager->Unlock();
|
fManager->Unlock();
|
||||||
break;
|
break;
|
||||||
@ -748,7 +749,7 @@ VideoProducer::_FrameGeneratorThread()
|
|||||||
if (ignoreEvent || !fRunning || !fEnabled) {
|
if (ignoreEvent || !fRunning || !fEnabled) {
|
||||||
TRACE("_FrameGeneratorThread: ignore event\n");
|
TRACE("_FrameGeneratorThread: ignore event\n");
|
||||||
// nothing to do
|
// nothing to do
|
||||||
} else if (nextWaitUntil < system_time()
|
} else if (nextWaitUntil < system_time() - fBufferLatency
|
||||||
&& droppedFrames < kMaxDroppedFrames) {
|
&& droppedFrames < kMaxDroppedFrames) {
|
||||||
// Drop frame if it's at least a frame late.
|
// Drop frame if it's at least a frame late.
|
||||||
printf("VideoProducer: dropped frame (%Ld)\n", fFrame);
|
printf("VideoProducer: dropped frame (%Ld)\n", fFrame);
|
||||||
@ -765,78 +766,72 @@ VideoProducer::_FrameGeneratorThread()
|
|||||||
TRACE("_FrameGeneratorThread: produce frame\n");
|
TRACE("_FrameGeneratorThread: produce frame\n");
|
||||||
BAutolock _(fLock);
|
BAutolock _(fLock);
|
||||||
// Fetch a buffer from the buffer group
|
// Fetch a buffer from the buffer group
|
||||||
|
fUsedBufferGroup->WaitForBuffers();
|
||||||
BBuffer* buffer = fUsedBufferGroup->RequestBuffer(
|
BBuffer* buffer = fUsedBufferGroup->RequestBuffer(
|
||||||
fConnectedFormat.display.bytes_per_row
|
fConnectedFormat.display.bytes_per_row
|
||||||
* fConnectedFormat.display.line_count, 0LL);
|
* fConnectedFormat.display.line_count, 0LL);
|
||||||
if (buffer != NULL) {
|
if (buffer == NULL) {
|
||||||
// Fill out the details about this buffer.
|
// Wait until a buffer becomes available again
|
||||||
media_header* h = buffer->Header();
|
|
||||||
h->type = B_MEDIA_RAW_VIDEO;
|
|
||||||
h->time_source = TimeSource()->ID();
|
|
||||||
h->size_used = fConnectedFormat.display.bytes_per_row
|
|
||||||
* fConnectedFormat.display.line_count;
|
|
||||||
// For a buffer originating from a device, you might
|
|
||||||
// want to calculate this based on the
|
|
||||||
// PerformanceTimeFor the time your buffer arrived at
|
|
||||||
// the hardware (plus any applicable adjustments).
|
|
||||||
h->start_time = fPerformanceTimeBase + performanceTime;
|
|
||||||
// TODO: Fix the runmode stuff! Setting the consumer to B_OFFLINE does
|
|
||||||
// not do the trick. I made the VideoConsumer check the performance
|
|
||||||
// time of the buffer and if it is 0, it plays it regardless.
|
|
||||||
if (playingMode < 0 || droppedFrames >= kMaxDroppedFrames) {
|
|
||||||
h->start_time = 0;
|
|
||||||
}
|
|
||||||
h->file_pos = 0;
|
|
||||||
h->orig_size = 0;
|
|
||||||
h->data_offset = 0;
|
|
||||||
h->u.raw_video.field_gamma = 1.0;
|
|
||||||
h->u.raw_video.field_sequence = fFrame;
|
|
||||||
h->u.raw_video.field_number = 0;
|
|
||||||
h->u.raw_video.pulldown_number = 0;
|
|
||||||
h->u.raw_video.first_active_line = 1;
|
|
||||||
h->u.raw_video.line_count
|
|
||||||
= fConnectedFormat.display.line_count;
|
|
||||||
// Fill in a frame
|
|
||||||
TRACE("_FrameGeneratorThread: frame: %Ld, "
|
|
||||||
"playlistFrame: %Ld\n", fFrame, playlistFrame);
|
|
||||||
bool forceOrWasCached = forceSendingBuffer;
|
|
||||||
|
|
||||||
err = fSupplier->FillBuffer(playlistFrame,
|
|
||||||
buffer->Data(), fConnectedFormat,
|
|
||||||
forceOrWasCached);
|
|
||||||
// clean the buffer if something went wrong
|
|
||||||
if (err != B_OK) {
|
|
||||||
// TODO: should use "back value" according
|
|
||||||
// to color space!
|
|
||||||
memset(buffer->Data(), 0, h->size_used);
|
|
||||||
err = B_OK;
|
|
||||||
}
|
|
||||||
// Send the buffer on down to the consumer
|
|
||||||
if (SendBuffer(buffer, fOutput.source,
|
|
||||||
fOutput.destination) < B_OK) {
|
|
||||||
ERROR("_FrameGeneratorThread: Error "
|
|
||||||
"sending buffer\n");
|
|
||||||
// If there is a problem sending the buffer,
|
|
||||||
// or if we don't send the buffer because its
|
|
||||||
// contents are the same as the last one,
|
|
||||||
// return it to its buffer group.
|
|
||||||
buffer->Recycle();
|
|
||||||
// we tell the supplier to delete
|
|
||||||
// its caches if there was a problem sending
|
|
||||||
// the buffer
|
|
||||||
fSupplier->DeleteCaches();
|
|
||||||
}
|
|
||||||
// Only if everything went fine we clear the flag
|
|
||||||
// that forces us to send a buffer even if not
|
|
||||||
// playing.
|
|
||||||
if (err == B_OK) {
|
|
||||||
forceSendingBuffer = false;
|
|
||||||
lastFrameSentAt = performanceTime;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
TRACE("_FrameGeneratorThread: no buffer!\n");
|
TRACE("_FrameGeneratorThread: no buffer!\n");
|
||||||
// ERROR("_FrameGeneratorThread: no buffer!\n");
|
// ERROR("_FrameGeneratorThread: no buffer!\n");
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
// Fill out the details about this buffer.
|
||||||
|
media_header* h = buffer->Header();
|
||||||
|
h->type = B_MEDIA_RAW_VIDEO;
|
||||||
|
h->time_source = TimeSource()->ID();
|
||||||
|
h->size_used = fConnectedFormat.display.bytes_per_row
|
||||||
|
* fConnectedFormat.display.line_count;
|
||||||
|
// For a buffer originating from a device, you might
|
||||||
|
// want to calculate this based on the
|
||||||
|
// PerformanceTimeFor the time your buffer arrived at
|
||||||
|
// the hardware (plus any applicable adjustments).
|
||||||
|
h->start_time = fPerformanceTimeBase + performanceTime;
|
||||||
|
h->file_pos = 0;
|
||||||
|
h->orig_size = 0;
|
||||||
|
h->data_offset = 0;
|
||||||
|
h->u.raw_video.field_gamma = 1.0;
|
||||||
|
h->u.raw_video.field_sequence = fFrame;
|
||||||
|
h->u.raw_video.field_number = 0;
|
||||||
|
h->u.raw_video.pulldown_number = 0;
|
||||||
|
h->u.raw_video.first_active_line = 1;
|
||||||
|
h->u.raw_video.line_count
|
||||||
|
= fConnectedFormat.display.line_count;
|
||||||
|
// Fill in a frame
|
||||||
|
TRACE("_FrameGeneratorThread: frame: %Ld, "
|
||||||
|
"playlistFrame: %Ld\n", fFrame, playlistFrame);
|
||||||
|
bool wasCached = false;
|
||||||
|
err = fSupplier->FillBuffer(playlistFrame,
|
||||||
|
buffer->Data(), fConnectedFormat, wasCached);
|
||||||
|
// clean the buffer if something went wrong
|
||||||
|
if (err != B_OK) {
|
||||||
|
// TODO: should use "back value" according
|
||||||
|
// to color space!
|
||||||
|
memset(buffer->Data(), 0, h->size_used);
|
||||||
|
err = B_OK;
|
||||||
|
}
|
||||||
|
// Send the buffer on down to the consumer
|
||||||
|
if (wasCached || (err = SendBuffer(buffer, fOutput.source,
|
||||||
|
fOutput.destination) != B_OK)) {
|
||||||
|
// If there is a problem sending the buffer,
|
||||||
|
// or if we don't send the buffer because its
|
||||||
|
// contents are the same as the last one,
|
||||||
|
// return it to its buffer group.
|
||||||
|
buffer->Recycle();
|
||||||
|
// we tell the supplier to delete
|
||||||
|
// its caches if there was a problem sending
|
||||||
|
// the buffer
|
||||||
|
if (err != B_OK) {
|
||||||
|
ERROR("_FrameGeneratorThread: Error "
|
||||||
|
"sending buffer\n");
|
||||||
|
fSupplier->DeleteCaches();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Only if everything went fine we clear the flag
|
||||||
|
// that forces us to send a buffer even if not
|
||||||
|
// playing.
|
||||||
|
if (err == B_OK)
|
||||||
|
forceSendingBuffer = false;
|
||||||
// next frame
|
// next frame
|
||||||
fFrame++;
|
fFrame++;
|
||||||
droppedFrames = 0;
|
droppedFrames = 0;
|
||||||
|
@ -132,6 +132,8 @@ protected:
|
|||||||
int64 fFrame;
|
int64 fFrame;
|
||||||
int64 fFrameBase;
|
int64 fFrameBase;
|
||||||
bigtime_t fPerformanceTimeBase;
|
bigtime_t fPerformanceTimeBase;
|
||||||
|
bigtime_t fBufferDuration;
|
||||||
|
bigtime_t fBufferLatency;
|
||||||
media_output fOutput;
|
media_output fOutput;
|
||||||
media_raw_video_format fConnectedFormat;
|
media_raw_video_format fConnectedFormat;
|
||||||
bool fRunning;
|
bool fRunning;
|
||||||
|
@ -18,18 +18,13 @@
|
|||||||
ProxyVideoSupplier::ProxyVideoSupplier()
|
ProxyVideoSupplier::ProxyVideoSupplier()
|
||||||
:
|
:
|
||||||
fSupplierLock("video supplier lock"),
|
fSupplierLock("video supplier lock"),
|
||||||
fSupplier(NULL),
|
fSupplier(NULL)
|
||||||
fCachedFrame(NULL),
|
|
||||||
fCachedFrameSize(0),
|
|
||||||
fCachedFrameValid(false),
|
|
||||||
fUseFrameCaching(true)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
ProxyVideoSupplier::~ProxyVideoSupplier()
|
ProxyVideoSupplier::~ProxyVideoSupplier()
|
||||||
{
|
{
|
||||||
free(fCachedFrame);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -44,35 +39,12 @@ ProxyVideoSupplier::FillBuffer(int64 startFrame, void* buffer,
|
|||||||
if (fSupplier == NULL)
|
if (fSupplier == NULL)
|
||||||
return B_NO_INIT;
|
return B_NO_INIT;
|
||||||
|
|
||||||
if (fUseFrameCaching) {
|
|
||||||
size_t bufferSize = format.display.bytes_per_row
|
|
||||||
* format.display.line_count;
|
|
||||||
if (fCachedFrame == NULL || fCachedFrameSize != bufferSize) {
|
|
||||||
// realloc cached frame
|
|
||||||
fCachedFrameValid = false;
|
|
||||||
void* cachedFrame = realloc(fCachedFrame, bufferSize);
|
|
||||||
if (cachedFrame != NULL) {
|
|
||||||
fCachedFrame = cachedFrame,
|
|
||||||
fCachedFrameSize = bufferSize;
|
|
||||||
} else
|
|
||||||
fUseFrameCaching = false;
|
|
||||||
fCachedFrameValid = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fSupplier->CurrentFrame() == startFrame + 1) {
|
if (fSupplier->CurrentFrame() == startFrame + 1) {
|
||||||
if (fCachedFrameValid) {
|
wasCached = true;
|
||||||
memcpy(buffer, fCachedFrame, fCachedFrameSize);
|
return B_OK;
|
||||||
wasCached = true;
|
|
||||||
return B_OK;
|
|
||||||
}
|
|
||||||
// TODO: The problem here is hidden in PlaybackManager::_PushState()
|
|
||||||
// not computing the correct current_frame for the new PlayingState.
|
|
||||||
printf("ProxyVideoSupplier::FillBuffer(%lld) - TODO: Avoid "
|
|
||||||
"asking for the same frame twice (%lld)!\n", startFrame,
|
|
||||||
fSupplier->CurrentFrame());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
wasCached = false;
|
||||||
status_t ret = B_OK;
|
status_t ret = B_OK;
|
||||||
bigtime_t performanceTime = 0;
|
bigtime_t performanceTime = 0;
|
||||||
if (fSupplier->CurrentFrame() != startFrame) {
|
if (fSupplier->CurrentFrame() != startFrame) {
|
||||||
@ -97,11 +69,6 @@ ProxyVideoSupplier::FillBuffer(int64 startFrame, void* buffer,
|
|||||||
|
|
||||||
ret = fSupplier->ReadFrame(buffer, &performanceTime, format, wasCached);
|
ret = fSupplier->ReadFrame(buffer, &performanceTime, format, wasCached);
|
||||||
|
|
||||||
if (fUseFrameCaching && ret == B_OK) {
|
|
||||||
memcpy(fCachedFrame, buffer, fCachedFrameSize);
|
|
||||||
fCachedFrameValid = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
fProcessingLatency = system_time() - now;
|
fProcessingLatency = system_time() - now;
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
|
@ -30,11 +30,6 @@ private:
|
|||||||
BLocker fSupplierLock;
|
BLocker fSupplierLock;
|
||||||
|
|
||||||
VideoTrackSupplier* fSupplier;
|
VideoTrackSupplier* fSupplier;
|
||||||
|
|
||||||
void* fCachedFrame;
|
|
||||||
size_t fCachedFrameSize;
|
|
||||||
bool fCachedFrameValid;
|
|
||||||
bool fUseFrameCaching;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // PROXY_VIDEO_SUPPLIER_H
|
#endif // PROXY_VIDEO_SUPPLIER_H
|
||||||
|
Loading…
Reference in New Issue
Block a user