mirror of
				https://github.com/saitohirga/WSJT-X.git
				synced 2025-10-30 04:20:22 -04:00 
			
		
		
		
	Broadcast Wave Format is a backwards compatible superset of teh Microsoft WAV file format that has been implemented in teh hope that Windows File Explorer might show the WAV file metadta, as it turns out that is not the case but as it's done anyway. It appears to be impossible to write a WAV file such that MS Windows File Explorer shows any metadata so unless we adopt FLAC format audio files we will have to show metadata with our own software :( git-svn-id: svn+ssh://svn.code.sf.net/p/wsjt/wsjt/branches/wsjtx@6384 ab8295b8-cf94-4d9e-aec4-7959e3be5d79
		
			
				
	
	
		
			210 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			210 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| #ifndef BWF_FILE_HPP__
 | |
| #define BWF_FILE_HPP__
 | |
| 
 | |
| #include <array>
 | |
| 
 | |
| #include <QFile>
 | |
| #include <QMap>
 | |
| #include <QByteArray>
 | |
| 
 | |
| #include "pimpl_h.hpp"
 | |
| 
 | |
| class QObject;
 | |
| class QString;
 | |
| class QAudioFormat;
 | |
| 
 | |
| //
 | |
| // BWFFile - Broadcast Wave Format File (a.k.a. WAV file)
 | |
| //
 | |
| // The  BWF file  format is  a  backward compatible  variation of  the
 | |
| // Microsoft  WAV file  format. It  contains  an extra  chunk with  id
 | |
| // 'bext' that contains metadata defined by the EBU in:
 | |
| //
 | |
| //  https://tech.ebu.ch/docs/tech/tech3285.pdf
 | |
| //
 | |
| // Also relevant is the recommendation document:
 | |
| //
 | |
| //  https://tech.ebu.ch/docs/r/r098.pdf
 | |
| //
 | |
| // which suggests a format to the free text coding history field.
 | |
| //
 | |
| // This class also supports the LIST-INFO chunk type which also allows
 | |
| // metadata to be  added to a WAV  file, the defined INFO  tag ids are
 | |
| // documented here:
 | |
| //
 | |
| //  http://bwfmetaedit.sourceforge.net/listinfo.html
 | |
| //
 | |
| // These  ids  are not  enforced  but  they  are recommended  as  most
 | |
| // operating systems and audio applications  recognize some or more of
 | |
| // them. Notably Microsoft Windows is not one of the operating systems
 | |
| // that  does :(  In fact  there seems  to be  no documented  metadata
 | |
| // tagging format that Windows Explorer recognizes.
 | |
| //
 | |
| // Changes to  the 'bext' fields  and the LIST-INFO dictionary  may be
 | |
| // made right up  until the file is closed as  the relevant chunks are
 | |
| // saved to the end of the file after the end of the sample data.
 | |
| //
 | |
| // This class emulates the QFile class, in fact it uses a QFile object
 | |
| // instance internally and forwards many of its operations directly to
 | |
| // it.
 | |
| //
 | |
| // BWFFile  is a  QIODevice subclass  and the  implementation provides
 | |
| // access to  the audio sample  data contained in  the BWF file  as if
 | |
| // only that data were  in the file. I.e. the first  sample is at file
 | |
| // offset zero  and the  size of the  file is the  size of  the sample
 | |
| // data.  The headers,  trailers and  metadata are  hidden but  can be
 | |
| // accessed by the operations below.
 | |
| //
 | |
| class BWFFile
 | |
|   : public QIODevice
 | |
| {
 | |
|   Q_OBJECT
 | |
| public:
 | |
|   using FileHandleFlags = QFile::FileHandleFlags;
 | |
|   using Permissions = QFile::Permissions;
 | |
|   using FileError = QFile::FileError;
 | |
|   using MemoryMapFlags = QFile::MemoryMapFlags;
 | |
|   using InfoDictionary = QMap<std::array<char, 4>, QByteArray>;
 | |
|   using UMID = std::array<quint8, 64>;
 | |
| 
 | |
|   explicit BWFFile (QAudioFormat const&, QObject * parent = nullptr);
 | |
|   explicit BWFFile (QAudioFormat const&, QString const& name,
 | |
|                     QObject * parent = nullptr);
 | |
| 
 | |
|   // The  InfoDictionary should  contain  valid  WAV format  LIST-INFO
 | |
|   // identifiers as keys, a list of them can be found here:
 | |
|   //
 | |
|   // http://bwfmetaedit.sourceforge.net/listinfo.html
 | |
|   //
 | |
|   // For  files  opened for  ReadOnly  access  the dictionary  is  not
 | |
|   // written to  the file.  For  files opened ReadWrite,  any existing
 | |
|   // LIST-INFO tags will  be merged into the dictionary  when the file
 | |
|   // is opened and if the file  is modified the merged dictionary will
 | |
|   // be written back to the file.
 | |
|   //
 | |
|   // Note that the sample  data may no be in the  native endian, it is
 | |
|   // the   callers   responsibility   to  do   any   required   endian
 | |
|   // conversions. The  internal data is  always in native  endian with
 | |
|   // conversions  being handled  automatically. Use  the BWF::format()
 | |
|   // operation     to    access     the    format     including    the
 | |
|   // QAudioFormat::byteOrder()  operation to  determine the  data byte
 | |
|   // ordering.
 | |
|   //
 | |
|   explicit BWFFile (QAudioFormat const&, QString const& name,
 | |
|                     InfoDictionary const&, QObject * parent = nullptr);
 | |
| 
 | |
|   ~BWFFile ();
 | |
|   QAudioFormat const& format () const;
 | |
|   InfoDictionary& list_info ();
 | |
| 
 | |
|   //
 | |
|   // Broadcast Audio Extension fields
 | |
|   //
 | |
|   // If any of these modifiers are  called then a "bext" chunk will be
 | |
|   // written to the file if the  file is writeable and the sample data
 | |
|   // is modified.
 | |
|   //
 | |
|   enum class BextVersion : quint16 {v_0, v_1, v_2};
 | |
|   BextVersion bext_version () const;
 | |
|   void bext_version (BextVersion = BextVersion::v_2);
 | |
| 
 | |
|   QByteArray bext_description () const;
 | |
|   void bext_description (QByteArray const&); // max 256 bytes
 | |
| 
 | |
|   QByteArray bext_originator () const;
 | |
|   void bext_originator (QByteArray const&);        // max 32 bytes
 | |
| 
 | |
|   QByteArray bext_originator_reference () const;
 | |
|   void bext_originator_reference (QByteArray const&); // max 32 bytes
 | |
| 
 | |
|   QDateTime bext_origination_date_time () const;
 | |
|   void bext_origination_date_time (QDateTime const&); // 1s resolution
 | |
| 
 | |
|   quint64 bext_time_reference () const;
 | |
|   void bext_time_reference (quint64); // samples since midnight at start
 | |
| 
 | |
|   UMID bext_umid () const; // bext version >= 1 only
 | |
|   void bext_umid (UMID const&);
 | |
| 
 | |
|   quint16 bext_loudness_value () const;
 | |
|   void bext_loudness_value (quint16); // bext version >= 2 only
 | |
| 
 | |
|   quint16 bext_loudness_range () const;
 | |
|   void bext_loudness_range (quint16); // bext version >= 2 only
 | |
| 
 | |
|   quint16 bext_max_true_peak_level () const;
 | |
|   void bext_max_true_peak_level (quint16); // bext version >= 2 only
 | |
| 
 | |
|   quint16 bext_max_momentary_loudness () const;
 | |
|   void bext_max_momentary_loudness (quint16); // bext version >= 2 only
 | |
| 
 | |
|   quint16 bext_max_short_term_loudness () const;
 | |
|   void bext_max_short_term_loudness (quint16); // bext version >= 2 only
 | |
| 
 | |
|   QByteArray bext_coding_history () const;
 | |
|   void bext_coding_history (QByteArray const&); // See EBU R 98
 | |
| 
 | |
| 
 | |
|   // Emulate QFile interface
 | |
|   bool open (OpenMode) override;
 | |
|   bool open (FILE *, OpenMode, FileHandleFlags = QFile::DontCloseHandle);
 | |
|   bool open (int fd, OpenMode, FileHandleFlags = QFile::DontCloseHandle);
 | |
|   bool copy (QString const& new_name);
 | |
|   bool exists () const;
 | |
|   bool link (QString const& link_name);
 | |
|   bool remove ();
 | |
|   bool rename (QString const& new_name);
 | |
|   void setFileName (QString const& name);
 | |
|   QString symLinkTarget () const;
 | |
|   QString fileName () const;
 | |
|   Permissions permissions () const;
 | |
| 
 | |
|   // Resize is of the sample data portion, header and trailer chunks
 | |
|   // are excess to the given size
 | |
|   bool resize (qint64 new_size);
 | |
| 
 | |
|   bool setPermissions (Permissions permissions);
 | |
|   FileError error () const;
 | |
|   bool flush ();
 | |
|   int handle () const;
 | |
| 
 | |
|   // The mapping offset is relative to the start of the sample data
 | |
|   uchar * map (qint64 offset, qint64 size,
 | |
|                MemoryMapFlags = QFile::NoOptions);
 | |
|   bool unmap (uchar * address);
 | |
| 
 | |
|   void unsetError ();
 | |
| 
 | |
| 
 | |
|   //
 | |
|   // QIODevice implementation
 | |
|   //
 | |
| 
 | |
|   // The size returned is of the sample data only, header and trailer
 | |
|   // chunks are hidden and handled internally
 | |
|   qint64 size () const override;
 | |
| 
 | |
|   bool isSequential () const override;
 | |
| 
 | |
|   // The reset  operation clears the  'bext' and LIST-INFO as  if they
 | |
|   // were  never supplied.  If the  file  is writable  the 'bext'  and
 | |
|   // LIST-INFO chunks will not be  written making the resulting file a
 | |
|   // lowest common denominator WAV file.
 | |
|   bool reset () override;
 | |
| 
 | |
|   // Seek offsets are relative to the start of the sample data
 | |
|   bool seek (qint64) override;
 | |
| 
 | |
|   void close () override;
 | |
| 
 | |
| protected:
 | |
|   qint64 readData (char * data, qint64 max_size) override;
 | |
|   qint64 writeData (char const* data, qint64 max_size) override;
 | |
| 
 | |
| private:
 | |
|   class impl;
 | |
|   pimpl<impl> m_;
 | |
| };
 | |
| 
 | |
| #endif
 |