mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-23 08:46:40 +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[] =
|
||||
"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,
|
||||
0xF0, 0xFF, 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;
|
||||
|
||||
|
@ -121,6 +122,9 @@ typedef struct
|
|||
GstApExJackType jack_type; /* APEX connected jack type, 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 */
|
||||
guint ctrl_port; /* APEX target control port */
|
||||
guint data_port; /* APEX negotiated data port, once SETUP performed */
|
||||
|
@ -130,12 +134,18 @@ typedef struct
|
|||
|
||||
int data_sd; /* data socket */
|
||||
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;
|
||||
|
||||
/* raop apex struct allocation */
|
||||
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;
|
||||
|
||||
|
@ -146,6 +156,10 @@ gst_apexraop_new (const gchar * host, const guint16 port)
|
|||
apexraop->ua = g_strdup (GST_APEX_RAOP_USER_AGENT);
|
||||
apexraop->jack_type = GST_APEX_JACK_TYPE_UNDEFINED;
|
||||
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;
|
||||
}
|
||||
|
@ -311,7 +325,9 @@ gst_apexraop_connect (GstApExRAOP * con)
|
|||
conn->url_abspath,
|
||||
inaddr,
|
||||
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_CHANNELS, GST_APEX_RAOP_BITRATE, ky, iv);
|
||||
|
||||
|
@ -451,8 +467,14 @@ gst_apexraop_connect (GstApExRAOP * con)
|
|||
if (res != GST_RTSP_STS_OK)
|
||||
return res;
|
||||
|
||||
if ((conn->data_sd = socket (AF_INET, SOCK_STREAM, 0)) < 0)
|
||||
return GST_RTSP_STS_DESTINATION_UNREACHABLE;
|
||||
if (conn->transport_protocol == GST_APEX_TCP) {
|
||||
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_port = htons (conn->data_port);
|
||||
|
@ -495,6 +517,34 @@ gst_apexraop_get_jackstatus (GstApExRAOP * con)
|
|||
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 */
|
||||
void
|
||||
gst_apexraop_close (GstApExRAOP * con)
|
||||
|
@ -628,25 +678,49 @@ gst_apexraop_write (GstApExRAOP * con, gpointer rawdata, guint length)
|
|||
gushort len;
|
||||
gint bit_offset, byte_offset, i, out_len, res;
|
||||
EVP_CIPHER_CTX aes_ctx;
|
||||
_GstApExRAOP *conn;
|
||||
|
||||
conn = (_GstApExRAOP *) con;
|
||||
_GstApExRAOP *conn = (_GstApExRAOP *) con;
|
||||
const int frame_header_size = conn->generation == GST_APEX_GENERATION_ONE
|
||||
? GST_APEX_RAOP_FRAME_HEADER_SIZE : GST_APEX_RTP_FRAME_HEADER_SIZE;
|
||||
|
||||
buffer =
|
||||
(guchar *) g_malloc0 (GST_APEX_RAOP_FRAME_HEADER_SIZE +
|
||||
(guchar *) g_malloc0 (frame_header_size +
|
||||
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 =
|
||||
length + GST_APEX_RAOP_FRAME_HEADER_SIZE +
|
||||
GST_APEX_RAOP_ALAC_HEADER_SIZE - 4;
|
||||
buffer[2] = len >> 8;
|
||||
buffer[3] = len & 0xff;
|
||||
len = length + frame_header_size + GST_APEX_RAOP_ALAC_HEADER_SIZE - 4;
|
||||
|
||||
buffer[2] = len >> 8;
|
||||
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;
|
||||
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, 0, 4, &bit_offset, &byte_offset); /* unknown */
|
||||
|
@ -673,16 +747,14 @@ gst_apexraop_write (GstApExRAOP * con, gpointer rawdata, guint length)
|
|||
|
||||
res =
|
||||
gst_apexraop_send (conn->data_sd, buffer,
|
||||
GST_APEX_RAOP_FRAME_HEADER_SIZE + GST_APEX_RAOP_ALAC_HEADER_SIZE +
|
||||
length);
|
||||
frame_header_size + GST_APEX_RAOP_ALAC_HEADER_SIZE + length);
|
||||
|
||||
g_free (buffer);
|
||||
|
||||
return (guint) ((res >=
|
||||
(GST_APEX_RAOP_FRAME_HEADER_SIZE +
|
||||
(frame_header_size +
|
||||
GST_APEX_RAOP_ALAC_HEADER_SIZE)) ? (res -
|
||||
GST_APEX_RAOP_FRAME_HEADER_SIZE -
|
||||
GST_APEX_RAOP_ALAC_HEADER_SIZE) : 0);
|
||||
frame_header_size - GST_APEX_RAOP_ALAC_HEADER_SIZE) : 0);
|
||||
}
|
||||
|
||||
/* raop apex buffer flush */
|
||||
|
@ -701,10 +773,13 @@ gst_apexraop_flush (GstApExRAOP * con)
|
|||
"Client-Instance: %s\r\n"
|
||||
"User-Agent: %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",
|
||||
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)
|
||||
return GST_RTSP_STS_GONE;
|
||||
|
|
|
@ -48,7 +48,8 @@ G_BEGIN_DECLS
|
|||
|
||||
/* raop fixed parameters */
|
||||
#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_CHANNELS 2
|
||||
#define GST_APEX_RAOP_BYTES_PER_SAMPLE (GST_APEX_RAOP_CHANNELS * GST_APEX_RAOP_BYTES_PER_CHANNEL)
|
||||
|
@ -78,13 +79,30 @@ typedef enum
|
|||
}
|
||||
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 */
|
||||
typedef struct
|
||||
{
|
||||
} GstApExRAOP;
|
||||
|
||||
/* 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);
|
||||
|
||||
/* 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);
|
||||
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
|
||||
|
||||
#endif
|
||||
|
|
|
@ -59,6 +59,8 @@ enum
|
|||
APEX_PROP_VOLUME,
|
||||
APEX_PROP_JACK_TYPE,
|
||||
APEX_PROP_JACK_STATUS,
|
||||
APEX_PROP_GENERATION,
|
||||
APEX_PROP_TRANSPORT_PROTOCOL,
|
||||
};
|
||||
|
||||
#define DEFAULT_APEX_HOST ""
|
||||
|
@ -66,6 +68,8 @@ enum
|
|||
#define DEFAULT_APEX_VOLUME 1.0
|
||||
#define DEFAULT_APEX_JACK_TYPE GST_APEX_JACK_TYPE_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 */
|
||||
GType
|
||||
|
@ -108,6 +112,43 @@ gst_apexsink_jacktype_get_type (void)
|
|||
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,
|
||||
const GValue * value, GParamSpec * pspec);
|
||||
|
@ -124,6 +165,8 @@ static gboolean gst_apexsink_unprepare (GstAudioSink * asink);
|
|||
static guint gst_apexsink_delay (GstAudioSink * asink);
|
||||
static void gst_apexsink_reset (GstAudioSink * asink);
|
||||
static gboolean gst_apexsink_close (GstAudioSink * asink);
|
||||
static GstStateChangeReturn gst_apexsink_change_state (GstElement * element,
|
||||
GstStateChange transition);
|
||||
|
||||
/* mixer interface standard api */
|
||||
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)->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_param_spec_string ("host", "Host", "AirPort Express target host",
|
||||
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",
|
||||
GST_APEX_SINK_JACKSTATUS_TYPE, DEFAULT_APEX_JACK_STATUS,
|
||||
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 */
|
||||
|
@ -295,6 +352,8 @@ gst_apexsink_init (GstApExSink * apexsink, GstApExSinkClass * g_class)
|
|||
apexsink->volume = CLAMP (DEFAULT_APEX_VOLUME * 75, 0, 100);
|
||||
apexsink->gst_apexraop = NULL;
|
||||
apexsink->tracks = g_list_append (apexsink->tracks, track);
|
||||
apexsink->clock = gst_system_clock_obtain ();
|
||||
apexsink->clock_id = NULL;
|
||||
|
||||
GST_INFO_OBJECT (apexsink,
|
||||
"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);
|
||||
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:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
|
@ -373,6 +454,14 @@ gst_apexsink_get_property (GObject * object, guint prop_id, GValue * value,
|
|||
g_value_set_enum (value,
|
||||
gst_apexraop_get_jackstatus (sink->gst_apexraop));
|
||||
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:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
|
@ -391,6 +480,8 @@ gst_apexsink_finalise (GObject * object)
|
|||
sink->tracks = NULL;
|
||||
}
|
||||
|
||||
gst_object_unref (sink->clock);
|
||||
|
||||
g_free (sink->host);
|
||||
|
||||
G_OBJECT_CLASS (parent_class)->finalize (object);
|
||||
|
@ -403,7 +494,8 @@ gst_apexsink_open (GstAudioSink * asink)
|
|||
int res;
|
||||
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) {
|
||||
GST_ERROR_OBJECT (apexsink,
|
||||
|
@ -460,12 +552,14 @@ static gboolean
|
|||
gst_apexsink_prepare (GstAudioSink * asink, GstRingBufferSpec * spec)
|
||||
{
|
||||
GstApExSink *apexsink = (GstApExSink *) asink;
|
||||
GstApExGeneration gen = gst_apexraop_get_generation (apexsink->gst_apexraop);
|
||||
|
||||
apexsink->latency_time = spec->latency_time;
|
||||
|
||||
spec->segsize =
|
||||
GST_APEX_RAOP_SAMPLES_PER_FRAME * GST_APEX_RAOP_BYTES_PER_SAMPLE;
|
||||
spec->segtotal = 1;
|
||||
spec->segsize = gen == GST_APEX_GENERATION_ONE
|
||||
? GST_APEX_RAOP_V1_SAMPLES_PER_FRAME * GST_APEX_RAOP_BYTES_PER_SAMPLE
|
||||
: 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));
|
||||
|
||||
|
@ -481,17 +575,28 @@ gst_apexsink_prepare (GstAudioSink * asink, GstRingBufferSpec * spec)
|
|||
static guint
|
||||
gst_apexsink_write (GstAudioSink * asink, gpointer data, guint length)
|
||||
{
|
||||
guint written;
|
||||
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,
|
||||
"WRITE : %d bytes not fully sended, skipping frame samples...", length);
|
||||
"WRITE : %d of %d bytes sent, skipping frame samples...", written,
|
||||
length);
|
||||
} else {
|
||||
GST_INFO_OBJECT (apexsink, "WRITE : %d bytes sent", length);
|
||||
|
||||
/* FIXME, sleeping is ugly and not interruptible */
|
||||
usleep ((gulong) ((length * 1000000.) / (GST_APEX_RAOP_BITRATE *
|
||||
GST_APEX_RAOP_BYTES_PER_SAMPLE) - apexsink->latency_time));
|
||||
/* NOTE, previous calculation subtracted apexsink->latency_time from this;
|
||||
* however, the value below is less than apexsink->latency_time for generation 2.
|
||||
* In this case, the number went negative (actualy wrapped around into a big number).
|
||||
*/
|
||||
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;
|
||||
|
@ -545,3 +650,16 @@ gst_apexsink_close (GstAudioSink * asink)
|
|||
|
||||
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_JACKTYPE_TYPE (gst_apexsink_jacktype_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 */
|
||||
typedef struct _GstApExSink GstApExSink;
|
||||
typedef struct _GstApExSinkClass GstApExSinkClass;
|
||||
|
@ -59,10 +61,19 @@ struct _GstApExSink
|
|||
gchar *host;
|
||||
guint port;
|
||||
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;
|
||||
GList *tracks;
|
||||
GstClock *clock;
|
||||
GstClockID clock_id;
|
||||
|
||||
/* private apex client */
|
||||
GstApExRAOP *gst_apexraop;
|
||||
|
@ -73,9 +84,11 @@ struct _GstApExSinkClass
|
|||
GstAudioSinkClass parent_class;
|
||||
};
|
||||
|
||||
/* genum jack access */
|
||||
/* genums */
|
||||
GType gst_apexsink_jackstatus_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 */
|
||||
GType gst_apexsink_get_type (void);
|
||||
|
|
Loading…
Reference in a new issue