You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
282 lines
9.4 KiB
282 lines
9.4 KiB
/*****************************************************************************
|
|
* Copyright (C) 2000-2013 VLC authors and VideoLAN
|
|
* $Id$
|
|
*
|
|
* Authors: Felix Paul Kühne <fkuehne at videolan dot org>
|
|
* Rémi Denis-Courmont
|
|
* Rafaël Carré <funman at videolan dot org>
|
|
*
|
|
* 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
|
|
* (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
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Lesser General Public License for more details.
|
|
*
|
|
* 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.
|
|
*****************************************************************************/
|
|
|
|
#pragma mark includes
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# import "config.h"
|
|
#endif
|
|
#import <vlc_common.h>
|
|
#import <vlc_plugin.h>
|
|
#import <vlc_aout.h>
|
|
#import <AudioToolbox/AudioQueue.h>
|
|
#import <TargetConditionals.h>
|
|
#if TARGET_OS_IPHONE
|
|
#import <AudioToolbox/AudioSession.h>
|
|
#else
|
|
#define AudioSessionSetActive(x)
|
|
#endif
|
|
|
|
#pragma mark -
|
|
#pragma mark private declarations
|
|
|
|
struct aout_sys_t
|
|
{
|
|
AudioQueueRef audioQueueRef;
|
|
AudioQueueTimelineRef timelineRef;
|
|
|
|
bool b_started;
|
|
|
|
mtime_t i_played_length;
|
|
int i_rate;
|
|
float f_volume;
|
|
};
|
|
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 );
|
|
vlc_module_begin ()
|
|
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)
|
|
vlc_module_end ()
|
|
|
|
#pragma mark -
|
|
#pragma mark initialization
|
|
|
|
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 */
|
|
ostatus = AudioQueueSetParameter(p_sys->audioQueueRef, kAudioQueueParam_Volume, volume * volume * volume);
|
|
|
|
return ostatus;
|
|
}
|
|
|
|
static int Start(audio_output_t *p_aout, audio_sample_format_t *restrict fmt)
|
|
{
|
|
aout_sys_t *p_sys = p_aout->sys;
|
|
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)...
|
|
&(p_sys->audioQueueRef)); // Output
|
|
msg_Dbg(p_aout, "New AudioQueue instance created (status = %li)", error);
|
|
if (error != noErr)
|
|
return VLC_EGENERIC;
|
|
fmt->i_format = VLC_CODEC_FL32;
|
|
fmt->i_original_channels = fmt->i_physical_channels = AOUT_CHANS_STEREO;
|
|
aout_FormatPrepare(fmt);
|
|
p_aout->sys->i_rate = fmt->i_rate;
|
|
|
|
// start queue
|
|
error = AudioQueueStart(p_sys->audioQueueRef, NULL);
|
|
msg_Dbg(p_aout, "Starting AudioQueue (status = %li)", error);
|
|
|
|
// start timeline for synchro
|
|
error = AudioQueueCreateTimeline(p_sys->audioQueueRef, &p_sys->timelineRef);
|
|
msg_Dbg(p_aout, "AudioQueue Timeline started (status = %li)", error);
|
|
if (error != noErr)
|
|
return VLC_EGENERIC;
|
|
|
|
#if TARGET_OS_IPHONE
|
|
// 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
|
|
|
|
p_aout->sys->b_started = true;
|
|
|
|
p_aout->time_get = TimeGet;
|
|
p_aout->play = Play;
|
|
p_aout->pause = Pause;
|
|
p_aout->flush = Flush;
|
|
return VLC_SUCCESS;
|
|
}
|
|
|
|
static void Stop(audio_output_t *p_aout)
|
|
{
|
|
AudioSessionSetActive(false);
|
|
|
|
p_aout->sys->i_played_length = 0;
|
|
AudioQueueDisposeTimeline(p_aout->sys->audioQueueRef, p_aout->sys->timelineRef);
|
|
AudioQueueStop(p_aout->sys->audioQueueRef, true);
|
|
AudioQueueDispose(p_aout->sys->audioQueueRef, true);
|
|
msg_Dbg(p_aout, "audioqueue stopped and disposed");
|
|
}
|
|
|
|
#pragma mark -
|
|
#pragma mark actual playback
|
|
|
|
static void Play(audio_output_t *p_aout, block_t *p_block)
|
|
{
|
|
AudioQueueBufferRef inBuffer = NULL;
|
|
OSStatus status;
|
|
|
|
status = AudioQueueAllocateBuffer(p_aout->sys->audioQueueRef, p_block->i_buffer, &inBuffer);
|
|
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);
|
|
|
|
block_Release(p_block);
|
|
}
|
|
|
|
void UnusedAudioQueueCallback(void * inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer) {
|
|
/* this function does nothing, but needs to be here to make the AudioQueue API happy.
|
|
* additionally, we clean-up after empty buffers */
|
|
VLC_UNUSED(inUserData);
|
|
AudioQueueFreeBuffer(inAQ, inBuffer);
|
|
}
|
|
|
|
static void Pause(audio_output_t *p_aout, bool pause, mtime_t date)
|
|
{
|
|
VLC_UNUSED(date);
|
|
|
|
if (pause) {
|
|
AudioQueuePause(p_aout->sys->audioQueueRef);
|
|
AudioSessionSetActive(false);
|
|
} else {
|
|
AudioQueueStart(p_aout->sys->audioQueueRef, NULL);
|
|
AudioSessionSetActive(true);
|
|
}
|
|
}
|
|
|
|
static void Flush(audio_output_t *p_aout, bool wait)
|
|
{
|
|
if (!p_aout->sys->audioQueueRef)
|
|
return;
|
|
|
|
AudioQueueDisposeTimeline(p_aout->sys->audioQueueRef, p_aout->sys->timelineRef);
|
|
|
|
if (wait)
|
|
AudioQueueStop(p_aout->sys->audioQueueRef, false);
|
|
else
|
|
AudioQueueStop(p_aout->sys->audioQueueRef, true);
|
|
|
|
p_aout->sys->b_started = false;
|
|
p_aout->sys->i_played_length = 0;
|
|
AudioQueueStart(p_aout->sys->audioQueueRef, NULL);
|
|
AudioQueueCreateTimeline(p_aout->sys->audioQueueRef, &p_aout->sys->timelineRef);
|
|
p_aout->sys->b_started = true;
|
|
}
|
|
|
|
static int TimeGet(audio_output_t *p_aout, mtime_t *restrict delay)
|
|
{
|
|
AudioTimeStamp outTimeStamp;
|
|
Boolean b_discontinuity;
|
|
OSStatus status = AudioQueueGetCurrentTime(p_aout->sys->audioQueueRef, p_aout->sys->timelineRef, &outTimeStamp, &b_discontinuity);
|
|
|
|
if (status != noErr)
|
|
return -1;
|
|
|
|
bool b_started = p_aout->sys->b_started;
|
|
|
|
if (!b_started)
|
|
return -1;
|
|
|
|
if (b_discontinuity) {
|
|
msg_Dbg(p_aout, "detected output discontinuity");
|
|
return -1;
|
|
}
|
|
|
|
mtime_t i_pos = (mtime_t) outTimeStamp.mSampleTime * CLOCK_FREQ / p_aout->sys->i_rate;
|
|
if (i_pos > 0) {
|
|
*delay = p_aout->sys->i_played_length - i_pos;
|
|
return 0;
|
|
} else
|
|
return -1;
|
|
}
|
|
|