/* * Copyright 2024 Google LLC * * 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 "audio_endpoint.h" #include "audio_endpoint_private.h" #include "comm/bt_lock.h" #include "services/common/comm_session/session_send_buffer.h" #include "services/common/new_timer/new_timer.h" #include "system/logging.h" #include "system/passert.h" #include "util/circular_buffer.h" #define AUDIO_ENDPOINT (10000) #define ACTIVE_MODE_TIMEOUT (10000) #define ACTIVE_MODE_START_BUFFER (100) _Static_assert(ACTIVE_MODE_TIMEOUT > ACTIVE_MODE_START_BUFFER, "ACTIVE_MODE_TIMEOUT must be greater than ACTIVE_MODE_START_BUFFER"); typedef struct { AudioEndpointSessionId id; AudioEndpointSetupCompleteCallback setup_completed; AudioEndpointStopTransferCallback stop_transfer; TimerID active_mode_trigger; } AudioEndpointSession; static AudioEndpointSessionId s_session_id = AUDIO_ENDPOINT_SESSION_INVALID_ID; static AudioEndpointSession s_session; static uint32_t s_dropped_frames; static void prv_session_deinit(bool call_stop_handler) { bt_lock(); if (call_stop_handler && s_session.stop_transfer) { s_session.stop_transfer(s_session.id); } if (s_session.active_mode_trigger != TIMER_INVALID_ID) { new_timer_delete(s_session.active_mode_trigger); s_session.active_mode_trigger = TIMER_INVALID_ID; CommSession *comm_session = comm_session_get_system_session(); comm_session_set_responsiveness( comm_session, BtConsumerPpAudioEndpoint, ResponseTimeMax, 0); } s_session.id = AUDIO_ENDPOINT_SESSION_INVALID_ID; s_session.setup_completed = NULL; s_session.stop_transfer = NULL; bt_unlock(); if (s_dropped_frames > 0) { PBL_LOG(LOG_LEVEL_INFO, "Dropped %"PRIu32" frames during audio transfer", s_dropped_frames); } } #ifndef PLATFORM_TINTIN void audio_endpoint_protocol_msg_callback(CommSession *session, const uint8_t* data, size_t size) { MsgId msg_id = data[0]; if (size >= sizeof(StopTransferMsg) && msg_id == MsgIdStopTransfer) { StopTransferMsg *msg = (StopTransferMsg *)data; if (msg->session_id == s_session.id) { prv_session_deinit(true /* call_stop_handler */); } else { PBL_LOG(LOG_LEVEL_WARNING, "Received mismatching session id: %u vs %u", msg->session_id, s_session.id); } } } #else void audio_endpoint_protocol_msg_callback(CommSession *session, const uint8_t* data, size_t size) { } #endif static void prv_responsiveness_granted_handler(void) { if (s_session.id == AUDIO_ENDPOINT_SESSION_INVALID_ID) { return; // Party's over } AudioEndpointSetupCompleteCallback cb = NULL; AudioEndpointSessionId id = AUDIO_ENDPOINT_SESSION_INVALID_ID; bt_lock(); // We're repeatedly calling comm_session_set_responsiveness_ext, but we only need to call the // completed handler the first time the requested responsiveness takes effect: if (s_session.setup_completed) { cb = s_session.setup_completed; id = s_session.id; s_session.setup_completed = NULL; } bt_unlock(); if (cb) { cb(id); } } static void prv_start_active_mode(void *data) { CommSession *comm_session = comm_session_get_system_session(); comm_session_set_responsiveness_ext(comm_session, BtConsumerPpAudioEndpoint, ResponseTimeMin, MIN_LATENCY_MODE_TIMEOUT_AUDIO_SECS, prv_responsiveness_granted_handler); } AudioEndpointSessionId audio_endpoint_setup_transfer( AudioEndpointSetupCompleteCallback setup_completed, AudioEndpointStopTransferCallback stop_transfer) { if (s_session.id != AUDIO_ENDPOINT_SESSION_INVALID_ID) { return AUDIO_ENDPOINT_SESSION_INVALID_ID; } bt_lock(); s_session.id = ++s_session_id; s_session.setup_completed = setup_completed; s_session.stop_transfer = stop_transfer; s_session.active_mode_trigger = new_timer_create(); s_dropped_frames = 0; // restart active mode before it expires, this way it will never be off during the transfer new_timer_start(s_session.active_mode_trigger, ACTIVE_MODE_TIMEOUT - ACTIVE_MODE_START_BUFFER, prv_start_active_mode, NULL, TIMER_START_FLAG_REPEATING); bt_unlock(); prv_start_active_mode(NULL); return s_session.id; } void audio_endpoint_add_frame(AudioEndpointSessionId session_id, uint8_t *frame, uint8_t frame_size) { PBL_ASSERTN(session_id != AUDIO_ENDPOINT_SESSION_INVALID_ID); if (s_session.id != session_id) { return; } CommSession *comm_session = comm_session_get_system_session(); SendBuffer *sb = comm_session_send_buffer_begin_write(comm_session, AUDIO_ENDPOINT, sizeof(DataTransferMsg) + frame_size + 1, 0 /* timeout_ms, never block */); if (!sb) { s_dropped_frames++; PBL_LOG(LOG_LEVEL_DEBUG, "Dropping a frame..."); return; } uint8_t header[sizeof(DataTransferMsg) + sizeof(uint8_t) /* frame_size */]; DataTransferMsg *msg = (DataTransferMsg *) header; *msg = (const DataTransferMsg) { .msg_id = MsgIdDataTransfer, .session_id = session_id, .frame_count = 1, }; msg->frames[0] = frame_size; comm_session_send_buffer_write(sb, header, sizeof(header)); comm_session_send_buffer_write(sb, frame, frame_size); comm_session_send_buffer_end_write(sb); } void audio_endpoint_cancel_transfer(AudioEndpointSessionId session_id) { PBL_ASSERTN(session_id != AUDIO_ENDPOINT_SESSION_INVALID_ID); if (s_session.id != session_id) { return; } prv_session_deinit(false /* call_stop_handler */); } void audio_endpoint_stop_transfer(AudioEndpointSessionId session_id) { PBL_ASSERTN(session_id != AUDIO_ENDPOINT_SESSION_INVALID_ID); if (s_session.id != session_id) { return; } StopTransferMsg msg = (const StopTransferMsg) { .msg_id = MsgIdStopTransfer, .session_id = session_id, }; prv_session_deinit(false /* call_stop_handler */); comm_session_send_data(comm_session_get_system_session(), AUDIO_ENDPOINT, (const uint8_t *) &msg, sizeof(msg), COMM_SESSION_DEFAULT_TIMEOUT); }