211 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
		
		
			
		
	
	
			211 lines
		
	
	
		
			7.1 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; | ||
|  | 
 | ||
|  |   // this can fail due to updating header issues, errors are ignored
 | ||
|  |   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
 |