diff options
Diffstat (limited to 'gr-blocks')
-rw-r--r-- | gr-blocks/CMakeLists.txt | 4 | ||||
-rw-r--r-- | gr-blocks/grc/blocks_wavfile_sink.block.yml | 105 | ||||
-rw-r--r-- | gr-blocks/include/gnuradio/blocks/wavfile.h | 90 | ||||
-rw-r--r-- | gr-blocks/include/gnuradio/blocks/wavfile_sink.h | 8 | ||||
-rw-r--r-- | gr-blocks/lib/CMakeLists.txt | 2 | ||||
-rw-r--r-- | gr-blocks/lib/wavfile.cc | 279 | ||||
-rw-r--r-- | gr-blocks/lib/wavfile_sink_impl.cc | 311 | ||||
-rw-r--r-- | gr-blocks/lib/wavfile_sink_impl.h | 23 | ||||
-rw-r--r-- | gr-blocks/lib/wavfile_source_impl.cc | 117 | ||||
-rw-r--r-- | gr-blocks/lib/wavfile_source_impl.h | 15 | ||||
-rw-r--r-- | gr-blocks/python/blocks/bindings/CMakeLists.txt | 7 | ||||
-rw-r--r-- | gr-blocks/python/blocks/bindings/docstrings/wavfile_sink_pydoc_template.h | 3 | ||||
-rw-r--r-- | gr-blocks/python/blocks/bindings/wavfile_python.cc | 62 | ||||
-rw-r--r-- | gr-blocks/python/blocks/bindings/wavfile_sink_python.cc | 16 | ||||
-rw-r--r-- | gr-blocks/python/blocks/qa_wavfile.py | 19 | ||||
-rw-r--r-- | gr-blocks/python/blocks/test_16bit_1chunk.wav | bin | 74 -> 74 bytes | |||
-rw-r--r-- | gr-blocks/python/blocks/test_16bit_1chunk_normal.wav | bin | 0 -> 52 bytes |
17 files changed, 404 insertions, 657 deletions
diff --git a/gr-blocks/CMakeLists.txt b/gr-blocks/CMakeLists.txt index 204f98a266..f584bf0d6b 100644 --- a/gr-blocks/CMakeLists.txt +++ b/gr-blocks/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright 2012 Free Software Foundation, Inc. +# Copyright 2012,2020 Free Software Foundation, Inc. # # This file is part of GNU Radio # @@ -10,6 +10,8 @@ ######################################################################## include(GrBoost) +find_package(SNDFILE) + ######################################################################## # Register component ######################################################################## diff --git a/gr-blocks/grc/blocks_wavfile_sink.block.yml b/gr-blocks/grc/blocks_wavfile_sink.block.yml index 4fd6aef5e5..fb7966d01c 100644 --- a/gr-blocks/grc/blocks_wavfile_sink.block.yml +++ b/gr-blocks/grc/blocks_wavfile_sink.block.yml @@ -15,16 +15,62 @@ parameters: label: Sample Rate dtype: int default: samp_rate -- id: bits_per_sample +- id: format + label: Output Format + dtype: enum + options: [FORMAT_WAV, FORMAT_FLAC, FORMAT_OGG, FORMAT_RF64] + option_labels: [WAV, FLAC, Ogg Vorbis, 64-bit WAV] + default: FORMAT_WAV + option_attributes: + hide_wav: [none, all, all, all] + hide_flac: [all, none, all, all] + hide_ogg: [all, all, none, all] + hide_rf64: [all, all, all, none] + hide_append: [none, all, all, none] + val: [blocks.FORMAT_WAV, blocks.FORMAT_FLAC, blocks.FORMAT_OGG, blocks.FORMAT_RF64] +- id: bits_per_sample1 label: Bits per Sample - dtype: int - default: '8' + dtype: enum + options: [FORMAT_PCM_16, FORMAT_PCM_24, FORMAT_PCM_32, FORMAT_PCM_U8, FORMAT_FLOAT, FORMAT_DOUBLE] + option_labels: [16-bit, 24-bit, 32-bit, unsigned 8-bit, float, double] + default: FORMAT_PCM_16 + option_attributes: + val: [blocks.FORMAT_PCM_16, blocks.FORMAT_PCM_24, blocks.FORMAT_PCM_32, blocks.FORMAT_PCM_U8, blocks.FORMAT_FLOAT, blocks.FORMAT_DOUBLE] + hide: ${ format.hide_wav } +- id: bits_per_sample2 + label: Bits per Sample + dtype: enum + options: [FORMAT_PCM_S8, FORMAT_PCM_16, FORMAT_PCM_24] + option_labels: [signed 8-bit, 16-bit, 24-bit] + default: FORMAT_PCM_16 + option_attributes: + val: [blocks.FORMAT_PCM_S8, blocks.FORMAT_PCM_16, blocks.FORMAT_PCM_24] + hide: ${ format.hide_flac } +- id: bits_per_sample3 + label: Bits per Sample + dtype: enum + options: [FORMAT_VORBIS] + option_labels: [Vorbis] + default: FORMAT_VORBIS + option_attributes: + val: [blocks.FORMAT_VORBIS] + hide: ${ format.hide_ogg } +- id: bits_per_sample4 + label: Bits per Sample + dtype: enum + options: [FORMAT_PCM_16, FORMAT_PCM_24, FORMAT_PCM_32, FORMAT_PCM_U8, FORMAT_FLOAT, FORMAT_DOUBLE] + option_labels: [16-bit, 24-bit, 32-bit, unsigned 8-bit, float, double] + default: FORMAT_PCM_16 + option_attributes: + val: [blocks.FORMAT_PCM_16, blocks.FORMAT_PCM_24, blocks.FORMAT_PCM_32, blocks.FORMAT_PCM_U8, blocks.FORMAT_FLOAT, blocks.FORMAT_DOUBLE] + hide: ${ format.hide_rf64 } - id: append label: Append to existing file dtype: enum default: 'False' options: ['True', 'False'] option_labels: ['Yes', 'No'] + hide: ${ format.hide_append } inputs: - domain: stream @@ -36,7 +82,32 @@ asserts: templates: imports: from gnuradio import blocks - make: blocks.wavfile_sink(${file}, ${nchan}, ${samp_rate}, ${bits_per_sample}, ${append}) + make: |- + blocks.wavfile_sink( + ${file}, + ${nchan}, + ${samp_rate}, + ${format.val}, + % if str(format) == 'FORMAT_WAV': + ${bits_per_sample1.val}, + % elif str(format) == 'FORMAT_FLAC': + ${bits_per_sample2.val}, + % elif str(format) == 'FORMAT_OGG': + ${bits_per_sample3.val}, + % elif str(format) == 'FORMAT_RF64': + ${bits_per_sample4.val}, + % endif + % if str(format) == 'FORMAT_WAV': + ${append} + % elif str(format) == 'FORMAT_FLAC': + ${False} + % elif str(format) == 'FORMAT_OGG': + ${False}, + % elif str(format) == 'FORMAT_RF64': + ${append} + % endif + ) + callbacks: - open(${file}) @@ -44,7 +115,31 @@ cpp_templates: includes: ['#include <gnuradio/blocks/wavfile_sink.h>'] declarations: 'blocks::wavfile_sink::sptr ${id};' make: |- - this->${id} = blocks::wavfile_sink::make(${file}${'.c_str()' if str(file)[0] != "'" and str(file)[0] != "\"" else ''}, ${nchan}, ${samp_rate}, ${bits_per_sample}, ${append}); + this->${id} = blocks::wavfile_sink::make( + ${file}, + ${nchan}, + ${samp_rate}, + ${format.val}, + % if str(format) == 'FORMAT_WAV': + ${bits_per_sample1.val}, + % elif str(format) == 'FORMAT_FLAC': + ${bits_per_sample2.val}, + % elif str(format) == 'FORMAT_OGG': + ${bits_per_sample3.val}, + % elif str(format) == 'FORMAT_RF64': + ${bits_per_sample4.val}, + % endif + % if str(format) == 'FORMAT_WAV': + ${append} + % elif str(format) == 'FORMAT_FLAC': + ${False} + % elif str(format) == 'FORMAT_OGG': + ${False}, + % elif str(format) == 'FORMAT_RF64': + ${append} + % endif + ); + callbacks: ## TODO Handle std::string type when const char* argument is needed - this->${id}->open(${file}) diff --git a/gr-blocks/include/gnuradio/blocks/wavfile.h b/gr-blocks/include/gnuradio/blocks/wavfile.h index 9f21834947..578900bf30 100644 --- a/gr-blocks/include/gnuradio/blocks/wavfile.h +++ b/gr-blocks/include/gnuradio/blocks/wavfile.h @@ -14,93 +14,55 @@ #ifndef _GR_WAVFILE_H_ #define _GR_WAVFILE_H_ -#include <gnuradio/blocks/api.h> #include <cstdio> 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; + int 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] info Parsed information. - * \return True on a successful read, false if the file could not be read or is - * not a valid or incompatible WAV file. - */ -BLOCKS_API bool wavheader_parse(FILE* fp, wav_header_info& info); - -/*! - * \brief Read one sample from an open WAV file at the current position. - * - * \details - * Takes care of endianness. - */ -BLOCKS_API short int wav_read_sample(FILE* fp, int bytes_per_sample); - + long long samples_per_chan; -/*! - * \brief Write a valid RIFF file header - * - * Note: Some header values are kept blank because they're usually - * not known a-priori (file and chunk lengths). Use - * gri_wavheader_complete() to fill these in. - */ -BLOCKS_API bool -wavheader_write(FILE* fp, unsigned int sample_rate, int nchans, int bytes_per_sample); + //! sndfile format + int format; -/*! - * \brief Write one sample to an open WAV file at the current position. - * - * \details - * Takes care of endianness. - */ -BLOCKS_API void wav_write_sample(FILE* fp, short int sample, int bytes_per_sample); + //! sndfile format + int subformat; +}; +enum wavfile_format_t { + FORMAT_WAV = 0x010000, + FORMAT_FLAC = 0x170000, + FORMAT_OGG = 0x200000, + FORMAT_RF64 = 0x220000, +}; -/*! - * \brief Complete a WAV header - * - * \details - * Note: The stream position is changed during this function. If - * anything needs to be written to the WAV file after calling this - * 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] first_sample_pos Position of the first sample in DATA chunk. - */ -BLOCKS_API bool wavheader_complete(FILE* fp, unsigned first_sample_pos); +enum wavfile_subformat_t { + FORMAT_PCM_S8 = 1, + FORMAT_PCM_16, + FORMAT_PCM_24, + FORMAT_PCM_32, + FORMAT_PCM_U8, + FORMAT_FLOAT, + FORMAT_DOUBLE, + FORMAT_VORBIS = 0x0060, +}; } /* namespace blocks */ } /* namespace gr */ +typedef gr::blocks::wavfile_format_t wavfile_format_t; +typedef gr::blocks::wavfile_subformat_t wavfile_subformat_t; + #endif /* _GR_WAVFILE_H_ */ diff --git a/gr-blocks/include/gnuradio/blocks/wavfile_sink.h b/gr-blocks/include/gnuradio/blocks/wavfile_sink.h index 144f446cce..f228f06168 100644 --- a/gr-blocks/include/gnuradio/blocks/wavfile_sink.h +++ b/gr-blocks/include/gnuradio/blocks/wavfile_sink.h @@ -12,6 +12,7 @@ #define INCLUDED_GR_WAVFILE_SINK_H #include <gnuradio/blocks/api.h> +#include <gnuradio/blocks/wavfile.h> #include <gnuradio/sync_block.h> namespace gr { @@ -35,12 +36,15 @@ public: * \param filename The .wav file to be opened * \param n_channels Number of channels (2 = stereo or I/Q output) * \param sample_rate Sample rate [S/s] - * \param bits_per_sample 16 or 8 bit, default is 16 + * \param format Output format (WAV, FLAC, Ogg Vorbis, RF64) + * \param subformat Bits per sample + * \param append Append to existing file */ static sptr make(const char* filename, int n_channels, unsigned int sample_rate, - int bits_per_sample = 16, + wavfile_format_t format, + wavfile_subformat_t subformat, bool append = false); /*! diff --git a/gr-blocks/lib/CMakeLists.txt b/gr-blocks/lib/CMakeLists.txt index 7a268f1522..c867d0ab09 100644 --- a/gr-blocks/lib/CMakeLists.txt +++ b/gr-blocks/lib/CMakeLists.txt @@ -53,7 +53,6 @@ add_library(gnuradio-blocks file_sink_base.cc pack_k_bits.cc unpack_k_bits.cc - wavfile.cc add_const_bb_impl.cc add_const_ss_impl.cc add_const_ii_impl.cc @@ -176,6 +175,7 @@ add_library(gnuradio-blocks target_link_libraries(gnuradio-blocks gnuradio-runtime Volk::volk + sndfile::sndfile ) target_include_directories(gnuradio-blocks diff --git a/gr-blocks/lib/wavfile.cc b/gr-blocks/lib/wavfile.cc deleted file mode 100644 index 2558d5c454..0000000000 --- a/gr-blocks/lib/wavfile.cc +++ /dev/null @@ -1,279 +0,0 @@ -/* -*- c++ -*- */ -/* - * Copyright 2004,2008,2012-2013,2020 Free Software Foundation, Inc. - * - * This file is part of GNU Radio - * - * SPDX-License-Identifier: GPL-3.0-or-later - * - */ - -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif - -#include <gnuradio/blocks/wavfile.h> -#include <gnuradio/logger.h> -#include <stdint.h> -#include <cstring> - - -namespace gr { -namespace blocks { -#define VALID_COMPRESSION_TYPE 0x0001 - -// Basically, this is the opposite of htonx() and ntohx() -// Define host to/from worknet (little endian) short and long -#ifdef GR_IS_BIG_ENDIAN - -static inline uint16_t __gri_wav_bs16(uint16_t x) { return (x >> 8) | (x << 8); } - -static inline uint32_t __gri_wav_bs32(uint32_t x) -{ - return (uint32_t(__gri_wav_bs16(uint16_t(x & 0xfffful))) << 16) | - (__gri_wav_bs16(uint16_t(x >> 16))); -} - -#define htowl(x) __gri_wav_bs32(x) -#define wtohl(x) __gri_wav_bs32(x) -#define htows(x) __gri_wav_bs16(x) -#define wtohs(x) __gri_wav_bs16(x) - -#else - -#define htowl(x) uint32_t(x) -#define wtohl(x) uint32_t(x) -#define htows(x) uint16_t(x) -#define wtohs(x) uint16_t(x) - -#endif // GR_IS_BIG_ENDIAN - -// WAV files are always little-endian, so we need some byte switching macros -static inline uint32_t host_to_wav(uint32_t x) { return htowl(x); } -static inline uint16_t host_to_wav(uint16_t x) { return htows(x); } -static inline int16_t host_to_wav(int16_t x) { return htows(x); } -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, wav_header_info& info) -{ - // _o variables take return values - char str_buf[8] = { 0 }; - - uint32_t file_size; - uint32_t fmt_hdr_skip; - uint16_t compression_type; - uint16_t nchans; - uint32_t sample_rate; - uint32_t avg_bytes_per_sec; - uint16_t block_align; - uint16_t bits_per_sample; - uint32_t chunk_size; - long real_file_size; - - size_t fresult; - - fseek(fp, 0L, SEEK_END); - real_file_size = ftell(fp); - rewind(fp); - - fresult = fread(str_buf, 1, 4, fp); - if (fresult != 4 || strncmp(str_buf, "RIFF", 4) || feof(fp)) { - return false; - } - - fresult = fread(&file_size, 1, 4, fp); - file_size = wav_to_host(file_size); - if (fresult != 4 || file_size != real_file_size - 8L) { - // FIXME use predefined loggers - gr::logger_ptr logger, debug_logger; - gr::configure_default_loggers(logger, debug_logger, "wavfile"); - GR_LOG_ERROR(logger, - boost::format("invalid file size (expected: %d; actual: %d)") % - (file_size + 8L) % real_file_size); - return false; - } - - fresult = fread(str_buf, 1, 8, fp); - if (fresult != 8 || strncmp(str_buf, "WAVEfmt ", 8) || feof(fp)) { - return false; - } - - fresult = fread(&fmt_hdr_skip, 1, 4, fp); - - fresult = fread(&compression_type, 1, 2, fp); - if (wav_to_host(compression_type) != VALID_COMPRESSION_TYPE) { - return false; - } - - fresult = fread(&nchans, 1, 2, fp); - fresult = fread(&sample_rate, 1, 4, fp); - fresult = fread(&avg_bytes_per_sec, 1, 4, fp); - fresult = fread(&block_align, 1, 2, fp); - fresult = fread(&bits_per_sample, 1, 2, fp); - - if (ferror(fp)) { - return false; - } - - fmt_hdr_skip = wav_to_host(fmt_hdr_skip); - nchans = wav_to_host(nchans); - sample_rate = wav_to_host(sample_rate); - bits_per_sample = wav_to_host(bits_per_sample); - - if (bits_per_sample != 8 && bits_per_sample != 16) { - return false; - } - - fmt_hdr_skip -= 16; - if (fmt_hdr_skip) { - if (fseek(fp, fmt_hdr_skip, SEEK_CUR) != 0) { - return false; - } - } - - // find data chunk - fresult = fread(str_buf, 1, 4, fp); - // keep parsing chunk until we hit the data chunk - while (fresult != 4 || strncmp(str_buf, "data", 4)) { - // all good? - if (fresult != 4 || ferror(fp) || feof(fp)) { - return false; - } - // get chunk body size and skip - fresult = fread(&chunk_size, 1, 4, fp); - if (fresult != 4 || ferror(fp) || feof(fp)) { - return false; - } - chunk_size = wav_to_host(chunk_size); - if (fseek(fp, chunk_size, SEEK_CUR) != 0) { - return false; - } - // read next chunk type - fresult = fread(str_buf, 1, 4, fp); - } - - fresult = fread(&chunk_size, 1, 4, fp); - if (ferror(fp)) { - return false; - } - - // More byte swapping - chunk_size = wav_to_host(chunk_size); - - // Output values - 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; -} - - -short int wav_read_sample(FILE* fp, int bytes_per_sample) -{ - int16_t buf_16bit = 0; - - if (fread(&buf_16bit, bytes_per_sample, 1, fp) != 1) { - return 0; - } - if (bytes_per_sample == 1) { - return (short)buf_16bit; - } - return (short)wav_to_host(buf_16bit); -} - - -bool wavheader_write(FILE* fp, unsigned int sample_rate, int nchans, int bytes_per_sample) -{ - const int header_len = 44; - char wav_hdr[header_len] = - "RIFF\0\0\0\0WAVEfmt \0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0data\0\0\0"; - uint16_t nchans_f = (uint16_t)nchans; - uint32_t sample_rate_f = (uint32_t)sample_rate; - uint16_t block_align = bytes_per_sample * nchans; - uint32_t avg_bytes = sample_rate * block_align; - uint16_t bits_per_sample = bytes_per_sample * 8; - - nchans_f = host_to_wav(nchans_f); - sample_rate_f = host_to_wav(sample_rate_f); - block_align = host_to_wav(block_align); - avg_bytes = host_to_wav(avg_bytes); - bits_per_sample = host_to_wav(bits_per_sample); - - wav_hdr[16] = 0x10; // no extra bytes - wav_hdr[20] = 0x01; // no compression - memcpy((void*)(wav_hdr + 22), (void*)&nchans_f, 2); - memcpy((void*)(wav_hdr + 24), (void*)&sample_rate_f, 4); - memcpy((void*)(wav_hdr + 28), (void*)&avg_bytes, 4); - memcpy((void*)(wav_hdr + 32), (void*)&block_align, 2); - memcpy((void*)(wav_hdr + 34), (void*)&bits_per_sample, 2); - - fwrite(&wav_hdr, 1, header_len, fp); - if (ferror(fp)) { - return false; - } - - return true; -} - - -void wav_write_sample(FILE* fp, short int sample, int bytes_per_sample) -{ - void* data_ptr; - unsigned char buf_8bit; - int16_t buf_16bit; - - if (bytes_per_sample == 1) { - buf_8bit = (unsigned char)sample; - data_ptr = (void*)&buf_8bit; - } else { - buf_16bit = host_to_wav((int16_t)sample); - data_ptr = (void*)&buf_16bit; - } - - fwrite(data_ptr, 1, bytes_per_sample, fp); -} - -inline bool fwrite_field_32(FILE* fp, long position, uint32_t data) -{ - data = host_to_wav(data); - if (fseek(fp, position, SEEK_SET) != 0) { - return false; - } - - 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; - } - - uint32_t field_data; - bool ok; - - // 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; - } - - // 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 */ -} /* namespace gr */ diff --git a/gr-blocks/lib/wavfile_sink_impl.cc b/gr-blocks/lib/wavfile_sink_impl.cc index bcd33a52cb..38325856bb 100644 --- a/gr-blocks/lib/wavfile_sink_impl.cc +++ b/gr-blocks/lib/wavfile_sink_impl.cc @@ -1,6 +1,6 @@ /* -*- c++ -*- */ /* - * Copyright 2004,2006-2011,2013 Free Software Foundation, Inc. + * Copyright 2004,2006-2011,2013,2020 Free Software Foundation, Inc. * * This file is part of GNU Radio * @@ -13,50 +13,30 @@ #endif #include "wavfile_sink_impl.h" -#include <gnuradio/blocks/wavfile.h> #include <gnuradio/io_signature.h> #include <gnuradio/thread/thread.h> -#include <fcntl.h> -#include <boost/math/special_functions/round.hpp> -#include <climits> -#include <cmath> #include <cstring> #include <stdexcept> -// win32 (mingw/msvc) specific -#ifdef HAVE_IO_H -#include <io.h> -#endif -#ifdef O_BINARY -#define OUR_O_BINARY O_BINARY -#else -#define OUR_O_BINARY 0 -#endif - -// should be handled via configure -#ifdef O_LARGEFILE -#define OUR_O_LARGEFILE O_LARGEFILE -#else -#define OUR_O_LARGEFILE 0 -#endif - namespace gr { namespace blocks { wavfile_sink::sptr wavfile_sink::make(const char* filename, int n_channels, unsigned int sample_rate, - int bits_per_sample, + wavfile_format_t format, + wavfile_subformat_t subformat, bool append) { return gnuradio::make_block_sptr<wavfile_sink_impl>( - filename, n_channels, sample_rate, bits_per_sample, append); + filename, n_channels, sample_rate, format, subformat, append); } wavfile_sink_impl::wavfile_sink_impl(const char* filename, int n_channels, unsigned int sample_rate, - int bits_per_sample, + wavfile_format_t format, + wavfile_subformat_t subformat, bool append) : sync_block("wavfile_sink", io_signature::make(1, n_channels, sizeof(float)), @@ -67,135 +47,178 @@ wavfile_sink_impl::wavfile_sink_impl(const char* filename, d_new_fp(nullptr), d_updated(false) { + int bits_per_sample; + + if (n_channels > 24) { + throw std::runtime_error("Number of channels greater than 24 not supported."); + } + d_h.sample_rate = sample_rate; d_h.nchans = n_channels; - + d_h.format = format; + d_h.subformat = subformat; + switch (subformat) { + case FORMAT_PCM_S8: + bits_per_sample = 8; + break; + case FORMAT_PCM_16: + bits_per_sample = 16; + break; + case FORMAT_PCM_24: + bits_per_sample = 24; + break; + case FORMAT_PCM_32: + bits_per_sample = 32; + break; + case FORMAT_PCM_U8: + bits_per_sample = 8; + break; + case FORMAT_FLOAT: + bits_per_sample = 32; + break; + case FORMAT_DOUBLE: + bits_per_sample = 64; + break; + case FORMAT_VORBIS: + bits_per_sample = 32; + break; + } set_bits_per_sample_unlocked(bits_per_sample); - d_h.bytes_per_sample = d_bytes_per_sample_new; if (!open(filename)) { - throw std::runtime_error("can't open WAV file"); + throw std::runtime_error("Can't open WAV file."); } } bool wavfile_sink_impl::open(const char* filename) { - gr::thread::scoped_lock guard(d_mutex); + SF_INFO sfinfo; - // we use the open system call to get access to the O_LARGEFILE flag. - int flags = OUR_O_LARGEFILE | OUR_O_BINARY; - - if (!d_append) { - // We are generating a new file. - flags |= O_CREAT | O_WRONLY | O_TRUNC; - } else { - flags |= O_RDWR; - } - - int fd; - if ((fd = ::open(filename, flags, 0664)) < 0) { - if (errno == ENOENT) { - throw std::runtime_error("WAV append mode requires target file to exist"); - } else { - GR_LOG_ERROR(d_logger, - boost::format("::open: %s: %s") % filename % strerror(errno)); - } - return false; - } + gr::thread::scoped_lock guard(d_mutex); if (d_new_fp) { // if we've already got a new one open, close it - fclose(d_new_fp); + sf_close(d_new_fp); d_new_fp = nullptr; } - if (!(d_new_fp = fdopen(fd, d_append ? "r+b" : "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; - } - if (d_append) { // We are appending to an existing file, be extra careful here. - if (!check_append_compat_file(d_new_fp)) { - fclose(d_new_fp); + sfinfo.format = 0; + if (!(d_new_fp = sf_open(filename, SFM_RDWR, &sfinfo))) { + GR_LOG_ERROR(d_logger, + boost::format("sf_open failed: %s: %s") % filename % + strerror(errno)); + return false; + } + if (d_h.sample_rate != sfinfo.samplerate || d_h.nchans != sfinfo.channels || + d_h.format != (sfinfo.format & SF_FORMAT_TYPEMASK) || + d_h.subformat != (sfinfo.format & SF_FORMAT_SUBMASK)) { + GR_LOG_ERROR(d_logger, + "Existing WAV file is incompatible with configured options."); + sf_close(d_new_fp); return false; } + if (sf_seek(d_new_fp, 0, SEEK_END) == -1) { + GR_LOG_ERROR(d_logger, "Seek error."); + return false; // This can only happen if the file disappears under our feet. + } } else { - 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, "could not save WAV header"); - fclose(d_new_fp); + memset(&sfinfo, 0, sizeof(sfinfo)); + sfinfo.samplerate = d_h.sample_rate; + sfinfo.channels = d_h.nchans; + switch (d_h.format) { + case FORMAT_WAV: + switch (d_h.subformat) { + case FORMAT_PCM_U8: + sfinfo.format = (SF_FORMAT_WAV | SF_FORMAT_PCM_U8); + break; + case FORMAT_PCM_16: + sfinfo.format = (SF_FORMAT_WAV | SF_FORMAT_PCM_16); + break; + case FORMAT_PCM_24: + sfinfo.format = (SF_FORMAT_WAV | SF_FORMAT_PCM_24); + break; + case FORMAT_PCM_32: + sfinfo.format = (SF_FORMAT_WAV | SF_FORMAT_PCM_32); + break; + case FORMAT_FLOAT: + sfinfo.format = (SF_FORMAT_WAV | SF_FORMAT_FLOAT); + break; + case FORMAT_DOUBLE: + sfinfo.format = (SF_FORMAT_WAV | SF_FORMAT_DOUBLE); + break; + } + break; + case FORMAT_FLAC: + switch (d_h.subformat) { + case FORMAT_PCM_S8: + sfinfo.format = (SF_FORMAT_FLAC | SF_FORMAT_PCM_S8); + break; + case FORMAT_PCM_16: + sfinfo.format = (SF_FORMAT_FLAC | SF_FORMAT_PCM_16); + break; + case FORMAT_PCM_24: + sfinfo.format = (SF_FORMAT_FLAC | SF_FORMAT_PCM_24); + break; + } + break; + case FORMAT_OGG: + switch (d_h.subformat) { + case FORMAT_VORBIS: + sfinfo.format = (SF_FORMAT_OGG | SF_FORMAT_VORBIS); + break; + } + break; + case FORMAT_RF64: + switch (d_h.subformat) { + case FORMAT_PCM_U8: + sfinfo.format = (SF_FORMAT_RF64 | SF_FORMAT_PCM_U8); + break; + case FORMAT_PCM_16: + sfinfo.format = (SF_FORMAT_RF64 | SF_FORMAT_PCM_16); + break; + case FORMAT_PCM_24: + sfinfo.format = (SF_FORMAT_RF64 | SF_FORMAT_PCM_24); + break; + case FORMAT_PCM_32: + sfinfo.format = (SF_FORMAT_RF64 | SF_FORMAT_PCM_32); + break; + case FORMAT_FLOAT: + sfinfo.format = (SF_FORMAT_RF64 | SF_FORMAT_FLOAT); + break; + case FORMAT_DOUBLE: + sfinfo.format = (SF_FORMAT_RF64 | SF_FORMAT_DOUBLE); + break; + } + break; + } + if (!(d_new_fp = sf_open(filename, SFM_WRITE, &sfinfo))) { + GR_LOG_ERROR(d_logger, + boost::format("sf_open failed: %s: %s") % filename % + strerror(errno)); return false; } } - d_updated = true; return true; } -bool wavfile_sink_impl::check_append_compat_file(FILE* fp) -{ - - if (d_bytes_per_sample_new != d_h.bytes_per_sample) { - GR_LOG_ERROR(d_logger, - "bytes_per_sample is not allowed to change in append mode"); - return false; - } - - wav_header_info h_tmp{}; - std::swap(d_h, h_tmp); - - if (!wavheader_parse(fp, d_h)) { - GR_LOG_ERROR(d_logger, "invalid or incompatible WAV file"); - return false; - } - - if (d_h.sample_rate != h_tmp.sample_rate || d_h.nchans != h_tmp.nchans || - d_h.bytes_per_sample != h_tmp.bytes_per_sample) { - GR_LOG_ERROR(d_logger, - "existing WAV file is incompatible with configured options"); - return false; - } - - // TODO: use GR_FSEEK, GR_FTELL. - if (fseek(d_new_fp, 0, SEEK_END) != 0) { - return false; // This can only happen if the file disappears under our feet. - } - - long file_size = ftell(fp); - if (file_size - d_h.first_sample_pos != d_h.data_chunk_size) { - // This is complicated to properly implement for too little benefit. - GR_LOG_ERROR(d_logger, - "existing WAV file is incompatible (extra chunks at the end)"); - return false; - } - - return true; -} - - void wavfile_sink_impl::close() { gr::thread::scoped_lock guard(d_mutex); - if (!d_fp) + if (!d_fp) { return; - + } close_wav(); } void wavfile_sink_impl::close_wav() { - 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); + sf_write_sync(d_fp); + sf_close(d_fp); d_fp = nullptr; } @@ -204,10 +227,9 @@ wavfile_sink_impl::~wavfile_sink_impl() { stop(); } bool wavfile_sink_impl::stop() { if (d_new_fp) { - fclose(d_new_fp); + sf_close(d_new_fp); d_new_fp = nullptr; } - close(); return true; @@ -219,53 +241,42 @@ int wavfile_sink_impl::work(int noutput_items, { auto in = (float**)&input_items[0]; int n_in_chans = input_items.size(); - - short int sample_buf_s; - int nwritten; + int errnum; + float sample[24]; gr::thread::scoped_lock guard(d_mutex); // hold mutex for duration of this block do_update(); // update: d_fp is read - if (!d_fp) // drop output on the floor + 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 < nchans; chan++) { // Write zeros to channels which are in the WAV file // but don't have any inputs here if (chan < n_in_chans) { - sample_buf_s = convert_to_short(in[chan][nwritten]); + sample[chan] = in[chan][nwritten]; } else { - sample_buf_s = 0; + sample[chan] = 0; } + } - wav_write_sample(d_fp, sample_buf_s, bytes_per_sample); + sf_write_float(d_fp, &sample[0], nchans); - if (feof(d_fp) || ferror(d_fp)) { - GR_LOG_ERROR(d_logger, - boost::format("file i/o error %s") % strerror(errno)); - close(); - exit(-1); - } + errnum = sf_error(d_fp); + if (errnum) { + GR_LOG_ERROR(d_logger, + boost::format("sf_error: %s") % sf_error_number(errnum)); + close(); + throw std::runtime_error("File I/O error."); } } return nwritten; } -short int wavfile_sink_impl::convert_to_short(float sample) -{ - sample += d_normalize_shift; - sample *= d_normalize_fac; - - // 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); -} - void wavfile_sink_impl::set_bits_per_sample(int bits_per_sample) { gr::thread::scoped_lock guard(d_mutex); @@ -274,22 +285,6 @@ void wavfile_sink_impl::set_bits_per_sample(int 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; } diff --git a/gr-blocks/lib/wavfile_sink_impl.h b/gr-blocks/lib/wavfile_sink_impl.h index d6e687dd03..c837592ad9 100644 --- a/gr-blocks/lib/wavfile_sink_impl.h +++ b/gr-blocks/lib/wavfile_sink_impl.h @@ -1,6 +1,6 @@ /* -*- c++ -*- */ /* - * Copyright 2008,2009,2013 Free Software Foundation, Inc. + * Copyright 2008,2009,2013,2020 Free Software Foundation, Inc. * * This file is part of GNU Radio * @@ -13,6 +13,7 @@ #include <gnuradio/blocks/wavfile.h> #include <gnuradio/blocks/wavfile_sink.h> +#include <sndfile.h> // for SNDFILE namespace gr { namespace blocks { @@ -29,18 +30,12 @@ private: float d_normalize_shift; float d_normalize_fac; - FILE* d_fp; - FILE* d_new_fp; + SNDFILE* d_fp; + SNDFILE* d_new_fp; bool d_updated; boost::mutex d_mutex; /*! - * \brief Convert a sample value within [-1;+1] to a corresponding - * short integer value - */ - short convert_to_short(float sample); - - /*! * \brief If any file changes have occurred, update now. This is called * internally by work() and thus doesn't usually need to be called by * hand. @@ -60,13 +55,6 @@ private: */ void close_wav(); - /*! - * \brief Checks if the given WAV file is compatible with the current - * configuration in order to open it to append information. - * This also finds the value of d_first_sample_pos. - */ - bool check_append_compat_file(FILE* fp); - protected: bool stop(); @@ -74,7 +62,8 @@ public: wavfile_sink_impl(const char* filename, int n_channels, unsigned int sample_rate, - int bits_per_sample, + wavfile_format_t format, + wavfile_subformat_t subformat, bool append); ~wavfile_sink_impl(); diff --git a/gr-blocks/lib/wavfile_source_impl.cc b/gr-blocks/lib/wavfile_source_impl.cc index b6bb559a5d..06db72c7e5 100644 --- a/gr-blocks/lib/wavfile_source_impl.cc +++ b/gr-blocks/lib/wavfile_source_impl.cc @@ -13,28 +13,10 @@ #endif #include "wavfile_source_impl.h" -#include <gnuradio/blocks/wavfile.h> #include <gnuradio/io_signature.h> -#include <fcntl.h> #include <sys/types.h> #include <stdexcept> -// win32 (mingw/msvc) specific -#ifdef HAVE_IO_H -#include <io.h> -#endif -#ifdef O_BINARY -#define OUR_O_BINARY O_BINARY -#else -#define OUR_O_BINARY 0 -#endif -// should be handled via configure -#ifdef O_LARGEFILE -#define OUR_O_LARGEFILE O_LARGEFILE -#else -#define OUR_O_LARGEFILE 0 -#endif - namespace gr { namespace blocks { @@ -52,51 +34,56 @@ wavfile_source_impl::wavfile_source_impl(const char* filename, bool repeat) 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, - boost::format("::open: %s: %s") % filename % strerror(errno)); - throw std::runtime_error("can't open file"); - } + SF_INFO sfinfo; - if ((d_fp = fdopen(fd, "rb")) == NULL) { + sfinfo.format = 0; + if (!(d_fp = sf_open(filename, SFM_READ, &sfinfo))) { GR_LOG_ERROR(d_logger, - boost::format("fdopen: %s: %s") % filename % strerror(errno)); - throw std::runtime_error("can't open file"); + boost::format("sf_open failed: %s: %s") % filename % + strerror(errno)); + throw std::runtime_error("Can't open WAV file."); } - // Scan headers, check file validity - if (!wavheader_parse(d_fp, d_h)) { - throw std::runtime_error("is not a valid wav file"); + d_h.sample_rate = (unsigned)sfinfo.samplerate; + d_h.nchans = sfinfo.channels; + if (sfinfo.channels > 24) { + throw std::runtime_error("Number of channels greater than 24 not supported."); } - - if (d_h.samples_per_chan == 0) { - throw std::runtime_error("WAV file does not contain any samples"); + switch (sfinfo.format & SF_FORMAT_SUBMASK) { + case SF_FORMAT_PCM_S8: + d_h.bytes_per_sample = 1; + break; + case SF_FORMAT_PCM_16: + d_h.bytes_per_sample = 2; + break; + case SF_FORMAT_PCM_24: + d_h.bytes_per_sample = 3; + break; + case SF_FORMAT_PCM_32: + d_h.bytes_per_sample = 4; + break; + case SF_FORMAT_PCM_U8: + d_h.bytes_per_sample = 1; + break; + case SF_FORMAT_FLOAT: + d_h.bytes_per_sample = 4; + break; + case SF_FORMAT_DOUBLE: + d_h.bytes_per_sample = 8; + break; } + d_h.samples_per_chan = sfinfo.frames; - if (d_h.bytes_per_sample == 1) { - d_normalize_fac = UINT8_MAX / 2; - d_normalize_shift = 1; - } else { - d_normalize_fac = INT16_MAX; - d_normalize_shift = 0; + if (d_h.samples_per_chan == 0) { + throw std::runtime_error("WAV file does not contain any samples."); } // Re-set the output signature set_output_signature(io_signature::make(1, d_h.nchans, sizeof(float))); } -wavfile_source_impl::~wavfile_source_impl() { fclose(d_fp); } - -float wavfile_source_impl::convert_to_float(short int sample) -{ - float sample_out = (float)sample; - sample_out /= d_normalize_fac; - sample_out -= d_normalize_shift; - return sample_out; -} +wavfile_source_impl::~wavfile_source_impl() { sf_close(d_fp); } int wavfile_source_impl::work(int noutput_items, gr_vector_const_void_star& input_items, @@ -104,9 +91,9 @@ int wavfile_source_impl::work(int noutput_items, { auto out = (float**)&output_items[0]; int n_out_chans = output_items.size(); - int i; - short sample; + int errnum; + float sample[24]; for (i = 0; i < noutput_items; i++) { if (d_sample_idx >= d_h.samples_per_chan) { @@ -115,33 +102,35 @@ int wavfile_source_impl::work(int noutput_items, return i ? i : -1; } - if (fseek(d_fp, d_h.first_sample_pos, SEEK_SET) == -1) { + if (sf_seek(d_fp, 0, SEEK_SET) == -1) { GR_LOG_ERROR(d_logger, - boost::format("fseek failed %s") % strerror(errno)); - exit(-1); + boost::format("sf_seek failed: %s") % strerror(errno)); + throw std::runtime_error("Seek error."); } d_sample_idx = 0; } + sf_read_float(d_fp, &sample[0], d_h.nchans); 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); + out[chan][i] = sample[chan]; } } d_sample_idx++; - // OK, EOF is not necessarily an error. But we're not going to - // deal with handling corrupt wav files, so if they give us any - // trouble they won't be processed. Serves them bloody right. - if (feof(d_fp) || ferror(d_fp)) { + // We're not going to deal with handling corrupt wav files, + // so if they give us any trouble they won't be processed. + // Serves them bloody right. + + errnum = sf_error(d_fp); + if (errnum) { if (i == 0) { - GR_LOG_ERROR(d_logger, - boost::format("WAV file has corrupted header or i/o error") % - strerror(errno)); + GR_LOG_ERROR( + d_logger, + boost::format("WAV file has corrupted header or I/O error, %s") % + sf_error_number(errnum)); return -1; } return i; diff --git a/gr-blocks/lib/wavfile_source_impl.h b/gr-blocks/lib/wavfile_source_impl.h index 8504413fec..9554feab67 100644 --- a/gr-blocks/lib/wavfile_source_impl.h +++ b/gr-blocks/lib/wavfile_source_impl.h @@ -1,6 +1,6 @@ /* -*- c++ -*- */ /* - * Copyright 2004,2008,2013 Free Software Foundation, Inc. + * Copyright 2004,2008,2013,2020 Free Software Foundation, Inc. * * This file is part of GNU Radio * @@ -13,7 +13,7 @@ #include <gnuradio/blocks/wavfile.h> #include <gnuradio/blocks/wavfile_source.h> -#include <cstdio> // for FILE +#include <sndfile.h> // for SNDFILE namespace gr { namespace blocks { @@ -21,18 +21,11 @@ namespace blocks { class wavfile_source_impl : public wavfile_source { private: - FILE* d_fp; + SNDFILE* d_fp; bool d_repeat; wav_header_info d_h; - unsigned d_sample_idx; - int d_normalize_shift; - int d_normalize_fac; - - /*! - * \brief Convert an integer sample value to a float value within [-1;1] - */ - float convert_to_float(short int sample); + long long d_sample_idx; public: wavfile_source_impl(const char* filename, bool repeat); diff --git a/gr-blocks/python/blocks/bindings/CMakeLists.txt b/gr-blocks/python/blocks/bindings/CMakeLists.txt index 976e12d180..4385c82a0f 100644 --- a/gr-blocks/python/blocks/bindings/CMakeLists.txt +++ b/gr-blocks/python/blocks/bindings/CMakeLists.txt @@ -1,3 +1,10 @@ +# Copyright 2020 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# SPDX-License-Identifier: GPL-3.0-or-later +# + include(GrPybind) ######################################################################## diff --git a/gr-blocks/python/blocks/bindings/docstrings/wavfile_sink_pydoc_template.h b/gr-blocks/python/blocks/bindings/docstrings/wavfile_sink_pydoc_template.h index 137dc82cd1..dc7a57fb7a 100644 --- a/gr-blocks/python/blocks/bindings/docstrings/wavfile_sink_pydoc_template.h +++ b/gr-blocks/python/blocks/bindings/docstrings/wavfile_sink_pydoc_template.h @@ -37,3 +37,6 @@ static const char* __doc_gr_blocks_wavfile_sink_set_sample_rate = R"doc()doc"; static const char* __doc_gr_blocks_wavfile_sink_set_bits_per_sample = R"doc()doc"; + + +static const char* __doc_gr_blocks_wavfile_sink_set_append = R"doc()doc"; diff --git a/gr-blocks/python/blocks/bindings/wavfile_python.cc b/gr-blocks/python/blocks/bindings/wavfile_python.cc index 950289d156..ff84bab7eb 100644 --- a/gr-blocks/python/blocks/bindings/wavfile_python.cc +++ b/gr-blocks/python/blocks/bindings/wavfile_python.cc @@ -14,7 +14,7 @@ /* BINDTOOL_GEN_AUTOMATIC(0) */ /* BINDTOOL_USE_PYGCCXML(0) */ /* BINDTOOL_HEADER_FILE(wavfile.h) */ -/* BINDTOOL_HEADER_FILE_HASH(85a13746fd109dbc7b94ba38aaf61e4c) */ +/* BINDTOOL_HEADER_FILE_HASH(69b513925578f5e61e5c98a083388fae) */ /***********************************************************************************/ #include <pybind11/complex.h> @@ -29,49 +29,31 @@ namespace py = pybind11; void bind_wavfile(py::module& m) { - using wav_header_info = gr::blocks::wav_header_info; + using wav_header_info = ::gr::blocks::wav_header_info; py::class_<wav_header_info, std::shared_ptr<wav_header_info>>(m, "wav_header_info") .def_readwrite("sample_rate", &wav_header_info::sample_rate) .def_readwrite("nchans", &wav_header_info::nchans) .def_readwrite("bytes_per_sample", &wav_header_info::bytes_per_sample) - .def_readwrite("first_sample_pos", &wav_header_info::first_sample_pos) .def_readwrite("samples_per_chan", &wav_header_info::samples_per_chan) - .def_readwrite("data_chunk_size", &wav_header_info::data_chunk_size); - - m.def("wavheader_parse", - &::gr::blocks::wavheader_parse, - py::arg("fp"), - py::arg("info"), - D(wavheader_parse)); - - m.def("wav_read_sample", - &::gr::blocks::wav_read_sample, - py::arg("fp"), - py::arg("bytes_per_sample"), - D(wav_read_sample)); - - - m.def("wavheader_write", - &::gr::blocks::wavheader_write, - py::arg("fp"), - py::arg("sample_rate"), - py::arg("nchans"), - py::arg("bytes_per_sample"), - D(wavheader_write)); - - - m.def("wav_write_sample", - &::gr::blocks::wav_write_sample, - py::arg("fp"), - py::arg("sample"), - py::arg("bytes_per_sample"), - D(wav_write_sample)); - - - m.def("wavheader_complete", - &::gr::blocks::wavheader_complete, - py::arg("fp"), - py::arg("byte_count"), - D(wavheader_complete)); + .def_readwrite("format", &wav_header_info::format) + .def_readwrite("subformat", &wav_header_info::subformat); + + py::enum_<::gr::blocks::wavfile_format_t>(m, "wavfile_format_t") + .value("FORMAT_WAV", ::gr::blocks::FORMAT_WAV) // 65536 + .value("FORMAT_FLAC", ::gr::blocks::FORMAT_FLAC) // 1507328 + .value("FORMAT_OGG", ::gr::blocks::FORMAT_OGG) // 2097152 + .value("FORMAT_RF64", ::gr::blocks::FORMAT_RF64) // 2228224 + .export_values(); + + py::enum_<::gr::blocks::wavfile_subformat_t>(m, "wavfile_subformat_t") + .value("FORMAT_PCM_S8", ::gr::blocks::FORMAT_PCM_S8) // 1 + .value("FORMAT_PCM_16", ::gr::blocks::FORMAT_PCM_16) // 2 + .value("FORMAT_PCM_24", ::gr::blocks::FORMAT_PCM_24) // 3 + .value("FORMAT_PCM_32", ::gr::blocks::FORMAT_PCM_32) // 4 + .value("FORMAT_PCM_U8", ::gr::blocks::FORMAT_PCM_U8) // 5 + .value("FORMAT_FLOAT", ::gr::blocks::FORMAT_FLOAT) // 6 + .value("FORMAT_DOUBLE", ::gr::blocks::FORMAT_DOUBLE) // 7 + .value("FORMAT_VORBIS", ::gr::blocks::FORMAT_VORBIS) // 96 + .export_values(); } diff --git a/gr-blocks/python/blocks/bindings/wavfile_sink_python.cc b/gr-blocks/python/blocks/bindings/wavfile_sink_python.cc index af7046d086..5a6e236eff 100644 --- a/gr-blocks/python/blocks/bindings/wavfile_sink_python.cc +++ b/gr-blocks/python/blocks/bindings/wavfile_sink_python.cc @@ -14,7 +14,7 @@ /* BINDTOOL_GEN_AUTOMATIC(0) */ /* BINDTOOL_USE_PYGCCXML(0) */ /* BINDTOOL_HEADER_FILE(wavfile_sink.h) */ -/* BINDTOOL_HEADER_FILE_HASH(b8184d24184e3bd7aa4a08f5fca8ac0d) */ +/* BINDTOOL_HEADER_FILE_HASH(aef0fe37adeb3a7bb763e4ed9e807a17) */ /***********************************************************************************/ #include <pybind11/complex.h> @@ -29,10 +29,8 @@ namespace py = pybind11; void bind_wavfile_sink(py::module& m) { - using wavfile_sink = ::gr::blocks::wavfile_sink; - py::class_<wavfile_sink, gr::sync_block, gr::block, @@ -43,27 +41,27 @@ void bind_wavfile_sink(py::module& m) py::arg("filename"), py::arg("n_channels"), py::arg("sample_rate"), - py::arg("bits_per_sample") = 16, + py::arg("format"), + py::arg("subformat"), py::arg("append") = false, D(wavfile_sink, make)) - .def("open", &wavfile_sink::open, py::arg("filename"), D(wavfile_sink, open)) - .def("close", &wavfile_sink::close, D(wavfile_sink, close)) - .def("set_sample_rate", &wavfile_sink::set_sample_rate, py::arg("sample_rate"), D(wavfile_sink, set_sample_rate)) - .def("set_bits_per_sample", &wavfile_sink::set_bits_per_sample, py::arg("bits_per_sample"), D(wavfile_sink, set_bits_per_sample)) - ; + .def("set_append", + &wavfile_sink::set_append, + py::arg("append"), + D(wavfile_sink, set_append)); } diff --git a/gr-blocks/python/blocks/qa_wavfile.py b/gr-blocks/python/blocks/qa_wavfile.py index f22934d71b..0b08ce3290 100644 --- a/gr-blocks/python/blocks/qa_wavfile.py +++ b/gr-blocks/python/blocks/qa_wavfile.py @@ -15,6 +15,7 @@ import os from os.path import getsize g_in_file = os.path.join(os.getenv("srcdir"), "test_16bit_1chunk.wav") +g_in_file_normal = os.path.join(os.getenv("srcdir"), "test_16bit_1chunk_normal.wav") g_extra_header_offset = 36 g_extra_header_len = 22 @@ -38,7 +39,8 @@ class test_wavefile(gr_unittest.TestCase): wf_out = blocks.wavfile_sink(outfile, wf_in.channels(), wf_in.sample_rate(), - wf_in.bits_per_sample()) + blocks.FORMAT_WAV, + blocks.FORMAT_PCM_16) self.tb.connect(wf_in, wf_out) self.tb.run() wf_out.close() @@ -72,7 +74,7 @@ class test_wavefile(gr_unittest.TestCase): in_data[g_extra_header_offset + g_extra_header_len:], out_data) def test_003_checkwav_append_copy(self): - infile = g_in_file + infile = g_in_file_normal outfile = "test_out_append.wav" # 1. Copy input to output @@ -84,7 +86,8 @@ class test_wavefile(gr_unittest.TestCase): wf_out = blocks.wavfile_sink(outfile, wf_in.channels(), wf_in.sample_rate(), - wf_in.bits_per_sample(), + blocks.FORMAT_WAV, + blocks.FORMAT_PCM_16, True) self.tb.connect(wf_in, wf_out) self.tb.run() @@ -96,7 +99,8 @@ class test_wavefile(gr_unittest.TestCase): wf_out = blocks.wavfile_sink(outfile, wf_in.channels(), wf_in.sample_rate(), - wf_in.bits_per_sample(), + blocks.FORMAT_WAV, + blocks.FORMAT_PCM_16, True) self.tb.connect(wf_in, halver, wf_out) self.tb.run() @@ -130,12 +134,15 @@ class test_wavefile(gr_unittest.TestCase): data_in_halved = [int(round(d/2)) for d in data_in] self.assertEqual(data_in_halved, data_out[2*len(data_in):]) + os.remove(outfile) + def test_003_checkwav_append_non_existent_should_error(self): outfile = "no_file.wav" - with self.assertRaisesRegex(RuntimeError, 'WAV append mode requires target file to exist'): - blocks.wavfile_sink(outfile, 1, 44100, 16, True) + with self.assertRaisesRegex(RuntimeError, "Can't open WAV file."): + blocks.wavfile_sink(outfile, 1, 44100, blocks.FORMAT_WAV, blocks.FORMAT_PCM_16, True) + os.remove(outfile) def wav_read_frames(w): import struct diff --git a/gr-blocks/python/blocks/test_16bit_1chunk.wav b/gr-blocks/python/blocks/test_16bit_1chunk.wav Binary files differindex 3d028a9582..e984f847a4 100644 --- a/gr-blocks/python/blocks/test_16bit_1chunk.wav +++ b/gr-blocks/python/blocks/test_16bit_1chunk.wav diff --git a/gr-blocks/python/blocks/test_16bit_1chunk_normal.wav b/gr-blocks/python/blocks/test_16bit_1chunk_normal.wav Binary files differnew file mode 100644 index 0000000000..42989ed588 --- /dev/null +++ b/gr-blocks/python/blocks/test_16bit_1chunk_normal.wav |