| /* |
| * Copyright (C) 2013 Intel Corporation |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| */ |
| |
| #include <errno.h> |
| #include <pthread.h> |
| #include <poll.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/socket.h> |
| #include <sys/un.h> |
| #include <unistd.h> |
| #include <arpa/inet.h> |
| #include <fcntl.h> |
| |
| #include <hardware/audio.h> |
| #include <hardware/hardware.h> |
| |
| #include "audio-msg.h" |
| #include "ipc-common.h" |
| #include "hal-log.h" |
| #include "hal-msg.h" |
| #include "hal-audio.h" |
| #include "hal-utils.h" |
| #include "hal.h" |
| |
| #define FIXED_A2DP_PLAYBACK_LATENCY_MS 25 |
| |
| #define FIXED_BUFFER_SIZE (20 * 512) |
| |
| #define MAX_DELAY 100000 /* 100ms */ |
| |
| static const uint8_t a2dp_src_uuid[] = { |
| 0x00, 0x00, 0x11, 0x0a, 0x00, 0x00, 0x10, 0x00, |
| 0x80, 0x00, 0x00, 0x80, 0x5f, 0x9b, 0x34, 0xfb }; |
| |
| static int listen_sk = -1; |
| static int audio_sk = -1; |
| |
| static pthread_t ipc_th = 0; |
| static pthread_mutex_t sk_mutex = PTHREAD_MUTEX_INITIALIZER; |
| |
| static void timespec_add(struct timespec *base, uint64_t time_us, |
| struct timespec *res) |
| { |
| res->tv_sec = base->tv_sec + time_us / 1000000; |
| res->tv_nsec = base->tv_nsec + (time_us % 1000000) * 1000; |
| |
| if (res->tv_nsec >= 1000000000) { |
| res->tv_sec++; |
| res->tv_nsec -= 1000000000; |
| } |
| } |
| |
| static void timespec_diff(struct timespec *a, struct timespec *b, |
| struct timespec *res) |
| { |
| res->tv_sec = a->tv_sec - b->tv_sec; |
| res->tv_nsec = a->tv_nsec - b->tv_nsec; |
| |
| if (res->tv_nsec < 0) { |
| res->tv_sec--; |
| res->tv_nsec += 1000000000; /* 1sec */ |
| } |
| } |
| |
| static uint64_t timespec_diff_us(struct timespec *a, struct timespec *b) |
| { |
| struct timespec res; |
| |
| timespec_diff(a, b, &res); |
| |
| return res.tv_sec * 1000000ll + res.tv_nsec / 1000ll; |
| } |
| |
| #if defined(ANDROID) |
| /* |
| * Bionic does not have clock_nanosleep() prototype in time.h even though |
| * it provides its implementation. |
| */ |
| extern int clock_nanosleep(clockid_t clock_id, int flags, |
| const struct timespec *request, |
| struct timespec *remain); |
| #endif |
| |
| static struct { |
| const audio_codec_get_t get_codec; |
| bool loaded; |
| } audio_codecs[] = { |
| { .get_codec = codec_aptx, .loaded = false }, |
| { .get_codec = codec_sbc, .loaded = false }, |
| }; |
| |
| #define NUM_CODECS (sizeof(audio_codecs) / sizeof(audio_codecs[0])) |
| |
| #define MAX_AUDIO_ENDPOINTS NUM_CODECS |
| |
| struct audio_endpoint { |
| uint8_t id; |
| const struct audio_codec *codec; |
| void *codec_data; |
| int fd; |
| |
| struct media_packet *mp; |
| size_t mp_data_len; |
| |
| uint16_t seq; |
| uint32_t samples; |
| struct timespec start; |
| |
| bool resync; |
| }; |
| |
| static struct audio_endpoint audio_endpoints[MAX_AUDIO_ENDPOINTS]; |
| |
| enum a2dp_state_t { |
| AUDIO_A2DP_STATE_NONE, |
| AUDIO_A2DP_STATE_STANDBY, |
| AUDIO_A2DP_STATE_SUSPENDED, |
| AUDIO_A2DP_STATE_STARTED |
| }; |
| |
| struct a2dp_stream_out { |
| struct audio_stream_out stream; |
| |
| struct audio_endpoint *ep; |
| enum a2dp_state_t audio_state; |
| struct audio_input_config cfg; |
| |
| uint8_t *downmix_buf; |
| }; |
| |
| struct a2dp_audio_dev { |
| struct audio_hw_device dev; |
| struct a2dp_stream_out *out; |
| }; |
| |
| static int audio_ipc_cmd(uint8_t service_id, uint8_t opcode, uint16_t len, |
| void *param, size_t *rsp_len, void *rsp, int *fd) |
| { |
| ssize_t ret; |
| struct msghdr msg; |
| struct iovec iv[2]; |
| struct ipc_hdr cmd; |
| char cmsgbuf[CMSG_SPACE(sizeof(int))]; |
| struct ipc_status s; |
| size_t s_len = sizeof(s); |
| |
| pthread_mutex_lock(&sk_mutex); |
| |
| if (audio_sk < 0) { |
| error("audio: Invalid cmd socket passed to audio_ipc_cmd"); |
| goto failed; |
| } |
| |
| if (!rsp || !rsp_len) { |
| memset(&s, 0, s_len); |
| rsp_len = &s_len; |
| rsp = &s; |
| } |
| |
| memset(&msg, 0, sizeof(msg)); |
| memset(&cmd, 0, sizeof(cmd)); |
| |
| cmd.service_id = service_id; |
| cmd.opcode = opcode; |
| cmd.len = len; |
| |
| iv[0].iov_base = &cmd; |
| iv[0].iov_len = sizeof(cmd); |
| |
| iv[1].iov_base = param; |
| iv[1].iov_len = len; |
| |
| msg.msg_iov = iv; |
| msg.msg_iovlen = 2; |
| |
| ret = sendmsg(audio_sk, &msg, 0); |
| if (ret < 0) { |
| error("audio: Sending command failed:%s", strerror(errno)); |
| goto failed; |
| } |
| |
| /* socket was shutdown */ |
| if (ret == 0) { |
| error("audio: Command socket closed"); |
| goto failed; |
| } |
| |
| memset(&msg, 0, sizeof(msg)); |
| memset(&cmd, 0, sizeof(cmd)); |
| |
| iv[0].iov_base = &cmd; |
| iv[0].iov_len = sizeof(cmd); |
| |
| iv[1].iov_base = rsp; |
| iv[1].iov_len = *rsp_len; |
| |
| msg.msg_iov = iv; |
| msg.msg_iovlen = 2; |
| |
| if (fd) { |
| memset(cmsgbuf, 0, sizeof(cmsgbuf)); |
| msg.msg_control = cmsgbuf; |
| msg.msg_controllen = sizeof(cmsgbuf); |
| } |
| |
| ret = recvmsg(audio_sk, &msg, 0); |
| if (ret < 0) { |
| error("audio: Receiving command response failed:%s", |
| strerror(errno)); |
| goto failed; |
| } |
| |
| if (ret < (ssize_t) sizeof(cmd)) { |
| error("audio: Too small response received(%zd bytes)", ret); |
| goto failed; |
| } |
| |
| if (cmd.service_id != service_id) { |
| error("audio: Invalid service id (%u vs %u)", cmd.service_id, |
| service_id); |
| goto failed; |
| } |
| |
| if (ret != (ssize_t) (sizeof(cmd) + cmd.len)) { |
| error("audio: Malformed response received(%zd bytes)", ret); |
| goto failed; |
| } |
| |
| if (cmd.opcode != opcode && cmd.opcode != AUDIO_OP_STATUS) { |
| error("audio: Invalid opcode received (%u vs %u)", |
| cmd.opcode, opcode); |
| goto failed; |
| } |
| |
| if (cmd.opcode == AUDIO_OP_STATUS) { |
| struct ipc_status *s = rsp; |
| |
| if (sizeof(*s) != cmd.len) { |
| error("audio: Invalid status length"); |
| goto failed; |
| } |
| |
| if (s->code == AUDIO_STATUS_SUCCESS) { |
| error("audio: Invalid success status response"); |
| goto failed; |
| } |
| |
| pthread_mutex_unlock(&sk_mutex); |
| |
| return s->code; |
| } |
| |
| pthread_mutex_unlock(&sk_mutex); |
| |
| /* Receive auxiliary data in msg */ |
| if (fd) { |
| struct cmsghdr *cmsg; |
| |
| *fd = -1; |
| |
| for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; |
| cmsg = CMSG_NXTHDR(&msg, cmsg)) { |
| if (cmsg->cmsg_level == SOL_SOCKET |
| && cmsg->cmsg_type == SCM_RIGHTS) { |
| memcpy(fd, CMSG_DATA(cmsg), sizeof(int)); |
| break; |
| } |
| } |
| |
| if (*fd < 0) |
| goto failed; |
| } |
| |
| *rsp_len = cmd.len; |
| |
| return AUDIO_STATUS_SUCCESS; |
| |
| failed: |
| /* Some serious issue happen on IPC - recover */ |
| shutdown(audio_sk, SHUT_RDWR); |
| pthread_mutex_unlock(&sk_mutex); |
| |
| return AUDIO_STATUS_FAILED; |
| } |
| |
| static int ipc_open_cmd(const struct audio_codec *codec) |
| { |
| uint8_t buf[BLUEZ_AUDIO_MTU]; |
| struct audio_cmd_open *cmd = (struct audio_cmd_open *) buf; |
| struct audio_rsp_open rsp; |
| size_t cmd_len = sizeof(buf) - sizeof(*cmd); |
| size_t rsp_len = sizeof(rsp); |
| int result; |
| |
| DBG(""); |
| |
| memcpy(cmd->uuid, a2dp_src_uuid, sizeof(a2dp_src_uuid)); |
| |
| cmd->codec = codec->type; |
| cmd->presets = codec->get_presets(cmd->preset, &cmd_len); |
| |
| cmd_len += sizeof(*cmd); |
| |
| result = audio_ipc_cmd(AUDIO_SERVICE_ID, AUDIO_OP_OPEN, cmd_len, cmd, |
| &rsp_len, &rsp, NULL); |
| |
| if (result != AUDIO_STATUS_SUCCESS) |
| return 0; |
| |
| return rsp.id; |
| } |
| |
| static int ipc_close_cmd(uint8_t endpoint_id) |
| { |
| struct audio_cmd_close cmd; |
| int result; |
| |
| DBG(""); |
| |
| cmd.id = endpoint_id; |
| |
| result = audio_ipc_cmd(AUDIO_SERVICE_ID, AUDIO_OP_CLOSE, |
| sizeof(cmd), &cmd, NULL, NULL, NULL); |
| |
| return result; |
| } |
| |
| static int ipc_open_stream_cmd(uint8_t *endpoint_id, uint16_t *mtu, int *fd, |
| struct audio_preset **caps) |
| { |
| char buf[BLUEZ_AUDIO_MTU]; |
| struct audio_cmd_open_stream cmd; |
| struct audio_rsp_open_stream *rsp = |
| (struct audio_rsp_open_stream *) &buf; |
| size_t rsp_len = sizeof(buf); |
| int result; |
| |
| DBG(""); |
| |
| if (!caps) |
| return AUDIO_STATUS_FAILED; |
| |
| cmd.id = *endpoint_id; |
| |
| result = audio_ipc_cmd(AUDIO_SERVICE_ID, AUDIO_OP_OPEN_STREAM, |
| sizeof(cmd), &cmd, &rsp_len, rsp, fd); |
| if (result == AUDIO_STATUS_SUCCESS) { |
| size_t buf_len = sizeof(struct audio_preset) + |
| rsp->preset[0].len; |
| *endpoint_id = rsp->id; |
| *mtu = rsp->mtu; |
| *caps = malloc(buf_len); |
| memcpy(*caps, &rsp->preset, buf_len); |
| } else { |
| *caps = NULL; |
| } |
| |
| return result; |
| } |
| |
| static int ipc_close_stream_cmd(uint8_t endpoint_id) |
| { |
| struct audio_cmd_close_stream cmd; |
| int result; |
| |
| DBG(""); |
| |
| cmd.id = endpoint_id; |
| |
| result = audio_ipc_cmd(AUDIO_SERVICE_ID, AUDIO_OP_CLOSE_STREAM, |
| sizeof(cmd), &cmd, NULL, NULL, NULL); |
| |
| return result; |
| } |
| |
| static int ipc_resume_stream_cmd(uint8_t endpoint_id) |
| { |
| struct audio_cmd_resume_stream cmd; |
| int result; |
| |
| DBG(""); |
| |
| cmd.id = endpoint_id; |
| |
| result = audio_ipc_cmd(AUDIO_SERVICE_ID, AUDIO_OP_RESUME_STREAM, |
| sizeof(cmd), &cmd, NULL, NULL, NULL); |
| |
| return result; |
| } |
| |
| static int ipc_suspend_stream_cmd(uint8_t endpoint_id) |
| { |
| struct audio_cmd_suspend_stream cmd; |
| int result; |
| |
| DBG(""); |
| |
| cmd.id = endpoint_id; |
| |
| result = audio_ipc_cmd(AUDIO_SERVICE_ID, AUDIO_OP_SUSPEND_STREAM, |
| sizeof(cmd), &cmd, NULL, NULL, NULL); |
| |
| return result; |
| } |
| |
| struct register_state { |
| struct audio_endpoint *ep; |
| bool error; |
| }; |
| |
| static void register_endpoint(const struct audio_codec *codec, |
| struct register_state *state) |
| { |
| struct audio_endpoint *ep = state->ep; |
| |
| /* don't even try to register more endpoints if one failed */ |
| if (state->error) |
| return; |
| |
| ep->id = ipc_open_cmd(codec); |
| |
| if (!ep->id) { |
| state->error = true; |
| error("Failed to register endpoint"); |
| return; |
| } |
| |
| ep->codec = codec; |
| ep->codec_data = NULL; |
| ep->fd = -1; |
| |
| state->ep++; |
| } |
| |
| static int register_endpoints(void) |
| { |
| struct register_state state; |
| unsigned int i; |
| |
| state.ep = &audio_endpoints[0]; |
| state.error = false; |
| |
| for (i = 0; i < NUM_CODECS; i++) { |
| const struct audio_codec *codec = audio_codecs[i].get_codec(); |
| |
| if (!audio_codecs[i].loaded) |
| continue; |
| |
| register_endpoint(codec, &state); |
| } |
| |
| return state.error ? AUDIO_STATUS_FAILED : AUDIO_STATUS_SUCCESS; |
| } |
| |
| static void unregister_endpoints(void) |
| { |
| size_t i; |
| |
| for (i = 0; i < MAX_AUDIO_ENDPOINTS; i++) { |
| struct audio_endpoint *ep = &audio_endpoints[i]; |
| |
| if (ep->id) { |
| ipc_close_cmd(ep->id); |
| memset(ep, 0, sizeof(*ep)); |
| } |
| } |
| } |
| |
| static bool open_endpoint(struct audio_endpoint **epp, |
| struct audio_input_config *cfg) |
| { |
| struct audio_preset *preset; |
| struct audio_endpoint *ep = *epp; |
| const struct audio_codec *codec; |
| uint16_t mtu; |
| uint16_t payload_len; |
| int fd; |
| size_t i; |
| uint8_t ep_id = 0; |
| |
| if (ep) |
| ep_id = ep->id; |
| |
| if (ipc_open_stream_cmd(&ep_id, &mtu, &fd, &preset) != |
| AUDIO_STATUS_SUCCESS) |
| return false; |
| |
| DBG("ep_id=%d mtu=%u", ep_id, mtu); |
| |
| for (i = 0; i < MAX_AUDIO_ENDPOINTS; i++) |
| if (audio_endpoints[i].id == ep_id) { |
| ep = &audio_endpoints[i]; |
| break; |
| } |
| |
| if (!ep) { |
| error("Cound not find opened endpoint"); |
| goto failed; |
| } |
| |
| *epp = ep; |
| |
| payload_len = mtu; |
| if (ep->codec->use_rtp) |
| payload_len -= sizeof(struct rtp_header); |
| |
| ep->fd = fd; |
| |
| codec = ep->codec; |
| codec->init(preset, payload_len, &ep->codec_data); |
| codec->get_config(ep->codec_data, cfg); |
| |
| ep->mp = calloc(mtu, 1); |
| if (!ep->mp) |
| goto failed; |
| |
| if (ep->codec->use_rtp) { |
| struct media_packet_rtp *mp_rtp = |
| (struct media_packet_rtp *) ep->mp; |
| mp_rtp->hdr.v = 2; |
| mp_rtp->hdr.pt = 0x60; |
| mp_rtp->hdr.ssrc = htonl(1); |
| } |
| |
| ep->mp_data_len = payload_len; |
| |
| free(preset); |
| |
| return true; |
| |
| failed: |
| close(fd); |
| free(preset); |
| |
| return false; |
| } |
| |
| static void close_endpoint(struct audio_endpoint *ep) |
| { |
| ipc_close_stream_cmd(ep->id); |
| if (ep->fd >= 0) { |
| close(ep->fd); |
| ep->fd = -1; |
| } |
| |
| free(ep->mp); |
| |
| ep->codec->cleanup(ep->codec_data); |
| ep->codec_data = NULL; |
| } |
| |
| static bool resume_endpoint(struct audio_endpoint *ep) |
| { |
| if (ipc_resume_stream_cmd(ep->id) != AUDIO_STATUS_SUCCESS) |
| return false; |
| |
| ep->samples = 0; |
| ep->resync = false; |
| |
| ep->codec->update_qos(ep->codec_data, QOS_POLICY_DEFAULT); |
| |
| return true; |
| } |
| |
| static void downmix_to_mono(struct a2dp_stream_out *out, const uint8_t *buffer, |
| size_t bytes) |
| { |
| const int16_t *input = (const void *) buffer; |
| int16_t *output = (void *) out->downmix_buf; |
| size_t i, frames; |
| |
| /* PCM 16bit stereo */ |
| frames = bytes / (2 * sizeof(int16_t)); |
| |
| for (i = 0; i < frames; i++) { |
| int16_t l = get_le16(&input[i * 2]); |
| int16_t r = get_le16(&input[i * 2 + 1]); |
| |
| put_le16((l + r) / 2, &output[i]); |
| } |
| } |
| |
| static bool wait_for_endpoint(struct audio_endpoint *ep, bool *writable) |
| { |
| int ret; |
| |
| while (true) { |
| struct pollfd pollfd; |
| |
| pollfd.fd = ep->fd; |
| pollfd.events = POLLOUT; |
| pollfd.revents = 0; |
| |
| ret = poll(&pollfd, 1, 500); |
| |
| if (ret >= 0) { |
| *writable = !!(pollfd.revents & POLLOUT); |
| break; |
| } |
| |
| if (errno != EINTR) { |
| ret = errno; |
| error("poll failed (%d)", ret); |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| static bool write_to_endpoint(struct audio_endpoint *ep, size_t bytes) |
| { |
| struct media_packet *mp = (struct media_packet *) ep->mp; |
| int ret; |
| |
| while (true) { |
| ret = write(ep->fd, mp, bytes); |
| |
| if (ret >= 0) |
| break; |
| |
| /* |
| * this should not happen so let's issue warning, but do not |
| * fail, we can try to write next packet |
| */ |
| if (errno == EAGAIN) { |
| ret = errno; |
| warn("write failed (%d)", ret); |
| break; |
| } |
| |
| if (errno != EINTR) { |
| ret = errno; |
| error("write failed (%d)", ret); |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| static bool write_data(struct a2dp_stream_out *out, const void *buffer, |
| size_t bytes) |
| { |
| struct audio_endpoint *ep = out->ep; |
| struct media_packet *mp = (struct media_packet *) ep->mp; |
| struct media_packet_rtp *mp_rtp = (struct media_packet_rtp *) ep->mp; |
| size_t free_space = ep->mp_data_len; |
| size_t consumed = 0; |
| |
| while (consumed < bytes) { |
| size_t written = 0; |
| ssize_t read; |
| uint32_t samples; |
| int ret; |
| struct timespec current; |
| uint64_t audio_sent, audio_passed; |
| bool do_write = false; |
| |
| /* |
| * prepare media packet in advance so we don't waste time after |
| * wakeup |
| */ |
| if (ep->codec->use_rtp) { |
| mp_rtp->hdr.sequence_number = htons(ep->seq++); |
| mp_rtp->hdr.timestamp = htonl(ep->samples); |
| } |
| read = ep->codec->encode_mediapacket(ep->codec_data, |
| buffer + consumed, |
| bytes - consumed, mp, |
| free_space, &written); |
| |
| /* |
| * not much we can do here, let's just ignore remaining |
| * data and continue |
| */ |
| if (read <= 0) |
| return true; |
| |
| /* calculate where are we and where we should be */ |
| clock_gettime(CLOCK_MONOTONIC, ¤t); |
| if (!ep->samples) |
| memcpy(&ep->start, ¤t, sizeof(ep->start)); |
| audio_sent = ep->samples * 1000000ll / out->cfg.rate; |
| audio_passed = timespec_diff_us(¤t, &ep->start); |
| |
| /* |
| * if we're ahead of stream then wait for next write point, |
| * if we're lagging more than 100ms then stop writing and just |
| * skip data until we're back in sync |
| */ |
| if (audio_sent > audio_passed) { |
| struct timespec anchor; |
| |
| ep->resync = false; |
| |
| timespec_add(&ep->start, audio_sent, &anchor); |
| |
| while (true) { |
| ret = clock_nanosleep(CLOCK_MONOTONIC, |
| TIMER_ABSTIME, &anchor, |
| NULL); |
| |
| if (!ret) |
| break; |
| |
| if (ret != EINTR) { |
| error("clock_nanosleep failed (%d)", |
| ret); |
| return false; |
| } |
| } |
| } else if (!ep->resync) { |
| uint64_t diff = audio_passed - audio_sent; |
| |
| if (diff > MAX_DELAY) { |
| warn("lag is %jums, resyncing", diff / 1000); |
| |
| ep->codec->update_qos(ep->codec_data, |
| QOS_POLICY_DECREASE); |
| ep->resync = true; |
| } |
| } |
| |
| /* we send data only in case codec encoded some data, i.e. some |
| * codecs do internal buffering and output data only if full |
| * frame can be encoded |
| * in resync mode we'll just drop mediapackets |
| */ |
| if (written > 0 && !ep->resync) { |
| /* wait some time for socket to be ready for write, |
| * but we'll just skip writing data if timeout occurs |
| */ |
| if (!wait_for_endpoint(ep, &do_write)) |
| return false; |
| |
| if (do_write) { |
| if (ep->codec->use_rtp) |
| written += sizeof(struct rtp_header); |
| |
| if (!write_to_endpoint(ep, written)) |
| return false; |
| } |
| } |
| |
| /* |
| * AudioFlinger provides 16bit PCM, so sample size is 2 bytes |
| * multiplied by number of channels. Number of channels is |
| * simply number of bits set in channels mask. |
| */ |
| samples = read / (2 * popcount(out->cfg.channels)); |
| ep->samples += samples; |
| consumed += read; |
| } |
| |
| return true; |
| } |
| |
| static ssize_t out_write(struct audio_stream_out *stream, const void *buffer, |
| size_t bytes) |
| { |
| struct a2dp_stream_out *out = (struct a2dp_stream_out *) stream; |
| const void *in_buf = buffer; |
| size_t in_len = bytes; |
| |
| /* just return in case we're closing */ |
| if (out->audio_state == AUDIO_A2DP_STATE_NONE) |
| return -1; |
| |
| /* We can auto-start only from standby */ |
| if (out->audio_state == AUDIO_A2DP_STATE_STANDBY) { |
| DBG("stream in standby, auto-start"); |
| |
| if (!resume_endpoint(out->ep)) |
| return -1; |
| |
| out->audio_state = AUDIO_A2DP_STATE_STARTED; |
| } |
| |
| if (out->audio_state != AUDIO_A2DP_STATE_STARTED) { |
| error("audio: stream not started"); |
| return -1; |
| } |
| |
| if (out->ep->fd < 0) { |
| error("audio: no transport socket"); |
| return -1; |
| } |
| |
| /* |
| * currently Android audioflinger is not able to provide mono stream on |
| * A2DP output so down mixing needs to be done in hal-audio plugin. |
| * |
| * for reference see |
| * AudioFlinger::PlaybackThread::readOutputParameters() |
| * frameworks/av/services/audioflinger/Threads.cpp:1631 |
| */ |
| if (out->cfg.channels == AUDIO_CHANNEL_OUT_MONO) { |
| if (!out->downmix_buf) { |
| error("audio: downmix buffer not initialized"); |
| return -1; |
| } |
| |
| downmix_to_mono(out, buffer, bytes); |
| |
| in_buf = out->downmix_buf; |
| in_len = bytes / 2; |
| } |
| |
| if (!write_data(out, in_buf, in_len)) |
| return -1; |
| |
| return bytes; |
| } |
| |
| static uint32_t out_get_sample_rate(const struct audio_stream *stream) |
| { |
| struct a2dp_stream_out *out = (struct a2dp_stream_out *) stream; |
| |
| DBG(""); |
| |
| return out->cfg.rate; |
| } |
| |
| static int out_set_sample_rate(struct audio_stream *stream, uint32_t rate) |
| { |
| struct a2dp_stream_out *out = (struct a2dp_stream_out *) stream; |
| |
| DBG(""); |
| |
| if (rate != out->cfg.rate) { |
| warn("audio: cannot set sample rate to %d", rate); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| static size_t out_get_buffer_size(const struct audio_stream *stream) |
| { |
| DBG(""); |
| |
| /* |
| * We should return proper buffer size calculated by codec (so each |
| * input buffer is encoded into single media packed) but this does not |
| * work well with AudioFlinger and causes problems. For this reason we |
| * use magic value here and out_write code takes care of splitting |
| * input buffer into multiple media packets. |
| */ |
| return FIXED_BUFFER_SIZE; |
| } |
| |
| static uint32_t out_get_channels(const struct audio_stream *stream) |
| { |
| DBG(""); |
| |
| /* |
| * AudioFlinger can only provide stereo stream, so we return it here and |
| * later we'll downmix this to mono in case codec requires it |
| */ |
| |
| return AUDIO_CHANNEL_OUT_STEREO; |
| } |
| |
| static audio_format_t out_get_format(const struct audio_stream *stream) |
| { |
| struct a2dp_stream_out *out = (struct a2dp_stream_out *) stream; |
| |
| DBG(""); |
| |
| return out->cfg.format; |
| } |
| |
| static int out_set_format(struct audio_stream *stream, audio_format_t format) |
| { |
| DBG(""); |
| return -ENOSYS; |
| } |
| |
| static int out_standby(struct audio_stream *stream) |
| { |
| struct a2dp_stream_out *out = (struct a2dp_stream_out *) stream; |
| |
| DBG(""); |
| |
| if (out->audio_state == AUDIO_A2DP_STATE_STARTED) { |
| if (ipc_suspend_stream_cmd(out->ep->id) != AUDIO_STATUS_SUCCESS) |
| return -1; |
| out->audio_state = AUDIO_A2DP_STATE_STANDBY; |
| } |
| |
| return 0; |
| } |
| |
| static int out_dump(const struct audio_stream *stream, int fd) |
| { |
| DBG(""); |
| return -ENOSYS; |
| } |
| |
| static int out_set_parameters(struct audio_stream *stream, const char *kvpairs) |
| { |
| struct a2dp_stream_out *out = (struct a2dp_stream_out *) stream; |
| char *kvpair; |
| char *str; |
| char *saveptr; |
| bool enter_suspend = false; |
| bool exit_suspend = false; |
| |
| DBG("%s", kvpairs); |
| |
| str = strdup(kvpairs); |
| if (!str) |
| return -ENOMEM; |
| |
| kvpair = strtok_r(str, ";", &saveptr); |
| |
| for (; kvpair && *kvpair; kvpair = strtok_r(NULL, ";", &saveptr)) { |
| char *keyval; |
| |
| keyval = strchr(kvpair, '='); |
| if (!keyval) |
| continue; |
| |
| *keyval = '\0'; |
| keyval++; |
| |
| if (!strcmp(kvpair, "closing")) { |
| if (!strcmp(keyval, "true")) |
| out->audio_state = AUDIO_A2DP_STATE_NONE; |
| } else if (!strcmp(kvpair, "A2dpSuspended")) { |
| if (!strcmp(keyval, "true")) |
| enter_suspend = true; |
| else |
| exit_suspend = true; |
| } |
| } |
| |
| free(str); |
| |
| if (enter_suspend && out->audio_state == AUDIO_A2DP_STATE_STARTED) { |
| if (ipc_suspend_stream_cmd(out->ep->id) != AUDIO_STATUS_SUCCESS) |
| return -1; |
| out->audio_state = AUDIO_A2DP_STATE_SUSPENDED; |
| } |
| |
| if (exit_suspend && out->audio_state == AUDIO_A2DP_STATE_SUSPENDED) |
| out->audio_state = AUDIO_A2DP_STATE_STANDBY; |
| |
| return 0; |
| } |
| |
| static char *out_get_parameters(const struct audio_stream *stream, |
| const char *keys) |
| { |
| DBG(""); |
| return strdup(""); |
| } |
| |
| static uint32_t out_get_latency(const struct audio_stream_out *stream) |
| { |
| struct a2dp_stream_out *out = (struct a2dp_stream_out *) stream; |
| struct audio_endpoint *ep = out->ep; |
| size_t pkt_duration; |
| |
| DBG(""); |
| |
| pkt_duration = ep->codec->get_mediapacket_duration(ep->codec_data); |
| |
| return FIXED_A2DP_PLAYBACK_LATENCY_MS + pkt_duration / 1000; |
| } |
| |
| static int out_set_volume(struct audio_stream_out *stream, float left, |
| float right) |
| { |
| DBG(""); |
| /* volume controlled in audioflinger mixer (digital) */ |
| return -ENOSYS; |
| } |
| |
| static int out_get_render_position(const struct audio_stream_out *stream, |
| uint32_t *dsp_frames) |
| { |
| DBG(""); |
| return -ENOSYS; |
| } |
| |
| static int out_add_audio_effect(const struct audio_stream *stream, |
| effect_handle_t effect) |
| { |
| DBG(""); |
| return -ENOSYS; |
| } |
| |
| static int out_remove_audio_effect(const struct audio_stream *stream, |
| effect_handle_t effect) |
| { |
| DBG(""); |
| return -ENOSYS; |
| } |
| |
| static uint32_t in_get_sample_rate(const struct audio_stream *stream) |
| { |
| DBG(""); |
| return -ENOSYS; |
| } |
| |
| static int in_set_sample_rate(struct audio_stream *stream, uint32_t rate) |
| { |
| DBG(""); |
| return -ENOSYS; |
| } |
| |
| static size_t in_get_buffer_size(const struct audio_stream *stream) |
| { |
| DBG(""); |
| return -ENOSYS; |
| } |
| |
| static uint32_t in_get_channels(const struct audio_stream *stream) |
| { |
| DBG(""); |
| return -ENOSYS; |
| } |
| |
| static audio_format_t in_get_format(const struct audio_stream *stream) |
| { |
| DBG(""); |
| return -ENOSYS; |
| } |
| |
| static int in_set_format(struct audio_stream *stream, audio_format_t format) |
| { |
| DBG(""); |
| return -ENOSYS; |
| } |
| |
| static int in_standby(struct audio_stream *stream) |
| { |
| DBG(""); |
| return -ENOSYS; |
| } |
| |
| static int in_dump(const struct audio_stream *stream, int fd) |
| { |
| DBG(""); |
| return -ENOSYS; |
| } |
| |
| static int in_set_parameters(struct audio_stream *stream, const char *kvpairs) |
| { |
| DBG(""); |
| return -ENOSYS; |
| } |
| |
| static char *in_get_parameters(const struct audio_stream *stream, |
| const char *keys) |
| { |
| DBG(""); |
| return strdup(""); |
| } |
| |
| static int in_set_gain(struct audio_stream_in *stream, float gain) |
| { |
| DBG(""); |
| return -ENOSYS; |
| } |
| |
| static ssize_t in_read(struct audio_stream_in *stream, void *buffer, |
| size_t bytes) |
| { |
| DBG(""); |
| return -ENOSYS; |
| } |
| |
| static uint32_t in_get_input_frames_lost(struct audio_stream_in *stream) |
| { |
| DBG(""); |
| return -ENOSYS; |
| } |
| |
| static int in_add_audio_effect(const struct audio_stream *stream, |
| effect_handle_t effect) |
| { |
| DBG(""); |
| return -ENOSYS; |
| } |
| |
| static int in_remove_audio_effect(const struct audio_stream *stream, |
| effect_handle_t effect) |
| { |
| DBG(""); |
| return -ENOSYS; |
| } |
| |
| static int audio_open_output_stream_real(struct audio_hw_device *dev, |
| audio_io_handle_t handle, |
| audio_devices_t devices, |
| audio_output_flags_t flags, |
| struct audio_config *config, |
| struct audio_stream_out **stream_out, |
| const char *address) |
| { |
| struct a2dp_audio_dev *a2dp_dev = (struct a2dp_audio_dev *) dev; |
| struct a2dp_stream_out *out; |
| |
| out = calloc(1, sizeof(struct a2dp_stream_out)); |
| if (!out) |
| return -ENOMEM; |
| |
| DBG(""); |
| |
| out->stream.common.get_sample_rate = out_get_sample_rate; |
| out->stream.common.set_sample_rate = out_set_sample_rate; |
| out->stream.common.get_buffer_size = out_get_buffer_size; |
| out->stream.common.get_channels = out_get_channels; |
| out->stream.common.get_format = out_get_format; |
| out->stream.common.set_format = out_set_format; |
| out->stream.common.standby = out_standby; |
| out->stream.common.dump = out_dump; |
| out->stream.common.set_parameters = out_set_parameters; |
| out->stream.common.get_parameters = out_get_parameters; |
| out->stream.common.add_audio_effect = out_add_audio_effect; |
| out->stream.common.remove_audio_effect = out_remove_audio_effect; |
| out->stream.get_latency = out_get_latency; |
| out->stream.set_volume = out_set_volume; |
| out->stream.write = out_write; |
| out->stream.get_render_position = out_get_render_position; |
| |
| /* We want to autoselect opened endpoint */ |
| out->ep = NULL; |
| |
| if (!open_endpoint(&out->ep, &out->cfg)) |
| goto fail; |
| |
| DBG("rate=%d channels=%d format=%d", out->cfg.rate, |
| out->cfg.channels, out->cfg.format); |
| |
| if (out->cfg.channels == AUDIO_CHANNEL_OUT_MONO) { |
| out->downmix_buf = malloc(FIXED_BUFFER_SIZE / 2); |
| if (!out->downmix_buf) |
| goto fail; |
| } |
| |
| *stream_out = &out->stream; |
| a2dp_dev->out = out; |
| |
| out->audio_state = AUDIO_A2DP_STATE_STANDBY; |
| |
| return 0; |
| |
| fail: |
| error("audio: cannot open output stream"); |
| free(out); |
| *stream_out = NULL; |
| return -EIO; |
| } |
| |
| #if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) |
| static int audio_open_output_stream(struct audio_hw_device *dev, |
| audio_io_handle_t handle, |
| audio_devices_t devices, |
| audio_output_flags_t flags, |
| struct audio_config *config, |
| struct audio_stream_out **stream_out, |
| const char *address) |
| { |
| return audio_open_output_stream_real(dev, handle, devices, flags, |
| config, stream_out, address); |
| } |
| #else |
| static int audio_open_output_stream(struct audio_hw_device *dev, |
| audio_io_handle_t handle, |
| audio_devices_t devices, |
| audio_output_flags_t flags, |
| struct audio_config *config, |
| struct audio_stream_out **stream_out) |
| { |
| return audio_open_output_stream_real(dev, handle, devices, flags, |
| config, stream_out, NULL); |
| } |
| #endif |
| |
| static void audio_close_output_stream(struct audio_hw_device *dev, |
| struct audio_stream_out *stream) |
| { |
| struct a2dp_audio_dev *a2dp_dev = (struct a2dp_audio_dev *) dev; |
| struct a2dp_stream_out *out = (struct a2dp_stream_out *) stream; |
| |
| DBG(""); |
| |
| close_endpoint(a2dp_dev->out->ep); |
| |
| free(out->downmix_buf); |
| |
| free(stream); |
| a2dp_dev->out = NULL; |
| } |
| |
| static int audio_set_parameters(struct audio_hw_device *dev, |
| const char *kvpairs) |
| { |
| struct a2dp_audio_dev *a2dp_dev = (struct a2dp_audio_dev *) dev; |
| struct a2dp_stream_out *out = a2dp_dev->out; |
| |
| DBG(""); |
| |
| if (!out) |
| return 0; |
| |
| return out->stream.common.set_parameters((struct audio_stream *) out, |
| kvpairs); |
| } |
| |
| static char *audio_get_parameters(const struct audio_hw_device *dev, |
| const char *keys) |
| { |
| DBG(""); |
| return strdup(""); |
| } |
| |
| static int audio_init_check(const struct audio_hw_device *dev) |
| { |
| DBG(""); |
| return 0; |
| } |
| |
| static int audio_set_voice_volume(struct audio_hw_device *dev, float volume) |
| { |
| DBG(""); |
| return -ENOSYS; |
| } |
| |
| static int audio_set_master_volume(struct audio_hw_device *dev, float volume) |
| { |
| DBG(""); |
| return -ENOSYS; |
| } |
| |
| static int audio_set_mode(struct audio_hw_device *dev, int mode) |
| { |
| DBG(""); |
| return -ENOSYS; |
| } |
| |
| static int audio_set_mic_mute(struct audio_hw_device *dev, bool state) |
| { |
| DBG(""); |
| return -ENOSYS; |
| } |
| |
| static int audio_get_mic_mute(const struct audio_hw_device *dev, bool *state) |
| { |
| DBG(""); |
| return -ENOSYS; |
| } |
| |
| static size_t audio_get_input_buffer_size(const struct audio_hw_device *dev, |
| const struct audio_config *config) |
| { |
| DBG(""); |
| return -ENOSYS; |
| } |
| |
| static int audio_open_input_stream_real(struct audio_hw_device *dev, |
| audio_io_handle_t handle, |
| audio_devices_t devices, |
| struct audio_config *config, |
| struct audio_stream_in **stream_in, |
| audio_input_flags_t flags, |
| const char *address, |
| audio_source_t source) |
| { |
| struct audio_stream_in *in; |
| |
| DBG(""); |
| |
| in = calloc(1, sizeof(struct audio_stream_in)); |
| if (!in) |
| return -ENOMEM; |
| |
| in->common.get_sample_rate = in_get_sample_rate; |
| in->common.set_sample_rate = in_set_sample_rate; |
| in->common.get_buffer_size = in_get_buffer_size; |
| in->common.get_channels = in_get_channels; |
| in->common.get_format = in_get_format; |
| in->common.set_format = in_set_format; |
| in->common.standby = in_standby; |
| in->common.dump = in_dump; |
| in->common.set_parameters = in_set_parameters; |
| in->common.get_parameters = in_get_parameters; |
| in->common.add_audio_effect = in_add_audio_effect; |
| in->common.remove_audio_effect = in_remove_audio_effect; |
| in->set_gain = in_set_gain; |
| in->read = in_read; |
| in->get_input_frames_lost = in_get_input_frames_lost; |
| |
| *stream_in = in; |
| |
| return 0; |
| } |
| |
| #if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) |
| static int audio_open_input_stream(struct audio_hw_device *dev, |
| audio_io_handle_t handle, |
| audio_devices_t devices, |
| struct audio_config *config, |
| struct audio_stream_in **stream_in, |
| audio_input_flags_t flags, |
| const char *address, |
| audio_source_t source) |
| { |
| return audio_open_input_stream_real(dev, handle, devices, config, |
| stream_in, flags, address, |
| source); |
| } |
| #else |
| static int audio_open_input_stream(struct audio_hw_device *dev, |
| audio_io_handle_t handle, |
| audio_devices_t devices, |
| struct audio_config *config, |
| struct audio_stream_in **stream_in) |
| { |
| return audio_open_input_stream_real(dev, handle, devices, config, |
| stream_in, 0, NULL, 0); |
| } |
| #endif |
| |
| static void audio_close_input_stream(struct audio_hw_device *dev, |
| struct audio_stream_in *stream_in) |
| { |
| DBG(""); |
| free(stream_in); |
| } |
| |
| static int audio_dump(const audio_hw_device_t *device, int fd) |
| { |
| DBG(""); |
| return -ENOSYS; |
| } |
| |
| #if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) |
| static int set_master_mute(struct audio_hw_device *dev, bool mute) |
| { |
| DBG(""); |
| return -ENOSYS; |
| } |
| |
| static int get_master_mute(struct audio_hw_device *dev, bool *mute) |
| { |
| DBG(""); |
| return -ENOSYS; |
| } |
| |
| static int create_audio_patch(struct audio_hw_device *dev, |
| unsigned int num_sources, |
| const struct audio_port_config *sources, |
| unsigned int num_sinks, |
| const struct audio_port_config *sinks, |
| audio_patch_handle_t *handle) |
| { |
| DBG(""); |
| return -ENOSYS; |
| } |
| |
| static int release_audio_patch(struct audio_hw_device *dev, |
| audio_patch_handle_t handle) |
| { |
| DBG(""); |
| return -ENOSYS; |
| } |
| |
| static int get_audio_port(struct audio_hw_device *dev, struct audio_port *port) |
| { |
| DBG(""); |
| return -ENOSYS; |
| } |
| |
| static int set_audio_port_config(struct audio_hw_device *dev, |
| const struct audio_port_config *config) |
| { |
| DBG(""); |
| return -ENOSYS; |
| } |
| #endif |
| |
| static int audio_close(hw_device_t *device) |
| { |
| struct a2dp_audio_dev *a2dp_dev = (struct a2dp_audio_dev *)device; |
| unsigned int i; |
| |
| DBG(""); |
| |
| unregister_endpoints(); |
| |
| for (i = 0; i < NUM_CODECS; i++) { |
| const struct audio_codec *codec = audio_codecs[i].get_codec(); |
| |
| if (!audio_codecs[i].loaded) |
| continue; |
| |
| if (codec->unload) |
| codec->unload(); |
| |
| audio_codecs[i].loaded = false; |
| } |
| |
| shutdown(listen_sk, SHUT_RDWR); |
| shutdown(audio_sk, SHUT_RDWR); |
| |
| pthread_join(ipc_th, NULL); |
| |
| close(listen_sk); |
| listen_sk = -1; |
| |
| free(a2dp_dev); |
| return 0; |
| } |
| |
| static void *ipc_handler(void *data) |
| { |
| bool done = false; |
| struct pollfd pfd; |
| int sk; |
| |
| DBG(""); |
| |
| while (!done) { |
| DBG("Waiting for connection ..."); |
| |
| sk = accept(listen_sk, NULL, NULL); |
| if (sk < 0) { |
| int err = errno; |
| |
| if (err == EINTR) |
| continue; |
| |
| if (err != ECONNABORTED && err != EINVAL) |
| error("audio: Failed to accept socket: %d (%s)", |
| err, strerror(err)); |
| |
| break; |
| } |
| |
| pthread_mutex_lock(&sk_mutex); |
| audio_sk = sk; |
| pthread_mutex_unlock(&sk_mutex); |
| |
| DBG("Audio IPC: Connected"); |
| |
| if (register_endpoints() != AUDIO_STATUS_SUCCESS) { |
| error("audio: Failed to register endpoints"); |
| |
| unregister_endpoints(); |
| |
| pthread_mutex_lock(&sk_mutex); |
| shutdown(audio_sk, SHUT_RDWR); |
| close(audio_sk); |
| audio_sk = -1; |
| pthread_mutex_unlock(&sk_mutex); |
| |
| continue; |
| } |
| |
| memset(&pfd, 0, sizeof(pfd)); |
| pfd.fd = audio_sk; |
| pfd.events = POLLHUP | POLLERR | POLLNVAL; |
| |
| /* Check if socket is still alive. Empty while loop.*/ |
| while (poll(&pfd, 1, -1) < 0 && errno == EINTR); |
| |
| info("Audio HAL: Socket closed"); |
| |
| pthread_mutex_lock(&sk_mutex); |
| close(audio_sk); |
| audio_sk = -1; |
| pthread_mutex_unlock(&sk_mutex); |
| } |
| |
| /* audio_sk is closed at this point, just cleanup endpoints states */ |
| memset(audio_endpoints, 0, sizeof(audio_endpoints)); |
| |
| info("Closing Audio IPC thread"); |
| return NULL; |
| } |
| |
| static int audio_ipc_init(void) |
| { |
| struct sockaddr_un addr; |
| int err; |
| int sk; |
| |
| DBG(""); |
| |
| sk = socket(PF_LOCAL, SOCK_SEQPACKET, 0); |
| if (sk < 0) { |
| err = -errno; |
| error("audio: Failed to create socket: %d (%s)", -err, |
| strerror(-err)); |
| return err; |
| } |
| |
| memset(&addr, 0, sizeof(addr)); |
| addr.sun_family = AF_UNIX; |
| |
| memcpy(addr.sun_path, BLUEZ_AUDIO_SK_PATH, |
| sizeof(BLUEZ_AUDIO_SK_PATH)); |
| |
| if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { |
| err = -errno; |
| error("audio: Failed to bind socket: %d (%s)", -err, |
| strerror(-err)); |
| goto failed; |
| } |
| |
| if (listen(sk, 1) < 0) { |
| err = -errno; |
| error("audio: Failed to listen on the socket: %d (%s)", -err, |
| strerror(-err)); |
| goto failed; |
| } |
| |
| listen_sk = sk; |
| |
| err = pthread_create(&ipc_th, NULL, ipc_handler, NULL); |
| if (err) { |
| err = -err; |
| ipc_th = 0; |
| error("audio: Failed to start Audio IPC thread: %d (%s)", |
| -err, strerror(-err)); |
| goto failed; |
| } |
| |
| return 0; |
| |
| failed: |
| close(sk); |
| return err; |
| } |
| |
| static int audio_open(const hw_module_t *module, const char *name, |
| hw_device_t **device) |
| { |
| struct a2dp_audio_dev *a2dp_dev; |
| size_t i; |
| int err; |
| |
| DBG(""); |
| |
| if (strcmp(name, AUDIO_HARDWARE_INTERFACE)) { |
| error("audio: interface %s not matching [%s]", name, |
| AUDIO_HARDWARE_INTERFACE); |
| return -EINVAL; |
| } |
| |
| err = audio_ipc_init(); |
| if (err < 0) |
| return err; |
| |
| a2dp_dev = calloc(1, sizeof(struct a2dp_audio_dev)); |
| if (!a2dp_dev) |
| return -ENOMEM; |
| |
| a2dp_dev->dev.common.tag = HARDWARE_DEVICE_TAG; |
| a2dp_dev->dev.common.version = AUDIO_DEVICE_API_VERSION_CURRENT; |
| a2dp_dev->dev.common.module = (struct hw_module_t *) module; |
| a2dp_dev->dev.common.close = audio_close; |
| |
| a2dp_dev->dev.init_check = audio_init_check; |
| a2dp_dev->dev.set_voice_volume = audio_set_voice_volume; |
| a2dp_dev->dev.set_master_volume = audio_set_master_volume; |
| a2dp_dev->dev.set_mode = audio_set_mode; |
| a2dp_dev->dev.set_mic_mute = audio_set_mic_mute; |
| a2dp_dev->dev.get_mic_mute = audio_get_mic_mute; |
| a2dp_dev->dev.set_parameters = audio_set_parameters; |
| a2dp_dev->dev.get_parameters = audio_get_parameters; |
| a2dp_dev->dev.get_input_buffer_size = audio_get_input_buffer_size; |
| a2dp_dev->dev.open_output_stream = audio_open_output_stream; |
| a2dp_dev->dev.close_output_stream = audio_close_output_stream; |
| a2dp_dev->dev.open_input_stream = audio_open_input_stream; |
| a2dp_dev->dev.close_input_stream = audio_close_input_stream; |
| a2dp_dev->dev.dump = audio_dump; |
| #if ANDROID_VERSION >= PLATFORM_VER(5, 0, 0) |
| a2dp_dev->dev.set_master_mute = set_master_mute; |
| a2dp_dev->dev.get_master_mute = get_master_mute; |
| a2dp_dev->dev.create_audio_patch = create_audio_patch; |
| a2dp_dev->dev.release_audio_patch = release_audio_patch; |
| a2dp_dev->dev.get_audio_port = get_audio_port; |
| a2dp_dev->dev.set_audio_port_config = set_audio_port_config; |
| #endif |
| |
| for (i = 0; i < NUM_CODECS; i++) { |
| const struct audio_codec *codec = audio_codecs[i].get_codec(); |
| |
| if (codec->load && !codec->load()) |
| continue; |
| |
| audio_codecs[i].loaded = true; |
| } |
| |
| /* |
| * Note that &a2dp_dev->dev.common is the same pointer as a2dp_dev. |
| * This results from the structure of following structs:a2dp_audio_dev, |
| * audio_hw_device. We will rely on this later in the code. |
| */ |
| *device = &a2dp_dev->dev.common; |
| |
| return 0; |
| } |
| |
| static struct hw_module_methods_t hal_module_methods = { |
| .open = audio_open, |
| }; |
| |
| struct audio_module HAL_MODULE_INFO_SYM = { |
| .common = { |
| .tag = HARDWARE_MODULE_TAG, |
| .version_major = 1, |
| .version_minor = 0, |
| .id = AUDIO_HARDWARE_MODULE_ID, |
| .name = "A2DP Bluez HW HAL", |
| .author = "Intel Corporation", |
| .methods = &hal_module_methods, |
| }, |
| }; |