chiark / gitweb /
Disobedience updates last_playing whenever a 'playing' command
[disorder] / clients / playrtp-coreaudio.c
1 /*
2  * This file is part of DisOrder.
3  * Copyright (C) 2007 Richard Kettlewell
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * 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, see <http://www.gnu.org/licenses/>.
17  */
18 /** @file clients/playrtp-coreaudio.c
19  * @brief RTP player - Core Audio support
20  */
21
22 #include "common.h"
23
24 #if HAVE_COREAUDIO_AUDIOHARDWARE_H
25 #include <pthread.h>
26 #include <CoreAudio/AudioHardware.h>
27
28 #include "mem.h"
29 #include "log.h"
30 #include "vector.h"
31 #include "heap.h"
32 #include "playrtp.h"
33
34 /** @brief Callback from Core Audio */
35 static OSStatus adioproc
36     (AudioDeviceID attribute((unused)) inDevice,
37      const AudioTimeStamp attribute((unused)) *inNow,
38      const AudioBufferList attribute((unused)) *inInputData,
39      const AudioTimeStamp attribute((unused)) *inInputTime,
40      AudioBufferList *outOutputData,
41      const AudioTimeStamp attribute((unused)) *inOutputTime,
42      void attribute((unused)) *inClientData) {
43   UInt32 nbuffers = outOutputData->mNumberBuffers;
44   AudioBuffer *ab = outOutputData->mBuffers;
45   uint32_t samples_available;
46
47   pthread_mutex_lock(&lock);
48   while(nbuffers > 0) {
49     float *samplesOut = ab->mData;
50     size_t samplesOutLeft = ab->mDataByteSize / sizeof (float);
51
52     while(samplesOutLeft > 0) {
53       const struct packet *p = playrtp_next_packet();
54       if(p && contains(p, next_timestamp)) {
55         /* This packet is ready to play */
56         const uint32_t packet_end = p->timestamp + p->nsamples;
57         const uint32_t offset = next_timestamp - p->timestamp;
58         const uint16_t *ptr = (void *)(p->samples_raw + offset);
59
60         samples_available = packet_end - next_timestamp;
61         if(samples_available > samplesOutLeft)
62           samples_available = samplesOutLeft;
63         next_timestamp += samples_available;
64         samplesOutLeft -= samples_available;
65         if(dump_buffer) {
66           size_t n;
67
68           for(n = 0; n < samples_available; ++n) {
69             dump_buffer[dump_index++] = (int16_t)ntohs(ptr[n]);
70             dump_index %= dump_size;
71           }
72         }
73         while(samples_available-- > 0)
74           *samplesOut++ = (int16_t)ntohs(*ptr++) * (0.5 / 32767);
75         /* We don't bother junking the packet - that'll be dealt with next time
76          * round */
77       } else {
78         /* No packet is ready to play (and there might be no packet at all) */
79         samples_available = p ? p->timestamp - next_timestamp
80                               : samplesOutLeft;
81         if(samples_available > samplesOutLeft)
82           samples_available = samplesOutLeft;
83         //info("infill by %"PRIu32, samples_available);
84         /* Conveniently the buffer is 0 to start with */
85         next_timestamp += samples_available;
86         samplesOut += samples_available;
87         samplesOutLeft -= samples_available;
88         if(dump_buffer) {
89           size_t n;
90
91           for(n = 0; n < samples_available; ++n) {
92             dump_buffer[dump_index++] = 0;
93             dump_index %= dump_size;
94           }
95         }
96       }
97     }
98     ++ab;
99     --nbuffers;
100   }
101   pthread_mutex_unlock(&lock);
102   return 0;
103 }
104
105 void playrtp_coreaudio(void) {
106   OSStatus status;
107   UInt32 propertySize;
108   AudioDeviceID adid;
109   AudioStreamBasicDescription asbd;
110
111   /* If this looks suspiciously like libao's macosx driver there's an
112    * excellent reason for that... */
113
114   /* TODO report errors as strings not numbers */
115   propertySize = sizeof adid;
116   status = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice,
117                                     &propertySize, &adid);
118   if(status)
119     fatal(0, "AudioHardwareGetProperty: %d", (int)status);
120   if(adid == kAudioDeviceUnknown)
121     fatal(0, "no output device");
122   propertySize = sizeof asbd;
123   status = AudioDeviceGetProperty(adid, 0, false,
124                                   kAudioDevicePropertyStreamFormat,
125                                   &propertySize, &asbd);
126   if(status)
127     fatal(0, "AudioHardwareGetProperty: %d", (int)status);
128   D(("mSampleRate       %f", asbd.mSampleRate));
129   D(("mFormatID         %08lx", asbd.mFormatID));
130   D(("mFormatFlags      %08lx", asbd.mFormatFlags));
131   D(("mBytesPerPacket   %08lx", asbd.mBytesPerPacket));
132   D(("mFramesPerPacket  %08lx", asbd.mFramesPerPacket));
133   D(("mBytesPerFrame    %08lx", asbd.mBytesPerFrame));
134   D(("mChannelsPerFrame %08lx", asbd.mChannelsPerFrame));
135   D(("mBitsPerChannel   %08lx", asbd.mBitsPerChannel));
136   D(("mReserved         %08lx", asbd.mReserved));
137   if(asbd.mFormatID != kAudioFormatLinearPCM)
138     fatal(0, "audio device does not support kAudioFormatLinearPCM");
139   status = AudioDeviceAddIOProc(adid, adioproc, 0);
140   if(status)
141     fatal(0, "AudioDeviceAddIOProc: %d", (int)status);
142   pthread_mutex_lock(&lock);
143   for(;;) {
144     /* Wait for the buffer to fill up a bit */
145     playrtp_fill_buffer();
146     /* Start playing now */
147     info("Playing...");
148     next_timestamp = pheap_first(&packets)->timestamp;
149     active = 1;
150     status = AudioDeviceStart(adid, adioproc);
151     if(status)
152       fatal(0, "AudioDeviceStart: %d", (int)status);
153     /* Wait until the buffer empties out */
154     while(nsamples >= minbuffer
155           || (nsamples > 0
156               && contains(pheap_first(&packets), next_timestamp)))
157       pthread_cond_wait(&cond, &lock);
158     /* Stop playing for a bit until the buffer re-fills */
159     status = AudioDeviceStop(adid, adioproc);
160     if(status)
161       fatal(0, "AudioDeviceStop: %d", (int)status);
162     active = 0;
163     /* Go back round */
164   }
165 }
166
167 #endif
168
169 /*
170 Local Variables:
171 c-basic-offset:2
172 comment-column:40
173 fill-column:79
174 indent-tabs-mode:nil
175 End:
176 */