Add support for Ogg Vorbis file format

#40
This commit is contained in:
Daniel Wolf 2018-07-13 22:59:05 +02:00
parent 1625de64e2
commit e13c222e28
7 changed files with 185 additions and 10 deletions

View File

@ -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).

View File

@ -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

View File

@ -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;
};
}

View File

@ -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;
};

View File

@ -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)));
}
}

View File

@ -0,0 +1,7 @@
#pragma once
#include <memory>
#include "AudioClip.h"
#include <boost/filesystem.hpp>
std::unique_ptr<AudioClip> createAudioFileClip(boost::filesystem::path filePath);

View File

@ -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);
}