Avoid raw lock/unlock calls
[openal-soft.git] / alc / backends / wave.cpp
blob3b0a30d88b903d053ea3f80da6c724a4ffec8fa9
1 /**
2 * OpenAL cross platform audio library
3 * Copyright (C) 1999-2007 by authors.
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
14 * You should have received a copy of the GNU Library General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 * Or go to http://www.gnu.org/copyleft/lgpl.html
21 #include "config.h"
23 #include "backends/wave.h"
25 #include <algorithm>
26 #include <atomic>
27 #include <cerrno>
28 #include <chrono>
29 #include <cstdint>
30 #include <cstdio>
31 #include <cstring>
32 #include <exception>
33 #include <functional>
34 #include <thread>
36 #include "AL/al.h"
38 #include "albyte.h"
39 #include "alcmain.h"
40 #include "alconfig.h"
41 #include "alexcpt.h"
42 #include "almalloc.h"
43 #include "alnumeric.h"
44 #include "alu.h"
45 #include "compat.h"
46 #include "endiantest.h"
47 #include "logging.h"
48 #include "strutils.h"
49 #include "threads.h"
50 #include "vector.h"
53 namespace {
55 using std::chrono::seconds;
56 using std::chrono::milliseconds;
57 using std::chrono::nanoseconds;
59 constexpr ALCchar waveDevice[] = "Wave File Writer";
61 constexpr ALubyte SUBTYPE_PCM[]{
62 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa,
63 0x00, 0x38, 0x9b, 0x71
65 constexpr ALubyte SUBTYPE_FLOAT[]{
66 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa,
67 0x00, 0x38, 0x9b, 0x71
70 constexpr ALubyte SUBTYPE_BFORMAT_PCM[]{
71 0x01, 0x00, 0x00, 0x00, 0x21, 0x07, 0xd3, 0x11, 0x86, 0x44, 0xc8, 0xc1,
72 0xca, 0x00, 0x00, 0x00
75 constexpr ALubyte SUBTYPE_BFORMAT_FLOAT[]{
76 0x03, 0x00, 0x00, 0x00, 0x21, 0x07, 0xd3, 0x11, 0x86, 0x44, 0xc8, 0xc1,
77 0xca, 0x00, 0x00, 0x00
80 void fwrite16le(ALushort val, FILE *f)
82 ALubyte data[2]{ static_cast<ALubyte>(val&0xff), static_cast<ALubyte>((val>>8)&0xff) };
83 fwrite(data, 1, 2, f);
86 void fwrite32le(ALuint val, FILE *f)
88 ALubyte data[4]{ static_cast<ALubyte>(val&0xff), static_cast<ALubyte>((val>>8)&0xff),
89 static_cast<ALubyte>((val>>16)&0xff), static_cast<ALubyte>((val>>24)&0xff) };
90 fwrite(data, 1, 4, f);
94 struct WaveBackend final : public BackendBase {
95 WaveBackend(ALCdevice *device) noexcept : BackendBase{device} { }
96 ~WaveBackend() override;
98 int mixerProc();
100 void open(const ALCchar *name) override;
101 bool reset() override;
102 bool start() override;
103 void stop() override;
105 FILE *mFile{nullptr};
106 long mDataStart{-1};
108 al::vector<al::byte> mBuffer;
110 std::atomic<bool> mKillNow{true};
111 std::thread mThread;
113 DEF_NEWDEL(WaveBackend)
116 WaveBackend::~WaveBackend()
118 if(mFile)
119 fclose(mFile);
120 mFile = nullptr;
123 int WaveBackend::mixerProc()
125 const milliseconds restTime{mDevice->UpdateSize*1000/mDevice->Frequency / 2};
127 althrd_setname(MIXER_THREAD_NAME);
129 const ALuint frameSize{mDevice->frameSizeFromFmt()};
131 int64_t done{0};
132 auto start = std::chrono::steady_clock::now();
133 while(!mKillNow.load(std::memory_order_acquire) &&
134 mDevice->Connected.load(std::memory_order_acquire))
136 auto now = std::chrono::steady_clock::now();
138 /* This converts from nanoseconds to nanosamples, then to samples. */
139 int64_t avail{std::chrono::duration_cast<seconds>((now-start) *
140 mDevice->Frequency).count()};
141 if(avail-done < mDevice->UpdateSize)
143 std::this_thread::sleep_for(restTime);
144 continue;
146 while(avail-done >= mDevice->UpdateSize)
149 std::lock_guard<WaveBackend> _{*this};
150 aluMixData(mDevice, mBuffer.data(), mDevice->UpdateSize);
152 done += mDevice->UpdateSize;
154 if(!IS_LITTLE_ENDIAN)
156 const ALuint bytesize{mDevice->bytesFromFmt()};
158 if(bytesize == 2)
160 ALushort *samples = reinterpret_cast<ALushort*>(mBuffer.data());
161 const size_t len{mBuffer.size() / 2};
162 for(size_t i{0};i < len;i++)
164 const ALushort samp{samples[i]};
165 samples[i] = static_cast<ALushort>((samp>>8) | (samp<<8));
168 else if(bytesize == 4)
170 ALuint *samples = reinterpret_cast<ALuint*>(mBuffer.data());
171 const size_t len{mBuffer.size() / 4};
172 for(size_t i{0};i < len;i++)
174 const ALuint samp{samples[i]};
175 samples[i] = (samp>>24) | ((samp>>8)&0x0000ff00) |
176 ((samp<<8)&0x00ff0000) | (samp<<24);
181 size_t fs{fwrite(mBuffer.data(), frameSize, mDevice->UpdateSize, mFile)};
182 (void)fs;
183 if(ferror(mFile))
185 ERR("Error writing to file\n");
186 aluHandleDisconnect(mDevice, "Failed to write playback samples");
187 break;
191 /* For every completed second, increment the start time and reduce the
192 * samples done. This prevents the difference between the start time
193 * and current time from growing too large, while maintaining the
194 * correct number of samples to render.
196 if(done >= mDevice->Frequency)
198 seconds s{done/mDevice->Frequency};
199 start += s;
200 done -= mDevice->Frequency*s.count();
204 return 0;
207 void WaveBackend::open(const ALCchar *name)
209 const char *fname{GetConfigValue(nullptr, "wave", "file", "")};
210 if(!fname[0]) throw al::backend_exception{ALC_INVALID_VALUE, "No wave output filename"};
212 if(!name)
213 name = waveDevice;
214 else if(strcmp(name, waveDevice) != 0)
215 throw al::backend_exception{ALC_INVALID_VALUE, "Device name \"%s\" not found", name};
217 #ifdef _WIN32
219 std::wstring wname = utf8_to_wstr(fname);
220 mFile = _wfopen(wname.c_str(), L"wb");
222 #else
223 mFile = fopen(fname, "wb");
224 #endif
225 if(!mFile)
227 ERR("Could not open file '%s': %s\n", fname, strerror(errno));
228 throw al::backend_exception{ALC_INVALID_VALUE, "Could not open file '%s': %s", fname,
229 strerror(errno)};
232 mDevice->DeviceName = name;
235 bool WaveBackend::reset()
237 ALuint channels=0, bytes=0, chanmask=0;
238 int isbformat = 0;
239 size_t val;
241 fseek(mFile, 0, SEEK_SET);
242 clearerr(mFile);
244 if(GetConfigValueBool(nullptr, "wave", "bformat", 0))
246 mDevice->FmtChans = DevFmtAmbi3D;
247 mDevice->mAmbiOrder = 1;
250 switch(mDevice->FmtType)
252 case DevFmtByte:
253 mDevice->FmtType = DevFmtUByte;
254 break;
255 case DevFmtUShort:
256 mDevice->FmtType = DevFmtShort;
257 break;
258 case DevFmtUInt:
259 mDevice->FmtType = DevFmtInt;
260 break;
261 case DevFmtUByte:
262 case DevFmtShort:
263 case DevFmtInt:
264 case DevFmtFloat:
265 break;
267 switch(mDevice->FmtChans)
269 case DevFmtMono: chanmask = 0x04; break;
270 case DevFmtStereo: chanmask = 0x01 | 0x02; break;
271 case DevFmtQuad: chanmask = 0x01 | 0x02 | 0x10 | 0x20; break;
272 case DevFmtX51: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x200 | 0x400; break;
273 case DevFmtX51Rear: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x010 | 0x020; break;
274 case DevFmtX61: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x100 | 0x200 | 0x400; break;
275 case DevFmtX71: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x010 | 0x020 | 0x200 | 0x400; break;
276 case DevFmtAmbi3D:
277 /* .amb output requires FuMa */
278 mDevice->mAmbiOrder = minu(mDevice->mAmbiOrder, 3);
279 mDevice->mAmbiLayout = AmbiLayout::FuMa;
280 mDevice->mAmbiScale = AmbiNorm::FuMa;
281 isbformat = 1;
282 chanmask = 0;
283 break;
285 bytes = mDevice->bytesFromFmt();
286 channels = mDevice->channelsFromFmt();
288 rewind(mFile);
290 fputs("RIFF", mFile);
291 fwrite32le(0xFFFFFFFF, mFile); // 'RIFF' header len; filled in at close
293 fputs("WAVE", mFile);
295 fputs("fmt ", mFile);
296 fwrite32le(40, mFile); // 'fmt ' header len; 40 bytes for EXTENSIBLE
298 // 16-bit val, format type id (extensible: 0xFFFE)
299 fwrite16le(0xFFFE, mFile);
300 // 16-bit val, channel count
301 fwrite16le(static_cast<ALushort>(channels), mFile);
302 // 32-bit val, frequency
303 fwrite32le(mDevice->Frequency, mFile);
304 // 32-bit val, bytes per second
305 fwrite32le(mDevice->Frequency * channels * bytes, mFile);
306 // 16-bit val, frame size
307 fwrite16le(static_cast<ALushort>(channels * bytes), mFile);
308 // 16-bit val, bits per sample
309 fwrite16le(static_cast<ALushort>(bytes * 8), mFile);
310 // 16-bit val, extra byte count
311 fwrite16le(22, mFile);
312 // 16-bit val, valid bits per sample
313 fwrite16le(static_cast<ALushort>(bytes * 8), mFile);
314 // 32-bit val, channel mask
315 fwrite32le(chanmask, mFile);
316 // 16 byte GUID, sub-type format
317 val = fwrite((mDevice->FmtType == DevFmtFloat) ?
318 (isbformat ? SUBTYPE_BFORMAT_FLOAT : SUBTYPE_FLOAT) :
319 (isbformat ? SUBTYPE_BFORMAT_PCM : SUBTYPE_PCM), 1, 16, mFile);
320 (void)val;
322 fputs("data", mFile);
323 fwrite32le(0xFFFFFFFF, mFile); // 'data' header len; filled in at close
325 if(ferror(mFile))
327 ERR("Error writing header: %s\n", strerror(errno));
328 return false;
330 mDataStart = ftell(mFile);
332 SetDefaultWFXChannelOrder(mDevice);
334 const ALuint bufsize{mDevice->frameSizeFromFmt() * mDevice->UpdateSize};
335 mBuffer.resize(bufsize);
337 return true;
340 bool WaveBackend::start()
342 try {
343 mKillNow.store(false, std::memory_order_release);
344 mThread = std::thread{std::mem_fn(&WaveBackend::mixerProc), this};
345 return true;
347 catch(std::exception& e) {
348 ERR("Failed to start mixing thread: %s\n", e.what());
350 catch(...) {
352 return false;
355 void WaveBackend::stop()
357 if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable())
358 return;
359 mThread.join();
361 long size{ftell(mFile)};
362 if(size > 0)
364 long dataLen{size - mDataStart};
365 if(fseek(mFile, mDataStart-4, SEEK_SET) == 0)
366 fwrite32le(static_cast<ALuint>(dataLen), mFile); // 'data' header len
367 if(fseek(mFile, 4, SEEK_SET) == 0)
368 fwrite32le(static_cast<ALuint>(size-8), mFile); // 'WAVE' header len
372 } // namespace
375 bool WaveBackendFactory::init()
376 { return true; }
378 bool WaveBackendFactory::querySupport(BackendType type)
379 { return type == BackendType::Playback; }
381 void WaveBackendFactory::probe(DevProbe type, std::string *outnames)
383 switch(type)
385 case DevProbe::Playback:
386 /* Includes null char. */
387 outnames->append(waveDevice, sizeof(waveDevice));
388 break;
389 case DevProbe::Capture:
390 break;
394 BackendPtr WaveBackendFactory::createBackend(ALCdevice *device, BackendType type)
396 if(type == BackendType::Playback)
397 return BackendPtr{new WaveBackend{device}};
398 return nullptr;
401 BackendFactory &WaveBackendFactory::getFactory()
403 static WaveBackendFactory factory{};
404 return factory;