Skip to content

Commit 3b7ebb4

Browse files
committed
feat(esp_audio_render): Add solo playback support for stream
1 parent 1a1d0d5 commit 3b7ebb4

File tree

7 files changed

+209
-3
lines changed

7 files changed

+209
-3
lines changed

packages/esp_audio_render/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
## v0.7.2
2+
3+
### Features
4+
5+
- Added API `esp_audio_render_set_solo_stream` for solo stream playback
6+
7+
18
## v0.7.1
29

310
### Features

packages/esp_audio_render/idf_component.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
version: "0.7.1"
1+
version: "0.7.2"
22
description: Espressif Audio Render is a module for render PCM audio through output device
33
url: https://github.com/espressif/esp-gmf/tree/main/packages/esp_audio_render
44
documentation: "https://github.com/espressif/esp-gmf/blob/main/packages/esp_audio_render/README.md"

packages/esp_audio_render/include/esp_audio_render.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ typedef uint8_t esp_audio_render_stream_id_t;
4141
#define ESP_AUDIO_RENDER_STREAM_ID(n) ((esp_audio_render_stream_id_t)(n))
4242
#define ESP_AUDIO_RENDER_FIRST_STREAM ESP_AUDIO_RENDER_STREAM_ID(0)
4343
#define ESP_AUDIO_RENDER_SECOND_STREAM ESP_AUDIO_RENDER_STREAM_ID(1)
44+
#define ESP_AUDIO_RENDER_ALL_STREAM ESP_AUDIO_RENDER_STREAM_ID(0xFF)
4445

4546
/**
4647
* @brief Audio render handle
@@ -186,6 +187,23 @@ esp_audio_render_err_t esp_audio_render_get_mixed_element(esp_audio_render_handl
186187
esp_audio_render_proc_type_t proc_type,
187188
esp_gmf_element_handle_t *element);
188189

190+
/**
191+
* @brief Enable or disable solo play mode for a specific audio stream
192+
*
193+
* @note In solo mode, the audio renderer bypasses the mixer and plays only the specified stream
194+
* - Can be called anytime during runtime
195+
* - Set `stream_id` to `ESP_AUDIO_RENDER_ALL_STREAM` to disable solo mode and re-enable mixing
196+
*
197+
* @param[in] render Audio render handle
198+
* @param[in] stream_id Stream identification
199+
*
200+
* @return
201+
* - ESP_AUDIO_RENDER_ERR_OK On success
202+
* - ESP_AUDIO_RENDER_ERR_INVALID_ARG Invalid input argument
203+
*/
204+
esp_audio_render_err_t esp_audio_render_set_solo_stream(esp_audio_render_handle_t render,
205+
esp_audio_render_stream_id_t stream_id);
206+
189207
/**
190208
* @brief Get stream handle by stream identification
191209
*

packages/esp_audio_render/src/esp_audio_render.c

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ struct audio_render_t {
8787
uint32_t mixer_block_size;
8888
audio_render_event_grp_handle_t event_grp;
8989
void *mutex;
90+
esp_audio_render_stream_id_t solo_stream;
9091
};
9192

9293
static inline audio_render_stream_t* get_stream(audio_render_t* render, esp_audio_render_stream_id_t stream_id)
@@ -136,6 +137,14 @@ static int audio_render_post_writer(uint8_t *pcm_data, uint32_t len, void *ctx)
136137
static int audio_render_stream_writer(uint8_t *pcm_data, uint32_t len, void *ctx)
137138
{
138139
audio_render_stream_t* stream = (audio_render_stream_t*)ctx;
140+
if (stream->stream_id == stream->parent->solo_stream) {
141+
// Solo play write to post directly
142+
audio_render_stream_t* post_stream = get_stream(stream->parent, ESP_AUDIO_RENDER_MIXED_STREAM);
143+
if (post_stream && post_stream->proc_handle) {
144+
return audio_render_proc_write(post_stream->proc_handle, pcm_data, len);
145+
}
146+
return audio_render_post_writer(pcm_data, len, stream->parent);
147+
}
139148
// Write to mixer thread
140149
if (stream->rb) {
141150
return write_rb(stream->rb, pcm_data, len);
@@ -225,7 +234,8 @@ static void audio_render_task(void *arg)
225234
clear_mixer_in(audio_render, stream);
226235
}
227236
}
228-
if (had_valid_data == false) {
237+
if (had_valid_data == false ||
238+
audio_render->solo_stream != ESP_AUDIO_RENDER_ALL_STREAM) {
229239
audio_render_delay(audio_render->cfg.process_period);
230240
consume_fast = false;
231241
continue;
@@ -567,6 +577,7 @@ esp_audio_render_err_t esp_audio_render_create(esp_audio_render_cfg_t *cfg, esp_
567577
audio_render->task_cfg.prio = CONFIG_ESP_AUDIO_RENDER_MIXER_THREAD_PRIORITY;
568578
audio_render->task_cfg.core = CONFIG_ESP_AUDIO_RENDER_MIXER_THREAD_CORE_ID;
569579
audio_render->task_cfg.stack_in_ext = true;
580+
audio_render->solo_stream = ESP_AUDIO_RENDER_ALL_STREAM;
570581
*render = audio_render;
571582
return ESP_AUDIO_RENDER_ERR_OK;
572583
} while (0);
@@ -696,6 +707,22 @@ esp_audio_render_err_t esp_audio_render_stream_set_mixer_gain(esp_audio_render_s
696707
return ESP_AUDIO_RENDER_ERR_OK;
697708
}
698709

710+
esp_audio_render_err_t esp_audio_render_set_solo_stream(esp_audio_render_handle_t render,
711+
esp_audio_render_stream_id_t stream_id)
712+
{
713+
if (render == NULL) {
714+
ESP_LOGE(TAG, "Invalid argument for render:%p", render);
715+
return ESP_AUDIO_RENDER_ERR_INVALID_ARG;
716+
}
717+
audio_render_t *audio_render = (audio_render_t *)render;
718+
if (stream_id >= audio_render->stream_num && stream_id != ESP_AUDIO_RENDER_MIXED_STREAM) {
719+
ESP_LOGE(TAG, "Invalid stream id %d", stream_id);
720+
return ESP_AUDIO_RENDER_ERR_INVALID_ARG;
721+
}
722+
audio_render->solo_stream = stream_id;
723+
return ESP_AUDIO_RENDER_ERR_OK;
724+
}
725+
699726
esp_audio_render_err_t esp_audio_render_stream_open(esp_audio_render_stream_handle_t stream_handle,
700727
esp_audio_render_sample_info_t *sample_info)
701728
{
@@ -771,6 +798,21 @@ esp_audio_render_err_t esp_audio_render_stream_write(esp_audio_render_stream_han
771798
return ESP_AUDIO_RENDER_ERR_INVALID_STATE;
772799
}
773800
AUDIO_RENDER_STREAM_SET_WRITING(stream->state);
801+
if (audio_render->solo_stream != ESP_AUDIO_RENDER_ALL_STREAM) {
802+
// Exclude stream which not solo play
803+
if (stream->stream_id != audio_render->solo_stream) {
804+
return ESP_AUDIO_RENDER_ERR_OK;
805+
}
806+
if (stream->proc_handle) {
807+
return audio_render_proc_write(stream->proc_handle, pcm_data, pcm_size);
808+
}
809+
audio_render_stream_t* post_stream = get_stream(audio_render, ESP_AUDIO_RENDER_MIXED_STREAM);
810+
if (post_stream && post_stream->proc_handle) {
811+
return audio_render_proc_write(post_stream->proc_handle, pcm_data, pcm_size);
812+
}
813+
int ret = audio_render_post_writer(pcm_data, pcm_size, audio_render);
814+
return ret == 0 ? ESP_AUDIO_RENDER_ERR_OK : ESP_AUDIO_RENDER_ERR_FAIL;
815+
}
774816
if (stream->proc_handle) {
775817
return audio_render_proc_write(stream->proc_handle, pcm_data, pcm_size);
776818
}

packages/esp_audio_render/test_apps/main/audio_render_test.c

Lines changed: 133 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@
3636

3737
#define SAMPLE_SIZE(sample_info) (sample_info.bits_per_sample * sample_info.channel / 8)
3838
#define SAMPLE_SIZE_PER_SECOND(sample_info) (sample_info.sample_rate * SAMPLE_SIZE(sample_info))
39-
#define SAME_SAMPLE_INFO(a, b) (a.sample_rate == b.sample_rate && a.bits_per_sample == b.bits_per_sample && a.channel == b.channel)
4039
#define AAC_FRAME_SAMPLES (1024)
4140
#define ELEMS(arr) sizeof(arr)/sizeof(arr[0])
4241
#define DEFAULT_MIXER_INTERVAL (20)
@@ -1102,3 +1101,136 @@ int audio_render_with_no_pool(int write_count)
11021101
}
11031102
return success ? 0 : -1;
11041103
}
1104+
1105+
1106+
static int render_solo_verify_hdlr(uint8_t *pcm_data, uint32_t len, void *ctx)
1107+
{
1108+
render_out_res_t *res = (render_out_res_t*)ctx;
1109+
res->actual_samples[0] = pcm_data[0];
1110+
res->actual_samples[1] = pcm_data[len - 1];
1111+
vTaskDelay(pdMS_TO_TICKS(DEFAULT_MIXER_INTERVAL));
1112+
return 0;
1113+
}
1114+
1115+
int audio_render_dual_stream_solo(int write_count)
1116+
{
1117+
render_out_res_t res = {};
1118+
// Force to use mixer
1119+
esp_audio_render_cfg_t cfg = {
1120+
.max_stream_num = 2,
1121+
.out_sample_info = {
1122+
.sample_rate = 8000,
1123+
.bits_per_sample = 16,
1124+
.channel = 2,
1125+
},
1126+
.out_writer = render_solo_verify_hdlr,
1127+
.out_ctx = &res,
1128+
};
1129+
create_default_pool((esp_gmf_pool_handle_t*)&cfg.pool);
1130+
esp_audio_render_err_t ret;
1131+
esp_audio_render_handle_t render = NULL;
1132+
bool success = false;
1133+
// Force input and out sample info same to verify data
1134+
esp_audio_render_sample_info_t in_sample_info = {
1135+
.sample_rate = 8000,
1136+
.bits_per_sample = 16,
1137+
.channel = 2,
1138+
};
1139+
uint8_t actual_data[2];
1140+
res.actual_samples = actual_data;
1141+
int frame_size = DEFAULT_MIXER_INTERVAL * in_sample_info.sample_rate / 1000 * SAMPLE_SIZE(in_sample_info);
1142+
uint8_t *write_data = (uint8_t*)calloc(1, frame_size);
1143+
do {
1144+
if (write_data == NULL) {
1145+
BREAK_ON_FAIL(ESP_AUDIO_RENDER_ERR_NO_MEM);
1146+
}
1147+
ret = esp_audio_render_create(&cfg, &render);
1148+
BREAK_ON_FAIL(ret);
1149+
1150+
ret = esp_audio_render_set_event_cb(render, render_event_hdlr, &res);
1151+
BREAK_ON_FAIL(ret);
1152+
1153+
esp_audio_render_stream_handle_t stream[2] = {NULL};
1154+
esp_audio_render_stream_get(render, ESP_AUDIO_RENDER_FIRST_STREAM, &stream[0]);
1155+
BREAK_ON_FAIL(stream[0] == NULL);
1156+
esp_audio_render_stream_get(render, ESP_AUDIO_RENDER_SECOND_STREAM, &stream[1]);
1157+
BREAK_ON_FAIL(stream[1] == NULL);
1158+
1159+
ret = esp_audio_render_stream_open(stream[0], &in_sample_info);
1160+
BREAK_ON_FAIL(ret);
1161+
ret = esp_audio_render_stream_open(stream[1], &in_sample_info);
1162+
BREAK_ON_FAIL(ret);
1163+
uint8_t stream_value[2] = {16, 48};
1164+
// Wait for enter stable status
1165+
for (int i = 0; i < 20; i++) {
1166+
ret = esp_audio_render_stream_write(stream[0], write_data, frame_size);
1167+
BREAK_ON_FAIL(ret);
1168+
}
1169+
vTaskDelay(pdMS_TO_TICKS(DEFAULT_MIXER_INTERVAL));
1170+
ret = esp_audio_render_set_solo_stream(render, ESP_AUDIO_RENDER_FIRST_STREAM);
1171+
BREAK_ON_FAIL(ret);
1172+
1173+
memset(write_data, stream_value[0], frame_size);
1174+
ret = esp_audio_render_stream_write(stream[0], write_data, frame_size);
1175+
BREAK_ON_FAIL(ret);
1176+
memset(write_data, stream_value[1], frame_size);
1177+
ret = esp_audio_render_stream_write(stream[1], write_data, frame_size);
1178+
BREAK_ON_FAIL(ret);
1179+
if (actual_data[0] != stream_value[0] || actual_data[1] != stream_value[0]) {
1180+
ESP_LOGE(TAG, "First stream expect %d but get %d-%d", stream_value[0], actual_data[0], actual_data[1]);
1181+
ret = -1;
1182+
BREAK_ON_FAIL(ret);
1183+
}
1184+
1185+
ret = esp_audio_render_set_solo_stream(render, ESP_AUDIO_RENDER_SECOND_STREAM);
1186+
BREAK_ON_FAIL(ret);
1187+
memset(write_data, stream_value[1], frame_size);
1188+
ret = esp_audio_render_stream_write(stream[1], write_data, frame_size);
1189+
BREAK_ON_FAIL(ret);
1190+
memset(write_data, stream_value[0], frame_size);
1191+
ret = esp_audio_render_stream_write(stream[0], write_data, frame_size);
1192+
BREAK_ON_FAIL(ret);
1193+
if (actual_data[0] != stream_value[1] || actual_data[1] != stream_value[1]) {
1194+
ESP_LOGE(TAG, "Second stream expect %d but get %d-%d", stream_value[1], actual_data[0], actual_data[1]);
1195+
ret = -1;
1196+
BREAK_ON_FAIL(ret);
1197+
}
1198+
1199+
ret = esp_audio_render_set_solo_stream(render, ESP_AUDIO_RENDER_ALL_STREAM);
1200+
BREAK_ON_FAIL(ret);
1201+
1202+
for (int j = 0; j < 2; j++) {
1203+
for (int i = 0; i < 2; i++) {
1204+
memset(write_data, stream_value[i], frame_size);
1205+
ret = esp_audio_render_stream_write(stream[i], write_data, frame_size);
1206+
BREAK_ON_FAIL(ret);
1207+
}
1208+
}
1209+
vTaskDelay(pdMS_TO_TICKS(DEFAULT_MIXER_INTERVAL));
1210+
uint8_t expected = (stream_value[0] + stream_value[1]) / 2;
1211+
#define IS_EXPECT(a, b) (a == b || a == b - 1)
1212+
if (!IS_EXPECT(actual_data[0], expected) || !IS_EXPECT(actual_data[1], expected)) {
1213+
ESP_LOGE(TAG, "Mixed stream expect %d but get %d-%d", expected, actual_data[0], actual_data[1]);
1214+
ret = -1;
1215+
BREAK_ON_FAIL(ret);
1216+
}
1217+
ret = esp_audio_render_stream_close(stream[0]);
1218+
BREAK_ON_FAIL(ret);
1219+
ret = esp_audio_render_stream_close(stream[1]);
1220+
BREAK_ON_FAIL(ret);
1221+
// Verify result
1222+
if (res.is_open == false || res.is_close == false) {
1223+
ESP_LOGE(TAG, "Failed to verify open:%d close:%d", res.is_open, res.is_close);
1224+
break;
1225+
}
1226+
success = true;
1227+
} while (0);
1228+
if (render) {
1229+
esp_audio_render_destroy(render);
1230+
}
1231+
if (write_data) {
1232+
free(write_data);
1233+
}
1234+
destroy_default_pool((esp_gmf_pool_handle_t)cfg.pool);
1235+
return success ? 0 : -1;
1236+
}

packages/esp_audio_render/test_apps/main/audio_render_test.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ int audio_render_dual_stream_one_slow(int write_count);
3030

3131
int audio_render_with_no_pool(int write_count);
3232

33+
int audio_render_dual_stream_solo(int write_count);
34+
3335
#ifdef __cplusplus
3436
}
3537
#endif /* __cplusplus */

packages/esp_audio_render/test_apps/main/main.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,11 @@ TEST_CASE("Audio Render with no pool", "[esp_audio_render]")
105105
TEST_ESP_OK(audio_render_with_no_pool(20));
106106
}
107107

108+
TEST_CASE("Audio Render with solo", "[esp_audio_render]")
109+
{
110+
TEST_ESP_OK(audio_render_dual_stream_solo(20));
111+
}
112+
108113
#endif /* TEST_USE_UNITY */
109114

110115
void app_main(void)

0 commit comments

Comments
 (0)