From 15bb2ef28ebe6fa4f979932360bfd491c8141b2c 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 | 3 + src/SFML/Audio/SoundFileReaderOpus.cpp | 189 +++++++++++++++++++++++++ src/SFML/Audio/SoundFileReaderOpus.hpp | 124 ++++++++++++++++ 5 files changed, 362 insertions(+) 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 000000000..bdbc76ef2 --- /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 5112aa2e8..52c552d5f 100644 --- a/src/SFML/Audio/CMakeLists.txt +++ b/src/SFML/Audio/CMakeLists.txt @@ -48,6 +48,8 @@ set(CODECS_SRC ${SRCROOT}/SoundFileReaderMp3.cpp ${SRCROOT}/SoundFileReaderOgg.hpp ${SRCROOT}/SoundFileReaderOgg.cpp + ${SRCROOT}/SoundFileReaderOpus.hpp + ${SRCROOT}/SoundFileReaderOpus.cpp ${SRCROOT}/SoundFileReaderWav.hpp ${SRCROOT}/SoundFileReaderWav.cpp ${INCROOT}/SoundFileWriter.hpp @@ -77,6 +79,7 @@ endif() # find external libraries find_package(Vorbis REQUIRED) find_package(FLAC REQUIRED) +find_package(Opus REQUIRED) # define the sfml-audio target sfml_add_library(Audio diff --git a/src/SFML/Audio/SoundFileFactory.cpp b/src/SFML/Audio/SoundFileFactory.cpp index f74d95241..9af1d89f2 100644 --- a/src/SFML/Audio/SoundFileFactory.cpp +++ b/src/SFML/Audio/SoundFileFactory.cpp @@ -29,9 +29,11 @@ #include #include #include +#include #include #include #include +//#include #include #include @@ -144,6 +146,7 @@ SoundFileFactory::ReaderFactoryMap& SoundFileFactory::getReaderFactoryMap() static ReaderFactoryMap result{{&priv::createReader, &priv::SoundFileReaderFlac::check}, {&priv::createReader, &priv::SoundFileReaderMp3::check}, {&priv::createReader, &priv::SoundFileReaderOgg::check}, + {&priv::createReader, &priv::SoundFileReaderOpus::check}, {&priv::createReader, &priv::SoundFileReaderWav::check}}; return result; diff --git a/src/SFML/Audio/SoundFileReaderOpus.cpp b/src/SFML/Audio/SoundFileReaderOpus.cpp new file mode 100644 index 000000000..0ba7a60aa --- /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 000000000..213dc7a3f --- /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