mirror of
https://github.com/love2d/love.git
synced 2026-08-15 15:51:12 +02:00
Add initial video playback support for Ogg Theora videos (resolves issue #66.)
The basic APIs are:
video = love.graphics.newVideo("myvideo.ogv")
love.graphics.draw(video, ...) -- Video objects are Drawables.
video:play(), video:pause()
video:getDuration(), video:tell(), video:rewind(), video:seek(seconds)
video:getSource()
video:getWidth(), video:getHeight(), video:setFilter(min, mag)
More advanced APIs include video:setSource(source), video:getStream(), and videostream:setSync.
To use a custom pixel shader when drawing a Video, call the new TexelVideo(texcoords) function instead of Texel(texture, texcoords) in order to get the pixel colors of a video frame.
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Copyright (c) 2006-2015 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_VIDEO_H
|
||||
#define LOVE_VIDEO_VIDEO_H
|
||||
|
||||
// LOVE
|
||||
#include "common/Module.h"
|
||||
#include "common/Stream.h"
|
||||
#include "filesystem/File.h"
|
||||
|
||||
#include "VideoStream.h"
|
||||
|
||||
namespace love
|
||||
{
|
||||
namespace video
|
||||
{
|
||||
|
||||
class Video : public Module
|
||||
{
|
||||
public:
|
||||
virtual ~Video() {}
|
||||
|
||||
// Implements Module
|
||||
virtual ModuleType getModuleType() const { return M_VIDEO; }
|
||||
|
||||
/**
|
||||
* Create a VideoStream representing video frames
|
||||
**/
|
||||
virtual VideoStream *newVideoStream(love::filesystem::File *file) = 0;
|
||||
}; // Video
|
||||
|
||||
} // video
|
||||
} // love
|
||||
|
||||
#endif // LOVE_VIDEO_VIDEO_H
|
||||
@@ -0,0 +1,168 @@
|
||||
/**
|
||||
* Copyright (c) 2006-2015 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 "VideoStream.h"
|
||||
|
||||
using love::thread::Lock;
|
||||
|
||||
namespace love
|
||||
{
|
||||
namespace video
|
||||
{
|
||||
|
||||
void VideoStream::setSync(VideoStream::FrameSync *frameSync)
|
||||
{
|
||||
this->frameSync = frameSync;
|
||||
}
|
||||
|
||||
VideoStream::FrameSync *VideoStream::getSync() const
|
||||
{
|
||||
return frameSync;
|
||||
}
|
||||
|
||||
void VideoStream::play()
|
||||
{
|
||||
frameSync->play();
|
||||
}
|
||||
|
||||
void VideoStream::pause()
|
||||
{
|
||||
frameSync->pause();
|
||||
}
|
||||
|
||||
void VideoStream::seek(double offset)
|
||||
{
|
||||
frameSync->seek(offset);
|
||||
}
|
||||
|
||||
double VideoStream::tell()
|
||||
{
|
||||
return frameSync->tell();
|
||||
}
|
||||
|
||||
bool VideoStream::isPlaying()
|
||||
{
|
||||
return frameSync->isPlaying();
|
||||
}
|
||||
|
||||
VideoStream::Frame::Frame()
|
||||
: yplane(nullptr)
|
||||
, cbplane(nullptr)
|
||||
, crplane(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
VideoStream::Frame::~Frame()
|
||||
{
|
||||
delete[] yplane;
|
||||
delete[] cbplane;
|
||||
delete[] crplane;
|
||||
}
|
||||
|
||||
void VideoStream::FrameSync::copyState(const VideoStream::FrameSync *other)
|
||||
{
|
||||
seek(other->tell());
|
||||
if (other->isPlaying())
|
||||
play();
|
||||
else
|
||||
pause();
|
||||
}
|
||||
|
||||
double VideoStream::FrameSync::tell() const
|
||||
{
|
||||
return getPosition();
|
||||
}
|
||||
|
||||
VideoStream::DeltaSync::DeltaSync()
|
||||
: playing(false)
|
||||
, position(0)
|
||||
, speed(1)
|
||||
{
|
||||
}
|
||||
|
||||
VideoStream::DeltaSync::~DeltaSync()
|
||||
{
|
||||
}
|
||||
|
||||
double VideoStream::DeltaSync::getPosition() const
|
||||
{
|
||||
return position;
|
||||
}
|
||||
|
||||
void VideoStream::DeltaSync::update(double dt)
|
||||
{
|
||||
Lock l(mutex);
|
||||
if (playing)
|
||||
position += dt*speed;
|
||||
}
|
||||
|
||||
void VideoStream::DeltaSync::play()
|
||||
{
|
||||
playing = true;
|
||||
}
|
||||
|
||||
void VideoStream::DeltaSync::pause()
|
||||
{
|
||||
playing = false;
|
||||
}
|
||||
|
||||
void VideoStream::DeltaSync::seek(double time)
|
||||
{
|
||||
Lock l(mutex);
|
||||
position = time;
|
||||
}
|
||||
|
||||
bool VideoStream::DeltaSync::isPlaying() const
|
||||
{
|
||||
return playing;
|
||||
}
|
||||
|
||||
VideoStream::SourceSync::SourceSync(love::audio::Source *source)
|
||||
: source(source)
|
||||
{
|
||||
}
|
||||
|
||||
double VideoStream::SourceSync::getPosition() const
|
||||
{
|
||||
return source->tell(love::audio::Source::UNIT_SECONDS);
|
||||
}
|
||||
|
||||
void VideoStream::SourceSync::play()
|
||||
{
|
||||
source->play();
|
||||
}
|
||||
|
||||
void VideoStream::SourceSync::pause()
|
||||
{
|
||||
source->pause();
|
||||
}
|
||||
|
||||
void VideoStream::SourceSync::seek(double time)
|
||||
{
|
||||
source->seek(time, love::audio::Source::UNIT_SECONDS);
|
||||
}
|
||||
|
||||
bool VideoStream::SourceSync::isPlaying() const
|
||||
{
|
||||
return !source->isStopped() && !source->isPaused();
|
||||
}
|
||||
|
||||
} // video
|
||||
} // love
|
||||
@@ -0,0 +1,131 @@
|
||||
/**
|
||||
* Copyright (c) 2006-2015 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_VIDEOSTREAM_H
|
||||
#define LOVE_VIDEO_VIDEOSTREAM_H
|
||||
|
||||
// LOVE
|
||||
#include "common/Stream.h"
|
||||
#include "audio/Source.h"
|
||||
#include "thread/threads.h"
|
||||
|
||||
namespace love
|
||||
{
|
||||
namespace video
|
||||
{
|
||||
|
||||
class VideoStream : public Stream
|
||||
{
|
||||
public:
|
||||
virtual ~VideoStream() {}
|
||||
|
||||
virtual int getWidth() const = 0;
|
||||
virtual int getHeight() const = 0;
|
||||
virtual const std::string &getFilename() const = 0;
|
||||
|
||||
// Playback api
|
||||
virtual void play();
|
||||
virtual void pause();
|
||||
virtual void seek(double offset);
|
||||
virtual double tell();
|
||||
virtual bool isPlaying();
|
||||
|
||||
class FrameSync;
|
||||
class DeltaSync;
|
||||
|
||||
// The stream now owns the sync, do not reuse or free
|
||||
virtual void setSync(FrameSync *frameSync);
|
||||
virtual FrameSync *getSync() const;
|
||||
|
||||
// Data structures
|
||||
struct Frame
|
||||
{
|
||||
Frame();
|
||||
~Frame();
|
||||
|
||||
int yw, yh;
|
||||
unsigned char *yplane;
|
||||
|
||||
int cw, ch;
|
||||
unsigned char *cbplane;
|
||||
unsigned char *crplane;
|
||||
};
|
||||
|
||||
class FrameSync : public Object
|
||||
{
|
||||
public:
|
||||
virtual double getPosition() const = 0;
|
||||
virtual void update(double /*dt*/) {}
|
||||
virtual ~FrameSync() {}
|
||||
|
||||
void copyState(const FrameSync *other);
|
||||
|
||||
// Playback api
|
||||
virtual void play() = 0;
|
||||
virtual void pause() = 0;
|
||||
virtual void seek(double offset) = 0;
|
||||
virtual double tell() const;
|
||||
virtual bool isPlaying() const = 0;
|
||||
};
|
||||
|
||||
class DeltaSync : public FrameSync
|
||||
{
|
||||
public:
|
||||
DeltaSync();
|
||||
~DeltaSync();
|
||||
|
||||
virtual double getPosition() const override;
|
||||
virtual void update(double dt) override;
|
||||
|
||||
virtual void play() override;
|
||||
virtual void pause() override;
|
||||
virtual void seek(double time) override;
|
||||
virtual bool isPlaying() const override;
|
||||
|
||||
private:
|
||||
bool playing;
|
||||
double position;
|
||||
double speed;
|
||||
love::thread::MutexRef mutex;
|
||||
};
|
||||
|
||||
class SourceSync : public FrameSync
|
||||
{
|
||||
public:
|
||||
SourceSync(love::audio::Source *source);
|
||||
|
||||
virtual double getPosition() const override;
|
||||
virtual void play() override;
|
||||
virtual void pause() override;
|
||||
virtual void seek(double time) override;
|
||||
virtual bool isPlaying() const override;
|
||||
|
||||
private:
|
||||
StrongRef<love::audio::Source> source;
|
||||
};
|
||||
|
||||
protected:
|
||||
StrongRef<FrameSync> frameSync;
|
||||
};
|
||||
|
||||
} // video
|
||||
} // love
|
||||
|
||||
#endif // LOVE_VIDEO_VIDEOSTREAM_H
|
||||
@@ -0,0 +1,122 @@
|
||||
/**
|
||||
* Copyright (c) 2006-2015 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.
|
||||
**/
|
||||
|
||||
// STL
|
||||
#include <vector>
|
||||
|
||||
// LOVE
|
||||
#include "Video.h"
|
||||
#include "common/delay.h"
|
||||
#include "timer/Timer.h"
|
||||
|
||||
namespace love
|
||||
{
|
||||
namespace video
|
||||
{
|
||||
namespace theora
|
||||
{
|
||||
|
||||
Video::Video()
|
||||
{
|
||||
workerThread = new Worker();
|
||||
workerThread->start();
|
||||
}
|
||||
|
||||
Video::~Video()
|
||||
{
|
||||
delete workerThread;
|
||||
}
|
||||
|
||||
VideoStream *Video::newVideoStream(love::filesystem::File *file)
|
||||
{
|
||||
VideoStream *stream = new VideoStream(file);
|
||||
workerThread->addStream(stream);
|
||||
return stream;
|
||||
}
|
||||
|
||||
const char *Video::getName() const
|
||||
{
|
||||
return "love.video.theora";
|
||||
}
|
||||
|
||||
Worker::Worker()
|
||||
: stopping(false)
|
||||
{
|
||||
threadName = "VideoWorker";
|
||||
}
|
||||
|
||||
Worker::~Worker()
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
void Worker::addStream(VideoStream *stream)
|
||||
{
|
||||
love::thread::Lock l(mutex);
|
||||
streams.push_back(stream);
|
||||
}
|
||||
|
||||
void Worker::stop()
|
||||
{
|
||||
{
|
||||
love::thread::Lock l(mutex);
|
||||
stopping = true;
|
||||
}
|
||||
|
||||
owner->wait();
|
||||
}
|
||||
|
||||
void Worker::threadFunction()
|
||||
{
|
||||
double lastFrame = love::timer::Timer::getTimeSinceEpoch();
|
||||
while (true)
|
||||
{
|
||||
double curFrame = love::timer::Timer::getTimeSinceEpoch();
|
||||
double dt = curFrame-lastFrame;
|
||||
lastFrame = curFrame;
|
||||
|
||||
{
|
||||
love::thread::Lock l(mutex);
|
||||
|
||||
if (stopping)
|
||||
return;
|
||||
|
||||
for (auto it = streams.begin(); it != streams.end(); ++it)
|
||||
{
|
||||
VideoStream *stream = *it;
|
||||
if (stream->getReferenceCount() == 1)
|
||||
{
|
||||
// We're the only ones left
|
||||
streams.erase(it);
|
||||
break;
|
||||
}
|
||||
|
||||
stream->threadedFillBackBuffer(dt);
|
||||
}
|
||||
}
|
||||
|
||||
// sleep
|
||||
love::delay(2);
|
||||
}
|
||||
}
|
||||
|
||||
} // theora
|
||||
} // video
|
||||
} // love
|
||||
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* Copyright (c) 2006-2015 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_VIDEO_H
|
||||
#define LOVE_VIDEO_THEORA_VIDEO_H
|
||||
|
||||
// STL
|
||||
#include <vector>
|
||||
|
||||
// LOVE
|
||||
#include "filesystem/File.h"
|
||||
#include "video/Video.h"
|
||||
#include "thread/threads.h"
|
||||
#include "VideoStream.h"
|
||||
|
||||
namespace love
|
||||
{
|
||||
namespace video
|
||||
{
|
||||
namespace theora
|
||||
{
|
||||
|
||||
class Worker;
|
||||
|
||||
class Video : public love::video::Video
|
||||
{
|
||||
public:
|
||||
Video();
|
||||
~Video();
|
||||
|
||||
// Implements Module
|
||||
virtual const char *getName() const;
|
||||
|
||||
VideoStream *newVideoStream(love::filesystem::File* file);
|
||||
|
||||
private:
|
||||
Worker *workerThread;
|
||||
}; // Video
|
||||
|
||||
class Worker : public love::thread::Threadable
|
||||
{
|
||||
public:
|
||||
Worker();
|
||||
~Worker();
|
||||
|
||||
// Implements Threadable
|
||||
void threadFunction();
|
||||
|
||||
void addStream(VideoStream *stream);
|
||||
// Frees itself!
|
||||
void stop();
|
||||
|
||||
private:
|
||||
std::vector<StrongRef<VideoStream>> streams;
|
||||
love::thread::MutexRef mutex;
|
||||
|
||||
volatile bool stopping;
|
||||
}; // Worker
|
||||
|
||||
} // theora
|
||||
} // video
|
||||
} // love
|
||||
|
||||
#endif // LOVE_VIDEO_THEORA_VIDEO_H
|
||||
@@ -0,0 +1,402 @@
|
||||
/**
|
||||
* Copyright (c) 2006-2015 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.
|
||||
**/
|
||||
|
||||
// STL
|
||||
#include <iostream>
|
||||
|
||||
// LOVE
|
||||
#include "VideoStream.h"
|
||||
|
||||
using love::filesystem::File;
|
||||
|
||||
namespace love
|
||||
{
|
||||
namespace video
|
||||
{
|
||||
namespace theora
|
||||
{
|
||||
|
||||
VideoStream::VideoStream(love::filesystem::File *file)
|
||||
: file(file)
|
||||
, headerParsed(false)
|
||||
, streamInited(false)
|
||||
, videoSerial(0)
|
||||
, decoder(nullptr)
|
||||
, frameReady(false)
|
||||
, lastFrame(0)
|
||||
, nextFrame(0)
|
||||
, eos(false)
|
||||
, lagCounter(0)
|
||||
{
|
||||
ogg_sync_init(&sync);
|
||||
th_info_init(&videoInfo);
|
||||
|
||||
frontBuffer = new Frame();
|
||||
backBuffer = new Frame();
|
||||
|
||||
try
|
||||
{
|
||||
parseHeader();
|
||||
}
|
||||
catch (love::Exception &ex)
|
||||
{
|
||||
delete backBuffer;
|
||||
delete frontBuffer;
|
||||
th_info_clear(&videoInfo);
|
||||
ogg_sync_clear(&sync);
|
||||
throw ex;
|
||||
}
|
||||
|
||||
frameSync = new DeltaSync();
|
||||
frameSync->release();
|
||||
}
|
||||
|
||||
VideoStream::~VideoStream()
|
||||
{
|
||||
if (decoder)
|
||||
th_decode_free(decoder);
|
||||
|
||||
th_info_clear(&videoInfo);
|
||||
if (headerParsed)
|
||||
ogg_stream_clear(&stream);
|
||||
|
||||
ogg_sync_clear(&sync);
|
||||
|
||||
delete frontBuffer;
|
||||
delete backBuffer;
|
||||
}
|
||||
|
||||
int VideoStream::getWidth() const
|
||||
{
|
||||
if (headerParsed)
|
||||
return videoInfo.pic_width;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
int VideoStream::getHeight() const
|
||||
{
|
||||
if (headerParsed)
|
||||
return videoInfo.pic_height;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
const std::string &VideoStream::getFilename() const
|
||||
{
|
||||
return file->getFilename();
|
||||
}
|
||||
|
||||
void VideoStream::setSync(FrameSync *frameSync)
|
||||
{
|
||||
love::thread::Lock l(bufferMutex);
|
||||
this->frameSync = frameSync;
|
||||
}
|
||||
|
||||
const void *VideoStream::getFrontBuffer() const
|
||||
{
|
||||
return frontBuffer;
|
||||
}
|
||||
|
||||
size_t VideoStream::getSize() const
|
||||
{
|
||||
return sizeof(Frame);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
// We need to read another page, but there is none, we're at the end
|
||||
if (ogg_page_eos(&page) && !mustSucceed)
|
||||
return eos = true;
|
||||
|
||||
do
|
||||
{
|
||||
readPage();
|
||||
} while (ogg_page_serialno(&page) != videoSerial);
|
||||
|
||||
ogg_stream_pagein(&stream, &page);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline void scaleFormat(th_pixel_fmt fmt, T &x, T &y)
|
||||
{
|
||||
switch(fmt)
|
||||
{
|
||||
case TH_PF_420:
|
||||
y /= 2;
|
||||
case TH_PF_422:
|
||||
x /= 2;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void VideoStream::parseHeader()
|
||||
{
|
||||
if (headerParsed)
|
||||
return;
|
||||
|
||||
th_comment comment;
|
||||
th_setup_info *setupInfo = nullptr;
|
||||
th_comment_init(&comment);
|
||||
int ret;
|
||||
|
||||
do
|
||||
{
|
||||
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)
|
||||
{
|
||||
th_comment_clear(&comment);
|
||||
throw love::Exception("Could not find header");
|
||||
}
|
||||
|
||||
while (ret > 0)
|
||||
{
|
||||
readPacket();
|
||||
ret = th_decode_headerin(&videoInfo, &comment, &setupInfo, &packet);
|
||||
}
|
||||
|
||||
th_comment_clear(&comment);
|
||||
|
||||
decoder = th_decode_alloc(&videoInfo, setupInfo);
|
||||
th_setup_free(setupInfo);
|
||||
|
||||
Frame *buffers[2] = {backBuffer, frontBuffer};
|
||||
|
||||
yPlaneXOffset = cPlaneXOffset = videoInfo.pic_x;
|
||||
yPlaneYOffset = cPlaneYOffset = videoInfo.pic_y;
|
||||
|
||||
scaleFormat(videoInfo.pixel_fmt, cPlaneXOffset, cPlaneYOffset);
|
||||
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
buffers[i]->cw = buffers[i]->yw = videoInfo.pic_width;
|
||||
buffers[i]->ch = buffers[i]->yh = videoInfo.pic_height;
|
||||
|
||||
scaleFormat(videoInfo.pixel_fmt, buffers[i]->cw, buffers[i]->ch);
|
||||
|
||||
buffers[i]->yplane = new unsigned char[buffers[i]->yw * buffers[i]->yh];
|
||||
buffers[i]->cbplane = new unsigned char[buffers[i]->cw * buffers[i]->ch];
|
||||
buffers[i]->crplane = new unsigned char[buffers[i]->cw * buffers[i]->ch];
|
||||
|
||||
memset(buffers[i]->yplane, 16, buffers[i]->yw * buffers[i]->yh);
|
||||
memset(buffers[i]->cbplane, 128, buffers[i]->cw * buffers[i]->ch);
|
||||
memset(buffers[i]->crplane, 128, buffers[i]->cw * buffers[i]->ch);
|
||||
}
|
||||
|
||||
headerParsed = true;
|
||||
th_decode_packetin(decoder, &packet, nullptr);
|
||||
}
|
||||
|
||||
// Arbitrary seeking isn't supported yet, but rewinding is
|
||||
void VideoStream::rewind()
|
||||
{
|
||||
// Seek our data stream back to the start
|
||||
file->seek(0);
|
||||
|
||||
// Break our sync, and discard the rest of the page
|
||||
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)
|
||||
{
|
||||
double low = 0;
|
||||
double high = file->getSize();
|
||||
|
||||
while (high-low > 0.0001)
|
||||
{
|
||||
// Determine our next binary search position
|
||||
double pos = (high-low)/2+low;
|
||||
file->seek(pos);
|
||||
|
||||
// Break sync
|
||||
ogg_sync_reset(&sync);
|
||||
ogg_sync_pageseek(&sync, &page);
|
||||
|
||||
// Read a packet
|
||||
readPacket(true);
|
||||
|
||||
// Determine if this is the right place
|
||||
double curTime = th_granule_time(decoder, packet.granulepos);
|
||||
if (curTime > target && th_granule_time(decoder, packet.granulepos-1) < target)
|
||||
break;
|
||||
else if (curTime > target)
|
||||
high = pos;
|
||||
else
|
||||
low = pos;
|
||||
}
|
||||
|
||||
// Now update theora and our decoder on this new position of ours
|
||||
lastFrame = nextFrame = -1;
|
||||
eos = false;
|
||||
th_decode_ctl(decoder, TH_DECCTL_SET_GRANPOS, &packet.granulepos, sizeof(packet.granulepos));
|
||||
}
|
||||
|
||||
void VideoStream::threadedFillBackBuffer(double dt)
|
||||
{
|
||||
// Synchronize
|
||||
frameSync->update(dt);
|
||||
double position = frameSync->getPosition();
|
||||
|
||||
// Seeking backwards
|
||||
if (position < lastFrame)
|
||||
{
|
||||
if (position < 0.01)
|
||||
rewind();
|
||||
else
|
||||
seekDecoder(position);
|
||||
}
|
||||
|
||||
// If we're at the end of the stream, or if we're displaying the right frame
|
||||
// stop here
|
||||
if (eos || position < nextFrame)
|
||||
return;
|
||||
|
||||
th_ycbcr_buffer bufferinfo;
|
||||
th_decode_ycbcr_out(decoder, bufferinfo);
|
||||
|
||||
ogg_int64_t granulePosition;
|
||||
do
|
||||
{
|
||||
if (readPacket())
|
||||
return;
|
||||
} while (th_decode_packetin(decoder, &packet, &granulePosition) != 0);
|
||||
lastFrame = nextFrame;
|
||||
nextFrame = th_granule_time(decoder, granulePosition);
|
||||
|
||||
{
|
||||
// Don't swap whilst we're writing to the backbuffer
|
||||
love::thread::Lock l(bufferMutex);
|
||||
frameReady = false;
|
||||
}
|
||||
|
||||
for (int y = 0; y < backBuffer->yh; ++y)
|
||||
{
|
||||
memcpy(backBuffer->yplane+backBuffer->yw*y,
|
||||
bufferinfo[0].data+
|
||||
bufferinfo[0].stride*(y+yPlaneYOffset)+yPlaneXOffset,
|
||||
backBuffer->yw);
|
||||
}
|
||||
|
||||
for (int y = 0; y < backBuffer->ch; ++y)
|
||||
{
|
||||
memcpy(backBuffer->cbplane+backBuffer->cw*y,
|
||||
bufferinfo[1].data+
|
||||
bufferinfo[1].stride*(y+cPlaneYOffset)+cPlaneXOffset,
|
||||
backBuffer->cw);
|
||||
}
|
||||
|
||||
for (int y = 0; y < backBuffer->ch; ++y)
|
||||
{
|
||||
memcpy(backBuffer->crplane+backBuffer->cw*y,
|
||||
bufferinfo[2].data+
|
||||
bufferinfo[2].stride*(y+cPlaneYOffset)+cPlaneXOffset,
|
||||
backBuffer->cw);
|
||||
}
|
||||
|
||||
// Seeking forwards:
|
||||
// If we're still not on the right frame, either we're lagging or we're seeking
|
||||
// After 5 frames, go for a seek. This is not ideal.. but what is
|
||||
if (position > nextFrame)
|
||||
{
|
||||
if (++lagCounter > 5)
|
||||
seek(position);
|
||||
}
|
||||
else
|
||||
lagCounter = 0;
|
||||
|
||||
love::thread::Lock l(bufferMutex);
|
||||
frameReady = true;
|
||||
}
|
||||
|
||||
void VideoStream::fillBackBuffer()
|
||||
{
|
||||
// Done in worker thread
|
||||
}
|
||||
|
||||
bool VideoStream::swapBuffers()
|
||||
{
|
||||
if (eos)
|
||||
return false;
|
||||
|
||||
love::thread::Lock l(bufferMutex);
|
||||
if (!frameReady)
|
||||
return false;
|
||||
frameReady = false;
|
||||
|
||||
Frame *temp = frontBuffer;
|
||||
frontBuffer = backBuffer;
|
||||
backBuffer = temp;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // theora
|
||||
} // video
|
||||
} // love
|
||||
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* Copyright (c) 2006-2015 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_VIDEOSTREAM_H
|
||||
#define LOVE_VIDEO_THEORA_VIDEOSTREAM_H
|
||||
|
||||
#include "video/VideoStream.h"
|
||||
|
||||
// LOVE
|
||||
#include "common/int.h"
|
||||
#include "filesystem/File.h"
|
||||
#include "thread/threads.h"
|
||||
|
||||
// OGG/Theora
|
||||
#include <ogg/ogg.h>
|
||||
#include <theora/codec.h>
|
||||
#include <theora/theoradec.h>
|
||||
|
||||
namespace love
|
||||
{
|
||||
namespace video
|
||||
{
|
||||
namespace theora
|
||||
{
|
||||
|
||||
class VideoStream : public love::video::VideoStream
|
||||
{
|
||||
public:
|
||||
VideoStream(love::filesystem::File *file);
|
||||
~VideoStream();
|
||||
|
||||
const void *getFrontBuffer() const;
|
||||
size_t getSize() const;
|
||||
void fillBackBuffer();
|
||||
bool swapBuffers();
|
||||
|
||||
int getWidth() const;
|
||||
int getHeight() const;
|
||||
const std::string &getFilename() const;
|
||||
void setSync(FrameSync *frameSync);
|
||||
|
||||
void threadedFillBackBuffer(double dt);
|
||||
|
||||
private:
|
||||
StrongRef<love::filesystem::File> file;
|
||||
|
||||
bool headerParsed;
|
||||
bool streamInited;
|
||||
int videoSerial;
|
||||
ogg_sync_state sync;
|
||||
ogg_stream_state stream;
|
||||
ogg_page page;
|
||||
ogg_packet packet;
|
||||
|
||||
th_info videoInfo;
|
||||
th_dec_ctx *decoder;
|
||||
|
||||
Frame *frontBuffer;
|
||||
Frame *backBuffer;
|
||||
unsigned int yPlaneXOffset;
|
||||
unsigned int cPlaneXOffset;
|
||||
unsigned int yPlaneYOffset;
|
||||
unsigned int cPlaneYOffset;
|
||||
|
||||
love::thread::MutexRef bufferMutex;
|
||||
bool frameReady;
|
||||
|
||||
double lastFrame;
|
||||
double nextFrame;
|
||||
bool eos;
|
||||
unsigned int lagCounter;
|
||||
|
||||
void readPage();
|
||||
bool readPacket(bool mustSucceed = false); // true if eos
|
||||
void parseHeader();
|
||||
void rewind();
|
||||
void seekDecoder(double target);
|
||||
}; // VideoStream
|
||||
|
||||
} // theora
|
||||
} // video
|
||||
} // love
|
||||
|
||||
#endif // LOVE_VIDEO_THEORA_VIDEOSTREAM_H
|
||||
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* Copyright (c) 2006-2015 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.
|
||||
**/
|
||||
|
||||
// LOVE
|
||||
#include "filesystem/wrap_Filesystem.h"
|
||||
|
||||
#include "theora/Video.h"
|
||||
#include "wrap_Video.h"
|
||||
#include "wrap_VideoStream.h"
|
||||
|
||||
namespace love
|
||||
{
|
||||
namespace video
|
||||
{
|
||||
|
||||
#define instance() (Module::getInstance<Video>(Module::M_VIDEO))
|
||||
|
||||
int w_newVideoStream(lua_State *L)
|
||||
{
|
||||
love::filesystem::File *file = love::filesystem::luax_getfile(L, 1);
|
||||
|
||||
VideoStream *stream = nullptr;
|
||||
luax_catchexcept(L, [&]() {
|
||||
// Can't check if open for reading
|
||||
if (!file->isOpen() && !file->open(love::filesystem::File::MODE_READ))
|
||||
luaL_error(L, "File is not open and cannot be opened");
|
||||
|
||||
stream = instance()->newVideoStream(file);
|
||||
});
|
||||
|
||||
luax_pushtype(L, VIDEO_VIDEO_STREAM_ID, stream);
|
||||
stream->release();
|
||||
return 1;
|
||||
}
|
||||
|
||||
static const lua_CFunction types[] =
|
||||
{
|
||||
luaopen_videostream,
|
||||
0
|
||||
};
|
||||
|
||||
static const luaL_Reg functions[] =
|
||||
{
|
||||
{ "newVideoStream", w_newVideoStream },
|
||||
{ 0, 0 }
|
||||
};
|
||||
|
||||
extern "C" int luaopen_love_video(lua_State *L)
|
||||
{
|
||||
Video *instance = instance();
|
||||
if (instance == nullptr)
|
||||
{
|
||||
luax_catchexcept(L, [&](){ instance = new love::video::theora::Video(); });
|
||||
}
|
||||
else
|
||||
instance->retain();
|
||||
|
||||
WrappedModule w;
|
||||
w.module = instance;
|
||||
w.name = "video";
|
||||
w.type = MODULE_ID;
|
||||
w.functions = functions;
|
||||
w.types = types;
|
||||
|
||||
return luax_register_module(L, w);
|
||||
}
|
||||
|
||||
} // video
|
||||
} // love
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Copyright (c) 2006-2015 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_WRAP_VIDEO_H
|
||||
#define LOVE_VIDEO_WRAP_VIDEO_H
|
||||
|
||||
// LOVE
|
||||
#include "VideoStream.h"
|
||||
#include "common/runtime.h"
|
||||
|
||||
namespace love
|
||||
{
|
||||
namespace video
|
||||
{
|
||||
|
||||
extern "C" LOVE_EXPORT int luaopen_love_video(lua_State *L);
|
||||
|
||||
} // video
|
||||
} // love
|
||||
|
||||
#endif // LOVE_VIDEO_WRAP_VIDEO_H
|
||||
@@ -0,0 +1,131 @@
|
||||
/**
|
||||
* Copyright (c) 2006-2015 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 "wrap_VideoStream.h"
|
||||
|
||||
namespace love
|
||||
{
|
||||
namespace video
|
||||
{
|
||||
|
||||
VideoStream *luax_checkvideostream(lua_State *L, int idx)
|
||||
{
|
||||
return luax_checktype<VideoStream>(L, idx, VIDEO_VIDEO_STREAM_ID);
|
||||
}
|
||||
|
||||
int w_VideoStream_setSync(lua_State *L)
|
||||
{
|
||||
auto stream = luax_checkvideostream(L, 1);
|
||||
|
||||
if (luax_istype(L, 2, AUDIO_SOURCE_ID))
|
||||
{
|
||||
auto src = luax_totype<love::audio::Source>(L, 2, AUDIO_SOURCE_ID);
|
||||
auto sync = new VideoStream::SourceSync(src);
|
||||
stream->setSync(sync);
|
||||
sync->release();
|
||||
}
|
||||
else if (luax_istype(L, 2, VIDEO_VIDEO_STREAM_ID))
|
||||
{
|
||||
auto other = luax_totype<VideoStream>(L, 2, VIDEO_VIDEO_STREAM_ID);
|
||||
stream->setSync(other->getSync());
|
||||
}
|
||||
else if (lua_isnoneornil(L, 2))
|
||||
{
|
||||
auto newSync = new VideoStream::DeltaSync();
|
||||
newSync->copyState(stream->getSync());
|
||||
stream->setSync(newSync);
|
||||
newSync->release();
|
||||
}
|
||||
else
|
||||
return luax_typerror(L, 2, "Source or VideoStream or nil");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int w_VideoStream_getFilename(lua_State *L)
|
||||
{
|
||||
auto stream = luax_checkvideostream(L, 1);
|
||||
luax_pushstring(L, stream->getFilename());
|
||||
return 1;
|
||||
}
|
||||
|
||||
int w_VideoStream_play(lua_State *L)
|
||||
{
|
||||
auto stream = luax_checkvideostream(L, 1);
|
||||
stream->play();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int w_VideoStream_pause(lua_State *L)
|
||||
{
|
||||
auto stream = luax_checkvideostream(L, 1);
|
||||
stream->pause();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int w_VideoStream_seek(lua_State *L)
|
||||
{
|
||||
auto stream = luax_checkvideostream(L, 1);
|
||||
double offset = luaL_checknumber(L, 2);
|
||||
stream->seek(offset);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int w_VideoStream_rewind(lua_State *L)
|
||||
{
|
||||
auto stream = luax_checkvideostream(L, 1);
|
||||
stream->seek(0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int w_VideoStream_tell(lua_State *L)
|
||||
{
|
||||
auto stream = luax_checkvideostream(L, 1);
|
||||
lua_pushnumber(L, stream->tell());
|
||||
return 1;
|
||||
}
|
||||
|
||||
int w_VideoStream_isPlaying(lua_State *L)
|
||||
{
|
||||
auto stream = luax_checkvideostream(L, 1);
|
||||
luax_pushboolean(L, stream->isPlaying());
|
||||
return 1;
|
||||
}
|
||||
|
||||
static const luaL_Reg videostream_functions[] =
|
||||
{
|
||||
{ "setSync", w_VideoStream_setSync },
|
||||
{ "getFilename", w_VideoStream_getFilename },
|
||||
{ "play", w_VideoStream_play },
|
||||
{ "pause", w_VideoStream_pause },
|
||||
{ "seek", w_VideoStream_seek },
|
||||
{ "rewind", w_VideoStream_rewind },
|
||||
{ "tell", w_VideoStream_tell },
|
||||
{ "isPlaying", w_VideoStream_isPlaying },
|
||||
{ 0, 0 }
|
||||
};
|
||||
|
||||
int luaopen_videostream(lua_State *L)
|
||||
{
|
||||
return luax_register_type(L, VIDEO_VIDEO_STREAM_ID, "VideoStream", videostream_functions, nullptr);
|
||||
}
|
||||
|
||||
} // video
|
||||
} // love
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Copyright (c) 2006-2015 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.
|
||||
**/
|
||||
|
||||
#pragma once
|
||||
|
||||
// LOVE
|
||||
#include "common/runtime.h"
|
||||
#include "VideoStream.h"
|
||||
|
||||
namespace love
|
||||
{
|
||||
namespace video
|
||||
{
|
||||
|
||||
LOVE_EXPORT int luaopen_videostream(lua_State *L);
|
||||
|
||||
} // video
|
||||
} // love
|
||||
Reference in New Issue
Block a user