]> git.sesse.net Git - vlc/blob - modules/audio_output/audiounit_ios.c
transcode: fix audio format given to filters (fixes #8385)
[vlc] / modules / audio_output / audiounit_ios.c
1 /*****************************************************************************
2  * audiounit_ios.c: AudioUnit output plugin for iOS
3  *****************************************************************************
4  * Copyright (C) 2012 - 2013 VLC authors and VideoLAN
5  * $Id$
6  *
7  * Authors: Felix Paul Kühne <fkuehne at videolan dot org>
8  *
9  * This program is free software; you can redistribute it and/or modify it
10  * under the terms of the GNU Lesser General Public License as published by
11  * the Free Software Foundation; either version 2.1 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public License
20  * along with this program; if not, write to the Free Software Foundation,
21  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22  *****************************************************************************/
23
24 #pragma mark includes
25
26 #ifdef HAVE_CONFIG_H
27 # import "config.h"
28 #endif
29
30 #import <vlc_common.h>
31 #import <vlc_plugin.h>
32 #import <vlc_aout.h>
33
34 #import <AudioUnit/AudioUnit.h>
35 #import <CoreAudio/CoreAudioTypes.h>
36 #import <AudioToolbox/AudioToolbox.h>
37 #import <mach/mach_time.h>
38
39 #import "TPCircularBuffer.h"
40
41 #pragma mark -
42 #pragma mark private declarations
43
44 #define STREAM_FORMAT_MSG(pre, sfm) \
45     pre "[%f][%4.4s][%u][%u][%u][%u][%u][%u]", \
46     sfm.mSampleRate, (char *)&sfm.mFormatID, \
47     (unsigned int)sfm.mFormatFlags, (unsigned int)sfm.mBytesPerPacket, \
48     (unsigned int)sfm.mFramesPerPacket, (unsigned int)sfm.mBytesPerFrame, \
49     (unsigned int)sfm.mChannelsPerFrame, (unsigned int)sfm.mBitsPerChannel
50
51 #define AUDIO_BUFFER_SIZE_IN_SECONDS (AOUT_MAX_ADVANCE_TIME / CLOCK_FREQ)
52
53 /*****************************************************************************
54  * aout_sys_t: private audio output method descriptor
55  *****************************************************************************
56  * This structure is part of the audio output thread descriptor.
57  * It describes the CoreAudio specific properties of an output thread.
58  *****************************************************************************/
59 struct aout_sys_t
60 {
61     TPCircularBuffer            circular_buffer;    /* circular buffer to swap the audio data */
62
63     /* AUHAL specific */
64     AudioComponent              au_component;       /* The AudioComponent we use */
65     AudioUnit                   au_unit;            /* The AudioUnit we use */
66
67     int                         i_rate;             /* media sample rate */
68     int                         i_bytes_per_sample;
69     bool                        b_got_first_sample;
70 };
71
72 #pragma mark -
73 #pragma mark local prototypes & module descriptor
74
75 static int      Open                    (vlc_object_t *);
76 static void     Close                   (vlc_object_t *);
77 static int      Start                   (audio_output_t *, audio_sample_format_t *);
78 static int      StartAnalog             (audio_output_t *, audio_sample_format_t *);
79 static void     Stop                    (audio_output_t *);
80
81 static void     Play                    (audio_output_t *, block_t *);
82 static void     Pause                   (audio_output_t *, bool, mtime_t);
83 static void     Flush                   (audio_output_t *, bool);
84 static int      TimeGet                 (audio_output_t *, mtime_t *);
85 static OSStatus RenderCallback    (vlc_object_t *, AudioUnitRenderActionFlags *, const AudioTimeStamp *,
86                                          UInt32 , UInt32, AudioBufferList *);
87 vlc_module_begin ()
88     set_shortname("audiounit_ios")
89     set_description(N_("AudioUnit output for iOS"))
90     set_capability("audio output", 101)
91     set_category(CAT_AUDIO)
92     set_subcategory(SUBCAT_AUDIO_AOUT)
93     set_callbacks(Open, Close)
94 vlc_module_end ()
95
96 #pragma mark -
97 #pragma mark initialization
98
99 static int Open(vlc_object_t *obj)
100 {
101     audio_output_t *aout = (audio_output_t *)obj;
102     aout_sys_t *sys = malloc(sizeof (*sys));
103
104     if (unlikely(sys == NULL))
105         return VLC_ENOMEM;
106
107     aout->sys = sys;
108     aout->start = Start;
109     aout->stop = Stop;
110
111     return VLC_SUCCESS;
112 }
113
114 static void Close(vlc_object_t *obj)
115 {
116     audio_output_t *aout = (audio_output_t *)obj;
117     aout_sys_t *sys = aout->sys;
118
119     free(sys);
120 }
121
122 static int Start(audio_output_t *p_aout, audio_sample_format_t *restrict fmt)
123 {
124     struct aout_sys_t *p_sys = NULL;
125
126     p_sys = p_aout->sys;
127     p_sys->au_component = NULL;
128     p_sys->au_unit = NULL;
129     p_sys->i_bytes_per_sample = 0;
130
131     aout_FormatPrint(p_aout, "VLC is looking for:", fmt);
132
133     if (StartAnalog(p_aout, fmt)) {
134         msg_Dbg(p_aout, "analog AudioUnit output successfully opened");
135         p_aout->play = Play;
136         p_aout->flush = Flush;
137         p_aout->time_get = TimeGet;
138         p_aout->pause = Pause;
139         return VLC_SUCCESS;
140     }
141
142     /* If we reach this, this aout has failed */
143     msg_Err(p_aout, "opening AudioUnit output failed");
144     return VLC_EGENERIC;
145 }
146
147 /*
148  * StartAnalog: open and setup a HAL AudioUnit to do PCM audio output
149  */
150 static int StartAnalog(audio_output_t *p_aout, audio_sample_format_t *fmt)
151 {
152     struct aout_sys_t           *p_sys = p_aout->sys;
153     UInt32                      i_param_size = 0;
154     AudioComponentDescription   desc;
155     AURenderCallbackStruct      callback;
156     OSStatus status;
157
158     /* Lets go find our Component */
159     desc.componentType = kAudioUnitType_Output;
160     desc.componentSubType = kAudioUnitSubType_RemoteIO;
161     desc.componentManufacturer = kAudioUnitManufacturer_Apple;
162     desc.componentFlags = 0;
163     desc.componentFlagsMask = 0;
164
165     p_sys->au_component = AudioComponentFindNext(NULL, &desc);
166     if (p_sys->au_component == NULL) {
167         msg_Warn(p_aout, "we cannot find our audio component");
168         return false;
169     }
170
171     status = AudioComponentInstanceNew(p_sys->au_component, &p_sys->au_unit);
172     if (status != noErr) {
173         msg_Warn(p_aout, "we cannot open our audio component (%li)", status);
174         return false;
175     }
176
177     UInt32 flag = 1;
178     status = AudioUnitSetProperty(p_sys->au_unit,
179                                   kAudioOutputUnitProperty_EnableIO,
180                                   kAudioUnitScope_Output,
181                                   0,
182                                   &flag,
183                                   sizeof(flag));
184     if (status != noErr)
185         msg_Warn(p_aout, "failed to set IO mode (%li)", status);
186
187     /* Get the current format */
188     AudioStreamBasicDescription streamDescription;
189     streamDescription.mSampleRate = fmt->i_rate;
190     fmt->i_format = VLC_CODEC_FL32;
191     fmt->i_physical_channels = AOUT_CHANS_STEREO;
192     streamDescription.mFormatID = kAudioFormatLinearPCM;
193     streamDescription.mFormatFlags = kAudioFormatFlagsNativeFloatPacked; // FL32
194     streamDescription.mChannelsPerFrame = aout_FormatNbChannels(fmt);
195     streamDescription.mFramesPerPacket = 1;
196     streamDescription.mBitsPerChannel = 32;
197     streamDescription.mBytesPerFrame = streamDescription.mBitsPerChannel * streamDescription.mChannelsPerFrame / 8;
198     streamDescription.mBytesPerPacket = streamDescription.mBytesPerFrame * streamDescription.mFramesPerPacket;
199     i_param_size = sizeof(streamDescription);
200     p_sys->i_rate = fmt->i_rate;
201
202     /* Set the desired format */
203     i_param_size = sizeof(AudioStreamBasicDescription);
204     status = AudioUnitSetProperty(p_sys->au_unit,
205                                   kAudioUnitProperty_StreamFormat,
206                                   kAudioUnitScope_Input,
207                                   0,
208                                   &streamDescription,
209                                   i_param_size);
210     if (status != noErr) {
211         msg_Err(p_aout, "failed to set stream format (%li)", status);
212         return false;
213     }
214     msg_Dbg(p_aout, STREAM_FORMAT_MSG("we set the AU format: " , streamDescription));
215
216     /* Retrieve actual format */
217     status = AudioUnitGetProperty(p_sys->au_unit,
218                                   kAudioUnitProperty_StreamFormat,
219                                   kAudioUnitScope_Input,
220                                   0,
221                                   &streamDescription,
222                                   &i_param_size);
223     if (status != noErr)
224         msg_Warn(p_aout, "failed to verify stream format (%li)", status);
225     msg_Dbg(p_aout, STREAM_FORMAT_MSG("the actual set AU format is " , streamDescription));
226
227     /* Do the last VLC aout setups */
228     aout_FormatPrepare(fmt);
229
230     /* set the IOproc callback */
231     callback.inputProc = (AURenderCallback) RenderCallback;
232     callback.inputProcRefCon = p_aout;
233
234     status = AudioUnitSetProperty(p_sys->au_unit,
235                             kAudioUnitProperty_SetRenderCallback,
236                             kAudioUnitScope_Input,
237                             0, &callback, sizeof(callback));
238     if (status != noErr) {
239         msg_Err(p_aout, "render callback setup failed (%li)", status);
240         return false;
241     }
242
243     /* AU initiliaze */
244     status = AudioUnitInitialize(p_sys->au_unit);
245     if (status != noErr) {
246         msg_Err(p_aout, "failed to init AudioUnit (%li)", status);
247         return false;
248     }
249
250     /* start audio session so playback continues if mute switch is on */
251     AudioSessionInitialize (NULL,
252                             kCFRunLoopCommonModes,
253                             NULL,
254                             NULL);
255
256         /* Set audio session to mediaplayback */
257         UInt32 sessionCategory = kAudioSessionCategory_MediaPlayback;
258         AudioSessionSetProperty(kAudioSessionProperty_AudioCategory, sizeof(sessionCategory),&sessionCategory);
259         AudioSessionSetActive(true);
260
261     /* setup circular buffer */
262     TPCircularBufferInit(&p_sys->circular_buffer, AUDIO_BUFFER_SIZE_IN_SECONDS * fmt->i_rate * fmt->i_bytes_per_frame);
263
264     p_sys->b_got_first_sample = false;
265
266     return true;
267 }
268
269 static void Stop(audio_output_t *p_aout)
270 {
271     struct aout_sys_t   *p_sys = p_aout->sys;
272     OSStatus status;
273
274     AudioSessionSetActive(false);
275
276     if (p_sys->au_unit) {
277         status = AudioOutputUnitStop(p_sys->au_unit);
278         if (status != noErr)
279             msg_Warn(p_aout, "failed to stop AudioUnit (%li)", status);
280
281         status = AudioComponentInstanceDispose(p_sys->au_unit);
282         if (status != noErr)
283             msg_Warn(p_aout, "failed to dispose Audio Component instance (%li)", status);
284     }
285     p_sys->i_bytes_per_sample = 0;
286
287     /* clean-up circular buffer */
288     TPCircularBufferCleanup(&p_sys->circular_buffer);
289 }
290
291 #pragma mark -
292 #pragma mark actual playback
293
294 static void Play (audio_output_t * p_aout, block_t * p_block)
295 {
296     struct aout_sys_t *p_sys = p_aout->sys;
297
298     if (p_block->i_nb_samples > 0) {
299         if (!p_sys->b_got_first_sample) {
300             /* Start the AU */
301             OSStatus status = AudioOutputUnitStart(p_sys->au_unit);
302             msg_Dbg(p_aout, "audio output unit started: %li", status);
303             p_sys->b_got_first_sample = true;
304         }
305
306         /* move data to buffer */
307         if (unlikely(!TPCircularBufferProduceBytes(&p_sys->circular_buffer, p_block->p_buffer, p_block->i_buffer)))
308             msg_Warn(p_aout, "Audio buffer was dropped");
309
310         if (!p_sys->i_bytes_per_sample)
311             p_sys->i_bytes_per_sample = p_block->i_buffer / p_block->i_nb_samples;
312     }
313
314     block_Release(p_block);
315 }
316
317 static void Pause (audio_output_t *p_aout, bool pause, mtime_t date)
318 {
319     struct aout_sys_t * p_sys = p_aout->sys;
320     VLC_UNUSED(date);
321
322     if (pause) {
323         AudioOutputUnitStop(p_sys->au_unit);
324         AudioSessionSetActive(false);
325     } else {
326         AudioOutputUnitStart(p_sys->au_unit);
327         AudioSessionSetActive(true);
328     }
329 }
330
331 static void Flush(audio_output_t *p_aout, bool wait)
332 {
333     struct aout_sys_t * p_sys = p_aout->sys;
334     VLC_UNUSED(wait);
335
336     p_sys->b_got_first_sample = false;
337
338     /* flush circular buffer */
339     AudioOutputUnitStop(p_aout->sys->au_unit);
340     TPCircularBufferClear(&p_aout->sys->circular_buffer);
341 }
342
343 static int TimeGet(audio_output_t *p_aout, mtime_t *delay)
344 {
345     struct aout_sys_t * p_sys = p_aout->sys;
346
347     if (!p_sys->i_bytes_per_sample)
348         return -1;
349
350     int32_t availableBytes;
351     TPCircularBufferTail(&p_sys->circular_buffer, &availableBytes);
352
353     *delay = (availableBytes / p_sys->i_bytes_per_sample) * CLOCK_FREQ / p_sys->i_rate;
354
355     return 0;
356 }
357
358 /*****************************************************************************
359  * RenderCallback: This function is called everytime the AudioUnit wants
360  * us to provide some more audio data.
361  * Don't print anything during normal playback, calling blocking function from
362  * this callback is not allowed.
363  *****************************************************************************/
364 static OSStatus RenderCallback(vlc_object_t *p_obj,
365                                AudioUnitRenderActionFlags *ioActionFlags,
366                                const AudioTimeStamp *inTimeStamp,
367                                UInt32 inBusNumber,
368                                UInt32 inNumberFrames,
369                                AudioBufferList *ioData) {
370     VLC_UNUSED(ioActionFlags);
371     VLC_UNUSED(inTimeStamp);
372     VLC_UNUSED(inBusNumber);
373
374     audio_output_t * p_aout = (audio_output_t *)p_obj;
375     struct aout_sys_t * p_sys = p_aout->sys;
376
377     int bytesToCopy = ioData->mBuffers[0].mDataByteSize;
378     Float32 *targetBuffer = (Float32*)ioData->mBuffers[0].mData;
379
380     /* Pull audio from buffer */
381     int32_t availableBytes = 0;
382     Float32 *buffer = TPCircularBufferTail(&p_sys->circular_buffer, &availableBytes);
383
384     /* check if we have enough data */
385     if (!availableBytes) {
386         /* bail out and restart unit the next time we receive some data */
387         Flush(p_aout, false);
388     } else {
389         memcpy(targetBuffer, buffer, __MIN(bytesToCopy, availableBytes));
390         TPCircularBufferConsume(&p_sys->circular_buffer, __MIN(bytesToCopy, availableBytes));
391         VLC_UNUSED(inNumberFrames);
392     }
393
394     return noErr;
395 }
396