1 /* -*- c++ -*- */
2 /*
3  * Copyright 2014 Free Software Foundation, Inc.
4  *
5  * This file is part of GNU Radio.
6  *
7  * GNU Radio is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 3, or (at your option)
10  * any later version.
11  *
12  * GNU Radio is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with GNU Radio; see the file COPYING.  If not, write to
19  * the Free Software Foundation, Inc., 51 Franklin Street,
20  * Boston, MA 02110-1301, USA.
21  */
22 
23 #ifdef HAVE_CONFIG_H
24 #include "config.h"
25 #endif
26 
27 #include "../audio_registry.h"
28 
29 #include <gnuradio/audio/osx_impl.h>
30 
31 #include <algorithm>
32 #include <iostream>
33 #include <locale>
34 #include <stdexcept>
35 
operator <<(std::ostream & s,const AudioStreamBasicDescription & asbd)36 std::ostream& operator<<(std::ostream& s, const AudioStreamBasicDescription& asbd)
37 {
38     char format_id[sizeof(asbd.mFormatID) + 1];
39     memcpy(format_id, (void*)(&asbd.mFormatID), sizeof(asbd.mFormatID));
40     format_id[sizeof(asbd.mFormatID)] = 0;
41     s << "  Sample Rate      : " << asbd.mSampleRate << std::endl;
42     s << "  Format ID        : " << format_id << std::endl;
43     s << "  Format Flags     : " << asbd.mFormatFlags << std::endl;
44     s << "    " << ((asbd.mFormatFlags & kAudioFormatFlagIsFloat) != 0) << " : Is Float"
45       << std::endl;
46     s << "    " << ((asbd.mFormatFlags & kAudioFormatFlagIsBigEndian) != 0)
47       << " : Is Big Endian" << std::endl;
48     s << "    " << ((asbd.mFormatFlags & kAudioFormatFlagIsSignedInteger) != 0)
49       << " : Is Signed Integer" << std::endl;
50     s << "    " << ((asbd.mFormatFlags & kAudioFormatFlagIsPacked) != 0) << " : Is Packed"
51       << std::endl;
52     s << "    " << ((asbd.mFormatFlags & kAudioFormatFlagIsAlignedHigh) != 0)
53       << " : Is Aligned High" << std::endl;
54     s << "    " << ((asbd.mFormatFlags & kAudioFormatFlagIsNonInterleaved) != 0)
55       << " : Is Non-Interleaved" << std::endl;
56     s << "    " << ((asbd.mFormatFlags & kAudioFormatFlagIsNonMixable) != 0)
57       << " : Is Non-Mixable" << std::endl;
58     s << "  Bytes / Packet   : " << asbd.mBytesPerPacket << std::endl;
59     s << "  Frames / Packet  : " << asbd.mFramesPerPacket << std::endl;
60     s << "  Bytes / Frame    : " << asbd.mBytesPerFrame << std::endl;
61     s << "  Channels / Frame : " << asbd.mChannelsPerFrame << std::endl;
62     s << "  Bits / Channel   : " << asbd.mBitsPerChannel;
63     return (s);
64 };
65 
66 namespace gr {
67 namespace audio {
68 namespace osx {
69 
_get_num_channels(AudioDeviceID ad_id,AudioObjectPropertyScope scope)70 static UInt32 _get_num_channels(AudioDeviceID ad_id, AudioObjectPropertyScope scope)
71 {
72     // retrieve the AudioBufferList associated with this ID using
73     // the provided scope
74 
75     UInt32 num_channels = 0;
76     UInt32 prop_size = 0;
77     AudioObjectPropertyAddress ao_address = { kAudioDevicePropertyStreamConfiguration,
78                                               scope,
79                                               0 };
80     OSStatus err = noErr;
81     if ((err = AudioObjectGetPropertyDataSize(ad_id, &ao_address, 0, NULL, &prop_size)) ==
82         noErr) {
83         boost::scoped_array<AudioBufferList> buf_list(
84             reinterpret_cast<AudioBufferList*>(new char[prop_size]));
85         if ((err = AudioObjectGetPropertyData(
86                  ad_id, &ao_address, 0, NULL, &prop_size, buf_list.get())) == noErr) {
87             for (UInt32 mm = 0; mm < buf_list.get()->mNumberBuffers; ++mm) {
88                 num_channels += buf_list.get()->mBuffers[mm].mNumberChannels;
89             }
90         } else {
91             // assume 2 channels
92             num_channels = 2;
93         }
94     } else {
95         // assume 2 channels
96         num_channels = 2;
97     }
98     return (num_channels);
99 }
100 
101 // works with both char and wchar_t
102 template <typename charT>
103 struct ci_equal {
ci_equalgr::audio::osx::ci_equal104     ci_equal(const std::locale& loc) : loc_(loc) {}
operator ()gr::audio::osx::ci_equal105     bool operator()(charT ch1, charT ch2)
106     {
107         return std::tolower(ch1, loc_) == std::tolower(ch2, loc_);
108     }
109 
110 private:
111     const std::locale& loc_;
112 };
113 
114 // find substring (case insensitive)
ci_find_substr(const std::string & str1,const std::string & str2,const std::locale & loc=std::locale ())115 static std::string::size_type ci_find_substr(const std::string& str1,
116                                              const std::string& str2,
117                                              const std::locale& loc = std::locale())
118 {
119     std::string::const_iterator it = std::search(str1.begin(),
120                                                  str1.end(),
121                                                  str2.begin(),
122                                                  str2.end(),
123                                                  ci_equal<std::string::value_type>(loc));
124     if (it != str1.end()) {
125         return (it - str1.begin());
126     }
127     // not found
128     return (std::string::npos);
129 }
130 
get_num_channels_for_audio_device_id(AudioDeviceID ad_id,UInt32 * n_input,UInt32 * n_output)131 void get_num_channels_for_audio_device_id(AudioDeviceID ad_id,
132                                           UInt32* n_input,
133                                           UInt32* n_output)
134 {
135     if (n_input) {
136         *n_input = _get_num_channels(ad_id, kAudioDevicePropertyScopeInput);
137     }
138     if (n_output) {
139         *n_output = _get_num_channels(ad_id, kAudioDevicePropertyScopeOutput);
140     }
141 }
142 
find_audio_devices(const std::string & device_name,bool is_input,std::vector<AudioDeviceID> * all_ad_ids,std::vector<std::string> * all_names)143 void find_audio_devices(const std::string& device_name,
144                         bool is_input,
145                         std::vector<AudioDeviceID>* all_ad_ids,
146                         std::vector<std::string>* all_names)
147 {
148     if ((!all_ad_ids) && (!all_names)) {
149         // if nothing is requested, no point in doing anything!
150         return;
151     }
152 
153     OSStatus err = noErr;
154 
155     // retrieve the size of the array of known audio device IDs
156 
157     UInt32 prop_size = 0;
158 
159     AudioObjectPropertyAddress ao_address = { kAudioHardwarePropertyDevices,
160                                               kAudioObjectPropertyScopeGlobal,
161                                               kAudioObjectPropertyElementMaster };
162 
163     if ((err = AudioObjectGetPropertyDataSize(
164              kAudioObjectSystemObject, &ao_address, 0, NULL, &prop_size)) != noErr) {
165 #if _OSX_AU_DEBUG_
166         std::cerr << "audio_osx::find_audio_devices: "
167                   << "Unable to retrieve number of audio objects: " << err << std::endl;
168 #endif
169         return;
170     }
171 
172     // get the total number of audio devices (input and output)
173 
174     UInt32 num_devices = prop_size / sizeof(AudioDeviceID);
175 
176     // retrieve all audio device ids
177 
178     boost::scoped_array<AudioDeviceID> all_dev_ids(new AudioDeviceID[num_devices]);
179 
180     if ((err = AudioObjectGetPropertyData(kAudioObjectSystemObject,
181                                           &ao_address,
182                                           0,
183                                           NULL,
184                                           &prop_size,
185                                           all_dev_ids.get())) != noErr) {
186 #if _OSX_AU_DEBUG_
187         std::cerr << "audio_osx::find_audio_devices: "
188                   << "Unable to retrieve audio object ids: " << err << std::endl;
189 #endif
190         return;
191     }
192 
193     // success; loop over all retrieved output device ids, retrieving
194     // the name for each and comparing with the desired name.
195 
196     std::vector<std::string> valid_names(num_devices);
197     std::vector<UInt32> valid_indices(num_devices);
198     UInt32 num_found_devices = 0;
199     AudioObjectPropertyScope scope =
200         is_input ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput;
201 
202     for (UInt32 nn = 0; nn < num_devices; ++nn) {
203 
204         // make sure this device has input / output channels (it might
205         // also have output / input channels, too, but we do not care
206         // about that here)
207 
208         AudioDeviceID t_id = all_dev_ids[nn];
209 
210         if (is_input) {
211             UInt32 n_input_channels = 0;
212             get_num_channels_for_audio_device_id(t_id, &n_input_channels, NULL);
213             if (n_input_channels == 0) {
214                 // no input channels; must be output device; just continue
215                 // to the next audio device.
216                 continue;
217             }
218         } else {
219             UInt32 n_output_channels = 0;
220             get_num_channels_for_audio_device_id(t_id, NULL, &n_output_channels);
221             if (n_output_channels == 0) {
222                 // no output channels; must be input device; just continue
223                 // to the next audio device.
224                 continue;
225             }
226         }
227 
228         // retrieve the device name; max name length is 64 characters.
229 
230         prop_size = 65;
231         char c_name_buf[prop_size];
232         bzero((void*)c_name_buf, prop_size);
233         --prop_size;
234 
235         AudioObjectPropertyAddress ao_address = { kAudioDevicePropertyDeviceName,
236                                                   scope,
237                                                   0 };
238 
239         if ((err = AudioObjectGetPropertyData(
240                  t_id, &ao_address, 0, NULL, &prop_size, (void*)c_name_buf)) != noErr) {
241 #if _OSX_AU_DEBUG_
242             std::cerr << "audio_osx::find_audio_devices: "
243                       << "Unable to retrieve audio device name #" << (nn + 1) << ": "
244                       << err << std::endl;
245 #endif
246             continue;
247         }
248         std::string name_buf(c_name_buf);
249 
250         // compare the retrieved name with the desired one, if
251         // provided; case insensitive.
252 
253         if (device_name.length() > 0) {
254 
255             std::string::size_type found = ci_find_substr(name_buf, device_name);
256             if (found == std::string::npos) {
257                 // not found; continue to the next ID
258                 continue;
259             }
260         }
261 
262         // store this info
263 
264         valid_names[nn] = name_buf;
265         valid_indices[num_found_devices++] = nn;
266     }
267 
268     // resize valid function arguments, then copy found values
269 
270     if (all_ad_ids) {
271         all_ad_ids->resize(num_found_devices);
272         for (UInt32 nn = 0; nn < num_found_devices; ++nn) {
273             (*all_ad_ids)[nn] = all_dev_ids[valid_indices[nn]];
274         }
275     }
276 
277     if (all_names) {
278         all_names->resize(num_found_devices);
279         for (UInt32 nn = 0; nn < num_found_devices; ++nn) {
280             (*all_names)[nn] = valid_names[valid_indices[nn]];
281         }
282     }
283 }
284 
285 } /* namespace osx */
286 } /* namespace audio */
287 } /* namespace gr */
288