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:
Bart van Strien
2015-12-08 22:41:19 -04:00
parent a1dab12117
commit 22f2175bec
41 changed files with 2241 additions and 20 deletions
+35
View File
@@ -308,6 +308,8 @@ set(LOVE_SRC_MODULE_GRAPHICS_OPENGL
src/modules/graphics/opengl/SpriteBatch.h
src/modules/graphics/opengl/Text.cpp
src/modules/graphics/opengl/Text.h
src/modules/graphics/opengl/Video.cpp
src/modules/graphics/opengl/Video.h
src/modules/graphics/opengl/wrap_Canvas.cpp
src/modules/graphics/opengl/wrap_Canvas.h
src/modules/graphics/opengl/wrap_Font.cpp
@@ -326,6 +328,8 @@ set(LOVE_SRC_MODULE_GRAPHICS_OPENGL
src/modules/graphics/opengl/wrap_SpriteBatch.h
src/modules/graphics/opengl/wrap_Text.cpp
src/modules/graphics/opengl/wrap_Text.h
src/modules/graphics/opengl/wrap_Video.cpp
src/modules/graphics/opengl/wrap_Video.h
)
set(LOVE_SRC_MODULE_GRAPHICS
@@ -778,6 +782,35 @@ set(LOVE_SRC_MODULE_TOUCH
source_group("modules\\touch" FILES ${LOVE_SRC_MODULE_TOUCH_ROOT})
source_group("modules\\touch\\sdl" FILES ${LOVE_SRC_MODULE_TOUCH_SDL})
#
# love.video
#
set(LOVE_SRC_MODULE_VIDEO_ROOT
src/modules/video/Video.h
src/modules/video/VideoStream.cpp
src/modules/video/VideoStream.h
src/modules/video/wrap_Video.cpp
src/modules/video/wrap_Video.h
src/modules/video/wrap_VideoStream.cpp
src/modules/video/wrap_VideoStream.h
)
set(LOVE_SRC_MODULE_VIDEO_THEORA
src/modules/video/theora/Video.cpp
src/modules/video/theora/Video.h
src/modules/video/theora/VideoStream.cpp
src/modules/video/theora/VideoStream.h
)
set(LOVE_SRC_MODULE_VIDEO
${LOVE_SRC_MODULE_VIDEO_ROOT}
${LOVE_SRC_MODULE_VIDEO_THEORA}
)
source_group("modules\\video" FILES ${LOVE_SRC_MODULE_VIDEO_ROOT})
source_group("modules\\video\\theora" FILES ${LOVE_SRC_MODULE_VIDEO_THEORA})
#
# love.window
#
@@ -1203,6 +1236,7 @@ set(LOVE_LIB_SRC
${LOVE_SRC_MODULE_THREAD}
${LOVE_SRC_MODULE_TIMER}
${LOVE_SRC_MODULE_TOUCH}
${LOVE_SRC_MODULE_VIDEO}
${LOVE_SRC_MODULE_WINDOW}
)
@@ -1222,6 +1256,7 @@ set(LOVE_MEGA_3P
${MEGA_LIBOGG}
${MEGA_LIBVORBISFILE}
${MEGA_LIBVORBIS}
${MEGA_LIBTHEORA}
${MEGA_LUA}
${MEGA_MODPLUG}
${MEGA_OPENAL}
+1
View File
@@ -53,6 +53,7 @@ PKG_CHECK_MODULES([openal], [openal], [], [LOVE_MSG_ERROR([OpenAL])])
PKG_CHECK_MODULES([libmodplug], [libmodplug], [], [LOVE_MSG_ERROR([libmodplug])])
PKG_CHECK_MODULES([vorbisfile], [vorbisfile], [], [LOVE_MSG_ERROR([libvorbis and libvorbisfile])])
PKG_CHECK_MODULES([zlib], [zlib], [], [LOVE_MSG_ERROR([zlib])])
PKG_CHECK_MODULES([theora], [theoradec], [], [LOVE_MSG_ERROR([libtheora])])
# Other libraries
AC_SEARCH_LIBS([sqrt], [m], [], [LOVE_MSG_ERROR([the C math library])])
+2 -2
View File
@@ -114,7 +114,7 @@ cat > src/Makefile.am << EOF
AM_CPPFLAGS = -I$inc_current -I$inc_modules -I$inc_libraries -I$inc_libraries/enet/libenet/include \$(LOVE_INCLUDES) \$(FILE_OFFSET)\
\$(SDL_CFLAGS) \$(lua_CFLAGS) \$(freetype2_CFLAGS)\
\$(openal_CFLAGS) \$(zlib_CFLAGS) \$(libmodplug_CFLAGS)\
\$(vorbisfile_CFLAGS)
\$(vorbisfile_CFLAGS) \$(theora_CFLAGS)
AUTOMAKE_OPTIONS = subdir-objects
SUBDIRS =
SUFFIXES = .lua .lua.h
@@ -146,7 +146,7 @@ liblove${love_amsuffix}_la_LDFLAGS = -module -export-dynamic \$(LDFLAGS)
liblove${love_amsuffix}_la_LIBADD = \
\$(SDL_LIBS) \$(freetype2_LIBS) \$(lua_LIBS)\
\$(openal_LIBS) \$(zlib_LIBS) \$(libmodplug_LIBS)\
\$(vorbisfile_LIBS)
\$(vorbisfile_LIBS) \$(theora_LIBS)
EOF
genmodules >> src/Makefile.am
+1
View File
@@ -53,6 +53,7 @@ public:
M_TIMER,
M_TOUCH,
M_WINDOW,
M_VIDEO,
M_MAX_ENUM
};
+10
View File
@@ -117,6 +117,16 @@ public:
return object;
}
operator bool() const
{
return object != nullptr;
}
operator T*() const
{
return object;
}
void set(T *obj)
{
if (obj) obj->retain();
+64
View File
@@ -0,0 +1,64 @@
/**
* 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_STREAM_H
#define LOVE_STREAM_H
// LOVE
#include <stddef.h>
#include "Object.h"
namespace love
{
class Stream : public Object
{
public:
virtual ~Stream() {}
// getData and getSize are assumed to talk about
// the buffer
/**
* A callback, gets called when some Stream consumer exhausts the data
**/
virtual void fillBackBuffer() {}
/**
* Get the front buffer, Streams are supposed to be (at least) double-buffered
**/
virtual const void *getFrontBuffer() const = 0;
/**
* Get the size of any (and in particular the front) buffer
**/
virtual size_t getSize() const = 0;
/**
* Swap buffers. Returns true if there is new data in the front buffer,
* false otherwise.
* NOTE: If there is no back buffer ready, this call must be ignored
**/
virtual bool swapBuffers() = 0;
}; // Stream
} // love
#endif // LOVE_STREAM_H
+2
View File
@@ -141,6 +141,8 @@
# define LOVE_ENABLE_TOUCH
# define LOVE_ENABLE_TOUCH_SDL
# define LOVE_ENABLE_UTF8
# define LOVE_ENABLE_VIDEO
# define LOVE_ENABLE_VIDEO_THEORA
# define LOVE_ENABLE_WINDOW
# define LOVE_ENABLE_WINDOW_SDL
# define LOVE_ENABLE_WUFF
+5
View File
@@ -34,6 +34,7 @@ static const TypeBits *createTypeFlags()
b[OBJECT_ID] = one << OBJECT_ID;
b[DATA_ID] = (one << DATA_ID) | b[OBJECT_ID];
b[MODULE_ID] = (one << MODULE_ID) | b[OBJECT_ID];
b[STREAM_ID] = (one << STREAM_ID) | b[OBJECT_ID];
// Filesystem.
b[FILESYSTEM_FILE_ID] = (one << FILESYSTEM_FILE_ID) | b[OBJECT_ID];
@@ -55,6 +56,7 @@ static const TypeBits *createTypeFlags()
b[GRAPHICS_SHADER_ID] = (one << GRAPHICS_SHADER_ID) | b[OBJECT_ID];
b[GRAPHICS_MESH_ID] = (one << GRAPHICS_MESH_ID) | b[GRAPHICS_DRAWABLE_ID];
b[GRAPHICS_TEXT_ID] = (one << GRAPHICS_TEXT_ID) | b[GRAPHICS_DRAWABLE_ID];
b[GRAPHICS_VIDEO_ID] = (one << GRAPHICS_VIDEO_ID) | b[GRAPHICS_DRAWABLE_ID];
// Image.
b[IMAGE_IMAGE_DATA_ID] = (one << IMAGE_IMAGE_DATA_ID) | b[DATA_ID];
@@ -105,6 +107,9 @@ static const TypeBits *createTypeFlags()
b[THREAD_THREAD_ID] = (one << THREAD_THREAD_ID) | b[OBJECT_ID];
b[THREAD_CHANNEL_ID] = (one << THREAD_CHANNEL_ID) | b[OBJECT_ID];
// Video
b[VIDEO_VIDEO_STREAM_ID] = (one << VIDEO_VIDEO_STREAM_ID) | b[STREAM_ID];
// Modules.
b[MODULE_FILESYSTEM_ID] = (one << MODULE_FILESYSTEM_ID) | b[MODULE_ID];
b[MODULE_GRAPHICS_ID] = (one << MODULE_GRAPHICS_ID) | b[MODULE_ID];
+5
View File
@@ -34,6 +34,7 @@ enum Type
OBJECT_ID,
DATA_ID,
MODULE_ID,
STREAM_ID,
// Filesystem.
FILESYSTEM_FILE_ID,
@@ -56,6 +57,7 @@ enum Type
GRAPHICS_SHADER_ID,
GRAPHICS_MESH_ID,
GRAPHICS_TEXT_ID,
GRAPHICS_VIDEO_ID,
// Image
IMAGE_IMAGE_DATA_ID,
@@ -106,6 +108,9 @@ enum Type
THREAD_THREAD_ID,
THREAD_CHANNEL_ID,
// Video
VIDEO_VIDEO_STREAM_ID,
// The modules themselves. Only add abstracted modules here.
MODULE_FILESYSTEM_ID,
MODULE_GRAPHICS_ID,
+14 -5
View File
@@ -155,19 +155,28 @@ int w_newFile(lua_State *L)
return 1;
}
FileData *luax_getfiledata(lua_State *L, int idx)
File *luax_getfile(lua_State *L, int idx)
{
FileData *data = nullptr;
File *file = nullptr;
if (lua_isstring(L, idx))
{
const char *filename = luaL_checkstring(L, idx);
file = instance()->newFile(filename);
}
else if (luax_istype(L, idx, FILESYSTEM_FILE_ID))
{
else
file = luax_checkfile(L, idx);
return file;
}
FileData *luax_getfiledata(lua_State *L, int idx)
{
FileData *data = nullptr;
File *file = nullptr;
if (lua_isstring(L, idx) || luax_istype(L, idx, FILESYSTEM_FILE_ID))
{
file = luax_getfile(L, idx);
file->retain();
}
else if (luax_istype(L, idx, FILESYSTEM_FILE_DATA_ID))
+2
View File
@@ -23,6 +23,7 @@
// LOVE
#include "common/runtime.h"
#include "File.h"
#include "FileData.h"
namespace love
@@ -38,6 +39,7 @@ namespace filesystem
* May trigger a Lua error.
**/
FileData *luax_getfiledata(lua_State *L, int idx);
File *luax_getfile(lua_State *L, int idx);
bool hack_setupWriteDirectory();
int loader(lua_State *L);
+17
View File
@@ -86,6 +86,11 @@ Graphics::~Graphics()
Shader::defaultShader->release();
Shader::defaultShader = nullptr;
}
if (Shader::defaultVideoShader)
{
Shader::defaultVideoShader->release();
Shader::defaultVideoShader = nullptr;
}
if (quadIndices)
delete quadIndices;
@@ -320,6 +325,13 @@ bool Graphics::setMode(int width, int height)
Shader::defaultShader = newShader(Shader::defaultCode[renderer]);
}
// and a default video shader.
if (!Shader::defaultVideoShader)
{
Renderer renderer = GLAD_ES_VERSION_2_0 ? RENDERER_OPENGLES : RENDERER_OPENGL;
Shader::defaultVideoShader = newShader(Shader::defaultVideoCode[renderer]);
}
// A shader should always be active, but the default shader shouldn't be
// returned by getShader(), so we don't do setShader(defaultShader).
if (!Shader::current)
@@ -819,6 +831,11 @@ Text *Graphics::newText(Font *font, const std::vector<Font::ColoredString> &text
return new Text(font, text);
}
Video *Graphics::newVideo(love::video::VideoStream *stream)
{
return new Video(stream);
}
bool Graphics::isGammaCorrect() const
{
return love::graphics::isGammaCorrect();
+5
View File
@@ -37,6 +37,8 @@
#include "window/Window.h"
#include "video/VideoStream.h"
#include "Font.h"
#include "Image.h"
#include "graphics/Quad.h"
@@ -47,6 +49,7 @@
#include "Shader.h"
#include "Mesh.h"
#include "Text.h"
#include "Video.h"
namespace love
{
@@ -185,6 +188,8 @@ public:
Text *newText(Font *font, const std::vector<Font::ColoredString> &text = {});
Video *newVideo(love::video::VideoStream *stream);
bool isGammaCorrect() const;
/**
+60
View File
@@ -64,8 +64,10 @@ namespace
Shader *Shader::current = nullptr;
Shader *Shader::defaultShader = nullptr;
Shader *Shader::defaultVideoShader = nullptr;
Shader::ShaderSource Shader::defaultCode[Graphics::RENDERER_MAX_ENUM];
Shader::ShaderSource Shader::defaultVideoCode[Graphics::RENDERER_MAX_ENUM];
std::vector<int> Shader::textureCounters;
@@ -77,6 +79,7 @@ Shader::Shader(const ShaderSource &source)
, lastCanvas((Canvas *) -1)
, lastViewport()
, lastPointSize(0.0f)
, videoTextureUnits()
{
if (source.vertex.empty() && source.pixel.empty())
throw love::Exception("Cannot create shader: no source code!");
@@ -225,6 +228,9 @@ bool Shader::loadVolatile()
lastProjectionMatrix.setTranslation(nan, nan);
lastTransformMatrix.setTranslation(nan, nan);
for (int i = 0; i < 3; i++)
videoTextureUnits[i] = 0;
// zero out active texture list
activeTexUnits.clear();
activeTexUnits.insert(activeTexUnits.begin(), gl.getMaxTextureUnits() - 1, 0);
@@ -635,6 +641,57 @@ bool Shader::hasVertexAttrib(VertexAttribID attrib) const
return builtinAttributes[int(attrib)] != -1;
}
void Shader::setVideoTextures(GLuint ytexture, GLuint cbtexture, GLuint crtexture)
{
TemporaryAttacher attacher(this);
// Set up the texture units that will be used by the shader to sample from
// the textures, if they haven't been set up yet.
if (videoTextureUnits[0] == 0)
{
const GLint locs[3] = {
builtinUniforms[BUILTIN_VIDEO_Y_CHANNEL],
builtinUniforms[BUILTIN_VIDEO_CB_CHANNEL],
builtinUniforms[BUILTIN_VIDEO_CR_CHANNEL]
};
const char *names[3] = {nullptr, nullptr, nullptr};
builtinNames.find(BUILTIN_VIDEO_Y_CHANNEL, names[0]);
builtinNames.find(BUILTIN_VIDEO_CB_CHANNEL, names[1]);
builtinNames.find(BUILTIN_VIDEO_CR_CHANNEL, names[2]);
for (int i = 0; i < 3; i++)
{
if (locs[i] >= 0 && names[i] != nullptr)
{
videoTextureUnits[i] = getTextureUnit(names[i]);
// Increment global shader texture id counter for this texture
// unit, if we haven't already.
if (activeTexUnits[videoTextureUnits[i] - 1] == 0)
++textureCounters[videoTextureUnits[i] - 1];
glUniform1i(locs[i], videoTextureUnits[i]);
}
}
}
const GLuint textures[3] = {ytexture, cbtexture, crtexture};
// Bind the textures to their respective texture units.
for (int i = 0; i < 3; i++)
{
if (videoTextureUnits[i] != 0)
{
// Store texture id so it can be re-bound later.
activeTexUnits[videoTextureUnits[i] - 1] = textures[i];
gl.bindTextureToUnit(textures[i], videoTextureUnits[i], false);
}
}
gl.setTextureUnit(0);
}
void Shader::checkSetScreenParams()
{
OpenGL::Viewport view = gl.getViewport();
@@ -898,6 +955,9 @@ StringMap<Shader::BuiltinUniform, Shader::BUILTIN_MAX_ENUM>::Entry Shader::built
{"NormalMatrix", Shader::BUILTIN_NORMAL_MATRIX},
{"love_PointSize", Shader::BUILTIN_POINT_SIZE},
{"love_ScreenSize", Shader::BUILTIN_SCREEN_SIZE},
{"love_VideoYChannel", Shader::BUILTIN_VIDEO_Y_CHANNEL},
{"love_VideoCbChannel", Shader::BUILTIN_VIDEO_CB_CHANNEL},
{"love_VideoCrChannel", Shader::BUILTIN_VIDEO_CR_CHANNEL},
};
StringMap<Shader::BuiltinUniform, Shader::BUILTIN_MAX_ENUM> Shader::builtinNames(Shader::builtinNameEntries, sizeof(Shader::builtinNameEntries));
+8
View File
@@ -64,6 +64,9 @@ public:
BUILTIN_NORMAL_MATRIX,
BUILTIN_POINT_SIZE,
BUILTIN_SCREEN_SIZE,
BUILTIN_VIDEO_Y_CHANNEL,
BUILTIN_VIDEO_CB_CHANNEL,
BUILTIN_VIDEO_CR_CHANNEL,
BUILTIN_MAX_ENUM
};
@@ -89,9 +92,11 @@ public:
// Pointer to the default Shader.
static Shader *defaultShader;
static Shader *defaultVideoShader;
// Default shader code (a shader is always required internally.)
static ShaderSource defaultCode[Graphics::RENDERER_MAX_ENUM];
static ShaderSource defaultVideoCode[Graphics::RENDERER_MAX_ENUM];
/**
* Creates a new Shader using a list of source codes.
@@ -182,6 +187,7 @@ public:
**/
bool hasVertexAttrib(VertexAttribID attrib) const;
void setVideoTextures(GLuint ytexture, GLuint cbtexture, GLuint crtexture);
void checkSetScreenParams();
void checkSetPointSize(float size);
void checkSetBuiltinUniforms();
@@ -263,6 +269,8 @@ private:
Matrix4 lastTransformMatrix;
Matrix4 lastProjectionMatrix;
GLuint videoTextureUnits[3];
// Counts total number of textures bound to each texture unit in all shaders
static std::vector<int> textureCounters;
+212
View File
@@ -0,0 +1,212 @@
/**
* 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 "Video.h"
// LOVE
#include "Shader.h"
namespace love
{
namespace graphics
{
namespace opengl
{
Video::Video(love::video::VideoStream *stream)
: stream(stream)
, filter(Texture::getDefaultFilter())
{
filter.mipmap = Texture::FILTER_NONE;
stream->fillBackBuffer();
for (int i = 0; i < 4; i++)
vertices[i].r = vertices[i].g = vertices[i].b = vertices[i].a = 255;
// Vertices are ordered for use with triangle strips:
// 0----2
// | / |
// | / |
// 1----3
vertices[0].x = 0.0f;
vertices[0].y = 0.0f;
vertices[1].x = 0.0f;
vertices[1].y = (float) stream->getHeight();
vertices[2].x = (float) stream->getWidth();
vertices[2].y = 0.0f;
vertices[3].x = (float) stream->getWidth();
vertices[3].y = (float) stream->getHeight();
vertices[0].s = 0.0f;
vertices[0].t = 0.0f;
vertices[1].s = 0.0f;
vertices[1].t = 1.0f;
vertices[2].s = 1.0f;
vertices[2].t = 0.0f;
vertices[3].s = 1.0f;
vertices[3].t = 1.0f;
loadVolatile();
}
Video::~Video()
{
unloadVolatile();
}
bool Video::loadVolatile()
{
glGenTextures(3, &textures[0]);
// Create the textures using the initial frame data.
auto frame = (const love::video::VideoStream::Frame*) stream->getFrontBuffer();
gl.bindTexture(textures[0]);
gl.setTextureFilter(filter);
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, frame->yw, frame->yh,
0, GL_LUMINANCE, GL_UNSIGNED_BYTE, frame->yplane);
gl.bindTexture(textures[1]);
gl.setTextureFilter(filter);
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, frame->cw, frame->ch,
0, GL_LUMINANCE, GL_UNSIGNED_BYTE, frame->cbplane);
gl.bindTexture(textures[2]);
gl.setTextureFilter(filter);
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, frame->cw, frame->ch,
0, GL_LUMINANCE, GL_UNSIGNED_BYTE, frame->crplane);
return true;
}
void Video::unloadVolatile()
{
for (int i = 0; i < 3; i++)
{
gl.deleteTexture(textures[i]);
textures[i] = 0;
}
}
love::video::VideoStream *Video::getStream()
{
return stream;
}
void Video::draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky)
{
update();
Shader *shader = Shader::current;
bool defaultShader = (shader == Shader::defaultShader);
if (defaultShader)
{
// If we're still using the default shader, substitute the video version
Shader::defaultVideoShader->attach();
shader = Shader::defaultVideoShader;
}
shader->setVideoTextures(textures[0], textures[1], textures[2]);
OpenGL::TempTransform transform(gl);
transform.get() *= Matrix4(x, y, angle, sx, sy, ox, oy, kx, ky);
gl.useVertexAttribArrays(ATTRIBFLAG_POS | ATTRIBFLAG_TEXCOORD);
glVertexAttribPointer(ATTRIB_POS, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), &vertices[0].x);
glVertexAttribPointer(ATTRIB_TEXCOORD, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), &vertices[0].s);
gl.prepareDraw();
gl.drawArrays(GL_TRIANGLE_STRIP, 0, 4);
// If we were using the default shader, reattach it
if (defaultShader)
Shader::defaultShader->attach();
}
void Video::update()
{
bool bufferschanged = stream->swapBuffers();
stream->fillBackBuffer();
if (bufferschanged)
{
auto frame = (const love::video::VideoStream::Frame*) stream->getFrontBuffer();
gl.bindTexture(textures[0]);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, frame->yw, frame->yh,
GL_LUMINANCE, GL_UNSIGNED_BYTE, frame->yplane);
gl.bindTexture(textures[1]);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, frame->cw, frame->ch,
GL_LUMINANCE, GL_UNSIGNED_BYTE, frame->cbplane);
gl.bindTexture(textures[2]);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, frame->cw, frame->ch,
GL_LUMINANCE, GL_UNSIGNED_BYTE, frame->crplane);
}
}
love::audio::Source *Video::getSource()
{
return source;
}
void Video::setSource(love::audio::Source *source)
{
this->source = source;
}
int Video::getWidth() const
{
return stream->getWidth();
}
int Video::getHeight() const
{
return stream->getHeight();
}
void Video::setFilter(const Texture::Filter &f)
{
if (!Texture::validateFilter(f, false))
throw love::Exception("Invalid texture filter.");
filter = f;
for (int i = 0; i < 3; i++)
{
gl.bindTexture(textures[i]);
gl.setTextureFilter(filter);
}
}
const Texture::Filter &Video::getFilter() const
{
return filter;
}
} // opengl
} // graphics
} // love
+79
View File
@@ -0,0 +1,79 @@
/**
* 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/math.h"
#include "graphics/Drawable.h"
#include "graphics/Volatile.h"
#include "video/VideoStream.h"
#include "audio/Source.h"
#include "OpenGL.h"
namespace love
{
namespace graphics
{
namespace opengl
{
class Video : public Drawable, public Volatile
{
public:
Video(love::video::VideoStream *stream);
~Video();
// Volatile
bool loadVolatile();
void unloadVolatile();
love::video::VideoStream *getStream();
void draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky);
love::audio::Source *getSource();
void setSource(love::audio::Source *source);
int getWidth() const;
int getHeight() const;
void setFilter(const Texture::Filter &f);
const Texture::Filter &getFilter() const;
private:
void update();
StrongRef<love::video::VideoStream> stream;
StrongRef<love::audio::Source> source;
GLuint textures[3];
Vertex vertices[4];
Texture::Filter filter;
}; // Video
} // opengl
} // graphics
} // love
+35 -6
View File
@@ -25,6 +25,7 @@
#include "image/Image.h"
#include "font/Rasterizer.h"
#include "filesystem/wrap_Filesystem.h"
#include "video/VideoStream.h"
#include "image/wrap_Image.h"
#include <cassert>
@@ -862,6 +863,20 @@ int w_newText(lua_State *L)
return 1;
}
int w_newVideo(lua_State *L)
{
if (!luax_istype(L, 1, VIDEO_VIDEO_STREAM_ID))
luax_convobj(L, 1, "video", "newVideoStream");
auto stream = luax_checktype<love::video::VideoStream>(L, 1, VIDEO_VIDEO_STREAM_ID);
Video *video = nullptr;
luax_catchexcept(L, [&]() { video = instance()->newVideo(stream); });
luax_pushtype(L, GRAPHICS_VIDEO_ID, video);
video->release();
return 1;
}
int w_setColor(lua_State *L)
{
Colorf c;
@@ -1274,25 +1289,37 @@ int w_setDefaultShaderCode(lua_State *L)
lua_getfield(L, 1, "opengl");
lua_rawgeti(L, -1, 1);
lua_rawgeti(L, -2, 2);
lua_rawgeti(L, -3, 3);
Shader::ShaderSource openglcode;
openglcode.vertex = luax_checkstring(L, -2);
openglcode.pixel = luax_checkstring(L, -1);
openglcode.vertex = luax_checkstring(L, -3);
openglcode.pixel = luax_checkstring(L, -2);
lua_pop(L, 3);
Shader::ShaderSource openglVideocode;
openglVideocode.vertex = luax_checkstring(L, -3);
openglVideocode.pixel = luax_checkstring(L, -1);
lua_pop(L, 4);
lua_getfield(L, 1, "opengles");
lua_rawgeti(L, -1, 1);
lua_rawgeti(L, -2, 2);
lua_rawgeti(L, -3, 3);
Shader::ShaderSource openglescode;
openglescode.vertex = luax_checkstring(L, -2);
openglescode.pixel = luax_checkstring(L, -1);
openglescode.vertex = luax_checkstring(L, -3);
openglescode.pixel = luax_checkstring(L, -2);
lua_pop(L, 3);
Shader::ShaderSource openglesVideocode;
openglesVideocode.vertex = luax_checkstring(L, -3);
openglesVideocode.pixel = luax_checkstring(L, -1);
lua_pop(L, 4);
Shader::defaultCode[Graphics::RENDERER_OPENGL] = openglcode;
Shader::defaultCode[Graphics::RENDERER_OPENGLES] = openglescode;
Shader::defaultVideoCode[Graphics::RENDERER_OPENGL] = openglVideocode;
Shader::defaultVideoCode[Graphics::RENDERER_OPENGLES] = openglesVideocode;
return 0;
}
@@ -1863,6 +1890,7 @@ static const luaL_Reg functions[] =
{ "newShader", w_newShader },
{ "newMesh", w_newMesh },
{ "newText", w_newText },
{ "_newVideo", w_newVideo },
{ "setColor", w_setColor },
{ "getColor", w_getColor },
@@ -1965,6 +1993,7 @@ static const lua_CFunction types[] =
luaopen_shader,
luaopen_mesh,
luaopen_text,
luaopen_video,
0
};
@@ -32,6 +32,7 @@
#include "wrap_Shader.h"
#include "wrap_Mesh.h"
#include "wrap_Text.h"
#include "wrap_Video.h"
#include "Graphics.h"
namespace love
+48 -1
View File
@@ -165,6 +165,28 @@ varying mediump vec4 VaryingColor;
uniform sampler2D _tex0_;]],
FUNCTIONS = [[
uniform sampler2D love_VideoYChannel;
uniform sampler2D love_VideoCbChannel;
uniform sampler2D love_VideoCrChannel;
vec4 VideoTexel(vec2 texcoords)
{
vec3 yuv;
yuv[0] = Texel(love_VideoYChannel, texcoords).r;
yuv[1] = Texel(love_VideoCbChannel, texcoords).r;
yuv[2] = Texel(love_VideoCrChannel, texcoords).r;
yuv += vec3(-0.0627451017, -0.501960814, -0.501960814);
vec4 color;
color.r = dot(yuv, vec3(1.164, 0.000, 1.596));
color.g = dot(yuv, vec3(1.164, -0.391, -0.813));
color.b = dot(yuv, vec3(1.164, 2.018, 0.000));
color.a = 1.0;
return gammaCorrectColor(color);
}]],
FOOTER = [[
void main() {
// fix crashing issue in OSX when _tex0_ is unused within effect()
@@ -209,6 +231,7 @@ local function createPixelCode(pixelcode, is_multicanvas, lang)
love.graphics.isGammaCorrect() and "#define LOVE_GAMMA_CORRECT 1" or "",
GLSL.PIXEL.HEADER, GLSL.UNIFORMS,
GLSL.FUNCTIONS,
GLSL.PIXEL.FUNCTIONS,
lang == "glsles" and "#line 1" or "#line 0",
pixelcode,
is_multicanvas and GLSL.PIXEL.FOOTER_MULTI_CANVAS or GLSL.PIXEL.FOOTER,
@@ -309,21 +332,45 @@ vec4 position(mat4 transform_proj, vec4 vertpos) {
pixel = [[
vec4 effect(mediump vec4 vcolor, Image tex, vec2 texcoord, vec2 pixcoord) {
return Texel(tex, texcoord) * vcolor;
}]]
}]],
videopixel = [[
vec4 effect(mediump vec4 vcolor, Image tex, vec2 texcoord, vec2 pixcoord) {
return VideoTexel(texcoord) * vcolor;
}]],
}
local defaults = {
opengl = {
createVertexCode(defaultcode.vertex, "glsl"),
createPixelCode(defaultcode.pixel, false, "glsl"),
createPixelCode(defaultcode.videopixel, false, "glsl"),
},
opengles = {
createVertexCode(defaultcode.vertex, "glsles"),
createPixelCode(defaultcode.pixel, false, "glsles"),
createPixelCode(defaultcode.videopixel, false, "glsles"),
},
}
love.graphics._setDefaultShaderCode(defaults)
function love.graphics.newVideo(file, loadaudio)
local video = love.graphics._newVideo(file)
local source, success
if loadaudio ~= false then
success, source = pcall(love.audio.newSource, video:getStream():getFilename())
end
if success then
video:setSource(source)
elseif loadaudio == true then
error("Video had no audio track", 2)
else
video:getStream():setSync(love.video.newRemote())
end
return video
end
-- DO NOT REMOVE THE NEXT LINE. It is used to load this file as a C++ string.
--)luastring"--"
+157
View File
@@ -0,0 +1,157 @@
/**
* 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_Video.h"
// Shove the wrap_Video.lua code directly into a raw string literal.
static const char video_lua[] =
#include "wrap_Video.lua"
;
namespace love
{
namespace graphics
{
namespace opengl
{
Video *luax_checkvideo(lua_State *L, int idx)
{
return luax_checktype<Video>(L, idx, GRAPHICS_VIDEO_ID);
}
int w_Video_getStream(lua_State *L)
{
Video *video = luax_checkvideo(L, 1);
luax_pushtype(L, VIDEO_VIDEO_STREAM_ID, video->getStream());
return 1;
}
int w_Video_getSource(lua_State *L)
{
Video *video = luax_checkvideo(L, 1);
auto source = video->getSource();
if (source)
luax_pushtype(L, AUDIO_SOURCE_ID, video->getSource());
else
lua_pushnil(L);
return 1;
}
int w_Video_setSource(lua_State *L)
{
Video *video = luax_checkvideo(L, 1);
if (lua_isnoneornil(L, 2))
video->setSource(nullptr);
else
{
auto source = luax_checktype<love::audio::Source>(L, 2, AUDIO_SOURCE_ID);
video->setSource(source);
}
return 0;
}
int w_Video_getWidth(lua_State *L)
{
Video *video = luax_checkvideo(L, 1);
lua_pushnumber(L, video->getWidth());
return 1;
}
int w_Video_getHeight(lua_State *L)
{
Video *video = luax_checkvideo(L, 1);
lua_pushnumber(L, video->getHeight());
return 1;
}
int w_Video_getDimensions(lua_State *L)
{
Video *video = luax_checkvideo(L, 1);
lua_pushnumber(L, video->getWidth());
lua_pushnumber(L, video->getHeight());
return 2;
}
int w_Video_setFilter(lua_State *L)
{
Video *video = luax_checkvideo(L, 1);
Texture::Filter f = video->getFilter();
const char *minstr = luaL_checkstring(L, 2);
const char *magstr = luaL_optstring(L, 3, minstr);
if (!Texture::getConstant(minstr, f.min))
return luaL_error(L, "Invalid filter mode: %s", minstr);
if (!Texture::getConstant(magstr, f.mag))
return luaL_error(L, "Invalid filter mode: %s", magstr);
f.anisotropy = (float) luaL_optnumber(L, 4, 1.0);
luax_catchexcept(L, [&](){ video->setFilter(f); });
return 0;
}
int w_Video_getFilter(lua_State *L)
{
Video *video = luax_checkvideo(L, 1);
const Texture::Filter f = video->getFilter();
const char *minstr = nullptr;
const char *magstr = nullptr;
if (!Texture::getConstant(f.min, minstr))
return luaL_error(L, "Unknown filter mode.");
if (!Texture::getConstant(f.mag, magstr))
return luaL_error(L, "Unknown filter mode.");
lua_pushstring(L, minstr);
lua_pushstring(L, magstr);
lua_pushnumber(L, f.anisotropy);
return 3;
}
static const luaL_Reg functions[] =
{
{ "getStream", w_Video_getStream },
{ "getSource", w_Video_getSource },
{ "_setSource", w_Video_setSource },
{ "getWidth", w_Video_getWidth },
{ "getHeight", w_Video_getHeight },
{ "getDimensions", w_Video_getDimensions },
{ "setFilter", w_Video_setFilter },
{ "getFilter", w_Video_getFilter },
{ 0, 0 }
};
int luaopen_video(lua_State *L)
{
int ret = luax_register_type(L, GRAPHICS_VIDEO_ID, "Video", functions, nullptr);
luaL_loadbuffer(L, video_lua, sizeof(video_lua), "Video.lua");
luax_gettypemetatable(L, GRAPHICS_VIDEO_ID);
lua_call(L, 1, 0);
return ret;
}
} // opengl
} // graphics
} // love
+38
View File
@@ -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.
**/
#pragma once
// LOVE
#include "Video.h"
#include "common/runtime.h"
namespace love
{
namespace graphics
{
namespace opengl
{
int luaopen_video(lua_State *L);
} // opengl
} // graphics
} // love
@@ -0,0 +1,69 @@
R"luastring"--(
-- DO NOT REMOVE THE ABOVE LINE. It is used to load this file as a C++ string.
-- There is a matching delimiter at the bottom of the file.
--[[
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.
--]]
local Video_mt = ...
local Video = Video_mt.__index
function Video:loadRemote()
local stream = self:getStream()
local remote = love.video.newRemote()
stream:setSync(remote)
return remote
end
function Video:setSource(source)
self:_setSource(source)
if source then
self:getStream():setSync(source)
else
self:getStream():setSync(love.video.newRemote())
end
end
function Video:play()
return self:getStream():play()
end
function Video:pause()
return self:getStream():pause()
end
function Video:seek(offset)
return self:getStream():seek(offset)
end
function Video:rewind()
return self:getStream():rewind()
end
function Video:tell()
return self:getStream():tell()
end
function Video:isPlaying()
return self:getStream():isPlaying()
end
-- DO NOT REMOVE THE NEXT LINE. It is used to load this file as a C++ string.
--)luastring"--"
+2
View File
@@ -18,6 +18,8 @@
* 3. This notice may not be removed or altered from any source distribution.
**/
#include "common/config.h"
#include "Image.h"
#include "ImageData.h"
+6
View File
@@ -118,6 +118,9 @@ extern "C"
#if defined(LOVE_ENABLE_TOUCH)
extern int luaopen_love_touch(lua_State*);
#endif
#if defined(LOVE_ENABLE_VIDEO)
extern int luaopen_love_video(lua_State*);
#endif
#if defined(LOVE_ENABLE_WINDOW)
extern int luaopen_love_window(lua_State*);
#endif
@@ -174,6 +177,9 @@ static const luaL_Reg modules[] = {
#if defined(LOVE_ENABLE_TOUCH)
{ "love.touch", luaopen_love_touch },
#endif
#if defined(LOVE_ENABLE_VIDEO)
{ "love.video", luaopen_love_video },
#endif
#if defined(LOVE_ENABLE_WINDOW)
{ "love.window", luaopen_love_window },
#endif
+1 -1
View File
@@ -168,7 +168,7 @@ bool VorbisDecoder::accepts(const std::string &ext)
{
static const std::string supported[] =
{
"ogg", "oga", ""
"ogg", "oga", "ogv", ""
};
for (int i = 0; !(supported[i].empty()); i++)
+9 -2
View File
@@ -56,7 +56,6 @@ Timer::Timer()
, fpsUpdateFrequency(1)
, frames(0)
, dt(0)
, timerPeriod(getTimerPeriod())
{
prevFpsUpdate = currTime = getTime();
}
@@ -115,8 +114,11 @@ double Timer::getTimerPeriod()
return 0;
}
double Timer::getTime() const
double Timer::getTimeSinceEpoch()
{
// The timer period (reciprocal of the frequency.)
static const double timerPeriod = getTimerPeriod();
#if defined(LOVE_LINUX)
double mt;
// Check for POSIX timers and monotonic clocks. If not supported, use the gettimeofday fallback.
@@ -143,5 +145,10 @@ double Timer::getTime() const
#endif
}
double Timer::getTime() const
{
return getTimeSinceEpoch();
}
} // timer
} // love
+1 -3
View File
@@ -77,6 +77,7 @@ public:
* @return The time (in seconds)
**/
virtual double getTime() const;
static double getTimeSinceEpoch();
private:
@@ -98,9 +99,6 @@ private:
// The current timestep.
double dt;
// The timer period (reciprocal of the frequency.)
const double timerPeriod;
// Returns the timer period on some platforms.
static double getTimerPeriod();
+53
View File
@@ -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
+168
View File
@@ -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
+131
View File
@@ -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
+122
View File
@@ -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
+81
View File
@@ -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
+402
View File
@@ -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
+101
View File
@@ -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
+86
View File
@@ -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
+38
View File
@@ -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
+131
View File
@@ -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
+35
View File
@@ -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
+2
View File
@@ -369,6 +369,7 @@ function love.init()
font = true,
thread = true,
window = true,
video = true,
},
console = false, -- Only relevant for windows.
identity = false,
@@ -425,6 +426,7 @@ function love.init()
"system",
"audio",
"image",
"video",
"font",
"window",
"graphics",
+2
View File
@@ -675,6 +675,7 @@ const unsigned char boot_lua[] =
0x09, 0x09, 0x09, 0x66, 0x6f, 0x6e, 0x74, 0x20, 0x3d, 0x20, 0x74, 0x72, 0x75, 0x65, 0x2c, 0x0a,
0x09, 0x09, 0x09, 0x74, 0x68, 0x72, 0x65, 0x61, 0x64, 0x20, 0x3d, 0x20, 0x74, 0x72, 0x75, 0x65, 0x2c, 0x0a,
0x09, 0x09, 0x09, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x20, 0x3d, 0x20, 0x74, 0x72, 0x75, 0x65, 0x2c, 0x0a,
0x09, 0x09, 0x09, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x20, 0x3d, 0x20, 0x74, 0x72, 0x75, 0x65, 0x2c, 0x0a,
0x09, 0x09, 0x7d, 0x2c, 0x0a,
0x09, 0x09, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x20, 0x3d, 0x20, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x2c,
0x20, 0x2d, 0x2d, 0x20, 0x4f, 0x6e, 0x6c, 0x79, 0x20, 0x72, 0x65, 0x6c, 0x65, 0x76, 0x61, 0x6e, 0x74, 0x20,
@@ -779,6 +780,7 @@ const unsigned char boot_lua[] =
0x09, 0x09, 0x22, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x22, 0x2c, 0x0a,
0x09, 0x09, 0x22, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x22, 0x2c, 0x0a,
0x09, 0x09, 0x22, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x22, 0x2c, 0x0a,
0x09, 0x09, 0x22, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x22, 0x2c, 0x0a,
0x09, 0x09, 0x22, 0x66, 0x6f, 0x6e, 0x74, 0x22, 0x2c, 0x0a,
0x09, 0x09, 0x22, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x22, 0x2c, 0x0a,
0x09, 0x09, 0x22, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x22, 0x2c, 0x0a,