vkQuake2 doxygen  1.0 dev
snd_miniaudio.c
Go to the documentation of this file.
1 /*
2 Copyright (C) 2018-2019 Krzysztof Kondrak
3 
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8 
9 This program 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.
12 
13 See the GNU General Public License for more details.
14 
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18 
19 */
20 
21 // music playback using Miniaudio library (https://github.com/dr-soft/miniaudio)
22 
23 #ifdef _WIN32
24 # include <windows.h>
25 #endif
26 // force ALSA on Linux to avoid playback issues when using mixed backends
27 #ifdef __linux__
28 # define MA_NO_PULSEAUDIO
29 # define MA_NO_JACK
30 #endif
31 
32 #define DR_FLAC_IMPLEMENTATION
33 #include "miniaudio/dr_flac.h" /* Enables FLAC decoding. */
34 #define DR_MP3_IMPLEMENTATION
35 #include "miniaudio/dr_mp3.h" /* Enables MP3 decoding. */
36 #define DR_WAV_IMPLEMENTATION
37 #include "miniaudio/dr_wav.h" /* Enables WAV decoding. */
38 
39 #include "miniaudio/stb_vorbis.c" /* Enables OGG/Vorbis decoding. */
40 
41 #define MINIAUDIO_IMPLEMENTATION
42 #include "miniaudio.h"
43 
44 #include "../client/client.h"
45 
48 static int loopcounter;
49 static int playTrack = 0;
50 static qboolean enabled = true;
51 static qboolean paused = false;
52 static qboolean playLooping = false;
53 static qboolean trackFinished = false;
54 
55 static cvar_t *cd_nocd;
58 
59 #ifdef __APPLE__
60 static ma_uint32 bufferSizeInFrames;
61 
62 static UInt32 GetBytesPerSampleFrame()
63 {
64  AudioDeviceID outputDeviceID;
65  AudioStreamBasicDescription outputStreamBasicDescription;
66  AudioObjectPropertyAddress propertyAddress = {
67  kAudioHardwarePropertyDefaultOutputDevice,
68  kAudioObjectPropertyScopeOutput,
69  kAudioObjectPropertyElementMaster
70  };
71 
72  UInt32 propertySize = sizeof(outputDeviceID);
73  OSStatus status = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propertyAddress, 0, NULL, &propertySize, &outputDeviceID);
74  if (status != kAudioHardwareNoError) {
75  Com_Printf("AudioHardwareGetProperty returned %d\n", status);
76  return 8;
77  }
78 
79  propertySize = sizeof(outputStreamBasicDescription);
80  propertyAddress.mSelector = kAudioDevicePropertyStreamFormat;
81  status = AudioObjectGetPropertyData(outputDeviceID, &propertyAddress, 0, NULL, &propertySize, &outputStreamBasicDescription);
82  if (status != kAudioHardwareNoError) {
83  Com_Printf("AudioDeviceGetProperty: returned %d when getting kAudioDevicePropertyStreamFormat\n", status);
84  return 8;
85  }
86 
87  return outputStreamBasicDescription.mBytesPerFrame;
88 }
89 #endif
90 
91 static void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount)
92 {
93  ma_decoder* pDecoder = (ma_decoder*)pDevice->pUserData;
94  if (pDecoder == NULL) {
95  return;
96  }
97 
98  // playback completed
99  if (!ma_decoder_read_pcm_frames(pDecoder, pOutput, frameCount))
100  {
101  trackFinished = true;
102  }
103 
104  (void)pInput;
105 }
106 
107 static void Miniaudio_Pause(void)
108 {
110  return;
111 
113  paused = true;
114 }
115 
116 static void Miniaudio_Resume(void)
117 {
118  if (!enabled || !paused)
119  return;
120 
122  paused = false;
123 }
124 
125 static void Miniaudio_f(void)
126 {
127  char *command;
128 
129  if (Cmd_Argc() < 2)
130  return;
131 
132  command = Cmd_Argv(1);
133 
134  if (Q_strcasecmp(command, "on") == 0)
135  {
136  enabled = true;
137  return;
138  }
139 
140  if (Q_strcasecmp(command, "off") == 0)
141  {
142  Miniaudio_Stop();
143  enabled = false;
144  return;
145  }
146 
147  if (Q_strcasecmp(command, "play") == 0)
148  {
149  Miniaudio_Play(atoi(Cmd_Argv(2)), false);
150  return;
151  }
152 
153  if (Q_strcasecmp(command, "loop") == 0)
154  {
155  Miniaudio_Play(atoi(Cmd_Argv(2)), true);
156  return;
157  }
158 
159  if (Q_strcasecmp(command, "stop") == 0)
160  {
161  Miniaudio_Stop();
162  return;
163  }
164 
165  if (Q_strcasecmp(command, "pause") == 0)
166  {
167  Miniaudio_Pause();
168  return;
169  }
170 
171  if (Q_strcasecmp(command, "resume") == 0)
172  {
174  return;
175  }
176 
177  if (Q_strcasecmp(command, "info") == 0)
178  {
179  if (device.pContext)
180  Com_Printf("Using %s backend. ", ma_get_backend_name(device.pContext->backend));
181  else
182  Com_Printf("No audio backend enabled. ");
183 
185  Com_Printf("Currently %s track %u\n", playLooping ? "looping" : "playing", playTrack);
186  else if (paused)
187  Com_Printf("Paused %s track %u\n", playLooping ? "looping" : "playing", playTrack);
188  else
189  Com_Printf("No music is playing.\n");
190  return;
191  }
192 }
193 
194 
195 void Miniaudio_Init(void)
196 {
197  cd_nocd = Cvar_Get("cd_nocd", "0", CVAR_ARCHIVE);
198  cd_loopcount = Cvar_Get("cd_loopcount", "4", 0);
199  cd_looptrack = Cvar_Get("cd_looptrack", "11", 0);
200  enabled = true;
201  paused = false;
202 
203 #ifdef __APPLE__
204  bufferSizeInFrames = Cvar_VariableValue("s_chunksize") * sizeof(float) / GetBytesPerSampleFrame();
205 #endif
206  Cmd_AddCommand("miniaudio", Miniaudio_f);
207 }
208 
209 static ma_result LoadTrack(const char *gamedir, int track)
210 {
211  ma_result result;
212  int trackExtIdx = 0;
213  static char *trackExts[] = { "ogg", "flac", "mp3", "wav" };
214 
215  do
216  {
217  char trackPath[64];
218  Com_sprintf(trackPath, sizeof(trackPath), "%s/music/track%s%i.%s", gamedir, track < 10 ? "0" : "", track, trackExts[trackExtIdx]);
219  result = ma_decoder_init_file(trackPath, NULL, &decoder);
220  } while (result != MA_SUCCESS && ++trackExtIdx < 4);
221 
222  return result;
223 }
224 
225 void Miniaudio_Play(int track, qboolean looping)
226 {
227  ma_result result;
228  ma_device_config deviceConfig;
229 
230  if (!enabled || playTrack == track)
231  return;
232 
233  Miniaudio_Stop();
234 
235  // ignore invalid tracks
236  if (track < 1)
237  return;
238 
239  result = LoadTrack(FS_Gamedir(), track);
240 
241  // try the baseq2 folder if loading the track from a custom gamedir failed
242  if (result != MA_SUCCESS && Q_stricmp(FS_Gamedir(), "./"BASEDIRNAME) != 0)
243  {
244  result = LoadTrack(BASEDIRNAME, track);
245  }
246 
247  if (result != MA_SUCCESS)
248  {
249  Com_Printf("Failed to open %s/music/track%s%i.[ogg/flac/mp3/wav]: error %i\n", FS_Gamedir(), track < 10 ? "0" : "", track, result);
250  return;
251  }
252 
254  deviceConfig.playback.format = decoder.outputFormat;
255  deviceConfig.playback.channels = decoder.outputChannels;
256  deviceConfig.sampleRate = decoder.outputSampleRate;
257  deviceConfig.dataCallback = data_callback;
258  deviceConfig.pUserData = &decoder;
259 #ifdef __APPLE__
260  deviceConfig.bufferSizeInFrames = bufferSizeInFrames;
261  deviceConfig.periods = 1;
262 #endif
263 
264  if (ma_device_init(NULL, &deviceConfig, &device) != MA_SUCCESS) {
265  Com_Printf("Failed to open playback device: error %i\n", result);
267  return;
268  }
269 
270  if (ma_device_start(&device) != MA_SUCCESS) {
271  Com_Printf("Failed to start playback device: error %i\n", result);
274  return;
275  }
276 
277  loopcounter = 0;
278  playTrack = track;
279  playLooping = looping;
280  paused = false;
281  trackFinished = false;
282 
283  if (Cvar_VariableValue("cd_nocd"))
284  Miniaudio_Pause();
285 }
286 
287 void Miniaudio_Stop(void)
288 {
290  return;
291 
292  paused = false;
293  playTrack = 0;
294 
297 }
298 
300 {
301  if (cd_nocd->value != !enabled)
302  {
303  if (cd_nocd->value)
304  {
305  Miniaudio_Pause();
306  enabled = false;
307  }
308  else
309  {
310  enabled = true;
311  int track = atoi(cl.configstrings[CS_CDTRACK]);
312  if (!paused || playTrack != track)
313  {
314  if ((playTrack == 0 && !playLooping) || (playTrack > 0 && playTrack != track))
315  Miniaudio_Play(track, true);
316  else
318  }
319  else
321  }
322  }
323 
325  {
326  trackFinished = false;
327  if (playLooping)
328  {
329  // if the track has played the given number of times,
330  // go to the ambient track
331  if (++loopcounter >= cd_loopcount->value)
332  {
333  // ambient track will be played for the first time, so we need to load it
335  {
336  Miniaudio_Stop();
338  }
339  else
340  {
342  }
343  }
344  else
345  {
347  }
348  }
349  }
350 }
351 
353 {
354  Miniaudio_Stop();
355  Cmd_RemoveCommand("miniaudio");
356 }
LoadTrack
static ma_result LoadTrack(const char *gamedir, int track)
Definition: snd_miniaudio.c:209
trackFinished
static qboolean trackFinished
Definition: snd_miniaudio.c:53
dr_flac.h
decoder
static ma_decoder decoder
Definition: snd_miniaudio.c:46
stb_vorbis.c
Miniaudio_Pause
static void Miniaudio_Pause(void)
Definition: snd_miniaudio.c:107
CS_CDTRACK
#define CS_CDTRACK
Definition: q_shared.h:1102
BASEDIRNAME
#define BASEDIRNAME
Definition: qcommon.h:28
Miniaudio_Play
void Miniaudio_Play(int track, qboolean looping)
Definition: snd_miniaudio.c:225
dr_mp3.h
ma_get_backend_name
const char * ma_get_backend_name(ma_backend backend)
qboolean
qboolean
Definition: q_shared.h:63
ma_device_stop
ma_result ma_device_stop(ma_device *pDevice)
device
static ma_device device
Definition: snd_miniaudio.c:47
Miniaudio_Shutdown
void Miniaudio_Shutdown(void)
Definition: snd_miniaudio.c:352
ma_device_config_init
ma_device_config ma_device_config_init(ma_device_type deviceType)
ma_device_config::pUserData
void * pUserData
Definition: miniaudio.h:1901
ma_device_start
ma_result ma_device_start(ma_device *pDevice)
ma_device_config::dataCallback
ma_device_callback_proc dataCallback
Definition: miniaudio.h:1899
Cvar_Get
cvar_t * Cvar_Get(char *var_name, char *var_value, int flags)
Definition: cvar.c:127
cd_nocd
static cvar_t * cd_nocd
Definition: snd_miniaudio.c:55
paused
static qboolean paused
Definition: snd_miniaudio.c:51
cvar_s
Definition: q_shared.h:324
ma_decoder::outputFormat
ma_format outputFormat
Definition: miniaudio.h:3031
Cmd_Argv
char * Cmd_Argv(int arg)
Definition: cmd.c:517
ma_decoder_read_pcm_frames
ma_uint64 ma_decoder_read_pcm_frames(ma_decoder *pDecoder, void *pFramesOut, ma_uint64 frameCount)
Cmd_Argc
int Cmd_Argc(void)
Definition: cmd.c:507
Cmd_RemoveCommand
void Cmd_RemoveCommand(char *cmd_name)
Definition: cmd.c:724
Miniaudio_Resume
static void Miniaudio_Resume(void)
Definition: snd_miniaudio.c:116
ma_device_uninit
void ma_device_uninit(ma_device *pDevice)
dr_wav.h
Cmd_AddCommand
void Cmd_AddCommand(char *cmd_name, xcommand_t function)
Definition: cmd.c:691
playTrack
static int playTrack
Definition: snd_miniaudio.c:49
CVAR_ARCHIVE
#define CVAR_ARCHIVE
Definition: q_shared.h:316
cvar_s::value
float value
Definition: q_shared.h:331
ma_device_init
ma_result ma_device_init(ma_context *pContext, const ma_device_config *pConfig, ma_device *pDevice)
cd_loopcount
static cvar_t * cd_loopcount
Definition: snd_miniaudio.c:56
enabled
static qboolean enabled
Definition: snd_miniaudio.c:50
NULL
#define NULL
Definition: q_shared.h:67
Miniaudio_Stop
void Miniaudio_Stop(void)
Definition: snd_miniaudio.c:287
ma_decoder
Definition: miniaudio.h:3021
MA_SUCCESS
#define MA_SUCCESS
Definition: miniaudio.h:673
ma_decoder_init_file
ma_result ma_decoder_init_file(const char *pFilePath, const ma_decoder_config *pConfig, ma_decoder *pDecoder)
Q_stricmp
int Q_stricmp(char *s1, char *s2)
Definition: q_shared.c:1180
ma_device_type_playback
@ ma_device_type_playback
Definition: miniaudio.h:1806
ma_device_config::bufferSizeInFrames
ma_uint32 bufferSizeInFrames
Definition: miniaudio.h:1895
ma_uint32
uint32_t ma_uint32
Definition: miniaudio.h:520
ma_device_is_started
ma_bool32 ma_device_is_started(ma_device *pDevice)
ma_device_config::format
ma_format format
Definition: miniaudio.h:1905
playLooping
static qboolean playLooping
Definition: snd_miniaudio.c:52
miniaudio.h
loopcounter
static int loopcounter
Definition: snd_miniaudio.c:48
ma_device_config::periods
ma_uint32 periods
Definition: miniaudio.h:1897
FS_Gamedir
char * FS_Gamedir(void)
Definition: files.c:559
data_callback
static void data_callback(ma_device *pDevice, void *pOutput, const void *pInput, ma_uint32 frameCount)
Definition: snd_miniaudio.c:91
ma_decoder_uninit
ma_result ma_decoder_uninit(ma_decoder *pDecoder)
ma_device_config::channels
ma_uint32 channels
Definition: miniaudio.h:1906
ma_decoder::outputChannels
ma_uint32 outputChannels
Definition: miniaudio.h:3032
ma_device
struct ma_device ma_device
Definition: miniaudio.h:612
client_state_t::configstrings
char configstrings[MAX_CONFIGSTRINGS][MAX_QPATH]
Definition: client.h:158
ma_device_config
Definition: miniaudio.h:1891
Miniaudio_Update
void Miniaudio_Update(void)
Definition: snd_miniaudio.c:299
ma_device_config::sampleRate
ma_uint32 sampleRate
Definition: miniaudio.h:1894
Com_Printf
void Com_Printf(char *fmt,...)
Definition: common.c:104
Q_strcasecmp
int Q_strcasecmp(char *s1, char *s2)
Definition: q_shared.c:1216
ma_result
int ma_result
Definition: miniaudio.h:672
cd_looptrack
static cvar_t * cd_looptrack
Definition: snd_miniaudio.c:57
Miniaudio_Init
void Miniaudio_Init(void)
Definition: snd_miniaudio.c:195
cl
client_state_t cl
Definition: cl_main.c:91
Miniaudio_f
static void Miniaudio_f(void)
Definition: snd_miniaudio.c:125
ma_device_config::playback
struct ma_device_config::@28 playback
Com_sprintf
void Com_sprintf(char *dest, int size, char *fmt,...)
Definition: q_shared.c:1223
ma_decoder::pUserData
void * pUserData
Definition: miniaudio.h:3025
void
void(APIENTRY *qglAccum)(GLenum op
ma_decoder_seek_to_pcm_frame
ma_result ma_decoder_seek_to_pcm_frame(ma_decoder *pDecoder, ma_uint64 frameIndex)
ma_decoder::outputSampleRate
ma_uint32 outputSampleRate
Definition: miniaudio.h:3033
Cvar_VariableValue
float Cvar_VariableValue(char *var_name)
Definition: cvar.c:63