mirror of
https://github.com/SFML/SFML.git
synced 2024-11-29 06:41:05 +08:00
Added opus encoder support.
This commit is contained in:
parent
15bb2ef28e
commit
7345d08d9c
@ -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
|
||||
)
|
||||
|
@ -33,7 +33,7 @@
|
||||
#include <SFML/Audio/SoundFileReaderWav.hpp>
|
||||
#include <SFML/Audio/SoundFileWriterFlac.hpp>
|
||||
#include <SFML/Audio/SoundFileWriterOgg.hpp>
|
||||
//#include <SFML/Audio/SoundFileWriterOpus.hpp>
|
||||
#include <SFML/Audio/SoundFileWriterOpus.hpp>
|
||||
#include <SFML/Audio/SoundFileWriterWav.hpp>
|
||||
|
||||
#include <SFML/System/Err.hpp>
|
||||
@ -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>, &priv::SoundFileWriterFlac::check},
|
||||
{&priv::createWriter<priv::SoundFileWriterOgg>, &priv::SoundFileWriterOgg::check},
|
||||
{&priv::createWriter<priv::SoundFileWriterOpus>, &priv::SoundFileWriterOpus::check},
|
||||
{&priv::createWriter<priv::SoundFileWriterWav>, &priv::SoundFileWriterWav::check}};
|
||||
|
||||
return result;
|
||||
|
250
src/SFML/Audio/SoundFileWriterOpus.cpp
Normal file
250
src/SFML/Audio/SoundFileWriterOpus.cpp
Normal file
@ -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 <SFML/Audio/SoundFileWriterOpus.hpp>
|
||||
#include <SFML/System/Err.hpp>
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <ctime>
|
||||
#include <cassert>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
// 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<void*>(headerData), 0, 19);
|
||||
memcpy(static_cast<void*>(headerData), "OpusHead", 8);
|
||||
headerData[8] = 1; // Version
|
||||
headerData[9] = channelCount;
|
||||
writeint(headerData, 12, static_cast<Uint32>(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<void*>(commentData), "OpusTags", 8);
|
||||
// unsigned 32bit integer: Length of vendor string (encoding library)
|
||||
writeint(commentData, 8, opusVersionLength);
|
||||
// Vendor string
|
||||
memcpy(static_cast<void*>(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<const char*>(page.header), page.header_len);
|
||||
m_file.write(reinterpret_cast<const char*>(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
|
124
src/SFML/Audio/SoundFileWriterOpus.hpp
Normal file
124
src/SFML/Audio/SoundFileWriterOpus.hpp
Normal file
@ -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 <SFML/Audio/SoundFileWriter.hpp>
|
||||
#include <opus/opus.h>
|
||||
#include <ogg/ogg.h>
|
||||
#include <fstream>
|
||||
|
||||
|
||||
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
|
Loading…
Reference in New Issue
Block a user