parent
1625de64e2
commit
e13c222e28
|
@ -1,5 +1,9 @@
|
||||||
# Version history
|
# Version history
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
* Support for Ogg Vorbis (.ogg) files
|
||||||
|
|
||||||
## Version 1.7.2
|
## Version 1.7.2
|
||||||
|
|
||||||
* Fixed bug in Rhubarb for Spine where processing failed depending on the number of existing animations. See [issue #34](https://github.com/DanielSWolf/rhubarb-lip-sync/issues/34#issuecomment-378198776).
|
* Fixed bug in Rhubarb for Spine where processing failed depending on the number of existing animations. See [issue #34](https://github.com/DanielSWolf/rhubarb-lip-sync/issues/34#issuecomment-378198776).
|
||||||
|
|
|
@ -309,11 +309,15 @@ target_link_libraries(rhubarb-animation
|
||||||
add_library(rhubarb-audio
|
add_library(rhubarb-audio
|
||||||
src/audio/AudioClip.cpp
|
src/audio/AudioClip.cpp
|
||||||
src/audio/AudioClip.h
|
src/audio/AudioClip.h
|
||||||
|
src/audio/audioFileReading.cpp
|
||||||
|
src/audio/audioFileReading.h
|
||||||
src/audio/AudioSegment.cpp
|
src/audio/AudioSegment.cpp
|
||||||
src/audio/AudioSegment.h
|
src/audio/AudioSegment.h
|
||||||
src/audio/DcOffset.cpp
|
src/audio/DcOffset.cpp
|
||||||
src/audio/DcOffset.h
|
src/audio/DcOffset.h
|
||||||
src/audio/ioTools.h
|
src/audio/ioTools.h
|
||||||
|
src/audio/OggVorbisFileReader.cpp
|
||||||
|
src/audio/OggVorbisFileReader.h
|
||||||
src/audio/processing.cpp
|
src/audio/processing.cpp
|
||||||
src/audio/processing.h
|
src/audio/processing.h
|
||||||
src/audio/SampleRateConverter.cpp
|
src/audio/SampleRateConverter.cpp
|
||||||
|
@ -328,6 +332,7 @@ add_library(rhubarb-audio
|
||||||
target_include_directories(rhubarb-audio PRIVATE "src/audio")
|
target_include_directories(rhubarb-audio PRIVATE "src/audio")
|
||||||
target_link_libraries(rhubarb-audio
|
target_link_libraries(rhubarb-audio
|
||||||
webRtc
|
webRtc
|
||||||
|
vorbis
|
||||||
rhubarb-logging
|
rhubarb-logging
|
||||||
rhubarb-time
|
rhubarb-time
|
||||||
rhubarb-tools
|
rhubarb-tools
|
||||||
|
|
|
@ -0,0 +1,119 @@
|
||||||
|
#include "OggVorbisFileReader.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include "vorbis/codec.h"
|
||||||
|
#include "vorbis/vorbisfile.h"
|
||||||
|
#include "tools/tools.h"
|
||||||
|
#include <format.h>
|
||||||
|
#include <numeric>
|
||||||
|
#include "tools/fileTools.h"
|
||||||
|
|
||||||
|
using boost::filesystem::path;
|
||||||
|
using std::vector;
|
||||||
|
using std::make_shared;
|
||||||
|
|
||||||
|
std::string vorbisErrorToString(int64_t errorCode) {
|
||||||
|
switch (errorCode) {
|
||||||
|
case OV_EREAD:
|
||||||
|
return "Read error while fetching compressed data for decode.";
|
||||||
|
case OV_EFAULT:
|
||||||
|
return "Internal logic fault; indicates a bug or heap/stack corruption.";
|
||||||
|
case OV_EIMPL:
|
||||||
|
return "Feature not implemented";
|
||||||
|
case OV_EINVAL:
|
||||||
|
return "Either an invalid argument, or incompletely initialized argument passed to a call.";
|
||||||
|
case OV_ENOTVORBIS:
|
||||||
|
return "The given file/data was not recognized as Ogg Vorbis data.";
|
||||||
|
case OV_EBADHEADER:
|
||||||
|
return "The file/data is apparently an Ogg Vorbis stream, but contains a corrupted or undecipherable header.";
|
||||||
|
case OV_EVERSION:
|
||||||
|
return "The bitstream format revision of the given Vorbis stream is not supported.";
|
||||||
|
case OV_ENOTAUDIO:
|
||||||
|
return "Packet is not an audio packet.";
|
||||||
|
case OV_EBADPACKET:
|
||||||
|
return "Error in packet.";
|
||||||
|
case OV_EBADLINK:
|
||||||
|
return "The given link exists in the Vorbis data stream, but is not decipherable due to garbacge or corruption.";
|
||||||
|
case OV_ENOSEEK:
|
||||||
|
return "The given stream is not seekable.";
|
||||||
|
default:
|
||||||
|
return "An unexpected Vorbis error occurred.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
T throwOnError(T code) {
|
||||||
|
// OV_HOLE, though technically an error code, is only informational
|
||||||
|
const bool error = code < 0 && code != OV_HOLE;
|
||||||
|
if (error) {
|
||||||
|
const std::string message =
|
||||||
|
fmt::format("{} (Vorbis error {})", vorbisErrorToString(code), code);
|
||||||
|
throw std::runtime_error(message);
|
||||||
|
}
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
// RAII wrapper around OggVorbis_File
|
||||||
|
class OggVorbisFile {
|
||||||
|
public:
|
||||||
|
OggVorbisFile(const OggVorbisFile&) = delete;
|
||||||
|
OggVorbisFile& operator=(const OggVorbisFile&) = delete;
|
||||||
|
|
||||||
|
OggVorbisFile(const path& filePath) {
|
||||||
|
throwOnError(ov_fopen(filePath.string().c_str(), &file));
|
||||||
|
}
|
||||||
|
|
||||||
|
OggVorbis_File* get() {
|
||||||
|
return &file;
|
||||||
|
}
|
||||||
|
|
||||||
|
~OggVorbisFile() {
|
||||||
|
ov_clear(&file);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
OggVorbis_File file;
|
||||||
|
};
|
||||||
|
|
||||||
|
OggVorbisFileReader::OggVorbisFileReader(const path& filePath) :
|
||||||
|
filePath(filePath)
|
||||||
|
{
|
||||||
|
// Make sure that common error cases result in readable exception messages
|
||||||
|
throwIfNotReadable(filePath);
|
||||||
|
|
||||||
|
OggVorbisFile file(filePath);
|
||||||
|
|
||||||
|
vorbis_info* vorbisInfo = ov_info(file.get(), -1);
|
||||||
|
sampleRate = vorbisInfo->rate;
|
||||||
|
channelCount = vorbisInfo->channels;
|
||||||
|
|
||||||
|
sampleCount = throwOnError(ov_pcm_total(file.get(), -1));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<AudioClip> OggVorbisFileReader::clone() const {
|
||||||
|
return std::make_unique<OggVorbisFileReader>(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
SampleReader OggVorbisFileReader::createUnsafeSampleReader() const {
|
||||||
|
return [
|
||||||
|
channelCount = channelCount,
|
||||||
|
file = make_shared<OggVorbisFile>(filePath),
|
||||||
|
currentIndex = size_type(0)
|
||||||
|
](size_type index) mutable {
|
||||||
|
// Seek
|
||||||
|
if (index != currentIndex) {
|
||||||
|
throwOnError(ov_pcm_seek(file->get(), index));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read a single sample
|
||||||
|
value_type** p = nullptr;
|
||||||
|
long readCount = throwOnError(ov_read_float(file->get(), &p, 1, nullptr));
|
||||||
|
if (readCount == 0) {
|
||||||
|
throw std::runtime_error("Unexpected end of file.");
|
||||||
|
}
|
||||||
|
++currentIndex;
|
||||||
|
|
||||||
|
// Downmix channels
|
||||||
|
return std::accumulate(*p, *p + channelCount, 0.0f) / channelCount;
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "AudioClip.h"
|
||||||
|
#include <boost/filesystem/path.hpp>
|
||||||
|
|
||||||
|
class OggVorbisFileReader : public AudioClip {
|
||||||
|
public:
|
||||||
|
OggVorbisFileReader(const boost::filesystem::path& filePath);
|
||||||
|
std::unique_ptr<AudioClip> clone() const override;
|
||||||
|
int getSampleRate() const override { return sampleRate; }
|
||||||
|
size_type size() const override { return sampleCount; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
SampleReader createUnsafeSampleReader() const override;
|
||||||
|
|
||||||
|
boost::filesystem::path filePath;
|
||||||
|
int sampleRate;
|
||||||
|
int channelCount;
|
||||||
|
size_type sampleCount;
|
||||||
|
};
|
|
@ -0,0 +1,27 @@
|
||||||
|
#include "audioFileReading.h"
|
||||||
|
#include <format.h>
|
||||||
|
#include "WaveFileReader.h"
|
||||||
|
#include <boost/algorithm/string.hpp>
|
||||||
|
#include "OggVorbisFileReader.h"
|
||||||
|
|
||||||
|
using boost::filesystem::path;
|
||||||
|
using std::string;
|
||||||
|
using std::runtime_error;
|
||||||
|
using fmt::format;
|
||||||
|
|
||||||
|
std::unique_ptr<AudioClip> createAudioFileClip(path filePath) {
|
||||||
|
try {
|
||||||
|
const string extension =
|
||||||
|
boost::algorithm::to_lower_copy(boost::filesystem::extension(filePath));
|
||||||
|
if (extension == ".wav") {
|
||||||
|
return std::make_unique<WaveFileReader>(filePath);
|
||||||
|
}
|
||||||
|
if (extension == ".ogg") {
|
||||||
|
return std::make_unique<OggVorbisFileReader>(filePath);
|
||||||
|
}
|
||||||
|
throw runtime_error(format(
|
||||||
|
"Unsupported file extension '{}'. Supported extensions are '.wav' and '.ogg'.", extension));
|
||||||
|
} catch (...) {
|
||||||
|
std::throw_with_nested(runtime_error(format("Could not open sound file {}.", filePath)));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include "AudioClip.h"
|
||||||
|
#include <boost/filesystem.hpp>
|
||||||
|
|
||||||
|
std::unique_ptr<AudioClip> createAudioFileClip(boost::filesystem::path filePath);
|
|
@ -3,7 +3,7 @@
|
||||||
#include "recognition/phoneRecognition.h"
|
#include "recognition/phoneRecognition.h"
|
||||||
#include "tools/textFiles.h"
|
#include "tools/textFiles.h"
|
||||||
#include "animation/mouthAnimation.h"
|
#include "animation/mouthAnimation.h"
|
||||||
#include "audio/WaveFileReader.h"
|
#include "audio/audioFileReading.h"
|
||||||
|
|
||||||
using boost::optional;
|
using boost::optional;
|
||||||
using std::string;
|
using std::string;
|
||||||
|
@ -22,14 +22,6 @@ JoiningContinuousTimeline<Shape> animateAudioClip(
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
unique_ptr<AudioClip> createWaveAudioClip(path filePath) {
|
|
||||||
try {
|
|
||||||
return std::make_unique<WaveFileReader>(filePath);
|
|
||||||
} catch (...) {
|
|
||||||
std::throw_with_nested(std::runtime_error(fmt::format("Could not open sound file {}.", filePath)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
JoiningContinuousTimeline<Shape> animateWaveFile(
|
JoiningContinuousTimeline<Shape> animateWaveFile(
|
||||||
path filePath,
|
path filePath,
|
||||||
optional<string> dialog,
|
optional<string> dialog,
|
||||||
|
@ -37,5 +29,6 @@ JoiningContinuousTimeline<Shape> animateWaveFile(
|
||||||
int maxThreadCount,
|
int maxThreadCount,
|
||||||
ProgressSink& progressSink)
|
ProgressSink& progressSink)
|
||||||
{
|
{
|
||||||
return animateAudioClip(*createWaveAudioClip(filePath), dialog, targetShapeSet, maxThreadCount, progressSink);
|
const auto audioClip = createAudioFileClip(filePath);
|
||||||
|
return animateAudioClip(*audioClip, dialog, targetShapeSet, maxThreadCount, progressSink);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue