summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjapm48 <japm48@users.noreply.github.com>2020-04-14 18:28:18 +0200
committerMichael Dickens <michael.dickens@ettus.com>2020-04-14 15:47:29 -0400
commit7975fb0f63bd90954960658be80790b7a518e64f (patch)
tree53f376103c9dc93676368f5864c005e71c75097b
parentd0a351a6fc2f018f36f6e89395f346707a059099 (diff)
blocks: refactor and reorganize WAV internals
This includes the following: - WAV header parameters are now stored in a struct. - do not assume fixed position of data chunk (useful for appending). - use INT{8,18}T_MAX/MIN. - 0|NULL -> nullptr.
-rw-r--r--gr-blocks/include/gnuradio/blocks/wavfile.h54
-rw-r--r--gr-blocks/lib/wavfile.cc56
-rw-r--r--gr-blocks/lib/wavfile_sink_impl.cc135
-rw-r--r--gr-blocks/lib/wavfile_sink_impl.h20
-rw-r--r--gr-blocks/lib/wavfile_source_impl.cc35
-rw-r--r--gr-blocks/lib/wavfile_source_impl.h13
6 files changed, 164 insertions, 149 deletions
diff --git a/gr-blocks/include/gnuradio/blocks/wavfile.h b/gr-blocks/include/gnuradio/blocks/wavfile.h
index de41c8b646..9f21834947 100644
--- a/gr-blocks/include/gnuradio/blocks/wavfile.h
+++ b/gr-blocks/include/gnuradio/blocks/wavfile.h
@@ -20,27 +20,43 @@
namespace gr {
namespace blocks {
+
+//! WAV file header information.
+struct wav_header_info {
+ // TODO: refactor to use correct types (int16/32, etc.).
+
+ //! sample rate [S/s]
+ unsigned sample_rate;
+
+ //! Number of channels
+ int nchans;
+
+ //! Bytes per sample
+ /** Can either be 1 or 2 (corresponding to 8 or 16 bit samples, respectively) */
+ int bytes_per_sample;
+
+ //! Number of the first byte containing a sample
+ /** Use this with fseek() to jump from the end of the file to the
+ * first sample when in repeat mode.
+ */
+ int first_sample_pos;
+
+ //! Number of samples per channel
+ unsigned samples_per_chan;
+
+ //! Size of DATA chunk
+ unsigned data_chunk_size;
+};
+
/*!
* \brief Read signal information from a given WAV file.
*
- * \param[in] fp File pointer to an opened, empty file.
- * \param[out] sample_rate Stores the sample rate [S/s]
- * \param[out] nchans Number of channels
- * \param[out] bytes_per_sample Bytes per sample, can either be 1 or 2 (corresponding o
- * 8 or 16 bit samples, respectively)
- * \param[out] first_sample_pos Number of the first byte containing a sample. Use this
- * with fseek() to jump from the end of the file to the
- * first sample when in repeat mode.
- * \param[out] samples_per_chan Number of samples per channel
+ * \param[in] fp File pointer to an opened, empty file.
+ * \param[out] info Parsed information.
* \return True on a successful read, false if the file could not be read or is
- * not a valid WAV file.
+ * not a valid or incompatible WAV file.
*/
-BLOCKS_API bool wavheader_parse(FILE* fp,
- unsigned int& sample_rate,
- int& nchans,
- int& bytes_per_sample,
- int& first_sample_pos,
- unsigned int& samples_per_chan);
+BLOCKS_API bool wavheader_parse(FILE* fp, wav_header_info& info);
/*!
* \brief Read one sample from an open WAV file at the current position.
@@ -79,10 +95,10 @@ BLOCKS_API void wav_write_sample(FILE* fp, short int sample, int bytes_per_sampl
* function (which shouldn't happen), you need to fseek() to the
* end of the file (or wherever).
*
- * \param[in] fp File pointer to an open WAV file with a blank header
- * \param[in] byte_count Length of all samples written to the file in bytes.
+ * \param[in] fp File pointer to an open WAV file with a blank header
+ * \param[in] first_sample_pos Position of the first sample in DATA chunk.
*/
-BLOCKS_API bool wavheader_complete(FILE* fp, unsigned int byte_count);
+BLOCKS_API bool wavheader_complete(FILE* fp, unsigned first_sample_pos);
} /* namespace blocks */
} /* namespace gr */
diff --git a/gr-blocks/lib/wavfile.cc b/gr-blocks/lib/wavfile.cc
index 846de48177..2558d5c454 100644
--- a/gr-blocks/lib/wavfile.cc
+++ b/gr-blocks/lib/wavfile.cc
@@ -17,6 +17,7 @@
#include <stdint.h>
#include <cstring>
+
namespace gr {
namespace blocks {
#define VALID_COMPRESSION_TYPE 0x0001
@@ -55,12 +56,7 @@ static inline uint32_t wav_to_host(uint32_t x) { return wtohl(x); }
static inline uint16_t wav_to_host(uint16_t x) { return wtohs(x); }
static inline int16_t wav_to_host(int16_t x) { return wtohs(x); }
-bool wavheader_parse(FILE* fp,
- unsigned int& sample_rate_o,
- int& nchans_o,
- int& bytes_per_sample_o,
- int& first_sample_pos_o,
- unsigned int& samples_per_chan_o)
+bool wavheader_parse(FILE* fp, wav_header_info& info)
{
// _o variables take return values
char str_buf[8] = { 0 };
@@ -167,11 +163,13 @@ bool wavheader_parse(FILE* fp,
chunk_size = wav_to_host(chunk_size);
// Output values
- sample_rate_o = (unsigned)sample_rate;
- nchans_o = (int)nchans;
- bytes_per_sample_o = (int)(bits_per_sample / 8);
- first_sample_pos_o = (int)ftell(fp);
- samples_per_chan_o = (unsigned)(chunk_size / (bytes_per_sample_o * nchans));
+ info.sample_rate = (unsigned)sample_rate;
+ info.nchans = (int)nchans;
+ info.bytes_per_sample = (int)(bits_per_sample / 8);
+ info.first_sample_pos = (int)ftell(fp);
+ info.samples_per_chan = (unsigned)(chunk_size / (info.bytes_per_sample * nchans));
+ info.data_chunk_size = (unsigned)chunk_size;
+
return true;
}
@@ -241,30 +239,40 @@ void wav_write_sample(FILE* fp, short int sample, int bytes_per_sample)
fwrite(data_ptr, 1, bytes_per_sample, fp);
}
-
-bool wavheader_complete(FILE* fp, unsigned int byte_count)
+inline bool fwrite_field_32(FILE* fp, long position, uint32_t data)
{
- uint32_t chunk_size = (uint32_t)byte_count;
- chunk_size = host_to_wav(chunk_size);
-
- if (fseek(fp, 40, SEEK_SET) != 0) {
+ data = host_to_wav(data);
+ if (fseek(fp, position, SEEK_SET) != 0) {
return false;
}
- fwrite(&chunk_size, 1, 4, fp);
- chunk_size = (uint32_t)byte_count + 36; // fmt chunk and data header
- chunk_size = host_to_wav(chunk_size);
- if (fseek(fp, 4, SEEK_SET) != 0) {
+ return 4 == fwrite(&data, 1, 4, fp);
+}
+
+bool wavheader_complete(FILE* fp, unsigned first_sample_pos)
+{
+ fseek(fp, 0L, SEEK_END);
+ long real_file_size = ftell(fp);
+
+ if (first_sample_pos >= real_file_size) {
return false;
}
- fwrite(&chunk_size, 1, 4, fp);
+ uint32_t field_data;
+ bool ok;
- if (ferror(fp)) {
+ // Write "data chunk size".
+ field_data = uint32_t(real_file_size - first_sample_pos);
+ ok = fwrite_field_32(fp, first_sample_pos - 4, field_data);
+ if (!ok) {
return false;
}
- return true;
+ // Write "total file size" - 8
+ field_data = uint32_t(real_file_size - 8);
+ ok = fwrite_field_32(fp, 4, field_data);
+
+ return ok;
}
} /* namespace blocks */
diff --git a/gr-blocks/lib/wavfile_sink_impl.cc b/gr-blocks/lib/wavfile_sink_impl.cc
index 69fb8d826d..431919e7ed 100644
--- a/gr-blocks/lib/wavfile_sink_impl.cc
+++ b/gr-blocks/lib/wavfile_sink_impl.cc
@@ -48,8 +48,8 @@ wavfile_sink::sptr wavfile_sink::make(const char* filename,
unsigned int sample_rate,
int bits_per_sample)
{
- return gnuradio::get_initial_sptr(
- new wavfile_sink_impl(filename, n_channels, sample_rate, bits_per_sample));
+ return gnuradio::get_initial_sptr(new wavfile_sink_impl(
+ filename, n_channels, sample_rate, bits_per_sample));
}
wavfile_sink_impl::wavfile_sink_impl(const char* filename,
@@ -59,32 +59,20 @@ wavfile_sink_impl::wavfile_sink_impl(const char* filename,
: sync_block("wavfile_sink",
io_signature::make(1, n_channels, sizeof(float)),
io_signature::make(0, 0, 0)),
- d_sample_rate(sample_rate),
- d_nchans(n_channels),
- d_fp(0),
- d_new_fp(0),
+ d_h{}, // Init with zeros
+ d_fp(nullptr),
+ d_new_fp(nullptr),
d_updated(false)
{
- if (bits_per_sample != 8 && bits_per_sample != 16) {
- throw std::runtime_error("Invalid bits per sample (supports 8 and 16)");
- }
- d_bytes_per_sample = bits_per_sample / 8;
- d_bytes_per_sample_new = d_bytes_per_sample;
+ d_h.sample_rate = sample_rate;
+ d_h.nchans = n_channels;
- if (!open(filename)) {
- throw std::runtime_error("can't open file");
- }
+ set_bits_per_sample_unlocked(bits_per_sample);
- if (bits_per_sample == 8) {
- d_max_sample_val = 0xFF;
- d_min_sample_val = 0;
- d_normalize_fac = d_max_sample_val / 2;
- d_normalize_shift = 1;
- } else {
- d_max_sample_val = 0x7FFF;
- d_min_sample_val = -0x7FFF;
- d_normalize_fac = d_max_sample_val;
- d_normalize_shift = 0;
+ d_h.bytes_per_sample = d_bytes_per_sample_new;
+
+ if (!open(filename)) {
+ throw std::runtime_error("can't open WAV file");
}
}
@@ -93,10 +81,10 @@ bool wavfile_sink_impl::open(const char* filename)
gr::thread::scoped_lock guard(d_mutex);
// we use the open system call to get access to the O_LARGEFILE flag.
+ int flags = OUR_O_LARGEFILE | OUR_O_BINARY | O_CREAT | O_WRONLY | O_TRUNC;
int fd;
- if ((fd = ::open(filename,
- O_WRONLY | O_CREAT | O_TRUNC | OUR_O_LARGEFILE | OUR_O_BINARY,
- 0664)) < 0) {
+
+ if ((fd = ::open(filename, flags, 0664)) < 0) {
GR_LOG_ERROR(d_logger,
boost::format("::open: %s: %s") % filename % strerror(errno));
return false;
@@ -104,23 +92,27 @@ bool wavfile_sink_impl::open(const char* filename)
if (d_new_fp) { // if we've already got a new one open, close it
fclose(d_new_fp);
- d_new_fp = 0;
+ d_new_fp = nullptr;
}
- if ((d_new_fp = fdopen(fd, "wb")) == NULL) {
+ if (!(d_new_fp = fdopen(fd, "wb"))) {
GR_LOG_ERROR(d_logger,
boost::format("fdopen: %s: %s") % filename % strerror(errno));
+
::close(fd); // don't leak file descriptor if fdopen fails.
return false;
}
- d_updated = true;
- if (!wavheader_write(d_new_fp, d_sample_rate, d_nchans, d_bytes_per_sample_new)) {
- GR_LOG_ERROR(d_logger,
- boost::format("could not write to WAV file: %s") % strerror(errno))
- exit(-1);
+ d_h.first_sample_pos = 44;
+ if (!wavheader_write(
+ d_new_fp, d_h.sample_rate, d_h.nchans, d_bytes_per_sample_new)) {
+ GR_LOG_ERROR(d_logger, boost::format("could not save WAV header"));
+ fclose(d_new_fp);
+ return false;
}
+ d_updated = true;
+
return true;
}
@@ -136,12 +128,12 @@ void wavfile_sink_impl::close()
void wavfile_sink_impl::close_wav()
{
- unsigned int byte_count = d_sample_count * d_bytes_per_sample;
-
- wavheader_complete(d_fp, byte_count);
+ if (!wavheader_complete(d_fp, d_h.first_sample_pos)) {
+ GR_LOG_ERROR(d_logger, boost::format("could not save WAV header"));
+ }
fclose(d_fp);
- d_fp = NULL;
+ d_fp = nullptr;
}
wavfile_sink_impl::~wavfile_sink_impl() { stop(); }
@@ -150,7 +142,7 @@ bool wavfile_sink_impl::stop()
{
if (d_new_fp) {
fclose(d_new_fp);
- d_new_fp = NULL;
+ d_new_fp = nullptr;
}
close();
@@ -162,7 +154,7 @@ int wavfile_sink_impl::work(int noutput_items,
gr_vector_const_void_star& input_items,
gr_vector_void_star& output_items)
{
- float** in = (float**)&input_items[0];
+ auto in = (float**)&input_items[0];
int n_in_chans = input_items.size();
short int sample_buf_s;
@@ -170,12 +162,14 @@ int wavfile_sink_impl::work(int noutput_items,
int nwritten;
gr::thread::scoped_lock guard(d_mutex); // hold mutex for duration of this block
- do_update(); // update: d_fp is reqd
+ do_update(); // update: d_fp is read
if (!d_fp) // drop output on the floor
return noutput_items;
+ int nchans = d_h.nchans;
+ int bytes_per_sample = d_h.bytes_per_sample;
for (nwritten = 0; nwritten < noutput_items; nwritten++) {
- for (int chan = 0; chan < d_nchans; chan++) {
+ for (int chan = 0; chan < nchans; chan++) {
// Write zeros to channels which are in the WAV file
// but don't have any inputs here
if (chan < n_in_chans) {
@@ -184,7 +178,7 @@ int wavfile_sink_impl::work(int noutput_items,
sample_buf_s = 0;
}
- wav_write_sample(d_fp, sample_buf_s, d_bytes_per_sample);
+ wav_write_sample(d_fp, sample_buf_s, bytes_per_sample);
if (feof(d_fp) || ferror(d_fp)) {
GR_LOG_ERROR(d_logger,
@@ -192,7 +186,6 @@ int wavfile_sink_impl::work(int noutput_items,
close();
exit(-1);
}
- d_sample_count++;
}
}
@@ -203,11 +196,9 @@ short int wavfile_sink_impl::convert_to_short(float sample)
{
sample += d_normalize_shift;
sample *= d_normalize_fac;
- if (sample > d_max_sample_val) {
- sample = d_max_sample_val;
- } else if (sample < d_min_sample_val) {
- sample = d_min_sample_val;
- }
+
+ // In C++17, use std::clamp.
+ sample = std::max(std::min(sample, d_max_sample_val), d_min_sample_val);
return (short int)boost::math::iround(sample);
}
@@ -215,20 +206,39 @@ short int wavfile_sink_impl::convert_to_short(float sample)
void wavfile_sink_impl::set_bits_per_sample(int bits_per_sample)
{
gr::thread::scoped_lock guard(d_mutex);
- if (bits_per_sample == 8 || bits_per_sample == 16) {
- d_bytes_per_sample_new = bits_per_sample / 8;
+ set_bits_per_sample_unlocked(bits_per_sample);
+}
+
+void wavfile_sink_impl::set_bits_per_sample_unlocked(int bits_per_sample)
+{
+ switch (bits_per_sample) {
+ case 8:
+ d_max_sample_val = float(UINT8_MAX);
+ d_min_sample_val = float(0);
+ d_normalize_fac = d_max_sample_val / 2;
+ d_normalize_shift = 1;
+ break;
+ case 16:
+ d_max_sample_val = float(INT16_MAX);
+ d_min_sample_val = float(INT16_MIN);
+ d_normalize_fac = d_max_sample_val;
+ d_normalize_shift = 0;
+ break;
+ default:
+ throw std::runtime_error("Invalid bits per sample (only 8 and 16 are supported)");
}
+ d_bytes_per_sample_new = bits_per_sample / 8;
}
void wavfile_sink_impl::set_sample_rate(unsigned int sample_rate)
{
gr::thread::scoped_lock guard(d_mutex);
- d_sample_rate = sample_rate;
+ d_h.sample_rate = sample_rate;
}
int wavfile_sink_impl::bits_per_sample() { return d_bytes_per_sample_new; }
-unsigned int wavfile_sink_impl::sample_rate() { return d_sample_rate; }
+unsigned int wavfile_sink_impl::sample_rate() { return d_h.sample_rate; }
void wavfile_sink_impl::do_update()
{
@@ -241,22 +251,11 @@ void wavfile_sink_impl::do_update()
}
d_fp = d_new_fp; // install new file pointer
- d_new_fp = 0;
- d_sample_count = 0;
- d_bytes_per_sample = d_bytes_per_sample_new;
-
- if (d_bytes_per_sample == 1) {
- d_max_sample_val = UCHAR_MAX;
- d_min_sample_val = 0;
- d_normalize_fac = d_max_sample_val / 2;
- d_normalize_shift = 1;
- } else if (d_bytes_per_sample == 2) {
- d_max_sample_val = SHRT_MAX;
- d_min_sample_val = SHRT_MIN;
- d_normalize_fac = d_max_sample_val;
- d_normalize_shift = 0;
- }
+ d_new_fp = nullptr;
+ d_h.bytes_per_sample = d_bytes_per_sample_new;
+ // Avoid deadlock.
+ set_bits_per_sample_unlocked(8 * d_bytes_per_sample_new);
d_updated = false;
}
diff --git a/gr-blocks/lib/wavfile_sink_impl.h b/gr-blocks/lib/wavfile_sink_impl.h
index 2d1a76845a..c81c9658a7 100644
--- a/gr-blocks/lib/wavfile_sink_impl.h
+++ b/gr-blocks/lib/wavfile_sink_impl.h
@@ -11,6 +11,7 @@
#ifndef INCLUDED_GR_WAVFILE_SINK_IMPL_H
#define INCLUDED_GR_WAVFILE_SINK_IMPL_H
+#include <gnuradio/blocks/wavfile.h>
#include <gnuradio/blocks/wavfile_sink.h>
namespace gr {
@@ -19,15 +20,13 @@ namespace blocks {
class wavfile_sink_impl : public wavfile_sink
{
private:
- unsigned d_sample_rate;
- const int d_nchans;
- unsigned d_sample_count;
- int d_bytes_per_sample;
+ wav_header_info d_h;
int d_bytes_per_sample_new;
- int d_max_sample_val;
- int d_min_sample_val;
- int d_normalize_shift;
- int d_normalize_fac;
+
+ float d_max_sample_val;
+ float d_min_sample_val;
+ float d_normalize_shift;
+ float d_normalize_fac;
FILE* d_fp;
FILE* d_new_fp;
@@ -48,6 +47,11 @@ private:
void do_update();
/*!
+ * \brief Implementation of set_bits_per_sample without mutex lock.
+ */
+ void set_bits_per_sample_unlocked(int bits_per_sample);
+
+ /*!
* \brief Writes information to the WAV header which is not available
* a-priori (chunk size etc.) and closes the file. Not thread-safe and
* assumes d_fp is a valid file pointer, should thus only be called by
diff --git a/gr-blocks/lib/wavfile_source_impl.cc b/gr-blocks/lib/wavfile_source_impl.cc
index 8c17724393..18ae74eb4b 100644
--- a/gr-blocks/lib/wavfile_source_impl.cc
+++ b/gr-blocks/lib/wavfile_source_impl.cc
@@ -49,15 +49,10 @@ wavfile_source_impl::wavfile_source_impl(const char* filename, bool repeat)
io_signature::make(1, 2, sizeof(float))),
d_fp(NULL),
d_repeat(repeat),
- d_sample_rate(1),
- d_nchans(1),
- d_bytes_per_sample(2),
- d_first_sample_pos(0),
- d_samples_per_chan(0),
+ d_h{}, // Init with zeros
d_sample_idx(0)
{
// we use "open" to use to the O_LARGEFILE flag
-
int fd;
if ((fd = ::open(filename, O_RDONLY | OUR_O_LARGEFILE | OUR_O_BINARY)) < 0) {
GR_LOG_ERROR(d_logger,
@@ -72,29 +67,25 @@ wavfile_source_impl::wavfile_source_impl(const char* filename, bool repeat)
}
// Scan headers, check file validity
- if (!wavheader_parse(d_fp,
- d_sample_rate,
- d_nchans,
- d_bytes_per_sample,
- d_first_sample_pos,
- d_samples_per_chan)) {
+ if (!wavheader_parse(d_fp, d_h)) {
throw std::runtime_error("is not a valid wav file");
}
- if (d_samples_per_chan == 0) {
+
+ if (d_h.samples_per_chan == 0) {
throw std::runtime_error("WAV file does not contain any samples");
}
- if (d_bytes_per_sample == 1) {
- d_normalize_fac = 128;
+ if (d_h.bytes_per_sample == 1) {
+ d_normalize_fac = UINT8_MAX / 2;
d_normalize_shift = 1;
} else {
- d_normalize_fac = 0x7FFF;
+ d_normalize_fac = INT16_MAX;
d_normalize_shift = 0;
}
// Re-set the output signature
- set_output_signature(io_signature::make(1, d_nchans, sizeof(float)));
+ set_output_signature(io_signature::make(1, d_h.nchans, sizeof(float)));
}
wavfile_source_impl::~wavfile_source_impl() { fclose(d_fp); }
@@ -111,20 +102,20 @@ int wavfile_source_impl::work(int noutput_items,
gr_vector_const_void_star& input_items,
gr_vector_void_star& output_items)
{
- float** out = (float**)&output_items[0];
+ auto out = (float**)&output_items[0];
int n_out_chans = output_items.size();
int i;
short sample;
for (i = 0; i < noutput_items; i++) {
- if (d_sample_idx >= d_samples_per_chan) {
+ if (d_sample_idx >= d_h.samples_per_chan) {
if (!d_repeat) {
// if nothing was read at all, say we're done.
return i ? i : -1;
}
- if (fseek(d_fp, d_first_sample_pos, SEEK_SET) == -1) {
+ if (fseek(d_fp, d_h.first_sample_pos, SEEK_SET) == -1) {
GR_LOG_ERROR(d_logger,
boost::format("fseek failed %s") % strerror(errno));
exit(-1);
@@ -133,8 +124,8 @@ int wavfile_source_impl::work(int noutput_items,
d_sample_idx = 0;
}
- for (int chan = 0; chan < d_nchans; chan++) {
- sample = wav_read_sample(d_fp, d_bytes_per_sample);
+ for (int chan = 0; chan < d_h.nchans; chan++) {
+ sample = wav_read_sample(d_fp, d_h.bytes_per_sample);
if (chan < n_out_chans) {
out[chan][i] = convert_to_float(sample);
diff --git a/gr-blocks/lib/wavfile_source_impl.h b/gr-blocks/lib/wavfile_source_impl.h
index ce56f8bb8d..8504413fec 100644
--- a/gr-blocks/lib/wavfile_source_impl.h
+++ b/gr-blocks/lib/wavfile_source_impl.h
@@ -11,6 +11,7 @@
#ifndef INCLUDED_GR_WAVFILE_SOURCE_IMPL_H
#define INCLUDED_GR_WAVFILE_SOURCE_IMPL_H
+#include <gnuradio/blocks/wavfile.h>
#include <gnuradio/blocks/wavfile_source.h>
#include <cstdio> // for FILE
@@ -23,11 +24,7 @@ private:
FILE* d_fp;
bool d_repeat;
- unsigned d_sample_rate;
- int d_nchans;
- int d_bytes_per_sample;
- int d_first_sample_pos;
- unsigned d_samples_per_chan;
+ wav_header_info d_h;
unsigned d_sample_idx;
int d_normalize_shift;
int d_normalize_fac;
@@ -41,11 +38,11 @@ public:
wavfile_source_impl(const char* filename, bool repeat);
~wavfile_source_impl();
- unsigned int sample_rate() const { return d_sample_rate; };
+ unsigned int sample_rate() const { return d_h.sample_rate; };
- int bits_per_sample() const { return d_bytes_per_sample * 8; };
+ int bits_per_sample() const { return d_h.bytes_per_sample * 8; };
- int channels() const { return d_nchans; };
+ int channels() const { return d_h.nchans; };
int work(int noutput_items,
gr_vector_const_void_star& input_items,