mirror of
https://github.com/google/pebble.git
synced 2025-05-05 09:21:40 -04:00
204 lines
6.5 KiB
C
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);
|
|
}
|