From 2fc23fe81e37f678fb081efd4e9d8143fbfbf661 Mon Sep 17 00:00:00 2001 From: Jaechul Lee Date: Mon, 5 Aug 2024 16:42:29 +0900 Subject: [PATCH] Add audio effect APIs in case of VD, the files related to audio_io_effect aren't installed because libaudio-effect is not up-to-date. it will make build breaks. [Version] 0.5.69 [Issue Type] New Feature Change-Id: I2b0af49067deab33716dbc428a5eb52f61858044 Signed-off-by: Jaechul Lee --- CMakeLists.txt | 8 +- include/audio_io_internal.h | 63 +++++ packaging/capi-media-audio-io.spec | 11 +- src/audio_io_internal.cpp | 444 +++++++++++++++++++++++++++++ test/CMakeLists.txt | 9 +- test/audio_io_effect_test.c | 562 +++++++++++++++++++++++++++++++++++++ 6 files changed, 1089 insertions(+), 8 deletions(-) create mode 100644 include/audio_io_internal.h create mode 100644 src/audio_io_internal.cpp create mode 100644 test/audio_io_effect_test.c diff --git a/CMakeLists.txt b/CMakeLists.txt index af1abcb..3be14cb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,8 +10,8 @@ SET(PREFIX ${CMAKE_INSTALL_PREFIX}) SET(INC_DIR include) INCLUDE_DIRECTORIES(${INC_DIR}) -SET(dependents "dlog capi-base-common capi-media-sound-manager libpulse dpm capi-system-info") -SET(pc_dependents "capi-base-common capi-media-sound-manager") +SET(dependents "dlog capi-base-common capi-media-sound-manager libpulse dpm capi-system-info libaudio-effect") +SET(pc_dependents "capi-base-common capi-media-sound-manager libaudio-effect") INCLUDE(FindPkgConfig) pkg_check_modules(${fw_name} REQUIRED ${dependents}) @@ -49,6 +49,10 @@ aux_source_directory(src SOURCES) SET(ALL_SRC ${SOURCES} ${CPPSOURCES}) +IF(TIZEN_FEATURE_PRODUCT_TV) + list(REMOVE_ITEM ALL_SRC "src/audio_io_internal.cpp") +ENDIF(TIZEN_FEATURE_PRODUCT_TV) + ADD_LIBRARY(${fw_name} SHARED ${ALL_SRC}) SET_TARGET_PROPERTIES(${fw_name} diff --git a/include/audio_io_internal.h b/include/audio_io_internal.h new file mode 100644 index 0000000..3dccb0e --- /dev/null +++ b/include/audio_io_internal.h @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2024 Samsung Electronics Co., Ltd All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __TIZEN_MEDIA_AUDIO_IO_INTERNAL_H__ +#define __TIZEN_MEDIA_AUDIO_IO_INTERNAL_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + AUDIO_IO_EFFECT_METHOD_ACOUSTIC_ECHO_CANCELLATION = 0x1, + AUDIO_IO_EFFECT_METHOD_NOISE_SUPPRESSION = 0x2, + AUDIO_IO_EFFECT_METHOD_AUTO_GAIN_CONTROL = 0x4, +} audio_io_effect_method_e; + +typedef struct audio_io_effect_method_s *audio_io_effect_method_h; +typedef struct audio_io_effect_method_chain_s *audio_io_effect_method_chain_h; +typedef struct audio_io_effect_method_chain_builder_s *audio_io_effect_method_chain_builder_h; + +int audio_io_create_acoustic_echo_canceller(int rate, audio_channel_e channels, + audio_sample_type_e sample_type, size_t framesize, + audio_io_effect_method_h *audio_io_effect, size_t *adjust_framesize); +int audio_io_create_noise_suppressor(int rate, audio_channel_e channels, + audio_sample_type_e sample_type, size_t framesize, + audio_io_effect_method_h *audio_io_effect, size_t *adjust_framesize); +int audio_io_create_auto_gain_control(int rate, audio_channel_e channels, + audio_sample_type_e sample_type, size_t framesize, + audio_io_effect_method_h *audio_io_effect, size_t *adjust_framesize); +int audio_io_destroy_effect_method(audio_io_effect_method_h audio_io_effect); + +int audio_io_apply_effect_method(audio_io_effect_method_h audio_io_effect, const char *in, char *out); +int audio_io_apply_effect_method_with_reference(audio_io_effect_method_h audio_io_effect, const char *in, const char *ref, char *out); + +/* effect chain */ +int audio_io_create_effect_method_chain_builder(int rate, audio_channel_e channels, + audio_sample_type_e sample_type, size_t request_framesize, + audio_io_effect_method_chain_builder_h *builder); +int audio_io_append_effect_method_chain_builder(audio_io_effect_method_chain_builder_h builder, audio_io_effect_method_e aio_method); +int audio_io_build_effect_method_chain(audio_io_effect_method_chain_builder_h builder, audio_io_effect_method_chain_h *chain, size_t *adjust_framesize); +int audio_io_apply_effect_method_chain(audio_io_effect_method_chain_h chain, const char *in, const char *ref, char *out); +int audio_io_destroy_effect_method_chain_builder(audio_io_effect_method_chain_builder_h builder); +int audio_io_destroy_effect_method_chain(audio_io_effect_method_chain_h chain); + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/packaging/capi-media-audio-io.spec b/packaging/capi-media-audio-io.spec index 80df104..5bcd293 100644 --- a/packaging/capi-media-audio-io.spec +++ b/packaging/capi-media-audio-io.spec @@ -1,6 +1,6 @@ Name: capi-media-audio-io Summary: An Audio Input & Audio Output library in Tizen Native API -Version: 0.5.68 +Version: 0.5.69 Release: 0 Group: Multimedia/API License: Apache-2.0 @@ -13,6 +13,7 @@ BuildRequires: pkgconfig(capi-base-common) BuildRequires: pkgconfig(libpulse) BuildRequires: pkgconfig(dpm) BuildRequires: pkgconfig(capi-system-info) +BuildRequires: pkgconfig(libaudio-effect) %description An Audio Input & Audio Output library in Tizen Native API @@ -85,13 +86,17 @@ find . -name '*.gcno' -exec cp --parents '{}' "$gcno_obj_dir" ';' %files devel %manifest %{name}.manifest %{_includedir}/media/audio_io.h +%if "%{tizen_profile_name}" == "tv" +%exclude %{_includedir}/media/audio_io_internal.h +%else +%{_includedir}/media/audio_io_internal.h +%endif %{_libdir}/pkgconfig/*.pc %{_libdir}/libcapi-media-audio-io.so %files tool %manifest %{name}.manifest -%{_prefix}/bin/audio_io_test -%{_prefix}/bin/audio_io_process_test +%{_prefix}/bin/* %if 0%{?gcov:1} %files gcov diff --git a/src/audio_io_internal.cpp b/src/audio_io_internal.cpp new file mode 100644 index 0000000..5b4ecd9 --- /dev/null +++ b/src/audio_io_internal.cpp @@ -0,0 +1,444 @@ +/* +* Copyright (c) 2024 Samsung Electronics Co., Ltd All Rights Reserved +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#include + +#include "CAudioIODef.h" +#include "audio_io.h" +#include "audio_io_internal.h" + +#include + +#define AUDIO_IO_EFFECT_MAX 3 + +using namespace tizen_media_audio; + +#define _check_param(cond) do { \ + if (!!!(cond)) { \ + AUDIO_IO_LOGE("invalid param. condition(\"%s\")", #cond); \ + return AUDIO_IO_ERROR_INVALID_PARAMETER; \ + } \ +} while(0) + +/* Use libaudio-effect domain in the every structure */ +typedef struct audio_io_effect_method_s { + audio_effect_method_e method; + audio_effect_s *ae; + bool need_reference; + + int rate; + int channels; + audio_effect_format_e format; + size_t framesize; +} audio_io_effect_method_s; + +typedef struct audio_io_effect_method_chain_s { + audio_effect_s *ae[AUDIO_IO_EFFECT_MAX]; + unsigned n_effect; + + int rate; + int channels; + audio_effect_format_e format; + size_t framesize; + + char *buffer[AUDIO_IO_EFFECT_MAX]; +} audio_io_effect_method_chain_s; + +typedef struct audio_io_effect_method_chain_builder_s { + audio_effect_method_e method[AUDIO_IO_EFFECT_MAX]; + unsigned method_index; + + int rate; + int channels; + audio_effect_format_e format; + size_t request_framesize; +} audio_io_effect_method_chain_builder_s; + +static int _convert_method(audio_io_effect_method_e from, audio_effect_method_e *to) +{ + switch (from) { + case AUDIO_IO_EFFECT_METHOD_ACOUSTIC_ECHO_CANCELLATION: + *to = AUDIO_EFFECT_METHOD_AEC_WEBRTC; + return 0; + case AUDIO_IO_EFFECT_METHOD_NOISE_SUPPRESSION: + *to = AUDIO_EFFECT_METHOD_NS_RNNOISE; + return 0; + case AUDIO_IO_EFFECT_METHOD_AUTO_GAIN_CONTROL: + *to = AUDIO_EFFECT_METHOD_AGC_SPEEX; + return 0; + default: + return -1; + } +} + +static int _convert_format(audio_sample_type_e sample_type, audio_effect_format_e *format) +{ + switch (sample_type) { + case AUDIO_SAMPLE_TYPE_U8: + *format = AUDIO_EFFECT_FORMAT_S8; + return 0; + case AUDIO_SAMPLE_TYPE_S16_LE: + *format = AUDIO_EFFECT_FORMAT_S16; + return 0; + case AUDIO_SAMPLE_TYPE_S24_LE: + *format = AUDIO_EFFECT_FORMAT_S24; + return 0; + case AUDIO_SAMPLE_TYPE_S24_32_LE: + return -1; + case AUDIO_SAMPLE_TYPE_S32_LE: + *format = AUDIO_EFFECT_FORMAT_S32; + return 0; + default: + break; + } + + AUDIO_IO_LOGE("failed to convert audio format. sample_type (%x)", sample_type); + + return -1; +} + +static audio_io_effect_method_s *_create_audio_io_effect_method(audio_effect_method_e method, + int rate, int channels, + audio_effect_format_e format, + size_t framesize, size_t *adjust_framesize) +{ + audio_effect_s *ae; + audio_io_effect_method_s *aio_effect; + + ae = audio_effect_create(method, rate, channels, format, framesize, adjust_framesize); + if (!ae) { + AUDIO_IO_LOGE("failed to create audio_effect"); + return NULL; + } + + aio_effect = (audio_io_effect_method_s *)calloc(1, sizeof(audio_io_effect_method_s)); + if (!aio_effect) { + AUDIO_IO_LOGE("failed to allocate audio_io_effect_method"); + audio_effect_destroy(ae); + return NULL; + } + + aio_effect->ae = ae; + aio_effect->method = method; + aio_effect->rate = rate; + aio_effect->channels = channels; + aio_effect->format = format; + aio_effect->framesize = *adjust_framesize; + + return aio_effect; +} + +int audio_io_create_acoustic_echo_canceller(int rate, audio_channel_e channels, + audio_sample_type_e sample_type, size_t framesize, + audio_io_effect_method_h *audio_io_effect, size_t *adjust_framesize) +{ + size_t _adjust_framesize; + audio_effect_format_e format; + audio_io_effect_method_s *aio_effect; + int ch = channels - AUDIO_CHANNEL_MONO + 1; + + _check_param(audio_io_effect); + _check_param(_convert_format(sample_type, &format) == 0); + + aio_effect = _create_audio_io_effect_method(AUDIO_EFFECT_METHOD_AEC_WEBRTC, rate, ch, format, framesize, &_adjust_framesize); + if (!aio_effect) { + AUDIO_IO_LOGE("failed to create audio_io_effect"); + return AUDIO_IO_ERROR_INVALID_OPERATION; + } + + aio_effect->need_reference = true; + *audio_io_effect = (audio_io_effect_method_h)aio_effect; + *adjust_framesize = _adjust_framesize; + + return AUDIO_IO_ERROR_NONE; +} + +int audio_io_create_noise_suppressor(int rate, audio_channel_e channels, + audio_sample_type_e sample_type, size_t framesize, + audio_io_effect_method_h *audio_io_effect, size_t *adjust_framesize) +{ + size_t _adjust_framesize; + audio_effect_format_e format; + audio_io_effect_method_s *aio_effect; + int ch = channels - AUDIO_CHANNEL_MONO + 1; + + _check_param(audio_io_effect); + _check_param(_convert_format(sample_type, &format) == 0); + + aio_effect = _create_audio_io_effect_method(AUDIO_EFFECT_METHOD_NS_RNNOISE, rate, ch, format, framesize, &_adjust_framesize); + if (!aio_effect) { + AUDIO_IO_LOGE("failed to create audio_io_effect"); + return AUDIO_IO_ERROR_INVALID_OPERATION; + } + + *audio_io_effect = (audio_io_effect_method_h)aio_effect; + *adjust_framesize = _adjust_framesize; + + return AUDIO_IO_ERROR_NONE; +} + +int audio_io_create_auto_gain_control(int rate, audio_channel_e channels, + audio_sample_type_e sample_type, size_t framesize, + audio_io_effect_method_h *audio_io_effect, size_t *adjust_framesize) +{ + size_t _adjust_framesize; + audio_effect_format_e format; + audio_io_effect_method_s *aio_effect; + int ch = channels - AUDIO_CHANNEL_MONO + 1; + + _check_param(audio_io_effect); + _check_param(_convert_format(sample_type, &format) == 0); + + aio_effect = _create_audio_io_effect_method(AUDIO_EFFECT_METHOD_AGC_SPEEX, rate, ch, format, framesize, &_adjust_framesize); + if (!aio_effect) { + AUDIO_IO_LOGE("failed to create audio_io_effect"); + return AUDIO_IO_ERROR_INVALID_OPERATION; + } + + *audio_io_effect = (audio_io_effect_method_h)aio_effect; + *adjust_framesize = _adjust_framesize; + + return AUDIO_IO_ERROR_NONE; +} + +int audio_io_destroy_effect_method(audio_io_effect_method_h audio_io_effect) +{ + audio_io_effect_method_s *aio_effect = audio_io_effect; + + _check_param(aio_effect); + + if (aio_effect->ae) + audio_effect_destroy(aio_effect->ae); + + memset(aio_effect, 0x00, sizeof(audio_io_effect_method_s)); + + free(aio_effect); + + return AUDIO_IO_ERROR_NONE; +} + +int audio_io_apply_effect_method(audio_io_effect_method_h audio_io_effect, const char *in, char *out) +{ + audio_io_effect_method_s *aio_effect = audio_io_effect; + + _check_param(aio_effect); + _check_param(aio_effect->need_reference == false); + _check_param(in); + _check_param(out); + _check_param(aio_effect->ae); + + if (audio_effect_process(aio_effect->ae, (const void *)in, out)) { + AUDIO_IO_LOGE("failed to process audio audio_io_effect"); + return AUDIO_IO_ERROR_INVALID_OPERATION; + } + + return AUDIO_IO_ERROR_NONE; +} + +int audio_io_apply_effect_method_with_reference(audio_io_effect_method_h audio_io_effect, const char *in, const char *ref, char *out) +{ + audio_io_effect_method_s *aio_effect = audio_io_effect; + + _check_param(aio_effect); + _check_param(aio_effect->need_reference == true); + _check_param(in); + _check_param(ref); + _check_param(out); + _check_param(aio_effect->ae); + + if (audio_effect_process_reference(aio_effect->ae, (const void *)in, (const void *)ref, (void *)out)) { + AUDIO_IO_LOGE("failed to process audio audio_io_effect"); + return AUDIO_IO_ERROR_INVALID_OPERATION; + } + + return AUDIO_IO_ERROR_NONE; +} + +int audio_io_create_effect_method_chain_builder(int rate, audio_channel_e channels, + audio_sample_type_e sample_type, size_t request_framesize, + audio_io_effect_method_chain_builder_h *builder) +{ + audio_effect_format_e format; + audio_io_effect_method_chain_builder_s *_builder; + + _check_param(builder); + _check_param(_convert_format(sample_type, &format) == 0); + + _builder = (audio_io_effect_method_chain_builder_s *)calloc(1, sizeof(audio_io_effect_method_chain_builder_s)); + if (!_builder) { + AUDIO_IO_LOGE("out of memory"); + return AUDIO_IO_ERROR_OUT_OF_MEMORY; + } + + _builder->rate = rate; + _builder->channels = channels - AUDIO_CHANNEL_MONO + 1; + _builder->format = format; + _builder->request_framesize = request_framesize; + + *builder = (audio_io_effect_method_chain_builder_h)_builder; + + return AUDIO_IO_ERROR_NONE; +} + +int audio_io_append_effect_method_chain_builder(audio_io_effect_method_chain_builder_h builder, audio_io_effect_method_e aio_method) +{ + audio_effect_method_e method; + audio_io_effect_method_chain_builder_s *chain_builder = (audio_io_effect_method_chain_builder_s *)builder; + + _check_param(chain_builder); + _check_param(_convert_method(aio_method, &method) == 0); + + if (chain_builder->method_index >= AUDIO_IO_EFFECT_MAX) { + AUDIO_IO_LOGE("failed to append audio effect. method_index(%d) must be less than MAX(%d)", + chain_builder->method_index, AUDIO_IO_EFFECT_MAX); + return AUDIO_IO_ERROR_INVALID_OPERATION; + } + + chain_builder->method[chain_builder->method_index] = method; + chain_builder->method_index += 1; + + return AUDIO_IO_ERROR_NONE; +} + +int audio_io_build_effect_method_chain(audio_io_effect_method_chain_builder_h builder, audio_io_effect_method_chain_h *chain, size_t *adjust_framesize) +{ + size_t _adjust_framesize; + size_t framesize_max = 0; + audio_io_effect_method_chain_s *_chain; + audio_io_effect_method_chain_builder_s *chain_builder = (audio_io_effect_method_chain_builder_s *)builder; + + _check_param(chain); + _check_param(chain_builder); + + _chain = (audio_io_effect_method_chain_s *)calloc(1, sizeof(audio_io_effect_method_chain_s)); + if (!_chain) { + AUDIO_IO_LOGE("out of memory"); + return AUDIO_IO_ERROR_OUT_OF_MEMORY; + } + + for (unsigned i=0; imethod_index; i++) { + _chain->ae[i] = audio_effect_create(chain_builder->method[i], + chain_builder->rate, + chain_builder->channels, + chain_builder->format, + chain_builder->request_framesize, + &_adjust_framesize); + if (!_chain->ae[i]) { + AUDIO_IO_LOGE("failed to create audio effect. %d", builder->method[i]); + goto fail; + } + + framesize_max = MAX(framesize_max, _adjust_framesize); + } + + _chain->rate = chain_builder->rate; + _chain->channels = chain_builder->channels; + _chain->format = chain_builder->format; + _chain->framesize = framesize_max; + _chain->n_effect = chain_builder->method_index; + + for (unsigned i=0; imethod_index; i++) { + if (i < chain_builder->method_index -1) { + _chain->buffer[i] = (char *)malloc(_chain->framesize * _chain->channels * 2); + if (!_chain->buffer[i]) { + AUDIO_IO_LOGE("out of memory"); + goto fail; + } + } + } + + /* TODO:limitation */ + if ((size_t)(_chain->rate / 100) != framesize_max) { + AUDIO_IO_LOGE("failed to create audio effect. rate(%d), framesize_max(%zu)", _chain->rate, framesize_max); + goto fail; + } + + *adjust_framesize = _chain->framesize = framesize_max; + *chain = _chain; + + return 0; + +fail: + for (unsigned i=0; _chain->ae[i]; i++) { + if (_chain->ae[i]) + audio_effect_destroy(_chain->ae[i]); + if (_chain->buffer[i]) + free(_chain->buffer[i]); + } + + free(_chain); + + return AUDIO_IO_ERROR_INVALID_OPERATION; +} + +int audio_io_apply_effect_method_chain(audio_io_effect_method_chain_h chain, const char *in, const char *ref, char *out) +{ + audio_io_effect_method_chain_s *_chain = (audio_io_effect_method_chain_s *)chain; + + _check_param(chain); + _check_param(_chain->n_effect > 1); + + void *_out; + const void *_in = (const void *)in; + const unsigned last_one = _chain->n_effect - 1; + + for (unsigned i=0; i<_chain->n_effect; i++) { + /* check this effect method is the last */ + if (i == last_one) + _out = out; + else + _out = chain->buffer[i]; + + if (audio_effect_process_reference(_chain->ae[i], _in, ref, _out)) { + AUDIO_IO_LOGE("failed to process audio audio_io_effect"); + return AUDIO_IO_ERROR_INVALID_OPERATION; + } + + _in = _out; + } + + return 0; +} + +int audio_io_destroy_effect_method_chain_builder(audio_io_effect_method_chain_builder_h builder) +{ + audio_io_effect_method_chain_builder_s *chain_builder = (audio_io_effect_method_chain_builder_s *)builder; + + _check_param(chain_builder); + + free(chain_builder); + + return 0; +} + +int audio_io_destroy_effect_method_chain(audio_io_effect_method_chain_h chain) +{ + audio_io_effect_method_chain_s *_chain = (audio_io_effect_method_chain_s *)chain; + + _check_param(chain); + + for (unsigned i=0; i<_chain->n_effect; i++) { + if (_chain->ae[i]) + audio_effect_destroy(_chain->ae[i]); + if (_chain->buffer[i]) + free(_chain->buffer[i]); + } + + free(_chain); + + return 0; +} + diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 9684f82..2a5a1c6 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -9,12 +9,15 @@ SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${EXTRA_CFLAGS} -Wall -Werror -pie") SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${EXTRA_CXXFLAGS} -fPIC -Wall -Werror -std=c++0x -pie") aux_source_directory(. sources) + +IF(TIZEN_FEATURE_PRODUCT_TV) + list(REMOVE_ITEM sources "./audio_io_effect_test.c") +ENDIF(TIZEN_FEATURE_PRODUCT_TV) + FOREACH(src ${sources}) GET_FILENAME_COMPONENT(src_name ${src} NAME_WE) MESSAGE("${src_name}") ADD_EXECUTABLE(${src_name} ${src}) TARGET_LINK_LIBRARIES(${src_name} ${fw_name} ${${fw_test}_LDFLAGS} -lm -pthread) + INSTALL(TARGETS ${src_name} DESTINATION bin) ENDFOREACH() - -INSTALL(TARGETS audio_io_test DESTINATION bin) -INSTALL(TARGETS audio_io_process_test DESTINATION bin) diff --git a/test/audio_io_effect_test.c b/test/audio_io_effect_test.c new file mode 100644 index 0000000..6f3c14d --- /dev/null +++ b/test/audio_io_effect_test.c @@ -0,0 +1,562 @@ +/* +* Copyright (c) 2024 Samsung Electronics Co., Ltd All Rights Reserved +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +enum { + STREAM_INPUT, + STREAM_REFERENCE, + STREAM_OUTPUT, + STREAM_MAX, +}; + +typedef struct wav_header { + char riff[4]; // "RIFF" + uint32_t overall_size; // File size - 8 + char wave[4]; // "WAVE" + char subchunkID[4]; // 'fmt ' + uint32_t subchunk2size; + uint16_t audioformat; + uint16_t numchannels; + uint32_t samplerate; + uint32_t byterate; + uint16_t blockalign; + uint16_t bitspersample; + char data_chunk_header[4]; + uint32_t data_size; +} wav_header_s; + +typedef struct arguments { + uint32_t rate; + uint16_t channels; + uint16_t format; + uint16_t frame_size; + uint32_t data_size; +} wav_specs_s; + +static void _print_usage() +{ + fprintf(stderr, "Usage)\n"); + fprintf(stderr, "\taudio_io_effect test -e [audio_io_effect_method_e] -i [input.wav] -r [reference.wav] -o [output.wav]\n"); + fprintf(stderr, "\t\texample) audio_io_effect_test -e 1 -i input.wav -r reference.wav -o output.wav\n"); + fprintf(stderr, "\t\texample) audio_io_effect_test -e 2 -i input.wav -o output.wav\n"); + fprintf(stderr, "\t\taec:1, ns:2, agc:4\n"); +} + +static void _close_all_resources(FILE **fp, char **buffer) +{ + int i; + + for (i = 0; i < STREAM_MAX; i++) { + if (fp[i]) + fclose(fp[i]); + if (buffer[i]) + free(buffer[i]); + } +} + +static void _make_wav_specs(wav_header_s *wav_header, wav_specs_s *specs) +{ + assert(wav_header); + assert(specs); + + specs->rate = wav_header->samplerate; + specs->channels = wav_header->numchannels; + specs->format = wav_header->audioformat; + specs->frame_size = specs->channels * (wav_header->bitspersample / 8); + specs->data_size = wav_header->data_size; +} + +static void _read_wav_info(const char *filename, wav_header_s *wav_header, wav_specs_s *wav_specs) +{ + FILE *fp; + + fp = fopen(filename, "r"); + assert(fp); + + assert(fread(wav_header, sizeof(wav_header_s), 1, fp) > 0); + + _make_wav_specs(wav_header, wav_specs); + + fclose(fp); +} + +static int _check_args_validation(const char *filename) +{ + wav_header_s wav_header; + wav_specs_s specs; + + const char *RIFF = "RIFF"; + const char *WAVE = "WAVE"; + + if (!filename) { + fprintf(stderr, "[INFO] filename is null\n"); + return -1; + } + + if (access(filename, R_OK) != 0) { + fprintf(stderr, "[INFO] file(%s) doesn't exist\n", filename); + return -1; + } + + _read_wav_info(filename, &wav_header, &specs); + + if (strncmp(wav_header.riff, RIFF, strlen(RIFF))) { + fprintf(stderr, "[INFO] file(%s) seemed not wav file.\n", filename); + return -1; + } + + if (strncmp(wav_header.wave, WAVE, strlen(WAVE))) { + fprintf(stderr, "[INFO] file(%s) seemed not wav file.\n", filename); + return -1; + } + + return 0; +} + +/* move the fp to the pcm start point */ +static FILE *_fopen_at_pcm_start(const char *filename) +{ + FILE *fp; + long filesize; + int32_t position; + wav_header_s wav_header; + + assert(filename); + + fp = fopen(filename, "r"); + assert(fp); + + fseek(fp, 0, SEEK_END); + filesize = ftell(fp); + fseek(fp, 0, SEEK_SET); + + assert(fread(&wav_header, sizeof(wav_header_s), 1, fp) > 0); + + position = (int32_t)wav_header.data_size; + fseek(fp, -position, SEEK_END); + + fprintf(stderr, "[INFO] file(%s), size(%lu), header size(%zu), pcm size(%d)\n", + filename, filesize, sizeof(wav_header_s), position); + + return fp; +} + +static int _effect_factory(audio_io_effect_method_e method, + int rate, + audio_channel_e channels, + audio_sample_type_e sample_type, + size_t framesize, + audio_io_effect_method_h *aio_effect, + size_t *adjust_framesize) { + int ret = -1; + size_t _adjust_framesize; + audio_io_effect_method_h _aio_effect; + + switch (method) { + case AUDIO_IO_EFFECT_METHOD_ACOUSTIC_ECHO_CANCELLATION: + ret = audio_io_create_acoustic_echo_canceller(rate, + channels, + sample_type, + framesize, + &_aio_effect, + &_adjust_framesize); + break; + case AUDIO_IO_EFFECT_METHOD_NOISE_SUPPRESSION: + ret = audio_io_create_noise_suppressor(rate, + channels, + sample_type, + framesize, + &_aio_effect, + &_adjust_framesize); + break; + case AUDIO_IO_EFFECT_METHOD_AUTO_GAIN_CONTROL: + ret = audio_io_create_auto_gain_control(rate, + channels, + sample_type, + framesize, + &_aio_effect, + &_adjust_framesize); + break; + default: + assert(0); + break; + } + + if (ret != AUDIO_IO_ERROR_NONE) { + fprintf(stderr, "failed to create audio effect\n"); + return -1; + } + + *aio_effect = _aio_effect; + *adjust_framesize = _adjust_framesize; + + return 0; +} + +int process_effect_aec(const char *input_file, const char *ref_file, char *output_file) +{ + int i; + size_t adjust_framesize; + audio_io_effect_method_h audio_io_effect; + + FILE *fp[STREAM_MAX] = { NULL, }; + char *buffer[STREAM_MAX] = { NULL, }; + size_t read_bytes; + size_t n_frames = 0, pcm_frames = 0; + int channels_base = AUDIO_CHANNEL_MONO - 1; + int ret; + + wav_specs_s wav_specs; + wav_header_s wav_header; + + _read_wav_info(input_file, &wav_header, &wav_specs); + + fp[STREAM_INPUT] = _fopen_at_pcm_start(input_file); + fp[STREAM_REFERENCE] = _fopen_at_pcm_start(ref_file); + + /* input and reference header must be same, anyway, we resuse reference wav header */ + fp[STREAM_OUTPUT] = fopen(output_file, "w"); + fwrite(&wav_header, sizeof(wav_header_s), 1, fp[STREAM_OUTPUT]); + + ret = _effect_factory(AUDIO_IO_EFFECT_METHOD_ACOUSTIC_ECHO_CANCELLATION, + wav_specs.rate, + channels_base + wav_specs.channels, + AUDIO_SAMPLE_TYPE_S16_LE, + wav_specs.rate / 100, + &audio_io_effect, + &adjust_framesize); + if (ret != 0) { + fprintf(stderr, "failed to create aec\n"); + _close_all_resources(fp, buffer); + return -1; + } + + read_bytes = adjust_framesize * wav_specs.frame_size; + assert(read_bytes > 0); + + fprintf(stderr, "[INFO] read size in bytes: %zu * %d\n", adjust_framesize, wav_specs.frame_size); + + for (i = 0; i < STREAM_MAX; i++) + buffer[i] = (char *)malloc(read_bytes); + + while (1) { + if ((ret = fread(buffer[STREAM_INPUT], read_bytes, 1, fp[STREAM_INPUT])) <= 0) + break; + + if ((ret = fread(buffer[STREAM_REFERENCE], read_bytes, 1, fp[STREAM_REFERENCE])) < 0) + break; + + ret = audio_io_apply_effect_method_with_reference(audio_io_effect, buffer[STREAM_INPUT], + buffer[STREAM_REFERENCE], + buffer[STREAM_OUTPUT]); + if (ret != AUDIO_IO_ERROR_NONE) { + fprintf(stderr, "failed to process AEC. ret(%x), frames(%zu)\n", ret, n_frames); + break; + } + + assert(fwrite(buffer[STREAM_OUTPUT], read_bytes, 1, fp[STREAM_OUTPUT]) > 0); + n_frames += adjust_framesize; + } + + audio_io_destroy_effect_method(audio_io_effect); + + _close_all_resources(fp, buffer); + + pcm_frames = wav_header.data_size / wav_specs.frame_size; + + fprintf(stderr, "[INFO] %zu/%zu frames were applied. remain(%zu)\n", + n_frames, pcm_frames, pcm_frames - n_frames); + + return 0; +} + +int process_effect(audio_io_effect_method_e method, const char *input_file, char *output_file) +{ + size_t adjust_framesize; + audio_io_effect_method_h audio_io_effect; + + FILE *fp[STREAM_MAX] = { NULL, }; + char *buffer[STREAM_MAX] = { NULL, }; + + size_t read_bytes; + size_t n_frames = 0, pcm_frames = 0; + int channels_base = AUDIO_CHANNEL_MONO - 1; + int ret; + + wav_specs_s wav_specs; + wav_header_s wav_header; + + _read_wav_info(input_file, &wav_header, &wav_specs); + + fp[STREAM_INPUT] = _fopen_at_pcm_start(input_file); + fp[STREAM_OUTPUT] = fopen(output_file, "w"); + + ret = _effect_factory(method, + wav_specs.rate, + channels_base + wav_specs.channels, + AUDIO_SAMPLE_TYPE_S16_LE, + wav_specs.rate / 100, + &audio_io_effect, + &adjust_framesize); + if (ret != 0) { + fprintf(stderr, "failed to create aec\n"); + _close_all_resources(fp, buffer); + return -1; + } + + wav_header.data_size = (uint32_t)((wav_header.data_size / adjust_framesize) * adjust_framesize); + fwrite(&wav_header, sizeof(wav_header_s), 1, fp[STREAM_OUTPUT]); + + read_bytes = adjust_framesize * wav_specs.frame_size; + assert(read_bytes > 0); + + fprintf(stderr, "[INFO] read size in bytes: %zu * %d\n", adjust_framesize, wav_specs.frame_size); + + assert((buffer[STREAM_INPUT] = (char *)malloc(read_bytes))); + assert((buffer[STREAM_OUTPUT] = (char *)malloc(read_bytes))); + + while(1) { + if ((ret = fread(buffer[STREAM_INPUT], read_bytes, 1, fp[STREAM_INPUT])) <= 0) + break; + + ret = audio_io_apply_effect_method(audio_io_effect, buffer[STREAM_INPUT], buffer[STREAM_OUTPUT]); + if (ret != AUDIO_IO_ERROR_NONE) { + fprintf(stderr, "failed to process. ret(%x), frames(%zu)\n", ret, n_frames); + break; + } + + assert(fwrite(buffer[STREAM_OUTPUT], read_bytes, 1, fp[STREAM_OUTPUT]) > 0); + n_frames += adjust_framesize; + } + + audio_io_destroy_effect_method(audio_io_effect); + + _close_all_resources(fp, buffer); + + pcm_frames = wav_header.data_size / wav_specs.frame_size; + + fprintf(stderr, "[INFO] %zu/%zu frames were applied. remain(%zu)\n", + n_frames, pcm_frames, pcm_frames - n_frames); + + return 0; +} + +int process_effect_chain(int method, const char *input_file, const char *reference_file, char *output_file) +{ + int ret, i; + wav_specs_s wav_specs; + wav_header_s wav_header; + int channels_base = AUDIO_CHANNEL_MONO - 1; + audio_io_effect_method_chain_builder_h builder = NULL; + audio_io_effect_method_chain_h chain = NULL; + + size_t read_bytes; + size_t n_frames = 0, pcm_frames = 0; + size_t adjust_framesize; + + FILE *fp[STREAM_MAX] = { NULL, }; + char *buffer[STREAM_MAX] = { NULL, }; + + _read_wav_info(input_file, &wav_header, &wav_specs); + + ret = audio_io_create_effect_method_chain_builder( + wav_specs.rate, + channels_base + wav_specs.channels, + AUDIO_SAMPLE_TYPE_S16_LE, + wav_specs.rate / 100, + &builder); + if (ret != AUDIO_IO_ERROR_NONE) { + fprintf(stderr, "failed to create effect method chain builder\n"); + return -1; + } + + if (method & AUDIO_IO_EFFECT_METHOD_AUTO_GAIN_CONTROL) { + ret = audio_io_append_effect_method_chain_builder(builder, AUDIO_IO_EFFECT_METHOD_AUTO_GAIN_CONTROL); + if (ret != AUDIO_IO_ERROR_NONE) { + fprintf(stderr, "failed to append effect method chain builder\n"); + goto fail; + } + } + + if (method & AUDIO_IO_EFFECT_METHOD_NOISE_SUPPRESSION) { + ret = audio_io_append_effect_method_chain_builder(builder, AUDIO_IO_EFFECT_METHOD_NOISE_SUPPRESSION); + if (ret != AUDIO_IO_ERROR_NONE) { + fprintf(stderr, "failed to append effect method chain builder\n"); + goto fail; + } + } + + if (method & AUDIO_IO_EFFECT_METHOD_ACOUSTIC_ECHO_CANCELLATION) { + ret = audio_io_append_effect_method_chain_builder(builder, AUDIO_IO_EFFECT_METHOD_ACOUSTIC_ECHO_CANCELLATION); + if (ret != AUDIO_IO_ERROR_NONE) { + fprintf(stderr, "failed to append effect method chain builder\n"); + goto fail; + } + } + + ret = audio_io_build_effect_method_chain(builder, &chain, &adjust_framesize); + if (ret != AUDIO_IO_ERROR_NONE) { + fprintf(stderr, "failed to build effect method chain builder\n"); + goto fail; + } + + fp[STREAM_INPUT] = _fopen_at_pcm_start(input_file); + fp[STREAM_REFERENCE] = _fopen_at_pcm_start(reference_file); + fp[STREAM_OUTPUT] = fopen(output_file, "w"); + + wav_header.data_size = (uint32_t)((wav_header.data_size / adjust_framesize) * adjust_framesize); + fwrite(&wav_header, sizeof(wav_header_s), 1, fp[STREAM_OUTPUT]); + + read_bytes = adjust_framesize * wav_specs.frame_size; + assert(read_bytes > 0); + + for (i = 0; i < STREAM_MAX; i++) + buffer[i] = (char *)malloc(read_bytes); + + fprintf(stderr, "[INFO] read size in bytes: %zu * %d\n", adjust_framesize, wav_specs.frame_size); + + while (1) { + if ((ret = fread(buffer[STREAM_INPUT], read_bytes, 1, fp[STREAM_INPUT])) <= 0) + break; + + if ((ret = fread(buffer[STREAM_REFERENCE], read_bytes, 1, fp[STREAM_REFERENCE])) < 0) + break; + + ret = audio_io_apply_effect_method_chain(chain, buffer[STREAM_INPUT], + buffer[STREAM_REFERENCE], + buffer[STREAM_OUTPUT]); + if (ret != AUDIO_IO_ERROR_NONE) { + fprintf(stderr, "failed to process AEC. ret(%x), frames(%zu)\n", ret, n_frames); + break; + } + + assert(fwrite(buffer[STREAM_OUTPUT], read_bytes, 1, fp[STREAM_OUTPUT]) > 0); + n_frames += adjust_framesize; + } + + audio_io_destroy_effect_method_chain_builder(builder); + audio_io_destroy_effect_method_chain(chain); + _close_all_resources(fp, buffer); + + pcm_frames = wav_header.data_size / wav_specs.frame_size; + + fprintf(stderr, "[INFO] %zu/%zu frames were applied. remain(%zu)\n", + n_frames, pcm_frames, pcm_frames - n_frames); + + return 0; + +fail: + if (builder) + audio_io_destroy_effect_method_chain_builder(builder); + + _close_all_resources(fp, buffer); + + return -1; +} + +int atoi_safe(const char *optarg) +{ + for (size_t i = 0; i < strlen(optarg); i++) + if (!isdigit(optarg[i])) + return -1; + + return atoi(optarg); +} + +int main(int argc, char **argv) +{ + int option; + const char *optstring = "e:i:r:o:"; + audio_io_effect_method_e method = 0; + char *input_file = NULL; + char *reference_file = NULL; + char *output_file = NULL; + int effect; + int ret; + + setbuf(stdout, NULL); + + while (-1 != (option = getopt(argc, argv, optstring))) { + switch (option) { + case 'e': + if ((effect = atoi_safe(optarg)) < 0) { + _print_usage(); + return 0; + } + method = (audio_io_effect_method_e)effect; + break; + case 'i': + input_file = optarg; + break; + case 'r': + reference_file = optarg; + break; + case 'o': + output_file = optarg; + break; + default: + _print_usage(); + return 0; + } + } + + if (_check_args_validation(input_file) < 0) { + _print_usage(); + return -1; + } + + fprintf(stderr, "[INFO] input file: %s\n", input_file); + + if (method & AUDIO_IO_EFFECT_METHOD_ACOUSTIC_ECHO_CANCELLATION) { + if (_check_args_validation(reference_file) == 0) { + fprintf(stderr, "[INFO] reference file: %s\n", reference_file); + } else { + _print_usage(); + return -1; + } + } + + if (!output_file) { + fprintf(stderr, "output file is null\n"); + _print_usage(); + return -1; + } + + fprintf(stderr, "[INFO] output file: %s\n", output_file); + + if (method == AUDIO_IO_EFFECT_METHOD_ACOUSTIC_ECHO_CANCELLATION) + ret = process_effect_aec(input_file, reference_file, output_file); + else if (method == AUDIO_IO_EFFECT_METHOD_NOISE_SUPPRESSION) + ret = process_effect(method, input_file, output_file); + else if (method == AUDIO_IO_EFFECT_METHOD_AUTO_GAIN_CONTROL) + ret = process_effect(method, input_file, output_file); + else + ret = process_effect_chain(method, input_file, reference_file, output_file); + + fprintf(stderr, "[INFO] result %d\n", ret); + + return 0; +} + -- 2.7.4