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