diff options
author | jcorgan | 2008-02-18 06:27:50 +0000 |
---|---|---|
committer | jcorgan | 2008-02-18 06:27:50 +0000 |
commit | e7fda5b546fb804a735b7880530b76018519faa6 (patch) | |
tree | 07e5be5049dd7f837cdaf9d85e5414b88971d491 /gnuradio-core/src | |
parent | dec3205cba1a23943b40e953a9e103e03be095ef (diff) | |
download | gnuradio-e7fda5b546fb804a735b7880530b76018519faa6.tar.gz gnuradio-e7fda5b546fb804a735b7880530b76018519faa6.tar.bz2 gnuradio-e7fda5b546fb804a735b7880530b76018519faa6.zip |
Merged -r7723:7729 from jcorgan/wav into trunk, with added example program. Adds Martin Braun's gr.wavfile_source and gr.wavfile_sink blocks.
git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@7730 221aa14e-8319-0410-a670-987f0aec2ac5
Diffstat (limited to 'gnuradio-core/src')
-rw-r--r-- | gnuradio-core/src/lib/io/Makefile.am | 17 | ||||
-rw-r--r-- | gnuradio-core/src/lib/io/gr_wavfile_sink.cc | 278 | ||||
-rw-r--r-- | gnuradio-core/src/lib/io/gr_wavfile_sink.h | 137 | ||||
-rw-r--r-- | gnuradio-core/src/lib/io/gr_wavfile_sink.i | 47 | ||||
-rw-r--r-- | gnuradio-core/src/lib/io/gr_wavfile_source.cc | 172 | ||||
-rw-r--r-- | gnuradio-core/src/lib/io/gr_wavfile_source.h | 93 | ||||
-rw-r--r-- | gnuradio-core/src/lib/io/gr_wavfile_source.i | 42 | ||||
-rw-r--r-- | gnuradio-core/src/lib/io/gri_wavfile.cc | 312 | ||||
-rw-r--r-- | gnuradio-core/src/lib/io/gri_wavfile.h | 100 | ||||
-rw-r--r-- | gnuradio-core/src/lib/io/io.i | 4 | ||||
-rw-r--r-- | gnuradio-core/src/python/gnuradio/gr/Makefile.am | 5 | ||||
-rwxr-xr-x | gnuradio-core/src/python/gnuradio/gr/qa_wavefile.py | 66 | ||||
-rw-r--r-- | gnuradio-core/src/python/gnuradio/gr/test_16bit_1chunk.wav | bin | 0 -> 52 bytes |
13 files changed, 1268 insertions, 5 deletions
diff --git a/gnuradio-core/src/lib/io/Makefile.am b/gnuradio-core/src/lib/io/Makefile.am index fb527da73..c94683735 100644 --- a/gnuradio-core/src/lib/io/Makefile.am +++ b/gnuradio-core/src/lib/io/Makefile.am @@ -53,7 +53,10 @@ libio_la_SOURCES = \ ppio_ppdev.cc \ sdr_1000.cc \ gr_udp_sink.cc \ - gr_udp_source.cc + gr_udp_source.cc \ + gr_wavfile_sink.cc \ + gr_wavfile_source.cc \ + gri_wavfile.cc grinclude_HEADERS = \ gr_file_sink.h \ @@ -83,7 +86,12 @@ grinclude_HEADERS = \ ppio_ppdev.h \ sdr_1000.h \ gr_udp_sink.h \ - gr_udp_source.h + gr_udp_source.h \ + gr_wavfile_source.h \ + gr_wavfile_sink.h \ + gri_wavfile.h + + swiginclude_HEADERS = \ @@ -102,4 +110,7 @@ swiginclude_HEADERS = \ ppio.i \ sdr_1000.i \ gr_udp_sink.i \ - gr_udp_source.i + gr_udp_source.i \ + gr_wavfile_source.i \ + gr_wavfile_sink.i + diff --git a/gnuradio-core/src/lib/io/gr_wavfile_sink.cc b/gnuradio-core/src/lib/io/gr_wavfile_sink.cc new file mode 100644 index 000000000..f06c33d5c --- /dev/null +++ b/gnuradio-core/src/lib/io/gr_wavfile_sink.cc @@ -0,0 +1,278 @@ +/* -*- c++ -*- */ +/* + * Copyright 2004,2006,2007,2008 Free Software Foundation, Inc. + * + * This file is part of GNU Radio + * + * GNU Radio 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 3, or (at your option) + * any later version. + * + * GNU Radio 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 GNU Radio; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <gr_wavfile_sink.h> +#include <gr_io_signature.h> +#include <gri_wavfile.h> +#include <stdexcept> +#include <climits> +#include <cstring> +#include <cmath> +#include <fcntl.h> + +// 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 + + +gr_wavfile_sink_sptr +gr_make_wavfile_sink(const char *filename, + int n_channels, + unsigned int sample_rate, + int bits_per_sample) +{ + return gr_wavfile_sink_sptr (new gr_wavfile_sink (filename, + n_channels, + sample_rate, + bits_per_sample)); +} + +gr_wavfile_sink::gr_wavfile_sink(const char *filename, + int n_channels, + unsigned int sample_rate, + int bits_per_sample) + : gr_sync_block ("wavfile_sink", + gr_make_io_signature(1, n_channels, sizeof(float)), + gr_make_io_signature(0, 0, 0)), + d_sample_rate(sample_rate), d_nchans(n_channels), + d_fp(0), d_new_fp(0), 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; + + if (!open(filename)) { + throw std::runtime_error ("can't open file"); + } + + 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; + if (bits_per_sample != 16) { + fprintf(stderr, "Invalid bits per sample value requested, using 16"); + } + } +} + + +bool +gr_wavfile_sink::open(const char* filename) +{ + omni_mutex_lock l(d_mutex); + + // we use the open system call to get access to the O_LARGEFILE flag. + int fd; + if ((fd = ::open (filename, + O_WRONLY|O_CREAT|O_TRUNC|OUR_O_LARGEFILE|OUR_O_BINARY, + 0664)) < 0){ + perror (filename); + return false; + } + + if (d_new_fp) { // if we've already got a new one open, close it + fclose(d_new_fp); + d_new_fp = 0; + } + + if ((d_new_fp = fdopen (fd, "wb")) == NULL) { + perror (filename); + ::close(fd); // don't leak file descriptor if fdopen fails. + return false; + } + d_updated = true; + + if (!gri_wavheader_write(d_new_fp, + d_sample_rate, + d_nchans, + d_bytes_per_sample_new)) { + fprintf(stderr, "[%s] could not write to WAV file\n", __FILE__); + exit(-1); + } + + return true; +} + + +void +gr_wavfile_sink::close() +{ + omni_mutex_lock l(d_mutex); + + if (!d_fp) + return; + + close_wav(); +} + +void gr_wavfile_sink::close_wav() +{ + unsigned int byte_count = d_sample_count * d_bytes_per_sample; + + gri_wavheader_complete(d_fp, byte_count); + + fclose(d_fp); + d_fp = NULL; +} + + +gr_wavfile_sink::~gr_wavfile_sink () +{ + if (d_new_fp) { + fclose(d_new_fp); + } + + close(); +} + + +int +gr_wavfile_sink::work (int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items) +{ + float **in = (float **) &input_items[0]; + int n_in_chans = input_items.size(); + + short int sample_buf_s; + + int nwritten; + + do_update(); // update: d_fp is reqd + if (!d_fp) // drop output on the floor + return noutput_items; + + for (nwritten = 0; nwritten < noutput_items; nwritten++) { + for (int chan = 0; chan < d_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]); + } else { + sample_buf_s = 0; + } + + gri_wav_write_sample(d_fp, sample_buf_s, d_bytes_per_sample); + + if (feof(d_fp) || ferror(d_fp)) { + fprintf(stderr, "[%s] file i/o error\n", __FILE__); + close(); + exit(-1); + } + d_sample_count++; + } + } + + return nwritten; +} + + +short int +gr_wavfile_sink::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; + } + + return (short int) roundf(sample); +} + + +void +gr_wavfile_sink::set_bits_per_sample(int bits_per_sample) +{ + omni_mutex_lock l(d_mutex); + if (bits_per_sample == 8 || bits_per_sample == 16) { + d_bytes_per_sample_new = bits_per_sample / 8; + } +} + + +void +gr_wavfile_sink::set_sample_rate(unsigned int sample_rate) +{ + omni_mutex_lock l(d_mutex); + d_sample_rate = sample_rate; +} + + +void +gr_wavfile_sink::do_update() +{ + if (!d_updated) { + return; + } + + omni_mutex_lock l(d_mutex); // hold mutex for duration of this block + if (d_fp) { + close_wav(); + } + + 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_updated = false; +} diff --git a/gnuradio-core/src/lib/io/gr_wavfile_sink.h b/gnuradio-core/src/lib/io/gr_wavfile_sink.h new file mode 100644 index 000000000..3fa06f202 --- /dev/null +++ b/gnuradio-core/src/lib/io/gr_wavfile_sink.h @@ -0,0 +1,137 @@ +/* -*- c++ -*- */ +/* + * Copyright 2008 Free Software Foundation, Inc. + * + * This file is part of GNU Radio + * + * GNU Radio 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 3, or (at your option) + * any later version. + * + * GNU Radio 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 GNU Radio; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef INCLUDED_GR_WAVFILE_SINK_H +#define INCLUDED_GR_WAVFILE_SINK_H + +#include <gr_sync_block.h> +#include <gr_file_sink_base.h> +#include <omnithread.h> + +class gr_wavfile_sink; +typedef boost::shared_ptr<gr_wavfile_sink> gr_wavfile_sink_sptr; + +/* + * \p filename The .wav file to be opened + * \p n_channels Number of channels (2 = stereo or I/Q output) + * \p sample_rate Sample rate [S/s] + * \p bits_per_sample 16 or 8 bit, default is 16 + */ +gr_wavfile_sink_sptr +gr_make_wavfile_sink (const char *filename, + int n_channels, + unsigned int sample_rate, + int bits_per_sample = 16); + +/*! + * \brief Read stream from a Microsoft PCM (.wav) file, output floats + * + * Values are within [-1;1]. + * Check gr_make_wavfile_source() for extra info. + * + * \ingroup sink + */ +class gr_wavfile_sink : public gr_sync_block +{ +private: + friend gr_wavfile_sink_sptr gr_make_wavfile_sink (const char *filename, + int n_channels, + unsigned int sample_rate, + int bits_per_sample); + + gr_wavfile_sink(const char *filename, + int n_channels, + unsigned int sample_rate, + int bits_per_sample); + + unsigned d_sample_rate; + int d_nchans; + unsigned d_sample_count; + int d_bytes_per_sample; + int d_bytes_per_sample_new; + int d_max_sample_val; + int d_min_sample_val; + int d_normalize_shift; + int d_normalize_fac; + + FILE *d_fp; + FILE *d_new_fp; + bool d_updated; + omni_mutex d_mutex; + + /*! + * \brief Convert a sample value within [-1;+1] to a corresponding + * short integer value + */ + short convert_to_short(float 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 + * other methods. + */ + void close_wav(); + +public: + ~gr_wavfile_sink (); + + /*! + * \brief Opens a new file and writes a WAV header. Thread-safe. + */ + bool open(const char* filename); + + /*! + * \brief Closes the currently active file and completes the WAV + * header. Thread-safe. + */ + void close(); + + /*! + * \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. + */ + void do_update(); + + /*! + * \brief Set the sample rate. This will not affect the WAV file + * currently opened. Any following open() calls will use this new + * sample rate. + */ + void set_sample_rate(unsigned int sample_rate); + + /*! + * \brief Set bits per sample. This will not affect the WAV file + * currently opened (see set_sample_rate()). If the value is neither + * 8 nor 16, the call is ignored and the current value is kept. + */ + void set_bits_per_sample(int bits_per_sample); + + + int work(int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items); + +}; + +#endif /* INCLUDED_GR_WAVFILE_SINK_H */ diff --git a/gnuradio-core/src/lib/io/gr_wavfile_sink.i b/gnuradio-core/src/lib/io/gr_wavfile_sink.i new file mode 100644 index 000000000..7ccad1978 --- /dev/null +++ b/gnuradio-core/src/lib/io/gr_wavfile_sink.i @@ -0,0 +1,47 @@ +/* -*- c++ -*- */ +/* + * Copyright 2008 Free Software Foundation, Inc. + * + * This file is part of GNU Radio + * + * GNU Radio 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 3, or (at your option) + * any later version. + * + * GNU Radio 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 GNU Radio; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + + +GR_SWIG_BLOCK_MAGIC(gr,wavfile_sink); + +gr_wavfile_sink_sptr +gr_make_wavfile_sink (const char *filename, + int n_channels, + unsigned int sample_rate, + int bits_per_sample = 16) throw (std::runtime_error); + +class gr_wavfile_sink : public gr_sync_block +{ +protected: + gr_wavfile_sink(const char *filename, + int n_channels, + unsigned int sample_rate, + int bits_per_sample) throw (std::runtime_error); + +public: + ~gr_wavfile_sink (); + bool open(const char* filename); + void close(); + void set_sample_rate(unsigned int sample_rate); + void set_bits_per_sample(int bits_per_sample); +}; + diff --git a/gnuradio-core/src/lib/io/gr_wavfile_source.cc b/gnuradio-core/src/lib/io/gr_wavfile_source.cc new file mode 100644 index 000000000..900f84efd --- /dev/null +++ b/gnuradio-core/src/lib/io/gr_wavfile_source.cc @@ -0,0 +1,172 @@ +/* -*- c++ -*- */ +/* + * Copyright 2004,2008 Free Software Foundation, Inc. + * + * This file is part of GNU Radio + * + * GNU Radio 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 3, or (at your option) + * any later version. + * + * GNU Radio 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 GNU Radio; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <gr_wavfile_source.h> +#include <gr_io_signature.h> +#include <gri_wavfile.h> +#include <cstdio> +#include <sys/types.h> +#include <fcntl.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 + + +gr_wavfile_source_sptr +gr_make_wavfile_source (const char *filename, bool repeat) +{ + return gr_wavfile_source_sptr (new gr_wavfile_source (filename, repeat)); +} + + +gr_wavfile_source::gr_wavfile_source (const char *filename, bool repeat) + : gr_sync_block ("wavfile_source", + gr_make_io_signature (0, 0, 0), + gr_make_io_signature (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_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) { + perror (filename); + throw std::runtime_error ("can't open file"); + } + + if ((d_fp = fdopen (fd, "rb")) == NULL) { + perror (filename); + throw std::runtime_error ("can't open file"); + } + + // Scan headers, check file validity + if (!gri_wavheader_parse(d_fp, + d_sample_rate, + d_nchans, + d_bytes_per_sample, + d_first_sample_pos, + d_samples_per_chan)) { + throw std::runtime_error("is not a valid wav file"); + } + + if (d_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; + d_normalize_shift = 1; + } else { + d_normalize_fac = 0x7FFF; + d_normalize_shift = 0; + } + + // Re-set the output signature + set_output_signature(gr_make_io_signature(1, d_nchans, sizeof(float))); +} + + +gr_wavfile_source::~gr_wavfile_source () +{ + fclose(d_fp); +} + + +int +gr_wavfile_source::work(int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items) +{ + float **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_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) { + fprintf(stderr, "[%s] fseek failed\n", __FILE__); + exit(-1); + } + + d_sample_idx = 0; + } + + for (int chan = 0; chan < d_nchans; chan++) { + sample = gri_wav_read_sample(d_fp, d_bytes_per_sample); + + if (chan < n_out_chans) { + out[chan][i] = convert_to_float(sample); + } + } + + 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)) { + if (i == 0) { + fprintf(stderr, "[%s] WAV file has corrupted header or i/o error\n", __FILE__); + return -1; + } + return i; + } + } + + return noutput_items; +} + + +float +gr_wavfile_source::convert_to_float(short int sample) +{ + float sample_out = (float) sample; + sample_out /= d_normalize_fac; + sample_out -= d_normalize_shift; + return sample_out; +} diff --git a/gnuradio-core/src/lib/io/gr_wavfile_source.h b/gnuradio-core/src/lib/io/gr_wavfile_source.h new file mode 100644 index 000000000..d737b441b --- /dev/null +++ b/gnuradio-core/src/lib/io/gr_wavfile_source.h @@ -0,0 +1,93 @@ +/* -*- c++ -*- */ +/* + * Copyright 2004,2008 Free Software Foundation, Inc. + * + * This file is part of GNU Radio + * + * GNU Radio 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 3, or (at your option) + * any later version. + * + * GNU Radio 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 GNU Radio; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifndef INCLUDED_GR_WAVFILE_SOURCE_H +#define INCLUDED_GR_WAVFILE_SOURCE_H + +#include <gr_sync_block.h> + +class gr_wavfile_source; +typedef boost::shared_ptr<gr_wavfile_source> gr_wavfile_source_sptr; + +gr_wavfile_source_sptr +gr_make_wavfile_source (const char *filename, bool repeat = false); + +/*! + * \brief Read stream from a Microsoft PCM (.wav) file, output floats + * + * Unless otherwise called, values are within [-1;1]. + * Check gr_make_wavfile_source() for extra info. + * + * \ingroup source + */ + +class gr_wavfile_source : public gr_sync_block +{ +private: + friend gr_wavfile_source_sptr gr_make_wavfile_source (const char *filename, + bool repeat); + gr_wavfile_source(const char *filename, bool repeat); + + 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; + 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); + +public: + ~gr_wavfile_source (); + + int work(int noutput_items, + gr_vector_const_void_star &input_items, + gr_vector_void_star &output_items); + + /*! + * \brief Read the sample rate as specified in the wav file header + */ + unsigned int sample_rate() const { return d_sample_rate; }; + + /*! + * \brief Return the number of bits per sample as specified in the wav + * file header. Only 8 or 16 bit are supported here. + */ + int bits_per_sample() const { return d_bytes_per_sample * 8; }; + + /*! + * \brief Return the number of channels in the wav file as specified in + * the wav file header. This is also the max number of outputs you can + * have. + */ + int channels() const { return d_nchans; }; +}; + +#endif /* INCLUDED_GR_WAVFILE_SOURCE_H */ diff --git a/gnuradio-core/src/lib/io/gr_wavfile_source.i b/gnuradio-core/src/lib/io/gr_wavfile_source.i new file mode 100644 index 000000000..5af2224f1 --- /dev/null +++ b/gnuradio-core/src/lib/io/gr_wavfile_source.i @@ -0,0 +1,42 @@ +/* -*- c++ -*- */ +/* + * Copyright 2008 Free Software Foundation, Inc. + * + * This file is part of GNU Radio + * + * GNU Radio 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 3, or (at your option) + * any later version. + * + * GNU Radio 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 GNU Radio; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +GR_SWIG_BLOCK_MAGIC(gr,wavfile_source); + +gr_wavfile_source_sptr +gr_make_wavfile_source (const char *filename, + bool repeat = false) throw (std::runtime_error); + +class gr_wavfile_source : public gr_sync_block +{ +private: + gr_wavfile_source(const char *filename, + bool repeat) throw (std::runtime_error); + +public: + ~gr_wavfile_source(); + + unsigned int sample_rate(); + int bits_per_sample(); + int channels(); +}; + diff --git a/gnuradio-core/src/lib/io/gri_wavfile.cc b/gnuradio-core/src/lib/io/gri_wavfile.cc new file mode 100644 index 000000000..4a9db61d8 --- /dev/null +++ b/gnuradio-core/src/lib/io/gri_wavfile.cc @@ -0,0 +1,312 @@ +/* -*- c++ -*- */ +/* + * Copyright 2004,2008 Free Software Foundation, Inc. + * + * This file is part of GNU Radio + * + * GNU Radio 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 3, or (at your option) + * any later version. + * + * GNU Radio 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 GNU Radio; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <gri_wavfile.h> +#include <cstring> +#include <stdint.h> + +# define VALID_COMPRESSION_TYPE 0x0001 + +// WAV files are always little-endian, so we need some byte switching macros + +// FIXME: These need to be refactored into a separate endianess header file +// as they duplicate routines defined in usrp/host/lib/legacy/usrp_bytesex.h + +#ifdef WORDS_BIGENDIAN + +#ifdef HAVE_BYTESWAP_H +#include <byteswap.h> +#else +#warning Using non-portable code (likely wrong other than ILP32). + +static inline short int +bswap_16 (unsigned short int x) +{ + return ((((x) >> 8) & 0xff) | (((x) & 0xff) << 8)); +} + +static inline unsigned int +bswap_32 (unsigned int x) +{ + return ((((x) & 0xff000000) >> 24) | (((x) & 0x00ff0000) >> 8) \ + | (((x) & 0x0000ff00) << 8) | (((x) & 0x000000ff) << 24)); +} +#endif // HAVE_BYTESWAP_H + +static inline uint32_t +host_to_wav(uint32_t x) +{ + return bswap_32(x); +} + +static inline uint16_t +host_to_wav(uint16_t x) +{ + return bswap_16(x); +} + +static inline int16_t +host_to_wav(int16_t x) +{ + return bswap_32(x); +} + +static inline uint32_t +wav_to_host(uint32_t x) +{ + return bswap_32(x); +} + +static inline uint16_t +wav_to_host(uint16_t x) +{ + return bswap_16(x); +} + +static inline int16_t +wav_to_host(int16_t x) +{ + return bswap_32(x); +} + +#else + +static inline uint32_t +host_to_wav(uint32_t x) +{ + return x; +} + +static inline uint16_t +host_to_wav(uint16_t x) +{ + return x; +} + +static inline int16_t +host_to_wav(int16_t x) +{ + return x; +} + +static inline uint32_t +wav_to_host(uint32_t x) +{ + return x; +} + +static inline uint16_t +wav_to_host(uint16_t x) +{ + return x; +} + +static inline int16_t +wav_to_host(int16_t x) +{ + return x; +} + +#endif // WORDS_BIGENDIAN + + +bool +gri_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) +{ + // _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; + + size_t fresult; + + fresult = fread(str_buf, 1, 4, fp); + if (fresult != 4 || strncmp(str_buf, "RIFF", 4) || feof(fp)) { + return false; + } + + fread(&file_size, 1, 4, fp); + + fresult = fread(str_buf, 1, 8, fp); + if (fresult != 8 || strncmp(str_buf, "WAVEfmt ", 8) || feof(fp)) { + return false; + } + + fread(&fmt_hdr_skip, 1, 4, fp); + + fread(&compression_type, 1, 2, fp); + if (wav_to_host(compression_type) != VALID_COMPRESSION_TYPE) { + return false; + } + + fread(&nchans, 1, 2, fp); + fread(&sample_rate, 1, 4, fp); + fread(&avg_bytes_per_sec, 1, 4, fp); + fread(&block_align, 1, 2, fp); + 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) { + fseek(fp, fmt_hdr_skip, SEEK_CUR); + } + + // data chunk + fresult = fread(str_buf, 1, 4, fp); + if (strncmp(str_buf, "data", 4)) { + return false; + } + + fread(&chunk_size, 1, 4, fp); + if (ferror(fp)) { + return false; + } + + // More byte swapping + 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)); + return true; +} + + +short int +gri_wav_read_sample(FILE *fp, int bytes_per_sample) +{ + int16_t buf = 0; + fread(&buf, bytes_per_sample, 1, fp); + + return (short) wav_to_host(buf); +} + + +bool +gri_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 +gri_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); +} + + +bool +gri_wavheader_complete(FILE *fp, unsigned int byte_count) +{ + uint32_t chunk_size = (uint32_t) byte_count; + chunk_size = host_to_wav(chunk_size); + + fseek(fp, 40, SEEK_SET); + 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); + fseek(fp, 4, SEEK_SET); + + fwrite(&chunk_size, 1, 4, fp); + + if (ferror(fp)) { + return false; + } + + return true; +} diff --git a/gnuradio-core/src/lib/io/gri_wavfile.h b/gnuradio-core/src/lib/io/gri_wavfile.h new file mode 100644 index 000000000..2c7c1207c --- /dev/null +++ b/gnuradio-core/src/lib/io/gri_wavfile.h @@ -0,0 +1,100 @@ +/* -*- c++ -*- */ +/* + * Copyright 2008 Free Software Foundation, Inc. + * + * This file is part of GNU Radio + * + * GNU Radio 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 3, or (at your option) + * any later version. + * + * GNU Radio 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 GNU Radio; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, + * Boston, MA 02110-1301, USA. + */ + +// This file stores all the RIFF file type knowledge for the gr_wavfile_* +// blocks. + +#include <cstdio> + +/*! + * \brief Read signal information from a given WAV file. + * + * \p fp File pointer to an opened, empty file. + * \p sample_rate Stores the sample rate [S/s] + * \p nchans Number of channels + * \p bytes_per_sample Bytes per sample, can either be 1 or 2 (corresponding to + * 8 or 16 bit samples, respectively) + * \p 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. + * \p samples_per_chan Number of samples per channel + * \p normalize_fac The normalization factor with which you need to divide the + * integer values of the samples to get them within [-1;1] + * \p normalize_shift The value by which the sample values need to be shifted + * after normalization (reason being, 8-bit WAV files store samples as + * unsigned char and 16-bit as signed short int) + * \return True on a successful read, false if the file could not be read or is + * not a valid WAV file. + */ +bool +gri_wavheader_parse(FILE *fp, + unsigned int &sample_rate, + int &nchans, + int &bytes_per_sample, + int &first_sample_pos, + unsigned int &samples_per_chan); + + +/*! + * \brief Read one sample from an open WAV file at the current position. + * + * Takes care of endianness. + */ +short int +gri_wav_read_sample(FILE *fp, int bytes_per_sample); + + +/*! + * \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. + */ +bool +gri_wavheader_write(FILE *fp, + unsigned int sample_rate, + int nchans, + int bytes_per_sample); + +/*! + * \brief Write one sample to an open WAV file at the current position. + * + * Takes care of endianness. + */ +void +gri_wav_write_sample(FILE *fp, short int sample, int bytes_per_sample); + + +/*! + * \brief Complete a WAV header + * + * 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 + * whereever). + * + * \p fp File pointer to an open WAV file with a blank header + * \p byte_count Length of all samples written to the file in bytes. + */ +bool +gri_wavheader_complete(FILE *fp, unsigned int byte_count); diff --git a/gnuradio-core/src/lib/io/io.i b/gnuradio-core/src/lib/io/io.i index 73a3ad993..63dd979ff 100644 --- a/gnuradio-core/src/lib/io/io.i +++ b/gnuradio-core/src/lib/io/io.i @@ -36,6 +36,8 @@ #include <gr_message_sink.h> #include <gr_udp_sink.h> #include <gr_udp_source.h> +#include <gr_wavfile_sink.h> +#include <gr_wavfile_source.h> %} @@ -54,4 +56,6 @@ %include "gr_message_sink.i" %include "gr_udp_sink.i" %include "gr_udp_source.i" +%include "gr_wavfile_sink.i" +%include "gr_wavfile_source.i" diff --git a/gnuradio-core/src/python/gnuradio/gr/Makefile.am b/gnuradio-core/src/python/gnuradio/gr/Makefile.am index 7406d062d..5aaef2540 100644 --- a/gnuradio-core/src/python/gnuradio/gr/Makefile.am +++ b/gnuradio-core/src/python/gnuradio/gr/Makefile.am @@ -21,8 +21,9 @@ include $(top_srcdir)/Makefile.common -EXTRA_DIST = run_tests.in - +EXTRA_DIST = \ + run_tests.in \ + test_16bit_1chunk.wav TESTS = \ run_tests diff --git a/gnuradio-core/src/python/gnuradio/gr/qa_wavefile.py b/gnuradio-core/src/python/gnuradio/gr/qa_wavefile.py new file mode 100755 index 000000000..5f46ea87b --- /dev/null +++ b/gnuradio-core/src/python/gnuradio/gr/qa_wavefile.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python +# +# Copyright 2008 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio 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 3, or (at your option) +# any later version. +# +# GNU Radio 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 GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +from gnuradio import gr, gr_unittest + +import os +from os.path import getsize + +class qa_wavefile(gr_unittest.TestCase): + + def setUp (self): + self.tb = gr.top_block () + + def tearDown (self): + self.tb = None + + def test_001_checkwavread (self): + wf = gr.wavfile_source("./test_16bit_1chunk.wav") + self.assertEqual(wf.sample_rate(), 8000) + + def test_002_checkwavcopy (self): + infile = "test_16bit_1chunk.wav" + outfile = "test_out.wav" + + wf_in = gr.wavfile_source(infile) + wf_out = gr.wavfile_sink(outfile, + wf_in.channels(), + wf_in.sample_rate(), + wf_in.bits_per_sample()) + self.tb.connect(wf_in, wf_out) + self.tb.run() + wf_out.close() + + self.assertEqual(getsize(infile), getsize(outfile)) + + in_f = file(infile, 'rb') + out_f = file(outfile, 'rb') + + in_data = in_f.read(getsize(infile)) + out_data = out_f.read(getsize(outfile)) + os.remove(outfile) + + self.assertEqual(in_data, out_data) + + +if __name__ == '__main__': + gr_unittest.main () diff --git a/gnuradio-core/src/python/gnuradio/gr/test_16bit_1chunk.wav b/gnuradio-core/src/python/gnuradio/gr/test_16bit_1chunk.wav Binary files differnew file mode 100644 index 000000000..0fe12a7a1 --- /dev/null +++ b/gnuradio-core/src/python/gnuradio/gr/test_16bit_1chunk.wav |