diff options
-rw-r--r-- | audio/gsta2dpsink.c | 744 | ||||
-rw-r--r-- | audio/gsta2dpsink.h | 28 | ||||
-rw-r--r-- | audio/gstbluetooth.c | 2 | ||||
-rw-r--r-- | audio/sink.c | 2 |
4 files changed, 703 insertions, 73 deletions
diff --git a/audio/gsta2dpsink.c b/audio/gsta2dpsink.c index 8a6a44f1..0e8c8e02 100644 --- a/audio/gsta2dpsink.c +++ b/audio/gsta2dpsink.c @@ -28,22 +28,128 @@ #include <unistd.h> #include <sys/un.h> #include <sys/socket.h> +#include <fcntl.h> +#include <pthread.h> + +#include <netinet/in.h> + +#include <bluetooth/bluetooth.h> #include "ipc.h" +#include "sbc.h" #include "gsta2dpsink.h" GST_DEBUG_CATEGORY_STATIC(a2dp_sink_debug); #define GST_CAT_DEFAULT a2dp_sink_debug -#define DEFAULT_DEVICE "default" +#define BUFFER_SIZE 2048 + +#define GST_A2DP_SINK_MUTEX_LOCK(s) G_STMT_START { \ + g_mutex_lock (s->sink_lock); \ + } G_STMT_END + +#define GST_A2DP_SINK_MUTEX_UNLOCK(s) G_STMT_START { \ + g_mutex_unlock (s->sink_lock); \ + } G_STMT_END + +#define GST_A2DP_SINK_WAIT_CON_END(s) G_STMT_START { \ + s->waiting_con_conf = TRUE; \ + g_cond_wait (s->con_conf_end, s->sink_lock); \ + s->waiting_con_conf = FALSE; \ + } G_STMT_END + +#define GST_A2DP_SINK_CONFIGURATION_FAIL(s) G_STMT_START { \ + s->con_state = NOT_CONFIGURED; \ + g_cond_signal (s->con_conf_end); \ + } G_STMT_END + +#define GST_A2DP_SINK_CONFIGURATION_SUCCESS(s) G_STMT_START { \ + s->con_state = CONFIGURED; \ + g_cond_signal (s->con_conf_end); \ + } G_STMT_END + +struct bluetooth_a2dp { + sbc_t sbc; /* Codec data */ + int codesize; /* SBC codesize */ + int samples; /* Number of encoded samples */ + uint8_t buffer[BUFFER_SIZE]; /* Codec transfer buffer */ + int count; /* Codec transfer buffer counter */ + + int nsamples; /* Cumulative number of codec samples */ + uint16_t seq_num; /* Cumulative packet sequence */ + int frame_count; /* Current frames in buffer*/ +}; +struct bluetooth_data { + struct ipc_data_cfg cfg; /* Bluetooth device config */ + uint8_t buffer[BUFFER_SIZE]; /* Encoded transfer buffer */ + int count; /* Transfer buffer counter */ + struct bluetooth_a2dp a2dp; /* A2DP data */ +}; + +#if __BYTE_ORDER == __LITTLE_ENDIAN + +struct rtp_header { + uint8_t cc:4; + uint8_t x:1; + uint8_t p:1; + uint8_t v:2; + + uint8_t pt:7; + uint8_t m:1; + + uint16_t sequence_number; + uint32_t timestamp; + uint32_t ssrc; + uint32_t csrc[0]; +} __attribute__ ((packed)); + +struct rtp_payload { + uint8_t frame_count:4; + uint8_t rfa0:1; + uint8_t is_last_fragment:1; + uint8_t is_first_fragment:1; + uint8_t is_fragmented:1; +} __attribute__ ((packed)); + +#elif __BYTE_ORDER == __BIG_ENDIAN + +struct rtp_header { + uint8_t v:2; + uint8_t p:1; + uint8_t x:1; + uint8_t cc:4; + + uint8_t m:1; + uint8_t pt:7; + + uint16_t sequence_number; + uint32_t timestamp; + uint32_t ssrc; + uint32_t csrc[0]; +} __attribute__ ((packed)); + +struct rtp_payload { + uint8_t is_fragmented:1; + uint8_t is_first_fragment:1; + uint8_t is_last_fragment:1; + uint8_t rfa0:1; + uint8_t frame_count:4; +} __attribute__ ((packed)); + +#else +#error "Unknown byte order" +#endif + +#define IS_SBC(n) (strcmp(n, "audio/x-sbc") == 0) +#define IS_MPEG(n) (strcmp(n, "audio/mpeg") == 0) enum { PROP_0, PROP_DEVICE, }; -GST_BOILERPLATE(GstA2dpSink, gst_a2dp_sink, GstAudioSink, GST_TYPE_AUDIO_SINK); +GST_BOILERPLATE(GstA2dpSink, gst_a2dp_sink, GstBaseSink, GST_TYPE_BASE_SINK); static const GstElementDetails a2dp_sink_details = GST_ELEMENT_DETAILS("Bluetooth A2DP sink", @@ -60,7 +166,6 @@ static GstStaticPadTemplate a2dp_sink_factory = "blocks = (int) { 4, 8, 12, 16 }, " "subbands = (int) { 4, 8 }, " "allocation = (string) { snr, loudness }; " - "audio/mpeg, " "mpegversion = (int) 1, " "layer = (int) [ 1, 3 ], " @@ -77,13 +182,54 @@ static void gst_a2dp_sink_base_init(gpointer g_class) gst_element_class_set_details(element_class, &a2dp_sink_details); } +static gboolean gst_a2dp_sink_stop(GstBaseSink *basesink) +{ + GstA2dpSink *self = GST_A2DP_SINK(basesink); + struct bluetooth_a2dp *a2dp = &self->data->a2dp; + + self->con_state = NOT_CONFIGURED; + self->total = 0; + + if (self->stream) { + g_io_channel_close(self->stream); + g_io_channel_unref(self->stream); + self->stream = NULL; + } + + if (self->server) { + g_io_channel_close(self->server); + g_io_channel_unref(self->server); + self->stream = NULL; + } + + if (self->data->cfg.codec == CFG_CODEC_SBC) + sbc_finish(&a2dp->sbc); + + if (self->data) { + g_free(self->data); + self->data = NULL; + } + + return TRUE; +} + static void gst_a2dp_sink_finalize(GObject *object) { - GstA2dpSink *sink = GST_A2DP_SINK(object); + GstA2dpSink *self = GST_A2DP_SINK(object); + + if (self->data) + gst_a2dp_sink_stop (GST_BASE_SINK (self)); + + if (self->device) + g_free(self->device); - g_io_channel_close(sink->server); + /* unlock any thread waiting for this signal */ + GST_A2DP_SINK_MUTEX_LOCK(self); + GST_A2DP_SINK_CONFIGURATION_FAIL(self); + GST_A2DP_SINK_MUTEX_UNLOCK(self); - g_free(sink->device); + g_cond_free(self->con_conf_end); + g_mutex_free(self->sink_lock); G_OBJECT_CLASS(parent_class)->finalize(object); } @@ -95,11 +241,9 @@ static void gst_a2dp_sink_set_property(GObject *object, guint prop_id, switch (prop_id) { case PROP_DEVICE: - g_free(sink->device); + if (sink->device) + g_free(sink->device); sink->device = g_value_dup_string(value); - - if (sink->device == NULL) - sink->device = g_strdup(DEFAULT_DEVICE); break; default: @@ -124,114 +268,580 @@ static void gst_a2dp_sink_get_property(GObject *object, guint prop_id, } } -static gboolean gst_a2dp_sink_open(GstAudioSink *self) +static gint gst_a2dp_sink_bluetooth_recvmsg_fd(GstA2dpSink *sink) { - GstA2dpSink *sink = GST_A2DP_SINK(self); + char cmsg_b[CMSG_SPACE(sizeof(int))], m; + int err, ret, stream_fd; + struct iovec iov = { &m, sizeof(m) }; + struct msghdr msgh; + struct cmsghdr *cmsg; + + memset(&msgh, 0, sizeof(msgh)); + msgh.msg_iov = &iov; + msgh.msg_iovlen = 1; + msgh.msg_control = &cmsg_b; + msgh.msg_controllen = CMSG_LEN(sizeof(int)); + + ret = recvmsg(g_io_channel_unix_get_fd(sink->server), &msgh, 0); + if (ret < 0) { + err = errno; + GST_ERROR_OBJECT(sink, "Unable to receive fd: %s (%d)", + strerror(err), err); + return -err; + } - printf("device %s\n", sink->device); - printf("open\n"); + /* Receive auxiliary data in msgh */ + for (cmsg = CMSG_FIRSTHDR(&msgh); cmsg != NULL; + cmsg = CMSG_NXTHDR(&msgh, cmsg)) { + if (cmsg->cmsg_level == SOL_SOCKET + && cmsg->cmsg_type == SCM_RIGHTS) { + stream_fd = (*(int *) CMSG_DATA(cmsg)); + sink->stream = g_io_channel_unix_new(stream_fd); + GST_DEBUG_OBJECT(sink, "stream_fd=%d", stream_fd); + return 0; + } + } - return TRUE; + return -EINVAL; } -static gboolean gst_a2dp_sink_prepare(GstAudioSink *self, - GstRingBufferSpec *spec) +static int gst_a2dp_sink_bluetooth_a2dp_init(GstA2dpSink *sink, + struct ipc_codec_sbc *sbc) { - printf("perpare\n"); - printf("rate %d\n", spec->rate); - printf("channels %d\n", spec->channels); + struct bluetooth_a2dp *a2dp = &sink->data->a2dp; + struct ipc_data_cfg *cfg = &sink->data->cfg; - return TRUE; + if (cfg == NULL) { + GST_ERROR_OBJECT(sink, "Error getting codec parameters"); + return -1; + } + + if (cfg->codec != CFG_CODEC_SBC) + return -1; + + /* FIXME: init using flags? */ + sbc_init(&a2dp->sbc, 0); + a2dp->sbc.rate = cfg->rate; + a2dp->sbc.channels = cfg->mode == CFG_MODE_MONO ? 1 : 2; + if (cfg->mode == CFG_MODE_MONO || cfg->mode == CFG_MODE_JOINT_STEREO) + a2dp->sbc.joint = 1; + a2dp->sbc.allocation = sbc->allocation; + a2dp->sbc.subbands = sbc->subbands; + a2dp->sbc.blocks = sbc->blocks; + a2dp->sbc.bitpool = sbc->bitpool; + a2dp->codesize = a2dp->sbc.subbands * a2dp->sbc.blocks * + a2dp->sbc.channels * 2; + a2dp->count = sizeof(struct rtp_header) + sizeof(struct rtp_payload); + + GST_DEBUG_OBJECT(sink, "Codec parameters: \ + \tallocation=%u\n\tsubbands=%u\n \ + \tblocks=%u\n\tbitpool=%u\n", + a2dp->sbc.allocation, a2dp->sbc.subbands, + a2dp->sbc.blocks, a2dp->sbc.bitpool); + + return 0; } -static gboolean gst_a2dp_sink_unprepare(GstAudioSink *self) +static gboolean gst_a2dp_sink_init_pkt_conf(GstA2dpSink *sink, + GstCaps *caps, + struct ipc_packet *pkt) { - printf("unprepare\n"); + + struct ipc_data_cfg *cfg = (void *) pkt->data; + struct ipc_codec_sbc *sbc = (void *) cfg->data; + const GValue *value = NULL; + const char *pref, *name; + GstStructure *structure = gst_caps_get_structure(caps,0); + + name = gst_structure_get_name(structure); + /* FIXME only sbc supported here, should suport mp3 */ + if (!(IS_SBC(name))) { + GST_ERROR_OBJECT(sink, "Unsupported format %s", name); + return FALSE; + } + + if (sink->device) + strncpy(pkt->device, sink->device, 18); + + pkt->role = PKT_ROLE_HIFI; + + value = gst_structure_get_value(structure, "rate"); + cfg->rate = g_value_get_int(value); + + value = gst_structure_get_value(structure, "mode"); + pref = g_value_get_string(value); + if (strcmp(pref, "auto") == 0) + cfg->mode = CFG_MODE_AUTO; + else if (strcmp(pref, "mono") == 0) + cfg->mode = CFG_MODE_MONO; + else if (strcmp(pref, "dual") == 0) + cfg->mode = CFG_MODE_DUAL_CHANNEL; + else if (strcmp(pref, "stereo") == 0) + cfg->mode = CFG_MODE_STEREO; + else if (strcmp(pref, "joint") == 0) + cfg->mode = CFG_MODE_JOINT_STEREO; + else { + GST_ERROR_OBJECT(sink, "Invalid mode %s", pref); + return FALSE; + } + + value = gst_structure_get_value(structure, "allocation"); + pref = g_value_get_string(value); + if (strcmp(pref, "auto") == 0) + sbc->allocation = CFG_ALLOCATION_AUTO; + else if (strcmp(pref, "loudness") == 0) + sbc->allocation = CFG_ALLOCATION_LOUDNESS; + else if (strcmp(pref, "snr") == 0) + sbc->allocation = CFG_ALLOCATION_SNR; + else { + GST_ERROR_OBJECT(sink, "Invalid allocation: %s", pref); + return FALSE; + } + + value = gst_structure_get_value(structure, "subbands"); + sbc->subbands = g_value_get_int(value); + + value = gst_structure_get_value(structure, "blocks"); + sbc->blocks = g_value_get_int(value); +/* FIXME how can I obtain the bitpool ? + if (strcmp(id, "bitpool") == 0) { + if (snd_config_get_string(n, &bitpool) < 0) { + SNDERR("Invalid type for %s", id); + return -EINVAL; + } + + sbc->bitpool = atoi(bitpool); + continue; + } + + SNDERR("Unknown field %s", id); + return -EINVAL; + } +*/ + sbc->bitpool = 32; + + pkt->length = sizeof(*cfg) + sizeof(*sbc); + pkt->type = PKT_TYPE_CFG_REQ; + pkt->error = PKT_ERROR_NONE; return TRUE; + } -static gboolean gst_a2dp_sink_close(GstAudioSink *self) +static gboolean gst_a2dp_sink_conf_resp(GstA2dpSink *sink) { - printf("close\n"); + gchar buf[IPC_MTU]; + GIOError io_error; + guint ret; + struct ipc_packet *pkt = (void *) buf; + struct ipc_data_cfg *cfg = (void *) pkt->data; + + memset(buf, 0, sizeof(buf)); + + io_error = g_io_channel_read(sink->server, (gchar*)buf, + sizeof(*pkt) + sizeof(*cfg), &ret); + if (io_error != G_IO_ERROR_NONE && ret > 0) { + GST_ERROR_OBJECT(sink, "Error ocurred while receiving \ + configurarion packet answer"); + return FALSE; + } + + sink->total = ret; + if (pkt->type != PKT_TYPE_CFG_RSP) { + GST_ERROR_OBJECT(sink, "Unexpected packet type %d \ + received", pkt->type); + return FALSE; + } + + if (pkt->error != PKT_ERROR_NONE) { + GST_ERROR_OBJECT(sink, "Error %d while configuring \ + device", pkt->error); + return FALSE; + } + + if (cfg->codec != CFG_CODEC_SBC) { + GST_ERROR_OBJECT(sink, "Unsupported format"); + return FALSE; + } return TRUE; } -static guint gst_a2dp_sink_write(GstAudioSink *self, - gpointer data, guint length) +static gboolean gst_a2dp_sink_conf_recv_dev_conf(GstA2dpSink *sink) { - return 0; + gchar buf[IPC_MTU]; + GIOError io_error; + guint ret=0; + struct ipc_packet *pkt = (void *) buf; + struct ipc_data_cfg *cfg = (void *) pkt->data; + struct ipc_codec_sbc *sbc = (void *) cfg->data; + + io_error = g_io_channel_read(sink->server, (gchar*) sbc, + sizeof(*sbc), &ret); + if (io_error != G_IO_ERROR_NONE) { + GST_ERROR_OBJECT(sink, "Error while reading data from socket \ + %s (%d)", strerror(errno), errno); + return FALSE; + } else if (ret == 0) { + GST_ERROR_OBJECT(sink, "Read 0 bytes from socket"); + return FALSE; + } + + sink->total += ret; + GST_DEBUG_OBJECT(sink, "OK - %d bytes received", sink->total); + + if (pkt->length != (sink->total - sizeof(struct ipc_packet))) { + GST_ERROR_OBJECT(sink, "Error while configuring device: \ + packet size doesn't match"); + return FALSE; + } + + memcpy(&sink->data->cfg, cfg, sizeof(*cfg)); + + GST_DEBUG_OBJECT(sink, "Device configuration:\n\tchannel=%p\n\t\ + fd_opt=%u\n\tpkt_len=%u\n\tsample_size=%u\n\trate=%u", + sink->stream, sink->data->cfg.fd_opt, \ + sink->data->cfg.pkt_len, sink->data->cfg.sample_size, \ + sink->data->cfg.rate); + + if (sink->data->cfg.codec == CFG_CODEC_SBC) { + ret = gst_a2dp_sink_bluetooth_a2dp_init(sink, sbc); + if (ret < 0) + return FALSE; + + } + return TRUE; } -static guint gst_a2dp_sink_delay(GstAudioSink *audiosink) +static gboolean gst_a2dp_sink_conf_recv_stream_fd(GstA2dpSink *sink) { - printf("delay\n"); + gint ret; + GIOError err; + gsize read; + + ret = gst_a2dp_sink_bluetooth_recvmsg_fd(sink); + if (ret < 0) + return FALSE; + + if (!sink->stream) { + GST_ERROR_OBJECT(sink, "Error while configuring device: \ + could not acquire audio socket"); + return FALSE; + } - return 0; + /* It is possible there is some outstanding + data in the pipe - we have to empty it */ + while (TRUE) { + err = g_io_channel_read(sink->stream, + (gchar *) sink->data->buffer, + (gsize) sink->data->cfg.pkt_len, + &read); + if (err != G_IO_ERROR_NONE || read <= 0) + break; + } + + memset(sink->data->buffer, 0, sizeof(sink->data->buffer)); + + return TRUE; } -static void gst_a2dp_sink_reset(GstAudioSink *audiosink) +static void gst_a2dp_sink_conf_recv_data(GstA2dpSink *sink) { - printf("reset\n"); + /* + * We hold the lock, since we can send a signal. + * It is a good practice, according to the glib api. + */ + GST_A2DP_SINK_MUTEX_LOCK(sink); + + switch (sink->con_state) { + case CONFIGURING_SENT_CONF: + if (gst_a2dp_sink_conf_resp(sink)) + sink->con_state = CONFIGURING_RCVD_CONF_RSP; + else + GST_A2DP_SINK_CONFIGURATION_FAIL(sink); + break; + case CONFIGURING_RCVD_CONF_RSP: + if (gst_a2dp_sink_conf_recv_dev_conf(sink)) + sink->con_state = CONFIGURING_RCVD_DEV_CONF; + else + GST_A2DP_SINK_CONFIGURATION_FAIL(sink); + case CONFIGURING_RCVD_DEV_CONF: + if (gst_a2dp_sink_conf_recv_stream_fd(sink)) + GST_A2DP_SINK_CONFIGURATION_SUCCESS(sink); + else + GST_A2DP_SINK_CONFIGURATION_FAIL(sink); + break; + } + + GST_A2DP_SINK_MUTEX_UNLOCK(sink); } + static gboolean server_callback(GIOChannel *chan, GIOCondition cond, gpointer data) { - printf("callback\n"); + GstA2dpSink *sink = GST_A2DP_SINK(data); + + switch (cond) { + case G_IO_IN: + if (sink->con_state != NOT_CONFIGURED && + sink->con_state != CONFIGURED) + gst_a2dp_sink_conf_recv_data(sink); + else + GST_WARNING_OBJECT(sink, "Unexpected data received"); + break; + case G_IO_HUP: + return FALSE; + break; + case G_IO_ERR: + GST_WARNING_OBJECT(sink, "Untreated callback G_IO_ERR"); + break; + case G_IO_NVAL: + return FALSE; + break; + default: + GST_WARNING_OBJECT(sink, "Unexpected callback call"); + break; + } return TRUE; } +static gboolean gst_a2dp_sink_start(GstBaseSink *basesink) +{ + GstA2dpSink *self = GST_A2DP_SINK(basesink); + struct sockaddr_un addr = { AF_UNIX, IPC_SOCKET_NAME }; + gint sk; + gint err; + + sk = socket(PF_LOCAL, SOCK_STREAM, 0); + if (sk < 0) { + err = errno; + GST_ERROR_OBJECT(self, "Cannot open socket: %s (%d)", + strerror(err), err); + return FALSE; + } + + if (connect(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + err = errno; + GST_ERROR_OBJECT(self, "Connection fail %s (%d)", + strerror(err), err); + close(sk); + return FALSE; + } + + self->server = g_io_channel_unix_new(sk); + + g_io_add_watch(self->server, G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, + server_callback, self); + + self->data = g_new0(struct bluetooth_data, 1); + memset(self->data, 0, sizeof(struct bluetooth_data)); + + return TRUE; +} + +static gboolean gst_a2dp_sink_send_conf_pkt(GstA2dpSink *sink, GstCaps *caps) +{ + gchar buf[IPC_MTU]; + struct ipc_packet *pkt = (void *) buf; + gboolean ret; + guint bytes_sent; + GIOError io_error; + + g_assert(sink->con_state == NOT_CONFIGURED); + + memset (pkt, 0, sizeof(buf)); + ret = gst_a2dp_sink_init_pkt_conf(sink, caps, pkt); + if (!ret) { + GST_ERROR_OBJECT(sink, "Couldn't initialize parse caps \ + to packet configuration"); + return FALSE; + } + + sink->con_state = CONFIGURING_INIT; + + io_error = g_io_channel_write(sink->server, (gchar*)pkt, + sizeof(*pkt) + pkt->length, &bytes_sent); + if (io_error != G_IO_ERROR_NONE) { + GST_ERROR_OBJECT(sink, "Error ocurred while sending \ + configurarion packet"); + sink->con_state = NOT_CONFIGURED; + return FALSE; + } + + GST_DEBUG_OBJECT(sink, "%d bytes sent", bytes_sent); + sink->con_state = CONFIGURING_SENT_CONF; + + return TRUE; +} + +static gboolean gst_a2dp_sink_start_dev_conf(GstA2dpSink *sink, GstCaps *caps) +{ + gboolean ret; + + g_assert(sink->con_state == NOT_CONFIGURED); + + GST_DEBUG_OBJECT(sink, "starting device configuration"); + + ret = gst_a2dp_sink_send_conf_pkt(sink, caps); + + return ret; +} + +static GstFlowReturn gst_a2dp_sink_preroll(GstBaseSink *basesink, + GstBuffer *buffer) +{ + GstA2dpSink *sink = GST_A2DP_SINK(basesink); + + GST_A2DP_SINK_MUTEX_LOCK(sink); + + if (sink->con_state == NOT_CONFIGURED) + gst_a2dp_sink_start_dev_conf(sink, GST_BUFFER_CAPS(buffer)); + + /* wait for the connection process to finish */ + if (sink->con_state != CONFIGURED) + GST_A2DP_SINK_WAIT_CON_END(sink); + + GST_A2DP_SINK_MUTEX_UNLOCK(sink); + + if (sink->con_state != CONFIGURED) + return GST_FLOW_ERROR; + + return GST_FLOW_OK; +} + +static int gst_a2dp_sink_avdtp_write(GstA2dpSink *sink) +{ + int ret = 0; + struct bluetooth_data *data; + struct rtp_header *header; + struct rtp_payload *payload; + struct bluetooth_a2dp *a2dp; + GIOError err; + + data = sink->data; + a2dp = &data->a2dp; + + header = (void *) a2dp->buffer; + payload = (void *) (a2dp->buffer + sizeof(*header)); + + memset(a2dp->buffer, 0, sizeof(*header) + sizeof(*payload)); + + payload->frame_count = a2dp->frame_count; + header->v = 2; + header->pt = 1; + header->sequence_number = htons(a2dp->seq_num); + header->timestamp = htonl(a2dp->nsamples); + header->ssrc = htonl(1); + + while (TRUE) { + err = g_io_channel_write(sink->stream, (const char *) a2dp->buffer, + (gsize) a2dp->count, (gsize *) &ret); + + if (err == G_IO_ERROR_AGAIN) { + usleep (100); + continue; + } + + break; + } + + /* Reset buffer of data to send */ + a2dp->count = sizeof(struct rtp_header) + sizeof(struct rtp_payload); + a2dp->frame_count = 0; + a2dp->samples = 0; + a2dp->seq_num++; + + return ret; +} + +static GstFlowReturn gst_a2dp_sink_render(GstBaseSink *basesink, + GstBuffer *buffer) +{ + GstA2dpSink *sink; + struct bluetooth_data *data; + struct bluetooth_a2dp *a2dp; + gint encoded, frame_size=1024; + gint ret=0; + + sink = GST_A2DP_SINK(basesink); + data = (struct bluetooth_data*) sink->data; + a2dp = &data->a2dp; + + encoded = GST_BUFFER_SIZE(buffer); + + if (a2dp->count + encoded >= data->cfg.pkt_len) { + ret = gst_a2dp_sink_avdtp_write(sink); + if (ret < 0) + return GST_FLOW_ERROR; + } + + memcpy(a2dp->buffer + a2dp->count, GST_BUFFER_DATA(buffer), encoded); + a2dp->count += encoded; + a2dp->frame_count++; + a2dp->samples += encoded / frame_size; + a2dp->nsamples += encoded / frame_size; + + return GST_FLOW_OK; +} + +static gboolean gst_a2dp_sink_set_caps(GstBaseSink *basesink, GstCaps *caps) +{ + GstA2dpSink *self = GST_A2DP_SINK(basesink); + + GST_A2DP_SINK_MUTEX_LOCK(self); + if (self->con_state == NOT_CONFIGURED) + gst_a2dp_sink_start_dev_conf(self, caps); + GST_A2DP_SINK_MUTEX_UNLOCK(self); + + return TRUE; +} + +static gboolean gst_a2dp_sink_unlock(GstBaseSink *basesink) +{ + return TRUE; +} + static void gst_a2dp_sink_class_init(GstA2dpSinkClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); - GstAudioSinkClass *audiosink_class = GST_AUDIO_SINK_CLASS(klass); + GstBaseSinkClass *basesink_class = GST_BASE_SINK_CLASS(klass); parent_class = g_type_class_peek_parent(klass); object_class->finalize = GST_DEBUG_FUNCPTR(gst_a2dp_sink_finalize); - object_class->set_property = GST_DEBUG_FUNCPTR(gst_a2dp_sink_set_property); - object_class->get_property = GST_DEBUG_FUNCPTR(gst_a2dp_sink_get_property); - - audiosink_class->open = GST_DEBUG_FUNCPTR(gst_a2dp_sink_open); - audiosink_class->prepare = GST_DEBUG_FUNCPTR(gst_a2dp_sink_prepare); - audiosink_class->unprepare = GST_DEBUG_FUNCPTR(gst_a2dp_sink_unprepare); - audiosink_class->close = GST_DEBUG_FUNCPTR(gst_a2dp_sink_close); - audiosink_class->write = GST_DEBUG_FUNCPTR(gst_a2dp_sink_write); - audiosink_class->delay = GST_DEBUG_FUNCPTR(gst_a2dp_sink_delay); - audiosink_class->reset = GST_DEBUG_FUNCPTR(gst_a2dp_sink_reset); + object_class->set_property = GST_DEBUG_FUNCPTR( + gst_a2dp_sink_set_property); + object_class->get_property = GST_DEBUG_FUNCPTR( + gst_a2dp_sink_get_property); + + basesink_class->start = GST_DEBUG_FUNCPTR(gst_a2dp_sink_start); + basesink_class->stop = GST_DEBUG_FUNCPTR(gst_a2dp_sink_stop); + basesink_class->render = GST_DEBUG_FUNCPTR(gst_a2dp_sink_render); + basesink_class->preroll = GST_DEBUG_FUNCPTR(gst_a2dp_sink_preroll); + basesink_class->set_caps = GST_DEBUG_FUNCPTR(gst_a2dp_sink_set_caps); + basesink_class->unlock = GST_DEBUG_FUNCPTR(gst_a2dp_sink_unlock); g_object_class_install_property(object_class, PROP_DEVICE, - g_param_spec_string("device", "Device", - "Bluetooth remote device", - DEFAULT_DEVICE, G_PARAM_READWRITE)); + g_param_spec_string("device", "Device", + "Bluetooth remote device address", + NULL, G_PARAM_READWRITE)); GST_DEBUG_CATEGORY_INIT(a2dp_sink_debug, "a2dpsink", 0, - "A2DP sink element"); + "A2DP sink element"); } static void gst_a2dp_sink_init(GstA2dpSink *self, GstA2dpSinkClass *klass) { - struct sockaddr_un addr = { AF_UNIX, IPC_SOCKET_NAME }; - int sk; + self->device = NULL; + self->data = NULL; - self->device = g_strdup(DEFAULT_DEVICE); - - sk = socket(PF_LOCAL, SOCK_STREAM, 0); - if (sk < 0) - return; - - if (connect(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { - close(sk); - return; - } - - self->server = g_io_channel_unix_new(sk); - - g_io_add_watch(self->server, G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, - server_callback, self); + self->stream = NULL; + self->con_state = NOT_CONFIGURED; + self->total = 0; - g_io_channel_unref(self->server); + self->con_conf_end = g_cond_new(); + self->waiting_con_conf = FALSE; + self->sink_lock = g_mutex_new(); } diff --git a/audio/gsta2dpsink.h b/audio/gsta2dpsink.h index c83ec66c..6c7cfe2a 100644 --- a/audio/gsta2dpsink.h +++ b/audio/gsta2dpsink.h @@ -22,7 +22,7 @@ */ #include <gst/gst.h> -#include <gst/audio/gstaudiosink.h> +#include <gst/base/gstbasesink.h> G_BEGIN_DECLS @@ -37,19 +37,41 @@ G_BEGIN_DECLS #define GST_IS_A2DP_SINK_CLASS(obj) \ (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_A2DP_SINK)) +enum { + NOT_CONFIGURED, + CONFIGURING_INIT, + CONFIGURING_SENT_CONF, + CONFIGURING_RCVD_CONF_RSP, + CONFIGURING_RCVD_DEV_CONF, + CONFIGURED +}; + typedef struct _GstA2dpSink GstA2dpSink; typedef struct _GstA2dpSinkClass GstA2dpSinkClass; +struct bluetooth_data; + struct _GstA2dpSink { - GstAudioSink sink; + GstBaseSink sink; gchar *device; + GIOChannel *stream; + struct bluetooth_data *data; GIOChannel *server; + + gint con_state; + + GCond *con_conf_end; + gboolean waiting_con_conf; + GMutex *sink_lock; + + gint total; + }; struct _GstA2dpSinkClass { - GstAudioSinkClass parent_class; + GstBaseSinkClass parent_class; }; GType gst_a2dp_sink_get_type(void); diff --git a/audio/gstbluetooth.c b/audio/gstbluetooth.c index ce065820..593a311e 100644 --- a/audio/gstbluetooth.c +++ b/audio/gstbluetooth.c @@ -67,11 +67,9 @@ static gboolean plugin_init(GstPlugin *plugin) GST_RANK_PRIMARY, GST_TYPE_SBC_PARSE) == FALSE) return FALSE; -#if 0 if (gst_element_register(plugin, "a2dpsink", GST_RANK_PRIMARY, GST_TYPE_A2DP_SINK) == FALSE) return FALSE; -#endif return TRUE; } diff --git a/audio/sink.c b/audio/sink.c index 0c5391f3..2515c968 100644 --- a/audio/sink.c +++ b/audio/sink.c @@ -177,7 +177,7 @@ static void stream_setup_complete(struct avdtp *session, struct a2dp_sep *sep, sink->session = NULL; if (avdtp_error_type(err) == AVDTP_ERROR_ERRNO && avdtp_error_posix_errno(err) != EHOSTDOWN) { - debug("connect:connect XCASE detected"); + debug("connect:connect XCASE detected"); g_timeout_add(STREAM_SETUP_RETRY_TIMER, stream_setup_retry, sink); } else { |