mirror of
https://github.com/CorsixTH/CorsixTH.git
synced 2025-07-23 04:13:01 +02:00
Adds original movies from Theme Hospital to CorsixTH. See issue 1428 for more information.
This commit is contained in:
6
.gitattributes
vendored
6
.gitattributes
vendored
@@ -1,7 +1,9 @@
|
||||
* text=auto !eol
|
||||
AnimView/AnimView.ico -text
|
||||
AnimView/AnimView.rc -text
|
||||
CMake/COPYING-CMAKE-SCRIPTS -text
|
||||
CMake/FindDirectX.cmake -text
|
||||
CMake/FindFFmpeg.cmake -text
|
||||
CMake/FindPkgMacros.cmake -text
|
||||
CorsixTH/Bitmap/aux_ui.dat -text svneol=unset#unset
|
||||
CorsixTH/Bitmap/aux_ui.spec -text
|
||||
@@ -64,8 +66,12 @@ CorsixTH/Levels/original07.level -text
|
||||
CorsixTH/Levels/original11.level -text
|
||||
CorsixTH/Levels/original12.level -text
|
||||
CorsixTH/Lua/dialogs/fullscreen/graphs.lua -text
|
||||
CorsixTH/Lua/movie_player.lua -text
|
||||
CorsixTH/Original_Logo.svg -text
|
||||
CorsixTH/Original_Logo_Text.svg -text
|
||||
CorsixTH/Src/th_lua_movie.cpp -text
|
||||
CorsixTH/Src/th_movie.cpp -text
|
||||
CorsixTH/Src/th_movie.h -text
|
||||
CorsixTH/__MACOSX/SDLMain/._SDLMain.m -text
|
||||
DebianPackage/control -text
|
||||
DebianPackage/debian-binary -text
|
||||
|
22
CMake/COPYING-CMAKE-SCRIPTS
Normal file
22
CMake/COPYING-CMAKE-SCRIPTS
Normal file
@@ -0,0 +1,22 @@
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
3. The name of the author may not be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
150
CMake/FindFFmpeg.cmake
Normal file
150
CMake/FindFFmpeg.cmake
Normal file
@@ -0,0 +1,150 @@
|
||||
# vim: ts=2 sw=2
|
||||
# - Try to find the required ffmpeg components(default: AVFORMAT, AVUTIL, AVCODEC)
|
||||
#
|
||||
# Once done this will define
|
||||
# FFMPEG_FOUND - System has the all required components.
|
||||
# FFMPEG_INCLUDE_DIRS - Include directory necessary for using the required components headers.
|
||||
# FFMPEG_LIBRARIES - Link these to use the required ffmpeg components.
|
||||
# FFMPEG_DEFINITIONS - Compiler switches required for using the required ffmpeg components.
|
||||
#
|
||||
# For each of the components it will additionally set.
|
||||
# - AVCODEC
|
||||
# - AVDEVICE
|
||||
# - AVFORMAT
|
||||
# - AVUTIL
|
||||
# - POSTPROCESS
|
||||
# - SWSCALE
|
||||
# - SWRESAMPLE
|
||||
# the following variables will be defined
|
||||
# <component>_FOUND - System has <component>
|
||||
# <component>_INCLUDE_DIRS - Include directory necessary for using the <component> headers
|
||||
# <component>_LIBRARIES - Link these to use <component>
|
||||
# <component>_DEFINITIONS - Compiler switches required for using <component>
|
||||
# <component>_VERSION - The components version
|
||||
#
|
||||
# Copyright (c) 2006, Matthias Kretz, <kretz@kde.org>
|
||||
# Copyright (c) 2008, Alexander Neundorf, <neundorf@kde.org>
|
||||
# Copyright (c) 2011, Michael Jansen, <kde@michael-jansen.biz>
|
||||
#
|
||||
# Redistribution and use is allowed according to the terms of the BSD license.
|
||||
# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
|
||||
# The default components were taken from a survey over other FindFFMPEG.cmake files
|
||||
if (NOT FFmpeg_FIND_COMPONENTS)
|
||||
set(FFmpeg_FIND_COMPONENTS AVCODEC AVFORMAT AVUTIL)
|
||||
endif ()
|
||||
|
||||
#
|
||||
### Macro: set_component_found
|
||||
#
|
||||
# Marks the given component as found if both *_LIBRARIES AND *_INCLUDE_DIRS is present.
|
||||
#
|
||||
macro(set_component_found _component )
|
||||
if (${_component}_LIBRARIES AND ${_component}_INCLUDE_DIRS)
|
||||
# message(STATUS " - ${_component} found.")
|
||||
set(${_component}_FOUND TRUE)
|
||||
else ()
|
||||
# message(STATUS " - ${_component} not found.")
|
||||
endif ()
|
||||
endmacro()
|
||||
|
||||
#
|
||||
### Macro: find_component
|
||||
#
|
||||
# Checks for the given component by invoking pkgconfig and then looking up the libraries and
|
||||
# include directories.
|
||||
#
|
||||
macro(find_component _component _pkgconfig _library _header)
|
||||
|
||||
if (NOT WIN32)
|
||||
# use pkg-config to get the directories and then use these values
|
||||
# in the FIND_PATH() and FIND_LIBRARY() calls
|
||||
find_package(PkgConfig)
|
||||
if (PKG_CONFIG_FOUND)
|
||||
pkg_check_modules(PC_${_component} ${_pkgconfig})
|
||||
endif ()
|
||||
endif (NOT WIN32)
|
||||
|
||||
find_path(${_component}_INCLUDE_DIRS ${_header}
|
||||
HINTS
|
||||
${PC_LIB${_component}_INCLUDEDIR}
|
||||
${PC_LIB${_component}_INCLUDE_DIRS}
|
||||
PATH_SUFFIXES
|
||||
ffmpeg
|
||||
)
|
||||
|
||||
find_library(${_component}_LIBRARIES NAMES ${_library}
|
||||
HINTS
|
||||
${PC_LIB${_component}_LIBDIR}
|
||||
${PC_LIB${_component}_LIBRARY_DIRS}
|
||||
)
|
||||
|
||||
set(${_component}_DEFINITIONS ${PC_${_component}_CFLAGS_OTHER} CACHE STRING "The ${_component} CFLAGS.")
|
||||
set(${_component}_VERSION ${PC_${_component}_VERSION} CACHE STRING "The ${_component} version number.")
|
||||
|
||||
set_component_found(${_component})
|
||||
|
||||
mark_as_advanced(
|
||||
${_component}_INCLUDE_DIRS
|
||||
${_component}_LIBRARIES
|
||||
${_component}_DEFINITIONS
|
||||
${_component}_VERSION)
|
||||
|
||||
endmacro()
|
||||
|
||||
|
||||
# Check for cached results. If there are skip the costly part.
|
||||
if (NOT FFMPEG_LIBRARIES)
|
||||
|
||||
# Check for all possible component.
|
||||
find_component(AVCODEC libavcodec avcodec libavcodec/avcodec.h)
|
||||
find_component(AVFORMAT libavformat avformat libavformat/avformat.h)
|
||||
find_component(AVDEVICE libavdevice avdevice libavdevice/avdevice.h)
|
||||
find_component(AVUTIL libavutil avutil libavutil/avutil.h)
|
||||
find_component(SWSCALE libswscale swscale libswscale/swscale.h)
|
||||
find_component(POSTPROC libpostproc postproc libpostproc/postprocess.h)
|
||||
find_component(SWRESAMPLE libswresample swresample libswresample/swresample.h)
|
||||
|
||||
# Check if the required components were found and add their stuff to the FFMPEG_* vars.
|
||||
foreach (_component ${FFmpeg_FIND_COMPONENTS})
|
||||
if (${_component}_FOUND)
|
||||
# message(STATUS "Required component ${_component} present.")
|
||||
set(FFMPEG_LIBRARIES ${FFMPEG_LIBRARIES} ${${_component}_LIBRARIES})
|
||||
set(FFMPEG_DEFINITIONS ${FFMPEG_DEFINITIONS} ${${_component}_DEFINITIONS})
|
||||
list(APPEND FFMPEG_INCLUDE_DIRS ${${_component}_INCLUDE_DIRS})
|
||||
else ()
|
||||
# message(STATUS "Required component ${_component} missing.")
|
||||
endif ()
|
||||
endforeach ()
|
||||
|
||||
# Build the include path with duplicates removed.
|
||||
if (FFMPEG_INCLUDE_DIRS)
|
||||
list(REMOVE_DUPLICATES FFMPEG_INCLUDE_DIRS)
|
||||
endif ()
|
||||
|
||||
# cache the vars.
|
||||
set(FFMPEG_INCLUDE_DIRS ${FFMPEG_INCLUDE_DIRS} CACHE STRING "The FFmpeg include directories." FORCE)
|
||||
set(FFMPEG_LIBRARIES ${FFMPEG_LIBRARIES} CACHE STRING "The FFmpeg libraries." FORCE)
|
||||
set(FFMPEG_DEFINITIONS ${FFMPEG_DEFINITIONS} CACHE STRING "The FFmpeg cflags." FORCE)
|
||||
|
||||
mark_as_advanced(FFMPEG_INCLUDE_DIRS
|
||||
FFMPEG_LIBRARIES
|
||||
FFMPEG_DEFINITIONS)
|
||||
|
||||
endif ()
|
||||
|
||||
# Now set the noncached _FOUND vars for the components.
|
||||
foreach (_component AVCODEC AVDEVICE AVFORMAT AVUTIL POSTPROCESS SWSCALE SWRESAMPLE)
|
||||
set_component_found(${_component})
|
||||
endforeach ()
|
||||
|
||||
# Compile the list of required vars
|
||||
set(_FFmpeg_REQUIRED_VARS FFMPEG_LIBRARIES FFMPEG_INCLUDE_DIRS)
|
||||
foreach (_component ${FFmpeg_FIND_COMPONENTS})
|
||||
list(APPEND _FFmpeg_REQUIRED_VARS ${_component}_LIBRARIES ${_component}_INCLUDE_DIRS)
|
||||
endforeach ()
|
||||
|
||||
# Give a nice error message if some of the required vars are missing.
|
||||
find_package_handle_standard_args(FFmpeg DEFAULT_MSG ${_FFmpeg_REQUIRED_VARS})
|
@@ -12,6 +12,7 @@
|
||||
|
||||
PROJECT(CorsixTH_Top_Level)
|
||||
CMAKE_MINIMUM_REQUIRED(VERSION 2.6)
|
||||
SET(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/CMake)
|
||||
INCLUDE(CheckIncludeFiles)
|
||||
SET(CORSIX_TH_DONE_TOP_LEVEL_CMAKE ON)
|
||||
|
||||
@@ -20,6 +21,7 @@ OPTION(WITH_OPENGL "Activate OpenGL Renderer" OFF)
|
||||
OPTION(WITH_DIRECTX "Activate DirectX Renderer" OFF)
|
||||
OPTION(WITH_SDL "Activate SDL Renderer" ON) # our default option
|
||||
OPTION(WITH_AUDIO "Activate Sound" ON) # enabled by default
|
||||
OPTION(WITH_MOVIES "Activate in game movies" ON)
|
||||
OPTION(WITH_FREETYPE2 "Enhanced Font Support" ON)
|
||||
OPTION(BUILD_ANIMVIEWER "Build the animation viewer as part of the build process" OFF)
|
||||
OPTION(BUILD_MAPEDITOR "Build the map editor as part of the build process" OFF)
|
||||
@@ -66,6 +68,14 @@ ELSE()
|
||||
MESSAGE("Note: SDL audio is disabled")
|
||||
ENDIF(WITH_AUDIO)
|
||||
|
||||
IF(WITH_MOVIES)
|
||||
SET(CORSIX_TH_USE_FFMPEG ON)
|
||||
MESSAGE("Note: FFMPEG video is enabled (default)")
|
||||
ELSE()
|
||||
SET(CORSIX_TH_USE_FFMPEG OFF)
|
||||
MESSAGE("Note: FFMPEG video is disabled")
|
||||
ENDIF(WITH_MOVIES)
|
||||
|
||||
IF(WITH_FREETYPE2)
|
||||
SET(CORSIX_TH_USE_FREETYPE2 ON)
|
||||
MESSAGE("Note: FreeType2 is enabled (default)")
|
||||
|
@@ -137,6 +137,20 @@ IF(CORSIX_TH_USE_SDL_MIXER)
|
||||
ENDIF(SDLMIXER_FOUND)
|
||||
ENDIF(CORSIX_TH_USE_SDL_MIXER)
|
||||
|
||||
message( STATUS "CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}" )
|
||||
|
||||
# Find FFMPEG
|
||||
IF(CORSIX_TH_USE_FFMPEG)
|
||||
FIND_PACKAGE(FFmpeg COMPONENTS AVFORMAT AVCODEC AVUTIL SWSCALE SWRESAMPLE REQUIRED)
|
||||
IF(FFMPEG_FOUND)
|
||||
TARGET_LINK_LIBRARIES(CorsixTH ${FFMPEG_LIBRARIES})
|
||||
INCLUDE_DIRECTORIES(${FFMPEG_INCLUDE_DIRS})
|
||||
message(" FFmpeg found")
|
||||
ELSE(FFMPEG_FOUND)
|
||||
message("Error: FFmpeg library not found, even though it was selected to be included")
|
||||
ENDIF(FFMPEG_FOUND)
|
||||
ENDIF(CORSIX_TH_USE_FFMPEG)
|
||||
|
||||
# Find Freetype2
|
||||
IF(CORSIX_TH_USE_FREETYPE2)
|
||||
FIND_PACKAGE(Freetype REQUIRED)
|
||||
@@ -163,7 +177,6 @@ ENDIF(CORSIX_TH_USE_OGL_RENDERER)
|
||||
|
||||
# Find DirectX
|
||||
IF(CORSIX_TH_USE_DX9_RENDERER)
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/CMake")
|
||||
FIND_PACKAGE(DirectX REQUIRED)
|
||||
IF(DirectX_FOUND)
|
||||
TARGET_LINK_LIBRARIES(CorsixTH ${DirectX_LIBRARY})
|
||||
@@ -174,6 +187,15 @@ IF(CORSIX_TH_USE_DX9_RENDERER)
|
||||
message(FATAL_ERROR "Error: DirectX library not found, it is required to build (consider changing choice of renderer)")
|
||||
ENDIF(DirectX_FOUND)
|
||||
ENDIF(CORSIX_TH_USE_DX9_RENDERER)
|
||||
|
||||
# Find msinttypes for MSVC
|
||||
IF(MSVC)
|
||||
FIND_PATH(MSINTTYPES_INCLUDE_DIRS "inttypes.h" NO_DEFAULT_PATH)
|
||||
MARK_AS_ADVANCED(MSINTTYPES_INCLUDE_DIRS)
|
||||
MESSAGE(STATUS "Adding include directory: ${MSINTTYPES_INCLUDE_DIRS}")
|
||||
INCLUDE_DIRECTORIES(${MSINTTYPES_INCLUDE_DIRS})
|
||||
ENDIF(MSVC)
|
||||
|
||||
# Declaration of the install process
|
||||
IF(APPLE)
|
||||
#Just use the prefix as it's sufficient to just set the prefix to /Applications on Mac.
|
||||
|
@@ -49,6 +49,8 @@ function App:App()
|
||||
motion = self.onMouseMove,
|
||||
active = self.onWindowActive,
|
||||
music_over = self.onMusicOver,
|
||||
movie_allocate_picture = self.onMovieAllocatePicture,
|
||||
movie_over = self.onMovieOver
|
||||
}
|
||||
self.strings = {}
|
||||
self.savegame_version = SAVEGAME_VERSION
|
||||
@@ -203,6 +205,13 @@ function App:init()
|
||||
self.audio = Audio(self)
|
||||
self.audio:init()
|
||||
|
||||
-- Load movie player
|
||||
dofile "movie_player"
|
||||
self.moviePlayer = MoviePlayer(self, self.audio)
|
||||
if good_install_folder then
|
||||
self.moviePlayer:init()
|
||||
end
|
||||
|
||||
-- Load strings before UI and before additional Lua
|
||||
dofile "strings"
|
||||
dofile "string_extensions"
|
||||
@@ -265,6 +274,9 @@ function App:init()
|
||||
self:loadLevel("")
|
||||
else
|
||||
self:loadMainMenu()
|
||||
if self.config.play_intro then
|
||||
self.moviePlayer:playIntro()
|
||||
end
|
||||
-- If we couldn't properly load the language, show an information dialog
|
||||
if not language_load_success then
|
||||
-- At this point we know the language is english, so no use having
|
||||
@@ -400,7 +412,11 @@ function App:loadLevel(level, ...)
|
||||
-- Load UI
|
||||
self.ui = GameUI(self, self.world:getLocalPlayerHospital())
|
||||
self.world:setUI(self.ui) -- Function call allows world to set up its keyHandlers
|
||||
|
||||
|
||||
if tonumber(level) then
|
||||
self.moviePlayer:playAdvanceMovie(level)
|
||||
end
|
||||
|
||||
-- Now restore progress from previous levels.
|
||||
if carry_to_next_level then
|
||||
self.world:initFromPreviousLevel(carry_to_next_level)
|
||||
@@ -749,10 +765,12 @@ function App:dispatch(evt_type, ...)
|
||||
end
|
||||
|
||||
function App:onTick(...)
|
||||
if self.world then
|
||||
self.world:onTick(...)
|
||||
if(not self.moviePlayer.playing) then
|
||||
if self.world then
|
||||
self.world:onTick(...)
|
||||
end
|
||||
self.ui:onTick(...)
|
||||
end
|
||||
self.ui:onTick(...)
|
||||
return true -- tick events always result in a repaint
|
||||
end
|
||||
|
||||
@@ -762,9 +780,13 @@ local fps_sum = 0 -- Sum of fps_history array
|
||||
local fps_next = 1 -- Used to loop through fps_history when [over]writing
|
||||
|
||||
function App:drawFrame()
|
||||
self.video:startFrame()
|
||||
self.ui:draw(self.video)
|
||||
self.video:endFrame()
|
||||
if(self.moviePlayer.playing) then
|
||||
self.moviePlayer:refresh()
|
||||
else
|
||||
self.video:startFrame()
|
||||
self.ui:draw(self.video)
|
||||
self.video:endFrame()
|
||||
end
|
||||
|
||||
if self.config.track_fps then
|
||||
fps_sum = fps_sum - fps_history[fps_next]
|
||||
@@ -808,6 +830,14 @@ function App:onMusicOver(...)
|
||||
return self.audio:onMusicOver(...)
|
||||
end
|
||||
|
||||
function App:onMovieAllocatePicture(...)
|
||||
return self.moviePlayer:onMovieAllocatePicture(...)
|
||||
end
|
||||
|
||||
function App:onMovieOver(...)
|
||||
self.moviePlayer:onMovieOver(...)
|
||||
end
|
||||
|
||||
function App:checkInstallFolder()
|
||||
self.fs = FileSystem()
|
||||
local status, err
|
||||
|
@@ -36,6 +36,7 @@ function Audio:Audio(app)
|
||||
}
|
||||
self.has_bg_music = false
|
||||
self.not_loaded = not app.config.audio
|
||||
self.abort_bg_music = false
|
||||
end
|
||||
|
||||
local function GetFileData(path)
|
||||
@@ -383,6 +384,16 @@ function Audio:pauseBackgroundTrack()
|
||||
self:notifyJukebox()
|
||||
end
|
||||
|
||||
function Audio:stopBackgroundMusic()
|
||||
self:stopBackgroundTrack()
|
||||
self.abort_bg_music = true
|
||||
end
|
||||
|
||||
function Audio:resumeBackgroundMusic()
|
||||
self.abort_bg_music = false
|
||||
self:playRandomBackgroundTrack()
|
||||
end
|
||||
|
||||
function Audio:stopBackgroundTrack()
|
||||
if self.background_paused then
|
||||
-- unpause first in order to clear the backupped volume
|
||||
@@ -431,12 +442,14 @@ function Audio:playBackgroundTrack(index)
|
||||
end)
|
||||
return
|
||||
end
|
||||
SDL.audio.setMusicVolume(self.app.config.music_volume)
|
||||
assert(SDL.audio.playMusic(music))
|
||||
self.background_music = music
|
||||
-- Update configuration that we want music
|
||||
self.app.config.play_music = not not self.background_music
|
||||
self:notifyJukebox()
|
||||
if not self.abort_bg_music then
|
||||
SDL.audio.setMusicVolume(self.app.config.music_volume)
|
||||
assert(SDL.audio.playMusic(music))
|
||||
self.background_music = music
|
||||
-- Update configuration that we want music
|
||||
self.app.config.play_music = not not self.background_music
|
||||
self:notifyJukebox()
|
||||
end
|
||||
end
|
||||
|
||||
function Audio:onMusicOver()
|
||||
@@ -483,3 +496,17 @@ function Audio:notifyJukebox()
|
||||
jukebox:updatePlayButton()
|
||||
end
|
||||
end
|
||||
|
||||
function Audio:reserveChannel()
|
||||
if self.sound_fx then
|
||||
return self.sound_fx:reserveChannel()
|
||||
else
|
||||
return -1
|
||||
end
|
||||
end
|
||||
|
||||
function Audio:releaseChannel(channel)
|
||||
if self.sound_fx and channel > -1 then
|
||||
self.sound_fx:releaseChannel(channel)
|
||||
end
|
||||
end
|
||||
|
@@ -105,6 +105,8 @@ local config_defaults = {
|
||||
adviser_disabled = false,
|
||||
allow_user_actions_while_paused = false,
|
||||
warmth_colors_display_default = 1,
|
||||
movies = true,
|
||||
play_intro = true
|
||||
}
|
||||
local fi = io.open(config_filename, "r")
|
||||
local config_values = {}
|
||||
@@ -176,6 +178,9 @@ sound_volume = ]=].. tostring(config_values.sound_volume) ..[=[
|
||||
-- Adviser on/off: If you set this setting to true the adviser will no longer
|
||||
-- pop up.
|
||||
adviser_disabled = ]=].. tostring(config_values.adviser_disabled) ..[=[
|
||||
--
|
||||
-- Intro movie: By default enabled
|
||||
play_intro = ]=].. tostring(config_values.play_intro) ..[=[
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
@@ -292,6 +297,13 @@ warmth_colors_display_default = ]=].. tostring(config_values.warmth_colors_displ
|
||||
-- present in the original game. Examples include Russian, Chinese and Polish.
|
||||
-- unicode_font = [[X:\ThemeHospital\font.ttc]]
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Movie global on/off switch.
|
||||
-- Note that movies will also be disabled if CorsixTH was compiled without the
|
||||
-- FFMPEG library.
|
||||
movies = ]=].. tostring(config_values.movies) ..[=[
|
||||
-------------------------------------------------------------------------------
|
||||
]=])
|
||||
end
|
||||
end
|
||||
|
@@ -168,6 +168,7 @@ function UIFax:choice(choice)
|
||||
-- TODO: Allow some kind of custom campaign with custom levels
|
||||
end
|
||||
elseif choice == "return_to_main_menu" then
|
||||
self.ui.app.moviePlayer:playWinMovie()
|
||||
self.ui.app:loadMainMenu()
|
||||
end
|
||||
self.icon:removeMessage()
|
||||
|
@@ -203,7 +203,13 @@ function GameUI:onKeyDown(code, rawchar)
|
||||
end
|
||||
rawchar = self.key_code_to_rawchar[code] -- UI may have translated rawchar
|
||||
local key = self:_translateKeyCode(code, rawchar)
|
||||
|
||||
--abort movies
|
||||
if self.app.moviePlayer.playing then
|
||||
if key == "esc" or key == " " then
|
||||
self.app.moviePlayer:stop()
|
||||
end
|
||||
return true
|
||||
end
|
||||
--Maybe the player wants to abort an "about to edit room" action
|
||||
if key == "esc" and self.edit_room then
|
||||
self:setEditRoom(false)
|
||||
@@ -381,6 +387,9 @@ end
|
||||
-- TODO: try to remove duplication with UI:onMouseMove
|
||||
function GameUI:onMouseMove(x, y, dx, dy)
|
||||
local repaint = UpdateCursorPosition(self.app.video, x, y)
|
||||
if self.app.moviePlayer.playing then
|
||||
return false
|
||||
end
|
||||
|
||||
self.cursor_x = x
|
||||
self.cursor_y = y
|
||||
@@ -460,6 +469,10 @@ function GameUI:onMouseMove(x, y, dx, dy)
|
||||
end
|
||||
|
||||
function GameUI:onMouseUp(code, x, y)
|
||||
if self.app.moviePlayer.playing then
|
||||
return UI.onMouseUp(self, code, x, y)
|
||||
end
|
||||
|
||||
if code == 4 or code == 5 then
|
||||
-- Mouse wheel
|
||||
local window = self:getWindow(UIFullscreen)
|
||||
|
183
CorsixTH/Lua/movie_player.lua
Normal file
183
CorsixTH/Lua/movie_player.lua
Normal file
@@ -0,0 +1,183 @@
|
||||
--[[ Copyright (c) 2012 Stephen Baker
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE. --]]
|
||||
|
||||
--! Layer which handles the Lua-facing side of loading and playing video.
|
||||
|
||||
local TH = require "TH"
|
||||
local pathsep = package.config:sub(1, 1)
|
||||
|
||||
class "MoviePlayer"
|
||||
|
||||
function MoviePlayer:MoviePlayer(app, audio)
|
||||
self.app = app
|
||||
self.audio = audio
|
||||
self.playing = false
|
||||
self.can_skip = true
|
||||
self.holding_bg_music = false
|
||||
self.channel = -1
|
||||
self.lose_movies = {}
|
||||
self.advance_movies = {}
|
||||
self.intro_movie = nil
|
||||
self.win_movie = nil
|
||||
end
|
||||
|
||||
function MoviePlayer:init()
|
||||
self.moviePlayer = TH.moviePlayer()
|
||||
|
||||
--find movies in Anims folder
|
||||
local num
|
||||
local movie
|
||||
local movies = self.app.fs:listFiles("Anims");
|
||||
if movies then
|
||||
for _,movie in pairs(movies) do
|
||||
--lose level movies
|
||||
if movie:upper():match(pathsep .. "LOSE%d+%.[^" .. pathsep .."]+$") then
|
||||
table.insert(self.lose_movies, movie)
|
||||
end
|
||||
--advance level movies
|
||||
num = tonumber(movie:upper():match(pathsep .. "AREA(%d+)V%.[^" .. pathsep .."]+$"), 10)
|
||||
if(num ~= nil) then
|
||||
self.advance_movies[num] = movie
|
||||
end
|
||||
--win game movie
|
||||
if movie:upper():match(pathsep .. "WINGAME%.[^" .. pathsep .. "]+$") then
|
||||
self.win_movie = movie
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--find intro
|
||||
movies = self.app.fs:listFiles("Intro")
|
||||
if movies then
|
||||
for _,movie in pairs(movies) do
|
||||
if movie:upper():match(pathsep .. "INTRO%.SM4$") then
|
||||
self.intro_movie = movie
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function MoviePlayer:playIntro()
|
||||
self:playMovie(self.intro_movie)
|
||||
end
|
||||
|
||||
function MoviePlayer:playWinMovie()
|
||||
self:playMovie(self.win_movie)
|
||||
end
|
||||
|
||||
function MoviePlayer:playAdvanceMovie(level)
|
||||
local filename = self.advance_movies[level]
|
||||
self.can_skip = false
|
||||
self.audio:stopBackgroundMusic()
|
||||
self.holding_bg_music = true
|
||||
if level == 12 then
|
||||
self.audio:playSound("DICE122M.WAV")
|
||||
else
|
||||
self.audio:playSound("DICEYFIN.WAV")
|
||||
end
|
||||
self:playMovie(filename)
|
||||
end
|
||||
|
||||
function MoviePlayer:playLoseMovie()
|
||||
if #self.lose_movies > 0 then
|
||||
local filename = self.lose_movies[math.random(#self.lose_movies)]
|
||||
self:playMovie(filename)
|
||||
end
|
||||
end
|
||||
|
||||
function MoviePlayer:playMovie(filename)
|
||||
local x, y, w, h = 0
|
||||
local screen_w, screen_h = self.app.config.width, self.app.config.height
|
||||
local ar
|
||||
|
||||
if(not self.app.config.movies or filename == nil) then
|
||||
return
|
||||
end
|
||||
|
||||
self.moviePlayer:load(filename)
|
||||
|
||||
if self.moviePlayer:hasAudioTrack() then
|
||||
self.channel = self.audio:reserveChannel()
|
||||
self.audio:stopBackgroundMusic()
|
||||
self.holding_bg_music = true
|
||||
end
|
||||
|
||||
-- calculate target dimensions
|
||||
local native_w = self.moviePlayer:getNativeWidth()
|
||||
local native_h = self.moviePlayer:getNativeHeight()
|
||||
if(native_w ~= 0 and native_h ~= 0) then
|
||||
ar = native_w / native_h
|
||||
if(math.abs((screen_w / screen_h) - ar) < 0.001) then
|
||||
x, y = 0, 0
|
||||
w, h = screen_w, screen_h
|
||||
else
|
||||
if(screen_w > screen_h / native_h * native_w) then
|
||||
w = screen_h / native_h * native_w
|
||||
h = screen_h
|
||||
x = (screen_w - w) / 2
|
||||
y = 0
|
||||
else
|
||||
w = screen_w
|
||||
h = screen_w / native_w * native_h
|
||||
x = 0
|
||||
y = (screen_h - h) / 2
|
||||
end
|
||||
end
|
||||
else
|
||||
x, y = 0, 0
|
||||
w, h = screen_w, screen_h
|
||||
end
|
||||
|
||||
self.app.video:startFrame()
|
||||
self.app.video:fillBlack()
|
||||
self.app.video:endFrame()
|
||||
--TODO: Add text e.g. for newspaper headlines
|
||||
self.moviePlayer:play(x, y, w, h, self.channel)
|
||||
self.playing = true
|
||||
end
|
||||
|
||||
function MoviePlayer:onMovieAllocatePicture()
|
||||
self.moviePlayer:allocatePicture()
|
||||
end
|
||||
|
||||
function MoviePlayer:onMovieOver()
|
||||
self.moviePlayer:unload()
|
||||
self.app.ui:resetVideo()
|
||||
if self.channel >= 0 then
|
||||
self.audio:releaseChannel(self.channel)
|
||||
self.channel = -1
|
||||
end
|
||||
if self.holding_bg_music then
|
||||
self.audio:resumeBackgroundMusic()
|
||||
end
|
||||
-- restore defaults
|
||||
self.playing = false
|
||||
self.can_skip = true
|
||||
end
|
||||
|
||||
function MoviePlayer:refresh()
|
||||
self.moviePlayer:refresh()
|
||||
end
|
||||
|
||||
function MoviePlayer:stop()
|
||||
if self.can_skip then
|
||||
self.moviePlayer:stop()
|
||||
end
|
||||
end
|
@@ -415,6 +415,19 @@ function UI:unregisterTextBox(box)
|
||||
end
|
||||
end
|
||||
|
||||
function UI:resetVideo()
|
||||
local width, height = self.app.config.width, self.app.config.height
|
||||
|
||||
self.app.video:endFrame()
|
||||
self.app.video = TH.surface(width, height, unpack(self.app.modes))
|
||||
self.app.gfx:updateTarget(self.app.video)
|
||||
self.app.video:startFrame()
|
||||
-- Redraw cursor
|
||||
local cursor = self.cursor
|
||||
self.cursor = nil
|
||||
self:setCursor(cursor)
|
||||
end
|
||||
|
||||
function UI:changeResolution(width, height)
|
||||
local old_width, old_height = self.app.config.width, self.app.config.height
|
||||
self.app.video:endFrame()
|
||||
@@ -552,6 +565,13 @@ function UI:onKeyDown(code, rawchar)
|
||||
|
||||
-- Apply key-remapping and normalisation
|
||||
local key = self.key_codes[code] or rawchar:lower()
|
||||
if self.app.moviePlayer.playing then
|
||||
if key == "esc" or key == " " then
|
||||
self.app.moviePlayer:stop()
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
do
|
||||
local mapped_button = self.key_to_button_remaps[key]
|
||||
if mapped_button then
|
||||
@@ -649,6 +669,12 @@ end
|
||||
function UI:onMouseDown(code, x, y)
|
||||
local repaint = false
|
||||
local button = self.button_codes[code] or code
|
||||
if self.app.moviePlayer.playing then
|
||||
if button == "left" then
|
||||
self.app.moviePlayer:stop()
|
||||
end
|
||||
return true
|
||||
end
|
||||
if self.cursor_entity == nil and self.down_count == 0
|
||||
and self.cursor == self.default_cursor then
|
||||
self:setCursor(self.down_cursor)
|
||||
|
@@ -1398,6 +1398,7 @@ end
|
||||
--!param limit (number) [optional] The number the player went over/under which caused him to lose.
|
||||
function World:loseGame(player_no, reason, limit)
|
||||
if player_no == 1 then -- TODO: Multiplayer
|
||||
self.ui.app.moviePlayer:playLoseMovie()
|
||||
local message = {_S.information.level_lost[1]}
|
||||
if reason then
|
||||
message[2] = _S.information.level_lost[2]
|
||||
|
@@ -61,6 +61,12 @@ SOFTWARE.
|
||||
// any music.
|
||||
#cmakedefine CORSIX_TH_USE_SDL_MIXER
|
||||
|
||||
/** Movie options **/
|
||||
// FFMPEG is used for in game movies. If this library is not present on your
|
||||
// system, then you can comment out the next line and the game will not have
|
||||
// movies.
|
||||
#cmakedefine CORSIX_TH_USE_FFMPEG
|
||||
|
||||
/** Font options **/
|
||||
// FreeType2 can be used for font support beyond the CP437 bitmap fonts which
|
||||
// come with Theme Hospital. It must be used if translations like Russian or
|
||||
@@ -84,6 +90,9 @@ SOFTWARE.
|
||||
#endif
|
||||
|
||||
/** Standard includes **/
|
||||
#ifndef __STDC_CONSTANT_MACROS
|
||||
# define __STDC_CONSTANT_MACROS
|
||||
#endif
|
||||
#include <stddef.h>
|
||||
#cmakedefine CORSIX_TH_HAS_STDINT_H
|
||||
#cmakedefine CORSIX_TH_HAS_MALLOC_H
|
||||
|
@@ -34,6 +34,10 @@ SOFTWARE.
|
||||
#define SDL_USEREVENT_MUSIC_OVER (SDL_USEREVENT + 1)
|
||||
// SDL_USEREVENT_CPCALL - calls lua_cpcall with SDL_Event user.data1 and data2
|
||||
#define SDL_USEREVENT_CPCALL (SDL_USEREVENT + 2)
|
||||
// SDL_USEREVENT_ALLOCATE_MOVIE_PICTURE - tells the engine that a picture in the movie picture buffer needs to be allocated
|
||||
#define SDL_USEREVENT_ALLOCATE_MOVIE_PICTURE (SDL_USEREVENT + 3)
|
||||
// SDL USEREVENT_MOVIE_OVER - informs script of THMovie movie finishing
|
||||
#define SDL_USEREVENT_MOVIE_OVER (SDL_USEREVENT + 4)
|
||||
|
||||
int luaopen_sdl(lua_State *L);
|
||||
|
||||
|
@@ -214,6 +214,14 @@ static int l_mainloop(lua_State *L)
|
||||
do_timer = true;
|
||||
nargs = 0;
|
||||
break;
|
||||
case SDL_USEREVENT_ALLOCATE_MOVIE_PICTURE:
|
||||
lua_pushliteral(dispatcher, "movie_allocate_picture");
|
||||
nargs = 1;
|
||||
break;
|
||||
case SDL_USEREVENT_MOVIE_OVER:
|
||||
lua_pushliteral(dispatcher, "movie_over");
|
||||
nargs = 1;
|
||||
break;
|
||||
default:
|
||||
nargs = 0;
|
||||
break;
|
||||
|
@@ -28,6 +28,7 @@ void THLuaRegisterAnims(const THLuaRegisterState_t *pState);
|
||||
void THLuaRegisterGfx(const THLuaRegisterState_t *pState);
|
||||
void THLuaRegisterMap(const THLuaRegisterState_t *pState);
|
||||
void THLuaRegisterSound(const THLuaRegisterState_t *pState);
|
||||
void THLuaRegisterMovie(const THLuaRegisterState_t *pState);
|
||||
void THLuaRegisterStrings(const THLuaRegisterState_t *pState);
|
||||
void THLuaRegisterUI(const THLuaRegisterState_t *pState);
|
||||
|
||||
@@ -312,6 +313,7 @@ int luaopen_th(lua_State *L)
|
||||
THLuaRegisterGfx(pState);
|
||||
THLuaRegisterAnims(pState);
|
||||
THLuaRegisterSound(pState);
|
||||
THLuaRegisterMovie(pState);
|
||||
THLuaRegisterStrings(pState);
|
||||
THLuaRegisterUI(pState);
|
||||
|
||||
|
@@ -193,6 +193,11 @@ template <> struct luaT_classinfo<THSoundEffects> {
|
||||
static inline const char* name() {return "SoundEffects";}
|
||||
};
|
||||
|
||||
class THMovie;
|
||||
template <> struct luaT_classinfo<THMovie> {
|
||||
static inline const char* name() {return "Movie";}
|
||||
};
|
||||
|
||||
struct THWindowBase_t;
|
||||
template <> struct luaT_classinfo<THWindowBase_t> {
|
||||
static inline const char* name() {return "WindowBase";}
|
||||
|
@@ -49,6 +49,7 @@ enum eTHLuaMetatable
|
||||
MT_Cursor,
|
||||
MT_SoundArc,
|
||||
MT_SoundFx,
|
||||
MT_Movie,
|
||||
MT_String,
|
||||
MT_WindowBase,
|
||||
MT_SpriteList,
|
||||
|
109
CorsixTH/Src/th_lua_movie.cpp
Normal file
109
CorsixTH/Src/th_lua_movie.cpp
Normal file
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
Copyright (c) 2012 Stephen Baker
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "th_lua_internal.h"
|
||||
#include "th_movie.h"
|
||||
|
||||
static int l_movie_new(lua_State *L)
|
||||
{
|
||||
luaT_stdnew<THMovie>(L, luaT_environindex, true);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int l_movie_load(lua_State *L)
|
||||
{
|
||||
THMovie *pMovie = luaT_testuserdata<THMovie>(L);
|
||||
const char* filepath = lua_tolstring(L, 2, NULL);
|
||||
pMovie->load(filepath);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int l_movie_play(lua_State *L)
|
||||
{
|
||||
THMovie *pMovie = luaT_testuserdata<THMovie>(L);
|
||||
pMovie->play(luaL_checkinteger(L, 2), luaL_checkinteger(L, 3), luaL_checkinteger(L, 4), luaL_checkinteger(L, 5), luaL_checkinteger(L, 6));
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int l_movie_unload(lua_State *L)
|
||||
{
|
||||
THMovie *pMovie = luaT_testuserdata<THMovie>(L);
|
||||
pMovie->unload();
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int l_movie_get_native_width(lua_State *L)
|
||||
{
|
||||
THMovie *pMovie = luaT_testuserdata<THMovie>(L);
|
||||
lua_pushinteger(L, pMovie->getNativeWidth());
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int l_movie_get_native_height(lua_State *L)
|
||||
{
|
||||
THMovie *pMovie = luaT_testuserdata<THMovie>(L);
|
||||
lua_pushinteger(L, pMovie->getNativeHeight());
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int l_movie_has_audio_track(lua_State *L)
|
||||
{
|
||||
THMovie *pMovie = luaT_testuserdata<THMovie>(L);
|
||||
lua_pushboolean(L, pMovie->hasAudioTrack());
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int l_movie_allocate_picture(lua_State *L)
|
||||
{
|
||||
THMovie *pMovie = luaT_testuserdata<THMovie>(L);
|
||||
pMovie->allocatePicture();
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int l_movie_stop(lua_State *L)
|
||||
{
|
||||
THMovie *pVideo = luaT_testuserdata<THMovie>(L);
|
||||
pVideo->stop();
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int l_movie_refresh(lua_State *L)
|
||||
{
|
||||
THMovie *pMovie = luaT_testuserdata<THMovie>(L);
|
||||
pMovie->refresh();
|
||||
return 1;
|
||||
}
|
||||
|
||||
void THLuaRegisterMovie(const THLuaRegisterState_t *pState)
|
||||
{
|
||||
luaT_class(THMovie, l_movie_new, "moviePlayer", MT_Movie);
|
||||
luaT_setfunction(l_movie_stop, "stop");
|
||||
luaT_setfunction(l_movie_allocate_picture, "allocatePicture");
|
||||
luaT_setfunction(l_movie_refresh, "refresh");
|
||||
luaT_setfunction(l_movie_load, "load");
|
||||
luaT_setfunction(l_movie_unload, "unload");
|
||||
luaT_setfunction(l_movie_play, "play");
|
||||
luaT_setfunction(l_movie_get_native_width, "getNativeWidth");
|
||||
luaT_setfunction(l_movie_get_native_height, "getNativeHeight");
|
||||
luaT_setfunction(l_movie_has_audio_track, "hasAudioTrack");
|
||||
luaT_endclass();
|
||||
}
|
@@ -214,6 +214,22 @@ static int l_soundfx_set_camera(lua_State *L)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_soundfx_reserve_channel(lua_State *L)
|
||||
{
|
||||
int iChannel;
|
||||
THSoundEffects *pEffects = luaT_testuserdata<THSoundEffects>(L);
|
||||
iChannel = pEffects->reserveChannel();
|
||||
lua_pushinteger(L, iChannel);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int l_soundfx_release_channel(lua_State *L)
|
||||
{
|
||||
THSoundEffects *pEffects = luaT_testuserdata<THSoundEffects>(L);
|
||||
pEffects->releaseChannel(luaL_checkinteger(L, 2));
|
||||
return 1;
|
||||
}
|
||||
|
||||
void THLuaRegisterSound(const THLuaRegisterState_t *pState)
|
||||
{
|
||||
// Sound Archive
|
||||
@@ -233,5 +249,7 @@ void THLuaRegisterSound(const THLuaRegisterState_t *pState)
|
||||
luaT_setfunction(l_soundfx_set_sound_volume, "setSoundVolume");
|
||||
luaT_setfunction(l_soundfx_set_sound_effects_on, "setSoundEffectsOn");
|
||||
luaT_setfunction(l_soundfx_set_camera, "setCamera");
|
||||
luaT_setfunction(l_soundfx_reserve_channel, "reserveChannel");
|
||||
luaT_setfunction(l_soundfx_release_channel, "releaseChannel");
|
||||
luaT_endclass();
|
||||
}
|
||||
|
954
CorsixTH/Src/th_movie.cpp
Normal file
954
CorsixTH/Src/th_movie.cpp
Normal file
@@ -0,0 +1,954 @@
|
||||
/*
|
||||
Copyright (c) 2012 Stephen Baker
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "th_movie.h"
|
||||
#include "config.h"
|
||||
#include "lua_sdl.h"
|
||||
#ifdef CORSIX_TH_USE_FFMPEG
|
||||
|
||||
#include "th_gfx.h"
|
||||
extern "C"
|
||||
{
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libswscale/swscale.h>
|
||||
#include <libavutil/avutil.h>
|
||||
#include <libavutil/opt.h>
|
||||
}
|
||||
#include <SDL_mixer.h>
|
||||
#include <iostream>
|
||||
|
||||
#define INBUF_SIZE 4096
|
||||
#define AUDIO_BUFFER_SIZE 1024
|
||||
|
||||
int th_movie_stream_reader_thread(void* pState)
|
||||
{
|
||||
THMovie *pMovie = (THMovie *)pState;
|
||||
pMovie->readStreams();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int th_movie_video_thread(void* pState)
|
||||
{
|
||||
THMovie *pMovie = (THMovie *)pState;
|
||||
pMovie->runVideo();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void th_movie_audio_callback(int iChannel, void *pStream, int iStreamSize, void *pUserData)
|
||||
{
|
||||
THMovie *pMovie = (THMovie *)pUserData;
|
||||
pMovie->copyAudioToStream((Uint8*)pStream, iStreamSize);
|
||||
}
|
||||
|
||||
THMoviePicture::THMoviePicture():
|
||||
m_fAllocated(false),
|
||||
m_pOverlay(NULL),
|
||||
m_fReallocate(false),
|
||||
m_pixelFormat(PIX_FMT_YUV420P) {}
|
||||
|
||||
THMoviePicture::~THMoviePicture()
|
||||
{
|
||||
if(m_pOverlay)
|
||||
{
|
||||
SDL_FreeYUVOverlay(m_pOverlay);
|
||||
m_pOverlay = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void THMoviePicture::allocate()
|
||||
{
|
||||
SDL_Surface* pSurface = SDL_GetVideoSurface();
|
||||
|
||||
if(m_pOverlay)
|
||||
{
|
||||
SDL_FreeYUVOverlay(m_pOverlay);
|
||||
}
|
||||
|
||||
m_pOverlay = SDL_CreateYUVOverlay(m_iWidth, m_iHeight, SDL_YV12_OVERLAY, pSurface);
|
||||
if(m_pOverlay == NULL || m_pOverlay->pitches[0] < m_iWidth)
|
||||
{
|
||||
std::cerr << "Problem creating overlay: " << SDL_GetError() << "\n";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void THMoviePicture::deallocate()
|
||||
{
|
||||
if(m_pOverlay)
|
||||
{
|
||||
SDL_FreeYUVOverlay(m_pOverlay);
|
||||
m_pOverlay = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void THMoviePicture::draw()
|
||||
{
|
||||
SDL_Rect rcDest;
|
||||
int iError;
|
||||
|
||||
rcDest.x = m_iX;
|
||||
rcDest.y = m_iY;
|
||||
rcDest.w = m_iWidth;
|
||||
rcDest.h = m_iHeight;
|
||||
|
||||
if(m_pOverlay)
|
||||
{
|
||||
iError = SDL_DisplayYUVOverlay(m_pOverlay, &rcDest);
|
||||
if(iError < 0)
|
||||
{
|
||||
std::cerr << "Error displaying overlay: " << SDL_GetError();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
THAVPacketQueue::THAVPacketQueue():
|
||||
iCount(0),
|
||||
m_pFirstPacket(NULL),
|
||||
m_pLastPacket(NULL)
|
||||
{
|
||||
m_pMutex = SDL_CreateMutex();
|
||||
m_pCond = SDL_CreateCond();
|
||||
}
|
||||
|
||||
THAVPacketQueue::~THAVPacketQueue()
|
||||
{
|
||||
SDL_DestroyCond(m_pCond);
|
||||
SDL_DestroyMutex(m_pMutex);
|
||||
}
|
||||
|
||||
int THAVPacketQueue::getCount()
|
||||
{
|
||||
return iCount;
|
||||
}
|
||||
|
||||
void THAVPacketQueue::push(AVPacket *pPacket)
|
||||
{
|
||||
AVPacketList* pNode;
|
||||
if(av_dup_packet(pPacket) < 0) { throw -1; }
|
||||
|
||||
pNode = (AVPacketList*)av_malloc(sizeof(AVPacketList));
|
||||
pNode->pkt = *pPacket;
|
||||
pNode->next = NULL;
|
||||
|
||||
SDL_LockMutex(m_pMutex);
|
||||
|
||||
if(m_pLastPacket == NULL)
|
||||
{
|
||||
m_pFirstPacket = pNode;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_pLastPacket->next = pNode;
|
||||
}
|
||||
m_pLastPacket = pNode;
|
||||
iCount++;
|
||||
|
||||
SDL_CondSignal(m_pCond);
|
||||
SDL_UnlockMutex(m_pMutex);
|
||||
}
|
||||
|
||||
AVPacket* THAVPacketQueue::pull(bool fBlock)
|
||||
{
|
||||
AVPacketList *pNode;
|
||||
AVPacket *pPacket;
|
||||
|
||||
SDL_LockMutex(m_pMutex);
|
||||
|
||||
pNode = m_pFirstPacket;
|
||||
if(pNode == NULL && fBlock)
|
||||
{
|
||||
SDL_CondWait(m_pCond, m_pMutex);
|
||||
pNode = m_pFirstPacket;
|
||||
}
|
||||
|
||||
if(pNode == NULL)
|
||||
{
|
||||
pPacket = NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_pFirstPacket = pNode->next;
|
||||
if(m_pFirstPacket == NULL) { m_pLastPacket = NULL; }
|
||||
iCount--;
|
||||
|
||||
pPacket = (AVPacket*)av_malloc(sizeof(AVPacket));
|
||||
*pPacket = pNode->pkt;
|
||||
av_free(pNode);
|
||||
}
|
||||
|
||||
SDL_UnlockMutex(m_pMutex);
|
||||
|
||||
return pPacket;
|
||||
}
|
||||
|
||||
void THAVPacketQueue::release()
|
||||
{
|
||||
SDL_LockMutex(m_pMutex);
|
||||
SDL_CondSignal(m_pCond);
|
||||
SDL_UnlockMutex(m_pMutex);
|
||||
}
|
||||
|
||||
THMovie::THMovie():
|
||||
m_pFormatContext(NULL),
|
||||
m_pVideoCodecContext(NULL),
|
||||
m_pAudioCodecContext(NULL),
|
||||
m_pSwrContext(NULL),
|
||||
m_iChannel(-1),
|
||||
m_pVideoQueue(NULL),
|
||||
m_pAudioQueue(NULL),
|
||||
m_pSwsContext(NULL),
|
||||
m_pChunk(NULL),
|
||||
m_frame(NULL),
|
||||
m_pStreamThread(NULL),
|
||||
m_pVideoThread(NULL),
|
||||
m_iAudioBufferMaxSize(0),
|
||||
m_iAudioBufferSize(0),
|
||||
m_pAudioPacket(NULL)
|
||||
{
|
||||
av_register_all();
|
||||
|
||||
m_flushPacket = (AVPacket*)av_malloc(sizeof(AVPacket));
|
||||
av_init_packet(m_flushPacket);
|
||||
m_flushPacket->data = (uint8_t *)"FLUSH";
|
||||
m_flushPacket->size = 5;
|
||||
|
||||
m_pPictureQueueCond = SDL_CreateCond();
|
||||
m_pPictureQueueMutex = SDL_CreateMutex();
|
||||
|
||||
m_pbChunkBuffer = (Uint8*)malloc(AUDIO_BUFFER_SIZE);
|
||||
memset(m_pbChunkBuffer, 0, AUDIO_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
THMovie::~THMovie()
|
||||
{
|
||||
unload();
|
||||
|
||||
av_free_packet(m_flushPacket);
|
||||
av_free(m_flushPacket);
|
||||
free(m_pbChunkBuffer);
|
||||
SDL_DestroyCond(m_pPictureQueueCond);
|
||||
SDL_DestroyMutex(m_pPictureQueueMutex);
|
||||
sws_freeContext(m_pSwsContext);
|
||||
}
|
||||
|
||||
void THMovie::load(const char* szFilepath)
|
||||
{
|
||||
int iError = 0;
|
||||
|
||||
unload(); //Unload any currently loaded video to free memory
|
||||
m_fAborting = false;
|
||||
|
||||
iError = avformat_open_input(&m_pFormatContext, szFilepath, NULL, NULL);
|
||||
if(iError < 0)
|
||||
{
|
||||
std::cerr << "Failed to open av input: " << iError;
|
||||
return;
|
||||
}
|
||||
|
||||
iError = avformat_find_stream_info(m_pFormatContext, NULL);
|
||||
if(iError < 0)
|
||||
{
|
||||
std::cerr << "Failed to find stream info: " << iError;
|
||||
return;
|
||||
}
|
||||
|
||||
m_iVideoStream = av_find_best_stream(m_pFormatContext, AVMEDIA_TYPE_VIDEO, -1, -1, &m_pVideoCodec, 0);
|
||||
m_pVideoCodecContext = m_pFormatContext->streams[m_iVideoStream]->codec;
|
||||
avcodec_open2(m_pVideoCodecContext, m_pVideoCodec, NULL);
|
||||
|
||||
m_iAudioStream = av_find_best_stream(m_pFormatContext, AVMEDIA_TYPE_AUDIO, -1, -1, &m_pAudioCodec, 0);
|
||||
if(m_iAudioStream >= 0)
|
||||
{
|
||||
m_pAudioCodecContext = m_pFormatContext->streams[m_iAudioStream]->codec;
|
||||
avcodec_open2(m_pAudioCodecContext, m_pAudioCodec, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
int THMovie::getNativeHeight()
|
||||
{
|
||||
int iHeight = 0;
|
||||
|
||||
if(m_pVideoCodecContext)
|
||||
{
|
||||
iHeight = m_pVideoCodecContext->height;
|
||||
}
|
||||
return iHeight;
|
||||
}
|
||||
|
||||
int THMovie::getNativeWidth()
|
||||
{
|
||||
int iWidth = 0;
|
||||
|
||||
if(m_pVideoCodecContext)
|
||||
{
|
||||
iWidth = m_pVideoCodecContext->width;
|
||||
}
|
||||
return iWidth;
|
||||
}
|
||||
|
||||
bool THMovie::hasAudioTrack()
|
||||
{
|
||||
return (m_iAudioStream >= 0);
|
||||
}
|
||||
|
||||
void THMovie::play(int iX, int iY, int iWidth, int iHeight, int iChannel)
|
||||
{
|
||||
m_iX = iX;
|
||||
m_iY = iY;
|
||||
m_iWidth = iWidth;
|
||||
m_iHeight = iHeight;
|
||||
|
||||
#ifdef CORSIX_TH_USE_OGL_RENDERER
|
||||
SDL_Surface* pSurface = SDL_GetVideoSurface();
|
||||
SDL_SetVideoMode(pSurface->w, pSurface->h, 0, pSurface->flags & ~SDL_OPENGL);
|
||||
#endif
|
||||
m_frame = NULL;
|
||||
|
||||
m_pVideoQueue = new THAVPacketQueue();
|
||||
m_iPictureQueueSize = 0;
|
||||
m_iPictureQueueReadIndex = 0;
|
||||
m_iPictureQueueWriteIndex = 0;
|
||||
|
||||
m_pAudioPacket = NULL;
|
||||
m_iAudioPacketSize = 0;
|
||||
m_pbAudioPacketData = NULL;
|
||||
|
||||
m_iAudioBufferSize = 0;
|
||||
m_iAudioBufferIndex = 0;
|
||||
m_iAudioBufferMaxSize = 0;
|
||||
|
||||
m_pAudioQueue = new THAVPacketQueue();
|
||||
m_iStartTime = SDL_GetTicks();
|
||||
|
||||
if(m_iAudioStream >= 0)
|
||||
{
|
||||
Mix_QuerySpec(&m_iMixerFrequency, NULL, &m_iMixerChannels);
|
||||
m_pSwrContext = swr_alloc_set_opts(
|
||||
m_pSwrContext,
|
||||
m_iMixerChannels==1?AV_CH_LAYOUT_MONO:AV_CH_LAYOUT_STEREO,
|
||||
AV_SAMPLE_FMT_S16,
|
||||
m_iMixerFrequency,
|
||||
m_pAudioCodecContext->channel_layout,
|
||||
m_pAudioCodecContext->sample_fmt,
|
||||
m_pAudioCodecContext->sample_rate,
|
||||
0,
|
||||
NULL);
|
||||
swr_init(m_pSwrContext);
|
||||
|
||||
m_pChunk = Mix_QuickLoad_RAW(m_pbChunkBuffer, AUDIO_BUFFER_SIZE);
|
||||
|
||||
m_iChannel = Mix_PlayChannel(iChannel, m_pChunk, -1);
|
||||
if(m_iChannel < 0)
|
||||
{
|
||||
m_iChannel = -1;
|
||||
std::cerr << "Error on Mix_PlayChannel for th_movie";
|
||||
}
|
||||
else
|
||||
{
|
||||
Mix_RegisterEffect(m_iChannel, th_movie_audio_callback, NULL, this);
|
||||
}
|
||||
}
|
||||
|
||||
m_pStreamThread = SDL_CreateThread(th_movie_stream_reader_thread, this);
|
||||
m_pVideoThread = SDL_CreateThread(th_movie_video_thread, this);
|
||||
}
|
||||
|
||||
void THMovie::stop()
|
||||
{
|
||||
m_fAborting = true;
|
||||
}
|
||||
|
||||
void THMovie::unload()
|
||||
{
|
||||
m_fAborting = true;
|
||||
|
||||
if(m_pAudioQueue)
|
||||
{
|
||||
m_pAudioQueue->release();
|
||||
}
|
||||
|
||||
if(m_pVideoQueue)
|
||||
{
|
||||
m_pVideoQueue->release();
|
||||
}
|
||||
|
||||
SDL_LockMutex(m_pPictureQueueMutex);
|
||||
SDL_CondSignal(m_pPictureQueueCond);
|
||||
SDL_UnlockMutex(m_pPictureQueueMutex);
|
||||
|
||||
if(m_pStreamThread)
|
||||
{
|
||||
SDL_WaitThread(m_pStreamThread, NULL);
|
||||
m_pStreamThread = NULL;
|
||||
}
|
||||
if(m_pVideoThread)
|
||||
{
|
||||
SDL_WaitThread(m_pVideoThread, NULL);
|
||||
m_pVideoThread = NULL;
|
||||
}
|
||||
|
||||
//wait until after other threads are closed to clear the packet queues
|
||||
//so we don't free something being used.
|
||||
if(m_pAudioQueue)
|
||||
{
|
||||
while(m_pAudioQueue->getCount() > 0)
|
||||
{
|
||||
AVPacket* p = m_pAudioQueue->pull(false);
|
||||
av_free_packet(p);
|
||||
}
|
||||
delete m_pAudioQueue;
|
||||
m_pAudioQueue = NULL;
|
||||
}
|
||||
|
||||
if(m_pVideoQueue)
|
||||
{
|
||||
while(m_pVideoQueue->getCount() > 0)
|
||||
{
|
||||
AVPacket* p = m_pVideoQueue->pull(false);
|
||||
av_free_packet(p);
|
||||
}
|
||||
delete m_pVideoQueue;
|
||||
m_pVideoQueue = NULL;
|
||||
}
|
||||
|
||||
for(int i=0; i<PICTURE_BUFFER_SIZE; i++)
|
||||
{
|
||||
m_aPictureQueue[i].deallocate();
|
||||
m_aPictureQueue[i].m_fAllocated = false;
|
||||
}
|
||||
|
||||
if(m_iChannel >= 0)
|
||||
{
|
||||
Mix_UnregisterAllEffects(m_iChannel);
|
||||
Mix_HaltChannel(m_iChannel);
|
||||
Mix_FreeChunk(m_pChunk);
|
||||
m_iChannel = -1;
|
||||
}
|
||||
|
||||
if(m_iAudioBufferMaxSize > 0)
|
||||
{
|
||||
av_free(m_pbAudioBuffer);
|
||||
m_iAudioBufferMaxSize = 0;
|
||||
}
|
||||
|
||||
if(m_pVideoCodecContext)
|
||||
{
|
||||
avcodec_close(m_pVideoCodecContext);
|
||||
m_pVideoCodecContext = NULL;
|
||||
}
|
||||
|
||||
if(m_pAudioCodecContext)
|
||||
{
|
||||
avcodec_close(m_pAudioCodecContext);
|
||||
m_pAudioCodecContext = NULL;
|
||||
}
|
||||
|
||||
if(m_pFormatContext)
|
||||
{
|
||||
avformat_close_input(&m_pFormatContext);
|
||||
}
|
||||
|
||||
if(m_frame)
|
||||
{
|
||||
av_free(m_frame);
|
||||
m_frame = NULL;
|
||||
}
|
||||
|
||||
swr_free(&m_pSwrContext);
|
||||
|
||||
if(m_pAudioPacket)
|
||||
{
|
||||
m_pAudioPacket->data = m_pbAudioPacketData;
|
||||
m_pAudioPacket->size = m_iAudioPacketSize;
|
||||
av_free_packet(m_pAudioPacket);
|
||||
av_free(m_pAudioPacket);
|
||||
|
||||
m_pAudioPacket = NULL;
|
||||
m_pbAudioPacketData = NULL;
|
||||
m_iAudioPacketSize = 0;
|
||||
}
|
||||
|
||||
#ifdef CORSIX_TH_USE_OGL_RENDERER
|
||||
SDL_Surface* pSurface = SDL_GetVideoSurface();
|
||||
SDL_SetVideoMode(pSurface->w, pSurface->h, 0, pSurface->flags | SDL_OPENGL);
|
||||
#endif
|
||||
}
|
||||
|
||||
void THMovie::readStreams()
|
||||
{
|
||||
AVPacket packet;
|
||||
int iError;
|
||||
|
||||
while(!m_fAborting)
|
||||
{
|
||||
iError = av_read_frame(m_pFormatContext, &packet);
|
||||
if(iError < 0)
|
||||
{
|
||||
if(iError == AVERROR_EOF || m_pFormatContext->pb->error || m_pFormatContext->pb->eof_reached)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(packet.stream_index == m_iVideoStream)
|
||||
{
|
||||
m_pVideoQueue->push(&packet);
|
||||
}
|
||||
else if (packet.stream_index == m_iAudioStream)
|
||||
{
|
||||
m_pAudioQueue->push(&packet);
|
||||
}
|
||||
else
|
||||
{
|
||||
av_free_packet(&packet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while(!m_fAborting)
|
||||
{
|
||||
if(m_pVideoQueue->getCount() == 0 && m_pAudioQueue->getCount() == 0 && m_iPictureQueueSize == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
SDL_Delay(10);
|
||||
}
|
||||
|
||||
SDL_Event endEvent;
|
||||
endEvent.type = SDL_USEREVENT_MOVIE_OVER;
|
||||
SDL_PushEvent(&endEvent);
|
||||
|
||||
m_fAborting = true;
|
||||
}
|
||||
|
||||
void THMovie::refresh()
|
||||
{
|
||||
THMoviePicture* pMoviePicture;
|
||||
|
||||
if(m_pFormatContext->streams[m_iVideoStream])
|
||||
{
|
||||
if(m_iPictureQueueSize > 0)
|
||||
{
|
||||
pMoviePicture = &(m_aPictureQueue[m_iPictureQueueReadIndex]);
|
||||
double curTime = SDL_GetTicks() - m_iStartTime;
|
||||
|
||||
//don't play a frame too early
|
||||
if(pMoviePicture->m_dPts > 0)
|
||||
{
|
||||
if(pMoviePicture->m_dPts * 1000.0 > curTime)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(m_iPictureQueueSize > 1)
|
||||
{
|
||||
THMoviePicture* nextPicture = &(m_aPictureQueue[(m_iPictureQueueReadIndex + 1) % PICTURE_BUFFER_SIZE]);
|
||||
|
||||
//if we have another frame and it's time has already passed, drop the current frame
|
||||
if(nextPicture->m_dPts > 0 && nextPicture->m_dPts * 1000.0 < curTime)
|
||||
{
|
||||
advancePictureQueue();
|
||||
}
|
||||
}
|
||||
|
||||
pMoviePicture->draw();
|
||||
advancePictureQueue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void THMovie::advancePictureQueue()
|
||||
{
|
||||
m_iPictureQueueReadIndex++;
|
||||
if(m_iPictureQueueReadIndex == PICTURE_BUFFER_SIZE)
|
||||
{
|
||||
m_iPictureQueueReadIndex = 0;
|
||||
}
|
||||
|
||||
SDL_LockMutex(m_pPictureQueueMutex);
|
||||
m_iPictureQueueSize--;
|
||||
SDL_CondSignal(m_pPictureQueueCond);
|
||||
SDL_UnlockMutex(m_pPictureQueueMutex);
|
||||
}
|
||||
|
||||
void THMovie::runVideo()
|
||||
{
|
||||
AVFrame *pFrame = avcodec_alloc_frame();
|
||||
int64_t iStreamPts = AV_NOPTS_VALUE;
|
||||
double dClockPts;
|
||||
int iError;
|
||||
|
||||
while(!m_fAborting)
|
||||
{
|
||||
avcodec_get_frame_defaults(pFrame);
|
||||
|
||||
iError = getVideoFrame(pFrame, &iStreamPts);
|
||||
if(iError < 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else if(iError == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
dClockPts = iStreamPts * av_q2d(m_pVideoCodecContext->time_base);
|
||||
iError = queuePicture(pFrame, dClockPts);
|
||||
|
||||
if(iError < 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
avcodec_flush_buffers(m_pVideoCodecContext);
|
||||
av_free(pFrame);
|
||||
}
|
||||
|
||||
int THMovie::getVideoFrame(AVFrame *pFrame, int64_t *piPts)
|
||||
{
|
||||
int iGotPicture = 0;
|
||||
int iError;
|
||||
|
||||
AVPacket *pPacket = m_pVideoQueue->pull(true);
|
||||
if(pPacket == NULL)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(pPacket->data == m_flushPacket->data)
|
||||
{
|
||||
//TODO: Flush
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
iError = avcodec_decode_video2(m_pVideoCodecContext, pFrame, &iGotPicture, pPacket);
|
||||
av_free_packet(pPacket);
|
||||
av_free(pPacket);
|
||||
|
||||
if(iError < 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(iGotPicture)
|
||||
{
|
||||
iError = 1;
|
||||
|
||||
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(54, 18, 100)
|
||||
*piPts = *(int64_t*)av_opt_ptr(avcodec_get_frame_class(), pFrame, "best_effort_timestamp");
|
||||
#else
|
||||
*piPts = av_frame_get_best_effort_timestamp(pFrame);
|
||||
#endif
|
||||
|
||||
if(*piPts == AV_NOPTS_VALUE)
|
||||
{
|
||||
*piPts = 0;
|
||||
}
|
||||
return iError;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int THMovie::queuePicture(AVFrame *pFrame, double dPts)
|
||||
{
|
||||
SDL_LockMutex(m_pPictureQueueMutex);
|
||||
while(!m_fAborting && m_iPictureQueueSize >= PICTURE_BUFFER_SIZE - 2)
|
||||
{
|
||||
SDL_CondWait(m_pPictureQueueCond, m_pPictureQueueMutex);
|
||||
}
|
||||
SDL_UnlockMutex(m_pPictureQueueMutex);
|
||||
|
||||
if(m_fAborting)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
THMoviePicture* pMoviePicture = &m_aPictureQueue[m_iPictureQueueWriteIndex];
|
||||
|
||||
//check if overlay needs to be allocated or changed
|
||||
if(!pMoviePicture->m_pOverlay ||
|
||||
!pMoviePicture->m_fAllocated ||
|
||||
pMoviePicture->m_fReallocate ||
|
||||
pMoviePicture->m_iWidth != m_iWidth ||
|
||||
pMoviePicture->m_iHeight != m_iHeight)
|
||||
{
|
||||
SDL_Event reallocEvent;
|
||||
|
||||
pMoviePicture->m_fAllocated = false;
|
||||
pMoviePicture->m_fReallocate = false;
|
||||
pMoviePicture->m_iWidth = m_iWidth;
|
||||
pMoviePicture->m_iHeight = m_iHeight;
|
||||
pMoviePicture->m_iX = m_iX;
|
||||
pMoviePicture->m_iY = m_iY;
|
||||
|
||||
//the actual allocation needs to be done on the main thread or else puppies may die.
|
||||
reallocEvent.type = SDL_USEREVENT_ALLOCATE_MOVIE_PICTURE;
|
||||
reallocEvent.user.data1 = this;
|
||||
SDL_PushEvent(&reallocEvent);
|
||||
|
||||
//wait for the main tread
|
||||
SDL_LockMutex(m_pPictureQueueMutex);
|
||||
while(!m_fAborting && !pMoviePicture->m_fAllocated)
|
||||
{
|
||||
SDL_CondWait(m_pPictureQueueCond, m_pPictureQueueMutex);
|
||||
}
|
||||
SDL_UnlockMutex(m_pPictureQueueMutex);
|
||||
|
||||
if(m_fAborting)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if(pMoviePicture->m_pOverlay)
|
||||
{
|
||||
AVPicture picture = {};
|
||||
|
||||
SDL_LockYUVOverlay(pMoviePicture->m_pOverlay);
|
||||
picture.data[0] = pMoviePicture->m_pOverlay->pixels[0];
|
||||
picture.data[1] = pMoviePicture->m_pOverlay->pixels[2];
|
||||
picture.data[2] = pMoviePicture->m_pOverlay->pixels[1];
|
||||
|
||||
picture.linesize[0] = pMoviePicture->m_pOverlay->pitches[0];
|
||||
picture.linesize[1] = pMoviePicture->m_pOverlay->pitches[2];
|
||||
picture.linesize[2] = pMoviePicture->m_pOverlay->pitches[1];
|
||||
|
||||
m_pSwsContext = sws_getCachedContext(m_pSwsContext, pFrame->width, pFrame->height, (PixelFormat)pFrame->format, pMoviePicture->m_iWidth, pMoviePicture->m_iHeight, pMoviePicture->m_pixelFormat, SWS_BICUBIC, NULL, NULL, NULL);
|
||||
|
||||
if(m_pSwsContext == NULL)
|
||||
{
|
||||
std::cerr << "Failed to initialize SwsContext";
|
||||
return 1;
|
||||
}
|
||||
|
||||
sws_scale(m_pSwsContext, pFrame->data, pFrame->linesize, 0, pMoviePicture->m_iHeight, picture.data, picture.linesize);
|
||||
|
||||
SDL_UnlockYUVOverlay(pMoviePicture->m_pOverlay);
|
||||
|
||||
pMoviePicture->m_dPts = dPts;
|
||||
|
||||
m_iPictureQueueWriteIndex++;
|
||||
if(m_iPictureQueueWriteIndex == PICTURE_BUFFER_SIZE)
|
||||
{
|
||||
m_iPictureQueueWriteIndex = 0;
|
||||
}
|
||||
SDL_LockMutex(m_pPictureQueueMutex);
|
||||
m_iPictureQueueSize++;
|
||||
SDL_UnlockMutex(m_pPictureQueueMutex);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void THMovie::allocatePicture()
|
||||
{
|
||||
THMoviePicture *pMoviePicture = &m_aPictureQueue[m_iPictureQueueWriteIndex];
|
||||
|
||||
pMoviePicture->allocate();
|
||||
|
||||
SDL_LockMutex(m_pPictureQueueMutex);
|
||||
pMoviePicture->m_fAllocated = true;
|
||||
SDL_CondSignal(m_pPictureQueueCond);
|
||||
SDL_UnlockMutex(m_pPictureQueueMutex);
|
||||
}
|
||||
|
||||
void THMovie::copyAudioToStream(Uint8 *pbStream, int iStreamSize)
|
||||
{
|
||||
int iAudioSize;
|
||||
int iCopyLength;
|
||||
|
||||
while(iStreamSize > 0)
|
||||
{
|
||||
if(m_iAudioBufferIndex >= m_iAudioBufferSize)
|
||||
{
|
||||
iAudioSize = decodeAudioFrame();
|
||||
|
||||
if(iAudioSize <= 0)
|
||||
{
|
||||
memset(m_pbAudioBuffer, 0, m_iAudioBufferSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_iAudioBufferSize = iAudioSize;
|
||||
}
|
||||
m_iAudioBufferIndex = 0;
|
||||
}
|
||||
|
||||
iCopyLength = m_iAudioBufferSize - m_iAudioBufferIndex;
|
||||
if(iCopyLength > iStreamSize) { iCopyLength = iStreamSize; }
|
||||
memcpy(pbStream, (uint8_t *)m_pbAudioBuffer + m_iAudioBufferIndex, iCopyLength);
|
||||
iStreamSize -= iCopyLength;
|
||||
pbStream += iCopyLength;
|
||||
m_iAudioBufferIndex += iCopyLength;
|
||||
}
|
||||
}
|
||||
|
||||
int THMovie::decodeAudioFrame()
|
||||
{
|
||||
int iBytesConsumed = 0;
|
||||
int iSampleSize = 0;
|
||||
int iOutSamples;
|
||||
int iGotFrame = 0;
|
||||
bool fNewPacket = false;
|
||||
bool fFlushComplete = false;
|
||||
|
||||
while(!iGotFrame && !m_fAborting)
|
||||
{
|
||||
if(!m_pAudioPacket || m_pAudioPacket->size == 0)
|
||||
{
|
||||
if(m_pAudioPacket)
|
||||
{
|
||||
m_pAudioPacket->data = m_pbAudioPacketData;
|
||||
m_iAudioPacketSize = m_iAudioPacketSize;
|
||||
av_free_packet(m_pAudioPacket);
|
||||
av_free(m_pAudioPacket);
|
||||
m_pAudioPacket = NULL;
|
||||
}
|
||||
m_pAudioPacket = m_pAudioQueue->pull(true);
|
||||
if(m_fAborting)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
m_pbAudioPacketData = m_pAudioPacket->data;
|
||||
m_iAudioPacketSize = m_pAudioPacket->size;
|
||||
|
||||
if(m_pAudioPacket == NULL)
|
||||
{
|
||||
fNewPacket = false;
|
||||
return -1;
|
||||
}
|
||||
fNewPacket = true;
|
||||
|
||||
if(m_pAudioPacket->data == m_flushPacket->data)
|
||||
{
|
||||
avcodec_flush_buffers(m_pAudioCodecContext);
|
||||
fFlushComplete = false;
|
||||
}
|
||||
}
|
||||
|
||||
while(m_pAudioPacket->size > 0 || (!m_pAudioPacket->data && fNewPacket))
|
||||
{
|
||||
if(!m_frame)
|
||||
{
|
||||
m_frame = avcodec_alloc_frame();
|
||||
}
|
||||
else
|
||||
{
|
||||
avcodec_get_frame_defaults(m_frame);
|
||||
}
|
||||
|
||||
if(fFlushComplete)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
fNewPacket = false;
|
||||
|
||||
iBytesConsumed = avcodec_decode_audio4(m_pAudioCodecContext, m_frame, &iGotFrame, m_pAudioPacket);
|
||||
|
||||
if(iBytesConsumed < 0)
|
||||
{
|
||||
m_pAudioPacket->size = 0;
|
||||
break;
|
||||
}
|
||||
m_pAudioPacket->data += iBytesConsumed;
|
||||
m_pAudioPacket->size -= iBytesConsumed;
|
||||
|
||||
if(!iGotFrame)
|
||||
{
|
||||
if(m_pAudioPacket->data && m_pAudioCodecContext->codec->capabilities & CODEC_CAP_DELAY)
|
||||
{
|
||||
fFlushComplete = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: Compensate for audio being out of sync
|
||||
|
||||
#if LIBSWRESAMPLE_VERSION_INT < AV_VERSION_INT(0, 12, 100)
|
||||
//over-estimate output samples
|
||||
iOutSamples = (int)av_rescale_rnd(m_frame->nb_samples, m_iMixerFrequency, m_pAudioCodecContext->sample_rate, AV_ROUND_UP);
|
||||
iSampleSize = av_get_bytes_per_sample(AV_SAMPLE_FMT_S16) * iOutSamples * m_iMixerChannels;
|
||||
|
||||
if(iSampleSize > m_iAudioBufferMaxSize)
|
||||
{
|
||||
if(m_iAudioBufferMaxSize > 0)
|
||||
{
|
||||
av_free(m_pbAudioBuffer);
|
||||
}
|
||||
m_pbAudioBuffer = (uint8_t*)av_malloc(iSampleSize);
|
||||
m_iAudioBufferMaxSize = iSampleSize;
|
||||
}
|
||||
#else
|
||||
//output samples = (input samples + delay) * output rate / input rate
|
||||
iOutSamples = (int)av_rescale_rnd(
|
||||
swr_get_delay(
|
||||
m_pSwrContext,
|
||||
m_pAudioCodecContext->sample_rate) + m_frame->nb_samples,
|
||||
m_iMixerFrequency,
|
||||
m_pAudioCodecContext->sample_rate,
|
||||
AV_ROUND_UP);
|
||||
iSampleSize = av_samples_get_buffer_size(NULL, m_iMixerChannels, iOutSamples, AV_SAMPLE_FMT_S16, 0);
|
||||
|
||||
if(iSampleSize > m_iAudioBufferMaxSize)
|
||||
{
|
||||
if(m_iAudioBufferMaxSize > 0)
|
||||
{
|
||||
av_free(m_pbAudioBuffer);
|
||||
}
|
||||
av_samples_alloc(&m_pbAudioBuffer, NULL, m_iMixerChannels, iOutSamples, AV_SAMPLE_FMT_S16, 0);
|
||||
m_iAudioBufferMaxSize = iSampleSize;
|
||||
}
|
||||
#endif
|
||||
|
||||
swr_convert(m_pSwrContext, &m_pbAudioBuffer, iOutSamples, (const uint8_t**)&m_frame->data[0], m_frame->nb_samples);
|
||||
return iSampleSize;
|
||||
}
|
||||
#else //CORSIX_TH_USE_FFMPEG
|
||||
THMovie::THMovie() {}
|
||||
THMovie::~THMovie() {}
|
||||
|
||||
void THMovie::load(const char* filepath) {}
|
||||
void THMovie::unload() {}
|
||||
void THMovie::play(int iX, int iY, int iWidth, int iHeight, int iChannel)
|
||||
{
|
||||
SDL_Event endEvent;
|
||||
endEvent.type = SDL_USEREVENT_MOVIE_OVER;
|
||||
SDL_PushEvent(&endEvent);
|
||||
}
|
||||
void THMovie::stop() {}
|
||||
int THMovie::getNativeHeight() { return 0; }
|
||||
int THMovie::getNativeWidth() { return 0; }
|
||||
bool THMovie::hasAudioTrack() { return false; }
|
||||
void THMovie::refresh() {}
|
||||
void THMovie::copyAudioToStream(Uint8 *stream, int length) {}
|
||||
void THMovie::runVideo() {}
|
||||
void THMovie::allocatePicture() {}
|
||||
void THMovie::readStreams() {}
|
||||
#endif //CORSIX_TH_USE_FFMPEG
|
||||
|
||||
|
160
CorsixTH/Src/th_movie.h
Normal file
160
CorsixTH/Src/th_movie.h
Normal file
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
Copyright (c) 2012 Stephen Baker
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef TH_VIDEO_H
|
||||
#define TH_VIDEO_H
|
||||
#define PICTURE_BUFFER_SIZE 4
|
||||
|
||||
#include <string>
|
||||
#include <queue>
|
||||
#include "config.h"
|
||||
#include "th_gfx.h"
|
||||
#include "th_sound.h"
|
||||
#include "SDL_mixer.h"
|
||||
|
||||
#ifdef CORSIX_TH_USE_FFMPEG
|
||||
extern "C"
|
||||
{
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavutil/avutil.h>
|
||||
#include <libswresample/swresample.h>
|
||||
#include <libswscale/swscale.h>
|
||||
}
|
||||
|
||||
class THMoviePicture
|
||||
{
|
||||
public:
|
||||
SDL_Overlay *m_pOverlay;
|
||||
int m_iX, m_iY, m_iWidth, m_iHeight;
|
||||
double m_dPts;
|
||||
bool m_fAllocated;
|
||||
bool m_fReallocate;
|
||||
PixelFormat m_pixelFormat;
|
||||
|
||||
THMoviePicture();
|
||||
~THMoviePicture();
|
||||
void allocate();
|
||||
void deallocate();
|
||||
void draw();
|
||||
};
|
||||
|
||||
class THAVPacketQueue
|
||||
{
|
||||
public:
|
||||
THAVPacketQueue();
|
||||
~THAVPacketQueue();
|
||||
void push(AVPacket *packet);
|
||||
AVPacket* pull(bool block);
|
||||
int getCount();
|
||||
void release();
|
||||
private:
|
||||
AVPacketList *m_pFirstPacket, *m_pLastPacket;
|
||||
int iCount;
|
||||
SDL_mutex *m_pMutex;
|
||||
SDL_cond *m_pCond;
|
||||
};
|
||||
#endif //CORSIX_TH_USE_FFMPEG
|
||||
|
||||
class THMovie
|
||||
{
|
||||
public:
|
||||
THMovie();
|
||||
~THMovie();
|
||||
|
||||
void load(const char* szFilepath);
|
||||
void unload();
|
||||
void play(int iX, int iY, int iWidth, int iHeight, int iChannel);
|
||||
void stop();
|
||||
int getNativeHeight();
|
||||
int getNativeWidth();
|
||||
bool hasAudioTrack();
|
||||
void refresh();
|
||||
void copyAudioToStream(Uint8 *pbStream, int iStreamSize);
|
||||
void runVideo();
|
||||
void allocatePicture();
|
||||
void readStreams();
|
||||
protected:
|
||||
#ifdef CORSIX_TH_USE_FFMPEG
|
||||
int decodeAudioFrame();
|
||||
int getVideoFrame(AVFrame *pFrame, int64_t *piPts);
|
||||
int queuePicture(AVFrame *pFrame, double dPts);
|
||||
void advancePictureQueue();
|
||||
|
||||
int m_iX;
|
||||
int m_iY;
|
||||
int m_iWidth;
|
||||
int m_iHeight;
|
||||
|
||||
AVFormatContext* m_pFormatContext;
|
||||
int m_iVideoStream;
|
||||
int m_iAudioStream;
|
||||
AVCodec* m_pVideoCodec;
|
||||
AVCodec* m_pAudioCodec;
|
||||
AVCodecContext* m_pVideoCodecContext;
|
||||
AVCodecContext* m_pAudioCodecContext;
|
||||
|
||||
THAVPacketQueue *m_pVideoQueue;
|
||||
|
||||
SDL_mutex *m_pPictureQueueMutex;
|
||||
SDL_cond *m_pPictureQueueCond;
|
||||
THMoviePicture m_aPictureQueue[PICTURE_BUFFER_SIZE];
|
||||
int m_iPictureQueueWriteIndex;
|
||||
int m_iPictureQueueReadIndex;
|
||||
int m_iPictureQueueSize;
|
||||
|
||||
//tick when video was started in ms
|
||||
int m_iStartTime;
|
||||
|
||||
THAVPacketQueue *m_pAudioQueue;
|
||||
|
||||
//audio buffer
|
||||
int m_iAudioBufferSize;
|
||||
int m_iAudioBufferIndex;
|
||||
int m_iAudioBufferMaxSize;
|
||||
uint8_t* m_pbAudioBuffer;
|
||||
|
||||
int m_iChannel;
|
||||
Uint8* m_pbChunkBuffer;
|
||||
Mix_Chunk* m_pChunk;
|
||||
|
||||
int m_iMixerChannels;
|
||||
int m_iMixerFrequency;
|
||||
SwrContext* m_pSwrContext;
|
||||
|
||||
bool m_fAborting;
|
||||
|
||||
AVPacket* m_pAudioPacket;
|
||||
int m_iAudioPacketSize;
|
||||
uint8_t *m_pbAudioPacketData;
|
||||
|
||||
AVPacket* m_flushPacket;
|
||||
|
||||
AVFrame* m_frame;
|
||||
SwsContext* m_pSwsContext;
|
||||
|
||||
SDL_Thread* m_pStreamThread;
|
||||
SDL_Thread* m_pRefreshThread;
|
||||
SDL_Thread* m_pVideoThread;
|
||||
#endif //CORSIX_TH_USE_FFMPEG
|
||||
};
|
||||
|
||||
#endif // TH_VIDEO_H
|
@@ -198,7 +198,7 @@ void THSoundEffects::_onChannelFinish(int iChannel)
|
||||
if(pThis == NULL)
|
||||
return;
|
||||
|
||||
pThis->m_iChannelStatus |= (1 << iChannel);
|
||||
pThis->releaseChannel(iChannel);
|
||||
}
|
||||
|
||||
THSoundEffects* THSoundEffects::getSingleton()
|
||||
@@ -274,13 +274,25 @@ void THSoundEffects::setSoundEffectsOn(bool bOn)
|
||||
m_bSoundEffectsOn = bOn;
|
||||
}
|
||||
|
||||
void THSoundEffects::_playRaw(size_t iIndex, int iVolume)
|
||||
int THSoundEffects::reserveChannel()
|
||||
{
|
||||
// NB: Callers ensure that m_iChannelStatus != 0
|
||||
int iChannel = 0;
|
||||
for(; (m_iChannelStatus & (1 << iChannel)) == 0; ++iChannel) {}
|
||||
m_iChannelStatus &=~ (1 << iChannel);
|
||||
|
||||
return iChannel;
|
||||
}
|
||||
|
||||
void THSoundEffects::releaseChannel(int iChannel)
|
||||
{
|
||||
m_iChannelStatus |= (1 << iChannel);
|
||||
}
|
||||
|
||||
void THSoundEffects::_playRaw(size_t iIndex, int iVolume)
|
||||
{
|
||||
int iChannel = reserveChannel();
|
||||
|
||||
Mix_Volume(iChannel, iVolume);
|
||||
Mix_PlayChannelTimed(iChannel, m_ppSounds[iIndex], 0, -1);
|
||||
}
|
||||
|
@@ -104,6 +104,8 @@ public:
|
||||
void setSoundEffectsVolume(double dVolume);
|
||||
void setSoundEffectsOn(bool bOn);
|
||||
void setCamera(int iX, int iY, int iRadius);
|
||||
int reserveChannel();
|
||||
void releaseChannel(int iChannel);
|
||||
|
||||
protected:
|
||||
#ifdef CORSIX_TH_USE_SDL_MIXER
|
||||
|
Reference in New Issue
Block a user