├── .gitignore ├── AirSpeaker.xcodeproj └── project.pbxproj ├── AirSpeaker ├── AirSpeaker-Info.plist ├── AirSpeaker-Prefix.pch ├── AirSpeakerAppDelegate.h ├── AirSpeakerAppDelegate.m ├── AirSpeakerViewController.h ├── AirSpeakerViewController.m ├── DeviceInfo.h ├── DeviceInfo.m ├── en.lproj │ ├── AirSpeakerViewController.xib │ ├── InfoPlist.strings │ └── MainWindow.xib ├── iPad │ └── en.lproj │ │ └── MainWindow-iPad.xib └── main.m ├── AirTunes ├── AirTunes.h ├── AirTunesController.h ├── AirTunesController.m ├── AudioPlayer.h ├── AudioPlayer.m ├── Base64.h ├── Base64.m ├── CryptoController.h ├── CryptoController.m ├── DMAP.h ├── DMAP.m ├── TimeSync.h ├── TimeSync.m ├── alac.c └── alac.h ├── CocoaAsyncSocket ├── AsyncUdpSocket.h ├── AsyncUdpSocket.m ├── GCDAsyncSocket.h └── GCDAsyncSocket.m ├── Icons ├── AirPlay Icon.png ├── AirSpeaker Large Icon.png ├── AirSpeaker iPad Icon.png ├── AirSpeaker iPhone Icon.png └── AirSpeaker iPhone Retina Icon.png ├── LICENSE └── README /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /build/ 3 | /*.xcodeproj/ 4 | !/*.xcodeproj/project.pbxproj 5 | -------------------------------------------------------------------------------- /AirSpeaker.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 2811245C13009F8F00BBB46C /* AsyncUdpSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 2811245B13009F8F00BBB46C /* AsyncUdpSocket.m */; }; 11 | 281124611300A04300BBB46C /* GCDAsyncSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 281124601300A04300BBB46C /* GCDAsyncSocket.m */; }; 12 | 281124641300A07700BBB46C /* AirTunesController.m in Sources */ = {isa = PBXBuildFile; fileRef = 281124631300A07700BBB46C /* AirTunesController.m */; }; 13 | 284001B812EE56D70048E3B2 /* DeviceInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 284001B712EE56D70048E3B2 /* DeviceInfo.m */; }; 14 | 284001BC12EE60390048E3B2 /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 284001BB12EE60390048E3B2 /* CFNetwork.framework */; }; 15 | 284B92D51301F9F300C87366 /* TimeSync.m in Sources */ = {isa = PBXBuildFile; fileRef = 284B92D41301F9F300C87366 /* TimeSync.m */; }; 16 | 285BCFE5135CBFC300E712C9 /* alac.c in Sources */ = {isa = PBXBuildFile; fileRef = 285BCFE3135CBFC300E712C9 /* alac.c */; }; 17 | 285BCFF3135D077200E712C9 /* MainWindow-iPad.xib in Resources */ = {isa = PBXBuildFile; fileRef = 285BCFF1135D077200E712C9 /* MainWindow-iPad.xib */; }; 18 | 285BD000135D0AF800E712C9 /* AirSpeaker iPad Icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 285BCFFD135D0AF700E712C9 /* AirSpeaker iPad Icon.png */; }; 19 | 285BD001135D0AF800E712C9 /* AirSpeaker iPhone Icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 285BCFFE135D0AF800E712C9 /* AirSpeaker iPhone Icon.png */; }; 20 | 285BD002135D0AF800E712C9 /* AirSpeaker iPhone Retina Icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 285BCFFF135D0AF800E712C9 /* AirSpeaker iPhone Retina Icon.png */; }; 21 | 28962969135E243900559145 /* DMAP.m in Sources */ = {isa = PBXBuildFile; fileRef = 28962968135E243900559145 /* DMAP.m */; }; 22 | 28A0433C1310ABFF00FD39A2 /* CryptoController.m in Sources */ = {isa = PBXBuildFile; fileRef = 28A0433B1310ABFF00FD39A2 /* CryptoController.m */; }; 23 | 28A0433F1310B42700FD39A2 /* Base64.m in Sources */ = {isa = PBXBuildFile; fileRef = 28A0433E1310B42600FD39A2 /* Base64.m */; }; 24 | 28A0D5E4130469B10075A7B4 /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 28A0D5E3130469B10075A7B4 /* AVFoundation.framework */; }; 25 | 28A4EF8B13036FD300BD52D7 /* AudioPlayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 28A4EF8A13036FD300BD52D7 /* AudioPlayer.m */; }; 26 | 28A4EF95130387AE00BD52D7 /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 28A4EF94130387AE00BD52D7 /* AudioToolbox.framework */; }; 27 | 28D3A4A1135A817300AF78CC /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 28D3A4A0135A817300AF78CC /* Security.framework */; }; 28 | 28E3D42012EDEACF00BB15D9 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 28E3D41F12EDEACF00BB15D9 /* UIKit.framework */; }; 29 | 28E3D42212EDEACF00BB15D9 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 28E3D42112EDEACF00BB15D9 /* Foundation.framework */; }; 30 | 28E3D42412EDEACF00BB15D9 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 28E3D42312EDEACF00BB15D9 /* CoreGraphics.framework */; }; 31 | 28E3D42A12EDEACF00BB15D9 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 28E3D42812EDEACF00BB15D9 /* InfoPlist.strings */; }; 32 | 28E3D42D12EDEACF00BB15D9 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 28E3D42C12EDEACF00BB15D9 /* main.m */; }; 33 | 28E3D43012EDEACF00BB15D9 /* AirSpeakerAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 28E3D42F12EDEACF00BB15D9 /* AirSpeakerAppDelegate.m */; }; 34 | 28E3D43312EDEACF00BB15D9 /* MainWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 28E3D43112EDEACF00BB15D9 /* MainWindow.xib */; }; 35 | 28E3D43612EDEACF00BB15D9 /* AirSpeakerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 28E3D43512EDEACF00BB15D9 /* AirSpeakerViewController.m */; }; 36 | 28E3D43912EDEACF00BB15D9 /* AirSpeakerViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 28E3D43712EDEACF00BB15D9 /* AirSpeakerViewController.xib */; }; 37 | /* End PBXBuildFile section */ 38 | 39 | /* Begin PBXFileReference section */ 40 | 2811245A13009F8F00BBB46C /* AsyncUdpSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AsyncUdpSocket.h; path = CocoaAsyncSocket/AsyncUdpSocket.h; sourceTree = ""; }; 41 | 2811245B13009F8F00BBB46C /* AsyncUdpSocket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AsyncUdpSocket.m; path = CocoaAsyncSocket/AsyncUdpSocket.m; sourceTree = ""; }; 42 | 2811245F1300A04300BBB46C /* GCDAsyncSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GCDAsyncSocket.h; path = CocoaAsyncSocket/GCDAsyncSocket.h; sourceTree = ""; }; 43 | 281124601300A04300BBB46C /* GCDAsyncSocket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GCDAsyncSocket.m; path = CocoaAsyncSocket/GCDAsyncSocket.m; sourceTree = ""; }; 44 | 281124621300A07100BBB46C /* AirTunesController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AirTunesController.h; path = AirTunes/AirTunesController.h; sourceTree = SOURCE_ROOT; }; 45 | 281124631300A07700BBB46C /* AirTunesController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AirTunesController.m; path = AirTunes/AirTunesController.m; sourceTree = SOURCE_ROOT; }; 46 | 284001B612EE56D70048E3B2 /* DeviceInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DeviceInfo.h; sourceTree = ""; }; 47 | 284001B712EE56D70048E3B2 /* DeviceInfo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DeviceInfo.m; sourceTree = ""; }; 48 | 284001BB12EE60390048E3B2 /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = System/Library/Frameworks/CFNetwork.framework; sourceTree = SDKROOT; }; 49 | 284B92D31301F9F300C87366 /* TimeSync.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = TimeSync.h; path = AirTunes/TimeSync.h; sourceTree = SOURCE_ROOT; }; 50 | 284B92D41301F9F300C87366 /* TimeSync.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TimeSync.m; path = AirTunes/TimeSync.m; sourceTree = SOURCE_ROOT; }; 51 | 285BCFE3135CBFC300E712C9 /* alac.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = alac.c; path = AirTunes/alac.c; sourceTree = SOURCE_ROOT; }; 52 | 285BCFE4135CBFC300E712C9 /* alac.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = alac.h; path = AirTunes/alac.h; sourceTree = SOURCE_ROOT; }; 53 | 285BCFF2135D077200E712C9 /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = "iPad/en.lproj/MainWindow-iPad.xib"; sourceTree = ""; }; 54 | 285BCFFD135D0AF700E712C9 /* AirSpeaker iPad Icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "AirSpeaker iPad Icon.png"; path = "Icons/AirSpeaker iPad Icon.png"; sourceTree = ""; }; 55 | 285BCFFE135D0AF800E712C9 /* AirSpeaker iPhone Icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "AirSpeaker iPhone Icon.png"; path = "Icons/AirSpeaker iPhone Icon.png"; sourceTree = ""; }; 56 | 285BCFFF135D0AF800E712C9 /* AirSpeaker iPhone Retina Icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "AirSpeaker iPhone Retina Icon.png"; path = "Icons/AirSpeaker iPhone Retina Icon.png"; sourceTree = ""; }; 57 | 28962967135E243900559145 /* DMAP.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DMAP.h; path = AirTunes/DMAP.h; sourceTree = SOURCE_ROOT; }; 58 | 28962968135E243900559145 /* DMAP.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = DMAP.m; path = AirTunes/DMAP.m; sourceTree = SOURCE_ROOT; }; 59 | 28A0433A1310ABFF00FD39A2 /* CryptoController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CryptoController.h; path = AirTunes/CryptoController.h; sourceTree = SOURCE_ROOT; }; 60 | 28A0433B1310ABFF00FD39A2 /* CryptoController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CryptoController.m; path = AirTunes/CryptoController.m; sourceTree = SOURCE_ROOT; }; 61 | 28A0433D1310B42600FD39A2 /* Base64.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Base64.h; path = AirTunes/Base64.h; sourceTree = SOURCE_ROOT; }; 62 | 28A0433E1310B42600FD39A2 /* Base64.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Base64.m; path = AirTunes/Base64.m; sourceTree = SOURCE_ROOT; }; 63 | 28A0D5E3130469B10075A7B4 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; }; 64 | 28A4EF88130366A000BD52D7 /* AirTunes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AirTunes.h; path = AirTunes/AirTunes.h; sourceTree = SOURCE_ROOT; }; 65 | 28A4EF8913036FD300BD52D7 /* AudioPlayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AudioPlayer.h; path = AirTunes/AudioPlayer.h; sourceTree = SOURCE_ROOT; }; 66 | 28A4EF8A13036FD300BD52D7 /* AudioPlayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AudioPlayer.m; path = AirTunes/AudioPlayer.m; sourceTree = SOURCE_ROOT; }; 67 | 28A4EF94130387AE00BD52D7 /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; }; 68 | 28D3A4A0135A817300AF78CC /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; 69 | 28E3D41B12EDEACF00BB15D9 /* AirSpeaker.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AirSpeaker.app; sourceTree = BUILT_PRODUCTS_DIR; }; 70 | 28E3D41F12EDEACF00BB15D9 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 71 | 28E3D42112EDEACF00BB15D9 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 72 | 28E3D42312EDEACF00BB15D9 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 73 | 28E3D42712EDEACF00BB15D9 /* AirSpeaker-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "AirSpeaker-Info.plist"; sourceTree = ""; }; 74 | 28E3D42912EDEACF00BB15D9 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 75 | 28E3D42B12EDEACF00BB15D9 /* AirSpeaker-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "AirSpeaker-Prefix.pch"; sourceTree = ""; }; 76 | 28E3D42C12EDEACF00BB15D9 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 77 | 28E3D42E12EDEACF00BB15D9 /* AirSpeakerAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AirSpeakerAppDelegate.h; sourceTree = ""; }; 78 | 28E3D42F12EDEACF00BB15D9 /* AirSpeakerAppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AirSpeakerAppDelegate.m; sourceTree = ""; }; 79 | 28E3D43212EDEACF00BB15D9 /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/MainWindow.xib; sourceTree = ""; }; 80 | 28E3D43412EDEACF00BB15D9 /* AirSpeakerViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AirSpeakerViewController.h; sourceTree = ""; }; 81 | 28E3D43512EDEACF00BB15D9 /* AirSpeakerViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AirSpeakerViewController.m; sourceTree = ""; }; 82 | 28E3D43812EDEACF00BB15D9 /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/AirSpeakerViewController.xib; sourceTree = ""; }; 83 | /* End PBXFileReference section */ 84 | 85 | /* Begin PBXFrameworksBuildPhase section */ 86 | 28E3D41812EDEACE00BB15D9 /* Frameworks */ = { 87 | isa = PBXFrameworksBuildPhase; 88 | buildActionMask = 2147483647; 89 | files = ( 90 | 28D3A4A1135A817300AF78CC /* Security.framework in Frameworks */, 91 | 28E3D42012EDEACF00BB15D9 /* UIKit.framework in Frameworks */, 92 | 28E3D42212EDEACF00BB15D9 /* Foundation.framework in Frameworks */, 93 | 28E3D42412EDEACF00BB15D9 /* CoreGraphics.framework in Frameworks */, 94 | 284001BC12EE60390048E3B2 /* CFNetwork.framework in Frameworks */, 95 | 28A4EF95130387AE00BD52D7 /* AudioToolbox.framework in Frameworks */, 96 | 28A0D5E4130469B10075A7B4 /* AVFoundation.framework in Frameworks */, 97 | ); 98 | runOnlyForDeploymentPostprocessing = 0; 99 | }; 100 | /* End PBXFrameworksBuildPhase section */ 101 | 102 | /* Begin PBXGroup section */ 103 | 2811245D13009F9A00BBB46C /* CocoaAsyncSocket */ = { 104 | isa = PBXGroup; 105 | children = ( 106 | 2811245A13009F8F00BBB46C /* AsyncUdpSocket.h */, 107 | 2811245B13009F8F00BBB46C /* AsyncUdpSocket.m */, 108 | 2811245F1300A04300BBB46C /* GCDAsyncSocket.h */, 109 | 281124601300A04300BBB46C /* GCDAsyncSocket.m */, 110 | ); 111 | name = CocoaAsyncSocket; 112 | sourceTree = ""; 113 | }; 114 | 2811245E13009FB900BBB46C /* AirTunes */ = { 115 | isa = PBXGroup; 116 | children = ( 117 | 285BCFE3135CBFC300E712C9 /* alac.c */, 118 | 285BCFE4135CBFC300E712C9 /* alac.h */, 119 | 28A4EF88130366A000BD52D7 /* AirTunes.h */, 120 | 281124621300A07100BBB46C /* AirTunesController.h */, 121 | 281124631300A07700BBB46C /* AirTunesController.m */, 122 | 284B92D31301F9F300C87366 /* TimeSync.h */, 123 | 284B92D41301F9F300C87366 /* TimeSync.m */, 124 | 28A4EF8913036FD300BD52D7 /* AudioPlayer.h */, 125 | 28A4EF8A13036FD300BD52D7 /* AudioPlayer.m */, 126 | 28A0433A1310ABFF00FD39A2 /* CryptoController.h */, 127 | 28A0433B1310ABFF00FD39A2 /* CryptoController.m */, 128 | 28A0433D1310B42600FD39A2 /* Base64.h */, 129 | 28A0433E1310B42600FD39A2 /* Base64.m */, 130 | 28962967135E243900559145 /* DMAP.h */, 131 | 28962968135E243900559145 /* DMAP.m */, 132 | ); 133 | name = AirTunes; 134 | path = AirSpeaker; 135 | sourceTree = ""; 136 | }; 137 | 285BCFF0135D077100E712C9 /* iPad */ = { 138 | isa = PBXGroup; 139 | children = ( 140 | 285BCFF1135D077200E712C9 /* MainWindow-iPad.xib */, 141 | ); 142 | name = iPad; 143 | sourceTree = ""; 144 | }; 145 | 285BCFF6135D079100E712C9 /* Icons */ = { 146 | isa = PBXGroup; 147 | children = ( 148 | 285BCFFD135D0AF700E712C9 /* AirSpeaker iPad Icon.png */, 149 | 285BCFFE135D0AF800E712C9 /* AirSpeaker iPhone Icon.png */, 150 | 285BCFFF135D0AF800E712C9 /* AirSpeaker iPhone Retina Icon.png */, 151 | ); 152 | name = Icons; 153 | sourceTree = ""; 154 | }; 155 | 285BD00B135D0D2800E712C9 /* Resources */ = { 156 | isa = PBXGroup; 157 | children = ( 158 | 285BCFF6135D079100E712C9 /* Icons */, 159 | ); 160 | name = Resources; 161 | sourceTree = ""; 162 | }; 163 | 28E3D41012EDEACE00BB15D9 = { 164 | isa = PBXGroup; 165 | children = ( 166 | 2811245D13009F9A00BBB46C /* CocoaAsyncSocket */, 167 | 2811245E13009FB900BBB46C /* AirTunes */, 168 | 28E3D42512EDEACF00BB15D9 /* AirSpeaker */, 169 | 285BD00B135D0D2800E712C9 /* Resources */, 170 | 28E3D41E12EDEACF00BB15D9 /* Frameworks */, 171 | 28E3D41C12EDEACF00BB15D9 /* Products */, 172 | ); 173 | sourceTree = ""; 174 | }; 175 | 28E3D41C12EDEACF00BB15D9 /* Products */ = { 176 | isa = PBXGroup; 177 | children = ( 178 | 28E3D41B12EDEACF00BB15D9 /* AirSpeaker.app */, 179 | ); 180 | name = Products; 181 | sourceTree = ""; 182 | }; 183 | 28E3D41E12EDEACF00BB15D9 /* Frameworks */ = { 184 | isa = PBXGroup; 185 | children = ( 186 | 28D3A4A0135A817300AF78CC /* Security.framework */, 187 | 28A0D5E3130469B10075A7B4 /* AVFoundation.framework */, 188 | 28A4EF94130387AE00BD52D7 /* AudioToolbox.framework */, 189 | 284001BB12EE60390048E3B2 /* CFNetwork.framework */, 190 | 28E3D41F12EDEACF00BB15D9 /* UIKit.framework */, 191 | 28E3D42112EDEACF00BB15D9 /* Foundation.framework */, 192 | 28E3D42312EDEACF00BB15D9 /* CoreGraphics.framework */, 193 | ); 194 | name = Frameworks; 195 | sourceTree = ""; 196 | }; 197 | 28E3D42512EDEACF00BB15D9 /* AirSpeaker */ = { 198 | isa = PBXGroup; 199 | children = ( 200 | 285BCFF0135D077100E712C9 /* iPad */, 201 | 284001B612EE56D70048E3B2 /* DeviceInfo.h */, 202 | 284001B712EE56D70048E3B2 /* DeviceInfo.m */, 203 | 28E3D42E12EDEACF00BB15D9 /* AirSpeakerAppDelegate.h */, 204 | 28E3D42F12EDEACF00BB15D9 /* AirSpeakerAppDelegate.m */, 205 | 28E3D43112EDEACF00BB15D9 /* MainWindow.xib */, 206 | 28E3D43412EDEACF00BB15D9 /* AirSpeakerViewController.h */, 207 | 28E3D43512EDEACF00BB15D9 /* AirSpeakerViewController.m */, 208 | 28E3D43712EDEACF00BB15D9 /* AirSpeakerViewController.xib */, 209 | 28E3D42612EDEACF00BB15D9 /* Supporting Files */, 210 | ); 211 | path = AirSpeaker; 212 | sourceTree = ""; 213 | }; 214 | 28E3D42612EDEACF00BB15D9 /* Supporting Files */ = { 215 | isa = PBXGroup; 216 | children = ( 217 | 28E3D42712EDEACF00BB15D9 /* AirSpeaker-Info.plist */, 218 | 28E3D42812EDEACF00BB15D9 /* InfoPlist.strings */, 219 | 28E3D42B12EDEACF00BB15D9 /* AirSpeaker-Prefix.pch */, 220 | 28E3D42C12EDEACF00BB15D9 /* main.m */, 221 | ); 222 | name = "Supporting Files"; 223 | sourceTree = ""; 224 | }; 225 | /* End PBXGroup section */ 226 | 227 | /* Begin PBXNativeTarget section */ 228 | 28E3D41A12EDEACE00BB15D9 /* AirSpeaker */ = { 229 | isa = PBXNativeTarget; 230 | buildConfigurationList = 28E3D43C12EDEACF00BB15D9 /* Build configuration list for PBXNativeTarget "AirSpeaker" */; 231 | buildPhases = ( 232 | 28E3D41712EDEACE00BB15D9 /* Sources */, 233 | 28E3D41812EDEACE00BB15D9 /* Frameworks */, 234 | 28E3D41912EDEACE00BB15D9 /* Resources */, 235 | ); 236 | buildRules = ( 237 | ); 238 | dependencies = ( 239 | ); 240 | name = AirSpeaker; 241 | productName = AirSpeaker; 242 | productReference = 28E3D41B12EDEACF00BB15D9 /* AirSpeaker.app */; 243 | productType = "com.apple.product-type.application"; 244 | }; 245 | /* End PBXNativeTarget section */ 246 | 247 | /* Begin PBXProject section */ 248 | 28E3D41212EDEACE00BB15D9 /* Project object */ = { 249 | isa = PBXProject; 250 | attributes = { 251 | ORGANIZATIONNAME = "Freebox S.A.S."; 252 | }; 253 | buildConfigurationList = 28E3D41512EDEACE00BB15D9 /* Build configuration list for PBXProject "AirSpeaker" */; 254 | compatibilityVersion = "Xcode 3.2"; 255 | developmentRegion = English; 256 | hasScannedForEncodings = 0; 257 | knownRegions = ( 258 | en, 259 | ); 260 | mainGroup = 28E3D41012EDEACE00BB15D9; 261 | productRefGroup = 28E3D41C12EDEACF00BB15D9 /* Products */; 262 | projectDirPath = ""; 263 | projectRoot = ""; 264 | targets = ( 265 | 28E3D41A12EDEACE00BB15D9 /* AirSpeaker */, 266 | ); 267 | }; 268 | /* End PBXProject section */ 269 | 270 | /* Begin PBXResourcesBuildPhase section */ 271 | 28E3D41912EDEACE00BB15D9 /* Resources */ = { 272 | isa = PBXResourcesBuildPhase; 273 | buildActionMask = 2147483647; 274 | files = ( 275 | 28E3D42A12EDEACF00BB15D9 /* InfoPlist.strings in Resources */, 276 | 28E3D43312EDEACF00BB15D9 /* MainWindow.xib in Resources */, 277 | 28E3D43912EDEACF00BB15D9 /* AirSpeakerViewController.xib in Resources */, 278 | 285BCFF3135D077200E712C9 /* MainWindow-iPad.xib in Resources */, 279 | 285BD000135D0AF800E712C9 /* AirSpeaker iPad Icon.png in Resources */, 280 | 285BD001135D0AF800E712C9 /* AirSpeaker iPhone Icon.png in Resources */, 281 | 285BD002135D0AF800E712C9 /* AirSpeaker iPhone Retina Icon.png in Resources */, 282 | ); 283 | runOnlyForDeploymentPostprocessing = 0; 284 | }; 285 | /* End PBXResourcesBuildPhase section */ 286 | 287 | /* Begin PBXSourcesBuildPhase section */ 288 | 28E3D41712EDEACE00BB15D9 /* Sources */ = { 289 | isa = PBXSourcesBuildPhase; 290 | buildActionMask = 2147483647; 291 | files = ( 292 | 28E3D42D12EDEACF00BB15D9 /* main.m in Sources */, 293 | 28E3D43012EDEACF00BB15D9 /* AirSpeakerAppDelegate.m in Sources */, 294 | 28E3D43612EDEACF00BB15D9 /* AirSpeakerViewController.m in Sources */, 295 | 284001B812EE56D70048E3B2 /* DeviceInfo.m in Sources */, 296 | 2811245C13009F8F00BBB46C /* AsyncUdpSocket.m in Sources */, 297 | 281124611300A04300BBB46C /* GCDAsyncSocket.m in Sources */, 298 | 281124641300A07700BBB46C /* AirTunesController.m in Sources */, 299 | 284B92D51301F9F300C87366 /* TimeSync.m in Sources */, 300 | 28A4EF8B13036FD300BD52D7 /* AudioPlayer.m in Sources */, 301 | 28A0433C1310ABFF00FD39A2 /* CryptoController.m in Sources */, 302 | 28A0433F1310B42700FD39A2 /* Base64.m in Sources */, 303 | 285BCFE5135CBFC300E712C9 /* alac.c in Sources */, 304 | 28962969135E243900559145 /* DMAP.m in Sources */, 305 | ); 306 | runOnlyForDeploymentPostprocessing = 0; 307 | }; 308 | /* End PBXSourcesBuildPhase section */ 309 | 310 | /* Begin PBXVariantGroup section */ 311 | 285BCFF1135D077200E712C9 /* MainWindow-iPad.xib */ = { 312 | isa = PBXVariantGroup; 313 | children = ( 314 | 285BCFF2135D077200E712C9 /* en */, 315 | ); 316 | name = "MainWindow-iPad.xib"; 317 | sourceTree = ""; 318 | }; 319 | 28E3D42812EDEACF00BB15D9 /* InfoPlist.strings */ = { 320 | isa = PBXVariantGroup; 321 | children = ( 322 | 28E3D42912EDEACF00BB15D9 /* en */, 323 | ); 324 | name = InfoPlist.strings; 325 | sourceTree = ""; 326 | }; 327 | 28E3D43112EDEACF00BB15D9 /* MainWindow.xib */ = { 328 | isa = PBXVariantGroup; 329 | children = ( 330 | 28E3D43212EDEACF00BB15D9 /* en */, 331 | ); 332 | name = MainWindow.xib; 333 | sourceTree = ""; 334 | }; 335 | 28E3D43712EDEACF00BB15D9 /* AirSpeakerViewController.xib */ = { 336 | isa = PBXVariantGroup; 337 | children = ( 338 | 28E3D43812EDEACF00BB15D9 /* en */, 339 | ); 340 | name = AirSpeakerViewController.xib; 341 | sourceTree = ""; 342 | }; 343 | /* End PBXVariantGroup section */ 344 | 345 | /* Begin XCBuildConfiguration section */ 346 | 28E3D43A12EDEACF00BB15D9 /* Debug */ = { 347 | isa = XCBuildConfiguration; 348 | buildSettings = { 349 | ARCHS = "$(ARCHS_STANDARD_32_BIT)"; 350 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 351 | GCC_C_LANGUAGE_STANDARD = gnu99; 352 | GCC_OPTIMIZATION_LEVEL = 0; 353 | GCC_PREPROCESSOR_DEFINITIONS = DEBUG; 354 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 355 | GCC_WARN_UNUSED_VARIABLE = YES; 356 | IPHONEOS_DEPLOYMENT_TARGET = 4.3; 357 | SDKROOT = iphoneos; 358 | }; 359 | name = Debug; 360 | }; 361 | 28E3D43B12EDEACF00BB15D9 /* Release */ = { 362 | isa = XCBuildConfiguration; 363 | buildSettings = { 364 | ARCHS = "$(ARCHS_STANDARD_32_BIT)"; 365 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 366 | GCC_C_LANGUAGE_STANDARD = gnu99; 367 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 368 | GCC_WARN_UNUSED_VARIABLE = YES; 369 | IPHONEOS_DEPLOYMENT_TARGET = 4.3; 370 | OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1"; 371 | SDKROOT = iphoneos; 372 | }; 373 | name = Release; 374 | }; 375 | 28E3D43D12EDEACF00BB15D9 /* Debug */ = { 376 | isa = XCBuildConfiguration; 377 | buildSettings = { 378 | ALWAYS_SEARCH_USER_PATHS = NO; 379 | COPY_PHASE_STRIP = NO; 380 | GCC_DYNAMIC_NO_PIC = NO; 381 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 382 | GCC_PREFIX_HEADER = "AirSpeaker/AirSpeaker-Prefix.pch"; 383 | INFOPLIST_FILE = "AirSpeaker/AirSpeaker-Info.plist"; 384 | IPHONEOS_DEPLOYMENT_TARGET = 4.3; 385 | PRODUCT_NAME = "$(TARGET_NAME)"; 386 | TARGETED_DEVICE_FAMILY = "1,2"; 387 | WRAPPER_EXTENSION = app; 388 | }; 389 | name = Debug; 390 | }; 391 | 28E3D43E12EDEACF00BB15D9 /* Release */ = { 392 | isa = XCBuildConfiguration; 393 | buildSettings = { 394 | ALWAYS_SEARCH_USER_PATHS = NO; 395 | COPY_PHASE_STRIP = YES; 396 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 397 | GCC_PREFIX_HEADER = "AirSpeaker/AirSpeaker-Prefix.pch"; 398 | INFOPLIST_FILE = "AirSpeaker/AirSpeaker-Info.plist"; 399 | IPHONEOS_DEPLOYMENT_TARGET = 4.3; 400 | PRODUCT_NAME = "$(TARGET_NAME)"; 401 | TARGETED_DEVICE_FAMILY = "1,2"; 402 | VALIDATE_PRODUCT = YES; 403 | WRAPPER_EXTENSION = app; 404 | }; 405 | name = Release; 406 | }; 407 | /* End XCBuildConfiguration section */ 408 | 409 | /* Begin XCConfigurationList section */ 410 | 28E3D41512EDEACE00BB15D9 /* Build configuration list for PBXProject "AirSpeaker" */ = { 411 | isa = XCConfigurationList; 412 | buildConfigurations = ( 413 | 28E3D43A12EDEACF00BB15D9 /* Debug */, 414 | 28E3D43B12EDEACF00BB15D9 /* Release */, 415 | ); 416 | defaultConfigurationIsVisible = 0; 417 | defaultConfigurationName = Release; 418 | }; 419 | 28E3D43C12EDEACF00BB15D9 /* Build configuration list for PBXNativeTarget "AirSpeaker" */ = { 420 | isa = XCConfigurationList; 421 | buildConfigurations = ( 422 | 28E3D43D12EDEACF00BB15D9 /* Debug */, 423 | 28E3D43E12EDEACF00BB15D9 /* Release */, 424 | ); 425 | defaultConfigurationIsVisible = 0; 426 | defaultConfigurationName = Release; 427 | }; 428 | /* End XCConfigurationList section */ 429 | }; 430 | rootObject = 28E3D41212EDEACE00BB15D9 /* Project object */; 431 | } 432 | -------------------------------------------------------------------------------- /AirSpeaker/AirSpeaker-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIconFile 12 | 13 | CFBundleIconFiles 14 | 15 | AirSpeaker iPhone Retina Icon.png 16 | AirSpeaker iPhone Icon.png 17 | AirSpeaker iPad Icon.png 18 | 19 | CFBundleIdentifier 20 | com.github.nto.${PRODUCT_NAME:rfc1034identifier} 21 | CFBundleInfoDictionaryVersion 22 | 6.0 23 | CFBundleName 24 | ${PRODUCT_NAME} 25 | CFBundlePackageType 26 | APPL 27 | CFBundleShortVersionString 28 | 1.0 29 | CFBundleSignature 30 | ???? 31 | CFBundleVersion 32 | 1.0 33 | LSRequiresIPhoneOS 34 | 35 | NSMainNibFile 36 | MainWindow 37 | NSMainNibFile~ipad 38 | MainWindow-iPad 39 | UIStatusBarStyle 40 | UIStatusBarStyleBlackOpaque 41 | UISupportedInterfaceOrientations 42 | 43 | UIInterfaceOrientationLandscapeRight 44 | UIInterfaceOrientationLandscapeLeft 45 | UIInterfaceOrientationPortrait 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /AirSpeaker/AirSpeaker-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'AirSpeaker' target in the 'AirSpeaker' project 3 | // 4 | 5 | #import 6 | 7 | #ifndef __IPHONE_3_0 8 | #warning "This project uses features only available in iPhone SDK 3.0 and later." 9 | #endif 10 | 11 | #ifdef __OBJC__ 12 | #import 13 | #import 14 | #endif 15 | -------------------------------------------------------------------------------- /AirSpeaker/AirSpeakerAppDelegate.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nto/AirSpeaker/09fe7762235b0809ddca0007cb1dd236faf52700/AirSpeaker/AirSpeakerAppDelegate.h -------------------------------------------------------------------------------- /AirSpeaker/AirSpeakerAppDelegate.m: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nto/AirSpeaker/09fe7762235b0809ddca0007cb1dd236faf52700/AirSpeaker/AirSpeakerAppDelegate.m -------------------------------------------------------------------------------- /AirSpeaker/AirSpeakerViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // AirSpeakerViewController.h 3 | // AirSpeaker 4 | // 5 | // Created by Clément Vasseur on 1/24/11. 6 | // Copyright 2011 Clément Vasseur. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AirTunesController.h" 11 | 12 | @interface AirSpeakerViewController : UIViewController { 13 | AirTunesController *airtunes; 14 | IBOutlet UIImageView *imageView; 15 | IBOutlet UILabel *artistLabel; 16 | IBOutlet UILabel *songLabel; 17 | } 18 | 19 | @property (readwrite, retain, nonatomic) AirTunesController *airtunes; 20 | 21 | - (void)setMetadata:(NSDictionary *)metadata; 22 | - (void)setCoverData:(NSData *)cover; 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /AirSpeaker/AirSpeakerViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // AirSpeakerViewController.m 3 | // AirSpeaker 4 | // 5 | // Created by Clément Vasseur on 1/24/11. 6 | // Copyright 2011 Clément Vasseur. All rights reserved. 7 | // 8 | 9 | #import "AirSpeakerViewController.h" 10 | 11 | @implementation AirSpeakerViewController 12 | 13 | @synthesize airtunes; 14 | 15 | - (void)setAirtunes:(AirTunesController *)airtunesController 16 | { 17 | airtunes = [airtunesController retain]; 18 | airtunes.metadataDelegate = self; 19 | airtunes.coverDelegate = self; 20 | } 21 | 22 | - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil 23 | { 24 | self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; 25 | if (self) { 26 | // Custom initialization 27 | } 28 | return self; 29 | } 30 | 31 | - (void)dealloc 32 | { 33 | [airtunes release]; 34 | [imageView dealloc]; 35 | [artistLabel release]; 36 | [songLabel release]; 37 | [super dealloc]; 38 | } 39 | 40 | - (void)didReceiveMemoryWarning 41 | { 42 | // Releases the view if it doesn't have a superview. 43 | [super didReceiveMemoryWarning]; 44 | 45 | // Release any cached data, images, etc that aren't in use. 46 | } 47 | 48 | #pragma mark - View lifecycle 49 | 50 | /* 51 | // Implement loadView to create a view hierarchy programmatically, without using a nib. 52 | - (void)loadView 53 | { 54 | } 55 | */ 56 | 57 | /* 58 | // Implement viewDidLoad to do additional setup after loading the view, typically from a nib. 59 | - (void)viewDidLoad 60 | { 61 | [super viewDidLoad]; 62 | } 63 | */ 64 | 65 | - (void)viewDidUnload 66 | { 67 | [imageView release]; 68 | imageView = nil; 69 | [artistLabel release]; 70 | artistLabel = nil; 71 | [songLabel release]; 72 | songLabel = nil; 73 | [super viewDidUnload]; 74 | // Release any retained subviews of the main view. 75 | // e.g. self.myOutlet = nil; 76 | } 77 | 78 | - (void)setMetadata:(NSDictionary *)metadata 79 | { 80 | NSString *artist = [metadata objectForKey:@"Artist"]; 81 | NSString *song = [metadata objectForKey:@"Song"]; 82 | 83 | NSLog(@"[Meta] song: %@", song); 84 | NSLog(@"[Meta] artist: %@", artist); 85 | NSLog(@"[Meta] album: %@", [metadata objectForKey:@"Album"]); 86 | 87 | imageView.image = nil; 88 | artistLabel.text = artist; 89 | songLabel.text = song; 90 | } 91 | 92 | - (void)setCoverData:(NSData *)cover 93 | { 94 | imageView.image = [UIImage imageWithData:cover]; 95 | } 96 | 97 | @end 98 | -------------------------------------------------------------------------------- /AirSpeaker/DeviceInfo.h: -------------------------------------------------------------------------------- 1 | // 2 | // DeviceInfo.h 3 | // AirSpeaker 4 | // 5 | // Created by Clément Vasseur on 12/22/10. 6 | // Copyright 2010 Clément Vasseur. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface DeviceInfo : NSObject { 12 | 13 | } 14 | 15 | + (NSString *)getSysInfoByName:(char *)typeSpecifier; 16 | + (NSString *)platform; 17 | + (NSData *)deviceId; 18 | + (NSString *)deviceIdString; 19 | + (NSString *)deviceIdWithSep:(NSString *)sep; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /AirSpeaker/DeviceInfo.m: -------------------------------------------------------------------------------- 1 | // 2 | // DeviceInfo.m 3 | // AirSpeaker 4 | // 5 | // Created by Clément Vasseur on 12/22/10. 6 | // Copyright 2010 Clément Vasseur. All rights reserved. 7 | // 8 | 9 | #import "DeviceInfo.h" 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | 19 | #if !defined(IFT_ETHER) 20 | # define IFT_ETHER 0x6 /* Ethernet CSMACD */ 21 | #endif 22 | 23 | @implementation DeviceInfo 24 | 25 | + (NSString *)getSysInfoByName:(char *)typeSpecifier 26 | { 27 | size_t size; 28 | sysctlbyname(typeSpecifier, NULL, &size, NULL, 0); 29 | char *answer = malloc(size); 30 | sysctlbyname(typeSpecifier, answer, &size, NULL, 0); 31 | NSString *results = [NSString stringWithCString:answer encoding:NSUTF8StringEncoding]; 32 | free(answer); 33 | return results; 34 | } 35 | 36 | + (NSString *)platform 37 | { 38 | return [self getSysInfoByName:"hw.machine"]; 39 | } 40 | 41 | + (NSData *)deviceId 42 | { 43 | NSData *res; 44 | struct ifaddrs *addrs; 45 | const struct ifaddrs *cursor; 46 | const struct sockaddr_dl *dlAddr; 47 | const uint8_t *base; 48 | 49 | if (getifaddrs(&addrs) != 0) { 50 | NSLog(@"[DeviceInfo] getifaddrs failed"); 51 | return nil; 52 | } 53 | 54 | res = nil; 55 | 56 | for (cursor = addrs; cursor != NULL; cursor = cursor->ifa_next) { 57 | if ((cursor->ifa_addr->sa_family == AF_LINK) && 58 | (((const struct sockaddr_dl *) cursor->ifa_addr)->sdl_type == IFT_ETHER)) { 59 | 60 | dlAddr = (const struct sockaddr_dl *) cursor->ifa_addr; 61 | base = (const uint8_t *) &dlAddr->sdl_data[dlAddr->sdl_nlen]; 62 | res = [NSData dataWithBytes:base length:dlAddr->sdl_alen]; 63 | break; 64 | } 65 | } 66 | 67 | freeifaddrs(addrs); 68 | return res; 69 | } 70 | 71 | + (NSString *)deviceIdWithSep:(NSString *)sep 72 | { 73 | NSMutableString *res; 74 | NSData *data; 75 | const uint8_t *bytes; 76 | 77 | data = [self deviceId]; 78 | if (data == nil) 79 | return nil; 80 | 81 | bytes = [data bytes]; 82 | 83 | res = [NSMutableString stringWithCapacity:32]; 84 | for (int i = 0; i < [data length]; i++) { 85 | if (sep != nil && i != 0) 86 | [res appendString:sep]; 87 | [res appendFormat:@"%02X", bytes[i]]; 88 | } 89 | 90 | return res; 91 | } 92 | 93 | + (NSString *)deviceIdString 94 | { 95 | return [self deviceIdWithSep:@":"]; 96 | } 97 | 98 | @end 99 | -------------------------------------------------------------------------------- /AirSpeaker/en.lproj/AirSpeakerViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1536 5 | 12A269 6 | 2541 7 | 1187 8 | 624.00 9 | 10 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 11 | 1875 12 | 13 | 14 | YES 15 | IBProxyObject 16 | IBUIImageView 17 | IBUILabel 18 | IBUIView 19 | 20 | 21 | YES 22 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 23 | 24 | 25 | PluginDependencyRecalculationVersion 26 | 27 | 28 | 29 | YES 30 | 31 | IBFilesOwner 32 | IBCocoaTouchFramework 33 | 34 | 35 | IBFirstResponder 36 | IBCocoaTouchFramework 37 | 38 | 39 | 40 | 274 41 | 42 | YES 43 | 44 | 45 | 274 46 | {320, 460} 47 | 48 | 49 | 50 | 51 | 1 52 | MCAwIDAAA 53 | 54 | 1 55 | NO 56 | IBCocoaTouchFramework 57 | 58 | 59 | 60 | 290 61 | {{20, 20}, {280, 21}} 62 | 63 | 64 | 65 | NO 66 | YES 67 | 7 68 | NO 69 | IBCocoaTouchFramework 70 | 71 | 1 72 | 10 73 | 74 | 75 | 1 76 | MSAxIDEAA 77 | 78 | 1 79 | 80 | 81 | 1 82 | 83 | Helvetica-Bold 84 | Helvetica 85 | 2 86 | 18 87 | 88 | 89 | Helvetica-Bold 90 | 18 91 | 16 92 | 93 | 2 94 | 95 | 96 | 97 | 266 98 | {{20, 400}, {280, 40}} 99 | 100 | 101 | NO 102 | YES 103 | 7 104 | NO 105 | IBCocoaTouchFramework 106 | 107 | 1 108 | 10 109 | 2 110 | 111 | 112 | 1 113 | MSAxIDEAA 114 | 115 | 116 | 1 117 | 118 | 1 119 | 17 120 | 121 | 122 | Helvetica 123 | 17 124 | 16 125 | 126 | 2 127 | 128 | 129 | {{0, 20}, {320, 460}} 130 | 131 | 132 | 133 | 134 | NO 135 | 136 | IBCocoaTouchFramework 137 | 138 | 139 | 140 | 141 | YES 142 | 143 | 144 | view 145 | 146 | 147 | 148 | 7 149 | 150 | 151 | 152 | imageView 153 | 154 | 155 | 156 | 9 157 | 158 | 159 | 160 | artistLabel 161 | 162 | 163 | 164 | 12 165 | 166 | 167 | 168 | songLabel 169 | 170 | 171 | 172 | 14 173 | 174 | 175 | 176 | 177 | YES 178 | 179 | 0 180 | 181 | YES 182 | 183 | 184 | 185 | 186 | 187 | -1 188 | 189 | 190 | File's Owner 191 | 192 | 193 | -2 194 | 195 | 196 | 197 | 198 | 6 199 | 200 | 201 | YES 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 8 210 | 211 | 212 | 213 | 214 | 11 215 | 216 | 217 | 218 | 219 | 13 220 | 221 | 222 | 223 | 224 | 225 | 226 | YES 227 | 228 | YES 229 | -1.CustomClassName 230 | -1.IBPluginDependency 231 | -2.CustomClassName 232 | -2.IBPluginDependency 233 | 11.IBPluginDependency 234 | 13.IBPluginDependency 235 | 6.IBPluginDependency 236 | 8.IBPluginDependency 237 | 238 | 239 | YES 240 | AirSpeakerViewController 241 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 242 | UIResponder 243 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 244 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 245 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 246 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 247 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 248 | 249 | 250 | 251 | YES 252 | 253 | 254 | 255 | 256 | 257 | YES 258 | 259 | 260 | 261 | 262 | 14 263 | 264 | 265 | 266 | YES 267 | 268 | AirSpeakerViewController 269 | UIViewController 270 | 271 | YES 272 | 273 | YES 274 | artistLabel 275 | imageView 276 | songLabel 277 | 278 | 279 | YES 280 | UILabel 281 | UIImageView 282 | UILabel 283 | 284 | 285 | 286 | YES 287 | 288 | YES 289 | artistLabel 290 | imageView 291 | songLabel 292 | 293 | 294 | YES 295 | 296 | artistLabel 297 | UILabel 298 | 299 | 300 | imageView 301 | UIImageView 302 | 303 | 304 | songLabel 305 | UILabel 306 | 307 | 308 | 309 | 310 | IBProjectSource 311 | ./Classes/AirSpeakerViewController.h 312 | 313 | 314 | 315 | 316 | 0 317 | IBCocoaTouchFramework 318 | 319 | com.apple.InterfaceBuilder.CocoaTouchPlugin.InterfaceBuilder3 320 | 321 | 322 | YES 323 | 3 324 | 1875 325 | 326 | 327 | -------------------------------------------------------------------------------- /AirSpeaker/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /AirSpeaker/en.lproj/MainWindow.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1024 5 | 10D571 6 | 786 7 | 1038.29 8 | 460.00 9 | 10 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 11 | 112 12 | 13 | 14 | YES 15 | 16 | 17 | 18 | YES 19 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 20 | 21 | 22 | YES 23 | 24 | YES 25 | 26 | 27 | YES 28 | 29 | 30 | 31 | YES 32 | 33 | IBFilesOwner 34 | IBCocoaTouchFramework 35 | 36 | 37 | IBFirstResponder 38 | IBCocoaTouchFramework 39 | 40 | 41 | IBCocoaTouchFramework 42 | 43 | 44 | AirSpeakerViewController 45 | 46 | 47 | 1 48 | 49 | IBCocoaTouchFramework 50 | NO 51 | 52 | 53 | 54 | 292 55 | {320, 480} 56 | 57 | 1 58 | MSAxIDEAA 59 | 60 | NO 61 | NO 62 | 63 | IBCocoaTouchFramework 64 | YES 65 | 66 | 67 | 68 | 69 | YES 70 | 71 | 72 | delegate 73 | 74 | 75 | 76 | 4 77 | 78 | 79 | 80 | viewController 81 | 82 | 83 | 84 | 11 85 | 86 | 87 | 88 | window 89 | 90 | 91 | 92 | 14 93 | 94 | 95 | 96 | 97 | YES 98 | 99 | 0 100 | 101 | 102 | 103 | 104 | 105 | -1 106 | 107 | 108 | File's Owner 109 | 110 | 111 | 3 112 | 113 | 114 | AirSpeaker App Delegate 115 | 116 | 117 | -2 118 | 119 | 120 | 121 | 122 | 10 123 | 124 | 125 | 126 | 127 | 12 128 | 129 | 130 | 131 | 132 | 133 | 134 | YES 135 | 136 | YES 137 | -1.CustomClassName 138 | -2.CustomClassName 139 | 10.CustomClassName 140 | 10.IBEditorWindowLastContentRect 141 | 10.IBPluginDependency 142 | 12.IBEditorWindowLastContentRect 143 | 12.IBPluginDependency 144 | 3.CustomClassName 145 | 3.IBPluginDependency 146 | 147 | 148 | YES 149 | UIApplication 150 | UIResponder 151 | AirSpeakerViewController 152 | {{234, 376}, {320, 480}} 153 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 154 | {{525, 346}, {320, 480}} 155 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 156 | AirSpeakerAppDelegate 157 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 158 | 159 | 160 | 161 | YES 162 | 163 | 164 | YES 165 | 166 | 167 | 168 | 169 | YES 170 | 171 | 172 | YES 173 | 174 | 175 | 176 | 15 177 | 178 | 179 | 180 | YES 181 | 182 | UIWindow 183 | UIView 184 | 185 | IBUserSource 186 | 187 | 188 | 189 | 190 | AirSpeakerAppDelegate 191 | NSObject 192 | 193 | YES 194 | 195 | YES 196 | viewController 197 | window 198 | 199 | 200 | YES 201 | AirSpeakerViewController 202 | UIWindow 203 | 204 | 205 | 206 | YES 207 | 208 | YES 209 | viewController 210 | window 211 | 212 | 213 | YES 214 | 215 | viewController 216 | AirSpeakerViewController 217 | 218 | 219 | window 220 | UIWindow 221 | 222 | 223 | 224 | 225 | IBProjectSource 226 | AirSpeakerAppDelegate.h 227 | 228 | 229 | 230 | AirSpeakerAppDelegate 231 | NSObject 232 | 233 | IBUserSource 234 | 235 | 236 | 237 | 238 | AirSpeakerViewController 239 | UIViewController 240 | 241 | IBProjectSource 242 | AirSpeakerViewController.h 243 | 244 | 245 | 246 | 247 | YES 248 | 249 | NSObject 250 | 251 | IBFrameworkSource 252 | Foundation.framework/Headers/NSError.h 253 | 254 | 255 | 256 | NSObject 257 | 258 | IBFrameworkSource 259 | Foundation.framework/Headers/NSFileManager.h 260 | 261 | 262 | 263 | NSObject 264 | 265 | IBFrameworkSource 266 | Foundation.framework/Headers/NSKeyValueCoding.h 267 | 268 | 269 | 270 | NSObject 271 | 272 | IBFrameworkSource 273 | Foundation.framework/Headers/NSKeyValueObserving.h 274 | 275 | 276 | 277 | NSObject 278 | 279 | IBFrameworkSource 280 | Foundation.framework/Headers/NSKeyedArchiver.h 281 | 282 | 283 | 284 | NSObject 285 | 286 | IBFrameworkSource 287 | Foundation.framework/Headers/NSObject.h 288 | 289 | 290 | 291 | NSObject 292 | 293 | IBFrameworkSource 294 | Foundation.framework/Headers/NSRunLoop.h 295 | 296 | 297 | 298 | NSObject 299 | 300 | IBFrameworkSource 301 | Foundation.framework/Headers/NSThread.h 302 | 303 | 304 | 305 | NSObject 306 | 307 | IBFrameworkSource 308 | Foundation.framework/Headers/NSURL.h 309 | 310 | 311 | 312 | NSObject 313 | 314 | IBFrameworkSource 315 | Foundation.framework/Headers/NSURLConnection.h 316 | 317 | 318 | 319 | NSObject 320 | 321 | IBFrameworkSource 322 | UIKit.framework/Headers/UIAccessibility.h 323 | 324 | 325 | 326 | NSObject 327 | 328 | IBFrameworkSource 329 | UIKit.framework/Headers/UINibLoading.h 330 | 331 | 332 | 333 | NSObject 334 | 335 | IBFrameworkSource 336 | UIKit.framework/Headers/UIResponder.h 337 | 338 | 339 | 340 | UIApplication 341 | UIResponder 342 | 343 | IBFrameworkSource 344 | UIKit.framework/Headers/UIApplication.h 345 | 346 | 347 | 348 | UIResponder 349 | NSObject 350 | 351 | 352 | 353 | UISearchBar 354 | UIView 355 | 356 | IBFrameworkSource 357 | UIKit.framework/Headers/UISearchBar.h 358 | 359 | 360 | 361 | UISearchDisplayController 362 | NSObject 363 | 364 | IBFrameworkSource 365 | UIKit.framework/Headers/UISearchDisplayController.h 366 | 367 | 368 | 369 | UIView 370 | 371 | IBFrameworkSource 372 | UIKit.framework/Headers/UITextField.h 373 | 374 | 375 | 376 | UIView 377 | UIResponder 378 | 379 | IBFrameworkSource 380 | UIKit.framework/Headers/UIView.h 381 | 382 | 383 | 384 | UIViewController 385 | 386 | IBFrameworkSource 387 | UIKit.framework/Headers/UINavigationController.h 388 | 389 | 390 | 391 | UIViewController 392 | 393 | IBFrameworkSource 394 | UIKit.framework/Headers/UIPopoverController.h 395 | 396 | 397 | 398 | UIViewController 399 | 400 | IBFrameworkSource 401 | UIKit.framework/Headers/UISplitViewController.h 402 | 403 | 404 | 405 | UIViewController 406 | 407 | IBFrameworkSource 408 | UIKit.framework/Headers/UITabBarController.h 409 | 410 | 411 | 412 | UIViewController 413 | UIResponder 414 | 415 | IBFrameworkSource 416 | UIKit.framework/Headers/UIViewController.h 417 | 418 | 419 | 420 | UIWindow 421 | UIView 422 | 423 | IBFrameworkSource 424 | UIKit.framework/Headers/UIWindow.h 425 | 426 | 427 | 428 | 429 | 0 430 | IBCocoaTouchFramework 431 | 432 | com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS 433 | 434 | 435 | 436 | com.apple.InterfaceBuilder.CocoaTouchPlugin.InterfaceBuilder3 437 | 438 | 439 | YES 440 | AirSpeaker.xcodeproj 441 | 3 442 | 112 443 | 444 | 445 | -------------------------------------------------------------------------------- /AirSpeaker/iPad/en.lproj/MainWindow-iPad.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1056 5 | 10J869 6 | 844 7 | 1038.35 8 | 461.00 9 | 10 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 11 | 141 12 | 13 | 14 | YES 15 | 16 | 17 | YES 18 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 19 | 20 | 21 | YES 22 | 23 | YES 24 | 25 | 26 | YES 27 | 28 | 29 | 30 | YES 31 | 32 | IBFilesOwner 33 | IBIPadFramework 34 | 35 | 36 | IBFirstResponder 37 | IBIPadFramework 38 | 39 | 40 | IBIPadFramework 41 | 42 | 43 | AirSpeakerViewController 44 | 45 | 2 46 | 47 | 48 | 1 49 | 50 | IBIPadFramework 51 | NO 52 | 53 | 54 | 55 | 292 56 | {768, 1004} 57 | 58 | 1 59 | MSAxIDEAA 60 | 61 | NO 62 | NO 63 | 64 | IBIPadFramework 65 | YES 66 | 67 | 68 | 69 | 70 | YES 71 | 72 | 73 | delegate 74 | 75 | 76 | 77 | 4 78 | 79 | 80 | 81 | viewController 82 | 83 | 84 | 85 | 11 86 | 87 | 88 | 89 | window 90 | 91 | 92 | 93 | 14 94 | 95 | 96 | 97 | 98 | YES 99 | 100 | 0 101 | 102 | 103 | 104 | 105 | 106 | -1 107 | 108 | 109 | File's Owner 110 | 111 | 112 | 3 113 | 114 | 115 | AirSpeaker App Delegate 116 | 117 | 118 | -2 119 | 120 | 121 | 122 | 123 | 10 124 | 125 | 126 | 127 | 128 | 12 129 | 130 | 131 | 132 | 133 | 134 | 135 | YES 136 | 137 | YES 138 | -1.CustomClassName 139 | -2.CustomClassName 140 | 10.CustomClassName 141 | 10.IBEditorWindowLastContentRect 142 | 10.IBLastUsedUIStatusBarStylesToTargetRuntimesMap 143 | 10.IBPluginDependency 144 | 12.IBEditorWindowLastContentRect 145 | 12.IBLastUsedUIStatusBarStylesToTargetRuntimesMap 146 | 12.IBPluginDependency 147 | 3.CustomClassName 148 | 3.IBPluginDependency 149 | 150 | 151 | YES 152 | UIApplication 153 | UIResponder 154 | AirSpeakerViewController 155 | {{234, 376}, {320, 480}} 156 | 157 | IBCocoaTouchFramework 158 | 159 | 160 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 161 | {{525, 346}, {320, 480}} 162 | 163 | IBCocoaTouchFramework 164 | 165 | 166 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 167 | AirSpeakerAppDelegate 168 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 169 | 170 | 171 | 172 | YES 173 | 174 | 175 | YES 176 | 177 | 178 | 179 | 180 | YES 181 | 182 | 183 | YES 184 | 185 | 186 | 187 | 15 188 | 189 | 190 | 191 | YES 192 | 193 | AirSpeakerAppDelegate 194 | NSObject 195 | 196 | YES 197 | 198 | YES 199 | viewController 200 | window 201 | 202 | 203 | YES 204 | AirSpeakerViewController 205 | UIWindow 206 | 207 | 208 | 209 | YES 210 | 211 | YES 212 | viewController 213 | window 214 | 215 | 216 | YES 217 | 218 | viewController 219 | AirSpeakerViewController 220 | 221 | 222 | window 223 | UIWindow 224 | 225 | 226 | 227 | 228 | IBProjectSource 229 | AirSpeakerAppDelegate.h 230 | 231 | 232 | 233 | AirSpeakerAppDelegate 234 | NSObject 235 | 236 | IBUserSource 237 | 238 | 239 | 240 | 241 | AirSpeakerViewController 242 | UIViewController 243 | 244 | IBProjectSource 245 | AirSpeakerViewController.h 246 | 247 | 248 | 249 | UIWindow 250 | UIView 251 | 252 | IBUserSource 253 | 254 | 255 | 256 | 257 | 258 | YES 259 | 260 | NSObject 261 | 262 | IBFrameworkSource 263 | Foundation.framework/Headers/NSError.h 264 | 265 | 266 | 267 | NSObject 268 | 269 | IBFrameworkSource 270 | Foundation.framework/Headers/NSFileManager.h 271 | 272 | 273 | 274 | NSObject 275 | 276 | IBFrameworkSource 277 | Foundation.framework/Headers/NSKeyValueCoding.h 278 | 279 | 280 | 281 | NSObject 282 | 283 | IBFrameworkSource 284 | Foundation.framework/Headers/NSKeyValueObserving.h 285 | 286 | 287 | 288 | NSObject 289 | 290 | IBFrameworkSource 291 | Foundation.framework/Headers/NSKeyedArchiver.h 292 | 293 | 294 | 295 | NSObject 296 | 297 | IBFrameworkSource 298 | Foundation.framework/Headers/NSObject.h 299 | 300 | 301 | 302 | NSObject 303 | 304 | IBFrameworkSource 305 | Foundation.framework/Headers/NSRunLoop.h 306 | 307 | 308 | 309 | NSObject 310 | 311 | IBFrameworkSource 312 | Foundation.framework/Headers/NSThread.h 313 | 314 | 315 | 316 | NSObject 317 | 318 | IBFrameworkSource 319 | Foundation.framework/Headers/NSURL.h 320 | 321 | 322 | 323 | NSObject 324 | 325 | IBFrameworkSource 326 | Foundation.framework/Headers/NSURLConnection.h 327 | 328 | 329 | 330 | NSObject 331 | 332 | IBFrameworkSource 333 | UIKit.framework/Headers/UIAccessibility.h 334 | 335 | 336 | 337 | NSObject 338 | 339 | IBFrameworkSource 340 | UIKit.framework/Headers/UINibLoading.h 341 | 342 | 343 | 344 | NSObject 345 | 346 | IBFrameworkSource 347 | UIKit.framework/Headers/UIResponder.h 348 | 349 | 350 | 351 | UIApplication 352 | UIResponder 353 | 354 | IBFrameworkSource 355 | UIKit.framework/Headers/UIApplication.h 356 | 357 | 358 | 359 | UIResponder 360 | NSObject 361 | 362 | 363 | 364 | UISearchBar 365 | UIView 366 | 367 | IBFrameworkSource 368 | UIKit.framework/Headers/UISearchBar.h 369 | 370 | 371 | 372 | UISearchDisplayController 373 | NSObject 374 | 375 | IBFrameworkSource 376 | UIKit.framework/Headers/UISearchDisplayController.h 377 | 378 | 379 | 380 | UIView 381 | 382 | IBFrameworkSource 383 | UIKit.framework/Headers/UITextField.h 384 | 385 | 386 | 387 | UIView 388 | UIResponder 389 | 390 | IBFrameworkSource 391 | UIKit.framework/Headers/UIView.h 392 | 393 | 394 | 395 | UIViewController 396 | 397 | IBFrameworkSource 398 | UIKit.framework/Headers/UINavigationController.h 399 | 400 | 401 | 402 | UIViewController 403 | 404 | IBFrameworkSource 405 | UIKit.framework/Headers/UIPopoverController.h 406 | 407 | 408 | 409 | UIViewController 410 | 411 | IBFrameworkSource 412 | UIKit.framework/Headers/UISplitViewController.h 413 | 414 | 415 | 416 | UIViewController 417 | 418 | IBFrameworkSource 419 | UIKit.framework/Headers/UITabBarController.h 420 | 421 | 422 | 423 | UIViewController 424 | UIResponder 425 | 426 | IBFrameworkSource 427 | UIKit.framework/Headers/UIViewController.h 428 | 429 | 430 | 431 | UIWindow 432 | UIView 433 | 434 | IBFrameworkSource 435 | UIKit.framework/Headers/UIWindow.h 436 | 437 | 438 | 439 | 440 | 0 441 | IBIPadFramework 442 | 443 | com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS 444 | 445 | 446 | 447 | com.apple.InterfaceBuilder.CocoaTouchPlugin.InterfaceBuilder3 448 | 449 | 450 | YES 451 | AirSpeaker.xcodeproj 452 | 3 453 | 141 454 | 455 | 456 | -------------------------------------------------------------------------------- /AirSpeaker/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // AirSpeaker 4 | // 5 | // Created by Clément Vasseur on 1/24/11. 6 | // Copyright 2011 Freebox S.A.S. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | int main(int argc, char *argv[]) { 12 | NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 13 | int retVal = UIApplicationMain(argc, argv, nil, nil); 14 | [pool release]; 15 | return retVal; 16 | } 17 | -------------------------------------------------------------------------------- /AirTunes/AirTunes.h: -------------------------------------------------------------------------------- 1 | // 2 | // AirTunes.h 3 | // AirSpeaker 4 | // 5 | // Created by Clément Vasseur on 2/10/11. 6 | // Copyright 2011 Clément Vasseur. All rights reserved. 7 | // 8 | 9 | #include 10 | 11 | #define AIRTUNES_PACKET 0x80 12 | #define AIRTUNES_FIRST_PACKET 0x90 13 | #define AIRTUNES_AUDIO_PACKET 0x60 14 | #define AIRTUNES_AUDIO_FIRST 0xe0 15 | #define AIRTUNES_TIMING_QUERY 0xd2 16 | #define AIRTUNES_TIMING_REPLY 0xd3 17 | #define AIRTUNES_CONTROL_SYNC 0xd4 18 | 19 | #define kAirTunesAudioSampleRate 44100 20 | #define kAirTunesAudioFramesPerPacket 352 21 | #define kAirTunesAudioChannelsPerFrame 2 22 | #define kAirTunesAudioBitsPerChannel 16 23 | 24 | struct airtunes_control_packet { 25 | uint8_t airtunes_packet; 26 | uint8_t airtunes_command; 27 | uint16_t fixed; 28 | uint32_t current_rtp_time; 29 | uint64_t current_ntp_timestamp; 30 | uint32_t next_rtp_time; 31 | } __attribute__((packed)); 32 | 33 | struct airtunes_timing_packet { 34 | uint8_t airtunes_packet; 35 | uint8_t airtunes_command; 36 | uint16_t fixed; 37 | uint32_t zero; 38 | uint64_t timestamp_1; 39 | uint64_t timestamp_2; 40 | uint64_t timestamp_3; 41 | } __attribute__((packed)); 42 | 43 | struct airtunes_audio_packet { 44 | uint8_t airtunes_packet; 45 | uint8_t airtunes_command; 46 | uint16_t rtp_sequence; 47 | uint32_t rtp_time; 48 | uint32_t session_id; 49 | uint8_t audio_data[]; 50 | } __attribute__((packed)); -------------------------------------------------------------------------------- /AirTunes/AirTunesController.h: -------------------------------------------------------------------------------- 1 | // 2 | // AirTunesController.h 3 | // AirSpeaker 4 | // 5 | // Created by Clément Vasseur on 1/24/11. 6 | // Copyright 2011 Clément Vasseur. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class GCDAsyncSocket; 12 | @class AsyncUdpSocket; 13 | @class TimeSync; 14 | @class AudioPlayer; 15 | @class CryptoController; 16 | 17 | @protocol AirTunesMetadataDelegate 18 | 19 | - (void)setMetadata:(NSDictionary *)metadata; 20 | 21 | @end 22 | 23 | @protocol AirTunesCoverDelegate 24 | 25 | - (void)setCoverData:(NSData *)cover; 26 | 27 | @end 28 | 29 | @interface AirTunesController : NSObject { 30 | NSNetService *netService; 31 | GCDAsyncSocket *asyncSocket; 32 | AudioPlayer *audioPlayer; 33 | CryptoController *cryptoController; 34 | 35 | NSString *method; 36 | NSString *location; 37 | NSMutableDictionary *headers; 38 | NSData *content; 39 | NSUInteger contentLength; 40 | 41 | UInt16 controlPort; 42 | UInt16 timingPort; 43 | int fmtp[12]; 44 | 45 | AsyncUdpSocket *serverSocket; 46 | AsyncUdpSocket *controlSocket; 47 | 48 | TimeSync *timeSync; 49 | 50 | id metadataDelegate; 51 | id coverDelegate; 52 | } 53 | 54 | @property (nonatomic, retain) id metadataDelegate; 55 | @property (nonatomic, retain) id coverDelegate; 56 | 57 | - (BOOL)start; 58 | 59 | @end 60 | -------------------------------------------------------------------------------- /AirTunes/AirTunesController.m: -------------------------------------------------------------------------------- 1 | // 2 | // AirTunesController.m 3 | // AirSpeaker 4 | // 5 | // Created by Clément Vasseur on 1/24/11. 6 | // Copyright 2011 Clément Vasseur. All rights reserved. 7 | // 8 | 9 | #import "AirTunesController.h" 10 | #import "AsyncUdpSocket.h" 11 | #import "GCDAsyncSocket.h" 12 | #import "DeviceInfo.h" 13 | #import "TimeSync.h" 14 | #import "AirTunes.h" 15 | #import "AudioPlayer.h" 16 | #import "Base64.h" 17 | #import "CryptoController.h" 18 | #import "DMAP.h" 19 | 20 | #include 21 | 22 | #define TIMEOUT_NONE -1 23 | 24 | enum tag_tcp { 25 | TAG_REQUEST, 26 | TAG_HEADER, 27 | TAG_CONTENT, 28 | TAG_REPLY, 29 | }; 30 | 31 | enum tag_udp { 32 | TAG_SERVER, 33 | TAG_CONTROL, 34 | }; 35 | 36 | @implementation AirTunesController 37 | 38 | @synthesize metadataDelegate; 39 | @synthesize coverDelegate; 40 | 41 | - (void)publishNetService 42 | { 43 | // create and publish the bonjour service 44 | 45 | UInt16 port = [asyncSocket localPort]; 46 | 47 | NSString *name = [[DeviceInfo deviceIdWithSep:@""] 48 | stringByAppendingFormat:@"@%@", 49 | [[UIDevice currentDevice] name]]; 50 | 51 | netService = [[NSNetService alloc] initWithDomain:@"local." 52 | type:@"_raop._tcp." 53 | name:name 54 | port:port]; 55 | [netService setDelegate:self]; 56 | [netService publish]; 57 | 58 | // add TXT record stuff 59 | 60 | NSDictionary *txtDict = [NSDictionary dictionaryWithObjectsAndKeys: 61 | 62 | // txt record version 63 | @"1", @"txtvers", 64 | 65 | // airtunes server version 66 | @"104.29", @"vs", 67 | 68 | // 2 channels, 44100 Hz, 16-bit audio 69 | @"2", @"ch", 70 | @"44100", @"sr", 71 | @"16", @"ss", 72 | 73 | // no password 74 | @"false", @"pw", 75 | 76 | // encryption types 77 | // 0: no encryption 78 | // 1: airport express (RSA+AES) 79 | // 3: apple tv (FairPlay+AES) 80 | @"0,1", @"et", 81 | @"1", @"ek", 82 | 83 | // transport protocols 84 | @"TCP,UDP", @"tp", 85 | 86 | @"0,1", @"cn", 87 | @"false", @"sv", 88 | @"true", @"da", 89 | @"65537", @"vn", 90 | @"0,1,2", @"md", 91 | @"0x4", @"sf", 92 | 93 | // [DeviceInfo platform], @"am", 94 | @"AppleTV2,1", @"am", 95 | nil]; 96 | 97 | NSData *txtData = [NSNetService dataFromTXTRecordDictionary:txtDict]; 98 | [netService setTXTRecordData:txtData]; 99 | } 100 | 101 | - (BOOL)start 102 | { 103 | // Stop the device from sleeping whilst we're playing our tunes 104 | [[UIApplication sharedApplication] setIdleTimerDisabled:YES]; 105 | 106 | // Create our socket. 107 | // We tell it to invoke our delegate methods on the main thread. 108 | 109 | asyncSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()]; 110 | 111 | // Now we tell the socket to accept incoming connections. 112 | // We don't care what port it listens on, so we pass zero for the port number. 113 | // This allows the operating system to automatically assign us an available port. 114 | 115 | NSError *err = nil; 116 | if (![asyncSocket acceptOnPort:0 error:&err]) 117 | { 118 | NSLog(@"Error in acceptOnPort:error: -> %@", err); 119 | return NO; 120 | } 121 | 122 | [self publishNetService]; 123 | 124 | cryptoController = [[CryptoController alloc] init]; 125 | 126 | return YES; 127 | } 128 | 129 | - (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket 130 | { 131 | NSLog(@"[Net] accept connection from %@:%hu", [newSocket connectedHost], [newSocket connectedPort]); 132 | 133 | [newSocket readDataToData:[GCDAsyncSocket CRLFData] withTimeout:TIMEOUT_NONE tag:TAG_REQUEST]; 134 | } 135 | 136 | - (void)replyOK:(GCDAsyncSocket *)sock 137 | withHeaders:(NSDictionary *)hDict 138 | withData:(NSData *)data 139 | { 140 | NSMutableData *rep = [[[NSMutableData alloc] init] autorelease]; 141 | NSMutableString *str = [NSMutableString stringWithString:@"RTSP/1.0 200 OK\r\n"]; 142 | 143 | if ([data length] > 0) { 144 | [str appendString:@"Content-Type: application/octet-stream\r\n"]; 145 | [str appendFormat:@"Content-Length: %u\r\n", [data length]]; 146 | } 147 | 148 | for (NSString *key in hDict) 149 | [str appendFormat:@"%@: %@\r\n", key, [hDict valueForKey:key]]; 150 | 151 | [str appendString:@"Server: AirTunes/104.29\r\n"]; 152 | [str appendFormat:@"CSeq: %@\r\n\r\n", [headers valueForKey:@"CSeq"]]; 153 | 154 | // NSLog(@"SEND %@", str); 155 | // NSLog(@"SEND RTSP/1.0 200 OK"); 156 | 157 | NSData *rep_data = [str dataUsingEncoding:NSASCIIStringEncoding]; 158 | 159 | [rep appendData:rep_data]; 160 | [rep appendData:data]; 161 | 162 | [sock writeData:rep withTimeout:TIMEOUT_NONE tag:TAG_REPLY]; 163 | } 164 | 165 | - (AsyncUdpSocket *)udpSocketWithTag:(long)tag 166 | { 167 | NSError *error; 168 | static int p = 4243; 169 | 170 | AsyncUdpSocket *socket = [[AsyncUdpSocket alloc] initWithDelegate:self]; 171 | 172 | if (![socket bindToPort:p++ error:&error]) { 173 | NSLog(@"Error: unable to bind UDP socket: %@", error); 174 | [socket release]; 175 | return nil; 176 | } 177 | 178 | [socket receiveWithTimeout:TIMEOUT_NONE tag:tag]; 179 | return socket; 180 | } 181 | 182 | - (void)setVolume:(float)volume 183 | { 184 | Float32 gain; 185 | 186 | // input : output 187 | // -144.0 : silence 188 | // -30.0 - 0.0 : 0.0 - 1.0 189 | 190 | if (volume == -144.0) 191 | gain = 0.0; 192 | else 193 | gain = 1.0 + volume / 30.0; 194 | 195 | [audioPlayer setGain:gain]; 196 | } 197 | 198 | - (NSData *)modifyAddress:(NSData *)address withPort:(UInt16)port 199 | { 200 | struct sockaddr_in addr4; 201 | struct sockaddr_in6 addr6; 202 | 203 | if ([address length] == sizeof(addr4)) { 204 | [address getBytes:&addr4 length:sizeof(addr4)]; 205 | addr4.sin_port = htons(port); 206 | return [NSData dataWithBytes:&addr4 length:sizeof(addr4)]; 207 | 208 | } else if ([address length] == sizeof(addr6)) { 209 | [address getBytes:&addr6 length:sizeof(addr6)]; 210 | addr6.sin6_port = htons(port); 211 | return [NSData dataWithBytes:&addr6 length:sizeof(addr6)]; 212 | } 213 | 214 | return nil; 215 | } 216 | 217 | static NSData *getLocalAddress(NSData *addr) 218 | { 219 | const struct sockaddr_in *addr_in; 220 | const struct sockaddr_in6 *addr_in6; 221 | 222 | addr_in = [addr bytes]; 223 | 224 | if (addr_in->sin_family == AF_INET6) { 225 | addr_in6 = [addr bytes]; 226 | return [NSData dataWithBytes:addr_in6->sin6_addr.__u6_addr.__u6_addr8 length:16]; 227 | } 228 | 229 | return [NSData dataWithBytes:&addr_in->sin_addr.s_addr length:4]; 230 | } 231 | 232 | - (void)socketReceivedRequest:(GCDAsyncSocket *)sock 233 | { 234 | NSLog(@"[RAOP] %@ %@", method, location); 235 | 236 | if ([method isEqualToString:@"POST"] && 237 | [location isEqualToString:@"/fp-setup"]) 238 | { 239 | // 2 1 1 -> 4 : 02 00 02 bb 240 | uint8_t fply_1[] __attribute__((unused)) = { 241 | 0x46, 0x50, 0x4c, 0x59, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x04, 0x02, 0x00, 0x02, 0xbb 242 | }; 243 | 244 | // 2 1 2 -> 130 : 02 02 xxx 245 | uint8_t fply_2[] = { 246 | 0x46, 0x50, 0x4c, 0x59, 0x02, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x82, 247 | 0x02, 0x02, 0x2f, 0x7b, 0x69, 0xe6, 0xb2, 0x7e, 0xbb, 0xf0, 0x68, 0x5f, 0x98, 0x54, 0x7f, 0x37, 248 | 0xce, 0xcf, 0x87, 0x06, 0x99, 0x6e, 0x7e, 0x6b, 0x0f, 0xb2, 0xfa, 0x71, 0x20, 0x53, 0xe3, 0x94, 249 | 0x83, 0xda, 0x22, 0xc7, 0x83, 0xa0, 0x72, 0x40, 0x4d, 0xdd, 0x41, 0xaa, 0x3d, 0x4c, 0x6e, 0x30, 250 | 0x22, 0x55, 0xaa, 0xa2, 0xda, 0x1e, 0xb4, 0x77, 0x83, 0x8c, 0x79, 0xd5, 0x65, 0x17, 0xc3, 0xfa, 251 | 0x01, 0x54, 0x33, 0x9e, 0xe3, 0x82, 0x9f, 0x30, 0xf0, 0xa4, 0x8f, 0x76, 0xdf, 0x77, 0x11, 0x7e, 252 | 0x56, 0x9e, 0xf3, 0x95, 0xe8, 0xe2, 0x13, 0xb3, 0x1e, 0xb6, 0x70, 0xec, 0x5a, 0x8a, 0xf2, 0x6a, 253 | 0xfc, 0xbc, 0x89, 0x31, 0xe6, 0x7e, 0xe8, 0xb9, 0xc5, 0xf2, 0xc7, 0x1d, 0x78, 0xf3, 0xef, 0x8d, 254 | 0x61, 0xf7, 0x3b, 0xcc, 0x17, 0xc3, 0x40, 0x23, 0x52, 0x4a, 0x8b, 0x9c, 0xb1, 0x75, 0x05, 0x66, 255 | 0xe6, 0xb3 256 | }; 257 | 258 | // 2 1 3 -> 152 259 | // 4 : 02 8f 1a 9c 260 | // 128 : xxx 261 | // 20 : 5b ed 04 ed c3 cd 5f e6 a8 28 90 3b 42 58 15 cb 74 7d ee 85 262 | 263 | uint8_t fply_3[] __attribute__((unused)) = { 264 | 0x46, 0x50, 0x4c, 0x59, 0x02, 0x01, 0x03, 0x00, 0x00, 0x00, 0x00, 0x98, 0x02, 0x8f, 265 | 0x1a, 0x9c, 0x6e, 0x73, 0xd2, 0xfa, 0x62, 0xb2, 0xb2, 0x07, 0x6f, 0x52, 0x5f, 0xe5, 0x72, 0xa5, 266 | 0xac, 0x4d, 0x19, 0xb4, 0x7c, 0xd8, 0x07, 0x1e, 0xdb, 0xbc, 0x98, 0xae, 0x7e, 0x4b, 0xb4, 0xb7, 267 | 0x2a, 0x7b, 0x5e, 0x2b, 0x8a, 0xde, 0x94, 0x4b, 0x1d, 0x59, 0xdf, 0x46, 0x45, 0xa3, 0xeb, 0xe2, 268 | 0x6d, 0xa2, 0x83, 0xf5, 0x06, 0x53, 0x8f, 0x76, 0xe7, 0xd3, 0x68, 0x3c, 0xeb, 0x1f, 0x80, 0x0e, 269 | 0x68, 0x9e, 0x27, 0xfc, 0x47, 0xbe, 0x3d, 0x8f, 0x73, 0xaf, 0xa1, 0x64, 0x39, 0xf7, 0xa8, 0xf7, 270 | 0xc2, 0xc8, 0xb0, 0x20, 0x0c, 0x85, 0xd6, 0xae, 0xb7, 0xb2, 0xd4, 0x25, 0x96, 0x77, 0x91, 0xf8, 271 | 0x83, 0x68, 0x10, 0xa1, 0xa9, 0x15, 0x4a, 0xa3, 0x37, 0x8c, 0xb7, 0xb9, 0x89, 0xbf, 0x86, 0x6e, 272 | 0xfb, 0x95, 0x41, 0xff, 0x03, 0x57, 0x61, 0x05, 0x00, 0x73, 0xcc, 0x06, 0x7e, 0x4f, 0xc7, 0x96, 273 | 0xae, 0xba, 0x5b, 0xed, 0x04, 0xed, 0xc3, 0xcd, 0x5f, 0xe6, 0xa8, 0x28, 0x90, 0x3b, 0x42, 0x58, 274 | 0x15, 0xcb, 0x74, 0x7d, 0xee, 0x85 275 | }; 276 | 277 | // 2 1 4 -> 20 : 5b ed 04 ed c3 cd 5f e6 a8 28 90 3b 42 58 15 cb 74 7d ee 85 278 | uint8_t fply_4[] = { 279 | 0x46, 0x50, 0x4c, 0x59, 0x02, 0x01, 0x04, 0x00, 0x00, 0x00, 0x00, 0x14, 0x5b, 280 | 0xed, 0x04, 0xed, 0xc3, 0xcd, 0x5f, 0xe6, 0xa8, 0x28, 0x90, 0x3b, 0x42, 0x58, 0x15, 0xcb, 0x74, 281 | 0x7d, 0xee, 0x85 282 | }; 283 | 284 | // NSLog(@" content:%@", content); 285 | 286 | uint8_t fply_header[12]; 287 | [content getBytes:fply_header length:sizeof(fply_header)]; 288 | NSRange payload_range = { 289 | .location = sizeof(fply_header), 290 | .length = [content length] - sizeof(fply_header), 291 | }; 292 | NSData *payload = [content subdataWithRange:payload_range]; 293 | // NSLog(@" fply seq:%u len:%u %@", fply_header[6], fply_header[11], payload); 294 | 295 | NSMutableData *data; 296 | if (fply_header[6] == 1) { 297 | NSRange fply_id_range = { 298 | .location = 12 + 2, 299 | .length = 1, 300 | }; 301 | [content getBytes:fply_2 + 13 range:fply_id_range]; 302 | data = [NSData dataWithBytesNoCopy:fply_2 length:sizeof(fply_2) freeWhenDone:NO]; 303 | [self replyOK:sock withHeaders:nil withData:data]; 304 | 305 | } else if (fply_header[6] == 3) { 306 | NSRange fply_4_range = { 307 | .location = [payload length] - 20, 308 | .length = 20, 309 | }; 310 | data = [NSMutableData dataWithBytes:fply_4 length:12]; 311 | [data appendData:[payload subdataWithRange:fply_4_range]]; 312 | [self replyOK:sock withHeaders:nil withData:data]; 313 | } 314 | 315 | return; 316 | 317 | } else if ([method isEqualToString:@"POST"] && 318 | [location isEqualToString:@"/auth-setup"]) { 319 | 320 | [self replyOK:sock withHeaders:nil withData:content]; 321 | 322 | } else if ([method isEqualToString:@"OPTIONS"]) { 323 | NSData *addr = getLocalAddress([sock localAddress]); 324 | 325 | NSMutableDictionary *hDict = [[NSMutableDictionary alloc] init]; 326 | 327 | NSString *apple_challenge = [headers valueForKey:@"Apple-Challenge"]; 328 | if (apple_challenge != nil) { 329 | NSData *challenge = base64_decode(apple_challenge); 330 | NSData *response = [cryptoController challengeResponse:challenge withAddr:addr]; 331 | // NSLog(@"Apple-Challenge: %@", challenge); 332 | // NSLog(@"Apple-Response: %@", response); 333 | [hDict setObject:base64_encode(response) forKey:@"Apple-Response"]; 334 | } 335 | 336 | [hDict setObject:@ 337 | "ANNOUNCE, " 338 | "SETUP, " 339 | "RECORD, " 340 | "PAUSE, " 341 | "FLUSH, " 342 | "TEARDOWN, " 343 | "OPTIONS, " 344 | "GET_PARAMETER, " 345 | "SET_PARAMETER, " 346 | "POST, " 347 | "GET" forKey:@"Public"]; 348 | 349 | [self replyOK:sock withHeaders:hDict withData:nil]; 350 | [hDict release]; 351 | return; 352 | 353 | } else if ([method isEqualToString:@"ANNOUNCE"]) { 354 | NSData *rsa_aes_key = nil; 355 | NSData *aes_iv = nil; 356 | 357 | NSString *sdp = [[NSString alloc] initWithData:content encoding:NSASCIIStringEncoding]; 358 | NSArray *a = [sdp componentsSeparatedByString:@"\r\n"]; 359 | for (NSString *str in a) { 360 | // NSLog(@" %@", str); 361 | 362 | if ([str hasPrefix:@"a=fmtp:"]) { 363 | sscanf([[str substringFromIndex:7] UTF8String], 364 | "%u %u %u %u %u %u %u %u %u %u %u %u", 365 | &fmtp[0], &fmtp[1], &fmtp[2], &fmtp[3], &fmtp[4], 366 | &fmtp[5], &fmtp[6], &fmtp[7], &fmtp[8], &fmtp[9], 367 | &fmtp[10], &fmtp[11]); 368 | 369 | } else if ([str hasPrefix:@"a=rsaaeskey:"]) { 370 | rsa_aes_key = base64_decode([str substringFromIndex:12]); 371 | [cryptoController setRsaAesKey:rsa_aes_key]; 372 | 373 | } else if ([str hasPrefix:@"a=aesiv:"]) { 374 | aes_iv = base64_decode([str substringFromIndex:8]); 375 | [cryptoController setAesIv:aes_iv]; 376 | } 377 | } 378 | 379 | [sdp release]; 380 | 381 | [self replyOK:sock withHeaders:nil withData:nil]; 382 | return; 383 | 384 | } else if ([method isEqualToString:@"SETUP"]) { 385 | 386 | // retrieve control_port and timing_port from the Transport header 387 | controlPort = 0; 388 | timingPort = 0; 389 | 390 | NSString *transport = [headers valueForKey:@"Transport"]; 391 | if (transport == nil) { 392 | // TODO: reply error 393 | return; 394 | } 395 | 396 | NSArray *settings = [transport componentsSeparatedByString:@";"]; 397 | for (NSString *setting in settings) { 398 | NSArray *a = [setting componentsSeparatedByString:@"="]; 399 | NSString *k = [a objectAtIndex:0]; 400 | if ([k isEqualToString:@"control_port"]) 401 | controlPort = [[a objectAtIndex:1] integerValue]; 402 | else if ([k isEqualToString:@"timing_port"]) 403 | timingPort = [[a objectAtIndex:1] integerValue]; 404 | } 405 | 406 | if (controlPort == 0 || timingPort == 0) { 407 | // TODO: reply error 408 | return; 409 | } 410 | 411 | NSLog(@" control_port: %u", controlPort); 412 | NSLog(@" timing_port: %u", timingPort); 413 | 414 | // create server, control and timing udp sockets 415 | 416 | if ((serverSocket = [self udpSocketWithTag:TAG_SERVER]) == nil || 417 | (controlSocket = [self udpSocketWithTag:TAG_CONTROL]) == nil) 418 | return; 419 | 420 | NSData *address = [self modifyAddress:[sock connectedAddress] withPort:timingPort]; 421 | timeSync = [[TimeSync alloc] initWithServer:address]; 422 | if (timeSync == nil) { 423 | // TODO: reply error 424 | return; 425 | } 426 | 427 | // reply with port information 428 | 429 | NSString *transport_reply = [NSString stringWithFormat:@"RTP/AVP/UDP;" 430 | "unicast;" 431 | "mode=record;" 432 | "server_port=%u;" 433 | "control_port=%u;" 434 | "timing_port=%u", 435 | [serverSocket localPort], 436 | [controlSocket localPort], 437 | [timeSync localPort]]; 438 | 439 | NSDictionary *hDict = [NSDictionary dictionaryWithObjectsAndKeys: 440 | transport_reply, @"Transport", 441 | @"1", @"Session", 442 | @"connected", @"Audio-Jack-Status", 443 | nil]; 444 | 445 | [self replyOK:sock withHeaders:hDict withData:nil]; 446 | return; 447 | 448 | } else if ([method isEqualToString:@"RECORD"]) { 449 | [timeSync startWithDelegate:self userData:sock]; 450 | return; 451 | 452 | } else if ([method isEqualToString:@"SET_PARAMETER"]) { 453 | NSString *content_type = [headers valueForKey:@"Content-Type"]; 454 | NSLog(@" %@", content_type); 455 | 456 | if ([content_type isEqualToString:@"text/parameters"]) { 457 | NSString *parameters = [[[NSString alloc] initWithData:content 458 | encoding:NSASCIIStringEncoding] autorelease]; 459 | for (NSString *param in [parameters componentsSeparatedByString:@"\r\n"]) { 460 | if ([param length] == 0) 461 | continue; 462 | NSLog(@" %@", param); 463 | NSArray *a = [param componentsSeparatedByString:@":"]; 464 | NSString *key = [a objectAtIndex:0]; 465 | if ([key isEqualToString:@"volume"] && [a count] == 2) 466 | [self setVolume:[[a objectAtIndex:1] floatValue]]; 467 | } 468 | 469 | } else if ([content_type hasPrefix:@"application/x-dmap-tagged"]) { 470 | DMAP *dmap = [[DMAP alloc] initWithData:content]; 471 | NSDictionary *metadata = [NSDictionary dictionaryWithObjectsAndKeys: 472 | [dmap get:@"dmap.listingitem/dmap.itemname"], @"Song", 473 | [dmap get:@"dmap.listingitem/daap.songartist"], @"Artist", 474 | [dmap get:@"dmap.listingitem/daap.songalbum"], @"Album", 475 | nil]; 476 | [metadataDelegate setMetadata:metadata]; 477 | [dmap release]; 478 | 479 | } else if ([content_type hasPrefix:@"image/"]) { 480 | [coverDelegate setCoverData:content]; 481 | } 482 | 483 | [self replyOK:sock withHeaders:nil withData:nil]; 484 | return; 485 | 486 | } else if ([method isEqualToString:@"FLUSH"]) { 487 | [audioPlayer stop]; 488 | [self replyOK:sock withHeaders:nil withData:nil]; 489 | 490 | } else if ([method isEqualToString:@"TEARDOWN"]) { 491 | [timeSync release]; 492 | timeSync = nil; 493 | 494 | [serverSocket release]; 495 | serverSocket = nil; 496 | [controlSocket release]; 497 | controlSocket = nil; 498 | 499 | [cryptoController reset]; 500 | 501 | [self replyOK:sock withHeaders:nil withData:nil]; 502 | } 503 | } 504 | 505 | - (void)timeSyncWithLatency:(uint32_t)latency userData:(id)data 506 | { 507 | GCDAsyncSocket *sock = data; 508 | 509 | audioPlayer = [[AudioPlayer alloc] initWithFmt:fmtp]; 510 | if (audioPlayer == nil) { 511 | NSLog(@"Error: unable to init audio player"); 512 | // TODO: reply error 513 | return; 514 | } 515 | 516 | [audioPlayer start]; 517 | 518 | NSDictionary *hDict = [NSDictionary dictionaryWithObjectsAndKeys: 519 | [NSString stringWithFormat:@"%u", latency + 2000], @"Audio-Latency", 520 | nil]; 521 | 522 | [self replyOK:sock withHeaders:hDict withData:nil]; 523 | } 524 | 525 | - (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag 526 | { 527 | switch (tag) 528 | { 529 | case TAG_REQUEST: 530 | { 531 | NSString *request = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]; 532 | NSArray *a = [request componentsSeparatedByString:@" "]; 533 | method = [[a objectAtIndex:0] retain]; 534 | location = [[a objectAtIndex:1] retain]; 535 | [request release]; 536 | headers = [[NSMutableDictionary alloc] init]; 537 | [sock readDataToData:[GCDAsyncSocket CRLFData] withTimeout:TIMEOUT_NONE tag:TAG_HEADER]; 538 | return; 539 | } 540 | 541 | case TAG_HEADER: 542 | { 543 | NSString *header = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]; 544 | if ([header isEqualToString:@"\r\n"]) { 545 | [header release]; 546 | if (contentLength > 0) 547 | [sock readDataToLength:contentLength withTimeout:TIMEOUT_NONE tag:TAG_CONTENT]; 548 | else 549 | [self socketReceivedRequest:sock]; 550 | return; 551 | } 552 | 553 | NSRange range = [header rangeOfString:@":"]; 554 | NSString *key = [header substringToIndex:range.location]; 555 | range.location += 1; 556 | range.length = [header length] - range.location - 2; 557 | NSString *value = [[header substringWithRange:range] 558 | stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@" "]]; 559 | 560 | [headers setObject:value forKey:key]; 561 | if ([key compare:@"Content-Length" options:NSCaseInsensitiveSearch] == NSOrderedSame) 562 | contentLength = [value integerValue]; 563 | [header release]; 564 | 565 | // NSLog(@" header %@ %@", key, value); 566 | [sock readDataToData:[GCDAsyncSocket CRLFData] withTimeout:TIMEOUT_NONE tag:TAG_HEADER]; 567 | return; 568 | } 569 | 570 | case TAG_CONTENT: 571 | { 572 | content = [data retain]; 573 | // NSLog(@" content: %@", content); 574 | [self socketReceivedRequest:sock]; 575 | return; 576 | } 577 | } 578 | 579 | NSLog(@"Error: invalid read tag %lu", tag); 580 | } 581 | 582 | - (void)handleControlPacket:(NSData *)data 583 | { 584 | struct airtunes_control_packet pkt; 585 | 586 | if ([data length] != sizeof(pkt)) 587 | return; 588 | 589 | [data getBytes:&pkt length:sizeof(pkt)]; 590 | 591 | if (pkt.airtunes_packet != AIRTUNES_PACKET && 592 | pkt.airtunes_packet != AIRTUNES_FIRST_PACKET) 593 | return; 594 | 595 | pkt.current_rtp_time = OSSwapBigToHostInt32(pkt.current_rtp_time); 596 | pkt.current_ntp_timestamp = OSSwapBigToHostInt64(pkt.current_ntp_timestamp); 597 | pkt.next_rtp_time = OSSwapBigToHostInt32(pkt.next_rtp_time); 598 | 599 | // NSLog(@"Control: rtp=0x%x ntp=0x%llx next_rtp=0x%x", 600 | // pkt.current_rtp_time, pkt.current_ntp_timestamp, pkt.next_rtp_time); 601 | } 602 | 603 | - (void)handleAudioPacket:(NSData *)data 604 | { 605 | struct airtunes_audio_packet pkt; 606 | 607 | // NSLog(@"Audio pkt: %u", [data length]); 608 | // NSLog(@"Audio pkt: %u %@", [data length], data); 609 | 610 | if ([data length] < sizeof(pkt)) 611 | return; 612 | 613 | [data getBytes:&pkt length:sizeof(pkt)]; 614 | 615 | NSRange range = { 616 | .location = 12, 617 | .length = [data length] - 12, 618 | }; 619 | 620 | [audioPlayer enqueuePacket:[cryptoController decryptData:[data subdataWithRange:range]]]; 621 | } 622 | 623 | - (BOOL)onUdpSocket:(AsyncUdpSocket *)sock 624 | didReceiveData:(NSData *)data 625 | withTag:(long)tag 626 | fromHost:(NSString *)host 627 | port:(UInt16)port 628 | { 629 | switch (tag) 630 | { 631 | case TAG_SERVER: 632 | [self handleAudioPacket:data]; 633 | [sock receiveWithTimeout:TIMEOUT_NONE tag:tag]; 634 | return YES; 635 | 636 | case TAG_CONTROL: 637 | [self handleControlPacket:data]; 638 | [sock receiveWithTimeout:TIMEOUT_NONE tag:tag]; 639 | return YES; 640 | } 641 | 642 | NSLog(@"Error: invalid udp read tag %lu", tag); 643 | return NO; 644 | } 645 | 646 | - (void)cleanup 647 | { 648 | [method release]; 649 | method = nil; 650 | [location release]; 651 | location = nil; 652 | [headers release]; 653 | headers = nil; 654 | contentLength = 0; 655 | [content release]; 656 | content = nil; 657 | } 658 | 659 | - (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag 660 | { 661 | switch (tag) 662 | { 663 | case TAG_REPLY: 664 | // cleanup the current request 665 | [self cleanup]; 666 | 667 | // read the next request 668 | [sock readDataToData:[GCDAsyncSocket CRLFData] withTimeout:TIMEOUT_NONE tag:TAG_REQUEST]; 669 | return; 670 | } 671 | 672 | NSLog(@"Error: invalid write tag %lu", tag); 673 | } 674 | 675 | - (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err 676 | { 677 | NSLog(@"[Net] disconnected"); 678 | [self cleanup]; 679 | } 680 | 681 | 682 | #pragma mark NSNetServiceDelegate 683 | 684 | 685 | - (void)netServiceDidPublish:(NSNetService *)ns 686 | { 687 | NSLog(@"[Bonjour] service published: domain(%@) type(%@) name(%@) port(%i)", 688 | [ns domain], [ns type], [ns name], [ns port]); 689 | } 690 | 691 | - (void)netService:(NSNetService *)ns didNotPublish:(NSDictionary *)errorDict 692 | { 693 | NSLog(@"[Bonjour] failed to publish service: domain(%@) type(%@) name(%@) - %@", 694 | [ns domain], [ns type], [ns name], errorDict); 695 | } 696 | 697 | @end 698 | -------------------------------------------------------------------------------- /AirTunes/AudioPlayer.h: -------------------------------------------------------------------------------- 1 | // 2 | // AudioPlayer.h 3 | // AirSpeaker 4 | // 5 | // Created by Clément Vasseur on 2/10/11. 6 | // Copyright 2011 Clément Vasseur. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import 12 | 13 | // I could not get the native AppleLossless codec to work on 14 | // the iPhone, even though it works fine on the simulator, 15 | // so we will use a software decoder instead. 16 | 17 | #define USE_ALAC_DECODER 18 | 19 | #ifdef USE_ALAC_DECODER 20 | # import "alac.h" 21 | #endif 22 | 23 | #define kNumberBuffers 8 24 | 25 | @interface AudioPlayer : NSObject { 26 | AudioQueueRef audioQueue; 27 | NSMutableArray *audioPackets; 28 | AudioQueueBufferRef buffers[kNumberBuffers]; 29 | BOOL playing; 30 | BOOL interruptedWhilePlaying; 31 | BOOL first_pkt; 32 | 33 | #ifdef USE_ALAC_DECODER 34 | alac_file *alac; 35 | #endif 36 | } 37 | 38 | - (id)initWithFmt:(int[12])fmtp; 39 | - (void)setGain:(Float32)gain; 40 | - (void)start; 41 | - (void)enqueuePacket:(NSData *)audioPacket; 42 | - (NSData *)dequeuePacket; 43 | - (void)pause; 44 | - (void)stop; 45 | - (void)dealloc; 46 | 47 | @end 48 | -------------------------------------------------------------------------------- /AirTunes/AudioPlayer.m: -------------------------------------------------------------------------------- 1 | // 2 | // AudioPlayer.m 3 | // AirSpeaker 4 | // 5 | // Created by Clément Vasseur on 2/10/11. 6 | // Copyright 2011 Clément Vasseur. All rights reserved. 7 | // 8 | 9 | #import "AudioPlayer.h" 10 | #import "AirTunes.h" 11 | 12 | #ifdef USE_ALAC_DECODER 13 | # define kBufferByteSize 1408 14 | #else 15 | # define kBufferByteSize 2048 16 | #endif 17 | 18 | struct alac_cookie { 19 | uint32_t size_1; 20 | char frma[4]; 21 | char alac_1[4]; 22 | 23 | uint32_t size_2; 24 | char alac_2[4]; 25 | uint32_t zero_1; 26 | uint32_t samples_per_frame; 27 | uint8_t fmtp_2; 28 | uint8_t sample_size; 29 | uint8_t rice_historymult; 30 | uint8_t rice_initialhistory; 31 | uint8_t rice_kmodifier; 32 | uint8_t channels; 33 | uint16_t fmtp_8; 34 | uint32_t fmtp_9; 35 | uint32_t fmtp_10; 36 | uint32_t sample_rate; 37 | } __attribute__((packed)); 38 | 39 | @implementation AudioPlayer 40 | 41 | static void logOSStatus(const char *func, OSStatus status) 42 | { 43 | if (status != noErr) 44 | NSLog(@"Audio Queue Services: %s error %ld (0x%lx)", func, status, status); 45 | } 46 | 47 | static void audio_cb(void *userData, 48 | AudioQueueRef audioQueue, 49 | AudioQueueBufferRef audioBuffer) 50 | { 51 | AudioPlayer *self = userData; 52 | NSData *audioPacket = [self dequeuePacket]; 53 | AudioQueueBufferRef *bptr = audioBuffer->mUserData; 54 | 55 | *bptr = audioBuffer; 56 | 57 | if (audioPacket != nil) 58 | [self enqueuePacket:audioPacket]; 59 | } 60 | 61 | - (void)beginInterruption 62 | { 63 | NSLog(@"[AVAudioSession] audio interruption begin"); 64 | 65 | if (playing) { 66 | playing = NO; 67 | interruptedWhilePlaying = YES; 68 | } 69 | } 70 | 71 | - (void)endInterruption 72 | { 73 | NSError *error = nil; 74 | 75 | if (interruptedWhilePlaying) { 76 | if (![[AVAudioSession sharedInstance] setActive:YES error:&error]) { 77 | NSLog(@"[AVAudioSession] setActive error %@", error); 78 | return; 79 | } 80 | // [player play]; 81 | playing = YES; 82 | interruptedWhilePlaying = NO; 83 | } 84 | } 85 | 86 | - (void)setupAudioSession 87 | { 88 | NSError *error = nil; 89 | 90 | // initialize audio session 91 | AVAudioSession *session = [AVAudioSession sharedInstance]; 92 | 93 | // set playback category 94 | if (![session setCategory:AVAudioSessionCategoryPlayback error:&error]) { 95 | NSLog(@"[AVAudioSession] setCategory error: %@", error); 96 | return; 97 | } 98 | 99 | // set interruption delegate 100 | session.delegate = self; 101 | 102 | // activate audio session 103 | if (![session setActive:YES error:&error]) 104 | NSLog(@"[AVAudioSession] setActive error: %@", error); 105 | } 106 | 107 | - (void)setFmt:(int[12])fmtp 108 | { 109 | #ifdef USE_ALAC_DECODER 110 | 111 | int sample_size = fmtp[3]; 112 | 113 | alac = create_alac(sample_size, kAirTunesAudioChannelsPerFrame); 114 | if (alac == NULL) 115 | return; 116 | 117 | alac->setinfo_max_samples_per_frame = fmtp[1]; 118 | alac->setinfo_7a = fmtp[2]; 119 | alac->setinfo_sample_size = sample_size; 120 | alac->setinfo_rice_historymult = fmtp[4]; 121 | alac->setinfo_rice_initialhistory = fmtp[5]; 122 | alac->setinfo_rice_kmodifier = fmtp[6]; 123 | alac->setinfo_7f = fmtp[7]; 124 | alac->setinfo_80 = fmtp[8]; 125 | alac->setinfo_82 = fmtp[9]; 126 | alac->setinfo_86 = fmtp[10]; 127 | alac->setinfo_8a_rate = fmtp[11]; 128 | 129 | allocate_buffers(alac); 130 | 131 | #else 132 | 133 | OSStatus status; 134 | 135 | struct alac_cookie cookie = { 136 | .size_1 = OSSwapHostToBigInt32(12), 137 | .frma = "frma", 138 | .alac_1 = "alac", 139 | 140 | .size_2 = OSSwapHostToBigInt32(36), 141 | .alac_2 = "alac", 142 | .zero_1 = 0, 143 | .samples_per_frame = OSSwapHostToBigInt32(fmtp[1]), 144 | .fmtp_2 = fmtp[2], 145 | .sample_size = fmtp[3], 146 | .rice_historymult = fmtp[4], 147 | .rice_initialhistory = fmtp[5], 148 | .rice_kmodifier = fmtp[6], 149 | .channels = fmtp[7], 150 | .fmtp_8 = OSSwapHostToBigInt16(fmtp[8]), 151 | .fmtp_9 = OSSwapHostToBigInt32(fmtp[9]), 152 | .fmtp_10 = OSSwapHostToBigInt32(fmtp[10]), 153 | .sample_rate = OSSwapHostToBigInt32(fmtp[11]), 154 | }; 155 | 156 | NSData *data = [NSData dataWithBytesNoCopy:&cookie length:sizeof(cookie) freeWhenDone:NO]; 157 | NSLog(@"[AudioPlayer] cookie: %@", data); 158 | 159 | status = AudioQueueSetProperty(audioQueue, kAudioQueueProperty_MagicCookie, &cookie, sizeof(cookie)); 160 | logOSStatus("AudioQueueSetProperty", status); 161 | #endif 162 | } 163 | 164 | - (id)initWithFmt:(int [12])fmtp 165 | { 166 | if ((self = [super init])) { 167 | OSStatus status; 168 | 169 | const AudioStreamBasicDescription format = { 170 | #ifdef USE_ALAC_DECODER 171 | .mSampleRate = kAirTunesAudioSampleRate, 172 | .mFormatID = kAudioFormatLinearPCM, 173 | .mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked, 174 | .mBytesPerPacket = (kAirTunesAudioBitsPerChannel / 8) * kAirTunesAudioChannelsPerFrame, 175 | .mFramesPerPacket = 1, 176 | .mBytesPerFrame = (kAirTunesAudioBitsPerChannel / 8) * kAirTunesAudioChannelsPerFrame, 177 | .mChannelsPerFrame = kAirTunesAudioChannelsPerFrame, 178 | .mBitsPerChannel = kAirTunesAudioBitsPerChannel, 179 | #else 180 | .mSampleRate = kAirTunesAudioSampleRate, 181 | .mFormatID = kAudioFormatAppleLossless, 182 | .mFormatFlags = kAppleLosslessFormatFlag_16BitSourceData, 183 | .mBytesPerPacket = 0, 184 | .mFramesPerPacket = kAirTunesAudioFramesPerPacket, 185 | .mBytesPerFrame = 0, 186 | .mChannelsPerFrame = kAirTunesAudioChannelsPerFrame, 187 | .mBitsPerChannel = 0, 188 | #endif 189 | .mReserved = 0, 190 | }; 191 | 192 | [self setupAudioSession]; 193 | 194 | status = AudioQueueNewOutput(&format, 195 | audio_cb, 196 | self, 197 | CFRunLoopGetCurrent(), 198 | kCFRunLoopCommonModes, 199 | 0, 200 | &audioQueue); 201 | 202 | logOSStatus("AudioQueueNewOutput", status); 203 | if (status != 0) { 204 | [self release]; 205 | return nil; 206 | } 207 | 208 | // allocate audio buffers 209 | for (int i = 0; i < kNumberBuffers; i++) { 210 | status = AudioQueueAllocateBufferWithPacketDescriptions(audioQueue, kBufferByteSize, 1, &buffers[i]); 211 | logOSStatus("AudioQueueAllocateBuffer", status); 212 | buffers[i]->mUserData = &buffers[i]; 213 | } 214 | 215 | [self setGain:1.0]; 216 | [self setFmt:fmtp]; 217 | 218 | first_pkt = YES; 219 | 220 | audioPackets = [[NSMutableArray alloc] init]; 221 | } 222 | 223 | return self; 224 | } 225 | 226 | - (void)setGain:(Float32)gain 227 | { 228 | OSStatus status = AudioQueueSetParameter(audioQueue, kAudioQueueParam_Volume, gain); 229 | logOSStatus("AudioQueueSetParameter", status); 230 | } 231 | 232 | - (void)start 233 | { 234 | // NSLog(@"[AudioPlayer] start"); 235 | // OSStatus status = AudioQueueStart(audioQueue, NULL); 236 | // logOSStatus("AudioQueueStart", status); 237 | } 238 | 239 | - (void)stop 240 | { 241 | NSLog(@"[AudioPlayer] stop"); 242 | 243 | [audioPackets removeAllObjects]; 244 | 245 | OSStatus status = AudioQueueStop(audioQueue, true); 246 | logOSStatus("AudioQueueStop", status); 247 | 248 | first_pkt = YES; 249 | } 250 | 251 | - (void)pause 252 | { 253 | OSStatus status = AudioQueuePause(audioQueue); 254 | logOSStatus("AudioQueuePause", status); 255 | } 256 | 257 | - (void)dealloc 258 | { 259 | #ifdef USE_ALAC_DECODER 260 | free(alac->predicterror_buffer_a); 261 | free(alac->predicterror_buffer_b); 262 | free(alac->outputsamples_buffer_a); 263 | free(alac->outputsamples_buffer_b); 264 | free(alac->uncompressed_bytes_buffer_a); 265 | free(alac->uncompressed_bytes_buffer_b); 266 | free(alac); 267 | #endif 268 | 269 | OSStatus status = AudioQueueDispose(audioQueue, true); 270 | logOSStatus("AudioQueueDispose", status); 271 | [audioPackets release]; 272 | 273 | [super dealloc]; 274 | } 275 | 276 | static void enqueueBufferWithPacket(AudioQueueRef audioQueue, 277 | AudioQueueBufferRef audioBuffer, 278 | NSData *audioPacket, 279 | AudioPlayer *this) 280 | { 281 | #ifdef USE_ALAC_DECODER 282 | int outsize; 283 | 284 | decode_frame(this->alac, [audioPacket bytes], audioBuffer->mAudioData, &outsize); 285 | audioBuffer->mAudioDataByteSize = outsize; 286 | 287 | #else 288 | if ([audioPacket length] > audioBuffer->mAudioDataBytesCapacity) { 289 | NSLog(@"Error: audio packet too big"); 290 | return; 291 | } 292 | 293 | audioBuffer->mAudioDataByteSize = [audioPacket length]; 294 | [audioPacket getBytes:audioBuffer->mAudioData length:audioBuffer->mAudioDataByteSize]; 295 | #endif 296 | 297 | audioBuffer->mPacketDescriptions[0].mStartOffset = 0; 298 | audioBuffer->mPacketDescriptions[0].mVariableFramesInPacket = kAirTunesAudioFramesPerPacket; 299 | audioBuffer->mPacketDescriptions[0].mDataByteSize = audioBuffer->mAudioDataByteSize; 300 | audioBuffer->mPacketDescriptionCount = 1; 301 | 302 | // NSLog(@"AudioPlayer: enqueue %lu bytes (%02x %02x %02x %02x)", audioBuffer->mAudioDataByteSize, 303 | // ((uint8_t *) audioBuffer->mAudioData)[0], 304 | // ((uint8_t *) audioBuffer->mAudioData)[1], 305 | // ((uint8_t *) audioBuffer->mAudioData)[2], 306 | // ((uint8_t *) audioBuffer->mAudioData)[3]); 307 | 308 | #if 0 309 | if (this->first_pkt) { 310 | AudioTimeStamp audioTimeStamp = { 311 | .mSampleTime = kAirTunesAudioSampleRate * 2, // 2 second buffer 312 | .mFlags = kAudioTimeStampSampleTimeValid, 313 | }; 314 | 315 | NSLog(@"SET AUDIO TIME STAMP"); 316 | OSStatus status = AudioQueueEnqueueBufferWithParameters(audioQueue, 317 | audioBuffer, 318 | 0, NULL, 319 | 0, 0, 320 | 0, NULL, 321 | &audioTimeStamp, NULL); 322 | 323 | logOSStatus("AudioQueueEnqueueBufferWithParameters", status); 324 | this->first_pkt = NO; 325 | 326 | NSLog(@"[AudioPlayer] start"); 327 | status = AudioQueueStart(audioQueue, NULL); 328 | logOSStatus("AudioQueueStart", status); 329 | 330 | } else { 331 | #endif 332 | OSStatus status = AudioQueueEnqueueBuffer(audioQueue, audioBuffer, 0, NULL); 333 | logOSStatus("AudioQueueEnqueueBuffer", status); 334 | // } 335 | } 336 | 337 | - (void)enqueuePacket:(NSData *)audioPacket 338 | { 339 | for (int i = 0; i < kNumberBuffers; i++) 340 | if (buffers[i] != NULL) { 341 | enqueueBufferWithPacket(audioQueue, buffers[i], audioPacket, self); 342 | buffers[i] = NULL; 343 | return; 344 | } 345 | 346 | [audioPackets addObject:audioPacket]; 347 | 348 | if (first_pkt) { 349 | if ([audioPackets count] == 200) { 350 | NSLog(@"[AudioPlayer] start"); 351 | OSStatus status = AudioQueueStart(audioQueue, NULL); 352 | logOSStatus("AudioQueueStart", status); 353 | first_pkt = NO; 354 | } 355 | } 356 | } 357 | 358 | - (NSData *)dequeuePacket 359 | { 360 | if ([audioPackets count] == 0) 361 | return nil; 362 | 363 | NSData *audioPacket = [audioPackets objectAtIndex:0]; 364 | [[audioPacket retain] autorelease]; // so it isn't dealloc'ed on remove 365 | [audioPackets removeObjectAtIndex:0]; 366 | return audioPacket; 367 | } 368 | 369 | @end 370 | -------------------------------------------------------------------------------- /AirTunes/Base64.h: -------------------------------------------------------------------------------- 1 | // 2 | // Base64.h 3 | // AirSpeaker 4 | // 5 | // Created by Clément Vasseur on 2/20/11. 6 | // Copyright 2011 Clément Vasseur. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NSString *base64_encode(NSData *data); 12 | NSData *base64_decode(NSString *string); 13 | -------------------------------------------------------------------------------- /AirTunes/Base64.m: -------------------------------------------------------------------------------- 1 | // 2 | // Base64.m 3 | // AirSpeaker 4 | // 5 | // Created by Clément Vasseur on 2/20/11. 6 | // Copyright 2011 Clément Vasseur. All rights reserved. 7 | // 8 | 9 | #import "Base64.h" 10 | 11 | static const char encodingTable[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 12 | 13 | NSString *base64_encode(NSData *data) 14 | { 15 | size_t data_length = [data length]; 16 | const uint8_t *data_bytes = [data bytes]; 17 | 18 | if (data_length == 0) 19 | return @""; 20 | 21 | char *characters = malloc(((data_length + 2) / 3) * 4); 22 | if (characters == NULL) 23 | return nil; 24 | 25 | NSUInteger length = 0; 26 | NSUInteger i = 0; 27 | 28 | while (i < data_length) { 29 | char buffer[3] = {0, 0, 0}; 30 | short bufferLength = 0; 31 | while (bufferLength < 3 && i < data_length) 32 | buffer[bufferLength++] = data_bytes[i++]; 33 | 34 | // Encode the bytes in the buffer to four characters, including padding "=" characters if necessary. 35 | characters[length++] = encodingTable[(buffer[0] & 0xFC) >> 2]; 36 | characters[length++] = encodingTable[((buffer[0] & 0x03) << 4) | ((buffer[1] & 0xF0) >> 4)]; 37 | 38 | if (bufferLength > 1) 39 | characters[length++] = encodingTable[((buffer[1] & 0x0F) << 2) | ((buffer[2] & 0xC0) >> 6)]; 40 | // else 41 | // characters[length++] = '='; 42 | 43 | if (bufferLength > 2) 44 | characters[length++] = encodingTable[buffer[2] & 0x3F]; 45 | // else 46 | // characters[length++] = '='; 47 | } 48 | 49 | return [[[NSString alloc] initWithBytesNoCopy:characters 50 | length:length 51 | encoding:NSASCIIStringEncoding 52 | freeWhenDone:YES] autorelease]; 53 | } 54 | 55 | NSData *base64_decode(NSString *string) 56 | { 57 | if ([string length] == 0) 58 | return [NSData data]; 59 | 60 | char decodingTable[256]; 61 | memset(decodingTable, CHAR_MAX, 256); 62 | 63 | for (int i = 0; i < 64; i++) 64 | decodingTable[(short)encodingTable[i]] = i; 65 | 66 | const char *characters = [string cStringUsingEncoding:NSASCIIStringEncoding]; 67 | if (characters == NULL) // Not an ASCII string! 68 | return nil; 69 | char *bytes = malloc((([string length] + 3) / 4) * 3); 70 | if (bytes == NULL) 71 | return nil; 72 | 73 | NSUInteger length = 0; 74 | NSUInteger i = 0; 75 | 76 | while (YES) { 77 | char buffer[4]; 78 | short bufferLength; 79 | 80 | for (bufferLength = 0; bufferLength < 4; i++) { 81 | if (characters[i] == '\0') 82 | break; 83 | if (isspace(characters[i]) || characters[i] == '=') 84 | continue; 85 | buffer[bufferLength] = decodingTable[(short)characters[i]]; 86 | if (buffer[bufferLength++] == CHAR_MAX) { // Illegal character! 87 | free(bytes); 88 | return nil; 89 | } 90 | } 91 | 92 | if (bufferLength == 0) 93 | break; 94 | 95 | if (bufferLength == 1) { // At least two characters are needed to produce one byte! 96 | free(bytes); 97 | return nil; 98 | } 99 | 100 | // Decode the characters in the buffer to bytes. 101 | bytes[length++] = (buffer[0] << 2) | (buffer[1] >> 4); 102 | if (bufferLength > 2) 103 | bytes[length++] = (buffer[1] << 4) | (buffer[2] >> 2); 104 | if (bufferLength > 3) 105 | bytes[length++] = (buffer[2] << 6) | buffer[3]; 106 | } 107 | 108 | realloc(bytes, length); 109 | 110 | return [NSData dataWithBytesNoCopy:bytes length:length]; 111 | } -------------------------------------------------------------------------------- /AirTunes/CryptoController.h: -------------------------------------------------------------------------------- 1 | // 2 | // CryptoController.h 3 | // AirSpeaker 4 | // 5 | // Created by Clément Vasseur on 2/20/11. 6 | // Copyright 2011 Clément Vasseur. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import 12 | 13 | @interface CryptoController : NSObject { 14 | SecKeyRef privateKey; 15 | CCCryptorRef cryptor; 16 | NSData *iv; 17 | } 18 | 19 | - (id)init; 20 | - (NSData *)challengeResponse:(NSData *)challenge withAddr:(NSData *)addr; 21 | - (BOOL)setRsaAesKey:(NSData *)key; 22 | - (BOOL)setAesIv:(NSData *)iv; 23 | - (NSData *)decryptData:(NSData *)data; 24 | - (void)reset; 25 | - (void)dealloc; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /AirTunes/CryptoController.m: -------------------------------------------------------------------------------- 1 | // 2 | // CryptoController.m 3 | // AirSpeaker 4 | // 5 | // Created by Clément Vasseur on 2/20/11. 6 | // Copyright 2011 Clément Vasseur. All rights reserved. 7 | // 8 | 9 | #import "CryptoController.h" 10 | #import "DeviceInfo.h" 11 | #import "Base64.h" 12 | 13 | #define kAESKeyLength 16 14 | 15 | @implementation CryptoController 16 | 17 | - (SecKeyRef)getKeyRefWithPersistentKeyRef:(CFTypeRef)persistentRef 18 | { 19 | OSStatus status = noErr; 20 | SecKeyRef keyRef = NULL; 21 | 22 | if (persistentRef == NULL) 23 | return NULL; 24 | 25 | NSMutableDictionary *queryKey = [[NSMutableDictionary alloc] init]; 26 | 27 | // Set the SecKeyRef query dictionary. 28 | [queryKey setObject:(id)persistentRef forKey:(id)kSecValuePersistentRef]; 29 | [queryKey setObject:[NSNumber numberWithBool:YES] forKey:(id)kSecReturnRef]; 30 | 31 | // Get the persistent key reference. 32 | status = SecItemCopyMatching((CFDictionaryRef)queryKey, (CFTypeRef *)&keyRef); 33 | [queryKey release]; 34 | 35 | return keyRef; 36 | } 37 | 38 | - (BOOL)setPrivateKey 39 | { 40 | OSStatus status; 41 | 42 | NSString *keyName = @"AirSpeaker - AirTunes Private Key"; 43 | 44 | NSData *key = base64_decode(@ // Thank you James Laird 45 | "MIIEpQIBAAKCAQEA59dE8qLieItsH1WgjrcFRKj6eUWqi+bGLOX1HL3U3GhC/j0Qg90u3sG/1CUt" 46 | "wC5vOYvfDmFI6oSFXi5ELabWJmT2dKHzBJKa3k9ok+8t9ucRqMd6DZHJ2YCCLlDRKSKv6kDqnw4U" 47 | "wPdpOMXziC/AMj3Z/lUVX1G7WSHCAWKf1zNS1eLvqr+boEjXuBOitnZ/bDzPHrTOZz0Dew0uowxf" 48 | "/+sG+NCK3eQJVxqcaJ/vEHKIVd2M+5qL71yJQ+87X6oV3eaYvt3zWZYD6z5vYTcrtij2VZ9Zmni/" 49 | "UAaHqn9JdsBWLUEpVviYnhimNVvYFZeCXg/IdTQ+x4IRdiXNv5hEewIDAQABAoIBAQDl8Axy9XfW" 50 | "BLmkzkEiqoSwF0PsmVrPzH9KsnwLGH+QZlvjWd8SWYGN7u1507HvhF5N3drJoVU3O14nDY4TFQAa" 51 | "LlJ9VM35AApXaLyY1ERrN7u9ALKd2LUwYhM7Km539O4yUFYikE2nIPscEsA5ltpxOgUGCY7b7ez5" 52 | "NtD6nL1ZKauw7aNXmVAvmJTcuPxWmoktF3gDJKK2wxZuNGcJE0uFQEG4Z3BrWP7yoNuSK3dii2jm" 53 | "lpPHr0O/KnPQtzI3eguhe0TwUem/eYSdyzMyVx/YpwkzwtYL3sR5k0o9rKQLtvLzfAqdBxBurciz" 54 | "aaA/L0HIgAmOit1GJA2saMxTVPNhAoGBAPfgv1oeZxgxmotiCcMXFEQEWflzhWYTsXrhUIuz5jFu" 55 | "a39GLS99ZEErhLdrwj8rDDViRVJ5skOp9zFvlYAHs0xh92ji1E7V/ysnKBfsMrPkk5KSKPrnjndM" 56 | "oPdevWnVkgJ5jxFuNgxkOLMuG9i53B4yMvDTCRiIPMQ++N2iLDaRAoGBAO9v//mU8eVkQaoANf0Z" 57 | "oMjW8CN4xwWA2cSEIHkd9AfFkftuv8oyLDCG3ZAf0vrhrrtkrfa7ef+AUb69DNggq4mHQAYBp7L+" 58 | "k5DKzJrKuO0r+R0YbY9pZD1+/g9dVt91d6LQNepUE/yY2PP5CNoFmjedpLHMOPFdVgqDzDFxU8hL" 59 | "AoGBANDrr7xAJbqBjHVwIzQ4To9pb4BNeqDndk5Qe7fT3+/H1njGaC0/rXE0Qb7q5ySgnsCb3DvA" 60 | "cJyRM9SJ7OKlGt0FMSdJD5KG0XPIpAVNwgpXXH5MDJg09KHeh0kXo+QA6viFBi21y340NonnEfdf" 61 | "54PX4ZGS/Xac1UK+pLkBB+zRAoGAf0AY3H3qKS2lMEI4bzEFoHeK3G895pDaK3TFBVmD7fV0Zhov" 62 | "17fegFPMwOII8MisYm9ZfT2Z0s5Ro3s5rkt+nvLAdfC/PYPKzTLalpGSwomSNYJcB9HNMlmhkGzc" 63 | "1JnLYT4iyUyx6pcZBmCd8bD0iwY/FzcgNDaUmbX9+XDvRA0CgYEAkE7pIPlE71qvfJQgoA9em0gI" 64 | "LAuE4Pu13aKiJnfft7hIjbK+5kyb3TysZvoyDnb3HOKvInK7vXbKuU4ISgxB2bB3HcYzQMGsz1qJ" 65 | "2gG0N5hvJpzwwhbhXqFKA4zaaSrw622wDniAK5MlIE0tIAKKP4yxNGjoD2QYjhBGuhvkWKaXTyY="); 66 | 67 | NSData *tag = [keyName dataUsingEncoding:NSUTF8StringEncoding]; 68 | 69 | NSMutableDictionary *keyAttr = [[NSMutableDictionary alloc] init]; 70 | 71 | [keyAttr setObject:(id)kSecClassKey forKey:(id)kSecClass]; 72 | [keyAttr setObject:(id)kSecAttrKeyTypeRSA forKey:(id)kSecAttrKeyType]; 73 | [keyAttr setObject:tag forKey:(id)kSecAttrApplicationTag]; 74 | 75 | // delete any old key with the same tag 76 | SecItemDelete((CFDictionaryRef) keyAttr); 77 | 78 | [keyAttr setObject:(id)kSecAttrKeyClassPrivate forKey:(id)kSecAttrKeyClass]; 79 | [keyAttr setObject:key forKey:(id)kSecValueData]; 80 | [keyAttr setObject:[NSNumber numberWithBool:YES] forKey:(id)kSecReturnPersistentRef]; 81 | 82 | SecKeyRef persistPrivateKey = NULL; 83 | 84 | status = SecItemAdd((CFDictionaryRef) keyAttr, (CFTypeRef *) &persistPrivateKey); 85 | if (status != noErr) { 86 | NSLog(@"[Crypto] SecItemAdd error %ld", status); 87 | return NO; 88 | } 89 | 90 | if (persistPrivateKey) { 91 | privateKey = [self getKeyRefWithPersistentKeyRef:persistPrivateKey]; 92 | 93 | } else { 94 | [keyAttr removeObjectForKey:(id)kSecValueData]; 95 | [keyAttr setObject:[NSNumber numberWithBool:YES] forKey:(id)kSecReturnRef]; 96 | 97 | status = SecItemCopyMatching((CFDictionaryRef) keyAttr, (CFTypeRef *) &privateKey); 98 | if (status != noErr) { 99 | NSLog(@"[Crypto] SecItemCopyMatching error %ld", status); 100 | return NO; 101 | } 102 | } 103 | 104 | [keyAttr release]; 105 | 106 | if (privateKey == NULL) { 107 | NSLog(@"[Crypto] unable to load private key"); 108 | return NO; 109 | } 110 | 111 | return YES; 112 | } 113 | 114 | - (id)init 115 | { 116 | if ((self = [super init])) { 117 | if (![self setPrivateKey]) { 118 | [self release]; 119 | return nil; 120 | } 121 | } 122 | 123 | return self; 124 | } 125 | 126 | - (NSData *)challengeResponse:(NSData *)challenge withAddr:(NSData *)addr 127 | { 128 | OSStatus status; 129 | uint8_t data[38]; 130 | size_t data_len; 131 | 132 | if (privateKey == NULL) 133 | return nil; 134 | 135 | // response begins with the challenge random data 136 | 137 | if ([challenge length] != 16) 138 | return nil; 139 | 140 | [challenge getBytes:data]; 141 | data_len = [challenge length]; 142 | 143 | // append ip address 144 | 145 | if ([addr length] > 16) 146 | return nil; 147 | 148 | [addr getBytes:data + data_len]; 149 | data_len += [addr length]; 150 | 151 | // append mac address 152 | 153 | NSData *deviceId = [DeviceInfo deviceId]; 154 | if ([deviceId length] != 6) 155 | return nil; 156 | 157 | [deviceId getBytes:data + data_len]; 158 | data_len += [deviceId length]; 159 | 160 | // pad with 0 if necessary 161 | 162 | while (data_len < 32) 163 | data[data_len++] = 0; 164 | 165 | // sign response using the private key 166 | 167 | uint8_t buf[1024]; 168 | size_t buf_len = sizeof(buf); 169 | 170 | status = SecKeyRawSign(privateKey, 171 | kSecPaddingPKCS1, 172 | data, data_len, 173 | buf, &buf_len); 174 | 175 | if (status != noErr) { 176 | NSLog(@"[Crypto] SecKeyRawSign error %ld", status); 177 | return nil; 178 | } 179 | 180 | return [NSData dataWithBytes:buf length:buf_len]; 181 | } 182 | 183 | - (NSData *)decryptRsa:(NSData *)data 184 | { 185 | OSStatus status; 186 | uint8_t *plainText; 187 | size_t plainTextLen; 188 | 189 | if (privateKey == NULL) 190 | return nil; 191 | 192 | // check input data size 193 | plainTextLen = [data length]; 194 | if (plainTextLen != SecKeyGetBlockSize(privateKey)) { 195 | NSLog(@"[Crypto] encrypted nonce is too large and falls outside multiplicative group"); 196 | return nil; 197 | } 198 | 199 | // allocate some buffer space 200 | plainText = calloc(1, plainTextLen); 201 | if (plainText == NULL) 202 | return nil; 203 | 204 | // decrypt using the private key 205 | status = SecKeyDecrypt(privateKey, 206 | kSecPaddingOAEP, 207 | [data bytes], 208 | [data length], 209 | plainText, 210 | &plainTextLen); 211 | 212 | if (status != noErr) { 213 | NSLog(@"[Crypto] SecKeyDecrypt error %ld", status); 214 | free(plainText); 215 | return nil; 216 | } 217 | 218 | return [NSData dataWithBytesNoCopy:plainText length:plainTextLen]; 219 | } 220 | 221 | - (BOOL)setRsaAesKey:(NSData *)key 222 | { 223 | CCCryptorStatus status; 224 | 225 | key = [self decryptRsa:key]; 226 | 227 | if ([key length] != kAESKeyLength) { 228 | NSLog(@"[Crypto] invalid AES key length"); 229 | return NO; 230 | } 231 | 232 | status = CCCryptorCreate(kCCDecrypt, kCCAlgorithmAES128, 0, 233 | [key bytes], [key length], NULL, 234 | &cryptor); 235 | 236 | if (status != kCCSuccess) { 237 | NSLog(@"[Crypto] CCCryptorCreate error %u", status); 238 | return FALSE; 239 | } 240 | 241 | return YES; 242 | } 243 | 244 | - (BOOL)setAesIv:(NSData *)aesIv 245 | { 246 | if ([aesIv length] != kAESKeyLength) 247 | return NO; 248 | 249 | [iv release]; 250 | iv = [aesIv retain]; 251 | 252 | return YES; 253 | } 254 | 255 | - (NSData *)decryptData:(NSData *)data 256 | { 257 | NSMutableData *outData; 258 | CCCryptorStatus status; 259 | size_t dataOutMoved; 260 | size_t clear_len; 261 | size_t crypt_len; 262 | 263 | if (cryptor == NULL) 264 | return [NSData dataWithData:data]; 265 | 266 | clear_len = [data length] % kAESKeyLength; 267 | crypt_len = [data length] - clear_len; 268 | 269 | outData = [NSMutableData dataWithData:data]; 270 | if (outData == nil) 271 | return nil; 272 | 273 | if (crypt_len == 0) 274 | return outData; 275 | 276 | status = CCCryptorReset(cryptor, [iv bytes]); 277 | if (status != kCCSuccess) { 278 | NSLog(@"[Crypto] CCCryptorReset error %u", status); 279 | [outData release]; 280 | return nil; 281 | } 282 | 283 | uint8_t dataOut[crypt_len]; 284 | 285 | status = CCCryptorUpdate(cryptor, 286 | [data bytes], crypt_len, 287 | dataOut, crypt_len, 288 | &dataOutMoved); 289 | 290 | [outData replaceBytesInRange:(NSRange){0, crypt_len} withBytes:dataOut]; 291 | 292 | if (status != kCCSuccess) { 293 | NSLog(@"[Crypto] CCCryptorUpdate error %u", status); 294 | [outData release]; 295 | return nil; 296 | } 297 | 298 | return outData; 299 | } 300 | 301 | - (void)reset 302 | { 303 | CCCryptorStatus status; 304 | 305 | if (cryptor != NULL) { 306 | status = CCCryptorRelease(cryptor); 307 | if (status != kCCSuccess) 308 | NSLog(@"[Crypto] CCCryptorRelease error %u", status); 309 | cryptor = NULL; 310 | } 311 | 312 | [iv release]; 313 | iv = nil; 314 | } 315 | 316 | - (void)dealloc 317 | { 318 | [self reset]; 319 | [super dealloc]; 320 | } 321 | 322 | @end 323 | -------------------------------------------------------------------------------- /AirTunes/DMAP.h: -------------------------------------------------------------------------------- 1 | // 2 | // DMAP.h 3 | // AirSpeaker 4 | // 5 | // Created by Clément Vasseur on 4/19/11. 6 | // Copyright 2011 Clément Vasseur. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface DMAP : NSObject { 12 | NSMutableDictionary *codes; 13 | NSMutableDictionary *types; 14 | NSData *data; 15 | } 16 | 17 | - (id)initWithData:(NSData *)data; 18 | - (id)get:(NSString *)name; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /AirTunes/DMAP.m: -------------------------------------------------------------------------------- 1 | // 2 | // DMAP.m 3 | // AirSpeaker 4 | // 5 | // Created by Clément Vasseur on 4/19/11. 6 | // Copyright 2011 Clément Vasseur. All rights reserved. 7 | // 8 | 9 | #import "DMAP.h" 10 | 11 | #define ARRAY_SIZE(A) (sizeof(A) / sizeof((A)[0])) 12 | 13 | @implementation DMAP 14 | 15 | enum dmap_type { 16 | DMAP_TYPE_BYTE, 17 | DMAP_TYPE_SHORT, 18 | DMAP_TYPE_INT, 19 | DMAP_TYPE_LONG, 20 | DMAP_TYPE_STRING, 21 | DMAP_TYPE_LIST, 22 | DMAP_TYPE_VERSION, 23 | DMAP_TYPE_DATE, 24 | }; 25 | 26 | static const struct { 27 | const char code[4]; 28 | enum dmap_type type; 29 | const char *name; 30 | 31 | } dmapDefs[] = { 32 | 33 | // Code Type Name Description 34 | { "mdcl", DMAP_TYPE_LIST, "dmap.dictionary" }, // a dictionary entry 35 | { "mstt", DMAP_TYPE_INT, "dmap.status" }, // the response status code, these appear to be http status codes, e.g. 200 36 | { "miid", DMAP_TYPE_INT, "dmap.itemid" }, // an item's id 37 | { "minm", DMAP_TYPE_STRING, "dmap.itemname" }, // an items name 38 | { "mikd", DMAP_TYPE_BYTE, "dmap.itemkind" }, // the kind of item. So far, only '2' has been seen, an audio file? 39 | { "mper", DMAP_TYPE_LONG, "dmap.persistentid" }, // a persistend id 40 | { "mcon", DMAP_TYPE_LIST, "dmap.container" }, // an arbitrary container 41 | { "mcti", DMAP_TYPE_INT, "dmap.containeritemid" }, // the id of an item in its container 42 | { "mpco", DMAP_TYPE_INT, "dmap.parentcontainerid" }, 43 | { "msts", DMAP_TYPE_STRING, "dmap.statusstring" }, 44 | { "mimc", DMAP_TYPE_INT, "dmap.itemcount" }, // number of items in a container 45 | { "mrco", DMAP_TYPE_INT, "dmap.returnedcount" }, // number of items returned in a request 46 | { "mtco", DMAP_TYPE_INT, "dmap.specifiedtotalcount" }, // number of items in response to a request 47 | { "mlcl", DMAP_TYPE_LIST, "dmap.listing" }, // a list 48 | { "mlit", DMAP_TYPE_LIST, "dmap.listingitem" }, // a single item in said list 49 | { "mbcl", DMAP_TYPE_LIST, "dmap.bag" }, 50 | { "mdcl", DMAP_TYPE_LIST, "dmap.dictionary" }, 51 | 52 | { "msrv", DMAP_TYPE_LIST, "dmap.serverinforesponse" }, // response to a /server-info 53 | { "msau", DMAP_TYPE_BYTE, "dmap.authenticationmethod" }, // (should be self explanitory) 54 | { "mslr", DMAP_TYPE_BYTE, "dmap.loginrequired" }, 55 | { "mpro", DMAP_TYPE_VERSION, "dmap.protocolversion" }, 56 | { "apro", DMAP_TYPE_VERSION, "daap.protocolversion" }, 57 | { "msal", DMAP_TYPE_BYTE, "dmap.supportsuatologout" }, 58 | { "msup", DMAP_TYPE_BYTE, "dmap.supportsupdate" }, 59 | { "mspi", DMAP_TYPE_BYTE, "dmap.supportspersistentids" }, 60 | { "msex", DMAP_TYPE_BYTE, "dmap.supportsextensions" }, 61 | { "msbr", DMAP_TYPE_BYTE, "dmap.supportsbrowse" }, 62 | { "msqy", DMAP_TYPE_BYTE, "dmap.supportsquery" }, 63 | { "msix", DMAP_TYPE_BYTE, "dmap.supportsindex" }, 64 | { "msrs", DMAP_TYPE_BYTE, "dmap.supportsresolve" }, 65 | { "mstm", DMAP_TYPE_INT, "dmap.timeoutinterval" }, 66 | { "msdc", DMAP_TYPE_INT, "dmap.databasescount" }, 67 | 68 | { "mccr", DMAP_TYPE_LIST, "dmap.contentcodesresponse" }, // the response to the content-codes request 69 | { "mcnm", DMAP_TYPE_INT, "dmap.contentcodesnumber" }, // the four letter code 70 | { "mcna", DMAP_TYPE_STRING, "dmap.contentcodesname" }, // the full name of the code 71 | { "mcty", DMAP_TYPE_SHORT, "dmap.contentcodestype" }, // the type of the code (see appendix b for type values) 72 | 73 | { "mlog", DMAP_TYPE_LIST, "dmap.loginresponse" }, // response to a /login 74 | { "mlid", DMAP_TYPE_INT, "dmap.sessionid" }, // the session id for the login session 75 | 76 | { "mupd", DMAP_TYPE_LIST, "dmap.updateresponse" }, // response to a /update 77 | { "msur", DMAP_TYPE_INT, "dmap.serverrevision" }, // revision to use for requests 78 | { "muty", DMAP_TYPE_BYTE, "dmap.updatetype" }, 79 | { "mudl", DMAP_TYPE_LIST, "dmap.deletedidlisting" }, // used in updates? (document soon) 80 | 81 | { "avdb", DMAP_TYPE_LIST, "daap.serverdatabases" }, // response to a /databases 82 | { "abro", DMAP_TYPE_LIST, "daap.databasebrowse" }, 83 | { "abal", DMAP_TYPE_LIST, "daap.browsealbumlistung" }, 84 | { "abar", DMAP_TYPE_LIST, "daap.browseartistlisting" }, 85 | { "abcp", DMAP_TYPE_LIST, "daap.browsecomposerlisting" }, 86 | { "abgn", DMAP_TYPE_LIST, "daap.browsegenrelisting" }, 87 | 88 | { "adbs", DMAP_TYPE_LIST, "daap.databasesongs" }, // response to a /databases/id/items 89 | { "asal", DMAP_TYPE_STRING, "daap.songalbum" }, // the song ones should be self exp. 90 | { "asar", DMAP_TYPE_STRING, "daap.songartist" }, 91 | { "asbt", DMAP_TYPE_SHORT, "daap.songsbeatsperminute" }, 92 | { "asbr", DMAP_TYPE_SHORT, "daap.songbitrate" }, 93 | { "ascm", DMAP_TYPE_STRING, "daap.songcomment" }, 94 | { "asco", DMAP_TYPE_BYTE, "daap.songcompilation" }, 95 | { "asda", DMAP_TYPE_DATE, "daap.songdateadded" }, 96 | { "asdm", DMAP_TYPE_DATE, "daap.songdatemodified" }, 97 | { "asdc", DMAP_TYPE_SHORT, "daap.songdisccount" }, 98 | { "asdn", DMAP_TYPE_SHORT, "daap.songdiscnumber" }, 99 | { "asdb", DMAP_TYPE_BYTE, "daap.songdisabled" }, 100 | { "aseq", DMAP_TYPE_STRING, "daap.songeqpreset" }, 101 | { "asfm", DMAP_TYPE_STRING, "daap.songformat" }, 102 | { "asgn", DMAP_TYPE_STRING, "daap.songgenre" }, 103 | { "asdt", DMAP_TYPE_STRING, "daap.songdescription" }, 104 | { "asrv", DMAP_TYPE_BYTE, "daap.songrelativevolume" }, 105 | { "assr", DMAP_TYPE_INT, "daap.songsamplerate" }, 106 | { "assz", DMAP_TYPE_INT, "daap.songsize" }, 107 | { "asst", DMAP_TYPE_INT, "daap.songstarttime" }, // (in milliseconds) 108 | { "assp", DMAP_TYPE_INT, "daap.songstoptime" }, // (in milliseconds) 109 | { "astm", DMAP_TYPE_INT, "daap.songtime" }, // (in milliseconds) 110 | { "astc", DMAP_TYPE_SHORT, "daap.songtrackcount" }, 111 | { "astn", DMAP_TYPE_SHORT, "daap.songtracknumber" }, 112 | { "asur", DMAP_TYPE_BYTE, "daap.songuserrating" }, 113 | { "asyr", DMAP_TYPE_SHORT, "daap.songyear" }, 114 | { "asdk", DMAP_TYPE_BYTE, "daap.songdatakind" }, 115 | { "asul", DMAP_TYPE_STRING, "daap.songdataurl" }, 116 | 117 | { "aply", DMAP_TYPE_LIST, "daap.databaseplaylists" }, // response to /databases/id/containers 118 | { "abpl", DMAP_TYPE_BYTE, "daap.baseplaylist" }, 119 | 120 | { "apso", DMAP_TYPE_LIST, "daap.playlistsongs" }, // response to /databases/id/containers/id/items 121 | { "prsv", DMAP_TYPE_LIST, "daap.resolve" }, 122 | { "arif", DMAP_TYPE_LIST, "daap.resolveinfo" }, 123 | 124 | { "aeNV", DMAP_TYPE_INT, "com.apple.itunes.norm-volume" }, 125 | { "aeSP", DMAP_TYPE_BYTE, "com.apple.itunes.smart-playlist" }, 126 | }; 127 | 128 | - (id)initWithData:(NSData *)dmapData 129 | { 130 | if ((self = [super init])) { 131 | self->codes = [[NSMutableDictionary alloc] init]; 132 | self->types = [[NSMutableDictionary alloc] init]; 133 | 134 | for (unsigned i = 0; i < ARRAY_SIZE(dmapDefs); i++) { 135 | NSString *name = [NSString stringWithCString:dmapDefs[i].name 136 | encoding:NSASCIIStringEncoding]; 137 | 138 | NSString *code = [[[NSString alloc] initWithBytes:dmapDefs[i].code 139 | length:4 140 | encoding:NSASCIIStringEncoding] autorelease]; 141 | 142 | NSNumber *type = [NSNumber numberWithInt:dmapDefs[i].type]; 143 | 144 | [codes setObject:code forKey:name]; 145 | [types setObject:type forKey:name]; 146 | } 147 | 148 | self->data = [dmapData retain]; 149 | } 150 | 151 | return self; 152 | } 153 | 154 | static const uint8_t *getItem(const uint8_t *data, 155 | size_t len, 156 | const char code[4], 157 | uint32_t *size) 158 | { 159 | if (len < 8) 160 | return NULL; 161 | 162 | memcpy(size, data + 4, sizeof(*size)); 163 | *size = OSSwapBigToHostInt32(*size); 164 | 165 | if (memcmp(data, code, 4) == 0) 166 | return data + 8; 167 | 168 | return getItem(data + 8 + *size, len - (8 + *size), code, size); 169 | } 170 | 171 | - (id)getRec:(NSString *)name data:(NSData *)dat 172 | { 173 | uint32_t size; 174 | 175 | NSRange range = [name rangeOfString:@"/"]; 176 | 177 | if (range.location == NSNotFound) { 178 | NSString *code = [codes objectForKey:name]; 179 | NSNumber *type = [types objectForKey:name]; 180 | 181 | const uint8_t *p = getItem([dat bytes], [dat length], 182 | [code cStringUsingEncoding:NSASCIIStringEncoding], 183 | &size); 184 | if (p == NULL) 185 | return nil; 186 | 187 | if ([type intValue] == DMAP_TYPE_STRING) 188 | return [[[NSString alloc] initWithBytes:p 189 | length:size 190 | encoding:NSUTF8StringEncoding] autorelease]; 191 | 192 | // TODO: implement the other types 193 | 194 | } else { 195 | 196 | NSString *name0 = [name substringWithRange:(NSRange){0, range.location}]; 197 | NSString *code = [codes objectForKey:name0]; 198 | NSNumber *type = [types objectForKey:name0]; 199 | 200 | if ([type intValue] != DMAP_TYPE_LIST) 201 | return nil; 202 | 203 | const uint8_t *p0 = [dat bytes]; 204 | const uint8_t *p = getItem(p0, [dat length], 205 | [code cStringUsingEncoding:NSASCIIStringEncoding], 206 | &size); 207 | if (p == NULL) 208 | return nil; 209 | 210 | return [self getRec:[name substringFromIndex:range.location + 1] 211 | data:[dat subdataWithRange:(NSRange){p - p0, size}]]; 212 | } 213 | 214 | return nil; 215 | } 216 | 217 | - (id)get:(NSString *)name 218 | { 219 | return [self getRec:name data:data]; 220 | } 221 | 222 | - (void)dealloc 223 | { 224 | [codes release]; 225 | [types release]; 226 | [data release]; 227 | [super dealloc]; 228 | } 229 | 230 | @end 231 | -------------------------------------------------------------------------------- /AirTunes/TimeSync.h: -------------------------------------------------------------------------------- 1 | // 2 | // TimeSync.h 3 | // AirSpeaker 4 | // 5 | // Created by Clément Vasseur on 2/8/11. 6 | // Copyright 2011 Clément Vasseur. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class AsyncUdpSocket; 12 | 13 | @interface TimeSync : NSObject { 14 | AsyncUdpSocket *socket; 15 | uint64_t startTime; 16 | uint64_t clockOffset; 17 | int queryCount; 18 | uint32_t latency; 19 | id delegate; 20 | id userData; 21 | } 22 | 23 | - (id)initWithServer:(NSData *)address; 24 | - (void)startWithDelegate:(id)delegate userData:(id)data; 25 | - (UInt16)localPort; 26 | - (uint32_t)latency; 27 | 28 | @end 29 | 30 | #pragma mark - 31 | 32 | @protocol TimeSyncDelegate 33 | @optional 34 | 35 | - (void)timeSyncWithLatency:(uint32_t)latency userData:(id)data; 36 | 37 | @end 38 | -------------------------------------------------------------------------------- /AirTunes/TimeSync.m: -------------------------------------------------------------------------------- 1 | // 2 | // TimeSync.m 3 | // AirSpeaker 4 | // 5 | // Created by Clément Vasseur on 2/8/11. 6 | // Copyright 2011 Clément Vasseur. All rights reserved. 7 | // 8 | 9 | #import "AirTunes.h" 10 | #import "TimeSync.h" 11 | #import "AsyncUdpSocket.h" 12 | 13 | #include 14 | #include 15 | 16 | #define TIMEOUT_NONE -1 17 | #define TIMESTAMP_EPOCH (0x83aa7e80LL << 32) 18 | 19 | 20 | @implementation TimeSync 21 | 22 | - (id)initWithServer:(NSData *)address 23 | { 24 | NSError *error; 25 | 26 | if ((self = [super init])) { 27 | socket = [[AsyncUdpSocket alloc] initWithDelegate:self]; 28 | 29 | if (![socket connectToAddress:address error:&error]) { 30 | NSLog(@"Error: unable to connect UDP socket: %@", error); 31 | [socket release]; 32 | [self release]; 33 | return nil; 34 | } 35 | } 36 | 37 | return self; 38 | } 39 | 40 | - (uint64_t)getTimestamp 41 | { 42 | static mach_timebase_info_data_t s_timebase_info; 43 | 44 | uint64_t t = mach_absolute_time() - startTime; 45 | 46 | if (s_timebase_info.denom == 0) 47 | (void) mach_timebase_info(&s_timebase_info); 48 | 49 | // convert absolute time difference to 32.32 fixed point timestamp 50 | return (t * s_timebase_info.numer * (1LL << 32)) / (s_timebase_info.denom * 1000000000LL); 51 | } 52 | 53 | - (BOOL)sendQuery 54 | { 55 | struct airtunes_timing_packet pkt = { 56 | .airtunes_packet = AIRTUNES_PACKET, 57 | .airtunes_command = AIRTUNES_TIMING_QUERY, 58 | .fixed = htons(0x0007), 59 | .zero = 0, 60 | .timestamp_1 = 0, 61 | .timestamp_2 = 0, 62 | .timestamp_3 = OSSwapHostToBigInt64([self getTimestamp] + clockOffset), 63 | }; 64 | 65 | NSLog(@"[Time] sync query: %f", 66 | (double) (OSSwapBigToHostInt64(pkt.timestamp_3) - TIMESTAMP_EPOCH) / (1LL << 32)); 67 | NSData *data = [NSData dataWithBytes:&pkt length:sizeof(pkt)]; 68 | return [socket sendData:data withTimeout:TIMEOUT_NONE tag:0]; 69 | } 70 | 71 | - (void)onUdpSocket:(AsyncUdpSocket *)sock didSendDataWithTag:(long)tag 72 | { 73 | [sock receiveWithTimeout:TIMEOUT_NONE tag:0]; 74 | } 75 | 76 | - (BOOL)onUdpSocket:(AsyncUdpSocket *)sock 77 | didReceiveData:(NSData *)data 78 | withTag:(long)tag 79 | fromHost:(NSString *)host 80 | port:(UInt16)port 81 | { 82 | struct airtunes_timing_packet pkt; 83 | 84 | if ([data length] != sizeof(pkt)) 85 | return NO; 86 | 87 | [data getBytes:&pkt length:sizeof(pkt)]; 88 | 89 | if (pkt.airtunes_packet != AIRTUNES_PACKET || 90 | pkt.airtunes_command != AIRTUNES_TIMING_REPLY) 91 | return NO; 92 | 93 | // sntp time sync 94 | // http://www.faqs.org/rfcs/rfc1769.html 95 | 96 | int64_t timestamp_originate = OSSwapBigToHostInt64(pkt.timestamp_1) - TIMESTAMP_EPOCH; 97 | int64_t timestamp_receive = OSSwapBigToHostInt64(pkt.timestamp_2) - TIMESTAMP_EPOCH; 98 | int64_t timestamp_transmit = OSSwapBigToHostInt64(pkt.timestamp_3) - TIMESTAMP_EPOCH; 99 | int64_t timestamp_destination = [self getTimestamp] + clockOffset - TIMESTAMP_EPOCH; 100 | 101 | // NSLog(@"time sync reply: originate %f", (double) timestamp_originate / (1LL << 32)); 102 | // NSLog(@"time sync reply: receive %f", (double) timestamp_receive / (1LL << 32)); 103 | // NSLog(@"time sync reply: transmit %f", (double) timestamp_transmit / (1LL << 32)); 104 | // NSLog(@"time sync reply: destination %f", (double) timestamp_destination / (1LL << 32)); 105 | 106 | // compute the local clock offset 107 | 108 | int64_t local_clock_offset = ((timestamp_receive - timestamp_originate) + 109 | (timestamp_transmit - timestamp_destination)) / 2; 110 | 111 | NSLog(@"[Time] local clock offset: %f", (double) local_clock_offset / (1LL << 32)); 112 | clockOffset += local_clock_offset; 113 | 114 | if (timestamp_transmit > timestamp_destination) 115 | latency = 0; 116 | else 117 | latency = (1000 * (timestamp_destination - timestamp_transmit)) / (1LL << 32); 118 | 119 | NSLog(@"[Time] latency %u ms", latency); 120 | 121 | if (queryCount > 0) { 122 | [self sendQuery]; 123 | queryCount--; 124 | } else if (delegate != nil) { 125 | [delegate timeSyncWithLatency:latency userData:userData]; 126 | [delegate release]; 127 | delegate = nil; 128 | } 129 | 130 | return YES; 131 | } 132 | 133 | - (void)startWithDelegate:(id)syncDelegate userData:(id)data 134 | { 135 | delegate = [syncDelegate retain]; 136 | userData = [data retain]; 137 | 138 | startTime = mach_absolute_time(); 139 | clockOffset = TIMESTAMP_EPOCH; 140 | queryCount = 2; 141 | 142 | [self sendQuery]; 143 | } 144 | 145 | - (UInt16)localPort 146 | { 147 | return [socket localPort]; 148 | } 149 | 150 | - (uint32_t)latency 151 | { 152 | return latency; 153 | } 154 | 155 | - (void)dealloc 156 | { 157 | [socket release]; 158 | [delegate release]; 159 | [userData release]; 160 | [super dealloc]; 161 | } 162 | 163 | @end 164 | -------------------------------------------------------------------------------- /AirTunes/alac.c: -------------------------------------------------------------------------------- 1 | /* 2 | * ALAC (Apple Lossless Audio Codec) decoder 3 | * Copyright (c) 2005 David Hammerton 4 | * All rights reserved. 5 | * 6 | * This is the actual decoder. 7 | * 8 | * http://crazney.net/programs/itunes/alac.html 9 | * 10 | * Permission is hereby granted, free of charge, to any person 11 | * obtaining a copy of this software and associated documentation 12 | * files (the "Software"), to deal in the Software without 13 | * restriction, including without limitation the rights to use, 14 | * copy, modify, merge, publish, distribute, sublicense, and/or 15 | * sell copies of the Software, and to permit persons to whom the 16 | * Software is furnished to do so, subject to the following conditions: 17 | * 18 | * The above copyright notice and this permission notice shall be 19 | * included in all copies or substantial portions of the Software. 20 | * 21 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 22 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 23 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 24 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 25 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 26 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 27 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 28 | * OTHER DEALINGS IN THE SOFTWARE. 29 | * 30 | */ 31 | 32 | static const int host_bigendian = 0; 33 | 34 | #include 35 | #include 36 | #include 37 | #ifdef _WIN32 38 | #include "stdint_win.h" 39 | #else 40 | #include 41 | #endif 42 | 43 | #include "alac.h" 44 | 45 | #define _Swap32(v) do { \ 46 | v = (((v) & 0x000000FF) << 0x18) | \ 47 | (((v) & 0x0000FF00) << 0x08) | \ 48 | (((v) & 0x00FF0000) >> 0x08) | \ 49 | (((v) & 0xFF000000) >> 0x18); } while(0) 50 | 51 | #define _Swap16(v) do { \ 52 | v = (((v) & 0x00FF) << 0x08) | \ 53 | (((v) & 0xFF00) >> 0x08); } while (0) 54 | 55 | struct {signed int x:24;} se_struct_24; 56 | #define SignExtend24(val) (se_struct_24.x = val) 57 | 58 | void allocate_buffers(alac_file *alac) 59 | { 60 | alac->predicterror_buffer_a = malloc(alac->setinfo_max_samples_per_frame * 4); 61 | alac->predicterror_buffer_b = malloc(alac->setinfo_max_samples_per_frame * 4); 62 | 63 | alac->outputsamples_buffer_a = malloc(alac->setinfo_max_samples_per_frame * 4); 64 | alac->outputsamples_buffer_b = malloc(alac->setinfo_max_samples_per_frame * 4); 65 | 66 | alac->uncompressed_bytes_buffer_a = malloc(alac->setinfo_max_samples_per_frame * 4); 67 | alac->uncompressed_bytes_buffer_b = malloc(alac->setinfo_max_samples_per_frame * 4); 68 | } 69 | 70 | void alac_set_info(alac_file *alac, char *inputbuffer) 71 | { 72 | char *ptr = inputbuffer; 73 | ptr += 4; /* size */ 74 | ptr += 4; /* frma */ 75 | ptr += 4; /* alac */ 76 | ptr += 4; /* size */ 77 | ptr += 4; /* alac */ 78 | 79 | ptr += 4; /* 0 ? */ 80 | 81 | alac->setinfo_max_samples_per_frame = *(uint32_t*)ptr; /* buffer size / 2 ? */ 82 | if (!host_bigendian) 83 | _Swap32(alac->setinfo_max_samples_per_frame); 84 | ptr += 4; 85 | alac->setinfo_7a = *(uint8_t*)ptr; 86 | ptr += 1; 87 | alac->setinfo_sample_size = *(uint8_t*)ptr; 88 | ptr += 1; 89 | alac->setinfo_rice_historymult = *(uint8_t*)ptr; 90 | ptr += 1; 91 | alac->setinfo_rice_initialhistory = *(uint8_t*)ptr; 92 | ptr += 1; 93 | alac->setinfo_rice_kmodifier = *(uint8_t*)ptr; 94 | ptr += 1; 95 | alac->setinfo_7f = *(uint8_t*)ptr; 96 | ptr += 1; 97 | alac->setinfo_80 = *(uint16_t*)ptr; 98 | if (!host_bigendian) 99 | _Swap16(alac->setinfo_80); 100 | ptr += 2; 101 | alac->setinfo_82 = *(uint32_t*)ptr; 102 | if (!host_bigendian) 103 | _Swap32(alac->setinfo_82); 104 | ptr += 4; 105 | alac->setinfo_86 = *(uint32_t*)ptr; 106 | if (!host_bigendian) 107 | _Swap32(alac->setinfo_86); 108 | ptr += 4; 109 | alac->setinfo_8a_rate = *(uint32_t*)ptr; 110 | if (!host_bigendian) 111 | _Swap32(alac->setinfo_8a_rate); 112 | ptr += 4; 113 | 114 | allocate_buffers(alac); 115 | 116 | } 117 | 118 | /* stream reading */ 119 | 120 | /* supports reading 1 to 16 bits, in big endian format */ 121 | static uint32_t readbits_16(alac_file *alac, int bits) 122 | { 123 | uint32_t result; 124 | int new_accumulator; 125 | 126 | result = (alac->input_buffer[0] << 16) | 127 | (alac->input_buffer[1] << 8) | 128 | (alac->input_buffer[2]); 129 | 130 | /* shift left by the number of bits we've already read, 131 | * so that the top 'n' bits of the 24 bits we read will 132 | * be the return bits */ 133 | result = result << alac->input_buffer_bitaccumulator; 134 | 135 | result = result & 0x00ffffff; 136 | 137 | /* and then only want the top 'n' bits from that, where 138 | * n is 'bits' */ 139 | result = result >> (24 - bits); 140 | 141 | new_accumulator = (alac->input_buffer_bitaccumulator + bits); 142 | 143 | /* increase the buffer pointer if we've read over n bytes. */ 144 | alac->input_buffer += (new_accumulator >> 3); 145 | 146 | /* and the remainder goes back into the bit accumulator */ 147 | alac->input_buffer_bitaccumulator = (new_accumulator & 7); 148 | 149 | return result; 150 | } 151 | 152 | /* supports reading 1 to 32 bits, in big endian format */ 153 | static uint32_t readbits(alac_file *alac, int bits) 154 | { 155 | int32_t result = 0; 156 | 157 | if (bits > 16) 158 | { 159 | bits -= 16; 160 | result = readbits_16(alac, 16) << bits; 161 | } 162 | 163 | result |= readbits_16(alac, bits); 164 | 165 | return result; 166 | } 167 | 168 | /* reads a single bit */ 169 | static int readbit(alac_file *alac) 170 | { 171 | int result; 172 | int new_accumulator; 173 | 174 | result = alac->input_buffer[0]; 175 | 176 | result = result << alac->input_buffer_bitaccumulator; 177 | 178 | result = result >> 7 & 1; 179 | 180 | new_accumulator = (alac->input_buffer_bitaccumulator + 1); 181 | 182 | alac->input_buffer += (new_accumulator / 8); 183 | 184 | alac->input_buffer_bitaccumulator = (new_accumulator % 8); 185 | 186 | return result; 187 | } 188 | 189 | static void unreadbits(alac_file *alac, int bits) 190 | { 191 | int new_accumulator = (alac->input_buffer_bitaccumulator - bits); 192 | 193 | alac->input_buffer += (new_accumulator >> 3); 194 | 195 | alac->input_buffer_bitaccumulator = (new_accumulator & 7); 196 | if (alac->input_buffer_bitaccumulator < 0) 197 | alac->input_buffer_bitaccumulator *= -1; 198 | } 199 | 200 | /* various implementations of count_leading_zero: 201 | * the first one is the original one, the simplest and most 202 | * obvious for what it's doing. never use this. 203 | * then there are the asm ones. fill in as necessary 204 | * and finally an unrolled and optimised c version 205 | * to fall back to 206 | */ 207 | #if 0 208 | /* hideously inefficient. could use a bitmask search, 209 | * alternatively bsr on x86, 210 | */ 211 | static int count_leading_zeros(int32_t input) 212 | { 213 | int i = 0; 214 | while (!(0x80000000 & input) && i < 32) 215 | { 216 | i++; 217 | input = input << 1; 218 | } 219 | return i; 220 | } 221 | #elif defined(__GNUC__) 222 | static int count_leading_zeros(int input) 223 | { 224 | return __builtin_clz(input); 225 | } 226 | #elif defined(_MSC_VER) && defined(_M_IX86) 227 | static int count_leading_zeros(int input) 228 | { 229 | int output = 0; 230 | if (!input) return 32; 231 | __asm 232 | { 233 | mov eax, input; 234 | mov edx, 0x1f; 235 | bsr ecx, eax; 236 | sub edx, ecx; 237 | mov output, edx; 238 | } 239 | return output; 240 | } 241 | #else 242 | #warning using generic count leading zeroes. You may wish to write one for your CPU / compiler 243 | static int count_leading_zeros(int input) 244 | { 245 | int output = 0; 246 | int curbyte = 0; 247 | 248 | curbyte = input >> 24; 249 | if (curbyte) goto found; 250 | output += 8; 251 | 252 | curbyte = input >> 16; 253 | if (curbyte & 0xff) goto found; 254 | output += 8; 255 | 256 | curbyte = input >> 8; 257 | if (curbyte & 0xff) goto found; 258 | output += 8; 259 | 260 | curbyte = input; 261 | if (curbyte & 0xff) goto found; 262 | output += 8; 263 | 264 | return output; 265 | 266 | found: 267 | if (!(curbyte & 0xf0)) 268 | { 269 | output += 4; 270 | } 271 | else 272 | curbyte >>= 4; 273 | 274 | if (curbyte & 0x8) 275 | return output; 276 | if (curbyte & 0x4) 277 | return output + 1; 278 | if (curbyte & 0x2) 279 | return output + 2; 280 | if (curbyte & 0x1) 281 | return output + 3; 282 | 283 | /* shouldn't get here: */ 284 | return output + 4; 285 | } 286 | #endif 287 | 288 | #define RICE_THRESHOLD 8 // maximum number of bits for a rice prefix. 289 | 290 | int32_t entropy_decode_value(alac_file* alac, 291 | int readSampleSize, 292 | int k, 293 | int rice_kmodifier_mask) 294 | { 295 | int32_t x = 0; // decoded value 296 | 297 | // read x, number of 1s before 0 represent the rice value. 298 | while (x <= RICE_THRESHOLD && readbit(alac)) 299 | { 300 | x++; 301 | } 302 | 303 | if (x > RICE_THRESHOLD) 304 | { 305 | // read the number from the bit stream (raw value) 306 | int32_t value; 307 | 308 | value = readbits(alac, readSampleSize); 309 | 310 | // mask value 311 | value &= (((uint32_t)0xffffffff) >> (32 - readSampleSize)); 312 | 313 | x = value; 314 | } 315 | else 316 | { 317 | if (k != 1) 318 | { 319 | int extraBits = readbits(alac, k); 320 | 321 | // x = x * (2^k - 1) 322 | x *= (((1 << k) - 1) & rice_kmodifier_mask); 323 | 324 | if (extraBits > 1) 325 | x += extraBits - 1; 326 | else 327 | unreadbits(alac, 1); 328 | } 329 | } 330 | 331 | return x; 332 | } 333 | 334 | void entropy_rice_decode(alac_file* alac, 335 | int32_t* outputBuffer, 336 | int outputSize, 337 | int readSampleSize, 338 | int rice_initialhistory, 339 | int rice_kmodifier, 340 | int rice_historymult, 341 | int rice_kmodifier_mask) 342 | { 343 | int outputCount; 344 | int history = rice_initialhistory; 345 | int signModifier = 0; 346 | 347 | for (outputCount = 0; outputCount < outputSize; outputCount++) 348 | { 349 | int32_t decodedValue; 350 | int32_t finalValue; 351 | int32_t k; 352 | 353 | k = 31 - rice_kmodifier - count_leading_zeros((history >> 9) + 3); 354 | 355 | if (k < 0) k += rice_kmodifier; 356 | else k = rice_kmodifier; 357 | 358 | // note: don't use rice_kmodifier_mask here (set mask to 0xFFFFFFFF) 359 | decodedValue = entropy_decode_value(alac, readSampleSize, k, 0xFFFFFFFF); 360 | 361 | decodedValue += signModifier; 362 | finalValue = (decodedValue + 1) / 2; // inc by 1 and shift out sign bit 363 | if (decodedValue & 1) // the sign is stored in the low bit 364 | finalValue *= -1; 365 | 366 | outputBuffer[outputCount] = finalValue; 367 | 368 | signModifier = 0; 369 | 370 | // update history 371 | history += (decodedValue * rice_historymult) 372 | - ((history * rice_historymult) >> 9); 373 | 374 | if (decodedValue > 0xFFFF) 375 | history = 0xFFFF; 376 | 377 | // special case, for compressed blocks of 0 378 | if ((history < 128) && (outputCount + 1 < outputSize)) 379 | { 380 | int32_t blockSize; 381 | 382 | signModifier = 1; 383 | 384 | k = count_leading_zeros(history) + ((history + 16) / 64) - 24; 385 | 386 | // note: blockSize is always 16bit 387 | blockSize = entropy_decode_value(alac, 16, k, rice_kmodifier_mask); 388 | 389 | // got blockSize 0s 390 | if (blockSize > 0) 391 | { 392 | memset(&outputBuffer[outputCount + 1], 0, blockSize * sizeof(*outputBuffer)); 393 | outputCount += blockSize; 394 | } 395 | 396 | if (blockSize > 0xFFFF) 397 | signModifier = 0; 398 | 399 | history = 0; 400 | } 401 | } 402 | } 403 | 404 | #define SIGN_EXTENDED32(val, bits) ((val << (32 - bits)) >> (32 - bits)) 405 | 406 | #define SIGN_ONLY(v) \ 407 | ((v < 0) ? (-1) : \ 408 | ((v > 0) ? (1) : \ 409 | (0))) 410 | 411 | static void predictor_decompress_fir_adapt(int32_t *error_buffer, 412 | int32_t *buffer_out, 413 | int output_size, 414 | int readsamplesize, 415 | int16_t *predictor_coef_table, 416 | int predictor_coef_num, 417 | int predictor_quantitization) 418 | { 419 | int i; 420 | 421 | /* first sample always copies */ 422 | *buffer_out = *error_buffer; 423 | 424 | if (!predictor_coef_num) 425 | { 426 | if (output_size <= 1) return; 427 | memcpy(buffer_out+1, error_buffer+1, (output_size-1) * 4); 428 | return; 429 | } 430 | 431 | if (predictor_coef_num == 0x1f) /* 11111 - max value of predictor_coef_num */ 432 | { /* second-best case scenario for fir decompression, 433 | * error describes a small difference from the previous sample only 434 | */ 435 | if (output_size <= 1) return; 436 | for (i = 0; i < output_size - 1; i++) 437 | { 438 | int32_t prev_value; 439 | int32_t error_value; 440 | 441 | prev_value = buffer_out[i]; 442 | error_value = error_buffer[i+1]; 443 | buffer_out[i+1] = SIGN_EXTENDED32((prev_value + error_value), readsamplesize); 444 | } 445 | return; 446 | } 447 | 448 | /* read warm-up samples */ 449 | if (predictor_coef_num > 0) 450 | { 451 | int i; 452 | for (i = 0; i < predictor_coef_num; i++) 453 | { 454 | int32_t val; 455 | 456 | val = buffer_out[i] + error_buffer[i+1]; 457 | 458 | val = SIGN_EXTENDED32(val, readsamplesize); 459 | 460 | buffer_out[i+1] = val; 461 | } 462 | } 463 | 464 | #if 0 465 | /* 4 and 8 are very common cases (the only ones i've seen). these 466 | * should be unrolled and optimised 467 | */ 468 | if (predictor_coef_num == 4) 469 | { 470 | /* FIXME: optimised general case */ 471 | return; 472 | } 473 | 474 | if (predictor_coef_table == 8) 475 | { 476 | /* FIXME: optimised general case */ 477 | return; 478 | } 479 | #endif 480 | 481 | 482 | /* general case */ 483 | if (predictor_coef_num > 0) 484 | { 485 | for (i = predictor_coef_num + 1; 486 | i < output_size; 487 | i++) 488 | { 489 | int j; 490 | int sum = 0; 491 | int outval; 492 | int error_val = error_buffer[i]; 493 | 494 | for (j = 0; j < predictor_coef_num; j++) 495 | { 496 | sum += (buffer_out[predictor_coef_num-j] - buffer_out[0]) * 497 | predictor_coef_table[j]; 498 | } 499 | 500 | outval = (1 << (predictor_quantitization-1)) + sum; 501 | outval = outval >> predictor_quantitization; 502 | outval = outval + buffer_out[0] + error_val; 503 | outval = SIGN_EXTENDED32(outval, readsamplesize); 504 | 505 | buffer_out[predictor_coef_num+1] = outval; 506 | 507 | if (error_val > 0) 508 | { 509 | int predictor_num = predictor_coef_num - 1; 510 | 511 | while (predictor_num >= 0 && error_val > 0) 512 | { 513 | int val = buffer_out[0] - buffer_out[predictor_coef_num - predictor_num]; 514 | int sign = SIGN_ONLY(val); 515 | 516 | predictor_coef_table[predictor_num] -= sign; 517 | 518 | val *= sign; /* absolute value */ 519 | 520 | error_val -= ((val >> predictor_quantitization) * 521 | (predictor_coef_num - predictor_num)); 522 | 523 | predictor_num--; 524 | } 525 | } 526 | else if (error_val < 0) 527 | { 528 | int predictor_num = predictor_coef_num - 1; 529 | 530 | while (predictor_num >= 0 && error_val < 0) 531 | { 532 | int val = buffer_out[0] - buffer_out[predictor_coef_num - predictor_num]; 533 | int sign = - SIGN_ONLY(val); 534 | 535 | predictor_coef_table[predictor_num] -= sign; 536 | 537 | val *= sign; /* neg value */ 538 | 539 | error_val -= ((val >> predictor_quantitization) * 540 | (predictor_coef_num - predictor_num)); 541 | 542 | predictor_num--; 543 | } 544 | } 545 | 546 | buffer_out++; 547 | } 548 | } 549 | } 550 | 551 | void deinterlace_16(int32_t *buffer_a, int32_t *buffer_b, 552 | int16_t *buffer_out, 553 | int numchannels, int numsamples, 554 | uint8_t interlacing_shift, 555 | uint8_t interlacing_leftweight) 556 | { 557 | int i; 558 | if (numsamples <= 0) return; 559 | 560 | /* weighted interlacing */ 561 | if (interlacing_leftweight) 562 | { 563 | for (i = 0; i < numsamples; i++) 564 | { 565 | int32_t difference, midright; 566 | int16_t left; 567 | int16_t right; 568 | 569 | midright = buffer_a[i]; 570 | difference = buffer_b[i]; 571 | 572 | 573 | right = midright - ((difference * interlacing_leftweight) >> interlacing_shift); 574 | left = right + difference; 575 | 576 | /* output is always little endian */ 577 | if (host_bigendian) 578 | { 579 | _Swap16(left); 580 | _Swap16(right); 581 | } 582 | 583 | buffer_out[i*numchannels] = left; 584 | buffer_out[i*numchannels + 1] = right; 585 | } 586 | 587 | return; 588 | } 589 | 590 | /* otherwise basic interlacing took place */ 591 | for (i = 0; i < numsamples; i++) 592 | { 593 | int16_t left, right; 594 | 595 | left = buffer_a[i]; 596 | right = buffer_b[i]; 597 | 598 | /* output is always little endian */ 599 | if (host_bigendian) 600 | { 601 | _Swap16(left); 602 | _Swap16(right); 603 | } 604 | 605 | buffer_out[i*numchannels] = left; 606 | buffer_out[i*numchannels + 1] = right; 607 | } 608 | } 609 | 610 | void deinterlace_24(int32_t *buffer_a, int32_t *buffer_b, 611 | int uncompressed_bytes, 612 | int32_t *uncompressed_bytes_buffer_a, int32_t *uncompressed_bytes_buffer_b, 613 | void *buffer_out, 614 | int numchannels, int numsamples, 615 | uint8_t interlacing_shift, 616 | uint8_t interlacing_leftweight) 617 | { 618 | int i; 619 | if (numsamples <= 0) return; 620 | 621 | /* weighted interlacing */ 622 | if (interlacing_leftweight) 623 | { 624 | for (i = 0; i < numsamples; i++) 625 | { 626 | int32_t difference, midright; 627 | int32_t left; 628 | int32_t right; 629 | 630 | midright = buffer_a[i]; 631 | difference = buffer_b[i]; 632 | 633 | right = midright - ((difference * interlacing_leftweight) >> interlacing_shift); 634 | left = right + difference; 635 | 636 | if (uncompressed_bytes) 637 | { 638 | uint32_t mask = ~(0xFFFFFFFF << (uncompressed_bytes * 8)); 639 | left <<= (uncompressed_bytes * 8); 640 | right <<= (uncompressed_bytes * 8); 641 | 642 | left |= uncompressed_bytes_buffer_a[i] & mask; 643 | right |= uncompressed_bytes_buffer_b[i] & mask; 644 | } 645 | 646 | ((uint8_t*)buffer_out)[i * numchannels * 3] = (left) & 0xFF; 647 | ((uint8_t*)buffer_out)[i * numchannels * 3 + 1] = (left >> 8) & 0xFF; 648 | ((uint8_t*)buffer_out)[i * numchannels * 3 + 2] = (left >> 16) & 0xFF; 649 | 650 | ((uint8_t*)buffer_out)[i * numchannels * 3 + 3] = (right) & 0xFF; 651 | ((uint8_t*)buffer_out)[i * numchannels * 3 + 4] = (right >> 8) & 0xFF; 652 | ((uint8_t*)buffer_out)[i * numchannels * 3 + 5] = (right >> 16) & 0xFF; 653 | } 654 | 655 | return; 656 | } 657 | 658 | /* otherwise basic interlacing took place */ 659 | for (i = 0; i < numsamples; i++) 660 | { 661 | int32_t left, right; 662 | 663 | left = buffer_a[i]; 664 | right = buffer_b[i]; 665 | 666 | if (uncompressed_bytes) 667 | { 668 | uint32_t mask = ~(0xFFFFFFFF << (uncompressed_bytes * 8)); 669 | left <<= (uncompressed_bytes * 8); 670 | right <<= (uncompressed_bytes * 8); 671 | 672 | left |= uncompressed_bytes_buffer_a[i] & mask; 673 | right |= uncompressed_bytes_buffer_b[i] & mask; 674 | } 675 | 676 | ((uint8_t*)buffer_out)[i * numchannels * 3] = (left) & 0xFF; 677 | ((uint8_t*)buffer_out)[i * numchannels * 3 + 1] = (left >> 8) & 0xFF; 678 | ((uint8_t*)buffer_out)[i * numchannels * 3 + 2] = (left >> 16) & 0xFF; 679 | 680 | ((uint8_t*)buffer_out)[i * numchannels * 3 + 3] = (right) & 0xFF; 681 | ((uint8_t*)buffer_out)[i * numchannels * 3 + 4] = (right >> 8) & 0xFF; 682 | ((uint8_t*)buffer_out)[i * numchannels * 3 + 5] = (right >> 16) & 0xFF; 683 | 684 | } 685 | 686 | } 687 | 688 | void decode_frame(alac_file *alac, 689 | const unsigned char *inbuffer, 690 | void *outbuffer, int *outputsize) 691 | { 692 | int channels; 693 | int32_t outputsamples = alac->setinfo_max_samples_per_frame; 694 | 695 | /* setup the stream */ 696 | alac->input_buffer = inbuffer; 697 | alac->input_buffer_bitaccumulator = 0; 698 | 699 | channels = readbits(alac, 3); 700 | 701 | *outputsize = outputsamples * alac->bytespersample; 702 | 703 | switch(channels) 704 | { 705 | case 0: /* 1 channel */ 706 | { 707 | int hassize; 708 | int isnotcompressed; 709 | int readsamplesize; 710 | 711 | int uncompressed_bytes; 712 | int ricemodifier; 713 | 714 | /* 2^result = something to do with output waiting. 715 | * perhaps matters if we read > 1 frame in a pass? 716 | */ 717 | readbits(alac, 4); 718 | 719 | readbits(alac, 12); /* unknown, skip 12 bits */ 720 | 721 | hassize = readbits(alac, 1); /* the output sample size is stored soon */ 722 | 723 | uncompressed_bytes = readbits(alac, 2); /* number of bytes in the (compressed) stream that are not compressed */ 724 | 725 | isnotcompressed = readbits(alac, 1); /* whether the frame is compressed */ 726 | 727 | if (hassize) 728 | { 729 | /* now read the number of samples, 730 | * as a 32bit integer */ 731 | outputsamples = readbits(alac, 32); 732 | *outputsize = outputsamples * alac->bytespersample; 733 | } 734 | 735 | readsamplesize = alac->setinfo_sample_size - (uncompressed_bytes * 8); 736 | 737 | if (!isnotcompressed) 738 | { /* so it is compressed */ 739 | int16_t predictor_coef_table[32]; 740 | int predictor_coef_num; 741 | int prediction_type; 742 | int prediction_quantitization; 743 | int i; 744 | 745 | /* skip 16 bits, not sure what they are. seem to be used in 746 | * two channel case */ 747 | readbits(alac, 8); 748 | readbits(alac, 8); 749 | 750 | prediction_type = readbits(alac, 4); 751 | prediction_quantitization = readbits(alac, 4); 752 | 753 | ricemodifier = readbits(alac, 3); 754 | predictor_coef_num = readbits(alac, 5); 755 | 756 | /* read the predictor table */ 757 | for (i = 0; i < predictor_coef_num; i++) 758 | { 759 | predictor_coef_table[i] = (int16_t)readbits(alac, 16); 760 | } 761 | 762 | if (uncompressed_bytes) 763 | { 764 | int i; 765 | for (i = 0; i < outputsamples; i++) 766 | { 767 | alac->uncompressed_bytes_buffer_a[i] = readbits(alac, uncompressed_bytes * 8); 768 | } 769 | } 770 | 771 | entropy_rice_decode(alac, 772 | alac->predicterror_buffer_a, 773 | outputsamples, 774 | readsamplesize, 775 | alac->setinfo_rice_initialhistory, 776 | alac->setinfo_rice_kmodifier, 777 | ricemodifier * alac->setinfo_rice_historymult / 4, 778 | (1 << alac->setinfo_rice_kmodifier) - 1); 779 | 780 | if (prediction_type == 0) 781 | { /* adaptive fir */ 782 | predictor_decompress_fir_adapt(alac->predicterror_buffer_a, 783 | alac->outputsamples_buffer_a, 784 | outputsamples, 785 | readsamplesize, 786 | predictor_coef_table, 787 | predictor_coef_num, 788 | prediction_quantitization); 789 | } 790 | else 791 | { 792 | fprintf(stderr, "FIXME: unhandled predicition type: %i\n", prediction_type); 793 | /* i think the only other prediction type (or perhaps this is just a 794 | * boolean?) runs adaptive fir twice.. like: 795 | * predictor_decompress_fir_adapt(predictor_error, tempout, ...) 796 | * predictor_decompress_fir_adapt(predictor_error, outputsamples ...) 797 | * little strange.. 798 | */ 799 | } 800 | 801 | } 802 | else 803 | { /* not compressed, easy case */ 804 | if (alac->setinfo_sample_size <= 16) 805 | { 806 | int i; 807 | for (i = 0; i < outputsamples; i++) 808 | { 809 | int32_t audiobits = readbits(alac, alac->setinfo_sample_size); 810 | 811 | audiobits = SIGN_EXTENDED32(audiobits, alac->setinfo_sample_size); 812 | 813 | alac->outputsamples_buffer_a[i] = audiobits; 814 | } 815 | } 816 | else 817 | { 818 | int i; 819 | for (i = 0; i < outputsamples; i++) 820 | { 821 | int32_t audiobits; 822 | 823 | audiobits = readbits(alac, 16); 824 | /* special case of sign extension.. 825 | * as we'll be ORing the low 16bits into this */ 826 | audiobits = audiobits << (alac->setinfo_sample_size - 16); 827 | audiobits |= readbits(alac, alac->setinfo_sample_size - 16); 828 | audiobits = SignExtend24(audiobits); 829 | 830 | alac->outputsamples_buffer_a[i] = audiobits; 831 | } 832 | } 833 | uncompressed_bytes = 0; // always 0 for uncompressed 834 | } 835 | 836 | switch(alac->setinfo_sample_size) 837 | { 838 | case 16: 839 | { 840 | int i; 841 | for (i = 0; i < outputsamples; i++) 842 | { 843 | int16_t sample = alac->outputsamples_buffer_a[i]; 844 | if (host_bigendian) 845 | _Swap16(sample); 846 | ((int16_t*)outbuffer)[i * alac->numchannels] = sample; 847 | } 848 | break; 849 | } 850 | case 24: 851 | { 852 | int i; 853 | for (i = 0; i < outputsamples; i++) 854 | { 855 | int32_t sample = alac->outputsamples_buffer_a[i]; 856 | 857 | if (uncompressed_bytes) 858 | { 859 | uint32_t mask; 860 | sample = sample << (uncompressed_bytes * 8); 861 | mask = ~(0xFFFFFFFF << (uncompressed_bytes * 8)); 862 | sample |= alac->uncompressed_bytes_buffer_a[i] & mask; 863 | } 864 | 865 | ((uint8_t*)outbuffer)[i * alac->numchannels * 3] = (sample) & 0xFF; 866 | ((uint8_t*)outbuffer)[i * alac->numchannels * 3 + 1] = (sample >> 8) & 0xFF; 867 | ((uint8_t*)outbuffer)[i * alac->numchannels * 3 + 2] = (sample >> 16) & 0xFF; 868 | } 869 | break; 870 | } 871 | case 20: 872 | case 32: 873 | fprintf(stderr, "FIXME: unimplemented sample size %i\n", alac->setinfo_sample_size); 874 | break; 875 | default: 876 | break; 877 | } 878 | break; 879 | } 880 | case 1: /* 2 channels */ 881 | { 882 | int hassize; 883 | int isnotcompressed; 884 | int readsamplesize; 885 | 886 | int uncompressed_bytes; 887 | 888 | uint8_t interlacing_shift; 889 | uint8_t interlacing_leftweight; 890 | 891 | /* 2^result = something to do with output waiting. 892 | * perhaps matters if we read > 1 frame in a pass? 893 | */ 894 | readbits(alac, 4); 895 | 896 | readbits(alac, 12); /* unknown, skip 12 bits */ 897 | 898 | hassize = readbits(alac, 1); /* the output sample size is stored soon */ 899 | 900 | uncompressed_bytes = readbits(alac, 2); /* the number of bytes in the (compressed) stream that are not compressed */ 901 | 902 | isnotcompressed = readbits(alac, 1); /* whether the frame is compressed */ 903 | 904 | if (hassize) 905 | { 906 | /* now read the number of samples, 907 | * as a 32bit integer */ 908 | outputsamples = readbits(alac, 32); 909 | *outputsize = outputsamples * alac->bytespersample; 910 | } 911 | 912 | readsamplesize = alac->setinfo_sample_size - (uncompressed_bytes * 8) + 1; 913 | 914 | if (!isnotcompressed) 915 | { /* compressed */ 916 | int16_t predictor_coef_table_a[32]; 917 | int predictor_coef_num_a; 918 | int prediction_type_a; 919 | int prediction_quantitization_a; 920 | int ricemodifier_a; 921 | 922 | int16_t predictor_coef_table_b[32]; 923 | int predictor_coef_num_b; 924 | int prediction_type_b; 925 | int prediction_quantitization_b; 926 | int ricemodifier_b; 927 | 928 | int i; 929 | 930 | interlacing_shift = readbits(alac, 8); 931 | interlacing_leftweight = readbits(alac, 8); 932 | 933 | /******** channel 1 ***********/ 934 | prediction_type_a = readbits(alac, 4); 935 | prediction_quantitization_a = readbits(alac, 4); 936 | 937 | ricemodifier_a = readbits(alac, 3); 938 | predictor_coef_num_a = readbits(alac, 5); 939 | 940 | /* read the predictor table */ 941 | for (i = 0; i < predictor_coef_num_a; i++) 942 | { 943 | predictor_coef_table_a[i] = (int16_t)readbits(alac, 16); 944 | } 945 | 946 | /******** channel 2 *********/ 947 | prediction_type_b = readbits(alac, 4); 948 | prediction_quantitization_b = readbits(alac, 4); 949 | 950 | ricemodifier_b = readbits(alac, 3); 951 | predictor_coef_num_b = readbits(alac, 5); 952 | 953 | /* read the predictor table */ 954 | for (i = 0; i < predictor_coef_num_b; i++) 955 | { 956 | predictor_coef_table_b[i] = (int16_t)readbits(alac, 16); 957 | } 958 | 959 | /*********************/ 960 | if (uncompressed_bytes) 961 | { /* see mono case */ 962 | int i; 963 | for (i = 0; i < outputsamples; i++) 964 | { 965 | alac->uncompressed_bytes_buffer_a[i] = readbits(alac, uncompressed_bytes * 8); 966 | alac->uncompressed_bytes_buffer_b[i] = readbits(alac, uncompressed_bytes * 8); 967 | } 968 | } 969 | 970 | /* channel 1 */ 971 | entropy_rice_decode(alac, 972 | alac->predicterror_buffer_a, 973 | outputsamples, 974 | readsamplesize, 975 | alac->setinfo_rice_initialhistory, 976 | alac->setinfo_rice_kmodifier, 977 | ricemodifier_a * alac->setinfo_rice_historymult / 4, 978 | (1 << alac->setinfo_rice_kmodifier) - 1); 979 | 980 | if (prediction_type_a == 0) 981 | { /* adaptive fir */ 982 | predictor_decompress_fir_adapt(alac->predicterror_buffer_a, 983 | alac->outputsamples_buffer_a, 984 | outputsamples, 985 | readsamplesize, 986 | predictor_coef_table_a, 987 | predictor_coef_num_a, 988 | prediction_quantitization_a); 989 | } 990 | else 991 | { /* see mono case */ 992 | fprintf(stderr, "FIXME: unhandled predicition type: %i\n", prediction_type_a); 993 | } 994 | 995 | /* channel 2 */ 996 | entropy_rice_decode(alac, 997 | alac->predicterror_buffer_b, 998 | outputsamples, 999 | readsamplesize, 1000 | alac->setinfo_rice_initialhistory, 1001 | alac->setinfo_rice_kmodifier, 1002 | ricemodifier_b * alac->setinfo_rice_historymult / 4, 1003 | (1 << alac->setinfo_rice_kmodifier) - 1); 1004 | 1005 | if (prediction_type_b == 0) 1006 | { /* adaptive fir */ 1007 | predictor_decompress_fir_adapt(alac->predicterror_buffer_b, 1008 | alac->outputsamples_buffer_b, 1009 | outputsamples, 1010 | readsamplesize, 1011 | predictor_coef_table_b, 1012 | predictor_coef_num_b, 1013 | prediction_quantitization_b); 1014 | } 1015 | else 1016 | { 1017 | fprintf(stderr, "FIXME: unhandled predicition type: %i\n", prediction_type_b); 1018 | } 1019 | } 1020 | else 1021 | { /* not compressed, easy case */ 1022 | if (alac->setinfo_sample_size <= 16) 1023 | { 1024 | int i; 1025 | for (i = 0; i < outputsamples; i++) 1026 | { 1027 | int32_t audiobits_a, audiobits_b; 1028 | 1029 | audiobits_a = readbits(alac, alac->setinfo_sample_size); 1030 | audiobits_b = readbits(alac, alac->setinfo_sample_size); 1031 | 1032 | audiobits_a = SIGN_EXTENDED32(audiobits_a, alac->setinfo_sample_size); 1033 | audiobits_b = SIGN_EXTENDED32(audiobits_b, alac->setinfo_sample_size); 1034 | 1035 | alac->outputsamples_buffer_a[i] = audiobits_a; 1036 | alac->outputsamples_buffer_b[i] = audiobits_b; 1037 | } 1038 | } 1039 | else 1040 | { 1041 | int i; 1042 | for (i = 0; i < outputsamples; i++) 1043 | { 1044 | int32_t audiobits_a, audiobits_b; 1045 | 1046 | audiobits_a = readbits(alac, 16); 1047 | audiobits_a = audiobits_a << (alac->setinfo_sample_size - 16); 1048 | audiobits_a |= readbits(alac, alac->setinfo_sample_size - 16); 1049 | audiobits_a = SignExtend24(audiobits_a); 1050 | 1051 | audiobits_b = readbits(alac, 16); 1052 | audiobits_b = audiobits_b << (alac->setinfo_sample_size - 16); 1053 | audiobits_b |= readbits(alac, alac->setinfo_sample_size - 16); 1054 | audiobits_b = SignExtend24(audiobits_b); 1055 | 1056 | alac->outputsamples_buffer_a[i] = audiobits_a; 1057 | alac->outputsamples_buffer_b[i] = audiobits_b; 1058 | } 1059 | } 1060 | uncompressed_bytes = 0; // always 0 for uncompressed 1061 | interlacing_shift = 0; 1062 | interlacing_leftweight = 0; 1063 | } 1064 | 1065 | switch(alac->setinfo_sample_size) 1066 | { 1067 | case 16: 1068 | { 1069 | deinterlace_16(alac->outputsamples_buffer_a, 1070 | alac->outputsamples_buffer_b, 1071 | (int16_t*)outbuffer, 1072 | alac->numchannels, 1073 | outputsamples, 1074 | interlacing_shift, 1075 | interlacing_leftweight); 1076 | break; 1077 | } 1078 | case 24: 1079 | { 1080 | deinterlace_24(alac->outputsamples_buffer_a, 1081 | alac->outputsamples_buffer_b, 1082 | uncompressed_bytes, 1083 | alac->uncompressed_bytes_buffer_a, 1084 | alac->uncompressed_bytes_buffer_b, 1085 | (int16_t*)outbuffer, 1086 | alac->numchannels, 1087 | outputsamples, 1088 | interlacing_shift, 1089 | interlacing_leftweight); 1090 | break; 1091 | } 1092 | case 20: 1093 | case 32: 1094 | fprintf(stderr, "FIXME: unimplemented sample size %i\n", alac->setinfo_sample_size); 1095 | break; 1096 | default: 1097 | break; 1098 | } 1099 | 1100 | break; 1101 | } 1102 | } 1103 | } 1104 | 1105 | alac_file *create_alac(int samplesize, int numchannels) 1106 | { 1107 | alac_file *newfile = malloc(sizeof(alac_file)); 1108 | 1109 | newfile->samplesize = samplesize; 1110 | newfile->numchannels = numchannels; 1111 | newfile->bytespersample = (samplesize / 8) * numchannels; 1112 | 1113 | return newfile; 1114 | } 1115 | -------------------------------------------------------------------------------- /AirTunes/alac.h: -------------------------------------------------------------------------------- 1 | #ifndef __ALAC__DECOMP_H 2 | #define __ALAC__DECOMP_H 3 | 4 | typedef struct alac_file alac_file; 5 | 6 | alac_file *create_alac(int samplesize, int numchannels); 7 | void decode_frame(alac_file *alac, 8 | const unsigned char *inbuffer, 9 | void *outbuffer, int *outputsize); 10 | void alac_set_info(alac_file *alac, char *inputbuffer); 11 | void allocate_buffers(alac_file *alac); 12 | 13 | struct alac_file 14 | { 15 | const unsigned char *input_buffer; 16 | int input_buffer_bitaccumulator; /* used so we can do arbitary 17 | bit reads */ 18 | 19 | int samplesize; 20 | int numchannels; 21 | int bytespersample; 22 | 23 | 24 | /* buffers */ 25 | int32_t *predicterror_buffer_a; 26 | int32_t *predicterror_buffer_b; 27 | 28 | int32_t *outputsamples_buffer_a; 29 | int32_t *outputsamples_buffer_b; 30 | 31 | int32_t *uncompressed_bytes_buffer_a; 32 | int32_t *uncompressed_bytes_buffer_b; 33 | 34 | 35 | 36 | /* stuff from setinfo */ 37 | uint32_t setinfo_max_samples_per_frame; /* 0x1000 = 4096 */ /* max samples per frame? */ 38 | uint8_t setinfo_7a; /* 0x00 */ 39 | uint8_t setinfo_sample_size; /* 0x10 */ 40 | uint8_t setinfo_rice_historymult; /* 0x28 */ 41 | uint8_t setinfo_rice_initialhistory; /* 0x0a */ 42 | uint8_t setinfo_rice_kmodifier; /* 0x0e */ 43 | uint8_t setinfo_7f; /* 0x02 */ 44 | uint16_t setinfo_80; /* 0x00ff */ 45 | uint32_t setinfo_82; /* 0x000020e7 */ /* max sample size?? */ 46 | uint32_t setinfo_86; /* 0x00069fe4 */ /* bit rate (avarge)?? */ 47 | uint32_t setinfo_8a_rate; /* 0x0000ac44 */ 48 | /* end setinfo stuff */ 49 | 50 | }; 51 | 52 | 53 | #endif /* __ALAC__DECOMP_H */ 54 | 55 | -------------------------------------------------------------------------------- /CocoaAsyncSocket/AsyncUdpSocket.h: -------------------------------------------------------------------------------- 1 | // 2 | // AsyncUdpSocket.h 3 | // 4 | // This class is in the public domain. 5 | // Originally created by Robbie Hanson on Wed Oct 01 2008. 6 | // Updated and maintained by Deusty Designs and the Mac development community. 7 | // 8 | // http://code.google.com/p/cocoaasyncsocket/ 9 | // 10 | 11 | #import 12 | 13 | @class AsyncSendPacket; 14 | @class AsyncReceivePacket; 15 | 16 | extern NSString *const AsyncUdpSocketException; 17 | extern NSString *const AsyncUdpSocketErrorDomain; 18 | 19 | enum AsyncUdpSocketError 20 | { 21 | AsyncUdpSocketCFSocketError = kCFSocketError, // From CFSocketError enum 22 | AsyncUdpSocketNoError = 0, // Never used 23 | AsyncUdpSocketBadParameter, // Used if given a bad parameter (such as an improper address) 24 | AsyncUdpSocketIPv4Unavailable, // Used if you bind/connect using IPv6 only 25 | AsyncUdpSocketIPv6Unavailable, // Used if you bind/connect using IPv4 only (or iPhone) 26 | AsyncUdpSocketSendTimeoutError, 27 | AsyncUdpSocketReceiveTimeoutError 28 | }; 29 | typedef enum AsyncUdpSocketError AsyncUdpSocketError; 30 | 31 | @interface AsyncUdpSocket : NSObject 32 | { 33 | CFSocketRef theSocket4; // IPv4 socket 34 | CFSocketRef theSocket6; // IPv6 socket 35 | 36 | CFRunLoopSourceRef theSource4; // For theSocket4 37 | CFRunLoopSourceRef theSource6; // For theSocket6 38 | CFRunLoopRef theRunLoop; 39 | CFSocketContext theContext; 40 | NSArray *theRunLoopModes; 41 | 42 | NSMutableArray *theSendQueue; 43 | AsyncSendPacket *theCurrentSend; 44 | NSTimer *theSendTimer; 45 | 46 | NSMutableArray *theReceiveQueue; 47 | AsyncReceivePacket *theCurrentReceive; 48 | NSTimer *theReceiveTimer; 49 | 50 | id theDelegate; 51 | UInt16 theFlags; 52 | 53 | long theUserData; 54 | 55 | NSString *cachedLocalHost; 56 | UInt16 cachedLocalPort; 57 | 58 | NSString *cachedConnectedHost; 59 | UInt16 cachedConnectedPort; 60 | 61 | UInt32 maxReceiveBufferSize; 62 | } 63 | 64 | /** 65 | * Creates new instances of AsyncUdpSocket. 66 | **/ 67 | - (id)init; 68 | - (id)initWithDelegate:(id)delegate; 69 | - (id)initWithDelegate:(id)delegate userData:(long)userData; 70 | 71 | /** 72 | * Creates new instances of AsyncUdpSocket that support only IPv4 or IPv6. 73 | * The other init methods will support both, unless specifically binded or connected to one protocol. 74 | * If you know you'll only be using one protocol, these init methods may be a bit more efficient. 75 | **/ 76 | - (id)initIPv4; 77 | - (id)initIPv6; 78 | 79 | - (id)delegate; 80 | - (void)setDelegate:(id)delegate; 81 | 82 | - (long)userData; 83 | - (void)setUserData:(long)userData; 84 | 85 | /** 86 | * Returns the local address info for the socket. 87 | * 88 | * Note: Address info may not be available until after the socket has been bind'ed, 89 | * or until after data has been sent. 90 | **/ 91 | - (NSString *)localHost; 92 | - (UInt16)localPort; 93 | 94 | /** 95 | * Returns the remote address info for the socket. 96 | * 97 | * Note: Since UDP is connectionless by design, connected address info 98 | * will not be available unless the socket is explicitly connected to a remote host/port 99 | **/ 100 | - (NSString *)connectedHost; 101 | - (UInt16)connectedPort; 102 | 103 | /** 104 | * Returns whether or not this socket has been connected to a single host. 105 | * By design, UDP is a connectionless protocol, and connecting is not needed. 106 | * If connected, the socket will only be able to send/receive data to/from the connected host. 107 | **/ 108 | - (BOOL)isConnected; 109 | 110 | /** 111 | * Returns whether or not this socket has been closed. 112 | * The only way a socket can be closed is if you explicitly call one of the close methods. 113 | **/ 114 | - (BOOL)isClosed; 115 | 116 | /** 117 | * Returns whether or not this socket supports IPv4. 118 | * By default this will be true, unless the socket is specifically initialized as IPv6 only, 119 | * or is binded or connected to an IPv6 address. 120 | **/ 121 | - (BOOL)isIPv4; 122 | 123 | /** 124 | * Returns whether or not this socket supports IPv6. 125 | * By default this will be true, unless the socket is specifically initialized as IPv4 only, 126 | * or is binded or connected to an IPv4 address. 127 | * 128 | * This method will also return false on platforms that do not support IPv6. 129 | * Note: The iPhone does not currently support IPv6. 130 | **/ 131 | - (BOOL)isIPv6; 132 | 133 | /** 134 | * Returns the mtu of the socket. 135 | * If unknown, returns zero. 136 | * 137 | * Sending data larger than this may result in an error. 138 | * This is an advanced topic, and one should understand the wide range of mtu's on networks and the internet. 139 | * Therefore this method is only for reference and may be of little use in many situations. 140 | **/ 141 | - (unsigned int)maximumTransmissionUnit; 142 | 143 | /** 144 | * Binds the UDP socket to the given port and optional address. 145 | * Binding should be done for server sockets that receive data prior to sending it. 146 | * Client sockets can skip binding, 147 | * as the OS will automatically assign the socket an available port when it starts sending data. 148 | * 149 | * You cannot bind a socket after its been connected. 150 | * You can only bind a socket once. 151 | * You can still connect a socket (if desired) after binding. 152 | * 153 | * On success, returns YES. 154 | * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass nil for errPtr. 155 | **/ 156 | - (BOOL)bindToPort:(UInt16)port error:(NSError **)errPtr; 157 | - (BOOL)bindToAddress:(NSString *)localAddr port:(UInt16)port error:(NSError **)errPtr; 158 | 159 | /** 160 | * Connects the UDP socket to the given host and port. 161 | * By design, UDP is a connectionless protocol, and connecting is not needed. 162 | * 163 | * Choosing to connect to a specific host/port has the following effect: 164 | * - You will only be able to send data to the connected host/port. 165 | * - You will only be able to receive data from the connected host/port. 166 | * - You will receive ICMP messages that come from the connected host/port, such as "connection refused". 167 | * 168 | * Connecting a UDP socket does not result in any communication on the socket. 169 | * It simply changes the internal state of the socket. 170 | * 171 | * You cannot bind a socket after its been connected. 172 | * You can only connect a socket once. 173 | * 174 | * On success, returns YES. 175 | * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass nil for errPtr. 176 | **/ 177 | - (BOOL)connectToHost:(NSString *)host onPort:(UInt16)port error:(NSError **)errPtr; 178 | - (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr; 179 | 180 | /** 181 | * Join multicast group 182 | * 183 | * Group should be an IP address (eg @"225.228.0.1") 184 | **/ 185 | - (BOOL)joinMulticastGroup:(NSString *)group error:(NSError **)errPtr; 186 | - (BOOL)joinMulticastGroup:(NSString *)group withAddress:(NSString *)interface error:(NSError **)errPtr; 187 | 188 | /** 189 | * By default, the underlying socket in the OS will not allow you to send broadcast messages. 190 | * In order to send broadcast messages, you need to enable this functionality in the socket. 191 | * 192 | * A broadcast is a UDP message to addresses like "192.168.255.255" or "255.255.255.255" that is 193 | * delivered to every host on the network. 194 | * The reason this is generally disabled by default is to prevent 195 | * accidental broadcast messages from flooding the network. 196 | **/ 197 | - (BOOL)enableBroadcast:(BOOL)flag error:(NSError **)errPtr; 198 | 199 | /** 200 | * Asynchronously sends the given data, with the given timeout and tag. 201 | * 202 | * This method may only be used with a connected socket. 203 | * 204 | * If data is nil or zero-length, this method does nothing and immediately returns NO. 205 | * If the socket is not connected, this method does nothing and immediately returns NO. 206 | **/ 207 | - (BOOL)sendData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag; 208 | 209 | /** 210 | * Asynchronously sends the given data, with the given timeout and tag, to the given host and port. 211 | * 212 | * This method cannot be used with a connected socket. 213 | * 214 | * If data is nil or zero-length, this method does nothing and immediately returns NO. 215 | * If the socket is connected, this method does nothing and immediately returns NO. 216 | * If unable to resolve host to a valid IPv4 or IPv6 address, this method returns NO. 217 | **/ 218 | - (BOOL)sendData:(NSData *)data toHost:(NSString *)host port:(UInt16)port withTimeout:(NSTimeInterval)timeout tag:(long)tag; 219 | 220 | /** 221 | * Asynchronously sends the given data, with the given timeout and tag, to the given address. 222 | * 223 | * This method cannot be used with a connected socket. 224 | * 225 | * If data is nil or zero-length, this method does nothing and immediately returns NO. 226 | * If the socket is connected, this method does nothing and immediately returns NO. 227 | **/ 228 | - (BOOL)sendData:(NSData *)data toAddress:(NSData *)remoteAddr withTimeout:(NSTimeInterval)timeout tag:(long)tag; 229 | 230 | /** 231 | * Asynchronously receives a single datagram packet. 232 | * 233 | * If the receive succeeds, the onUdpSocket:didReceiveData:fromHost:port:tag delegate method will be called. 234 | * Otherwise, a timeout will occur, and the onUdpSocket:didNotReceiveDataWithTag: delegate method will be called. 235 | **/ 236 | - (void)receiveWithTimeout:(NSTimeInterval)timeout tag:(long)tag; 237 | 238 | /** 239 | * Closes the socket immediately. Any pending send or receive operations are dropped. 240 | **/ 241 | - (void)close; 242 | 243 | /** 244 | * Closes after all pending send operations have completed. 245 | * After calling this, the sendData: and receive: methods will do nothing. 246 | * In other words, you won't be able to add any more send or receive operations to the queue. 247 | * The socket will close even if there are still pending receive operations. 248 | **/ 249 | - (void)closeAfterSending; 250 | 251 | /** 252 | * Closes after all pending receive operations have completed. 253 | * After calling this, the sendData: and receive: methods will do nothing. 254 | * In other words, you won't be able to add any more send or receive operations to the queue. 255 | * The socket will close even if there are still pending send operations. 256 | **/ 257 | - (void)closeAfterReceiving; 258 | 259 | /** 260 | * Closes after all pending send and receive operations have completed. 261 | * After calling this, the sendData: and receive: methods will do nothing. 262 | * In other words, you won't be able to add any more send or receive operations to the queue. 263 | **/ 264 | - (void)closeAfterSendingAndReceiving; 265 | 266 | /** 267 | * Gets/Sets the maximum size of the buffer that will be allocated for receive operations. 268 | * The default size is 9216 bytes. 269 | * 270 | * The theoretical maximum size of any IPv4 UDP packet is UINT16_MAX = 65535. 271 | * The theoretical maximum size of any IPv6 UDP packet is UINT32_MAX = 4294967295. 272 | * 273 | * In practice, however, the size of UDP packets will be much smaller. 274 | * Indeed most protocols will send and receive packets of only a few bytes, 275 | * or will set a limit on the size of packets to prevent fragmentation in the IP layer. 276 | * 277 | * If you set the buffer size too small, the sockets API in the OS will silently discard 278 | * any extra data, and you will not be notified of the error. 279 | **/ 280 | - (UInt32)maxReceiveBufferSize; 281 | - (void)setMaxReceiveBufferSize:(UInt32)max; 282 | 283 | /** 284 | * When you create an AsyncUdpSocket, it is added to the runloop of the current thread. 285 | * So it is easiest to simply create the socket on the thread you intend to use it. 286 | * 287 | * If, however, you need to move the socket to a separate thread at a later time, this 288 | * method may be used to accomplish the task. 289 | * 290 | * This method must be called from the thread/runloop the socket is currently running on. 291 | * 292 | * Note: After calling this method, all further method calls to this object should be done from the given runloop. 293 | * Also, all delegate calls will be sent on the given runloop. 294 | **/ 295 | - (BOOL)moveToRunLoop:(NSRunLoop *)runLoop; 296 | 297 | /** 298 | * Allows you to configure which run loop modes the socket uses. 299 | * The default set of run loop modes is NSDefaultRunLoopMode. 300 | * 301 | * If you'd like your socket to continue operation during other modes, you may want to add modes such as 302 | * NSModalPanelRunLoopMode or NSEventTrackingRunLoopMode. Or you may simply want to use NSRunLoopCommonModes. 303 | * 304 | * Note: NSRunLoopCommonModes is defined in 10.5. For previous versions one can use kCFRunLoopCommonModes. 305 | **/ 306 | - (BOOL)setRunLoopModes:(NSArray *)runLoopModes; 307 | 308 | /** 309 | * Returns the current run loop modes the AsyncSocket instance is operating in. 310 | * The default set of run loop modes is NSDefaultRunLoopMode. 311 | **/ 312 | - (NSArray *)runLoopModes; 313 | 314 | @end 315 | 316 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 317 | #pragma mark - 318 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 319 | 320 | @protocol AsyncUdpSocketDelegate 321 | @optional 322 | 323 | /** 324 | * Called when the datagram with the given tag has been sent. 325 | **/ 326 | - (void)onUdpSocket:(AsyncUdpSocket *)sock didSendDataWithTag:(long)tag; 327 | 328 | /** 329 | * Called if an error occurs while trying to send a datagram. 330 | * This could be due to a timeout, or something more serious such as the data being too large to fit in a sigle packet. 331 | **/ 332 | - (void)onUdpSocket:(AsyncUdpSocket *)sock didNotSendDataWithTag:(long)tag dueToError:(NSError *)error; 333 | 334 | /** 335 | * Called when the socket has received the requested datagram. 336 | * 337 | * Due to the nature of UDP, you may occasionally receive undesired packets. 338 | * These may be rogue UDP packets from unknown hosts, 339 | * or they may be delayed packets arriving after retransmissions have already occurred. 340 | * It's important these packets are properly ignored, while not interfering with the flow of your implementation. 341 | * As an aid, this delegate method has a boolean return value. 342 | * If you ever need to ignore a received packet, simply return NO, 343 | * and AsyncUdpSocket will continue as if the packet never arrived. 344 | * That is, the original receive request will still be queued, and will still timeout as usual if a timeout was set. 345 | * For example, say you requested to receive data, and you set a timeout of 500 milliseconds, using a tag of 15. 346 | * If rogue data arrives after 250 milliseconds, this delegate method would be invoked, and you could simply return NO. 347 | * If the expected data then arrives within the next 250 milliseconds, 348 | * this delegate method will be invoked, with a tag of 15, just as if the rogue data never appeared. 349 | * 350 | * Under normal circumstances, you simply return YES from this method. 351 | **/ 352 | - (BOOL)onUdpSocket:(AsyncUdpSocket *)sock didReceiveData:(NSData *)data withTag:(long)tag fromHost:(NSString *)host port:(UInt16)port; 353 | 354 | /** 355 | * Called if an error occurs while trying to receive a requested datagram. 356 | * This is generally due to a timeout, but could potentially be something else if some kind of OS error occurred. 357 | **/ 358 | - (void)onUdpSocket:(AsyncUdpSocket *)sock didNotReceiveDataWithTag:(long)tag dueToError:(NSError *)error; 359 | 360 | /** 361 | * Called when the socket is closed. 362 | * A socket is only closed if you explicitly call one of the close methods. 363 | **/ 364 | - (void)onUdpSocketDidClose:(AsyncUdpSocket *)sock; 365 | 366 | @end 367 | -------------------------------------------------------------------------------- /Icons/AirPlay Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nto/AirSpeaker/09fe7762235b0809ddca0007cb1dd236faf52700/Icons/AirPlay Icon.png -------------------------------------------------------------------------------- /Icons/AirSpeaker Large Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nto/AirSpeaker/09fe7762235b0809ddca0007cb1dd236faf52700/Icons/AirSpeaker Large Icon.png -------------------------------------------------------------------------------- /Icons/AirSpeaker iPad Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nto/AirSpeaker/09fe7762235b0809ddca0007cb1dd236faf52700/Icons/AirSpeaker iPad Icon.png -------------------------------------------------------------------------------- /Icons/AirSpeaker iPhone Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nto/AirSpeaker/09fe7762235b0809ddca0007cb1dd236faf52700/Icons/AirSpeaker iPhone Icon.png -------------------------------------------------------------------------------- /Icons/AirSpeaker iPhone Retina Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nto/AirSpeaker/09fe7762235b0809ddca0007cb1dd236faf52700/Icons/AirSpeaker iPhone Retina Icon.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Clément Vasseur 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | Redistributions in binary form must reproduce the above copyright notice, this 11 | list of conditions and the following disclaimer in the documentation and/or 12 | other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | AirSpeaker - AirPlay audio player for iOS devices 2 | Copyright (c) 2011 Clément Vasseur 3 | 4 | Use this application to listen to AirPlay audio streams from another iOS device 5 | (iPhone, iPod Touch or iPad running iOS 4.2) on the same local network, or from 6 | iTunes. 7 | 8 | Just launch AirSpeaker, then tap the AirPlay icon on the audio player from 9 | another iPhone for example, and the audio should start playing on AirSpeaker. 10 | This works with audio played in iTunes on a Mac or PC too. 11 | 12 | This app uses the same "AirTunes" protocol used in Apple's Airport Express, 13 | also known as RAOP (Remote Audio Output Protocol). 14 | 15 | 16 | TODO 17 | ==== 18 | 19 | * audio synchronization 20 | * audio packet retransmission 21 | * audio multitasking 22 | * check memory leaks 23 | 24 | 25 | Copyright 26 | ========= 27 | 28 | AirSpeaker is under the Simplified BSD License. 29 | http://github.com/nto/AirSpeaker/ 30 | 31 | The CocoaAsyncSocket project is under Public Domain license. 32 | http://code.google.com/p/cocoaasyncsocket/ 33 | --------------------------------------------------------------------------------