1 #ifndef BWF_FILE_HPP__ 2 #define BWF_FILE_HPP__ 3 4 #include <array> 5 6 #include <QFile> 7 #include <QMap> 8 #include <QByteArray> 9 10 #include "pimpl_h.hpp" 11 12 class QObject; 13 class QString; 14 class QAudioFormat; 15 16 // 17 // BWFFile - Broadcast Wave Format File (a.k.a. WAV file) 18 // 19 // The BWF file format is a backward compatible variation of the 20 // Microsoft WAV file format. It contains an extra chunk with id 21 // 'bext' that contains metadata defined by the EBU in: 22 // 23 // https://tech.ebu.ch/docs/tech/tech3285.pdf 24 // 25 // Also relevant is the recommendation document: 26 // 27 // https://tech.ebu.ch/docs/r/r098.pdf 28 // 29 // which suggests a format to the free text coding history field. 30 // 31 // This class also supports the LIST-INFO chunk type which also allows 32 // metadata to be added to a WAV file, the defined INFO tag ids are 33 // documented here: 34 // 35 // http://bwfmetaedit.sourceforge.net/listinfo.html 36 // 37 // These ids are not enforced but they are recommended as most 38 // operating systems and audio applications recognize some or more of 39 // them. Notably Microsoft Windows is not one of the operating systems 40 // that does :( In fact there seems to be no documented metadata 41 // tagging format that Windows Explorer recognizes. 42 // 43 // Changes to the 'bext' fields and the LIST-INFO dictionary may be 44 // made right up until the file is closed as the relevant chunks are 45 // saved to the end of the file after the end of the sample data. 46 // 47 // This class emulates the QFile class, in fact it uses a QFile object 48 // instance internally and forwards many of its operations directly to 49 // it. 50 // 51 // BWFFile is a QIODevice subclass and the implementation provides 52 // access to the audio sample data contained in the BWF file as if 53 // only that data were in the file. I.e. the first sample is at file 54 // offset zero and the size of the file is the size of the sample 55 // data. The headers, trailers and metadata are hidden but can be 56 // accessed by the operations below. 57 // 58 class BWFFile 59 : public QIODevice 60 { 61 Q_OBJECT 62 public: 63 using FileHandleFlags = QFile::FileHandleFlags; 64 using Permissions = QFile::Permissions; 65 using FileError = QFile::FileError; 66 using MemoryMapFlags = QFile::MemoryMapFlags; 67 using InfoDictionary = QMap<std::array<char, 4>, QByteArray>; 68 using UMID = std::array<quint8, 64>; 69 70 explicit BWFFile (QAudioFormat const&, QObject * parent = nullptr); 71 explicit BWFFile (QAudioFormat const&, QString const& name, 72 QObject * parent = nullptr); 73 74 // The InfoDictionary should contain valid WAV format LIST-INFO 75 // identifiers as keys, a list of them can be found here: 76 // 77 // http://bwfmetaedit.sourceforge.net/listinfo.html 78 // 79 // For files opened for ReadOnly access the dictionary is not 80 // written to the file. For files opened ReadWrite, any existing 81 // LIST-INFO tags will be merged into the dictionary when the file 82 // is opened and if the file is modified the merged dictionary will 83 // be written back to the file. 84 // 85 // Note that the sample data may no be in the native endian, it is 86 // the callers responsibility to do any required endian 87 // conversions. The internal data is always in native endian with 88 // conversions being handled automatically. Use the BWF::format() 89 // operation to access the format including the 90 // QAudioFormat::byteOrder() operation to determine the data byte 91 // ordering. 92 // 93 explicit BWFFile (QAudioFormat const&, QString const& name, 94 InfoDictionary const&, QObject * parent = nullptr); 95 96 ~BWFFile (); 97 QAudioFormat const& format () const; 98 InfoDictionary& list_info (); 99 100 // 101 // Broadcast Audio Extension fields 102 // 103 // If any of these modifiers are called then a "bext" chunk will be 104 // written to the file if the file is writeable and the sample data 105 // is modified. 106 // 107 enum class BextVersion : quint16 {v_0, v_1, v_2}; 108 BextVersion bext_version () const; 109 void bext_version (BextVersion = BextVersion::v_2); 110 111 QByteArray bext_description () const; 112 void bext_description (QByteArray const&); // max 256 bytes 113 114 QByteArray bext_originator () const; 115 void bext_originator (QByteArray const&); // max 32 bytes 116 117 QByteArray bext_originator_reference () const; 118 void bext_originator_reference (QByteArray const&); // max 32 bytes 119 120 QDateTime bext_origination_date_time () const; 121 void bext_origination_date_time (QDateTime const&); // 1s resolution 122 123 quint64 bext_time_reference () const; 124 void bext_time_reference (quint64); // samples since midnight at start 125 126 UMID bext_umid () const; // bext version >= 1 only 127 void bext_umid (UMID const&); 128 129 quint16 bext_loudness_value () const; 130 void bext_loudness_value (quint16); // bext version >= 2 only 131 132 quint16 bext_loudness_range () const; 133 void bext_loudness_range (quint16); // bext version >= 2 only 134 135 quint16 bext_max_true_peak_level () const; 136 void bext_max_true_peak_level (quint16); // bext version >= 2 only 137 138 quint16 bext_max_momentary_loudness () const; 139 void bext_max_momentary_loudness (quint16); // bext version >= 2 only 140 141 quint16 bext_max_short_term_loudness () const; 142 void bext_max_short_term_loudness (quint16); // bext version >= 2 only 143 144 QByteArray bext_coding_history () const; 145 void bext_coding_history (QByteArray const&); // See EBU R 98 146 147 148 // Emulate QFile interface 149 bool open (OpenMode) override; 150 bool open (FILE *, OpenMode, FileHandleFlags = QFile::DontCloseHandle); 151 bool open (int fd, OpenMode, FileHandleFlags = QFile::DontCloseHandle); 152 bool copy (QString const& new_name); 153 bool exists () const; 154 bool link (QString const& link_name); 155 bool remove (); 156 bool rename (QString const& new_name); 157 void setFileName (QString const& name); 158 QString symLinkTarget () const; 159 QString fileName () const; 160 Permissions permissions () const; 161 162 // Resize is of the sample data portion, header and trailer chunks 163 // are excess to the given size 164 bool resize (qint64 new_size); 165 166 bool setPermissions (Permissions permissions); 167 FileError error () const; 168 bool flush (); 169 int handle () const; 170 171 // The mapping offset is relative to the start of the sample data 172 uchar * map (qint64 offset, qint64 size, 173 MemoryMapFlags = QFile::NoOptions); 174 bool unmap (uchar * address); 175 176 void unsetError (); 177 178 179 // 180 // QIODevice implementation 181 // 182 183 // The size returned is of the sample data only, header and trailer 184 // chunks are hidden and handled internally 185 qint64 size () const override; 186 187 bool isSequential () const override; 188 189 // The reset operation clears the 'bext' and LIST-INFO as if they 190 // were never supplied. If the file is writable the 'bext' and 191 // LIST-INFO chunks will not be written making the resulting file a 192 // lowest common denominator WAV file. 193 bool reset () override; 194 195 // Seek offsets are relative to the start of the sample data 196 bool seek (qint64) override; 197 198 // this can fail due to updating header issues, errors are ignored 199 void close () override; 200 201 protected: 202 qint64 readData (char * data, qint64 max_size) override; 203 qint64 writeData (char const* data, qint64 max_size) override; 204 205 private: 206 class impl; 207 pimpl<impl> m_; 208 }; 209 210 #endif 211