diff --git a/src/SFML/Audio/CMakeLists.txt b/src/SFML/Audio/CMakeLists.txt index 52c552d5f..8e28e9a38 100644 --- a/src/SFML/Audio/CMakeLists.txt +++ b/src/SFML/Audio/CMakeLists.txt @@ -57,6 +57,8 @@ set(CODECS_SRC ${SRCROOT}/SoundFileWriterFlac.cpp ${SRCROOT}/SoundFileWriterOgg.hpp ${SRCROOT}/SoundFileWriterOgg.cpp + ${SRCROOT}/SoundFileWriterOpus.hpp + ${SRCROOT}/SoundFileWriterOpus.cpp ${SRCROOT}/SoundFileWriterWav.hpp ${SRCROOT}/SoundFileWriterWav.cpp ) diff --git a/src/SFML/Audio/SoundFileFactory.cpp b/src/SFML/Audio/SoundFileFactory.cpp index 9af1d89f2..679ed84f6 100644 --- a/src/SFML/Audio/SoundFileFactory.cpp +++ b/src/SFML/Audio/SoundFileFactory.cpp @@ -33,7 +33,7 @@ #include #include #include -//#include +#include #include #include @@ -159,6 +159,7 @@ SoundFileFactory::WriterFactoryMap& SoundFileFactory::getWriterFactoryMap() // The map is pre-populated with default writers on construction static WriterFactoryMap result{{&priv::createWriter, &priv::SoundFileWriterFlac::check}, {&priv::createWriter, &priv::SoundFileWriterOgg::check}, + {&priv::createWriter, &priv::SoundFileWriterOpus::check}, {&priv::createWriter, &priv::SoundFileWriterWav::check}}; return result; diff --git a/src/SFML/Audio/SoundFileWriterOpus.cpp b/src/SFML/Audio/SoundFileWriterOpus.cpp new file mode 100644 index 000000000..ce790c9da --- /dev/null +++ b/src/SFML/Audio/SoundFileWriterOpus.cpp @@ -0,0 +1,250 @@ +//////////////////////////////////////////////////////////// +// +// 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 + +// Make sure to write int into buffer little endian +#define writeint(buf, offset, val) { buf[offset+3]=((val)>>24)&0xff; \ + buf[offset+2]=((val)>>16)&0xff; \ + buf[offset+1]=((val)>>8)&0xff; \ + buf[offset]=(val)&0xff; \ + } + +namespace sf +{ +namespace priv +{ +//////////////////////////////////////////////////////////// +bool SoundFileWriterOpus::check(const std::string& filename) +{ + std::string extension = filename.substr(filename.find_last_of(".") + 1); + std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower); + + return extension == "opus"; +} + + +//////////////////////////////////////////////////////////// +SoundFileWriterOpus::SoundFileWriterOpus() : +m_channelCount (0), +m_file (), +m_ogg (), +m_opus (NULL), +m_packageNumber(0) +{ +} + + +//////////////////////////////////////////////////////////// +SoundFileWriterOpus::~SoundFileWriterOpus() +{ + close(); +} + + +//////////////////////////////////////////////////////////// +bool SoundFileWriterOpus::open(const std::string& filename, unsigned int sampleRate, unsigned int channelCount) +{ + // Save the channel count + m_channelCount = channelCount; + m_sampleRate = sampleRate; + std::srand(std::time(0)); + + // Initialize the ogg/opus stream + if (ogg_stream_init(&m_ogg, std::rand()) == -1) + { + err() << "Stream init of ogg/opus file \"" << filename << "\" failed" << std::endl; + close(); + return false; + } + int status = 0; + m_opus = opus_encoder_create(sampleRate, channelCount, OPUS_APPLICATION_AUDIO, &status); + if (status != OPUS_OK) + { + err() << "Failed to write ogg/opus file \"" << filename << "\"" << std::endl; + if (status == OPUS_BAD_ARG) + err() << "Possible wrong sample rate, allowed are 8000, 12000, 16000, 24000, or 48000 Hz." << std::endl; + close(); + return false; + } + + m_file.open(filename.c_str(), std::ios::binary); + if (!m_file) + { + err() << "Failed to write ogg/opus file \"" << filename << "\" (cannot open file)" << std::endl; + close(); + return false; + } + + // Set bitrate (VBR is default) + opus_encoder_ctl(m_opus, OPUS_SET_BITRATE(128000)); + + // Create opus header + unsigned char headerData[19]; + memset(static_cast(headerData), 0, 19); + memcpy(static_cast(headerData), "OpusHead", 8); + headerData[8] = 1; // Version + headerData[9] = channelCount; + writeint(headerData, 12, static_cast(sampleRate)); + headerData[18] = channelCount > 8 ? 255 : (channelCount > 2); // Mapping family + + // Map opus header to ogg packet + ogg_packet op; + op.packet = headerData; + op.bytes = 19; + op.b_o_s = 1; + op.e_o_s = 0; + op.granulepos = 0; + op.packetno = m_packageNumber++; + + // Write the header packet to the ogg stream + ogg_stream_packetin(&m_ogg, &op); + flushBlocks(); + + // Create comment header, needs to be in a new page + const char* opusVersion = opus_get_version_string(); + Uint32 opusVersionLength = strlen(opusVersion); + const int commentLength = 8 + 4 + opusVersionLength + 4; + + unsigned char commentData[commentLength]; + + // Magic bytes + memcpy(static_cast(commentData), "OpusTags", 8); + // unsigned 32bit integer: Length of vendor string (encoding library) + writeint(commentData, 8, opusVersionLength); + // Vendor string + memcpy(static_cast(commentData+12), opusVersion, opusVersionLength); + // Length of user comments (E.g. you can add a ENCODER tag for SFML) + writeint(commentData, 12+opusVersionLength, 0); + + op.packet = commentData; + op.bytes = commentLength; + op.b_o_s = 0; + op.e_o_s = 0; + op.granulepos = 0; + op.packetno = m_packageNumber++; + ogg_stream_packetin(&m_ogg, &op); + + // This ensures the actual audio data will start on a new page, as per spec + flushBlocks(); + + return true; +} + + +//////////////////////////////////////////////////////////// +void SoundFileWriterOpus::write(const Int16* samples, Uint64 count) +{ + assert(m_opus); + + const opus_uint32 frame_size = 960; + const opus_int32 buffer_size = frame_size * m_channelCount * sizeof(Int16); + unsigned char buffer[buffer_size]; + Uint32 frame_number = 0; + Uint8 endOfStream = 0; + ogg_packet op; + + while (count > 0) + { + opus_int32 packet_size; + // Check if wee need to pad the input + if (count < (frame_size * m_channelCount)) + { + Int16 pad[frame_size*m_channelCount]; + memset(pad, 0, frame_size * m_channelCount); + memcpy(pad, samples + (frame_number * frame_size * m_channelCount), count); + packet_size = opus_encode(m_opus, pad, frame_size, buffer, buffer_size); + endOfStream = 1; + count = 0; + } + else + { + packet_size = opus_encode(m_opus, samples + (frame_number * frame_size * m_channelCount), frame_size, buffer, buffer_size); + count -= frame_size * m_channelCount; + } + + if (packet_size < 0) + { + err() << "An error occurred when encoding sound to opus." << std::endl; + break; + } + op.packet = buffer; + op.bytes = packet_size; + op.granulepos = frame_number * frame_size * 48000ul / m_sampleRate; + op.packetno = m_packageNumber++; + op.b_o_s = 0; + op.e_o_s = endOfStream; + ogg_stream_packetin(&m_ogg, &op); + + frame_number++; + } + // Flush any produced block + flushBlocks(); +} + + +//////////////////////////////////////////////////////////// +void SoundFileWriterOpus::flushBlocks() +{ + ogg_page page; + while (ogg_stream_pageout(&m_ogg, &page) > 0 || ogg_stream_flush(&m_ogg, &page) > 0) + { + m_file.write(reinterpret_cast(page.header), page.header_len); + m_file.write(reinterpret_cast(page.body), page.body_len); + } +} + + +//////////////////////////////////////////////////////////// +void SoundFileWriterOpus::close() +{ + if (m_file.is_open()) + { + flushBlocks(); + // Close the file + m_file.close(); + } + + // Clear all the ogg/opus structures + ogg_stream_clear(&m_ogg); + if (m_opus != NULL) + { + opus_encoder_destroy(m_opus); + m_opus = NULL; + } +} + +} // namespace priv + +} // namespace sf diff --git a/src/SFML/Audio/SoundFileWriterOpus.hpp b/src/SFML/Audio/SoundFileWriterOpus.hpp new file mode 100644 index 000000000..3e9496caf --- /dev/null +++ b/src/SFML/Audio/SoundFileWriterOpus.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_SOUNDFILEWRITEROPUS_HPP +#define SFML_SOUNDFILEWRITEROPUS_HPP + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include +#include +#include +#include + + +namespace sf +{ +namespace priv +{ +//////////////////////////////////////////////////////////// +/// \brief Implementation of sound file writer that handles OGG/Opus files +/// +//////////////////////////////////////////////////////////// +class SoundFileWriterOpus : public SoundFileWriter +{ +public: + + //////////////////////////////////////////////////////////// + /// \brief Check if this writer can handle a file on disk + /// + /// \param filename Path of the sound file to check + /// + /// \return True if the file can be written by this writer + /// + //////////////////////////////////////////////////////////// + static bool check(const std::string& filename); + +public: + + //////////////////////////////////////////////////////////// + /// \brief Default constructor + /// + //////////////////////////////////////////////////////////// + SoundFileWriterOpus(); + + //////////////////////////////////////////////////////////// + /// \brief Destructor + /// + //////////////////////////////////////////////////////////// + ~SoundFileWriterOpus(); + + //////////////////////////////////////////////////////////// + /// \brief Open a sound file for writing + /// + /// \param filename Path of the file to open + /// \param sampleRate Sample rate of the sound + /// \param channelCount Number of channels of the sound + /// + /// \return True if the file was successfully opened + /// + //////////////////////////////////////////////////////////// + virtual bool open(const std::string& filename, unsigned int sampleRate, unsigned int channelCount); + + //////////////////////////////////////////////////////////// + /// \brief Write audio samples to the open file + /// + /// \param samples Pointer to the sample array to write + /// \param count Number of samples to write + /// + //////////////////////////////////////////////////////////// + virtual void write(const Int16* samples, Uint64 count); + +private: + + //////////////////////////////////////////////////////////// + /// \brief Flush blocks produced by the ogg stream, if any + /// + //////////////////////////////////////////////////////////// + void flushBlocks(); + + //////////////////////////////////////////////////////////// + /// \brief Close the file + /// + //////////////////////////////////////////////////////////// + void close(); + + //////////////////////////////////////////////////////////// + // Member data + //////////////////////////////////////////////////////////// + unsigned int m_channelCount; // channel count of the sound being written + std::ofstream m_file; // output file + ogg_stream_state m_ogg; // ogg stream + OpusEncoder* m_opus; // opus encoder handle + Uint64 m_packageNumber; + Uint32 m_sampleRate; +}; + +} // namespace priv + +} // namespace sf + + +#endif // SFML_SOUNDFILEWRITEROPUS_HPP