mirror of
https://github.com/love2d/love.git
synced 2026-08-19 12:14:20 +02:00
Split theora::VideoStream into OggDemuxer and TheoraVideoStream
This makes the internal API more modular, but the split also forces a cleaner implementation. It also means the OggDemuxer is now responsible for the ogg-specific stuff like seeking and (ogg-specific) type detection. Backported from the love.video-vpx branch of love-experiments --HG-- branch : minor
This commit is contained in:
+4
-2
@@ -1048,8 +1048,10 @@ set(LOVE_SRC_MODULE_VIDEO_ROOT
|
|||||||
set(LOVE_SRC_MODULE_VIDEO_THEORA
|
set(LOVE_SRC_MODULE_VIDEO_THEORA
|
||||||
src/modules/video/theora/Video.cpp
|
src/modules/video/theora/Video.cpp
|
||||||
src/modules/video/theora/Video.h
|
src/modules/video/theora/Video.h
|
||||||
src/modules/video/theora/VideoStream.cpp
|
src/modules/video/theora/OggDemuxer.cpp
|
||||||
src/modules/video/theora/VideoStream.h
|
src/modules/video/theora/OggDemuxer.h
|
||||||
|
src/modules/video/theora/TheoraVideoStream.cpp
|
||||||
|
src/modules/video/theora/TheoraVideoStream.h
|
||||||
)
|
)
|
||||||
|
|
||||||
set(LOVE_SRC_MODULE_VIDEO
|
set(LOVE_SRC_MODULE_VIDEO
|
||||||
|
|||||||
@@ -0,0 +1,242 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2006-2016 LOVE Development Team
|
||||||
|
*
|
||||||
|
* This software is provided 'as-is', without any express or implied
|
||||||
|
* warranty. In no event will the authors be held liable for any damages
|
||||||
|
* arising from the use of this software.
|
||||||
|
*
|
||||||
|
* Permission is granted to anyone to use this software for any purpose,
|
||||||
|
* including commercial applications, and to alter it and redistribute it
|
||||||
|
* freely, subject to the following restrictions:
|
||||||
|
*
|
||||||
|
* 1. The origin of this software must not be misrepresented; you must not
|
||||||
|
* claim that you wrote the original software. If you use this software
|
||||||
|
* in a product, an acknowledgment in the product documentation would be
|
||||||
|
* appreciated but is not required.
|
||||||
|
* 2. Altered source versions must be plainly marked as such, and must not be
|
||||||
|
* misrepresented as being the original software.
|
||||||
|
* 3. This notice may not be removed or altered from any source distribution.
|
||||||
|
**/
|
||||||
|
|
||||||
|
#include "OggDemuxer.h"
|
||||||
|
|
||||||
|
namespace love
|
||||||
|
{
|
||||||
|
namespace video
|
||||||
|
{
|
||||||
|
namespace theora
|
||||||
|
{
|
||||||
|
|
||||||
|
OggDemuxer::OggDemuxer(love::filesystem::File *file)
|
||||||
|
: file(file)
|
||||||
|
, streamInited(false)
|
||||||
|
, videoSerial(0)
|
||||||
|
{
|
||||||
|
ogg_sync_init(&sync);
|
||||||
|
}
|
||||||
|
|
||||||
|
OggDemuxer::~OggDemuxer()
|
||||||
|
{
|
||||||
|
if (streamInited)
|
||||||
|
ogg_stream_clear(&stream);
|
||||||
|
ogg_sync_clear(&sync);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OggDemuxer::readPage()
|
||||||
|
{
|
||||||
|
char *syncBuffer = nullptr;
|
||||||
|
while (ogg_sync_pageout(&sync, &page) != 1)
|
||||||
|
{
|
||||||
|
if (syncBuffer && !streamInited && ogg_stream_check(&stream))
|
||||||
|
throw love::Exception("Invalid stream");
|
||||||
|
|
||||||
|
syncBuffer = ogg_sync_buffer(&sync, 8192);
|
||||||
|
size_t read = file->read(syncBuffer, 8192);
|
||||||
|
ogg_sync_wrote(&sync, read);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OggDemuxer::readPacket(ogg_packet &packet, bool mustSucceed)
|
||||||
|
{
|
||||||
|
if (!streamInited)
|
||||||
|
throw love::Exception("Reading from OggDemuxer before initialization (engine bug)");
|
||||||
|
|
||||||
|
while (ogg_stream_packetout(&stream, &packet) != 1)
|
||||||
|
{
|
||||||
|
do
|
||||||
|
{
|
||||||
|
// We need to read another page, but there is none, we're at the end
|
||||||
|
if (ogg_page_eos(&page) && !mustSucceed)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
readPage();
|
||||||
|
} while (ogg_page_serialno(&page) != videoSerial);
|
||||||
|
|
||||||
|
ogg_stream_pagein(&stream, &page);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OggDemuxer::resync()
|
||||||
|
{
|
||||||
|
ogg_sync_reset(&sync);
|
||||||
|
ogg_sync_pageseek(&sync, &page);
|
||||||
|
ogg_stream_reset(&stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OggDemuxer::isEos() const
|
||||||
|
{
|
||||||
|
return ogg_page_eos(&page);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string &OggDemuxer::getFilename() const
|
||||||
|
{
|
||||||
|
return file->getFilename();
|
||||||
|
}
|
||||||
|
|
||||||
|
OggDemuxer::StreamType OggDemuxer::determineType()
|
||||||
|
{
|
||||||
|
ogg_packet packet;
|
||||||
|
if (ogg_stream_packetpeek(&stream, &packet) != 1)
|
||||||
|
return TYPE_UNKNOWN;
|
||||||
|
|
||||||
|
// Theora
|
||||||
|
// See https://www.theora.org/doc/Theora.pdf section 6.1
|
||||||
|
if (packet.bytes >= 7) {
|
||||||
|
uint8_t headerType = packet.packet[0];
|
||||||
|
if (headerType & 0x80 && std::strncmp((const char*) packet.packet+1, "theora", 6) == 0)
|
||||||
|
return TYPE_THEORA;
|
||||||
|
}
|
||||||
|
|
||||||
|
return TYPE_UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
OggDemuxer::StreamType OggDemuxer::findStream()
|
||||||
|
{
|
||||||
|
if (streamInited)
|
||||||
|
{
|
||||||
|
file->seek(0);
|
||||||
|
ogg_stream_clear(&stream);
|
||||||
|
ogg_sync_reset(&sync);
|
||||||
|
}
|
||||||
|
|
||||||
|
streamInited = true;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
// If this page isn't at the start of a stream, we've seen all streams
|
||||||
|
readPage();
|
||||||
|
if (!ogg_page_bos(&page))
|
||||||
|
break;
|
||||||
|
|
||||||
|
videoSerial = ogg_page_serialno(&page);
|
||||||
|
ogg_stream_init(&stream, videoSerial);
|
||||||
|
ogg_stream_pagein(&stream, &page);
|
||||||
|
|
||||||
|
StreamType type = determineType();
|
||||||
|
switch(type)
|
||||||
|
{
|
||||||
|
case TYPE_THEORA:
|
||||||
|
return type;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
ogg_stream_clear(&stream);
|
||||||
|
ogg_sync_reset(&sync);
|
||||||
|
}
|
||||||
|
|
||||||
|
streamInited = false;
|
||||||
|
ogg_stream_clear(&stream);
|
||||||
|
ogg_sync_reset(&sync);
|
||||||
|
|
||||||
|
return TYPE_UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OggDemuxer::seek(ogg_packet &packet, double target, std::function<double(int64)> getTime)
|
||||||
|
{
|
||||||
|
static const double rewindThreshold = 0.01;
|
||||||
|
|
||||||
|
if (target < rewindThreshold)
|
||||||
|
{
|
||||||
|
file->seek(0);
|
||||||
|
resync();
|
||||||
|
readPacket(packet, true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
double low = 0;
|
||||||
|
double high = file->getSize();
|
||||||
|
|
||||||
|
while (high-low > rewindThreshold)
|
||||||
|
{
|
||||||
|
// Determine our next binary search position
|
||||||
|
double pos = (high+low)/2;
|
||||||
|
file->seek(pos);
|
||||||
|
|
||||||
|
// Break sync
|
||||||
|
resync();
|
||||||
|
|
||||||
|
// Read a page
|
||||||
|
readPage();
|
||||||
|
readPacket(packet, false);
|
||||||
|
if (isEos())
|
||||||
|
{
|
||||||
|
// EOS, so we're definitely past our target (or the target is past
|
||||||
|
// the end)
|
||||||
|
high = pos;
|
||||||
|
|
||||||
|
// And a workaround for single-page files:
|
||||||
|
if (high < rewindThreshold)
|
||||||
|
{
|
||||||
|
file->seek(0);
|
||||||
|
resync();
|
||||||
|
readPacket(packet, true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now search all packets in this page
|
||||||
|
int result = -1;
|
||||||
|
for (int i = 0; i < ogg_page_packets(&page); ++i)
|
||||||
|
{
|
||||||
|
if (i > 0)
|
||||||
|
readPacket(packet, true);
|
||||||
|
|
||||||
|
// Determine if this is the right place
|
||||||
|
double curTime = getTime(packet.granulepos);
|
||||||
|
double nextTime = getTime(packet.granulepos+1);
|
||||||
|
|
||||||
|
if (curTime == -1)
|
||||||
|
continue; // Invalid granule position (magic?)
|
||||||
|
else if (curTime <= target && nextTime > target)
|
||||||
|
{
|
||||||
|
// the current frame should be displaying right now
|
||||||
|
result = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (curTime > target)
|
||||||
|
{
|
||||||
|
// No need to check the other packets, they're all past
|
||||||
|
// this one
|
||||||
|
result = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The sign of result determines the direction
|
||||||
|
if (result == 0)
|
||||||
|
break;
|
||||||
|
else if (result < 0)
|
||||||
|
low = pos;
|
||||||
|
else
|
||||||
|
high = pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // theora
|
||||||
|
} // video
|
||||||
|
} // love
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2006-2016 LOVE Development Team
|
||||||
|
*
|
||||||
|
* This software is provided 'as-is', without any express or implied
|
||||||
|
* warranty. In no event will the authors be held liable for any damages
|
||||||
|
* arising from the use of this software.
|
||||||
|
*
|
||||||
|
* Permission is granted to anyone to use this software for any purpose,
|
||||||
|
* including commercial applications, and to alter it and redistribute it
|
||||||
|
* freely, subject to the following restrictions:
|
||||||
|
*
|
||||||
|
* 1. The origin of this software must not be misrepresented; you must not
|
||||||
|
* claim that you wrote the original software. If you use this software
|
||||||
|
* in a product, an acknowledgment in the product documentation would be
|
||||||
|
* appreciated but is not required.
|
||||||
|
* 2. Altered source versions must be plainly marked as such, and must not be
|
||||||
|
* misrepresented as being the original software.
|
||||||
|
* 3. This notice may not be removed or altered from any source distribution.
|
||||||
|
**/
|
||||||
|
|
||||||
|
#ifndef LOVE_VIDEO_THEORA_OGGDEMUXER_H
|
||||||
|
#define LOVE_VIDEO_THEORA_OGGDEMUXER_H
|
||||||
|
|
||||||
|
// STL
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
// LOVE
|
||||||
|
#include "filesystem/File.h"
|
||||||
|
|
||||||
|
// OGG
|
||||||
|
#include <ogg/ogg.h>
|
||||||
|
|
||||||
|
namespace love
|
||||||
|
{
|
||||||
|
namespace video
|
||||||
|
{
|
||||||
|
namespace theora
|
||||||
|
{
|
||||||
|
|
||||||
|
class OggDemuxer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum StreamType
|
||||||
|
{
|
||||||
|
TYPE_THEORA,
|
||||||
|
TYPE_UNKNOWN,
|
||||||
|
};
|
||||||
|
|
||||||
|
OggDemuxer(love::filesystem::File *file);
|
||||||
|
~OggDemuxer();
|
||||||
|
|
||||||
|
StreamType findStream();
|
||||||
|
bool readPacket(ogg_packet &packet, bool mustSucceed = false);
|
||||||
|
void resync();
|
||||||
|
bool isEos() const;
|
||||||
|
const std::string &getFilename() const;
|
||||||
|
bool seek(ogg_packet &packet, double target, std::function<double(int64)> getTime);
|
||||||
|
|
||||||
|
private:
|
||||||
|
StrongRef<love::filesystem::File> file;
|
||||||
|
|
||||||
|
ogg_sync_state sync;
|
||||||
|
ogg_stream_state stream;
|
||||||
|
ogg_page page;
|
||||||
|
|
||||||
|
bool streamInited;
|
||||||
|
int videoSerial;
|
||||||
|
|
||||||
|
void readPage();
|
||||||
|
StreamType determineType();
|
||||||
|
}; // OggDemuxer
|
||||||
|
|
||||||
|
} // theora
|
||||||
|
} // video
|
||||||
|
} // love
|
||||||
|
|
||||||
|
#endif // LOVE_VIDEO_THEORA_OGGDEMUXER_H
|
||||||
+32
-178
@@ -22,7 +22,7 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
// LOVE
|
// LOVE
|
||||||
#include "VideoStream.h"
|
#include "TheoraVideoStream.h"
|
||||||
|
|
||||||
using love::filesystem::File;
|
using love::filesystem::File;
|
||||||
|
|
||||||
@@ -33,19 +33,18 @@ namespace video
|
|||||||
namespace theora
|
namespace theora
|
||||||
{
|
{
|
||||||
|
|
||||||
VideoStream::VideoStream(love::filesystem::File *file)
|
TheoraVideoStream::TheoraVideoStream(love::filesystem::File *file)
|
||||||
: file(file)
|
: demuxer(file)
|
||||||
, headerParsed(false)
|
, headerParsed(false)
|
||||||
, streamInited(false)
|
|
||||||
, videoSerial(0)
|
|
||||||
, decoder(nullptr)
|
, decoder(nullptr)
|
||||||
, frameReady(false)
|
, frameReady(false)
|
||||||
, lastFrame(0)
|
, lastFrame(0)
|
||||||
, nextFrame(0)
|
, nextFrame(0)
|
||||||
, eos(false)
|
|
||||||
, lagCounter(0)
|
, lagCounter(0)
|
||||||
{
|
{
|
||||||
ogg_sync_init(&sync);
|
if (demuxer.findStream() != OggDemuxer::TYPE_THEORA)
|
||||||
|
throw love::Exception("Invalid video file, video is not theora");
|
||||||
|
|
||||||
th_info_init(&videoInfo);
|
th_info_init(&videoInfo);
|
||||||
|
|
||||||
frontBuffer = new Frame();
|
frontBuffer = new Frame();
|
||||||
@@ -60,29 +59,24 @@ VideoStream::VideoStream(love::filesystem::File *file)
|
|||||||
delete backBuffer;
|
delete backBuffer;
|
||||||
delete frontBuffer;
|
delete frontBuffer;
|
||||||
th_info_clear(&videoInfo);
|
th_info_clear(&videoInfo);
|
||||||
ogg_sync_clear(&sync);
|
|
||||||
throw ex;
|
throw ex;
|
||||||
}
|
}
|
||||||
|
|
||||||
frameSync.set(new DeltaSync(), Acquire::NORETAIN);
|
frameSync.set(new DeltaSync(), Acquire::NORETAIN);
|
||||||
}
|
}
|
||||||
|
|
||||||
VideoStream::~VideoStream()
|
TheoraVideoStream::~TheoraVideoStream()
|
||||||
{
|
{
|
||||||
if (decoder)
|
if (decoder)
|
||||||
th_decode_free(decoder);
|
th_decode_free(decoder);
|
||||||
|
|
||||||
th_info_clear(&videoInfo);
|
th_info_clear(&videoInfo);
|
||||||
if (headerParsed)
|
|
||||||
ogg_stream_clear(&stream);
|
|
||||||
|
|
||||||
ogg_sync_clear(&sync);
|
|
||||||
|
|
||||||
delete frontBuffer;
|
delete frontBuffer;
|
||||||
delete backBuffer;
|
delete backBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
int VideoStream::getWidth() const
|
int TheoraVideoStream::getWidth() const
|
||||||
{
|
{
|
||||||
if (headerParsed)
|
if (headerParsed)
|
||||||
return videoInfo.pic_width;
|
return videoInfo.pic_width;
|
||||||
@@ -90,7 +84,7 @@ int VideoStream::getWidth() const
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int VideoStream::getHeight() const
|
int TheoraVideoStream::getHeight() const
|
||||||
{
|
{
|
||||||
if (headerParsed)
|
if (headerParsed)
|
||||||
return videoInfo.pic_height;
|
return videoInfo.pic_height;
|
||||||
@@ -98,72 +92,30 @@ int VideoStream::getHeight() const
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string &VideoStream::getFilename() const
|
const std::string &TheoraVideoStream::getFilename() const
|
||||||
{
|
{
|
||||||
return file->getFilename();
|
return demuxer.getFilename();
|
||||||
}
|
}
|
||||||
|
|
||||||
void VideoStream::setSync(FrameSync *frameSync)
|
void TheoraVideoStream::setSync(FrameSync *frameSync)
|
||||||
{
|
{
|
||||||
love::thread::Lock l(bufferMutex);
|
love::thread::Lock l(bufferMutex);
|
||||||
this->frameSync = frameSync;
|
this->frameSync = frameSync;
|
||||||
}
|
}
|
||||||
|
|
||||||
const void *VideoStream::getFrontBuffer() const
|
const void *TheoraVideoStream::getFrontBuffer() const
|
||||||
{
|
{
|
||||||
return frontBuffer;
|
return frontBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t VideoStream::getSize() const
|
size_t TheoraVideoStream::getSize() const
|
||||||
{
|
{
|
||||||
return sizeof(Frame);
|
return sizeof(Frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VideoStream::isPlaying() const
|
bool TheoraVideoStream::isPlaying() const
|
||||||
{
|
{
|
||||||
return frameSync->isPlaying() && !eos;
|
return frameSync->isPlaying() && !demuxer.isEos();
|
||||||
}
|
|
||||||
|
|
||||||
void VideoStream::readPage()
|
|
||||||
{
|
|
||||||
char *syncBuffer = nullptr;
|
|
||||||
while (ogg_sync_pageout(&sync, &page) != 1)
|
|
||||||
{
|
|
||||||
if (syncBuffer && !headerParsed && ogg_stream_check(&stream))
|
|
||||||
throw love::Exception("Invalid stream");
|
|
||||||
|
|
||||||
syncBuffer = ogg_sync_buffer(&sync, 8192);
|
|
||||||
size_t read = file->read(syncBuffer, 8192);
|
|
||||||
ogg_sync_wrote(&sync, read);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool VideoStream::readPacket(bool mustSucceed)
|
|
||||||
{
|
|
||||||
if (!streamInited)
|
|
||||||
{
|
|
||||||
readPage();
|
|
||||||
videoSerial = ogg_page_serialno(&page);
|
|
||||||
ogg_stream_init(&stream, videoSerial);
|
|
||||||
streamInited = true;
|
|
||||||
ogg_stream_pagein(&stream, &page);
|
|
||||||
}
|
|
||||||
|
|
||||||
while (ogg_stream_packetout(&stream, &packet) != 1)
|
|
||||||
{
|
|
||||||
do
|
|
||||||
{
|
|
||||||
// We need to read another page, but there is none, we're at the end
|
|
||||||
if (ogg_page_eos(&page) && !mustSucceed)
|
|
||||||
return eos = true;
|
|
||||||
|
|
||||||
readPage();
|
|
||||||
} while (ogg_page_serialno(&page) != videoSerial);
|
|
||||||
|
|
||||||
ogg_stream_pagein(&stream, &page);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
@@ -181,7 +133,7 @@ inline void scaleFormat(th_pixel_fmt fmt, T &x, T &y)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void VideoStream::parseHeader()
|
void TheoraVideoStream::parseHeader()
|
||||||
{
|
{
|
||||||
if (headerParsed)
|
if (headerParsed)
|
||||||
return;
|
return;
|
||||||
@@ -191,16 +143,8 @@ void VideoStream::parseHeader()
|
|||||||
th_comment_init(&comment);
|
th_comment_init(&comment);
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
do
|
demuxer.readPacket(packet);
|
||||||
{
|
ret = th_decode_headerin(&videoInfo, &comment, &setupInfo, &packet);
|
||||||
readPacket();
|
|
||||||
ret = th_decode_headerin(&videoInfo, &comment, &setupInfo, &packet);
|
|
||||||
if (ret == TH_ENOTFORMAT)
|
|
||||||
{
|
|
||||||
ogg_stream_clear(&stream);
|
|
||||||
streamInited = false;
|
|
||||||
}
|
|
||||||
} while(ret < 0 && !ogg_page_eos(&page));
|
|
||||||
|
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
@@ -210,7 +154,7 @@ void VideoStream::parseHeader()
|
|||||||
|
|
||||||
while (ret > 0)
|
while (ret > 0)
|
||||||
{
|
{
|
||||||
readPacket();
|
demuxer.readPacket(packet);
|
||||||
ret = th_decode_headerin(&videoInfo, &comment, &setupInfo, &packet);
|
ret = th_decode_headerin(&videoInfo, &comment, &setupInfo, &packet);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -246,111 +190,21 @@ void VideoStream::parseHeader()
|
|||||||
th_decode_packetin(decoder, &packet, nullptr);
|
th_decode_packetin(decoder, &packet, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Arbitrary seeking isn't supported yet, but rewinding is
|
void TheoraVideoStream::seekDecoder(double target)
|
||||||
void VideoStream::rewind()
|
|
||||||
{
|
{
|
||||||
// Seek our data stream back to the start
|
bool success = demuxer.seek(packet, target, [this](int64 granulepos) {
|
||||||
file->seek(0);
|
return th_granule_time(decoder, granulepos);
|
||||||
|
});
|
||||||
|
|
||||||
// Break our sync, and discard the rest of the page
|
if (!success)
|
||||||
ogg_sync_reset(&sync);
|
|
||||||
ogg_sync_pageseek(&sync, &page);
|
|
||||||
|
|
||||||
// Read our first page/packet from the stream again
|
|
||||||
readPacket(true);
|
|
||||||
|
|
||||||
// Now tell theora we're at frame 1 (not 0!)
|
|
||||||
int64 granPos = 1;
|
|
||||||
th_decode_ctl(decoder, TH_DECCTL_SET_GRANPOS, &granPos, sizeof(granPos));
|
|
||||||
|
|
||||||
// Force a redraw, since this will always be less than the sync's position
|
|
||||||
lastFrame = nextFrame = -1;
|
|
||||||
eos = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void VideoStream::seekDecoder(double target)
|
|
||||||
{
|
|
||||||
const double rewindThreshold = 0.01;
|
|
||||||
|
|
||||||
if (target < rewindThreshold)
|
|
||||||
{
|
|
||||||
rewind();
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
double low = 0;
|
|
||||||
double high = file->getSize();
|
|
||||||
|
|
||||||
while (high-low > rewindThreshold)
|
|
||||||
{
|
|
||||||
// Determine our next binary search position
|
|
||||||
double pos = (high+low)/2;
|
|
||||||
file->seek(pos);
|
|
||||||
|
|
||||||
// Break sync
|
|
||||||
ogg_sync_reset(&sync);
|
|
||||||
ogg_sync_pageseek(&sync, &page);
|
|
||||||
|
|
||||||
// Read a page
|
|
||||||
readPacket(false);
|
|
||||||
if (eos)
|
|
||||||
{
|
|
||||||
// EOS, so we're definitely past our target (or the target is past
|
|
||||||
// the end)
|
|
||||||
high = pos;
|
|
||||||
eos = false;
|
|
||||||
|
|
||||||
// And a workaround for single-page files:
|
|
||||||
if (high < rewindThreshold)
|
|
||||||
rewind();
|
|
||||||
else
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now search all packets in this page
|
|
||||||
int result = -1;
|
|
||||||
for (int i = 0; i < ogg_page_packets(&page); ++i)
|
|
||||||
{
|
|
||||||
if (i > 0)
|
|
||||||
readPacket(true);
|
|
||||||
|
|
||||||
// Determine if this is the right place
|
|
||||||
double curTime = th_granule_time(decoder, packet.granulepos);
|
|
||||||
double nextTime = th_granule_time(decoder, packet.granulepos+1);
|
|
||||||
|
|
||||||
if (curTime == -1)
|
|
||||||
continue; // Invalid granule position (magic?)
|
|
||||||
else if (curTime <= target && nextTime > target)
|
|
||||||
{
|
|
||||||
// the current frame should be displaying right now
|
|
||||||
result = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else if (curTime > target)
|
|
||||||
{
|
|
||||||
// No need to check the other packets, they're all past
|
|
||||||
// this one
|
|
||||||
result = 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The sign of result determines the direction
|
|
||||||
if (result == 0)
|
|
||||||
break;
|
|
||||||
else if (result < 0)
|
|
||||||
low = pos;
|
|
||||||
else
|
|
||||||
high = pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now update theora and our decoder on this new position of ours
|
// Now update theora and our decoder on this new position of ours
|
||||||
lastFrame = nextFrame = -1;
|
lastFrame = nextFrame = -1;
|
||||||
eos = false;
|
|
||||||
th_decode_ctl(decoder, TH_DECCTL_SET_GRANPOS, &packet.granulepos, sizeof(packet.granulepos));
|
th_decode_ctl(decoder, TH_DECCTL_SET_GRANPOS, &packet.granulepos, sizeof(packet.granulepos));
|
||||||
}
|
}
|
||||||
|
|
||||||
void VideoStream::threadedFillBackBuffer(double dt)
|
void TheoraVideoStream::threadedFillBackBuffer(double dt)
|
||||||
{
|
{
|
||||||
// Synchronize
|
// Synchronize
|
||||||
frameSync->update(dt);
|
frameSync->update(dt);
|
||||||
@@ -362,7 +216,7 @@ void VideoStream::threadedFillBackBuffer(double dt)
|
|||||||
|
|
||||||
// If we're at the end of the stream, or if we're displaying the right frame
|
// If we're at the end of the stream, or if we're displaying the right frame
|
||||||
// stop here
|
// stop here
|
||||||
if (eos || position < nextFrame)
|
if (demuxer.isEos() || position < nextFrame)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
th_ycbcr_buffer bufferinfo;
|
th_ycbcr_buffer bufferinfo;
|
||||||
@@ -371,7 +225,7 @@ void VideoStream::threadedFillBackBuffer(double dt)
|
|||||||
ogg_int64_t granulePosition;
|
ogg_int64_t granulePosition;
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
if (readPacket())
|
if (demuxer.readPacket(packet))
|
||||||
return;
|
return;
|
||||||
} while (th_decode_packetin(decoder, &packet, &granulePosition) != 0);
|
} while (th_decode_packetin(decoder, &packet, &granulePosition) != 0);
|
||||||
lastFrame = nextFrame;
|
lastFrame = nextFrame;
|
||||||
@@ -413,7 +267,7 @@ void VideoStream::threadedFillBackBuffer(double dt)
|
|||||||
if (position > nextFrame)
|
if (position > nextFrame)
|
||||||
{
|
{
|
||||||
if (++lagCounter > 5)
|
if (++lagCounter > 5)
|
||||||
seek(position);
|
seekDecoder(position);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
lagCounter = 0;
|
lagCounter = 0;
|
||||||
@@ -422,14 +276,14 @@ void VideoStream::threadedFillBackBuffer(double dt)
|
|||||||
frameReady = true;
|
frameReady = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void VideoStream::fillBackBuffer()
|
void TheoraVideoStream::fillBackBuffer()
|
||||||
{
|
{
|
||||||
// Done in worker thread
|
// Done in worker thread
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VideoStream::swapBuffers()
|
bool TheoraVideoStream::swapBuffers()
|
||||||
{
|
{
|
||||||
if (eos)
|
if (demuxer.isEos())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
love::thread::Lock l(bufferMutex);
|
love::thread::Lock l(bufferMutex);
|
||||||
+7
-14
@@ -27,6 +27,7 @@
|
|||||||
#include "common/int.h"
|
#include "common/int.h"
|
||||||
#include "filesystem/File.h"
|
#include "filesystem/File.h"
|
||||||
#include "thread/threads.h"
|
#include "thread/threads.h"
|
||||||
|
#include "OggDemuxer.h"
|
||||||
|
|
||||||
// OGG/Theora
|
// OGG/Theora
|
||||||
#include <ogg/ogg.h>
|
#include <ogg/ogg.h>
|
||||||
@@ -40,11 +41,11 @@ namespace video
|
|||||||
namespace theora
|
namespace theora
|
||||||
{
|
{
|
||||||
|
|
||||||
class VideoStream : public love::video::VideoStream
|
class TheoraVideoStream : public love::video::VideoStream
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
VideoStream(love::filesystem::File *file);
|
TheoraVideoStream(love::filesystem::File *file);
|
||||||
~VideoStream();
|
~TheoraVideoStream();
|
||||||
|
|
||||||
const void *getFrontBuffer() const;
|
const void *getFrontBuffer() const;
|
||||||
size_t getSize() const;
|
size_t getSize() const;
|
||||||
@@ -61,14 +62,10 @@ public:
|
|||||||
void threadedFillBackBuffer(double dt);
|
void threadedFillBackBuffer(double dt);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
StrongRef<love::filesystem::File> file;
|
OggDemuxer demuxer;
|
||||||
|
|
||||||
bool headerParsed;
|
bool headerParsed;
|
||||||
bool streamInited;
|
|
||||||
int videoSerial;
|
|
||||||
ogg_sync_state sync;
|
|
||||||
ogg_stream_state stream;
|
|
||||||
ogg_page page;
|
|
||||||
ogg_packet packet;
|
ogg_packet packet;
|
||||||
|
|
||||||
th_info videoInfo;
|
th_info videoInfo;
|
||||||
@@ -86,15 +83,11 @@ private:
|
|||||||
|
|
||||||
double lastFrame;
|
double lastFrame;
|
||||||
double nextFrame;
|
double nextFrame;
|
||||||
bool eos;
|
|
||||||
unsigned int lagCounter;
|
unsigned int lagCounter;
|
||||||
|
|
||||||
void readPage();
|
|
||||||
bool readPacket(bool mustSucceed = false); // true if eos
|
|
||||||
void parseHeader();
|
void parseHeader();
|
||||||
void rewind();
|
|
||||||
void seekDecoder(double target);
|
void seekDecoder(double target);
|
||||||
}; // VideoStream
|
}; // TheoraVideoStream
|
||||||
|
|
||||||
} // theora
|
} // theora
|
||||||
} // video
|
} // video
|
||||||
@@ -46,7 +46,7 @@ Video::~Video()
|
|||||||
|
|
||||||
VideoStream *Video::newVideoStream(love::filesystem::File *file)
|
VideoStream *Video::newVideoStream(love::filesystem::File *file)
|
||||||
{
|
{
|
||||||
VideoStream *stream = new VideoStream(file);
|
TheoraVideoStream *stream = new TheoraVideoStream(file);
|
||||||
workerThread->addStream(stream);
|
workerThread->addStream(stream);
|
||||||
return stream;
|
return stream;
|
||||||
}
|
}
|
||||||
@@ -67,7 +67,7 @@ Worker::~Worker()
|
|||||||
stop();
|
stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Worker::addStream(VideoStream *stream)
|
void Worker::addStream(TheoraVideoStream *stream)
|
||||||
{
|
{
|
||||||
love::thread::Lock l(mutex);
|
love::thread::Lock l(mutex);
|
||||||
streams.push_back(stream);
|
streams.push_back(stream);
|
||||||
@@ -110,7 +110,7 @@ void Worker::threadFunction()
|
|||||||
|
|
||||||
for (auto it = streams.begin(); it != streams.end(); ++it)
|
for (auto it = streams.begin(); it != streams.end(); ++it)
|
||||||
{
|
{
|
||||||
VideoStream *stream = *it;
|
TheoraVideoStream *stream = *it;
|
||||||
if (stream->getReferenceCount() == 1)
|
if (stream->getReferenceCount() == 1)
|
||||||
{
|
{
|
||||||
// We're the only ones left
|
// We're the only ones left
|
||||||
|
|||||||
@@ -28,7 +28,8 @@
|
|||||||
#include "filesystem/File.h"
|
#include "filesystem/File.h"
|
||||||
#include "video/Video.h"
|
#include "video/Video.h"
|
||||||
#include "thread/threads.h"
|
#include "thread/threads.h"
|
||||||
#include "VideoStream.h"
|
#include "video/VideoStream.h"
|
||||||
|
#include "TheoraVideoStream.h"
|
||||||
|
|
||||||
namespace love
|
namespace love
|
||||||
{
|
{
|
||||||
@@ -63,13 +64,13 @@ public:
|
|||||||
// Implements Threadable
|
// Implements Threadable
|
||||||
void threadFunction();
|
void threadFunction();
|
||||||
|
|
||||||
void addStream(VideoStream *stream);
|
void addStream(TheoraVideoStream *stream);
|
||||||
// Frees itself!
|
// Frees itself!
|
||||||
void stop();
|
void stop();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
std::vector<StrongRef<VideoStream>> streams;
|
std::vector<StrongRef<TheoraVideoStream>> streams;
|
||||||
|
|
||||||
love::thread::MutexRef mutex;
|
love::thread::MutexRef mutex;
|
||||||
love::thread::ConditionalRef cond;
|
love::thread::ConditionalRef cond;
|
||||||
|
|||||||
Reference in New Issue
Block a user