mirror of
https://github.com/SFML/SFML.git
synced 2025-01-18 23:35:11 +08:00
Add support for opus and opusfile
This commit is contained in:
parent
bc268fbaea
commit
3d3d60a4a2
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@ -128,7 +128,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
CLANG_VERSION=$(clang++ --version | sed -n 's/.*version \([0-9]\+\)\..*/\1/p')
|
CLANG_VERSION=$(clang++ --version | sed -n 's/.*version \([0-9]\+\)\..*/\1/p')
|
||||||
echo "CLANG_VERSION=$CLANG_VERSION" >> $GITHUB_ENV
|
echo "CLANG_VERSION=$CLANG_VERSION" >> $GITHUB_ENV
|
||||||
sudo apt-get update && sudo apt-get install xorg-dev libxrandr-dev libxcursor-dev libxi-dev libudev-dev libflac-dev libvorbis-dev libgl1-mesa-dev libegl1-mesa-dev libdrm-dev libgbm-dev xvfb fluxbox ccache gcovr ${{ matrix.platform.name == 'Linux Clang' && 'llvm-$CLANG_VERSION' || '' }}
|
sudo apt-get update && sudo apt-get install xorg-dev libxrandr-dev libxcursor-dev libxi-dev libudev-dev libflac-dev libvorbis-dev libopus-dev libopusfile-dev libgl1-mesa-dev libegl1-mesa-dev libdrm-dev libgbm-dev xvfb fluxbox ccache gcovr ${{ matrix.platform.name == 'Linux Clang' && 'llvm-$CLANG_VERSION' || '' }}
|
||||||
|
|
||||||
- name: Remove ALSA Library
|
- name: Remove ALSA Library
|
||||||
if: runner.os == 'Linux' && matrix.platform.name != 'Android'
|
if: runner.os == 'Linux' && matrix.platform.name != 'Android'
|
||||||
|
@ -85,6 +85,9 @@ int main()
|
|||||||
// Play a sound
|
// Play a sound
|
||||||
playSound();
|
playSound();
|
||||||
|
|
||||||
|
// Play music from an opus file
|
||||||
|
playMusic("error.opus");
|
||||||
|
|
||||||
// Play music from an ogg file
|
// Play music from an ogg file
|
||||||
playMusic("doodle_pop.ogg");
|
playMusic("doodle_pop.ogg");
|
||||||
|
|
||||||
|
BIN
examples/sound/resources/error.opus
Normal file
BIN
examples/sound/resources/error.opus
Normal file
Binary file not shown.
@ -108,7 +108,8 @@ public:
|
|||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
/// \brief Open a sound file from the disk for reading
|
/// \brief Open a sound file from the disk for reading
|
||||||
///
|
///
|
||||||
/// The supported audio formats are: WAV (PCM only), OGG/Vorbis, FLAC, MP3.
|
/// The supported audio formats are: WAV (PCM only), OGG/Vorbis, Opus,
|
||||||
|
/// FLAC, and MP3.
|
||||||
/// The supported sample sizes for FLAC and WAV are 8, 16, 24 and 32 bit.
|
/// The supported sample sizes for FLAC and WAV are 8, 16, 24 and 32 bit.
|
||||||
///
|
///
|
||||||
/// Because of minimp3_ex limitation, for MP3 files with big (>16kb) APEv2 tag,
|
/// Because of minimp3_ex limitation, for MP3 files with big (>16kb) APEv2 tag,
|
||||||
@ -126,7 +127,8 @@ public:
|
|||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
/// \brief Open a sound file in memory for reading
|
/// \brief Open a sound file in memory for reading
|
||||||
///
|
///
|
||||||
/// The supported audio formats are: WAV (PCM only), OGG/Vorbis, FLAC.
|
/// The supported audio formats are: WAV (PCM only), OGG/Vorbis, Opus,
|
||||||
|
/// FLAC, and MP3.
|
||||||
/// The supported sample sizes for FLAC and WAV are 8, 16, 24 and 32 bit.
|
/// The supported sample sizes for FLAC and WAV are 8, 16, 24 and 32 bit.
|
||||||
///
|
///
|
||||||
/// \param data Pointer to the file data in memory
|
/// \param data Pointer to the file data in memory
|
||||||
@ -140,7 +142,8 @@ public:
|
|||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
/// \brief Open a sound file from a custom stream for reading
|
/// \brief Open a sound file from a custom stream for reading
|
||||||
///
|
///
|
||||||
/// The supported audio formats are: WAV (PCM only), OGG/Vorbis, FLAC.
|
/// The supported audio formats are: WAV (PCM only), OGG/Vorbis, Opus,
|
||||||
|
/// FLAC, and MP3.
|
||||||
/// The supported sample sizes for FLAC and WAV are 8, 16, 24 and 32 bit.
|
/// The supported sample sizes for FLAC and WAV are 8, 16, 24 and 32 bit.
|
||||||
///
|
///
|
||||||
/// \param stream Source stream to read from
|
/// \param stream Source stream to read from
|
||||||
|
@ -48,6 +48,8 @@ set(CODECS_SRC
|
|||||||
${SRCROOT}/SoundFileReaderMp3.cpp
|
${SRCROOT}/SoundFileReaderMp3.cpp
|
||||||
${SRCROOT}/SoundFileReaderOgg.hpp
|
${SRCROOT}/SoundFileReaderOgg.hpp
|
||||||
${SRCROOT}/SoundFileReaderOgg.cpp
|
${SRCROOT}/SoundFileReaderOgg.cpp
|
||||||
|
${SRCROOT}/SoundFileReaderOpus.hpp
|
||||||
|
${SRCROOT}/SoundFileReaderOpus.cpp
|
||||||
${SRCROOT}/SoundFileReaderWav.hpp
|
${SRCROOT}/SoundFileReaderWav.hpp
|
||||||
${SRCROOT}/SoundFileReaderWav.cpp
|
${SRCROOT}/SoundFileReaderWav.cpp
|
||||||
${INCROOT}/SoundFileWriter.hpp
|
${INCROOT}/SoundFileWriter.hpp
|
||||||
@ -55,6 +57,8 @@ set(CODECS_SRC
|
|||||||
${SRCROOT}/SoundFileWriterFlac.cpp
|
${SRCROOT}/SoundFileWriterFlac.cpp
|
||||||
${SRCROOT}/SoundFileWriterOgg.hpp
|
${SRCROOT}/SoundFileWriterOgg.hpp
|
||||||
${SRCROOT}/SoundFileWriterOgg.cpp
|
${SRCROOT}/SoundFileWriterOgg.cpp
|
||||||
|
${SRCROOT}/SoundFileWriterOpus.hpp
|
||||||
|
${SRCROOT}/SoundFileWriterOpus.cpp
|
||||||
${SRCROOT}/SoundFileWriterWav.hpp
|
${SRCROOT}/SoundFileWriterWav.hpp
|
||||||
${SRCROOT}/SoundFileWriterWav.cpp
|
${SRCROOT}/SoundFileWriterWav.cpp
|
||||||
)
|
)
|
||||||
@ -94,10 +98,23 @@ else()
|
|||||||
set(INSTALL_DOCS OFF)
|
set(INSTALL_DOCS OFF)
|
||||||
set(INSTALL_PKG_CONFIG_MODULE OFF)
|
set(INSTALL_PKG_CONFIG_MODULE OFF)
|
||||||
set(INSTALL_PKGCONFIG_MODULES OFF)
|
set(INSTALL_PKGCONFIG_MODULES OFF)
|
||||||
|
set(OPUS_INSTALL_PKG_CONFIG_MODULE OFF)
|
||||||
set(WITH_FORTIFY_SOURCE OFF)
|
set(WITH_FORTIFY_SOURCE OFF)
|
||||||
set(WITH_STACK_PROTECTOR OFF)
|
set(WITH_STACK_PROTECTOR OFF)
|
||||||
set(WITH_AVX OFF) # LLVM/Clang on Windows has issues with AVX2
|
set(WITH_AVX OFF) # LLVM/Clang on Windows has issues with AVX2
|
||||||
|
SET(OP_DISABLE_HTTP ON)
|
||||||
|
SET(OP_DISABLE_EXAMPLES ON)
|
||||||
|
SET(OP_DISABLE_DOCS ON)
|
||||||
|
|
||||||
|
FetchContent_Declare(opus
|
||||||
|
GIT_REPOSITORY https://github.com/xiph/opus.git
|
||||||
|
GIT_TAG v1.5.2
|
||||||
|
GIT_SHALLOW ON
|
||||||
|
# patch out parts we don't want of the Opus CMake configuration
|
||||||
|
# - feature summary
|
||||||
|
# - installing headers
|
||||||
|
# - add CMAKE_DEBUG_POSTFIX
|
||||||
|
PATCH_COMMAND ${CMAKE_COMMAND} -DOPUS_DIR=${FETCHCONTENT_BASE_DIR}/opus-src -P ${PROJECT_SOURCE_DIR}/tools/opus/PatchOpus.cmake)
|
||||||
FetchContent_Declare(ogg
|
FetchContent_Declare(ogg
|
||||||
GIT_REPOSITORY https://github.com/xiph/ogg.git
|
GIT_REPOSITORY https://github.com/xiph/ogg.git
|
||||||
GIT_TAG v1.3.5
|
GIT_TAG v1.3.5
|
||||||
@ -106,6 +123,13 @@ else()
|
|||||||
# - installing headers & pkgconfig files
|
# - installing headers & pkgconfig files
|
||||||
# - add CMAKE_DEBUG_POSTFIX
|
# - add CMAKE_DEBUG_POSTFIX
|
||||||
PATCH_COMMAND ${CMAKE_COMMAND} -DOGG_DIR=${FETCHCONTENT_BASE_DIR}/ogg-src -P ${PROJECT_SOURCE_DIR}/tools/ogg/PatchOgg.cmake)
|
PATCH_COMMAND ${CMAKE_COMMAND} -DOGG_DIR=${FETCHCONTENT_BASE_DIR}/ogg-src -P ${PROJECT_SOURCE_DIR}/tools/ogg/PatchOgg.cmake)
|
||||||
|
FetchContent_Declare(opusfile
|
||||||
|
GIT_REPOSITORY https://github.com/xiph/opusfile.git
|
||||||
|
GIT_TAG 9d718345ce03b2fad5d7d28e0bcd1cc69ab2b166
|
||||||
|
# patch out parts we don't want of the Opusfile CMake configuration
|
||||||
|
# - installing headers
|
||||||
|
# - add CMAKE_DEBUG_POSTFIX
|
||||||
|
PATCH_COMMAND ${CMAKE_COMMAND} -DOPUSFILE_DIR=${FETCHCONTENT_BASE_DIR}/opusfile-src -P ${PROJECT_SOURCE_DIR}/tools/opus/PatchOpusfile.cmake)
|
||||||
FetchContent_Declare(flac
|
FetchContent_Declare(flac
|
||||||
GIT_REPOSITORY https://github.com/xiph/flac.git
|
GIT_REPOSITORY https://github.com/xiph/flac.git
|
||||||
GIT_TAG 1.4.3
|
GIT_TAG 1.4.3
|
||||||
@ -124,20 +148,22 @@ else()
|
|||||||
# - installing headers & pkgconfig files
|
# - installing headers & pkgconfig files
|
||||||
# - add CMAKE_DEBUG_POSTFIX
|
# - add CMAKE_DEBUG_POSTFIX
|
||||||
PATCH_COMMAND ${CMAKE_COMMAND} -DVORBIS_DIR=${FETCHCONTENT_BASE_DIR}/vorbis-src -P ${PROJECT_SOURCE_DIR}/tools/vorbis/PatchVorbis.cmake)
|
PATCH_COMMAND ${CMAKE_COMMAND} -DVORBIS_DIR=${FETCHCONTENT_BASE_DIR}/vorbis-src -P ${PROJECT_SOURCE_DIR}/tools/vorbis/PatchVorbis.cmake)
|
||||||
FetchContent_MakeAvailable(ogg flac vorbis)
|
FetchContent_MakeAvailable(opus ogg opusfile flac vorbis)
|
||||||
|
|
||||||
set_target_properties(ogg FLAC vorbis vorbisenc vorbisfile PROPERTIES FOLDER "Dependencies")
|
set_target_properties(opus ogg opusfile FLAC vorbis vorbisenc vorbisfile PROPERTIES FOLDER "Dependencies")
|
||||||
|
|
||||||
# if building SFML as a shared library and linking our dependencies in
|
# if building SFML as a shared library and linking our dependencies in
|
||||||
# as static libraries we need to build them with -fPIC
|
# as static libraries we need to build them with -fPIC
|
||||||
if(SFML_BUILD_SHARED_LIBS)
|
if(SFML_BUILD_SHARED_LIBS)
|
||||||
set_target_properties(ogg FLAC vorbis vorbisenc vorbisfile PROPERTIES POSITION_INDEPENDENT_CODE ON)
|
set_target_properties(opus ogg opusfile FLAC vorbis vorbisenc vorbisfile PROPERTIES POSITION_INDEPENDENT_CODE ON)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# disable building dependencies as part of a unity build, they don't support it
|
# disable building dependencies as part of a unity build, they don't support it
|
||||||
set_target_properties(ogg FLAC vorbis vorbisenc vorbisfile PROPERTIES UNITY_BUILD OFF)
|
set_target_properties(opus ogg opusfile FLAC vorbis vorbisenc vorbisfile PROPERTIES UNITY_BUILD OFF)
|
||||||
|
|
||||||
|
sfml_set_stdlib(opus)
|
||||||
sfml_set_stdlib(ogg)
|
sfml_set_stdlib(ogg)
|
||||||
|
sfml_set_stdlib(opusfile)
|
||||||
sfml_set_stdlib(FLAC)
|
sfml_set_stdlib(FLAC)
|
||||||
sfml_set_stdlib(vorbis)
|
sfml_set_stdlib(vorbis)
|
||||||
sfml_set_stdlib(vorbisenc)
|
sfml_set_stdlib(vorbisenc)
|
||||||
@ -181,7 +207,7 @@ target_compile_definitions(sfml-audio PRIVATE SFML_IS_BIG_ENDIAN=$<STREQUAL:${CM
|
|||||||
# setup dependencies
|
# setup dependencies
|
||||||
target_link_libraries(sfml-audio
|
target_link_libraries(sfml-audio
|
||||||
PUBLIC SFML::System
|
PUBLIC SFML::System
|
||||||
PRIVATE Vorbis::vorbis Vorbis::vorbisfile Vorbis::vorbisenc FLAC::FLAC Threads::Threads)
|
PRIVATE Vorbis::vorbis Vorbis::vorbisfile Vorbis::vorbisenc FLAC::FLAC OpusFile::opusfile Threads::Threads)
|
||||||
if(SFML_OS_IOS)
|
if(SFML_OS_IOS)
|
||||||
target_link_libraries(sfml-audio PRIVATE "-framework Foundation" "-framework CoreFoundation" "-framework CoreAudio" "-framework AudioToolbox" "-framework AVFoundation")
|
target_link_libraries(sfml-audio PRIVATE "-framework Foundation" "-framework CoreFoundation" "-framework CoreAudio" "-framework AudioToolbox" "-framework AVFoundation")
|
||||||
endif()
|
endif()
|
||||||
|
@ -30,9 +30,11 @@ set(FIND_SFML_DEPENDENCIES_NOTFOUND)
|
|||||||
if(SFML_BUILT_USING_SYSTEM_DEPS)
|
if(SFML_BUILT_USING_SYSTEM_DEPS)
|
||||||
find_dependency(Vorbis)
|
find_dependency(Vorbis)
|
||||||
find_dependency(FLAC)
|
find_dependency(FLAC)
|
||||||
|
find_dependency(opus)
|
||||||
else()
|
else()
|
||||||
find_dependency(Vorbis CONFIG PATHS "${CMAKE_CURRENT_LIST_DIR}/../../../")
|
find_dependency(Vorbis CONFIG PATHS "${CMAKE_CURRENT_LIST_DIR}/../../../")
|
||||||
find_dependency(FLAC CONFIG PATHS "${CMAKE_CURRENT_LIST_DIR}/../../../")
|
find_dependency(FLAC CONFIG PATHS "${CMAKE_CURRENT_LIST_DIR}/../../../")
|
||||||
|
find_dependency(opus CONFIG PATHS "${CMAKE_CURRENT_LIST_DIR}/../../../")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(FIND_SFML_DEPENDENCIES_NOTFOUND)
|
if(FIND_SFML_DEPENDENCIES_NOTFOUND)
|
||||||
|
@ -29,6 +29,7 @@
|
|||||||
#include <SFML/Audio/SoundFileReaderFlac.hpp>
|
#include <SFML/Audio/SoundFileReaderFlac.hpp>
|
||||||
#include <SFML/Audio/SoundFileReaderMp3.hpp>
|
#include <SFML/Audio/SoundFileReaderMp3.hpp>
|
||||||
#include <SFML/Audio/SoundFileReaderOgg.hpp>
|
#include <SFML/Audio/SoundFileReaderOgg.hpp>
|
||||||
|
#include <SFML/Audio/SoundFileReaderOpus.hpp>
|
||||||
#include <SFML/Audio/SoundFileReaderWav.hpp>
|
#include <SFML/Audio/SoundFileReaderWav.hpp>
|
||||||
#include <SFML/Audio/SoundFileWriterFlac.hpp>
|
#include <SFML/Audio/SoundFileWriterFlac.hpp>
|
||||||
#include <SFML/Audio/SoundFileWriterOgg.hpp>
|
#include <SFML/Audio/SoundFileWriterOgg.hpp>
|
||||||
@ -144,6 +145,7 @@ SoundFileFactory::ReaderFactoryMap& SoundFileFactory::getReaderFactoryMap()
|
|||||||
static ReaderFactoryMap result{{&priv::createReader<priv::SoundFileReaderFlac>, &priv::SoundFileReaderFlac::check},
|
static ReaderFactoryMap result{{&priv::createReader<priv::SoundFileReaderFlac>, &priv::SoundFileReaderFlac::check},
|
||||||
{&priv::createReader<priv::SoundFileReaderMp3>, &priv::SoundFileReaderMp3::check},
|
{&priv::createReader<priv::SoundFileReaderMp3>, &priv::SoundFileReaderMp3::check},
|
||||||
{&priv::createReader<priv::SoundFileReaderOgg>, &priv::SoundFileReaderOgg::check},
|
{&priv::createReader<priv::SoundFileReaderOgg>, &priv::SoundFileReaderOgg::check},
|
||||||
|
{&priv::createReader<priv::SoundFileReaderOpus>, &priv::SoundFileReaderOpus::check},
|
||||||
{&priv::createReader<priv::SoundFileReaderWav>, &priv::SoundFileReaderWav::check}};
|
{&priv::createReader<priv::SoundFileReaderWav>, &priv::SoundFileReaderWav::check}};
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
234
src/SFML/Audio/SoundFileReaderOpus.cpp
Normal file
234
src/SFML/Audio/SoundFileReaderOpus.cpp
Normal file
@ -0,0 +1,234 @@
|
|||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// SFML - Simple and Fast Multimedia Library
|
||||||
|
// Copyright (C) 2007-2024 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/SoundFileReaderOpus.hpp>
|
||||||
|
|
||||||
|
#include <SFML/System/Err.hpp>
|
||||||
|
#include <SFML/System/InputStream.hpp>
|
||||||
|
|
||||||
|
#include <ostream>
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
int read(void* data, unsigned char* ptr, int bytes)
|
||||||
|
{
|
||||||
|
auto* stream = static_cast<sf::InputStream*>(data);
|
||||||
|
return static_cast<int>(stream->read(ptr, static_cast<std::size_t>(bytes)).value_or(-1));
|
||||||
|
}
|
||||||
|
|
||||||
|
int seek(void* data, opus_int64 signedOffset, int whence)
|
||||||
|
{
|
||||||
|
auto* stream = static_cast<sf::InputStream*>(data);
|
||||||
|
auto offset = static_cast<std::size_t>(signedOffset);
|
||||||
|
switch (whence)
|
||||||
|
{
|
||||||
|
case SEEK_SET:
|
||||||
|
break;
|
||||||
|
case SEEK_CUR:
|
||||||
|
offset += stream->tell().value();
|
||||||
|
break;
|
||||||
|
case SEEK_END:
|
||||||
|
offset = stream->getSize().value() - offset;
|
||||||
|
}
|
||||||
|
const std::optional position = stream->seek(offset);
|
||||||
|
return position ? 0 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
opus_int64 tell(void* data)
|
||||||
|
{
|
||||||
|
auto* stream = static_cast<sf::InputStream*>(data);
|
||||||
|
const std::optional position = stream->tell();
|
||||||
|
return position ? static_cast<long>(*position) : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const OpusFileCallbacks callbacks = {&read, &seek, &tell, nullptr};
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace sf::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;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
SoundFileReaderOpus::~SoundFileReaderOpus()
|
||||||
|
{
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
std::optional<SoundFileReader::Info> SoundFileReaderOpus::open(InputStream& stream)
|
||||||
|
{
|
||||||
|
// Open the Opus stream
|
||||||
|
int error = 0;
|
||||||
|
m_opus = op_open_callbacks(&stream, &callbacks, nullptr, 0, &error);
|
||||||
|
if (error != 0)
|
||||||
|
{
|
||||||
|
err() << "Failed to open Opus file for reading" << std::endl;
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve the music attributes
|
||||||
|
const OpusHead* opusHead = op_head(m_opus, -1);
|
||||||
|
Info info;
|
||||||
|
info.channelCount = static_cast<unsigned int>(opusHead->channel_count);
|
||||||
|
info.sampleCount = static_cast<std::size_t>(op_pcm_total(m_opus, -1) * opusHead->channel_count);
|
||||||
|
|
||||||
|
// All Opus audio is encoded at 48kHz
|
||||||
|
// https://www.opus-codec.org/docs/opusfile_api-0.12/structOpusHead.html#a73b80a913eca33d829f1667caee80d9e
|
||||||
|
info.sampleRate = 48000;
|
||||||
|
|
||||||
|
// For Vorbis channel mapping refer to: https://xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-810004.3.9
|
||||||
|
switch (info.channelCount)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
err() << "No channels in Opus file" << std::endl;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
info.channelMap = {SoundChannel::Mono};
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
info.channelMap = {SoundChannel::FrontLeft, SoundChannel::FrontRight};
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
info.channelMap = {SoundChannel::FrontLeft, SoundChannel::FrontCenter, SoundChannel::FrontRight};
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
info.channelMap = {SoundChannel::FrontLeft, SoundChannel::FrontRight, SoundChannel::BackLeft, SoundChannel::BackRight};
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
info.channelMap = {SoundChannel::FrontLeft,
|
||||||
|
SoundChannel::FrontCenter,
|
||||||
|
SoundChannel::FrontRight,
|
||||||
|
SoundChannel::BackLeft,
|
||||||
|
SoundChannel::BackRight};
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
info.channelMap = {SoundChannel::FrontLeft,
|
||||||
|
SoundChannel::FrontCenter,
|
||||||
|
SoundChannel::FrontRight,
|
||||||
|
SoundChannel::BackLeft,
|
||||||
|
SoundChannel::BackRight,
|
||||||
|
SoundChannel::LowFrequencyEffects};
|
||||||
|
break;
|
||||||
|
case 7:
|
||||||
|
info.channelMap = {SoundChannel::FrontLeft,
|
||||||
|
SoundChannel::FrontCenter,
|
||||||
|
SoundChannel::FrontRight,
|
||||||
|
SoundChannel::SideLeft,
|
||||||
|
SoundChannel::SideRight,
|
||||||
|
SoundChannel::BackCenter,
|
||||||
|
SoundChannel::LowFrequencyEffects};
|
||||||
|
break;
|
||||||
|
case 8:
|
||||||
|
info.channelMap = {SoundChannel::FrontLeft,
|
||||||
|
SoundChannel::FrontCenter,
|
||||||
|
SoundChannel::FrontRight,
|
||||||
|
SoundChannel::SideLeft,
|
||||||
|
SoundChannel::SideRight,
|
||||||
|
SoundChannel::BackLeft,
|
||||||
|
SoundChannel::BackRight,
|
||||||
|
SoundChannel::LowFrequencyEffects};
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
err() << "Opus files with more than 8 channels not supported" << std::endl;
|
||||||
|
assert(false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We must keep the channel count for the seek function
|
||||||
|
m_channelCount = info.channelCount;
|
||||||
|
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
void SoundFileReaderOpus::seek(std::uint64_t sampleOffset)
|
||||||
|
{
|
||||||
|
assert(m_opus && "Opus stream is missing. Call SoundFileReaderOpus::open() to initialize it.");
|
||||||
|
|
||||||
|
op_pcm_seek(m_opus, static_cast<ogg_int64_t>(sampleOffset / m_channelCount));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
std::uint64_t SoundFileReaderOpus::read(std::int16_t* samples, std::uint64_t maxCount)
|
||||||
|
{
|
||||||
|
assert(m_opus && "Opus stream is missing. Call SoundFileReaderOpus::open() to initialize it.");
|
||||||
|
|
||||||
|
// Try to read the requested number of samples, stop only on error or end of file
|
||||||
|
std::uint64_t count = 0;
|
||||||
|
while (count < maxCount)
|
||||||
|
{
|
||||||
|
const int bytesToRead = static_cast<int>(maxCount - count) * static_cast<int>(sizeof(std::int16_t));
|
||||||
|
const long bytesRead = op_read(m_opus, static_cast<opus_int16*>(samples), bytesToRead, nullptr);
|
||||||
|
if (bytesRead > 0)
|
||||||
|
{
|
||||||
|
const long samplesRead = bytesRead / static_cast<long>(sizeof(std::int16_t));
|
||||||
|
count += static_cast<std::uint64_t>(samplesRead);
|
||||||
|
samples += samplesRead;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// error or end of file
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
void SoundFileReaderOpus::close()
|
||||||
|
{
|
||||||
|
if (m_opus)
|
||||||
|
{
|
||||||
|
op_free(m_opus);
|
||||||
|
m_channelCount = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace sf::priv
|
119
src/SFML/Audio/SoundFileReaderOpus.hpp
Normal file
119
src/SFML/Audio/SoundFileReaderOpus.hpp
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// SFML - Simple and Fast Multimedia Library
|
||||||
|
// Copyright (C) 2007-2024 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.
|
||||||
|
//
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
// Headers
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
#include <SFML/Audio/SoundFileReader.hpp>
|
||||||
|
|
||||||
|
#include <opusfile.h>
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
|
||||||
|
namespace sf
|
||||||
|
{
|
||||||
|
class InputStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace sf::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
|
||||||
|
///
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
[[nodiscard]] static bool check(InputStream& stream);
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
/// \brief Destructor
|
||||||
|
///
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
~SoundFileReaderOpus() override;
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
/// \brief Open a sound file for reading
|
||||||
|
///
|
||||||
|
/// \param stream Source stream to read from
|
||||||
|
///
|
||||||
|
/// \return Properties of the loaded sound if the file was successfully opened
|
||||||
|
///
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
[[nodiscard]] std::optional<Info> open(InputStream& stream) override;
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
/// \brief Change the current read position to the given sample offset
|
||||||
|
///
|
||||||
|
/// The sample offset takes the channels into account.
|
||||||
|
/// If you have a time offset instead, you can easily find
|
||||||
|
/// the corresponding sample offset with the following formula:
|
||||||
|
/// `timeInSeconds * sampleRate * channelCount`
|
||||||
|
/// 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
|
||||||
|
///
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
void seek(std::uint64_t sampleOffset) override;
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
/// \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)
|
||||||
|
///
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
[[nodiscard]] std::uint64_t read(std::int16_t* samples, std::uint64_t maxCount) override;
|
||||||
|
|
||||||
|
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 sf::priv
|
@ -118,7 +118,7 @@ bool SoundFileWriterOgg::open(const std::filesystem::path& filename,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the channel map contains channels that we cannot remap to a mapping supported by FLAC
|
// Check if the channel map contains channels that we cannot remap to a mapping supported by Vorbis
|
||||||
if (!std::is_permutation(channelMap.begin(), channelMap.end(), targetChannelMap.begin()))
|
if (!std::is_permutation(channelMap.begin(), channelMap.end(), targetChannelMap.begin()))
|
||||||
{
|
{
|
||||||
err() << "Provided channel map cannot be reordered to a channel map supported by Vorbis" << std::endl;
|
err() << "Provided channel map cannot be reordered to a channel map supported by Vorbis" << std::endl;
|
||||||
|
328
src/SFML/Audio/SoundFileWriterOpus.cpp
Normal file
328
src/SFML/Audio/SoundFileWriterOpus.cpp
Normal file
@ -0,0 +1,328 @@
|
|||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// SFML - Simple and Fast Multimedia Library
|
||||||
|
// Copyright (C) 2007-2024 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 <SFML/System/Utils.hpp>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <ostream>
|
||||||
|
#include <random>
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
// Make sure to write int into buffer little endian
|
||||||
|
void writeUint32(std::vector<unsigned char>& buffer, const std::uint32_t value)
|
||||||
|
{
|
||||||
|
buffer.push_back(static_cast<unsigned char>(value & 0x000000FF));
|
||||||
|
buffer.push_back(static_cast<unsigned char>((value & 0x0000FF00) >> 8));
|
||||||
|
buffer.push_back(static_cast<unsigned char>((value & 0x00FF0000) >> 16));
|
||||||
|
buffer.push_back(static_cast<unsigned char>((value & 0xFF000000) >> 24));
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace sf::priv
|
||||||
|
{
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
bool SoundFileWriterOpus::check(const std::filesystem::path& filename)
|
||||||
|
{
|
||||||
|
return toLower(filename.extension().string()) == ".opus";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
SoundFileWriterOpus::~SoundFileWriterOpus()
|
||||||
|
{
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
bool SoundFileWriterOpus::open(const std::filesystem::path& filename,
|
||||||
|
unsigned int sampleRate,
|
||||||
|
unsigned int channelCount,
|
||||||
|
const std::vector<SoundChannel>& channelMap)
|
||||||
|
{
|
||||||
|
std::vector<SoundChannel> targetChannelMap;
|
||||||
|
|
||||||
|
// For Vorbis channel mapping refer to: https://xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-810004.3.9
|
||||||
|
switch (channelCount)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
err() << "No channels to write to Opus file" << std::endl;
|
||||||
|
return false;
|
||||||
|
case 1:
|
||||||
|
targetChannelMap = {SoundChannel::Mono};
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
targetChannelMap = {SoundChannel::FrontLeft, SoundChannel::FrontRight};
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
targetChannelMap = {SoundChannel::FrontLeft, SoundChannel::FrontCenter, SoundChannel::FrontRight};
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
targetChannelMap = {SoundChannel::FrontLeft, SoundChannel::FrontRight, SoundChannel::BackLeft, SoundChannel::BackRight};
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
targetChannelMap = {SoundChannel::FrontLeft,
|
||||||
|
SoundChannel::FrontCenter,
|
||||||
|
SoundChannel::FrontRight,
|
||||||
|
SoundChannel::BackLeft,
|
||||||
|
SoundChannel::BackRight};
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
targetChannelMap = {SoundChannel::FrontLeft,
|
||||||
|
SoundChannel::FrontCenter,
|
||||||
|
SoundChannel::FrontRight,
|
||||||
|
SoundChannel::BackLeft,
|
||||||
|
SoundChannel::BackRight,
|
||||||
|
SoundChannel::LowFrequencyEffects};
|
||||||
|
break;
|
||||||
|
case 7:
|
||||||
|
targetChannelMap = {SoundChannel::FrontLeft,
|
||||||
|
SoundChannel::FrontCenter,
|
||||||
|
SoundChannel::FrontRight,
|
||||||
|
SoundChannel::SideLeft,
|
||||||
|
SoundChannel::SideRight,
|
||||||
|
SoundChannel::BackCenter,
|
||||||
|
SoundChannel::LowFrequencyEffects};
|
||||||
|
break;
|
||||||
|
case 8:
|
||||||
|
targetChannelMap = {SoundChannel::FrontLeft,
|
||||||
|
SoundChannel::FrontCenter,
|
||||||
|
SoundChannel::FrontRight,
|
||||||
|
SoundChannel::SideLeft,
|
||||||
|
SoundChannel::SideRight,
|
||||||
|
SoundChannel::BackLeft,
|
||||||
|
SoundChannel::BackRight,
|
||||||
|
SoundChannel::LowFrequencyEffects};
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
err() << "Opus files with more than 8 channels not supported" << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the channel map contains channels that we cannot remap to a mapping supported by Opus
|
||||||
|
if (!std::is_permutation(channelMap.begin(), channelMap.end(), targetChannelMap.begin()))
|
||||||
|
{
|
||||||
|
err() << "Provided channel map cannot be reordered to a channel map supported by Opus" << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the remap table
|
||||||
|
for (auto i = 0u; i < channelCount; ++i)
|
||||||
|
m_remapTable[i] = static_cast<std::size_t>(
|
||||||
|
std::find(channelMap.begin(), channelMap.end(), targetChannelMap[i]) - channelMap.begin());
|
||||||
|
|
||||||
|
// Save the channel count
|
||||||
|
m_channelCount = channelCount;
|
||||||
|
m_sampleRate = sampleRate;
|
||||||
|
|
||||||
|
// Initialize the ogg/opus stream
|
||||||
|
static std::mt19937 rng(std::random_device{}());
|
||||||
|
if (ogg_stream_init(&m_ogg, std::uniform_int_distribution(0, std::numeric_limits<int>::max())(rng)) == -1)
|
||||||
|
{
|
||||||
|
err() << "Stream init of ogg/opus failed" << std::endl;
|
||||||
|
close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int status = OPUS_INTERNAL_ERROR;
|
||||||
|
m_opus = opus_encoder_create(static_cast<opus_int32>(sampleRate), static_cast<int>(channelCount), OPUS_APPLICATION_AUDIO, &status);
|
||||||
|
if (status != OPUS_OK)
|
||||||
|
{
|
||||||
|
err() << "Failed to write ogg/opus file\n" << formatDebugPathInfo(filename) << std::endl;
|
||||||
|
if (status == OPUS_BAD_ARG)
|
||||||
|
err() << "Possibly wrong sample rate, allowed are 8000, 12000, 16000, 24000, or 48000 Hz." << std::endl;
|
||||||
|
close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open the file after the opus setup is ok
|
||||||
|
m_file.open(filename, std::ios::binary);
|
||||||
|
if (!m_file)
|
||||||
|
{
|
||||||
|
err() << "Failed to write opus file (cannot open file)\n" << formatDebugPathInfo(filename) << std::endl;
|
||||||
|
close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set bitrate (VBR is default)
|
||||||
|
opus_encoder_ctl(m_opus, OPUS_SET_BITRATE(128000));
|
||||||
|
|
||||||
|
// Create opus header MAGICBYTES
|
||||||
|
std::vector<unsigned char> headerData({'O', 'p', 'u', 's', 'H', 'e', 'a', 'd'});
|
||||||
|
|
||||||
|
headerData.push_back(1); // Version
|
||||||
|
headerData.push_back(static_cast<unsigned char>(channelCount));
|
||||||
|
headerData.push_back(0); // Preskip
|
||||||
|
headerData.push_back(0);
|
||||||
|
|
||||||
|
writeUint32(headerData, static_cast<std::uint32_t>(sampleRate));
|
||||||
|
|
||||||
|
headerData.push_back(0); // Gain
|
||||||
|
headerData.push_back(0);
|
||||||
|
|
||||||
|
headerData.push_back(channelCount > 8 ? 255 : (channelCount > 2)); // Mapping family
|
||||||
|
|
||||||
|
// Map opus header to ogg packet
|
||||||
|
ogg_packet op;
|
||||||
|
op.packet = headerData.data();
|
||||||
|
op.bytes = static_cast<long>(headerData.size());
|
||||||
|
op.b_o_s = 1;
|
||||||
|
op.e_o_s = 0;
|
||||||
|
op.granulepos = 0;
|
||||||
|
op.packetno = static_cast<ogg_int64_t>(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
|
||||||
|
// commentData initialized with magic bytes
|
||||||
|
std::vector<unsigned char> commentData({'O', 'p', 'u', 's', 'T', 'a', 'g', 's'});
|
||||||
|
|
||||||
|
// Vendor string
|
||||||
|
const std::string opusVersion(opus_get_version_string());
|
||||||
|
|
||||||
|
// unsigned 32bit integer: Length of vendor string (encoding library)
|
||||||
|
writeUint32(commentData, static_cast<std::uint32_t>(opusVersion.size()));
|
||||||
|
commentData.insert(commentData.end(), opusVersion.begin(), opusVersion.end());
|
||||||
|
|
||||||
|
// Length of user comments (E.g. one could add an ENCODER tag for SFML)
|
||||||
|
writeUint32(commentData, 0);
|
||||||
|
|
||||||
|
op.packet = &commentData.front();
|
||||||
|
op.bytes = static_cast<long>(commentData.size());
|
||||||
|
op.b_o_s = 0;
|
||||||
|
op.e_o_s = 0;
|
||||||
|
op.granulepos = 0;
|
||||||
|
op.packetno = static_cast<ogg_int64_t>(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 std::int16_t* samples, std::uint64_t count)
|
||||||
|
{
|
||||||
|
assert(m_opus && "Opus stream is missing. Call SoundFileWriterOpus::open() to initialize it.");
|
||||||
|
|
||||||
|
const opus_uint32 frameSize = 960;
|
||||||
|
std::vector<unsigned char> buffer(frameSize * m_channelCount);
|
||||||
|
|
||||||
|
std::uint32_t frameNumber = 0;
|
||||||
|
std::uint8_t endOfStream = 0;
|
||||||
|
|
||||||
|
while (count > 0)
|
||||||
|
{
|
||||||
|
opus_int32 packetSize;
|
||||||
|
|
||||||
|
// Check if wee need to pad the input
|
||||||
|
if (count < frameSize * m_channelCount)
|
||||||
|
{
|
||||||
|
const std::uint32_t begin = frameNumber * frameSize * m_channelCount;
|
||||||
|
std::vector<opus_int16> pad(samples + begin, samples + begin + count);
|
||||||
|
pad.insert(pad.end(), (frameSize * m_channelCount) - pad.size(),0);
|
||||||
|
packetSize = opus_encode(m_opus, &pad.front(), frameSize, buffer.data(), static_cast<opus_int32>(buffer.size()));
|
||||||
|
endOfStream = 1;
|
||||||
|
count = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
packetSize = opus_encode(m_opus,
|
||||||
|
samples + (frameNumber * frameSize * m_channelCount),
|
||||||
|
frameSize,
|
||||||
|
&buffer.front(),
|
||||||
|
static_cast<opus_int32>(buffer.size()));
|
||||||
|
count -= frameSize * m_channelCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (packetSize < 0)
|
||||||
|
{
|
||||||
|
err() << "An error occurred when encoding sound to opus." << std::endl;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
ogg_packet op;
|
||||||
|
op.packet = &buffer.front();
|
||||||
|
op.bytes = packetSize;
|
||||||
|
op.granulepos = frameNumber * frameSize * 48000ul / m_sampleRate;
|
||||||
|
op.packetno = static_cast<ogg_int64_t>(m_packageNumber++);
|
||||||
|
op.b_o_s = 0;
|
||||||
|
op.e_o_s = endOfStream;
|
||||||
|
ogg_stream_packetin(&m_ogg, &op);
|
||||||
|
|
||||||
|
frameNumber++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
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 sf::priv
|
117
src/SFML/Audio/SoundFileWriterOpus.hpp
Normal file
117
src/SFML/Audio/SoundFileWriterOpus.hpp
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// SFML - Simple and Fast Multimedia Library
|
||||||
|
// Copyright (C) 2007-2024 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.
|
||||||
|
//
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
// Headers
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
#include <SFML/Audio/SoundFileWriter.hpp>
|
||||||
|
|
||||||
|
#include <opus.h>
|
||||||
|
#include <ogg/ogg.h>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
|
||||||
|
namespace sf::priv
|
||||||
|
{
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
/// \brief Implementation of sound file writer that handles 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
|
||||||
|
///
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
[[nodiscard]] static bool check(const std::filesystem::path& filename);
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
/// \brief Destructor
|
||||||
|
///
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
~SoundFileWriterOpus() override;
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
/// \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
|
||||||
|
/// \param channelMap Map of position in sample frame to sound channel
|
||||||
|
///
|
||||||
|
/// \return `true` if the file was successfully opened
|
||||||
|
///
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
[[nodiscard]] bool open(const std::filesystem::path& filename,
|
||||||
|
unsigned int sampleRate,
|
||||||
|
unsigned int channelCount,
|
||||||
|
const std::vector<SoundChannel>& channelMap) override;
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
/// \brief Write audio samples to the open file
|
||||||
|
///
|
||||||
|
/// \param samples Pointer to the sample array to write
|
||||||
|
/// \param count Number of samples to write
|
||||||
|
///
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
void write(const std::int16_t* samples, std::uint64_t count) override;
|
||||||
|
|
||||||
|
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::array<std::size_t, 8> m_remapTable{}; //!< Table we use to remap source to target channel order
|
||||||
|
std::ofstream m_file; //!< Output file
|
||||||
|
ogg_stream_state m_ogg{}; //!< OGG stream
|
||||||
|
OpusEncoder* m_opus{}; //!< Opus handle
|
||||||
|
std::uint64_t m_packageNumber{}; //!<
|
||||||
|
std::uint32_t m_sampleRate{}; //!<
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace sf::priv
|
5
tools/opus/PatchOpus.cmake
Normal file
5
tools/opus/PatchOpus.cmake
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
file(READ "${OPUS_DIR}/CMakeLists.txt" OPUS_CMAKELISTS_CONTENTS)
|
||||||
|
string(REPLACE "feature_summary(WHAT ALL)" "" OPUS_CMAKELISTS_CONTENTS "${OPUS_CMAKELISTS_CONTENTS}")
|
||||||
|
string(REPLACE "\n\nadd_library(opus" "\nset(CMAKE_DEBUG_POSTFIX d)\nadd_library(opus" OPUS_CMAKELISTS_CONTENTS "${OPUS_CMAKELISTS_CONTENTS}")
|
||||||
|
string(REPLACE "PUBLIC_HEADER\n \"\${Opus_PUBLIC_HEADER}\"" "" OPUS_CMAKELISTS_CONTENTS "${OPUS_CMAKELISTS_CONTENTS}")
|
||||||
|
file(WRITE "${OPUS_DIR}/CMakeLists.txt" "${OPUS_CMAKELISTS_CONTENTS}")
|
4
tools/opus/PatchOpusfile.cmake
Normal file
4
tools/opus/PatchOpusfile.cmake
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
file(READ "${OPUSFILE_DIR}/CMakeLists.txt" OPUSFILE_CMAKELISTS_CONTENTS)
|
||||||
|
string(REPLACE "\n\nadd_library(opusfile" "\nset(CMAKE_DEBUG_POSTFIX d)\nadd_library(opusfile" OPUSFILE_CMAKELISTS_CONTENTS "${OPUSFILE_CMAKELISTS_CONTENTS}")
|
||||||
|
string(REPLACE "PUBLIC_HEADER \"\${CMAKE_CURRENT_SOURCE_DIR}/include/opusfile.h\"" "" OPUSFILE_CMAKELISTS_CONTENTS "${OPUSFILE_CMAKELISTS_CONTENTS}")
|
||||||
|
file(WRITE "${OPUSFILE_DIR}/CMakeLists.txt" "${OPUSFILE_CMAKELISTS_CONTENTS}")
|
Loading…
Reference in New Issue
Block a user