diff --git a/src/audio/component.c b/src/audio/component.c index 8b5cc022e98f..0964b0b62ebb 100644 --- a/src/audio/component.c +++ b/src/audio/component.c @@ -5,6 +5,7 @@ // Author: Liam Girdwood #include +#include #include #include #include @@ -496,6 +497,62 @@ void audio_stream_copy_to_linear(const struct audio_stream *source, int ioffset, } } +static bool comp_check_eos(struct comp_dev *dev) +{ + enum sof_audio_buffer_state sink_state = AUDIOBUF_STATE_INITIAL; + struct comp_buffer *buffer; + + if (!dev->pipeline->expect_eos) + return false; + + comp_dev_for_each_producer(dev, buffer) { + struct sof_source *source = audio_buffer_get_source(&buffer->audio_buffer); + enum sof_audio_buffer_state state = source_get_state(source); + + if (source_get_pipeline_id(source) != dev->pipeline->pipeline_id) + continue; + + if (state == AUDIOBUF_STATE_END_OF_STREAM_FLUSH) { + /* Earlier in the pipeline, there is a DP module that has reached + * the EOS state. However, silence is generated to flush its internal + * buffers, so pass this state to the output buffers. + */ + comp_dbg(dev, "comp_check_eos() - EOS flush detected"); + sink_state = AUDIOBUF_STATE_END_OF_STREAM_FLUSH; + break; + } else if (state == AUDIOBUF_STATE_END_OF_STREAM) { + /* EOS is detected, so we need to set the sink state to AUDIOBUF_STATE_EOS. */ + size_t min_avail = source_get_min_available(source); + + if (source_get_data_available(source) < min_avail) { + comp_dbg(dev, "comp_check_eos() - EOS detected"); + if (dev->ipc_config.proc_domain == COMP_PROCESSING_DOMAIN_DP) { + /* For DP modules, fill missing input data with silence to + * allow it to process the remaining data. + */ + struct sof_sink *previous_mod_data_sink = + audio_buffer_get_sink(&buffer->audio_buffer); + sink_fill_with_silence(previous_mod_data_sink, min_avail); + sink_state = AUDIOBUF_STATE_END_OF_STREAM_FLUSH; + } else { + sink_state = AUDIOBUF_STATE_END_OF_STREAM; + break; + } + } + } + } + + if (sink_state != AUDIOBUF_STATE_INITIAL) { + comp_dev_for_each_consumer(dev, buffer) + audio_buffer_set_state(&buffer->audio_buffer, sink_state); + + /* For AUDIOBUF_STATE_END_OF_STREAM_FLUSH process data normally. */ + return sink_state != AUDIOBUF_STATE_END_OF_STREAM_FLUSH; + } + + return false; +} + /** See comp_ops::copy */ int comp_copy(struct comp_dev *dev) { @@ -530,6 +587,9 @@ int comp_copy(struct comp_dev *dev) const uint32_t begin_stamp = (uint32_t)telemetry_timestamp(); #endif + if (comp_check_eos(dev)) + return 0; + ret = dev->drv->ops.copy(dev); #ifdef CONFIG_SOF_TELEMETRY_PERFORMANCE_MEASUREMENTS diff --git a/src/audio/host-zephyr.c b/src/audio/host-zephyr.c index b7f203eea3a0..5aec7ff1a4d4 100644 --- a/src/audio/host-zephyr.c +++ b/src/audio/host-zephyr.c @@ -393,6 +393,32 @@ static int host_get_status(struct comp_dev *dev, struct host_data *hd, struct dm /* Minimum time between 2 consecutive "no bytes to copy" messages in milliseconds */ #define SOF_MIN_NO_BYTES_INTERVAL_MS 20 +static inline bool host_handle_eos(struct host_data *hd, struct comp_dev *dev, + uint32_t avail_samples) +{ + struct sof_audio_buffer *buffer = &hd->local_buffer->audio_buffer; + enum sof_audio_buffer_state state = audio_buffer_get_state(buffer); + + if (!dev->pipeline->expect_eos) + return false; + + if (!avail_samples) { + /* EOS is detected, so we need to set the sink + * state to AUDIOBUF_STATE_END_OF_STREAM. + */ + if (state != AUDIOBUF_STATE_END_OF_STREAM) { + audio_buffer_set_eos(buffer); + comp_info(dev, "host_handle_eos() - EOS detected"); + } + return true; + } + + if (state == AUDIOBUF_STATE_END_OF_STREAM) + comp_warn(dev, "Data available after reporting end of stream!"); + + return false; +} + /** * Calculates bytes to be copied in normal mode. * @param dev Host component device. @@ -440,6 +466,9 @@ static uint32_t host_get_copy_bytes_normal(struct host_data *hd, struct comp_dev if (dev->direction == SOF_IPC_STREAM_PLAYBACK) { avail_samples = (dma_stat.pending_length - hd->partial_size) / dma_sample_bytes; free_samples = audio_stream_get_free_samples(&buffer->stream); + + if (host_handle_eos(hd, dev, avail_samples)) + return 0; } else { avail_samples = audio_stream_get_avail_samples(&buffer->stream); free_samples = (dma_stat.free - hd->partial_size) / dma_sample_bytes; diff --git a/src/audio/mixin_mixout/mixin_mixout.c b/src/audio/mixin_mixout/mixin_mixout.c index 5043d02920f0..6eaba83aca3b 100644 --- a/src/audio/mixin_mixout/mixin_mixout.c +++ b/src/audio/mixin_mixout/mixin_mixout.c @@ -79,6 +79,8 @@ struct mixin_data { #if CONFIG_XRUN_NOTIFICATIONS_ENABLE uint32_t last_reported_underrun; uint32_t underrun_notification_period; + uint32_t eos_delay_periods; + bool eos_delay_configured; #endif }; @@ -248,23 +250,40 @@ static void silence(struct cir_buf_ptr *stream, uint32_t start_offset, #if CONFIG_XRUN_NOTIFICATIONS_ENABLE static void mixin_check_notify_underrun(struct comp_dev *dev, struct mixin_data *mixin_data, + enum sof_audio_buffer_state state, size_t source_avail, size_t sinks_free) { + const bool eos_detected = state == AUDIOBUF_STATE_END_OF_STREAM_FLUSH || + state == AUDIOBUF_STATE_END_OF_STREAM; + struct ipc_msg *notify; mixin_data->last_reported_underrun++; - if (!source_avail && mixin_data->last_reported_underrun >= - mixin_data->underrun_notification_period) { - mixin_data->last_reported_underrun = 0; + if (!source_avail || eos_detected) { + if (eos_detected) { + if (mixin_data->eos_delay_configured) { + mixin_data->eos_delay_periods--; + } else { + pipeline_get_dai_comp_latency(dev->pipeline->pipeline_id, + &mixin_data->eos_delay_periods); + mixin_data->eos_delay_configured = true; + } + } + + if ((!eos_detected && mixin_data->last_reported_underrun >= + mixin_data->underrun_notification_period) || + (eos_detected && mixin_data->eos_delay_periods == 0)) { + mixin_data->last_reported_underrun = 0; - notify = ipc_notification_pool_get(IPC4_RESOURCE_EVENT_SIZE); - if (!notify) - return; + notify = ipc_notification_pool_get(IPC4_RESOURCE_EVENT_SIZE); + if (!notify) + return; - mixer_underrun_notif_msg_init(notify, dev->ipc_config.id, false, - source_avail, sinks_free); - ipc_msg_send(notify, notify->tx_data, false); + mixer_underrun_notif_msg_init(notify, dev->ipc_config.id, eos_detected, + source_avail, sinks_free); + ipc_msg_send(notify, notify->tx_data, false); + } } } #endif @@ -294,9 +313,10 @@ static int mixin_process(struct processing_module *mod, uint32_t source_avail_frames, sinks_free_frames; struct processing_module *active_mixouts[MIXIN_MAX_SINKS]; uint16_t sinks_ids[MIXIN_MAX_SINKS]; + struct pending_frames *pending_frames; uint32_t bytes_to_consume = 0; uint32_t frames_to_copy; - struct pending_frames *pending_frames; + size_t frame_bytes; int i, ret; struct cir_buf_ptr source_ptr; @@ -389,10 +409,10 @@ static int mixin_process(struct processing_module *mod, return 0; #if CONFIG_XRUN_NOTIFICATIONS_ENABLE - size_t frame_bytes = source_get_frame_bytes(sources[0]); - size_t min_frames = MIN(dev->frames, sinks_free_frames); + frame_bytes = source_get_frame_bytes(sources[0]); + const size_t min_frames = MIN(dev->frames, sinks_free_frames); - mixin_check_notify_underrun(dev, mixin_data, + mixin_check_notify_underrun(dev, mixin_data, source_get_state(sources[0]), source_avail_frames * frame_bytes, min_frames * frame_bytes); #endif @@ -461,7 +481,7 @@ static int mixin_process(struct processing_module *mod, * silence instead of that source data */ if (source_avail_frames == 0) { - uint32_t frame_bytes = sink_get_frame_bytes(mixout_mod->sinks[0]); + frame_bytes = sink_get_frame_bytes(mixout_mod->sinks[0]); /* generate silence */ silence(&mixout_data->acquired_buf, start_frame * frame_bytes, @@ -698,6 +718,7 @@ static int mixin_prepare(struct processing_module *mod, int ret; comp_info(dev, "mixin_prepare()"); + md->eos_delay_configured = false; ret = mixin_params(mod); if (ret < 0) diff --git a/src/audio/pipeline/pipeline-graph.c b/src/audio/pipeline/pipeline-graph.c index 4dc4466865f3..678b8095289f 100644 --- a/src/audio/pipeline/pipeline-graph.c +++ b/src/audio/pipeline/pipeline-graph.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -551,4 +552,6 @@ struct comp_dev *pipeline_get_dai_comp_latency(uint32_t pipeline_id, uint32_t *l return NULL; } +EXPORT_SYMBOL(pipeline_get_dai_comp_latency); + #endif diff --git a/src/include/module/audio/audio_stream.h b/src/include/module/audio/audio_stream.h index f66fc414e865..e032ef322f85 100644 --- a/src/include/module/audio/audio_stream.h +++ b/src/include/module/audio/audio_stream.h @@ -13,6 +13,24 @@ #include #include "../ipc/stream.h" + +/** + * @enum sof_audio_buffer_state + * @brief Define states of an audio stream buffer connecting two components. + * + * This enum represents the lifecycle of an audio stream, including its + * initialization, readiness, and end-of-stream handling. It is used to + * track and manage the state transitions of the stream during audio processing. + */ +enum sof_audio_buffer_state { + AUDIOBUF_STATE_INITIAL, /* Initial state, hw params not configured. */ + AUDIOBUF_STATE_READY, /* Stream ready, hw params configured */ + AUDIOBUF_STATE_END_OF_STREAM, /* Detected End Of Stream */ + AUDIOBUF_STATE_END_OF_STREAM_FLUSH, /* Detected End Of Stream, generating silence + * to flush buffers in dp modules. + */ +}; + /** * set of parameters describing audio stream * this structure is shared between audio_stream.h and sink/source interface @@ -53,7 +71,7 @@ struct sof_audio_stream_params { uint16_t chmap[SOF_IPC_MAX_CHANNELS]; /**< channel map - SOF_CHMAP_ */ - bool hw_params_configured; /**< indicates whether hw params were set */ + enum sof_audio_buffer_state state; /**< audio stream state */ }; #endif /* __MODULE_AUDIO_AUDIO_STREAM_H__ */ diff --git a/src/include/module/audio/source_api.h b/src/include/module/audio/source_api.h index 336535e1be26..556dead4a583 100644 --- a/src/include/module/audio/source_api.h +++ b/src/include/module/audio/source_api.h @@ -323,4 +323,9 @@ static inline struct processing_module *source_get_bound_module(struct sof_sourc return source->bound_module; } +static inline enum sof_audio_buffer_state source_get_state(const struct sof_source *source) +{ + return source->audio_stream_params->state; +} + #endif /* __MODULE_AUDIO_SOURCE_API_H__ */ diff --git a/src/include/sof/audio/audio_buffer.h b/src/include/sof/audio/audio_buffer.h index a4b52248ce59..5156971d515f 100644 --- a/src/include/sof/audio/audio_buffer.h +++ b/src/include/sof/audio/audio_buffer.h @@ -214,17 +214,34 @@ static inline bool audio_buffer_is_shared(struct sof_audio_buffer *buffer) static inline bool audio_buffer_hw_params_configured(struct sof_audio_buffer *buffer) { - return buffer->audio_stream_params->hw_params_configured; + return buffer->audio_stream_params->state != AUDIOBUF_STATE_INITIAL; } static inline void audio_buffer_set_hw_params_configured(struct sof_audio_buffer *buffer) { - buffer->audio_stream_params->hw_params_configured = true; + buffer->audio_stream_params->state = AUDIOBUF_STATE_READY; } static inline void audio_buffer_reset_params(struct sof_audio_buffer *buffer) { - buffer->audio_stream_params->hw_params_configured = false; + buffer->audio_stream_params->state = AUDIOBUF_STATE_INITIAL; +} + +static inline enum sof_audio_buffer_state audio_buffer_get_state( + const struct sof_audio_buffer *buffer) +{ + return buffer->audio_stream_params->state; +} + +static inline void audio_buffer_set_state(struct sof_audio_buffer *buffer, + enum sof_audio_buffer_state state) +{ + buffer->audio_stream_params->state = state; +} + +static inline void audio_buffer_set_eos(struct sof_audio_buffer *buffer) +{ + buffer->audio_stream_params->state = AUDIOBUF_STATE_END_OF_STREAM; } static inline uint16_t audio_buffer_get_chmap(struct sof_audio_buffer *buffer, size_t index) diff --git a/src/include/sof/audio/pipeline.h b/src/include/sof/audio/pipeline.h index affe6569d547..5221d330e0f1 100644 --- a/src/include/sof/audio/pipeline.h +++ b/src/include/sof/audio/pipeline.h @@ -68,6 +68,7 @@ struct pipeline { int32_t xrun_bytes; /* last xrun length */ uint32_t status; /* pipeline status */ struct tr_ctx tctx; /* trace settings */ + bool expect_eos; /* pipeline is expecting end of stream */ /* scheduling */ struct task *pipe_task; /* pipeline processing task */ diff --git a/src/ipc/ipc4/handler.c b/src/ipc/ipc4/handler.c index c50a1d996c5d..b2ddb3c3f742 100644 --- a/src/ipc/ipc4/handler.c +++ b/src/ipc/ipc4/handler.c @@ -287,6 +287,12 @@ int ipc4_pipeline_prepare(struct ipc_comp_dev *ppl_icd, uint32_t cmd) switch (cmd) { case SOF_IPC4_PIPELINE_STATE_RUNNING: + if (ppl_icd->pipeline->expect_eos) { + ipc_cmd_err(&ipc_tr, "pipeline %d: Can't transition from EOS to RUNNING", + ppl_icd->id); + return IPC4_INVALID_REQUEST; + } + /* init params when pipeline is complete or reset */ switch (status) { case COMP_STATE_ACTIVE: @@ -300,8 +306,6 @@ int ipc4_pipeline_prepare(struct ipc_comp_dev *ppl_icd, uint32_t cmd) tr_dbg(&ipc_tr, "pipeline %d: set params", ppl_icd->id); ret = ipc4_pcm_params(host); - if (ret < 0) - return IPC4_INVALID_REQUEST; break; default: ipc_cmd_err(&ipc_tr, @@ -315,9 +319,6 @@ int ipc4_pipeline_prepare(struct ipc_comp_dev *ppl_icd, uint32_t cmd) case COMP_STATE_INIT: tr_dbg(&ipc_tr, "pipeline %d: reset from init", ppl_icd->id); ret = ipc4_pipeline_complete(ipc, ppl_icd->id, cmd); - if (ret < 0) - ret = IPC4_INVALID_REQUEST; - break; case COMP_STATE_READY: case COMP_STATE_ACTIVE: @@ -337,9 +338,6 @@ int ipc4_pipeline_prepare(struct ipc_comp_dev *ppl_icd, uint32_t cmd) case COMP_STATE_INIT: tr_dbg(&ipc_tr, "pipeline %d: pause from init", ppl_icd->id); ret = ipc4_pipeline_complete(ipc, ppl_icd->id, cmd); - if (ret < 0) - ret = IPC4_INVALID_REQUEST; - break; default: /* No action needed */ @@ -347,11 +345,15 @@ int ipc4_pipeline_prepare(struct ipc_comp_dev *ppl_icd, uint32_t cmd) } break; - /* special case- TODO */ case SOF_IPC4_PIPELINE_STATE_EOS: - if (status != COMP_STATE_ACTIVE) + if (status != COMP_STATE_ACTIVE) { + ipc_cmd_err(&ipc_tr, "pipeline %d: Invalid state for EOS: %d", + ppl_icd->id, status); return IPC4_INVALID_REQUEST; - COMPILER_FALLTHROUGH; + } + ppl_icd->pipeline->expect_eos = true; + return 0; /* Must return here. Any other transition clears expect_eos. */ + /* special case - TODO */ case SOF_IPC4_PIPELINE_STATE_SAVED: case SOF_IPC4_PIPELINE_STATE_ERROR_STOP: default: @@ -360,6 +362,11 @@ int ipc4_pipeline_prepare(struct ipc_comp_dev *ppl_icd, uint32_t cmd) return IPC4_INVALID_REQUEST; } + if (ret < 0) + return IPC4_INVALID_REQUEST; + + ppl_icd->pipeline->expect_eos = false; + return ret; } @@ -423,6 +430,9 @@ int ipc4_pipeline_trigger(struct ipc_comp_dev *ppl_icd, uint32_t cmd, bool *dela } break; + case SOF_IPC4_PIPELINE_STATE_EOS: + /* EOS handled in ipc4_pipeline_prepare */ + return 0; default: ipc_cmd_err(&ipc_tr, "pipeline %d: unsupported trigger cmd: %d", ppl_icd->id, cmd); diff --git a/test/cmocka/src/audio/eq_iir/CMakeLists.txt b/test/cmocka/src/audio/eq_iir/CMakeLists.txt index a328590b7106..aa704a1af92b 100644 --- a/test/cmocka/src/audio/eq_iir/CMakeLists.txt +++ b/test/cmocka/src/audio/eq_iir/CMakeLists.txt @@ -35,6 +35,8 @@ add_library(audio_for_eq_iir STATIC ${PROJECT_SOURCE_DIR}/src/audio/audio_stream.c ${PROJECT_SOURCE_DIR}/src/audio/component.c ${PROJECT_SOURCE_DIR}/src/audio/data_blob.c + ${PROJECT_SOURCE_DIR}/src/module/audio/source_api.c + ${PROJECT_SOURCE_DIR}/src/module/audio/sink_api.c ${PROJECT_SOURCE_DIR}/src/ipc/ipc3/helper.c ${PROJECT_SOURCE_DIR}/src/ipc/ipc-common.c ${PROJECT_SOURCE_DIR}/src/ipc/ipc-helper.c