Added: FFmpeg based audio decoder
--HG-- branch : develop
This commit is contained in:
parent
c93d8ffd50
commit
e994c12983
7 changed files with 761 additions and 25 deletions
173
code/CMakeModules/FindFFmpeg.cmake
Normal file
173
code/CMakeModules/FindFFmpeg.cmake
Normal file
|
@ -0,0 +1,173 @@
|
|||
# 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 additionaly set.
|
||||
# - AVCODEC
|
||||
# - AVDEVICE
|
||||
# - AVFORMAT
|
||||
# - AVUTIL
|
||||
# - POSTPROC
|
||||
# - 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.
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
|
||||
if(NOT FFmpeg_FIND_COMPONENTS)
|
||||
set(FFmpeg_FIND_COMPONENTS AVFORMAT AVCODEC 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()
|
||||
|
||||
find_path(${_component}_INCLUDE_DIRS ${_header}
|
||||
HINTS
|
||||
${FFMPEGSDK_INC}
|
||||
${PC_LIB${_component}_INCLUDEDIR}
|
||||
${PC_LIB${_component}_INCLUDE_DIRS}
|
||||
PATH_SUFFIXES
|
||||
ffmpeg
|
||||
)
|
||||
|
||||
find_library(${_component}_LIBRARIES NAMES ${_library}
|
||||
HINTS
|
||||
${FFMPEGSDK_LIB}
|
||||
${PC_LIB${_component}_LIBDIR}
|
||||
${PC_LIB${_component}_LIBRARY_DIRS}
|
||||
)
|
||||
|
||||
STRING(REGEX REPLACE "/.*" "/version.h" _ver_header ${_header})
|
||||
if(EXISTS "${${_component}_INCLUDE_DIRS}/${_ver_header}")
|
||||
file(STRINGS "${${_component}_INCLUDE_DIRS}/${_ver_header}" version_str REGEX "^#define[\t ]+LIB${_component}_VERSION_M.*")
|
||||
|
||||
foreach(_str "${version_str}")
|
||||
if(NOT version_maj)
|
||||
string(REGEX REPLACE "^.*LIB${_component}_VERSION_MAJOR[\t ]+([0-9]*).*$" "\\1" version_maj "${_str}")
|
||||
endif()
|
||||
if(NOT version_min)
|
||||
string(REGEX REPLACE "^.*LIB${_component}_VERSION_MINOR[\t ]+([0-9]*).*$" "\\1" version_min "${_str}")
|
||||
endif()
|
||||
if(NOT version_mic)
|
||||
string(REGEX REPLACE "^.*LIB${_component}_VERSION_MICRO[\t ]+([0-9]*).*$" "\\1" version_mic "${_str}")
|
||||
endif()
|
||||
endforeach()
|
||||
unset(version_str)
|
||||
|
||||
set(${_component}_VERSION "${version_maj}.${version_min}.${version_mic}" CACHE STRING "The ${_component} version number.")
|
||||
unset(version_maj)
|
||||
unset(version_min)
|
||||
unset(version_mic)
|
||||
endif(EXISTS "${${_component}_INCLUDE_DIRS}/${_ver_header}")
|
||||
set(${_component}_VERSION ${PC_${_component}_VERSION} CACHE STRING "The ${_component} version number.")
|
||||
set(${_component}_DEFINITIONS ${PC_${_component}_CFLAGS_OTHER} CACHE STRING "The ${_component} CFLAGS.")
|
||||
|
||||
set_component_found(${_component})
|
||||
|
||||
mark_as_advanced(
|
||||
${_component}_INCLUDE_DIRS
|
||||
${_component}_LIBRARIES
|
||||
${_component}_DEFINITIONS
|
||||
${_component}_VERSION)
|
||||
endmacro()
|
||||
|
||||
|
||||
set(FFMPEGSDK $ENV{FFMPEG_HOME})
|
||||
if(FFMPEGSDK)
|
||||
set(FFMPEGSDK_INC "${FFMPEGSDK}/include")
|
||||
set(FFMPEGSDK_LIB "${FFMPEGSDK}/lib")
|
||||
endif()
|
||||
|
||||
# Check for all possible components.
|
||||
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(SWRESAMPLE libswresample swresample libswresample/swresample.h)
|
||||
find_component(POSTPROC libpostproc postproc libpostproc/postprocess.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 and library list with duplicates removed.
|
||||
if(FFMPEG_INCLUDE_DIRS)
|
||||
list(REMOVE_DUPLICATES FFMPEG_INCLUDE_DIRS)
|
||||
endif()
|
||||
|
||||
if(FFMPEG_LIBRARIES)
|
||||
list(REMOVE_DUPLICATES FFMPEG_LIBRARIES)
|
||||
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)
|
||||
|
||||
# Now set the noncached _FOUND vars for the components.
|
||||
foreach(_component AVCODEC AVDEVICE AVFORMAT AVUTIL POSTPROCESS SWRESAMPLE SWSCALE)
|
||||
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})
|
|
@ -20,6 +20,7 @@ ENDIF()
|
|||
IF(WITH_SOUND)
|
||||
FIND_PACKAGE(Ogg)
|
||||
FIND_PACKAGE(Vorbis)
|
||||
FIND_PACKAGE(FFmpeg COMPONENTS AVCODEC AVFORMAT AVUTIL SWRESAMPLE)
|
||||
|
||||
IF(WITH_DRIVER_OPENAL)
|
||||
FIND_PACKAGE(OpenAL)
|
||||
|
|
108
code/nel/include/nel/sound/audio_decoder_ffmpeg.h
Normal file
108
code/nel/include/nel/sound/audio_decoder_ffmpeg.h
Normal file
|
@ -0,0 +1,108 @@
|
|||
// NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
|
||||
// Copyright (C) 2018 Winch Gate Property Limited
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as
|
||||
// published by the Free Software Foundation, either version 3 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#ifndef NLSOUND_AUDIO_DECODER_FFMPEG_H
|
||||
#define NLSOUND_AUDIO_DECODER_FFMPEG_H
|
||||
#include <nel/misc/types_nl.h>
|
||||
|
||||
#include <nel/sound/audio_decoder.h>
|
||||
|
||||
struct AVCodecContext;
|
||||
struct AVFormatContext;
|
||||
struct AVIOContext;
|
||||
struct AVPacket;
|
||||
struct SwrContext;
|
||||
|
||||
namespace NLSOUND {
|
||||
|
||||
/**
|
||||
* \brief CAudioDecoderFfmpeg
|
||||
* \date 2018-10-21 08:08GMT
|
||||
* \author Meelis Mägi (Nimetu)
|
||||
* CAudioDecoderFfmpeg
|
||||
* Create trough IAudioDecoder
|
||||
*/
|
||||
class CAudioDecoderFfmpeg : public IAudioDecoder
|
||||
{
|
||||
protected:
|
||||
NLMISC::IStream *_Stream;
|
||||
|
||||
bool _IsSupported;
|
||||
bool _Loop;
|
||||
bool _IsMusicEnded;
|
||||
sint32 _StreamOffset;
|
||||
sint32 _StreamSize;
|
||||
|
||||
AVIOContext *_AvioContext;
|
||||
AVFormatContext *_FormatContext;
|
||||
AVCodecContext *_AudioContext;
|
||||
SwrContext *_SwrContext;
|
||||
|
||||
// selected stream
|
||||
sint32 _AudioStreamIndex;
|
||||
|
||||
// output buffer for decoded frame
|
||||
SwrContext *_ConvertContext;
|
||||
|
||||
private:
|
||||
// called from constructor if ffmpeg fails to initialize
|
||||
// or from destructor to cleanup ffmpeg pointers
|
||||
void release();
|
||||
|
||||
public:
|
||||
CAudioDecoderFfmpeg(NLMISC::IStream *stream, bool loop);
|
||||
virtual ~CAudioDecoderFfmpeg();
|
||||
|
||||
inline NLMISC::IStream *getStream() { return _Stream; }
|
||||
inline sint32 getStreamSize() { return _StreamSize; }
|
||||
inline sint32 getStreamOffset() { return _StreamOffset; }
|
||||
|
||||
// Return true if ffmpeg is able to decode the stream
|
||||
bool isFormatSupported() const;
|
||||
|
||||
/// Get information on a music file (only artist and title at the moment).
|
||||
static bool getInfo(NLMISC::IStream *stream, std::string &artist, std::string &title, float &length);
|
||||
|
||||
/// Get how many bytes the music buffer requires for output minimum.
|
||||
virtual uint32 getRequiredBytes();
|
||||
|
||||
/// Get an amount of bytes between minimum and maximum (can be lower than minimum if at end).
|
||||
virtual uint32 getNextBytes(uint8 *buffer, uint32 minimum, uint32 maximum);
|
||||
|
||||
/// Get the amount of channels (2 is stereo) in output.
|
||||
virtual uint8 getChannels();
|
||||
|
||||
/// Get the samples per second (often 44100) in output.
|
||||
virtual uint getSamplesPerSec();
|
||||
|
||||
/// Get the bits per sample (often 16) in output.
|
||||
virtual uint8 getBitsPerSample();
|
||||
|
||||
/// Get if the music has ended playing (never true if loop).
|
||||
virtual bool isMusicEnded();
|
||||
|
||||
/// Get the total time in seconds.
|
||||
virtual float getLength();
|
||||
|
||||
/// Set looping
|
||||
virtual void setLooping(bool loop);
|
||||
}; /* class CAudioDecoderFfmpeg */
|
||||
|
||||
} /* namespace NLSOUND */
|
||||
|
||||
#endif // NLSOUND_AUDIO_DECODER_FFMPEG_H
|
||||
|
||||
/* end of file */
|
|
@ -58,6 +58,7 @@ FILE(GLOB STREAM
|
|||
FILE(GLOB STREAM_FILE
|
||||
audio_decoder.cpp ../../include/nel/sound/audio_decoder.h
|
||||
audio_decoder_vorbis.cpp ../../include/nel/sound/audio_decoder_vorbis.h
|
||||
audio_decoder_ffmpeg.cpp ../../include/nel/sound/audio_decoder_ffmpeg.h
|
||||
stream_file_sound.cpp ../../include/nel/sound/stream_file_sound.h
|
||||
stream_file_source.cpp ../../include/nel/sound/stream_file_source.h
|
||||
)
|
||||
|
@ -95,6 +96,12 @@ IF(WITH_STATIC)
|
|||
TARGET_LINK_LIBRARIES(nelsound ${OGG_LIBRARY})
|
||||
ENDIF()
|
||||
|
||||
IF(FFMPEG_FOUND)
|
||||
ADD_DEFINITIONS(-DFFMPEG_ENABLED)
|
||||
INCLUDE_DIRECTORIES(${FFMPEG_INCLUDE_DIRS})
|
||||
TARGET_LINK_LIBRARIES(nelsound ${FFMPEG_LIBRARIES})
|
||||
ENDIF()
|
||||
|
||||
|
||||
INCLUDE_DIRECTORIES(${LIBXML2_INCLUDE_DIR})
|
||||
|
||||
|
|
|
@ -37,6 +37,10 @@
|
|||
// Project includes
|
||||
#include <nel/sound/audio_decoder_vorbis.h>
|
||||
|
||||
#ifdef FFMPEG_ENABLED
|
||||
#include <nel/sound/audio_decoder_ffmpeg.h>
|
||||
#endif
|
||||
|
||||
using namespace std;
|
||||
using namespace NLMISC;
|
||||
|
||||
|
@ -82,6 +86,17 @@ IAudioDecoder *IAudioDecoder::createAudioDecoder(const std::string &type, NLMISC
|
|||
nlwarning("Stream is NULL");
|
||||
return NULL;
|
||||
}
|
||||
#ifdef FFMPEG_ENABLED
|
||||
try {
|
||||
CAudioDecoderFfmpeg *decoder = new CAudioDecoderFfmpeg(stream, loop);
|
||||
return static_cast<IAudioDecoder *>(decoder);
|
||||
}
|
||||
catch(const Exception &e)
|
||||
{
|
||||
nlwarning("Exception %s during ffmpeg setup", e.what());
|
||||
return NULL;
|
||||
}
|
||||
#else
|
||||
std::string type_lower = toLower(type);
|
||||
if (type_lower == "ogg")
|
||||
{
|
||||
|
@ -92,6 +107,7 @@ IAudioDecoder *IAudioDecoder::createAudioDecoder(const std::string &type, NLMISC
|
|||
nlwarning("Music file type unknown: '%s'", type_lower.c_str());
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool IAudioDecoder::getInfo(const std::string &filepath, std::string &artist, std::string &title, float &length)
|
||||
|
@ -102,6 +118,14 @@ bool IAudioDecoder::getInfo(const std::string &filepath, std::string &artist, st
|
|||
nlwarning("Music file %s does not exist!", filepath.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef FFMPEG_ENABLED
|
||||
CIFile ifile;
|
||||
ifile.setCacheFileOnOpen(false);
|
||||
ifile.allowBNPCacheFileOnOpen(false);
|
||||
if (ifile.open(lookup))
|
||||
return CAudioDecoderFfmpeg::getInfo(&ifile, artist, title, length);
|
||||
#else
|
||||
std::string type = CFile::getExtension(filepath);
|
||||
std::string type_lower = NLMISC::toLower(type);
|
||||
|
||||
|
@ -119,6 +143,7 @@ bool IAudioDecoder::getInfo(const std::string &filepath, std::string &artist, st
|
|||
{
|
||||
nlwarning("Music file type unknown: '%s'", type_lower.c_str());
|
||||
}
|
||||
#endif
|
||||
|
||||
artist.clear(); title.clear();
|
||||
return false;
|
||||
|
@ -132,6 +157,11 @@ void IAudioDecoder::getMusicExtensions(std::vector<std::string> &extensions)
|
|||
{
|
||||
extensions.push_back("ogg");
|
||||
}
|
||||
#ifdef FFMPEG_ENABLED
|
||||
extensions.push_back("mp3");
|
||||
extensions.push_back("flac");
|
||||
extensions.push_back("aac");
|
||||
#endif
|
||||
|
||||
// extensions.push_back("wav"); // TODO: Easy.
|
||||
}
|
||||
|
@ -139,7 +169,11 @@ void IAudioDecoder::getMusicExtensions(std::vector<std::string> &extensions)
|
|||
/// Return if a music extension is supported by the nel sound library.
|
||||
bool IAudioDecoder::isMusicExtensionSupported(const std::string &extension)
|
||||
{
|
||||
#ifdef FFMPEG_ENABLED
|
||||
return (extension == "ogg" || extension == "mp3" || extension == "flac" || extension == "aac");
|
||||
#else
|
||||
return (extension == "ogg");
|
||||
#endif
|
||||
}
|
||||
|
||||
} /* namespace NLSOUND */
|
||||
|
|
430
code/nel/src/sound/audio_decoder_ffmpeg.cpp
Normal file
430
code/nel/src/sound/audio_decoder_ffmpeg.cpp
Normal file
|
@ -0,0 +1,430 @@
|
|||
// NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
|
||||
// Copyright (C) 2018 Winch Gate Property Limited
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as
|
||||
// published by the Free Software Foundation, either version 3 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
#include "stdsound.h"
|
||||
|
||||
#include <nel/sound/audio_decoder_ffmpeg.h>
|
||||
|
||||
#define __STDC_CONSTANT_MACROS
|
||||
extern "C"
|
||||
{
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libswresample/swresample.h>
|
||||
#include <libavutil/opt.h>
|
||||
};
|
||||
|
||||
using namespace std;
|
||||
using namespace NLMISC;
|
||||
|
||||
namespace {
|
||||
|
||||
const std::string av_err2string(sint err)
|
||||
{
|
||||
char buf[AV_ERROR_MAX_STRING_SIZE];
|
||||
av_strerror(err, buf, AV_ERROR_MAX_STRING_SIZE);
|
||||
return (std::string)buf;
|
||||
}
|
||||
|
||||
void nel_logger(void *ptr, int level, const char *fmt, va_list vargs)
|
||||
{
|
||||
static char msg[1024];
|
||||
|
||||
const char *module = NULL;
|
||||
|
||||
// AV_LOG_DEBUG, AV_LOG_TRACE
|
||||
if (level >= AV_LOG_DEBUG) return;
|
||||
|
||||
if (ptr)
|
||||
{
|
||||
AVClass *avc = *(AVClass**) ptr;
|
||||
module = avc->item_name(ptr);
|
||||
}
|
||||
|
||||
vsnprintf(msg, sizeof(msg), fmt, vargs);
|
||||
msg[sizeof(msg)-1] = '\0';
|
||||
|
||||
switch(level)
|
||||
{
|
||||
case AV_LOG_PANIC:
|
||||
// ffmpeg is about to crash so lets throw
|
||||
nlerror("FFMPEG(P): (%s) %s", module, msg);
|
||||
break;
|
||||
case AV_LOG_FATAL:
|
||||
// ffmpeg had unrecoverable error, corrupted stream or such
|
||||
nlerrornoex("FFMPEG(F): (%s) %s", module, msg);
|
||||
break;
|
||||
case AV_LOG_ERROR:
|
||||
nlwarning("FFMPEG(E): (%s) %s", module, msg);
|
||||
break;
|
||||
case AV_LOG_WARNING:
|
||||
nlwarning("FFMPEG(W): (%s) %s", module, msg);
|
||||
break;
|
||||
case AV_LOG_INFO:
|
||||
nlinfo("FFMPEG(I): (%s) %s", module, msg);
|
||||
break;
|
||||
case AV_LOG_VERBOSE:
|
||||
nldebug("FFMPEG(V): (%s) %s", module, msg);
|
||||
break;
|
||||
case AV_LOG_DEBUG:
|
||||
nldebug("FFMPEG(D): (%s) %s", module, msg);
|
||||
break;
|
||||
default:
|
||||
nlinfo("FFMPEG: invalid log level:%d (%s) %s", level, module, msg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
class CFfmpegInstance
|
||||
{
|
||||
public:
|
||||
CFfmpegInstance()
|
||||
{
|
||||
av_log_set_level(AV_LOG_DEBUG);
|
||||
av_log_set_callback(nel_logger);
|
||||
|
||||
av_register_all();
|
||||
|
||||
//avformat_network_init();
|
||||
}
|
||||
|
||||
virtual ~CFfmpegInstance()
|
||||
{
|
||||
//avformat_network_deinit();
|
||||
}
|
||||
};
|
||||
|
||||
CFfmpegInstance ffmpeg;
|
||||
|
||||
// Send bytes to ffmpeg
|
||||
int avio_read_packet(void *opaque, uint8 *buf, int buf_size)
|
||||
{
|
||||
NLSOUND::CAudioDecoderFfmpeg *decoder = static_cast<NLSOUND::CAudioDecoderFfmpeg *>(opaque);
|
||||
NLMISC::IStream *stream = decoder->getStream();
|
||||
nlassert(stream->isReading());
|
||||
|
||||
uint32 available = decoder->getStreamSize() - stream->getPos();
|
||||
if (available == 0) return 0;
|
||||
|
||||
buf_size = FFMIN(buf_size, available);
|
||||
stream->serialBuffer((uint8 *)buf, buf_size);
|
||||
return buf_size;
|
||||
}
|
||||
|
||||
sint64 avio_seek(void *opaque, sint64 offset, int whence)
|
||||
{
|
||||
NLSOUND::CAudioDecoderFfmpeg *decoder = static_cast<NLSOUND::CAudioDecoderFfmpeg *>(opaque);
|
||||
NLMISC::IStream *stream = decoder->getStream();
|
||||
nlassert(stream->isReading());
|
||||
|
||||
NLMISC::IStream::TSeekOrigin origin;
|
||||
switch(whence)
|
||||
{
|
||||
case SEEK_SET:
|
||||
origin = NLMISC::IStream::begin;
|
||||
break;
|
||||
case SEEK_CUR:
|
||||
origin = NLMISC::IStream::current;
|
||||
break;
|
||||
case SEEK_END:
|
||||
origin = NLMISC::IStream::end;
|
||||
break;
|
||||
case AVSEEK_SIZE:
|
||||
return decoder->getStreamSize();
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
|
||||
stream->seek((sint32) offset, origin);
|
||||
return stream->getPos();
|
||||
}
|
||||
|
||||
}//ns
|
||||
|
||||
namespace NLSOUND {
|
||||
|
||||
// swresample will convert audio to this format
|
||||
#define FFMPEG_SAMPLE_RATE 44100
|
||||
#define FFMPEG_CHANNELS 2
|
||||
#define FFMPEG_CHANNEL_LAYOUT AV_CH_LAYOUT_STEREO
|
||||
#define FFMPEG_BITS_PER_SAMPLE 16
|
||||
#define FFMPEG_SAMPLE_FORMAT AV_SAMPLE_FMT_S16
|
||||
|
||||
CAudioDecoderFfmpeg::CAudioDecoderFfmpeg(NLMISC::IStream *stream, bool loop)
|
||||
: IAudioDecoder(),
|
||||
_Stream(stream), _Loop(loop), _IsMusicEnded(false), _StreamSize(0), _IsSupported(false),
|
||||
_AvioContext(NULL), _FormatContext(NULL),
|
||||
_AudioContext(NULL), _AudioStreamIndex(0),
|
||||
_SwrContext(NULL)
|
||||
{
|
||||
_StreamOffset = stream->getPos();
|
||||
stream->seek(0, NLMISC::IStream::end);
|
||||
_StreamSize = stream->getPos();
|
||||
stream->seek(_StreamOffset, NLMISC::IStream::begin);
|
||||
|
||||
try {
|
||||
_FormatContext = avformat_alloc_context();
|
||||
if (!_FormatContext)
|
||||
throw Exception("Can't create AVFormatContext");
|
||||
|
||||
// avio_ctx_buffer can be reallocated by ffmpeg and assigned to avio_ctx->buffer
|
||||
uint8 *avio_ctx_buffer = NULL;
|
||||
size_t avio_ctx_buffer_size = 4096;
|
||||
avio_ctx_buffer = static_cast<uint8 *>(av_malloc(avio_ctx_buffer_size));
|
||||
if (!avio_ctx_buffer)
|
||||
throw Exception("Can't allocate avio context buffer");
|
||||
|
||||
_AvioContext = avio_alloc_context(avio_ctx_buffer, avio_ctx_buffer_size, 0, this, &avio_read_packet, NULL, &avio_seek);
|
||||
if (!_AvioContext)
|
||||
throw Exception("Can't allocate avio context");
|
||||
|
||||
_FormatContext->pb = _AvioContext;
|
||||
sint ret = avformat_open_input(&_FormatContext, NULL, NULL, NULL);
|
||||
if (ret < 0)
|
||||
throw Exception("avformat_open_input: %d", ret);
|
||||
|
||||
// find stream and then audio codec to see if ffmpeg supports this
|
||||
_IsSupported = false;
|
||||
if (avformat_find_stream_info(_FormatContext, NULL) >= 0)
|
||||
{
|
||||
_AudioStreamIndex = av_find_best_stream(_FormatContext, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
|
||||
if (_AudioStreamIndex >= 0)
|
||||
{
|
||||
_AudioContext = _FormatContext->streams[_AudioStreamIndex]->codec;
|
||||
av_opt_set_int(_AudioContext, "refcounted_frames", 1, 0);
|
||||
|
||||
AVCodec *codec = avcodec_find_decoder(_AudioContext->codec_id);
|
||||
if (codec != NULL && avcodec_open2(_AudioContext, codec, NULL) >= 0)
|
||||
{
|
||||
_IsSupported = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
release();
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
if (!_IsSupported)
|
||||
{
|
||||
nlwarning("FFMPEG: Decoder created, unknown stream format / codec");
|
||||
}
|
||||
}
|
||||
|
||||
CAudioDecoderFfmpeg::~CAudioDecoderFfmpeg()
|
||||
{
|
||||
release();
|
||||
}
|
||||
|
||||
void CAudioDecoderFfmpeg::release()
|
||||
{
|
||||
if (_SwrContext)
|
||||
swr_free(&_SwrContext);
|
||||
|
||||
if (_AudioContext)
|
||||
avcodec_close(_AudioContext);
|
||||
|
||||
if (_FormatContext)
|
||||
avformat_close_input(&_FormatContext);
|
||||
|
||||
if (_AvioContext && _AvioContext->buffer)
|
||||
av_freep(&_AvioContext->buffer);
|
||||
|
||||
if (_AvioContext)
|
||||
av_freep(&_AvioContext);
|
||||
}
|
||||
|
||||
bool CAudioDecoderFfmpeg::isFormatSupported() const
|
||||
{
|
||||
return _IsSupported;
|
||||
}
|
||||
|
||||
/// Get information on a music file.
|
||||
bool CAudioDecoderFfmpeg::getInfo(NLMISC::IStream *stream, std::string &artist, std::string &title, float &length)
|
||||
{
|
||||
CAudioDecoderFfmpeg ffmpeg(stream, false);
|
||||
if (!ffmpeg.isFormatSupported())
|
||||
{
|
||||
title.clear();
|
||||
artist.clear();
|
||||
length = 0.f;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
AVDictionaryEntry *tag = NULL;
|
||||
while((tag = av_dict_get(ffmpeg._FormatContext->metadata, "", tag, AV_DICT_IGNORE_SUFFIX)))
|
||||
{
|
||||
if (!strcmp(tag->key, "artist"))
|
||||
{
|
||||
artist = tag->value;
|
||||
}
|
||||
else if (!strcmp(tag->key, "title"))
|
||||
{
|
||||
title = tag->value;
|
||||
}
|
||||
}
|
||||
|
||||
if (ffmpeg._FormatContext->duration != AV_NOPTS_VALUE)
|
||||
{
|
||||
length = ffmpeg._FormatContext->duration * av_q2d(AV_TIME_BASE_Q);
|
||||
}
|
||||
else if (ffmpeg._FormatContext->streams[ffmpeg._AudioStreamIndex]->duration != AV_NOPTS_VALUE)
|
||||
{
|
||||
length = ffmpeg._FormatContext->streams[ffmpeg._AudioStreamIndex]->duration * av_q2d(ffmpeg._FormatContext->streams[ffmpeg._AudioStreamIndex]->time_base);
|
||||
}
|
||||
else
|
||||
{
|
||||
length = 0.f;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32 CAudioDecoderFfmpeg::getRequiredBytes()
|
||||
{
|
||||
return 0; // no minimum requirement of bytes to buffer out
|
||||
}
|
||||
|
||||
uint32 CAudioDecoderFfmpeg::getNextBytes(uint8 *buffer, uint32 minimum, uint32 maximum)
|
||||
{
|
||||
if (_IsMusicEnded) return 0;
|
||||
nlassert(minimum <= maximum); // can't have this..
|
||||
|
||||
// TODO: CStreamFileSource::play() will stall when there is no frames on warmup
|
||||
// supported can be set false if there is an issue creating converter
|
||||
if (!_IsSupported)
|
||||
{
|
||||
_IsMusicEnded = true;
|
||||
return 1;
|
||||
}
|
||||
|
||||
uint32 bytes_read = 0;
|
||||
|
||||
AVFrame frame = {0};
|
||||
AVPacket packet = {0};
|
||||
|
||||
if (!_SwrContext)
|
||||
{
|
||||
sint64 in_channel_layout = av_get_default_channel_layout(_AudioContext->channels);
|
||||
_SwrContext = swr_alloc_set_opts(NULL,
|
||||
// output
|
||||
FFMPEG_CHANNEL_LAYOUT, FFMPEG_SAMPLE_FORMAT, FFMPEG_SAMPLE_RATE,
|
||||
// input
|
||||
in_channel_layout, _AudioContext->sample_fmt, _AudioContext->sample_rate,
|
||||
0, NULL);
|
||||
swr_init(_SwrContext);
|
||||
}
|
||||
|
||||
sint ret;
|
||||
while(bytes_read < minimum)
|
||||
{
|
||||
// read packet from stream
|
||||
if ((ret = av_read_frame(_FormatContext, &packet)) < 0)
|
||||
{
|
||||
_IsMusicEnded = true;
|
||||
// TODO: looping
|
||||
break;
|
||||
}
|
||||
|
||||
if (packet.stream_index == _AudioStreamIndex)
|
||||
{
|
||||
// packet can contain multiple frames
|
||||
AVPacket first = packet;
|
||||
int got_frame = 0;
|
||||
do {
|
||||
got_frame = 0;
|
||||
ret = avcodec_decode_audio4(_AudioContext, &frame, &got_frame, &packet);
|
||||
if (ret < 0)
|
||||
{
|
||||
nlwarning("FFMPEG: error decoding audio frame: %s", av_err2string(ret).c_str());
|
||||
break;
|
||||
}
|
||||
packet.size -= ret;
|
||||
packet.data += ret;
|
||||
|
||||
if (got_frame)
|
||||
{
|
||||
uint32 out_bps = av_get_bytes_per_sample(FFMPEG_SAMPLE_FORMAT) * FFMPEG_CHANNELS;
|
||||
uint32 max_samples = (maximum - bytes_read) / out_bps;
|
||||
|
||||
uint32 out_samples = av_rescale_rnd(swr_get_delay(_SwrContext, _AudioContext->sample_rate) + frame.nb_samples,
|
||||
FFMPEG_SAMPLE_RATE, _AudioContext->sample_rate, AV_ROUND_UP);
|
||||
|
||||
if (max_samples > out_samples)
|
||||
max_samples = out_samples;
|
||||
|
||||
uint32 converted = swr_convert(_SwrContext, &buffer, max_samples, (const uint8 **)frame.extended_data, frame.nb_samples);
|
||||
uint32 size = out_bps * converted;
|
||||
|
||||
bytes_read += size;
|
||||
buffer += size;
|
||||
|
||||
av_frame_unref(&frame);
|
||||
}
|
||||
} while (got_frame && packet.size > 0);
|
||||
|
||||
av_packet_unref(&first);
|
||||
}
|
||||
else
|
||||
{
|
||||
ret = 0;
|
||||
av_packet_unref(&packet);
|
||||
}
|
||||
}
|
||||
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
uint8 CAudioDecoderFfmpeg::getChannels()
|
||||
{
|
||||
return FFMPEG_CHANNELS;
|
||||
}
|
||||
|
||||
uint CAudioDecoderFfmpeg::getSamplesPerSec()
|
||||
{
|
||||
return FFMPEG_SAMPLE_RATE;
|
||||
}
|
||||
|
||||
uint8 CAudioDecoderFfmpeg::getBitsPerSample()
|
||||
{
|
||||
return FFMPEG_BITS_PER_SAMPLE;
|
||||
}
|
||||
|
||||
bool CAudioDecoderFfmpeg::isMusicEnded()
|
||||
{
|
||||
return _IsMusicEnded;
|
||||
}
|
||||
|
||||
float CAudioDecoderFfmpeg::getLength()
|
||||
{
|
||||
printf(">> CAudioDecoderFfmpeg::getLength\n");
|
||||
// TODO: return (float)ov_time_total(&_OggVorbisFile, -1);
|
||||
return 0.f;
|
||||
}
|
||||
|
||||
void CAudioDecoderFfmpeg::setLooping(bool loop)
|
||||
{
|
||||
_Loop = loop;
|
||||
}
|
||||
|
||||
} /* namespace NLSOUND */
|
||||
|
||||
/* end of file */
|
|
@ -372,22 +372,10 @@ public:
|
|||
// no format supported
|
||||
if (extensions.empty()) return;
|
||||
|
||||
bool oggSupported = false;
|
||||
bool mp3Supported = false;
|
||||
|
||||
std::string message;
|
||||
for(uint i = 0; i < extensions.size(); ++i)
|
||||
{
|
||||
if (extensions[i] == "ogg")
|
||||
{
|
||||
oggSupported = true;
|
||||
message += " ogg";
|
||||
}
|
||||
else if (extensions[i] == "mp3")
|
||||
{
|
||||
mp3Supported = true;
|
||||
message += " mp3";
|
||||
}
|
||||
message += " " + extensions[i];
|
||||
}
|
||||
message += " m3u m3u8";
|
||||
nlinfo("Media player supports: '%s'", message.substr(1).c_str());
|
||||
|
@ -404,14 +392,8 @@ public:
|
|||
for (i = 0; i < filesToProcess.size(); ++i)
|
||||
{
|
||||
std::string ext = toLower(CFile::getExtension(filesToProcess[i]));
|
||||
if (ext == "ogg")
|
||||
if (std::find(extensions.begin(), extensions.end(), ext) != extensions.end())
|
||||
{
|
||||
if (oggSupported)
|
||||
filenames.push_back(filesToProcess[i]);
|
||||
}
|
||||
else if (ext == "mp3" || ext == "mp2" || ext == "mp1")
|
||||
{
|
||||
if (mp3Supported)
|
||||
filenames.push_back(filesToProcess[i]);
|
||||
}
|
||||
else if (ext == "m3u" || ext == "m3u8")
|
||||
|
@ -448,6 +430,7 @@ public:
|
|||
|
||||
CMusicPlayer::CSongs song;
|
||||
song.Filename = filenames[i];
|
||||
// TODO: cache the result for next refresh
|
||||
SoundMngr->getMixer()->getSongTitle(filenames[i], song.Title, song.Length);
|
||||
if (song.Length > 0)
|
||||
songs.push_back (song);
|
||||
|
|
Loading…
Reference in a new issue