1 /** 2 * OpenAL cross platform audio library 3 * Copyright (C) 2010 by Chris Robinson 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. 8 * 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. 13 * 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 19 */ 20 21 #include "config.h" 22 23 #include "backends/null.h" AA24 25 #include <exception> 26 #include <atomic> 27 #include <chrono> 28 #include <cstdint> 29 #include <cstring> 30 #include <functional> 31 #include <thread> 32 33 #include "alcmain.h" 34 #include "almalloc.h" 35 #include "alu.h" 36 #include "threads.h" 37 38 39 namespace { 40 41 using std::chrono::seconds; 42 using std::chrono::milliseconds; 43 using std::chrono::nanoseconds; 44 45 constexpr char nullDevice[] = "No Output"; 46 47 48 struct NullBackend final : public BackendBase { 49 NullBackend(ALCdevice *device) noexcept : BackendBase{device} { } 50 51 int mixerProc(); 52 53 void open(const char *name) override; 54 bool reset() override; 55 void start() override; 56 void stop() override; 57 58 std::atomic<bool> mKillNow{true}; 59 std::thread mThread; 60 61 DEF_NEWDEL(NullBackend) 62 }; 63 64 int NullBackend::mixerProc() 65 { 66 const milliseconds restTime{mDevice->UpdateSize*1000/mDevice->Frequency / 2}; 67 68 SetRTPriority(); 69 althrd_setname(MIXER_THREAD_NAME); 70 71 int64_t done{0}; 72 auto start = std::chrono::steady_clock::now(); 73 while(!mKillNow.load(std::memory_order_acquire) 74 && mDevice->Connected.load(std::memory_order_acquire)) 75 { 76 auto now = std::chrono::steady_clock::now(); 77 78 /* This converts from nanoseconds to nanosamples, then to samples. */ 79 int64_t avail{std::chrono::duration_cast<seconds>((now-start) * mDevice->Frequency).count()}; 80 if(avail-done < mDevice->UpdateSize) 81 { 82 std::this_thread::sleep_for(restTime); 83 continue; 84 } 85 while(avail-done >= mDevice->UpdateSize) 86 { 87 mDevice->renderSamples(nullptr, mDevice->UpdateSize, 0u); 88 done += mDevice->UpdateSize; 89 } 90 91 /* For every completed second, increment the start time and reduce the 92 * samples done. This prevents the difference between the start time 93 * and current time from growing too large, while maintaining the 94 * correct number of samples to render. 95 */ 96 if(done >= mDevice->Frequency) 97 { 98 seconds s{done/mDevice->Frequency}; 99 start += s; 100 done -= mDevice->Frequency*s.count(); 101 } 102 } 103 104 return 0; 105 } 106 107 108 void NullBackend::open(const char *name) 109 { 110 if(!name) 111 name = nullDevice; 112 else if(strcmp(name, nullDevice) != 0) 113 throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", 114 name}; 115 116 mDevice->DeviceName = name; 117 } 118 119 bool NullBackend::reset() 120 { 121 setDefaultWFXChannelOrder(); 122 return true; 123 } 124 125 void NullBackend::start() 126 { 127 try { 128 mKillNow.store(false, std::memory_order_release); 129 mThread = std::thread{std::mem_fn(&NullBackend::mixerProc), this}; 130 } 131 catch(std::exception& e) { 132 throw al::backend_exception{al::backend_error::DeviceError, 133 "Failed to start mixing thread: %s", e.what()}; 134 } 135 } 136 137 void NullBackend::stop() 138 { 139 if(mKillNow.exchange(true, std::memory_order_acq_rel) || !mThread.joinable()) 140 return; 141 mThread.join(); 142 } 143 144 } // namespace 145 146 147 bool NullBackendFactory::init() 148 { return true; } 149 150 bool NullBackendFactory::querySupport(BackendType type) 151 { return (type == BackendType::Playback); } 152 153 std::string NullBackendFactory::probe(BackendType type) 154 { 155 std::string outnames; 156 switch(type) 157 { 158 case BackendType::Playback: 159 /* Includes null char. */ 160 outnames.append(nullDevice, sizeof(nullDevice)); 161 break; 162 case BackendType::Capture: 163 break; 164 } 165 return outnames; 166 } 167 168 BackendPtr NullBackendFactory::createBackend(ALCdevice *device, BackendType type) 169 { 170 if(type == BackendType::Playback) 171 return BackendPtr{new NullBackend{device}}; 172 return nullptr; 173 } 174 175 BackendFactory &NullBackendFactory::getFactory() 176 { 177 static NullBackendFactory factory{}; 178 return factory; 179 } 180