added simple wave classes
To assist the input/output of PCM data, we use the fairly straight-forward WAVE format. These classes simplify the input and output of such data.
This commit is contained in:
@@ -0,0 +1,182 @@
|
||||
#include "waveinput.h"
|
||||
|
||||
extern "C" {
|
||||
#include <libavcodec/avcodec.h>
|
||||
}
|
||||
|
||||
#include <QDataStream>
|
||||
|
||||
WaveInput::WaveInput(const QString &f) :
|
||||
file_(f)
|
||||
{
|
||||
}
|
||||
|
||||
WaveInput::~WaveInput()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
bool WaveInput::open()
|
||||
{
|
||||
if (!file_.open(QFile::ReadOnly)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (file_.read(4) != "RIFF") {
|
||||
close();
|
||||
qDebug() << "No RIFF found";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Skip filesize bytes
|
||||
file_.seek(file_.pos() + 4);
|
||||
|
||||
if (file_.read(4) != "WAVE") {
|
||||
close();
|
||||
qDebug() << "No WAVE found";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Find fmt_ section
|
||||
if (!find_str(&file_, "fmt ")) {
|
||||
close();
|
||||
qDebug() << "No fmt found";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Skip fmt_ section size
|
||||
file_.seek(file_.pos()+4);
|
||||
|
||||
// Create data stream for reading bytes into types
|
||||
QDataStream data_stream(&file_);
|
||||
data_stream.setByteOrder(QDataStream::LittleEndian);
|
||||
|
||||
// Read data type
|
||||
uint16_t data_type;
|
||||
data_stream >> data_type;
|
||||
|
||||
bool data_is_float;
|
||||
switch (data_type) {
|
||||
case 1: // PCM Integer
|
||||
data_is_float = false;
|
||||
break;
|
||||
case 3:
|
||||
data_is_float = true;
|
||||
break;
|
||||
default:
|
||||
// If it's neither float nor int, we can't work with this file
|
||||
close();
|
||||
qDebug() << "Invalid WAV type" << data_type;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read number of channels
|
||||
uint16_t channel_count;
|
||||
data_stream >> channel_count;
|
||||
|
||||
uint64_t channel_layout = static_cast<uint64_t>(av_get_default_channel_layout(channel_count));
|
||||
|
||||
int32_t sample_rate;
|
||||
data_stream >> sample_rate;
|
||||
|
||||
// Skip bytes per second value and bytes per sample value
|
||||
file_.seek(file_.pos() + 6);
|
||||
|
||||
uint16_t bits_per_sample;
|
||||
data_stream >> bits_per_sample;
|
||||
|
||||
SampleFormat format;
|
||||
|
||||
switch (bits_per_sample) {
|
||||
case 8:
|
||||
format = SAMPLE_FMT_U8;
|
||||
break;
|
||||
case 16:
|
||||
format = SAMPLE_FMT_S16;
|
||||
break;
|
||||
case 32:
|
||||
if (data_is_float) {
|
||||
format = SAMPLE_FMT_S32;
|
||||
} else {
|
||||
format = SAMPLE_FMT_FLT;
|
||||
}
|
||||
break;
|
||||
case 64:
|
||||
if (data_is_float) {
|
||||
format = SAMPLE_FMT_S64;
|
||||
} else {
|
||||
format = SAMPLE_FMT_DBL;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// We don't know this format...
|
||||
close();
|
||||
qDebug() << "Invalid format found" << bits_per_sample;
|
||||
return false;
|
||||
}
|
||||
|
||||
// We're good to go!
|
||||
params_ = AudioRenderingParams(sample_rate, channel_layout, format);
|
||||
|
||||
if (!find_str(&file_, "data")) {
|
||||
close();
|
||||
qDebug() << "No data tag found";
|
||||
return false;
|
||||
}
|
||||
|
||||
data_position_ = file_.pos() + 4;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WaveInput::is_open()
|
||||
{
|
||||
return file_.isOpen();
|
||||
}
|
||||
|
||||
QByteArray WaveInput::read(int offset, int length)
|
||||
{
|
||||
if (!is_open()) {
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
file_.seek(offset + data_position_);
|
||||
return file_.read(length);
|
||||
}
|
||||
|
||||
void WaveInput::read(int offset, char *buffer, int length)
|
||||
{
|
||||
if (!is_open()) {
|
||||
return;
|
||||
}
|
||||
|
||||
file_.seek(offset + data_position_);
|
||||
file_.read(buffer, length);
|
||||
}
|
||||
|
||||
AudioRenderingParams WaveInput::params()
|
||||
{
|
||||
return params_;
|
||||
}
|
||||
|
||||
void WaveInput::close()
|
||||
{
|
||||
if (file_.isOpen()) {
|
||||
file_.close();
|
||||
}
|
||||
}
|
||||
|
||||
bool WaveInput::find_str(QFile *f, const char *str)
|
||||
{
|
||||
qint64 pos = f->pos();
|
||||
while (f->read(4) != str) {
|
||||
if (f->atEnd()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
pos++;
|
||||
f->seek(pos);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
#ifndef WAVEINPUT_H
|
||||
#define WAVEINPUT_H
|
||||
|
||||
#include <QFile>
|
||||
|
||||
#include "render/audioparams.h"
|
||||
|
||||
class WaveInput
|
||||
{
|
||||
public:
|
||||
WaveInput(const QString& f);
|
||||
|
||||
~WaveInput();
|
||||
|
||||
Q_DISABLE_COPY_MOVE(WaveInput)
|
||||
|
||||
bool open();
|
||||
|
||||
bool is_open();
|
||||
|
||||
QByteArray read(int offset, int length);
|
||||
void read(int offset, char *buffer, int length);
|
||||
|
||||
AudioRenderingParams params();
|
||||
|
||||
void close();
|
||||
|
||||
private:
|
||||
bool find_str(QFile* f, const char* str);
|
||||
|
||||
AudioRenderingParams params_;
|
||||
|
||||
QFile file_;
|
||||
|
||||
qint64 data_position_;
|
||||
};
|
||||
|
||||
#endif // WAVEINPUT_H
|
||||
@@ -0,0 +1,144 @@
|
||||
#include "waveoutput.h"
|
||||
|
||||
const int16_t kWAVIntegerFormat = 1;
|
||||
const int16_t kWAVFloatFormat = 3;
|
||||
|
||||
WaveOutput::WaveOutput(const QString &f,
|
||||
const AudioRenderingParams& params) :
|
||||
file_(f),
|
||||
params_(params)
|
||||
{
|
||||
Q_ASSERT(params_.is_valid());
|
||||
}
|
||||
|
||||
WaveOutput::~WaveOutput()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
bool WaveOutput::open()
|
||||
{
|
||||
data_length_ = 0;
|
||||
|
||||
if (file_.open(QFile::WriteOnly)) {
|
||||
// RIFF header
|
||||
file_.write("RIFF");
|
||||
|
||||
// Total file size minus RIFF and this integer (minus 8 bytes, filled in later)
|
||||
write_int<int32_t>(&file_, 0);
|
||||
|
||||
// File type header
|
||||
file_.write("WAVE");
|
||||
|
||||
// Begin format descriptor chunk
|
||||
file_.write("fmt ");
|
||||
|
||||
// Format chunk size
|
||||
write_int<int32_t>(&file_, 16);
|
||||
|
||||
// Type of format
|
||||
switch (params_.format()) {
|
||||
case SAMPLE_FMT_U8:
|
||||
case SAMPLE_FMT_S16:
|
||||
case SAMPLE_FMT_S32:
|
||||
case SAMPLE_FMT_S64:
|
||||
write_int<int16_t>(&file_, kWAVIntegerFormat);
|
||||
break;
|
||||
case SAMPLE_FMT_FLT:
|
||||
case SAMPLE_FMT_DBL:
|
||||
write_int<int16_t>(&file_, kWAVFloatFormat);
|
||||
break;
|
||||
case SAMPLE_FMT_INVALID:
|
||||
case SAMPLE_FMT_COUNT:
|
||||
qWarning() << "Invalid sample format for WAVE audio";
|
||||
file_.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Number of channels
|
||||
write_int<int16_t>(&file_, static_cast<int16_t>(params_.channel_count()));
|
||||
|
||||
// Sample rate
|
||||
write_int<int32_t>(&file_, params_.sample_rate());
|
||||
|
||||
// Bytes per second
|
||||
write_int<int32_t>(&file_, params_.samples_to_bytes(params_.sample_rate()));
|
||||
|
||||
// Bytes per sample
|
||||
write_int<int16_t>(&file_, static_cast<int16_t>(params_.samples_to_bytes(1)));
|
||||
|
||||
// Bits per sample per channel
|
||||
write_int<int16_t>(&file_, static_cast<int16_t>(params_.bits_per_sample()));
|
||||
|
||||
// Data chunk header
|
||||
file_.write("data");
|
||||
|
||||
// Size of data chunk (filled in later)
|
||||
write_int<int32_t>(&file_, 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void WaveOutput::write(const QByteArray &bytes)
|
||||
{
|
||||
if (file_.isOpen()) {
|
||||
file_.write(bytes);
|
||||
|
||||
data_length_ += bytes.size();
|
||||
}
|
||||
}
|
||||
|
||||
void WaveOutput::write(const char *bytes, int length)
|
||||
{
|
||||
if (file_.isOpen()) {
|
||||
file_.write(bytes, length);
|
||||
|
||||
data_length_ += length;
|
||||
}
|
||||
}
|
||||
|
||||
void WaveOutput::close()
|
||||
{
|
||||
if (file_.isOpen()) {
|
||||
|
||||
// Write file sizes
|
||||
file_.seek(4);
|
||||
write_int<int32_t>(&file_, data_length_ + 36);
|
||||
|
||||
file_.seek(40);
|
||||
write_int<int32_t>(&file_, data_length_);
|
||||
|
||||
file_.close();
|
||||
}
|
||||
}
|
||||
|
||||
void WaveOutput::switch_endianness(QByteArray& array)
|
||||
{
|
||||
int half_sz = array.size()/2;
|
||||
|
||||
for (int i=0;i<half_sz;i++) {
|
||||
int oppose_index = array.size() - i - 1;
|
||||
|
||||
char temp = array[i];
|
||||
array[i] = array[oppose_index];
|
||||
array[oppose_index] = temp;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void WaveOutput::write_int(QFile *file, T integer)
|
||||
{
|
||||
QByteArray bytes;
|
||||
bytes.resize(sizeof(T));
|
||||
memcpy(bytes.data(), &integer, static_cast<size_t>(bytes.size()));
|
||||
|
||||
// WAV expects little-endian, so if the integer is big endian we need to switch
|
||||
if (QSysInfo::ByteOrder == QSysInfo::BigEndian) {
|
||||
switch_endianness(bytes);
|
||||
}
|
||||
|
||||
file->write(bytes);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
#ifndef WAVEAUDIO_H
|
||||
#define WAVEAUDIO_H
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QFile>
|
||||
|
||||
#include "audio/sampleformat.h"
|
||||
#include "render/audioparams.h"
|
||||
|
||||
class WaveOutput
|
||||
{
|
||||
public:
|
||||
WaveOutput(const QString& f,
|
||||
const AudioRenderingParams& params);
|
||||
|
||||
~WaveOutput();
|
||||
|
||||
Q_DISABLE_COPY_MOVE(WaveOutput)
|
||||
|
||||
bool open();
|
||||
|
||||
void write(const QByteArray& bytes);
|
||||
void write(const char* bytes, int length);
|
||||
|
||||
void close();
|
||||
|
||||
private:
|
||||
template<typename T>
|
||||
void write_int(QFile* file, T integer);
|
||||
|
||||
void switch_endianness(QByteArray &array);
|
||||
|
||||
QFile file_;
|
||||
|
||||
AudioRenderingParams params_;
|
||||
|
||||
int data_length_;
|
||||
|
||||
};
|
||||
|
||||
#endif // WAVEAUDIO_H
|
||||
@@ -0,0 +1,6 @@
|
||||
#include "audioworker.h"
|
||||
|
||||
AudioWorker::AudioWorker()
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
#ifndef AUDIOWORKER_H
|
||||
#define AUDIOWORKER_H
|
||||
|
||||
|
||||
class AudioWorker
|
||||
{
|
||||
public:
|
||||
AudioWorker();
|
||||
};
|
||||
|
||||
#endif // AUDIOWORKER_H
|
||||
Reference in New Issue
Block a user