mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-23 16:50:47 +00:00
apexsink: Add support for generation 2 AirTunes hardware
Fixes bug #649931.
This commit is contained in:
parent
a570b3d76f
commit
6e4a14d231
4 changed files with 268 additions and 38 deletions
|
@ -52,14 +52,15 @@ const static gchar GST_APEX_RAOP_RSA_PUBLIC_EXP[] = "AQAB";
|
||||||
const static gchar GST_APEX_RAOP_USER_AGENT[] =
|
const static gchar GST_APEX_RAOP_USER_AGENT[] =
|
||||||
"iTunes/4.6 (Macintosh; U; PPC Mac OS X 10.3)";
|
"iTunes/4.6 (Macintosh; U; PPC Mac OS X 10.3)";
|
||||||
|
|
||||||
const static guchar GST_APEX_RAOP_FRAME_HEADER[] = {
|
const static guchar GST_APEX_RAOP_FRAME_HEADER[] = { // Used by gen. 1
|
||||||
0x24, 0x00, 0x00, 0x00,
|
0x24, 0x00, 0x00, 0x00,
|
||||||
0xF0, 0xFF, 0x00, 0x00,
|
0xF0, 0xFF, 0x00, 0x00,
|
||||||
0x00, 0x00, 0x00, 0x00,
|
0x00, 0x00, 0x00, 0x00,
|
||||||
0x00, 0x00, 0x00, 0x00
|
0x00, 0x00, 0x00, 0x00
|
||||||
};
|
};
|
||||||
|
|
||||||
const static int GST_APEX_RAOP_FRAME_HEADER_SIZE = 16;
|
const static int GST_APEX_RAOP_FRAME_HEADER_SIZE = 16; // Used by gen. 1
|
||||||
|
const static int GST_APEX_RTP_FRAME_HEADER_SIZE = 12; // Used by gen. 2
|
||||||
|
|
||||||
const static int GST_APEX_RAOP_ALAC_HEADER_SIZE = 3;
|
const static int GST_APEX_RAOP_ALAC_HEADER_SIZE = 3;
|
||||||
|
|
||||||
|
@ -121,6 +122,9 @@ typedef struct
|
||||||
GstApExJackType jack_type; /* APEX connected jack type, once ANNOUNCE performed */
|
GstApExJackType jack_type; /* APEX connected jack type, once ANNOUNCE performed */
|
||||||
GstApExJackStatus jack_status; /* APEX connected jack status, once ANNOUNCE performed */
|
GstApExJackStatus jack_status; /* APEX connected jack status, once ANNOUNCE performed */
|
||||||
|
|
||||||
|
GstApExGeneration generation; /* Different devices accept different audio streams */
|
||||||
|
GstApExTransportProtocol transport_protocol; /* For media stream, not RAOP/RTSP */
|
||||||
|
|
||||||
gchar *host; /* APEX target ip */
|
gchar *host; /* APEX target ip */
|
||||||
guint ctrl_port; /* APEX target control port */
|
guint ctrl_port; /* APEX target control port */
|
||||||
guint data_port; /* APEX negotiated data port, once SETUP performed */
|
guint data_port; /* APEX negotiated data port, once SETUP performed */
|
||||||
|
@ -130,12 +134,18 @@ typedef struct
|
||||||
|
|
||||||
int data_sd; /* data socket */
|
int data_sd; /* data socket */
|
||||||
struct sockaddr_in data_sd_in;
|
struct sockaddr_in data_sd_in;
|
||||||
|
|
||||||
|
short rtp_seq_num; /* RTP sequence number, used by gen. 2 */
|
||||||
|
int rtp_timestamp; /* RTP timestamp, used by gen. 2 */
|
||||||
}
|
}
|
||||||
_GstApExRAOP;
|
_GstApExRAOP;
|
||||||
|
|
||||||
/* raop apex struct allocation */
|
/* raop apex struct allocation */
|
||||||
GstApExRAOP *
|
GstApExRAOP *
|
||||||
gst_apexraop_new (const gchar * host, const guint16 port)
|
gst_apexraop_new (const gchar * host,
|
||||||
|
const guint16 port,
|
||||||
|
const GstApExGeneration generation,
|
||||||
|
const GstApExTransportProtocol transport_protocol)
|
||||||
{
|
{
|
||||||
_GstApExRAOP *apexraop;
|
_GstApExRAOP *apexraop;
|
||||||
|
|
||||||
|
@ -146,6 +156,10 @@ gst_apexraop_new (const gchar * host, const guint16 port)
|
||||||
apexraop->ua = g_strdup (GST_APEX_RAOP_USER_AGENT);
|
apexraop->ua = g_strdup (GST_APEX_RAOP_USER_AGENT);
|
||||||
apexraop->jack_type = GST_APEX_JACK_TYPE_UNDEFINED;
|
apexraop->jack_type = GST_APEX_JACK_TYPE_UNDEFINED;
|
||||||
apexraop->jack_status = GST_APEX_JACK_STATUS_DISCONNECTED;
|
apexraop->jack_status = GST_APEX_JACK_STATUS_DISCONNECTED;
|
||||||
|
apexraop->generation = generation;
|
||||||
|
apexraop->transport_protocol = transport_protocol;
|
||||||
|
apexraop->rtp_seq_num = 0;
|
||||||
|
apexraop->rtp_timestamp = 0;
|
||||||
|
|
||||||
return (GstApExRAOP *) apexraop;
|
return (GstApExRAOP *) apexraop;
|
||||||
}
|
}
|
||||||
|
@ -311,7 +325,9 @@ gst_apexraop_connect (GstApExRAOP * con)
|
||||||
conn->url_abspath,
|
conn->url_abspath,
|
||||||
inaddr,
|
inaddr,
|
||||||
conn->host,
|
conn->host,
|
||||||
GST_APEX_RAOP_SAMPLES_PER_FRAME,
|
conn->generation == GST_APEX_GENERATION_ONE
|
||||||
|
? GST_APEX_RAOP_V1_SAMPLES_PER_FRAME
|
||||||
|
: GST_APEX_RAOP_V2_SAMPLES_PER_FRAME,
|
||||||
GST_APEX_RAOP_BYTES_PER_CHANNEL * 8,
|
GST_APEX_RAOP_BYTES_PER_CHANNEL * 8,
|
||||||
GST_APEX_RAOP_CHANNELS, GST_APEX_RAOP_BITRATE, ky, iv);
|
GST_APEX_RAOP_CHANNELS, GST_APEX_RAOP_BITRATE, ky, iv);
|
||||||
|
|
||||||
|
@ -451,8 +467,14 @@ gst_apexraop_connect (GstApExRAOP * con)
|
||||||
if (res != GST_RTSP_STS_OK)
|
if (res != GST_RTSP_STS_OK)
|
||||||
return res;
|
return res;
|
||||||
|
|
||||||
if ((conn->data_sd = socket (AF_INET, SOCK_STREAM, 0)) < 0)
|
if (conn->transport_protocol == GST_APEX_TCP) {
|
||||||
return GST_RTSP_STS_DESTINATION_UNREACHABLE;
|
if ((conn->data_sd = socket (AF_INET, SOCK_STREAM, 0)) < 0)
|
||||||
|
return GST_RTSP_STS_DESTINATION_UNREACHABLE;
|
||||||
|
} else if (conn->transport_protocol == GST_APEX_UDP) {
|
||||||
|
if ((conn->data_sd = socket (AF_INET, SOCK_DGRAM, 0)) < 0)
|
||||||
|
return GST_RTSP_STS_DESTINATION_UNREACHABLE;
|
||||||
|
} else
|
||||||
|
return GST_RTSP_STS_METHOD_NOT_ALLOWED;
|
||||||
|
|
||||||
conn->data_sd_in.sin_family = AF_INET;
|
conn->data_sd_in.sin_family = AF_INET;
|
||||||
conn->data_sd_in.sin_port = htons (conn->data_port);
|
conn->data_sd_in.sin_port = htons (conn->data_port);
|
||||||
|
@ -495,6 +517,34 @@ gst_apexraop_get_jackstatus (GstApExRAOP * con)
|
||||||
return conn->jack_status;
|
return conn->jack_status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* raop apex generation access */
|
||||||
|
GstApExGeneration
|
||||||
|
gst_apexraop_get_generation (GstApExRAOP * con)
|
||||||
|
{
|
||||||
|
_GstApExRAOP *conn;
|
||||||
|
|
||||||
|
conn = (_GstApExRAOP *) con;
|
||||||
|
|
||||||
|
if (!conn)
|
||||||
|
return GST_APEX_GENERATION_ONE;
|
||||||
|
|
||||||
|
return conn->generation;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* raop apex transport protocol access */
|
||||||
|
GstApExTransportProtocol
|
||||||
|
gst_apexraop_get_transport_protocol (GstApExRAOP * con)
|
||||||
|
{
|
||||||
|
_GstApExRAOP *conn;
|
||||||
|
|
||||||
|
conn = (_GstApExRAOP *) con;
|
||||||
|
|
||||||
|
if (!conn)
|
||||||
|
return GST_APEX_TCP;
|
||||||
|
|
||||||
|
return conn->transport_protocol;
|
||||||
|
}
|
||||||
|
|
||||||
/* raop apex sockets close */
|
/* raop apex sockets close */
|
||||||
void
|
void
|
||||||
gst_apexraop_close (GstApExRAOP * con)
|
gst_apexraop_close (GstApExRAOP * con)
|
||||||
|
@ -628,25 +678,49 @@ gst_apexraop_write (GstApExRAOP * con, gpointer rawdata, guint length)
|
||||||
gushort len;
|
gushort len;
|
||||||
gint bit_offset, byte_offset, i, out_len, res;
|
gint bit_offset, byte_offset, i, out_len, res;
|
||||||
EVP_CIPHER_CTX aes_ctx;
|
EVP_CIPHER_CTX aes_ctx;
|
||||||
_GstApExRAOP *conn;
|
_GstApExRAOP *conn = (_GstApExRAOP *) con;
|
||||||
|
const int frame_header_size = conn->generation == GST_APEX_GENERATION_ONE
|
||||||
conn = (_GstApExRAOP *) con;
|
? GST_APEX_RAOP_FRAME_HEADER_SIZE : GST_APEX_RTP_FRAME_HEADER_SIZE;
|
||||||
|
|
||||||
buffer =
|
buffer =
|
||||||
(guchar *) g_malloc0 (GST_APEX_RAOP_FRAME_HEADER_SIZE +
|
(guchar *) g_malloc0 (frame_header_size +
|
||||||
GST_APEX_RAOP_ALAC_HEADER_SIZE + length);
|
GST_APEX_RAOP_ALAC_HEADER_SIZE + length);
|
||||||
|
|
||||||
memcpy (buffer, GST_APEX_RAOP_FRAME_HEADER, GST_APEX_RAOP_FRAME_HEADER_SIZE);
|
if (conn->generation == GST_APEX_GENERATION_ONE) {
|
||||||
|
g_assert (frame_header_size == GST_APEX_RAOP_FRAME_HEADER_SIZE);
|
||||||
|
memcpy (buffer, GST_APEX_RAOP_FRAME_HEADER, frame_header_size);
|
||||||
|
|
||||||
len =
|
len = length + frame_header_size + GST_APEX_RAOP_ALAC_HEADER_SIZE - 4;
|
||||||
length + GST_APEX_RAOP_FRAME_HEADER_SIZE +
|
|
||||||
GST_APEX_RAOP_ALAC_HEADER_SIZE - 4;
|
buffer[2] = len >> 8;
|
||||||
buffer[2] = len >> 8;
|
buffer[3] = len & 0xff;
|
||||||
buffer[3] = len & 0xff;
|
} else {
|
||||||
|
/* Gen. 2 uses RTP-like header (RFC 3550). */
|
||||||
|
short network_seq_num;
|
||||||
|
int network_timestamp, unknown_const;
|
||||||
|
static gboolean first = TRUE;
|
||||||
|
|
||||||
|
buffer[0] = 0x80;
|
||||||
|
if (first) {
|
||||||
|
buffer[1] = 0xe0;
|
||||||
|
first = FALSE;
|
||||||
|
} else
|
||||||
|
buffer[1] = 0x60;
|
||||||
|
|
||||||
|
network_seq_num = htons (conn->rtp_seq_num++);
|
||||||
|
memcpy (buffer + 2, &network_seq_num, 2);
|
||||||
|
|
||||||
|
network_timestamp = htons (conn->rtp_timestamp);
|
||||||
|
memcpy (buffer + 4, &network_timestamp, 4);
|
||||||
|
conn->rtp_timestamp += GST_APEX_RAOP_V2_SAMPLES_PER_FRAME;
|
||||||
|
|
||||||
|
unknown_const = 0xdeadbeef;
|
||||||
|
memcpy (buffer + 8, &unknown_const, 4);
|
||||||
|
}
|
||||||
|
|
||||||
bit_offset = 0;
|
bit_offset = 0;
|
||||||
byte_offset = 0;
|
byte_offset = 0;
|
||||||
frame_data = buffer + GST_APEX_RAOP_FRAME_HEADER_SIZE;
|
frame_data = buffer + frame_header_size;
|
||||||
|
|
||||||
gst_apexraop_write_bits (frame_data, 1, 3, &bit_offset, &byte_offset); /* channels, 0 mono, 1 stereo */
|
gst_apexraop_write_bits (frame_data, 1, 3, &bit_offset, &byte_offset); /* channels, 0 mono, 1 stereo */
|
||||||
gst_apexraop_write_bits (frame_data, 0, 4, &bit_offset, &byte_offset); /* unknown */
|
gst_apexraop_write_bits (frame_data, 0, 4, &bit_offset, &byte_offset); /* unknown */
|
||||||
|
@ -673,16 +747,14 @@ gst_apexraop_write (GstApExRAOP * con, gpointer rawdata, guint length)
|
||||||
|
|
||||||
res =
|
res =
|
||||||
gst_apexraop_send (conn->data_sd, buffer,
|
gst_apexraop_send (conn->data_sd, buffer,
|
||||||
GST_APEX_RAOP_FRAME_HEADER_SIZE + GST_APEX_RAOP_ALAC_HEADER_SIZE +
|
frame_header_size + GST_APEX_RAOP_ALAC_HEADER_SIZE + length);
|
||||||
length);
|
|
||||||
|
|
||||||
g_free (buffer);
|
g_free (buffer);
|
||||||
|
|
||||||
return (guint) ((res >=
|
return (guint) ((res >=
|
||||||
(GST_APEX_RAOP_FRAME_HEADER_SIZE +
|
(frame_header_size +
|
||||||
GST_APEX_RAOP_ALAC_HEADER_SIZE)) ? (res -
|
GST_APEX_RAOP_ALAC_HEADER_SIZE)) ? (res -
|
||||||
GST_APEX_RAOP_FRAME_HEADER_SIZE -
|
frame_header_size - GST_APEX_RAOP_ALAC_HEADER_SIZE) : 0);
|
||||||
GST_APEX_RAOP_ALAC_HEADER_SIZE) : 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* raop apex buffer flush */
|
/* raop apex buffer flush */
|
||||||
|
@ -701,10 +773,13 @@ gst_apexraop_flush (GstApExRAOP * con)
|
||||||
"Client-Instance: %s\r\n"
|
"Client-Instance: %s\r\n"
|
||||||
"User-Agent: %s\r\n"
|
"User-Agent: %s\r\n"
|
||||||
"Session: %s\r\n"
|
"Session: %s\r\n"
|
||||||
"RTP-Info: seq=0;rtptime=0\r\n"
|
"RTP-Info: seq=%d;rtptime=%d\r\n"
|
||||||
"\r\n",
|
"\r\n",
|
||||||
conn->host,
|
conn->host,
|
||||||
conn->url_abspath, ++conn->cseq, conn->cid, conn->ua, conn->session);
|
conn->url_abspath,
|
||||||
|
++conn->cseq,
|
||||||
|
conn->cid,
|
||||||
|
conn->ua, conn->session, conn->rtp_seq_num, conn->rtp_timestamp);
|
||||||
|
|
||||||
if (gst_apexraop_send (conn->ctrl_sd, hreq, strlen (hreq)) <= 0)
|
if (gst_apexraop_send (conn->ctrl_sd, hreq, strlen (hreq)) <= 0)
|
||||||
return GST_RTSP_STS_GONE;
|
return GST_RTSP_STS_GONE;
|
||||||
|
|
|
@ -48,7 +48,8 @@ G_BEGIN_DECLS
|
||||||
|
|
||||||
/* raop fixed parameters */
|
/* raop fixed parameters */
|
||||||
#define GST_APEX_RAOP_BITRATE 44100
|
#define GST_APEX_RAOP_BITRATE 44100
|
||||||
#define GST_APEX_RAOP_SAMPLES_PER_FRAME 4096
|
#define GST_APEX_RAOP_V1_SAMPLES_PER_FRAME 4096
|
||||||
|
#define GST_APEX_RAOP_V2_SAMPLES_PER_FRAME 352
|
||||||
#define GST_APEX_RAOP_BYTES_PER_CHANNEL 2
|
#define GST_APEX_RAOP_BYTES_PER_CHANNEL 2
|
||||||
#define GST_APEX_RAOP_CHANNELS 2
|
#define GST_APEX_RAOP_CHANNELS 2
|
||||||
#define GST_APEX_RAOP_BYTES_PER_SAMPLE (GST_APEX_RAOP_CHANNELS * GST_APEX_RAOP_BYTES_PER_CHANNEL)
|
#define GST_APEX_RAOP_BYTES_PER_SAMPLE (GST_APEX_RAOP_CHANNELS * GST_APEX_RAOP_BYTES_PER_CHANNEL)
|
||||||
|
@ -78,13 +79,30 @@ typedef enum
|
||||||
}
|
}
|
||||||
GstApExJackStatus;
|
GstApExJackStatus;
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
GST_APEX_GENERATION_ONE = 1,
|
||||||
|
GST_APEX_GENERATION_TWO,
|
||||||
|
}
|
||||||
|
GstApExGeneration;
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
GST_APEX_TCP = 0,
|
||||||
|
GST_APEX_UDP,
|
||||||
|
}
|
||||||
|
GstApExTransportProtocol;
|
||||||
|
|
||||||
/* raop context handle */
|
/* raop context handle */
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
} GstApExRAOP;
|
} GstApExRAOP;
|
||||||
|
|
||||||
/* host might be null and port might be 0 while instanciating */
|
/* host might be null and port might be 0 while instanciating */
|
||||||
GstApExRAOP *gst_apexraop_new (const gchar * host, const guint16 port);
|
GstApExRAOP *gst_apexraop_new (const gchar * host,
|
||||||
|
const guint16 port,
|
||||||
|
const GstApExGeneration generation,
|
||||||
|
const GstApExTransportProtocol transport_protocol);
|
||||||
void gst_apexraop_free (GstApExRAOP * conn);
|
void gst_apexraop_free (GstApExRAOP * conn);
|
||||||
|
|
||||||
/* must not be connected yet while setting the host target */
|
/* must not be connected yet while setting the host target */
|
||||||
|
@ -118,6 +136,12 @@ GstRTSPStatusCode gst_apexraop_flush (GstApExRAOP * conn);
|
||||||
GstApExJackType gst_apexraop_get_jacktype (GstApExRAOP * conn);
|
GstApExJackType gst_apexraop_get_jacktype (GstApExRAOP * conn);
|
||||||
GstApExJackStatus gst_apexraop_get_jackstatus (GstApExRAOP * conn);
|
GstApExJackStatus gst_apexraop_get_jackstatus (GstApExRAOP * conn);
|
||||||
|
|
||||||
|
/* retrieve the generation */
|
||||||
|
GstApExGeneration gst_apexraop_get_generation (GstApExRAOP * conn);
|
||||||
|
|
||||||
|
/* retrieve the transport protocol */
|
||||||
|
GstApExTransportProtocol gst_apexraop_get_transport_protocol (GstApExRAOP * conn);
|
||||||
|
|
||||||
G_END_DECLS
|
G_END_DECLS
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -59,6 +59,8 @@ enum
|
||||||
APEX_PROP_VOLUME,
|
APEX_PROP_VOLUME,
|
||||||
APEX_PROP_JACK_TYPE,
|
APEX_PROP_JACK_TYPE,
|
||||||
APEX_PROP_JACK_STATUS,
|
APEX_PROP_JACK_STATUS,
|
||||||
|
APEX_PROP_GENERATION,
|
||||||
|
APEX_PROP_TRANSPORT_PROTOCOL,
|
||||||
};
|
};
|
||||||
|
|
||||||
#define DEFAULT_APEX_HOST ""
|
#define DEFAULT_APEX_HOST ""
|
||||||
|
@ -66,6 +68,8 @@ enum
|
||||||
#define DEFAULT_APEX_VOLUME 1.0
|
#define DEFAULT_APEX_VOLUME 1.0
|
||||||
#define DEFAULT_APEX_JACK_TYPE GST_APEX_JACK_TYPE_UNDEFINED
|
#define DEFAULT_APEX_JACK_TYPE GST_APEX_JACK_TYPE_UNDEFINED
|
||||||
#define DEFAULT_APEX_JACK_STATUS GST_APEX_JACK_STATUS_UNDEFINED
|
#define DEFAULT_APEX_JACK_STATUS GST_APEX_JACK_STATUS_UNDEFINED
|
||||||
|
#define DEFAULT_APEX_GENERATION GST_APEX_GENERATION_ONE
|
||||||
|
#define DEFAULT_APEX_TRANSPORT_PROTOCOL GST_APEX_TCP
|
||||||
|
|
||||||
/* genum apex jack resolution */
|
/* genum apex jack resolution */
|
||||||
GType
|
GType
|
||||||
|
@ -108,6 +112,43 @@ gst_apexsink_jacktype_get_type (void)
|
||||||
return jacktype_type;
|
return jacktype_type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GType
|
||||||
|
gst_apexsink_generation_get_type (void)
|
||||||
|
{
|
||||||
|
static GType generation_type = 0;
|
||||||
|
static GEnumValue generation[] = {
|
||||||
|
{GST_APEX_GENERATION_ONE, "generation-one",
|
||||||
|
"First generation (e.g., original AirPort Express)"},
|
||||||
|
{GST_APEX_GENERATION_TWO, "generation-two",
|
||||||
|
"Second generation (e.g., Apple TV v2)"},
|
||||||
|
{0, NULL, NULL},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!generation_type) {
|
||||||
|
generation_type = g_enum_register_static ("GstApExGeneration", generation);
|
||||||
|
}
|
||||||
|
|
||||||
|
return generation_type;
|
||||||
|
}
|
||||||
|
|
||||||
|
GType
|
||||||
|
gst_apexsink_transport_protocol_get_type (void)
|
||||||
|
{
|
||||||
|
static GType transport_protocol_type = 0;
|
||||||
|
static GEnumValue transport_protocol[] = {
|
||||||
|
{GST_APEX_TCP, "tcp", "TCP"},
|
||||||
|
{GST_APEX_UDP, "udp", "UDP"},
|
||||||
|
{0, NULL, NULL},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!transport_protocol_type) {
|
||||||
|
transport_protocol_type =
|
||||||
|
g_enum_register_static ("GstApExTransportProtocol", transport_protocol);
|
||||||
|
}
|
||||||
|
|
||||||
|
return transport_protocol_type;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static void gst_apexsink_set_property (GObject * object, guint prop_id,
|
static void gst_apexsink_set_property (GObject * object, guint prop_id,
|
||||||
const GValue * value, GParamSpec * pspec);
|
const GValue * value, GParamSpec * pspec);
|
||||||
|
@ -124,6 +165,8 @@ static gboolean gst_apexsink_unprepare (GstAudioSink * asink);
|
||||||
static guint gst_apexsink_delay (GstAudioSink * asink);
|
static guint gst_apexsink_delay (GstAudioSink * asink);
|
||||||
static void gst_apexsink_reset (GstAudioSink * asink);
|
static void gst_apexsink_reset (GstAudioSink * asink);
|
||||||
static gboolean gst_apexsink_close (GstAudioSink * asink);
|
static gboolean gst_apexsink_close (GstAudioSink * asink);
|
||||||
|
static GstStateChangeReturn gst_apexsink_change_state (GstElement * element,
|
||||||
|
GstStateChange transition);
|
||||||
|
|
||||||
/* mixer interface standard api */
|
/* mixer interface standard api */
|
||||||
static void gst_apexsink_interfaces_init (GType type);
|
static void gst_apexsink_interfaces_init (GType type);
|
||||||
|
@ -252,6 +295,9 @@ gst_apexsink_class_init (GstApExSinkClass * klass)
|
||||||
((GstAudioSinkClass *) klass)->reset = GST_DEBUG_FUNCPTR (gst_apexsink_reset);
|
((GstAudioSinkClass *) klass)->reset = GST_DEBUG_FUNCPTR (gst_apexsink_reset);
|
||||||
((GstAudioSinkClass *) klass)->close = GST_DEBUG_FUNCPTR (gst_apexsink_close);
|
((GstAudioSinkClass *) klass)->close = GST_DEBUG_FUNCPTR (gst_apexsink_close);
|
||||||
|
|
||||||
|
((GstElementClass *) klass)->change_state =
|
||||||
|
GST_DEBUG_FUNCPTR (gst_apexsink_change_state);
|
||||||
|
|
||||||
g_object_class_install_property ((GObjectClass *) klass, APEX_PROP_HOST,
|
g_object_class_install_property ((GObjectClass *) klass, APEX_PROP_HOST,
|
||||||
g_param_spec_string ("host", "Host", "AirPort Express target host",
|
g_param_spec_string ("host", "Host", "AirPort Express target host",
|
||||||
DEFAULT_APEX_HOST, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
DEFAULT_APEX_HOST, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||||
|
@ -275,6 +321,17 @@ gst_apexsink_class_init (GstApExSinkClass * klass)
|
||||||
"AirPort Express jack connection status",
|
"AirPort Express jack connection status",
|
||||||
GST_APEX_SINK_JACKSTATUS_TYPE, DEFAULT_APEX_JACK_STATUS,
|
GST_APEX_SINK_JACKSTATUS_TYPE, DEFAULT_APEX_JACK_STATUS,
|
||||||
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
|
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
|
||||||
|
g_object_class_install_property ((GObjectClass *) klass,
|
||||||
|
APEX_PROP_GENERATION, g_param_spec_enum ("generation", "Generation",
|
||||||
|
"AirPort device generation",
|
||||||
|
GST_APEX_SINK_GENERATION_TYPE, DEFAULT_APEX_GENERATION,
|
||||||
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||||
|
g_object_class_install_property ((GObjectClass *) klass,
|
||||||
|
APEX_PROP_TRANSPORT_PROTOCOL, g_param_spec_enum ("transport-protocol",
|
||||||
|
"Transport Protocol", "AirPort transport protocol",
|
||||||
|
GST_APEX_SINK_TRANSPORT_PROTOCOL_TYPE,
|
||||||
|
DEFAULT_APEX_TRANSPORT_PROTOCOL,
|
||||||
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* sink plugin instance init */
|
/* sink plugin instance init */
|
||||||
|
@ -295,6 +352,8 @@ gst_apexsink_init (GstApExSink * apexsink, GstApExSinkClass * g_class)
|
||||||
apexsink->volume = CLAMP (DEFAULT_APEX_VOLUME * 75, 0, 100);
|
apexsink->volume = CLAMP (DEFAULT_APEX_VOLUME * 75, 0, 100);
|
||||||
apexsink->gst_apexraop = NULL;
|
apexsink->gst_apexraop = NULL;
|
||||||
apexsink->tracks = g_list_append (apexsink->tracks, track);
|
apexsink->tracks = g_list_append (apexsink->tracks, track);
|
||||||
|
apexsink->clock = gst_system_clock_obtain ();
|
||||||
|
apexsink->clock_id = NULL;
|
||||||
|
|
||||||
GST_INFO_OBJECT (apexsink,
|
GST_INFO_OBJECT (apexsink,
|
||||||
"ApEx sink default initialization, target=\"%s\", port=\"%d\", volume=\"%d%%\"",
|
"ApEx sink default initialization, target=\"%s\", port=\"%d\", volume=\"%d%%\"",
|
||||||
|
@ -343,6 +402,28 @@ gst_apexsink_set_property (GObject * object, guint prop_id,
|
||||||
GST_INFO_OBJECT (sink, "ApEx volume set to \"%d%%\"", sink->volume);
|
GST_INFO_OBJECT (sink, "ApEx volume set to \"%d%%\"", sink->volume);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case APEX_PROP_GENERATION:
|
||||||
|
if (sink->gst_apexraop == NULL) {
|
||||||
|
sink->generation = g_value_get_enum (value);
|
||||||
|
|
||||||
|
GST_INFO_OBJECT (sink, "ApEx generation set to \"%d\"",
|
||||||
|
sink->generation);
|
||||||
|
} else {
|
||||||
|
GST_WARNING_OBJECT (sink,
|
||||||
|
"SET-PROPERTY : generation property may not be set when apexsink opened !");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case APEX_PROP_TRANSPORT_PROTOCOL:
|
||||||
|
if (sink->gst_apexraop == NULL) {
|
||||||
|
sink->transport_protocol = g_value_get_enum (value);
|
||||||
|
|
||||||
|
GST_INFO_OBJECT (sink, "ApEx transport protocol set to \"%d\"",
|
||||||
|
sink->transport_protocol);
|
||||||
|
} else {
|
||||||
|
GST_WARNING_OBJECT (sink,
|
||||||
|
"SET-PROPERTY : transport protocol property may not be set when apexsink opened !");
|
||||||
|
}
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||||
break;
|
break;
|
||||||
|
@ -373,6 +454,14 @@ gst_apexsink_get_property (GObject * object, guint prop_id, GValue * value,
|
||||||
g_value_set_enum (value,
|
g_value_set_enum (value,
|
||||||
gst_apexraop_get_jackstatus (sink->gst_apexraop));
|
gst_apexraop_get_jackstatus (sink->gst_apexraop));
|
||||||
break;
|
break;
|
||||||
|
case APEX_PROP_GENERATION:
|
||||||
|
g_value_set_enum (value,
|
||||||
|
gst_apexraop_get_generation (sink->gst_apexraop));
|
||||||
|
break;
|
||||||
|
case APEX_PROP_TRANSPORT_PROTOCOL:
|
||||||
|
g_value_set_enum (value,
|
||||||
|
gst_apexraop_get_transport_protocol (sink->gst_apexraop));
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||||
break;
|
break;
|
||||||
|
@ -391,6 +480,8 @@ gst_apexsink_finalise (GObject * object)
|
||||||
sink->tracks = NULL;
|
sink->tracks = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gst_object_unref (sink->clock);
|
||||||
|
|
||||||
g_free (sink->host);
|
g_free (sink->host);
|
||||||
|
|
||||||
G_OBJECT_CLASS (parent_class)->finalize (object);
|
G_OBJECT_CLASS (parent_class)->finalize (object);
|
||||||
|
@ -403,7 +494,8 @@ gst_apexsink_open (GstAudioSink * asink)
|
||||||
int res;
|
int res;
|
||||||
GstApExSink *apexsink = (GstApExSink *) asink;
|
GstApExSink *apexsink = (GstApExSink *) asink;
|
||||||
|
|
||||||
apexsink->gst_apexraop = gst_apexraop_new (apexsink->host, apexsink->port);
|
apexsink->gst_apexraop = gst_apexraop_new (apexsink->host,
|
||||||
|
apexsink->port, apexsink->generation, apexsink->transport_protocol);
|
||||||
|
|
||||||
if ((res = gst_apexraop_connect (apexsink->gst_apexraop)) != GST_RTSP_STS_OK) {
|
if ((res = gst_apexraop_connect (apexsink->gst_apexraop)) != GST_RTSP_STS_OK) {
|
||||||
GST_ERROR_OBJECT (apexsink,
|
GST_ERROR_OBJECT (apexsink,
|
||||||
|
@ -460,12 +552,14 @@ static gboolean
|
||||||
gst_apexsink_prepare (GstAudioSink * asink, GstRingBufferSpec * spec)
|
gst_apexsink_prepare (GstAudioSink * asink, GstRingBufferSpec * spec)
|
||||||
{
|
{
|
||||||
GstApExSink *apexsink = (GstApExSink *) asink;
|
GstApExSink *apexsink = (GstApExSink *) asink;
|
||||||
|
GstApExGeneration gen = gst_apexraop_get_generation (apexsink->gst_apexraop);
|
||||||
|
|
||||||
apexsink->latency_time = spec->latency_time;
|
apexsink->latency_time = spec->latency_time;
|
||||||
|
|
||||||
spec->segsize =
|
spec->segsize = gen == GST_APEX_GENERATION_ONE
|
||||||
GST_APEX_RAOP_SAMPLES_PER_FRAME * GST_APEX_RAOP_BYTES_PER_SAMPLE;
|
? GST_APEX_RAOP_V1_SAMPLES_PER_FRAME * GST_APEX_RAOP_BYTES_PER_SAMPLE
|
||||||
spec->segtotal = 1;
|
: GST_APEX_RAOP_V2_SAMPLES_PER_FRAME * GST_APEX_RAOP_BYTES_PER_SAMPLE;
|
||||||
|
spec->segtotal = 2;
|
||||||
|
|
||||||
memset (spec->silence_sample, 0, sizeof (spec->silence_sample));
|
memset (spec->silence_sample, 0, sizeof (spec->silence_sample));
|
||||||
|
|
||||||
|
@ -481,17 +575,28 @@ gst_apexsink_prepare (GstAudioSink * asink, GstRingBufferSpec * spec)
|
||||||
static guint
|
static guint
|
||||||
gst_apexsink_write (GstAudioSink * asink, gpointer data, guint length)
|
gst_apexsink_write (GstAudioSink * asink, gpointer data, guint length)
|
||||||
{
|
{
|
||||||
|
guint written;
|
||||||
GstApExSink *apexsink = (GstApExSink *) asink;
|
GstApExSink *apexsink = (GstApExSink *) asink;
|
||||||
|
|
||||||
if (gst_apexraop_write (apexsink->gst_apexraop, data, length) != length) {
|
if ((written =
|
||||||
|
gst_apexraop_write (apexsink->gst_apexraop, data,
|
||||||
|
length)) != length) {
|
||||||
GST_INFO_OBJECT (apexsink,
|
GST_INFO_OBJECT (apexsink,
|
||||||
"WRITE : %d bytes not fully sended, skipping frame samples...", length);
|
"WRITE : %d of %d bytes sent, skipping frame samples...", written,
|
||||||
|
length);
|
||||||
} else {
|
} else {
|
||||||
GST_INFO_OBJECT (apexsink, "WRITE : %d bytes sent", length);
|
GST_INFO_OBJECT (apexsink, "WRITE : %d bytes sent", length);
|
||||||
|
/* NOTE, previous calculation subtracted apexsink->latency_time from this;
|
||||||
/* FIXME, sleeping is ugly and not interruptible */
|
* however, the value below is less than apexsink->latency_time for generation 2.
|
||||||
usleep ((gulong) ((length * 1000000.) / (GST_APEX_RAOP_BITRATE *
|
* In this case, the number went negative (actualy wrapped around into a big number).
|
||||||
GST_APEX_RAOP_BYTES_PER_SAMPLE) - apexsink->latency_time));
|
*/
|
||||||
|
apexsink->clock_id = gst_clock_new_single_shot_id (apexsink->clock,
|
||||||
|
(GstClockTime) (gst_clock_get_time (apexsink->clock) +
|
||||||
|
((length * 1000000000.)
|
||||||
|
/ (GST_APEX_RAOP_BITRATE * GST_APEX_RAOP_BYTES_PER_SAMPLE))));
|
||||||
|
gst_clock_id_wait (apexsink->clock_id, NULL);
|
||||||
|
gst_clock_id_unref (apexsink->clock_id);
|
||||||
|
apexsink->clock_id = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
return length;
|
return length;
|
||||||
|
@ -545,3 +650,16 @@ gst_apexsink_close (GstAudioSink * asink)
|
||||||
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static GstStateChangeReturn
|
||||||
|
gst_apexsink_change_state (GstElement * element, GstStateChange transition)
|
||||||
|
{
|
||||||
|
GstApExSink *apexsink = (GstApExSink *) element;
|
||||||
|
|
||||||
|
if (apexsink->clock_id && transition == GST_STATE_CHANGE_PAUSED_TO_READY) {
|
||||||
|
gst_clock_id_unschedule (apexsink->clock_id);
|
||||||
|
gst_clock_id_unref (apexsink->clock_id);
|
||||||
|
apexsink->clock_id = NULL;
|
||||||
|
}
|
||||||
|
return GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
|
||||||
|
}
|
||||||
|
|
|
@ -46,6 +46,8 @@ G_BEGIN_DECLS
|
||||||
#define GST_APEX_SINK_NAME "apexsink"
|
#define GST_APEX_SINK_NAME "apexsink"
|
||||||
#define GST_APEX_SINK_JACKTYPE_TYPE (gst_apexsink_jacktype_get_type())
|
#define GST_APEX_SINK_JACKTYPE_TYPE (gst_apexsink_jacktype_get_type())
|
||||||
#define GST_APEX_SINK_JACKSTATUS_TYPE (gst_apexsink_jackstatus_get_type())
|
#define GST_APEX_SINK_JACKSTATUS_TYPE (gst_apexsink_jackstatus_get_type())
|
||||||
|
#define GST_APEX_SINK_GENERATION_TYPE (gst_apexsink_generation_get_type())
|
||||||
|
#define GST_APEX_SINK_TRANSPORT_PROTOCOL_TYPE (gst_apexsink_transport_protocol_get_type())
|
||||||
/* ApEx classes declaration */
|
/* ApEx classes declaration */
|
||||||
typedef struct _GstApExSink GstApExSink;
|
typedef struct _GstApExSink GstApExSink;
|
||||||
typedef struct _GstApExSinkClass GstApExSinkClass;
|
typedef struct _GstApExSinkClass GstApExSinkClass;
|
||||||
|
@ -59,10 +61,19 @@ struct _GstApExSink
|
||||||
gchar *host;
|
gchar *host;
|
||||||
guint port;
|
guint port;
|
||||||
guint volume;
|
guint volume;
|
||||||
|
GstApExGeneration generation;
|
||||||
|
GstApExTransportProtocol transport_protocol;
|
||||||
|
|
||||||
/* private attributes : latency time local copy, tracks list of the mixer interface */
|
/* private attributes :
|
||||||
|
* latency time local copy
|
||||||
|
* tracks list of the mixer interface
|
||||||
|
* clock for sleeping
|
||||||
|
* clock ID for sleeping / canceling sleep
|
||||||
|
*/
|
||||||
guint64 latency_time;
|
guint64 latency_time;
|
||||||
GList *tracks;
|
GList *tracks;
|
||||||
|
GstClock *clock;
|
||||||
|
GstClockID clock_id;
|
||||||
|
|
||||||
/* private apex client */
|
/* private apex client */
|
||||||
GstApExRAOP *gst_apexraop;
|
GstApExRAOP *gst_apexraop;
|
||||||
|
@ -73,9 +84,11 @@ struct _GstApExSinkClass
|
||||||
GstAudioSinkClass parent_class;
|
GstAudioSinkClass parent_class;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* genum jack access */
|
/* genums */
|
||||||
GType gst_apexsink_jackstatus_get_type (void);
|
GType gst_apexsink_jackstatus_get_type (void);
|
||||||
GType gst_apexsink_jacktype_get_type (void);
|
GType gst_apexsink_jacktype_get_type (void);
|
||||||
|
GType gst_apexsink_generation_get_type (void);
|
||||||
|
GType gst_apexsink_transport_protocol_get_type (void);
|
||||||
|
|
||||||
/* audio sink standard api */
|
/* audio sink standard api */
|
||||||
GType gst_apexsink_get_type (void);
|
GType gst_apexsink_get_type (void);
|
||||||
|
|
Loading…
Reference in a new issue