pebble/src/fw/services/normal/audio_endpoint.c
2025-01-27 11:38:16 -08:00

204 lines
6.5 KiB
C

/*
* 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);
}