mdns_airplay.c 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. #include "esp_log.h"
  2. #include "esp_mac.h"
  3. #include "mdns.h"
  4. #include <stdio.h>
  5. #include <string.h>
  6. #include "hap.h"
  7. #include "mdns_airplay.h"
  8. #include "rtsp_handlers.h"
  9. #include "wifi.h"
  10. #include "settings.h"
  11. static const char *TAG = "mdns_airplay";
  12. // Feature flags are defined in rtsp_handlers.h (shared with /info handler)
  13. // Protocol version
  14. #ifdef CONFIG_AIRPLAY_FORCE_V1
  15. #define AIRPLAY_PROTOCOL_VERSION "1"
  16. #else
  17. #define AIRPLAY_PROTOCOL_VERSION "2"
  18. #endif
  19. #define AIRPLAY_SOURCE_VERSION "377.40.00"
  20. // Flags: 0x4 = audio receiver
  21. #define AIRPLAY_FLAGS "0x4"
  22. // Metadata types advertised in the "md" txt record:
  23. // 0 = text (track title/artist/album), 1 = artwork (cover art images),
  24. // 2 = progress.
  25. // When artwork is disabled, drop "1" so senders do not transmit cover art
  26. // (which can stall the audio pipeline and cause drop-outs on realtime
  27. // streams) while still sending text and progress metadata.
  28. #ifdef CONFIG_ENABLE_AIRPLAY_ARTWORK
  29. #define AIRPLAY_METADATA_TYPES "0,1,2"
  30. #else
  31. #define AIRPLAY_METADATA_TYPES "0,2"
  32. #endif
  33. // Model identifier - AudioAccessory for speaker appearance
  34. // AppleTV3,2 = Apple TV, AudioAccessory5,1 = HomePod mini (speaker)
  35. #define AIRPLAY_MODEL "AudioAccessory5,1"
  36. void mdns_airplay_init(void) {
  37. char mac_str[18];
  38. char device_id[18];
  39. char features_str[32];
  40. char service_name[80];
  41. char pk_str[65]; // 32 bytes = 64 hex chars + null
  42. char device_name[65];
  43. // Get device name from settings
  44. settings_get_device_name(device_name, sizeof(device_name));
  45. // Get MAC address
  46. wifi_get_mac_str(mac_str, sizeof(mac_str));
  47. strncpy(device_id, mac_str, sizeof(device_id));
  48. // Get real Ed25519 public key from HAP module
  49. const uint8_t *pk = hap_get_public_key();
  50. for (int i = 0; i < 32; i++) {
  51. snprintf(pk_str + (size_t)i * 2, 3, "%02x", pk[i]);
  52. }
  53. // Format features as "hi,lo" hex string
  54. snprintf(features_str, sizeof(features_str), "0x%X,0x%X", AIRPLAY_FEATURES_LO,
  55. AIRPLAY_FEATURES_HI);
  56. // Create service name for RAOP: <mac>@<name>
  57. uint8_t mac[6];
  58. esp_read_mac(mac, ESP_MAC_WIFI_STA);
  59. snprintf(service_name, sizeof(service_name), "%02X%02X%02X%02X%02X%02X@%s",
  60. mac[0], mac[1], mac[2], mac[3], mac[4], mac[5], device_name);
  61. // Initialize mDNS
  62. ESP_ERROR_CHECK(mdns_init());
  63. // Set hostname
  64. ESP_ERROR_CHECK(mdns_hostname_set(device_name));
  65. #ifndef CONFIG_AIRPLAY_FORCE_V1
  66. // ========================================
  67. // _airplay._tcp service (port 7000)
  68. // Only registered for AirPlay 2 mode
  69. // ========================================
  70. mdns_txt_item_t airplay_txt[] = {
  71. {"deviceid", device_id},
  72. {"features", features_str},
  73. {"flags", AIRPLAY_FLAGS},
  74. {"model", AIRPLAY_MODEL},
  75. {"pk", pk_str},
  76. {"pi", "00000000-0000-0000-0000-000000000000"}, // Pairing identity UUID
  77. {"srcvers", AIRPLAY_SOURCE_VERSION},
  78. {"vv", AIRPLAY_PROTOCOL_VERSION},
  79. {"acl", "0"},
  80. };
  81. esp_err_t err =
  82. mdns_service_add(device_name, "_airplay", "_tcp", 7000, airplay_txt,
  83. sizeof(airplay_txt) / sizeof(airplay_txt[0]));
  84. if (err != ESP_OK) {
  85. ESP_LOGE(TAG, "Failed to add _airplay._tcp service: %s",
  86. esp_err_to_name(err));
  87. }
  88. #endif
  89. // ========================================
  90. // _raop._tcp service (port 7000)
  91. // RAOP = Remote Audio Output Protocol
  92. // Service name format: <MAC>@<DeviceName>
  93. // ========================================
  94. #ifdef CONFIG_AIRPLAY_FORCE_V1
  95. // AirPlay v1 (classic RAOP): match squeezelite-esp32 txt record format.
  96. // No features, no pk, no HAP pairing — just classic RAOP fields.
  97. mdns_txt_item_t raop_txt[] = {
  98. {"am", AIRPLAY_MODEL},
  99. {"tp", "UDP"}, // Transport protocol
  100. {"sm", "false"}, // Sharing mode
  101. {"sv", "false"}, // Server version (unused)
  102. {"ek", "1"}, // Encryption key available
  103. {"et", "0,1"}, // Encryption types: none, RSA
  104. {"md", AIRPLAY_METADATA_TYPES}, // Metadata types
  105. {"cn", "0,1"}, // Audio codecs: PCM, ALAC
  106. {"ch", "2"}, // Channels
  107. {"ss", "16"}, // Sample size (bits)
  108. {"sr", "44100"}, // Sample rate
  109. {"vn", "3"}, // Version number
  110. {"txtvers", "1"}, // TXT record version
  111. };
  112. #else
  113. // Dual-mode: include et=1 (RSA) so RAOP-only clients (TuneBlade, AirMusic,
  114. // shairtunes2, etc.) accept the advertisement, while keeping et=3,5 for
  115. // AirPlay 2 FairPlay/MFi-SAP. ek=1 advertises that an RSA-encrypted key
  116. // can be supplied via SDP rsaaeskey: at ANNOUNCE time.
  117. mdns_txt_item_t raop_txt[] = {
  118. {"am", AIRPLAY_MODEL},
  119. {"cn", "0,1,2,3"}, // Audio codecs: PCM, ALAC, AAC, AAC-ELD
  120. {"da", "true"}, // Digest auth
  121. {"ek", "1"}, // Encryption key available (RSA)
  122. {"et", "0,1,3,5"}, // Encryption types
  123. {"ft", features_str}, // Features (same as airplay)
  124. {"md", AIRPLAY_METADATA_TYPES}, // Metadata types
  125. {"pk", pk_str}, // Public key
  126. {"sf", AIRPLAY_FLAGS}, // Status flags
  127. {"tp", "UDP"}, // Transport protocol
  128. {"vn", "65537"}, // Version number
  129. {"vs", AIRPLAY_SOURCE_VERSION},
  130. {"vv", AIRPLAY_PROTOCOL_VERSION},
  131. };
  132. #endif
  133. esp_err_t err_raop =
  134. mdns_service_add(service_name, "_raop", "_tcp", 7000, raop_txt,
  135. sizeof(raop_txt) / sizeof(raop_txt[0]));
  136. if (err_raop != ESP_OK) {
  137. ESP_LOGE(TAG, "Failed to add _raop._tcp service: %s",
  138. esp_err_to_name(err_raop));
  139. }
  140. }