From 350e9cdbd7ce9a80aec07f6d76976ecbcba53c83 Mon Sep 17 00:00:00 2001 From: Yu-Hsuan Hsu Date: Thu, 31 Dec 2020 15:31:38 +0800 Subject: CRAS: Library ABI for stream callback Make the stream callback support version skew. BUG=b:176570789 TEST=Verified the audio work fine Change-Id: Ie6e8ea0081866fc9ad99753dc1cf80415ed44d6f Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/third_party/adhd/+/2605941 Reviewed-by: Cheng-Yi Chiang Commit-Queue: Yu-Hsuan Hsu Tested-by: Yu-Hsuan Hsu --- cras/src/libcras/cras_client.c | 135 +++++++++++++++++++++++++++++++++--- cras/src/libcras/cras_client.h | 106 +++++++++++++++++++++++++--- cras/src/tests/cras_abi_unittest.cc | 96 +++++++++++++++++++++++-- 3 files changed, 311 insertions(+), 26 deletions(-) diff --git a/cras/src/libcras/cras_client.c b/cras/src/libcras/cras_client.c index f8891477..c351a80a 100644 --- a/cras/src/libcras/cras_client.c +++ b/cras/src/libcras/cras_client.c @@ -119,7 +119,8 @@ struct thread_state { }; /* Parameters used when setting up a capture or playback stream. See comment - * above cras_client_create_stream_params in the header for descriptions. */ + * above cras_client_stream_params_create or libcras_stream_params_set in the + * header for descriptions. */ struct cras_stream_params { enum CRAS_STREAM_DIRECTION direction; size_t buffer_frames; @@ -133,6 +134,7 @@ struct cras_stream_params { cras_unified_cb_t unified_cb; cras_error_cb_t err_cb; struct cras_audio_format format; + libcras_stream_cb_t stream_cb; }; /* Represents an attached audio stream. @@ -274,6 +276,92 @@ struct cras_hotword_handle { void *user_data; }; +struct cras_stream_cb_data { + cras_stream_id_t stream_id; + enum CRAS_STREAM_DIRECTION direction; + uint8_t *buf; + unsigned int frames; + struct timespec sample_ts; + void *user_arg; +}; + +int stream_cb_get_stream_id(struct cras_stream_cb_data *data, + cras_stream_id_t *id) +{ + *id = data->stream_id; + return 0; +} + +int stream_cb_get_buf(struct cras_stream_cb_data *data, uint8_t **buf) +{ + *buf = data->buf; + return 0; +} + +int stream_cb_get_frames(struct cras_stream_cb_data *data, unsigned int *frames) +{ + *frames = data->frames; + return 0; +} + +int stream_cb_get_latency(struct cras_stream_cb_data *data, + struct timespec *latency) +{ + if (data->direction == CRAS_STREAM_INPUT) + cras_client_calc_capture_latency(&data->sample_ts, latency); + else + cras_client_calc_playback_latency(&data->sample_ts, latency); + return 0; +} + +int stream_cb_get_user_arg(struct cras_stream_cb_data *data, void **user_arg) +{ + *user_arg = data->user_arg; + return 0; +} + +struct libcras_stream_cb_data * +libcras_stream_cb_data_create(cras_stream_id_t stream_id, + enum CRAS_STREAM_DIRECTION direction, + uint8_t *buf, unsigned int frames, + struct timespec sample_ts, void *user_arg) +{ + struct libcras_stream_cb_data *data = + (struct libcras_stream_cb_data *)calloc( + 1, sizeof(struct libcras_stream_cb_data)); + if (!data) { + syslog(LOG_ERR, "cras_client: calloc: %s", strerror(errno)); + return NULL; + } + data->data_ = (struct cras_stream_cb_data *)calloc( + 1, sizeof(struct cras_stream_cb_data)); + if (!data->data_) { + syslog(LOG_ERR, "cras_client: calloc: %s", strerror(errno)); + free(data); + return NULL; + } + data->api_version = CRAS_API_VERSION; + data->get_stream_id = stream_cb_get_stream_id; + data->get_buf = stream_cb_get_buf; + data->get_frames = stream_cb_get_frames; + data->get_latency = stream_cb_get_latency; + data->get_user_arg = stream_cb_get_user_arg; + data->data_->stream_id = stream_id; + data->data_->direction = direction; + data->data_->buf = buf; + data->data_->frames = frames; + data->data_->sample_ts = sample_ts; + data->data_->user_arg = user_arg; + return data; +} + +void libcras_stream_cb_data_destroy(struct libcras_stream_cb_data *data) +{ + if (data) + free(data->data_); + free(data); +} + /* * Local Helpers */ @@ -1084,6 +1172,7 @@ static int handle_capture_data_ready(struct client_stream *stream, uint8_t *captured_frames; struct timespec ts; int rc = 0; + struct libcras_stream_cb_data *data; config = stream->config; /* If this message is for an output stream, log error and drop it. */ @@ -1098,14 +1187,24 @@ static int handle_capture_data_ready(struct client_stream *stream, cras_timespec_to_timespec(&ts, &stream->shm->header->ts); - if (config->unified_cb) + if (config->stream_cb) { + data = libcras_stream_cb_data_create( + stream->id, stream->direction, captured_frames, + num_frames, ts, config->user_data); + if (!data) + return -errno; + frames = config->stream_cb(data); + libcras_stream_cb_data_destroy(data); + data = NULL; + } else if (config->unified_cb) { frames = config->unified_cb(stream->client, stream->id, captured_frames, NULL, num_frames, &ts, NULL, config->user_data); - else + } else { frames = config->aud_cb(stream->client, stream->id, captured_frames, num_frames, &ts, config->user_data); + } if (frames < 0) { send_stream_message(stream, CLIENT_STREAM_EOF); rc = frames; @@ -1152,6 +1251,7 @@ static int handle_playback_request(struct client_stream *stream, struct cras_stream_params *config; struct cras_audio_shm *shm = stream->shm; struct timespec ts; + struct libcras_stream_cb_data *data; config = stream->config; @@ -1169,13 +1269,24 @@ static int handle_playback_request(struct client_stream *stream, cras_timespec_to_timespec(&ts, &shm->header->ts); /* Get samples from the user */ - if (config->unified_cb) + if (config->stream_cb) { + data = libcras_stream_cb_data_create(stream->id, + stream->direction, buf, + num_frames, ts, + config->user_data); + if (!data) + return -errno; + frames = config->stream_cb(data); + libcras_stream_cb_data_destroy(data); + data = NULL; + } else if (config->unified_cb) { frames = config->unified_cb(stream->client, stream->id, NULL, buf, num_frames, NULL, &ts, config->user_data); - else + } else { frames = config->aud_cb(stream->client, stream->id, buf, num_frames, &ts, config->user_data); + } if (frames < 0) { send_stream_message(stream, CLIENT_STREAM_EOF); rc = frames; @@ -2255,6 +2366,7 @@ struct cras_stream_params *cras_client_stream_params_create( params->user_data = user_data; params->aud_cb = aud_cb; params->unified_cb = 0; + params->stream_cb = 0; params->err_cb = err_cb; memcpy(&(params->format), format, sizeof(*format)); return params; @@ -2328,6 +2440,7 @@ struct cras_stream_params *cras_client_unified_params_create( params->user_data = user_data; params->aud_cb = 0; params->unified_cb = unified_cb; + params->stream_cb = 0; params->err_cb = err_cb; memcpy(&(params->format), format, sizeof(*format)); @@ -2350,7 +2463,8 @@ static inline int cras_client_send_add_stream_command_message( if (client == NULL || config == NULL || stream_id_out == NULL) return -EINVAL; - if (config->aud_cb == NULL && config->unified_cb == NULL) + if (config->stream_cb == NULL && config->aud_cb == NULL && + config->unified_cb == NULL) return -EINVAL; if (config->err_cb == NULL) @@ -3851,9 +3965,9 @@ int stream_params_set(struct cras_stream_params *params, size_t buffer_frames, size_t cb_threshold, enum CRAS_STREAM_TYPE stream_type, enum CRAS_CLIENT_TYPE client_type, uint32_t flags, - void *user_data, cras_unified_cb_t unified_cb, - cras_playback_cb_t aud_cb, cras_error_cb_t err_cb, - size_t rate, snd_pcm_format_t format, size_t num_channels) + void *user_data, libcras_stream_cb_t stream_cb, + cras_error_cb_t err_cb, size_t rate, + snd_pcm_format_t format, size_t num_channels) { params->direction = direction; params->buffer_frames = buffer_frames; @@ -3862,8 +3976,7 @@ int stream_params_set(struct cras_stream_params *params, params->client_type = client_type; params->flags = flags; params->user_data = user_data; - params->unified_cb = unified_cb; - params->aud_cb = aud_cb; + params->stream_cb = stream_cb; params->err_cb = err_cb; params->format.frame_rate = rate; params->format.format = format; diff --git a/cras/src/libcras/cras_client.h b/cras/src/libcras/cras_client.h index 2a674207..b60d2697 100644 --- a/cras/src/libcras/cras_client.h +++ b/cras/src/libcras/cras_client.h @@ -1349,6 +1349,21 @@ struct libcras_client { float volume_scaler); }; +struct cras_stream_cb_data; +struct libcras_stream_cb_data { + int api_version; + struct cras_stream_cb_data *data_; + int (*get_stream_id)(struct cras_stream_cb_data *data, + cras_stream_id_t *id); + int (*get_buf)(struct cras_stream_cb_data *data, uint8_t **buf); + int (*get_frames)(struct cras_stream_cb_data *data, + unsigned int *frames); + int (*get_latency)(struct cras_stream_cb_data *data, + struct timespec *latency); + int (*get_user_arg)(struct cras_stream_cb_data *data, void **user_arg); +}; +typedef int (*libcras_stream_cb_t)(struct libcras_stream_cb_data *data); + struct libcras_stream_params { int api_version; struct cras_stream_params *params_; @@ -1356,9 +1371,9 @@ struct libcras_stream_params { enum CRAS_STREAM_DIRECTION direction, size_t buffer_frames, size_t cb_threshold, enum CRAS_STREAM_TYPE stream_type, enum CRAS_CLIENT_TYPE client_type, uint32_t flags, - void *user_data, cras_unified_cb_t unified_cb, - cras_playback_cb_t aud_cb, cras_error_cb_t err_cb, - size_t rate, snd_pcm_format_t format, size_t num_channels); + void *user_data, libcras_stream_cb_t stream_cb, + cras_error_cb_t err_cb, size_t rate, snd_pcm_format_t format, + size_t num_channels); int (*set_channel_layout)(struct cras_stream_params *params, int length, const int8_t *layout); void (*enable_aec)(struct cras_stream_params *params); @@ -1545,8 +1560,8 @@ void libcras_stream_params_destroy(struct libcras_stream_params *params); * client_type - The client type, like Chrome or CrOSVM. * flags - Currently only used for CRAS_INPUT_STREAM_FLAG. * user_data - Pointer that will be passed to the callback. - * unified_cb - The playback callback. Called when audio is needed. - * aud_cb - The capture callback. Called when audio is ready. + * stream_cb - The audio callback. Called when audio is needed(playback) or + * ready(capture). * err_cb - Called when there is an error with the stream. * rate - The sample rate of the audio stream. * format - The format of the audio stream. @@ -1559,13 +1574,12 @@ inline int libcras_stream_params_set( enum CRAS_STREAM_DIRECTION direction, size_t buffer_frames, size_t cb_threshold, enum CRAS_STREAM_TYPE stream_type, enum CRAS_CLIENT_TYPE client_type, uint32_t flags, void *user_data, - cras_unified_cb_t unified_cb, cras_playback_cb_t aud_cb, - cras_error_cb_t err_cb, size_t rate, snd_pcm_format_t format, - size_t num_channels) + libcras_stream_cb_t stream_cb, cras_error_cb_t err_cb, size_t rate, + snd_pcm_format_t format, size_t num_channels) { return params->set(params->params_, direction, buffer_frames, cb_threshold, stream_type, client_type, flags, - user_data, unified_cb, aud_cb, err_cb, rate, format, + user_data, stream_cb, err_cb, rate, format, num_channels); } @@ -1600,6 +1614,80 @@ libcras_stream_params_enable_aec(struct libcras_stream_params *params) return 0; } +/* + * Gets stream id from the callback data. + * Args: + * data - The pointer passed to the callback function. + * id - The pointer to save the stream id. + * Returns: + * 0 on success negative error code on failure (from errno.h). + */ +inline int +libcras_stream_cb_data_get_stream_id(struct libcras_stream_cb_data *data, + cras_stream_id_t *id) +{ + return data->get_stream_id(data->data_, id); +} + +/* + * Gets stream buf from the callback data. + * Args: + * data - The pointer passed to the callback function. + * buf - The pointer to save the stream buffer. + * Returns: + * 0 on success negative error code on failure (from errno.h). + */ +inline int libcras_stream_cb_data_get_buf(struct libcras_stream_cb_data *data, + uint8_t **buf) +{ + return data->get_buf(data->data_, buf); +} + +/* + * Gets how many frames to read or play from the callback data. + * Args: + * data - The pointer passed to the callback function. + * frames - The pointer to save the number of frames. + * Returns: + * 0 on success negative error code on failure (from errno.h). + */ +inline int +libcras_stream_cb_data_get_frames(struct libcras_stream_cb_data *data, + unsigned int *frames) +{ + return data->get_frames(data->data_, frames); +} + +/* + * Gets the latency from the callback data. + * Args: + * data - The pointer passed to the callback function. + * frames - The timespec pointer to save the latency. + * Returns: + * 0 on success negative error code on failure (from errno.h). + */ +inline int +libcras_stream_cb_data_get_latency(struct libcras_stream_cb_data *data, + struct timespec *latency) +{ + return data->get_latency(data->data_, latency); +} + +/* + * Gets the user data from the callback data. + * Args: + * data - The pointer passed to the callback function. + * frames - The pointer to save the user data. + * Returns: + * 0 on success negative error code on failure (from errno.h). + */ +inline int +libcras_stream_cb_data_get_usr_arg(struct libcras_stream_cb_data *data, + void **user_arg) +{ + return data->get_user_arg(data->data_, user_arg); +} + #ifdef __cplusplus } #endif diff --git a/cras/src/tests/cras_abi_unittest.cc b/cras/src/tests/cras_abi_unittest.cc index 4b61166e..d566a9b7 100644 --- a/cras/src/tests/cras_abi_unittest.cc +++ b/cras/src/tests/cras_abi_unittest.cc @@ -13,18 +13,60 @@ inline int libcras_unsupported_func(struct libcras_client* client) { CHECK_VERSION(client, INT_MAX); return 0; } + +cras_stream_id_t cb_stream_id; +uint8_t* cb_buf; +unsigned int cb_frames; +struct timespec cb_latency; +void* cb_usr_arg; +int get_stream_cb_called; +struct timespec now; + +int get_stream_cb(struct libcras_stream_cb_data* data) { + get_stream_cb_called++; + EXPECT_NE((void*)NULL, data); + EXPECT_EQ(0, libcras_stream_cb_data_get_stream_id(data, &cb_stream_id)); + EXPECT_EQ(0, libcras_stream_cb_data_get_buf(data, &cb_buf)); + EXPECT_EQ(0, libcras_stream_cb_data_get_frames(data, &cb_frames)); + EXPECT_EQ(0, libcras_stream_cb_data_get_latency(data, &cb_latency)); + EXPECT_EQ(0, libcras_stream_cb_data_get_usr_arg(data, &cb_usr_arg)); + return 0; +} } namespace { +class CrasAbiTestSuite : public testing::Test { + protected: + struct cras_audio_shm* InitShm(int frames) { + struct cras_audio_shm* shm = + static_cast(calloc(1, sizeof(*shm))); + shm->header = + static_cast(calloc(1, sizeof(*shm->header))); + cras_shm_set_frame_bytes(shm, 4); + uint32_t used_size = frames * 4; + cras_shm_set_used_size(shm, used_size); + shm->samples_info.length = used_size * 2; + memcpy(&shm->header->config, &shm->config, sizeof(shm->config)); + return shm; + } + + void DestroyShm(struct cras_audio_shm* shm) { + if (shm) + free(shm->header); + free(shm); + } -TEST(CrasAbiTestSuite, CheckUnsupportedFunction) { + virtual void SetUp() { get_stream_cb_called = 0; } +}; + +TEST_F(CrasAbiTestSuite, CheckUnsupportedFunction) { auto* client = libcras_client_create(); EXPECT_NE((void*)NULL, client); EXPECT_EQ(-ENOSYS, libcras_unsupported_func(client)); libcras_client_destroy(client); } -TEST(CrasAbiTestSuite, BasicStream) { +TEST_F(CrasAbiTestSuite, BasicStream) { auto* client = libcras_client_create(); EXPECT_NE((void*)NULL, client); auto* stream = libcras_stream_params_create(); @@ -32,10 +74,10 @@ TEST(CrasAbiTestSuite, BasicStream) { /* Returns timeout because there is no real CRAS server in unittest. */ EXPECT_EQ(-ETIMEDOUT, libcras_client_connect_timeout(client, 0)); EXPECT_EQ(0, libcras_client_run_thread(client)); - EXPECT_EQ(0, libcras_stream_params_set( - stream, CRAS_STREAM_INPUT, 480, 480, - CRAS_STREAM_TYPE_DEFAULT, CRAS_CLIENT_TYPE_TEST, 0, NULL, - NULL, NULL, NULL, 48000, SND_PCM_FORMAT_S16, 2)); + EXPECT_EQ(0, libcras_stream_params_set(stream, CRAS_STREAM_INPUT, 480, 480, + CRAS_STREAM_TYPE_DEFAULT, + CRAS_CLIENT_TYPE_TEST, 0, NULL, NULL, + NULL, 48000, SND_PCM_FORMAT_S16, 2)); cras_stream_id_t id; /* Fails to add a stream because the stream callback is not set. */ EXPECT_EQ(-EINVAL, libcras_client_add_pinned_stream(client, 0, &id, stream)); @@ -47,9 +89,51 @@ TEST(CrasAbiTestSuite, BasicStream) { libcras_client_destroy(client); } +TEST_F(CrasAbiTestSuite, StreamCallback) { + struct client_stream stream; + struct cras_stream_params params; + stream.id = 0x123; + stream.direction = CRAS_STREAM_INPUT; + stream.flags = 0; + stream.config = ¶ms; + params.stream_cb = get_stream_cb; + params.cb_threshold = 480; + params.user_data = (void*)0x321; + stream.shm = InitShm(960); + stream.shm->header->write_offset[0] = 960 * 4; + stream.shm->header->write_buf_idx = 0; + stream.shm->header->read_offset[0] = 0; + stream.shm->header->read_buf_idx = 0; + now.tv_sec = 100; + now.tv_nsec = 0; + stream.shm->header->ts.tv_sec = 90; + stream.shm->header->ts.tv_nsec = 0; + + handle_capture_data_ready(&stream, 480); + + EXPECT_EQ(1, get_stream_cb_called); + EXPECT_EQ(stream.id, cb_stream_id); + EXPECT_EQ(cras_shm_get_write_buffer_base(stream.shm), cb_buf); + EXPECT_EQ(480, cb_frames); + EXPECT_EQ(10, cb_latency.tv_sec); + EXPECT_EQ(0, cb_latency.tv_nsec); + EXPECT_EQ((void*)0x321, cb_usr_arg); + + DestroyShm(stream.shm); +} + } // namespace +extern "C" { + +int clock_gettime(clockid_t clk_id, struct timespec* tp) { + *tp = now; + return 0; +} +} + int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); + openlog(NULL, LOG_PERROR, LOG_USER); return RUN_ALL_TESTS(); } -- cgit v1.2.3