sintonia/library/ecasound-2.7.2/libecasound/audioio-wave.cpp

613 lines
18 KiB
C++

// ------------------------------------------------------------------------
// audioio-wave.cpp: RIFF WAVE audio file input/output.
// Copyright (C) 1999-2003,2005,2008,2009 Kai Vehmanen
//
// Attributes:
// eca-style-version: 3
//
// References:
// - http://ccrma.stanford.edu/courses/422/projects/WaveFormat/
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
// ------------------------------------------------------------------------
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <cstdio>
#include <cstring>
#include <cmath>
#include <string>
#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h> /* off_t */
#endif
#include <kvu_message_item.h>
#include <kvu_numtostr.h>
#include <kvu_dbc.h>
#include "sample-specs.h" /* for system endianess */
#include "samplebuffer.h"
#include "audioio-wave.h"
#include "eca-fileio-mmap.h"
#include "eca-fileio-stream.h"
#include "eca-logger.h"
/**
* Private macro definitions
*/
#ifndef UINT32_MAX
#define UINT32_MAX 4294967295U
#endif
#ifdef WORDS_BIGENDIAN
static const bool is_system_littleendian = false;
#else
static const bool is_system_littleendian = true;
#endif
/**
* Private function declarations
*/
static uint16_t little_endian_uint16(uint16_t arg);
static uint32_t little_endian_uint32(uint32_t arg);
/**
* Private function definitions
*/
static uint16_t little_endian_uint16(uint16_t arg)
{
if (is_system_littleendian != true) {
return ((arg >> 8) & 0x00ff) | ((arg << 8) & 0xff00);
}
return arg;
}
static uint32_t little_endian_uint32(uint32_t arg)
{
if (is_system_littleendian != true) {
return ((arg >> 24) & 0x000000ff) |
((arg >> 8) & 0x0000ff00) |
((arg << 8) & 0x00ff0000) |
((arg << 24) & 0xff000000);
}
return arg;
}
/**
* Public definitions
*/
/**
* Print extra debug information about RIFF header
* contents to stdout when opening files.
*/
// #define DEBUG_WAVE_HEADER
WAVEFILE::WAVEFILE (const std::string& name)
{
set_label(name);
fio_repp = 0;
mmaptoggle_rep = "0";
}
WAVEFILE::~WAVEFILE(void)
{
if (is_open() == true) {
close();
}
}
WAVEFILE* WAVEFILE::clone(void) const
{
WAVEFILE* target = new WAVEFILE();
for(int n = 0; n < number_of_params(); n++) {
target->set_parameter(n + 1, get_parameter(n + 1));
}
return target;
}
void WAVEFILE::format_query(void) throw(AUDIO_IO::SETUP_ERROR&)
{
// --------
DBC_REQUIRE(is_open() != true);
// --------
if (io_mode() == io_write) return;
fio_repp = new ECA_FILE_IO_STREAM();
if (fio_repp == 0) {
throw(SETUP_ERROR(SETUP_ERROR::io_mode, "AUDIOIO-WAVE: Critical error when opening file \"" + label() + "\" for reading."));
}
fio_repp->open_file(label(), "rb");
if (fio_repp->file_mode() != "") {
set_length_in_bytes();
read_riff_fmt(); // also sets format()
find_riff_datablock();
fio_repp->close_file();
}
delete fio_repp;
fio_repp = 0;
// -------
DBC_ENSURE(!is_open());
DBC_ENSURE(fio_repp == 0);
// -------
}
void WAVEFILE::open(void) throw (AUDIO_IO::SETUP_ERROR &)
{
switch(io_mode()) {
case io_read:
{
if (mmaptoggle_rep == "1") {
ECA_LOG_MSG(ECA_LOGGER::user_objects, "using mmap() mode for file access");
fio_repp = new ECA_FILE_IO_MMAP();
}
else fio_repp = new ECA_FILE_IO_STREAM();
if (fio_repp == 0) {
throw(SETUP_ERROR(SETUP_ERROR::io_mode, "AUDIOIO-WAVE: Critical error when opening file \"" + label() + "\" for reading."));
}
fio_repp->open_file(label(), "rb");
if (fio_repp->is_file_ready() != true) {
throw(SETUP_ERROR(SETUP_ERROR::io_mode, "AUDIOIO-WAVE: Couldn't open file \"" + label() + "\" for reading."));
}
read_riff_header();
read_riff_fmt(); // also sets format()
set_length_in_bytes();
find_riff_datablock();
break;
}
case io_write:
{
fio_repp = new ECA_FILE_IO_STREAM();
if (fio_repp == 0) {
throw(SETUP_ERROR(SETUP_ERROR::io_mode, "AUDIOIO-WAVE: Critical error when opening file \"" + label() + "\" for writing."));
}
fio_repp->open_file(label(), "w+b");
if (fio_repp->is_file_ready() != true) {
throw(SETUP_ERROR(SETUP_ERROR::io_mode, "AUDIOIO-WAVE: Couldn't open file \"" + label() + "\" for writing."));
}
write_riff_header();
write_riff_fmt();
write_riff_datablock();
break;
}
case io_readwrite:
{
fio_repp = new ECA_FILE_IO_STREAM();
if (fio_repp == 0) {
throw(SETUP_ERROR(SETUP_ERROR::io_mode, "AUDIOIO-WAVE: Critical error when opening file \"" + label() + "\" for read&write."));
}
fio_repp->open_file(label(), "r+b");
if (fio_repp->file_mode() != "") {
set_length_in_bytes();
read_riff_fmt(); // also sets format()
find_riff_datablock();
}
else {
fio_repp->open_file(label(), "w+b");
if (fio_repp->is_file_ready() != true)
throw(SETUP_ERROR(SETUP_ERROR::io_mode, "AUDIOIO-WAVE: Couldn't open file \"" + label() + "\" for read&write."));
write_riff_header();
write_riff_fmt();
write_riff_datablock();
}
if (fio_repp->is_file_ready() != true) {
throw(SETUP_ERROR(SETUP_ERROR::io_mode, "AUDIOIO-WAVE: Couldn't open file \"" + label() + "\" for read&write."));
}
}
}
if (little_endian_uint16(riff_format_rep.bits) > 8 &&
format_string()[0] == 'u')
throw(SETUP_ERROR(SETUP_ERROR::sample_format, "AUDIOIO-WAVE: unsigned sample format accepted only with 8bit."));
if (little_endian_uint16(riff_format_rep.bits) > 8 &&
format_string().size() > 4 &&
format_string()[4] == 'b') {
/* force little-endian operation / affects only write-mode */
set_sample_format_string(format_string()[0] + kvu_numtostr(bits()) + "_le");
ECA_LOG_MSG(ECA_LOGGER::user_objects, "forcing little-endian operation (" + format_string() + ")");
DBC_CHECK(format_string().size() > 4 && format_string()[4] != 'b');
}
AUDIO_IO::open();
}
void WAVEFILE::close(void)
{
ECA_LOG_MSG(ECA_LOGGER::user_objects,"Closing file " + label());
if (is_open() == true && fio_repp != 0) {
update();
fio_repp->close_file();
delete fio_repp;
fio_repp = 0;
}
AUDIO_IO::close();
}
void WAVEFILE::update (void)
{
if (io_mode() != io_read) {
update_riff_datablock();
write_riff_header();
set_length_in_bytes();
}
}
void WAVEFILE::find_riff_datablock (void) throw(AUDIO_IO::SETUP_ERROR&)
{
if (find_block("data", 0) != true) {
throw(ECA_ERROR("AUDIOIO-WAVE", "no RIFF data block found", ECA_ERROR::retry));
}
data_start_position_rep = fio_repp->get_file_position();
}
void WAVEFILE::read_riff_header (void) throw(AUDIO_IO::SETUP_ERROR&)
{
// ECA_LOG_MSG(ECA_LOGGER::user_objects, "read_riff_header()");
fio_repp->read_to_buffer(&riff_header_rep, sizeof(riff_header_rep));
// fread(&riff_header_rep,1,sizeof(riff_header_rep),fobject);
if ((memcmp("RIFF",riff_header_rep.id,4) == 0 &&
memcmp("WAVE",riff_header_rep.wname,4) == 0) != true) {
throw(SETUP_ERROR(SETUP_ERROR::unexpected, "AUDIOIO-WAVE: invalid RIFF-header (read)"));
}
}
void WAVEFILE::write_riff_header (void) throw(AUDIO_IO::SETUP_ERROR&)
{
ECA_LOG_MSG(ECA_LOGGER::user_objects, "write_riff_header()");
off_t savetemp = fio_repp->get_file_position();
memcpy(riff_header_rep.id,"RIFF",4);
memcpy(riff_header_rep.wname,"WAVE",4);
/* hack for 64bit wav files */
#if _FILE_OFFSET_BITS == 64
if (fio_repp->get_file_length() > static_cast<off_t>(UINT32_MAX))
riff_header_rep.size = little_endian_uint32(UINT32_MAX);
else
#endif
if (fio_repp->get_file_length() > static_cast<off_t>(sizeof(riff_header_rep)))
riff_header_rep.size = little_endian_uint32(fio_repp->get_file_length() - sizeof(riff_header_rep));
else
riff_header_rep.size = little_endian_uint32(0);
fio_repp->set_file_position(0);
// fseek(fobject,0,SEEK_SET);
fio_repp->write_from_buffer(&riff_header_rep, sizeof(riff_header_rep));
// fwrite(&riff_header_rep,1,sizeof(riff_header_rep),fobject);
if (memcmp("RIFF",riff_header_rep.id,4) != 0 ||
memcmp("WAVE",riff_header_rep.wname,4) != 0)
throw(SETUP_ERROR(SETUP_ERROR::unexpected, "AUDIOIO-WAVE: invalid RIFF-header (write)"));
ECA_LOG_MSG(ECA_LOGGER::user_objects, "Wave data size " + kvu_numtostr(little_endian_uint32(riff_header_rep.size)));
fio_repp->set_file_position(savetemp);
}
void WAVEFILE::read_riff_fmt(void) throw(AUDIO_IO::SETUP_ERROR&)
{
// ECA_LOG_MSG(ECA_LOGGER::user_objects, "read_riff_fmt()");
off_t savetemp = fio_repp->get_file_position();
if (find_block("fmt ", 0) != true)
throw(SETUP_ERROR(SETUP_ERROR::unexpected, "AUDIOIO-WAVE: no riff fmt-block found"));
else {
fio_repp->read_to_buffer(&riff_format_rep, sizeof(riff_format_rep));
// fread(&riff_format_rep,1,sizeof(riff_format_rep),fobject);
#ifdef DEBUG_WAVE_HEADER
std::cout << "RF: format = " << little_endian_uint16(riff_format_rep.format) << std::endl;
std::cout << "RF: channels = " << little_endian_uint16(riff_format_rep.channels) << std::endl;
std::cout << "RF: srate = " << little_endian_uint32(riff_format_rep.srate) << std::endl;
std::cout << "RF: byte_second = " << little_endian_uint32(riff_format_rep.byte_second) << std::endl;
std::cout << "RF: align = " << little_endian_uint16(riff_format_rep.align) << std::endl;
std::cout << "RF: bits = " << little_endian_uint16(riff_format_rep.bits) << std::endl;
#endif
if (little_endian_uint16(riff_format_rep.format) != 1 &&
little_endian_uint16(riff_format_rep.format) != 3) {
throw(SETUP_ERROR(SETUP_ERROR::sample_format, "AUDIOIO-WAVE: Only WAVE_FORMAT_PCM and WAVE_FORMAT_IEEE_FLOAT are supported."));
}
set_samples_per_second(little_endian_uint32(riff_format_rep.srate));
set_channels(little_endian_uint16(riff_format_rep.channels));
if (little_endian_uint16(riff_format_rep.bits) == 32) {
if (little_endian_uint16(riff_format_rep.format) == 3)
set_sample_format(ECA_AUDIO_FORMAT::sfmt_f32_le);
else
set_sample_format(ECA_AUDIO_FORMAT::sfmt_s32_le);
}
else if (little_endian_uint16(riff_format_rep.bits) == 24) {
if (riff_format_rep.align == little_endian_uint16(channels() * 3)) {
/* packet s24 format */
set_sample_format(ECA_AUDIO_FORMAT::sfmt_s24_le);
}
else if (riff_format_rep.align == little_endian_uint16(channels() * 4)) {
/* unpacked s24 format */
set_sample_format(ECA_AUDIO_FORMAT::sfmt_s32_le);
}
else {
throw(SETUP_ERROR(SETUP_ERROR::sample_format, "AUDIOIO-WAVE: Invalid 24bit sample format combination."));
}
}
else if (little_endian_uint16(riff_format_rep.bits) == 16)
set_sample_format(ECA_AUDIO_FORMAT::sfmt_s16_le);
else if (little_endian_uint16(riff_format_rep.bits) == 8)
set_sample_format(ECA_AUDIO_FORMAT::sfmt_u8);
else
throw(SETUP_ERROR(SETUP_ERROR::sample_format, "AUDIOIO-WAVE: Sample format not supported."));
}
DBC_CHECK(little_endian_uint16(riff_format_rep.channels) == channels());
DBC_CHECK(little_endian_uint16(riff_format_rep.bits) == bits());
DBC_CHECK(little_endian_uint32(riff_format_rep.srate) == static_cast<uint32_t>(samples_per_second()));
DBC_CHECK(little_endian_uint32(riff_format_rep.byte_second) == static_cast<uint32_t>(bytes_per_second()));
DBC_CHECK(little_endian_uint16(riff_format_rep.align) == frame_size());
fio_repp->set_file_position(savetemp);
}
void WAVEFILE::write_riff_fmt(void)
{
ECA_LOG_MSG(ECA_LOGGER::user_objects, "write_riff_fmt()");
RB fblock;
fio_repp->set_file_position_end();
riff_format_rep.channels = little_endian_uint16(channels());
riff_format_rep.bits = little_endian_uint16(bits());
riff_format_rep.srate = little_endian_uint32(samples_per_second());
riff_format_rep.byte_second = little_endian_uint32(bytes_per_second());
riff_format_rep.align = little_endian_uint16(frame_size());
if (sample_coding() == ECA_AUDIO_FORMAT::sc_float) {
// WAVE_FORMAT_IEEE_FLOAT 0x0003 (Microsoft IEEE754 range [-1, +1))
riff_format_rep.format = little_endian_uint16(3);
}
else {
// WAVE_FORMAT_PCM (0x0001) Microsoft Pulse Code Modulation (PCM) format
riff_format_rep.format = little_endian_uint16(1);
}
memcpy(fblock.sig, "fmt ", 4);
fblock.bsize = little_endian_uint32(16);
fio_repp->write_from_buffer(&fblock, sizeof(fblock));
fio_repp->write_from_buffer(&riff_format_rep, sizeof(riff_format_rep));
// ECA_LOG_MSG(ECA_LOGGER::user_objects, "Wrote RIFF format header.");
}
void WAVEFILE::write_riff_datablock(void)
{
ECA_LOG_MSG(ECA_LOGGER::user_objects, "write_riff_datablock()");
RB fblock;
// ECA_LOG_MSG(ECA_LOGGER::user_objects, "write_riff_datablock()");
fio_repp->set_file_position_end();
memcpy(fblock.sig,"data",4);
fblock.bsize = little_endian_uint32(0);
fio_repp->write_from_buffer(&fblock, sizeof(fblock));
data_start_position_rep = fio_repp->get_file_position();
}
void WAVEFILE::update_riff_datablock(void)
{
ECA_LOG_MSG(ECA_LOGGER::user_objects, "update_riff_datablock()");
RB fblock;
memcpy(fblock.sig,"data",4);
find_block("data", 0);
off_t savetemp = fio_repp->get_file_position();
fio_repp->set_file_position_end();
/* hack for wav files with length over 2^32-1 bytes */
#if _FILE_OFFSET_BITS == 64
if (fio_repp->get_file_position() - savetemp > static_cast<off_t>(UINT32_MAX))
fblock.bsize = little_endian_uint32(UINT32_MAX);
else
#endif
fblock.bsize = little_endian_uint32(fio_repp->get_file_position() - savetemp);
ECA_LOG_MSG(ECA_LOGGER::user_objects,
"updating data block header length to " +
kvu_numtostr(little_endian_uint32(fblock.bsize)));
savetemp = savetemp - sizeof(fblock);
if (savetemp > 0) {
fio_repp->set_file_position(savetemp);
fio_repp->write_from_buffer(&fblock, sizeof(fblock));
}
}
bool WAVEFILE::next_riff_block(RB *t, off_t *offtmp)
{
// ECA_LOG_MSG(ECA_LOGGER::user_objects, "next_riff_block()");
fio_repp->read_to_buffer(t, sizeof(RB));
if (fio_repp->file_bytes_processed() != sizeof(RB)) {
ECA_LOG_MSG(ECA_LOGGER::user_objects, "invalid RIFF block!");
return false;
}
if (!fio_repp->is_file_ready()) return false;
*offtmp = little_endian_uint32(t->bsize) + fio_repp->get_file_position();
return true;
}
bool WAVEFILE::find_block(const char* fblock, uint32_t* blksize)
{
off_t offset;
RB block;
// ECA_LOG_MSG(ECA_LOGGER::user_objects, "find_block(): " + string(fblock,4));
fio_repp->set_file_position(sizeof(riff_header_rep));
while(next_riff_block(&block,&offset)) {
// ECA_LOG_MSG(ECA_LOGGER::user_objects, "found RIFF-block ");
if (memcmp(block.sig,fblock,4) == 0) {
if (blksize != 0)
*blksize = little_endian_uint32(block.bsize);
return true;
}
fio_repp->set_file_position(offset);
}
return false;
}
bool WAVEFILE::finished(void) const
{
if (io_mode() == io_read &&
(length_set() == true &&
position_in_samples() >= length_in_samples())) {
return true;
}
if (fio_repp->is_file_error() ||
!fio_repp->is_file_ready())
return true;
return false;
}
long int WAVEFILE::read_samples(void* target_buffer, long int samples)
{
// --------
DBC_REQUIRE(samples >= 0);
DBC_REQUIRE(target_buffer != 0);
// --------
if (length_set() == true &&
position_in_samples() + samples >= length_in_samples())
samples = length_in_samples() - position_in_samples();
fio_repp->read_to_buffer(target_buffer, frame_size() * samples);
return fio_repp->file_bytes_processed() / frame_size();
}
void WAVEFILE::write_samples(void* target_buffer, long int samples)
{
// --------
DBC_REQUIRE(samples >= 0);
DBC_REQUIRE(target_buffer != 0);
// --------
/* note: When writing in write-update mode (i.e. modifying an
* existing file), we do not honor the previous length value
* in "data" header chunk. This may lead to overriding some
* non-data chunk at the end of the file.
*/
fio_repp->write_from_buffer(target_buffer, frame_size() * samples);
}
SAMPLE_SPECS::sample_pos_t WAVEFILE::seek_position(SAMPLE_SPECS::sample_pos_t pos)
{
if (is_open() == true) {
fio_repp->set_file_position(data_start_position_rep + pos * frame_size());
}
return pos;
}
void WAVEFILE::set_length_in_bytes(void)
{
off_t savetemp = fio_repp->get_file_position();
uint32_t blksize = 0;
find_block("data", &blksize);
off_t datastart = fio_repp->get_file_position();
fio_repp->set_file_position_end();
off_t datalen = fio_repp->get_file_position() - datastart;
/* note: If the audio stream length defined in "data" header
* block is zero (not updated), or it's set to maximum
* value, set the length according to actual file length.
*
* This is not strictly according to the RIFF WAVE spec,
* but allows to handle RIFF WAVE files with a broken
* header, as well as files with size exceeding the 2GiB
* limit. */
if (blksize != 0 &&
blksize != UINT32_MAX) {
set_length_in_samples(blksize / frame_size());
}
else
set_length_in_samples(datalen / frame_size());
ECA_LOG_MSG(ECA_LOGGER::user_objects,
"data block length in header " +
kvu_numtostr(blksize) +
", file length after data block " +
kvu_numtostr(datalen) +
", length set to " +
kvu_numtostr(length_in_samples()) +
" samples");
fio_repp->set_file_position(savetemp);
}
void WAVEFILE::set_parameter(int param,
string value)
{
switch (param) {
case 1:
set_label(value);
break;
case 2:
mmaptoggle_rep = value;
break;
}
}
string WAVEFILE::get_parameter(int param) const
{
switch (param) {
case 1:
return label();
case 2:
return mmaptoggle_rep;
}
return "";
}