diff options
author | japm48 <japm48@users.noreply.github.com> | 2020-04-14 18:28:18 +0200 |
---|---|---|
committer | Michael Dickens <michael.dickens@ettus.com> | 2020-04-14 15:47:29 -0400 |
commit | 7975fb0f63bd90954960658be80790b7a518e64f (patch) | |
tree | 53f376103c9dc93676368f5864c005e71c75097b | |
parent | d0a351a6fc2f018f36f6e89395f346707a059099 (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.h | 54 | ||||
-rw-r--r-- | gr-blocks/lib/wavfile.cc | 56 | ||||
-rw-r--r-- | gr-blocks/lib/wavfile_sink_impl.cc | 135 | ||||
-rw-r--r-- | gr-blocks/lib/wavfile_sink_impl.h | 20 | ||||
-rw-r--r-- | gr-blocks/lib/wavfile_source_impl.cc | 35 | ||||
-rw-r--r-- | gr-blocks/lib/wavfile_source_impl.h | 13 |
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, |