90 Commits
0.0.0 ... dev

Author SHA1 Message Date
f5be7b35a8 [DEV] integrate GLD 2021-12-15 22:46:23 +01:00
71e6089a40 [VERSION] update dev tag version 2021-02-16 21:47:40 +01:00
6dfa58dedd [RELEASE] Release v1.0.0 2021-02-16 21:47:40 +01:00
121602813e [DEBUG] update new API of lutin log 2019-05-03 10:18:23 +02:00
b53e67044f [DEV] add tools to record words 2019-04-01 22:11:44 +02:00
715598a0ae [DEV] add a basic tool to record audio sentence/ word or other things ... 2019-01-04 21:29:32 +01:00
1773297dab [DEV] update new etk Uri API 2018-10-23 22:19:32 +02:00
965df2ee2f [DEV] update etk null 2018-06-19 22:13:48 +02:00
9a21d57c1f [DEBUG] build is back 2017-11-07 13:38:04 +01:00
be4f6217d0 [DEV] update to the new ETK allocator wrapper 2017-10-21 19:05:21 +02:00
38c9a69e1e [DEV] remove unneded return 2017-10-09 10:19:54 +02:00
0c57614b64 [DEV] update new ETK 2017-09-26 15:57:44 +02:00
c3d78cf387 [DEV] continue removing STL 2017-09-14 00:59:21 +02:00
2ee24d5baa [DEV] remove STL 2017-09-07 23:38:26 +02:00
0e175ad23c [DEV] continue removing stl 2017-08-28 00:08:50 +02:00
c3b5ba2bdd [DEV] a a default user config file 2017-04-26 22:09:23 +00:00
ddc9150179 Change licence APACHE-2 to MPL-2 ==> force source redistribution and permit static link 2017-01-05 21:28:23 +01:00
5fe729f4dc [DEBUG] corect order of dot display 2016-11-17 00:20:31 +01:00
8fef74e061 [DEV] update dev tag version 2016-10-24 22:25:37 +02:00
b114de5f1d [RELEASE] new version 0.4.0 2016-10-24 22:25:37 +02:00
d13298acf9 [CI] update build CI 2016-10-24 22:25:37 +02:00
a5ca43b7dd [DEV] remove dependency of unistd.h 2016-10-13 21:29:18 +02:00
abc2ce6a49 [DEV] Update new lutin 2.2.0 (no legacy support) 2016-10-04 23:41:29 +02:00
6a2e12c2ad [DEBUG] correct .hpp port + clean libC include 2016-10-02 23:42:50 +02:00
7d38eb190d [DEV/API] change .h in .hpp 2016-10-02 22:20:33 +02:00
bc47714cff [DEV] add support of the output float,double,int32/64 muxer 2016-09-27 21:46:47 +02:00
6ecf73bb11 [DEV] CORRECT INDENTATION 2016-09-26 22:31:05 +02:00
417845abc2 [DOC] wrong website 2016-09-16 21:05:30 +02:00
5acf1ead90 [DOC] update sample to the new doxy methode 2016-09-14 22:19:22 +02:00
e80670fd0c [DEV] remove catkin build methode 2016-09-14 21:43:24 +02:00
77241b828d [DEV] update dev tag version 2016-09-12 21:07:12 +02:00
d0bc917536 [RELEASE] create release 0.3.1 2016-09-12 21:06:37 +02:00
4fcde8de4f [DEV] update to future lutin 2.0 2016-09-08 21:35:02 +02:00
d20753518c [DEV] update to future lutin 2.0 2016-09-07 22:05:42 +02:00
2ffe31a3e9 [DEV] update dev tag version 2016-08-30 22:54:57 +02:00
f91dc09ac1 [RELEASE] create release 0.3.0 2016-08-30 22:54:08 +02:00
2566843a66 [DEV] add support of multiple input stream type 2016-08-22 21:52:31 +02:00
9a37b437c6 [DEV] update sharedPtr 2016-07-19 21:43:58 +02:00
e83ac7620d [DEV] rm __class__ 2016-05-02 22:01:55 +02:00
434c8097d0 [DEV] update the change on 'enum' to 'enum class' 2016-04-29 23:16:07 +02:00
07a56e29ae [DEV] update to ne new ejson API (remove helper) 2016-04-28 23:46:12 +02:00
aa5df52e14 [DEV] update new ejson interface 2016-04-20 21:19:11 +02:00
c9bb77e8d6 [DEBUG] correct set titile 2016-03-16 23:57:13 +01:00
ef0bda6405 [DEV] build again 2016-03-14 21:26:44 +01:00
e7d5b421c3 [DEV] update of external of elog and ethread 2016-03-08 21:29:34 +01:00
dd2e5240e3 [DEV] add basic subfolder parser request 2016-02-23 21:56:42 +01:00
f1643a1231 [DEV] replace 'include guard' with 'pragma once' 2016-02-02 21:18:54 +01:00
569d59e05a [DEV] update new lutin 0.8.0 2015-10-14 21:21:03 +02:00
88c8178a72 [DEV] update next lutin version and debug android play audio 2015-09-24 21:44:04 +02:00
dbd66a0157 [DEV] update Build interface 2015-09-14 21:11:04 +02:00
5cab7f39eb [CI] update travis with new interface (no sudo) 2015-08-24 23:55:27 +02:00
9323412a44 [DEV] update test with gale 2015-08-24 22:29:47 +02:00
58ceff9371 [DEV] start work on File node 2015-07-10 23:10:16 +02:00
c329af6863 [DEV] Add mute in volume manager 2015-07-02 21:17:24 +02:00
3ce856c1c6 [DEV] better viewer 2015-07-01 22:00:09 +02:00
0692b6f8bc [DEV] add basic input viewer widget 2015-06-30 21:29:08 +02:00
f791cb3613 [DEV] indent correction 2015-06-26 22:20:04 +02:00
972bbf60bc [CI] remove test (must have hardware) 2015-06-16 22:02:08 +02:00
2ed71b8c7a [CI] remove pulse from dependency (old) 2015-06-16 21:17:08 +02:00
10820f0c86 [DOC] Create readme 2015-06-15 22:27:39 +02:00
771aae6469 [CI] travis orchestra missing C 2015-06-15 22:17:17 +02:00
9b8351e489 [DEV] update new worktree 2015-06-15 19:36:08 +02:00
50dcfaff82 [DEV] update new orchestra modification 2015-06-14 18:33:24 +02:00
63fb66d045 [DEV] some basic update 2015-06-11 22:17:17 +02:00
2071cf1c46 [DEV] add capacity of example 2015-05-12 21:49:16 +02:00
f2f57d4081 [BUILD] update new lutin 0.5.3 2015-05-08 22:36:02 +02:00
4ba07ac087 [DEV] add log print 2015-04-17 21:49:52 +02:00
f1d937e173 [BUILD] correct cakin build 2015-04-14 22:01:37 +02:00
be5489bc73 [DEV] update Catkin 2015-04-14 21:53:14 +02:00
41f55ab83d [DEV] update audio time interface 2015-04-13 21:49:48 +02:00
620fb8e54f [DEV] update the input output work 2015-04-11 17:43:09 +02:00
8daa32a2f7 [DEBUG] build error in portaudio 2015-04-11 13:21:38 +02:00
26e71b2a92 [DEV] rename lib river in audio::river 2015-04-10 00:07:09 +02:00
bf05f46f24 [STYLE] update 2015-03-26 23:35:43 +01:00
6622cd8237 [DEV] update Log interafce 2015-03-25 21:14:12 +01:00
13043536c2 [DEV] integrate echo cutter 2015-03-24 21:29:06 +01:00
58da3cf125 [DEV] work on the stream of AEC 2015-03-23 22:46:21 +01:00
3f6d736454 [DOC] start doc might be OK 2015-03-23 21:26:45 +01:00
1c34c157ab [DOC] Start riting tutorials and basic documentation. 2015-03-22 20:44:37 +01:00
3891b0ac52 [DOC] write nealy all documentation on the class 2015-03-22 09:29:21 +01:00
4c3fe665cf [DEBUG] correct segfault 2015-03-20 21:45:30 +01:00
ae0aa6aa6c [DEV] replace NULL => nullptr 2015-03-19 21:55:55 +01:00
967b2386bd [BUILD] set catkin etk correct define 2015-03-18 23:43:06 +01:00
d272410836 [DEV] Correct mixer of multiple flow 2015-03-18 21:48:57 +01:00
ee126927cd [DEV] mode Circular buffer in Drain lib 2015-03-16 21:03:23 +01:00
08ae045bf0 [DEV] correct the test and catkin build 2015-03-13 23:54:54 +01:00
0d448bed76 [DEV] rework test with new API 2015-03-13 23:22:17 +01:00
57c4795a1e [DEBUG] compilation OK 2015-03-12 23:17:14 +01:00
f9ae8f23bd [DEV] some River rework 2015-03-12 22:28:15 +01:00
ec4eae4242 [DEV] add basic catkin 2015-03-12 21:33:44 +01:00
99 changed files with 8245 additions and 4923 deletions

5
.gitignore vendored
View File

@@ -1,3 +1,8 @@
__pycache__
.bck
out
target
build
################################### ###################################
# backup files # backup files
################################### ###################################

98
.travis.yml Normal file
View File

@@ -0,0 +1,98 @@
language: cpp
sudo: required
dist: trusty
branches:
only:
- master
- dev
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- g++-4.9
- expect
- binutils-mingw-w64-x86-64 # 64bit MinGW
- gcc-mingw-w64-x86-64
- g++-mingw-w64-x86-64
matrix:
include:
- os: linux
env: CONF=release BUILDER=gcc TARGET=Linux TAG=Linux COMPILATOR_OPTION="--compilator-version=4.9" GCOV=--gcov
compiler: gcc
- os: linux
env: CONF=debug BUILDER=clang TARGET=Linux
compiler: clang
- os: linux
env: CONF=release BUILDER=gcc TARGET=Windows TAG=Mingw
compiler: x86_64-w64-mingw32-gcc
- os: linux
env: CONF=release BUILDER=gcc TARGET=Android TAG=Android DISABLE_PACKAGE=-p
compiler: gcc
- os: osx
env: CONF=release BUILDER=clang TARGET=MacOs TAG=MacOs
compiler: clang
- os: osx
env: CONF=release BUILDER=clang TARGET=IOs TAG=IOs
compiler: clang
install:
- cd ..
- pip install --user lutin
- if [ "$TAG" == "Android" ]; then
git clone --depth 1 --branch master https://github.com/HeeroYui/android-download-tool;
./android-download-tool/dl-android.sh;
fi
- git clone --depth 1 --branch master https://github.com/atria-soft/ci.git
- cd -
before_script:
- cd ..
- git clone https://github.com/atria-soft/etk.git -b $TRAVIS_BRANCH
- git clone https://github.com/atria-soft/elog.git -b $TRAVIS_BRANCH
- git clone https://github.com/atria-soft/ememory.git -b $TRAVIS_BRANCH
- git clone https://github.com/atria-soft/echrono.git -b $TRAVIS_BRANCH
- git clone https://github.com/atria-soft/ethread.git -b $TRAVIS_BRANCH
- git clone https://github.com/atria-soft/ejson.git -b $TRAVIS_BRANCH
- git clone https://github.com/atria-soft/jvm-basics.git -b $TRAVIS_BRANCH
- git clone https://github.com/musicdsp/audio.git -b $TRAVIS_BRANCH
- git clone https://github.com/musicdsp/audio-algo-drain.git -b $TRAVIS_BRANCH
- git clone https://github.com/musicdsp/audio-algo-river.git -b $TRAVIS_BRANCH
- git clone https://github.com/musicdsp/audio-algo-speex.git -b $TRAVIS_BRANCH
- git clone https://github.com/musicdsp/audio-drain.git -b $TRAVIS_BRANCH
- git clone https://github.com/musicdsp/audio-orchestra.git -b $TRAVIS_BRANCH
- git clone https://github.com/generic-library/gtest-lutin.git --recursive
- git clone https://github.com/generic-library/z-lutin.git --recursive
- git clone https://github.com/generic-library/speex-dsp-lutin.git --recursive
- pwd
- ls -l
- if [ "$TRAVIS_OS_NAME" == "osx" ]; then
export PATH=$PATH:/Users/travis/Library/Python/2.7/bin/;
fi
- ./ci/build_send.py --tag=$TAG --status=START;
script:
- lutin -w -j4 -C -P -t$TARGET -c $BUILDER $COMPILATOR_OPTION $BUS -m $CONF $GCOV $DISABLE_PACKAGE audio-river-test*; STATUS=$?
- ./ci/build_send.py --tag=$TAG --status="$STATUS";
after_script:
- if [ "$GCOV" != "" ]; then
./ci/warning_send.py --find-path ./out/Linux_x86_64/$CONF/build/$BUILDER/audio-river/;
fi
#- lutin -w -j4 -C -P -t$TARGET -c $BUILDER $COMPILATOR_OPTION $BUS -m $CONF $GCOV $DISABLE_PACKAGE audio-river-test?run:--elog-level=3 | tee out_test.txt
#- if [ "$GCOV" != "" ]; then
# ./ci/test_send.py --file=out_test.txt;
# lutin -C -P -t $TARGET -c $BUILDER $COMPILATOR_OPTION $BUS -m $CONF -p audio-river?gcov;
# ./ci/coverage_send.py --json=out/Linux_x86_64/$CONF/build/$BUILDER/audio-river/audio-river_coverage.json;
# fi
notifications:
email:
- yui.heero@gmail.com

36
Doxyfile Normal file
View File

@@ -0,0 +1,36 @@
PROJECT_NAME = river
PROJECT_NUMBER = latest
OUTPUT_DIRECTORY = dox/
SORT_MEMBERS_CTORS_1ST = YES
SORT_GROUP_NAMES = YES
SORT_BY_SCOPE_NAME = YES
SHOW_NAMESPACES = YES
RECURSIVE = YES
EXTRACT_ALL = NO
EXCLUDE_PATTERNS = */examples/*
SOURCE_BROWSER = NO
ALPHABETICAL_INDEX = NO
GENERATE_HTML = YES
#HTML_HEADER = /home/edupin/work/master-2015-02-04/doc/templates/doxygen/header.html
#HTML_FOOTER = /home/edupin/work/master-2015-02-04/doc/templates/doxygen/footer.html
#HTML_STYLESHEET = /home/edupin/work/master-2015-02-04/doc/templates/doxygen/doxygen.css
GENERATE_TREEVIEW = YES
CLASS_DIAGRAMS = YES
HAVE_DOT = YES
CLASS_GRAPH = YES
COLLABORATION_GRAPH = NO
INCLUDE_GRAPH = NO
INCLUDED_BY_GRAPH = NO
GRAPHICAL_HIERARCHY = NO
DOT_MULTI_TARGETS = YES
INPUT = river/
ENABLE_PREPROCESSING = YES
MACRO_EXPANSION = YES
SKIP_FUNCTION_MACROS = YES
BRIEF_MEMBER_DESC = NO
VERBATIM_HEADERS = NO
TAB_SIZE = 4
#EXAMPLE_PATH = sample/
GENERATE_XML = YES
GENERATE_LATEX = NO
GENERATE_TAGFILE = dox/ruver-doc.tag

0
GLDParseSubFolders.txt Normal file
View File

34
GLD_audio-river-test.json Normal file
View File

@@ -0,0 +1,34 @@
{
"type":"BINARY",
"sub-type":"TEST",
"group-id":"com.atria-soft",
"description":"Multi-nodal audio interface test",
"license":"MPL-2",
"license-file":"file://LICENSE",
"maintainer":"file://authors.txt",
"author":"file://authors.txt",
"version":"file://version.txt",
"code-quality":"MEDIUM",
"source": [
"test/main.cpp",
"test/testAEC.cpp",
"test/testEchoDelay.cpp",
"test/testFormat.cpp",
"test/testMuxer.cpp",
"test/testPlaybackCallback.cpp",
"test/testPlaybackWrite.cpp",
"test/testRecordCallback.cpp",
"test/testRecordRead.cpp",
"test/testVolume.cpp"
],
"compilation-version": {
"c++": 2017
},
"dependency": [
"audio-river",
"etest",
"etk",
"test-debug"
]
}

58
GLD_audio-river.json Normal file
View File

@@ -0,0 +1,58 @@
{
"type":"LIBRARY",
"group-id":"com.atria-soft",
"description":"Multi-nodal audio interface",
"license":"MPL-2",
"license-file":"file://LICENSE",
"maintainer":"file://authors.txt",
"author":"file://authors.txt",
"version":"file://version.txt",
"code-quality":"MEDIUM",
"source": [
"audio/river/debug.cpp",
"audio/river/river.cpp",
"audio/river/Manager.cpp",
"audio/river/Interface.cpp",
"audio/river/io/Group.cpp",
"audio/river/io/Node.cpp",
"audio/river/io/NodeOrchestra.cpp",
"audio/river/io/NodePortAudio.cpp",
"audio/river/io/NodeAEC.cpp",
"audio/river/io/NodeMuxer.cpp",
"audio/river/io/Manager.cpp"
],
"header": [
"audio/river/river.hpp",
"audio/river/Manager.hpp",
"audio/river/Interface.hpp",
"audio/river/io/Group.hpp",
"audio/river/io/Node.hpp",
"audio/river/io/Manager.hpp"
],
"path":[
"."
],
"compilation-version": {
"c++": 2017
},
"dependency": [
"audio",
"audio-drain",
"ejson",{
"name": "audio-orchestra",
"optional": true,
"export": true,
"flag": {
"c++": "-DAUDIO_RIVER_BUILD_ORCHESTRA"
}
},{
"name": "portaudio",
"optional": true,
"export": true,
"flag": {
"c++": "-DAUDIO_RIVER_BUILD_PORTAUDIO"
}
}
]
}

373
LICENSE Normal file
View File

@@ -0,0 +1,373 @@
Mozilla Public License Version 2.0
==================================
1. Definitions
--------------
1.1. "Contributor"
means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
--------------------------------
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.

88
README.md Normal file
View File

@@ -0,0 +1,88 @@
audio-river
===========
`audio-river` is a High level hardware audio interface that connect to orchestra (multi-platform backend)
Release (master)
----------------
[![Build Status](https://travis-ci.org/musicdsp/audio-river.svg?branch=master)](https://travis-ci.org/musicdsp/audio-river)
[![Coverage Status](http://musicdsp.com/ci/coverage/musicdsp/audio-river.svg?branch=master)](http://musicdsp.com/ci/musicdsp/audio-river)
[![Test Status](http://musicdsp.com/ci/test/musicdsp/audio-river.svg?branch=master)](http://musicdsp.com/ci/musicdsp/audio-river)
[![Warning Status](http://musicdsp.com/ci/warning/musicdsp/audio-river.svg?branch=master)](http://musicdsp.com/ci/musicdsp/audio-river)
[![Build Status](http://musicdsp.com/ci/build/musicdsp/audio-river.svg?branch=master&tag=Linux)](http://musicdsp.com/ci/musicdsp/audio-river)
[![Build Status](http://musicdsp.com/ci/build/musicdsp/audio-river.svg?branch=master&tag=MacOs)](http://musicdsp.com/ci/musicdsp/audio-river)
[![Build Status](http://musicdsp.com/ci/build/musicdsp/audio-river.svg?branch=master&tag=Mingw)](http://musicdsp.com/ci/musicdsp/audio-river)
[![Build Status](http://musicdsp.com/ci/build/musicdsp/audio-river.svg?branch=master&tag=Android)](http://musicdsp.com/ci/musicdsp/audio-river)
[![Build Status](http://musicdsp.com/ci/build/musicdsp/audio-river.svg?branch=master&tag=IOs)](http://musicdsp.com/ci/musicdsp/audio-river)
Developement (dev)
------------------
[![Build Status](https://travis-ci.org/musicdsp/audio-river.svg?branch=dev)](https://travis-ci.org/musicdsp/audio-river)
[![Coverage Status](http://musicdsp.com/ci/coverage/musicdsp/audio-river.svg?branch=dev)](http://musicdsp.com/ci/musicdsp/audio-river)
[![Test Status](http://musicdsp.com/ci/test/musicdsp/audio-river.svg?branch=dev)](http://musicdsp.com/ci/musicdsp/audio-river)
[![Warning Status](http://musicdsp.com/ci/warning/musicdsp/audio-river.svg?branch=dev)](http://musicdsp.com/ci/musicdsp/audio-river)
[![Build Status](http://musicdsp.com/ci/build/musicdsp/audio-river.svg?branch=dev&tag=Linux)](http://musicdsp.com/ci/musicdsp/audio-river)
[![Build Status](http://musicdsp.com/ci/build/musicdsp/audio-river.svg?branch=dev&tag=MacOs)](http://musicdsp.com/ci/musicdsp/audio-river)
[![Build Status](http://musicdsp.com/ci/build/musicdsp/audio-river.svg?branch=dev&tag=Mingw)](http://musicdsp.com/ci/musicdsp/audio-river)
[![Build Status](http://musicdsp.com/ci/build/musicdsp/audio-river.svg?branch=dev&tag=Android)](http://musicdsp.com/ci/musicdsp/audio-river)
[![Build Status](http://musicdsp.com/ci/build/musicdsp/audio-river.svg?branch=dev&tag=IOs)](http://musicdsp.com/ci/musicdsp/audio-river)
Instructions
============
download Build system:
----------------------
sudo pip install lutin
sudo pip install pillow
download the software:
----------------------
mkdir WORKING_DIRECTORY
cd WORKING_DIRECTORY
git clone https://github.com/atria-soft/etk.git
git clone https://github.com/atria-soft/elog.git
git clone https://github.com/atria-soft/ememory.git
git clone https://github.com/atria-soft/ethread.git
git clone https://github.com/atria-soft/ejson.git
git clone https://github.com/musicdsp/audio.git
git clone https://github.com/musicdsp/audio-algo-drain.git
git clone https://github.com/musicdsp/audio-algo-river.git
git clone https://github.com/musicdsp/audio-algo-speex.git
git clone https://github.com/musicdsp/audio-drain.git
git clone https://github.com/musicdsp/audio-orchestra.git
git clone https://github.com/musicdsp/audio-river.git
git clone https://github.com/generic-library/gtest-lutin.git --recursive
git clone https://github.com/generic-library/z-lutin.git --recursive
git clone https://github.com/generic-library/speex-dsp-lutin.git --recursive
Compile software:
-----------------
cd WORKING_DIRECTORY
lutin -C -P audio-river-test?build?run
License (MPL v2.0)
=====================
Copyright audio-river Edouard DUPIN
Licensed under the Mozilla Public 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
https://www.mozilla.org/MPL/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.

522
audio/river/Interface.cpp Normal file
View File

@@ -0,0 +1,522 @@
/** @file
* @author Edouard DUPIN
* @copyright 2015, Edouard DUPIN, all right reserved
* @license MPL v2.0 (see license file)
*/
#include <audio/river/debug.hpp>
#include <audio/river/Interface.hpp>
#include <audio/river/io/Node.hpp>
#include <audio/drain/EndPointCallback.hpp>
#include <audio/drain/EndPointWrite.hpp>
#include <audio/drain/EndPointRead.hpp>
#include <audio/drain/Volume.hpp>
audio::river::Interface::Interface(void) {
static uint32_t uid = 0;
m_uid = uid++;
}
bool audio::river::Interface::init(float _freq,
const etk::Vector<audio::channel>& _map,
audio::format _format,
ememory::SharedPtr<audio::river::io::Node> _node,
const ejson::Object& _config) {
etk::Vector<audio::channel> map(_map);
m_node = _node;
m_config = _config;
m_mode = audio::river::modeInterface_unknow;
etk::String type = m_config["io"].toString().get("error");
static int32_t uid=0;
m_name = _node->getName() + "__" + (_node->isInput()==true?"input":"output") + "__" + type + "__" + etk::toString(uid++);
if (type == "output") {
m_mode = audio::river::modeInterface_output;
} else if (type == "input") {
m_mode = audio::river::modeInterface_input;
} else if (type == "feedback") {
m_mode = audio::river::modeInterface_feedback;
}
// register interface to be notify from the volume change.
m_node->registerAsRemote(sharedFromThis());
if (map.size() == 0) {
RIVER_INFO("Select auto map system ...");
map = m_node->getInterfaceFormat().getMap();
RIVER_INFO(" ==> " << map);
}
// Create convertion interface
if ( m_node->isInput() == true
&& m_mode == audio::river::modeInterface_input) {
m_process.setInputConfig(m_node->getInterfaceFormat());
// Add volume only if the Low level has a volume (otherwise it will be added by the application)
ememory::SharedPtr<audio::drain::VolumeElement> tmpVolume = m_node->getVolume();
if (tmpVolume != null) {
// add all time the volume stage :
ememory::SharedPtr<audio::drain::Volume> algo = audio::drain::Volume::create();
//algo->setInputFormat(m_node->getInterfaceFormat());
algo->setName("volume");
m_process.pushBack(algo);
RIVER_INFO(" add volume for node");
algo->addVolumeStage(tmpVolume);
}
m_process.setOutputConfig(audio::drain::IOFormatInterface(map, _format, _freq));
} else if ( m_node->isOutput() == true
&& m_mode == audio::river::modeInterface_output) {
m_process.setInputConfig(audio::drain::IOFormatInterface(map, _format, _freq));
// Add volume only if the Low level has a volume (otherwise it will be added by the application)
ememory::SharedPtr<audio::drain::VolumeElement> tmpVolume = m_node->getVolume();
if (tmpVolume != null) {
// add all time the volume stage :
ememory::SharedPtr<audio::drain::Volume> algo = audio::drain::Volume::create();
//algo->setOutputFormat(m_node->getInterfaceFormat());
algo->setName("volume");
m_process.pushBack(algo);
RIVER_INFO(" add volume for node");
algo->addVolumeStage(tmpVolume);
}
m_process.setOutputConfig(m_node->getInterfaceFormat());
} else if ( m_node->isOutput() == true
&& m_mode == audio::river::modeInterface_feedback) {
m_process.setInputConfig(m_node->getHarwareFormat());
// note : feedback has no volume stage ...
m_process.setOutputConfig(audio::drain::IOFormatInterface(map, _format, _freq));
} else {
RIVER_ERROR("Can not link virtual interface with type : " << m_mode << " to a hardware interface " << (m_node->isInput()==true?"input":"output"));
return false;
}
return true;
}
ememory::SharedPtr<audio::river::Interface> audio::river::Interface::create(float _freq,
const etk::Vector<audio::channel>& _map,
audio::format _format,
const ememory::SharedPtr<audio::river::io::Node>& _node,
const ejson::Object& _config) {
ememory::SharedPtr<audio::river::Interface> out = ememory::SharedPtr<audio::river::Interface>(ETK_NEW(audio::river::Interface));
out->init(_freq, _map, _format, _node, _config);
return out;
}
audio::river::Interface::~Interface() {
//stop(true, true);
ethread::RecursiveLock lock(m_mutex);
//m_node->interfaceRemove(sharedFromThis());
}
/*
bool audio::river::Interface::hasEndPoint() {
}
*/
void audio::river::Interface::setReadwrite() {
ethread::RecursiveLock lock(m_mutex);
m_process.removeAlgoDynamic();
if (m_process.hasType<audio::drain::EndPoint>() ) {
RIVER_ERROR("Endpoint is already present ==> can not change");
return;
}
RIVER_WARNING("Add output ReadWrite");
if (m_node->isInput() == true) {
m_process.removeIfLast<audio::drain::EndPoint>();
ememory::SharedPtr<audio::drain::EndPointRead> algo = audio::drain::EndPointRead::create();
m_process.pushBack(algo);
} else {
m_process.removeIfFirst<audio::drain::EndPoint>();
ememory::SharedPtr<audio::drain::EndPointWrite> algo = audio::drain::EndPointWrite::create();
m_process.pushFront(algo);
}
}
void audio::river::Interface::setOutputCallback(audio::drain::playbackFunction _function) {
ethread::RecursiveLock lock(m_mutex);
if (m_mode != audio::river::modeInterface_output) {
RIVER_ERROR("Can not set output endpoint on other than a output IO");
return;
}
RIVER_WARNING("Add output callback");
m_process.removeAlgoDynamic();
m_process.removeIfFirst<audio::drain::EndPoint>();
ememory::SharedPtr<audio::drain::Algo> algo = audio::drain::EndPointCallback::create(_function);
m_process.pushFront(algo);
}
void audio::river::Interface::setInputCallback(audio::drain::recordFunction _function) {
ethread::RecursiveLock lock(m_mutex);
if (m_mode == audio::river::modeInterface_output) {
RIVER_ERROR("Can not set output endpoint on other than a input or feedback IO");
return;
}
RIVER_WARNING("Add input callback");
m_process.removeAlgoDynamic();
m_process.removeIfLast<audio::drain::EndPoint>();
ememory::SharedPtr<audio::drain::Algo> algo = audio::drain::EndPointCallback::create(_function);
m_process.pushBack(algo);
}
void audio::river::Interface::setWriteCallback(audio::drain::playbackFunctionWrite _function) {
ethread::RecursiveLock lock(m_mutex);
if (m_mode != audio::river::modeInterface_output) {
RIVER_ERROR("Can not set output endpoint on other than a output IO");
return;
}
RIVER_WARNING("Add output Write");
m_process.removeAlgoDynamic();
ememory::SharedPtr<audio::drain::EndPointWrite> algo = m_process.get<audio::drain::EndPointWrite>(0);
if (algo == null) {
return;
}
algo->setCallback(_function);
}
void audio::river::Interface::start(const audio::Time& _time) {
ethread::RecursiveLock lock(m_mutex);
RIVER_DEBUG("start [BEGIN]");
m_process.updateInterAlgo();
m_node->interfaceAdd(sharedFromThis());
RIVER_DEBUG("start [ END ]");
}
void audio::river::Interface::stop(bool _fast, bool _abort) {
ethread::RecursiveLock lock(m_mutex);
RIVER_DEBUG("stop [BEGIN]");
m_node->interfaceRemove(sharedFromThis());
RIVER_DEBUG("stop [ END]");
}
void audio::river::Interface::abort() {
ethread::RecursiveLock lock(m_mutex);
RIVER_DEBUG("abort [BEGIN]");
// TODO :...
RIVER_DEBUG("abort [ END ]");
}
bool audio::river::Interface::setParameter(const etk::String& _filter, const etk::String& _parameter, const etk::String& _value) {
RIVER_DEBUG("setParameter [BEGIN] : '" << _filter << "':'" << _parameter << "':'" << _value << "'");
bool out = false;
if ( _filter == "volume"
&& _parameter != "FLOW") {
RIVER_ERROR("Interface is not allowed to modify '" << _parameter << "' Volume just allowed to modify 'FLOW' volume");
return false;
}
ememory::SharedPtr<audio::drain::Algo> algo = m_process.get<audio::drain::Algo>(_filter);
if (algo == null) {
RIVER_ERROR("setParameter(" << _filter << ") ==> no filter named like this ...");
return false;
}
out = algo->setParameter(_parameter, _value);
RIVER_DEBUG("setParameter [ END ] : '" << out << "'");
return out;
}
etk::String audio::river::Interface::getParameter(const etk::String& _filter, const etk::String& _parameter) const {
RIVER_DEBUG("getParameter [BEGIN] : '" << _filter << "':'" << _parameter << "'");
etk::String out;
ememory::SharedPtr<const audio::drain::Algo> algo = m_process.get<const audio::drain::Algo>(_filter);
if (algo == null) {
RIVER_ERROR("setParameter(" << _filter << ") ==> no filter named like this ...");
return "[ERROR]";
}
out = algo->getParameter(_parameter);
RIVER_DEBUG("getParameter [ END ] : '" << out << "'");
return out;
}
etk::String audio::river::Interface::getParameterProperty(const etk::String& _filter, const etk::String& _parameter) const {
RIVER_DEBUG("getParameterProperty [BEGIN] : '" << _filter << "':'" << _parameter << "'");
etk::String out;
ememory::SharedPtr<const audio::drain::Algo> algo = m_process.get<const audio::drain::Algo>(_filter);
if (algo == null) {
RIVER_ERROR("setParameter(" << _filter << ") ==> no filter named like this ...");
return "[ERROR]";
}
out = algo->getParameterProperty(_parameter);
RIVER_DEBUG("getParameterProperty [ END ] : '" << out << "'");
return out;
}
void audio::river::Interface::write(const void* _value, size_t _nbChunk) {
ethread::RecursiveLock lock(m_mutex);
m_process.updateInterAlgo();
ememory::SharedPtr<audio::drain::EndPointWrite> algo = m_process.get<audio::drain::EndPointWrite>(0);
if (algo == null) {
return;
}
algo->write(_value, _nbChunk);
}
#if 0
// TODO : add API aCCess mutex for Read and write...
etk::Vector<int16_t> audio::river::Interface::read(size_t _nbChunk) {
// TODO :...
etk::Vector<int16_t> data;
/*
data.resize(_nbChunk*m_map.size(), 0);
m_mutex.lock();
int32_t nbChunkBuffer = m_circularBuffer.size() / m_map.size();
m_mutex.unlock();
while (nbChunkBuffer < _nbChunk) {
ethread::sleepMilliSeconds((1));
nbChunkBuffer = m_circularBuffer.size() / m_map.size();
}
m_mutex.lock();
for (size_t iii = 0; iii<data.size(); ++iii) {
data[iii] = m_circularBuffer[iii];
}
m_circularBuffer.erase(m_circularBuffer.begin(), m_circularBuffer.begin()+data.size());
m_mutex.unlock();
*/
return data;
}
#endif
void audio::river::Interface::read(void* _value, size_t _nbChunk) {
ethread::RecursiveLock lock(m_mutex);
m_process.updateInterAlgo();
// TODO :...
}
size_t audio::river::Interface::size() const {
ethread::RecursiveLock lock(m_mutex);
// TODO :...
return 0;
}
void audio::river::Interface::setBufferSize(size_t _nbChunk) {
ethread::RecursiveLock lock(m_mutex);
if (m_node->isInput() == true) {
ememory::SharedPtr<audio::drain::EndPointRead> algo = m_process.get<audio::drain::EndPointRead>(m_process.size()-1);
if (algo == null) {
RIVER_ERROR("Request set buffer size for Interface that is not READ or WRITE mode ...");
return;
}
algo->setBufferSize(_nbChunk);
return;
}
ememory::SharedPtr<audio::drain::EndPointWrite> algo = m_process.get<audio::drain::EndPointWrite>(0);
if (algo == null) {
RIVER_ERROR("Request set buffer size for Interface that is not READ or WRITE mode ...");
return;
}
algo->setBufferSize(_nbChunk);
}
void audio::river::Interface::setBufferSize(const echrono::microseconds& _time) {
ethread::RecursiveLock lock(m_mutex);
if (m_node->isInput() == true) {
ememory::SharedPtr<audio::drain::EndPointRead> algo = m_process.get<audio::drain::EndPointRead>(m_process.size()-1);
if (algo == null) {
RIVER_ERROR("Request set buffer size for Interface that is not READ or WRITE mode ...");
return;
}
algo->setBufferSize(_time);
return;
}
ememory::SharedPtr<audio::drain::EndPointWrite> algo = m_process.get<audio::drain::EndPointWrite>(0);
if (algo == null) {
RIVER_ERROR("Request set buffer size for Interface that is not READ or WRITE mode ...");
return;
}
algo->setBufferSize(_time);
}
size_t audio::river::Interface::getBufferSize() {
ethread::RecursiveLock lock(m_mutex);
if (m_node->isInput() == true) {
ememory::SharedPtr<audio::drain::EndPointRead> algo = m_process.get<audio::drain::EndPointRead>(m_process.size()-1);
if (algo == null) {
RIVER_ERROR("Request get buffer size for Interface that is not READ or WRITE mode ...");
return 0;
}
return algo->getBufferSize();
}
ememory::SharedPtr<audio::drain::EndPointWrite> algo = m_process.get<audio::drain::EndPointWrite>(0);
if (algo == null) {
RIVER_ERROR("Request get buffer size for Interface that is not READ or WRITE mode ...");
return 0;
}
return algo->getBufferSize();
}
echrono::microseconds audio::river::Interface::getBufferSizeMicrosecond() {
ethread::RecursiveLock lock(m_mutex);
if (m_node->isInput() == true) {
ememory::SharedPtr<audio::drain::EndPointRead> algo = m_process.get<audio::drain::EndPointRead>(m_process.size()-1);
if (algo == null) {
RIVER_ERROR("Request get buffer size for Interface that is not READ or WRITE mode ...");
return echrono::microseconds(0);
}
return algo->getBufferSizeMicrosecond();
}
ememory::SharedPtr<audio::drain::EndPointWrite> algo = m_process.get<audio::drain::EndPointWrite>(0);
if (algo == null) {
RIVER_ERROR("Request get buffer size for Interface that is not READ or WRITE mode ...");
return echrono::microseconds(0);
}
return algo->getBufferSizeMicrosecond();
}
size_t audio::river::Interface::getBufferFillSize() {
ethread::RecursiveLock lock(m_mutex);
if (m_node->isInput() == true) {
ememory::SharedPtr<audio::drain::EndPointRead> algo = m_process.get<audio::drain::EndPointRead>(m_process.size()-1);
if (algo == null) {
RIVER_ERROR("Request get buffer size for Interface that is not READ or WRITE mode ...");
return 0;
}
return algo->getBufferFillSize();
}
ememory::SharedPtr<audio::drain::EndPointWrite> algo = m_process.get<audio::drain::EndPointWrite>(0);
if (algo == null) {
RIVER_ERROR("Request get buffer size for Interface that is not READ or WRITE mode ...");
return 0;
}
return algo->getBufferFillSize();
}
echrono::microseconds audio::river::Interface::getBufferFillSizeMicrosecond() {
ethread::RecursiveLock lock(m_mutex);
if (m_node->isInput() == true) {
ememory::SharedPtr<audio::drain::EndPointRead> algo = m_process.get<audio::drain::EndPointRead>(m_process.size()-1);
if (algo == null) {
RIVER_ERROR("Request get buffer size for Interface that is not READ or WRITE mode ...");
return echrono::microseconds(0);
}
return algo->getBufferFillSizeMicrosecond();
}
ememory::SharedPtr<audio::drain::EndPointWrite> algo = m_process.get<audio::drain::EndPointWrite>(0);
if (algo == null) {
RIVER_ERROR("Request get buffer size for Interface that is not READ or WRITE mode ...");
return echrono::microseconds(0);
}
return algo->getBufferFillSizeMicrosecond();
}
void audio::river::Interface::clearInternalBuffer() {
ethread::RecursiveLock lock(m_mutex);
m_process.updateInterAlgo();
// TODO :...
}
audio::Time audio::river::Interface::getCurrentTime() const {
ethread::RecursiveLock lock(m_mutex);
// TODO :...
return audio::Time();
return audio::Time::now();
}
void audio::river::Interface::addVolumeGroup(const etk::String& _name) {
ethread::RecursiveLock lock(m_mutex);
RIVER_DEBUG("addVolumeGroup(" << _name << ")");
ememory::SharedPtr<audio::drain::Volume> algo = m_process.get<audio::drain::Volume>("volume");
if (algo == null) {
m_process.removeAlgoDynamic();
// add all time the volume stage :
algo = audio::drain::Volume::create();
algo->setName("volume");
if (m_node->isInput() == true) {
m_process.pushFront(algo);
} else {
m_process.pushBack(algo);
}
}
if (_name == "FLOW") {
// Local volume name
algo->addVolumeStage(ememory::makeShared<audio::drain::VolumeElement>(_name));
} else {
// get manager unique instance:
ememory::SharedPtr<audio::river::io::Manager> mng = audio::river::io::Manager::getInstance();
algo->addVolumeStage(mng->getVolumeGroup(_name));
}
}
void audio::river::Interface::systemNewInputData(audio::Time _time, const void* _data, size_t _nbChunk) {
ethread::RecursiveLock lockProcess(m_mutex);
void * tmpData = const_cast<void*>(_data);
m_process.push(_time, tmpData, _nbChunk);
}
void audio::river::Interface::systemNeedOutputData(audio::Time _time, void* _data, size_t _nbChunk, size_t _chunkSize) {
ethread::RecursiveLock lockProcess(m_mutex);
//RIVER_INFO("time : " << _time);
m_process.pull(_time, _data, _nbChunk, _chunkSize);
}
void audio::river::Interface::systemVolumeChange() {
ethread::RecursiveLock lockProcess(m_mutex);
ememory::SharedPtr<audio::drain::Volume> algo = m_process.get<audio::drain::Volume>("volume");
if (algo == null) {
return;
}
algo->volumeChange();
}
static void link(ememory::SharedPtr<etk::io::Interface>& _io, const etk::String& _first, const etk::String& _op, const etk::String& _second, bool _isLink=true) {
if (_op == "->") {
if (_isLink) {
*_io << " " << _first << " -> " << _second << ";\n";
} else {
*_io << " " << _first << " -> " << _second << " [style=dashed];\n";
}
} else if (_op == "<-") {
*_io << " " << _first << " -> " <<_second<< " [color=transparent];\n";
if (_isLink) {
*_io << " " << _second << " -> " << _first << " [constraint=false];\n";
} else {
*_io << " " << _second << " -> " << _first << " [constraint=false, style=dashed];\n";
}
}
}
etk::String audio::river::Interface::getDotNodeName() const {
if (m_mode == audio::river::modeInterface_input) {
return "API_" + etk::toString(m_uid) + "_input";
} else if (m_mode == audio::river::modeInterface_feedback) {
return "API_" + etk::toString(m_uid) + "_feedback";
} else if (m_mode == audio::river::modeInterface_output) {
return "API_" + etk::toString(m_uid) + "_output";
}
return "error";
}
void audio::river::Interface::generateDot(ememory::SharedPtr<etk::io::Interface>& _io, const etk::String& _nameIO, bool _isLink) {
*_io << " subgraph clusterInterface_" << m_uid << " {\n";
*_io << " color=orange;\n";
*_io << " label=\"[" << m_uid << "] Interface : " << m_name << "\";\n";
etk::String nameIn;
etk::String nameOut;
if ( m_mode == audio::river::modeInterface_input
|| m_mode == audio::river::modeInterface_feedback) {
m_process.generateDot(_io, 3, 10000+m_uid, nameIn, nameOut, false);
} else {
m_process.generateDot(_io, 3, 10000+m_uid, nameOut, nameIn, true);
}
if ( m_mode == audio::river::modeInterface_input
|| m_mode == audio::river::modeInterface_feedback) {
link(_io, _nameIO, "->", nameIn, _isLink);
} else {
link(_io, _nameIO, "<-", nameOut, _isLink);
}
*_io << " node [shape=Mdiamond];\n";
if (m_mode == audio::river::modeInterface_input) {
*_io << " " << getDotNodeName() << " [ label=\"API\\nINPUT\" ];\n";
link(_io, nameOut, "->", getDotNodeName());
} else if (m_mode == audio::river::modeInterface_feedback) {
*_io << " " << getDotNodeName() << " [ label=\"API\\nFEEDBACK\" ];\n";
link(_io, nameOut, "->", getDotNodeName());
} else if (m_mode == audio::river::modeInterface_output) {
*_io << " " << getDotNodeName() << " [ label=\"API\\nOUTPUT\" ];\n";
link(_io, nameIn, "<-", getDotNodeName());
}
*_io << " }\n \n";
}

329
audio/river/Interface.hpp Normal file
View File

@@ -0,0 +1,329 @@
/** @file
* @author Edouard DUPIN
* @copyright 2015, Edouard DUPIN, all right reserved
* @license MPL v2.0 (see license file)
*/
#pragma once
#include <etk/String.hpp>
#include <etk/Vector.hpp>
#include <ethread/Mutex.hpp>
#include <ethread/MutexRecursive.hpp>
#include <echrono/echrono.hpp>
#include <etk/Function.hpp>
#include <ememory/memory.hpp>
#include <audio/format.hpp>
#include <audio/channel.hpp>
#include <audio/drain/Process.hpp>
#include <audio/drain/EndPointCallback.hpp>
#include <audio/drain/EndPointWrite.hpp>
#include <ejson/ejson.hpp>
#include <audio/Time.hpp>
namespace audio {
namespace river {
namespace io {
class Node;
class NodeAirTAudio;
class NodeAEC;
class NodeMuxer;
}
enum modeInterface {
modeInterface_unknow,
modeInterface_input,
modeInterface_output,
modeInterface_feedback,
};
/**
* @brief Interface is the basic handle to manage the input output stream
* @note To create this class see @ref audio::river::Manager class
*/
class Interface : public ememory::EnableSharedFromThis<Interface> {
friend class io::Node;
friend class io::NodeAirTAudio;
friend class io::NodeAEC;
friend class io::NodeMuxer;
friend class Manager;
protected:
uint32_t m_uid; //!< unique ID for interface
protected:
/**
* @brief Constructor (use factory)
*/
Interface();
/**
* @brief Initilize the Class (do all that is not manage by constructor) Call by factory.
* @param[in] _freq Frequency.
* @param[in] _map Channel map organization.
* @param[in] _format Sample format
* @param[in] _node Low level interface to connect the flow.
* @param[in] _config Special configuration of this interface.
* @return true Configuration done corectly.
* @return false the configuration has an error.
*/
bool init(float _freq,
const etk::Vector<audio::channel>& _map,
audio::format _format,
ememory::SharedPtr<audio::river::io::Node> _node,
const ejson::Object& _config);
/**
* @brief Factory of this interface (called by class audio::river::Manager)
* @param[in] _freq Frequency.
* @param[in] _map Channel map organization.
* @param[in] _format Sample format
* @param[in] _node Low level interface to connect the flow.
* @param[in] _config Special configuration of this interface.
* @return null The configuration does not work.
* @return pointer The interface has been corectly created.
*/
static ememory::SharedPtr<Interface> create(float _freq,
const etk::Vector<audio::channel>& _map,
audio::format _format,
const ememory::SharedPtr<audio::river::io::Node>& _node,
const ejson::Object& _config);
public:
/**
* @brief Destructor
*/
virtual ~Interface();
protected:
mutable ethread::MutexRecursive m_mutex; //!< Local mutex to protect data
ejson::Object m_config; //!< configuration set by the user.
protected:
enum modeInterface m_mode; //!< interface type (input/output/feedback)
public:
/**
* @brief Get mode type of the current interface.
* @return The mode requested.
*/
enum modeInterface getMode() {
return m_mode;
}
protected:
audio::drain::Process m_process; //!< Algorithme processing engine
public:
/**
* @brief Get the interface format configuration.
* @return The current format.
*/
const audio::drain::IOFormatInterface& getInterfaceFormat() {
if ( m_mode == modeInterface_input
|| m_mode == modeInterface_feedback) {
return m_process.getOutputConfig();
} else {
return m_process.getInputConfig();
}
}
protected:
ememory::SharedPtr<audio::river::io::Node> m_node; //!< Hardware interface to/from stream audio flow.
protected:
etk::String m_name; //!< Name of the interface.
public:
/**
* @brief Get interface name.
* @return The current name.
*/
virtual etk::String getName() {
return m_name;
};
/**
* @brief Set the interface name
* @param[in] _name new name of the interface
*/
virtual void setName(const etk::String& _name) {
m_name = _name;
};
/**
* @brief set the read/write mode enable.
* @note If you not set a output/input callback you must call this function.
*/
virtual void setReadwrite();
/**
* @brief When we want to implement a Callback Mode:
*/
/**
* @brief Set a callback on the write mode interface to know when data is needed in the buffer
* @param[in] _function Function to call
*/
virtual void setWriteCallback(audio::drain::playbackFunctionWrite _function);
/**
* @brief Set Output callback mode with the specify callback.
* @param[in] _function Function to call
*/
virtual void setOutputCallback(audio::drain::playbackFunction _function);
/**
* @brief Set Input callback mode with the specify callback.
* @param[in] _function Function to call
*/
virtual void setInputCallback(audio::drain::recordFunction _function);
/**
* @brief Add a volume group of the current channel.
* @note If you do not call this function with the group "FLOW" you chan not have a channel volume.
* @note the set volume stage can not be set after the start.
* @param[in] _name Name of the group classicle common group:
* - FLOW for channel volume.
* - MEDIA for multimedia volume control (audio player, player video, web streaming ...).
* - TTS for Test-to-speech volume control.
* - COMMUNICATION for user communication volume control.
* - NOTIFICATION for urgent notification volume control.
* - NOISE for small noise volume control.
*/
virtual void addVolumeGroup(const etk::String& _name);
public:
/**
* @brief Start the Audio interface flow.
* @param[in] _time Time to start the flow (0) to start as fast as possible...
* @note _time to play buffer when output interface (if possible)
* @note _time to read buffer when inut interface (if possible)
*/
virtual void start(const audio::Time& _time = audio::Time());
/**
* @brief Stop the current flow.
* @param[in] _fast The stream stop as fast as possible (not write all the buffer in speaker) but apply cross fade out.
* @param[in] _abort The stream stop whith no garenty of good audio stop.
*/
virtual void stop(bool _fast=false, bool _abort=false);
/**
* @brief Abort flow (no audio garenty)
*/
virtual void abort();
/**
* @brief Set a parameter in the stream flow
* @param[in] _filter name of the filter (if you added some personels)
* @param[in] _parameter Parameter name.
* @param[in] _value Value to set.
* @return true set done
* @return false An error occured
* @example : setParameter("volume", "FLOW", "-3dB");
* @example : setParameter("LowPassFilter", "cutFrequency", "1000Hz");
*/
virtual bool setParameter(const etk::String& _filter, const etk::String& _parameter, const etk::String& _value);
/**
* @brief Get a parameter value
* @param[in] _filter name of the filter (if you added some personels)
* @param[in] _parameter Parameter name.
* @return The requested value.
* @example : getParameter("volume", "FLOW"); can return something like "-3dB"
* @example : getParameter("LowPassFilter", "cutFrequency"); can return something like "[-120..0]dB"
*/
virtual etk::String getParameter(const etk::String& _filter, const etk::String& _parameter) const;
/**
* @brief Get a parameter value
* @param[in] _filter name of the filter (if you added some personels)
* @param[in] _parameter Parameter name.
* @return The requested value.
* @example : getParameter("volume", "FLOW"); can return something like "[-120..0]dB"
* @example : getParameter("LowPassFilter", "cutFreqiency"); can return something like "]100..10000]Hz"
*/
virtual etk::String getParameterProperty(const etk::String& _filter, const etk::String& _parameter) const;
/**
* @brief write some audio sample in the speakers
* @param[in] _value Data To write on output
* @param[in] _nbChunk Number of audio chunk to write
*/
// TODO : TimeOut ???
virtual void write(const void* _value, size_t _nbChunk);
/**
* @brief read some audio sample from Microphone
* @param[in] _value Data To write on output
* @param[in] _nbChunk Number of audio chunk to write
*/
// TODO : TimeOut ???
virtual void read(void* _value, size_t _nbChunk);
/**
* @brief Get number of chunk in the local buffer
* @return Number of chunk
*/
virtual size_t size() const;
/**
* @brief Set buffer size in chunk number
* @param[in] _nbChunk Number of chunk in the buffer
*/
virtual void setBufferSize(size_t _nbChunk);
/**
* @brief Set buffer size size of the buffer with the stored time in <20>s
* @param[in] _time Time in microsecond of the buffer
*/
virtual void setBufferSize(const echrono::microseconds& _time);
/**
* @brief get buffer size in chunk number
* @return Number of chunk that can be written in the buffer
*/
virtual size_t getBufferSize();
/**
* @brief Set buffer size size of the buffer with the stored time in <20>s
* @return Time in microsecond that can be written in the buffer
*/
virtual echrono::microseconds getBufferSizeMicrosecond();
/**
* @brief Get buffer size filled in chunk number
* @return Number of chunk in the buffer (that might be read/write)
*/
virtual size_t getBufferFillSize();
/**
* @brief Set buffer size size of the buffer with the stored time in <20>s
* @return Time in microsecond of the buffer (that might be read/write)
*/
virtual echrono::microseconds getBufferFillSizeMicrosecond();
/**
* @brief Remove internal Buffer
*/
virtual void clearInternalBuffer();
/**
* @brief Write : Get the time of the next sample time to write in the local buffer
* @brief Read : Get the time of the next sample time to read in the local buffer
*/
virtual audio::Time getCurrentTime() const;
private:
/**
* @brief Node Call interface : Input interface node has new data.
* @param[in] _time Time where the first sample has been capture.
* @param[in] _data Pointer on the new data.
* @param[in] _nbChunk Number of chunk in the buffer.
*/
virtual void systemNewInputData(audio::Time _time, const void* _data, size_t _nbChunk);
/**
* @brief Node Call interface: Output interface node need new data.
* @param[in] _time Time where the data might be played
* @param[in] _data Pointer on the data.
* @param[in] _nbChunk Number of chunk that might be write
* @param[in] _chunkSize Chunk size.
*/
virtual void systemNeedOutputData(audio::Time _time, void* _data, size_t _nbChunk, size_t _chunkSize);
/**
* @brief Node Call interface: A volume has change.
*/
virtual void systemVolumeChange();
public:
/**
* @brief Create the dot in the FileNode stream.
* @param[in,out] _io File interafce to write data.
* @param[in] _nameIO Name to link the interface node
* @param[in] _isLink True if the node is connected on the current interface.
*/
virtual void generateDot(ememory::SharedPtr<etk::io::Interface>& _io, const etk::String& _nameIO, bool _isLink=true);
/**
* @brief Get the current 'dot' name of the interface
* @return The anme requested.
*/
virtual etk::String getDotNodeName() const;
protected:
/**
* @brief Interfanel generate of status
* @param[in] _origin status source
* @param[in] _status Event status
*/
void generateStatus(const etk::String& _origin, const etk::String& _status) {
m_process.generateStatus(_origin, _status);
}
public:
/**
* @brief Set status callback
* @param[in] _newFunction Function to call
*/
void setStatusFunction(audio::drain::statusFunction _newFunction) {
m_process.setStatusFunction(_newFunction);
}
};
}
}

243
audio/river/Manager.cpp Normal file
View File

@@ -0,0 +1,243 @@
/** @file
* @author Edouard DUPIN
* @copyright 2015, Edouard DUPIN, all right reserved
* @license MPL v2.0 (see license file)
*/
#include "Manager.hpp"
#include "Interface.hpp"
#include "io/Manager.hpp"
#include "io/Node.hpp"
#include "debug.hpp"
#include <ejson/ejson.hpp>
static ethread::Mutex g_mutex;
static etk::Vector<ememory::WeakPtr<audio::river::Manager> > g_listOfAllManager;
ememory::SharedPtr<audio::river::Manager> audio::river::Manager::create(const etk::String& _applicationUniqueId) {
ethread::UniqueLock lock(g_mutex);
for (size_t iii=0; iii<g_listOfAllManager.size() ; ++iii) {
ememory::SharedPtr<audio::river::Manager> tmp = g_listOfAllManager[iii].lock();
if (tmp == null) {
continue;
}
if (tmp->m_applicationUniqueId == _applicationUniqueId) {
return tmp;
}
}
// create a new one:
ememory::SharedPtr<audio::river::Manager> out = ememory::SharedPtr<audio::river::Manager>(ETK_NEW(audio::river::Manager, _applicationUniqueId));
// add it at the list:
for (size_t iii=0; iii<g_listOfAllManager.size() ; ++iii) {
if (g_listOfAllManager[iii].expired() == true) {
g_listOfAllManager[iii] = out;
return out;
}
}
g_listOfAllManager.pushBack(out);
return out;
}
audio::river::Manager::Manager(const etk::String& _applicationUniqueId) :
m_applicationUniqueId(_applicationUniqueId),
m_listOpenInterface() {
}
audio::river::Manager::~Manager() {
// TODO : Stop all interfaces...
}
etk::Vector<etk::String> audio::river::Manager::getListStreamInput() {
etk::Vector<etk::String> output;
ememory::SharedPtr<audio::river::io::Manager> manager = audio::river::io::Manager::getInstance();
if (manager == null) {
RIVER_ERROR("Unable to load harware IO manager ... ");
} else {
output = manager->getListStreamInput();
}
return output;
}
etk::Vector<etk::String> audio::river::Manager::getListStreamOutput() {
etk::Vector<etk::String> output;
ememory::SharedPtr<audio::river::io::Manager> manager = audio::river::io::Manager::getInstance();
if (manager == null) {
RIVER_ERROR("Unable to load harware IO manager ... ");
} else {
output = manager->getListStreamOutput();
}
return output;
}
etk::Vector<etk::String> audio::river::Manager::getListStreamVirtual() {
etk::Vector<etk::String> output;
ememory::SharedPtr<audio::river::io::Manager> manager = audio::river::io::Manager::getInstance();
if (manager == null) {
RIVER_ERROR("Unable to load harware IO manager ... ");
} else {
output = manager->getListStreamVirtual();
}
return output;
}
etk::Vector<etk::String> audio::river::Manager::getListStream() {
etk::Vector<etk::String> output;
ememory::SharedPtr<audio::river::io::Manager> manager = audio::river::io::Manager::getInstance();
if (manager == null) {
RIVER_ERROR("Unable to load harware IO manager ... ");
} else {
output = manager->getListStream();
}
return output;
}
bool audio::river::Manager::setVolume(const etk::String& _volumeName, float _valuedB) {
ememory::SharedPtr<audio::river::io::Manager> manager = audio::river::io::Manager::getInstance();
if (manager == null) {
RIVER_ERROR("Unable to load harware IO manager ... ");
return false;
}
return manager->setVolume(_volumeName, _valuedB);
}
float audio::river::Manager::getVolume(const etk::String& _volumeName) const {
ememory::SharedPtr<audio::river::io::Manager> manager = audio::river::io::Manager::getInstance();
if (manager == null) {
RIVER_ERROR("Unable to load harware IO manager ... ");
return false;
}
return manager->getVolume(_volumeName);
}
etk::Pair<float,float> audio::river::Manager::getVolumeRange(const etk::String& _volumeName) const {
ememory::SharedPtr<audio::river::io::Manager> manager = audio::river::io::Manager::getInstance();
if (manager == null) {
RIVER_ERROR("Unable to load harware IO manager ... ");
return etk::makePair<float,float>(0.0f,0.0f);
}
return manager->getVolumeRange(_volumeName);
}
void audio::river::Manager::setMute(const etk::String& _volumeName, bool _mute) {
ememory::SharedPtr<audio::river::io::Manager> manager = audio::river::io::Manager::getInstance();
if (manager == null) {
RIVER_ERROR("Unable to load harware IO manager ... ");
return;
}
manager->setMute(_volumeName, _mute);
}
bool audio::river::Manager::getMute(const etk::String& _volumeName) const {
ememory::SharedPtr<audio::river::io::Manager> manager = audio::river::io::Manager::getInstance();
if (manager == null) {
RIVER_ERROR("Unable to load harware IO manager ... ");
return false;
}
return manager->getMute(_volumeName);
}
ememory::SharedPtr<audio::river::Interface> audio::river::Manager::createOutput(float _freq,
const etk::Vector<audio::channel>& _map,
audio::format _format,
const etk::String& _streamName,
const etk::String& _options) {
// get global hardware interface:
ememory::SharedPtr<audio::river::io::Manager> manager = audio::river::io::Manager::getInstance();
if (manager == null) {
RIVER_ERROR("Unable to load harware IO manager ... ");
return ememory::SharedPtr<audio::river::Interface>();
}
// get the output or input channel :
ememory::SharedPtr<audio::river::io::Node> node = manager->getNode(_streamName);
if (node == null) {
RIVER_ERROR("Can not get the Requested stream '" << _streamName << "' ==> not listed in : " << manager->getListStream());
return ememory::SharedPtr<audio::river::Interface>();
}
if (node->isOutput() != true) {
RIVER_ERROR("Can not Connect output on other thing than output ... for stream '" << _streamName << "'");;
return ememory::SharedPtr<audio::river::Interface>();
}
// create user iterface:
ememory::SharedPtr<audio::river::Interface> interface;
ejson::Object tmpOption = ejson::Object(_options);
tmpOption.add("io", ejson::String("output"));
interface = audio::river::Interface::create(_freq, _map, _format, node, tmpOption);
// store it in a list (needed to apply some parameters).
m_listOpenInterface.pushBack(interface);
return interface;
}
ememory::SharedPtr<audio::river::Interface> audio::river::Manager::createInput(float _freq,
const etk::Vector<audio::channel>& _map,
audio::format _format,
const etk::String& _streamName,
const etk::String& _options) {
// get global hardware interface:
ememory::SharedPtr<audio::river::io::Manager> manager = audio::river::io::Manager::getInstance();
if (manager == null) {
RIVER_ERROR("Unable to load harware IO manager ... ");
return ememory::SharedPtr<audio::river::Interface>();
}
// get the output or input channel :
ememory::SharedPtr<audio::river::io::Node> node = manager->getNode(_streamName);
if (node == null) {
RIVER_ERROR("Can not get the Requested stream '" << _streamName << "' ==> not listed in : " << manager->getListStream());
return ememory::SharedPtr<audio::river::Interface>();
}
if (node->isInput() != true) {
RIVER_ERROR("Can not Connect input on other thing than input ... for stream '" << _streamName << "'");;
return ememory::SharedPtr<audio::river::Interface>();
}
// create user iterface:
ememory::SharedPtr<audio::river::Interface> interface;
ejson::Object tmpOption = ejson::Object(_options);
tmpOption.add("io", ejson::String("input"));
interface = audio::river::Interface::create(_freq, _map, _format, node, tmpOption);
// store it in a list (needed to apply some parameters).
m_listOpenInterface.pushBack(interface);
return interface;
}
ememory::SharedPtr<audio::river::Interface> audio::river::Manager::createFeedback(float _freq,
const etk::Vector<audio::channel>& _map,
audio::format _format,
const etk::String& _streamName,
const etk::String& _options) {
// get global hardware interface:
ememory::SharedPtr<audio::river::io::Manager> manager = audio::river::io::Manager::getInstance();
if (manager == null) {
RIVER_ERROR("Unable to load harware IO manager ... ");
return ememory::SharedPtr<audio::river::Interface>();
}
// get the output or input channel :
ememory::SharedPtr<audio::river::io::Node> node = manager->getNode(_streamName);
if (node == null) {
RIVER_ERROR("Can not get the Requested stream '" << _streamName << "' ==> not listed in : " << manager->getListStream());
return ememory::SharedPtr<audio::river::Interface>();
}
if (node->isOutput() != true) {
RIVER_ERROR("Can not Connect feedback on other thing than output ... for stream '" << _streamName << "'");;
return ememory::SharedPtr<audio::river::Interface>();
}
// create user iterface:
ememory::SharedPtr<audio::river::Interface> interface;
ejson::Object tmpOption = ejson::Object(_options);
tmpOption.add("io", ejson::String("feedback"));
interface = audio::river::Interface::create(_freq, _map, _format, node, tmpOption);
// store it in a list (needed to apply some parameters).
m_listOpenInterface.pushBack(interface);
return interface;
}
void audio::river::Manager::generateDotAll(const etk::String& _filename) {
// get global hardware interface:
ememory::SharedPtr<audio::river::io::Manager> manager = audio::river::io::Manager::getInstance();
if (manager == null) {
RIVER_ERROR("Can not get the harware manager");
return;
}
manager->generateDot(_filename);
}

149
audio/river/Manager.hpp Normal file
View File

@@ -0,0 +1,149 @@
/** @file
* @author Edouard DUPIN
* @copyright 2015, Edouard DUPIN, all right reserved
* @license MPL v2.0 (see license file)
*/
#pragma once
#include <etk/String.hpp>
#include <ememory/memory.hpp>
#include <audio/river/Interface.hpp>
#include <audio/format.hpp>
#include <audio/channel.hpp>
#include <ejson/ejson.hpp>
namespace audio {
namespace river {
/**
* @brief Audio interface manager : Single interface for every application that want to access on the Audio input/output
*/
class Manager : public ememory::EnableSharedFromThis<Manager> {
private:
const etk::String& m_applicationUniqueId; //!< name of the application that open the Audio Interface.
etk::Vector<ememory::WeakPtr<audio::river::Interface> > m_listOpenInterface; //!< List of all open Stream.
protected:
/**
* @brief Constructor
*/
Manager(const etk::String& _applicationUniqueId);
public:
/**
* @brief factory of the manager. Every Application will have only one maager for all his flow. this permit to manage all of it
* @param[in] _applicationUniqueId Unique name of the application
* @return Pointer on the manager or null if an error occured
*/
static ememory::SharedPtr<audio::river::Manager> create(const etk::String& _applicationUniqueId);
/**
* @brief Destructor
*/
virtual ~Manager();
public:
/**
* @brief Get all input audio stream.
* @return a list of all availlables input stream name
*/
etk::Vector<etk::String> getListStreamInput();
/**
* @brief Get all output audio stream.
* @return a list of all availlables output stream name
*/
etk::Vector<etk::String> getListStreamOutput();
/**
* @brief Get all audio virtual stream.
* @return a list of all availlables virtual stream name
*/
etk::Vector<etk::String> getListStreamVirtual();
/**
* @brief Get all audio stream.
* @return a list of all availlables stream name
*/
etk::Vector<etk::String> getListStream();
/**
* @brief Set a volume for a specific group
* @param[in] _volumeName Name of the volume (MASTER, MATER_BT ...)
* @param[in] _value Volume in dB to set.
* @return true set done
* @return false An error occured
* @example : setVolume("MASTER", -3.0f);
*/
virtual bool setVolume(const etk::String& _volumeName, float _valuedB);
/**
* @brief Get a volume value
* @param[in] _volumeName Name of the volume (MASTER, MATER_BT ...)
* @return The Volume value in dB.
* @example ret = getVolume("MASTER"); can return something like ret = -3.0f
*/
virtual float getVolume(const etk::String& _volumeName) const;
/**
* @brief Get a parameter value
* @param[in] _volumeName Name of the volume (MASTER, MATER_BT ...)
* @return The requested value Range.
* @example ret = getVolumeRange("MASTER"); can return something like ret=(-120.0f,0.0f)
*/
virtual etk::Pair<float,float> getVolumeRange(const etk::String& _volumeName) const;
/**
* @brief Set a Mute for a specific volume group
* @param[in] _volumeName Name of the volume (MASTER, MATER_BT ...)
* @param[in] _mute Mute enable or disable.
*/
virtual void setMute(const etk::String& _volumeName, bool _mute);
/**
* @brief Get a volume value
* @param[in] _volumeName Name of the volume (MASTER, MATER_BT ...)
* @return The Mute of the volume volume.
*/
virtual bool getMute(const etk::String& _volumeName) const;
/**
* @brief Create output Interface
* @param[in] _freq Frequency to open Interface [8,16,22,32,48] kHz
* @param[in] _map ChannelMap of the Output
* @param[in] _format Sample Format to open the stream [int8_t, int16_t, ...]
* @param[in] _streamName Stream name to open: "" or "default" open current selected output
* @param[in] _options Json option to configure default resampling and many other things.
* @return a pointer on the interface
*/
virtual ememory::SharedPtr<Interface> createOutput(float _freq = 48000,
const etk::Vector<audio::channel>& _map = etk::Vector<audio::channel>(),
audio::format _format = audio::format_int16,
const etk::String& _streamName = "",
const etk::String& _options = "");
/**
* @brief Create input Interface
* @param[in] _freq Frequency to open Interface [8,16,22,32,48] kHz
* @param[in] _map ChannelMap of the Output
* @param[in] _format Sample Format to open the stream [int8_t, int16_t, ...]
* @param[in] _streamName Stream name to open: "" or "default" open current selected input
* @param[in] _options Json option to configure default resampling and many other things.
* @return a pointer on the interface
*/
virtual ememory::SharedPtr<Interface> createInput(float _freq = 48000,
const etk::Vector<audio::channel>& _map = etk::Vector<audio::channel>(),
audio::format _format = audio::format_int16,
const etk::String& _streamName = "",
const etk::String& _options = "");
/**
* @brief Create input Feedback Interface
* @param[in] _freq Frequency to open Interface [8,16,22,32,48] kHz
* @param[in] _map ChannelMap of the Output
* @param[in] _format Sample Format to open the stream [int8_t, int16_t, ...]
* @param[in] _streamName Stream name to open: "" or "default" open current selected input
* @param[in] _options Json option to configure default resampling and many other things.
* @return a pointer on the interface
*/
virtual ememory::SharedPtr<Interface> createFeedback(float _freq = 48000,
const etk::Vector<audio::channel>& _map = etk::Vector<audio::channel>(),
audio::format _format = audio::format_int16,
const etk::String& _streamName = "",
const etk::String& _options = "");
/**
* @brief Generate the dot file corresponding at all the actif nodes.
* @param[in] _filename Name of the file to write data.
*/
virtual void generateDotAll(const etk::String& _filename);
};
}
}

13
audio/river/debug.cpp Normal file
View File

@@ -0,0 +1,13 @@
/** @file
* @author Edouard DUPIN
* @copyright 2015, Edouard DUPIN, all right reserved
* @license MPL v2.0 (see license file)
*/
#include <audio/river/debug.hpp>
int32_t audio::river::getLogId() {
static int32_t g_val = elog::registerInstance("river");
return g_val;
}

View File

@@ -1,29 +1,20 @@
/** @file /** @file
* @author Edouard DUPIN * @author Edouard DUPIN
* @copyright 2015, Edouard DUPIN, all right reserved * @copyright 2015, Edouard DUPIN, all right reserved
* @license APACHE v2.0 (see license file) * @license MPL v2.0 (see license file)
*/ */
#pragma once
#include <elog/log.hpp>
#ifndef __RIVER_DEBUG_H__ namespace audio {
#define __RIVER_DEBUG_H__ namespace river {
#include <etk/log.h>
namespace river {
int32_t getLogId(); int32_t getLogId();
}; }
// TODO : Review this problem of multiple intanciation of "std::stringbuf sb" }
#define RIVER_BASE(info,data) \ #define RIVER_BASE(info,data) ELOG_BASE(audio::river::getLogId(),info,data)
do { \
if (info <= etk::log::getLevel(river::getLogId())) { \
std::stringbuf sb; \
std::ostream tmpStream(&sb); \
tmpStream << data; \
etk::log::logStream(river::getLogId(), info, __LINE__, __class__, __func__, tmpStream); \
} \
} while(0)
#define RIVER_PRINT(data) RIVER_BASE(-1, data)
#define RIVER_CRITICAL(data) RIVER_BASE(1, data) #define RIVER_CRITICAL(data) RIVER_BASE(1, data)
#define RIVER_ERROR(data) RIVER_BASE(2, data) #define RIVER_ERROR(data) RIVER_BASE(2, data)
#define RIVER_WARNING(data) RIVER_BASE(3, data) #define RIVER_WARNING(data) RIVER_BASE(3, data)
@@ -50,23 +41,20 @@ namespace river {
#define RIVER_SAVE_FILE_MACRO(type,fileName,dataPointer,nbElement) \ #define RIVER_SAVE_FILE_MACRO(type,fileName,dataPointer,nbElement) \
do { \ do { \
static FILE *pointerOnFile = nullptr; \ static FILE *pointerOnFile = null; \
static bool errorOpen = false; \ static bool errorOpen = false; \
if (NULL==pointerOnFile) { \ if (pointerOnFile == null) { \
RIVER_WARNING("open file '" << fileName << "' type=" << #type); \ RIVER_WARNING("open file '" << fileName << "' type=" << #type); \
pointerOnFile = fopen(fileName,"w"); \ pointerOnFile = fopen(fileName,"w"); \
if ( errorOpen == false \ if ( errorOpen == false \
&& pointerOnFile == nullptr) { \ && pointerOnFile == null) { \
RIVER_ERROR("ERROR OPEN file ... '" << fileName << "' type=" << #type); \ RIVER_ERROR("ERROR OPEN file ... '" << fileName << "' type=" << #type); \
errorOpen=true; \ errorOpen=true; \
} \ } \
} \ } \
if (pointerOnFile != nullptr) { \ if (pointerOnFile != null) { \
fwrite((dataPointer), sizeof(type), (nbElement), pointerOnFile); \ fwrite((dataPointer), sizeof(type), (nbElement), pointerOnFile); \
/* fflush(pointerOnFile);*/ \ /* fflush(pointerOnFile);*/ \
} \ } \
}while(0) }while(0)
#endif

View File

@@ -1,11 +1,9 @@
/** @file /** @file
* @author Edouard DUPIN * @author Edouard DUPIN
* @copyright 2015, Edouard DUPIN, all right reserved * @copyright 2015, Edouard DUPIN, all right reserved
* @license APACHE v2.0 (see license file) * @license MPL v2.0 (see license file)
*/ */
#pragma once
#ifdef __RIVER_DEBUG_H__
#undef __RIVER_DEBUG_H__
#undef RIVER_BASE #undef RIVER_BASE
#undef RIVER_CRITICAL #undef RIVER_CRITICAL
@@ -16,5 +14,4 @@
#undef RIVER_VERBOSE #undef RIVER_VERBOSE
#undef RIVER_TODO #undef RIVER_TODO
#undef RIVER_ASSERT #undef RIVER_ASSERT
#endif

View File

@@ -1,46 +1,43 @@
/** @file /** @file
* @author Edouard DUPIN * @author Edouard DUPIN
* @copyright 2015, Edouard DUPIN, all right reserved * @copyright 2015, Edouard DUPIN, all right reserved
* @license APACHE v2.0 (see license file) * @license MPL v2.0 (see license file)
*/ */
#include <river/io/Group.h> #include <audio/river/io/Group.hpp>
#include <river/debug.h> #include <audio/river/debug.hpp>
#include "Node.h" #include <audio/river/io/Node.hpp>
#include "NodeAEC.h" #include <audio/river/io/NodeAEC.hpp>
#include "NodeAirTAudio.h" #include <audio/river/io/NodeOrchestra.hpp>
#include "NodePortAudio.h" #include <audio/river/io/NodePortAudio.hpp>
#include "Node.h" #include <audio/river/io/Node.hpp>
#undef __class__ void audio::river::io::Group::createFrom(const ejson::Document& _obj, const etk::String& _name) {
#define __class__ "io::Group"
void river::io::Group::createFrom(const ejson::Document& _obj, const std::string& _name) {
RIVER_INFO("Create Group[" << _name << "] (START) ___________________________"); RIVER_INFO("Create Group[" << _name << "] (START) ___________________________");
for (size_t iii=0; iii<_obj.size(); ++iii) { for (size_t iii=0; iii<_obj.size(); ++iii) {
const std11::shared_ptr<const ejson::Object> tmpObject = _obj.getObject(_obj.getKey(iii)); const ejson::Object tmpObject = _obj[iii].toObject();
if (tmpObject == nullptr) { if (tmpObject.exist() == false) {
continue; continue;
} }
std::string groupName = tmpObject->getStringValue("group", ""); etk::String groupName = tmpObject["group"].toString().get();
if (groupName == _name) { if (groupName == _name) {
RIVER_INFO("Add element in Group[" << _name << "]: " << _obj.getKey(iii)); RIVER_INFO("Add element in Group[" << _name << "]: " << _obj.getKey(iii));
// get type : io // get type : io
std::string ioType = tmpObject->getStringValue("io", "error"); etk::String ioType = tmpObject["io"].toString().get("error");
#ifdef __AIRTAUDIO_INFERFACE__ #ifdef AUDIO_RIVER_BUILD_ORCHESTRA
if ( ioType == "input" if ( ioType == "input"
|| ioType == "output") { || ioType == "output") {
std11::shared_ptr<river::io::Node> tmp = river::io::NodeAirTAudio::create(_obj.getKey(iii), tmpObject); ememory::SharedPtr<audio::river::io::Node> tmp = audio::river::io::NodeOrchestra::create(_obj.getKey(iii), tmpObject);
tmp->setGroup(shared_from_this()); tmp->setGroup(sharedFromThis());
m_list.push_back(tmp); m_list.pushBack(tmp);
} }
#endif #endif
#ifdef __PORTAUDIO_INFERFACE__ #ifdef AUDIO_RIVER_BUILD_PORTAUDIO
if ( ioType == "PAinput" if ( ioType == "PAinput"
|| ioType == "PAoutput") { || ioType == "PAoutput") {
std11::shared_ptr<river::io::Node> tmp = river::io::NodePortAudio::create(_obj.getKey(iii), tmpObject); ememory::SharedPtr<audio::river::io::Node> tmp = audio::river::io::NodePortAudio::create(_obj.getKey(iii), tmpObject);
tmp->setGroup(shared_from_this()); tmp->setGroup(sharedFromThis());
m_list.push_back(tmp); m_list.pushBack(tmp);
} }
#endif #endif
} }
@@ -48,22 +45,22 @@ void river::io::Group::createFrom(const ejson::Document& _obj, const std::string
// Link all the IO together : (not needed if one device ... // Link all the IO together : (not needed if one device ...
// Note : The interlink work only for alsa (NOW) and with AirTAudio... // Note : The interlink work only for alsa (NOW) and with AirTAudio...
if(m_list.size() > 1) { if(m_list.size() > 1) {
#ifdef __AIRTAUDIO_INFERFACE__ #ifdef AUDIO_RIVER_BUILD_ORCHESTRA
std11::shared_ptr<river::io::NodeAirTAudio> linkRef = std11::dynamic_pointer_cast<river::io::NodeAirTAudio>(m_list[0]); ememory::SharedPtr<audio::river::io::NodeOrchestra> linkRef = ememory::dynamicPointerCast<audio::river::io::NodeOrchestra>(m_list[0]);
for (size_t iii=1; iii<m_list.size(); ++iii) { for (size_t iii=1; iii<m_list.size(); ++iii) {
if (m_list[iii] != nullptr) { if (m_list[iii] != null) {
std11::shared_ptr<river::io::NodeAirTAudio> link = std11::dynamic_pointer_cast<river::io::NodeAirTAudio>(m_list[iii]); ememory::SharedPtr<audio::river::io::NodeOrchestra> link = ememory::dynamicPointerCast<audio::river::io::NodeOrchestra>(m_list[iii]);
linkRef->m_adac.isMasterOf(link->m_adac); linkRef->m_interface.isMasterOf(link->m_interface);
} }
} }
#endif #endif
} }
/* /*
// manage Link Between Nodes : // manage Link Between Nodes :
if (m_link != nullptr) { if (m_link != null) {
RIVER_INFO("******** START LINK ************"); RIVER_INFO("******** START LINK ************");
std11::shared_ptr<river::io::NodeAirTAudio> link = std11::dynamic_pointer_cast<river::io::NodeAirTAudio>(m_link); ememory::SharedPtr<audio::river::io::NodeOrchestra> link = ememory::dynamicPointerCast<audio::river::io::NodeOrchestra>(m_link);
if (link == nullptr) { if (link == null) {
RIVER_ERROR("Can not link 2 Interface with not the same type (reserved for HW interface)"); RIVER_ERROR("Can not link 2 Interface with not the same type (reserved for HW interface)");
return; return;
} }
@@ -75,29 +72,29 @@ void river::io::Group::createFrom(const ejson::Document& _obj, const std::string
RIVER_INFO("Create Group[" << _name << "] ( END ) ___________________________"); RIVER_INFO("Create Group[" << _name << "] ( END ) ___________________________");
RIVER_INFO("Group[" << _name << "] List elements : "); RIVER_INFO("Group[" << _name << "] List elements : ");
for (size_t iii=0; iii<m_list.size(); ++iii) { for (size_t iii=0; iii<m_list.size(); ++iii) {
if (m_list[iii] != nullptr) { if (m_list[iii] != null) {
RIVER_INFO(" " << m_list[iii]->getName()); RIVER_INFO(" " << m_list[iii]->getName());
} }
} }
} }
std11::shared_ptr<river::io::Node> river::io::Group::getNode(const std::string& _name) { ememory::SharedPtr<audio::river::io::Node> audio::river::io::Group::getNode(const etk::String& _name) {
for (size_t iii=0; iii<m_list.size(); ++iii) { for (size_t iii=0; iii<m_list.size(); ++iii) {
if (m_list[iii] != nullptr) { if (m_list[iii] != null) {
if (m_list[iii]->getName() == _name) { if (m_list[iii]->getName() == _name) {
return m_list[iii]; return m_list[iii];
} }
} }
} }
return std11::shared_ptr<river::io::Node>(); return ememory::SharedPtr<audio::river::io::Node>();
} }
void river::io::Group::start() { void audio::river::io::Group::start() {
RIVER_ERROR("request start "); RIVER_ERROR("request start ");
int32_t count = 0; int32_t count = 0;
for (size_t iii=0; iii<m_list.size(); ++iii) { for (size_t iii=0; iii<m_list.size(); ++iii) {
if (m_list[iii] != nullptr) { if (m_list[iii] != null) {
count += m_list[iii]->getNumberOfInterface(); count += m_list[iii]->getNumberOfInterface();
} }
} }
@@ -105,7 +102,7 @@ void river::io::Group::start() {
if (count == 1) { if (count == 1) {
RIVER_ERROR("GROUP :::::::::::: START() [START]"); RIVER_ERROR("GROUP :::::::::::: START() [START]");
for (size_t iii=0; iii<m_list.size(); ++iii) { for (size_t iii=0; iii<m_list.size(); ++iii) {
if (m_list[iii] != nullptr) { if (m_list[iii] != null) {
m_list[iii]->start(); m_list[iii]->start();
} }
} }
@@ -113,11 +110,11 @@ void river::io::Group::start() {
} }
} }
void river::io::Group::stop() { void audio::river::io::Group::stop() {
RIVER_ERROR("request stop "); RIVER_ERROR("request stop ");
int32_t count = 0; int32_t count = 0;
for (size_t iii=0; iii<m_list.size(); ++iii) { for (size_t iii=0; iii<m_list.size(); ++iii) {
if (m_list[iii] != nullptr) { if (m_list[iii] != null) {
count += m_list[iii]->getNumberOfInterface(); count += m_list[iii]->getNumberOfInterface();
} }
} }
@@ -125,7 +122,7 @@ void river::io::Group::stop() {
if (count == 0) { if (count == 0) {
RIVER_ERROR("GROUP :::::::::::: STOP() [START]"); RIVER_ERROR("GROUP :::::::::::: STOP() [START]");
for (int32_t iii=m_list.size()-1; iii>=0; --iii) { for (int32_t iii=m_list.size()-1; iii>=0; --iii) {
if (m_list[iii] != nullptr) { if (m_list[iii] != null) {
m_list[iii]->stop(); m_list[iii]->stop();
} }
} }
@@ -133,11 +130,11 @@ void river::io::Group::stop() {
} }
} }
void river::io::Group::generateDot(etk::FSNode& _node, bool _hardwareNode) { void audio::river::io::Group::generateDot(ememory::SharedPtr<etk::io::Interface>& _io, bool _hardwareNode) {
for (size_t iii=0; iii<m_list.size(); ++iii) { for (size_t iii=0; iii<m_list.size(); ++iii) {
if (m_list[iii] != nullptr) { if (m_list[iii] != null) {
if (m_list[iii]->isHarwareNode() == _hardwareNode) { if (m_list[iii]->isHarwareNode() == _hardwareNode) {
m_list[iii]->generateDot(_node); m_list[iii]->generateDot(_io);
} }
} }
} }

73
audio/river/io/Group.hpp Normal file
View File

@@ -0,0 +1,73 @@
/** @file
* @author Edouard DUPIN
* @copyright 2015, Edouard DUPIN, all right reserved
* @license MPL v2.0 (see license file)
*/
#pragma once
#include <etk/String.hpp>
#include <etk/Vector.hpp>
#include <ejson/ejson.hpp>
#include <etk/io/Interface.hpp>
namespace audio {
namespace river {
namespace io {
class Node;
class Manager;
/**
* @brief Group is contituate to manage some input and output in the same start and stop.
* It link N interface in a group. The start and the sopt is requested in Node inside the
* group they will start and stop when the first start is requested and stop when the last
* is stopped.
* @note For the Alsa interface a low level link is availlable with AirTAudio for Alsa (One thread)
*/
class Group : public ememory::EnableSharedFromThis<Group> {
public:
/**
* @brief Contructor. No special thing to do.
*/
Group() {}
/**
* @brief Destructor
*/
~Group() = default;
private:
etk::Vector< ememory::SharedPtr<Node> > m_list; //!< List of all node in the group
public:
/**
* @brief Create a group with all node needed to syncronize together
* @param[in] _obj json document to create all the node in the group named _name
* @param[in] _name Name of the group to create
*/
void createFrom(const ejson::Document& _obj, const etk::String& _name);
/**
* @brief Get a node in the group (if the node is not in the group nothing append).
* @param[in] _name Name of the node requested.
* @return null The node named _name was not found.
* @return pointer The node was find in this group.
*/
ememory::SharedPtr<audio::river::io::Node> getNode(const etk::String& _name);
/**
* @brief Start the group.
* @note all sub-node will be started.
*/
void start();
/**
* @brief Stop the group.
* @note All sub-node will be stopped at the reserve order that they start.
*/
void stop();
/**
* @brief Create the dot in the FileNode stream.
* @param[in,out] _node File node to write data.
* @param[in] _hardwareNode true if user want only display the hardware
* node and not the software node. false The oposite.
*/
void generateDot(ememory::SharedPtr<etk::io::Interface>& _node, bool _hardwareNode);
};
}
}
}

426
audio/river/io/Manager.cpp Normal file
View File

@@ -0,0 +1,426 @@
/** @file
* @author Edouard DUPIN
* @copyright 2015, Edouard DUPIN, all right reserved
* @license MPL v2.0 (see license file)
*/
#include <audio/river/io/Manager.hpp>
#include <audio/river/debug.hpp>
#include <audio/river/river.hpp>
#include <audio/river/io/Node.hpp>
#include <audio/river/io/NodeAEC.hpp>
#include <audio/river/io/NodeMuxer.hpp>
#include <audio/river/io/NodeOrchestra.hpp>
#include <audio/river/io/NodePortAudio.hpp>
#include <ememory/memory.hpp>
#include <etk/types.hpp>
#include <etk/path/fileSystem.hpp>
#include <etk/uri/uri.hpp>
#ifdef AUDIO_RIVER_BUILD_PORTAUDIO
extern "C" {
#include <portaudio/portaudio.h>
}
#endif
static etk::String basicAutoConfig =
"{\n"
" microphone:{\n"
" io:'input',\n"
" map-on:{\n"
" interface:'auto',\n"
" name:'default',\n"
" },\n"
" frequency:0,\n"
" channel-map:[\n"
" 'front-left', 'front-right'\n"
" ],\n"
" type:'auto',\n"
" nb-chunk:1024,\n"
" mux-demux-type:'float'\n"
" },\n"
" speaker:{\n"
" io:'output',\n"
" map-on:{\n"
" interface:'auto',\n"
" name:'default',\n"
" },\n"
" frequency:0,\n"
" channel-map:[\n"
" 'front-left', 'front-right',\n"
" ],\n"
" type:'auto',\n"
" nb-chunk:1024,\n"
" volume-name:'MASTER',\n"
" mux-demux-type:'float'\n"
" }\n"
"}\n";
static etk::Uri pathToTheRiverConfigInHome(etk::path::getHomePath() / ".local" / "share" / "audio-river" / "config.json");
audio::river::io::Manager::Manager() {
#ifdef AUDIO_RIVER_BUILD_PORTAUDIO
PaError err = Pa_Initialize();
if(err != paNoError) {
RIVER_WARNING("Can not initialize portaudio : " << Pa_GetErrorText(err));
}
#endif
}
void audio::river::io::Manager::init(const etk::Uri& _uri) {
RIVER_ERROR("kjqsdhfkjqshdfkjqhsdskjdfhfkqjshqhskdjfhqsdfqsdqsdfqsdqsdfqsdfqsdfqsdfqsdfqsd");
ethread::RecursiveLock lock(m_mutex);
if (_uri.isEmpty() == true) {
if (m_config.load(pathToTheRiverConfigInHome) == false) {
RIVER_INFO("Load default config");
m_config.parse(basicAutoConfig);
} else {
RIVER_INFO("Load default user configuration: " << pathToTheRiverConfigInHome);
}
} else if (m_config.load(_uri) == false) {
RIVER_ERROR("you must set a basic configuration file for harware configuration: " << _uri);
}
}
void audio::river::io::Manager::initString(const etk::String& _data) {
ethread::RecursiveLock lock(m_mutex);
m_config.parse(_data);
}
void audio::river::io::Manager::unInit() {
ethread::RecursiveLock lock(m_mutex);
// TODO : ...
}
audio::river::io::Manager::~Manager() {
#ifdef AUDIO_RIVER_BUILD_PORTAUDIO
PaError err = Pa_Terminate();
if(err != paNoError) {
RIVER_WARNING("Can not initialize portaudio : " << Pa_GetErrorText(err));
}
#endif
};
ememory::SharedPtr<audio::river::io::Manager> audio::river::io::Manager::getInstance() {
if (audio::river::isInit() == false) {
return ememory::SharedPtr<audio::river::io::Manager>();
}
static ememory::SharedPtr<audio::river::io::Manager> manager(ETK_NEW(Manager));
return manager;
}
etk::Vector<etk::String> audio::river::io::Manager::getListStreamInput() {
ethread::RecursiveLock lock(m_mutex);
etk::Vector<etk::String> output;
etk::Vector<etk::String> keys = m_config.getKeys();
for (auto &it : keys) {
const ejson::Object tmppp = m_config[it].toObject();
if (tmppp.exist() == true) {
etk::String type = tmppp["io"].toString().get("error");
if ( type == "input"
|| type == "PAinput") {
output.pushBack(it);
}
}
}
return output;
}
etk::Vector<etk::String> audio::river::io::Manager::getListStreamOutput() {
ethread::RecursiveLock lock(m_mutex);
etk::Vector<etk::String> output;
etk::Vector<etk::String> keys = m_config.getKeys();
for (auto &it : keys) {
const ejson::Object tmppp = m_config[it].toObject();
if (tmppp.exist() == true) {
etk::String type = tmppp["io"].toString().get("error");
if ( type == "output"
|| type == "PAoutput") {
output.pushBack(it);
}
}
}
return output;
}
etk::Vector<etk::String> audio::river::io::Manager::getListStreamVirtual() {
ethread::RecursiveLock lock(m_mutex);
etk::Vector<etk::String> output;
etk::Vector<etk::String> keys = m_config.getKeys();
for (auto &it : keys) {
const ejson::Object tmppp = m_config[it].toObject();
if (tmppp.exist() == true) {
etk::String type = tmppp["io"].toString().get("error");
if ( type != "input"
&& type != "PAinput"
&& type != "output"
&& type != "PAoutput"
&& type != "error") {
output.pushBack(it);
}
}
}
return output;
}
etk::Vector<etk::String> audio::river::io::Manager::getListStream() {
ethread::RecursiveLock lock(m_mutex);
etk::Vector<etk::String> output;
etk::Vector<etk::String> keys = m_config.getKeys();
for (auto &it : keys) {
const ejson::Object tmppp = m_config[it].toObject();
if (tmppp.exist() == true) {
etk::String type = tmppp["io"].toString().get("error");
if (type != "error") {
output.pushBack(it);
}
}
}
return output;
}
ememory::SharedPtr<audio::river::io::Node> audio::river::io::Manager::getNode(const etk::String& _name) {
ethread::RecursiveLock lock(m_mutex);
RIVER_WARNING("Get node : " << _name);
// search in the standalone list :
for (size_t iii=0; iii<m_list.size(); ++iii) {
ememory::SharedPtr<audio::river::io::Node> tmppp = m_list[iii].lock();
if ( tmppp != null
&& _name == tmppp->getName()) {
RIVER_WARNING(" find it ... in standalone");
return tmppp;
}
}
// search in the group list:
{
for (etk::Map<etk::String, ememory::SharedPtr<audio::river::io::Group> >::Iterator it(m_listGroup.begin());
it != m_listGroup.end();
++it) {
if (it->second != null) {
ememory::SharedPtr<audio::river::io::Node> node = it->second->getNode(_name);
if (node != null) {
RIVER_WARNING(" find it ... in group: " << it->first);
return node;
}
}
}
}
RIVER_WARNING("Try create a new one : " << _name);
// check if the node can be open :
const ejson::Object tmpObject = m_config[_name].toObject();
if (tmpObject.exist() == true) {
//Check if it is in a group:
etk::String groupName = tmpObject["group"].toString().get();
// get type : io
etk::String ioType = tmpObject["io"].toString().get("error");
if ( groupName != ""
&& ( ioType == "input"
|| ioType == "output"
|| ioType == "PAinput"
|| ioType == "PAoutput") ) {
ememory::SharedPtr<audio::river::io::Group> tmpGroup = getGroup(groupName);
if (tmpGroup == null) {
RIVER_WARNING("Can not get group ... '" << groupName << "'");
return ememory::SharedPtr<audio::river::io::Node>();
}
return tmpGroup->getNode(_name);
} else {
if (groupName != "") {
RIVER_WARNING("Group is only availlable for Hardware interface ... '" << _name << "'");
}
// TODO : Create a standalone group for every single element ==> simplify understanding ... but not for virtual interface ...
if ( ioType == "input"
|| ioType == "output") {
#ifdef AUDIO_RIVER_BUILD_ORCHESTRA
ememory::SharedPtr<audio::river::io::Node> tmp = audio::river::io::NodeOrchestra::create(_name, tmpObject);
m_list.pushBack(tmp);
return tmp;
#else
RIVER_WARNING("not present interface");
#endif
}
if ( ioType == "PAinput"
|| ioType == "PAoutput") {
#ifdef AUDIO_RIVER_BUILD_PORTAUDIO
ememory::SharedPtr<audio::river::io::Node> tmp = audio::river::io::NodePortAudio::create(_name, tmpObject);
m_list.pushBack(tmp);
return tmp;
#else
RIVER_WARNING("not present interface");
#endif
}
if (ioType == "aec") {
ememory::SharedPtr<audio::river::io::Node> tmp = audio::river::io::NodeAEC::create(_name, tmpObject);
m_list.pushBack(tmp);
return tmp;
}
if (ioType == "muxer") {
ememory::SharedPtr<audio::river::io::Node> tmp = audio::river::io::NodeMuxer::create(_name, tmpObject);
m_list.pushBack(tmp);
return tmp;
}
}
}
RIVER_ERROR("Can not create the interface : '" << _name << "' the node is not DEFINED in the configuration file availlable : " << m_config.getKeys());
return ememory::SharedPtr<audio::river::io::Node>();
}
ememory::SharedPtr<audio::drain::VolumeElement> audio::river::io::Manager::getVolumeGroup(const etk::String& _name) {
ethread::RecursiveLock lock(m_mutex);
if (_name == "") {
RIVER_ERROR("Try to create an audio group with no name ...");
return ememory::SharedPtr<audio::drain::VolumeElement>();
}
for (size_t iii=0; iii<m_volumeGroup.size(); ++iii) {
if (m_volumeGroup[iii] == null) {
continue;
}
if (m_volumeGroup[iii]->getName() == _name) {
return m_volumeGroup[iii];
}
}
RIVER_DEBUG("Add a new volume group : '" << _name << "'");
ememory::SharedPtr<audio::drain::VolumeElement> tmpVolume = ememory::makeShared<audio::drain::VolumeElement>(_name);
m_volumeGroup.pushBack(tmpVolume);
return tmpVolume;
}
bool audio::river::io::Manager::setVolume(const etk::String& _volumeName, float _valuedB) {
ethread::RecursiveLock lock(m_mutex);
ememory::SharedPtr<audio::drain::VolumeElement> volume = getVolumeGroup(_volumeName);
if (volume == null) {
RIVER_ERROR("Can not set volume ... : '" << _volumeName << "'");
return false;
}
if ( _valuedB < -300
|| _valuedB > 300) {
RIVER_ERROR("Can not set volume ... : '" << _volumeName << "' out of range : [-300..300]");
return false;
}
volume->setVolume(_valuedB);
for (size_t iii=0; iii<m_list.size(); ++iii) {
ememory::SharedPtr<audio::river::io::Node> val = m_list[iii].lock();
if (val != null) {
val->volumeChange();
}
}
return true;
}
float audio::river::io::Manager::getVolume(const etk::String& _volumeName) {
ethread::RecursiveLock lock(m_mutex);
ememory::SharedPtr<audio::drain::VolumeElement> volume = getVolumeGroup(_volumeName);
if (volume == null) {
RIVER_ERROR("Can not get volume ... : '" << _volumeName << "'");
return 0.0f;
}
return volume->getVolume();
}
etk::Pair<float,float> audio::river::io::Manager::getVolumeRange(const etk::String& _volumeName) const {
return etk::makePair<float,float>(-300, 300);
}
void audio::river::io::Manager::setMute(const etk::String& _volumeName, bool _mute) {
ethread::RecursiveLock lock(m_mutex);
ememory::SharedPtr<audio::drain::VolumeElement> volume = getVolumeGroup(_volumeName);
if (volume == null) {
RIVER_ERROR("Can not set volume ... : '" << _volumeName << "'");
return;
}
volume->setMute(_mute);
for (size_t iii=0; iii<m_list.size(); ++iii) {
ememory::SharedPtr<audio::river::io::Node> val = m_list[iii].lock();
if (val != null) {
val->volumeChange();
}
}
}
bool audio::river::io::Manager::getMute(const etk::String& _volumeName) {
ethread::RecursiveLock lock(m_mutex);
ememory::SharedPtr<audio::drain::VolumeElement> volume = getVolumeGroup(_volumeName);
if (volume == null) {
RIVER_ERROR("Can not get volume ... : '" << _volumeName << "'");
return false;
}
return volume->getMute();
}
void audio::river::io::Manager::generateDot(const etk::Uri& _uri) {
ethread::RecursiveLock lock(m_mutex);
ememory::SharedPtr<etk::io::Interface> ioFile = etk::uri::get(_uri);
RIVER_INFO("Generate the DOT files: " << _uri);
if (ioFile->open(etk::io::OpenMode::Write) == false) {
RIVER_ERROR("Can not Write the dot file (fail to open) : " << _uri);
return;
}
*ioFile << "digraph G {" << "\n";
*ioFile << " rankdir=\"LR\";\n";
// First Step : Create all HW interface:
{
// standalone
for (size_t iii=0; iii<m_list.size(); ++iii) {
ememory::SharedPtr<audio::river::io::Node> val = m_list[iii].lock();
if (val != null) {
if (val->isHarwareNode() == true) {
val->generateDot(ioFile);
}
}
}
for (etk::Map<etk::String, ememory::SharedPtr<audio::river::io::Group> >::Iterator it(m_listGroup.begin());
it != m_listGroup.end();
++it) {
if (it->second != null) {
it->second->generateDot(ioFile, true);
}
}
}
// All other ...
{
// standalone
for (size_t iii=0; iii<m_list.size(); ++iii) {
ememory::SharedPtr<audio::river::io::Node> val = m_list[iii].lock();
if (val != null) {
if (val->isHarwareNode() == false) {
val->generateDot(ioFile);
}
}
}
for (etk::Map<etk::String, ememory::SharedPtr<audio::river::io::Group> >::Iterator it(m_listGroup.begin());
it != m_listGroup.end();
++it) {
if (it->second != null) {
it->second->generateDot(ioFile, false);
}
}
}
*ioFile << "}" << "\n";
ioFile->close();
RIVER_INFO("Generate the DOT files: " << _uri << " (DONE)");
}
ememory::SharedPtr<audio::river::io::Group> audio::river::io::Manager::getGroup(const etk::String& _name) {
ethread::RecursiveLock lock(m_mutex);
ememory::SharedPtr<audio::river::io::Group> out;
etk::Map<etk::String, ememory::SharedPtr<audio::river::io::Group> >::Iterator it = m_listGroup.find(_name);
if (it == m_listGroup.end()) {
RIVER_INFO("Create a new group: " << _name << " (START)");
out = ememory::makeShared<audio::river::io::Group>();
if (out != null) {
out->createFrom(m_config, _name);
m_listGroup.add(_name, out);
RIVER_INFO("Create a new group: " << _name << " ( END )");
} else {
RIVER_ERROR("Can not create new group: " << _name << " ( END )");
}
} else {
out = it->second;
}
return out;
}

151
audio/river/io/Manager.hpp Normal file
View File

@@ -0,0 +1,151 @@
/** @file
* @author Edouard DUPIN
* @copyright 2015, Edouard DUPIN, all right reserved
* @license MPL v2.0 (see license file)
*/
#pragma once
#include <etk/String.hpp>
#include <etk/Vector.hpp>
#include <etk/Map.hpp>
#include <ethread/Mutex.hpp>
#include <etk/Function.hpp>
#include <ememory/memory.hpp>
#include <audio/format.hpp>
#include <audio/channel.hpp>
#include <ejson/ejson.hpp>
#include <audio/drain/Volume.hpp>
#include <audio/river/io/Group.hpp>
#include <ethread/MutexRecursive.hpp>
namespace audio {
namespace river {
namespace io {
class Node;
/**
* @brief Internal sigleton of all Flow hadware and virtuals.
* @note this class will be initialize by the audio::river::init() function at the start of the application.
*/
class Manager : public ememory::EnableSharedFromThis<Manager> {
private:
mutable ethread::MutexRecursive m_mutex; //!< prevent multiple access
private:
/**
* @brief Constructor
*/
Manager();
public:
static ememory::SharedPtr<Manager> getInstance();
/**
* @brief Destructor
*/
~Manager();
/**
* @brief Called by audio::river::init() to set the hardware configuration file.
* @param[in] _uri Uri file to initialize.
*/
void init(const etk::Uri& _uri);
/**
* @brief Called by audio::river::initString() to set the hardware configuration string.
* @param[in] _data json configuration string.
*/
void initString(const etk::String& _data);
/**
* @brief Called by audio::river::inInit() to uninitialize all the low level interface.
*/
void unInit();
private:
ejson::Document m_config; //!< harware configuration
etk::Vector<ememory::SharedPtr<audio::river::io::Node> > m_listKeepAlive; //!< list of all Node that might be keep alive sone/all time
etk::Vector<ememory::WeakPtr<audio::river::io::Node> > m_list; //!< List of all IO node
public:
/**
* @brief Get a node with his name (the name is set in the description file.
* @param[in] _name Name of the node
* @return Pointer on the noe or a null if the node does not exist in the file or an error occured.
*/
ememory::SharedPtr<audio::river::io::Node> getNode(const etk::String& _name);
private:
etk::Vector<ememory::SharedPtr<audio::drain::VolumeElement> > m_volumeGroup; //!< List of All global volume in the Low level interface.
public:
/**
* @brief Get a volume in the global list of vilume
* @param[in] _name Name of the volume.
* @return pointer on the requested volume (create it if does not exist). null if the name is empty.
*/
ememory::SharedPtr<audio::drain::VolumeElement> getVolumeGroup(const etk::String& _name);
/**
* @brief Get all input audio stream.
* @return a list of all availlables input stream name
*/
etk::Vector<etk::String> getListStreamInput();
/**
* @brief Get all output audio stream.
* @return a list of all availlables output stream name
*/
etk::Vector<etk::String> getListStreamOutput();
/**
* @brief Get all audio virtual stream.
* @return a list of all availlables virtual stream name
*/
etk::Vector<etk::String> getListStreamVirtual();
/**
* @brief Get all audio stream.
* @return a list of all availlables stream name
*/
etk::Vector<etk::String> getListStream();
/**
* @brief Set a volume for a specific group
* @param[in] _volumeName Name of the volume (MASTER, MATER_BT ...)
* @param[in] _value Volume in dB to set.
* @return true set done
* @return false An error occured
* @example : setVolume("MASTER", -3.0f);
*/
bool setVolume(const etk::String& _volumeName, float _valuedB);
/**
* @brief Get a volume value
* @param[in] _volumeName Name of the volume (MASTER, MATER_BT ...)
* @return The Volume value in dB.
* @example ret = getVolume("MASTER"); can return something like ret = -3.0f
*/
float getVolume(const etk::String& _volumeName);
/**
* @brief Get a parameter value
* @param[in] _volumeName Name of the volume (MASTER, MATER_BT ...)
* @return The requested value Range.
* @example ret = getVolumeRange("MASTER"); can return something like ret=(-120.0f,0.0f)
*/
etk::Pair<float,float> getVolumeRange(const etk::String& _volumeName) const;
/**
* @brief Set a Mute for a specific volume group
* @param[in] _volumeName Name of the volume (MASTER, MATER_BT ...)
* @param[in] _mute Mute enable or disable.
*/
void setMute(const etk::String& _volumeName, bool _mute);
/**
* @brief Get a volume value
* @param[in] _volumeName Name of the volume (MASTER, MATER_BT ...)
* @return The Mute of the volume volume.
*/
bool getMute(const etk::String& _volumeName);
/**
* @brief Generate the dot file corresponding at the actif nodes.
* @param[in] _uri Uri of the file to write data.
*/
void generateDot(const etk::Uri& _uri);
private:
etk::Map<etk::String, ememory::SharedPtr<audio::river::io::Group> > m_listGroup; //!< List of all groups
/**
* @brief get a low level interface group.
* @param[in] _name Name of the group.
* @return Pointer on the requested group or null if the group does not existed.
*/
ememory::SharedPtr<audio::river::io::Group> getGroup(const etk::String& _name);
};
}
}
}

497
audio/river/io/Node.cpp Normal file
View File

@@ -0,0 +1,497 @@
/** @file
* @author Edouard DUPIN
* @copyright 2015, Edouard DUPIN, all right reserved
* @license MPL v2.0 (see license file)
*/
#include "Node.hpp"
#include <audio/river/debug.hpp>
audio::river::io::Node::Node(const etk::String& _name, const ejson::Object& _config) :
m_config(_config),
m_name(_name),
m_isInput(false) {
static uint32_t uid=0;
m_uid = uid++;
RIVER_INFO("-----------------------------------------------------------------");
RIVER_INFO("-- CREATE NODE --");
RIVER_INFO("-----------------------------------------------------------------");
audio::drain::IOFormatInterface interfaceFormat;
audio::drain::IOFormatInterface hardwareFormat;
/**
io:"input", # input, output or aec
frequency:48000, # frequency to open device
channel-map:[ # mapping of the harware device (to change map if needed)
"front-left", "front-right",
"read-left", "rear-right",
],
# format to open device (int8, int16, int16-on-ont32, int24, int32, float)
type:"int16",
# muxer/demuxer format type (int8-on-int16, int16-on-int32, int24-on-int32, int32-on-int64, float)
mux-demux-type:"int16_on_int32",
*/
etk::String interfaceType = m_config["io"].toString().get();
RIVER_INFO("interfaceType=" << interfaceType);
if ( interfaceType == "input"
|| interfaceType == "PAinput"
|| interfaceType == "aec"
|| interfaceType == "muxer") {
m_isInput = true;
} else {
m_isInput = false;
}
int32_t frequency = m_config["frequency"].toNumber().get(1);
// Get audio format type:
etk::String type = m_config["type"].toString().get("int16");
enum audio::format formatType = audio::getFormatFromString(type);
// Get volume stage :
etk::String volumeName = m_config["volume-name"].toString().get();
if (volumeName != "") {
RIVER_INFO("add node volume stage : '" << volumeName << "'");
// use global manager for volume ...
m_volume = audio::river::io::Manager::getInstance()->getVolumeGroup(volumeName);
}
// Get map type :
etk::Vector<audio::channel> map;
const ejson::Array listChannelMap = m_config["channel-map"].toArray();
if ( listChannelMap.exist() == false
|| listChannelMap.size() == 0) {
// set default channel property:
map.pushBack(audio::channel_frontLeft);
map.pushBack(audio::channel_frontRight);
} else {
for (auto it : listChannelMap) {
etk::String value = it.toString().get();
map.pushBack(audio::getChannelFromString(value));
}
}
hardwareFormat.set(map, formatType, frequency);
etk::String muxerDemuxerConfig;
if (m_isInput == true) {
muxerDemuxerConfig = m_config["mux-demux-type"].toString().get("int16");
} else {
muxerDemuxerConfig = m_config["mux-demux-type"].toString().get("int16-on-int32");
}
enum audio::format muxerFormatType = audio::getFormatFromString(muxerDemuxerConfig);
if (m_isInput == true) {
// Support all ...
} else {
if ( muxerFormatType != audio::format_int8_on_int16
&& muxerFormatType != audio::format_int16_on_int32
&& muxerFormatType != audio::format_int24_on_int32
&& muxerFormatType != audio::format_int32_on_int64
&& muxerFormatType != audio::format_float
&& muxerFormatType != audio::format_double) {
RIVER_CRITICAL("not supported demuxer type ... " << muxerFormatType << " for OUTPUT set in file:" << muxerDemuxerConfig);
}
}
// no map change and no frequency change ...
interfaceFormat.set(map, muxerFormatType, frequency);
// configure process interface
if (m_isInput == true) {
m_process.setInputConfig(hardwareFormat);
m_process.setOutputConfig(interfaceFormat);
} else {
m_process.setOutputConfig(hardwareFormat);
m_process.setInputConfig(interfaceFormat);
}
//m_process.updateInterAlgo();
}
audio::river::io::Node::~Node() {
RIVER_INFO("-----------------------------------------------------------------");
RIVER_INFO("-- DESTROY NODE --");
RIVER_INFO("-----------------------------------------------------------------");
};
size_t audio::river::io::Node::getNumberOfInterface(enum audio::river::modeInterface _interfaceType) {
size_t out = 0;
for (size_t iii=0; iii<m_list.size(); ++iii) {
if (m_list[iii] == null) {
continue;
}
if (m_list[iii]->getMode() == _interfaceType) {
out++;
}
}
return out;
}
size_t audio::river::io::Node::getNumberOfInterfaceAvaillable(enum audio::river::modeInterface _interfaceType) {
size_t out = 0;
for (size_t iii=0; iii<m_listAvaillable.size(); ++iii) {
auto element = m_listAvaillable[iii].lock();
if (element == null) {
continue;
}
if (element->getMode() == _interfaceType) {
out++;
}
}
return out;
}
void audio::river::io::Node::registerAsRemote(const ememory::SharedPtr<audio::river::Interface>& _interface) {
auto it = m_listAvaillable.begin();
while (it != m_listAvaillable.end()) {
if (it->expired() == true) {
it = m_listAvaillable.erase(it);
continue;
}
++it;
}
m_listAvaillable.pushBack(_interface);
}
void audio::river::io::Node::interfaceAdd(const ememory::SharedPtr<audio::river::Interface>& _interface) {
{
ethread::UniqueLock lock(m_mutex);
for (size_t iii=0; iii<m_list.size(); ++iii) {
if (_interface == m_list[iii]) {
return;
}
}
RIVER_INFO("ADD interface for stream : '" << m_name << "' mode=" << (m_isInput?"input":"output") );
m_list.pushBack(_interface);
}
if (m_list.size() == 1) {
startInGroup();
}
}
void audio::river::io::Node::interfaceRemove(const ememory::SharedPtr<audio::river::Interface>& _interface) {
{
ethread::UniqueLock lock(m_mutex);
for (size_t iii=0; iii< m_list.size(); ++iii) {
if (_interface == m_list[iii]) {
m_list.erase(m_list.begin()+iii);
RIVER_INFO("RM interface for stream : '" << m_name << "' mode=" << (m_isInput?"input":"output") );
break;
}
}
}
if (m_list.size() == 0) {
stopInGroup();
}
}
void audio::river::io::Node::volumeChange() {
for (size_t iii=0; iii< m_listAvaillable.size(); ++iii) {
auto node = m_listAvaillable[iii].lock();
if (node != null) {
node->systemVolumeChange();
}
}
}
void audio::river::io::Node::newInput(const void* _inputBuffer,
uint32_t _nbChunk,
const audio::Time& _time) {
if (_inputBuffer == null) {
return;
}
for (size_t iii=0; iii< m_list.size(); ++iii) {
if (m_list[iii] == null) {
continue;
}
if (m_list[iii]->getMode() != audio::river::modeInterface_input) {
continue;
}
RIVER_VERBOSE(" IO name="<< m_list[iii]->getName());
m_list[iii]->systemNewInputData(_time, _inputBuffer, _nbChunk);
}
RIVER_VERBOSE("data Input size request :" << _nbChunk << " [ END ]");
return;
}
void audio::river::io::Node::newOutput(void* _outputBuffer,
uint32_t _nbChunk,
const audio::Time& _time) {
if (_outputBuffer == null) {
return;
}
enum audio::format muxerFormatType = m_process.getInputConfig().getFormat();
etk::Vector<uint8_t> outputTmp2;
uint32_t nbByteTmpBuffer = audio::getFormatBytes(muxerFormatType)*m_process.getInputConfig().getMap().size()*_nbChunk;
RIVER_VERBOSE("resize=" << nbByteTmpBuffer);
outputTmp2.resize(nbByteTmpBuffer);
if (muxerFormatType == audio::format_int8_on_int16) {
//////////////////////////////////////////////////////////////////////////////////////////////////
// process 16 bits
//////////////////////////////////////////////////////////////////////////////////////////////////
// $$$$ change the int16
etk::Vector<int16_t> output;
RIVER_VERBOSE("resize=" << _nbChunk*m_process.getInputConfig().getMap().size());
output.resize(_nbChunk*m_process.getInputConfig().getMap().size(), 0);
// $$$$ change the int16
const int16_t* outputTmp = null;
for (size_t iii=0; iii< m_list.size(); ++iii) {
if (m_list[iii] == null) {
continue;
}
if (m_list[iii]->getMode() != audio::river::modeInterface_output) {
continue;
}
RIVER_VERBOSE(" IO name="<< m_list[iii]->getName() << " " << iii);
// clear datas ...
memset(&outputTmp2[0], 0, nbByteTmpBuffer);
RIVER_VERBOSE(" request Data="<< _nbChunk << " time=" << _time);
m_list[iii]->systemNeedOutputData(_time, &outputTmp2[0], _nbChunk, audio::getFormatBytes(muxerFormatType)*m_process.getInputConfig().getMap().size());
// $$$$ change the int16
outputTmp = reinterpret_cast<const int16_t*>(&outputTmp2[0]);
RIVER_VERBOSE(" Mix it ...");
// Add data to the output tmp buffer:
for (size_t kkk=0; kkk<output.size(); ++kkk) {
output[kkk] += outputTmp[kkk];
}
// TODO : if a signal is upper than 256* the maximum of 1 it can create a real problem ...
}
RIVER_VERBOSE(" End stack process data ...");
m_process.processIn(&output[0], _nbChunk, _outputBuffer, _nbChunk);
} else if ( muxerFormatType == audio::format_int16_on_int32
|| muxerFormatType == audio::format_int24_on_int32) {
//////////////////////////////////////////////////////////////////////////////////////////////////
// process 32 bits
//////////////////////////////////////////////////////////////////////////////////////////////////
etk::Vector<int32_t> output;
RIVER_VERBOSE("resize=" << _nbChunk*m_process.getInputConfig().getMap().size());
output.resize(_nbChunk*m_process.getInputConfig().getMap().size(), 0);
const int32_t* outputTmp = null;
for (size_t iii=0; iii< m_list.size(); ++iii) {
if (m_list[iii] == null) {
continue;
}
if (m_list[iii]->getMode() != audio::river::modeInterface_output) {
continue;
}
RIVER_VERBOSE(" IO name="<< m_list[iii]->getName() << " " << iii);
// clear datas ...
memset(&outputTmp2[0], 0, nbByteTmpBuffer);
RIVER_VERBOSE(" request Data="<< _nbChunk << " time=" << _time);
m_list[iii]->systemNeedOutputData(_time, &outputTmp2[0], _nbChunk, audio::getFormatBytes(muxerFormatType)*m_process.getInputConfig().getMap().size());
outputTmp = reinterpret_cast<const int32_t*>(&outputTmp2[0]);
RIVER_VERBOSE(" Mix it ...");
// Add data to the output tmp buffer:
for (size_t kkk=0; kkk<output.size(); ++kkk) {
output[kkk] += outputTmp[kkk];
}
// TODO : if a signal is upper than 256* (for 24 bits) or 65335* (for 16 bits) the maximum of 1 it can create a real problem ...
}
RIVER_VERBOSE(" End stack process data ...");
m_process.processIn(&output[0], _nbChunk, _outputBuffer, _nbChunk);
} else if (muxerFormatType == audio::format_int32_on_int64) {
//////////////////////////////////////////////////////////////////////////////////////////////////
// process 64 bits
//////////////////////////////////////////////////////////////////////////////////////////////////
etk::Vector<int64_t> output;
RIVER_VERBOSE("resize=" << _nbChunk*m_process.getInputConfig().getMap().size());
output.resize(_nbChunk*m_process.getInputConfig().getMap().size(), 0);
const int64_t* outputTmp = null;
for (size_t iii=0; iii< m_list.size(); ++iii) {
if (m_list[iii] == null) {
continue;
}
if (m_list[iii]->getMode() != audio::river::modeInterface_output) {
continue;
}
RIVER_VERBOSE(" IO name="<< m_list[iii]->getName() << " " << iii);
// clear datas ...
memset(&outputTmp2[0], 0, nbByteTmpBuffer);
RIVER_VERBOSE(" request Data="<< _nbChunk << " time=" << _time);
m_list[iii]->systemNeedOutputData(_time, &outputTmp2[0], _nbChunk, audio::getFormatBytes(muxerFormatType)*m_process.getInputConfig().getMap().size());
outputTmp = reinterpret_cast<const int64_t*>(&outputTmp2[0]);
RIVER_VERBOSE(" Mix it ...");
// Add data to the output tmp buffer:
for (size_t kkk=0; kkk<output.size(); ++kkk) {
output[kkk] += outputTmp[kkk];
}
// TODO : if a signal is upper than 2000000000* the maximum of 1 it can create a real problem ...
}
RIVER_VERBOSE(" End stack process data ...");
m_process.processIn(&output[0], _nbChunk, _outputBuffer, _nbChunk);
} else if (muxerFormatType == audio::format_float) {
//////////////////////////////////////////////////////////////////////////////////////////////////
// process 32 bits FLOAT
//////////////////////////////////////////////////////////////////////////////////////////////////
etk::Vector<float> output;
RIVER_VERBOSE("resize=" << _nbChunk*m_process.getInputConfig().getMap().size());
output.resize(_nbChunk*m_process.getInputConfig().getMap().size(), 0);
const float* outputTmp = null;
for (size_t iii=0; iii< m_list.size(); ++iii) {
if (m_list[iii] == null) {
continue;
}
if (m_list[iii]->getMode() != audio::river::modeInterface_output) {
continue;
}
RIVER_VERBOSE(" IO name="<< m_list[iii]->getName() << " " << iii);
// clear datas ...
memset(&outputTmp2[0], 0, nbByteTmpBuffer);
RIVER_VERBOSE(" request Data="<< _nbChunk << " time=" << _time);
m_list[iii]->systemNeedOutputData(_time, &outputTmp2[0], _nbChunk, audio::getFormatBytes(muxerFormatType)*m_process.getInputConfig().getMap().size());
outputTmp = reinterpret_cast<const float*>(&outputTmp2[0]);
RIVER_VERBOSE(" Mix it ...");
// Add data to the output tmp buffer:
for (size_t kkk=0; kkk<output.size(); ++kkk) {
output[kkk] += outputTmp[kkk];
}
}
RIVER_VERBOSE(" End stack process data ...");
m_process.processIn(&output[0], _nbChunk, _outputBuffer, _nbChunk);
} else if (muxerFormatType == audio::format_double) {
//////////////////////////////////////////////////////////////////////////////////////////////////
// process 64 bits FLOAT
//////////////////////////////////////////////////////////////////////////////////////////////////
etk::Vector<double> output;
RIVER_VERBOSE("resize=" << _nbChunk*m_process.getInputConfig().getMap().size());
output.resize(_nbChunk*m_process.getInputConfig().getMap().size(), 0);
const double* outputTmp = null;
for (size_t iii=0; iii< m_list.size(); ++iii) {
if (m_list[iii] == null) {
continue;
}
if (m_list[iii]->getMode() != audio::river::modeInterface_output) {
continue;
}
RIVER_VERBOSE(" IO name="<< m_list[iii]->getName() << " " << iii);
// clear datas ...
memset(&outputTmp2[0], 0, nbByteTmpBuffer);
RIVER_VERBOSE(" request Data="<< _nbChunk << " time=" << _time);
m_list[iii]->systemNeedOutputData(_time, &outputTmp2[0], _nbChunk, audio::getFormatBytes(muxerFormatType)*m_process.getInputConfig().getMap().size());
outputTmp = reinterpret_cast<const double*>(&outputTmp2[0]);
RIVER_VERBOSE(" Mix it ...");
// Add data to the output tmp buffer:
for (size_t kkk=0; kkk<output.size(); ++kkk) {
output[kkk] += outputTmp[kkk];
}
}
RIVER_VERBOSE(" End stack process data ...");
m_process.processIn(&output[0], _nbChunk, _outputBuffer, _nbChunk);
} else {
RIVER_ERROR("Wrong demuxer type: " << muxerFormatType);
return;
}
// The feedback get the real output data (after processing ...==> then no nneed to specify for each channels
RIVER_VERBOSE(" Feedback :");
for (size_t iii=0; iii< m_list.size(); ++iii) {
if (m_list[iii] == null) {
continue;
}
if (m_list[iii]->getMode() != audio::river::modeInterface_feedback) {
continue;
}
RIVER_VERBOSE(" IO name="<< m_list[iii]->getName() << " (feedback) time=" << _time);
m_list[iii]->systemNewInputData(_time, _outputBuffer, _nbChunk);
}
RIVER_VERBOSE("data Output size request :" << _nbChunk << " [ END ]");
return;
}
static void link(ememory::SharedPtr<etk::io::Interface>& _io, const etk::String& _first, const etk::String& _op, const etk::String& _second) {
if (_op == "->") {
*_io << " " << _first << " -> " << _second << ";\n";
} else if (_op == "<-") {
*_io << " " << _first << " -> " <<_second<< " [color=transparent];\n";
*_io << " " << _second << " -> " << _first << " [constraint=false];\n";
}
}
void audio::river::io::Node::generateDot(ememory::SharedPtr<etk::io::Interface>& _io) {
*_io << " subgraph clusterNode_" << m_uid << " {\n";
*_io << " color=blue;\n";
*_io << " label=\"[" << m_uid << "] IO::Node : " << m_name << "\";\n";
if (m_isInput == true) {
*_io << " node [shape=rarrow];\n";
*_io << " NODE_" << m_uid << "_HW_interface [ label=\"HW interface\\n interface=ALSA\\n stream=" << m_name << "\\n type=input\" ];\n";
etk::String nameIn;
etk::String nameOut;
m_process.generateDotProcess(_io, 3, m_uid, nameIn, nameOut, false);
*_io << " node [shape=square];\n";
*_io << " NODE_" << m_uid << "_demuxer [ label=\"DEMUXER\\n format=" << etk::toString(m_process.getOutputConfig().getFormat()) << "\" ];\n";
// Link all nodes :
*_io << " NODE_" << m_uid << "_HW_interface -> " << nameIn << " [arrowhead=\"open\"];\n";
*_io << " " << nameOut << " -> NODE_" << m_uid << "_demuxer [arrowhead=\"open\"];\n";
} else {
size_t nbOutput = getNumberOfInterfaceAvaillable(audio::river::modeInterface_output);
size_t nbfeedback = getNumberOfInterfaceAvaillable(audio::river::modeInterface_feedback);
*_io << " node [shape=larrow];\n";
*_io << " NODE_" << m_uid << "_HW_interface [ label=\"HW interface\\n interface=ALSA\\n stream=" << m_name << "\\n type=output\" ];\n";
etk::String nameIn;
etk::String nameOut;
if (nbOutput>0) {
m_process.generateDotProcess(_io, 3, m_uid, nameIn, nameOut, true);
}
*_io << " node [shape=square];\n";
if (nbOutput>0) {
*_io << " NODE_" << m_uid << "_muxer [ label=\"MUXER\\n format=" << etk::toString(m_process.getInputConfig().getFormat()) << "\" ];\n";
}
if (nbfeedback>0) {
*_io << " NODE_" << m_uid << "_demuxer [ label=\"DEMUXER\\n format=" << etk::toString(m_process.getOutputConfig().getFormat()) << "\" ];\n";
}
// Link all nodes :
if (nbOutput>0) {
link(_io, "NODE_" + etk::toString(m_uid) + "_HW_interface", "<-", nameOut);
link(_io, nameIn, "<-", "NODE_" + etk::toString(m_uid) + "_muxer");
}
if (nbfeedback>0) {
*_io << " NODE_" << m_uid << "_HW_interface -> NODE_" << m_uid << "_demuxer [arrowhead=\"open\"];\n";
}
if ( nbOutput>0
&& nbfeedback>0) {
*_io << " { rank=same; NODE_" << m_uid << "_demuxer; NODE_" << m_uid << "_muxer }\n";
}
}
*_io << " }\n \n";
for (size_t iii=0; iii< m_listAvaillable.size(); ++iii) {
if (m_listAvaillable[iii].expired() == true) {
continue;
}
ememory::SharedPtr<audio::river::Interface> element = m_listAvaillable[iii].lock();
if (element == null) {
continue;
}
bool isLink = false;
for (size_t jjj=0; jjj<m_list.size(); ++jjj) {
if (element == m_list[jjj]) {
isLink = true;
}
}
if (element != null) {
if (element->getMode() == modeInterface_input) {
element->generateDot(_io, "NODE_" + etk::toString(m_uid) + "_demuxer", isLink);
} else if (element->getMode() == modeInterface_output) {
element->generateDot(_io, "NODE_" + etk::toString(m_uid) + "_muxer", isLink);
} else if (element->getMode() == modeInterface_feedback) {
element->generateDot(_io, "NODE_" + etk::toString(m_uid) + "_demuxer", isLink);
} else {
}
}
}
}
void audio::river::io::Node::startInGroup() {
ememory::SharedPtr<audio::river::io::Group> group = m_group.lock();
if (group != null) {
group->start();
} else {
start();
}
}
void audio::river::io::Node::stopInGroup() {
ememory::SharedPtr<audio::river::io::Group> group = m_group.lock();
if (group != null) {
group->stop();
} else {
stop();
}
}

219
audio/river/io/Node.hpp Normal file
View File

@@ -0,0 +1,219 @@
/** @file
* @author Edouard DUPIN
* @copyright 2015, Edouard DUPIN, all right reserved
* @license MPL v2.0 (see license file)
*/
#pragma once
#include <etk/String.hpp>
#include <etk/Vector.hpp>
#include <etk/Function.hpp>
#include <ememory/memory.hpp>
#include <audio/format.hpp>
#include <audio/channel.hpp>
#include "Manager.hpp"
#include <audio/river/Interface.hpp>
#include <audio/drain/IOFormatInterface.hpp>
#include <audio/drain/Volume.hpp>
#include <etk/io/Interface.hpp>
namespace audio {
namespace river {
namespace io {
class Manager;
class Group;
/**
* @brief A node is the base for input/output interface. When a output id declared, we automaticly have a feedback associated.
* this manage the muxing of data for output an the demuxing for input.
*/
class Node : public ememory::EnableSharedFromThis<Node> {
friend class audio::river::io::Group;
protected:
uint32_t m_uid; //!< uniqueNodeID use for debug an dot generation.
protected:
/**
* @brief Constructor
* @param[in] _name Name of the node.
* @param[in] _config Configuration of the node.
*/
Node(const etk::String& _name, const ejson::Object& _config);
public:
/**
* @brief Destructor
*/
virtual ~Node();
/**
* @brief Get the status of this node acces on harware or acces on other node (virtual).
* @return true This is an harware interface.
* @return false this is a virtual interface.
*/
virtual bool isHarwareNode() {
return false;
};
protected:
mutable ethread::Mutex m_mutex; //!< prevent open/close/write/read access that is multi-threaded.
const ejson::Object m_config; //!< configuration description.
protected:
audio::drain::Process m_process; //!< Low level algorithms
public:
/**
* @brief Get the uper client interface configuration.
* @return process configuration.
*/
const audio::drain::IOFormatInterface& getInterfaceFormat() {
if (m_isInput == true) {
return m_process.getOutputConfig();
} else {
return m_process.getInputConfig();
}
}
/**
* @brief Get the harware client interface configuration.
* @return process configuration.
*/
const audio::drain::IOFormatInterface& getHarwareFormat() {
if (m_isInput == true) {
return m_process.getInputConfig();
} else {
return m_process.getOutputConfig();
}
}
protected:
ememory::SharedPtr<audio::drain::VolumeElement> m_volume; //!< if a volume is set it is set here ... for hardware interface only.
protected:
etk::Vector<ememory::WeakPtr<audio::river::Interface> > m_listAvaillable; //!< List of all interface that exist on this Node
etk::Vector<ememory::SharedPtr<audio::river::Interface> > m_list; //!< List of all connected interface at this node.
/**
* @brief Get the number of interface with a specific type.
* @param[in] _interfaceType Type of the interface.
* @return Number of interface connected.
*/
size_t getNumberOfInterface(enum audio::river::modeInterface _interfaceType);
/**
* @brief Get the number of interface with a specific type that can connect on the Node.
* @param[in] _interfaceType Type of the interface.
* @return Number of interface that can connect.
*/
size_t getNumberOfInterfaceAvaillable(enum audio::river::modeInterface _interfaceType);
public:
/**
* @brief Get the number of interface connected
* @return Number of interfaces.
*/
size_t getNumberOfInterface() {
return m_list.size();
}
public:
/**
* @brief Register an interface that can connect on it. (might be done in the Interface Init)
* @note We keep a ememory::WeakPtr. this is the reason why we do not have a remove.
* @param[in] _interface Pointer on the interface to register.
*/
void registerAsRemote(const ememory::SharedPtr<audio::river::Interface>& _interface);
/**
* @brief Request this interface might receve/send dat on the flow. (start/resume)
* @param[in] _interface Pointer on the interface to register.
*/
void interfaceAdd(const ememory::SharedPtr<audio::river::Interface>& _interface);
/**
* @brief Un-register the interface as an availlable read/write interface. (suspend/stop)
* @param[in] _interface Pointer on the interface to register.
*/
void interfaceRemove(const ememory::SharedPtr<audio::river::Interface>& _interface);
protected:
etk::String m_name; //!< Name of the interface
public:
/**
* @brief Get the interface name.
* @return Current name.
*/
const etk::String& getName() {
return m_name;
}
protected:
bool m_isInput; //!< sense of the stream
public:
/**
* @brief Check if it is an input stream
* @return true if it is an input/ false otherwise
*/
bool isInput() {
return m_isInput;
}
/**
* @brief Check if it is an output stream
* @return true if it is an output/ false otherwise
*/
bool isOutput() {
return !m_isInput;
}
protected:
ememory::WeakPtr<audio::river::io::Group> m_group; //!< reference on the group. If available.
public:
/**
* @brief Set this node in a low level group.
* @param[in] _group Group reference.
*/
void setGroup(ememory::SharedPtr<audio::river::io::Group> _group) {
m_group = _group;
}
protected:
/**
* @brief Start the flow in the group (start if no group)
*/
void startInGroup();
/**
* @brief Stop the flow in the group (stop if no group)
*/
void stopInGroup();
/**
* @brief Real start of the stream
*/
virtual void start() = 0;
/**
* @brief Real stop of the stream
*/
virtual void stop() = 0;
public:
/**
* @brief If this iss an hardware interface we can have a resuest of the volume stage:
* @return pointer on the requested volume.
*/
const ememory::SharedPtr<audio::drain::VolumeElement>& getVolume() {
return m_volume;
}
public:
/**
* @brief Called when a group wolume has been change to update all volume stage.
*/
void volumeChange();
protected:
/**
* @brief Call by child classes to process data in all interface linked on the current Node. Have new input to process.
* @param[in] _inputBuffer Pointer on the data.
* @param[in] _nbChunk Number of chunk in the buffer.
* @param[in] _time Time where the first sample has been capture.
*/
void newInput(const void* _inputBuffer,
uint32_t _nbChunk,
const audio::Time& _time);
/**
* @brief Call by child classes to process data in all interface linked on the current Node. Have new output to get. this call the feedback too.
* @param[in,out] _outputBuffer Pointer on the buffer to write the data.
* @param[in] _nbChunk Number of chunk to write in the buffer.
* @param[in] _time Time where the data might be played.
*/
void newOutput(void* _outputBuffer,
uint32_t _nbChunk,
const audio::Time& _time);
public:
/**
* @brief Generate the node dot file section
* @param[in] _io File interface to dump data.
*/
virtual void generateDot(ememory::SharedPtr<etk::io::Interface>& _io);
};
}
}
}

367
audio/river/io/NodeAEC.cpp Normal file
View File

@@ -0,0 +1,367 @@
/** @file
* @author Edouard DUPIN
* @copyright 2015, Edouard DUPIN, all right reserved
* @license MPL v2.0 (see license file)
*/
#include <audio/river/io/NodeAEC.hpp>
#include <audio/river/debug.hpp>
#include <etk/types.hpp>
#include <ememory/memory.hpp>
#include <etk/Function.hpp>
ememory::SharedPtr<audio::river::io::NodeAEC> audio::river::io::NodeAEC::create(const etk::String& _name, const ejson::Object& _config) {
return ememory::SharedPtr<audio::river::io::NodeAEC>(ETK_NEW(audio::river::io::NodeAEC, _name, _config));
}
ememory::SharedPtr<audio::river::Interface> audio::river::io::NodeAEC::createInput(float _freq,
const etk::Vector<audio::channel>& _map,
audio::format _format,
const etk::String& _objectName,
const etk::String& _name) {
// check if the output exist
const ejson::Object tmppp = m_config[_objectName].toObject();
if (tmppp.exist() == false) {
RIVER_ERROR("can not open a non existance virtual interface: '" << _objectName << "' not present in : " << m_config.getKeys());
return ememory::SharedPtr<audio::river::Interface>();
}
etk::String streamName = tmppp["map-on"].toString().get("error");
m_nbChunk = m_config["nb-chunk"].toNumber().get(1024);
// check if it is an Output:
etk::String type = tmppp["io"].toString().get("error");
if ( type != "input"
&& type != "feedback") {
RIVER_ERROR("can not open in output a virtual interface: '" << streamName << "' configured has : " << type);
return ememory::SharedPtr<audio::river::Interface>();
}
// get global hardware interface:
ememory::SharedPtr<audio::river::io::Manager> manager = audio::river::io::Manager::getInstance();
// get the output or input channel :
ememory::SharedPtr<audio::river::io::Node> node = manager->getNode(streamName);
// create user iterface:
ememory::SharedPtr<audio::river::Interface> interface;
interface = audio::river::Interface::create(_freq, _map, _format, node, tmppp);
if (interface != null) {
interface->setName(_name);
}
return interface;
}
audio::river::io::NodeAEC::NodeAEC(const etk::String& _name, const ejson::Object& _config) :
Node(_name, _config),
m_P_attaqueTime(1),
m_P_releaseTime(100),
m_P_minimumGain(10),
m_P_threshold(2),
m_P_latencyTime(100) {
audio::drain::IOFormatInterface interfaceFormat = getInterfaceFormat();
audio::drain::IOFormatInterface hardwareFormat = getHarwareFormat();
m_sampleTime = audio::Duration(1000000000/int64_t(hardwareFormat.getFrequency()));
/**
# connect in input mode
map-on-microphone:{
# generic virtual definition
io:"input",
map-on:"microphone",
resampling-type:"speexdsp",
resampling-option:"quality=10"
},
# connect in feedback mode
map-on-feedback:{
io:"feedback",
map-on:"speaker",
resampling-type:"speexdsp",
resampling-option:"quality=10",
},
# AEC algo definition
algo:"river-remover",
algo-mode:"cutter",
*/
etk::Vector<audio::channel> feedbackMap;
feedbackMap.pushBack(audio::channel_frontCenter);
RIVER_INFO("Create FEEDBACK : ");
m_interfaceFeedBack = createInput(hardwareFormat.getFrequency(),
feedbackMap,
hardwareFormat.getFormat(),
"map-on-feedback",
_name + "-AEC-feedback");
if (m_interfaceFeedBack == null) {
RIVER_ERROR("Can not opne virtual device ... map-on-feedback in " << _name);
return;
}
RIVER_INFO("Create MICROPHONE : ");
m_interfaceMicrophone = createInput(hardwareFormat.getFrequency(),
hardwareFormat.getMap(),
hardwareFormat.getFormat(),
"map-on-microphone",
_name + "-AEC-microphone");
if (m_interfaceMicrophone == null) {
RIVER_ERROR("Can not opne virtual device ... map-on-microphone in " << _name);
return;
}
// set callback mode ...
m_interfaceFeedBack->setInputCallback([=](const void* _data,
const audio::Time& _time,
size_t _nbChunk,
enum audio::format _format,
uint32_t _frequency,
const etk::Vector<audio::channel>& _map) {
onDataReceivedFeedBack(_data, _time, _nbChunk, _format, _frequency, _map);
});
// set callback mode ...
m_interfaceMicrophone->setInputCallback([=](const void* _data,
const audio::Time& _time,
size_t _nbChunk,
enum audio::format _format,
uint32_t _frequency,
const etk::Vector<audio::channel>& _map) {
onDataReceivedMicrophone(_data, _time, _nbChunk, _format, _frequency, _map);
});
m_bufferMicrophone.setCapacity(echrono::milliseconds(1000),
audio::getFormatBytes(hardwareFormat.getFormat())*hardwareFormat.getMap().size(),
hardwareFormat.getFrequency());
m_bufferFeedBack.setCapacity(echrono::milliseconds(1000),
audio::getFormatBytes(hardwareFormat.getFormat()), // only one channel ...
hardwareFormat.getFrequency());
m_process.updateInterAlgo();
}
audio::river::io::NodeAEC::~NodeAEC() {
RIVER_INFO("close input stream");
stop();
m_interfaceFeedBack.reset();
m_interfaceMicrophone.reset();
};
void audio::river::io::NodeAEC::start() {
ethread::UniqueLock lock(m_mutex);
RIVER_INFO("Start stream : '" << m_name << "' mode=" << (m_isInput?"input":"output") );
if (m_interfaceFeedBack != null) {
RIVER_INFO("Start FEEDBACK : ");
m_interfaceFeedBack->start();
}
if (m_interfaceMicrophone != null) {
RIVER_INFO("Start Microphone : ");
m_interfaceMicrophone->start();
}
}
void audio::river::io::NodeAEC::stop() {
ethread::UniqueLock lock(m_mutex);
if (m_interfaceFeedBack != null) {
m_interfaceFeedBack->stop();
}
if (m_interfaceMicrophone != null) {
m_interfaceMicrophone->stop();
}
}
void audio::river::io::NodeAEC::onDataReceivedMicrophone(const void* _data,
const audio::Time& _time,
size_t _nbChunk,
enum audio::format _format,
uint32_t _frequency,
const etk::Vector<audio::channel>& _map) {
RIVER_DEBUG("Microphone Time=" << _time << " _nbChunk=" << _nbChunk << " _map=" << _map << " _format=" << _format << " freq=" << _frequency);
RIVER_DEBUG(" next=" << _time + audio::Duration(0, _nbChunk*1000000000LL/int64_t(_frequency)) );
if (_format != audio::format_int16) {
RIVER_ERROR("call wrong type ... (need int16_t)");
}
// push data synchronize
ethread::UniqueLock lock(m_mutex);
m_bufferMicrophone.write(_data, _nbChunk, _time);
//RIVER_SAVE_FILE_MACRO(int16_t, "REC_Microphone.raw", _data, _nbChunk*_map.size());
process();
}
void audio::river::io::NodeAEC::onDataReceivedFeedBack(const void* _data,
const audio::Time& _time,
size_t _nbChunk,
enum audio::format _format,
uint32_t _frequency,
const etk::Vector<audio::channel>& _map) {
RIVER_DEBUG("FeedBack Time=" << _time << " _nbChunk=" << _nbChunk << " _map=" << _map << " _format=" << _format << " freq=" << _frequency);
RIVER_DEBUG(" next=" << _time + audio::Duration(0, _nbChunk*1000000000LL/int64_t(_frequency)) );
if (_format != audio::format_int16) {
RIVER_ERROR("call wrong type ... (need int16_t)");
}
// push data synchronize
ethread::UniqueLock lock(m_mutex);
m_bufferFeedBack.write(_data, _nbChunk, _time);
//RIVER_SAVE_FILE_MACRO(int16_t, "REC_FeedBack.raw", _data, _nbChunk*_map.size());
process();
}
void audio::river::io::NodeAEC::process() {
if ( m_bufferMicrophone.getSize() <= m_nbChunk
|| m_bufferFeedBack.getSize() <= m_nbChunk) {
return;
}
audio::Time MicTime = m_bufferMicrophone.getReadTimeStamp();
audio::Time fbTime = m_bufferFeedBack.getReadTimeStamp();
audio::Duration delta;
if (MicTime < fbTime) {
delta = fbTime - MicTime;
} else {
delta = MicTime - fbTime;
}
RIVER_INFO("check delta " << delta << " > " << m_sampleTime);
if (delta > m_sampleTime) {
// Synchronize if possible
if (MicTime < fbTime) {
RIVER_INFO("micTime < fbTime : Change Microphone time start " << fbTime);
RIVER_INFO(" old time stamp=" << m_bufferMicrophone.getReadTimeStamp());
m_bufferMicrophone.setReadPosition(fbTime);
RIVER_INFO(" new time stamp=" << m_bufferMicrophone.getReadTimeStamp());
}
if (MicTime > fbTime) {
RIVER_INFO("micTime > fbTime : Change FeedBack time start " << MicTime);
RIVER_INFO(" old time stamp=" << m_bufferFeedBack.getReadTimeStamp());
m_bufferFeedBack.setReadPosition(MicTime);
RIVER_INFO(" new time stamp=" << m_bufferFeedBack.getReadTimeStamp());
}
}
// check if enought time after synchronisation ...
if ( m_bufferMicrophone.getSize() <= m_nbChunk
|| m_bufferFeedBack.getSize() <= m_nbChunk) {
return;
}
MicTime = m_bufferMicrophone.getReadTimeStamp();
fbTime = m_bufferFeedBack.getReadTimeStamp();
if (MicTime-fbTime > m_sampleTime) {
RIVER_ERROR("Can not synchronize flow ... : " << MicTime << " != " << fbTime << " delta = " << (MicTime-fbTime));
return;
}
etk::Vector<uint8_t> dataMic;
etk::Vector<uint8_t> dataFB;
dataMic.resize(m_nbChunk*sizeof(int16_t)*2, 0);
dataFB.resize(m_nbChunk*sizeof(int16_t), 0);
while (true) {
MicTime = m_bufferMicrophone.getReadTimeStamp();
fbTime = m_bufferFeedBack.getReadTimeStamp();
RIVER_INFO(" process 256 samples ... micTime=" << MicTime << " fbTime=" << fbTime << " delta = " << (MicTime-fbTime));
m_bufferMicrophone.read(&dataMic[0], m_nbChunk);
m_bufferFeedBack.read(&dataFB[0], m_nbChunk);
RIVER_SAVE_FILE_MACRO(int16_t, "REC_Microphone_sync.raw", &dataMic[0], m_nbChunk*getHarwareFormat().getMap().size());
RIVER_SAVE_FILE_MACRO(int16_t, "REC_FeedBack_sync.raw", &dataFB[0], m_nbChunk);
// if threaded : send event / otherwise, process ...
processAEC(&dataMic[0], &dataFB[0], m_nbChunk, MicTime);
if ( m_bufferMicrophone.getSize() <= m_nbChunk
|| m_bufferFeedBack.getSize() <= m_nbChunk) {
return;
}
}
}
void audio::river::io::NodeAEC::processAEC(void* _dataMic, void* _dataFB, uint32_t _nbChunk, const audio::Time& _time) {
audio::drain::IOFormatInterface hardwareFormat = getHarwareFormat();
// TODO : Set all these parameter in the parameter configuration section ...
int32_t attaqueTime = etk::min(etk::max(0,m_P_attaqueTime),1000);
int32_t releaseTime = etk::min(etk::max(0,m_P_releaseTime),1000);
int32_t min_gain = 32767 * etk::min(etk::max(0,m_P_minimumGain),1000) / 1000;
int32_t threshold = 32767 * etk::min(etk::max(0,m_P_threshold),1000) / 1000;
int32_t latencyTime = etk::min(etk::max(0,m_P_latencyTime),1000);
int32_t nb_sample_latency = (hardwareFormat.getFrequency()/1000)*latencyTime;
int32_t increaseSample = 32767;
if (attaqueTime != 0) {
increaseSample = 32767/(hardwareFormat.getFrequency() * attaqueTime / 1000);
}
int32_t decreaseSample = 32767;
if (attaqueTime != 0) {
decreaseSample = 32767/(hardwareFormat.getFrequency() * releaseTime / 1000);
}
// Process section:
int16_t* dataMic = static_cast<int16_t*>(_dataMic);
int16_t* dataFB = static_cast<int16_t*>(_dataFB);
for (size_t iii=0; iii<_nbChunk; ++iii) {
if (abs(*dataFB++) > threshold) {
m_sampleCount = 0;
} else {
m_sampleCount++;
}
if (m_sampleCount > nb_sample_latency) {
m_gainValue += decreaseSample;
if (m_gainValue >= 32767) {
m_gainValue = 32767;
}
} else {
if (m_gainValue <= increaseSample) {
m_gainValue = 0;
} else {
m_gainValue -= increaseSample;
}
if (m_gainValue < min_gain) {
m_gainValue = min_gain;
}
}
for (size_t jjj=0; jjj<hardwareFormat.getMap().size(); ++jjj) {
*dataMic = static_cast<int16_t>((*dataMic * m_gainValue) >> 15);
dataMic++;
}
}
RIVER_SAVE_FILE_MACRO(int16_t, "REC_Microphone_clean.raw", _dataMic, _nbChunk*getHarwareFormat().getMap().size());
// simply send to upper requester...
newInput(_dataMic, _nbChunk, _time);
}
void audio::river::io::NodeAEC::generateDot(ememory::SharedPtr<etk::io::Interface>& _io) {
*_io << " subgraph clusterNode_" << m_uid << " {\n";
*_io << " color=blue;\n";
*_io << " label=\"[" << m_uid << "] IO::Node : " << m_name << "\";\n";
*_io << " NODE_" << m_uid << "_HW_AEC [ label=\"AEC\\n channelMap=" << etk::toString(getInterfaceFormat().getMap()) << "\" ];\n";
etk::String nameIn;
etk::String nameOut;
m_process.generateDot(_io, 3, m_uid, nameIn, nameOut, false);
*_io << " node [shape=square];\n";
*_io << " NODE_" << m_uid << "_demuxer [ label=\"DEMUXER\\n format=" << etk::toString(m_process.getOutputConfig().getFormat()) << "\" ];\n";
// Link all nodes :
*_io << " NODE_" << m_uid << "_HW_AEC -> " << nameIn << ";\n";
*_io << " " << nameOut << " -> NODE_" << m_uid << "_demuxer;\n";
*_io << " }\n";
if (m_interfaceMicrophone != null) {
*_io << " " << m_interfaceMicrophone->getDotNodeName() << " -> NODE_" << m_uid << "_HW_AEC;\n";
}
if (m_interfaceFeedBack != null) {
*_io << " " << m_interfaceFeedBack->getDotNodeName() << " -> NODE_" << m_uid << "_HW_AEC;\n";
}
*_io << " \n";
for (size_t iii=0; iii< m_listAvaillable.size(); ++iii) {
if (m_listAvaillable[iii].expired() == true) {
continue;
}
ememory::SharedPtr<audio::river::Interface> element = m_listAvaillable[iii].lock();
if (element == null) {
continue;
}
bool isLink = false;
for (size_t jjj=0; jjj<m_list.size(); ++jjj) {
if (element == m_list[jjj]) {
isLink = true;
}
}
if (element != null) {
if (element->getMode() == modeInterface_input) {
element->generateDot(_io, "NODE_" + etk::toString(m_uid) + "_demuxer", isLink);
} else if (element->getMode() == modeInterface_output) {
element->generateDot(_io, "NODE_" + etk::toString(m_uid) + "_muxer", isLink);
} else if (element->getMode() == modeInterface_feedback) {
element->generateDot(_io, "NODE_" + etk::toString(m_uid) + "_demuxer", isLink);
} else {
}
}
}
}

106
audio/river/io/NodeAEC.hpp Normal file
View File

@@ -0,0 +1,106 @@
/** @file
* @author Edouard DUPIN
* @copyright 2015, Edouard DUPIN, all right reserved
* @license MPL v2.0 (see license file)
*/
#pragma once
#include <audio/river/io/Node.hpp>
#include <audio/river/Interface.hpp>
#include <audio/drain/CircularBuffer.hpp>
namespace audio {
namespace river {
namespace io {
class Manager;
class NodeAEC : public Node {
protected:
/**
* @brief Constructor
*/
NodeAEC(const etk::String& _name, const ejson::Object& _config);
public:
/**
* @brief Factory of this Virtual Node.
* @param[in] _name Name of the node.
* @param[in] _config Configuration of the node.
*/
static ememory::SharedPtr<NodeAEC> create(const etk::String& _name, const ejson::Object& _config);
/**
* @brief Destructor
*/
virtual ~NodeAEC();
protected:
virtual void start();
virtual void stop();
ememory::SharedPtr<audio::river::Interface> m_interfaceMicrophone; //!< Interface on the Microphone.
ememory::SharedPtr<audio::river::Interface> m_interfaceFeedBack; //!< Interface on the feedback of speaker.
/**
* @brief Internal: create an input with the specific parameter:
* @param[in] _freq Frequency.
* @param[in] _map Channel map organization.
* @param[in] _format Sample format
* @param[in] _streamName
* @param[in] _name
* @return Interfae Pointer.
*/
ememory::SharedPtr<audio::river::Interface> createInput(float _freq,
const etk::Vector<audio::channel>& _map,
audio::format _format,
const etk::String& _streamName,
const etk::String& _name);
/**
* @brief Stream data input callback
* @todo : copy doc ..
*/
void onDataReceivedMicrophone(const void* _data,
const audio::Time& _time,
size_t _nbChunk,
enum audio::format _format,
uint32_t _frequency,
const etk::Vector<audio::channel>& _map);
/**
* @brief Stream data input callback
* @todo : copy doc ..
*/
void onDataReceivedFeedBack(const void* _data,
const audio::Time& _time,
size_t _nbChunk,
enum audio::format _format,
uint32_t _frequency,
const etk::Vector<audio::channel>& _map);
protected:
audio::drain::CircularBuffer m_bufferMicrophone; //!< temporary buffer to synchronize data.
audio::drain::CircularBuffer m_bufferFeedBack; //!< temporary buffer to synchronize data.
audio::Duration m_sampleTime; //!< represent the sample time at the specify frequency.
/**
* @brief Process synchronization on the 2 flow.
*/
void process();
/**
* @brief Process algorithm on the current 2 syncronize flow.
* @param[in] _dataMic Pointer in the Microphione interface.
* @param[in] _dataFB Pointer on the beedback buffer.
* @param[in] _nbChunk Number of chunk to process.
* @param[in] _time Time on the firsta sample that data has been captured.
* @return
*/
void processAEC(void* _dataMic, void* _dataFB, uint32_t _nbChunk, const audio::Time& _time);
public:
virtual void generateDot(ememory::SharedPtr<etk::io::Interface>& _io);
private:
int32_t m_nbChunk;
int32_t m_gainValue;
int32_t m_sampleCount;
int32_t m_P_attaqueTime; //ms
int32_t m_P_releaseTime; //ms
int32_t m_P_minimumGain; // %
int32_t m_P_threshold; // %
int32_t m_P_latencyTime; // ms
};
}
}
}

281
audio/river/io/NodeFile.cpp Normal file
View File

@@ -0,0 +1,281 @@
/** @file
* @author Edouard DUPIN
* @copyright 2015, Edouard DUPIN, all right reserved
* @license MPL v2.0 (see license file)
*/
#ifdef AUDIO_RIVER_BUILD_FILE
#include <audio/river/io/NodeFile.hpp>
#include <audio/river/debug.hpp>
#include <ememory/memory.hpp>
int32_t audio::river::io::NodeFile::recordCallback(const void* _inputBuffer,
const audio::Time& _timeInput,
uint32_t _nbChunk,
const etk::Vector<audio::orchestra::status>& _status) {
ethread::UniqueLock lock(m_mutex);
// TODO : Manage status ...
RIVER_VERBOSE("data Input size request :" << _nbChunk << " [BEGIN] status=" << _status << " nbIO=" << m_list.size());
newInput(_inputBuffer, _nbChunk, _timeInput);
return 0;
}
int32_t audio::river::io::NodeFile::playbackCallback(void* _outputBuffer,
const audio::Time& _timeOutput,
uint32_t _nbChunk,
const etk::Vector<audio::orchestra::status>& _status) {
ethread::UniqueLock lock(m_mutex);
// TODO : Manage status ...
RIVER_VERBOSE("data Output size request :" << _nbChunk << " [BEGIN] status=" << _status << " nbIO=" << m_list.size());
newOutput(_outputBuffer, _nbChunk, _timeOutput);
return 0;
}
ememory::SharedPtr<audio::river::io::NodeFile> audio::river::io::NodeFile::create(const etk::Path& _path, const ejson::Object& _config) {
return ememory::SharedPtr<audio::river::io::NodeFile>(ETK_NEW(audio::river::io::NodeFile, _path, _config));
}
audio::river::io::NodeFile::NodeFile(const etk::Path& _path, const ejson::Object& _config) :
Node(_path.getString(), _config) {
audio::drain::IOFormatInterface interfaceFormat = getInterfaceFormat();
audio::drain::IOFormatInterface hardwareFormat = getHarwareFormat();
/**
map-on:{ # select hardware interface and name
interface:"alsa", # interface : "alsa", "pulse", "core", ...
name:"default", # name of the interface
},
nb-chunk:1024 # number of chunk to open device (create the latency anf the frequency to call user)
*/
etk::String typeInterface = audio::orchestra::type::undefined;
etk::String streamName = "default";
const ejson::Object tmpObject = m_config["map-on"].toObject();
if (tmpObject.exist() == false) {
RIVER_WARNING("missing node : 'map-on' ==> auto map : 'auto:default'");
} else {
typeInterface = tmpObject.getStringValue("interface", audio::orchestra::type::undefined);
if (typeInterface == "auto") {
typeInterface = audio::orchestra::type::undefined;
}
streamName = tmpObject.getStringValue("name", "default");
}
int32_t nbChunk = m_config.getNumberValue("nb-chunk", 1024);
// intanciate specific API ...
m_interface.instanciate(typeInterface);
m_interface.setName(_name);
// TODO : Check return ...
etk::String type = m_config.getStringValue("type", "int16");
if (streamName == "") {
streamName = "default";
}
// search device ID :
RIVER_INFO("Open :");
RIVER_INFO(" m_streamName=" << streamName);
RIVER_INFO(" m_freq=" << hardwareFormat.getFrequency());
RIVER_INFO(" m_map=" << hardwareFormat.getMap());
RIVER_INFO(" m_format=" << hardwareFormat.getFormat());
RIVER_INFO(" m_isInput=" << m_isInput);
int32_t deviceId = -1;
/*
// TODO : Remove this from here (create an extern interface ...)
RIVER_INFO("Device list:");
for (int32_t iii=0; iii<m_interface.getDeviceCount(); ++iii) {
m_info = m_interface.getDeviceInfo(iii);
RIVER_INFO(" " << iii << " name :" << m_info.name);
m_info.display(2);
}
*/
// special case for default IO:
if (streamName == "default") {
if (m_isInput == true) {
deviceId = m_interface.getDefaultInputDevice();
} else {
deviceId = m_interface.getDefaultOutputDevice();
}
} else {
for (int32_t iii=0; iii<m_interface.getDeviceCount(); ++iii) {
m_info = m_interface.getDeviceInfo(iii);
if (m_info.name == streamName) {
RIVER_INFO(" Select ... id =" << iii);
deviceId = iii;
}
}
}
// TODO : Check if the devace with the specific name exist ...
/*
if (deviceId == -1) {
RIVER_ERROR("Can not find the " << streamName << " audio interface ... (use O default ...)");
deviceId = 0;
}
*/
// Open specific ID :
if (deviceId == -1) {
m_info = m_interface.getDeviceInfo(streamName);
} else {
m_info = m_interface.getDeviceInfo(deviceId);
}
// display property :
{
RIVER_INFO("Device " << deviceId << " - '" << streamName << "' property :");
m_info.display();
if (etk::isIn(hardwareFormat.getFormat(), m_info.nativeFormats) == false) {
if (type == "auto") {
if (etk::isIn(audio::format_int16, m_info.nativeFormats) == true) {
hardwareFormat.setFormat(audio::format_int16);
RIVER_INFO("auto set format: " << hardwareFormat.getFormat());
} else if (etk::isIn(audio::format_float, m_info.nativeFormats) == true) {
hardwareFormat.setFormat(audio::format_float);
RIVER_INFO("auto set format: " << hardwareFormat.getFormat());
} else if (etk::isIn(audio::format_int16_on_int32, m_info.nativeFormats) == true) {
hardwareFormat.setFormat(audio::format_int16_on_int32);
RIVER_INFO("auto set format: " << hardwareFormat.getFormat());
} else if (etk::isIn(audio::format_int24, m_info.nativeFormats) == true) {
hardwareFormat.setFormat(audio::format_int24);
RIVER_INFO("auto set format: " << hardwareFormat.getFormat());
} else if (m_info.nativeFormats.size() != 0) {
hardwareFormat.setFormat(m_info.nativeFormats[0]);
RIVER_INFO("auto set format: " << hardwareFormat.getFormat());
} else {
RIVER_CRITICAL("auto set format no element in the configuration: " << m_info.nativeFormats);
}
} else {
RIVER_CRITICAL("Can not manage input transforamtion: " << hardwareFormat.getFormat() << " not in " << m_info.nativeFormats);
}
}
if (etk::isIn(hardwareFormat.getFrequency(), m_info.sampleRates) == false) {
if (etk::isIn(48000, m_info.sampleRates) == true) {
hardwareFormat.setFrequency(48000);
RIVER_INFO("auto set frequency: " << hardwareFormat.getFrequency());
} else if (etk::isIn(44100, m_info.sampleRates) == true) {
hardwareFormat.setFrequency(44100);
RIVER_INFO("auto set frequency: " << hardwareFormat.getFrequency());
} else if (etk::isIn(32000, m_info.sampleRates) == true) {
hardwareFormat.setFrequency(32000);
RIVER_INFO("auto set frequency: " << hardwareFormat.getFrequency());
} else if (etk::isIn(16000, m_info.sampleRates) == true) {
hardwareFormat.setFrequency(16000);
RIVER_INFO("auto set frequency: " << hardwareFormat.getFrequency());
} else if (etk::isIn(8000, m_info.sampleRates) == true) {
hardwareFormat.setFrequency(8000);
RIVER_INFO("auto set frequency: " << hardwareFormat.getFrequency());
} else if (etk::isIn(96000, m_info.sampleRates) == true) {
hardwareFormat.setFrequency(96000);
RIVER_INFO("auto set frequency: " << hardwareFormat.getFrequency());
} else if (m_info.sampleRates.size() != 0) {
hardwareFormat.setFrequency(m_info.sampleRates[0]);
RIVER_INFO("auto set frequency: " << hardwareFormat.getFrequency() << "(first element in list) in " << m_info.sampleRates);
} else {
RIVER_CRITICAL("Can not manage input transforamtion:" << hardwareFormat.getFrequency() << " not in " << m_info.sampleRates);
}
interfaceFormat.setFrequency(hardwareFormat.getFrequency());
}
}
// open Audio device:
audio::orchestra::StreamParameters params;
params.deviceId = deviceId;
params.deviceName = streamName;
params.nChannels = hardwareFormat.getMap().size();
if (m_info.channels.size() < params.nChannels) {
RIVER_CRITICAL("Can not open hardware device with more channel (" << params.nChannels << ") that is autorized by hardware (" << m_info.channels.size() << ").");
}
audio::orchestra::StreamOptions option;
etk::from_string(option.mode, tmpObject->getStringValue("timestamp-mode", "soft"));
RIVER_DEBUG("interfaceFormat=" << interfaceFormat);
RIVER_DEBUG("hardwareFormat=" << hardwareFormat);
m_rtaudioFrameSize = nbChunk;
RIVER_INFO("Open output stream nbChannels=" << params.nChannels);
enum audio::orchestra::error err = audio::orchestra::error_none;
if (m_isInput == true) {
m_process.setInputConfig(hardwareFormat);
m_process.setOutputConfig(interfaceFormat);
err = m_interface.openStream(null,
&params,
hardwareFormat.getFormat(),
hardwareFormat.getFrequency(),
&m_rtaudioFrameSize,
[=](const void* _data,
const audio::Time& _time,
size_t _nbChunk,
enum audio::format _format,
uint32_t _frequency,
const etk::Vector<audio::channel>& _map
) {
recordCallback(_data, _time, _nbChunk, _map);
},
option
);
} else {
m_process.setInputConfig(interfaceFormat);
m_process.setOutputConfig(hardwareFormat);
err = m_interface.openStream(&params,
null,
hardwareFormat.getFormat(),
hardwareFormat.getFrequency(),
&m_rtaudioFrameSize,
[=](const void* _data,
const audio::Time& _time,
size_t _nbChunk,
enum audio::format _format,
uint32_t _frequency,
const etk::Vector<audio::channel>& _map
) {
playbackCallback(_data, _time, _nbChunk, _map);
},
option
);
}
if (err != audio::orchestra::error_none) {
RIVER_ERROR("Create stream : '" << m_name << "' mode=" << (m_isInput?"input":"output") << " can not create stream " << err);
}
m_process.updateInterAlgo();
}
audio::river::io::NodeFile::~NodeFile() {
ethread::UniqueLock lock(m_mutex);
RIVER_INFO("close input stream");
if (m_interface.isStreamOpen() ) {
m_interface.closeStream();
}
};
void audio::river::io::NodeFile::threadCallback() {
etk::thread::setName("RIVER file-IO");
// open the file
while (m_alive == true) {
ethread::sleepMilliSeconds((100));
}
}
void audio::river::io::NodeFile::start() {
ethread::UniqueLock lock(m_mutex);
if (m_thread != null) {
RIVER_ERROR("Start stream : '" << m_name << "' mode=" << (m_isInput?"read":"write") << " ==> already started ..." );
return;
}
m_alive = true;
RIVER_INFO("Start stream : '" << m_name << "' mode=" << (m_isInput?"read":"write") );
m_thread = ememory::makeShared<ethread::Thread>(&audio::river::io::NodeFile::threadCallback2, this);
m_time = audio::Time::now();
}
void audio::river::io::NodeFile::stop() {
ethread::UniqueLock lock(m_mutex);
m_alive = false;
RIVER_INFO("Stop stream : '" << m_name << "' mode=" << (m_isInput?"read":"write") );
// TODO : Need join ...
m_thread->join();
m_thread.reset();
}
#endif

View File

@@ -0,0 +1,54 @@
/** @file
* @author Edouard DUPIN
* @copyright 2015, Edouard DUPIN, all right reserved
* @license MPL v2.0 (see license file)
*/
#pragma once
#ifdef AUDIO_RIVER_BUILD_FILE
#include <audio/river/io/Node.hpp>
#include <audio/orchestra/Interface.hpp>
namespace audio {
namespace river {
namespace io {
class Manager;
class Group;
/**
* @brief Low level node that is manage on the interface with the extern lib airtaudio
*/
class NodeFile : public audio::river::io::Node {
friend class audio::river::io::Group;
protected:
/**
* @brief Constructor
*/
NodeFile(const etk::Path& _path, const ejson::Object& _config);
public:
static ememory::SharedPtr<NodeFile> create(const etk::Path& _path, const ejson::Object& _config);
/**
* @brief Destructor
*/
virtual ~NodeFile();
virtual bool isHarwareNode() {
return true;
};
protected:
audio::Time m_time; //!< time of the flow
ememory::SharedPtr<etk::io::Interface>& m_file; //!< File interface
bool m_restartAtEnd; //!< The read is done in loop
uint32_t m_sampleRate; //!< Sample Rate of the Raw file
audio::format m_format; //!< Format of the file
etk::Vector<audio::channel> m_map; //!< Map of the file
ememory::SharedPtr<ethread::Thread> m_thread; //!< playing thread of the flow
bool m_alive; //!< thread is active
protected:
virtual void start();
virtual void stop();
virtual void threadCallback():
};
}
}
}
#endif

View File

@@ -1,59 +1,59 @@
/** @file /** @file
* @author Edouard DUPIN * @author Edouard DUPIN
* @copyright 2015, Edouard DUPIN, all right reserved * @copyright 2015, Edouard DUPIN, all right reserved
* @license APACHE v2.0 (see license file) * @license MPL v2.0 (see license file)
*/ */
#include <river/io/NodeMuxer.h> #include <audio/river/io/NodeMuxer.hpp>
#include <river/debug.h> #include <audio/river/debug.hpp>
#include <etk/types.h> #include <etk/types.hpp>
#include <etk/memory.h> #include <ememory/memory.hpp>
#include <etk/functional.h> #include <etk/Function.hpp>
#undef __class__ ememory::SharedPtr<audio::river::io::NodeMuxer> audio::river::io::NodeMuxer::create(const etk::String& _name, const ejson::Object& _config) {
#define __class__ "io::NodeMuxer" return ememory::SharedPtr<audio::river::io::NodeMuxer>(ETK_NEW(audio::river::io::NodeMuxer, _name, _config));
std11::shared_ptr<river::io::NodeMuxer> river::io::NodeMuxer::create(const std::string& _name, const std11::shared_ptr<const ejson::Object>& _config) {
return std11::shared_ptr<river::io::NodeMuxer>(new river::io::NodeMuxer(_name, _config));
} }
std11::shared_ptr<river::Interface> river::io::NodeMuxer::createInput(float _freq, ememory::SharedPtr<audio::river::Interface> audio::river::io::NodeMuxer::createInput(float _freq,
const std::vector<audio::channel>& _map, const etk::Vector<audio::channel>& _map,
audio::format _format, audio::format _format,
const std::string& _objectName, const etk::String& _objectName,
const std::string& _name) { const etk::String& _name) {
// check if the output exist // check if the output exist
const std11::shared_ptr<const ejson::Object> tmppp = m_config->getObject(_objectName); const ejson::Object tmppp = m_config[_objectName].toObject();
if (tmppp == nullptr) { if (tmppp.exist() == false) {
RIVER_ERROR("can not open a non existance virtual interface: '" << _objectName << "' not present in : " << m_config->getKeys()); RIVER_ERROR("can not open a non existance virtual interface: '" << _objectName << "' not present in : " << m_config.getKeys());
return std11::shared_ptr<river::Interface>(); return ememory::SharedPtr<audio::river::Interface>();
} }
std::string streamName = tmppp->getStringValue("map-on", "error"); etk::String streamName = tmppp["map-on"].toString().get("error");
// check if it is an Output: // check if it is an Output:
std::string type = tmppp->getStringValue("io", "error"); etk::String type = tmppp["io"].toString().get("error");
if ( type != "input" if ( type != "input"
&& type != "feedback") { && type != "feedback") {
RIVER_ERROR("can not open in output a virtual interface: '" << streamName << "' configured has : " << type); RIVER_ERROR("can not open in output a virtual interface: '" << streamName << "' configured has : " << type);
return std11::shared_ptr<river::Interface>(); return ememory::SharedPtr<audio::river::Interface>();
} }
// get global hardware interface: // get global hardware interface:
std11::shared_ptr<river::io::Manager> manager = river::io::Manager::getInstance(); ememory::SharedPtr<audio::river::io::Manager> manager = audio::river::io::Manager::getInstance();
// get the output or input channel : // get the output or input channel :
std11::shared_ptr<river::io::Node> node = manager->getNode(streamName); ememory::SharedPtr<audio::river::io::Node> node = manager->getNode(streamName);
// create user iterface: // create user iterface:
std11::shared_ptr<river::Interface> interface; ememory::SharedPtr<audio::river::Interface> interface;
interface = river::Interface::create(_name, _freq, _map, _format, node, tmppp); interface = audio::river::Interface::create(_freq, _map, _format, node, tmppp);
if (interface != null) {
interface->setName(_name);
}
return interface; return interface;
} }
river::io::NodeMuxer::NodeMuxer(const std::string& _name, const std11::shared_ptr<const ejson::Object>& _config) : audio::river::io::NodeMuxer::NodeMuxer(const etk::String& _name, const ejson::Object& _config) :
Node(_name, _config) { Node(_name, _config) {
drain::IOFormatInterface interfaceFormat = getInterfaceFormat(); audio::drain::IOFormatInterface interfaceFormat = getInterfaceFormat();
drain::IOFormatInterface hardwareFormat = getHarwareFormat(); audio::drain::IOFormatInterface hardwareFormat = getHarwareFormat();
m_sampleTime = std11::chrono::nanoseconds(1000000000/int64_t(hardwareFormat.getFrequency())); m_sampleTime = audio::Duration(1000000000/int64_t(hardwareFormat.getFrequency()));
/** /**
# connect in input mode # connect in input mode
map-on-input-1:{ map-on-input-1:{
@@ -77,23 +77,23 @@ river::io::NodeMuxer::NodeMuxer(const std::string& _name, const std11::shared_pt
*/ */
RIVER_INFO("Create IN 1 : "); RIVER_INFO("Create IN 1 : ");
m_interfaceInput1 = createInput(hardwareFormat.getFrequency(), m_interfaceInput1 = createInput(hardwareFormat.getFrequency(),
std::vector<audio::channel>(), etk::Vector<audio::channel>(),
hardwareFormat.getFormat(), hardwareFormat.getFormat(),
"map-on-input-1", "map-on-input-1",
_name + "-muxer-in1"); _name + "-muxer-in1");
if (m_interfaceInput1 == nullptr) { if (m_interfaceInput1 == null) {
RIVER_ERROR("Can not opne virtual device ... map-on-input-1 in " << _name); RIVER_ERROR("Can not opne virtual device ... map-on-input-1 in " << _name);
return; return;
} }
std11::shared_ptr<const ejson::Array> listChannelMap = m_config->getArray("input-1-remap"); const ejson::Array listChannelMap = m_config["input-1-remap"].toArray();
if ( listChannelMap == nullptr if ( listChannelMap.exist() == false
|| listChannelMap->size() == 0) { || listChannelMap.size() == 0) {
m_mapInput1 = m_interfaceInput1->getInterfaceFormat().getMap(); m_mapInput1 = m_interfaceInput1->getInterfaceFormat().getMap();
} else { } else {
m_mapInput1.clear(); m_mapInput1.clear();
for (size_t iii=0; iii<listChannelMap->size(); ++iii) { for (const auto it : listChannelMap) {
std::string value = listChannelMap->getStringValue(iii); etk::String value = it.toString().get();
m_mapInput1.push_back(audio::getChannelFromString(value)); m_mapInput1.pushBack(audio::getChannelFromString(value));
} }
if (m_mapInput1.size() != m_interfaceInput1->getInterfaceFormat().getMap().size()) { if (m_mapInput1.size() != m_interfaceInput1->getInterfaceFormat().getMap().size()) {
RIVER_ERROR("Request remap of the Input 1 the 2 size is wrong ... request="); RIVER_ERROR("Request remap of the Input 1 the 2 size is wrong ... request=");
@@ -103,23 +103,23 @@ river::io::NodeMuxer::NodeMuxer(const std::string& _name, const std11::shared_pt
RIVER_INFO("Create IN 2 : "); RIVER_INFO("Create IN 2 : ");
m_interfaceInput2 = createInput(hardwareFormat.getFrequency(), m_interfaceInput2 = createInput(hardwareFormat.getFrequency(),
std::vector<audio::channel>(), etk::Vector<audio::channel>(),
hardwareFormat.getFormat(), hardwareFormat.getFormat(),
"map-on-input-2", "map-on-input-2",
_name + "-muxer-in2"); _name + "-muxer-in2");
if (m_interfaceInput2 == nullptr) { if (m_interfaceInput2 == null) {
RIVER_ERROR("Can not opne virtual device ... map-on-input-2 in " << _name); RIVER_ERROR("Can not opne virtual device ... map-on-input-2 in " << _name);
return; return;
} }
listChannelMap = m_config->getArray("input-2-remap"); const ejson::Array listChannelMap2 = m_config["input-2-remap"].toArray();
if ( listChannelMap == nullptr if ( listChannelMap2.exist() == false
|| listChannelMap->size() == 0) { || listChannelMap2.size() == 0) {
m_mapInput2 = m_interfaceInput2->getInterfaceFormat().getMap(); m_mapInput2 = m_interfaceInput2->getInterfaceFormat().getMap();
} else { } else {
m_mapInput2.clear(); m_mapInput2.clear();
for (size_t iii=0; iii<listChannelMap->size(); ++iii) { for (const auto it : listChannelMap2) {
std::string value = listChannelMap->getStringValue(iii); etk::String value = it.toString().get();
m_mapInput2.push_back(audio::getChannelFromString(value)); m_mapInput2.pushBack(audio::getChannelFromString(value));
} }
if (m_mapInput2.size() != m_interfaceInput2->getInterfaceFormat().getMap().size()) { if (m_mapInput2.size() != m_interfaceInput2->getInterfaceFormat().getMap().size()) {
RIVER_ERROR("Request remap of the Input 2 the 2 size is wrong ... request="); RIVER_ERROR("Request remap of the Input 2 the 2 size is wrong ... request=");
@@ -128,128 +128,132 @@ river::io::NodeMuxer::NodeMuxer(const std::string& _name, const std11::shared_pt
} }
// set callback mode ... // set callback mode ...
m_interfaceInput1->setInputCallback(std11::bind(&river::io::NodeMuxer::onDataReceivedInput1, m_interfaceInput1->setInputCallback([=](const void* _data,
this, const audio::Time& _time,
std11::placeholders::_1, size_t _nbChunk,
std11::placeholders::_2, enum audio::format _format,
std11::placeholders::_3, uint32_t _frequency,
std11::placeholders::_4, const etk::Vector<audio::channel>& _map) {
std11::placeholders::_5, onDataReceivedInput1(_data, _time, _nbChunk, _format, _frequency, _map);
std11::placeholders::_6)); });
// set callback mode ... // set callback mode ...
m_interfaceInput2->setInputCallback(std11::bind(&river::io::NodeMuxer::onDataReceivedInput2, m_interfaceInput2->setInputCallback([=](const void* _data,
this, const audio::Time& _time,
std11::placeholders::_1, size_t _nbChunk,
std11::placeholders::_2, enum audio::format _format,
std11::placeholders::_3, uint32_t _frequency,
std11::placeholders::_4, const etk::Vector<audio::channel>& _map) {
std11::placeholders::_5, onDataReceivedInput2(_data, _time, _nbChunk, _format, _frequency, _map);
std11::placeholders::_6)); });
m_bufferInput1.setCapacity(std11::chrono::milliseconds(1000), m_bufferInput1.setCapacity(echrono::milliseconds(1000),
audio::getFormatBytes(hardwareFormat.getFormat())*m_mapInput1.size(), audio::getFormatBytes(hardwareFormat.getFormat())*m_mapInput1.size(),
hardwareFormat.getFrequency()); hardwareFormat.getFrequency());
m_bufferInput2.setCapacity(std11::chrono::milliseconds(1000), m_bufferInput2.setCapacity(echrono::milliseconds(1000),
audio::getFormatBytes(hardwareFormat.getFormat())*m_mapInput2.size(), audio::getFormatBytes(hardwareFormat.getFormat())*m_mapInput2.size(),
hardwareFormat.getFrequency()); hardwareFormat.getFrequency());
m_process.updateInterAlgo(); m_process.updateInterAlgo();
} }
river::io::NodeMuxer::~NodeMuxer() { audio::river::io::NodeMuxer::~NodeMuxer() {
RIVER_INFO("close input stream"); RIVER_INFO("close input stream");
stop(); stop();
m_interfaceInput1.reset(); m_interfaceInput1.reset();
m_interfaceInput2.reset(); m_interfaceInput2.reset();
}; };
void river::io::NodeMuxer::start() { void audio::river::io::NodeMuxer::start() {
std11::unique_lock<std11::mutex> lock(m_mutex); ethread::UniqueLock lock(m_mutex);
RIVER_INFO("Start stream : '" << m_name << "' mode=" << (m_isInput?"input":"output") ); RIVER_INFO("Start stream : '" << m_name << "' mode=" << (m_isInput?"input":"output") );
if (m_interfaceInput1 != nullptr) { if (m_interfaceInput1 != null) {
RIVER_INFO("Start FEEDBACK : "); RIVER_INFO("Start FEEDBACK : ");
m_interfaceInput1->start(); m_interfaceInput1->start();
} }
if (m_interfaceInput2 != nullptr) { if (m_interfaceInput2 != null) {
RIVER_INFO("Start Microphone : "); RIVER_INFO("Start Microphone : ");
m_interfaceInput2->start(); m_interfaceInput2->start();
} }
} }
void river::io::NodeMuxer::stop() { void audio::river::io::NodeMuxer::stop() {
std11::unique_lock<std11::mutex> lock(m_mutex); ethread::UniqueLock lock(m_mutex);
if (m_interfaceInput1 != nullptr) { if (m_interfaceInput1 != null) {
m_interfaceInput1->stop(); m_interfaceInput1->stop();
} }
if (m_interfaceInput2 != nullptr) { if (m_interfaceInput2 != null) {
m_interfaceInput2->stop(); m_interfaceInput2->stop();
} }
} }
void river::io::NodeMuxer::onDataReceivedInput1(const void* _data, void audio::river::io::NodeMuxer::onDataReceivedInput1(const void* _data,
const std11::chrono::system_clock::time_point& _time, const audio::Time& _time,
size_t _nbChunk, size_t _nbChunk,
enum audio::format _format, enum audio::format _format,
uint32_t _frequency, uint32_t _frequency,
const std::vector<audio::channel>& _map) { const etk::Vector<audio::channel>& _map) {
RIVER_DEBUG("Microphone Time=" << _time << " _nbChunk=" << _nbChunk << " _map=" << _map << " _format=" << _format << " freq=" << _frequency); RIVER_PRINT("Input-1 Time=" << _time << " _nbChunk=" << _nbChunk << " _map=" << _map << " _format=" << _format << " freq=" << _frequency);
RIVER_DEBUG(" next=" << _time + std11::chrono::nanoseconds(_nbChunk*1000000000LL/int64_t(_frequency)) ); RIVER_DEBUG(" next=" << _time + audio::Duration(0, _nbChunk*1000000000LL/int64_t(_frequency)) );
/*
if (_format != audio::format_int16) { if (_format != audio::format_int16) {
RIVER_ERROR("call wrong type ... (need int16_t)"); RIVER_ERROR("call wrong type ... (need int16_t)");
} }
*/
// push data synchronize // push data synchronize
std11::unique_lock<std11::mutex> lock(m_mutex); ethread::UniqueLock lock(m_mutex);
m_bufferInput1.write(_data, _nbChunk, _time); m_bufferInput1.write(_data, _nbChunk, _time);
//RIVER_SAVE_FILE_MACRO(int16_t, "REC_Microphone.raw", _data, _nbChunk*_map.size()); //RIVER_SAVE_FILE_MACRO(int16_t, "REC_muxer_input_1.raw", _data, _nbChunk*_map.size());
process(); process();
} }
void river::io::NodeMuxer::onDataReceivedInput2(const void* _data, void audio::river::io::NodeMuxer::onDataReceivedInput2(const void* _data,
const std11::chrono::system_clock::time_point& _time, const audio::Time& _time,
size_t _nbChunk, size_t _nbChunk,
enum audio::format _format, enum audio::format _format,
uint32_t _frequency, uint32_t _frequency,
const std::vector<audio::channel>& _map) { const etk::Vector<audio::channel>& _map) {
RIVER_DEBUG("FeedBack Time=" << _time << " _nbChunk=" << _nbChunk << " _map=" << _map << " _format=" << _format << " freq=" << _frequency); RIVER_PRINT("Input-2 Time=" << _time << " _nbChunk=" << _nbChunk << " _map=" << _map << " _format=" << _format << " freq=" << _frequency);
RIVER_DEBUG(" next=" << _time + std11::chrono::nanoseconds(_nbChunk*1000000000LL/int64_t(_frequency)) ); RIVER_DEBUG(" next=" << _time + audio::Duration(0, _nbChunk*1000000000LL/int64_t(_frequency)) );
/*
if (_format != audio::format_int16) { if (_format != audio::format_int16) {
RIVER_ERROR("call wrong type ... (need int16_t)"); RIVER_ERROR("call wrong type ... (need int16_t)");
} }
*/
// push data synchronize // push data synchronize
std11::unique_lock<std11::mutex> lock(m_mutex); ethread::UniqueLock lock(m_mutex);
m_bufferInput2.write(_data, _nbChunk, _time); m_bufferInput2.write(_data, _nbChunk, _time);
//RIVER_SAVE_FILE_MACRO(int16_t, "REC_FeedBack.raw", _data, _nbChunk*_map.size()); //RIVER_SAVE_FILE_MACRO(int16_t, "REC_muxer_input_2.raw", _data, _nbChunk*_map.size());
process(); process();
} }
void river::io::NodeMuxer::process() { void audio::river::io::NodeMuxer::process() {
if (m_bufferInput1.getSize() <= 256) { if (m_bufferInput1.getSize() <= 256) {
return; return;
} }
if (m_bufferInput2.getSize() <= 256) { if (m_bufferInput2.getSize() <= 256) {
return; return;
} }
std11::chrono::system_clock::time_point in1Time = m_bufferInput1.getReadTimeStamp(); RIVER_PRINT("process : s1=" << m_bufferInput1.getSize() << " s2=" << m_bufferInput2.getSize());
std11::chrono::system_clock::time_point in2Time = m_bufferInput2.getReadTimeStamp(); audio::Time in1Time = m_bufferInput1.getReadTimeStamp();
std11::chrono::nanoseconds delta; audio::Time in2Time = m_bufferInput2.getReadTimeStamp();
audio::Duration delta;
if (in1Time < in2Time) { if (in1Time < in2Time) {
delta = in2Time - in1Time; delta = in2Time - in1Time;
} else { } else {
delta = in1Time - in2Time; delta = in1Time - in2Time;
} }
RIVER_VERBOSE("check delta " << delta << " > " << m_sampleTime);
RIVER_INFO("check delta " << delta.count() << " > " << m_sampleTime.count());
if (delta > m_sampleTime) { if (delta > m_sampleTime) {
// Synchronize if possible // Synchronize if possible
if (in1Time < in2Time) { if (in1Time < in2Time) {
RIVER_INFO("in1Time < in2Time : Change Microphone time start " << in2Time); RIVER_INFO("in1Time < in2Time : Change Input-1 time start " << in2Time);
RIVER_INFO(" old time stamp=" << m_bufferInput1.getReadTimeStamp()); RIVER_INFO(" old time stamp=" << m_bufferInput1.getReadTimeStamp());
m_bufferInput1.setReadPosition(in2Time); m_bufferInput1.setReadPosition(in2Time);
RIVER_INFO(" new time stamp=" << m_bufferInput1.getReadTimeStamp()); RIVER_INFO(" new time stamp=" << m_bufferInput1.getReadTimeStamp());
} }
if (in1Time > in2Time) { if (in1Time > in2Time) {
RIVER_INFO("in1Time > in2Time : Change FeedBack time start " << in1Time); RIVER_INFO("in1Time > in2Time : Change Input-2 time start " << in1Time);
RIVER_INFO(" old time stamp=" << m_bufferInput2.getReadTimeStamp()); RIVER_INFO(" old time stamp=" << m_bufferInput2.getReadTimeStamp());
m_bufferInput2.setReadPosition(in1Time); m_bufferInput2.setReadPosition(in1Time);
RIVER_INFO(" new time stamp=" << m_bufferInput2.getReadTimeStamp()); RIVER_INFO(" new time stamp=" << m_bufferInput2.getReadTimeStamp());
@@ -267,22 +271,22 @@ void river::io::NodeMuxer::process() {
in2Time = m_bufferInput2.getReadTimeStamp(); in2Time = m_bufferInput2.getReadTimeStamp();
if (in1Time-in2Time > m_sampleTime) { if (in1Time-in2Time > m_sampleTime) {
RIVER_ERROR("Can not synchronize flow ... : " << in1Time << " != " << in2Time << " delta = " << (in1Time-in2Time).count()/1000 << " µs"); RIVER_ERROR("Can not synchronize flow ... : " << in1Time << " != " << in2Time << " delta = " << (in1Time-in2Time));
return; return;
} }
std::vector<uint8_t> dataIn1; etk::Vector<uint8_t> dataIn1;
std::vector<uint8_t> dataIn2; etk::Vector<uint8_t> dataIn2;
dataIn1.resize(256*sizeof(int16_t)*m_mapInput1.size(), 0); dataIn1.resize(256*audio::getFormatBytes(getInterfaceFormat().getFormat())*m_mapInput1.size(), 0);
dataIn2.resize(256*sizeof(int16_t)*m_mapInput2.size(), 0); dataIn2.resize(256*audio::getFormatBytes(getInterfaceFormat().getFormat())*m_mapInput2.size(), 0);
m_data.resize(256*sizeof(int16_t)*getInterfaceFormat().getMap().size(), 0); m_data.resize(256*audio::getFormatBytes(getInterfaceFormat().getFormat())*getInterfaceFormat().getMap().size(), 0);
while (true) { while (true) {
in1Time = m_bufferInput1.getReadTimeStamp(); in1Time = m_bufferInput1.getReadTimeStamp();
in2Time = m_bufferInput2.getReadTimeStamp(); in2Time = m_bufferInput2.getReadTimeStamp();
//RIVER_INFO(" process 256 samples ... in1Time=" << in1Time << " in2Time=" << in2Time << " delta = " << (in1Time-in2Time).count()); //RIVER_INFO(" process 256 samples ... in1Time=" << in1Time << " in2Time=" << in2Time << " delta = " << (in1Time-in2Time));
m_bufferInput1.read(&dataIn1[0], 256); m_bufferInput1.read(&dataIn1[0], 256);
m_bufferInput2.read(&dataIn2[0], 256); m_bufferInput2.read(&dataIn2[0], 256);
//RIVER_SAVE_FILE_MACRO(int16_t, "REC_INPUT1.raw", &dataIn1[0], 256 * m_mapInput1.size()); //RIVER_SAVE_FILE_MACRO(int16_t, "REC_muxer_output_1.raw", &dataIn1[0], 256 * m_mapInput1.size());
//RIVER_SAVE_FILE_MACRO(int16_t, "REC_INPUT2.raw", &dataIn2[0], 256 * m_mapInput2.size()); //RIVER_SAVE_FILE_MACRO(int16_t, "REC_muxer_output_2.raw", &dataIn2[0], 256 * m_mapInput2.size());
// if threaded : send event / otherwise, process ... // if threaded : send event / otherwise, process ...
processMuxer(&dataIn1[0], &dataIn2[0], 256, in1Time); processMuxer(&dataIn1[0], &dataIn2[0], 256, in1Time);
if ( m_bufferInput1.getSize() <= 256 if ( m_bufferInput1.getSize() <= 256
@@ -293,12 +297,12 @@ void river::io::NodeMuxer::process() {
} }
void river::io::NodeMuxer::reorder(void* _output, uint32_t _nbChunk, void* _input, const std::vector<audio::channel>& _mapInput) { void audio::river::io::NodeMuxer::reorder(void* _output, uint32_t _nbChunk, void* _input, const etk::Vector<audio::channel>& _mapInput) {
// real process: (only depend of data size): // real process: (only depend of data size):
switch (getInterfaceFormat().getFormat()) { switch (getInterfaceFormat().getFormat()) {
case audio::format_int8: case audio::format_int8:
{ {
DRAIN_VERBOSE("convert " << _mapInput << " ==> " << getInterfaceFormat().getMap()); RIVER_VERBOSE("convert " << _mapInput << " ==> " << getInterfaceFormat().getMap());
int8_t* in = static_cast<int8_t*>(_input); int8_t* in = static_cast<int8_t*>(_input);
int8_t* out = static_cast<int8_t*>(_output); int8_t* out = static_cast<int8_t*>(_output);
for (size_t kkk=0; kkk<getInterfaceFormat().getMap().size(); ++kkk) { for (size_t kkk=0; kkk<getInterfaceFormat().getMap().size(); ++kkk) {
@@ -314,7 +318,7 @@ void river::io::NodeMuxer::reorder(void* _output, uint32_t _nbChunk, void* _inpu
} }
} }
} }
DRAIN_VERBOSE(" " << convertId << " ==> " << kkk); RIVER_VERBOSE(" " << convertId << " ==> " << kkk);
if (convertId != -1) { if (convertId != -1) {
for (size_t iii=0; iii<_nbChunk; ++iii) { for (size_t iii=0; iii<_nbChunk; ++iii) {
out[iii*getInterfaceFormat().getMap().size()+kkk] = in[iii*_mapInput.size()+convertId]; out[iii*getInterfaceFormat().getMap().size()+kkk] = in[iii*_mapInput.size()+convertId];
@@ -326,7 +330,7 @@ void river::io::NodeMuxer::reorder(void* _output, uint32_t _nbChunk, void* _inpu
default: default:
case audio::format_int16: case audio::format_int16:
if (getInterfaceFormat().getMap().size() == 1) { if (getInterfaceFormat().getMap().size() == 1) {
DRAIN_VERBOSE("convert " << _mapInput << " ==> " << getInterfaceFormat().getMap()); RIVER_VERBOSE("convert " << _mapInput << " ==> " << getInterfaceFormat().getMap());
int16_t* in = static_cast<int16_t*>(_input); int16_t* in = static_cast<int16_t*>(_input);
int16_t* out = static_cast<int16_t*>(_output); int16_t* out = static_cast<int16_t*>(_output);
for (size_t iii=0; iii<_nbChunk; ++iii) { for (size_t iii=0; iii<_nbChunk; ++iii) {
@@ -337,7 +341,7 @@ void river::io::NodeMuxer::reorder(void* _output, uint32_t _nbChunk, void* _inpu
out[iii] = val/_mapInput.size(); out[iii] = val/_mapInput.size();
} }
} else { } else {
DRAIN_VERBOSE("convert " << _mapInput << " ==> " << getInterfaceFormat().getMap()); RIVER_VERBOSE("convert " << _mapInput << " ==> " << getInterfaceFormat().getMap());
int16_t* in = static_cast<int16_t*>(_input); int16_t* in = static_cast<int16_t*>(_input);
int16_t* out = static_cast<int16_t*>(_output); int16_t* out = static_cast<int16_t*>(_output);
for (size_t kkk=0; kkk<getInterfaceFormat().getMap().size(); ++kkk) { for (size_t kkk=0; kkk<getInterfaceFormat().getMap().size(); ++kkk) {
@@ -353,7 +357,7 @@ void river::io::NodeMuxer::reorder(void* _output, uint32_t _nbChunk, void* _inpu
} }
} }
} }
DRAIN_VERBOSE(" " << convertId << " ==> " << kkk); RIVER_VERBOSE(" " << convertId << " ==> " << kkk);
if (convertId != -1) { if (convertId != -1) {
for (size_t iii=0; iii<_nbChunk; ++iii) { for (size_t iii=0; iii<_nbChunk; ++iii) {
out[iii*getInterfaceFormat().getMap().size()+kkk] = in[iii*_mapInput.size()+convertId]; out[iii*getInterfaceFormat().getMap().size()+kkk] = in[iii*_mapInput.size()+convertId];
@@ -367,7 +371,7 @@ void river::io::NodeMuxer::reorder(void* _output, uint32_t _nbChunk, void* _inpu
case audio::format_int32: case audio::format_int32:
case audio::format_float: case audio::format_float:
{ {
DRAIN_VERBOSE("convert (2) " << _mapInput << " ==> " << getInterfaceFormat().getMap()); RIVER_VERBOSE("convert (2) " << _mapInput << " ==> " << getInterfaceFormat().getMap());
uint32_t* in = static_cast<uint32_t*>(_input); uint32_t* in = static_cast<uint32_t*>(_input);
uint32_t* out = static_cast<uint32_t*>(_output); uint32_t* out = static_cast<uint32_t*>(_output);
for (size_t kkk=0; kkk<getInterfaceFormat().getMap().size(); ++kkk) { for (size_t kkk=0; kkk<getInterfaceFormat().getMap().size(); ++kkk) {
@@ -393,7 +397,7 @@ void river::io::NodeMuxer::reorder(void* _output, uint32_t _nbChunk, void* _inpu
break; break;
case audio::format_double: case audio::format_double:
{ {
DRAIN_VERBOSE("convert (2) " << _mapInput << " ==> " << getInterfaceFormat().getMap()); RIVER_VERBOSE("convert (2) " << _mapInput << " ==> " << getInterfaceFormat().getMap());
uint64_t* in = static_cast<uint64_t*>(_input); uint64_t* in = static_cast<uint64_t*>(_input);
uint64_t* out = static_cast<uint64_t*>(_output); uint64_t* out = static_cast<uint64_t*>(_output);
for (size_t kkk=0; kkk<getInterfaceFormat().getMap().size(); ++kkk) { for (size_t kkk=0; kkk<getInterfaceFormat().getMap().size(); ++kkk) {
@@ -420,7 +424,7 @@ void river::io::NodeMuxer::reorder(void* _output, uint32_t _nbChunk, void* _inpu
} }
} }
void river::io::NodeMuxer::processMuxer(void* _dataIn1, void* _dataIn2, uint32_t _nbChunk, const std11::chrono::system_clock::time_point& _time) { void audio::river::io::NodeMuxer::processMuxer(void* _dataIn1, void* _dataIn2, uint32_t _nbChunk, const audio::Time& _time) {
//RIVER_INFO("must Mux data : " << m_mapInput1 << " + " << m_mapInput2 << " ==> " << getInterfaceFormat().getMap()); //RIVER_INFO("must Mux data : " << m_mapInput1 << " + " << m_mapInput2 << " ==> " << getInterfaceFormat().getMap());
memset(&m_data[0], 0, m_data.size()); memset(&m_data[0], 0, m_data.size());
reorder(&m_data[0], _nbChunk, _dataIn1, m_mapInput1); reorder(&m_data[0], _nbChunk, _dataIn1, m_mapInput1);
@@ -429,36 +433,36 @@ void river::io::NodeMuxer::processMuxer(void* _dataIn1, void* _dataIn2, uint32_t
} }
void river::io::NodeMuxer::generateDot(etk::FSNode& _node) { void audio::river::io::NodeMuxer::generateDot(ememory::SharedPtr<etk::io::Interface>& _io) {
_node << " subgraph clusterNode_" << m_uid << " {\n"; *_io << " subgraph clusterNode_" << m_uid << " {\n";
_node << " color=blue;\n"; *_io << " color=blue;\n";
_node << " label=\"[" << m_uid << "] IO::Node : " << m_name << "\";\n"; *_io << " label=\"[" << m_uid << "] IO::Node : " << m_name << "\";\n";
_node << " node [shape=box];\n"; *_io << " node [shape=box];\n";
// TODO : Create a structure ... // TODO : Create a structure ...
_node << " NODE_" << m_uid << "_HW_MUXER [ label=\"Muxer\\n channelMap=" << etk::to_string(getInterfaceFormat().getMap()) << "\" ];\n"; *_io << " NODE_" << m_uid << "_HW_MUXER [ label=\"Muxer\\n channelMap=" << etk::toString(getInterfaceFormat().getMap()) << "\" ];\n";
std::string nameIn; etk::String nameIn;
std::string nameOut; etk::String nameOut;
m_process.generateDot(_node, 3, m_uid, nameIn, nameOut, false); m_process.generateDot(_io, 3, m_uid, nameIn, nameOut, false);
_node << " node [shape=square];\n"; *_io << " node [shape=square];\n";
_node << " NODE_" << m_uid << "_demuxer [ label=\"DEMUXER\\n format=" << etk::to_string(m_process.getOutputConfig().getFormat()) << "\" ];\n"; *_io << " NODE_" << m_uid << "_demuxer [ label=\"DEMUXER\\n format=" << etk::toString(m_process.getOutputConfig().getFormat()) << "\" ];\n";
// Link all nodes : // Link all nodes :
_node << " NODE_" << m_uid << "_HW_MUXER -> " << nameIn << ";\n"; *_io << " NODE_" << m_uid << "_HW_MUXER -> " << nameIn << ";\n";
_node << " " << nameOut << " -> NODE_" << m_uid << "_demuxer;\n"; *_io << " " << nameOut << " -> NODE_" << m_uid << "_demuxer;\n";
_node << " }\n"; *_io << " }\n";
if (m_interfaceInput2 != nullptr) { if (m_interfaceInput2 != null) {
_node << " " << m_interfaceInput2->getDotNodeName() << " -> NODE_" << m_uid << "_HW_MUXER;\n"; *_io << " " << m_interfaceInput2->getDotNodeName() << " -> NODE_" << m_uid << "_HW_MUXER;\n";
} }
if (m_interfaceInput1 != nullptr) { if (m_interfaceInput1 != null) {
_node << " " << m_interfaceInput1->getDotNodeName() << " -> NODE_" << m_uid << "_HW_MUXER;\n"; *_io << " " << m_interfaceInput1->getDotNodeName() << " -> NODE_" << m_uid << "_HW_MUXER;\n";
} }
_node << " \n"; *_io << " \n";
for (size_t iii=0; iii< m_listAvaillable.size(); ++iii) { for (size_t iii=0; iii< m_listAvaillable.size(); ++iii) {
if (m_listAvaillable[iii].expired() == true) { if (m_listAvaillable[iii].expired() == true) {
continue; continue;
} }
std11::shared_ptr<river::Interface> element = m_listAvaillable[iii].lock(); ememory::SharedPtr<audio::river::Interface> element = m_listAvaillable[iii].lock();
if (element == nullptr) { if (element == null) {
continue; continue;
} }
bool isLink = false; bool isLink = false;
@@ -467,17 +471,17 @@ void river::io::NodeMuxer::generateDot(etk::FSNode& _node) {
isLink = true; isLink = true;
} }
} }
if (element != nullptr) { if (element != null) {
if (element->getMode() == modeInterface_input) { if (element->getMode() == modeInterface_input) {
element->generateDot(_node, "NODE_" + etk::to_string(m_uid) + "_demuxer", isLink); element->generateDot(_io, "NODE_" + etk::toString(m_uid) + "_demuxer", isLink);
} else if (element->getMode() == modeInterface_output) { } else if (element->getMode() == modeInterface_output) {
element->generateDot(_node, "NODE_" + etk::to_string(m_uid) + "_muxer", isLink); element->generateDot(_io, "NODE_" + etk::toString(m_uid) + "_muxer", isLink);
} else if (element->getMode() == modeInterface_feedback) { } else if (element->getMode() == modeInterface_feedback) {
element->generateDot(_node, "NODE_" + etk::to_string(m_uid) + "_demuxer", isLink); element->generateDot(_io, "NODE_" + etk::toString(m_uid) + "_demuxer", isLink);
} else { } else {
} }
} }
} }
_node << "\n"; *_io << "\n";
} }

View File

@@ -0,0 +1,66 @@
/** @file
* @author Edouard DUPIN
* @copyright 2015, Edouard DUPIN, all right reserved
* @license MPL v2.0 (see license file)
*/
#pragma once
#include <audio/river/io/Node.hpp>
#include <audio/river/Interface.hpp>
#include <audio/drain/CircularBuffer.hpp>
namespace audio {
namespace river {
namespace io {
class Manager;
class NodeMuxer : public Node {
protected:
/**
* @brief Constructor
*/
NodeMuxer(const etk::String& _name, const ejson::Object& _config);
public:
static ememory::SharedPtr<NodeMuxer> create(const etk::String& _name, const ejson::Object& _config);
/**
* @brief Destructor
*/
virtual ~NodeMuxer();
protected:
virtual void start();
virtual void stop();
ememory::SharedPtr<audio::river::Interface> m_interfaceInput1;
ememory::SharedPtr<audio::river::Interface> m_interfaceInput2;
ememory::SharedPtr<audio::river::Interface> createInput(float _freq,
const etk::Vector<audio::channel>& _map,
audio::format _format,
const etk::String& _streamName,
const etk::String& _name);
void onDataReceivedInput1(const void* _data,
const audio::Time& _time,
size_t _nbChunk,
enum audio::format _format,
uint32_t _frequency,
const etk::Vector<audio::channel>& _map);
void onDataReceivedInput2(const void* _data,
const audio::Time& _time,
size_t _nbChunk,
enum audio::format _format,
uint32_t _frequency,
const etk::Vector<audio::channel>& _map);
etk::Vector<audio::channel> m_mapInput1;
etk::Vector<audio::channel> m_mapInput2;
audio::drain::CircularBuffer m_bufferInput1;
audio::drain::CircularBuffer m_bufferInput2;
audio::Duration m_sampleTime; //!< represent the sample time at the specify frequency.
void process();
void processMuxer(void* _dataMic, void* _dataFB, uint32_t _nbChunk, const audio::Time& _time);
etk::Vector<uint8_t> m_data;
public:
virtual void generateDot(ememory::SharedPtr<etk::io::Interface>& _io);
private:
void reorder(void* _output, uint32_t _nbChunk, void* _input, const etk::Vector<audio::channel>& _mapInput);
};
}
}
}

View File

@@ -0,0 +1,267 @@
/** @file
* @author Edouard DUPIN
* @copyright 2015, Edouard DUPIN, all right reserved
* @license MPL v2.0 (see license file)
*/
#ifdef AUDIO_RIVER_BUILD_ORCHESTRA
#include <audio/river/io/NodeOrchestra.hpp>
#include <audio/river/debug.hpp>
#include <ememory/memory.hpp>
int32_t audio::river::io::NodeOrchestra::recordCallback(const void* _inputBuffer,
const audio::Time& _timeInput,
uint32_t _nbChunk,
const etk::Vector<audio::orchestra::status>& _status) {
ethread::UniqueLock lock(m_mutex);
// TODO : Manage status ...
RIVER_VERBOSE("data Input size request :" << _nbChunk << " [BEGIN] status=" << _status << " nbIO=" << m_list.size());
newInput(_inputBuffer, _nbChunk, _timeInput);
return 0;
}
int32_t audio::river::io::NodeOrchestra::playbackCallback(void* _outputBuffer,
const audio::Time& _timeOutput,
uint32_t _nbChunk,
const etk::Vector<audio::orchestra::status>& _status) {
ethread::UniqueLock lock(m_mutex);
// TODO : Manage status ...
RIVER_VERBOSE("data Output size request :" << _nbChunk << " [BEGIN] status=" << _status << " nbIO=" << m_list.size() << " data=" << uint64_t(_outputBuffer));
newOutput(_outputBuffer, _nbChunk, _timeOutput);
return 0;
}
ememory::SharedPtr<audio::river::io::NodeOrchestra> audio::river::io::NodeOrchestra::create(const etk::String& _name, const ejson::Object& _config) {
return ememory::SharedPtr<audio::river::io::NodeOrchestra>(ETK_NEW(audio::river::io::NodeOrchestra, _name, _config));
}
audio::river::io::NodeOrchestra::NodeOrchestra(const etk::String& _name, const ejson::Object& _config) :
Node(_name, _config) {
audio::drain::IOFormatInterface interfaceFormat = getInterfaceFormat();
audio::drain::IOFormatInterface hardwareFormat = getHarwareFormat();
/**
map-on:{ # select hardware interface and name
interface:"alsa", # interface : "alsa", "pulse", "core", ...
name:"default", # name of the interface
},
nb-chunk:1024 # number of chunk to open device (create the latency anf the frequency to call user)
*/
etk::String typeInterface = audio::orchestra::typeUndefined;
etk::String streamName = "default";
const ejson::Object tmpObject = m_config["map-on"].toObject();
if (tmpObject.exist() == false) {
RIVER_WARNING("missing node : 'map-on' ==> auto map : 'auto:default'");
} else {
typeInterface = tmpObject["interface"].toString().get(audio::orchestra::typeUndefined);
if (typeInterface == "auto") {
typeInterface = audio::orchestra::typeUndefined;
}
streamName = tmpObject["name"].toString().get("default");
}
int32_t nbChunk = m_config["nb-chunk"].toNumber().get(1024);
// intanciate specific API ...
m_interface.instanciate(typeInterface);
m_interface.setName(_name);
// TODO : Check return ...
etk::String type = m_config["type"].toString().get("int16");
if (streamName == "") {
streamName = "default";
}
// search device ID :
RIVER_INFO("Open :");
RIVER_INFO(" m_streamName=" << streamName);
RIVER_INFO(" m_freq=" << hardwareFormat.getFrequency());
RIVER_INFO(" m_map=" << hardwareFormat.getMap());
RIVER_INFO(" m_format=" << hardwareFormat.getFormat());
RIVER_INFO(" m_isInput=" << m_isInput);
int32_t deviceId = -1;
/*
// TODO : Remove this from here (create an extern interface ...)
RIVER_INFO("Device list:");
for (int32_t iii=0; iii<m_interface.getDeviceCount(); ++iii) {
m_info = m_interface.getDeviceInfo(iii);
RIVER_INFO(" " << iii << " name :" << m_info.name);
m_info.display(2);
}
*/
// special case for default IO:
if (streamName == "default") {
if (m_isInput == true) {
deviceId = m_interface.getDefaultInputDevice();
} else {
deviceId = m_interface.getDefaultOutputDevice();
}
} else {
for (int32_t iii=0; iii<m_interface.getDeviceCount(); ++iii) {
m_info = m_interface.getDeviceInfo(iii);
if (m_info.name == streamName) {
RIVER_INFO(" Select ... id =" << iii);
deviceId = iii;
}
}
}
// TODO : Check if the devace with the specific name exist ...
/*
if (deviceId == -1) {
RIVER_ERROR("Can not find the " << streamName << " audio interface ... (use O default ...)");
deviceId = 0;
}
*/
// Open specific ID :
if (deviceId == -1) {
m_info = m_interface.getDeviceInfo(streamName);
} else {
m_info = m_interface.getDeviceInfo(deviceId);
}
// display property :
{
RIVER_INFO("Device " << deviceId << " - '" << streamName << "' property :");
m_info.display();
if (etk::isIn(hardwareFormat.getFormat(), m_info.nativeFormats) == false) {
if (type == "auto") {
if (etk::isIn(audio::format_int16, m_info.nativeFormats) == true) {
hardwareFormat.setFormat(audio::format_int16);
RIVER_INFO("auto set format: " << hardwareFormat.getFormat());
} else if (etk::isIn(audio::format_float, m_info.nativeFormats) == true) {
hardwareFormat.setFormat(audio::format_float);
RIVER_INFO("auto set format: " << hardwareFormat.getFormat());
} else if (etk::isIn(audio::format_int16_on_int32, m_info.nativeFormats) == true) {
hardwareFormat.setFormat(audio::format_int16_on_int32);
RIVER_INFO("auto set format: " << hardwareFormat.getFormat());
} else if (etk::isIn(audio::format_int24, m_info.nativeFormats) == true) {
hardwareFormat.setFormat(audio::format_int24);
RIVER_INFO("auto set format: " << hardwareFormat.getFormat());
} else if (m_info.nativeFormats.size() != 0) {
hardwareFormat.setFormat(m_info.nativeFormats[0]);
RIVER_INFO("auto set format: " << hardwareFormat.getFormat());
} else {
RIVER_CRITICAL("auto set format no element in the configuration: " << m_info.nativeFormats);
}
} else {
RIVER_ERROR("Can not manage input transforamtion: " << hardwareFormat.getFormat() << " not in " << m_info.nativeFormats);
hardwareFormat.setFormat(hardwareFormat.getFormat());
}
}
if (etk::isIn(hardwareFormat.getFrequency(), m_info.sampleRates) == false) {
if (etk::isIn(48000, m_info.sampleRates) == true) {
hardwareFormat.setFrequency(48000);
RIVER_INFO("auto set frequency: " << hardwareFormat.getFrequency());
} else if (etk::isIn(44100, m_info.sampleRates) == true) {
hardwareFormat.setFrequency(44100);
RIVER_INFO("auto set frequency: " << hardwareFormat.getFrequency());
} else if (etk::isIn(32000, m_info.sampleRates) == true) {
hardwareFormat.setFrequency(32000);
RIVER_INFO("auto set frequency: " << hardwareFormat.getFrequency());
} else if (etk::isIn(16000, m_info.sampleRates) == true) {
hardwareFormat.setFrequency(16000);
RIVER_INFO("auto set frequency: " << hardwareFormat.getFrequency());
} else if (etk::isIn(8000, m_info.sampleRates) == true) {
hardwareFormat.setFrequency(8000);
RIVER_INFO("auto set frequency: " << hardwareFormat.getFrequency());
} else if (etk::isIn(96000, m_info.sampleRates) == true) {
hardwareFormat.setFrequency(96000);
RIVER_INFO("auto set frequency: " << hardwareFormat.getFrequency());
} else if (m_info.sampleRates.size() != 0) {
hardwareFormat.setFrequency(m_info.sampleRates[0]);
RIVER_INFO("auto set frequency: " << hardwareFormat.getFrequency() << "(first element in list) in " << m_info.sampleRates);
} else {
RIVER_ERROR("Can not manage input transforamtion:" << hardwareFormat.getFrequency() << " not in " << m_info.sampleRates);
}
interfaceFormat.setFrequency(hardwareFormat.getFrequency());
}
}
// open Audio device:
audio::orchestra::StreamParameters params;
params.deviceId = deviceId;
params.deviceName = streamName;
params.nChannels = hardwareFormat.getMap().size();
if (m_info.channels.size() < params.nChannels) {
RIVER_ERROR("Can not open hardware device with more channel (" << params.nChannels << ") that is autorized by hardware (" << m_info.channels.size() << ").");
}
audio::orchestra::StreamOptions option;
etk::from_string(option.mode, tmpObject["timestamp-mode"].toString().get("soft"));
RIVER_DEBUG("interfaceFormat=" << interfaceFormat);
RIVER_DEBUG("hardwareFormat=" << hardwareFormat);
m_rtaudioFrameSize = nbChunk;
RIVER_INFO("Open output stream nbChannels=" << params.nChannels);
enum audio::orchestra::error err = audio::orchestra::error_none;
if (m_isInput == true) {
m_process.setInputConfig(hardwareFormat);
m_process.setOutputConfig(interfaceFormat);
err = m_interface.openStream(null,
&params,
hardwareFormat.getFormat(),
hardwareFormat.getFrequency(),
&m_rtaudioFrameSize,
[=] (const void* _inputBuffer,
const audio::Time& _timeInput,
void* _outputBuffer,
const audio::Time& _timeOutput,
uint32_t _nbChunk,
const etk::Vector<audio::orchestra::status>& _status) {
return recordCallback(_inputBuffer, _timeInput, _nbChunk, _status);
},
option
);
} else {
m_process.setInputConfig(interfaceFormat);
m_process.setOutputConfig(hardwareFormat);
err = m_interface.openStream(&params,
null,
hardwareFormat.getFormat(),
hardwareFormat.getFrequency(),
&m_rtaudioFrameSize,
[=] (const void* _inputBuffer,
const audio::Time& _timeInput,
void* _outputBuffer,
const audio::Time& _timeOutput,
uint32_t _nbChunk,
const etk::Vector<audio::orchestra::status>& _status) {
return playbackCallback(_outputBuffer, _timeOutput, _nbChunk, _status);
},
option
);
}
if (err != audio::orchestra::error_none) {
RIVER_ERROR("Create stream : '" << m_name << "' mode=" << (m_isInput?"input":"output") << " can not create stream " << err);
}
m_process.updateInterAlgo();
}
audio::river::io::NodeOrchestra::~NodeOrchestra() {
ethread::UniqueLock lock(m_mutex);
RIVER_INFO("close input stream");
if (m_interface.isStreamOpen() ) {
m_interface.closeStream();
}
};
void audio::river::io::NodeOrchestra::start() {
ethread::UniqueLock lock(m_mutex);
RIVER_INFO("Start stream : '" << m_name << "' mode=" << (m_isInput?"input":"output") );
enum audio::orchestra::error err = m_interface.startStream();
if (err != audio::orchestra::error_none) {
RIVER_ERROR("Start stream : '" << m_name << "' mode=" << (m_isInput?"input":"output") << " can not start stream ... " << err);
}
}
void audio::river::io::NodeOrchestra::stop() {
ethread::UniqueLock lock(m_mutex);
RIVER_INFO("Stop stream : '" << m_name << "' mode=" << (m_isInput?"input":"output") );
enum audio::orchestra::error err = m_interface.stopStream();
if (err != audio::orchestra::error_none) {
RIVER_ERROR("Stop stream : '" << m_name << "' mode=" << (m_isInput?"input":"output") << " can not stop stream ... " << err);
}
}
#endif

View File

@@ -0,0 +1,73 @@
/** @file
* @author Edouard DUPIN
* @copyright 2015, Edouard DUPIN, all right reserved
* @license MPL v2.0 (see license file)
*/
#pragma once
#ifdef AUDIO_RIVER_BUILD_ORCHESTRA
#include <audio/river/io/Node.hpp>
#include <audio/orchestra/Interface.hpp>
namespace audio {
namespace river {
namespace io {
class Manager;
class Group;
/**
* @brief Low level node that is manage on the interface with the extern lib airtaudio
*/
class NodeOrchestra : public audio::river::io::Node {
friend class audio::river::io::Group;
protected:
/**
* @brief Constructor
*/
NodeOrchestra(const etk::String& _name, const ejson::Object& _config);
public:
static ememory::SharedPtr<NodeOrchestra> create(const etk::String& _name, const ejson::Object& _config);
/**
* @brief Destructor
*/
virtual ~NodeOrchestra();
virtual bool isHarwareNode() {
return true;
};
protected:
audio::orchestra::Interface m_interface; //!< Real airtaudio interface
audio::orchestra::DeviceInfo m_info; //!< information on the stream.
unsigned int m_rtaudioFrameSize; // DEPRECATED soon...
public:
/**
* @brief Input Callback . Have recaive new data to process.
* @param[in] _inputBuffer Pointer on the data buffer.
* @param[in] _timeInput Time on the fist sample has been recorded.
* @param[in] _nbChunk Number of chunk in the buffer
* @param[in] _status DEPRECATED soon
* @return DEPRECATED soon
*/
int32_t recordCallback(const void* _inputBuffer,
const audio::Time& _timeInput,
uint32_t _nbChunk,
const etk::Vector<audio::orchestra::status>& _status);
/**
* @brief Playback callback. Request new data on output
* @param[in,out] _outputBuffer Pointer on the buffer to fill data.
* @param[in] _timeOutput Time on wich the data might be played.
* @param[in] _nbChunk Number of chunk in the buffer
* @param[in] _status DEPRECATED soon
* @return DEPRECATED soon
*/
int32_t playbackCallback(void* _outputBuffer,
const audio::Time& _timeOutput,
uint32_t _nbChunk,
const etk::Vector<audio::orchestra::status>& _status);
protected:
virtual void start();
virtual void stop();
};
}
}
}
#endif

View File

@@ -1,27 +1,16 @@
/** @file /** @file
* @author Edouard DUPIN * @author Edouard DUPIN
* @copyright 2015, Edouard DUPIN, all right reserved * @copyright 2015, Edouard DUPIN, all right reserved
* @license APACHE v2.0 (see license file) * @license MPL v2.0 (see license file)
*/ */
#ifdef __PORTAUDIO_INFERFACE__ #ifdef AUDIO_RIVER_BUILD_PORTAUDIO
#include <river/io/NodePortAudio.h> #include <audio/river/io/NodePortAudio.hpp>
#include <river/debug.h> #include <audio/river/debug.hpp>
#include <etk/memory.h> #include <ememory/memory.hpp>
#include <audio/Time.hpp>
#undef __class__ #include <audio/Duration.hpp>
#define __class__ "io::NodePortAudio"
static std::string asString(const std11::chrono::system_clock::time_point& tp) {
// convert to system time:
std::time_t t = std11::chrono::system_clock::to_time_t(tp);
// convert in human string
std::string ts = std::ctime(&t);
// remove \n
ts.resize(ts.size()-1);
return ts;
}
static int portAudioStreamCallback(const void *_input, static int portAudioStreamCallback(const void *_input,
void *_output, void *_output,
@@ -29,13 +18,13 @@ static int portAudioStreamCallback(const void *_input,
const PaStreamCallbackTimeInfo* _timeInfo, const PaStreamCallbackTimeInfo* _timeInfo,
PaStreamCallbackFlags _statusFlags, PaStreamCallbackFlags _statusFlags,
void *_userData) { void *_userData) {
river::io::NodePortAudio* myClass = reinterpret_cast<river::io::NodePortAudio*>(_userData); audio::river::io::NodePortAudio* myClass = reinterpret_cast<audio::river::io::NodePortAudio*>(_userData);
int64_t sec = int64_t(_timeInfo->inputBufferAdcTime); int64_t sec = int64_t(_timeInfo->inputBufferAdcTime);
int64_t nsec = (_timeInfo->inputBufferAdcTime-double(sec))*1000000000LL; int64_t nsec = (_timeInfo->inputBufferAdcTime-double(sec))*1000000000LL;
std11::chrono::system_clock::time_point timeInput = std11::chrono::system_clock::from_time_t(sec) + std11::chrono::nanoseconds(nsec); audio::Time timeInput(sec, nsec);
sec = int64_t(_timeInfo->outputBufferDacTime); sec = int64_t(_timeInfo->outputBufferDacTime);
nsec = (_timeInfo->outputBufferDacTime-double(sec))*1000000000LL; nsec = (_timeInfo->outputBufferDacTime-double(sec))*1000000000LL;
std11::chrono::system_clock::time_point timeOutput = std11::chrono::system_clock::from_time_t(sec) + std11::chrono::nanoseconds(nsec); audio::Time timeOutput(sec, nsec);
return myClass->duplexCallback(_input, return myClass->duplexCallback(_input,
timeInput, timeInput,
_output, _output,
@@ -44,19 +33,19 @@ static int portAudioStreamCallback(const void *_input,
_statusFlags); _statusFlags);
} }
int32_t river::io::NodePortAudio::duplexCallback(const void* _inputBuffer, int32_t audio::river::io::NodePortAudio::duplexCallback(const void* _inputBuffer,
const std11::chrono::system_clock::time_point& _timeInput, const audio::Time& _timeInput,
void* _outputBuffer, void* _outputBuffer,
const std11::chrono::system_clock::time_point& _timeOutput, const audio::Time& _timeOutput,
uint32_t _nbChunk, uint32_t _nbChunk,
PaStreamCallbackFlags _status) { PaStreamCallbackFlags _status) {
std11::unique_lock<std11::mutex> lock(m_mutex); ethread::UniqueLock lock(m_mutex);
// TODO : Manage status ... // TODO : Manage status ...
if (_inputBuffer != nullptr) { if (_inputBuffer != null) {
RIVER_VERBOSE("data Input size request :" << _nbChunk << " [BEGIN] status=" << _status << " nbIO=" << m_list.size()); RIVER_VERBOSE("data Input size request :" << _nbChunk << " [BEGIN] status=" << _status << " nbIO=" << m_list.size());
newInput(_inputBuffer, _nbChunk, _timeInput); newInput(_inputBuffer, _nbChunk, _timeInput);
} }
if (_outputBuffer != nullptr) { if (_outputBuffer != null) {
RIVER_VERBOSE("data Output size request :" << _nbChunk << " [BEGIN] status=" << _status << " nbIO=" << m_list.size()); RIVER_VERBOSE("data Output size request :" << _nbChunk << " [BEGIN] status=" << _status << " nbIO=" << m_list.size());
newOutput(_outputBuffer, _nbChunk, _timeOutput); newOutput(_outputBuffer, _nbChunk, _timeOutput);
} }
@@ -64,14 +53,14 @@ int32_t river::io::NodePortAudio::duplexCallback(const void* _inputBuffer,
} }
std11::shared_ptr<river::io::NodePortAudio> river::io::NodePortAudio::create(const std::string& _name, const std11::shared_ptr<const ejson::Object>& _config) { ememory::SharedPtr<audio::river::io::NodePortAudio> audio::river::io::NodePortAudio::create(const etk::String& _name, const ejson::Object& _config) {
return std11::shared_ptr<river::io::NodePortAudio>(new river::io::NodePortAudio(_name, _config)); return ememory::SharedPtr<audio::river::io::NodePortAudio>(ETK_NEW(audio::river::io::NodePortAudio,_name, _config));
} }
river::io::NodePortAudio::NodePortAudio(const std::string& _name, const std11::shared_ptr<const ejson::Object>& _config) : audio::river::io::NodePortAudio::NodePortAudio(const etk::String& _name, const ejson::Object& _config) :
Node(_name, _config) { Node(_name, _config) {
drain::IOFormatInterface interfaceFormat = getInterfaceFormat(); audio::drain::IOFormatInterface interfaceFormat = getInterfaceFormat();
drain::IOFormatInterface hardwareFormat = getHarwareFormat(); audio::drain::IOFormatInterface hardwareFormat = getHarwareFormat();
/** /**
map-on:{ # select hardware interface and name map-on:{ # select hardware interface and name
interface:"alsa", # interface : "alsa", "pulse", "core", ... interface:"alsa", # interface : "alsa", "pulse", "core", ...
@@ -79,17 +68,15 @@ river::io::NodePortAudio::NodePortAudio(const std::string& _name, const std11::s
}, },
nb-chunk:1024 # number of chunk to open device (create the latency anf the frequency to call user) nb-chunk:1024 # number of chunk to open device (create the latency anf the frequency to call user)
*/ */
enum airtaudio::type typeInterface = airtaudio::type_undefined; etk::String streamName = "default";
std::string streamName = "default"; const ejson::Object tmpObject = m_config["map-on"].toObject();
const std11::shared_ptr<const ejson::Object> tmpObject = m_config->getObject("map-on"); if (tmpObject.exist() == false) {
if (tmpObject == nullptr) {
RIVER_WARNING("missing node : 'map-on' ==> auto map : 'auto:default'"); RIVER_WARNING("missing node : 'map-on' ==> auto map : 'auto:default'");
} else { } else {
std::string value = tmpObject->getStringValue("interface", "default"); etk::String value = tmpObject.getStringValue("interface", "default");
typeInterface = airtaudio::getTypeFromString(value); streamName = tmpObject.getStringValue("name", "default");
streamName = tmpObject->getStringValue("name", "default");
} }
int32_t nbChunk = m_config->getNumberValue("nb-chunk", 1024); int32_t nbChunk = m_config.getNumberValue("nb-chunk", 1024);
PaError err = 0; PaError err = 0;
if (m_isInput == true) { if (m_isInput == true) {
@@ -117,8 +104,8 @@ river::io::NodePortAudio::NodePortAudio(const std::string& _name, const std11::s
m_process.updateInterAlgo(); m_process.updateInterAlgo();
} }
river::io::NodePortAudio::~NodePortAudio() { audio::river::io::NodePortAudio::~NodePortAudio() {
std11::unique_lock<std11::mutex> lock(m_mutex); ethread::UniqueLock lock(m_mutex);
RIVER_INFO("close input stream"); RIVER_INFO("close input stream");
PaError err = Pa_CloseStream( m_stream ); PaError err = Pa_CloseStream( m_stream );
if( err != paNoError ) { if( err != paNoError ) {
@@ -126,8 +113,8 @@ river::io::NodePortAudio::~NodePortAudio() {
} }
}; };
void river::io::NodePortAudio::start() { void audio::river::io::NodePortAudio::start() {
std11::unique_lock<std11::mutex> lock(m_mutex); ethread::UniqueLock lock(m_mutex);
RIVER_INFO("Start stream : '" << m_name << "' mode=" << (m_isInput?"input":"output") ); RIVER_INFO("Start stream : '" << m_name << "' mode=" << (m_isInput?"input":"output") );
PaError err = Pa_StartStream(m_stream); PaError err = Pa_StartStream(m_stream);
if( err != paNoError ) { if( err != paNoError ) {
@@ -135,8 +122,8 @@ void river::io::NodePortAudio::start() {
} }
} }
void river::io::NodePortAudio::stop() { void audio::river::io::NodePortAudio::stop() {
std11::unique_lock<std11::mutex> lock(m_mutex); ethread::UniqueLock lock(m_mutex);
RIVER_INFO("Stop stream : '" << m_name << "' mode=" << (m_isInput?"input":"output") ); RIVER_INFO("Stop stream : '" << m_name << "' mode=" << (m_isInput?"input":"output") );
PaError err = Pa_StopStream(m_stream); PaError err = Pa_StopStream(m_stream);
if( err != paNoError ) { if( err != paNoError ) {

View File

@@ -0,0 +1,51 @@
/** @file
* @author Edouard DUPIN
* @copyright 2015, Edouard DUPIN, all right reserved
* @license MPL v2.0 (see license file)
*/
#pragma once
#ifdef AUDIO_RIVER_BUILD_PORTAUDIO
#include <audio/river/Interface.hpp>
#include <audio/river/io/Node.hpp>
#include <portaudio/portaudio.hpp>
namespace audio {
namespace river {
namespace io {
class Manager;
//! @not_in_doc
class NodePortAudio : public Node {
protected:
/**
* @brief Constructor
*/
NodePortAudio(const etk::String& _name, const ejson::Object& _config);
public:
static ememory::SharedPtr<NodePortAudio> create(const etk::String& _name, const ejson::Object& _config);
/**
* @brief Destructor
*/
virtual ~NodePortAudio();
virtual bool isHarwareNode() {
return true;
};
protected:
PaStream* m_stream;
public:
int32_t duplexCallback(const void* _inputBuffer,
const audio::Time& _timeInput,
void* _outputBuffer,
const audio::Time& _timeOutput,
uint32_t _nbChunk,
PaStreamCallbackFlags _status);
protected:
virtual void start();
virtual void stop();
};
}
}
}
#endif

59
audio/river/river.cpp Normal file
View File

@@ -0,0 +1,59 @@
/** @file
* @author Edouard DUPIN
* @copyright 2015, Edouard DUPIN, all right reserved
* @license MPL v2.0 (see license file)
*/
#include <audio/river/river.hpp>
#include <audio/river/debug.hpp>
#include <audio/river/io/Manager.hpp>
static bool river_isInit = false;
static etk::String river_configFile = "";
void audio::river::init(const etk::String& _filename) {
if (river_isInit == false) {
river_isInit = true;
river_configFile = _filename;
RIVER_DEBUG("init RIVER :" << river_configFile);
ememory::SharedPtr<audio::river::io::Manager> mng = audio::river::io::Manager::getInstance();
if (mng != null) {
mng->init(river_configFile);
}
} else {
RIVER_ERROR("River is already init not use : " << _filename);
}
}
void audio::river::initString(const etk::String& _config) {
if (river_isInit == false) {
river_isInit = true;
river_configFile = _config;
RIVER_DEBUG("init RIVER with config.");
ememory::SharedPtr<audio::river::io::Manager> mng = audio::river::io::Manager::getInstance();
if (mng != null) {
mng->initString(river_configFile);
}
} else {
RIVER_ERROR("River is already init not use Data ...");
}
}
void audio::river::unInit() {
if (river_isInit == true) {
river_isInit = false;
RIVER_DEBUG("un-init RIVER.");
ememory::SharedPtr<audio::river::io::Manager> mng = audio::river::io::Manager::getInstance();
if (mng != null) {
RIVER_ERROR("Can not get on the RIVER hardware manager !!!");
mng->unInit();
}
}
}
bool audio::river::isInit() {
return river_isInit;
}

42
audio/river/river.hpp Normal file
View File

@@ -0,0 +1,42 @@
/** @file
* @author Edouard DUPIN
* @copyright 2015, Edouard DUPIN, all right reserved
* @license MPL v2.0 (see license file)
*/
#pragma once
#include <etk/types.hpp>
#include <etk/String.hpp>
/**
* @brief Audio library namespace
*/
namespace audio {
/**
* @brief Audio-river library namespace
*/
namespace river {
/**
* @brief Initialize the River Library
* @param[in] _filename Name of the configuration file (if "" ==> default config file)
*/
void init(const etk::String& _filename = "");
/**
* @brief Initialize the River Library with a json data string
* @param[in] _config json sting data
*/
void initString(const etk::String& _config);
/**
* @brief Un-initialize the River Library
* @note this close all stream of all interfaces.
* @note really good for test.
*/
void unInit();
/**
* @brief Get the status of initialisation
* @return true River is init
* @return false River is NOT init
*/
bool isInit();
}
}

1
authors.txt Normal file
View File

@@ -0,0 +1 @@
MR Edouard DUPIN <yui.heero@gmail.com>

141
data/hardwareNao.json Normal file
View File

@@ -0,0 +1,141 @@
{
speaker:{
io:"output",
map-on:{
interface:"alsa",
name:"hw:0,0",
#name:"default",
#name:"AD1989A_outputs",
timestamp-mode:"trigered",
},
#group:"baseIOSynchrone",
frequency:48000,
channel-map:[
"front-left", "front-right",
],
type:"int16",
nb-chunk:6000,
#volume-name:"MASTER",
mux-demux-type:"int16-on-int32",
},
microphone-virtual-alsa:{
io:"input",
map-on:{
interface:"alsa",
name:"AD1989A_inputs",
#name:"hw:0,0",
timestamp-mode:"trigered",
},
frequency:48000,
channel-map:[
"front-left", "front-right"
, "rear-left", "rear-right"
],
type:"int16",
nb-chunk:6000,
mux-demux-type:"int16",
},
speakerGroup:{
io:"output",
map-on:{
interface:"alsa",
name:"hw:0,0",
timestamp-mode:"trigered",
},
group:"baseIOSynchrone",
frequency:48000,
channel-map:[
"front-left", "front-right",
],
type:"int16",
nb-chunk:1024,
volume-name:"MASTER",
mux-demux-type:"int16-on-int32",
},
microphone-front:{
io:"input",
map-on:{
interface:"alsa",
name:"hw:0,0,1",
#name:"AD1989A_inputs",
#name:"default",
timestamp-mode:"trigered",
},
#group:"baseIOSynchrone",
frequency:48000,
channel-map:[
"front-left", "front-right"
],
type:"int16",
nb-chunk:128,
mux-demux-type:"int16",
},
microphone-rear:{
io:"input",
map-on:{
interface:"alsa",
name:"hw:0,0,0",
timestamp-mode:"trigered",
},
#group:"baseIOSynchrone",
frequency:48000,
channel-map:[
"rear-left", "rear-right"
],
type:"int16",
nb-chunk:1024,
mux-demux-type:"int16",
},
# virtual Nodes :
microphone-clean:{
io:"aec",
# connect in input mode
map-on-microphone:{
# generic virtual definition
io:"input",
map-on:"microphone-muxed",
resampling-type:"speexdsp",
resampling-option:"quality=10"
},
# connect in feedback mode
map-on-feedback:{
io:"feedback",
map-on:"speaker",
resampling-type:"speexdsp",
resampling-option:"quality=10",
},
#classical format configuration:
frequency:16000,
channel-map:[
"front-left", "front-right", "rear-left", "rear-right"
],
type:"int16",
# AEC algo definition
algo:"river-remover",
algo-mode:"cutter",
feedback-delay:10000, # in nanosecond
mux-demux-type:"int16",
},
microphone-muxed:{
io:"muxer",
map-on-input-1:{
# generic virtual definition
io:"input",
map-on:"microphone-front",
resampling-type:"speexdsp",
resampling-option:"quality=10"
},
map-on-input-2:{
io:"input",
map-on:"microphone-rear",
resampling-type:"speexdsp",
resampling-option:"quality=10",
},
frequency:48000,
channel-map:[
"front-left", "front-right", "rear-left", "rear-right"
],
type:"int16",
mux-demux-type:"int16",
},
}

View File

@@ -1,38 +0,0 @@
{
# name of the virtual interface
microphone:{
# input or output
io:"input",
# name of the harware device
map-on:"microphone",
# name of the resampler
resampling-type:"speexdsp",
# some option to the resampler
resampling-option:"quality=10"
},
speaker:{
io:"output",
map-on:"speaker",
resampling-type:"speexdsp",
resampling-option:"quality=10"
},
feedback:{
# note : Feedback is plugged on an output not an input
io:"feedback",
map-on:"speaker",
resampling-type:"speexdsp",
resampling-option:"quality=10"
},
microphone-clean:{
io:"input",
map-on:"microphone-clean",
resampling-type:"speexdsp",
resampling-option:"quality=10"
},
microphone-muxed:{
io:"input",
map-on:"microphone-muxed",
resampling-type:"speexdsp",
resampling-option:"quality=10"
},
}

100
doc/build.md Normal file
View File

@@ -0,0 +1,100 @@
Build lib & build sample {#audio_river_build}
========================
@tableofcontents
Download: {#audio_river_build_download}
=========
ege use some tools to manage source and build it:
need google repo: {#audio_river_build_download_repo}
-----------------
see: http://source.android.com/source/downloading.html#installing-repo
On all platform:
```{.sh}
mkdir ~/.bin
PATH=~/.bin:$PATH
curl https://storage.googleapis.com/git-repo-downloads/repo > ~/.bin/repo
chmod a+x ~/.bin/repo
```
On ubuntu
```{.sh}
sudo apt-get install repo
```
On archlinux
```{.sh}
sudo pacman -S repo
```
lutin (build-system): {#audio_river_build_download_lutin}
---------------------
```{.sh}
pip install lutin --user
# optionnal dependency of lutin (manage image changing size for application release)
pip install pillow --user
```
dependency: {#audio_river_build_download_dependency}
-----------
```{.sh}
mkdir -p WORKING_DIRECTORY/framework
cd WORKING_DIRECTORY/framework
repo init -u git://github.com/atria-soft/manifest.git
repo sync -j8
cd ../..
```
sources: {#audio_river_build_download_sources}
--------
They are already download in the repo manifest in:
```{.sh}
cd WORKING_DIRECTORY/framework/musicdsp/audio-river
```
Build: {#audio_river_build_build}
======
you must stay in zour working directory...
```{.sh}
cd WORKING_DIRECTORY
```
library: {#audio_river_build_build_library}
--------
```{.sh}
lutin -mdebug audio-river
```
Sample: {#audio_river_build_build_sample}
-------
```{.sh}
lutin -mdebug audio-river-sample-read?run
lutin -mdebug audio-river-sample-write?run
```
A fast way:
```{.sh}
lutin -mdebug audio-river-*
```
Run sample: {#audio_river_build_run_sample}
===========
in distinct bash:
```{.sh}
lutin -mdebug audio-river-sample-read?run
lutin -mdebug audio-river-sample-write?run
```

119
doc/configFile.md Normal file
View File

@@ -0,0 +1,119 @@
River configuration file {#audio_river_config_file}
========================
@tableofcontents
Objectifs: {#audio_river_config_file_objectif}
==========
- Understand the architecture of the configuration file.
- all that can be done with it.
Basis: {#audio_river_config_file_bases}
======
The river configuration file is a json file. We use @ref ejson_mainpage_what to parse it then we have some writing facilities.
River provide a list a harware interface and virtual interface.
The hardware interface are provided by @ref audio_orchestra_mainpage_what then we will plug on every platform.
The file is simply architecture around a list of object:
```{.json}
{
"speaker":{
},
"microphone":{
},
"mixed-in-out":{
},
}
```
With this config we declare 3 interfaces : speaker, microphone and mixed-in-out.
Harware configuration: {#audio_river_config_file_hw_config}
======================
In every interface we need to define some Element:
- "io" : Can be input/output/... depending of virtual interface...
- "map-on": An object to configure airtaudio interface.
- "frequency": 0 to automatic select one. Or the frequency to open harware device
- "channel-map": List of all channel in the stream:
* "front-left"
* "front-center"
* "front-right"
* "rear-left"
* "rear-center"
* "rear-right"
* "surround-left",
* "surround-right",
* "sub-woofer",
* "lfe"
- "type": Fomat to open the stream:
* "auto": Detect the best type
* "int8",
* "int8-on-int16",
* "int16",
* "int16-on-int32",
* "int24",
* "int32",
* "int32-on-int64",
* "int64",
* "float",
* "double"
- "nb-chunk": Number of chunk to open the stream.
Generic configuration file use
==============================
You can specify a generic configuration file foir a user in the path ```~/.local/share/audio-river/config.json```
The default config file is:
```{.json}
{
microphone:{
io:"input",
map-on:{
interface:"auto",
name:"default",
},
frequency:0,
channel-map:[
"front-left", "front-right"
],
type:"auto",
nb-chunk:1024,
mux-demux-type:"float"
},
speaker:{
io:"output",
map-on:{
interface:"alsa",
name:"default",
},
frequency:0,
channel-map:[
"front-left", "front-right",
],
type:"auto",
nb-chunk:1024,
volume-name:"MASTER",
mux-demux-type:"float"
}
}
```
If the pplication start with no name it try to load this file and if it fail it load the internalversion of a basic file

30
doc/feedback.md Normal file
View File

@@ -0,0 +1,30 @@
Read stream feedback {#audio_river_feedback}
====================
@tableofcontents
Objectifs: {#audio_river_feedback_objectif}
==========
- Implement a feedback.
Bases: {#audio_river_feedback_base}
======
A feedback is a stream that is generated by an output.
To get a feedback this is the same implementation of an input and link it on an output.
What change:
```{.cpp}
//Get the generic feedback on speaker:
interface = manager->createFeedback(48000,
etk::Vector<audio::channel>(),
audio::format_int16,
"speaker");
```
**Note:** Input interface does not provide feedback.

92
doc/mainpage.md Normal file
View File

@@ -0,0 +1,92 @@
AUDIO-RIVER library {#mainpage}
===================
@tableofcontents
What is AUDIO-RIVER: {#audio_river_mainpage_what}
====================
AUDIO-RIVER, is a multi-platform library to manage the input and output audio flow.
It can be compared with PulseAudio or Jack, but at the difference at the 2 interfaces
it is designed to be multi-platform and is based on licence that permit to integrate it
on every program we want.
What it does: {#audio_river_mainpage_what_it_does}
=============
Everywhere! RIVER is cross-platform devolopped to support bases OS:
: ** Linux (over Alsa, Pulseaudio, JackD)
: ** Windows (over ASIO)
: ** MacOs (over CoreAudio)
: ** Android (Over Ewol wrapper little complicated need to be change later)
: ** IOs (over CoreAudio for ios)
AUDIO-RIVER is dependent of the STL (compatible with MacOs stl (CXX))
Architecture:
-------------
River has been designed to replace the pulseAudio basic asyncronous interface that create
more problem that it will solve. The second point is that is not enougth portable to be
embended in a proprietary software without distributing all the sources (Ios).
Start at this point we will have simple objectives :
- Manage multiple Low level interface: @ref audio_orchestra_mainpage_what
* for linux (Alsa, Pulse, Oss)
* for Mac-OsX (CoreAudio)
* for IOs (coreAudio (embended version))
* for Windows (ASIO)
* For Android (Java (JDK...))
- Synchronous interface ==> no delay and reduce latency
- Manage the thread priority (need sometimes to be more reactive)
- manage mixing of some flow (2 inputs stereo and the user want 1 input quad)
- AEC Acoustic Echo Cancelation (TODO : in the current implementation we have a simple sound cutter)
- Equalizer (done with @ref audio_drain_mainpage_what)
- Resmpling (done by the libspeexDSP)
- Correct volume management (and configurable)
- Fade-in and Fade-out @ref audio_drain_mainpage_what
- Channel reorganisation @ref audio_drain_mainpage_what
- A correct feedback interface
What languages are supported? {#audio_river_mainpage_language}
=============================
AUDIO-RIVER is written in C++.
Are there any licensing restrictions? {#audio_river_mainpage_license_restriction}
=====================================
AUDIO-RIVER is **FREE software** and _all sub-library are FREE and staticly linkable !!!_
License (MPL v2.0) {#audio_river_mainpage_license}
==================
Copyright AUDIO-RIVER Edouard DUPIN
Licensed under the Mozilla Public 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
<https://www.mozilla.org/MPL/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.
Other pages {#audio_river_mainpage_sub_page}
===========
- @ref audio_river_build
- @ref audio_river_read
- @ref audio_river_write
- @ref audio_river_feedback
- @ref audio_river_config_file
- [**ewol coding style**](http://atria-soft.github.io/ewol/ewol_coding_style.html)

115
doc/read.md Normal file
View File

@@ -0,0 +1,115 @@
Read stream form Audio input {#audio_river_read}
============================
@tableofcontents
Objectifs: {#audio_river_read_objectif}
==========
- Understand basis of river
- Create a simple recording interface that print the average of sample absolute value.
When you will create an application based on the river audio interface you need :
Include: {#audio_river_read_include}
========
Include manager and interface node
@snippet read.cpp audio_river_sample_include
Initilize the River library: {#audio_river_read_init}
============================
We first need to initialize etk sub library (needed to select the log level of sub-libraries and file access abstraction
@snippet read.cpp audio_river_sample_init
Now we will initilaize the river library.
To do this We have 2 posibilities:
With a file:
------------
```{.cpp}
// initialize river interface
river::init("DATA:///configFileName.json");
```
With a json string:
-------------------
@snippet read.cpp audio_river_sample_read_config_file
```{.cpp}
// initialize river interface
river::initString(configurationRiver);
```
For the example we select the second solution (faster to implement example and resource at the same position.
river::init / river::initString must be called only one time for all the application, this represent the hardware configuration.
It is NOT dynamic
To understand the configuration file Please see @ref audio_river_config_file
This json is parsed by the @ref {#ejson_mainpage_what} it contain some update like:
- Optionnal " in the name of element.
- The possibilities to remplace " with '.
Get the river interface manager: {#audio_river_read_river_interface}
================================
An application can have many interface and only one Manager. And a process can contain many application.
Then, we will get the first application manager handle.
@snippet read.cpp audio_river_sample_get_interface
*Note:* You can get back the application handle when you create a new one with the same name.
Create your read interface: {#audio_river_read_river_read_interface}
===========================
Generic code:
@snippet read.cpp audio_river_sample_create_read_interface
Here we create an interface with:
- The frequency of 48000 Hz.
- The default Low level definition channel
- A data interface of 16 bits samples coded in [-32768..32767]
- Select input interaface name "microphone"
set data callback: {#audio_river_read_get_data}
==================
The best way to get data is to instanciate a simple callback.
The callback is called when sample arrive and you have the nbChunk/frequency
to process the data, otherwise you can generate error in data stream.
@snippet read.cpp audio_river_sample_set_callback
Callback inplementation: {#audio_river_read_callback}
========================
Simply declare your function and do what you want inside.
@snippet read.cpp audio_river_sample_callback_implement
start and stop the stream: {#audio_river_read_start_stop}
==========================
@snippet read.cpp audio_river_sample_read_start_stop
Remove interfaces: {#audio_river_read_reset}
==================
@snippet read.cpp audio_river_sample_read_reset
Full Sample: {#audio_river_read_full_sample}
============
@snippet read.cpp audio_river_sample_read_all

53
doc/write.md Normal file
View File

@@ -0,0 +1,53 @@
Write stream to Audio output {#audio_river_write}
============================
@tableofcontents
Objectifs: {#audio_river_write_objectif}
==========
- Understand write audio stream
The writing work nearly like the read turoral. Then we will just see what has change.
File configuration: {#audio_river_write_config}
===================
@snippet write.cpp audio_river_sample_write_config_file
Create your write interface: {#audio_river_write_interface}
============================
Generic code:
@snippet write.cpp audio_river_sample_create_write_interface
Here we create an interface with:
- The frequency of 48000 Hz.
- The default Low level definition channel
- A data interface of 16 bits samples coded in [-32768..32767]
- Select input interaface name "speaker"
set data callback: {#audio_river_write_get_data}
==================
The best way to get data is to instanciate a simple callback.
The callback is called when sample are needed and you have the nbChunk/frequency
to generate the data, otherwise you can generate error in data stream.
@snippet write.cpp audio_river_sample_set_callback
Callback inplementation: {#audio_river_write_callback}
========================
Simply declare your function and do what you want inside.
@snippet write.cpp audio_river_sample_callback_implement
Full Sample: {#audio_river_write_full_sample}
============
@snippet write.cpp audio_river_sample_write_all

37
doxy_audio-river.py Normal file
View File

@@ -0,0 +1,37 @@
#!/usr/bin/python
import os
import doxy.module as module
import doxy.debug as debug
import doxy.tools as tools
def create(target, module_name):
my_module = module.Module(__file__, module_name)
my_module.set_version("version.txt")
my_module.set_title("audio-river: Multi-nodal audio interface")
my_module.set_website("http://musicdsp.github.io/" + module_name)
my_module.set_website_sources("http://github.com/musicdsp/" + module_name)
my_module.add_path([
"audio",
"doc"
])
my_module.add_sample_path([
"sample",
])
my_module.add_depend([
'audio',
'audio-drain',
'audio-orchestra',
'ejson'
])
my_module.add_exclude_symbols([
'*operator<<*',
])
my_module.add_exclude_file([
'debug.h',
])
my_module.add_file_patterns([
'*.h',
'*.md',
])
return my_module

View File

@@ -1,41 +0,0 @@
#!/usr/bin/python
import lutinModule as module
import lutinTools as tools
import lutinDebug as debug
def get_desc():
return "river : Multi-nodal audio interface"
def create(target):
myModule = module.Module(__file__, 'river', 'LIBRARY')
myModule.add_src_file([
'river/debug.cpp',
'river/Manager.cpp',
'river/Interface.cpp',
'river/CircularBuffer.cpp',
'river/io/Group.cpp',
'river/io/Node.cpp',
'river/io/NodeAirTAudio.cpp',
'river/io/NodePortAudio.cpp',
'river/io/NodeAEC.cpp',
'river/io/NodeMuxer.cpp',
'river/io/Manager.cpp'
])
myModule.add_optionnal_module_depend('airtaudio', "__AIRTAUDIO_INFERFACE__")
myModule.add_optionnal_module_depend('portaudio', "__PORTAUDIO_INFERFACE__")
myModule.add_module_depend(['audio', 'drain', 'ejson'])
myModule.add_export_path(tools.get_current_path(__file__))
# add the currrent module at the
return myModule

View File

@@ -1,44 +0,0 @@
#!/usr/bin/python
import lutinModule as module
import lutinTools as tools
import lutinDebug as debug
def get_desc():
return "river_test : Multi-nodal audio interface test"
def create(target):
myModule = module.Module(__file__, 'river_test', 'BINARY')
myModule.add_src_file([
'test/main.cpp',
'test/debug.cpp'
])
if target.name=="Windows":
myModule.copy_file('data/hardwareWindows.json', 'hardware.json')
elif target.name=="Linux":
myModule.copy_file('data/hardwareLinux.json', 'hardware.json')
elif target.name=="MacOs":
myModule.copy_file('data/hardwareMacOs.json', 'hardware.json')
elif target.name=="IOs":
myModule.copy_file('data/hardwareIOs.json', 'hardware.json')
elif target.name=="Android":
myModule.copy_file('data/hardwareAndroid.json', 'hardware.json')
else:
debug.warning("unknow target for AIRTAudio : " + target.name);
myModule.copy_file('data/virtual.json', 'virtual.json')
myModule.add_module_depend(['river', 'gtest', 'etk'])
# add the currrent module at the
return myModule

View File

@@ -1,279 +0,0 @@
/** @file
* @author Edouard DUPIN
* @copyright 2011, Edouard DUPIN, all right reserved
* @license APACHE v2.0 (see license file)
*/
#include <river/CircularBuffer.h>
#include <river/debug.h>
river::CircularBuffer::CircularBuffer(const river::CircularBuffer& _obj) :
m_data(),
m_write(nullptr),
m_read(nullptr),
m_timeRead(),
m_capacity(0),
m_sizeChunk(0),
m_size(0) {
RIVER_CRITICAL("error");
};
/**
* @brief copy operator.
*/
river::CircularBuffer& river::CircularBuffer::operator=(const river::CircularBuffer& _obj) {
RIVER_CRITICAL("error");
return *this;
};
river::CircularBuffer::CircularBuffer() :
m_data(),
m_write(nullptr),
m_read(nullptr),
m_timeRead(),
m_capacity(0),
m_sizeChunk(0),
m_size(0) {
// nothing to do ...
}
river::CircularBuffer::~CircularBuffer() {
m_data.clear();
m_read = nullptr;
m_write = nullptr;
}
void river::CircularBuffer::setCapacity(size_t _capacity, size_t _chunkSize, uint32_t _frequency) {
if ( _chunkSize == m_sizeChunk
&& _capacity == m_capacity) {
clear();
return;
}
RIVER_DEBUG("buffer setCapacity(" << _capacity << "," << _chunkSize << ")");
m_data.clear();
m_write = nullptr;
m_read = nullptr;
m_frequency = _frequency;
m_capacity = _capacity;
m_sizeChunk = _chunkSize;
m_size = 0;
if ( _capacity == 0
|| _chunkSize == 0) {
m_capacity = 0;
m_sizeChunk = 0;
return;
}
m_data.resize(m_capacity*m_sizeChunk, 0);
m_read = &m_data[0];
m_write = &m_data[0];
}
void river::CircularBuffer::setCapacity(std11::chrono::milliseconds _capacity, size_t _chunkSize, uint32_t _frequency) {
uint32_t nbSampleNeeded = _frequency*_capacity.count()/1000;
RIVER_DEBUG("buffer setCapacity(" << _capacity.count() << "ms ," << _chunkSize << ")");
setCapacity(nbSampleNeeded, _chunkSize, _frequency);
}
size_t river::CircularBuffer::getUsedSizeBeforEnd() const {
size_t size;
if (m_read < m_write) {
size = static_cast<uint8_t*>(m_write) - static_cast<uint8_t*>(m_read);
// the size result is in bytes we need to have it in element
size /= m_sizeChunk;
} else if ( m_read == m_write
&& m_size == 0) {
// no element in the buffer
size = 0;
} else {
size = &m_data[0] + (m_capacity*m_sizeChunk) - static_cast<uint8_t*>(m_read);
// the size result is in bytes we need to have it in element
size /= m_sizeChunk;
}
return size;
}
size_t river::CircularBuffer::getFreeSizeBeforEnd() const {
size_t size;
size = &m_data[0]
+ (m_capacity*m_sizeChunk)
- static_cast<uint8_t*>(m_write);
// the size result is in Octet we need to have it in element
size /= m_sizeChunk;
return size;
}
size_t river::CircularBuffer::write(const void* _data, size_t _nbChunk, const std11::chrono::system_clock::time_point& _time) {
size_t nbElementDrop = 0;
size_t freeSizeBeforeEnd = getFreeSizeBeforEnd();
size_t freeSize = m_capacity - m_size;
// Write element in all case
// calculate the number of element that are overwritten
if (freeSize < _nbChunk) {
nbElementDrop = _nbChunk - freeSize;
}
// if User Request a write more important than the size of the buffer ==> update the pointer to feet only on the buffer size
if (m_capacity < _nbChunk) {
RIVER_WARNING("CircularBuffer Write too BIG " << _nbChunk << " buffer max size : " << m_capacity << " (keep last Elements)");
// Move data pointer
_data = static_cast<const uint8_t*>(_data) + (_nbChunk - m_capacity) * m_sizeChunk;
// update size
_nbChunk = m_capacity;
}
// if no element in the FIFO ==> first time write or no more data inside ==> start set the file of the read data ...
if (m_size == 0) {
m_timeRead = _time;
}
// TODO : Check time to push continuous data ...
if (freeSizeBeforeEnd >= _nbChunk) {
// all Data will be copy
memcpy(m_write, _data, _nbChunk * m_sizeChunk);
// update Writing pointer
if (freeSizeBeforeEnd == _nbChunk) {
// update to the end of FIFO ==> update to the start
m_write = &m_data[0];
} else {
m_write = static_cast<uint8_t*>(m_write) + _nbChunk * m_sizeChunk;
}
// Update the number of element in the buffer.
m_size += _nbChunk;
} else {
// copy data to the end of buffer
memcpy(m_write, _data, freeSizeBeforeEnd * m_sizeChunk);
// update Writing pointer ==> end of buffer ==> go to the start
m_write = &m_data[0];
// update data pointer
_data = static_cast<const uint8_t*>(_data) + freeSizeBeforeEnd * m_sizeChunk;
m_size += freeSizeBeforeEnd;
// get the number of element we need to write
_nbChunk -= freeSizeBeforeEnd;
// Copy the las data if needed
if (_nbChunk != 0) {
memcpy(m_write, _data, _nbChunk * m_sizeChunk);
// update Writing pointer
m_write = static_cast<uint8_t*>(m_write) + _nbChunk * m_sizeChunk;
m_size += _nbChunk;
}
}
if (nbElementDrop > 0) {
// if drop element we need to update the reading pointer
m_read = m_write;
m_size = m_capacity;
}
// return the number of element Overwrite
return nbElementDrop;
}
size_t river::CircularBuffer::read(void* _data, size_t _nbChunk) {
return read(_data, _nbChunk, m_timeRead);
}
size_t river::CircularBuffer::read(void* _data, size_t _nbChunk, const std11::chrono::system_clock::time_point& _time) {
size_t nbElementDrop = 0;
// Critical section (theoriquely protected by Mutex)
size_t usedSizeBeforeEnd = getUsedSizeBeforEnd();
// verify if we have elements in the Buffer
if (0 < m_size) {
// check the time of the read :
std11::chrono::nanoseconds deltaTime = m_timeRead - _time;
if (deltaTime.count() == 0) {
// nothing to do ==> just copy data ...
} else if (deltaTime.count() > 0) {
// Add empty sample in the output buffer ...
size_t nbSampleEmpty = m_frequency*deltaTime.count()/100000000;
nbSampleEmpty = std::min(nbSampleEmpty, _nbChunk);
RIVER_WARNING("add Empty sample in the output buffer " << nbSampleEmpty << " / " << _nbChunk);
memset(_data, 0, nbSampleEmpty * m_sizeChunk);
if (nbSampleEmpty == _nbChunk) {
return 0;
}
_nbChunk -= nbSampleEmpty;
} else {
// Remove data from the FIFO
setReadPosition(_time);
}
if (m_size < _nbChunk) {
nbElementDrop = _nbChunk - m_size;
_nbChunk = m_size;
}
m_timeRead += std11::chrono::microseconds(_nbChunk*1000000/m_frequency);
if (usedSizeBeforeEnd >= _nbChunk) {
// all Data will be copy
memcpy(_data, m_read, _nbChunk * m_sizeChunk);
// update Writing pointer
m_read = static_cast<uint8_t*>(m_read) + _nbChunk * m_sizeChunk;
m_size -= _nbChunk;
// update output pointer in case of flush with 0 data
_data = static_cast<uint8_t*>(_data) + _nbChunk * m_sizeChunk;
} else {
// copy data to the end of buffer
memcpy(_data, m_read, usedSizeBeforeEnd * m_sizeChunk);
// update Writing pointer ==> end of buffer ==> go to the start
m_read = &m_data[0];
_data = static_cast<uint8_t*>(_data) + usedSizeBeforeEnd * m_sizeChunk;
m_size -= usedSizeBeforeEnd;
// get the number of element we need to write
_nbChunk -= usedSizeBeforeEnd;
// Copy the last data if needed
if (0 != _nbChunk) {
memcpy(_data, m_read, _nbChunk * m_sizeChunk);
// update Writing pointer
m_read = static_cast<uint8_t*>(m_read) + _nbChunk * m_sizeChunk;
m_size -= _nbChunk;
// update output pointer in case of flush with 0 data
_data = static_cast<uint8_t*>(_data) + _nbChunk * m_sizeChunk;
}
}
} else {
nbElementDrop = _nbChunk;
}
if (0 < nbElementDrop) {
// set 0 in last element of the output
memset(_data, 0, m_sizeChunk * nbElementDrop);
}
// return the number of element droped
return nbElementDrop;
}
void river::CircularBuffer::setReadPosition(const std11::chrono::system_clock::time_point& _time) {
// Critical section (theoriquely protected by Mutex)
size_t usedSizeBeforeEnd = getUsedSizeBeforEnd();
if (0 < m_size) {
// check the time of the read :
std11::chrono::nanoseconds deltaTime = _time - m_timeRead;
size_t nbSampleToRemove = int64_t(m_frequency)*int64_t(deltaTime.count())/1000000000LL;
nbSampleToRemove = std::min(nbSampleToRemove, m_size);
RIVER_VERBOSE("Remove sample in the buffer " << nbSampleToRemove << " / " << m_size);
std11::chrono::nanoseconds updateTime((int64_t(nbSampleToRemove)*1000000000LL)/int64_t(m_frequency));
RIVER_VERBOSE(" add time : " << updateTime.count() << "ns / " << deltaTime.count() << "ns");
if (usedSizeBeforeEnd >= nbSampleToRemove) {
usedSizeBeforeEnd -= nbSampleToRemove;
m_size -= nbSampleToRemove;
m_read = static_cast<uint8_t*>(m_read) + nbSampleToRemove * m_sizeChunk;
} else {
nbSampleToRemove -= usedSizeBeforeEnd;
m_size -= nbSampleToRemove;
m_read = &m_data[0] + nbSampleToRemove*m_sizeChunk;
}
m_timeRead += updateTime;
//m_timeRead += deltaTime;
} else {
m_timeRead = std11::chrono::system_clock::time_point();
}
}
size_t river::CircularBuffer::getFreeSize() const {
return m_capacity - m_size;
}
void river::CircularBuffer::clear() {
RIVER_DEBUG("buffer clear()");
// set pointer to the start
m_read = &m_data[0];
m_write = &m_data[0];
// Clean the number of element in the buffer
m_size = 0;
// Clean all element inside :
memset(&m_data[0], 0, m_sizeChunk * m_capacity);
}

View File

@@ -1,139 +0,0 @@
/** @file
* @author Edouard DUPIN
* @copyright 2011, Edouard DUPIN, all right reserved
* @license APACHE v2.0 (see license file)
*/
#ifndef __RIVER_CIRCULAR_BUFFER_H__
#define __RIVER_CIRCULAR_BUFFER_H__
#include <etk/types.h>
#include <vector>
#include <etk/chrono.h>
namespace river {
/**
* For these functions we have 4 solutions :
* - Free Buffer
* ----------------------------------------------------------
* m_data | | |
* ----------------------------------------------------------
* m_write
* m_read
* - Full Buffer
* ----------------------------------------------------------
* m_data |****************************|***************************|
* ----------------------------------------------------------
* m_write
* m_read
* - Buffer in used
* ----------------------------------------------------------
* m_data | |********************| |
* ----------------------------------------------------------
* m_read m_write
* - Buffer out used
* ----------------------------------------------------------
* m_data |****************| |******************|
* ----------------------------------------------------------
* m_read m_write
*/
class CircularBuffer {
private:
std::vector<uint8_t> m_data; //!< data pointer
void* m_write; //!< write pointer
void* m_read; //!< read pointer
std11::chrono::system_clock::time_point m_timeRead; //!< current read time
uint32_t m_frequency;
// TODO : Remove the m_size ==> this is a bad element to be mutex-less
size_t m_size; //!< number of chunk availlable in this buffer
size_t m_capacity; //!< number of chunk available in this Buffer
size_t m_sizeChunk; //!< Size of one chunk (in byte)
public:
CircularBuffer();
~CircularBuffer();
/**
* @brief copy contructor.
* @param[in] _obj Circular buffer object
*/
CircularBuffer(const river::CircularBuffer& _obj);
/**
* @brief copy operator.
* @param[in] _obj Circular buffer object
*/
CircularBuffer& operator=(const river::CircularBuffer& _obj);
/**
* @brief set the capacity of the circular buffer.
* @param[in] _capacity Number of chunk in the buffer.
* @param[in] _chunkSize Size of one chunk.
* @param[in] _frequency Frequency of the buffer
*/
void setCapacity(size_t _capacity, size_t _chunkSize, uint32_t _frequency);
/**
* @brief set the capacity of the circular buffer.
* @param[in] _capacity time in millisecond stored in the buffer.
* @param[in] _chunkSize Size of one chunk.
* @param[in] _frequency Frequency of the buffer
*/
void setCapacity(std11::chrono::milliseconds _capacity, size_t _chunkSize, uint32_t _frequency);
/**
* @brief get free size of the buffer.
* @return Number of free chunk.
*/
size_t getFreeSize() const;
/**
* @brief Get number of chunk in the buffer.
* @return number of chunk.
*/
size_t getSize() const {
return m_size;
}
/**
* @brief Get number of chunk that can be set in the buffer.
* @return number of chunk.
*/
size_t getCapacity() const {
return m_capacity;
}
/**
* @brief Write chunk in the buffer.
* @param[in] _data Pointer on the data.
* @param[in] _nbChunk number of chunk to copy.
* @param[in] _time Time to start write data (if before end ==> not replace data, write only if after end)
* @return Number of chunk copied.
*/
size_t write(const void* _data, size_t _nbChunk, const std11::chrono::system_clock::time_point& _time);
/**
* @brief Read Chunk from the buffer to the pointer data.
* @param[out] _data Pointer on the data.
* @param[in] _nbChunk number of chunk to copy.
* @param[in] _time Time to start read data (if before start ==> add 0 at start, if after, remove unread data)
* @return Number of chunk copied.
*/
size_t read(void* _data, size_t _nbChunk, const std11::chrono::system_clock::time_point& _time);
//! @previous
size_t read(void* _data, size_t _nbChunk);
void setReadPosition(const std11::chrono::system_clock::time_point& _time);
std11::chrono::system_clock::time_point getReadTimeStamp() {
return m_timeRead;
}
/**
* @brief Clear the buffer.
*/
void clear();
private:
/**
* @brief Get number of free chunks before end of buffer.
* @return Number of chunk.
*/
size_t getFreeSizeBeforEnd() const;
/**
* @brief Get number of used chunks before end of buffer.
* @return Number of chunk.
*/
size_t getUsedSizeBeforEnd() const;
};
}
#endif

View File

@@ -1,424 +0,0 @@
/** @file
* @author Edouard DUPIN
* @copyright 2015, Edouard DUPIN, all right reserved
* @license APACHE v2.0 (see license file)
*/
#include "debug.h"
#include "Interface.h"
#include "io/Node.h"
#include <drain/EndPointCallback.h>
#include <drain/EndPointWrite.h>
#include <drain/EndPointRead.h>
#include <drain/Volume.h>
#undef __class__
#define __class__ "Interface"
river::Interface::Interface(void) :
m_node(),
m_name(""),
m_volume(0.0f) {
static uint32_t uid = 0;
m_uid = uid++;
}
bool river::Interface::init(const std::string& _name,
float _freq,
const std::vector<audio::channel>& _map,
audio::format _format,
const std11::shared_ptr<river::io::Node>& _node,
const std11::shared_ptr<const ejson::Object>& _config) {
std::vector<audio::channel> map(_map);
m_name = _name;
m_node = _node;
m_volume = 0.0f;
m_config = _config;
m_mode = river::modeInterface_unknow;
std::string type = m_config->getStringValue("io", "error");
if (type == "output") {
m_mode = river::modeInterface_output;
} else if (type == "input") {
m_mode = river::modeInterface_input;
} else if (type == "feedback") {
m_mode = river::modeInterface_feedback;
}
// register interface to be notify from the volume change.
m_node->registerAsRemote(shared_from_this());
if (map.size() == 0) {
RIVER_INFO("Select auto map system ...");
map = m_node->getInterfaceFormat().getMap();
RIVER_INFO(" ==> " << map);
}
// Create convertion interface
if ( m_node->isInput() == true
&& m_mode == river::modeInterface_input) {
m_process.setInputConfig(m_node->getInterfaceFormat());
// Add volume only if the Low level has a volume (otherwise it will be added by the application)
std11::shared_ptr<drain::VolumeElement> tmpVolume = m_node->getVolume();
if (tmpVolume != nullptr) {
// add all time the volume stage :
std11::shared_ptr<drain::Volume> algo = drain::Volume::create();
//algo->setInputFormat(m_node->getInterfaceFormat());
algo->setName("volume");
m_process.pushBack(algo);
RIVER_INFO(" add volume for node");
algo->addVolumeStage(tmpVolume);
}
m_process.setOutputConfig(drain::IOFormatInterface(map, _format, _freq));
} else if ( m_node->isOutput() == true
&& m_mode == river::modeInterface_output) {
m_process.setInputConfig(drain::IOFormatInterface(map, _format, _freq));
// Add volume only if the Low level has a volume (otherwise it will be added by the application)
std11::shared_ptr<drain::VolumeElement> tmpVolume = m_node->getVolume();
if (tmpVolume != nullptr) {
// add all time the volume stage :
std11::shared_ptr<drain::Volume> algo = drain::Volume::create();
//algo->setOutputFormat(m_node->getInterfaceFormat());
algo->setName("volume");
m_process.pushBack(algo);
RIVER_INFO(" add volume for node");
algo->addVolumeStage(tmpVolume);
}
m_process.setOutputConfig(m_node->getInterfaceFormat());
} else if ( m_node->isOutput() == true
&& m_mode == river::modeInterface_feedback) {
m_process.setInputConfig(m_node->getHarwareFormat());
// note : feedback has no volume stage ...
m_process.setOutputConfig(drain::IOFormatInterface(map, _format, _freq));
} else {
RIVER_ERROR("Can not link virtual interface with type : " << m_mode << " to a hardware interface " << (m_node->isInput()==true?"input":"output"));
return false;
}
return true;
}
std11::shared_ptr<river::Interface> river::Interface::create(const std::string& _name,
float _freq,
const std::vector<audio::channel>& _map,
audio::format _format,
const std11::shared_ptr<river::io::Node>& _node,
const std11::shared_ptr<const ejson::Object>& _config) {
std11::shared_ptr<river::Interface> out = std11::shared_ptr<river::Interface>(new river::Interface());
out->init(_name, _freq, _map, _format, _node, _config);
return out;
}
river::Interface::~Interface() {
//stop(true, true);
std11::unique_lock<std11::recursive_mutex> lock(m_mutex);
//m_node->interfaceRemove(shared_from_this());
}
/*
bool river::Interface::hasEndPoint() {
}
*/
void river::Interface::setReadwrite() {
std11::unique_lock<std11::recursive_mutex> lock(m_mutex);
m_process.removeAlgoDynamic();
if (m_process.hasType<drain::EndPoint>() ) {
RIVER_ERROR("Endpoint is already present ==> can not change");
return;
}
if (m_node->isInput() == true) {
m_process.removeIfLast<drain::EndPoint>();
std11::shared_ptr<drain::EndPointRead> algo = drain::EndPointRead::create();
m_process.pushBack(algo);
} else {
m_process.removeIfFirst<drain::EndPoint>();
std11::shared_ptr<drain::EndPointWrite> algo = drain::EndPointWrite::create();
m_process.pushFront(algo);
}
}
void river::Interface::setOutputCallback(drain::playbackFunction _function) {
std11::unique_lock<std11::recursive_mutex> lock(m_mutex);
if (m_mode != river::modeInterface_output) {
RIVER_ERROR("Can not set output endpoint on other than a output IO");
return;
}
m_process.removeAlgoDynamic();
m_process.removeIfFirst<drain::EndPoint>();
std11::shared_ptr<drain::Algo> algo = drain::EndPointCallback::create(_function);
m_process.pushFront(algo);
}
void river::Interface::setInputCallback(drain::recordFunction _function) {
std11::unique_lock<std11::recursive_mutex> lock(m_mutex);
if (m_mode == river::modeInterface_output) {
RIVER_ERROR("Can not set output endpoint on other than a input or feedback IO");
return;
}
m_process.removeAlgoDynamic();
m_process.removeIfLast<drain::EndPoint>();
std11::shared_ptr<drain::Algo> algo = drain::EndPointCallback::create(_function);
m_process.pushBack(algo);
}
void river::Interface::setWriteCallback(drain::playbackFunctionWrite _function) {
std11::unique_lock<std11::recursive_mutex> lock(m_mutex);
if (m_mode != river::modeInterface_output) {
RIVER_ERROR("Can not set output endpoint on other than a output IO");
return;
}
m_process.removeAlgoDynamic();
std11::shared_ptr<drain::EndPointWrite> algo = m_process.get<drain::EndPointWrite>(0);
if (algo == nullptr) {
return;
}
algo->setCallback(_function);
}
void river::Interface::start(const std11::chrono::system_clock::time_point& _time) {
std11::unique_lock<std11::recursive_mutex> lock(m_mutex);
RIVER_DEBUG("start [BEGIN]");
m_process.updateInterAlgo();
m_node->interfaceAdd(shared_from_this());
RIVER_DEBUG("start [ END ]");
}
void river::Interface::stop(bool _fast, bool _abort) {
std11::unique_lock<std11::recursive_mutex> lock(m_mutex);
RIVER_DEBUG("stop [BEGIN]");
m_node->interfaceRemove(shared_from_this());
RIVER_DEBUG("stop [ END]");
}
void river::Interface::abort() {
std11::unique_lock<std11::recursive_mutex> lock(m_mutex);
RIVER_DEBUG("abort [BEGIN]");
// TODO :...
RIVER_DEBUG("abort [ END ]");
}
bool river::Interface::setParameter(const std::string& _filter, const std::string& _parameter, const std::string& _value) {
RIVER_DEBUG("setParameter [BEGIN] : '" << _filter << "':'" << _parameter << "':'" << _value << "'");
bool out = false;
if ( _filter == "volume"
&& _parameter != "FLOW") {
RIVER_ERROR("Interface is not allowed to modify '" << _parameter << "' Volume just allowed to modify 'FLOW' volume");
return false;
}
std11::shared_ptr<drain::Algo> algo = m_process.get<drain::Algo>(_filter);
if (algo == nullptr) {
RIVER_ERROR("setParameter(" << _filter << ") ==> no filter named like this ...");
return false;
}
out = algo->setParameter(_parameter, _value);
RIVER_DEBUG("setParameter [ END ] : '" << out << "'");
return out;
}
std::string river::Interface::getParameter(const std::string& _filter, const std::string& _parameter) const {
RIVER_DEBUG("getParameter [BEGIN] : '" << _filter << "':'" << _parameter << "'");
std::string out;
std11::shared_ptr<const drain::Algo> algo = m_process.get<const drain::Algo>(_filter);
if (algo == nullptr) {
RIVER_ERROR("setParameter(" << _filter << ") ==> no filter named like this ...");
return "[ERROR]";
}
out = algo->getParameter(_parameter);
RIVER_DEBUG("getParameter [ END ] : '" << out << "'");
return out;
}
std::string river::Interface::getParameterProperty(const std::string& _filter, const std::string& _parameter) const {
RIVER_DEBUG("getParameterProperty [BEGIN] : '" << _filter << "':'" << _parameter << "'");
std::string out;
std11::shared_ptr<const drain::Algo> algo = m_process.get<const drain::Algo>(_filter);
if (algo == nullptr) {
RIVER_ERROR("setParameter(" << _filter << ") ==> no filter named like this ...");
return "[ERROR]";
}
out = algo->getParameterProperty(_parameter);
RIVER_DEBUG("getParameterProperty [ END ] : '" << out << "'");
return out;
}
void river::Interface::write(const void* _value, size_t _nbChunk) {
std11::unique_lock<std11::recursive_mutex> lock(m_mutex);
m_process.updateInterAlgo();
std11::shared_ptr<drain::EndPointWrite> algo = m_process.get<drain::EndPointWrite>(0);
if (algo == nullptr) {
return;
}
algo->write(_value, _nbChunk);
}
#if 0
// TODO : add API aCCess mutex for Read and write...
std::vector<int16_t> river::Interface::read(size_t _nbChunk) {
// TODO :...
std::vector<int16_t> data;
/*
data.resize(_nbChunk*m_map.size(), 0);
m_mutex.lock();
int32_t nbChunkBuffer = m_circularBuffer.size() / m_map.size();
m_mutex.unlock();
while (nbChunkBuffer < _nbChunk) {
usleep(1000);
nbChunkBuffer = m_circularBuffer.size() / m_map.size();
}
m_mutex.lock();
for (size_t iii = 0; iii<data.size(); ++iii) {
data[iii] = m_circularBuffer[iii];
}
m_circularBuffer.erase(m_circularBuffer.begin(), m_circularBuffer.begin()+data.size());
m_mutex.unlock();
*/
return data;
}
#endif
void river::Interface::read(void* _value, size_t _nbChunk) {
std11::unique_lock<std11::recursive_mutex> lock(m_mutex);
m_process.updateInterAlgo();
// TODO :...
}
size_t river::Interface::size() const {
std11::unique_lock<std11::recursive_mutex> lock(m_mutex);
// TODO :...
return 0;
}
void river::Interface::setBufferSize(size_t _nbChunk) {
std11::unique_lock<std11::recursive_mutex> lock(m_mutex);
m_process.updateInterAlgo();
// TODO :...
}
void river::Interface::setBufferSize(const std11::chrono::microseconds& _time) {
std11::unique_lock<std11::recursive_mutex> lock(m_mutex);
m_process.updateInterAlgo();
// TODO :...
}
void river::Interface::clearInternalBuffer() {
std11::unique_lock<std11::recursive_mutex> lock(m_mutex);
m_process.updateInterAlgo();
// TODO :...
}
std11::chrono::system_clock::time_point river::Interface::getCurrentTime() const {
std11::unique_lock<std11::recursive_mutex> lock(m_mutex);
// TODO :...
return std11::chrono::system_clock::time_point();
return std11::chrono::system_clock::now();
}
void river::Interface::addVolumeGroup(const std::string& _name) {
std11::unique_lock<std11::recursive_mutex> lock(m_mutex);
RIVER_DEBUG("addVolumeGroup(" << _name << ")");
std11::shared_ptr<drain::Volume> algo = m_process.get<drain::Volume>("volume");
if (algo == nullptr) {
m_process.removeAlgoDynamic();
// add all time the volume stage :
algo = drain::Volume::create();
algo->setName("volume");
if (m_node->isInput() == true) {
m_process.pushFront(algo);
} else {
m_process.pushBack(algo);
}
}
if (_name == "FLOW") {
// Local volume name
algo->addVolumeStage(std11::make_shared<drain::VolumeElement>(_name));
} else {
// get manager unique instance:
std11::shared_ptr<river::io::Manager> mng = river::io::Manager::getInstance();
algo->addVolumeStage(mng->getVolumeGroup(_name));
}
}
void river::Interface::systemNewInputData(std11::chrono::system_clock::time_point _time, const void* _data, size_t _nbChunk) {
std11::unique_lock<std11::recursive_mutex> lockProcess(m_mutex);
void * tmpData = const_cast<void*>(_data);
m_process.push(_time, tmpData, _nbChunk);
}
void river::Interface::systemNeedOutputData(std11::chrono::system_clock::time_point _time, void* _data, size_t _nbChunk, size_t _chunkSize) {
std11::unique_lock<std11::recursive_mutex> lockProcess(m_mutex);
//RIVER_INFO("time : " << _time);
m_process.pull(_time, _data, _nbChunk, _chunkSize);
}
void river::Interface::systemVolumeChange() {
std11::unique_lock<std11::recursive_mutex> lockProcess(m_mutex);
std11::shared_ptr<drain::Volume> algo = m_process.get<drain::Volume>("volume");
if (algo == nullptr) {
return;
}
algo->volumeChange();
}
static void link(etk::FSNode& _node, const std::string& _first, const std::string& _op, const std::string& _second, bool _isLink=true) {
if (_op == "->") {
if (_isLink) {
_node << " " << _first << " -> " << _second << ";\n";
} else {
_node << " " << _first << " -> " << _second << " [style=dashed];\n";
}
} else if (_op == "<-") {
_node << " " << _first << " -> " <<_second<< " [color=transparent];\n";
if (_isLink) {
_node << " " << _second << " -> " << _first << " [constraint=false];\n";
} else {
_node << " " << _second << " -> " << _first << " [constraint=false, style=dashed];\n";
}
}
}
std::string river::Interface::getDotNodeName() const {
if (m_mode == river::modeInterface_input) {
return "API_" + etk::to_string(m_uid) + "_input";
} else if (m_mode == river::modeInterface_feedback) {
return "API_" + etk::to_string(m_uid) + "_feedback";
} else if (m_mode == river::modeInterface_output) {
return "API_" + etk::to_string(m_uid) + "_output";
}
return "error";
}
void river::Interface::generateDot(etk::FSNode& _node, const std::string& _nameIO, bool _isLink) {
_node << " subgraph clusterInterface_" << m_uid << " {\n";
_node << " color=orange;\n";
_node << " label=\"[" << m_uid << "] Interface : " << m_name << "\";\n";
std::string nameIn;
std::string nameOut;
if ( m_mode == river::modeInterface_input
|| m_mode == river::modeInterface_feedback) {
m_process.generateDot(_node, 3, 10000+m_uid, nameIn, nameOut, false);
} else {
m_process.generateDot(_node, 3, 10000+m_uid, nameIn, nameOut, true);
}
if ( m_mode == river::modeInterface_input
|| m_mode == river::modeInterface_feedback) {
link(_node, _nameIO, "->", nameIn, _isLink);
} else {
link(_node, _nameIO, "<-", nameOut, _isLink);
}
_node << " node [shape=Mdiamond];\n";
if (m_mode == river::modeInterface_input) {
_node << " " << getDotNodeName() << " [ label=\"API\\nINPUT\" ];\n";
link(_node, nameOut, "->", getDotNodeName());
} else if (m_mode == river::modeInterface_feedback) {
_node << " " << getDotNodeName() << " [ label=\"API\\nFEEDBACK\" ];\n";
link(_node, nameOut, "->", getDotNodeName());
} else if (m_mode == river::modeInterface_output) {
_node << " " << getDotNodeName() << " [ label=\"API\\nOUTPUT\" ];\n";
link(_node, nameIn, "<-", getDotNodeName());
}
_node << " }\n \n";
}

View File

@@ -1,218 +0,0 @@
/** @file
* @author Edouard DUPIN
* @copyright 2015, Edouard DUPIN, all right reserved
* @license APACHE v2.0 (see license file)
*/
#ifndef __RIVER_INTERFACE_H__
#define __RIVER_INTERFACE_H__
#include <string>
#include <vector>
#include <stdint.h>
#include <etk/mutex.h>
#include <etk/chrono.h>
#include <etk/functional.h>
#include <etk/memory.h>
#include <audio/format.h>
#include <audio/channel.h>
#include <drain/Process.h>
#include <drain/EndPointCallback.h>
#include <drain/EndPointWrite.h>
#include <ejson/ejson.h>
#include <etk/os/FSNode.h>
namespace river {
namespace io {
class Node;
class NodeAirTAudio;
class NodeAEC;
class NodeMuxer;
}
enum modeInterface {
modeInterface_unknow,
modeInterface_input,
modeInterface_output,
modeInterface_feedback,
};
class Interface : public std11::enable_shared_from_this<Interface> {
friend class io::Node;
friend class io::NodeAirTAudio;
friend class io::NodeAEC;
friend class io::NodeMuxer;
friend class Manager;
protected:
uint32_t m_uid; //!< unique ID for interface
protected:
/**
* @brief Constructor
*/
Interface();
bool init(const std::string& _name,
float _freq,
const std::vector<audio::channel>& _map,
audio::format _format,
const std11::shared_ptr<river::io::Node>& _node,
const std11::shared_ptr<const ejson::Object>& _config);
public:
/**
* @brief Destructor
*/
virtual ~Interface();
static std11::shared_ptr<Interface> create(const std::string& _name,
float _freq,
const std::vector<audio::channel>& _map,
audio::format _format,
const std11::shared_ptr<river::io::Node>& _node,
const std11::shared_ptr<const ejson::Object>& _config);
protected:
mutable std11::recursive_mutex m_mutex;
std11::shared_ptr<const ejson::Object> m_config;
protected:
enum modeInterface m_mode;
public:
enum modeInterface getMode() {
return m_mode;
}
drain::Process m_process;
public:
const drain::IOFormatInterface& getInterfaceFormat() {
if ( m_mode == modeInterface_input
|| m_mode == modeInterface_feedback) {
return m_process.getOutputConfig();
} else {
return m_process.getInputConfig();
}
}
protected:
std11::shared_ptr<river::io::Node> m_node;
protected:
std::string m_name;
public:
virtual std::string getName() {
return m_name;
};
/**
* @brief set the read/write mode enable.
*/
virtual void setReadwrite();
/**
* @brief When we want to implement a Callback Mode:
*/
virtual void setWriteCallback(drain::playbackFunctionWrite _function);
virtual void setOutputCallback(drain::playbackFunction _function);
virtual void setInputCallback(drain::recordFunction _function);
/**
* @brief Add a volume group of the current channel.
* @note If you do not call this function with the group "FLOW" you chan not have a channel volume.
* @note the set volume stage can not be set after the start.
* @param[in] _name Name of the group classicle common group:
* - FLOW for channel volume.
* - MEDIA for multimedia volume control (audio player, player video, web streaming ...).
* - TTS for Test-to-speech volume control.
* - COMMUNICATION for user communication volume control.
* - NOTIFICATION for urgent notification volume control.
* - NOISE for small noise volume control.
*/
virtual void addVolumeGroup(const std::string& _name);
public:
/**
* @brief Start the Audio interface flow.
* @param[in] _time Time to start the flow (0) to start as fast as possible...
* @note _time to play buffer when output interface (if possible)
* @note _time to read buffer when inut interface (if possible)
*/
virtual void start(const std11::chrono::system_clock::time_point& _time = std11::chrono::system_clock::time_point());
/**
* @brief Stop the current flow.
* @param[in] _fast The stream stop as fast as possible (not write all the buffer in speaker) but apply cross fade out.
* @param[in] _abort The stream stop whith no garenty of good audio stop.
*/
virtual void stop(bool _fast=false, bool _abort=false);
/**
* @brief Abort flow (no audio garenty)
*/
virtual void abort();
/**
* @brief Set a parameter in the stream flow
* @param[in] _filter name of the filter (if you added some personels)
* @param[in] _parameter Parameter name.
* @param[in] _value Value to set.
* @return true set done
* @return false An error occured
* @example : setParameter("volume", "FLOW", "-3dB");
* @example : setParameter("LowPassFilter", "cutFrequency", "1000Hz");
*/
virtual bool setParameter(const std::string& _filter, const std::string& _parameter, const std::string& _value);
/**
* @brief Get a parameter value
* @param[in] _filter name of the filter (if you added some personels)
* @param[in] _parameter Parameter name.
* @return The requested value.
* @example : getParameter("volume", "FLOW"); can return something like "-3dB"
* @example : getParameter("LowPassFilter", "cutFrequency"); can return something like "[-120..0]dB"
*/
virtual std::string getParameter(const std::string& _filter, const std::string& _parameter) const;
/**
* @brief Get a parameter value
* @param[in] _filter name of the filter (if you added some personels)
* @param[in] _parameter Parameter name.
* @return The requested value.
* @example : getParameter("volume", "FLOW"); can return something like "[-120..0]dB"
* @example : getParameter("LowPassFilter", "cutFreqiency"); can return something like "]100..10000]Hz"
*/
virtual std::string getParameterProperty(const std::string& _filter, const std::string& _parameter) const;
/**
* @brief write some audio sample in the speakers
* @param[in] _value Data To write on output
* @param[in] _nbChunk Number of audio chunk to write
*/
// TODO : TimeOut ???
virtual void write(const void* _value, size_t _nbChunk);
/**
* @brief read some audio sample from Microphone
* @param[in] _value Data To write on output
* @param[in] _nbChunk Number of audio chunk to write
*/
// TODO : TimeOut ???
virtual void read(void* _value, size_t _nbChunk);
/**
* @brief Get number of chunk in the local buffer
* @return Number of chunk
*/
virtual size_t size() const;
/**
* @brief Set buffer size in chunk number
* @param[in] _nbChunk Number of chunk in the buffer
*/
virtual void setBufferSize(size_t _nbChunk);
/**
* @brief Set buffer size in chunk number
* @param[in] _nbChunk Number of chunk in the buffer
*/
virtual void setBufferSize(const std11::chrono::microseconds& _time);
/**
* @brief Remove internal Buffer
*/
virtual void clearInternalBuffer();
/**
* @brief Write : Get the time of the next sample time to write in the local buffer
* @brief Read : Get the time of the next sample time to read in the local buffer
*/
virtual std11::chrono::system_clock::time_point getCurrentTime() const;
private:
virtual void systemNewInputData(std11::chrono::system_clock::time_point _time, const void* _data, size_t _nbChunk);
virtual void systemNeedOutputData(std11::chrono::system_clock::time_point _time, void* _data, size_t _nbChunk, size_t _chunkSize);
virtual void systemVolumeChange();
float m_volume; //!< Local channel Volume
public:
virtual void generateDot(etk::FSNode& _node, const std::string& _nameIO, bool _isLink=true);
virtual std::string getDotNodeName() const;
};
};
#endif

View File

@@ -1,175 +0,0 @@
/** @file
* @author Edouard DUPIN
* @copyright 2015, Edouard DUPIN, all right reserved
* @license APACHE v2.0 (see license file)
*/
#include "Manager.h"
#include "Interface.h"
#include <stdexcept>
#include "io/Manager.h"
#include "io/Node.h"
#include "debug.h"
#undef __class__
#define __class__ "Manager"
static std::string basicAutoConfig =
"{\n"
" microphone:{\n"
" io:'input',\n"
" map-on:'microphone',\n"
" resampling-type:'speexdsp',\n"
" resampling-option:'quality=10'\n"
" },\n"
" speaker:{\n"
" io:'output',\n"
" map-on:'speaker',\n"
" resampling-type:'speexdsp',\n"
" resampling-option:'quality=10'\n"
" }\n"
"}\n";
std11::shared_ptr<river::Manager> river::Manager::create(const std::string& _applicationUniqueId) {
return std11::shared_ptr<river::Manager>(new river::Manager(_applicationUniqueId));
}
river::Manager::Manager(const std::string& _applicationUniqueId) :
m_applicationUniqueId(_applicationUniqueId),
m_listOpenInterface() {
// TODO : Maybe create a single interface property (and all get the same ...)
if (m_config.load("DATA:virtual.json") == false) {
RIVER_WARNING("you must set a basic configuration file for virtual configuration: DATA:virtual.json (load default interface)");
m_config.parse(basicAutoConfig);
}
}
river::Manager::~Manager() {
// TODO : Stop all interfaces...
}
std::vector<std::pair<std::string,std::string> > river::Manager::getListStreamInput() {
std::vector<std::pair<std::string,std::string> > output;
std::vector<std::string> keys = m_config.getKeys();
for (size_t iii=0; iii<keys.size(); ++iii) {
const std11::shared_ptr<const ejson::Object> tmppp = m_config.getObject(keys[iii]);
if (tmppp != nullptr) {
std::string type = tmppp->getStringValue("io", "error");
if ( type == "input"
|| type == "feedback") {
output.push_back(std::make_pair<std::string,std::string>(std::string(keys[iii]), std::string("---")));
}
}
}
return output;
}
std::vector<std::pair<std::string,std::string> > river::Manager::getListStreamOutput() {
std::vector<std::pair<std::string,std::string> > output;
std::vector<std::string> keys = m_config.getKeys();
for (size_t iii=0; iii<keys.size(); ++iii) {
const std11::shared_ptr<const ejson::Object> tmppp = m_config.getObject(keys[iii]);
if (tmppp != nullptr) {
std::string type = tmppp->getStringValue("io", "error");
if (type == "output") {
output.push_back(std::make_pair<std::string,std::string>(std::string(keys[iii]), std::string("---")));
}
}
}
return output;
}
bool river::Manager::setVolume(const std::string& _volumeName, float _valuedB) {
return river::io::Manager::getInstance()->setVolume(_volumeName, _valuedB);
}
float river::Manager::getVolume(const std::string& _volumeName) const {
return river::io::Manager::getInstance()->getVolume(_volumeName);
}
std::pair<float,float> river::Manager::getVolumeRange(const std::string& _volumeName) const {
return river::io::Manager::getInstance()->getVolumeRange(_volumeName);
}
std11::shared_ptr<river::Interface> river::Manager::createOutput(float _freq,
const std::vector<audio::channel>& _map,
audio::format _format,
const std::string& _streamName,
const std::string& _name) {
// check if the output exist
const std11::shared_ptr<const ejson::Object> tmppp = m_config.getObject(_streamName);
if (tmppp == nullptr) {
RIVER_ERROR("can not open a non existance virtual input: '" << _streamName << "' not present in : " << m_config.getKeys());
return std11::shared_ptr<river::Interface>();
}
// check if it is an Output:
std::string type = tmppp->getStringValue("io", "error");
if (type != "output") {
RIVER_ERROR("can not open in output a virtual interface: '" << _streamName << "' configured has : " << type);
return std11::shared_ptr<river::Interface>();
}
std::string mapOn = tmppp->getStringValue("map-on", "");
if (mapOn == "") {
RIVER_ERROR("can not open in output a virtual interface: '" << _streamName << "' No 'map-on' element in json file ... ");
return std11::shared_ptr<river::Interface>();
}
// get global hardware interface:
std11::shared_ptr<river::io::Manager> manager = river::io::Manager::getInstance();
// get the output or input channel :
std11::shared_ptr<river::io::Node> node = manager->getNode(mapOn);
// create user iterface:
std11::shared_ptr<river::Interface> interface;
interface = river::Interface::create(_name, _freq, _map, _format, node, tmppp);
// store it in a list (needed to apply some parameters).
m_listOpenInterface.push_back(interface);
return interface;
}
std11::shared_ptr<river::Interface> river::Manager::createInput(float _freq,
const std::vector<audio::channel>& _map,
audio::format _format,
const std::string& _streamName,
const std::string& _name) {
// check if the output exist
const std11::shared_ptr<const ejson::Object> tmppp = m_config.getObject(_streamName);
if (tmppp == nullptr) {
RIVER_ERROR("can not open a non existance virtual interface: '" << _streamName << "' not present in : " << m_config.getKeys());
return std11::shared_ptr<river::Interface>();
}
// check if it is an Output:
std::string type = tmppp->getStringValue("io", "error");
if ( type != "input"
&& type != "feedback") {
RIVER_ERROR("can not open in output a virtual interface: '" << _streamName << "' configured has : " << type);
return std11::shared_ptr<river::Interface>();
}
std::string mapOn = tmppp->getStringValue("map-on", "");
if (mapOn == "") {
RIVER_ERROR("can not open in output a virtual interface: '" << _streamName << "' No 'map-on' element in json file ... ");
return std11::shared_ptr<river::Interface>();
}
// get global hardware interface:
std11::shared_ptr<river::io::Manager> manager = river::io::Manager::getInstance();
// get the output or input channel :
std11::shared_ptr<river::io::Node> node = manager->getNode(mapOn);
// create user iterface:
std11::shared_ptr<river::Interface> interface;
interface = river::Interface::create(_name, _freq, _map, _format, node, tmppp);
// store it in a list (needed to apply some parameters).
m_listOpenInterface.push_back(interface);
return interface;
}
void river::Manager::generateDotAll(const std::string& _filename) {
// get global hardware interface:
std11::shared_ptr<river::io::Manager> manager = river::io::Manager::getInstance();
if (manager == nullptr) {
RIVER_ERROR("Can not get the harware manager");
return;
}
manager->generateDot(_filename);
}

View File

@@ -1,110 +0,0 @@
/** @file
* @author Edouard DUPIN
* @copyright 2015, Edouard DUPIN, all right reserved
* @license APACHE v2.0 (see license file)
*/
#ifndef __RIVER_MANAGER_H__
#define __RIVER_MANAGER_H__
#include <string>
#include <stdint.h>
#include <etk/memory.h>
#include <river/Interface.h>
#include <audio/format.h>
#include <audio/channel.h>
#include <ejson/ejson.h>
namespace river {
/**
* @brief Audio interface manager : Single interface for every application that want to access on the Audio input/output
*/
class Manager {
private:
ejson::Document m_config; // virtual configuration
const std::string& m_applicationUniqueId; //!< name of the application that open the Audio Interface.
std::vector<std11::weak_ptr<river::Interface> > m_listOpenInterface; //!< List of all open Stream.
protected:
/**
* @brief Constructor
*/
Manager(const std::string& _applicationUniqueId);
public:
static std11::shared_ptr<river::Manager> create(const std::string& _applicationUniqueId);
/**
* @brief Destructor
*/
virtual ~Manager();
public:
/**
* @brief Get all input audio stream description.
* @return a list of all availlables input stream (name + description)
*/
virtual std::vector<std::pair<std::string,std::string> > getListStreamInput();
/**
* @brief Get all output audio stream description.
* @return a list of all availlables output stream (name + description)
*/
virtual std::vector<std::pair<std::string,std::string> > getListStreamOutput();
/**
* @brief Set a volume for a specific group
* @param[in] _volumeName Name of the volume (MASTER, MATER_BT ...)
* @param[in] _value Volume in dB to set.
* @return true set done
* @return false An error occured
* @example : setVolume("MASTER", -3.0f);
*/
virtual bool setVolume(const std::string& _volumeName, float _valuedB);
/**
* @brief Get a volume value
* @param[in] _volumeName Name of the volume (MASTER, MATER_BT ...)
* @return The Volume value in dB.
* @example ret = getVolume("MASTER"); can return something like ret = -3.0f
*/
virtual float getVolume(const std::string& _volumeName) const;
/**
* @brief Get a parameter value
* @param[in] _volumeName Name of the volume (MASTER, MATER_BT ...)
* @return The requested value Range.
* @example ret = getVolumeRange("MASTER"); can return something like ret=(-120.0f,0.0f)
*/
virtual std::pair<float,float> getVolumeRange(const std::string& _volumeName) const;
/**
* @brief Create output Interface
* @param[in] _freq Frequency to open Interface [8,16,22,32,48] kHz
* @param[in] _map ChannelMap of the Output
* @param[in] _format Sample Format to open the stream [int8_t]
* @param[in] _streamName Stream name to open: "" or "default" open current selected output
* @param[in] _name Name of this interface
* @return a pointer on the interface
*/
virtual std11::shared_ptr<Interface> createOutput(float _freq,
const std::vector<audio::channel>& _map,
audio::format _format,
const std::string& _streamName = "",
const std::string& _name = "");
/**
* @brief Create input Interface
* @param[in] _freq Frequency to open Interface [8,16,22,32,48] kHz
* @param[in] _map ChannelMap of the Output
* @param[in] _format Sample Format to open the stream [int8_t]
* @param[in] _streamName Stream name to open: "" or "default" open current selected input
* @param[in] _name Name of this interface
* @return a pointer on the interface
*/
virtual std11::shared_ptr<Interface> createInput(float _freq,
const std::vector<audio::channel>& _map,
audio::format _format,
const std::string& _streamName = "",
const std::string& _name = "");
/**
* @brief Generate the dot file corresponding at all the actif nodes.
* @param[in] _filename Name of the file to write data.
*/
virtual void generateDotAll(const std::string& _filename);
};
};
#endif

View File

@@ -1,13 +0,0 @@
/** @file
* @author Edouard DUPIN
* @copyright 2015, Edouard DUPIN, all right reserved
* @license APACHE v2.0 (see license file)
*/
#include <river/debug.h>
int32_t river::getLogId() {
static int32_t g_val = etk::log::registerInstance("river");
return g_val;
}

View File

@@ -1,36 +0,0 @@
/** @file
* @author Edouard DUPIN
* @copyright 2015, Edouard DUPIN, all right reserved
* @license APACHE v2.0 (see license file)
*/
#ifndef __RIVER_IO_GROUP_H__
#define __RIVER_IO_GROUP_H__
#include <string>
#include <vector>
#include <ejson/ejson.h>
#include <etk/os/FSNode.h>
namespace river {
namespace io {
class Node;
class Manager;
class Group : public std11::enable_shared_from_this<Group> {
public:
Group() {}
~Group() {}
private:
std::vector< std11::shared_ptr<Node> > m_list;
public:
void createFrom(const ejson::Document& _obj, const std::string& _name);
std11::shared_ptr<river::io::Node> getNode(const std::string& _name);
void start();
void stop();
void generateDot(etk::FSNode& _node, bool _hardwareNode);
};
}
}
#endif

View File

@@ -1,301 +0,0 @@
/** @file
* @author Edouard DUPIN
* @copyright 2015, Edouard DUPIN, all right reserved
* @license APACHE v2.0 (see license file)
*/
#include "Manager.h"
#include <river/debug.h>
#include "Node.h"
#include "NodeAEC.h"
#include "NodeMuxer.h"
#include "NodeAirTAudio.h"
#include "NodePortAudio.h"
#include <etk/os/FSNode.h>
#include <etk/memory.h>
#include <etk/types.h>
#include <utility>
#undef __class__
#define __class__ "io::Manager"
#ifdef __PORTAUDIO_INFERFACE__
#include <portaudio.h>
#endif
static std::string basicAutoConfig =
"{\n"
" microphone:{\n"
" io:'input',\n"
" map-on:{\n"
" interface:'auto',\n"
" name:'default',\n"
" },\n"
" frequency:0,\n"
" channel-map:[\n"
" 'front-left', 'front-right'\n"
" ],\n"
" type:'auto',\n"
" nb-chunk:1024\n"
" },\n"
" speaker:{\n"
" io:'output',\n"
" map-on:{\n"
" interface:'auto',\n"
" name:'default',\n"
" },\n"
" frequency:0,\n"
" channel-map:[\n"
" 'front-left', 'front-right',\n"
" ],\n"
" type:'auto',\n"
" nb-chunk:1024,\n"
" volume-name:'MASTER'\n"
" }\n"
"}\n";
river::io::Manager::Manager() {
if (m_config.load("DATA:hardware.json") == false) {
RIVER_WARNING("you must set a basic configuration file for harware configuration: DATA:hardware.json (load default interface)");
m_config.parse(basicAutoConfig);
}
// TODO : Load virtual.json and check if all is correct ...
#ifdef __PORTAUDIO_INFERFACE__
PaError err = Pa_Initialize();
if(err != paNoError) {
RIVER_WARNING("Can not initialize portaudio : " << Pa_GetErrorText(err));
}
#endif
};
river::io::Manager::~Manager() {
#ifdef __PORTAUDIO_INFERFACE__
PaError err = Pa_Terminate();
if(err != paNoError) {
RIVER_WARNING("Can not initialize portaudio : " << Pa_GetErrorText(err));
}
#endif
};
std11::shared_ptr<river::io::Manager> river::io::Manager::getInstance() {
static std11::shared_ptr<river::io::Manager> manager(new Manager());
return manager;
}
std11::shared_ptr<river::io::Node> river::io::Manager::getNode(const std::string& _name) {
std11::unique_lock<std11::recursive_mutex> lock(m_mutex);
RIVER_WARNING("Get node : " << _name);
// search in the standalone list :
for (size_t iii=0; iii<m_list.size(); ++iii) {
std11::shared_ptr<river::io::Node> tmppp = m_list[iii].lock();
if ( tmppp != nullptr
&& _name == tmppp->getName()) {
RIVER_WARNING(" find it ... in standalone");
return tmppp;
}
}
// search in the group list:
{
for (std::map<std::string, std11::shared_ptr<river::io::Group> >::iterator it(m_listGroup.begin());
it != m_listGroup.end();
++it) {
if (it->second != nullptr) {
std11::shared_ptr<river::io::Node> node = it->second->getNode(_name);
if (node != nullptr) {
RIVER_WARNING(" find it ... in group: " << it->first);
return node;
}
}
}
}
RIVER_WARNING("Create a new one : " << _name);
// check if the node can be open :
const std11::shared_ptr<const ejson::Object> tmpObject = m_config.getObject(_name);
if (tmpObject != nullptr) {
//Check if it is in a group:
std::string groupName = tmpObject->getStringValue("group", "");
// get type : io
std::string ioType = tmpObject->getStringValue("io", "error");
if ( groupName != ""
&& ( ioType == "input"
|| ioType == "output"
|| ioType == "PAinput"
|| ioType == "PAoutput") ) {
std11::shared_ptr<river::io::Group> tmpGroup = getGroup(groupName);
if (tmpGroup == nullptr) {
RIVER_WARNING("Can not get group ... '" << groupName << "'");
return std11::shared_ptr<river::io::Node>();
}
return tmpGroup->getNode(_name);
} else {
if (groupName != "") {
RIVER_WARNING("Group is only availlable for Hardware interface ... '" << _name << "'");
}
// TODO : Create a standalone group for every single element ==> simplify understanding ... but not for virtual interface ...
#ifdef __AIRTAUDIO_INFERFACE__
if ( ioType == "input"
|| ioType == "output") {
std11::shared_ptr<river::io::Node> tmp = river::io::NodeAirTAudio::create(_name, tmpObject);
m_list.push_back(tmp);
return tmp;
}
#endif
#ifdef __PORTAUDIO_INFERFACE__
if ( ioType == "PAinput"
|| ioType == "PAoutput") {
std11::shared_ptr<river::io::Node> tmp = river::io::NodePortAudio::create(_name, tmpObject);
m_list.push_back(tmp);
return tmp;
}
#endif
if (ioType == "aec") {
std11::shared_ptr<river::io::Node> tmp = river::io::NodeAEC::create(_name, tmpObject);
m_list.push_back(tmp);
return tmp;
}
if (ioType == "muxer") {
std11::shared_ptr<river::io::Node> tmp = river::io::NodeMuxer::create(_name, tmpObject);
m_list.push_back(tmp);
return tmp;
}
}
}
RIVER_ERROR("Can not create the interface : '" << _name << "' the node is not DEFINED in the configuration file availlable : " << m_config.getKeys());
return std11::shared_ptr<river::io::Node>();
}
std11::shared_ptr<drain::VolumeElement> river::io::Manager::getVolumeGroup(const std::string& _name) {
std11::unique_lock<std11::recursive_mutex> lock(m_mutex);
if (_name == "") {
RIVER_ERROR("Try to create an audio group with no name ...");
return std11::shared_ptr<drain::VolumeElement>();
}
for (size_t iii=0; iii<m_volumeGroup.size(); ++iii) {
if (m_volumeGroup[iii] == nullptr) {
continue;
}
if (m_volumeGroup[iii]->getName() == _name) {
return m_volumeGroup[iii];
}
}
RIVER_DEBUG("Add a new volume group : '" << _name << "'");
std11::shared_ptr<drain::VolumeElement> tmpVolume = std11::make_shared<drain::VolumeElement>(_name);
m_volumeGroup.push_back(tmpVolume);
return tmpVolume;
}
bool river::io::Manager::setVolume(const std::string& _volumeName, float _valuedB) {
std11::unique_lock<std11::recursive_mutex> lock(m_mutex);
std11::shared_ptr<drain::VolumeElement> volume = getVolumeGroup(_volumeName);
if (volume == nullptr) {
RIVER_ERROR("Can not set volume ... : '" << _volumeName << "'");
return false;
}
if ( _valuedB < -300
|| _valuedB > 300) {
RIVER_ERROR("Can not set volume ... : '" << _volumeName << "' out of range : [-300..300]");
return false;
}
volume->setVolume(_valuedB);
for (size_t iii=0; iii<m_list.size(); ++iii) {
std11::shared_ptr<river::io::Node> val = m_list[iii].lock();
if (val != nullptr) {
val->volumeChange();
}
}
return true;
}
float river::io::Manager::getVolume(const std::string& _volumeName) {
std11::unique_lock<std11::recursive_mutex> lock(m_mutex);
std11::shared_ptr<drain::VolumeElement> volume = getVolumeGroup(_volumeName);
if (volume == nullptr) {
RIVER_ERROR("Can not get volume ... : '" << _volumeName << "'");
return 0.0f;
}
return volume->getVolume();
}
std::pair<float,float> river::io::Manager::getVolumeRange(const std::string& _volumeName) const {
return std::make_pair<float,float>(-300, 300);
}
void river::io::Manager::generateDot(const std::string& _filename) {
std11::unique_lock<std11::recursive_mutex> lock(m_mutex);
etk::FSNode node(_filename);
RIVER_INFO("Generate the DOT files: " << node);
if (node.fileOpenWrite() == false) {
RIVER_ERROR("Can not Write the dot file (fail to open) : " << node);
return;
}
node << "digraph G {" << "\n";
node << " rankdir=\"LR\";\n";
// First Step : Create all HW interface:
{
// standalone
for (size_t iii=0; iii<m_list.size(); ++iii) {
std11::shared_ptr<river::io::Node> val = m_list[iii].lock();
if (val != nullptr) {
if (val->isHarwareNode() == true) {
val->generateDot(node);
}
}
}
for (std::map<std::string, std11::shared_ptr<river::io::Group> >::iterator it(m_listGroup.begin());
it != m_listGroup.end();
++it) {
if (it->second != nullptr) {
it->second->generateDot(node, true);
}
}
}
// All other ...
{
// standalone
for (size_t iii=0; iii<m_list.size(); ++iii) {
std11::shared_ptr<river::io::Node> val = m_list[iii].lock();
if (val != nullptr) {
if (val->isHarwareNode() == false) {
val->generateDot(node);
}
}
}
for (std::map<std::string, std11::shared_ptr<river::io::Group> >::iterator it(m_listGroup.begin());
it != m_listGroup.end();
++it) {
if (it->second != nullptr) {
it->second->generateDot(node, false);
}
}
}
node << "}" << "\n";
node.fileClose();
RIVER_INFO("Generate the DOT files: " << node << " (DONE)");
}
std11::shared_ptr<river::io::Group> river::io::Manager::getGroup(const std::string& _name) {
std11::unique_lock<std11::recursive_mutex> lock(m_mutex);
std11::shared_ptr<river::io::Group> out;
std::map<std::string, std11::shared_ptr<river::io::Group> >::iterator it = m_listGroup.find(_name);
if (it == m_listGroup.end()) {
RIVER_INFO("Create a new group: " << _name << " (START)");
out = std11::make_shared<river::io::Group>();
if (out != nullptr) {
out->createFrom(m_config, _name);
std::pair<std::string, std11::shared_ptr<river::io::Group> > plop(std::string(_name), out);
m_listGroup.insert(plop);
RIVER_INFO("Create a new group: " << _name << " ( END )");
} else {
RIVER_ERROR("Can not create new group: " << _name << " ( END )");
}
} else {
out = it->second;
}
return out;
}

View File

@@ -1,90 +0,0 @@
/** @file
* @author Edouard DUPIN
* @copyright 2015, Edouard DUPIN, all right reserved
* @license APACHE v2.0 (see license file)
*/
#ifndef __RIVER_IO_MANAGER_H__
#define __RIVER_IO_MANAGER_H__
#include <string>
#include <vector>
#include <map>
#include <list>
#include <stdint.h>
#include <etk/mutex.h>
#include <etk/chrono.h>
#include <etk/functional.h>
#include <etk/memory.h>
#include <audio/format.h>
#include <audio/channel.h>
#include <ejson/ejson.h>
#include <drain/Volume.h>
#include <river/io/Group.h>
namespace river {
namespace io {
class Node;
class Manager {
private:
mutable std11::recursive_mutex m_mutex;
private:
/**
* @brief Constructor
*/
Manager();
public:
static std11::shared_ptr<Manager> getInstance();
/**
* @brief Destructor
*/
virtual ~Manager();
private:
ejson::Document m_config; // harware configuration
std::vector<std11::shared_ptr<river::io::Node> > m_listKeepAlive; //!< list of all Node that might be keep alive sone time
std::vector<std11::weak_ptr<river::io::Node> > m_list; //!< List of all IO node
public:
std11::shared_ptr<river::io::Node> getNode(const std::string& _name);
private:
std::vector<std11::shared_ptr<drain::VolumeElement> > m_volumeGroup;
public:
std11::shared_ptr<drain::VolumeElement> getVolumeGroup(const std::string& _name);
/**
* @brief Set a volume for a specific group
* @param[in] _volumeName Name of the volume (MASTER, MATER_BT ...)
* @param[in] _value Volume in dB to set.
* @return true set done
* @return false An error occured
* @example : setVolume("MASTER", -3.0f);
*/
virtual bool setVolume(const std::string& _volumeName, float _valuedB);
/**
* @brief Get a volume value
* @param[in] _volumeName Name of the volume (MASTER, MATER_BT ...)
* @return The Volume value in dB.
* @example ret = getVolume("MASTER"); can return something like ret = -3.0f
*/
virtual float getVolume(const std::string& _volumeName);
/**
* @brief Get a parameter value
* @param[in] _volumeName Name of the volume (MASTER, MATER_BT ...)
* @return The requested value Range.
* @example ret = getVolumeRange("MASTER"); can return something like ret=(-120.0f,0.0f)
*/
virtual std::pair<float,float> getVolumeRange(const std::string& _volumeName) const;
/**
* @brief Generate the dot file corresponding at the actif nodes.
* @param[in] _filename Name of the file to write data.
*/
virtual void generateDot(const std::string& _filename);
private:
std::map<std::string, std11::shared_ptr<river::io::Group> > m_listGroup; //!< List of all groups
std11::shared_ptr<river::io::Group> getGroup(const std::string& _name);
};
}
}
#endif

View File

@@ -1,358 +0,0 @@
/** @file
* @author Edouard DUPIN
* @copyright 2015, Edouard DUPIN, all right reserved
* @license APACHE v2.0 (see license file)
*/
#include "Node.h"
#include <river/debug.h>
#undef __class__
#define __class__ "io::Node"
river::io::Node::Node(const std::string& _name, const std11::shared_ptr<const ejson::Object>& _config) :
m_config(_config),
m_name(_name),
m_isInput(false) {
static uint32_t uid=0;
m_uid = uid++;
RIVER_INFO("-----------------------------------------------------------------");
RIVER_INFO("-- CREATE NODE --");
RIVER_INFO("-----------------------------------------------------------------");
drain::IOFormatInterface interfaceFormat;
drain::IOFormatInterface hardwareFormat;
/**
io:"input", # input, output or aec
frequency:48000, # frequency to open device
channel-map:[ # mapping of the harware device (to change map if needed)
"front-left", "front-right",
"read-left", "rear-right",
],
# format to open device (int8, int16, int16-on-ont32, int24, int32, float)
type:"int16",
# muxer/demuxer format type (int8-on-int16, int16-on-int32, int24-on-int32, int32-on-int64, float)
mux-demux-type:"int16_on_int32",
*/
std::string interfaceType = m_config->getStringValue("io");
if ( interfaceType == "input"
|| interfaceType == "PAinput"
|| interfaceType == "aec"
|| interfaceType == "muxer") {
m_isInput = true;
} else {
m_isInput = false;
}
int32_t frequency = m_config->getNumberValue("frequency", 1);
// Get audio format type:
std::string type = m_config->getStringValue("type", "int16");
enum audio::format formatType = audio::getFormatFromString(type);
// Get volume stage :
std::string volumeName = m_config->getStringValue("volume-name", "");
if (volumeName != "") {
RIVER_INFO("add node volume stage : '" << volumeName << "'");
// use global manager for volume ...
m_volume = river::io::Manager::getInstance()->getVolumeGroup(volumeName);
}
// Get map type :
std::vector<audio::channel> map;
const std11::shared_ptr<const ejson::Array> listChannelMap = m_config->getArray("channel-map");
if ( listChannelMap == nullptr
|| listChannelMap->size() == 0) {
// set default channel property:
map.push_back(audio::channel_frontLeft);
map.push_back(audio::channel_frontRight);
} else {
for (size_t iii=0; iii<listChannelMap->size(); ++iii) {
std::string value = listChannelMap->getStringValue(iii);
map.push_back(audio::getChannelFromString(value));
}
}
hardwareFormat.set(map, formatType, frequency);
std::string muxerDemuxerConfig = m_config->getStringValue("mux-demux-type", "int16-on-int32");
enum audio::format muxerFormatType = audio::getFormatFromString(muxerDemuxerConfig);
if (m_isInput == true) {
if (muxerFormatType != audio::format_int16) {
RIVER_CRITICAL("not supported demuxer type ... " << muxerFormatType << " for INPUT set in file:" << muxerDemuxerConfig);
}
} else {
if (muxerFormatType != audio::format_int16_on_int32) {
RIVER_CRITICAL("not supported demuxer type ... " << muxerFormatType << " for OUTPUT set in file:" << muxerDemuxerConfig);
}
}
// no map change and no frequency change ...
interfaceFormat.set(map, muxerFormatType, frequency);
// configure process interface
if (m_isInput == true) {
m_process.setInputConfig(hardwareFormat);
m_process.setOutputConfig(interfaceFormat);
} else {
m_process.setOutputConfig(hardwareFormat);
m_process.setInputConfig(interfaceFormat);
}
//m_process.updateInterAlgo();
}
river::io::Node::~Node() {
RIVER_INFO("-----------------------------------------------------------------");
RIVER_INFO("-- DESTROY NODE --");
RIVER_INFO("-----------------------------------------------------------------");
};
size_t river::io::Node::getNumberOfInterface(enum river::modeInterface _interfaceType) {
size_t out = 0;
for (size_t iii=0; iii<m_list.size(); ++iii) {
if (m_list[iii] == nullptr) {
continue;
}
if (m_list[iii]->getMode() == _interfaceType) {
out++;
}
}
return out;
}
size_t river::io::Node::getNumberOfInterfaceAvaillable(enum river::modeInterface _interfaceType) {
size_t out = 0;
for (size_t iii=0; iii<m_listAvaillable.size(); ++iii) {
std11::shared_ptr<river::Interface> element = m_listAvaillable[iii].lock();
if (element == nullptr) {
continue;
}
if (element->getMode() == _interfaceType) {
out++;
}
}
return out;
}
void river::io::Node::registerAsRemote(const std11::shared_ptr<river::Interface>& _interface) {
std::vector<std11::weak_ptr<river::Interface> >::iterator it = m_listAvaillable.begin();
while (it != m_listAvaillable.end()) {
if (it->expired() == true) {
it = m_listAvaillable.erase(it);
}
++it;
}
m_listAvaillable.push_back(_interface);
}
void river::io::Node::interfaceAdd(const std11::shared_ptr<river::Interface>& _interface) {
{
std11::unique_lock<std11::mutex> lock(m_mutex);
for (size_t iii=0; iii<m_list.size(); ++iii) {
if (_interface == m_list[iii]) {
return;
}
}
RIVER_INFO("ADD interface for stream : '" << m_name << "' mode=" << (m_isInput?"input":"output") );
m_list.push_back(_interface);
}
if (m_list.size() == 1) {
startInGroup();
}
}
void river::io::Node::interfaceRemove(const std11::shared_ptr<river::Interface>& _interface) {
{
std11::unique_lock<std11::mutex> lock(m_mutex);
for (size_t iii=0; iii< m_list.size(); ++iii) {
if (_interface == m_list[iii]) {
m_list.erase(m_list.begin()+iii);
RIVER_INFO("RM interface for stream : '" << m_name << "' mode=" << (m_isInput?"input":"output") );
break;
}
}
}
if (m_list.size() == 0) {
stopInGroup();
}
return;
}
void river::io::Node::volumeChange() {
for (size_t iii=0; iii< m_listAvaillable.size(); ++iii) {
std11::shared_ptr<river::Interface> node = m_listAvaillable[iii].lock();
if (node != nullptr) {
node->systemVolumeChange();
}
}
}
int32_t river::io::Node::newInput(const void* _inputBuffer,
uint32_t _nbChunk,
const std11::chrono::system_clock::time_point& _time) {
if (_inputBuffer == nullptr) {
return -1;
}
const int16_t* inputBuffer = static_cast<const int16_t *>(_inputBuffer);
for (size_t iii=0; iii< m_list.size(); ++iii) {
if (m_list[iii] == nullptr) {
continue;
}
if (m_list[iii]->getMode() != river::modeInterface_input) {
continue;
}
RIVER_VERBOSE(" IO name="<< m_list[iii]->getName());
m_list[iii]->systemNewInputData(_time, inputBuffer, _nbChunk);
}
RIVER_VERBOSE("data Input size request :" << _nbChunk << " [ END ]");
return 0;
}
int32_t river::io::Node::newOutput(void* _outputBuffer,
uint32_t _nbChunk,
const std11::chrono::system_clock::time_point& _time) {
if (_outputBuffer == nullptr) {
return -1;
}
std::vector<int32_t> output;
RIVER_VERBOSE("resize=" << _nbChunk*m_process.getInputConfig().getMap().size());
output.resize(_nbChunk*m_process.getInputConfig().getMap().size(), 0);
const int32_t* outputTmp = nullptr;
std::vector<uint8_t> outputTmp2;
RIVER_VERBOSE("resize=" << sizeof(int32_t)*m_process.getInputConfig().getMap().size()*_nbChunk);
outputTmp2.resize(sizeof(int32_t)*m_process.getInputConfig().getMap().size()*_nbChunk, 0);
for (size_t iii=0; iii< m_list.size(); ++iii) {
if (m_list[iii] == nullptr) {
continue;
}
if (m_list[iii]->getMode() != river::modeInterface_output) {
continue;
}
RIVER_VERBOSE(" IO name="<< m_list[iii]->getName());
// clear datas ...
memset(&outputTmp2[0], 0, sizeof(int32_t)*m_process.getInputConfig().getMap().size()*_nbChunk);
RIVER_VERBOSE(" request Data="<< _nbChunk << " time=" << _time);
m_list[iii]->systemNeedOutputData(_time, &outputTmp2[0], _nbChunk, sizeof(int32_t)*m_process.getInputConfig().getMap().size());
RIVER_VERBOSE(" Mix it ...");
outputTmp = reinterpret_cast<const int32_t*>(&outputTmp2[0]);
// Add data to the output tmp buffer :
for (size_t kkk=0; kkk<output.size(); ++kkk) {
output[kkk] += outputTmp[kkk];
}
}
RIVER_VERBOSE(" End stack process data ...");
m_process.processIn(&outputTmp2[0], _nbChunk, _outputBuffer, _nbChunk);
RIVER_VERBOSE(" Feedback :");
for (size_t iii=0; iii< m_list.size(); ++iii) {
if (m_list[iii] == nullptr) {
continue;
}
if (m_list[iii]->getMode() != river::modeInterface_feedback) {
continue;
}
RIVER_VERBOSE(" IO name="<< m_list[iii]->getName() << " (feedback) time=" << _time);
m_list[iii]->systemNewInputData(_time, _outputBuffer, _nbChunk);
}
RIVER_VERBOSE("data Output size request :" << _nbChunk << " [ END ]");
return 0;
}
static void link(etk::FSNode& _node, const std::string& _first, const std::string& _op, const std::string& _second) {
if (_op == "->") {
_node << " " << _first << " -> " << _second << ";\n";
} else if (_op == "<-") {
_node << " " << _first << " -> " <<_second<< " [color=transparent];\n";
_node << " " << _second << " -> " << _first << " [constraint=false];\n";
}
}
void river::io::Node::generateDot(etk::FSNode& _node) {
_node << " subgraph clusterNode_" << m_uid << " {\n";
_node << " color=blue;\n";
_node << " label=\"[" << m_uid << "] IO::Node : " << m_name << "\";\n";
if (m_isInput == true) {
_node << " node [shape=rarrow];\n";
_node << " NODE_" << m_uid << "_HW_interface [ label=\"HW interface\\n interface=ALSA\\n stream=" << m_name << "\\n type=input\" ];\n";
std::string nameIn;
std::string nameOut;
m_process.generateDot(_node, 3, m_uid, nameIn, nameOut, false);
_node << " node [shape=square];\n";
_node << " NODE_" << m_uid << "_demuxer [ label=\"DEMUXER\\n format=" << etk::to_string(m_process.getOutputConfig().getFormat()) << "\" ];\n";
// Link all nodes :
_node << " NODE_" << m_uid << "_HW_interface -> " << nameIn << " [arrowhead=\"open\"];\n";
_node << " " << nameOut << " -> NODE_" << m_uid << "_demuxer [arrowhead=\"open\"];\n";
} else {
size_t nbOutput = getNumberOfInterfaceAvaillable(river::modeInterface_output);
size_t nbfeedback = getNumberOfInterfaceAvaillable(river::modeInterface_feedback);
_node << " node [shape=larrow];\n";
_node << " NODE_" << m_uid << "_HW_interface [ label=\"HW interface\\n interface=ALSA\\n stream=" << m_name << "\\n type=output\" ];\n";
std::string nameIn;
std::string nameOut;
if (nbOutput>0) {
m_process.generateDot(_node, 3, m_uid, nameIn, nameOut, true);
}
_node << " node [shape=square];\n";
if (nbOutput>0) {
_node << " NODE_" << m_uid << "_muxer [ label=\"MUXER\\n format=" << etk::to_string(m_process.getInputConfig().getFormat()) << "\" ];\n";
}
if (nbfeedback>0) {
_node << " NODE_" << m_uid << "_demuxer [ label=\"DEMUXER\\n format=" << etk::to_string(m_process.getOutputConfig().getFormat()) << "\" ];\n";
}
// Link all nodes :
if (nbOutput>0) {
link(_node, "NODE_" + etk::to_string(m_uid) + "_HW_interface", "<-", nameOut);
link(_node, nameIn, "<-", "NODE_" + etk::to_string(m_uid) + "_muxer");
}
if (nbfeedback>0) {
_node << " NODE_" << m_uid << "_HW_interface -> NODE_" << m_uid << "_demuxer [arrowhead=\"open\"];\n";
}
if ( nbOutput>0
&& nbfeedback>0) {
_node << " { rank=same; NODE_" << m_uid << "_demuxer; NODE_" << m_uid << "_muxer }\n";
}
}
_node << " }\n \n";
for (size_t iii=0; iii< m_listAvaillable.size(); ++iii) {
if (m_listAvaillable[iii].expired() == true) {
continue;
}
std11::shared_ptr<river::Interface> element = m_listAvaillable[iii].lock();
if (element == nullptr) {
continue;
}
bool isLink = false;
for (size_t jjj=0; jjj<m_list.size(); ++jjj) {
if (element == m_list[jjj]) {
isLink = true;
}
}
if (element != nullptr) {
if (element->getMode() == modeInterface_input) {
element->generateDot(_node, "NODE_" + etk::to_string(m_uid) + "_demuxer", isLink);
} else if (element->getMode() == modeInterface_output) {
element->generateDot(_node, "NODE_" + etk::to_string(m_uid) + "_muxer", isLink);
} else if (element->getMode() == modeInterface_feedback) {
element->generateDot(_node, "NODE_" + etk::to_string(m_uid) + "_demuxer", isLink);
} else {
}
}
}
}
void river::io::Node::startInGroup() {
std11::shared_ptr<river::io::Group> group = m_group.lock();
if (group != nullptr) {
group->start();
} else {
start();
}
}
void river::io::Node::stopInGroup() {
std11::shared_ptr<river::io::Group> group = m_group.lock();
if (group != nullptr) {
group->stop();
} else {
stop();
}
}

View File

@@ -1,130 +0,0 @@
/** @file
* @author Edouard DUPIN
* @copyright 2015, Edouard DUPIN, all right reserved
* @license APACHE v2.0 (see license file)
*/
#ifndef __RIVER_IO_NODE_H__
#define __RIVER_IO_NODE_H__
#include <string>
#include <vector>
#include <list>
#include <stdint.h>
#include <etk/chrono.h>
#include <etk/functional.h>
#include <etk/memory.h>
#include <audio/format.h>
#include <audio/channel.h>
#include "Manager.h"
#include <river/Interface.h>
#include <airtaudio/Interface.h>
#include <drain/IOFormatInterface.h>
#include <drain/Volume.h>
#include <etk/os/FSNode.h>
namespace river {
namespace io {
class Manager;
class Group;
class Node : public std11::enable_shared_from_this<Node> {
friend class river::io::Group;
protected:
uint32_t m_uid; // uniqueNodeID
protected:
/**
* @brief Constructor
*/
Node(const std::string& _name, const std11::shared_ptr<const ejson::Object>& _config);
public:
/**
* @brief Destructor
*/
virtual ~Node();
virtual bool isHarwareNode() {
return false;
};
protected:
mutable std11::mutex m_mutex;
std11::shared_ptr<const ejson::Object> m_config;
protected:
drain::Process m_process;
public:
const drain::IOFormatInterface& getInterfaceFormat() {
if (m_isInput == true) {
return m_process.getOutputConfig();
} else {
return m_process.getInputConfig();
}
}
const drain::IOFormatInterface& getHarwareFormat() {
if (m_isInput == true) {
return m_process.getInputConfig();
} else {
return m_process.getOutputConfig();
}
}
protected:
std11::shared_ptr<drain::VolumeElement> m_volume; //!< if a volume is set it is set here ...
protected:
std::vector<std11::weak_ptr<river::Interface> > m_listAvaillable; //!< List of all interface that exist on this Node
std::vector<std11::shared_ptr<river::Interface> > m_list;
size_t getNumberOfInterface(enum river::modeInterface _interfaceType);
size_t getNumberOfInterfaceAvaillable(enum river::modeInterface _interfaceType);
public:
size_t getNumberOfInterface() {
return m_list.size();
}
public:
void registerAsRemote(const std11::shared_ptr<river::Interface>& _interface);
void interfaceAdd(const std11::shared_ptr<river::Interface>& _interface);
void interfaceRemove(const std11::shared_ptr<river::Interface>& _interface);
protected:
std::string m_name; //!< Harware.json configuration name
public:
const std::string& getName() {
return m_name;
}
protected:
bool m_isInput;
public:
bool isInput() {
return m_isInput;
}
bool isOutput() {
return !m_isInput;
}
protected:
std11::weak_ptr<river::io::Group> m_group;
public:
void setGroup(std11::shared_ptr<river::io::Group> _group) {
m_group = _group;
}
protected:
void startInGroup();
void stopInGroup();
virtual void start() = 0;
virtual void stop() = 0;
public:
const std11::shared_ptr<drain::VolumeElement>& getVolume() {
return m_volume;
}
public:
void volumeChange();
protected:
int32_t newInput(const void* _inputBuffer,
uint32_t _nbChunk,
const std11::chrono::system_clock::time_point& _time);
int32_t newOutput(void* _outputBuffer,
uint32_t _nbChunk,
const std11::chrono::system_clock::time_point& _time);
public:
virtual void generateDot(etk::FSNode& _node);
};
}
}
#endif

View File

@@ -1,393 +0,0 @@
/** @file
* @author Edouard DUPIN
* @copyright 2015, Edouard DUPIN, all right reserved
* @license APACHE v2.0 (see license file)
*/
#include <river/io/NodeAEC.h>
#include <river/debug.h>
#include <etk/types.h>
#include <etk/memory.h>
#include <etk/functional.h>
#undef __class__
#define __class__ "io::NodeAEC"
#if 0
int32_t river::io::NodeAEC::airtAudioCallback(void* _outputBuffer,
void* _inputBuffer,
uint32_t _nbChunk,
const std11::chrono::system_clock::time_point& _time,
airtaudio::status _status) {
std11::unique_lock<std11::mutex> lock(m_mutex);
//RIVER_INFO("Time=" << _time);
/*
for (int32_t iii=0; iii<400; ++iii) {
RIVER_VERBOSE("dummy=" << uint64_t(dummy[iii]));
}
*/
if (_outputBuffer != nullptr) {
RIVER_VERBOSE("data Output size request :" << _nbChunk << " [BEGIN] status=" << _status << " nbIO=" << m_list.size());
std::vector<int32_t> output;
RIVER_VERBOSE("resize=" << _nbChunk*m_process.getInputConfig().getMap().size());
output.resize(_nbChunk*m_process.getInputConfig().getMap().size(), 0);
const int32_t* outputTmp = nullptr;
std::vector<uint8_t> outputTmp2;
RIVER_VERBOSE("resize=" << sizeof(int32_t)*m_process.getInputConfig().getMap().size()*_nbChunk);
outputTmp2.resize(sizeof(int32_t)*m_process.getInputConfig().getMap().size()*_nbChunk, 0);
for (auto &it : m_list) {
if (it == nullptr) {
continue;
}
if (it->getMode() != river::modeInterface_output) {
continue;
}
RIVER_VERBOSE(" IO name="<< it->getName());
// clear datas ...
memset(&outputTmp2[0], 0, sizeof(int32_t)*m_process.getInputConfig().getMap().size()*_nbChunk);
RIVER_VERBOSE(" request Data="<< _nbChunk);
it->systemNeedOutputData(_time, &outputTmp2[0], _nbChunk, sizeof(int32_t)*m_process.getInputConfig().getMap().size());
RIVER_VERBOSE(" Mix it ...");
outputTmp = reinterpret_cast<const int32_t*>(&outputTmp2[0]);
// Add data to the output tmp buffer :
for (size_t kkk=0; kkk<output.size(); ++kkk) {
output[kkk] += outputTmp[kkk];
}
}
RIVER_VERBOSE(" End stack process data ...");
m_process.processIn(&outputTmp2[0], _nbChunk, _outputBuffer, _nbChunk);
RIVER_VERBOSE(" Feedback :");
for (auto &it : m_list) {
if (it == nullptr) {
continue;
}
if (it->getMode() != river::modeInterface_feedback) {
continue;
}
RIVER_VERBOSE(" IO name="<< it->getName() << " (feedback)");
it->systemNewInputData(_time, _outputBuffer, _nbChunk);
}
RIVER_VERBOSE("data Output size request :" << _nbChunk << " [ END ]");
}
if (_inputBuffer != nullptr) {
RIVER_VERBOSE("data Input size request :" << _nbChunk << " [BEGIN] status=" << _status << " nbIO=" << m_list.size());
int16_t* inputBuffer = static_cast<int16_t *>(_inputBuffer);
for (auto &it : m_list) {
if (it == nullptr) {
continue;
}
if (it->getMode() != river::modeInterface_input) {
continue;
}
RIVER_INFO(" IO name="<< it->getName());
it->systemNewInputData(_time, inputBuffer, _nbChunk);
}
RIVER_VERBOSE("data Input size request :" << _nbChunk << " [ END ]");
}
return 0;
}
#endif
std11::shared_ptr<river::io::NodeAEC> river::io::NodeAEC::create(const std::string& _name, const std11::shared_ptr<const ejson::Object>& _config) {
return std11::shared_ptr<river::io::NodeAEC>(new river::io::NodeAEC(_name, _config));
}
std11::shared_ptr<river::Interface> river::io::NodeAEC::createInput(float _freq,
const std::vector<audio::channel>& _map,
audio::format _format,
const std::string& _objectName,
const std::string& _name) {
// check if the output exist
const std11::shared_ptr<const ejson::Object> tmppp = m_config->getObject(_objectName);
if (tmppp == nullptr) {
RIVER_ERROR("can not open a non existance virtual interface: '" << _objectName << "' not present in : " << m_config->getKeys());
return std11::shared_ptr<river::Interface>();
}
std::string streamName = tmppp->getStringValue("map-on", "error");
// check if it is an Output:
std::string type = tmppp->getStringValue("io", "error");
if ( type != "input"
&& type != "feedback") {
RIVER_ERROR("can not open in output a virtual interface: '" << streamName << "' configured has : " << type);
return std11::shared_ptr<river::Interface>();
}
// get global hardware interface:
std11::shared_ptr<river::io::Manager> manager = river::io::Manager::getInstance();
// get the output or input channel :
std11::shared_ptr<river::io::Node> node = manager->getNode(streamName);
// create user iterface:
std11::shared_ptr<river::Interface> interface;
interface = river::Interface::create(_name, _freq, _map, _format, node, tmppp);
return interface;
}
river::io::NodeAEC::NodeAEC(const std::string& _name, const std11::shared_ptr<const ejson::Object>& _config) :
Node(_name, _config) {
drain::IOFormatInterface interfaceFormat = getInterfaceFormat();
drain::IOFormatInterface hardwareFormat = getHarwareFormat();
m_sampleTime = std11::chrono::nanoseconds(1000000000/int64_t(hardwareFormat.getFrequency()));
/**
# connect in input mode
map-on-microphone:{
# generic virtual definition
io:"input",
map-on:"microphone",
resampling-type:"speexdsp",
resampling-option:"quality=10"
},
# connect in feedback mode
map-on-feedback:{
io:"feedback",
map-on:"speaker",
resampling-type:"speexdsp",
resampling-option:"quality=10",
},
# AEC algo definition
algo:"river-remover",
algo-mode:"cutter",
*/
std::vector<audio::channel> feedbackMap;
feedbackMap.push_back(audio::channel_frontCenter);
RIVER_INFO("Create FEEDBACK : ");
m_interfaceFeedBack = createInput(hardwareFormat.getFrequency(),
feedbackMap,
hardwareFormat.getFormat(),
"map-on-feedback",
_name + "-AEC-feedback");
if (m_interfaceFeedBack == nullptr) {
RIVER_ERROR("Can not opne virtual device ... map-on-feedback in " << _name);
return;
}
RIVER_INFO("Create MICROPHONE : ");
m_interfaceMicrophone = createInput(hardwareFormat.getFrequency(),
hardwareFormat.getMap(),
hardwareFormat.getFormat(),
"map-on-microphone",
_name + "-AEC-microphone");
if (m_interfaceMicrophone == nullptr) {
RIVER_ERROR("Can not opne virtual device ... map-on-microphone in " << _name);
return;
}
// set callback mode ...
m_interfaceFeedBack->setInputCallback(std11::bind(&river::io::NodeAEC::onDataReceivedFeedBack,
this,
std11::placeholders::_1,
std11::placeholders::_2,
std11::placeholders::_3,
std11::placeholders::_4,
std11::placeholders::_5,
std11::placeholders::_6));
// set callback mode ...
m_interfaceMicrophone->setInputCallback(std11::bind(&river::io::NodeAEC::onDataReceivedMicrophone,
this,
std11::placeholders::_1,
std11::placeholders::_2,
std11::placeholders::_3,
std11::placeholders::_4,
std11::placeholders::_5,
std11::placeholders::_6));
m_bufferMicrophone.setCapacity(std11::chrono::milliseconds(1000),
audio::getFormatBytes(hardwareFormat.getFormat())*hardwareFormat.getMap().size(),
hardwareFormat.getFrequency());
m_bufferFeedBack.setCapacity(std11::chrono::milliseconds(1000),
audio::getFormatBytes(hardwareFormat.getFormat()), // only one channel ...
hardwareFormat.getFrequency());
m_process.updateInterAlgo();
}
river::io::NodeAEC::~NodeAEC() {
RIVER_INFO("close input stream");
stop();
m_interfaceFeedBack.reset();
m_interfaceMicrophone.reset();
};
void river::io::NodeAEC::start() {
std11::unique_lock<std11::mutex> lock(m_mutex);
RIVER_INFO("Start stream : '" << m_name << "' mode=" << (m_isInput?"input":"output") );
if (m_interfaceFeedBack != nullptr) {
RIVER_INFO("Start FEEDBACK : ");
m_interfaceFeedBack->start();
}
if (m_interfaceMicrophone != nullptr) {
RIVER_INFO("Start Microphone : ");
m_interfaceMicrophone->start();
}
}
void river::io::NodeAEC::stop() {
std11::unique_lock<std11::mutex> lock(m_mutex);
if (m_interfaceFeedBack != nullptr) {
m_interfaceFeedBack->stop();
}
if (m_interfaceMicrophone != nullptr) {
m_interfaceMicrophone->stop();
}
}
void river::io::NodeAEC::onDataReceivedMicrophone(const void* _data,
const std11::chrono::system_clock::time_point& _time,
size_t _nbChunk,
enum audio::format _format,
uint32_t _frequency,
const std::vector<audio::channel>& _map) {
RIVER_DEBUG("Microphone Time=" << _time << " _nbChunk=" << _nbChunk << " _map=" << _map << " _format=" << _format << " freq=" << _frequency);
RIVER_DEBUG(" next=" << _time + std11::chrono::nanoseconds(_nbChunk*1000000000LL/int64_t(_frequency)) );
if (_format != audio::format_int16) {
RIVER_ERROR("call wrong type ... (need int16_t)");
}
// push data synchronize
std11::unique_lock<std11::mutex> lock(m_mutex);
m_bufferMicrophone.write(_data, _nbChunk, _time);
//RIVER_SAVE_FILE_MACRO(int16_t, "REC_Microphone.raw", _data, _nbChunk*_map.size());
process();
}
void river::io::NodeAEC::onDataReceivedFeedBack(const void* _data,
const std11::chrono::system_clock::time_point& _time,
size_t _nbChunk,
enum audio::format _format,
uint32_t _frequency,
const std::vector<audio::channel>& _map) {
RIVER_DEBUG("FeedBack Time=" << _time << " _nbChunk=" << _nbChunk << " _map=" << _map << " _format=" << _format << " freq=" << _frequency);
RIVER_DEBUG(" next=" << _time + std11::chrono::nanoseconds(_nbChunk*1000000000LL/int64_t(_frequency)) );
if (_format != audio::format_int16) {
RIVER_ERROR("call wrong type ... (need int16_t)");
}
// push data synchronize
std11::unique_lock<std11::mutex> lock(m_mutex);
m_bufferFeedBack.write(_data, _nbChunk, _time);
//RIVER_SAVE_FILE_MACRO(int16_t, "REC_FeedBack.raw", _data, _nbChunk*_map.size());
process();
}
void river::io::NodeAEC::process() {
if (m_bufferMicrophone.getSize() <= 256) {
return;
}
if (m_bufferFeedBack.getSize() <= 256) {
return;
}
std11::chrono::system_clock::time_point MicTime = m_bufferMicrophone.getReadTimeStamp();
std11::chrono::system_clock::time_point fbTime = m_bufferFeedBack.getReadTimeStamp();
std11::chrono::nanoseconds delta;
if (MicTime < fbTime) {
delta = fbTime - MicTime;
} else {
delta = MicTime - fbTime;
}
RIVER_INFO("check delta " << delta.count() << " > " << m_sampleTime.count());
if (delta > m_sampleTime) {
// Synchronize if possible
if (MicTime < fbTime) {
RIVER_INFO("micTime < fbTime : Change Microphone time start " << fbTime);
RIVER_INFO(" old time stamp=" << m_bufferMicrophone.getReadTimeStamp());
m_bufferMicrophone.setReadPosition(fbTime);
RIVER_INFO(" new time stamp=" << m_bufferMicrophone.getReadTimeStamp());
}
if (MicTime > fbTime) {
RIVER_INFO("micTime > fbTime : Change FeedBack time start " << MicTime);
RIVER_INFO(" old time stamp=" << m_bufferFeedBack.getReadTimeStamp());
m_bufferFeedBack.setReadPosition(MicTime);
RIVER_INFO(" new time stamp=" << m_bufferFeedBack.getReadTimeStamp());
}
}
// check if enought time after synchronisation ...
if (m_bufferMicrophone.getSize() <= 256) {
return;
}
if (m_bufferFeedBack.getSize() <= 256) {
return;
}
MicTime = m_bufferMicrophone.getReadTimeStamp();
fbTime = m_bufferFeedBack.getReadTimeStamp();
if (MicTime-fbTime > m_sampleTime) {
RIVER_ERROR("Can not synchronize flow ... : " << MicTime << " != " << fbTime << " delta = " << (MicTime-fbTime).count()/1000 << " µs");
return;
}
std::vector<uint8_t> dataMic;
std::vector<uint8_t> dataFB;
dataMic.resize(256*sizeof(int16_t)*2, 0);
dataFB.resize(256*sizeof(int16_t), 0);
while (true) {
MicTime = m_bufferMicrophone.getReadTimeStamp();
fbTime = m_bufferFeedBack.getReadTimeStamp();
RIVER_INFO(" process 256 samples ... micTime=" << MicTime << " fbTime=" << fbTime << " delta = " << (MicTime-fbTime).count());
m_bufferMicrophone.read(&dataMic[0], 256);
m_bufferFeedBack.read(&dataFB[0], 256);
RIVER_SAVE_FILE_MACRO(int16_t, "REC_Microphone_sync.raw", &dataMic[0], 256*2);
RIVER_SAVE_FILE_MACRO(int16_t, "REC_FeedBack_sync.raw", &dataFB[0], 256);
// if threaded : send event / otherwise, process ...
//processAEC(&dataMic[0], &dataFB[0], 256, _time);
if (m_bufferMicrophone.getSize() <= 256) {
return;
}
if (m_bufferFeedBack.getSize() <= 256) {
return;
}
}
}
void river::io::NodeAEC::processAEC(void* _dataMic, void* _dataFB, uint32_t _nbChunk, const std11::chrono::system_clock::time_point& _time) {
newInput(_dataMic, _nbChunk, _time);
}
void river::io::NodeAEC::generateDot(etk::FSNode& _node) {
_node << " subgraph clusterNode_" << m_uid << " {\n";
_node << " color=blue;\n";
_node << " label=\"[" << m_uid << "] IO::Node : " << m_name << "\";\n";
_node << " node [shape=box];\n";
// TODO : Create a structure ...
_node << " NODE_" << m_uid << "_HW_AEC [ label=\"AEC\" ];\n";
_node << " subgraph clusterNode_" << m_uid << "_process {\n";
_node << " label=\"Drain::Process\";\n";
_node << " node [shape=ellipse];\n";
_node << " node_ALGO_" << m_uid << "_in [ label=\"format=" << etk::to_string(m_process.getInputConfig().getFormat())
<< "\\n freq=" << m_process.getInputConfig().getFrequency()
<< "\\n channelMap=" << etk::to_string(m_process.getInputConfig().getMap()) << "\" ];\n";
_node << " node_ALGO_" << m_uid << "_out [ label=\"format=" << etk::to_string(m_process.getOutputConfig().getFormat())
<< "\\n freq=" << m_process.getOutputConfig().getFrequency()
<< "\\n channelMap=" << etk::to_string(m_process.getOutputConfig().getMap()) << "\" ];\n";
_node << " }\n";
_node << " node [shape=square];\n";
_node << " NODE_" << m_uid << "_demuxer [ label=\"DEMUXER\\n format=" << etk::to_string(m_process.getOutputConfig().getFormat()) << "\" ];\n";
// Link all nodes :
_node << " NODE_" << m_uid << "_HW_AEC -> node_ALGO_" << m_uid << "_in;\n";
_node << " node_ALGO_" << m_uid << "_in -> node_ALGO_" << m_uid << "_out;\n";
_node << " node_ALGO_" << m_uid << "_out -> NODE_" << m_uid << "_demuxer;\n";
_node << " }\n";
if (m_interfaceMicrophone != nullptr) {
_node << " " << m_interfaceMicrophone->getDotNodeName() << " -> NODE_" << m_uid << "_HW_AEC;\n";
}
if (m_interfaceFeedBack != nullptr) {
_node << " " << m_interfaceFeedBack->getDotNodeName() << " -> NODE_" << m_uid << "_HW_AEC;\n";
}
for (size_t iii=0; iii<m_list.size(); ++iii) {
if (m_list[iii] != nullptr) {
if (m_list[iii]->getMode() == modeInterface_input) {
m_list[iii]->generateDot(_node, "NODE_" + etk::to_string(m_uid) + "_demuxer");
} else if (m_list[iii]->getMode() == modeInterface_output) {
m_list[iii]->generateDot(_node, "NODE_" + etk::to_string(m_uid) + "_muxer");
} else if (m_list[iii]->getMode() == modeInterface_feedback) {
m_list[iii]->generateDot(_node, "NODE_" + etk::to_string(m_uid) + "_demuxer");
} else {
}
}
}
}

View File

@@ -1,63 +0,0 @@
/** @file
* @author Edouard DUPIN
* @copyright 2015, Edouard DUPIN, all right reserved
* @license APACHE v2.0 (see license file)
*/
#ifndef __RIVER_IO_NODE_AEC_H__
#define __RIVER_IO_NODE_AEC_H__
#include <river/io/Node.h>
#include <river/Interface.h>
#include <river/CircularBuffer.h>
namespace river {
namespace io {
class Manager;
class NodeAEC : public Node {
protected:
/**
* @brief Constructor
*/
NodeAEC(const std::string& _name, const std11::shared_ptr<const ejson::Object>& _config);
public:
static std11::shared_ptr<NodeAEC> create(const std::string& _name, const std11::shared_ptr<const ejson::Object>& _config);
/**
* @brief Destructor
*/
virtual ~NodeAEC();
protected:
virtual void start();
virtual void stop();
std11::shared_ptr<river::Interface> m_interfaceMicrophone;
std11::shared_ptr<river::Interface> m_interfaceFeedBack;
std11::shared_ptr<river::Interface> createInput(float _freq,
const std::vector<audio::channel>& _map,
audio::format _format,
const std::string& _streamName,
const std::string& _name);
void onDataReceivedMicrophone(const void* _data,
const std11::chrono::system_clock::time_point& _time,
size_t _nbChunk,
enum audio::format _format,
uint32_t _frequency,
const std::vector<audio::channel>& _map);
void onDataReceivedFeedBack(const void* _data,
const std11::chrono::system_clock::time_point& _time,
size_t _nbChunk,
enum audio::format _format,
uint32_t _frequency,
const std::vector<audio::channel>& _map);
river::CircularBuffer m_bufferMicrophone;
river::CircularBuffer m_bufferFeedBack;
std11::chrono::nanoseconds m_sampleTime; //!< represent the sample time at the specify frequency.
void process();
void processAEC(void* _dataMic, void* _dataFB, uint32_t _nbChunk, const std11::chrono::system_clock::time_point& _time);
public:
virtual void generateDot(etk::FSNode& _node);
};
}
}
#endif

View File

@@ -1,281 +0,0 @@
/** @file
* @author Edouard DUPIN
* @copyright 2015, Edouard DUPIN, all right reserved
* @license APACHE v2.0 (see license file)
*/
#ifdef __AIRTAUDIO_INFERFACE__
#include <river/io/NodeAirTAudio.h>
#include <river/debug.h>
#include <etk/memory.h>
#undef __class__
#define __class__ "io::NodeAirTAudio"
static std::string asString(const std11::chrono::system_clock::time_point& tp) {
// convert to system time:
std::time_t t = std11::chrono::system_clock::to_time_t(tp);
// convert in human string
std::string ts = std::ctime(&t);
// remove \n
ts.resize(ts.size()-1);
return ts;
}
int32_t river::io::NodeAirTAudio::duplexCallback(const void* _inputBuffer,
const std11::chrono::system_clock::time_point& _timeInput,
void* _outputBuffer,
const std11::chrono::system_clock::time_point& _timeOutput,
uint32_t _nbChunk,
const std::vector<airtaudio::status>& _status) {
std11::unique_lock<std11::mutex> lock(m_mutex);
// TODO : Manage status ...
if (_inputBuffer != nullptr) {
RIVER_VERBOSE("data Input size request :" << _nbChunk << " [BEGIN] status=" << _status << " nbIO=" << m_list.size());
newInput(_inputBuffer, _nbChunk, _timeInput);
}
if (_outputBuffer != nullptr) {
RIVER_VERBOSE("data Output size request :" << _nbChunk << " [BEGIN] status=" << _status << " nbIO=" << m_list.size());
newOutput(_outputBuffer, _nbChunk, _timeOutput);
}
return 0;
}
int32_t river::io::NodeAirTAudio::recordCallback(const void* _inputBuffer,
const std11::chrono::system_clock::time_point& _timeInput,
uint32_t _nbChunk,
const std::vector<airtaudio::status>& _status) {
std11::unique_lock<std11::mutex> lock(m_mutex);
// TODO : Manage status ...
RIVER_VERBOSE("data Input size request :" << _nbChunk << " [BEGIN] status=" << _status << " nbIO=" << m_list.size());
newInput(_inputBuffer, _nbChunk, _timeInput);
return 0;
}
int32_t river::io::NodeAirTAudio::playbackCallback(void* _outputBuffer,
const std11::chrono::system_clock::time_point& _timeOutput,
uint32_t _nbChunk,
const std::vector<airtaudio::status>& _status) {
std11::unique_lock<std11::mutex> lock(m_mutex);
// TODO : Manage status ...
RIVER_VERBOSE("data Output size request :" << _nbChunk << " [BEGIN] status=" << _status << " nbIO=" << m_list.size());
newOutput(_outputBuffer, _nbChunk, _timeOutput);
return 0;
}
std11::shared_ptr<river::io::NodeAirTAudio> river::io::NodeAirTAudio::create(const std::string& _name, const std11::shared_ptr<const ejson::Object>& _config) {
return std11::shared_ptr<river::io::NodeAirTAudio>(new river::io::NodeAirTAudio(_name, _config));
}
river::io::NodeAirTAudio::NodeAirTAudio(const std::string& _name, const std11::shared_ptr<const ejson::Object>& _config) :
Node(_name, _config) {
drain::IOFormatInterface interfaceFormat = getInterfaceFormat();
drain::IOFormatInterface hardwareFormat = getHarwareFormat();
/**
map-on:{ # select hardware interface and name
interface:"alsa", # interface : "alsa", "pulse", "core", ...
name:"default", # name of the interface
},
nb-chunk:1024 # number of chunk to open device (create the latency anf the frequency to call user)
*/
enum airtaudio::type typeInterface = airtaudio::type_undefined;
std::string streamName = "default";
const std11::shared_ptr<const ejson::Object> tmpObject = m_config->getObject("map-on");
if (tmpObject == nullptr) {
RIVER_WARNING("missing node : 'map-on' ==> auto map : 'auto:default'");
} else {
std::string value = tmpObject->getStringValue("interface", "default");
typeInterface = airtaudio::getTypeFromString(value);
streamName = tmpObject->getStringValue("name", "default");
}
int32_t nbChunk = m_config->getNumberValue("nb-chunk", 1024);
// intanciate specific API ...
m_adac.instanciate(typeInterface);
m_adac.setName(_name);
// TODO : Check return ...
std::string type = m_config->getStringValue("type", "int16");
if (streamName == "") {
streamName = "default";
}
// search device ID :
RIVER_INFO("Open :");
RIVER_INFO(" m_streamName=" << streamName);
RIVER_INFO(" m_freq=" << hardwareFormat.getFrequency());
RIVER_INFO(" m_map=" << hardwareFormat.getMap());
RIVER_INFO(" m_format=" << hardwareFormat.getFormat());
RIVER_INFO(" m_isInput=" << m_isInput);
int32_t deviceId = -1;
// TODO : Remove this from here (create an extern interface ...)
RIVER_INFO("Device list:");
for (int32_t iii=0; iii<m_adac.getDeviceCount(); ++iii) {
m_info = m_adac.getDeviceInfo(iii);
RIVER_INFO(" " << iii << " name :" << m_info.name);
m_info.display(2);
}
// special case for default IO:
if (streamName == "default") {
if (m_isInput == true) {
deviceId = m_adac.getDefaultInputDevice();
} else {
deviceId = m_adac.getDefaultOutputDevice();
}
} else {
for (int32_t iii=0; iii<m_adac.getDeviceCount(); ++iii) {
m_info = m_adac.getDeviceInfo(iii);
if (m_info.name == streamName) {
RIVER_INFO(" Select ... id =" << iii);
deviceId = iii;
}
}
}
// TODO : Check if the devace with the specific name exist ...
/*
if (deviceId == -1) {
RIVER_ERROR("Can not find the " << streamName << " audio interface ... (use O default ...)");
deviceId = 0;
}
*/
// Open specific ID :
if (deviceId == -1) {
m_info = m_adac.getDeviceInfo(streamName);
} else {
m_info = m_adac.getDeviceInfo(deviceId);
}
// display property :
{
RIVER_INFO("Device " << deviceId << " - '" << streamName << "' property :");
m_info.display();
if (etk::isIn(hardwareFormat.getFormat(), m_info.nativeFormats) == false) {
if (type == "auto") {
if (etk::isIn(audio::format_int16, m_info.nativeFormats) == true) {
hardwareFormat.setFormat(audio::format_int16);
RIVER_INFO("auto set format: " << hardwareFormat.getFormat());
} else if (etk::isIn(audio::format_float, m_info.nativeFormats) == true) {
hardwareFormat.setFormat(audio::format_float);
RIVER_INFO("auto set format: " << hardwareFormat.getFormat());
} else if (etk::isIn(audio::format_int16_on_int32, m_info.nativeFormats) == true) {
hardwareFormat.setFormat(audio::format_int16_on_int32);
RIVER_INFO("auto set format: " << hardwareFormat.getFormat());
} else if (etk::isIn(audio::format_int24, m_info.nativeFormats) == true) {
hardwareFormat.setFormat(audio::format_int24);
RIVER_INFO("auto set format: " << hardwareFormat.getFormat());
} else if (m_info.nativeFormats.size() != 0) {
hardwareFormat.setFormat(m_info.nativeFormats[0]);
RIVER_INFO("auto set format: " << hardwareFormat.getFormat());
} else {
RIVER_CRITICAL("auto set format no element in the configuration: " << m_info.nativeFormats);
}
} else {
RIVER_CRITICAL("Can not manage input transforamtion: " << hardwareFormat.getFormat() << " not in " << m_info.nativeFormats);
}
}
if (etk::isIn(hardwareFormat.getFrequency(), m_info.sampleRates) == false) {
if (etk::isIn(48000, m_info.sampleRates) == true) {
hardwareFormat.setFrequency(48000);
RIVER_INFO("auto set frequency: " << hardwareFormat.getFrequency());
} else if (etk::isIn(44100, m_info.sampleRates) == true) {
hardwareFormat.setFrequency(44100);
RIVER_INFO("auto set frequency: " << hardwareFormat.getFrequency());
} else if (etk::isIn(32000, m_info.sampleRates) == true) {
hardwareFormat.setFrequency(32000);
RIVER_INFO("auto set frequency: " << hardwareFormat.getFrequency());
} else if (etk::isIn(16000, m_info.sampleRates) == true) {
hardwareFormat.setFrequency(16000);
RIVER_INFO("auto set frequency: " << hardwareFormat.getFrequency());
} else if (etk::isIn(8000, m_info.sampleRates) == true) {
hardwareFormat.setFrequency(8000);
RIVER_INFO("auto set frequency: " << hardwareFormat.getFrequency());
} else if (etk::isIn(96000, m_info.sampleRates) == true) {
hardwareFormat.setFrequency(96000);
RIVER_INFO("auto set frequency: " << hardwareFormat.getFrequency());
} else if (m_info.sampleRates.size() != 0) {
hardwareFormat.setFrequency(m_info.sampleRates[0]);
RIVER_INFO("auto set frequency: " << hardwareFormat.getFrequency() << "(first element in list) in " << m_info.sampleRates);
} else {
RIVER_CRITICAL("Can not manage input transforamtion:" << hardwareFormat.getFrequency() << " not in " << m_info.sampleRates);
}
interfaceFormat.setFrequency(hardwareFormat.getFrequency());
}
}
// open Audio device:
airtaudio::StreamParameters params;
params.deviceId = deviceId;
params.deviceName = streamName;
// TODO : Remove limit of 2 channels ...
if (m_isInput == true) {
m_info.inputChannels = 2;
params.nChannels = 2;
} else {
m_info.outputChannels = 2;
params.nChannels = 2;
}
airtaudio::StreamOptions option;
etk::from_string(option.mode, tmpObject->getStringValue("timestamp-mode", "soft"));
m_rtaudioFrameSize = nbChunk;
RIVER_INFO("Open output stream nbChannels=" << params.nChannels);
enum airtaudio::error err = airtaudio::error_none;
if (m_isInput == true) {
err = m_adac.openStream(nullptr, &params,
hardwareFormat.getFormat(), hardwareFormat.getFrequency(), &m_rtaudioFrameSize,
std11::bind(&river::io::NodeAirTAudio::recordCallback,
this,
std11::placeholders::_1,
std11::placeholders::_2,
std11::placeholders::_5,
std11::placeholders::_6),
option
);
} else {
err = m_adac.openStream(&params, nullptr,
hardwareFormat.getFormat(), hardwareFormat.getFrequency(), &m_rtaudioFrameSize,
std11::bind(&river::io::NodeAirTAudio::playbackCallback,
this,
std11::placeholders::_3,
std11::placeholders::_4,
std11::placeholders::_5,
std11::placeholders::_6),
option
);
}
if (err != airtaudio::error_none) {
RIVER_ERROR("Create stream : '" << m_name << "' mode=" << (m_isInput?"input":"output") << " can not create stream " << err);
}
m_process.updateInterAlgo();
}
river::io::NodeAirTAudio::~NodeAirTAudio() {
std11::unique_lock<std11::mutex> lock(m_mutex);
RIVER_INFO("close input stream");
if (m_adac.isStreamOpen() ) {
m_adac.closeStream();
}
};
void river::io::NodeAirTAudio::start() {
std11::unique_lock<std11::mutex> lock(m_mutex);
RIVER_INFO("Start stream : '" << m_name << "' mode=" << (m_isInput?"input":"output") );
enum airtaudio::error err = m_adac.startStream();
if (err != airtaudio::error_none) {
RIVER_ERROR("Start stream : '" << m_name << "' mode=" << (m_isInput?"input":"output") << " can not start stream ... " << err);
}
}
void river::io::NodeAirTAudio::stop() {
std11::unique_lock<std11::mutex> lock(m_mutex);
RIVER_INFO("Stop stream : '" << m_name << "' mode=" << (m_isInput?"input":"output") );
enum airtaudio::error err = m_adac.stopStream();
if (err != airtaudio::error_none) {
RIVER_ERROR("Stop stream : '" << m_name << "' mode=" << (m_isInput?"input":"output") << " can not stop stream ... " << err);
}
}
#endif

View File

@@ -1,62 +0,0 @@
/** @file
* @author Edouard DUPIN
* @copyright 2015, Edouard DUPIN, all right reserved
* @license APACHE v2.0 (see license file)
*/
#ifndef __RIVER_IO_NODE_AIRTAUDIO_H__
#define __RIVER_IO_NODE_AIRTAUDIO_H__
#ifdef __AIRTAUDIO_INFERFACE__
#include <river/io/Node.h>
namespace river {
namespace io {
class Manager;
class Group;
class NodeAirTAudio : public Node {
friend class river::io::Group;
protected:
/**
* @brief Constructor
*/
NodeAirTAudio(const std::string& _name, const std11::shared_ptr<const ejson::Object>& _config);
public:
static std11::shared_ptr<NodeAirTAudio> create(const std::string& _name, const std11::shared_ptr<const ejson::Object>& _config);
/**
* @brief Destructor
*/
virtual ~NodeAirTAudio();
virtual bool isHarwareNode() {
return true;
};
protected:
airtaudio::Interface m_adac; //!< Real audio interface
airtaudio::DeviceInfo m_info;
unsigned int m_rtaudioFrameSize;
public:
int32_t duplexCallback(const void* _inputBuffer,
const std11::chrono::system_clock::time_point& _timeInput,
void* _outputBuffer,
const std11::chrono::system_clock::time_point& _timeOutput,
uint32_t _nbChunk,
const std::vector<airtaudio::status>& _status);
int32_t recordCallback(const void* _inputBuffer,
const std11::chrono::system_clock::time_point& _timeInput,
uint32_t _nbChunk,
const std::vector<airtaudio::status>& _status);
int32_t playbackCallback(void* _outputBuffer,
const std11::chrono::system_clock::time_point& _timeOutput,
uint32_t _nbChunk,
const std::vector<airtaudio::status>& _status);
protected:
virtual void start();
virtual void stop();
};
}
}
#endif
#endif

View File

@@ -1,68 +0,0 @@
/** @file
* @author Edouard DUPIN
* @copyright 2015, Edouard DUPIN, all right reserved
* @license APACHE v2.0 (see license file)
*/
#ifndef __RIVER_IO_NODE_MUXER_H__
#define __RIVER_IO_NODE_MUXER_H__
#include <river/io/Node.h>
#include <river/Interface.h>
#include <river/CircularBuffer.h>
namespace river {
namespace io {
class Manager;
class NodeMuxer : public Node {
protected:
/**
* @brief Constructor
*/
NodeMuxer(const std::string& _name, const std11::shared_ptr<const ejson::Object>& _config);
public:
static std11::shared_ptr<NodeMuxer> create(const std::string& _name, const std11::shared_ptr<const ejson::Object>& _config);
/**
* @brief Destructor
*/
virtual ~NodeMuxer();
protected:
virtual void start();
virtual void stop();
std11::shared_ptr<river::Interface> m_interfaceInput1;
std11::shared_ptr<river::Interface> m_interfaceInput2;
std11::shared_ptr<river::Interface> createInput(float _freq,
const std::vector<audio::channel>& _map,
audio::format _format,
const std::string& _streamName,
const std::string& _name);
void onDataReceivedInput1(const void* _data,
const std11::chrono::system_clock::time_point& _time,
size_t _nbChunk,
enum audio::format _format,
uint32_t _frequency,
const std::vector<audio::channel>& _map);
void onDataReceivedInput2(const void* _data,
const std11::chrono::system_clock::time_point& _time,
size_t _nbChunk,
enum audio::format _format,
uint32_t _frequency,
const std::vector<audio::channel>& _map);
std::vector<audio::channel> m_mapInput1;
std::vector<audio::channel> m_mapInput2;
river::CircularBuffer m_bufferInput1;
river::CircularBuffer m_bufferInput2;
std11::chrono::nanoseconds m_sampleTime; //!< represent the sample time at the specify frequency.
void process();
void processMuxer(void* _dataMic, void* _dataFB, uint32_t _nbChunk, const std11::chrono::system_clock::time_point& _time);
std::vector<uint8_t> m_data;
public:
virtual void generateDot(etk::FSNode& _node);
private:
void reorder(void* _output, uint32_t _nbChunk, void* _input, const std::vector<audio::channel>& _mapInput);
};
}
}
#endif

View File

@@ -1,52 +0,0 @@
/** @file
* @author Edouard DUPIN
* @copyright 2015, Edouard DUPIN, all right reserved
* @license APACHE v2.0 (see license file)
*/
#ifndef __RIVER_IO_NODE_PORTAUDIO_H__
#define __RIVER_IO_NODE_PORTAUDIO_H__
#ifdef __PORTAUDIO_INFERFACE__
#include <river/Interface.h>
#include <river/io/Node.h>
#include <portaudio.h>
namespace river {
namespace io {
class Manager;
class NodePortAudio : public Node {
protected:
/**
* @brief Constructor
*/
NodePortAudio(const std::string& _name, const std11::shared_ptr<const ejson::Object>& _config);
public:
static std11::shared_ptr<NodePortAudio> create(const std::string& _name, const std11::shared_ptr<const ejson::Object>& _config);
/**
* @brief Destructor
*/
virtual ~NodePortAudio();
virtual bool isHarwareNode() {
return true;
};
protected:
PaStream* m_stream;
public:
int32_t duplexCallback(const void* _inputBuffer,
const std11::chrono::system_clock::time_point& _timeInput,
void* _outputBuffer,
const std11::chrono::system_clock::time_point& _timeOutput,
uint32_t _nbChunk,
PaStreamCallbackFlags _status);
protected:
virtual void start();
virtual void stop();
};
}
}
#endif
#endif

View File

@@ -0,0 +1,31 @@
{
"type":"BINARY",
"sub-type":"TEST",
"group-id":"com.atria-soft",
"description":"Simpleaudio IO viewer and test ...",
"license":"MPL-2",
"license-file":"file://../../LICENSE",
"maintainer":"file://../../authors.txt",
"author":"file://../../authors.txt",
"version":"file://../../version.txt",
"code-quality":"MEDIUM",
"source": [
"appl/debug.cpp",
"appl/main.cpp",
"appl/Windows.cpp"
],
"compilation-version": {
"c++": 2017
},
"path": [
"."
],
"dependency": [
"ewol",
"audio-river",
"audio-river-widget"
],
"comment": "# set the package properties \n my_module.set_pkg(SECTION, [Development]) \n my_module.set_pkg(PRIORITY, optional) \n my_module.add_pkg(RIGHT, RECORD_AUDIO)"
}

View File

@@ -0,0 +1,64 @@
/** @file
* @author Edouard DUPIN
* @copyright 2015, Edouard DUPIN, all right reserved
* @license MPL v2.0 (see license file)
*/
#include <ewol/ewol.hpp>
#include <appl/debug.hpp>
#include <appl/Windows.hpp>
#include <ewol/widget/Label.hpp>
#include <ewol/widget/Button.hpp>
#include <audio/river/widget/TemporalViewer.hpp>
#include <etk/tool.hpp>
appl::Windows::Windows() :
m_composer(null) {
addObjectType("appl::Windows");
propertyTitle.setDirectCheck("River IO viewer");
}
void appl::Windows::init() {
ewol::widget::Windows::init();
etk::String composition = etk::String("");
composition += "<sizer mode='vert'>\n";
composition += " <sizer mode='hori' lock='true' min-size='10,10%'>\n";
composition += " <button name='bt-record' expend='true' fill='true'>\n";
composition += " <label>\n";
composition += " Start/Stop record\n";
composition += " </label>\n";
composition += " </button>\n";
composition += " <button name='bt-generate' expend='true' fill='true'>\n";
composition += " <label>\n";
composition += " Start/Stop Generate\n";
composition += " </label>\n";
composition += " </button>\n";
composition += " </sizer>\n";
composition += " <TemporalViewer name='displayer' expand='true' fill='true'/>\n";
composition += "</sizer>\n";
m_composer = ewol::widget::Composer::create();
if (m_composer == null) {
APPL_CRITICAL(" An error occured ... in the windows creatrion ...");
return;
}
m_composer->loadFromString(composition);
setSubWidget(m_composer);
subBind(ewol::widget::Button, "bt-record", signalPressed, sharedFromThis(), &appl::Windows::onCallbackRecord);
subBind(ewol::widget::Button, "bt-generate", signalPressed, sharedFromThis(), &appl::Windows::onCallbackGenerate);
}
void appl::Windows::onCallbackRecord() {
ememory::SharedPtr<audio::river::widget::TemporalViewer> tmpDisp = ememory::dynamicPointerCast<audio::river::widget::TemporalViewer>(getSubObjectNamed("displayer"));
if (tmpDisp != null) {
tmpDisp->recordToggle();
}
}
void appl::Windows::onCallbackGenerate() {
ememory::SharedPtr<audio::river::widget::TemporalViewer> tmpDisp = ememory::dynamicPointerCast<audio::river::widget::TemporalViewer>(getSubObjectNamed("displayer"));
if (tmpDisp != null) {
tmpDisp->generateToggle();
}
}

View File

@@ -0,0 +1,25 @@
/** @file
* @author Edouard DUPIN
* @copyright 2015, Edouard DUPIN, all right reserved
* @license MPL v2.0 (see license file)
*/
#pragma once
#include <ewol/widget/Windows.hpp>
#include <ewol/widget/Composer.hpp>
namespace appl {
class Windows : public ewol::widget::Windows {
private:
ememory::SharedPtr<ewol::widget::Composer> m_composer;
protected:
Windows();
void init();
public:
DECLARE_FACTORY(Windows);
public: // callback functions
void onCallbackRecord();
void onCallbackGenerate();
};
}

View File

@@ -0,0 +1,13 @@
/** @file
* @author Edouard DUPIN
* @copyright 2011, Edouard DUPIN, all right reserved
* @license MPL v2.0 (see license file)
*/
#include <appl/debug.hpp>
int32_t appl::getLogId() {
static int32_t g_val = elog::registerInstance("ioViewer");
return g_val;
}

View File

@@ -1,30 +1,19 @@
/** /** @file
* @author Edouard DUPIN * @author Edouard DUPIN
* * @copyright 2011, Edouard DUPIN, all right reserved
* @copyright 2010, Edouard DUPIN, all right reserved * @license MPL v2.0 (see license file)
*
* @license GPL v3 (see license file)
*/ */
#pragma once
#ifndef __APPL_DEBUG_H__ #include <elog/log.hpp>
#define __APPL_DEBUG_H__
#include <etk/log.h>
namespace appl { namespace appl {
int32_t getLogId(); int32_t getLogId();
}; }
// TODO : Review this problem of multiple intanciation of "std::stringbuf sb"
#define APPL_BASE(info,data) \
do { \
if (info <= etk::log::getLevel(appl::getLogId())) { \
std::stringbuf sb; \
std::ostream tmpStream(&sb); \
tmpStream << data; \
etk::log::logStream(appl::getLogId(), info, __LINE__, __class__, __func__, tmpStream); \
} \
} while(0)
#define APPL_BASE(info,data) ELOG_BASE(appl::getLogId(),info,data)
#define APPL_PRINT(data) APPL_BASE(-1, data)
#define APPL_CRITICAL(data) APPL_BASE(1, data) #define APPL_CRITICAL(data) APPL_BASE(1, data)
#define APPL_ERROR(data) APPL_BASE(2, data) #define APPL_ERROR(data) APPL_BASE(2, data)
#define APPL_WARNING(data) APPL_BASE(3, data) #define APPL_WARNING(data) APPL_BASE(3, data)
@@ -48,4 +37,3 @@ namespace appl {
} \ } \
} while (0) } while (0)
#endif

View File

@@ -0,0 +1,140 @@
/** @file
* @author Edouard DUPIN
* @copyright 2019, Edouard DUPIN, all right reserved
* @license MPL v2.0 (see license file)
*/
#include <etk/types.hpp>
#include <ewol/ewol.hpp>
#include <gale/context/commandLine.hpp>
#include <appl/debug.hpp>
#include <appl/Windows.hpp>
#include <ewol/object/Object.hpp>
#include <ewol/widget/Manager.hpp>
#include <ewol/context/Context.hpp>
#include <audio/river/widget/TemporalViewer.hpp>
#include <etk/theme/theme.hpp>
static const etk::String configurationRiver =
"{\n"
" microphone:{\n"
" io:'input',\n"
" map-on:{\n"
" interface:'auto',\n"
" name:'default',\n"
" },\n"
" frequency:0,\n"
" channel-map:['front-left', 'front-right'],\n"
" type:'auto',\n"
" nb-chunk:1024\n"
" }\n"
"}\n";
class MainApplication : public ewol::context::Application {
public:
virtual void onCreate(ewol::Context& _context) override {
APPL_INFO(" == > CREATE ... (START) [" << gale::getBoardType() << "] (" << gale::getCompilationMode() << ") (BEGIN)");
for( int32_t iii=0 ; iii<_context.getCmd().size(); iii++) {
etk::String tmpppp = _context.getCmd().get(iii);
if ( tmpppp == "-h"
|| tmpppp == "--help") {
//APPL_PRINT(" --ctags=xxx c-flags-file-name" );
exit(0);
}
}
etk::theme::setName("COLOR", "color/white/");
_context.setSize(vec2(800, 600));
_context.setTitle("edn");
// select internal data for font ...
_context.getFontDefault().setUseExternal(true);
#ifdef __TARGET_OS__Android
_context.getFontDefault().set("FreeSerif", 19);
#else
_context.getFontDefault().set("FreeSerif;DejaVuSansMono",14);
#endif
// set the application icon ...
//_context.setIcon("DATA:///icon.png");
APPL_INFO("==> CREATE ... (END)");
}
void onStart(ewol::Context& _context) override {
APPL_INFO("==> START ... (BEGIN)");
// init internal global value
audio::river::initString(configurationRiver);
_context.setSize(vec2(800, 600));
// select internal data for font ...
_context.getFontDefault().setUseExternal(true);
_context.getFontDefault().setSize(19);
audio::river::widget::TemporalViewer::createManagerWidget(_context.getWidgetManager());
ememory::SharedPtr<ewol::widget::Windows> basicWindows = appl::Windows::create();
if (basicWindows == null) {
APPL_ERROR("Can not allocate the basic windows");
_context.exit(-1);
return;
}
// create the specific windows
_context.setWindows(basicWindows);
// add files
APPL_INFO("show list of files : ");
/*
for( int32_t iii=0 ; iii<_context.getCmd().size(); iii++) {
etk::String tmpppp = _context.getCmd().get(iii);
if (tmpppp.startWith("--ctags=") == true) {
etk::String name = tmpppp.extract(8);
APPL_INFO("Load ctag file : \"" << name << "\"" );
appl::setCtagsFileName(name);
} else if ( tmpppp == "-h"
|| tmpppp == "--help") {
// nothing to do ...
} else {
etk::Path file(tmpppp);
if (etk::path::isFile(file) == true) {
APPL_INFO("need load file : \"" << file << "\"" );
m_bufferManager->open(file);
} else if (etk::path::isDirectory(file) == true) {
etk::Vector<etk::Path> listOfFiles = etk::path::list(file, etk::path::LIST_FILE);
for (auto &it: listOfFiles) {
if (etk::path::isFile(it) == true) {
APPL_INFO("need load file : \"" << it << "\"" );
m_bufferManager->open(it);
}
}
}
}
}
*/
APPL_INFO("==> START ... (END)");
return;
}
void onStop(ewol::Context& _context) override {
APPL_INFO("==> STOP ... (START)");
APPL_INFO("==> STOP ... (END)");
}
void onKillDemand(ewol::Context& _context) override {
APPL_INFO("==> User demand kill ... (START)");
_context.exit(0);
APPL_INFO("==> User demand kill ... (END)");
}
};
/**
* @brief Main of the program (This can be set in every case, but it is not used in Andoid...).
* @param std IO
* @return std IO
*/
int main(int _argc, const char *_argv[]) {
// second possibility
return ewol::run(ETK_NEW(MainApplication), _argc, _argv);
}

View File

@@ -0,0 +1,24 @@
{
"type":"BINARY",
"sub-type":"TEST",
"group-id":"com.atria-soft",
"description":"Read some data",
"license":"MPL-2",
"license-file":"file://../../LICENSE",
"maintainer":"file://../../authors.txt",
"author":"file://../../authors.txt",
"version":"file://../../version.txt",
"code-quality":"MEDIUM",
"source": [
"read.cpp"
],
"compilation-version": {
"c++": 2017
},
"dependency": [
"audio-river",
"test-debug",
"etk"
]
}

170
sample/read/read.cpp Normal file
View File

@@ -0,0 +1,170 @@
/** @file
* @author Edouard DUPIN
* @copyright 2015, Edouard DUPIN, all right reserved
* @license MPL v2.0 (see license file)
*/
//! [audio_river_sample_read_all]
//! [audio_river_sample_include]
#include <audio/river/river.hpp>
#include <audio/river/Manager.hpp>
#include <audio/river/Interface.hpp>
//! [audio_river_sample_include]
#include <etk/etk.hpp>
#include <ethread/Thread.hpp>
#include <ethread/tools.hpp>
#include <test-debug/debug.hpp>
#include <etk/uri/uri.hpp>
//! [audio_river_sample_read_config_file]
static const etk::String configurationRiver =
"{\n"
" microphone:{\n"
" io:'input',\n"
" map-on:{\n"
" interface:'auto',\n"
" name:'default',\n"
//" timestamp-mode:'trigered',\n"
" },\n"
" frequency:0,\n"
" channel-map:['front-left', 'front-right'],\n"
" type:'auto',\n"
" nb-chunk:1024\n"
" }\n"
"}\n";
//! [audio_river_sample_read_config_file]
//! [audio_river_sample_callback_implement]
void onDataReceived(const void* _data,
const audio::Time& _time,
size_t _nbChunk,
enum audio::format _format,
uint32_t _frequency,
const etk::Vector<audio::channel>& _map,
ememory::SharedPtr<etk::io::Interface> _fileIO) {
if ( _format != audio::format_int16
&& _format != audio::format_float) {
TEST_ERROR("Call wrong type ... (need int16_t.float)");
return;
}
//! [audio_river_sample_callback_implement]
if (_fileIO->isOpen() == false) {
if (_format != audio::format_int16) {
// get the curent power of the signal.
const int16_t* data = static_cast<const int16_t*>(_data);
int64_t value = 0;
for (size_t iii=0; iii<_nbChunk*_map.size(); ++iii) {
value += etk::abs(data[iii]);
}
value /= (_nbChunk*_map.size());
TEST_PRINT("Get data ... average=" << int32_t(value));
} else {
// get the curent power of the signal.
const float* data = static_cast<const float*>(_data);
float value = 0;
for (size_t iii=0; iii<_nbChunk*_map.size(); ++iii) {
value += etk::abs(data[iii]);
}
value /= (_nbChunk*_map.size());
TEST_PRINT("Get data ... average=" << float(value));
}
} else {
// just write data
TEST_VERBOSE("Get data ... chunks=" << _nbChunk << " time=" << _time);
_fileIO->write(_data, _map.size()*audio::getFormatBytes(_format), _nbChunk);
}
}
int main(int _argc, const char **_argv) {
//! [audio_river_sample_init]
// the only one init for etk:
etk::init(_argc, _argv);
//! [audio_river_sample_init]
// local parameter:
etk::String configFile;
etk::String ioName="microphone";
etk::Path outputFileName = "";
for (int32_t iii=0; iii<_argc ; ++iii) {
etk::String data = _argv[iii];
if ( data == "-h"
|| data == "--help") {
TEST_PRINT("Help : ");
TEST_PRINT(" --conf=xxx.json Input/output configuration");
TEST_PRINT(" --io=xxx name configuration input");
TEST_PRINT(" --file=yyy.raw File name to store data");
exit(0);
} else if (etk::start_with(data, "--conf=") == true) {
configFile = etk::String(data.begin()+7, data.end());
TEST_PRINT("Select config: " << configFile);
} else if (etk::start_with(data, "--io=") == true) {
ioName = etk::String(data.begin()+5, data.end());
TEST_PRINT("Select io: " << ioName);
} else if (etk::start_with(data, "--file=") == true) {
outputFileName = etk::String(data.begin()+7, data.end());
TEST_PRINT("Select output file name: " << outputFileName);
}
}
// initialize river interface
if (configFile == "") {
audio::river::initString(configurationRiver);
} else {
audio::river::init(configFile);
}
//! [audio_river_sample_get_interface]
// Create the River manager for tha application or part of the application.
ememory::SharedPtr<audio::river::Manager> manager = audio::river::Manager::create("river_sample_read");
//! [audio_river_sample_get_interface]
//! [audio_river_sample_create_read_interface]
// create interface:
ememory::SharedPtr<audio::river::Interface> interface;
//Get the generic input:
interface = manager->createInput(48000,
etk::Vector<audio::channel>(),
audio::format_int16,
ioName);
if(interface == null) {
TEST_ERROR("null interface");
return -1;
}
//! [audio_river_sample_create_read_interface]
ememory::SharedPtr<etk::io::Interface> fileIO = etk::uri::get(outputFileName);
// open output file if needed:
if (outputFileName.isEmpty() == false) {
fileIO->open(etk::io::OpenMode::Write);
}
//! [audio_river_sample_set_callback]
// set callback mode ...
interface->setInputCallback([&](const void* _data,
const audio::Time& _time,
size_t _nbChunk,
enum audio::format _format,
uint32_t _frequency,
const etk::Vector<audio::channel>& _map) {
onDataReceived(_data, _time, _nbChunk, _format, _frequency, _map, fileIO);
});
//! [audio_river_sample_set_callback]
//! [audio_river_sample_read_start_stop]
// start the stream
interface->start();
// wait 10 second ...
ethread::sleepMilliSeconds(1000*(10));
// stop the stream
interface->stop();
//! [audio_river_sample_read_start_stop]
//! [audio_river_sample_read_reset]
// remove interface and manager.
interface.reset();
manager.reset();
//! [audio_river_sample_read_reset]
// close the output file
if (outputFileName != "") {
fileIO->close();
}
return 0;
}
//! [audio_river_sample_read_all]

View File

@@ -0,0 +1,24 @@
{
"type":"BINARY",
"sub-type":"TEST",
"group-id":"com.atria-soft",
"description":"Write some data",
"license":"MPL-2",
"license-file":"file://../../LICENSE",
"maintainer":"file://../../authors.txt",
"author":"file://../../authors.txt",
"version":"file://../../version.txt",
"code-quality":"MEDIUM",
"source": [
"write.cpp"
],
"compilation-version": {
"c++": 2017
},
"dependency": [
"audio-river",
"test-debug",
"etk"
]
}

119
sample/write/write.cpp Normal file
View File

@@ -0,0 +1,119 @@
/** @file
* @author Edouard DUPIN
* @copyright 2015, Edouard DUPIN, all right reserved
* @license MPL v2.0 (see license file)
*/
//! [audio_river_sample_write_all]
#include <audio/river/river.hpp>
#include <audio/river/Manager.hpp>
#include <audio/river/Interface.hpp>
#include <etk/etk.hpp>
#include <ethread/Thread.hpp>
#include <ethread/tools.hpp>
#include <test-debug/debug.hpp>
//! [audio_river_sample_write_config_file]
static const etk::String configurationRiver =
"{\n"
" speaker:{\n"
" io:'output',\n"
" map-on:{\n"
" interface:'alsa',\n"
" name:'hw:0,3',\n"
" },\n"
" frequency:48000,\n"
//" channel-map:['front-left', 'front-right', 'rear-left', 'rear-right'],\n"
" channel-map:['front-left', 'front-right'],\n"
" type:'int16',\n"
" nb-chunk:1024,\n"
" volume-name:'MASTER'\n"
" }\n"
"}\n";
//! [audio_river_sample_write_config_file]
static const int32_t nbChannelMax=8;
//! [audio_river_sample_callback_implement]
void onDataNeeded(void* _data,
const audio::Time& _time,
size_t _nbChunk,
enum audio::format _format,
uint32_t _sampleRate,
const etk::Vector<audio::channel>& _map) {
static double phase[8] = {0,0,0,0,0,0,0,0};
if (_format != audio::format_int16) {
TEST_ERROR("Call wrong type ... (need int16_t)");
}
//TEST_VERBOSE("Map " << _map);
int16_t* data = static_cast<int16_t*>(_data);
double baseCycle = 2.0*M_PI/double(48000) * double(440);
for (int32_t iii=0; iii<_nbChunk; iii++) {
for (int32_t jjj=0; jjj<_map.size(); jjj++) {
data[_map.size()*iii+jjj] = cos(phase[jjj]) * 30000;
phase[jjj] += baseCycle*jjj;
if (phase[jjj] >= 2*M_PI) {
phase[jjj] -= 2*M_PI;
}
}
}
}
//! [audio_river_sample_callback_implement]
int main(int _argc, const char **_argv) {
// the only one init for etk:
etk::init(_argc, _argv);
for (int32_t iii=0; iii<_argc ; ++iii) {
etk::String data = _argv[iii];
if ( data == "-h"
|| data == "--help") {
TEST_PRINT("Help:");
TEST_PRINT(" ./xxx ---");
exit(0);
}
}
// initialize river interface
audio::river::initString(configurationRiver);
// Create the River manager for tha application or part of the application.
ememory::SharedPtr<audio::river::Manager> manager = audio::river::Manager::create("river_sample_read");
//! [audio_river_sample_create_write_interface]
// create interface:
ememory::SharedPtr<audio::river::Interface> interface;
//Get the generic input:
interface = manager->createOutput(48000,
etk::Vector<audio::channel>(),
audio::format_int16,
"speaker");
if(interface == null) {
TEST_ERROR("null interface");
return -1;
}
//! [audio_river_sample_create_write_interface]
//! [audio_river_sample_set_callback]
// set callback mode ...
interface->setOutputCallback([=](void* _data,
const audio::Time& _time,
size_t _nbChunk,
enum audio::format _format,
uint32_t _frequency,
const etk::Vector<audio::channel>& _map) {
onDataNeeded(_data, _time, _nbChunk, _format, _frequency, _map);
});
//! [audio_river_sample_set_callback]
// start the stream
interface->start();
// wait 10 second ...
ethread::sleepMilliSeconds(1000*(10));
// stop the stream
interface->stop();
// remove interface and manager.
interface.reset();
manager.reset();
return 0;
}
//! [audio_river_sample_write_all]

View File

@@ -1,15 +0,0 @@
/**
* @author Edouard DUPIN
*
* @copyright 2010, Edouard DUPIN, all right reserved
*
* @license GPL v3 (see license file)
*/
#include "debug.h"
int32_t appl::getLogId() {
static int32_t g_val = etk::log::registerInstance("test");
return g_val;
}

View File

@@ -1,103 +1,54 @@
/** @file /** @file
* @author Edouard DUPIN * @author Edouard DUPIN
* @copyright 2015, Edouard DUPIN, all right reserved * @copyright 2015, Edouard DUPIN, all right reserved
* @license APACHE v2.0 (see license file) * @license MPL v2.0 (see license file)
*/ */
#include "debug.h" #include <test-debug/debug.hpp>
#include <river/Manager.h>
#include <river/Interface.h>
#include <gtest/gtest.h>
#include <etk/os/FSNode.h>
#include <math.h>
#include <sstream>
#include <unistd.h>
#include <etk/thread.h>
#include "testAEC.h"
#include "testEchoDelay.h"
#include "testFormat.h"
#include "testMuxer.h"
#include "testPlaybackCallback.h"
#include "testPlaybackWrite.h"
#include "testRecordCallback.h"
#include "testRecordRead.h"
#include "testVolume.h"
#define TEST_SAVE_FILE_MACRO(type,fileName,dataPointer,nbElement) \
#undef __class__ do { \
#define __class__ "test" static FILE *pointerOnFile = null; \
static bool errorOpen = false; \
if (pointerOnFile == null) { \
TEST_WARNING("open file '" << fileName << "' type=" << #type); \
pointerOnFile = fopen(fileName,"w"); \
if ( errorOpen == false \
&& pointerOnFile == null) { \
TEST_ERROR("ERROR OPEN file ... '" << fileName << "' type=" << #type); \
errorOpen=true; \
} \
} \
if (pointerOnFile != null) { \
fwrite((dataPointer), sizeof(type), (nbElement), pointerOnFile); \
/* fflush(pointerOnFile);*/ \
} \
}while(0)
#include <audio/river/river.hpp>
#include <audio/river/Manager.hpp>
/* #include <audio/river/Interface.hpp>
static void threadVolume() { #include <etest/etest.hpp>
std11::shared_ptr<river::Manager> manager; #include <etk/etk.hpp>
manager = river::Manager::create("testApplication"); extern "C" {
std11::shared_ptr<testCallbackVolume> process = std11::make_shared<testCallbackVolume>(manager); #include <math.h>
usleep(100000);
process->run();
process.reset();
usleep(500000);
} }
TEST(TestALL, testInputCallBackMicClean) { #include <ethread/Thread.hpp>
std11::shared_ptr<river::Manager> manager;
manager = river::Manager::create("testApplication");
std11::thread tmpThread(std11::bind(&threadVolume)); int main(int _argc, const char** _argv) {
usleep(30000); etest::init(_argc, _argv);
APPL_INFO("test input (callback mode)"); for (int32_t iii=0; iii<_argc ; ++iii) {
std11::shared_ptr<testInCallback> process = std11::make_shared<testInCallback>(manager, "microphone-clean"); etk::String data = _argv[iii];
process->run(); if ( data == "-h"
usleep(100000);
process.reset();
usleep(500000);
tmpThread.join();
}
*/
int main(int argc, char **argv) {
// init Google test :
::testing::InitGoogleTest(&argc, const_cast<char **>(argv));
// the only one init for etk:
etk::log::setLevel(etk::log::logLevelNone);
for (int32_t iii=0; iii<argc ; ++iii) {
std::string data = argv[iii];
if (data == "-l0") {
etk::log::setLevel(etk::log::logLevelNone);
} else if (data == "-l1") {
etk::log::setLevel(etk::log::logLevelCritical);
} else if (data == "-l2") {
etk::log::setLevel(etk::log::logLevelError);
} else if (data == "-l3") {
etk::log::setLevel(etk::log::logLevelWarning);
} else if (data == "-l4") {
etk::log::setLevel(etk::log::logLevelInfo);
} else if (data == "-l5") {
etk::log::setLevel(etk::log::logLevelDebug);
} else if (data == "-l6") {
etk::log::setLevel(etk::log::logLevelVerbose);
} else if ( data == "-h"
|| data == "--help") { || data == "--help") {
APPL_INFO("Help : "); TEST_PRINT("Help : ");
APPL_INFO(" ./xxx [options]"); TEST_PRINT(" ./xxx ---");
APPL_INFO(" -l0: debug None");
APPL_INFO(" -l1: debug Critical");
APPL_INFO(" -l2: debug Error");
APPL_INFO(" -l3: debug Warning");
APPL_INFO(" -l4: debug Info");
APPL_INFO(" -l5: debug Debug");
APPL_INFO(" -l6: debug Verbose");
APPL_INFO(" -h/--help: this help");
exit(0); exit(0);
} }
} }
etk::setArgZero(argv[0]);
etk::initDefaultFolder("exml_test");
return RUN_ALL_TESTS(); return RUN_ALL_TESTS();
} }

227
test/testAEC.cpp Normal file
View File

@@ -0,0 +1,227 @@
/** @file
* @author Edouard DUPIN
* @copyright 2015, Edouard DUPIN, all right reserved
* @license MPL v2.0 (see license file)
*/
#include <test-debug/debug.hpp>
#include <audio/river/river.hpp>
#include <audio/river/Manager.hpp>
#include <audio/river/Interface.hpp>
#include <etest/etest.hpp>
#include <etk/etk.hpp>
extern "C" {
#include <math.h>
}
#include <ethread/Thread.hpp>
#include <ethread/tools.hpp>
namespace river_test_aec {
class Linker {
private:
ememory::SharedPtr<audio::river::Manager> m_manager;
ememory::SharedPtr<audio::river::Interface> m_interfaceOut;
ememory::SharedPtr<audio::river::Interface> m_interfaceIn;
audio::drain::CircularBuffer m_buffer;
public:
Linker(ememory::SharedPtr<audio::river::Manager> _manager, const etk::String& _input, const etk::String& _output) :
m_manager(_manager) {
//Set stereo output:
etk::Vector<audio::channel> channelMap;
if (false) { //"speaker" == _output) {
channelMap.pushBack(audio::channel_frontCenter);
} else {
channelMap.pushBack(audio::channel_frontLeft);
channelMap.pushBack(audio::channel_frontRight);
}
m_buffer.setCapacity(echrono::milliseconds(2000), sizeof(int16_t)*channelMap.size(), 48000);
m_interfaceOut = m_manager->createOutput(48000,
channelMap,
audio::format_int16,
_output);
if(m_interfaceOut == null) {
TEST_ERROR("null interface");
return;
}
// set callback mode ...
m_interfaceOut->setOutputCallback([=](void* _data,
const audio::Time& _time,
size_t _nbChunk,
enum audio::format _format,
uint32_t _frequency,
const etk::Vector<audio::channel>& _map) {
onDataNeeded(_data, _time, _nbChunk, _format, _frequency, _map);
});
m_interfaceOut->addVolumeGroup("FLOW");
if ("speaker" == _output) {
m_interfaceOut->setParameter("volume", "FLOW", "0dB");
}
m_interfaceIn = m_manager->createInput(48000,
channelMap,
audio::format_int16,
_input);
if(m_interfaceIn == null) {
TEST_ERROR("null interface");
return;
}
// set callback mode ...
m_interfaceIn->setInputCallback([=](const void* _data,
const audio::Time& _time,
size_t _nbChunk,
enum audio::format _format,
uint32_t _frequency,
const etk::Vector<audio::channel>& _map) {
onDataReceived(_data, _time, _nbChunk, _format, _frequency, _map);
});
}
void onDataNeeded(void* _data,
const audio::Time& _time,
size_t _nbChunk,
enum audio::format _format,
uint32_t _frequency,
const etk::Vector<audio::channel>& _map) {
if (_format != audio::format_int16) {
TEST_ERROR("call wrong type ... (need int16_t)");
}
m_buffer.read(_data, _nbChunk);
}
void onDataReceived(const void* _data,
const audio::Time& _time,
size_t _nbChunk,
enum audio::format _format,
uint32_t _frequency,
const etk::Vector<audio::channel>& _map) {
if (_format != audio::format_int16) {
TEST_ERROR("call wrong type ... (need int16_t)");
}
m_buffer.write(_data, _nbChunk);
}
void start() {
if(m_interfaceIn == null) {
TEST_ERROR("null interface");
return;
}
if(m_interfaceOut == null) {
TEST_ERROR("null interface");
return;
}
m_interfaceOut->start();
m_interfaceIn->start();
}
void stop() {
if(m_interfaceIn == null) {
TEST_ERROR("null interface");
return;
}
if(m_interfaceOut == null) {
TEST_ERROR("null interface");
return;
}
m_manager->generateDotAll("activeProcess.dot");
m_interfaceOut->stop();
m_interfaceIn->stop();
}
};
static const etk::String configurationRiver =
"{\n"
" speaker:{\n"
" io:'output',\n"
" map-on:{\n"
" interface:'auto',\n"
" name:'hw:0,0',\n"
" timestamp-mode:'trigered',\n"
" },\n"
" frequency:0,\n"
" channel-map:['front-left', 'front-right'],\n"
" type:'auto',\n"
" nb-chunk:1024,\n"
" },\n"
" microphone:{\n"
" io:'input',\n"
" map-on:{\n"
" interface:'auto',\n"
" name:'hw:0,0',\n"
" timestamp-mode:'trigered',\n"
" },\n"
" frequency:0,\n"
" channel-map:['front-left', 'front-right'],\n"
" type:'auto',\n"
" nb-chunk:1024\n"
" },\n"
" speaker-test:{\n"
" io:'output',\n"
" map-on:{\n"
" interface:'alsa',\n"
" name:'hw:2,0',\n"
" timestamp-mode:'trigered',\n"
" },\n"
//" group:'groupSynchro',\n"
" frequency:0,\n"
" channel-map:['front-left', 'front-right'],\n"
" type:'auto',\n"
" nb-chunk:1024\n"
" },\n"
" microphone-test:{\n"
" io:'input',\n"
" map-on:{\n"
" interface:'alsa',\n"
" name:'hw:2,0',\n"
" timestamp-mode:'trigered',\n"
" },\n"
//" group:'groupSynchro',\n"
" frequency:0,\n"
" channel-map:['front-center'],\n"
" type:'auto',\n"
" nb-chunk:1024\n"
" },\n"
" # virtual Nodes :\n"
" microphone-clean:{\n"
" io:'aec',\n"
" map-on-microphone:{\n"
" io:'input',\n"
" map-on:'microphone-test'\n"
" },\n"
" map-on-feedback:{\n"
" io:'feedback',\n"
" map-on:'speaker-test',\n"
" },\n"
" frequency:48000,\n"
" channel-map:[\n"
" 'front-left', 'front-right'\n"
//" 'front-center'\n"
" ],\n"
" nb-chunk:1024,\n"
" type:'int16',\n"
" algo:'river-remover',\n"
" algo-mode:'cutter',\n"
" feedback-delay:10000,\n"
" mux-demux-type:'int16'\n"
" }\n"
"}\n";
TEST(TestUser, testAECManually) {
audio::river::initString(configurationRiver);
ememory::SharedPtr<audio::river::Manager> manager;
manager = audio::river::Manager::create("testApplication");
ememory::SharedPtr<Linker> processLink1 = ememory::makeShared<Linker>(manager, "microphone-clean", "speaker");
ememory::SharedPtr<Linker> processLink2 = ememory::makeShared<Linker>(manager, "microphone", "speaker-test");
processLink1->start();
processLink2->start();
ethread::sleepMilliSeconds(1000*(20));
processLink1->stop();
processLink2->stop();
processLink1.reset();
processLink2.reset();
manager.reset();
audio::river::unInit();
}
};

View File

@@ -1,20 +0,0 @@
/** @file
* @author Edouard DUPIN
* @copyright 2015, Edouard DUPIN, all right reserved
* @license APACHE v2.0 (see license file)
*/
#ifndef __RIVER_TEST_AEC_H__
#define __RIVER_TEST_AEC_H__
#undef __class__
#define __class__ "test_aec"
namespace river_test_aec {
};
#undef __class__
#define __class__ nullptr
#endif

436
test/testEchoDelay.cpp Normal file
View File

@@ -0,0 +1,436 @@
/** @file
* @author Edouard DUPIN
* @copyright 2015, Edouard DUPIN, all right reserved
* @license MPL v2.0 (see license file)
*/
#include <test-debug/debug.hpp>
#include <audio/river/river.hpp>
#include <audio/river/Manager.hpp>
#include <audio/river/Interface.hpp>
#include <etest/etest.hpp>
#include <etk/etk.hpp>
extern "C" {
#include <math.h>
}
#include <ethread/Thread.hpp>
#include <ethread/tools.hpp>
namespace river_test_echo_delay {
class TestClass {
private:
ememory::SharedPtr<audio::river::Manager> m_manager;
ememory::SharedPtr<audio::river::Interface> m_interfaceOut;
ememory::SharedPtr<audio::river::Interface> m_interfaceIn;
ememory::SharedPtr<audio::river::Interface> m_interfaceFB;
double m_phase;
double m_freq;
int32_t m_nextSampleCount;
echrono::milliseconds m_delayBetweenEvent;
audio::Time m_nextTick;
audio::Time m_currentTick;
int32_t m_stateFB;
int32_t m_stateMic;
etk::Vector<uint64_t> m_delayListMic;
bool m_estimateVolumeInput;
int16_t m_volumeInputMax;
int16_t m_volumeInputMin;
float m_gain;
public:
TestClass(ememory::SharedPtr<audio::river::Manager> _manager) :
m_manager(_manager),
m_phase(0),
m_freq(400),
m_nextSampleCount(0),
m_delayBetweenEvent(400),
m_stateFB(3),
m_stateMic(0),
m_estimateVolumeInput(true),
m_gain(-40) {
//Set stereo output:
etk::Vector<audio::channel> channelMap;
m_interfaceOut = m_manager->createOutput(48000,
channelMap,
audio::format_int16,
"speaker");
if(m_interfaceOut == null) {
TEST_ERROR("null interface");
return;
}
// set callback mode ...
m_interfaceOut->setOutputCallback([=](void* _data,
const audio::Time& _time,
size_t _nbChunk,
enum audio::format _format,
uint32_t _frequency,
const etk::Vector<audio::channel>& _map) {
onDataNeeded(_data, _time, _nbChunk, _format, _frequency, _map);
});
m_interfaceOut->addVolumeGroup("FLOW");
m_interfaceOut->setParameter("volume", "FLOW", etk::toString(m_gain) + "dB");
m_interfaceIn = m_manager->createInput(48000,
channelMap,
audio::format_int16,
"microphone");
if(m_interfaceIn == null) {
TEST_ERROR("null interface");
return;
}
// set callback mode ...
m_interfaceIn->setInputCallback([=](const void* _data,
const audio::Time& _time,
size_t _nbChunk,
enum audio::format _format,
uint32_t _frequency,
const etk::Vector<audio::channel>& _map) {
onDataReceived(_data, _time, _nbChunk, _format, _frequency, _map);
});
m_interfaceFB = m_manager->createFeedback(48000,
channelMap,
audio::format_int16,
"speaker");
if(m_interfaceFB == null) {
TEST_ERROR("null interface");
return;
}
// set callback mode ...
m_interfaceFB->setInputCallback([=](const void* _data,
const audio::Time& _time,
size_t _nbChunk,
enum audio::format _format,
uint32_t _frequency,
const etk::Vector<audio::channel>& _map) {
onDataReceivedFeedBack(_data, _time, _nbChunk, _format, _frequency, _map);
});
m_manager->generateDotAll("activeProcess.dot");
}
void onDataNeeded(void* _data,
const audio::Time& _time,
size_t _nbChunk,
enum audio::format _format,
uint32_t _frequency,
const etk::Vector<audio::channel>& _map) {
int16_t* data = static_cast<int16_t*>(_data);
double baseCycle = 2.0*M_PI/(double)48000 * m_freq;
if (m_estimateVolumeInput == true) {
for (int32_t iii=0; iii<_nbChunk; iii++) {
for (int32_t jjj=0; jjj<_map.size(); jjj++) {
data[_map.size()*iii+jjj] = sin(m_phase) * 30000;
}
m_phase += baseCycle;
if (m_phase >= 2*M_PI) {
m_phase -= 2*M_PI;
}
}
} else {
if (_time == audio::Time()) {
for (int32_t iii=0; iii<_nbChunk; iii++) {
for (int32_t jjj=0; jjj<_map.size(); jjj++) {
data[_map.size()*iii+jjj] = 0;
}
}
return;
}
if (m_nextTick == audio::Time()) {
m_nextTick = _time + m_delayBetweenEvent;
m_nextSampleCount = m_delayBetweenEvent.get()*int64_t(_frequency)/1000;
m_phase = -1;
}
//TEST_INFO("sample : " << m_nextSampleCount);
for (int32_t iii=0; iii<_nbChunk; iii++) {
if (m_nextSampleCount > 0) {
m_nextSampleCount--;
} else {
m_phase = 0;
m_nextSampleCount = m_delayBetweenEvent.get()*int64_t(_frequency)/1000;
m_currentTick = m_nextTick;
m_nextTick += m_delayBetweenEvent;
}
if (m_phase >= 0) {
for (int32_t jjj=0; jjj<_map.size(); jjj++) {
data[_map.size()*iii+jjj] = sin(m_phase) * 30000;
}
double newPhase = m_phase+baseCycle;
if ( m_phase < M_PI
&& newPhase >= M_PI) {
// the zero crossing position :
m_currentTick = getInterpolateTime(_time, iii, sin(m_phase) * 30000, sin(newPhase) * 30000, _frequency);
// start detection ...
m_stateFB = 0;
m_stateMic = 0;
TEST_WARNING("Time Pulse zero crossing: " << m_currentTick << " id=" << iii);
}
m_phase = newPhase;
if (m_phase >= 2*M_PI) {
m_phase = -1;
//m_freq += 50.0;
if (m_freq>20000.0) {
m_freq = 400.0;
}
}
} else {
for (int32_t jjj=0; jjj<_map.size(); jjj++) {
data[_map.size()*iii+jjj] = 0;
}
}
}
}
}
audio::Time getInterpolateTime(audio::Time _time, int32_t _pos, int16_t _val1, int16_t _val2, uint32_t _frequency) {
if (_val1 == 0) {
return _time + audio::Duration(0, int64_t(_pos)*1000000000LL/int64_t(_frequency));
} else if (_val2 == 0) {
return _time + audio::Duration(0, int64_t(_pos+1)*1000000000LL/int64_t(_frequency));
}
double xxx = double(-_val1) / double(_val2 - _val1);
TEST_VERBOSE("deltaPos:" << xxx);
return _time + audio::Duration(0, int64_t((double(_pos)+xxx)*1000000000.0)/int64_t(_frequency));
}
void onDataReceivedFeedBack(const void* _data,
const audio::Time& _time,
size_t _nbChunk,
enum audio::format _format,
uint32_t _frequency,
const etk::Vector<audio::channel>& _map) {
if (_format != audio::format_int16) {
TEST_ERROR("call wrong type ... (need int16_t)");
}
//TEST_SAVE_FILE_MACRO(int16_t, "REC_FeedBack.raw", _data, _nbChunk*_map.size());
if (m_estimateVolumeInput == true) {
// nothing to do ...
} else {
const int16_t* data = static_cast<const int16_t*>(_data);
// Detect Zero crossing after a max/min ...
for (size_t iii=0; iii<_nbChunk; ++iii) {
//for (size_t jjj=0; jjj<_map.size(); ++jjj) {
size_t jjj=0; {
if (m_stateFB == 0) {
if (data[iii*_map.size() + jjj] > INT16_MAX/5) {
m_stateFB = 1;
TEST_VERBOSE("FB: detect Normal " << iii);
} else if (data[iii*_map.size() + jjj] < -INT16_MAX/5) {
m_stateFB = 2;
TEST_VERBOSE("FB: detect inverse " << iii);
}
} else if (m_stateFB == 1) {
// normale phase
if (data[iii*_map.size() + jjj] <= 0) {
// detect inversion of signe ...
m_stateFB = 3;
audio::Time time = getInterpolateTime(_time, iii-1, data[(iii-1)*_map.size() + jjj], data[iii*_map.size() + jjj], _frequency);
TEST_VERBOSE("FB: 1 position -1: " << iii-1 << " " << data[(iii-1)*_map.size() + jjj]);
TEST_VERBOSE("FB: 1 position 0: " << iii << " " << data[iii*_map.size() + jjj]);
TEST_WARNING("FB: 1 time detected: " << time << " delay = " << float((time-m_currentTick).get())/1000.0f << "µs");
}
} else if (m_stateFB == 2) {
// inverse phase
if (data[iii*_map.size() + jjj] >= 0) {
// detect inversion of signe ...
m_stateFB = 3;
audio::Time time = getInterpolateTime(_time, iii-1, data[(iii-1)*_map.size() + jjj], data[iii*_map.size() + jjj], _frequency);
TEST_VERBOSE("FB: 2 position -1: " << iii-1 << " " << data[(iii-1)*_map.size() + jjj]);
TEST_VERBOSE("FB: 2 position 0: " << iii << " " << data[iii*_map.size() + jjj]);
TEST_WARNING("FB: 2 time detected: " << time << " delay = " << float((time-m_currentTick).get())/1000.0f << "µs");
}
} else if (m_stateFB == 3) {
// TODO : Detect the pic ...
// do nothing ...
}
}
}
}
}
void onDataReceived(const void* _data,
const audio::Time& _time,
size_t _nbChunk,
enum audio::format _format,
uint32_t _frequency,
const etk::Vector<audio::channel>& _map) {
if (_format != audio::format_int16) {
TEST_ERROR("call wrong type ... (need int16_t)");
}
//TEST_SAVE_FILE_MACRO(int16_t, "REC_Microphone.raw", _data, _nbChunk*_map.size());
const int16_t* data = static_cast<const int16_t*>(_data);
if (m_estimateVolumeInput == true) {
m_stateMic ++;
const int16_t* data = static_cast<const int16_t*>(_data);
if (m_stateMic <= 40) {
for (size_t iii=0; iii<_nbChunk*_map.size(); ++iii) {
//TEST_INFO("value=" << data[iii]);
m_volumeInputMax = etk::max(int16_t(data[iii]), m_volumeInputMax);
m_volumeInputMin = etk::min(int16_t(data[iii]), m_volumeInputMin);
}
if (m_stateMic == 40) {
m_volumeInputMax *= 2;
m_volumeInputMin *= 2;
}
} else if (m_stateMic <= 10000) {
int16_t valueMax = 0;
int16_t valueMin = 0;
for (size_t iii=0; iii<_nbChunk*_map.size(); ++iii) {
//TEST_INFO("value=" << data[iii]);
valueMax = etk::max(int16_t(data[iii]), valueMax);
valueMin = etk::min(int16_t(data[iii]), valueMin);
}
if ( valueMax > m_volumeInputMax
&& valueMin < m_volumeInputMin
&& ( m_gain == 0.0
|| ( valueMax > INT16_MAX*2/3
&& valueMin < INT16_MIN*2/3
)
)
) {
m_gain += 3.0f;
m_gain = etk::min(m_gain, 0.0f);
m_interfaceOut->setParameter("volume", "FLOW", etk::toString(m_gain) + "dB");
TEST_INFO("Set detection volume : " << m_gain << " m_stateMic=" << m_stateMic);
m_stateMic = 3;
m_phase = -1;
m_estimateVolumeInput = false;
return;
} else {
if (m_stateMic%2 == 0) {
if (m_gain == 0.0f) {
TEST_CRITICAL("Can not find the basicVolume ...");
}
// just update volume
m_gain += 1.0f;
m_gain = etk::min(m_gain, 0.0f);
m_interfaceOut->setParameter("volume", "FLOW", etk::toString(m_gain) + "dB");
}
}
}
} else {
// Detect Zero crossing after a max/min ...
for (size_t iii=0; iii<_nbChunk; ++iii) {
//for (size_t jjj=0; jjj<_map.size(); ++jjj) {
size_t jjj=0; {
if (m_stateMic == 0) {
if (data[iii*_map.size() + jjj] > m_volumeInputMax) {
m_stateMic = 1;
TEST_VERBOSE("Mic: detect Normal " << iii);
} else if (data[iii*_map.size() + jjj] < m_volumeInputMin) {
m_stateMic = 2;
TEST_VERBOSE("Mic: detect inverse " << iii);
}
} else if (m_stateMic == 1) {
// normale phase
if (data[iii*_map.size() + jjj] <= 0) {
// detect inversion of signe ...
m_stateMic = 3;
audio::Time time = getInterpolateTime(_time, iii-1, data[(iii-1)*_map.size() + jjj], data[iii*_map.size() + jjj], _frequency);
TEST_VERBOSE("MIC: 1 position -1: " << iii-1 << " " << data[(iii-1)*_map.size() + jjj]);
TEST_VERBOSE("MIC: 1 position 0: " << iii << " " << data[iii*_map.size() + jjj]);
audio::Duration delay = time-m_currentTick;
int32_t sampleDalay = (delay.get()*_frequency)/1000000000LL;
TEST_WARNING("MIC: 1 time detected: " << time << " delay = " << float(delay.get())/1000.0f << "µs samples=" << sampleDalay);
m_delayListMic.pushBack(delay.get());
}
} else if (m_stateMic == 2) {
// inverse phase
if (data[iii*_map.size() + jjj] >= 0) {
// detect inversion of signe ...
m_stateMic = 3;
audio::Time time = getInterpolateTime(_time, iii-1, data[(iii-1)*_map.size() + jjj], data[iii*_map.size() + jjj], _frequency);
TEST_VERBOSE("MIC: 2 position -1: " << iii-1 << " " << data[(iii-1)*_map.size() + jjj]);
TEST_VERBOSE("MIC: 2 position 0: " << iii << " " << data[iii*_map.size() + jjj]);
audio::Duration delay = time-m_currentTick;
int32_t sampleDalay = (delay.get()*_frequency)/1000000000LL;
TEST_WARNING("MIC: 2 time detected: " << time << " delay = " << float(delay.get())/1000.0f << "µs samples=" << sampleDalay);
m_delayListMic.pushBack(delay.get());
}
} else if (m_stateMic == 3) {
// TODO : Detect the pic ...
// do nothing ...
}
}
}
}
}
void run() {
if(m_interfaceIn == null) {
TEST_ERROR("null interface");
return;
}
if(m_interfaceOut == null) {
TEST_ERROR("null interface");
return;
}
if(m_interfaceFB == null) {
TEST_ERROR("null interface");
return;
}
m_interfaceOut->start();
m_interfaceIn->start();
//m_interfaceFB->start();
while (m_estimateVolumeInput == true) {
ethread::sleepMilliSeconds((10));
}
ethread::sleepMilliSeconds(1000*(10));
//m_interfaceFB->stop();
m_interfaceIn->stop();
m_interfaceOut->stop();
int64_t delayAverage = 0;
if (m_delayListMic.size() > 0) {
for (size_t iii=0; iii<m_delayListMic.size(); ++iii) {
delayAverage += m_delayListMic[iii];
}
delayAverage /= m_delayListMic.size();
}
int32_t sampleDalay = (delayAverage*48000)/1000000000LL;
TEST_ERROR("Average delay in ns : " << delayAverage << " nbSample=" << sampleDalay);
}
};
static const etk::String configurationRiver =
"{\n"
" speaker:{\n"
" io:'output',\n"
" map-on:{\n"
" interface:'auto',\n"
" name:'default',\n"
//" name:'hw:0,0',\n"
" timestamp-mode:'trigered',\n"
" },\n"
//" group:'groupSynchro',\n"
" frequency:0,\n"
" channel-map:['front-left', 'front-right'],\n"
" type:'auto',\n"
" nb-chunk:1024,\n"
" },\n"
" microphone:{\n"
" io:'input',\n"
" map-on:{\n"
" interface:'auto',\n"
" name:'default',\n"
//" name:'hw:0,0',\n"
" timestamp-mode:'trigered',\n"
" },\n"
//" group:'groupSynchro',\n"
" frequency:0,\n"
" channel-map:['front-left', 'front-right'],\n"
" type:'auto',\n"
" nb-chunk:1024\n"
" }\n"
"}\n";
TEST(TestTime, testDelay) {
audio::river::initString(configurationRiver);
ememory::SharedPtr<audio::river::Manager> manager;
manager = audio::river::Manager::create("testApplication");
ememory::SharedPtr<TestClass> process = ememory::makeShared<TestClass>(manager);
process->run();
process.reset();
ethread::sleepMilliSeconds((500));
audio::river::unInit();
}
};

View File

@@ -1,378 +0,0 @@
/** @file
* @author Edouard DUPIN
* @copyright 2015, Edouard DUPIN, all right reserved
* @license APACHE v2.0 (see license file)
*/
#ifndef __RIVER_TEST_ECHO_DELAY_H__
#define __RIVER_TEST_ECHO_DELAY_H__
#include <river/debug.h>
#undef __class__
#define __class__ "test_echo_delay"
namespace river_test_echo_delay {
class TestClass {
private:
std11::shared_ptr<river::Manager> m_manager;
std11::shared_ptr<river::Interface> m_interfaceOut;
std11::shared_ptr<river::Interface> m_interfaceIn;
std11::shared_ptr<river::Interface> m_interfaceFB;
double m_phase;
double m_freq;
int32_t m_nextSampleCount;
std11::chrono::milliseconds m_delayBetweenEvent;
std11::chrono::system_clock::time_point m_nextTick;
std11::chrono::system_clock::time_point m_currentTick;
int32_t m_stateFB;
int32_t m_stateMic;
std::vector<uint64_t> m_delayListMic;
bool m_estimateVolumeInput;
int16_t m_volumeInputMax;
int16_t m_volumeInputMin;
float m_gain;
public:
TestClass(std11::shared_ptr<river::Manager> _manager) :
m_manager(_manager),
m_phase(0),
m_freq(400),
m_nextSampleCount(0),
m_delayBetweenEvent(400),
m_stateFB(3),
m_stateMic(0),
m_estimateVolumeInput(true),
m_gain(-40) {
//Set stereo output:
std::vector<audio::channel> channelMap;
channelMap.push_back(audio::channel_frontLeft);
channelMap.push_back(audio::channel_frontRight);
m_interfaceOut = m_manager->createOutput(48000,
channelMap,
audio::format_int16,
"speaker",
"delayTestOut");
// set callback mode ...
m_interfaceOut->setOutputCallback(std11::bind(&TestClass::onDataNeeded,
this,
std11::placeholders::_1,
std11::placeholders::_2,
std11::placeholders::_3,
std11::placeholders::_4,
std11::placeholders::_5,
std11::placeholders::_6));
m_interfaceOut->addVolumeGroup("FLOW");
m_interfaceOut->setParameter("volume", "FLOW", etk::to_string(m_gain) + "dB");
m_interfaceIn = m_manager->createInput(48000,
channelMap,
audio::format_int16,
"microphone",
"delayTestIn");
// set callback mode ...
m_interfaceIn->setInputCallback(std11::bind(&TestClass::onDataReceived,
this,
std11::placeholders::_1,
std11::placeholders::_2,
std11::placeholders::_3,
std11::placeholders::_4,
std11::placeholders::_5,
std11::placeholders::_6));
m_interfaceFB = m_manager->createInput(48000,
channelMap,
audio::format_int16,
"feedback",
"delayTestFB");
// set callback mode ...
m_interfaceFB->setInputCallback(std11::bind(&TestClass::onDataReceivedFeedBack,
this,
std11::placeholders::_1,
std11::placeholders::_2,
std11::placeholders::_3,
std11::placeholders::_4,
std11::placeholders::_5,
std11::placeholders::_6));
m_manager->generateDotAll("activeProcess.dot");
}
void onDataNeeded(void* _data,
const std11::chrono::system_clock::time_point& _time,
size_t _nbChunk,
enum audio::format _format,
uint32_t _frequency,
const std::vector<audio::channel>& _map) {
int16_t* data = static_cast<int16_t*>(_data);
double baseCycle = 2.0*M_PI/(double)48000 * m_freq;
if (m_estimateVolumeInput == true) {
for (int32_t iii=0; iii<_nbChunk; iii++) {
for (int32_t jjj=0; jjj<_map.size(); jjj++) {
data[_map.size()*iii+jjj] = sin(m_phase) * 30000;
}
m_phase += baseCycle;
if (m_phase >= 2*M_PI) {
m_phase -= 2*M_PI;
}
}
} else {
if (_time == std11::chrono::system_clock::time_point()) {
for (int32_t iii=0; iii<_nbChunk; iii++) {
for (int32_t jjj=0; jjj<_map.size(); jjj++) {
data[_map.size()*iii+jjj] = 0;
}
}
return;
}
if (m_nextTick == std11::chrono::system_clock::time_point()) {
m_nextTick = _time + m_delayBetweenEvent;
m_nextSampleCount = m_delayBetweenEvent.count()*int64_t(_frequency)/1000;
m_phase = -1;
}
//APPL_INFO("sample : " << m_nextSampleCount);
for (int32_t iii=0; iii<_nbChunk; iii++) {
if (m_nextSampleCount > 0) {
m_nextSampleCount--;
} else {
m_phase = 0;
m_nextSampleCount = m_delayBetweenEvent.count()*int64_t(_frequency)/1000;
m_currentTick = m_nextTick;
m_nextTick += m_delayBetweenEvent;
}
if (m_phase >= 0) {
for (int32_t jjj=0; jjj<_map.size(); jjj++) {
data[_map.size()*iii+jjj] = sin(m_phase) * 30000;
}
double newPhase = m_phase+baseCycle;
if ( m_phase < M_PI
&& newPhase >= M_PI) {
// the zero crossing position :
m_currentTick = getInterpolateTime(_time, iii, sin(m_phase) * 30000, sin(newPhase) * 30000, _frequency);
// start detection ...
m_stateFB = 0;
m_stateMic = 0;
APPL_WARNING("Time Pulse zero crossing: " << m_currentTick << " id=" << iii);
}
m_phase = newPhase;
if (m_phase >= 2*M_PI) {
m_phase = -1;
//m_freq += 50.0;
if (m_freq>20000.0) {
m_freq = 400.0;
}
}
} else {
for (int32_t jjj=0; jjj<_map.size(); jjj++) {
data[_map.size()*iii+jjj] = 0;
}
}
}
}
}
std11::chrono::system_clock::time_point getInterpolateTime(std11::chrono::system_clock::time_point _time, int32_t _pos, int16_t _val1, int16_t _val2, uint32_t _frequency) {
if (_val1 == 0) {
return _time + std11::chrono::nanoseconds(int64_t(_pos)*1000000000LL/int64_t(_frequency));
} else if (_val2 == 0) {
return _time + std11::chrono::nanoseconds(int64_t(_pos+1)*1000000000LL/int64_t(_frequency));
}
double xxx = double(-_val1) / double(_val2 - _val1);
APPL_VERBOSE("deltaPos:" << xxx);
return _time + std11::chrono::nanoseconds(int64_t((double(_pos)+xxx)*1000000000.0)/int64_t(_frequency));
}
void onDataReceivedFeedBack(const void* _data,
const std11::chrono::system_clock::time_point& _time,
size_t _nbChunk,
enum audio::format _format,
uint32_t _frequency,
const std::vector<audio::channel>& _map) {
if (_format != audio::format_int16) {
APPL_ERROR("call wrong type ... (need int16_t)");
}
RIVER_SAVE_FILE_MACRO(int16_t, "REC_FeedBack.raw", _data, _nbChunk*_map.size());
if (m_estimateVolumeInput == true) {
// nothing to do ...
} else {
const int16_t* data = static_cast<const int16_t*>(_data);
// Detect Zero crossing after a max/min ...
for (size_t iii=0; iii<_nbChunk; ++iii) {
//for (size_t jjj=0; jjj<_map.size(); ++jjj) {
size_t jjj=0; {
if (m_stateFB == 0) {
if (data[iii*_map.size() + jjj] > INT16_MAX/5) {
m_stateFB = 1;
APPL_VERBOSE("FB: detect Normal " << iii);
} else if (data[iii*_map.size() + jjj] < -INT16_MAX/5) {
m_stateFB = 2;
APPL_VERBOSE("FB: detect inverse " << iii);
}
} else if (m_stateFB == 1) {
// normale phase
if (data[iii*_map.size() + jjj] <= 0) {
// detect inversion of signe ...
m_stateFB = 3;
std11::chrono::system_clock::time_point time = getInterpolateTime(_time, iii-1, data[(iii-1)*_map.size() + jjj], data[iii*_map.size() + jjj], _frequency);
APPL_VERBOSE("FB: 1 position -1: " << iii-1 << " " << data[(iii-1)*_map.size() + jjj]);
APPL_VERBOSE("FB: 1 position 0: " << iii << " " << data[iii*_map.size() + jjj]);
APPL_WARNING("FB: 1 time detected: " << time << " delay = " << float((time-m_currentTick).count())/1000.0f << "µs");
}
} else if (m_stateFB == 2) {
// inverse phase
if (data[iii*_map.size() + jjj] >= 0) {
// detect inversion of signe ...
m_stateFB = 3;
std11::chrono::system_clock::time_point time = getInterpolateTime(_time, iii-1, data[(iii-1)*_map.size() + jjj], data[iii*_map.size() + jjj], _frequency);
APPL_VERBOSE("FB: 2 position -1: " << iii-1 << " " << data[(iii-1)*_map.size() + jjj]);
APPL_VERBOSE("FB: 2 position 0: " << iii << " " << data[iii*_map.size() + jjj]);
APPL_WARNING("FB: 2 time detected: " << time << " delay = " << float((time-m_currentTick).count())/1000.0f << "µs");
}
} else if (m_stateFB == 3) {
// TODO : Detect the pic ...
// do nothing ...
}
}
}
}
}
void onDataReceived(const void* _data,
const std11::chrono::system_clock::time_point& _time,
size_t _nbChunk,
enum audio::format _format,
uint32_t _frequency,
const std::vector<audio::channel>& _map) {
if (_format != audio::format_int16) {
APPL_ERROR("call wrong type ... (need int16_t)");
}
RIVER_SAVE_FILE_MACRO(int16_t, "REC_Microphone.raw", _data, _nbChunk*_map.size());
const int16_t* data = static_cast<const int16_t*>(_data);
if (m_estimateVolumeInput == true) {
m_stateMic ++;
const int16_t* data = static_cast<const int16_t*>(_data);
if (m_stateMic <= 40) {
for (size_t iii=0; iii<_nbChunk*_map.size(); ++iii) {
//APPL_INFO("value=" << data[iii]);
m_volumeInputMax = std::max(int16_t(data[iii]), m_volumeInputMax);
m_volumeInputMin = std::min(int16_t(data[iii]), m_volumeInputMin);
}
if (m_stateMic == 40) {
m_volumeInputMax *= 2;
m_volumeInputMin *= 2;
}
} else if (m_stateMic <= 10000) {
int16_t valueMax = 0;
int16_t valueMin = 0;
for (size_t iii=0; iii<_nbChunk*_map.size(); ++iii) {
//APPL_INFO("value=" << data[iii]);
valueMax = std::max(int16_t(data[iii]), valueMax);
valueMin = std::min(int16_t(data[iii]), valueMin);
}
if ( valueMax > m_volumeInputMax
&& valueMin < m_volumeInputMin
&& ( m_gain == 0.0
|| ( valueMax > INT16_MAX*2/3
&& valueMin < INT16_MIN*2/3
)
)
) {
m_gain += 3.0f;
m_gain = std::min(m_gain, 0.0f);
m_interfaceOut->setParameter("volume", "FLOW", etk::to_string(m_gain) + "dB");
APPL_INFO("Set detection volume : " << m_gain << " m_stateMic=" << m_stateMic);
m_stateMic = 3;
m_phase = -1;
m_estimateVolumeInput = false;
return;
} else {
if (m_stateMic%2 == 0) {
if (m_gain == 0.0f) {
APPL_CRITICAL("Can not find the basicVolume ...");
}
// just update volume
m_gain += 1.0f;
m_gain = std::min(m_gain, 0.0f);
m_interfaceOut->setParameter("volume", "FLOW", etk::to_string(m_gain) + "dB");
}
}
}
} else {
// Detect Zero crossing after a max/min ...
for (size_t iii=0; iii<_nbChunk; ++iii) {
//for (size_t jjj=0; jjj<_map.size(); ++jjj) {
size_t jjj=0; {
if (m_stateMic == 0) {
if (data[iii*_map.size() + jjj] > m_volumeInputMax) {
m_stateMic = 1;
APPL_VERBOSE("Mic: detect Normal " << iii);
} else if (data[iii*_map.size() + jjj] < m_volumeInputMin) {
m_stateMic = 2;
APPL_VERBOSE("Mic: detect inverse " << iii);
}
} else if (m_stateMic == 1) {
// normale phase
if (data[iii*_map.size() + jjj] <= 0) {
// detect inversion of signe ...
m_stateMic = 3;
std11::chrono::system_clock::time_point time = getInterpolateTime(_time, iii-1, data[(iii-1)*_map.size() + jjj], data[iii*_map.size() + jjj], _frequency);
APPL_VERBOSE("MIC: 1 position -1: " << iii-1 << " " << data[(iii-1)*_map.size() + jjj]);
APPL_VERBOSE("MIC: 1 position 0: " << iii << " " << data[iii*_map.size() + jjj]);
std11::chrono::nanoseconds delay = time-m_currentTick;
int32_t sampleDalay = (delay.count()*_frequency)/1000000000LL;
APPL_WARNING("MIC: 1 time detected: " << time << " delay = " << float(delay.count())/1000.0f << "µs samples=" << sampleDalay);
m_delayListMic.push_back(delay.count());
}
} else if (m_stateMic == 2) {
// inverse phase
if (data[iii*_map.size() + jjj] >= 0) {
// detect inversion of signe ...
m_stateMic = 3;
std11::chrono::system_clock::time_point time = getInterpolateTime(_time, iii-1, data[(iii-1)*_map.size() + jjj], data[iii*_map.size() + jjj], _frequency);
APPL_VERBOSE("MIC: 2 position -1: " << iii-1 << " " << data[(iii-1)*_map.size() + jjj]);
APPL_VERBOSE("MIC: 2 position 0: " << iii << " " << data[iii*_map.size() + jjj]);
std11::chrono::nanoseconds delay = time-m_currentTick;
int32_t sampleDalay = (delay.count()*_frequency)/1000000000LL;
APPL_WARNING("MIC: 2 time detected: " << time << " delay = " << float(delay.count())/1000.0f << "µs samples=" << sampleDalay);
m_delayListMic.push_back(delay.count());
}
} else if (m_stateMic == 3) {
// TODO : Detect the pic ...
// do nothing ...
}
}
}
}
}
void run() {
m_interfaceOut->start();
m_interfaceIn->start();
//m_interfaceFB->start();
usleep(10000000);
//m_interfaceFB->stop();
m_interfaceIn->stop();
m_interfaceOut->stop();
int64_t delayAverage = 0;
if (m_delayListMic.size() > 0) {
for (size_t iii=0; iii<m_delayListMic.size(); ++iii) {
delayAverage += m_delayListMic[iii];
}
delayAverage /= m_delayListMic.size();
}
int32_t sampleDalay = (delayAverage*48000)/1000000000LL;
APPL_ERROR("Average delay in ns : " << delayAverage << " nbSample=" << sampleDalay);
}
};
TEST(TestTime, testDelay) {
std11::shared_ptr<river::Manager> manager;
manager = river::Manager::create("testApplication");
std11::shared_ptr<TestClass> process = std11::make_shared<TestClass>(manager);
process->run();
process.reset();
usleep(500000);
}
};
#undef __class__
#define __class__ nullptr
#endif

219
test/testFormat.cpp Normal file
View File

@@ -0,0 +1,219 @@
/** @file
* @author Edouard DUPIN
* @copyright 2015, Edouard DUPIN, all right reserved
* @license MPL v2.0 (see license file)
*/
#include <test-debug/debug.hpp>
#include <audio/river/river.hpp>
#include <audio/river/Manager.hpp>
#include <audio/river/Interface.hpp>
#include <etest/etest.hpp>
#include <etk/etk.hpp>
extern "C" {
#include <math.h>
}
#include <ethread/Thread.hpp>
#include <ethread/tools.hpp>
namespace river_test_format {
static const etk::String configurationRiver =
"{\n"
" speaker:{\n"
" io:'output',\n"
" map-on:{\n"
" interface:'auto',\n"
" name:'default',\n"
" },\n"
" frequency:0,\n"
" channel-map:['front-left', 'front-right'],\n"
" type:'auto',\n"
" nb-chunk:1024,\n"
" volume-name:'MASTER'\n"
" }\n"
"}\n";
class testOutCallbackType {
private:
ememory::SharedPtr<audio::river::Manager> m_manager;
ememory::SharedPtr<audio::river::Interface> m_interface;
double m_phase;
float m_freq;
int32_t m_nbChannels;
float m_generateFreq;
public:
testOutCallbackType(const ememory::SharedPtr<audio::river::Manager>& _manager,
float _freq=48000.0f,
int32_t _nbChannels=2,
audio::format _format=audio::format_int16) :
m_manager(_manager),
m_phase(0),
m_freq(_freq),
m_nbChannels(_nbChannels),
m_generateFreq(550.0f) {
//Set stereo output:
etk::Vector<audio::channel> channelMap;
if (m_nbChannels == 1) {
channelMap.pushBack(audio::channel_frontCenter);
} else if (m_nbChannels == 2) {
channelMap.pushBack(audio::channel_frontLeft);
channelMap.pushBack(audio::channel_frontRight);
} else if (m_nbChannels == 4) {
channelMap.pushBack(audio::channel_frontLeft);
channelMap.pushBack(audio::channel_frontRight);
channelMap.pushBack(audio::channel_rearLeft);
channelMap.pushBack(audio::channel_rearRight);
} else {
TEST_ERROR("Can not generate with channel != 1,2,4");
return;
}
m_interface = m_manager->createOutput(m_freq,
channelMap,
_format,
"speaker",
"WriteModeCallbackType");
if(m_interface == null) {
TEST_ERROR("null interface");
return;
}
// set callback mode ...
m_interface->setOutputCallback([=](void* _data,
const audio::Time& _time,
size_t _nbChunk,
enum audio::format _format,
uint32_t _frequency,
const etk::Vector<audio::channel>& _map) {
onDataNeeded(_data, _time, _nbChunk, _format, _frequency, _map);
});
}
void onDataNeeded(void* _data,
const audio::Time& _time,
size_t _nbChunk,
enum audio::format _format,
uint32_t _frequency,
const etk::Vector<audio::channel>& _map) {
//TEST_DEBUG("Get data ... " << _format << " map=" << _map << " chunk=" << _nbChunk);
double baseCycle = 2.0*M_PI/double(m_freq) * double(m_generateFreq);
if (_format == audio::format_int16) {
int16_t* data = static_cast<int16_t*>(_data);
for (int32_t iii=0; iii<_nbChunk; iii++) {
for (int32_t jjj=0; jjj<_map.size(); jjj++) {
data[_map.size()*iii+jjj] = cos(m_phase) * double(INT16_MAX);
}
m_phase += baseCycle;
if (m_phase >= 2*M_PI) {
m_phase -= 2*M_PI;
}
}
} else if (_format == audio::format_int16_on_int32) {
int32_t* data = static_cast<int32_t*>(_data);
for (int32_t iii=0; iii<_nbChunk; iii++) {
for (int32_t jjj=0; jjj<_map.size(); jjj++) {
data[_map.size()*iii+jjj] = cos(m_phase) * double(INT16_MAX);
}
m_phase += baseCycle;
if (m_phase >= 2*M_PI) {
m_phase -= 2*M_PI;
}
}
} else if (_format == audio::format_int32) {
int32_t* data = static_cast<int32_t*>(_data);
for (int32_t iii=0; iii<_nbChunk; iii++) {
for (int32_t jjj=0; jjj<_map.size(); jjj++) {
data[_map.size()*iii+jjj] = cos(m_phase) * double(INT32_MAX);
}
m_phase += baseCycle;
if (m_phase >= 2*M_PI) {
m_phase -= 2*M_PI;
}
}
} else if (_format == audio::format_float) {
float* data = static_cast<float*>(_data);
for (int32_t iii=0; iii<_nbChunk; iii++) {
for (int32_t jjj=0; jjj<_map.size(); jjj++) {
data[_map.size()*iii+jjj] = cos(m_phase);
}
m_phase += baseCycle;
if (m_phase >= 2*M_PI) {
m_phase -= 2*M_PI;
}
}
}
}
void run() {
if(m_interface == null) {
TEST_ERROR("null interface");
return;
}
m_interface->start();
// wait 2 second ...
ethread::sleepMilliSeconds(1000*(1));
m_interface->stop();
ethread::sleepMilliSeconds((100));
}
};
etk::Vector<float> listFreq = {4000, 8000, 16000, 32000, 48000, 48001, 64000, 96000, 11250, 2250, 44100, 88200};
etk::Vector<int32_t> listChannel = {1, 2, 4};
etk::Vector<audio::format> listFormat = {audio::format_int16, audio::format_int16_on_int32, audio::format_int32, audio::format_float};
TEST(testResampling, base) {
audio::river::initString(configurationRiver);
ememory::SharedPtr<audio::river::Manager> manager;
manager = audio::river::Manager::create("testApplication");
for (auto itFrequency: listFreq) {
ememory::SharedPtr<testOutCallbackType> process = ememory::makeShared<testOutCallbackType>(manager, itFrequency, 2, audio::format_int16);
process->run();
process.reset();
ethread::sleepMilliSeconds((500));
}
audio::river::unInit();
}
TEST(testFormat, base) {
audio::river::initString(configurationRiver);
ememory::SharedPtr<audio::river::Manager> manager;
manager = audio::river::Manager::create("testApplication");
for (auto itFormat: listFormat) {
ememory::SharedPtr<testOutCallbackType> process = ememory::makeShared<testOutCallbackType>(manager, 48000, 2, itFormat);
process->run();
process.reset();
ethread::sleepMilliSeconds((500));
}
audio::river::unInit();
}
TEST(testChannels, base) {
audio::river::initString(configurationRiver);
ememory::SharedPtr<audio::river::Manager> manager;
manager = audio::river::Manager::create("testApplication");
for (auto itChannel: listChannel) {
ememory::SharedPtr<testOutCallbackType> process = ememory::makeShared<testOutCallbackType>(manager, 48000, itChannel, audio::format_int16);
process->run();
process.reset();
ethread::sleepMilliSeconds((500));
}
audio::river::unInit();
}
TEST(TestALL, testChannelsFormatResampling) {
audio::river::initString(configurationRiver);
ememory::SharedPtr<audio::river::Manager> manager;
manager = audio::river::Manager::create("testApplication");
TEST_INFO("test convert flaot to output (callback mode)");
for (auto itFrequency: listFreq) {
for (auto itChannel: listChannel) {
for (auto itFormat: listFormat) {
TEST_INFO("freq=" << itFrequency << " channel=" << listChannel << " format=" << audio::getFormatString(itFormat));
ememory::SharedPtr<testOutCallbackType> process = ememory::makeShared<testOutCallbackType>(manager, itFrequency, itChannel, itFormat);
process->run();
process.reset();
ethread::sleepMilliSeconds((500));
}
}
}
audio::river::unInit();
}
}

View File

@@ -1,220 +0,0 @@
/** @file
* @author Edouard DUPIN
* @copyright 2015, Edouard DUPIN, all right reserved
* @license APACHE v2.0 (see license file)
*/
#ifndef __RIVER_TEST_FORMAT_H__
#define __RIVER_TEST_FORMAT_H__
#undef __class__
#define __class__ "test_format"
namespace river_test_format {
class testOutCallbackType {
private:
std11::shared_ptr<river::Manager> m_manager;
std11::shared_ptr<river::Interface> m_interface;
double m_phase;
float m_freq;
int32_t m_nbChannels;
float m_generateFreq;
public:
testOutCallbackType(const std11::shared_ptr<river::Manager>& _manager,
float _freq=48000.0f,
int32_t _nbChannels=2,
audio::format _format=audio::format_int16) :
m_manager(_manager),
m_phase(0),
m_freq(_freq),
m_nbChannels(_nbChannels),
m_generateFreq(550.0f) {
//Set stereo output:
std::vector<audio::channel> channelMap;
if (m_nbChannels == 1) {
channelMap.push_back(audio::channel_frontCenter);
} else if (m_nbChannels == 2) {
channelMap.push_back(audio::channel_frontLeft);
channelMap.push_back(audio::channel_frontRight);
} else if (m_nbChannels == 4) {
channelMap.push_back(audio::channel_frontLeft);
channelMap.push_back(audio::channel_frontRight);
channelMap.push_back(audio::channel_rearLeft);
channelMap.push_back(audio::channel_rearRight);
} else {
APPL_ERROR("Can not generate with channel != 1,2,4");
return;
}
m_interface = m_manager->createOutput(m_freq,
channelMap,
_format,
"speaker",
"WriteModeCallbackType");
// set callback mode ...
m_interface->setOutputCallback(std11::bind(&testOutCallbackType::onDataNeeded,
this,
std11::placeholders::_1,
std11::placeholders::_2,
std11::placeholders::_3,
std11::placeholders::_4,
std11::placeholders::_5,
std11::placeholders::_6));
}
void onDataNeeded(void* _data,
const std11::chrono::system_clock::time_point& _time,
size_t _nbChunk,
enum audio::format _format,
uint32_t _frequency,
const std::vector<audio::channel>& _map) {
//APPL_DEBUG("Get data ... " << _format << " map=" << _map << " chunk=" << _nbChunk);
double baseCycle = 2.0*M_PI/double(m_freq) * double(m_generateFreq);
if (_format == audio::format_int16) {
int16_t* data = static_cast<int16_t*>(_data);
for (int32_t iii=0; iii<_nbChunk; iii++) {
for (int32_t jjj=0; jjj<_map.size(); jjj++) {
data[_map.size()*iii+jjj] = cos(m_phase) * double(INT16_MAX);
}
m_phase += baseCycle;
if (m_phase >= 2*M_PI) {
m_phase -= 2*M_PI;
}
}
} else if (_format == audio::format_int16_on_int32) {
int32_t* data = static_cast<int32_t*>(_data);
for (int32_t iii=0; iii<_nbChunk; iii++) {
for (int32_t jjj=0; jjj<_map.size(); jjj++) {
data[_map.size()*iii+jjj] = cos(m_phase) * double(INT16_MAX);
}
m_phase += baseCycle;
if (m_phase >= 2*M_PI) {
m_phase -= 2*M_PI;
}
}
} else if (_format == audio::format_int32) {
int32_t* data = static_cast<int32_t*>(_data);
for (int32_t iii=0; iii<_nbChunk; iii++) {
for (int32_t jjj=0; jjj<_map.size(); jjj++) {
data[_map.size()*iii+jjj] = cos(m_phase) * double(INT32_MAX);
}
m_phase += baseCycle;
if (m_phase >= 2*M_PI) {
m_phase -= 2*M_PI;
}
}
} else if (_format == audio::format_float) {
float* data = static_cast<float*>(_data);
for (int32_t iii=0; iii<_nbChunk; iii++) {
for (int32_t jjj=0; jjj<_map.size(); jjj++) {
data[_map.size()*iii+jjj] = cos(m_phase);
}
m_phase += baseCycle;
if (m_phase >= 2*M_PI) {
m_phase -= 2*M_PI;
}
}
}
}
void run() {
if (m_interface != nullptr) {
m_interface->start();
// wait 2 second ...
usleep(1000000);
m_interface->stop();
usleep(100000);
} else {
APPL_ERROR("Can not create interface !!!");
}
}
};
class testResampling : public ::testing::TestWithParam<float> {};
TEST_P(testResampling, base) {
std11::shared_ptr<river::Manager> manager;
manager = river::Manager::create("testApplication");
std11::shared_ptr<testOutCallbackType> process = std11::make_shared<testOutCallbackType>(manager, GetParam(), 2, audio::format_int16);
process->run();
process.reset();
usleep(500000);
}
INSTANTIATE_TEST_CASE_P(InstantiationName,
testResampling,
::testing::Values(4000, 8000, 16000, 32000, 48000, 48001, 64000, 96000, 11250, 2250, 44100, 88200));
class testFormat : public ::testing::TestWithParam<audio::format> {};
TEST_P(testFormat, base) {
std11::shared_ptr<river::Manager> manager;
manager = river::Manager::create("testApplication");
std11::shared_ptr<testOutCallbackType> process = std11::make_shared<testOutCallbackType>(manager, 48000, 2, GetParam());
process->run();
process.reset();
usleep(500000);
}
INSTANTIATE_TEST_CASE_P(InstantiationName,
testFormat,
::testing::Values(audio::format_int16, audio::format_int16_on_int32, audio::format_int32, audio::format_float));
class testChannels : public ::testing::TestWithParam<int32_t> {};
TEST_P(testChannels, base) {
std11::shared_ptr<river::Manager> manager;
manager = river::Manager::create("testApplication");
std11::shared_ptr<testOutCallbackType> process = std11::make_shared<testOutCallbackType>(manager, 48000, GetParam(), audio::format_int16);
process->run();
process.reset();
usleep(500000);
}
INSTANTIATE_TEST_CASE_P(InstantiationName,
testChannels,
::testing::Values(1,2,4));
TEST(TestALL, testChannelsFormatResampling) {
std11::shared_ptr<river::Manager> manager;
manager = river::Manager::create("testApplication");
APPL_INFO("test convert flaot to output (callback mode)");
std::vector<float> listFreq;
listFreq.push_back(4000);
listFreq.push_back(8000);
listFreq.push_back(16000);
listFreq.push_back(32000);
listFreq.push_back(48000);
listFreq.push_back(48001);
listFreq.push_back(64000);
listFreq.push_back(96000);
listFreq.push_back(11250);
listFreq.push_back(2250);
listFreq.push_back(44100);
listFreq.push_back(88200);
std::vector<int32_t> listChannel;
listChannel.push_back(1);
listChannel.push_back(2);
listChannel.push_back(4);
std::vector<audio::format> listFormat;
listFormat.push_back(audio::format_int16);
listFormat.push_back(audio::format_int16_on_int32);
listFormat.push_back(audio::format_int32);
listFormat.push_back(audio::format_float);
for (size_t fff=0; fff<listFreq.size(); ++fff) {
for (size_t ccc=0; ccc<listChannel.size(); ++ccc) {
for (size_t iii=0; iii<listFormat.size(); ++iii) {
APPL_INFO("freq=" << listFreq[fff] << " channel=" << listChannel[ccc] << " format=" << getFormatString(listFormat[iii]));
std11::shared_ptr<testOutCallbackType> process = std11::make_shared<testOutCallbackType>(manager, listFreq[fff], listChannel[ccc], listFormat[iii]);
process->run();
process.reset();
usleep(500000);
}
}
}
}
};
#undef __class__
#define __class__ nullptr
#endif

185
test/testMuxer.cpp Normal file
View File

@@ -0,0 +1,185 @@
/** @file
* @author Edouard DUPIN
* @copyright 2015, Edouard DUPIN, all right reserved
* @license MPL v2.0 (see license file)
*/
#include <test-debug/debug.hpp>
#include <audio/river/river.hpp>
#include <audio/river/Manager.hpp>
#include <audio/river/Interface.hpp>
#include <etest/etest.hpp>
#include <etk/etk.hpp>
extern "C" {
#include <math.h>
}
#include <ethread/Thread.hpp>
#include <ethread/tools.hpp>
namespace river_test_muxer {
class TestClass {
private:
ememory::SharedPtr<audio::river::Manager> m_manager;
ememory::SharedPtr<audio::river::Interface> m_interfaceIn;
ememory::SharedPtr<audio::river::Interface> m_interfaceOut;
double m_phase;
public:
TestClass(ememory::SharedPtr<audio::river::Manager> _manager) :
m_manager(_manager),
m_phase(0) {
etk::Vector<audio::channel> channelMap;
channelMap.pushBack(audio::channel_frontLeft);
channelMap.pushBack(audio::channel_frontRight);
m_interfaceOut = m_manager->createOutput(48000,
channelMap,
audio::format_int16,
"speaker");
if(m_interfaceOut == null) {
TEST_ERROR("null interface");
return;
}
// set callback mode ...
m_interfaceOut->setOutputCallback([=](void* _data,
const audio::Time& _time,
size_t _nbChunk,
enum audio::format _format,
uint32_t _frequency,
const etk::Vector<audio::channel>& _map) {
onDataNeeded(_data, _time, _nbChunk, _format, _frequency, _map);
});
m_interfaceOut->addVolumeGroup("FLOW");
//m_interfaceOut->setParameter("volume", "FLOW", "-6dB");
//Set stereo output:
m_interfaceIn = m_manager->createInput(48000,
etk::Vector<audio::channel>(),
audio::format_int16,
"microphone-muxed");
if(m_interfaceIn == null) {
TEST_ERROR("null interface");
return;
}
// set callback mode ...
m_interfaceIn->setInputCallback([=](const void* _data,
const audio::Time& _time,
size_t _nbChunk,
enum audio::format _format,
uint32_t _frequency,
const etk::Vector<audio::channel>& _map) {
onDataReceived(_data, _time, _nbChunk, _format, _frequency, _map);
});
m_manager->generateDotAll("activeProcess.dot");
}
void onDataNeeded(void* _data,
const audio::Time& _time,
size_t _nbChunk,
enum audio::format _format,
uint32_t _frequency,
const etk::Vector<audio::channel>& _map) {
int16_t* data = static_cast<int16_t*>(_data);
double baseCycle = 2.0*M_PI/(double)48000 * 440;
for (int32_t iii=0; iii<_nbChunk; iii++) {
for (int32_t jjj=0; jjj<_map.size(); jjj++) {
data[_map.size()*iii+jjj] = sin(m_phase) * 7000;
}
m_phase += baseCycle;
if (m_phase >= 2*M_PI) {
m_phase -= 2*M_PI;
}
}
}
void onDataReceived(const void* _data,
const audio::Time& _time,
size_t _nbChunk,
enum audio::format _format,
uint32_t _frequency,
const etk::Vector<audio::channel>& _map) {
if (_format != audio::format_int16) {
TEST_ERROR("call wrong type ... (need int16_t)");
}
//TEST_SAVE_FILE_MACRO(int16_t, "REC_MicrophoneMuxed.raw", _data, _nbChunk*_map.size());
TEST_ERROR("Receive data ... " << _nbChunk << " map=" << _map);
}
void run() {
if(m_interfaceIn == null) {
TEST_ERROR("null interface");
return;
}
if(m_interfaceOut == null) {
TEST_ERROR("null interface");
return;
}
m_interfaceOut->start();
m_interfaceIn->start();
ethread::sleepMilliSeconds(1000*(10));
m_interfaceIn->stop();
m_interfaceOut->stop();
}
};
static const etk::String configurationRiver =
"{\n"
" speaker:{\n"
" io:'output',\n"
" map-on:{\n"
" interface:'auto',\n"
" name:'default',\n"
" timestamp-mode:'trigered',\n"
" },\n"
" group:'groupSynchro',\n"
" frequency:0,\n"
" channel-map:['front-left', 'front-right'],\n"
" type:'auto',\n"
" nb-chunk:1024,\n"
" },\n"
" microphone:{\n"
" io:'input',\n"
" map-on:{\n"
" interface:'auto',\n"
" name:'default',\n"
" timestamp-mode:'trigered',\n"
" },\n"
" group:'groupSynchro',\n"
" frequency:0,\n"
" channel-map:['front-left', 'front-right'],\n"
" type:'auto',\n"
" nb-chunk:1024\n"
" },\n"
" microphone-muxed:{\n"
" io:'muxer',\n"
" # connect in input mode\n"
" map-on-input-1:{\n"
" # generic virtual definition\n"
" io:'input',\n"
" map-on:'microphone',\n"
" },\n"
" # connect in feedback mode\n"
" map-on-input-2:{\n"
" io:'feedback',\n"
" map-on:'speaker',\n"
" },\n"
" input-2-remap:['rear-left', 'rear-right'],\n"
" #classical format configuration:\n"
" frequency:48000,\n"
" channel-map:[\n"
" 'front-left', 'front-right', 'rear-left', 'rear-right'\n"
" ],\n"
" type:'int16',\n"
" mux-demux-type:'int16',\n"
" }\n"
"}\n";
TEST(TestMuxer, testMuxing) {
audio::river::initString(configurationRiver);
ememory::SharedPtr<audio::river::Manager> manager;
manager = audio::river::Manager::create("testApplication");
ememory::SharedPtr<TestClass> process = ememory::makeShared<TestClass>(manager);
process->run();
process.reset();
ethread::sleepMilliSeconds((500));
audio::river::unInit();
}
};

View File

@@ -1,116 +0,0 @@
/** @file
* @author Edouard DUPIN
* @copyright 2015, Edouard DUPIN, all right reserved
* @license APACHE v2.0 (see license file)
*/
#ifndef __RIVER_TEST_MUXER_H__
#define __RIVER_TEST_MUXER_H__
#include <river/debug.h>
#undef __class__
#define __class__ "test_muxer"
namespace river_test_muxer {
class TestClass {
private:
std11::shared_ptr<river::Manager> m_manager;
std11::shared_ptr<river::Interface> m_interfaceIn;
std11::shared_ptr<river::Interface> m_interfaceOut;
double m_phase;
public:
TestClass(std11::shared_ptr<river::Manager> _manager) :
m_manager(_manager),
m_phase(0) {
std::vector<audio::channel> channelMap;
channelMap.push_back(audio::channel_frontLeft);
channelMap.push_back(audio::channel_frontRight);
m_interfaceOut = m_manager->createOutput(48000,
channelMap,
audio::format_int16,
"speaker",
"MuxerTestOut");
// set callback mode ...
m_interfaceOut->setOutputCallback(std11::bind(&TestClass::onDataNeeded,
this,
std11::placeholders::_1,
std11::placeholders::_2,
std11::placeholders::_3,
std11::placeholders::_4,
std11::placeholders::_5,
std11::placeholders::_6));
m_interfaceOut->addVolumeGroup("FLOW");
m_interfaceOut->setParameter("volume", "FLOW", "-6dB");
//Set stereo output:
m_interfaceIn = m_manager->createInput(48000,
std::vector<audio::channel>(),
audio::format_int16,
"microphone-muxed",
"microphone-muxed-local-name");
// set callback mode ...
m_interfaceIn->setInputCallback(std11::bind(&TestClass::onDataReceived,
this,
std11::placeholders::_1,
std11::placeholders::_2,
std11::placeholders::_3,
std11::placeholders::_4,
std11::placeholders::_5,
std11::placeholders::_6));
m_manager->generateDotAll("activeProcess.dot");
}
void onDataNeeded(void* _data,
const std11::chrono::system_clock::time_point& _time,
size_t _nbChunk,
enum audio::format _format,
uint32_t _frequency,
const std::vector<audio::channel>& _map) {
int16_t* data = static_cast<int16_t*>(_data);
double baseCycle = 2.0*M_PI/(double)48000 * 440;
for (int32_t iii=0; iii<_nbChunk; iii++) {
for (int32_t jjj=0; jjj<_map.size(); jjj++) {
data[_map.size()*iii+jjj] = sin(m_phase) * 30000;
}
m_phase += baseCycle;
if (m_phase >= 2*M_PI) {
m_phase -= 2*M_PI;
}
}
}
void onDataReceived(const void* _data,
const std11::chrono::system_clock::time_point& _time,
size_t _nbChunk,
enum audio::format _format,
uint32_t _frequency,
const std::vector<audio::channel>& _map) {
if (_format != audio::format_int16) {
APPL_ERROR("call wrong type ... (need int16_t)");
}
RIVER_SAVE_FILE_MACRO(int16_t, "REC_MicrophoneMuxed.raw", _data, _nbChunk*_map.size());
APPL_ERROR("Receive data ... " << _nbChunk << " map=" << _map);
}
void run() {
m_interfaceOut->start();
m_interfaceIn->start();
usleep(10000000);
m_interfaceIn->stop();
m_interfaceOut->stop();
}
};
TEST(TestMuxer, testMuxing) {
std11::shared_ptr<river::Manager> manager;
manager = river::Manager::create("testApplication");
std11::shared_ptr<TestClass> process = std11::make_shared<TestClass>(manager);
process->run();
process.reset();
usleep(500000);
}
};
#undef __class__
#define __class__ nullptr
#endif

View File

@@ -0,0 +1,142 @@
/** @file
* @author Edouard DUPIN
* @copyright 2015, Edouard DUPIN, all right reserved
* @license MPL v2.0 (see license file)
*/
#include <test-debug/debug.hpp>
#include <audio/river/river.hpp>
#include <audio/river/Manager.hpp>
#include <audio/river/Interface.hpp>
#include <etest/etest.hpp>
#include <etk/etk.hpp>
extern "C" {
#include <math.h>
}
#include <ethread/Thread.hpp>
#include <ethread/tools.hpp>
namespace river_test_playback_callback {
class testOutCallback {
public:
ememory::SharedPtr<audio::river::Manager> m_manager;
ememory::SharedPtr<audio::river::Interface> m_interface;
double m_phase;
public:
testOutCallback(ememory::SharedPtr<audio::river::Manager> _manager, const etk::String& _io="speaker") :
m_manager(_manager),
m_phase(0) {
//Set stereo output:
etk::Vector<audio::channel> channelMap;
channelMap.pushBack(audio::channel_frontLeft);
channelMap.pushBack(audio::channel_frontRight);
m_interface = m_manager->createOutput(48000,
channelMap,
audio::format_int16,
_io);
if(m_interface == null) {
TEST_ERROR("null interface");
return;
}
// set callback mode ...
m_interface->setOutputCallback([=](void* _data,
const audio::Time& _time,
size_t _nbChunk,
enum audio::format _format,
uint32_t _frequency,
const etk::Vector<audio::channel>& _map) {
onDataNeeded(_data, _time, _nbChunk, _format, _frequency, _map);
});
}
void onDataNeeded(void* _data,
const audio::Time& _time,
size_t _nbChunk,
enum audio::format _format,
uint32_t _frequency,
const etk::Vector<audio::channel>& _map) {
if (_format != audio::format_int16) {
TEST_ERROR("call wrong type ... (need int16_t)");
}
int16_t* data = static_cast<int16_t*>(_data);
double baseCycle = 2.0*M_PI/(double)48000 * (double)550;
for (int32_t iii=0; iii<_nbChunk; iii++) {
for (int32_t jjj=0; jjj<_map.size(); jjj++) {
data[_map.size()*iii+jjj] = cos(m_phase) * 30000;
}
m_phase += baseCycle;
if (m_phase >= 2*M_PI) {
m_phase -= 2*M_PI;
}
}
}
void run() {
if(m_interface == null) {
TEST_ERROR("null interface");
return;
}
m_interface->start();
// wait 2 second ...
ethread::sleepMilliSeconds(1000*(2));
m_interface->stop();
}
};
static const etk::String configurationRiver =
"{\n"
" speaker:{\n"
" io:'output',\n"
" map-on:{\n"
" interface:'auto',\n"
" name:'default',\n"
" },\n"
" frequency:0,\n"
" channel-map:['front-left', 'front-right'],\n"
" type:'auto',\n"
" nb-chunk:1024,\n"
" volume-name:'MASTER'\n"
" }\n"
"}\n";
TEST(TestALL, testOutputCallBack) {
audio::river::initString(configurationRiver);
ememory::SharedPtr<audio::river::Manager> manager;
manager = audio::river::Manager::create("testApplication");
TEST_INFO("test output (callback mode)");
ememory::SharedPtr<testOutCallback> process = ememory::makeShared<testOutCallback>(manager, "speaker");
ASSERT_NE(process, null);
process->run();
process.reset();
ethread::sleepMilliSeconds((500));
audio::river::unInit();
}
TEST(TestALL, testOutputCallBackPulse) {
audio::river::initString(configurationRiver);
ememory::SharedPtr<audio::river::Manager> manager;
manager = audio::river::Manager::create("testApplication");
TEST_INFO("test output (callback mode)");
ememory::SharedPtr<testOutCallback> process = ememory::makeShared<testOutCallback>(manager, "speaker-pulse");
process->run();
process.reset();
ethread::sleepMilliSeconds((500));
audio::river::unInit();
}
TEST(TestALL, testOutputCallBackJack) {
audio::river::initString(configurationRiver);
ememory::SharedPtr<audio::river::Manager> manager;
manager = audio::river::Manager::create("testApplication");
TEST_INFO("test output (callback mode)");
ememory::SharedPtr<testOutCallback> process = ememory::makeShared<testOutCallback>(manager, "speaker-jack");
process->run();
process.reset();
ethread::sleepMilliSeconds((500));
audio::river::unInit();
}
};

View File

@@ -1,111 +0,0 @@
/** @file
* @author Edouard DUPIN
* @copyright 2015, Edouard DUPIN, all right reserved
* @license APACHE v2.0 (see license file)
*/
#ifndef __RIVER_TEST_PLAYBACK_CALLBACK_H__
#define __RIVER_TEST_PLAYBACK_CALLBACK_H__
#undef __class__
#define __class__ "test_playback_callback"
namespace river_test_playback_callback {
class testOutCallback {
private:
std11::shared_ptr<river::Manager> m_manager;
std11::shared_ptr<river::Interface> m_interface;
double m_phase;
public:
testOutCallback(std11::shared_ptr<river::Manager> _manager, const std::string& _io="speaker") :
m_manager(_manager),
m_phase(0) {
//Set stereo output:
std::vector<audio::channel> channelMap;
channelMap.push_back(audio::channel_frontLeft);
channelMap.push_back(audio::channel_frontRight);
m_interface = m_manager->createOutput(48000,
channelMap,
audio::format_int16,
_io,
"WriteModeCallback");
// set callback mode ...
m_interface->setOutputCallback(std11::bind(&testOutCallback::onDataNeeded,
this,
std11::placeholders::_1,
std11::placeholders::_2,
std11::placeholders::_3,
std11::placeholders::_4,
std11::placeholders::_5,
std11::placeholders::_6));
}
void onDataNeeded(void* _data,
const std11::chrono::system_clock::time_point& _time,
size_t _nbChunk,
enum audio::format _format,
uint32_t _frequency,
const std::vector<audio::channel>& _map) {
if (_format != audio::format_int16) {
APPL_ERROR("call wrong type ... (need int16_t)");
}
int16_t* data = static_cast<int16_t*>(_data);
double baseCycle = 2.0*M_PI/(double)48000 * (double)550;
for (int32_t iii=0; iii<_nbChunk; iii++) {
for (int32_t jjj=0; jjj<_map.size(); jjj++) {
data[_map.size()*iii+jjj] = cos(m_phase) * 30000;
}
m_phase += baseCycle;
if (m_phase >= 2*M_PI) {
m_phase -= 2*M_PI;
}
}
}
void run() {
m_interface->start();
// wait 2 second ...
usleep(2000000);
m_interface->stop();
}
};
TEST(TestALL, testOutputCallBack) {
std11::shared_ptr<river::Manager> manager;
manager = river::Manager::create("testApplication");
APPL_INFO("test output (callback mode)");
std11::shared_ptr<testOutCallback> process = std11::make_shared<testOutCallback>(manager, "speaker");
process->run();
process.reset();
usleep(500000);
}
TEST(TestALL, testOutputCallBackPulse) {
std11::shared_ptr<river::Manager> manager;
manager = river::Manager::create("testApplication");
APPL_INFO("test output (callback mode)");
std11::shared_ptr<testOutCallback> process = std11::make_shared<testOutCallback>(manager, "speaker-pulse");
process->run();
process.reset();
usleep(500000);
}
TEST(TestALL, testOutputCallBackJack) {
std11::shared_ptr<river::Manager> manager;
manager = river::Manager::create("testApplication");
APPL_INFO("test output (callback mode)");
std11::shared_ptr<testOutCallback> process = std11::make_shared<testOutCallback>(manager, "speaker-jack");
process->run();
process.reset();
usleep(500000);
}
};
#undef __class__
#define __class__ nullptr
#endif

190
test/testPlaybackWrite.cpp Normal file
View File

@@ -0,0 +1,190 @@
/** @file
* @author Edouard DUPIN
* @copyright 2015, Edouard DUPIN, all right reserved
* @license MPL v2.0 (see license file)
*/
#include <test-debug/debug.hpp>
#include <audio/river/river.hpp>
#include <audio/river/Manager.hpp>
#include <audio/river/Interface.hpp>
#include <etest/etest.hpp>
#include <etk/etk.hpp>
extern "C" {
#include <math.h>
}
#include <ethread/Thread.hpp>
#include <ethread/tools.hpp>
namespace river_test_playback_write {
static const etk::String configurationRiver =
"{\n"
" speaker:{\n"
" io:'output',\n"
" map-on:{\n"
" interface:'auto',\n"
" name:'default',\n"
" },\n"
" frequency:0,\n"
" channel-map:['front-left', 'front-right'],\n"
" type:'auto',\n"
" nb-chunk:1024,\n"
" volume-name:'MASTER'\n"
" }\n"
"}\n";
class testOutWrite {
public:
etk::Vector<audio::channel> m_channelMap;
ememory::SharedPtr<audio::river::Manager> m_manager;
ememory::SharedPtr<audio::river::Interface> m_interface;
public:
testOutWrite(ememory::SharedPtr<audio::river::Manager> _manager) :
m_manager(_manager) {
//Set stereo output:
m_channelMap.pushBack(audio::channel_frontLeft);
m_channelMap.pushBack(audio::channel_frontRight);
m_interface = m_manager->createOutput(48000,
m_channelMap,
audio::format_int16,
"speaker");
if(m_interface == null) {
TEST_ERROR("null interface");
return;
}
m_interface->setReadwrite();
}
void run() {
if(m_interface == null) {
TEST_ERROR("null interface");
return;
}
double phase=0;
etk::Vector<int16_t> data;
data.resize(1024*m_channelMap.size());
double baseCycle = 2.0*M_PI/48000.0 * 440.0;
// start fill buffer
for (int32_t kkk=0; kkk<10; ++kkk) {
for (int32_t iii=0; iii<data.size()/m_channelMap.size(); iii++) {
for (int32_t jjj=0; jjj<m_channelMap.size(); jjj++) {
data[m_channelMap.size()*iii+jjj] = cos(phase) * 30000.0;
}
phase += baseCycle;
if (phase >= 2*M_PI) {
phase -= 2*M_PI;
}
}
m_interface->write(&data[0], data.size()/m_channelMap.size());
}
m_interface->start();
for (int32_t kkk=0; kkk<100; ++kkk) {
for (int32_t iii=0; iii<data.size()/m_channelMap.size(); iii++) {
for (int32_t jjj=0; jjj<m_channelMap.size(); jjj++) {
data[m_channelMap.size()*iii+jjj] = cos(phase) * 30000.0;
}
phase += baseCycle;
if (phase >= 2*M_PI) {
phase -= 2*M_PI;
}
}
m_interface->write(&data[0], data.size()/m_channelMap.size());
// TODO : Add a function to get number of time we need to wait enought time ...
ethread::sleepMilliSeconds((15));
}
m_interface->stop();
}
};
TEST(TestALL, testOutputWrite) {
audio::river::initString(configurationRiver);
ememory::SharedPtr<audio::river::Manager> manager;
manager = audio::river::Manager::create("testApplication");
TEST_INFO("test output (write mode)");
ememory::SharedPtr<testOutWrite> process = ememory::makeShared<testOutWrite>(manager);
process->run();
process.reset();
ethread::sleepMilliSeconds((500));
audio::river::unInit();
}
class testOutWriteCallback {
public:
ememory::SharedPtr<audio::river::Manager> m_manager;
ememory::SharedPtr<audio::river::Interface> m_interface;
double m_phase;
public:
testOutWriteCallback(ememory::SharedPtr<audio::river::Manager> _manager) :
m_manager(_manager),
m_phase(0) {
etk::Vector<audio::channel> channelMap;
//Set stereo output:
channelMap.pushBack(audio::channel_frontLeft);
channelMap.pushBack(audio::channel_frontRight);
m_interface = m_manager->createOutput(48000,
channelMap,
audio::format_int16,
"speaker");
if(m_interface == null) {
TEST_ERROR("null interface");
return;
}
m_interface->setReadwrite();
m_interface->setWriteCallback([=](const audio::Time& _time,
size_t _nbChunk,
enum audio::format _format,
uint32_t _frequency,
const etk::Vector<audio::channel>& _map) {
onDataNeeded(_time, _nbChunk, _format, _frequency, _map);
});
}
void onDataNeeded(const audio::Time& _time,
size_t _nbChunk,
enum audio::format _format,
uint32_t _frequency,
const etk::Vector<audio::channel>& _map) {
if (_format != audio::format_int16) {
TEST_ERROR("call wrong type ... (need int16_t)");
}
etk::Vector<int16_t> data;
data.resize(1024*_map.size());
double baseCycle = 2.0*M_PI/48000.0 * 440.0;
// start fill buffer
for (int32_t iii=0; iii<data.size()/_map.size(); iii++) {
for (int32_t jjj=0; jjj<_map.size(); jjj++) {
data[_map.size()*iii+jjj] = cos(m_phase) * 30000.0;
}
m_phase += baseCycle;
if (m_phase >= 2*M_PI) {
m_phase -= 2*M_PI;
}
}
m_interface->write(&data[0], data.size()/_map.size());
}
void run() {
if(m_interface == null) {
TEST_ERROR("null interface");
return;
}
m_interface->start();
ethread::sleepMilliSeconds(1000*(1));
m_interface->stop();
}
};
TEST(TestALL, testOutputWriteWithCallback) {
audio::river::initString(configurationRiver);
ememory::SharedPtr<audio::river::Manager> manager;
manager = audio::river::Manager::create("testApplication");
TEST_INFO("test output (write with callback event mode)");
ememory::SharedPtr<testOutWriteCallback> process = ememory::makeShared<testOutWriteCallback>(manager);
process->run();
process.reset();
ethread::sleepMilliSeconds((500));
audio::river::unInit();
}
};

View File

@@ -1,153 +0,0 @@
/** @file
* @author Edouard DUPIN
* @copyright 2015, Edouard DUPIN, all right reserved
* @license APACHE v2.0 (see license file)
*/
#ifndef __RIVER_TEST_PLAYBACK_WRITE_H__
#define __RIVER_TEST_PLAYBACK_WRITE_H__
#undef __class__
#define __class__ "test_playback_write"
namespace river_test_playback_write {
class testOutWrite {
private:
std::vector<audio::channel> m_channelMap;
std11::shared_ptr<river::Manager> m_manager;
std11::shared_ptr<river::Interface> m_interface;
public:
testOutWrite(std11::shared_ptr<river::Manager> _manager) :
m_manager(_manager) {
//Set stereo output:
m_channelMap.push_back(audio::channel_frontLeft);
m_channelMap.push_back(audio::channel_frontRight);
m_interface = m_manager->createOutput(48000,
m_channelMap,
audio::format_int16,
"speaker",
"WriteMode");
m_interface->setReadwrite();
}
void run() {
double phase=0;
std::vector<int16_t> data;
data.resize(1024*m_channelMap.size());
double baseCycle = 2.0*M_PI/48000.0 * 440.0;
// start fill buffer
for (int32_t kkk=0; kkk<10; ++kkk) {
for (int32_t iii=0; iii<data.size()/m_channelMap.size(); iii++) {
for (int32_t jjj=0; jjj<m_channelMap.size(); jjj++) {
data[m_channelMap.size()*iii+jjj] = cos(phase) * 30000.0;
}
phase += baseCycle;
if (phase >= 2*M_PI) {
phase -= 2*M_PI;
}
}
m_interface->write(&data[0], data.size()/m_channelMap.size());
}
m_interface->start();
for (int32_t kkk=0; kkk<100; ++kkk) {
for (int32_t iii=0; iii<data.size()/m_channelMap.size(); iii++) {
for (int32_t jjj=0; jjj<m_channelMap.size(); jjj++) {
data[m_channelMap.size()*iii+jjj] = cos(phase) * 30000.0;
}
phase += baseCycle;
if (phase >= 2*M_PI) {
phase -= 2*M_PI;
}
}
m_interface->write(&data[0], data.size()/m_channelMap.size());
// TODO : Add a function to get number of time we need to wait enought time ...
usleep(15000);
}
m_interface->stop();
}
};
TEST(TestALL, testOutputWrite) {
std11::shared_ptr<river::Manager> manager;
manager = river::Manager::create("testApplication");
APPL_INFO("test output (write mode)");
std11::shared_ptr<testOutWrite> process = std11::make_shared<testOutWrite>(manager);
process->run();
process.reset();
usleep(500000);
}
class testOutWriteCallback {
private:
std11::shared_ptr<river::Manager> m_manager;
std11::shared_ptr<river::Interface> m_interface;
double m_phase;
public:
testOutWriteCallback(std11::shared_ptr<river::Manager> _manager) :
m_manager(_manager),
m_phase(0) {
std::vector<audio::channel> channelMap;
//Set stereo output:
channelMap.push_back(audio::channel_frontLeft);
channelMap.push_back(audio::channel_frontRight);
m_interface = m_manager->createOutput(48000,
channelMap,
audio::format_int16,
"speaker",
"WriteMode+Callback");
m_interface->setReadwrite();
m_interface->setWriteCallback(std11::bind(&testOutWriteCallback::onDataNeeded,
this,
std11::placeholders::_1,
std11::placeholders::_2,
std11::placeholders::_3,
std11::placeholders::_4,
std11::placeholders::_5));
}
void onDataNeeded(const std11::chrono::system_clock::time_point& _time,
size_t _nbChunk,
enum audio::format _format,
uint32_t _frequency,
const std::vector<audio::channel>& _map) {
if (_format != audio::format_int16) {
APPL_ERROR("call wrong type ... (need int16_t)");
}
std::vector<int16_t> data;
data.resize(1024*_map.size());
double baseCycle = 2.0*M_PI/48000.0 * 440.0;
// start fill buffer
for (int32_t iii=0; iii<data.size()/_map.size(); iii++) {
for (int32_t jjj=0; jjj<_map.size(); jjj++) {
data[_map.size()*iii+jjj] = cos(m_phase) * 30000.0;
}
m_phase += baseCycle;
if (m_phase >= 2*M_PI) {
m_phase -= 2*M_PI;
}
}
m_interface->write(&data[0], data.size()/_map.size());
}
void run() {
m_interface->start();
usleep(1000000);
m_interface->stop();
}
};
TEST(TestALL, testOutputWriteWithCallback) {
std11::shared_ptr<river::Manager> manager;
manager = river::Manager::create("testApplication");
APPL_INFO("test output (write with callback event mode)");
std11::shared_ptr<testOutWriteCallback> process = std11::make_shared<testOutWriteCallback>(manager);
process->run();
process.reset();
usleep(500000);
}
};
#undef __class__
#define __class__ nullptr
#endif

105
test/testRecordCallback.cpp Normal file
View File

@@ -0,0 +1,105 @@
/** @file
* @author Edouard DUPIN
* @copyright 2015, Edouard DUPIN, all right reserved
* @license MPL v2.0 (see license file)
*/
#include <test-debug/debug.hpp>
#include <audio/river/river.hpp>
#include <audio/river/Manager.hpp>
#include <audio/river/Interface.hpp>
#include <etest/etest.hpp>
#include <etk/etk.hpp>
extern "C" {
#include <math.h>
}
#include <ethread/Thread.hpp>
#include <ethread/tools.hpp>
namespace river_test_record_callback {
static const etk::String configurationRiver =
"{\n"
" microphone:{\n"
" io:'input',\n"
" map-on:{\n"
" interface:'auto',\n"
" name:'default',\n"
" },\n"
" frequency:0,\n"
" channel-map:['front-left', 'front-right'],\n"
" type:'auto',\n"
" nb-chunk:1024\n"
" }\n"
"}\n";
class testInCallback {
public:
ememory::SharedPtr<audio::river::Manager> m_manager;
ememory::SharedPtr<audio::river::Interface> m_interface;
public:
testInCallback(ememory::SharedPtr<audio::river::Manager> _manager, const etk::String& _input="microphone") :
m_manager(_manager) {
//Set stereo output:
etk::Vector<audio::channel> channelMap;
m_interface = m_manager->createInput(48000,
channelMap,
audio::format_int16,
_input);
if(m_interface == null) {
TEST_ERROR("null interface");
return;
}
// set callback mode ...
m_interface->setInputCallback([=](const void* _data,
const audio::Time& _time,
size_t _nbChunk,
enum audio::format _format,
uint32_t _frequency,
const etk::Vector<audio::channel>& _map) {
onDataReceived(_data, _time, _nbChunk, _format, _frequency, _map);
});
}
void onDataReceived(const void* _data,
const audio::Time& _time,
size_t _nbChunk,
enum audio::format _format,
uint32_t _frequency,
const etk::Vector<audio::channel>& _map) {
if (_format != audio::format_int16) {
TEST_ERROR("call wrong type ... (need int16_t)");
}
//TEST_SAVE_FILE_MACRO(int16_t, "REC_INPUT.raw", _data, _nbChunk * _map.size());
const int16_t* data = static_cast<const int16_t*>(_data);
int64_t value = 0;
for (size_t iii=0; iii<_nbChunk*_map.size(); ++iii) {
value += etk::abs(data[iii]);
}
value /= (_nbChunk*_map.size());
TEST_INFO("Get data ... average=" << int32_t(value));
}
void run() {
if(m_interface == null) {
TEST_ERROR("null interface");
return;
}
m_interface->start();
// wait 2 second ...
ethread::sleepMilliSeconds(1000*(20));
m_interface->stop();
}
};
TEST(TestALL, testInputCallBack) {
audio::river::initString(configurationRiver);
ememory::SharedPtr<audio::river::Manager> manager;
manager = audio::river::Manager::create("testApplication");
TEST_INFO("test input (callback mode)");
ememory::SharedPtr<testInCallback> process = ememory::makeShared<testInCallback>(manager);
process->run();
process.reset();
ethread::sleepMilliSeconds((500));
audio::river::unInit();
}
};

View File

@@ -1,82 +0,0 @@
/** @file
* @author Edouard DUPIN
* @copyright 2015, Edouard DUPIN, all right reserved
* @license APACHE v2.0 (see license file)
*/
#ifndef __RIVER_TEST_RECORD_CALLBACK_H__
#define __RIVER_TEST_RECORD_CALLBACK_H__
#undef __class__
#define __class__ "test_record_callback"
namespace river_test_record_callback {
class testInCallback {
private:
std11::shared_ptr<river::Manager> m_manager;
std11::shared_ptr<river::Interface> m_interface;
public:
testInCallback(std11::shared_ptr<river::Manager> _manager, const std::string& _input="microphone") :
m_manager(_manager) {
//Set stereo output:
std::vector<audio::channel> channelMap;
channelMap.push_back(audio::channel_frontLeft);
channelMap.push_back(audio::channel_frontRight);
m_interface = m_manager->createInput(48000,
channelMap,
audio::format_int16,
_input,
"WriteModeCallback");
// set callback mode ...
m_interface->setInputCallback(std11::bind(&testInCallback::onDataReceived,
this,
std11::placeholders::_1,
std11::placeholders::_2,
std11::placeholders::_3,
std11::placeholders::_4,
std11::placeholders::_5,
std11::placeholders::_6));
}
void onDataReceived(const void* _data,
const std11::chrono::system_clock::time_point& _time,
size_t _nbChunk,
enum audio::format _format,
uint32_t _frequency,
const std::vector<audio::channel>& _map) {
if (_format != audio::format_int16) {
APPL_ERROR("call wrong type ... (need int16_t)");
}
const int16_t* data = static_cast<const int16_t*>(_data);
int64_t value = 0;
for (size_t iii=0; iii<_nbChunk*_map.size(); ++iii) {
value += std::abs(data[iii]);
}
value /= (_nbChunk*_map.size());
APPL_INFO("Get data ... average=" << int32_t(value));
}
void run() {
m_interface->start();
// wait 2 second ...
usleep(2000000);
m_interface->stop();
}
};
TEST(TestALL, testInputCallBack) {
std11::shared_ptr<river::Manager> manager;
manager = river::Manager::create("testApplication");
APPL_INFO("test input (callback mode)");
std11::shared_ptr<testInCallback> process = std11::make_shared<testInCallback>(manager);
process->run();
process.reset();
usleep(500000);
}
};
#undef __class__
#define __class__ nullptr
#endif

22
test/testRecordRead.cpp Normal file
View File

@@ -0,0 +1,22 @@
/** @file
* @author Edouard DUPIN
* @copyright 2015, Edouard DUPIN, all right reserved
* @license MPL v2.0 (see license file)
*/
#include <test-debug/debug.hpp>
#include <audio/river/river.hpp>
#include <audio/river/Manager.hpp>
#include <audio/river/Interface.hpp>
#include <etest/etest.hpp>
#include <etk/etk.hpp>
extern "C" {
#include <math.h>
}
#include <ethread/Thread.hpp>
namespace river_test_record_read {
};

View File

@@ -1,20 +0,0 @@
/** @file
* @author Edouard DUPIN
* @copyright 2015, Edouard DUPIN, all right reserved
* @license APACHE v2.0 (see license file)
*/
#ifndef __RIVER_TEST_RECORD_READ_H__
#define __RIVER_TEST_RECORD_READ_H__
#undef __class__
#define __class__ "test_record_read"
namespace river_test_record_read {
};
#undef __class__
#define __class__ nullptr
#endif

145
test/testVolume.cpp Normal file
View File

@@ -0,0 +1,145 @@
/** @file
* @author Edouard DUPIN
* @copyright 2015, Edouard DUPIN, all right reserved
* @license MPL v2.0 (see license file)
*/
#include <test-debug/debug.hpp>
#include <audio/river/river.hpp>
#include <audio/river/Manager.hpp>
#include <audio/river/Interface.hpp>
#include <etest/etest.hpp>
#include <etk/etk.hpp>
extern "C" {
#include <math.h>
}
#include <ethread/Thread.hpp>
#include <ethread/tools.hpp>
namespace river_test_volume {
static const etk::String configurationRiver =
"{\n"
" speaker:{\n"
" io:'output',\n"
" map-on:{\n"
" interface:'auto',\n"
" name:'default',\n"
" },\n"
" frequency:0,\n"
" channel-map:['front-left', 'front-right'],\n"
" type:'auto',\n"
" nb-chunk:1024,\n"
" volume-name:'MASTER'\n"
" }\n"
"}\n";
class testCallbackVolume {
private:
ememory::SharedPtr<audio::river::Manager> m_manager;
ememory::SharedPtr<audio::river::Interface> m_interface;
double m_phase;
public:
testCallbackVolume(ememory::SharedPtr<audio::river::Manager> _manager) :
m_manager(_manager),
m_phase(0) {
//Set stereo output:
etk::Vector<audio::channel> channelMap;
channelMap.pushBack(audio::channel_frontLeft);
channelMap.pushBack(audio::channel_frontRight);
m_interface = m_manager->createOutput(48000,
channelMap,
audio::format_int16,
"speaker");
if(m_interface == null) {
TEST_ERROR("null interface");
return;
}
// set callback mode ...
m_interface->setOutputCallback([=](void* _data,
const audio::Time& _time,
size_t _nbChunk,
enum audio::format _format,
uint32_t _frequency,
const etk::Vector<audio::channel>& _map) {
onDataNeeded(_data, _time, _nbChunk, _format, _frequency, _map);
});
m_interface->addVolumeGroup("MEDIA");
m_interface->addVolumeGroup("FLOW");
}
void onDataNeeded(void* _data,
const audio::Time& _time,
size_t _nbChunk,
enum audio::format _format,
uint32_t _frequency,
const etk::Vector<audio::channel>& _map) {
int16_t* data = static_cast<int16_t*>(_data);
double baseCycle = 2.0*M_PI/(double)48000 * (double)550;
for (int32_t iii=0; iii<_nbChunk; iii++) {
for (int32_t jjj=0; jjj<_map.size(); jjj++) {
data[_map.size()*iii+jjj] = cos(m_phase) * 30000;
}
m_phase += baseCycle;
if (m_phase >= 2*M_PI) {
m_phase -= 2*M_PI;
}
}
}
void run() {
if(m_interface == null) {
TEST_ERROR("null interface");
return;
}
m_interface->start();
ethread::sleepMilliSeconds(1000*(1));
m_interface->setParameter("volume", "FLOW", "-3dB");
TEST_INFO(" get volume : " << m_interface->getParameter("volume", "FLOW") );
ethread::sleepMilliSeconds((500));
m_interface->setParameter("volume", "FLOW", "-6dB");
TEST_INFO(" get volume : " << m_interface->getParameter("volume", "FLOW") );
ethread::sleepMilliSeconds((500));
m_interface->setParameter("volume", "FLOW", "-9dB");
TEST_INFO(" get volume : " << m_interface->getParameter("volume", "FLOW") );
ethread::sleepMilliSeconds((500));
m_interface->setParameter("volume", "FLOW", "-12dB");
TEST_INFO(" get volume : " << m_interface->getParameter("volume", "FLOW") );
ethread::sleepMilliSeconds((500));
m_interface->setParameter("volume", "FLOW", "-3dB");
TEST_INFO(" get volume : " << m_interface->getParameter("volume", "FLOW") );
ethread::sleepMilliSeconds((500));
m_interface->setParameter("volume", "FLOW", "3dB");
TEST_INFO(" get volume : " << m_interface->getParameter("volume", "FLOW") );
ethread::sleepMilliSeconds((500));
m_interface->setParameter("volume", "FLOW", "6dB");
TEST_INFO(" get volume : " << m_interface->getParameter("volume", "FLOW") );
ethread::sleepMilliSeconds((500));
m_interface->setParameter("volume", "FLOW", "9dB");
TEST_INFO(" get volume : " << m_interface->getParameter("volume", "FLOW") );
ethread::sleepMilliSeconds((500));
m_interface->setParameter("volume", "FLOW", "0dB");
TEST_INFO(" get volume : " << m_interface->getParameter("volume", "FLOW") );
ethread::sleepMilliSeconds((500));
m_manager->setVolume("MASTER", -3.0f);
TEST_INFO("get volume MASTER: " << m_manager->getVolume("MASTER") );
ethread::sleepMilliSeconds((500));
m_manager->setVolume("MEDIA", -3.0f);
TEST_INFO("get volume MEDIA: " << m_manager->getVolume("MEDIA") );
ethread::sleepMilliSeconds(1000*(1));
m_interface->stop();
}
};
TEST(TestALL, testVolume) {
audio::river::initString(configurationRiver);
ememory::SharedPtr<audio::river::Manager> manager;
manager = audio::river::Manager::create("testApplication");
ememory::SharedPtr<testCallbackVolume> process = ememory::makeShared<testCallbackVolume>(manager);
process->run();
process.reset();
ethread::sleepMilliSeconds((500));
audio::river::unInit();
}
};

View File

@@ -1,117 +0,0 @@
/** @file
* @author Edouard DUPIN
* @copyright 2015, Edouard DUPIN, all right reserved
* @license APACHE v2.0 (see license file)
*/
#ifndef __RIVER_TEST_VOLUME_H__
#define __RIVER_TEST_VOLUME_H__
#undef __class__
#define __class__ "test_volume"
namespace river_test_volume {
class testCallbackVolume {
private:
std11::shared_ptr<river::Manager> m_manager;
std11::shared_ptr<river::Interface> m_interface;
double m_phase;
public:
testCallbackVolume(std11::shared_ptr<river::Manager> _manager) :
m_manager(_manager),
m_phase(0) {
//Set stereo output:
std::vector<audio::channel> channelMap;
channelMap.push_back(audio::channel_frontLeft);
channelMap.push_back(audio::channel_frontRight);
m_interface = m_manager->createOutput(48000,
channelMap,
audio::format_int16,
"speaker",
"WriteModeCallback");
// set callback mode ...
m_interface->setOutputCallback(std11::bind(&testCallbackVolume::onDataNeeded,
this,
std11::placeholders::_1,
std11::placeholders::_2,
std11::placeholders::_3,
std11::placeholders::_4,
std11::placeholders::_5,
std11::placeholders::_6));
m_interface->addVolumeGroup("MEDIA");
m_interface->addVolumeGroup("FLOW");
}
void onDataNeeded(void* _data,
const std11::chrono::system_clock::time_point& _time,
size_t _nbChunk,
enum audio::format _format,
uint32_t _frequency,
const std::vector<audio::channel>& _map) {
int16_t* data = static_cast<int16_t*>(_data);
double baseCycle = 2.0*M_PI/(double)48000 * (double)550;
for (int32_t iii=0; iii<_nbChunk; iii++) {
for (int32_t jjj=0; jjj<_map.size(); jjj++) {
data[_map.size()*iii+jjj] = cos(m_phase) * 30000;
}
m_phase += baseCycle;
if (m_phase >= 2*M_PI) {
m_phase -= 2*M_PI;
}
}
}
void run() {
m_interface->start();
usleep(1000000);
m_interface->setParameter("volume", "FLOW", "-3dB");
APPL_INFO(" get volume : " << m_interface->getParameter("volume", "FLOW") );
usleep(500000);
m_interface->setParameter("volume", "FLOW", "-6dB");
APPL_INFO(" get volume : " << m_interface->getParameter("volume", "FLOW") );
usleep(500000);
m_interface->setParameter("volume", "FLOW", "-9dB");
APPL_INFO(" get volume : " << m_interface->getParameter("volume", "FLOW") );
usleep(500000);
m_interface->setParameter("volume", "FLOW", "-12dB");
APPL_INFO(" get volume : " << m_interface->getParameter("volume", "FLOW") );
usleep(500000);
m_interface->setParameter("volume", "FLOW", "-3dB");
APPL_INFO(" get volume : " << m_interface->getParameter("volume", "FLOW") );
usleep(500000);
m_interface->setParameter("volume", "FLOW", "3dB");
APPL_INFO(" get volume : " << m_interface->getParameter("volume", "FLOW") );
usleep(500000);
m_interface->setParameter("volume", "FLOW", "6dB");
APPL_INFO(" get volume : " << m_interface->getParameter("volume", "FLOW") );
usleep(500000);
m_interface->setParameter("volume", "FLOW", "9dB");
APPL_INFO(" get volume : " << m_interface->getParameter("volume", "FLOW") );
usleep(500000);
m_interface->setParameter("volume", "FLOW", "0dB");
APPL_INFO(" get volume : " << m_interface->getParameter("volume", "FLOW") );
usleep(500000);
m_manager->setVolume("MASTER", -3.0f);
APPL_INFO("get volume MASTER: " << m_manager->getVolume("MASTER") );
usleep(500000);
m_manager->setVolume("MEDIA", -3.0f);
APPL_INFO("get volume MEDIA: " << m_manager->getVolume("MEDIA") );
usleep(1000000);
m_interface->stop();
}
};
TEST(TestALL, testVolume) {
std11::shared_ptr<river::Manager> manager;
manager = river::Manager::create("testApplication");
std11::shared_ptr<testCallbackVolume> process = std11::make_shared<testCallbackVolume>(manager);
process->run();
process.reset();
usleep(500000);
}
};
#undef __class__
#define __class__ nullptr
#endif

1
version.txt Normal file
View File

@@ -0,0 +1 @@
1.0.0-dev

View File

@@ -0,0 +1,29 @@
{
"type":"LIBRARY",
"group-id":"com.atria-soft",
"description":"audio specific widget",
"license":"MPL-2",
"license-file":"file://../LICENSE",
"maintainer":"file://../authors.txt",
"author":"file://../authors.txt",
"version":"file://../version.txt",
"code-quality":"MEDIUM",
"source": [
"audio/river/widget/TemporalViewer.cpp",
"audio/river/widget/debug.cpp"
],
"header": [
"audio/river/widget/TemporalViewer.hpp"
],
"compilation-version": {
"c++": 2017
},
"path":[
"."
],
"dependency": [
"ewol",
"audio-river"
]
}

View File

@@ -0,0 +1,157 @@
/** @file
* @author Edouard DUPIN
* @copyright 2011, Edouard DUPIN, all right reserved
* @license MPL v2.0 (see license file)
*/
#include <audio/river/widget/debug.hpp>
#include <audio/river/widget/TemporalViewer.hpp>
#include <etk/tool.hpp>
#include <ewol/object/Manager.hpp>
static const int32_t nbSecond = 3;
audio::river::widget::TemporalViewer::TemporalViewer() :
m_minVal(-1.0f),
m_maxVal(1.0f),
m_sampleRate(48000) {
addObjectType("audio::river::widget::TemporalViewer");
}
void audio::river::widget::TemporalViewer::init() {
ewol::Widget::init();
m_manager = audio::river::Manager::create("audio::river::widget::TemporalViewer");
m_data.resize(m_sampleRate*3, 0.0);
}
audio::river::widget::TemporalViewer::~TemporalViewer() {
}
void audio::river::widget::TemporalViewer::onDataReceived(const void* _data,
const audio::Time& _time,
size_t _nbChunk,
enum audio::format _format,
uint32_t _frequency,
const etk::Vector<audio::channel>& _map) {
ethread::UniqueLock lock(m_mutex);
if (_format != audio::format_float) {
ARW_ERROR("call wrong type ... (need int16_t)");
}
// get the curent power of the signal.
const float* data = static_cast<const float*>(_data);
for (size_t iii=0; iii<_nbChunk*_map.size(); ++iii) {
m_data.pushBack(data[iii]);
}
/*
if (m_data.size()>m_sampleRate*nbSecond*10) {
m_data.erase(m_data.begin(), m_data.begin()+(m_data.size()-m_sampleRate*nbSecond));
}
*/
//markToRedraw();
}
void audio::river::widget::TemporalViewer::recordToggle() {
ethread::UniqueLock lock(m_mutex);
if (m_interface == null) {
//Get the generic input:
etk::Vector<audio::channel> channel;
channel.pushBack(audio::channel_frontLeft);
m_interface = m_manager->createInput(m_sampleRate,
channel,
audio::format_float,
"microphone");
if(m_interface == null) {
ARW_ERROR("null interface");
return;
}
// set callback mode ...
m_interface->setInputCallback([&](const void* _data,
const audio::Time& _time,
size_t _nbChunk,
enum audio::format _format,
uint32_t _frequency,
const etk::Vector<audio::channel>& _map) {
onDataReceived(_data, _time, _nbChunk, _format, _frequency, _map);
});
// start the stream
m_interface->start();
m_PCH = getObjectManager().periodicCall.connect(this, &audio::river::widget::TemporalViewer::periodicCall);
} else {
m_interface->stop();
m_interface.reset();
m_PCH.disconnect();
}
}
void audio::river::widget::TemporalViewer::onDraw() {
m_draw.draw();
}
void audio::river::widget::TemporalViewer::onRegenerateDisplay() {
//!< Check if we really need to redraw the display, if not needed, we redraw the previous data ...
if (needRedraw() == false) {
return;
}
// remove previous data
m_draw.clear();
// set background
m_draw.setColor(etk::color::black);
m_draw.setPos(vec2(0,0));
m_draw.rectangleWidth(m_size);
ethread::UniqueLock lock(m_mutex);
if (m_data.size() == 0) {
return;
}
// create n section for display:
int32_t nbSlot = m_size.x();
int32_t sizeSlot = m_size.x()/nbSlot;
etk::Vector<float> list;
//ARW_INFO("nbSlot : " << nbSlot << " sizeSlot=" << sizeSlot << " m_size=" << m_size);
list.resize(nbSlot,0.0f);
int32_t step = m_sampleRate*nbSecond/nbSlot;
for (size_t kkk=0; kkk<m_sampleRate*nbSecond; ++kkk) {
int32_t id = kkk/step;
if (id < list.size()) {
if (kkk < m_data.size()) {
list[id] = etk::max(list[id],m_data[kkk]);
}
}
}
// set all the line:
m_draw.setColor(etk::color::white);
m_draw.setThickness(1);
float origin = m_size.y()*0.5f;
float ratioY = m_size.y() / (m_maxVal - m_minVal);
float baseX = 0;
for (size_t iii=1; iii<list.size(); ++iii) {
m_draw.setPos(vec2(iii*sizeSlot, origin - ratioY*list[iii]));
m_draw.rectangle(vec2((iii+1)*sizeSlot, origin + ratioY*list[iii]));
if ((iii+1)*sizeSlot > m_size.x()) {
ARW_ERROR("wrong display position");
}
}
}
void audio::river::widget::TemporalViewer::periodicCall(const ewol::event::Time& _event) {
ethread::UniqueLock lock(m_mutex);
int32_t nbSampleDelta = _event.getDeltaCall() * float(m_sampleRate);
if (m_data.size()>m_sampleRate*nbSecond) {
if (nbSampleDelta < m_data.size()) {
m_data.erase(m_data.begin(), m_data.begin()+nbSampleDelta);
} else {
m_data.erase(m_data.begin(), m_data.begin()+(m_data.size()-m_sampleRate*nbSecond));
}
}
markToRedraw();
}

View File

@@ -0,0 +1,65 @@
/** @file
* @author Edouard DUPIN
* @copyright 2011, Edouard DUPIN, all right reserved
* @license MPL v2.0 (see license file)
*/
#pragma once
#include <ewol/widget/Widget.hpp>
#include <ewol/compositing/Drawing.hpp>
#include <audio/river/river.hpp>
#include <audio/river/Manager.hpp>
#include <audio/river/Interface.hpp>
#include <ethread/Mutex.hpp>
namespace audio {
namespace river {
namespace widget {
class TemporalViewer : public ewol::Widget {
private:
mutable ethread::Mutex m_mutex;
private:
ewol::compositing::Drawing m_draw; //!< drawing instance
protected:
//! @brief constructor
TemporalViewer();
void init();
public:
DECLARE_WIDGET_FACTORY(TemporalViewer, "TemporalViewer");
//! @brief destructor
virtual ~TemporalViewer();
void recordToggle();
void generateToggle() {
// ...
}
private:
etk::Vector<float> m_data;
private:
float m_minVal; //!< display minimum value
float m_maxVal; //!< display maximum value
public: // herited function
virtual void onDraw();
virtual void onRegenerateDisplay();
protected:
esignal::Connection m_PCH; //!< Periodic Call Handle to remove it when needed
/**
* @brief Periodic call to update grapgic display
* @param[in] _event Time generic event
*/
virtual void periodicCall(const ewol::event::Time& _event);
private:
ememory::SharedPtr<audio::river::Manager> m_manager;
ememory::SharedPtr<audio::river::Interface> m_interface;
void onDataReceived(const void* _data,
const audio::Time& _time,
size_t _nbChunk,
enum audio::format _format,
uint32_t _frequency,
const etk::Vector<audio::channel>& _map);
int32_t m_sampleRate;
};
}
}
}

View File

@@ -0,0 +1,13 @@
/** @file
* @author Edouard DUPIN
* @copyright 2011, Edouard DUPIN, all right reserved
* @license MPL v2.0 (see license file)
*/
#include <audio/river/widget/debug.hpp>
int32_t audio::river::widget::getLogId() {
static int32_t g_val = elog::registerInstance("audio-river-widget");
return g_val;
}

View File

@@ -0,0 +1,43 @@
/** @file
* @author Edouard DUPIN
* @copyright 2011, Edouard DUPIN, all right reserved
* @license MPL v2.0 (see license file)
*/
#pragma once
#include <elog/log.hpp>
namespace audio {
namespace river {
namespace widget {
int32_t getLogId();
}
}
}
#define ARW_BASE(info,data) ELOG_BASE(audio::river::widget::getLogId(),info,data)
#define ARW_PRINT(data) ARW_BASE(-1, data)
#define ARW_CRITICAL(data) ARW_BASE(1, data)
#define ARW_ERROR(data) ARW_BASE(2, data)
#define ARW_WARNING(data) ARW_BASE(3, data)
#ifdef DEBUG
#define ARW_INFO(data) ARW_BASE(4, data)
#define ARW_DEBUG(data) ARW_BASE(5, data)
#define ARW_VERBOSE(data) ARW_BASE(6, data)
#define ARW_TODO(data) ARW_BASE(4, "TODO : " << data)
#else
#define ARW_INFO(data) do { } while(false)
#define ARW_DEBUG(data) do { } while(false)
#define ARW_VERBOSE(data) do { } while(false)
#define ARW_TODO(data) do { } while(false)
#endif
#define ARW_ASSERT(cond,data) \
do { \
if (!(cond)) { \
ARW_CRITICAL(data); \
assert(!#cond); \
} \
} while (0)