Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cd99a8d7c1 | |||
| 2720556d6a |
@@ -553,9 +553,14 @@ error_t close_stream(AudioStreamHandle handle_base) {
|
||||
Device* codec = is_input ? data->input_codec : data->output_codec;
|
||||
AudioStreamHandleImpl** slot = is_input ? &data->open_input : &data->open_output;
|
||||
|
||||
// Determine if underlying codec is shared (BOTH codec used for both directions)
|
||||
// In that case we must NOT close the codec if the other direction is still active.
|
||||
Device* other_codec = is_input ? data->output_codec : data->input_codec;
|
||||
AudioStreamHandleImpl** other_slot = is_input ? &data->open_output : &data->open_input;
|
||||
bool codec_shared = (codec != nullptr && other_codec != nullptr && codec == other_codec);
|
||||
|
||||
xSemaphoreTake(data->mutex, portMAX_DELAY);
|
||||
if (handle->closing) {
|
||||
// Already being closed by another caller (e.g. concurrent set_enabled + app close).
|
||||
xSemaphoreGive(data->mutex);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
@@ -563,14 +568,16 @@ error_t close_stream(AudioStreamHandle handle_base) {
|
||||
if (*slot == handle) {
|
||||
*slot = nullptr;
|
||||
}
|
||||
bool other_still_open = (other_slot != nullptr && *other_slot != nullptr && *other_slot != reinterpret_cast<AudioStreamHandleImpl*>(1));
|
||||
bool must_drain = (handle->busy_count > 0);
|
||||
bool should_close_codec = !codec_shared || !other_still_open;
|
||||
xSemaphoreGive(data->mutex);
|
||||
|
||||
if (must_drain && handle->drain_semaphore != nullptr) {
|
||||
xSemaphoreTake(handle->drain_semaphore, portMAX_DELAY);
|
||||
}
|
||||
|
||||
if (codec != nullptr) {
|
||||
if (should_close_codec && codec != nullptr) {
|
||||
audio_codec_close(codec);
|
||||
}
|
||||
|
||||
|
||||
@@ -53,16 +53,36 @@ error_t open(Device* device, const struct AudioCodecStreamConfig* config) {
|
||||
};
|
||||
|
||||
if (data->is_open) {
|
||||
// open_direction == BOTH already serves INPUT-only or OUTPUT-only requests on the
|
||||
// same sample settings -- only an exact direction mismatch (e.g. requesting BOTH
|
||||
// while opened for INPUT only) needs a reopen.
|
||||
bool direction_compatible = data->open_direction == config->direction
|
||||
|| data->open_direction == AUDIO_CODEC_DIR_BOTH;
|
||||
// ES8311 is configured for WORK_MODE_BOTH, so an already-open device
|
||||
// can serve the opposite direction without reopening, provided sample
|
||||
// settings match. Promote open_direction to BOTH when we see a
|
||||
// complementary request.
|
||||
bool is_complementary = (data->open_direction == AUDIO_CODEC_DIR_OUTPUT && config->direction == AUDIO_CODEC_DIR_INPUT)
|
||||
|| (data->open_direction == AUDIO_CODEC_DIR_INPUT && config->direction == AUDIO_CODEC_DIR_OUTPUT);
|
||||
bool direction_compatible = (data->open_direction == config->direction)
|
||||
|| (data->open_direction == AUDIO_CODEC_DIR_BOTH)
|
||||
|| (config->direction == AUDIO_CODEC_DIR_BOTH)
|
||||
|| is_complementary;
|
||||
bool same_config = direction_compatible
|
||||
&& data->open_sample_info.bits_per_sample == sample_info.bits_per_sample
|
||||
&& data->open_sample_info.channel == sample_info.channel
|
||||
&& data->open_sample_info.sample_rate == sample_info.sample_rate;
|
||||
return same_config ? ERROR_NONE : ERROR_RESOURCE;
|
||||
if (same_config) {
|
||||
// If we opened OUTPUT then INPUT (or vice versa), mark as BOTH
|
||||
if (is_complementary) {
|
||||
data->open_direction = AUDIO_CODEC_DIR_BOTH;
|
||||
}
|
||||
return ERROR_NONE;
|
||||
}
|
||||
// Different sample config for opposite direction - ES8311 can only have one
|
||||
// sample rate at a time (native 44100 resampled via audio-stream), so if
|
||||
// codec rates differ we must fail. But if both sides use native 44100 (audio-stream
|
||||
// always opens codec with native rate), we allow it.
|
||||
if (direction_compatible) {
|
||||
// Allow if both use same native rate path (audio-stream opens with codec's native)
|
||||
return ERROR_RESOURCE;
|
||||
}
|
||||
return ERROR_RESOURCE;
|
||||
}
|
||||
|
||||
if (esp_codec_dev_open(data->codec_device, &sample_info) != ESP_CODEC_DEV_OK) {
|
||||
|
||||
@@ -14,6 +14,9 @@ bool isValidAppVersionName(const std::string& version);
|
||||
bool isValidAppVersionCode(const std::string& version);
|
||||
bool isValidName(const std::string& name);
|
||||
|
||||
/** Parses a comma-separated flags string (e.g. "HideStatusBar,Hidden") into appFlags bitmask. */
|
||||
uint16_t parseAppFlagsString(const std::string& raw);
|
||||
|
||||
/** Parses a V1 (sectioned INI, e.g. "[app]versionName=...") manifest map. */
|
||||
bool parseManifestV1(const std::map<std::string, std::string>& map, AppManifest& manifest);
|
||||
|
||||
|
||||
@@ -56,6 +56,34 @@ bool isValidName(const std::string& name) {
|
||||
});
|
||||
}
|
||||
|
||||
uint16_t parseAppFlagsString(const std::string& raw) {
|
||||
uint16_t flags = AppManifest::Flags::None;
|
||||
if (raw.empty()) {
|
||||
return flags;
|
||||
}
|
||||
|
||||
auto parts = string::split(raw, ",");
|
||||
for (auto& part : parts) {
|
||||
std::string trimmed = string::trim(part, " \t\r\n");
|
||||
if (trimmed.empty()) {
|
||||
continue;
|
||||
}
|
||||
std::string lower = string::lowercase(trimmed);
|
||||
|
||||
if (lower == "hidestatusbar" || lower == "hide_statusbar" || lower == "hide-statusbar" || lower == "hide_status_bar") {
|
||||
flags |= AppManifest::Flags::HideStatusBar;
|
||||
} else if (lower == "hidden") {
|
||||
flags |= AppManifest::Flags::Hidden;
|
||||
} else if (lower == "none" || lower == "0" || lower == "") {
|
||||
// keep as none, no additional flag
|
||||
} else {
|
||||
LOG_W(TAG, "Unknown app flag \"%s\" - ignoring", trimmed.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
/** The V1 format's first line is always the literal "[manifest]" section header; V2 files are flat from the first line onward. */
|
||||
static bool detectIsV1Format(const std::string& filePath) {
|
||||
std::string first_line;
|
||||
|
||||
@@ -71,6 +71,12 @@ bool parseManifestV1(const std::map<std::string, std::string>& map, AppManifest&
|
||||
return false;
|
||||
}
|
||||
|
||||
// Optional: [app]flags - e.g. "HideStatusBar" or "HideStatusBar,Hidden"
|
||||
auto flags_it = map.find("[app]flags");
|
||||
if (flags_it != map.end()) {
|
||||
manifest.appFlags = parseAppFlagsString(flags_it->second);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -71,6 +71,12 @@ bool parseManifestV2(const std::map<std::string, std::string>& map, AppManifest&
|
||||
return false;
|
||||
}
|
||||
|
||||
// Optional: app.flags - e.g. "HideStatusBar" or "HideStatusBar,Hidden"
|
||||
auto flags_it = map.find("app.flags");
|
||||
if (flags_it != map.end()) {
|
||||
manifest.appFlags = parseAppFlagsString(flags_it->second);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -81,3 +81,70 @@ TEST_CASE("parseManifestV2() should fail when the app id is invalid") {
|
||||
AppManifest manifest;
|
||||
CHECK_EQ(parseManifestV2(properties, manifest), false);
|
||||
}
|
||||
|
||||
TEST_CASE("parseAppFlagsString() should parse various flag combinations") {
|
||||
CHECK_EQ(parseAppFlagsString(""), 0);
|
||||
CHECK_EQ(parseAppFlagsString("None"), 0);
|
||||
CHECK_EQ(parseAppFlagsString("HideStatusBar"), AppManifest::Flags::HideStatusBar);
|
||||
CHECK_EQ(parseAppFlagsString("hidden"), AppManifest::Flags::Hidden);
|
||||
CHECK_EQ(parseAppFlagsString("HideStatusBar,Hidden"), AppManifest::Flags::HideStatusBar | AppManifest::Flags::Hidden);
|
||||
CHECK_EQ(parseAppFlagsString(" hidestatusbar , hidden "), AppManifest::Flags::HideStatusBar | AppManifest::Flags::Hidden);
|
||||
CHECK_EQ(parseAppFlagsString("HideStatusBar, Hidden"), AppManifest::Flags::HideStatusBar | AppManifest::Flags::Hidden);
|
||||
}
|
||||
|
||||
TEST_CASE("parseManifest() should parse V1 flags for fullscreen") {
|
||||
TestFile file("test-manifest-v1-flags.properties");
|
||||
file.writeData(
|
||||
"[manifest]\n"
|
||||
"version=0.1\n"
|
||||
"[target]\n"
|
||||
"sdk=0.0.0\n"
|
||||
"platforms=esp32\n"
|
||||
"[app]\n"
|
||||
"id=one.tactility.sdktest\n"
|
||||
"versionName=0.1.0\n"
|
||||
"versionCode=1\n"
|
||||
"name=SDK Test\n"
|
||||
"flags=HideStatusBar\n"
|
||||
);
|
||||
|
||||
AppManifest manifest;
|
||||
CHECK_EQ(parseManifest(file.getPath(), manifest), true);
|
||||
CHECK_EQ(manifest.appFlags & AppManifest::Flags::HideStatusBar, AppManifest::Flags::HideStatusBar);
|
||||
}
|
||||
|
||||
TEST_CASE("parseManifest() should parse V2 flags for fullscreen") {
|
||||
TestFile file("test-manifest-v2-flags.properties");
|
||||
file.writeData(
|
||||
"manifest.version=0.1\n"
|
||||
"target.sdk=0.0.0\n"
|
||||
"target.platforms=esp32\n"
|
||||
"app.id=one.tactility.sdktest\n"
|
||||
"app.version.name=0.1.0\n"
|
||||
"app.version.code=1\n"
|
||||
"app.name=SDK Test\n"
|
||||
"app.flags=HideStatusBar\n"
|
||||
);
|
||||
|
||||
AppManifest manifest;
|
||||
CHECK_EQ(parseManifest(file.getPath(), manifest), true);
|
||||
CHECK_EQ(manifest.appFlags & AppManifest::Flags::HideStatusBar, AppManifest::Flags::HideStatusBar);
|
||||
}
|
||||
|
||||
TEST_CASE("parseManifest() should parse combined flags") {
|
||||
TestFile file("test-manifest-v2-flags2.properties");
|
||||
file.writeData(
|
||||
"manifest.version=0.1\n"
|
||||
"target.sdk=0.0.0\n"
|
||||
"target.platforms=esp32\n"
|
||||
"app.id=one.tactility.sdktest\n"
|
||||
"app.version.name=0.1.0\n"
|
||||
"app.version.code=1\n"
|
||||
"app.name=SDK Test\n"
|
||||
"app.flags=HideStatusBar,Hidden\n"
|
||||
);
|
||||
|
||||
AppManifest manifest;
|
||||
CHECK_EQ(parseManifest(file.getPath(), manifest), true);
|
||||
CHECK_EQ(manifest.appFlags, (AppManifest::Flags::HideStatusBar | AppManifest::Flags::Hidden));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user