diff --git a/CMake/FindLibAV.cmake b/CMake/FindLibAV.cmake new file mode 100644 index 000000000..9bfdf1c63 --- /dev/null +++ b/CMake/FindLibAV.cmake @@ -0,0 +1,145 @@ +# vim: ts=2 sw=2 +# - Try to find the required libav components(default: AVFORMAT, AVUTIL, AVCODEC) +# +# Once done this will define +# LIBAV_FOUND - System has the all required components. +# LIBAV_INCLUDE_DIRS - Include directory necessary for using the required components headers. +# LIBAV_LIBRARIES - Link these to use the required libav components. +# LIBAV_DEFINITIONS - Compiler switches required for using the required libav components. +# +# For each of the components it will additionally set. +# - AVCODEC +# - AVDEVICE +# - AVFILTER +# - AVFORMAT +# - AVRESAMPLE +# - AVUTIL +# - SWSCALE +# the following variables will be defined +# _FOUND - System has +# _INCLUDE_DIRS - Include directory necessary for using the headers +# _LIBRARIES - Link these to use +# _DEFINITIONS - Compiler switches required for using +# _VERSION - The components version +# +# Copyright (c) 2006, Matthias Kretz, +# Copyright (c) 2008, Alexander Neundorf, +# Copyright (c) 2011, Michael Jansen, +# Copyright (c) 2013,2015 Stephen Baker +# +# 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 FindLIBAV.cmake files +if (NOT LibAV_FIND_COMPONENTS) + set(LibAV_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 + libav + ) + + 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}) + +endmacro() + + +# Check for cached results. If there are skip the costly part. +if (NOT LIBAV_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(AVFILTER libavfilter avfilter libavfilter/avfilter.h) + find_component(AVRESAMPLE libavresample avresample libavresample/avresample.h) + find_component(AVUTIL libavutil avutil libavutil/avutil.h) + find_component(SWSCALE libswscale swscale libswscale/swscale.h) + + # Check if the required components were found and add their stuff to the LIBAV_* vars. + foreach (_component ${LibAV_FIND_COMPONENTS}) + if (${_component}_FOUND) + # message(STATUS "Required component ${_component} present.") + set(LIBAV_LIBRARIES ${LIBAV_LIBRARIES} ${${_component}_LIBRARIES}) + set(LIBAV_DEFINITIONS ${LIBAV_DEFINITIONS} ${${_component}_DEFINITIONS}) + list(APPEND LIBAV_INCLUDE_DIRS ${${_component}_INCLUDE_DIRS}) + else () + # message(STATUS "Required component ${_component} missing.") + endif () + endforeach () + + # Build the include path with duplicates removed. + if (LIBAV_INCLUDE_DIRS) + list(REMOVE_DUPLICATES LIBAV_INCLUDE_DIRS) + endif () + + # cache the vars. + set(LIBAV_INCLUDE_DIRS ${LIBAV_INCLUDE_DIRS} CACHE STRING "The LibAV include directories." FORCE) + set(LIBAV_LIBRARIES ${LIBAV_LIBRARIES} CACHE STRING "The LibAV libraries." FORCE) + set(LIBAV_DEFINITIONS ${LIBAV_DEFINITIONS} CACHE STRING "The LibAV cflags." FORCE) + + mark_as_advanced(LIBAV_INCLUDE_DIRS + LIBAV_LIBRARIES + LIBAV_DEFINITIONS) + +endif () + +# Now set the noncached _FOUND vars for the components. +foreach (_component AVCODEC AVDEVICE AVFILTER AVFORMAT AVRESAMPLE AVUTIL SWSCALE) + set_component_found(${_component}) +endforeach () + +# Compile the list of required vars +set(_LibAV_REQUIRED_VARS LIBAV_LIBRARIES LIBAV_INCLUDE_DIRS) +foreach (_component ${LibAV_FIND_COMPONENTS}) + list(APPEND _LibAV_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(LibAV DEFAULT_MSG ${_LibAV_REQUIRED_VARS}) diff --git a/CMakeLists.txt b/CMakeLists.txt index e2a617d87..d59cacc33 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,6 +11,7 @@ # - BUILD_ANIMVIEWER # - BUILD_MAPEDITOR # - WITH_LUAJIT : Whether to use LuaJIT 2 instead of Lua51 (default is LuaJIT 2) +# - WITH_LIBAV : Whether to use LibAV (as opposed to FFMEPG) when building movies PROJECT(CorsixTH_Top_Level) CMAKE_MINIMUM_REQUIRED(VERSION 2.6) @@ -36,6 +37,7 @@ 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(WITH_LUAJIT "Use LuaJIT instead of Lua" ON) +OPTION(WITH_LIBAV "Use LibAV instead of FFmpeg" OFF) 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) @@ -49,14 +51,23 @@ ENDIF(WITH_AUDIO) IF(WITH_MOVIES) IF(WITH_AUDIO) - SET(CORSIX_TH_USE_FFMPEG ON) - MESSAGE("Note: FFMPEG video is enabled (default)") + IF(WITH_LIBAV) + SET(CORSIX_TH_USE_FFMPEG OFF) + SET(CORSIX_TH_USE_LIBAV ON) + MESSAGE("Note: LibAV video is enabled") + ELSE() + SET(CORSIX_TH_USE_FFMPEG ON) + SET(CORSIX_TH_USE_LIBAV OFF) + MESSAGE("Note: FFMPEG video is enabled (default)") + ENDIF(WITH_LIBAV) ELSE() SET(CORSIX_TH_USE_FFMPEG OFF) + SET(CORSIX_TH_USE_LIBAV OFF) MESSAGE("Note: FFMPEG video disabled since it requires SDL audio.") ENDIF(WITH_AUDIO) ELSE() SET(CORSIX_TH_USE_FFMPEG OFF) + SET(CORSIX_TH_USE_LIBAV OFF) MESSAGE("Note: FFMPEG video is disabled") ENDIF(WITH_MOVIES) diff --git a/CorsixTH/CMakeLists.txt b/CorsixTH/CMakeLists.txt index 0af753061..19c017a74 100644 --- a/CorsixTH/CMakeLists.txt +++ b/CorsixTH/CMakeLists.txt @@ -137,7 +137,7 @@ 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) + FIND_PACKAGE(FFmpeg COMPONENTS AVFORMAT AVCODEC AVUTIL SWSCALE SWRESAMPLE REQUIRED) IF(FFMPEG_FOUND) TARGET_LINK_LIBRARIES(CorsixTH ${FFMPEG_LIBRARIES}) INCLUDE_DIRECTORIES(${FFMPEG_INCLUDE_DIRS}) @@ -150,6 +150,20 @@ IF(CORSIX_TH_USE_FFMPEG) ENDIF(FFMPEG_FOUND) ENDIF(CORSIX_TH_USE_FFMPEG) +IF(CORSIX_TH_USE_LIBAV) + FIND_PACKAGE(LibAV COMPONENTS AVFORMAT AVCODEC AVRESAMPLE AVUTIL SWSCALE REQUIRED) + IF(LIBAV_FOUND) + TARGET_LINK_LIBRARIES(CorsixTH ${LIBAV_LIBRARIES}) + INCLUDE_DIRECTORIES(${LIBAV_INCLUDE_DIRS}) + IF(APPLE) + TARGET_LINK_LIBRARIES(CorsixTH libz.dylib) + ENDIF() + message(" LibAV found") + ELSE(LIBAV_FOUND) + message("Error: LibAV library not found, even though it was selected to be included") + ENDIF(LIBAV_FOUND) +ENDIF(CORSIX_TH_USE_LIBAV) + # Find Freetype2 IF(CORSIX_TH_USE_FREETYPE2) FIND_PACKAGE(Freetype REQUIRED) diff --git a/CorsixTH/Src/config.h.in b/CorsixTH/Src/config.h.in index 3c9d7c8b3..a09313040 100644 --- a/CorsixTH/Src/config.h.in +++ b/CorsixTH/Src/config.h.in @@ -46,11 +46,13 @@ SOFTWARE. #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. +// FFMPEG or LibAV are 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 - +#ifndef CORSIX_TH_USE_FFMPEG +#cmakedefine CORSIX_TH_USE_LIBAV +#endif /** 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 diff --git a/CorsixTH/Src/th_movie.cpp b/CorsixTH/Src/th_movie.cpp index 81e9f568c..39833480b 100644 --- a/CorsixTH/Src/th_movie.cpp +++ b/CorsixTH/Src/th_movie.cpp @@ -23,7 +23,7 @@ SOFTWARE. #include "th_movie.h" #include "config.h" #include "lua_sdl.h" -#ifdef CORSIX_TH_USE_FFMPEG +#if defined(CORSIX_TH_USE_FFMPEG) || defined(CORSIX_TH_USE_LIBAV) #include "th_gfx.h" extern "C" @@ -31,6 +31,7 @@ extern "C" #include #include #include + #include #include } #include @@ -39,6 +40,13 @@ extern "C" #define INBUF_SIZE 4096 #define AUDIO_BUFFER_SIZE 1024 +#if (defined(CORSIX_TH_USE_LIBAV) && LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55, 45, 101)) || \ + (defined(CORSIX_TH_USE_FFMPEG) && LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55, 28, 1)) +#define av_frame_alloc avcodec_alloc_frame +#define av_frame_unref avcodec_get_frame_defaults +#define av_frame_free avcodec_free_frame +#endif + int th_movie_stream_reader_thread(void* pState) { THMovie *pMovie = (THMovie *)pState; @@ -271,7 +279,7 @@ int THMoviePictureBuffer::write(AVFrame* pFrame, double dPts) } /* Allocate a new frame and buffer for the destination RGB24 data. */ - AVFrame *pFrameRGB = avcodec_alloc_frame(); + AVFrame *pFrameRGB = av_frame_alloc(); int numBytes = avpicture_get_size(pMoviePicture->m_pixelFormat, pMoviePicture->m_iWidth, pMoviePicture->m_iHeight); uint8_t *buffer = (uint8_t *)av_malloc(numBytes * sizeof(uint8_t)); avpicture_fill((AVPicture *)pFrameRGB, buffer, pMoviePicture->m_pixelFormat, pMoviePicture->m_iWidth, pMoviePicture->m_iHeight); @@ -283,7 +291,7 @@ int THMoviePictureBuffer::write(AVFrame* pFrame, double dPts) SDL_UpdateTexture(pMoviePicture->m_pTexture, NULL, buffer, pMoviePicture->m_iWidth * 3); av_free(buffer); - av_free(pFrameRGB); + av_frame_free(&pFrameRGB); pMoviePicture->m_dPts = dPts; @@ -399,7 +407,7 @@ THMovie::THMovie(): m_pVideoQueue(NULL), m_pAudioQueue(NULL), m_pMoviePictureBuffer(new THMoviePictureBuffer()), - m_pSwrContext(NULL), + m_pAudioResampleContext(NULL), m_iAudioBufferSize(0), m_iAudioBufferMaxSize(0), m_pAudioPacket(NULL), @@ -567,13 +575,18 @@ void THMovie::unload() avformat_close_input(&m_pFormatContext); } - if(m_frame) + av_frame_free(&m_frame); + +#ifdef CORSIX_TH_USE_FFMPEG + swr_free(&m_pAudioResampleContext); +#elif defined(CORSIX_TH_USE_LIBAV) + // avresample_free doesn't skip NULL on it's own. + if (m_pAudioResampleContext != NULL) { - av_free(m_frame); - m_frame = NULL; + avresample_free(&m_pAudioResampleContext); + m_pAudioResampleContext = NULL; } - - swr_free(&m_pSwrContext); +#endif if(m_pAudioPacket) { @@ -620,8 +633,9 @@ void THMovie::play(int iX, int iY, int iWidth, int iHeight, int iChannel) if(m_iAudioStream >= 0) { Mix_QuerySpec(&m_iMixerFrequency, NULL, &m_iMixerChannels); - m_pSwrContext = swr_alloc_set_opts( - m_pSwrContext, +#ifdef CORSIX_TH_USE_FFMPEG + m_pAudioResampleContext = swr_alloc_set_opts( + m_pAudioResampleContext, m_iMixerChannels==1?AV_CH_LAYOUT_MONO:AV_CH_LAYOUT_STEREO, AV_SAMPLE_FMT_S16, m_iMixerFrequency, @@ -630,8 +644,17 @@ void THMovie::play(int iX, int iY, int iWidth, int iHeight, int iChannel) m_pAudioCodecContext->sample_rate, 0, NULL); - swr_init(m_pSwrContext); - + swr_init(m_pAudioResampleContext); +#elif defined(CORSIX_TH_USE_LIBAV) + m_pAudioResampleContext = avresample_alloc_context(); + av_opt_set_int(m_pAudioResampleContext, "in_channel_layout", m_pAudioCodecContext->channel_layout, 0); + av_opt_set_int(m_pAudioResampleContext, "out_channel_layout", m_iMixerChannels == 1 ? AV_CH_LAYOUT_MONO : AV_CH_LAYOUT_STEREO, 0); + av_opt_set_int(m_pAudioResampleContext, "in_sample_rate", m_pAudioCodecContext->sample_rate, 0); + av_opt_set_int(m_pAudioResampleContext, "out_sample_rate", m_iMixerFrequency, 0); + av_opt_set_int(m_pAudioResampleContext, "in_sample_fmt", m_pAudioCodecContext->sample_fmt, 0); + av_opt_set_int(m_pAudioResampleContext, "out_sample_fmt", AV_SAMPLE_FMT_S16, 0); + avresample_open(m_pAudioResampleContext); +#endif m_pChunk = Mix_QuickLoad_RAW(m_pbChunkBuffer, AUDIO_BUFFER_SIZE); m_iChannel = Mix_PlayChannel(iChannel, m_pChunk, -1); @@ -767,7 +790,7 @@ void THMovie::readStreams() void THMovie::runVideo() { - AVFrame *pFrame = avcodec_alloc_frame(); + AVFrame *pFrame = av_frame_alloc(); int64_t iStreamPts = AV_NOPTS_VALUE; double dClockPts; int iError; @@ -776,7 +799,7 @@ void THMovie::runVideo() while(!m_fAborting) { - avcodec_get_frame_defaults(pFrame); + av_frame_unref(pFrame); iError = getVideoFrame(pFrame, &iStreamPts); if(iError < 0) @@ -788,7 +811,7 @@ void THMovie::runVideo() continue; } - dClockPts = iStreamPts * av_q2d(m_pVideoCodecContext->time_base); + dClockPts = iStreamPts * av_q2d(m_pFormatContext->streams[m_iVideoStream]->time_base); iError = m_pMoviePictureBuffer->write(pFrame, dClockPts); if(iError < 0) @@ -798,7 +821,7 @@ void THMovie::runVideo() } avcodec_flush_buffers(m_pVideoCodecContext); - av_free(pFrame); + av_frame_free(&pFrame); } int THMovie::getVideoFrame(AVFrame *pFrame, int64_t *piPts) @@ -832,11 +855,19 @@ int THMovie::getVideoFrame(AVFrame *pFrame, int64_t *piPts) { iError = 1; +#ifdef CORSIX_TH_USE_LIBAV + *piPts = pFrame->pts; + if (*piPts == AV_NOPTS_VALUE) + { + *piPts = pFrame->pkt_dts; + } +#else #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 +#endif //LIBAVCODEC_VERSION_INT +#endif //CORSIX_T_USE_LIBAV if(*piPts == AV_NOPTS_VALUE) { @@ -943,11 +974,11 @@ int THMovie::decodeAudioFrame(bool fFirst) { if(!m_frame) { - m_frame = avcodec_alloc_frame(); + m_frame = av_frame_alloc(); } else { - avcodec_get_frame_defaults(m_frame); + av_frame_unref(m_frame); } if(fFlushComplete) @@ -977,7 +1008,6 @@ int THMovie::decodeAudioFrame(bool fFirst) } } -#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; @@ -991,33 +1021,15 @@ int THMovie::decodeAudioFrame(bool fFirst) 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; - } +#ifdef CORSIX_TH_USE_FFMPEG + swr_convert(m_pAudioResampleContext, &m_pbAudioBuffer, iOutSamples, (const uint8_t**)&m_frame->data[0], m_frame->nb_samples); +#elif defined(CORSIX_TH_USE_LIBAV) + avresample_convert(m_pAudioResampleContext, &m_pbAudioBuffer, 0, iOutSamples, (uint8_t**)&m_frame->data[0], 0, m_frame->nb_samples); #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 +#else //CORSIX_TH_USE_FFMPEG || CORSIX_TH_USE_LIBAV THMovie::THMovie() {} THMovie::~THMovie() {} void THMovie::setRenderer(SDL_Renderer *pRenderer) {} diff --git a/CorsixTH/Src/th_movie.h b/CorsixTH/Src/th_movie.h index bb1afde41..286826f4c 100644 --- a/CorsixTH/Src/th_movie.h +++ b/CorsixTH/Src/th_movie.h @@ -29,7 +29,7 @@ SOFTWARE. #include #include "config.h" -#ifdef CORSIX_TH_USE_FFMPEG +#if defined(CORSIX_TH_USE_FFMPEG) || defined(CORSIX_TH_USE_LIBAV) #include "SDL_mixer.h" extern "C" @@ -40,8 +40,12 @@ extern "C" #endif #include #include -#include #include +#ifdef CORSIX_TH_USE_FFMPEG +#include +#elif defined(CORSIX_TH_USE_LIBAV) +#include +#endif } struct SDL_Renderer; @@ -115,7 +119,7 @@ class THAVPacketQueue SDL_mutex *m_pMutex; SDL_cond *m_pCond; }; -#endif //CORSIX_TH_USE_FFMPEG +#endif //CORSIX_TH_USE_FFMPEG || CORSIX_TH_USE_LIBAV class THMovie { @@ -148,7 +152,7 @@ class THMovie void runVideo(); void copyAudioToStream(uint8_t *pbStream, int iStreamSize); protected: -#ifdef CORSIX_TH_USE_FFMPEG +#if defined(CORSIX_TH_USE_FFMPEG) || defined(CORSIX_TH_USE_LIBAV) int decodeAudioFrame(bool fFirst); int getVideoFrame(AVFrame *pFrame, int64_t *piPts); @@ -184,7 +188,11 @@ class THMovie double m_iCurSyncPts; //audio resample context - SwrContext* m_pSwrContext; +#ifdef CORSIX_TH_USE_FFMPEG + SwrContext* m_pAudioResampleContext; +#elif defined(CORSIX_TH_USE_LIBAV) + AVAudioResampleContext* m_pAudioResampleContext; +#endif //decoded audio buffer int m_iAudioBufferSize; @@ -215,7 +223,7 @@ class THMovie //threads SDL_Thread* m_pStreamThread; SDL_Thread* m_pVideoThread; -#endif //CORSIX_TH_USE_FFMPEG +#endif //CORSIX_TH_USE_FFMPEG || CORSIX_TH_USE_LIBAV }; #endif // TH_VIDEO_H