Project

General

Profile

sdlout_sdl2.cc

SDL2 version of sdlout.cc - Jim Turner, October 23, 2016 20:58

 
1
/*
2
 * SDL Output Plugin for Audacious
3
 * Copyright 2010 John Lindgren
4
 *
5
 * Redistribution and use in source and binary forms, with or without
6
 * modification, are permitted provided that the following conditions are met:
7
 *
8
 * 1. Redistributions of source code must retain the above copyright notice,
9
 *    this list of conditions, and the following disclaimer.
10
 *
11
 * 2. Redistributions in binary form must reproduce the above copyright notice,
12
 *    this list of conditions, and the following disclaimer in the documentation
13
 *    provided with the distribution.
14
 *
15
 * This software is provided "as is" and without any warranty, express or
16
 * implied. In no event shall the authors be liable for any damages arising from
17
 * the use of this software.
18
 */
19

    
20
#include <math.h>
21
#include <pthread.h>
22
#include <string.h>
23
#include <sys/time.h>
24

    
25
#include <SDL.h>
26
#include <SDL_audio.h>
27

    
28
#include <libaudcore/audstrings.h>
29
#include <libaudcore/i18n.h>
30
#include <libaudcore/plugin.h>
31
#include <libaudcore/ringbuf.h>
32
#include <libaudcore/runtime.h>
33

    
34
#define VOLUME_RANGE 40 /* decibels */
35

    
36
class SDLOutput : public OutputPlugin
37
{
38
public:
39
    static const char about[];
40
    static const char * const defaults[];
41

    
42
    static constexpr PluginInfo info = {
43
        N_("SDL Output"),
44
        PACKAGE,
45
        about
46
    };
47

    
48
    constexpr SDLOutput () : OutputPlugin (info, 1) {}
49

    
50
    bool init ();
51
    void cleanup ();
52

    
53
    StereoVolume get_volume ();
54
    void set_volume (StereoVolume v);
55

    
56
    bool open_audio (int aud_format, int rate, int chans, String & error);
57
    void close_audio ();
58

    
59
    void period_wait ();
60
    int write_audio (const void * data, int size);
61
    void drain ();
62

    
63
    int get_delay ();
64

    
65
    void pause (bool pause);
66
    void flush ();
67
};
68

    
69
EXPORT SDLOutput aud_plugin_instance;
70

    
71
const char SDLOutput::about[] =
72
 N_("SDL Output Plugin for Audacious\n"
73
    "Copyright 2010 John Lindgren");
74

    
75
const char * const SDLOutput::defaults[] = {
76
 "vol_left", "100",
77
 "vol_right", "100",
78
 nullptr};
79

    
80
static pthread_mutex_t sdlout_mutex = PTHREAD_MUTEX_INITIALIZER;
81
static pthread_cond_t sdlout_cond = PTHREAD_COND_INITIALIZER;
82

    
83
static volatile int vol_left, vol_right;
84

    
85
static int sdlout_chan, sdlout_rate;
86

    
87
static RingBuf<unsigned char> buffer;
88

    
89
static bool prebuffer_flag, paused_flag;
90

    
91
static int block_delay;
92
static struct timeval block_time;
93
static SDL_AudioDeviceID ouraudiodevice;
94

    
95
bool SDLOutput::init ()
96
{
97
    aud_config_set_defaults ("sdlout", defaults);
98

    
99
    vol_left = aud_get_int ("sdlout", "vol_left");
100
    vol_right = aud_get_int ("sdlout", "vol_right");
101

    
102
    if (SDL_Init (SDL_INIT_AUDIO) < 0)
103
    {
104
        AUDERR ("Failed to init SDL: %s.\n", SDL_GetError ());
105
        return false;
106
    }
107

    
108
    return true;
109
}
110

    
111
void SDLOutput::cleanup ()
112
{
113
    SDL_Quit ();
114
}
115

    
116
StereoVolume SDLOutput::get_volume ()
117
{
118
    return {vol_left, vol_right};
119
}
120

    
121
void SDLOutput::set_volume (StereoVolume v)
122
{
123
    vol_left = v.left;
124
    vol_right = v.right;
125

    
126
    aud_set_int ("sdlout", "vol_left", v.left);
127
    aud_set_int ("sdlout", "vol_right", v.right);
128
}
129

    
130
static void apply_mono_volume (unsigned char * data, int len)
131
{
132
    int vol = aud::max (vol_left, vol_right);
133
    int factor = (vol == 0) ? 0 : powf (10, (float) VOLUME_RANGE * (vol - 100)
134
     / 100 / 20) * 65536;
135

    
136
    int16_t * i = (int16_t *) data;
137
    int16_t * end = (int16_t *) (data + len);
138

    
139
    while (i < end)
140
    {
141
        * i = ((int) * i * factor) >> 16;
142
        i ++;
143
    }
144
}
145

    
146
static void apply_stereo_volume (unsigned char * data, int len)
147
{
148
    int factor_left = (vol_left == 0) ? 0 : powf (10, (float) VOLUME_RANGE *
149
     (vol_left - 100) / 100 / 20) * 65536;
150
    int factor_right = (vol_right == 0) ? 0 : powf (10, (float) VOLUME_RANGE *
151
     (vol_right - 100) / 100 / 20) * 65536;
152

    
153
    int16_t * i = (int16_t *) data;
154
    int16_t * end = (int16_t *) (data + len);
155

    
156
    while (i < end)
157
    {
158
        * i = ((int) * i * factor_left) >> 16;
159
        i ++;
160
        * i = ((int) * i * factor_right) >> 16;
161
        i ++;
162
    }
163
}
164

    
165
static void callback (void * user, unsigned char * buf, int len)
166
{
167
    pthread_mutex_lock (& sdlout_mutex);
168

    
169
    int copy = aud::min (len, buffer.len ());
170
    buffer.move_out (buf, copy);
171

    
172
    if (sdlout_chan == 2)
173
        apply_stereo_volume (buf, copy);
174
    else
175
        apply_mono_volume (buf, copy);
176

    
177
    if (copy < len)
178
        memset (buf + copy, 0, len - copy);
179

    
180
    /* At this moment, we know that there is a delay of (at least) the block of
181
     * data just written.  We save the block size and the current time for
182
     * estimating the delay later on. */
183
    block_delay = aud::rescale (copy / (2 * sdlout_chan), sdlout_rate, 1000);
184
    gettimeofday (& block_time, nullptr);
185

    
186
    pthread_cond_broadcast (& sdlout_cond);
187
    pthread_mutex_unlock (& sdlout_mutex);
188
}
189

    
190
bool SDLOutput::open_audio (int format, int rate, int chan, String & error)
191
{
192
    if (format != FMT_S16_NE)
193
    {
194
        error = String ("SDL error: Only signed 16-bit, native endian audio is supported.");
195
        return false;
196
    }
197

    
198
    AUDDBG ("Opening audio for %d channels, %d Hz.\n", chan, rate);
199

    
200
    sdlout_chan = chan;
201
    sdlout_rate = rate;
202

    
203
    int buffer_ms = aud_get_int (nullptr, "output_buffer_size");
204
    buffer.alloc (2 * chan * aud::rescale (buffer_ms, 1000, rate));
205

    
206
    prebuffer_flag = true;
207
    paused_flag = false;
208

    
209
    SDL_AudioSpec spec = {0};
210
    SDL_AudioSpec gotspec;
211

    
212
    spec.freq = rate;
213
    spec.format = AUDIO_S16;
214
    spec.channels = chan;
215
    spec.samples = 4096;
216
    spec.callback = callback;
217

    
218
    if (! (ouraudiodevice = SDL_OpenAudioDevice (NULL, 0, & spec, & gotspec, SDL_AUDIO_ALLOW_FORMAT_CHANGE)))
219
    {
220
        error = String (str_printf
221
         ("SDL error: Failed to open audio stream: %s.", SDL_GetError ()));
222
        buffer.destroy ();
223
        return false;
224
    }
225
    else if (spec.format != gotspec.format)
226
        AUDINFO ("We didn't get desired audio format, but playing anyway.");
227

    
228
    return true;
229
}
230

    
231
void SDLOutput::close_audio ()
232
{
233
    AUDDBG ("Closing audio.\n");
234
    if (ouraudiodevice)
235
        SDL_CloseAudioDevice (ouraudiodevice);
236
    buffer.destroy ();
237
}
238

    
239
static void check_started ()
240
{
241
    if (! prebuffer_flag)
242
        return;
243

    
244
    AUDDBG ("Starting playback.\n");
245
    prebuffer_flag = false;
246
    block_delay = 0;
247
    if (ouraudiodevice)
248
        SDL_PauseAudioDevice (ouraudiodevice, 0);
249
}
250

    
251
void SDLOutput::period_wait ()
252
{
253
    pthread_mutex_lock (& sdlout_mutex);
254

    
255
    while (! buffer.space ())
256
    {
257
        if (! paused_flag)
258
            check_started ();
259

    
260
        pthread_cond_wait (& sdlout_cond, & sdlout_mutex);
261
    }
262

    
263
    pthread_mutex_unlock (& sdlout_mutex);
264
}
265

    
266
int SDLOutput::write_audio (const void * data, int len)
267
{
268
    pthread_mutex_lock (& sdlout_mutex);
269

    
270
    len = aud::min (len, buffer.space ());
271
    buffer.copy_in ((const unsigned char *) data, len);
272

    
273
    pthread_mutex_unlock (& sdlout_mutex);
274
    return len;
275
}
276

    
277
void SDLOutput::drain ()
278
{
279
    AUDDBG ("Draining.\n");
280
    pthread_mutex_lock (& sdlout_mutex);
281

    
282
    check_started ();
283

    
284
    while (buffer.len ())
285
        pthread_cond_wait (& sdlout_cond, & sdlout_mutex);
286

    
287
    pthread_mutex_unlock (& sdlout_mutex);
288
}
289

    
290
int SDLOutput::get_delay ()
291
{
292
    auto timediff = [] (const timeval & a, const timeval & b) -> int64_t
293
        { return 1000 * (int64_t) (b.tv_sec - a.tv_sec) + (b.tv_usec - a.tv_usec) / 1000; };
294

    
295
    pthread_mutex_lock (& sdlout_mutex);
296

    
297
    int delay = aud::rescale (buffer.len (), 2 * sdlout_chan * sdlout_rate, 1000);
298

    
299
    /* Estimate the additional delay of the last block written. */
300
    if (! prebuffer_flag && ! paused_flag && block_delay)
301
    {
302
        struct timeval cur;
303
        gettimeofday (& cur, nullptr);
304

    
305
        delay += aud::max (block_delay - timediff (block_time, cur), (int64_t) 0);
306
    }
307

    
308
    pthread_mutex_unlock (& sdlout_mutex);
309
    return delay;
310
}
311

    
312
void SDLOutput::pause (bool pause)
313
{
314
    AUDDBG ("%sause.\n", pause ? "P" : "Unp");
315
    pthread_mutex_lock (& sdlout_mutex);
316

    
317
    paused_flag = pause;
318

    
319
    if (! prebuffer_flag && ouraudiodevice)
320
        SDL_PauseAudioDevice (ouraudiodevice, pause);
321

    
322
    pthread_cond_broadcast (& sdlout_cond); /* wake up period wait */
323
    pthread_mutex_unlock (& sdlout_mutex);
324
}
325

    
326
void SDLOutput::flush ()
327
{
328
    AUDDBG ("Seek requested; discarding buffer.\n");
329
    pthread_mutex_lock (& sdlout_mutex);
330

    
331
    buffer.discard ();
332

    
333
    prebuffer_flag = true;
334

    
335
    pthread_cond_broadcast (& sdlout_cond); /* wake up period wait */
336
    pthread_mutex_unlock (& sdlout_mutex);
337
}