From f67a9f0515a3eac01a10f5fdfbd369a92a2959ab Mon Sep 17 00:00:00 2001 From: Ferdinand Thiessen Date: Sun, 21 Feb 2016 16:08:44 +0100 Subject: [PATCH] Added Opus sound file support, added SoundFileReaderOpus and FindOpus cmake Module --- cmake/Modules/FindOpus.cmake | 43 ++++++ src/SFML/Audio/CMakeLists.txt | 3 + src/SFML/Audio/SoundFileFactory.cpp | 5 +- src/SFML/Audio/SoundFileReaderOpus.cpp | 189 +++++++++++++++++++++++++ src/SFML/Audio/SoundFileReaderOpus.hpp | 124 ++++++++++++++++ 5 files changed, 363 insertions(+), 1 deletion(-) create mode 100644 cmake/Modules/FindOpus.cmake create mode 100644 src/SFML/Audio/SoundFileReaderOpus.cpp create mode 100644 src/SFML/Audio/SoundFileReaderOpus.hpp diff --git a/cmake/Modules/FindOpus.cmake b/cmake/Modules/FindOpus.cmake new file mode 100644 index 00000000..bdbc76ef --- /dev/null +++ b/cmake/Modules/FindOpus.cmake @@ -0,0 +1,43 @@ +# This file was taken from Unvanquished, +# Copyright 2000-2009 Kitware, Inc., Insight Software Consortium +# It's licensed under the terms of the 3-clause OpenBSD license. +# Modifications Copyright 2014-2015 the openage authors. +# See copying.md for further legal info. + +# - Find opus library +# Find the native Opus headers and libraries. +# This module defines +# OPUS_INCLUDE_DIRS - where to find opus/opus.h, opus/opusfile.h, etc +# OPUS_LIBRARIES - List of libraries when using libopus +# OPUS_FOUND - True if opus is found. + +# find the opusfile header, defines our api. +find_path(OPUS_INCLUDE_DIR + NAMES opus/opusfile.h + DOC "Opus include directory" +) +mark_as_advanced(OPUS_INCLUDE_DIR) + +# look for libopusfile, the highlevel container-aware api. +find_library(OPUSFILE_LIBRARY + NAMES opusfile + DOC "Path to OpusFile library" +) +mark_as_advanced(OPUSFILE_LIBRARY) + +# find libopus, the core codec component. +find_library(OPUS_LIBRARY + NAMES opus + DOC "Path to Opus library" +) +mark_as_advanced(OPUS_LIBRARY) + + +# handle the QUIETLY and REQUIRED arguments and set OPUSFILE_FOUND to TRUE if +# all listed variables are TRUE +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Opus DEFAULT_MSG OPUSFILE_LIBRARY OPUS_LIBRARY OPUS_INCLUDE_DIR) + +# export the variables +set(OPUS_LIBRARIES "${OPUSFILE_LIBRARY}" "${OPUS_LIBRARY}") +set(OPUS_INCLUDE_DIRS "${OPUS_INCLUDE_DIR}" "${OPUS_INCLUDE_DIR}/opus") diff --git a/src/SFML/Audio/CMakeLists.txt b/src/SFML/Audio/CMakeLists.txt index ab97a37c..8dbb3817 100644 --- a/src/SFML/Audio/CMakeLists.txt +++ b/src/SFML/Audio/CMakeLists.txt @@ -43,6 +43,8 @@ set(CODECS_SRC ${SRCROOT}/SoundFileReaderFlac.cpp ${SRCROOT}/SoundFileReaderOgg.hpp ${SRCROOT}/SoundFileReaderOgg.cpp + ${SRCROOT}/SoundFileReaderOpus.hpp + ${SRCROOT}/SoundFileReaderOpus.cpp ${SRCROOT}/SoundFileReaderWav.hpp ${SRCROOT}/SoundFileReaderWav.cpp ${INCROOT}/SoundFileWriter.hpp @@ -69,6 +71,7 @@ endif() sfml_find_package(OpenAL INCLUDE "OPENAL_INCLUDE_DIR" LINK "OPENAL_LIBRARY") sfml_find_package(VORBIS INCLUDE "VORBIS_INCLUDE_DIRS" LINK "VORBIS_LIBRARIES") sfml_find_package(FLAC INCLUDE "FLAC_INCLUDE_DIR" LINK "FLAC_LIBRARY") +sfml_find_package(Opus INCLUDE "OPUS_INCLUDE_DIR" LINK "OPUS_LIBRARY") # avoids warnings in vorbisfile.h target_compile_definitions(VORBIS INTERFACE "OV_EXCLUDE_STATIC_CALLBACKS") diff --git a/src/SFML/Audio/SoundFileFactory.cpp b/src/SFML/Audio/SoundFileFactory.cpp index 2f7ddfc2..ee48d5a1 100644 --- a/src/SFML/Audio/SoundFileFactory.cpp +++ b/src/SFML/Audio/SoundFileFactory.cpp @@ -30,6 +30,8 @@ #include #include #include +#include +//#include #include #include #include @@ -49,6 +51,8 @@ namespace sf::SoundFileFactory::registerWriter(); sf::SoundFileFactory::registerReader(); sf::SoundFileFactory::registerWriter(); + sf::SoundFileFactory::registerReader(); + //sf::SoundFileFactory::registerReader(); sf::SoundFileFactory::registerReader(); sf::SoundFileFactory::registerWriter(); registered = true; @@ -61,7 +65,6 @@ namespace sf SoundFileFactory::ReaderFactoryArray SoundFileFactory::s_readers; SoundFileFactory::WriterFactoryArray SoundFileFactory::s_writers; - //////////////////////////////////////////////////////////// SoundFileReader* SoundFileFactory::createReaderFromFilename(const std::string& filename) { diff --git a/src/SFML/Audio/SoundFileReaderOpus.cpp b/src/SFML/Audio/SoundFileReaderOpus.cpp new file mode 100644 index 00000000..0ba7a60a --- /dev/null +++ b/src/SFML/Audio/SoundFileReaderOpus.cpp @@ -0,0 +1,189 @@ +//////////////////////////////////////////////////////////// +// +// SFML - Simple and Fast Multimedia Library +// Copyright (C) 2007-2016 Laurent Gomila (laurent@sfml-dev.org) +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it freely, +// subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; +// you must not claim that you wrote the original software. +// If you use this software in a product, an acknowledgment +// in the product documentation would be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, +// and must not be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// +//////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include +#include +#include +#include +#include +#include +#include + +namespace +{ + int read(void* data, unsigned char* ptr, int bytes) + { + sf::InputStream* stream = static_cast(data); + return static_cast(stream->read(ptr, bytes)); + } + + int seek(void* data, opus_int64 offset, int whence) + { + sf::InputStream* stream = static_cast(data); + switch (whence) + { + case SEEK_SET: + break; + case SEEK_CUR: + offset += stream->tell(); + break; + case SEEK_END: + offset = stream->getSize() - offset; + } + // Return value expected from libopusfile: 0 - Success and -1 - Failure + return static_cast(stream->seek(offset)) >= 0 ? 0 : -1; + } + + opus_int64 tell(void* data) + { + sf::InputStream* stream = static_cast(data); + return static_cast(stream->tell()); + } + + static OpusFileCallbacks callbacks = {&read, &seek, &tell, NULL}; +} + +namespace sf +{ +namespace priv +{ +//////////////////////////////////////////////////////////// +bool SoundFileReaderOpus::check(InputStream& stream) +{ + int error = 0; + OggOpusFile* file = op_test_callbacks(&stream, &callbacks, NULL, 0, &error); + if (error == 0) + { + op_free(file); + return true; + } + else + { + return false; + } +} + + +//////////////////////////////////////////////////////////// +SoundFileReaderOpus::SoundFileReaderOpus() : +m_opus (NULL), +m_channelCount(0) +{ +} + + +//////////////////////////////////////////////////////////// +SoundFileReaderOpus::~SoundFileReaderOpus() +{ + close(); +} + + +//////////////////////////////////////////////////////////// +bool SoundFileReaderOpus::open(InputStream& stream, Info& info) +{ + // Open the Opus stream + int status = 0; + m_opus = op_open_callbacks(&stream, &callbacks, NULL, 0, &status); + if (status != 0) + { + err() << "Failed to open Opus file for reading" << std::endl; + return false; + } + + // Retrieve the music attributes + const OpusHead* opusHead = op_head(m_opus, -1); + info.channelCount = opusHead->channel_count; + info.sampleRate = opusHead->input_sample_rate; + info.sampleCount = static_cast(op_pcm_total(m_opus, -1) * opusHead->channel_count); + // We must keep the channel count for the seek function + m_channelCount = info.channelCount; + + return true; +} + + +//////////////////////////////////////////////////////////// +void SoundFileReaderOpus::seek(Uint64 sampleOffset) +{ + assert(m_opus != NULL); + + op_pcm_seek(m_opus, sampleOffset / m_channelCount); +} + + +//////////////////////////////////////////////////////////// +Uint64 SoundFileReaderOpus::read(Int16* samples, Uint64 maxCount) +{ + assert(m_opus != NULL); + + int samplesToRead; + // Try to read the requested number of samples, stop only on error or end of file + Uint64 count = 0; + while (maxCount > 0) + { + // since maxCount is uint64 we have to ensure that samplesToRead is <= INT_MAX (int overflow) + if (maxCount > INT_MAX) + { + samplesToRead = INT_MAX; + } + else + { + samplesToRead = maxCount; + } + // op_read returns number of SAMPLES read PER CHANNEL + int samplesRead = op_read(m_opus, samples, samplesToRead, NULL) * m_channelCount; + if (samplesRead > 0) + { + maxCount -= samplesRead; + count += samplesRead; + samples += samplesRead; + } + else + { + // error or end of file + break; + } + } + + return count; +} + + +//////////////////////////////////////////////////////////// +void SoundFileReaderOpus::close() +{ + if (m_opus != NULL) + { + op_free(m_opus); + m_channelCount = 0; + } +} + +} // namespace priv + +} // namespace sf diff --git a/src/SFML/Audio/SoundFileReaderOpus.hpp b/src/SFML/Audio/SoundFileReaderOpus.hpp new file mode 100644 index 00000000..213dc7a3 --- /dev/null +++ b/src/SFML/Audio/SoundFileReaderOpus.hpp @@ -0,0 +1,124 @@ +//////////////////////////////////////////////////////////// +// +// SFML - Simple and Fast Multimedia Library +// Copyright (C) 2007-2016 Laurent Gomila (laurent@sfml-dev.org) +// +// This software is provided 'as-is', without any express or implied warranty. +// In no event will the authors be held liable for any damages arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it freely, +// subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; +// you must not claim that you wrote the original software. +// If you use this software in a product, an acknowledgment +// in the product documentation would be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, +// and must not be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. +// +//////////////////////////////////////////////////////////// + +#ifndef SFML_SOUNDFILEREADEROPUS_HPP +#define SFML_SOUNDFILEREADEROPUS_HPP + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include +#include + + +namespace sf +{ +namespace priv +{ +//////////////////////////////////////////////////////////// +/// \brief Implementation of sound file reader that handles Opus files +/// +//////////////////////////////////////////////////////////// +class SoundFileReaderOpus : public SoundFileReader +{ +public: + + //////////////////////////////////////////////////////////// + /// \brief Check if this reader can handle a file given by an input stream + /// + /// \param stream Source stream to check + /// + /// \return True if the file is supported by this reader + /// + //////////////////////////////////////////////////////////// + static bool check(InputStream& stream); + +public: + + //////////////////////////////////////////////////////////// + /// \brief Default constructor + /// + //////////////////////////////////////////////////////////// + SoundFileReaderOpus(); + + //////////////////////////////////////////////////////////// + /// \brief Destructor + /// + //////////////////////////////////////////////////////////// + ~SoundFileReaderOpus(); + + //////////////////////////////////////////////////////////// + /// \brief Open a sound file for reading + /// + /// \param stream Source stream to read from + /// \param info Structure to fill with the properties of the loaded sound + /// + /// \return True if the file was successfully opened + /// + //////////////////////////////////////////////////////////// + virtual bool open(InputStream& stream, Info& info); + + //////////////////////////////////////////////////////////// + /// \brief Change the current read position to the given sample offset + /// + /// If the given offset exceeds to total number of samples, + /// this function must jump to the end of the file. + /// + /// \param sampleOffset Index of the sample to jump to, relative to the beginning + /// + //////////////////////////////////////////////////////////// + virtual void seek(Uint64 sampleOffset); + + //////////////////////////////////////////////////////////// + /// \brief Read audio samples from the open file + /// + /// \param samples Pointer to the sample array to fill + /// \param maxCount Maximum number of samples to read + /// + /// \return Number of samples actually read (may be less than \a maxCount) + /// + //////////////////////////////////////////////////////////// + virtual Uint64 read(Int16* samples, Uint64 maxCount); + +private: + + //////////////////////////////////////////////////////////// + /// \brief Close the open Opus file + /// + //////////////////////////////////////////////////////////// + void close(); + + //////////////////////////////////////////////////////////// + // Member data + //////////////////////////////////////////////////////////// + OggOpusFile* m_opus; // opus file handle + unsigned int m_channelCount; // number of channels of the open sound file +}; + +} // namespace priv + +} // namespace sf + + +#endif // SFML_SOUNDFILEREADEROPUS_HPP