audioqueue.c 9.34 KB
Newer Older
1
/*****************************************************************************
2
 * Copyright (C) 2000-2013 VLC authors and VideoLAN
Felix Paul Kühne's avatar
Felix Paul Kühne committed
3
 * $Id$
4
 *
5
 * Authors: Felix Paul Kühne <fkuehne at videolan dot org>
6 7
 *          Rémi Denis-Courmont
 *          Rafaël Carré <funman at videolan dot org>
8
 *
9 10 11
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
12 13 14 15
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
18
 *
19 20 21
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22 23
 *****************************************************************************/

24 25
#pragma mark includes

26
#ifdef HAVE_CONFIG_H
Felix Paul Kühne's avatar
Felix Paul Kühne committed
27
# import "config.h"
28
#endif
Felix Paul Kühne's avatar
Felix Paul Kühne committed
29 30 31
#import <vlc_common.h>
#import <vlc_plugin.h>
#import <vlc_aout.h>
32
#import <AudioToolbox/AudioQueue.h>
33
#import <TargetConditionals.h>
34
#if TARGET_OS_IPHONE
35
#import <AudioToolbox/AudioSession.h>
36 37 38
#else
#define AudioSessionSetActive(x)
#endif
39

40 41 42
#pragma mark -
#pragma mark private declarations

43 44
struct aout_sys_t
{
45 46
    AudioQueueRef           audioQueueRef;
    AudioQueueTimelineRef   timelineRef;
47

48 49
    bool                    b_started;

50
    mtime_t                 i_played_length;
51
    int                     i_rate;
52
    float                   f_volume;
53
};
Felix Paul Kühne's avatar
Felix Paul Kühne committed
54 55 56 57 58 59 60 61 62 63
static int  Open                     (vlc_object_t *);
static void Close                    (vlc_object_t *);
static void Play                     (audio_output_t *, block_t *);
static void Pause                    (audio_output_t *p_aout, bool pause, mtime_t date);
static void Flush                    (audio_output_t *p_aout, bool wait);
static int  TimeGet                  (audio_output_t *aout, mtime_t *);
static void UnusedAudioQueueCallback (void *, AudioQueueRef, AudioQueueBufferRef);
static int Start(audio_output_t *, audio_sample_format_t *);
static void Stop(audio_output_t *);
static int VolumeSet(audio_output_t *, float );
64
vlc_module_begin ()
Felix Paul Kühne's avatar
Felix Paul Kühne committed
65 66 67 68 69 70 71
set_shortname("AudioQueue")
set_description(N_("AudioQueue (iOS / Mac OS) audio output"))
set_capability("audio output", 40)
set_category(CAT_AUDIO)
set_subcategory(SUBCAT_AUDIO_AOUT)
add_shortcut("audioqueue")
set_callbacks(Open, Close)
72
vlc_module_end ()
73

74 75
#pragma mark -
#pragma mark initialization
Felix Paul Kühne's avatar
Felix Paul Kühne committed
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113

static int Open(vlc_object_t *obj)
{
    audio_output_t *aout = (audio_output_t *)obj;
    aout_sys_t *sys = malloc(sizeof (*sys));

    if (unlikely(sys == NULL))
        return VLC_ENOMEM;

    aout->sys = sys;
    aout->start = Start;
    aout->stop = Stop;
    aout->volume_set = VolumeSet;

    /* reset volume */
    aout_VolumeReport(aout, 1.0);

    return VLC_SUCCESS;
}

static void Close(vlc_object_t *obj)
{
    audio_output_t *aout = (audio_output_t *)obj;
    msg_Dbg( aout, "audioqueue: Close");
    aout_sys_t *sys = aout->sys;

    free(sys);
}

static int VolumeSet(audio_output_t * p_aout, float volume)
{
    struct aout_sys_t *p_sys = p_aout->sys;
    OSStatus ostatus;

    aout_VolumeReport(p_aout, volume);
    p_sys->f_volume = volume;

    /* Set volume for output unit */
114
    ostatus = AudioQueueSetParameter(p_sys->audioQueueRef, kAudioQueueParam_Volume, volume * volume * volume);
Felix Paul Kühne's avatar
Felix Paul Kühne committed
115 116 117 118

    return ostatus;
}

Felix Paul Kühne's avatar
Felix Paul Kühne committed
119
static int Start(audio_output_t *p_aout, audio_sample_format_t *restrict fmt)
120
{
121
    aout_sys_t *p_sys = p_aout->sys;
Felix Paul Kühne's avatar
Felix Paul Kühne committed
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
    OSStatus error = 0;

    // prepare the format description for our output
    AudioStreamBasicDescription streamDescription;
    streamDescription.mSampleRate = fmt->i_rate;
    streamDescription.mFormatID = kAudioFormatLinearPCM;
    streamDescription.mFormatFlags = kAudioFormatFlagsNativeFloatPacked; // FL32
    streamDescription.mFramesPerPacket = 1;
    streamDescription.mChannelsPerFrame = 2;
    streamDescription.mBitsPerChannel = 32;
    streamDescription.mBytesPerFrame = streamDescription.mBitsPerChannel * streamDescription.mChannelsPerFrame / 8;
    streamDescription.mBytesPerPacket = streamDescription.mBytesPerFrame * streamDescription.mFramesPerPacket;

    // init new output instance
    error = AudioQueueNewOutput(&streamDescription,           // Format
                                 UnusedAudioQueueCallback,    // Unused Callback, which needs to be provided to have a proper instance
                                 NULL,                        // User data, passed to the callback
                                 NULL,                        // RunLoop
                                 kCFRunLoopCommonModes,       // RunLoop mode
                                 0,                           // Flags ; must be zero (per documentation)...
142
                                 &(p_sys->audioQueueRef));    // Output
143
    msg_Dbg(p_aout, "New AudioQueue instance created (status = %li)", error);
Felix Paul Kühne's avatar
Felix Paul Kühne committed
144
    if (error != noErr)
145
        return VLC_EGENERIC;
146
    fmt->i_format = VLC_CODEC_FL32;
147
    fmt->i_physical_channels = AOUT_CHANS_STEREO;
148
    aout_FormatPrepare(fmt);
149
    p_aout->sys->i_rate = fmt->i_rate;
150

Felix Paul Kühne's avatar
Felix Paul Kühne committed
151
    // start queue
152
    error = AudioQueueStart(p_sys->audioQueueRef, NULL);
Felix Paul Kühne's avatar
Felix Paul Kühne committed
153 154 155
    msg_Dbg(p_aout, "Starting AudioQueue (status = %li)", error);

    // start timeline for synchro
156
    error = AudioQueueCreateTimeline(p_sys->audioQueueRef, &p_sys->timelineRef);
Felix Paul Kühne's avatar
Felix Paul Kühne committed
157 158 159
    msg_Dbg(p_aout, "AudioQueue Timeline started (status = %li)", error);
    if (error != noErr)
        return VLC_EGENERIC;
160

161
#if TARGET_OS_IPHONE
162 163 164 165 166 167 168 169 170 171 172 173
    // start audio session so playback continues if mute switch is on
    AudioSessionInitialize (NULL,
                            kCFRunLoopCommonModes,
                            NULL,
                            NULL);

	// Set audio session to mediaplayback
	UInt32 sessionCategory = kAudioSessionCategory_MediaPlayback;
	AudioSessionSetProperty(kAudioSessionProperty_AudioCategory, sizeof(sessionCategory),&sessionCategory);
	AudioSessionSetActive(true);
#endif

174 175
    p_aout->sys->b_started = true;

176 177 178 179 180 181
    p_aout->time_get = TimeGet;
    p_aout->play = Play;
    p_aout->pause = Pause;
    p_aout->flush = Flush;
    return VLC_SUCCESS;
}
182

Felix Paul Kühne's avatar
Felix Paul Kühne committed
183
static void Stop(audio_output_t *p_aout)
184
{
185 186
    AudioSessionSetActive(false);

187
    p_aout->sys->i_played_length = 0;
188 189 190
    AudioQueueDisposeTimeline(p_aout->sys->audioQueueRef, p_aout->sys->timelineRef);
    AudioQueueStop(p_aout->sys->audioQueueRef, true);
    AudioQueueDispose(p_aout->sys->audioQueueRef, true);
Felix Paul Kühne's avatar
Felix Paul Kühne committed
191
    msg_Dbg(p_aout, "audioqueue stopped and disposed");
192 193
}

194 195
#pragma mark -
#pragma mark actual playback
196

Felix Paul Kühne's avatar
Felix Paul Kühne committed
197
static void Play(audio_output_t *p_aout, block_t *p_block)
198
{
199 200 201
    AudioQueueBufferRef inBuffer = NULL;
    OSStatus status;

202
    status = AudioQueueAllocateBuffer(p_aout->sys->audioQueueRef, p_block->i_buffer, &inBuffer);
203 204 205 206 207 208 209 210 211 212 213
    if (status == noErr) {
        memcpy(inBuffer->mAudioData, p_block->p_buffer, p_block->i_buffer);
        inBuffer->mAudioDataByteSize = p_block->i_buffer;

        status = AudioQueueEnqueueBuffer(p_aout->sys->audioQueueRef, inBuffer, 0, NULL);
        if (status == noErr)
            p_aout->sys->i_played_length += p_block->i_length;
        else
            msg_Err(p_aout, "enqueuing buffer failed (%li)", status);
    } else
            msg_Err(p_aout, "buffer alloction failed (%li)", status);
214 215

    block_Release(p_block);
216
}
217

Felix Paul Kühne's avatar
Felix Paul Kühne committed
218
void UnusedAudioQueueCallback(void * inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer) {
219
    /* this function does nothing, but needs to be here to make the AudioQueue API happy.
220
     * additionally, we clean-up after empty buffers */
221
    VLC_UNUSED(inUserData);
222
    AudioQueueFreeBuffer(inAQ, inBuffer);
223 224
}

Felix Paul Kühne's avatar
Felix Paul Kühne committed
225
static void Pause(audio_output_t *p_aout, bool pause, mtime_t date)
226
{
Felix Paul Kühne's avatar
Felix Paul Kühne committed
227
    VLC_UNUSED(date);
228

229
    if (pause) {
230
        AudioQueuePause(p_aout->sys->audioQueueRef);
231 232
        AudioSessionSetActive(false);
    } else {
233
        AudioQueueStart(p_aout->sys->audioQueueRef, NULL);
234 235
        AudioSessionSetActive(true);
    }
236 237
}

Felix Paul Kühne's avatar
Felix Paul Kühne committed
238
static void Flush(audio_output_t *p_aout, bool wait)
239
{
240
    if (!p_aout->sys->audioQueueRef)
241
        return;
242

243
    AudioQueueDisposeTimeline(p_aout->sys->audioQueueRef, p_aout->sys->timelineRef);
244

245
    if (wait)
246
        AudioQueueStop(p_aout->sys->audioQueueRef, false);
247
    else
248
        AudioQueueStop(p_aout->sys->audioQueueRef, true);
249

250
    p_aout->sys->b_started = false;
251
    p_aout->sys->i_played_length = 0;
252 253
    AudioQueueStart(p_aout->sys->audioQueueRef, NULL);
    AudioQueueCreateTimeline(p_aout->sys->audioQueueRef, &p_aout->sys->timelineRef);
254
    p_aout->sys->b_started = true;
255 256
}

Felix Paul Kühne's avatar
Felix Paul Kühne committed
257
static int TimeGet(audio_output_t *p_aout, mtime_t *restrict delay)
258
{
259 260
    AudioTimeStamp outTimeStamp;
    Boolean b_discontinuity;
261
    OSStatus status = AudioQueueGetCurrentTime(p_aout->sys->audioQueueRef, p_aout->sys->timelineRef, &outTimeStamp, &b_discontinuity);
262

263
    if (status != noErr)
264 265
        return -1;

266 267 268 269 270 271
    bool b_started = p_aout->sys->b_started;

    if (!b_started)
        return -1;

    if (b_discontinuity) {
272
        msg_Dbg(p_aout, "detected output discontinuity");
273 274
        return -1;
    }
275

276
    mtime_t i_pos = (mtime_t) outTimeStamp.mSampleTime * CLOCK_FREQ / p_aout->sys->i_rate;
277 278 279 280 281
    if (i_pos > 0) {
        *delay = p_aout->sys->i_played_length - i_pos;
        return 0;
    } else
        return -1;
282
}