Skip to content

Commit

Permalink
Improve dbus and add some dacp services
Browse files Browse the repository at this point in the history
  • Loading branch information
mikebrady committed Nov 12, 2017
1 parent 532c77e commit 3198ec4
Show file tree
Hide file tree
Showing 13 changed files with 557 additions and 149 deletions.
2 changes: 1 addition & 1 deletion Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ shairport_sync_SOURCES += mdns_dns_sd.c
endif

if USE_DBUS
shairport_sync_SOURCES += dbus/src/dbus_service.c dbus/src/shairportsync.c
shairport_sync_SOURCES += dbus/src/dbus_service.c dbus/src/shairportsync.c dacp.c
endif

install-exec-hook:
Expand Down
2 changes: 1 addition & 1 deletion common.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
#define _COMMON_H

#include <libconfig.h>
#include <signal.h>
#include <stdint.h>
#include <sys/socket.h>
#include <signal.h>

#include "audio.h"
#include "config.h"
Expand Down
2 changes: 1 addition & 1 deletion configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# Process this file with autoconf to produce a configure script.

AC_PREREQ([2.50])
AC_INIT([shairport-sync], [3.2d10], [mikebrady@eircom.net])
AC_INIT([shairport-sync], [3.2d11], [mikebrady@eircom.net])
AM_INIT_AUTOMAKE
AC_CONFIG_SRCDIR([shairport.c])
AC_CONFIG_HEADERS([config.h])
Expand Down
310 changes: 310 additions & 0 deletions dacp.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,310 @@
/*
* DACP protocol handler. This file is part of Shairport Sync.
* Copyright (c) Mike Brady
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/

#include "dacp.h"
#include "common.h"
#include "config.h"

#include <arpa/inet.h>
#include <errno.h>
#include <math.h>
#include <memory.h>
#include <netdb.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <unistd.h>

uint32_t dacp_tlv_crawl(char **p, int32_t *length) {
char typecode[5];
memcpy(typecode, *p, 4);
typecode[4] = '\0';
uint32_t type = ntohl(*(uint32_t *)*p);
*p += 4;
*length = ntohl(*(int32_t *)*p);
*p += 4 + *length;
// debug(1,"Type seen: '%s' of length %d",typecode,*length);
return type;
}

ssize_t dacp_send_client_command(rtsp_conn_info *conn, const char *command, char *response,
size_t max_response_length) {
ssize_t reply_size = -1;
if (conn->rtp_running) {
if (conn->dacp_port == 0) {
debug(1, "Can't send a remote request: no valid active remote.");
} else {

struct addrinfo hints, *res;
int sockfd;

char message[20000], server_reply[2000], portstring[10], server[256];
memset(&message, 0, sizeof(message));
if ((response) && (max_response_length))
memset(response, 0, max_response_length);
else
memset(&server_reply, 0, sizeof(server_reply));
memset(&portstring, 0, sizeof(portstring));

if (conn->connection_ip_family == AF_INET6) {
sprintf(server, "%s%%%u", conn->client_ip_string, conn->self_scope_id);
} else {
strcpy(server, conn->client_ip_string);
}

sprintf(portstring, "%u", conn->dacp_port);

// first, load up address structs with getaddrinfo():

memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;

getaddrinfo(server, portstring, &hints, &res);

// make a socket:

sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);

if (sockfd == -1) {
debug(1, "Could not create socket");
} else {

// connect!

if (connect(sockfd, res->ai_addr, res->ai_addrlen) < 0) {
debug(1, "connect failed. Error");
} else {

sprintf(message,
"GET /ctrl-int/1/%s HTTP/1.1\r\nHost: %s:%u\r\nActive-Remote: %u\r\n\r\n",
command, conn->client_ip_string, conn->dacp_port, conn->dacp_active_remote);

// Send command

if (send(sockfd, message, strlen(message), 0) < 0) {
debug(1, "Send failed");
}

// Receive a reply from the server
if ((response) && (max_response_length))
reply_size = recv(sockfd, response, max_response_length, 0);
else
reply_size = recv(sockfd, server_reply, sizeof(server_reply), 0);
if (reply_size < 0) {
debug(1, "recv failed");
}
close(sockfd);
}
}
}
} else {
debug(1, "Request to pause non-existent play stream -- ignored.");
}
return reply_size;
}

int32_t dacp_get_client_volume(rtsp_conn_info *conn) {
char server_reply[2000];
int32_t overall_volume = -1;

ssize_t reply_size = dacp_send_client_command(conn, "getproperty?properties=dmcp.volume",
server_reply, sizeof(server_reply));
if (reply_size >= 0) {
if (strstr(server_reply, "HTTP/1.1 200") == server_reply) { // if we get an okay
char *sp = strstr(server_reply, "Content-Length: ");
if (sp) { // there is something there
sp += strlen("Content-Length: ");
int le = atoi(sp);
if (le == 32) {
sp = strstr(sp, "\r") + 4 + 28;
uint32_t *np = (uint32_t *)sp;
overall_volume = ntohl(*np);
// debug(1,"Overall Volume is %d.",overall_volume);
} else {
debug(1, "Can't find the volume tag");
}
} else {
debug(1, "Can't find any content in volume control request");
}
} else {
debug(1, "Unexpected response to dacp volume control request");
}
} else {
debug(1, "Error asking for dacp volume.");
}
return overall_volume;
}

int dacp_set_include_speaker_volume(rtsp_conn_info *conn, int64_t machine_number, int32_t vo) {
char server_reply[2000];
int reply = -1; // will bve fixed if there is no problem
char message[1000];
memset(message, 0, sizeof(message));
sprintf(message, "setproperty?include-speaker-id=%ld&dmcp.volume=%d", machine_number, vo);
// debug(1,"sending \"%s\"",message);
ssize_t reply_size = dacp_send_client_command(conn, message, server_reply, sizeof(server_reply));
if (reply_size >= 0) {
if (strstr(server_reply, "HTTP/1.1 204") == server_reply) {
// debug(1,"dacp_set_include_speaker_volume successful.");
reply = 0;
}
} else {
debug(1, "dacp_set_include_speaker_volume unsuccessful.");
}
return reply;
}

int dacp_set_speaker_volume(rtsp_conn_info *conn, int64_t machine_number, int32_t vo) {
char server_reply[2000];
int reply = -1; // will bve fixed if there is no problem
char message[1000];
memset(message, 0, sizeof(message));
sprintf(message, "setproperty?speaker-id=%ld&dmcp.volume=%d", machine_number, vo);
// debug(1,"sending \"%s\"",message);
ssize_t reply_size = dacp_send_client_command(conn, message, server_reply, sizeof(server_reply));
if (reply_size >= 0) {
if (strstr(server_reply, "HTTP/1.1 204") == server_reply) {
// debug(1,"dacp_set_speaker_volume successful.");
reply = 0;
}
} else {
debug(1, "dacp_set_speaker_volume unsuccessful.");
}
return reply;
}

int dacp_get_speaker_list(rtsp_conn_info *conn, dacp_spkr_stuff *speaker_info,
int max_size_of_array) {
char server_reply[2000];
int speaker_index = -1; // will be incremented before use
int reply = -1; // will bve fixed if there is no problem
ssize_t reply_size =
dacp_send_client_command(conn, "getspeakers", server_reply, sizeof(server_reply));
if (reply_size >= 0) {
if (strstr(server_reply, "HTTP/1.1 200") == server_reply) { // if we get an okay
char *sp = strstr(server_reply, "Content-Length: ");
if (sp) { // there is something there
sp += strlen("Content-Length: ");
int32_t le = atoi(sp);
int32_t item_size = 0;
sp = strstr(sp, "\r") + 4;
if (dacp_tlv_crawl(&sp, &item_size) == 'casp') {
// debug(1,"Speakers:",item_size);
sp -= item_size; // drop down into the array -- don't skip over it
le -= 8;
while (le >= 8) {
uint32_t type = dacp_tlv_crawl(&sp, &item_size);
if (type == 'mdcl') { // drop down into the dictionary -- don't skip over it
// debug(1,">>>> Dictionary:");
sp -= item_size;
le -= 8;
speaker_index++;
if (speaker_index == max_size_of_array)
return -1; // too many speakers
speaker_info[speaker_index].active = 0;
speaker_info[speaker_index].speaker_number = 0;
speaker_info[speaker_index].volume = 0;
speaker_info[speaker_index].name = NULL;
} else {
le -= item_size + 8;
char *t;
char u;
int32_t r;
int64_t s, v;
switch (type) {
case 'minm':
t = sp - item_size;
speaker_info[speaker_index].name = strndup(t, item_size);
// debug(1," \"%s\"",speaker_info[speaker_index].name);
break;
/*
case 'cads':
t = sp-item_size;
r = ntohl(*(int32_t*)(t));
//debug(1,"CADS: \"%d\".",r);
break;
*/
case 'cmvo':
t = sp - item_size;
r = ntohl(*(int32_t *)(t));
speaker_info[speaker_index].volume = r;
// debug(1,"Volume: \"%d\".",r);
break;
case 'msma':
t = sp - item_size;
s = ntohl(*(uint32_t *)(t));
s = s << 32;
t += 4;
v = (ntohl(*(uint32_t *)(t))) & 0xffffffff;
s += v;
speaker_info[speaker_index].speaker_number = s;
// debug(1,"Speaker machine number: %ld",s);
break;

case 'caia':
speaker_info[speaker_index].active = 1;
break;
/*
case 'caip':
case 'cavd':
case 'caiv':
t = sp-item_size;
u = *t;
//debug(1,"Value: \"%d\".",u);
break;
*/
default:
break;
}
}
}
// debug(1,"Total of %d speakers found. Here are the active ones:",speaker_index+1);
reply = speaker_index + 1; // number of speaker entries in the array
} else {
debug(1, "Speaker array not found.");
}
/*
int i;
for (i=0;i<le;i++) {
if (*sp < ' ')
debug(1,"%d %02x", i, *sp);
else
debug(1,"%d %02x '%c'", i, *sp,*sp);
sp++;
}
*/

} else {
debug(1, "Can't find any content in dacp speakers request");
}
} else {
debug(1, "Unexpected response to dacp speakers request");
}
} else {
debug(1, "Error asking for dacp speakers.");
}
return reply;
}
24 changes: 24 additions & 0 deletions dacp.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#pragma once
#include "common.h"
#include "config.h"
#include <sys/socket.h>

#include "player.h"

typedef struct dacp_speaker_stuff {
int64_t speaker_number;
int active;
int32_t volume;
char *name; // this is really just for debugging
} dacp_spkr_stuff;

uint32_t dacp_tlv_crawl(
char **p,
int32_t *length); // return the code of the next TLV entity and advance the pointer beyond it.
ssize_t dacp_send_client_command(rtsp_conn_info *conn, const char *command, char *response,
size_t max_response_length);
int32_t dacp_get_client_volume(rtsp_conn_info *conn); // return the overall volume from the client
int dacp_set_include_speaker_volume(rtsp_conn_info *conn, int64_t machine_number, int32_t vo);
int dacp_set_speaker_volume(rtsp_conn_info *conn, int64_t machine_number, int32_t vo);
int dacp_get_speaker_list(rtsp_conn_info *conn, dacp_spkr_stuff *speaker_array,
int max_size_of_array);
Loading

0 comments on commit 3198ec4

Please sign in to comment.