source: trunk/StreamVision/AudioDevicemodule.c @ 657

Last change on this file since 657 was 657, checked in by Nicholas Riley, 6 years ago

AudioDevicemodule?.c: Make error handling more useful and consistent.
Make spacing consistent. Ignore sporadic kAudioHardwareBadObjectError.

File size: 4.1 KB
Line 
1#include "Python.h"
2#include <AudioToolbox/AudioServices.h>
3
4#define FourCC2Str(code) (char[5]){(code >> 24) & 0xFF, (code >> 16) & 0xFF, (code >> 8) & 0xFF, code & 0xFF, 0}
5
6static PyObject *
7OSError_from_HALError(const char *failed_operation, OSStatus err) {
8    // these error codes are actually mnemonic, so display them
9    return PyErr_Format(PyExc_OSError,
10                        "%s failed (%ld - %s)",
11                        failed_operation, (long)err, FourCC2Str(err));
12}
13
14static PyObject *
15AudioDevice_default_output_device_is_airplay(PyObject *self, PyObject *args) {
16  AudioObjectPropertyAddress propertyAddress;
17  propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
18  propertyAddress.mElement = kAudioObjectPropertyElementMaster;
19  propertyAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
20
21  AudioDeviceID deviceID = kAudioDeviceUnknown;
22  UInt32 size = sizeof(deviceID);
23  OSStatus err;
24 
25  err = AudioHardwareServiceGetPropertyData(kAudioObjectSystemObject,
26                                            &propertyAddress, 0, NULL,
27                                            &size, &deviceID);
28  if (err != noErr)
29    return OSError_from_HALError("AudioHardwareServiceGetPropertyData", err);
30 
31  if (deviceID == kAudioDeviceUnknown)
32    Py_RETURN_NONE;
33
34  UInt32 transportType;
35  propertyAddress.mSelector = kAudioDevicePropertyTransportType,
36  err = AudioObjectGetPropertyData(deviceID,
37                                   &propertyAddress, 0, NULL,
38                                   &size, &transportType);
39  if (err == kAudioHardwareBadObjectError)
40    Py_RETURN_NONE;
41  if (err != noErr)
42    return OSError_from_HALError("AudioObjectGetPropertyData", err);
43
44  if (transportType == kAudioDeviceTransportTypeAirPlay)
45    Py_RETURN_TRUE;
46  else
47    Py_RETURN_FALSE;
48}
49
50static PyObject *default_output_device_changed_callback = NULL;
51
52OSStatus
53output_device_changed_listener(AudioObjectID inObjectID,
54                               UInt32 inNumberAddresses,
55                               const AudioObjectPropertyAddress inAddresses[],
56                               void *inClientData) {
57  PyGILState_STATE gstate = PyGILState_Ensure();
58  PyObject_CallObject(default_output_device_changed_callback, NULL);
59  PyGILState_Release(gstate);
60
61  return noErr;
62}
63
64static PyObject *
65AudioDevice_set_default_output_device_changed_callback(PyObject *self,
66                                                       PyObject *args) {
67  PyObject *new_callback;
68  if (!PyArg_ParseTuple(args, "O", &new_callback))
69    return NULL;
70
71  if (!PyCallable_Check(new_callback)) {
72    PyErr_SetString(PyExc_TypeError, "parameter must be callable");
73    return NULL;
74  }
75  Py_INCREF(new_callback);
76  if (default_output_device_changed_callback == NULL) {
77    AudioObjectPropertyAddress propertyAddress;
78    propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
79    propertyAddress.mElement = kAudioObjectPropertyElementMaster;
80    propertyAddress.mSelector = kAudioHardwarePropertyDefaultOutputDevice;
81
82    OSStatus err;
83    err = AudioObjectAddPropertyListener(kAudioObjectSystemObject,
84                                         &propertyAddress,
85                                         &output_device_changed_listener,
86                                         NULL);
87    if (err != noErr)
88      return OSError_from_HALError("AudioObjectAddPropertyListener", err);
89  } else {
90    Py_DECREF(default_output_device_changed_callback);
91  }
92  default_output_device_changed_callback = new_callback;
93 
94  Py_RETURN_NONE;
95}
96
97static PyMethodDef AudioDevicemodule_methods[] = {
98  {"default_output_device_is_airplay",
99   AudioDevice_default_output_device_is_airplay, METH_NOARGS,
100   "default_output_device() -> bool or None\n\n"
101   "Return whether the default CoreAudio output device is an AirPlay device."},
102  {"set_default_output_device_changed_callback",
103   AudioDevice_set_default_output_device_changed_callback, METH_VARARGS,
104   "set_default_output_device_changed_callback(callable)\n\n"
105   "Set a callback invoked when the default CoreAudio output device changes."},
106  {NULL, NULL, 0, NULL}
107};
108
109PyMODINIT_FUNC
110initAudioDevice(void) {
111  (void)Py_InitModule("AudioDevice", AudioDevicemodule_methods);
112}
Note: See TracBrowser for help on using the repository browser.