219 lines
6.5 KiB
C++
219 lines
6.5 KiB
C++
// Copyright (c) Charles J. Cliffe
|
|
// SPDX-License-Identifier: GPL-2.0+
|
|
|
|
#include "AudioFileWAV.h"
|
|
#include "CubicSDR.h"
|
|
#include <iomanip>
|
|
|
|
//limit file size to 2GB (- margin) for maximum compatibility.
|
|
#define MAX_WAV_FILE_SIZE (0x7FFFFFFF - 1024)
|
|
|
|
// Simple endian io read/write handling from
|
|
// http://www.cplusplus.com/forum/beginner/31584/#msg171056
|
|
namespace little_endian_io
|
|
{
|
|
template <typename Word>
|
|
std::ostream& write_word(std::ostream& outs, Word value, unsigned size = sizeof(Word)) {
|
|
for (; size; --size, value >>= 8) {
|
|
outs.put(static_cast <char> (value & 0xFF));
|
|
}
|
|
return outs;
|
|
}
|
|
|
|
template <typename Word>
|
|
std::istream& read_word(std::istream& ins, Word& value, unsigned size = sizeof(Word)) {
|
|
value = 0;
|
|
for (unsigned n = 0; n < size; ++n) {
|
|
value |= ins.get() << (8 * n);
|
|
}
|
|
return ins;
|
|
}
|
|
}
|
|
|
|
namespace big_endian_io
|
|
{
|
|
template <typename Word>
|
|
std::ostream& write_word(std::ostream& outs, Word value, unsigned size = sizeof(Word)) {
|
|
while (size) {
|
|
outs.put(static_cast <char> ((value >> (8 * --size)) & 0xFF));
|
|
}
|
|
return outs;
|
|
}
|
|
|
|
template <typename Word>
|
|
std::istream& read_word(std::istream& ins, Word& value, unsigned size = sizeof(Word)) {
|
|
for (value = 0; size; --size) {
|
|
value = (value << 8) | ins.get();
|
|
}
|
|
return ins;
|
|
}
|
|
}
|
|
|
|
using namespace little_endian_io;
|
|
|
|
AudioFileWAV::AudioFileWAV() : AudioFile(), dataChunkPos(0) {
|
|
}
|
|
|
|
AudioFileWAV::~AudioFileWAV() = default;
|
|
|
|
|
|
std::string AudioFileWAV::getExtension()
|
|
{
|
|
return "wav";
|
|
}
|
|
|
|
bool AudioFileWAV::writeToFile(AudioThreadInputPtr input)
|
|
{
|
|
if (!outputFileStream.is_open()) {
|
|
|
|
std::string ofName = getOutputFileName();
|
|
|
|
outputFileStream.open(ofName.c_str(), std::ios::binary);
|
|
currentFileSize = 0;
|
|
|
|
writeHeaderToFileStream(input);
|
|
}
|
|
|
|
size_t maxRoomInCurrentFileInSamples = getMaxWritableNumberOfSamples(input);
|
|
|
|
if (maxRoomInCurrentFileInSamples >= input->data.size()) {
|
|
writePayloadToFileStream(input, 0, input->data.size());
|
|
}
|
|
else {
|
|
//we complete the current file and open another:
|
|
writePayloadToFileStream(input, 0, maxRoomInCurrentFileInSamples);
|
|
|
|
closeFile();
|
|
|
|
// Open a new file with the next sequence number, and dump the rest of samples in it.
|
|
currentSequenceNumber++;
|
|
currentFileSize = 0;
|
|
|
|
std::string ofName = getOutputFileName();
|
|
outputFileStream.open(ofName.c_str(), std::ios::binary);
|
|
|
|
writeHeaderToFileStream(input);
|
|
writePayloadToFileStream(input, maxRoomInCurrentFileInSamples, input->data.size());
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool AudioFileWAV::closeFile()
|
|
{
|
|
if (outputFileStream.is_open()) {
|
|
size_t file_length = outputFileStream.tellp();
|
|
|
|
// Fix the data chunk header to contain the data size
|
|
outputFileStream.seekp(dataChunkPos + 4);
|
|
write_word(outputFileStream, file_length - (dataChunkPos + 8), 4);
|
|
|
|
// Fix the file header to contain the proper RIFF chunk size, which is (file size - 8) bytes
|
|
outputFileStream.seekp(0 + 4);
|
|
write_word(outputFileStream, file_length - 8, 4);
|
|
|
|
outputFileStream.close();
|
|
currentFileSize = 0;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void AudioFileWAV::writeHeaderToFileStream(const AudioThreadInputPtr& input) {
|
|
|
|
// Based on simple wav file output code from
|
|
// http://www.cplusplus.com/forum/beginner/166954/
|
|
|
|
// Write the wav file headers
|
|
outputFileStream << "RIFF----WAVEfmt "; // (chunk size to be filled in later)
|
|
write_word(outputFileStream, 16, 4); // no extension data
|
|
write_word(outputFileStream, 1, 2); // PCM - integer samples
|
|
write_word(outputFileStream, input->channels, 2); // channels
|
|
write_word(outputFileStream, input->sampleRate, 4); // samples per second (Hz)
|
|
write_word(outputFileStream, (input->sampleRate * 16 * input->channels) / 8, 4); // (Sample Rate * BitsPerSample * Channels) / 8
|
|
write_word(outputFileStream, input->channels * 2, 2); // data block size (size of integer samples, one for each channel, in bytes)
|
|
write_word(outputFileStream, 16, 2); // number of bits per sample (use a multiple of 8)
|
|
|
|
// Write the data chunk header
|
|
dataChunkPos = outputFileStream.tellp();
|
|
currentFileSize = dataChunkPos;
|
|
outputFileStream << "data----"; // (chunk size to be filled in later)
|
|
}
|
|
|
|
void AudioFileWAV::writePayloadToFileStream(const AudioThreadInputPtr& input, size_t startInputPosition, size_t endInputPosition) {
|
|
|
|
// Prevent clipping
|
|
float intScale = (input->peak < 1.0) ? 32767.0f : (32767.0f / input->peak);
|
|
|
|
if (input->channels == 1) {
|
|
for (size_t i = startInputPosition, iMax = endInputPosition; i < iMax; i++) {
|
|
|
|
write_word(outputFileStream, int(input->data[i] * intScale), 2);
|
|
|
|
currentFileSize += 2;
|
|
}
|
|
}
|
|
else if (input->channels == 2) {
|
|
for (size_t i = startInputPosition, iMax = endInputPosition / 2; i < iMax; i++) {
|
|
|
|
write_word(outputFileStream, int(input->data[i * 2] * intScale), 2);
|
|
write_word(outputFileStream, int(input->data[i * 2 + 1] * intScale), 2);
|
|
|
|
currentFileSize += 4;
|
|
}
|
|
}
|
|
}
|
|
|
|
size_t AudioFileWAV::getMaxWritableNumberOfSamples(const AudioThreadInputPtr& input) const {
|
|
|
|
long long remainingBytesInFile = (long long)(MAX_WAV_FILE_SIZE) - currentFileSize;
|
|
|
|
return (size_t)(remainingBytesInFile / (input->channels * 2));
|
|
|
|
}
|
|
|
|
void AudioFileWAV::setOutputFileName(std::string filename) {
|
|
|
|
if (filename != filenameBase) {
|
|
|
|
currentSequenceNumber = 0;
|
|
}
|
|
|
|
AudioFile::setOutputFileName(filename);
|
|
}
|
|
|
|
std::string AudioFileWAV::getOutputFileName() {
|
|
|
|
std::string recPath = wxGetApp().getConfig()->getRecordingPath();
|
|
|
|
// Strip any invalid characters from the name
|
|
std::string stripChars("<>:\"/\\|?*");
|
|
std::string filenameBaseSafe = filenameBase;
|
|
|
|
for (size_t i = 0, iMax = filenameBaseSafe.length(); i < iMax; i++) {
|
|
if (stripChars.find(filenameBaseSafe[i]) != std::string::npos) {
|
|
filenameBaseSafe.replace(i, 1, "_");
|
|
}
|
|
}
|
|
|
|
// Create output file name
|
|
std::stringstream outputFileName;
|
|
outputFileName << recPath << filePathSeparator << filenameBaseSafe;
|
|
|
|
//customized part: append a sequence number.
|
|
if (currentSequenceNumber > 0) {
|
|
outputFileName << "_" << std::setfill('0') << std::setw(3) << currentSequenceNumber;
|
|
}
|
|
|
|
int idx = 0;
|
|
|
|
// If the file exists; then find the next non-existing file in sequence.
|
|
std::string fileNameCandidate = outputFileName.str();
|
|
|
|
while (FILE *file = fopen((fileNameCandidate + "." + getExtension()).c_str(), "r")) {
|
|
fclose(file);
|
|
fileNameCandidate = outputFileName.str() + "-" + std::to_string(++idx);
|
|
}
|
|
|
|
return fileNameCandidate + "." + getExtension();
|
|
} |