Mercurial > foo_out_sdl
comparison foosdk/sdk/foobar2000/helpers/writer_wav.cpp @ 1:20d02a178406 default tip
*: check in everything else
yay
| author | Paper <paper@tflc.us> |
|---|---|
| date | Mon, 05 Jan 2026 02:15:46 -0500 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| 0:e9bb126753e7 | 1:20d02a178406 |
|---|---|
| 1 #include "StdAfx.h" | |
| 2 | |
| 3 #include <vector> | |
| 4 | |
| 5 #ifndef _WIN32 | |
| 6 #include "winmm-types.h" | |
| 7 #endif | |
| 8 | |
| 9 #include "writer_wav.h" | |
| 10 | |
| 11 #include "audio_render_float.h" | |
| 12 | |
| 13 static const GUID guid_RIFF = pfc::GUID_from_text("66666972-912E-11CF-A5D6-28DB04C10000"); | |
| 14 static const GUID guid_WAVE = pfc::GUID_from_text("65766177-ACF3-11D3-8CD1-00C04F8EDB8A"); | |
| 15 static const GUID guid_FMT = pfc::GUID_from_text("20746D66-ACF3-11D3-8CD1-00C04F8EDB8A"); | |
| 16 static const GUID guid_DATA = pfc::GUID_from_text("61746164-ACF3-11D3-8CD1-00C04F8EDB8A"); | |
| 17 static const GUID guid_JUNK = pfc::GUID_from_text("6b6E756A-ACF3-11D3-8CD1-00C04f8EDB8A"); | |
| 18 | |
| 19 struct RIFF_chunk_desc { | |
| 20 GUID m_guid; | |
| 21 const char * m_name; | |
| 22 }; | |
| 23 | |
| 24 static const RIFF_chunk_desc RIFF_chunks[] = { | |
| 25 {guid_RIFF, "RIFF"}, | |
| 26 {guid_WAVE, "WAVE"}, | |
| 27 {guid_FMT , "fmt "}, | |
| 28 {guid_DATA, "data"}, | |
| 29 {guid_JUNK, "JUNK"}, | |
| 30 }; | |
| 31 | |
| 32 bool wavWriterSetup_t::needWFXE() const { | |
| 33 if (this->m_bpsValid != this->m_bps) return true; | |
| 34 switch(m_channels) | |
| 35 { | |
| 36 case 1: | |
| 37 return m_channel_mask != audio_chunk::channel_config_mono; | |
| 38 case 2: | |
| 39 return m_channel_mask != audio_chunk::channel_config_stereo; | |
| 40 /* case 4: | |
| 41 m_wfxe = m_setup.m_channel_mask != (audio_chunk::channel_front_left | audio_chunk::channel_front_right | audio_chunk::channel_back_left | audio_chunk::channel_back_right); | |
| 42 break; | |
| 43 case 6: | |
| 44 m_wfxe = m_setup.m_channel_mask != audio_chunk::channel_config_5point1; | |
| 45 break;*/ | |
| 46 default: | |
| 47 return true; | |
| 48 } | |
| 49 | |
| 50 } | |
| 51 | |
| 52 void wavWriterSetup_t::initialize3(const audio_chunk::spec_t & spec, unsigned bps, unsigned bpsValid, bool bFloat, bool bDither, bool bWave64) { | |
| 53 m_bps = bps; | |
| 54 m_bpsValid = bpsValid; | |
| 55 m_samplerate = spec.sampleRate; | |
| 56 m_channels = spec.chanCount; | |
| 57 m_channel_mask = spec.chanMask; | |
| 58 m_float = bFloat; | |
| 59 m_dither = bDither; | |
| 60 m_wave64 = bWave64; | |
| 61 | |
| 62 m_rf64_explicit = false; | |
| 63 m_rf64_implicit = false; | |
| 64 } | |
| 65 | |
| 66 void wavWriterSetup_t::initialize2(const audio_chunk & p_chunk, unsigned p_bps, unsigned p_bpsValid, bool p_float, bool p_dither, bool p_wave64) { | |
| 67 initialize3(p_chunk.get_spec(), p_bps, p_bpsValid, p_float, p_dither, p_wave64); | |
| 68 } | |
| 69 | |
| 70 void wavWriterSetup_t::initialize(const audio_chunk & p_chunk, unsigned p_bps, bool p_float, bool p_dither, bool p_wave64) | |
| 71 { | |
| 72 unsigned bpsValid = p_bps; | |
| 73 unsigned bps = (p_bps + 7) & ~7; | |
| 74 initialize2(p_chunk, bps, bpsValid, p_float, p_dither, p_wave64); | |
| 75 } | |
| 76 | |
| 77 void wavWriterSetup_t::setup_wfx(WAVEFORMATEX & p_wfx) | |
| 78 { | |
| 79 p_wfx.wFormatTag = m_float ? WAVE_FORMAT_IEEE_FLOAT : WAVE_FORMAT_PCM; | |
| 80 p_wfx.nChannels = m_channels; | |
| 81 p_wfx.nSamplesPerSec = m_samplerate; | |
| 82 p_wfx.nAvgBytesPerSec = (m_bps >> 3) * m_channels * m_samplerate; | |
| 83 p_wfx.nBlockAlign = (m_bps>>3) * m_channels; | |
| 84 p_wfx.wBitsPerSample = m_bps; | |
| 85 p_wfx.cbSize = 0; | |
| 86 } | |
| 87 | |
| 88 void wavWriterSetup_t::setup_wfxe(WAVEFORMATEXTENSIBLE & p_wfxe) | |
| 89 { | |
| 90 setup_wfx(p_wfxe.Format); | |
| 91 p_wfxe.Format.wFormatTag=WAVE_FORMAT_EXTENSIBLE; | |
| 92 p_wfxe.Format.cbSize=22; | |
| 93 p_wfxe.Samples.wValidBitsPerSample = this->m_bpsValid; | |
| 94 p_wfxe.dwChannelMask = audio_chunk::g_channel_config_to_wfx(m_channel_mask); | |
| 95 p_wfxe.SubFormat = m_float ? KSDATAFORMAT_SUBTYPE_IEEE_FLOAT : KSDATAFORMAT_SUBTYPE_PCM; | |
| 96 | |
| 97 } | |
| 98 | |
| 99 void CWavWriter::writeID(const GUID & id, abort_callback & abort) { | |
| 100 if (is64()) { | |
| 101 m_file->write_object_t(id, abort); | |
| 102 } else { | |
| 103 for( auto & walk : RIFF_chunks) { | |
| 104 if (id == walk.m_guid) { | |
| 105 m_file->write(walk.m_name, 4, abort); return; | |
| 106 } | |
| 107 } | |
| 108 uBugCheck(); | |
| 109 } | |
| 110 } | |
| 111 | |
| 112 void CWavWriter::writeSize(t_uint64 size, abort_callback & abort) { | |
| 113 if (is64()) { | |
| 114 if (size != UINT64_MAX) size += 24; | |
| 115 m_file->write_lendian_t(size, abort); | |
| 116 } else { | |
| 117 t_uint32 clipped; | |
| 118 if (size > UINT32_MAX) clipped = UINT32_MAX; | |
| 119 else clipped = (t_uint32) size; | |
| 120 m_file->write_lendian_t(clipped, abort); | |
| 121 } | |
| 122 } | |
| 123 | |
| 124 size_t CWavWriter::align(abort_callback & abort) { | |
| 125 t_uint8 dummy[8] = {}; | |
| 126 const t_uint32 val = is64() ? 8 : 2; | |
| 127 t_filesize pos = m_file->get_position(abort); | |
| 128 t_size delta = (val - (pos%val)) % val; | |
| 129 if (delta > 0) m_file->write(dummy, delta, abort); | |
| 130 return delta; | |
| 131 } | |
| 132 | |
| 133 void CWavWriter::open(const char * p_path, const wavWriterSetup_t & p_setup, abort_callback & p_abort) | |
| 134 { | |
| 135 service_ptr_t<file> l_file; | |
| 136 filesystem::g_open_write_new(l_file,p_path,p_abort); | |
| 137 open(l_file,p_setup,p_abort); | |
| 138 } | |
| 139 | |
| 140 namespace { | |
| 141 PFC_DECLARE_EXCEPTION(exceptionBadBitDepth, exception_io_data, "Invalid bit depth specified"); | |
| 142 } | |
| 143 void CWavWriter::open(service_ptr_t<file> p_file, const wavWriterSetup_t & p_setup, abort_callback & p_abort) | |
| 144 { | |
| 145 m_file = p_file; | |
| 146 m_setup = p_setup; | |
| 147 | |
| 148 if (m_setup.m_channels == 0 || m_setup.m_channels > 18 || m_setup.m_channels != audio_chunk::g_count_channels(m_setup.m_channel_mask)) throw exception_io_data(); | |
| 149 | |
| 150 if (!audio_chunk::g_is_valid_sample_rate(m_setup.m_samplerate)) throw exception_io_data(); | |
| 151 | |
| 152 if (m_setup.m_bpsValid > m_setup.m_bps) throw exceptionBadBitDepth(); | |
| 153 | |
| 154 if (m_setup.m_float) | |
| 155 { | |
| 156 if (m_setup.m_bps != 32 && m_setup.m_bps != 64) throw exceptionBadBitDepth(); | |
| 157 if (m_setup.m_bpsValid != m_setup.m_bps) throw exceptionBadBitDepth(); | |
| 158 } | |
| 159 else | |
| 160 { | |
| 161 if (m_setup.m_bps != 8 && m_setup.m_bps != 16 && m_setup.m_bps != 24 && m_setup.m_bps != 32) throw exceptionBadBitDepth(); | |
| 162 if (m_setup.m_bpsValid < 1) throw exceptionBadBitDepth(); | |
| 163 } | |
| 164 | |
| 165 m_wfxe = m_setup.needWFXE(); | |
| 166 | |
| 167 if (m_setup.m_wave64) { | |
| 168 m_file->write_object_t(guid_RIFF, p_abort); | |
| 169 } else if (m_setup.m_rf64_explicit) { | |
| 170 m_file->write("RF64", 4, p_abort); | |
| 171 } else { | |
| 172 m_file->write("RIFF", 4, p_abort); | |
| 173 } | |
| 174 | |
| 175 m_offset_fix1 = m_file->get_position(p_abort); | |
| 176 writeSize(UINT64_MAX, p_abort); | |
| 177 | |
| 178 writeID(guid_WAVE, p_abort); | |
| 179 | |
| 180 if (!is64() && m_file->can_seek() && (m_setup.m_rf64_explicit || m_setup.m_rf64_implicit)) { | |
| 181 // write JUNK placeholder for DS64 | |
| 182 m_ds64_at = m_file->get_position(p_abort); | |
| 183 static const uint8_t dummy[28] = {}; // riffsize64, datasize64, samplecount64, tablecount32 | |
| 184 writeID(guid_JUNK, p_abort); | |
| 185 writeSize(sizeof(dummy), p_abort); | |
| 186 m_file->write_object(dummy, sizeof(dummy), p_abort); | |
| 187 } | |
| 188 | |
| 189 | |
| 190 writeID(guid_FMT, p_abort); | |
| 191 if (m_wfxe) { | |
| 192 writeSize(sizeof(WAVEFORMATEXTENSIBLE),p_abort); | |
| 193 | |
| 194 WAVEFORMATEXTENSIBLE wfxe = {}; | |
| 195 m_setup.setup_wfxe(wfxe); | |
| 196 m_file->write_object(&wfxe,sizeof(wfxe),p_abort); | |
| 197 } else { | |
| 198 writeSize(sizeof(PCMWAVEFORMAT),p_abort); | |
| 199 | |
| 200 WAVEFORMATEX wfx = {}; | |
| 201 m_setup.setup_wfx(wfx); | |
| 202 m_file->write_object(&wfx,/* blah */ sizeof(PCMWAVEFORMAT),p_abort); | |
| 203 } | |
| 204 align(p_abort); | |
| 205 | |
| 206 writeID(guid_DATA, p_abort); | |
| 207 m_offset_fix2 = m_file->get_position(p_abort); | |
| 208 writeSize(UINT64_MAX, p_abort); | |
| 209 m_offset_fix1_delta = m_file->get_position(p_abort) - chunkOverhead(); | |
| 210 | |
| 211 m_bytes_written = 0; | |
| 212 | |
| 213 if (!m_setup.m_float) | |
| 214 { | |
| 215 m_postprocessor = standard_api_create_t<audio_postprocessor>(); | |
| 216 } | |
| 217 } | |
| 218 | |
| 219 void CWavWriter::write_raw( const void * raw, size_t rawSize, abort_callback & p_abort ) { | |
| 220 m_file->write_object(raw,rawSize,p_abort); | |
| 221 m_bytes_written += rawSize; | |
| 222 } | |
| 223 | |
| 224 void CWavWriter::write(const audio_chunk & p_chunk, abort_callback & p_abort) | |
| 225 { | |
| 226 if (p_chunk.get_channels() != m_setup.m_channels | |
| 227 || p_chunk.get_channel_config() != m_setup.m_channel_mask | |
| 228 || p_chunk.get_srate() != m_setup.m_samplerate | |
| 229 ) throw exception_unexpected_audio_format_change(); | |
| 230 | |
| 231 | |
| 232 if (m_setup.m_float) | |
| 233 { | |
| 234 const size_t count = p_chunk.get_channels() * p_chunk.get_sample_count(); | |
| 235 const void* data = render_float_by_bps(m_setup.m_bps, m_postprocessor_output, p_chunk.get_data(), count); | |
| 236 write_raw(data, count * m_setup.m_bps / 8, p_abort); | |
| 237 } | |
| 238 else | |
| 239 { | |
| 240 m_postprocessor->run(p_chunk,m_postprocessor_output,m_setup.m_bpsValid,m_setup.m_bps,m_setup.m_dither,1.0f); | |
| 241 write_raw( m_postprocessor_output.get_ptr(),m_postprocessor_output.get_size(), p_abort ); | |
| 242 } | |
| 243 } | |
| 244 | |
| 245 void CWavWriter::finalize(abort_callback & p_abort) | |
| 246 { | |
| 247 if (m_file.is_valid()) | |
| 248 { | |
| 249 const size_t alignG = align(p_abort); | |
| 250 | |
| 251 if (m_file->can_seek()) { | |
| 252 m_file->seek(m_offset_fix1,p_abort); | |
| 253 const uint64_t riffSize = m_bytes_written + alignG + m_offset_fix1_delta; | |
| 254 writeSize(riffSize, p_abort); | |
| 255 m_file->seek(m_offset_fix2,p_abort); | |
| 256 writeSize(m_bytes_written, p_abort); | |
| 257 | |
| 258 if (!is64() && (m_setup.m_rf64_explicit || (m_setup.m_rf64_implicit && m_bytes_written > UINT32_MAX))) { | |
| 259 if (!m_setup.m_rf64_explicit) { // turn RIFF into RF64? | |
| 260 m_file->seek(0, p_abort); | |
| 261 m_file->write("RF64", 4, p_abort); | |
| 262 } | |
| 263 m_file->seek(m_ds64_at, p_abort); | |
| 264 m_file->write("ds64", 4, p_abort); | |
| 265 writeSize(28, p_abort); | |
| 266 | |
| 267 // RIFF size, data size, sample count | |
| 268 m_file->write_lendian_t(riffSize, p_abort); | |
| 269 m_file->write_lendian_t(m_bytes_written, p_abort); | |
| 270 | |
| 271 unsigned sampleBytes = (m_setup.m_bps + 7) / 8 * m_setup.m_channels; | |
| 272 uint64_t samples = m_bytes_written / sampleBytes; | |
| 273 m_file->write_lendian_t(samples, p_abort); | |
| 274 uint32_t tableCount = 0; | |
| 275 m_file->write_lendian_t(tableCount, p_abort); | |
| 276 } | |
| 277 } | |
| 278 m_file.release(); | |
| 279 } | |
| 280 m_postprocessor.release(); | |
| 281 } | |
| 282 | |
| 283 void CWavWriter::close() | |
| 284 { | |
| 285 m_file.release(); | |
| 286 m_postprocessor.release(); | |
| 287 } | |
| 288 | |
| 289 audio_chunk::spec_t CWavWriter::get_spec() const { | |
| 290 audio_chunk::spec_t spec = {}; | |
| 291 spec.sampleRate = m_setup.m_samplerate; | |
| 292 spec.chanCount = m_setup.m_channels; | |
| 293 spec.chanMask = m_setup.m_channel_mask; | |
| 294 return spec; | |
| 295 } | |
| 296 | |
| 297 namespace { | |
| 298 class fileWav : public foobar2000_io::file { | |
| 299 public: | |
| 300 size_t read( void * buffer, size_t bytes, abort_callback & aborter ) override { | |
| 301 aborter.check(); | |
| 302 uint8_t * out = (uint8_t*) buffer; | |
| 303 size_t ret = 0; | |
| 304 if (m_position < m_header.size()) { | |
| 305 size_t delta = (size_t) ( m_header.size() - m_position ); | |
| 306 if (delta > bytes) delta = bytes; | |
| 307 memcpy( out, &m_header[(size_t)m_position], delta ); | |
| 308 m_position += delta; | |
| 309 out += delta; ret += delta; bytes -= delta; | |
| 310 } | |
| 311 if (bytes > 0) { | |
| 312 m_data->seek( m_position, aborter ); | |
| 313 size_t didRead = m_data->read( out, bytes, aborter ); | |
| 314 m_position += didRead; | |
| 315 ret += didRead; | |
| 316 } | |
| 317 return ret; | |
| 318 } | |
| 319 void write( const void * buffer, size_t bytes, abort_callback & aborter ) override { | |
| 320 throw exception_io_denied(); | |
| 321 } | |
| 322 fileWav( std::vector<uint8_t> const & header, file::ptr data) { | |
| 323 m_data = data; | |
| 324 m_position = 0; | |
| 325 m_header = header; | |
| 326 } | |
| 327 fileWav( std::vector<uint8_t> && header, file::ptr data) { | |
| 328 m_data = data; | |
| 329 m_position = 0; | |
| 330 m_header = std::move(header); | |
| 331 } | |
| 332 t_filesize get_size(abort_callback & p_abort) override { | |
| 333 t_filesize s = m_data->get_size( p_abort ); | |
| 334 if (s != filesize_invalid) s += m_header.size(); | |
| 335 return s; | |
| 336 } | |
| 337 t_filesize get_position(abort_callback & p_abort) override { | |
| 338 return m_position; | |
| 339 } | |
| 340 void resize(t_filesize p_size,abort_callback & p_abort) override { | |
| 341 throw exception_io_denied(); | |
| 342 } | |
| 343 void seek(t_filesize p_position,abort_callback & p_abort) override { | |
| 344 if (p_position > get_size(p_abort)) throw exception_io_seek_out_of_range(); | |
| 345 m_position = p_position; | |
| 346 } | |
| 347 bool can_seek() override {return true; } | |
| 348 bool get_content_type(pfc::string_base & p_out) override { return false; } | |
| 349 void reopen(abort_callback & p_abort) override { seek(0, p_abort); } | |
| 350 bool is_remote() override { return m_data->is_remote(); } | |
| 351 private: | |
| 352 std::vector<uint8_t> m_header; | |
| 353 t_filesize m_position; | |
| 354 file::ptr m_data; | |
| 355 }; | |
| 356 } | |
| 357 | |
| 358 static std::vector<uint8_t> makeWavHeader( const wavWriterSetup_t & setup, t_filesize dataSize, abort_callback & aborter ) { | |
| 359 std::vector<uint8_t> ret; | |
| 360 file::ptr temp; filesystem::g_open_tempmem( temp, aborter ); | |
| 361 { | |
| 362 CWavWriter w; | |
| 363 w.open( temp, setup, aborter ); | |
| 364 } | |
| 365 const size_t s = pfc::downcast_guarded<size_t>( temp->get_size( aborter ) ); | |
| 366 if (s > 0) { | |
| 367 ret.resize( s ); | |
| 368 temp->seek( 0, aborter ); | |
| 369 temp->read_object( &ret[0], s, aborter ); | |
| 370 } | |
| 371 return ret; | |
| 372 } | |
| 373 | |
| 374 file::ptr makeLiveWAVFile( const wavWriterSetup_t & setup, file::ptr data ) { | |
| 375 t_filesize size = data->get_size( fb2k::noAbort ); | |
| 376 auto vec = makeWavHeader( setup, size, fb2k::noAbort ); | |
| 377 return new service_impl_t< fileWav >( std::move(vec), data ); | |
| 378 } |
