chore: checkpoint 第一版
ci / verify (push) Canceled after 0s

This commit is contained in:
Codex Local
2026-09-06 17:04:11 +08:00
parent a8cdec98d1
commit 0c2151ace9
659 changed files with 185855 additions and 554 deletions
@@ -0,0 +1,4 @@
[codespell]
skip = build,*.yuv,alice.txt,*.rgb
ignore-words-list = ser,dout,rsource,fram,inout,shs,ans,aci,unstall,unstalling,hart,wheight,wel,ot,afe,muxer,parm
write-changes = true
@@ -0,0 +1,53 @@
---
# FILE: .github/workflows/sync-jira.yml
# This GitHub Actions workflow synchronizes GitHub issues, comments, and pull requests with Jira.
# It triggers on new issues, issue comments, and on a scheduled basis.
# The workflow uses a custom action to perform the synchronization with Jira (espressif/sync-jira-actions).
name: 🔷 Sync to Jira
run-name: >
Sync to Jira -
${{ github.event_name == 'issue_comment' && 'Issue Comment' ||
github.event_name == 'schedule' && 'New Pull Requests' ||
github.event_name == 'issues' && 'New Issue' ||
github.event_name == 'workflow_dispatch' && 'Manual Sync' }}
on:
issues: {types: [opened]}
issue_comment: {types: [created, edited, deleted]}
schedule: [cron: '0 * * * *']
workflow_dispatch:
inputs:
action: {description: 'Action to be performed', required: true, default: 'mirror-issues'}
issue-numbers: {description: 'Issue numbers to sync (comma-separated)', required: true}
jobs:
sync-to-jira:
name: >
Sync to Jira -
${{ github.event_name == 'issue_comment' && 'Issue Comment' ||
github.event_name == 'schedule' && 'New Pull Requests' ||
github.event_name == 'issues' && 'New Issue' ||
github.event_name == 'workflow_dispatch' && 'Manual Sync' }}
runs-on: ubuntu-latest
permissions:
contents: read
issues: write
pull-requests: write
steps:
- name: Check out
uses: actions/checkout@v4
- name: Run synchronization to Jira
uses: espressif/sync-jira-actions@v1
with:
cron_job: ${{ github.event_name == 'schedule' && 'true' || '' }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
JIRA_PASS: ${{ secrets.JIRA_PASS }}
JIRA_PROJECT: AUD
JIRA_COMPONENT: GMF_GitHub
JIRA_URL: ${{ secrets.JIRA_URL }}
JIRA_USER: ${{ secrets.JIRA_USER }}
@@ -0,0 +1,31 @@
name: Push components to https://components.espressif.com
on:
push:
branches:
- main
jobs:
upload_components:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: "recursive"
- name: Upload components to the component registry
uses: espressif/upload-components-ci-action@v2
with:
components: |
gmf_core:./gmf_core
gmf_audio:./elements/gmf_audio
gmf_io:./elements/gmf_io
gmf_misc:./elements/gmf_misc
gmf_ai_audio:./elements/gmf_ai_audio
gmf_video:./elements/gmf_video
gmf_loader:./packages/gmf_loader
esp_audio_simple_player:./packages/esp_audio_simple_player
esp_capture: ./packages/esp_capture
gmf_app_utils: ./packages/gmf_app_utils
esp_board_manager: ./packages/esp_board_manager
esp_audio_render: ./packages/esp_audio_render
gmf_examples: ./gmf_examples
namespace: "espressif"
api_token: ${{ secrets.IDF_COMPONENT_API_TOKEN }}
@@ -0,0 +1,54 @@
.config
*.o
*.pyc
# gtags
GTAGS
GRTAGS
GPATH
# emacs
.dir-locals.el
# emacs temp file suffixes
*~
.#*
\#*#
# MacOS directory files
.DS_Store
# eclipse setting
.settings
# Example project files
**/sdkconfig
**/sdkconfig.old
**/build
**/managed_components
**/dependencies.lock
**/dist
**/gen_bmgr_codes/
# Doc build artifacts
gmf_core/docs/*/_build/
gmf_core/docs/*/doxygen-warning-log.txt
gmf_core/docs/*/sphinx-warning-log.txt
gmf_core/docs/*/sphinx-warning-log-sanitized.txt
gmf_core/docs/*/xml/
gmf_core/docs/*/xml_in/
gmf_core/docs/*/man/
gmf_core/docs/*/doxygen_sqlite3.db
# Downloaded font files
gmf_core/docs/_static/DejaVuSans.ttf
gmf_core/docs/_static/NotoSansSC-Regular.otf
# gcov coverage reports
*.gcda
*.gcno
coverage.info
coverage_report/
.vscode
.cache/
@@ -0,0 +1,33 @@
stages:
- pre_check
- build
- target_test
- deploy
variables:
GIT_STRATEGY: clone
GET_SOURCES_ATTEMPTS: "10"
ARTIFACT_DOWNLOAD_ATTEMPTS: "10"
GIT_SUBMODULE_STRATEGY: none
IDF_SKIP_CHECK_SUBMODULES: 1
GIT_DEPTH: 1
IDF_PATH: "/root/.espressif/esp-idf"
PROJECT_PATH: "${CI_PROJECT_DIR}"
BASE_FRAMEWORK: $IDF_REPOSITORY
BASE_FRAMEWORK_PATH: "${IDF_PATH}"
TARGET_DIR_TYPE: "example"
EXAMPLES_PATH: "${CI_PROJECT_DIR}"
before_script:
- source ${CI_PROJECT_DIR}/tools/ci/utils.sh
- add_gitlab_ssh_keys
include:
- '.gitlab/ci/rules.yml'
- '.gitlab/ci/common.yml'
- '.gitlab/ci/pre_check.yml'
- '.gitlab/ci/build_examples.yml'
- '.gitlab/ci/build_ut.yml'
- '.gitlab/ci/target_ut.yml'
- '.gitlab/ci/target_examples.yml'
- '.gitlab/ci/deploy.yml'
@@ -0,0 +1,19 @@
# The file mainly marks apps with special rules, while other apps are enabled by default
.disable_test_apps: &disable_test_apps
disable:
- if: TARGET_DIR_TYPE == "test_apps"
.disable_example: &disable_example
disable:
- if: TARGET_DIR_TYPE == "example"
.disable_test_apps_esp32: &disable_test_apps_esp32
disable:
- if: TARGET_DIR_TYPE == "test_apps" AND IDF_TARGET == "esp32"
gmf_core/test_apps:
<<: *disable_example
packages/esp_board_manager:
<<: *disable_test_apps_esp32
@@ -0,0 +1,72 @@
.build_cmake_template: &build_cmake_template
stage: build
image: ${IMAGE}
tags:
- multimedia_build
parallel: 2
interruptible: true
dependencies: # set dependencies to null to avoid missing artifacts issue
variables:
DEEP_CLONE: "false"
IDF_FIND_APP_PATH_ARG: "-p"
SDKCFG_DEFAULTS: "sdkconfig.defaults.$IDF_TARGET"
BASE_FRAMEWORK_PATH: "${IDF_PATH}"
BASE_FRAMEWORK: $IDF_REPOSITORY
artifacts:
paths:
- "**/build*/size.json"
- "**/build*/build_log.txt"
- "**/build*/*.bin"
- "**/build*/*.elf"
- "**/build*/flasher_args.json"
- "**/build*/flash_project_args"
- "**/build*/config/sdkconfig.json"
- "**/build*/sdkconfig"
- "**/build*/bootloader/*.bin"
- "**/build*/partition_table/*.bin"
- "**/build*/srmodels/*.bin"
when: always
expire_in: 4 days
extends:
- .build_template
script:
- python $CI_PROJECT_DIR/tools/ci/esp_bmgr_pre_build.py --target $IDF_TARGET --board $AUDIO_BOARD --project-path ${EXAMPLES_PATH[@]} --target-dir-type ${TARGET_DIR_TYPE}
- run_cmd python $CI_PROJECT_DIR/tools/ci/build_apps.py ${EXAMPLES_PATH[@]} \
-t $IDF_TARGET -vv \
--target-dir-type ${TARGET_DIR_TYPE} \
--build-dir ${IDF_VERSION_TAG}/build_@t_@w \
--parallel-count ${parallel##*/} \
--parallel-index ${parallel%%/*}
.matrix_template: &matrix_template
parallel:
matrix:
- IDF_VERSION_TAG: ['v5.4.3']
parallel: [ 1/1 ]
build_examples_esp32_lyrat_mini_v1_1:
extends:
- .rules:build:protected-merge-requests-pipeline
- .build_cmake_template
- .matrix_template
variables:
IDF_TARGET: esp32
AUDIO_BOARD: lyrat_mini_v1_1
.build_examples_esp32s2_kaluga_v1_2:
extends:
- .rules:build:protected-merge-requests-pipeline
- .build_cmake_template
- .matrix_template
variables:
IDF_TARGET: esp32s2
AUDIO_BOARD: ''
build_examples_esp32s3_korvo_v2:
extends:
- .rules:build:protected-merge-requests-pipeline
- .build_cmake_template
- .matrix_template
variables:
IDF_TARGET: esp32s3
AUDIO_BOARD: esp32_s3_korvo2_v3
@@ -0,0 +1,115 @@
variables:
BASE_FRAMEWORK_PATH: "${IDF_PATH}"
BASE_FRAMEWORK: $IDF_REPOSITORY
IDF_TAG_FLAG: true
.build_gmf_component:
interruptible: true
variables:
TARGET_DIR_TYPE: "test_apps"
TEST_DIR: ${CI_PROJECT_DIR}/gmf_core
parallel:
matrix:
- IDF_VERSION_TAG: ['v5.4.3']
parallel: [ 1/1 ]
extends:
- .rules:build:protected-merge-requests-pipeline
- .build_template
artifacts:
paths:
- "**/build*/size.json"
- "**/build*/build_log.txt"
- "**/build*/*.bin"
- "**/build*/*.elf"
- "**/build*/flasher_args.json"
- "**/build*/flash_project_args"
- "**/build*/config/sdkconfig.json"
- "**/build*/sdkconfig"
- "**/build*/bootloader/*.bin"
- "**/build*/partition_table/*.bin"
- "**/build*/srmodels/*.bin"
- "${TEST_DIR}/**/test_apps/*.py"
expire_in: 4 days
script:
- python $CI_PROJECT_DIR/tools/ci/esp_bmgr_pre_build.py --target $IDF_TARGET --board $AUDIO_BOARD --project-path ${TEST_DIR} --target-dir-type ${TARGET_DIR_TYPE}
- run_cmd python $CI_PROJECT_DIR/tools/ci/build_apps.py ${TEST_DIR} \
-t $IDF_TARGET -vv \
--target-dir-type ${TARGET_DIR_TYPE} \
--build-dir ${IDF_VERSION_TAG}/build_@t_@w \
--parallel-count ${parallel##*/} \
--parallel-index ${parallel%%/*}
- python $CI_PROJECT_DIR/tools/ci/check_test_cxx_build.py
.build_ut_gmf_packages:
extends:
- .build_gmf_component
variables:
TEST_DIR: ${CI_PROJECT_DIR}/packages
.build_ut_gmf_elements:
extends:
- .build_gmf_component
variables:
TEST_DIR: ${CI_PROJECT_DIR}/elements
build_ut_gmf_core_esp32:
extends:
- .build_gmf_component
variables:
IDF_TARGET: esp32
AUDIO_BOARD: lyrat_mini_v1_1
.build_ut_gmf_core_esp32c3:
extends:
- .build_gmf_component
variables:
IDF_TARGET: esp32c3
build_ut_gmf_core_esp32s3:
extends:
- .build_gmf_component
variables:
IDF_TARGET: esp32s3
AUDIO_BOARD: esp32_s3_korvo2_v3
.build_ut_gmf_core_esp32p4:
extends:
- .build_gmf_component
variables:
IDF_TARGET: esp32p4
AUDIO_BOARD: esp32_p4_function_ev
build_ut_gmf_elements_esp32:
extends:
- .build_ut_gmf_elements
variables:
IDF_TARGET: esp32
AUDIO_BOARD: lyrat_mini_v1_1
build_ut_gmf_elements_esp32s3:
extends:
- .build_ut_gmf_elements
variables:
IDF_TARGET: esp32s3
AUDIO_BOARD: esp32_s3_korvo2_v3
.build_ut_gmf_elements_esp32p4:
extends:
- .build_ut_gmf_elements
variables:
IDF_TARGET: esp32p4
AUDIO_BOARD: esp32_p4_function_ev
build_ut_gmf_packages_esp32:
extends:
- .build_ut_gmf_packages
variables:
IDF_TARGET: esp32
AUDIO_BOARD: lyrat_mini_v1_1
build_ut_gmf_packages_esp32s3:
extends:
- .build_ut_gmf_packages
variables:
IDF_TARGET: esp32s3
AUDIO_BOARD: esp32_s3_korvo2_v3
@@ -0,0 +1,47 @@
variables:
IDF_TAG_FLAG: true
IDF_EXTRA_ACTIONS_PATH: ${CI_PROJECT_DIR}/packages/esp_board_manager
.update_source: &update_source
- source ${CI_PROJECT_DIR}/tools/ci/utils.sh
- add_gitlab_ssh_keys
- git submodule update --init --recursive --depth 1
- git clone --depth 1 -b $IDF_VERSION_TAG $IDF_REPOSITORY $IDF_PATH
- fetch_idf_branch $IDF_VERSION_TAG
- cd $BASE_FRAMEWORK_PATH
- git log -2
- common_before_scripts
- setup_tools_and_idf_python_venv
- set_env_variable
- cd ${CI_PROJECT_DIR}
.build_template:
stage: build
image: ${IMAGE}
interruptible: true
tags:
- multimedia_build
before_script:
- *update_source
after_script:
- find "${TEST_DIR}" -type d \( -name "managed_components" -o -name "build" \) -exec rm -rf {} +
.target_test_template:
stage: target_test
image: "$CI_DOCKER_REGISTRY/target-test-env-v5.4:2"
interruptible: true
timeout: 1 hour
retry: 2
cache:
# Usually do not need submodule-cache in target_test
- key: pip-cache
paths:
- .cache/pip
policy: pull
before_script:
- source ${CI_PROJECT_DIR}/tools/ci/utils.sh
- add_gitlab_ssh_keys
- git clone --depth 1 -b $IDF_VERSION_TAG $IDF_REPOSITORY $IDF_PATH
- check_idf_version $IDF_VERSION_TAG
- export PYTHONPATH="$IDF_PATH/tools:$IDF_PATH/tools/ci/python_packages:$PYTHONPATH"
- export PYTHONPATH="$CI_PROJECT_DIR/tools:$PYTHONPATH"
@@ -0,0 +1,21 @@
push_to_github:
stage: deploy
rules:
- if:
'($CI_COMMIT_REF_NAME == "main" ||
$CI_COMMIT_BRANCH =~ /^release\/v/ ||
$CI_COMMIT_TAG =~ /^v\d+\.\d+(\.\d+)?($|-)/) &&
$CI_PIPELINE_SOURCE != "schedule"'
when: on_success
image: $CI_DOCKER_REGISTRY/esp32-ci-env
variables:
GIT_STRATEGY: clone
before_script:
- echo "skip default before_script"
script:
- source ${CI_PROJECT_DIR}/tools/ci/utils.sh
- add_github_ssh_keys
- git fetch --unshallow
- git remote remove github &>/dev/null || true
- git remote add github ${TARGET_GITHUB_REPO}
- push_to_github
@@ -0,0 +1,102 @@
.pre_check_template:
stage: pre_check
image: "${CI_DOCKER_REGISTRY}/esp-env-v5.3:1"
interruptible: true
tags:
- host_test
dependencies: []
.check_branch_script: &check_branch_script |
if [[ "$BOT_LABEL_SKIP_BRANCH_CHECK" = "true" || "$CI_MERGE_REQUEST_LABELS" =~ skip_branch_check ]]; then
echo "Skip branch name check."
else
${CHECK_TOOLS_PATH}/check_commit_format branch-name
fi
.check_commit_script: &check_commit_script |
if [[ "$BOT_LABEL_SKIP_COMMIT_CHECK" = "true" || "$CI_MERGE_REQUEST_LABELS" =~ skip_commit_check ]]; then
echo "Skip commit message check."
else
${CHECK_TOOLS_PATH}/check_commit_format commit-message
fi
.check_code_script: &check_code_script |
if [[ "$BOT_LABEL_SKIP_CODE_CHECK" = "true" || "$CI_MERGE_REQUEST_LABELS" =~ skip_code_check ]]; then
echo "Skip code style check."
else
${CHECK_TOOLS_PATH}/check_commit_format --verbose --format code-style;
if [ -d "${CHECK_REPO_PATH}/formatted" ] && [ "$(ls -A ${CHECK_REPO_PATH}/formatted)" ]; then
git diff -- . ':(exclude).gitmodules' > ${CHECK_REPO_PATH}/patch/code_format.patch
exit 1
fi
fi
check_commit_format:
image: "$CI_DOCKER_IMAGE_REGISTRY/pre_commit:1"
interruptible: true
stage: pre_check
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_LABELS !~ /^(?:[^,\n\r]+,)*skip_check(?:,[^,\n\r]+)*$/i'
script:
- git clone --depth 1 $CHECK_FORMAT_TOOL_REPO
- cd check-format-tool/bin
- export CHECK_REPO_PATH=${CI_PROJECT_DIR}
- ./install.sh
- . ./export.sh
- cd ${CHECK_REPO_PATH};
- *check_branch_script
- *check_commit_script
- *check_code_script
- check_project_root_dir_change
allow_failure: true
artifacts:
paths:
- ${CI_PROJECT_DIR}/patch
- ${CI_PROJECT_DIR}/formatted
when: always
expire_in: 4 days
check_pre_commit:
extends:
- .rules:build:protected-merge-requests
- .pre_check_template
before_script:
- pip install pre-commit
script:
- |
# merged results pipelines, by default
if [[ -n $CI_MERGE_REQUEST_SOURCE_BRANCH_SHA ]]; then
git fetch origin $CI_MERGE_REQUEST_DIFF_BASE_SHA --depth=1 ${GIT_FETCH_EXTRA_FLAGS}
git fetch origin $CI_MERGE_REQUEST_SOURCE_BRANCH_SHA --depth=1 ${GIT_FETCH_EXTRA_FLAGS}
export GIT_DIFF_OUTPUT=$(git diff --name-only $CI_MERGE_REQUEST_DIFF_BASE_SHA $CI_MERGE_REQUEST_SOURCE_BRANCH_SHA)
# merge request pipelines, when the mr got conflicts
elif [[ -n $CI_MERGE_REQUEST_DIFF_BASE_SHA ]]; then
git fetch origin $CI_MERGE_REQUEST_DIFF_BASE_SHA --depth=1 ${GIT_FETCH_EXTRA_FLAGS}
git fetch origin $CI_COMMIT_SHA --depth=1 ${GIT_FETCH_EXTRA_FLAGS}
export GIT_DIFF_OUTPUT=$(git diff --name-only $CI_MERGE_REQUEST_DIFF_BASE_SHA $CI_COMMIT_SHA)
# other pipelines, like the protected branches pipelines
elif [[ "$CI_COMMIT_BEFORE_SHA" != "0000000000000000000000000000000000000000" ]]; then
git fetch origin $CI_COMMIT_BEFORE_SHA --depth=1 ${GIT_FETCH_EXTRA_FLAGS}
git fetch origin $CI_COMMIT_SHA --depth=1 ${GIT_FETCH_EXTRA_FLAGS}
export GIT_DIFF_OUTPUT=$(git diff --name-only $CI_COMMIT_BEFORE_SHA $CI_COMMIT_SHA)
else
# pipeline source could be web, scheduler, etc.
git fetch origin $CI_COMMIT_SHA --depth=2 ${GIT_FETCH_EXTRA_FLAGS}
export GIT_DIFF_OUTPUT=$(git diff --name-only $CI_COMMIT_SHA~1 $CI_COMMIT_SHA)
fi
- MODIFIED_FILES=$(echo $GIT_DIFF_OUTPUT | xargs)
- echo "MODIFIED_FILES=$MODIFIED_FILES" >> pipeline.env
- pre-commit install --allow-missing-config -t pre-commit -t commit-msg
- pre-commit run --files $MODIFIED_FILES
after_script:
- git diff > fixed.patch
- cat fixed.patch
artifacts:
reports:
dotenv: pipeline.env
paths:
- pipeline.env
expire_in: 1 week
when: always
@@ -0,0 +1,118 @@
.patterns-build_components: &patterns-build_components
- ".gitlab/**/*"
- "tools/**/*"
- "elements/**/*"
- "gmf_core/**/*"
- "packages/**/*"
##############
# if anchors #
##############
.if-web: &if-web
if: '$CI_PIPELINE_SOURCE == "web"'
.if-ref-main: &if-ref-main
if: '$CI_COMMIT_REF_NAME == "main"'
.if-trigger: &if-trigger
if: '$CI_PIPELINE_SOURCE == "trigger"'
.if-pipeline: &if-pipeline
if: '$CI_PIPELINE_SOURCE == "pipeline"'
.if-schedule: &if-schedule
if: '$CI_PIPELINE_SOURCE == "schedule"'
.if-merge_request_event: &if-merge_request_event
if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
.if-open-merge-request: &if-open-merge-request
if: '$CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS && ($CI_PIPELINE_SOURCE == "push")'
.if-dev-push: &if-dev-push
if: '$CI_COMMIT_REF_NAME != "main" && $CI_COMMIT_BRANCH !~ /^release\/v/ && $CI_COMMIT_TAG !~ /^v\d+\.\d+(\.\d+)?($|-)/ && ($CI_PIPELINE_SOURCE == "push")'
.if-protected: &if-protected
if: '($CI_COMMIT_REF_NAME == "main" || $CI_COMMIT_BRANCH =~ /^release\/v/ || $CI_COMMIT_TAG =~ /^v\d+\.\d+(\.\d+)?($|-)/)'
.if-label-skip_unit_test: &if-label-skip_unit_test
if: '$BOT_LABEL_SKIP_UNIT_TEST || $CI_MERGE_REQUEST_LABELS =~ /^(?:[^,\n\r]+,)*skip_unit_test(?:,[^,\n\r]+)*$/i'
.if-label-skip_example_test: &if-label-skip_example_test
if: '$BOT_LABEL_SKIP_EXAMPLE_TEST || $CI_MERGE_REQUEST_LABELS =~ /^(?:[^,\n\r]+,)*skip_example_test(?:,[^,\n\r]+)*$/i'
.if-label-skip_example: &if-label-skip_example
if: '$BOT_LABEL_SKIP_EXAMPLE_TEST || $CI_MERGE_REQUEST_LABELS =~ /^(?:[^,\n\r]+,)*skip_example(?:,[^,\n\r]+)*$/i'
.idf-version-tag-v5-3: &idf-version-tag-v5-3
if: '$IDF_VERSION_TAG == "v5.3"'
variables:
IMAGE: "$CI_DOCKER_IMAGE_REGISTRY/env_v5.3:1"
.idf-version-tag-v5-4: &idf-version-tag-v5-4
if: '$IDF_VERSION_TAG == "v5.4.3"'
variables:
IMAGE: "$CI_IDF_DOCKER_REGISTRY/esp-env-v5.4:2"
.rules:ref:main-schedule:
rules:
- <<: *if-ref-main
- <<: *if-schedule
.rules:ref:check-label:
rules:
- <<: *if-merge_request_event
.rules:build:regular-board-idf-ver-tag:
rules:
- <<: *if-dev-push
when: never
- <<: *if-schedule
when: never
.rules:build:non-regular-board-idf-ver-tag:
rules:
- <<: *if-dev-push
when: never
- <<: *if-merge_request_event
when: never
.rules:build:protected-merge-requests-pipeline:
rules:
- <<: *if-dev-push
when: never
- <<: *idf-version-tag-v5-3
- <<: *idf-version-tag-v5-4
- <<: *if-web
- <<: *if-trigger
- <<: *if-pipeline
- <<: *if-protected
- <<: *if-merge_request_event
.rules:build:protected-merge-requests:
rules:
- <<: *if-web
- <<: *if-protected
- <<: *if-merge_request_event
.rules:build:enable-gmf-example-test:
rules:
- <<: *if-label-skip_example_test
when: never
- <<: *if-label-skip_example
when: never
- <<: *if-web
- <<: *if-protected
- <<: *if-merge_request_event
.rules:build:enable-gmf-unit-test:
rules:
- <<: *if-dev-push
when: never
- <<: *if-label-skip_unit_test
when: never
- <<: *if-web
- <<: *if-protected
- <<: *if-trigger
- <<: *if-schedule
- changes: *patterns-build_components
@@ -0,0 +1,70 @@
.target_example_test_template:
extends:
- .target_test_template
parallel:
matrix:
- IDF_VERSION_TAG: ['v5.4.3']
variables:
GIT_DEPTH: 1
SUBMODULES_TO_FETCH: "none"
IDF_TAG_FLAG: "true"
TEST_FW_PATH: "$CI_PROJECT_DIR/tools/tiny-test-fw"
LOG_PATH: "$CI_PROJECT_DIR/TEST_LOGS"
ENV_FILE: "${BASE_FRAMEWORK_PATH}/ci-test-runner-configs/$CI_RUNNER_DESCRIPTION/EnvConfig.yml"
artifacts:
when: always
paths:
- XUNIT_RESULT.xml
- "**/*.log"
- $LOG_PATH
- "$CI_PROJECT_DIR/**/output.wav"
reports:
junit: XUNIT_RESULT.xml
expire_in: 1 week
.pytest_template:
extends:
- .target_example_test_template
script:
- pip install pytest pytest-embedded-idf pytest-embedded-serial-esp pytest-rerunfailures pytest-timeout idf_build_apps
- eval "$(python ${CI_PROJECT_DIR}/tools/ci/build_apps.py ${TEST_DIR} --find --target-dir-type ${TARGET_DIR_TYPE} --target ${IDF_TARGET} -vv)"
- pytest "${APP_LIST[@]}" --target ${IDF_TARGET} -m "${tags}" --junitxml=XUNIT_RESULT.xml --parallel-count 1 --parallel-index 1
.pytest_audio_loopback_template:
image: "$CI_DOCKER_IMAGE_REGISTRY/target_test_env_v5.3:2"
extends:
- .rules:build:enable-gmf-example-test
- .target_example_test_template
script:
- export PYTHONPATH="/usr/local/lib/python3.9/dist-packages:$PYTHONPATH"
- pip install pytest pytest-embedded-idf pytest-embedded-serial-esp pytest-rerunfailures pytest-timeout idf_build_apps
- arecord -l
- eval "$(python ${CI_PROJECT_DIR}/tools/ci/build_apps.py ${TEST_DIR} --find --target-dir-type ${TARGET_DIR_TYPE} --target ${IDF_TARGET} -vv)"
- pytest "${APP_LIST[@]}" --target ${IDF_TARGET} -m "${tags}" --junitxml=XUNIT_RESULT.xml --parallel-count 1 --parallel-index 1
variables:
TEST_DIR: "${EXAMPLES_PATH}"
.pytest_examples_dir_template:
extends:
- .rules:build:enable-gmf-example-test
- .pytest_template
variables:
TEST_DIR: "${EXAMPLES_PATH}"
audio_playback_test_esp32:
extends:
- .pytest_audio_loopback_template
variables:
IDF_TARGET: esp32
needs:
- 'build_examples_esp32_lyrat_mini_v1_1: [v5.4.3, 1/1]'
tags: [ esp32, AUDIO_LOOPBACK_ENV ]
example_test_esp32s3:
extends:
- .pytest_examples_dir_template
variables:
IDF_TARGET: esp32s3
needs:
- 'build_examples_esp32s3_korvo_v2: [v5.4.3, 1/1]'
tags: [ esp32s3 ]
@@ -0,0 +1,62 @@
.pytest_ut_template:
extends:
- .target_test_template
variables:
IDF_VERSION_TAG: v5.4.3
IDF_TAG_FLAG: true
TARGET_DIR_TYPE: "test_apps"
artifacts:
when: always
paths:
- XUNIT_RESULT.xml
- pytest-embedded/
reports:
junit: XUNIT_RESULT.xml
expire_in: 1 week
script:
- cd $CI_PROJECT_DIR
- pip install pytest pytest-embedded-idf pytest-embedded-serial-esp pytest-rerunfailures pytest-timeout idf_build_apps
- eval "$(python ${CI_PROJECT_DIR}/tools/ci/build_apps.py ${TEST_DIR} --find --target-dir-type ${TARGET_DIR_TYPE} --target ${IDF_TARGET} -vv)"
- pytest "${APP_LIST[@]}"
--target ${IDF_TARGET}
--junitxml=XUNIT_RESULT.xml
--parallel-count ${CI_NODE_TOTAL:-1}
--parallel-index ${CI_NODE_INDEX:-1}
${PYTEST_EXTRA_FLAGS}
.pytest_components_dir_template:
extends:
- .rules:build:enable-gmf-unit-test
- .pytest_ut_template
variables:
TEST_DIR: ${CI_PROJECT_DIR}/gmf_core
BUILD_JOB_PREFIX: build_pytest_components_
IDF_TARGET: esp32s3
retry: 2
ut_gmf_core_esp32s3:
extends:
- .pytest_components_dir_template
needs:
- 'build_ut_gmf_core_esp32s3: [v5.4.3, 1/1]'
variables:
TEST_DIR: ${CI_PROJECT_DIR}/gmf_core
tags: [ ESP32S3, ESP_GMF_COMPONENT ]
ut_gmf_elements_esp32s3:
extends:
- .pytest_components_dir_template
needs:
- 'build_ut_gmf_elements_esp32s3: [v5.4.3, 1/1]'
variables:
TEST_DIR: ${CI_PROJECT_DIR}/elements
tags: [ ESP32S3, ESP_GMF_COMPONENT ]
ut_gmf_packages_esp32s3:
extends:
- .pytest_components_dir_template
needs:
- 'build_ut_gmf_packages_esp32s3: [v5.4.3, 1/1]'
variables:
TEST_DIR: ${CI_PROJECT_DIR}/packages
tags: [ ESP32S3, ESP_GMF_COMPONENT ]
@@ -0,0 +1,28 @@
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
repos:
- repo: https://github.com/codespell-project/codespell
rev: v2.2.6
hooks:
- id: codespell
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.3.0
hooks:
- id: check-executables-have-shebangs
- id: check-merge-conflict
- id: double-quote-string-fixer
- id: end-of-file-fixer
types_or: [c, c++]
- id: mixed-line-ending
args: ['-f=lf']
types_or: [c, c++]
- id: no-commit-to-branch
name: Do not use more than one slash in the branch name
args: ['--pattern', '^[^/]*/[^/]*/']
- id: no-commit-to-branch
name: Do not use uppercase letters in the branch name
args: ['--pattern', '^[^A-Z]*[A-Z]']
- id: trailing-whitespace
types_or: [c, c++]
@@ -0,0 +1,22 @@
Espressif Modified MIT License
Copyright (c) 2025 Espressif Systems (Shanghai) CO., LTD
Permission is hereby granted for use EXCLUSIVELY with Espressif Systems products.
This includes the right to use, copy, modify, merge, publish, distribute, and sublicense
the Software, subject to the following conditions:
1. This Software MUST BE USED IN CONJUNCTION WITH ESPRESSIF SYSTEMS PRODUCTS.
2. The above copyright notice and this permission notice shall be included in all copies
or substantial portions of the Software.
3. Redistribution of the Software in source or binary form FOR USE WITH NON-ESPRESSIF PRODUCTS
is strictly prohibited.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
SPDX-License-Identifier: LicenseRef-Espressif-Modified-MIT
@@ -0,0 +1,59 @@
# ESP-GMF
[中文版](./README_CN.md)
The Espressif General Multimedia Framework (ESP-GMF) is a lightweight and versatile software framework developed by Espressif specifically for IoT multimedia applications. It is highly flexible and scalable and tailored for IoT chips, with RAM usage as low as 7 KB. ESP-GMF supports applications in audio, image, and video processing, as well as any data-streaming product.
ESP-GMF consists of four modules: GMF-Core, Elements, Packages, and GMF-Examples.
- **GMF-Core** is the core of the ESP-GMF software framework and includes the main components such as GMF-Element, GMF-Pipeline, and GMF-Task.
- **Elements** implements various functional elements based on GMF-Core, such as codec algorithms, audio processing algorithms, and more.
- **Packages** implements high-level functional components based on GMF-Pipeline, such as the **ESP Audio Simple Player**, which is a basic audio decoding player.
- **GMF-Examples** provides examples of how to use GMF-Pipeline to implement simple functionalities, such as playing music from flash or an SD card.
# ESP-GMF Component Overview
ESP-GMF modules exist as components, which are categorized by functionality into **atomic components**, **basic components** and **advanced components**. When developing a project, it is recommended to use the elements and IOs components from the official repository. You can also create your own elements and IO components to extend its application scenarios.
## Atomic Components
Atomic components are essential, core building of ESP-GMF.
| Component Name | Function | Dependent Components |
| :------------: | :------------: | :------------ |
| [gmf_core](./gmf_core) | Core framework for GMF | None |
## Basic Components
Basic components are intermediate-layer modules in ESP-GMF that handle data processing and core capabilities like audio/video stream encoding/decoding. These components have clear input/output interfaces, focus on specific tasks, and are reusable and composable. They can be used independently or commonly combined to build complex applications.
| Component Name | Function | Dependent Components |
| :------------: | :------------: | :------------ |
| [gmf_audio](./elements/gmf_audio) | Elements for audio encoding, decoding,<br>and sound effects processing | - `gmf_core`<br>- `esp_audio_effects`<br>- `esp_audio_codec` |
| [gmf_misc](./elements/gmf_misc) | Utility elements | None |
| [gmf_io](./elements/gmf_io) | Input/output for files, flash, and HTTP | - `gmf_core`<br>- `esp_codec_dev` |
| [gmf_ai_audio](./elements/gmf_ai_audio) | Elements for intelligence audio processing<br>and speech recognition | - `esp-sr`<br>- `gmf_core` |
| [gmf_video](./elements/gmf_video) | Elements for video encoding, decoding,<br>and video effects processing | - `gmf_core`<br>- `esp_video_codec` |
## Advanced Components
Advanced components are encapsulation modules in ESP-GMF that target specific application scenarios, typically composed of multiple basic components or even atomic components. They encapsulate common multimedia business processes, hide the underlying pipeline construction and component configuration logic, provide simple and easy-to-use interfaces to help users quickly implement complex functions, simplify user development processes, and facilitate rapid integration. This category also includes utility modules and example collections.
| Component Name | Function | Dependent Components |
| :------------: | :------------: | :------------ |
| [esp_audio_simple_player](./packages/esp_audio_simple_player) | Simple audio player | - `gmf_audio`<br>- `gmf_io` |
| [gmf_loader](./packages/gmf_loader) | Set up the given GMF pool using the <br> configuration selected via `Kconfig` | - `gmf_core`<br>- `gmf_io`<br>- `gmf_audio`<br>- `gmf_misc`<br>- `gmf_video`<br>- `gmf_ai_audio`<br>- `esp_codec_dev`<br>- `esp_audio_codec`<br>- `esp_video_codec` |
| [gmf_app_utils](./packages/gmf_app_utils) | Common peripheral configuration, unit <br>testing tools Memory leak detection tools | - `gmf_core`<br>- `protocol_examples_common`<br>- `esp_board_manager`|
| [esp_capture](./packages/esp_capture) | High-level multimedia capture module | - `gmf_core`<br>- `gmf-audio`<br>- `gmf-video`<br>- `esp_muxer`<br>- `esp_codec_dev`<br>- `esp-sr`<br>- `esp_video`<br>- `esp32-camera`|
| [esp_board_manager](./packages/esp_board_manager) | Intelligent, automated board configuration and management utilities with YAML-based setup | Dependencies vary based on selected board |
| [esp_audio_render](./packages/esp_audio_render) | Audio render module with mixer support | - `gmf_core`<br>- `gmf-audio`<br>|
# ESP-GMF Usage Guide
For a basic example of GMF-Core API usage, see [test_apps](./gmf_core/test_apps/main/cases/gmf_pool_test.c). For practical application examples of elements, see the [ examples ](./gmf_examples/basic_examples/).
# Frequently Asked Questions
- **What is the difference between ESP-GMF and ESP-ADF?**
ESP-ADF is a functional repository that includes many modules, such as `audio_pipeline`, `services`, `peripherals`, and `audio boards`. It is commonly used for more complex projects. ESP-GMF is an extension of the `audio_pipeline` module, designed to support applications for streaming audio, video, and image data. ESP-GMF offers a more modular structure than ESP-ADF's `audio_pipeline` by organizing functionality into different components, making it more flexible. For example, it is suitable for simple data streaming, such as playing audio from SD card/flash, or combining multiple components to provide more complex modules (e.g., audio player `esp_audio_simple_player`). Future versions of ESP-ADF will replace the `audio_pipeline` module with ESP-GMF.
@@ -0,0 +1,58 @@
# ESP-GMF
[English](./README.md)
ESP-GMF 全称 Espressif General Multimedia Framework,是乐鑫开发的应用于 IoT 多媒体领域的轻量级通用软件框架。它灵活性高,可扩展性强,专为 IoT 芯片量身打造,RAM 资源占用只有 7 KB。ESP-GMF 可应用于音频、图像、视频等产品,还可应用于任何流式处理数据的产品中。
ESP-GMF 包含 GMF-Core、 Elements、Packages 和 GMF-Examples 四个模块。
- **GMF-Core** 是 ESP-GMF 软件框架的核心,有 GMF-Element、GMF-Pipeline、GMF-Task 等主要部件。
- **Elements** 是基于 GMF-Core 实现的各种具体功能 element,比如音视频编解码算法、音效处理算法 和 AI 算法等。
- **Packages** 基于 GMF-Pipeline 实现的 High Level 功能的组件示例,比如 **ESP Audio Simple Player** 是一个简单音频解码播放器。
- **GMF-Examples** 提供如何使用 GMF-Pipeline 实现简单功能的示例,比如播放 flash 或 SD 卡中的音乐。
# ESP-GMF 组件介绍
ESP-GMF 各个模块以组件的形式存在,组件又按功能分为**原子组件**、**基础组件**和**高级组件**。在开发项目时,推荐使用官方仓库的 elements 和 IOs 组件进行开发,也可以自行创建 element 和 IO 组件来扩展其应用场景。
## 原子组件
原子组件是 ESP-GMF 不可或缺的、核心的基础构建单元。
| 组件名称 | 功能 | 依赖的组件 |
| :------------: | :------------:|:------------ |
| [gmf_core](./gmf_core) | GMF 基础框架 | 无 |
## 基础组件
基础组件是 ESP-GMF 中的中间层模块,承担数据处理和音视频流编解码等核心能力。这些组件具备清晰的输入/输出接口,专注于一个具体的任务,具有复用性和可组合性。它们既可以单独使用,也常用于构建复杂应用程序。
| 组件名称 | 功能 | 依赖的组件 |
| :------------: | :------------:|:------------ |
| [gmf_audio](./elements/gmf_audio) | GMF 音频编解码和<br>音效处理 element | - `gmf_core`<br>- `esp_audio_effects`<br> - `esp_audio_codec` |
| [gmf_misc](./elements/gmf_misc) | 工具类 element | 无 |
| [gmf_io](./elements/gmf_io) | 文件、flash、HTTP 输入输出 | - `gmf_core`<br>- `esp_codec_dev` |
| [gmf_ai_audio](./elements/gmf_ai_audio) | 智能语音算法和<br>语音识别 element | - `esp-sr`<br>- `gmf_core` |
| [gmf_video](./elements/gmf_video) | GMF 视频编解码和<br>视频效果处理 element | - `gmf_core`<br>- `esp_video_codec` |
## 高级组件
高级组件是 ESP-GMF 中面向特定应用场景的封装模块,通常由多个基础功能组件甚至原子组件组合而成。它们封装了常见的多媒体业务流程,隐藏了底层的 pipeline 构建和组件配置逻辑,提供简单易用的接口,帮助用户快速实现复杂的功能,以简化用户开发流程,便于快速集成。该分类还包括一些工具类模块和示例集合。
| 组件名称 | 功能 | 依赖的组件 |
| :------------: | :------------:|:------------ |
| [esp_audio_simple_player](./packages/esp_audio_simple_player) | 简单的音频播放器 | - `gmf_audio`<br>- `gmf_io` |
| [gmf_loader](./packages/gmf_loader) | 使用 `Kconfig` 选择的配置<br>设置给定的 GMF Pool | - `gmf_core`<br>- `gmf_io`<br>- `gmf_audio`<br>- `gmf_misc`<br>- `gmf_video`<br>- `gmf_ai_audio`<br>- `esp_codec_dev`<br>- `esp_audio_codec`<br>- `esp_video_codec` |
| [gmf_app_utils](./packages/gmf_app_utils) | 常用外设配置,单元测试工具<br>内存泄漏检测工具 | - `gmf_core`<br>- `protocol_examples_common`<br>- `esp_board_manager` |
| [esp_capture](./packages/esp_capture) | 易用的音视频采集器 | - `gmf_core`<br>- `gmf-audio`<br>- `gmf-video`<br>- `esp_muxer`<br>- `esp_codec_dev`<br>- `esp-sr`<br>- `esp_video`<br>- `esp32-camera`|
| [esp_board_manager](./packages/esp_board_manager) | 智能、自动化的板子配置和管理工具,支持基于 YAML 的设置 | 根据选择的板子变化依赖 |
| [esp_audio_render](./packages/esp_audio_render) | 支持混音的音频渲染器 | - `gmf_core`<br>- `gmf-audio`<br>|
# ESP-GMF 使用说明
GMF-Core API 的简单示例代码请参考 [test_apps](./gmf_core/test_apps/main/cases/gmf_pool_test.c),Elements 实际应用示例请参考 [ examples ](./gmf_examples/basic_examples/)。
# 常见问题
- **ESP-GMF 和 ESP-ADF 有什么区别?**
ESP-ADF 是一个包含很多模块的功能性仓库,比如 `audio_pipeline`、`services`、`peripherals` 和 `audio boards` 等,它常应用于比较复杂的项目中。ESP-GMF 是将 `audio_pipeline` 独立出来并进行功能扩展,使其支持音频、视频、图像等流式数据的应用场景。ESP-GMF 按功能分为不同的组件,灵活性优于 ESP-ADF 的 `audio_pipeline`,例如用于简单流式数据的处理,从 SD 卡/flash 播放一个音频,以及多个组件结合使用提供较复杂的功能模块(如音频播放器 `esp_audio_simple_player `)。ESP-ADF 的后期版本会使用 ESP-GMF 替代 `audio_pipeline` 模块。
@@ -0,0 +1,340 @@
# SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
#
# SPDX-License-Identifier: Apache-2.0
import logging
import os
import pathlib
import re
import sys
from datetime import datetime
from typing import Callable, List, Optional, Tuple
import pytest
from _pytest.nodes import Item
from pytest import Config, FixtureRequest, Function, Session
from pytest_embedded.plugin import multi_dut_argument, multi_dut_fixture
from pytest_embedded.utils import to_list
IDF_VERSION = os.environ.get('IDF_VERSION')
PYTEST_ROOT_DIR = str(pathlib.Path(__file__).parent)
logging.info(f'Pytest root dir: {PYTEST_ROOT_DIR}')
SUPPORTED_TARGETS = ['esp32', 'esp32s2', 'esp32c3', 'esp32s3', 'esp32c2', 'esp32c6', 'esp32h2', 'esp32p4']
PREVIEW_TARGETS: List[str] = [] # this PREVIEW_TARGETS excludes 'linux' target
DEFAULT_SDKCONFIG = 'default'
TARGET_MARKERS = {
'esp32': 'support esp32 target',
'esp32s2': 'support esp32s2 target',
'esp32s3': 'support esp32s3 target',
'esp32c3': 'support esp32c3 target',
'esp32c2': 'support esp32c2 target',
'esp32c6': 'support esp32c6 target',
'esp32h2': 'support esp32h2 target',
'esp32p4': 'support esp32p4 target',
'linux': 'support linux target',
}
SPECIAL_MARKERS = {
'supported_targets': "support all officially announced supported targets ('esp32', 'esp32s2', 'esp32c3', 'esp32s3', 'esp32c2', 'esp32c6')",
'preview_targets': "support all preview targets ('none')",
'all_targets': 'support all targets, including supported ones and preview ones',
'temp_skip_ci': 'temp skip tests for specified targets only in ci',
'temp_skip': 'temp skip tests for specified targets both in ci and locally',
'nightly_run': 'tests should be executed as part of the nightly trigger pipeline',
'host_test': 'tests which should not be built at the build stage, and instead built in host_test stage',
'qemu': 'build and test using qemu-system-xtensa, not real target',
}
ENV_MARKERS = {
'GMF_EXAMPLE_GENERIC': 'Tests should be run on GMF_EXAMPLE_GENERIC runners.',
'ESP_GMF_COMPONENT': 'Tests should be run on ESP_GMF_COMPONENT runners.',
'ESP_GMF_ELEMENT': 'Tests should be run on random runners.',
'ESP_GMF_TASK': 'Tests should be run on random runners.',
'ESP_GMF_IO': 'Tests should be run on random runners.',
'ESP_GMF_RINGBUF': 'Tests should be run on random runners.',
'ESP_GMF_PBUF': 'Tests should be run on random runners.',
'ESP_GMF_BLOCK': 'Tests should be run on random runners.',
'GMF_LOADER': 'Tests should be run on random runners.',
'ELEMENT_POOL': 'Tests should be run on random runners.',
'ELEMENT_PORT': 'Tests should be run on random runners.',
'ESP_GMF_METHOD': 'Tests should be run on random runners.',
'AUDIO_LOOPBACK_ENV': 'Audio loopback testing verifies the functionality by comparing the audio input signal with the output signal.',
}
##################
# Help Functions #
##################
def format_case_id(target: Optional[str], config: Optional[str], case: str) -> str:
return f'{target}.{config}.{case}'
def item_marker_names(item: Item) -> List[str]:
return [marker.name for marker in item.iter_markers()]
def item_skip_targets(item: Item) -> List[str]:
def _get_temp_markers_disabled_targets(marker_name: str) -> List[str]:
temp_marker = item.get_closest_marker(marker_name)
if not temp_marker:
return []
# temp markers should always use keyword arguments `targets` and `reason`
if not temp_marker.kwargs.get('targets') or not temp_marker.kwargs.get('reason'):
raise ValueError(
f'`{marker_name}` should always use keyword arguments `targets` and `reason`. '
f'For example: '
f'`@pytest.mark.{marker_name}(targets=["esp32"], reason="IDF-xxxx, will fix it ASAP")`'
)
return to_list(temp_marker.kwargs['targets']) # type: ignore
temp_skip_ci_targets = _get_temp_markers_disabled_targets('temp_skip_ci')
temp_skip_targets = _get_temp_markers_disabled_targets('temp_skip')
# in CI we skip the union of `temp_skip` and `temp_skip_ci`
if os.getenv('CI_JOB_ID'):
skip_targets = list(set(temp_skip_ci_targets).union(set(temp_skip_targets)))
else: # we use `temp_skip` locally
skip_targets = temp_skip_targets
return skip_targets
@pytest.fixture(scope='session', autouse=True)
def idf_version() -> str:
if os.environ.get('IDF_VERSION'):
return os.environ.get('IDF_VERSION')
idf_path = os.environ.get('IDF_PATH')
if not idf_path:
logging.warning('Failed to get IDF_VERSION!')
return ''
version_path = os.path.join(os.environ['IDF_PATH'], 'tools/cmake/version.cmake')
regex = re.compile(r'^\s*set\s*\(\s*IDF_VERSION_([A-Z]{5})\s+(\d+)')
ver = {}
with open(version_path) as f:
for line in f:
m = regex.match(line)
if m:
ver[m.group(1)] = m.group(2)
return '{}.{}'.format(int(ver['MAJOR']), int(ver['MINOR']))
@pytest.fixture(scope='session', autouse=True)
def session_tempdir() -> str:
_tmpdir = os.path.join(
os.path.dirname(__file__),
'pytest_log',
datetime.now().strftime('%Y-%m-%d_%H-%M-%S'),
)
os.makedirs(_tmpdir, exist_ok=True)
return _tmpdir
@pytest.fixture
@multi_dut_argument
def config(request: FixtureRequest) -> str:
config_marker = list(request.node.iter_markers(name='config'))
return config_marker[0].args[0] if config_marker else 'default'
@pytest.fixture
@multi_dut_argument
def app_path(request: FixtureRequest, test_file_path: str) -> str:
config_marker = list(request.node.iter_markers(name='app_path'))
if config_marker:
return config_marker[0].args[0]
else:
# compatible with old pytest-embedded parametrize --app_path
return request.config.getoption('app_path', None) or os.path.dirname(test_file_path)
@pytest.fixture
def test_case_name(request: FixtureRequest, target: str, config: str) -> str:
if not isinstance(target, str):
target = '|'.join(sorted(list(set(target))))
if not isinstance(config, str):
config = '|'.join(sorted(list(config)))
return f'{target}.{config}.{request.node.originalname}'
@pytest.fixture
@multi_dut_fixture
def build_dir(
app_path: str,
target: Optional[str],
config: Optional[str],
idf_version: str
) -> Optional[str]:
"""
Check local build dir with the following priority:
1. <app_path>/${IDF_VERSION}/build_<target>_<config>
2. <app_path>/${IDF_VERSION}/build_<target>
3. <app_path>/build_<target>_<config>
4. <app_path>/build
5. <app_path>
Args:
app_path: app path
target: target
config: config
Returns:
valid build directory
"""
assert target
assert config
check_dirs = []
if idf_version:
check_dirs.append(os.path.join(idf_version, f'build_{target}_{config}'))
check_dirs.append(os.path.join(idf_version, f'build_{target}'))
check_dirs.append(f'build_{target}_{config}')
check_dirs.append(f'build_{target}_{config}s')
check_dirs.append('build')
check_dirs.append('.')
for check_dir in check_dirs:
binary_path = os.path.join(app_path, check_dir)
if os.path.isdir(binary_path):
logging.info(f'find valid binary path: {binary_path}')
return check_dir
logging.warning(
f'checking binary path: {binary_path} ... missing ... try another place')
logging.error(
f'no build dir. Please build the binary "python tools/build_apps.py {app_path}" and run pytest again')
sys.exit(1)
@pytest.fixture(autouse=True)
@multi_dut_fixture
def junit_properties(
test_case_name: str, record_xml_attribute: Callable[[str, object], None]
) -> None:
"""
This fixture is autoused and will modify the junit report test case name to <target>.<config>.<case_name>
"""
record_xml_attribute('name', test_case_name)
##################
# Hook functions #
##################
_idf_pytest_embedded_key = pytest.StashKey['IdfPytestEmbedded']
def pytest_addoption(parser: pytest.Parser) -> None:
base_group = parser.getgroup('idf')
base_group.addoption(
'--env',
help='only run tests matching the environment NAME.',
)
def pytest_configure(config: Config) -> None:
# Require cli option "--target"
help_commands = ['--help', '--fixtures', '--markers', '--version']
for cmd in help_commands:
if cmd in config.invocation_params.args:
target = 'unneeded'
break
else:
target = config.getoption('target')
if not target:
raise ValueError('Please specify one target marker via "--target [TARGET]"')
config.stash[_idf_pytest_embedded_key] = IdfPytestEmbedded(
target=target,
env_name=config.getoption('env'),
)
config.pluginmanager.register(config.stash[_idf_pytest_embedded_key])
for name, description in {**TARGET_MARKERS, **ENV_MARKERS, **SPECIAL_MARKERS}.items():
config.addinivalue_line('markers', f'{name}: {description}')
def pytest_unconfigure(config: Config) -> None:
_pytest_embedded = config.stash.get(_idf_pytest_embedded_key, None)
if _pytest_embedded:
del config.stash[_idf_pytest_embedded_key]
config.pluginmanager.unregister(_pytest_embedded)
class IdfPytestEmbedded:
def __init__(
self,
target: Optional[str] = None,
env_name: Optional[str] = None,
):
# CLI options to filter the test cases
self.target = target
self.env_name = env_name
self._failed_cases: List[
Tuple[str, bool, bool]
] = [] # (test_case_name, is_known_failure_cases, is_xfail)
@pytest.hookimpl(tryfirst=True)
def pytest_sessionstart(self, session: Session) -> None:
if self.target:
self.target = self.target.lower()
session.config.option.target = self.target
@pytest.hookimpl(tryfirst=True)
def pytest_collection_modifyitems(self, items: List[Function]) -> None:
# sort by file path and callspec.config
# implement like this since this is a limitation of pytest, couldn't get fixture values while collecting
# https://github.com/pytest-dev/pytest/discussions/9689
# after sort the test apps, the test may use the app cache to reduce the flash times.
def _get_param_config(_item: Function) -> str:
if hasattr(_item, 'callspec'):
return _item.callspec.params.get('config', DEFAULT_SDKCONFIG) # type: ignore
return DEFAULT_SDKCONFIG
items.sort(key=lambda x: (os.path.dirname(x.path), _get_param_config(x)))
# set default timeout 10 minutes for each case
for item in items:
if 'timeout' not in item.keywords:
item.add_marker(pytest.mark.timeout(10 * 60))
# add markers for special markers
for item in items:
if 'supported_targets' in item.keywords:
for _target in SUPPORTED_TARGETS:
item.add_marker(_target)
if 'preview_targets' in item.keywords:
for _target in PREVIEW_TARGETS:
item.add_marker(_target)
if 'all_targets' in item.keywords:
for _target in [*SUPPORTED_TARGETS, *PREVIEW_TARGETS]:
item.add_marker(_target)
# add 'xtal_40mhz' tag as a default tag for esp32c2 target
# only add this marker for esp32c2 cases
if (
self.target == 'esp32c2'
and 'esp32c2' in item_marker_names(item)
and 'xtal_26mhz' not in item_marker_names(item)
):
item.add_marker('xtal_40mhz')
# filter all the test cases with "nightly_run" marker
if os.getenv('INCLUDE_NIGHTLY_RUN') == '1':
# Do not filter nightly_run cases
pass
elif os.getenv('NIGHTLY_RUN') == '1':
items[:] = [item for item in items if 'nightly_run' in item_marker_names(item)]
else:
items[:] = [item for item in items if 'nightly_run' not in item_marker_names(item)]
# filter all the test cases with target and skip_targets
items[:] = [
item
for item in items
if self.target in item_marker_names(item) and self.target not in item_skip_targets(item)
]
@@ -0,0 +1,5 @@
# ESP-GMF-Elements
- [中文版](./README_CN.md)
This is Espressif's official GMF elements component collection: [GMF Element Audio](./gmf_audio/README.md), [GMF Element Misc](./gmf_misc/README.md), GMF Element Image, [GMF IO](./gmf_io/README.md), and GMF AI Audio. Please refer to the README files of each component for detailed functionality and descriptions.
@@ -0,0 +1,5 @@
# ESP-GMF-Elements
- [English](./README.md)
这是 Espressif 官方 GMF elements 组件集合:[GMF Element Audio](./gmf_audio/README_CN.md),[GMF Element Misc](./gmf_misc/README_CN.md),GMF Element Image, [GMF IO](./gmf_io/README_CN.md) 和 GMF AI Audio。请查看各个组件的 readme 了解具体功能和说明。
@@ -0,0 +1,67 @@
# Changelog
## v0.7.4
### Bug Fixes
- Fixed a crash issue that occurred when the AFE manager was destroyed.
## v0.7.3
### Features
- Added default sdkconfig for esp32p4 in wwe example
### Bug Fixes
- Fixed build error for esp32p4
## v0.7.2
### Features
- Added `esp_gmf_afe_trigger_wakeup` and `esp_gmf_afe_trigger_sleep` APIs to trigger wakeup and sleep events manually
## v0.7.1
### Features
- Increased `esp-sr` dependency to version 2.1.5
## v0.7.0
### Features
- Added `esp_gmf_afe_set_event_cb` API to register AFE element event callbacks
- Replaced the interface for decoder reconfiguration
- Introduced `esp_gmf_wn` element to support wake word detection
- Corrected return value validation for *acq_write* and *acq_release* callback function implementations
- Added `esp_gmf_afe_keep_awake` API to enable/disable the keep awake feature
- Increased `esp-sr` dependency to version 2.1.4
### Bug Fixes
- Fixed an issue where the output attribute of the `esp_gmf_afe` element was not set correctly
- Fixed a build error occurring when SPIRAM is disabled
- Integrated gmf_app_utils package for peripheral and system management
- Migrated common utilities to gmf_app_utils package
- Standardize TAG identifier format across all elements with `ai` prefix
- Fixed examples TAGs issue because of updating TAGs gmf_audio gmf_io
## v0.6.2
### Features
- Limit the version of `esp_audio_codec` and `esp_codec_dev` in `aec_rec` example
## v0.6.1
### Bug Fixes
- Fixed a build issue caused by a variable type mismatch
## v0.6.0
### Features
- Initial version of GMF AI Audio Elements
@@ -0,0 +1,12 @@
set(srcs "./src/esp_gmf_wn.c")
if((${IDF_TARGET} STREQUAL "esp32s3") OR (${IDF_TARGET} STREQUAL "esp32p4") OR (${IDF_TARGET} STREQUAL "esp32"))
list(APPEND srcs "./src/esp_gmf_afe_manager.c" "./src/esp_gmf_afe.c" "./src/esp_gmf_aec.c")
endif()
idf_component_register(
SRCS "${srcs}"
INCLUDE_DIRS "./include"
PRIV_INCLUDE_DIRS "./private_include"
REQUIRES esp-sr esp_timer
)
@@ -0,0 +1,22 @@
Espressif Modified MIT License
Copyright (c) 2025 Espressif Systems (Shanghai) CO., LTD
Permission is hereby granted for use EXCLUSIVELY with Espressif Systems products.
This includes the right to use, copy, modify, merge, publish, distribute, and sublicense
the Software, subject to the following conditions:
1. This Software MUST BE USED IN CONJUNCTION WITH ESPRESSIF SYSTEMS PRODUCTS.
2. The above copyright notice and this permission notice shall be included in all copies
or substantial portions of the Software.
3. Redistribution of the Software in source or binary form FOR USE WITH NON-ESPRESSIF PRODUCTS
is strictly prohibited.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
SPDX-License-Identifier: LicenseRef-Espressif-Modified-MIT
@@ -0,0 +1,248 @@
# GMF AI Audio
- [![Component Registry](https://components.espressif.com/components/espressif/gmf_ai_audio/badge.svg)](https://components.espressif.com/components/espressif/gmf_ai_audio)
- [中文版](./README_CN.md)
`GMF AI Audio` is an artificial intelligence audio processing module that provides users with convenient and easy-to-use intelligent audio processing algorithms at the [GMF](https://github.com/espressif/esp-gmf) framework, such as voice wake-up, command word recognition, and echo cancellation. Currently, it offers the following modules based on `esp-sr`:
- [esp_gmf_afe_manager](./src/esp_gmf_afe_manager.c): `audio front end(afe)` manager
- [esp_gmf_aec](./src/esp_gmf_aec.c): Echo Cancellation
- [esp_gmf_wn](./src/esp_gmf_wn.c): A standalone wake word detection module that can be used independently
- [esp_gmf_afe](./src/esp_gmf_afe.c): An easy-to-use interface based on the `audio front end (afe)` from `esp-sr`, providing functionalities such as voice wake-up, command word recognition, and speech detection
| Name | Tag | Function | Method | Input Channel Number | Output Channel Number | Model Partition Dependency | Input Frame Length | Notes |
|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----|
| esp_gmf_afe | ai_afe | Audio front-end processing: Wake word detection, command word recognition, voice enhancement, echo cancellation, noise suppression, automatic gain control | `start_vcmd_det` | 1-4 | 1 | Yes | 256(sample) | Currently supports up to 2 microphone channels + 1 speaker reference signal, remaining channel selection marked as N, requires following voice command detection procedure |
| esp_gmf_aec | ai_aec | Echo cancellation: Eliminates echo interference in audio, improves voice quality | None | 1-4 | 1 | No | 256(sample) | Input channels can be set to multiple microphones, uses first microphone channel and reference channel for calculation, must include reference signal |
| esp_gmf_wn | ai_wn | Independent wake word detection: Lightweight wake word detection, independent of AFE, low resource consumption | None | 1-4 | 1 | Yes | 256(sample) | Supports up to 3 microphone channels, microphone channel count in input format must match working mode |
## AFE Manager `esp_gmf_afe_manager`
### Features
- Manages the data path and task scheduling of the Audio Front-End (AFE)
- Supports dynamic enabling/disabling of features (e.g., wake-up, AEC, VAD)
- Provides multi-task coordination to ensure real-time audio stream processing
### Key Characteristics
- Task Management: Creates independent `feed_task` and `fetch_task` for audio data input and result processing
- Feature Control: Dynamically toggle algorithm modules via `esp_afe_manager_enable_features`
- Event-Driven: Supports suspend/resume operations (`esp_afe_manager_suspend`) for low-power scenarios
### System Diagram
```mermaid
graph LR
subgraph AFE Manager
DATA_CB[Raw Data]
FEED_TASK[feed_task<br>Audio Input Task]
FETCH_TASK[fetch_task<br>Result Processing Task]
AFE_CORE[AFE Core<br>esp_afe_sr_iface]
EVENT_CB[Result Callback]
CTRL_IF[Control Interface]
end
FEED_TASK -->|Data Callback| DATA_CB
FEED_TASK -->|Raw Audio Data| AFE_CORE
AFE_CORE -->|Processing Results| FETCH_TASK
FETCH_TASK -->|Result Callback<br>Data/Wake-Up/VAD/Command Words| EVENT_CB
CTRL_IF -->|Suspend/Resume Interface| FEED_TASK
CTRL_IF -->|Suspend/Resume Interface| FETCH_TASK
CTRL_IF -->|Feature Toggle Interface<br>AEC/VAD/SE| AFE_CORE
```
## Echo Cancellation `esp_gmf_aec`
### Features
- Eliminates echo interference in audio to improve voice quality
- Supports multi-channel input and single-channel output
### Technical Specifications
- Supported Hardware: `ESP32`, `ESP32-S3`, and `ESP32-P4`
- Input Format
- Sampling Rate: 8 kHz and 16 kHz
- Bit Width: 16-bit PCM
- Channel Configuration: String identifiers (e.g., `MMNR`)
- `M`: Microphone Channel
- `R`: Reference Signal Channel
- `N`: Invalid Signal
- Output Format: 16-bit single-channel PCM
## Wake Word Detection `esp_gmf_wn`
### Features
- Runs independently without AFE dependency, low resource usage
- Supports multi-channel input and single-channel output
- Notifies wake word detection results via callback function
### Technical Specifications
- Supported Hardware: `ESP32`, `ESP32-S3`, `ESP32-C3`, `ESP32-C5` and `ESP32-P4`
- Input Format
- Sampling Rate: 16 kHz
- Bit Width: 16-bit PCM
- Channel Configuration: String identifiers (e.g., `MMNR`)
- `M`: Microphone Channel
- `R`: Reference Signal Channel
- `N`: Invalid Signal
- Output Format: 16-bit single-channel PCM
## Speech Recognition `esp_gmf_afe`
### Features
- Voice Wake-Up (Wake Word Detection)
- Command Word Recognition
- Voice Activity Detection (VAD) and State Management
### Key Characteristics
- Supported Hardware: `ESP32`, `ESP32-S3`, and `ESP32-P4`
- Input and Output: Same as described in `esp_gmf_aec`
- Unified Event Interface: Notifies events (wake-up start/end, VAD state changes, command word triggers) via callback functions
- State Machine Management: Automatically maintains transitions between wake-up, speech, and silence states
### System Diagram
```mermaid
graph TD
subgraph Audio Preprocessing
RAW[Audio Data]
end
subgraph GMF_AFE
AFE_MGR[AFE Manager<br>esp_gmf_afe_manager]
INPUT_RB[Input Buffer]
OUTPUT_RB[Output Buffer]
STATE_MACHINE[Wake-Up/VAD State Machine]
MN_DET[Command Word Detection]
DELAY[Delay Mechanism]
end
subgraph Application Layer
EVENT_CB[Event Callback]
OUT_PORT[Data Output]
end
RAW -->|Processed Audio Data| INPUT_RB
INPUT_RB -->|Data Callback| AFE_MGR
AFE_MGR -->|Result Callback<br>Data| OUTPUT_RB
AFE_MGR -->|Result Callback<br>Wake-Up/VAD Events| STATE_MACHINE
AFE_MGR -->|Result Callback<br>Data| MN_DET
OUTPUT_RB --> DELAY --> OUT_PORT
STATE_MACHINE -->|Event Notification| EVENT_CB
MN_DET -->|Event Notification| EVENT_CB
```
### `esp_gmf_afe_cfg_t` Advanced Configuration Parameters
| Parameter | Description | Default Value |
| --- | --- | --- |
| **wakeup_time** | Time in milliseconds to trigger wake-up end event if no voice activity is detected after enabling wake-up | 10000 (ms) |
| **wakeup_end** | Time in milliseconds to trigger wake-up end event after detecting silence following voice activity | 2000 (ms) |
| **vcmd_timeout** | Timeout for command word detection. After timeout, the interface must be called again to start a new detection round | 5760 (ms) |
| **delay_samples** | Output data delay to compensate for VAD detection lag. The specified data length (in samples) should be greater than or equal to `afe_config_t.vad_min_speech_ms` configured during AFE manager initialization | 2048 (samples) |
### Wake Word and VAD State Machine
The following illustrates state transitions when features are enabled. The `/` character indicates the triggered user event
- **Enable Wake Word Detection, Disable VAD**
This scenario focuses on detecting wake words and can be configured (`wakeup_time`) to trigger an end event after a certain period
Modify the configuration in the example [wwe](./examples/wwe/main/main.c) to use this scenario
```c
#define WAKENET_ENABLE (true)
#define VAD_ENABLE (false)
```
```mermaid
stateDiagram-v2
[*] --> IDLE
IDLE --> WAKEUP: Wake Word / (WAKEUP_START)
WAKEUP --> IDLE: Timer (wakeup_time) Timeout / (WAKEUP_END)
```
- **Enable VAD, Disable Wake Word Detection**
This scenario focuses on detecting voice activity and triggers corresponding events at the start and end of speech
Modify the configuration in the example [wwe](./examples/wwe/main/main.c) to use this scenario
```c
#define WAKENET_ENABLE (false)
#define VAD_ENABLE (true)
```
```mermaid
stateDiagram-v2
[*] --> IDLE
IDLE --> SPEECHING: Voice Detected / (VAD_START)
SPEECHING --> IDLE: Noise or Silence Detected / (VAD_END)
```
- **Enable Both Wake Word Detection and VAD**
This scenario combines wake word detection with voice activity detection to avoid frequent voice activity events outside the wake word interval
Modify the configuration in the example [wwe](./examples/wwe/main/main.c) to use this scenario
```c
#define WAKENET_ENABLE (true)
#define VAD_ENABLE (true)
```
```mermaid
stateDiagram-v2
[*] --> IDLE
IDLE --> WAKEUP: Wake Word / (WAKEUP_START)
WAKEUP --> SPEECHING: Voice Detected / (VAD_START)
WAKEUP --> IDLE: Timer (wakeup_time) Timeout / (WAKEUP_END)
SPEECHING --> WAIT_FOR_SLEEP: Noise or Silence Detected / (VAD_END)
WAIT_FOR_SLEEP --> SPEECHING: Voice Detected / (VAD_START)
WAIT_FOR_SLEEP --> IDLE: Timer (wakeup_end) Timeout / (WAKEUP_END)
```
### Command Word Detection
Users need to decide when to start command word detection. A typical use case is to enable detection after the state machine pushes a (WAKEUP_START) event and determine the next operation based on the detected command word index in the callback function
- Command word detection is independent of the wake word state machine
- Command word detection supports continuous detection until timeout
## Usage
For example code, refer to the [examples](./examples) folder for the Wake Word Detection and Audio Echo Cancellation demos.
You can also create and compile a project using the following commands, taking the `aec_rec` project as an example. Before starting, make sure you have a working [ESP-IDF](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/get-started/index.html) environment.
### 1. Create the Example Project
Create the `aec_rec` example project based on the `gmf_ai_audio` component (using version v0.7.0 as an example; update the version as needed):
```shell
idf.py create-project-from-example "espressif/gmf_ai_audio=0.7.0:aec_rec"
```
### 2. Build and Flash the Project Using an ESP32-S3 Board
```shell
cd aec_rec
idf.py set-target esp32s3
idf.py -p YOUR_PORT flash monitor
```
@@ -0,0 +1,248 @@
# GMF AI Audio
- [![Component Registry](https://components.espressif.com/components/espressif/gmf_ai_audio/badge.svg)](https://components.espressif.com/components/espressif/gmf_ai_audio)
- [English](./README.md)
`GMF AI Audio` 是一个人工智能语音处理模块,它在 [GMF](https://github.com/espressif/esp-gmf) 层面为用户提供方便、易用的语音唤醒、命令词识别和回声消除等常用智能语音处理算法。目前基于 `esp-sr` 提供以下模块:
- [esp_gmf_afe_manager](./src/esp_gmf_afe_manager.c): `audio front end(afe)` 管理器
- [esp_gmf_aec](./src/esp_gmf_aec.c): 回声消除
- [esp_gmf_wn](./src/esp_gmf_wn.c): 独立的唤醒词检测模块,不依赖AFE
- [esp_gmf_afe](./src/esp_gmf_afe.c): 基于 `esp-sr` 中 `audio front end (afe)` 实现的易用接口,提供了语音唤醒,命令词识别,人声检测等语音识别的功能
| 名称 | 标签 | 功能 | 函数方法 | 输入通道 | 输出通道 | 依赖模型数据分区 | 输入帧长 | 备注 |
|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----:|:----|
| esp_gmf_afe | ai_afe | 语音前处理:唤醒词检测、命令词识别、人声增强、回声消除、降噪、自动增益控制 | `start_vcmd_det` | 1-4 | 1 | 是 | 256(sample) | 目前最多支持2通道麦克风+1个扬声器的回采信号,剩余通道选择记为N,同时需要按照命令词检测步骤操作 |
| esp_gmf_aec | ai_aec | 回声消除:消除音频中的回声干扰,提升语音质量 | 无 | 1-4 | 1 | 否 | 256(sample) | 输入通道可以设置多个麦克风,采用第一个麦克风通道和回采通道进行计算,且必须包含回采信号 |
| esp_gmf_wn | ai_wn | 独立唤醒词检测:轻量级唤醒词检测,不依赖AFE,资源占用小 | 无 | 1-4 | 1 | 是 | 256(sample) | 最高支持3通道麦克风,输入格式中的麦克风通道数需要与工作模式对应 |
## AFE 管理器 `esp_gmf_afe_manager`
### 功能
- 管理音频前端(Audio Front-End, AFE)的数据通路与任务调度
- 支持动态启用/禁用特性(如唤醒、AEC、VAD)
- 提供多任务协同机制,确保实时音频流处理
### 关键特性
- 任务管理:创建独立的 `feed_task` 和 `fetch_task`,分别负责音频数据输入和结果处理
- 特性控制:通过 `esp_afe_manager_enable_features` 动态开关算法模块
- 事件驱动:支持挂起/恢复操作 `esp_afe_manager_suspend`,适配低功耗场景
### 系统框图
```mermaid
graph LR
subgraph AFE管理器
DATA_CB[原始数据]
FEED_TASK[feed_task<br>音频输入任务]
FETCH_TASK[fetch_task<br>结果处理任务]
AFE_CORE[AFE核心<br>esp_afe_sr_iface]
EVENT_CB[结果回调]
CTRL_IF[控制接口]
end
FEED_TASK -->|数据回调| DATA_CB
FEED_TASK -->|原始音频数据| AFE_CORE
AFE_CORE -->|处理结果| FETCH_TASK
FETCH_TASK -->|结果回调<br>(数据/唤醒/VAD/命令词)| EVENT_CB
CTRL_IF -->|挂起/恢复接口| FEED_TASK
CTRL_IF -->|挂起/恢复接口| FETCH_TASK
CTRL_IF -->|特性使能接口<br>(AEC/VAD/SE)| AFE_CORE
```
## 回声消除 `esp_gmf_aec`
### 功能
- 消除音频中的回声干扰,提升语音质量
- 支持多通道输入,单通道输出
### 技术规格
- 支持硬件:`ESP32`、`ESP32-S3` 和 `ESP32-P4`
- 输入格式
- 采样率:16 kHz
- 位宽:16-bit PCM
- 通道配置:字符串标识(如 `MMNR`)
- `M`:麦克风通道
- `R`:回采信号通道
- `N`:无效信号
- 输出格式:16-bit 单通道 PCM
## 唤醒词检测 `esp_gmf_wn`
### 功能
- 独立运行,不依赖AFE,资源占用小
- 支持多通道输入,单通道输出
- 通过回调函数通知唤醒词检测结果
### 技术规格
- 支持硬件:`ESP32`、`ESP32-S3`、`ESP32-C3`、`ESP32-C5` 和 `ESP32-P4`
- 输入格式
- 采样率:8 kHz, 16 kHz
- 位宽:16-bit PCM
- 通道配置:字符串标识(如 `MMNR`)
- `M`:麦克风通道
- `R`:回采信号通道
- `N`:无效信号
- 输出格式:16-bit 单通道 PCM
## 语音识别 `esp_gmf_afe`
### 功能
- 语音唤醒(Wake Word Detection)
- 命令词识别(Voice Command Detection)
- 人声检测(VAD)与状态管理
### 关键特性
- 支持硬件: `esp32`, `esp32s3` 和 `esp32p4`
- 输入与输出同上文中的 `esp_gmf_aec`
- 统一事件接口:通过回调函数通知事件(唤醒开始/结束、VAD 状态变化、命令词触发)
- 状态机管理:自动维护唤醒、语音、静音等状态切换
### 系统框图
```mermaid
graph TD
subgraph 音频前级处理
RAW[音频数据]
end
subgraph GMF_AFE
AFE_MGR[AFE管理器<br>esp_gmf_afe_manager]
INPUT_RB[输入缓存]
OUTPUT_RB[输出缓存]
STATE_MACHINE[唤醒/VAD状态机]
MN_DET[命令词检测]
DELAY[延时机制]
end
subgraph 应用层
EVENT_CB[事件回调]
OUT_PORT[数据出口]
end
RAW -->| 音频前级处理后的数据| INPUT_RB
INPUT_RB -->| 数据回调 | AFE_MGR
AFE_MGR -->| 结果回调<br>数据 | OUTPUT_RB
AFE_MGR -->| 结果回调<br>唤醒/VAD事件 | STATE_MACHINE
AFE_MGR -->| 结果回调<br>数据 | MN_DET
OUTPUT_RB --> DELAY --> OUT_PORT
STATE_MACHINE -->| 事件通知 | EVENT_CB
MN_DET -->| 事件通知 | EVENT_CB
```
### `esp_gmf_afe_cfg_t` 高级配置参数说明
| 参数 | 描述 | 默认值 |
| --- | --- | --- |
| **wakeup_time** | 唤醒功能启用后,若未检测到人声事件,则在等待 `wakeup_time` 毫秒后触发唤醒结束事件 | 10000(ms) |
| **wakeup_end** | 唤醒功能启用后,若依次检测到人声和静音,则在静音事件触发后等待 `wakeup_end` 毫秒,触发唤醒结束事件 | 2000(ms) |
| **vcmd_timeout** | 命令词检测的超时时间,超时后需重新调用接口以开启新一轮命令词检测 | 5760(ms) |
| **delay_samples** | 输出数据延迟,用于补偿 VAD 检测滞后导致的数据输出与事件回调不同步的问题。该参数指定的数据长度(以采样点为单位)换算为时间后,应大于等于初始化 AFE 管理器时配置的 `afe_config_t.vad_min_speech_ms` | 2048(samples) |
### 唤醒与VAD状态机
以下列举了功能使能情况下的状态切换,`/` 字符之后为触发的用户事件
- **使能唤醒,不使能VAD**
该场景主要关注唤醒词的检测,同时可以配置(wakeup_time),在一段时间后触发结束事件
修改示例 [wwe](./examples/wwe/main/main.c) 中的配置,从而使用该场景
```c
#define WAKENET_ENABLE (true)
#define VAD_ENABLE (false)
```
```mermaid
stateDiagram-v2
[*] --> IDLE
IDLE --> WAKEUP: 唤醒词 / (WAKEUP_START)
WAKEUP --> IDLE: 定时器(wakeup_time)超时 / (WAKEUP_END)
```
- **使VAD,不使能唤醒词**
该场景主要关注人声的检测,在检测到人说话的前后节点触发相应事件
修改示例 [wwe](./examples/wwe/main/main.c) 中的配置,从而使用该场景
```c
#define WAKENET_ENABLE (false)
#define VAD_ENABLE (true)
```
```mermaid
stateDiagram-v2
[*] --> IDLE
IDLE --> SPEECHING: 检测到人声 / (VAD_START)
SPEECHING --> IDLE: 检测到噪声或静音 / (VAD_END)
```
- **使能唤醒和VAD**
该场景结合唤醒词与人声检测,避免在唤醒间隔之外频繁触发人声检测事件
修改示例 [wwe](./examples/wwe/main/main.c) 中的配置,从而使用该场景
```c
#define WAKENET_ENABLE (true)
#define VAD_ENABLE (true)
```
```mermaid
stateDiagram-v2
[*] --> IDLE
IDLE --> WAKEUP: 唤醒词 / (WAKEUP_START)
WAKEUP --> SPEECHING: 检测到人声 / (VAD_START)
WAKEUP --> IDLE: 定时器(wakeup_time)超时 / (WAKEUP_END)
SPEECHING --> WAIT_FOR_SLEEP: 检测到噪声或静音 / (VAD_END)
WAIT_FOR_SLEEP --> SPEECHING: 检测到人声 / (VAD_START)
WAIT_FOR_SLEEP --> IDLE: 定时器(wakeup_end)超时 / (WAKEUP_END)
```
### 命令词检测
用户需决定何时开始检测命令词,如典型的使用场景:在状态机推送(WAKEUP_START)事件之后,开启检测,并在回调函数中根据检测到的命令词序号决定下一步操作
- 命令词检测与唤醒状态机独立
- 命令词支持连续检测,直到超时
## 示例
示例代码,请参阅 [示例](./examples) 文件夹中的唤醒词检测和音频回声消除程序。
此外,也可通过以下命令创建并编译工程,以 `aec_rec` 示例工程为例。开始之前需要有可运行的 [ESP-IDF](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/get-started/index.html) 环境。
### 1. 创建示例工程
基于 `gmf_ai_audio` 组件创建 `aec_rec` 的示例(以 v0.7.0 版本为例, 请根据实际使用更新版本参数)
``` shell
idf.py create-project-from-example "espressif/gmf_ai_audio=0.7.0:aec_rec"
```
### 2. 基于 ESP32S3 编译和下载
```shell
cd aec_rec
idf.py set-target esp32s3`
idf.py -p YOUR_PORT flash monitor
```
@@ -0,0 +1,7 @@
# The following lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(aec_rec)
@@ -0,0 +1,244 @@
# Play Music from SDCard While Recording Audio with AEC
- [中文版](./README_CN.md)
## Example Brief
This example uses two pipelines to demonstrate the audio recording effect of AEC. One pipeline reads MP3 files stored on the SD card using File IO, then decodes the data through a decoder element. After decoding, the data undergoes audio effect processing and is output as music through the CODEC_DEV_TX IO. Simultaneously, another pipeline uses CODEC_DEV_RX IO to read PCM data from the I2S device, processes it with AEC, encodes the audio, and finally saves it as a file using File IO
This example supports compressing the audio processed by AEC into the `AAC` format, which can be enabled through the macro `ENCODER_ENABLE` in [main.c](./main/main.c):
```c
#define ENCODER_ENABLE (false)
```
- `ENCODER_ENABLE`: Enabling this option will activate the `AAC` encoder in the recording pipeline and save the encoded data as `aec.aac`
## GMF Pipeline
Below is the diagram of the pipelines used in this example:
```mermaid
graph TD
subgraph Path1 [MP3 Player]
A[File IO] --> B[Decoder: MP3] --> C[Audio Effects] --> D[CODEC_DEV_TX]
end
subgraph Path2 [AEC Recorder]
E[CODEC_DEV_RX] --> F[Rate_cvt] --> G[AEC] --> H[Encoder: AAC] --> I[File IO]
end
```
## AEC Element Initialization Example
```c
esp_gmf_element_handle_t gmf_aec_handle = NULL;
esp_gmf_aec_cfg_t gmf_aec_cfg = {
.filter_len = 4,
.type = AFE_TYPE_VC,
.mode = AFE_MODE_HIGH_PERF,
.input_format = "RMNM",
};
esp_gmf_aec_init(&gmf_aec_cfg, &gmf_aec_handle);
```
### Parameter Explanation
- `filter_len`: Filter length. Higher values increase CPU load. For `esp32s3` and `esp32p4`, a filter length of 4 is recommended. For `esp32c5`, a filter length of 2 is recommended
- `type`: AEC type, with the following options:
- `AFE_TYPE_VC`: Suitable for voice communication echo cancellation
- `AFE_TYPE_SR`: Suitable for voice recognition echo cancellation
- `mode`: AEC mode, with the following options:
- `AFE_MODE_LOW_POWER`: Low-power mode, suitable for power-sensitive scenarios
- `AFE_MODE_HIGH_PERF`: High-performance mode, suitable for scenarios requiring high-quality echo cancellation
- `input_format`: Input data format, such as `RMNM`:
- `M`: Microphone channel
- `R`: Reference signal channel
- `N`: Invalid signal
By configuring these parameters, you can adjust the performance and resource usage of AEC according to specific application scenarios. Once added to the `GMF Pool`, the AEC element can be used in the `GMF Pipeline` to process audio streams
## Example Set Up
### Default IDF Branch
This example supports IDF release/v5.3 and later branches
### Build and Flash
Before compiling this example, ensure that the ESP-IDF environment is properly configured. If it is already set up, you can proceed to the next configuration step. If not, run the following script in the root directory of ESP-IDF to set up the build environment. For detailed steps on configuring and using ESP-IDF, please refer to the [ESP-IDF Programming Guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/index.html)
```
./install.sh
. ./export.sh
```
Here are the summarized steps for compilation:
- Enter the location where the test project is stored:
```
cd gmf_ai_audio/examples/aec_rec
```
- Select the target chip for compilation. For example, to use the ESP32S3:
```
idf.py set-target esp32s3
```
- Select the compilation board, taking ESP32-S3-Korvo V2 as an example:
```
idf.py menuconfig
In 'menuconfig', select 'GMF APP Configuration' -> 'Audio Board' -> 'ESP32-S3-Korvo V2', and then save and exit
```
- Build the example:
```
idf.py build
```
- Flash the program and run the monitor tool to view serial output (replace PORT with the port name):
```
idf.py -p PORT flash monitor
```
- Exit the debugging interface using ``Ctrl-]``
## How to Use the Example
- This example requires an SDCard. Prepare an MP3 file and store it on the SDCard
- Before compiling the program, modify the value of `ENCODER_ENABLE` in `main.c` to enable or disable the `AAC` encoder
- Before running the example, ensure the SDCard is correctly installed on the development board
- During the example's execution, you can speak to the development board to verify the AEC effect later
- After the example finishes running, you can export the files from the SDCard and use software to check the recording content:
- If encoding is disabled, the recording file will be named `aec_16k_16bit_1ch.pcm` with a format of 16K sampling rate, 16-bit width, and 1 channel
- If encoding is enabled, the recording file will be named `aec.aac`
### Example Functionality
- Once the example starts running, it will read MP3 audio from the SDCard and play it while recording and saving to the SDCard. After 20 seconds, it will stop and exit, printing the following:
```c
I (926) gpio: GPIO[15]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (935) gpio: GPIO[7]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (944) gpio: GPIO[4]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (995) gpio: GPIO[17]| InputEn: 1| OutputEn: 1| OpenDrain: 1| Pullup: 1| Pulldown: 0| Intr:0
I (996) gpio: GPIO[18]| InputEn: 1| OutputEn: 1| OpenDrain: 1| Pullup: 1| Pulldown: 0| Intr:0
I (1010) ES8311: Work in Slave mode
I (1013) gpio: GPIO[48]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0
E (1017) i2s_common: i2s_channel_disable(1116): the channel has not been enabled yet
I (1025) I2S_IF: channel mode 0 bits:32/32 channel:2 mask:3
I (1031) I2S_IF: STD Mode 1 bits:32/32 channel:2 sample_rate:48000 mask:3
I (1053) Adev_Codec: Open codec device OK
I (1057) ES7210: Work in Slave mode
I (1063) ES7210: Enable ES7210_INPUT_MIC1
I (1066) ES7210: Enable ES7210_INPUT_MIC2
I (1069) ES7210: Enable ES7210_INPUT_MIC3
I (1072) ES7210: Enable TDM mode
E (1077) i2s_common: i2s_channel_disable(1116): the channel has not been enabled yet
I (1078) I2S_IF: channel mode 0 bits:32/32 channel:2 mask:3
I (1085) I2S_IF: STD Mode 0 bits:32/32 channel:2 sample_rate:48000 mask:3
I (1093) ES7210: Bits 16
I (1102) ES7210: Enable ES7210_INPUT_MIC1
I (1105) ES7210: Enable ES7210_INPUT_MIC2
I (1108) ES7210: Enable ES7210_INPUT_MIC3
I (1112) ES7210: Enable TDM mode
I (1118) ES7210: Unmuted
I (1118) Adev_Codec: Open codec device OK
I (1123) ESP_GMF_POOL: Registered items on pool:0x3c23ddf8, app_main-142
I (1129) ESP_GMF_POOL: IO, Item:0x3c23decc, H:0x3c23de0c, TAG:io_http
I (1135) ESP_GMF_POOL: IO, Item:0x3c23df9c, H:0x3c23dedc, TAG:io_http
I (1142) ESP_GMF_POOL: IO, Item:0x3c23e038, H:0x3c23dfac, TAG:io_file
I (1149) ESP_GMF_POOL: IO, Item:0x3c23e0d4, H:0x3c23e048, TAG:io_file
I (1156) ESP_GMF_POOL: IO, Item:0x3c23e204, H:0x3c23e170, TAG:io_embed_flash
I (1163) ESP_GMF_POOL: IO, Item:0x3c23ede0, H:0x3c23ed50, TAG:io_codec_dev
I (1171) ESP_GMF_POOL: IO, Item:0x3c23ee80, H:0x3c23edf0, TAG:io_codec_dev
I (1178) ESP_GMF_POOL: EL, Item:0x3c23e160, H:0x3c23e0e4, TAG:copier
I (1185) ESP_GMF_POOL: EL, Item:0x3c23e2e8, H:0x3c23e214, TAG:aud_enc
I (1193) ESP_GMF_POOL: EL, Item:0x3c23e3e8, H:0x3c23e2f8, TAG:aud_dec
I (1200) ESP_GMF_POOL: EL, Item:0x3c23e4bc, H:0x3c23e3f8, TAG:aud_alc
I (1207) ESP_GMF_POOL: EL, Item:0x3c23e570, H:0x3c23e4cc, TAG:aud_eq
I (1214) ESP_GMF_POOL: EL, Item:0x3c23e5a4, H:0x3c23e6d8, TAG:aud_ch_cvt
I (1221) ESP_GMF_POOL: EL, Item:0x3c23e5d4, H:0x3c23e774, TAG:aud_bit_cvt
I (1228) ESP_GMF_POOL: EL, Item:0x3c23e8ac, H:0x3c23e810, TAG:aud_rate_cvt
I (1235) ESP_GMF_POOL: EL, Item:0x3c23e988, H:0x3c23e8bc, TAG:aud_fade
I (1242) ESP_GMF_POOL: EL, Item:0x3c23ea84, H:0x3c23e998, TAG:aud_sonic
I (1249) ESP_GMF_POOL: EL, Item:0x3c23eb64, H:0x3c23ea94, TAG:aud_deintlv
I (1256) ESP_GMF_POOL: EL, Item:0x3c23ec40, H:0x3c23eb74, TAG:aud_intlv
I (1264) ESP_GMF_POOL: EL, Item:0x3c23ed40, H:0x3c23ec50, TAG:aud_mixer
I (1271) ESP_GMF_POOL: EL, Item:0x3c23ef60, H:0x3c23ee90, TAG:ai_aec
W (1278) ESP_GMF_PIPELINE: There is no thread for add jobs, pipe:0x3c23ef70, tsk:0x0, [el:aud_rate_cvt-0x3c23efa8]
I (1288) ESP_GMF_THREAD: The TSK_0x3fcb5404 created on internal memory
I (1288) ESP_GMF_TASK: Waiting to run... [tsk:TSK_0x3fcb5404-0x3fcb5404, wk:0x0, run:0]
I (1304) AEC_EL_2_FILE: CB: RECV Pipeline EVT: el:NULL-0x3c23ef70, type:8192, sub:ESP_GMF_EVENT_STATE_OPENING, payload:0x0, size:0,0x0
I (1328) ESP_GMF_PORT: ACQ IN, new self payload:0x3c23f35c, port:0x3c23f2a8, el:0x3c23efa8-aud_rate_cvt
I (1329) ESP_GMF_PORT: ACQ OUT SET, new self payload:0x3c23fe68, p:0x3c23f1a8, el:0x3c23efa8-aud_rate_cvt
I (1358) GMF_AEC: GMF AEC open, frame_len: 2048, nch 4, chunksize 256
I (1359) AEC_EL_2_FILE: CB: RECV Pipeline EVT: el:ai_aec-0x3c23f0c4, type:12288, sub:ESP_GMF_EVENT_STATE_INITIALIZED, payload:0x3fcb6790, size:12,0x0
I (1371) AEC_EL_2_FILE: CB: RECV Pipeline EVT: el:ai_aec-0x3c23f0c4, type:8192, sub:ESP_GMF_EVENT_STATE_RUNNING, payload:0x0, size:0,0x0
I (1383) ESP_GMF_TASK: One times job is complete, del[wk:0x3c23f3dc,ctx:0x3c23f0c4, label:aec_open]
I (1401) ESP_GMF_TASK: Waiting to run... [tsk:TSK_0x3fcca2f8-0x3fcca2f8, wk:0x0, run:0]
I (1401) ESP_GMF_THREAD: The TSK_0x3fcca2f8 created on internal memory
I (1432) ESP_GMF_TASK: Waiting to run... [tsk:TSK_0x3fcca2f8-0x3fcca2f8, wk:0x3c252ac0, run:0]
I (1433) ESP_GMF_FILE: Open, dir:1, uri:/sdcard/test.mp3
I (1446) ESP_GMF_FILE: File size: 2994349 byte, file position: 0
I (1447) AEC_EL_2_FILE: CB: RECV Pipeline EVT: el:NULL-0x3c25230c, type:8192
I (1460) ESP_GMF_TASK: One times job is complete, del[wk:0x3c252ac0,ctx:0x3c252344, label:aud_simp_dec_open]
I (1469) ESP_GMF_PORT: ACQ IN, new self payload:0x3c252ac0, port:0x3c252964, el:0x3c252344-aud_dec
I (1482) ESP_GMF_PORT: ACQ OUT SET, new self payload:0x3c252d64, p:0x3c25252c, el:0x3c252344-aud_dec
W (1491) ESP_GMF_ASMP_DEC: Not enough memory for out, need:4608, old: 1024, new: 4608
I (1505) ESP_GMF_ASMP_DEC: NOTIFY Info, rate: 0, bits: 0, ch: 0 --> rate: 44100, bits: 16, ch: 2
Audio >
I (1589) ESP_GMF_TASK: One times job is complete, del[wk:0x3c253fa4,ctx:0x3c252420, label:rate_cvt_open]
I (1590) ESP_GMF_PORT: ACQ OUT SET, new self payload:0x3c253fa4, p:0x3c2526c4, el:0x3c252420-aud_rate_cvt
I (1603) ESP_GMF_TASK: One times job is complete, del[wk:0x3c253fec,ctx:0x3c2525ac, label:ch_cvt_open]
I (1615) AEC_EL_2_FILE: CB: RECV Pipeline EVT: el:aud_bit_cvt-0x3c252744, type:12288, sub:ESP_GMF_EVENT_STATE_INITIALIZED, payload:0x3fccb680, size:12,0x0
I (1627) AEC_EL_2_FILE: CB: RECV Pipeline EVT: el:aud_bit_cvt-0x3c252744, type:8192, sub:ESP_GMF_EVENT_STATE_RUNNING, payload:0x0, size:0,0x0
I (1639) ESP_GMF_TASK: One times job is complete, del[wk:0x3c255608,ctx:0x3c252744, label:bit_cvt_open]
I (1651) ESP_GMF_PORT: ACQ OUT, new self payload:0x3c255608, port:0x3c252a34, el:0x3c252744-aud_bit_cvt
I (21462) ESP_GMF_CODEC_DEV: CLose, 0x3c23f228, pos = 7716864/0
I (21464) ESP_GMF_TASK: One times job is complete, del[wk:0x3c23f410,ctx:0x3c23efa8, label:rate_cvt_close]
I (21478) ESP_GMF_TASK: One times job is complete, del[wk:0x3c253fec,ctx:0x3c23f0c4, label:aec_close]
I (21479) AEC_EL_2_FILE: CB: RECV Pipeline EVT: el:NULL-0x3c23ef70, type:8192, sub:ESP_GMF_EVENT_STATE_STOPPED, payload:0x0, size:0,0x0
I (21501) ESP_GMF_TASK: Waiting to run... [tsk:TSK_0x3fcb5404-0x3fcb5404, wk:0x0, run:0]
I (21502) ESP_GMF_TASK: Waiting to run... [tsk:TSK_0x3fcb5404-0x3fcb5404, wk:0x0, run:0]
I (21514) ESP_GMF_FILE: CLose, 0x3c2528d8, pos = 318464/2994349
I (21525) ESP_GMF_CODEC_DEV: CLose, 0x3c2529a4, pos = 7633608/0
I (21526) ESP_GMF_TASK: One times job is complete, del[wk:0x3c25562c,ctx:0x3c252344, label:aud_simp_dec_close]
I (21538) ESP_GMF_TASK: One times job is complete, del[wk:0x3c23f410,ctx:0x3c252420, label:rate_cvt_close]
I (21549) ESP_GMF_TASK: One times job is complete, del[wk:0x3c23f380,ctx:0x3c2525ac, label:ch_cvt_close]
I (21561) ESP_GMF_TASK: One times job is complete, del[wk:0x3c23f3b8,ctx:0x3c252744, label:bit_cvt_close]
I (21572) AEC_EL_2_FILE: CB: RECV Pipeline EVT: el:NULL-0x3c25230c, type:8192, sub:ESP_GMF_EVENT_STATE_STOPPED, payload:0x0, size:0,0x0
I (21584) ESP_GMF_TASK: Waiting to run... [tsk:TSK_0x3fcca2f8-0x3fcca2f8, wk:0x0, run:0]
I (21595) ESP_GMF_TASK: Waiting to run... [tsk:TSK_0x3fcca2f8-0x3fcca2f8, wk:0x0, run:0]
E (25298) i2s_common: i2s_channel_disable(1116): the channel has not been enabled yet
E (25303) i2s_common: i2s_channel_disable(1116): the channel has not been enabled yet
I (25314) main_task: Returned from app_main()
```
## Troubleshooting
### 1. SDCard Not Recognized
- Ensure the SDCard is formatted as FAT32
- Check if the SDCard is properly inserted into the development board
### 2. MP3 File Not Playing
- Ensure the MP3 file is stored in the root directory of the SDCard
- Verify that the MP3 file is not corrupted
- Ensure the MP3 file is named `test.mp3`
### 3. No Audio Recorded
- Verify that the microphone hardware on the development board is functioning correctly
- Ensure the audio channel configuration matches the hardware design
### 4. High CPU Usage with AEC in the Pipeline
Since the AEC algorithm requires significant CPU resources, consider the following steps if issues arise:
- Identify CPU-intensive elements in the pipeline, such as `ai_aec`, `aud_enc`, or `io_file`
- Split the problematic pipeline into two separate pipelines, run them on different CPU cores, and connect them using `gmf port`
@@ -0,0 +1,244 @@
# 播放 SDCard 音乐同时 AEC 录制音频
- [English](./README.md)
## 例程简介
本例程使用两路 pipeline 演示 AEC 录制音频的效果。一路 pipeline 使用 File IO 读取存储在 SDCard 中的 MP3 文件,然后经过 decoder 元素解码,解码后数据进行音频效果处理后用 CODEC_DEV_TX IO 输出音乐。同时,另外一路 pipeline 使用 CODEC_DEV_RX IO 从 I2S 设备读取 PCM 数据,经过 AEC 处理后进行音频编码,最终通过 File IO 保存为文件
本例程支持将 AEC 处理后的音频压缩为 `AAC` 格式,通过 [main.c](./main/main.c) 中的宏 `ENCODER_ENABLE` 使能
```c
#define ENCODER_ENABLE (false)
```
- `ENCODER_ENABLE`: 使能该选项将会在录音的 pipeline 中使能 `AAC` 编码器,并保存编码后的数据为 `aec.aac`
## GMF Pipeline
以下是本例程中使用的 pipeline 的示意图:
```mermaid
graph TD
subgraph Path1 [MP3 Player]
A[File IO] --> B[Decoder: MP3] --> C[Audio Effects] --> D[CODEC_DEV_TX]
end
subgraph Path2 [AEC Recorder]
E[CODEC_DEV_RX] --> F[Rate_cvt] --> G[AEC] --> H[Encoder: AAC] --> I[File IO]
end
```
## AEC 元素初始化示例
```c
esp_gmf_element_handle_t gmf_aec_handle = NULL;
esp_gmf_aec_cfg_t gmf_aec_cfg = {
.filter_len = 4,
.type = AFE_TYPE_VC,
.mode = AFE_MODE_HIGH_PERF,
.input_format = "RMNM",
};
esp_gmf_aec_init(&gmf_aec_cfg, &gmf_aec_handle);
```
#### 参数说明
- `filter_len`: 滤波器长度。数值越大,CPU 负载越高。对于 esp32s3 和 esp32p4,建议 filter_length = 4。对于 esp32c5,建议 filter_length = 2
- `type`: AEC 类型,支持以下选项:
- `AFE_TYPE_VC`: 适用于语音通信的回声消除
- `AFE_TYPE_SR`: 适用于语音识别的回声消除
- `mode`: AEC 模式,支持以下选项:
- `AFE_MODE_LOW_POWER`: 低功耗模式,适用于功耗敏感的场景
- `AFE_MODE_HIGH_PERF`: 高性能模式,适用于需要高质量回声消除的场景
- `input_format`: 输入数据的格式, 如 `RMNM`:
- `M`:麦克风通道
- `R`:回采信号通道
- `N`:无效信号
通过配置这些参数,可以根据具体应用场景调整 AEC 的性能和资源使用。添加到 `GMF Pool` 中后,可在 `GMF Pipeline` 中使用 AEC 处理音频流
## 示例创建
### IDF 默认分支
本例程支持 IDF release/v5.3 及以后的分支
### 编译和下载
编译本例程前需要先确保已配置 ESP-IDF 的环境,如果已配置可跳到下一项配置,如果未配置需要先在 ESP-IDF 根目录运行下面脚本设置编译环境,有关配置和使用 ESP-IDF 完整步骤,请参阅 [《ESP-IDF 编程指南》](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32s3/index.html)
```
./install.sh
. ./export.sh
```
下面是简略编译步骤:
- 进入示例工程的位置
```
cd gmf_ai_audio/examples/aec_rec
```
- 选择编译芯片,以 esp32s3 为例:
```
idf.py set-target esp32s3
```
- 选择编译目标板,以 ESP32-S3-Korvo V2 为例:
```
idf.py menuconfig
在 `menuconfig` 中选择 `GMF APP Configuration` -> `Audio Board` -> `ESP32-S3-Korvo V2`,然后保存退出
```
- 编译例子程序
```
idf.py build
```
- 烧录程序并运行 monitor 工具来查看串口输出 (替换 PORT 为端口名称):
```
idf.py -p PORT flash monitor
```
- 退出调试界面使用 ``Ctrl-]``
## 如何使用例程
- 本例程需要 SDCard 和 MP3 文件,用户需准备一份 MP3 文件并存入 SDCard 中
- 编译程序前,可以通过 `main.c` 中的 `ENCODER_ENABLE` 的值来决定是否启用 `AAC` 编码
- 运行例程前,请确保 SDCard 已正确安装到开发板上
- 运行例程时,用户可以对开发板说话,以便后续验证 AEC 效果
- 运行结束后,用户可以将 SDCard 中的文件导出,用软件查看录音内容:
- 如果未启用编码,录音文件名为 `aec_16k_16bit_1ch.pcm`,格式为 16K 采样率,16 bit 位宽,单声道
- 如果启用编码,录音文件名为 `aec.aac`
### 功能和用法
- 例程运行后,将从 SDCard 中读取 MP3 音频进行播放,同时录音并保存到 SDCard。运行 20 秒后停止并退出,串口打印如下日志:
```c
I (926) gpio: GPIO[15]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (935) gpio: GPIO[7]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (944) gpio: GPIO[4]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (995) gpio: GPIO[17]| InputEn: 1| OutputEn: 1| OpenDrain: 1| Pullup: 1| Pulldown: 0| Intr:0
I (996) gpio: GPIO[18]| InputEn: 1| OutputEn: 1| OpenDrain: 1| Pullup: 1| Pulldown: 0| Intr:0
I (1010) ES8311: Work in Slave mode
I (1013) gpio: GPIO[48]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0
E (1017) i2s_common: i2s_channel_disable(1116): the channel has not been enabled yet
I (1025) I2S_IF: channel mode 0 bits:32/32 channel:2 mask:3
I (1031) I2S_IF: STD Mode 1 bits:32/32 channel:2 sample_rate:48000 mask:3
I (1053) Adev_Codec: Open codec device OK
I (1057) ES7210: Work in Slave mode
I (1063) ES7210: Enable ES7210_INPUT_MIC1
I (1066) ES7210: Enable ES7210_INPUT_MIC2
I (1069) ES7210: Enable ES7210_INPUT_MIC3
I (1072) ES7210: Enable TDM mode
E (1077) i2s_common: i2s_channel_disable(1116): the channel has not been enabled yet
I (1078) I2S_IF: channel mode 0 bits:32/32 channel:2 mask:3
I (1085) I2S_IF: STD Mode 0 bits:32/32 channel:2 sample_rate:48000 mask:3
I (1093) ES7210: Bits 16
I (1102) ES7210: Enable ES7210_INPUT_MIC1
I (1105) ES7210: Enable ES7210_INPUT_MIC2
I (1108) ES7210: Enable ES7210_INPUT_MIC3
I (1112) ES7210: Enable TDM mode
I (1118) ES7210: Unmuted
I (1118) Adev_Codec: Open codec device OK
I (1123) ESP_GMF_POOL: Registered items on pool:0x3c23ddf8, app_main-142
I (1129) ESP_GMF_POOL: IO, Item:0x3c23decc, H:0x3c23de0c, TAG:io_http
I (1135) ESP_GMF_POOL: IO, Item:0x3c23df9c, H:0x3c23dedc, TAG:io_http
I (1142) ESP_GMF_POOL: IO, Item:0x3c23e038, H:0x3c23dfac, TAG:io_file
I (1149) ESP_GMF_POOL: IO, Item:0x3c23e0d4, H:0x3c23e048, TAG:io_file
I (1156) ESP_GMF_POOL: IO, Item:0x3c23e204, H:0x3c23e170, TAG:io_embed_flash
I (1163) ESP_GMF_POOL: IO, Item:0x3c23ede0, H:0x3c23ed50, TAG:io_codec_dev
I (1171) ESP_GMF_POOL: IO, Item:0x3c23ee80, H:0x3c23edf0, TAG:io_codec_dev
I (1178) ESP_GMF_POOL: EL, Item:0x3c23e160, H:0x3c23e0e4, TAG:copier
I (1185) ESP_GMF_POOL: EL, Item:0x3c23e2e8, H:0x3c23e214, TAG:aud_enc
I (1193) ESP_GMF_POOL: EL, Item:0x3c23e3e8, H:0x3c23e2f8, TAG:aud_dec
I (1200) ESP_GMF_POOL: EL, Item:0x3c23e4bc, H:0x3c23e3f8, TAG:aud_alc
I (1207) ESP_GMF_POOL: EL, Item:0x3c23e570, H:0x3c23e4cc, TAG:aud_eq
I (1214) ESP_GMF_POOL: EL, Item:0x3c23e5a4, H:0x3c23e6d8, TAG:aud_ch_cvt
I (1221) ESP_GMF_POOL: EL, Item:0x3c23e5d4, H:0x3c23e774, TAG:aud_bit_cvt
I (1228) ESP_GMF_POOL: EL, Item:0x3c23e8ac, H:0x3c23e810, TAG:aud_rate_cvt
I (1235) ESP_GMF_POOL: EL, Item:0x3c23e988, H:0x3c23e8bc, TAG:aud_fade
I (1242) ESP_GMF_POOL: EL, Item:0x3c23ea84, H:0x3c23e998, TAG:aud_sonic
I (1249) ESP_GMF_POOL: EL, Item:0x3c23eb64, H:0x3c23ea94, TAG:aud_deintlv
I (1256) ESP_GMF_POOL: EL, Item:0x3c23ec40, H:0x3c23eb74, TAG:aud_intlv
I (1264) ESP_GMF_POOL: EL, Item:0x3c23ed40, H:0x3c23ec50, TAG:aud_mixer
I (1271) ESP_GMF_POOL: EL, Item:0x3c23ef60, H:0x3c23ee90, TAG:ai_aec
W (1278) ESP_GMF_PIPELINE: There is no thread for add jobs, pipe:0x3c23ef70, tsk:0x0, [el:aud_rate_cvt-0x3c23efa8]
I (1288) ESP_GMF_THREAD: The TSK_0x3fcb5404 created on internal memory
I (1288) ESP_GMF_TASK: Waiting to run... [tsk:TSK_0x3fcb5404-0x3fcb5404, wk:0x0, run:0]
I (1304) AEC_EL_2_FILE: CB: RECV Pipeline EVT: el:NULL-0x3c23ef70, type:8192, sub:ESP_GMF_EVENT_STATE_OPENING, payload:0x0, size:0,0x0
I (1328) ESP_GMF_PORT: ACQ IN, new self payload:0x3c23f35c, port:0x3c23f2a8, el:0x3c23efa8-aud_rate_cvt
I (1329) ESP_GMF_PORT: ACQ OUT SET, new self payload:0x3c23fe68, p:0x3c23f1a8, el:0x3c23efa8-aud_rate_cvt
I (1358) GMF_AEC: GMF AEC open, frame_len: 2048, nch 4, chunksize 256
I (1359) AEC_EL_2_FILE: CB: RECV Pipeline EVT: el:ai_aec-0x3c23f0c4, type:12288, sub:ESP_GMF_EVENT_STATE_INITIALIZED, payload:0x3fcb6790, size:12,0x0
I (1371) AEC_EL_2_FILE: CB: RECV Pipeline EVT: el:ai_aec-0x3c23f0c4, type:8192, sub:ESP_GMF_EVENT_STATE_RUNNING, payload:0x0, size:0,0x0
I (1383) ESP_GMF_TASK: One times job is complete, del[wk:0x3c23f3dc,ctx:0x3c23f0c4, label:aec_open]
I (1401) ESP_GMF_TASK: Waiting to run... [tsk:TSK_0x3fcca2f8-0x3fcca2f8, wk:0x0, run:0]
I (1401) ESP_GMF_THREAD: The TSK_0x3fcca2f8 created on internal memory
I (1432) ESP_GMF_TASK: Waiting to run... [tsk:TSK_0x3fcca2f8-0x3fcca2f8, wk:0x3c252ac0, run:0]
I (1433) ESP_GMF_FILE: Open, dir:1, uri:/sdcard/test.mp3
I (1446) ESP_GMF_FILE: File size: 2994349 byte, io_file position: 0
I (1447) AEC_EL_2_FILE: CB: RECV Pipeline EVT: el:NULL-0x3c25230c, type:8192
I (1460) ESP_GMF_TASK: One times job is complete, del[wk:0x3c252ac0,ctx:0x3c252344, label:aud_simp_dec_open]
I (1469) ESP_GMF_PORT: ACQ IN, new self payload:0x3c252ac0, port:0x3c252964, el:0x3c252344-aud_dec
I (1482) ESP_GMF_PORT: ACQ OUT SET, new self payload:0x3c252d64, p:0x3c25252c, el:0x3c252344-aud_dec
W (1491) ESP_GMF_ASMP_DEC: Not enough memory for out, need:4608, old: 1024, new: 4608
I (1505) ESP_GMF_ASMP_DEC: NOTIFY Info, rate: 0, bits: 0, ch: 0 --> rate: 44100, bits: 16, ch: 2
Audio >
I (1589) ESP_GMF_TASK: One times job is complete, del[wk:0x3c253fa4,ctx:0x3c252420, label:rate_cvt_open]
I (1590) ESP_GMF_PORT: ACQ OUT SET, new self payload:0x3c253fa4, p:0x3c2526c4, el:0x3c252420-aud_rate_cvt
I (1603) ESP_GMF_TASK: One times job is complete, del[wk:0x3c253fec,ctx:0x3c2525ac, label:ch_cvt_open]
I (1615) AEC_EL_2_FILE: CB: RECV Pipeline EVT: el:aud_bit_cvt-0x3c252744, type:12288, sub:ESP_GMF_EVENT_STATE_INITIALIZED, payload:0x3fccb680, size:12,0x0
I (1627) AEC_EL_2_FILE: CB: RECV Pipeline EVT: el:aud_bit_cvt-0x3c252744, type:8192, sub:ESP_GMF_EVENT_STATE_RUNNING, payload:0x0, size:0,0x0
I (1639) ESP_GMF_TASK: One times job is complete, del[wk:0x3c255608,ctx:0x3c252744, label:bit_cvt_open]
I (1651) ESP_GMF_PORT: ACQ OUT, new self payload:0x3c255608, port:0x3c252a34, el:0x3c252744-aud_bit_cvt
I (21462) ESP_GMF_CODEC_DEV: CLose, 0x3c23f228, pos = 7716864/0
I (21464) ESP_GMF_TASK: One times job is complete, del[wk:0x3c23f410,ctx:0x3c23efa8, label:rate_cvt_close]
I (21478) ESP_GMF_TASK: One times job is complete, del[wk:0x3c253fec,ctx:0x3c23f0c4, label:aec_close]
I (21479) AEC_EL_2_FILE: CB: RECV Pipeline EVT: el:NULL-0x3c23ef70, type:8192, sub:ESP_GMF_EVENT_STATE_STOPPED, payload:0x0, size:0,0x0
I (21501) ESP_GMF_TASK: Waiting to run... [tsk:TSK_0x3fcb5404-0x3fcb5404, wk:0x0, run:0]
I (21502) ESP_GMF_TASK: Waiting to run... [tsk:TSK_0x3fcb5404-0x3fcb5404, wk:0x0, run:0]
I (21514) ESP_GMF_FILE: CLose, 0x3c2528d8, pos = 318464/2994349
I (21525) ESP_GMF_CODEC_DEV: CLose, 0x3c2529a4, pos = 7633608/0
I (21526) ESP_GMF_TASK: One times job is complete, del[wk:0x3c25562c,ctx:0x3c252344, label:aud_simp_dec_close]
I (21538) ESP_GMF_TASK: One times job is complete, del[wk:0x3c23f410,ctx:0x3c252420, label:rate_cvt_close]
I (21549) ESP_GMF_TASK: One times job is complete, del[wk:0x3c23f380,ctx:0x3c2525ac, label:ch_cvt_close]
I (21561) ESP_GMF_TASK: One times job is complete, del[wk:0x3c23f3b8,ctx:0x3c252744, label:bit_cvt_close]
I (21572) AEC_EL_2_FILE: CB: RECV Pipeline EVT: el:NULL-0x3c25230c, type:8192, sub:ESP_GMF_EVENT_STATE_STOPPED, payload:0x0, size:0,0x0
I (21584) ESP_GMF_TASK: Waiting to run... [tsk:TSK_0x3fcca2f8-0x3fcca2f8, wk:0x0, run:0]
I (21595) ESP_GMF_TASK: Waiting to run... [tsk:TSK_0x3fcca2f8-0x3fcca2f8, wk:0x0, run:0]
E (25298) i2s_common: i2s_channel_disable(1116): the channel has not been enabled yet
E (25303) i2s_common: i2s_channel_disable(1116): the channel has not been enabled yet
I (25314) main_task: Returned from app_main()
```
## 常见问题
### 1. 无法识别 SDCard
- 确保 SDCard 已正确格式化为 FAT32 文件系统
- 检查 SDCard 是否正确插入开发板
### 2. 无法播放 MP3 文件
- 确保 MP3 文件存储在 SDCard 的根目录中
- 确保 MP3 文件未损坏
- 确保 MP3 文件名为 `test.mp3`
### 3. 录制音频无声音
- 确保开发板的麦克风硬件正常工作
- 确保语音通道配置符合硬件设计
### 4. 集成 AEC 的 Pipeline CPU 占用过高
由于 AEC 算法需要的 cpu 算力较多,所以组建包含 AEC 元素的 Pipeline 时,需合理分配资源,如遇到问题,可按如下方式处理:
- 查看 Pipeline 有哪些需要 cpu 算力较多的元素,如 `ai_aec`, `aud_enc`, `io_file` 等
- 将出现问题的 Pipeline 拆分为两个,分别运行到不同的 cpu 核心上,使用 `gmf port` 连接
@@ -0,0 +1,2 @@
idf_component_register(SRCS "main.c"
INCLUDE_DIRS ".")
@@ -0,0 +1,19 @@
dependencies:
espressif/gmf_loader:
override_path: ../../../../../packages/gmf_loader
version: "^0.7"
espressif/gmf_app_utils:
override_path: ../../../../../packages/gmf_app_utils
version: "^0.7"
espressif/gmf_core:
override_path: ../../../../../gmf_core
espressif/gmf_io:
override_path: ../../../../gmf_io
espressif/gmf_audio:
override_path: ../../../../gmf_audio
espressif/gmf_ai_audio:
override_path: ../../../../gmf_ai_audio
espressif/gmf_video:
override_path: ../../../../gmf_video
espressif/gmf_misc:
override_path: ../../../../gmf_misc
@@ -0,0 +1,219 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO., LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <string.h>
#include "esp_err.h"
#include "esp_log.h"
#include "esp_gmf_err.h"
#include "esp_gmf_io_codec_dev.h"
#include "esp_gmf_obj.h"
#include "esp_gmf_payload.h"
#include "esp_gmf_port.h"
#include "esp_gmf_aec.h"
#include "esp_gmf_element.h"
#include "esp_gmf_pipeline.h"
#include "esp_gmf_pool.h"
#include "esp_gmf_rate_cvt.h"
#include "esp_gmf_bit_cvt.h"
#include "esp_gmf_ch_cvt.h"
#include "esp_gmf_audio_dec.h"
#include "esp_gmf_app_setup_peripheral.h"
#include "esp_gmf_app_cli.h"
#include "gmf_loader_setup_defaults.h"
#define BOARD_LYRAT_MINI (0)
#define BOARD_KORVO_2 (1)
#if defined CONFIG_IDF_TARGET_ESP32S3
#define AUDIO_BOARD (BOARD_KORVO_2)
#elif defined CONFIG_IDF_TARGET_ESP32
#define AUDIO_BOARD (BOARD_LYRAT_MINI)
#endif /* defined CONFIG_IDF_TARGET_ESP32S3 */
#if AUDIO_BOARD == BOARD_KORVO_2
#define ADC_I2S_PORT (0)
#define ADC_I2S_CH (2)
#define ADC_I2S_BITS (32)
#define DAC_I2S_PORT (0)
#define DAC_I2S_CH (2)
#define DAC_I2S_BITS (32)
#define INPUT_CH_NUM (4)
#define INPUT_CH_BITS (16) /* For board `ESP32-S3-Korvo-2`, the es7210 is configured as 32-bit,
2-channel mode to accommodate 16-bit, 4-channel data */
#define INPUT_CH_ALLOCATION ("RMNM")
#elif AUDIO_BOARD == BOARD_LYRAT_MINI
#define ADC_I2S_PORT (1)
#define ADC_I2S_CH (2)
#define ADC_I2S_BITS (16)
#define DAC_I2S_PORT (0)
#define DAC_I2S_CH (1)
#define DAC_I2S_BITS (16)
#define INPUT_CH_NUM (ADC_I2S_CH)
#define INPUT_CH_BITS (ADC_I2S_BITS)
#define INPUT_CH_ALLOCATION ("RM")
#endif /* AUDIO_BOARD == BOARD_KORVO_2 */
#define ENCODER_ENABLE (false)
static const char *TAG = "AEC_EL_2_FILE";
static uint8_t *pcm_buffer = NULL;
const uint32_t buf_size = 600 * 1024;
static size_t pcm_received = 0;
static esp_err_t _pipeline_event(esp_gmf_event_pkt_t *event, void *ctx)
{
ESP_LOGI(TAG, "CB: RECV Pipeline EVT: el:%s-%p, type:%d, sub:%s, payload:%p, size:%d,%p",
OBJ_GET_TAG(event->from), event->from, event->type, esp_gmf_event_get_state_str(event->sub),
event->payload, event->payload_size, ctx);
return ESP_OK;
}
static esp_gmf_err_io_t pcm_buf_acq_write(void *handle, esp_gmf_payload_t *load, int wanted_size, int block_ticks)
{
return ESP_GMF_IO_OK;
}
static esp_gmf_err_io_t pcm_buf_release_write(void *handle, esp_gmf_payload_t *load, int block_ticks)
{
if (load == NULL) {
return ESP_GMF_IO_FAIL;
}
if (load->valid_size && (pcm_received + load->valid_size <= buf_size)) {
memcpy(pcm_buffer + pcm_received, load->buf, load->valid_size);
pcm_received += load->valid_size;
}
return ESP_GMF_IO_OK;
}
void app_main(void)
{
esp_log_level_set("*", ESP_LOG_INFO);
pcm_buffer = heap_caps_malloc(buf_size, MALLOC_CAP_SPIRAM);
if (!pcm_buffer) {
ESP_LOGE(TAG, "Failed to allocate SPIRAM buffer");
return;
}
pcm_received = 0;
esp_gmf_app_codec_info_t codec_info = ESP_GMF_APP_CODEC_INFO_DEFAULT();
codec_info.play_info.sample_rate = 48000;
codec_info.play_info.channel = DAC_I2S_CH;
codec_info.play_info.bits_per_sample = DAC_I2S_BITS;
codec_info.record_info.sample_rate = codec_info.play_info.sample_rate;
codec_info.record_info.channel = ADC_I2S_CH;
codec_info.record_info.bits_per_sample = ADC_I2S_BITS;
esp_gmf_app_setup_codec_dev(&codec_info);
void *sdcard_handle = NULL;
esp_gmf_app_setup_sdcard(&sdcard_handle);
esp_gmf_pool_handle_t pool = NULL;
esp_gmf_pool_init(&pool);
gmf_loader_setup_all_defaults(pool);
ESP_GMF_POOL_SHOW_ITEMS(pool);
esp_gmf_pipeline_handle_t read_pipe = NULL;
#if ENCODER_ENABLE
const char *name[] = {"aud_rate_cvt", "ai_aec", "aud_enc"};
#else
const char *name[] = {"aud_rate_cvt", "ai_aec"};
#endif /* ENCODER_ENABLE */
esp_gmf_pool_new_pipeline(pool, "io_codec_dev", name, sizeof(name) / sizeof(char *), NULL, &read_pipe);
if (read_pipe == NULL) {
ESP_LOGE(TAG, "There is no pipeline");
return;
}
esp_gmf_io_codec_dev_set_dev(ESP_GMF_PIPELINE_GET_IN_INSTANCE(read_pipe), esp_gmf_app_get_record_handle());
esp_gmf_port_handle_t out_port = NEW_ESP_GMF_PORT_OUT_BYTE(pcm_buf_acq_write, pcm_buf_release_write, NULL, NULL, 1024, portMAX_DELAY);
esp_gmf_element_register_out_port(read_pipe->last_el, out_port);
esp_gmf_obj_handle_t rate_cvt = NULL;
esp_gmf_pipeline_get_el_by_name(read_pipe, "aud_rate_cvt", &rate_cvt);
esp_gmf_rate_cvt_set_dest_rate(rate_cvt, 16000);
esp_gmf_info_sound_t info = {
.format_id = ESP_AUDIO_SIMPLE_DEC_TYPE_MP3,
.sample_rates = 48000,
.channels = INPUT_CH_NUM,
.bits = INPUT_CH_BITS,
};
esp_gmf_pipeline_report_info(read_pipe, ESP_GMF_INFO_SOUND, &info, sizeof(info));
esp_gmf_task_cfg_t cfg = DEFAULT_ESP_GMF_TASK_CONFIG();
cfg.thread.core = 1;
cfg.thread.stack = 5120;
esp_gmf_task_handle_t read_task = NULL;
esp_gmf_task_init(&cfg, &read_task);
esp_gmf_pipeline_bind_task(read_pipe, read_task);
esp_gmf_pipeline_loading_jobs(read_pipe);
esp_gmf_pipeline_set_event(read_pipe, _pipeline_event, NULL);
esp_gmf_pipeline_run(read_pipe);
esp_gmf_app_cli_init("Audio >", NULL);
// New pipeline to play 'test.mp3'
esp_gmf_pipeline_handle_t play_pipe = NULL;
const char *play_name[] = {"aud_dec", "aud_rate_cvt", "aud_ch_cvt", "aud_bit_cvt"};
esp_gmf_pool_new_pipeline(pool, "io_file", play_name, sizeof(play_name) / sizeof(char *), "io_codec_dev", &play_pipe);
if (play_pipe == NULL) {
ESP_LOGE(TAG, "There is no play pipeline");
return;
}
esp_gmf_io_codec_dev_set_dev(ESP_GMF_PIPELINE_GET_OUT_INSTANCE(play_pipe), esp_gmf_app_get_playback_handle());
esp_gmf_obj_handle_t bit_cvt = NULL;
esp_gmf_pipeline_get_el_by_name(play_pipe, "aud_bit_cvt", &bit_cvt);
esp_gmf_bit_cvt_set_dest_bits(bit_cvt, DAC_I2S_BITS);
esp_gmf_obj_handle_t ch_cvt = NULL;
esp_gmf_pipeline_get_el_by_name(play_pipe, "aud_ch_cvt", &ch_cvt);
esp_gmf_ch_cvt_set_dest_channel(ch_cvt, DAC_I2S_CH);
esp_gmf_obj_handle_t dec_el = NULL;
esp_gmf_pipeline_get_el_by_name(play_pipe, "aud_dec", &dec_el);
esp_gmf_audio_dec_reconfig_by_sound_info(dec_el, &info);
esp_gmf_pipeline_set_in_uri(play_pipe, "/sdcard/test.mp3");
esp_gmf_task_handle_t play_task = NULL;
cfg.thread.core = 0;
esp_gmf_task_init(&cfg, &play_task);
esp_gmf_pipeline_bind_task(play_pipe, play_task);
esp_gmf_pipeline_loading_jobs(play_pipe);
esp_gmf_pipeline_set_event(play_pipe, _pipeline_event, NULL);
esp_gmf_pipeline_run(play_pipe);
vTaskDelay(20000 / portTICK_PERIOD_MS);
esp_gmf_pipeline_stop(read_pipe);
esp_gmf_pipeline_stop(play_pipe);
#if ENCODER_ENABLE
FILE *file = fopen("/sdcard/aec.aac", "wb");
#else
FILE *file = fopen("/sdcard/aec_16k_16bit_1ch.pcm", "wb");
#endif /* ENCODER_ENABLE */
if (file) {
fwrite(pcm_buffer, 1, pcm_received, file);
fclose(file);
} else {
ESP_LOGE(TAG, "Failed to open file for writing");
}
heap_caps_free(pcm_buffer);
esp_gmf_task_deinit(read_task);
esp_gmf_task_deinit(play_task);
esp_gmf_pipeline_destroy(read_pipe);
esp_gmf_pipeline_destroy(play_pipe);
gmf_loader_teardown_all_defaults(pool);
esp_gmf_pool_deinit(pool);
esp_gmf_app_teardown_codec_dev();
esp_gmf_app_teardown_sdcard(sdcard_handle);
}
@@ -0,0 +1,4 @@
# Espressif ESP32 Partition Table
# Name, Type, SubType, Offset, Size
nvs, data, nvs, 0x9000, 0x6000,
factory, app, factory, 0x010000, 2500k
1 # Espressif ESP32 Partition Table
2 # Name, Type, SubType, Offset, Size
3 nvs, data, nvs, 0x9000, 0x6000,
4 factory, app, factory, 0x010000, 2500k
@@ -0,0 +1,14 @@
# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO., LTD
#
# SPDX-License-Identifier: Apache-2.0
import pytest
import os
from pytest_embedded import Dut
@pytest.mark.esp32
@pytest.mark.esp32s3
@pytest.mark.temp_skip_ci(targets=['esp32', 'esp32s3', 'esp32p4'], reason='No running in CI')
def test_aec_rec(dut: Dut)-> None:
dut.expect(r'Audio >', timeout=20)
@@ -0,0 +1,52 @@
#
# Serial flasher config
#
CONFIG_ESPTOOLPY_FLASH_MODE_AUTO_DETECT=y
CONFIG_ESPTOOLPY_FLASHMODE_DIO=y
CONFIG_ESPTOOLPY_FLASH_SAMPLE_MODE_STR=y
CONFIG_ESPTOOLPY_FLASHMODE="dio"
CONFIG_ESPTOOLPY_FLASHFREQ_80M=y
CONFIG_ESPTOOLPY_FLASHFREQ="80m"
CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y
CONFIG_ESPTOOLPY_FLASHSIZE="8MB"
#
# Partition Table
#
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
CONFIG_PARTITION_TABLE_FILENAME="partitions.csv"
CONFIG_PARTITION_TABLE_OFFSET=0x8000
CONFIG_PARTITION_TABLE_MD5=y
# end of Partition Table
#
# Compiler options
#
CONFIG_COMPILER_OPTIMIZATION_PERF=y
# end of Compiler options
#
# ESP PSRAM
#
CONFIG_SPIRAM=y
CONFIG_SPIRAM_TYPE_AUTO=y
CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY=y
CONFIG_SPIRAM_SPEED_80M=y
CONFIG_SPIRAM_SPEED=80
#
# Audio Codec Device Configuration
#
# CONFIG_CODEC_I2C_BACKWARD_COMPATIBLE is not set
# end of Audio Codec Device Configuration
CONFIG_FATFS_LFN_HEAP=y
CONFIG_FREERTOS_HZ=1000
CONFIG_FREERTOS_USE_TRACE_FACILITY=y
CONFIG_FREERTOS_USE_STATS_FORMATTING_FUNCTIONS=y
# CONFIG_FREERTOS_USE_LIST_DATA_INTEGRITY_CHECK_BYTES is not set
CONFIG_FREERTOS_VTASKLIST_INCLUDE_COREID=y
CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS=y
@@ -0,0 +1,15 @@
CONFIG_IDF_CMAKE=y
CONFIG_IDF_TARGET_ARCH_XTENSA=y
CONFIG_IDF_TARGET="esp32"
CONFIG_IDF_TARGET_ESP32=y
CONFIG_IDF_FIRMWARE_CHIP_ID=0x0000
#
# ESP System Settings
#
# CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_80 is not set
# CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_160 is not set
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ=240
CONFIG_AUDIO_BOARD="LYRAT_MINI_V1"
@@ -0,0 +1,15 @@
CONFIG_IDF_CMAKE=y
CONFIG_IDF_TARGET_ARCH_XTENSA=y
CONFIG_IDF_TARGET="esp32s3"
CONFIG_IDF_TARGET_ESP32S3=y
CONFIG_IDF_FIRMWARE_CHIP_ID=0x0009
CONFIG_COMPILER_OPTIMIZATION_PERF=y
CONFIG_SPIRAM_MODE_OCT=y
CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240=y
CONFIG_ESP32S3_INSTRUCTION_CACHE_32KB=y
CONFIG_ESP32S3_DATA_CACHE_LINE_64B=y
CONFIG_AUDIO_BOARD="S3_Korvo_V2"
@@ -0,0 +1,7 @@
# The following lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(gmf_wwe_demo)
@@ -0,0 +1,150 @@
# Speech Recognition
- [中文版](./README_CN.md)
## Example Brief
This example demonstrates how to use the `AFE element` for wake word detection, voice activity detection (VAD), and command word recognition. It allows configuring features through the following macro definitions in [main.c](./main/main.c) to achieve different application combinations:
```c
#define VOICE2FILE (true)
#define WAKENET_ENABLE (true)
#define VAD_ENABLE (true)
#define VCMD_ENABLE (true)
```
- `VOICE2FILE`: Save the audio between VAD start and VAD end as a file
- `WAKENET_ENABLE`: Enable wake word recognition
- `VAD_ENABLE`: Enable voice activity detection
- `VCMD_ENABLE`: Enable command word recognition
## GMF Pipeline
```mermaid
graph
subgraph WWE
E[CODEC_DEV_RX] --> F[GMF_AFE] --> G[GMF_PORT]
end
```
## Example Set Up
### Default IDF Branch
This example supports IDF release/v5.3 and later branches
### Prerequisites
The voice wake word and command word detection used in this example are derived from `esp-sr`. Please familiarize yourself with its configuration and usage: [README](https://github.com/espressif/esp-sr/blob/master/README.md)
### Build and Flash
Before compiling this example, ensure that the ESP-IDF environment is properly configured. If it is already set up, you can proceed to the next configuration step. If not, run the following script in the root directory of ESP-IDF to set up the build environment. For detailed steps on configuring and using ESP-IDF, please refer to the [ESP-IDF Programming Guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/index.html)
```
./install.sh
. ./export.sh
```
Here are the summarized steps for compilation:
- Enter the location where the example project is stored:
```
cd gmf_ai_audio/examples/wwe
```
- Select the target chip for compilation. For example, to use the ESP32S3:
```
idf.py set-target esp32s3
```
- Select the compilation board, taking ESP32-S3-Korvo V2 as an example:
```
idf.py menuconfig
In 'menuconfig', select 'GMF APP Configuration' -> 'Audio Board' -> 'ESP32-S3-Korvo V2', and then save and exit
```
- Build the example:
```
idf.py build
```
- Flash the program and run the monitor tool to view serial output (replace PORT with the port name):
```
idf.py -p PORT flash monitor
```
- Exit the debugging interface using `Ctrl-]`
## How to use the Example
- Before compiling the program, confirm whether wake word detection is enabled by checking the value of `WAKENET_ENABLE` in `main.c`. Also, confirm whether the `afe` output needs to be saved as a file by checking the value of `VOICE2FILE`
- If `VOICE2FILE` is set to `true`, ensure that the SDCard is correctly installed on the development board before running the example
- During the example's execution, if `WAKENET_ENABLE` is set to `true`, first say the wake word, then say the command word to verify the effects of wake word detection, voice activity detection, and command word detection. If `WAKENET_ENABLE` is set to `false`, you can directly use command words. Pay attention to the corresponding event prompts in the `log`
- If `VOICE2FILE` is set to `true`, after the example finishes running, you can export the files from the SDCard and use software to check the recording content. The recording file names are `16k_16bit_1ch_{idx}.pcm`, where `{idx}` increments. The PCM format is 16K sampling rate, 16-bit width, and 1 channel
### Example Functionality
- After the example starts running, you can say wake words and command words to the development board. The following will be printed:
```c
I (1547) AFE: AFE Version: (2MIC_V250113)
I (1550) AFE: Input PCM Config: total 4 channels(2 microphone, 1 playback), sample rate:16000
I (1560) AFE: AFE Pipeline: [input] -> |AEC(SR_HIGH_PERF)| -> |SE(BSS)| -> |VAD(WebRTC)| -> |WakeNet(wn9_hilexin,)| -> [output]
I (1572) AFE_manager: Feed task, ch 4, chunk 1024, buf size 8192
I (1579) GMF_AFE: Create AFE, ai_afe-0x3c2dcf90
I (1584) GMF_AFE: Create AFE, ai_afe-0x3c2dd0b8
I (1589) GMF_AFE: New an object,ai_afe-0x3c2dd0b8
I (1595) ESP_GMF_TASK: Waiting to run... [tsk:TSK_0x3fcc500c-0x3fcc500c, wk:0x0, run:0]
I (1603) ESP_GMF_THREAD: The TSK_0x3fcc500c created on internal memory
I (1610) ESP_GMF_TASK: Waiting to run... [tsk:TSK_0x3fcc500c-0x3fcc500c, wk:0x3c2dd17c, run:0]
I (1620) AI_AUDIO_WWE: CB: RECV Pipeline EVT: el:NULL-0x3c2dd080, type:8192, sub:ESP_GMF_EVENT_STATE_OPENING, payload:0x0, size:0,0x0
I (1632) AFE_manager: AFE Ctrl [1, 0]
I (1637) AFE_manager: VAD ctrl ret 1
Build fst from commands.
Quantized MultiNet6:rnnt_ctc_1.0, name:mn6_cn, (Feb 18 2025 12:00:53)
Quantized MultiNet6 search method: 2, time out:5.8 s
I (2639) NEW_DATA_BUS: New ringbuffer:0x3c6feeb4, num:2, item_cnt:8192, db:0x3c6e2b3c
I (2643) NEW_DATA_BUS: New ringbuffer:0x3c6fe4d0, num:1, item_cnt:20480, db:0x3c6fcbd8
I (2651) AFE_manager: AFE manager suspend 1
I (2656) AFE_manager: AFE manager suspend 0
I (2661) AI_AUDIO_WWE: CB: RECV Pipeline EVT: el:ai_afe-0x3c2dd0b8, type:12288, sub:ESP_GMF_EVENT_STATE_INITIALIZED, payload:0x3fccd920, size:12,0x0
I (2675) AI_AUDIO_WWE: CB: RECV Pipeline EVT: el:ai_afe-0x3c2dd0b8, type:8192, sub:ESP_GMF_EVENT_STATE_RUNNING, payload:0x0, size:0,0x0
I (2688) ESP_GMF_TASK: One times job is complete, del[wk:0x3c2dd17c,ctx:0x3c2dd0b8, label:gmf_afe_open]
I (2698) ESP_GMF_PORT: ACQ IN, new self payload:0x3c2dd17c, port:0x3c2dd240, el:0x3c2dd0b8-ai_afe
Type 'help' to get the list of commands.
Use UP/DOWN arrows to navigate through command history.
Press TAB when typing command name to auto-complete.
I (2893) ESP_GMF_PORT: ACQ OUT, new self payload:0x3c6fc548, port:0x3c2dd280, el:0x3c2dd0b8-ai_afe
Audio > I (5668) AFE_manager: AFE Ctrl [1, 1]
I (5669) AFE_manager: VAD ctrl ret 1
I (5674) AI_AUDIO_WWE: WAKEUP_START [1 : 1]
I (6334) AI_AUDIO_WWE: VAD_START
W (7833) AI_AUDIO_WWE: Command 25, phrase_id 25, prob 0.998051, str: da kai kong tiao
I (8110) AI_AUDIO_WWE: VAD_END
I (8128) AI_AUDIO_WWE: File closed
I (10110) AFE_manager: AFE Ctrl [1, 0]
I (10111) AFE_manager: VAD ctrl ret 1
I (10118) AI_AUDIO_WWE: WAKEUP_END
```
### Troubleshooting
1. **Wake Word Not Detected**:
- Ensure `WAKENET_ENABLE` is set to `true`
- Verify that the model files are correctly loaded
- Check if the channel configuration matches the hardware setup
2. **Recording File Not Generated**:
- Ensure the SD card is correctly installed
- Verify that `VOICE2FILE` is set to `true`
- Confirm that speech is detected after wakeup
3. **Task Watchdog Timeout**:
- Ensure `esp_afe_manager_cfg_t.feed_task_setting.core` and `esp_afe_manager_cfg_t.fetch_task_setting.core` are configured on different CPU cores
- Properly allocate tasks on the core where the timeout occurs
@@ -0,0 +1,156 @@
# 语音识别
- [English](./README.md)
## 例程简介
本例程介绍使用 `AFE element` 进行唤醒词、人声检测和命令词检测的方法,并通过 [main.c](./main/main.c) 中的以下宏定义来配置功能,从而实现不同的应用组合:
```c
#define VOICE2FILE (true)
#define WAKENET_ENABLE (true)
#define VAD_ENABLE (true)
#define VCMD_ENABLE (true)
```
- `VOICE2FILE`: 以文件的形式保存 VAD 开始到 VAD 结束之间的音频
- `WAKENET_ENABLE`: 唤醒词识别使能
- `VAD_ENABLE`: 人声检测使能
- `VCMD_ENABLE`: 命令词检测使能
## GMF Pipeline
```mermaid
graph
subgraph WWE
E[CODEC_DEV_RX] --> F[GMF_AFE] --> G[GMF_PORT]
end
```
## 示例创建
### IDF 默认分支
本例程支持 IDF release/v5.3 及以后的分支
### 预备知识
例程中使用的语音唤醒和命令词检测,源自于 `esp-sr`,请先了解其配置和使用: [README](https://github.com/espressif/esp-sr/blob/master/README.md)
### 编译和下载
编译本例程前需要先确保已配置 ESP-IDF 的环境,如果已配置可跳到下一项配置,如果未配置需要先在 ESP-IDF 根目录运行下面脚本设置编译环境,有关配置和使用 ESP-IDF 完整步骤,请参阅 [《ESP-IDF 编程指南》](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32s3/index.html)
```
./install.sh
. ./export.sh
```
下面是简略编译步骤:
- 进入示例工程的位置
```
cd gmf_ai_audio/examples/wwe
```
- 选择编译芯片,以 esp32s3 为例:
```
idf.py set-target esp32s3
```
- 选择编译目标板,以 ESP32-S3-Korvo V2 为例:
```
idf.py menuconfig
在 `menuconfig` 中选择 `GMF APP Configuration` -> `Audio Board` -> `ESP32-S3-Korvo V2`,然后保存退出
```
- 编译例子程序
```
idf.py build
```
- 烧录程序并运行 monitor 工具来查看串口输出 (替换 PORT 为端口名称):
```
idf.py -p PORT flash monitor
```
- 退出调试界面使用 `Ctrl-]`
## 如何使用例程
1. **配置功能**: 在 `main.c` 中通过上文中说明的宏定义启用或禁用功能来选择对应的应用场景
2. **运行要求**:
- 如果启用了 `VOICE2FILE`,请确保 SD 卡已正确安装到开发板上
- 如果启用了 `WAKENET_ENABLE`,运行时需要先说出唤醒词,然后再说命令词
- 如果关闭了 `WAKENET_ENABLE`,运行时可以直接说命令词
3. **查看结果**:
- 运行过程中,串口会打印检测到的事件日志
- 如果启用了 `VOICE2FILE`,录音文件将保存到 SD 卡,文件名格式为 `16k_16bit_1ch_{idx}.pcm`
### 功能和用法
运行后,可以对开发板说唤醒词和命令词,串口会打印如下日志:
```c
I (1547) AFE: AFE Version: (2MIC_V250113)
I (1550) AFE: Input PCM Config: total 4 channels(2 microphone, 1 playback), sample rate:16000
I (1560) AFE: AFE Pipeline: [input] -> |AEC(SR_HIGH_PERF)| -> |SE(BSS)| -> |VAD(WebRTC)| -> |WakeNet(wn9_hilexin,)| -> [output]
I (1572) AFE_manager: Feed task, ch 4, chunk 1024, buf size 8192
I (1579) GMF_AFE: Create AFE, ai_afe-0x3c2dcf90
I (1584) GMF_AFE: Create AFE, ai_afe-0x3c2dd0b8
I (1589) GMF_AFE: New an object,ai_afe-0x3c2dd0b8
I (1595) ESP_GMF_TASK: Waiting to run... [tsk:TSK_0x3fcc500c-0x3fcc500c, wk:0x0, run:0]
I (1603) ESP_GMF_THREAD: The TSK_0x3fcc500c created on internal memory
I (1610) ESP_GMF_TASK: Waiting to run... [tsk:TSK_0x3fcc500c-0x3fcc500c, wk:0x3c2dd17c, run:0]
I (1620) AI_AUDIO_WWE: CB: RECV Pipeline EVT: el:NULL-0x3c2dd080, type:8192, sub:ESP_GMF_EVENT_STATE_OPENING, payload:0x0, size:0,0x0
I (1632) AFE_manager: AFE Ctrl [1, 0]
I (1637) AFE_manager: VAD ctrl ret 1
Build fst from commands.
Quantized MultiNet6:rnnt_ctc_1.0, name:mn6_cn, (Feb 18 2025 12:00:53)
Quantized MultiNet6 search method: 2, time out:5.8 s
I (2639) NEW_DATA_BUS: New ringbuffer:0x3c6feeb4, num:2, item_cnt:8192, db:0x3c6e2b3c
I (2643) NEW_DATA_BUS: New ringbuffer:0x3c6fe4d0, num:1, item_cnt:20480, db:0x3c6fcbd8
I (2651) AFE_manager: AFE manager suspend 1
I (2656) AFE_manager: AFE manager suspend 0
I (2661) AI_AUDIO_WWE: CB: RECV Pipeline EVT: el:ai_afe-0x3c2dd0b8, type:12288, sub:ESP_GMF_EVENT_STATE_INITIALIZED, payload:0x3fccd920, size:12,0x0
I (2675) AI_AUDIO_WWE: CB: RECV Pipeline EVT: el:ai_afe-0x3c2dd0b8, type:8192, sub:ESP_GMF_EVENT_STATE_RUNNING, payload:0x0, size:0,0x0
I (2688) ESP_GMF_TASK: One times job is complete, del[wk:0x3c2dd17c,ctx:0x3c2dd0b8, label:gmf_afe_open]
I (2698) ESP_GMF_PORT: ACQ IN, new self payload:0x3c2dd17c, port:0x3c2dd240, el:0x3c2dd0b8-ai_afe
Type 'help' to get the list of commands.
Use UP/DOWN arrows to navigate through command history.
Press TAB when typing command name to auto-complete.
I (2893) ESP_GMF_PORT: ACQ OUT, new self payload:0x3c6fc548, port:0x3c2dd280, el:0x3c2dd0b8-ai_afe
Audio > I (5668) AFE_manager: AFE Ctrl [1, 1]
I (5669) AFE_manager: VAD ctrl ret 1
I (5674) AI_AUDIO_WWE: WAKEUP_START [1 : 1]
I (6334) AI_AUDIO_WWE: VAD_START
W (7833) AI_AUDIO_WWE: Command 25, phrase_id 25, prob 0.998051, str: da kai kong tiao
I (8110) AI_AUDIO_WWE: VAD_END
I (8128) AI_AUDIO_WWE: File closed
I (10110) AFE_manager: AFE Ctrl [1, 0]
I (10111) AFE_manager: VAD ctrl ret 1
I (10118) AI_AUDIO_WWE: WAKEUP_END
```
### 常见问题
1. **无法检测唤醒词**:
- 确保 `WAKENET_ENABLE` 设置为 `true`
- 检查模型文件是否正确加载
- 配置的通道顺序是否符合硬件实际
2. **录音文件未生成**:
- 确保 SD 卡已正确安装。
- 检查 `VOICE2FILE` 是否设置为 `true`
- 唤醒后是否有说话
3. **Task Watch Dog**:
- 确保 `esp_afe_manager_cfg_t.feed_task_setting.core` 和 `esp_afe_manager_cfg_t.fetch_task_setting.core` 配置在不同的 cpu 核心上
- 将出现 `task wdt` 的核上任务合理分配
@@ -0,0 +1,3 @@
idf_component_register(SRC_DIRS "./"
INCLUDE_DIRS "./")
@@ -0,0 +1,19 @@
dependencies:
espressif/gmf_loader:
override_path: ../../../../../packages/gmf_loader
version: "^0.7"
espressif/gmf_app_utils:
override_path: ../../../../../packages/gmf_app_utils
version: "^0.7"
espressif/gmf_core:
override_path: ../../../../../gmf_core
espressif/gmf_io:
override_path: ../../../../gmf_io
espressif/gmf_audio:
override_path: ../../../../gmf_audio
espressif/gmf_ai_audio:
override_path: ../../../../gmf_ai_audio
espressif/gmf_video:
override_path: ../../../../gmf_video
espressif/gmf_misc:
override_path: ../../../../gmf_misc
@@ -0,0 +1,399 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO., LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include "esp_err.h"
#include "esp_log.h"
#include "soc/soc_caps.h"
#include "esp_console.h"
#include "argtable3/argtable3.h"
#include "esp_gmf_io.h"
#include "esp_gmf_pipeline.h"
#include "esp_gmf_pool.h"
#include "esp_gmf_app_setup_peripheral.h"
#include "esp_gmf_io_codec_dev.h"
#include "esp_gmf_app_cli.h"
#include "gmf_loader_setup_defaults.h"
#if SOC_SDMMC_HOST_SUPPORTED == 1
#define VOICE2FILE (true)
#endif /* SOC_SDMMC_HOST_SUPPORTED == 1 */
#ifdef CONFIG_GMF_AI_AUDIO_WAKEUP_ENABLE
#define WAKENET_ENABLE (true)
#else
#define WAKENET_ENABLE (false)
#endif /* CONFIG_GMF_AI_AUDIO_WAKEUP_ENABLE */
#ifdef CONFIG_GMF_AI_AUDIO_VOICE_COMMAND_ENABLE
#define VCMD_ENABLE (true)
#else
#define VCMD_ENABLE (false)
#endif /* CONFIG_GMF_AI_AUDIO_VOICE_COMMAND_ENABLE */
#define QUIT_CMD_FOUND (BIT0)
#define BOARD_LYRAT_MINI (0)
#define BOARD_KORVO_2 (1)
#define BOARD_XD_AIOT_C3 (2)
#define BOARD_ESP_SPOT (3)
#define BOARD_P4_FUN_EV (4)
#if defined CONFIG_IDF_TARGET_ESP32S3
#define WITH_AFE (true)
#define AUDIO_BOARD (BOARD_KORVO_2)
#elif defined CONFIG_IDF_TARGET_ESP32
#define WITH_AFE (true)
#define AUDIO_BOARD (BOARD_LYRAT_MINI)
#elif defined CONFIG_IDF_TARGET_ESP32C3
#define WITH_AFE (false)
#define AUDIO_BOARD (BOARD_XD_AIOT_C3)
#elif defined CONFIG_IDF_TARGET_ESP32C5
#define WITH_AFE (false)
#define AUDIO_BOARD (BOARD_ESP_SPOT)
#elif defined CONFIG_IDF_TARGET_ESP32P4
#define WITH_AFE (true)
#define AUDIO_BOARD (BOARD_P4_FUN_EV)
#endif /* defined CONFIG_IDF_TARGET_ESP32S3 */
#if AUDIO_BOARD == BOARD_KORVO_2
#define ADC_I2S_CH (2)
#define ADC_I2S_BITS (32)
#define INPUT_CH_NUM (4)
#define INPUT_CH_BITS (16) /* For board `ESP32-S3-Korvo-2`, the es7210 is configured as 32-bit,
2-channel mode to accommodate 16-bit, 4-channel data */
#elif AUDIO_BOARD == BOARD_LYRAT_MINI
#define ADC_I2S_CH (2)
#define ADC_I2S_BITS (16)
#define INPUT_CH_NUM (ADC_I2S_CH)
#define INPUT_CH_BITS (ADC_I2S_BITS)
#elif AUDIO_BOARD == BOARD_XD_AIOT_C3
#define ADC_I2S_CH (2)
#define ADC_I2S_BITS (16)
#define INPUT_CH_NUM (ADC_I2S_CH)
#define INPUT_CH_BITS (ADC_I2S_BITS)
#elif AUDIO_BOARD == BOARD_ESP_SPOT
#define ADC_I2S_CH (2)
#define ADC_I2S_BITS (16)
#define INPUT_CH_NUM (ADC_I2S_CH)
#define INPUT_CH_BITS (ADC_I2S_BITS)
#elif AUDIO_BOARD == BOARD_P4_FUN_EV
#define ADC_I2S_CH (2)
#define ADC_I2S_BITS (16)
#define INPUT_CH_NUM (ADC_I2S_CH)
#define INPUT_CH_BITS (ADC_I2S_BITS)
#endif /* AUDIO_BOARD == BOARD_KORVO_2 */
#if WITH_AFE == true
#include "esp_gmf_afe.h"
#else
#include "esp_gmf_wn.h"
#endif /* WITH_AFE == true */
static const char *TAG = "AI_AUDIO_WWE";
#if WITH_AFE == true
static bool speeching = false;
static bool wakeup = false;
#endif /* WITH_AFE == true */
static EventGroupHandle_t g_event_group = NULL;
#if WITH_AFE == true
static esp_gmf_element_handle_t g_afe = NULL;
#endif /* WITH_AFE == true */
static esp_err_t _pipeline_event(esp_gmf_event_pkt_t *event, void *ctx)
{
ESP_LOGI(TAG, "CB: RECV Pipeline EVT: el:%s-%p, type:%d, sub:%s, payload:%p, size:%d,%p",
OBJ_GET_TAG(event->from), event->from, event->type, esp_gmf_event_get_state_str(event->sub),
event->payload, event->payload_size, ctx);
return 0;
}
#if WITH_AFE == true
void esp_gmf_afe_event_cb(esp_gmf_obj_handle_t obj, esp_gmf_afe_evt_t *event, void *user_data)
{
switch (event->type) {
case ESP_GMF_AFE_EVT_WAKEUP_START: {
wakeup = true;
#if WAKENET_ENABLE == true && VCMD_ENABLE == true
esp_gmf_afe_vcmd_detection_cancel(obj);
esp_gmf_afe_vcmd_detection_begin(obj);
#endif /* WAKENET_ENABLE == true && VCMD_ENABLE == true */
if (event->event_data) {
esp_gmf_afe_wakeup_info_t *info = event->event_data;
ESP_LOGI(TAG, "WAKEUP_START [%d : %d]", info->wake_word_index, info->wakenet_model_index);
} else {
ESP_LOGI(TAG, "WAKEUP_START");
}
break;
}
case ESP_GMF_AFE_EVT_WAKEUP_END: {
wakeup = false;
#if WAKENET_ENABLE == true && VCMD_ENABLE == true
esp_gmf_afe_vcmd_detection_cancel(obj);
#endif /* WAKENET_ENABLE == true && VCMD_ENABLE == true */
ESP_LOGI(TAG, "WAKEUP_END");
break;
}
case ESP_GMF_AFE_EVT_VAD_START: {
#if WAKENET_ENABLE != true && VCMD_ENABLE == true
esp_gmf_afe_vcmd_detection_cancel(obj);
esp_gmf_afe_vcmd_detection_begin(obj);
#endif /* WAKENET_ENABLE != true && VCMD_ENABLE == true */
speeching = true;
ESP_LOGI(TAG, "VAD_START");
break;
}
case ESP_GMF_AFE_EVT_VAD_END: {
#if WAKENET_ENABLE != true && VCMD_ENABLE == true
esp_gmf_afe_vcmd_detection_cancel(obj);
#endif /* WAKENET_ENABLE != true && VCMD_ENABLE == true */
speeching = false;
ESP_LOGI(TAG, "VAD_END");
break;
}
case ESP_GMF_AFE_EVT_VCMD_DECT_TIMEOUT: {
ESP_LOGI(TAG, "VCMD_DECT_TIMEOUT");
break;
}
default: {
esp_gmf_afe_vcmd_info_t *info = event->event_data;
ESP_LOGW(TAG, "Command %d, phrase_id %d, prob %f, str: %s",
event->type, info->phrase_id, info->prob, info->str);
break;
}
}
}
#else
static void esp_gmf_wn_event_cb(esp_gmf_obj_handle_t obj, int32_t trigger_ch, void *user_ctx)
{
static int32_t cnt = 1;
ESP_LOGI(TAG, "WWE detected on channel %" PRIi32 ", cnt: %" PRIi32, trigger_ch, cnt++);
if (cnt >= 10) {
xEventGroupSetBits(g_event_group, QUIT_CMD_FOUND);
}
}
#endif /* WITH_AFE == true */
static void voice_2_file(uint8_t *buffer, int len)
{
#if VOICE2FILE == true && WITH_AFE == true
#define MAX_FNAME_LEN (50)
static FILE *fp = NULL;
static int fcnt = 0;
if (speeching) {
if (!fp) {
char fname[MAX_FNAME_LEN] = {0};
snprintf(fname, MAX_FNAME_LEN - 1, "/sdcard/16k_16bit_1ch_%d.pcm", fcnt++);
fp = fopen(fname, "wb");
if (!fp) {
ESP_LOGE(TAG, "File open failed");
return;
}
}
if (len) {
fwrite(buffer, len, 1, fp);
}
} else {
if (fp) {
ESP_LOGI(TAG, "File closed");
fclose(fp);
fp = NULL;
}
}
#endif /* VOICE2FILE == true && WITH_AFE == true */
}
static esp_gmf_err_io_t outport_acquire_write(void *handle, esp_gmf_payload_t *load, int wanted_size, int block_ticks)
{
ESP_LOGD(TAG, "Acquire write");
return ESP_GMF_IO_OK;
}
static esp_gmf_err_io_t outport_release_write(void *handle, esp_gmf_payload_t *load, int block_ticks)
{
ESP_LOGD(TAG, "Release write");
voice_2_file(load->buf, load->valid_size);
return ESP_GMF_IO_OK;
}
#if WITH_AFE == true
static struct {
struct arg_int *keep;
struct arg_end *end;
} keep_args;
static int keep_awake(int argc, char **argv)
{
int nerrors = arg_parse(argc, argv, (void **)&keep_args);
if (nerrors != 0) {
arg_print_errors(stderr, keep_args.end, argv[0]);
return ESP_FAIL;
}
if (g_afe) {
esp_gmf_afe_keep_awake(g_afe, keep_args.keep->ival[0]);
printf("Keep awake: %d\n", keep_args.keep->ival[0]);
} else {
ESP_LOGE(TAG, "AFE not found");
}
return ESP_OK;
}
static struct {
struct arg_str *cmd;
struct arg_end *end;
} trigger_args;
static int trigger(int argc, char **argv)
{
int nerrors = arg_parse(argc, argv, (void **)&trigger_args);
if (nerrors != 0) {
arg_print_errors(stderr, trigger_args.end, argv[0]);
return ESP_FAIL;
}
if (g_afe) {
if (strcmp(trigger_args.cmd->sval[0], "wakeup") == 0) {
esp_gmf_afe_trigger_wakeup(g_afe);
} else if (strcmp(trigger_args.cmd->sval[0], "sleep") == 0) {
esp_gmf_afe_trigger_sleep(g_afe);
}
}
return 0;
}
#endif /* WITH_AFE == true */
static int quit(int argc, char **argv)
{
xEventGroupSetBits(g_event_group, QUIT_CMD_FOUND);
return 0;
}
static void wwe_cmds_register(void)
{
#if WITH_AFE == true
esp_console_cmd_t cmd_keep = {
.command = "keep",
.help = "keep awake",
.hint = NULL,
.func = &keep_awake,
.argtable = &keep_args,
};
keep_args.keep = arg_int0(NULL, NULL, "<0 - 1>", "Keep awake");
keep_args.end = arg_end(1);
esp_console_cmd_register(&cmd_keep);
esp_console_cmd_t cmd_trigger_wakeup = {
.command = "trigger",
.help = "trigger wakeup or sleep",
.hint = NULL,
.func = &trigger,
.argtable = &trigger_args,
};
trigger_args.cmd = arg_str0(NULL, NULL, "<wakeup|sleep>", "Trigger wakeup or sleep");
trigger_args.end = arg_end(1);
esp_console_cmd_register(&cmd_trigger_wakeup);
#endif /* WITH_AFE == true */
esp_console_cmd_t cmd_quit = {
.command = "quit",
.help = "quit",
.hint = NULL,
.func = &quit,
.argtable = NULL,
};
esp_console_cmd_register(&cmd_quit);
}
void app_main(void)
{
esp_log_level_set("*", ESP_LOG_INFO);
esp_gmf_app_codec_info_t codec_info = ESP_GMF_APP_CODEC_INFO_DEFAULT();
codec_info.record_info.sample_rate = 16000;
codec_info.record_info.channel = ADC_I2S_CH;
codec_info.record_info.bits_per_sample = ADC_I2S_BITS;
codec_info.play_info.sample_rate = codec_info.record_info.sample_rate;
esp_gmf_app_setup_codec_dev(&codec_info);
void *sdcard_handle = NULL;
esp_gmf_app_setup_sdcard(&sdcard_handle);
g_event_group = xEventGroupCreate();
esp_gmf_pool_handle_t pool = NULL;
esp_gmf_pool_init(&pool);
gmf_loader_setup_all_defaults(pool);
esp_gmf_pipeline_handle_t pipe = NULL;
#if WITH_AFE == true
const char *name[] = {"ai_afe"};
#else
const char *name[] = {"ai_wn"};
#endif /* WITH_AFE == true */
esp_gmf_pool_new_pipeline(pool, "io_codec_dev", name, sizeof(name) / sizeof(char *), NULL, &pipe);
if (pipe == NULL) {
ESP_LOGE(TAG, "There is no pipeline");
goto __quit;
}
esp_gmf_io_codec_dev_set_dev(ESP_GMF_PIPELINE_GET_IN_INSTANCE(pipe), esp_gmf_app_get_record_handle());
#if WITH_AFE == true
esp_gmf_pipeline_get_el_by_name(pipe, "ai_afe", &g_afe);
esp_gmf_afe_set_event_cb(g_afe, esp_gmf_afe_event_cb, NULL);
#else
esp_gmf_element_handle_t wn = NULL;
esp_gmf_pipeline_get_el_by_name(pipe, "ai_wn", &wn);
esp_gmf_wn_set_detect_cb(wn, esp_gmf_wn_event_cb, NULL);
#endif /* WITH_AFE == true */
esp_gmf_port_handle_t outport = NEW_ESP_GMF_PORT_OUT_BYTE(outport_acquire_write,
outport_release_write,
NULL,
NULL,
2048,
100);
esp_gmf_pipeline_reg_el_port(pipe, name[0], ESP_GMF_IO_DIR_WRITER, outport);
esp_gmf_info_sound_t info = {
.sample_rates = 16000,
.channels = INPUT_CH_NUM,
.bits = INPUT_CH_BITS,
};
esp_gmf_pipeline_report_info(pipe, ESP_GMF_INFO_SOUND, &info, sizeof(info));
esp_gmf_task_cfg_t cfg = DEFAULT_ESP_GMF_TASK_CONFIG();
cfg.ctx = NULL;
cfg.cb = NULL;
cfg.thread.core = 0;
cfg.thread.prio = 5;
cfg.thread.stack = 5120;
esp_gmf_task_handle_t task = NULL;
esp_gmf_task_init(&cfg, &task);
esp_gmf_pipeline_bind_task(pipe, task);
esp_gmf_pipeline_loading_jobs(pipe);
esp_gmf_pipeline_set_event(pipe, _pipeline_event, NULL);
esp_gmf_pipeline_run(pipe);
esp_gmf_app_cli_init("Audio >", wwe_cmds_register);
while (1) {
EventBits_t bits = xEventGroupWaitBits(g_event_group, QUIT_CMD_FOUND, pdTRUE, pdFALSE, portMAX_DELAY);
if (bits & QUIT_CMD_FOUND) {
ESP_LOGI(TAG, "Quit command found, stopping pipeline");
break;
}
}
__quit:
esp_gmf_pipeline_stop(pipe);
esp_gmf_task_deinit(task);
esp_gmf_pipeline_destroy(pipe);
gmf_loader_teardown_all_defaults(pool);
esp_gmf_pool_deinit(pool);
esp_gmf_app_teardown_codec_dev();
esp_gmf_app_teardown_sdcard(sdcard_handle);
vEventGroupDelete(g_event_group);
ESP_LOGW(TAG, "Wake word engine demo finished");
}
@@ -0,0 +1,5 @@
# Espressif ESP32 Partition Table
# Name, Type, SubType, Offset, Size
nvs, data, nvs, 0x9000, 0x6000,
factory, app, factory, 0x010000, 2500k
model, data, spiffs, , 5168K,
1 # Espressif ESP32 Partition Table
2 # Name, Type, SubType, Offset, Size
3 nvs, data, nvs, 0x9000, 0x6000,
4 factory, app, factory, 0x010000, 2500k
5 model, data, spiffs, , 5168K,
@@ -0,0 +1,5 @@
# Espressif ESP32 Partition Table
# Name, Type, SubType, Offset, Size
nvs, data, nvs, 0x9000, 0x6000,
factory, app, factory, 0x010000, 2500k
model, data, spiffs, , 2M,
1 # Espressif ESP32 Partition Table
2 # Name, Type, SubType, Offset, Size
3 nvs, data, nvs, 0x9000, 0x6000,
4 factory, app, factory, 0x010000, 2500k
5 model, data, spiffs, , 2M,
@@ -0,0 +1,14 @@
# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO., LTD
#
# SPDX-License-Identifier: Apache-2.0
import pytest
import os
from pytest_embedded import Dut
@pytest.mark.esp32
@pytest.mark.esp32s3
@pytest.mark.temp_skip_ci(targets=['esp32', 'esp32s3', 'esp32p4'], reason='No running in CI')
def test_wwe(dut: Dut)-> None:
dut.expect(r'Audio >', timeout=20)
@@ -0,0 +1,41 @@
#
# Serial flasher config
#
CONFIG_ESPTOOLPY_FLASH_MODE_AUTO_DETECT=y
CONFIG_ESPTOOLPY_FLASHMODE_DIO=y
CONFIG_ESPTOOLPY_FLASH_SAMPLE_MODE_STR=y
CONFIG_ESPTOOLPY_FLASHMODE="dio"
CONFIG_ESPTOOLPY_FLASHFREQ_80M=y
CONFIG_ESPTOOLPY_FLASHFREQ="80m"
CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y
CONFIG_ESPTOOLPY_FLASHSIZE="8MB"
#
# Compiler options
#
CONFIG_COMPILER_OPTIMIZATION_PERF=y
# end of Compiler options
#
# ESP PSRAM
#
CONFIG_SPIRAM=y
CONFIG_SPIRAM_TYPE_AUTO=y
CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY=y
CONFIG_SPIRAM_SPEED_80M=y
CONFIG_SPIRAM_SPEED=80
#
# Audio Codec Device Configuration
#
# CONFIG_CODEC_I2C_BACKWARD_COMPATIBLE is not set
# end of Audio Codec Device Configuration
CONFIG_FATFS_LFN_HEAP=y
CONFIG_FREERTOS_HZ=1000
CONFIG_FREERTOS_USE_TRACE_FACILITY=y
CONFIG_FREERTOS_USE_STATS_FORMATTING_FUNCTIONS=y
# CONFIG_FREERTOS_USE_LIST_DATA_INTEGRITY_CHECK_BYTES is not set
CONFIG_FREERTOS_VTASKLIST_INCLUDE_COREID=y
CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS=y
@@ -0,0 +1,45 @@
CONFIG_IDF_CMAKE=y
CONFIG_IDF_TARGET_ARCH_XTENSA=y
CONFIG_IDF_TARGET="esp32"
CONFIG_IDF_TARGET_ESP32=y
CONFIG_IDF_FIRMWARE_CHIP_ID=0x0000
#
# Partition Table
#
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_esp32.csv"
CONFIG_PARTITION_TABLE_FILENAME="partitions_esp32.csv"
CONFIG_PARTITION_TABLE_OFFSET=0x8000
CONFIG_PARTITION_TABLE_MD5=y
# end of Partition Table
#
# ESP Speech Recognition
#
CONFIG_AFE_INTERFACE_V1=y
CONFIG_SR_NSN_WEBRTC=y
CONFIG_SR_VADN_WEBRTC=y
#
# Load Multiple Wake Words
#
CONFIG_SR_WN_WN9_HILEXIN=y
CONFIG_SR_MN_CN_NONE=y
# CONFIG_SR_MN_CN_MULTINET2_SINGLE_RECOGNITION is not set
CONFIG_SR_MN_EN_NONE=y
# end of ESP Speech Recognition
#
# GMF AI Audio
#
CONFIG_GMF_AI_AUDIO_INIT_AEC=y
CONFIG_GMF_AI_AUDIO_INIT_AFE=y
CONFIG_GMF_AI_AUDIO_AFE_VAD_ENABLE=y
CONFIG_GMF_AI_AUDIO_VAD_MODE=3
CONFIG_GMF_AI_AUDIO_WAKEUP_ENABLE=y
# end of GMF AI Audio
CONFIG_AUDIO_BOARD="LYRAT_MINI_V1"
@@ -0,0 +1,32 @@
#
# Partition Table
#
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
CONFIG_PARTITION_TABLE_FILENAME="partitions.csv"
CONFIG_PARTITION_TABLE_OFFSET=0x8000
CONFIG_PARTITION_TABLE_MD5=y
# end of Partition Table
#
# ESP Speech Recognition
#
CONFIG_AFE_INTERFACE_V1=y
CONFIG_SR_NSN_WEBRTC=y
CONFIG_SR_VADN_WEBRTC=y
#
# Load Multiple Wake Words
#
CONFIG_SR_WN_WN9S_HILEXIN=y
# CONFIG_SR_WN_WN9S_HIESP is not set
# CONFIG_SR_WN_WN9S_NIHAOXIAOZHI is not set
# CONFIG_SR_WN_WN9S_HIJASON is not set
# end of Load Multiple Wake Words
CONFIG_SR_MN_CN_NONE=y
CONFIG_SR_MN_EN_NONE=y
# end of ESP Speech Recognition
CONFIG_GMF_AI_AUDIO_INIT_WN=y
CONFIG_GMF_AI_AUDIO_WN_CH_ALLOCATION="MR"
@@ -0,0 +1,34 @@
#
# Partition Table
#
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
CONFIG_PARTITION_TABLE_FILENAME="partitions.csv"
CONFIG_PARTITION_TABLE_OFFSET=0x8000
CONFIG_PARTITION_TABLE_MD5=y
# end of Partition Table
#
# ESP Speech Recognition
#
CONFIG_AFE_INTERFACE_V1=y
CONFIG_SR_NSN_WEBRTC=y
CONFIG_SR_VADN_WEBRTC=y
#
# Load Multiple Wake Words
#
CONFIG_SR_WN_WN9S_HILEXIN=y
# CONFIG_SR_WN_WN9S_HIESP is not set
# CONFIG_SR_WN_WN9S_NIHAOXIAOZHI is not set
# CONFIG_SR_WN_WN9S_HIJASON is not set
# end of Load Multiple Wake Words
CONFIG_SR_MN_CN_NONE=y
CONFIG_SR_MN_EN_NONE=y
# end of ESP Speech Recognition
CONFIG_GMF_AI_AUDIO_INIT_WN=y
CONFIG_GMF_AI_AUDIO_WN_CH_ALLOCATION="MR"
CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y
@@ -0,0 +1,59 @@
CONFIG_IDF_TARGET="esp32p4"
CONFIG_IDF_TARGET_ESP32P4=y
CONFIG_IDF_EXPERIMENTAL_FEATURES=y
#
# Partition Table
#
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
CONFIG_PARTITION_TABLE_FILENAME="partitions.csv"
# end of Partition Table
#
# ESP Speech Recognition
#
CONFIG_MODEL_IN_FLASH=y
CONFIG_AFE_INTERFACE_V1=y
CONFIG_SR_NSN_WEBRTC=y
CONFIG_SR_VADN_WEBRTC=y
CONFIG_SR_WN_WN9S_HILEXIN=y
CONFIG_SR_MN_CN_MULTINET7_QUANT=y
CONFIG_SR_MN_EN_NONE=y
# end of ESP Speech Recognition
#
# ESP PSRAM
#
CONFIG_SPIRAM=y
#
# PSRAM config
#
CONFIG_SPIRAM_MODE_HEX=y
CONFIG_SPIRAM_SPEED_200M=y
CONFIG_SPIRAM_SPEED=200
CONFIG_SPIRAM_BOOT_INIT=y
CONFIG_SPIRAM_USE_MALLOC=y
CONFIG_SPIRAM_MEMTEST=y
CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=16384
CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL=32768
#
# GMF AI Audio
#
CONFIG_GMF_AI_AUDIO_INIT_AEC=y
CONFIG_GMF_AI_AUDIO_INIT_AFE=y
CONFIG_GMF_AI_AUDIO_AFE_VAD_ENABLE=y
CONFIG_GMF_AI_AUDIO_AFE_AEC_ENABLE=y
CONFIG_GMF_AI_AUDIO_AEC_CH_ALLOCATION="MR"
CONFIG_GMF_AI_AUDIO_AFE_CH_ALLOCATION="MR"
CONFIG_GMF_AI_AUDIO_WAKEUP_ENABLE=y
CONFIG_GMF_AI_AUDIO_VOICE_COMMAND_ENABLE=y
# end of GMF AI Audio
CONFIG_AUDIO_BOARD="ESP32_P4_DEV_V14"
@@ -0,0 +1,84 @@
CONFIG_IDF_CMAKE=y
CONFIG_IDF_TARGET_ARCH_XTENSA=y
CONFIG_IDF_TARGET="esp32s3"
CONFIG_IDF_TARGET_ESP32S3=y
CONFIG_IDF_FIRMWARE_CHIP_ID=0x0009
#
# Partition Table
#
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
CONFIG_PARTITION_TABLE_FILENAME="partitions.csv"
CONFIG_PARTITION_TABLE_OFFSET=0x8000
CONFIG_PARTITION_TABLE_MD5=y
# end of Partition Table
#
# ESP Speech Recognition
#
CONFIG_MODEL_IN_FLASH=y
CONFIG_AFE_INTERFACE_V1=y
CONFIG_SR_WN_WN9_HILEXIN=y
CONFIG_SR_MN_CN_MULTINET6_QUANT=y
CONFIG_SR_MN_EN_NONE=y
# end of ESP Speech Recognition
#
# ESP PSRAM
#
CONFIG_SPIRAM=y
#
# SPI RAM config
#
CONFIG_SPIRAM_MODE_OCT=y
CONFIG_SPIRAM_TYPE_AUTO=y
CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY=y
CONFIG_SPIRAM_CLK_IO=30
CONFIG_SPIRAM_CS_IO=26
CONFIG_SPIRAM_SPEED_80M=y
CONFIG_SPIRAM_SPEED=80
CONFIG_SPIRAM_BOOT_INIT=y
CONFIG_SPIRAM_USE_MALLOC=y
CONFIG_SPIRAM_MEMTEST=y
CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=16384
CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL=32768
# end of SPI RAM config
# end of ESP PSRAM
#
# ESP System Settings
#
CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240=y
#
# Cache config
#
CONFIG_ESP32S3_INSTRUCTION_CACHE_32KB=y
CONFIG_ESP32S3_INSTRUCTION_CACHE_SIZE=0x8000
CONFIG_ESP32S3_INSTRUCTION_CACHE_8WAYS=y
CONFIG_ESP32S3_ICACHE_ASSOCIATED_WAYS=8
CONFIG_ESP32S3_INSTRUCTION_CACHE_LINE_32B=y
CONFIG_ESP32S3_INSTRUCTION_CACHE_LINE_SIZE=32
CONFIG_ESP32S3_DATA_CACHE_64KB=y
CONFIG_ESP32S3_DATA_CACHE_SIZE=0x10000
CONFIG_ESP32S3_DATA_CACHE_8WAYS=y
CONFIG_ESP32S3_DCACHE_ASSOCIATED_WAYS=8
CONFIG_ESP32S3_DATA_CACHE_LINE_64B=y
CONFIG_ESP32S3_DATA_CACHE_LINE_SIZE=64
# end of Cache config
#
# GMF AI Audio
#
CONFIG_GMF_AI_AUDIO_INIT_AEC=y
CONFIG_GMF_AI_AUDIO_INIT_AFE=y
CONFIG_GMF_AI_AUDIO_AFE_VAD_ENABLE=y
CONFIG_GMF_AI_AUDIO_AFE_AEC_ENABLE=y
CONFIG_GMF_AI_AUDIO_WAKEUP_ENABLE=y
CONFIG_GMF_AI_AUDIO_VOICE_COMMAND_ENABLE=y
# end of GMF AI Audio
CONFIG_AUDIO_BOARD="S3_Korvo_V2"
@@ -0,0 +1,15 @@
version: "0.7.4"
description: Espressif GMF AI Audio Elements Module
url: https://github.com/espressif/esp-gmf/tree/main/elements/gmf_ai_audio
documentation: "https://github.com/espressif/esp-gmf/blob/main/elements/gmf_ai_audio/README.md"
issues: "https://github.com/espressif/esp-gmf/issues"
tags:
- "gmf"
dependencies:
espressif/esp-sr:
version: "2.1.5"
espressif/gmf_core:
version: "^0.7"
require: public
@@ -0,0 +1,53 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO., LTD
* SPDX-License-Identifier: LicenseRef-Espressif-Modified-MIT
*
* See LICENSE file for details.
*/
#pragma once
#include "esp_gmf_err.h"
#include "esp_gmf_element.h"
#include "esp_afe_config.h"
/* This element can run with esp32, esp32s3, esp32c5 and esp32p4 */
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
/**
* @brief Configuration structure for AEC
*
* @note The input format, same as afe config:
* `M` to represent the microphone channel,
* `R` to represent the playback reference channel,
* `N` to represent an unknown or unused channel
* For example, input_format="MMNR" indicates that the input data consists of four channels,
* which are the microphone channel, the microphone channel, an unused channel, and the playback channel
*/
typedef struct {
uint8_t filter_len; /*!< The length of filter. The larger the filter, the higher the CPU loading
* Recommended filter_length = 4 for esp32s3 and esp32p4. Recommended filter_length = 2 for esp32c5 */
afe_type_t type; /*!< AFE type */
afe_mode_t mode; /*!< AFE mode */
char *input_format; /*!< Input format */
} esp_gmf_aec_cfg_t;
/**
* @brief Initialize the Espressif AEC element
*
* @param[in] cfg Pointer to the configuration structure
* @param[out] out_handle Pointer to the handle to be created
*
* @return
* - ESP_GMF_ERR_OK Success
* - ESP_GMF_ERR_MEMORY_LACK Memory allocation failed
* - ESP_GMF_ERR_INVALID_ARG Invalid argument
*/
esp_gmf_err_t esp_gmf_aec_init(esp_gmf_aec_cfg_t *cfg, esp_gmf_obj_handle_t *out_handle);
#ifdef __cplusplus
}
#endif /* __cplusplus */
@@ -0,0 +1,223 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO., LTD
* SPDX-License-Identifier: LicenseRef-Espressif-Modified-MIT
*
* See LICENSE file for details.
*/
#pragma once
#include "esp_gmf_afe_manager.h"
#include "esp_gmf_err.h"
#include "esp_gmf_element.h"
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
/* This element can run with esp32, esp32s3 and esp32p4 */
#define ESP_GMF_AFE_VCMD_MAX_LEN (256)
#define ESP_GMF_AFE_DEFAULT_DELAY_SAMPLES (2048)
#define ESP_GMF_AFE_DEFAULT_WAKEUP_TIME_MS (10000)
#define ESP_GMF_AFE_DEFAULT_WAKEUP_END_MS (2000)
#define ESP_GMF_AFE_DEFAULT_VCMD_TIMEOUT_MS (5760)
/**
* @brief Information when wakeup state detected, event data for "ESP_GMF_AFE_EVT_WAKEUP_START"
*/
typedef struct {
float data_volume; /*!< Volume of input audio, the unit is decibel(dB) */
int wake_word_index; /*!< Wake word index which start from 1 */
int wakenet_model_index; /*!< Wakenets index which start from 1 */
} esp_gmf_afe_wakeup_info_t;
/**
* @brief Information when voice command detected, event data for `ESP_GMF_AFE_EVT_VCMD_DECTECTED`
*/
typedef struct {
int phrase_id; /*!< Phrase ID */
float prob; /*!< probability */
char str[ESP_GMF_AFE_VCMD_MAX_LEN]; /*!< Command string */
} esp_gmf_afe_vcmd_info_t;
/**
* @brief AFE manager event type
*/
typedef enum {
ESP_GMF_AFE_EVT_WAKEUP_START = -100, /*!< Wakeup start */
ESP_GMF_AFE_EVT_WAKEUP_END, /*!< Wakeup stop */
ESP_GMF_AFE_EVT_VAD_START, /*!< Vad start */
ESP_GMF_AFE_EVT_VAD_END, /*!< Vad stop */
ESP_GMF_AFE_EVT_VCMD_DECT_TIMEOUT, /*!< Voice command detect timeout */
ESP_GMF_AFE_EVT_VCMD_DECTECTED = 0 /*!< Form 0 is the id of the voice commands detected by Multinet*/
/* DO NOT add items below this line */
} esp_gmf_afe_event_t;
/**
* @brief Event structure for GMF AFE
*/
typedef struct {
esp_gmf_afe_event_t type; /*!< Event type */
void *event_data; /*!< Event data */
size_t data_len; /*!< Length of event data */
} esp_gmf_afe_evt_t;
/**
* @brief Callback type for GMF AFE events
*/
typedef void (*esp_gmf_afe_event_cb_t)(esp_gmf_element_handle_t el, esp_gmf_afe_evt_t *event, void *user_data);
/**
* @brief Configuration structure for GMF AFE wrapper
*/
typedef struct {
esp_gmf_afe_manager_handle_t afe_manager; /*!< AFE Manager handle */
uint32_t delay_samples; /*!< Number of samples to delay
Note: If the user wants to using the output of AFE only after detecting the VAD start event,
the time corresponding to the value of this parameter should not be less than the `vad_min_speech_ms` in `afe_config_t` used when creating the afe_manager,
otherwise, a small portion of the data at the beginning of the voice may be lost */
void *models; /*!< List of models */
uint32_t wakeup_time; /*!< Unit:ms. The duration that the wakeup state remains when VAD is not triggered */
uint32_t wakeup_end; /*!< Unit:ms. When the silence time after AUDIO_REC_VAD_END state exceeds this value, it is determined as AUDIO_REC_WAKEUP_END */
bool vcmd_detect_en; /*!< Enable voice command detection */
uint32_t vcmd_timeout; /*!< Timeout for voice command detection, units: ms*/
const char *mn_language; /*!< Language for the multi-net model, `cn` or `en` */
esp_gmf_afe_event_cb_t event_cb; /*!< Callback function for AI audio events */
void *event_ctx; /*!< User context to be passed to the callback function */
} esp_gmf_afe_cfg_t;
#define DEFAULT_GMF_AFE_CFG(__afe_manager, __event_cb, __event_ctx, __models) { \
.afe_manager = __afe_manager, /* */ \
.models = __models, /* */ \
.delay_samples = ESP_GMF_AFE_DEFAULT_DELAY_SAMPLES, /* Default delay samples, 64ms */ \
.wakeup_time = ESP_GMF_AFE_DEFAULT_WAKEUP_TIME_MS, /* Default wakeup duration */ \
.wakeup_end = ESP_GMF_AFE_DEFAULT_WAKEUP_END_MS, /* Default wakeup end time */ \
.vcmd_detect_en = false, /* */ \
.vcmd_timeout = ESP_GMF_AFE_DEFAULT_VCMD_TIMEOUT_MS, /* */ \
.mn_language = "cn", /* Default language `cn` */ \
.event_cb = __event_cb, /* */ \
.event_ctx = __event_ctx, /* */ \
}
/**
* @brief Initialize the GMF AFE
*
* @param[in] config Pointer to the configuration structure
* @param[out] handle Pointer to the handle to be created
*
* @return
* - ESP_GMF_ERR_OK Success
* - ESP_GMF_ERR_MEMORY_LACK Memory allocation failed
* - ESP_GMF_ERR_INVALID_ARG Invalid argument
*/
esp_gmf_err_t esp_gmf_afe_init(void *config, esp_gmf_obj_handle_t *handle);
/**
* @brief Begin voice command detection
*
* @param[in] handle Handle to the GMF object
*
* @return
* - ESP_GMF_ERR_OK Success
* - ESP_GMF_ERR_INVALID_ARG Invalid argument
* - ESP_GMF_ERR_INVALID_STATE Voice command not enabled
*/
esp_gmf_err_t esp_gmf_afe_vcmd_detection_begin(esp_gmf_element_handle_t handle);
/**
* @brief Cancel voice command detection
*
* @note This function is used to clear the states of voice command detection process,
* the voice command detection will stay enabled,
* and the user can call `esp_gmf_afe_vcmd_detection_begin` to start the detection again
*
* @param[in] handle Handle to the GMF object
*
* @return
* - ESP_GMF_ERR_OK Success
* - ESP_GMF_ERR_INVALID_ARG Invalid argument
* - ESP_GMF_ERR_INVALID_STATE Voice command not enabled
*/
esp_gmf_err_t esp_gmf_afe_vcmd_detection_cancel(esp_gmf_element_handle_t handle);
/**
* @brief Set the event callback for the AFE (Audio Front-End) element
*
* This function registers a callback function to handle events generated by the
* AFE element. The callback will be invoked with the specified context whenever
* an event occurs
*
* @param handle The handle to the AFE element
* @param cb The callback function to handle AFE events
* @param ctx User-defined context to be passed to the callback function
*
* @return
* - ESP_GMF_ERR_OK Success
* - ESP_GMF_ERR_INVALID_ARG Invalid argument
* - ESP_GMF_ERR_INVALID_STATE Config not exist
*/
esp_gmf_err_t esp_gmf_afe_set_event_cb(esp_gmf_element_handle_t handle, esp_gmf_afe_event_cb_t cb, void *ctx);
/**
* @brief Enable or disable keep-awake mode
*
* When keep-awake mode is enabled, the system will remain in the wake state
* and prevent wakeup_end events from being triggered automatically
* This is useful for scenarios where you want to keep the system active
* without automatic timeout
*
* @param handle The handle to the AFE element
* @param enable True to enable keep-awake mode, false to disable
*
* @return
* - ESP_GMF_ERR_OK Success
* - ESP_GMF_ERR_INVALID_ARG Invalid argument
* - ESP_GMF_ERR_INVALID_STATE Config not exist
* - ESP_GMF_ERR_TIMEOUT Command send timeout
*/
esp_gmf_err_t esp_gmf_afe_keep_awake(esp_gmf_element_handle_t handle, bool enable);
/**
* @brief Manually trigger wakeup state
*
* This function allows manual activation of the wakeup state without waiting
* for automatic wakeword detection. It is useful in the following scenarios:
*
* 1. **Button-triggered activation**: When users press a physical button to
* activate voice interaction, bypassing the need for wakewords
* 2. **External event-driven activation**: When the system needs to enter
* wakeup state based on external triggers (sensors, timers, network events)
*
* After calling this function, the AFE will enter wakeup state and begin
* listening for voice commands (if voice command detection is enabled).
* The system will generate ESP_GMF_AFE_EVT_WAKEUP_START event and remain
* active according to the configured wakeup_time duration.
*
* @param[in] handle Handle to the GMF object
*
* @return
* - ESP_GMF_ERR_OK Success
* - ESP_GMF_ERR_INVALID_ARG Invalid argument
* - ESP_GMF_ERR_INVALID_STATE Element not opened
* - ESP_GMF_ERR_TIMEOUT Command send timeout
*/
esp_gmf_err_t esp_gmf_afe_trigger_wakeup(esp_gmf_element_handle_t handle);
/**
* @brief Manually trigger sleep of wakeup state
*
* @param[in] handle Handle to the GMF object
*
* @return
* - ESP_GMF_ERR_OK Success
* - ESP_GMF_ERR_INVALID_ARG Invalid argument
* - ESP_GMF_ERR_INVALID_STATE Element not opened
* - ESP_GMF_ERR_TIMEOUT Command send timeout
*/
esp_gmf_err_t esp_gmf_afe_trigger_sleep(esp_gmf_element_handle_t handle);
#ifdef __cplusplus
}
#endif /* __cplusplus */
@@ -0,0 +1,243 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO., LTD
* SPDX-License-Identifier: LicenseRef-Espressif-Modified-MIT
*
* See LICENSE file for details.
*/
#pragma once
#include "esp_afe_sr_iface.h"
#include "esp_gmf_err.h"
/**
* The AFE Manager aims to provide users with a simple interface for managing AFE (Audio front end) functions,
* including WakeNet, VAD, AEC, SE, and more
* This component will automatically create feed and fetch tasks,
* users only need to provide data read callback functions and result processing callback functions
* Users can configure `AFE` functions through the `afe_config_t` structure
* The data fed into `AFE` must be in 16-bit PCM format with a sampling rate of 16kHz,
* the number of channels and channel arrangement are determined by the configuration in the `afe_config_init` function,
* for details, please refer to the description of the `afe_config_init` function which provide by `esp-sr`
*/
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
#define ESP_AFE_MANAGER_FEED_TASK_CORE (0)
#define ESP_AFE_MANAGER_FEED_TASK_PRIO (5)
#define ESP_AFE_MANAGER_FEED_TASK_STACK (3 * 1024)
#define ESP_AFE_MANAGER_FETCH_TASK_CORE (1)
#define ESP_AFE_MANAGER_FETCH_TASK_PRIO (5)
#define ESP_AFE_MANAGER_FETCH_TASK_STACK (3 * 1024)
/**
* @enum
* @brief Enumeration of AFE functions
*/
typedef enum {
ESP_AFE_FEATURE_WAKENET, /*!< WakeNet function */
ESP_AFE_FEATURE_VAD, /*!< Voice Activity Detection function */
ESP_AFE_FEATURE_AEC, /*!< Acoustic Echo Cancellation function */
ESP_AFE_FEATURE_SE, /*!< Speech Enhancement function */
} esp_gmf_afe_feature_t;
/**
* @brief Handle for the AFE manager
*/
typedef void *esp_gmf_afe_manager_handle_t;
/**
* @brief Callback function type for processing AFE results
*
* @param[in] result Pointer to the result structure
* @param[in] user_ctx User context to be passed to the callback function
*/
typedef void (*esp_gmf_afe_manager_result_cb_t)(afe_fetch_result_t *result, void *user_ctx);
/**
* @brief Callback type for reading data
*
* @param[in] buffer Pointer to the buffer to read data into
* @param[in] buf_sz Size of the buffer
* @param[in] user_ctx User context to be passed to the callback function
* @param[in] ticks Number of ticks to wait for data
*
* @return
*/
typedef int32_t (*esp_gmf_afe_manager_read_cb_t)(void *buffer, int buf_sz, void *user_ctx, uint32_t ticks);
/**
* @brief Configuration structure for the task setting
*/
typedef struct {
uint32_t stack_size; /*!< Task stack size */
uint8_t core; /*!< Task core id */
uint8_t prio; /*!< Task priority */
} esp_gmf_afe_manager_task_setting_t;
/**
* @brief Configuration structure for the AFE manager
*/
typedef struct {
afe_config_t *afe_cfg; /*!< Configuration of ESP AFE */
esp_gmf_afe_manager_task_setting_t feed_task_setting; /*!< Feed task setting */
esp_gmf_afe_manager_task_setting_t fetch_task_setting; /*!< Fetch task setting */
esp_gmf_afe_manager_read_cb_t read_cb; /*!< Callback function for reading audio data */
void *read_ctx; /*!< Context for the read callback function */
esp_gmf_afe_manager_result_cb_t result_cb; /*!< Callback function for processing AFE results */
void *result_ctx; /*!< Context for the result callback function */
} esp_gmf_afe_manager_cfg_t;
#define DEFAULT_GMF_AFE_MANAGER_CFG(_afe_cfg, _read_cb, _read_ctx, _result_cb, _result_ctx) { \
.afe_cfg = _afe_cfg, \
.feed_task_setting = { \
.stack_size = ESP_AFE_MANAGER_FEED_TASK_STACK, \
.core = ESP_AFE_MANAGER_FEED_TASK_CORE, \
.prio = ESP_AFE_MANAGER_FEED_TASK_PRIO \
}, \
.fetch_task_setting = { \
.stack_size = ESP_AFE_MANAGER_FETCH_TASK_STACK, \
.core = ESP_AFE_MANAGER_FETCH_TASK_CORE, \
.prio = ESP_AFE_MANAGER_FETCH_TASK_PRIO \
}, \
.read_cb = _read_cb, \
.read_ctx = _read_ctx, \
.result_cb = _result_cb, \
.result_ctx = _result_ctx \
}
/**
* @brief GMF AFE Manager Feature Configuration
*
* This structure defines the feature enable states for the AFE manager
* A value of `true` indicates that the feature is enabled, while `false` indicates it is disabled
*/
typedef struct {
bool wakeup; /*!< Wake-up detection */
bool vad; /*!< Voice Activity Detection (VAD) */
bool ns; /*!< Noise Suppression (NS) */
bool aec; /*!< Acoustic Echo Cancellation (AEC) */
bool se; /*!< Speech Enhancement (SE) */
} esp_gmf_afe_manager_features_t;
/**
* @brief Create an AFE Manager instance
*
* @param[in] cfg Pointer to the AFE manager configuration structure
* @param[out] handle Pointer to the created AFE manager handle
*
* @return
* - ESP_GMF_ERR_OK Success
* - ESP_GMF_ERR_FAIL Failed to create the AFE manager
* - ESP_GMF_ERR_MEMORY_LACK Insufficient memory allocation
*/
esp_gmf_err_t esp_gmf_afe_manager_create(esp_gmf_afe_manager_cfg_t *cfg, esp_gmf_afe_manager_handle_t *handle);
/**
* @brief Destroy an AFE Manager instance
*
* @param[in] handle AFE manager handle to be destroyed
*
* @return
* - ESP_GMF_ERR_OK Success
* - ESP_GMF_ERR_INVALID_ARG Invalid handle
*/
esp_gmf_err_t esp_gmf_afe_manager_destroy(esp_gmf_afe_manager_handle_t handle);
/**
* @brief Set the audio input read callback for the AFE Manager
*
* @note If the read callback is set to `NULL`, the AFE Manager will be suspended
*
* @param[in] handle AFE manager handle
* @param[in] read_cb Function pointer to the read callback
* @param[in] read_ctx User-defined context to be passed to the callback
*
* @return
* - ESP_GMF_ERR_OK Success
* - ESP_GMF_ERR_INVALID_ARG Invalid arguments
*/
esp_gmf_err_t esp_gmf_afe_manager_set_read_cb(esp_gmf_afe_manager_handle_t handle, esp_gmf_afe_manager_read_cb_t read_cb, void *read_ctx);
/**
* @brief Register a processing result callback for the AFE Manager
*
* @param[in] handle AFE manager handle
* @param[in] proc_cb Function pointer to the result callback
* @param[in] user_ctx User-defined context to be passed to the callback
*
* @return
* - ESP_GMF_ERR_OK Success
* - ESP_GMF_ERR_INVALID_ARG Invalid arguments
*/
esp_gmf_err_t esp_gmf_afe_manager_set_result_cb(esp_gmf_afe_manager_handle_t handle, esp_gmf_afe_manager_result_cb_t proc_cb, void *user_ctx);
/**
* @brief Suspend or resume the AFE Manager
*
* @param[in] handle AFE manager handle
* @param[in] suspend `true` to suspend, `false` to resume
*
* @return
* - ESP_GMF_ERR_OK Success
* - ESP_GMF_ERR_INVALID_ARG Invalid arguments
*/
esp_gmf_err_t esp_gmf_afe_manager_suspend(esp_gmf_afe_manager_handle_t handle, bool suspend);
/**
* @brief Enable or disable specific features in the AFE Manager
*
* @param[in] handle AFE manager handle
* @param[in] feature Feature to be configured (see `esp_gmf_afe_feature_t`)
* @param[in] enable `true` to enable, `false` to disable
*
* @return
* - ESP_GMF_ERR_OK Success
* - ESP_GMF_ERR_INVALID_ARG Invalid arguments
*/
esp_gmf_err_t esp_gmf_afe_manager_enable_features(esp_gmf_afe_manager_handle_t handle, esp_gmf_afe_feature_t feature, bool enable);
/**
* @brief Retrieve the current feature enable states of the AFE Manager
*
* @param[in] handle AFE manager handle
* @param[out] features Pointer to a structure to store the feature states
*
* @return
* - ESP_GMF_ERR_OK Success
* - ESP_GMF_ERR_INVALID_ARG Invalid arguments
*/
esp_gmf_err_t esp_gmf_afe_manager_get_features(esp_gmf_afe_manager_handle_t handle, esp_gmf_afe_manager_features_t *features);
/**
* @brief Get the processing chunk size for the AFE Manager
*
* @note The chunk size represents the number of audio samples per channel.
* The AFE Manager processes data in fixed-size chunks.
*
* @param[in] handle AFE manager handle
* @param[out] size Pointer to store the chunk size (unit: samples)
*
* @return
* - ESP_GMF_ERR_OK Success
* - ESP_GMF_ERR_INVALID_ARG Invalid arguments
*/
esp_gmf_err_t esp_gmf_afe_manager_get_chunk_size(esp_gmf_afe_manager_handle_t handle, size_t *size);
/**
* @brief Retrieve the number of input channels for the AFE Manager
*
* @param[in] handle AFE manager handle
* @param[out] ch_num Pointer to store the number of channels
*
* @return
* - ESP_GMF_ERR_OK Success
* - ESP_GMF_ERR_INVALID_ARG Invalid arguments
*/
esp_gmf_err_t esp_gmf_afe_manager_get_input_ch_num(esp_gmf_afe_manager_handle_t handle, uint8_t *ch_num);
#ifdef __cplusplus
}
#endif /* __cplusplus */
@@ -0,0 +1,20 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO., LTD
* SPDX-License-Identifier: LicenseRef-Espressif-Modified-MIT
*
* See LICENSE file for details.
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
// AFE Method
#define ESP_GMF_METHOD_AFE_START_VCMD_DET "start_vcmd_det"
#define ESP_GMF_METHOD_AFE_START_VCMD_DET_ARG_EN "vcmd_det"
#ifdef __cplusplus
}
#endif /* __cplusplus */
@@ -0,0 +1,77 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO., LTD
* SPDX-License-Identifier: LicenseRef-Espressif-Modified-MIT
*
* See LICENSE file for details.
*/
#pragma once
#include "esp_gmf_element.h"
#include "esp_gmf_err.h"
#include "esp_wn_iface.h"
#include "model_path.h"
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
/**
* @brief Callback type for WakeNet detection
*
* @param[in] handle Handle to the WakeNet object
* @param[in] trigger_ch The microphone channel that triggered the detection
* @param[in] user_ctx User context passed during initialization
*/
typedef void (*esp_wn_detect_cb_t)(esp_gmf_element_handle_t handle, int32_t trigger_ch, void *user_ctx);
/**
* @brief Configuration structure for WakeNet
*
* @note The input format, same as afe config:
* `M` to represent the microphone channel,
* `R` to represent the playback reference channel,
* `N` to represent an unknown or unused channel
* For example, input_format="MMNR" indicates that the input data consists of four channels,
* which are the microphone channel, the microphone channel, an unused channel, and the playback channel
*/
typedef struct {
srmodel_list_t *models; /*!< Model list containing wake word models */
det_mode_t det_mode; /*!< Detection mode */
char *input_format; /*!< Input format */
esp_wn_detect_cb_t detect_cb; /*!< Detection callback function */
void *user_ctx; /*!< User context to be passed to the callback function */
} esp_gmf_wn_cfg_t;
/**
* @brief Initialize the WakeNet element
*
* @param[in] config Pointer to the configuration structure
* @param[out] handle Pointer to the handle to be initialized
*
* @return
* - ESP_GMF_ERR_OK Success
* - ESP_GMF_ERR_INVALID_ARG Invalid argument
* - ESP_GMF_ERR_MEMORY_LACK Memory allocation failed
* - ESP_GMF_ERR_FAIL Other failures
*/
esp_gmf_err_t esp_gmf_wn_init(esp_gmf_wn_cfg_t *config, esp_gmf_element_handle_t *handle);
/**
* @brief Set the voice trigger detection callback for WakeNet
* This function registers a user-defined callback that will be invoked
* when WakeNet detects a wake word
*
* @param[in] handle Handle to the WakeNet element
* @param[in] detect_cb Callback function to be called on wake word detection
* @param[in] ctx User-defined context to be passed to the callback
*
* @return
* - ESP_GMF_ERR_OK Success
* - ESP_GMF_ERR_INVALID_ARG Invalid argument
*/
esp_gmf_err_t esp_gmf_wn_set_detect_cb(esp_gmf_element_handle_t handle, esp_wn_detect_cb_t detect_cb, void *ctx);
#ifdef __cplusplus
}
#endif /* __cplusplus */
@@ -0,0 +1,56 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO., LTD
* SPDX-License-Identifier: LicenseRef-Espressif-Modified-MIT
*
* See LICENSE file for details.
*/
#pragma once
#include "esp_types.h"
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
/**
* @brief Sort input data according to the input format and convert data layout
*
* @note This function performs two operations:
* 1. Channel sorting: Keeps microphone (M) channels in original order, removes unused (N) channels,
* and places reference (R) channels at the end
* 2. Layout conversion: Converts data from interleaved format (M0[0]R[0]M1[0]N[0]M0[1]R[1]M1[1]N[1]...) to block format (M0[0]M0[1]...M1[0]M1[1]...R[0]R[1]...)
*
* @param[in] input_data Pointer to the interleaved input data buffer
* @param[in] input_format Input format string (e.g., "MMNR" for 2 mic channels, 1 unused, 1 reference)
* @param[in] num_samples Number of samples per channel
* @param[in] num_channels Total number of channels in the input data
* @param[out] sorted_data Pointer to the output buffer for block-formatted sorted data
* Data will be arranged as: [mic_samples][ref_samples]
*/
static inline void esp_gmf_sort_with_format(int16_t *input_data, const char *input_format, int num_samples, int num_channels, int16_t *sorted_data)
{
int mic_index = 0;
int ref_index = 0;
for (int ch = 0; ch < num_channels; ++ch) {
if (input_format[ch] == 'M') {
for (int sample = 0; sample < num_samples; ++sample) {
sorted_data[mic_index++] = input_data[sample * num_channels + ch];
}
}
}
ref_index = mic_index;
for (int ch = 0; ch < num_channels; ++ch) {
if (input_format[ch] == 'R') {
for (int sample = 0; sample < num_samples; ++sample) {
sorted_data[ref_index++] = input_data[sample * num_channels + ch];
}
}
}
}
#ifdef __cplusplus
}
#endif /* __cplusplus */
@@ -0,0 +1,249 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO., LTD
* SPDX-License-Identifier: LicenseRef-Espressif-Modified-MIT
*
* See LICENSE file for details.
*/
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "esp_log.h"
#include "esp_afe_config.h"
#include "esp_afe_aec.h"
#include "esp_gmf_payload.h"
#include "esp_gmf_aec.h"
#include "esp_gmf_audio_element.h"
#include "esp_gmf_err.h"
#include "esp_gmf_job.h"
#include "esp_gmf_node.h"
#include "esp_gmf_oal_mem.h"
#include "esp_gmf_cache.h"
#include "esp_gmf_cap.h"
#include "esp_gmf_caps_def.h"
#define ESP_AEC_BYTES_PER_SAMPLE (sizeof(uint16_t)) // ESP AEC processes data at 16 kHz, 16-bit, and single-channel
typedef struct {
esp_gmf_audio_element_t parent;
afe_aec_handle_t *aec_handle;
uint32_t chunk_size;
uint32_t frame_len;
esp_gmf_cache_t *cache;
esp_gmf_payload_t *in_load;
} gmf_aec_t;
static const char *TAG = "GMF_AEC";
static esp_gmf_err_t gmf_aec_new(void *cfg, esp_gmf_obj_handle_t *handle)
{
ESP_GMF_NULL_CHECK(TAG, cfg, { return ESP_GMF_ERR_INVALID_ARG;});
ESP_GMF_NULL_CHECK(TAG, handle, { return ESP_GMF_ERR_INVALID_ARG;});
*handle = NULL;
esp_gmf_aec_cfg_t *aec_cfg = (esp_gmf_aec_cfg_t *)cfg;
esp_gmf_obj_handle_t new_obj = NULL;
esp_gmf_err_t ret = esp_gmf_aec_init(aec_cfg, &new_obj);
if (ret != ESP_GMF_ERR_OK) {
return ret;
}
*handle = (void *)new_obj;
return ESP_GMF_ERR_OK;
}
static esp_gmf_err_t gmf_aec_received_event_handler(esp_gmf_event_pkt_t *evt, void *ctx)
{
ESP_GMF_NULL_CHECK(TAG, evt, { return ESP_GMF_ERR_INVALID_ARG;});
ESP_GMF_NULL_CHECK(TAG, ctx, { return ESP_GMF_ERR_INVALID_ARG;});
esp_gmf_element_handle_t self = (esp_gmf_element_handle_t)ctx;
esp_gmf_element_handle_t el = evt->from;
esp_gmf_event_state_t state = ESP_GMF_EVENT_STATE_NONE;
esp_gmf_element_get_state(self, &state);
esp_gmf_element_handle_t prev = NULL;
esp_gmf_element_get_prev_el(self, &prev);
if ((state == ESP_GMF_EVENT_STATE_NONE) || (prev == el)) {
if (evt->sub == ESP_GMF_INFO_SOUND) {
esp_gmf_info_sound_t info = {0};
memcpy(&info, evt->payload, evt->payload_size);
ESP_LOGD(TAG, "RECV info, from: %s-%p, next: %p, self: %s-%p, type: %x, state: %s, rate: %d, ch: %d, bits: %d",
OBJ_GET_TAG(el), el, esp_gmf_node_for_next((esp_gmf_node_t *)el), OBJ_GET_TAG(self), self, evt->type,
esp_gmf_event_get_state_str(state), info.sample_rates, info.channels, info.bits);
if (info.sample_rates != 16000 || info.bits != 16) {
ESP_LOGE(TAG, "Unsupported format, rate: %d, bits: %d", info.sample_rates, info.bits);
return ESP_GMF_ERR_NOT_SUPPORT;
}
esp_gmf_element_set_state(self, ESP_GMF_EVENT_STATE_INITIALIZED);
}
}
return ESP_GMF_ERR_OK;
}
static esp_gmf_err_t gmf_aec_destroy(esp_gmf_audio_element_handle_t self)
{
ESP_GMF_NULL_CHECK(TAG, self, { return ESP_GMF_ERR_INVALID_ARG;});
gmf_aec_t *gmf_aec = (gmf_aec_t *)self;
if (OBJ_GET_CFG(self)) {
esp_gmf_oal_free(OBJ_GET_CFG(self));
}
if (gmf_aec->aec_handle) {
afe_aec_destroy(gmf_aec->aec_handle);
gmf_aec->aec_handle = NULL;
}
if (gmf_aec->cache) {
esp_gmf_cache_delete(gmf_aec->cache);
gmf_aec->cache = NULL;
}
ESP_LOGD(TAG, "Destroyed");
esp_gmf_audio_el_deinit(self);
esp_gmf_oal_free(self);
return ESP_GMF_ERR_OK;
}
static esp_gmf_job_err_t gmf_aec_open(esp_gmf_audio_element_handle_t self, void *para)
{
gmf_aec_t *gmf_aec = (gmf_aec_t *)self;
esp_gmf_aec_cfg_t *cfg = OBJ_GET_CFG(self);
gmf_aec->aec_handle = afe_aec_create(cfg->input_format, cfg->filter_len, cfg->type, cfg->mode);
ESP_GMF_NULL_CHECK(TAG, gmf_aec->aec_handle, { return ESP_GMF_JOB_ERR_FAIL;});
gmf_aec->chunk_size = afe_aec_get_chunksize(gmf_aec->aec_handle);
gmf_aec->frame_len = gmf_aec->chunk_size * gmf_aec->aec_handle->pcm_config.total_ch_num * ESP_AEC_BYTES_PER_SAMPLE;
esp_gmf_cache_new(gmf_aec->frame_len, &gmf_aec->cache);
ESP_GMF_NULL_CHECK(TAG, gmf_aec->cache, { return ESP_GMF_JOB_ERR_FAIL;});
ESP_GMF_ELEMENT_IN_PORT_ATTR_SET(ESP_GMF_ELEMENT_GET(self)->in_attr, ESP_GMF_EL_PORT_CAP_SINGLE, 16, 0,
ESP_GMF_PORT_TYPE_BLOCK | ESP_GMF_PORT_TYPE_BYTE, gmf_aec->frame_len);
ESP_LOGI(TAG, "GMF AEC open, frame_len: %lu, nch %d, chunksize %lu", gmf_aec->frame_len, gmf_aec->aec_handle->pcm_config.total_ch_num, gmf_aec->chunk_size);
esp_gmf_info_sound_t snd_info = {0};
snd_info.sample_rates = 16000;
snd_info.bits = 16;
snd_info.channels = 1;
esp_gmf_element_notify_snd_info(self, &snd_info);
return ESP_GMF_JOB_ERR_OK;
}
static esp_gmf_job_err_t gmf_aec_process(esp_gmf_audio_element_handle_t self, void *para)
{
int ret = ESP_GMF_JOB_ERR_OK;
gmf_aec_t *gmf_aec = (gmf_aec_t *)self;
esp_gmf_port_handle_t in_port = ESP_GMF_ELEMENT_GET(self)->in;
esp_gmf_port_handle_t out_port = ESP_GMF_ELEMENT_GET(self)->out;
esp_gmf_payload_t *out_load = NULL;
esp_gmf_payload_t *cache_load = NULL;
esp_gmf_err_io_t load_ret = 0;
bool need_load = false;
esp_gmf_cache_ready_for_load(gmf_aec->cache, &need_load);
if (need_load) {
load_ret = esp_gmf_port_acquire_in(in_port, &gmf_aec->in_load, gmf_aec->frame_len, in_port->wait_ticks);
ESP_GMF_PORT_ACQUIRE_IN_CHECK(TAG, load_ret, ret, { goto __quit;});
esp_gmf_cache_load(gmf_aec->cache, gmf_aec->in_load);
}
esp_gmf_cache_acquire(gmf_aec->cache, gmf_aec->frame_len, &cache_load);
if (cache_load->valid_size != gmf_aec->frame_len) {
if (cache_load->is_done == true) {
ret = ESP_GMF_JOB_ERR_DONE;
} else {
ret = ESP_GMF_JOB_ERR_CONTINUE;
}
ESP_LOGD(TAG, "Return Continue, size:%u", cache_load->valid_size);
goto __quit;
}
load_ret = esp_gmf_port_acquire_out(out_port, &out_load, gmf_aec->chunk_size * ESP_AEC_BYTES_PER_SAMPLE, ESP_GMF_MAX_DELAY);
ESP_GMF_PORT_ACQUIRE_OUT_CHECK(TAG, load_ret, ret, { goto __quit;});
if (out_load->buf_length < gmf_aec->chunk_size * ESP_AEC_BYTES_PER_SAMPLE) {
ret = ESP_GMF_JOB_ERR_FAIL;
ESP_LOGE(TAG, "Output buffer is not enough");
goto __quit;
}
afe_aec_process(gmf_aec->aec_handle, (int16_t *)cache_load->buf, (int16_t *)out_load->buf);
out_load->valid_size = gmf_aec->chunk_size * ESP_AEC_BYTES_PER_SAMPLE;
out_load->is_done = cache_load->is_done;
esp_gmf_cache_ready_for_load(gmf_aec->cache, &need_load);
if (need_load == false) {
ret = ESP_GMF_JOB_ERR_TRUNCATE;
} else {
ret = (out_load->is_done == true ? ESP_GMF_JOB_ERR_DONE : ESP_GMF_JOB_ERR_OK);
}
__quit:
if (out_load) {
esp_gmf_port_release_out(out_port, out_load, ESP_GMF_MAX_DELAY);
}
if (gmf_aec->in_load && (ret != ESP_GMF_JOB_ERR_TRUNCATE)) {
esp_gmf_port_release_in(in_port, gmf_aec->in_load, ESP_GMF_MAX_DELAY);
gmf_aec->in_load = NULL;
}
if (cache_load) {
esp_gmf_cache_release(gmf_aec->cache, cache_load);
}
return ret;
}
static esp_gmf_job_err_t gmf_aec_close(esp_gmf_audio_element_handle_t self, void *para)
{
gmf_aec_t *gmf_aec = (gmf_aec_t *)self;
if (gmf_aec->aec_handle) {
afe_aec_destroy(gmf_aec->aec_handle);
gmf_aec->aec_handle = NULL;
}
if (gmf_aec->cache) {
esp_gmf_cache_delete(gmf_aec->cache);
gmf_aec->cache = NULL;
}
return ESP_GMF_JOB_ERR_OK;
}
static esp_gmf_err_t _load_aec_caps_func(esp_gmf_element_handle_t handle)
{
esp_gmf_cap_t *caps = NULL;
esp_gmf_cap_t aec_caps = {0};
aec_caps.cap_eightcc = ESP_GMF_CAPS_AUDIO_AEC;
aec_caps.attr_fun = NULL;
int ret = esp_gmf_cap_append(&caps, &aec_caps);
ESP_GMF_RET_ON_NOT_OK(TAG, ret, { return ret;}, "Failed to create capability");
esp_gmf_element_t *el = (esp_gmf_element_t *)handle;
el->caps = caps;
return ESP_GMF_ERR_OK;
}
esp_gmf_err_t esp_gmf_aec_init(esp_gmf_aec_cfg_t *config, esp_gmf_obj_handle_t *handle)
{
ESP_GMF_NULL_CHECK(TAG, config, { return ESP_GMF_ERR_INVALID_ARG;});
ESP_GMF_NULL_CHECK(TAG, handle, { return ESP_GMF_ERR_INVALID_ARG;});
*handle = NULL;
gmf_aec_t *gmf_aec = esp_gmf_oal_calloc(1, sizeof(gmf_aec_t));
ESP_GMF_MEM_VERIFY(TAG, gmf_aec, { return ESP_GMF_ERR_MEMORY_LACK;}, "aec", sizeof(gmf_aec_t));
esp_gmf_obj_t *obj = (esp_gmf_obj_t *)gmf_aec;
obj->new_obj = gmf_aec_new;
obj->del_obj = gmf_aec_destroy;
esp_gmf_aec_cfg_t *obj_cfg = esp_gmf_oal_calloc(1, sizeof(esp_gmf_aec_cfg_t));
memcpy(obj_cfg, config, sizeof(esp_gmf_aec_cfg_t));
esp_gmf_err_t ret = esp_gmf_obj_set_config(obj, obj_cfg, sizeof(esp_gmf_aec_cfg_t));
ESP_GMF_RET_ON_NOT_OK(TAG, ret, goto __failed, "Failed set OBJ configuration");
ret = esp_gmf_obj_set_tag(obj, "ai_aec");
ESP_GMF_RET_ON_NOT_OK(TAG, ret, goto __failed, "Failed set OBJ tag");
esp_gmf_element_cfg_t el_cfg = {0};
ESP_GMF_ELEMENT_IN_PORT_ATTR_SET(el_cfg.in_attr, ESP_GMF_EL_PORT_CAP_SINGLE, 16, 0,
ESP_GMF_PORT_TYPE_BLOCK | ESP_GMF_PORT_TYPE_BYTE, 1024);
ESP_GMF_ELEMENT_OUT_PORT_ATTR_SET(el_cfg.out_attr, ESP_GMF_EL_PORT_CAP_SINGLE, 16, 0,
ESP_GMF_PORT_TYPE_BLOCK | ESP_GMF_PORT_TYPE_BYTE, 1024);
el_cfg.dependency = true;
esp_gmf_audio_el_init(gmf_aec, &el_cfg);
ESP_GMF_ELEMENT_GET(gmf_aec)->ops.open = gmf_aec_open;
ESP_GMF_ELEMENT_GET(gmf_aec)->ops.process = gmf_aec_process;
ESP_GMF_ELEMENT_GET(gmf_aec)->ops.close = gmf_aec_close;
ESP_GMF_ELEMENT_GET(gmf_aec)->ops.event_receiver = gmf_aec_received_event_handler;
ESP_GMF_ELEMENT_GET(gmf_aec)->ops.load_caps = _load_aec_caps_func;
*handle = obj;
return ESP_GMF_ERR_OK;
__failed:
esp_gmf_obj_delete(obj);
return ret;
}
@@ -0,0 +1,820 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO., LTD
* SPDX-License-Identifier: LicenseRef-Espressif-Modified-MIT
*
* See LICENSE file for details.
*/
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "esp_gmf_obj.h"
#include "freertos/idf_additions.h"
#include "freertos/queue.h"
#include "esp_log.h"
#include "esp_timer.h"
#include "esp_gmf_audio_element.h"
#include "esp_gmf_err.h"
#include "esp_gmf_job.h"
#include "esp_gmf_new_databus.h"
#include "esp_gmf_oal_mem.h"
#include "esp_wn_iface.h"
#include "esp_mn_iface.h"
#include "esp_mn_models.h"
#include "esp_process_sdkconfig.h"
#include "esp_gmf_afe_manager.h"
#include "esp_gmf_afe.h"
#include "esp_gmf_cap.h"
#include "esp_gmf_caps_def.h"
#include "esp_gmf_method.h"
#include "esp_gmf_ai_audio_methods.h"
#define AFE_DEFAULT_DATA_SIZE (2048)
#define WAKEUP_ST_SET(handle, st) (handle->wakeup_state = st)
/**
* @brief Represents the various states of the wakeup process in the system
*
* This enumeration defines the different states that the system can be in
* during the wakeup and speech processing lifecycle.
*/
typedef enum {
ST_IDLE,
ST_WAKEUP,
ST_SPEECHING,
ST_WAIT_FOR_SLEEP,
} wakeup_state_t;
/**
* @brief Enumeration representing various wakeup events in the system
*
* This enumeration defines the different types of events that can trigger
* the state changing in the system. Each event corresponds to a specific
* condition or state.
*/
typedef enum {
ET_NOISE_DECT,
ET_SPEECH_DECT,
ET_WWE_DECT,
ET_WAKEUP_TIMER_EXPIRED,
ET_KEEP_WAKE_MODIFIED,
ET_TRIGGER_WAKEUP,
ET_TRIGGER_SLEEP,
ET_UNKNOWN
} wakeup_event_t;
typedef struct {
wakeup_event_t event;
union {
struct {
bool enable;
} keep_awake;
struct {
bool state;
} trigger;
} payload;
} afe_evt_msg_t;
/**
* @brief Structure representing the ESP GMF AFE component
*/
typedef struct {
esp_gmf_audio_element_t parent;
esp_gmf_db_handle_t in_db;
esp_gmf_db_handle_t out_db;
wakeup_state_t wakeup_state;
wakeup_event_t last_event;
bool origin_vad_enable;
bool keep_wake;
esp_timer_handle_t wakeup_timer;
SemaphoreHandle_t wake_st_lock;
model_iface_data_t *mn_handle;
bool mn_detecting;
SemaphoreHandle_t mn_lock;
esp_mn_state_t mn_state;
esp_mn_iface_t *multinet;
QueueHandle_t msg_queue;
} esp_gmf_afe_t;
static const char *TAG = "GMF_AFE";
static const char *state_str[] = {
"ST_IDLE",
"ST_WAKEUP",
"ST_SPEECHING",
"ST_WAIT_FOR_SLEEP",
};
static const char *event_str[] = {
"ET_NOISE_DECT",
"ET_SPEECH_DECT",
"ET_WWE_DECT",
"ET_WAKEUP_TIMER_EXPIRED",
"ET_KEEP_WAKE_MODIFIED",
"ET_TRIGGER_WAKEUP",
"ET_TRIGGER_SLEEP",
"ET_UNKNOWN",
};
static wakeup_event_t result_2_event(afe_fetch_result_t *result)
{
if (result->wakeup_state == WAKENET_DETECTED) {
return ET_WWE_DECT;
}
if (result->vad_state == VAD_SILENCE) {
return ET_NOISE_DECT;
}
if (result->vad_state == VAD_SPEECH) {
return ET_SPEECH_DECT;
}
return ET_UNKNOWN;
}
static void event_2_user(esp_gmf_afe_t *gmf_afe, int event, void *event_data, size_t dlen)
{
esp_gmf_afe_cfg_t *cfg = OBJ_GET_CFG(gmf_afe);
if (cfg->event_cb) {
esp_gmf_afe_evt_t afe_event = {
.type = event,
.event_data = event_data,
.data_len = dlen,
};
cfg->event_cb(gmf_afe, &afe_event, cfg->event_ctx);
}
}
static void afe_result_2_wakeup_info(afe_fetch_result_t *result, esp_gmf_afe_wakeup_info_t *info)
{
info->wake_word_index = result->wake_word_index;
info->wakenet_model_index = result->wakenet_model_index;
info->data_volume = result->data_volume;
}
static void wakeup_state_reset(esp_gmf_afe_t *gmf_afe)
{
esp_timer_stop(gmf_afe->wakeup_timer);
WAKEUP_ST_SET(gmf_afe, ST_IDLE);
esp_gmf_afe_manager_features_t feat = { 0 };
esp_gmf_afe_cfg_t *cfg = OBJ_GET_CFG(gmf_afe);
if (cfg && cfg->afe_manager) {
esp_gmf_afe_manager_get_features(cfg->afe_manager, &feat);
if (feat.wakeup && gmf_afe->origin_vad_enable) {
esp_gmf_afe_manager_enable_features(cfg->afe_manager, ESP_AFE_FEATURE_VAD, false);
}
}
}
static void wakeup_timer_start(esp_gmf_afe_t *gmf_afe)
{
esp_gmf_afe_cfg_t *cfg = OBJ_GET_CFG(gmf_afe);
int timeout = 0;
if (gmf_afe->wakeup_state == ST_WAKEUP) {
timeout = cfg->wakeup_time;
} else if (gmf_afe->wakeup_state == ST_WAIT_FOR_SLEEP) {
timeout = cfg->wakeup_end;
}
esp_timer_stop(gmf_afe->wakeup_timer);
if (timeout) {
esp_timer_start_once(gmf_afe->wakeup_timer, timeout * 1000);
}
}
static void wakeup_state_update(esp_gmf_afe_t *gmf_afe, wakeup_event_t event, void *event_data, size_t len)
{
esp_gmf_afe_cfg_t *cfg = OBJ_GET_CFG(gmf_afe);
esp_gmf_afe_manager_features_t afe_feat = {0};
int32_t user_event = -1;
if (event == ET_KEEP_WAKE_MODIFIED || gmf_afe->last_event != event) {
ESP_LOGV(TAG, "Recorder update state, cur %s, event %s", state_str[gmf_afe->wakeup_state], event_str[event]);
gmf_afe->last_event = event;
} else {
return;
}
xSemaphoreTake(gmf_afe->wake_st_lock, portMAX_DELAY);
if ((event == ET_WWE_DECT || event == ET_TRIGGER_WAKEUP) && gmf_afe->wakeup_state != ST_IDLE) {
wakeup_state_reset(gmf_afe);
}
esp_gmf_afe_manager_get_features(cfg->afe_manager, &afe_feat);
switch (gmf_afe->wakeup_state) {
case ST_IDLE: {
if (event == ET_WWE_DECT || event == ET_TRIGGER_WAKEUP) {
WAKEUP_ST_SET(gmf_afe, ST_WAKEUP);
if (!gmf_afe->keep_wake) {
wakeup_timer_start(gmf_afe);
}
if (gmf_afe->origin_vad_enable) {
esp_gmf_afe_manager_enable_features(cfg->afe_manager, ESP_AFE_FEATURE_VAD, true);
}
user_event = ESP_GMF_AFE_EVT_WAKEUP_START;
} else if (event == ET_SPEECH_DECT && afe_feat.wakeup == false) {
WAKEUP_ST_SET(gmf_afe, ST_SPEECHING);
user_event = ESP_GMF_AFE_EVT_VAD_START;
}
break;
}
case ST_WAKEUP: {
if (event == ET_SPEECH_DECT) {
esp_timer_stop(gmf_afe->wakeup_timer);
WAKEUP_ST_SET(gmf_afe, ST_SPEECHING);
user_event = ESP_GMF_AFE_EVT_VAD_START;
} else if (event == ET_KEEP_WAKE_MODIFIED) {
if (gmf_afe->keep_wake) {
esp_timer_stop(gmf_afe->wakeup_timer);
} else {
wakeup_timer_start(gmf_afe);
}
} else if (event == ET_WAKEUP_TIMER_EXPIRED) {
WAKEUP_ST_SET(gmf_afe, ST_IDLE);
esp_timer_stop(gmf_afe->wakeup_timer);
user_event = ESP_GMF_AFE_EVT_WAKEUP_END;
} else if (event == ET_TRIGGER_SLEEP) {
WAKEUP_ST_SET(gmf_afe, ST_IDLE);
esp_timer_stop(gmf_afe->wakeup_timer);
if (gmf_afe->origin_vad_enable) {
esp_gmf_afe_manager_enable_features(cfg->afe_manager, ESP_AFE_FEATURE_VAD, false);
}
user_event = ESP_GMF_AFE_EVT_WAKEUP_END;
}
break;
}
case ST_SPEECHING: {
if (event == ET_NOISE_DECT) {
if (afe_feat.wakeup == true) {
if (!gmf_afe->keep_wake) {
WAKEUP_ST_SET(gmf_afe, ST_WAIT_FOR_SLEEP);
wakeup_timer_start(gmf_afe);
} else {
WAKEUP_ST_SET(gmf_afe, ST_WAKEUP);
}
} else {
WAKEUP_ST_SET(gmf_afe, ST_IDLE);
}
user_event = ESP_GMF_AFE_EVT_VAD_END;
} else if (event == ET_TRIGGER_SLEEP) {
WAKEUP_ST_SET(gmf_afe, ST_IDLE);
if (gmf_afe->origin_vad_enable) {
esp_gmf_afe_manager_enable_features(cfg->afe_manager, ESP_AFE_FEATURE_VAD, false);
}
user_event = ESP_GMF_AFE_EVT_WAKEUP_END;
}
break;
}
case ST_WAIT_FOR_SLEEP: {
if (event == ET_SPEECH_DECT) {
WAKEUP_ST_SET(gmf_afe, ST_SPEECHING);
user_event = ESP_GMF_AFE_EVT_VAD_START;
} else if (event == ET_WAKEUP_TIMER_EXPIRED) {
WAKEUP_ST_SET(gmf_afe, ST_IDLE);
if (gmf_afe->origin_vad_enable) {
esp_gmf_afe_manager_enable_features(cfg->afe_manager, ESP_AFE_FEATURE_VAD, false);
}
user_event = ESP_GMF_AFE_EVT_WAKEUP_END;
} else if (event == ET_KEEP_WAKE_MODIFIED) {
if (gmf_afe->keep_wake) {
esp_timer_stop(gmf_afe->wakeup_timer);
WAKEUP_ST_SET(gmf_afe, ST_WAKEUP);
} else {
wakeup_timer_start(gmf_afe);
}
} else if (event == ET_TRIGGER_SLEEP) {
WAKEUP_ST_SET(gmf_afe, ST_IDLE);
esp_timer_stop(gmf_afe->wakeup_timer);
if (gmf_afe->origin_vad_enable) {
esp_gmf_afe_manager_enable_features(cfg->afe_manager, ESP_AFE_FEATURE_VAD, false);
}
user_event = ESP_GMF_AFE_EVT_WAKEUP_END;
}
break;
}
default:
break;
}
xSemaphoreGive(gmf_afe->wake_st_lock);
if (user_event != -1) {
event_2_user(gmf_afe, user_event, event_data, len);
}
}
static void wakeup_afe_monitor(afe_fetch_result_t *result, void *user_ctx)
{
wakeup_event_t event = result_2_event(result);
if (event == ET_WWE_DECT) {
esp_gmf_afe_wakeup_info_t info = {0};
afe_result_2_wakeup_info(result, &info);
wakeup_state_update(user_ctx, event, &info, sizeof(esp_gmf_afe_wakeup_info_t));
} else {
wakeup_state_update(user_ctx, event, NULL, 0);
}
}
static void wakeup_timer_expired(void *arg)
{
esp_gmf_afe_t *gmf_afe = arg;
wakeup_state_update(gmf_afe, ET_WAKEUP_TIMER_EXPIRED, NULL, 0);
}
static esp_err_t vcmd_det_begin(esp_gmf_afe_t *gmf_afe)
{
if (!gmf_afe) {
return ESP_ERR_INVALID_ARG;
}
xSemaphoreTake(gmf_afe->mn_lock, portMAX_DELAY);
gmf_afe->mn_detecting = true;
gmf_afe->mn_state = ESP_MN_STATE_TIMEOUT;
xSemaphoreGive(gmf_afe->mn_lock);
return ESP_OK;
}
static esp_err_t vcmd_det_cancel(esp_gmf_afe_t *gmf_afe)
{
if (!gmf_afe) {
return ESP_ERR_INVALID_ARG;
}
xSemaphoreTake(gmf_afe->mn_lock, portMAX_DELAY);
gmf_afe->mn_detecting = false;
gmf_afe->mn_state = ESP_MN_STATE_TIMEOUT;
gmf_afe->multinet->clean(gmf_afe->mn_handle);
xSemaphoreGive(gmf_afe->mn_lock);
return ESP_OK;
}
static void mn_afe_monitor(afe_fetch_result_t *result, void *user_ctx)
{
esp_gmf_afe_t *gmf_afe = user_ctx;
if (gmf_afe->mn_detecting) {
if ((gmf_afe->mn_state != ESP_MN_STATE_DETECTING) && result->vad_cache_size) {
uint8_t *cache = (uint8_t *)result->vad_cache;
while (cache < ((uint8_t *)result->vad_cache + result->vad_cache_size)) {
gmf_afe->mn_state = gmf_afe->multinet->detect(gmf_afe->mn_handle, (int16_t *)cache);
if (gmf_afe->mn_state == ESP_MN_STATE_DETECTED) {
goto __proc_state;
}
cache += result->data_size;
}
}
gmf_afe->mn_state = gmf_afe->multinet->detect(gmf_afe->mn_handle, result->data);
__proc_state:
switch (gmf_afe->mn_state) {
case ESP_MN_STATE_DETECTED: {
esp_mn_results_t *mn_result = gmf_afe->multinet->get_results(gmf_afe->mn_handle);
esp_gmf_afe_vcmd_info_t vcmd_info = {
.phrase_id = mn_result->phrase_id[0],
.prob = mn_result->prob[0]};
strlcpy(vcmd_info.str, mn_result->string, sizeof(vcmd_info.str));
event_2_user(gmf_afe, vcmd_info.phrase_id, &vcmd_info, sizeof(esp_gmf_afe_vcmd_info_t));
break;
}
case ESP_MN_STATE_TIMEOUT: {
xSemaphoreTake(gmf_afe->mn_lock, portMAX_DELAY);
gmf_afe->mn_detecting = false;
xSemaphoreGive(gmf_afe->mn_lock);
event_2_user(gmf_afe, ESP_GMF_AFE_EVT_VCMD_DECT_TIMEOUT, NULL, 0);
break;
}
default:
break;
}
}
}
static void esp_gmf_afe_result_proc(afe_fetch_result_t *result, void *user_ctx)
{
esp_gmf_afe_t *gmf_afe = (esp_gmf_afe_t *)user_ctx;
if (result->data_size) {
esp_gmf_afe_manager_features_t feat = {0};
esp_gmf_afe_cfg_t *cfg = OBJ_GET_CFG(gmf_afe);
esp_gmf_afe_manager_get_features(cfg->afe_manager, &feat);
if (feat.wakeup || feat.vad) {
wakeup_afe_monitor(result, user_ctx);
}
if (cfg->vcmd_detect_en) {
mn_afe_monitor(result, user_ctx);
}
uint32_t available_size = 0;
esp_gmf_db_get_available(gmf_afe->out_db, &available_size);
if (available_size < result->data_size) {
esp_gmf_data_bus_block_t blk = {0};
blk.buf_length = result->data_size - available_size;
ESP_GMF_RET_ON_FAIL(TAG, esp_gmf_db_acquire_read(gmf_afe->out_db, &blk, blk.buf_length, ESP_GMF_MAX_DELAY), { return;}, "DB failed to acquire read");
ESP_GMF_RET_ON_FAIL(TAG, esp_gmf_db_release_read(gmf_afe->out_db, &blk, ESP_GMF_MAX_DELAY), { return;}, "DB failed to release read");
}
esp_gmf_data_bus_block_t blk = {0};
ESP_GMF_RET_ON_FAIL(TAG, esp_gmf_db_acquire_write(gmf_afe->out_db, &blk, result->data_size, ESP_GMF_MAX_DELAY), { return;}, "DB failed to acquire write");
blk.buf = (uint8_t *)result->data;
blk.valid_size = result->data_size;
ESP_GMF_RET_ON_FAIL(TAG, esp_gmf_db_release_write(gmf_afe->out_db, &blk, ESP_GMF_MAX_DELAY), { return;}, "DB failed to release write");
}
}
static int32_t esp_gmf_afe_read_cb(void *buffer, int buf_sz, void *user_ctx, TickType_t ticks)
{
esp_gmf_afe_t *gmf_afe = (esp_gmf_afe_t *)user_ctx;
esp_gmf_data_bus_block_t blk = {0};
blk.buf = buffer;
blk.buf_length = buf_sz;
ESP_LOGD(TAG, "Feed %zu", blk.buf_length);
ESP_GMF_RET_ON_FAIL(TAG, esp_gmf_db_acquire_read(gmf_afe->in_db, &blk, buf_sz, ticks), { return 0;}, "DB failed to acquire read");
ESP_GMF_RET_ON_FAIL(TAG, esp_gmf_db_release_read(gmf_afe->in_db, &blk, ticks), { return 0;}, "DB failed to release read");
return buf_sz;
}
static esp_gmf_err_t esp_gmf_afe_new(void *cfg, esp_gmf_obj_handle_t *handle)
{
ESP_GMF_MEM_CHECK(TAG, cfg, { return ESP_GMF_ERR_INVALID_ARG;});
ESP_GMF_MEM_CHECK(TAG, handle, { return ESP_GMF_ERR_INVALID_ARG;});
esp_gmf_afe_cfg_t *gmf_afe_manager_cfg = (esp_gmf_afe_cfg_t *)cfg;
esp_gmf_obj_handle_t new_obj = NULL;
int ret = ESP_GMF_ERR_OK;
ret = esp_gmf_afe_init(gmf_afe_manager_cfg, &new_obj);
if (ret != ESP_GMF_ERR_OK) {
return ret;
}
*handle = (void *)new_obj;
ESP_LOGI(TAG, "New an object,%s-%p", OBJ_GET_TAG(new_obj), new_obj);
return ret;
}
static esp_gmf_err_t esp_gmf_afe_destroy(esp_gmf_audio_element_handle_t self)
{
if (self) {
if (OBJ_GET_CFG(self)) {
esp_gmf_oal_free(OBJ_GET_CFG(self));
}
esp_gmf_audio_el_deinit(self);
esp_gmf_oal_free(self);
}
return ESP_GMF_ERR_OK;
}
static esp_gmf_job_err_t esp_gmf_afe_open(esp_gmf_audio_element_handle_t self, void *para)
{
esp_gmf_afe_t *gmf_afe = (esp_gmf_afe_t *)self;
esp_gmf_afe_cfg_t *cfg = OBJ_GET_CFG(self);
size_t buf_size = 0, chunk_size = 0;
uint8_t ch_num = 0;
esp_gmf_afe_manager_get_input_ch_num(cfg->afe_manager, &ch_num);
esp_gmf_afe_manager_get_chunk_size(cfg->afe_manager, &chunk_size);
buf_size = chunk_size * ch_num * sizeof(uint16_t);
ESP_GMF_ELEMENT_GET(self)->in_attr.data_size = buf_size;
ESP_GMF_ELEMENT_GET(self)->out_attr.data_size = chunk_size * 2;
esp_gmf_afe_manager_features_t feat = {0};
esp_gmf_afe_manager_get_features(cfg->afe_manager, &feat);
if (feat.wakeup || feat.vad) {
gmf_afe->wake_st_lock = xSemaphoreCreateMutex();
esp_timer_create_args_t wakeup_timer_cfg = {
.callback = wakeup_timer_expired,
.arg = gmf_afe,
.dispatch_method = ESP_TIMER_TASK,
.name = "wakeup_timer",
};
esp_timer_create(&wakeup_timer_cfg, &gmf_afe->wakeup_timer);
if (feat.wakeup && feat.vad) {
esp_gmf_afe_manager_enable_features(cfg->afe_manager, ESP_AFE_FEATURE_VAD, false);
gmf_afe->origin_vad_enable = true;
}
}
if (cfg->vcmd_detect_en) {
char *mn_name = esp_srmodel_filter(cfg->models, ESP_MN_PREFIX, cfg->mn_language);
ESP_GMF_NULL_CHECK(TAG, mn_name, return ESP_GMF_JOB_ERR_FAIL);
gmf_afe->multinet = esp_mn_handle_from_name(mn_name);
if (cfg->vcmd_timeout == 0) {
ESP_LOGW(TAG, "Voice command timeout configured as 0, reset to default: 5760 ms");
cfg->vcmd_timeout = 5760;
}
gmf_afe->mn_handle = gmf_afe->multinet->create(mn_name, cfg->vcmd_timeout);
ESP_GMF_NULL_CHECK(TAG, gmf_afe->mn_handle, return ESP_GMF_JOB_ERR_FAIL);
gmf_afe->mn_lock = xSemaphoreCreateMutex();
ESP_GMF_NULL_CHECK(TAG, gmf_afe->mn_lock, return ESP_GMF_JOB_ERR_FAIL);
gmf_afe->mn_state = ESP_MN_STATE_TIMEOUT;
esp_mn_commands_update_from_sdkconfig((esp_mn_iface_t *)gmf_afe->multinet, gmf_afe->mn_handle);
}
esp_gmf_db_new_ringbuf(2, buf_size, &gmf_afe->in_db);
esp_gmf_db_new_ringbuf(1, buf_size * 2 + (cfg->delay_samples * sizeof(uint16_t)), &gmf_afe->out_db);
esp_gmf_afe_manager_set_result_cb(cfg->afe_manager, esp_gmf_afe_result_proc, self);
esp_gmf_afe_manager_set_read_cb(cfg->afe_manager, esp_gmf_afe_read_cb, self);
if (gmf_afe->msg_queue == NULL) {
gmf_afe->msg_queue = xQueueCreate(4, sizeof(afe_evt_msg_t));
ESP_GMF_NULL_CHECK(TAG, gmf_afe->msg_queue, goto __failed);
}
esp_gmf_info_sound_t snd_info = {0};
snd_info.sample_rates = 16000;
snd_info.bits = 16;
snd_info.channels = 1;
esp_gmf_element_notify_snd_info(self, &snd_info);
return ESP_GMF_JOB_ERR_OK;
__failed:
esp_gmf_obj_delete(self);
return ESP_GMF_JOB_ERR_FAIL;
}
static esp_gmf_job_err_t esp_gmf_afe_close(esp_gmf_audio_element_handle_t self, void *para)
{
esp_gmf_afe_t *gmf_afe = (esp_gmf_afe_t *)self;
esp_gmf_afe_cfg_t *cfg = OBJ_GET_CFG(self);
if (cfg->vcmd_detect_en) {
vcmd_det_cancel(gmf_afe);
}
esp_gmf_afe_manager_set_read_cb(cfg->afe_manager, NULL, NULL);
esp_gmf_afe_manager_set_result_cb(cfg->afe_manager, NULL, NULL);
if (gmf_afe->in_db) {
esp_gmf_db_deinit(gmf_afe->in_db);
gmf_afe->in_db = NULL;
}
if (gmf_afe->out_db) {
esp_gmf_db_deinit(gmf_afe->out_db);
gmf_afe->out_db = NULL;
}
if (gmf_afe->wake_st_lock) {
vSemaphoreDelete(gmf_afe->wake_st_lock);
gmf_afe->wake_st_lock = NULL;
}
if (gmf_afe->wakeup_timer) {
esp_timer_stop(gmf_afe->wakeup_timer);
esp_timer_delete(gmf_afe->wakeup_timer);
gmf_afe->wakeup_timer = NULL;
}
if (gmf_afe->mn_lock) {
vSemaphoreDelete(gmf_afe->mn_lock);
gmf_afe->mn_lock = NULL;
}
if (gmf_afe->mn_handle) {
gmf_afe->multinet->destroy(gmf_afe->mn_handle);
gmf_afe->mn_handle = NULL;
}
if (gmf_afe->msg_queue) {
vQueueDelete(gmf_afe->msg_queue);
gmf_afe->msg_queue = NULL;
}
return ESP_GMF_JOB_ERR_OK;
}
static void esp_gmf_afe_cmd_handler(esp_gmf_audio_element_handle_t self)
{
esp_gmf_afe_t *gmf_afe = (esp_gmf_afe_t *)self;
if (gmf_afe->msg_queue) {
afe_evt_msg_t msg;
while (xQueueReceive(gmf_afe->msg_queue, &msg, 0) == pdTRUE) {
switch (msg.event) {
case ET_KEEP_WAKE_MODIFIED: {
xSemaphoreTake(gmf_afe->wake_st_lock, portMAX_DELAY);
gmf_afe->keep_wake = msg.payload.keep_awake.enable;
xSemaphoreGive(gmf_afe->wake_st_lock);
wakeup_state_update(gmf_afe, msg.event, NULL, 0);
break;
}
case ET_TRIGGER_WAKEUP:
case ET_TRIGGER_SLEEP: {
wakeup_state_update(gmf_afe, msg.event, NULL, 0);
break;
}
default:
break;
}
}
}
}
static esp_gmf_job_err_t esp_gmf_afe_proc(esp_gmf_audio_element_handle_t self, void *para)
{
int ret = 0;
esp_gmf_port_handle_t in_port = ESP_GMF_ELEMENT_GET(self)->in;
esp_gmf_port_handle_t out_port = ESP_GMF_ELEMENT_GET(self)->out;
esp_gmf_payload_t *in_load = NULL;
esp_gmf_payload_t *out_load = NULL;
esp_gmf_afe_t *gmf_afe = (esp_gmf_afe_t *)self;
esp_gmf_afe_cfg_t *cfg = OBJ_GET_CFG(self);
esp_gmf_afe_cmd_handler(self);
ret = esp_gmf_port_acquire_in(in_port, &in_load, ESP_GMF_ELEMENT_GET(self)->in_attr.data_size, ESP_GMF_MAX_DELAY);
if (ret < 0) {
ESP_LOGE(TAG, "Read data error, ret:%d, line:%d", ret, __LINE__);
return ret == ESP_GMF_IO_ABORT ? ESP_GMF_JOB_ERR_OK : ESP_GMF_JOB_ERR_FAIL;
}
esp_gmf_data_bus_block_t blk = {0};
esp_gmf_db_acquire_write(gmf_afe->in_db, &blk, in_load->valid_size, ESP_GMF_MAX_DELAY);
blk.buf = in_load->buf;
blk.valid_size = in_load->valid_size;
esp_gmf_db_release_write(gmf_afe->in_db, &blk, ESP_GMF_MAX_DELAY);
uint32_t filled_size = 0;
esp_gmf_db_get_filled_size(gmf_afe->out_db, &filled_size);
if (filled_size > (cfg->delay_samples * sizeof(uint16_t))) {
uint32_t rsize = filled_size - (cfg->delay_samples * sizeof(uint16_t));
esp_gmf_port_acquire_out(out_port, &out_load, rsize, ESP_GMF_MAX_DELAY);
esp_gmf_data_bus_block_t blk = {0};
blk.buf = out_load->buf;
blk.buf_length = rsize;
esp_gmf_db_acquire_read(gmf_afe->out_db, &blk, rsize, ESP_GMF_MAX_DELAY);
esp_gmf_db_release_read(gmf_afe->out_db, &blk, ESP_GMF_MAX_DELAY);
out_load->valid_size = rsize;
esp_gmf_port_release_out(out_port, out_load, ESP_GMF_MAX_DELAY);
ret = ESP_GMF_JOB_ERR_OK;
} else {
ret = ESP_GMF_JOB_ERR_CONTINUE;
}
if (in_load->is_done) {
ret = ESP_GMF_JOB_ERR_DONE;
}
esp_gmf_port_release_in(in_port, in_load, ESP_GMF_MAX_DELAY);
return ret;
}
static esp_gmf_err_t _load_afe_caps_func(esp_gmf_element_handle_t handle)
{
esp_gmf_cap_t *caps = NULL;
esp_gmf_cap_t afe_caps = {0};
afe_caps.cap_eightcc = ESP_GMF_CAPS_AUDIO_AEC;
afe_caps.attr_fun = NULL;
int ret = esp_gmf_cap_append(&caps, &afe_caps);
ESP_GMF_RET_ON_NOT_OK(TAG, ret, { return ret;}, "Failed to create AEC capability");
afe_caps.cap_eightcc = ESP_GMF_CAPS_AUDIO_AGC;
ret = esp_gmf_cap_append(&caps, &afe_caps);
ESP_GMF_RET_ON_NOT_OK(TAG, ret, { return ret;}, "Failed to create AGC capability");
afe_caps.cap_eightcc = ESP_GMF_CAPS_AUDIO_NS;
afe_caps.attr_fun = NULL;
ret = esp_gmf_cap_append(&caps, &afe_caps);
ESP_GMF_RET_ON_NOT_OK(TAG, ret, { return ret;}, "Failed to create NS capability");
afe_caps.cap_eightcc = ESP_GMF_CAPS_AUDIO_VAD;
afe_caps.attr_fun = NULL;
ret = esp_gmf_cap_append(&caps, &afe_caps);
ESP_GMF_RET_ON_NOT_OK(TAG, ret, { return ret;}, "Failed to create VAD capability");
afe_caps.cap_eightcc = ESP_GMF_CAPS_AUDIO_WWE;
afe_caps.attr_fun = NULL;
ret = esp_gmf_cap_append(&caps, &afe_caps);
ESP_GMF_RET_ON_NOT_OK(TAG, ret, { return ret;}, "Failed to create WWE capability");
afe_caps.cap_eightcc = ESP_GMF_CAPS_AUDIO_VCMD;
afe_caps.attr_fun = NULL;
ret = esp_gmf_cap_append(&caps, &afe_caps);
ESP_GMF_RET_ON_NOT_OK(TAG, ret, { return ret;}, "Failed to create VCMD capability");
esp_gmf_element_t *el = (esp_gmf_element_t *)handle;
el->caps = caps;
return ESP_GMF_ERR_OK;
}
static esp_gmf_err_t __afe_set_vcmd_det(esp_gmf_audio_element_handle_t handle, esp_gmf_args_desc_t *arg_desc,
uint8_t *buf, int buf_len)
{
ESP_GMF_NULL_CHECK(TAG, handle, {return ESP_GMF_ERR_INVALID_ARG;});
ESP_GMF_NULL_CHECK(TAG, arg_desc, {return ESP_GMF_ERR_INVALID_ARG;});
ESP_GMF_NULL_CHECK(TAG, buf, {return ESP_GMF_ERR_INVALID_ARG;});
esp_gmf_afe_t *gmf_afe = (esp_gmf_afe_t *)handle;
esp_gmf_afe_cfg_t *cfg = OBJ_GET_CFG(gmf_afe);
uint8_t vcmd_det_start = buf[0];
if (cfg->vcmd_detect_en) {
if (vcmd_det_start) {
vcmd_det_begin(gmf_afe);
} else {
vcmd_det_cancel(gmf_afe);
}
return ESP_GMF_ERR_OK;
}
return ESP_GMF_ERR_INVALID_STATE;
}
static esp_gmf_err_t _load_afe_methods_func(esp_gmf_element_handle_t handle)
{
esp_gmf_method_t *method = NULL;
esp_gmf_args_desc_t *set_args = NULL;
esp_gmf_err_t ret = esp_gmf_args_desc_append(&set_args, ESP_GMF_METHOD_AFE_START_VCMD_DET_ARG_EN,
ESP_GMF_ARGS_TYPE_UINT8, sizeof(uint8_t), 0);
ESP_GMF_RET_ON_NOT_OK(TAG, ret, {return ret;}, "Failed to append vcmd det argument");
ret = esp_gmf_method_append(&method, ESP_GMF_METHOD_AFE_START_VCMD_DET, __afe_set_vcmd_det, set_args);
ESP_GMF_RET_ON_ERROR(TAG, ret, {return ret;}, "Failed to register %s method", ESP_GMF_METHOD_AFE_START_VCMD_DET);
esp_gmf_element_t *el = (esp_gmf_element_t *)handle;
el->method = method;
return ESP_GMF_ERR_OK;
}
esp_gmf_err_t esp_gmf_afe_init(void *config, esp_gmf_obj_handle_t *handle)
{
ESP_GMF_MEM_CHECK(TAG, config, { return ESP_GMF_ERR_INVALID_ARG;});
ESP_GMF_MEM_CHECK(TAG, handle, { return ESP_GMF_ERR_INVALID_ARG;});
ESP_GMF_NULL_CHECK(TAG, ((esp_gmf_afe_cfg_t *)config)->afe_manager, { return ESP_GMF_ERR_INVALID_ARG;})
esp_gmf_afe_t *gmf_afe = esp_gmf_oal_calloc(1, sizeof(esp_gmf_afe_t));
ESP_GMF_MEM_CHECK(TAG, gmf_afe, { return ESP_GMF_ERR_MEMORY_LACK;});
esp_gmf_obj_t *obj = (esp_gmf_obj_t *)gmf_afe;
obj->new_obj = esp_gmf_afe_new;
obj->del_obj = esp_gmf_afe_destroy;
esp_gmf_afe_cfg_t *obj_cfg = esp_gmf_oal_calloc(1, sizeof(esp_gmf_afe_cfg_t));
memcpy(obj_cfg, config, sizeof(esp_gmf_afe_cfg_t));
esp_gmf_obj_set_config(obj, obj_cfg, sizeof(esp_gmf_afe_cfg_t));
int ret = esp_gmf_obj_set_tag(obj, "ai_afe");
ESP_GMF_RET_ON_NOT_OK(TAG, ret, goto __failed, "Failed set OBJ tag");
esp_gmf_element_cfg_t el_cfg = {0};
ESP_GMF_ELEMENT_IN_PORT_ATTR_SET(el_cfg.in_attr, ESP_GMF_EL_PORT_CAP_SINGLE, 16, 0,
ESP_GMF_PORT_TYPE_BLOCK | ESP_GMF_PORT_TYPE_BYTE, AFE_DEFAULT_DATA_SIZE);
ESP_GMF_ELEMENT_OUT_PORT_ATTR_SET(el_cfg.out_attr, ESP_GMF_EL_PORT_CAP_SINGLE, 16, 0,
ESP_GMF_PORT_TYPE_BLOCK | ESP_GMF_PORT_TYPE_BYTE, AFE_DEFAULT_DATA_SIZE);
el_cfg.dependency = false;
ret = esp_gmf_audio_el_init(gmf_afe, &el_cfg);
ESP_GMF_RET_ON_NOT_OK(TAG, ret, goto __failed, "Failed to initialize audio element");
gmf_afe->last_event = ET_UNKNOWN;
ESP_GMF_ELEMENT_GET(gmf_afe)->ops.open = esp_gmf_afe_open;
ESP_GMF_ELEMENT_GET(gmf_afe)->ops.process = esp_gmf_afe_proc;
ESP_GMF_ELEMENT_GET(gmf_afe)->ops.close = esp_gmf_afe_close;
ESP_GMF_ELEMENT_GET(gmf_afe)->ops.load_caps = _load_afe_caps_func;
ESP_GMF_ELEMENT_GET(gmf_afe)->ops.load_methods = _load_afe_methods_func;
*handle = obj;
ESP_LOGI(TAG, "Create AFE, %s-%p", OBJ_GET_TAG(obj), obj);
return ESP_GMF_ERR_OK;
__failed:
esp_gmf_afe_destroy(gmf_afe);
return ESP_GMF_ERR_FAIL;
}
static esp_gmf_err_t esp_gmf_afe_set_vcmd_detection(esp_gmf_element_handle_t handle, bool enable)
{
ESP_GMF_NULL_CHECK(TAG, handle, {return ESP_GMF_ERR_INVALID_ARG;});
const esp_gmf_method_t *method_head = NULL;
const esp_gmf_method_t *method = NULL;
esp_gmf_element_get_method((esp_gmf_element_handle_t)handle, &method_head);
esp_gmf_method_found(method_head, ESP_GMF_METHOD_AFE_START_VCMD_DET, &method);
uint8_t buf[1] = { enable };
esp_gmf_args_set_value(method->args_desc, ESP_GMF_METHOD_AFE_START_VCMD_DET_ARG_EN, buf, buf, sizeof(uint8_t));
return esp_gmf_element_exe_method((esp_gmf_element_handle_t)handle, ESP_GMF_METHOD_AFE_START_VCMD_DET, buf, sizeof(buf));
}
esp_gmf_err_t esp_gmf_afe_vcmd_detection_begin(esp_gmf_element_handle_t handle)
{
return esp_gmf_afe_set_vcmd_detection(handle, true);
}
esp_gmf_err_t esp_gmf_afe_vcmd_detection_cancel(esp_gmf_element_handle_t handle)
{
return esp_gmf_afe_set_vcmd_detection(handle, false);
}
esp_gmf_err_t esp_gmf_afe_set_event_cb(esp_gmf_element_handle_t handle, esp_gmf_afe_event_cb_t cb, void *ctx)
{
ESP_GMF_NULL_CHECK(TAG, handle, return ESP_GMF_ERR_INVALID_ARG;);
esp_gmf_afe_cfg_t *cfg = OBJ_GET_CFG(handle);
ESP_GMF_NULL_CHECK(TAG, cfg, return ESP_GMF_ERR_INVALID_STATE;)
cfg->event_cb = cb;
cfg->event_ctx = ctx;
return ESP_GMF_ERR_OK;
}
esp_gmf_err_t esp_gmf_afe_keep_awake(esp_gmf_element_handle_t handle, bool enable)
{
ESP_GMF_NULL_CHECK(TAG, handle, return ESP_GMF_ERR_INVALID_ARG;);
esp_gmf_afe_t *gmf_afe = (esp_gmf_afe_t *)handle;
if (gmf_afe->msg_queue == NULL) {
ESP_LOGE(TAG, "GMF AFE Message queue not initialized");
return ESP_GMF_ERR_INVALID_STATE;
}
afe_evt_msg_t msg = { 0 };
msg.event = ET_KEEP_WAKE_MODIFIED;
msg.payload.keep_awake.enable = enable;
BaseType_t ok = xQueueSend(gmf_afe->msg_queue, &msg, pdMS_TO_TICKS(1000));
return ok == pdTRUE ? ESP_GMF_ERR_OK : ESP_GMF_ERR_TIMEOUT;
}
esp_gmf_err_t esp_gmf_afe_trigger_wakeup(esp_gmf_element_handle_t handle)
{
ESP_GMF_NULL_CHECK(TAG, handle, return ESP_GMF_ERR_INVALID_ARG;);
esp_gmf_afe_t *gmf_afe = (esp_gmf_afe_t *)handle;
if (gmf_afe->msg_queue == NULL) {
ESP_LOGE(TAG, "GMF AFE Message queue not initialized");
return ESP_GMF_ERR_INVALID_STATE;
}
afe_evt_msg_t msg = { 0 };
msg.event = ET_TRIGGER_WAKEUP;
msg.payload.trigger.state = true;
BaseType_t ok = xQueueSend(gmf_afe->msg_queue, &msg, pdMS_TO_TICKS(1000));
return ok == pdTRUE ? ESP_GMF_ERR_OK : ESP_GMF_ERR_TIMEOUT;
}
esp_gmf_err_t esp_gmf_afe_trigger_sleep(esp_gmf_element_handle_t handle)
{
ESP_GMF_NULL_CHECK(TAG, handle, return ESP_GMF_ERR_INVALID_ARG;);
esp_gmf_afe_t *gmf_afe = (esp_gmf_afe_t *)handle;
if (gmf_afe->msg_queue == NULL) {
ESP_LOGE(TAG, "GMF AFE Message queue not initialized");
return ESP_GMF_ERR_INVALID_STATE;
}
afe_evt_msg_t msg = { 0 };
msg.event = ET_TRIGGER_SLEEP;
msg.payload.trigger.state = false;
BaseType_t ok = xQueueSend(gmf_afe->msg_queue, &msg, pdMS_TO_TICKS(1000));
return ok == pdTRUE ? ESP_GMF_ERR_OK : ESP_GMF_ERR_TIMEOUT;
}
@@ -0,0 +1,372 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO., LTD
* SPDX-License-Identifier: LicenseRef-Espressif-Modified-MIT
*
* See LICENSE file for details.
*/
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "freertos/portmacro.h"
#include "freertos/task.h"
#include "freertos/idf_additions.h"
#include "freertos/semphr.h"
#include "esp_private/freertos_idf_additions_priv.h"
#include "esp_afe_sr_models.h"
#include "esp_check.h"
#include "esp_err.h"
#include "esp_heap_caps.h"
#include "esp_gmf_afe_manager.h"
#define AFE_RUN_EVENT (BIT0)
#define AFE_DESTROYING_EVENT (BIT1)
#define FEED_TASK_DESTROYED (BIT2)
#define FETCH_TASK_DESTROYED (BIT3)
/**
* @brief Information about an AFE (Audio Front-End) task.
*/
typedef struct {
TaskHandle_t task; /*!< Task handle */
bool running; /*!< Task running status */
} afe_task_info_t;
/**
* @brief AFE (Audio Front-End) manager.
*/
typedef struct __afe {
afe_task_info_t feed; /*!< Feed task information */
afe_task_info_t fetch; /*!< Fetch task information */
esp_gmf_afe_manager_read_cb_t read_cb; /*!< Read callback function */
void *read_ctx; /*!< Read callback context */
esp_afe_sr_data_t *afe_data; /*!< AFE data */
esp_gmf_afe_manager_result_cb_t result_proc; /*!< Result processing callback function */
void *result_ctx; /*!< Result processing callback context */
esp_gmf_afe_manager_features_t feat; /*!< AFE feature state */
EventGroupHandle_t ctrl_events; /*!< Control events */
SemaphoreHandle_t read_cb_lock; /*!< Mutex for read callback */
SemaphoreHandle_t result_cb_lock;/*!< Mutex for result callback */
esp_afe_sr_iface_t *esp_afe; /*!< AFE interface */
} esp_gmf_afe_manager_t;
static const char *TAG = "AFE_MANAGER";
static void feed_task(void *arg)
{
esp_gmf_afe_manager_t *afe_manager = (esp_gmf_afe_manager_t *)arg;
int chan_num = afe_manager->esp_afe->get_feed_channel_num(afe_manager->afe_data);
int chunksize = afe_manager->esp_afe->get_feed_chunksize(afe_manager->afe_data);
const int buf_size = chunksize * chan_num * sizeof(uint16_t);
ESP_LOGI(TAG, "Feed task, ch %d, chunk %d, buf size %d", chan_num, chunksize, buf_size);
int16_t *buf = heap_caps_calloc_prefer(1, buf_size, 2, MALLOC_CAP_SPIRAM, MALLOC_CAP_INTERNAL);
if (buf == NULL) {
ESP_LOGE(TAG, "Feed task calloc failed, task quit");
goto __quit;
}
afe_manager->feed.running = true;
while (afe_manager->feed.running) {
EventBits_t bits = xEventGroupWaitBits(afe_manager->ctrl_events, AFE_RUN_EVENT | AFE_DESTROYING_EVENT, false, false, portMAX_DELAY);
if (bits & AFE_DESTROYING_EVENT) {
break;
}
int rlen = 0;
xSemaphoreTake(afe_manager->read_cb_lock, portMAX_DELAY);
if (afe_manager->read_cb) {
rlen = afe_manager->read_cb(buf, buf_size, afe_manager->read_ctx, pdMS_TO_TICKS(500));
}
xSemaphoreGive(afe_manager->read_cb_lock);
if (rlen == buf_size) {
afe_manager->esp_afe->feed(afe_manager->afe_data, buf);
} else {
ESP_LOGE(TAG, "AFE read failed %d", rlen);
}
}
__quit:
if (buf) {
heap_caps_free(buf);
}
xEventGroupSetBits(afe_manager->ctrl_events, FEED_TASK_DESTROYED);
vTaskDelete(NULL);
}
static void fetch_task(void *arg)
{
esp_gmf_afe_manager_t *afe_manager = (esp_gmf_afe_manager_t *)arg;
afe_manager->fetch.running = true;
while (afe_manager->fetch.running) {
EventBits_t bits = xEventGroupWaitBits(afe_manager->ctrl_events, AFE_RUN_EVENT | AFE_DESTROYING_EVENT, false, false, portMAX_DELAY);
if (bits & AFE_DESTROYING_EVENT) {
break;
}
afe_fetch_result_t *result = afe_manager->esp_afe->fetch(afe_manager->afe_data);
xSemaphoreTake(afe_manager->result_cb_lock, portMAX_DELAY);
if (afe_manager->result_proc != NULL) {
afe_manager->result_proc(result, afe_manager->result_ctx);
}
xSemaphoreGive(afe_manager->result_cb_lock);
}
xEventGroupSetBits(afe_manager->ctrl_events, FETCH_TASK_DESTROYED);
vTaskDelete(NULL);
}
esp_gmf_err_t esp_gmf_afe_manager_set_result_cb(esp_gmf_afe_manager_handle_t handle, esp_gmf_afe_manager_result_cb_t proc, void *ctx)
{
esp_gmf_afe_manager_t *afe_manager = (esp_gmf_afe_manager_t *)handle;
ESP_RETURN_ON_FALSE(afe_manager, ESP_GMF_ERR_INVALID_ARG, TAG, "AFE set result cb invalid handle");
xSemaphoreTake(afe_manager->result_cb_lock, portMAX_DELAY);
afe_manager->result_proc = proc;
afe_manager->result_ctx = ctx;
xSemaphoreGive(afe_manager->result_cb_lock);
return ESP_GMF_ERR_OK;
}
esp_gmf_err_t esp_gmf_afe_manager_destroy(esp_gmf_afe_manager_handle_t handle)
{
esp_gmf_afe_manager_t *afe_manager = (esp_gmf_afe_manager_t *)handle;
ESP_RETURN_ON_FALSE(afe_manager, ESP_GMF_ERR_INVALID_ARG, TAG, "AFE destroy: handle NULL");
xEventGroupSetBits(afe_manager->ctrl_events, AFE_DESTROYING_EVENT);
EventBits_t wait_bits = 0;
if (afe_manager->feed.task) {
afe_manager->feed.running = false;
wait_bits |= FEED_TASK_DESTROYED;
}
if (afe_manager->fetch.task) {
afe_manager->fetch.running = false;
wait_bits |= FETCH_TASK_DESTROYED;
}
EventBits_t bits = xEventGroupWaitBits(afe_manager->ctrl_events, wait_bits, true, true, portMAX_DELAY);
if ((bits & wait_bits) != wait_bits) {
ESP_LOGE(TAG, "AFE destroy wait bits timeout %" PRIu32, bits);
return ESP_GMF_ERR_TIMEOUT;
}
if (afe_manager->ctrl_events) {
vEventGroupDelete(afe_manager->ctrl_events);
}
if (afe_manager->read_cb_lock) {
vSemaphoreDelete(afe_manager->read_cb_lock);
}
if (afe_manager->result_cb_lock) {
vSemaphoreDelete(afe_manager->result_cb_lock);
}
if (afe_manager->afe_data) {
afe_manager->esp_afe->destroy(afe_manager->afe_data);
afe_manager->afe_data = NULL;
}
heap_caps_free(afe_manager);
ESP_LOGI(TAG, "AFE manager destroy");
return ESP_GMF_ERR_OK;
}
esp_gmf_err_t esp_gmf_afe_manager_create(esp_gmf_afe_manager_cfg_t *cfg, esp_gmf_afe_manager_handle_t *handle)
{
esp_gmf_err_t ret = ESP_GMF_ERR_OK;
*handle = NULL;
ESP_RETURN_ON_FALSE(cfg != NULL, ESP_GMF_ERR_INVALID_ARG, TAG, "AFE manager create: cfg NULL");
ESP_RETURN_ON_FALSE(handle != NULL, ESP_GMF_ERR_INVALID_ARG, TAG, "AFE manager create: handle NULL");
ESP_RETURN_ON_FALSE(cfg->afe_cfg != NULL, ESP_GMF_ERR_INVALID_ARG, TAG, "AFE manager create: cfg->afe_cfg NULL");
esp_gmf_afe_manager_t *afe_manager = heap_caps_calloc_prefer(1, sizeof(esp_gmf_afe_manager_t), 2, MALLOC_CAP_SPIRAM, MALLOC_CAP_INTERNAL);
ESP_GOTO_ON_FALSE(afe_manager, ESP_GMF_ERR_MEMORY_LACK, __err, TAG, "AFE manager create no memory");
afe_manager->read_cb = cfg->read_cb;
afe_manager->read_ctx = cfg->read_ctx;
afe_config_print(cfg->afe_cfg);
afe_manager->esp_afe = esp_afe_handle_from_config(cfg->afe_cfg);
afe_manager->afe_data = afe_manager->esp_afe->create_from_config(cfg->afe_cfg);
ESP_GOTO_ON_FALSE(afe_manager->afe_data, ESP_GMF_ERR_FAIL, __err, TAG, "AFE manager create failed");
afe_manager->ctrl_events = xEventGroupCreate();
ESP_GOTO_ON_FALSE(afe_manager->ctrl_events, ESP_GMF_ERR_MEMORY_LACK, __err, TAG, "AFE manager create events failed");
afe_manager->read_cb_lock = xSemaphoreCreateMutex();
ESP_GOTO_ON_FALSE(afe_manager->read_cb_lock, ESP_GMF_ERR_MEMORY_LACK, __err, TAG, "AFE manager create mutex failed");
afe_manager->result_cb_lock = xSemaphoreCreateMutex();
ESP_GOTO_ON_FALSE(afe_manager->result_cb_lock, ESP_GMF_ERR_MEMORY_LACK, __err, TAG, "AFE manager create mutex failed");
if (afe_manager->read_cb) {
xEventGroupSetBits(afe_manager->ctrl_events, AFE_RUN_EVENT);
}
#if (configSUPPORT_STATIC_ALLOCATION == 1) && defined(CONFIG_SPIRAM_BOOT_INIT)
prvTaskCreateDynamicPinnedToCoreWithCaps(feed_task,
"afe_feed",
cfg->feed_task_setting.stack_size,
afe_manager,
cfg->feed_task_setting.prio,
cfg->feed_task_setting.core,
(MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT),
&afe_manager->feed.task);
ESP_GOTO_ON_FALSE(afe_manager->feed.task, ESP_GMF_ERR_MEMORY_LACK, __err, TAG, "Create afe feed task failed");
prvTaskCreateDynamicPinnedToCoreWithCaps(fetch_task,
"afe_fetch",
cfg->fetch_task_setting.stack_size,
afe_manager,
cfg->fetch_task_setting.prio,
cfg->fetch_task_setting.core,
(MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT),
&afe_manager->fetch.task);
ESP_GOTO_ON_FALSE(afe_manager->fetch.task, ESP_GMF_ERR_MEMORY_LACK, __err, TAG, "Create afe fetch task failed");
#else
ESP_LOGW(TAG, "The AFE Manager may not work when PSRAM is disabled");
xTaskCreatePinnedToCore(feed_task,
"afe_feed",
cfg->feed_task_setting.stack_size,
afe_manager,
cfg->feed_task_setting.prio,
&afe_manager->feed.task,
cfg->feed_task_setting.core);
ESP_GOTO_ON_FALSE(afe_manager->feed.task, ESP_GMF_ERR_MEMORY_LACK, __err, TAG, "Create afe feed task failed");
xTaskCreatePinnedToCore(fetch_task,
"afe_fetch",
cfg->fetch_task_setting.stack_size,
afe_manager,
cfg->fetch_task_setting.prio,
&afe_manager->fetch.task,
cfg->fetch_task_setting.core);
ESP_GOTO_ON_FALSE(afe_manager->fetch.task, ESP_GMF_ERR_MEMORY_LACK, __err, TAG, "Create afe fetch task failed");
#endif /* (configSUPPORT_STATIC_ALLOCATION == 1) */
afe_manager->feat.wakeup = cfg->afe_cfg->wakenet_init;
afe_manager->feat.aec = cfg->afe_cfg->aec_init;
afe_manager->feat.se = cfg->afe_cfg->se_init;
afe_manager->feat.vad = (cfg->afe_cfg->vad_init);
*handle = afe_manager;
return ret;
__err:
if (afe_manager) {
esp_gmf_afe_manager_destroy(afe_manager);
}
return ret;
}
esp_gmf_err_t esp_gmf_afe_manager_get_features(esp_gmf_afe_manager_handle_t handle, esp_gmf_afe_manager_features_t *features)
{
ESP_RETURN_ON_FALSE(handle, ESP_GMF_ERR_INVALID_ARG, TAG, "AFE get feature: handle NULL");
ESP_RETURN_ON_FALSE(features, ESP_GMF_ERR_INVALID_ARG, TAG, "AFE get feature: features NULL");
esp_gmf_afe_manager_t *afe_manager = (esp_gmf_afe_manager_t *)handle;
memcpy(features, &(afe_manager->feat), sizeof(esp_gmf_afe_manager_features_t));
return ESP_GMF_ERR_OK;
}
esp_gmf_err_t esp_gmf_afe_manager_suspend(esp_gmf_afe_manager_handle_t handle, bool suspend)
{
esp_gmf_afe_manager_t *afe_manager = (esp_gmf_afe_manager_t *)handle;
ESP_RETURN_ON_FALSE(afe_manager, ESP_GMF_ERR_INVALID_ARG, TAG, "AFE suspend: handle NULL");
ESP_LOGI(TAG, "AFE manager suspend %d", suspend);
if (suspend) {
xEventGroupClearBits(afe_manager->ctrl_events, AFE_RUN_EVENT);
} else {
xEventGroupSetBits(afe_manager->ctrl_events, AFE_RUN_EVENT);
}
return ESP_GMF_ERR_OK;
}
esp_gmf_err_t esp_gmf_afe_manager_enable_features(esp_gmf_afe_manager_handle_t handle, esp_gmf_afe_feature_t feature, bool enable)
{
esp_gmf_afe_manager_t *afe_manager = (esp_gmf_afe_manager_t *)handle;
ESP_RETURN_ON_FALSE(afe_manager, ESP_GMF_ERR_INVALID_ARG, TAG, "AFE suspend: handle NULL");
esp_gmf_err_t ret = ESP_GMF_ERR_OK;
ESP_LOGD(TAG, "AFE Ctrl [%u, %d]", feature, enable);
switch (feature) {
case ESP_AFE_FEATURE_WAKENET: {
if (enable) {
ret = afe_manager->esp_afe->enable_wakenet(afe_manager->afe_data);
} else {
ret = afe_manager->esp_afe->disable_wakenet(afe_manager->afe_data);
}
ESP_LOGD(TAG, "Wakenet ctrl ret %d", ret);
if (ret >= 0) {
afe_manager->feat.wakeup = ret;
}
break;
}
case ESP_AFE_FEATURE_AEC: {
if (enable) {
ret = afe_manager->esp_afe->enable_aec(afe_manager->afe_data);
} else {
ret = afe_manager->esp_afe->disable_aec(afe_manager->afe_data);
}
ESP_LOGD(TAG, "AEC ctrl ret %d", ret);
if (ret >= 0) {
afe_manager->feat.aec = ret;
}
break;
}
case ESP_AFE_FEATURE_SE: {
if (enable) {
ret = afe_manager->esp_afe->enable_se(afe_manager->afe_data);
} else {
ret = afe_manager->esp_afe->disable_se(afe_manager->afe_data);
}
ESP_LOGD(TAG, "AE ctrl ret %d", ret);
if (ret >= 0) {
afe_manager->feat.se = ret;
}
break;
}
case ESP_AFE_FEATURE_VAD: {
if (enable) {
ret = afe_manager->esp_afe->enable_vad(afe_manager->afe_data);
} else {
ret = afe_manager->esp_afe->disable_vad(afe_manager->afe_data);
}
ESP_LOGD(TAG, "VAD ctrl ret %d", ret);
if (ret >= 0) {
afe_manager->feat.vad = ret;
}
ret = afe_manager->esp_afe->reset_vad(afe_manager->afe_data);
break;
}
default:
ESP_LOGW(TAG, "AFE manager feature ctrl: %u not support", feature);
ret = ESP_GMF_ERR_INVALID_ARG;
break;
}
return ret;
}
esp_gmf_err_t esp_gmf_afe_manager_set_read_cb(esp_gmf_afe_manager_handle_t handle, esp_gmf_afe_manager_read_cb_t read_cb, void *read_ctx)
{
ESP_RETURN_ON_FALSE(handle, ESP_GMF_ERR_INVALID_ARG, TAG, "AFE Manager set read: handle NULL");
esp_gmf_afe_manager_t *afe_manager = (esp_gmf_afe_manager_t *)handle;
esp_gmf_afe_manager_suspend(afe_manager, true);
xSemaphoreTake(afe_manager->read_cb_lock, portMAX_DELAY);
afe_manager->read_cb = read_cb;
afe_manager->read_ctx = read_ctx;
if (afe_manager->read_cb) {
esp_gmf_afe_manager_suspend(afe_manager, false);
}
xSemaphoreGive(afe_manager->read_cb_lock);
return ESP_GMF_ERR_OK;
}
esp_gmf_err_t esp_gmf_afe_manager_get_chunk_size(esp_gmf_afe_manager_handle_t handle, size_t *size)
{
ESP_RETURN_ON_FALSE(handle, ESP_GMF_ERR_INVALID_ARG, TAG, "AFE Manager get feed size: handle NULL");
ESP_RETURN_ON_FALSE(size, ESP_GMF_ERR_INVALID_ARG, TAG, "AFE Manager get feed size: size NULL");
esp_gmf_afe_manager_t *afe_manager = (esp_gmf_afe_manager_t *)handle;
*size = afe_manager->esp_afe->get_feed_chunksize(afe_manager->afe_data);
return ESP_GMF_ERR_OK;
}
esp_gmf_err_t esp_gmf_afe_manager_get_input_ch_num(esp_gmf_afe_manager_handle_t handle, uint8_t *ch_num)
{
ESP_RETURN_ON_FALSE(handle, ESP_GMF_ERR_INVALID_ARG, TAG, "AFE Manager get feed size: handle NULL");
ESP_RETURN_ON_FALSE(ch_num, ESP_GMF_ERR_INVALID_ARG, TAG, "AFE Manager get feed size: ch_num NULL");
esp_gmf_afe_manager_t *afe_manager = (esp_gmf_afe_manager_t *)handle;
*ch_num = afe_manager->esp_afe->get_feed_channel_num(afe_manager->afe_data);
return ESP_GMF_ERR_OK;
}
@@ -0,0 +1,287 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO., LTD
* SPDX-License-Identifier: LicenseRef-Espressif-Modified-MIT
*
* See LICENSE file for details.
*/
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "esp_log.h"
#include "esp_types.h"
#include "esp_gmf_audio_element.h"
#include "esp_gmf_cache.h"
#include "esp_gmf_cap.h"
#include "esp_gmf_caps_def.h"
#include "esp_gmf_err.h"
#include "esp_gmf_job.h"
#include "esp_gmf_node.h"
#include "esp_gmf_oal_mem.h"
#include "esp_gmf_payload.h"
#include "esp_wn_models.h"
#include "esp_gmf_wn.h"
#include "esp_gmf_ch_sort.h"
#define ESP_WN_BYTES_PER_SAMPLE (sizeof(uint16_t))
typedef struct {
esp_gmf_audio_element_t parent;
esp_wn_iface_t *wn_iface;
model_iface_data_t *model_data;
uint32_t sample_rate;
uint32_t ch;
uint32_t chunk_size;
uint32_t frame_len;
esp_gmf_cache_t *cache;
esp_gmf_payload_t *in_load;
} gmf_wn_t;
static const char *TAG = "GMF_WN";
static esp_gmf_err_t gmf_wn_new(void *cfg, esp_gmf_obj_handle_t *handle)
{
ESP_GMF_NULL_CHECK(TAG, cfg, { return ESP_GMF_ERR_INVALID_ARG; });
ESP_GMF_NULL_CHECK(TAG, handle, { return ESP_GMF_ERR_INVALID_ARG; });
*handle = NULL;
return esp_gmf_wn_init((esp_gmf_wn_cfg_t *)cfg, (esp_gmf_element_handle_t *)handle);
}
static esp_gmf_err_t gmf_wn_received_event_handler(esp_gmf_event_pkt_t *evt, void *ctx)
{
ESP_GMF_NULL_CHECK(TAG, evt, { return ESP_GMF_ERR_INVALID_ARG; });
ESP_GMF_NULL_CHECK(TAG, ctx, { return ESP_GMF_ERR_INVALID_ARG; });
esp_gmf_element_handle_t self = (esp_gmf_element_handle_t)ctx;
esp_gmf_element_handle_t el = evt->from;
esp_gmf_event_state_t state = ESP_GMF_EVENT_STATE_NONE;
esp_gmf_element_get_state(self, &state);
if (evt->sub == ESP_GMF_INFO_SOUND) {
esp_gmf_info_sound_t info = {0};
memcpy(&info, evt->payload, evt->payload_size);
ESP_LOGD(TAG, "RECV info, from: %s-%p, next: %p, self: %s-%p, type: %x, state: %s, rate: %d, ch: %d, bits: %d",
OBJ_GET_TAG(el), el, esp_gmf_node_for_next((esp_gmf_node_t *)el), OBJ_GET_TAG(self), self, evt->type,
esp_gmf_event_get_state_str(state), info.sample_rates, info.channels, info.bits);
if (info.sample_rates != 16000 || info.bits != 16) {
ESP_LOGE(TAG, "Unsupported format, rate: %d, bits: %d", info.sample_rates, info.bits);
return ESP_GMF_ERR_NOT_SUPPORT;
}
}
if (state == ESP_GMF_EVENT_STATE_NONE) {
esp_gmf_element_set_state(self, ESP_GMF_EVENT_STATE_INITIALIZED);
}
return ESP_GMF_ERR_OK;
}
static esp_gmf_err_t gmf_wn_destroy(esp_gmf_obj_handle_t self)
{
ESP_GMF_NULL_CHECK(TAG, self, { return ESP_GMF_ERR_INVALID_ARG; });
gmf_wn_t *gmf_wn = (gmf_wn_t *)self;
if (OBJ_GET_CFG(self)) {
esp_gmf_oal_free(OBJ_GET_CFG(self));
}
if (gmf_wn->wn_iface && gmf_wn->model_data) {
gmf_wn->wn_iface->destroy(gmf_wn->model_data);
gmf_wn->model_data = NULL;
}
if (gmf_wn->cache) {
esp_gmf_cache_delete(gmf_wn->cache);
gmf_wn->cache = NULL;
}
ESP_LOGD(TAG, "Destroyed");
esp_gmf_audio_el_deinit(self);
esp_gmf_oal_free(self);
return ESP_GMF_ERR_OK;
}
static esp_gmf_job_err_t gmf_wn_open(esp_gmf_audio_element_handle_t self, void *para)
{
gmf_wn_t *gmf_wn = (gmf_wn_t *)self;
esp_gmf_wn_cfg_t *cfg = OBJ_GET_CFG(self);
char *model_name = esp_srmodel_filter(cfg->models, ESP_WN_PREFIX, NULL);
ESP_GMF_NULL_CHECK(TAG, model_name, { return ESP_GMF_JOB_ERR_FAIL; });
gmf_wn->wn_iface = (esp_wn_iface_t *)esp_wn_handle_from_name(model_name);
ESP_GMF_NULL_CHECK(TAG, gmf_wn->wn_iface, { return ESP_GMF_JOB_ERR_FAIL; });
gmf_wn->model_data = gmf_wn->wn_iface->create(model_name, cfg->det_mode);
ESP_GMF_NULL_CHECK(TAG, gmf_wn->model_data, { return ESP_GMF_JOB_ERR_FAIL; });
int algo_ch_num = gmf_wn->wn_iface->get_channel_num(gmf_wn->model_data);
gmf_wn->ch = strlen(cfg->input_format);
if (algo_ch_num > gmf_wn->ch) {
ESP_LOGE(TAG, "Input channel number is less than needed");
return ESP_GMF_JOB_ERR_FAIL;
}
gmf_wn->sample_rate = gmf_wn->wn_iface->get_samp_rate(gmf_wn->model_data);
gmf_wn->chunk_size = gmf_wn->wn_iface->get_samp_chunksize(gmf_wn->model_data);
gmf_wn->frame_len = gmf_wn->chunk_size * gmf_wn->ch * ESP_WN_BYTES_PER_SAMPLE;
esp_gmf_cache_new(gmf_wn->frame_len, &gmf_wn->cache);
ESP_GMF_NULL_CHECK(TAG, gmf_wn->cache, { return ESP_GMF_JOB_ERR_FAIL; });
ESP_GMF_ELEMENT_IN_PORT_ATTR_SET(ESP_GMF_ELEMENT_GET(self)->in_attr, ESP_GMF_EL_PORT_CAP_SINGLE, 16, 0,
ESP_GMF_PORT_TYPE_BLOCK | ESP_GMF_PORT_TYPE_BYTE, gmf_wn->frame_len);
ESP_GMF_ELEMENT_OUT_PORT_ATTR_SET(ESP_GMF_ELEMENT_GET(self)->out_attr, ESP_GMF_EL_PORT_CAP_SINGLE, 16, 0,
ESP_GMF_PORT_TYPE_BLOCK | ESP_GMF_PORT_TYPE_BYTE, gmf_wn->chunk_size * ESP_WN_BYTES_PER_SAMPLE);
ESP_LOGI(TAG, "Open, frame_len: %" PRIu32 ", ch %" PRIu32 ", chunksize %" PRIu32, gmf_wn->frame_len, gmf_wn->ch, gmf_wn->chunk_size);
esp_gmf_info_sound_t snd_info = {0};
snd_info.sample_rates = 16000;
snd_info.bits = 16;
snd_info.channels = 1;
esp_gmf_element_notify_snd_info(self, &snd_info);
return ESP_GMF_JOB_ERR_OK;
}
static esp_gmf_job_err_t gmf_wn_process(esp_gmf_audio_element_handle_t self, void *para)
{
int ret = ESP_GMF_JOB_ERR_OK;
gmf_wn_t *gmf_wn = (gmf_wn_t *)self;
esp_gmf_port_handle_t in_port = ESP_GMF_ELEMENT_GET(self)->in;
esp_gmf_port_handle_t out_port = ESP_GMF_ELEMENT_GET(self)->out;
esp_gmf_payload_t *out_load = NULL;
esp_gmf_payload_t *cache_load = NULL;
esp_gmf_err_io_t load_ret = 0;
esp_gmf_wn_cfg_t *cfg = OBJ_GET_CFG(self);
bool need_load = false;
esp_gmf_cache_ready_for_load(gmf_wn->cache, &need_load);
if (need_load) {
load_ret = esp_gmf_port_acquire_in(in_port, &gmf_wn->in_load, gmf_wn->frame_len, in_port->wait_ticks);
ESP_GMF_PORT_ACQUIRE_IN_CHECK(TAG, load_ret, ret, { goto __quit; });
esp_gmf_cache_load(gmf_wn->cache, gmf_wn->in_load);
}
esp_gmf_cache_acquire(gmf_wn->cache, gmf_wn->frame_len, &cache_load);
if (cache_load->valid_size != gmf_wn->frame_len) {
if (cache_load->is_done == true) {
ret = ESP_GMF_JOB_ERR_DONE;
} else {
ret = ESP_GMF_JOB_ERR_CONTINUE;
ESP_LOGD(TAG, "Return Continue, size:%u", cache_load->valid_size);
}
goto __quit;
}
load_ret = esp_gmf_port_acquire_out(out_port, &out_load, gmf_wn->frame_len, ESP_GMF_MAX_DELAY);
out_load->is_done = cache_load->is_done;
ESP_GMF_PORT_ACQUIRE_OUT_CHECK(TAG, load_ret, ret, { goto __quit; });
if (out_load->buf_length < gmf_wn->frame_len) {
ret = ESP_GMF_JOB_ERR_FAIL;
ESP_LOGE(TAG, "Output buffer is not enough");
goto __quit;
}
esp_gmf_sort_with_format((int16_t *)cache_load->buf,
cfg->input_format,
gmf_wn->chunk_size,
gmf_wn->ch,
(int16_t *)out_load->buf);
int res = gmf_wn->wn_iface->detect(gmf_wn->model_data, (int16_t *)out_load->buf);
if (res > 0) {
int trigger_ch = gmf_wn->wn_iface->get_triggered_channel(gmf_wn->model_data);
if (cfg->detect_cb) {
cfg->detect_cb(self, trigger_ch, cfg->user_ctx);
}
}
out_load->valid_size = gmf_wn->chunk_size * ESP_WN_BYTES_PER_SAMPLE;
esp_gmf_cache_ready_for_load(gmf_wn->cache, &need_load);
if (need_load == false) {
ret = ESP_GMF_JOB_ERR_TRUNCATE;
} else {
ret = (out_load->is_done == true ? ESP_GMF_JOB_ERR_DONE : ESP_GMF_JOB_ERR_OK);
}
__quit:
if (out_load) {
esp_gmf_port_release_out(out_port, out_load, ESP_GMF_MAX_DELAY);
}
if (gmf_wn->in_load && (ret != ESP_GMF_JOB_ERR_TRUNCATE)) {
esp_gmf_port_release_in(in_port, gmf_wn->in_load, ESP_GMF_MAX_DELAY);
gmf_wn->in_load = NULL;
}
if (cache_load) {
esp_gmf_cache_release(gmf_wn->cache, cache_load);
}
return ret;
}
static esp_gmf_job_err_t gmf_wn_close(esp_gmf_audio_element_handle_t self, void *para)
{
gmf_wn_t *gmf_wn = (gmf_wn_t *)self;
if (gmf_wn->wn_iface && gmf_wn->model_data) {
gmf_wn->wn_iface->destroy(gmf_wn->model_data);
gmf_wn->model_data = NULL;
}
if (gmf_wn->cache) {
esp_gmf_cache_delete(gmf_wn->cache);
gmf_wn->cache = NULL;
}
return ESP_GMF_JOB_ERR_OK;
}
static esp_gmf_err_t _load_wn_caps_func(esp_gmf_element_handle_t handle)
{
esp_gmf_cap_t *caps = NULL;
esp_gmf_cap_t wn_caps = {0};
wn_caps.cap_eightcc = ESP_GMF_CAPS_AUDIO_WWE;
wn_caps.attr_fun = NULL;
int ret = esp_gmf_cap_append(&caps, &wn_caps);
ESP_GMF_RET_ON_NOT_OK(TAG, ret, { return ret; }, "Failed to create capability");
esp_gmf_element_t *el = (esp_gmf_element_t *)handle;
el->caps = caps;
return ESP_GMF_ERR_OK;
}
esp_gmf_err_t esp_gmf_wn_init(esp_gmf_wn_cfg_t *config, esp_gmf_element_handle_t *handle)
{
esp_gmf_err_t ret = ESP_GMF_ERR_OK;
ESP_GMF_NULL_CHECK(TAG, config, { return ESP_GMF_ERR_INVALID_ARG; });
ESP_GMF_NULL_CHECK(TAG, handle, { return ESP_GMF_ERR_INVALID_ARG; });
*handle = NULL;
gmf_wn_t *gmf_wn = esp_gmf_oal_calloc(1, sizeof(gmf_wn_t));
ESP_GMF_MEM_VERIFY(TAG, gmf_wn, { return ESP_GMF_ERR_MEMORY_LACK; }, "wn", sizeof(gmf_wn_t));
esp_gmf_obj_t *obj = (esp_gmf_obj_t *)gmf_wn;
obj->new_obj = gmf_wn_new;
obj->del_obj = gmf_wn_destroy;
esp_gmf_wn_cfg_t *obj_cfg = esp_gmf_oal_calloc(1, sizeof(esp_gmf_wn_cfg_t));
ESP_GMF_NULL_CHECK(TAG, obj_cfg, { ret = ESP_GMF_ERR_MEMORY_LACK; goto __failed; });
memcpy(obj_cfg, config, sizeof(esp_gmf_wn_cfg_t));
ret = esp_gmf_obj_set_config(obj, obj_cfg, sizeof(esp_gmf_wn_cfg_t));
ESP_GMF_RET_ON_NOT_OK(TAG, ret, goto __failed, "Failed set OBJ configuration");
ret = esp_gmf_obj_set_tag(obj, "ai_wn");
ESP_GMF_RET_ON_NOT_OK(TAG, ret, goto __failed, "Failed set OBJ tag");
esp_gmf_element_cfg_t el_cfg = {0};
ESP_GMF_ELEMENT_IN_PORT_ATTR_SET(el_cfg.in_attr, ESP_GMF_EL_PORT_CAP_SINGLE, 16, 0,
ESP_GMF_PORT_TYPE_BLOCK | ESP_GMF_PORT_TYPE_BYTE, 1024);
ESP_GMF_ELEMENT_OUT_PORT_ATTR_SET(el_cfg.out_attr, ESP_GMF_EL_PORT_CAP_SINGLE, 16, 0,
ESP_GMF_PORT_TYPE_BLOCK | ESP_GMF_PORT_TYPE_BYTE, 1024);
el_cfg.dependency = true;
ret = esp_gmf_audio_el_init(gmf_wn, &el_cfg);
ESP_GMF_RET_ON_NOT_OK(TAG, ret, goto __failed, "Failed to initialize wn element");
ESP_GMF_ELEMENT_GET(gmf_wn)->ops.open = gmf_wn_open;
ESP_GMF_ELEMENT_GET(gmf_wn)->ops.process = gmf_wn_process;
ESP_GMF_ELEMENT_GET(gmf_wn)->ops.close = gmf_wn_close;
ESP_GMF_ELEMENT_GET(gmf_wn)->ops.event_receiver = gmf_wn_received_event_handler;
ESP_GMF_ELEMENT_GET(gmf_wn)->ops.load_caps = _load_wn_caps_func;
*handle = obj;
return ESP_GMF_ERR_OK;
__failed:
esp_gmf_obj_delete(obj);
return ret;
}
esp_gmf_err_t esp_gmf_wn_set_detect_cb(esp_gmf_element_handle_t self, esp_wn_detect_cb_t cb, void *ctx)
{
ESP_GMF_NULL_CHECK(TAG, self, { return ESP_GMF_ERR_INVALID_ARG; });
ESP_GMF_NULL_CHECK(TAG, cb, { return ESP_GMF_ERR_INVALID_ARG; });
esp_gmf_wn_cfg_t *cfg = OBJ_GET_CFG(self);
cfg->detect_cb = cb;
cfg->user_ctx = ctx;
return ESP_GMF_ERR_OK;
}
@@ -0,0 +1,105 @@
# Changelog
## v0.7.2
### Features
- Support C++ build
## v0.7.1
### Bug Fixes
- Fixed sonic bypass failed for first payload
### Features
- Added audio param setting for `aud_alc`, `aud_fade`, `aud_sonic`
## v0.7.0
### Features
- Updated `esp_audio_codec` to version v2.3.0
- Updated `esp_audio_effects` to version v1.1.0
- Added `esp_gmf_audio_enc_get_frame_size`, `esp_gmf_audio_enc_set_bitrate`, `esp_gmf_audio_enc_get_bitrate`, `esp_gmf_audio_enc_reconfig` and `esp_gmf_audio_enc_reconfig_by_sound_info` functions to `gmf_audio_enc`
- Added `SBC` and `LC3` encoders
- Added `esp_gmf_audio_dec_reconfig` and `esp_gmf_audio_dec_reconfig_by_sound_info` functions to `gmf_audio_dec`
- Added `SBC` and `LC3` decoders
- Added `meta_flag` in `gmf_audio_dec` to support audio decoder recovery status tracking
- Added common audio parameter setting interface
- Redefined audio methods name
- Removed the audio encoder and decoder reconfig interface in `esp_gmf_audio_helper.c`
- Used the `esp_gmf_element_handle_t` type handle in the `gmf_audio` module
- Use default configuration for `esp_gmf_audio_xxx_init` internally if configuration set to NULL
- Added TRUNCATE support for SONIC element to enable its flexible placement at any position within the pipeline
- Supported scenarios where the input data for audio encoding is not a complete frame
- Added PTS (Presentation Time Stamp) calculation and propagation for the `aud_alc`, `aud_eq`, `aud_fade`, `aud_enc`
- Supported user-configurable input frame length and load acquisition timeout for the mixer
### Bug Fixes
- Fixed `gmf_audio_enc` process blocked due to forget release of in_load when truncate is returned
- Fixed parameter mismatch in `audio_dec_reconfig_dec_by_sound_info`
- Standardize TAG identifier format across all audio elements with `aud` prefix
- Fixed a crash caused by passing a NULL encoder config to the init function and subsequently reporting info
- Fixed bugs in the implementation of methods for sonic, fade, and mixer
- Fixed the issue where the encoder modified the configuration during runtime
- Fixed the issue where the mixer did not forward the out_load parameter to downstream components when all input stream were no data
- Fixed bugs that not check validation of in and out buffer for mixer which will cause crash
- Fixed bugs where setting parameters before opening had no effect for `aud_fade` and `aud_mixer`
## v0.6.3
### Bug Fixes
- Fixed decoder not releasing input port on failure
- Fixed missing input size check in gmf_audio_enc when input is insufficient to encode a frame and stream has ended
- Fixed missing zero size check for acquire_in in gmf_audio_dec
- Fixed unmatched sub_cfg when reconfigured by esp_gmf_audio_helper_reconfig_dec_by_uri
## v0.6.2
### Features
- Limit the version of `esp_audio_codec` and `esp_audio_effects`
## v0.6.1
- Added missing #include "esp_gmf_audio_element.h" across all audio elements
- Fixed out-of-range parameter handling for mixer and EQ elements
- Resolved rate/bit/channel converter bypass errors caused by asynchronous modification of obj->cfg between set/event callbacks and process functions
- Deleted one unreasonable log from esp_gmf_audio_helper.c
## v0.6.0
### Features
- Updated the logic for GMF capabilities and method handling
- Added support for NULL object configuration
- Added GMF port attribution for elements
- Added gmf_cache and optimized the loop path for the audio encoder and decoder
- Renamed component to `gmf_audio`
- Updated the License
## v0.5.2
### Bug Fixes
- Fixed an issue where the decoder's output done flag was repeatedly set after the input was marked as done
- Fixed an issue where the input port was not released when the input data size was 0
## v0.5.1
### Bug Fixes
- Fixed the component requirements
## v0.5.0
### Features
- Initial version of `esp-gmf-audio`
@@ -0,0 +1,4 @@
idf_component_register(SRC_DIRS "./"
INCLUDE_DIRS "./include"
PRIV_INCLUDE_DIRS "./private_include"
REQUIRES esp_timer)
@@ -0,0 +1,22 @@
Espressif Modified MIT License
Copyright (c) 2025 Espressif Systems (Shanghai) CO., LTD
Permission is hereby granted for use EXCLUSIVELY with Espressif Systems products.
This includes the right to use, copy, modify, merge, publish, distribute, and sublicense
the Software, subject to the following conditions:
1. This Software MUST BE USED IN CONJUNCTION WITH ESPRESSIF SYSTEMS PRODUCTS.
2. The above copyright notice and this permission notice shall be included in all copies
or substantial portions of the Software.
3. Redistribution of the Software in source or binary form FOR USE WITH NON-ESPRESSIF PRODUCTS
is strictly prohibited.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
SPDX-License-Identifier: LicenseRef-Espressif-Modified-MIT
@@ -0,0 +1,43 @@
# ESP-GMF-Audio
- [![Component Registry](https://components.espressif.com/components/espressif/gmf_audio/badge.svg)](https://components.espressif.com/components/espressif/gmf_audio)
- [中文版](./README_CN.md)
ESP GMF Audio is a collection of GMF elements related to audio processing, including audio encoding, decoding, and audio effect processing algorithms. The currently supported audio modules are listed in the table below.
|Name|TAG|Function|Method|Input Port|Output Port|Input blocking time|Output blocking time|Dependent on Audio Information|
|:----:|:----:|:-----:|:----:|:----:|:----:|:----:|:----:|:----|
| AUDIO_DEC |aud_dec |Audio decoder: MP3,AAC,AMRNB,<br>AMRWB,FLAC,WAV,M4A,TS|Nil|Single|Single|User configurable, default value is maximum delay|User configurable, default value is maximum delay|No|
| AUDIO_ENC |aud_enc |Audio encoder: AAC,AMRNB,AMRWB,<br>ADPCM,OPUS,PCM|Nil|Single|Single|User configurable, default value is maximum delay|User configurable, default value is maximum delay|Yes|
| RATE_CVT|aud_rate_cvt |Audio sampling rate adjustment|`set_dest_rate`|Single|Single|Maximum delay|Maximum delay|Yes|
| BIT_CVT |aud_bit_cvt |Audio bit-depth conversion|`set_dest_bits`|Single|Single|Maximum delay|Maximum delay|Yes|
| CH_CVT |aud_ch_cvt |Audio channel conversion|`set_dest_ch`|Single|Single|Maximum delay|Maximum delay|Yes|
| ALC |aud_alc |Audio volume adjustment|`set_gain`<br>`get_gain`|Single|Single|Maximum delay|Maximum delay|Yes|
| EQ |aud_eq |Audio equalizer adjustment|`set_para`<br>`get_para`<br>`enable_filter`<br>`disable_filter`|Single|Single|Maximum delay|Maximum delay|Yes|
| FADE |aud_fade |Audio fade-in and fade-out effects|`set_mode`<br>`get_mode`<br>`reset_weight`|Single|Single|Maximum delay|Maximum delay|Yes|
| SONIC |aud_sonic |Audio pitch and speed shifting effects|`set_speed`<br>`get_speed`<br>`set_pitch`<br>`get_pitch`|Single|Single|Maximum delay|Maximum delay|Yes|
| MIXER |aud_mixer |Audio mixing effects|`set_info`<br>`set_mode`|Multiple|Single|The blocking time for the first channel is 0, while the blocking time for other channels is maximum delay|Maximum delay|No|
|INTERLEAVE|aud_intlv|Data interleaving|Nil|Multiple|Single|User configurable, default value is maximum delay|Maximum delay|Yes|
|DEINTERLEAVE|aud_deintlv|Data de-interleaving|Nil|Single|Multiple|Maximum delay|User configurable, default value is maximum delay|Yes|
## Usage
The ESP GMF Audio is often used in combination to form a pipeline. For example code, please refer to [test_app](../test_apps/main/elements/gmf_audio_play_el_test.c)。
You can also create and compile a project using the following commands, taking the `pipeline_play_embed_music` project as an example. Before starting, make sure you have a working [ESP-IDF](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/get-started/index.html) environment.
### 1. Create the Example Project
Create the `pipeline_play_embed_music` example project based on the `gmf_examples` component (using version v0.7.0 as an example; update the version as needed):
```shell
idf.py create-project-from-example "espressif/gmf_examples=0.7.0:pipeline_play_embed_music"
```
### 2. Build and Flash the Project Using an ESP32-S3 Board
```shell
cd pipeline_play_embed_music
idf.py set-target esp32s3
idf.py -p YOUR_PORT flash monitor
```
@@ -0,0 +1,43 @@
# ESP-GMF-Audio
- [![Component Registry](https://components.espressif.com/components/espressif/gmf_audio/badge.svg)](https://components.espressif.com/components/espressif/gmf_audio)
- [English](./README.md)
ESP GMF Audio 是 GMF 音频处理相关元素的集合,包括音频编码,解码和音频效果处理算法。目前已支持的 Audio 模块参见下表。
| 名称 | 标签 | 功能 | 函数方法 | 输入端口 | 输出端口 | 输入阻塞时间 | 输出阻塞时间 |依赖音频信息 |
|:----:| :-----: | :----: | :----: |:----: |:----: |:----: |:----: |:---- |
| AUDIO_DEC | aud_dec | 音频解码:MP3,AAC,AMRNB,<br>AMRWB,FLAC,WAV,M4A,TS | 无 | 单个 | 单个 | 可用户配置,默认是最大延迟 |可用户配置,默认是最大延迟 |否 |
| AUDIO_ENC | aud_enc | 音频编码:AAC,AMRNB,AMRWB,<br>ADPCM,OPUS,PCM | 无 | 单个 | 单个 | 可用户配置,默认是最大延迟 |可用户配置,默认是最大延迟 |是 |
| RATE_CVT| aud_rate_cvt | 音频采样率调节 | `set_dest_rate` | 单个 | 单个 |最大延迟 |最大延迟| 是 |
| BIT_CVT | aud_bit_cvt | 音频比特位转换 | `set_dest_bits`| 单个 | 单个 |最大延迟 |最大延迟| 是 |
| CH_CVT | aud_ch_cvt | 音频声道数转换 | `set_dest_ch`| 单个 | 单个 |最大延迟 |最大延迟| 是 |
| ALC | aud_alc | 音频音量调节 | `set_gain`<br>`get_gain`| 单个 | 单个 |最大延迟 |最大延迟| 是 |
| EQ | aud_eq | 音频均衡器调节 |`set_para`<br>`get_para`<br>`enable_filter`<br>`disable_filter` |单个 |单个|最大延迟 |最大延迟|是 |
| FADE | aud_fade | 音频淡入淡出效果 |`set_mode`<br>`get_mode`<br>`reset_weight` | 单个 | 单个 |最大延迟 |最大延迟 |是 |
| SONIC | aud_sonic | 音频变速变调效果 |`set_speed`<br>`get_speed`<br>`set_pitch`<br>`get_pitch`| 单个 | 单个 |最大延迟 |最大延迟| 是 |
| MIXER | aud_mixer | 音频混音效果 |`set_info`<br>`set_mode`| 多个 | 单个 | 第一路阻塞时间为0,其他路阻塞时间为最大延迟 |最大延迟| 否 |
|INTERLEAVE| aud_intlv | 数据交织 | 无 | 多个 | 单个 | 可用户配置,默认是最大延迟 |最大延迟| 是 |
|DEINTERLEAVE| aud_deintlv | 数据解交织 | 无| 单个 | 多个 |最大延迟|可用户配置,默认是最大延迟 |是 |
## 示例
ESP GMF Audio 常常组合成管道使用,示例代码请参考 [test_app](../test_apps/main/elements/gmf_audio_play_el_test.c)。
此外,也可通过以下命令创建并编译工程,以 `pipeline_play_embed_music` 示例工程为例。开始之前需要有可运行的 [ESP-IDF](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/get-started/index.html) 环境。
### 1. 创建示例工程
基于 `gmf_examples` 组件创建 `pipeline_play_embed_music` 的示例(以 v0.7.0 版本为例, 请根据实际使用更新版本参数)
``` shell
idf.py create-project-from-example "espressif/gmf_examples=0.7.0:pipeline_play_embed_music"
```
### 2. 基于 ESP32S3 编译和下载
```shell
cd pipeline_play_embed_music
idf.py set-target esp32s3`
idf.py -p YOUR_PORT flash monitor
```
@@ -0,0 +1,353 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO., LTD
* SPDX-License-Identifier: LicenseRef-Espressif-Modified-MIT
*
* See LICENSE file for details.
*/
#include <string.h>
#include "esp_log.h"
#include "esp_gmf_oal_mem.h"
#include "esp_gmf_oal_mutex.h"
#include "esp_gmf_node.h"
#include "esp_gmf_alc.h"
#include "esp_gmf_args_desc.h"
#include "gmf_audio_common.h"
#include "esp_gmf_audio_methods_def.h"
#include "esp_gmf_cap.h"
#include "esp_gmf_caps_def.h"
#include "esp_gmf_audio_element.h"
#define GMF_ALC_DEFAULT_MAX_CHANNEL 2
/**
* @brief Audio ALC context in GMF
*/
typedef struct {
esp_gmf_audio_element_t parent; /*!< The GMF alc handle */
esp_ae_alc_handle_t alc_hd; /*!< The audio effects alc handle */
uint8_t bytes_per_sample; /*!< Bytes number of per sampling point */
int8_t *gain; /*!< The modified gain value of a certain channel number */
int8_t max_ch; /*!< The maximum channel number */
bool need_reopen : 1; /*!< Whether need to reopen.
True: Execute the close function first, then execute the open function
False: Do nothing */
} esp_gmf_alc_t;
static const char *TAG = "ESP_GMF_ALC";
static esp_gmf_err_t __alc_set_gain(esp_gmf_element_handle_t handle, esp_gmf_args_desc_t *arg_desc,
uint8_t *buf, int buf_len)
{
ESP_GMF_NULL_CHECK(TAG, arg_desc, { return ESP_GMF_ERR_INVALID_ARG;});
ESP_GMF_NULL_CHECK(TAG, buf, { return ESP_GMF_ERR_INVALID_ARG;});
esp_gmf_args_desc_t *alc_desc = arg_desc;
uint8_t idx = (uint8_t)(*buf);
alc_desc = alc_desc->next;
int8_t gain = (int8_t)(*(buf + alc_desc->offset));
esp_gmf_err_t ret = ESP_GMF_ERR_OK;
if (idx == 0xFF) {
// Apply gain to all channels
esp_ae_alc_cfg_t *config = (esp_ae_alc_cfg_t *)OBJ_GET_CFG(handle);
if (config) {
for (uint8_t i = 0; i < config->channel; i++) {
ret |= esp_gmf_alc_set_gain(handle, i, gain);
}
}
} else {
ret = esp_gmf_alc_set_gain(handle, idx, gain);
}
return ret;
}
static esp_gmf_err_t __alc_get_gain(esp_gmf_element_handle_t handle, esp_gmf_args_desc_t *arg_desc,
uint8_t *buf, int buf_len)
{
ESP_GMF_NULL_CHECK(TAG, arg_desc, {return ESP_GMF_ERR_INVALID_ARG;});
ESP_GMF_NULL_CHECK(TAG, buf, {return ESP_GMF_ERR_INVALID_ARG;});
esp_gmf_args_desc_t *alc_desc = arg_desc;
uint8_t idx = (uint8_t)(*buf);
alc_desc = alc_desc->next;
int8_t *gain = (int8_t *)(buf + alc_desc->offset);
return esp_gmf_alc_get_gain(handle, idx, gain);
}
static esp_gmf_err_t esp_gmf_alc_new(void *cfg, esp_gmf_obj_handle_t *handle)
{
return esp_gmf_alc_init(cfg, (esp_gmf_element_handle_t *)handle);
}
static esp_gmf_job_err_t esp_gmf_alc_open(esp_gmf_element_handle_t self, void *para)
{
esp_gmf_alc_t *alc = (esp_gmf_alc_t *)self;
esp_ae_alc_cfg_t *config = (esp_ae_alc_cfg_t *)OBJ_GET_CFG(self);
ESP_GMF_NULL_CHECK(TAG, config, { return ESP_GMF_JOB_ERR_FAIL;});
alc->bytes_per_sample = (config->bits_per_sample >> 3) * config->channel;
esp_ae_alc_open(config, &alc->alc_hd);
ESP_GMF_CHECK(TAG, alc->alc_hd, { return ESP_GMF_JOB_ERR_FAIL;}, "Failed to create alc handle");
GMF_AUDIO_UPDATE_SND_INFO(self, config->sample_rate, config->bits_per_sample, config->channel);
for (size_t i = 0; i < config->channel; i++) {
esp_ae_err_t ret = esp_ae_alc_set_gain(alc->alc_hd, i, alc->gain[i]);
if (ret != ESP_AE_ERR_OK) {
return ESP_GMF_JOB_ERR_FAIL;
}
}
alc->need_reopen = false;
ESP_LOGD(TAG, "Open, %p", self);
return ESP_GMF_JOB_ERR_OK;
}
static esp_gmf_job_err_t esp_gmf_alc_close(esp_gmf_element_handle_t self, void *para)
{
esp_gmf_alc_t *alc = (esp_gmf_alc_t *)self;
ESP_LOGD(TAG, "Closed, %p", self);
if (alc->alc_hd != NULL) {
esp_ae_alc_close(alc->alc_hd);
alc->alc_hd = NULL;
}
return ESP_GMF_ERR_OK;
}
static esp_gmf_job_err_t esp_gmf_alc_process(esp_gmf_element_handle_t self, void *para)
{
esp_gmf_alc_t *alc = (esp_gmf_alc_t *)self;
esp_gmf_job_err_t out_len = ESP_GMF_JOB_ERR_OK;
if (alc->need_reopen) {
esp_gmf_alc_close(self, NULL);
out_len = esp_gmf_alc_open(self, NULL);
if (out_len != ESP_GMF_JOB_ERR_OK) {
ESP_LOGE(TAG, "ALC reopen failed");
return out_len;
}
}
esp_gmf_port_handle_t in_port = ESP_GMF_ELEMENT_GET(self)->in;
esp_gmf_port_handle_t out_port = ESP_GMF_ELEMENT_GET(self)->out;
esp_gmf_payload_t *in_load = NULL;
esp_gmf_payload_t *out_load = NULL;
int samples_num = ESP_GMF_ELEMENT_GET(alc)->in_attr.data_size / (alc->bytes_per_sample);
int bytes = samples_num * alc->bytes_per_sample;
esp_gmf_err_io_t load_ret = esp_gmf_port_acquire_in(in_port, &in_load, bytes, ESP_GMF_MAX_DELAY);
samples_num = in_load->valid_size / (alc->bytes_per_sample);
bytes = samples_num * alc->bytes_per_sample;
if ((bytes != in_load->valid_size) || (load_ret < ESP_GMF_IO_OK)) {
ESP_LOGE(TAG, "Invalid in load size %d, ret %d", in_load->valid_size, load_ret);
out_len = ESP_GMF_JOB_ERR_FAIL;
goto __alc_release;
}
if (in_port->is_shared == 1) {
out_load = in_load;
}
load_ret = esp_gmf_port_acquire_out(out_port, &out_load, samples_num ? bytes : in_load->buf_length, ESP_GMF_MAX_DELAY);
ESP_GMF_PORT_ACQUIRE_OUT_CHECK(TAG, load_ret, out_len, { goto __alc_release;});
if (samples_num) {
esp_gmf_oal_mutex_lock(((esp_gmf_audio_element_t *)self)->lock);
esp_ae_err_t ret = esp_ae_alc_process(alc->alc_hd, samples_num, in_load->buf, out_load->buf);
esp_gmf_oal_mutex_unlock(((esp_gmf_audio_element_t *)self)->lock);
ESP_GMF_RET_ON_ERROR(TAG, ret, {out_len = ESP_GMF_JOB_ERR_FAIL; goto __alc_release;}, "ALC process error %d", ret);
}
ESP_LOGV(TAG, "Samples: %d, IN-PLD: %p-%p-%d-%d-%d, OUT-PLD: %p-%p-%d-%d-%d",
samples_num, in_load, in_load->buf, in_load->valid_size, in_load->buf_length, in_load->is_done,
out_load, out_load->buf, out_load->valid_size, out_load->buf_length, out_load->is_done);
out_load->valid_size = samples_num * alc->bytes_per_sample;
out_load->is_done = in_load->is_done;
out_load->pts = in_load->pts;
esp_gmf_audio_el_update_file_pos((esp_gmf_element_handle_t)self, out_load->valid_size);
if (in_load->is_done) {
out_len = ESP_GMF_JOB_ERR_DONE;
ESP_LOGD(TAG, "ALC done, out len: %d", out_load->valid_size);
}
__alc_release:
// Release in and out port
if (out_load != NULL) {
load_ret = esp_gmf_port_release_out(out_port, out_load, ESP_GMF_MAX_DELAY);
if ((load_ret < ESP_GMF_IO_OK) && (load_ret != ESP_GMF_IO_ABORT)) {
ESP_LOGE(TAG, "OUT port release error, ret:%d", load_ret);
out_len = ESP_GMF_JOB_ERR_FAIL;
}
}
if (in_load != NULL) {
load_ret = esp_gmf_port_release_in(in_port, in_load, ESP_GMF_MAX_DELAY);
if ((load_ret < ESP_GMF_IO_OK) && (load_ret != ESP_GMF_IO_ABORT)) {
ESP_LOGE(TAG, "IN port release error, ret:%d", load_ret);
out_len = ESP_GMF_JOB_ERR_FAIL;
}
}
return out_len;
}
static esp_gmf_err_t alc_received_event_handler(esp_gmf_event_pkt_t *evt, void *ctx)
{
ESP_GMF_NULL_CHECK(TAG, ctx, {return ESP_GMF_ERR_INVALID_ARG;});
ESP_GMF_NULL_CHECK(TAG, evt, {return ESP_GMF_ERR_INVALID_ARG;});
if ((evt->type != ESP_GMF_EVT_TYPE_REPORT_INFO)
|| (evt->sub != ESP_GMF_INFO_SOUND)
|| (evt->payload == NULL)) {
return ESP_GMF_ERR_OK;
}
esp_gmf_element_handle_t self = (esp_gmf_element_handle_t)ctx;
esp_gmf_element_handle_t el = evt->from;
esp_gmf_event_state_t state = ESP_GMF_EVENT_STATE_NONE;
esp_gmf_element_get_state(self, &state);
esp_gmf_info_sound_t *info = (esp_gmf_info_sound_t *)evt->payload;
esp_ae_alc_cfg_t *config = (esp_ae_alc_cfg_t *)OBJ_GET_CFG(self);
ESP_GMF_NULL_CHECK(TAG, config, return ESP_GMF_ERR_FAIL);
esp_gmf_alc_t *alc = (esp_gmf_alc_t *)self;
if (info->channels > alc->max_ch) {
alc->gain = esp_gmf_oal_realloc(alc->gain, config->channel * sizeof(*alc->gain));
ESP_GMF_MEM_VERIFY(TAG, alc->gain, return ESP_GMF_ERR_MEMORY_LACK, "alc gain", config->channel * sizeof(*alc->gain));
alc->max_ch = info->channels;
}
alc->need_reopen = (config->sample_rate != info->sample_rates) || (info->channels != config->channel) || (config->bits_per_sample != info->bits);
config->sample_rate = info->sample_rates;
config->channel = info->channels;
config->bits_per_sample = info->bits;
ESP_LOGD(TAG, "RECV element info, from: %s-%p, next: %p, self: %s-%p, type: %x, state: %s, rate: %d, ch: %d, bits: %d",
OBJ_GET_TAG(el), el, esp_gmf_node_for_next((esp_gmf_node_t *)el), OBJ_GET_TAG(self), self, evt->type,
esp_gmf_event_get_state_str(state), info->sample_rates, info->channels, info->bits);
if (state == ESP_GMF_EVENT_STATE_NONE) {
esp_gmf_element_set_state(self, ESP_GMF_EVENT_STATE_INITIALIZED);
}
return ESP_GMF_ERR_OK;
}
static esp_gmf_err_t esp_gmf_alc_destroy(esp_gmf_element_handle_t self)
{
esp_gmf_alc_t *alc = (esp_gmf_alc_t *)self;
ESP_LOGD(TAG, "Destroyed, %p", self);
void *cfg = OBJ_GET_CFG(self);
if (cfg) {
esp_gmf_oal_free(cfg);
}
if (alc->gain) {
esp_gmf_oal_free(alc->gain);
alc->gain = NULL;
}
esp_gmf_audio_el_deinit(self);
esp_gmf_oal_free(alc);
return ESP_GMF_ERR_OK;
}
static esp_gmf_err_t _load_alc_caps_func(esp_gmf_element_handle_t handle)
{
esp_gmf_cap_t *caps = NULL;
esp_gmf_cap_t dec_caps = {0};
dec_caps.cap_eightcc = ESP_GMF_CAPS_AUDIO_ALC;
dec_caps.attr_fun = NULL;
int ret = esp_gmf_cap_append(&caps, &dec_caps);
ESP_GMF_RET_ON_NOT_OK(TAG, ret, {return ret;}, "Failed to create capability");
esp_gmf_element_t *el = (esp_gmf_element_t *)handle;
el->caps = caps;
return ESP_GMF_ERR_OK;
}
static esp_gmf_err_t _load_alc_methods_func(esp_gmf_element_handle_t handle)
{
esp_gmf_method_t *method = NULL;
esp_gmf_args_desc_t *set_args = NULL;
esp_gmf_args_desc_t *get_args = NULL;
esp_gmf_err_t ret = esp_gmf_args_desc_append(&set_args, AMETHOD_ARG(ALC, SET_GAIN, IDX),
ESP_GMF_ARGS_TYPE_UINT8, sizeof(uint8_t), 0);
ESP_GMF_RET_ON_NOT_OK(TAG, ret, {return ret;}, "Failed to append index argument");
ret = esp_gmf_args_desc_append(&set_args, AMETHOD_ARG(ALC, SET_GAIN, GAIN), ESP_GMF_ARGS_TYPE_INT8,
sizeof(int8_t), sizeof(uint8_t));
ESP_GMF_RET_ON_NOT_OK(TAG, ret, {return ret;}, "Failed to append gain argument");
ret = esp_gmf_method_append(&method, AMETHOD(ALC, SET_GAIN), __alc_set_gain, set_args);
ESP_GMF_RET_ON_ERROR(TAG, ret, {return ret;}, "Failed to register %s method", AMETHOD(ALC, SET_GAIN));
ret = esp_gmf_args_desc_copy(set_args, &get_args);
ESP_GMF_RET_ON_NOT_OK(TAG, ret, {return ret;}, "Failed to copy argument");
ret = esp_gmf_method_append(&method, AMETHOD(ALC, GET_GAIN), __alc_get_gain, get_args);
ESP_GMF_RET_ON_ERROR(TAG, ret, {return ret;}, "Failed to register %s method", AMETHOD(ALC, GET_GAIN));
esp_gmf_element_t *el = (esp_gmf_element_t *)handle;
el->method = method;
return ESP_GMF_ERR_OK;
}
esp_gmf_err_t esp_gmf_alc_set_gain(esp_gmf_element_handle_t handle, uint8_t idx, int8_t gain)
{
ESP_GMF_NULL_CHECK(TAG, handle, { return ESP_GMF_ERR_INVALID_ARG;});
esp_gmf_alc_t *alc = (esp_gmf_alc_t *)handle;
if (idx >= alc->max_ch) {
ESP_LOGE(TAG, "Gain index %d is out of range", idx);
return ESP_GMF_ERR_INVALID_ARG;
}
if (alc->alc_hd) {
esp_gmf_oal_mutex_lock(((esp_gmf_audio_element_t *)handle)->lock);
esp_ae_err_t ret = esp_ae_alc_set_gain(alc->alc_hd, idx, gain);
esp_gmf_oal_mutex_unlock(((esp_gmf_audio_element_t *)handle)->lock);
if (ret != ESP_AE_ERR_OK) {
return ESP_GMF_JOB_ERR_FAIL;
}
}
alc->gain[idx] = gain;
return ESP_GMF_JOB_ERR_OK;
}
esp_gmf_err_t esp_gmf_alc_get_gain(esp_gmf_element_handle_t handle, uint8_t idx, int8_t *gain)
{
ESP_GMF_NULL_CHECK(TAG, handle, { return ESP_GMF_ERR_INVALID_ARG;});
ESP_GMF_NULL_CHECK(TAG, gain, { return ESP_GMF_ERR_INVALID_ARG;});
esp_gmf_alc_t *alc = (esp_gmf_alc_t *)handle;
if(idx > alc->max_ch) {
ESP_LOGE(TAG, "Gain index %d is out of range", idx);
return ESP_GMF_ERR_INVALID_ARG;
}
if (alc->alc_hd) {
esp_ae_err_t ret = esp_ae_alc_get_gain(alc->alc_hd, idx, gain);
if (ret != ESP_AE_ERR_OK) {
return ESP_GMF_JOB_ERR_FAIL;
}
return ESP_GMF_JOB_ERR_OK;
}
*gain = alc->gain[idx];
return ESP_GMF_JOB_ERR_OK;
}
esp_gmf_err_t esp_gmf_alc_init(esp_ae_alc_cfg_t *config, esp_gmf_element_handle_t *handle)
{
ESP_GMF_NULL_CHECK(TAG, handle, {return ESP_GMF_ERR_INVALID_ARG;});
*handle = NULL;
esp_gmf_err_t ret = ESP_GMF_ERR_OK;
esp_gmf_alc_t *alc = esp_gmf_oal_calloc(1, sizeof(esp_gmf_alc_t));
ESP_GMF_MEM_VERIFY(TAG, alc, {return ESP_GMF_ERR_MEMORY_LACK;}, "ALC", sizeof(esp_gmf_alc_t));
esp_gmf_obj_t *obj = (esp_gmf_obj_t *)alc;
obj->new_obj = esp_gmf_alc_new;
obj->del_obj = esp_gmf_alc_destroy;
alc->max_ch = GMF_ALC_DEFAULT_MAX_CHANNEL;
esp_ae_alc_cfg_t *cfg = esp_gmf_oal_calloc(1, sizeof(esp_ae_alc_cfg_t));
ESP_GMF_MEM_VERIFY(TAG, cfg, ret = ESP_GMF_ERR_MEMORY_LACK; goto ALC_INIT_FAIL;, "alc configuration", sizeof(esp_ae_alc_cfg_t));
esp_gmf_obj_set_config(obj, cfg, sizeof(esp_ae_alc_cfg_t));
if (config) {
alc->max_ch = config->channel > 0 ? config->channel : GMF_ALC_DEFAULT_MAX_CHANNEL;
memcpy(cfg, config, sizeof(esp_ae_alc_cfg_t));
} else {
esp_ae_alc_cfg_t dcfg = DEFAULT_ESP_GMF_ALC_CONFIG();
alc->max_ch = dcfg.channel;
memcpy(cfg, &dcfg, sizeof(esp_ae_alc_cfg_t));
}
alc->gain = esp_gmf_oal_calloc(1, alc->max_ch * sizeof(int8_t));
ESP_GMF_MEM_VERIFY(TAG, alc->gain, goto ALC_INIT_FAIL, "alc gain", alc->max_ch * sizeof(int8_t));
ret = esp_gmf_obj_set_tag(obj, "aud_alc");
ESP_GMF_RET_ON_NOT_OK(TAG, ret, goto ALC_INIT_FAIL, "Failed to set obj tag");
esp_gmf_element_cfg_t el_cfg = {0};
ESP_GMF_ELEMENT_IN_PORT_ATTR_SET(el_cfg.in_attr, ESP_GMF_EL_PORT_CAP_SINGLE, 0, 0,
ESP_GMF_PORT_TYPE_BLOCK | ESP_GMF_PORT_TYPE_BYTE, ESP_GMF_ELEMENT_PORT_DATA_SIZE_DEFAULT);
ESP_GMF_ELEMENT_OUT_PORT_ATTR_SET(el_cfg.out_attr, ESP_GMF_EL_PORT_CAP_SINGLE, 0, 0,
ESP_GMF_PORT_TYPE_BLOCK | ESP_GMF_PORT_TYPE_BYTE, ESP_GMF_ELEMENT_PORT_DATA_SIZE_DEFAULT);
el_cfg.dependency = true;
ret = esp_gmf_audio_el_init(alc, &el_cfg);
ESP_GMF_RET_ON_NOT_OK(TAG, ret, goto ALC_INIT_FAIL, "Failed to initialize alc element");
ESP_GMF_ELEMENT_GET(alc)->ops.open = esp_gmf_alc_open;
ESP_GMF_ELEMENT_GET(alc)->ops.process = esp_gmf_alc_process;
ESP_GMF_ELEMENT_GET(alc)->ops.close = esp_gmf_alc_close;
ESP_GMF_ELEMENT_GET(alc)->ops.event_receiver = alc_received_event_handler;
ESP_GMF_ELEMENT_GET(alc)->ops.load_caps = _load_alc_caps_func;
ESP_GMF_ELEMENT_GET(alc)->ops.load_methods = _load_alc_methods_func;
*handle = obj;
ESP_LOGD(TAG, "Initialization, %s-%p", OBJ_GET_TAG(obj), obj);
return ESP_GMF_ERR_OK;
ALC_INIT_FAIL:
esp_gmf_alc_destroy(obj);
return ret;
}
@@ -0,0 +1,517 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO., LTD
* SPDX-License-Identifier: LicenseRef-Espressif-Modified-MIT
*
* See LICENSE file for details.
*/
#include <string.h>
#include "esp_log.h"
#include "esp_gmf_oal_mem.h"
#include "esp_gmf_err.h"
#include "esp_gmf_audio_dec.h"
#include "esp_audio_types.h"
#include "esp_audio_simple_dec_default.h"
#include "gmf_audio_common.h"
#include "esp_gmf_cap.h"
#include "esp_fourcc.h"
#include "esp_gmf_caps_def.h"
#include "esp_gmf_audio_methods_def.h"
#include "esp_gmf_audio_element.h"
#define DEFAULT_DEC_OUTPUT_BUFFER_SIZE 1024
/**
* @brief Audio simple decoder context in GMF
*/
typedef struct {
esp_gmf_audio_element_t parent; /*!< The GMF audio decoder handle */
esp_audio_simple_dec_handle_t dec_hd; /*!< The audio simple decoder handle */
esp_audio_simple_dec_raw_t in_data; /*!< The audio simple decoder input data handle */
esp_audio_simple_dec_out_t out_data; /*!< The audio simple decoder output data handle */
int32_t buf_size; /*!< The size of decoder out buffer */
esp_gmf_payload_t *in_load; /*!< The input payload */
uint64_t pts; /*!< Audio pts */
} esp_gmf_audio_dec_t;
static const char *TAG = "ESP_GMF_ASMP_DEC";
static esp_gmf_err_t _dec_caps_iter_fun(uint32_t attr_index, esp_gmf_cap_attr_t *attr)
{
switch (attr_index) {
case 0: {
const static uint32_t support_dec_type[] = {ESP_FOURCC_MP3, ESP_FOURCC_AAC, ESP_FOURCC_OPUS, ESP_FOURCC_FLAC, ESP_FOURCC_AMRNB, ESP_FOURCC_AMRWB, ESP_FOURCC_ALAC,
ESP_FOURCC_M4A, ESP_FOURCC_ALAW, ESP_FOURCC_ULAW, ESP_FOURCC_LC3, ESP_FOURCC_SBC, ESP_FOURCC_PCM};
ESP_GMF_CAP_ATTR_SET_DISCRETE(attr, ESP_FOURCC_TO_INT('T', 'Y', 'P', 'E'), (uint32_t *) &support_dec_type,
sizeof(support_dec_type) / sizeof(uint32_t), sizeof(uint32_t));
break;
}
default:
attr->prop_type = ESP_GMF_PROP_TYPE_NONE;
return ESP_GMF_ERR_NOT_SUPPORT;
}
return ESP_GMF_ERR_OK;
}
static esp_gmf_err_t __audio_dec_reconfig(esp_gmf_element_handle_t handle, esp_gmf_args_desc_t *arg_desc,
uint8_t *buf, int buf_len)
{
ESP_GMF_NULL_CHECK(TAG, buf, return ESP_GMF_ERR_INVALID_ARG);
esp_audio_simple_dec_cfg_t *config = (esp_audio_simple_dec_cfg_t *)buf;
return esp_gmf_audio_dec_reconfig(handle, config);
}
static esp_gmf_err_t __audio_dec_reconfig_by_sound_info(esp_gmf_element_handle_t handle, esp_gmf_args_desc_t *arg_desc,
uint8_t *buf, int buf_len)
{
ESP_GMF_NULL_CHECK(TAG, buf, return ESP_GMF_ERR_INVALID_ARG);
esp_gmf_info_sound_t *snd_info = (esp_gmf_info_sound_t *)buf;
return esp_gmf_audio_dec_reconfig_by_sound_info(handle, snd_info);
}
static inline esp_gmf_err_t dupl_esp_audio_simple_cfg(esp_audio_simple_dec_cfg_t *config, esp_audio_simple_dec_cfg_t **new_config)
{
void *sub_cfg = NULL;
*new_config = esp_gmf_oal_calloc(1, sizeof(*config));
ESP_GMF_MEM_VERIFY(TAG, *new_config, {return ESP_GMF_ERR_MEMORY_LACK;}, "audio simple decoder configuration", sizeof(*config));
memcpy(*new_config, config, sizeof(*config));
if (config->dec_cfg && (config->cfg_size > 0)) {
sub_cfg = esp_gmf_oal_calloc(1, config->cfg_size);
ESP_GMF_MEM_VERIFY(TAG, sub_cfg, {esp_gmf_oal_free(*new_config); return ESP_GMF_ERR_MEMORY_LACK;},
"decoder configuration", config->cfg_size);
memcpy(sub_cfg, config->dec_cfg, config->cfg_size);
(*new_config)->dec_cfg = sub_cfg;
}
return ESP_GMF_JOB_ERR_OK;
}
static esp_gmf_err_t audio_dec_set_subcfg(esp_audio_simple_dec_cfg_t *dec_cfg, void *sub_cfg, int32_t sub_cfg_sz)
{
dec_cfg->dec_cfg = esp_gmf_oal_calloc(1, sub_cfg_sz);
ESP_GMF_MEM_CHECK(TAG, dec_cfg->dec_cfg, return ESP_GMF_ERR_MEMORY_LACK;);
dec_cfg->cfg_size = sub_cfg_sz;
memcpy(dec_cfg->dec_cfg, sub_cfg, sub_cfg_sz);
return ESP_GMF_ERR_OK;
}
static esp_gmf_err_t audio_dec_reconfig_dec_by_sound_info(esp_gmf_element_handle_t handle, esp_gmf_info_sound_t *info)
{
esp_audio_simple_dec_cfg_t *dec_cfg = (esp_audio_simple_dec_cfg_t *)OBJ_GET_CFG(handle);
if (dec_cfg == NULL) {
dec_cfg = esp_gmf_oal_calloc(1, sizeof(esp_audio_simple_dec_cfg_t));
ESP_GMF_MEM_VERIFY(TAG, dec_cfg, return ESP_GMF_ERR_MEMORY_LACK, "audio simple decoder configuration", sizeof(esp_audio_simple_dec_cfg_t));
esp_gmf_obj_set_config(handle, dec_cfg, sizeof(esp_audio_simple_dec_cfg_t));
}
esp_gmf_err_t ret = ESP_GMF_ERR_OK;
bool same_type = (info->format_id == dec_cfg->dec_type) ? (true) : (false);
dec_cfg->dec_type = info->format_id;
// free sub cfg first
if (dec_cfg->dec_cfg && (same_type == false)) {
esp_gmf_oal_free(dec_cfg->dec_cfg);
dec_cfg->dec_cfg = NULL;
dec_cfg->cfg_size = 0;
}
switch (info->format_id) {
case ESP_AUDIO_SIMPLE_DEC_TYPE_MP3:
case ESP_AUDIO_SIMPLE_DEC_TYPE_AMRWB:
case ESP_AUDIO_SIMPLE_DEC_TYPE_AMRNB:
case ESP_AUDIO_SIMPLE_DEC_TYPE_FLAC:
case ESP_AUDIO_SIMPLE_DEC_TYPE_WAV:
case ESP_AUDIO_SIMPLE_DEC_TYPE_M4A:
case ESP_AUDIO_SIMPLE_DEC_TYPE_TS:
break;
case ESP_AUDIO_SIMPLE_DEC_TYPE_AAC:
if (dec_cfg->dec_cfg == NULL) {
esp_aac_dec_cfg_t aac_cfg = {
.no_adts_header = false,
.aac_plus_enable = true,
};
ret = audio_dec_set_subcfg(dec_cfg, &aac_cfg, sizeof(esp_aac_dec_cfg_t));
}
esp_aac_dec_cfg_t *aac_cfg = (esp_aac_dec_cfg_t *)dec_cfg->dec_cfg;
aac_cfg->sample_rate = info->sample_rates;
aac_cfg->channel = info->channels;
aac_cfg->bits_per_sample = info->bits;
break;
case ESP_AUDIO_SIMPLE_DEC_TYPE_RAW_OPUS:
if (dec_cfg->dec_cfg == NULL) {
dec_cfg->dec_type = ESP_AUDIO_SIMPLE_DEC_TYPE_RAW_OPUS;
esp_opus_dec_cfg_t opus_cfg = ESP_OPUS_DEC_CONFIG_DEFAULT();
opus_cfg.frame_duration = ESP_OPUS_DEC_FRAME_DURATION_20_MS;
opus_cfg.channel = info->channels;
opus_cfg.sample_rate = info->sample_rates;
ret = audio_dec_set_subcfg(dec_cfg, &opus_cfg, sizeof(esp_opus_dec_cfg_t));
} else {
esp_opus_dec_cfg_t *opus_cfg = (esp_opus_dec_cfg_t *)dec_cfg->dec_cfg;
opus_cfg->channel = info->channels;
opus_cfg->sample_rate = info->sample_rates;
}
break;
case ESP_AUDIO_SIMPLE_DEC_TYPE_PCM:
esp_pcm_dec_cfg_t pcm_cfg = {
.sample_rate = info->sample_rates,
.channel = info->channels,
.bits_per_sample = info->bits,
};
ret = audio_dec_set_subcfg(dec_cfg, &pcm_cfg, sizeof(esp_pcm_dec_cfg_t));
break;
case ESP_AUDIO_SIMPLE_DEC_TYPE_G711A:
case ESP_AUDIO_SIMPLE_DEC_TYPE_G711U:
esp_g711_dec_cfg_t g711_cfg = {
.channel = info->channels,
};
ret = audio_dec_set_subcfg(dec_cfg, &g711_cfg, sizeof(esp_g711_dec_cfg_t));
break;
case ESP_AUDIO_SIMPLE_DEC_TYPE_ADPCM:
dec_cfg->dec_type = ESP_AUDIO_SIMPLE_DEC_TYPE_ADPCM;
esp_adpcm_dec_cfg_t adpcm_cfg = {
.bits_per_sample = 4,
.channel = info->channels,
.sample_rate = info->sample_rates,
};
ret = audio_dec_set_subcfg(dec_cfg, &adpcm_cfg, sizeof(esp_adpcm_dec_cfg_t));
break;
case ESP_AUDIO_SIMPLE_DEC_TYPE_SBC:
if (dec_cfg->dec_cfg == NULL) {
dec_cfg->dec_type = ESP_AUDIO_SIMPLE_DEC_TYPE_SBC;
esp_sbc_dec_cfg_t sbc_cfg = {
.sbc_mode = ESP_SBC_MODE_STD,
.ch_num = 2,
.enable_plc = false,
};
ret = audio_dec_set_subcfg(dec_cfg, &sbc_cfg, sizeof(esp_sbc_dec_cfg_t));
}
break;
case ESP_AUDIO_SIMPLE_DEC_TYPE_LC3:
if (dec_cfg->dec_cfg == NULL) {
dec_cfg->dec_type = ESP_AUDIO_SIMPLE_DEC_TYPE_LC3;
esp_lc3_dec_cfg_t lc3_cfg = ESP_LC3_DEC_CONFIG_DEFAULT();
lc3_cfg.channel = info->channels;
lc3_cfg.sample_rate = info->sample_rates;
lc3_cfg.bits_per_sample = info->bits;
ret = audio_dec_set_subcfg(dec_cfg, &lc3_cfg, sizeof(esp_lc3_dec_cfg_t));
} else {
esp_lc3_dec_cfg_t *lc3_cfg = (esp_lc3_dec_cfg_t *)dec_cfg->dec_cfg;
lc3_cfg->channel = info->channels;
lc3_cfg->sample_rate = info->sample_rates;
lc3_cfg->bits_per_sample = info->bits;
}
break;
default:
dec_cfg->dec_type = ESP_AUDIO_SIMPLE_DEC_TYPE_NONE;
ESP_LOGW(TAG, "Not support for simple decoder type %ld", info->format_id);
return ESP_GMF_ERR_NOT_SUPPORT;
}
ESP_LOGD(TAG, "The new dec type is %d", dec_cfg->dec_type);
return ((ret == ESP_GMF_ERR_OK) ? (ESP_GMF_ERR_OK) : (ret));
}
static inline void free_esp_audio_simple_cfg(esp_audio_simple_dec_cfg_t *config)
{
if (config) {
if (config->dec_cfg) {
esp_gmf_oal_free(config->dec_cfg);
config->cfg_size = 0;
}
esp_gmf_oal_free(config);
}
}
static esp_gmf_err_t esp_gmf_audio_dec_new(void *cfg, esp_gmf_obj_handle_t *handle)
{
return esp_gmf_audio_dec_init((esp_audio_simple_dec_cfg_t *)cfg, (esp_gmf_element_handle_t *)handle);
}
static esp_gmf_job_err_t esp_gmf_audio_dec_open(esp_gmf_element_handle_t self, void *para)
{
esp_gmf_audio_dec_t *audio_dec = (esp_gmf_audio_dec_t *)self;
esp_audio_simple_dec_cfg_t *dec_cfg = (esp_audio_simple_dec_cfg_t *)OBJ_GET_CFG(self);
ESP_GMF_NULL_CHECK(TAG, dec_cfg, return ESP_GMF_ERR_FAIL);
esp_audio_simple_dec_open(dec_cfg, &audio_dec->dec_hd);
ESP_GMF_CHECK(TAG, audio_dec->dec_hd, {return ESP_GMF_JOB_ERR_FAIL;}, "Failed to create simple decoder handle");
esp_gmf_port_enable_payload_share(ESP_GMF_ELEMENT_GET(self)->in, false);
audio_dec->buf_size = DEFAULT_DEC_OUTPUT_BUFFER_SIZE;
ESP_LOGD(TAG, "Open, el: %p, cfg: %p, type: %d", self, dec_cfg, dec_cfg->dec_type);
return ESP_GMF_JOB_ERR_OK;
}
static esp_gmf_job_err_t esp_gmf_audio_dec_process(esp_gmf_element_handle_t self, void *para)
{
esp_gmf_port_t *in_port = ESP_GMF_ELEMENT_GET(self)->in;
esp_gmf_port_t *out = ESP_GMF_ELEMENT_GET(self)->out;
esp_audio_err_t ret = ESP_AUDIO_ERR_OK;
esp_gmf_job_err_t out_len = ESP_GMF_JOB_ERR_OK;
esp_gmf_err_io_t load_ret = ESP_GMF_IO_OK;
esp_gmf_audio_dec_t *audio_dec = (esp_gmf_audio_dec_t *)self;
esp_gmf_payload_t *out_load = NULL;
esp_audio_simple_dec_info_t dec_info = {0};
esp_gmf_info_sound_t snd_info = {0};
if (audio_dec->in_data.len == 0) {
load_ret = esp_gmf_port_acquire_in(in_port, &audio_dec->in_load, ESP_GMF_ELEMENT_GET(audio_dec)->in_attr.data_size, in_port->wait_ticks);
ESP_GMF_PORT_ACQUIRE_IN_CHECK(TAG, load_ret, out_len, {goto __aud_proc_release;});
audio_dec->in_data.buffer = audio_dec->in_load->buf;
audio_dec->in_data.len = audio_dec->in_load->valid_size;
audio_dec->in_data.consumed = 0;
audio_dec->in_data.eos = audio_dec->in_load->is_done;
audio_dec->in_data.frame_recover = (audio_dec->in_load->meta_flag & ESP_GMF_META_FLAG_AUD_RECOVERY_PLC) ? 1 : 0;
}
ESP_LOGV(TAG, "Read, in_len: %ld, done: %d\r\n", audio_dec->in_data.len, audio_dec->in_load ? audio_dec->in_load->is_done : -1);
load_ret = esp_gmf_port_acquire_out(out, &out_load, audio_dec->buf_size, ESP_GMF_MAX_DELAY);
ESP_GMF_PORT_ACQUIRE_OUT_CHECK(TAG, load_ret, out_len, {goto __aud_proc_release;});
out_load->valid_size = 0;
audio_dec->out_data.buffer = out_load->buf;
audio_dec->out_data.len = out_load->buf_length;
if (audio_dec->in_data.len == 0) {
if (audio_dec->in_load->is_done == true) {
out_len = ESP_GMF_JOB_ERR_DONE;
out_load->is_done = audio_dec->in_load->is_done;
ESP_LOGD(TAG, "Return done, line:%d", __LINE__);
goto __aud_proc_release;
} else {
out_len = ESP_GMF_JOB_ERR_CONTINUE;
ESP_LOGD(TAG, "Return Continue, size:%d", audio_dec->in_load->valid_size);
goto __aud_proc_release;
}
}
while (1) {
ret = esp_audio_simple_dec_process(audio_dec->dec_hd, &audio_dec->in_data, &audio_dec->out_data);
if (ret != ESP_AUDIO_ERR_OK && ret != ESP_AUDIO_ERR_BUFF_NOT_ENOUGH) {
ESP_LOGE(TAG, "Failed to decode data, ret: %d", ret);
out_len = ESP_GMF_JOB_ERR_FAIL;
goto __aud_proc_release;
}
if (ret == ESP_AUDIO_ERR_BUFF_NOT_ENOUGH) {
load_ret = esp_gmf_port_release_out(out, out_load, out->wait_ticks);
ESP_GMF_PORT_RELEASE_OUT_CHECK(TAG, load_ret, out_len, {goto __aud_proc_release;});
load_ret = esp_gmf_port_acquire_out(out, &out_load, audio_dec->out_data.needed_size, ESP_GMF_MAX_DELAY);
ESP_GMF_PORT_ACQUIRE_OUT_CHECK(TAG, load_ret, out_len, {goto __aud_proc_release;});
ESP_LOGW(TAG, "Not enough memory for out, need:%d, old: %ld, new: %d", (int)audio_dec->out_data.needed_size,
audio_dec->out_data.len, out_load->buf_length);
audio_dec->out_data.buffer = out_load->buf;
audio_dec->out_data.len = out_load->buf_length;
audio_dec->buf_size = audio_dec->out_data.needed_size;
continue;
}
if (audio_dec->in_data.consumed <= audio_dec->in_data.len) {
audio_dec->in_data.buffer += audio_dec->in_data.consumed;
audio_dec->in_data.len -= audio_dec->in_data.consumed;
}
ESP_LOGV(TAG, "Dec, out len: %ld, need: %ld, in len: %ld, consumed: %ld, dec: %ld",
audio_dec->out_data.len, audio_dec->out_data.needed_size,
audio_dec->in_data.len, audio_dec->in_data.consumed, audio_dec->out_data.decoded_size);
ESP_LOGV(TAG, "buf: %p, sz: %d, dec: %ld", out_load->buf, out_load->valid_size, audio_dec->out_data.decoded_size);
if (audio_dec->out_data.decoded_size > 0) {
esp_audio_simple_dec_get_info(audio_dec->dec_hd, &dec_info);
esp_gmf_audio_el_get_snd_info(self, &snd_info);
if (snd_info.sample_rates != dec_info.sample_rate
|| snd_info.channels != dec_info.channel
|| snd_info.bits != dec_info.bits_per_sample) {
ESP_LOGD(TAG, "NOTIFY Info, rate: %d, bits: %d, ch: %d --> rate: %ld, bits: %d, ch: %d",
snd_info.sample_rates, snd_info.bits, snd_info.channels, dec_info.sample_rate, dec_info.bits_per_sample, dec_info.channel);
GMF_AUDIO_UPDATE_SND_INFO(self, dec_info.sample_rate, dec_info.bits_per_sample, dec_info.channel);
}
out_load->valid_size = audio_dec->out_data.decoded_size;
out_load->pts = audio_dec->pts;
audio_dec->pts += GMF_AUDIO_CALC_PTS(out_load->valid_size, dec_info.sample_rate, dec_info.channel, dec_info.bits_per_sample);
esp_gmf_audio_el_update_file_pos(self, out_load->valid_size);
if (audio_dec->in_load != NULL && audio_dec->in_data.len > 0) {
ESP_LOGD(TAG, "Return truncate, in len:%ld", audio_dec->in_data.len);
out_len = ESP_GMF_JOB_ERR_TRUNCATE;
}
} else {
if (audio_dec->in_data.len > 0) {
continue;
}
if (audio_dec->in_load && audio_dec->in_load->is_done) {
out_load->is_done = audio_dec->in_load->is_done;
ESP_LOGD(TAG, "Return done, line:%d", __LINE__);
out_len = ESP_GMF_JOB_ERR_DONE;
} else {
ESP_LOGD(TAG, "Return Continue, in len:%ld", audio_dec->in_data.len);
out_len = ESP_GMF_JOB_ERR_CONTINUE;
}
}
ESP_LOGV(TAG, "Release IN, in_len: %ld, done: %d, decoded_size: %ld",
audio_dec->in_data.len, audio_dec->in_load ? audio_dec->in_load->is_done : -1, audio_dec->out_data.decoded_size);
break;
}
__aud_proc_release:
if (out_load != NULL) {
load_ret = esp_gmf_port_release_out(out, out_load, out->wait_ticks);
if ((load_ret < ESP_GMF_IO_OK) && (load_ret != ESP_GMF_IO_ABORT)) {
ESP_LOGE(TAG, "OUT port release error, ret:%d", load_ret);
out_len = ESP_GMF_JOB_ERR_FAIL;
}
}
// If decoding fails or there is no data on input side, release input port
if ((out_len == ESP_GMF_JOB_ERR_FAIL) || (audio_dec->in_load && (audio_dec->in_data.len == 0))) {
load_ret = esp_gmf_port_release_in(in_port, audio_dec->in_load, ESP_GMF_MAX_DELAY);
if ((load_ret < ESP_GMF_IO_OK) && (load_ret != ESP_GMF_IO_ABORT)) {
ESP_LOGE(TAG, "IN port release error, ret:%d", load_ret);
out_len = ESP_GMF_JOB_ERR_FAIL;
}
audio_dec->in_load = NULL;
}
return out_len;
}
static esp_gmf_job_err_t esp_gmf_audio_dec_close(esp_gmf_element_handle_t self, void *para)
{
ESP_LOGD(TAG, "Closed, %p", self);
esp_gmf_audio_dec_t *audio_dec = (esp_gmf_audio_dec_t *)self;
if (audio_dec->dec_hd != NULL) {
esp_audio_simple_dec_close(audio_dec->dec_hd);
audio_dec->dec_hd = NULL;
}
audio_dec->pts = 0;
esp_gmf_info_sound_t snd_info = {0};
audio_dec->in_load = NULL;
audio_dec->in_data.len = 0;
esp_gmf_audio_el_set_snd_info(self, &snd_info);
return ESP_GMF_ERR_OK;
}
static esp_gmf_err_t esp_gmf_audio_dec_destroy(esp_gmf_element_handle_t self)
{
ESP_LOGD(TAG, "Destroyed, %p", self);
free_esp_audio_simple_cfg(OBJ_GET_CFG(self));
esp_gmf_audio_el_deinit(self);
esp_gmf_oal_free(self);
return ESP_GMF_ERR_OK;
}
static esp_gmf_err_t _load_dec_caps_func(esp_gmf_element_handle_t handle)
{
esp_gmf_cap_t *caps = NULL;
esp_gmf_cap_t dec_caps = {0};
dec_caps.cap_eightcc = ESP_GMF_CAPS_AUDIO_DECODER;
dec_caps.attr_fun = _dec_caps_iter_fun;
int ret = esp_gmf_cap_append(&caps, &dec_caps);
ESP_GMF_RET_ON_NOT_OK(TAG, ret, {return ret;}, "Failed to create capability");
esp_gmf_element_t *el = (esp_gmf_element_t *)handle;
el->caps = caps;
return ESP_GMF_ERR_OK;
}
static esp_gmf_err_t _load_dec_methods_func(esp_gmf_element_handle_t handle)
{
esp_gmf_method_t *method = NULL;
esp_gmf_args_desc_t *set_args = NULL;
esp_gmf_args_desc_t *reconfig_args = NULL;
esp_gmf_args_desc_t *sndinfo_args = NULL;
esp_gmf_err_t ret = esp_gmf_args_desc_append(&sndinfo_args, AMETHOD_ARG(DECODER, RECONFIG_BY_SND_INFO, INFO_TYPE), ESP_GMF_ARGS_TYPE_UINT32,
sizeof(uint32_t), offsetof(esp_gmf_info_sound_t, format_id));
ESP_GMF_RET_ON_NOT_OK(TAG, ret, return ret, "Failed to append type argument");
ret = esp_gmf_args_desc_append(&sndinfo_args, AMETHOD_ARG(DECODER, RECONFIG_BY_SND_INFO, INFO_SAMPLERATE), ESP_GMF_ARGS_TYPE_INT32,
sizeof(int32_t), offsetof(esp_gmf_info_sound_t, sample_rates));
ESP_GMF_RET_ON_NOT_OK(TAG, ret, return ret, "Failed to append sample_rates argument");
ret = esp_gmf_args_desc_append(&sndinfo_args, AMETHOD_ARG(DECODER, RECONFIG_BY_SND_INFO, INFO_CHANNEL), ESP_GMF_ARGS_TYPE_INT8,
sizeof(int8_t), 12);
ESP_GMF_RET_ON_NOT_OK(TAG, ret, return ret, "Failed to append channels argument");
ret = esp_gmf_args_desc_append(&sndinfo_args, AMETHOD_ARG(DECODER, RECONFIG_BY_SND_INFO, INFO_BITS), ESP_GMF_ARGS_TYPE_INT8,
sizeof(int8_t), 13);
ESP_GMF_RET_ON_NOT_OK(TAG, ret, return ret, "Failed to append bits argument");
ret = esp_gmf_args_desc_append_array(&set_args, AMETHOD_ARG(DECODER, RECONFIG_BY_SND_INFO, INFO), sndinfo_args,
sizeof(esp_gmf_info_sound_t), 0);
ESP_GMF_RET_ON_NOT_OK(TAG, ret, return ret, "Failed to append sound info argument");
ret = esp_gmf_method_append(&method, AMETHOD(DECODER, RECONFIG_BY_SND_INFO), __audio_dec_reconfig_by_sound_info, set_args);
ESP_GMF_RET_ON_ERROR(TAG, ret, return ret, "Failed to register %s method", AMETHOD(DECODER, RECONFIG_BY_SND_INFO));
set_args = NULL;
ret = esp_gmf_args_desc_append(&reconfig_args, AMETHOD_ARG(DECODER, RECONFIG, CFG_TYPE), ESP_GMF_ARGS_TYPE_INT32,
sizeof(int32_t), offsetof(esp_audio_simple_dec_cfg_t, dec_type));
ESP_GMF_RET_ON_NOT_OK(TAG, ret, return ret, "Failed to append type argument");
ret = esp_gmf_args_desc_append(&reconfig_args, AMETHOD_ARG(DECODER, RECONFIG, CFG_SUBCFGPTR), ESP_GMF_ARGS_TYPE_INT32,
sizeof(int32_t), offsetof(esp_audio_simple_dec_cfg_t, dec_cfg));
ESP_GMF_RET_ON_NOT_OK(TAG, ret, return ret, "Failed to append cfg argument");
ret = esp_gmf_args_desc_append(&reconfig_args, AMETHOD_ARG(DECODER, RECONFIG, CFG_SUBCFGSZ), ESP_GMF_ARGS_TYPE_UINT32,
sizeof(uint32_t), offsetof(esp_audio_simple_dec_cfg_t, cfg_size));
ESP_GMF_RET_ON_NOT_OK(TAG, ret, return ret, "Failed to append cfg_sz argument");
ret = esp_gmf_args_desc_append_array(&set_args, AMETHOD_ARG(DECODER, RECONFIG, CFG), reconfig_args,
sizeof(esp_audio_simple_dec_cfg_t), 0);
ESP_GMF_RET_ON_NOT_OK(TAG, ret, return ret, "Failed to append config argument");
ret = esp_gmf_method_append(&method, AMETHOD(DECODER, RECONFIG), __audio_dec_reconfig, set_args);
ESP_GMF_RET_ON_ERROR(TAG, ret, return ret, "Failed to register %s method", AMETHOD(DECODER, RECONFIG));
esp_gmf_element_t *el = (esp_gmf_element_t *)handle;
el->method = method;
return ESP_GMF_ERR_OK;
}
esp_gmf_err_t esp_gmf_audio_dec_reconfig(esp_gmf_element_handle_t handle, esp_audio_simple_dec_cfg_t *config)
{
ESP_GMF_NULL_CHECK(TAG, handle, return ESP_GMF_ERR_INVALID_ARG);
ESP_GMF_NULL_CHECK(TAG, config, return ESP_GMF_ERR_INVALID_ARG);
esp_gmf_event_state_t state = ESP_GMF_EVENT_STATE_NONE;
esp_gmf_element_get_state(handle, &state);
if (state < ESP_GMF_EVENT_STATE_OPENING) {
esp_audio_simple_dec_cfg_t *new_config = NULL;
esp_gmf_err_t ret = dupl_esp_audio_simple_cfg(config, &new_config);
ESP_GMF_RET_ON_NOT_OK(TAG, ret, return ret, "Failed to duplicate audio decoder configuration");
free_esp_audio_simple_cfg(OBJ_GET_CFG(handle));
esp_gmf_obj_set_config(handle, new_config, sizeof(*config));
return ESP_GMF_ERR_OK;
} else {
ESP_LOGE(TAG, "Failed to reconfig decoder due to invalid state: %s", esp_gmf_event_get_state_str(state));
return ESP_GMF_ERR_FAIL;
}
}
esp_gmf_err_t esp_gmf_audio_dec_reconfig_by_sound_info(esp_gmf_element_handle_t handle, esp_gmf_info_sound_t *info)
{
ESP_GMF_NULL_CHECK(TAG, handle, return ESP_GMF_ERR_INVALID_ARG);
esp_gmf_event_state_t state = ESP_GMF_EVENT_STATE_NONE;
esp_gmf_element_get_state(handle, &state);
if (state < ESP_GMF_EVENT_STATE_OPENING) {
esp_gmf_err_t ret = audio_dec_reconfig_dec_by_sound_info(handle, info);
ESP_GMF_RET_ON_NOT_OK(TAG, ret, return ret, "Failed to reconfig simple decoder by sound information");
return ESP_GMF_ERR_OK;
} else {
ESP_LOGE(TAG, "Failed to reconfig decoder due to invalid state: %s", esp_gmf_event_get_state_str(state));
return ESP_GMF_ERR_FAIL;
}
}
esp_gmf_err_t esp_gmf_audio_dec_init(esp_audio_simple_dec_cfg_t *config, esp_gmf_element_handle_t *handle)
{
ESP_GMF_NULL_CHECK(TAG, handle, {return ESP_GMF_ERR_INVALID_ARG;});
*handle = NULL;
esp_gmf_err_t ret = ESP_GMF_ERR_OK;
esp_gmf_audio_dec_t *dec_hd = esp_gmf_oal_calloc(1, sizeof(esp_gmf_audio_dec_t));
ESP_GMF_MEM_VERIFY(TAG, dec_hd, {return ESP_GMF_ERR_MEMORY_LACK;}, "audio decoder", sizeof(esp_gmf_audio_dec_t));
esp_gmf_obj_t *obj = (esp_gmf_obj_t *)dec_hd;
obj->new_obj = esp_gmf_audio_dec_new;
obj->del_obj = esp_gmf_audio_dec_destroy;
esp_audio_simple_dec_cfg_t *cfg = NULL;
if (config) {
dupl_esp_audio_simple_cfg(config, &cfg);
} else {
esp_audio_simple_dec_cfg_t dcfg = DEFAULT_ESP_GMF_AUDIO_DEC_CONFIG();
dupl_esp_audio_simple_cfg(&dcfg, &cfg);
}
ESP_GMF_CHECK(TAG, cfg, {ret = ESP_GMF_ERR_MEMORY_LACK; goto ES_DEC_FAIL;}, "Failed to allocate audio decoder configuration");
esp_gmf_obj_set_config(obj, cfg, sizeof(esp_audio_simple_dec_cfg_t));
ret = esp_gmf_obj_set_tag(obj, "aud_dec");
ESP_GMF_RET_ON_NOT_OK(TAG, ret, goto ES_DEC_FAIL, "Failed to set obj tag");
esp_gmf_element_cfg_t el_cfg = {0};
ESP_GMF_ELEMENT_IN_PORT_ATTR_SET(el_cfg.in_attr, ESP_GMF_EL_PORT_CAP_SINGLE, 0, 0,
ESP_GMF_PORT_TYPE_BLOCK | ESP_GMF_PORT_TYPE_BYTE, 512);
ESP_GMF_ELEMENT_OUT_PORT_ATTR_SET(el_cfg.out_attr, ESP_GMF_EL_PORT_CAP_SINGLE, 0, 0,
ESP_GMF_PORT_TYPE_BLOCK | ESP_GMF_PORT_TYPE_BYTE, 512);
el_cfg.dependency = false;
ret = esp_gmf_audio_el_init(dec_hd, &el_cfg);
ESP_GMF_RET_ON_NOT_OK(TAG, ret, goto ES_DEC_FAIL, "Failed to initialize audio decoder element");
ESP_LOGD(TAG, "Initialization, %s-%p", OBJ_GET_TAG(obj), obj);
ESP_GMF_ELEMENT_GET(dec_hd)->ops.open = esp_gmf_audio_dec_open;
ESP_GMF_ELEMENT_GET(dec_hd)->ops.process = esp_gmf_audio_dec_process;
ESP_GMF_ELEMENT_GET(dec_hd)->ops.close = esp_gmf_audio_dec_close;
ESP_GMF_ELEMENT_GET(dec_hd)->ops.load_caps = _load_dec_caps_func;
ESP_GMF_ELEMENT_GET(dec_hd)->ops.load_methods = _load_dec_methods_func;
*handle = obj;
return ESP_GMF_ERR_OK;
ES_DEC_FAIL:
esp_gmf_audio_dec_destroy(obj);
return ret;
}
@@ -0,0 +1,701 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO., LTD
* SPDX-License-Identifier: LicenseRef-Espressif-Modified-MIT
*
* See LICENSE file for details.
*/
#include <string.h>
#include "esp_log.h"
#include "esp_gmf_node.h"
#include "esp_gmf_oal_mem.h"
#include "esp_gmf_oal_mutex.h"
#include "esp_gmf_audio_enc.h"
#include "esp_audio_enc_default.h"
#include "esp_gmf_cache.h"
#include "esp_gmf_cap.h"
#include "esp_gmf_caps_def.h"
#include "esp_gmf_audio_methods_def.h"
#include "esp_gmf_audio_element.h"
#include "gmf_audio_common.h"
#define AUD_ENC_DEFAULT_INPUT_TIME_MS (20)
#define SET_ENC_BASIC_INFO(cfg, info) do { \
(cfg)->sample_rate = (info)->sample_rates; \
(cfg)->channel = (info)->channels; \
(cfg)->bits_per_sample = (info)->bits; \
} while (0)
/**
* @brief Audio encoder context in GMF
*/
typedef struct {
esp_gmf_audio_element_t parent; /*!< The GMF audio encoder handle */
esp_audio_enc_handle_t audio_enc_hd; /*!< The audio encoder handle */
esp_gmf_cache_t *cached_payload; /*!< A Cached payload for data concatenation */
uint32_t bitrate; /*!< The bitrate of the encoded data */
esp_gmf_payload_t *origin_in_load; /*!< The original input payload */
int64_t cur_pts; /*!< The audio Presentation Time Stamp(pts) */
} esp_gmf_audio_enc_t;
static const char *TAG = "ESP_GMF_AENC";
static esp_gmf_err_t __audio_enc_get_frame_size(esp_gmf_element_handle_t handle, esp_gmf_args_desc_t *arg_desc,
uint8_t *buf, int buf_len)
{
ESP_GMF_NULL_CHECK(TAG, arg_desc, return ESP_GMF_ERR_INVALID_ARG);
ESP_GMF_NULL_CHECK(TAG, buf, return ESP_GMF_ERR_INVALID_ARG);
esp_gmf_args_desc_t *enc_desc = arg_desc;
uint32_t *in_size = (uint32_t *)(buf);
enc_desc = enc_desc->next;
uint32_t *out_size = (uint32_t *)(buf + enc_desc->offset);
return esp_gmf_audio_enc_get_frame_size(handle, in_size, out_size);
}
static esp_gmf_err_t __audio_enc_set_bitrate(esp_gmf_element_handle_t handle, esp_gmf_args_desc_t *arg_desc,
uint8_t *buf, int buf_len)
{
ESP_GMF_NULL_CHECK(TAG, arg_desc, return ESP_GMF_ERR_INVALID_ARG);
ESP_GMF_NULL_CHECK(TAG, buf, return ESP_GMF_ERR_INVALID_ARG);
uint32_t bitrate = *((uint32_t *)buf);
return esp_gmf_audio_enc_set_bitrate(handle, bitrate);
}
static esp_gmf_err_t __audio_enc_get_bitrate(esp_gmf_element_handle_t handle, esp_gmf_args_desc_t *arg_desc,
uint8_t *buf, int buf_len)
{
ESP_GMF_NULL_CHECK(TAG, arg_desc, return ESP_GMF_ERR_INVALID_ARG);
ESP_GMF_NULL_CHECK(TAG, buf, return ESP_GMF_ERR_INVALID_ARG);
uint32_t *bitrate = (uint32_t *)buf;
return esp_gmf_audio_enc_get_bitrate(handle, bitrate);
}
static esp_gmf_err_t __audio_enc_reconfig(esp_gmf_element_handle_t handle, esp_gmf_args_desc_t *arg_desc,
uint8_t *buf, int buf_len)
{
ESP_GMF_NULL_CHECK(TAG, arg_desc, return ESP_GMF_ERR_INVALID_ARG);
ESP_GMF_NULL_CHECK(TAG, buf, return ESP_GMF_ERR_INVALID_ARG);
esp_audio_enc_config_t *config = (esp_audio_enc_config_t *)buf;
return esp_gmf_audio_enc_reconfig(handle, config);
}
static esp_gmf_err_t __audio_enc_reconfig_by_sound_info(esp_gmf_element_handle_t handle, esp_gmf_args_desc_t *arg_desc,
uint8_t *buf, int buf_len)
{
ESP_GMF_NULL_CHECK(TAG, arg_desc, return ESP_GMF_ERR_INVALID_ARG);
ESP_GMF_NULL_CHECK(TAG, buf, return ESP_GMF_ERR_INVALID_ARG);
esp_gmf_info_sound_t *snd_info = (esp_gmf_info_sound_t *)buf;
return esp_gmf_audio_enc_reconfig_by_sound_info(handle, snd_info);
}
static inline esp_gmf_err_t dupl_esp_gmf_audio_enc_cfg(esp_audio_enc_config_t *config, esp_audio_enc_config_t **new_config)
{
void *sub_cfg = NULL;
*new_config = esp_gmf_oal_calloc(1, sizeof(*config));
ESP_GMF_MEM_VERIFY(TAG, *new_config, {return ESP_GMF_ERR_MEMORY_LACK;}, "audio encoder handle configuration", sizeof(*config));
memcpy(*new_config, config, sizeof(*config));
if (config->cfg && (config->cfg_sz > 0)) {
sub_cfg = esp_gmf_oal_calloc(1, config->cfg_sz);
ESP_GMF_MEM_VERIFY(TAG, sub_cfg, {esp_gmf_oal_free(*new_config); return ESP_GMF_ERR_MEMORY_LACK;},
"audio encoder configuration", (int)config->cfg_sz);
memcpy(sub_cfg, config->cfg, config->cfg_sz);
(*new_config)->cfg = sub_cfg;
}
return ESP_GMF_JOB_ERR_OK;
}
static inline void free_esp_gmf_audio_enc_cfg(esp_audio_enc_config_t *config)
{
if (config) {
if (config->cfg) {
esp_gmf_oal_free(config->cfg);
config->cfg = NULL;
config->cfg_sz = 0;
}
esp_gmf_oal_free(config);
}
}
static inline void audio_enc_change_audio_info(esp_audio_enc_config_t *enc_cfg, esp_gmf_info_sound_t *info)
{
switch (enc_cfg->type) {
case ESP_AUDIO_TYPE_AAC: {
esp_aac_enc_config_t *aac_enc_cfg = (esp_aac_enc_config_t *)enc_cfg->cfg;
SET_ENC_BASIC_INFO(aac_enc_cfg, info);
break;
}
case ESP_AUDIO_TYPE_AMRNB: {
esp_amrnb_enc_config_t *amr_enc_cfg = (esp_amrnb_enc_config_t *)enc_cfg->cfg;
SET_ENC_BASIC_INFO(amr_enc_cfg, info);
break;
}
case ESP_AUDIO_TYPE_G711U:
case ESP_AUDIO_TYPE_G711A: {
esp_g711_enc_config_t *g711_enc_cfg = (esp_g711_enc_config_t *)enc_cfg->cfg;
SET_ENC_BASIC_INFO(g711_enc_cfg, info);
break;
}
case ESP_AUDIO_TYPE_AMRWB: {
esp_amrnb_enc_config_t *amr_enc_cfg = (esp_amrnb_enc_config_t *)enc_cfg->cfg;
SET_ENC_BASIC_INFO(amr_enc_cfg, info);
break;
}
case ESP_AUDIO_TYPE_ALAC: {
esp_alac_enc_config_t *alac_enc_cfg = (esp_alac_enc_config_t *)enc_cfg->cfg;
SET_ENC_BASIC_INFO(alac_enc_cfg, info);
break;
}
case ESP_AUDIO_TYPE_PCM: {
esp_pcm_enc_config_t *pcm_enc_cfg = (esp_pcm_enc_config_t *)enc_cfg->cfg;
SET_ENC_BASIC_INFO(pcm_enc_cfg, info);
break;
}
case ESP_AUDIO_TYPE_OPUS: {
esp_opus_enc_config_t *opus_enc_cfg = (esp_opus_enc_config_t *)enc_cfg->cfg;
SET_ENC_BASIC_INFO(opus_enc_cfg, info);
break;
}
case ESP_AUDIO_TYPE_ADPCM: {
esp_adpcm_enc_config_t *adpcm_enc_cfg = (esp_adpcm_enc_config_t *)enc_cfg->cfg;
SET_ENC_BASIC_INFO(adpcm_enc_cfg, info);
break;
}
case ESP_AUDIO_TYPE_LC3: {
esp_lc3_enc_config_t *lc3_enc_cfg = (esp_lc3_enc_config_t *)enc_cfg->cfg;
SET_ENC_BASIC_INFO(lc3_enc_cfg, info);
break;
}
case ESP_AUDIO_TYPE_SBC: {
esp_sbc_enc_config_t *sbc_enc_cfg = (esp_sbc_enc_config_t *)enc_cfg->cfg;
sbc_enc_cfg->sample_rate = info->sample_rates;
sbc_enc_cfg->bits_per_sample = info->bits;
if (info->channels == 1) {
sbc_enc_cfg->ch_mode = ESP_SBC_CH_MODE_MONO;
} else if (info->channels == 2) {
sbc_enc_cfg->ch_mode = ((sbc_enc_cfg->ch_mode > ESP_SBC_CH_MODE_MONO && sbc_enc_cfg->ch_mode <= ESP_SBC_CH_MODE_JOINT_STEREO) ? (sbc_enc_cfg->ch_mode) : (ESP_SBC_CH_MODE_DUAL));
} else {
sbc_enc_cfg->ch_mode = ESP_SBC_CH_MODE_INVALID;
}
break;
}
default:
break;
}
}
static esp_gmf_err_t audio_enc_set_subcfg(esp_audio_enc_config_t *enc_cfg, void *sub_cfg, int32_t sub_cfg_sz)
{
enc_cfg->cfg = esp_gmf_oal_calloc(1, sub_cfg_sz);
ESP_GMF_MEM_CHECK(TAG, enc_cfg->cfg, return ESP_GMF_ERR_MEMORY_LACK;);
enc_cfg->cfg_sz = sub_cfg_sz;
memcpy(enc_cfg->cfg, sub_cfg, sub_cfg_sz);
return ESP_GMF_ERR_OK;
}
static esp_gmf_err_t audio_enc_reconfig_enc_by_sound_info(esp_gmf_element_handle_t handle, esp_gmf_info_sound_t *info)
{
esp_audio_enc_config_t *cfg = (esp_audio_enc_config_t *)OBJ_GET_CFG(handle);
if (cfg == NULL) {
cfg = esp_gmf_oal_calloc(1, sizeof(esp_audio_enc_config_t));
ESP_GMF_MEM_VERIFY(TAG, cfg, return ESP_GMF_ERR_MEMORY_LACK, "audio encoder configuration", sizeof(esp_audio_enc_config_t));
esp_gmf_obj_set_config(handle, cfg, sizeof(esp_audio_enc_config_t));
}
esp_gmf_err_t ret = ESP_GMF_ERR_OK;
bool same_type = (cfg->type == info->format_id) ? true : false;
// free sub cfg first
if (cfg->cfg && (same_type == false)) {
esp_gmf_oal_free(cfg->cfg);
cfg->cfg = NULL;
cfg->cfg_sz = 0;
}
cfg->type = info->format_id;
if (same_type == true && (cfg->cfg != NULL)) {
audio_enc_change_audio_info(cfg, info);
return ESP_GMF_ERR_OK;
}
switch (info->format_id) {
case ESP_AUDIO_TYPE_AAC: {
esp_aac_enc_config_t aac_enc_cfg = ESP_AAC_ENC_CONFIG_DEFAULT();
SET_ENC_BASIC_INFO(&aac_enc_cfg, info);
aac_enc_cfg.bitrate = info->bitrate;
ret = audio_enc_set_subcfg(cfg, &aac_enc_cfg, sizeof(esp_aac_enc_config_t));
break;
}
case ESP_AUDIO_TYPE_AMRNB: {
esp_amrnb_enc_config_t amr_enc_cfg = ESP_AMRNB_ENC_CONFIG_DEFAULT();
SET_ENC_BASIC_INFO(&amr_enc_cfg, info);
amr_enc_cfg.bitrate_mode = info->bitrate;
ret = audio_enc_set_subcfg(cfg, &amr_enc_cfg, sizeof(esp_amrnb_enc_config_t));
break;
}
case ESP_AUDIO_TYPE_AMRWB: {
esp_amrnb_enc_config_t amr_enc_cfg = ESP_AMRNB_ENC_CONFIG_DEFAULT();
SET_ENC_BASIC_INFO(&amr_enc_cfg, info);
amr_enc_cfg.bitrate_mode = info->bitrate;
ret = audio_enc_set_subcfg(cfg, &amr_enc_cfg, sizeof(esp_amrnb_enc_config_t));
break;
}
case ESP_AUDIO_TYPE_G711A: {
esp_g711_enc_config_t g711_enc_cfg = ESP_G711_ENC_CONFIG_DEFAULT();
SET_ENC_BASIC_INFO(&g711_enc_cfg, info);
ret = audio_enc_set_subcfg(cfg, &g711_enc_cfg, sizeof(esp_g711_enc_config_t));
break;
}
case ESP_AUDIO_TYPE_G711U: {
esp_g711_enc_config_t g711_enc_cfg = ESP_G711_ENC_CONFIG_DEFAULT();
SET_ENC_BASIC_INFO(&g711_enc_cfg, info);
ret = audio_enc_set_subcfg(cfg, &g711_enc_cfg, sizeof(esp_g711_enc_config_t));
break;
}
case ESP_AUDIO_TYPE_ALAC: {
esp_alac_enc_config_t alac_enc_cfg = ESP_ALAC_ENC_CONFIG_DEFAULT();
SET_ENC_BASIC_INFO(&alac_enc_cfg, info);
ret = audio_enc_set_subcfg(cfg, &alac_enc_cfg, sizeof(esp_alac_enc_config_t));
break;
}
case ESP_AUDIO_TYPE_PCM: {
esp_pcm_enc_config_t pcm_enc_cfg = ESP_PCM_ENC_CONFIG_DEFAULT();
SET_ENC_BASIC_INFO(&pcm_enc_cfg, info);
ret = audio_enc_set_subcfg(cfg, &pcm_enc_cfg, sizeof(esp_pcm_enc_config_t));
break;
}
case ESP_AUDIO_TYPE_OPUS: {
esp_opus_enc_config_t opus_enc_cfg = ESP_OPUS_ENC_CONFIG_DEFAULT();
SET_ENC_BASIC_INFO(&opus_enc_cfg, info);
opus_enc_cfg.bitrate = info->bitrate;
ret = audio_enc_set_subcfg(cfg, &opus_enc_cfg, sizeof(esp_opus_enc_config_t));
break;
}
case ESP_AUDIO_TYPE_ADPCM: {
esp_adpcm_enc_config_t adpcm_enc_cfg = ESP_ADPCM_ENC_CONFIG_DEFAULT();
SET_ENC_BASIC_INFO(&adpcm_enc_cfg, info);
ret = audio_enc_set_subcfg(cfg, &adpcm_enc_cfg, sizeof(esp_adpcm_enc_config_t));
break;
}
case ESP_AUDIO_TYPE_LC3: {
esp_lc3_enc_config_t lc3_enc_cfg = ESP_LC3_ENC_CONFIG_DEFAULT();
SET_ENC_BASIC_INFO(&lc3_enc_cfg, info);
ret = audio_enc_set_subcfg(cfg, &lc3_enc_cfg, sizeof(esp_lc3_enc_config_t));
break;
}
case ESP_AUDIO_TYPE_SBC: {
esp_sbc_enc_config_t sbc_enc_cfg = ESP_SBC_STD_ENC_CONFIG_DEFAULT();
sbc_enc_cfg.sample_rate = info->sample_rates;
sbc_enc_cfg.bits_per_sample = info->bits;
if (info->channels == 1) {
sbc_enc_cfg.ch_mode = ESP_SBC_CH_MODE_MONO;
} else if (info->channels == 2) {
sbc_enc_cfg.ch_mode = ESP_SBC_CH_MODE_DUAL;
} else {
sbc_enc_cfg.ch_mode = ESP_SBC_CH_MODE_INVALID;
}
ret = audio_enc_set_subcfg(cfg, &sbc_enc_cfg, sizeof(esp_sbc_enc_config_t));
break;
}
default:
ESP_LOGE(TAG, "Not support for encoder, %ld", info->format_id);
cfg->type = ESP_AUDIO_TYPE_UNSUPPORT;
return ESP_GMF_ERR_NOT_SUPPORT;
}
return ((ret == ESP_GMF_ERR_OK) ? (ESP_GMF_ERR_OK) : (ret));
}
static esp_gmf_job_err_t gmf_audio_enc_acquire_in(esp_gmf_audio_enc_t *audio_enc, esp_gmf_port_handle_t in_port, esp_gmf_payload_t **in_load)
{
bool needed_load = false;
esp_gmf_job_err_t job_ret = ESP_GMF_JOB_ERR_OK;
esp_gmf_cache_ready_for_load(audio_enc->cached_payload, &needed_load);
if (needed_load) {
esp_gmf_err_io_t load_ret = esp_gmf_port_acquire_in(in_port, &audio_enc->origin_in_load, ESP_GMF_ELEMENT_GET(audio_enc)->in_attr.data_size, in_port->wait_ticks);
ESP_GMF_PORT_ACQUIRE_IN_CHECK(TAG, load_ret, job_ret, return job_ret);
int cache_size = 0;
esp_gmf_cache_get_cached_size(audio_enc->cached_payload, &cache_size);
audio_enc->cur_pts = audio_enc->origin_in_load->pts - GMF_AUDIO_CALC_PTS(cache_size, audio_enc->parent.snd_info.sample_rates, audio_enc->parent.snd_info.channels, audio_enc->parent.snd_info.bits);
esp_gmf_cache_load(audio_enc->cached_payload, audio_enc->origin_in_load);
}
esp_gmf_err_t ret = esp_gmf_cache_acquire(audio_enc->cached_payload, ESP_GMF_ELEMENT_GET(audio_enc)->in_attr.data_size, in_load);
if (ret != ESP_GMF_ERR_OK) {
job_ret = ((ret == ESP_GMF_ERR_NOT_ENOUGH) ? (ESP_GMF_JOB_ERR_CONTINUE) : (ESP_GMF_JOB_ERR_FAIL));
}
return job_ret;
}
static esp_gmf_err_t esp_gmf_audio_enc_new(void *cfg, esp_gmf_obj_handle_t *handle)
{
return esp_gmf_audio_enc_init(cfg, (esp_gmf_element_handle_t *)handle);
}
static esp_gmf_job_err_t esp_gmf_audio_enc_open(esp_gmf_element_handle_t self, void *para)
{
esp_gmf_audio_enc_t *enc = (esp_gmf_audio_enc_t *)self;
esp_audio_enc_config_t *enc_cfg = (esp_audio_enc_config_t *)OBJ_GET_CFG(enc);
ESP_GMF_CHECK(TAG, enc_cfg, {return ESP_GMF_JOB_ERR_FAIL;}, "There is no encoder configuration");
esp_audio_err_t ret = esp_audio_enc_open(enc_cfg, &enc->audio_enc_hd);
ESP_GMF_CHECK(TAG, enc->audio_enc_hd, {return ESP_GMF_JOB_ERR_FAIL;}, "Failed to create audio encoder handle");
if (esp_audio_enc_get_frame_size(enc->audio_enc_hd, &ESP_GMF_ELEMENT_GET(enc)->in_attr.data_size, &ESP_GMF_ELEMENT_GET(enc)->out_attr.data_size) != ESP_AUDIO_ERR_OK) {
ESP_LOGE(TAG, "Failed to obtain frame size, ret: %d", ret);
return ESP_GMF_JOB_ERR_FAIL;
}
esp_gmf_port_enable_payload_share(ESP_GMF_ELEMENT_GET(self)->in, false);
esp_gmf_cache_new(ESP_GMF_ELEMENT_GET(enc)->in_attr.data_size, &enc->cached_payload);
ESP_GMF_CHECK(TAG, enc->cached_payload, {return ESP_GMF_JOB_ERR_FAIL;}, "Failed to new a cached payload on open");
esp_audio_enc_info_t enc_info = {0};
esp_audio_enc_get_info(enc->audio_enc_hd, &enc_info);
GMF_AUDIO_UPDATE_SND_INFO(self, enc_info.sample_rate, enc_info.bits_per_sample, enc_info.channel);
ESP_LOGI(TAG, "Open, type:%s, acquire in frame: %d, out frame: %d", esp_audio_codec_get_name(enc_cfg->type), ESP_GMF_ELEMENT_GET(enc)->in_attr.data_size, ESP_GMF_ELEMENT_GET(enc)->out_attr.data_size);
return ESP_GMF_JOB_ERR_OK;
}
static esp_gmf_job_err_t esp_gmf_audio_enc_process(esp_gmf_element_handle_t self, void *para)
{
// Get audio encoder instance and initialize variables
esp_gmf_audio_enc_t *audio_enc = (esp_gmf_audio_enc_t *)self;
esp_gmf_job_err_t out_len = ESP_GMF_JOB_ERR_OK;
esp_audio_err_t ret = ESP_AUDIO_ERR_OK;
esp_gmf_port_handle_t in_port = ESP_GMF_ELEMENT_GET(self)->in;
esp_gmf_port_handle_t out_port = ESP_GMF_ELEMENT_GET(self)->out;
esp_gmf_payload_t *out_load = NULL;
esp_audio_enc_in_frame_t enc_in_frame = {0};
esp_audio_enc_out_frame_t enc_out_frame = {0};
esp_gmf_err_io_t load_ret = 0;
bool needed_load = false;
esp_gmf_payload_t *in_load = NULL;
out_len = gmf_audio_enc_acquire_in(audio_enc, in_port, &in_load);
if (out_len != ESP_GMF_JOB_ERR_OK) {
goto __audio_enc_release;
}
ESP_LOGD(TAG, "Acq cache, buf:%p, vld:%d, len:%d, done:%d", in_load->buf, in_load->valid_size, in_load->buf_length, in_load->is_done);
// Acquire output buffer for encoded data and verify output buffer size
load_ret = esp_gmf_port_acquire_out(out_port, &out_load, ESP_GMF_ELEMENT_GET(audio_enc)->out_attr.data_size, ESP_GMF_MAX_DELAY);
ESP_GMF_PORT_ACQUIRE_OUT_CHECK(TAG, load_ret, out_len, {goto __audio_enc_release;});
if (out_load->buf_length < (ESP_GMF_ELEMENT_GET(audio_enc)->out_attr.data_size)) {
ESP_LOGE(TAG, "The out payload valid size(%d) is smaller than wanted size(%d)",
out_load->buf_length, (ESP_GMF_ELEMENT_GET(audio_enc)->out_attr.data_size));
out_len = ESP_GMF_JOB_ERR_FAIL;
goto __audio_enc_release;
}
// Check if have enough input data for processing
if (in_load->valid_size != ESP_GMF_ELEMENT_GET(audio_enc)->in_attr.data_size) {
if (in_load->is_done == true) {
out_len = ESP_GMF_JOB_ERR_DONE;
out_load->valid_size = 0;
out_load->is_done = in_load->is_done;
ESP_LOGD(TAG, "Return done, line:%d", __LINE__);
goto __audio_enc_release;
} else {
out_len = ESP_GMF_JOB_ERR_CONTINUE;
ESP_LOGD(TAG, "Return Continue, line:%d", __LINE__);
goto __audio_enc_release;
}
}
// Perform audio encoding with mutex protection
enc_in_frame.buffer = in_load->buf;
enc_in_frame.len = in_load->valid_size;
enc_out_frame.buffer = out_load->buf;
enc_out_frame.len = ESP_GMF_ELEMENT_GET(audio_enc)->out_attr.data_size;
esp_gmf_oal_mutex_lock(((esp_gmf_audio_element_t *)self)->lock);
ret = esp_audio_enc_process(audio_enc->audio_enc_hd, &enc_in_frame, &enc_out_frame);
esp_gmf_oal_mutex_unlock(((esp_gmf_audio_element_t *)self)->lock);
ESP_GMF_RET_ON_ERROR(TAG, ret, {out_len = ESP_GMF_JOB_ERR_FAIL; goto __audio_enc_release;}, "Audio encoder process error %d", ret);
out_load->valid_size = enc_out_frame.encoded_bytes;
out_load->is_done = in_load->is_done;
out_load->pts = audio_enc->cur_pts;
audio_enc->cur_pts += GMF_AUDIO_CALC_PTS(enc_in_frame.len, audio_enc->parent.snd_info.sample_rates, audio_enc->parent.snd_info.channels, audio_enc->parent.snd_info.bits);
// Handle end of stream
if (in_load->is_done) {
ESP_LOGW(TAG, "Got done, out size: %d", out_load->valid_size);
out_len = ESP_GMF_JOB_ERR_DONE;
}
// Check if need to truncate
esp_gmf_cache_ready_for_load(audio_enc->cached_payload, &needed_load);
if (needed_load == false) {
out_len = ESP_GMF_JOB_ERR_TRUNCATE;
int cached_size = 0;
esp_gmf_cache_get_cached_size(audio_enc->cached_payload, &cached_size);
ESP_LOGD(TAG, "Return TRUNCATE, reminder in size: %d", cached_size);
}
__audio_enc_release:
// Cleanup resources
esp_gmf_cache_release(audio_enc->cached_payload, in_load);
if (out_load != NULL) {
load_ret = esp_gmf_port_release_out(out_port, out_load, out_port->wait_ticks);
if ((load_ret < ESP_GMF_IO_OK) && (load_ret != ESP_GMF_IO_ABORT)) {
ESP_LOGE(TAG, "OUT port release error, ret:%d", load_ret);
out_len = ESP_GMF_JOB_ERR_FAIL;
}
}
if ((audio_enc->origin_in_load != NULL) && (out_len != ESP_GMF_JOB_ERR_TRUNCATE)) {
load_ret = esp_gmf_port_release_in(in_port, audio_enc->origin_in_load, in_port->wait_ticks);
if ((load_ret < ESP_GMF_IO_OK) && (load_ret != ESP_GMF_IO_ABORT)) {
ESP_LOGE(TAG, "IN port release error, ret:%d", load_ret);
out_len = ESP_GMF_JOB_ERR_FAIL;
}
audio_enc->origin_in_load = NULL;
}
return out_len;
}
static esp_gmf_job_err_t esp_gmf_audio_enc_close(esp_gmf_element_handle_t self, void *para)
{
ESP_LOGD(TAG, "Closed, %p", self);
esp_gmf_audio_enc_t *enc = (esp_gmf_audio_enc_t *)self;
if (enc->cached_payload) {
esp_gmf_cache_delete(enc->cached_payload);
enc->cached_payload = NULL;
}
if (enc->audio_enc_hd != NULL) {
esp_audio_enc_close(enc->audio_enc_hd);
enc->audio_enc_hd = NULL;
}
return ESP_GMF_JOB_ERR_OK;
}
static esp_gmf_err_t audio_enc_received_event_handler(esp_gmf_event_pkt_t *evt, void *ctx)
{
ESP_GMF_NULL_CHECK(TAG, ctx, { return ESP_GMF_ERR_INVALID_ARG;});
ESP_GMF_NULL_CHECK(TAG, evt, { return ESP_GMF_ERR_INVALID_ARG;});
if ((evt->type != ESP_GMF_EVT_TYPE_REPORT_INFO)
|| (evt->sub != ESP_GMF_INFO_SOUND)
|| (evt->payload == NULL)) {
return ESP_GMF_ERR_OK;
}
esp_gmf_element_handle_t self = (esp_gmf_element_handle_t)ctx;
esp_gmf_element_handle_t el = evt->from;
esp_gmf_event_state_t state = ESP_GMF_EVENT_STATE_NONE;
esp_gmf_element_get_state(self, &state);
if (state < ESP_GMF_EVENT_STATE_OPENING) {
esp_audio_enc_config_t *enc_cfg = (esp_audio_enc_config_t *)OBJ_GET_CFG(self);
ESP_GMF_NULL_CHECK(TAG, enc_cfg, return ESP_GMF_ERR_FAIL);
esp_gmf_info_sound_t *info = (esp_gmf_info_sound_t *)evt->payload;
audio_enc_change_audio_info(enc_cfg, info);
ESP_LOGD(TAG, "RECV info, from: %s-%p, next: %p, self: %s-%p, type: %x, state: %s, rate: %d, ch: %d, bits: %d",
OBJ_GET_TAG(el), el, esp_gmf_node_for_next((esp_gmf_node_t *)el), OBJ_GET_TAG(self), self, evt->type,
esp_gmf_event_get_state_str(state), info->sample_rates, info->channels, info->bits);
if (state == ESP_GMF_EVENT_STATE_NONE) {
esp_gmf_element_set_state(self, ESP_GMF_EVENT_STATE_INITIALIZED);
}
}
return ESP_GMF_ERR_OK;
}
static esp_gmf_err_t esp_gmf_audio_enc_destroy(esp_gmf_element_handle_t self)
{
ESP_LOGD(TAG, "Destroyed, %p", self);
esp_gmf_audio_enc_t *enc = (esp_gmf_audio_enc_t *)self;
free_esp_gmf_audio_enc_cfg(OBJ_GET_CFG(self));
esp_gmf_audio_el_deinit(self);
esp_gmf_oal_free(enc);
return ESP_GMF_ERR_OK;
}
static esp_gmf_err_t _load_enc_caps_func(esp_gmf_element_handle_t handle)
{
esp_gmf_cap_t *caps = NULL;
esp_gmf_cap_t dec_caps = {0};
dec_caps.cap_eightcc = ESP_GMF_CAPS_AUDIO_ENCODER;
dec_caps.attr_fun = NULL;
int ret = esp_gmf_cap_append(&caps, &dec_caps);
ESP_GMF_RET_ON_NOT_OK(TAG, ret, {return ret;}, "Failed to create capability");
esp_gmf_element_t *el = (esp_gmf_element_t *)handle;
el->caps = caps;
return ESP_GMF_ERR_OK;
}
static esp_gmf_err_t _load_enc_methods_func(esp_gmf_element_handle_t handle)
{
esp_gmf_method_t *method = NULL;
esp_gmf_args_desc_t *set_args = NULL;
esp_gmf_args_desc_t *get_args = NULL;
esp_gmf_err_t ret = esp_gmf_args_desc_append(&set_args, AMETHOD_ARG(ENCODER, SET_BITRATE, BITRATE),
ESP_GMF_ARGS_TYPE_INT32, sizeof(uint32_t), 0);
ESP_GMF_RET_ON_NOT_OK(TAG, ret, return ret, "Failed to append bitrate argument");
ret = esp_gmf_method_append(&method, AMETHOD(ENCODER, SET_BITRATE), __audio_enc_set_bitrate, set_args);
ESP_GMF_RET_ON_ERROR(TAG, ret, return ret, "Failed to register %s method", AMETHOD(ENCODER, SET_BITRATE));
ret = esp_gmf_args_desc_copy(set_args, &get_args);
ESP_GMF_RET_ON_NOT_OK(TAG, ret, return ret, "Failed to copy argument");
ret = esp_gmf_method_append(&method, AMETHOD(ENCODER, GET_BITRATE), __audio_enc_get_bitrate, get_args);
ESP_GMF_RET_ON_ERROR(TAG, ret, return ret, "Failed to register %s method", AMETHOD(ENCODER, GET_BITRATE));
get_args = NULL;
set_args = NULL;
ret = esp_gmf_args_desc_append(&get_args, AMETHOD_ARG(ENCODER, GET_FRAME_SZ, INSIZE),
ESP_GMF_ARGS_TYPE_INT32, sizeof(uint32_t), 0);
ESP_GMF_RET_ON_NOT_OK(TAG, ret, return ret, "Failed to append insize argument");
ret = esp_gmf_args_desc_append(&get_args, AMETHOD_ARG(ENCODER, GET_FRAME_SZ, OUTSIZE), ESP_GMF_ARGS_TYPE_INT32,
sizeof(uint32_t), sizeof(uint32_t));
ESP_GMF_RET_ON_NOT_OK(TAG, ret, return ret, "Failed to append outsize argument");
ret = esp_gmf_method_append(&method, AMETHOD(ENCODER, GET_FRAME_SZ), __audio_enc_get_frame_size, get_args);
ESP_GMF_RET_ON_ERROR(TAG, ret, return ret, "Failed to register %s method", AMETHOD(ENCODER, GET_FRAME_SZ));
set_args = NULL;
esp_gmf_args_desc_t *sndinfo_args = NULL;
ret = esp_gmf_args_desc_append(&sndinfo_args, AMETHOD_ARG(ENCODER, RECONFIG_BY_SND_INFO, INFO_TYPE), ESP_GMF_ARGS_TYPE_UINT32,
sizeof(uint32_t), offsetof(esp_gmf_info_sound_t, format_id));
ESP_GMF_RET_ON_NOT_OK(TAG, ret, return ret, "Failed to append type argument");
ret = esp_gmf_args_desc_append(&sndinfo_args, AMETHOD_ARG(ENCODER, RECONFIG_BY_SND_INFO, INFO_SAMPLERATE), ESP_GMF_ARGS_TYPE_INT32,
sizeof(int32_t), offsetof(esp_gmf_info_sound_t, sample_rates));
ESP_GMF_RET_ON_NOT_OK(TAG, ret, return ret, "Failed to append sample_rates argument");
ret = esp_gmf_args_desc_append(&sndinfo_args, AMETHOD_ARG(ENCODER, RECONFIG_BY_SND_INFO, INFO_BITRATE), ESP_GMF_ARGS_TYPE_INT32,
sizeof(int32_t), offsetof(esp_gmf_info_sound_t, bitrate));
ESP_GMF_RET_ON_NOT_OK(TAG, ret, return ret, "Failed to append bitrate argument");
ret = esp_gmf_args_desc_append(&sndinfo_args, AMETHOD_ARG(ENCODER, RECONFIG_BY_SND_INFO, INFO_CHANNEL), ESP_GMF_ARGS_TYPE_INT8,
sizeof(int8_t), 12);
ESP_GMF_RET_ON_NOT_OK(TAG, ret, return ret, "Failed to append channels argument");
ret = esp_gmf_args_desc_append(&sndinfo_args, AMETHOD_ARG(ENCODER, RECONFIG_BY_SND_INFO, INFO_BITS), ESP_GMF_ARGS_TYPE_INT8,
sizeof(int8_t), 13);
ESP_GMF_RET_ON_NOT_OK(TAG, ret, return ret, "Failed to append bits argument");
ret = esp_gmf_args_desc_append_array(&set_args, AMETHOD_ARG(ENCODER, RECONFIG_BY_SND_INFO, INFO), sndinfo_args,
sizeof(esp_gmf_info_sound_t), 0);
ESP_GMF_RET_ON_NOT_OK(TAG, ret, return ret, "Failed to append sound info argument");
ret = esp_gmf_method_append(&method, AMETHOD(ENCODER, RECONFIG_BY_SND_INFO), __audio_enc_reconfig_by_sound_info, set_args);
ESP_GMF_RET_ON_ERROR(TAG, ret, return ret, "Failed to register %s method", AMETHOD(ENCODER, RECONFIG_BY_SND_INFO));
set_args = NULL;
esp_gmf_args_desc_t *reconfig_args = NULL;
ret = esp_gmf_args_desc_append(&reconfig_args, AMETHOD_ARG(ENCODER, RECONFIG, CFG_TYPE), ESP_GMF_ARGS_TYPE_INT32,
sizeof(int32_t), offsetof(esp_audio_enc_config_t, type));
ESP_GMF_RET_ON_NOT_OK(TAG, ret, return ret, "Failed to append type argument");
ret = esp_gmf_args_desc_append(&reconfig_args, AMETHOD_ARG(ENCODER, RECONFIG, CFG_SUBCFGPTR), ESP_GMF_ARGS_TYPE_INT32,
sizeof(int32_t), offsetof(esp_audio_enc_config_t, cfg));
ESP_GMF_RET_ON_NOT_OK(TAG, ret, return ret, "Failed to append cfg argument");
ret = esp_gmf_args_desc_append(&reconfig_args, AMETHOD_ARG(ENCODER, RECONFIG, CFG_SUBCFGSZ), ESP_GMF_ARGS_TYPE_UINT32,
sizeof(uint32_t), offsetof(esp_audio_enc_config_t, cfg_sz));
ESP_GMF_RET_ON_NOT_OK(TAG, ret, return ret, "Failed to append cfg_sz argument");
ret = esp_gmf_args_desc_append_array(&set_args, AMETHOD_ARG(ENCODER, RECONFIG, CFG), reconfig_args,
sizeof(esp_audio_enc_config_t), 0);
ESP_GMF_RET_ON_NOT_OK(TAG, ret, return ret, "Failed to append argument");
ret = esp_gmf_method_append(&method, AMETHOD(ENCODER, RECONFIG), __audio_enc_reconfig, set_args);
ESP_GMF_RET_ON_ERROR(TAG, ret, return ret, "Failed to register %s method", AMETHOD(ENCODER, RECONFIG));
esp_gmf_element_t *el = (esp_gmf_element_t *)handle;
el->method = method;
return ESP_GMF_ERR_OK;
}
esp_gmf_err_t esp_gmf_audio_enc_get_frame_size(esp_gmf_element_handle_t handle, uint32_t *in_size, uint32_t *out_size)
{
ESP_GMF_NULL_CHECK(TAG, handle, return ESP_GMF_ERR_INVALID_ARG);
esp_audio_enc_config_t *cfg = (esp_audio_enc_config_t *)OBJ_GET_CFG(handle);
ESP_GMF_NULL_CHECK(TAG, cfg, return ESP_GMF_ERR_FAIL);
esp_gmf_audio_enc_t *enc = (esp_gmf_audio_enc_t *)handle;
esp_audio_err_t ret = -1;
if (enc->audio_enc_hd == NULL) {
esp_audio_enc_frame_info_t frame_info = {0};
ret = esp_audio_enc_get_frame_info_by_cfg(cfg, &frame_info);
if (ret != ESP_AUDIO_ERR_OK) {
return ESP_GMF_ERR_FAIL;
}
*in_size = (uint32_t)frame_info.in_frame_size;
*out_size = (uint32_t)frame_info.out_frame_size;
} else {
ret = esp_audio_enc_get_frame_size(enc->audio_enc_hd, (int *)in_size, (int *)out_size);
}
return ((ret == ESP_AUDIO_ERR_OK) ? (ESP_GMF_ERR_OK) : (ESP_GMF_ERR_FAIL));
}
esp_gmf_err_t esp_gmf_audio_enc_set_bitrate(esp_gmf_element_handle_t handle, uint32_t bitrate)
{
ESP_GMF_NULL_CHECK(TAG, handle, return ESP_GMF_ERR_INVALID_ARG);
esp_gmf_audio_enc_t *enc = (esp_gmf_audio_enc_t *)handle;
if (enc->audio_enc_hd) {
esp_gmf_oal_mutex_lock(((esp_gmf_audio_element_t *)handle)->lock);
esp_audio_err_t ret = esp_audio_enc_set_bitrate(enc->audio_enc_hd, (int)bitrate);
esp_gmf_oal_mutex_unlock(((esp_gmf_audio_element_t *)handle)->lock);
return ((ret == ESP_AUDIO_ERR_OK) ? (ESP_GMF_ERR_OK) : (ESP_GMF_ERR_FAIL));
}
enc->bitrate = bitrate;
return ESP_GMF_JOB_ERR_OK;
}
esp_gmf_err_t esp_gmf_audio_enc_get_bitrate(esp_gmf_element_handle_t handle, uint32_t *bitrate)
{
ESP_GMF_NULL_CHECK(TAG, handle, return ESP_GMF_ERR_INVALID_ARG);
esp_gmf_audio_enc_t *enc = (esp_gmf_audio_enc_t *)handle;
if (enc->audio_enc_hd) {
esp_audio_enc_info_t enc_info = {0};
esp_audio_err_t ret = esp_audio_enc_get_info(enc->audio_enc_hd, &enc_info);
*bitrate = enc_info.bitrate;
return ((ret == ESP_AUDIO_ERR_OK) ? (ESP_GMF_ERR_OK) : (ESP_GMF_ERR_FAIL));
}
*bitrate = enc->bitrate;
return ESP_GMF_JOB_ERR_OK;
}
esp_gmf_err_t esp_gmf_audio_enc_reconfig(esp_gmf_element_handle_t handle, esp_audio_enc_config_t *config)
{
ESP_GMF_NULL_CHECK(TAG, handle, return ESP_GMF_ERR_INVALID_ARG);
ESP_GMF_NULL_CHECK(TAG, config, return ESP_GMF_ERR_INVALID_ARG);
esp_gmf_event_state_t state = ESP_GMF_EVENT_STATE_NONE;
esp_gmf_element_get_state(handle, &state);
if (state < ESP_GMF_EVENT_STATE_OPENING) {
esp_audio_enc_config_t *new_config = NULL;
esp_gmf_err_t ret = dupl_esp_gmf_audio_enc_cfg(config, &new_config);
ESP_GMF_RET_ON_NOT_OK(TAG, ret, {return ret;}, "Failed to duplicate config");
free_esp_gmf_audio_enc_cfg(OBJ_GET_CFG(handle));
esp_gmf_obj_set_config(handle, new_config, sizeof(esp_audio_enc_config_t));
return ESP_GMF_ERR_OK;
} else {
ESP_LOGE(TAG, "Failed to reconfig encoder due to invalid state: %s", esp_gmf_event_get_state_str(state));
return ESP_GMF_ERR_FAIL;
}
}
esp_gmf_err_t esp_gmf_audio_enc_reconfig_by_sound_info(esp_gmf_element_handle_t handle, esp_gmf_info_sound_t *info)
{
ESP_GMF_NULL_CHECK(TAG, handle, return ESP_GMF_ERR_INVALID_ARG);
esp_gmf_event_state_t state = ESP_GMF_EVENT_STATE_NONE;
esp_gmf_element_get_state(handle, &state);
if (state < ESP_GMF_EVENT_STATE_OPENING) {
esp_gmf_err_t ret = audio_enc_reconfig_enc_by_sound_info(handle, info);
ESP_GMF_RET_ON_NOT_OK(TAG, ret, return ret, "Failed to reconfig encoder by sound information");
return ESP_GMF_ERR_OK;
} else {
ESP_LOGE(TAG, "Failed to reconfig encoder due to invalid state: %s", esp_gmf_event_get_state_str(state));
return ESP_GMF_ERR_FAIL;
}
}
esp_gmf_err_t esp_gmf_audio_enc_init(esp_audio_enc_config_t *config, esp_gmf_element_handle_t *handle)
{
ESP_GMF_NULL_CHECK(TAG, handle, {return ESP_GMF_ERR_INVALID_ARG;});
*handle = NULL;
esp_gmf_err_t ret = ESP_GMF_ERR_OK;
esp_gmf_audio_enc_t *audio_enc = esp_gmf_oal_calloc(1, sizeof(esp_gmf_audio_enc_t));
ESP_GMF_MEM_VERIFY(TAG, audio_enc, {return ESP_GMF_ERR_MEMORY_LACK;}, "audio encoder", sizeof(esp_gmf_audio_enc_t));
esp_gmf_obj_t *obj = (esp_gmf_obj_t *)audio_enc;
obj->new_obj = esp_gmf_audio_enc_new;
obj->del_obj = esp_gmf_audio_enc_destroy;
esp_audio_enc_config_t *cfg = NULL;
if (config) {
dupl_esp_gmf_audio_enc_cfg(config, &cfg);
} else {
esp_audio_enc_config_t dcfg = DEFAULT_ESP_GMF_AUDIO_ENC_CONFIG();
dupl_esp_gmf_audio_enc_cfg(&dcfg, &cfg);
}
ESP_GMF_CHECK(TAG, cfg, ret = ESP_GMF_ERR_MEMORY_LACK; goto ES_ENC_FAIL;, "Failed to allocate audio encoder configuration");
esp_gmf_obj_set_config(obj, cfg, sizeof(esp_audio_enc_config_t));
ret = esp_gmf_obj_set_tag(obj, "aud_enc");
ESP_GMF_RET_ON_NOT_OK(TAG, ret, goto ES_ENC_FAIL, "Failed to set obj tag");
esp_gmf_element_cfg_t el_cfg = {0};
ESP_GMF_ELEMENT_IN_PORT_ATTR_SET(el_cfg.in_attr, ESP_GMF_EL_PORT_CAP_SINGLE, 0, 0,
ESP_GMF_PORT_TYPE_BLOCK | ESP_GMF_PORT_TYPE_BYTE, ESP_GMF_ELEMENT_PORT_DATA_SIZE_DEFAULT);
ESP_GMF_ELEMENT_OUT_PORT_ATTR_SET(el_cfg.out_attr, ESP_GMF_EL_PORT_CAP_SINGLE, 0, 0,
ESP_GMF_PORT_TYPE_BLOCK | ESP_GMF_PORT_TYPE_BYTE, ESP_GMF_ELEMENT_PORT_DATA_SIZE_DEFAULT);
el_cfg.dependency = true;
ret = esp_gmf_audio_el_init(audio_enc, &el_cfg);
ESP_GMF_RET_ON_NOT_OK(TAG, ret, goto ES_ENC_FAIL, "Failed to initialize audio encoder element");
audio_enc->parent.base.ops.open = esp_gmf_audio_enc_open;
audio_enc->parent.base.ops.process = esp_gmf_audio_enc_process;
audio_enc->parent.base.ops.close = esp_gmf_audio_enc_close;
audio_enc->parent.base.ops.event_receiver = audio_enc_received_event_handler;
audio_enc->parent.base.ops.load_caps = _load_enc_caps_func;
audio_enc->parent.base.ops.load_methods = _load_enc_methods_func;
*handle = obj;
ESP_LOGD(TAG, "Initialization, %s-%p", OBJ_GET_TAG(obj), obj);
return ret;
ES_ENC_FAIL:
esp_gmf_audio_enc_destroy(obj);
return ret;
}
@@ -0,0 +1,60 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO., LTD
* SPDX-License-Identifier: LicenseRef-Espressif-Modified-MIT
*
* See LICENSE file for details.
*/
#include <string.h>
#include "esp_log.h"
#include "esp_gmf_audio_helper.h"
#include "esp_fourcc.h"
static const char *TAG = "ESP_GMF_AUDIO_HELPER";
esp_gmf_err_t esp_gmf_audio_helper_get_audio_type_by_uri(const char *uri, uint32_t *format_id)
{
const char *ext = strrchr(uri, '.');
if (ext == NULL) {
return ESP_GMF_ERR_NOT_SUPPORT;
}
ext++;
if (strncasecmp(ext, "aac", 3) == 0) {
*format_id = ESP_FOURCC_AAC;
} else if (strncasecmp(ext, "g711a", 5) == 0) {
*format_id = ESP_FOURCC_ALAW;
} else if (strncasecmp(ext, "g711u", 5) == 0) {
*format_id = ESP_FOURCC_ULAW;
} else if (strncasecmp(ext, "amr", 3) == 0) {
*format_id = ESP_FOURCC_AMRNB;
} else if (strncasecmp(ext, "awb", 3) == 0) {
*format_id = ESP_FOURCC_AMRWB;
} else if (strncasecmp(ext, "alac", 4) == 0) {
*format_id = ESP_FOURCC_ALAC;
} else if (strncasecmp(ext, "pcm", 3) == 0) {
*format_id = ESP_FOURCC_PCM;
} else if (strncasecmp(ext, "opus", 4) == 0) {
*format_id = ESP_FOURCC_OPUS;
} else if (strncasecmp(ext, "adpcm", 5) == 0) {
*format_id = ESP_FOURCC_ADPCM;
} else if (strncasecmp(ext, "sbc", 3) == 0) {
*format_id = ESP_FOURCC_SBC;
} else if (strncasecmp(ext, "lc3", 3) == 0) {
*format_id = ESP_FOURCC_LC3;
} else if (strncasecmp(ext, "mp3", 3) == 0) {
*format_id = ESP_FOURCC_MP3;
} else if (strncasecmp(ext, "m4a", 3) == 0) {
*format_id = ESP_FOURCC_M4A;
} else if (strncasecmp(ext, "wav", 3) == 0) {
*format_id = ESP_FOURCC_WAV;
} else if (strncasecmp(ext, "ts", 2) == 0) {
*format_id = ESP_FOURCC_M2TS;
} else if (strncasecmp(ext, "flac", 4) == 0) {
*format_id = ESP_FOURCC_FLAC;
} else {
*format_id = 0;
ESP_LOGE(TAG, "Not support audio codec type, %s", uri);
return ESP_GMF_ERR_NOT_SUPPORT;
}
return ESP_GMF_ERR_OK;
}
@@ -0,0 +1,102 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO., LTD
* SPDX-License-Identifier: LicenseRef-Espressif-Modified-MIT
*
* See LICENSE file for details.
*/
#include "esp_gmf_method_helper.h"
#include "esp_gmf_audio_param.h"
#include "esp_gmf_audio_methods_def.h"
#define PREPARE_AMETHOD_SETTING() \
esp_gmf_method_exec_ctx_t exec_ctx = {}; \
const esp_gmf_method_t* method_head = NULL; \
esp_gmf_element_get_method(self, &method_head); \
esp_gmf_err_t ret = esp_gmf_method_prepare_exec_ctx(method_head, method_name, &exec_ctx); \
if (ret != ESP_GMF_ERR_OK) { \
return ret; \
}
#define SET_AMETHOD_ARG(arg_name, value) \
esp_gmf_args_set_value(exec_ctx.method->args_desc, arg_name, exec_ctx.exec_buf, (uint8_t*) &value, sizeof(value));
#define RELEASE_AMETHOD_SETTING() \
ret = exec_ctx.method->func(self, exec_ctx.method->args_desc, exec_ctx.exec_buf, exec_ctx.buf_size); \
esp_gmf_method_release_exec_ctx(&exec_ctx); \
return ret;
esp_gmf_err_t esp_gmf_audio_param_set_dest_rate(esp_gmf_element_handle_t self, uint32_t dest_rate)
{
const char *method_name = AMETHOD(RATE_CVT, SET_DEST_RATE);
PREPARE_AMETHOD_SETTING();
SET_AMETHOD_ARG(AMETHOD_ARG(RATE_CVT, SET_DEST_RATE, RATE), dest_rate);
RELEASE_AMETHOD_SETTING();
}
esp_gmf_err_t esp_gmf_audio_param_set_dest_bits(esp_gmf_element_handle_t self, uint8_t dest_bits)
{
const char *method_name = AMETHOD(BIT_CVT, SET_DEST_BITS);
PREPARE_AMETHOD_SETTING();
SET_AMETHOD_ARG(AMETHOD_ARG(BIT_CVT, SET_DEST_BITS, BITS), dest_bits);
RELEASE_AMETHOD_SETTING();
}
esp_gmf_err_t esp_gmf_audio_param_set_dest_ch(esp_gmf_element_handle_t self, uint8_t dest_ch)
{
const char *method_name = AMETHOD(CH_CVT, SET_DEST_CH);
PREPARE_AMETHOD_SETTING();
SET_AMETHOD_ARG(AMETHOD_ARG(CH_CVT, SET_DEST_CH, CH), dest_ch);
RELEASE_AMETHOD_SETTING();
}
esp_gmf_err_t esp_gmf_audio_param_set_speed(esp_gmf_element_handle_t self, float speed)
{
const char *method_name = AMETHOD(SONIC, SET_SPEED);
PREPARE_AMETHOD_SETTING();
SET_AMETHOD_ARG(AMETHOD_ARG(SONIC, SET_SPEED, SPEED), speed);
RELEASE_AMETHOD_SETTING();
}
esp_gmf_err_t esp_gmf_audio_param_set_pitch(esp_gmf_element_handle_t self, float pitch)
{
const char *method_name = AMETHOD(SONIC, SET_PITCH);
PREPARE_AMETHOD_SETTING();
SET_AMETHOD_ARG(AMETHOD_ARG(SONIC, SET_PITCH, PITCH), pitch);
RELEASE_AMETHOD_SETTING();
}
esp_gmf_err_t esp_gmf_audio_param_set_alc_channel_gain(esp_gmf_element_handle_t self, uint8_t ch_idx, float db)
{
const char *method_name = AMETHOD(ALC, SET_GAIN);
// ALC currently only support int8 db
int8_t gain = (int8_t) db;
PREPARE_AMETHOD_SETTING();
SET_AMETHOD_ARG(AMETHOD_ARG(ALC, SET_GAIN, IDX), ch_idx);
SET_AMETHOD_ARG(AMETHOD_ARG(ALC, SET_GAIN, GAIN), gain);
RELEASE_AMETHOD_SETTING();
}
esp_gmf_err_t esp_gmf_audio_param_set_fade(esp_gmf_element_handle_t self, bool is_fade_in)
{
const char *method_name = AMETHOD(FADE, SET_MODE);
// Aligned with ESP_AE_FADE_MODE_FADE_IN = 1, ESP_AE_FADE_MODE_FADE_OUT = 2
int32_t mode = is_fade_in ? 1 : 2;
PREPARE_AMETHOD_SETTING();
SET_AMETHOD_ARG(AMETHOD_ARG(FADE, SET_MODE, MODE), mode);
RELEASE_AMETHOD_SETTING();
}
@@ -0,0 +1,280 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO., LTD
* SPDX-License-Identifier: LicenseRef-Espressif-Modified-MIT
*
* See LICENSE file for details.
*/
#include <string.h>
#include "esp_log.h"
#include "esp_gmf_node.h"
#include "esp_gmf_oal_mem.h"
#include "esp_gmf_oal_mutex.h"
#include "esp_gmf_bit_cvt.h"
#include "gmf_audio_common.h"
#include "esp_gmf_audio_methods_def.h"
#include "esp_gmf_cap.h"
#include "esp_gmf_caps_def.h"
#include "esp_gmf_audio_element.h"
/**
* @brief Audio bit conversion context in GMF
*/
typedef struct {
esp_gmf_audio_element_t parent; /*!< The GMF bit cvt handle */
esp_ae_bit_cvt_handle_t bit_hd; /*!< The audio effects bit cvt handle */
uint8_t in_bytes_per_sample; /*!< Source bytes number of per sampling point */
uint8_t out_bytes_per_sample; /*!< Dest bytes number of per sampling point */
bool need_reopen : 1; /*!< Whether need to reopen.
True: Execute the close function first, then execute the open function
False: Do nothing */
bool bypass : 1; /*!< Whether bypass. True: need bypass. False: needn't bypass */
} esp_gmf_bit_cvt_t;
static const char *TAG = "ESP_GMF_BIT_CVT";
static esp_gmf_err_t __set_dest_bits(esp_gmf_element_handle_t handle, esp_gmf_args_desc_t *arg_desc,
uint8_t *buf, int buf_len)
{
ESP_GMF_NULL_CHECK(TAG, buf, {return ESP_GMF_ERR_INVALID_ARG;});
uint8_t dest_bits = (uint8_t)(*buf);
return esp_gmf_bit_cvt_set_dest_bits(handle, dest_bits);
}
static esp_gmf_err_t esp_gmf_bit_cvt_new(void *cfg, esp_gmf_obj_handle_t *handle)
{
return esp_gmf_bit_cvt_init(cfg, (esp_gmf_element_handle_t *)handle);
}
static esp_gmf_job_err_t esp_gmf_bit_cvt_open(esp_gmf_element_handle_t self, void *para)
{
esp_gmf_bit_cvt_t *bit_cvt = (esp_gmf_bit_cvt_t *)self;
esp_ae_bit_cvt_cfg_t *bit_info = (esp_ae_bit_cvt_cfg_t *)OBJ_GET_CFG(self);
ESP_GMF_NULL_CHECK(TAG, bit_info, {return ESP_GMF_JOB_ERR_FAIL;});
esp_ae_bit_cvt_open(bit_info, &bit_cvt->bit_hd);
ESP_GMF_CHECK(TAG, bit_cvt->bit_hd, {return ESP_GMF_JOB_ERR_FAIL;}, "Failed to create bit conversion handle");
bit_cvt->in_bytes_per_sample = (bit_info->src_bits >> 3) * bit_info->channel;
bit_cvt->out_bytes_per_sample = (bit_info->dest_bits >> 3) * bit_info->channel;
GMF_AUDIO_UPDATE_SND_INFO(self, bit_info->sample_rate, bit_info->dest_bits, bit_info->channel);
ESP_LOGD(TAG, "Open, rate: %ld, channel: %d, src_bits: %d, dest_bits: %d",
bit_info->sample_rate, bit_info->channel, bit_info->src_bits, bit_info->dest_bits);
bit_cvt->need_reopen = false;
bit_cvt->bypass = (bit_info->src_bits == bit_info->dest_bits);
return ESP_GMF_JOB_ERR_OK;
}
static esp_gmf_job_err_t esp_gmf_bit_cvt_close(esp_gmf_element_handle_t self, void *para)
{
esp_gmf_bit_cvt_t *bit_cvt = (esp_gmf_bit_cvt_t *)self;
ESP_LOGD(TAG, "Closed, %p", self);
if (bit_cvt->bit_hd != NULL) {
esp_ae_bit_cvt_close(bit_cvt->bit_hd);
bit_cvt->bit_hd = NULL;
}
return ESP_GMF_ERR_OK;
}
static esp_gmf_job_err_t esp_gmf_bit_cvt_process(esp_gmf_element_handle_t self, void *para)
{
esp_gmf_bit_cvt_t *bit_cvt = (esp_gmf_bit_cvt_t *)self;
esp_gmf_job_err_t out_len = ESP_GMF_JOB_ERR_OK;
if (bit_cvt->need_reopen) {
esp_gmf_bit_cvt_close(self, NULL);
out_len = esp_gmf_bit_cvt_open(self, NULL);
if (out_len != ESP_GMF_JOB_ERR_OK) {
ESP_LOGE(TAG, "Bit conversion reopen failed");
return out_len;
}
}
esp_gmf_port_handle_t in_port = ESP_GMF_ELEMENT_GET(self)->in;
esp_gmf_port_handle_t out_port = ESP_GMF_ELEMENT_GET(self)->out;
esp_gmf_payload_t *in_load = NULL;
esp_gmf_payload_t *out_load = NULL;
int samples_num = ESP_GMF_ELEMENT_GET(bit_cvt)->in_attr.data_size / (bit_cvt->in_bytes_per_sample);
int bytes = samples_num * bit_cvt->in_bytes_per_sample;
esp_gmf_err_io_t load_ret = esp_gmf_port_acquire_in(in_port, &in_load, bytes, ESP_GMF_MAX_DELAY);
if (load_ret < ESP_GMF_IO_OK) {
ESP_LOGE(TAG, "ACQ IN, ret: %d", load_ret);
out_len = ESP_GMF_JOB_ERR_FAIL;
goto __bit_release;
}
samples_num = in_load->valid_size / (bit_cvt->in_bytes_per_sample);
bytes = samples_num * bit_cvt->out_bytes_per_sample;
if ((bit_cvt->in_bytes_per_sample * samples_num != in_load->valid_size) || (load_ret < ESP_GMF_IO_OK)) {
ESP_LOGE(TAG, "Invalid in load size %d, ret %d", in_load->valid_size, load_ret);
out_len = ESP_GMF_JOB_ERR_FAIL;
goto __bit_release;
}
if ((bit_cvt->bypass) && (in_port->is_shared == 1)) {
// This case bit conversion is do bypass
out_load = in_load;
}
load_ret = esp_gmf_port_acquire_out(out_port, &out_load, samples_num ? bytes : in_load->buf_length, ESP_GMF_MAX_DELAY);
ESP_GMF_PORT_ACQUIRE_OUT_CHECK(TAG, load_ret, out_len, { goto __bit_release;});
if (samples_num) {
esp_ae_err_t ret = esp_ae_bit_cvt_process(bit_cvt->bit_hd, samples_num, (unsigned char *)in_load->buf, (unsigned char *)out_load->buf);
ESP_GMF_RET_ON_ERROR(TAG, ret, {out_len = ESP_GMF_JOB_ERR_FAIL; goto __bit_release;}, "Bit conversion process error, ret: %d", ret);
}
ESP_LOGV(TAG, "Samples: %d, IN-PLD: %p-%p-%d-%d-%d, OUT-PLD: %p-%p-%d-%d-%d",
samples_num, in_load, in_load->buf, in_load->valid_size, in_load->buf_length, in_load->is_done,
out_load, out_load->buf, out_load->valid_size, out_load->buf_length, out_load->is_done);
out_load->valid_size = samples_num * bit_cvt->out_bytes_per_sample;
out_load->pts = in_load->pts;
out_load->is_done = in_load->is_done;
if (out_load->valid_size > 0) {
esp_gmf_audio_el_update_file_pos((esp_gmf_element_handle_t)self, out_load->valid_size);
}
if (in_load->is_done) {
out_len = ESP_GMF_JOB_ERR_DONE;
ESP_LOGD(TAG, "Bit conversion is done, out len: %d", out_load->valid_size);
}
__bit_release:
// Release in and out port
if (out_load != NULL) {
load_ret = esp_gmf_port_release_out(out_port, out_load, ESP_GMF_MAX_DELAY);
if ((load_ret < ESP_GMF_IO_OK) && (load_ret != ESP_GMF_IO_ABORT)) {
ESP_LOGE(TAG, "OUT port release error, ret:%d", load_ret);
out_len = ESP_GMF_JOB_ERR_FAIL;
}
}
if (in_load != NULL) {
load_ret = esp_gmf_port_release_in(in_port, in_load, ESP_GMF_MAX_DELAY);
if ((load_ret < ESP_GMF_IO_OK) && (load_ret != ESP_GMF_IO_ABORT)) {
ESP_LOGE(TAG, "IN port release error, ret:%d", load_ret);
out_len = ESP_GMF_JOB_ERR_FAIL;
}
}
return out_len;
}
static esp_gmf_err_t bit_cvt_received_event_handler(esp_gmf_event_pkt_t *evt, void *ctx)
{
ESP_GMF_NULL_CHECK(TAG, ctx, {return ESP_GMF_ERR_INVALID_ARG;});
ESP_GMF_NULL_CHECK(TAG, evt, {return ESP_GMF_ERR_INVALID_ARG;});
if ((evt->type != ESP_GMF_EVT_TYPE_REPORT_INFO)
|| (evt->sub != ESP_GMF_INFO_SOUND)
|| (evt->payload == NULL)) {
return ESP_GMF_ERR_OK;
}
esp_gmf_element_handle_t self = (esp_gmf_element_handle_t)ctx;
esp_gmf_element_handle_t el = evt->from;
esp_gmf_event_state_t state = ESP_GMF_EVENT_STATE_NONE;
esp_gmf_element_get_state(self, &state);
esp_gmf_info_sound_t *info = (esp_gmf_info_sound_t *)evt->payload;
esp_ae_bit_cvt_cfg_t *config = (esp_ae_bit_cvt_cfg_t *)OBJ_GET_CFG(self);
ESP_GMF_NULL_CHECK(TAG, config, return ESP_GMF_ERR_FAIL);
esp_gmf_bit_cvt_t *bit_cvt = (esp_gmf_bit_cvt_t *)self;
bit_cvt->need_reopen = (config->sample_rate != info->sample_rates) || (info->channels != config->channel) || (config->src_bits != info->bits);
config->sample_rate = info->sample_rates;
config->channel = info->channels;
config->src_bits = info->bits;
ESP_LOGD(TAG, "RECV info, from: %s-%p, next: %p, self: %s-%p, type: %x, state: %s, rate: %d, ch: %d, bits: %d",
OBJ_GET_TAG(el), el, esp_gmf_node_for_next((esp_gmf_node_t *)el), OBJ_GET_TAG(self), self, evt->type,
esp_gmf_event_get_state_str(state), info->sample_rates, info->channels, info->bits);
if (state == ESP_GMF_EVENT_STATE_NONE) {
esp_gmf_element_set_state(self, ESP_GMF_EVENT_STATE_INITIALIZED);
}
return ESP_GMF_ERR_OK;
}
static esp_gmf_err_t esp_gmf_bit_cvt_destroy(esp_gmf_element_handle_t self)
{
esp_gmf_bit_cvt_t *bit_cvt = (esp_gmf_bit_cvt_t *)self;
ESP_LOGD(TAG, "Destroyed, %p", self);
void *cfg = OBJ_GET_CFG(self);
if (cfg) {
esp_gmf_oal_free(cfg);
}
esp_gmf_audio_el_deinit(self);
esp_gmf_oal_free(bit_cvt);
return ESP_GMF_ERR_OK;
}
static esp_gmf_err_t _load_bit_cvt_caps_func(esp_gmf_element_handle_t handle)
{
esp_gmf_cap_t *caps = NULL;
esp_gmf_cap_t dec_caps = {0};
dec_caps.cap_eightcc = ESP_GMF_CAPS_AUDIO_BIT_CONVERT;
dec_caps.attr_fun = NULL;
int ret = esp_gmf_cap_append(&caps, &dec_caps);
ESP_GMF_RET_ON_NOT_OK(TAG, ret, {return ret;}, "Failed to create capability");
esp_gmf_element_t *el = (esp_gmf_element_t *)handle;
el->caps = caps;
return ESP_GMF_ERR_OK;
}
static esp_gmf_err_t _load_bit_cvt_methods_func(esp_gmf_element_handle_t handle)
{
esp_gmf_method_t *method = NULL;
esp_gmf_args_desc_t *set_args = NULL;
esp_gmf_err_t ret = esp_gmf_args_desc_append(&set_args, AMETHOD_ARG(BIT_CVT, SET_DEST_BITS, BITS),
ESP_GMF_ARGS_TYPE_UINT8, sizeof(uint8_t), 0);
ESP_GMF_RET_ON_NOT_OK(TAG, ret, {return ret;}, "Failed to append argument");
ret = esp_gmf_method_append(&method, AMETHOD(BIT_CVT, SET_DEST_BITS), __set_dest_bits, set_args);
ESP_GMF_RET_ON_ERROR(TAG, ret, {return ret;}, "Failed to register %s method", AMETHOD(BIT_CVT, SET_DEST_BITS));
esp_gmf_element_t *el = (esp_gmf_element_t *)handle;
el->method = method;
return ESP_GMF_ERR_OK;
}
esp_gmf_err_t esp_gmf_bit_cvt_set_dest_bits(esp_gmf_element_handle_t handle, uint8_t dest_bits)
{
ESP_GMF_NULL_CHECK(TAG, handle, { return ESP_GMF_ERR_INVALID_ARG;});
esp_ae_bit_cvt_cfg_t *cfg = (esp_ae_bit_cvt_cfg_t *)OBJ_GET_CFG(handle);
if (cfg) {
if (cfg->dest_bits == dest_bits) {
return ESP_GMF_ERR_OK;
}
cfg->dest_bits = dest_bits;
esp_gmf_bit_cvt_t *bit_cvt = (esp_gmf_bit_cvt_t *)handle;
bit_cvt->need_reopen = true;
return ESP_GMF_ERR_OK;
}
ESP_LOGE(TAG, "Failed to set dest bits, cfg is NULL");
return ESP_GMF_ERR_FAIL;
}
esp_gmf_err_t esp_gmf_bit_cvt_init(esp_ae_bit_cvt_cfg_t *config, esp_gmf_element_handle_t *handle)
{
ESP_GMF_NULL_CHECK(TAG, handle, {return ESP_GMF_ERR_INVALID_ARG;});
*handle = NULL;
esp_gmf_err_t ret = ESP_GMF_ERR_OK;
esp_gmf_bit_cvt_t *bit_cvt = esp_gmf_oal_calloc(1, sizeof(esp_gmf_bit_cvt_t));
ESP_GMF_MEM_VERIFY(TAG, bit_cvt, {return ESP_GMF_ERR_MEMORY_LACK;}, "bit conversion", sizeof(esp_gmf_bit_cvt_t));
esp_gmf_obj_t *obj = (esp_gmf_obj_t *)bit_cvt;
obj->new_obj = esp_gmf_bit_cvt_new;
obj->del_obj = esp_gmf_bit_cvt_destroy;
esp_ae_bit_cvt_cfg_t *cfg = esp_gmf_oal_calloc(1, sizeof(esp_ae_bit_cvt_cfg_t));
ESP_GMF_MEM_VERIFY(TAG, cfg, {ret = ESP_GMF_ERR_MEMORY_LACK; goto BIT_CVT_INIT_FAIL;}, "bit conversion configuration", sizeof(esp_ae_bit_cvt_cfg_t));
esp_gmf_obj_set_config(obj, cfg, sizeof(esp_ae_bit_cvt_cfg_t));
if (config) {
memcpy(cfg, config, sizeof(esp_ae_bit_cvt_cfg_t));
} else {
esp_ae_bit_cvt_cfg_t dcfg = DEFAULT_ESP_GMF_BIT_CVT_CONFIG();
memcpy(cfg, &dcfg, sizeof(esp_ae_bit_cvt_cfg_t));
}
ret = esp_gmf_obj_set_tag(obj, "aud_bit_cvt");
ESP_GMF_RET_ON_NOT_OK(TAG, ret, goto BIT_CVT_INIT_FAIL, "Failed to set obj tag");
esp_gmf_element_cfg_t el_cfg = {0};
ESP_GMF_ELEMENT_IN_PORT_ATTR_SET(el_cfg.in_attr, ESP_GMF_EL_PORT_CAP_SINGLE, 0, 0,
ESP_GMF_PORT_TYPE_BLOCK | ESP_GMF_PORT_TYPE_BYTE, ESP_GMF_ELEMENT_PORT_DATA_SIZE_DEFAULT);
ESP_GMF_ELEMENT_IN_PORT_ATTR_SET(el_cfg.out_attr, ESP_GMF_EL_PORT_CAP_SINGLE, 0, 0,
ESP_GMF_PORT_TYPE_BLOCK | ESP_GMF_PORT_TYPE_BYTE, ESP_GMF_ELEMENT_PORT_DATA_SIZE_DEFAULT);
el_cfg.dependency = true;
ret = esp_gmf_audio_el_init(bit_cvt, &el_cfg);
ESP_GMF_RET_ON_NOT_OK(TAG, ret, goto BIT_CVT_INIT_FAIL, "Failed to initialize bit conversion element");
ESP_GMF_ELEMENT_GET(bit_cvt)->ops.open = esp_gmf_bit_cvt_open;
ESP_GMF_ELEMENT_GET(bit_cvt)->ops.process = esp_gmf_bit_cvt_process;
ESP_GMF_ELEMENT_GET(bit_cvt)->ops.close = esp_gmf_bit_cvt_close;
ESP_GMF_ELEMENT_GET(bit_cvt)->ops.event_receiver = bit_cvt_received_event_handler;
ESP_GMF_ELEMENT_GET(bit_cvt)->ops.load_caps = _load_bit_cvt_caps_func;
ESP_GMF_ELEMENT_GET(bit_cvt)->ops.load_methods = _load_bit_cvt_methods_func;
*handle = obj;
ESP_LOGD(TAG, "Initialization, %s-%p", OBJ_GET_TAG(obj), obj);
return ESP_GMF_ERR_OK;
BIT_CVT_INIT_FAIL:
esp_gmf_bit_cvt_destroy(obj);
return ret;
}
@@ -0,0 +1,304 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO., LTD
* SPDX-License-Identifier: LicenseRef-Espressif-Modified-MIT
*
* See LICENSE file for details.
*/
#include <string.h>
#include "esp_log.h"
#include "esp_gmf_node.h"
#include "esp_gmf_oal_mem.h"
#include "esp_gmf_oal_mutex.h"
#include "esp_gmf_ch_cvt.h"
#include "gmf_audio_common.h"
#include "esp_gmf_audio_methods_def.h"
#include "esp_gmf_cap.h"
#include "esp_gmf_caps_def.h"
#include "esp_gmf_audio_element.h"
/**
* @brief Audio channel conversion context in GMF
*/
typedef struct {
esp_gmf_audio_element_t parent; /*!< The GMF channel cvt handle */
esp_ae_ch_cvt_handle_t ch_hd; /*!< The audio effects channel cvt handle */
uint8_t in_bytes_per_sample; /*!< Source bytes number of per sampling point */
uint8_t out_bytes_per_sample; /*!< Dest bytes number of per sampling point */
bool need_reopen : 1; /*!< Whether need to reopen.
True: Execute the close function first, then execute the open function
False: Do nothing */
bool bypass : 1; /*!< Whether bypass. True: need bypass. False: needn't bypass */
} esp_gmf_ch_cvt_t;
static const char *TAG = "ESP_GMF_CH_CVT";
static inline esp_gmf_err_t dupl_esp_ae_ch_cvt_cfg(esp_ae_ch_cvt_cfg_t *config, esp_ae_ch_cvt_cfg_t **new_config)
{
void *sub_cfg = NULL;
*new_config = esp_gmf_oal_calloc(1, sizeof(*config));
ESP_GMF_MEM_VERIFY(TAG, *new_config, {return ESP_GMF_ERR_MEMORY_LACK;}, "channel conversion configuration", sizeof(*config));
memcpy(*new_config, config, sizeof(*config));
if (config->weight && (config->weight_len > 0)) {
sub_cfg = esp_gmf_oal_calloc(1, config->weight_len);
ESP_GMF_MEM_VERIFY(TAG, sub_cfg, {esp_gmf_oal_free(*new_config); return ESP_GMF_ERR_MEMORY_LACK;},
"weight array", (int)config->weight_len);
memcpy(sub_cfg, config->weight, config->weight_len);
(*new_config)->weight = sub_cfg;
}
return ESP_GMF_JOB_ERR_OK;
}
static inline void free_esp_ae_ch_cvt_cfg(esp_ae_ch_cvt_cfg_t *config)
{
if (config) {
if (config->weight) {
esp_gmf_oal_free(config->weight);
config->weight = NULL;
config->weight_len = 0;
}
esp_gmf_oal_free(config);
}
}
static esp_gmf_err_t __set_dest_ch(esp_gmf_element_handle_t handle, esp_gmf_args_desc_t *arg_desc,
uint8_t *buf, int buf_len)
{
ESP_GMF_NULL_CHECK(TAG, buf, {return ESP_GMF_ERR_INVALID_ARG;});
uint8_t dest_ch = (uint8_t)(*buf);
return esp_gmf_ch_cvt_set_dest_channel(handle, dest_ch);
}
static esp_gmf_err_t esp_gmf_ch_cvt_new(void *cfg, esp_gmf_obj_handle_t *handle)
{
return esp_gmf_ch_cvt_init(cfg, (esp_gmf_element_handle_t *)handle);
}
static esp_gmf_job_err_t esp_gmf_ch_cvt_open(esp_gmf_element_handle_t self, void *para)
{
esp_gmf_ch_cvt_t *ch_cvt = (esp_gmf_ch_cvt_t *)self;
esp_ae_ch_cvt_cfg_t *ch_info = (esp_ae_ch_cvt_cfg_t *)OBJ_GET_CFG(self);
ESP_GMF_NULL_CHECK(TAG, ch_info, {return ESP_GMF_JOB_ERR_FAIL;});
esp_ae_ch_cvt_open(ch_info, &ch_cvt->ch_hd);
ESP_GMF_CHECK(TAG, ch_cvt->ch_hd, {return ESP_GMF_JOB_ERR_FAIL;}, "Failed to create channel conversion handle");
ch_cvt->in_bytes_per_sample = (ch_info->bits_per_sample >> 3) * ch_info->src_ch;
ch_cvt->out_bytes_per_sample = (ch_info->bits_per_sample >> 3) * ch_info->dest_ch;
GMF_AUDIO_UPDATE_SND_INFO(self, ch_info->sample_rate, ch_info->bits_per_sample, ch_info->dest_ch);
ESP_LOGD(TAG, "Open, rate: %ld, bits: %d, src_channel: %d, dest_channel: %d",
ch_info->sample_rate, ch_info->bits_per_sample, ch_info->src_ch, ch_info->dest_ch);
ch_cvt->need_reopen = false;
ch_cvt->bypass = ch_info->src_ch == ch_info->dest_ch;
return ESP_GMF_JOB_ERR_OK;
}
static esp_gmf_job_err_t esp_gmf_ch_cvt_close(esp_gmf_element_handle_t self, void *para)
{
esp_gmf_ch_cvt_t *ch_cvt = (esp_gmf_ch_cvt_t *)self;
ESP_LOGD(TAG, "Closed, %p", self);
if (ch_cvt->ch_hd != NULL) {
esp_ae_ch_cvt_close(ch_cvt->ch_hd);
ch_cvt->ch_hd = NULL;
}
return ESP_GMF_JOB_ERR_OK;
}
static esp_gmf_job_err_t esp_gmf_ch_cvt_process(esp_gmf_element_handle_t self, void *para)
{
esp_gmf_ch_cvt_t *ch_cvt = (esp_gmf_ch_cvt_t *)self;
esp_gmf_job_err_t out_len = ESP_GMF_JOB_ERR_OK;
if (ch_cvt->need_reopen) {
esp_gmf_ch_cvt_close(self, NULL);
out_len = esp_gmf_ch_cvt_open(self, NULL);
if (out_len != ESP_GMF_JOB_ERR_OK) {
ESP_LOGE(TAG, "Channel conversion reopen failed");
return out_len;
}
}
esp_gmf_port_handle_t in_port = ESP_GMF_ELEMENT_GET(self)->in;
esp_gmf_port_handle_t out_port = ESP_GMF_ELEMENT_GET(self)->out;
esp_gmf_payload_t *in_load = NULL;
esp_gmf_payload_t *out_load = NULL;
int samples_num = ESP_GMF_ELEMENT_GET(ch_cvt)->in_attr.data_size / (ch_cvt->in_bytes_per_sample);
int bytes = samples_num * ch_cvt->in_bytes_per_sample;
esp_gmf_err_io_t load_ret = esp_gmf_port_acquire_in(in_port, &in_load, bytes, ESP_GMF_MAX_DELAY);
if (load_ret < ESP_GMF_IO_OK) {
ESP_LOGE(TAG, "ACQ IN, ret: %d", load_ret);
out_len = ESP_GMF_JOB_ERR_FAIL;
goto __ch_release;
}
samples_num = in_load->valid_size / (ch_cvt->in_bytes_per_sample);
bytes = samples_num * ch_cvt->out_bytes_per_sample;
if ((ch_cvt->in_bytes_per_sample * samples_num != in_load->valid_size) || (load_ret < ESP_GMF_IO_OK)) {
ESP_LOGE(TAG, "Invalid in load size %d, ret %d", in_load->valid_size, load_ret);
out_len = ESP_GMF_JOB_ERR_FAIL;
goto __ch_release;
}
if (ch_cvt->bypass && (in_port->is_shared == true)) {
// This case channel conversion is do bypass
out_load = in_load;
}
load_ret = esp_gmf_port_acquire_out(out_port, &out_load, samples_num ? bytes : in_load->buf_length, ESP_GMF_MAX_DELAY);
ESP_GMF_PORT_ACQUIRE_OUT_CHECK(TAG, load_ret, out_len, { goto __ch_release;});
if (samples_num) {
esp_ae_err_t ret = esp_ae_ch_cvt_process(ch_cvt->ch_hd, samples_num, (unsigned char *)in_load->buf, (unsigned char *)out_load->buf);
ESP_GMF_RET_ON_ERROR(TAG, ret, {out_len = ESP_GMF_JOB_ERR_FAIL; goto __ch_release;}, "Channel conversion process error, ret: %d", ret);
}
out_load->valid_size = samples_num * ch_cvt->out_bytes_per_sample;
out_load->pts = in_load->pts;
out_load->is_done = in_load->is_done;
ESP_LOGV(TAG, "Samples: %d, IN-PLD: %p-%p-%d-%d-%d, OUT-PLD: %p-%p-%d-%d-%d",
samples_num, in_load, in_load->buf, in_load->valid_size, in_load->buf_length, in_load->is_done,
out_load, out_load->buf, out_load->valid_size, out_load->buf_length, out_load->is_done);
if (out_load->valid_size > 0) {
esp_gmf_audio_el_update_file_pos((esp_gmf_element_handle_t)self, out_load->valid_size);
}
if (in_load->is_done) {
out_len = ESP_GMF_JOB_ERR_DONE;
ESP_LOGD(TAG, "The channel cvt done, out len: %d", out_load->valid_size);
}
__ch_release:
// Release in and out port
if (out_load != NULL) {
load_ret = esp_gmf_port_release_out(out_port, out_load, ESP_GMF_MAX_DELAY);
if ((load_ret < ESP_GMF_IO_OK) && (load_ret != ESP_GMF_IO_ABORT)) {
ESP_LOGE(TAG, "OUT port release error, ret:%d", load_ret);
out_len = ESP_GMF_JOB_ERR_FAIL;
}
}
if (in_load != NULL) {
load_ret = esp_gmf_port_release_in(in_port, in_load, ESP_GMF_MAX_DELAY);
if ((load_ret < ESP_GMF_IO_OK) && (load_ret != ESP_GMF_IO_ABORT)) {
ESP_LOGE(TAG, "IN port release error, ret:%d", load_ret);
out_len = ESP_GMF_JOB_ERR_FAIL;
}
}
return out_len;
}
static esp_gmf_err_t ch_cvt_received_event_handler(esp_gmf_event_pkt_t *evt, void *ctx)
{
ESP_GMF_NULL_CHECK(TAG, ctx, {return ESP_GMF_ERR_INVALID_ARG;});
ESP_GMF_NULL_CHECK(TAG, evt, {return ESP_GMF_ERR_INVALID_ARG;});
if ((evt->type != ESP_GMF_EVT_TYPE_REPORT_INFO)
|| (evt->sub != ESP_GMF_INFO_SOUND)
|| (evt->payload == NULL)) {
return ESP_GMF_ERR_OK;
}
esp_gmf_element_handle_t self = (esp_gmf_element_handle_t)ctx;
esp_gmf_element_handle_t el = evt->from;
esp_gmf_event_state_t state = ESP_GMF_EVENT_STATE_NONE;
esp_gmf_element_get_state(self, &state);
esp_gmf_info_sound_t *info = (esp_gmf_info_sound_t *)evt->payload;
esp_ae_ch_cvt_cfg_t *config = (esp_ae_ch_cvt_cfg_t *)OBJ_GET_CFG(self);
ESP_GMF_NULL_CHECK(TAG, config, return ESP_GMF_ERR_FAIL);
esp_gmf_ch_cvt_t *ch_cvt = (esp_gmf_ch_cvt_t *)self;
ch_cvt->need_reopen = (config->sample_rate != info->sample_rates) || (info->channels != config->src_ch) || (config->bits_per_sample != info->bits);
config->sample_rate = info->sample_rates;
config->src_ch = info->channels;
config->bits_per_sample = info->bits;
ESP_LOGD(TAG, "RECV info, from: %s-%p, next: %p, self: %s-%p, type: %x, state: %s, rate: %d, ch: %d, bits: %d",
OBJ_GET_TAG(el), el, esp_gmf_node_for_next((esp_gmf_node_t *)el), OBJ_GET_TAG(self), self, evt->type,
esp_gmf_event_get_state_str(state), info->sample_rates, info->channels, info->bits);
if (state == ESP_GMF_EVENT_STATE_NONE) {
esp_gmf_element_set_state(self, ESP_GMF_EVENT_STATE_INITIALIZED);
}
return ESP_GMF_ERR_OK;
}
static esp_gmf_err_t esp_gmf_ch_cvt_destroy(esp_gmf_element_handle_t self)
{
esp_gmf_ch_cvt_t *ch_cvt = (esp_gmf_ch_cvt_t *)self;
ESP_LOGD(TAG, "Destroyed, %p", self);
free_esp_ae_ch_cvt_cfg(OBJ_GET_CFG(self));
esp_gmf_audio_el_deinit(self);
esp_gmf_oal_free(ch_cvt);
return ESP_GMF_ERR_OK;
}
static esp_gmf_err_t _load_channel_cvt_caps_func(esp_gmf_element_handle_t handle)
{
esp_gmf_cap_t *caps = NULL;
esp_gmf_cap_t dec_caps = {0};
dec_caps.cap_eightcc = ESP_GMF_CAPS_AUDIO_CHANNEL_CONVERT;
dec_caps.attr_fun = NULL;
int ret = esp_gmf_cap_append(&caps, &dec_caps);
ESP_GMF_RET_ON_NOT_OK(TAG, ret, {return ret;}, "Failed to create capability");
esp_gmf_element_t *el = (esp_gmf_element_t *)handle;
el->caps = caps;
return ESP_GMF_ERR_OK;
}
static esp_gmf_err_t _load_channel_cvt_methods_func(esp_gmf_element_handle_t handle)
{
esp_gmf_method_t *method = NULL;
esp_gmf_args_desc_t *set_args = NULL;
esp_gmf_err_t ret = esp_gmf_args_desc_append(&set_args, AMETHOD_ARG(CH_CVT, SET_DEST_CH, CH), ESP_GMF_ARGS_TYPE_UINT8, sizeof(uint8_t), 0);
ESP_GMF_RET_ON_NOT_OK(TAG, ret, {return ret;}, "Failed to append argument");
ret = esp_gmf_method_append(&method, AMETHOD(CH_CVT, SET_DEST_CH), __set_dest_ch, set_args);
ESP_GMF_RET_ON_ERROR(TAG, ret, {return ret;}, "Failed to register %s method", AMETHOD(CH_CVT, SET_DEST_CH));
esp_gmf_element_t *el = (esp_gmf_element_t *)handle;
el->method = method;
return ESP_GMF_ERR_OK;
}
esp_gmf_err_t esp_gmf_ch_cvt_set_dest_channel(esp_gmf_element_handle_t handle, uint8_t dest_ch)
{
ESP_GMF_NULL_CHECK(TAG, handle, { return ESP_GMF_ERR_INVALID_ARG;});
esp_ae_ch_cvt_cfg_t *cfg = (esp_ae_ch_cvt_cfg_t *)OBJ_GET_CFG(handle);
if (cfg) {
if (cfg->dest_ch == dest_ch) {
return ESP_GMF_ERR_OK;
}
cfg->dest_ch = dest_ch;
esp_gmf_ch_cvt_t *ch_cvt = (esp_gmf_ch_cvt_t *)handle;
ch_cvt->need_reopen = true;
return ESP_GMF_ERR_OK;
}
ESP_LOGE(TAG, "Failed to set dest channel, cfg is NULL");
return ESP_GMF_ERR_FAIL;
}
esp_gmf_err_t esp_gmf_ch_cvt_init(esp_ae_ch_cvt_cfg_t *config, esp_gmf_element_handle_t *handle)
{
ESP_GMF_NULL_CHECK(TAG, handle, {return ESP_GMF_ERR_INVALID_ARG;});
*handle = NULL;
esp_gmf_err_t ret = ESP_GMF_ERR_OK;
esp_gmf_ch_cvt_t *ch_cvt = esp_gmf_oal_calloc(1, sizeof(esp_gmf_ch_cvt_t));
ESP_GMF_MEM_VERIFY(TAG, ch_cvt, {return ESP_GMF_ERR_MEMORY_LACK;}, "channel conversion", sizeof(esp_gmf_ch_cvt_t));
esp_gmf_obj_t *obj = (esp_gmf_obj_t *)ch_cvt;
obj->new_obj = esp_gmf_ch_cvt_new;
obj->del_obj = esp_gmf_ch_cvt_destroy;
esp_ae_ch_cvt_cfg_t *cfg = NULL;
if (config) {
dupl_esp_ae_ch_cvt_cfg(config, &cfg);
} else {
esp_ae_ch_cvt_cfg_t dcfg = DEFAULT_ESP_GMF_CH_CVT_CONFIG();
dupl_esp_ae_ch_cvt_cfg(&dcfg, &cfg);
}
ESP_GMF_CHECK(TAG, cfg, {ret = ESP_GMF_ERR_MEMORY_LACK; goto CH_CVT_INIT_FAIL;}, "Failed to allocate channel conversion configuration");
esp_gmf_obj_set_config(obj, cfg, sizeof(esp_ae_ch_cvt_cfg_t));
ret = esp_gmf_obj_set_tag(obj, "aud_ch_cvt");
ESP_GMF_RET_ON_NOT_OK(TAG, ret, goto CH_CVT_INIT_FAIL, "Failed to set obj tag");
esp_gmf_element_cfg_t el_cfg = {0};
ESP_GMF_ELEMENT_IN_PORT_ATTR_SET(el_cfg.in_attr, ESP_GMF_EL_PORT_CAP_SINGLE, 0, 0,
ESP_GMF_PORT_TYPE_BLOCK | ESP_GMF_PORT_TYPE_BYTE, ESP_GMF_ELEMENT_PORT_DATA_SIZE_DEFAULT);
ESP_GMF_ELEMENT_IN_PORT_ATTR_SET(el_cfg.out_attr, ESP_GMF_EL_PORT_CAP_SINGLE, 0, 0,
ESP_GMF_PORT_TYPE_BLOCK | ESP_GMF_PORT_TYPE_BYTE, ESP_GMF_ELEMENT_PORT_DATA_SIZE_DEFAULT);
el_cfg.dependency = true;
ret = esp_gmf_audio_el_init(ch_cvt, &el_cfg);
ESP_GMF_RET_ON_NOT_OK(TAG, ret, goto CH_CVT_INIT_FAIL, "Failed to initialize channel conversion element");
ESP_GMF_ELEMENT_GET(ch_cvt)->ops.open = esp_gmf_ch_cvt_open;
ESP_GMF_ELEMENT_GET(ch_cvt)->ops.process = esp_gmf_ch_cvt_process;
ESP_GMF_ELEMENT_GET(ch_cvt)->ops.close = esp_gmf_ch_cvt_close;
ESP_GMF_ELEMENT_GET(ch_cvt)->ops.event_receiver = ch_cvt_received_event_handler;
ESP_GMF_ELEMENT_GET(ch_cvt)->ops.load_caps = _load_channel_cvt_caps_func;
ESP_GMF_ELEMENT_GET(ch_cvt)->ops.load_methods = _load_channel_cvt_methods_func;
*handle = obj;
ESP_LOGD(TAG, "Initialization, %s-%p", OBJ_GET_TAG(obj), obj);
return ESP_GMF_ERR_OK;
CH_CVT_INIT_FAIL:
esp_gmf_ch_cvt_destroy(obj);
return ret;
}
@@ -0,0 +1,274 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO., LTD
* SPDX-License-Identifier: LicenseRef-Espressif-Modified-MIT
*
* See LICENSE file for details.
*/
#include <string.h>
#include "esp_log.h"
#include "esp_gmf_oal_mem.h"
#include "esp_gmf_oal_mutex.h"
#include "esp_gmf_node.h"
#include "esp_gmf_deinterleave.h"
#include "esp_ae_data_weaver.h"
#include "esp_heap_caps.h"
#include "gmf_audio_common.h"
#include "esp_gmf_cap.h"
#include "esp_gmf_caps_def.h"
#include "esp_gmf_audio_element.h"
#define ESP_GMF_PROCESS_SAMPLE (256)
/**
* @brief Audio deinterleave context in GMF
*/
typedef struct {
esp_gmf_audio_element_t parent; /*!< The GMF deinterleave handle */
uint8_t bytes_per_sample; /*!< Bytes number of per sampling point */
esp_gmf_payload_t *in_load; /*!< The input payload */
esp_gmf_payload_t **out_load; /*!< The array of output payload */
uint8_t **out_arr; /*!< The array of output buffer pointer */
uint8_t channel; /*!< The audio channel */
uint8_t bits_per_sample; /*!< Bits number of per sampling point */
bool need_reopen : 1; /*!< Whether need to reopen.
True: Execute the close function first, then execute the open function
False: Do nothing */
} esp_gmf_deinterleave_t;
static const char *TAG = "ESP_GMF_DEINTLV";
static inline esp_gmf_err_t dupl_esp_ae_deinterleave_cfg(esp_gmf_deinterleave_cfg *config, esp_gmf_deinterleave_cfg **new_config)
{
*new_config = esp_gmf_oal_calloc(1, sizeof(*config));
ESP_GMF_MEM_VERIFY(TAG, *new_config, {return ESP_GMF_ERR_MEMORY_LACK;}, "deinterleave configuration", sizeof(*config));
memcpy(*new_config, config, sizeof(*config));
return ESP_GMF_JOB_ERR_OK;
}
static inline void free_esp_ae_deinterleave_cfg(esp_gmf_deinterleave_cfg *config)
{
if (config) {
esp_gmf_oal_free(config);
}
}
static esp_gmf_err_t esp_gmf_deinterleave_new(void *cfg, esp_gmf_obj_handle_t *handle)
{
return esp_gmf_deinterleave_init(cfg, (esp_gmf_element_handle_t *)handle);
}
static esp_gmf_job_err_t esp_gmf_deinterleave_open(esp_gmf_element_handle_t self, void *para)
{
esp_gmf_deinterleave_t *deinterleave = (esp_gmf_deinterleave_t *)self;
esp_gmf_deinterleave_cfg *deinterleave_info = (esp_gmf_deinterleave_cfg *)OBJ_GET_CFG(self);
ESP_GMF_NULL_CHECK(TAG, deinterleave_info, {return ESP_GMF_JOB_ERR_FAIL;})
deinterleave->bytes_per_sample = (deinterleave_info->bits_per_sample >> 3);
deinterleave->out_load = esp_gmf_oal_calloc(1, sizeof(esp_gmf_payload_t *) * deinterleave_info->channel);
ESP_GMF_MEM_VERIFY(TAG, deinterleave->out_load, {return ESP_GMF_JOB_ERR_FAIL;},
"out load", sizeof(esp_gmf_payload_t *) * deinterleave_info->channel);
deinterleave->out_arr = esp_gmf_oal_calloc(1, sizeof(uint8_t *) * deinterleave_info->channel);
ESP_GMF_MEM_VERIFY(TAG, deinterleave->out_arr, {return ESP_GMF_JOB_ERR_FAIL;},
"out buffer array", sizeof(uint8_t *) * deinterleave_info->channel);
GMF_AUDIO_UPDATE_SND_INFO(self, deinterleave_info->sample_rate, deinterleave_info->bits_per_sample, 1);
deinterleave->channel = deinterleave_info->channel;
deinterleave->bits_per_sample = deinterleave_info->bits_per_sample;
deinterleave->need_reopen = false;
ESP_LOGD(TAG, "Open, %p", self);
return ESP_GMF_ERR_OK;
}
static esp_gmf_job_err_t esp_gmf_deinterleave_close(esp_gmf_element_handle_t self, void *para)
{
esp_gmf_deinterleave_t *deinterleave = (esp_gmf_deinterleave_t *)self;
ESP_LOGD(TAG, "Closed, %p", self);
if (deinterleave->out_arr != NULL) {
esp_gmf_oal_free(deinterleave->out_arr);
deinterleave->out_arr = NULL;
}
if (deinterleave->out_load != NULL) {
esp_gmf_oal_free(deinterleave->out_load);
deinterleave->out_load = NULL;
}
return ESP_GMF_ERR_OK;
}
static esp_gmf_job_err_t esp_gmf_deinterleave_process(esp_gmf_element_handle_t self, void *para)
{
esp_gmf_deinterleave_t *deinterleave = (esp_gmf_deinterleave_t *)self;
esp_gmf_job_err_t out_len = ESP_GMF_JOB_ERR_OK;
int i = 0;
if (deinterleave->need_reopen) {
esp_gmf_deinterleave_close(self, NULL);
out_len = esp_gmf_deinterleave_open(self, NULL);
if (out_len != ESP_GMF_JOB_ERR_OK) {
ESP_LOGE(TAG, "deinterleave reopen failed");
return out_len;
}
}
esp_gmf_port_handle_t in_port = ESP_GMF_ELEMENT_GET(self)->in;
esp_gmf_port_handle_t out = ESP_GMF_ELEMENT_GET(self)->out;
esp_gmf_port_handle_t out_port = out;
deinterleave->in_load = NULL;
memset(deinterleave->out_load, 0, sizeof(esp_gmf_payload_t *) * deinterleave->channel);
int samples_num = ESP_GMF_ELEMENT_GET(deinterleave)->in_attr.data_size / (deinterleave->bytes_per_sample * deinterleave->channel);
int bytes = samples_num * deinterleave->bytes_per_sample * deinterleave->channel;
esp_gmf_err_io_t load_ret = esp_gmf_port_acquire_in(in_port, &deinterleave->in_load, bytes, ESP_GMF_MAX_DELAY);
samples_num = deinterleave->in_load->valid_size / (deinterleave->bytes_per_sample * deinterleave->channel);
bytes = samples_num * deinterleave->bytes_per_sample;
if (((bytes * deinterleave->channel) != deinterleave->in_load->valid_size) || (load_ret < ESP_GMF_IO_OK)) {
ESP_LOGE(TAG, "Invalid in load size %d, ret %d", deinterleave->in_load->valid_size, load_ret);
out_len = ESP_GMF_JOB_ERR_FAIL;
goto __deintlv_release;
}
ESP_LOGV(TAG, "IN: load: %p, buf: %p, valid size: %d, buf length: %d, done: %d",
deinterleave->in_load, deinterleave->in_load->buf, deinterleave->in_load->valid_size,
deinterleave->in_load->buf_length, deinterleave->in_load->is_done);
// Not do deinterleave anymore if one channel failed
while (out_port != NULL) {
load_ret = esp_gmf_port_acquire_out(out_port, &(deinterleave->out_load[i]),
samples_num ? bytes : deinterleave->in_load->buf_length, ESP_GMF_MAX_DELAY);
ESP_GMF_PORT_CHECK(TAG, load_ret, out_len, {out_len = ESP_GMF_JOB_ERR_FAIL; goto __deintlv_release;}, "Failed to acquire out, idx:%d, ret: %d", i, load_ret);
deinterleave->out_arr[i] = deinterleave->out_load[i]->buf;
out_port = out_port->next;
i++;
}
if (samples_num > 0) {
esp_ae_err_t proc_ret = esp_ae_deintlv_process(deinterleave->channel, deinterleave->bits_per_sample, samples_num,
deinterleave->in_load->buf, (void **)deinterleave->out_arr);
ESP_GMF_RET_ON_ERROR(TAG, proc_ret, {out_len = ESP_GMF_JOB_ERR_FAIL; goto __deintlv_release;}, "Deinterleave process error, ret: %d", proc_ret);
}
out_port = out;
i = 0;
while (out_port != NULL) {
deinterleave->out_load[i]->pts = deinterleave->in_load->pts;
deinterleave->out_load[i]->is_done = deinterleave->in_load->is_done;
deinterleave->out_load[i]->valid_size = samples_num * deinterleave->bytes_per_sample;
ESP_LOGV(TAG, "OUT: idx: %d load: %p, buf: %p, valid size: %d, buf length: %d, done: %d",
i, deinterleave->out_load[i], deinterleave->out_load[i]->buf, deinterleave->out_load[i]->valid_size,
deinterleave->out_load[i]->buf_length, deinterleave->out_load[i]->is_done);
out_port = out_port->next;
i++;
}
if (deinterleave->in_load->is_done) {
ESP_LOGD(TAG, "Deinterleave is done");
out_len = ESP_GMF_JOB_ERR_DONE;
}
__deintlv_release:
out_port = out;
i = 0;
while (out_port != NULL && deinterleave->out_load[i] != NULL) {
load_ret = esp_gmf_port_release_out(out_port, deinterleave->out_load[i], out_port->wait_ticks);
if ((load_ret < ESP_GMF_IO_OK) && (load_ret != ESP_GMF_IO_ABORT)) {
ESP_LOGE(TAG, "OUT port release error, ret:%d", load_ret);
out_len = ESP_GMF_JOB_ERR_FAIL;
}
out_port = out_port->next;
i++;
}
if (deinterleave->in_load != NULL) {
load_ret = esp_gmf_port_release_in(in_port, deinterleave->in_load, ESP_GMF_MAX_DELAY);
if ((load_ret < ESP_GMF_IO_OK) && (load_ret != ESP_GMF_IO_ABORT)) {
ESP_LOGE(TAG, "IN port release error, ret:%d", load_ret);
out_len = ESP_GMF_JOB_ERR_FAIL;
}
}
return out_len;
}
static esp_gmf_err_t deinterleave_received_event_handler(esp_gmf_event_pkt_t *evt, void *ctx)
{
ESP_GMF_NULL_CHECK(TAG, ctx, {return ESP_GMF_ERR_INVALID_ARG;});
ESP_GMF_NULL_CHECK(TAG, evt, {return ESP_GMF_ERR_INVALID_ARG;});
if ((evt->type != ESP_GMF_EVT_TYPE_REPORT_INFO)
|| (evt->sub != ESP_GMF_INFO_SOUND)
|| (evt->payload == NULL)) {
return ESP_GMF_ERR_OK;
}
esp_gmf_element_handle_t self = (esp_gmf_element_handle_t)ctx;
esp_gmf_element_handle_t el = evt->from;
esp_gmf_event_state_t state = ESP_GMF_EVENT_STATE_NONE;
esp_gmf_element_get_state(self, &state);
esp_gmf_info_sound_t *info = (esp_gmf_info_sound_t *)evt->payload;
ESP_GMF_NULL_CHECK(TAG, info, { return ESP_GMF_ERR_FAIL;});
esp_gmf_deinterleave_cfg *config = (esp_gmf_deinterleave_cfg *)OBJ_GET_CFG(self);
ESP_GMF_NULL_CHECK(TAG, config, return ESP_GMF_ERR_FAIL);
esp_gmf_deinterleave_t *deinterleave = (esp_gmf_deinterleave_t *)self;
deinterleave->need_reopen = (config->sample_rate != info->sample_rates) || (info->channels != config->channel) || (config->bits_per_sample != info->bits);
config->sample_rate = info->sample_rates;
config->channel = info->channels;
config->bits_per_sample = info->bits;
ESP_LOGD(TAG, "RECV element info, from: %s-%p, next: %p, self: %s-%p, type: %x, state: %s, rate: %d, ch: %d, bits: %d",
OBJ_GET_TAG(el), el, esp_gmf_node_for_next((esp_gmf_node_t *)el), OBJ_GET_TAG(self), self, evt->type,
esp_gmf_event_get_state_str(state), info->sample_rates, info->channels, info->bits);
if (state == ESP_GMF_EVENT_STATE_NONE) {
esp_gmf_element_set_state(self, ESP_GMF_EVENT_STATE_INITIALIZED);
}
return ESP_GMF_ERR_OK;
}
static esp_gmf_err_t esp_gmf_deinterleave_destroy(esp_gmf_element_handle_t self)
{
esp_gmf_deinterleave_t *deinterleave = (esp_gmf_deinterleave_t *)self;
ESP_LOGD(TAG, "Destroyed, %p", self);
free_esp_ae_deinterleave_cfg(OBJ_GET_CFG(self));
esp_gmf_audio_el_deinit(self);
esp_gmf_oal_free(deinterleave);
return ESP_GMF_ERR_OK;
}
static esp_gmf_err_t _load_deinterleave_caps_func(esp_gmf_element_handle_t handle)
{
esp_gmf_cap_t *caps = NULL;
esp_gmf_cap_t dec_caps = {0};
dec_caps.cap_eightcc = ESP_GMF_CAPS_AUDIO_DEINTERLEAVE;
dec_caps.attr_fun = NULL;
int ret = esp_gmf_cap_append(&caps, &dec_caps);
ESP_GMF_RET_ON_NOT_OK(TAG, ret, {return ret;}, "Failed to create capability");
esp_gmf_element_t *el = (esp_gmf_element_t *)handle;
el->caps = caps;
return ESP_GMF_ERR_OK;
}
esp_gmf_err_t esp_gmf_deinterleave_init(esp_gmf_deinterleave_cfg *config, esp_gmf_element_handle_t *handle)
{
ESP_GMF_NULL_CHECK(TAG, handle, {return ESP_GMF_ERR_INVALID_ARG;});
*handle = NULL;
esp_gmf_err_t ret = ESP_GMF_ERR_OK;
esp_gmf_deinterleave_t *deinterleave = esp_gmf_oal_calloc(1, sizeof(esp_gmf_deinterleave_t));
ESP_GMF_MEM_VERIFY(TAG, deinterleave, {return ESP_GMF_ERR_MEMORY_LACK;}, "deinterleave", sizeof(esp_gmf_deinterleave_t));
esp_gmf_obj_t *obj = (esp_gmf_obj_t *)deinterleave;
obj->new_obj = esp_gmf_deinterleave_new;
obj->del_obj = esp_gmf_deinterleave_destroy;
esp_gmf_deinterleave_cfg *cfg = NULL;
if (config) {
dupl_esp_ae_deinterleave_cfg(config, &cfg);
} else {
esp_gmf_deinterleave_cfg dcfg = DEFAULT_ESP_GMF_DEINTERLEAVE_CONFIG();
dupl_esp_ae_deinterleave_cfg(&dcfg, &cfg);
}
ESP_GMF_CHECK(TAG, cfg, ret = ESP_GMF_ERR_MEMORY_LACK; goto DEINTLV_INIT_FAIL;, "Failed to allocate deinterleave configuration");
esp_gmf_obj_set_config(obj, cfg, sizeof(esp_gmf_deinterleave_cfg));
ret = esp_gmf_obj_set_tag(obj, "aud_deintlv");
ESP_GMF_RET_ON_NOT_OK(TAG, ret, goto DEINTLV_INIT_FAIL, "Failed to set obj tag");
esp_gmf_element_cfg_t el_cfg = {0};
ESP_GMF_ELEMENT_IN_PORT_ATTR_SET(el_cfg.in_attr, ESP_GMF_EL_PORT_CAP_SINGLE, 0, 0,
ESP_GMF_PORT_TYPE_BLOCK | ESP_GMF_PORT_TYPE_BYTE, ESP_GMF_ELEMENT_PORT_DATA_SIZE_DEFAULT);
ESP_GMF_ELEMENT_IN_PORT_ATTR_SET(el_cfg.out_attr, ESP_GMF_EL_PORT_CAP_MULTI, 0, 0,
ESP_GMF_PORT_TYPE_BLOCK | ESP_GMF_PORT_TYPE_BYTE, ESP_GMF_ELEMENT_PORT_DATA_SIZE_DEFAULT);
el_cfg.dependency = true;
ret = esp_gmf_audio_el_init(deinterleave, &el_cfg);
ESP_GMF_RET_ON_NOT_OK(TAG, ret, goto DEINTLV_INIT_FAIL, "Failed to initialize deinterleave element");
ESP_GMF_ELEMENT_GET(deinterleave)->ops.open = esp_gmf_deinterleave_open;
ESP_GMF_ELEMENT_GET(deinterleave)->ops.process = esp_gmf_deinterleave_process;
ESP_GMF_ELEMENT_GET(deinterleave)->ops.close = esp_gmf_deinterleave_close;
ESP_GMF_ELEMENT_GET(deinterleave)->ops.event_receiver = deinterleave_received_event_handler;
ESP_GMF_ELEMENT_GET(deinterleave)->ops.load_caps = _load_deinterleave_caps_func;
*handle = obj;
ESP_LOGD(TAG, "Initialization, %s-%p", OBJ_GET_TAG(obj), obj);
return ESP_GMF_ERR_OK;
DEINTLV_INIT_FAIL:
esp_gmf_deinterleave_destroy(obj);
return ret;
}
@@ -0,0 +1,446 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO., LTD
* SPDX-License-Identifier: LicenseRef-Espressif-Modified-MIT
*
* See LICENSE file for details.
*/
#include <string.h>
#include "esp_log.h"
#include "esp_gmf_oal_mem.h"
#include "esp_gmf_oal_mutex.h"
#include "esp_gmf_node.h"
#include "esp_gmf_eq.h"
#include "esp_gmf_args_desc.h"
#include "gmf_audio_common.h"
#include "esp_gmf_audio_methods_def.h"
#include "esp_gmf_cap.h"
#include "esp_gmf_caps_def.h"
#include "esp_gmf_audio_element.h"
/**
* @brief Audio equalizer context in GMF
*/
typedef struct {
esp_gmf_audio_element_t parent; /*!< The GMF eq handle */
esp_ae_eq_handle_t eq_hd; /*!< The audio effects eq handle */
uint8_t bytes_per_sample; /*!< Bytes number of per sampling point */
bool *is_filter_enabled; /*!< The flag of whether eq filter is enabled */
bool need_reopen : 1; /*!< Whether need to reopen.
True: Execute the close function first, then execute the open function
False: Do nothing */
} esp_gmf_eq_t;
static const char *TAG = "ESP_GMF_EQ";
const esp_ae_eq_filter_para_t esp_gmf_default_eq_paras[10] = {
{ESP_AE_EQ_FILTER_PEAK, 31, 1.0, 0.0},
{ESP_AE_EQ_FILTER_PEAK, 62, 1.0, 0.0},
{ESP_AE_EQ_FILTER_PEAK, 125, 1.0, 0.0},
{ESP_AE_EQ_FILTER_PEAK, 250, 1.0, 1.0},
{ESP_AE_EQ_FILTER_PEAK, 500, 1.0, 2.0},
{ESP_AE_EQ_FILTER_PEAK, 1000, 1.0, 3.0},
{ESP_AE_EQ_FILTER_PEAK, 2000, 1.0, 3.0},
{ESP_AE_EQ_FILTER_PEAK, 4000, 1.0, 2.0},
{ESP_AE_EQ_FILTER_PEAK, 8000, 1.0, 1.0},
{ESP_AE_EQ_FILTER_PEAK, 16000, 1.0, 0.0},
};
static inline esp_gmf_err_t dupl_esp_ae_eq_cfg(esp_ae_eq_cfg_t *config, esp_ae_eq_cfg_t **new_config)
{
void *sub_cfg = NULL;
*new_config = esp_gmf_oal_calloc(1, sizeof(*config));
ESP_GMF_MEM_VERIFY(TAG, *new_config, {return ESP_GMF_ERR_MEMORY_LACK;}, "eq configuration", sizeof(*config));
memcpy(*new_config, config, sizeof(*config));
if (config->para && (config->filter_num > 0)) {
sub_cfg = esp_gmf_oal_calloc(1, config->filter_num * sizeof(esp_ae_eq_filter_para_t));
ESP_GMF_MEM_VERIFY(TAG, sub_cfg, {esp_gmf_oal_free(*new_config); return ESP_GMF_ERR_MEMORY_LACK;},
"filter parameter", config->filter_num * sizeof(esp_ae_eq_filter_para_t));
memcpy(sub_cfg, config->para, config->filter_num * sizeof(esp_ae_eq_filter_para_t));
(*new_config)->para = sub_cfg;
}
return ESP_GMF_JOB_ERR_OK;
}
static inline void free_esp_ae_eq_cfg(esp_ae_eq_cfg_t *config)
{
if (config) {
if (config->para && (config->para != esp_gmf_default_eq_paras)) {
esp_gmf_oal_free(config->para);
}
esp_gmf_oal_free(config);
}
}
static inline void eq_change_src_info(esp_gmf_element_handle_t self, uint32_t src_rate, uint8_t src_ch, uint8_t src_bits)
{
esp_ae_eq_cfg_t *eq_info = (esp_ae_eq_cfg_t *)OBJ_GET_CFG(self);
eq_info->channel = src_ch;
eq_info->sample_rate = src_rate;
eq_info->bits_per_sample = src_bits;
}
static esp_gmf_err_t __eq_set_para(esp_gmf_element_handle_t handle, esp_gmf_args_desc_t *arg_desc,
uint8_t *buf, int buf_len)
{
ESP_GMF_NULL_CHECK(TAG, handle, {return ESP_GMF_ERR_INVALID_ARG;});
ESP_GMF_NULL_CHECK(TAG, arg_desc, {return ESP_GMF_ERR_INVALID_ARG;});
ESP_GMF_NULL_CHECK(TAG, buf, {return ESP_GMF_ERR_INVALID_ARG;});
uint8_t idx = (uint8_t)(*buf);
return esp_gmf_eq_set_para(handle, idx, (esp_ae_eq_filter_para_t *)(buf + arg_desc->next->offset));
}
static esp_gmf_err_t __eq_get_para(esp_gmf_element_handle_t handle, esp_gmf_args_desc_t *arg_desc,
uint8_t *buf, int buf_len)
{
ESP_GMF_NULL_CHECK(TAG, handle, {return ESP_GMF_ERR_INVALID_ARG;});
ESP_GMF_NULL_CHECK(TAG, arg_desc, {return ESP_GMF_ERR_INVALID_ARG;});
ESP_GMF_NULL_CHECK(TAG, buf, {return ESP_GMF_ERR_INVALID_ARG;});
uint8_t idx = (uint8_t)(*buf);
esp_ae_eq_filter_para_t *para = (esp_ae_eq_filter_para_t *)(buf + arg_desc->next->offset);
return esp_gmf_eq_get_para(handle, idx, para);
}
static esp_gmf_err_t __eq_enable_filter(esp_gmf_element_handle_t handle, esp_gmf_args_desc_t *arg_desc,
uint8_t *buf, int buf_len)
{
ESP_GMF_NULL_CHECK(TAG, handle, {return ESP_GMF_ERR_INVALID_ARG;});
ESP_GMF_NULL_CHECK(TAG, arg_desc, {return ESP_GMF_ERR_INVALID_ARG;});
ESP_GMF_NULL_CHECK(TAG, buf, {return ESP_GMF_ERR_INVALID_ARG;});
esp_gmf_args_desc_t *filter_desc = arg_desc;
uint8_t idx = (uint8_t)(*buf);
filter_desc = filter_desc->next;
uint8_t is_enable = (uint8_t)(*(buf + filter_desc->offset));
return esp_gmf_eq_enable_filter(handle, idx,is_enable);
}
static esp_gmf_err_t esp_gmf_eq_new(void *cfg, esp_gmf_obj_handle_t *handle)
{
return esp_gmf_eq_init(cfg, (esp_gmf_element_handle_t *)handle);
}
static esp_gmf_job_err_t esp_gmf_eq_open(esp_gmf_element_handle_t self, void *para)
{
esp_gmf_eq_t *eq = (esp_gmf_eq_t *)self;
esp_ae_eq_cfg_t *eq_info = (esp_ae_eq_cfg_t *)OBJ_GET_CFG(self);
ESP_GMF_NULL_CHECK(TAG, eq_info, {return ESP_GMF_JOB_ERR_FAIL;});
eq->bytes_per_sample = (eq_info->bits_per_sample >> 3) * eq_info->channel;
esp_ae_eq_open(eq_info, &eq->eq_hd);
ESP_GMF_CHECK(TAG, eq->eq_hd, {return ESP_GMF_JOB_ERR_FAIL;}, "Failed to create eq handle");
GMF_AUDIO_UPDATE_SND_INFO(self, eq_info->sample_rate, eq_info->bits_per_sample, eq_info->channel);
for (int i = 0; i < eq_info->filter_num; i++) {
if (eq->is_filter_enabled[i]) {
esp_ae_eq_enable_filter(eq->eq_hd, i);
} else {
esp_ae_eq_disable_filter(eq->eq_hd, i);
}
}
eq->need_reopen = false;
ESP_LOGD(TAG, "Open, %p", eq);
return ESP_GMF_ERR_OK;
}
static esp_gmf_job_err_t esp_gmf_eq_close(esp_gmf_element_handle_t self, void *para)
{
esp_gmf_eq_t *eq = (esp_gmf_eq_t *)self;
ESP_LOGD(TAG, "Closed, %p", self);
if (eq->eq_hd != NULL) {
esp_ae_eq_close(eq->eq_hd);
eq->eq_hd = NULL;
}
return ESP_GMF_ERR_OK;
}
static esp_gmf_job_err_t esp_gmf_eq_process(esp_gmf_element_handle_t self, void *para)
{
esp_gmf_eq_t *eq = (esp_gmf_eq_t *)self;
esp_gmf_job_err_t out_len = ESP_GMF_JOB_ERR_OK;
if (eq->need_reopen) {
esp_gmf_eq_close(self, NULL);
out_len = esp_gmf_eq_open(self, NULL);
if (out_len != ESP_GMF_JOB_ERR_OK) {
ESP_LOGE(TAG, "EQ reopen failed");
return out_len;
}
}
esp_gmf_port_handle_t in_port = ESP_GMF_ELEMENT_GET(self)->in;
esp_gmf_port_handle_t out_port = ESP_GMF_ELEMENT_GET(self)->out;
esp_gmf_payload_t *in_load = NULL;
esp_gmf_payload_t *out_load = NULL;
int samples_num = ESP_GMF_ELEMENT_GET(eq)->in_attr.data_size / (eq->bytes_per_sample);
int bytes = samples_num * eq->bytes_per_sample;
esp_gmf_err_io_t load_ret = esp_gmf_port_acquire_in(in_port, &in_load, bytes, ESP_GMF_MAX_DELAY);
samples_num = in_load->valid_size / (eq->bytes_per_sample);
bytes = samples_num * eq->bytes_per_sample;
if ((bytes != in_load->valid_size) || (load_ret < ESP_GMF_IO_OK)) {
ESP_LOGE(TAG, "Invalid in load size %d, ret %d", in_load->valid_size, load_ret);
out_len = ESP_GMF_JOB_ERR_FAIL;
goto __eq_release;
}
if (in_port->is_shared == 1) {
out_load = in_load;
}
load_ret = esp_gmf_port_acquire_out(out_port, &out_load, samples_num ? bytes : in_load->buf_length, ESP_GMF_MAX_DELAY);
ESP_GMF_PORT_ACQUIRE_OUT_CHECK(TAG, load_ret, out_len, { goto __eq_release;});
if (samples_num > 0) {
esp_gmf_oal_mutex_lock(((esp_gmf_audio_element_t *)self)->lock);
esp_ae_err_t ret = esp_ae_eq_process(eq->eq_hd, samples_num, in_load->buf, out_load->buf);
esp_gmf_oal_mutex_unlock(((esp_gmf_audio_element_t *)self)->lock);
ESP_GMF_RET_ON_ERROR(TAG, ret, {out_len = ESP_GMF_JOB_ERR_FAIL; goto __eq_release;}, "Equalize process error %d", ret);
}
ESP_LOGV(TAG, "Samples: %d, IN-PLD: %p-%p-%d-%d-%d, OUT-PLD: %p-%p-%d-%d-%d",
samples_num, in_load, in_load->buf, in_load->valid_size, in_load->buf_length, in_load->is_done,
out_load, out_load->buf, out_load->valid_size, out_load->buf_length, out_load->is_done);
out_load->valid_size = samples_num * eq->bytes_per_sample;
out_load->is_done = in_load->is_done;
out_load->pts = in_load->pts;
if (out_load->valid_size > 0) {
esp_gmf_audio_el_update_file_pos((esp_gmf_element_handle_t)self, out_load->valid_size);
}
if (in_load->is_done) {
out_len = ESP_GMF_JOB_ERR_DONE;
ESP_LOGD(TAG, "Equalize done, out len: %d", out_load->valid_size);
}
__eq_release:
// Release in and out port
if (out_load != NULL) {
load_ret = esp_gmf_port_release_out(out_port, out_load, ESP_GMF_MAX_DELAY);
if ((load_ret < ESP_GMF_IO_OK) && (load_ret != ESP_GMF_IO_ABORT)) {
ESP_LOGE(TAG, "OUT port release error, ret:%d", load_ret);
out_len = ESP_GMF_JOB_ERR_FAIL;
}
}
if (in_load != NULL) {
load_ret = esp_gmf_port_release_in(in_port, in_load, ESP_GMF_MAX_DELAY);
if ((load_ret < ESP_GMF_IO_OK) && (load_ret != ESP_GMF_IO_ABORT)) {
ESP_LOGE(TAG, "IN port release error, ret:%d", load_ret);
out_len = ESP_GMF_JOB_ERR_FAIL;
}
}
return out_len;
}
static esp_gmf_err_t eq_received_event_handler(esp_gmf_event_pkt_t *evt, void *ctx)
{
ESP_GMF_NULL_CHECK(TAG, ctx, {return ESP_GMF_ERR_INVALID_ARG;});
ESP_GMF_NULL_CHECK(TAG, evt, {return ESP_GMF_ERR_INVALID_ARG;});
if ((evt->type != ESP_GMF_EVT_TYPE_REPORT_INFO)
|| (evt->sub != ESP_GMF_INFO_SOUND)
|| (evt->payload == NULL)) {
return ESP_GMF_ERR_OK;
}
esp_gmf_element_handle_t self = (esp_gmf_element_handle_t)ctx;
esp_gmf_element_handle_t el = evt->from;
esp_gmf_event_state_t state = ESP_GMF_EVENT_STATE_NONE;
esp_gmf_element_get_state(self, &state);
esp_gmf_info_sound_t *info = (esp_gmf_info_sound_t *)evt->payload;
esp_ae_eq_cfg_t *config = (esp_ae_eq_cfg_t *)OBJ_GET_CFG(self);
ESP_GMF_NULL_CHECK(TAG, config, return ESP_GMF_ERR_FAIL);
esp_gmf_eq_t *eq = (esp_gmf_eq_t *)self;
eq->need_reopen = (config->sample_rate != info->sample_rates) || (info->channels != config->channel) || (config->bits_per_sample != info->bits);
config->sample_rate = info->sample_rates;
config->channel = info->channels;
config->bits_per_sample = info->bits;
ESP_LOGD(TAG, "RECV element info, from: %s-%p, next: %p, self: %s-%p, type: %x, state: %s, rate: %d, ch: %d, bits: %d",
OBJ_GET_TAG(el), el, esp_gmf_node_for_next((esp_gmf_node_t *)el), OBJ_GET_TAG(self), self, evt->type,
esp_gmf_event_get_state_str(state), info->sample_rates, info->channels, info->bits);
if (state == ESP_GMF_EVENT_STATE_NONE) {
esp_gmf_element_set_state(self, ESP_GMF_EVENT_STATE_INITIALIZED);
}
return ESP_GMF_ERR_OK;
}
static esp_gmf_err_t esp_gmf_eq_destroy(esp_gmf_element_handle_t self)
{
esp_gmf_eq_t *eq = (esp_gmf_eq_t *)self;
ESP_LOGD(TAG, "Destroyed, %p", self);
free_esp_ae_eq_cfg(OBJ_GET_CFG(self));
if(eq->is_filter_enabled){
esp_gmf_oal_free(eq->is_filter_enabled);
}
esp_gmf_audio_el_deinit(self);
esp_gmf_oal_free(eq);
return ESP_GMF_ERR_OK;
}
static esp_gmf_err_t _load_eq_caps_func(esp_gmf_element_handle_t handle)
{
esp_gmf_cap_t *caps = NULL;
esp_gmf_cap_t dec_caps = {0};
dec_caps.cap_eightcc = ESP_GMF_CAPS_AUDIO_EQUALIZER;
dec_caps.attr_fun = NULL;
int ret = esp_gmf_cap_append(&caps, &dec_caps);
ESP_GMF_RET_ON_NOT_OK(TAG, ret, {return ret;}, "Failed to create capability");
esp_gmf_element_t *el = (esp_gmf_element_t *)handle;
el->caps = caps;
return ESP_GMF_ERR_OK;
}
static esp_gmf_err_t _load_eq_methods_func(esp_gmf_element_handle_t handle)
{
esp_gmf_method_t *method = NULL;
esp_gmf_args_desc_t *set_args = NULL;
esp_gmf_args_desc_t *get_args = NULL;
esp_gmf_args_desc_t *pointer_args = NULL;
esp_gmf_err_t ret = esp_gmf_args_desc_append(&pointer_args, AMETHOD_ARG(EQ, SET_PARA, PARA_FT), ESP_GMF_ARGS_TYPE_UINT32,
sizeof(uint32_t), offsetof(esp_ae_eq_filter_para_t, filter_type));
ESP_GMF_RET_ON_NOT_OK(TAG, ret, {return ret;}, "Failed to append FILTER argument");
ret = esp_gmf_args_desc_append(&pointer_args, AMETHOD_ARG(EQ, SET_PARA, PARA_FC), ESP_GMF_ARGS_TYPE_UINT32,
sizeof(uint32_t), offsetof(esp_ae_eq_filter_para_t, fc));
ESP_GMF_RET_ON_NOT_OK(TAG, ret, {return ret;}, "Failed to append FC argument");
ret = esp_gmf_args_desc_append(&pointer_args, AMETHOD_ARG(EQ, SET_PARA, PARA_Q), ESP_GMF_ARGS_TYPE_FLOAT,
sizeof(float), offsetof(esp_ae_eq_filter_para_t, q));
ESP_GMF_RET_ON_NOT_OK(TAG, ret, {return ret;}, "Failed to append Q argument");
ret = esp_gmf_args_desc_append(&pointer_args, AMETHOD_ARG(EQ, SET_PARA, PARA_GAIN), ESP_GMF_ARGS_TYPE_FLOAT,
sizeof(float), offsetof(esp_ae_eq_filter_para_t, gain));
ESP_GMF_RET_ON_NOT_OK(TAG, ret, {return ret;}, "Failed to append GAIN argument");
ret = esp_gmf_args_desc_append(&set_args, AMETHOD_ARG(EQ, SET_PARA, IDX), ESP_GMF_ARGS_TYPE_UINT8, sizeof(uint8_t), 0);
ESP_GMF_RET_ON_NOT_OK(TAG, ret, {return ret;}, "Failed to append INDEX argument");
ret = esp_gmf_args_desc_append_array(&set_args, AMETHOD_ARG(EQ, SET_PARA, PARA), pointer_args,
sizeof(esp_ae_eq_filter_para_t), sizeof(uint8_t));
ESP_GMF_RET_ON_NOT_OK(TAG, ret, {return ret;}, "Failed to append PARA argument");
ret = esp_gmf_method_append(&method, AMETHOD(EQ, SET_PARA), __eq_set_para, set_args);
ESP_GMF_RET_ON_ERROR(TAG, ret, {return ret;}, "Failed to register %s method", AMETHOD(EQ, SET_PARA));
ret = esp_gmf_args_desc_copy(set_args, &get_args);
ESP_GMF_RET_ON_NOT_OK(TAG, ret, {return ret;}, "Failed to copy PARA argument");
ret = esp_gmf_method_append(&method, AMETHOD(EQ, GET_PARA), __eq_get_para, get_args);
ESP_GMF_RET_ON_ERROR(TAG, ret, {return ret;}, "Failed to register %s method", AMETHOD(EQ, GET_PARA));
set_args = NULL;
get_args = NULL;
ret = esp_gmf_args_desc_append(&set_args, AMETHOD_ARG(EQ, ENABLE_FILTER, IDX), ESP_GMF_ARGS_TYPE_UINT8, sizeof(uint8_t), 0);
ESP_GMF_RET_ON_NOT_OK(TAG, ret, {return ret;}, "Failed to append INDEX argument");
ret = esp_gmf_args_desc_append(&set_args, AMETHOD_ARG(EQ, ENABLE_FILTER, ENABLE), ESP_GMF_ARGS_TYPE_UINT8,
sizeof(uint8_t), sizeof(uint8_t));
ESP_GMF_RET_ON_NOT_OK(TAG, ret, {return ret;}, "Failed to append PARA argument");
ret = esp_gmf_method_append(&method, AMETHOD(EQ, ENABLE_FILTER), __eq_enable_filter, set_args);
ESP_GMF_RET_ON_ERROR(TAG, ret, {return ret;}, "Failed to register %s method", AMETHOD(EQ, ENABLE_FILTER));
esp_gmf_element_t *el = (esp_gmf_element_t *)handle;
el->method = method;
return ESP_GMF_ERR_OK;
}
esp_gmf_err_t esp_gmf_eq_set_para(esp_gmf_element_handle_t handle, uint8_t idx, esp_ae_eq_filter_para_t *para)
{
ESP_GMF_NULL_CHECK(TAG, handle, { return ESP_GMF_ERR_INVALID_ARG;});
ESP_GMF_NULL_CHECK(TAG, para, { return ESP_GMF_ERR_INVALID_ARG;});
esp_gmf_eq_t *eq = (esp_gmf_eq_t *)handle;
if (eq->eq_hd) {
esp_gmf_oal_mutex_lock(((esp_gmf_audio_element_t *)handle)->lock);
esp_ae_err_t ret = esp_ae_eq_set_filter_para(eq->eq_hd, idx, para);
esp_gmf_oal_mutex_unlock(((esp_gmf_audio_element_t *)handle)->lock);
ESP_GMF_RET_ON_ERROR(TAG, ret, return ESP_GMF_JOB_ERR_FAIL;, "Equalize set error %d", ret);
}
esp_ae_eq_cfg_t *cfg = (esp_ae_eq_cfg_t *)OBJ_GET_CFG(handle);
ESP_GMF_NULL_CHECK(TAG, cfg, return ESP_GMF_ERR_FAIL);
if (cfg->para) {
if (idx >= cfg->filter_num) {
ESP_LOGE(TAG, "Invalid idx %d", idx);
return ESP_GMF_ERR_FAIL;
}
memcpy(&cfg->para[idx], para, sizeof(esp_ae_eq_filter_para_t));
return ESP_GMF_ERR_OK;
}
ESP_LOGE(TAG, "Failed to set EQ para, no para allocated");
return ESP_GMF_ERR_FAIL;
}
esp_gmf_err_t esp_gmf_eq_get_para(esp_gmf_element_handle_t handle, uint8_t idx, esp_ae_eq_filter_para_t *para)
{
ESP_GMF_NULL_CHECK(TAG, handle, { return ESP_GMF_ERR_INVALID_ARG;});
esp_gmf_eq_t *eq = (esp_gmf_eq_t *)handle;
if (eq->eq_hd) {
esp_ae_err_t ret = esp_ae_eq_get_filter_para(eq->eq_hd, idx, para);
ESP_GMF_RET_ON_ERROR(TAG, ret, return ESP_GMF_JOB_ERR_FAIL;, "Equalize set error %d", ret);
} else {
esp_ae_eq_cfg_t *cfg = (esp_ae_eq_cfg_t *)OBJ_GET_CFG(handle);
ESP_GMF_NULL_CHECK(TAG, cfg, return ESP_GMF_ERR_FAIL);
if (cfg->para) {
if (idx >= cfg->filter_num) {
ESP_LOGE(TAG, "Invalid idx %d", idx);
return ESP_GMF_ERR_FAIL;
}
memcpy(para, &cfg->para[idx], sizeof(esp_ae_eq_filter_para_t));
}
}
return ESP_GMF_ERR_OK;
}
esp_gmf_err_t esp_gmf_eq_enable_filter(esp_gmf_element_handle_t handle, uint8_t idx, bool is_enable)
{
ESP_GMF_NULL_CHECK(TAG, handle, { return ESP_GMF_ERR_INVALID_ARG;});
esp_gmf_eq_t *eq = (esp_gmf_eq_t *)handle;
esp_ae_eq_cfg_t *cfg = (esp_ae_eq_cfg_t *)OBJ_GET_CFG(handle);
ESP_GMF_NULL_CHECK(TAG, cfg, return ESP_GMF_ERR_FAIL);
if (idx >= cfg->filter_num) {
ESP_LOGE(TAG, "Filter index %d overlimit %d hd:%p", idx, cfg->filter_num, eq);
return ESP_GMF_ERR_INVALID_ARG;
}
if (eq->eq_hd) {
esp_gmf_oal_mutex_lock(((esp_gmf_audio_element_t *)handle)->lock);
esp_ae_err_t ret = 0;
if (is_enable) {
ret = esp_ae_eq_enable_filter(eq->eq_hd, idx);
} else {
ret = esp_ae_eq_disable_filter(eq->eq_hd, idx);
}
esp_gmf_oal_mutex_unlock(((esp_gmf_audio_element_t *)handle)->lock);
ESP_GMF_RET_ON_ERROR(TAG, ret, return ESP_GMF_JOB_ERR_FAIL;, "Equalize set error %d", ret);
}
eq->is_filter_enabled[idx] = is_enable;
return ESP_GMF_ERR_OK;
}
esp_gmf_err_t esp_gmf_eq_init(esp_ae_eq_cfg_t *config, esp_gmf_element_handle_t *handle)
{
ESP_GMF_NULL_CHECK(TAG, handle, {return ESP_GMF_ERR_INVALID_ARG;});
*handle = NULL;
esp_gmf_err_t ret = ESP_GMF_ERR_OK;
esp_gmf_eq_t *eq = esp_gmf_oal_calloc(1, sizeof(esp_gmf_eq_t));
ESP_GMF_MEM_VERIFY(TAG, eq, {return ESP_GMF_ERR_MEMORY_LACK;}, "eq", sizeof(esp_gmf_eq_t));
esp_gmf_obj_t *obj = (esp_gmf_obj_t *)eq;
obj->new_obj = esp_gmf_eq_new;
obj->del_obj = esp_gmf_eq_destroy;
esp_ae_eq_cfg_t *cfg = NULL;
if (config) {
if (config->para == NULL) {
config->para = (esp_ae_eq_filter_para_t *)esp_gmf_default_eq_paras;
config->filter_num = sizeof(esp_gmf_default_eq_paras) / sizeof(esp_ae_eq_filter_para_t);
}
dupl_esp_ae_eq_cfg(config, &cfg);
} else {
esp_ae_eq_cfg_t dcfg = DEFAULT_ESP_GMF_EQ_CONFIG();
dcfg.para = (esp_ae_eq_filter_para_t *)esp_gmf_default_eq_paras;
dcfg.filter_num = sizeof(esp_gmf_default_eq_paras) / sizeof(esp_ae_eq_filter_para_t);
dupl_esp_ae_eq_cfg(&dcfg, &cfg);
}
ESP_GMF_CHECK(TAG, cfg, {ret = ESP_GMF_ERR_MEMORY_LACK; goto EQ_INI_FAIL;}, "Failed to allocate eq configuration");
esp_gmf_obj_set_config(obj, cfg, sizeof(esp_ae_eq_cfg_t));
eq->is_filter_enabled = esp_gmf_oal_calloc(cfg->filter_num, sizeof(bool));
ESP_GMF_MEM_VERIFY(TAG, eq->is_filter_enabled, ret = ESP_GMF_ERR_MEMORY_LACK; goto EQ_INI_FAIL, "Rellocation failed", cfg->filter_num);
ret = esp_gmf_obj_set_tag(obj, "aud_eq");
ESP_GMF_RET_ON_NOT_OK(TAG, ret, goto EQ_INI_FAIL, "Failed to set obj tag");
esp_gmf_element_cfg_t el_cfg = {0};
ESP_GMF_ELEMENT_IN_PORT_ATTR_SET(el_cfg.in_attr, ESP_GMF_EL_PORT_CAP_SINGLE, 0, 0,
ESP_GMF_PORT_TYPE_BLOCK | ESP_GMF_PORT_TYPE_BYTE, ESP_GMF_ELEMENT_PORT_DATA_SIZE_DEFAULT);
ESP_GMF_ELEMENT_IN_PORT_ATTR_SET(el_cfg.out_attr, ESP_GMF_EL_PORT_CAP_SINGLE, 0, 0,
ESP_GMF_PORT_TYPE_BLOCK | ESP_GMF_PORT_TYPE_BYTE, ESP_GMF_ELEMENT_PORT_DATA_SIZE_DEFAULT);
el_cfg.dependency = true;
ret = esp_gmf_audio_el_init(eq, &el_cfg);
ESP_GMF_RET_ON_NOT_OK(TAG, ret, goto EQ_INI_FAIL, "Failed to initialize eq element");
*handle = obj;
ESP_LOGD(TAG, "Initialization, %s-%p", OBJ_GET_TAG(obj), obj);
ESP_GMF_ELEMENT_GET(eq)->ops.open = esp_gmf_eq_open;
ESP_GMF_ELEMENT_GET(eq)->ops.process = esp_gmf_eq_process;
ESP_GMF_ELEMENT_GET(eq)->ops.close = esp_gmf_eq_close;
ESP_GMF_ELEMENT_GET(eq)->ops.event_receiver = eq_received_event_handler;
ESP_GMF_ELEMENT_GET(eq)->ops.load_caps = _load_eq_caps_func;
ESP_GMF_ELEMENT_GET(eq)->ops.load_methods = _load_eq_methods_func;
return ESP_GMF_ERR_OK;
EQ_INI_FAIL:
esp_gmf_eq_destroy(obj);
return ret;
}
@@ -0,0 +1,329 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO., LTD
* SPDX-License-Identifier: LicenseRef-Espressif-Modified-MIT
*
* See LICENSE file for details.
*/
#include <string.h>
#include "esp_log.h"
#include "esp_gmf_oal_mem.h"
#include "esp_gmf_oal_mutex.h"
#include "esp_gmf_node.h"
#include "esp_gmf_fade.h"
#include "gmf_audio_common.h"
#include "esp_gmf_audio_methods_def.h"
#include "esp_gmf_cap.h"
#include "esp_gmf_caps_def.h"
#include "esp_gmf_audio_element.h"
/**
* @brief Audio fade context in GMF
*/
typedef struct {
esp_gmf_audio_element_t parent; /*!< The GMF fade handle */
esp_ae_fade_handle_t fade_hd; /*!< The audio effects fade handle */
uint8_t bytes_per_sample; /*!< Bytes number of per sampling point */
bool need_reopen : 1; /*!< Whether need to reopen.
True: Execute the close function first, then execute the open function
False: Do nothing */
} esp_gmf_fade_t;
static const char *TAG = "ESP_GMF_FADE";
static esp_gmf_err_t __fade_set_mode(esp_gmf_element_handle_t handle, esp_gmf_args_desc_t *arg_desc,
uint8_t *buf, int buf_len)
{
ESP_GMF_NULL_CHECK(TAG, arg_desc, {return ESP_GMF_ERR_INVALID_ARG;});
ESP_GMF_NULL_CHECK(TAG, buf, {return ESP_GMF_ERR_INVALID_ARG;});
return esp_gmf_fade_set_mode(handle, *((esp_ae_fade_mode_t *)buf));
}
static esp_gmf_err_t __fade_get_mode(esp_gmf_element_handle_t handle, esp_gmf_args_desc_t *arg_desc,
uint8_t *buf, int buf_len)
{
ESP_GMF_NULL_CHECK(TAG, arg_desc, {return ESP_GMF_ERR_INVALID_ARG;});
return esp_gmf_fade_get_mode(handle, (esp_ae_fade_mode_t *)buf);
}
static esp_gmf_err_t __fade_reset(esp_gmf_element_handle_t handle, esp_gmf_args_desc_t *arg_desc,
uint8_t *buf, int buf_len)
{
return esp_gmf_fade_reset_weight(handle);
}
static esp_gmf_err_t esp_gmf_fade_new(void *cfg, esp_gmf_obj_handle_t *handle)
{
return esp_gmf_fade_init(cfg, (esp_gmf_element_handle_t *)handle);
}
static esp_gmf_job_err_t esp_gmf_fade_open(esp_gmf_element_handle_t self, void *para)
{
esp_gmf_fade_t *fade = (esp_gmf_fade_t *)self;
esp_ae_fade_cfg_t *fade_info = (esp_ae_fade_cfg_t *)OBJ_GET_CFG(self);
ESP_GMF_NULL_CHECK(TAG, fade_info, {return ESP_GMF_JOB_ERR_FAIL;});
fade->bytes_per_sample = (fade_info->bits_per_sample >> 3) * fade_info->channel;
esp_ae_fade_open(fade_info, &fade->fade_hd);
ESP_GMF_CHECK(TAG, fade->fade_hd, {return ESP_GMF_JOB_ERR_FAIL;}, "Failed to create fade handle");
GMF_AUDIO_UPDATE_SND_INFO(self, fade_info->sample_rate, fade_info->bits_per_sample, fade_info->channel);
fade->need_reopen = false;
ESP_LOGD(TAG, "Open, %p", self);
return ESP_GMF_ERR_OK;
}
static esp_gmf_job_err_t esp_gmf_fade_close(esp_gmf_element_handle_t self, void *para)
{
esp_gmf_fade_t *fade = (esp_gmf_fade_t *)self;
ESP_LOGD(TAG, "Closed, %p", self);
if (fade->fade_hd != NULL) {
esp_ae_fade_close(fade->fade_hd);
fade->fade_hd = NULL;
}
return ESP_GMF_ERR_OK;
}
static esp_gmf_job_err_t esp_gmf_fade_process(esp_gmf_element_handle_t self, void *para)
{
esp_gmf_fade_t *fade = (esp_gmf_fade_t *)self;
esp_gmf_job_err_t out_len = ESP_GMF_JOB_ERR_OK;
if (fade->need_reopen) {
esp_gmf_fade_close(self, NULL);
out_len = esp_gmf_fade_open(self, NULL);
if (out_len != ESP_GMF_JOB_ERR_OK) {
ESP_LOGE(TAG, "Fade reopen failed");
return out_len;
}
}
esp_gmf_port_handle_t in_port = ESP_GMF_ELEMENT_GET(self)->in;
esp_gmf_port_handle_t out_port = ESP_GMF_ELEMENT_GET(self)->out;
esp_gmf_payload_t *in_load = NULL;
esp_gmf_payload_t *out_load = NULL;
int samples_num = ESP_GMF_ELEMENT_GET(fade)->in_attr.data_size / (fade->bytes_per_sample);
int bytes = samples_num * fade->bytes_per_sample;
esp_gmf_err_io_t load_ret = esp_gmf_port_acquire_in(in_port, &in_load, bytes, ESP_GMF_MAX_DELAY);
samples_num = in_load->valid_size / (fade->bytes_per_sample);
bytes = samples_num * fade->bytes_per_sample;
if ((bytes != in_load->valid_size) || (load_ret < ESP_GMF_IO_OK)) {
ESP_LOGE(TAG, "Invalid in load size %d, ret %d", in_load->valid_size, load_ret);
out_len = ESP_GMF_JOB_ERR_FAIL;
goto __fade_release;
}
if (in_port->is_shared == 1) {
out_load = in_load;
}
load_ret = esp_gmf_port_acquire_out(out_port, &out_load, samples_num ? bytes : in_load->buf_length, ESP_GMF_MAX_DELAY);
ESP_GMF_PORT_ACQUIRE_OUT_CHECK(TAG, load_ret, out_len, { goto __fade_release;});
if (samples_num > 0) {
esp_gmf_oal_mutex_lock(((esp_gmf_audio_element_t *)self)->lock);
esp_ae_err_t ret = esp_ae_fade_process(fade->fade_hd, samples_num, in_load->buf, out_load->buf);
esp_gmf_oal_mutex_unlock(((esp_gmf_audio_element_t *)self)->lock);
ESP_GMF_RET_ON_ERROR(TAG, ret, {out_len = ESP_GMF_JOB_ERR_FAIL; goto __fade_release;}, "Fade process error %d", ret);
}
ESP_LOGV(TAG, "Samples: %d, IN-PLD: %p-%p-%d-%d-%d, OUT-PLD: %p-%p-%d-%d-%d",
samples_num, in_load, in_load->buf, in_load->valid_size, in_load->buf_length, in_load->is_done,
out_load, out_load->buf, out_load->valid_size, out_load->buf_length, out_load->is_done);
out_load->valid_size = samples_num * fade->bytes_per_sample;
out_load->is_done = in_load->is_done;
out_load->pts = in_load->pts;
if (out_load->valid_size > 0) {
esp_gmf_audio_el_update_file_pos((esp_gmf_element_handle_t)self, out_load->valid_size);
}
if (in_load->is_done) {
out_len = ESP_GMF_JOB_ERR_DONE;
ESP_LOGD(TAG, "Fade done, out len: %d", out_load->valid_size);
}
__fade_release:
// Release in and out port
if (out_load != NULL) {
load_ret = esp_gmf_port_release_out(out_port, out_load, ESP_GMF_MAX_DELAY);
if ((load_ret < ESP_GMF_IO_OK) && (load_ret != ESP_GMF_IO_ABORT)) {
ESP_LOGE(TAG, "OUT port release error, ret:%d", load_ret);
out_len = ESP_GMF_JOB_ERR_FAIL;
}
}
if (in_load != NULL) {
load_ret = esp_gmf_port_release_in(in_port, in_load, ESP_GMF_MAX_DELAY);
if ((load_ret < ESP_GMF_IO_OK) && (load_ret != ESP_GMF_IO_ABORT)) {
ESP_LOGE(TAG, "IN port release error, ret:%d", load_ret);
out_len = ESP_GMF_JOB_ERR_FAIL;
}
}
return out_len;
}
static esp_gmf_err_t fade_received_event_handler(esp_gmf_event_pkt_t *evt, void *ctx)
{
ESP_GMF_NULL_CHECK(TAG, ctx, {return ESP_GMF_ERR_INVALID_ARG;});
ESP_GMF_NULL_CHECK(TAG, evt, {return ESP_GMF_ERR_INVALID_ARG;});
if ((evt->type != ESP_GMF_EVT_TYPE_REPORT_INFO)
|| (evt->sub != ESP_GMF_INFO_SOUND)
|| (evt->payload == NULL)) {
return ESP_GMF_ERR_OK;
}
esp_gmf_element_handle_t self = (esp_gmf_element_handle_t)ctx;
esp_gmf_element_handle_t el = evt->from;
esp_gmf_event_state_t state = ESP_GMF_EVENT_STATE_NONE;
esp_gmf_element_get_state(self, &state);
esp_gmf_info_sound_t *info = (esp_gmf_info_sound_t *)evt->payload;
esp_ae_fade_cfg_t *config = (esp_ae_fade_cfg_t *)OBJ_GET_CFG(self);
ESP_GMF_NULL_CHECK(TAG, config, return ESP_GMF_ERR_FAIL);
esp_gmf_fade_t *fade = (esp_gmf_fade_t *)self;
fade->need_reopen = (config->sample_rate != info->sample_rates) || (info->channels != config->channel) || (config->bits_per_sample != info->bits);
config->sample_rate = info->sample_rates;
config->channel = info->channels;
config->bits_per_sample = info->bits;
ESP_LOGD(TAG, "RECV element info, from: %s-%p, next: %p, self: %s-%p, type: %x, state: %s, rate: %d, ch: %d, bits: %d",
OBJ_GET_TAG(el), el, esp_gmf_node_for_next((esp_gmf_node_t *)el), OBJ_GET_TAG(self), self, evt->type,
esp_gmf_event_get_state_str(state), info->sample_rates, info->channels, info->bits);
if (state == ESP_GMF_EVENT_STATE_NONE) {
esp_gmf_element_set_state(self, ESP_GMF_EVENT_STATE_INITIALIZED);
}
return ESP_GMF_ERR_OK;
}
static esp_gmf_err_t esp_gmf_fade_destroy(esp_gmf_element_handle_t self)
{
esp_gmf_fade_t *fade = (esp_gmf_fade_t *)self;
ESP_LOGD(TAG, "Destroyed, %p", self);
void *cfg = OBJ_GET_CFG(self);
if (cfg) {
esp_gmf_oal_free(cfg);
}
esp_gmf_audio_el_deinit(self);
esp_gmf_oal_free(fade);
return ESP_GMF_ERR_OK;
}
static esp_gmf_err_t _load_fade_caps_func(esp_gmf_element_handle_t handle)
{
esp_gmf_cap_t *caps = NULL;
esp_gmf_cap_t dec_caps = {0};
dec_caps.cap_eightcc = ESP_GMF_CAPS_AUDIO_FADE;
dec_caps.attr_fun = NULL;
int ret = esp_gmf_cap_append(&caps, &dec_caps);
ESP_GMF_RET_ON_NOT_OK(TAG, ret, {return ret;}, "Failed to create capability");
esp_gmf_element_t *el = (esp_gmf_element_t *)handle;
el->caps = caps;
return ESP_GMF_ERR_OK;
}
static esp_gmf_err_t _load_fade_methods_func(esp_gmf_element_handle_t handle)
{
esp_gmf_method_t *method = NULL;
esp_gmf_args_desc_t *set_args = NULL;
esp_gmf_args_desc_t *get_args = NULL;
esp_gmf_err_t ret = esp_gmf_args_desc_append(&set_args, AMETHOD_ARG(FADE, SET_MODE, MODE), ESP_GMF_ARGS_TYPE_INT32, sizeof(int32_t), 0);
ESP_GMF_RET_ON_NOT_OK(TAG, ret, {return ret;}, "Failed to append MODE argument");
ret = esp_gmf_method_append(&method, AMETHOD(FADE, SET_MODE), __fade_set_mode, set_args);
ESP_GMF_RET_ON_ERROR(TAG, ret, {return ret;}, "Failed to register %s method", AMETHOD(FADE, SET_MODE));
ret = esp_gmf_args_desc_copy(set_args, &get_args);
ESP_GMF_RET_ON_NOT_OK(TAG, ret, {return ret;}, "Failed to copy argument");
ret = esp_gmf_method_append(&method, AMETHOD(FADE, GET_MODE), __fade_get_mode, get_args);
ESP_GMF_RET_ON_ERROR(TAG, ret, {return ret;}, "Failed to register %s method", AMETHOD(FADE, GET_MODE));
ret = esp_gmf_method_append(&method, AMETHOD(FADE, RESET_WEIGHT), __fade_reset, NULL);
ESP_GMF_RET_ON_ERROR(TAG, ret, {return ret;}, "Failed to register %s method", AMETHOD(FADE, RESET_WEIGHT));
esp_gmf_element_t *el = (esp_gmf_element_t *)handle;
el->method = method;
return ESP_GMF_ERR_OK;
}
esp_gmf_err_t esp_gmf_fade_set_mode(esp_gmf_element_handle_t handle, esp_ae_fade_mode_t mode)
{
ESP_GMF_NULL_CHECK(TAG, handle, { return ESP_GMF_ERR_INVALID_ARG;});
esp_gmf_fade_t *fade = (esp_gmf_fade_t *)handle;
esp_ae_fade_cfg_t *cfg = (esp_ae_fade_cfg_t *)OBJ_GET_CFG(handle);
ESP_GMF_NULL_CHECK(TAG, cfg, return ESP_GMF_ERR_FAIL);
if (fade->fade_hd) {
esp_gmf_oal_mutex_lock(((esp_gmf_audio_element_t *)handle)->lock);
esp_ae_err_t ret = esp_ae_fade_set_mode(fade->fade_hd, mode);
esp_gmf_oal_mutex_unlock(((esp_gmf_audio_element_t *)handle)->lock);
if (ret != ESP_AE_ERR_OK) {
return ESP_GMF_JOB_ERR_FAIL;
}
}
cfg->mode = mode;
return ESP_GMF_JOB_ERR_OK;
}
esp_gmf_err_t esp_gmf_fade_get_mode(esp_gmf_element_handle_t handle, esp_ae_fade_mode_t *mode)
{
ESP_GMF_NULL_CHECK(TAG, handle, { return ESP_GMF_ERR_INVALID_ARG;});
ESP_GMF_NULL_CHECK(TAG, mode, { return ESP_GMF_ERR_INVALID_ARG;});
esp_ae_fade_cfg_t *cfg = (esp_ae_fade_cfg_t *)OBJ_GET_CFG(handle);
ESP_GMF_NULL_CHECK(TAG, cfg, return ESP_GMF_ERR_FAIL);
esp_gmf_fade_t *fade = (esp_gmf_fade_t *)handle;
if (fade->fade_hd) {
esp_ae_err_t ret = esp_ae_fade_get_mode(fade->fade_hd, mode);
if (ret != ESP_AE_ERR_OK) {
return ESP_GMF_JOB_ERR_FAIL;
}
} else {
*mode = cfg->mode;
}
return ESP_GMF_JOB_ERR_OK;
}
esp_gmf_err_t esp_gmf_fade_reset_weight(esp_gmf_element_handle_t handle)
{
ESP_GMF_NULL_CHECK(TAG, handle, { return ESP_GMF_ERR_INVALID_ARG;});
esp_ae_fade_cfg_t *cfg = (esp_ae_fade_cfg_t *)OBJ_GET_CFG(handle);
ESP_GMF_NULL_CHECK(TAG, cfg, return ESP_GMF_ERR_FAIL);
esp_gmf_fade_t *fade = (esp_gmf_fade_t *)handle;
if(fade->fade_hd) {
esp_gmf_oal_mutex_lock(((esp_gmf_audio_element_t *)handle)->lock);
esp_ae_err_t ret = esp_ae_fade_reset_weight(fade->fade_hd);
esp_gmf_oal_mutex_unlock(((esp_gmf_audio_element_t *)handle)->lock);
if (ret != ESP_AE_ERR_OK) {
return ESP_GMF_JOB_ERR_FAIL;
}
esp_ae_fade_get_mode(fade->fade_hd, &cfg->mode);
}
return ESP_GMF_JOB_ERR_OK;
}
esp_gmf_err_t esp_gmf_fade_init(esp_ae_fade_cfg_t *config, esp_gmf_element_handle_t *handle)
{
ESP_GMF_NULL_CHECK(TAG, handle, {return ESP_GMF_ERR_INVALID_ARG;});
*handle = NULL;
esp_gmf_err_t ret = ESP_GMF_ERR_OK;
esp_gmf_fade_t *fade = esp_gmf_oal_calloc(1, sizeof(esp_gmf_fade_t));
ESP_GMF_MEM_VERIFY(TAG, fade, {return ESP_GMF_ERR_MEMORY_LACK;}, "fade", sizeof(esp_gmf_fade_t));
esp_gmf_obj_t *obj = (esp_gmf_obj_t *)fade;
obj->new_obj = esp_gmf_fade_new;
obj->del_obj = esp_gmf_fade_destroy;
esp_ae_fade_cfg_t *cfg = esp_gmf_oal_calloc(1, sizeof(esp_ae_fade_cfg_t));
ESP_GMF_MEM_VERIFY(TAG, cfg, {ret = ESP_GMF_ERR_MEMORY_LACK; goto FADE_INIT_FAIL;}, "fade configuration", sizeof(esp_ae_fade_cfg_t));
esp_gmf_obj_set_config(obj, cfg, sizeof(esp_ae_fade_cfg_t));
if (config) {
memcpy(cfg, config, sizeof(esp_ae_fade_cfg_t));
} else {
esp_ae_fade_cfg_t dcfg = DEFAULT_ESP_GMF_FADE_CONFIG();
memcpy(cfg, &dcfg, sizeof(esp_ae_fade_cfg_t));
}
ret = esp_gmf_obj_set_tag(obj, "aud_fade");
ESP_GMF_RET_ON_NOT_OK(TAG, ret, goto FADE_INIT_FAIL, "Failed to set obj tag");
esp_gmf_element_cfg_t el_cfg = {0};
ESP_GMF_ELEMENT_IN_PORT_ATTR_SET(el_cfg.in_attr, ESP_GMF_EL_PORT_CAP_SINGLE, 0, 0,
ESP_GMF_PORT_TYPE_BLOCK | ESP_GMF_PORT_TYPE_BYTE, ESP_GMF_ELEMENT_PORT_DATA_SIZE_DEFAULT);
ESP_GMF_ELEMENT_IN_PORT_ATTR_SET(el_cfg.out_attr, ESP_GMF_EL_PORT_CAP_SINGLE, 0, 0,
ESP_GMF_PORT_TYPE_BLOCK | ESP_GMF_PORT_TYPE_BYTE, ESP_GMF_ELEMENT_PORT_DATA_SIZE_DEFAULT);
el_cfg.dependency = true;
ret = esp_gmf_audio_el_init(fade, &el_cfg);
ESP_GMF_RET_ON_NOT_OK(TAG, ret, goto FADE_INIT_FAIL, "Failed to initialize fade element");
ESP_GMF_ELEMENT_GET(fade)->ops.open = esp_gmf_fade_open;
ESP_GMF_ELEMENT_GET(fade)->ops.process = esp_gmf_fade_process;
ESP_GMF_ELEMENT_GET(fade)->ops.close = esp_gmf_fade_close;
ESP_GMF_ELEMENT_GET(fade)->ops.event_receiver = fade_received_event_handler;
ESP_GMF_ELEMENT_GET(fade)->ops.load_caps = _load_fade_caps_func;
ESP_GMF_ELEMENT_GET(fade)->ops.load_methods = _load_fade_methods_func;
*handle = obj;
ESP_LOGD(TAG, "Initialization, %s-%p", OBJ_GET_TAG(obj), obj);
return ESP_GMF_ERR_OK;
FADE_INIT_FAIL:
esp_gmf_fade_destroy(obj);
return ret;
}
@@ -0,0 +1,270 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO., LTD
* SPDX-License-Identifier: LicenseRef-Espressif-Modified-MIT
*
* See LICENSE file for details.
*/
#include <string.h>
#include "esp_log.h"
#include "esp_gmf_oal_mem.h"
#include "esp_gmf_oal_mutex.h"
#include "esp_gmf_node.h"
#include "esp_gmf_interleave.h"
#include "esp_ae_data_weaver.h"
#include "esp_heap_caps.h"
#include "gmf_audio_common.h"
#include "esp_gmf_cap.h"
#include "esp_gmf_caps_def.h"
#include "esp_gmf_audio_element.h"
#define ESP_GMF_PROCESS_SAMPLE (256)
/**
* @brief Audio interleave context in GMF
*/
typedef struct {
esp_gmf_audio_element_t parent; /*!< The GMF interleave handle */
uint8_t bytes_per_sample; /*!< Bytes number of per sampling point */
esp_gmf_payload_t **in_load; /*!< The array of input payload */
esp_gmf_payload_t *out_load; /*!< The output payload */
uint8_t **in_arr; /*!< The array of input buffer pointer */
uint8_t src_num; /*!< The number of input stream source */
uint8_t bits_per_sample; /*!< Bits number of per sampling point */
bool need_reopen : 1; /*!< Whether need to reopen.
True: Execute the close function first, then execute the open function
False: Do nothing */
} esp_gmf_interleave_t;
static const char *TAG = "ESP_GMF_INTLV";
static inline esp_gmf_err_t dupl_esp_ae_interleave_cfg(esp_gmf_interleave_cfg *config, esp_gmf_interleave_cfg **new_config)
{
*new_config = esp_gmf_oal_calloc(1, sizeof(*config));
ESP_GMF_MEM_VERIFY(TAG, *new_config, {return ESP_GMF_ERR_MEMORY_LACK;}, "interleave configuration", sizeof(*config));
memcpy(*new_config, config, sizeof(*config));
return ESP_GMF_JOB_ERR_OK;
}
static inline void free_esp_ae_interleave_cfg(esp_gmf_interleave_cfg *config)
{
if (config) {
esp_gmf_oal_free(config);
}
}
static esp_gmf_err_t esp_gmf_interleave_new(void *cfg, esp_gmf_obj_handle_t *handle)
{
return esp_gmf_interleave_init(cfg, (esp_gmf_element_handle_t *)handle);
}
static esp_gmf_job_err_t esp_gmf_interleave_open(esp_gmf_element_handle_t self, void *para)
{
esp_gmf_interleave_t *interleave = (esp_gmf_interleave_t *)self;
esp_gmf_interleave_cfg *interleave_info = (esp_gmf_interleave_cfg *)OBJ_GET_CFG(self);
ESP_GMF_NULL_CHECK(TAG, interleave_info, {return ESP_GMF_JOB_ERR_FAIL;})
interleave->bytes_per_sample = (interleave_info->bits_per_sample >> 3);
interleave->in_load = esp_gmf_oal_calloc(1, sizeof(esp_gmf_payload_t *) * interleave_info->src_num);
ESP_GMF_MEM_VERIFY(TAG, interleave->in_load, {return ESP_GMF_JOB_ERR_FAIL;},
"in load", sizeof(esp_gmf_payload_t *) * interleave_info->src_num);
interleave->in_arr = esp_gmf_oal_calloc(1, sizeof(int *) * interleave_info->src_num);
ESP_GMF_MEM_VERIFY(TAG, interleave->in_arr, {return ESP_GMF_JOB_ERR_FAIL;},
"in buffer array", sizeof(int *) * interleave_info->src_num);
GMF_AUDIO_UPDATE_SND_INFO(self, interleave_info->sample_rate, interleave_info->bits_per_sample, interleave_info->src_num);
interleave->src_num = interleave_info->src_num;
interleave->bits_per_sample = interleave_info->bits_per_sample;
interleave->need_reopen = false;
ESP_LOGD(TAG, "Open, %p", self);
return ESP_GMF_JOB_ERR_OK;
}
static esp_gmf_job_err_t esp_gmf_interleave_close(esp_gmf_element_handle_t self, void *para)
{
esp_gmf_interleave_t *interleave = (esp_gmf_interleave_t *)self;
ESP_LOGD(TAG, "Closed, %p", self);
if (interleave->in_arr != NULL) {
esp_gmf_oal_free(interleave->in_arr);
interleave->in_arr = NULL;
}
if (interleave->in_load != NULL) {
esp_gmf_oal_free(interleave->in_load);
interleave->in_load = NULL;
}
return ESP_GMF_ERR_OK;
}
static esp_gmf_job_err_t esp_gmf_interleave_process(esp_gmf_element_handle_t self, void *para)
{
esp_gmf_interleave_t *interleave = (esp_gmf_interleave_t *)self;
esp_gmf_job_err_t out_len = ESP_GMF_JOB_ERR_OK;
if (interleave->need_reopen) {
esp_gmf_interleave_close(self, NULL);
out_len = esp_gmf_interleave_open(self, NULL);
if (out_len != ESP_GMF_JOB_ERR_OK) {
ESP_LOGE(TAG, "Interleave reopen failed");
return out_len;
}
}
esp_gmf_port_handle_t in = ESP_GMF_ELEMENT_GET(self)->in;
esp_gmf_port_handle_t in_port = in;
esp_gmf_port_handle_t out_port = ESP_GMF_ELEMENT_GET(self)->out;
esp_gmf_interleave_cfg *interleave_info = (esp_gmf_interleave_cfg *)OBJ_GET_CFG(self);
ESP_GMF_NULL_CHECK(TAG, interleave_info, return ESP_GMF_JOB_ERR_FAIL);
int index = interleave_info->src_num;
int i = 0;
bool is_done = false;
esp_gmf_err_io_t load_ret = ESP_GMF_IO_OK;
memset(interleave->in_load, 0, sizeof(esp_gmf_payload_t *) * index);
interleave->out_load = NULL;
int samples_num = ESP_GMF_ELEMENT_GET(interleave)->in_attr.data_size / (interleave->bytes_per_sample);
int bytes = samples_num * interleave->bytes_per_sample;
while (in_port != NULL) {
load_ret = esp_gmf_port_acquire_in(in_port, &(interleave->in_load[i]), bytes, in_port->wait_ticks);
ESP_GMF_PORT_CHECK(TAG, load_ret, out_len, {out_len = ESP_GMF_JOB_ERR_FAIL; goto __intlv_release;}, "Failed to acquire in, idx:%d, ret: %d", i, load_ret);
interleave->in_arr[i] = interleave->in_load[i]->buf;
in_port = in_port->next;
// if one load is done means interleave need to done
is_done = (((is_done == 1) || (interleave->in_load[i]->is_done == 1)) ? true : false);
ESP_LOGV(TAG, "IN: idx: %d load: %p, buf: %p, valid size: %d, buf length: %d, done: %d",
i, interleave->in_load[i], interleave->in_load[i]->buf, interleave->in_load[i]->valid_size,
interleave->in_load[i]->buf_length, interleave->in_load[i]->is_done);
i++;
}
samples_num = interleave->in_load[0]->valid_size / (interleave->bytes_per_sample);
bytes = samples_num * interleave->bytes_per_sample;
load_ret = esp_gmf_port_acquire_out(out_port, &interleave->out_load,
samples_num ? bytes * interleave->src_num : interleave->in_load[0]->buf_length, ESP_GMF_MAX_DELAY);
ESP_GMF_PORT_ACQUIRE_OUT_CHECK(TAG, load_ret, out_len, { goto __intlv_release;});
if (samples_num > 0) {
esp_ae_err_t ret = esp_ae_intlv_process(interleave->src_num, interleave->bits_per_sample, samples_num,
(void **)interleave->in_arr, interleave->out_load->buf);
ESP_GMF_RET_ON_ERROR(TAG, ret, {out_len = ESP_GMF_JOB_ERR_FAIL; goto __intlv_release;}, "Interleave process error, ret: %d", ret);
}
ESP_LOGV(TAG, "OUT: load: %p, buf: %p, valid size: %d, buf length: %d",
interleave->out_load, interleave->out_load->buf, interleave->out_load->valid_size, interleave->out_load->buf_length);
interleave->out_load->valid_size = samples_num * interleave->bytes_per_sample * interleave->src_num;
if (interleave->out_load->valid_size > 0) {
esp_gmf_audio_el_update_file_pos((esp_gmf_element_handle_t)self, interleave->out_load->valid_size);
}
// Interleave finished
if (is_done == true) {
ESP_LOGD(TAG, "Interleave is done.");
interleave->out_load->is_done = true;
out_len = ESP_GMF_JOB_ERR_DONE;
}
__intlv_release:
if (interleave->out_load != NULL) {
load_ret = esp_gmf_port_release_out(out_port, interleave->out_load, ESP_GMF_MAX_DELAY);
if ((load_ret < ESP_GMF_IO_OK) && (load_ret != ESP_GMF_IO_ABORT)) {
ESP_LOGE(TAG, "OUT port release error, ret:%d", load_ret);
out_len = ESP_GMF_JOB_ERR_FAIL;
}
}
in_port = in;
i = 0;
while (in_port != NULL && interleave->in_load[i] != NULL) {
load_ret = esp_gmf_port_release_in(in_port, interleave->in_load[i], ESP_GMF_MAX_DELAY);
if ((load_ret < ESP_GMF_IO_OK) && (load_ret != ESP_GMF_IO_ABORT)) {
ESP_LOGE(TAG, "IN port release error, ret:%d", load_ret);
out_len = ESP_GMF_JOB_ERR_FAIL;
}
in_port = in_port->next;
i++;
}
return out_len;
}
static esp_gmf_err_t interleave_received_event_handler(esp_gmf_event_pkt_t *evt, void *ctx)
{
ESP_GMF_NULL_CHECK(TAG, ctx, {return ESP_GMF_ERR_INVALID_ARG;});
ESP_GMF_NULL_CHECK(TAG, evt, {return ESP_GMF_ERR_INVALID_ARG;});
if ((evt->type != ESP_GMF_EVT_TYPE_REPORT_INFO)
|| (evt->sub != ESP_GMF_INFO_SOUND)
|| (evt->payload == NULL)) {
return ESP_GMF_ERR_OK;
}
esp_gmf_element_handle_t self = (esp_gmf_element_handle_t)ctx;
esp_gmf_element_handle_t el = evt->from;
esp_gmf_event_state_t state = ESP_GMF_EVENT_STATE_NONE;
esp_gmf_element_get_state(self, &state);
esp_gmf_info_sound_t *info = (esp_gmf_info_sound_t *)evt->payload;
esp_gmf_interleave_cfg *config = (esp_gmf_interleave_cfg *)OBJ_GET_CFG(self);
ESP_GMF_NULL_CHECK(TAG, config, return ESP_GMF_ERR_FAIL);
esp_gmf_interleave_t *interleave = (esp_gmf_interleave_t *)self;
interleave->need_reopen = (config->sample_rate != info->sample_rates) || (config->bits_per_sample != info->bits);
config->sample_rate = info->sample_rates;
config->bits_per_sample = info->bits;
ESP_LOGD(TAG, "RECV element info, from: %s-%p, next: %p, self: %s-%p, type: %x, state: %s, rate: %d, ch: %d, bits: %d",
OBJ_GET_TAG(el), el, esp_gmf_node_for_next((esp_gmf_node_t *)el), OBJ_GET_TAG(self), self, evt->type,
esp_gmf_event_get_state_str(state), info->sample_rates, info->channels, info->bits);
if (state == ESP_GMF_EVENT_STATE_NONE) {
esp_gmf_element_set_state(self, ESP_GMF_EVENT_STATE_INITIALIZED);
}
return ESP_GMF_ERR_OK;
}
static esp_gmf_err_t esp_gmf_interleave_destroy(esp_gmf_element_handle_t self)
{
esp_gmf_interleave_t *interleave = (esp_gmf_interleave_t *)self;
ESP_LOGD(TAG, "Destroyed, %p", self);
free_esp_ae_interleave_cfg(OBJ_GET_CFG(self));
esp_gmf_audio_el_deinit(self);
esp_gmf_oal_free(interleave);
return ESP_GMF_ERR_OK;
}
static esp_gmf_err_t _load_interleave_caps_func(esp_gmf_element_handle_t handle)
{
esp_gmf_cap_t *caps = NULL;
esp_gmf_cap_t dec_caps = {0};
dec_caps.cap_eightcc = ESP_GMF_CAPS_AUDIO_INTERLEAVE;
dec_caps.attr_fun = NULL;
int ret = esp_gmf_cap_append(&caps, &dec_caps);
ESP_GMF_RET_ON_NOT_OK(TAG, ret, {return ret;}, "Failed to create capability");
esp_gmf_element_t *el = (esp_gmf_element_t *)handle;
el->caps = caps;
return ESP_GMF_ERR_OK;
}
esp_gmf_err_t esp_gmf_interleave_init(esp_gmf_interleave_cfg *config, esp_gmf_element_handle_t *handle)
{
ESP_GMF_NULL_CHECK(TAG, handle, {return ESP_GMF_ERR_INVALID_ARG;});
*handle = NULL;
esp_gmf_err_t ret = ESP_GMF_ERR_OK;
esp_gmf_interleave_t *interleave = esp_gmf_oal_calloc(1, sizeof(esp_gmf_interleave_t));
ESP_GMF_MEM_VERIFY(TAG, interleave, {return ESP_GMF_ERR_MEMORY_LACK;}, "interleave", sizeof(esp_gmf_interleave_t));
esp_gmf_obj_t *obj = (esp_gmf_obj_t *)interleave;
obj->new_obj = esp_gmf_interleave_new;
obj->del_obj = esp_gmf_interleave_destroy;
esp_gmf_interleave_cfg *cfg = esp_gmf_oal_calloc(1, sizeof(esp_gmf_interleave_cfg));
ESP_GMF_MEM_VERIFY(TAG, cfg, {ret = ESP_GMF_ERR_MEMORY_LACK; goto INTLV_INIT_FAIL;}, "interleave configuration", sizeof(esp_gmf_interleave_cfg));
esp_gmf_obj_set_config(obj, cfg, sizeof(esp_gmf_interleave_cfg));
if (config) {
memcpy(cfg, config, sizeof(esp_gmf_interleave_cfg));
} else {
esp_gmf_interleave_cfg dcfg = DEFAULT_ESP_GMF_INTERLEAVE_CONFIG();
memcpy(cfg, &dcfg, sizeof(esp_gmf_interleave_cfg));
}
ret = esp_gmf_obj_set_tag(obj, "aud_intlv");
ESP_GMF_RET_ON_NOT_OK(TAG, ret, goto INTLV_INIT_FAIL, "Failed to set obj tag");
esp_gmf_element_cfg_t el_cfg = {0};
ESP_GMF_ELEMENT_IN_PORT_ATTR_SET(el_cfg.in_attr, ESP_GMF_EL_PORT_CAP_MULTI, 0, 0,
ESP_GMF_PORT_TYPE_BLOCK | ESP_GMF_PORT_TYPE_BYTE, ESP_GMF_ELEMENT_PORT_DATA_SIZE_DEFAULT);
ESP_GMF_ELEMENT_IN_PORT_ATTR_SET(el_cfg.out_attr, ESP_GMF_EL_PORT_CAP_SINGLE, 0, 0,
ESP_GMF_PORT_TYPE_BLOCK | ESP_GMF_PORT_TYPE_BYTE, ESP_GMF_ELEMENT_PORT_DATA_SIZE_DEFAULT);
el_cfg.dependency = true;
ret = esp_gmf_audio_el_init(interleave, &el_cfg);
ESP_GMF_RET_ON_NOT_OK(TAG, ret, goto INTLV_INIT_FAIL, "Failed to initialize interleave element");
ESP_GMF_ELEMENT_GET(interleave)->ops.open = esp_gmf_interleave_open;
ESP_GMF_ELEMENT_GET(interleave)->ops.process = esp_gmf_interleave_process;
ESP_GMF_ELEMENT_GET(interleave)->ops.close = esp_gmf_interleave_close;
ESP_GMF_ELEMENT_GET(interleave)->ops.event_receiver = interleave_received_event_handler;
ESP_GMF_ELEMENT_GET(interleave)->ops.load_caps = _load_interleave_caps_func;
*handle = obj;
ESP_LOGD(TAG, "Initialization, %s-%p", OBJ_GET_TAG(obj), obj);
return ESP_GMF_ERR_OK;
INTLV_INIT_FAIL:
esp_gmf_interleave_destroy(obj);
return ret;
}
@@ -0,0 +1,437 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO., LTD
* SPDX-License-Identifier: LicenseRef-Espressif-Modified-MIT
*
* See LICENSE file for details.
*/
#include <string.h>
#include "esp_log.h"
#include "esp_gmf_oal_mem.h"
#include "esp_gmf_oal_mutex.h"
#include "esp_gmf_node.h"
#include "esp_gmf_mixer.h"
#include "esp_heap_caps.h"
#include "gmf_audio_common.h"
#include "esp_gmf_audio_methods_def.h"
#include "esp_gmf_cap.h"
#include "esp_gmf_caps_def.h"
#include "esp_gmf_audio_element.h"
#ifdef MIXER_DEBUG
#include "esp_timer.h"
#endif
#define MIXER_DEFAULT_PROC_TIME_MS (10)
#define MIXER_GET_FRAME_BYTE_SIZE(info) (MIXER_DEFAULT_PROC_TIME_MS * info->sample_rate * info->channel * info->bits_per_sample / 8000)
typedef struct {
esp_gmf_audio_element_t parent; /*!< The GMF mixer handle */
esp_ae_mixer_handle_t mixer_hd; /*!< The audio effects mixer handle */
uint8_t bytes_per_sample; /*!< Bytes number of per sampling point */
uint32_t process_num; /*!< The data number of mixer processing */
uint32_t frame_time; /*!< The time of one frame, unit: ms */
esp_gmf_payload_t **in_load; /*!< The array of input payload */
esp_gmf_payload_t *out_load; /*!< The output payload */
uint8_t **in_arr; /*!< The input buffer pointer array of mixer */
esp_ae_mixer_mode_t *mode; /*!< The mixer mode */
uint8_t src_num; /*!< The number of source */
bool need_reopen : 1; /*!< Whether need to reopen.
True: Execute the close function first, then execute the open function
False: Do nothing */
} esp_gmf_mixer_t;
static const char *TAG = "ESP_GMF_MIXER";
const esp_ae_mixer_info_t esp_gmf_default_mixer_src_info[] = {
{1.0, 0.5, 500},
{0.5, 0.0, 500},
};
static inline esp_gmf_err_t dupl_esp_ae_mixer_cfg(esp_ae_mixer_cfg_t *config, esp_ae_mixer_cfg_t **new_config)
{
void *sub_cfg = NULL;
*new_config = esp_gmf_oal_calloc(1, sizeof(*config));
ESP_GMF_MEM_VERIFY(TAG, *new_config, {return ESP_GMF_ERR_MEMORY_LACK;}, "mixer configuration", sizeof(*config));
memcpy(*new_config, config, sizeof(*config));
if (config->src_info && (config->src_num > 0)) {
sub_cfg = esp_gmf_oal_calloc(1, config->src_num * sizeof(esp_ae_mixer_info_t));
ESP_GMF_MEM_VERIFY(TAG, sub_cfg, {esp_gmf_oal_free(*new_config); return ESP_GMF_ERR_MEMORY_LACK;},
"source information", config->src_num * sizeof(esp_ae_mixer_info_t));
memcpy(sub_cfg, config->src_info, config->src_num * sizeof(esp_ae_mixer_info_t));
(*new_config)->src_info = sub_cfg;
}
return ESP_GMF_JOB_ERR_OK;
}
static inline void free_esp_ae_mixer_cfg(esp_ae_mixer_cfg_t *config)
{
if (config) {
if (config->src_info && (config->src_info != esp_gmf_default_mixer_src_info)) {
esp_gmf_oal_free(config->src_info);
}
esp_gmf_oal_free(config);
}
}
static esp_gmf_err_t __mixer_set_mode(esp_gmf_element_handle_t handle, esp_gmf_args_desc_t *arg_desc,
uint8_t *buf, int buf_len)
{
ESP_GMF_NULL_CHECK(TAG, arg_desc, {return ESP_GMF_ERR_INVALID_ARG;});
ESP_GMF_NULL_CHECK(TAG, buf, {return ESP_GMF_ERR_INVALID_ARG;});
uint8_t src_idx = (uint8_t)(*buf);
return esp_gmf_mixer_set_mode(handle, src_idx, *((esp_ae_mixer_mode_t *)(buf + arg_desc->next->offset)));
}
static esp_gmf_err_t __mixer_set_audio_info(esp_gmf_element_handle_t handle, esp_gmf_args_desc_t *arg_desc,
uint8_t *buf, int buf_len)
{
ESP_GMF_NULL_CHECK(TAG, handle, {return ESP_GMF_ERR_INVALID_ARG;});
ESP_GMF_NULL_CHECK(TAG, arg_desc, {return ESP_GMF_ERR_INVALID_ARG;});
ESP_GMF_NULL_CHECK(TAG, buf, {return ESP_GMF_ERR_INVALID_ARG;});
esp_gmf_args_desc_t *mix_desc = arg_desc;
uint32_t rate = *((uint32_t *)buf);
mix_desc = mix_desc->next;
uint8_t ch = (uint8_t)(*(buf + mix_desc->offset));
mix_desc = mix_desc->next;
uint8_t bits = (uint8_t)(*(buf + mix_desc->offset));
return esp_gmf_mixer_set_audio_info(handle, rate, bits, ch);
}
static esp_gmf_err_t esp_gmf_mixer_new(void *cfg, esp_gmf_obj_handle_t *handle)
{
return esp_gmf_mixer_init(cfg, (esp_gmf_element_handle_t *)handle);
}
static esp_gmf_job_err_t esp_gmf_mixer_open(esp_gmf_element_handle_t self, void *para)
{
esp_gmf_mixer_t *mixer = (esp_gmf_mixer_t *)self;
esp_ae_mixer_cfg_t *mixer_info = (esp_ae_mixer_cfg_t *)OBJ_GET_CFG(self);
ESP_GMF_NULL_CHECK(TAG, mixer_info, {return ESP_GMF_JOB_ERR_FAIL;})
mixer->bytes_per_sample = (mixer_info->bits_per_sample >> 3) * mixer_info->channel;
esp_ae_mixer_open(mixer_info, &mixer->mixer_hd);
ESP_GMF_CHECK(TAG, mixer->mixer_hd, {return ESP_GMF_JOB_ERR_FAIL;}, "Failed to create mixer handle");
for (int i = 0; i < mixer_info->src_num; i++) {
esp_ae_mixer_set_mode(mixer->mixer_hd, i, mixer->mode[i]);
}
uint32_t process_num = MIXER_GET_FRAME_BYTE_SIZE(mixer_info);
mixer->process_num = (ESP_GMF_ELEMENT_GET(mixer)->in_attr.data_size == 0) ? (process_num) : (ESP_GMF_ELEMENT_GET(mixer)->in_attr.data_size);
mixer->frame_time = GMF_AUDIO_CALC_PTS(mixer->process_num, mixer_info->sample_rate, mixer_info->channel, mixer_info->bits_per_sample);
mixer->src_num = mixer_info->src_num;
mixer->in_load = esp_gmf_oal_calloc(1, sizeof(esp_gmf_payload_t *) * mixer_info->src_num);
ESP_GMF_MEM_VERIFY(TAG, mixer->in_load, {return ESP_GMF_JOB_ERR_FAIL;},
"in load", sizeof(esp_gmf_payload_t *) * mixer_info->src_num);
mixer->in_arr = esp_gmf_oal_calloc(1, sizeof(int *) * mixer_info->src_num);
ESP_GMF_MEM_VERIFY(TAG, mixer->in_arr, {return ESP_GMF_JOB_ERR_FAIL;},
"in buffer array", sizeof(int *) * mixer_info->src_num);
GMF_AUDIO_UPDATE_SND_INFO(self, mixer_info->sample_rate, mixer_info->bits_per_sample, mixer_info->channel);
mixer->need_reopen = false;
ESP_LOGD(TAG, "Open, %p", self);
return ESP_GMF_JOB_ERR_OK;
}
static esp_gmf_job_err_t esp_gmf_mixer_close(esp_gmf_element_handle_t self, void *para)
{
esp_gmf_mixer_t *mixer = (esp_gmf_mixer_t *)self;
ESP_LOGD(TAG, "Closed, %p", self);
if (mixer->mixer_hd != NULL) {
esp_ae_mixer_close(mixer->mixer_hd);
mixer->mixer_hd = NULL;
}
if (mixer->in_arr != NULL) {
esp_gmf_oal_free(mixer->in_arr);
mixer->in_arr = NULL;
}
if (mixer->in_load != NULL) {
esp_gmf_oal_free(mixer->in_load);
mixer->in_load = NULL;
}
return ESP_GMF_ERR_OK;
}
static esp_gmf_job_err_t esp_gmf_mixer_process(esp_gmf_element_handle_t self, void *para)
{
esp_gmf_mixer_t *mixer = (esp_gmf_mixer_t *)self;
esp_gmf_job_err_t out_len = ESP_GMF_JOB_ERR_OK;
if (mixer->need_reopen) {
esp_gmf_mixer_close(self, NULL);
out_len = esp_gmf_mixer_open(self, NULL);
if (out_len != ESP_GMF_JOB_ERR_OK) {
ESP_LOGE(TAG, "Mixer reopen failed");
return out_len;
}
}
int read_len = 0;
esp_gmf_err_io_t ret = ESP_GMF_IO_OK;
int status_end = 0;
ESP_GMF_RET_ON_NOT_OK(TAG, ret, { return ESP_GMF_JOB_ERR_FAIL;}, "Failed to apply mixer setting");
esp_gmf_port_handle_t in = ESP_GMF_ELEMENT_GET(self)->in;
esp_gmf_port_handle_t in_port = in;
esp_gmf_port_handle_t out_port = ESP_GMF_ELEMENT_GET(self)->out;
int index = mixer->src_num;
memset(mixer->in_load, 0, sizeof(esp_gmf_payload_t *) * index);
mixer->out_load = NULL;
int i = 0;
#ifdef MIXER_DEBUG
uint64_t start = 0;
uint64_t end = 0;
uint64_t acquire_in_time = 0;
uint32_t frame_time = mixer->frame_time;
#endif
while (in_port != NULL) {
#ifdef MIXER_DEBUG
start = esp_timer_get_time();
#endif
int wait_ticks = ((in_port->wait_ticks < (mixer->frame_time / index)) ? (mixer->frame_time / index) : (in_port->wait_ticks));
ret = esp_gmf_port_acquire_in(in_port, &(mixer->in_load[i]), mixer->process_num, wait_ticks);
#ifdef MIXER_DEBUG
end = esp_timer_get_time();
acquire_in_time += ((end - start) / 1000);
ESP_LOGD(TAG, "Port %d acquire in time: %lld ms, wait ticks: %d", i, (end - start) / 1000, wait_ticks);
#endif
if (ret == ESP_GMF_IO_FAIL || mixer->in_load[i]->buf == NULL) {
ESP_LOGE(TAG, "Acquire in failed, idx:%d, ret: %d", i, ret);
out_len = ESP_GMF_JOB_ERR_FAIL;
goto __mixer_release;
}
if (ret == ESP_GMF_IO_TIMEOUT || ret == ESP_GMF_IO_ABORT || mixer->in_load[i]->is_done) {
status_end++;
}
read_len = mixer->in_load[i]->valid_size;
mixer->in_arr[i] = mixer->in_load[i]->buf;
if (read_len < mixer->process_num) {
memset(mixer->in_arr[i] + read_len, 0, mixer->process_num - read_len);
}
in_port = in_port->next;
ESP_LOGV(TAG, "IN: idx: %d load: %p, buf: %p, valid size: %d, buf length: %d, done: %d",
i, mixer->in_load[i], mixer->in_load[i]->buf, mixer->in_load[i]->valid_size,
mixer->in_load[i]->buf_length, mixer->in_load[i]->is_done);
i++;
}
#ifdef MIXER_DEBUG
if (acquire_in_time > frame_time) {
ESP_LOGW(TAG, "Total acquire in time: %lld ms, frame time: %ld ms", acquire_in_time, frame_time);
}
#endif
ret = esp_gmf_port_acquire_out(out_port, &mixer->out_load, mixer->process_num, ESP_GMF_MAX_DELAY);
ESP_GMF_PORT_ACQUIRE_OUT_CHECK(TAG, ret, out_len, { goto __mixer_release;});
// Down-mixer never stop in gmf, only user can set to stop
if (status_end == index) {
memset(mixer->out_load->buf, 0, mixer->process_num);
out_len = ESP_GMF_JOB_ERR_OK;
goto __mixer_release;
}
esp_gmf_oal_mutex_lock(((esp_gmf_audio_element_t *)self)->lock);
esp_ae_err_t porc_ret = esp_ae_mixer_process(mixer->mixer_hd, mixer->process_num / mixer->bytes_per_sample,
(void *)mixer->in_arr, mixer->out_load->buf);
esp_gmf_oal_mutex_unlock(((esp_gmf_audio_element_t *)self)->lock);
if (porc_ret != ESP_AE_ERR_OK) {
ESP_LOGE(TAG, "Mix process error %d.", porc_ret);
return ESP_GMF_JOB_ERR_FAIL;
}
ESP_LOGV(TAG, "OUT: load: %p, buf: %p, valid size: %d, buf length: %d",
mixer->out_load, mixer->out_load->buf, mixer->out_load->valid_size, mixer->out_load->buf_length);
mixer->out_load->valid_size = mixer->process_num;
if (mixer->out_load->valid_size > 0) {
esp_gmf_audio_el_update_file_pos((esp_gmf_element_handle_t)self, mixer->out_load->valid_size);
}
__mixer_release:
// Release in and out port
if (mixer->out_load != NULL) {
ret = esp_gmf_port_release_out(out_port, mixer->out_load, ESP_GMF_MAX_DELAY);
if ((ret < ESP_GMF_IO_OK) && (ret != ESP_GMF_IO_ABORT)) {
ESP_LOGE(TAG, "OUT port release error, ret:%d", ret);
out_len = ESP_GMF_JOB_ERR_FAIL;
}
}
in_port = in;
i = 0;
while (in_port != NULL && mixer->in_load[i] != NULL) {
ret = esp_gmf_port_release_in(in_port, mixer->in_load[i], ESP_GMF_MAX_DELAY);
if ((ret < ESP_GMF_IO_OK) && (ret != ESP_GMF_IO_ABORT)) {
ESP_LOGE(TAG, "IN port release error, ret:%d", ret);
out_len = ESP_GMF_JOB_ERR_FAIL;
}
in_port = in_port->next;
i++;
}
return out_len;
}
static esp_gmf_err_t mixer_received_event_handler(esp_gmf_event_pkt_t *evt, void *ctx)
{
ESP_GMF_NULL_CHECK(TAG, ctx, {return ESP_GMF_ERR_INVALID_ARG;});
ESP_GMF_NULL_CHECK(TAG, evt, {return ESP_GMF_ERR_INVALID_ARG;});
if ((evt->type != ESP_GMF_EVT_TYPE_REPORT_INFO)
|| (evt->sub != ESP_GMF_INFO_SOUND)
|| (evt->payload == NULL)) {
return ESP_GMF_ERR_OK;
}
esp_gmf_element_handle_t self = (esp_gmf_element_handle_t)ctx;
esp_gmf_element_handle_t el = evt->from;
esp_gmf_event_state_t state = ESP_GMF_EVENT_STATE_NONE;
esp_gmf_element_get_state(self, &state);
esp_gmf_info_sound_t *info = (esp_gmf_info_sound_t *)evt->payload;
esp_ae_mixer_cfg_t *config = (esp_ae_mixer_cfg_t *)OBJ_GET_CFG(self);
ESP_GMF_NULL_CHECK(TAG, config, return ESP_GMF_ERR_FAIL);
esp_gmf_mixer_t *mixer = (esp_gmf_mixer_t *)self;
mixer->need_reopen = (config->sample_rate != info->sample_rates) || (info->channels != config->channel) || (config->bits_per_sample != info->bits);
config->sample_rate = info->sample_rates;
config->channel = info->channels;
config->bits_per_sample = info->bits;
ESP_LOGD(TAG, "RECV element info, from: %s-%p, next: %p, self: %s-%p, type: %x, state: %s, rate: %d, ch: %d, bits: %d",
OBJ_GET_TAG(el), el, esp_gmf_node_for_next((esp_gmf_node_t *)el), OBJ_GET_TAG(self), self, evt->type,
esp_gmf_event_get_state_str(state), info->sample_rates, info->channels, info->bits);
if (state == ESP_GMF_EVENT_STATE_NONE) {
esp_gmf_element_set_state(self, ESP_GMF_EVENT_STATE_INITIALIZED);
}
return ESP_GMF_ERR_OK;
}
static esp_gmf_err_t esp_gmf_mixer_destroy(esp_gmf_element_handle_t self)
{
esp_gmf_mixer_t *mixer = (esp_gmf_mixer_t *)self;
ESP_LOGD(TAG, "Destroyed, %p", self);
free_esp_ae_mixer_cfg(OBJ_GET_CFG(self));
if (mixer->mode) {
esp_gmf_oal_free(mixer->mode);
}
esp_gmf_audio_el_deinit(self);
esp_gmf_oal_free(mixer);
return ESP_GMF_ERR_OK;
}
static esp_gmf_err_t _load_mixer_caps_func(esp_gmf_element_handle_t handle)
{
esp_gmf_cap_t *caps = NULL;
esp_gmf_cap_t dec_caps = {0};
dec_caps.cap_eightcc = ESP_GMF_CAPS_AUDIO_MIXER;
dec_caps.attr_fun = NULL;
int ret = esp_gmf_cap_append(&caps, &dec_caps);
ESP_GMF_RET_ON_NOT_OK(TAG, ret, {return ret;}, "Failed to create capability");
esp_gmf_element_t *el = (esp_gmf_element_t *)handle;
el->caps = caps;
return ESP_GMF_ERR_OK;
}
static esp_gmf_err_t _load_mixer_methods_func(esp_gmf_element_handle_t handle)
{
esp_gmf_method_t *method = NULL;
esp_gmf_args_desc_t *set_args = NULL;
esp_gmf_err_t ret = esp_gmf_args_desc_append(&set_args, AMETHOD_ARG(MIXER, SET_INFO, RATE), ESP_GMF_ARGS_TYPE_UINT32, sizeof(uint32_t), 0);
ESP_GMF_RET_ON_NOT_OK(TAG, ret, {return ret;}, "Failed to append RATE argument");
ret = esp_gmf_args_desc_append(&set_args, AMETHOD_ARG(MIXER, SET_INFO, CH), ESP_GMF_ARGS_TYPE_UINT8,
sizeof(uint8_t), sizeof(uint32_t));
ESP_GMF_RET_ON_NOT_OK(TAG, ret, {return ret;}, "Failed to append CHANNEL argument");
ret = esp_gmf_args_desc_append(&set_args, AMETHOD_ARG(MIXER, SET_INFO, BITS), ESP_GMF_ARGS_TYPE_UINT8,
sizeof(uint8_t), sizeof(uint8_t) + sizeof(uint32_t));
ESP_GMF_RET_ON_NOT_OK(TAG, ret, {return ret;}, "Failed to append BITS argument");
ret = esp_gmf_method_append(&method, AMETHOD(MIXER, SET_INFO), __mixer_set_audio_info, set_args);
ESP_GMF_RET_ON_ERROR(TAG, ret, {return ret;}, "Failed to register %s method", AMETHOD(MIXER, SET_INFO));
set_args = NULL;
ret = esp_gmf_args_desc_append(&set_args, AMETHOD_ARG(MIXER, SET_MODE, IDX), ESP_GMF_ARGS_TYPE_UINT8, sizeof(uint8_t), 0);
ESP_GMF_RET_ON_NOT_OK(TAG, ret, {return ret;}, "Failed to append INDEX argument");
ret = esp_gmf_args_desc_append(&set_args, AMETHOD_ARG(MIXER, SET_MODE, MODE), ESP_GMF_ARGS_TYPE_INT32,
sizeof(int32_t), sizeof(uint8_t));
ESP_GMF_RET_ON_NOT_OK(TAG, ret, {return ret;}, "Failed to append MODE argument");
ret = esp_gmf_method_append(&method, AMETHOD(MIXER, SET_MODE), __mixer_set_mode, set_args);
ESP_GMF_RET_ON_ERROR(TAG, ret, {return ret;}, "Failed to register %s method", AMETHOD(MIXER, SET_MODE));
esp_gmf_element_t *el = (esp_gmf_element_t *)handle;
el->method = method;
return ESP_GMF_ERR_OK;
}
esp_gmf_err_t esp_gmf_mixer_set_mode(esp_gmf_element_handle_t handle, uint8_t src_idx, esp_ae_mixer_mode_t mode)
{
ESP_GMF_NULL_CHECK(TAG, handle, { return ESP_GMF_ERR_INVALID_ARG;});
esp_gmf_mixer_t *mixer = (esp_gmf_mixer_t *)handle;
esp_ae_mixer_cfg_t *cfg = (esp_ae_mixer_cfg_t *)OBJ_GET_CFG(handle);
ESP_GMF_NULL_CHECK(TAG, cfg, return ESP_GMF_ERR_FAIL);
if (src_idx >= cfg->src_num) {
ESP_LOGE(TAG, "Source index %d overlimit %d hd:%p", src_idx, cfg->src_num, mixer);
return ESP_GMF_ERR_INVALID_ARG;
}
if (mixer->mixer_hd) {
esp_gmf_oal_mutex_lock(((esp_gmf_audio_element_t *)handle)->lock);
esp_ae_err_t ret = esp_ae_mixer_set_mode(mixer->mixer_hd, src_idx, mode);
esp_gmf_oal_mutex_unlock(((esp_gmf_audio_element_t *)handle)->lock);
ESP_GMF_RET_ON_ERROR(TAG, ret, return ESP_GMF_JOB_ERR_FAIL;, "mixerualize set error %d", ret);
}
mixer->mode[src_idx] = mode;
return ESP_GMF_ERR_OK;
}
esp_gmf_err_t esp_gmf_mixer_set_audio_info(esp_gmf_element_handle_t handle, uint32_t sample_rate,
uint8_t bits, uint8_t channel)
{
ESP_GMF_NULL_CHECK(TAG, handle, { return ESP_GMF_ERR_INVALID_ARG;});
esp_ae_mixer_cfg_t *cfg = (esp_ae_mixer_cfg_t *)OBJ_GET_CFG(handle);
ESP_GMF_NULL_CHECK(TAG, cfg, return ESP_GMF_ERR_FAIL);
if (cfg->sample_rate == sample_rate && cfg->bits_per_sample == bits && cfg->channel == channel) {
return ESP_GMF_ERR_OK;
}
cfg->sample_rate = sample_rate;
cfg->bits_per_sample = bits;
cfg->channel = channel;
esp_gmf_mixer_t *mixer = (esp_gmf_mixer_t *)handle;
mixer->need_reopen = true;
return ESP_GMF_ERR_OK;
}
esp_gmf_err_t esp_gmf_mixer_init(esp_ae_mixer_cfg_t *config, esp_gmf_element_handle_t *handle)
{
ESP_GMF_NULL_CHECK(TAG, handle, {return ESP_GMF_ERR_INVALID_ARG;});
*handle = NULL;
esp_gmf_err_t ret = ESP_GMF_ERR_OK;
esp_gmf_mixer_t *mixer = esp_gmf_oal_calloc(1, sizeof(esp_gmf_mixer_t));
ESP_GMF_MEM_VERIFY(TAG, mixer, {return ESP_GMF_ERR_MEMORY_LACK;}, "mixer", sizeof(esp_gmf_mixer_t));
esp_gmf_obj_t *obj = (esp_gmf_obj_t *)mixer;
obj->new_obj = esp_gmf_mixer_new;
obj->del_obj = esp_gmf_mixer_destroy;
esp_ae_mixer_cfg_t *cfg = NULL;
if (config) {
if (config->src_info == NULL) {
config->src_info = (esp_ae_mixer_info_t *)esp_gmf_default_mixer_src_info;
config->src_num = sizeof(esp_gmf_default_mixer_src_info) / sizeof(esp_ae_mixer_info_t);
}
dupl_esp_ae_mixer_cfg(config, &cfg);
} else {
esp_ae_mixer_cfg_t dcfg = DEFAULT_ESP_GMF_MIXER_CONFIG();
dcfg.src_info = (esp_ae_mixer_info_t *)esp_gmf_default_mixer_src_info;
dcfg.src_num = sizeof(esp_gmf_default_mixer_src_info) / sizeof(esp_ae_mixer_info_t);
dupl_esp_ae_mixer_cfg(&dcfg, &cfg);
}
ESP_GMF_CHECK(TAG, cfg, ret = ESP_GMF_ERR_MEMORY_LACK; goto MIXER_INIT_FAIL;, "Failed to allocate mixer configuration");
esp_gmf_obj_set_config(obj, cfg, sizeof(esp_ae_mixer_cfg_t));
mixer->mode = esp_gmf_oal_calloc(cfg->src_num, sizeof(esp_ae_mixer_mode_t));
ESP_GMF_MEM_VERIFY(TAG, mixer->mode, {ret = ESP_GMF_ERR_MEMORY_LACK; goto MIXER_INIT_FAIL;}, "Allocate(%d) failed", cfg->src_num * sizeof(esp_ae_mixer_mode_t));
for (int i = 0; i < cfg->src_num; i++) {
mixer->mode[i] = ESP_AE_MIXER_MODE_FADE_UPWARD;
}
ret = esp_gmf_obj_set_tag(obj, "aud_mixer");
ESP_GMF_RET_ON_NOT_OK(TAG, ret, goto MIXER_INIT_FAIL, "Failed to set obj tag");
esp_gmf_element_cfg_t el_cfg = {0};
mixer->process_num = MIXER_GET_FRAME_BYTE_SIZE(cfg);
ESP_GMF_ELEMENT_IN_PORT_ATTR_SET(el_cfg.in_attr, ESP_GMF_EL_PORT_CAP_MULTI, 0, 0,
ESP_GMF_PORT_TYPE_BLOCK | ESP_GMF_PORT_TYPE_BYTE, mixer->process_num);
ESP_GMF_ELEMENT_IN_PORT_ATTR_SET(el_cfg.out_attr, ESP_GMF_EL_PORT_CAP_SINGLE, 0, 0,
ESP_GMF_PORT_TYPE_BLOCK | ESP_GMF_PORT_TYPE_BYTE, mixer->process_num);
el_cfg.dependency = true;
ret = esp_gmf_audio_el_init(mixer, &el_cfg);
ESP_GMF_RET_ON_NOT_OK(TAG, ret, goto MIXER_INIT_FAIL, "Failed to initialize mixer element");
ESP_GMF_ELEMENT_GET(mixer)->ops.open = esp_gmf_mixer_open;
ESP_GMF_ELEMENT_GET(mixer)->ops.process = esp_gmf_mixer_process;
ESP_GMF_ELEMENT_GET(mixer)->ops.close = esp_gmf_mixer_close;
ESP_GMF_ELEMENT_GET(mixer)->ops.event_receiver = mixer_received_event_handler;
ESP_GMF_ELEMENT_GET(mixer)->ops.load_caps = _load_mixer_caps_func;
ESP_GMF_ELEMENT_GET(mixer)->ops.load_methods = _load_mixer_methods_func;
*handle = obj;
ESP_LOGD(TAG, "Initialization, %s-%p", OBJ_GET_TAG(obj), obj);
return ESP_GMF_ERR_OK;
MIXER_INIT_FAIL:
esp_gmf_mixer_destroy(obj);
return ret;
}
@@ -0,0 +1,276 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO., LTD
* SPDX-License-Identifier: LicenseRef-Espressif-Modified-MIT
*
* See LICENSE file for details.
*/
#include <string.h>
#include "esp_log.h"
#include "esp_gmf_oal_mem.h"
#include "esp_gmf_oal_mutex.h"
#include "esp_gmf_node.h"
#include "esp_gmf_rate_cvt.h"
#include "gmf_audio_common.h"
#include "esp_gmf_audio_methods_def.h"
#include "esp_gmf_cap.h"
#include "esp_gmf_caps_def.h"
#include "esp_gmf_audio_element.h"
/**
* @brief Audio rate conversion context in GMF
*/
typedef struct {
esp_gmf_audio_element_t parent; /*!< The GMF rate cvt handle */
esp_ae_rate_cvt_handle_t rate_hd; /*!< The audio effects rate cvt handle */
uint8_t bytes_per_sample; /*!< Bytes number of per sampling point */
bool need_reopen : 1; /*!< Whether need to reopen.
True: Execute the close function first, then execute the open function
False: Do nothing */
bool bypass : 1; /*!< Whether bypass. True: need bypass. False: needn't bypass */
} esp_gmf_rate_cvt_t;
static const char *TAG = "ESP_GMF_RATE_CVT";
static esp_gmf_err_t __rate_cvt_set_dest_rate(esp_gmf_element_handle_t handle, esp_gmf_args_desc_t *arg_desc,
uint8_t *buf, int buf_len)
{
ESP_GMF_NULL_CHECK(TAG, buf, {return ESP_GMF_ERR_INVALID_ARG;});
uint32_t dest_rate = *((uint32_t *)buf);
return esp_gmf_rate_cvt_set_dest_rate(handle, dest_rate);
}
static esp_gmf_err_t esp_gmf_rate_cvt_new(void *cfg, esp_gmf_obj_handle_t *handle)
{
return esp_gmf_rate_cvt_init(cfg, (esp_gmf_element_handle_t *)handle);
}
static esp_gmf_job_err_t esp_gmf_rate_cvt_open(esp_gmf_element_handle_t self, void *para)
{
esp_gmf_rate_cvt_t *rate_cvt = (esp_gmf_rate_cvt_t *)self;
esp_ae_rate_cvt_cfg_t *rate_info = (esp_ae_rate_cvt_cfg_t *)OBJ_GET_CFG(self);
ESP_GMF_NULL_CHECK(TAG, rate_info, {return ESP_GMF_JOB_ERR_FAIL;});
rate_cvt->bytes_per_sample = (rate_info->bits_per_sample >> 3) * rate_info->channel;
esp_ae_rate_cvt_open(rate_info, &rate_cvt->rate_hd);
ESP_GMF_CHECK(TAG, rate_cvt->rate_hd, {return ESP_GMF_JOB_ERR_FAIL;}, "Failed to create rate conversion handle");
GMF_AUDIO_UPDATE_SND_INFO(self, rate_info->dest_rate, rate_info->bits_per_sample, rate_info->channel);
ESP_LOGD(TAG, "Open, src: %"PRIu32", dest: %"PRIu32", ch: %d, bits: %d",
rate_info->src_rate, rate_info->dest_rate, rate_info->channel, rate_info->bits_per_sample);
rate_cvt->need_reopen = false;
rate_cvt->bypass = rate_info->src_rate == rate_info->dest_rate;
return ESP_GMF_JOB_ERR_OK;
}
static esp_gmf_job_err_t esp_gmf_rate_cvt_close(esp_gmf_element_handle_t self, void *para)
{
esp_gmf_rate_cvt_t *rate_cvt = (esp_gmf_rate_cvt_t *)self;
ESP_LOGD(TAG, "Closed, %p", self);
if (rate_cvt->rate_hd != NULL) {
esp_ae_rate_cvt_close(rate_cvt->rate_hd);
rate_cvt->rate_hd = NULL;
}
return ESP_GMF_JOB_ERR_OK;
}
static esp_gmf_job_err_t esp_gmf_rate_cvt_process(esp_gmf_element_handle_t self, void *para)
{
esp_gmf_rate_cvt_t *rate_cvt = (esp_gmf_rate_cvt_t *)self;
esp_gmf_job_err_t out_len = ESP_GMF_JOB_ERR_OK;
if (rate_cvt->need_reopen) {
esp_gmf_rate_cvt_close(self, NULL);
out_len = esp_gmf_rate_cvt_open(self, NULL);
if (out_len != ESP_GMF_JOB_ERR_OK) {
ESP_LOGE(TAG, "Rate conversion reopen failed");
return out_len;
}
}
esp_ae_err_t ret = ESP_AE_ERR_OK;
esp_gmf_port_handle_t in_port = ESP_GMF_ELEMENT_GET(self)->in;
esp_gmf_port_handle_t out_port = ESP_GMF_ELEMENT_GET(self)->out;
esp_gmf_payload_t *in_load = NULL;
esp_gmf_payload_t *out_load = NULL;
int samples_num = ESP_GMF_ELEMENT_GET(rate_cvt)->in_attr.data_size / (rate_cvt->bytes_per_sample);
int bytes = samples_num * rate_cvt->bytes_per_sample;
esp_gmf_err_io_t load_ret = esp_gmf_port_acquire_in(in_port, &in_load, bytes, ESP_GMF_MAX_DELAY);
samples_num = in_load->valid_size / (rate_cvt->bytes_per_sample);
bytes = samples_num * rate_cvt->bytes_per_sample;
if((bytes != in_load->valid_size) || (load_ret < ESP_GMF_IO_OK)) {
ESP_LOGE(TAG, "Invalid in load size %d, ret %d", in_load->valid_size, load_ret);
out_len = ESP_GMF_JOB_ERR_FAIL;
goto __rate_release;
}
uint32_t out_samples_num = 0;
if (samples_num) {
ret = esp_ae_rate_cvt_get_max_out_sample_num(rate_cvt->rate_hd, samples_num, &out_samples_num);
ESP_GMF_RET_ON_ERROR(TAG, ret, {out_len = ESP_GMF_JOB_ERR_FAIL; goto __rate_release;}, "Failed to get resample out size, ret: %d", ret);
}
int acq_out_size = out_samples_num == 0 ? in_load->buf_length : out_samples_num * rate_cvt->bytes_per_sample;
if (rate_cvt->bypass && (in_port->is_shared == true)) {
// This case rate conversion is do bypass
out_load = in_load;
}
load_ret = esp_gmf_port_acquire_out(out_port, &out_load, acq_out_size, ESP_GMF_MAX_DELAY);
ESP_GMF_PORT_ACQUIRE_OUT_CHECK(TAG, load_ret, out_len, {goto __rate_release;});
if (samples_num) {
ret = esp_ae_rate_cvt_process(rate_cvt->rate_hd, (unsigned char *)in_load->buf, samples_num,
(unsigned char *)out_load->buf, &out_samples_num);
ESP_GMF_RET_ON_ERROR(TAG, ret, {out_len = ESP_GMF_JOB_ERR_FAIL; goto __rate_release;}, "Rate conversion process error, ret: %d", ret);
}
out_load->valid_size = out_samples_num * rate_cvt->bytes_per_sample;
out_load->pts = in_load->pts;
out_load->is_done = in_load->is_done;
ESP_LOGV(TAG, "Out Samples: %ld, IN-PLD: %p-%p-%d-%d-%d, OUT-PLD: %p-%p-%d-%d-%d", out_samples_num, in_load, in_load->buf,
in_load->valid_size, in_load->buf_length, in_load->is_done, out_load,
out_load->buf, out_load->valid_size, out_load->buf_length, out_load->is_done);
esp_gmf_audio_el_update_file_pos((esp_gmf_element_handle_t)self, out_load->valid_size);
if (in_load->is_done) {
out_len = ESP_GMF_JOB_ERR_DONE;
ESP_LOGD(TAG, "Rate convert done, out len: %d", out_load->valid_size);
}
__rate_release:
// Release in and out port
if (out_load != NULL) {
load_ret = esp_gmf_port_release_out(out_port, out_load, ESP_GMF_MAX_DELAY);
if ((load_ret < ESP_GMF_IO_OK) && (load_ret != ESP_GMF_IO_ABORT)) {
ESP_LOGE(TAG, "OUT port release error, ret:%d", load_ret);
out_len = ESP_GMF_JOB_ERR_FAIL;
}
}
if (in_load != NULL) {
load_ret = esp_gmf_port_release_in(in_port, in_load, ESP_GMF_MAX_DELAY);
if ((load_ret < ESP_GMF_IO_OK) && (load_ret != ESP_GMF_IO_ABORT)) {
ESP_LOGE(TAG, "IN port release error, ret:%d", load_ret);
out_len = ESP_GMF_JOB_ERR_FAIL;
}
}
return out_len;
}
static esp_gmf_err_t rate_cvt_received_event_handler(esp_gmf_event_pkt_t *evt, void *ctx)
{
ESP_GMF_NULL_CHECK(TAG, ctx, {return ESP_GMF_ERR_INVALID_ARG;});
ESP_GMF_NULL_CHECK(TAG, evt, {return ESP_GMF_ERR_INVALID_ARG;});
if ((evt->type != ESP_GMF_EVT_TYPE_REPORT_INFO)
|| (evt->sub != ESP_GMF_INFO_SOUND)
|| (evt->payload == NULL)) {
return ESP_GMF_ERR_OK;
}
esp_gmf_element_handle_t self = (esp_gmf_element_handle_t)ctx;
esp_gmf_element_handle_t el = evt->from;
esp_gmf_event_state_t state = ESP_GMF_EVENT_STATE_NONE;
esp_gmf_element_get_state(self, &state);
esp_gmf_info_sound_t *info = (esp_gmf_info_sound_t *)evt->payload;
esp_ae_rate_cvt_cfg_t *config = (esp_ae_rate_cvt_cfg_t *)OBJ_GET_CFG(self);
ESP_GMF_NULL_CHECK(TAG, config, return ESP_GMF_ERR_FAIL);
esp_gmf_rate_cvt_t *rate_cvt = (esp_gmf_rate_cvt_t *)self;
rate_cvt->need_reopen = (config->src_rate != info->sample_rates) || (info->channels != config->channel) || (config->bits_per_sample != info->bits);
config->src_rate = info->sample_rates;
config->channel = info->channels;
config->bits_per_sample = info->bits;
ESP_LOGD(TAG, "RECV element info, from: %s-%p, next: %p, self: %s-%p, type: %x, state: %s, rate: %d, ch: %d, bits: %d",
OBJ_GET_TAG(el), el, esp_gmf_node_for_next((esp_gmf_node_t *)el), OBJ_GET_TAG(self), self, evt->type,
esp_gmf_event_get_state_str(state), info->sample_rates, info->channels, info->bits);
if (state == ESP_GMF_EVENT_STATE_NONE) {
esp_gmf_element_set_state(self, ESP_GMF_EVENT_STATE_INITIALIZED);
}
return ESP_GMF_ERR_OK;
}
static esp_gmf_err_t esp_gmf_rate_cvt_destroy(esp_gmf_element_handle_t self)
{
esp_gmf_rate_cvt_t *rate_cvt = (esp_gmf_rate_cvt_t *)self;
ESP_LOGD(TAG, "Destroyed, %p", self);
void *cfg = OBJ_GET_CFG(self);
if (cfg) {
esp_gmf_oal_free(cfg);
}
esp_gmf_audio_el_deinit(self);
esp_gmf_oal_free(rate_cvt);
return ESP_GMF_ERR_OK;
}
static esp_gmf_err_t _load_rate_cvt_caps_func(esp_gmf_element_handle_t handle)
{
esp_gmf_cap_t *caps = NULL;
esp_gmf_cap_t dec_caps = {0};
dec_caps.cap_eightcc = ESP_GMF_CAPS_AUDIO_RATE_CONVERT;
dec_caps.attr_fun = NULL;
int ret = esp_gmf_cap_append(&caps, &dec_caps);
ESP_GMF_RET_ON_NOT_OK(TAG, ret, {return ret;}, "Failed to create capability");
esp_gmf_element_t *el = (esp_gmf_element_t *)handle;
el->caps = caps;
return ESP_GMF_ERR_OK;
}
static esp_gmf_err_t _load_rate_cvt_methods_func(esp_gmf_element_handle_t handle)
{
esp_gmf_method_t *method = NULL;
esp_gmf_args_desc_t *set_args = NULL;
esp_gmf_err_t ret = esp_gmf_args_desc_append(&set_args, AMETHOD_ARG(RATE_CVT, SET_DEST_RATE, RATE),
ESP_GMF_ARGS_TYPE_UINT32, sizeof(uint32_t), 0);
ESP_GMF_RET_ON_NOT_OK(TAG, ret, {return ret;}, "Failed to append RATE argument");
ret = esp_gmf_method_append(&method, AMETHOD(RATE_CVT, SET_DEST_RATE), __rate_cvt_set_dest_rate, set_args);
ESP_GMF_RET_ON_ERROR(TAG, ret, {return ret;}, "Failed to register %s method", AMETHOD(RATE_CVT, SET_DEST_RATE));
esp_gmf_element_t *el = (esp_gmf_element_t *)handle;
el->method = method;
return ESP_GMF_ERR_OK;
}
esp_gmf_err_t esp_gmf_rate_cvt_set_dest_rate(esp_gmf_element_handle_t handle, uint32_t dest_rate)
{
ESP_GMF_NULL_CHECK(TAG, handle, { return ESP_GMF_ERR_INVALID_ARG;});
esp_ae_rate_cvt_cfg_t *cfg = (esp_ae_rate_cvt_cfg_t *)OBJ_GET_CFG(handle);
ESP_GMF_NULL_CHECK(TAG, cfg, return ESP_GMF_ERR_FAIL);
if (cfg->dest_rate == dest_rate) {
return ESP_GMF_ERR_OK;
}
cfg->dest_rate = dest_rate;
esp_gmf_rate_cvt_t *rate_cvt = (esp_gmf_rate_cvt_t *)handle;
rate_cvt->need_reopen = true;
return ESP_GMF_ERR_OK;
}
esp_gmf_err_t esp_gmf_rate_cvt_init(esp_ae_rate_cvt_cfg_t *config, esp_gmf_element_handle_t *handle)
{
ESP_GMF_NULL_CHECK(TAG, handle, {return ESP_GMF_ERR_INVALID_ARG;});
*handle = NULL;
esp_gmf_err_t ret = ESP_GMF_ERR_OK;
esp_gmf_rate_cvt_t *rate_cvt = esp_gmf_oal_calloc(1, sizeof(esp_gmf_rate_cvt_t));
ESP_GMF_MEM_VERIFY(TAG, rate_cvt, {return ESP_GMF_ERR_MEMORY_LACK;}, "rate conversion", sizeof(esp_gmf_rate_cvt_t));
esp_gmf_obj_t *obj = (esp_gmf_obj_t *)rate_cvt;
obj->new_obj = esp_gmf_rate_cvt_new;
obj->del_obj = esp_gmf_rate_cvt_destroy;
esp_ae_rate_cvt_cfg_t *cfg = esp_gmf_oal_calloc(1, sizeof(esp_ae_rate_cvt_cfg_t));
ESP_GMF_MEM_VERIFY(TAG, cfg, {ret = ESP_GMF_ERR_MEMORY_LACK; goto RATE_CVT_INIT_FAIL;}, "Rate conversion configuration", sizeof(esp_ae_rate_cvt_cfg_t));
esp_gmf_obj_set_config(obj, cfg, sizeof(esp_ae_rate_cvt_cfg_t));
if (config) {
memcpy(cfg, config, sizeof(esp_ae_rate_cvt_cfg_t));
} else {
esp_ae_rate_cvt_cfg_t dcfg = DEFAULT_ESP_GMF_RATE_CVT_CONFIG();
memcpy(cfg, &dcfg, sizeof(esp_ae_rate_cvt_cfg_t));
}
ret = esp_gmf_obj_set_tag(obj, "aud_rate_cvt");
ESP_GMF_RET_ON_NOT_OK(TAG, ret, goto RATE_CVT_INIT_FAIL, "Failed to set obj tag");
esp_gmf_element_cfg_t el_cfg = {0};
ESP_GMF_ELEMENT_IN_PORT_ATTR_SET(el_cfg.in_attr, ESP_GMF_EL_PORT_CAP_SINGLE, 0, 0,
ESP_GMF_PORT_TYPE_BLOCK | ESP_GMF_PORT_TYPE_BYTE, ESP_GMF_ELEMENT_PORT_DATA_SIZE_DEFAULT);
ESP_GMF_ELEMENT_IN_PORT_ATTR_SET(el_cfg.out_attr, ESP_GMF_EL_PORT_CAP_SINGLE, 0, 0,
ESP_GMF_PORT_TYPE_BLOCK | ESP_GMF_PORT_TYPE_BYTE, ESP_GMF_ELEMENT_PORT_DATA_SIZE_DEFAULT);
el_cfg.dependency = true;
ret = esp_gmf_audio_el_init(rate_cvt, &el_cfg);
ESP_GMF_RET_ON_NOT_OK(TAG, ret, goto RATE_CVT_INIT_FAIL, "Failed to initialize rate conversion element");
ESP_GMF_ELEMENT_GET(rate_cvt)->ops.open = esp_gmf_rate_cvt_open;
ESP_GMF_ELEMENT_GET(rate_cvt)->ops.process = esp_gmf_rate_cvt_process;
ESP_GMF_ELEMENT_GET(rate_cvt)->ops.close = esp_gmf_rate_cvt_close;
ESP_GMF_ELEMENT_GET(rate_cvt)->ops.event_receiver = rate_cvt_received_event_handler;
ESP_GMF_ELEMENT_GET(rate_cvt)->ops.load_caps = _load_rate_cvt_caps_func;
ESP_GMF_ELEMENT_GET(rate_cvt)->ops.load_methods = _load_rate_cvt_methods_func;
*handle = obj;
ESP_LOGD(TAG, "Initialization, %s-%p", OBJ_GET_TAG(obj), obj);
return ESP_GMF_ERR_OK;
RATE_CVT_INIT_FAIL:
esp_gmf_rate_cvt_destroy(obj);
return ret;
}
@@ -0,0 +1,435 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO., LTD
* SPDX-License-Identifier: LicenseRef-Espressif-Modified-MIT
*
* See LICENSE file for details.
*/
#include <string.h>
#include "esp_log.h"
#include "esp_gmf_oal_mem.h"
#include "esp_gmf_oal_mutex.h"
#include "esp_gmf_node.h"
#include "esp_gmf_sonic.h"
#include "gmf_audio_common.h"
#include "esp_gmf_audio_methods_def.h"
#include "esp_gmf_cap.h"
#include "esp_gmf_caps_def.h"
#include "esp_gmf_audio_element.h"
#define SONIC_DEFAULT_OUTPUT_TIME_MS (10)
/**
* @brief Audio sonic context in GMF
*/
typedef struct {
esp_gmf_audio_element_t parent; /*!< The GMF sonic handle */
esp_ae_sonic_handle_t sonic_hd; /*!< The audio effects sonic handle */
uint8_t bytes_per_sample; /*!< Bytes number of per sampling point */
uint32_t sample_rate; /*!< The audio sample rate */
uint8_t bits_per_sample; /*!< Bits number of per sampling point */
uint8_t channel; /*!< The audio channel */
esp_ae_sonic_in_data_t in_data_hd; /*!< The sonic input data handle */
esp_ae_sonic_out_data_t out_data_hd; /*!< The sonic output data handle */
float speed; /*!< The audio speed */
float pitch; /*!< The audio pitch */
int32_t out_size; /*!< The acquired out size */
int64_t cur_pts; /*!< The audio Presentation Time Stamp(pts) */
bool need_reopen : 1; /*!< Whether need to reopen.
True: Execute the close function first, then execute the open function
False: Do nothing */
bool is_done : 1; /*!< Whether the input data is done */
} esp_gmf_sonic_t;
static const char *TAG = "ESP_GMF_SONIC";
static esp_gmf_err_t __sonic_set_speed(esp_gmf_element_handle_t handle, esp_gmf_args_desc_t *arg_desc,
uint8_t *buf, int buf_len)
{
ESP_GMF_NULL_CHECK(TAG, arg_desc, {return ESP_GMF_ERR_INVALID_ARG;});
ESP_GMF_NULL_CHECK(TAG, buf, {return ESP_GMF_ERR_INVALID_ARG;});
float *speed = (float *)buf;
return esp_gmf_sonic_set_speed(handle, *speed);
}
static esp_gmf_err_t __sonic_get_speed(esp_gmf_element_handle_t handle, esp_gmf_args_desc_t *arg_desc,
uint8_t *buf, int buf_len)
{
ESP_GMF_NULL_CHECK(TAG, buf, {return ESP_GMF_ERR_INVALID_ARG;});
float *speed = (float *)buf;
return esp_gmf_sonic_get_speed(handle, speed);
}
static esp_gmf_err_t __sonic_set_pitch(esp_gmf_element_handle_t handle, esp_gmf_args_desc_t *arg_desc,
uint8_t *buf, int buf_len)
{
ESP_GMF_NULL_CHECK(TAG, arg_desc, {return ESP_GMF_ERR_INVALID_ARG;});
ESP_GMF_NULL_CHECK(TAG, buf, {return ESP_GMF_ERR_INVALID_ARG;});
float *pitch = (float *)buf;
return esp_gmf_sonic_set_pitch(handle, *pitch);
}
static esp_gmf_err_t __sonic_get_pitch(esp_gmf_element_handle_t handle, esp_gmf_args_desc_t *arg_desc,
uint8_t *buf, int buf_len)
{
ESP_GMF_NULL_CHECK(TAG, buf, {return ESP_GMF_ERR_INVALID_ARG;});
float *pitch = (float *)buf;
return esp_gmf_sonic_get_pitch(handle, pitch);
}
static esp_gmf_err_t esp_gmf_sonic_new(void *cfg, esp_gmf_obj_handle_t *handle)
{
return esp_gmf_sonic_init(cfg, (esp_gmf_element_handle_t *)handle);
}
static esp_gmf_job_err_t esp_gmf_sonic_open(esp_gmf_element_handle_t self, void *para)
{
esp_gmf_sonic_t *sonic = (esp_gmf_sonic_t *)self;
esp_ae_sonic_cfg_t *sonic_info = (esp_ae_sonic_cfg_t *)OBJ_GET_CFG(self);
ESP_GMF_NULL_CHECK(TAG, sonic_info, {return ESP_GMF_JOB_ERR_FAIL;});
sonic->sample_rate = sonic_info->sample_rate;
sonic->channel = sonic_info->channel;
sonic->bits_per_sample = sonic_info->bits_per_sample;
sonic->bytes_per_sample = (sonic_info->bits_per_sample >> 3) * sonic_info->channel;
GMF_AUDIO_UPDATE_SND_INFO(self, sonic_info->sample_rate, sonic_info->bits_per_sample, sonic_info->channel);
sonic->out_size = SONIC_DEFAULT_OUTPUT_TIME_MS * sonic->sample_rate * sonic->bytes_per_sample / 1000;
esp_ae_sonic_open(sonic_info, &sonic->sonic_hd);
ESP_GMF_CHECK(TAG, sonic->sonic_hd, {return ESP_GMF_JOB_ERR_FAIL;}, "Failed to create sonic handle");
esp_ae_sonic_set_speed(sonic->sonic_hd, sonic->speed);
esp_ae_sonic_set_pitch(sonic->sonic_hd, sonic->pitch);
sonic->need_reopen = false;
ESP_LOGD(TAG, "Open, %p", self);
return ESP_GMF_JOB_ERR_OK;
}
static esp_gmf_job_err_t esp_gmf_sonic_close(esp_gmf_element_handle_t self, void *para)
{
esp_gmf_sonic_t *sonic = (esp_gmf_sonic_t *)self;
ESP_LOGD(TAG, "Closed, %p", self);
if (sonic->sonic_hd != NULL) {
esp_ae_sonic_close(sonic->sonic_hd);
sonic->sonic_hd = NULL;
}
sonic->cur_pts = 0;
return ESP_GMF_ERR_OK;
}
static esp_gmf_job_err_t gmf_sonic_bypass_process(esp_gmf_sonic_t *sonic, esp_gmf_port_handle_t in_port, esp_gmf_port_handle_t out_port,
esp_gmf_payload_t **in_load, esp_gmf_payload_t **out_load)
{
esp_gmf_job_err_t out_len = ESP_GMF_JOB_ERR_OK;
int load_ret = esp_gmf_port_acquire_in(in_port, in_load, ESP_GMF_ELEMENT_GET(sonic)->in_attr.data_size, ESP_GMF_MAX_DELAY);
ESP_GMF_PORT_ACQUIRE_IN_CHECK(TAG, load_ret, out_len, return out_len);
sonic->is_done = (*in_load)->is_done;
if (!sonic->is_done && (*in_load)->valid_size == 0) {
return ESP_GMF_JOB_ERR_CONTINUE;
}
if (in_port->is_shared == 1) {
*out_load = *in_load;
}
size_t out_size = (*in_load)->valid_size ? (*in_load)->valid_size : ESP_GMF_ELEMENT_GET(sonic)->in_attr.data_size;
load_ret = esp_gmf_port_acquire_out(out_port, out_load, out_size, ESP_GMF_MAX_DELAY);
ESP_GMF_PORT_ACQUIRE_OUT_CHECK(TAG, load_ret, out_len, return out_len);
if (in_port->is_shared != 1) {
memcpy((*out_load)->buf, (*in_load)->buf, (*in_load)->valid_size);
(*out_load)->valid_size = (*in_load)->valid_size;
(*out_load)->is_done = (*in_load)->is_done;
(*out_load)->pts = (*in_load)->pts;
}
return sonic->is_done ? ESP_GMF_JOB_ERR_DONE : out_len;
}
static esp_gmf_job_err_t esp_gmf_sonic_process(esp_gmf_element_handle_t self, void *para)
{
esp_gmf_sonic_t *sonic = (esp_gmf_sonic_t *)self;
esp_gmf_job_err_t out_len = ESP_GMF_JOB_ERR_OK;
esp_gmf_err_io_t load_ret = ESP_GMF_IO_OK;
if (sonic->need_reopen) {
esp_gmf_sonic_close(self, NULL);
out_len = esp_gmf_sonic_open(self, NULL);
if (out_len != ESP_GMF_JOB_ERR_OK) {
ESP_LOGE(TAG, "Sonic reopen failed");
return out_len;
}
}
esp_gmf_port_handle_t in_port = ESP_GMF_ELEMENT_GET(self)->in;
esp_gmf_port_handle_t out_port = ESP_GMF_ELEMENT_GET(self)->out;
esp_gmf_payload_t *in_load = NULL;
esp_gmf_payload_t *out_load = NULL;
bool is_done = false;
sonic->out_data_hd.needed_num = sonic->out_size / sonic->bytes_per_sample;
if (sonic->speed == 1.0f && sonic->pitch == 1.0f && (sonic->out_data_hd.out_num < sonic->out_data_hd.needed_num)) {
out_len = gmf_sonic_bypass_process(sonic, in_port, out_port, &in_load, &out_load);
goto __sonic_release;
} else {
if ((sonic->out_data_hd.out_num < sonic->out_data_hd.needed_num) && sonic->is_done == false) {
int samples_num = ESP_GMF_ELEMENT_GET(sonic)->in_attr.data_size / (sonic->bytes_per_sample);
int bytes = samples_num * sonic->bytes_per_sample;
load_ret = esp_gmf_port_acquire_in(in_port, &in_load, bytes, ESP_GMF_MAX_DELAY);
samples_num = in_load->valid_size / (sonic->bytes_per_sample);
bytes = samples_num * sonic->bytes_per_sample;
sonic->is_done = in_load->is_done;
if ((bytes != in_load->valid_size) || (load_ret < ESP_GMF_IO_OK)) {
ESP_LOGE(TAG, "Invalid in load size %d, ret %d", in_load->valid_size, load_ret);
out_len = ESP_GMF_JOB_ERR_FAIL;
goto __sonic_release;
}
sonic->in_data_hd.samples = in_load->buf;
sonic->in_data_hd.num = samples_num;
sonic->cur_pts = in_load->pts;
if (sonic->in_data_hd.num == 0) {
if (sonic->is_done == true) {
out_len = ESP_GMF_JOB_ERR_DONE;
is_done = true;
} else {
out_len = ESP_GMF_JOB_ERR_CONTINUE;
goto __sonic_release;
}
}
}
load_ret = esp_gmf_port_acquire_out(out_port, &out_load, sonic->out_size, ESP_GMF_MAX_DELAY);
ESP_GMF_PORT_ACQUIRE_OUT_CHECK(TAG, load_ret, out_len, goto __sonic_release);
sonic->out_data_hd.samples = out_load->buf;
out_load->valid_size = 0;
out_load->pts = sonic->cur_pts;
if (!is_done) {
esp_gmf_oal_mutex_lock(((esp_gmf_audio_element_t *)self)->lock);
esp_ae_err_t ret = esp_ae_sonic_process(sonic->sonic_hd, &sonic->in_data_hd, &sonic->out_data_hd);
esp_gmf_oal_mutex_unlock(((esp_gmf_audio_element_t *)self)->lock);
ESP_GMF_RET_ON_ERROR(TAG, ret, {out_len = ESP_GMF_JOB_ERR_FAIL; goto __sonic_release;}, "Sonic process error %d", ret);
sonic->in_data_hd.samples = ((uint8_t *)sonic->in_data_hd.samples) + sonic->in_data_hd.consume_num * sonic->bytes_per_sample;
sonic->in_data_hd.num -= sonic->in_data_hd.consume_num;
sonic->in_data_hd.consume_num = 0;
out_load->valid_size = sonic->out_data_hd.out_num * sonic->bytes_per_sample;
if (out_load->valid_size > 0) {
esp_gmf_audio_el_update_file_pos((esp_gmf_element_handle_t)self, out_load->valid_size);
}
out_load->is_done = false;
out_load->pts = sonic->cur_pts;
sonic->cur_pts += (uint64_t)((float)(GMF_AUDIO_CALC_PTS(out_load->valid_size, sonic->sample_rate, sonic->channel, sonic->bits_per_sample)) * sonic->speed);
ESP_LOGV(TAG, "%s, I: %p-buf: %p-sz: %d, O: %p-buf: %p-sz: %d, ret: %d", __func__, in_port,
in_load->buf, in_load->valid_size, out_port,
out_load->buf, out_load->buf_length, ret);
if (sonic->out_data_hd.out_num == sonic->out_data_hd.needed_num) {
out_len = ESP_GMF_JOB_ERR_TRUNCATE;
goto __sonic_release;
}
}
if (sonic->is_done == true) {
out_load->is_done = true;
out_len = ESP_GMF_JOB_ERR_DONE;
goto __sonic_release;
}
}
__sonic_release:
if (out_load != NULL) {
load_ret = esp_gmf_port_release_out(out_port, out_load, ESP_GMF_MAX_DELAY);
if ((load_ret < ESP_GMF_IO_OK) && (load_ret != ESP_GMF_IO_ABORT)) {
ESP_LOGE(TAG, "OUT port release error, ret:%d", load_ret);
out_len = ESP_GMF_JOB_ERR_FAIL;
}
}
if (in_load != NULL) {
load_ret = esp_gmf_port_release_in(in_port, in_load, ESP_GMF_MAX_DELAY);
if ((load_ret < ESP_GMF_IO_OK) && (load_ret != ESP_GMF_IO_ABORT)) {
ESP_LOGE(TAG, "IN port release error, ret:%d", load_ret);
out_len = ESP_GMF_JOB_ERR_FAIL;
}
}
return out_len;
}
static esp_gmf_err_t sonic_received_event_handler(esp_gmf_event_pkt_t *evt, void *ctx)
{
ESP_GMF_NULL_CHECK(TAG, ctx, {return ESP_GMF_ERR_INVALID_ARG;});
ESP_GMF_NULL_CHECK(TAG, evt, {return ESP_GMF_ERR_INVALID_ARG;});
if ((evt->type != ESP_GMF_EVT_TYPE_REPORT_INFO)
|| (evt->sub != ESP_GMF_INFO_SOUND)
|| (evt->payload == NULL)) {
return ESP_GMF_ERR_OK;
}
esp_gmf_element_handle_t self = (esp_gmf_element_handle_t)ctx;
esp_gmf_element_handle_t el = evt->from;
esp_gmf_event_state_t state = ESP_GMF_EVENT_STATE_NONE;
esp_gmf_element_get_state(self, &state);
esp_gmf_info_sound_t *info = (esp_gmf_info_sound_t *)evt->payload;
esp_ae_sonic_cfg_t *config = (esp_ae_sonic_cfg_t *)OBJ_GET_CFG(self);
ESP_GMF_NULL_CHECK(TAG, config, return ESP_GMF_ERR_FAIL);
esp_gmf_sonic_t *sonic = (esp_gmf_sonic_t *)self;
sonic->need_reopen = (config->sample_rate != info->sample_rates) || (info->channels != config->channel) || (config->bits_per_sample != info->bits);
config->sample_rate = info->sample_rates;
config->channel = info->channels;
config->bits_per_sample = info->bits;
ESP_LOGD(TAG, "RECV element info, from: %s-%p, next: %p, self: %s-%p, type: %x, state: %s, rate: %d, ch: %d, bits: %d",
OBJ_GET_TAG(el), el, esp_gmf_node_for_next((esp_gmf_node_t *)el), OBJ_GET_TAG(self), self, evt->type,
esp_gmf_event_get_state_str(state), info->sample_rates, info->channels, info->bits);
if (state == ESP_GMF_EVENT_STATE_NONE) {
esp_gmf_element_set_state(self, ESP_GMF_EVENT_STATE_INITIALIZED);
}
return ESP_GMF_ERR_OK;
}
static esp_gmf_err_t esp_gmf_sonic_destroy(esp_gmf_element_handle_t self)
{
esp_gmf_sonic_t *sonic = (esp_gmf_sonic_t *)self;
ESP_LOGD(TAG, "Destroyed, %p", self);
void *cfg = OBJ_GET_CFG(self);
if (cfg) {
esp_gmf_oal_free(cfg);
}
esp_gmf_audio_el_deinit(self);
esp_gmf_oal_free(sonic);
return ESP_GMF_ERR_OK;
}
static esp_gmf_err_t _load_sonic_caps_func(esp_gmf_element_handle_t handle)
{
esp_gmf_cap_t *caps = NULL;
esp_gmf_cap_t dec_caps = {0};
dec_caps.cap_eightcc = ESP_GMF_CAPS_AUDIO_SONIC;
dec_caps.attr_fun = NULL;
int ret = esp_gmf_cap_append(&caps, &dec_caps);
ESP_GMF_RET_ON_NOT_OK(TAG, ret, {return ret;}, "Failed to create capability");
esp_gmf_element_t *el = (esp_gmf_element_t *)handle;
el->caps = caps;
return ESP_GMF_ERR_OK;
}
static esp_gmf_err_t _load_sonic_methods_func(esp_gmf_element_handle_t handle)
{
esp_gmf_method_t *method = NULL;
esp_gmf_args_desc_t *set_args = NULL;
esp_gmf_args_desc_t *get_args = NULL;
esp_gmf_err_t ret = esp_gmf_args_desc_append(&set_args, AMETHOD_ARG(SONIC, SET_SPEED, SPEED),
ESP_GMF_ARGS_TYPE_FLOAT, sizeof(float), 0);
ESP_GMF_RET_ON_NOT_OK(TAG, ret, {return ret;}, "Failed to append argument");
ret = esp_gmf_method_append(&method, AMETHOD(SONIC, SET_SPEED), __sonic_set_speed, set_args);
ESP_GMF_RET_ON_ERROR(TAG, ret, {return ret;}, "Failed to register %s method", AMETHOD(SONIC, SET_SPEED));
ret = esp_gmf_args_desc_copy(set_args, &get_args);
ESP_GMF_RET_ON_NOT_OK(TAG, ret, {return ret;}, "Failed to copy argument");
ret = esp_gmf_method_append(&method, AMETHOD(SONIC, GET_SPEED), __sonic_get_speed, get_args);
ESP_GMF_RET_ON_ERROR(TAG, ret, {return ret;}, "Failed to register %s method", AMETHOD(SONIC, GET_SPEED));
set_args = NULL;
get_args = NULL;
ret = esp_gmf_args_desc_append(&set_args, AMETHOD_ARG(SONIC, SET_PITCH, PITCH), ESP_GMF_ARGS_TYPE_FLOAT, sizeof(float), 0);
ESP_GMF_RET_ON_NOT_OK(TAG, ret, {return ret;}, "Failed to append argument");
ret = esp_gmf_method_append(&method, AMETHOD(SONIC, SET_PITCH), __sonic_set_pitch, set_args);
ESP_GMF_RET_ON_ERROR(TAG, ret, {return ret;}, "Failed to register %s method", AMETHOD(SONIC, SET_PITCH));
ret = esp_gmf_args_desc_copy(set_args, &get_args);
ESP_GMF_RET_ON_NOT_OK(TAG, ret, {return ret;}, "Failed to copy argument");
ret = esp_gmf_method_append(&method, AMETHOD(SONIC, GET_PITCH), __sonic_get_pitch, get_args);
ESP_GMF_RET_ON_ERROR(TAG, ret, {return ret;}, "Failed to register %s method", AMETHOD(SONIC, GET_PITCH));
esp_gmf_element_t *el = (esp_gmf_element_t *)handle;
el->method = method;
return ESP_GMF_ERR_OK;
}
esp_gmf_err_t esp_gmf_sonic_set_speed(esp_gmf_element_handle_t handle, float speed)
{
ESP_GMF_NULL_CHECK(TAG, handle, { return ESP_GMF_ERR_INVALID_ARG;});
esp_gmf_sonic_t *sonic = (esp_gmf_sonic_t *)handle;
if (sonic->sonic_hd) {
esp_gmf_oal_mutex_lock(((esp_gmf_audio_element_t *)handle)->lock);
esp_ae_err_t ret = esp_ae_sonic_set_speed(sonic->sonic_hd, speed);
esp_gmf_oal_mutex_unlock(((esp_gmf_audio_element_t *)handle)->lock);
ESP_GMF_RET_ON_ERROR(TAG, ret, return ESP_GMF_JOB_ERR_FAIL;, "sonicualize set error %d", ret);
}
sonic->speed = speed;
return ESP_GMF_ERR_OK;
}
esp_gmf_err_t esp_gmf_sonic_get_speed(esp_gmf_element_handle_t handle, float *speed)
{
ESP_GMF_NULL_CHECK(TAG, handle, { return ESP_GMF_ERR_INVALID_ARG;});
ESP_GMF_NULL_CHECK(TAG, speed, { return ESP_GMF_ERR_INVALID_ARG;});
esp_gmf_sonic_t *sonic = (esp_gmf_sonic_t *)handle;
if (sonic->sonic_hd) {
esp_gmf_oal_mutex_lock(((esp_gmf_audio_element_t *)handle)->lock);
esp_ae_err_t ret = esp_ae_sonic_get_speed(sonic->sonic_hd, speed);
esp_gmf_oal_mutex_unlock(((esp_gmf_audio_element_t *)handle)->lock);
ESP_GMF_RET_ON_ERROR(TAG, ret, return ESP_GMF_JOB_ERR_FAIL;, "sonicualize set error %d", ret);
} else {
*speed = sonic->speed;
}
return ESP_GMF_ERR_OK;
}
esp_gmf_err_t esp_gmf_sonic_set_pitch(esp_gmf_element_handle_t handle, float pitch)
{
ESP_GMF_NULL_CHECK(TAG, handle, { return ESP_GMF_ERR_INVALID_ARG;});
esp_gmf_sonic_t *sonic = (esp_gmf_sonic_t *)handle;
if (sonic->sonic_hd) {
esp_gmf_oal_mutex_lock(((esp_gmf_audio_element_t *)handle)->lock);
esp_ae_err_t ret = esp_ae_sonic_set_pitch(sonic->sonic_hd, pitch);
esp_gmf_oal_mutex_unlock(((esp_gmf_audio_element_t *)handle)->lock);
ESP_GMF_RET_ON_ERROR(TAG, ret, return ESP_GMF_JOB_ERR_FAIL;, "sonicualize set error %d", ret);
}
sonic->pitch = pitch;
return ESP_GMF_ERR_OK;
}
esp_gmf_err_t esp_gmf_sonic_get_pitch(esp_gmf_element_handle_t handle, float *pitch)
{
ESP_GMF_NULL_CHECK(TAG, handle, { return ESP_GMF_ERR_INVALID_ARG;});
ESP_GMF_NULL_CHECK(TAG, pitch, { return ESP_GMF_ERR_INVALID_ARG;});
esp_gmf_sonic_t *sonic = (esp_gmf_sonic_t *)handle;
if (sonic->sonic_hd) {
esp_gmf_oal_mutex_lock(((esp_gmf_audio_element_t *)handle)->lock);
esp_ae_err_t ret = esp_ae_sonic_get_pitch(sonic->sonic_hd, pitch);
esp_gmf_oal_mutex_unlock(((esp_gmf_audio_element_t *)handle)->lock);
ESP_GMF_RET_ON_ERROR(TAG, ret, return ESP_GMF_JOB_ERR_FAIL;, "sonicualize set error %d", ret);
} else {
*pitch = sonic->pitch;
}
return ESP_GMF_ERR_OK;
}
esp_gmf_err_t esp_gmf_sonic_init(esp_ae_sonic_cfg_t *config, esp_gmf_element_handle_t *handle)
{
ESP_GMF_NULL_CHECK(TAG, handle, {return ESP_GMF_ERR_INVALID_ARG;});
*handle = NULL;
esp_gmf_err_t ret = ESP_GMF_ERR_OK;
esp_gmf_sonic_t *sonic = esp_gmf_oal_calloc(1, sizeof(esp_gmf_sonic_t));
ESP_GMF_MEM_VERIFY(TAG, sonic, {return ESP_GMF_ERR_MEMORY_LACK;}, "sonic", sizeof(esp_gmf_sonic_t));
esp_gmf_obj_t *obj = (esp_gmf_obj_t *)sonic;
obj->new_obj = esp_gmf_sonic_new;
obj->del_obj = esp_gmf_sonic_destroy;
esp_ae_sonic_cfg_t *cfg = esp_gmf_oal_calloc(1, sizeof(esp_ae_sonic_cfg_t));
ESP_GMF_MEM_VERIFY(TAG, cfg, ret = ESP_GMF_ERR_MEMORY_LACK; goto SONIC_INIT_FAIL;, "sonic configuration", sizeof(esp_ae_sonic_cfg_t));
esp_gmf_obj_set_config(obj, cfg, sizeof(esp_ae_sonic_cfg_t));
if (config) {
memcpy(cfg, config, sizeof(esp_ae_sonic_cfg_t));
} else {
esp_ae_sonic_cfg_t dcfg = DEFAULT_ESP_GMF_SONIC_CONFIG();
memcpy(cfg, &dcfg, sizeof(esp_ae_sonic_cfg_t));
}
ret = esp_gmf_obj_set_tag(obj, "aud_sonic");
ESP_GMF_RET_ON_NOT_OK(TAG, ret, goto SONIC_INIT_FAIL, "Failed to set obj tag");
sonic->speed = 1.0f;
sonic->pitch = 1.0f;
esp_gmf_element_cfg_t el_cfg = {0};
ESP_GMF_ELEMENT_IN_PORT_ATTR_SET(el_cfg.in_attr, ESP_GMF_EL_PORT_CAP_SINGLE, 0, 0,
ESP_GMF_PORT_TYPE_BLOCK | ESP_GMF_PORT_TYPE_BYTE, ESP_GMF_ELEMENT_PORT_DATA_SIZE_DEFAULT);
ESP_GMF_ELEMENT_IN_PORT_ATTR_SET(el_cfg.out_attr, ESP_GMF_EL_PORT_CAP_SINGLE, 0, 0,
ESP_GMF_PORT_TYPE_BLOCK | ESP_GMF_PORT_TYPE_BYTE, ESP_GMF_ELEMENT_PORT_DATA_SIZE_DEFAULT);
el_cfg.dependency = true;
ret = esp_gmf_audio_el_init(sonic, &el_cfg);
ESP_GMF_RET_ON_NOT_OK(TAG, ret, goto SONIC_INIT_FAIL, "Failed to initialize sonic element");
ESP_GMF_ELEMENT_GET(sonic)->ops.open = esp_gmf_sonic_open;
ESP_GMF_ELEMENT_GET(sonic)->ops.process = esp_gmf_sonic_process;
ESP_GMF_ELEMENT_GET(sonic)->ops.close = esp_gmf_sonic_close;
ESP_GMF_ELEMENT_GET(sonic)->ops.event_receiver = sonic_received_event_handler;
ESP_GMF_ELEMENT_GET(sonic)->ops.load_caps = _load_sonic_caps_func;
ESP_GMF_ELEMENT_GET(sonic)->ops.load_methods = _load_sonic_methods_func;
*handle = obj;
ESP_LOGD(TAG, "Initialization, %s-%p", OBJ_GET_TAG(obj), obj);
return ESP_GMF_ERR_OK;
SONIC_INIT_FAIL:
esp_gmf_sonic_destroy(obj);
return ret;
}
@@ -0,0 +1,19 @@
version: "0.7.2"
description: Espressif GMF Audio Elements Module
url: https://github.com/espressif/esp-gmf/tree/main/elements/gmf_audio
documentation: "https://github.com/espressif/esp-gmf/blob/main/elements/gmf_audio/README.md"
issues: "https://github.com/espressif/esp-gmf/issues"
tags:
- "gmf"
dependencies:
espressif/esp_audio_codec:
require: public
version: "~2.3"
espressif/esp_audio_effects:
require: public
version: "~1.1"
espressif/gmf_core:
require: public
version: "^0.7"
@@ -0,0 +1,72 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO., LTD
* SPDX-License-Identifier: LicenseRef-Espressif-Modified-MIT
*
* See LICENSE file for details.
*/
#pragma once
#include "esp_gmf_err.h"
#include "esp_gmf_element.h"
#include "esp_ae_alc.h"
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
#define DEFAULT_ESP_GMF_ALC_CONFIG() { \
.sample_rate = 48000, \
.channel = 2, \
.bits_per_sample = 16, \
}
/**
* @brief Initializes the GMF ALC with the provided configuration
*
* @param[in] config Pointer to the ALC configuration
* @param[out] handle Pointer to the ALC handle to be initialized
*
* @return
* - ESP_GMF_ERR_OK Success
* - ESP_GMF_ERR_INVALID_ARG Invalid configuration provided
* - ESP_GMF_ERR_MEMORY_LACK Failed to allocate memory
*/
esp_gmf_err_t esp_gmf_alc_init(esp_ae_alc_cfg_t *config, esp_gmf_element_handle_t *handle);
/**
* @brief Set specific channel gain of the ALC handle.
* Positive gain indicates an increase in volume,
* negative gain indicates a decrease in volume.
* 0 gain indicates the volume level remains unchanged.
*
* @param[in] handle The ALC handle
* @param[in] idx The channel index of the gain to retrieve. eg: 0 refers to the first channel
* @param[in] gain The gain value needs to conform to the following conditions:
* - Supported range [-64, 63]
* - Below -64 will set to mute
* - Higher than 63 not supported
* Unit: dB
*
* @return
* - ESP_GMF_ERR_OK Operation succeeded
* - ESP_GMF_ERR_INVALID_ARG Invalid input parameter
*/
esp_gmf_err_t esp_gmf_alc_set_gain(esp_gmf_element_handle_t handle, uint8_t idx, int8_t gain);
/**
* @brief Get the gain for a specific channel from the ALC handle
*
* @param[in] handle The ALC handle
* @param[in] ch_idx The channel index of the gain to retrieve. eg: 0 refers to the first channel
* @param[out] gain Pointer to store the retrieved gain. Unit: dB
*
* @return
* - ESP_GMF_ERR_OK Operation succeeded
* - ESP_GMF_ERR_INVALID_ARG Invalid input parameter
*/
esp_gmf_err_t esp_gmf_alc_get_gain(esp_gmf_element_handle_t handle, uint8_t idx, int8_t *gain);
#ifdef __cplusplus
}
#endif /* __cplusplus */
@@ -0,0 +1,75 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO., LTD
* SPDX-License-Identifier: LicenseRef-Espressif-Modified-MIT
*
* See LICENSE file for details.
*/
#pragma once
#include "esp_audio_simple_dec.h"
#include "esp_gmf_element.h"
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
#define DEFAULT_ESP_GMF_AUDIO_DEC_CONFIG() { \
.dec_type = ESP_AUDIO_SIMPLE_DEC_TYPE_NONE, \
.dec_cfg = NULL, \
.cfg_size = 0, \
}
/**
* @brief Initializes the GMF audio decoder with the provided configuration
*
* @param[in] config Pointer to the audio decoder configuration
* @param[out] handle Pointer to the audio decoder handle to be initialized
*
* @return
* - ESP_GMF_ERR_OK Success
* - ESP_GMF_ERR_INVALID_ARG Invalid configuration provided
* - ESP_GMF_ERR_MEMORY_LACK Failed to allocate memory
*/
esp_gmf_err_t esp_gmf_audio_dec_init(esp_audio_simple_dec_cfg_t *config, esp_gmf_element_handle_t *handle);
/**
* @brief Reconfigure the GMF audio decoder and filled into provided configuration
*
* @note Only allowed when not running, i.e., in the `ESP_GMF_EVENT_STATE_NONE` or `ESP_GMF_EVENT_STATE_INITIALIZED` state
*
* @param[in] handle Audio decoder handle to be reconfigured
* @param[in] config Pointer to the new simple decoder configuration
*
* @return
* - ESP_GMF_ERR_OK Success
* - ESP_GMF_ERR_INVALID_ARG Invalid handle or configuration provided
* - ESP_GMF_ERR_MEMORY_LACK Failed to allocate memory
*/
esp_gmf_err_t esp_gmf_audio_dec_reconfig(esp_gmf_element_handle_t handle, esp_audio_simple_dec_cfg_t *config);
/**
* @brief Reconfigures the GMF audio decoder with default configuration based on provided sound information
*
* @note 1. Only allowed when not running, i.e., in the `ESP_GMF_EVENT_STATE_NONE` or `ESP_GMF_EVENT_STATE_INITIALIZED` state
* 2. The actual reconfiguration behavior depends on the relationship between the provided `sound info` and
* the current decoder configuration (`cfg`):
* - If the `format_id` in `sound info` matches the decoder's current `type`, and the decoder's sub-config (`sub cfg`) is not NULL,
* only the basic fields in the sub-config will be updated with information from `sound info` (e.g., sample rate, channel count).
* - If the `format_id` differs from the current `type`, or if the sub-config is NULL,
* the decoder will be reconfigured using the **default configuration** for the specified `format_id`,
* and the current decoder `type` will be updated accordingly.
*
* @param[in] handle Audio decoder handle to be reconfigured
* @param[in] info Sound information to be configured
*
* @return
* - ESP_GMF_ERR_OK Success
* - ESP_GMF_ERR_INVALID_ARG Invalid handle or decoder type provided
* - ESP_GMF_ERR_MEMORY_LACK Failed to allocate memory
*/
esp_gmf_err_t esp_gmf_audio_dec_reconfig_by_sound_info(esp_gmf_element_handle_t handle, esp_gmf_info_sound_t *info);
#ifdef __cplusplus
}
#endif /* __cplusplus */
@@ -0,0 +1,113 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO., LTD
* SPDX-License-Identifier: LicenseRef-Espressif-Modified-MIT
*
* See LICENSE file for details.
*/
#pragma once
#include "esp_gmf_err.h"
#include "esp_gmf_element.h"
#include "encoder/esp_audio_enc.h"
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
#define DEFAULT_ESP_GMF_AUDIO_ENC_CONFIG() { \
.type = ESP_AUDIO_TYPE_UNSUPPORT, \
.cfg = NULL, \
.cfg_sz = 0, \
}
/**
* @brief Initializes the GMF audio encoder with the provided configuration
*
* @param[in] config Pointer to the audio encoder configuration
* @param[out] handle Pointer to the audio encoder handle to be initialized
*
* @return
* - ESP_GMF_ERR_OK Success
* - ESP_GMF_ERR_INVALID_ARG Invalid configuration provided
* - ESP_GMF_ERR_MEMORY_LACK Failed to allocate memory
*/
esp_gmf_err_t esp_gmf_audio_enc_init(esp_audio_enc_config_t *config, esp_gmf_element_handle_t *handle);
/**
* @brief Get the input and output frame size information of the GMF audio encoder
*
* @param[in] handle Audio encoder handle
* @param[out] in_size Pointer to store input frame size
* @param[out] out_size Pointer to store suggested output frame size
*
* @return
* - ESP_GMF_ERR_OK Success
* - ESP_GMF_ERR_INVALID_ARG Invalid handle or size pointers provided
*/
esp_gmf_err_t esp_gmf_audio_enc_get_frame_size(esp_gmf_element_handle_t handle, uint32_t *in_size, uint32_t *out_size);
/**
* @brief Set the bitrate for the GMF audio encoder
*
* @param[in] handle Audio encoder handle
* @param[in] bitrate The bitrate of the encoder
*
* @return
* - ESP_GMF_ERR_OK Success
* - ESP_GMF_ERR_INVALID_ARG Invalid handle or bitrate value provided
*/
esp_gmf_err_t esp_gmf_audio_enc_set_bitrate(esp_gmf_element_handle_t handle, uint32_t bitrate);
/**
* @brief Get the current bitrate of the GMF audio encoder
*
* @param[in] handle Audio encoder handle
* @param[out] bitrate Pointer to store the current bitrate
*
* @return
* - ESP_GMF_ERR_OK Success
* - ESP_GMF_ERR_INVALID_ARG Invalid handle or bitrate pointer provided
*/
esp_gmf_err_t esp_gmf_audio_enc_get_bitrate(esp_gmf_element_handle_t handle, uint32_t *bitrate);
/**
* @brief Reconfigure the GMF audio encoder and filled into provided configuration
*
* @note Only allowed when not running, i.e., in the `ESP_GMF_EVENT_STATE_NONE` or `ESP_GMF_EVENT_STATE_INITIALIZED` state
*
* @param[in] handle Audio encoder handle to be reconfigured
* @param[in] config Pointer to the new audio encoder configuration
*
* @return
* - ESP_GMF_ERR_OK Success
* - ESP_GMF_ERR_INVALID_ARG Invalid handle or configuration provided
* - ESP_GMF_ERR_MEMORY_LACK Failed to allocate memory
*/
esp_gmf_err_t esp_gmf_audio_enc_reconfig(esp_gmf_element_handle_t handle, esp_audio_enc_config_t *config);
/**
* @brief Reconfigures the GMF audio encoder with default configuration for the sound information
*
* @note 1. Only allowed when not running, i.e., in the `ESP_GMF_EVENT_STATE_NONE` or `ESP_GMF_EVENT_STATE_INITIALIZED` state
* 2. The actual reconfiguration behavior depends on the relationship between the provided `sound info` and
* the current encoder configuration (`cfg`):
* - If the `format_id` in `sound info` matches the encoder's current `type`, and the encoder's sub-config (`sub cfg`) is not NULL,
* only the basic fields in the sub-config will be updated with information from `sound info` (e.g., sample rate, channel count).
* - If the `format_id` differs from the current `type`, or if the sub-config is NULL,
* the encoder will be reconfigured using the **default configuration** for the specified `format_id`,
* and the current encoder `type` will be updated accordingly.
*
* @param[in] handle Audio encoder handle to be reconfigured
* @param[in] info Sound information to be configured
*
* @return
* - ESP_GMF_ERR_OK Success
* - ESP_GMF_ERR_INVALID_ARG Invalid handle or encoder type provided
* - ESP_GMF_ERR_MEMORY_LACK Failed to allocate memory
*/
esp_gmf_err_t esp_gmf_audio_enc_reconfig_by_sound_info(esp_gmf_element_handle_t handle, esp_gmf_info_sound_t *info);
#ifdef __cplusplus
}
#endif /* __cplusplus */
@@ -0,0 +1,30 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO., LTD
* SPDX-License-Identifier: LicenseRef-Espressif-Modified-MIT
*
* See LICENSE file for details.
*/
#pragma once
#include "esp_gmf_err.h"
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
/**
* @brief Get audio codec type by uri
*
* @param[in] uri URI of audio codec
* @param[out] format_id Type of audio codec(ESP_FourCC type)
*
* @return
* - ESP_GMF_ERR_OK Success
* - ESP_GMF_ERR_NOT_SUPPORT Not supported audio codec type
*/
esp_gmf_err_t esp_gmf_audio_helper_get_audio_type_by_uri(const char *uri, uint32_t *format_id);
#ifdef __cplusplus
}
#endif /* __cplusplus */
@@ -0,0 +1,217 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO., LTD
* SPDX-License-Identifier: LicenseRef-Espressif-Modified-MIT
*
* See LICENSE file for details.
*/
#pragma once
#include "esp_gmf_method_helper.h"
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
/**
* @brief Audio general method argument definition
*/
#define ESP_GMF_AUDIO_INDEX_ARG "index"
#define ESP_GMF_AUDIO_GAIN_ARG "gain"
#define ESP_GMF_AUDIO_BITS_ARG "bits"
#define ESP_GMF_AUDIO_CHANNEL_ARG "channel"
#define ESP_GMF_AUDIO_SAMPLE_RATE_ARG "sample_rate"
#define ESP_GMF_AUDIO_BITRATE_ARG "bitrate"
#define ESP_GMF_AUDIO_ENABLE_ARG "enable"
#define ESP_GMF_AUDIO_TYPE_ARG "type"
#define ESP_GMF_AUDIO_PARA_ARG "para"
#define ESP_GMF_AUDIO_MODE_ARG "mode"
#define ESP_GMF_AUDIO_SPEED_ARG "speed"
#define ESP_GMF_AUDIO_PITCH_ARG "pitch"
#define ESP_GMF_AUDIO_INSIZE_ARG "in_size"
#define ESP_GMF_AUDIO_OUTSIZE_ARG "out_size"
#define ESP_GMF_AUDIO_CFG_ARG "cfg"
#define ESP_GMF_AUDIO_INFO_ARG "info"
/**
* @brief Audio EQ parameter argument definition
*/
#define ESP_GMF_AUDIO_EQ_PARA_ARG ESP_GMF_AUDIO_PARA_ARG
#define ESP_GMF_AUDIO_EQ_PARA_FT_ARG ESP_GMF_AUDIO_TYPE_ARG
#define ESP_GMF_AUDIO_EQ_PARA_FC_ARG "fc"
#define ESP_GMF_AUDIO_EQ_PARA_Q_ARG "q"
#define ESP_GMF_AUDIO_EQ_PARA_GAIN_ARG ESP_GMF_AUDIO_GAIN_ARG
/**
* @brief Audio general config parameter argument definition
*/
#define ESP_GMF_AUDIO_CFG_TYPE_ARG ESP_GMF_AUDIO_TYPE_ARG
#define ESP_GMF_AUDIO_CFG_SUBCFGPTR_ARG "sub_cfg"
#define ESP_GMF_AUDIO_CFG_SUBCFGSZ_ARG "sub_cfg_sz"
/**
* @brief Audio sound information parameter argument definition
*/
#define ESP_GMF_AUDIO_SND_INFO_ARG ESP_GMF_AUDIO_INFO_ARG
#define ESP_GMF_AUDIO_SND_INFO_TYPE_ARG ESP_GMF_AUDIO_TYPE_ARG
#define ESP_GMF_AUDIO_SND_INFO_SAMPLERATE_ARG ESP_GMF_AUDIO_SAMPLE_RATE_ARG
#define ESP_GMF_AUDIO_SND_INFO_BITRATE_ARG ESP_GMF_AUDIO_BITRATE_ARG
#define ESP_GMF_AUDIO_SND_INFO_CHANNEL_ARG ESP_GMF_AUDIO_CHANNEL_ARG
#define ESP_GMF_AUDIO_SND_INFO_BITS_ARG ESP_GMF_AUDIO_BITS_ARG
/**
* @brief GMF audio method definition
*/
#define AMETHOD_DEF(module, method, str) ESP_GMF_METHOD_DEF(AUDIO, module, method, str)
/**
* @brief Get GMF audio method string
*/
#define AMETHOD(module, method) ESP_GMF_METHOD_STR(AUDIO, module, method)
/**
* @brief GMF audio method argument definition
*/
#define AMETHOD_ARG_DEF(module, method, arg, str) ESP_GMF_METHOD_ARG_DEF(AUDIO, module, method, arg, str)
/**
* @brief Get GMF audio method argument string
*/
#define AMETHOD_ARG(module, method, arg) ESP_GMF_METHOD_ARG_STR(AUDIO, module, method, arg)
/**
* @brief ALC method
*/
AMETHOD_DEF(ALC, SET_GAIN, "set_gain");
AMETHOD_ARG_DEF(ALC, SET_GAIN, IDX, ESP_GMF_AUDIO_INDEX_ARG);
AMETHOD_ARG_DEF(ALC, SET_GAIN, GAIN, ESP_GMF_AUDIO_GAIN_ARG);
AMETHOD_DEF(ALC, GET_GAIN, "get_gain");
AMETHOD_ARG_DEF(ALC, GET_GAIN, IDX, ESP_GMF_AUDIO_INDEX_ARG);
AMETHOD_ARG_DEF(ALC, GET_GAIN, GAIN, ESP_GMF_AUDIO_GAIN_ARG);
/**
* @brief BIT CVT method
*/
AMETHOD_DEF(BIT_CVT, SET_DEST_BITS, "set_dest_bits");
AMETHOD_ARG_DEF(BIT_CVT, SET_DEST_BITS, BITS, ESP_GMF_AUDIO_BITS_ARG);
/**
* @brief CH CVT method
*/
AMETHOD_DEF(CH_CVT, SET_DEST_CH, "set_dest_ch");
AMETHOD_ARG_DEF(CH_CVT, SET_DEST_CH, CH, ESP_GMF_AUDIO_CHANNEL_ARG);
/**
* @brief RATE CVT method
*/
AMETHOD_DEF(RATE_CVT, SET_DEST_RATE, "set_dest_rate");
AMETHOD_ARG_DEF(RATE_CVT, SET_DEST_RATE, RATE, ESP_GMF_AUDIO_SAMPLE_RATE_ARG);
/**
* @brief EQ method
*/
AMETHOD_DEF(EQ, SET_PARA, "set_para");
AMETHOD_ARG_DEF(EQ, SET_PARA, IDX, ESP_GMF_AUDIO_INDEX_ARG);
AMETHOD_ARG_DEF(EQ, SET_PARA, PARA, ESP_GMF_AUDIO_EQ_PARA_ARG);
AMETHOD_ARG_DEF(EQ, SET_PARA, PARA_FT, ESP_GMF_AUDIO_EQ_PARA_FT_ARG);
AMETHOD_ARG_DEF(EQ, SET_PARA, PARA_FC, ESP_GMF_AUDIO_EQ_PARA_FC_ARG);
AMETHOD_ARG_DEF(EQ, SET_PARA, PARA_Q, ESP_GMF_AUDIO_EQ_PARA_Q_ARG);
AMETHOD_ARG_DEF(EQ, SET_PARA, PARA_GAIN, ESP_GMF_AUDIO_EQ_PARA_GAIN_ARG);
AMETHOD_DEF(EQ, GET_PARA, "get_para");
AMETHOD_ARG_DEF(EQ, GET_PARA, IDX, ESP_GMF_AUDIO_INDEX_ARG);
AMETHOD_ARG_DEF(EQ, GET_PARA, PARA, ESP_GMF_AUDIO_EQ_PARA_ARG);
AMETHOD_ARG_DEF(EQ, GET_PARA, PARA_FT, ESP_GMF_AUDIO_EQ_PARA_FT_ARG);
AMETHOD_ARG_DEF(EQ, GET_PARA, PARA_FC, ESP_GMF_AUDIO_EQ_PARA_FC_ARG);
AMETHOD_ARG_DEF(EQ, GET_PARA, PARA_Q, ESP_GMF_AUDIO_EQ_PARA_Q_ARG);
AMETHOD_ARG_DEF(EQ, GET_PARA, PARA_GAIN, ESP_GMF_AUDIO_EQ_PARA_GAIN_ARG);
AMETHOD_DEF(EQ, ENABLE_FILTER, "enable_filter");
AMETHOD_ARG_DEF(EQ, ENABLE_FILTER, IDX, ESP_GMF_AUDIO_INDEX_ARG);
AMETHOD_ARG_DEF(EQ, ENABLE_FILTER, ENABLE, ESP_GMF_AUDIO_ENABLE_ARG);
/**
* @brief FADE method
*/
AMETHOD_DEF(FADE, SET_MODE, "set_mode");
AMETHOD_ARG_DEF(FADE, SET_MODE, MODE, ESP_GMF_AUDIO_MODE_ARG);
AMETHOD_DEF(FADE, GET_MODE, "get_mode");
AMETHOD_ARG_DEF(FADE, GET_MODE, MODE, ESP_GMF_AUDIO_MODE_ARG);
AMETHOD_DEF(FADE, RESET_WEIGHT, "reset_weight");
/**
* @brief MIXER method
*/
AMETHOD_DEF(MIXER, SET_MODE, "set_mode");
AMETHOD_ARG_DEF(MIXER, SET_MODE, IDX, ESP_GMF_AUDIO_INDEX_ARG);
AMETHOD_ARG_DEF(MIXER, SET_MODE, MODE, ESP_GMF_AUDIO_MODE_ARG);
AMETHOD_DEF(MIXER, SET_INFO, "set_info");
AMETHOD_ARG_DEF(MIXER, SET_INFO, RATE, ESP_GMF_AUDIO_SAMPLE_RATE_ARG);
AMETHOD_ARG_DEF(MIXER, SET_INFO, CH, ESP_GMF_AUDIO_CHANNEL_ARG);
AMETHOD_ARG_DEF(MIXER, SET_INFO, BITS, ESP_GMF_AUDIO_BITS_ARG);
/**
* @brief SONIC method
*/
AMETHOD_DEF(SONIC, SET_SPEED, "set_speed");
AMETHOD_ARG_DEF(SONIC, SET_SPEED, SPEED, ESP_GMF_AUDIO_SPEED_ARG);
AMETHOD_DEF(SONIC, GET_SPEED, "get_speed");
AMETHOD_ARG_DEF(SONIC, GET_SPEED, SPEED, ESP_GMF_AUDIO_SPEED_ARG);
AMETHOD_DEF(SONIC, SET_PITCH, "set_pitch");
AMETHOD_ARG_DEF(SONIC, SET_PITCH, PITCH, ESP_GMF_AUDIO_PITCH_ARG);
AMETHOD_DEF(SONIC, GET_PITCH, "get_pitch");
AMETHOD_ARG_DEF(SONIC, GET_PITCH, PITCH, ESP_GMF_AUDIO_PITCH_ARG);
/**
* @brief ENCODER method
*/
AMETHOD_DEF(ENCODER, GET_FRAME_SZ, "get_frame_sz");
AMETHOD_ARG_DEF(ENCODER, GET_FRAME_SZ, INSIZE, ESP_GMF_AUDIO_INSIZE_ARG);
AMETHOD_ARG_DEF(ENCODER, GET_FRAME_SZ, OUTSIZE, ESP_GMF_AUDIO_OUTSIZE_ARG);
AMETHOD_DEF(ENCODER, SET_BITRATE, "set_bitrate");
AMETHOD_ARG_DEF(ENCODER, SET_BITRATE, BITRATE, ESP_GMF_AUDIO_BITRATE_ARG);
AMETHOD_DEF(ENCODER, GET_BITRATE, "get_bitrate");
AMETHOD_ARG_DEF(ENCODER, GET_BITRATE, BITRATE, ESP_GMF_AUDIO_BITRATE_ARG);
AMETHOD_DEF(ENCODER, RECONFIG, "recfg");
AMETHOD_ARG_DEF(ENCODER, RECONFIG, CFG, ESP_GMF_AUDIO_CFG_ARG);
AMETHOD_ARG_DEF(ENCODER, RECONFIG, CFG_TYPE, ESP_GMF_AUDIO_CFG_TYPE_ARG);
AMETHOD_ARG_DEF(ENCODER, RECONFIG, CFG_SUBCFGPTR, ESP_GMF_AUDIO_CFG_SUBCFGPTR_ARG);
AMETHOD_ARG_DEF(ENCODER, RECONFIG, CFG_SUBCFGSZ, ESP_GMF_AUDIO_CFG_SUBCFGSZ_ARG);
AMETHOD_DEF(ENCODER, RECONFIG_BY_SND_INFO, "recfg_by_sndinfo");
AMETHOD_ARG_DEF(ENCODER, RECONFIG_BY_SND_INFO, INFO, ESP_GMF_AUDIO_SND_INFO_ARG);
AMETHOD_ARG_DEF(ENCODER, RECONFIG_BY_SND_INFO, INFO_TYPE, ESP_GMF_AUDIO_SND_INFO_TYPE_ARG);
AMETHOD_ARG_DEF(ENCODER, RECONFIG_BY_SND_INFO, INFO_SAMPLERATE, ESP_GMF_AUDIO_SND_INFO_SAMPLERATE_ARG);
AMETHOD_ARG_DEF(ENCODER, RECONFIG_BY_SND_INFO, INFO_BITRATE, ESP_GMF_AUDIO_SND_INFO_BITRATE_ARG);
AMETHOD_ARG_DEF(ENCODER, RECONFIG_BY_SND_INFO, INFO_CHANNEL, ESP_GMF_AUDIO_SND_INFO_CHANNEL_ARG);
AMETHOD_ARG_DEF(ENCODER, RECONFIG_BY_SND_INFO, INFO_BITS, ESP_GMF_AUDIO_SND_INFO_BITS_ARG);
/**
* @brief DECODER method
*/
AMETHOD_DEF(DECODER, RECONFIG, "recfg");
AMETHOD_ARG_DEF(DECODER, RECONFIG, CFG, ESP_GMF_AUDIO_CFG_ARG);
AMETHOD_ARG_DEF(DECODER, RECONFIG, CFG_TYPE, ESP_GMF_AUDIO_CFG_TYPE_ARG);
AMETHOD_ARG_DEF(DECODER, RECONFIG, CFG_SUBCFGPTR, ESP_GMF_AUDIO_CFG_SUBCFGPTR_ARG);
AMETHOD_ARG_DEF(DECODER, RECONFIG, CFG_SUBCFGSZ, ESP_GMF_AUDIO_CFG_SUBCFGSZ_ARG);
AMETHOD_DEF(DECODER, RECONFIG_BY_SND_INFO, "recfg_by_sndinfo");
AMETHOD_ARG_DEF(DECODER, RECONFIG_BY_SND_INFO, INFO, ESP_GMF_AUDIO_SND_INFO_ARG);
AMETHOD_ARG_DEF(DECODER, RECONFIG_BY_SND_INFO, INFO_TYPE, ESP_GMF_AUDIO_SND_INFO_TYPE_ARG);
AMETHOD_ARG_DEF(DECODER, RECONFIG_BY_SND_INFO, INFO_SAMPLERATE, ESP_GMF_AUDIO_SND_INFO_SAMPLERATE_ARG);
AMETHOD_ARG_DEF(DECODER, RECONFIG_BY_SND_INFO, INFO_CHANNEL, ESP_GMF_AUDIO_SND_INFO_CHANNEL_ARG);
AMETHOD_ARG_DEF(DECODER, RECONFIG_BY_SND_INFO, INFO_BITS, ESP_GMF_AUDIO_SND_INFO_BITS_ARG);
#ifdef __cplusplus
}
#endif /* __cplusplus */
@@ -0,0 +1,128 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO., LTD
* SPDX-License-Identifier: LicenseRef-Espressif-Modified-MIT
*
* See LICENSE file for details.
*/
#pragma once
#include "esp_gmf_element.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Set frame destination sample rate for audio element
*
* @param[in] handle Audio element handle
* @param[in] dest_rate Destination sample rate
*
* @return
* - ESP_GMF_ERR_OK On success
* - ESP_GMF_ERR_NOT_FOUND Not found the method
* - ESP_GMF_ERR_MEMORY_LACK Failed to allocate memory
* - Others Failed to apply method
*/
esp_gmf_err_t esp_gmf_audio_param_set_dest_rate(esp_gmf_element_handle_t self, uint32_t dest_rate);
/**
* @brief Set frame destination bits for audio element
*
* @param[in] handle Audio element handle
* @param[in] dest_bits Destination bits
*
* @return
* - ESP_GMF_ERR_OK On success
* - ESP_GMF_ERR_NOT_FOUND Not found the method
* - ESP_GMF_ERR_MEMORY_LACK Failed to allocate memory
* - Others Failed to apply method
*/
esp_gmf_err_t esp_gmf_audio_param_set_dest_bits(esp_gmf_element_handle_t self, uint8_t dest_bits);
/**
* @brief Set frame destination channel for audio element
*
* @param[in] handle Audio element handle
* @param[in] dest_ch Destination channel
*
* @return
* - ESP_GMF_ERR_OK On success
* - ESP_GMF_ERR_NOT_FOUND Not found the method
* - ESP_GMF_ERR_MEMORY_LACK Failed to allocate memory
* - Others Failed to apply method
*/
esp_gmf_err_t esp_gmf_audio_param_set_dest_ch(esp_gmf_element_handle_t self, uint8_t dest_ch);
/**
* @brief Set speed for audio element
*
* @param[in] handle Audio element handle
* @param[in] speed Audio playback speed
*
* @return
* - ESP_GMF_ERR_OK On success
* - ESP_GMF_ERR_NOT_FOUND Not found the method
* - ESP_GMF_ERR_MEMORY_LACK Failed to allocate memory
* - Others Failed to apply method
*/
esp_gmf_err_t esp_gmf_audio_param_set_speed(esp_gmf_element_handle_t self, float speed);
/**
* @brief Set pitch for audio element
*
* @param[in] handle Audio element handle
* @param[in] pitch Audio pitch to set
*
* @return
* - ESP_GMF_ERR_OK On success
* - ESP_GMF_ERR_NOT_FOUND Not found the method
* - ESP_GMF_ERR_MEMORY_LACK Failed to allocate memory
* - Others Failed to apply method
*/
esp_gmf_err_t esp_gmf_audio_param_set_pitch(esp_gmf_element_handle_t self, float pitch);
/**
* @brief Set ALC certain channel gain for audio element
*
* @param[in] handle Audio element handle
* @param[in] ch_idx Channel index (0: Left channel 1: Right Channel 0xFF: All channels)
* @param[in] gain_db ALC gain (unit decibel)
*
* @return
* - ESP_GMF_ERR_OK On success
* - ESP_GMF_ERR_NOT_FOUND Not found the method
* - ESP_GMF_ERR_MEMORY_LACK Failed to allocate memory
* - Others Failed to apply method
*/
esp_gmf_err_t esp_gmf_audio_param_set_alc_channel_gain(esp_gmf_element_handle_t self, uint8_t ch_idx, float gain_db);
/**
* @brief Set ALC gain for all channels
*/
#define esp_gmf_audio_param_set_alc_gain(self, gain_db) esp_gmf_audio_param_set_alc_channel_gain(self, 0xFF, gain_db)
/**
* @brief Set fade in/out direction for audio element
*
* @param[in] handle Audio element handle
* @param[in] is_fade_in Whether fade in or out
*
* @return
* - ESP_GMF_ERR_OK On success
* - ESP_GMF_ERR_NOT_FOUND Not found the method
* - ESP_GMF_ERR_MEMORY_LACK Failed to allocate memory
* - Others Failed to apply method
*/
esp_gmf_err_t esp_gmf_audio_param_set_fade(esp_gmf_element_handle_t self, bool is_fade_in);
/**
* @brief Helper wrapper for fade in and fade out setting
*/
#define esp_gmf_audio_param_set_fade_in(self) esp_gmf_audio_param_set_fade(self, true);
#define esp_gmf_audio_param_set_fade_out(self) esp_gmf_audio_param_set_fade(self, false);
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,53 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO., LTD
* SPDX-License-Identifier: LicenseRef-Espressif-Modified-MIT
*
* See LICENSE file for details.
*/
#pragma once
#include "esp_gmf_err.h"
#include "esp_ae_bit_cvt.h"
#include "esp_gmf_element.h"
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
#define DEFAULT_ESP_GMF_BIT_CVT_CONFIG() { \
.sample_rate = 48000, \
.channel = 2, \
.src_bits = 16, \
.dest_bits = 16, \
}
/**
* @brief Initializes the GMF bit conversion with the provided configuration
*
* @param[in] config Pointer to the bit conversion configuration
* @param[out] handle Pointer to the bit conversion handle to be initialized
*
* @return
* - ESP_GMF_ERR_OK Success
* - ESP_GMF_ERR_INVALID_ARG Invalid configuration provided
* - ESP_GMF_ERR_MEMORY_LACK Failed to allocate memory
*/
esp_gmf_err_t esp_gmf_bit_cvt_init(esp_ae_bit_cvt_cfg_t *config, esp_gmf_element_handle_t *handle);
/**
* @brief Set destination bits for the bit conversion handle
*
* @param[in] handle The channel bit handle
* @param[in] dest_bits The destination bits
*
* @return
* - ESP_GMF_ERR_OK Operation succeeded
* - ESP_GMF_ERR_INVALID_ARG Invalid input parameter
* - ESP_GMF_ERR_FAIL Failed to set configuration
*/
esp_gmf_err_t esp_gmf_bit_cvt_set_dest_bits(esp_gmf_element_handle_t handle, uint8_t dest_bits);
#ifdef __cplusplus
}
#endif /* __cplusplus */
@@ -0,0 +1,55 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO., LTD
* SPDX-License-Identifier: LicenseRef-Espressif-Modified-MIT
*
* See LICENSE file for details.
*/
#pragma once
#include "esp_gmf_err.h"
#include "esp_ae_ch_cvt.h"
#include "esp_gmf_element.h"
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
#define DEFAULT_ESP_GMF_CH_CVT_CONFIG() { \
.sample_rate = 48000, \
.bits_per_sample = 16, \
.src_ch = 2, \
.dest_ch = 2, \
.weight = NULL, \
.weight_len = 0, \
}
/**
* @brief Initializes the GMF channel conversion with the provided configuration
*
* @param[in] config Pointer to the channel conversion configuration
* @param[out] handle Pointer to the channel conversion handle to be initialized
*
* @return
* - ESP_GMF_ERR_OK Success
* - ESP_GMF_ERR_INVALID_ARG Invalid configuration provided
* - ESP_GMF_ERR_MEMORY_LACK Failed to allocate memory
*/
esp_gmf_err_t esp_gmf_ch_cvt_init(esp_ae_ch_cvt_cfg_t *config, esp_gmf_element_handle_t *handle);
/**
* @brief Set dest channel in the channel conversion handle
*
* @param[in] handle The channel conversion handle
* @param[in] dest_ch The dest channel
*
* @return
* - ESP_GMF_ERR_OK Operation succeeded
* - ESP_GMF_ERR_INVALID_ARG Invalid input parameter
* - ESP_GMF_ERR_FAIL Failed to set configuration
*/
esp_gmf_err_t esp_gmf_ch_cvt_set_dest_channel(esp_gmf_element_handle_t handle, uint8_t dest_ch);
#ifdef __cplusplus
}
#endif /* __cplusplus */
@@ -0,0 +1,47 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO., LTD
* SPDX-License-Identifier: LicenseRef-Espressif-Modified-MIT
*
* See LICENSE file for details.
*/
#pragma once
#include "esp_gmf_err.h"
#include "esp_gmf_element.h"
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
#define DEFAULT_ESP_GMF_DEINTERLEAVE_CONFIG() { \
.sample_rate = 48000, \
.bits_per_sample = 16, \
.channel = 2, \
}
/**
* @brief Configuration structure for deinterleave
*/
typedef struct {
uint32_t sample_rate; /*!< The audio sample rate */
uint8_t bits_per_sample; /*!< The audio bits per sample, supports 16, 24, 32 bits */
uint8_t channel; /*!< The audio channel */
} esp_gmf_deinterleave_cfg;
/**
* @brief Initializes the GMF deinterleave with the provided configuration
*
* @param[in] config Pointer to the deinterleave configuration
* @param[out] handle Pointer to the deinterleave handle to be initialized
*
* @return
* - ESP_GMF_ERR_OK Success
* - ESP_GMF_ERR_INVALID_ARG Invalid configuration provided
* - ESP_GMF_ERR_MEMORY_LACK Failed to allocate memory
*/
esp_gmf_err_t esp_gmf_deinterleave_init(esp_gmf_deinterleave_cfg *config, esp_gmf_element_handle_t *handle);
#ifdef __cplusplus
}
#endif /* __cplusplus */
@@ -0,0 +1,87 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO., LTD
* SPDX-License-Identifier: LicenseRef-Espressif-Modified-MIT
*
* See LICENSE file for details.
*/
#pragma once
#include "esp_gmf_err.h"
#include "esp_ae_eq.h"
#include "esp_gmf_element.h"
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
/**
* @brief Default EQ parameters
* If `para` is NULL in the `esp_ae_eq_cfg_t` , the EQ will use `esp_gmf_default_eq_paras` as default
* And the `filter_num` will be set to sizeof(esp_gmf_default_eq_paras) / sizeof(esp_ae_eq_filter_para_t)
*/
#define DEFAULT_ESP_GMF_EQ_CONFIG() { \
.sample_rate = 48000, \
.channel = 2, \
.bits_per_sample = 16, \
.filter_num = 0, \
.para = NULL, \
}
/**
* @brief Initializes the GMF EQ with the provided configuration
*
* @param[in] config Pointer to the EQ configuration
* @param[out] handle Pointer to the EQ handle to be initialized
*
* @return
* - ESP_GMF_ERR_OK Success
* - ESP_GMF_ERR_INVALID_ARG Invalid configuration provided
* - ESP_GMF_ERR_MEMORY_LACK Failed to allocate memory
*/
esp_gmf_err_t esp_gmf_eq_init(esp_ae_eq_cfg_t *config, esp_gmf_element_handle_t *handle);
/**
* @brief Set the filter parameters for a specific filter identified by 'idx'
*
* @param[in] handle The EQ handle
* @param[in] idx The index of a specific filter for which the parameters are to be set.
* eg: 0 refers to the first filter
* @param[in] para The filter setup parameter
*
* @return
* - ESP_GMF_ERR_OK Operation succeeded
* - ESP_GMF_ERR_INVALID_ARG Invalid input parameter
*/
esp_gmf_err_t esp_gmf_eq_set_para(esp_gmf_element_handle_t handle, uint8_t idx, esp_ae_eq_filter_para_t *para);
/**
* @brief Get the filter parameters for a specific filter identified by 'idx'
*
* @param[in] handle The EQ handle
* @param[in] idx The index of a specific filter for which the parameters are to be retrieved.
* eg: 0 refers to first filter
* @param[out] para The filter setup parameter
*
* @return
* - ESP_GMF_ERR_OK Operation succeeded
* - ESP_GMF_ERR_INVALID_ARG Invalid input parameter
*/
esp_gmf_err_t esp_gmf_eq_get_para(esp_gmf_element_handle_t handle, uint8_t idx, esp_ae_eq_filter_para_t *para);
/**
* @brief Choose to enable or disable filter processing for a specific filter identified by 'idx' in the equalizer
*
* @param[in] handle The EQ handle
* @param[in] idx The index of a specific filter to be enabled
* @param[in] is_enable The flag of whether to enable band filter processing
*
* @return
* - ESP_GMF_ERR_OK Operation succeeded
* - ESP_GMF_ERR_INVALID_ARG Invalid input parameter
*/
esp_gmf_err_t esp_gmf_eq_enable_filter(esp_gmf_element_handle_t handle, uint8_t idx, bool is_enable);
#ifdef __cplusplus
}
#endif /* __cplusplus */
@@ -0,0 +1,81 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO., LTD
* SPDX-License-Identifier: LicenseRef-Espressif-Modified-MIT
*
* See LICENSE file for details.
*/
#pragma once
#include "esp_gmf_err.h"
#include "esp_ae_fade.h"
#include "esp_gmf_element.h"
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
#define DEFAULT_ESP_GMF_FADE_CONFIG() { \
.mode = ESP_AE_FADE_MODE_FADE_IN, \
.curve = ESP_AE_FADE_CURVE_LINE, \
.transit_time = 500, \
.sample_rate = 48000, \
.channel = 2, \
.bits_per_sample = 16, \
}
/**
* @brief Initializes the GMF fade with the provided configuration
*
* @param[in] config Pointer to the fade configuration
* @param[out] handle Pointer to the fade handle to be initialized
*
* @return
* - ESP_GMF_ERR_OK Success
* - ESP_GMF_ERR_INVALID_ARG Invalid configuration provided
* - ESP_GMF_ERR_MEMORY_LACK Failed to allocate memory
*/
esp_gmf_err_t esp_gmf_fade_init(esp_ae_fade_cfg_t *config, esp_gmf_element_handle_t *handle);
/**
* @brief Set the fade process mode
*
* @param[in] handle The fade handle
* @param[in] mode The mode of fade
*
* @return
* - ESP_GMF_ERR_OK Operation succeeded
* - ESP_GMF_ERR_INVALID_ARG Invalid input parameter
*/
esp_gmf_err_t esp_gmf_fade_set_mode(esp_gmf_element_handle_t handle, esp_ae_fade_mode_t mode);
/**
* @brief Get the fade process mode
*
* @param[in] handle The fade handle
* @param[out] mode The mode of fade
*
* @return
* - ESP_GMF_ERR_OK Operation succeeded
* - ESP_GMF_ERR_INVALID_ARG Invalid input parameter
*/
esp_gmf_err_t esp_gmf_fade_get_mode(esp_gmf_element_handle_t handle, esp_ae_fade_mode_t *mode);
/**
* @brief Reset the fade process to the initial configuration state.
* If the initial configuration mode is fade in, the current weight is set to 0.
* If the initial configuration mode is fade out, the current weight is set to 1.
* This function helps the user to restart fade in or fade out without
* reopening and closing.
*
* @param[in] handle The fade handle
*
* @return
* - ESP_GMF_ERR_OK Operation succeeded
* - ESP_GMF_ERR_INVALID_ARG Invalid input parameter
*/
esp_gmf_err_t esp_gmf_fade_reset_weight(esp_gmf_element_handle_t handle);
#ifdef __cplusplus
}
#endif /* __cplusplus */
@@ -0,0 +1,47 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO., LTD
* SPDX-License-Identifier: LicenseRef-Espressif-Modified-MIT
*
* See LICENSE file for details.
*/
#pragma once
#include "esp_gmf_err.h"
#include "esp_gmf_element.h"
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
#define DEFAULT_ESP_GMF_INTERLEAVE_CONFIG() { \
.sample_rate = 48000, \
.bits_per_sample = 16, \
.src_num = 2, \
}
/**
* @brief Configuration structure for interleave
*/
typedef struct {
uint32_t sample_rate; /*!< The audio sample rate */
uint8_t bits_per_sample; /*!< The audio bits per sample, supports 16, 24, 32 bits */
uint8_t src_num; /*!< The number of input source num */
} esp_gmf_interleave_cfg;
/**
* @brief Initializes the GMF interleave with the provided configuration
*
* @param[in] config Pointer to the interleave configuration
* @param[out] handle Pointer to the interleave handle to be initialized
*
* @return
* - ESP_GMF_ERR_OK Success
* - ESP_GMF_ERR_INVALID_ARG Invalid configuration provided
* - ESP_GMF_ERR_MEMORY_LACK Failed to allocate memory
*/
esp_gmf_err_t esp_gmf_interleave_init(esp_gmf_interleave_cfg *config, esp_gmf_element_handle_t *handle);
#ifdef __cplusplus
}
#endif /* __cplusplus */
@@ -0,0 +1,76 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO., LTD
* SPDX-License-Identifier: LicenseRef-Espressif-Modified-MIT
*
* See LICENSE file for details.
*/
#pragma once
#include "esp_gmf_err.h"
#include "esp_ae_mixer.h"
#include "esp_gmf_element.h"
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
/**
* @brief Default mixer source info
* If `src_info` is NULL in `esp_ae_mixer_cfg_t`, the mixer will use esp_gmf_default_mixer_src_info as default
* And `src_num` will set to `sizeof(esp_gmf_default_mixer_src_info) / sizeof(esp_ae_mixer_info_t)`;
*/
#define DEFAULT_ESP_GMF_MIXER_CONFIG() { \
.sample_rate = 48000, \
.channel = 2, \
.bits_per_sample = 16, \
.src_num = 0, \
.src_info = NULL, \
}
/**
* @brief Initializes the GMF mixer with the provided configuration
*
* @param[in] config Pointer to the mixer configuration
* @param[out] handle Pointer to the mixer handle to be initialized
*
* @return
* - ESP_GMF_ERR_OK Success
* - ESP_GMF_ERR_INVALID_ARG Invalid configuration provided
* - ESP_GMF_ERR_MEMORY_LACK Failed to allocate memory
*/
esp_gmf_err_t esp_gmf_mixer_init(esp_ae_mixer_cfg_t *config, esp_gmf_element_handle_t *handle);
/**
* @brief Set the transit mode of a certain stream according to src_idx
*
* @param[in] handle The mixer handle
* @param[in] src_idx The index of a certain source stream which want to set transit mode.
* eg: 0 refer to first source stream
* @param[in] mode The transit mode of source stream
*
* @return
* - ESP_GMF_ERR_OK Operation succeeded
* - ESP_GMF_ERR_INVALID_ARG Invalid input parameter
*/
esp_gmf_err_t esp_gmf_mixer_set_mode(esp_gmf_element_handle_t handle, uint8_t src_idx, esp_ae_mixer_mode_t mode);
/**
* @brief Set audio information to the mixer handle
*
* @param[in] handle The mixer handle
* @param[in] sample_rate The audio sample rate
* @param[in] bits The audio bits per sample
* @param[in] channel The audio channel
*
* @return
* - ESP_GMF_ERR_OK Operation succeeded
* - ESP_GMF_ERR_INVALID_ARG Invalid input parameter
* - ESP_GMF_ERR_FAIL Failed to set configuration
*/
esp_gmf_err_t esp_gmf_mixer_set_audio_info(esp_gmf_element_handle_t handle, uint32_t sample_rate,
uint8_t bits, uint8_t channel);
#ifdef __cplusplus
}
#endif /* __cplusplus */
@@ -0,0 +1,55 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO., LTD
* SPDX-License-Identifier: LicenseRef-Espressif-Modified-MIT
*
* See LICENSE file for details.
*/
#pragma once
#include "esp_gmf_err.h"
#include "esp_ae_rate_cvt.h"
#include "esp_gmf_element.h"
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
#define DEFAULT_ESP_GMF_RATE_CVT_CONFIG() { \
.src_rate = 44100, \
.dest_rate = 48000, \
.channel = 2, \
.bits_per_sample = 16, \
.complexity = 2, \
.perf_type = ESP_AE_RATE_CVT_PERF_TYPE_SPEED, \
}
/**
* @brief Initializes the GMF rate conversion with the provided configuration
*
* @param[in] config Pointer to the rate conversion configuration
* @param[out] handle Pointer to the rate conversion handle to be initialized
*
* @return
* - ESP_GMF_ERR_OK Success
* - ESP_GMF_ERR_INVALID_ARG Invalid configuration provided
* - ESP_GMF_ERR_MEMORY_LACK Failed to allocate memory
*/
esp_gmf_err_t esp_gmf_rate_cvt_init(esp_ae_rate_cvt_cfg_t *config, esp_gmf_element_handle_t *handle);
/**
* @brief Set dest rate in the rate conversion handle
*
* @param[in] handle The rate conversion handle
* @param[in] dest_ch The dest rate
*
* @return
* - ESP_GMF_ERR_OK Operation succeeded
* - ESP_GMF_ERR_INVALID_ARG Invalid input parameter
* - ESP_GMF_ERR_FAIL Failed to set configuration
*/
esp_gmf_err_t esp_gmf_rate_cvt_set_dest_rate(esp_gmf_element_handle_t handle, uint32_t dest_rate);
#ifdef __cplusplus
}
#endif /* __cplusplus */
@@ -0,0 +1,92 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO., LTD
* SPDX-License-Identifier: LicenseRef-Espressif-Modified-MIT
*
* See LICENSE file for details.
*/
#pragma once
#include "esp_gmf_err.h"
#include "esp_ae_sonic.h"
#include "esp_gmf_element.h"
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
#define DEFAULT_ESP_GMF_SONIC_CONFIG() { \
.sample_rate = 48000, \
.channel = 2, \
.bits_per_sample = 16, \
}
/**
* @brief Initializes the GMF sonic with the provided configuration
*
* @param[in] config Pointer to the sonic configuration
* @param[out] handle Pointer to the sonic handle to be initialized
*
* @return
* - ESP_GMF_ERR_OK Success
* - ESP_ERR_INVALID_ARG Invalid configuration provided
* - ESP_GMF_ERR_MEMORY_LACK Failed to allocate memory
*/
esp_gmf_err_t esp_gmf_sonic_init(esp_ae_sonic_cfg_t *config, esp_gmf_element_handle_t *handle);
/**
* @brief Set the audio speed
*
* @param[in] handle The handle of the sonic
* @param[in] speed The scaling factor of audio speed.
* The range of speed is [0.5, 2.0]
*
* @return
* - ESP_GMF_ERR_OK Operation succeeded
* - ESP_GMF_ERR_INVALID_ARG Invalid input parameter
*/
esp_gmf_err_t esp_gmf_sonic_set_speed(esp_gmf_element_handle_t handle, float speed);
/**
* @brief Get the audio speed
*
* @param[in] handle The handle of the sonic
* @param[out] speed The scaling factor of audio speed
*
* @return
* - ESP_GMF_ERR_OK Operation succeeded
* - ESP_GMF_ERR_INVALID_ARG Invalid input parameter
*/
esp_gmf_err_t esp_gmf_sonic_get_speed(esp_gmf_element_handle_t handle, float *speed);
/**
* @brief Set the audio pitch
*
* @param[in] handle The handle of the sonic
* @param[in] pitch The scaling factor of audio pitch.
* The range of pitch is [0.5, 2.0].
* If the pitch value is smaller than 1.0, the sound is deep voice;
* if the pitch value is equal to 1.0, the sound is no change;
* if the pitch value is gather than 1.0, the sound is sharp voice;
*
* @return
* - ESP_GMF_ERR_OK Operation succeeded
* - ESP_GMF_ERR_INVALID_ARG Invalid input parameter
*/
esp_gmf_err_t esp_gmf_sonic_set_pitch(esp_gmf_element_handle_t handle, float pitch);
/**
* @brief Get the audio pitch
*
* @param[in] handle The handle of the sonic
* @param[out] pitch The scaling factor of audio pitch
*
* @return
* - ESP_GMF_ERR_OK Operation succeeded
* - ESP_GMF_ERR_INVALID_ARG Invalid input parameter
*/
esp_gmf_err_t esp_gmf_sonic_get_pitch(esp_gmf_element_handle_t handle, float *pitch);
#ifdef __cplusplus
}
#endif /* __cplusplus */
@@ -0,0 +1,35 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO., LTD
* SPDX-License-Identifier: LicenseRef-Espressif-Modified-MIT
*
* See LICENSE file for details.
*/
#pragma once
#include "esp_err.h"
#include "esp_gmf_info.h"
#include "esp_gmf_audio_element.h"
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
#define GMF_AUDIO_UPDATE_SND_INFO(self, sample_rate, bits, channel) gmf_audio_update_snd_info(self, sample_rate, bits, channel)
#define GMF_AUDIO_INPUT_SAMPLE_NUM (256)
#define GMF_AUDIO_CALC_PTS(out_len, sample_rate, ch, bits) ((out_len) * 8000 / ((sample_rate) * (ch) * (bits)))
static inline void gmf_audio_update_snd_info(void *self, uint32_t sample_rate, uint8_t bits, uint8_t channel)
{
esp_gmf_info_sound_t snd_info = {0};
esp_gmf_audio_el_get_snd_info(self, &snd_info);
snd_info.sample_rates = sample_rate;
snd_info.channels = channel;
snd_info.bits = bits;
esp_gmf_audio_el_set_snd_info(self, &snd_info);
esp_gmf_element_notify_snd_info(self, &snd_info);
}
#ifdef __cplusplus
}
#endif /* __cplusplus */
@@ -0,0 +1,97 @@
# Changelog
## v0.7.6
### Features
- Support C++ build
## v0.7.5
### Bug Fixes
- Fixed the issue where idf_build_set_property is not a valid function for component
## v0.7.4
### Bug Fixes
- Fixed data bus init when direction is write in `io_http`
## v0.7.3
### Bug Fixes
- Fixed cache caps issues for esp32 in `io_file`
## v0.7.2
### Features
- Added io_file cache size configuration to enhance read and write performance
### Bug Fixes
- Delay task creation to open stage in `io_http` to avoid task resource waste if no used
## v0.7.1
- Updated `esp-codec-dev` dependency to v1.4.0
## v0.7.0
### Features
- Added `esp_gmf_io_codec_dev_set_dev` API to set the audio codec device handle
- Added `esp_gmf_io_http_set_event_callback` API to set an HTTP event callback
- Added basic reset functionality to each IO to properly clean up resources and reset state
### Bug Fixes
- Fixed an issue where HTTP IO did not reset the thread and databus properly
- Fixed file open check for treat return error as valid fd
- Corrected return value validation for *acq_write/read and *acq_release_write/read callback function implementations
- Fixed HTTP connection timeout handling during _http_close operations
- Standardize TAG identifier format across all I/O elements with `io` prefix
## v0.6.3
### Bug Fixes
- Changed the tag for embedded flash IO from `embed_flash` to `embed`
- Refactor the script to make the generated files follow our coding style
## v0.6.2
### Features
- Limit the version of `esp_codec_dev`
## v0.6.1
- Fixed memory leaks in I/O flash destroy API
- Corrected default values for I/O file and I/O I2S elements to ensure stable initialization
- Enforced esp_gmf_err_t return type for all element initialization functions (I/O flash, HTTP)
## v0.6.0
### Features
- Added support for NULL object configuration
- Renamed component to `gmf_io`
- Updated the License
### Bug Fixes
- Standardized return values of port and data bus acquire/release APIs to esp_gmf_err_io_t only
## v0.5.1
### Bug Fixes
- Fixed the component requirements
## v0.5.0
### Features
- Initial version of `esp-gmf-io`

Some files were not shown because too many files have changed in this diff Show More