diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 7167c94f..815a6ff3 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -163,3 +163,8 @@ if ( NOT APPLE ) target_include_directories( airplay PRIVATE ${DNSSD_INCLUDE_DIR} ) endif() endif() + +pkg_check_modules (BLUETOOTH REQUIRED bluez) +include_directories(${BLUETOOTH_INCLUDE_DIRS}) +# link_directories(${BLUEZ_LIBRARY_DIRS}) +target_link_libraries( airplay PUBLIC ${BLUETOOTH_LIBRARIES} ) \ No newline at end of file diff --git a/lib/ble.c b/lib/ble.c new file mode 100644 index 00000000..dfcc58f2 --- /dev/null +++ b/lib/ble.c @@ -0,0 +1,128 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // For close() +#include +#include +#include +#include +#include +#include // For struct ifreq +#include +#include +#include + +#include "ble.h" + +#define DEVICE_NAME "hci0" + +static int open_device(char *device_name) { + int dev_id = hci_devid(device_name); + if (dev_id < 0) + dev_id = hci_get_route(NULL); // Find the device ID of the first available Bluetooth interface + + int dd = hci_open_dev(dev_id); + if (dd < 0) { + return -1; // Failed to obtain device descriptor + } + + return dd; +} + +static int send_command(uint8_t ogf, uint16_t ocf, int len, unsigned char *data) { + struct hci_filter flt; + + int dd = open_device(DEVICE_NAME); + + hci_filter_clear(&flt); + hci_filter_set_ptype(HCI_EVENT_PKT, &flt); + hci_filter_all_events(&flt); + if (setsockopt(dd, SOL_HCI, HCI_FILTER, &flt, sizeof(flt)) < 0) { + hci_close_dev(dd); + printf("HCI filter set up failed"); + return -1; + } + + if (hci_send_cmd(dd, ogf, ocf, len, data) < 0) { + hci_close_dev(dd); + printf("Failed to send BLE command to controller (usually requires elevated privileges)\n"); + return -1; + } + + hci_close_dev(dd); + return 0; +} + +static char* get_ip_addr(const char *interface) { + struct ifreq ifr; + static char ip_address[INET_ADDRSTRLEN]; + + int fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd == -1) { + return NULL; + } + + strncpy(ifr.ifr_name, interface, IFNAMSIZ - 1); + ifr.ifr_name[IFNAMSIZ - 1] = '\0'; + + if (ioctl(fd, SIOCGIFADDR, &ifr) == -1) { + perror("ioctl"); + close(fd); + return NULL; + } + + struct sockaddr_in *addr = (struct sockaddr_in *)&ifr.ifr_addr; + inet_ntop(AF_INET, &addr->sin_addr, ip_address, sizeof(ip_address)); + close(fd); + return ip_address; +} + +static int set_advertisement_data(const char *interface) { + char* ip_address = get_ip_addr(interface); + int byte1, byte2, byte3, byte4; + + sscanf(ip_address, "%d.%d.%d.%d", &byte1, &byte2, &byte3, &byte4); + + uint8_t advertisement[] = { + 0x0f, 0x02, 0x01, 0x1a, 0x0b, 0xff, 0x4c, 0x00, + 0x09, 0x06, 0x03, + 0x30, + (uint8_t)byte1, (uint8_t)byte2, (uint8_t)byte3, (uint8_t)byte4 + }; + + return send_command(0x08, 0x0008, 16, advertisement); +} + +int configure_ble(const char *interface, uint8_t *ble_address) { + uint8_t configure_cmd[] = { + 0xa0, 0x00, + 0xa0, 0x00, + 0x03, + 0x01, + 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x07, + 0x00 + }; + + if (send_command(0x08, 0x0006, 15, configure_cmd) != 0) return -1; + + if (send_command(0x08, 0x0005, 6, ble_address) != 0) return -1; + + return set_advertisement_data(interface); +} + +int ble_advertise(bool on) { + uint8_t cmd[] = { + on ? 1 : 0 + }; + return send_command(0x08, 0x000a, 1, cmd); +} + diff --git a/lib/ble.h b/lib/ble.h new file mode 100644 index 00000000..2ff71a22 --- /dev/null +++ b/lib/ble.h @@ -0,0 +1,18 @@ +#ifndef BLE_H +#define BLE_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +int configure_ble(const char *interface, uint8_t *ble_address); + +int ble_advertise(bool on); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/lib/dnssd.c b/lib/dnssd.c index 07259375..00bbbae1 100644 --- a/lib/dnssd.c +++ b/lib/dnssd.c @@ -396,6 +396,13 @@ dnssd_get_airplay_txt(dnssd_t *dnssd, int *length) return dnssd->TXTRecordGetBytesPtr(&dnssd->airplay_record); } +const char * +dnssd_get_raop_txt(dnssd_t *dnssd, int *length) +{ + *length = dnssd->TXTRecordGetLength(&dnssd->raop_record); + return dnssd->TXTRecordGetBytesPtr(&dnssd->raop_record); +} + const char * dnssd_get_name(dnssd_t *dnssd, int *length) { diff --git a/lib/dnssd.h b/lib/dnssd.h index 1f832103..64dc8752 100644 --- a/lib/dnssd.h +++ b/lib/dnssd.h @@ -44,6 +44,7 @@ DNSSD_API void dnssd_unregister_raop(dnssd_t *dnssd); DNSSD_API void dnssd_unregister_airplay(dnssd_t *dnssd); DNSSD_API const char *dnssd_get_airplay_txt(dnssd_t *dnssd, int *length); +DNSSD_API const char *dnssd_get_raop_txt(dnssd_t *dnssd, int *length); DNSSD_API const char *dnssd_get_name(dnssd_t *dnssd, int *length); DNSSD_API const char *dnssd_get_hw_addr(dnssd_t *dnssd, int *length); DNSSD_API void dnssd_set_airplay_features(dnssd_t *dnssd, int bit, int val); diff --git a/lib/raop.c b/lib/raop.c index af14baf7..eab4c92d 100644 --- a/lib/raop.c +++ b/lib/raop.c @@ -209,7 +209,7 @@ conn_request(void *ptr, http_request_t *request, http_response_t **response) { } /* this rejects unsupported messages from _airplay._tcp for video streaming protocol*/ - if (!cseq) { + if (!cseq && strstr(url, "/info") == NULL) { return; } @@ -255,7 +255,7 @@ conn_request(void *ptr, http_request_t *request, http_response_t **response) { logger_log(conn->raop->logger, LOGGER_DEBUG, "Handling request %s with URL %s", method, url); raop_handler_t handler = NULL; - if (!strcmp(method, "GET") && !strcmp(url, "/info")) { + if (!strcmp(method, "GET") && strstr(url, "/info") != NULL) { handler = &raop_handler_info; } else if (!strcmp(method, "POST") && !strcmp(url, "/pair-pin-start")) { handler = &raop_handler_pairpinstart; @@ -292,7 +292,9 @@ conn_request(void *ptr, http_request_t *request, http_response_t **response) { } finish:; http_response_add_header(*response, "Server", "AirTunes/"GLOBAL_VERSION); - http_response_add_header(*response, "CSeq", cseq); + if (cseq) { + http_response_add_header(*response, "CSeq", cseq); + } http_response_finish(*response, response_data, response_datalen); int len; diff --git a/lib/raop_handlers.h b/lib/raop_handlers.h index 6493c333..6418b3ec 100644 --- a/lib/raop_handlers.h +++ b/lib/raop_handlers.h @@ -59,6 +59,11 @@ raop_handler_info(raop_conn_t *conn, plist_t txt_airplay_node = plist_new_data(airplay_txt, airplay_txt_len); plist_dict_set_item(res_node, "txtAirPlay", txt_airplay_node); + int raop_txt_len = 0; + const char *raop_txt = dnssd_get_raop_txt(conn->raop->dnssd, &raop_txt_len); + plist_t txt_raop_node = plist_new_data(raop_txt, raop_txt_len); + plist_dict_set_item(res_node, "txtRAOP", txt_raop_node); + uint64_t features = dnssd_get_airplay_features(conn->raop->dnssd); plist_t features_node = plist_new_uint(features); plist_dict_set_item(res_node, "features", features_node); diff --git a/uxplay.cpp b/uxplay.cpp index ddf7af98..d4f3872e 100644 --- a/uxplay.cpp +++ b/uxplay.cpp @@ -59,6 +59,7 @@ #include "lib/stream.h" #include "lib/logger.h" #include "lib/dnssd.h" +#include "lib/ble.h" #include "renderers/video_renderer.h" #include "renderers/audio_renderer.h" @@ -140,6 +141,8 @@ static std::vector registered_keys; static double db_low = -30.0; static double db_high = 0.0; static bool taper_volume = false; +static bool ble_advertisement = false; +static uint8_t ble_address[6] = {0}; /* logging */ @@ -633,6 +636,7 @@ static void print_info (char *name) { printf(" =1,2,..; fn=\"audiodump\"; change with \"-admp [n] filename\".\n"); printf(" x increases when audio format changes. If n is given, <= n\n"); printf(" audio packets are dumped. \"aud\"= unknown format.\n"); + printf("-btip Advertise IP Address of this receiver over Bluetooth for discovery\n"); printf("-d Enable debug logging\n"); printf("-v Displays version information\n"); printf("-h Displays this help\n"); @@ -1125,6 +1129,11 @@ static void parse_arguments (int argc, char *argv[]) { db_low = db1; db_high = db2; printf("db range %f:%f\n", db_low, db_high); + } else if (arg == "-btip") { + ble_advertisement = true; + ble_address[0] = rand() % 255; ble_address[1] = rand() % 255; + ble_address[2] = rand() % 255; ble_address[3] = rand() % 255; + ble_address[4] = rand() % 255; ble_address[5] = rand() % 255; } else { fprintf(stderr, "unknown option %s, stopping (for help use option \"-h\")\n",argv[i]); exit(1); @@ -2154,6 +2163,12 @@ int main (int argc, char *argv[]) { } restart: + if (ble_advertisement) { + if (configure_ble("eth0", ble_address) != 0 || ble_advertise(true) != 0) { + ble_advertisement = false; + LOGI("Failed to initialise BLE interface: Disabling until application restarts"); + } + } if (start_dnssd(server_hw_addr, server_name)) { goto cleanup; } @@ -2201,6 +2216,9 @@ int main (int argc, char *argv[]) { stop_dnssd(); } cleanup: + if (ble_advertisement) { + ble_advertise(false); + } if (use_audio) { audio_renderer_destroy(); }