parent
1625de64e2
commit
e13c222e28
|
@ -1,5 +1,9 @@
|
|||
# Version history
|
||||
|
||||
## Unreleased
|
||||
|
||||
* Support for Ogg Vorbis (.ogg) files
|
||||
|
||||
## 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).
|
||||
|
|
|
@ -309,11 +309,15 @@ target_link_libraries(rhubarb-animation
|
|||
add_library(rhubarb-audio
|
||||
src/audio/AudioClip.cpp
|
||||
src/audio/AudioClip.h
|
||||
src/audio/audioFileReading.cpp
|
||||
src/audio/audioFileReading.h
|
||||
src/audio/AudioSegment.cpp
|
||||
src/audio/AudioSegment.h
|
||||
src/audio/DcOffset.cpp
|
||||
src/audio/DcOffset.h
|
||||
src/audio/ioTools.h
|
||||
src/audio/OggVorbisFileReader.cpp
|
||||
src/audio/OggVorbisFileReader.h
|
||||
src/audio/processing.cpp
|
||||
src/audio/processing.h
|
||||
src/audio/SampleRateConverter.cpp
|
||||
|
@ -328,6 +332,7 @@ add_library(rhubarb-audio
|
|||
target_include_directories(rhubarb-audio PRIVATE "src/audio")
|
||||
target_link_libraries(rhubarb-audio
|
||||
webRtc
|
||||
vorbis
|
||||
rhubarb-logging
|
||||
rhubarb-time
|
||||
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 "tools/textFiles.h"
|
||||
#include "animation/mouthAnimation.h"
|
||||
#include "audio/WaveFileReader.h"
|
||||
#include "audio/audioFileReading.h"
|
||||
|
||||
using boost::optional;
|
||||
using std::string;
|
||||
|
@ -22,14 +22,6 @@ JoiningContinuousTimeline<Shape> animateAudioClip(
|
|||
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(
|
||||
path filePath,
|
||||
optional<string> dialog,
|
||||
|
@ -37,5 +29,6 @@ JoiningContinuousTimeline<Shape> animateWaveFile(
|
|||
int maxThreadCount,
|
||||
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