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:
itsmattkc
2019-11-15 13:49:40 +09:00
parent 7a6dd02e5b
commit 9a44e656b5
7 changed files with 422 additions and 0 deletions
View File
+182
View File
@@ -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;
}
+38
View File
@@ -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
+144
View File
@@ -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);
}
+41
View File
@@ -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
+6
View File
@@ -0,0 +1,6 @@
#include "audioworker.h"
AudioWorker::AudioWorker()
{
}
+11
View File
@@ -0,0 +1,11 @@
#ifndef AUDIOWORKER_H
#define AUDIOWORKER_H
class AudioWorker
{
public:
AudioWorker();
};
#endif // AUDIOWORKER_H